diff --git a/docs/technical/【技术方案】AI游戏创作Agent Runtime交互边界重构实施计划-2026-08-12.md b/docs/technical/【技术方案】AI游戏创作Agent Runtime交互边界重构实施计划-2026-08-12.md index a3628b63c..0feeeaef9 100644 --- a/docs/technical/【技术方案】AI游戏创作Agent Runtime交互边界重构实施计划-2026-08-12.md +++ b/docs/technical/【技术方案】AI游戏创作Agent Runtime交互边界重构实施计划-2026-08-12.md @@ -1,7 +1,7 @@ # AI 游戏创作 Agent Runtime 交互边界重构实施计划 -更新时间:`2026-08-13` -状态:评审中(协议冻结前禁止进入工程编码) +更新时间:`2026-08-15` +状态:评审中(已完成 #168、持续边界复核及冻结前补充审计;第 10 节证据门禁闭合前禁止进入生产实现编码,允许 P0 只读 fixture/test harness 用于验证本方案) ## 0. 目标与范围 @@ -10,7 +10,7 @@ - **Consumer 对 Runtime 生命周期只做两件事**:`render(snapshot)` 与 `dispatch(command)`,不保留跨轮业务真相或生命周期决策;conversation/response stream 仍是独立展示通道,但不得反向推导 Runtime 状态。 - **交互 Loop 收归后端 Supervisor Shell**:Consumer 不再根据 Runtime 状态自行选择 start / steer / confirm / retry / resume。 - **GUI、CLI、测试夹具是同一套协议的平等 Consumer**,GUI 没有任何特权通道。 -- **Runner 自驱**:工作发现、恢复、继续执行不依赖 Consumer 在线或主动触发。 +- **Runner 在 owner/lifecycle 门禁有效期间自驱**:工作发现、确定性唤醒和安全恢复不依赖 Consumer 轮询或主动触发;本轮仍保留 GUI-owner/Runner 生命周期门禁,不宣称无 GUI 常驻或无限制 headless 自驱。 本重构**不重新设计 Runtime 内部执行模型**(Part D 保持黑盒),只补充 Shell 需要的边界能力。 @@ -19,6 +19,7 @@ - Agent 执行状态机(main_loop / task_queue / recovery)内部重构。 - Runner 进程生命周期策略(开机自启 / 无 GUI 常驻)——自驱只限于"Runner 存活期间",保留 GUI 启动 + GUI-owner watchdog。 - LLM / Provider / 提示词体系改动。 +- 现有项目资源上传/登记、`preview.start`/`preview.validate`、本地预览 Registry 和 Session 管理面不在本协议内重定义;它们继续使用各自现行的项目权限、immutable revision、preview authorization 和 exactly-once 合同,不得因删除 Consumer 的 Runtime 生命周期分支而被误删或由前端从 Snapshot 自行推导。 --- @@ -28,13 +29,42 @@ 权威执行拓扑固定为:Tauri/CLI 只是 transport adapter;启用 External Runner 时,五个写命令和 Public projector 的权威 Shell handler 必须在已经取得 project execution owner 的 Runner 内执行并写项目 ledger,GUI 不得先行写一份平行 ledger。Public read/订阅通过 Runner 返回已修复投影;Runner 不可达时 transport 返回 `TRANSIENT_UNAVAILABLE`,但不能把可能陈旧的 Snapshot 伪装成成功响应。非 owner 进程不得修复 dirty journal。未启用 Runner 的进程内模式和测试使用同一 handler,并先取得等价 project owner。Developer read 在 Tauri/受信任开发 CLI 内只读内部状态并经过宿主来源 capability,不承担 Public 投影修复。 -V1 初始 `schemaVersion`(Rust 字段 `schema_version`)固定为 `game-creator-agent-interaction.v1`。Snapshot、事件 envelope、命令和公开错误必须携带该值,或由同一 transport 在调用前明确协商到该值;不支持的 major 返回 `PROTOCOL_VERSION_UNSUPPORTED`,写命令失败关闭。Rust 字段按 camelCase 序列化;公开枚举使用本文冻结的 lowerCamelCase wire value;所有公开 ID 均为不透明字符串,Consumer 不得从 ID 推导路径、run 或时序。V1 写命令使用严格字段合同:缺少必填字段、未知字段、重复字段、错误类型或超出长度/数量上限均返回 `INVALID_REQUEST`,不执行任何副作用;不通过“忽略未知字段”实现协议兼容,后续字段只能通过新 schemaVersion 引入。 +V1 初始 `schemaVersion`(Rust 字段 `schema_version`)固定为 `game-creator-agent-interaction.v1`。Snapshot、事件 envelope、命令和公开错误必须携带该值,或由同一 transport 在调用前明确协商到该值;不支持的 major 返回 `PROTOCOL_VERSION_UNSUPPORTED`,写命令失败关闭。Rust 字段按 camelCase 序列化;公开业务枚举使用本文冻结的 lowerCamelCase wire value,结构化 `error.code` 使用本文冻结的 SCREAMING_SNAKE code;所有公开 ID 均为不透明字符串,Consumer 不得从 ID 推导路径、run 或时序。所有带数据的公开 tagged union 统一使用扁平的内部 tag 形式:对象必须包含 `kind` 字段,variant 使用 lowerCamelCase wire value,其余 variant 字段与 `kind` 同级;不使用 serde 默认的外部 variant 包装,也不允许同一 union 在不同 transport 使用不同 tag。无数据的枚举仍序列化为 lowerCamelCase 字符串。V1 写命令使用严格字段合同:缺少必填字段、未知字段、重复字段、错误类型或超出长度/数量上限均返回 `INVALID_REQUEST`,不执行任何副作用;不通过“忽略未知字段”实现协议兼容,后续字段只能通过新 schemaVersion 引入。请求处理顺序固定为:先完成 schema/version/结构/类型/大小校验,再完成 locator、项目身份与来源权限校验,最后执行 session/rotation phase gate 和 ledger 查询;因此 rotation 期间 malformed request 仍返回 `INVALID_REQUEST`,只有结构合法且未命中既有 requestId 的新请求才返回 `TARGET_BUSY`。 + +带数据 variant 的 canonical JSON 形态固定为 `{ "kind": "", ...variantFields }`。例如 `SubmitIntentPayload::Conversation` 使用 `{"kind":"conversation","sessionId":"...","expectedSessionRevision":1,"message":"...","attachments":[],"intentKind":"createFromPrompt","entryBinding":null,"runProfile":{"name":"standard","version":"v1"}}`,`BuiltinCommand` 使用 `{"kind":"builtinCommand","commandLine":"/status","expectedParserVersion":"..."}`;`AgentRuntimePublicInteractionPresentation`、`AgentRuntimeIntentEntryBinding`、`AgentRuntimeResumeIntent`、`AgentRuntimeCommandAck` 等其它带数据 union 也必须遵守同一形态。`kind` 是必填 discriminator,variant 不得再携带同名字段;未知 kind、缺失 kind、variant 字段多带/缺失、错误类型和重复字段均按 `INVALID_REQUEST` 处理,并纳入 Rust→TypeScript schema 生成与 golden fixture。 + +V1 统一边界常量必须由 Rust 单一来源生成 TypeScript schema、fixture 和 transport validator,不能由各 Consumer 各自复制: + +| 项目 | V1 上限与校验 | 超限行为 | +|---|---|---| +| 通用 Public 不透明 ID(适用于所有 Public DTO/command/event 中的 `*Id` 字段与 `cursor`,包括 `projectId/agentId/sessionId/runId/requestId/interactionId/responseMessageId/conversationUserMessageId/runtimeStatusMessageId/steerId/cancelOperationId/eventId/collaborationId`;下列 UserInput ID 与已有 resource/artifact binding identity 是显式例外) | `1–128` 个 UTF-8 字节;禁止控制字符、空白首尾和路径分隔符;新增 Public `*Id` 默认继承该规则,不能靠名称枚举遗漏校验 | ledger 前 `INVALID_REQUEST` | +| UserInput `responseId` | 沿用现有 answer 合同,最多 `160` 个 Unicode scalar、`640` 个 UTF-8 字节;禁止控制字符,trim 后不能为空;该 ID 仍是不透明值,不得用于路径或时序推导 | ledger 前 `INVALID_REQUEST`;若未来收紧上限必须提升 `schemaVersion` | +| 单个 command JSON | `64 KiB`(UTF-8,包含 `schemaVersion/projectId/requestId`) | ledger 前 `INVALID_REQUEST` | +| `submit_intent.Conversation.message` | 最多 `4,000` 个 Unicode scalar,且最多 `16 KiB` UTF-8 字节 | ledger 前 `INVALID_REQUEST` | +| `submit_intent.BuiltinCommand.commandLine` | 最多 `512` 个 Unicode scalar,且最多 `4 KiB` UTF-8 字节;规范化后必须仍以 `/` 开头 | ledger 前 `INVALID_REQUEST`;未知命令只允许固定长度 direct reply | +| same-run steer | 每个 run 最多 `16` 条追加指令、累计最多 `16 KiB`;单条最多 `4 KiB` UTF-8(沿用现有 steer ledger 合同),不得因 Conversation message 上限为 `16 KiB` 而放宽单条上限 | ledger 前或 steer ledger 受理前 `INVALID_REQUEST` / `TARGET_BUSY` | +| 输入附件 | V1 仅允许随非空 message 提交,最多 `8` 个既有项目资源 binding;不得携带原始字节、绝对路径或临时上传 token | attachment-only 或超限在 ledger 前 `INVALID_REQUEST`;binding 漂移为 `ARTIFACT_BINDING_UNAVAILABLE` | +| `requestChanges.feedback` | 最多 `2,000` 个 Unicode scalar,且最多 `8 KiB` UTF-8 字节;过滤、Unicode 规范化后重新计数 | ledger 前 `INVALID_REQUEST`;不保存原文 | +| UserInput | 最多 `3` 个问题;单个答案最多 `4,000` 个字符;全部答案最多 `8,000` 个字符、`32 KiB` UTF-8 字节 | ledger 前 `INVALID_REQUEST` | +| Public presentation 文本 | `title/summary/currentStepSummary/currentTaskSummary/checkSummary/latestReworkSummary` 各最多 `512` 个 Unicode scalar、`2 KiB` UTF-8;单个 interaction presentation 最多 `8 KiB` | ledger 前 `INVALID_REQUEST`;投影超限为 `PUBLIC_STATE_INVALID` | +| Public UserInput presentation | `question.id` 沿用现有唯一 snake_case 合同,最多 `64` 个 ASCII/UTF-8 字节;`header` 最多 `12` 个 Unicode scalar、`48` 字节;`question` 最多 `400` 个 Unicode scalar、`1,600` 字节;每个 option 的 `id` 最多 `64` 个 UTF-8 字节,`label` 最多 `60` 个 Unicode scalar、`240` 字节,`description` 最多 `240` 个 Unicode scalar、`960` 字节;均沿用现有单行、控制字符和 trim 后非空合同;问题数为 `1–3`,每题 option 数为 `2–3` | ledger 前 `INVALID_REQUEST`;投影超限、不安全或结构不完整为 `PUBLIC_STATE_INVALID` | +| Public conversation `directReply.publicText` | 最多 `4,000` 个 Unicode scalar、`16 KiB` UTF-8 字节;复用现有有界安全文本常量,不另建平行正文机制 | conversation append/commit 前确定性拒绝:现有 response delivery 进入 `rejected`,command 返回有界 `INTERNAL` 安全摘要;不得提交 message、sequence 或 cursor,也不得留下无法分页补读的 committed 消息 | +| Runtime status / public event message | 脱敏 status/publicText 各最多 `512` 个 Unicode scalar、`2 KiB` UTF-8;status 使用固定模板,publicText 只来自 Rust allowlist projector | 超限或不安全内容不投递;status 硬门失败/unknown 阻止 Run 推进,event message 以带原因的 `discarded` tombstone 丢弃并保留私有审计 | +| Local transport reply / local error message | reply 最多 `4,000` 个 Unicode scalar、`16 KiB` UTF-8;error message 最多 `512` 个 Unicode scalar、`2 KiB` UTF-8;均拒绝控制字符并只允许安全摘要/受信任 resolver 展示值 | ledger/response 前 `INVALID_REQUEST`;已持久化记录损坏为 `CORRUPT_RECORD`;不得截断后当作完整路径、错误或结果提交 | +| Public interactions / 专业组 / progress / intent options | 同时最多 `16` 个 User audience interaction;专业组固定最多 `6` 个公开 slot;progress `activeGroups` 最多 `6`、`latestChecks` 固定最多 `4`;submit intent option 最多 `16` 个,动态 child 只计数不列身份 | 超出表示投影不变量破坏,进入 `PUBLIC_STATE_INVALID`,不得静默截断 | +| Project approval target set | 最多 `16` 个精确 action target;按 `(agentId,parentRunId,runId,actionId,actionFingerprint)` 去重并固化 digest | 超限或集合漂移为业务拒绝 / `INTERACTION_STALE`,不得扩展为“当前全部任务” | +| Public Snapshot JSON | 最多 `256 KiB` UTF-8;数组按稳定 identity 排序 | 超限进入 `PUBLIC_STATE_INVALID`,不得返回部分 Snapshot | +| Public event 补读 | 单次最多 `256` 条 envelope;`afterCursor` 最多 `128` 字节 | `CURSOR_INVALID` / `CURSOR_EXPIRED`,不自动扩大批次 | +| Public conversation 补读 | 单页最多 `256` 条 committed message、最多 `1 MiB` JSON;`limit` 为 `1–256`,`afterCursor` 最多 `128` 字节;committed message/cursor 在 session 生命周期内逻辑保留 | `CURSOR_INVALID` 或 `CONVERSATION_HISTORY_INCOMPLETE` 时不返回部分页;该接口不返回 `CURSOR_EXPIRED`,不能扩大批次、静默截断历史或改读私有 ledger | +| response stream | 沿用现有 `AGENT_RUNTIME_RESPONSE_STREAM_MAX_CHARS=32,000`;sidecar 最多 `256 KiB`(`AGENT_RUNTIME_RESPONSE_STREAM_SIDECAR_MAX_BYTES`) | 进入 response operation 的确定性失败或 `outcome-unknown`,不得截断后提交 | + +长度均按 UTF-8 字节和 Unicode scalar **同时**检查;组合 JSON 大小优先于单字段上限。上限改变属于 breaking contract,必须提升 `schemaVersion`,不能在实现 PR 中悄悄放宽。 ### 1.1 项目身份、事实源与双 Snapshot - 项目 manifest 的稳定 `projectId` 是公开协议身份;`projectPath` 只作为本地 transport locator。Shell 每次调用都先 canonicalize locator、验证项目已在现有本地项目授权/目录簿中、取得并重读 manifest,再验证 `projectId` 一致。仅持有任意路径字符串不构成授权;符号链接、替换目录和 TOCTOU 按现有安全 path resolver/目录句柄约束处理。路径不进入公开 DTO、错误、事件、指纹或报告。 - Runtime durable state 及其同事务/同锁持久投影是业务事实;`SupervisorPublicSnapshot` 是 Consumer 唯一可见的完整状态事实。事件、命令 ack、错误和 response stream 都不能被合并成另一份 Runtime 状态。 -- V1 每个项目只有一个 Public Snapshot、一个 `snapshotRevision` 和一个项目级事件流;Snapshot 只包含当前 Project Supervisor。专业 Agent/动态 child 只折叠为协作数量,不公开身份或列表。 +- V1 每个项目只有一个 Public Snapshot、一个 `snapshotRevision` 和一个项目级事件流。Snapshot 的控制主体仍只有当前 Project Supervisor,但必须同时投影当前 run 的 Runtime-owned progress 与六个静态专业组的有界只读状态;动态 child 仍只折叠为数量,不公开 instance 身份。仅给出 `collaboratorCount` 无法满足现有工作台底栏、专业 Agent 确认和失败重试合同,因此不再作为完整 Public read model。 ```rust struct SupervisorPublicSnapshot { @@ -42,11 +72,72 @@ struct SupervisorPublicSnapshot { snapshot_revision: u64, event_cursor: String, project_id: String, + session_context: AgentRuntimePublicSessionContext, supervisor: Option, + collaborators: Vec, interactions: Vec, + command_capabilities: AgentRuntimeCommandCapabilities, updated_at: u64, } +enum AgentRuntimePublicSessionContext { + Ready { session_id: String, session_revision: u64 }, + NeedsBootstrap, + HandoffInProgress, + NeedsReconciliation, +} + +enum AgentRuntimeCollaboratorProjectionSource { + Runtime, + ManifestFallback, +} + +enum AgentRuntimePublicStatus { + Idle, + Preparing, // 已受理但 Public status message 尚未提交,不可 dequeue + Queued, + Running, + Waiting, + Paused, + Cancelling, + Completed, + Failed, + Cancelled, + TerminalPending, // 终态摘要尚未完成唯一 Runtime status message + NeedsReconciliation, +} + +enum AgentRuntimePublicStage { + Idle, + Preparing, + PublicStatusPending, + Planning, + Executing, + Coordinating, + WaitingForUserInput, + WaitingForUserApproval, + WaitingForPolicyApproval, + WaitingForDeveloperApproval, + WaitingForTimer, + WaitingForRunner, + PausedByUser, + Cancelling, + Finalizing, + Reconciling, + Completed, + Failed, + Cancelled, + TerminalPending, +} + +enum AgentRuntimePublicOutcome { + None, + Success, + Failure, + Cancelled, + Unknown, +} + struct SupervisorRuntimeSummary { agent_id: String, session_id: String, @@ -56,14 +147,408 @@ struct SupervisorRuntimeSummary { completed_step_count: u32, total_step_count: u32, current_step_summary: Option, - waiting_on: Option, - next_step: Option, - collaborator_count: u32, - outcome: Option, - error: Option, + progress: Option, + waiting_on: AgentRuntimePublicWaitingOn, + next_step: AgentRuntimePublicNextStep, + collaborator_count: u32, // 当前 Supervisor run 中已登记且未终结的协作单元;不含 manifestFallback、历史 child 或静态占位组 + outcome: AgentRuntimePublicOutcome, + error: Option, updated_at: u64, } +struct AgentRuntimeSupervisorProgressView { + run_id: String, + loop_iteration: u32, + task_progress: AgentRuntimePublicProgressCount, + plan_progress: AgentRuntimePublicProgressCount, + active_groups: Vec, + latest_checks: Vec, + latest_rework_summary: Option, + updated_at: u64, +} + +struct AgentRuntimePublicProgressCount { + completed: u32, + total: u32, +} + +enum AgentRuntimePublicCheckKind { + Playtest, + StaticVerification, + CodeMutation, + ScreenshotVerification, +} + +enum AgentRuntimePublicCheckOutcome { + Pending, + Passed, + Failed, + Unknown, +} + +struct AgentRuntimePublicCheckSummary { + kind: AgentRuntimePublicCheckKind, + outcome: AgentRuntimePublicCheckOutcome, + summary: Option, + evidence_count: u32, +} + +struct AgentRuntimeCollaboratorSummary { + collaboration_id: String, // 公开不透明身份,不能解析为内部 agentId + group: AgentRuntimePublicCollaboratorGroup, + source: AgentRuntimeCollaboratorProjectionSource, // runtime | manifestFallback + parent_run_id: Option, + run_id: Option, + status: AgentRuntimePublicStatus, + stage: AgentRuntimePublicStage, + completed_step_count: u32, + total_step_count: u32, + current_task_summary: Option, + outcome: AgentRuntimePublicOutcome, + error: Option, + recovery: Option, + updated_at: u64, +} + +enum AgentRuntimePublicCollaboratorGroup { + Design, Art, Code, Balance, Audio, Publishing +} + +enum AgentRuntimeCollaboratorRecoveryView { + RetryAvailable, + RepairApprovalPending { + interaction_id: String, + interaction_revision: u64, + }, +} + +// RepairApprovalPending 不是新的写 capability;它只引用同一份 Public +// PolicyApproval interaction。Consumer 必须从 interactions 原样构造 approve, +// 不得从 recovery view 生成第二个 repair/approval 命令。 + +struct AgentRuntimeCollaboratorRunTarget { + collaboration_id: String, + parent_run_id: String, + run_id: String, + expected_terminal_revision: u64, +} + +struct AgentRuntimeCommandCapabilities { + submit_intent: Option, + cancel: Option, + resume: Vec, +} + +struct AgentRuntimeSubmitIntentCapability { + session_id: String, + expected_session_revision: u64, + conversation_options: Vec, + builtin_command: Option, +} + +struct AgentRuntimeBuiltinCommandCapability { + parser_version: String, + supported_commands: Vec, +} + +struct AgentRuntimeBuiltinCommandSummary { + name: String, + route: AgentRuntimeBuiltinCommandRoute, + accepts_attachments: bool, // V1 永远为 false;显式字段用于拒绝漂移 +} + +enum AgentRuntimeBuiltinCommandRoute { + DirectReply, + Interaction, + ManagementAction, + RuntimeCommand, +} + +enum AgentRuntimeSlashParseResult { + RuntimeBuiltinCommand, + LocalManagementRoute, + TransportOnly, + UnknownCommand, +} + +// 本地/全局 route(尤其是无 projectId/sessionId 的命令)不复用 Public +// Snapshot 中的 submit_intent capability;它们通过受信任 local transport +// 单独取得 parser capability。宿主来源由 transport 绑定,Consumer 不能自报 localScope。 +struct AgentRuntimeLocalManagementCapability { + schema_version: String, + capability_id: String, + parser_version: String, + scope: AgentRuntimeLocalCapabilityScope, + supported_commands: Vec, +} + +enum AgentRuntimeLocalCapabilityScope { + Global, + Project { + project_id: String, + session_id: Option, + expected_session_revision: Option, + }, +} + +// `Project` 是 capability 的上界,不代表每个命令都可以省略 session 或 +// target。Shell 在锁内按命令再次校验 route-specific invariant:Goal mutation +// (`/goal <目标>`、`edit`、`pause`、`resume`、`clear`) 必须同时携带 +// `session_id + expected_session_revision`,并在 command target 中携带 Goal ID +// 与 Goal revision;Goal read/status 可以只使用 project read capability。 +// `/resume` 必须绑定 session、run 以及 recovery target revision,不能只靠 +// project scope;`/project` 的初始解析和 `/config` 使用 Global scope,二者 +// 不得携带 project/session target;其它 project-scoped 命令若要求 active session +// 也必须显式声明并验证这两个字段。字段缺失返回 `SCOPE_MISMATCH`,不能以 +// `None` 放宽 mutation 权限或从当前窗口猜目标。 + +struct AgentRuntimeLocalCommandSummary { + name: String, + route: AgentRuntimeLocalCommandRoute, + // 服务端私有绑定的精确目标;Goal/Runtime/legacy trace 等 revision 变化时 + // 整个 capability 失效,Consumer 不解析 targetRef。 + target_ref: Option, +} + +enum AgentRuntimeLocalCommandRoute { + DirectReply, + ManagementAction, + TransportOnly, +} + +enum AgentRuntimeLocalManagementOperationStatus { + Prepared, + Executing, + Succeeded, + Rejected, + OutcomeUnknown, + NeedsReconciliation, +} + +// Local operation 与项目 request ledger 使用同一未知结果闭合规则: +// `prepared -> executing | rejected | outcome-unknown`,其中无法证明是否已 +// 开始执行时必须走 `outcome-unknown`;`executing -> succeeded | rejected | +// outcome-unknown`,`outcome-unknown -> needs-reconciliation`;只有受信任 +// reconciliation 流程能把 `needs-reconciliation` 推进为确定的 succeeded/rejected。 +// unknown/reconciliation 期间同一 (localScopeId, requestId) 只能读回或核对, +// 不能换 handle/requestId 重做;Local readback 对前者返回 COMMAND_RESULT_UNKNOWN, +// 对后者返回 NEEDS_RECONCILIATION,并始终保留原 operation identity。 + +// Local command 的 commandLine 只在受信任 transport → parser → resolver 的 +// 短链路中出现;含 host locator 时原始路径由 resolver 拆出并绑定临时 handle, +// command durable record 和 response 只引用 locatorHandleRef/digest,不保存原文。 +struct AgentRuntimeLocalManagementCommand { + schema_version: String, + capability_id: String, + request_id: String, + command_line: String, + expected_parser_version: String, +} + +struct AgentRuntimeLocalTransportReply { + text: String, +} + +struct AgentRuntimeLocalManagementResponseMeta { + schema_version: String, + // malformed/missing requestId 的 ledger 前错误使用 None;transport 自身 + // 仍按调用上下文关联该响应,不能伪造业务 requestId。 + request_id: Option, + request_fingerprint: Option, + replayed: bool, +} + +enum AgentRuntimeLocalManagementResponse { + Succeeded { + meta: AgentRuntimeLocalManagementResponseMeta, + result: AgentRuntimeLocalManagementResult, + // Succeeded 的 meta.request_fingerprint 必须为 Some;TransportClosed + // 也使用规范化 command fingerprint,不创建项目 ledger。 + }, + Failed { + meta: AgentRuntimeLocalManagementResponseMeta, + error: AgentRuntimeLocalManagementError, + }, +} + +enum AgentRuntimeLocalManagementResult { + Reply { reply: AgentRuntimeLocalTransportReply }, + ManagementAccepted { + operation_id: String, + status: AgentRuntimeLocalManagementOperationStatus, + resolved_project_id: Option, + project_operation_ref: Option, + }, + TransportClosed, +} + +// 本地 route 的错误是独立合同;Consumer/GUI/CLI 只能按 code/retryable 和 +// readback 处理,不能解析中文 message。错误响应不伪造 projectId,也不携带 +// observedSnapshotRevision 或 interactionRequired;前者只在成功结果的 +// `resolved_project_id` 有真实解析结果时出现,后者永远不是 local route 的字段。 +// ResponseMeta 是唯一的 requestId/fingerprint/replayed 来源;Succeeded/Failed +// 不得再复制这些字段。缺少/非法 requestId 的 ledger 前错误使用 +// `meta.request_id = None`,不因为错误本身无法构造而退回 transport 文本。 +struct AgentRuntimeLocalManagementError { + code: AgentRuntimeLocalManagementErrorCode, + retryable: bool, + // 只允许安全、有限的摘要;Consumer 不解析该字段。 + message: String, +} + +enum AgentRuntimeLocalManagementErrorCode { + CapabilityNotFound, + ProtocolVersionUnsupported, + InvalidRequest, + TargetStale, + ScopeMismatch, + PermissionDenied, + LocatorUnavailable, + CommandInProgress, + CommandResultUnknown, + NeedsReconciliation, + IdempotencyKeyReused, + CorruptRecord, + RequestNotFound, + Internal, +} + +// AgentRuntimeLocalTransportReply 可在受信任本地 UI/CLI 展示本地路径;它没有 +// observedSnapshotRevision,也不能进入 Public conversation/response stream。 +// `Succeeded` 与 `Failed` 是严格互斥的 tagged union;不存在 result/error +// 同时为空或同时存在的第三种形态。`requestFingerprint` 仅在完成规范化且 +// 未含 host locator 原文时返回;错误 message 只允许安全摘要,并遵守上方 +// Local transport reply / local error message 的字符、字节和控制字符上限。 + +// 不使用 intent/profile/binding 三个独立数组,避免 Consumer 误把它们做 +// 笛卡尔积。每个 option 是一个已经由 Shell 注册并校验过的组合;实际 +// Template/ExistingDesign 资源仍由资源管理面返回 immutable binding, +// capability 只声明所需 binding kind,不把资源目录复制进 Snapshot。 +struct AgentRuntimeSubmitIntentOption { + intent_kind: AgentRuntimeIntentKind, + run_profile: AgentRuntimeRunProfile, + entry_binding_kind: AgentRuntimeIntentEntryBindingKind, + input_policy: AgentRuntimeSubmitInputPolicy, + allowed_attachment_media_kinds: Vec, +} + +enum AgentRuntimeSubmitInputPolicy { + TextOnly, // textOnly + TextWithOptionalAttachments, // textWithOptionalAttachments;仍要求 message 非空 +} + +enum AgentRuntimeIntentEntryBindingKind { + None, + Template, + ExistingDesign, +} + +struct AgentRuntimeCancelCapability { + session_id: String, + expected_session_revision: u64, + run_id: String, +} + +enum AgentRuntimeResumeCapability { + ContinueSupervisorRun { + session_id: String, + expected_session_revision: u64, + run_id: String, + expected_run_revision: u64, + }, + RetrySupervisorRun { + session_id: String, + expected_session_revision: u64, + run_id: String, + expected_terminal_revision: u64, + }, + RetryCollaboratorRun { + session_id: String, + expected_session_revision: u64, + target: AgentRuntimeCollaboratorRunTarget, + }, +} + +enum AgentRuntimePublicWaitingOn { + None, + PublicStatusMessage, + UserInput, + UserApproval, + PolicyApproval, + DeveloperApproval, + Timer, + Runner, + Reconciliation, +} + +enum AgentRuntimePublicNextStep { + None, + WaitForPublicStatus, + SubmitIntent, + AnswerInteraction, + ApproveInteraction, + CancelRun, + ResumeRun, + RetryTerminalRun, + WaitForRunner, + Reconcile, +} + +enum AgentRuntimeCommandErrorCode { + ProtocolVersionUnsupported, // PROTOCOL_VERSION_UNSUPPORTED + InvalidRequest, // INVALID_REQUEST + PermissionDenied, // PERMISSION_DENIED + TargetStale, // TARGET_STALE + TargetBusy, // TARGET_BUSY + InteractionStale, // INTERACTION_STALE + InteractionAlreadyResolved, // INTERACTION_ALREADY_RESOLVED + CancelAlreadyTerminal, // CANCEL_ALREADY_TERMINAL + ArtifactBindingUnavailable, // ARTIFACT_BINDING_UNAVAILABLE + IdempotencyKeyReused, // IDEMPOTENCY_KEY_REUSED + CommandInProgress, // COMMAND_IN_PROGRESS + CommandResultUnknown, // COMMAND_RESULT_UNKNOWN + NeedsReconciliation, // NEEDS_RECONCILIATION + OwnerUnavailable, // OWNER_UNAVAILABLE + TransientUnavailable, // TRANSIENT_UNAVAILABLE + OwnerFenced, // OWNER_FENCED + RequestNotFound, // REQUEST_NOT_FOUND + CursorInvalid, // CURSOR_INVALID + CursorExpired, // CURSOR_EXPIRED + Internal, // INTERNAL +} + +enum AgentRuntimeCommandErrorKind { + Protocol, Validation, Authorization, Target, Interaction, + Idempotency, InProgress, UnknownOutcome, Ownership, Cursor, Internal, +} + +enum AgentRuntimeSnapshotErrorCode { + ConfigurationRequired, // CONFIGURATION_REQUIRED + AuthenticationFailed, // AUTHENTICATION_FAILED + RateLimited, // RATE_LIMITED + TransportFailed, // TRANSPORT_FAILED + ProviderFailed, // PROVIDER_FAILED + VerificationFailed, // VERIFICATION_FAILED + BudgetExhausted, // BUDGET_EXHAUSTED + SandboxDenied, // SANDBOX_DENIED + ArtifactUnavailable, // ARTIFACT_UNAVAILABLE + ReconciliationRequired, // RECONCILIATION_REQUIRED + PublicStateInvalid, // PUBLIC_STATE_INVALID + Internal, // INTERNAL +} + +enum AgentRuntimeSnapshotErrorKind { + Configuration, Authentication, RateLimit, Transport, Provider, + Verification, Budget, Sandbox, Artifact, Reconciliation, PublicState, Internal, +} + +struct AgentRuntimeSnapshotError { + code: AgentRuntimeSnapshotErrorCode, + kind: AgentRuntimeSnapshotErrorKind, + retryable: bool, + interaction_required: bool, +} + struct DeveloperRuntimeSnapshot { // 独立开发 DTO,不嵌入 Public 类型或 event cursor: schema_version: String, @@ -81,9 +566,64 @@ struct DeveloperRuntimeSnapshot { recent_tool_calls: Vec, interaction_records: Vec, } + +struct AgentRuntimeToolCallDebugView { + tool_call_id: String, + tool_name: String, + status: AgentRuntimeDebugStatus, + request_summary: Option, + response_summary: Option, + started_at: u64, + finished_at: Option, +} + +struct AgentRuntimeInteractionDebugView { + interaction_id: String, + interaction_revision: u64, + kind: AgentRuntimeInteractionKind, + status: AgentRuntimeInteractionStatus, + presentation: AgentRuntimeInteractionPrivatePresentation, +} + +struct AgentRuntimeInteractionPrivatePresentation { + title: String, + summary: String, + questions: Vec, + allowed_decisions: Vec, + private_context_summary: Option, +} + +struct AgentRuntimePrivateQuestion { + id: String, + header: String, + question: String, + options: Vec, + allow_freeform: bool, +} + +struct AgentRuntimePrivateQuestionOption { + id: String, + label: String, + description: String, +} + +enum AgentRuntimeDebugStatus { + Started, // started + Succeeded, // succeeded + Failed, // failed + OutcomeUnknown, // outcomeUnknown +} ``` -Public Snapshot 白名单固定为:稳定项目与当前 Project Supervisor 身份、紧凑阶段、完成数/总数、当前步骤摘要、等待对象、下一步、协作数量、正式用户可回答的最小交互、终态摘要和稳定公开错误。它不得包含项目绝对路径、完整任务/action/plan、动态 child 身份、原始 observation、工具名称/参数/计划、Provider 原文、`recentToolCalls` 或内部 interaction fingerprint。`tool_request` 不进入正式公开 Snapshot 或事件。 +Developer-only DTO 也必须沿用 V1 的字符串、数量、字节和内容安全上限;`request_summary`、`response_summary`、`private_context_summary` 只允许有界脱敏摘要,禁止 Provider 原文、凭据、完整工具参数、绝对路径和未经过滤的 observation。Developer capability 只允许读取这些 DTO,不能把它们作为 Public interaction 或命令 target 发送回 Shell。 + +Public Snapshot 白名单固定为:稳定项目与当前 Project Supervisor 身份、紧凑阶段、完成数/总数、当前步骤摘要、稳定等待枚举、稳定下一步枚举、六个静态专业组的有界只读状态、动态 child 数量、正式用户可回答的最小交互、可由服务端生成的 command capabilities、终态摘要和不含命令私有字段的 Snapshot error。命令错误(包括 requestId、requestFingerprint、replayed 和 observedSnapshotRevision)只出现在命令响应/结果读回,不得嵌入 Snapshot。它不得包含项目绝对路径、完整任务/action/plan、动态 child 身份、原始 observation、工具名称/参数/计划、Provider 原文、`recentToolCalls` 或内部 interaction fingerprint。`collaborationId`、`runId` 和 capability target 都是不透明句柄,不能由 Consumer 推导 agent/task/path;`tool_request` 不进入正式公开 Snapshot 或事件。 + +`sessionContext=NeedsBootstrap` 只表示现有项目确实没有可绑定的 Project Supervisor Session。若 active Session 索引缺失但项目内存在身份完整的当前 `project-supervisor` durable Runtime,projector 必须先按 1.1.1.b 的 project/session/run/lineage 证据恢复 Session record/handoff 并继续展示该 Runtime;证据冲突则 `NeedsReconciliation`,不能误报 NeedsBootstrap、隐藏已落盘失败事实或创建第二 Session。只有确无 Runtime/session 证据时,Consumer 才调用既有 Session 管理面创建/恢复 active session 后重新读取 Snapshot;不能向五命令伪造 sessionId 或把首次启动隐式塞入 submit_intent。 + +Session rotation 的 `Prepared` 只表示唯一 operation 已持久化、active index 尚未 fenced;它单独存在时继续按旧 active session 投影 `sessionContext=Ready`,不得投影 `HandoffInProgress`,也不得创建 successor session、handoff、target/continuation set、manifest 或其它 rotation 副作用。Prepared 后若旧 session revision、run creation epoch 或 active index 已变化,该 operation 必须以 `Rejected` tombstone 结束,不能用旧预期强行提交 fence。只有 operation 的 `phase=FenceCommitted`、`rotationFenceCommitMarker` 与 active index 的 `rotationFenceOperationId + rotationFenceCommitMarker` 在同一 journal 线性化提交并可一致回读后,才开始投影 `sessionContext=HandoffInProgress`。`FenceCommitted`、`HandoffManifestCommitted`、`SuccessorSessionCommitted` 和 `HandoffsCommitted` 均只允许读取 Snapshot、已有 command result/conversation 和 reconciliation 证据,`commandCapabilities` 为空,不能提交 submit/answer/approve/cancel/resume/retry,旧 session 的 interaction 也不能在切换期间写入;同时所有 interaction view 的 `actionable=false` 且 `allowedActions=[]`,Consumer 不得仅因 interaction 仍出现在 Snapshot 就渲染或派发 answer/approve。只有 `sessionContext=Ready`、interaction 为 Open 且对应 action 通过 capability/锁内复核时,Shell 才返回 `actionable=true`。rotation fence 生效后,新 requestId 的五命令统一在 ledger 前返回 `TARGET_BUSY`;已经存在的 requestId 仍可只读回原结果,但不能借重放推进新的 session-bound 副作用。只有 operation 为 `ActiveSessionCommitted`、`activeSessionCommitMarker` 可读,且同一线性化点满足 `activeIndex.committedRotationOperationId == operation.operationId`、`activeIndex.activeSessionId == operation.successorSessionId == successorSession.sessionId`、`activeIndex.activeSessionRevision == successorSession.sessionRevision`、manifest 的 operation/predecessor/successor/session revision 均与 operation 相等、`manifest.runCreationEpoch == operation.expectedRunCreationEpoch`、`activeIndex.runCreationEpoch == operation.expectedRunCreationEpoch + 1`,并且完整 handoff manifest、Run target set 与 continuation set 均可读时,才投影 `Ready { successorSessionId, successorSessionRevision }` 并生成新 capability;任一关系不成立统一 `NeedsReconciliation`,不得扫描其它 operation 猜测已提交者。Open/Resolving interaction、未终结 command/input envelope、DirectReply/RuntimeFinalReply、Runtime status/public-event delivery 的原 `sessionId` 保持创建时 lineage,不改写为 successor;successor 只能凭 manifest continuation item 继续授权、投影或恢复同一 record identity。未被 continuation set 覆盖的旧 session record 必须失败关闭。`Rejected` 在旧 active session 未改变时恢复为旧 `Ready`;`ReconciliationRequired` 或 index/manifest/target/continuation digest 不一致统一投影 `NeedsReconciliation`,清空全部写 capability。`HandoffInProgress` 期间只读,`NeedsReconciliation` 不产生写 capability。`source=manifestFallback` 只表示“没有可匹配 Runtime 的历史 manifest 摘要”,不得伪造 running、进度百分比、完成或失败结论;该状态的 command capability 必须为空,直到真实 Runtime Snapshot 建立。Transport 暂时读失败时,Consumer 可以在本地短暂保留最后可信 Snapshot 作为 stale display,但必须标记本地 stale、禁用除上一份精确 cancel capability 外的其它写按钮并继续重试;stale cancel 只是用户止损尝试,Shell 仍重新授权和锁内复核,且当前 rotation/drain/reconciliation phase gate 优先于 stale 止损例外。不得把 stale display 当作新的 Runtime 事实,也不得清空或倒退已有专业 Agent 状态。Shell 不接受 Consumer 传来的 stale 标记。 + +专业组投影必须按 `parentRunId == 当前 Project Supervisor runId` 精确筛选,并按固定六组顺序输出;旧父 run、其它项目、动态 child、没有父绑定的 Runtime 记录不能冒充当前协作。只存在 manifest 任务的静态组可以输出 `source=manifestFallback + parentRunId=None + runId=None + idle/idle/none` 的占位 slot,但不得携带 Runtime error、进度、当前任务或 recovery,也不能计作活动 collaborator;存在当前父 run 的真实 Runtime 记录后必须由 `source=runtime` 唯一替换。每个公开 `collaborationId` 都由 Shell 绑定真实 `agentId + parentRunId + runId`,失败重试和 ToolApproval 重新读回时必须同时核对该三元身份。`recovery=RetryAvailable` 只是展示“当前 commandCapabilities.resume 中存在同 collaborationId 的 RetryCollaboratorRun”;`RepairApprovalPending` 只引用 Public `interactions` 中同一 interactionId/revision 的审批,不是第二个写 capability。两处任一缺失或不一致都按 `PUBLIC_STATE_INVALID` 失败关闭。`expectedArtifacts`、artifact SHA、verification gate 和文本回执只以安全摘要/数量/结果类别投影,详情仍通过既有项目资源/文档 read model 读取,不能把专业 Agent 内部对话或私有 observation 塞入 Snapshot。 Developer Snapshot 使用独立命令和 Rust DTO,不嵌入 Public Snapshot,避免开发调用方误订阅正式事件后把两种投影合并。仅前端 `devMode`、query/hash 或调用者提供的布尔值不构成授权;Tauri 端只允许 debug 构建中受信任的 `developer` 窗口标签,受信任开发 CLI 使用显式本地 capability,进程内测试使用 test capability;release/client/supervisor-chat 和 Runner 普通 Consumer 一律返回 `PERMISSION_DENIED`。后续若开放其它开发调用方,必须新增等价的服务端 capability,不得复用 Public read 权限。 @@ -99,9 +639,11 @@ Projection-dirty 的提交顺序冻结为以下四步,所有会改变 Public 因此,“Runtime durable state 已提交但 projection 未刷新”是可自动补投影状态;“Runtime durable state 是否提交无法证明”不是可补投影状态,必须进入 `needs-reconciliation`。 -Public status/stage 是稳定枚举,不直接透传内部 phase。映射必须穷尽已知内部状态:排队为 `Queued`;planning/LLM 为 `Running/Planning`;action/observation/协作为 `Running/Executing|Coordinating`;user input、developer approval、确定性 retry/lane/timer、paused 分别为 `Waiting` 下的明确 stage;completed、failed/budget-exhausted、cancelled 和 needs-reconciliation 分别映射稳定终态/核对态。遇到未知或互相矛盾的内部 status/phase 时不得猜成 Running,而要投影 `NeedsReconciliation` 和脱敏 `PUBLIC_STATE_INVALID`。具体映射表与 DTO 同模块维护并做穷尽契约测试。 +Public status/stage 是稳定枚举,不直接透传内部 phase。映射必须穷尽已知内部状态:Start 在 user message 已提交但 Runtime-owned status message 尚未提交时为 `Preparing/PublicStatusPending`,`waitingOn=publicStatusMessage`、`nextStep=waitForPublicStatus`,绝不显示 `queued`;status commit marker 成功后才为 `Queued`。planning/LLM 为 `Running/Planning`;action/observation/协作为 `Running/Executing|Coordinating`;user input、user/tool/policy approval、确定性 retry/lane/timer 分别为 `Waiting` 下的明确 stage;Runner/owner 暂不可用或等待 Runner 恢复时为 `Waiting/WaitingForRunner`,`waitingOn=runner`、`nextStep=waitForRunner`;paused、cancelling 分别为 `Paused/PausedByUser`、`Cancelling/Cancelling`;根终态事实已确定但唯一 terminal failure status 尚未提交时为 `TerminalPending/TerminalPending`,`waitingOn=publicStatusMessage`,`outcome=Unknown`,不得提前显示 `Failed` 或 `Failure`;只有 status commit marker 可读回后才投影 `Failed/Failure`。completed、failed/budget-exhausted、cancelled 和 needs-reconciliation 分别映射稳定终态/核对态。遇到未知或互相矛盾的内部 status/phase 时不得猜成 Running,而要投影 `NeedsReconciliation` 和脱敏 `PUBLIC_STATE_INVALID`。Runtime 失败原因使用独立 `AgentRuntimeSnapshotErrorCode`,不能复用命令协议错误码;配置缺失、鉴权、限流、Provider/验证/sandbox/预算失败都必须映射固定 code,不把 transport/Provider 原文带到 Public。具体映射表与 DTO 同模块维护并做穷举契约测试。 -进度只从当前 Supervisor 的可信结构化计划计算:`totalStepCount=planSteps.len()`,`completedStepCount` 只计 completed,当前摘要只取唯一 active step 的脱敏标题;没有结构化计划时为 `0/0`,不得按 tool/action/loop 数猜进度。`collaboratorCount` 只计当前 Supervisor run 的 durable、尚未终结专业协作单元并去重,不包含历史 child。`waitingOn/nextStep/outcome/error` 是有界、脱敏、仅展示的 Shell 文本,Consumer 不得解析它们路由命令;可执行能力只由 interaction view 和写命令结果决定。 +进度只从当前 Supervisor run 的可信结构化事实计算:顶层 `totalStepCount=planSteps.len()`,`completedStepCount` 只计 completed,当前摘要只取唯一 active step 的脱敏标题;没有结构化计划时为 `0/0`,不得按 tool/action 数猜进度。现有工作台要求的 Runtime-owned 进度卡不能继续由客户端跨 manifest/plan/event 自行拼装,因此 `progress` 由 Shell 投影同 run 的 `loopIteration`、任务/计划完成数、当前活跃专业组,以及最多四类最新校验(试玩、静态检查、代码 mutation、截图检查)的稳定 outcome/安全摘要/证据数量;返工只提供有界安全摘要。没有可信 receipt/verification evidence 就投影 `unknown` 或省略,绝不根据日志文字猜通过。该 progress 只更新 Snapshot 同一 run 的卡片,不写 conversation;run 切换时由新 Snapshot 原子替换,Consumer 不能合并旧 run 证据。`collaboratorCount` 只计当前 Supervisor run 的 durable、尚未终结专业协作单元并去重,不包含历史 child 或 manifestFallback slot。`waitingOn/nextStep/outcome/error` 是有界、脱敏、仅展示的 Shell 字段,Consumer 不得解析它们路由命令;可执行能力只由 interaction view 与 command capabilities 决定。 + +进度字段关系冻结如下:`SupervisorRuntimeSummary.completed_step_count/total_step_count` 在 `progress` 存在且 `plan_progress` 可用时必须逐字等于 `plan_progress.completed/total`;无结构化计划、plan receipt 缺失或 task/plan receipt 不一致时,Shell 省略 `progress` 并将顶层计数固定为 `0/0`,不得用 task 计数替代或把数字伪装成 unknown。`task_progress` 只来自当前 Supervisor run 的 Runtime task journal,`plan_progress` 只来自同一 run 的 immutable plan journal,二者不互相推导、不得跨 run 合并;同一 revision 内若两者 receipt 不一致,进入 reconciliation,而不是选择较新或较大数字;单个 check 缺少 receipt 时仍可保留该 check,但 outcome 必须为 `Unknown` 且 evidenceCount 为 `0`。`active_groups` 只列当前 run 的 active group,`latest_checks` 每种 check 最多一条且必须带同一 run 的 evidence receipt;任何上述字段变化都推进同一 Snapshot revision。 所有可能改变 Public 白名单的 Runtime 写入都必须经过统一 projection-dirty 协议:先在同一 project lock 下写 durable dirty journal,再提交原 Runtime 变更,随后重建 Public Snapshot/事件并关闭 journal。变更前崩溃可重建为无变化,变更后崩溃可由 Public read、订阅启动、Runner 启动或项目 wake 幂等补投影。P2 必须枚举并接入现有 state、task、interaction、终态和恢复写入口;不允许依赖 Consumer 轮询偶然发现漏掉的内部变更。 @@ -120,19 +662,289 @@ Public status/stage 是稳定枚举,不直接透传内部 phase。映射必须 所有身份校验都在取得 project execution owner 后、写入 request ledger 前完成。locator canonicalize 与 manifest 复核必须针对同一已打开目录句柄完成;复核失败返回结构化错误并不产生 ledger 记录。会话或 run 的 revision 只由其权威持久化记录递增,不能使用 Consumer 看到的时间戳或事件 sequence 代替。 +### 1.1.1.a Durable record envelope + +所有项目级 Shell durable record 先嵌入同一个 envelope;尚未解析 `projectId` 的本地 locator/窗口操作使用独立 local envelope,不能把 `projectId` 改成空字符串或可空字段来复用项目 envelope: + +```rust +// 项目级 durable record 使用;envelope.projectId 必须与 record body 中重复出现的 +// projectId 一致,不一致即损坏,不能选择其一继续。 +struct AgentRuntimeDurableEnvelope { + schema_version: String, + record_id: String, + // 记录自身的 CAS/recovery revision;每次该 record 的 durable 状态变化递增, + // continuation item 的 record_revision 固定取这里,不能用时间或文件版本猜测。 + record_revision: u64, + // 项目 ledger 的追加顺序;它不是 record_revision,不可单独证明某条记录未变化。 + ledger_version: u64, + project_id: String, + checksum: String, + owner_boot_id: String, + owner_generation: u64, + created_at: u64, + updated_at: u64, +} + +// 仅用于尚未解析 projectId 的受信任本地管理面。localScopeId 由宿主的 +// app profile/control lease 派生,不接受 Consumer 自报,也不包含路径。 +struct AgentRuntimeLocalDurableEnvelope { + schema_version: String, + record_id: String, + // local record 自身的 CAS/recovery revision;与项目 envelope 使用同一递增规则。 + record_revision: u64, + ledger_version: u64, + local_scope_id: String, + checksum: String, + owner_boot_id: String, + owner_generation: u64, + created_at: u64, + updated_at: u64, +} +``` + +两类 envelope 的 `recordId` 都与业务 identity 一一对应;`recordRevision` 从 `1` 开始并在同一 record 的每次状态变化时单调递增,`ledgerVersion` 只表示项目 ledger 追加顺序;`checksum` 均按“去掉 checksum 字段后的完整 record”计算 RFC 8785 canonical JSON SHA-256,owner boot/generation 参与 fencing。local envelope 不能进入 Public Snapshot、项目事件或 Runtime conversation;一旦解析出项目身份,后续项目级副作用必须在 project owner/lock 下建立或关联项目级 operation record,并把稳定引用写入 local record 的 `projectOperationRef`;两边状态无法唯一对应时进入 outcome-unknown/reconciliation,不能继续只靠 local record 执行或重复副作用。 + +### 1.1.1.b Session handoff 映射 + +现有 Runtime/Process Session 的 `conversation_session_id` 只能作为候选 `sessionId`,不能直接当作 Supervisor session lineage;它没有证明“新窗口可以控制旧 Run”的关系。P1 必须新增与既有会话管理记录一对一关联的 durable handoff 记录(不复制 conversation 正文): + +```rust +enum ActiveSessionStatus { + Pending, // pending;rotation 尚未提交 active-session marker + Active, // active + Superseded, // superseded + Closed, // closed +} + +enum SessionHandoffReason { + Rotation, // rotation + OwnerRecovery, // ownerRecovery + Migration, // migration +} + +struct ProjectSupervisorSessionRecord { + envelope: AgentRuntimeDurableEnvelope, + project_id: String, + supervisor_lineage_id: String, // 首次创建后不可变 + session_id: String, // 每次 rotation 新建,永不复用 + session_revision: u64, + predecessor_session_id: Option, + status: ActiveSessionStatus, // pending | active | superseded | closed + issued_by_control_lease_id: String, + created_at: u64, + closed_at: Option, +} + +struct ProjectSupervisorRunHandoff { + envelope: AgentRuntimeDurableEnvelope, + project_id: String, + supervisor_lineage_id: String, + run_id: String, + rotation_operation_id: String, + handoff_manifest_id: String, + predecessor_session_id: String, + successor_session_id: String, + target_run_set_digest: String, + handoff_revision: u64, + handoff_reason: SessionHandoffReason, +} + +// rotation 开始时固化的非终态 Run 集合。目标集合本身也是 durable record,不能 +// 只把 target_run_set_ref 当作未定义的 sidecar;恢复不得重新扫描当前 Run 集合。 +struct ProjectSupervisorRunHandoffTargetSetRecord { + envelope: AgentRuntimeDurableEnvelope, + target_set_id: String, + rotation_operation_id: String, + manifest_id: String, + project_id: String, + supervisor_lineage_id: String, + predecessor_session_id: String, + successor_session_id: String, + // 按 runId 排序的不可变 payload;每个 item 同时固化捕获时的 session/run revision。 + sorted_targets_ref: String, + target_run_count: u32, + target_run_set_digest: String, + commit_marker: Option, + status: HandoffTargetSetStatus, +} + +struct ProjectSupervisorRunHandoffTargetPayload { + envelope: AgentRuntimeDurableEnvelope, + target_set_id: String, + chunk_index: u32, + items: Vec, // 每 chunk 最多 256 条 + checksum: String, +} + +struct ProjectSupervisorRunHandoffTargetItem { + run_id: String, + predecessor_session_id: String, + captured_run_revision: u64, +} + +enum HandoffTargetSetStatus { + Reserved, + Committed, + Corrupt, +} + +// target set payload 使用同一 owner 下有界、带 checksum 的私有 record/chunk;V1 +// 每个 chunk 最多 256 条、整个 target set 最多 4096 条,超过上限拒绝 rotation +// 并保留原 active session。digest 固定为 sha256(RFC 8785 canonical JSON(sorted +// target items)),不允许按文件名或最新 updatedAt 选择。target set/chunk 的保留与 +// 隔离规则和其它 durable record 一并冻结;payload 缺失、checksum 不一致或 commit +// marker 不可读直接 reconciliation。 +struct ProjectSupervisorRunHandoffManifest { + envelope: AgentRuntimeDurableEnvelope, + manifest_id: String, + rotation_operation_id: String, + project_id: String, + supervisor_lineage_id: String, + predecessor_session_id: String, + successor_session_id: String, + source_session_revision: u64, + target_run_set_ref: String, + target_run_count: u32, + target_run_set_digest: String, + run_creation_epoch: u64, + continuation_set_ref: String, + continuation_count: u32, + continuation_set_digest: String, +} + +// 除 Run 本身外,rotation 还必须固化所有会跨 session 继续的交互/投递记录。 +// 这些记录的 session_id 是创建时的 lineage/provenance,不在 handoff 中改写; +// successor 只能凭该 continuation set 取得一次性的当前 session 授权,不能 +// 通过“当前 session + 最近记录”重新猜测要继续哪条消息或 interaction。 +struct ProjectSupervisorSessionHandoffContinuationSetRecord { + envelope: AgentRuntimeDurableEnvelope, + continuation_set_id: String, + rotation_operation_id: String, + manifest_id: String, + project_id: String, + supervisor_lineage_id: String, + predecessor_session_id: String, + successor_session_id: String, + sorted_continuations_ref: String, + continuation_count: u32, + continuation_set_digest: String, + commit_marker: Option, + status: HandoffContinuationSetStatus, +} + +struct ProjectSupervisorSessionHandoffContinuationItem { + record_kind: HandoffContinuationRecordKind, + record_id: String, + // 必须等于被引用 record 的 envelope.record_revision;不得使用 + // interactionRevision、ledgerVersion、时间戳或文件版本替代。 + record_revision: u64, + session_id: String, + run_id: Option, +} + +struct ProjectSupervisorSessionHandoffContinuationPayload { + envelope: AgentRuntimeDurableEnvelope, + continuation_set_id: String, + chunk_index: u32, + items: Vec, // 每 chunk 最多 256 条 + checksum: String, +} + +enum HandoffContinuationRecordKind { + Interaction, + CommandOperation, + InputEnvelope, + DirectReplyDelivery, + RuntimeFinalReply, + RuntimeStatusMessage, + PublicEventMessage, +} + +enum HandoffContinuationSetStatus { + Reserved, + Committed, + Corrupt, +} + +// continuation payload 同样使用有界、带 checksum 的 chunk;V1 最多 8192 条, +// digest 固定为 sha256(RFC 8785 canonical JSON(sorted continuation items))。 +// 缺失记录、revision/record_kind/session/run 不匹配、checksum 或 commit marker +// 不一致,均直接进入 reconciliation,不按 ledger 当前“最新状态”补猜集合。 + +// Session rotation 跨多个 session/handoff record,不能靠“最后写入的文件”判断 +// 成功。rotation operation 是唯一恢复事实;active-session commit marker 之前 +// 旧 active session 仍然有效,新的 session/handoff 只能作为待提交事实存在。 +struct ProjectSupervisorSessionRotationRecord { + envelope: AgentRuntimeDurableEnvelope, + operation_id: String, + project_id: String, + supervisor_lineage_id: String, + predecessor_session_id: String, + successor_session_id: String, + expected_session_revision: u64, + // Prepared 时尚未建立 manifest,必须为 None;只有 target/continuation set 与 + // manifest 均提交并回读成功后,才在 HandoffManifestCommitted 中补为 Some。 + handoff_manifest_ref: Option, + expected_run_creation_epoch: u64, + phase: SessionRotationPhase, + // 与 active index 的 rotation_fence_commit_marker 一致;Prepared 时必须为 None。 + // fence 与该 marker/phase 必须在同一 journal 线性化提交。 + rotation_fence_commit_marker: Option, + active_session_commit_marker: Option, +} + +enum SessionRotationPhase { + Prepared, // operation 已持久化,active index 尚未 fenced + FenceCommitted, // operation phase/marker 与 active-index fence 已原子提交 + HandoffManifestCommitted, + SuccessorSessionCommitted, + HandoffsCommitted, + ActiveSessionCommitted, + ReconciliationRequired, + Rejected, +} + +// 当前 active session 只能从该项目唯一 index 读取;不能扫描 session 文件 +// 或按 updatedAt/文件名选择“最新”记录。 +struct ProjectSupervisorActiveSessionIndex { + envelope: AgentRuntimeDurableEnvelope, + project_id: String, + supervisor_lineage_id: String, + active_session_id: String, + active_session_revision: u64, + run_creation_epoch: u64, + // 非空表示 rotation fence 已提交;所有五命令、新 Run/child/delegation、 + // interaction 和 conversation delivery identity 的分配都必须在同一 project + // lock 内拒绝或延后,不能继续落到 predecessor session。 + rotation_fence_operation_id: Option, + // 与 rotation operation 的同名 marker 一致;operationId/marker 必须同时为空或 + // 同时存在,禁止出现 active-index fence 找不到唯一 operation recovery fact。 + rotation_fence_commit_marker: Option, + // 最近一次成功切换 active session 的 operationId。最终 active-session journal + // 必须把它更新为当前 operationId;任何 Rejected 都保留历史成功值,不得清空或覆盖。 + committed_rotation_operation_id: Option, +} +``` + +Session、handoff、target set 和 lineage 身份记录同样嵌入统一 durable envelope;`status`、`closedAt`、target-set commit marker 和 lineage 状态迁移必须通过 checksum 与 owner-generation fencing。`Prepared` recovery 只允许校验唯一 operation、predecessor/successor 预分配身份、expected session revision/epoch 和“active index 尚未引用该 operation”;它不能创建 successor session、manifest、handoff 或外部入队。若预期仍成立,可以在重新取得 owner/project lock 后继续同一 operation 的 fence commit;预期已漂移则写 `Rejected` tombstone。若 active index 已引用该 operation,但 operation 仍为 `Prepared`、两边 marker 缺失或不一致,则属于不可达的 torn commit,必须 `ReconciliationRequired`,不能把 Prepared 当作 fence 已成功。 + +session rotation 必须在项目锁内先校验当前 active index 没有其它 fence,分配唯一 `rotation_operation_id`、`successor_session_id` 并固化 `expected_session_revision + expected_run_creation_epoch`,首先持久化 `ProjectSupervisorSessionRotationRecord(Prepared)`;此时 `handoff_manifest_ref=None`、两个 commit marker 均为 `None`,active index 不变,旧 session 继续 `Ready`。随后重新读取并校验 active index、session revision、run creation epoch 和 owner generation,在同一 projection/rotation journal 线性化提交中同时写入 operation 的 `phase=FenceCommitted + rotation_fence_commit_marker` 与 active index 的 `rotation_fence_operation_id + rotation_fence_commit_marker`;任一侧缺失、operationId/marker 不一致或只能读到半边时进入 `ReconciliationRequired`,不得自动清除 fence。只有该 journal 可一致回读后 fence 才生效并投影 `HandoffInProgress`。fence 同时阻止新 requestId 的五命令、新 Run/retry successor/child/delegation/Runtime-owned schedule run、Interaction 和 conversation delivery identity 分配;不得先分配 identity 后再补 handoff。fence 前已进入 prepared/executing 的 answer/approve/cancel/resume/submit 必须停止创建新的下游 identity,并收束到 handoff-safe durable boundary:能证明权威结果则闭合原 command result;尚未产生未知外部结果但需要跨 session 继续的 operation 与其已分配 response/status/interaction identity 一并写入 continuation set;结果已未知则 rotation 进入 reconciliation。已分配 identity 的 response/status/public-event delivery 可补到稳定 commit marker、handoff-safe operation boundary 或稳定 `outcome-unknown`,但不得创建替代 message identity。所有目标 Run 到达不再产生未登记 session-bound identity 的 durable handoff barrier 后,才在同一锁序内按当前 active session 和 supervisor lineage 固化、排序并 digest 非终态 Run 集合,以及全部 Open/Resolving interaction、未终结 command/input envelope、DirectReply/RuntimeFinalReply、Runtime status/public-event delivery,分别写入 committed target set 与 continuation set,并提交 manifest;manifest 可读且 refs/count/digest 全部匹配后,才把同一 operation 的 `handoff_manifest_ref` 从 `None` 补为 `Some(manifestId)` 并推进到 `HandoffManifestCommitted`。`Prepared` 或 `FenceCommitted` 不得保存一个声称已提交但尚不可回读的 manifest ref。无法在有界期限内到达 barrier 时,按同一 operation 写 `Rejected` tombstone;若 fence 已提交则通过 journal 隔离 pending records、清除 active-index fence/marker 并递增 epoch,恢复旧 active session,不能不带 fence 强切。 + +manifest 的 `target_run_set_ref + target_run_count + target_run_set_digest + run_creation_epoch` 和 `continuation_set_ref + continuation_count + continuation_set_digest` 共同构成本次 handoff 的不可变目标边界,rotation fence 生效后没有新 session-bound identity 可以落到 predecessor;若检测到 epoch 漂移、barrier 后 predecessor record revision 变化或集合摘要漂移,rotation 必须失效并进入 reconciliation,不能静默排除新 Run、interaction 或 delivery。所有 `ProjectSupervisorRunHandoff` 必须一一匹配 manifest/target set 的 operation、manifest、predecessor/successor session、run、lineage、captured revision 和 digest;所有跨 session 继续的 command/input/interaction/delivery 必须一一匹配 continuation item 的 record kind/id/revision/session/run,不能恢复时重新扫描当前 ledger 集合。`ActiveSessionCommitted` marker 之前,旧 session 保持 `active`,不得先落 `superseded`;`Prepared` 单独存在时仍可按旧 session 受理写入,但提交 fence 前必须重新校验 revision/epoch,`FenceCommitted` 之后不得再受理新写命令或 session-bound identity。阶段按 `Prepared → FenceCommitted → HandoffManifestCommitted → SuccessorSessionCommitted → HandoffsCommitted` 推进;只有新 session、manifest、全部 Run handoff 与 continuation record 均可读且身份/lease/revision 校验通过后,才在同一 active-session journal commit 中切换 active index、将旧 session 标记 `superseded`、把 `activeIndex.committedRotationOperationId` 同步写为当前 `operation.operationId`、清除当前 operation 的 rotation fence/marker、把 `activeIndex.runCreationEpoch` 从 `operation.expectedRunCreationEpoch` 精确递增为 `expected + 1`,并提交 operation 的 `active_session_commit_marker`,再进入 `ActiveSessionCommitted`。该 journal 恢复和 `Ready` projector 必须同时校验 committed operationId、predecessor/successor session、source/successor session revision、manifest operation/session/epoch、active index epoch 和 active-session marker;不得只凭 activeSessionId 或最新文件判定成功。任一步崩溃或写失败都由 rotation operation 恢复,并严格区分两类 Rejected:`Prepared-before-fence Rejected` 仅在 active index 没有引用该 operation 时成立;恢复只幂等写 operation 的 `Rejected` tombstone,不清理任何 fence、不递增 active-index epoch、不创建或隔离本来就不允许存在的 successor/manifest/handoff/set,并原样保留 active index 既有 `committed_rotation_operation_id`,然后继续使用旧 active session。`FenceCommitted-after-fence Rejected` 只在 operation/index 的 operationId 与 fence marker 一致、active-session marker 尚未提交且没有未知外部结果时成立;恢复先隔离该 operation 的 pending successor/handoff/target-set/continuation,再在同一 journal 中清除且只清除该 operation 的 active-index fence/marker,把 epoch 从捕获值按规则递增一次,保留历史 `committed_rotation_operation_id` 不变,并继续使用旧 active session。operation/index marker 不一致、active-session marker 已提交或结果未知时不得走 Rejected,统一 `ReconciliationRequired`。active-session marker 之后发现 committed operationId、session/manifest/epoch 关系、任一集合/manifest、digest、handoff/continuation 缺失或不一致,同样进入 `ReconciliationRequired`,保留 fence/commit 证据,禁止两个 session 控制或继续同一 record,不能按“最新文件”猜成功。新 session 只有在同一 `supervisorLineageId`、项目归属、control lease、单调 revision 和对应 handoff/continuation proof 全部成立时,才可以控制旧 session 遗留 Run 或继续旧 session record;旧 session 永远不能重新变为 active。没有 proof、lineage 不匹配、revision 回退或双窗口竞争时返回 `TARGET_STALE`,不能根据 `conversation_session_id`、路径或时间猜测归属。迁移前已有 session 必须先生成显式 migration handoff/continuation fixture,无法证明 lineage 的旧 Run、command/input envelope、interaction 或 delivery 暂停在 `needs-reconciliation`,不自动开放 answer/approve/cancel/resume。 + ### 1.1.2 Public 摘要枚举与投影提交合同 `status`、`stage`、`waitingOn`、`nextStep` 和 `outcome` 是公开稳定枚举,不向 Consumer 透传内部 phase,也不要求 Consumer 解析自然语言。V1 至少冻结以下值: | 字段 | 稳定值 | |---|---| -| `status` | `idle`、`queued`、`running`、`waiting`、`completed`、`failed`、`cancelled`、`needsReconciliation` | -| `stage` | `idle`、`planning`、`executing`、`coordinating`、`waitingForUserInput`、`waitingForPolicyApproval`、`waitingForDeveloperApproval`、`waitingForTimer`、`reconciling`、`completed`、`failed`、`cancelled` | -| `waitingOn` | `none`、`userInput`、`policyApproval`、`developerApproval`、`timer`、`runner`、`reconciliation` | -| `nextStep` | `none`、`submitIntent`、`answerInteraction`、`approveInteraction`、`cancelRun`、`resumeProject`、`waitForRunner`、`reconcile` | +| `status` | `idle`、`preparing`、`queued`、`running`、`waiting`、`paused`、`cancelling`、`completed`、`failed`、`cancelled`、`terminalPending`、`needsReconciliation` | +| `stage` | `idle`、`preparing`、`publicStatusPending`、`planning`、`executing`、`coordinating`、`waitingForUserInput`、`waitingForUserApproval`、`waitingForPolicyApproval`、`waitingForDeveloperApproval`、`waitingForTimer`、`waitingForRunner`、`pausedByUser`、`cancelling`、`finalizing`、`reconciling`、`completed`、`failed`、`cancelled`、`terminalPending` | +| `waitingOn` | `none`、`publicStatusMessage`、`userInput`、`userApproval`、`policyApproval`、`developerApproval`、`timer`、`runner`、`reconciliation` | +| `nextStep` | `none`、`waitForPublicStatus`、`submitIntent`、`answerInteraction`、`approveInteraction`、`cancelRun`、`resumeRun`、`retryTerminalRun`、`waitForRunner`、`reconcile` | | `outcome` | `none`、`success`、`failure`、`cancelled`、`unknown` | -`waitingOn` 和 `nextStep` 只用于展示提示,任何可执行按钮必须来自当前 Public `interactions` 或明确的五命令能力;Consumer 不得根据这两个字段自行拼装命令。文案由 Shell 根据稳定值本地化并做长度、路径、Provider 原文和敏感信息过滤;文案变化不改变协议状态,不单独推进 `snapshotRevision`。 +`waitingOn` 和 `nextStep` 只用于展示提示,任何可执行按钮必须来自当前 Public `interactions` 或 `commandCapabilities`;专业组 `recovery` 只是与这两处互相校验的展示索引,不能独立构造命令。Consumer 不得根据状态、错误文案或这两个字段自行拼装命令。capability 是 Snapshot 在同一 revision 上投影的精确命令目标:无 capability 就禁用入口,有 capability 才原样提交其中的 session/run/revision/interaction target;Shell 仍在写锁内重读事实,过期 capability 返回 `TARGET_STALE`。`submit_intent.conversationOptions` 按可用组合而不是多个独立白名单字段输出,避免 Consumer 误组合 intentKind、runProfile 和 entryBinding;resource identity 仍由既有资源管理 read model 提供。该 capability 只声明 Conversation 请求形态可提交,不预先承诺 interaction kernel 的 `execute` 一定可转为 start/steer:active Project Supervisor 根 Run 已绑定冻结 Goal Contract 时仍可保留合法 Conversation option 以承接 direct reply,但 Shell 不投影、也不接受任何从该 capability 推导出的 replacement-steer 能力;若同一请求在锁内被判定为 `execute`,稳定返回 `TARGET_BUSY` 并指向显式 Goal management mutation。短暂 Public read 失败时,Consumer 可以继续显示上一份已验证 Snapshot 并明确标记 stale;除上一份精确 `cancel` capability 外所有写入口禁用。为避免失去用户止损能力,stale cancel 可继续提交原 session/sessionRevision/run target,但它只在最后可信 Snapshot 的 `sessionContext=Ready` 且本地没有观测到 `HandoffInProgress`/draining/reconciliation 时作为例外;Shell 必须先在 project lock 内检查当前 phase/fence,再重新授权和锁内复核,phase gate 优先时返回 `TARGET_BUSY`/`TARGET_STALE`,目标变化返回 `TARGET_STALE`,transport/owner 不可用则返回 `TRANSIENT_UNAVAILABLE/OWNER_UNAVAILABLE`。已有 cancel request 的结果读回仍可进行,但不得借 stale capability 创建第二个 cancel operation。这解决“nextStep 不可路由、但 UI 又需要启停 submit/cancel/resume/retry”以及“读短暂失败时仍要能尝试取消”的矛盾。文案由 Shell 根据稳定值本地化并做长度、路径、Provider 原文和敏感信息过滤;Snapshot hash 必须比较除 `snapshotRevision/eventCursor/updatedAt` 这些提交元数据外的所有实际序列化 Public 业务字段,包括规范化展示文案,不能出现业务响应字节变化但 `snapshotRevision` 不变。只有不进入 DTO 的本地化资源或渲染变化不推进 revision。 Public projector 的一次提交以 `projectId` 为边界,在 `project execution owner → supervisor project lock → projection journal` 的锁序内完成: @@ -157,6 +969,16 @@ Public projector 的一次提交以 `projectId` 为边界,在 `project executi owner record 的取得、续租和释放使用同一项目锁内的 CAS;generation 每次成功换主递增,旧 generation 的写入返回 `OWNER_FENCED`,不得覆盖新 owner 的 ledger、Runtime state 或 projection。lease 到期不能只凭本地时钟判定可接管:新进程必须先取得 owner,再在锁内复核 Runner drain、GUI-owner 和 manifest projectId。时钟只用于 lease 超时提示,CAS/generation 才是权威。 +调用 capability 与生命周期 owner 是两套轴: + +| 调用方 | 可读 | 可写/自动动作 | +|---|---|---| +| Public User GUI/CLI | Public Snapshot、Public event、自己的 command result/conversation | `submit_intent/answer`、用户 audience 的 `approve`、cancel、ContinueRun/RetryTerminalRun;必须通过项目权限和当前 active session 校验 | +| Developer capability | Public + Developer Snapshot | Developer audience ToolApproval、显式 ReconcileRun 和受信任管理面;仍受 project owner/fencing 约束 | +| Runner internal capability | Public projector、durable runnable/recovery records | 只执行已持久 wake/recovery intent;不能伪装 Public Consumer 生成用户 answer/approve/requestChanges | + +GUI-owner 是 GUI 启动 Runner 的生命周期 lease,不是 GUI 的业务特权。V1 明确**不提供无 GUI CLI 写入的 headless control lease**:CLI 可以读取 Public Snapshot、事件和自己的 command result;CLI 写入只有在已存在且有效的 GUI-owner/Runner 上转发时才可执行,否则在写入 `prepared` 前返回 `OWNER_UNAVAILABLE`。不得为让 CLI “可用”而绕过 GUI-owner,也不得把本轮协议平等表述成无 GUI 常驻 Runner;后续若实现 headless lease,必须提升/协商生命周期能力合同并新增 fencing fixture。 + 调度 worker 的顺序固定为:取得/续租 project owner → 检查 Runner drain 与 GUI-owner → 从 durable wake/runnable 索引取一项 → 在 dequeue 前再次校验 owner generation 和项目状态 → 以同一 operation identity 调用 Runtime。任何检查失败都不得先 dequeue 后补救;旧 worker 在失去 generation 后的结果一律按 fencing 处理并进入既有 reconciliation 路径。 ### 1.2 出向事件、有序性与重连 @@ -195,11 +1017,11 @@ V1 将公开事件刻意收窄为项目级失效通知。`agentId/sessionId/runI | 命令 | 吸收的旧命令 | V1 业务输入 | |---|---|---| -| `submit_intent` | CLI `Reply/Execute/Resume` 与 `start_*` / `steer_*` | 用户消息、目标 Project Supervisor sessionId 与公开 runProfile;Shell 判定 direct reply/start/steer/resume,source 由 transport 派生 | +| `submit_intent` | CLI `Reply/Execute` 与 `start_*` / `steer_*` | `Conversation` payload 为用户消息、目标 Project Supervisor sessionId、入口 `intentKind` 与公开 runProfile;`BuiltinCommand` payload 为 command line 与 expected parser version;Shell 按 parser/intent policy matrix 判定 direct reply/start/steer/reject,source 由 transport 派生 | | `answer` | `answer_*_user_input` | `interactionId + responseId + answers` | -| `approve` | `confirm_*` / `reject_*` | `interactionId + responseId + decision(approve/reject)` | +| `approve` | `confirm_*` / `reject_*` | `interactionId + responseId + decision(approve/reject/requestChanges)`;`requestChanges` 可携带有界意见 | | `cancel` | `cancel_*` | 当前公开 Project Supervisor `runId` | -| `resume` | `resume_*` / `retry_*` / `schedule_*` | 项目级恢复意图;若需确认则只创建/返回 InteractionRequired | +| `resume` | 旧 `resume_*` / `retry_*` 生命周期入口 | 明确 tagged intent:继续同一 durable run、针对终态失败创建 successor run,或由受信任 capability 进入人工 reconciliation;timer/lane/schedule 属于 Runner 内部 wake,不是公开 resume | 所有写命令都包含: @@ -214,9 +1036,34 @@ struct InteractionResponseMeta { interaction_id: String, response_id: String, expected_interaction_revision: u64, + session_id: String, + expected_session_revision: u64, +} + +enum AgentRuntimeIntentKind { + CreateFromPrompt, // createFromPrompt + ContinueProject, // continueProject + CreateFromTemplate, // createFromTemplate + ImportExistingDesign, // importExistingDesign +} + +// Shell 私有路由审计;不要求 Consumer 解析 command name。 +enum AgentRuntimeIntentRoute { + BuiltinCommand { + parser_version: String, + command_name: String, + }, + Conversation, +} + +enum ApprovalDecision { + Approve, // approve + Reject, // reject,终止当前审批链 + RequestChanges, // requestChanges,带意见退回同一工作链重做 } struct AgentRuntimeCommandResponse { + schema_version: String, request_id: String, request_fingerprint: String, replayed: bool, @@ -224,22 +1071,40 @@ struct AgentRuntimeCommandResponse { result: T, } +enum IntentDisposition { + DirectReply, // directReply + Start, // start + Steer, // steer:可在当前安全 Provider 边界中接管 + SteerDeferred, // steerDeferred:保留当前 action/approval,receipt/barrier 后消费 +} + enum AgentRuntimeCommandAck { IntentAccepted { - disposition: IntentDisposition, // Reply | Start | Steer | Resume + disposition: IntentDisposition, // directReply | start | steer | steerDeferred accepted_run_id: Option, + steer_id: Option, // Shell prepared 后为 Steer 映射的现有 steer ledger 身份 response_message_id: Option, + runtime_status_message_id: Option, // Start 才有;Runtime-owned status message + }, + InteractionAccepted { + interaction_id: String, + decision: Option, + follow_up_interaction_id: Option, + }, + CancelAccepted { run_id: String, cancel_operation_id: String }, + ResumeAccepted { + mode: AgentRuntimeResumeMode, // continueRun | retryTerminalRun | reconcileRun + predecessor_run_id: String, + accepted_run_id: Option, // RetryTerminalRun 才分配 successor runId + affected_run_count: u32, }, - InteractionAccepted { interaction_id: String, decision: Option }, - CancelAccepted { run_id: String }, - ResumeAccepted { affected_run_count: u32 }, InteractionRequired { interaction_id: String, interaction_revision: u64 }, } -struct AgentRuntimePublicError { +struct AgentRuntimeCommandError { schema_version: String, - code: String, - kind: AgentRuntimePublicErrorKind, + code: AgentRuntimeCommandErrorCode, + kind: AgentRuntimeCommandErrorKind, retryable: bool, message: String, request_id: Option, @@ -250,20 +1115,101 @@ struct AgentRuntimePublicError { } ``` -`submit_intent` 要求 Consumer 先经现有会话管理面取得明确的 Project Supervisor `sessionId`;Shell 锁内验证该 session 属于本项目/当前 Supervisor,active session 已变化则返回 `TARGET_STALE`,本轮不把会话 CRUD 隐式塞入 Runtime 命令。`source` 由受信任 transport 固定映射,Consumer 不得自报任意 source;`runProfile` 使用公开白名单枚举并在锁内校验当前项目支持。Shell 复用现有 interaction kernel 决定 direct reply/execute/resume,再对 execute 决定 start/steer;direct reply 仍只写 conversation/response stream,不伪造 Runtime Snapshot 变化,其稳定 responseMessageId 预先写入 request ledger。显式 `resume` 命令服务按钮/自动化的结构化恢复意图,自然语言“继续”也可由 `submit_intent` 路由到同一内部实现。 +`submit_intent` 仍必须保留现有项目的 `/` 内置命令语义,但解析权不能留在 GUI/CLI。Shell 在 interaction kernel 和 intent policy matrix 之前,使用 capability 声明版本的同一 Rust parser 对规范化 message 做一次确定性路由:普通文本只能进入 `Conversation`;不含 absolute host locator、`file://` 或本地句柄的项目级 slash 才能进入 `SubmitIntentPayload::BuiltinCommand`。经项目相对路径安全校验的 resource path 不属于 host locator,但仍必须拒绝绝对路径、`..` 和 symlink escape。路径、窗口和全局配置等本地管理命令必须在同一 parser 中先产出 `LocalManagementRoute`,先进入 `AgentRuntimeLocalManagementOperationRecord`,不能伪装成 `submit_intent` 或用空 `projectId/sessionId` 进入 Runtime ledger;若该 local route 在解析后确实产生项目级领域副作用,则必须在同一 project lock 下建立关联的 `AgentRuntimeBuiltinManagementOperationRecord`,把 `projectOperationRef` 双向固化,但仍不得伪造 conversation user message 或进入 Public conversation。BuiltinCommand 只提交 `command_line + expected_parser_version`,不再伪装成 `continueProject + runProfile`,也不携带 attachments、entry binding 或 Runtime intent/profile;项目级 BuiltinCommand 才进入 `submit_intent` 的 command ledger。命令需要的 project/session scope 由 Shell 在锁内从当前 capability/管理面取得。expected parser version 必须是调用方从当前 capability 原样读回的版本:格式/版本不支持返回 `PROTOCOL_VERSION_UNSUPPORTED`,与当前 capability 不一致返回 `TARGET_STALE`;一旦写入 prepared,parser version、规范化 command line 和 route 固化在 ledger,恢复/重放不得重新按新 parser 解释同一文本。未知命令仍可产生有界、持久 direct reply,不落成 Runtime task,但只有在 host-locator/path safety 检查通过且不含绝对路径、`file://` 或无法分类的本地句柄时才允许保存原 command line;未知命令携带这类输入必须在 ledger 前 `INVALID_REQUEST`,不能以 direct reply 回显或持久化原文。只读/说明类内置命令可走 `DirectReplyDelivery`;需要预览、文件、工具或其它副作用的内置命令必须创建既有类型的稳定 Interaction,由 `answer/approve` 或受信任管理面继续,不能在 parser 内直接绕过确认;若最终需要 start/steer/resume/cancel,必须调用同一 Shell 内部能力与 project lock/ledger,不得回退旧公开命令。存在 Open/Resolving interaction 时,内置只读命令是否可回复、会产生新 interaction/副作用的命令是否 `TARGET_BUSY`,由逐命令 fixture 冻结;V1 禁止嵌套第二个 User interaction。这样 `/preview` 仍只生成 `preview.start` 确认而不会成为自主构建任务,GUI/CLI 也不再各自维护 slash 分支。 + +现役 slash catalog 按当前 `apps/ai-game-creator-shell/src/App.tsx` 与 `projectSummaryConstants.ts` 冻结为以下路由;新增命令必须先更新 Rust parser、capability catalog、GUI/CLI golden fixture 和本表,不能落入未知命令的偶然行为: + +| 路由类别 | 现役命令(完整命令名;带参数的参数形态保持现有帮助文案) | 协议行为 | +|---|---|---| +| 只读 direct reply | `/help`、`/capabilities`、`/能力`、`/audit`、`/审计`、`/status`、`/llm-status`、`/llm-routes`、`/brief`、`/goal`、`/progress`、`/spec`、`/mvp`、`/pitch`、`/demo`、`/rules`、`/tutorial`、`/mobile`、`/compatibility`、`/accessibility`、`/localization`、`/performance`、`/polish`、`/risks`、`/blockers`、`/ready`、`/evidence`、`/deps`、`/revise`、`/privacy`、`/audience`、`/invite`、`/bug-report`、`/survey`、`/cover`、`/screenshots`、`/trailer`、`/faq`、`/post`、`/store`、`/media-kit`、`/release-notes`、`/known-issues`、`/criteria`、`/groups`、`/balance`、`/budget`、`/qa`、`/changes`、`/review`、`/context`、`/timeline`、`/handoff`、`/next`、`/guide`、`/plan`、`/todo`、`/publish`、`/listing`、`/playtest`、`/test-plan`、`/feedback`、`/retention`、`/share`、`/tasks`、`/agents`、`/agent-conversations`、`/agent-memories`、`/trace`、`/loop`、`/agent-status`、`/history`、`/files`、`/assets`、`/credits`、`/art`、`/audio`、`/artifacts`、`/run-artifacts`、`/passes`、`/runs`、`/run-files`、`/internals`、`/logs`、`/checkpoints`、`/diff `、`/policy`、`/read `、`/exports`、`/preview-status`、`/memory [scope]`、`/commands`、`/limited-commands` | 按 parser route 选择交付:`RuntimeBuiltinCommand` 的 direct-reply 分支先持久化一次用户 command line,再提交 `DirectReplyDelivery`;`LocalManagementRoute` 改用 `AgentRuntimeLocalManagementResponse::Succeeded { meta, result: Reply }`,不写 Public conversation/response stream;两者均不创建 Runtime run。文件/Runtime 摘要按各自 read capability 脱敏;有 Open/Resolving interaction 时不得偷偷推进副作用。 | +| 交互或管理动作 | `/project <绝对路径>`、`/generate `、`/draft `、`/index`、`/checkpoint`、`/smoke`、`/restore `、`/policy-deny `、`/policy-allow `、`/policy-confirm `、`/policy-auto `、`/agent-policy-deny `、`/agent-policy-allow `、`/agent-policy-confirm `、`/agent-policy-auto `、`/asset-register [kind] [mediaType]`、`/run`、`/export`、`/open-project`、`/show-project`、`/switch-project`、`/open-preview`、`/preview-stop`、`/remember [scope] `、`/memory-set [scope] `、`/forget-memory [scope]`、`/canvas `、`/sync-canvas-project `、`/generate-art `、`/import-canvas-asset `、`/import-canvas-export ` | `/project`、`/open-project`、`/switch-project`、`/config` 是本地管理面;其中 `/project <绝对路径>`、`/import-canvas-export ...` 等 host-locator 参数只能进入 trusted local locator resolver,不能进入 Public conversation、Snapshot、事件、错误或 request fingerprint 原文;`/asset-register`、`/import-canvas-asset`、`/read` 的 path 必须先证明是项目内安全相对路径,不能接受绝对路径、`..` 或符号链接逃逸。其它命令按现有管理面权限/CAS/确认合同执行。资源登记、文件导入、记忆写入和 External Editor 调用都必须有稳定 operation identity,失败/重放不得生成第二副作用;不得把这些管理动作伪装成 Runtime task。`/diff`、`/policy`、`/read` 已列入只读 direct reply,不创建 Interaction。 | +| CLI-only 管理/观察别名 | `/resume`、`/goal <目标>`、`/goal status`、`/goal edit <目标>`、`/goal pause`、`/goal resume`、`/goal clear`、`/compact`、`/mcp`、`/quit`、`/exit` | 这些命令来自 `swarm_cli/input.rs`,必须纳入同一 Rust parser 的显式 catalog,不能依赖 GUI 未知命令 fallback。精确 `/goal` 已在上一行作为 direct read 列出;`/goal status` 同样是 direct read。`/resume` 是既有 Runtime recovery scan + observe 入口,不等于模糊的 `ResumeCommand`,只能按当前 session/run capability 恢复并观察;`/goal <目标>`、`/goal edit <目标>`、`/goal resume` 是带 Goal ID+revision CAS 的管理 mutation,其中现有 CLI 会在 mutation 成功后继续等待/启动该 Goal 对应 Runtime turn,迁移后必须通过同一 Shell start/steer/lineage handler,不能旁路创建第二个 Supervisor run;`/goal pause`/`/goal clear` 只改变 Goal 状态,不自动取消或终止 Runtime;`/compact` 只调用既有 context-compaction 管理合同,不创建 task/provider;`/mcp` 是 direct reply;`/quit`/`/exit` 只关闭 CLI 观察 transport,不写 conversation/Runtime ledger。 | +| 本地配置/窗口动作 | `/config` | 只打开现有独立配置面板,不写 Runtime ledger、不持久化为 Runtime task;配置保存继续走现有 config CAS/secret boundary。 | +| Agent run control 管理动作 | `/agent-kill`、`/agent-retry`、`/agent-resume [说明]` | 这些 slash 在当前 `App.tsx` 中调用既有 `control_agent_run`:`agent.kill` 只更新 legacy run trace/activity/output/context,`agent.retry` 和 `agent.resume` 从最近 trace 的 goal 启动新的本地生成 run;它们不能伪装成五命令的 `cancel`、`RetryTerminalRun` 或 same-run `ContinueRun`。parser 只生成带精确 legacy trace target、action 和有界 detail 的 management operation;如果迁移后确实要控制正式 Runtime,必须另有 capability 显式选择五命令,并写 `RunLineageRecord`,否则只返回管理动作结果。`/agent-resume` 的说明作为 prompt detail 保存,不能从文字猜 Runtime mode。 | +| Preview sibling contract | `/preview` | 只进入既有 `preview.start` authorization/confirmation 链;不得进入 `submit_intent` Conversation、自动创建 Supervisor run 或从 `nextStep` 猜授权。`/run` 若同时请求自检和预览,先执行既有静态检查,预览部分仍复用同一 `preview.start` 链;“准备启动预览”的 UI 提示只能来自该 Interaction/管理面状态,不再额外持久化一条 assistant ack。 | + +关键命令必须同时满足以下“单一协议落点”矩阵;表外别名只能先进入同一 parser catalog,不能由 GUI/CLI 增加第二种解释: + +| 命令族 | parser route | scope | durable 事实 | delivery / recovery | +|---|---|---|---|---| +| `/help`、`/llm-status`、`/mcp` | `LocalManagementRoute` | 无 project/session | 纯 direct reply;`/mcp` 不落 ledger | `AgentRuntimeLocalManagementResponse::Succeeded { meta, result: Reply }`;不读写 Public Snapshot | +| `/config` | `LocalManagementRoute` | 无 project/session | 既有 config CAS/secret boundary | 独立配置面板结果;不进入 Runtime ledger 或 conversation | +| `/quit`、`/exit` | `TransportOnly` | 无 project/session | 无 durable record | `TransportClosed`;不写 conversation/Runtime ledger | +| `/goal`、`/goal status` | `LocalManagementRoute` | 当前 project/session(缺失时只返回 scope 错误) | Goal read model;不创建 Runtime operation | local/CLI readback;不触发 Runtime turn | +| `/goal <目标>`、`/goal edit/pause/resume/clear` | `LocalManagementRoute` | project + active session + Goal ID/revision | 先写 `AgentRuntimeLocalManagementOperationRecord`(无 conversation user message),需要项目副作用时再以唯一 `projectOperationRef` 关联 `AgentRuntimeBuiltinManagementOperationRecord`;会产生 Runtime turn 时关联 `lineageRef`;现有冻结 Goal Contract 根 Run replacement primitive 仅允许由该管理 route 调用 | operation readback;同 requestId 重放,未知结果不重做;Runtime admission/replacement 必须走同一 project lock、operation identity 和 lineage,普通 `submit_intent` 不得复用 | +| `/resume` | `LocalManagementRoute` | project + session/run recovery target | recovery-observe operation;不创建 Public `ResumeCommand` | 按精确 session/run/revision 扫描并观察;同一 operation identity 恢复,不能借自然语言或最近 run 猜 target | +| `/agent-kill`、`/agent-retry`、`/agent-resume` | `LocalManagementRoute` | project + 精确 legacy trace target | legacy management operation;retry/resume 只有取得正式 lineage 后才关联 successor | operation readback;不得映射为 cancel/ContinueRun/RetryTerminalRun | +| `/project `、`/import-canvas-export ` | `LocalManagementRoute` | 初始无 project;解析后绑定 project | `AgentRuntimeLocalManagementOperationRecord`,解析后关联 project operation | local resolver/OS handle 结果;路径原文不入 Public/Runtime delivery,local→project link 必须唯一 | +| `/asset-register`、`/import-canvas-asset` | `RuntimeBuiltinCommand` 的 `ManagementAction` 分支 | project + safe project-relative path | resource/External Editor 既有 CAS + project management operation | project operation readback;相对路径不等于 host locator,仍须拒绝绝对路径、`..` 和 symlink escape | +| `/read `、`/diff`、`/policy` | `RuntimeBuiltinCommand` 的 direct-reply 分支 | project read capability | command ledger + 一次 user message;不创建 Runtime run | `DirectReplyDelivery`;超时按同 requestId 读回,不把读取结果写入 Snapshot | + +该矩阵明确:没有 project/session 的命令不伪造五命令 `meta.projectId`,有 project scope 的管理命令也不伪装成 Runtime run;resource path 只有在项目相对路径安全校验通过后才可属于 RuntimeBuiltinCommand,不得把 absolute host locator、`file://` 或本地句柄混入该 route;任何命令若无法唯一落到 route、scope、durable record 和 delivery/recovery 四项,必须在协议冻结前补充 fixture,而不是由 Consumer fallback。local/global command 在调用前必须从受信任 transport 取得 `AgentRuntimeLocalManagementCapability`,并原样提交 `capabilityId + expectedParserVersion`;Shell 重新校验 capability scope、project/session/target revision 和 principal。capability 不存在返回 `CAPABILITY_NOT_FOUND`,targetRef 漂移返回 `TARGET_STALE`,parser version 不支持返回 `PROTOCOL_VERSION_UNSUPPORTED`,不得从当前窗口、最近 Goal 或最近 run 猜目标。local operation record 必须保存 originatingCapabilityId(仅审计)与 authorizationPrincipalRef/authorizationScopeFingerprint;读回时使用当前 capability 重新授权,不要求 capabilityId 跨 owner boot 稳定;不同权限调用方不能借相同 requestId 读取本地路径或管理结果。 + +项目级 management route 在自身领域 CAS 之前先写 `AgentRuntimeBuiltinManagementOperationRecord`;Goal mutation 若会产生后续 Runtime turn,operation result 必须同时保存 `goalId + goalRevision + resultingRunId/lineageRef`,并由同一 project lock 串起 Goal CAS 与 Runtime admission。现有 `goal_contract_root_steer`/replacement primitive 只作为该 Goal management operation 的内部实现:必须复用同一 `projectOperationRef`、预分配的 resulting run identity 和 lineage,不能从 `submit_intent` 的 `Steer` disposition 进入,也不能把 replacement 回显成 same-run steer;同 requestId/同 argument fingerprint 回放已保存结果,异指纹返回 `IDEMPOTENCY_KEY_REUSED`,`prepared/executing` 返回 `COMMAND_IN_PROGRESS`,`outcome-unknown` 禁止重新执行;核对流程只能沿同一 `projectOperationRef` 推进 `needsReconciliation`,并写入 `reconciliationOperationId` 后闭合为确定的 `succeeded/rejected`。领域 writer 成功但 operation result 未闭合时由 owner 按 operation identity 补结果;不能以现有 UI pending state 代替 durable operation。 + +Agent run control management action 的 prepared payload 必须至少保存 `legacyTraceIdentity`(项目、trace/run 标识及其 revision/digest)、`action=kill|retry|resume`、`detail`(仅 resume,按同一 message 上限过滤)和新 generation operation identity。`kill` 的成功只表示 legacy trace 已写入 killed,不得向 Public Runtime Snapshot 投影 `cancelled`;`retry/resume` 只有新 generation 已取得正式 Runtime lineage 并完成唯一 predecessor/successor 记录后,才能向 Runtime read model 暴露 successor,否则只作为管理面结果。旧 trace 缺失、被替换或 revision/digest 漂移时返回 `TARGET_STALE`,禁止对“最近 run”重新猜 target。 + +所有进入项目/对话协议的 slash route 都拒绝 attachments、entry binding 和自然语言补偿;参数缺失/重复/未知字段在对应 ledger 前 `INVALID_REQUEST`,未知命令则仅生成固定长度 direct reply。进入 `submit_intent`/DirectReplyDelivery 的 command line,其用户消息只持久化一次,后续 delivery/operation 均引用该 `conversationUserMessageId`;`LocalManagementRoute`/`LocalTransportReply` 不写 Public conversation,原始 path 只留在 resolver/OS handle 边界内;`/quit`、`/exit` 这类 transport-only 命令是显式例外,不写 conversation/Runtime ledger。GUI 和 CLI 必须使用同一 Rust parser 和相同规范化/错误合同,公共命令得到相同 route、interaction kind、operation identity 规则和副作用计数;CLI-only 别名只能通过上表显式映射到不同 management/observation route,不能在 GUI/CLI 各自增加隐含解析分支。 + +`submit_intent` 的 `Conversation` payload 要求 Consumer 先经现有会话管理面取得明确的 Project Supervisor `sessionId + expectedSessionRevision`;Shell 锁内验证该 session/revision 属于本项目/当前 Supervisor,active session 已变化则返回 `TARGET_STALE`,本轮不把会话 CRUD 隐式塞入 Runtime 命令。BuiltinCommand 的项目级命令由 Shell 根据当前 capability/管理面 scope 取得同一上下文;无项目上下文的 `/help`、`/config`、`/llm-status` 等本地/全局命令不得伪造 sessionId。请求中的 `intentKind` 是 Consumer 对用户实际入口的业务陈述,不是权限或 source 主张;V1 固定为 `createFromPrompt`、`continueProject`、`createFromTemplate`、`importExistingDesign`,并与执行方式正交:`runProfile` 只表示怎么执行,`intentKind` 表示从哪个业务入口开始。Shell 在锁内按项目 manifest、当前 session 和策略校验 intent,可接受、拒绝或将其归一到受支持的内部分支;Consumer 不能自报 source。受信任 transport 使用 `(transport, intentKind) -> source` 的后端映射,映射和否决权始终在 Shell。这样同一 GUI 的不同入口即使 `message` 与 `runProfile` 相同,请求也不会逐字节相同。Shell 复用现有 interaction kernel 只决定 direct reply/execute,再由冻结 intent policy matrix 对 execute 决定 start/steer/reject;direct reply 仍只写 conversation/response stream,不伪造 Runtime Snapshot 变化,其稳定 responseMessageId 预先写入 request ledger。显式 `resume` 命令服务按钮/自动化的结构化生命周期意图;自然语言“继续”只作为普通 message 参与 reply/steer,不得触发 ContinueRun、RetryTerminalRun 或 reconciliation。现有 `agent/interaction.rs` 的 `runtime_resume` tool / `AgentInteractionAction::Resume` 属于迁移前内部分支:P5 必须禁止模型输出直接升级为 Public ResumeCommand;若保留该内部 action,只能在同一 Shell 内归一为普通 steer/reply 或显式 recovery-observe,并保留原 request/message identity,不能绕过新 capability、ledger 和 target revision。 + +V1 的并发不变量是:一个 projectId 同一时刻最多只有一个非终态 Project Supervisor run;专业 Agent/child run 由该 Supervisor 管理,不计作第二个公开 Supervisor。`BuiltinCommand` 先按上一段冻结规则收束,不进入下面的 Runtime matrix;所有 `Conversation` 路由的 `submit_intent` 在 project lock 内串行读取当前 run,再按已注册、带版本的 intent policy matrix 决定 `directReply | start | steer | reject`。对于已由 interaction kernel 判定为 `directReply` 的消息,Shell 先执行下表的“开放交互门禁”:存在 Open/Resolving interaction 时统一 `TARGET_BUSY`,否则不产生 Runtime run,直接走第 1.5 节交付合同;只有 `execute` 分支进入下表。这样 direct reply 不是 Consumer 自己绕过 matrix 的第二条路径。 + +V1 execute 分支的 intent policy matrix 冻结如下;表内每格只有一个 disposition,`steer` 表示在同一 Project Supervisor run 内调整当前执行,必须保持原 runId,不得创建 replacement 或第二个并行 Supervisor run: + +| `intentKind` | 无 run | 活动 run(`running`) | 等待交互(`waiting`) | 已有终态(`completed/failed/cancelled`) | `needs-reconciliation` | +|---|---|---|---|---|---| +| `createFromPrompt` | `start` | `steer` | `TARGET_BUSY` | `start` | `NEEDS_RECONCILIATION` | +| `continueProject` | `start` | `steer` | `TARGET_BUSY` | `start` | `NEEDS_RECONCILIATION` | +| `createFromTemplate` | `start` | `TARGET_BUSY` | `TARGET_BUSY` | `start` | `NEEDS_RECONCILIATION` | +| `importExistingDesign` | `start` | `TARGET_BUSY` | `TARGET_BUSY` | `start` | `NEEDS_RECONCILIATION` | + +`createFromTemplate` 必须携带且锁内复核 `entry_binding=Template`;`importExistingDesign` 必须携带且锁内复核 `entry_binding=ExistingDesign`;另外两种必须省略 `entry_binding`。绑定的 revision/digest 不存在、已失效或不能证明来源时返回 `ARTIFACT_BINDING_UNAVAILABLE`,而不是从 `message` 猜测模板/设计。所有 `start` 的 `runId` 只在 request ledger 的 `prepared` 阶段分配;matrix、绑定校验和并发竞争均纳入 P0 每格 fixture。 + +上表的 `活动 run/等待交互` 还必须按现有 V1.13/V1.23 barrier 细分,不能把所有 `waiting` 当成同一种状态: + +| durable barrier | `createFromPrompt/continueProject` | 其它新入口 | 规则 | +|---|---|---|---| +| active root Run 已绑定冻结 Goal Contract | `TARGET_BUSY` | `TARGET_BUSY` | 该行优先于其它 barrier;普通 `submit_intent` 不调用现有 replacement steer,返回稳定错误并引导使用 `/goal edit` 等显式 Goal management mutation;interaction kernel 判为 direct reply 时仍按开放交互门禁交付 | +| planning/final-reply Provider await | `steer` | `TARGET_BUSY` | 只中断可中断的纯 Provider await;不 abort worker,不重放 action | +| pending confirmation / approved / executing action | `steerDeferred` | `TARGET_BUSY` | 保留原 action fingerprint、确认和 process session;terminal receipt 后再消费 steer | +| `waiting-for-user-input` | `TARGET_BUSY` | `TARGET_BUSY` | 必须精确 `answer`,不能用普通自然语言绕过问题 | +| `waiting-for-isolated-join` | `steerDeferred` | `TARGET_BUSY` | 保留 join barrier,不创建 joinRun/旁路 Provider | +| timer/lane/schedule wait | `steerDeferred` | `TARGET_BUSY` | 只写 steer ledger,唤醒后按 cursor 消费,不把 timer 当作用户 resume | +| `pausedByUser` / `cancelling` / `finalizing` | `TARGET_BUSY` | `TARGET_BUSY` | 必须分别使用 ContinueRun、cancel 读回或等既有 finalization 收束 | + +当 matrix 选择 `steer` 时,Shell 必须先在 project lock 内证明 active root Run 未绑定冻结 Goal Contract;命中冻结合同一律以 `TARGET_BUSY` 持久化业务拒绝,不能进入现有会取消旧树并创建 replacement Run 的特殊 steer primitive。通过该门禁后,Shell 才在同一 `prepared` 记录中由 `sha256(RFC 8785 canonical JSON(["runtime-steer", projectId, requestId, targetRunId]))` 确定性派生并保存 `steerId`,同时保存并复核 V1.13 要求的 `agentId/taskId/sessionId/runId/source` 完整身份;该 seed 只负责稳定生成公开 steerId,不能替代 V1.13 的 source/audience 校验。再通过现有 V1.13 `steer` ledger 的 `prepared → conversation-persisted → queued → applied → closed` 合同提交;重试永远复用这个派生身份。`requestId` 负责公开命令幂等,`steerId` 负责同一 run 的追加指令身份,二者不能互相替代。Steer 的容量、正文脱敏、`appliedSteerCursor`、Provider 中断和 finalization 竞态沿用 V1.13;已有 action/confirmation/process session/side effect 不因普通 steer 被暗中取消,旧计划只能在安全边界失效。 + +`source` 只用于受信任归因、权限和审计,不能替代 intentKind 或改变 start/steer/retry 业务路由。source 不进入 requestFingerprint,但 command ledger 必须保存内部 `authorizationScopeFingerprint`;每次重试/读回仍先重新授权,只有当前 principal 对同一项目、同一 audience 拥有等价或更高有效 capability 时才允许回放,否则返回 `PERMISSION_DENIED`。这样 GUI 与 CLI 可以用同一 requestId 安全恢复,但低权限调用方不能借已存在 ledger 结果越权读回。 + +`cancel` 是持久化取消意图,不等同于调用返回时 Run 已经进入终态;`CancelAccepted` 只表示取消受理并返回同一 runId 与稳定 `cancelOperationId`,Consumer 必须继续读 Snapshot/事件观察最终收束。`cancelling` 是独立公开状态,不能让 UI 把仍在执行的 Run 显示成 `cancelled`,也不能在取消期间再次发起普通 submit/retry。V1 取消矩阵冻结如下: + +| 目标状态 | Shell 行为 | 结果边界 | +|---|---|---| +| `queued/pending` 且尚未开始执行 | 写 cancel tombstone,阻止 dequeue,收束为 cancelled | 不产生 Runtime/Provider 副作用 | +| `running`、等待 child、等待 lane 或等待 timer | 写同一 operation identity 的 cancel intent,锁内固化当前 parent/child/action/process-session target set,向非终态 child 写 cancel tombstone,通知 Driver 中断并等待 durable finalization | 取消请求可先成功受理,不能提前伪造终态;child 未收束不能把 parent 标成 cancelled | +| `waitingForUserInput`、`waitingForPolicyApproval`、`waitingForDeveloperApproval` | cancel interaction 为 superseded/cancelled,再收束 Run | 不自动提交 answer/approve/reject | +| pending action、Provider/工具执行中或 `finalizing` | 只允许走现有 interruption/finalization 合同;无法证明副作用结果时进入 `needs-reconciliation` | 不因 cancel 创建第二 action、第二 Provider 请求或虚假 cancelled 终态 | +| `cancelling` 且已有 cancel operation | 读取并返回已有 `cancelOperationId`;同 requestId 回放原 ack,不创建第二 tombstone | 新旧 requestId 都指向同一取消操作,不重复中断或写第二终态 | +| `needs-reconciliation` / 外部结果未知 | 记录取消请求但不自动宣称已取消;由 reconciliation owner 判定 parent、child、action 和 process session 是否可安全收束 | 任一 target 外部结果未知时返回 `COMMAND_RESULT_UNKNOWN` 或等价结构化状态,禁止只收束 parent 掩盖 child 未知 | +| 已经是 `completed/failed/cancelled` | 同 requestId 回放原结果;不同 requestId 返回 `CANCEL_ALREADY_TERMINAL` | 零副作用、不可重新取消或隐式 retry | + +`CancelCommand.sessionId` 表示当前调用方的 active project session,不要求等于 Run 创建时的旧 session;只要 session handoff 已由会话管理面持久化并证明属于同一 Project Supervisor lineage,新的 active session 可以取消旧 session 遗留的 Run。session 切换不会复用旧 sessionId,也不会自动解绑仍在运行的 Run;没有有效 handoff/lineage 时返回 `TARGET_STALE`。没有 active session 的 owner recovery 只能走受信任 Runner/Developer reconciliation capability,不能由 Public Consumer 猜测恢复身份。 `submit_intent/cancel/resume` 不使用项目级 `snapshotRevision` 作为业务 CAS:无关的进度刷新不能让用户命令无效。它们在锁内以 payload 中的精确目标身份和当前 durable state 校验可执行性;`cancel` 的 run 已切换时返回 `TARGET_STALE`。`answer/approve` 使用 interaction 自身的 `expectedInteractionRevision`,而不是全项目 Snapshot revision;Public Snapshot 中的 interaction view 同时投影该值。命令返回值只是最小 ack 和操作完成时观察到的 revision,不携带 Runtime state;Consumer 成功或 `interactionRequired=true` 后都重读完整 Snapshot。 command ack 的 `responseMessageId` 只用于在既有 conversation/response-stream 管道定位 direct reply;对话正文仍通过原有 durable conversation read/stream 获取,不进入 Snapshot、事件或 command response。`observedSnapshotRevision` 是操作完成时已闭合的 Public revision,不表示命令结果本身是一份状态。 -公开错误使用稳定 `code/kind/retryable/message/interactionRequired`,命中 ledger 的错误还返回原 `requestFingerprint` 和 `replayed`。Consumer 不解析中文 message。最小错误矩阵如下;未知错误码按不可重试失败关闭: +命令错误使用稳定 `code/kind/retryable/message/interactionRequired`,命中 ledger 的错误还返回原 `requestFingerprint` 和 `replayed`;SnapshotError 只投影稳定 code/kind/retryable/interactionRequired,不携带命令身份或正文。Consumer 不解析中文 message。最小错误矩阵如下;未知错误码按不可重试失败关闭: | code | 语义 | retryable / Consumer 动作 | |---|---|---| | `PROTOCOL_VERSION_UNSUPPORTED` / `INVALID_REQUEST` / `PERMISSION_DENIED` | ledger 前的版本、格式或权限拒绝 | false;修正客户端/权限,不能原请求盲重试 | -| `TARGET_STALE` / `INTERACTION_STALE` / `INTERACTION_ALREADY_RESOLVED` | 精确目标或 interaction 已变化 | false;重读 Snapshot,若仍需操作则新 requestId | +| `TARGET_STALE` / `TARGET_BUSY` / `INTERACTION_STALE` / `INTERACTION_ALREADY_RESOLVED` / `CANCEL_ALREADY_TERMINAL` | 精确目标、并发槽位或 interaction 已变化 | false;重读 Snapshot,若仍需操作则新 requestId | +| `ARTIFACT_BINDING_UNAVAILABLE` | entry binding、输入附件或 requestChanges 的目标产物没有可复核的 immutable revision + `sha256` digest | false;只能等待既有资源/artifact lineage 补齐或改用不需要该 binding 的合法入口,不得猜测当前最新版本或静默使用最新资源 | | `IDEMPOTENCY_KEY_REUSED` | 同 requestId/responseId 被不同内容复用 | false;视为调用方错误 | | `COMMAND_IN_PROGRESS` | 同请求已有 live executor 或同 response 正在 Resolving | true;同 payload/requestId 读回或重试,不启动第二 executor | | `COMMAND_RESULT_UNKNOWN` / `NEEDS_RECONCILIATION` | 已受理操作的外部结果无法证明 | false;重读并进入人工核对,禁止换 ID 自动重放 | -| `OWNER_UNAVAILABLE` / `OWNER_FENCED` / `TRANSIENT_UNAVAILABLE` | 尚未受理,或当前执行者已失去 owner generation | true;完全相同 requestId 可重试;旧 owner 不得继续写入 | +| `OWNER_UNAVAILABLE` / `TRANSIENT_UNAVAILABLE` | 尚未写入 prepared,当前没有合法 owner/transport | true;完全相同 payload/requestId 可重试 | +| `OWNER_FENCED` | 已受理 executor 失去 owner generation | false;Consumer 先读回同 requestId,由新 owner 按 ledger 恢复;若副作用无法证明则转 `COMMAND_RESULT_UNKNOWN`,旧 owner 不得继续写入 | | `REQUEST_NOT_FOUND` | 当前 project ledger 没有该 requestId 的受理记录 | false;不泄漏项目存在性;调用方根据原 transport 结果决定是否用原 requestId 重试 | | `CURSOR_INVALID` / `CURSOR_EXPIRED` | 事件补读起点非法或过期 | 不适用于写重试;全量读取 Snapshot 后换新 cursor | | `INTERNAL` | 已脱敏的未分类内部失败 | false,除非未来细分为明确暂态 code | @@ -284,35 +1230,91 @@ struct AgentRuntimeCommandResultView { status: CommandLedgerStatus, replayable: bool, result: Option, - error: Option, + error: Option, observed_snapshot_revision: Option, } + +enum CommandLedgerStatus { + Prepared, // prepared + Executing, // executing + Succeeded, // succeeded + Rejected, // rejected + OutcomeUnknown, // outcomeUnknown;外部结果无法证明 + NeedsReconciliation, // needsReconciliation;仅由核对流程推进,不自动重试 +} ``` -读回先做 locator/projectId/调用来源复核,再在 project command lock 内查询;不能跨项目按 requestId 搜索。`prepared`/`executing` 返回 `COMMAND_IN_PROGRESS` 或等价的 `status`,Consumer 继续用同一 requestId 读回;`succeeded`/`rejected` 永久返回已保存结果;`outcome-unknown` 返回 `COMMAND_RESULT_UNKNOWN` 并标记 `needs-reconciliation`。如果 requestId 从未被受理,返回不泄漏项目存在性的 `REQUEST_NOT_FOUND`;该错误只表示“本次调用没有留下受理记录”,调用方仍需根据原 transport 响应决定是否使用同一 requestId 重试,不能据此生成新 requestId 重放未知副作用。已进入 ledger 的确定性业务拒绝必须通过 `rejected` 结果读回,而不是依赖错误文字重新判断。 +`result/error` 是随 `status` 绑定的严格 tagged-union 投影,不能出现未定义组合:`succeeded` 必须是 `result=Some、error=None`;`rejected` 必须是 `result=None、error=Some` 且 error 为已持久化的确定性业务拒绝;`prepared/executing` 必须是 `result=None、error=None`;`outcomeUnknown` 必须是 `result=None、error.code=COMMAND_RESULT_UNKNOWN`;`needsReconciliation` 必须是 `result=None、error.code=NEEDS_RECONCILIATION`。`observedSnapshotRevision` 只有在对应 Public projection 已闭合并可读回时才为 `Some`。任一组合不满足该不变量都按 corrupt record 隔离并进入 `outcome-unknown/needs-reconciliation`,不得让 Consumer 猜测结果。 + +`CommandLedgerStatus` 的 wire value 固定为注释中的 lowerCamelCase。`prepared`/`executing` 的 `replayable=true`,表示只能使用同一 requestId 继续或读回;`succeeded`/`rejected`/`outcomeUnknown`/`needsReconciliation` 为不可自动重做的结果,`replayable=false`。`outcomeUnknown` 不是可执行状态:它必须由受信任 reconciliation 流程转为 `needsReconciliation`;`needsReconciliation` 在核对完成前只能读回并返回 `NEEDS_RECONCILIATION`,核对流程确认权威结果后才可推进为确定的 `succeeded/rejected`。在 `outcomeUnknown` 或 `needsReconciliation` 下,任何同 requestId 调用都不得执行命令或创建新的副作用;所有转移都必须保留原 operation identity,并记录 reconciliation operation identity。 + +读回先做 locator/projectId/调用来源复核,再在 project command lock 内查询;不能跨项目按 requestId 搜索。`prepared`/`executing` 返回 `COMMAND_IN_PROGRESS` 或等价的 `status`,Consumer 继续用同一 requestId 读回;`succeeded`/`rejected` 永久返回已保存结果;`outcome-unknown` 返回 `COMMAND_RESULT_UNKNOWN` 并标记项目 `needs-reconciliation`;已进入核对终态的记录返回 `NEEDS_RECONCILIATION`。如果 requestId 从未被受理,返回不泄漏项目存在性的 `REQUEST_NOT_FOUND`;该错误只表示“本次调用没有留下受理记录”,调用方仍需根据原 transport 响应决定是否使用同一 requestId 重试,不能据此生成新 requestId 重放未知副作用。已进入 ledger 的确定性业务拒绝必须通过 `rejected` 结果读回,而不是依赖错误文字重新判断。 request ledger 的状态转移冻结为: ```text -prepared -> executing -> succeeded | rejected | outcome-unknown -prepared -> rejected (可证明尚未产生副作用的业务拒绝) -executing -> succeeded | rejected (有权威 durable 证据) -outcome-unknown -> needs-reconciliation(终态,不自动回退) +prepared -> executing | rejected (后者仅限可证明尚未产生副作用) +prepared -> outcome-unknown (无法证明是否已开始执行) +executing -> succeeded | rejected (有权威 durable 证据) +executing -> outcome-unknown (外部结果无法证明) +outcome-unknown -> needs-reconciliation(仅受信任核对流程) +needs-reconciliation -> succeeded | rejected(仅核对确认权威结果) ``` -每条记录保存 `ledgerVersion`、创建/更新时间、请求指纹、操作身份、执行者 generation、状态和完整结果引用;写入使用临时文件/同步/原子替换,恢复时按版本校验,损坏记录保留原始证据并阻止同 requestId 再执行。`replayed=true` 仅表示返回已持久化的同一结果,不代表再次执行。任何“Shell 已写入 prepared 但 transport 未收到响应”的情况都必须先读回;不能以 unknown-command fallback 或新 requestId 规避 ledger。 +每条记录保存 `recordRevision`、`ledgerVersion`、创建/更新时间、请求指纹、操作身份、执行者 generation、状态和完整结果引用;写入使用临时文件/同步/原子替换,恢复时按 record revision 与 ledger version 双重校验,损坏记录保留原始证据并阻止同 requestId 再执行。`replayed=true` 仅表示返回已持久化的同一结果,不代表再次执行。任何“Shell 已写入 prepared 但 transport 未收到响应”的情况都必须先读回;不能以 unknown-command fallback 或新 requestId 规避 ledger。 -request ledger 是项目级私有 durable 记录,状态为 `prepared / executing / succeeded / rejected / outcome-unknown`,并保存规范请求指纹、预分配的内部 operation identity、executor boot/generation 及完整权威成功或错误结果。恢复所需的用户消息/answers 只保存有界私有 payload 或指向既有 durable conversation/interaction record 的稳定引用,沿用现有内容安全、权限和脱敏规则;绝不复制到 Public Snapshot、事件或错误。requestId 提供幂等受理与结果读回边界,不承诺无法判定的外部副作用 exactly-once;这种窗口必须显式 outcome-unknown。处理顺序固定: +所有 durable 记录统一使用 `DurabilityCapability`,不把“rename 成功”当成跨平台持久化证明;V1 至少覆盖 command ledger、Builtin management operation、interaction、rework、projection、input envelope、Runtime status message、public event message、response delivery、RunLineage、Session/Handoff/ActiveSessionIndex/SessionRotation/HandoffManifest 和 local management operation: + +1. Unix:同目录创建临时普通文件 → 写完整 envelope → `fsync(file)` → 原子 rename/replace → `fsync(parent directory)`;临时文件、目标文件或目录是 symlink、类型错误或 schema/ledgerVersion 不完整时隔离为 `.corrupt.`,不覆盖旧证据。 +2. Windows:同目录临时文件 → `FlushFileBuffers` → 使用平台原子 replace(保留目标备份/ACL)→ 对目标句柄再次 `FlushFileBuffers`;若平台 API 或文件系统不能证明 replace 后持久化,能力报告为 `unsupported`,禁止 prepared→executing。 +3. 恢复只接受完整 envelope、单调 `recordRevision`/`ledgerVersion`、匹配 request/operation identity 和完整 checksum;发现新旧两个版本都存在时按 journal 状态选择唯一合法前缀,无法唯一选择就进入 `outcome-unknown/needs-reconciliation`,不“取最新文件”。 + +能力报告必须在 Runner boot 时持久化并绑定 `ownerBootId`;运行期间能力降级会停止新受理,但不回滚已提交事实。上述规则是安全门禁,不以“目标平台通常支持”替代 Unix/Windows crash-point 实测。 + +request ledger 是项目级私有 durable 记录,状态为 `prepared / executing / succeeded / rejected / outcome-unknown / needs-reconciliation`,并保存规范请求指纹、预分配的内部 operation identity、executor boot/generation 及完整权威成功或错误结果。恢复所需的用户消息/answers 只保存有界私有 payload 或指向既有 durable conversation/interaction record 的稳定引用,沿用现有内容安全、权限和脱敏规则;绝不复制到 Public Snapshot、事件或错误。requestId 提供幂等受理与结果读回边界,不承诺无法判定的外部副作用 exactly-once;这种窗口必须显式 outcome-unknown,核对完成后才可进入 needs-reconciliation 或确定结果。处理顺序固定: 1. transport 先 canonicalize root、验证本地项目授权、manifest `projectId` 与调用权限;版本/身份/权限失败发生在 ledger 之前,保证攻击者不能向任意项目写记录。 -2. Shell 用 RFC 8785 canonical JSON 规范化“schema version + command kind + projectId + 完整业务 payload(含 interaction/response identity、decision、answers 和任何精确 target)”,计算小写 SHA-256。`requestId`、路径、时间戳及 transport 字段不进指纹。 -3. 在 project command lock 内先按 `requestId` 查 ledger,再做任何当前状态校验。相同 ID/相同指纹的 `succeeded` 或 `rejected` 直接返回原结果;不同指纹返回 `IDEMPOTENCY_KEY_REUSED`;`prepared/executing` 返回 `COMMAND_IN_PROGRESS`(可同 ID重试/读回);`outcome-unknown` 返回原 `COMMAND_RESULT_UNKNOWN`,不再执行。 -4. 只有 ledger 未命中时才验证 target/interaction 当前状态,并在副作用前原子写 `prepared`;`submit_intent` 的 acceptedRunId/steerId 以及下游 Runner requestId 必须在 prepared 中预分配并在恢复时复用,不能在重试中生成第二身份。业务拒绝也原子落为 `rejected`,使同一请求重放得到相同结果。进入内部执行前转为 `executing` 并绑定 executor;完成内部状态写后必须先闭合对应 Public projection,再写入并回读 `succeeded/rejected` 权威结果和 `observedSnapshotRevision`。 +2. Shell 在 ledger 前完成严格结构/数量/长度/UTF-8 字节上限和内容安全校验;失败返回 `INVALID_REQUEST` 且不落原始正文。通过后,项目级 Runtime command 用 RFC 8785 canonical JSON 规范化“schema version + command kind + projectId + 完整业务 payload(含 Conversation/BuiltinCommand variant、规范化 commandLine、expectedParserVersion、intentKind、entryBinding、immutable attachments、runProfile、resume tagged intent、interaction/response identity、decision、feedback、answers 和任何精确 target)”,计算小写 SHA-256。LocalManagementRoute 不进入项目 request ledger;它只对去掉 locator 的参数计算 `argumentFingerprint`,并把不含原文的 `locatorDigest` 单独固化。任何 fingerprint 都不得保存或回显原始路径;`requestId`、时间戳及 transport/source 字段不进项目业务指纹;当前有效 capability 另存内部 authorizationScopeFingerprint。Local command 的幂等键为 `(localScopeId, requestId)`;同 scope/同 requestId 必须保持 parser version、route、commandName、targetRef、locatorDigest 和 path-free arguments 这组**历史操作 identity**完全相同,才可回放;不同则 `IDEMPOTENCY_KEY_REUSED`。首次受理的 capabilityId 只作为 `originatingCapabilityId` 审计记录,不进入历史操作 identity;重试/读回可以使用 capability rotation 后的新 capabilityId,但当前 capability 必须重新证明同一 localScope、principal、route/target 的授权范围未被撤销且覆盖该操作,否则返回 `PERMISSION_DENIED`/`TARGET_STALE`。这些 parser/route/target 字段必须原样写入 `AgentRuntimeLocalManagementOperationRecord`,成为 prepared 后的历史 identity;当前 capability 只能用于重新授权,不能替代或重新解释已保存的 parser/route/target。恢复时任一历史字段不一致返回 `TARGET_STALE`,不得仅按新的 capability 或 commandLine 继续执行。local command 结果通过 `read_local_management_result(localScopeId, requestId)` 读回并重新授权;`prepared/executing/outcome-unknown/needs-reconciliation` 的恢复语义与项目 operation 相同,但绝不按 commandLine 原文或当前窗口重新解析路径。 +3. 在 project command lock 内先按 `requestId` 查 ledger,再做任何当前业务状态校验;但每次重放仍必须通过 project identity、principal 和 authorizationScopeFingerprint 兼容性复核。相同 ID/相同指纹且授权等价的 `succeeded` 或 `rejected` 直接返回原结果;不同指纹返回 `IDEMPOTENCY_KEY_REUSED`;`prepared/executing` 返回 `COMMAND_IN_PROGRESS`(可同 ID重试/读回);`outcome-unknown` 返回原 `COMMAND_RESULT_UNKNOWN`,不再执行;`needs-reconciliation` 返回 `NEEDS_RECONCILIATION`,只能读回或进入受信任核对流程,不得执行命令。 +4. 只有 ledger 未命中时才验证 target/interaction 当前状态,并在副作用前原子写 `prepared`;`submit_intent` 的 inputEnvelopeId/acceptedRunId/steerId、RetryTerminalRun 的 successorRunId、requestChanges 的 reworkOperationId、direct reply 的 responseMessageId 以及下游 Runner/Provider request identity 必须在 prepared 中预分配并在恢复时复用,不能在重试中生成第二身份。业务拒绝也原子落为 `rejected`,使同一请求重放得到相同结果。进入内部执行前转为 `executing` 并绑定 executor;完成内部状态写后必须先闭合对应 Public projection,再写入并回读 `succeeded/rejected` 权威结果和 `observedSnapshotRevision`。 5. 崩溃恢复只能依据 ledger、executor 生命状态、Runtime journal 和既有 durable identity 前向闭合;仍有 live executor 时不得由第二执行者接管。能证明未产生副作用可用同一 operation identity 继续;能证明结果则幂等补投影/结果;外部结果不明则原子转为 `outcome-unknown` 并使项目进入 `needs-reconciliation`,禁止自动换 requestId 或重复入队。 6. Consumer 对 transport 超时、`COMMAND_IN_PROGRESS` 或明确暂态错误只可重发完全相同 payload 和同一 requestId,或调用 `read_game_creator_agent_command_result(projectId, requestId)`;读回接口同样先完成 locator/project identity/权限复核,禁止跨项目扫描。 7. V1 ledger 跟随项目 Runtime durable archive 生命周期保存,不按时间或条数隐式淘汰。若将来压缩,必须先设计持久 tombstone,使已淘汰 requestId 仍能失败关闭。 -现有 Runner 内存 request cache、`acceptedRunId` 和 Goal CAS 只作为内部附加护栏,不替代公开 ledger。goal CRUD、`compact`、会话管理和配置读写属于管理面,不进入这五个 Runtime Loop 命令;它们若是公开写操作,继续遵守各自现行 CAS/权限合同,不能借本次重构降级。 +`RetryTerminalRun` 的 successor lineage 不再直接复用现有 `acceptedRunId` 或调用方传入的 `nextRunId`。当前实现中的 `agent.runtime.background_task.retry` 记录、`retryRunId` 和 `accepted_run_id` 只能作为迁移 adapter 的输入:adapter 必须在 project lock 内验证 `(projectId, agentId, taskId, predecessorRunId, retryRunId, sessionId)` 唯一且 predecessor 确为终态,然后写入新的 `RunLineageRecord`;`acceptedRunId` 仍只是响应中的实际 successor 回显,不是 lineage 事实。V1 `RetryTerminalRun` 不接受 `nextRunId`,successor runId 由 prepared 阶段生成并写入: + +```rust +struct RunLineageRecord { + envelope: AgentRuntimeDurableEnvelope, + project_id: String, + supervisor_lineage_id: String, + agent_id: String, + session_id: String, + task_id: String, + parent_run_id: Option, // Supervisor=None;专业 Agent 必须为当前父 run + delegation_id: Option, // 专业 Agent 必填并锁内复核 + predecessor_run_id: String, + successor_run_id: String, + predecessor_terminal_revision: u64, + retry_operation_id: String, + source_record_ref: Option, // 仅迁移旧 retry sidecar/DB record + status: SuccessorLineageStatus, // prepared | enqueued | active | terminal | rejected | outcomeUnknown | needsReconciliation +} + +enum SuccessorLineageStatus { + Prepared, + Enqueued, + Active, + Terminal, + Rejected, + OutcomeUnknown, + NeedsReconciliation, +} +``` + +唯一约束使用 `(projectId, agentId, predecessorRunId)`,同一 predecessor 在其整个 lineage 生命周期内最多有一个 successor(不论 successor 当前是 prepared、active 还是 terminal);不能假设不同 Agent 的 runId 全局唯一。并发 retry 通过 predecessor terminal revision + lineage 唯一约束收敛为同一 successor。successor 已终态后不得再次从旧 predecessor 分叉;后续 retry 必须针对该 successor 的最新终态记录创建下一条 lineage,重复提交同一 retryOperationId 才能回放原 successor。Supervisor lineage 的 `parentRunId/delegationId` 必须为空;专业 Agent lineage 必须同时保存并复核当前 `parentRunId + delegationId + taskId + sessionId`,且父 Supervisor 仍是允许该 recovery 的当前 run。旧 `nextRunId` 与新生成 ID 冲突、旧 retry 记录缺字段、父 Supervisor 已终态或任一 lineage 证据不完整时,业务拒绝并进入 `TARGET_STALE`/`NEEDS_RECONCILIATION`,不能另造 successor。`ContinueRun` 不写 `RunLineageRecord` 且始终保持原 runId。 + +现有 Runner 内存 request cache、`acceptedRunId` 和 Goal CAS 只作为内部附加护栏,不替代公开 ledger。goal CRUD、`compact`、会话管理、资源上传/登记、`preview.start`/`preview.validate` 和配置读写属于管理面,不进入这五个 Runtime Loop 命令;它们若是公开写操作,继续遵守各自现行 CAS/权限合同,不能借本次重构降级。 ### 1.3.2 五命令请求体冻结 @@ -322,9 +1324,193 @@ request ledger 是项目级私有 durable 记录,状态为 `prepared / executi ```rust struct SubmitIntentCommand { meta: AgentRuntimeCommandMeta, + payload: SubmitIntentPayload, +} + +enum SubmitIntentPayload { + Conversation { + session_id: String, + expected_session_revision: u64, + message: String, + attachments: Vec, + intent_kind: AgentRuntimeIntentKind, + // 只有 createFromTemplate/importExistingDesign 使用;不能把模板或设计身份 + // 藏在自然语言 message 中。 + entry_binding: Option, + run_profile: AgentRuntimeRunProfile, + }, + BuiltinCommand { + command_line: String, + expected_parser_version: String, + // slash 不允许 attachments、entry binding 或 Runtime intent/profile; + // project/session scope 由 Shell 从 capability/管理面取得。 + }, +} + +// 项目级 slash management action 的私有幂等记录;不把 management action 伪装成 Runtime run。 +struct AgentRuntimeBuiltinManagementOperationRecord { + envelope: AgentRuntimeDurableEnvelope, + command_request_id: String, + // RuntimeBuiltinCommand 的项目级 slash action 必须引用已经幂等提交的 + // conversation user message;LocalManagementRoute(例如 CLI /goal)不写 + // Public conversation,此字段必须为 None,不能伪造 message identity。 + conversation_user_message_id: Option, + command_name: String, + target_identity_fingerprint: String, + argument_fingerprint: String, + private_argument_ref: Option, + // 领域副作用一旦被受理,必须绑定同一 project operation;核对期间保留 + // 原 operation identity,不能通过新 requestId 或新的领域 operation 重做。 + project_operation_ref: Option, + reconciliation_operation_id: Option, + status: AgentRuntimeBuiltinManagementOperationStatus, + result_ref: Option, + error_ref: Option, +} + +enum AgentRuntimeBuiltinManagementOperationStatus { + Prepared, + Executing, + Succeeded, + Rejected, + OutcomeUnknown, + NeedsReconciliation, +} + +// 状态迁移:prepared -> executing | rejected;prepared -> outcomeUnknown; +// executing -> succeeded | rejected | outcomeUnknown; +// outcomeUnknown -> needsReconciliation; +// needsReconciliation -> succeeded | rejected。最后两条只能由受信任核对流程 +// 执行,并必须写入 reconciliation_operation_id 与确定的 result/error ref。 +// 只有需要项目身份/副作用的 Builtin management route 建该记录;/mcp、/quit、/exit +// 等纯 CLI transport 动作不进入项目 ledger。其 target/argument fingerprint 在 +// prepared 后固定,重试未知结果只能读回或核对,不能换 requestId 再做副作用。 + +// LocalManagementRoute 的 path-free 参数恢复规则:`private_argument_ref` 指向 +// 同一 localScopeId 下有界、脱敏、带 checksum 的私有 payload。恢复只能读取该 +// ref,并同时校验 payload checksum 与 `argument_fingerprint`;缺失、篡改或二者 +// 不一致进入 `CORRUPT_RECORD`,不得重新解析 commandLine。locator 原文永不入 +// payload、fingerprint、result/error 或 Public delivery。 + +// LocalManagementResponse 的错误闭合规则:capability 不存在返回 +// `CAPABILITY_NOT_FOUND`;parser 版本不支持返回 `PROTOCOL_VERSION_UNSUPPORTED`; +// targetRef 漂移返回 `TARGET_STALE`,scope 字段缺失/多带或 session revision +// 不匹配返回 `SCOPE_MISMATCH`,权限不足返回 `PERMISSION_DENIED`,locator +// 不可用/过期/撤销返回 `LOCATOR_UNAVAILABLE`,活跃执行返回 +// `COMMAND_IN_PROGRESS`,结果无法证明返回 `COMMAND_RESULT_UNKNOWN`;已进入 +// reconciliation 的记录返回 `NEEDS_RECONCILIATION`,同一 `(localScopeId, +// requestId)` 换指纹返回 `IDEMPOTENCY_KEY_REUSED`,损坏记录 +// 返回 `CORRUPT_RECORD`。确定性校验失败不得依赖中文 message;已进入 local +// record 的拒绝和未知结果必须可由 `read_local_management_result(localScopeId, +// requestId)` 读回。Local response 的 `requestFingerprint` 在成功时必有,错误 +// 仅在已完成无路径原文的规范化时有值;`projectId` 只通过成功结果的 +// `resolved_project_id` 返回,`observedSnapshotRevision` 和 +// `interactionRequired` 对所有 local response 均不存在。 + +enum AgentRuntimeLocalLocatorHandleState { + NotAcquired, + Acquired, + Released, + ReconciliationHeld, +} + +// locator_handle_ref 的生命周期与 projectOperationRef 绑定:resolver 先验证 +// locator 类型、digest、权限和 symlink/reparse 安全,再取得绑定 +// `(localScopeId, requestId, ownerGeneration)` 的临时 handle。local-only route +// 在 local record 已进入 terminal 且 `project_operation_ref = None` 时释放; +// project-linked route 必须等 local record 与关联 project operation 都到达 +// terminal 且结果已 durable commit 后释放。`prepared -> executing` 前 handle +// 过期/撤销返回 `LOCATOR_UNAVAILABLE`;executing 或 project operation 已受理 +// 后失去 handle 必须进入 `outcome-unknown/reconciliation`,状态固定为 +// `ReconciliationHeld`,不能换 handle 或 requestId 重做。owner 重启只能用同一 +// 稳定 resolver identity 重新绑定并复核 locator digest、target revision 和 +// operation identity,不能从原 commandLine 重新解析。`Released` 只表示 handle +// 已撤销,record 可保留不透明 ref 供审计但不得再次使用。至少覆盖 +// `prepared → handle acquired → crash`、`executing → handle expired`、 +// `outcome-unknown → retry/readback`、无 project operation 的 resolver rejection +// 和“project resolve 成功但 project operation 失败”的 fixture;所有分支最终 +// 都必须释放或明确保留待 reconciliation 的 handle。 + +// 尚未解析出 projectId 的本地 locator / path-bearing action 使用独立私有记录, +// 不强行伪造 projectId,也不把绝对路径写入 Public/Runtime ledger。 +struct AgentRuntimeLocalManagementOperationRecord { + envelope: AgentRuntimeLocalDurableEnvelope, + command_request_id: String, + originating_capability_id: String, // 仅审计;不作为重放的历史业务 identity + authorization_principal_ref: String, + authorization_scope_fingerprint: String, + expected_parser_version: String, + route: AgentRuntimeLocalCommandRoute, + target_ref: Option, + command_name: String, + resolved_project_id: Option, + project_operation_ref: Option, + locator_digest: Option, + // 仅引用受信任 resolver/OS bookmark/handle store;不能包含原始路径。 + locator_handle_ref: Option, + locator_handle_state: AgentRuntimeLocalLocatorHandleState, + // 只覆盖去掉 locator 后的规范化参数;路径身份单独由 locatorDigest 约束。 + argument_fingerprint: String, + // `/goal <目标>`、`/goal edit` 和 `/agent-resume [说明]` 等 path-free + // 参数保存为有界私有 payload;host path 原文禁止进入该 payload。 + private_argument_ref: Option, + status: AgentRuntimeLocalManagementOperationStatus, + result_ref: Option, + error_ref: Option, +} + +struct AgentRuntimeInputAttachmentBinding { + attachment_namespace: String, + attachment_id: String, + attachment_revision: u64, + digest_algorithm: String, // V1 固定 sha256 + content_digest: String, + media_kind: AgentRuntimePublicMediaKind, +} + +// Shell 私有 durable payload;不是 Public Snapshot 或 conversation 正文。 +struct AgentRuntimeInputEnvelopeRecord { + envelope: AgentRuntimeDurableEnvelope, + input_envelope_id: String, + status: AgentRuntimeInputEnvelopeStatus, // reserved | committed | outcome-unknown | corrupt + command_request_id: String, + project_id: String, session_id: String, + session_revision: u64, + conversation_user_message_id: String, + conversation_commit_marker: Option, message: String, - run_profile: AgentRuntimeRunProfile, + attachments: Vec, + input_fingerprint: String, +} + +enum AgentRuntimeInputEnvelopeStatus { + Reserved, + Committed, + OutcomeUnknown, + Corrupt, +} + +`Committed` 必须同时具有可回读的 `conversation_commit_marker`;`OutcomeUnknown`/`Corrupt` 禁止进入 Provider/Runtime context,恢复只能保留同一 envelope identity 并进入 reconciliation。 + +enum AgentRuntimeIntentEntryBinding { + Template { + template_id: String, + template_revision: u64, + digest_algorithm: String, // V1 固定 sha256 + content_digest: String, + }, + ExistingDesign { + design_id: String, + design_revision: u64, + digest_algorithm: String, // V1 固定 sha256 + content_digest: String, + }, +} + +struct AgentRuntimeRunProfile { + name: String, + version: String, } struct AnswerCommand { @@ -336,33 +1522,87 @@ struct AnswerCommand { struct ApproveCommand { meta: AgentRuntimeCommandMeta, interaction: InteractionResponseMeta, - decision: ApprovalDecision, // Approve | Reject,必填 + decision: ApprovalDecision, // approve | reject | requestChanges,必填 + feedback: Option, // 仅 requestChanges;有界、脱敏,其他 decision 必须省略 } struct CancelCommand { meta: AgentRuntimeCommandMeta, session_id: String, + expected_session_revision: u64, run_id: String, } +enum AgentRuntimeResumeMode { + ContinueRun, // continueRun + RetryTerminalRun, // retryTerminalRun + ReconcileRun, // reconcileRun +} + +enum AgentRuntimeResumeIntent { + ContinueRun { + session_id: String, + expected_session_revision: u64, + run_id: String, + expected_run_revision: u64, + }, + RetryTerminalRun { + session_id: String, + expected_session_revision: u64, + target: AgentRuntimeRetryTarget, + }, + ReconcileRun { + // 没有 active session 时允许受信任 reconciliation capability 置空; + // Public User/CLI 不能使用该分支。 + session_id: Option, + run_id: String, + expected_run_revision: u64, + reconciliation_id: String, + }, +} + +enum AgentRuntimeRetryTarget { + Supervisor { + run_id: String, + expected_terminal_revision: u64, + }, + Collaborator { + target: AgentRuntimeCollaboratorRunTarget, + }, +} + struct ResumeCommand { meta: AgentRuntimeCommandMeta, - resume_scope: AgentRuntimeResumeScope, // Project - reason: ResumeReason, + intent: AgentRuntimeResumeIntent, } ``` -`submit_intent.message` 不能为空,长度上限沿用现有 Runtime 输入限制;`sessionId` 必须是当前 Project Supervisor 的 session。`cancel` 必须同时提交当前 `sessionId + runId`,防止旧 runId 被新会话误取消;run 已终结、session 已切换或绑定关系不一致均为 `TARGET_STALE`。V1 `resumeScope` 只允许 `project`,`reason` 使用稳定枚举 `userRequested | ownerRecovered | timerElapsed | interactionResolved`;`ownerRecovered/timerElapsed/interactionResolved` 只能由受信任 Shell/Runner 生成,Consumer 只能提交 `userRequested`。`runProfile` 只允许已注册的公开 profile 名称和版本,不能携带 Provider、模型、工具、提示词或路径配置。 +V1 `submit_intent` 的 `Conversation.message` 必须是非空、非纯空白文本并按第 1 节统一常量校验;attachment-only 在 ledger 前返回 `INVALID_REQUEST`。slash route 携带 attachment 或 entryBinding 同样在 ledger 前返回 `INVALID_REQUEST`,不得先执行 parser 再忽略多余输入。这是对现有 Runtime `task` 非空合同的显式继承,Consumer 不得为了绕过门禁自动合成“请处理附件”等自然语言。attachment 只能在 capability 对应 option 的 `inputPolicy=textWithOptionalAttachments` 时随 message 提交,`mediaKind` 必须命中同一 option 的 `allowedAttachmentMediaKinds` 且由资源 registry 的真实类型复核;`Other` 不属于 V1 可输入媒体。attachment 必须已经通过现有上传/项目资源管理面进入 manifest/resource lineage。Runtime 命令不接收文件字节、绝对路径、`file://`、浏览器临时 URL 或上传 token;Shell 在锁内重读 revision 和 `sha256`,任一漂移整条请求失败关闭。attachment 顺序、完整 binding 和 mediaKind 均进入 request fingerprint,不能只散列文件名。V1 所有 `digestAlgorithm` 必须等于 `sha256`,`contentDigest` 必须是 64 位小写十六进制;namespace/id、revision 和实际重读内容均在 project lock 内复核,缺失/格式错误在 ledger 前 `INVALID_REQUEST`,内容不一致为 `ARTIFACT_BINDING_UNAVAILABLE`。 -`AnswerCommand` 的答案结构只允许 Public Snapshot 当前 interaction view 中声明的 question/option/自由回答约束;缺失、重复、越界或不符合约束返回 `INVALID_REQUEST`,不产生部分写入。`ApproveCommand` 的 decision 不允许由 UI button、命令名或缺省值推断。`requestFingerprint` 覆盖上述规范化业务请求体以及 `schemaVersion/commandKind/projectId`,不覆盖 transport source、locator、时间戳、重试次数或响应展示文案。通过身份、权限和版本校验的请求,即使因 target stale、interaction stale、当前状态不允许或策略拒绝而没有 Runtime 副作用,也必须在 ledger 中以 `rejected` 持久化;只有格式、版本、身份或权限失败且请求尚未进入项目 ledger 的情况才返回 `REQUEST_NOT_FOUND`。 +附件不能只做“校验后丢弃”。对于 `Conversation` payload,Shell 在 command `prepared` 时将规范化 message 与有序 immutable bindings 写入同一私有 `AgentRuntimeInputEnvelopeRecord`,direct reply/start/steer 三条路径都引用同一 envelope identity;`BuiltinCommand` 不创建 Runtime input envelope,而是将规范化 command line 固化在 command ledger,并只引用一次 `conversationUserMessageId`;现有 Runtime 继续以原 message 作为非空 task/steer 文本,Provider/context adapter 另以结构化 attachment context 注入 resource identity、revision、digest 和 mediaKind,工具读取仍只经既有 manifest/resource resolver。不得把资源路径、签名 URL 或自动生成的自然语言拼进 task;恢复时必须重读同一 envelope 和 binding,资源已替换/删除则在 Provider/Runtime 副作用前失败关闭。conversation user message 可以显示用户原文和安全附件摘要,但摘要不是第二份资源事实。对 `Start`,同一 prepared 记录必须同时预分配 `conversationUserMessageId + runId + runtimeStatusMessageId`;`runtimeStatusMessageId = runtime-public-status- + lowerHex(sha256(RFC 8785 canonical JSON(["runtime-status", projectId, runBoundSessionId, runId, statusKind])))`;其中 `runBoundSessionId` 是 prepared/Runtime run 固化的 session 身份,不随后续 active session handoff 改写,只在该 prepared identity 下生成一次:先幂等提交用户原文/安全附件摘要的 conversation message 并回读 commit marker,再以 `runtime-public-status-*` 前缀将脱敏“任务已接收,正在启动处理”作为 Runtime-owned status message 原子写入 conversation;status message 的 commit marker 可读回后才允许把 Run 从 `preparing/public-status-pending` 提升为 `queued/pending` 并让 Runner dequeue。崩溃在 user message committed 与 status committed 之间时,恢复只用同一三组 identity 补 status;conversation 已提交而辅助 audit 尚未提交时,以 conversation commit marker 为公开真相继续补 audit/入队,不得留下“已接收但永不执行”或写第二条 user/status message。status 写入失败则持久化 `failed/public-status-write-failed`,不得执行或继续重试 Provider;写入结果未知则 Run 保持不可执行并进入 reconciliation,恢复只能复用同一 message/run identity。Steer、DirectReply 和 RuntimeFinalReply 不重复写该 Start status message。根 Supervisor 的正式失败、预算耗尽或硬期限 reconciliation 也必须先用唯一 Runtime-owned failure status message 写入用户可理解摘要,再提交其它 task/event/state 终态;status 写入失败/结果未知时不把 Public outcome 提前投影为 `failed`,而是保持 terminal-pending/reconciliation,直到同一 status identity 可读回或由 owner 明确核对;status message 必须被 prompt builder 按固定前缀排除,前端按其来源顺序展示且不得二次持久化。现有需要进入聊天的安全 Runtime 事件另走 `AgentRuntimePublicEventMessageRecord`:Rust 只在同一父 run 下生成稳定 `eventId + publicText`(`eventId = runtime-public-event- + lowerHex(sha256(RFC 8785 canonical JSON(["runtime-public-event", projectId, parentRunId, sourceRunId, durableEventId, eventKind, normalizedPublicPayload])))`,其指纹输入包含 project/parent/source run、底层 durable event identity、allowlisted event kind 和规范化 public payload),前端按该二元身份去重和展示;无 eventId、空 publicText、legacy/raw tool/provider/runner payload 一律丢弃。它不进入 `SnapshotChanged` envelope、Public Snapshot 或 Runtime status message,也不改变 Runtime 状态事实;只允许现有 allowlist 的专业 Agent/公开 child 事件进入该 delivery;根 Supervisor 的 `turn.started/turn.failed/turn.budget_exhausted` 不再生成第二条 event message,终态失败/预算摘要只走上面的唯一 Runtime-owned status message。当前工作台对 Supervisor/主 Agent/直接 child 的最近 4/最多 20 条聚合只是该 conversation read model 的展示限制,不能在 Consumer 自行从 raw event 重算。 + +`Conversation.intentKind` 必填且只能取 V1 稳定枚举值,不能从 `message` 启发式推断,也不能塞入 `runProfile`。`entry_binding` 与 `intentKind` 必须是严格一一对应的 tagged union,且 `(intentKind, runProfile, entryBindingKind, inputPolicy, attachmentMediaKind)` 必须命中当前 `conversationOptions` 的同一个 option;禁止从独立列表做笛卡尔积、通过未知字段或自然语言补充身份。实际 entry binding 由既有模板/资源管理面提供,不把所有模板或设计 identity 复制到 Snapshot。Shell 必须在锁内校验该入口对当前项目、session 和状态是否可用;校验失败为持久化业务拒绝,不产生副作用。`sessionId + expectedSessionRevision` 必须命中当前 Project Supervisor active session。`cancel` 必须同时提交当前 `sessionId + expectedSessionRevision + runId`,防止旧 capability 被新会话误用;session handoff 已明确绑定同一 lineage 时只接受新 active session 的 capability,run 已终结、session 已切换或绑定关系不一致均为 `TARGET_STALE`。V1 `resume` 不再使用模糊的 project scope/reason。`ContinueRun` 只允许 Snapshot capability 明确声明的 `pausedByUser` 同一 durable Supervisor run,必须携带 `expectedRunRevision`;running、waiting interaction、pending/executing action、timer/lane、finalizing 和 needs-reconciliation 均不得用 ContinueRun 越过各自门禁。`RetryTerminalRun` 必须精确绑定 capability 给出的失败终态 run 和 terminal revision,在 prepared 阶段预分配新的 successor runId,并持久化 predecessor/successor lineage,绝不复用旧 runId;completed 不可 retry,cancelled 只有产品策略明确给出 retry capability 时才可 retry。专业 Agent retry 使用 `AgentRuntimeCollaboratorRunTarget`,同时复核 collaborationId、parentRunId、runId 和 delegation/repair 状态;若 Supervisor 已准备唯一 contract repair,Snapshot 只给 `recovery=RepairApprovalPending` 并指向同一 Public PolicyApproval interaction;在该 interaction 解决前不得创建平行 successor 或开放 `RetryCollaboratorRun`。`ReconcileRun` 只允许受信任 Developer/Runner reconciliation capability,且允许没有 active session,但必须由 capability 通过 Project Supervisor lineage 精确定位目标,不能以空 session 放宽权限,也不能自动重放未知 Provider/工具副作用。timer、lane release、ownerRecovered、interactionResolved 和 schedule-ready 都是 Shell/Runner 内部 recovery intent,不进入 Public ResumeCommand。自然语言“继续”不得路由为任何 resume tagged intent,更不能静默触发 RetryTerminalRun;Consumer 必须显式发送结构化 ResumeCommand。`runProfile` 的 wire 形态为 Snapshot capability 中已注册的公开 profile 名称和版本,不能携带 Provider、模型、工具、提示词或路径配置。 + +`AnswerCommand` 的答案结构只允许 Public Snapshot 当前 interaction view 中声明的 question/option/自由回答约束;缺失、重复、越界或不符合约束返回 `INVALID_REQUEST`,不产生部分写入。`ApproveCommand` 的 decision 不允许由 UI button、命令名或缺省值推断;`requestChanges` 只在当前 interaction 的 `allowed_decisions` 声明该值时可用,V1 公开支持的场景仅为 `scope=Run | Action`、绑定单一不可变 targetArtifact 的用户 `PolicyApproval`;Project scope PolicyApproval 与 ToolApproval 仍只允许 `approve/reject`。选择 `requestChanges` 时必须携带 `feedback`,长度限制为最多 2,000 个 Unicode 字符;选择 `approve/reject` 时必须省略该字段。Shell 对 feedback 复用公开内容安全过滤,拒绝绝对路径、密钥/Token、Provider 原文和超长内容;过滤失败返回 `INVALID_REQUEST`,不产生部分写入。`requestFingerprint` 覆盖上述规范化业务请求体以及 `schemaVersion/commandKind/projectId`,不覆盖 transport source、locator、时间戳、重试次数或响应展示文案。通过身份、权限和版本校验的请求,即使因 target stale、interaction stale、当前状态不允许或策略拒绝而没有 Runtime 副作用,也必须在 ledger 中以 `rejected` 持久化;只有格式、版本、身份或权限失败且请求尚未进入项目 ledger 的情况才返回 `REQUEST_NOT_FOUND`。 ### 1.4 InteractionRequired 身份、版本和解决状态机 ```rust +struct AgentRuntimeArtifactBinding { + artifact_namespace: String, + artifact_id: String, + artifact_revision: u64, + digest_algorithm: String, // V1 固定 sha256 + content_digest: String, +} + // Shell 内部 durable record;不直接作为正式公开 DTO。 struct AgentRuntimeInteractionRecord { + envelope: AgentRuntimeDurableEnvelope, interaction_id: String, interaction_revision: u64, + // continuation item 的 record_revision 取 envelope.record_revision;interaction_revision + // 只表示交互业务版本,不能替代 durable record revision。 kind: AgentRuntimeInteractionKind, scope: AgentRuntimeInteractionScope, audience: AgentRuntimeInteractionAudience, @@ -371,6 +1611,11 @@ struct AgentRuntimeInteractionRecord { session_id: Option, run_id: Option, action_id: Option, + action_fingerprint: Option, + policy_snapshot_fingerprint: Option, + approval_target_set: Option, + // 私有绑定:保证审批意见针对创建交互时看到的确切不可变产物版本。 + target_artifact: Option, request_fingerprint: String, bound_state_fingerprint: String, status: AgentRuntimeInteractionStatus, @@ -378,18 +1623,86 @@ struct AgentRuntimeInteractionRecord { private_presentation: AgentRuntimeInteractionPrivatePresentation, } +// Shell 内部 durable resolution;UserInput 与 Approval 使用严格 tagged union。 +enum AgentRuntimeInteractionResolution { + UserInput { + response_id: String, + response_fingerprint: String, + answers: Vec, + }, + Approval { + response_id: String, + response_fingerprint: String, + decision: ApprovalDecision, + // 只保存经过公开内容安全过滤的副本。 + feedback: Option, + target_artifact: Option, + rework_operation_id: Option, + follow_up_interaction_id: Option, + }, +} + +struct AgentRuntimeApprovalTargetSet { + targets: Vec, + target_set_fingerprint: String, +} + +struct AgentRuntimeApprovalTarget { + agent_id: String, + parent_run_id: Option, // Supervisor=None;专业 Agent 必须为当前父 run + run_id: String, + action_id: String, + action_fingerprint: String, +} + +`parent_run_id=None` 仅允许 Supervisor action;专业 Agent/child action 必须为当前 Public Supervisor run 的 Some(parentRunId),并在 target-set fingerprint 与 approve 锁内复核。 + struct AgentRuntimeInteractionView { interaction_id: String, interaction_revision: u64, kind: AgentRuntimeInteractionKind, scope: AgentRuntimeInteractionScope, + // Consumer 只能在 actionable=true 且 allowed_actions 非空时渲染 answer/approve; + // Shell 仍须在锁内重新校验,字段不是绕过服务端权限的凭据。 + actionable: bool, + allowed_actions: Vec, status: AgentRuntimeInteractionPublicStatus, + context: AgentRuntimePublicInteractionContext, presentation: AgentRuntimePublicInteractionPresentation, } +enum AgentRuntimePublicInteractionContext { + Supervisor, + Collaborator { + collaboration_id: String, + group: AgentRuntimePublicCollaboratorGroup, + }, +} + +struct AgentRuntimePublicQuestion { + id: String, // 沿用现有唯一 snake_case,最多 64 个 ASCII/UTF-8 字节 + header: String, + question: String, + options: Vec, +} + +struct AgentRuntimePublicQuestionOption { + id: String, // Shell 在 interaction 创建时分配并持久化的不透明 option identity + label: String, + description: String, +} + +enum AgentRuntimeAnswer { + Option { question_id: String, option_id: String }, + Freeform { question_id: String, text: String }, +} + +enum AgentRuntimePublicMediaKind { Image, Audio, Video, Document, Code, ProjectVersion, Other } + enum AgentRuntimePublicInteractionPresentation { UserInput { questions: Vec, + // V1 固定为 true;false 是 PUBLIC_STATE_INVALID,不是关闭自由回答的能力。 allow_freeform: bool, }, PolicyApproval { @@ -397,22 +1710,330 @@ enum AgentRuntimePublicInteractionPresentation { summary: String, allowed_decisions: Vec, }, + ToolApproval { + title: String, + summary: String, + risk_level: AgentRuntimePublicRiskLevel, + allowed_decisions: Vec, // V1 只能 approve/reject + }, } +enum AgentRuntimePublicRiskLevel { Low, Medium, High } + enum AgentRuntimeInteractionKind { UserInput, ToolApproval, PolicyApproval } enum AgentRuntimeInteractionScope { Project, Run, Action } enum AgentRuntimeInteractionAudience { User, Developer } -enum AgentRuntimeInteractionStatus { Open, Resolving, Resolved, Superseded } +enum AgentRuntimeInteractionStatus { Open, Resolving, Resolved, Superseded, Cancelled } enum AgentRuntimeInteractionPublicStatus { Open, Resolving } ``` -`interactionId` 在项目内稳定唯一,`interactionRevision` 从 1 开始并只在该 interaction 的可回答内容、约束或状态变化时递增;`responseId` 在单个 interaction 内唯一。`boundStateFingerprint` 绑定创建交互的 durable 对象与策略前提,不能用全项目 revision 替代。项目级 resume/retry 的 PolicyApproval 使用 `scope=Project`,可在锁内覆盖重新枚举出的多个 run,因此 agent/session/run/action 均可为空;ToolApproval 使用 Action scope 并绑定精确 action;UserInput 按真实落点使用 Run 或 Action scope。 +`interactionId` 在项目内稳定唯一,`interactionRevision` 从 1 开始并只在该 interaction 的可回答内容、约束或状态变化时递增;`responseId` 在单个 interaction 内唯一。UserInput 的 Public presentation 必须保持现有合同:question id 为唯一 snake_case 且最多 64 个 ASCII/UTF-8 字节,问题数 1–3,每题有 2–3 个 option,所有题都必须回答且自由输入始终可选,模型不能关闭“其他”。option identity 不从 UI label 推导:Shell 在 interaction 创建时按规范化 question id 与 option ordinal 分配 `optionId = "opt_" + lowerHex(sha256(canonicalJson({interactionId, questionId, ordinal})))[:32]`,将该 ID 与 option 顺序同时写入 private presentation、interaction request fingerprint、continuation item 和 resolution;同一 interaction 内 option 顺序不可变,label/description 或顺序变化必须递增 interactionRevision 并使旧 response stale。答案必须恰好覆盖当前全部 question id,每个 question 只能是一个 option 或一个 freeform;`Option` 必须提交 Public view 中的稳定 option id,不能提交 UI label。`boundStateFingerprint` 绑定创建交互的 durable 对象与策略前提,不能用全项目 revision 替代;若交互针对产物审批,`targetArtifact.artifactNamespace + artifactId + artifactRevision + digestAlgorithm + contentDigest` 必须同时绑定并在锁内复核,不能只靠用户意见正文或当前最新产物猜测目标版本。V1 只为已有稳定 immutable revision 且能在同一锁内计算 `sha256` 的 manifest/resource/artifact lineage 生成该 binding;当前只有 `resourceId`、可变路径、无 revision 或无法重读内容计算 digest 的对象不得开放 `requestChanges`,返回 `ARTIFACT_BINDING_UNAVAILABLE`,不新建一套平行 artifact identity。项目级 PolicyApproval 使用 `scope=Project`,必须把锁内重新枚举的精确 `agentId + parentRunId + runId + actionId + actionFingerprint` 集合固化为 `approval_target_set`,可一次覆盖多个 run 但只允许 `approve/reject`,不能把“当前所有 run”当作隐含目标;目标集合变更必须创建新 interaction。允许 `requestChanges` 的 PolicyApproval 必须使用 Run/Action scope 并绑定单一不可变 targetArtifact;User audience ToolApproval 使用 Action scope 并绑定精确 action,只允许 `approve/reject`;Developer audience ToolApproval 继续走 Developer Snapshot。UserInput 按真实落点使用 Run 或 Action scope。 -Public Snapshot 只投影 `audience=User` 且状态为 Open/Resolving 的最小 view;Open 可回答,Resolving 只显示处理中并禁用再次提交,Resolved/Superseded 从公开列表移除。正式写命令仍在执行时重新校验当前调用来源与项目策略,不公开 audience、request fingerprint、bound fingerprint、内部 actionId、策略字符串、工具名称/参数或动态 child 身份。`audience=Developer`(包括现行 ToolApproval)只进入 Developer Snapshot 的脱敏 debug interaction view;正式 Public 仅通过 Supervisor summary 的 `waitingOn=developer-approval` 表示暂停,不创建可点击 interaction。内部 private presentation 与 Public presentation 使用不同 DTO;Public presentation 采用严格 tagged union 和长度上限:UserInput 从本地私有 user-input sidecar 经过敏感信息/路径过滤后,复用现行最多 3 题、每题 2–3 选项与自由回答约束;若问题或选项不能安全公开则转为 `audience=Developer`/needs-reconciliation,不把原文带入 Public。用户 PolicyApproval 只含有界、脱敏的行为影响摘要和允许 decision。Public view 只允许 UserInput/UserInput 和 PolicyApproval/PolicyApproval 两种 kind/variant 组合,未知或不匹配的 variant 按不支持协议失败关闭;presentation 不是可执行 payload。Developer ToolApproval 使用独立 debug DTO。 +Public Snapshot 只投影 `audience=User` 且状态为 Open/Resolving 的最小 view;Open 可回答,Resolving 只显示处理中并禁用再次提交,Resolved/Superseded 从公开列表移除。正式写命令仍在执行时重新校验当前调用来源与项目策略,不公开 audience、request fingerprint、bound fingerprint、内部 actionId、策略字符串、工具名称/参数或动态 child 身份;`context=Collaborator` 只使用公开不透明 `collaborationId` 和普通用户组名,Shell 内部仍必须复核真实 `agentId + runId + parentRunId + actionId`。`audience=User` 的 ToolApproval 必须进入 Public Snapshot,提供脱敏行为摘要和风险等级,满足现有工作台“确认”入口;`audience=Developer` 的 ToolApproval 才只进入 Developer Snapshot 的 debug interaction view。内部 private presentation 与 Public presentation 使用不同 DTO;Public presentation 采用严格 tagged union 和长度上限:UserInput 从本地私有 user-input sidecar 经过敏感信息/路径过滤后,复用现行最多 3 题、每题 2–3 选项与自由回答约束,`allow_freeform` 在 V1 必须为 `true`,不能由模型或 sidecar 关闭;若问题或选项不能安全公开则转为 `audience=Developer`/needs-reconciliation,不把原文带入 Public。用户 PolicyApproval 只含有界、脱敏的行为影响摘要和允许 decision。Public view 允许 `kind=UserInput` 搭配 `UserInput`、`kind=ToolApproval` 搭配 `ToolApproval`、`kind=PolicyApproval` 搭配 `PolicyApproval` 三种组合,未知或不匹配的 variant 按不支持协议失败关闭;presentation 不是可执行 payload。Developer ToolApproval 使用独立 debug DTO。 -`answer` 仅接受 UserInput,`approve` 仅接受 ToolApproval/PolicyApproval;命令与 kind 不匹配返回 `INVALID_REQUEST` 且零副作用。`answer/approve` 的处理顺序为:先走 request ledger 重放检查,再取得 project execution owner 与 interaction lock,重读 record,校验 `interactionId + expectedInteractionRevision`、状态为 Open、bound fingerprint 仍与 durable 对象一致,然后重新执行当前权限和策略检查。通过后以 responseId 和 response fingerprint 原子转为 Resolving,调用既有内部 answer/confirm/reject/resume 实现,最后写 Resolved 和权威 command result;response fingerprint 覆盖 interactionId、interactionRevision、responseId、decision/answers,不能只散列自由文本。`approve` 必须显式携带 `decision=approve|reject`;禁止从按钮、命令名或缺省值猜测。 +`answer` 仅接受 UserInput,`approve` 仅接受 User audience ToolApproval/PolicyApproval 或 Developer capability 的 Developer ToolApproval;命令与 kind 不匹配返回 `INVALID_REQUEST` 且零副作用。`answer/approve` 的处理顺序为:先走 request ledger 重放检查,再取得 project execution owner 与 interaction lock,重读 record,校验 `interactionId + expectedInteractionRevision`、状态为 Open、bound fingerprint 仍与 durable 对象一致,并复核 `targetArtifact` 的 identity、revision 和 content digest 仍指向创建交互时的不可变产物版本,然后重新校验当前 principal capability、不可放宽的 hard-deny/sandbox/安全策略和交互绑定的 `policy_snapshot_fingerprint`。业务策略快照不因普通 live policy 文案变化而重新解释;若安全 hard-deny 收紧、目标 action/actionFingerprint 漂移或 policy snapshot 不可读,则零副作用返回 `INTERACTION_STALE` 或进入 `NEEDS_RECONCILIATION`,不能把旧 approve 施加到新 action。通过后以 responseId 和 response fingerprint 原子转为 Resolving,调用既有内部 answer/confirm/reject 实现;UserInput/approve-reject 可在权威结果闭合后写 Resolved,requestChanges 只有在 rework operation 已 durable prepared/enqueued 后才能写 Resolved,否则保留 Resolving/unknown;response fingerprint 覆盖 interactionId、interactionRevision、responseId、decision、feedback 和 answers 的完整规范化响应,不能只散列自由文本。`approve` 必须显式携带 `decision=approve|reject|requestChanges`;禁止从按钮、命令名或缺省值猜测。 -即使 Consumer 更换 command requestId,同一 `responseId`、相同 response fingerprint 也只创建新 command ledger 的幂等成功结果并返回原 interaction resolution,不重复消费;同一 responseId 不同内容失败为 `IDEMPOTENCY_KEY_REUSED`。已由其它 response 解决返回 `INTERACTION_ALREADY_RESOLVED` 并携带 `interactionRequired=false`;interaction revision、绑定对象或策略前提漂移返回 `INTERACTION_STALE`,零副作用。项目其它无关 Runtime/Snapshot 更新不使 interaction stale。禁止只凭持久化的 `"agent.resume"` 字符串或旧 policy 文案直接恢复。 +`answer/approve` 的 `InteractionResponseMeta` 还必须提交当前 `sessionId + expectedSessionRevision`。interaction record 的 `session_id` 保留创建时的 lineage/provenance;正常情况下二者必须相同,rotation 成功后只有 continuation set 中精确匹配 `interactionId + interactionRevision + recordRevision` 的 item 才允许 successor session 重新授权同一 interaction。该 proof 只授予继续原 interaction operation 的权限,不改变 `interactionRevision`、`boundStateFingerprint`、target artifact 或 response identity;Open interaction 可在 successor Snapshot 中继续展示并回答,Resolving interaction 只能恢复/读回原 response,不能接受第二 response。proof 缺失、interaction 未被捕获、session/revision 不一致或 rotation phase 不是 `Ready`,统一零副作用返回 `TARGET_BUSY`/`TARGET_STALE`/`NEEDS_RECONCILIATION`,不能按当前项目最新 Open interaction 猜目标。 + +`requestChanges` 与 `reject` 不是同一语义:`reject` 终止当前审批链并收束 run;`requestChanges` 是带意见的非终止工作流转移。V1 只允许 `scope=Run | Action`、恰好绑定一个不可变 targetArtifact 的用户 PolicyApproval 声明 `requestChanges`;覆盖多个 run 的 Project scope PolicyApproval 仍只允许 `approve/reject`,避免一段 feedback 模糊作用于多个目标。 + +处理 `requestChanges` 时,Shell 必须在 interaction lock 内先把 interaction 原子认领为 Resolving,同时预分配并保存 `reworkOperationId`、response fingerprint、过滤后的 feedback 和 target artifact binding;随后通过同一 project operation journal 创建/查找唯一 rework operation。只有 rework operation 已 durable `prepared/enqueued` 后才能把旧 interaction 写为 Resolved;崩溃恢复按 `reworkOperationId` 幂等补入队,不能重新解释 feedback 或创建第二操作。外部执行结果未知时旧 interaction 保持 Resolving 或进入 needs-reconciliation,不得伪造 Resolved。旧产物版本保持不可变;新版本生成并通过 lineage 校验后才创建下一次 PolicyApproval interaction,并写回 followUpInteractionId。若 rework 尚未物化,`InteractionAccepted` 返回 `follow_up_interaction_id=None`,Consumer 只读取 Snapshot/事件等待后续交互,不得重复提交;同一 requestId 或 responseId 重放按 ledger 当前状态返回成功、in-progress 或 unknown,不能把 unknown 伪装成原 resolution 成功。 + +即使 Consumer 更换 command requestId,同一 `responseId`、相同 response fingerprint 也不能重复消费:interaction 已 Resolved 时新 command ledger 返回原 resolution;仍为 Resolving 时返回 `COMMAND_IN_PROGRESS`;对应 operation 已 outcome-unknown 时返回 `COMMAND_RESULT_UNKNOWN`。同一 responseId 不同内容失败为 `IDEMPOTENCY_KEY_REUSED`。已由其它 response 解决返回 `INTERACTION_ALREADY_RESOLVED` 并携带 `interactionRequired=false`;interaction revision、绑定对象或策略前提漂移返回 `INTERACTION_STALE`,零副作用。项目其它无关 Runtime/Snapshot 更新不使 interaction stale。禁止只凭持久化的 `"agent.resume"` 字符串或旧 policy 文案直接恢复。 + + +### 1.5 Conversation / response stream 交付合同 + +conversation/response stream 仍是独立展示通道,但现有“Runtime final-reply stream”和新协议 `submit_intent` 的 direct reply 不是同一种交付,且 Start/根终态的 Runtime-owned status message 也不是 Provider response;不能共用一个没有来源标签的 ledger。V1 冻结两条 response 分支、一条 status-message 分支和一条不进入 Public conversation 的 LocalTransport 分支: + +1. `DirectReplyDelivery`:interaction kernel 判定为 direct reply 时,在 request ledger `prepared` 阶段预分配稳定 `responseMessageId`,绑定 projectId、sessionId、requestId 和 response operation identity;消息记录至少具有 `reserved | streaming | committed | outcome-unknown` 状态。只有该分支的 `IntentAccepted.responseMessageId` 可直接定位 command reply;Start/Steer 的最终 assistant 属于下面的 RuntimeFinalReply,不在受理 ack 中假装已经存在。 +2. `RuntimeFinalReply`:已有 durable Runtime run 的最终回复继续由现有 finalization v4、`responseRequestSlot`、Agent/Session/run 和 response fingerprint 约束;Shell 不另造一套 Runtime message identity,必须读取/复用 finalization journal 已生成的 `finalizationId + messageId`(当前实现由 `game_creator_agent_runtime_finalization_id` 与 `game_creator_agent_runtime_finalization_message_id` 生成),并在 adapter 中校验二者与 run/session 完全一致。Runtime state、conversation assistant 和 response stream 三者以同一 finalization identity 闭合。Runtime final reply 的流中间态不能被 submit_intent command 结果代替,最终 assistant 也不能反向改变 Snapshot 的生命周期结论。 + +3. `LocalTransportReply / LocalManagement`:现有 CLI `project_location`、GUI/CLI 的 `/project`、`/open-project`、`/switch-project`、绝对导出包路径和本地配置操作只在受信任本地 transport/独立面板显示;它们不是 Public conversation message,不进入 Runtime prompt、Snapshot、事件或 Runtime response stream。所有 absolute host-locator operation(包括 `/project`、`/import-canvas-export`、本地文件选择器结果及 CLI `project_location`)只保存 `locatorDigest + locatorHandleRef + path-free argumentFingerprint + projectId(若已解析)`,原始路径留在受信任 path resolver/OS handle 边界内;路径需要展示时只能作为本地 UI/CLI transport result 返回,不能由 assistant message 或公共错误承载。`project_location` 现有 `ProjectLocation` action 若继续保留,P5 必须改为该分支,禁止把绝对路径写入 `conversation`。 + +三条会话写入顺序也冻结:DirectReply(包括未知 slash command)必须使用 `AgentRuntimeConversationUserMessageBinding::Present`,先用其中的 `conversationUserMessageId` 幂等提交原始用户文本(slash 保留规范化 command line),回读 user commit marker 后再写 assistant response,最后才关闭 command ledger;Steer 必须先完成同一 `conversationUserMessageId` 的 `conversation-persisted`,再推进既有 steer ledger 的 `queued`,不得在 steer ledger 与 input envelope 各写一条用户消息;若恢复发现 user marker 已存在,只能复用它,若正文/来源不一致则 `IDEMPOTENCY_KEY_REUSED`/`NEEDS_RECONCILIATION`。Start 也必须使用 `Present`,沿用 user message → Runtime status message → queued/dequeue。RuntimeFinalReply 或 terminal failure status 若属于没有原始用户轮次的后台恢复,才可使用 `NotApplicable`;该分支仍必须绑定 `source_record_ref`,不得借此补写第二条用户消息。 + +Public conversation 使用独立于 Snapshot event、Provider response stream 和各私有 delivery ledger 的**单一会话全局序列**。每个 `(projectId, sessionId)` 在 conversation append lock 内为待提交的规范 message identity 预留严格递增且永不复用的 `sequence` 与不透明 `cursor`;只有提交并回读成功后才把该 cursor 接入私有 committed cursor chain 并对 Public 可见,预留后失败可以留下永久 sequence 空洞,但失败 reservation 不进入 committed cursor chain,同一 `messageId` 不得获得第二个 sequence/cursor。cursor chain 与 `afterCursor/nextCursor` 分页是 committed message 补读完整性的唯一权威依据;`sequence` 只用于同一会话内稳定排序和诊断,数值不连续是合法终态,不表示漏读,也不得触发 Consumer 全量重读或无限恢复。只有 committed cursor chain 断链、重复链接或漏掉已 committed message 才表示历史不完整。response stream 的 `sequence` 只表示同一 Provider/final-reply stream 的 chunk/source revision,**绝不等于 conversation sequence**,也不能作为 Public conversation `afterCursor`。Runtime status message 不进入 response stream,但以独立 commit marker、唯一 `runtimeStatusMessageId` 和 conversation read-back 恢复。Consumer 只消费下面冻结的 Public conversation read DTO,按 `(projectId, sessionId, deliveryKind, messageId)` 去重,并按 `sequence` 稳定展示;不得读取私有 status/event/response ledger、解析不透明 ID 前缀或把 chunk 合并成 Runtime Snapshot。只有对应分支的私有 commit marker 已提交、conversation message 以同一 message key/正文/作用域回读成功且 delivery/input record 已进入 `committed` 后,Public read 才能返回该消息;`reserved/streaming/failed/rejected/discarded/outcome-unknown` 均不返回。commit marker、finalizationId、Provider request/slot/chunk、locator/path 和内部 observation 永远不进入 Public DTO。任一 Provider 已调用但正文提交无法证明时,仅将该分支置为 `outcome-unknown` 并进入 reconciliation,禁止换 requestId 或新 finalization identity 生成第二条回答。公开正文沿用同一权限、长度和内容安全过滤;其中 user 复用 `Conversation.message` 上限,runtimeFinalReply 复用 response stream 上限,runtimeStatus/publicEvent 复用 Runtime status/public event message 上限,directReply 统一复用上表 `4,000` Unicode scalar/`16 KiB` UTF-8 有界安全文本常量。Shell 必须在 conversation append lock 内、提交 Public message/sequence/cursor 前完成 directReply 正文规范化、内容安全和双上限校验;超限或不安全正文只能使既有 response delivery 确定性进入 `rejected` 并返回有界 `INTERNAL` 安全摘要,不得截断或提交正文,也不得留下无法分页补读的 committed 消息。原始 Provider chunk、凭据、绝对路径和内部 observation 不进入 Public Snapshot、Public conversation、公开错误或审计摘要。 + +下面的 Public conversation request/message/page/provenance/error/enums 与五命令、Public Snapshot 使用同一 Rust 权威 wire 模块:字段统一 camelCase,无数据枚举统一 lowerCamelCase 字符串。Rust request decoder 必须拒绝缺失字段、未知字段、重复字段、错误类型、非法 `schemaVersion`、越界 `limit/afterCursor` 和超限 request JSON,不能依赖 serde 默认忽略未知字段;结构或类型错误在任何 transport 查询 conversation 前统一返回 `INVALID_REQUEST`,不支持的版本统一返回 `PROTOCOL_VERSION_UNSUPPORTED`。GUI、CLI 和测试不得自行放宽、补默认值或回退读取私有 ledger。`AgentRuntimePublicConversationReadRequest`、`AgentRuntimePublicConversationMessage`、`AgentRuntimePublicConversationReadPage`、`AgentRuntimePublicConversationProvenance`、`AgentRuntimePublicConversationReadError` 及其全部枚举必须从该 Rust 单一来源生成 TypeScript schema/decoder、transport validator、golden fixture 和 negative fixture;Public response 的 success page 与 error envelope 必须分别进入同一生成和验证链路,decoder 对缺失/未知/重复字段、错误类型、未知枚举/code 或错误 schemaVersion 必须确定性报协议错误,不能静默丢字段后继续渲染。新增字段或改变必填性、wire value、文本/ID/cursor/数组/JSON 上限均须提升 `schemaVersion`,不能借 Public read 的只读性质绕过版本规则。 + +Session rotation 不会重写上述 delivery/status/event record 的 `session_id`、`responseMessageId`、`finalizationId`、`runtimeStatusMessageId`、`eventId` 或 `conversationMessageKey`。rotation barrier 捕获的 `DirectReplyDelivery`、`RuntimeFinalReply`、Start/terminal `RuntimeStatusMessage` 和 `PublicEventMessage` 必须由 continuation item 精确授权给 successor 继续同一 operation:`reserved/streaming` 只能复用原 message identity 继续提交,`outcome-unknown` 只能读回或 reconciliation,Start status 未提交前不得 dequeue,terminal failure status 未提交前不得投影 `failed/failure`。若 record 已在 barrier 后发生 revision 变化、缺少 continuation proof 或 provider call evidence 不明,successor 不得重建 response/finalization/status/event identity;统一保留证据并进入 reconciliation。 + +```rust +enum AgentRuntimePublicConversationDeliveryKind { + User, // user + DirectReply, // directReply + RuntimeFinalReply, // runtimeFinalReply + RuntimeStatus, // runtimeStatus + PublicEvent, // publicEvent +} + +enum AgentRuntimePublicConversationRole { + User, // user + Assistant, // assistant + System, // system +} + +// 仅允许公开、安全且渲染所需的 lineage;不包含 finalizationId、Provider、 +// locator、path、tool/action 或私有 ledger identity。 +struct AgentRuntimePublicConversationProvenance { + run_id: Option, + source_agent_id: Option, + source_run_id: Option, +} + +struct AgentRuntimePublicConversationMessage { + schema_version: String, + project_id: String, + session_id: String, + delivery_kind: AgentRuntimePublicConversationDeliveryKind, + message_id: String, + // 同一 project/session 的 conversation-global 顺序;不是 response-stream sequence。 + sequence: u64, + cursor: String, + role: AgentRuntimePublicConversationRole, + public_text: String, + // 仅 publicEvent 为 Some,且必须逐字节等于 message_id;其它类型必须为 None。 + event_id: Option, + provenance: Option, +} + +struct AgentRuntimePublicConversationReadRequest { + schema_version: String, + project_id: String, + session_id: String, + // None 表示从 session Public conversation 的 committed origin 开始全量分页。 + after_cursor: Option, + limit: u32, +} + +enum AgentRuntimePublicConversationHistoryState { + // 已覆盖 session origin 以来全部 retained committed message。 + Complete, // complete + // V1 committed history 完整,但存在无法证明 identity 的 pre-V1 私有隔离项。 + LegacyEntriesIsolated, // legacyEntriesIsolated +} + +enum AgentRuntimePublicConversationReadErrorCode { + CursorInvalid, // CURSOR_INVALID + HistoryIncomplete, // CONVERSATION_HISTORY_INCOMPLETE + Internal, // INTERNAL +} + +struct AgentRuntimePublicConversationReadError { + schema_version: String, + code: AgentRuntimePublicConversationReadErrorCode, + retryable: bool, +} + +struct AgentRuntimePublicConversationReadPage { + schema_version: String, + project_id: String, + session_id: String, + messages: Vec, + // 本页最后一条 committed 消息的 cursor;该 cursor 在 session 生命周期内持续有效。 + // 空页沿用 afterCursor,首次空历史为 None。 + next_cursor: Option, + has_more: bool, + history_state: AgentRuntimePublicConversationHistoryState, +} + +// 不属于 Provider response stream;它是 Runtime-owned conversation status message。 +enum AgentRuntimeInteractionAction { + Answer, // answer + Approve, // approve +} + +// 用户消息可能来自 submit_intent/input envelope;后台恢复的既有 Runtime final reply +// 可以没有可证明的用户轮次,但不得伪造一个 user message identity。 +enum AgentRuntimeConversationUserMessageBinding { + Present { + conversation_user_message_id: String, + // reserved 阶段可以为 None;用户消息 commit 成功并回读后必须补齐, + // delivery committed 前不得仍为 None。 + conversation_commit_marker: Option, + }, + NotApplicable { + reason: AgentRuntimeConversationUserMessageAbsenceReason, + source_record_ref: String, + }, +} + +enum AgentRuntimeConversationUserMessageAbsenceReason { + BackgroundRuntimeRecovery, // 仅 RuntimeFinalReply/terminal failure status 可用 +} + +struct AgentRuntimeStatusMessageRecord { + envelope: AgentRuntimeDurableEnvelope, + runtime_status_message_id: String, + project_id: String, + session_id: String, + run_id: String, + command_request_id: Option, + conversation_user_message: AgentRuntimeConversationUserMessageBinding, + status_kind: RuntimeStatusMessageKind, // startAccepted | terminalFailure; 固定 runtime-public-status- 前缀 + conversation_message_key: String, + status: RuntimeStatusMessageStatus, // reserved | committed | failed | outcome-unknown + failure_code: Option, + commit_marker: Option, + message_sequence: Option, + conversation_cursor: Option, +} + +enum RuntimeStatusMessageKind { + StartAccepted, // startAccepted + TerminalFailure, // terminalFailure +} + +enum RuntimeStatusMessageStatus { + Reserved, + Committed, + Failed, + OutcomeUnknown, +} + +// 现有“进入聊天的安全 Runtime 事件”也不是 SnapshotChanged envelope。 +// 由 Rust event projector 生成,前端只消费 eventId + publicText。 +struct AgentRuntimePublicEventMessageRecord { + envelope: AgentRuntimeDurableEnvelope, + // Shell 私有 delivery record;正式 conversation delivery 只投影 eventId + publicText。 + event_id: String, + project_id: String, + session_id: String, // 创建该 event 的 session provenance;rotation 后保持不变 + parent_run_id: String, + source_agent_id: String, + source_run_id: String, + durable_event_id: String, + event_kind: String, + public_text: String, + public_payload_digest: String, + conversation_message_key: String, + commit_marker: Option, + message_sequence: Option, + conversation_cursor: Option, + discard_reason: Option, + status: PublicEventMessageStatus, // reserved | committed | discarded | outcome-unknown +} + +enum PublicEventMessageStatus { + Reserved, // reserved + Committed, // committed + Discarded, // discarded;安全/大小策略确定拒绝,不再重试 + OutcomeUnknown, // outcomeUnknown +} + +enum AgentRuntimePublicEventDiscardReason { + PayloadTooLarge, + UnsafePayload, + NotAllowlisted, +} + +struct AgentRuntimeResponseDeliveryRecord { + envelope: AgentRuntimeDurableEnvelope, + delivery_kind: ResponseDeliveryKind, // directReply | runtimeFinalReply + response_message_id: String, // DirectReply 预分配;RuntimeFinalReply 复用 finalization.messageId + command_request_id: Option, + conversation_user_message: AgentRuntimeConversationUserMessageBinding, + finalization_id: Option, + response_operation_id: String, + project_id: String, + session_id: String, + run_id: Option, + response_request_slot: Option, + // RuntimeFinalReply legacy adapter 必须保存捕获时的完整 source identity; + // 不能只保存可重新指向当前 sidecar 的 key。 + legacy_source_binding: Option, + conversation_message_key: String, + commit_marker: Option, + status: ResponseDeliveryStatus, // reserved | streaming | committed | rejected | outcome-unknown + message_sequence: Option, + conversation_cursor: Option, + provider_call_evidence: ProviderCallEvidence, +} + +struct AgentRuntimeLegacyResponseStreamBinding { + legacy_stream_key: String, + task_id: String, + session_id: String, + run_id: String, + request_kind: String, + request_slot: String, + applied_steer_cursor: u64, + response_revision: u64, + source_sequence: u64, + source_status: AgentRuntimeLegacyResponseStreamStatus, + // 按捕获时完整 legacy source record(含正文/finish reason)计算; + // 恢复时 sidecar key、全部 identity、status、sequence 或 digest 任一漂移 + // 都不得继续 finalization,必须进入 reconciliation。 + source_record_digest: String, +} + +enum AgentRuntimeLegacyResponseStreamStatus { + Streaming, + Ready, + Committed, + Discarded, + Failed, +} + +enum ResponseDeliveryKind { + DirectReply, + RuntimeFinalReply, +} + +enum ResponseDeliveryStatus { + Reserved, + Streaming, + Committed, + Rejected, + OutcomeUnknown, +} + +struct ProviderCallEvidence { + request_slot: Option, + provider_request_id: Option, + call_status: ProviderCallStatus, +} + +enum ProviderCallStatus { + NotCalled, + Started, + Completed, + OutcomeUnknown, +} +``` + +五类 Public conversation message 的映射固定如下,adapter 不得按正文、文件位置、时间戳或 ID 前缀猜类型: + +| `deliveryKind` | `role` | Public `messageId` 来源 | `eventId` | Public provenance | +|---|---|---|---|---| +| `user` | `user` | `conversationUserMessageId` | 必须为 `None` | 默认 `None`;不得暴露 command/input envelope 私有 identity | +| `directReply` | `assistant` | prepared 时预分配的 `responseMessageId` | 必须为 `None` | 默认 `None`;request/operation identity 保持私有 | +| `runtimeFinalReply` | `assistant` | 现有 finalization journal 的 `messageId` | 必须为 `None` | `runId` 必填;`sourceAgentId/sourceRunId` 仅在已有公开 lineage 可证明时填写 | +| `runtimeStatus` | `system` | `runtimeStatusMessageId` | 必须为 `None` | `runId` 必填;不公开 failure 内部堆栈或 status ledger identity | +| `publicEvent` | `system` | 与 `eventId` **同一不透明 identity、同一 wire value** | 必须为 `Some(messageId)` | `runId/sourceAgentId/sourceRunId` 按 allowlist event projector 的已验证公开 lineage 填写 | + +Public 去重键固定为 `(projectId, sessionId, deliveryKind, messageId)`;同键正文、role、eventId、provenance 或 sequence/cursor 任一不一致都表示 durable identity 冲突,必须阻断该 session 的增量投影并进入 reconciliation,不能选择“最新”副本。`sequence` 只用于 conversation-global 排序和诊断,不参与业务 identity或分页完整性判断;同一去重键的 at-least-once 重放必须返回完全相同的 message。不同 deliveryKind 即使偶然得到同一 messageId 也不是同一消息,但 Public event 的 eventId/messageId 等值规则是显式例外,不得再生成第二 conversation message identity。 + +`read_public_conversation` 返回 `Result`。合法 `afterCursor` 必须属于同一 project/session 且指向 committed cursor chain 中已确认的 Public conversation 位置,返回链上其后的 committed message;单页同时受 `limit<=256` 和 `1 MiB` JSON 上限约束,下一条完整消息会使任一上限超出时在该消息前结束本页,只有链上仍存在未返回的 committed message 时 `hasMore=true`,`nextCursor` 指向本页最后一条 committed message。任一单消息仍必须先满足自身 Public 正文上限,不允许为满足页大小而截断正文。`afterCursor=None` 表示从该 session 的 committed origin 开始全量分页,不表示“只读最新一页”;全量读取期间新 committed message 只追加到后续 cursor 页,不得改写已经返回的 sequence/cursor。 + +V1 选择与现有本地 append-only/durable conversation 一致的**逻辑永久保留**方案:一条消息首次对 Public 可见后,其 committed message、cursor、私有 predecessor/successor chain link 以及 session conversation origin/tail marker 必须保留到该 session 被显式删除;物理压缩、checkpoint 或文件合并只有在仍能按原 cursor 读出完全相同的逻辑链时才允许执行。因此 `nextCursor` 在 session 生命周期内持续有效,Public conversation read **不得返回 `CURSOR_EXPIRED`**;`CURSOR_EXPIRED` 只保留给有界 Public Snapshot event 日志。格式非法、属于其它 project/session 或从未由该 session 签发的 cursor 返回 `CURSOR_INVALID`,不夹带部分 page;Consumer 可以在确认本地 cursor 损坏或丢失后以 `afterCursor=None` 重新全量分页,但不能把服务端曾签发 cursor 的消失当作普通 invalid 后静默重置。 + +若任一已签发 cursor、committed message、chain link 或 origin/tail marker 丢失、物理截断,或者 chain 漏掉已 committed message,服务端必须返回不可重试的 `CONVERSATION_HISTORY_INCOMPLETE`,不返回 page、`nextCursor` 或 `historyState`,并进入 reconciliation;不得从“当前最早文件”继续并伪装成 `complete`。`CURSOR_INVALID` 对同一 cursor 不可重试;`INTERNAL` 在 V1 也不可重试,后续只有通过新错误码才能声明明确暂态语义。成功 page 的 `historyState=complete` 表示从 session origin 起的 V1 committed history 完整,`legacyEntriesIsolated` 只表示完整 V1 history 之外另有无法证明 identity 的 pre-V1 隔离项,绝不能表示 retention truncation。Consumer 遇到 `CONVERSATION_HISTORY_INCOMPLETE` 只能停止增量/全量合并并显示有界恢复状态,不能回退读取私有 ledger、`LocalConversationResult` 或用 `afterCursor=None` 掩盖服务端截断。 + +现有 `LocalConversationResult` 是受信任本地/Developer 管理 DTO,其 `path`、本地 session catalog 和磁盘顺序不能复用为 Public read 合同。Public adapter 必须从已 read-back 的 conversation commit 生成上述 path-free message/page,绝不返回 `path/absolutePath/finalizationId/commitMarker/providerRequestId/requestSlot/chunk/tool observation`。commit marker 只保存在 Shell 私有 delivery/input record 中,用于证明 Public message 已 committed;Consumer 既不能读取也不能提交它。 + +legacy conversation message 若没有 `messageId`,只有在现有 finalization/audit/input/steer 证据能唯一证明 project/session、五类 deliveryKind、规范 message identity、role、正文和 lineage 时,migration adapter 才能以该规范 identity 分配一次 conversation-global sequence/cursor,并保存不可变 migration binding;不得按数组位置、正文 hash、mtime 或相邻消息猜 ID。无法唯一证明的 legacy record 必须移入只读私有隔离索引,保留原证据且不进入 Public incremental/full read,page 返回 `historyState=legacyEntriesIsolated`;它只能在受信任 Developer/local history 中查看,不能阻塞新 committed message 继续使用新的 sequence/cursor,也不能在以后重新投影成另一个 Public identity。 + +`Present.conversation_commit_marker=None` 只允许存在于尚未提交用户消息的 reserved/准备阶段;`conversation_commit_marker=Some(...)` 必须经过 conversation read-back 校验。DirectReply、Start 和有用户轮次的 RuntimeFinalReply 在 delivery/status committed 前必须使用 `Present(Some(marker))`;只有有明确 durable 证据证明不存在原始用户轮次的后台 RuntimeFinalReply 或 terminal failure status 才允许使用 `NotApplicable`。 + +所有 delivery record 的 `committed` 状态都必须同时具有可读回的 `commit_marker + conversation_message_key + message_sequence + conversation_cursor`,并与 Public conversation message 的 project/session/deliveryKind/messageId/正文逐项校验;`reserved/streaming` 不得被 Consumer 当作消息已交付,`outcome-unknown` 不得通过新 message key 或新 sequence 补写第二条消息。Public event 的 `discarded` 状态必须具有 `discard_reason`、私有审计证据且不可重新投递,且不得带有已提交的 `commit_marker/message_sequence/conversation_cursor`;其它状态的 `discard_reason` 必须为空。Public event 的 `public_payload_digest` 必须等于 eventId 派生时使用的规范化 public payload digest,不能只依赖可变 `publicText`。 + +所有上述 record 的 `AgentRuntimeInputEnvelopeRecord`、`AgentRuntimeInteractionRecord`、`AgentRuntimeBuiltinManagementOperationRecord`、`RunLineageRecord`、Session/Handoff/ActiveSessionIndex/SessionRotation/HandoffManifest、status、public-event、response 以及 LocalManagement record 的 `envelope.record_id` 必须与业务 identity 一一对应;每条 record 的 `envelope.record_revision` 是该 record 的唯一 CAS revision,`checksum` 是去掉 checksum 字段后的 RFC 8785 canonical JSON SHA-256,`ledger_version` 单调递增,`owner_boot_id + owner_generation` 用于 fencing,`created_at/updated_at` 只作审计不能决定恢复胜负。校验失败统一隔离为 corrupt record 并保留原证据,不能用“最新文件”覆盖或静默降级;状态枚举之外的值、缺失的 commit marker、身份不匹配和重复 message key 都按 `outcome-unknown/needs-reconciliation` 处理。 + +现有 `response-streams/{agentIdHash}/{runIdHash}.json` 实际属于 `RuntimeFinalReply`:它按 `agentId/runId` 定位,记录还包含 `taskId/sessionId/requestKind/requestSlot/appliedSteerCursor/responseRevision/sequence` 等 lineage 与流身份字段;它没有 direct reply 的 `responseMessageId` 或 conversation message cursor。adapter 在 prepared 阶段必须把捕获时的这些字段、完整 source status/sequence 和含正文/finish reason 的 `source_record_digest` 写入 `legacy_source_binding`;恢复时只能校验同一 binding,不能按 key 重新读取当前 sidecar 猜测 lineage。状态是 `streaming/ready/committed/discarded/failed`,legacy adapter 必须对五种状态穷举映射:`streaming` 只能映射为 `streaming`;`ready` 只能映射为“待 finalization commit”,不能直接映射为 `committed`;`committed` 只有在 conversation layer 以同一 finalization journal 的 `finalizationId + messageId` 原子写入并可读回最终消息后才可提交;`discarded`/`failed` 只有在 durable evidence 证明没有 conversation commit、没有未决 Provider/进程副作用且可确定拒绝原因时才映射为 `rejected` 并保留不可重试 tombstone,否则一律映射为 `outcome-unknown` 并进入 reconciliation。legacy `committed` 若找不到对应 conversation commit marker、`AgentRuntimeConversationUserMessageBinding`,或其 messageId 与现有 finalization 不一致,也必须转 `outcome-unknown`,不得凭 sidecar 正文补写第二条消息。只有存在明确 durable 证据证明历史 finalization 没有原始用户轮次时,legacy adapter 才能生成 `NotApplicable { source_record_ref }` 的后台恢复记录;用户消息缺失、存在多个候选、正文/来源冲突或 lineage 无法唯一解析时,必须进入 `outcome-unknown/reconciliation`,不得使用 NotApplicable 掩盖歧义。DirectReplyDelivery 必须新增独立 ledger/stream adapter,不能把现有 final-reply sidecar 路径或 `ready` 状态冒充 direct reply 身份。 + +P0/P3 fixture 必须分别覆盖 direct reply 和 Runtime final reply 在预分配 messageId/finalizationId 后崩溃、stream 中断、正文 committed 但 command/finalization result 未写、同 requestId 重放、跨 transport 读回、steer/取消使旧 finalization 失效和 content-filter 拒绝;另覆盖 Start status message committed 前禁止 queued/dequeue、status write failure/unknown、根终态 failure status 先于 task/event/state 终态、prompt 排除固定前缀、public event 的 eventId/publicText 去重和恢复不重复 status message;并覆盖五类 Public message mapping、eventId/messageId 等值、conversation-global sequence/cursor 预留崩溃形成永久合法空洞且 Consumer 不重启补读、同 identity 重放、committed cursor chain 不漏消息、跨页并发追加、`afterCursor=None` 从 session origin 全量分页、已签发 cursor 经物理压缩后仍有效、格式/跨 scope cursor 返回 `CURSOR_INVALID`、message/cursor/chain/origin/tail 人为截断返回无 partial page 的 `CONVERSATION_HISTORY_INCOMPLETE`、conversation read 永不返回 `CURSOR_EXPIRED`、legacy message 唯一迁移或隔离、LocalConversationResult.path 与 finalization/Provider 私有字段零泄漏,以及 response-stream sequence 不能冒充 conversation sequence。验收以每条 delivery 分支最终 durable 消息唯一、cursor chain 补读覆盖全部 retained committed message、合法 sequence 空洞不造成恢复循环、截断历史不伪装完整和 Provider 调用计数不重复为准。 --- @@ -448,10 +2069,10 @@ Public Snapshot 只投影 `audience=User` 且状态为 Open/Resolving 的最小 ### P0 行为基线与协议验证框架 -**目标**:在改动前建立可判断迁移语义和新协议安全性的验证入口,不冻结旧 DTO。 +**目标**:在改动前建立可判断迁移语义和新协议安全性的验证入口,不冻结旧 DTO。P0 允许新增只读 fixture、golden input/output、crash-point harness 和测试辅助代码;禁止新增生产 handler、Consumer fallback 或改变现有 Runtime 生产行为。 - 复用确定性 Provider 与现有进程内 Runtime 测试,记录当前 master 的用户可见语义:提交模式、目标 run、等待/终态、批准/拒绝、取消、恢复及重复副作用计数;统一归一化随机 ID 和时间戳。 -- 为第 1 节建立尚未启用的契约 fixture:项目身份、Public 白名单、Developer capability、revision/cursor、request ledger 状态机、Interaction 状态机、结构化错误和崩溃点。 +- 为第 1 节建立尚未启用的契约 fixture:项目/session handoff 身份、不可变 Run target set/continuation set/manifest/rotation phase/active-session index 与 Public `sessionContext` 映射、`Prepared` operation 先于 fence 持久化、`FenceCommitted` operation/index marker 原子提交、barrier 后 Open/Resolving interaction、command/input envelope 和 direct/final/status/event delivery 的 successor 重新授权、LocalManagement parser/route/target 历史 identity 恢复、Snapshot/Command error 类型隔离、六个专业组父 run 投影与 retry/repair、Runtime-owned progress view、command capabilities、attachment binding、UserInput/User ToolApproval/PolicyApproval、slash built-in parser/交互门禁、intent/steer policy matrix、cancel matrix、ContinueRun/RetryTerminalRun/ReconcileRun、revision/cursor、request/response/status-message/public-event ledger、Interaction/rework 状态机、结构化错误和崩溃点。 - 基线比较只要求业务语义和副作用一致,不要求旧 DTO、旧事件名字或旧命令调用序列与新协议相同。 - P0 不修改 Runtime 生产行为,也不以空 handler、ignored 断言或永真 stub 让新契约提前通过。 @@ -462,7 +2083,7 @@ Public Snapshot 只投影 `audience=User` 且状态为 Open/Resolving 的最小 **目标**:先落不依赖公开命令和 Snapshot 的共享基础设施,避免 P1 命令反向依赖 P2/P3。P1 不注册五个新公开命令。 - 新增 `agent/supervisor_shell/` 内部模块,集中处理 canonical root、manifest project identity、调用来源 capability、结构化公开错误映射和 project lock 顺序;`schemaVersion`、五命令、错误码及 Snapshot/Interaction DTO 放入同一共享契约模块,由 Rust 与 TypeScript 绑定共同生成/校验,避免两端手抄漂移。 -- 实现带 schema 的 command ledger、interaction ledger、projection journal 基础读写:私有目录、原子写/回读、损坏隔离、锁、状态转移校验、同 ID 指纹冲突和 archive 生命周期。 +- 实现带 schema 的 command ledger、response message ledger、interaction/rework ledger、projection journal 基础读写:私有目录、原子写/回读、损坏隔离、锁、状态转移校验、同 ID 指纹冲突、authorization scope replay guard 和 archive/tombstone 生命周期。 - 将 RFC 8785 + SHA-256 规范化、request/response fingerprint、公开文本脱敏和稳定 ID 生成收敛为单一实现;禁止各命令自行拼接字符串做指纹。 - 明确锁顺序为 `project execution owner → supervisor project lock → command/interaction/projection 子记录`;不得持有文件锁等待 Consumer,也不得绕过现有 Runtime 的 run/action 锁顺序。长时间内部执行使用 ledger ownership 标记而非长期占用 transport 线程锁。 - 现有公开命令和 Runner 内存 request cache 行为不变;P1 只通过存储/状态机单测和 crash-point 测试验证底座。 @@ -473,27 +2094,28 @@ Public Snapshot 只投影 `audience=User` 且状态为 Open/Resolving 的最小 **目标**:先建立稳定、完整、可重连的唯一公开读模型,继续保留旧 read 接口供迁移。 -- 从现有 Runtime state、task/event、response stream 元数据和 pending sidecar 生成第 1.1 节双投影;Public 只包含当前 Project Supervisor,Developer 通过独立受信任 capability 读取选定内部 run。 -- 在 shadow/read-only 语义下把既有 user-input、pending tool confirmation 和可识别的 policy confirm 物化为稳定 Interaction record/view:首次投影在 owner/lock 内持久化 identity,后续按 bound fingerprint 复用;旧 sidecar 缺少可信绑定时投影 needs-reconciliation,不能每次读取生成新 interactionId。P2 只建立/刷新 record,不改变旧命令的交互行为。 +- 从现有 Runtime state、task/event、response stream 元数据和 pending sidecar 生成第 1.1 节双投影;Public 包含当前 Project Supervisor、同 run 的 Runtime-owned progress view、父 run 精确匹配的六个静态专业组、interaction 和 command/recovery capabilities,waitingOn/nextStep 使用稳定枚举,Snapshot error 与 Command error 使用不同 code/DTO;Developer 通过独立受信任 capability 读取选定内部 run。 +- 在 shadow/read-only 语义下把既有 user-input、User/Developer pending tool confirmation 和可识别的 policy confirm 物化为稳定 Interaction record/view:首次投影在 owner/lock 内持久化 identity、audience、context、action/policy fingerprint 和 target set,后续按 bound fingerprint 复用;旧 sidecar 缺少可信绑定时投影 needs-reconciliation,不能每次读取生成新 interactionId。P2 只建立/刷新 record,不改变旧命令的交互行为。 - project projection ledger 原子维护当前规范化 Public Snapshot、revision、event cursor、最近 256 条 envelope 及恢复 journal;每次 Public read 先在 projection lock 内修复未闭合 journal,再返回同一线性化点的 Snapshot/cursor。 - 提供 `read_game_creator_agent_runtime_snapshot`、Public 事件订阅和 `afterCursor` 有界补读;Developer read 使用独立命令/DTO,不与 Public 返回 union。 - Public revision 只由白名单变化推进;事件只发布 `SnapshotChanged`,同 revision 的恢复沿用同 eventId。测试不得依赖 Tauri best-effort event 自身保存补读历史。 -- P2 不删除 Consumer normalize/merge,也不注册五个写命令;只允许测试或 shadow observer 对照旧 read 与新 Snapshot。 +- P2 不删除 Consumer normalize/merge,也不注册五个写命令;只允许测试或 shadow observer 对照旧 read 与新 Snapshot。stale display 可以保留上一份 Snapshot 供展示,但只有上一份精确 cancel capability 可作为止损入口;其它旧 capability 必须禁用,且 Snapshot 恢复后不得把旧 capability 当作当前事实。 -**完成门禁**:重复、乱序、缺口、读订阅竞态、cursor 非法/过期、投影崩溃窗口和本地高 revision 均通过完整 Snapshot 收敛;正式 Public 零路径、完整任务/action/plan、动态 child、原始工具计划和 Provider 正文;Developer capability 服务端拒绝未授权来源。 +**完成门禁**:重复、乱序、缺口、读订阅竞态、cursor 非法/过期、投影崩溃窗口和本地高 revision 均通过完整 Snapshot 收敛;progress 不跨 run 合并且校验证据缺失不猜通过,六个专业组按当前 parentRunId 稳定排序,manifest fallback 不伪造 Runtime 状态,stale display 只允许上一份精确 cancel capability 且服务端重新校验;User ToolApproval/Needs input 可定位到 Supervisor 或专业组且不泄漏内部 action;正式 Public 零路径、完整任务/action/plan、动态 child、原始工具计划和 Provider 正文;Developer capability 服务端拒绝未授权来源。 ### P3 完整写协议与 Interaction Loop 收归 **目标**:在 P1 底座和 P2 唯一读模型都可用后,一次性注册真实可用的五命令;不发布“DTO 已存在但仍要求 Consumer 选择旧分支”的半成品协议。 -- `submit_intent` 将 CLI 的 Reply/Execute/Resume interaction kernel 和 GUI 的 start/steer 判定上提到 Shell:先决定 direct reply/execute/resume,execute 再读取当前 Project Supervisor 与 run profile 决定 start/steer;source 由 transport 派生,最终调用现有内部实现。 -- 接管 P2 已物化的 user-input/tool-confirm Interaction record,并为项目 resume/retry policy confirm 创建稳定 record;`answer/approve` 只按 interaction response meta 路由,approve 显式处理 approve/reject。 -- `cancel` 锁内校验精确当前 Supervisor run;`resume` 统一处理 pending、确定性 retry/timer、ready task 和 reconciliation policy,遇到需人工确认时创建项目级 PolicyApproval 而不是执行。 +- `submit_intent` 先将 GUI/CLI 现有 slash parser 收到 Shell:`/` 输入只走同一版本 parser,只读命令走 direct reply,副作用命令生成 Interaction,禁止成为自主 Runtime task;随后才将 CLI 的 Reply/Execute interaction kernel 和 GUI 的 start/steer 判定上提到 Shell:在 project lock 内按冻结 intent policy matrix 决定 direct reply/start/steer/reject;active root Run 已绑定冻结 Goal Contract 且 kernel 判定为 execute 时必须在旧 steer wrapper 前稳定拒绝为 `TARGET_BUSY`,direct reply 仍按原门禁交付;现有 replacement primitive 仅由显式 Goal management mutation 经其 operation identity/lineage 调用;`(intentKind, entryBindingKind, runProfile, inputPolicy)` 必须命中当前 capability 的同一 option,实际资源 binding 来自既有资源管理面,message 必须非空,附件进入同一 input envelope 且不能被丢弃。steer 在 prepared 阶段绑定现有 V1.13 steerId/cursor,source 只做受信任归因/权限/审计,不能改变业务路由。Continue/Retry/Reconcile 只能走结构化 `resume` tagged intent。 +- 接管 P2 已物化的 user-input/User/Developer tool-confirm Interaction record,并为项目 resume/retry policy confirm 创建稳定 record;`answer/approve` 只按 interaction response meta 路由,approve 显式处理 `approve | reject | requestChanges` 及其 feedback、policy snapshot 和 target set 合同。 +- `cancel` 按第 1.3 节取消矩阵锁内校验精确 Supervisor run;`resume` 只按 tagged intent 区分 ContinueRun、RetryTerminalRun、受信任 ReconcileRun。pending/timer/lane/ready task 的 Runner wake 与项目级自动恢复留在内部 recovery,不由 Public ResumeCommand 模糊触发;需要用户决定时创建项目级 PolicyApproval,而不是执行。 - 五命令全部先走 request ledger,再进行 target/interaction 校验和内部调用;成功、业务拒绝、并发 in-progress、崩溃可证明结果及 outcome unknown 都按第 1.3 节闭合。 -- Shell 负责生成 Public `stage/waitingOn/nextStep` 和安全 Interaction presentation;CLI 的 Reply/Execute/Resume、Consumer 的 start/steer/confirm/retry/resume 判断在此阶段只作为旧公开路径的兼容实现存在,不作为新命令输入。 +- 在同一阶段实现第 1.5 节独立 Public conversation append/read adapter:五类 committed message 共用 project/session conversation-global sequence/cursor,committed cursor chain 是分页完整性的权威,sequence 永久空洞合法且只用于排序/诊断;`read_public_conversation(afterCursor, limit)` 只返回 path-free Public DTO,并将 committed message/cursor/origin/tail 逻辑保留到 session 显式删除。DirectReply、RuntimeFinalReply、RuntimeStatus、PublicEvent 与 user input 的私有 commit marker/read-back 必须先闭合,不能把现有 `LocalConversationResult.path`、response-stream sequence 或私有 delivery ledger 暴露给新 Consumer。legacy 无 messageId 记录只按唯一 durable evidence 迁移,否则进入只读隔离并返回 `legacyEntriesIsolated`;物理截断或 cursor chain 损坏必须返回 `CONVERSATION_HISTORY_INCOMPLETE`,不得返回 partial page、`CURSOR_EXPIRED` 或伪造 `complete`。 +- Shell 负责生成 Public `stage/waitingOn/nextStep`、六个专业组状态、command/recovery capabilities 和安全 Interaction presentation;CLI 的 Reply/Execute/Resume、Consumer 的 start/steer/confirm/retry/resume 判断在此阶段只作为旧公开路径的兼容实现存在,不作为新命令输入。 - 新旧公开命令并存,但新命令从注册之日起即具备完整生产语义。所有旧写 wrapper 同时改为经过同一 Shell project lock 和 projection-dirty/Interaction 同步 adapter:旧接口可以没有新 requestId 保证,但不能绕过 Interaction 状态、Public 投影或与新命令并发写出矛盾事实。P3 通过进程内调用和专用协议 harness 验证,不提前迁移正式 CLI/GUI 调用点。 -**完成门禁**:五命令的同 requestId 重放、同键异内容、并发重复、业务拒绝重放、Runner 强杀读回和 unknown outcome 全部闭合;无关 Snapshot 更新不使 interaction 失效,interaction/策略漂移失败关闭;项目级 PolicyApproval 可安全覆盖锁内重新枚举的多个 run;新旧路径用户可见终态与副作用计数等价。 +**完成门禁**:五命令的同 requestId 重放、同键异内容、并发重复、业务拒绝重放、Runner 强杀读回和 unknown outcome 全部闭合;slash 已知/未知/带附件/开放 interaction 路由与现有语义等价且绝不创建自主任务;入口 intentKind/attachment 的同 requestId 异值命中 `IDEMPOTENCY_KEY_REUSED`,attachment 漂移零副作用,并发 intent 不创建第二个 Supervisor run;same-run steer 保持 runId/steer cursor 且不重复 conversation,冻结 Goal Contract 的 execute 请求返回 `TARGET_BUSY`、不会调用 replacement primitive;Goal management replacement 使用独立 operation identity/lineage,并覆盖旧树已取消但 replacement 未入队的 crash point;cancel matrix 逐行验证、CancelAccepted 不伪造终态且重复 cancel 共用 operation;ContinueRun 保持 runId、Supervisor/专业 RetryTerminalRun 生成唯一 successor lineage、prepared repair 只生成/引用唯一 Public PolicyApproval interaction 且在其解决前不开放 retry、ReconcileRun 不重放未知副作用;UserInput/User ToolApproval/Developer ToolApproval audience 隔离,无关 Snapshot 更新不使 interaction 失效,action/policy/target set 漂移失败关闭;`approve` 的 `requestChanges` 反馈过滤、单一不可变产物绑定、唯一 reworkOperationId 与后续审批交互可读回且不可重复;direct reply 的 durable responseMessageId/stream、Start/terminal Runtime status message 先后顺序与恢复闭合;五类 Public conversation message 的 mapping、全局 sequence/cursor、去重、cursor-chain 分页、合法 sequence 空洞、session-lifetime retention、`CURSOR_INVALID` 与 `CONVERSATION_HISTORY_INCOMPLETE` 失败关闭、legacy 隔离及零 path/私有字段跨 transport fixture 全部通过,且 conversation read 不产生 `CURSOR_EXPIRED`;新旧路径用户可见终态与副作用计数等价。 ### P4 Runner 自驱与安全恢复 @@ -504,6 +2126,7 @@ Public Snapshot 只投影 `audience=User` 且状态为 Open/Resolving 的最小 - 将进程内 `known_roots` 扩展为 AppData 私有 `game-creator-known-roots.v1`。记录稳定 project identity、canonical root、首次/末次登记时间和有效状态,不保存用户输入、Provider 内容或凭据。 - Unix 父目录/文件权限分别为 `0700/0600`;Windows 使用仅当前用户可访问的等价 ACL。写入使用同目录临时文件、文件同步、原子替换及目录同步(平台支持时);读取校验 schema、普通文件/非链接、owner/ACL、重复 identity 和重复 canonical path。 - root 每次使用前重新 canonicalize 并重读 manifest identity。目录消失只标记失效;搬迁仅在新的、已授权 locator 注册并能唯一证明同一 projectId 时更新,不主动遍历磁盘寻找项目。identity/path 冲突或目录簿损坏保留证据并进入 reconciliation。 +- root 注册权属于现有项目创建/打开管理面,不属于五个 Runtime 命令。项目首次创建或授权打开时,必须在发送任何 Runner wake 前写入 manifest projectId 并原子登记 canonical root;登记失败则项目不能进入自动调度。项目关闭只撤销 active control lease,不删除 ledger;归档/删除必须先 drain 并保留 request/interaction tombstone。复制目录若沿用同一 projectId 视为 identity 冲突,必须显式执行 clone-as-new-project 生成新 projectId,不能靠路径先到先得。 **wake、扫描与 owner**: @@ -517,13 +2140,20 @@ Public Snapshot 只投影 `audience=User` 且状态为 Open/Resolving 的最小 | durable 状态 / 条件 | 自动动作 | 禁止动作 | |---|---|---| | command `prepared` 且可证明未产生副作用 | 取得 owner 后继续同一 request | 不创建替代 requestId | +| session rotation `Prepared` 且 active index 未 fenced | 继续按旧 active session 投影 `Ready`;校验 operation 的 predecessor/successor、expected revision/epoch 和 owner generation,预期仍成立时继续同一 operation 的 fence commit,否则写 `Rejected` tombstone | 不投影 `HandoffInProgress`,不创建 successor session/manifest/handoff/集合或其它 rotation 副作用;不得在 index 已引用 operation 时把 Prepared 当作有效 fence | +| session rotation `FenceCommitted` / `HandoffManifestCommitted` / `SuccessorSessionCommitted` / `HandoffsCommitted` | 校验 operation/index 的 operationId 与 fence marker、不可变 target set、continuation set 与 manifest;只读显示旧 interaction/已提交 conversation,阻止新 requestId 和新 delivery identity;仅在所有 handoff、continuation 与 active-session marker 可证明后一次性切换 | 不使用 pending successor/handoff/continuation 控制 Run 或旧 record,不按当前扫描集合补目标,不开放写 capability;stale cancel 例外服从 rotation phase gate | +| session rotation `ActiveSessionCommitted` | 校验 `activeIndex.committedRotationOperationId == operation.operationId`、active-session marker、predecessor/successor session、source/successor revision、manifest operation/session/epoch、`activeIndex.runCreationEpoch == expected + 1` 和全部 handoff/continuation,幂等补 Public `Ready` 投影 | 不按 activeSessionId/最新文件猜已提交 operation,不同时接受旧/新 session,不重复生成 capability | +| session rotation `Prepared-before-fence Rejected` | 校验 active index 从未引用该 operation;只写 operation tombstone,不清 fence、不递增 epoch、不隔离或创建 successor/manifest/handoff/set,保留历史 `committedRotationOperationId`,幂等维持旧 `Ready` | 不修改 active index,不恢复 successor、不发送 Runtime wake、不复用被拒绝的 operation/successor identity | +| session rotation `FenceCommitted-after-fence Rejected` | 校验 operation/index 的 operationId 与 fence marker 一致、active-session marker 未提交且无未知结果;隔离该 operation 的 pending records,在同一 journal 只清除该 operation 的 fence/marker并按规则递增 epoch,保留历史 `committedRotationOperationId`,幂等恢复旧 `Ready` | marker/commit 状态不一致时不清 fence;不覆盖历史 committed id,不恢复 successor、不发送 Runtime wake、不把冻结 interaction 当新交互 | +| session rotation `ReconciliationRequired` 或 manifest/index/continuation 冲突 | 清空写 capability,保留所有 target/continuation、旧 interaction 和 delivery 证据并进入 reconciliation | 不选择最新文件、不自动开放 answer/approve/cancel/resume,不恢复旧/新双写窗口 | | command `executing` 且结果有可信 durable 证据 | 幂等补 command result / Public 投影 | 不重复内部或外部副作用 | | command `executing` 且外部结果未知 | `outcome-unknown` + `needs-reconciliation` | 不自动重放 | | Runtime `pending` 且身份可信、owner 已取得 | 可调度一次 | 不跨 owner 重复调度 | | 等待确定性 timer / lane release | 到期或 wake 后继续 | 未到期不轮询重放 | | Open user input / tool approval / policy approval | 保持 InteractionRequired,只刷新投影 | 不自动批准或把权限拒绝当恢复失败 | | interaction `resolving` 且结果可证明 | 幂等补 Resolved 和 command result | 不重新消费 response | -| interaction `resolving` 且外部结果未知 | supersede/reconciliation,保留 response 证据 | 不以第二 response 自动解决 | +| `requestChanges` rework `prepared/enqueued` 但旧 interaction 未闭合 | 按 operation identity 补齐唯一 rework link,再关闭旧 interaction | 不重解释 feedback、不创建第二 rework | +| interaction `resolving` 且外部结果未知 | 保留 response/target 证据并进入 reconciliation;旧 interaction 不伪造 Resolved | 不以第二 response 自动解决 | | Runtime `executing` 且 Provider/工具/进程结果未知 | `needs-reconciliation` | 不自动重放 request/action slot | | journal、ledger、interaction 或 run 身份损坏/冲突 | `needs-reconciliation` | 不猜测、不覆盖证据 | | 业务已完成、revision 尚未分配 | 由 dirty journal 生成唯一下一 revision/eventId | 不回滚业务事实、不跳号 | @@ -535,7 +2165,7 @@ Public Snapshot 只投影 `audience=User` 且状态为 Open/Resolving 的最小 **边界**:Runner 仍由 GUI 启动,保留 GUI-owner watchdog 与 `game_chat_release` 退出协议;本轮不实现开机自启或无 GUI 常驻。 -自动调度只允许处理以下状态:身份可信、已取得 project execution owner、项目不处于 draining、任务为 `pending` 且其依赖已满足,或确定性 timer/lane 到期且可证明尚未执行;投影补偿只允许重复生成已确定的 Snapshot/event 结果。`waiting for user input`、`waiting for policy approval`、`waiting for developer approval` 永不自动推进;`executing`、Provider/工具/进程结果未知、状态身份冲突和 `needs-reconciliation` 永不自动重放。Runner 的调度 worker 在 owner lease、GUI-owner lease 或 drain 状态任一失效时先停止 dequeue,再决定进行中工作如何收束;不得先取出任务后再补做 owner 检查。 +自动调度只允许处理以下状态:身份可信、已取得 project execution owner、项目不处于 draining、任务为 `pending` 且其依赖已满足,或确定性 timer/lane 到期且可证明尚未执行;这些都是内部 recovery intent,不得伪装为 Public ResumeCommand。投影补偿只允许重复生成已确定的 Snapshot/event 结果。`waiting for user input`、`waiting for policy approval`、`waiting for developer approval` 永不自动推进;`executing`、Provider/工具/进程结果未知、状态身份冲突和 `needs-reconciliation` 永不自动重放。Runner 的调度 worker 在 owner lease、GUI-owner lease 或 drain 状态任一失效时先停止 dequeue,再决定进行中工作如何收束;不得先取出任务后再补做 owner 检查。 所有自动动作都必须记录可恢复的 wake reason 和 operation identity。wake 只负责唤醒,不能证明任务仍可执行;worker 每次 dequeue 前重新读取 durable state、owner generation 和 project drain 状态,校验通过后才创建或继续同一 operation。兜底扫描发现不满足上述条件的 root 时只记录跳过原因,不改变 Runtime 状态;扫描过程不得为了“发现新项目”而遍历未登记目录。 @@ -547,22 +2177,23 @@ Public Snapshot 只投影 `audience=User` 且状态为 Open/Resolving 的最小 - 面向用户/自动化的 Supervisor CLI 改为 Public Snapshot + 五命令 + event cursor;现有 `--swarm-chat` 若继续展示完整专业 Agent 状态,必须明确归类为受信任开发 CLI 并走 Developer capability,不能一边读取内部字段一边宣称是正式 Public Consumer。 - 公开 e2e/fixture 迁移到同一协议;直接调用内部 start/steer/resume 的单元和恢复回归继续保留,不把内部能力误算为 Consumer。 -- GUI 正式 Supervisor 只使用 Public Snapshot;删除 normalize/merge、phase 文案、steer/start、confirm/retry/resume 和启动 schedule-ready 决策。开发窗口显式走 Developer read capability;开发者对当前 Project Supervisor 的写操作仍走同一五命令,专业/child Agent 的直接调试控制属于既有受信任管理面,不伪装成正式 Supervisor 协议。 -- Consumer 只按结构化 code/kind/retryable/interactionRequired 分流;事件去重、缺口和 cursor 过期都只触发完整 Snapshot 读取。 +- 面向用户的 CLI 与 GUI 会话列表统一改读 `AgentRuntimePublicConversationReadPage`:使用 `(projectId,sessionId,deliveryKind,messageId)` 去重、conversation-global sequence 排序、committed cursor chain 与 `afterCursor` 分页;首次读取或本地 cursor 确认丢失时以 `afterCursor=None` 从 session origin 全量补读。sequence 数值空洞是合法诊断信息,不得触发重读;服务端签发的 conversation cursor 在 session 生命周期内必须持续有效,该 read 不处理 `CURSOR_EXPIRED`。`CURSOR_INVALID` 不夹带 partial page;`CONVERSATION_HISTORY_INCOMPLETE` 必须停止合并并显示恢复状态,不能以 `afterCursor=None`、`LocalConversationResult.path` 或私有 ledger 掩盖截断。不得按 response-stream sequence 排序、从 eventId/ID 前缀猜 deliveryKind 或直接访问私有 delivery ledger。受信任 Developer/local history 可以继续使用独立本地 DTO,但必须在类型和调用面上与 Public read 隔离。 +- GUI 正式 Supervisor、Runtime-owned 进度卡、六组底栏状态、用户确认/Needs input 和专业失败重试只使用 Public Snapshot 的 collaborator/interaction/capability;上传先经既有项目资源管理面取得 immutable attachment binding,再以非空 message 调用 submit_intent。attachment-only 在 V1 明确拒绝,不能由 GUI 合成自然语言;附件必须在 input envelope 和 Provider/resource context 中可恢复。`preview.start`/`preview.validate` 继续只消费既有 preview authorization 与 `PreviewRegistry`,不从 Runtime Snapshot 的 `nextStep` 猜测,不被 P6 旧 Runtime 命令删除误伤。删除 normalize/merge、phase 文案、前端 slash 解析、steer/start、confirm/retry/resume 和启动 schedule-ready 决策。开发窗口显式走 Developer read capability;开发者对当前 Project Supervisor 的写操作仍走同一五命令,专业/child Agent 的直接调试控制属于既有受信任管理面,不伪装成正式 Supervisor 协议。 +- Consumer 只按结构化 code/kind/retryable/interactionRequired 分流;此处的事件去重、缺口和 cursor 过期仅指有界 `SnapshotChanged` event 日志,并只触发完整 Snapshot 读取,不得套用到逻辑永久保留的 Public conversation cursor。 - 迁移期 fallback 仅处理 transport 明确的 unknown command:该错误证明新 handler 未执行,才可调用旧命令。任何已到达新 handler 的结构化错误或超时都不得 fallback;超时只可同 requestId 重试/读回。 -**完成门禁**:三类正式 Consumer 的 Runtime 调用面一致,差异只剩输入输出形态;Public/Developer 类型无交叉;迁移前后用户语义和副作用计数等价。 +**完成门禁**:三类正式 Consumer 的 Runtime 调用面和 Public conversation read 合同一致,差异只剩输入输出形态;Public/Developer 类型无交叉,正式 Consumer 返回体不存在 `path/absolutePath/finalizationId/commitMarker/Provider` 私有字段;迁移前后用户语义、消息顺序和副作用计数等价。 ### P6 删除旧公开面并最终收口 **目标**:Interaction Contract 成为唯一稳定公开 Runtime 控制边界。 - 从 Tauri invoke handler 和其它正式 transport 删除旧 start/steer/confirm/reject/answer/cancel/retry/resume/schedule/read 注册、旧公开 DTO、旧事件和 migration fallback。 -- 删除 Consumer 旧调用点与生命周期分支;管理面 goal/compact/session/config 按第 1.3 节边界保留。 +- 删除 Consumer 旧调用点、生命周期分支和把 `LocalConversationResult`/response stream 当作正式会话补读的路径;正式 Public conversation 只保留第 1.5 节 message/read DTO、五类 mapping、committed cursor chain 和 session-lifetime logical retention。任何 cleanup/compact 旧路径都不得删除仍存续 session 的 Public committed message/cursor 或把截断历史重新标记为 `complete`。管理面 goal/compact/session/config 及受信任 Developer/local history 按第 1.3/1.5 节边界保留,但不得重新导出为 Public DTO。 - Shell 内部 start/steer/resume/recovery 函数、Runner 内部方法及验证这些能力的回归测试允许保留或重命名,不设置全仓旧名称为零的伪门禁。 - 同步 Runtime V1.1、智能体 App 实施计划、文档索引和长期架构记忆,确保本计划不成为与权威 Runtime 并行的冲突事实源。 -**完成门禁**:定向静态检查证明 invoke handler、正式 transport、Consumer 调用点和公开 DTO 不再引用旧协议;协议契约、进程内、确定性、独立进程恢复、真实 Runner 和前端验证覆盖最终调用面。 +**完成门禁**:定向静态检查证明 invoke handler、正式 transport、Consumer 调用点和公开 DTO 不再引用旧协议,也不引用 `LocalConversationResult.path` 或私有 delivery/finalization/Provider 字段;协议契约、conversation cursor chain/永久空洞/session-lifetime retention/截断失败关闭、进程内、确定性、独立进程恢复、真实 Runner 和前端验证覆盖最终调用面。 --- @@ -597,15 +2228,26 @@ Public Snapshot 只投影 `audience=User` 且状态为 Open/Resolving 的最小 | 风险 | 强制约束 | |---|---| | Snapshot 与 durable state 在文件崩溃窗口不同步 | projection journal + 单一锁序;Public read 先修复,事件永远只作提示 | +| 进度卡由 Consumer 跨 manifest/plan/event 猜结论 | Shell 投影当前 run 的结构化 progress/check outcome;无可信 receipt/evidence 就 unknown/省略,run 切换原子替换 | | 全项目 revision 导致无关更新误杀交互 | 回答 CAS 使用 interactionRevision;精确 target 命令锁内重读 durable identity | | 重试先做当前状态校验而失去首次结果 | 固定先查 request ledger,再做状态/策略校验;成功和业务拒绝都持久化 | | responseId 换 requestId 造成二次消费 | interaction resolution 同时保存 responseId/fingerprint,独立于 command requestId 去重 | | command/interaction executing 的外部结果未知 | outcome-unknown / reconciliation;禁止自动重放或换 ID | | Tauri 前端 devMode 被当作权限 | Developer read 只认服务端构建、窗口标签或显式受信任 capability | | Public interaction 正文可能泄漏私有问题、工具计划或路径 | interaction audience + kind 白名单 + 公共内容安全过滤;不安全内容降为 Developer/reconciliation,原始信息仅私有 sidecar/开发 capability | +| P5 误删预览/资源管理面或从 Runtime 状态猜授权 | preview/resource/session 管理面保持 sibling contract;只消费现行 authorization/immutable revision/PreviewRegistry,不纳入五命令 | | 目录簿泄漏本地路径或扫描失控 | 私有权限、仅 AppData 持久化、公开零路径、wake 优先和有界低频轮转 | | P3/P5 重复迁移导致阶段不可独立审查 | P3 只完成后端协议与 harness;P5 只切换 Consumer 和删 Consumer 决策 | | 旧名称静态检查误删内部能力 | P6 只检查正式 transport、Consumer 和公开 DTO | +| `resume` 模糊吸收 retry/schedule/reconcile | Public ResumeCommand 使用 tagged intent;ContinueRun 不换 runId,RetryTerminalRun 必须生成 successor lineage,自动 wake 不进入公开命令 | +| slash 命令被当作普通 prompt 或由 Consumer 分叉解析 | submit_intent 在 matrix 前统一调用带版本 Rust parser;只读命令 direct reply,副作用命令生成 Interaction,`/preview` 等绝不进入自主 Runtime task | +| CancelAccepted 被误当成 cancelled 终态 | 冻结取消矩阵;pending action/finalizing/unknown 不能虚假完成,Consumer 继续读 Snapshot | +| Command error 私有字段进入 Snapshot | SnapshotError 与 CommandError 分型,Public projection 零 requestFingerprint/requestId/replayed | +| requestChanges 在 resolution 与 rework 入队之间崩溃 | interaction Resolving 先预分配 reworkOperationId,operation journal 幂等补链,unknown 不伪造 Resolved | +| direct reply stream 超时后生成重复回答 | responseMessageId/response operation identity 在 prepared 预分配,durable message ledger 读回,unknown 禁止换 requestId | +| Runtime status message 与 Run 受理顺序错乱 | Start 先提交唯一 `runtimeStatusMessageId` 的 commit marker,再允许 queued/dequeue;写失败/unknown 不执行,根终态 failure status 先于其它终态投影 | +| 安全 Runtime 事件被前端拼成重复/泄漏消息 | event projector 只交付 Rust 生成的 `eventId + publicText`,不把 raw event 放进 SnapshotChanged;无身份、空正文、legacy/raw payload 丢弃 | +| “Runner 自驱”与 GUI-owner 门禁表述冲突 | 自驱仅指 owner/lifecycle lease 有效时不依赖 Consumer 轮询;无 GUI headless lease 未落地前返回 OWNER_UNAVAILABLE | --- @@ -615,17 +2257,23 @@ P0 建语义基线与契约 fixture → P1 建身份/权限/ledger/锁底座 → --- -## 8. 协议冻结输出 +## 8. 协议候选冻结输出 -本方案提交后,以下内容视为 V1 编码前的冻结合同,不在实现 PR 中由 Consumer 或 transport 自行解释: +本方案提交后,以下内容视为 V1 编码前的候选合同;只有第 10 节证据门禁闭合并重新评审后,才转为正式冻结合同。候选合同期间,Consumer 或 transport 不得自行解释或扩展: - 身份:manifest `projectId` 不可变;`sessionId` 来自会话管理面;`runId` 由 Shell 在 prepared 阶段预分配;所有 locator、owner generation 和内部 operation identity 不进入 Public 协议。 - 所有权:project execution owner、Runner owner、GUI-owner 分层;owner generation 是 fencing 权威;未取得 owner、draining 或 GUI-owner 失效时不扫描、不执行、不接受自动恢复。 - 投影:先 durable 业务事实,再按 dirty journal 补 Public projection;已提交事实但投影未刷新可幂等补偿,事实提交结果未知必须 reconciliation。 - 事件:项目级 at-least-once `SnapshotChanged`;`snapshotRevision`、`sequence`、`eventId`、opaque `cursor` 的持久关系不可改变;事件只通知,Consumer 只能重读完整 Snapshot。 -- 写入:五命令统一 request ledger;相同 requestId/指纹回放原结果,异指纹冲突;结果未知不换 ID 重放;结果读回只按 `(projectId, requestId)` 查询。 -- 交互:`interactionId + interactionRevision + responseId` 独立于全项目 revision;Shell 在锁内重读交互和策略;项目级 PolicyApproval 不要求绑定单一 run。 -- 公开面:Public 字段白名单和稳定枚举是唯一正式 Supervisor 展示合同;Developer Snapshot 必须经过服务端 capability;`tool_request` 不进入 Public。 +- 写入:五命令统一 request ledger;相同 requestId/指纹且 authorization scope 等价才回放原结果,异指纹冲突;结果未知不换 ID 重放;结果读回只按 `(projectId, requestId)` 查询。 +- 生命周期:CancelAccepted 只表示取消受理;ContinueRun 保持原 runId,RetryTerminalRun 生成唯一 successor runId,ReconcileRun 只允许受信任 capability;timer/lane/schedule/recovery wake 不属于 Public ResumeCommand。 +- 入口与边界:slash 输入先由 Shell 同一版本 parser 路由,严格使用 `BuiltinCommand(commandLine, expectedParserVersion)` tagged payload,不能作为普通 prompt/start/steer;普通 `Conversation` 与四种 `intentKind` 使用完整 policy matrix;模板/导入必须提交 typed `entryBinding` 及 immutable revision/digest;V1 ID、JSON、文本、答案和补读上限由 Rust 单一常量源生成,超限在 ledger 前失败。 +- 本地命令:`AgentRuntimeLocalManagementCapability`、`LocalManagementCommand/Response` 与 project-scoped targetRef 是独立候选合同;无 project/session 的命令不伪造五命令 meta,local path 仅在 resolver/OS handle 边界展示;同一 parser route 不能同时落入 Public conversation 和 LocalTransportReply。 +- 会话与 lineage:`conversation_session_id` 不自动等价 Supervisor session;session handoff 依赖持久 `supervisorLineageId + sessionRevision`,Retry 的 `acceptedRunId` 只是回显,唯一 predecessor/successor 由 `RunLineageRecord` 证明;Run target set 之外,Open/Resolving interaction、未终结 command/input envelope 与 response/status/event 还必须由不可变 continuation set 逐条证明,successor 不改写原 record identity。 +- 交互:`interactionId + interactionRevision + responseId` 独立于全项目 revision;UserInput、User ToolApproval 和 PolicyApproval 均有明确 audience/context/presentation 合同;Shell 在锁内重读 action、父子身份、policy snapshot 和 artifact binding。ApprovalDecision 冻结为 `approve | reject | requestChanges`,其中 `requestChanges` 只允许单一不可变产物绑定的 Run/Action PolicyApproval,必须携带有界、脱敏 feedback 和唯一 reworkOperationId;项目级多 Run PolicyApproval 只能针对固化的精确 target set 执行 approve/reject。 +- 交付:direct reply 的 responseMessageId 和 operation identity 在 command prepared 预分配;Runtime final reply 复用现有 finalization journal 的 finalizationId/messageId,不生成第二身份;Start/terminal Runtime status message 使用独立 `runtimeStatusMessageId + commit marker`,先后顺序和 prompt 排除规则固定;安全 Runtime event message 的 Public `messageId` 与 Rust `eventId` 使用同一 wire identity 且独立于 SnapshotChanged。五类 committed conversation message 统一投影为 path-free Public message/read DTO,去重键固定为 `(projectId,sessionId,deliveryKind,messageId)`;committed cursor chain/分页是补读完整性的权威,conversation-global sequence 只排序/诊断且允许永久空洞,response-stream sequence 不是 conversation sequence。committed message/cursor/origin/tail 在 session 生命周期内逻辑保留,conversation read 不返回 `CURSOR_EXPIRED`;物理截断返回无 partial page 的 `CONVERSATION_HISTORY_INCOMPLETE`,不能伪装 `historyState=complete`。commitMarker/finalization/Provider/locator 保持私有,legacy 无 messageId 只能按唯一证据迁移或隔离。conversation/response stream 独立持久、可补读且不参与 Runtime Snapshot,结果未知时不生成第二回答。 +- 兼容与生命周期:现有 response sidecar 必须经 delivery ledger adapter 穷举映射 `streaming/ready/committed/discarded/failed`,并持久化捕获时完整 source identity/digest;legacy `ready/committed` 不能直接当作消息 committed,`discarded/failed` 需有证据才能 rejected 否则 reconciliation;V1 不提供无 GUI CLI headless control lease,无有效 GUI-owner 时写入返回 `OWNER_UNAVAILABLE`。所有 durable journal 受跨平台 `DurabilityCapability` 门禁,无法证明持久化时禁止进入 executing。 +- 公开面:Public 字段白名单和稳定枚举是唯一正式 Supervisor/专业组展示合同;同 run progress、六组 collaborator、interaction 和 command capabilities 都必须受父 run/权限/数量边界约束;waitingOn/nextStep 不使用任意字符串,SnapshotError 与 CommandError 分型;Developer Snapshot 必须经过服务端 capability;`tool_request` 不进入 Public。 任何实现若无法满足上述合同,必须先修改本技术方案并重新评审,不得通过新增 Consumer fallback、缓存或隐式状态字段绕过。 @@ -635,10 +2283,75 @@ P0 建语义基线与契约 fixture → P1 建身份/权限/ledger/锁底座 → 1. Snapshot 是唯一公开 Runtime 状态事实;事件收窄为项目级 `SnapshotChanged`,不再公开可被误合并的 run/interaction/tool 状态载荷。 2. 项目 manifest `projectId` 是协议身份,`projectPath` 只是每次都要 canonicalize 和复核的私有 locator。 -3. Public Snapshot 只含当前 Project Supervisor 紧凑摘要与协作数量;Developer Snapshot 使用独立 DTO 和服务端 capability,前端 devMode 不算授权。 -4. 五命令不再统一滥用全项目 Snapshot revision:interaction 回答使用独立 interactionRevision,cancel 使用精确 run target,其余命令锁内校验 durable state。 -5. `approve` 显式携带 approve/reject decision;requestId 负责命令幂等受理/结果读回,responseId 负责 interaction response 去重,两层身份不可互相替代,未知外部结果不虚假承诺 exactly-once。 +3. Public Snapshot 除当前 Project Supervisor 紧凑摘要外,还必须提供当前父 run 下六个静态专业组的有界只读状态、专业 retry capability 和指向既有 approval interaction 的 repair view、UserInput/ToolApproval/PolicyApproval 和五命令 capabilities;动态 child 仍只计数。Developer Snapshot 使用独立 DTO 和服务端 capability,前端 devMode 不算授权。 +4. 五命令不再统一滥用全项目 Snapshot revision:interaction 回答使用独立 interactionRevision;cancel 使用精确 run target、稳定 cancelOperationId 和 `cancelling` 中间态;resume 使用带 capability target 的 ContinueRun/RetryTerminalRun/ReconcileRun tagged intent;same-run steer 另外保留 V1.13 的 steerId/cursor 合同。冻结 Goal Contract 的 active root Run 不接受普通 execute→steer,稳定返回 `TARGET_BUSY`;replacement 只属于显式 Goal management operation。 +5. `submit_intent` 显式携带稳定 `intentKind`,与表示执行方式的 `runProfile` 正交;capability 按 `(intentKind, runProfile, entryBindingKind, inputPolicy, attachmentMediaKind)` 冻结完整组合,Conversation message 在 V1 必须非空,immutable attachment binding、entry binding 和 input envelope 进入 requestFingerprint,source 只做受信任归因/权限/审计,业务路由由冻结 policy matrix 决定。`approve` 显式携带 `approve | reject | requestChanges` decision;requestChanges 的 feedback 有界、脱敏,绑定单一不可变产物和唯一 reworkOperationId。requestId 负责命令幂等受理/结果读回,steerId 负责 same-run 追加指令,responseId 负责 interaction response 去重,三层身份不可互相替代,未知外部结果不虚假承诺 exactly-once。 6. request ledger 明确“身份/权限 → 指纹 → 先查 ledger → 再校验状态 → 写 prepared → 执行 → 权威结果”的顺序,并持久化成功和业务拒绝;外部结果未知统一 reconciliation。 7. P1 改为内部持久协议底座,P2 先提供唯一 read model,P3 才注册完整可用的五命令;P3 不迁移 Consumer,P5 不再重复设计后端 Loop。 8. Runner 自驱补齐跨平台目录权限、丢 wake 恢复、启动有界轮转、interaction resolving 和 command executing 恢复矩阵。 -9. P6 只删除正式 transport、Consumer 和公开 DTO 的旧协议引用,内部 start/steer/resume/recovery 能力和回归测试保留。 +9. P6 只删除正式 transport、Consumer 和公开 DTO 的旧协议引用,内部 start/steer/resume/retry/recovery 能力和回归测试保留。 +10. direct reply 与 Runtime final-reply 是两条独立 durable response 分支,Start/terminal Runtime-owned status message 与安全 Runtime event message 另有独立 ledger;现有 response sidecar 只通过 finalization adapter 接入,不能把 Runtime `ready` 冒充 direct message;SnapshotError/CommandError、Public/Developer Snapshot 和 Runtime/response stream 均不得混型。 +11. 四次复核进一步补齐了现有项目所需的专业组状态与失败重试、用户确认、Needs input、slash 内置命令、上传附件、command capabilities、Runtime 错误分类、取消中间态、steer identity 和父 run policy snapshot;这些规则已有字段/状态机,但仍需 P0/P1 fixture 与 Unix/Windows 实测证明。 +12. 第五次冻结前审计移除了 slash 对 `continueProject + runProfile` 的伪装,改为严格 `BuiltinCommand` tagged payload,并把 parser version 放进请求、指纹和 prepared ledger;现役 `/preview`、`/agent-resume`、资源/记忆/画板导入和只读命令按当前代码逐项冻结路由,其中 `/agent-kill`/`/agent-retry`/`/agent-resume` 明确保持 legacy Agent run control 语义,不冒充正式五命令;CLI `/resume`、`/goal`、`/compact`、`/mcp`、`/quit`/`/exit` 也按其现役 management/observation 语义显式纳入 catalog。 +13. 新增 `preparing/publicStatusPending` 与 `terminalPending` 的 Public 状态映射,禁止 status message commit marker 之前 dequeue,禁止根失败 status message 之前公开 failed;同时冻结 direct reply、steer 的用户消息单次提交顺序和崩溃恢复。 +14. 补齐 durable input/status/event/response record 的统一 envelope、checksum、owner generation、ledgerVersion 和 corrupt 隔离,并将 status/event identity 改为 RFC 8785 canonical JSON 派生,消除直接字符串拼接的边界碰撞。 +15. 冻结 Supervisor 顶层 plan progress 与 progress view 的等式、task/plan journal 来源及同 run 证据边界;retry successor 改为 predecessor 全生命周期永久唯一,后续 retry 必须针对最新 successor;Supervisor approval target 的 `parentRunId` 允许且仅允许为 None。 +16. Session rotation 改为独立 `ProjectSupervisorSessionRotationRecord` 多记录恢复合同;active-session marker 提交前旧 session 保持 active,其中 operation/index 的 operationId 与 fence marker 一致、active-session marker 尚未提交且无未知外部结果时,incomplete handoff 可按 `FenceCommitted-after-fence Rejected` 合同隔离该 operation 的 pending records、同 journal 清除且只清除该 operation 的 fence/marker、按规则递增 epoch并保留历史 `committedRotationOperationId`,安全回退旧 active session;只有 operation/index marker 冲突、结果未知或 active-session marker 提交后校验失败时进入 reconciliation,不按最新文件猜成功。 +17. Local Management response 使用唯一 `ResponseMeta` 承载 requestId/fingerprint/replayed,ledger 前缺失 requestId 可用 `None` 表达;local reply/error message 增加字符、字节和控制字符边界;local operation 补齐 `outcomeUnknown -> needsReconciliation` 与 `NEEDS_RECONCILIATION` 读回。 +18. locator handle 增加 `NotAcquired/Acquired/Released/ReconciliationHeld` 状态;local-only terminal 与 project-linked terminal 分别定义释放条件,未知结果不得换 handle 或 requestId 重做。 +19. Session rotation 先持久化含 predecessor/successor/expected revision/epoch 的唯一 `ProjectSupervisorSessionRotationRecord(Prepared)`,再以同一 journal 原子提交 `FenceCommitted` operation marker 与 active-index fence marker;之后才固化不可变 `ProjectSupervisorRunHandoffTargetSetRecord + ProjectSupervisorRunHandoffManifest`。目标 payload/chunk 有 envelope、checksum、commit marker、固定 digest 和数量上限;fence 先于集合快照生效,任何新 Run/child/delegation 都拒绝或延后,不会落到 predecessor;manifest ref 在 Prepared/FenceCommitted 时为空,只在 manifest 可回读后补齐,manifest/index/phase 与 Public `sessionContext` 共享线性化边界。 +20. Session rotation 的 `Prepared` 单独存在时继续发布旧 active `Ready`;只有 `FenceCommitted` 及其后 pending phase 才映射为 `HandoffInProgress` 并清空写 capability。最终 active-session journal 必须同步写 `committedRotationOperationId=current operationId`,并以 operation/session/manifest/revision/epoch/active-session marker 全量相等关系作为 successor `Ready` 门禁。Rejected 拆为 Prepared-before-fence(只写 tombstone、不动 active index/epoch、保留历史 committed id)和 FenceCommitted-after-fence(校验 marker、隔离 pending、同 journal 清当前 fence并递增 epoch、保留历史 committed id);marker/commit torn state 进入 reconciliation、fail-closed。 +21. Local Management record 显式保存 `expectedParserVersion + route + targetRef + authorizationPrincipalRef`,`originatingCapabilityId` 仅作审计;恢复只按历史 operation identity 校验,当前 capability 可在 rotation 后重新授权,不得替代历史解析合同。 +22. Session rotation 还必须在 handoff barrier 固化 continuation set,覆盖 interaction、未终结 command/input envelope、DirectReply/RuntimeFinalReply、Runtime status 和 Public event;旧 `sessionId` 作为 provenance 保持不变,successor 只能凭 operation/manifest/record revision proof 继续原 identity,缺失或漂移统一 reconciliation;`HandoffInProgress`/stale cancel 的 phase gate 优先级已冻结。 + +--- + +## 10. 持续边界复核及冻结前补充审计:已修正的合同与编码前证据门禁 + +本轮复核只检查本方案直接承诺的公开交互协议、持久恢复、Runner owner、Consumer 迁移和旧公开面删除,不扩展到 Agent main loop、Provider 选型、提示词或无 GUI 常驻服务。以下不是未来优化,而是进入 P1/P2/P3 编码前必须闭合的合同: + +| 冻结项 | 当前不足 | 冻结证据 | +|---|---|---| +| intent policy matrix | 已冻结四种入口在五类状态下的唯一 disposition,并将 directReply 置于 interaction 门禁之后;模板/导入入口必须有 typed binding;冻结 Goal Contract 是优先于普通 active/waiting 状态的 execute barrier | P0 fixture 逐格断言唯一 disposition/error;冻结合同下 direct reply 正常、execute 稳定 `TARGET_BUSY` 且 replacement 调用计数为 0;并证明并发请求不创建第二 Supervisor run | +| slash built-in 路由 | 现有项目要求 `/preview`、`/agent-resume`、资源/记忆/画板导入和只读命令继续走现役语义;已改为严格 `BuiltinCommand(commandLine, expectedParserVersion)`,不再伪装成 Runtime intent/profile,并区分 legacy Agent run control 与正式五命令,且已按 App/帮助清单冻结完整 catalog | 当前全部 slash 命令逐项 golden fixture;GUI/CLI 同输入同 parser version、route/interaction/管理动作、副作用计数,带附件/entry binding/未知字段或嵌套 interaction 失败关闭,parser 升级与 prepared 恢复不漂移,`/preview` 零自主任务 | +| local management 参数恢复 | path-free Goal 文本、`/agent-resume` detail 等参数原先只有 fingerprint,无法在 owner 重启后恢复同一操作 | `privateArgumentRef` 有界私有 payload 与 fingerprint/checksum 一致性 fixture;host path 原文、secret 和内部 fingerprint 不进入 local/Public delivery;缺失或篡改统一隔离为 `CORRUPT_RECORD` | +| local management 错误响应 | 已改为 `Succeeded/Failed + ResponseMeta`,统一承载 requestId/fingerprint/replayed;缺失 requestId 可用 `None` 表示,并补齐 capability、scope、locator、幂等复用、执行中和 unknown-result 错误合同 | 每个错误 code、retryable、replayed、缺失 requestId 和 readback 行为 golden fixture;GUI/CLI 不解析中文 message;local response 不携带 `observedSnapshotRevision`/`interactionRequired`,projectId 只在成功解析结果中出现 | +| local response 输出边界 | `LocalTransportReply.text` 与 local error message 原先没有独立字符/字节/控制字符合同,路径或异常文本可能超出本地 transport/UI 边界 | reply `4,000` 字符/`16 KiB`、error `512` 字符/`2 KiB` 边界值、控制字符、长路径展示和错误摘要 fixture;不得截断后当作完整结果提交 | +| local durable command identity | 幂等合同要求 parserVersion/route/targetRef 固化,但不能把轮换后的 capabilityId 错当历史 operation identity | prepared/owner 重启/capability 轮换/targetRef 漂移 fixture;record 显式保存 parserVersion、route、targetRef、authorizationPrincipalRef,originatingCapabilityId 仅审计,等价新 capability 可恢复且权限收紧会失败 | +| local capability scope | `Project` scope 的 optional session 字段可能把 Goal mutation、resume 的 active-session 约束放宽,Global 与 project target 也可能混用 | Goal mutation 必须 session+sessionRevision+Goal target;Goal read 可 project-only;`/resume` 必须 session/run/recovery revision;`/project`、`/config` 不带 project/session target 的 scope fixture | +| locator handle 生命周期 | 已补 `NotAcquired/Acquired/Released/ReconciliationHeld`;local-only terminal 在无 project operation 时可释放,project-linked 必须等待双方 terminal,unknown 保留 reconciliation handle | `prepared→handle acquired→crash`、`executing→handle expired`、`outcome-unknown→retry/readback`、无 project operation 的 resolver rejection、project resolve 成功但 operation 失败;禁止换 handle/requestId 重做 | +| 数量与体积上限 | 已冻结 ID、command JSON、message、feedback、answers、补读和 response stream 的字符/字节/数量上限,且规定 Rust 单一来源 | 共享常量生成 TS schema/fixture;边界值、超限、组合爆炸在 ledger 前返回 `INVALID_REQUEST` | +| 现有项目专业 Agent 状态 | 仅有 `collaboratorCount` 无法满足当前工作台六个专业组状态栏、父 run 精确筛选、专业 Agent confirmation 和“当前项目重试”要求;已补 `collaborators`、opaque `collaborationId`、runtime/manifestFallback、retry/repair capability | `parentRunId` 过滤、六组固定顺序、旧父 run 隔离、manifest fallback 不伪造状态、专业 Agent retry/repair 竞争与唯一父子绑定 fixture | +| Runtime-owned 进度卡 | 现有工作台需要轮次、任务/计划、活跃 Agent、试玩/静态/代码/截图校验和返工摘要;若继续由客户端拼接会违反 Consumer 不承接业务真相 | 当前 run 结构化 progress fixture、四类 check receipt 映射、缺证据 unknown、run 切换不合并、路径/Provider/fingerprint 零泄漏 | +| preview/resource sibling contract | `preview.start`/`preview.validate` 与上传/登记不是 Runtime 生命周期命令,不能因统一 Shell 而由 Consumer 依据 nextStep 重造;本方案明确保留现有授权和 PreviewRegistry | preview authorization revision、一次启动/刷新、项目切换/Run 终止/写锁竞争和 P6 静态删除范围 fixture | +| Public command capabilities | 仅有 `nextStep` 会迫使 Consumer 自行推断可执行按钮;已补 `commandCapabilities`,提供按组合冻结的 intent/profile/binding/input target 与精确 session revision/run target,stale capability 失败关闭;冻结 Goal Contract 时 Conversation option 可保留 direct reply 入口,但不产生 replacement-steer capability | Snapshot revision 与 capability 同点、过期 cancel/resume/retry、冻结合同 capability + direct/execute 分流、无 capability 不显示可执行入口、读失败保留 stale display 但仅允许上一份精确 cancel capability 作为止损入口 | +| 用户确认与 Needs input | 原方案把 ToolApproval 全部放到 Developer,且未冻结 question/answer schema;已补 User audience ToolApproval、Collaborator context、1–3 题/2–3 option/自由输入/全量 answers 合同 | 普通工作台用户确认、Developer ToolApproval 隔离、问题重复/缺题/option label 冒充 id、action/parent/run 漂移和 interaction 恢复 fixture | +| 现有项目输入附件 | `submit_intent` 原来只有文本,无法承接工作台上传文件/既有资源引用;已补有界 immutable attachment binding、input envelope 和结构化 resource context,Runtime 不接收字节/path/token;V1 显式要求 message 非空 | 上传后 resource revision/digest 绑定、替换/删除/跨项目拒绝、附件顺序指纹、attachment-only 前置拒绝和恢复后附件不丢失 fixture | +| submit capability 组合完整性 | 独立 intent/profile/binding 数组会允许 Consumer 组合出未注册入口;已改为有限 option,每个 option 固定一个 runProfile、entryBindingKind、inputPolicy 和允许媒体类型 | option 上限、合法/非法笛卡尔组合、TextOnly 携带附件、Other 媒体、入口绑定类型错配均在 ledger 前失败 | +| input envelope 交付 | 现有 Runtime task 必须非空,附件不能校验后丢弃,也不能由前端伪造自然语言;已定义私有 input envelope 与结构化 resource context,direct/start/steer 共用同一身份 | prepared/强杀/恢复、attachment revision/digest 漂移、Provider 前失败、steer 与 direct reply 均能读回同一 envelope 且不泄漏路径/token | +| Runtime 失败与取消中间态 | 原来的公共错误 enum 混合 command error 与 Runtime failure,且没有 `cancelling/paused/finalizing` 的可展示状态;已拆分 SnapshotErrorCode/CommandErrorCode 并补状态 | 配置/鉴权/限流/Provider/验证/sandbox/预算错误脱敏映射;CancelAccepted→cancelling→cancelled/needsReconciliation,重复 cancel 共用 cancelOperationId | +| steer 与同 run 语义 | `submit_intent` 选择 steer 但 ack 未返回现有 steer 身份;已规定 prepared 阶段保存 `steerId`,复用 V1.13 cursor/容量/中断/finalization 合同;现有冻结 Goal Contract replacement 分支不得被普通 submit 调用 | same-run steer 不新建 task/run、steerId 幂等/冲突、冻结合同 execute=`TARGET_BUSY`/replacement 调用计数为 0、Goal management replacement 的 operation identity/lineage 与 cancel-old→start-new crash 恢复、confirmation/process action 不被暗中中断、steer/finalization 双向竞态 | +| approval policy snapshot | 只重新检查 live policy 会与 V1.38 的父 run 持久 policy snapshot 冲突;已补 `policy_snapshot_fingerprint`、精确 Project target set 和 hard-deny 例外 | 普通 policy 文案漂移不重解释;hard-deny 收紧、action fingerprint 漂移和 target set 变化 fail-closed | +| session handoff/lineage | 已定义 target-set payload/chunk、rotation fence、`ProjectSupervisorRunHandoff` 的 manifest/operation/digest identity、interaction/delivery continuation set 和 `ProjectSupervisorSessionRotationRecord`;先持久化 `Prepared` operation,再由同一 journal 提交 `FenceCommitted` operation/index marker;Prepared 的 manifest ref 允许为空且仍投影旧 active `Ready`,FenceCommitted 后才进入 `HandoffInProgress`/无写 capability;最终 journal 同步提交 `committedRotationOperationId=current operationId`,Ready/recovery 校验 operation/session/manifest/revision/epoch/active-session marker;Rejected 按 fence 是否提交拆为两个不混写 active index 的分支 | operation 写入前崩溃不留 fence;Prepared 后/fence 前崩溃继续旧 Ready;operation/index fence marker 半提交必须 reconciliation;Prepared-before-fence Rejected 断言不清 fence、不增 epoch、保留历史 committed id;FenceCommitted-after-fence Rejected 断言 marker 匹配、隔离 pending、只清当前 fence并精确增 epoch、保留历史 committed id;最终 commit 断言 committed operationId、predecessor/successor session、source/successor revision、manifest operation/session/epoch、active index `expected+1` 和 active-session marker 全量匹配;FenceCommitted 后 target/continuation set 缺失/损坏/超限、manifest ref 补写、barrier 后 revision 漂移、rotation fence 与新 Run/interaction/delivery 并发、旧 record successor 重新授权、各阶段崩溃、部分 handoff、Public sessionContext/capability 线性化、active index 缺失、重复/双窗口 rotation、旧 session 永不复活、无 proof 的 `TARGET_STALE` fixture | +| submit payload 与 parser version | 原 slash 方案要求无意义的 `continueProject + runProfile`,且 capability version 未进入请求;已改为 Conversation/BuiltinCommand strict tagged union,BuiltinCommand 显式提交 expected parser version,prepared 后固化 route/version | variant/未知字段/版本漂移、同文本 parser 升级后恢复、同 requestId 异 payload、GUI/CLI 复放只复用已固化结果 fixture | +| interaction kernel 隐式 resume | 当前 `agent/interaction.rs` 仍暴露 `runtime_resume` 工具,CLI active runtime 会把模型返回的 `AgentInteractionAction::Resume` 直接送入 recovery observe;这会让自然语言/模型分类绕过显式 ResumeCommand,且与“继续只走普通 message/steer”冲突 | `runtime_resume` tool 输出、自然语言“继续”、active/non-active Runtime、显式 `/resume` 的 golden fixture;迁移后模型 action 不得直接获得 Resume capability,所有 recovery/steer 都要带显式 target、ledger 和 revision | +| CLI slash catalog 漏项 | `swarm_cli/input.rs` 现役还有 `/resume`、`/goal ...`、`/compact`、`/mcp`、`/quit`/`/exit`,与 GUI 的 `/agent-resume` 和项目摘要命令语义不同;只写 GUI catalog 会造成 CLI fallback/语义漂移 | CLI-only 命令逐项 parser fixture;`/resume` recovery observe、Goal status/CAS mutation 及其后续 Runtime turn、`goal pause/clear` 不取消 Runtime、compact 无 provider、mcp direct reply、quit 无 ledger,GUI/CLI 同 parser 但 route 差异仅来自显式 catalog | +| slash path/classification boundary | 已把项目级 `BuiltinCommand` 与 `LocalManagementRoute` 分开;补齐 local capability、request/response、capabilityId/targetRef 和单一落点矩阵;absolute host locator 只能走 trusted resolver/local envelope,project-relative `/asset-register`、`/import-canvas-asset`、`/read` 先做相对路径/符号链接逃逸校验;未知 slash 携带 host locator 时不进入 direct reply;`/goal` exact 与 `/goal <目标>` 按 arity 分开 | known/unknown slash、绝对路径、`file://`、`..`、symlink escape、project-relative path、无 project/session、targetRef 漂移、local response 不进入 Public conversation、GUI/CLI 同 parser 和原文不进入 Public fingerprint fixture | +| legacy slash lifecycle 误映射 | 当前 `/agent-kill` 只是写 legacy trace,`/agent-retry`/`/agent-resume` 会从最近 goal 启动新 generation;若直接映射 cancel/retry/continue 会丢失旧 target、detail 和 lineage 语义 | legacy trace target revision/digest、kill 不产生 Runtime cancelled、retry/resume 新 generation 的 operation/lineage、旧 trace 替换和跨 transport replay fixture | +| public hard-gate status | `preparing/public-status-pending` 与 `terminal-pending` 原先只是内部文字;已新增稳定 Public status/stage/waitingOn/nextStep 枚举和 commit marker 映射 | user message/status commit/dequeue 崩溃点、status 写失败/unknown、根失败 status 先于 failed/task/event 终态、Consumer 不把 preparing/terminalPending 当 queued/failed fixture | +| conversation user-message 顺序 | Start 已有 status 门,但 DirectReply/Steer 的 user message 可能重复或越过 ledger;已冻结 direct reply 先 user message→assistant→ledger close,steer 复用 V1.13 conversation-persisted 和同一 message identity | direct/slash/steer 崩溃恢复、同正文/同身份回放、重复 user message、未知 commit marker、旧 action/confirmation 不被 steer 越过 fixture | +| 本地 path / project_location 泄漏 | 当前 CLI `ProjectLocation` 会把 `root.display()` 持久化到 conversation;`/project` 与 `/import-canvas-export` 也携带绝对路径,若沿用 DirectReply 会违反 Public 零路径合同 | LocalTransportReply/LocalManagement 分支 fixture;路径只在 trusted resolver/local UI 返回,Public conversation/Snapshot/event/error/prompt/fingerprint 无原文,projectId 未解析时不伪造 ledger target | +| Builtin management operation 幂等 | slash 管理动作原先只有 UI pending command,重试/恢复可能重复 checkpoint、导入、记忆、Agent control 或 External Editor 副作用;已补项目级 management operation record,global CLI action 明确不进 ledger | 每类 management action prepared/执行/结果未知/恢复、同 requestId 异 fingerprint、旧 target revision/digest、domain CAS 与 operation result 不一致 fixture | +| durable delivery envelope | 新增 input/status/event/response record 但需要统一恢复字段;已补 schemaVersion/recordId/ledgerVersion/checksum/owner boot+generation/timestamps 和 corrupt 隔离;interaction 与尚未解析项目身份的 local management 也分别绑定 project/local envelope,response delivery 具备可回读 commit marker | 每类 record 缺字段、checksum/身份/版本冲突、旧新文件并存、owner fencing、跨平台原子替换和结果未知 fixture;local→project operation link 丢失或重复副作用 fixture | +| Public conversation message/read envelope | 私有 delivery record 已有 message key/commit marker,但现有 `LocalConversationResult` 只有 role/content/messageId 并暴露 `path`,Consumer 无法按 deliveryKind 做跨 transport 去重、排序和 cursor 补读;已冻结五类 path-free Public message、eventId/messageId 等值规则、conversation-global sequence/cursor、committed cursor chain、session-lifetime logical retention、read page/historyState、read error envelope、directReply `4,000` scalar/`16 KiB` 双上限和私有字段边界;全部 Public conversation DTO 由 Rust 单一来源生成严格 TS schema/decoder | user/directReply/runtimeFinalReply/runtimeStatus/publicEvent 五类 golden mapping;directReply 分别恰好命中 scalar/UTF-8 边界、任一维度超限、多字节组合、同 requestId 拒绝读回和单消息不阻塞分页;request、success page 与 error envelope 分别覆盖缺失、未知、重复、类型错和版本错误的跨 transport negative fixture,error DTO 另覆盖未知 code;另覆盖同键重复/异正文冲突、乱序返回按 sequence 稳定展示、reservation 崩溃形成永久空洞但不触发重读、cursor chain 不漏 committed message、跨页新写入、空页、256 条/1 MiB 边界、`afterCursor=None` 从 origin 全量补读、物理压缩后旧 cursor 仍有效、格式/跨 scope cursor 为 `CURSOR_INVALID`、message/cursor/chain/origin/tail 截断为无 partial page 的 `CONVERSATION_HISTORY_INCOMPLETE` 且不得返回 `complete`、conversation `CURSOR_EXPIRED` 不可达、response-stream sequence 混淆拒绝、LocalConversationResult.path/finalization/commitMarker/Provider 字段零泄漏 | +| progress invariant | 顶层 step 计数与 progress 内 task/plan 计数可能重复且来源不明;已冻结顶层等于同 run planProgress、task/plan journal 分源、receipt 不一致 unknown/省略 | plan 缺失、task/plan 不一致、run 切换、旧 evidence 混入、check 重复和每种 check 多条 fixture | +| approval parent scope | Supervisor action 没有 parent run,但 ApprovalTarget 曾要求必填;已改为 `Option` 并冻结 Supervisor=None、专业 Agent=当前父 run | Supervisor/专业 Agent target-set 指纹、父 run 漂移、None/Some 错配和 approve/retry 权限 fixture | +| retry successor lineage | 已定义带 `agentId/taskId/sessionId/parentRunId/delegationId` 的 `RunLineageRecord`;`acceptedRunId` 仅为回显,V1 不接受 `nextRunId`,且 predecessor 全生命周期最多一个 successor;旧 retry record 只能经唯一性校验 adapter 导入 | Supervisor/专业 Agent 终态 retry、同 predecessor 并发 retry、崩溃后重放、父 Supervisor 已终态、delegation 漂移和 needs-reconciliation 拒绝测试 | +| response delivery 分支 | 已发现现有 `response-streams` 是 Runtime final-reply,不是 direct reply;已拆 `DirectReplyDelivery` 与 `RuntimeFinalReply`,Runtime 分支复用现有 finalization journal 的 messageId,legacy adapter 穷举 `streaming/ready/committed/discarded/failed` 并持久化 source identity/digest,不能把 `ready/committed` 伪造成 direct message,`discarded/failed` 无充分证据时进入 reconciliation;Start/terminal status 另走 status-message ledger,只有私有 commit marker/read-back 与 conversation-global sequence/cursor 同时闭合后才进入 Public read | user-message→status→queued crash points、两条 response 分支与 status-message 分支分别做 crash-point、Provider 调用计数、finalization/message/status commit marker 唯一性、conversation sequence/cursor 唯一性和跨 transport golden replay | +| stale display 与止损取消 | Public read 暂时失败时既不能把缓存当事实,也不能让用户失去取消入口;已规定只保留上一份精确 cancel capability,但 rotation/draining/reconciliation phase gate 优先,服务端重新授权/锁内校验,其他旧 capability 禁用 | 读失败/恢复、读失败期间开始 rotation、目标已变化、owner 不可用、重复 cancel 和 cancel operation 读回 fixture;不得从 stale Snapshot 推进 submit/resume/retry,不能以 stale cancel 绕过 HandoffInProgress | +| Runtime public event message | 现有项目要求安全 Runtime 事件进入聊天,但不能把 raw event 载荷并入 Snapshot;已定义 Rust 生成 `eventId + publicText` 的独立 conversation delivery,Public `messageId` 必须与 eventId 同值、deliveryKind 固定为 publicEvent、role 固定为 system | Supervisor/主 Agent/直接 child 父 run 过滤、eventId/messageId 不等、eventId/publicText 缺失或重复、同 event 重放保持 sequence/cursor、legacy/raw payload 泄漏和恢复去重 fixture | +| targetArtifact binding | 已扩展为 namespace/id/revision/algorithm/digest;仅允许可重读、不可变且可算 `sha256` 的现有 lineage,其他目标返回 `ARTIFACT_BINDING_UNAVAILABLE`,不造平行身份 | artifact/resource/manifest 适配表及 digest 漂移、删除、替换和旧版本审批 fixture | +| owner 与 headless 能力 | 已冻结 V1 不提供无 GUI CLI headless control lease;CLI 写入只能复用有效 GUI-owner,否则 prepared 前 `OWNER_UNAVAILABLE` | GUI-owner 断线、CLI 读写权限、owner fencing 和“无 GUI 不写入” fixture | +| 文件系统原子性 | 已补齐能力门禁:Unix 要求文件与目录同步;Windows 要求 `FlushFileBuffers` + 原子替换;任一平台无法证明持久性时禁止从 prepared 进入 executing,并隔离 torn record | Unix/Windows crash-point、损坏隔离、旧 owner fencing、恢复后副作用计数与证据保留实测 | + +本次已把上述问题从“泛化待定”修正为可编码的字段、状态机和失败闭合规则,但证据 fixture/跨平台实测尚未完成;在证据完成前,文档状态仍为“评审中”,不得把 P1 基础 DTO 或空 handler 当作协议已完成。停止继续扩展本轮审查的条件是:上述每项都有权威字段/状态机、可达失败路径和验收 fixture;cancel/resume/retry/requestChanges/direct-reply/slash-route/attachment/input-envelope/user-approval/collaborator-status/progress-view/stale-cancel 的崩溃矩阵无未分类窗口;冻结 Goal Contract 的普通 execute→steer 必须稳定 `TARGET_BUSY` 且 replacement 调用计数为零,显式 Goal management replacement 的 operation identity/lineage 与旧树取消后崩溃恢复必须闭合;BuiltinCommand parser version、命令 catalog(含 CLI-only aliases)和 management/runtime route 没有语义漂移;LocalManagement parser/route/target 历史 identity 不被新 capability 替代;SessionRotation Prepared operation、FenceCommitted operation/index marker、manifest ref、最终 committed operationId/session/manifest/revision/epoch/active-session marker、两类 Rejected、phase/continuation 与 Public sessionContext/capability 没有线性化漂移;旧 interaction/delivery 只能凭 continuation proof 继续;legacy Agent run control 和 interaction kernel resume 不伪造五命令状态或 successor;模型 action 不绕过显式 capability;preparing/terminalPending 不越过 status commit marker,顶层 plan progress 不混入旧 run;所有 durable record(含 local management、session rotation、target/continuation set payload/chunk 和 rotation fence)都能按 envelope/checksum/owner generation/commit marker 唯一恢复,且不存在有 active-index fence 而无唯一 rotation operation recovery fact 的崩溃窗口;path-bearing/local reply 不泄漏原文且遵守 local output 上限;predecessor 不产生第二 successor;Public Snapshot 不再暴露命令私有字段;capability 不存在可错误组合的独立白名单;Runtime final reply 不产生第二 message identity,Start status 不越过 commit marker 执行,public event 不重复/不泄漏;Public conversation 五类 mapping、去重键、committed cursor chain、合法 sequence 永久空洞、session-lifetime logical retention、afterCursor origin 全量补读、截断失败关闭、legacy 隔离和零 path/私有字段泄漏均有跨 transport 证据;且没有 P0/P1 级协议矛盾。其它 Runtime 内核技术债、性能优化和无 GUI 常驻能力记录为后续工程,不阻塞本次 Interaction Shell 重构。