b63923eb04
根任务先持久化启动确认并在审计异常时保持可执行队列连续性 严格校验根子任务与会话绑定并收束消息身份冲突 幂等恢复公开终态消息并消除回执和隔离汇合重复输出 统一同一运行关联消息的排序与前端状态展示 隔离运行时公开状态与智能体提示词及压缩上下文 补充运行时回归测试、前端测试和工程文档
618 lines
21 KiB
TypeScript
618 lines
21 KiB
TypeScript
import { describe, expect, test, vi } from 'vitest';
|
||
|
||
import type {
|
||
AgentRuntimeResponseStream,
|
||
AgentRuntimeResult,
|
||
AgentRuntimeState,
|
||
ChatMessage,
|
||
LocalConversationMessageRecord,
|
||
} from '../src/app/types';
|
||
import {
|
||
formatAgentRuntimeEvent,
|
||
mergeGameChatRuntimeResponseMessagesIntoHistory,
|
||
mergeProjectSupervisorConversation,
|
||
MUD_POINT_INSUFFICIENT_INTERRUPTION_MESSAGE,
|
||
projectRuntimeVisibleCurrentWork,
|
||
projectRuntimeVisibleError,
|
||
projectSupervisorChatRuntimeStatus,
|
||
projectSupervisorResponseStreamIdentity,
|
||
projectSupervisorVisibleConversationText,
|
||
submitProjectSupervisorRuntimeTask,
|
||
} from '../src/features/agent-runtime/model';
|
||
|
||
describe('Runtime-owned public statuses', () => {
|
||
test('keeps backend status messages visible without treating them as client-authored conversation', () => {
|
||
const projectRecords: LocalConversationMessageRecord[] = [
|
||
{
|
||
schemaVersion: 'game-creator-conversation.v1',
|
||
role: 'assistant',
|
||
content: '任务已接收,项目总控 Agent 正在启动处理。',
|
||
agentId: null,
|
||
messageId:
|
||
'runtime-public-status-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-accepted',
|
||
updatedAt: 2,
|
||
},
|
||
{
|
||
schemaVersion: 'game-creator-conversation.v1',
|
||
role: 'assistant',
|
||
content: '普通项目消息',
|
||
agentId: null,
|
||
messageId: 'project-message-1',
|
||
updatedAt: 3,
|
||
},
|
||
];
|
||
const supervisorRecords: LocalConversationMessageRecord[] = [
|
||
{
|
||
schemaVersion: 'game-creator-conversation.v1',
|
||
role: 'user',
|
||
content: '请继续完成当前游戏',
|
||
agentId: 'project-supervisor',
|
||
messageId: 'runtime-task-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
||
updatedAt: 2,
|
||
},
|
||
];
|
||
|
||
const messages = mergeProjectSupervisorConversation(
|
||
projectRecords,
|
||
supervisorRecords,
|
||
);
|
||
|
||
expect(messages).toEqual([
|
||
expect.objectContaining({
|
||
text: '请继续完成当前游戏',
|
||
runtimeOwned: true,
|
||
}),
|
||
expect.objectContaining({
|
||
text: '任务已接收,项目总控 Agent 正在启动处理。',
|
||
runtimeOwned: true,
|
||
}),
|
||
expect.objectContaining({
|
||
text: '普通项目消息',
|
||
runtimeOwned: false,
|
||
}),
|
||
]);
|
||
});
|
||
|
||
test('interleaves rapid same-second tasks with their accepted and terminal statuses', () => {
|
||
const projectRecords: LocalConversationMessageRecord[] = [
|
||
{
|
||
schemaVersion: 'game-creator-conversation.v1',
|
||
role: 'assistant',
|
||
content: '任务已接收,项目总控 Agent 正在启动处理。',
|
||
agentId: null,
|
||
messageId:
|
||
'runtime-public-status-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-accepted',
|
||
updatedAt: 9,
|
||
},
|
||
{
|
||
schemaVersion: 'game-creator-conversation.v1',
|
||
role: 'assistant',
|
||
content: '项目总控 Agent 执行失败,请稍后重试',
|
||
agentId: null,
|
||
messageId:
|
||
'runtime-public-status-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-failed',
|
||
updatedAt: 9,
|
||
},
|
||
{
|
||
schemaVersion: 'game-creator-conversation.v1',
|
||
role: 'assistant',
|
||
content: '任务已接收,项目总控 Agent 正在启动处理。',
|
||
agentId: null,
|
||
messageId:
|
||
'runtime-public-status-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb-accepted',
|
||
updatedAt: 9,
|
||
},
|
||
{
|
||
schemaVersion: 'game-creator-conversation.v1',
|
||
role: 'assistant',
|
||
content: '项目总控 Agent 已达到执行预算,请缩小任务范围后重试',
|
||
agentId: null,
|
||
messageId:
|
||
'runtime-public-status-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb-budget',
|
||
updatedAt: 9,
|
||
},
|
||
];
|
||
const supervisorRecords: LocalConversationMessageRecord[] = [
|
||
{
|
||
schemaVersion: 'game-creator-conversation.v1',
|
||
role: 'user',
|
||
content: '第一条任务',
|
||
agentId: 'project-supervisor',
|
||
messageId: 'runtime-task-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
||
updatedAt: 9,
|
||
},
|
||
{
|
||
schemaVersion: 'game-creator-conversation.v1',
|
||
role: 'user',
|
||
content: '第二条任务',
|
||
agentId: 'project-supervisor',
|
||
messageId: 'runtime-task-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
|
||
updatedAt: 9,
|
||
},
|
||
];
|
||
|
||
expect(
|
||
mergeProjectSupervisorConversation(projectRecords, supervisorRecords).map(
|
||
(message) => message.text,
|
||
),
|
||
).toEqual([
|
||
'第一条任务',
|
||
'任务已接收,项目总控 Agent 正在启动处理。',
|
||
'项目总控 Agent 执行失败,请稍后重试',
|
||
'第二条任务',
|
||
'任务已接收,项目总控 Agent 正在启动处理。',
|
||
'项目总控 Agent 已达到执行预算,请缩小任务范围后重试',
|
||
]);
|
||
});
|
||
|
||
test('keeps an older sparse terminal before a new same-second task', () => {
|
||
const projectRecords: LocalConversationMessageRecord[] = [
|
||
{
|
||
schemaVersion: 'game-creator-conversation.v1',
|
||
role: 'assistant',
|
||
content: '项目总控 Agent 执行失败,请稍后重试',
|
||
agentId: null,
|
||
messageId:
|
||
'runtime-public-status-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-failed',
|
||
updatedAt: 9,
|
||
},
|
||
{
|
||
schemaVersion: 'game-creator-conversation.v1',
|
||
role: 'assistant',
|
||
content: '任务已接收,项目总控 Agent 正在启动处理。',
|
||
agentId: null,
|
||
messageId:
|
||
'runtime-public-status-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb-accepted',
|
||
updatedAt: 9,
|
||
},
|
||
];
|
||
const supervisorRecords: LocalConversationMessageRecord[] = [
|
||
{
|
||
schemaVersion: 'game-creator-conversation.v1',
|
||
role: 'user',
|
||
content: '旧任务',
|
||
agentId: 'project-supervisor',
|
||
messageId: 'runtime-task-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
||
updatedAt: 8,
|
||
},
|
||
{
|
||
schemaVersion: 'game-creator-conversation.v1',
|
||
role: 'user',
|
||
content: '新任务',
|
||
agentId: 'project-supervisor',
|
||
messageId: 'runtime-task-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
|
||
updatedAt: 9,
|
||
},
|
||
];
|
||
|
||
expect(
|
||
mergeProjectSupervisorConversation(projectRecords, supervisorRecords).map(
|
||
(message) => message.text,
|
||
),
|
||
).toEqual([
|
||
'旧任务',
|
||
'项目总控 Agent 执行失败,请稍后重试',
|
||
'新任务',
|
||
'任务已接收,项目总控 Agent 正在启动处理。',
|
||
]);
|
||
});
|
||
});
|
||
|
||
describe('Game Chat stream identity and source', () => {
|
||
test('response stream identity binds run, slot, and revision', () => {
|
||
const base: AgentRuntimeResponseStream = {
|
||
schemaVersion: 'game-creator-runtime-response-stream.v1',
|
||
agentId: 'project-supervisor',
|
||
taskId: 'project-supervisor',
|
||
sessionId: 'session-1',
|
||
runId: 'run-1',
|
||
requestKind: 'final-reply',
|
||
requestSlot: 'final-reply-loop-1-revision-0',
|
||
appliedSteerCursor: 0,
|
||
responseRevision: 0,
|
||
sequence: 1,
|
||
status: 'ready',
|
||
accumulatedText: 'same text',
|
||
finishReason: 'stop',
|
||
startedAt: 1,
|
||
updatedAt: 2,
|
||
};
|
||
|
||
expect(projectSupervisorResponseStreamIdentity(base)).toBe(
|
||
'run-1\u001ffinal-reply-loop-1-revision-0\u001f0',
|
||
);
|
||
expect(
|
||
projectSupervisorResponseStreamIdentity({ ...base, runId: 'run-2' }),
|
||
).not.toBe(projectSupervisorResponseStreamIdentity(base));
|
||
expect(
|
||
projectSupervisorResponseStreamIdentity({
|
||
...base,
|
||
requestSlot: 'final-reply-loop-2-revision-0',
|
||
}),
|
||
).not.toBe(projectSupervisorResponseStreamIdentity(base));
|
||
expect(
|
||
projectSupervisorResponseStreamIdentity({ ...base, responseRevision: 1 }),
|
||
).not.toBe(projectSupervisorResponseStreamIdentity(base));
|
||
});
|
||
|
||
test('game-chat source is forwarded for start and steer without changing default source behavior', async () => {
|
||
const runtimeResult = {
|
||
state: {
|
||
schemaVersion: 'game-creator-agent-runtime.v1',
|
||
agentId: 'project-supervisor',
|
||
taskId: 'project-supervisor',
|
||
sessionId: 'session-provider-retry',
|
||
runId: 'accepted-run',
|
||
source: 'project-supervisor-chat',
|
||
status: 'running',
|
||
phase: 'planning',
|
||
currentTask: null,
|
||
currentAction: null,
|
||
waitingOn: null,
|
||
nextStep: null,
|
||
plan: [],
|
||
planSteps: [],
|
||
observations: [],
|
||
allowedTools: [],
|
||
lastResponse: null,
|
||
error: null,
|
||
updatedAt: 1,
|
||
},
|
||
sessionPath: 'session.json',
|
||
eventPath: 'events.jsonl',
|
||
} satisfies AgentRuntimeResult;
|
||
const invoke = vi.fn(
|
||
async (_command: string, _args?: Record<string, unknown>) =>
|
||
runtimeResult,
|
||
);
|
||
|
||
await submitProjectSupervisorRuntimeTask({
|
||
invoke,
|
||
projectPath: '/tmp/game-chat',
|
||
sessionId: 'session-provider-retry',
|
||
prompt: 'make a game',
|
||
runtime: null,
|
||
runProfile: 'autonomous-game-build',
|
||
source: 'project-supervisor-game-chat',
|
||
});
|
||
|
||
expect(invoke).toHaveBeenCalledWith(
|
||
'start_game_creator_supervisor_runtime_task',
|
||
expect.objectContaining({ source: 'project-supervisor-game-chat' }),
|
||
);
|
||
|
||
invoke.mockClear();
|
||
await submitProjectSupervisorRuntimeTask({
|
||
invoke,
|
||
projectPath: '/tmp/game-chat',
|
||
sessionId: 'session-provider-retry',
|
||
prompt: 'chat',
|
||
runtime: null,
|
||
runProfile: 'standard',
|
||
});
|
||
expect(invoke.mock.calls[0]?.[1]).not.toHaveProperty('source');
|
||
|
||
invoke.mockClear();
|
||
const steerRuntime = {
|
||
...runtimeResult.state,
|
||
runId: 'steer-run',
|
||
runProfile: 'autonomous-game-build',
|
||
source: 'project-supervisor-game-chat',
|
||
status: 'running',
|
||
phase: 'planning',
|
||
} satisfies AgentRuntimeState;
|
||
invoke.mockResolvedValueOnce({
|
||
runtime: runtimeResult,
|
||
steerId: 'steer-1',
|
||
sequence: 1,
|
||
status: 'applied',
|
||
providerInterrupted: false,
|
||
});
|
||
await submitProjectSupervisorRuntimeTask({
|
||
invoke,
|
||
projectPath: '/tmp/game-chat',
|
||
sessionId: 'session-provider-retry',
|
||
prompt: 'continue this run',
|
||
runtime: steerRuntime,
|
||
runProfile: 'autonomous-game-build',
|
||
source: 'project-supervisor-game-chat',
|
||
});
|
||
expect(invoke).toHaveBeenCalledWith(
|
||
'steer_game_creator_agent_runtime_task',
|
||
expect.objectContaining({ source: 'project-supervisor-game-chat' }),
|
||
);
|
||
|
||
invoke.mockClear();
|
||
invoke.mockResolvedValueOnce({
|
||
runtime: runtimeResult,
|
||
steerId: 'steer-2',
|
||
sequence: 2,
|
||
status: 'applied',
|
||
providerInterrupted: false,
|
||
});
|
||
await submitProjectSupervisorRuntimeTask({
|
||
invoke,
|
||
projectPath: '/tmp/game-chat',
|
||
sessionId: 'session-provider-retry',
|
||
prompt: 'continue this run',
|
||
runtime: steerRuntime,
|
||
runProfile: 'autonomous-game-build',
|
||
});
|
||
expect(invoke.mock.calls[0]?.[1]).not.toHaveProperty('source');
|
||
|
||
invoke.mockClear();
|
||
await submitProjectSupervisorRuntimeTask({
|
||
invoke,
|
||
projectPath: '/tmp/game-chat',
|
||
sessionId: 'session-provider-retry',
|
||
prompt: 'continue from the app',
|
||
runtime: {
|
||
...steerRuntime,
|
||
source: 'project-supervisor-cli',
|
||
},
|
||
runProfile: 'autonomous-game-build',
|
||
source: 'project-supervisor-game-chat',
|
||
});
|
||
expect(invoke).toHaveBeenCalledWith(
|
||
'start_game_creator_supervisor_runtime_task',
|
||
expect.objectContaining({ source: 'project-supervisor-game-chat' }),
|
||
);
|
||
});
|
||
|
||
test('game-chat hydration keeps identical text from a different runtime response identity', () => {
|
||
const history = [
|
||
{
|
||
role: 'assistant' as const,
|
||
text: 'same text',
|
||
messageId: 'persisted-message-1',
|
||
updatedAt: 20,
|
||
},
|
||
];
|
||
const pending = [
|
||
{
|
||
role: 'assistant' as const,
|
||
text: 'same text',
|
||
messageId: 'runtime-response:run-2\u001fslot-1\u001f0',
|
||
updatedAt: 10,
|
||
runtimeOwned: true,
|
||
},
|
||
];
|
||
|
||
expect(
|
||
mergeGameChatRuntimeResponseMessagesIntoHistory(history, pending),
|
||
).toEqual([pending[0], history[0]]);
|
||
expect(
|
||
mergeGameChatRuntimeResponseMessagesIntoHistory(history, [
|
||
{ ...pending[0], messageId: 'persisted-message-1' },
|
||
]),
|
||
).toEqual(history);
|
||
});
|
||
|
||
test('ready response survives either ordering of runtime commit and conversation hydration', () => {
|
||
const history = [
|
||
{
|
||
role: 'user' as const,
|
||
text: 'build a game',
|
||
messageId: 'persisted-message-1',
|
||
updatedAt: 1,
|
||
},
|
||
];
|
||
const ready = {
|
||
role: 'assistant' as const,
|
||
text: 'ready response',
|
||
messageId: 'runtime-response:run-1\u001fslot-1\u001f0',
|
||
updatedAt: 2,
|
||
runtimeOwned: true,
|
||
};
|
||
const commitReady = (current: ChatMessage[]) =>
|
||
current.some((message) => message.messageId === ready.messageId)
|
||
? current
|
||
: [...current, ready];
|
||
const hydrateConversation = (current: ChatMessage[]) =>
|
||
mergeGameChatRuntimeResponseMessagesIntoHistory(history, current);
|
||
|
||
expect(hydrateConversation(commitReady([]))).toContainEqual(ready);
|
||
expect(commitReady(hydrateConversation([]))).toContainEqual(ready);
|
||
});
|
||
});
|
||
|
||
function providerRetryRuntime(): AgentRuntimeState {
|
||
return {
|
||
schemaVersion: 'game-creator-agent-runtime.v1',
|
||
agentId: 'project-supervisor',
|
||
taskId: 'task-provider-retry',
|
||
sessionId: 'session-provider-retry',
|
||
runId: 'run-provider-retry',
|
||
source: 'project-supervisor-chat',
|
||
status: 'running',
|
||
phase: 'waiting-for-provider-retry',
|
||
currentTask: '生成项目总控计划',
|
||
currentAction: 'Provider 上游返回 HTTP 503,准备自动重试 1/3',
|
||
waitingOn: '预计 30 秒后重试',
|
||
nextStep: '到期后继续同一 Run',
|
||
plan: ['生成项目总控计划'],
|
||
planSteps: [
|
||
{
|
||
step: '这个活跃计划步骤不应覆盖真实 Provider 等待进度',
|
||
status: 'in_progress',
|
||
index: 0,
|
||
},
|
||
],
|
||
observations: [],
|
||
allowedTools: [],
|
||
lastResponse: null,
|
||
error: null,
|
||
updatedAt: 1,
|
||
};
|
||
}
|
||
|
||
describe('Agent Runtime Provider 状态投影', () => {
|
||
test('等待 503 重试时优先显示 Runtime 的真实动作和等待时间', () => {
|
||
const runtime = providerRetryRuntime();
|
||
const expected =
|
||
'Provider 上游返回 HTTP 503,准备自动重试 1/3;预计 30 秒后重试';
|
||
|
||
expect(projectSupervisorChatRuntimeStatus(runtime)).toBe(expected);
|
||
expect(projectRuntimeVisibleCurrentWork(runtime)).toBe(expected);
|
||
});
|
||
|
||
test('旧等待投影不再显示内部 upstream-5xx 分类', () => {
|
||
const legacyRuntime = {
|
||
...providerRetryRuntime(),
|
||
currentAction: '等待 Provider 瞬态重试 1/3',
|
||
waitingOn: 'Provider upstream-5xx 瞬态故障退避到期',
|
||
};
|
||
const expected = 'Provider 上游服务暂时不可用,准备自动重试 1/3';
|
||
|
||
expect(projectSupervisorChatRuntimeStatus(legacyRuntime)).toBe(expected);
|
||
expect(projectRuntimeVisibleCurrentWork(legacyRuntime)).toBe(expected);
|
||
expect(projectSupervisorChatRuntimeStatus(legacyRuntime)).not.toContain(
|
||
'upstream-5xx',
|
||
);
|
||
});
|
||
|
||
test('严格解析 503 重试耗尽的稳定字段', () => {
|
||
const fingerprint = 'a'.repeat(64);
|
||
const message =
|
||
`agentLlm.project-supervisor 规划调用 LLM 失败:kind=upstream-503 ` +
|
||
`httpStatus=503 fingerprint=${fingerprint} chars=248 ` +
|
||
'retryAttempt=3 maxRetries=3 retryState=exhausted';
|
||
|
||
expect(projectRuntimeVisibleError(message, '项目总控 Agent', true)).toBe(
|
||
'项目总控 Agent 上游服务返回 HTTP 503;自动重试已耗尽(3/3)',
|
||
);
|
||
const failedRuntime = {
|
||
...providerRetryRuntime(),
|
||
status: 'failed',
|
||
phase: 'failed',
|
||
error: message,
|
||
};
|
||
expect(projectSupervisorChatRuntimeStatus(failedRuntime)).toBe(
|
||
'项目总控 Agent 上游服务返回 HTTP 503;自动重试已耗尽(3/3)',
|
||
);
|
||
});
|
||
|
||
test('明确说明泥点余额不足导致的中断并隐藏附加诊断', () => {
|
||
for (const message of [
|
||
'agentLlm.project-supervisor 规划调用 LLM 失败:kind=mud-points-insufficient',
|
||
'平台图片生成任务失败:泥点余额不足;operationId=private-operation-id',
|
||
]) {
|
||
expect(projectRuntimeVisibleError(message, '项目总控 Agent', true)).toBe(
|
||
MUD_POINT_INSUFFICIENT_INTERRUPTION_MESSAGE,
|
||
);
|
||
expect(
|
||
projectSupervisorVisibleConversationText(`后台任务失败:${message}`),
|
||
).toBe(MUD_POINT_INSUFFICIENT_INTERRUPTION_MESSAGE);
|
||
}
|
||
|
||
const failedRuntime = {
|
||
...providerRetryRuntime(),
|
||
status: 'failed',
|
||
phase: 'failed',
|
||
error:
|
||
'平台图片生成任务失败:泥点余额不足;operationId=private-operation-id',
|
||
};
|
||
const status = projectSupervisorChatRuntimeStatus(failedRuntime);
|
||
expect(status).toBe(MUD_POINT_INSUFFICIENT_INTERRUPTION_MESSAGE);
|
||
expect(status).not.toContain('operationId');
|
||
expect(status).not.toContain('private-operation-id');
|
||
|
||
expect(
|
||
projectRuntimeVisibleError('上游余额不足', '项目总控 Agent', true),
|
||
).toBe('项目总控 Agent 执行失败,请稍后重试');
|
||
});
|
||
|
||
test('即使前缀含恶意上游正文也只展示严格字段派生的摘要', () => {
|
||
const fingerprint = 'b'.repeat(64);
|
||
const malicious =
|
||
'https://provider.example/private?api_key=sk-secret C:\\Users\\victim\\project';
|
||
const message =
|
||
`${malicious}:kind=upstream-503 httpStatus=503 ` +
|
||
`fingerprint=${fingerprint} chars=999 retryAttempt=3 ` +
|
||
'maxRetries=3 retryState=exhausted';
|
||
const visible = projectRuntimeVisibleError(message, '项目总控 Agent', true);
|
||
|
||
expect(visible).toBe(
|
||
'项目总控 Agent 上游服务返回 HTTP 503;自动重试已耗尽(3/3)',
|
||
);
|
||
expect(visible).not.toContain('provider.example');
|
||
expect(visible).not.toContain('sk-secret');
|
||
expect(visible).not.toContain('victim');
|
||
});
|
||
|
||
test('拒绝字段不一致或带尾随正文的伪造 Provider 摘要', () => {
|
||
const fingerprint = 'c'.repeat(64);
|
||
const inconsistent =
|
||
`kind=upstream-503 httpStatus=500 fingerprint=${fingerprint} chars=1 ` +
|
||
'retryAttempt=3 maxRetries=3 retryState=exhausted';
|
||
const trailingBody =
|
||
`kind=upstream-503 httpStatus=503 fingerprint=${fingerprint} chars=1 ` +
|
||
'retryAttempt=3 maxRetries=3 retryState=exhausted provider body';
|
||
|
||
expect(
|
||
projectRuntimeVisibleError(inconsistent, '项目总控 Agent', true),
|
||
).toBe('项目总控 Agent 执行失败,请稍后重试');
|
||
expect(
|
||
projectRuntimeVisibleError(trailingBody, '项目总控 Agent', true),
|
||
).toBe('项目总控 Agent 执行失败,请稍后重试');
|
||
});
|
||
|
||
test('隐藏旧失败对话与事件中的内部诊断标记', () => {
|
||
const fingerprint = 'd'.repeat(64);
|
||
const legacyConversation =
|
||
'后台任务失败:<absolute-path> [redacted sensitive context] ' +
|
||
`kind=upstream-503 httpStatus=503 fingerprint=${fingerprint} chars=99 ` +
|
||
'retryAttempt=3 maxRetries=3 retryState=exhausted';
|
||
expect(projectSupervisorVisibleConversationText(legacyConversation)).toBe(
|
||
'项目总控 Agent 上游服务返回 HTTP 503;自动重试已耗尽(3/3)',
|
||
);
|
||
expect(
|
||
projectSupervisorVisibleConversationText(legacyConversation, 'user'),
|
||
).toBe(legacyConversation);
|
||
|
||
const formattedEvent = formatAgentRuntimeEvent({
|
||
schemaVersion: 'game-creator-agent-runtime.v1',
|
||
agentId: 'project-supervisor',
|
||
taskId: 'task-provider-retry',
|
||
sessionId: 'session-provider-retry',
|
||
runId: 'run-provider-retry',
|
||
source: 'project-supervisor-chat',
|
||
eventType: 'turn.failed',
|
||
status: 'failed',
|
||
phase: 'failed',
|
||
summary: 'Agent Runtime 本轮处理失败。',
|
||
detail:
|
||
`<absolute-path> [redacted sensitive context] ` +
|
||
`errorSha256=${fingerprint} · errorChars=99`,
|
||
updatedAt: 1,
|
||
});
|
||
expect(formattedEvent).toBe(
|
||
'turn.failed · failed / failed · Agent Runtime 本轮处理失败。',
|
||
);
|
||
expect(formattedEvent).not.toContain('errorSha256');
|
||
expect(formattedEvent).not.toContain(fingerprint);
|
||
|
||
const emptySummaryEvent = formatAgentRuntimeEvent({
|
||
schemaVersion: 'game-creator-agent-runtime.v1',
|
||
agentId: 'project-supervisor',
|
||
taskId: 'task-provider-retry',
|
||
sessionId: 'session-provider-retry',
|
||
runId: 'run-provider-retry',
|
||
source: 'project-supervisor-chat',
|
||
eventType: 'error',
|
||
status: 'failed',
|
||
phase: 'failed',
|
||
summary: '',
|
||
detail:
|
||
`<absolute-path> [redacted sensitive context] ` +
|
||
`fingerprint=${fingerprint} chars=99`,
|
||
updatedAt: 1,
|
||
});
|
||
expect(emptySummaryEvent).toBe(
|
||
'error · failed / failed · Agent Runtime 本轮处理失败。',
|
||
);
|
||
expect(emptySummaryEvent).not.toContain('absolute-path');
|
||
expect(emptySummaryEvent).not.toContain(fingerprint);
|
||
});
|
||
});
|