This commit is contained in:
2026-04-21 00:48:17 +08:00
parent 75944b1f1f
commit effe0355bd
19 changed files with 2897 additions and 180 deletions

View File

@@ -16,6 +16,8 @@ import type {
} from '../../../packages/shared/src/contracts/customWorldAgent.js';
import { badRequest, notFound } from '../errors.js';
import { prepareEventStreamResponse } from '../http.js';
import { normalizeCustomWorldProfile } from '../modules/custom-world/runtimeProfile.js';
import type { CustomWorldProfile } from '../modules/custom-world/runtimeTypes.js';
import { CustomWorldAgentAssetBridgeService } from './customWorldAgentAssetBridgeService.js';
import { CustomWorldAgentAutoAssetService } from './customWorldAgentAutoAssetService.js';
import { CustomWorldAgentChangeSummaryService } from './customWorldAgentChangeSummaryService.js';
@@ -150,6 +152,8 @@ function buildOperation(type: CustomWorldAgentOperationRecord['type']) {
? '正在把已确认设定编成第一版世界底稿。'
: type === 'update_draft_card'
? '正在把这次设定改动写回草稿。'
: type === 'sync_result_profile'
? '正在把结果页里的世界快照同步回当前草稿。'
: type === 'generate_characters'
? '正在围绕当前底稿补出新角色。'
: type === 'generate_landmarks'
@@ -194,6 +198,27 @@ function buildRoleAssetSyncResultText(params: {
return `已把「${params.roleName}」的角色资产写回草稿,当前状态:${params.assetStatusLabel}`;
}
function syncResultProfileIntoDraftProfile(params: {
currentDraftProfile: Record<string, unknown> | null | undefined;
resultProfile: CustomWorldProfile;
}) {
const currentDraftProfile = params.currentDraftProfile ?? {};
const resultProfile = params.resultProfile;
return {
// 阶段一只回写基础摘要和完整 legacy 快照,避免把结果页的运行时结构反向拆回 foundation draft。
...currentDraftProfile,
name: resultProfile.name,
subtitle: resultProfile.subtitle,
summary: resultProfile.summary,
tone: resultProfile.tone,
playerGoal: resultProfile.playerGoal,
majorFactions: resultProfile.majorFactions,
coreConflicts: resultProfile.coreConflicts,
legacyResultProfile: resultProfile as unknown as Record<string, unknown>,
} satisfies Record<string, unknown>;
}
function buildQuestionLines(
pendingClarifications: CustomWorldPendingClarification[],
) {
@@ -548,6 +573,7 @@ export class CustomWorldAgentOrchestrator {
if (
payload.action === 'update_draft_card' ||
payload.action === 'sync_result_profile' ||
payload.action === 'generate_characters' ||
payload.action === 'generate_landmarks' ||
payload.action === 'generate_role_assets' ||
@@ -595,6 +621,32 @@ export class CustomWorldAgentOrchestrator {
};
}
if (payload.action === 'sync_result_profile') {
const normalizedProfile = normalizeCustomWorldProfile(
payload.profile,
'',
);
if (!normalizedProfile) {
throw badRequest('sync_result_profile requires a valid profile');
}
const operation = buildOperation('sync_result_profile');
await this.sessionStore.createOperation(userId, sessionId, operation);
void this.processSyncResultProfileOperation({
userId,
sessionId,
operationId: operation.operationId,
payload: {
...payload,
profile: normalizedProfile as unknown as Record<string, unknown>,
},
});
return {
operation,
};
}
if (payload.action === 'generate_characters') {
if (payload.count < 1 || payload.count > 3) {
throw badRequest('generate_characters count must be between 1 and 3');
@@ -1113,6 +1165,97 @@ export class CustomWorldAgentOrchestrator {
}
}
private async processSyncResultProfileOperation(params: {
userId: string;
sessionId: string;
operationId: string;
payload: Extract<
CustomWorldAgentActionRequest,
{ action: 'sync_result_profile' }
>;
}) {
const { userId, sessionId, operationId, payload } = params;
try {
await this.sessionStore.updateOperation(userId, sessionId, operationId, {
status: 'running',
phaseLabel: '同步结果页快照',
phaseDetail: '正在把结果页里的最新世界结构写回当前草稿。',
progress: 36,
});
const latestSession = (await this.sessionStore.get(
userId,
sessionId,
)) as CustomWorldAgentSessionRecord | null;
if (!latestSession) {
throw new Error('custom world agent session not found');
}
const resultProfile = payload.profile as unknown as CustomWorldProfile;
const nextDraftProfile = syncResultProfileIntoDraftProfile({
currentDraftProfile: latestSession.draftProfile,
resultProfile,
});
await this.sessionStore.updateOperation(userId, sessionId, operationId, {
phaseLabel: '重编译草稿摘要',
phaseDetail: '正在刷新草稿卡摘要、资产覆盖和结果页恢复快照。',
progress: 72,
});
const nextDraftCards = this.draftCompiler.compileDraftCards(nextDraftProfile);
const assetCoverage = rebuildRoleAssetCoverage(nextDraftProfile);
const nextStage =
latestSession.stage === 'visual_refining'
? ('visual_refining' as const)
: ('object_refining' as const);
const nextSuggestedActions = buildSuggestedActions({
stage: nextStage,
isReady: true,
draftProfile: nextDraftProfile,
draftCards: nextDraftCards,
});
await this.sessionStore.replaceDerivedState(userId, sessionId, {
stage: nextStage,
draftProfile: nextDraftProfile,
draftCards: nextDraftCards,
assetCoverage,
suggestedActions: nextSuggestedActions,
recommendedReplies: [],
});
await this.sessionStore.appendCheckpoint(userId, sessionId, {
label: '同步结果页编辑',
});
await this.sessionStore.appendMessage(
userId,
sessionId,
buildActionResultMessage({
relatedOperationId: operationId,
text: '结果页里的最新世界结构已经同步回当前草稿。',
}),
);
await this.sessionStore.updateOperation(userId, sessionId, operationId, {
status: 'completed',
phaseLabel: '结果页快照已同步',
phaseDetail: '当前结果页编辑内容已经并回 Agent 草稿主链。',
progress: 100,
error: null,
});
} catch (error) {
await this.sessionStore.updateOperation(userId, sessionId, operationId, {
status: 'failed',
phaseLabel: '结果页同步失败',
phaseDetail: '这一轮结果页编辑没有成功写回当前草稿。',
progress: 100,
error:
error instanceof Error ? error.message : 'sync result profile failed',
});
}
}
private async processGenerateCharactersOperation(params: {
userId: string;
sessionId: string;