加固Goal Provider请求与最终化恢复

绑定Provider请求快照与稳定requestId并禁止歧义错误自动重放
新增Goal真实E2E控制序列、Runner归属清理和失败验证门禁
修复finalization取消竞态、严格七槽容量预留和精确审计恢复
收紧任务委派验证错误等公共审计与敏感信息脱敏
补齐Goal CLI初始化、orphan reconciliation和异常恢复回归
同步Runtime技术方案与长期决策记录
This commit is contained in:
AIGameCreator App
2026-07-15 12:59:28 +08:00
parent b0be078d6c
commit fda565f40b
7 changed files with 9024 additions and 514 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+67 -11
View File
@@ -40,6 +40,7 @@ pub(crate) enum CliCommand {
agent_id: String,
session_id: String,
run_id: String,
initialize: bool,
},
AgentGoalEdit {
project_path: PathBuf,
@@ -142,11 +143,15 @@ impl CliCommand {
project_path,
initialize,
..
}
| Self::AgentGoalStart {
project_path,
initialize,
..
} => Some((project_path, *initialize)),
Self::AgentChat { project_path, .. }
| Self::AgentRuntimeStatus { project_path, .. }
| Self::AgentGoalStatus { project_path, .. }
| Self::AgentGoalStart { project_path, .. }
| Self::AgentGoalEdit { project_path, .. }
| Self::AgentGoalPause { project_path, .. }
| Self::AgentGoalResume { project_path, .. }
@@ -385,19 +390,26 @@ pub(crate) fn parse_cli_command(args: &[String]) -> Result<Option<CliCommand>, S
}));
}
if args.first().map(String::as_str) == Some("--agent-goal-start") {
const USAGE: &str =
"用法:--agent-goal-start <本地项目绝对路径> <agentId> <sessionId> <runId> --stdin";
if args.len() != 6
|| args.last().map(String::as_str) != Some("--stdin")
|| args[1..5].iter().any(|value| value.trim().is_empty())
const USAGE: &str = "用法:--agent-goal-start [--init] <本地项目绝对路径> <agentId> <sessionId> <runId> --stdin";
let mut rest = args[1..].to_vec();
let initialize = if let Some(index) = rest.iter().position(|arg| arg == "--init") {
rest.remove(index);
true
} else {
false
};
if rest.len() != 5
|| rest.last().map(String::as_str) != Some("--stdin")
|| rest[..4].iter().any(|value| value.trim().is_empty())
{
return Err(USAGE.to_string());
}
return Ok(Some(CliCommand::AgentGoalStart {
project_path: PathBuf::from(&args[1]),
agent_id: args[2].trim().to_string(),
session_id: args[3].trim().to_string(),
run_id: args[4].trim().to_string(),
project_path: PathBuf::from(&rest[0]),
agent_id: rest[1].trim().to_string(),
session_id: rest[2].trim().to_string(),
run_id: rest[3].trim().to_string(),
initialize,
}));
}
if args.first().map(String::as_str) == Some("--agent-goal-edit") {
@@ -834,9 +846,26 @@ pub(crate) fn run_cli_command(command: CliCommand) -> Result<(), String> {
agent_id,
session_id,
run_id,
initialize,
} => {
let project_path = canonicalize_cli_path(&project_path, "本地项目路径", false)?;
let project_path = canonicalize_cli_path(&project_path, "本地项目路径", initialize)?;
require_external_agent_runner_for_cli_runtime_write(&project_path)?;
if initialize && !project_path.join(".agent/manifest.json").is_file() {
let project_name = project_path
.file_name()
.and_then(|value| value.to_str())
.map(str::trim)
.filter(|value| !value.is_empty())
.unwrap_or("CLI Goal 项目");
init_local_game_project_at(
&project_path,
&format!("cli-goal-{}", unix_millis()),
project_name,
)?;
}
if !project_path.join(".agent/manifest.json").is_file() {
return Err("项目尚未初始化;请先在 App 中创建项目,或显式传入 --init".to_string());
}
let payload = read_cli_agent_goal_payload(&mut std::io::stdin().lock())?;
let result = start_game_creator_agent_goal(
project_path.display().to_string(),
@@ -1251,9 +1280,36 @@ mod tests {
agent_id: "code-prototype".to_string(),
session_id: "session-7".to_string(),
run_id: "goal-run-9".to_string(),
initialize: false,
}
);
let mut initialized_start = parse_cli_command(&[
"--agent-goal-start".to_string(),
"--init".to_string(),
project_path.display().to_string(),
"code-prototype".to_string(),
"session-8".to_string(),
"goal-run-10".to_string(),
"--stdin".to_string(),
])
.expect("parse initialized goal start")
.expect("initialized goal start command");
assert_eq!(
initialized_start,
CliCommand::AgentGoalStart {
project_path: project_path.clone(),
agent_id: "code-prototype".to_string(),
session_id: "session-8".to_string(),
run_id: "goal-run-10".to_string(),
initialize: true,
}
);
let (_, initialize) = initialized_start
.project_path_mut()
.expect("initialized Goal start project path");
assert!(initialize);
let edit = parse_cli_command(&[
"--agent-goal-edit".to_string(),
project_path.display().to_string(),
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -133,11 +133,11 @@
- 2026-07-12 加固:`file.delete` 取得项目写锁后必须重新读取当前项目和 Agent 权限策略;锁竞争期间从 allow 改为 deny 时立即阻断,从 allow 改为 confirm 时自动动作退回待确认,只有已确认动作可继续。Agent 私有记忆以及客户端开发面板的文件、记忆、资产、草案、导出和 checkpoint 恢复写入都在同一项目锁内保守推进全局 revision,确保等待中的旧删除动作不会作用于客户端刚改写的内容。manifest 持久化使用同目录临时文件;平台不能覆盖既有文件时先移动到 `.manifest.json.previous` 恢复副本,主文件缺失时从副本读取,安装新文件失败时恢复旧文件。
- 2026-07-11 补充,2026-07-12 更新:单 Agent 代码闭环新增开发专用 `project.verify`,用于在修改后执行项目根 `package.json` 已定义的固定脚本 `check / typecheck / test / lint / build`,或以 `check: / test: / lint: / typecheck: / build: / verify: / validate:` 开头、后缀由安全非空段组成的命名脚本;当前只支持 npm,不接受自由命令、参数或工作目录。Agent 必须先读取普通文件 `package.json`,再把真实存在的脚本名、完整原始脚本文本 `expectedCommand` 和 1-300 秒超时一起提交;Runtime 在真正执行前重新解析 JSON,要求脚本仍存在且正文精确一致,脚本漂移时拒绝执行。非 npm `packageManager` 或 pnpm / yarn / bun 锁文件必须失败关闭;`pre* / post*` 生命周期脚本名不在允许范围,npm 执行再附加 `--ignore-scripts`,阻止所选脚本关联的 pre/post lifecycle。该工具使用独立、默认 `confirm``project.verify` 权限,不再与 `command.run_limited` / `game.static_smoke` 共用授权;确认指纹覆盖脚本正文和超时。执行时不经过 App 自行拼接的 `bash -c`;继承环境被清理到 PATH 与必要平台变量,HOME/TMP/npm cache 隔离,stdin 关闭,输出保留有界头尾。Unix 下验证根进程正常结束或超时都会清理同进程组残留后代;项目写锁会按持有 PID 回收崩溃遗留锁,并拒绝 `.agent` 符号链接逃逸。进入进程执行后的成功、非零退出、启动失败和超时会写 `.agent/logs/command.log`、manifest command run 与 `agent.runtime.project.verify` 审计;输入预检拒绝则只进入 Runtime observation / error 事件。输出先过滤敏感内容再进入 observation。只要最新 `project.verify` 未通过,或通过后又发生 `file.write / file.patch / file.delete / project.restore`Runtime 就拒绝模型用空 actions 假完成,继续要求修复和重新验证;多窗口重复无进展而以 `loop-budget-exhausted` 终止时,仍未形成新通过结果则保持失败。该能力会执行用户项目自身脚本,环境隔离不等同于 OS 沙箱,不能把不可信项目脚本视为安全代码;它不是自由 shell 代理,也不进入普通用户命令入口。
- 2026-07-11 补充:开发侧新增 headless 单 Agent Runtime 入口 `npm run ai-game-creator-shell:agent-task -- [--init] <projectPath> <agentId> <task>`。该入口不实现第二套 Agent,只复用 Tauri App 的持久任务队列、per-agent 锁、LLM 路由、权限策略、工具 action / observation loop、对话和审计文件,并轮询到 `completed / failed / waiting-for-confirmation` 后用稳定键值行退出;`--init` 只在显式传入且 manifest 不存在时初始化项目。遇到待确认动作时 CLI 返回非零并打印 actionId、tool 和脱敏摘要,后续仍由开发窗口完成确认,不提供静默 `--yes` 绕过。
- 2026-07-11 补充:后台 Agent 工具规划和最终回复请求`LlmError::EmptyResponse` 最多自动重试 3 次(含首次共 4 次请求),与既有 Generator 对上游 HTTP 成功但空 content 的恢复策略一致;对 `Timeout / Connectivity / Transport` 及上游 `408 / 429 / 5xx` 最多额外自动重试 2 次并做线性退避,配置、请求、流能力、反序列化错误及其他 `4xx` 仍立即失败。重试发生在工具计划被解析和执行前,或最终回复尚未落盘时,不会重复执行已经落盘的工具动作
- 2026-07-15 收口:废止后台 Agent 工具规划和最终回复对 `EmptyResponse / Timeout / Connectivity / Transport / 408 / 429 / 5xx` 的原样自动重试。每个 Provider request lifecycle 只允许一次物理请求,专用客户端强制 `max_retries=0`,不继承全局或 per-Agent 的 `maxRetries`;可观察错误写唯一 `failed` 终态,`started` 后没有可信终态则进入 `needs-reconciliation` orphan barrier。只有显式 steer、Goal resume 或人工 reconciliation 后的新 request slot 才能建立新 lifecycle;工具协议格式修复使用新的 repair slot/lifecycle,不属于传输重试
- 2026-07-11 调整,2026-07-12 更新:后台单 Agent 的 planning loop 每 6 轮形成一个上下文压缩窗口,每轮工具动作上限仍为 3;6 轮不再是整个 run 的固定上限。`loopIteration` 在同一 run 内连续递增,`maxLoopIterations` 指向当前窗口结束轮次,跨重启待确认动作按 context bundle 的 `nextLoopIndex` 继续。每个窗口结束时压缩已有 observation;窗口产生新的独立观察时继续同一 run,最近 6 轮没有独立进展或相邻窗口指纹重复时才进入 `failed / budget-exhausted`,并记录 `loop-budget-exhausted`,不会伪装完成。该调整只作用于后台单 Agent Runtime,不改变游戏草案 Generator/Evaluator 的 3 轮上限;旧实施摘要中“后台 3 轮后整理最终回复”或“整个 run 最多 6 轮”的描述由本条取代。
- 2026-07-12 补充:后台 Agent 每个 run 的可恢复 planning 上下文使用 `.agent/runtime/context-bundles/<agentId>/<runId>.json`。Runtime 通过临时文件替换原子写入,绑定 Agent、Task、Session、Run 和任务正文,保存 `nextLoopIndex`、当前窗口、计划、fallback response、压缩后的 observation 与上一窗口指纹;单文件最多 64 KiB、最多 12 条 observation。写入前统一截断并过滤敏感内容和项目绝对路径,安全校验失败时拒绝落盘;读取时要求普通文件,并校验 schema、Agent、Session、Run、任务正文和 observation 数量,身份不一致时拒绝续跑。该路径属于 Runtime 私有控制面,与根级 `.agent/context.bundle.json` 的旧 run-control 辅助文件不是同一契约,通用文件工具不得暴露。
- 2026-07-11 调整:开发者投递的后台任务从队列记录、Runtime `currentTask/currentGoal` 到待确认动作私有账本统一保留最多 4,000 字符,不再在入队时截成 180 字符。180 字符只用于 UI、事件和审计预览;LLM planning、失败重试、确认续跑和重启恢复必须使用完整任务字段,避免位于长需求末尾的验收条件、禁止项或输出格式在真正执行前丢失
- 2026-07-11 调整:后台结构化 planning 使用独立的 4,000 输出 token 上限,最终回复使用 2,400;两者显式请求 low reasoning effort 和 low text verbosity。`platform-llm` 会把 reasoning effort 同时映射到 OpenAI Responses 的 `reasoning.effort` 与 Chat Completions 的 `reasoning_effort`,未设置时不新增字段。真实 gpt-5.5 Chat 响应曾连续消耗约 1,000-1,400 completion tokens 却不返回 message content低推理强度较大的可见输出余量和 EmptyResponse 重试共同构成恢复策略
- 2026-07-11 调整2026-07-15 收口:开发者投递的后台任务从队列记录、Runtime `currentTask/currentGoal` 到待确认动作私有账本统一保留最多 4,000 字符,不再在入队时截成 180 字符。必要的 180 字符可见摘要只用于私有执行界面;公共 event、Agent DB、receipt、activity、output 和报告不再保存任务预览或正文,只保存 `taskSha256 / taskChars` 等身份、哈希和计数。LLM planning、显式恢复、确认续跑和重启恢复继续使用私有完整任务字段,避免丢失长需求末尾的验收条件、禁止项或输出格式。
- 2026-07-11 调整2026-07-15 收口:后台结构化 planning 使用独立的 4,000 输出 token 上限,最终回复使用 2,400;两者显式请求 low reasoning effort 和 low text verbosity。`platform-llm` 会把 reasoning effort 同时映射到 OpenAI Responses 的 `reasoning.effort` 与 Chat Completions 的 `reasoning_effort`,未设置时不新增字段。低推理强度较大的可见输出余量只用于降低空响应概率;`EmptyResponse` 仍按单次 lifecycle 的歧义失败处理,不再自动原样重放
- 2026-07-11 补充:后台单 Agent 的工具计划响应只接受可反序列化为计划 schema 的 JSON object。解析器提取模型输出中的首个完整对象并允许对象后带普通说明;未找到完整 JSON 对象,或提取对象无法反序列化为工具计划时,Runtime 最多追加 2 次自动格式修复请求。每次修复只携带限长、脱敏后的上一次无效输出,并写入 `agent.runtime.tool_plan.repair` 审计。两次修复后仍无有效对象则按工具规划失败处理;工具规划阶段的普通文本不得转换为默认的空 actions + response,也不得据此把任务标记为完成。
- 2026-07-11 调整:工具计划顶层 `thinkingSummary / plan / actions / response` 四个字段必须同时存在,未知顶层字段、空 thinkingSummary 和空 tool 均属于协议错误并进入同一格式修复预算,`{}` 或前置无关 JSON 对象不能再触发空计划收束。空 actions 表示 planning 收束;response 非空时直接采用,response 为空时进入独立的最终回复生成。`agent.runtime.project.verify` 审计同时保存 `runId / actionId / actionFingerprint`,使并行 Agent 的失败与通过记录能够精确归属到发起动作。
- 2026-07-12 补充:OpenAI Chat / Responses 的后台 Agent 工具 planning 改用唯一 `submit_agent_tool_plan` 原生 function tool,字符串 `tool_choice=required` 和 strict schema;只接受恰好一次同名调用,arguments 继续经过本地计划 schema、工具白名单和权限策略校验,错误函数、多调用或非法 arguments 进入原有两次格式修复预算且不产生副作用。Anthropic 保留文本 JSON 回退;planning 非流式,最终回复仍可流式。每轮成功协议写 `agent.runtime.tool_plan.protocol`,修复审计记录 protocol、callId 和 functionName。
@@ -4433,7 +4433,7 @@
- 决策:父 run 在 `waitingGroups > 0` 时必须持久进入 `waiting-for-isolated-join`、保存原 context cursor 并释放 Agent lane;重复 resume 只返回等待状态,不请求 LLM、不推进 loop,也不取消仍在工作的 child。最后一个 child 就绪后写 `deliveryTarget=parent-wake` 并唤醒同一 parent run / session,由模型通过持久 `agent.run_status` actionId 认领;不得创建 join continuation。活跃 planning / running 父 run 直接保留 ready join 等待认领,重复 dispatch 不创建任务。旧 delivery 缺少 target 时按 continuation 单向兼容。
- 决策:action task / event 投影的幂等阶段键为 `runId + actionId + phase`,允许同一 action 从 waiting-for-confirmation 合法推进到终态 observation,同阶段冲突仍失败关闭。Agent DB 终态 observation 扫描跳过同 action 的非终态前置记录,只对既有终态做全字段一致性检查;`recentToolCalls` 按 actionId 原位更新,避免 waiting 投影遮住最终结果。
- 决策:动作历史结构化 detail 上限 7,200 字符;超预算时只能先删除可选字段,再按最旧优先删除完整记录,不得字符截断 JSON,也不得清空 `runId / actionFingerprint` 破坏身份。运行时文本清洗必须覆盖 Unix、Windows 盘符、正反斜杠 UNC、`file:/...``file:///...` 及百分号编码绝对路径,统一替换为 `<absolute-path>`,并保留普通相对文本与 HTTP(S) URL。
- 决策:后台 planning 和最终回复瞬时 LLM 错误重试上限提高为额外 5 次,覆盖 `Timeout / Connectivity / Transport` 与上游 `408 / 429 / 5xx`,按 500ms 线性递增退避;不可重试错误继续直接失败,任何重试都不得跨越工具执行或回复落盘提交点
- 2026-07-15 修正:撤销后台 planning 和最终回复瞬时错误额外自动重试”的旧契约。真实长 planning 的 TLS 失败证明“尚未形成 plan/action”不能证明 Provider 未接收或未计费;`Timeout / Connectivity / Transport / EmptyResponse / 408 / 429 / 5xx` 均不得在同一 lifecycle 内原样重放,底层 `LlmClient` 强制 `max_retries=0`。错误只保存 kind、SHA-256、字符数和脱敏摘要;是否再次请求必须经过显式恢复并使用新的 request slot/lifecycle。按错误指纹切换 TLS 栈、HTTP 版本或协议版本的实验没有真实收益且扩大共享依赖,继续不作为全局传输分支
- 决策:本轮只交付模型工具,不新增前端动作历史弹窗;UI 继续显示最近动作投影,后续历史查看必须使用独立弹窗。
- 验证:Rust 全量 507 项中 504 通过、3 项真实浏览器 opt-in 用例按设计忽略;覆盖 receipt 折叠、组合过滤、默认值与上限、敏感清洗、旧记录、尾部修复、中间损坏失败关闭、身份冲突、句柄安全、目录同步、确认阶段到终态投影、`parent-wake` 和恢复补齐。最终真实 `gpt-5.5` V1.6 `llm-runtime` 套件中,模型实际调用 1 次 `agent.action_history` 并返回 1 条与 `.agent/agent.db` 全身份对齐的当前 run 记录;94 条 task、158 条 event、164 条 Agent DB、11 条合法工具协议、13 次成功工具执行和 24 条 terminal receipt 中,主 run receipt 为 18,递归历史、重复 receipt / action / message、receipt identity 冲突、密钥和诱饵泄漏均为 0。Runner 强杀后恢复原 run / session 且身份稳定,3 个隔离实例形成唯一 all-join 认领,本次真实竞态未创建 continuation task;动作历史只在父 run 认领 join 后执行,项目、桌面和移动验证通过。
@@ -4584,8 +4584,14 @@
- 动作门禁:pending action 升级为 `game-creator-pending-action.v5`,在 project revision、verification gate、repository context fingerprint 和 steer cursor 之外绑定 `goalId / goalRevision / goalSnapshotFingerprint`;旧 v1-v4 全部失败关闭。Goal edit 提交新 revision 后,旧自动动作和旧待确认动作统一转成 `blocked` observation,在原 run 重规划,禁止执行旧副作用、从当前 Goal 猜回绑定或创建 retry run。
- 暂停恢复:Runner 重启先处理 cancel / Goal control,再进入 process reconciliation、finalization、pending action 和 runnable task`pause-requested` 必须先收束成 `paused``paused` 直接保持休眠。resume 的有效迁移只接受 `paused -> active`,先清理同一 run 遗留 cancel tombstone,再唤醒原 Agent/Session/run,不创建新 run;若 sidecar 已 `active` 但 Runtime 投影或 Runner 唤醒未提交,重复 resume 继续补齐同一 run,不能假成功。当前 Agent/Session/run 的 Goal sidecar 损坏或冲突时,即使 Runtime 缺少 legacy `goalId` 投影也失败关闭到 reconciliation。
- Finalizationjournal 升级为 `game-creator-runtime-finalization.v3` 并绑定 Goal revision/快照。assistant 按稳定 messageId 落盘后,先可靠写入 Runtime completed task/state,再提交 Goal completed,并补写携带 Goal 终态的 task/state projection;全部可靠后 journal 才进入 `runtime-completed` 并删除。assistant 尚未落盘且 Goal revision 漂移时丢弃旧 prepared journal 并 same-run 重规划,assistant 已落盘后只补投影,不再请求 Provider。
- 持久请求证据:background planning / final reply 的 request snapshot 固定绑定 project、Agent、task、Session、run、source、Goal ID/revision/snapshot fingerprint、applied steer cursor、request kind 和 request slotrequestId 从该闭集稳定派生。Provider future 真正开始前可靠追加 `agent.runtime.provider_request.lifecycle / started`,且该 lifecycle 只能发起一次物理请求;专用客户端强制 `max_retries=0`。返回、可观察失败或控制中断后以同一 requestId 追加唯一 `completed / failed / interrupted`,任何歧义错误不得原样自动重放;显式恢复必须创建新的 slot/lifecycle。记录不含 prompt、工具输入、URL、模型、回复或错误正文。
- Provider orphan barrier:注册后在项目写锁内复核 queued steer、cancel tombstone、规范 Goal、task/Runtime 身份和 steer cursor,再提交 `started`;已生效控制不写伪 `started`。启动新请求前全量扫描同 Agent/run 的 lifecycle;发现 `started` 没有可信唯一终态,或同 request 多终态、字段冲突、阶段重复/倒置时,立即把原 run 投影为 `needs-reconciliation`,阻断 Provider、工具和 finalization,禁止自动补发。paused 重启窗口必须以 started 数量零增长证明没有暗中请求。
- Finalization 顺序与容量:同一 `finalizationId / messageId` 的物理七槽严格固定为 `lifecycle/prepared -> conversation.message assistant 审计 -> lifecycle/assistant-persisted -> lifecycle/runtime-completed -> lifecycle/goal-completed -> agent.runtime.completed -> agent.runtime.background_task.completed`。prepared 成功即在独立的 128 条 lifecycle/finalization reserve 中同时预留后六条的记录数和最大字节容量;七条都不能占用 64 条 action receipt/reconciliation reserve。缺前序、倒序、重复、跨身份匹配失败或容量无法兑现时失败关闭;prepared journal 后首条审计失败保持 `finalizing` 并恢复补齐,不得改判普通 failed 或重放 Provider/assistant。
- Finalization 生产闭集:四条 lifecycle 只允许固定 lifecycle 字段和统一 `schemaVersion / updatedAt` envelope,并绑定 `responseChars / conversationPath`assistant 审计只允许 `recordType / agentId / sessionId / role / path / messageId / finalizationId`,两条 completed 审计只允许 `recordType / agentId / taskId / sessionId / runId / source / finalizationId / messageId / responseFingerprint / responseChars`,再加同一 envelope。匹配必须逐字核对 finalization/message、Agent/task/Session/run/source、Goal/plan 快照、response fingerprint/chars 和 conversation path,四阶段还必须核对 ordinal/previousStage 与 JSONL 物理顺序;任何额外生产字段都不能获得 finalization reservation。
- 公共投影边界:task、Goal/steer、委派任务、`project.verify` 命令和 Provider/Runtime error 正文只保留在对应私有执行事实中。event、Agent DB、receipt、activity、output 与报告统一只存身份、状态、SHA-256、字符/字节/条目计数和经 URL、项目根、其它绝对路径及凭据清洗的有界摘要;公共 task 固定不存正文,委派只存 `taskSha256 / taskChars`verify 只存脚本安全标识、`expectedCommandSha256 / expectedCommandChars`、timeout 和结果计数,error 只存 kind/fingerprint/chars 或脱敏摘要。禁止保留 task/Goal/委派/命令/error 的正文、preview、head 或 tail;普通非 Goal 任务也不例外。
- 真实验收器:revision 2 marker/path/content 不再预埋首轮项目 fixture;两个 revision 都必须命中同一交付路径的真实 `file.write``project.patchset create` 待确认动作,edit 前最终 marker/文件必须不存在,revision 1 已完成步骤在 revision 2 和终态不可回退。Goal suite 使用带 sentinel 的专用 AppData,配置只以 hardlink 复用并在清理前核对 inode/hash;全部 CLI 固定指向专用 config dirRunner 强杀绑定 endpoint、boot、实际二进制/argv 和 OS 启动指纹,endpoint 丢失只允许回收已认领的同指纹进程。CLI JSON 只接受精确 assigned 前缀,Goal completion evidence 按四项生产契约逐字核对,公共扫描同时包含完整正文和两个 marker,失败报告从现存 task/event/Agent DB/conversation 分面容错回收部分证据而不再全报 0。
- 展示边界:开发 Agent UI 使用 `执行 / 聊天 / 目标` 三段模式,Goal 创建/编辑通过独立弹层完成,并展示状态、revision、完成标准和暂停/恢复/清理;纯聊天 CLI 提供对应 `/goal` 命令。正式用户 Project Supervisor 页面不暴露 Goal 管理控件。
- 验收现状:确定性回归与 UI 覆盖不能替代真实 Provider 长链路。截至 2026-07-15 尚未记录 V1.18 真实 Provider PASS;恢复后必须用一次性项目完成 Goal edit、pause、Runner 强杀、重启保持 paused、显式同 run resume、唯一 assistant 和零旧动作重放的交叉取证。
- 验收现状:确定性回归与 UI 覆盖不能替代真实 Provider 长链路。截至 2026-07-15 尚未记录 V1.18 真实 Provider PASS最新现场仍在首轮 planning、零 plan/action 时由对端关闭长连接,Rust 25.2 秒短请求成功只能证明基础通道。恢复后必须用一次性项目完成 Goal edit、pause、Runner 强杀、重启保持 paused、显式同 run resume、唯一 assistant 和零旧动作重放的交叉取证。
## 2026-07-15 Project Supervisor 纯聊天短入口
@@ -376,7 +376,7 @@ npm run ai-game-creator-shell:agent-runtime:real-e2e -- --config-dir <AppData> -
- policy denied 和未知工具也要先落 durable observed pending,再追加 terminal receipt。terminal observation 一旦持久化,就不能在 receipt 前响应取消;receipt 成功后再结束取消流程。receipt 写入失败统一进入 `needs-reconciliation`,恢复仅补回执,不重放工具。
- action 关联的 task / event 投影按 `runId + actionId + phase` 幂等,同一动作允许从 `waiting-for-confirmation` 合法推进到终态 observation,但同阶段身份冲突继续失败关闭。Agent DB 终态 observation 扫描跳过同 action 的非终态前置记录,只对既有终态做全字段复核;`recentToolCalls` 按 actionId 原位更新,不能让 waiting 投影遮住后续 `ok / failed`
- `agent.action_history` 结构化 detail 上限 7,200 字符;超预算时先移除可选字段,再删除完整最旧项,禁止用字符截断破坏 JSON,禁止清空 `runId / actionFingerprint`。上下文清洗覆盖 Unix、Windows 盘符、正反斜杠 UNC、`file:/...``file:///...` 和百分号编码的 `file:` URI,绝对路径统一替换为 `<absolute-path>`,同时保留普通相对文本与 HTTP(S) URL。
- 后台 planning 与最终回复遇到 `Timeout / Connectivity / Transport` 或上游 `408 / 429 / 5xx` 时最多额外重试 5 次,按 `500 / 1000 / 1500 / 2000 / 2500ms` 退避;配置、请求、流协议、反序列化错误及其他 `4xx` 不重试。重试只发生在工具计划执行前或最终回复落盘前,不能重放已完成副作用
- 2026-07-15 收口:后台 planning 与最终回复的每个 Provider request lifecycle 只允许一次物理请求,专用 `LlmClient` 无条件强制 `max_retries=0`,不继承全局或 per-Agent `maxRetries``Timeout / Connectivity / Transport / EmptyResponse / 408 / 429 / 5xx` 等歧义错误只收束当前 lifecycle,禁止自动原样重放;只有显式 steer、Goal resume 或人工 reconciliation 后的新 request slot 才能建立新 lifecycle。工具协议解析失败后的格式修复使用独立 repair slot/lifecycle,不属于传输重试
- 本轮只提供模型工具,不新增前端动作历史弹窗。UI 继续显示最近动作投影;后续若增加历史查看能力,必须使用点击后打开的独立弹窗,不得在当前面板下方追加内容。
### 2026-07-13 真实验收结果
@@ -769,13 +769,13 @@ V1.18 对标 Codex CLI `/goal` 的长任务语义:目标文本既是首轮任
### 启动、编辑与运行隔离
- `/goal <文本>`Tauri start command 创建 Goal 并用同一 outcome 启动首个 background run未显式提供 verification 时,outcome 自身作为完成标准。Goal 活跃或暂停期间,当前 Session 禁止另起不相关 run;后续普通输入默认继续走同 run steer,独立任务应使用另一 Session。
- `/goal <文本>`Tauri start command `--agent-goal-start [--init] <project> <agent> <session> <run> --stdin` 创建 Goal 并用同一 outcome 启动首个 background runCLI 的 `--init` 只在 manifest 缺失时初始化一次性/新项目,不借道其它 Agent task。未显式提供 verification 时,outcome 自身作为完成标准。Goal 活跃或暂停期间,当前 Session 禁止另起不相关 run;后续普通输入默认继续走同 run steer,独立任务应使用另一 Session。
- 编辑 Goal 先在项目写锁内提交 revision,再把规范化的新目标作为同 run steer 持久化。Provider 正在 planning 时允许中断;确认中或工具执行中只排队,旧动作在下一安全边界前必须校验 Goal revision,不能在目标已变更后继续执行。旧自动动作或待确认动作若绑定旧 Goal 快照,统一转成 `blocked` observation 并在同一 run 重规划,不执行旧副作用,也不创建 retry run。编辑失败不得回退已提交 revisionRuntime 会以 sidecar 为事实源重规划并拒绝旧 finalization。
- Goal 内容进入每轮 planning/final reply 的显式“持久目标”上下文。模型仍通过 V1.17 `planUpdate` 维护可观察步骤;Goal revision 不推进 project revision、不改变权限或 verification gate,也不能放宽 sandbox/approval。
### 暂停、恢复与清理
- pause 写 durable request,并通过 typed Runner `runtime.pause` 中断当前 planning/final Provider;已进入工具的动作允许返回后再停。Provider 中断或返回边界先把可恢复 continuation 写入 v4 context bundle,其中恢复快照按 `active` Goal 语义保存,随后 Runtime 才在 LLM/工具/observation/finalization 安全边界把同一 run 收束为 `paused`。Runner 重启时先处理 cancel / Goal control,再进入 process reconciliation、finalization 和 pending action`pause-requested` 会先收束成 `paused``paused` 直接保持休眠,不生成 assistant、不创建新 run、不调用 Provider。
- pause 写 durable request,并通过 typed Runner `runtime.pause` 中断当前 planning/final Provider;已进入工具的动作允许返回后再停。Provider 注册、durable Goal control/cancel 二次复核与 `started` 审计使用同一项目写锁形成线性化边界:锁内同时核对 cancel tombstone、queued steer、Goal 状态和 task 绑定的 Goal revisionedit 已提交新 revision 但 steer 尚未落盘时也不得轮询旧 Provider 或写伪 `started`;请求先提交时才属于可中断或等待安全边界的在途调用。Provider 中断或返回边界先把可恢复 continuation 写入 v4 context bundle,其中恢复快照按 `active` Goal 语义保存,随后 Runtime 才在 LLM/工具/observation/finalization 安全边界把同一 run 收束为 `paused`。Runner 重启时先处理 cancel / Goal control,再进入 process reconciliation、finalization 和 pending action`pause-requested` 会先收束成 `paused``paused` 直接保持休眠,不生成 assistant、不创建新 run、不调用 Provider。
- 暂停父 Agent 不撤销已经 durable 投递的专业 childchild 可以把结果写成 ready,但父 run 在显式 resume 前不能认领或继续 Provider。Runner-owned process session 在暂停提交前终止,恢复后由 Agent 根据 observation 重规划,禁止按 PID 重连或重放未知 start。
- resume 的有效状态迁移只接受 `paused -> active`,先清理同一 run 遗留的 cancel tombstone,再把原 Agent/Session/run 重新投影为 pending 并唤醒 External Runner;不创建 retry run。若进程在 Goal sidecar 已写成 `active`、Runtime 投影或 Runner 唤醒尚未完成时失败,重复 resume 必须识别同一 run 的半提交并继续补齐,不能把 `active` 单独当成恢复成功或直接返回。pending confirmation 仍回到确认态,普通 planning 从 v4 context bundle 继续。clear 对活跃 Goal 复用取消 tombstone,待安全取消后把 Goal 写为 clearedpaused/completed Goal 可直接清理。clear 不删除历史 conversation、task、event 或 Goal history。
@@ -785,13 +785,22 @@ V1.18 对标 Codex CLI `/goal` 的长任务语义:目标文本既是首轮任
- pending action 升级为 `game-creator-pending-action.v5`,在既有 project revision、verification gate、repository context fingerprint 和 steer cursor 外,固定绑定 `goalId / goalRevision / goalSnapshotFingerprint`。旧 v1-v4 一律失败关闭,不能从当前 Goal 猜回缺失绑定;Goal edit 后,无论动作原为自动还是待确认,都把旧记录收束成稳定 `blocked` observation 并在同一 run 重规划。
- finalization journal 升级为 `game-creator-runtime-finalization.v3`,把 Goal 快照指纹纳入 finalizationId。prepared 回复必须绑定当前 active Goal、同一 run/revision、全部完成的结构化计划、清空的 process/join/delegate 屏障和现有 verification gate。Goal 编辑、暂停、清理或 revision 漂移会让未提交 assistant 的旧 finalization 失效并回到同 runassistant 已提交后只允许按 journal 原快照补齐 Runtime 与 Goal completed,不能重新请求 Provider。
- assistant 持久化后,finalization 先写 Runtime completed task/state,再把规范 Goal 写为 completed,并补写携带 completed Goal 状态的 task/state projection;两层投影均可靠后,journal 才推进 `runtime-completed` 并删除。完成证据由系统从最终结构化计划、verification gate、run/session 身份和 response fingerprint 生成,不保存模型 thinking 或原始私有 observation。Goal 未完成、paused、clearing、sidecar 缺失或 revision 不匹配时,response 不能绕过完成门禁。
- finalization 以同一 `finalizationId / messageId` 在 Agent DB 追加四条 `agent.runtime.finalization.lifecycle``prepared` 在 prepared journal 后,`assistant-persisted` 在会话及其审计和 journal 状态后,`runtime-completed` 在首个 Runtime completed task/state 后且 Goal 完成前,`goal-completed` 在规范 Goal 与第二个 completed task/state 均可靠后。四阶段字段闭集固定为 `recordType / auditSchemaVersion / journalSchemaVersion / agentId / taskId / sessionId / runId / source / finalizationId / messageId / stage / stageOrdinal / previousStage / goalId / goalRevision / goalSnapshotFingerprint / planRevision / planSnapshotFingerprint / responseFingerprint / responseChars / conversationPath / stageAt`,持久层只可再添加统一 `schemaVersion / updatedAt` envelope;不得携带 task、response、Goal、observation、error 或其它额外字段。
- finalization-critical 物理顺序严格固定为七槽:`lifecycle/prepared -> conversation.message assistant 审计 -> lifecycle/assistant-persisted -> lifecycle/runtime-completed -> lifecycle/goal-completed -> agent.runtime.completed -> agent.runtime.background_task.completed`。assistant 审计字段闭集只允许 `recordType / agentId / sessionId / role / path / messageId / finalizationId`,两条 completed 审计只允许 `recordType / agentId / taskId / sessionId / runId / source / finalizationId / messageId / responseFingerprint / responseChars`,并只允许同一持久 envelope。prepared 成功时必须在独立的 128 条 lifecycle/finalization reserve 中同时预留后六条的记录数和最大字节容量,七条都不得占用 64 条 action receipt/reconciliation reserve;容量不足时 prepared 自身失败关闭。容量扫描和幂等检查都在 Agent DB 追加锁内读取完整受支持文件,不依赖 32 MiB recent tail,并对 `finalizationId / messageId / Agent / task / Session / run / source / Goal/plan 快照 / responseFingerprint / responseChars / conversationPath`、stage ordinal/previousStage 和 JSONL 物理顺序做精确匹配。缺前序、倒序、重复、身份冲突、额外生产字段或 reservation 无法兑现都失败关闭;严格七槽未全部齐全时不得删除 journal。prepared journal 已写入但首条 lifecycle 暂时失败时,run 保持可恢复 `finalizing`,不得被外层改判普通 failed;恢复只补同一七槽,不重放 Provider 或 assistant。
### 控制面与验收
- 纯聊天入口支持 `/goal <文本>``/goal status``/goal pause``/goal resume``/goal edit <文本>``/goal clear`;开发 Agent UI 使用 `执行 / 聊天 / 目标` 三段模式,Goal 创建/编辑通过独立弹层提交,并在状态行显示 outcome、revision、状态与完成标准,提供暂停/恢复/清理。普通用户 Supervisor 首页不暴露开发 Goal 管理控件。
- background planning / final reply 的专用 Provider 客户端强制 `max_retries=0`;每个 `agent.runtime.provider_request.lifecycle``started` 到唯一 `completed / failed / interrupted` 最多对应一次物理请求。`Timeout / Connectivity / Transport / EmptyResponse / 408 / 429 / 5xx` 以及无法证明请求未被上游接收的其它错误,不得在同一 lifecycle 内自动原样重放;只记录 error kind、SHA-256、字符数或脱敏摘要。显式 steer、Goal resume 或人工 reconciliation 决定再次调用时,必须使用新的 request slot/lifecycle;格式修复同样使用 `loop-<n>-repair-<m>` 新 slot,不能伪装成底层 retry。
- 每次 request snapshot 固定绑定 `projectId / agentId / taskId / sessionId / runId / source / goalId / goalRevision / goalSnapshotFingerprint / appliedSteerCursor / requestKind / requestSlot`,requestId 从该闭集稳定派生。真正进入 Provider future 前,Runtime 在同一项目写锁内重读 task/Runtime 身份、queued steer、cancel tombstone、规范 Goal 状态与快照;已生效控制只返回未启动,不得写伪 `started`。Provider lifecycle 的生产字段闭集只允许 `recordType / auditSchemaVersion / agentId / taskId / sessionId / runId / source / requestId / requestKind / requestSlot / status`,持久层只可再添加统一 `schemaVersion / updatedAt` envelope;不包含 prompt、工具输入、URL、模型、回复或错误正文。
- 启动新请求前必须在 Agent DB 锁内全量扫描同 Agent/run 的 Provider lifecycle,不依赖 recent tail。只要发现 `started` 后没有可信唯一终态,就把原 run/task/state 收束到 `needs-reconciliation` orphan barrier,阻断后续 Provider、工具和 finalization;同 request 多终态、缺 started、物理顺序倒置、重复阶段、身份/字段冲突或额外生产字段同样失败关闭,禁止自动补发。paused Runner 重启窗口必须以 started 数量零增长证明没有暗中请求,不能只看 plan/action 是否落盘。
- 确定性验收覆盖:单 Session 单 Goal、跨 Agent/Session 隔离、expectedRevision 幂等/冲突、活跃 Goal 阻止新 run、Provider in-flight pause、工具返回后 pause、paused 重启不自启、同 run resume、pending confirmation 恢复、编辑触发 steer 与旧动作失效、clear/cancel 竞态、v4/v3/v2 恢复、v3 finalization、assistant 后崩溃补齐 completed,以及 Goal 元数据零 project revision/policy 变化。
- 真实 Provider 使用一次性项目证明:Goal 自行建立并多次更新计划,运行中编辑一次,暂停并强杀 Runner,重启后保持 paused,显式恢复同 run,最终全部步骤和 verification 收束后只写一个 assistanttask/event/context/finalization/goal/conversation 交叉证明无新主 run、无动作重放、无 paused Provider 调用,并扫描密钥、Goal 正文和项目绝对路径的公共泄漏
- 截至 2026-07-15V1.18 真实 Provider 门禁尚未通过,不能把 Rust/Runner/UI 确定性回归或短鉴权请求外推为 V1.18 PASS;Provider 链路恢复后仍需完整执行上一条一次性项目验收
- 真实 Provider 使用现有 `agent-runtime-real-e2e.mjs` 的独立 `goal-runtime` suite 和一次性项目证明,不新增平行验收器。suite 只要求 AppData 中 `code-prototype` 的真实 LLM 配置,不要求 Chrome 或 External Editor API。revision 1 必须先形成至少三步、已有 completed 且仍有未完成步骤的计划,并停在一个绑定 Goal revision 1 的 `game-creator-pending-action.v5` 写动作;编辑到 revision 2 后,该旧动作必须形成 `runtime.goal / blocked` observation 且旧标记从未落盘,同一 run 再形成绑定 revision 2 的 v5 写动作
- `goal-runtime` 不复用正在运行的正式 AppData Runner。验收器在用户提供的 AppData 下创建 `0700` 专用子目录和带随机 owner token/PID/时间的 sentinel;主配置与可选 local 配置只以普通文件 hardlink 复用,结束时复核 source/link 的 device、inode 与 SHA-256,全程不复制或输出 API Key。所有 Goal/Runner CLI 统一附加该专用 `runtimeConfigDir`。SIGKILL 前必须同时核对 sentinel、endpoint、Runner boot/PID/port、实际 CLI 路径、`--agent-runner --config-dir` argv 和 OS 启动指纹;endpoint 在已认领后异常消失时,只允许按先前同一启动指纹回收。成功或失败都先停止自有 Runner,再按 sentinel 和受限目录前缀删除专用 AppData;身份不一致时保留现场并失败,禁止猜测或清理其它 Runner
- revision 2 验证 fixture 只在 revision 1 待确认动作与隔离证明完成后注入,并先由宿主真实执行一次失败命令形成不可伪造的失败边界。失败报告对 task、event、Agent DB 和 conversation 分面容错读取,截断尾行保留已完成 JSONL 记录并单独报告读取错误;不得把某一分面损坏折算成其它分面全为零。
- revision 2 写动作待确认时执行 pause;命令返回、Goal status 与 Runtime state 都必须是 durable `paused`。随后记录 Goal、`game-creator-runtime-context-bundle.v4`、pending v5、计划、task/event/Agent DB、conversation 和副作用计数,SIGKILL Runner 并使用全局 `--agent-resume` 启动新 boot;至少两个稳定采样窗口内上述运行证据不得推进,不得新增 Provider plan、工具执行、assistant 或主 run。只有显式 `--agent-goal-resume` 后才允许确认 revision 2 动作并继续。
- 最终 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、v4 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-15V1.18 真实 Provider 门禁尚未通过。最新保留现场在首轮 planning、`planRevision=0`、零 pending action/observation 时由对端关闭长 TLS 连接;同一发布配置、模型和 Rust native-tls 客户端的最小单 Agent 请求在 25.2 秒成功,证明基础鉴权与短请求通道可用,但不能外推为工具 planning 或 Goal 长链路 PASS。Provider 长请求恢复后仍需完整执行上一条一次性项目验收。
## 验收命令
@@ -806,6 +815,7 @@ V1.18 对标 Codex CLI `/goal` 的长任务语义:目标文本既是首轮任
- `cargo test -p shared-contracts --manifest-path server-rs/Cargo.toml game_creation_app`
- `npm run ai-game-creator-shell:agent-run:smoke`
- `npm run ai-game-creator-shell:agent-runtime:real-e2e -- --config-dir <AppData> --suite llm-runtime`
- `npm run ai-game-creator-shell:agent-runtime:real-e2e -- --config-dir <AppData> --suite goal-runtime`
- `npm run ai-game-creator-shell:agent-runtime:real-e2e -- --config-dir <AppData> --suite full`
- `npm run check:encoding`
- `git diff --check`