完善Agent Runtime工具计划持久交接
新增 tool-plan 成功响应私有账本并支持 base 与 repair 零网络恢复 补齐 Provider 生命周期闭合、终态清理和 Runner 空闲门禁 收紧 Agent DB 审计白名单并拒绝原始 Provider 与调用标识 加固 Unix exchange/quarantine 与 Windows 句柄枚举存储路径 扩展 E2E 自测、跨平台回归和 V1.43 工程文档
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -36,4 +36,4 @@ zip = { version = "2", default-features = false, features = ["deflate"] }
|
||||
libc = "0.2"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows-sys = { version = "0.61", features = ["Win32_Foundation", "Win32_System_JobObjects"] }
|
||||
windows-sys = { version = "0.61", features = ["Win32_Foundation", "Win32_Storage_FileSystem", "Win32_System_JobObjects"] }
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -71,6 +71,7 @@ mod provider_retry;
|
||||
mod repository_context;
|
||||
mod runner;
|
||||
mod swarm_cli;
|
||||
mod tool_plan_handoff;
|
||||
mod user_input;
|
||||
mod windows;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -542,6 +542,11 @@ fn redact_runner_secret(message: &str, token: &str) -> String {
|
||||
redacted.chars().take(2_000).collect()
|
||||
}
|
||||
|
||||
fn redact_external_agent_runner_runtime_error(root: &Path, message: &str, token: &str) -> String {
|
||||
let redacted = redact_runner_secret(message, token);
|
||||
crate::redact_agent_runtime_error(root, &redacted, 500)
|
||||
}
|
||||
|
||||
fn normalize_external_agent_runner_config_dir(config_dir: &Path) -> Result<PathBuf, String> {
|
||||
crate::prepare_game_creator_runtime_config_dir(config_dir)
|
||||
}
|
||||
@@ -2488,7 +2493,7 @@ fn dispatch_external_agent_runner_runtime_request(
|
||||
Err(error) => ExternalAgentRunnerResponse::failure(
|
||||
&request.request_id,
|
||||
"runtime-error",
|
||||
redact_runner_secret(&error, &token),
|
||||
redact_external_agent_runner_runtime_error(&root, &error, &token),
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -2747,6 +2752,7 @@ fn external_agent_runner_root_is_idle(root: &Path) -> Result<bool, String> {
|
||||
root.join(".agent/runtime/finalizations"),
|
||||
root.join(".agent/runtime/provider-handoffs"),
|
||||
root.join(".agent/runtime/provider-retries"),
|
||||
root.join(".agent/runtime/tool-plan-handoffs"),
|
||||
] {
|
||||
if external_agent_runner_directory_has_durable_files(&durable_dir)? {
|
||||
return Ok(false);
|
||||
@@ -3889,6 +3895,26 @@ mod tests {
|
||||
assert!(!serialized.contains("\"token\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_error_redaction_hides_project_and_absolute_paths_from_runner_clients() {
|
||||
let directory = unique_test_directory();
|
||||
let token = "runner-private-token-runner-private-token";
|
||||
let failing_path = directory
|
||||
.0
|
||||
.join(".agent/runtime/tool-plan-handoffs/broken-ledger.json");
|
||||
let error = format!(
|
||||
"读取 tool-plan 成功响应交接失败:{};token={token};backup=/home/private/ledger.previous",
|
||||
failing_path.display()
|
||||
);
|
||||
|
||||
let redacted = redact_external_agent_runner_runtime_error(&directory.0, &error, token);
|
||||
assert!(!redacted.contains(directory.0.to_string_lossy().as_ref()));
|
||||
assert!(!redacted.contains(token));
|
||||
assert!(!redacted.contains("/home/private"));
|
||||
assert!(redacted.contains("$PROJECT_ROOT"));
|
||||
assert!(redacted.contains("<absolute-path>"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn continuation_params_bind_agent_run_and_action_exactly() {
|
||||
let request = ExternalAgentRunnerRequest {
|
||||
@@ -4468,6 +4494,27 @@ mod tests {
|
||||
assert!(!state.draining.load(Ordering::Acquire));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn durable_tool_plan_handoff_prevents_shutdown_even_when_corrupt() {
|
||||
let directory = unique_test_directory();
|
||||
let root = directory.0.join("project");
|
||||
let handoff_path = root
|
||||
.join(".agent/runtime/tool-plan-handoffs")
|
||||
.join("agent-key")
|
||||
.join("run-key.json");
|
||||
fs::create_dir_all(handoff_path.parent().expect("tool-plan handoff parent"))
|
||||
.expect("create tool-plan handoff directory");
|
||||
fs::write(&handoff_path, b"{").expect("write corrupt tool-plan handoff");
|
||||
|
||||
assert!(!external_agent_runner_root_is_idle(&root).expect("scan primary handoff"));
|
||||
let previous_path = crate::agent::agent_runtime_json_sidecar_backup_path(&handoff_path);
|
||||
fs::rename(&handoff_path, &previous_path).expect("move tool-plan handoff to previous");
|
||||
assert!(!external_agent_runner_root_is_idle(&root).expect("scan previous handoff"));
|
||||
|
||||
fs::remove_file(previous_path).expect("remove tool-plan handoff previous");
|
||||
assert!(external_agent_runner_root_is_idle(&root).expect("scan idle root"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn durable_provider_retry_prevents_shutdown_and_reopens_writes() {
|
||||
let directory = unique_test_directory();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -4933,3 +4933,12 @@
|
||||
- 确定性证据:fault proxy `14/14`、E2E self-test **PASS**、前端 `308/308`,以及 shell typecheck、`platform-llm 41/41`、`platform-agent 17/17`、`shared-contracts 7/7` 均已完成。
|
||||
- 六轮记录:真实外部 Provider suite 共执行六轮,前五轮均为 **FAIL** 且不得拼接。第一、二轮沿用既有失败记录;第三轮已走通故障、重试和唯一回复,但过早观察到 `1` 个 finalization journal;第四轮在 quality-review 普通 tool-plan 连续 transport/connectivity 失败并耗尽重试,未进入目标故障;第五轮命中上述项目写锁竞争与绝对锁路径泄漏问题。第六轮在同一轮内完整 **PASS**。
|
||||
- 第六轮证据:正式路由为 `gpt-5.5 / openai_chat`,`2` 条初始加 `1` 条 repair delivery、`3` 条专业 Agent assistant,目标为 Project Supervisor base final-reply,可信宿主 verify marker 门禁通过;受控 Provider `failed=1 / retry=1`,incidental `failure=0 / retry=0`,`30s` backoff,pidfd `claim=2 / signal=2`,Runner `resumed=true / identityStable=true`。父 tool-plan 故障前后均为 `13`,parent final-reply 与最终 assistant 唯一,response stream `sequence=2 / committed`;pending、retry、handoff、finalization、confirmation sidecar、全部重复计数及 API Key、私有正文、项目路径、正式配置路径和公共报告泄漏扫描命中均为 `0`。本决策不关闭 V1.41 handoff 原子落盘并回读前的 unknown-result 窗口,也不覆盖 tool-plan 成功响应/function arguments 的 durable handoff。
|
||||
|
||||
## 2026-07-20 AI 游戏创作 Agent Runtime V1.43 tool-plan 成功响应持久交接与 repair 链恢复
|
||||
|
||||
- 决策:保留 V1.41 `game-creator-provider-handoff.v1` 的无工具文本不变量,新增 `.agent/runtime/tool-plan-handoffs/<agentKey>/<runKey>.json` 与严格 `game-creator-tool-plan-handoff.v1`。同一 Agent/run 账本按 `(loopIteration, repairAttempt)` 单调记录 `repair-0..N`,每条绑定完整 retry identity、实际物理 requestId、slot/attempt、Provider/model、去 thinking 响应、完整 function call envelope/arguments、usage、指纹和时间。
|
||||
- 提交顺序:Provider 成功后必须先追加并回读 tool-plan handoff,之后才可为同一实际 requestId 写 lifecycle `completed`,再进入 parser、repair 或动作预检。`repair-0` 与所有 `repair-N` 统一使用持久 transient retry;恢复从当前 loop base 开始按序回放已有 entry,已成功请求零网络,前序回放不得删除后继 repair retry。
|
||||
- 隐私与失败关闭:function arguments 只存在于私有 handoff 和后续 pending/action batch,公共 task/event/Agent DB/CLI/report 只写安全身份、哈希与计数。tool-plan protocol/repair 公共审计共同保存 Agent/task/Session/run/source、loop/repair/slot、响应指纹、Provider request ID SHA-256 和 protocol;protocol 只额外保存 function call 数量、call ID SHA-256 数组、catalog-bound function names、response ID SHA-256/字符数和归一化元数据,repair 只额外保存 attempt/maxAttempts、协议错误/响应 preview 哈希与字符数、call ID/function name SHA-256,不保存原始 callId/callIds/responseId/providerRequestId。审计写入在 Agent DB append 锁内按完整身份做全历史 compare-and-append,不使用 32 MiB 尾部近似去重。参数为保持语义不得静默脱敏;命中密钥、配置痕迹、结构化可执行路径中的项目/其它绝对路径、超限、乱序、slot/identity/requestId/response 冲突时进入 reconciliation。源码正文与计划叙述只做密钥检查,不能把 HTML 闭合标签或叙述路径误判为执行参数。格式错误但安全有界的 opaque arguments 只用于重建 repair,严格 parser/schema/catalog 通过前不能执行;未闭合或孤立 thinking wrapper 只持久化无正文的无效元数据,重放时仍必须进入 repair。
|
||||
- 所有权与清理:账本保留同一 run 的已成功 planning entry,直到 run 完成、取消、失败、作废或明确 reconciliation 清理;这样单动作、多动作、confirmation、协作 batch 和直接回复都不会在下一 durable owner 建立前丢失。steer/cancel/终态/漂移清理前必须按整本账本补齐所有实际 requestId lifecycle,任一条失败时保留账本并进入 reconciliation。Runner 恢复会严格扫描 hash 路径、primary/`.previous` 和安全原子临时文件,清理合法终态遗留;Unix 全程使用固定目录句柄和根目录/Agent 目录 `flock`,安装用 `RENAME_EXCHANGE` 复核回滚,删除用 `RENAME_NOREPLACE` quarantine、inode 复核和原 fd 清空同步;Windows 使用相对父句柄及 `GetFileInformationByHandleEx` 句柄枚举,拒绝 reparse point/junction/硬链接并以禁止共享的独占句柄表示活跃 temp。两端都不依赖 PID 存活判断。未知、链接、目录身份替换或内容冲突项失败关闭。primary、`.previous` 或损坏账本阻止 `runner.shutdown_if_idle`。非协作同 UID 进程可主动忽略 Unix advisory lock,属于宿主 OS 信任边界,不纳入完整沙箱承诺。
|
||||
- 验收边界:确定性测试必须分别覆盖 base handoff 与 repair handoff 在 lifecycle completed 前停止,关闭 mock Provider 后恢复零网络、原 requestId 唯一闭合、repair/protocol audit 幂等、唯一 assistant/completed/committed stream和终局零 sidecar。规划中的真实 `supervisor-swarm-tool-plan-handoff-runner-kill` 应作为独立非默认 suite,使用 sentinel-owned AppData、随机 capability、精确 Agent/run/slot 和 pidfd 强杀;但该 suite 当前尚未实现、尚未注册,因此未执行且不得记 PASS,更不能记为真实外部验收。Provider 成功到 handoff 原子回读前的 unknown-result 及手动 context-compaction 仍不在本决策承诺内。
|
||||
- 当前证据:`tool_plan_` 61/61、`tool_plan_handoff_` 36/36、`provider_handoff_` 11/11、`provider_retry_` 21/21、`response_stream_` 31/31、`finalization_` 48/48、`finalization_resume_` 12/12;Tauri/Rust 串行全量 1043 tests 为 `1039 passed / 4 ignored / 0 failed`,Linux `cargo check` 与 `x86_64-pc-windows-gnu cargo check --tests` 均通过。默认并发全量曾分别在两个共享执行器异步投影断言上波动,两个失败用例精确复跑均通过,因此现行稳定门禁使用 `--test-threads=1`,默认并发结果只作竞态诊断。客户端 `308/308`(其中 `appSurface 280/280`)、E2E self-test、typecheck、变更脚本 ESLint、encoding、`platform-llm 41/41`、`platform-agent game_creation 17/17`、`shared-contracts game_creation_app 7/7` 与 agent-run smoke 全部通过。实现过程中发现并修复 thinking 归一化、源码路径误判、repair 漂移删账本、durable control 清理遗漏后继 repair lifecycle、复数敏感 key/Provider ID 泄漏、malformed JSON trivia 路径绕过、Agent DB 审计字段扩张、PID 复用 temp 误判、中间目录/文件名称换绑 TOCTOU、Windows 路径枚举 ABA 和审计尾部近似去重问题。`supervisor-swarm-tool-plan-handoff-runner-kill` 当前尚未实现、尚未注册,所以本轮没有执行,仍不得记 PASS。
|
||||
|
||||
@@ -522,3 +522,12 @@ npm run check:server-rs-ddd
|
||||
- Linux `command.exec / command.start / project.verify` 必须经过受信任系统 bubblewrap;缺失或 namespace / mount preflight 失败时工具失败关闭,不能回退宿主执行。
|
||||
- 修改命令执行、PTY、项目验证或发布配置后,至少运行 `GENARRATIVE_COMMAND_SANDBOX_REAL_TEST=1 cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml command_sandbox -- --nocapture --test-threads=1`、`cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml command_exec` 和 `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml process_session -- --test-threads=1`。
|
||||
- 真实门禁必须同时证明项目内 Cargo / npm / Git 成功,项目外普通文件读写失败,`.git / .agent / .agents / .codex / .hermes` 写入失败,网络默认不可达,shell / PTY 后代在 `setsid + chdir` 后仍继承相同边界;process record、poll / stdin / terminate 和 project.verify 审计必须断言 `bubblewrap / workspace-write / disabled / workspace-v1`。另测 `RUSTUP_HOME=$HOME` 与 `.rustup -> $HOME` 必须在目标执行前失败。Linux deb / rpm 包必须声明 `bubblewrap` 依赖;AppImage 发布说明必须要求宿主预装受支持的 bwrap,缺失时只能返回 sandbox unavailable。
|
||||
|
||||
## AI 游戏创作 App Provider 成功交接验证
|
||||
|
||||
- 文本回复继续使用 `game-creator-provider-handoff.v1`;tool-plan/function arguments 使用独立 `game-creator-tool-plan-handoff.v1`,禁止为省事放宽文本 handoff。两类 handoff 都必须在 Provider lifecycle `completed` 前原子落盘并回读。
|
||||
- tool-plan `repair-0..N` 必须共用持久 retry 与 handoff-first 恢复;测试停止点按 request kind/精确 slot 命中,不能让较早的 tool-plan 抢占 final-reply 断点。恢复测试必须关闭 mock Provider,证明零网络、原 requestId 唯一闭合、audit 幂等和终局零 sidecar。
|
||||
- function arguments 只能进入私有 `0600` handoff 和后续 pending/action batch。参数命中密钥、配置痕迹或结构化可执行路径中的绝对路径时失败关闭,不得先脱敏再执行;源码正文与计划叙述不能用日志路径 token 扫描,以免把 HTML `</tag>` 当路径。未闭合/错配 thinking wrapper 要保留无正文的无效事实并走 repair,不能清洗成可执行计划。公共事件、Agent DB、CLI 和报告只保留哈希、计数与安全身份字段;tool-plan protocol 不保存原始 callId/callIds/responseId/providerRequestId,只保存 call ID SHA-256 数组、catalog-bound function names、response ID SHA-256/字符数和 Provider request ID SHA-256,repair 只保存 call ID/function name SHA-256 及协议错误/preview 哈希。protocol/repair 审计必须在 Agent DB append 锁内按完整身份全历史 compare-and-append。
|
||||
- steer/cancel/终态/身份漂移删除 tool-plan handoff 前,必须先按账本顺序幂等闭合全部实际 requestId lifecycle;不能只闭合当前 base entry 后删除后继 repair。Runner 恢复必须扫描 hash 路径归属、primary/`.previous` 和安全临时文件,回收合法终态残留;Unix 读写、扫描和删除固定在逐层打开的目录句柄,handoff 根目录和 Agent 目录用跨进程 `flock` 序列化,临时文件再用非阻塞 `flock` 判断写入方是否仍持有。已有 primary 的安装通过 `RENAME_EXCHANGE` 双端复核并在冲突时回滚;删除先以 `RENAME_NOREPLACE` 隔离到可恢复 temp 名、复核 inode,再按原 fd 清空并同步私有内容。Windows 逐层使用相对父句柄打开,并用 `GetFileInformationByHandleEx` 直接枚举已验证目录句柄,拒绝 reparse point/junction 与硬链接,临时文件以禁止共享的独占句柄表示活跃写入。不得再用文件名中的 PID 或进程存活推断临时文件所有权。未知、链接、身份替换或内容冲突项保持 busy 并失败关闭;主动忽略 advisory lock 的同 UID 进程仍属于宿主 OS 信任边界,不能宣称为完整沙箱隔离。
|
||||
- 修改 Provider handoff/retry/Runner idle 判断后,至少运行 `tool_plan_`、`tool_plan_handoff_`、`provider_handoff_`、`provider_retry_`、相关强杀恢复用例、Tauri 串行全量 `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml -- --test-threads=1`、编码检查和 `git diff --check`;涉及跨平台扫描、PID 或临时文件回收时追加 `cargo check --tests --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --target x86_64-pc-windows-gnu`。当前默认并发全量会受 Tauri 共享执行器饱和影响,曾在不同异步投影断言上偶发失败;它只作竞态诊断,失败时必须精确复跑,不能替代串行门禁,也不能把精确复跑结果伪装成默认并发 PASS。真实 Provider suite 单轮 PASS 前,确定性 mock 结果不得写成外部验收完成。
|
||||
- `supervisor-swarm-tool-plan-handoff-runner-kill` 目前只是规划中的真实 Provider 门禁,尚未实现且未注册;因此当前不能执行,也不得把“本轮没跑”或其它确定性结果记成该 suite PASS。
|
||||
|
||||
@@ -1471,12 +1471,33 @@ npm run ai-game-creator-shell:agent-runtime:supervisor-swarm-final-reply-transie
|
||||
- 第六轮在同一轮内完整 **PASS**:正式路由为 `gpt-5.5 / openai_chat`;形成 `2` 条初始 delivery、`1` 条 repair delivery 和 `3` 条专业 Agent assistant;受控目标精确命中 Project Supervisor base final-reply,可信宿主 verify marker 门禁通过。受控 Provider `failed=1 / retry=1`,incidental `failure=0 / retry=0`;`30s` backoff 成立,pidfd `claim=2 / signal=2`,Runner `resumed=true / identityStable=true`;故障前后父 tool-plan 均为 `13`,只产生唯一 parent final-reply 和唯一最终 assistant,response stream 以 `sequence=2` 提交为 `committed`。pending、retry、handoff、finalization、confirmation sidecar 全部为 `0`,全部重复计数为 `0`,API Key、私有正文、项目路径、正式配置路径及公共报告泄漏扫描命中均为 `0`。
|
||||
- 第六轮 PASS 只关闭“final-reply 瞬态失败进入持久退避后强杀 Runner”的真实证据缺口。V1.41 中“Provider 已成功返回、但 handoff 尚未原子落盘并回读”仍是 unknown-result 边界;tool-plan 成功响应及 function arguments 的 durable handoff 仍未覆盖。
|
||||
|
||||
## V1.43 tool-plan 成功响应持久交接与 repair 链恢复
|
||||
|
||||
V1.43 不放宽 V1.41 的文本型 `game-creator-provider-handoff.v1`,而是为 `requestKind=tool-plan` 增加独立私有账本 `.agent/runtime/tool-plan-handoffs/<agentKey>/<runKey>.json`,schema 固定为 `game-creator-tool-plan-handoff.v1`。同一 Agent/run 账本按 `(loopIteration, repairAttempt)` 单调保存已成功的 `repair-0..N` Provider 响应,每条绑定完整 retry identity、实际物理 `providerRequestId`、真实 request slot/attempt、Provider/model、去除 thinking 后的响应、thinking 归一化哈希/计数、完整 function call envelope、usage、响应指纹和创建时间。账本使用既有 `0600`、原子替换、父目录同步、`.previous` 恢复和写后完整回读;未知字段、乱序/缺口、重复 slot 冲突、超限、危险可执行路径、密钥或配置痕迹一律失败关闭。
|
||||
|
||||
### 提交、重放与所有权
|
||||
|
||||
- 每个 tool-plan 物理请求的顺序固定为:Provider 成功 -> tool-plan handoff 追加并回读 -> 同一实际 requestId lifecycle `completed` -> 解析/格式修复或动作预检。function arguments 只存在于私有 handoff 与后续 pending/action batch。protocol/repair 公共审计共同保存 `agentId/taskId/sessionId/runId/source/loopIteration/repairAttempt/requestSlot/responseFingerprint/providerRequestIdSha256/protocol`;protocol 只额外保存 `functionCallCount/callIdSha256s/functionNames/responseIdSha256/responseIdChars` 和既有 normalization 字段,其中 function names 必须由 catalog 绑定;repair 只额外保存 attempt/maxAttempts、协议错误/preview 哈希与字符数及 `callIdSha256/functionNameSha256`。公共 task、event、Agent DB、CLI 和报告不得保存原始 callId/callIds/responseId/providerRequestId。两类审计都在 Agent DB append 锁内按完整 Agent/task/Session/run/source/slot 身份做全历史 compare-and-append,不能以受限尾部读取替代幂等。
|
||||
- `repair-0` 与全部 `repair-N` 统一使用持久 transient retry。Runner 恢复总是从当前 loop 的 `repair-0` 重建请求:账本中已成功的 entry 按序零网络回放并幂等补齐原 requestId lifecycle;若某条响应需要格式修复,则用同一有界 preview/protocol error 重建下一 repair 请求。后继 repair 的 retry sidecar 可以与前序 handoff entry 同时存在,前序回放不得误删后继 retry。
|
||||
- 账本保留同一 run 已成功的 tool-plan entry,直到当前 run 完成、取消、失败、作废或明确 reconciliation 清理。这样单动作、并行动作、confirmation、协作 batch 和直接回复都不会在下一 durable owner 建立前丢失 function arguments;后续 loop 可在同一账本中追加,条目数和总字节数都有硬上限。steer/cancel/终态/漂移删除前必须按整本账本补齐所有实际 requestId lifecycle,任一条失败则保留账本并进入 reconciliation。
|
||||
- 同 slot 同 identity/response 重写是幂等成功;同 slot 指纹、requestId、attempt 或响应冲突进入 reconciliation。Goal/steer/request/config 漂移命中旧 slot 时,先按账本顺序把旧 base 与全部后继 repair 的实际 requestId 幂等闭合,再删除旧 tool-plan handoff/retry 并在同一 run 重新规划。Runner 启动恢复会严格发现 hash 归属正确的 primary/`.previous`;Unix 扫描和删除固定在已打开目录句柄,只回收原子命名正确、`0600`、单链接且 owner PID 已死亡的临时文件,活跃 owner 临时文件保持 busy;Windows 拒绝 reparse/非普通项并只回收 owner PID 已死亡的原子临时文件。已终态 run 的合法遗留可回收,未知、链接、冲突或无所属任务项失败关闭并保持 Runner busy。跨 durable run、越序 entry、未来 entry 或不匹配 retry 不允许猜测恢复。
|
||||
- 有效和无效 Provider 响应都必须先做私有内容安全检查。完整 function arguments 为恢复语义不得被静默脱敏或改写;命中 API Key、Token、password/client secret/private key/credential 等敏感 key、配置痕迹,或结构化可执行路径/命令参数中的项目及其它绝对路径时,handoff 写入失败并进入 reconciliation。`content / patch / plan / step / response` 等正文与叙述字段仍做密钥检查,但不能用面向日志的路径 token 扫描把 HTML `</tag>`、源码字符串或计划标题误判为可执行路径。格式不合法但安全且有界的 arguments 可作为不执行的 opaque repair 输入持久化,只有既有严格 parser/schema/catalog 门禁通过后才可转成工具动作。未闭合或孤立 thinking wrapper 不保存 thinking 正文,只保存严格无效元数据;重放合成无正文协议标记并进入同一 repair,不能被清洗成可执行计划。
|
||||
|
||||
### 确定性与真实门禁
|
||||
|
||||
- 单元测试覆盖严格 schema、原生 tool call 精确 round-trip、thinking 去除、base+repair 单调追加、幂等重写、乱序/冲突/超限、敏感内容拒绝、`.previous` 恢复、活跃/死亡 temp 判定、目录替换和双副本安全清理;Agent DB 审计另以真实双进程竞争固定 compare-and-append。primary、`.previous` 或损坏账本都必须使 Runner 保持 busy。
|
||||
- Runtime 集成测试至少在 `repair-0` handoff 落盘且 lifecycle 仅 `started`、以及 malformed base 已完成而 `repair-1` handoff 落盘且 lifecycle 仅 `started` 两个断点停止 Runner。关闭 mock Provider 后恢复必须零网络,原 physical request lifecycle 唯一闭合,protocol/repair audit 不重复,最终 assistant/completed/stream 唯一,终局 retry/tool-plan handoff/finalization 均为零。
|
||||
- 规划中的真实 Provider 门禁名为非默认 `supervisor-swarm-tool-plan-handoff-runner-kill`:实现后只允许 sentinel-owned 隔离 AppData、随机 capability 和目标 Agent/run/request slot 启用断点,并复用 pidfd Runner 强杀、metadata-only proxy 与零泄漏扫描;它必须证明 checkpoint 到 durable batch 之间 `networkReplayCount=0`、handoff 与 batch plan fingerprint 一致、动作/pending/delivery/claim 无提前副作用和终局零残留。该 suite 当前尚未实现、尚未注册,因此未执行且不得记 PASS;确定性 mock PASS 不得替代它。
|
||||
- V1.43 仍不关闭“外部 Provider 已成功返回、但本地 handoff 尚未完成原子写入并回读”的 unknown-result 窗口;没有 Provider 级幂等键或结果查询能力时,该窗口继续进入人工 reconciliation,不能宣称端到端物理调用 exactly-once。手动 context-compaction 也不在本切片。
|
||||
- 2026-07-20 当前确定性证据:`tool_plan_` 61/61、`tool_plan_handoff_` 36/36、`provider_handoff_` 11/11、`provider_retry_` 21/21、`response_stream_` 31/31、`finalization_` 48/48、`finalization_resume_` 12/12;Tauri/Rust 串行全量 1043 tests 为 `1039 passed / 4 ignored / 0 failed`,Linux `cargo check` 与 `x86_64-pc-windows-gnu cargo check --tests` 均通过。默认并发全量曾分别在两个 Tauri 共享执行器异步投影断言上波动,两个失败用例精确复跑均通过,因此稳定门禁使用 `--test-threads=1`,默认并发只作竞态诊断。客户端测试总计 `308/308`,其中 `appSurface` 为 `280/280`;E2E self-test、typecheck、变更脚本 ESLint、encoding、`platform-llm 41/41`、`platform-agent game_creation 17/17` 和本地 agent-run smoke 均通过。实现过程中发现并固定 thinking 归一化与无效 wrapper、源码路径误判、repair 漂移删账本、durable control 清理遗漏后继 repair lifecycle、复数敏感 key/Provider ID 泄漏、malformed JSON trivia 路径绕过、Agent DB 审计字段扩张、PID 复用 temp 误判、中间目录/文件名称换绑 TOCTOU、Windows 路径枚举 ABA、终态/temp 遗留及 Agent DB 尾部近似去重问题。Unix handoff 存储使用固定目录句柄、根/Agent 双层 `flock`、`RENAME_EXCHANGE` 安装回滚和 `RENAME_NOREPLACE` quarantine;Windows 使用相对父句柄、`GetFileInformationByHandleEx` 句柄枚举与独占 temp 句柄,并拒绝 junction/reparse point 与硬链接。非协作同 UID 进程仍属于宿主 OS 信任边界,不能据此宣称完整沙箱。真实 `supervisor-swarm-tool-plan-handoff-runner-kill` 尚未实现、尚未注册,所以本轮没有执行,仍不得记 PASS。
|
||||
|
||||
## 验收命令
|
||||
|
||||
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml structured_plan_ -- --nocapture`
|
||||
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml agent_goal_ -- --nocapture`
|
||||
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml goal_context_bundle_v4_migrates_v3_and_v2_then_rejects_plan_mismatch -- --nocapture`
|
||||
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml provider_handoff_ -- --nocapture --test-threads=1`
|
||||
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml tool_plan_handoff_ -- --nocapture --test-threads=1`
|
||||
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml response_stream_ -- --nocapture`
|
||||
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml finalization_v4_binds_response_request_slot_into_identity -- --nocapture`
|
||||
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml finalization_v3_without_response_request_slot_remains_readable -- --nocapture`
|
||||
@@ -1499,6 +1520,7 @@ npm run ai-game-creator-shell:agent-runtime:supervisor-swarm-final-reply-transie
|
||||
- `npm run ai-game-creator-shell:typecheck`
|
||||
- `npm run test -- apps/ai-game-creator-shell/tests`
|
||||
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml`
|
||||
- `cargo check --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --target x86_64-pc-windows-gnu`
|
||||
- `cargo test -p platform-agent --manifest-path server-rs/Cargo.toml game_creation`
|
||||
- `cargo test -p shared-contracts --manifest-path server-rs/Cargo.toml game_creation_app`
|
||||
- `npm run ai-game-creator-shell:agent-run:smoke`
|
||||
|
||||
@@ -618,3 +618,7 @@ game-project/
|
||||
- 第五轮暴露并修复并行 Agent 的 `file.write` 与项目写锁竞争:失败 observation 携带绝对锁路径,导致 pending 持久化拒绝并进入 `needs-reconciliation`。现 `file.write / file.patch / file.delete` 统一使用 Runtime 短等待项目写锁,`file.write` 错误在持久化前脱敏,并新增 `2` 条 Rust 回归测试。
|
||||
- 第六轮在同一轮内完整 **PASS**:`gpt-5.5 / openai_chat` 路由形成 `2` 条初始加 `1` 条 repair delivery、`3` 条专业 Agent assistant;故障精确命中 Project Supervisor base final-reply,可信宿主 verify marker 门禁通过。受控 Provider `failed=1 / retry=1`、incidental `failure=0 / retry=0`,`30s` backoff,pidfd `claim=2 / signal=2`,Runner `resumed=true / identityStable=true`;父 tool-plan 在故障前后均为 `13`,parent final-reply 与最终 assistant 唯一,response stream `sequence=2 / committed`。pending、retry、handoff、finalization、confirmation sidecar、全部重复计数,以及 API Key、私有正文、项目路径、正式配置路径和公共报告泄漏扫描命中均为 `0`。
|
||||
- 第六轮 PASS 不改变 V1.41 handoff 原子落盘并回读前的 unknown-result 边界,tool-plan 成功响应/function arguments 的 durable handoff 仍未覆盖。
|
||||
- 2026-07-20 起,同一 Runtime 文档的“V1.43 tool-plan 成功响应持久交接与 repair 链恢复”作为规划成功响应的现行恢复契约。V1.41 文本 handoff 保持不变;新增独立 `game-creator-tool-plan-handoff.v1` 私有账本,按同一 Agent/run 的 loop/repair 顺序保存实际 Provider requestId、retry identity、去 thinking 的响应、完整 function call envelope/arguments、usage 与响应指纹。`repair-0` 和全部 `repair-N` 统一进入持久 retry/handoff-first 路径,Runner 可从 base 开始零网络重放既有 repair 链。
|
||||
- tool-plan arguments 只允许出现在 `0600` 原子 sidecar 及后续 pending/action batch,不得进入 task/event/Agent DB/CLI/report。公共 protocol/repair 审计共同保存 Agent/task/Session/run/source、loop/repair/slot、响应指纹、Provider request ID SHA-256 和 protocol;protocol 只保存 function call 数量、call ID SHA-256 数组、catalog-bound function names、response ID SHA-256/字符数及 normalization 元数据,repair 只保存 attempt/maxAttempts、协议错误/preview 哈希和 call ID/function name SHA-256,不保存原始 callId/callIds/responseId/providerRequestId,并在 Agent DB append 锁内按完整身份全历史幂等追加。为了保持执行语义,参数禁止静默脱敏;命中密钥、配置痕迹、敏感 JSON key、Provider ID 中的秘密/绝对路径、结构化可执行路径中的项目或其它绝对路径、大小/顺序/身份冲突时直接 reconciliation。源码正文和计划叙述只做密钥检查,不能把 HTML 闭合标签当绝对路径;未闭合 thinking 只留无正文无效元数据并继续 repair。账本保留到 run 终态或明确作废;steer/cancel/漂移/终态清理前先闭合整本账本的实际 requestId,Runner 恢复严格扫描 hash/primary/`.previous`/安全临时文件并回收合法终态残留,确保单动作、多动作、confirmation、协作 batch 与直接回复在下一 durable owner 建立前都有恢复来源;未知、冲突、primary、`.previous` 或损坏账本都阻止 Runner idle shutdown。
|
||||
- V1.43 的确定性门禁必须覆盖 base handoff 与 repair handoff 两个 lifecycle-completed 前断点,关闭 mock Provider 后恢复零网络、原 requestId 唯一闭合、repair/protocol audit 幂等、唯一 assistant/completed/committed stream及终局零 sidecar。规划中的真实 Provider 门禁名为 `supervisor-swarm-tool-plan-handoff-runner-kill`,但该独立非默认 suite 当前尚未实现、尚未注册,因此未执行且不得记 PASS;不能把 mock 结果记为它的外部验收。Provider 成功到 handoff 原子回读前的 unknown-result 和手动 context-compaction 仍不在本切片承诺内。
|
||||
- V1.43 当前确定性实现已通过 `tool_plan_` 61/61、`tool_plan_handoff_` 36/36、`provider_handoff_` 11/11、`provider_retry_` 21/21、`response_stream_` 31/31、`finalization_` 48/48、`finalization_resume_` 12/12,以及 Tauri/Rust 串行全量 `1039 passed / 4 ignored`;Linux `cargo check` 与 `x86_64-pc-windows-gnu cargo check --tests` 均通过。默认并发全量仍有共享执行器异步投影时序波动,精确失败用例均通过,因此不记默认并发 PASS。客户端 `308/308`(其中 `appSurface 280/280`)、E2E self-test、typecheck、变更脚本 ESLint、encoding、`platform-llm 41/41`、`platform-agent game_creation 17/17`、`shared-contracts game_creation_app 7/7` 与 agent-run smoke 已通过;Supervisor 真实 E2E 报告已把 `toolPlanHandoffSidecarCount` 纳入终局残留。handoff 跨平台存储使用 Unix 固定目录句柄、目录 `flock`、exchange/quarantine 与 Windows 相对父句柄、句柄枚举、独占 temp,不再根据 PID 推断写入方是否存活;主动忽略锁的同 UID 进程仍属于宿主 OS 信任边界。`supervisor-swarm-tool-plan-handoff-runner-kill` 尚未实现、尚未注册,所以本轮没有执行,不能记为外部 PASS。
|
||||
|
||||
Reference in New Issue
Block a user