1
This commit is contained in:
@@ -1,94 +1,13 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
|
||||
import type { CustomWorldSessionRecord } from '../../../packages/shared/src/contracts/runtime.js';
|
||||
import type { RuntimeRepositoryPort } from '../repositories/runtimeRepository.js';
|
||||
import { normalizeFoundationDraftProfile } from './customWorldAgentDraftCompiler.js';
|
||||
import { CustomWorldAgentOrchestrator } from './customWorldAgentOrchestrator.js';
|
||||
import { createInMemoryRpgWorldRepositoryPorts } from './customWorldAgentRepositoryTestHelpers.js';
|
||||
import { CustomWorldAgentSessionStore } from './customWorldAgentSessionStore.js';
|
||||
import { createTestCustomWorldAgentSingleTurnLlmClient } from './customWorldAgentTestHelpers.js';
|
||||
import { listCustomWorldWorkSummaries } from './customWorldWorkSummaryService.js';
|
||||
|
||||
function createRuntimeRepositoryStub(): RuntimeRepositoryPort {
|
||||
const sessionsByUser = new Map<
|
||||
string,
|
||||
Map<string, CustomWorldSessionRecord>
|
||||
>();
|
||||
const profilesByUser = new Map<string, Record<string, unknown>[]>();
|
||||
|
||||
const getSessionBucket = (userId: string) => {
|
||||
const existing = sessionsByUser.get(userId);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
const nextBucket = new Map<string, CustomWorldSessionRecord>();
|
||||
sessionsByUser.set(userId, nextBucket);
|
||||
return nextBucket;
|
||||
};
|
||||
|
||||
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) {
|
||||
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) {
|
||||
return [...getSessionBucket(userId).values()];
|
||||
},
|
||||
async getCustomWorldSession(userId, sessionId) {
|
||||
return getSessionBucket(userId).get(sessionId) ?? null;
|
||||
},
|
||||
async upsertCustomWorldSession(userId, sessionId, session) {
|
||||
getSessionBucket(userId).set(
|
||||
sessionId,
|
||||
JSON.parse(JSON.stringify(session)),
|
||||
);
|
||||
return JSON.parse(JSON.stringify(session));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function waitForOperation(
|
||||
orchestrator: CustomWorldAgentOrchestrator,
|
||||
userId: string,
|
||||
@@ -167,8 +86,10 @@ async function createObjectRefiningSession(
|
||||
}
|
||||
|
||||
test('phase4 update_draft_card writes back draft profile and recompiles summaries', async () => {
|
||||
const runtimeRepository = createRuntimeRepositoryStub();
|
||||
const sessionStore = new CustomWorldAgentSessionStore(runtimeRepository);
|
||||
const { rpgAgentSessionRepository } = createInMemoryRpgWorldRepositoryPorts();
|
||||
const sessionStore = new CustomWorldAgentSessionStore(
|
||||
rpgAgentSessionRepository,
|
||||
);
|
||||
const orchestrator = new CustomWorldAgentOrchestrator(sessionStore, null, {
|
||||
singleTurnLlmClient: createTestCustomWorldAgentSingleTurnLlmClient(),
|
||||
});
|
||||
@@ -228,8 +149,10 @@ test('phase4 update_draft_card writes back draft profile and recompiles summarie
|
||||
});
|
||||
|
||||
test('phase4 sync_result_profile writes result-page snapshot back into session draft chain', async () => {
|
||||
const runtimeRepository = createRuntimeRepositoryStub();
|
||||
const sessionStore = new CustomWorldAgentSessionStore(runtimeRepository);
|
||||
const { rpgAgentSessionRepository } = createInMemoryRpgWorldRepositoryPorts();
|
||||
const sessionStore = new CustomWorldAgentSessionStore(
|
||||
rpgAgentSessionRepository,
|
||||
);
|
||||
const orchestrator = new CustomWorldAgentOrchestrator(sessionStore, null, {
|
||||
singleTurnLlmClient: createTestCustomWorldAgentSingleTurnLlmClient(),
|
||||
});
|
||||
@@ -290,6 +213,15 @@ test('phase4 sync_result_profile writes result-page snapshot back into session d
|
||||
profile?.summary,
|
||||
'结果页已经把世界概述继续往沉船夜暗线收紧。',
|
||||
);
|
||||
assert.equal(snapshot?.resultPreview?.source, 'session_preview');
|
||||
assert.equal(
|
||||
snapshot?.resultPreview?.preview.name,
|
||||
'潮雾列岛·结果页精修版',
|
||||
);
|
||||
assert.equal(
|
||||
snapshot?.resultPreview?.preview.playerGoal,
|
||||
'查清沉船夜与假航灯的真正操盘者。',
|
||||
);
|
||||
assert.equal(legacyResultProfile?.name, '潮雾列岛·结果页精修版');
|
||||
assert.equal(
|
||||
legacyResultProfile?.playerGoal,
|
||||
@@ -305,8 +237,10 @@ test('phase4 sync_result_profile writes result-page snapshot back into session d
|
||||
});
|
||||
|
||||
test('phase4 sync_result_profile keeps existing foundation structure while updating summary snapshot', async () => {
|
||||
const runtimeRepository = createRuntimeRepositoryStub();
|
||||
const sessionStore = new CustomWorldAgentSessionStore(runtimeRepository);
|
||||
const { rpgAgentSessionRepository } = createInMemoryRpgWorldRepositoryPorts();
|
||||
const sessionStore = new CustomWorldAgentSessionStore(
|
||||
rpgAgentSessionRepository,
|
||||
);
|
||||
const orchestrator = new CustomWorldAgentOrchestrator(sessionStore, null, {
|
||||
singleTurnLlmClient: createTestCustomWorldAgentSingleTurnLlmClient(),
|
||||
});
|
||||
@@ -422,8 +356,10 @@ test('phase4 sync_result_profile keeps existing foundation structure while updat
|
||||
});
|
||||
|
||||
test('phase4 sync_result_profile also writes latest role and scene assets back into draft profile', async () => {
|
||||
const runtimeRepository = createRuntimeRepositoryStub();
|
||||
const sessionStore = new CustomWorldAgentSessionStore(runtimeRepository);
|
||||
const { rpgAgentSessionRepository } = createInMemoryRpgWorldRepositoryPorts();
|
||||
const sessionStore = new CustomWorldAgentSessionStore(
|
||||
rpgAgentSessionRepository,
|
||||
);
|
||||
const orchestrator = new CustomWorldAgentOrchestrator(sessionStore, null, {
|
||||
singleTurnLlmClient: createTestCustomWorldAgentSingleTurnLlmClient(),
|
||||
});
|
||||
@@ -578,8 +514,11 @@ test('phase4 sync_result_profile also writes latest role and scene assets back i
|
||||
});
|
||||
|
||||
test('phase4 generate_characters appends story npcs and updates work summary counts', async () => {
|
||||
const runtimeRepository = createRuntimeRepositoryStub();
|
||||
const sessionStore = new CustomWorldAgentSessionStore(runtimeRepository);
|
||||
const { rpgAgentSessionRepository, rpgWorldProfileRepository } =
|
||||
createInMemoryRpgWorldRepositoryPorts();
|
||||
const sessionStore = new CustomWorldAgentSessionStore(
|
||||
rpgAgentSessionRepository,
|
||||
);
|
||||
const orchestrator = new CustomWorldAgentOrchestrator(sessionStore, null, {
|
||||
singleTurnLlmClient: createTestCustomWorldAgentSingleTurnLlmClient(),
|
||||
});
|
||||
@@ -614,7 +553,7 @@ test('phase4 generate_characters appends story npcs and updates work summary cou
|
||||
),
|
||||
].length;
|
||||
const workItems = await listCustomWorldWorkSummaries(userId, {
|
||||
runtimeRepository,
|
||||
rpgWorldProfiles: rpgWorldProfileRepository,
|
||||
customWorldAgentSessions: sessionStore,
|
||||
});
|
||||
const draftItem = workItems.find((item) => item.sessionId === session.sessionId);
|
||||
@@ -634,8 +573,10 @@ test('phase4 generate_characters appends story npcs and updates work summary cou
|
||||
});
|
||||
|
||||
test('phase4 generate_landmarks appends new landmark cards and checkpoints', async () => {
|
||||
const runtimeRepository = createRuntimeRepositoryStub();
|
||||
const sessionStore = new CustomWorldAgentSessionStore(runtimeRepository);
|
||||
const { rpgAgentSessionRepository } = createInMemoryRpgWorldRepositoryPorts();
|
||||
const sessionStore = new CustomWorldAgentSessionStore(
|
||||
rpgAgentSessionRepository,
|
||||
);
|
||||
const orchestrator = new CustomWorldAgentOrchestrator(sessionStore, null, {
|
||||
singleTurnLlmClient: createTestCustomWorldAgentSingleTurnLlmClient(),
|
||||
});
|
||||
@@ -675,25 +616,33 @@ test('phase4 generate_landmarks appends new landmark cards and checkpoints', asy
|
||||
});
|
||||
|
||||
test('phase4 work summaries exclude library draft entries after phase3 downgrade', async () => {
|
||||
const runtimeRepository = createRuntimeRepositoryStub();
|
||||
const sessionStore = new CustomWorldAgentSessionStore(runtimeRepository);
|
||||
const { rpgAgentSessionRepository, rpgWorldProfileRepository } =
|
||||
createInMemoryRpgWorldRepositoryPorts();
|
||||
const sessionStore = new CustomWorldAgentSessionStore(
|
||||
rpgAgentSessionRepository,
|
||||
);
|
||||
const orchestrator = new CustomWorldAgentOrchestrator(sessionStore, null, {
|
||||
singleTurnLlmClient: createTestCustomWorldAgentSingleTurnLlmClient(),
|
||||
});
|
||||
const userId = 'user-phase4-work-summary-phase3';
|
||||
const session = await createObjectRefiningSession(orchestrator, userId);
|
||||
|
||||
await runtimeRepository.upsertCustomWorldProfile(userId, 'library-draft-1', {
|
||||
id: 'library-draft-1',
|
||||
name: '旧兼容草稿',
|
||||
subtitle: '仍保留在作品库',
|
||||
summary: '不应该继续出现在创作中心 works 聚合里。',
|
||||
playableNpcs: [],
|
||||
landmarks: [],
|
||||
});
|
||||
await rpgWorldProfileRepository.upsertOwnProfile(
|
||||
userId,
|
||||
'library-draft-1',
|
||||
{
|
||||
id: 'library-draft-1',
|
||||
name: '旧兼容草稿',
|
||||
subtitle: '仍保留在作品库',
|
||||
summary: '不应该继续出现在创作中心 works 聚合里。',
|
||||
playableNpcs: [],
|
||||
landmarks: [],
|
||||
},
|
||||
'玩家',
|
||||
);
|
||||
|
||||
const workItems = await listCustomWorldWorkSummaries(userId, {
|
||||
runtimeRepository,
|
||||
rpgWorldProfiles: rpgWorldProfileRepository,
|
||||
customWorldAgentSessions: sessionStore,
|
||||
});
|
||||
|
||||
@@ -703,3 +652,54 @@ test('phase4 work summaries exclude library draft entries after phase3 downgrade
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
test('phase4 work summaries hide published agent sessions from draft lane and keep published entry enterable', async () => {
|
||||
const { rpgAgentSessionRepository, rpgWorldProfileRepository } =
|
||||
createInMemoryRpgWorldRepositoryPorts();
|
||||
const sessionStore = new CustomWorldAgentSessionStore(
|
||||
rpgAgentSessionRepository,
|
||||
);
|
||||
const orchestrator = new CustomWorldAgentOrchestrator(sessionStore, null, {
|
||||
singleTurnLlmClient: createTestCustomWorldAgentSingleTurnLlmClient(),
|
||||
});
|
||||
const userId = 'user-phase4-work-summary-published';
|
||||
const session = await createObjectRefiningSession(orchestrator, userId);
|
||||
|
||||
await sessionStore.replaceDerivedState(userId, session.sessionId, {
|
||||
stage: 'published',
|
||||
qualityFindings: [],
|
||||
});
|
||||
await rpgWorldProfileRepository.upsertOwnProfile(
|
||||
userId,
|
||||
`agent-draft-${session.sessionId}`,
|
||||
{
|
||||
id: `agent-draft-${session.sessionId}`,
|
||||
name: '潮雾列岛',
|
||||
subtitle: '旧灯塔与失控航路',
|
||||
summary: '已发布版本。',
|
||||
playableNpcs: [],
|
||||
landmarks: [],
|
||||
},
|
||||
'玩家',
|
||||
);
|
||||
await rpgWorldProfileRepository.publishOwnProfile(
|
||||
userId,
|
||||
`agent-draft-${session.sessionId}`,
|
||||
'玩家',
|
||||
);
|
||||
|
||||
const workItems = await listCustomWorldWorkSummaries(userId, {
|
||||
rpgWorldProfiles: rpgWorldProfileRepository,
|
||||
customWorldAgentSessions: sessionStore,
|
||||
});
|
||||
const draftItem = workItems.find((item) => item.sessionId === session.sessionId);
|
||||
const publishedItem = workItems.find(
|
||||
(item) => item.profileId === `agent-draft-${session.sessionId}`,
|
||||
);
|
||||
|
||||
assert.equal(draftItem, undefined);
|
||||
assert.equal(publishedItem?.status, 'published');
|
||||
assert.equal(publishedItem?.canEnterWorld, true);
|
||||
assert.equal(publishedItem?.publishReady, true);
|
||||
assert.equal(publishedItem?.blockerCount, 0);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user