Files
Genarrative/apps/ai-game-creator-shell/src/features/agent-runtime/model.ts
T
kdletters c2ef8631f8 明确泥点不足的游戏生成中断原因
统一余额不足分类与稳定原因码
阻止泥点不足进入重试和对账状态
在游戏聊天与阶段记录展示固定安全说明
补齐 API、Runtime 与前端回归测试
同步技术方案与项目决策记录
2026-08-04 18:58:48 +08:00

1737 lines
51 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
);
}
/**
* Durable identity for one final-reply response. The response text is
* intentionally excluded so two turns with identical wording remain
* distinct messages in game-chat.
*/
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,
),
);
}
/**
* Keep final responses that were committed into the game-chat window while
* conversation history is being hydrated. Runtime responses have a durable
* local id; matching by text or timestamp would collapse two different runs
* that happen to produce the same wording.
*/
export function mergeGameChatRuntimeResponseMessagesIntoHistory(
historyMessages: ChatMessage[],
currentMessages: ChatMessage[],
) {
const historyMessageIds = new Set(
historyMessages
.map((message) => message.messageId?.trim())
.filter((messageId): messageId is string => Boolean(messageId)),
);
const addedMessageIds = new Set<string>();
const responsesToKeep = currentMessages.filter((message) => {
const messageId = message.messageId?.trim();
if (
!message.runtimeOwned ||
message.role !== 'assistant' ||
!messageId?.startsWith('runtime-response:') ||
historyMessageIds.has(messageId) ||
addedMessageIds.has(messageId)
) {
return false;
}
addedMessageIds.add(messageId);
return true;
});
if (responsesToKeep.length === 0) {
return historyMessages;
}
return [...historyMessages, ...responsesToKeep].sort(
(left, right) => (left.updatedAt ?? 0) - (right.updatedAt ?? 0),
);
}
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,
source,
}: {
invoke: TauriInvoke;
projectPath: string;
sessionId: string;
prompt: string;
runtime: AgentRuntimeState | null;
runProfile: 'standard' | 'autonomous-game-build';
source?: string;
}) {
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,
...(source ? { 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 ? { source } : {}),
},
);
return {
mode: 'start' as const,
runtimeResult,
acceptedRunId: agentRuntimeStartedRunId(runtimeResult, requestedRunId),
};
}
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}`;
}
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};${safeWaitingOn}`;
}
if (safeCurrentAction) {
return safeCurrentAction;
}
const legacyAttempt = currentAction?.match(
/^(?:等待 Provider 瞬态重试|Goal 已恢复,继续等待 Provider 瞬态重试) (\d+)\/(\d+)$/,
);
const retryProgress =
legacyAttempt?.[1] && legacyAttempt[2]
? `,准备自动重试 ${legacyAttempt[1]}/${legacyAttempt[2]}`
: ',正在准备自动重试';
const safeFallback = `Provider 上游服务暂时不可用${retryProgress}`;
return safeWaitingOn ? `${safeFallback};${safeWaitingOn}` : safeFallback;
}
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 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 (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 containsInternalFailureDiagnostics = Boolean(
event.detail &&
/(?:errorSha256|errorChars|fingerprint|chars|retryAttempt|retryState)=|<(?:absolute-path|redacted-url)>|\[redacted(?:[- ]secret| sensitive context)\]/i.test(
event.detail,
),
);
const visibleDetail =
isFailureEvent && containsInternalFailureDiagnostics ? null : event.detail;
const summary =
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 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.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')
);
}
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 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} 服务繁忙,请稍后重试`;
}
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'] = 'assistant',
) {
const failurePrefix = '后台任务失败:';
if (role !== 'assistant' || !message.startsWith(failurePrefix)) {
return message;
}
return projectRuntimeVisibleError(
message.slice(failurePrefix.length),
'项目总控 Agent',
true,
);
}
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}` : ''
}`;
}