diff --git a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e.mjs b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e.mjs index a2961c7cd..68fc6c1b5 100644 --- a/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e.mjs +++ b/apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e.mjs @@ -7235,6 +7235,25 @@ async function readGoalStatus() { return goal; } +async function assertGoalRuntimeCanProgress(codePrefix) { + const [taskSnapshot, runtime] = await Promise.all([ + readTaskSnapshot(), + readJson(mainRuntimeStatePath()).catch(() => null), + ]); + const current = taskSnapshot.latest.find( + (task) => task.agentId === mainAgentId && task.runId === state.initialRunId, + ); + if (current && isFailedTask(current)) { + throw codedError(`${codePrefix}-runtime-failed`); + } + if ( + runtime?.status === 'needs-reconciliation' || + runtime?.phase === 'needs-reconciliation' + ) { + throw codedError(`${codePrefix}-runtime-needs-reconciliation`); + } +} + function assertGoalContextSnapshot(runtime, contextBundle, goal, codePrefix) { const expectedGoalSnapshotFingerprint = goalSnapshotFingerprint(goal); assert( @@ -7497,6 +7516,7 @@ async function waitForGoalOldActionBlocked(initialPending) { const deadline = Date.now() + 120_000; let lastError = null; while (Date.now() < deadline) { + await assertGoalRuntimeCanProgress('goal-old-action-block'); await assertGoalInitialMarkerAbsent( 'goal-old-action-block-poll', initialPending.actionId, @@ -7574,6 +7594,7 @@ async function waitForGoalRevisionTwoAgentVerificationFailure() { const deadline = Date.now() + runTimeoutMs; let lastError = null; while (Date.now() < deadline) { + await assertGoalRuntimeCanProgress('goal-revision-two-agent-failure'); await assertGoalInitialMarkerAbsent('goal-revision-two-verify-poll'); const records = await readOptionalJsonl( path.join(state.projectRoot, '.agent/agent.db'), @@ -10410,6 +10431,7 @@ async function monitorGoalWriteActionUntilSettled(pending) { state.goal.monitoredWriteActionIds.add(pending.actionId); const deadline = Date.now() + 120_000; while (Date.now() < deadline) { + await assertGoalRuntimeCanProgress('goal-write-action-settle'); await assertGoalInitialMarkerAbsent( 'goal-write-after-confirm', pending.actionId, @@ -14046,7 +14068,8 @@ async function validateGoalRuntimeEvidence() { state.lureLeakCount = await countLureLeaks(); assert(state.lureLeakCount === 0, 'sensitive-lure-leak-detected'); - const protocolCount = validateMainRunToolPlanProtocols(agentDb); + const nativeToolPlanProtocolEvidence = + validateNativeRuntimeToolPlanProtocolEvidence(agentDb); const successfulToolExecutionCount = receipts.filter( (record) => record.agentId === mainAgentId && @@ -14060,7 +14083,7 @@ async function validateGoalRuntimeEvidence() { agentDbRecordCount: agentDb.length, conversationMessageCount: conversations.length, successfulToolExecutionCount, - toolPlanProtocolCount: protocolCount, + ...nativeToolPlanProtocolEvidence, structuredPlanRevision: plan.revision, structuredPlanCompletedStepCount: plan.completedStepHashes.length, goalInitialCompletedStepCount: state.goal.initialCompletedStepHashes.length, @@ -15219,6 +15242,12 @@ function emptyGoalEvidence() { conversationMessageCount: 0, successfulToolExecutionCount: 0, toolPlanProtocolCount: 0, + nativeRuntimeToolPlanCount: 0, + toolPlanRepairCount: 0, + nativeRuntimeToolPlanRepairCount: 0, + wrapperToolPlanFallbackCount: 0, + textJsonToolPlanFallbackCount: 0, + toolPlanAuditPayloadLeakCount: 0, structuredPlanRevision: 0, structuredPlanCompletedStepCount: 0, goalInitialCompletedStepCount: 0, @@ -15728,6 +15757,8 @@ async function collectPartialGoalEvidence() { const completedStepCount = Array.isArray(runtime?.planSteps) ? runtime.planSteps.filter((step) => step.status === 'completed').length : 0; + const nativeToolPlanProtocolEvidence = + collectNativeRuntimeToolPlanProtocolEvidence(agentDb); return { taskCount: taskSnapshot.all.length, eventCount: events.length, @@ -15736,9 +15767,7 @@ async function collectPartialGoalEvidence() { successfulToolExecutionCount: receipts.filter( (record) => record.status === 'ok', ).length, - toolPlanProtocolCount: agentDb.filter( - (record) => record.recordType === 'agent.runtime.tool_plan.protocol', - ).length, + ...nativeToolPlanProtocolEvidence, structuredPlanRevision: runtime?.planRevision ?? 0, structuredPlanCompletedStepCount: completedStepCount, goalEditedEvidenceAbsentBeforeEdit: @@ -16210,6 +16239,80 @@ function validateMainRunToolPlanProtocols(records) { return protocols.length; } +function collectNativeRuntimeToolPlanProtocolEvidence(records) { + const protocols = records.filter( + (record) => + record.recordType === 'agent.runtime.tool_plan.protocol' && + record.agentId === mainAgentId && + record.runId === state.initialRunId, + ); + const repairs = records.filter( + (record) => + record.recordType === 'agent.runtime.tool_plan.repair' && + record.agentId === mainAgentId && + record.runId === state.initialRunId, + ); + const audits = [...protocols, ...repairs]; + return { + toolPlanProtocolCount: protocols.length, + nativeRuntimeToolPlanCount: protocols.filter( + (record) => record.protocol === 'native_runtime_tools', + ).length, + toolPlanRepairCount: repairs.length, + nativeRuntimeToolPlanRepairCount: repairs.filter( + (record) => record.protocol === 'native_runtime_tools', + ).length, + wrapperToolPlanFallbackCount: audits.filter( + (record) => record.protocol === 'native_function', + ).length, + textJsonToolPlanFallbackCount: audits.filter( + (record) => record.protocol === 'text_json', + ).length, + toolPlanAuditPayloadLeakCount: audits.filter( + (record) => + Object.hasOwn(record, 'arguments') || + Object.hasOwn(record, 'response') || + Object.hasOwn(record, 'toolArguments'), + ).length, + }; +} + +function validateNativeRuntimeToolPlanProtocolEvidence(records) { + const protocolCount = validateMainRunToolPlanProtocols(records); + const evidence = collectNativeRuntimeToolPlanProtocolEvidence(records); + const protocols = records.filter( + (record) => + record.recordType === 'agent.runtime.tool_plan.protocol' && + record.agentId === mainAgentId && + record.runId === state.initialRunId, + ); + assert( + evidence.toolPlanProtocolCount === protocolCount && + evidence.nativeRuntimeToolPlanCount === protocolCount && + evidence.nativeRuntimeToolPlanRepairCount === + evidence.toolPlanRepairCount && + evidence.wrapperToolPlanFallbackCount === 0 && + evidence.textJsonToolPlanFallbackCount === 0 && + evidence.toolPlanAuditPayloadLeakCount === 0 && + protocols.every( + (record) => + Number.isSafeInteger(record.functionCallCount) && + record.functionCallCount > 0 && + Array.isArray(record.callIds) && + record.callIds.length === record.functionCallCount && + new Set(record.callIds).size === record.callIds.length && + Array.isArray(record.functionNames) && + record.functionNames.length === record.functionCallCount && + record.functionNames.every( + (name) => + isNonEmptyString(name) && name !== 'submit_agent_tool_plan', + ), + ), + 'goal-native-tool-plan-protocol-required', + ); + return evidence; +} + async function validateSameRunSteerEvidence({ agentDb, activity, diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 1abce5126..92cf326af 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -16,6 +16,14 @@ --- +## 2026-07-16 AI 游戏创作 Goal 真实验收强制原生工具协议 + +- 背景:V1.18 的 `goal-runtime` 已证明 edit/pause/Runner 强杀/resume/finalization,但该 PASS 早于 V1.26 原生工具目录;旧验收只接受协议兼容集合,无法证明长任务没有静默退回 wrapper/text JSON。阶段等待在 Runtime 已因外部 transport 失败时还可能继续等 30 分钟。 +- 决策:Goal PASS 必须要求同一主 run 的全部成功 tool-plan 和 repair audit 都是 `native_runtime_tools`,wrapper/text fallback 为 0,function call 数量、call id、函数名数组完整且协议审计不含 arguments/response/toolArguments。PASS、部分失败与空报告统一输出协议计数。旧动作 blocked、revision 2 失败验证和写动作 settle 等长等待必须同步读取 task/runtime,failed、cancelled、budget-exhausted 或 needs-reconciliation 立即结构化失败。 +- 影响范围:`apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e.mjs`、AI 游戏创作 Runtime 与 App 实施计划;生产 Goal/Runner 状态机不改变。 +- 验证方式:正式 `openai_chat / gpt-5.5` 加强版 `goal-runtime` 最终成功计划 21/21、repair 17/17 全原生,fallback/协议 payload 为 0;Goal revision 1 -> 2、真实失败后 patchset 修复、pause、pidfd Runner 强杀、paused 零推进、显式同 run resume、verification、四阶段 finalization、唯一 assistant、零重复/重放/泄漏全部 PASS。独立 transport failure 报告的成功 6/6、repair 5/5 仍全原生,并促成 terminal fail-fast。 +- 关联文档:`docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。 + ## 2026-07-16 AI 游戏创作 Agent Runtime 使用 Provider 原生工具目录 - 背景:OpenAI-compatible planning 虽已使用 function calling,但只向 Provider 提供 `submit_agent_tool_plan` 包装函数,真实工具藏在 `actions[].tool + input` 中,具体工具名和参数主要依赖长提示词,Provider 不能按工具 schema 约束选择与输入。 diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index f78e6fcde..0ff53b029 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -14,6 +14,14 @@ - 关联:相关文件、文档、提交或 Issue ``` +## Agent 真实验收的阶段等待必须同步观察 Runtime 终态 + +- 现象:真实 Provider 已因 transport、格式修复或其它不可恢复错误把 task/Runtime 写成 failed,专项验收仍在等待某个 pending action、observation 或 receipt,直到 30 分钟总超时才返回。 +- 原因:阶段等待只轮询“想看到的成功证据”,没有同时读取 owning Agent/run 的最新 task 与 Runtime phase;外部错误发生在该证据之前时,目标条件永远不会出现。 +- 处理:所有分钟级阶段等待都要在每轮先检查 owning task 的 failed/cancelled/budget-exhausted,以及 Runtime 的 needs-reconciliation;命中后立即抛出带阶段前缀的结构化错误。正常 pause 必须保留为可恢复状态,不能被 fail-fast 当失败;Runner 强杀后的 paused 稳定窗口继续按签名零推进单独验证。 +- 验证:用正式 `goal-runtime` 观察 Provider repair transport failure,确认部分报告立即保留成功/repair 协议计数、生命周期闭合和零泄漏证据;随后完整复跑仍能通过 Goal edit/pause/Runner kill/resume/finalization,证明 fail-fast 未破坏正常恢复路径。 +- 关联:`apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e.mjs`、`docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md`。 + ## 禁止 Data URL 持久化时不要漏掉异步任务 JSON - 现象:工程、素材、图层和元数据都已禁止 Data URL 后,服务器仍在生成高峰出现 SpacetimeDB / api-server 内存急剧膨胀甚至 OOM;读取少量正式生成任务也会造成远大于响应体的瞬时内存增长。 diff --git a/docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md b/docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md index 6624d339e..368f97350 100644 --- a/docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md +++ b/docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md @@ -802,6 +802,8 @@ V1.18 对标 Codex CLI `/goal` 的长任务语义:目标文本既是首轮任 - 最终 Goal、Runtime 和最新 task 必须在原 Agent/Session/run 上 completed,结构化计划全部完成,Goal completion evidence 必须精确匹配当前 Goal/plan/verification/run/session;同一 finalizationId 必须按严格七槽物理顺序形成完整记录,finalization sidecar 最终不残留,目标 Session 只允许一个 assistant。Goal sidecar、task JSONL、Runtime state、v5 context 和 conversation 属于本地私有执行事实,可包含完成目标所需正文;event、Agent DB、receipt、activity、output 与最终报告不得保存 task、Goal/steer、委派任务、verify 命令或 error 正文,只允许身份/状态、SHA-256、字符/字节/条目计数和经 URL、项目根、其它绝对路径及凭据清洗的有界摘要。公共 task 统一不保留正文;委派只保留 `taskSha256 / taskChars`,verify 只保留脚本安全标识、`expectedCommandSha256 / expectedCommandChars`、timeout 和结果计数,error 只保留 kind/fingerprint/chars 或脱敏摘要,禁止任何正文、preview、head 或 tail。验收必须扫描完整 Goal/编辑/委派/verify/error canary、已加载密钥和一次性项目绝对路径在全部公共持久面泄漏为 0,并确认动作、消息、receipt 和 Provider lifecycle 均无重复。 - 2026-07-16 使用正式 AppData 的 `openai_chat / gpt-5.5` 路由执行隔离 `goal-runtime` suite,V1.18 真实 Provider 门禁 **PASS**。同一 Agent/Session/run 从 Goal revision 1 编辑到 revision 2,保留 1 个已完成计划步骤;旧写动作形成 1 条 `runtime.goal / blocked` receipt,执行与重放均为 0。Agent 先取得真实退出码 1,再用 1 个 patchset 修复并通过 revision 2 verification;暂停前后、Runner pidfd 强杀换 boot 后的稳定窗口中 task/plan/conversation/Provider/action 均零推进,显式 resume 后恢复原 execution owner 和原 run。最终代码快照复跑的计划 revision 11 的 8 步全部完成,11 组 Provider lifecycle 均唯一闭合,finalization v3 四阶段和两层 completed projection 完整,Session 只有 1 条 assistant;3 个副作用无重放,重复 action/message/receipt、Goal 正文、失败证据 canary、API Key、诱饵和项目绝对路径公共泄漏均为 0。验收过程同时修正 `file.write` 静默裁剪末尾换行、旧 Goal action receipt 的合法 tool/摘要转换、file/project diff 正文进入公共 event、file/memory 审计保存绝对路径,以及旧 event detail 在新脱敏投影下被误判幂等冲突五类真实缺陷;隔离 Runner、AppData 和 disposable 项目均已按 sentinel 清理。 +2026-07-16 在 V1.26 Provider 原生工具目录落地后再次执行加强版 `goal-runtime`,正式 `openai_chat / gpt-5.5` 路由 **PASS**。验收器现在强制成功计划与格式 repair 全部使用 `native_runtime_tools`,拒绝 wrapper/text fallback,逐条校验 function call 数量、call id 唯一性、函数名数组,并拒绝协议审计携带 arguments、response 或 toolArguments 正文;失败报告也输出同一组协议计数。最终复跑的成功计划 21/21、repair 17/17 均为原生目录,fallback 与协议审计 payload 为 0;同一 Goal revision 1 -> 2、旧动作 blocked、真实退出码 1、单 patchset 修复、pause、Runner pidfd 强杀、paused 零推进、显式同 run resume、计划 revision 11 的 6 步完成、verification、finalization v3 四阶段、两层 completed projection 和唯一 assistant 全部成立,3 个副作用无重放,重复与正文/Key/诱饵/路径泄漏为 0,隔离资源完整清理。另一次独立复跑在 revision 2 repair lifecycle 遇到上游 transport failure,Runtime 正确失败且 6/6 成功计划、5/5 repair 仍全为原生目录;Goal 专用长等待现会同步检查 task/runtime 终态,在 failed 或 needs-reconciliation 时立即返回结构化失败,不再空等 30 分钟总超时。 + ## V1.19 后台 Agent 真流式最终回复 V1.19 对标 Codex 富客户端的增量 turn 事件:工具开始、完成和等待继续沿用现有 Runtime state/event;只有已经进入 `phase=response` 的用户可见最终回复允许输出 Provider 文本 delta。后台工具 planning、function arguments、`thinkingSummary`、原始 observation 和修复上下文不得进入流。禁止前端拆字、定时补字或先生成完整正文再伪装流式。 diff --git a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md index 5e5e0932c..b2aed2566 100644 --- a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md +++ b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md @@ -574,4 +574,5 @@ game-project/ - 2026-07-16 V1.25 已完成真实验收:正式 `openai_chat / gpt-5.5` 的 `project-skill` suite 先让 hash-only 原始验收真实失败;Agent 在首个变更前精确读取匹配 Skill 1 次、无关 Skill 0 次,以 1 个变更动作只修改目标文件,Agent `project.verify` 与宿主复验均通过。最终脚本复跑记录 25 条 task、41 条 event、57 条 Agent DB 和 5 个成功工具动作;4 组 tool-plan Provider lifecycle 唯一闭合,最终 assistant/completed 各 1,Skill 正文、API Key、诱饵、项目/配置路径泄漏和重复持久化均为 0,隔离 Runner/AppData/项目完整清理。 - 2026-07-16 起,同一 Runtime 文档的“V1.26 Provider 原生工具目录”作为 OpenAI-compatible planning 协议事实源。Chat / Responses 不再只广告 `submit_agent_tool_plan` 包装函数,而是直接提供 `update_agent_plan`、`respond_to_user`、全部内置 Runtime action 和动态 MCP function;每个函数使用独立 schema,Runtime 继续负责身份、权限、确认、沙箱、revision、验证、恢复与副作用防重放。Anthropic 与历史 fixture 保留 text JSON / wrapper 解析兼容,但新请求和 repair 不能静默降级。plan-only 是合法持久 checkpoint,未完成计划仍阻止最终化。 - 2026-07-16 V1.26 已完成真实验收:正式 `openai_chat / gpt-5.5` 的 `project-skill` suite 中 9/9 个成功工具计划与 6/6 个格式修复全部使用 `native_runtime_tools`,wrapper/text fallback 均为 0。Agent 自主读取匹配 Skill、只改唯一目标文件并完成 Agent/宿主双重验证;15 个 tool-plan 和 1 个 final-reply lifecycle 唯一闭合,最终 assistant/completed 各 1,重复、Skill 正文、API Key、诱饵、项目/配置路径和报告泄漏均为 0,隔离 Runner/AppData/项目完整清理。 +- 2026-07-16 V1.26 后重新加强并复验 `goal-runtime`:Goal suite 现在把成功计划、repair、call metadata、wrapper/text fallback 和协议审计零 payload 纳入硬门禁。正式 `openai_chat / gpt-5.5` 最终复跑的成功计划 21/21、repair 17/17 全为 `native_runtime_tools`;Goal edit、旧动作失效、真实失败修复、pause、Runner 强杀、显式同 run resume、verification、finalization 和唯一回复全部 PASS,重复、重放、正文、密钥、诱饵与路径泄漏均为 0。Goal 阶段等待同时增加 terminal fail-fast,Provider transport failure 不再占满 30 分钟验收超时。 - 开发模式可通过本地项目文件面板执行 `file.list/read/write/delete`,普通用户界面不暴露文件面板。