Files
Genarrative/apps/ai-game-creator-shell/src/features/agent-runtime/model.ts
T
lhk229 bcbf398b65 移除退役策划界面样式
删除旧 GDD 审批卡与策划窄条的孤儿 CSS

更新聊天布局测试以覆盖现役 DirectCodex 选择器

保留设计 Agent 和通用项目工作台表现
2026-09-15 07:05:46 +00:00

2286 lines
71 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type {
GameCreationAppManifest,
GameCreationAppTaskState,
} from '../../../../../packages/shared/src/contracts/gameCreationApp';
import {
PROJECT_SUPERVISOR_AGENT_ID,
PROJECT_SUPERVISOR_PLAN_SOURCE,
seedManifest,
} from '../../app/constants';
import type {
AgentConversationSessionListResult,
AgentConversationSessionRecord,
AgentGoalRecord,
AgentRuntimeEventRecord,
AgentRuntimePendingToolActionSummary,
AgentRuntimePlanStep,
AgentRuntimeResponseStream,
AgentRuntimeResult,
AgentRuntimeState,
AgentRuntimeSteerResult,
AgentRuntimeTaskQueueSummary,
AgentRuntimeTaskRecord,
ChatMessage,
LocalConversationMessageRecord,
TauriInvoke,
} from '../../app/types';
const AGENT_RUNTIME_PUBLIC_STATUS_MESSAGE_ID_PREFIX = 'runtime-public-status-';
const AGENT_RUNTIME_TASK_MESSAGE_ID_PREFIX = 'runtime-task-';
const AGENT_RUNTIME_STEER_MESSAGE_ID_PREFIX = 'agent-steer-';
const AGENT_RUNTIME_MESSAGE_CORRELATION_PATTERN = /^[0-9a-f]{32}$/;
export type ProjectSupervisorRuntimeSubmission = {
runProfile: 'standard' | 'autonomous-game-build';
source: 'project-supervisor-gui' | typeof PROJECT_SUPERVISOR_PLAN_SOURCE;
};
export function resolveProjectSupervisorRuntimeSubmission({
workspaceProjectKind,
orchestrationMode,
supervisorChatOnly,
planningEntry = false,
}: {
workspaceProjectKind: 'web' | 'godot' | 'cocos';
orchestrationMode: 'single-supervisor' | 'professional-dag';
supervisorChatOnly: boolean;
planningEntry?: boolean;
}): ProjectSupervisorRuntimeSubmission {
// 立项策划入口独立成链:命中时固定 standard + plan source,不参与下面按
// godot / chat / DAG 的分流,也不改动做游戏与做素材的既有路由。
if (planningEntry && workspaceProjectKind === 'web') {
return {
runProfile: 'standard',
source: PROJECT_SUPERVISOR_PLAN_SOURCE,
};
}
if (workspaceProjectKind === 'godot' || supervisorChatOnly) {
return {
runProfile: 'standard',
source: 'project-supervisor-gui',
};
}
if (orchestrationMode !== 'single-supervisor') {
return {
runProfile: 'autonomous-game-build',
source: 'project-supervisor-gui',
};
}
return {
runProfile: 'standard',
source: 'project-supervisor-gui',
};
}
function agentRuntimeMessageCorrelationId(
messageId: string | null | undefined,
) {
const normalized = messageId?.trim() ?? '';
const prefix = normalized.startsWith(
AGENT_RUNTIME_PUBLIC_STATUS_MESSAGE_ID_PREFIX,
)
? AGENT_RUNTIME_PUBLIC_STATUS_MESSAGE_ID_PREFIX
: normalized.startsWith(AGENT_RUNTIME_TASK_MESSAGE_ID_PREFIX)
? AGENT_RUNTIME_TASK_MESSAGE_ID_PREFIX
: normalized.startsWith(AGENT_RUNTIME_STEER_MESSAGE_ID_PREFIX)
? AGENT_RUNTIME_STEER_MESSAGE_ID_PREFIX
: null;
if (!prefix) {
return null;
}
const correlationId = normalized.slice(prefix.length).split('-', 1)[0] ?? '';
return AGENT_RUNTIME_MESSAGE_CORRELATION_PATTERN.test(correlationId)
? correlationId
: null;
}
export function createLocalConversationDraftMessage(
content: string,
updatedAt = Date.now(),
): LocalConversationMessageRecord {
return {
schemaVersion: 'game-creator-conversation.v1',
role: 'assistant',
content,
agentId: null,
updatedAt,
};
}
export function createAgentChatRunId(prefix: string) {
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
}
export function parseAgentGoalItems(value: string) {
return value
.split(/\r?\n/)
.map((item) => item.trim())
.filter(Boolean);
}
export function formatAgentConversationSessionMeta(
session: AgentConversationSessionRecord,
sessions: AgentConversationSessionRecord[],
activeSessionId: string | null,
) {
const details = [String(session.messageCount)];
if (session.sessionId === activeSessionId) {
details.push('活动');
}
if (session.forkedFromSessionId) {
const source = sessions.find(
(candidate) => candidate.sessionId === session.forkedFromSessionId,
);
const sourceLabel = source?.title ?? session.forkedFromSessionId;
details.push(
session.forkedMessageCount === null ||
session.forkedMessageCount === undefined
? `分支自 ${sourceLabel}`
: `分支自 ${sourceLabel}(${session.forkedMessageCount} 条)`,
);
}
return details.join(' · ');
}
export function agentRuntimePlanStepsFromPlan(
plan: string[],
): AgentRuntimePlanStep[] {
return plan
.filter((item) => item.trim().length > 0)
.slice(0, 8)
.map((title, index) => ({
step: title,
index,
title,
status: index === 0 ? 'active' : 'pending',
detail: null,
updatedAt: 0,
}));
}
export function agentRuntimePlanStepText(step: AgentRuntimePlanStep) {
return (step.step ?? step.title ?? '').trim();
}
export function normalizeAgentRuntimePlanStep(
step: AgentRuntimePlanStep,
fallbackIndex: number,
): AgentRuntimePlanStep | null {
const text = agentRuntimePlanStepText(step);
if (!text) {
return null;
}
const index =
typeof step.index === 'number' &&
Number.isFinite(step.index) &&
step.index >= 0
? Math.trunc(step.index)
: fallbackIndex;
return {
...step,
step: text,
index,
title: step.title?.trim() || text,
status: step.status?.trim() || 'pending',
detail: step.detail ?? null,
updatedAt:
typeof step.updatedAt === 'number' && Number.isFinite(step.updatedAt)
? step.updatedAt
: 0,
};
}
export function normalizeAgentRuntimePlanStepList(
steps: AgentRuntimePlanStep[],
) {
return steps
.map((step, index) => normalizeAgentRuntimePlanStep(step, index))
.filter((step): step is AgentRuntimePlanStep => step !== null);
}
export function normalizeAgentRuntimePlanSteps(
state: AgentRuntimeState,
previous?: AgentRuntimeState | null,
) {
if (state.planSteps && state.planSteps.length > 0) {
return normalizeAgentRuntimePlanStepList(state.planSteps);
}
if (state.planSteps !== undefined && state.planRevision !== undefined) {
return [];
}
if (previous?.planSteps && previous.planSteps.length > 0) {
return normalizeAgentRuntimePlanStepList(previous.planSteps);
}
return agentRuntimePlanStepsFromPlan(state.plan ?? []);
}
export function normalizeAgentRuntimeActivePlanStepIndex(
state: AgentRuntimeState,
planSteps: AgentRuntimePlanStep[],
previous?: AgentRuntimeState | null,
) {
if (state.activePlanStepIndex !== undefined) {
if (state.activePlanStepIndex === null) {
return null;
}
return Number.isFinite(state.activePlanStepIndex) &&
state.activePlanStepIndex >= 0
? Math.trunc(state.activePlanStepIndex)
: null;
}
if (previous?.activePlanStepIndex !== undefined) {
return previous.activePlanStepIndex;
}
const activeStepIndex = planSteps.findIndex((step) =>
['active', 'in_progress', 'running'].includes(step.status),
);
if (activeStepIndex < 0) {
return null;
}
return planSteps[activeStepIndex]?.index ?? activeStepIndex;
}
export function sameAgentRuntimeRun(
state: AgentRuntimeState,
previous?: AgentRuntimeState | null,
) {
return Boolean(
previous &&
previous.agentId === state.agentId &&
previous.sessionId === state.sessionId &&
previous.runId === state.runId,
);
}
export function normalizeAgentRuntimePlanRevision(
revision: number | undefined,
previousRevision: number | undefined,
) {
if (
typeof revision === 'number' &&
Number.isFinite(revision) &&
revision >= 0
) {
return Math.trunc(revision);
}
return previousRevision;
}
export function shouldReplaceAgentRuntimePlan(
state: AgentRuntimeState,
previous?: AgentRuntimeState | null,
) {
if (!previous) {
return true;
}
const previousRevision = normalizeAgentRuntimePlanRevision(
previous.planRevision,
undefined,
);
if (previousRevision === undefined) {
return true;
}
const nextRevision = normalizeAgentRuntimePlanRevision(
state.planRevision,
undefined,
);
return nextRevision !== undefined && nextRevision >= previousRevision;
}
export function normalizeAgentRuntimeState(
state: AgentRuntimeState,
previous?: AgentRuntimeState | null,
): AgentRuntimeState {
const previousPlanState = sameAgentRuntimeRun(state, previous)
? previous
: null;
const replacePlan = shouldReplaceAgentRuntimePlan(state, previousPlanState);
const planState = replacePlan ? state : previousPlanState!;
const planFallbackState = replacePlan ? previousPlanState : null;
const planSteps = normalizeAgentRuntimePlanSteps(
planState,
planFallbackState,
);
return {
...state,
currentGoal: state.currentGoal ?? state.currentTask ?? '',
goalId:
state.goalId !== undefined
? state.goalId
: (previousPlanState?.goalId ?? null),
goalRevision:
state.goalRevision !== undefined
? state.goalRevision
: (previousPlanState?.goalRevision ?? 0),
goalStatus:
state.goalStatus !== undefined
? state.goalStatus
: (previousPlanState?.goalStatus ?? null),
goalOutcome:
state.goalOutcome !== undefined
? state.goalOutcome
: (previousPlanState?.goalOutcome ?? null),
goalConstraints:
state.goalConstraints !== undefined
? state.goalConstraints
: (previousPlanState?.goalConstraints ?? []),
goalVerification:
state.goalVerification !== undefined
? state.goalVerification
: (previousPlanState?.goalVerification ?? []),
waitingOn: state.waitingOn ?? agentRuntimeWaitingOnFromPhase(state.phase),
nextStep: state.nextStep ?? agentRuntimeNextStepFromPhase(state.phase),
loopIteration: state.loopIteration ?? previous?.loopIteration ?? 0,
startedAt:
state.startedAt ??
(previousPlanState?.startedAt && previousPlanState.startedAt > 0
? previousPlanState.startedAt
: undefined),
maxLoopIterations:
state.maxLoopIterations ?? previous?.maxLoopIterations ?? 3,
toolActionBudget: state.toolActionBudget ?? previous?.toolActionBudget ?? 3,
contextUsage: state.contextUsage ??
previous?.contextUsage ?? {
estimatedInputTokens: 0,
autoCompactTokenLimit: 0,
lastPromptTokens: null,
lastCompletionTokens: null,
lastTotalTokens: null,
compactionRevision: 0,
compactionCount: 0,
lastCompactionTrigger: null,
lastCompactedAt: null,
},
plan: planState.plan ?? planFallbackState?.plan ?? [],
planRevision: normalizeAgentRuntimePlanRevision(
planState.planRevision,
planFallbackState?.planRevision,
),
planExplanation:
planState.planExplanation ?? planFallbackState?.planExplanation,
planSteps,
activePlanStepIndex: normalizeAgentRuntimeActivePlanStepIndex(
planState,
planSteps,
planFallbackState,
),
recentToolCalls: state.recentToolCalls ?? previous?.recentToolCalls ?? [],
userInputRequest:
state.userInputRequest !== undefined
? state.userInputRequest
: (previousPlanState?.userInputRequest ?? null),
toolPolicy: state.toolPolicy ??
previous?.toolPolicy ?? {
allowedTools: state.allowedTools ?? [],
autoTools: [],
confirmTools: [],
deniedTools: [],
updatedAt: 0,
},
taskQueue: {
...(state.taskQueue ??
previousPlanState?.taskQueue ?? {
total: 0,
pending: 0,
running: 0,
waitingForConfirmation: 0,
waitingForUserInput: 0,
paused: 0,
cancelled: 0,
completed: 0,
failed: 0,
latestRunId: null,
updatedAt: 0,
}),
paused:
state.taskQueue?.paused ?? previousPlanState?.taskQueue?.paused ?? 0,
waitingForUserInput:
state.taskQueue?.waitingForUserInput ??
previousPlanState?.taskQueue?.waitingForUserInput ??
0,
},
recentEvents: state.recentEvents ?? previous?.recentEvents ?? [],
recentTasks: state.recentTasks ?? previous?.recentTasks ?? [],
};
}
export function mergeAgentGoalRecordFromRuntime(
goal: AgentGoalRecord | null,
runtime: AgentRuntimeState,
) {
if (
!goal ||
!runtime.goalId ||
goal.goalId !== runtime.goalId ||
goal.agentId !== runtime.agentId ||
goal.sessionId !== runtime.sessionId
) {
return goal;
}
return {
...goal,
revision: runtime.goalRevision ?? goal.revision,
status: runtime.goalStatus ?? goal.status,
outcome: runtime.goalOutcome ?? goal.outcome,
constraints: runtime.goalConstraints ?? goal.constraints,
verification: runtime.goalVerification ?? goal.verification,
updatedAt: Math.max(goal.updatedAt, runtime.updatedAt),
};
}
export function mergeAgentRuntimeStateIntoMap(
current: Record<string, AgentRuntimeState | undefined>,
incoming: AgentRuntimeState,
preserveNewerRun: boolean,
) {
const previous = Object.values(current).find(
(runtime) => runtime?.agentId === incoming.agentId,
);
const mergedRuntime =
preserveNewerRun &&
previous &&
(sameAgentRuntimeRun(incoming, previous)
? previous.updatedAt > incoming.updatedAt
: previous.updatedAt >= incoming.updatedAt)
? previous
: normalizeAgentRuntimeState(incoming, previous);
const next = { ...current };
for (const [key, runtime] of Object.entries(next)) {
if (runtime?.agentId === incoming.agentId) {
delete next[key];
}
}
next[mergedRuntime.agentId] = mergedRuntime;
next[mergedRuntime.taskId] = mergedRuntime;
return next;
}
export function agentRuntimeStateFromResult(
result: AgentRuntimeResult,
previous?: AgentRuntimeState | null,
): AgentRuntimeState {
const acceptedRunId = result.acceptedRunId?.trim();
const acceptedTask = acceptedRunId
? (result.recentTasks ?? result.state.recentTasks ?? []).find(
(task) => task.runId === acceptedRunId,
)
: null;
const state =
acceptedTask && result.state.runId !== acceptedRunId
? {
...result.state,
agentId: acceptedTask.agentId,
taskId: acceptedTask.taskId,
sessionId: acceptedTask.sessionId,
runId: acceptedTask.runId,
source: acceptedTask.source,
parentAgentId: acceptedTask.parentAgentId ?? null,
parentRunId: acceptedTask.parentRunId ?? null,
delegationId: acceptedTask.delegationId ?? null,
goalId: acceptedTask.goalId ?? null,
goalRevision: acceptedTask.goalRevision ?? 0,
goalStatus: acceptedTask.goalStatus ?? null,
currentTask: acceptedTask.task,
currentGoal: acceptedTask.task,
status: acceptedTask.status,
phase: acceptedTask.phase,
currentAction: acceptedTask.currentAction,
waitingOn: agentRuntimeWaitingOnFromPhase(acceptedTask.phase),
nextStep: agentRuntimeNextStepFromPhase(acceptedTask.phase),
plan: [],
planRevision: undefined,
planExplanation: undefined,
planSteps: [],
activePlanStepIndex: null,
observations: [],
recentToolCalls: [],
pendingToolAction: null,
userInputRequest: null,
lastResponse: null,
error: acceptedTask.error,
startedAt: acceptedTask.updatedAt,
updatedAt: acceptedTask.updatedAt,
}
: result.state;
return normalizeAgentRuntimeState(
{
...state,
taskQueue: result.taskQueue ?? state.taskQueue,
recentEvents: result.recentEvents ?? state.recentEvents,
recentTasks: result.recentTasks ?? state.recentTasks,
userInputRequest:
result.userInputRequest !== undefined
? result.userInputRequest
: state.userInputRequest,
},
previous,
);
}
export function normalizeProjectSupervisorResponseStream(
stream: AgentRuntimeResponseStream | null | undefined,
runtime: AgentRuntimeState,
) {
if (!stream) {
return null;
}
const integerFields = [
stream.appliedSteerCursor,
stream.responseRevision,
stream.sequence,
stream.startedAt,
stream.updatedAt,
];
if (
stream.schemaVersion !== 'game-creator-runtime-response-stream.v1' ||
stream.agentId !== PROJECT_SUPERVISOR_AGENT_ID ||
stream.agentId !== runtime.agentId ||
stream.taskId !== runtime.taskId ||
stream.sessionId !== runtime.sessionId ||
stream.runId !== runtime.runId ||
stream.requestKind !== 'final-reply' ||
!stream.requestSlot.trim() ||
integerFields.some(
(value) =>
typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0,
) ||
stream.startedAt <= 0 ||
stream.updatedAt < stream.startedAt ||
typeof stream.accumulatedText !== 'string' ||
Array.from(stream.accumulatedText).length > 32_000 ||
/<\/?think>/i.test(stream.accumulatedText)
) {
return null;
}
if (
typeof runtime.appliedSteerCursor === 'number' &&
stream.appliedSteerCursor !== runtime.appliedSteerCursor
) {
return null;
}
if ((runtime.queuedSteerCount ?? 0) > 0) {
return null;
}
if (
typeof runtime.loopIteration === 'number' &&
Number.isSafeInteger(runtime.loopIteration) &&
stream.requestSlot !==
`final-reply-loop-${runtime.loopIteration}-revision-${stream.responseRevision}`
) {
return null;
}
if (
(stream.status === 'streaming' || stream.status === 'ready') &&
(runtime.status !== 'running' ||
!['response', 'finalizing'].includes(runtime.phase))
) {
return null;
}
if (stream.status === 'ready' && !stream.accumulatedText.trim()) {
return null;
}
return stream;
}
export function sameProjectSupervisorResponseStream(
left: AgentRuntimeResponseStream,
right: AgentRuntimeResponseStream,
) {
return (
left.agentId === right.agentId &&
left.taskId === right.taskId &&
left.sessionId === right.sessionId &&
left.runId === right.runId &&
left.requestKind === right.requestKind &&
left.requestSlot === right.requestSlot &&
left.appliedSteerCursor === right.appliedSteerCursor &&
left.responseRevision === right.responseRevision
);
}
/**
* Durable identity for one final-reply response. The response text is
* intentionally excluded so two turns with identical wording remain
* distinct messages in the conversation.
*/
export function projectSupervisorResponseStreamIdentity(
stream: Pick<
AgentRuntimeResponseStream,
'runId' | 'requestSlot' | 'responseRevision'
>,
) {
return [stream.runId, stream.requestSlot, stream.responseRevision].join(
'\u001f',
);
}
export function mergeProjectSupervisorResponseStream(
current: AgentRuntimeResponseStream | null,
incoming: AgentRuntimeResponseStream | null | undefined,
runtime: AgentRuntimeState,
) {
const currentForRuntime = normalizeProjectSupervisorResponseStream(
current,
runtime,
);
if (!incoming) {
return null;
}
const next = normalizeProjectSupervisorResponseStream(incoming, runtime);
if (!next) {
return currentForRuntime;
}
if (next.status !== 'streaming' && next.status !== 'ready') {
return null;
}
if (
!currentForRuntime ||
!sameProjectSupervisorResponseStream(currentForRuntime, next)
) {
return next;
}
if (next.sequence <= currentForRuntime.sequence) {
return currentForRuntime;
}
return next;
}
export function conversationContainsProjectSupervisorResponseStream(
messages: LocalConversationMessageRecord[],
stream: AgentRuntimeResponseStream | null | undefined,
) {
return Boolean(
stream?.accumulatedText.trim() &&
messages.some(
(message) =>
message.role === 'assistant' &&
message.content === stream.accumulatedText &&
message.updatedAt >= stream.startedAt,
),
);
}
export function agentRuntimeWaitingOnFromPhase(phase: string) {
switch (phase) {
case 'planning':
return 'Agent 输出计划或回复';
case 'llm':
return 'Agent LLM 回复';
case 'action':
return '工具观察结果';
case 'waiting-for-confirmation':
return '开发者确认 Agent 工具动作';
case 'waiting-for-user-input':
return '你的澄清回答';
case 'cancelling':
return '当前 LLM 或工具调用返回';
case 'pausing':
return '当前动作到达安全边界';
case 'paused':
return '开发者恢复持久目标';
case 'response':
return 'Agent 整理最终回复';
case 'completed':
case 'idle':
return '开发者下一轮输入';
case 'cancelled':
return '开发者下一轮输入';
case 'failed':
return '开发者处理失败';
default:
return '当前任务推进';
}
}
export function agentRuntimeNextStepFromPhase(phase: string) {
switch (phase) {
case 'planning':
return '等待 Agent 输出计划或回复';
case 'action':
return '等待工具观察结果';
case 'waiting-for-confirmation':
return '等待开发者确认工具动作';
case 'waiting-for-user-input':
return '提交全部回答后继续同一 Run';
case 'cancelling':
return '取消完成后可重试该任务或提交新任务';
case 'pausing':
return '等待持久目标暂停';
case 'paused':
return '恢复持久目标后继续同一 Run';
case 'response':
return '等待 Agent 整理最终回复';
case 'completed':
case 'idle':
return '等待下一轮输入';
case 'cancelled':
return '可重试该后台任务或提交新任务';
case 'failed':
return '等待开发者处理失败';
default:
return '继续推进当前任务';
}
}
export function agentRuntimeStartStatus(result: AgentRuntimeResult) {
const pendingTask = (result.recentTasks ?? []).find(
(task) => task.status === 'pending',
);
return pendingTask
? `已加入后台队列:${pendingTask.runId}`
: `已启动后台任务:${result.state.runId}`;
}
export function agentRuntimeStartedRunId(
result: AgentRuntimeResult,
requestedRunId: string,
) {
if (result.acceptedRunId?.trim()) {
return result.acceptedRunId.trim();
}
if (
result.recentTasks?.some((task) => task.runId === requestedRunId) ||
result.state.runId === requestedRunId
) {
return requestedRunId;
}
return (
result.taskQueue?.latestRunId ??
result.state.taskQueue?.latestRunId ??
result.state.runId ??
requestedRunId
);
}
export function isAgentRuntimeTerminalState(runtime: AgentRuntimeState) {
return (
['completed', 'failed', 'cancelled', 'needs-reconciliation'].includes(
runtime.phase,
) ||
[
'completed',
'failed',
'cancelled',
'idle',
'needs-reconciliation',
].includes(runtime.status)
);
}
export function isAgentRuntimeSteerableState(runtime: AgentRuntimeState) {
if (isAgentRuntimeTerminalState(runtime)) {
return false;
}
const blockedStates = [
'cancelling',
'finalizing',
'needs-reconciliation',
'waiting-for-user-input',
];
if (
blockedStates.includes(runtime.status) ||
blockedStates.includes(runtime.phase)
) {
return false;
}
return (
['running', 'waiting-for-confirmation'].includes(runtime.status) ||
[
'planning',
'llm',
'action',
'observation',
'waiting-for-confirmation',
'waiting-for-isolated-join',
'waiting-for-delegate-receipts',
'waiting-for-manifest-tasks',
'response',
].includes(runtime.phase)
);
}
export function agentRuntimeNeedsUserInput(
runtime: AgentRuntimeState | null | undefined,
) {
return Boolean(
runtime?.userInputRequest ||
runtime?.status === 'waiting-for-user-input' ||
runtime?.phase === 'waiting-for-user-input',
);
}
export const AGENT_RUNTIME_USER_INPUT_REQUEST_TOOL = 'user.input_request';
export const AGENT_RUNTIME_PLAN_SUBMIT_GDD_TOOL = 'plan.submit_gdd';
// 有两个 pending 动作不是「等待批准的动作」,而是某张专用卡的载体:Runtime 把内容停在
// pending 上,真正的交互面是另一个组件。
//
// - `user.input_request`:子 Agent 以 needs-user-input 终态退出后,Runtime 在 parent-wake
// 屏障处按信封原文构造这个 pending(`ensure_static_delegate_user_input_wait_at_locked`),
// 同一份问题再投影成 `userInputRequest`,交互面是问答卡。
// - `plan.submit_gdd`:策划子 Agent 提交 Fast GDD 后,`planning/pending.json` 的
// 的批准 / 修改 / 退回。
//
// 两者都没有「确认 / 拒绝」语义:`user.input_request` 从来不在
// `GAME_CREATION_APP_COMMANDS` 的 confirm 集合里;`plan.submit_gdd` 更是被运行时策略明确
// 拒绝转成通用确认 pending(「Runtime-owned create-only 提交」),点确认必然失败。
//
// 通用待确认卡按 `tool` + `inputSummary` 渲染,套到它们身上就是把同一个请求画两遍——标题
// 是原始工具名,副标题是 `questionCount=… · questionsSha256=…` 这种给日志看的取证摘要,
// 还会把真正该看的那张卡压在下面。所以这里统一把通用确认面判掉。
const AGENT_RUNTIME_DEDICATED_CARD_TOOLS = new Set([
AGENT_RUNTIME_USER_INPUT_REQUEST_TOOL,
AGENT_RUNTIME_PLAN_SUBMIT_GDD_TOOL,
]);
export function agentRuntimePendingActionHasDedicatedCard(
action: AgentRuntimePendingToolActionSummary | null | undefined,
) {
return Boolean(action && AGENT_RUNTIME_DEDICATED_CARD_TOOLS.has(action.tool));
}
export function matchingAgentRuntimeForSteer(
runtimes: Array<AgentRuntimeState | null | undefined>,
agentId: string,
sessionId: string | null,
requestedRunProfile: NonNullable<
AgentRuntimeState['runProfile']
> = 'standard',
requestedSource?: string,
) {
if (!sessionId) {
return null;
}
return (
runtimes.find(
(runtime) =>
runtime?.agentId === agentId &&
runtime.sessionId === sessionId &&
(runtime.runProfile ?? 'standard') === requestedRunProfile &&
(!requestedSource || runtime.source === requestedSource) &&
isAgentRuntimeSteerableState(runtime),
) ?? null
);
}
export function projectSupervisorActiveSessionIdFromList(
sessionList: AgentConversationSessionListResult,
) {
if (
!sessionList ||
sessionList.agentId !== PROJECT_SUPERVISOR_AGENT_ID ||
!Array.isArray(sessionList.sessions)
) {
throw new Error('项目总控 Agent 会话列表返回格式无效');
}
const activeSession = sessionList.activeSessionId
? sessionList.sessions.find(
(session) =>
session.sessionId === sessionList.activeSessionId &&
session.archivedAt === null,
)
: null;
return (
activeSession?.sessionId ||
sessionList.sessions.find((session) => session.archivedAt === null)
?.sessionId ||
null
);
}
export async function readProjectSupervisorActiveSessionId(
invoke: TauriInvoke,
projectPath: string,
) {
try {
const sessionList = await invoke<AgentConversationSessionListResult>(
'list_game_creator_agent_sessions',
{
projectPath,
agentId: PROJECT_SUPERVISOR_AGENT_ID,
},
);
return projectSupervisorActiveSessionIdFromList(sessionList);
} catch (error) {
if (isMissingAgentSessionCommandError(error)) {
return null;
}
throw error;
}
}
export async function ensureProjectSupervisorActiveSessionId(
invoke: TauriInvoke,
projectPath: string,
) {
const existingSessionId = await readProjectSupervisorActiveSessionId(
invoke,
projectPath,
);
if (existingSessionId) {
return existingSessionId;
}
const sessionList = await invoke<AgentConversationSessionListResult>(
'create_game_creator_agent_session',
{
projectPath,
agentId: PROJECT_SUPERVISOR_AGENT_ID,
title: '项目总控',
},
);
const sessionId = projectSupervisorActiveSessionIdFromList(sessionList);
if (!sessionId) {
throw new Error('项目总控 Agent active Session 创建失败');
}
return sessionId;
}
export async function submitProjectSupervisorRuntimeTask({
invoke,
projectPath,
sessionId,
prompt,
runtime,
runProfile,
source,
}: {
invoke: TauriInvoke;
projectPath: string;
sessionId: string;
prompt: string;
runtime: AgentRuntimeState | null;
runProfile: 'standard' | 'autonomous-game-build';
source: ProjectSupervisorRuntimeSubmission['source'];
}) {
const steerRuntime = matchingAgentRuntimeForSteer(
[runtime],
PROJECT_SUPERVISOR_AGENT_ID,
sessionId,
runProfile,
source,
);
if (steerRuntime) {
const steer = await invoke<AgentRuntimeSteerResult>(
'steer_game_creator_agent_runtime_task',
{
projectPath,
agentId: PROJECT_SUPERVISOR_AGENT_ID,
sessionId,
runId: steerRuntime.runId,
steerId: createAgentChatRunId('project-supervisor-steer'),
instruction: prompt,
runProfile,
source,
},
);
return {
mode: 'steer' as const,
runtimeResult: steer.runtime,
acceptedRunId: steerRuntime.runId,
};
}
const requestedRunId = createAgentChatRunId('project-supervisor-task');
const runtimeResult = await invoke<AgentRuntimeResult>(
'start_game_creator_supervisor_runtime_task',
{
projectPath,
sessionId,
task: prompt,
runId: requestedRunId,
runProfile,
source,
},
);
return {
mode: 'start' as const,
runtimeResult,
acceptedRunId: agentRuntimeStartedRunId(runtimeResult, requestedRunId),
};
}
export function agentRuntimeSteerStatus(result: AgentRuntimeSteerResult) {
const runId = result.runtime.state.runId;
if (result.status === 'restarted') {
return `目标已变化,正在新 Run 重新理解并执行:${runId}`;
}
if (result.providerInterrupted) {
return `智能服务已根据追加指令调整,旧请求已安全中断:${runId}`;
}
if (result.interruptDecision === false) {
return `智能服务已回复且判定无需中断,当前 Run 继续:${runId}`;
}
if (result.interruptDecision === true) {
return `智能服务已判定需要改向;旧请求已结束或新规划已开始:${runId}`;
}
if (result.status === 'applied') {
return `追加指令已应用,当前 Run 正在继续:${runId}`;
}
return `追加指令已排队,等待当前 Run 应用:${runId}`;
}
export function agentRuntimeCancelStatus(
runtime: AgentRuntimeState,
runId: string,
) {
return runtime.status === 'cancelling' || runtime.phase === 'cancelling'
? `正在取消后台任务:${runId}`
: `已取消后台任务:${runId}`;
}
function agentRuntimeProviderRetryStatus(runtime: AgentRuntimeState) {
if (runtime.phase !== 'waiting-for-provider-retry') {
return null;
}
const currentAction = runtime.currentAction?.trim();
const waitingOn = runtime.waitingOn?.trim();
const safeCurrentAction =
currentAction &&
/^(?:Goal 已恢复,)?Provider (?:上游返回 HTTP \d{3}|瞬态故障),准备自动重试 \d+\/\d+$/.test(
currentAction,
)
? currentAction
: null;
const safeWaitingOn =
waitingOn && /^预计 \d+ 秒后重试$/.test(waitingOn) ? waitingOn : null;
if (safeCurrentAction && safeWaitingOn) {
return `${safeCurrentAction.replaceAll('Provider', '智能服务')};${safeWaitingOn}`;
}
if (safeCurrentAction) {
return safeCurrentAction.replaceAll('Provider', '智能服务');
}
const legacyAttempt = currentAction?.match(
/^(?:等待 Provider 瞬态重试|Goal 已恢复,继续等待 Provider 瞬态重试) (\d+)\/(\d+)$/,
);
const retryProgress =
legacyAttempt?.[1] && legacyAttempt[2]
? `,准备自动重试 ${legacyAttempt[1]}/${legacyAttempt[2]}`
: ',正在准备自动重试';
const safeFallback = `智能服务暂时不可用${retryProgress}`;
return safeWaitingOn ? `${safeFallback};${safeWaitingOn}` : safeFallback;
}
export function agentRuntimeConversationStatus(runtime: AgentRuntimeState) {
if (isAgentRuntimeTerminalState(runtime)) {
if (
runtime.status === 'needs-reconciliation' ||
runtime.phase === 'needs-reconciliation'
) {
return 'Agent 运行状态需要核对,正在同步记录';
}
if (runtime.status === 'failed' || runtime.phase === 'failed') {
return 'Agent 运行失败,正在同步错误记录';
}
if (runtime.status === 'cancelled' || runtime.phase === 'cancelled') {
return 'Agent 任务已取消,正在同步对话';
}
return 'Agent 已完成,正在同步回复';
}
if (runtime.status === 'pending' || runtime.phase === 'queued') {
return 'Agent 任务已排队,正在等待执行';
}
if (
runtime.status === 'waiting-for-user-input' ||
runtime.phase === 'waiting-for-user-input'
) {
return 'Agent 需要你补充信息';
}
if (
runtime.status === 'pausing' ||
runtime.phase === 'pausing' ||
runtime.goalStatus === 'pause-requested'
) {
return '持久目标正在暂停';
}
if (
runtime.status === 'paused' ||
runtime.phase === 'paused' ||
runtime.goalStatus === 'paused'
) {
return '持久目标已暂停';
}
const providerRetryStatus = agentRuntimeProviderRetryStatus(runtime);
if (providerRetryStatus) {
return providerRetryStatus;
}
const waitingOn =
runtime.waitingOn ?? agentRuntimeWaitingOnFromPhase(runtime.phase);
return waitingOn ? `Agent 正在运行,等待${waitingOn}` : 'Agent 正在运行';
}
export function projectSupervisorChatRuntimeStatus(runtime: AgentRuntimeState) {
if (runtime.status === 'idle' || runtime.phase === 'idle') {
return '等待输入';
}
if (
runtime.status === 'needs-reconciliation' ||
runtime.phase === 'needs-reconciliation'
) {
return runtime.error
? projectRuntimeVisibleError(runtime.error, '项目总控 Agent', true)
: '项目总控 Agent 运行状态需要核对,请打开运行详情后重试';
}
if (isAgentRuntimeTerminalState(runtime)) {
if (runtime.status === 'failed' || runtime.phase === 'failed') {
return runtime.error
? projectRuntimeVisibleError(runtime.error, '项目总控 Agent', true)
: 'Agent 运行失败';
}
if (runtime.status === 'cancelled' || runtime.phase === 'cancelled') {
return '本轮已取消';
}
return '本轮已完成';
}
return agentRuntimeConversationStatus(runtime);
}
export function formatAgentRuntimeEvent(event: AgentRuntimeEventRecord) {
const isFailureEvent = [
'error',
'turn.failed',
'turn.budget_exhausted',
].includes(event.eventType);
const visibleDetail = isFailureEvent ? null : event.detail;
const publicFailureText =
isFailureEvent && typeof event.publicText === 'string'
? event.publicText.trim()
: '';
const summary =
publicFailureText ||
event.summary ||
visibleDetail ||
(isFailureEvent ? 'Agent Runtime 本轮处理失败。' : event.runId);
const detail =
visibleDetail && visibleDetail !== summary ? ` · ${visibleDetail}` : '';
return `${event.eventType} · ${event.status} / ${event.phase} · ${summary}${detail}`;
}
export function formatAgentRuntimeTaskQueue(
queue: AgentRuntimeTaskQueueSummary | null | undefined,
) {
if (!queue || queue.total <= 0) {
return null;
}
const parts = [
`pending ${queue.pending}`,
`running ${queue.running}`,
`waiting ${queue.waitingForConfirmation ?? 0}`,
`needsInput ${queue.waitingForUserInput ?? 0}`,
];
if ((queue.paused ?? 0) > 0) {
parts.push(`paused ${queue.paused}`);
}
parts.push(
`cancelled ${queue.cancelled ?? 0}`,
`completed ${queue.completed}`,
`failed ${queue.failed}`,
`total ${queue.total}`,
);
if (queue.latestRunId) {
parts.push(`latest ${queue.latestRunId}`);
}
return `任务队列:${parts.join(' · ')}`;
}
export function formatAgentRuntimeLoopProgress(
runtime: Pick<
AgentRuntimeState,
'loopIteration' | 'maxLoopIterations' | 'toolActionBudget'
>,
) {
const loopIteration = runtime.loopIteration ?? 0;
if (loopIteration <= 0) {
return null;
}
return `Loop:${loopIteration}/${runtime.maxLoopIterations ?? 3} · 工具预算 ${
runtime.toolActionBudget ?? 3
}`;
}
export function formatAgentRuntimePlanStep(
step: AgentRuntimePlanStep,
fallbackIndex = 0,
) {
const index = step.index ?? fallbackIndex;
return `#${index + 1} ${step.status} · ${agentRuntimePlanStepText(step)}${
step.detail ? ` · ${step.detail}` : ''
}`;
}
export function agentRuntimeActivePlanStep(
runtime: Pick<AgentRuntimeState, 'planSteps' | 'activePlanStepIndex'>,
) {
const steps = runtime.planSteps ?? [];
const activePlanStepIndex = runtime.activePlanStepIndex;
if (activePlanStepIndex !== null && activePlanStepIndex !== undefined) {
const indexedStep =
steps.find((step) => step.index === activePlanStepIndex) ??
steps[activePlanStepIndex];
if (indexedStep) {
return indexedStep;
}
}
return (
steps.find((step) =>
['active', 'in_progress', 'running'].includes(step.status),
) ??
steps.find((step) => step.status === 'pending') ??
null
);
}
export function agentRuntimeCanCancel(status: string) {
return [
'pending',
'running',
'waiting-for-confirmation',
'waiting-for-user-input',
].includes(status);
}
export function agentRuntimeCanRetry(status: string) {
return ['cancelled', 'failed', 'completed', 'idle'].includes(status);
}
export function agentGoalStatusIsPaused(status: string | null | undefined) {
return ['pause-requested', 'pausing', 'paused'].includes(status ?? '');
}
export function agentGoalStatusIsTerminal(status: string | null | undefined) {
return ['completed', 'cleared'].includes(status ?? '');
}
export function agentRuntimeCanConfirm(status: string) {
return status === 'waiting-for-confirmation';
}
export function formatAgentRuntimeDelegationSource(runtime: {
source: string;
parentAgentId?: string | null;
parentRunId?: string | null;
delegationId?: string | null;
}) {
if (runtime.source === 'agent-delegate-receipt') {
return [
'来源:委派回执',
runtime.delegationId ? `委派:${runtime.delegationId}` : null,
]
.filter(Boolean)
.join(' · ');
}
const parts = [
runtime.parentAgentId ? `委派自:${runtime.parentAgentId}` : null,
runtime.parentRunId ? `父 run:${runtime.parentRunId}` : null,
runtime.delegationId ? `委派:${runtime.delegationId}` : null,
].filter(Boolean);
return parts.length > 0 ? parts.join(' · ') : null;
}
export function createAgentRuntimeUserInputResponseId() {
const entropy =
typeof globalThis.crypto?.randomUUID === 'function'
? globalThis.crypto.randomUUID()
: Math.random().toString(36).slice(2);
return `app-user-input-${Date.now().toString(36)}-${entropy}`.slice(0, 160);
}
export function isMissingAgentSessionCommandError(error: unknown) {
const message = error instanceof Error ? error.message : String(error);
const normalized = message.toLowerCase();
return (
normalized.includes('list_game_creator_agent_sessions') &&
(normalized.includes('not found') ||
normalized.includes('unknown command') ||
normalized.includes('unexpected invoke') ||
normalized.includes('unexpected command') ||
normalized.includes('不存在') ||
normalized.includes('未找到'))
);
}
export function isMissingAgentRuntimeResumeCommandError(error: unknown) {
const message = error instanceof Error ? error.message : String(error);
const normalized = message.toLowerCase();
return (
normalized.includes('resume_game_creator_agent_runtime_tasks') &&
(normalized.includes('not found') ||
normalized.includes('unknown command') ||
normalized.includes('unexpected invoke') ||
normalized.includes('unexpected command') ||
normalized.includes('不存在') ||
normalized.includes('未找到'))
);
}
export function isMissingAgentGoalCommandError(error: unknown) {
const message = error instanceof Error ? error.message : String(error);
const normalized = message.toLowerCase();
return (
normalized.includes('game_creator_agent_goal') &&
(normalized.includes('not found') ||
normalized.includes('unknown command') ||
normalized.includes('unexpected invoke') ||
normalized.includes('unexpected command') ||
normalized.includes('不存在') ||
normalized.includes('未找到'))
);
}
export function createDefaultChatMessages(): ChatMessage[] {
return [
{
role: 'assistant',
text: '想做什么游戏?',
},
];
}
export function isRuntimeConfigMissingError(message: string) {
return (
message.includes('LLM 未配置') ||
message.includes('LLM base_url 未配置') ||
message.includes('LLM model 未配置') ||
message.includes('editorApi.apiKey')
);
}
export function projectNameFromPath(projectPath: string) {
return (
projectPath
.split(/[\\/]/)
.map((part) => part.trim())
.filter(Boolean)
.pop() || '未命名游戏原型'
);
}
export function projectWorkspaceStatusForDisplay(workspaceStatus: string) {
const openedPrefix = '已打开:';
if (!workspaceStatus.startsWith(openedPrefix)) {
return workspaceStatus;
}
const projectPath = workspaceStatus.slice(openedPrefix.length).trim();
if (!/[\\/]/u.test(projectPath)) {
return workspaceStatus;
}
return `${openedPrefix}${projectNameFromPath(projectPath)}`;
}
export function mergeProjectSupervisorConversation(
projectRecords: LocalConversationMessageRecord[],
supervisorRecords: LocalConversationMessageRecord[],
): ChatMessage[] {
const supervisorRecordIndexByCorrelation = new Map<string, number>();
supervisorRecords.forEach((record, index) => {
if (record.role !== 'user') {
return;
}
const correlationId = agentRuntimeMessageCorrelationId(record.messageId);
if (
correlationId &&
!supervisorRecordIndexByCorrelation.has(correlationId)
) {
supervisorRecordIndexByCorrelation.set(correlationId, index);
}
});
const records = [
...projectRecords.map((record, index) => {
const runtimeOwned = Boolean(
record.messageId
?.trim()
.startsWith(AGENT_RUNTIME_PUBLIC_STATUS_MESSAGE_ID_PREFIX),
);
const accepted =
runtimeOwned &&
record.content === '任务已接收,项目总控 Agent 正在启动处理。';
const correlationId = agentRuntimeMessageCorrelationId(record.messageId);
const supervisorRecordIndex = correlationId
? supervisorRecordIndexByCorrelation.get(correlationId)
: undefined;
const runtimeRecordIndex =
supervisorRecordIndex ?? supervisorRecords.length + index;
return {
record,
runtimeOwned,
sameSecondLane: runtimeOwned ? 1 : 0,
sameSecondOrder: runtimeOwned
? runtimeRecordIndex * 4 + (accepted ? 1 : 2)
: index,
stableIndex: index,
};
}),
...supervisorRecords.map((record, index) => {
const userMessage = record.role === 'user';
return {
record,
runtimeOwned: true,
sameSecondLane: 1,
// Conversation timestamps have second precision. Runtime task and
// public status IDs carry the same opaque run correlation digest, so
// status ordering follows its actual task record instead of guessing
// from independent user/accepted/terminal category counters.
sameSecondOrder: index * 4 + (userMessage ? 0 : 3),
stableIndex: projectRecords.length + index,
};
}),
]
.filter(
({ record }) => record.role === 'user' || record.role === 'assistant',
)
.sort(
(left, right) =>
left.record.updatedAt - right.record.updatedAt ||
left.sameSecondLane - right.sameSecondLane ||
left.sameSecondOrder - right.sameSecondOrder ||
left.stableIndex - right.stableIndex,
);
const seen = new Set<string>();
const messages: ChatMessage[] = [];
for (const { record, runtimeOwned } of records) {
const messageId = record.messageId?.trim();
const identity = messageId
? `message:${messageId}`
: `identity:${JSON.stringify([
record.role,
record.agentId,
record.updatedAt,
record.content,
])}`;
if (seen.has(identity)) {
continue;
}
seen.add(identity);
messages.push({
role: record.role as ChatMessage['role'],
text: record.content,
messageId: messageId ?? null,
agentId: record.agentId,
updatedAt: record.updatedAt,
runtimeOwned,
});
}
return messages.length > 0 ? messages : createDefaultChatMessages();
}
export function projectSupervisorRuntimeStatusLabel(
runtime: AgentRuntimeState | null,
runtimeError: string,
) {
if (
runtime?.status === 'needs-reconciliation' ||
runtime?.phase === 'needs-reconciliation'
) {
return '待核对';
}
if (
runtimeError ||
runtime?.status === 'failed' ||
runtime?.phase === 'failed'
) {
return '失败';
}
if (!runtime) {
return null;
}
if (
runtime.userInputRequest ||
runtime.status === 'waiting-for-user-input' ||
runtime.phase === 'waiting-for-user-input'
) {
return '需要回答';
}
if (
runtime.pendingToolAction ||
runtime.status === 'waiting-for-confirmation' ||
runtime.phase === 'waiting-for-confirmation'
) {
return '等待确认';
}
if (
runtime.phase === 'waiting-for-delegate-receipts' ||
runtime.phase === 'waiting-for-isolated-join'
) {
return '等待专业 Agent';
}
if (runtime.phase === 'waiting-for-manifest-tasks') {
return '等待项目任务';
}
if (
['action', 'observation', 'executing'].includes(runtime.phase) ||
runtime.status === 'executing'
) {
return '执行';
}
if (runtime.phase === 'response' || runtime.phase === 'finalizing') {
return '回复中';
}
if (runtime.status === 'cancelled' || runtime.phase === 'cancelled') {
return '已取消';
}
if (runtime.status === 'completed' || runtime.phase === 'completed') {
return '已完成';
}
if (runtime.status === 'idle') {
return null;
}
return '分析';
}
export function projectSupervisorCollaboratingAgentRuntimes(
supervisorRuntime: AgentRuntimeState | null,
runtimeByAgentId: Record<string, AgentRuntimeState | undefined>,
) {
if (!supervisorRuntime?.runId) {
return [];
}
const runtimesByAgentId = new Map<string, AgentRuntimeState>();
for (const runtime of Object.values(runtimeByAgentId)) {
const isVisibleChildSource = [
'agent-delegate',
'agent-delegate-retry',
].includes(runtime?.source ?? '');
if (
!runtime ||
runtime.agentId === PROJECT_SUPERVISOR_AGENT_ID ||
!isVisibleChildSource ||
runtime.parentAgentId !== PROJECT_SUPERVISOR_AGENT_ID ||
runtime.parentRunId !== supervisorRuntime.runId
) {
continue;
}
const previous = runtimesByAgentId.get(runtime.agentId);
if (!previous || runtime.updatedAt >= previous.updatedAt) {
runtimesByAgentId.set(runtime.agentId, runtime);
}
}
return Array.from(runtimesByAgentId.values()).sort((left, right) =>
left.agentId.localeCompare(right.agentId),
);
}
export function projectProfessionalAgentLabel(agentId: string) {
if (agentId === 'project-planning') {
return '立项策划 Agent';
}
if (agentId === 'design-foundation') {
return '玩法策划 Agent';
}
if (agentId === 'art-asset-plan') {
return '美术资源计划 Agent';
}
if (agentId === 'code-prototype') {
return '程序原型 Agent';
}
if (agentId.includes('balance')) {
return '数值 Agent';
}
if (agentId.includes('design')) {
return '设计实现 Agent';
}
if (agentId.includes('art')) {
return '美术 Agent';
}
if (agentId.includes('code')) {
return '程序 Agent';
}
if (agentId.includes('audio')) {
return '音频 Agent';
}
if (
agentId.includes('publish') ||
agentId.includes('quality') ||
agentId.includes('preview')
) {
return '发布 Agent';
}
return agentId;
}
export function isAgentFinalizationMessageId(
messageId: string | null | undefined,
) {
return /^agent-finalization-[0-9a-f]{32}$/u.test(messageId ?? '');
}
export function projectRuntimeStatusPresentation(runtime: AgentRuntimeState) {
if (runtime.phase === 'needs-reconciliation') {
return { label: '待核对', tone: 'failed' };
}
if (
runtime.userInputRequest ||
runtime.status === 'waiting-for-user-input' ||
runtime.phase === 'waiting-for-user-input'
) {
return { label: '待回答', tone: 'waiting' };
}
if (
runtime.pendingToolAction ||
runtime.status === 'waiting-for-confirmation' ||
runtime.phase === 'waiting-for-confirmation'
) {
return { label: '待确认', tone: 'waiting' };
}
if (runtime.status === 'failed' || runtime.phase === 'failed') {
return { label: '失败', tone: 'failed' };
}
if (runtime.status === 'completed' || runtime.phase === 'completed') {
return { label: '已完成', tone: 'completed' };
}
if (runtime.status === 'cancelled' || runtime.phase === 'cancelled') {
return { label: '已取消', tone: 'idle' };
}
if (runtime.status === 'idle' || runtime.phase === 'idle') {
return { label: '等待中', tone: 'idle' };
}
if (runtime.phase === 'planning') {
return { label: '分析中', tone: 'running' };
}
if (
runtime.phase === 'waiting-for-delegate-receipts' ||
runtime.phase === 'waiting-for-isolated-join'
) {
return { label: '协作中', tone: 'running' };
}
if (runtime.phase === 'waiting-for-manifest-tasks') {
return {
label: '项目任务中',
tone: 'running',
};
}
return { label: '执行中', tone: 'running' };
}
export function projectRuntimePlanProgress(runtime: AgentRuntimeState) {
const steps = (runtime.planSteps ?? []).filter(
(step) => agentRuntimePlanStepText(step).length > 0,
);
return {
completed: steps.filter((step) => step.status === 'completed').length,
total: steps.length,
active: agentRuntimeActivePlanStep(runtime),
};
}
export function projectRuntimeVisibleCurrentWork(runtime: AgentRuntimeState) {
const providerRetryStatus = agentRuntimeProviderRetryStatus(runtime);
if (providerRetryStatus) {
return providerRetryStatus;
}
const activePlanStep = agentRuntimeActivePlanStep(runtime);
if (activePlanStep) {
const stepText = agentRuntimePlanStepText(activePlanStep);
if (stepText) {
return stepText;
}
}
if (
runtime.pendingToolAction ||
runtime.status === 'waiting-for-confirmation' ||
runtime.phase === 'waiting-for-confirmation'
) {
return '等待确认后继续工作';
}
if (
runtime.userInputRequest ||
runtime.status === 'waiting-for-user-input' ||
runtime.phase === 'waiting-for-user-input'
) {
return '等待回答后继续工作';
}
if (runtime.phase === 'planning') {
return '正在分析需求并制定计划';
}
if (
runtime.phase === 'waiting-for-delegate-receipts' ||
runtime.phase === 'waiting-for-isolated-join'
) {
return '正在等待专业 Agent 回执';
}
if (runtime.phase === 'waiting-for-manifest-tasks') {
return '正在等待项目专业任务完成';
}
if (runtime.status === 'failed' || runtime.phase === 'failed') {
return '本轮工作执行失败';
}
if (runtime.status === 'completed' || runtime.phase === 'completed') {
return '本轮工作已完成';
}
const currentTask = runtime.currentTask?.trim();
if (currentTask) {
return currentTask;
}
return '正在执行当前任务';
}
export const MUD_POINT_INSUFFICIENT_INTERRUPTION_MESSAGE =
'泥点余额不足,本轮游戏生成已中断。请充值后发送“继续”,系统会从当前项目进度接着完成。';
export function isMudPointInsufficientRuntimeError(message: string) {
const normalized = message.trim().toLowerCase();
return (
message.includes('泥点余额不足') ||
message.includes('可消费泥点不足:') ||
normalized.includes('kind=mud-points-insufficient')
);
}
const DIRECT_FAILURE_SENSITIVE_ASSIGNMENT_PATTERN =
/(?:^|[^\w])(?:proxy-authorization|authorization|set-cookie|cookie|access[_ -]?token|accesstoken|refresh[_ -]?token|refreshtoken|oauth[_ -]?token|id[_ -]?token|auth[_ -]?token|authtoken|client[_ -]?secret|clientsecret|private[_ -]?key|privatekey|secret[_ -]?key|secretkey|x-api-key|x_api_key|api[_ -]?key|apikey|credentials?|password|token|secret|bearer)\s*["']?\s*[:=]>?\s*("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|(?:Bearer\s+(?:\[[^\]]+\]|<[^>]+>|[^\s,;,;&}\]]+)|\[[^\]]+\]|<[^>]+>|[^\s,;,;&}\]]+))/gi;
const DIRECT_FAILURE_BEARER_PATTERN =
/(?:^|[^\w])Bearer\s+(\[[^\]]+\]|<[^>]+>|[^\s,;,;&}\]]+)/gi;
function isRedactedDirectFailureValue(value: string) {
const normalized = value.trim();
return (
normalized.length === 0 ||
/^(?:Bearer\s+)?(?:\[redacted-secret\]|\[redacted-sensitive-field\]|\[已隐藏凭据\]|\[已隐藏敏感字段\]|\[已隐藏链接\]|\[已隐藏路径\]|\[已隐藏配置\]|\[已隐藏敏感信息\]|<redacted-secret>|<redacted-url>|<absolute-path>|\[redacted-config\]|\[redacted sensitive context\])$/i.test(
normalized,
)
);
}
function containsUnredactedDirectFailureSecret(value: string) {
// Provider payloads are sometimes embedded as escaped JSON in a single
// diagnostic line (`{\"token\":\"...\"}`). Normalize only the quote
// escapes for the detector; the displayed value still goes through the
// backend's redaction and marker conversion unchanged.
const normalizedValue = value.replace(/\\(["'])/g, '$1');
DIRECT_FAILURE_SENSITIVE_ASSIGNMENT_PATTERN.lastIndex = 0;
let match: RegExpExecArray | null;
while (
(match =
DIRECT_FAILURE_SENSITIVE_ASSIGNMENT_PATTERN.exec(normalizedValue)) !==
null
) {
const rawCandidate = (match[1] ?? '').trim();
const candidate =
rawCandidate.length >= 2 &&
((rawCandidate.startsWith('"') && rawCandidate.endsWith('"')) ||
(rawCandidate.startsWith("'") && rawCandidate.endsWith("'")))
? rawCandidate.slice(1, -1).trim()
: rawCandidate;
if (!isRedactedDirectFailureValue(candidate)) {
return true;
}
}
DIRECT_FAILURE_BEARER_PATTERN.lastIndex = 0;
while (
(match = DIRECT_FAILURE_BEARER_PATTERN.exec(normalizedValue)) !== null
) {
if (!isRedactedDirectFailureValue(match[1] ?? '')) {
return true;
}
}
return false;
}
function redactDirectFailureMarkers(value: string) {
return value
.replace(/<redacted-url>/gi, '[已隐藏链接]')
.replace(/\$PROJECT_ROOT|<absolute-path>/g, '[已隐藏路径]')
.replace(/\[redacted-secret\]/gi, '[已隐藏凭据]')
.replace(/\[redacted-sensitive-field\]/gi, '[已隐藏敏感字段]')
.replace(/\[redacted-config\]/gi, '[已隐藏配置]')
.replace(/\[redacted sensitive context\]/gi, '[已隐藏敏感信息]');
}
function directCodexDiagnosticFailureDetail(message: string) {
const trimmed = message.trim();
const match =
/^direct-codex-failure:v1 stage=(request|art-preparation|code-generation|browser-validation|version-registration) retryable=(true|false) summary=(.+?);建议:(.+?);(?:已保存脱敏项目诊断|未能保存项目诊断)$/u.exec(
trimmed,
);
if (!match) {
return null;
}
const [, stage, retryable, rawSummary, rawHint] = match;
if (!stage || !retryable || !rawSummary || !rawHint) {
return null;
}
const summary = redactDirectFailureMarkers(rawSummary)
.replace(/\s+/g, ' ')
.trim()
.slice(0, 320);
const hint = redactDirectFailureMarkers(rawHint)
.replace(/\s+/g, ' ')
.trim()
.slice(0, 180);
const combined = `${summary} ${hint}`;
if (
containsUnredactedDirectFailureSecret(combined) ||
/https?:\/\/|(?:^|[\s::])\/?(?:users|home|var|tmp|private)\/|[A-Z]:\\Users\\/i.test(
combined,
)
) {
return null;
}
const stageLabel = {
request: '智能创作请求失败',
'art-preparation': '平台资源准备失败',
'code-generation': '代码生成失败',
'browser-validation': '真实试玩未通过',
'version-registration': '项目版本登记失败',
}[stage];
if (!stageLabel) {
return null;
}
if (!summary || !hint) {
return null;
}
return `${stageLabel}:${summary}。${hint}${
retryable === 'true' ? '(可直接重试)' : ''
}`;
}
function directPlatformFailureDetail(message: string) {
const trimmed = message.trim();
if (!trimmed) {
return null;
}
const lower = trimmed.toLowerCase();
if (
!(
lower.includes('陶泥儿美术包生成失败') ||
lower.includes('平台图片生成任务失败') ||
lower.includes('external editor') ||
lower.includes('透明美术图集') ||
lower.includes('图集切片')
)
) {
return null;
}
if (
containsUnredactedDirectFailureSecret(trimmed) ||
/https?:\/\/|(?:^|[\\s::])\/?(?:users|home|var|tmp|private)\/|[A-Z]:\\Users\\/i.test(
trimmed,
)
) {
return null;
}
const safe = redactDirectFailureMarkers(trimmed)
.replace(/(?:;|;|\s)operationId\s*=\s*[^;;\s]+/gi, '')
.replace(/(?:;|;)\s*externalGenerationJobId\s*=\s*[^;;\s]+/gi, '')
.replace(/\s+/g, ' ')
.trim()
.slice(0, 280);
return safe || null;
}
function directCodexFailureDetail(message: string) {
const trimmed = message.trim();
const marker = 'direct-codex-error:';
const markerIndex = trimmed.indexOf(marker);
if (markerIndex < 0) {
return null;
}
const detail = trimmed.slice(markerIndex + marker.length).trim();
if (!detail) {
return null;
}
if (
/(?:authorization|bearer|api[_ -]?key|token|secret|cookie|set-cookie)/i.test(
detail,
)
) {
return null;
}
const safe = detail
.replace(/https?:\/\/[^\s]+/gi, '[已隐藏链接]')
.replace(
/(?:[A-Z]:\\|\\\\|\/(?:Users|home|var|tmp|private)\/)[^\s]+/gi,
'[已隐藏路径]',
)
.replace(/\s+/g, ' ')
.trim()
.slice(0, 280);
return safe || null;
}
function directRuntimeFailureDetail(message: string) {
const trimmed = message.trim();
if (
!(
trimmed.startsWith('Codex 已返回,但客户端登记生成产物失败:') ||
trimmed.includes('陶泥儿美术包不完整,已终止代码生成') ||
trimmed.includes('陶泥儿规范图生成后未形成可用平台合同') ||
trimmed.includes('陶泥儿美术包生成返回后未形成可用的已登记平台素材合同')
)
) {
return null;
}
if (
/(?:authorization|bearer|api[_ -]?key|token|secret|cookie|set-cookie|https?:\/\/|(?:^|[\\s::])\/?(?:users|home|var|tmp|private)\/|[A-Z]:\\Users\\)/i.test(
trimmed,
)
) {
return null;
}
const safe = trimmed.replace(/\s+/g, ' ').trim().slice(0, 320);
return safe || null;
}
export function projectRuntimeVisibleError(
message: string,
subject: string,
preservePublicMessage = false,
) {
const visibleMessage = message.trim();
const normalized = visibleMessage.toLowerCase();
if (isRuntimeConfigMissingError(message)) {
return '运行时配置未完成,请先打开配置';
}
if (isMudPointInsufficientRuntimeError(message)) {
return MUD_POINT_INSUFFICIENT_INTERRUPTION_MESSAGE;
}
const directDiagnosticDetail = directCodexDiagnosticFailureDetail(message);
if (directDiagnosticDetail) {
return `${subject}:${directDiagnosticDetail}`;
}
const directFailureDetail = directPlatformFailureDetail(message);
if (directFailureDetail) {
return `${subject}:${directFailureDetail}`;
}
const codexAppServerKind = visibleMessage.match(
/(?:^|[\s::])kind=codex-app-server-([a-z-]+)(?=\s|$)/,
)?.[1];
if (codexAppServerKind) {
const detail = {
'context-window-exceeded': '模型上下文已超限,请缩小任务范围后重试',
'session-budget-exceeded': '本次会话预算已耗尽,请缩小任务范围或新建任务',
'usage-limit-exceeded': '智能创作用量已达上限,请检查账户额度后重试',
unauthorized: '智能服务鉴权失败,请重新登录后重试',
'request-too-large': '模型请求体过大,请减少参考图或上下文后重试',
'bad-request': '智能创作请求无效,请稍后重试',
'cyber-policy': '智能创作安全策略拒绝了本次请求,请调整任务内容',
'sandbox-error': '智能创作隔离环境启动失败,请重试或检查本机环境',
'thread-rollback-failed': '智能创作会话恢复失败,请新建任务后重试',
'active-turn-not-steerable':
'当前智能创作任务无法追加指令,请等待结束后重试',
other: '智能创作执行失败,请查看运行详情后重试',
}[codexAppServerKind];
if (detail) {
return `${subject} ${detail}`;
}
}
const directCodexAppServerKind = visibleMessage.match(
/codex-app-server-error:([a-z-]+)/,
)?.[1];
if (directCodexAppServerKind) {
const detail = {
'context-window-exceeded': '模型上下文已超限,请缩小任务范围后重试',
'session-budget-exceeded': '本次会话预算已耗尽,请缩小任务范围或新建任务',
'usage-limit-exceeded': '用量已达上限,请检查账户额度后重试',
unauthorized: '鉴权失败,请重新登录后重试',
'request-too-large': '模型请求体过大,请减少参考图或上下文后重试',
'bad-request': '请求无效,请稍后重试',
'cyber-policy': '安全策略拒绝了本次请求,请调整任务内容',
'sandbox-error': '工作区隔离启动失败,请检查项目目录后重试',
other: '未完成本次执行,请查看项目文件是否已修改后再重试',
}[directCodexAppServerKind];
if (detail) {
return `${subject} ${detail}`;
}
}
if (visibleMessage.includes('codex-app-server-terminal-unknown:')) {
return `${subject}服务或网络在执行中断开,最终状态未知;请先检查项目文件是否已修改,再决定是否重试`;
}
const directCodexDetail = directCodexFailureDetail(visibleMessage);
if (directCodexDetail) {
return `${subject}:智能服务执行失败:${directCodexDetail}`;
}
const directRuntimeDetail = directRuntimeFailureDetail(visibleMessage);
if (directRuntimeDetail) {
return `${subject}:${directRuntimeDetail}`;
}
const exhaustedUpstreamRetry = visibleMessage.match(
/(?:^|[\s::])kind=upstream-(\d{3}) httpStatus=(\d{3}) fingerprint=[0-9a-f]{64} chars=\d+ retryAttempt=(\d+) maxRetries=(\d+) retryState=exhausted\s*$/,
);
if (exhaustedUpstreamRetry) {
const [, kindStatusText, httpStatusText, retryAttemptText, maxRetriesText] =
exhaustedUpstreamRetry;
if (
!kindStatusText ||
!httpStatusText ||
!retryAttemptText ||
!maxRetriesText
) {
return `${subject} 执行失败,请稍后重试`;
}
const httpStatus = Number.parseInt(httpStatusText, 10);
const retryAttempt = Number.parseInt(retryAttemptText, 10);
const maxRetries = Number.parseInt(maxRetriesText, 10);
if (
kindStatusText === httpStatusText &&
httpStatus >= 500 &&
httpStatus <= 599 &&
retryAttempt === maxRetries &&
maxRetries >= 0 &&
maxRetries <= 4_294_967_295
) {
return `${subject} 上游服务返回 HTTP ${httpStatus};自动重试已耗尽(${retryAttempt}/${maxRetries})`;
}
}
if (
normalized.includes('kind=transport') ||
normalized.includes('transport') ||
normalized.includes('connect') ||
normalized.includes('network') ||
normalized.includes('tls')
) {
return `${subject} 服务连接失败,请稍后重试`;
}
if (normalized.includes('timeout') || normalized.includes('超时')) {
return `${subject} 响应超时,请稍后重试`;
}
if (
normalized.includes('unauthorized') ||
normalized.includes('api key') ||
normalized.includes('401') ||
normalized.includes('鉴权')
) {
return `${subject} 鉴权失败,请检查运行时配置`;
}
if (
normalized.includes('rate limit') ||
normalized.includes('429') ||
normalized.includes('quota') ||
normalized.includes('额度') ||
normalized.includes('限流')
) {
return `${subject} 服务繁忙,请稍后重试`;
}
if (
normalized.includes('needs-reconciliation') ||
normalized.includes('result-unknown') ||
normalized.includes('终态未知') ||
normalized.includes('需要人工核对')
) {
return `${subject} 运行状态需要核对,请打开运行详情后重试`;
}
if (
normalized.includes('budget-exhausted') ||
normalized.includes('预算耗尽') ||
normalized.includes('预算已耗尽')
) {
return `${subject} 本轮预算已耗尽,请缩小任务范围后重试`;
}
if (
normalized.includes('missing expected artifact') ||
normalized.includes('expected artifact') ||
normalized.includes('缺少预期产物') ||
normalized.includes('缺少 expected artifact')
) {
return `${subject} 未生成要求的产物,请查看任务要求后重试`;
}
if (
normalized.includes('verification') ||
normalized.includes('project.verify') ||
normalized.includes('preview.validate') ||
normalized.includes('验证未通过') ||
normalized.includes('验证失败')
) {
return `${subject} 项目验证未通过,请查看运行详情并修复后重试`;
}
if (
normalized.includes('policy') ||
normalized.includes('permission') ||
normalized.includes('拒绝') ||
normalized.includes('禁止') ||
normalized.includes('不允许')
) {
return `${subject} 被项目权限或安全策略阻止,请检查审批配置`;
}
if (
normalized.includes('落盘失败') ||
normalized.includes('持久化失败') ||
normalized.includes('写入失败')
) {
return `${subject} 保存运行记录失败,请检查项目目录后重试`;
}
if (
normalized.includes('planning_invalid') ||
normalized.includes('gdd 结构无效') ||
normalized.includes('策划输出格式')
) {
return `${subject} 输出格式不符合当前 GDD 结构,请重试`;
}
const containsInternalDiagnostics =
normalized.includes('agentllm.') ||
/(?:^|[\s::])kind=/.test(normalized) ||
/(?:^|\s)(?:fingerprint|chars|contentchars|contentbytes|sha256)=/.test(
normalized,
) ||
/(?:authorization|bearer|api[_ -]?key|token|secret|cookie|set-cookie)/i.test(
visibleMessage,
) ||
/(?:https?:\/\/|(?:^|\s)\/(?:home|Users|var|tmp)\/)/.test(visibleMessage);
const isKnownPublicMessage =
visibleMessage === 'LLM 服务暂时不可用,请检查配置后重试' ||
visibleMessage === '请先回答项目总控 Agent 当前的澄清问题' ||
visibleMessage === '待回答请求已变更,请刷新 Runtime 状态' ||
/^回答提交失败:[^\r\n]{1,80}$/.test(visibleMessage);
if (
preservePublicMessage &&
isKnownPublicMessage &&
!containsInternalDiagnostics
) {
return visibleMessage;
}
return `${subject} 执行失败,请稍后重试`;
}
export function projectSupervisorVisibleConversationText(
message: string,
role: ChatMessage['role'] | 'tool' = 'assistant',
subject = '项目总控 Agent',
) {
const failurePrefix = '后台任务失败:';
if (role !== 'assistant' || !message.startsWith(failurePrefix)) {
return message;
}
return projectRuntimeVisibleError(
message.slice(failurePrefix.length),
subject,
true,
);
}
export function projectSupervisorChatMessageText(
message: Pick<ChatMessage, 'text' | 'role'>,
) {
return message.role === 'assistant'
? projectSupervisorVisibleConversationText(message.text, message.role)
: message.text;
}
export function projectRuntimeVisibleToolSummary(summary: string) {
return summary
.split('·')
.map((part) => part.trim())
.filter(
(part) =>
part.length > 0 &&
!/^(contentChars|contentBytes|sha256|fingerprint)=/i.test(part),
)
.join(' · ');
}
export function projectSupervisorPendingActionPresentation(
action: AgentRuntimePendingToolActionSummary,
) {
if (action.tool === 'agent.delegate') {
const agentId =
action.inputSummary?.match(/(?:^|·)\s*agentId=([^·]+)/)?.[1]?.trim() ??
'';
const agentLabel = agentId
? projectProfessionalAgentLabel(agentId)
: '专业 Agent';
const isRepair =
action.inputSummary?.includes('repairOf=') &&
!action.inputSummary.includes('repairOf=null');
return {
title: isRepair ? `安排${agentLabel}返工` : `安排${agentLabel}执行任务`,
detail: isRepair
? '确认后将在当前项目创建一轮带原验收合同的返工任务'
: '确认后将在当前项目启动新的专业任务',
};
}
return {
title: action.tool,
detail: action.inputSummary
? projectRuntimeVisibleToolSummary(action.inputSummary)
: '确认后继续当前项目任务',
};
}
export function projectSupervisorPendingRepairMatchesProfessional(
action: AgentRuntimePendingToolActionSummary | null | undefined,
runtime: AgentRuntimeState,
) {
const summary = action?.inputSummary ?? '';
return Boolean(
action?.tool === 'agent.delegate' &&
runtime.delegationId &&
summary.includes(`agentId=${runtime.agentId}`) &&
summary.includes(`repairOf=${runtime.delegationId}`),
);
}
export function formatProjectRuntimeUpdatedAt(updatedAt: number) {
if (!Number.isFinite(updatedAt) || updatedAt <= 0) {
return '更新时间未知';
}
const milliseconds =
updatedAt < 1_000_000_000_000 ? updatedAt * 1000 : updatedAt;
return `${new Date(milliseconds).toLocaleTimeString('zh-CN', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
})} 更新`;
}
export function formatProjectSupervisorCompactProgress(
runtime: AgentRuntimeState,
collaboratingAgentCount: number,
) {
const planSteps = (runtime.planSteps ?? []).filter(
(step) => agentRuntimePlanStepText(step).length > 0,
);
const completedPlanStepCount = planSteps.filter(
(step) => step.status === 'completed',
).length;
const activePlanStep = agentRuntimeActivePlanStep(runtime);
const currentPlanStep = activePlanStep
? agentRuntimePlanStepText(activePlanStep)
: planSteps.length > 0 && completedPlanStepCount === planSteps.length
? '已完成'
: '暂无';
const waitingOn =
runtime.waitingOn?.trim() || agentRuntimeWaitingOnFromPhase(runtime.phase);
const nextStep =
runtime.nextStep?.trim() || agentRuntimeNextStepFromPhase(runtime.phase);
return [
`计划完成:${completedPlanStepCount}/${planSteps.length}`,
`当前步骤:${currentPlanStep}`,
`等待:${waitingOn}`,
`下一步:${nextStep}`,
`专业 Agent 协作:${collaboratingAgentCount}`,
].join(' · ');
}
export function taskRowsFromManifest(
manifest: GameCreationAppManifest,
): GameCreationAppTaskState[] {
return manifest.tasks.length > 0 ? manifest.tasks : seedManifest.tasks;
}
export function agentConversationId(task: GameCreationAppTaskState) {
return task.id;
}
export function formatAgentRecentRuntimeTask(task: AgentRuntimeTaskRecord) {
const delegationSource = formatAgentRuntimeDelegationSource(task);
const goalSource = task.goalId
? `Goal ${task.goalStatus ?? '-'} · revision ${task.goalRevision ?? 0}`
: null;
const failed =
task.status === 'failed' ||
['failed', 'budget-exhausted', 'needs-reconciliation'].includes(task.phase);
const failureDetail = failed
? task.terminalDetail?.trim() || task.error?.trim() || null
: null;
const failureSummary = failureDetail
? projectRuntimeVisibleError(
failureDetail,
projectProfessionalAgentLabel(task.agentId),
true,
)
: null;
return `${task.status} / ${task.phase} · ${
task.task || task.currentAction || task.runId
}${goalSource ? ` · ${goalSource}` : ''}${
delegationSource ? ` · ${delegationSource}` : ''
}${failureSummary ? ` · 失败原因:${failureSummary}` : ''}`;
}