Files
Genarrative/apps/ai-game-creator-shell/src/features/agent-runtime/model.ts
T
AIGameCreator App 427a8c151a
Project CI / Frontend tests (push) Failing after 24s
Project CI / Repository checks (push) Failing after 46s
Project CI / Backend tests (push) Successful in 4m14s
Project CI / Native shell tests (push) Failing after 7m17s
完善自主构建正式产物与终端测试
新增终端 AppData 配置向导并强化跨平台密钥与进程树安全

升级自主构建为十六任务产物 DAG 并接入两类真实画布素材

完善完成基线、并发补验、验证证据与受限画布返工合同

强化确定性与真实 Swarm E2E 的 exactly-once 和正式产物校验

修复确定性 Provider 终态修复后的复验交付状态

同步更新客户端实现计划、决策记录和踩坑说明
2026-07-27 04:04:06 +08:00

1550 lines
45 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, seedManifest } from '../../app/constants';
import type {
AgentConversationSessionListResult,
AgentConversationSessionRecord,
AgentGoalRecord,
AgentRuntimeEventRecord,
AgentRuntimePendingToolActionSummary,
AgentRuntimePlanStep,
AgentRuntimeResponseStream,
AgentRuntimeResult,
AgentRuntimeState,
AgentRuntimeSteerResult,
AgentRuntimeTaskQueueSummary,
AgentRuntimeTaskRecord,
ChatMessage,
LocalConversationMessageRecord,
TauriInvoke,
} from '../../app/types';
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,
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 {
return normalizeAgentRuntimeState(
{
...result.state,
taskQueue: result.taskQueue ?? result.state.taskQueue,
recentEvents: result.recentEvents ?? result.state.recentEvents,
recentTasks: result.recentTasks ?? result.state.recentTasks,
userInputRequest:
result.userInputRequest !== undefined
? result.userInputRequest
: result.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
);
}
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'].includes(runtime.phase) ||
['completed', 'failed', 'cancelled', 'idle'].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 function matchingAgentRuntimeForSteer(
runtimes: Array<AgentRuntimeState | null | undefined>,
agentId: string,
sessionId: string | null,
requestedRunProfile: NonNullable<
AgentRuntimeState['runProfile']
> = 'standard',
) {
if (!sessionId) {
return null;
}
return (
runtimes.find(
(runtime) =>
runtime?.agentId === agentId &&
runtime.sessionId === sessionId &&
(runtime.runProfile ?? 'standard') === requestedRunProfile &&
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,
}: {
invoke: TauriInvoke;
projectPath: string;
sessionId: string;
prompt: string;
runtime: AgentRuntimeState | null;
runProfile: 'standard' | 'autonomous-game-build';
}) {
const steerRuntime = matchingAgentRuntimeForSteer(
[runtime],
PROJECT_SUPERVISOR_AGENT_ID,
sessionId,
runProfile,
);
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,
},
);
return { mode: 'steer' as const, runtimeResult: steer.runtime };
}
const runtimeResult = await invoke<AgentRuntimeResult>(
'start_game_creator_supervisor_runtime_task',
{
projectPath,
sessionId,
task: prompt,
runId: createAgentChatRunId('project-supervisor-task'),
runProfile,
},
);
return { mode: 'start' as const, runtimeResult };
}
export function agentRuntimeSteerStatus(result: AgentRuntimeSteerResult) {
const runId = result.runtime.state.runId;
if (result.providerInterrupted) {
return `已中断 Provider,正在同一 Run 重新规划:${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}`;
}
export function agentRuntimeConversationStatus(runtime: AgentRuntimeState) {
if (isAgentRuntimeTerminalState(runtime)) {
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 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 (isAgentRuntimeTerminalState(runtime)) {
if (runtime.status === 'failed' || runtime.phase === 'failed') {
return runtime.error || 'Agent 运行失败';
}
if (runtime.status === 'cancelled' || runtime.phase === 'cancelled') {
return '本轮已取消';
}
return '本轮已完成';
}
return agentRuntimeConversationStatus(runtime);
}
export function formatAgentRuntimeEvent(event: AgentRuntimeEventRecord) {
const summary = event.summary || event.detail || event.runId;
const detail =
event.detail && event.detail !== summary ? ` · ${event.detail}` : '';
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 mergeProjectSupervisorConversation(
projectRecords: LocalConversationMessageRecord[],
supervisorRecords: LocalConversationMessageRecord[],
): ChatMessage[] {
const records = [
...projectRecords.map((record, index) => ({
record,
runtimeOwned: false,
stableIndex: index,
})),
...supervisorRecords.map((record, index) => ({
record,
runtimeOwned: true,
stableIndex: projectRecords.length + index,
})),
]
.filter(
({ record }) => record.role === 'user' || record.role === 'assistant',
)
.sort(
(left, right) =>
left.record.updatedAt - right.record.updatedAt ||
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 (
runtimeError ||
runtime?.status === 'failed' ||
runtime?.phase === 'failed' ||
runtime?.phase === 'needs-reconciliation'
) {
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)) {
if (
!runtime ||
runtime.agentId === PROJECT_SUPERVISOR_AGENT_ID ||
!['agent-delegate', 'agent-delegate-retry'].includes(runtime.source) ||
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 === '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.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 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 function projectRuntimeVisibleError(
message: string,
subject: string,
preservePublicMessage = false,
) {
const visibleMessage = message.trim();
const normalized = visibleMessage.toLowerCase();
if (isRuntimeConfigMissingError(message)) {
return '运行时配置未完成,请先打开配置';
}
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} 服务繁忙,请稍后重试`;
}
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 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;
return `${task.status} / ${task.phase} · ${
task.task || task.currentAction || task.runId
}${goalSource ? ` · ${goalSource}` : ''}${
delegationSource ? ` · ${delegationSource}` : ''
}`;
}