收紧story与custom world迁移能力边界
This commit is contained in:
@@ -14,6 +14,7 @@
|
|||||||
- [RUNTIME_STORY_TO_STDB_PHASE1_TRANSPORT_ABSTRACTION_2026-04-20.md](./RUNTIME_STORY_TO_STDB_PHASE1_TRANSPORT_ABSTRACTION_2026-04-20.md):把 `runtimeStoryService` 改成可替换 transport,为后续 STDB provider 接入预留稳定边界。
|
- [RUNTIME_STORY_TO_STDB_PHASE1_TRANSPORT_ABSTRACTION_2026-04-20.md](./RUNTIME_STORY_TO_STDB_PHASE1_TRANSPORT_ABSTRACTION_2026-04-20.md):把 `runtimeStoryService` 改成可替换 transport,为后续 STDB provider 接入预留稳定边界。
|
||||||
- [RUNTIME_STORY_TO_STDB_PHASE2_CONTRACT_DESIGN_2026-04-20.md](./RUNTIME_STORY_TO_STDB_PHASE2_CONTRACT_DESIGN_2026-04-20.md):梳理 runtime story 从 Express 迁到 STDB 所需的聚合 view、procedure、mapper 与前端 provider 设计。
|
- [RUNTIME_STORY_TO_STDB_PHASE2_CONTRACT_DESIGN_2026-04-20.md](./RUNTIME_STORY_TO_STDB_PHASE2_CONTRACT_DESIGN_2026-04-20.md):梳理 runtime story 从 Express 迁到 STDB 所需的聚合 view、procedure、mapper 与前端 provider 设计。
|
||||||
- [RUNTIME_STORY_TO_STDB_PHASE2A_COMPAT_BRIDGE_2026-04-20.md](./RUNTIME_STORY_TO_STDB_PHASE2A_COMPAT_BRIDGE_2026-04-20.md):确认 runtime story 当前 STDB/Express 快照真源分裂,并补一层只改 story 边界的 STDB 兼容桥。
|
- [RUNTIME_STORY_TO_STDB_PHASE2A_COMPAT_BRIDGE_2026-04-20.md](./RUNTIME_STORY_TO_STDB_PHASE2A_COMPAT_BRIDGE_2026-04-20.md):确认 runtime story 当前 STDB/Express 快照真源分裂,并补一层只改 story 边界的 STDB 兼容桥。
|
||||||
|
- [STORY_WORLD_TO_STDB_CAPABILITY_FIRST_PLAN_2026-04-20.md](./STORY_WORLD_TO_STDB_CAPABILITY_FIRST_PLAN_2026-04-20.md):明确 story / custom world 后续迁移顺序应先拆 capability/provider,再逐段迁移纯逻辑,避免整块硬搬。
|
||||||
- [TASK_AUTO_COMMIT_WORKFLOW_2026-04-20.md](./TASK_AUTO_COMMIT_WORKFLOW_2026-04-20.md):任务完成后按文件边界自动提交的脚本与协作约定。
|
- [TASK_AUTO_COMMIT_WORKFLOW_2026-04-20.md](./TASK_AUTO_COMMIT_WORKFLOW_2026-04-20.md):任务完成后按文件边界自动提交的脚本与协作约定。
|
||||||
- [NODE_DEV_STARTUP_HOTFIX_2026-04-20.md](./NODE_DEV_STARTUP_HOTFIX_2026-04-20.md):`npm run dev` 启动失败的热修记录、根因与验证结果。
|
- [NODE_DEV_STARTUP_HOTFIX_2026-04-20.md](./NODE_DEV_STARTUP_HOTFIX_2026-04-20.md):`npm run dev` 启动失败的热修记录、根因与验证结果。
|
||||||
- [NODE_SERVER_KNOWLEDGE_GRAPH_2026-04-08.md](./NODE_SERVER_KNOWLEDGE_GRAPH_2026-04-08.md):当前 Node 运行时后端的技术栈、入口、鉴权、存储与接口知识图谱。
|
- [NODE_SERVER_KNOWLEDGE_GRAPH_2026-04-08.md](./NODE_SERVER_KNOWLEDGE_GRAPH_2026-04-08.md):当前 Node 运行时后端的技术栈、入口、鉴权、存储与接口知识图谱。
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
# Story / World 向 STDB 迁移:能力先迁、逻辑后搬实施方案(2026-04-20)
|
||||||
|
|
||||||
|
更新时间:`2026-04-20`
|
||||||
|
|
||||||
|
## 1. 为什么先迁能力,不先整块搬逻辑
|
||||||
|
|
||||||
|
当前 `runtime story` 和 `custom world` 都不是“只差把逻辑换个地方跑”。
|
||||||
|
|
||||||
|
真实问题有两类:
|
||||||
|
|
||||||
|
1. 依赖面过大,很多 service 直接吃 `RuntimeRepositoryPort` 这类大接口
|
||||||
|
2. `LLM / SSE / HTTP request / 账号鉴权 / snapshot/session 存取` 仍然缠在一起
|
||||||
|
|
||||||
|
如果在这个阶段直接把整块逻辑搬去 STDB,会出现两种风险:
|
||||||
|
|
||||||
|
1. 把 Express 耦合一并搬过去,结果不是迁移而是复制
|
||||||
|
2. 在 STDB 里同时承接“确定性规则”和“非确定性外部能力”,边界会继续变脏
|
||||||
|
|
||||||
|
因此后续迁移顺序固定为:
|
||||||
|
|
||||||
|
1. 先拆能力口子
|
||||||
|
2. 再给能力口子补 STDB provider
|
||||||
|
3. 最后再搬只依赖这些能力口子的纯逻辑
|
||||||
|
|
||||||
|
## 2. 迁移分层
|
||||||
|
|
||||||
|
### 2.1 可以先收进 STDB 的能力
|
||||||
|
|
||||||
|
这类能力适合先做成 capability/provider:
|
||||||
|
|
||||||
|
1. runtime snapshot 读取与写回
|
||||||
|
2. custom world session 读取与写回
|
||||||
|
3. custom world profile / works 聚合读取
|
||||||
|
4. runtime story state 聚合读取
|
||||||
|
5. runtime story action 聚合写入结果
|
||||||
|
|
||||||
|
它们的共同特点:
|
||||||
|
|
||||||
|
1. 以数据真源为主
|
||||||
|
2. 可以明确建模为 table / view / procedure
|
||||||
|
3. 前后端 contract 可以稳定冻结
|
||||||
|
|
||||||
|
### 2.2 不能直接塞进 reducer 的能力
|
||||||
|
|
||||||
|
以下能力不应和纯规则一起直接塞进 STDB reducer:
|
||||||
|
|
||||||
|
1. LLM 调用
|
||||||
|
2. SSE 流式输出
|
||||||
|
3. 图片 / 资产生成
|
||||||
|
4. 长任务轮询与异步编排
|
||||||
|
5. 旧 HTTP 鉴权链路
|
||||||
|
|
||||||
|
这些能力应该保留在 Express 或 procedure / orchestrator 边界,直到它们被进一步拆清。
|
||||||
|
|
||||||
|
## 3. 当前代码收口原则
|
||||||
|
|
||||||
|
这一轮先不扩大兼容桥,而是先把依赖面收紧:
|
||||||
|
|
||||||
|
1. `storyActionService` 只依赖 `RuntimeStoryCapability`
|
||||||
|
2. `CustomWorldSessionStore` 只依赖 `CustomWorldSessionCapability`
|
||||||
|
3. `CustomWorldAgentSessionStore` 只依赖 `CustomWorldSessionCapability`
|
||||||
|
4. `listCustomWorldWorkSummaries` 只依赖 `CustomWorldWorkSummaryCapability`
|
||||||
|
|
||||||
|
这一步的意义不是“已经迁完 STDB”,而是:
|
||||||
|
|
||||||
|
1. 后续替换 provider 时,不再需要动整块 `RuntimeRepositoryPort`
|
||||||
|
2. 测试 stub 会被迫只实现真实需要的能力
|
||||||
|
3. 可以更准确地区分“真源能力迁移”和“业务逻辑迁移”
|
||||||
|
|
||||||
|
## 4. 后续实施顺序
|
||||||
|
|
||||||
|
### 4.1 Story
|
||||||
|
|
||||||
|
1. 继续收紧 `runtime story` 所需 capability
|
||||||
|
2. 为 `get state / resolve action` 设计正式 STDB provider contract
|
||||||
|
3. 让 `storyActionService` 改为吃 provider,而不是直接认具体仓储
|
||||||
|
4. 再把可确定性的结算逻辑逐段迁入 STDB
|
||||||
|
|
||||||
|
### 4.2 Custom World
|
||||||
|
|
||||||
|
1. 先把 `session / works / profile` 各自 capability 固化
|
||||||
|
2. 为 `custom world session` 提供 STDB provider
|
||||||
|
3. 为 `agent session` 的会话存取提供 STDB provider
|
||||||
|
4. 保留 `LLM / asset / stream` 在 orchestrator 边界
|
||||||
|
5. 等会话和作品真源稳定后,再搬纯状态推导逻辑
|
||||||
|
|
||||||
|
## 5. 本轮落地点
|
||||||
|
|
||||||
|
本轮只做一件事:
|
||||||
|
|
||||||
|
1. 把 `story/custom world` 的 service 依赖从大仓储接口改成最小 capability
|
||||||
|
|
||||||
|
本轮不做:
|
||||||
|
|
||||||
|
1. 新增更大范围兼容桥
|
||||||
|
2. 直接把 `custom world agent` 整块搬进 STDB
|
||||||
|
3. 把 LLM / stream / asset orchestration 一起迁移
|
||||||
|
|
||||||
|
## 6. 验收标准
|
||||||
|
|
||||||
|
1. `server-node` 编译通过
|
||||||
|
2. 相关测试 stub 不再依赖完整 `RuntimeRepositoryPort`
|
||||||
|
3. 文档明确固定“能力先迁、逻辑后搬”的顺序
|
||||||
@@ -6,11 +6,9 @@ import type {
|
|||||||
RuntimeStoryPatch,
|
RuntimeStoryPatch,
|
||||||
} from '../../../../packages/shared/src/contracts/story.js';
|
} from '../../../../packages/shared/src/contracts/story.js';
|
||||||
import { conflict, invalidRequest } from '../../errors.js';
|
import { conflict, invalidRequest } from '../../errors.js';
|
||||||
import type {
|
import type { SavedSnapshot } from '../../repositories/runtimeRepository.js';
|
||||||
RuntimeRepositoryPort,
|
|
||||||
SavedSnapshot,
|
|
||||||
} from '../../repositories/runtimeRepository.js';
|
|
||||||
import type { UpstreamLlmClient } from '../../services/llmClient.js';
|
import type { UpstreamLlmClient } from '../../services/llmClient.js';
|
||||||
|
import type { RuntimeStoryCapability } from '../../services/runtimeCapabilities.js';
|
||||||
import {
|
import {
|
||||||
buildStrictNpcChatDialoguePrompt,
|
buildStrictNpcChatDialoguePrompt,
|
||||||
NPC_CHAT_DIALOGUE_STRICT_SYSTEM_PROMPT,
|
NPC_CHAT_DIALOGUE_STRICT_SYSTEM_PROMPT,
|
||||||
@@ -80,10 +78,7 @@ type GeneratedStoryPayload = {
|
|||||||
savedCurrentStory: JsonRecord;
|
savedCurrentStory: JsonRecord;
|
||||||
};
|
};
|
||||||
|
|
||||||
type RuntimeSnapshotRepositoryPort = Pick<
|
type RuntimeSnapshotRepositoryPort = RuntimeStoryCapability;
|
||||||
RuntimeRepositoryPort,
|
|
||||||
'getSnapshot' | 'putSnapshot'
|
|
||||||
>;
|
|
||||||
|
|
||||||
const CONTINUE_ADVENTURE_OPTION = {
|
const CONTINUE_ADVENTURE_OPTION = {
|
||||||
functionId: 'story_continue_adventure',
|
functionId: 'story_continue_adventure',
|
||||||
|
|||||||
@@ -6,18 +6,15 @@ import type {
|
|||||||
} from '../../../../src/spacetime/generated/types.ts';
|
} from '../../../../src/spacetime/generated/types.ts';
|
||||||
import { conflict, unauthorized } from '../../errors.js';
|
import { conflict, unauthorized } from '../../errors.js';
|
||||||
import type {
|
import type {
|
||||||
RuntimeRepositoryPort,
|
|
||||||
SavedSnapshot,
|
SavedSnapshot,
|
||||||
} from '../../repositories/runtimeRepository.js';
|
} from '../../repositories/runtimeRepository.js';
|
||||||
import type { AppConfig } from '../../config.js';
|
import type { AppConfig } from '../../config.js';
|
||||||
import { hydrateSavedSnapshot } from '../runtime/runtimeSnapshotHydration.js';
|
import { hydrateSavedSnapshot } from '../runtime/runtimeSnapshotHydration.js';
|
||||||
|
import type { RuntimeStoryCapability } from '../../services/runtimeCapabilities.js';
|
||||||
|
|
||||||
const STORY_STDB_AUTH_HEADER = 'x-genarrative-runtime-story-auth';
|
const STORY_STDB_AUTH_HEADER = 'x-genarrative-runtime-story-auth';
|
||||||
|
|
||||||
type RuntimeSnapshotRepositoryPort = Pick<
|
type RuntimeSnapshotRepositoryPort = RuntimeStoryCapability;
|
||||||
RuntimeRepositoryPort,
|
|
||||||
'getSnapshot' | 'putSnapshot'
|
|
||||||
>;
|
|
||||||
|
|
||||||
type StoryStdbBridgeContext = {
|
type StoryStdbBridgeContext = {
|
||||||
config: AppConfig;
|
config: AppConfig;
|
||||||
@@ -160,7 +157,7 @@ export async function authenticateRuntimeStoryViaSpacetime(
|
|||||||
|
|
||||||
export function createRuntimeStorySnapshotRepository(params: {
|
export function createRuntimeStorySnapshotRepository(params: {
|
||||||
request: Request;
|
request: Request;
|
||||||
runtimeRepository: RuntimeRepositoryPort;
|
runtimeRepository: RuntimeStoryCapability;
|
||||||
config: AppConfig;
|
config: AppConfig;
|
||||||
}): RuntimeSnapshotRepositoryPort {
|
}): RuntimeSnapshotRepositoryPort {
|
||||||
if (params.request.runtimeStoryAuthMode !== 'spacetime') {
|
if (params.request.runtimeStoryAuthMode !== 'spacetime') {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import assert from 'node:assert/strict';
|
|||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
|
|
||||||
import type { CustomWorldSessionRecord } from '../../../packages/shared/src/contracts/runtime.js';
|
import type { CustomWorldSessionRecord } from '../../../packages/shared/src/contracts/runtime.js';
|
||||||
import type { RuntimeRepositoryPort } from '../repositories/runtimeRepository.js';
|
|
||||||
import {
|
import {
|
||||||
buildPendingClarifications,
|
buildPendingClarifications,
|
||||||
evaluateCreatorIntentReadiness,
|
evaluateCreatorIntentReadiness,
|
||||||
@@ -15,8 +14,13 @@ import { CustomWorldAgentOrchestrator } from './customWorldAgentOrchestrator.js'
|
|||||||
import { CustomWorldAgentSessionStore } from './customWorldAgentSessionStore.js';
|
import { CustomWorldAgentSessionStore } from './customWorldAgentSessionStore.js';
|
||||||
import { createTestCustomWorldAgentSingleTurnLlmClient } from './customWorldAgentTestHelpers.js';
|
import { createTestCustomWorldAgentSingleTurnLlmClient } from './customWorldAgentTestHelpers.js';
|
||||||
import { listCustomWorldWorkSummaries } from './customWorldWorkSummaryService.js';
|
import { listCustomWorldWorkSummaries } from './customWorldWorkSummaryService.js';
|
||||||
|
import type {
|
||||||
|
CustomWorldSessionCapability,
|
||||||
|
CustomWorldWorkSummaryCapability,
|
||||||
|
} from './runtimeCapabilities.js';
|
||||||
|
|
||||||
function createRuntimeRepositoryStub(): RuntimeRepositoryPort {
|
function createRuntimeRepositoryStub(): CustomWorldSessionCapability &
|
||||||
|
CustomWorldWorkSummaryCapability {
|
||||||
const sessionsByUser = new Map<
|
const sessionsByUser = new Map<
|
||||||
string,
|
string,
|
||||||
Map<string, CustomWorldSessionRecord>
|
Map<string, CustomWorldSessionRecord>
|
||||||
@@ -35,51 +39,9 @@ function createRuntimeRepositoryStub(): RuntimeRepositoryPort {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
async getSnapshot(_userId) {
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
async putSnapshot(_userId, _payload) {
|
|
||||||
throw new Error('not implemented');
|
|
||||||
},
|
|
||||||
async deleteSnapshot(_userId) {
|
|
||||||
return undefined;
|
|
||||||
},
|
|
||||||
async getSettings() {
|
|
||||||
return {
|
|
||||||
musicVolume: 0.42,
|
|
||||||
platformTheme: 'light',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
async putSettings(_userId, settings) {
|
|
||||||
return settings;
|
|
||||||
},
|
|
||||||
async listCustomWorldProfiles(userId) {
|
async listCustomWorldProfiles(userId) {
|
||||||
return [...(profilesByUser.get(userId) ?? [])];
|
return [...(profilesByUser.get(userId) ?? [])];
|
||||||
},
|
},
|
||||||
async upsertCustomWorldProfile(userId, profileId, profile) {
|
|
||||||
const current = [...(profilesByUser.get(userId) ?? [])].filter(
|
|
||||||
(item) => String(item.id ?? '') !== profileId,
|
|
||||||
);
|
|
||||||
current.unshift({
|
|
||||||
...profile,
|
|
||||||
id: profileId,
|
|
||||||
});
|
|
||||||
profilesByUser.set(userId, current);
|
|
||||||
return current;
|
|
||||||
},
|
|
||||||
async deleteCustomWorldProfile(userId, profileId) {
|
|
||||||
const current = [...(profilesByUser.get(userId) ?? [])].filter(
|
|
||||||
(item) => String(item.id ?? '') !== profileId,
|
|
||||||
);
|
|
||||||
profilesByUser.set(userId, current);
|
|
||||||
return current;
|
|
||||||
},
|
|
||||||
async listProfileSaveArchives() {
|
|
||||||
return [];
|
|
||||||
},
|
|
||||||
async resumeProfileSaveArchive() {
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
async listCustomWorldSessions(userId) {
|
async listCustomWorldSessions(userId) {
|
||||||
return [...getSessionBucket(userId).values()];
|
return [...getSessionBucket(userId).values()];
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,13 +2,17 @@ import assert from 'node:assert/strict';
|
|||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
|
|
||||||
import type { CustomWorldSessionRecord } from '../../../packages/shared/src/contracts/runtime.js';
|
import type { CustomWorldSessionRecord } from '../../../packages/shared/src/contracts/runtime.js';
|
||||||
import type { RuntimeRepositoryPort } from '../repositories/runtimeRepository.js';
|
|
||||||
import { CustomWorldAgentOrchestrator } from './customWorldAgentOrchestrator.js';
|
import { CustomWorldAgentOrchestrator } from './customWorldAgentOrchestrator.js';
|
||||||
import { CustomWorldAgentSessionStore } from './customWorldAgentSessionStore.js';
|
import { CustomWorldAgentSessionStore } from './customWorldAgentSessionStore.js';
|
||||||
import { createTestCustomWorldAgentSingleTurnLlmClient } from './customWorldAgentTestHelpers.js';
|
import { createTestCustomWorldAgentSingleTurnLlmClient } from './customWorldAgentTestHelpers.js';
|
||||||
import { listCustomWorldWorkSummaries } from './customWorldWorkSummaryService.js';
|
import { listCustomWorldWorkSummaries } from './customWorldWorkSummaryService.js';
|
||||||
|
import type {
|
||||||
|
CustomWorldSessionCapability,
|
||||||
|
CustomWorldWorkSummaryCapability,
|
||||||
|
} from './runtimeCapabilities.js';
|
||||||
|
|
||||||
function createRuntimeRepositoryStub(): RuntimeRepositoryPort {
|
function createRuntimeRepositoryStub(): CustomWorldSessionCapability &
|
||||||
|
CustomWorldWorkSummaryCapability {
|
||||||
const sessionsByUser = new Map<
|
const sessionsByUser = new Map<
|
||||||
string,
|
string,
|
||||||
Map<string, CustomWorldSessionRecord>
|
Map<string, CustomWorldSessionRecord>
|
||||||
@@ -27,51 +31,9 @@ function createRuntimeRepositoryStub(): RuntimeRepositoryPort {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
async getSnapshot(_userId) {
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
async putSnapshot(_userId, _payload) {
|
|
||||||
throw new Error('not implemented');
|
|
||||||
},
|
|
||||||
async deleteSnapshot(_userId) {
|
|
||||||
return undefined;
|
|
||||||
},
|
|
||||||
async getSettings() {
|
|
||||||
return {
|
|
||||||
musicVolume: 0.42,
|
|
||||||
platformTheme: 'light',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
async putSettings(_userId, settings) {
|
|
||||||
return settings;
|
|
||||||
},
|
|
||||||
async listCustomWorldProfiles(userId) {
|
async listCustomWorldProfiles(userId) {
|
||||||
return [...(profilesByUser.get(userId) ?? [])];
|
return [...(profilesByUser.get(userId) ?? [])];
|
||||||
},
|
},
|
||||||
async upsertCustomWorldProfile(userId, profileId, profile) {
|
|
||||||
const current = [...(profilesByUser.get(userId) ?? [])].filter(
|
|
||||||
(item) => String(item.id ?? '') !== profileId,
|
|
||||||
);
|
|
||||||
current.unshift({
|
|
||||||
...profile,
|
|
||||||
id: profileId,
|
|
||||||
});
|
|
||||||
profilesByUser.set(userId, current);
|
|
||||||
return current;
|
|
||||||
},
|
|
||||||
async deleteCustomWorldProfile(userId, profileId) {
|
|
||||||
const current = [...(profilesByUser.get(userId) ?? [])].filter(
|
|
||||||
(item) => String(item.id ?? '') !== profileId,
|
|
||||||
);
|
|
||||||
profilesByUser.set(userId, current);
|
|
||||||
return current;
|
|
||||||
},
|
|
||||||
async listProfileSaveArchives() {
|
|
||||||
return [];
|
|
||||||
},
|
|
||||||
async resumeProfileSaveArchive() {
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
async listCustomWorldSessions(userId) {
|
async listCustomWorldSessions(userId) {
|
||||||
return [...getSessionBucket(userId).values()];
|
return [...getSessionBucket(userId).values()];
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,14 +2,18 @@ import assert from 'node:assert/strict';
|
|||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
|
|
||||||
import type { CustomWorldSessionRecord } from '../../../packages/shared/src/contracts/runtime.js';
|
import type { CustomWorldSessionRecord } from '../../../packages/shared/src/contracts/runtime.js';
|
||||||
import type { RuntimeRepositoryPort } from '../repositories/runtimeRepository.js';
|
|
||||||
import { normalizeFoundationDraftProfile } from './customWorldAgentDraftCompiler.js';
|
import { normalizeFoundationDraftProfile } from './customWorldAgentDraftCompiler.js';
|
||||||
import { CustomWorldAgentOrchestrator } from './customWorldAgentOrchestrator.js';
|
import { CustomWorldAgentOrchestrator } from './customWorldAgentOrchestrator.js';
|
||||||
import { CustomWorldAgentSessionStore } from './customWorldAgentSessionStore.js';
|
import { CustomWorldAgentSessionStore } from './customWorldAgentSessionStore.js';
|
||||||
import { createTestCustomWorldAgentSingleTurnLlmClient } from './customWorldAgentTestHelpers.js';
|
import { createTestCustomWorldAgentSingleTurnLlmClient } from './customWorldAgentTestHelpers.js';
|
||||||
import { listCustomWorldWorkSummaries } from './customWorldWorkSummaryService.js';
|
import { listCustomWorldWorkSummaries } from './customWorldWorkSummaryService.js';
|
||||||
|
import type {
|
||||||
|
CustomWorldSessionCapability,
|
||||||
|
CustomWorldWorkSummaryCapability,
|
||||||
|
} from './runtimeCapabilities.js';
|
||||||
|
|
||||||
function createRuntimeRepositoryStub(): RuntimeRepositoryPort {
|
function createRuntimeRepositoryStub(): CustomWorldSessionCapability &
|
||||||
|
CustomWorldWorkSummaryCapability {
|
||||||
const sessionsByUser = new Map<
|
const sessionsByUser = new Map<
|
||||||
string,
|
string,
|
||||||
Map<string, CustomWorldSessionRecord>
|
Map<string, CustomWorldSessionRecord>
|
||||||
@@ -28,51 +32,9 @@ function createRuntimeRepositoryStub(): RuntimeRepositoryPort {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
async getSnapshot(_userId) {
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
async putSnapshot(_userId, _payload) {
|
|
||||||
throw new Error('not implemented');
|
|
||||||
},
|
|
||||||
async deleteSnapshot(_userId) {
|
|
||||||
return undefined;
|
|
||||||
},
|
|
||||||
async getSettings() {
|
|
||||||
return {
|
|
||||||
musicVolume: 0.42,
|
|
||||||
platformTheme: 'light',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
async putSettings(_userId, settings) {
|
|
||||||
return settings;
|
|
||||||
},
|
|
||||||
async listCustomWorldProfiles(userId) {
|
async listCustomWorldProfiles(userId) {
|
||||||
return [...(profilesByUser.get(userId) ?? [])];
|
return [...(profilesByUser.get(userId) ?? [])];
|
||||||
},
|
},
|
||||||
async upsertCustomWorldProfile(userId, profileId, profile) {
|
|
||||||
const current = [...(profilesByUser.get(userId) ?? [])].filter(
|
|
||||||
(item) => String(item.id ?? '') !== profileId,
|
|
||||||
);
|
|
||||||
current.unshift({
|
|
||||||
...profile,
|
|
||||||
id: profileId,
|
|
||||||
});
|
|
||||||
profilesByUser.set(userId, current);
|
|
||||||
return current;
|
|
||||||
},
|
|
||||||
async deleteCustomWorldProfile(userId, profileId) {
|
|
||||||
const current = [...(profilesByUser.get(userId) ?? [])].filter(
|
|
||||||
(item) => String(item.id ?? '') !== profileId,
|
|
||||||
);
|
|
||||||
profilesByUser.set(userId, current);
|
|
||||||
return current;
|
|
||||||
},
|
|
||||||
async listProfileSaveArchives() {
|
|
||||||
return [];
|
|
||||||
},
|
|
||||||
async resumeProfileSaveArchive() {
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
async listCustomWorldSessions(userId) {
|
async listCustomWorldSessions(userId) {
|
||||||
return [...getSessionBucket(userId).values()];
|
return [...getSessionBucket(userId).values()];
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,18 +2,17 @@ import assert from 'node:assert/strict';
|
|||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
|
|
||||||
import type { CustomWorldSessionRecord } from '../../../packages/shared/src/contracts/runtime.js';
|
import type { CustomWorldSessionRecord } from '../../../packages/shared/src/contracts/runtime.js';
|
||||||
import type { RuntimeRepositoryPort } from '../repositories/runtimeRepository.js';
|
|
||||||
import { normalizeFoundationDraftProfile } from './customWorldAgentDraftCompiler.js';
|
import { normalizeFoundationDraftProfile } from './customWorldAgentDraftCompiler.js';
|
||||||
import { CustomWorldAgentOrchestrator } from './customWorldAgentOrchestrator.js';
|
import { CustomWorldAgentOrchestrator } from './customWorldAgentOrchestrator.js';
|
||||||
import { CustomWorldAgentSessionStore } from './customWorldAgentSessionStore.js';
|
import { CustomWorldAgentSessionStore } from './customWorldAgentSessionStore.js';
|
||||||
import { createTestCustomWorldAgentSingleTurnLlmClient } from './customWorldAgentTestHelpers.js';
|
import { createTestCustomWorldAgentSingleTurnLlmClient } from './customWorldAgentTestHelpers.js';
|
||||||
|
import type { CustomWorldSessionCapability } from './runtimeCapabilities.js';
|
||||||
|
|
||||||
function createRuntimeRepositoryStub(): RuntimeRepositoryPort {
|
function createRuntimeRepositoryStub(): CustomWorldSessionCapability {
|
||||||
const sessionsByUser = new Map<
|
const sessionsByUser = new Map<
|
||||||
string,
|
string,
|
||||||
Map<string, CustomWorldSessionRecord>
|
Map<string, CustomWorldSessionRecord>
|
||||||
>();
|
>();
|
||||||
const profilesByUser = new Map<string, Record<string, unknown>[]>();
|
|
||||||
|
|
||||||
const getSessionBucket = (userId: string) => {
|
const getSessionBucket = (userId: string) => {
|
||||||
const existing = sessionsByUser.get(userId);
|
const existing = sessionsByUser.get(userId);
|
||||||
@@ -27,51 +26,6 @@ function createRuntimeRepositoryStub(): RuntimeRepositoryPort {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
async getSnapshot() {
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
async putSnapshot(_userId, payload) {
|
|
||||||
return payload;
|
|
||||||
},
|
|
||||||
async deleteSnapshot() {
|
|
||||||
return undefined;
|
|
||||||
},
|
|
||||||
async getSettings() {
|
|
||||||
return {
|
|
||||||
musicVolume: 0.42,
|
|
||||||
platformTheme: 'light',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
async putSettings(_userId, settings) {
|
|
||||||
return settings;
|
|
||||||
},
|
|
||||||
async listCustomWorldProfiles(userId) {
|
|
||||||
return [...(profilesByUser.get(userId) ?? [])];
|
|
||||||
},
|
|
||||||
async upsertCustomWorldProfile(userId, profileId, profile) {
|
|
||||||
const current = [...(profilesByUser.get(userId) ?? [])].filter(
|
|
||||||
(item) => String(item.id ?? '') !== profileId,
|
|
||||||
);
|
|
||||||
current.unshift({
|
|
||||||
...profile,
|
|
||||||
id: profileId,
|
|
||||||
});
|
|
||||||
profilesByUser.set(userId, current);
|
|
||||||
return current;
|
|
||||||
},
|
|
||||||
async deleteCustomWorldProfile(userId, profileId) {
|
|
||||||
const current = [...(profilesByUser.get(userId) ?? [])].filter(
|
|
||||||
(item) => String(item.id ?? '') !== profileId,
|
|
||||||
);
|
|
||||||
profilesByUser.set(userId, current);
|
|
||||||
return current;
|
|
||||||
},
|
|
||||||
async listProfileSaveArchives() {
|
|
||||||
return [];
|
|
||||||
},
|
|
||||||
async resumeProfileSaveArchive() {
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
async listCustomWorldSessions(userId) {
|
async listCustomWorldSessions(userId) {
|
||||||
return [...getSessionBucket(userId).values()];
|
return [...getSessionBucket(userId).values()];
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import type {
|
|||||||
EightAnchorContent,
|
EightAnchorContent,
|
||||||
} from '../../../packages/shared/src/contracts/customWorldAgent.js';
|
} from '../../../packages/shared/src/contracts/customWorldAgent.js';
|
||||||
import type { CustomWorldSessionRecord as LegacyCustomWorldSessionRecord } from '../../../packages/shared/src/contracts/runtime.js';
|
import type { CustomWorldSessionRecord as LegacyCustomWorldSessionRecord } from '../../../packages/shared/src/contracts/runtime.js';
|
||||||
import type { RuntimeRepositoryPort } from '../repositories/runtimeRepository.js';
|
|
||||||
import {
|
import {
|
||||||
buildPendingClarifications,
|
buildPendingClarifications,
|
||||||
evaluateCreatorIntentReadiness,
|
evaluateCreatorIntentReadiness,
|
||||||
@@ -33,6 +32,7 @@ import {
|
|||||||
estimateProgressPercentFromAnchorContent,
|
estimateProgressPercentFromAnchorContent,
|
||||||
normalizeEightAnchorContent,
|
normalizeEightAnchorContent,
|
||||||
} from './eightAnchorCompatibilityService.js';
|
} from './eightAnchorCompatibilityService.js';
|
||||||
|
import type { CustomWorldSessionCapability } from './runtimeCapabilities.js';
|
||||||
|
|
||||||
export const CUSTOM_WORLD_AGENT_SESSION_ID_PREFIX =
|
export const CUSTOM_WORLD_AGENT_SESSION_ID_PREFIX =
|
||||||
'custom-world-agent-session-';
|
'custom-world-agent-session-';
|
||||||
@@ -565,7 +565,7 @@ function toSnapshot(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class CustomWorldAgentSessionStore {
|
export class CustomWorldAgentSessionStore {
|
||||||
constructor(private readonly runtimeRepository: RuntimeRepositoryPort) {}
|
constructor(private readonly runtimeRepository: CustomWorldSessionCapability) {}
|
||||||
|
|
||||||
private async persist(record: CustomWorldAgentSessionRecord) {
|
private async persist(record: CustomWorldAgentSessionRecord) {
|
||||||
await this.runtimeRepository.upsertCustomWorldSession(
|
await this.runtimeRepository.upsertCustomWorldSession(
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import type {
|
|||||||
CustomWorldSessionRecord,
|
CustomWorldSessionRecord,
|
||||||
CustomWorldSessionStatus,
|
CustomWorldSessionStatus,
|
||||||
} from '../../../packages/shared/src/contracts/runtime.js';
|
} from '../../../packages/shared/src/contracts/runtime.js';
|
||||||
import type { RuntimeRepositoryPort } from '../repositories/runtimeRepository.js';
|
import type { CustomWorldSessionCapability } from './runtimeCapabilities.js';
|
||||||
|
|
||||||
export type CustomWorldSession = {
|
export type CustomWorldSession = {
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
@@ -111,7 +111,7 @@ function buildClarificationQuestions(
|
|||||||
|
|
||||||
export class CustomWorldSessionStore {
|
export class CustomWorldSessionStore {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly runtimeRepository: RuntimeRepositoryPort,
|
private readonly runtimeRepository: CustomWorldSessionCapability,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async create(
|
async create(
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import type {
|
|||||||
CustomWorldLibraryEntry,
|
CustomWorldLibraryEntry,
|
||||||
CustomWorldProfileRecord,
|
CustomWorldProfileRecord,
|
||||||
} from '../../../packages/shared/src/contracts/runtime.js';
|
} from '../../../packages/shared/src/contracts/runtime.js';
|
||||||
import type { RuntimeRepositoryPort } from '../repositories/runtimeRepository.js';
|
|
||||||
import { resolveCustomWorldCoverPresentation } from '../repositories/customWorldLibraryMetadata.js';
|
import { resolveCustomWorldCoverPresentation } from '../repositories/customWorldLibraryMetadata.js';
|
||||||
import { normalizeFoundationDraftProfile } from './customWorldAgentDraftCompiler.js';
|
import { normalizeFoundationDraftProfile } from './customWorldAgentDraftCompiler.js';
|
||||||
import {
|
import {
|
||||||
@@ -26,6 +25,7 @@ import {
|
|||||||
buildDraftSummaryFromEightAnchorContent,
|
buildDraftSummaryFromEightAnchorContent,
|
||||||
buildDraftTitleFromEightAnchorContent,
|
buildDraftTitleFromEightAnchorContent,
|
||||||
} from './eightAnchorCompatibilityService.js';
|
} from './eightAnchorCompatibilityService.js';
|
||||||
|
import type { CustomWorldWorkSummaryCapability } from './runtimeCapabilities.js';
|
||||||
|
|
||||||
function toText(value: unknown) {
|
function toText(value: unknown) {
|
||||||
return typeof value === 'string' ? value.trim() : '';
|
return typeof value === 'string' ? value.trim() : '';
|
||||||
@@ -171,7 +171,7 @@ function isLibraryEntry(
|
|||||||
export async function listCustomWorldWorkSummaries(
|
export async function listCustomWorldWorkSummaries(
|
||||||
userId: string,
|
userId: string,
|
||||||
dependencies: {
|
dependencies: {
|
||||||
runtimeRepository: RuntimeRepositoryPort;
|
runtimeRepository: CustomWorldWorkSummaryCapability;
|
||||||
customWorldAgentSessions: CustomWorldAgentSessionStore;
|
customWorldAgentSessions: CustomWorldAgentSessionStore;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
|||||||
53
server-node/src/services/runtimeCapabilities.ts
Normal file
53
server-node/src/services/runtimeCapabilities.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import type { CustomWorldProfileRecord, CustomWorldSessionRecord } from '../../../packages/shared/src/contracts/runtime.js';
|
||||||
|
import type {
|
||||||
|
RuntimeRepositoryPort,
|
||||||
|
SavedSnapshot,
|
||||||
|
} from '../repositories/runtimeRepository.js';
|
||||||
|
|
||||||
|
export type RuntimeSnapshotCapability = Pick<
|
||||||
|
RuntimeRepositoryPort,
|
||||||
|
'getSnapshot' | 'putSnapshot'
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type CustomWorldProfileCapability = Pick<
|
||||||
|
RuntimeRepositoryPort,
|
||||||
|
'listCustomWorldProfiles'
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type CustomWorldSessionCapability = {
|
||||||
|
listCustomWorldSessions(userId: string): Promise<CustomWorldSessionRecord[]>;
|
||||||
|
getCustomWorldSession(
|
||||||
|
userId: string,
|
||||||
|
sessionId: string,
|
||||||
|
): Promise<CustomWorldSessionRecord | null>;
|
||||||
|
upsertCustomWorldSession(
|
||||||
|
userId: string,
|
||||||
|
sessionId: string,
|
||||||
|
session: CustomWorldSessionRecord,
|
||||||
|
): Promise<CustomWorldSessionRecord>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RuntimeStoryCapability = RuntimeSnapshotCapability;
|
||||||
|
|
||||||
|
export type CustomWorldWorkSummaryCapability = CustomWorldProfileCapability;
|
||||||
|
|
||||||
|
export function createRuntimeSnapshotCapability(
|
||||||
|
runtimeRepository: RuntimeRepositoryPort,
|
||||||
|
): RuntimeSnapshotCapability {
|
||||||
|
return runtimeRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCustomWorldSessionCapability(
|
||||||
|
runtimeRepository: RuntimeRepositoryPort,
|
||||||
|
): CustomWorldSessionCapability {
|
||||||
|
return runtimeRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCustomWorldProfileCapability(
|
||||||
|
runtimeRepository: RuntimeRepositoryPort,
|
||||||
|
): CustomWorldProfileCapability {
|
||||||
|
return runtimeRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RuntimeStorySnapshot = SavedSnapshot;
|
||||||
|
export type RuntimeCustomWorldProfile = CustomWorldProfileRecord;
|
||||||
Reference in New Issue
Block a user