Files
Genarrative/apps/ai-game-creator-shell/tests/agentRuntimeModel.test.ts
T
kdletters 1e62d8317a 合并 origin/master 到 AGC 渲染层下沉分支并完成三处对齐
- 活动回合事实源统一到 Direct 线程管理器:删除 direct_runtime 的第二份快照,接单、进度内容变化与收口各广播一次活动回合变更事件
- 平台维护态判定移入 Rust 并在渲染层只订阅单一事件:新增 platform_maintenance 模块与各平台 facade 错误分支的分类入口
- 封面生成请求补 generationInputs.source,保持队列回填后仍能拿到平台素材 ID
- 渲染层按 master 5398a53e6 退役诊断详情入口:删除 agentRuntimeErrorDetail 与「查看详情」交互及其专属用例
- 冲突收口:nginx SPA 白名单、.gitignore、mobile 检查脚本、capabilities 描述取上游,两个已退役计划随上游删除,文档保留双方条目
- 新增并回写本里程碑取证、decision-log 与 pitfalls 的 2026-09-28 记录
2026-09-28 15:07:30 +08:00

725 lines
30 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 { describe, expect, test } from 'vitest';
import { createGameCreationAppManifest } from '../../../packages/shared/src/contracts/gameCreationApp';
import type {
AgentRuntimeResult,
AgentRuntimeState,
AgentRuntimeTaskRecord,
} from '../src/app/types';
import {
agentRuntimeConversationStatus,
agentRuntimeStateFromResult,
formatAgentRecentRuntimeTask,
formatAgentRuntimeEvent,
isAgentRuntimeTerminalState,
MUD_POINT_INSUFFICIENT_INTERRUPTION_MESSAGE,
projectRuntimeVisibleCurrentWork,
projectRuntimeVisibleError,
projectRuntimeVisibleRejectionError,
} from '../src/features/agent-runtime/model';
import {
deriveAgentStatusCards,
formatAgentCardRuntimeStatus,
formatCodexRuntimeCapabilities,
} from '../src/features/project-summary/agentPresentation';
function providerRetryRuntime(): AgentRuntimeState {
return {
schemaVersion: 'game-creator-agent-runtime.v1',
agentId: 'planning-agent-v2',
taskId: 'task-provider-retry',
sessionId: 'session-provider-retry',
runId: 'run-provider-retry',
source: 'planning-gui',
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('普通用户工作区状态', () => {
test('官方智能服务状态跟随流式与联网检索设置', () => {
expect(
formatCodexRuntimeCapabilities({
agentMode: 'codex_app_server',
stream: true,
webSearchEnabled: true,
}),
).toBe('流式开启,联网检索开启');
expect(
formatCodexRuntimeCapabilities({
agentMode: 'codex_app_server',
stream: false,
webSearchEnabled: false,
}),
).toBe('流式关闭,联网检索关闭');
expect(
formatCodexRuntimeCapabilities({
agentMode: 'codex_cli',
stream: true,
webSearchEnabled: true,
}),
).toBe('流式开启,联网检索关闭');
});
});
describe('Agent 最近任务失败摘要', () => {
test('重试已入队时立即投影新 Run 并保留旧失败历史', () => {
const previous: AgentRuntimeState = {
...providerRetryRuntime(),
runId: 'code-failed',
source: 'agent-delegate',
parentAgentId: 'planning-agent-v2',
parentRunId: 'planning-active',
status: 'failed',
phase: 'failed',
error: 'kind=budget-exhausted',
updatedAt: 20,
};
const retryTask: AgentRuntimeTaskRecord = {
schemaVersion: 'game-creator-agent-runtime-task.v1',
agentId: previous.agentId,
taskId: previous.taskId,
sessionId: previous.sessionId,
runId: 'code-retry',
source: 'agent-delegate-retry',
parentAgentId: 'planning-agent-v2',
parentRunId: 'planning-active',
delegationId: 'retry-delegation',
task: previous.currentTask,
status: 'pending',
phase: 'queued',
currentAction: '等待当前后台任务完成',
error: null,
updatedAt: 21,
};
const result: AgentRuntimeResult = {
state: previous,
acceptedRunId: retryTask.runId,
sessionPath: '.agent/runtime/session.json',
eventPath: '.agent/runtime/events.jsonl',
taskPath: '.agent/runtime/tasks.jsonl',
taskQueue: {
total: 2,
pending: 1,
running: 0,
completed: 0,
failed: 1,
latestRunId: retryTask.runId,
updatedAt: 21,
},
recentEvents: [],
recentTasks: [
{
...retryTask,
runId: previous.runId,
source: previous.source,
status: 'failed',
phase: 'failed',
currentAction: '等待开发者处理失败',
terminalDetail: 'kind=budget-exhausted',
error: 'kind=budget-exhausted',
updatedAt: 20,
},
retryTask,
],
};
const projected = agentRuntimeStateFromResult(result, previous);
expect(projected.runId).toBe('code-retry');
expect(projected.source).toBe('agent-delegate-retry');
expect(projected.status).toBe('pending');
expect(projected.phase).toBe('queued');
expect(projected.error).toBeNull();
expect(projected.parentRunId).toBe('planning-active');
expect(projected.recentTasks).toEqual(result.recentTasks);
});
test('展示安全可行动原因且不透传私有诊断', () => {
const task: AgentRuntimeTaskRecord = {
schemaVersion: 'game-creator-agent-runtime-task.v1',
agentId: 'code-prototype',
taskId: 'code-prototype',
sessionId: 'session-code',
runId: 'run-code-failed',
source: 'agent-delegate',
task: '实现首个可玩版本',
status: 'failed',
phase: 'failed',
currentAction: '等待开发者处理失败',
terminalDetail:
'kind=codex-app-server-context-window-exceeded fingerprint=' +
'a'.repeat(64),
error: 'private provider body https://provider.example/api?key=secret',
updatedAt: 1,
};
const visible = formatAgentRecentRuntimeTask(task);
expect(visible).toContain(
'失败原因:程序原型 Agent 模型上下文已超限,请缩小任务范围后重试',
);
expect(visible).not.toContain('fingerprint');
expect(visible).not.toContain('provider.example');
expect(visible).not.toContain('secret');
});
test('状态卡在 Runtime error 为空时使用当前 Run 的终态任务原因', () => {
const terminalDetail =
'kind=codex-app-server-context-window-exceeded fingerprint=' +
'b'.repeat(64);
const manifest = createGameCreationAppManifest(
'local-project-draft',
'failure-card-game',
);
const cards = deriveAgentStatusCards(manifest, null, {
'code-prototype': {
schemaVersion: 'game-creator-agent-runtime.v1',
agentId: 'code-prototype',
taskId: 'code-prototype',
sessionId: 'session-code',
runId: 'run-code-failed',
source: 'agent-delegate',
status: 'failed',
phase: 'failed',
currentTask: '实现首个可玩版本',
plan: [],
observations: [],
allowedTools: [],
error: null,
updatedAt: 2,
recentTasks: [
{
schemaVersion: 'game-creator-agent-runtime-task.v1',
agentId: 'code-prototype',
taskId: 'code-prototype',
sessionId: 'session-code',
runId: 'run-code-failed',
source: 'agent-delegate',
task: '实现首个可玩版本',
status: 'failed',
phase: 'failed',
currentAction: '等待开发者处理失败',
terminalDetail,
error: null,
updatedAt: 2,
},
],
},
});
const card = cards.find((candidate) => candidate.id === 'code-prototype');
expect(card?.runtimeError).toBe(terminalDetail);
expect(formatAgentCardRuntimeStatus(card!)).toContain(
'程序原型 Agent 模型上下文已超限,请缩小任务范围后重试',
);
expect(formatAgentCardRuntimeStatus(card!)).not.toContain('fingerprint');
});
});
describe('Runtime-owned public statuses', () => {
test('treats needs-reconciliation as a terminal state that requires manual resolution', () => {
const runtime = {
...providerRetryRuntime(),
status: 'needs-reconciliation',
phase: 'needs-reconciliation',
error: 'result-unknown,需要人工核对',
};
expect(isAgentRuntimeTerminalState(runtime)).toBe(true);
expect(agentRuntimeConversationStatus(runtime)).toBe(
'Agent 运行状态需要核对,正在同步记录',
);
});
});
describe('Agent Runtime Provider 状态投影', () => {
test('等待 503 重试时优先显示 Runtime 的真实动作和等待时间', () => {
const runtime = providerRetryRuntime();
const expected =
'智能服务 上游返回 HTTP 503,准备自动重试 1/3;预计 30 秒后重试';
expect(projectRuntimeVisibleCurrentWork(runtime)).toBe(expected);
});
test('旧等待投影不再显示内部 upstream-5xx 分类', () => {
const legacyRuntime = {
...providerRetryRuntime(),
currentAction: '等待 Provider 瞬态重试 1/3',
waitingOn: 'Provider upstream-5xx 瞬态故障退避到期',
};
const expected = '智能服务暂时不可用,准备自动重试 1/3';
expect(projectRuntimeVisibleCurrentWork(legacyRuntime)).toBe(expected);
expect(projectRuntimeVisibleCurrentWork(legacyRuntime)).not.toContain(
'upstream-5xx',
);
});
test('严格解析 503 重试耗尽的稳定字段', () => {
const fingerprint = 'a'.repeat(64);
const message =
`agentLlm.planning-agent-v2 规划调用 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)',
);
});
test('明确说明泥点余额不足导致的中断并隐藏附加诊断', () => {
for (const message of [
'agentLlm.planning-agent-v2 规划调用 LLM 失败:kind=mud-points-insufficient',
'平台图片生成任务失败:泥点余额不足;operationId=private-operation-id',
]) {
expect(projectRuntimeVisibleError(message, '项目总控 Agent', true)).toBe(
MUD_POINT_INSUFFICIENT_INTERRUPTION_MESSAGE,
);
}
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('展示 Codex app-server 稳定失败分类且隐藏内部诊断', () => {
const fingerprint = 'e'.repeat(64);
const contextError =
`agentLlm.code-prototype 调用 LLM 失败:` +
`kind=codex-app-server-context-window-exceeded ` +
`fingerprint=${fingerprint} chars=2048`;
const visible = projectRuntimeVisibleError(
contextError,
'程序原型 Agent',
true,
);
expect(visible).toBe(
'程序原型 Agent 模型上下文已超限,请缩小任务范围后重试',
);
expect(visible).not.toContain('fingerprint');
expect(visible).not.toContain(fingerprint);
expect(
projectRuntimeVisibleError(
`kind=codex-app-server-unauthorized fingerprint=${fingerprint} chars=1`,
'项目总控 Agent',
true,
),
).toBe('项目总控 Agent 智能服务鉴权失败,请重新登录后重试');
});
test('直连智能创作的错误码也展示可行动原因并识别终态未知', () => {
expect(
projectRuntimeVisibleError(
'codex-app-server-error:usage-limit-exceeded',
'陶泥儿智能创作',
true,
),
).toBe('陶泥儿智能创作 用量已达上限,请检查账户额度后重试');
expect(
projectRuntimeVisibleError(
'codex-app-server-error:request-too-large',
'陶泥儿智能创作',
true,
),
).toBe('陶泥儿智能创作 模型请求体过大,请减少参考图或上下文后重试');
expect(
projectRuntimeVisibleError(
'codex-app-server-terminal-unknown: 等待 turn/completed 超时',
'陶泥儿智能创作',
true,
),
).toBe(
'陶泥儿智能创作服务或网络在执行中断开,最终状态未知;请先检查项目文件是否已修改,再决定是否重试',
);
});
test('直连智能创作展示脱敏的阶段诊断与重试建议', () => {
expect(
projectRuntimeVisibleError(
'direct-codex-failure:v1 stage=art-preparation retryable=true summary=读取陶泥儿画布资源失败:HTTP 503;建议:平台资源暂时无法完成准备,请稍后重试;如持续失败请检查项目诊断;已保存脱敏项目诊断',
'陶泥儿智能创作',
true,
),
).toBe(
'陶泥儿智能创作:平台资源准备失败:读取陶泥儿画布资源失败:HTTP 503。平台资源暂时无法完成准备,请稍后重试;如持续失败请检查项目诊断(可直接重试)',
);
expect(
projectRuntimeVisibleError(
'direct-codex-failure:v1 stage=art-preparation retryable=false summary=陶泥儿画布存在多个同源核心图集,身份不唯一,已拒绝恢复;建议:历史画布资源不满足安全恢复条件,请先在资源画布确认唯一可用的核心图集;已保存脱敏项目诊断',
'陶泥儿智能创作',
true,
),
).toBe(
'陶泥儿智能创作:平台资源准备失败:陶泥儿画布存在多个同源核心图集,身份不唯一,已拒绝恢复。历史画布资源不满足安全恢复条件,请先在资源画布确认唯一可用的核心图集',
);
expect(
projectRuntimeVisibleError(
'direct-codex-failure:v1 stage=art-preparation retryable=false summary=本机开发者凭据存储目录未安全初始化;未创建远端凭据;建议:请检查当前 Windows 用户对本机私有凭据目录的权限后重试;已保存脱敏项目诊断',
'陶泥儿智能创作',
true,
),
).toBe(
'陶泥儿智能创作:平台资源准备失败:本机开发者凭据存储目录未安全初始化;未创建远端凭据。请检查当前 Windows 用户对本机私有凭据目录的权限后重试',
);
expect(
projectRuntimeVisibleError(
'direct-codex-failure:v1 stage=art-preparation retryable=true summary=读取陶泥儿画布资源失败:<redacted-url> <absolute-path> [redacted-secret];建议:平台资源暂时无法完成准备,请稍后重试;如持续失败请检查项目诊断;已保存脱敏项目诊断',
'陶泥儿智能创作',
true,
),
).toBe(
'陶泥儿智能创作:平台资源准备失败:读取陶泥儿画布资源失败:[已隐藏链接] [已隐藏路径] [已隐藏凭据]。平台资源暂时无法完成准备,请稍后重试;如持续失败请检查项目诊断(可直接重试)',
);
expect(
projectRuntimeVisibleError(
'direct-codex-failure:v1 stage=art-preparation retryable=true summary=请求失败 authorization=<redacted-url> [redacted-config] token=[redacted-sensitive-field];建议:请检查已隐藏配置并稍后重试;已保存脱敏项目诊断',
'陶泥儿智能创作',
true,
),
).toBe(
'陶泥儿智能创作:平台资源准备失败:请求失败 authorization=[已隐藏链接] [已隐藏配置] token=[已隐藏敏感字段]。请检查已隐藏配置并稍后重试(可直接重试)',
);
});
test('直连智能创作的 v2 失败文案保留阶段诊断并忽略末尾诊断引用', () => {
// 当前 Native 运行时下发 v2:在 v1 之上多了 `code=`,末尾拼上诊断引用。
expect(
projectRuntimeVisibleError(
'direct-codex-failure:v2 stage=code-generation code=runtime-failure retryable=false summary=入口模块语法错误;建议:修复 game/src 下的语法错误后重新发送需求;已保存脱敏项目诊断;详情:.agent/runtime/errors/error-1700000000000-1.json',
'陶泥儿智能创作',
true,
),
).toBe(
'陶泥儿智能创作:代码生成失败:入口模块语法错误。修复 game/src 下的语法错误后重新发送需求',
);
expect(
projectRuntimeVisibleError(
'direct-codex-failure:v2 stage=browser-validation code=idle-timeout retryable=true summary=真实试玩等待超时;建议:稍后重试或缩小任务范围;未能保存项目诊断',
'陶泥儿智能创作',
true,
),
).toBe(
'陶泥儿智能创作:真实试玩未通过:真实试玩等待超时。稍后重试或缩小任务范围(可直接重试)',
);
// 脱敏口径与 v1 相同:带未脱敏凭据的 v2 文案仍回落通用兜底,不把私密内容透出来。
expect(
projectRuntimeVisibleError(
'direct-codex-failure:v2 stage=request code=runtime-failure retryable=true summary=请求失败 authorization=Bearer secret;建议:请检查已隐藏配置并稍后重试;已保存脱敏项目诊断;详情:.agent/runtime/errors/error-1-1.json',
'陶泥儿智能创作',
true,
),
).toBe('陶泥儿智能创作 执行失败,请稍后重试');
});
test('直连平台错误保留 HTTP 诊断字段但只显示已脱敏的敏感值', () => {
const safe = projectRuntimeVisibleError(
'陶泥儿美术包生成失败(规范图):请求平台图片生成失败:HTTP 401;code=invalid-token;field=authorization;message=token=[redacted-secret];detail=登录态已失效',
'陶泥儿智能创作',
true,
);
expect(safe).toContain('HTTP 401');
expect(safe).toContain('code=invalid-token');
expect(safe).toContain('field=authorization');
expect(safe).toContain('登录态已失效');
expect(safe).toContain('[已隐藏凭据]');
expect(safe).not.toContain('[redacted sensitive context]');
const unsafe = projectRuntimeVisibleError(
'陶泥儿美术包生成失败(规范图):请求平台图片生成失败:HTTP 401;code=invalid-token;field=authorization;message=token=raw-secret-value',
'陶泥儿智能创作',
true,
);
expect(unsafe).not.toContain('raw-secret-value');
expect(unsafe).toBe('陶泥儿智能创作 鉴权失败,请检查运行时配置');
const unsafeBearer = projectRuntimeVisibleError(
'陶泥儿美术包生成失败(规范图):请求平台图片生成失败:HTTP 401;bearer=raw-bearer-value',
'陶泥儿智能创作',
true,
);
expect(unsafeBearer).not.toContain('raw-bearer-value');
expect(unsafeBearer).toBe('陶泥儿智能创作 鉴权失败,请检查运行时配置');
const unsafeEscapedJson = projectRuntimeVisibleError(
String.raw`陶泥儿美术包生成失败(规范图):请求平台图片生成失败:HTTP 401;payload={\"token\":\"raw-escaped-json-value\"}`,
'陶泥儿智能创作',
true,
);
expect(unsafeEscapedJson).not.toContain('raw-escaped-json-value');
expect(unsafeEscapedJson).toBe('陶泥儿智能创作 鉴权失败,请检查运行时配置');
const markerSafe = projectRuntimeVisibleError(
'陶泥儿美术包生成失败(规范图):请求平台图片生成失败:HTTP 403;detail=authorization=[redacted-config];field=[redacted-sensitive-field];path=<absolute-path>',
'陶泥儿智能创作',
true,
);
expect(markerSafe).toContain('HTTP 403');
expect(markerSafe).toContain('authorization=[已隐藏配置]');
expect(markerSafe).toContain('field=[已隐藏敏感字段]');
expect(markerSafe).toContain('path=[已隐藏路径]');
expect(markerSafe).not.toContain('[redacted-sensitive-field]');
expect(markerSafe).not.toBe('陶泥儿智能创作 鉴权失败,请检查运行时配置');
});
test('直连 Codex 失败保留安全原因并隐藏链接与路径', () => {
expect(
projectRuntimeVisibleError(
'direct-codex-error:Codex app-server turn 失败:HTTP 400',
'陶泥儿智能创作',
true,
),
).toBe(
'陶泥儿智能创作:智能服务执行失败:Codex app-server turn 失败:HTTP 400',
);
expect(
projectRuntimeVisibleError(
'direct-codex-error:请求失败 https://provider.example/private C:\\Users\\private\\project',
'陶泥儿智能创作',
true,
),
).toBe(
'陶泥儿智能创作:智能服务执行失败:请求失败 [已隐藏链接] [已隐藏路径]',
);
expect(
projectRuntimeVisibleError(
'Codex 已返回,但客户端登记生成产物失败:游戏代码已生成,但未在实际渲染中同时使用陶泥儿背景图 assets/direct-game-background.png 与四类核心切片;不会将项目标记为完成',
'陶泥儿智能创作',
true,
),
).toBe(
'陶泥儿智能创作:Codex 已返回,但客户端登记生成产物失败:游戏代码已生成,但未在实际渲染中同时使用陶泥儿背景图 assets/direct-game-background.png 与四类核心切片;不会将项目标记为完成',
);
expect(
projectRuntimeVisibleError(
'陶泥儿美术包不完整,已终止代码生成',
'陶泥儿智能创作',
true,
),
).toBe('陶泥儿智能创作:陶泥儿美术包不完整,已终止代码生成');
});
test('把常见 Runtime 失败映射为可行动原因', () => {
expect(
projectRuntimeVisibleError(
'动态隔离子 Agent 缺少 expected artifact:game/index.html',
'程序 Agent',
),
).toBe('程序 Agent 未生成要求的产物,请查看任务要求后重试');
expect(
projectRuntimeVisibleError('project.verify 验证失败', '程序 Agent'),
).toBe('程序 Agent 项目验证未通过,请查看运行详情并修复后重试');
expect(
projectRuntimeVisibleError('Agent loop 预算耗尽', '程序 Agent'),
).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';
const formattedEvent = formatAgentRuntimeEvent({
schemaVersion: 'game-creator-agent-runtime.v1',
agentId: 'planning-agent-v2',
taskId: 'task-provider-retry',
sessionId: 'session-provider-retry',
runId: 'run-provider-retry',
source: 'planning-gui',
eventType: 'turn.failed',
status: 'failed',
phase: 'failed',
summary: 'Agent Runtime 本轮处理失败。',
publicText: '项目总控 Agent 模型上下文已超限,请缩小任务范围后重试',
detail:
`<absolute-path> [redacted sensitive context] ` +
`errorSha256=${fingerprint} · errorChars=99`,
updatedAt: 1,
});
expect(formattedEvent).toBe(
'turn.failed · failed / failed · 项目总控 Agent 模型上下文已超限,请缩小任务范围后重试',
);
expect(formattedEvent).not.toContain('errorSha256');
expect(formattedEvent).not.toContain(fingerprint);
expect(
formatAgentRuntimeEvent({
schemaVersion: 'game-creator-agent-runtime.v1',
agentId: 'planning-agent-v2',
taskId: 'legacy-private-detail',
sessionId: 'legacy-private-detail-session',
runId: 'legacy-private-detail-run',
source: 'planning-gui',
eventType: 'error',
status: 'failed',
phase: 'failed',
summary: 'Agent Runtime 本轮处理失败。',
detail: 'private provider body without stable diagnostics marker',
updatedAt: 1,
}),
).toBe('error · failed / failed · Agent Runtime 本轮处理失败。');
const emptySummaryEvent = formatAgentRuntimeEvent({
schemaVersion: 'game-creator-agent-runtime.v1',
agentId: 'planning-agent-v2',
taskId: 'task-provider-retry',
sessionId: 'session-provider-retry',
runId: 'run-provider-retry',
source: 'planning-gui',
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);
});
test('直连阶段收口文案的 v2 形状也要认出脱敏摘要', () => {
// 宿主发的是 v2(比 v1 多一段 `code=`):只认 v1 时这一路永远命中不了,脱敏摘要等于白写。
expect(
projectRuntimeVisibleError(
'direct-codex-failure:v2 stage=code-generation code=runtime-failure retryable=false summary=DirectProject 收尾历史失败:未确认历史完整落盘;建议:请检查项目目录后重试;如持续失败请检查项目诊断;已保存脱敏项目诊断',
'陶泥儿智能创作',
true,
),
).toBe(
'陶泥儿智能创作:代码生成失败:DirectProject 收尾历史失败:未确认历史完整落盘。请检查项目目录后重试;如持续失败请检查项目诊断',
);
// 脱敏标记照旧要换成中文标记,带路径 / 凭据的摘要照样拒收。
expect(
projectRuntimeVisibleError(
'direct-codex-failure:v2 stage=art-preparation code=runtime-failure retryable=true summary=读取陶泥儿画布资源失败:<redacted-url>;建议:请稍后重试;已保存脱敏项目诊断',
'陶泥儿智能创作',
true,
),
).toBe(
'陶泥儿智能创作:平台资源准备失败:读取陶泥儿画布资源失败:[已隐藏链接]。请稍后重试(可直接重试)',
);
expect(
projectRuntimeVisibleError(
'direct-codex-failure:v2 stage=art-preparation code=runtime-failure retryable=true summary=读取失败:https://provider.example/private;建议:请稍后重试;已保存脱敏项目诊断',
'陶泥儿智能创作',
true,
),
).toBe('陶泥儿智能创作 执行失败,请稍后重试');
});
test('拒单文案只取收口文案里的脱敏摘要与建议,不套阶段标签', () => {
// 阶段说的是"失败发生在交付的哪一步",而拒单是"这一轮没有开始":阶段只会是默认值,
// 套上去会把没发生的事讲成发生了。
expect(
projectRuntimeVisibleRejectionError(
'direct-codex-failure:v2 stage=code-generation code=runtime-failure retryable=true summary=Codex app-server 启动失败:找不到可执行文件;建议:请重试;如持续失败请检查项目诊断;已保存脱敏项目诊断',
'陶泥儿智能创作',
),
).toBe(
'陶泥儿智能创作:Codex app-server 启动失败:找不到可执行文件。请重试;如持续失败请检查项目诊断(可直接重试)',
);
// 不是收口形状时退回同一份运行错误映射(宿主 `Display` 的事实句仍然只给一句可读的话)。
expect(
projectRuntimeVisibleRejectionError(
'执行通道已断开,不能自动重放未确认操作:Codex app-server 已退出;exitStatus=signal: 9 (SIGKILL)',
'陶泥儿智能创作',
),
).toBe('陶泥儿智能创作 服务连接已断开,请稍后重试');
// 已知边界(本轮不动):映射里"拒绝"那条子串分支会先认领"拒绝访问"这类文件系统事实;
// 目录锚不定的拒单在聊天里走 `Display` 原样显示(它不在上报名单里),所以摸不到这句。
expect(
projectRuntimeVisibleRejectionError(
'无法锚定 Direct 调用项目目录:拒绝访问',
'陶泥儿智能创作',
),
).toBe('陶泥儿智能创作 被项目权限或安全策略阻止,请检查审批配置');
});
test('宿主 `Display` 的事实句不再掉进通用兜底', () => {
expect(
projectRuntimeVisibleError(
'执行通道已断开,不能自动重放未确认操作:Codex app-server 已退出;exitStatus=signal: 9 (SIGKILL) stderrClass=crash',
'陶泥儿智能创作',
true,
),
).toBe('陶泥儿智能创作 服务连接已断开,请稍后重试');
expect(
projectRuntimeVisibleError(
'等待模型回合结束达到硬上限,已停止本轮并核对后台操作。',
'陶泥儿智能创作',
true,
),
).toBe('陶泥儿智能创作 响应超时,请稍后重试');
expect(
projectRuntimeVisibleError(
'陶泥儿回合的宿主任务提前结束(崩溃或任务被取消),本轮已按失败收口,请重试。',
'陶泥儿智能创作',
true,
),
).toBe('陶泥儿智能创作 本轮执行已中断,请重试');
expect(
projectRuntimeVisibleError(
'DirectProject 收尾历史失败:未确认历史完整落盘',
'陶泥儿智能创作',
true,
),
).toBe('陶泥儿智能创作 保存运行记录失败,请检查项目目录后重试');
});
});