diff --git a/.project-docs/10-decisions/proposals/20260822-pi-runtime-spec-b6e2c9a4__pi-runtime-cutover-spec.md b/.project-docs/10-decisions/proposals/20260822-pi-runtime-spec-b6e2c9a4__pi-runtime-cutover-spec.md new file mode 100644 index 0000000..9322237 --- /dev/null +++ b/.project-docs/10-decisions/proposals/20260822-pi-runtime-spec-b6e2c9a4__pi-runtime-cutover-spec.md @@ -0,0 +1,1125 @@ +# Makelore Code OpenCode → Pi 硬切换实施规范 + +## 0. 文档信息 + +| 字段 | 值 | +| --- | --- | +| Spec ID | `ML-CODE-PI-001` | +| 状态 | 已获用户实施授权;实施中 | +| 日期 | 2026-08-22 | +| Task | `20260822-pi-runtime-spec-b6e2c9a4` | +| 基线 | `fba68e86d93c66d2c50f53d215de76a7c69c97a7` | +| 来源设计 | Task `20260821-pi-runtime-replacement-doc-8e31c4a7` 的 OpenCode → Pi 硬切换改造设计 | +| 关联票据 | `20260822-pi-runtime-spec-b6e2c9a4__pi-runtime-cutover-tickets.md` | +| Pi 基线 | `@earendil-works/pi-coding-agent@0.84.2`;上游 tag `v0.84.2` / commit `914cf1472e715297caa30db4b9535d534a9eb718`,已由 `PI-000` 精确固化 | +| `PI-000` 实施基线 | Commit `2bc423ebc58589307442ebdaf1c81d83ec9dc2d5`(`feat: qualify Pi runtime cutover foundation`);实施任务 `20260822-pi-runtime-qualification-c52e8a14` 已 `ready_for_integration` | +| `PI-010` 交接 | 实施任务 `20260822-pi-conversation-contracts-a61d9c42` 已从 `2bc423e` 建立,当前状态 `planning` | +| 发布单元 | 一次完整硬切换;不允许部分能力以双 runtime 形式发布 | +| Phase-0 范围修订 | 用户于 2026-08-22 明确将 macOS x64/arm64 资格验证延期到 `PI-150`;该平台未通过、也不得记为 Pass | +| Provider 风险决策 | 用户于 2026-08-22 明确豁免真实外部 Provider Account 与真实 provider 双 worker 验证并接受对应风险;`QG-004`/`QG-005` 不是 Pass | +| `PI-000` 审计状态 | Done with explicit waivers;`QG-001/002/003/006` Pass,`QG-004/005` Accepted Risk,`QG-007` Pending / not triggered | + +本文把详细改造设计收敛为可验证的实施合同。它规定“必须交付什么、边界在哪里、如何判定完成”,不规定每个函数的内部写法。 + +本文中的关键词含义如下: + +- **MUST / 必须**:发布前不可缺少;不满足即 Spec 未完成。 +- **MUST NOT / 禁止**:任何实现均不得出现。 +- **SHOULD / 应当**:默认实现方式;偏离时必须在实施记录中给出可验证理由。 +- **MAY / 可以**:不影响合规的实现选择。 + +## 1. 目标结果 + +### 1.1 产品结果 + +完成后,Pi 是 Makelore Code 唯一 coding-agent runtime。用户仍使用 Makelore 的项目、伙伴和 Conversation 产品模型,不直接感知 Pi CLI、RPC、session 文件或 provider 配置格式。 + +系统必须同时达成以下结果: + +1. 删除 OpenCode runtime、依赖、插件、Host API、Renderer store、运行资源与打包产物。 +2. 项目、伙伴和空 Conversation 的创建仅写本地产品元数据,不启动 Pi,也不阻塞输入框。 +3. 每个运行中或温热 Conversation 使用一个独立 Pi worker;多个 Conversation 可以真正并行。 +4. Renderer 只消费 Makelore-owned Snapshot/Patch 协议;Pi wire 只存在于 Electron Main 的 Adapter 内。 +5. Pi subagent 由 Makelore 自有 extension 编排,具有全局并发上限、父子 abort 和同项目写租约。 +6. 保留有产品价值的文本、图片、thinking、tool、retry、compaction、queue、interaction、模型、thinking level、文件、changes、browser 和 skill 能力。 +7. 旧 OpenCode Conversation 不在 Pi 中继续或展示;迁移前备份,发布说明明确告知。 +8. packaged runtime、四协议 loopback/provider-shaped 双 worker、资源占用和性能预算都有实际证据;真实外部 Provider 兼容与并发风险以明确 waiver 记录,不伪装成 Pass。 + +### 1.2 成功定义 + +只有第 20 节 Definition of Done 全部通过,才可宣称 OpenCode 已被 Pi 替换。仅能启动 Pi、仅能完成单轮聊天、或仅删除 package dependency 都不构成完成。 + +## 2. 范围 + +### 2.1 包含 + +- Code 模块的项目/伙伴/Conversation 产品持久化升级。 +- Electron Main 的 Pi runtime、worker pool、session registry、provider/resource loader、event projector、extensions 和 Host API。 +- Renderer 的 vendor-neutral store、Chat UI、执行图、工具卡、交互、queue、compaction、subagent、changes 和诊断投影。 +- 一次性 OpenCode 数据备份与 schema 迁移。 +- Pi runtime staging、artifact verifier、真实 smoke、三平台打包验证。 +- 流式渲染、首 Conversation、并发和资源预算的遥测与验收。 +- OpenCode 专属能力的删除或产品语义替换。 + +### 2.2 不包含 + +- OpenCode 与 Pi 双 runtime、fallback、feature flag、compatibility Adapter 或旧 Host API facade。 +- 旧 OpenCode Session 到 Pi Session 的续聊转换。 +- 通用 runtime 插件平台或任意第三方 MCP 市场。 +- 同一项目多个写入 Agent 的 git worktree 产品;本次只实现项目级 mutation lease。 +- Makelore app id、protocol、`niancode` 存储根、通用用户数据或其他产品模块迁移。 +- Works gallery、asset gallery、publish/upload 或 cloud-deploy workbench 的恢复。 +- Pi TUI 所有功能的 GUI 镜像。 +- 自动删除 `/opencode` 或用户不确定所有权的 `.opencode` 文件。 + +## 3. 不可变架构决定 + +### 3.1 硬切换 + +- `CUT-001`:发布产物 MUST 只包含 Pi runtime,不得包含 OpenCode binary、package、plugin 或可执行 fallback。 +- `CUT-002`:代码 MUST NOT 定义 `OpenCodeRuntime | PiRuntime`、runtime capability negotiation、runtime selector 或 OpenCode Adapter。 +- `CUT-003`:所有 `/api/opencode/*` 路由 MUST 删除;Renderer MUST NOT 继续调用旧路由。 +- `CUT-004`:源码中不得用 `Opencode*` 名称包装 Pi 数据。仅历史文档、迁移提示和旧数据目录说明可以保留 OpenCode 字样。 +- `CUT-005`:硬切换以一个完整应用版本为回退单位,不在同一版本内做 runtime 开关。 + +### 3.2 产品权威数据 + +- `BND-001`:`.niancode/project.json` MUST 继续是项目、伙伴、默认模型和 skill 选择的权威来源。 +- `BND-002`:稳定 Agent ID、`app.niancode.desktop`、现有 URL protocol、`niancode` 存储标识 MUST 保持不变。 +- `BND-003`:不得生成项目内 `.pi/agents/*` 作为伙伴配置的第二权威来源。 +- `BND-004`:项目中任意 `.pi/settings.json`、自动发现的 `.pi/extensions`、`.pi/skills` 或第三方 package MUST NOT 被生产 runtime 隐式加载。 + +### 3.3 Main / Renderer 边界 + +- `BND-005`:Electron Main MUST 拥有 runtime、worker、provider、credential、session 文件、项目文件、browser 和系统集成。 +- `BND-006`:Renderer 只能通过 `src/lib/host-api.ts` 或 `src/lib/api-client.ts` 的 typed facade 访问上述能力;禁止页面/组件直接 IPC 或直连 Pi localhost/stdin。 +- `BND-007`:Renderer MUST NOT 导入 Pi 类型、解析 Pi JSONL、读取 Pi session、接收绝对 session path 或接收 credential。 +- `BND-008`:Pi 实现 MUST 位于一个深的 Main-owned `PiConversationRuntime` Module 后;公共 `CodingConversationRuntime` Interface 只表达 Makelore 产品语义。 + +## 4. 用户行为规范 + +### 4.1 项目、伙伴与 Conversation 创建 + +- `UX-001`:创建项目 MUST 只完成项目目录和产品 metadata 初始化;成功响应不得等待 Pi spawn、provider network、session list 或 runtime diagnostics。 +- `UX-002`:创建/保存伙伴 MUST 只写 `.niancode/project.json` 及受管本地资源;不得启动或重启所有 worker。 +- `UX-003`:创建空 Conversation MUST 生成稳定产品 `conversationId` 并写入 schema v2;此时 `piSessionId` 和 `sessionKey` 可以为空。 +- `UX-004`:选择 Conversation 后 Main MAY 后台 prewarm worker,但 prewarm 失败不得阻止用户编辑 draft。 +- `UX-005`:项目、伙伴和 Conversation metadata 写入失败必须明确失败,不得返回成功后再依赖 runtime 补齐产品记录。 + +### 4.2 Composer + +- `UX-010`:Textarea 可编辑条件只能依赖 active project 存在、有效伙伴存在、当前项目不处于销毁/迁移临界状态。 +- `UX-011`:Textarea 可编辑性 MUST NOT 依赖 worker state、session/status 请求、全局 loading、其他 Conversation busy、provider request 或 diagnostics。 +- `UX-012`:附件编码、模型不可用或当前 submit 本地校验 MAY 暂时禁用 Send,但不得禁用 Textarea。 +- `UX-013`:cold worker 尚未 ready 时,UI 显示 Conversation 级“正在准备本地 Agent”,不得显示覆盖整个 Chat 的无界 loading。 +- `UX-014`:任何本地准备超过 10 秒 MUST 进入明确、可恢复错误;不得让 Composer 分钟级 disabled。 + +### 4.3 Prompt、queue 与完成 + +- `UX-020`:发送时 Renderer MUST 立即创建带 `clientRequestId` 的 optimistic user node。 +- `UX-021`:HTTP 成功只表示 prompt/steer/follow-up 已接受,不能表示 run 完成。 +- `UX-022`:prompt 接受后,Provider/tool 错误通过 patch stream 投影;不得删除已接受的 user node。 +- `UX-023`:一个 Conversation 的 prompt、abort、retry、queue、model、thinking、error MUST 与其他 Conversation 隔离。 +- `UX-024`:`agent_settled` 是权威 idle;`agent_end`、`turn_end`、`message_end`、`compaction_end` 单独出现时均不得释放 top-level permit 或清除 busy。 +- `UX-025`:steer 与 follow-up 必须是两个明确的 queue mode;UI 不得继续使用含义不明的单一 OpenCode queue。 +- `UX-026`:abort 只影响目标 Conversation 及其 child subagents,不得终止其他 worker。 + +### 4.4 Conversation 设置 + +- `UX-030`:伙伴模型只是新 Conversation 默认值;已有 Conversation 保持自己的 `accountId/modelId/thinkingLevel`。 +- `UX-031`:模型切换只调用目标 worker 的 Pi `set_model`,并原子更新目标 Conversation metadata;不得修改伙伴默认、重启其他 worker或全局 provider 配置。 +- `UX-032`:thinking level 是 Conversation setting,行为与模型切换相同。 +- `UX-033`:目标模型不在当前受管 catalog 时切换必须失败,旧模型保持不变。 + +### 4.5 并发与 subagent + +- `UX-040`:不同 Conversation 必须使用两个独立 worker,且最终 packaged provider-shaped loopback turn 必须能在时间上重叠;不能通过一个 worker 反复 `switch_session` 冒充并发。真实外部 Provider 可能并发、串行、限流或拒绝,本 Spec 不再以外部 turn 重叠作为 release hard gate。 +- `UX-041`:多个 subagent 支持 `single`、`parallel`、`chain` 三种模式。 +- `UX-042`:subagent 作为父 tool 下的嵌套执行节点展示,不得创建虚构的 `role: subagent` chat message。 +- `UX-043`:只读 subagent 可以并行;同一项目 mutation 必须遵守项目写租约。 +- `UX-044`:abort parent 必须取消所有未完成 child;parallel 中一个 child 失败不得抹掉其他已完成结果;chain 在首个失败处停止。 + +### 4.6 功能删除与改名 + +| OpenCode 旧能力 | Pi 版本要求 | +| --- | --- | +| share / unshare | UI、route、type、test 全部删除;未来 sharing 另立 Makelore 合同。 | +| revert / unrevert | 删除;用“从这里创建新对话分支”表达 Pi fork,不承诺文件回滚。 | +| todos endpoint | 删除;若需要结构化进度,使用版本化 `task_state` extension details。 | +| global runtime start/stop/restart | 删除;worker 由 Conversation lazy lifecycle 管理。 | +| Session diff | 改为 Main-owned `ConversationChangeTracker`,不向 Pi 查询。 | +| questions / permissions 双模型 | 合并为 Makelore `interactions`;具体高影响 tool 自己发 confirm。 | +| Playwright MCP | 删除;`agent_browser` extension 直接调用 Main browser seam。 | +| OpenCode Agent materialization | 删除;伙伴 prompt/manifest 从 `.niancode/project.json` 生成到受管 runtime cache。 | + +## 5. 目标架构与模块责任 + +```mermaid +flowchart LR + UI[Renderer Coding UI] + API[Main Host API /api/coding] + CS[Conversation Service] + PS[Project & Conversation Store] + FS[Project File / Change Service] + RT[CodingConversationRuntime] + POOL[Pi Worker Pool] + W1[Pi RPC Worker A] + W2[Pi RPC Worker B] + EXT[Makelore Pi Extension] + PR[Provider Service / Secure Storage] + + UI -->|typed command| API + API --> CS + API --> PS + API --> FS + CS --> RT + RT --> POOL + POOL --> W1 + POOL --> W2 + W1 --> EXT + W2 --> EXT + PR --> POOL + CS -->|Snapshot + ordered Patch| API + API -->|SSE| UI +``` + +### 5.1 产品模块 + +| 模块 | 必须拥有 | 禁止拥有 | +| --- | --- | --- | +| `electron/coding-projects` | project config/store、conversation metadata、migration、file service、skills registry | Pi RPC、provider turn、Renderer state | +| `electron/coding-runtime/contracts.ts` | vendor-neutral runtime Interface 和产品 DTO | Pi event/type import | +| `electron/coding-runtime/conversation-service.ts` | product command orchestration、snapshot/patch subscription、acceptance | child process 细节 | +| `electron/coding-runtime/pi` | process、RPC、worker pool、session registry、event/session projector、provider/resource input、extensions | project CRUD、Renderer component | +| `electron/api/routes/coding-*` | request validation、typed response、SSE、安全错误投影 | vendor wire passthrough | +| Renderer `coding-conversations` | per-Conversation snapshot/patch reducer、draft 和 selection | Pi JSONL、绝对路径、credential | + +### 5.2 `CodingConversationRuntime` Interface + +实现 MUST 提供等价于下列产品能力的最小接口: + +```ts +interface CodingConversationRuntime { + prepare(input: PrepareConversationInput): Promise; + getSnapshot(conversationId: string): Promise; + prompt(input: PromptConversationInput): Promise; + steer(input: QueueMessageInput): Promise; + followUp(input: QueueMessageInput): Promise; + abort(conversationId: string): Promise; + setModel(input: SetConversationModelInput): Promise; + setThinking(input: SetThinkingLevelInput): Promise; + compact(conversationId: string): Promise; + fork(input: ForkConversationInput): Promise; + recover(conversationId: string): Promise; + dispose(conversationId: string): Promise; + subscribe(listener: (patch: ConversationPatchEnvelope) => void): () => void; +} +``` + +- `ARC-001`:生产 `PiConversationRuntime` 与测试 `InMemoryConversationRuntime` 是该 Interface 的两个 Implementation。 +- `ARC-002`:Interface MUST NOT 包含 vendor enum、Pi CLI option、OpenCode capability 或绝对路径。 +- `ARC-003`:项目 CRUD 和文件读取不属于 runtime Interface,应由对应 Main service 直接提供。 +- `ARC-004`:实现内部 MAY 进一步拆分,但不得把 process/RPC/event/session/provider 生命周期重新堆回单个 Host route 文件。 + +### 5.3 建议目录 + +```text +electron/ + coding-projects/ + project-config.ts + project-store.ts + conversation-store.ts + project-files.ts + conversation-change-tracker.ts + skill-registry.ts + coding-runtime/ + contracts.ts + conversation-service.ts + runtime-errors.ts + pi/ + runtime.ts + worker-pool.ts + worker-process.ts + rpc-framer.ts + rpc-client.ts + event-projector.ts + session-projector.ts + provider-config.ts + resource-loader.ts + extensions/ + makelore-runtime.ts + interaction.ts + write-lease.ts + subagent.ts + agent-browser.ts + game-assets.ts + api/routes/ + coding-projects.ts + coding-conversations.ts + coding-files.ts +``` + +目录名称可以按仓库约定小幅调整,但责任边界和依赖方向是规范要求。 + +## 6. 产品数据模型 + +### 6.1 产品 ID 与 Pi ID + +- `DAT-001`:`conversationId` MUST 是 Makelore 生成的稳定 UUID;Renderer、URL、archive、unread、selection、Zustand key 只使用该 ID。 +- `DAT-002`:Pi `sessionId`、session file、entry ID、leaf ID 是 Main 私有实现字段。 +- `DAT-003`:worker 重启、session reopen 或 Pi 升级不得改变产品 `conversationId`。 +- `DAT-004`:任何绝对 session path MUST NOT 出现在 Host API、SSE、Renderer log 或 UI。 + +### 6.2 `.niancode/project.json` schema v2 + +每个 Agent 的模型选择 MUST 迁移为: + +```ts +interface ProductModelRef { + accountId: string; + modelId: string; + thinkingLevel: "off" | "minimal" | "low" | "medium" | "high"; +} + +interface ProductModelSelection { + model: ProductModelRef | null; + modelResolution: "resolved" | "required"; +} +``` + +- `accountId` 指向 Main Provider Service 的稳定账号。 +- `modelId` 是 provider 原生模型 ID,不是 Pi 派生 provider key。 +- runtime provider ID 只在 Main 运行时派生。 +- 旧 string model 只有在 Provider Service 能唯一映射时才自动转换。 +- 不能唯一映射时保留 Agent 其他字段,写入 + `{ model: null, modelResolution: "required" }`;UI 要求用户选择,不得静默使用默认模型。 +- Agent id、name、prompt、skills、archive 状态 MUST 保持。 + +### 6.3 `.niancode/conversations.json` schema v2 + +```ts +interface CodingConversationFileV2 { + schemaVersion: 2; + conversations: Array<{ + id: string; + agentId: string; + title: string; + model: ProductModelRef | null; + modelResolution: "resolved" | "required"; + piSessionId?: string; + sessionKey?: string; + archivedAt: string | null; + unread: boolean; + createdAt: string; + updatedAt: string; + }>; +} +``` + +- `DAT-010`:`sessionKey` 必须是 Main session root 下的 opaque relative key;禁止 `..`、绝对路径或 Renderer 提供路径。 +- `DAT-011`:Conversation 可以在 Pi session 不存在时持久化。 +- `DAT-012`:第一次 prompt 的 session create/open 与 Conversation 绑定 MUST 使用 per-Conversation single-flight;并发首发不得创建两个 session。 +- `DAT-013`:title、archive、unread 和 model metadata 不依赖 worker 存活。 +- `DAT-014`:删除 Conversation 时先 dispose worker,再把 session 移入 Main-owned trash;Renderer 不直接删除文件。 +- `DAT-015`:产品 metadata 写入 MUST 使用原子替换;session 文件由 Pi 持久化合同负责。 + +### 6.4 受管 Pi 目录 + +```text +/coding-runtime/pi/ + config/ + sessions//.jsonl + prompts//.md + extensions/ + logs/ + trash/ +``` + +`PI_CODING_AGENT_DIR` MUST 指向受管 config。项目 cwd 仍是实际项目目录,但 Pi 全局配置、session、prompt 和 extension 不写入任意用户 `~/.pi`。 + +## 7. Provider、模型与受管资源 + +### 7.1 Provider 投影 + +Main 内部 MUST 用 vendor-neutral account 生成等价于下列描述: + +```ts +interface PiProviderDescriptor { + accountId: string; + runtimeProviderId: string; + api: + | "openai-completions" + | "openai-responses" + | "anthropic-messages" + | "google-generative-ai"; + baseUrl?: string; + headers: Record; + apiKeyEnv?: string; + models: Array<{ + id: string; + name: string; + input: Array<"text" | "image">; + contextWindow?: number; + maxOutputTokens?: number; + }>; +} +``` + +具体 `api` union 在 `PI-000` 中按锁定 Pi 版本固化。 + +- `PRV-001`:只有认证、base URL 和协议完全匹配时才使用 Pi built-in provider。 +- `PRV-002`:自定义 base URL/header、Works gateway/proxy 和导入模型必须由 Main 生成受管 catalog。 +- `PRV-003`:runtime provider ID 由稳定 account ID 派生,两个同 vendor 账号不得冲突。 +- `PRV-004`:当前 Works gateway `/v1` 归一化、proxy token 与 refresh 语义必须迁移并有 fixture。 +- `PRV-005`:credential 只在 spawn 前从 secure storage 读取,放入单 worker environment 或受管 credential provider;不得进入 command line、snapshot、SSE、Renderer 或日志。 +- `PRV-006`:同 account refresh 使用 Provider Service single-flight;认证恢复最多执行一次 refresh + worker reopen,不得无限循环。 +- `PRV-007`:Provider/config 维护 Main-owned revision。idle stale worker 在下次 prompt 前重建;running worker在 settled 后重建,不得中断当前 run。 + +### 7.2 Prompt、skills 与 context + +- `RES-001`:伙伴 prompt 从 `.niancode/project.json` 生成到受管 cache;command line 只出现受管路径,不出现 prompt 内容。 +- `RES-002`:内建 coding skills 从 `.opencode/skills` 移到 `resources/coding-skills`,由 skill ID 映射到显式打包路径。 +- `RES-003`:child 只加载当前伙伴选择的 skill;禁止自动发现项目或用户目录的 Pi resources。 +- `RES-004`:正在运行的 worker 保持本 run 的 resource snapshot;新 run/新 worker使用最新 revision。 +- `RES-005`:`AGENTS.md` 等 context file 的加载策略必须由 `PI-000` 在锁定版本上显式决定并测试,不能依赖 Pi 默认值漂移。 + +## 8. Pi 进程、RPC 与 Worker Pool + +### 8.1 Phase-0 资格门 + +- `QG-001`:在任何产品迁移代码开始前,必须精确 pin 一个 Pi 版本并记录 package、CLI entry、Node engine 和上游 tag/commit。 +- `QG-002`:Phase-0 平台范围内的 Windows x64、Linux x64 packaged qualification artifact 必须证明 Electron Node 能 spawn、RPC ready、open session、accept prompt、abort、settle、reopen、clean exit。 +- `QG-003`:必须证明 Phase-0 平台范围内的 Windows x64、Linux x64 完整 production dependency closure、WASM/native/optional 资源能从 packaged qualification artifact resolve;最终产品 artifact 的同项验证属于 `PI-150`。 +- `QG-004`:真实外部 Provider Account 的四协议验证由用户于 2026-08-22 标记为 **Explicitly Waived / Accepted Risk**,不得记为 Pass。Phase-0 仍必须保留 Windows/Linux packaged loopback 四协议矩阵,验证 request/SSE、base URL/path、header、environment credential plumbing、model 和 image serialization;报告中的 `realTurnVerified` 必须保持 `false`。 +- `QG-005`:真实外部 provider 的双 worker turn overlap/isolation 由用户于 2026-08-22 标记为 **Explicitly Waived / Accepted Risk**,不得记为 Pass。Phase-0 仍必须以两个独立 worker 通过 packaged provider-shaped loopback 证明时间窗口重叠、单侧 abort 后另一侧 settled,以及受控 seam 内的 event/session/model/credential 路由不串线;不得把它表述为真实外部 Provider 并发证据。 +- `QG-006`:必须报告 Phase-0 平台范围内 Windows x64、Linux x64 的 cold/warm latency、RSS、退出清理 p50/p95/max 和样本数。 +- `QG-007`:若 RPC 无法通过未豁免的打包、provider-shaped contract/concurrency/abort 或预算门,必须在业务迁移前停止并修订本 Spec,选择“Pi SDK in Electron utility process”;禁止继续实现双 runtime 或同时维护 RPC/SDK 两条生产路径。缺少已明确豁免的真实外部 Provider 样本不是已确认失败,不触发本条。 + +**Phase-0 macOS waiver(用户范围决策,2026-08-22)**:macOS x64 与 +macOS arm64 在 `PI-000` 中标记为 **Explicitly Waived / Deferred by user**, +不得标记为 Pass。原 Phase-0 中的 macOS workspace、staged production closure、 +controlled packaged、packaged loopback 和 metrics 全部移交 `PI-150` 实际验证。 +该 waiver 只改变 `QG-002`、`QG-003`、`QG-006` 的 Phase-0 平台范围; +`QG-004`、`QG-005` 后续由下面独立的 Provider waiver 处理。这会把 macOS +packaging、native、resource 和 performance defect 的暴露时间推迟到 release +stage,可能导致更晚返工;这是用户接受的时序风险,不代表风险已经消除。 + +**真实 Provider waiver(用户风险决策,2026-08-22)**:用户明确豁免真实 +外部 Provider Account/credential 与真实 provider 双 worker 验证,并接受以下 +尚未验证的风险: + +- 真实认证、endpoint、proxy、rate-limit 和 provider-specific response variation; +- base URL、header、environment credential、model、image 等字段在真实协议端点 + 的兼容性; +- 真实 provider 可能并发、串行、限流或拒绝两个 worker turn; +- 跨 worker abort、event、session、model、credential 的真实外部隔离。 + +该决定把 `QG-004`、`QG-005` 置为 **Explicitly Waived / Accepted Risk**, +不是 Pass,也不是已确认失败。Windows/Linux packaged loopback 与 +provider-shaped concurrency/abort smoke 仍是必须通过的替代证据;任何报告均须 +保留 `realTurnVerified: false`。真实外部 Provider Account、凭证或 turn 不再是 +`PI-000`、`PI-150` 或 release 的阻断条件,缺失它们不触发 `QG-007`。 + +### 8.2 启动合同 + +生产实现 SHOULD 使用 Electron executable + `ELECTRON_RUN_AS_NODE=1` 启动 staged Pi CLI RPC entry。锁定版本的实际参数必须由 smoke 确认,语义要求如下: + +- RPC mode; +- offline/禁用 Pi 自更新类启动网络,但不阻断 provider; +- `--no-approve` 或锁定版本等价选项; +- 禁止自动 extension/skill/prompt-template 发现; +- 只显式加载 Makelore extension、selected skills、受管 session/provider/model; +- stdout 只能输出 RPC JSONL,日志只能走 stderr; +- secret 只进入 child environment; +- cwd 是当前项目目录;Pi config/session root 是 Main-owned 目录。 + +### 8.3 RPC + +- `RPC-001`:使用自有严格 LF JSONL framer;不得用 Node `readline`,JSON string 内 U+2028/U+2029 不能分帧。 +- `RPC-002`:command 必须带相关 ID;response 可乱序并通过 ID resolve;event 不得误配到 pending command。 +- `RPC-003`:半行跨 chunk、单 chunk 多行、CRLF 尾部、malformed JSON、oversized line、stdout 非协议文字、stderr、backpressure、exit 和 timeout 都必须覆盖。 +- `RPC-004`:stdout protocol violation 必须 kill 当前 worker、generation +1,并只保留有界且脱敏的诊断片段。 +- `RPC-005`:只读 command 可在 generation 不变时重试一次;`prompt/steer/follow_up/fork/compact` timeout 后禁止自动重发。 + +### 8.4 Worker 生命周期 + +```text +absent -> queued -> spawning -> ready -> running + ^ | | + | v v + crashed <- idle <- settling + | + evicted +``` + +- `RUN-001`:`prepare` 必须是 per-Conversation 幂等 single-flight。 +- `RUN-002`:一个 worker 同一时间只拥有一个产品 Conversation/Pi AgentSession;不得在两个 streaming Conversation 间 `switch_session`。 +- `RUN-003`:running worker 禁止 LRU eviction;idle worker 超预算按最久未使用关闭。 +- `RUN-004`:worker crash/recover 后 generation 加一;所有旧 generation event、response、interaction 和 lease 必须丢弃/取消。 +- `RUN-005`:app quit 先拒绝新 prompt,再给 worker 3 秒 graceful shutdown,超时 kill;退出不得无限等待。 +- `RUN-006`:Session 已持久化时,idle eviction 后可以重新 spawn 和 hydrate。 +- `RUN-007`:worker unexpected exit 必须取消 pending RPC、interaction、write lease 和 child subagent,但保留 session 文件。 + +### 8.5 初始调度预算 + +| 资源 | 初始产品上限 | 必须行为 | +| --- | ---: | --- | +| 同时 running top-level Conversation | 4 | 第 5 个进入自己的队列,UI 显示等待。 | +| warm idle worker | 4 | 超出后 LRU eviction。 | +| 单次 subagent dispatch task | 8 | schema validation 拒绝第 9 个。 | +| 全应用 running subagent child | 4 | 所有 parent 共用 semaphore。 | +| Pi 总进程软上限 | 8 | 4 parent + 4 child;running 不被强杀。 | + +这些值是 Makelore 初始常量,不是 Pi 引擎保证,也不做用户 feature flag。只有 packaged RSS/latency 证据支持时才可在后续独立变更中调整。 + +### 8.6 项目写租约 + +- `LEASE-001`:同一项目同时最多一个 mutation tool 持有 write lease。 +- `LEASE-002`:`read/grep/find/ls` 和纯推理无需 lease,可以并行。 +- `LEASE-003`:受管 `write`、`edit` 及可执行任意命令的 `bash` 在执行前必须向 Main 获取 lease。 +- `LEASE-004`:等待时 tool card 显示“等待项目写入”,且用户可 abort。 +- `LEASE-005`:complete、error、abort、timeout、worker crash、child exit 都必须释放 lease。 +- `LEASE-006`:不同项目 mutation 可以并行;同 parent 的 coding subagent 也必须遵守租约。 + +## 9. Makelore Pi Extension 合同 + +### 9.1 显式 bundle + +生产包只显式加载一个版本化 Makelore extension entry。它至少注册: + +| 能力 | 责任 | +| --- | --- | +| `ask_user` | select/confirm/input/editor 与 pending interaction | +| `project_write_lease` | mutation 前后申请/释放项目租约 | +| `subagent` | stable Agent manifest、single/parallel/chain、nested progress | +| `agent_browser` | Main-owned browser seam | +| `game_asset_browser/review` | 迁移现有游戏资源产品能力 | +| `task_state` | 版本化任务步骤/进度;不是 Pi core todo | +| `changed_file` | 上报受管 tool touched path | +| `runtime_context` | 项目、Conversation、Agent、knowledge、skills;不含 secret | + +### 9.2 Extension → Main seam + +- `EXT-001`:extension 不得直连 Renderer。 +- `EXT-002`:Main 提供 loopback internal endpoint 或 child IPC bridge,并为每个 worker生成短期随机 bearer token;worker dispose 后 token 失效。 +- `EXT-003`:该 endpoint 只暴露 interaction、browser、write lease、subagent permit 和受管资源读取;不得复用 Works credential。 +- `EXT-004`:request 必须带 `conversationId/workerGeneration/runId`,Main 必须核对 worker registry。 +- `EXT-005`:同机受管子进程边界只要求随机 bearer token + loopback;不得额外引入签名、哈希或通用远程授权框架。 +- `EXT-006`:extension 误写 stdout 属于 RPC protocol violation。 + +### 9.3 Interaction + +```ts +interface ConversationInteraction { + id: string; + conversationId: string; + runId: string; + kind: "select" | "confirm" | "input" | "editor"; + title: string; + message?: string; + options?: Array<{ id: string; label: string; description?: string }>; + status: "pending" | "answered" | "rejected" | "cancelled"; +} +``` + +- 常规 read/write/edit/bash 沿用本地协作者默认授权,不建设泛化 permission policy engine。 +- 高影响操作只有在具体产品 tool 明确要求时调用 confirm。 +- worker crash/abort 必须取消其 pending interactions。 +- notify 投影为 toast;status/widget 只接受已注册类型;unknown details 只进入 bounded diagnostics。 + +### 9.4 Subagent details + +Makelore MUST 自有稳定 schema,不能直接把 Pi 官方示例 details 传给 GUI: + +```ts +interface SubagentDetailsV1 { + schema: "subagent.v1"; + dispatchId: string; + mode: "single" | "parallel" | "chain"; + tasks: Array<{ + taskId: string; + agentId: string; + toolProfile: "read-only" | "coding"; + status: "queued" | "running" | "complete" | "error" | "aborted" | "skipped"; + summary?: string; + errorCode?: string; + usage?: PublicUsage; + }>; +} +``` + +- child 使用独立 Pi process/context,默认不成为可恢复的用户 Conversation。 +- parent UI 只消费 `subagent.v1` 及普通嵌套 tool/message progress。 +- unknown schema version 不得 dump raw JSON;显示通用不可用状态并记录诊断。 + +## 10. Snapshot / Patch 领域协议 + +### 10.1 Snapshot + +```ts +interface ConversationSnapshot { + schemaVersion: 1; + conversation: { + id: string; + projectId: string; + agentId: string; + title: string; + model: ConversationModelState; + }; + nodes: ConversationNode[]; + run: ConversationRunState; + queue: ConversationQueueState; + context: ConversationContextState; + pendingInteractions: ConversationInteraction[]; + worker: PublicWorkerState; + cursor: { + workerGeneration: number; + seq: number; + leafEntryId?: string; + }; +} +``` + +### 10.2 Node + +```ts +type ConversationNode = + | ConversationMessageNode + | ConversationToolNode + | ConversationCompactionNode + | ConversationBoundaryNode + | ConversationSubagentNode + | ConversationNoticeNode; + +interface ConversationMessageNode { + kind: "message"; + id: string; + sourceEntryId?: string; + clientRequestId?: string; + role: "user" | "assistant"; + status: "optimistic" | "streaming" | "complete" | "error" | "aborted"; + blocks: ConversationContentBlock[]; + usage?: PublicUsage; + stopReason?: "stop" | "length" | "tool-use" | "error" | "aborted"; +} + +type ConversationContentBlock = + | { kind: "text"; id: string; text: string; status: "streaming" | "complete" } + | { kind: "thinking"; id: string; text: string; status: "streaming" | "complete" } + | { kind: "image"; id: string; attachmentId: string; mime: string }; + +interface ConversationToolNode { + kind: "tool"; + id: string; + toolCallId: string; + toolName: string; + title: string; + inputText: string; + status: "declared" | "waiting" | "running" | "complete" | "error" | "aborted"; + output: ConversationContentBlock[]; + details?: KnownToolDetails; +} +``` + +Pi `toolResult` MUST attach to `ConversationToolNode`;它不是公开 message role,也不得生成独立 bubble。 + +### 10.3 Patch + +```ts +type ConversationPatch = + | { op: "worker.state"; state: PublicWorkerState } + | { op: "run.state"; run: ConversationRunState } + | { op: "message.upsert"; node: ConversationMessageNode } + | { op: "message.block-delta"; messageId: string; blockId: string; delta: string } + | { op: "tool.upsert"; node: ConversationToolNode } + | { op: "compaction.upsert"; node: ConversationCompactionNode } + | { op: "subagent.upsert"; node: ConversationSubagentNode } + | { op: "queue.replace"; queue: ConversationQueueState } + | { op: "interaction.upsert"; interaction: ConversationInteraction } + | { op: "interaction.remove"; interactionId: string } + | { op: "context.replace"; context: ConversationContextState } + | { op: "snapshot.invalidated"; reason: string }; + +interface ConversationPatchEnvelope { + conversationId: string; + workerGeneration: number; + runId?: string; + seq: number; + at: number; + patch: ConversationPatch; +} +``` + +- `EVT-001`:`seq` 在单 Conversation + generation 内严格递增。 +- `EVT-002`:Renderer 丢弃旧 generation;发现 gap 不猜测缺失内容,GET 目标 Conversation snapshot。 +- `EVT-003`:初次订阅和重连先取 snapshot,再接 live stream;不建设永久 event log。 +- `EVT-004`:live UI ID 在 `message_start` 生成,durable reconcile 只绑定 `sourceEntryId`,不得替换 UI ID。 +- `EVT-005`:禁止使用 timestamp 或 array index 作为唯一 UI identity。 +- `EVT-006`:live assembly 与 hydrated snapshot 必须使用同一 normalization reducer 和 fixture。 +- `EVT-007`:unknown Pi event/content 默认不进入 timeline,只进入有界 diagnostics。 + +### 10.4 Active branch hydration + +`get_entries` 不能直接按 append order 渲染。Main MUST: + +1. 读取 `entries` 与当前 `leafId`。 +2. 建立 `id → entry` map。 +3. 从 `leafId` 沿 `parentId` 回溯到 root。 +4. 反转为 active path。 +5. 应用 compaction/retained-tail 规则。 +6. 通过统一 reducer 生成 Snapshot。 +7. 在 `agent_settled` 后用 durable entries/leaf reconcile。 + +废弃 branch 不进入当前 transcript;只有明确的分支历史查询可以读取。 + +## 11. Pi 事件映射 + +| Pi 输入 | 产品投影 | 规范要求 | +| --- | --- | --- | +| prompt response success | `PromptAcceptance` | 只表示接受/排队,不是完成。 | +| prompt response failure | optimistic node rejected/error | 保留 draft/附件恢复能力;不得创建 assistant。 | +| `agent_start` | run `running` | 验证/绑定预分配 `runId`。 | +| `agent_end` | low-level checkpoint | 禁止标 idle。 | +| `agent_settled` | run `idle` + durable reconcile | 唯一权威 idle。 | +| `turn_start/end` | boundary node | 不重复插入 message/tool result。 | +| user `message_start` | reconcile optimistic user | 通过 request/run 关联。 | +| assistant `message_start` | streaming assistant node | 生成稳定 UI ID。 | +| `text_start/delta/end` | text block | 按 `contentIndex` 组装;end 为权威值。 | +| `thinking_start/delta/end` | thinking block | 按 `contentIndex`,遵守展示偏好。 | +| `toolcall_start/delta/end` | declared tool + args buffer | delta 阶段不 parse JSON,end 才解析。 | +| `tool_execution_start` | tool running | 只更新 `toolCallId` 对应节点。 | +| `tool_execution_update` | replace cumulative output/details | 必须 replace,禁止 append `partialResult`。 | +| `tool_execution_end` | complete/error/aborted tool | final result 权威。 | +| assistant `message_end` | authoritative message | 替换 draft blocks/usage/stop reason。 | +| `toolResult` `message_end` | attach final result | 不生成独立 bubble。 | +| `queue_update` | queue replace | UI badge/composer 状态,不进 transcript。 | +| `compaction_start/end` | compaction node/context | `willRetry` 时 run 保持 active;summary 默认隐藏。 | +| retry events | retry state/trace | 脱敏 error,显示 attempt/delay。 | +| extension interaction | pending interaction | request ID 关联 response。 | +| notify/status/widget/title/editor text | toast/status/known widget/title/draft suggestion | draft revision 已变化时不得覆盖用户输入。 | +| `bashExecution` | direct command node | 仅产品暴露 direct command 时显示。 | +| `custom display:false` | hidden | 不显示。 | +| registered custom | typed product node | 只渲染已注册 schema。 | +| unknown custom | diagnostic | 不 dump raw details。 | +| branch/compaction summary | metadata/timeline marker | 不当 user/assistant bubble。 | + +### 11.1 Usage 与 context + +- `message_update.usage` 可流式显示,但最终值以 `message_end` 与 `get_session_stats` reconcile。 +- session total 必须包含 compaction/branch 等有效 usage,不能只相加 assistant message。 +- 刚压缩后 context 尚未可算时显示“重新计算中”,不得显示虚假的 0%。 + +## 12. Host API + +### 12.1 路由 + +所有新路由使用 `/api/coding/*`: + +| 路由 | 方法 | 语义 | +| --- | --- | --- | +| `/api/coding/projects` | GET | 列出项目。 | +| `/api/coding/projects/open` | POST | 打开已有目录。 | +| `/api/coding/projects/create` | POST | 创建本地项目;不启动 Pi。 | +| `/api/coding/projects/remove` | POST | 从 catalog 移除,不删除项目文件。 | +| `/api/coding/projects/active` | GET/POST | 查询/选择 active project。 | +| `/api/coding/projects/config` | GET/PUT | `.niancode/project.json`。 | +| `/api/coding/projects/knowledge` | POST | 更新项目 knowledge。 | +| `/api/coding/projects/conversations` | GET/POST | metadata;POST 不启动 Pi。 | +| `/api/coding/conversations/:id` | GET/PATCH/DELETE | metadata 与受管 session lifecycle。 | +| `/api/coding/conversations/:id/snapshot` | GET | active branch 完整 Snapshot。 | +| `/api/coding/events` | GET/SSE | 全局有序 envelope stream;seq 按 Conversation。 | +| `/api/coding/conversations/:id/prompt` | POST | prompt/steer/follow-up;只等待 acceptance。 | +| `/api/coding/conversations/:id/abort` | POST | abort 目标 Conversation。 | +| `/api/coding/conversations/:id/model` | POST | 目标 Conversation model。 | +| `/api/coding/conversations/:id/thinking` | POST | 目标 Conversation thinking level。 | +| `/api/coding/conversations/:id/compact` | POST | 手动压缩。 | +| `/api/coding/conversations/:id/fork` | POST | 从 user entry 创建新产品 Conversation。 | +| `/api/coding/conversations/:id/recover` | POST | 重新创建 worker 并 hydrate。 | +| `/api/coding/conversations/:id/commands` | GET | Pi command + Makelore command 安全投影。 | +| `/api/coding/interactions` | GET | 当前 pending interactions。 | +| `/api/coding/interactions/:id/respond` | POST | 回答/拒绝 interaction。 | +| `/api/coding/skills` | GET | 产品 skill registry。 | +| `/api/coding/files/status` | GET | 项目文件状态。 | +| `/api/coding/files/find` | GET | 文件查找。 | +| `/api/coding/files/content` | GET | 文件内容预览。 | +| `/api/coding/search` | GET | 文本搜索。 | +| `/api/coding/runtime/diagnostics` | GET | 脱敏 worker pool/resource summary。 | + +### 12.2 Prompt request/acceptance + +```ts +interface PromptConversationInput { + clientRequestId: string; + conversationId: string; + mode: "prompt" | "steer" | "follow-up"; + text: string; + attachments: Array<{ attachmentId: string }>; +} + +interface PromptAcceptance { + accepted: true; + conversationId: string; + clientRequestId: string; + runId: string; + mode: "prompt" | "steer" | "follow-up"; + queuePosition?: number; +} +``` + +- `API-001`:接受时返回 HTTP `202`;产品校验未通过时返回 typed 4xx,且 `accepted` 不得伪造为 true。 +- `API-002`:HTTP 不等待 `agent_settled`、provider first token 或完整 assistant message。 +- `API-003`:同一进程内重复 `clientRequestId` 若已明确 accepted,应返回相同 acceptance;状态不确定时不得自动发送第二次 prompt。 +- `API-004`:请求 attachment 只引用 Main-owned attachment ID;Renderer 不提交任意本地路径。 +- `API-005`:SSE/event stream 断线不得导致 accepted prompt 自动重发。 + +### 12.3 错误合同 + +Renderer 只接收稳定 code、可操作状态和固定中文提示。至少覆盖: + +| code | 场景 | 可恢复性 | +| --- | --- | --- | +| `CODING_RUNTIME_START_FAILED` | child spawn 失败 | 手动 recover | +| `CODING_RUNTIME_READY_TIMEOUT` | 10 秒未 ready | kill 后 recover | +| `CODING_RUNTIME_PROTOCOL_ERROR` | stdout/RPC 违规 | kill 后 recover | +| `CODING_PROVIDER_AUTH_REQUIRED` | credential 不可用/刷新一次仍失败 | 修复账号后 recover | +| `CODING_MODEL_UNAVAILABLE` | model 不在 catalog | 选择模型 | +| `CODING_SESSION_UNREADABLE` | session JSONL 无法读取 | 保留文件,新建/诊断 | +| `CODING_STORAGE_WRITE_FAILED` | product/session persistence 失败 | 停止新 prompt,修复存储 | +| `CODING_REQUEST_UNCERTAIN` | mutating RPC timeout 后无法确认 | 用户显式 reconcile/retry | +| `CODING_MIGRATION_MODEL_REQUIRED` | 旧 model 无唯一映射 | 用户选择模型 | + +原始 stderr、provider body、credential、账号 ID、完整路径、stack 和 extension path 不得穿透 Renderer。 + +## 13. Project File、Changes 与 Browser + +### 13.1 Project File Service + +- 文件 status/find/content/search 从 OpenCode route 移入 Main-owned product service。 +- 所有路径必须相对 active project 解析;Renderer 不获得绝对项目根。 +- 行为保持现有受支持功能,不因 runtime 切换扩展为任意文件系统代理。 + +### 13.2 `ConversationChangeTracker` + +- `CHG-001`:run start 记录项目基线状态和当前 git head(若存在)。 +- `CHG-002`:受管 write/edit tool 通过 `changed_file` 上报 touched relative path。 +- `CHG-003`:bash 可能修改任意文件,因此 settled 后执行一次项目级 git status/diff refresh。 +- `CHG-004`:只读取 changed path 的 diff;untracked 只提供受限 preview。 +- `CHG-005`:结果进入 Conversation changes panel,不写入 Pi message/session。 +- `CHG-006`:不得为每个文件增加哈希/checkpoint 框架;若将来需要文件回滚,另立 git checkpoint 设计。 + +### 13.3 Browser + +```text +Pi tool agent_browser + -> Makelore extension + -> Main browser service + -> bounded structured result / screenshot attachmentId +``` + +- 不启动 Playwright MCP server,不维护 MCP discovery/stdio/second protocol。 +- screenshot 经 Main attachment service 投影,不把大 base64 反复传给 Renderer。 +- browser 继续遵守现有 Main-owned browser seam 的边界。 + +## 14. 渲染与内存性能 + +- `REN-001`:Main 按 16–33 ms 窗口合并 text/thinking delta。 +- `REN-002`:Renderer 只更新当前 Conversation 的目标 node/block;禁止每 token 替换整个 transcript。 +- `REN-003`:tool cumulative partial output 必须 replace;禁止重复拼接。 +- `REN-004`:tool-call args 在 end 前只作为字符串 buffer。 +- `REN-005`:大图片/base64 转成 Main-owned attachment ID/preview URL;SSE/Zustand 不反复复制 base64。 +- `REN-006`:隐藏 Conversation 只更新轻量 run/unread summary,不做 Markdown 全量重渲染。 +- `REN-007`:Markdown、thinking 和大 tool output 使用 memoized block;超长输出默认折叠或虚拟化。 +- `REN-008`:压力 fixture 必须记录 Main patch 数、Renderer commit 数和 IPC/SSE bytes,证明 batching 生效。 + +## 15. 一次性迁移与旧数据政策 + +### 15.1 首次迁移 + +打开 schema v1 项目时,Main MUST 按顺序执行: + +1. 在 `.niancode/migration-backups/opencode-cutover-/` 复制原 `project.json` 与 `conversations.json`。 +2. 通过 Provider Service 把旧 Agent model string 唯一映射为结构化 model ref。 +3. 不能唯一映射的 Agent 标记 model required,保留其他字段。 +4. 原子写 project schema v2,保持 Agent identity、prompt、skills、archive。 +5. 写空的 Conversation schema v2;旧 OpenCode Session 不进入新 sidebar。 +6. 显示一次性说明:“旧版 OpenCode 对话不能在 Pi 中继续,原数据已保留用于回退或独立导出。” +7. 成功后写明确 schema version;失败时不覆盖 backup 或原始可恢复数据。 + +- `MIG-001`:迁移是一次性、显式版本分支,不建设通用 migration framework。 +- `MIG-002`:迁移前必须备份;无备份时不得覆写 schema v1。 +- `MIG-003`:旧 Conversation 不显示、不继续、不被 Pi release 读取。 +- `MIG-004`:如果产品需要旧聊天可读,必须在仍含 OpenCode 的旧版本中另做 read-only exporter;Pi release 不保留 OpenCode client/daemon。 + +### 15.2 `.opencode` 项目内容 + +- 只删除 materialization metadata 能证明是 Makelore 生成且未修改的 `.opencode/agent` 文件。 +- 用户修改过或所有权不确定的生成文件移到 migration backup。 +- 项目中其他 `.opencode` 内容不删除,但新应用永不读取。 +- 不创建 `.pi/agents` 替代。 + +### 15.3 userData + +`/opencode` 在首个 Pi release 保持原地、惰性且不读取。不得自动递归删除。它只支持完整应用版本回退或人工导出,不代表兼容层。 + +### 15.4 版本回退 + +1. 发布 Pi 版本前保留上一版安装包和 release tag。 +2. schema migration backup 是回退旧 metadata 的唯一输入。 +3. 阻断问题发生时安装上一版,并按 runbook 恢复 backup。 +4. Pi 新建 Conversation 不能在 OpenCode 版本继续,release note 必须明确。 +5. 普通项目文件不自动回滚;用户自行使用 VCS。 + +## 16. 打包和运行时交付 + +### 16.1 依赖 + +- `PKG-001`:删除精确版本 `opencode-ai`、`@opencode-ai/plugin` 和只为 OpenCode MCP 存在的 `@playwright/mcp`。 +- `PKG-002`:`@earendil-works/pi-coding-agent` 必须精确 pin;禁止 `^`/`~`。 +- `PKG-003`:lockfile、许可清单、runtime manifest 和 diagnostics 记录同一精确版本。 +- `PKG-004`:目标 Pi Node engine 必须与每个平台 bundled Electron Node 实测兼容。 + +### 16.2 Staging + +`scripts/bundle-pi-runtime.mjs` 或等价脚本 MUST 从 frozen lockfile/已安装依赖图生成 `build/pi-runtime`,包含: + +- Pi 运行所需 dist/resource; +- 全部 production dependencies; +- 当前平台 required optional dependencies; +- WASM/native 资源; +- Makelore extension bundle; +- 内建 coding skills; +- runtime manifest:package、version、entry、Node engine、resource paths。 + +构建阶段禁止在线安装或自动更新 Pi。不得只复制顶层 package。 + +### 16.3 electron-builder 与 artifact verifier + +- 删除 `build/opencode-ai` 与 `.opencode/skills` 打包映射。 +- 增加 `build/pi-runtime → pi-runtime`。 +- native/WASM 使用正确 `extraResources` 或 `asarUnpack`。 +- 保持 appId、productName、protocol 和现有产品路径。 + +最终 artifact verifier 必须证明: + +1. OpenCode package/binary/plugin 不存在。 +2. Pi manifest、package.json 与 lockfile 版本一致。 +3. CLI/RPC entry 可由 Electron Node 加载。 +4. production closure 全部 resolve。 +5. Node engine 相容。 +6. extension/skills paths 存在。 +7. 临时空项目 `get_state` 成功。 +8. 无 credential protocol smoke 可启动/退出;四协议受控 loopback/provider-shaped endpoint 使用环境凭据引用完成 prompt、settled、双 worker overlap 与 abort isolation。真实外部 Provider Account 不属于 release hard gate,`realTurnVerified` 保持 `false`。 +9. Windows、macOS x64/arm64、Linux artifact 无开发机绝对路径。 + +`PI-000` 的 macOS waiver 到 `PI-150` 即失效。`PI-150` 必须分别在 macOS +x64 与 arm64 上以独立 checkout/frozen install 运行 workspace 5+5、staged +5+5、controlled packaged 5+5、packaged loopback、closure/native/resource +检查和 metrics,再对最终目标 artifact 运行 verifier/smoke。静态 darwin path +test、其他平台回归或 Phase-0 waiver 均不能替代 macOS 实跑证据。 + +### 16.4 Real runtime smoke + +`smoke:pi:real` 中的 `real` 指最终 artifact 内实际 Pi runtime/process seam,不指真实外部 Provider Account。它必须通过受控 loopback/provider-shaped endpoint 覆盖:spawn、`get_state`、persistent session create/open、prompt accepted、text/tool projection、abort、settled、restart/reopen hydration、两个 worker 同时 run、一个 subagent child、clean shutdown。 + +## 17. 可观测性与性能预算 + +### 17.1 Privacy-safe milestones + +每次 cold/warm 首发记录: + +| milestone | 含义 | +| --- | --- | +| `conversation.local_create` | 产品 Conversation metadata 写入 | +| `composer.interactive` | 选择到 Textarea 可编辑 | +| `worker.queue_wait` | worker permit 等待 | +| `worker.spawn` | child spawn | +| `rpc.ready` | 首次 `get_state` 成功 | +| `session.open` | Pi session create/open | +| `resources.ready` | provider/prompt/skills/extensions ready | +| `prompt.accepted` | RPC prompt response success | +| `agent.start` | accepted 到 `agent_start` | +| `provider.first_event` | agent start 到首 provider event | +| `renderer.first_commit` | Main 首 patch 到 Renderer commit | +| `agent.settled` | accepted 到权威 idle | + +- `OBS-001`:记录匿名 Conversation 局部短 ID、workerGeneration、runId、cold/warm 和 duration。 +- `OBS-002`:不得记录 prompt、文件内容、credential、完整路径、账号 ID、完整 request ID 或原始 provider body。 +- `OBS-003`:本地 runtime overhead 与 provider first-event 延迟必须分开。 +- `OBS-004`:报告 p50/p95/max、样本数和平台/packaged 状态,不只报单次体验。 + +### 17.2 发布候选预算 + +| 指标 | p95 目标 | +| --- | ---: | +| 创建项目本地 metadata | ≤ 1,000 ms | +| 创建伙伴本地 metadata | ≤ 500 ms | +| 创建空 Conversation metadata | ≤ 500 ms | +| 选择新 Conversation → Composer 可编辑 | ≤ 500 ms | +| warm worker `rpc.ready` | ≤ 1,500 ms | +| cold worker `rpc.ready` | ≤ 3,000 ms | +| warm prompt accepted | ≤ 250 ms | +| cold prompt accepted | ≤ 3,000 ms | +| Main delta → Renderer commit | ≤ 50 ms | + +Operational limits:metadata Host API 5 秒;spawn/RPC ready 10 秒;graceful shutdown 3 秒;stream batching 16–33 ms。 + +这些预算只衡量本地 runtime/GUI overhead。受控 provider-shaped first event 单独报告,不得用它掩盖本地超时,也不得把它计入本地 p95。若未来自愿收集真实外部 Provider first token 数据,只能作为非阻断诊断样本,不改变本次 Accepted Risk。 + +### 17.3 必测场景 + +1. 全新 userData:创建项目、首伙伴、首 Conversation,不发送,测 Composer。 +2. 首次 prompt:拆分 spawn/RPC/session/resources/accepted/provider/first commit。 +3. 重启同一 Conversation,测 warm restore。 +4. 两个不同项目 Conversation 同时 prompt,通过最终 packaged provider-shaped loopback 验证窗口重叠、事件路由和单侧 abort 隔离;不要求真实外部 Provider turn。 +5. 同项目两个 Conversation 同时只读 tool,验证并行。 +6. 同项目两个 mutation tool,验证 lease 串行和可取消。 +7. parent 并行四个只读 subagent,同时另一个 Conversation 运行,验证全局 cap。 +8. 100 KB cumulative tool output、长 thinking、混合 blocks,测 patch/commit/bytes。 +9. 大图片输入,确认 SSE/Renderer 不重复搬运 base64。 +10. worker crash、SSE reconnect、app quit,确认恢复与进程清理。 + +## 18. 故障与恢复 + +| 故障 | 用户状态 | 自动动作 | 禁止行为 | +| --- | --- | --- | --- | +| spawn 失败 | 本地 Agent 启动失败;Composer 可编辑 | 手动 recover | 无限重试/禁用整个 Chat | +| RPC ready timeout | worker failed | kill、generation +1 | 复用未知 child | +| malformed stdout | worker crashed | 有界诊断、kill/recover | 当 assistant 文本 | +| mutating RPC timeout | request uncertain | reconcile session/queue | 静默重发 prompt | +| provider 429/5xx | retry banner | Pi 有界 retry | Main 叠加无界 retry | +| credential expired | auth error | refresh once + reopen | 输出 credential | +| extension error | 对应 tool/run error | 隔离目标 Conversation | 杀所有 worker | +| worker exit | recoverable | 取消 RPC/interaction/lease/child | 接收旧 generation event | +| SSE gap | reconnect | GET 目标 snapshot | 清空所有 Conversation | +| unreadable session | recoverable | 保留文件、允许诊断/新建 | 自动截断/覆盖 | +| disk write failure | persistence error | 停止目标新 prompt | 继续生成并假装持久化 | +| write lease wait | tool waiting | 可 abort | 并发 mutation | + +`recover` MUST:关闭旧 generation → cancel pending 资源 → 3 秒有界退出/kill → 从 registry 读取 sessionKey → 用最新 provider/resource revision spawn → `get_state/get_entries` hydrate → 发布新 generation Snapshot。不得自动重放未确认 prompt。 + +## 19. 验证合同 + +### 19.1 Unit + +- RPC framing、correlation、protocol violation、timeout、generation invalidation。 +- event projector 的 text/thinking/toolcall、cumulative tool、`toolResult`、`agent_settled`、retry、compaction、queue、interaction、unknown custom。 +- active leaf/parent hydration、abandoned branch、compaction retained tail、live/durable ID reconcile。 +- worker single-flight、caps、LRU、crash cleanup、quit、provider revision。 +- extension explicit resources、interaction cancel、subagent modes/caps/abort、write lease、known schema versions。 +- schema v1 backup/migration、unresolved model、stable Agent ID、atomic failure。 + +### 19.2 Main integration + +使用可编程 fake Pi child 输出锁定版本 fixture,覆盖 Host acceptance → SSE patches → snapshot reconcile、gap/reconnect、provider redaction/model、persistence/reopen/fork/delete、files/changes、crash/recover。另有少量 real Pi smoke 防止 fixture 漂移。 + +### 19.3 Renderer + +- metadata 请求 pending 时 selected Agent 仍可输入。 +- 首发 optimistic/reconcile。 +- per-Conversation run/error/queue/model/draft 隔离。 +- message/tool/thinking/compaction/interaction/subagent 映射。 +- seq gap 只刷新目标 Conversation。 +- cumulative tool output 不重复。 +- hidden Conversation 不重渲染全文。 + +### 19.4 Electron E2E + +至少覆盖: + +1. 首项目/伙伴/Conversation Composer readiness; +2. 首 prompt cold path; +3. 两个 Conversation 同时 streaming; +4. 模型切换只影响一个 Conversation; +5. steer/follow-up queue; +6. compaction retry + settled; +7. interaction select/confirm/input; +8. parallel subagent nested UI; +9. worker crash/recover; +10. image attachment; +11. packaged skill/browser tool; +12. old schema migration notice。 + +### 19.5 必跑命令 + +实现完成后必须运行仓库 pin 的 pnpm: + +```text +pnpm run typecheck +pnpm run lint:check +pnpm test +pnpm run build:vite +pnpm run test:e2e +``` + +另外必须运行 Pi artifact verifier、real runtime smoke、Windows/macOS x64/arm64/Linux packaged smoke 和第 17 节性能场景。任何未运行项必须阻止 release,不得写成“本地测试通过即可替代”。这里的 real runtime smoke 按第 16.4 节使用实际 packaged Pi + 受控 provider-shaped endpoint,不要求真实外部 Provider Account。`PI-000` 的 macOS waiver 不延续到此发布门;macOS x64 或 arm64 任一缺失时均不得宣称 cross-platform release-ready。 + +## 20. Definition of Done + +### 20.1 架构 + +- `CUT-*`、`BND-*`、`ARC-*` 全部满足。 +- Main 有 vendor-neutral `CodingConversationRuntime` 和唯一生产 `PiConversationRuntime`。 +- Renderer 不导入或解析任何 Pi wire 类型。 +- `.niancode/project.json`、稳定 Agent ID 和通用 app/storage 标识保持。 + +### 20.2 能力 + +- 文本、图片、thinking、tool、retry、compaction、queue、interaction、模型/thinking、file/changes、browser、skills 全部通过测试与 E2E。 +- 两个独立 Conversation worker 通过最终 packaged provider-shaped loopback turn 重叠且受控状态不串线;真实外部 Provider 隔离仍是明确 Accepted Risk,不得宣称已验证。 +- subagent single/parallel/chain、child abort、失败隔离和全局 cap 正常。 +- 同项目 mutation 受写租约控制。 +- share、revert/unrevert、todos、global runtime controls 已从 UI、route、type、test、文档删除。 + +### 20.3 性能 + +- 第 17 节 p95 预算全部达标。 +- 不存在分钟级无界 Composer disabled/loading。 +- 100 KB tool/长 thinking/大图片压力场景证明 batching、局部更新和 attachment ref 生效。 +- 4 parent + 4 child 场景下主 UI 保持可交互,RSS/CPU 数据进入发布报告。 + +### 20.4 迁移与交付 + +- schema v1 backup、schema v2、unresolved model、一次性 notice、完整版本回退 runbook 通过。 +- package/lock/artifact 只含精确 Pi runtime,OpenCode 为零。 +- typecheck、lint、unit、build、E2E、实际 packaged Pi + provider-shaped loopback、Windows x64/Linux x64/macOS x64/macOS arm64 packaged 与性能验证全部通过;`PI-000` 的 macOS waiver 不构成此处证据,真实外部 Provider waiver 也不得被写成 Pass。 +- README、accepted ADR、canonical architecture/current state/business rules/success criteria 和 superseded OpenCode commitments 由 Integration Gate 更新。 +- release note 明确旧 Conversation 不继续、已删除能力和回退限制。 + +### 20.5 零残留 + +下列搜索只允许命中历史 task/proposal、migration backup/notice 文案: + +```text +rg -i "opencode|@opencode-ai|/api/opencode|\.opencode" . +``` + +`package.json`、lockfile、生产源码、`dist-electron`、`build/pi-runtime`、ASAR/extraResources 和最终安装产物不得命中 OpenCode package、binary、plugin、route 或 runtime resource。 + +## 21. 需求到票据追踪 + +| 需求组 | 主交付票据 | +| --- | --- | +| `QG-*`(Phase-0 Windows x64/Linux x64;`QG-004`/`QG-005` explicit waivers) | `PI-000` | +| Phase-0 延期的 macOS x64/arm64 workspace/staged/controlled packaged/loopback/metrics | `PI-150` | +| `BND-*` | `PI-010`, `PI-020`, `PI-100`, `PI-110`, `PI-140` | +| `ARC-*`, `EVT-*` contracts | `PI-010`, `PI-060` | +| `DAT-*`, `MIG-*` schema/migration | `PI-020`, `PI-140` | +| `RPC-*`, `RUN-*` process foundation | `PI-030`, `PI-050` | +| `PRV-*`, `RES-*` | `PI-040` | +| event mapping/hydration/recovery | `PI-060` | +| `EXT-*`, `LEASE-*`, interaction | `PI-070` | +| `UX-*` overall | `PI-020`, `PI-050`, `PI-080`, `PI-100`, `PI-120`, `PI-130`, `PI-150` | +| `UX-040` true Conversation concurrency | `PI-050`, `PI-150` | +| `UX-041`~`UX-044`, `subagent.v1` | `PI-080`, `PI-130` | +| `CHG-*`, browser/game/skills/changes | `PI-090`, `PI-105`, `PI-130` | +| `API-*` and `/api/coding` | `PI-100`, `PI-105` | +| Snapshot/Patch Renderer store | `PI-110` | +| `UX-001`~`UX-005` local-only creation | `PI-020`, `PI-100`, `PI-120` | +| `UX-010`~`UX-033` Conversation UX | `PI-100`, `PI-120`, `PI-130` | +| `REN-*` | `PI-120`, `PI-150` | +| model/queue/compaction/interaction/subagent/changes UI | `PI-130` | +| `CUT-*`, OpenCode deletion, release migration notice | `PI-140`, `PI-160` | +| `PKG-*`, `OBS-*`, performance/cross-platform verification | `PI-150` | +| 全部 DoD、独立审阅、canonical promotion | `PI-160` | + +依赖、ready frontier、文件所有权和每张票据验收条件见关联票据文档。 diff --git a/.project-docs/10-decisions/proposals/20260822-pi-runtime-spec-b6e2c9a4__pi-runtime-cutover-tickets.md b/.project-docs/10-decisions/proposals/20260822-pi-runtime-spec-b6e2c9a4__pi-runtime-cutover-tickets.md new file mode 100644 index 0000000..5ef217b --- /dev/null +++ b/.project-docs/10-decisions/proposals/20260822-pi-runtime-spec-b6e2c9a4__pi-runtime-cutover-tickets.md @@ -0,0 +1,1142 @@ +# ML-CODE-PI-001 实施票据与依赖图 + +## 0. 文档信息 + +| 字段 | 值 | +| --- | --- | +| 关联 Spec | `ML-CODE-PI-001` | +| 状态 | 实施中;`PI-000` Done with explicit waivers | +| Task | `20260822-pi-runtime-spec-b6e2c9a4` | +| `PI-000` 实施基线 | `2bc423ebc58589307442ebdaf1c81d83ec9dc2d5`(`feat: qualify Pi runtime cutover foundation`) | +| 目标 | 把完整硬切换拆成可验证、无循环依赖的执行图 | +| 当前 Ready Frontier | `PI-010`, `PI-020`, `PI-030`(`PI-000` 已 Done with explicit waivers) | +| Phase-0 macOS | **Explicitly Waived / Deferred by user on 2026-08-22**;不是 Pass,实际验收移至 `PI-150` | +| 真实 Provider | `QG-004`/`QG-005` **Explicitly Waived / Accepted Risk by user on 2026-08-22**;不是 Pass,也不再阻断 release | + +本文中的“票据”是仓库内实施单元,不代表已经创建 GitHub Issue、分支、PR 或子智能体。后续执行者可以把票据同步到外部 issue tracker,但票据 ID、依赖和验收语义必须保持。 + +## 0.1 当前规划状态(2026-08-22) + +| 字段 | 当前判断 | +| --- | --- | +| `PI-000` 实施任务 | `20260822-pi-runtime-qualification-c52e8a14`;commit `2bc423e`;`ready_for_integration` | +| `PI-000` 状态 | Done with explicit waivers;不是全部 QG Pass | +| 规划 frontier | `PI-010`, `PI-020`, `PI-030`;其余票据继续按 31 条依赖边锁定 | +| `PI-010` 交接 | `20260822-pi-conversation-contracts-a61d9c42`;基线 `2bc423e`;当前 `planning` | +| 资格证据 | `.project-docs/50-evidence/topics/20260822-pi-runtime-qualification-c52e8a14__pi-runtime-qualification.md` | + +已确认事实: + +- `PI-000` 已作为 commit + `2bc423ebc58589307442ebdaf1c81d83ec9dc2d5` 提交;实施工作树干净,任务登记为 + `ready_for_integration`。`PI-010` 已在该 commit 上建立独立工作树并进入 + `planning`;这不改变依赖已满足的 Ready Frontier 定义。 +- 精确候选 pin 为 `@earendil-works/pi-coding-agent@0.84.2`;上游 tag + `v0.84.2`、npm `gitHead` 与 commit 均为 + `914cf1472e715297caa30db4b9535d534a9eb718`。 +- Windows x64 的 workspace、临时 production closure,以及独立受控 Electron + Builder `dir`/`app.asar` probe 均可通过 `ELECTRON_RUN_AS_NODE=1` 启动 Pi + RPC。受控 artifact 使用 `MakelorePiProbe.exe` 和 + `resources/app.asar/dist/cli.js`,刻意不继承 Makelore 正式 + `electron-builder.yml` 的 OpenCode/Python/UV/signing/install hooks,也没有 + 改动最终产品打包 wiring。 +- 受控 artifact 可定位 Pi 0.84.2、CLI、130 个 Windows 适用 production + packages、9 个按平台跳过项和 6 个 runtime WASM/native assets;`.node` + 位于 `app.asar.unpacked`。Electron Builder 把同版本 + `@types/retry@0.12.0` 从 `p-retry` nested layout 扁平化到 root + `node_modules`,属于可解析布局变化,不是依赖丢失。 +- 受控 packaged artifact 的 5 cold / 5 warm 样本分别为:cold + p50/p95/max 668/682/682 ms,warm 660/663/663 ms;10 个 RSS 样本 + 138404/141320/141320 KiB;exit 16/18/18 ms。session/reopen、 + deterministic failure、local shell overlap、单侧 abort isolation、stdin + close 均通过,结束后没有 artifact 或 Pi RPC 残留进程。 +- packaged runtime 上的 `127.0.0.1` HTTP/SSE 合同 probe 已覆盖 + `openai-completions`、`openai-responses`、`anthropic-messages`、`openrouter`。 + 每种协议恰好产生 4 个 POST,验证 base URL/path、环境凭据 header、custom + header、model、PNG image、双 worker 成功 settled、turn 重叠、单侧 abort + 与另一侧成功以及 stdin-close;最终收紧重跑的重叠时间依次为 + 304/309/290/297 ms。 +- Anthropic 配置使用 Provider root,由 SDK 追加 `/v1/messages`;OpenAI 风格 + base URL 以 `/v1` 结尾,再追加 `chat/completions` 或 `responses`。该差异应 + 进入后续 Provider catalog fixture,而不能对四种协议统一拼接路径。 +- 最终 focused suite 为 9 tests;scoped ESLint、typecheck、`build:vite`、 + `git diff --check` 和 project-doc drift gate 已通过。 +- Linux x64 在 WSL2 Ubuntu 24.04、kernel + `6.6.87.2-microsoft-standard-WSL2` 中取得 workspace、staged production + closure 与 controlled Electron Builder `dir/app.asar` 三层证据。隔离快照以 + Node 22.22.2 和仓库 pin `pnpm@10.33.4` frozen install,运行 seam 为 + Electron 43.4.0 与 embedded Node 24.18.1。 +- Linux workspace 5 cold / 5 warm:cold p50/p95/max 426/853/853 ms,warm + 432/437/437 ms,RSS p95 170388 KiB,exit p95 8 ms。staged closure:cold + 426/428/428 ms,warm 423/440/440 ms,RSS p95 175956 KiB,exit p95 8 ms。 +- Linux controlled `dir/app.asar`:cold 526/545/545 ms,warm 545/569/569 + ms,RSS p95 181928 KiB,exit p95 7 ms;131 个 Linux 适用 package、8 个 + platform skip、6 个 runtime asset 和 unpacked native assets 均无缺失,只有 + 与 Windows 相同的同版本 `@types/retry@0.12.0` flattening。 +- Linux packaged 四协议 loopback 每种恰好 4 个 POST,路径、鉴权头、自定义 + header、model、PNG image、双 worker overlap、单侧 abort isolation 与清理 + 均通过;overlap 依次为 292/290/277/288 ms,结束后无 artifact/Pi RPC + 残留进程。Linux 报告代码也已移除运行时仍硬编码“Linux missing”的字段, + 改为 cross-platform aggregate requirement;7 tests、scoped ESLint、 + typecheck 和 1-sample integration probe 通过。 +- macOS 实跑前发现并修复一个真实 packaged-closure path defect:旧 inspector + 在 artifact 内用 `dirname(process.execPath)/resources` 推导资源目录;macOS + executable 位于 `/Contents/MacOS/MakelorePiProbe`,旧逻辑会错误读取 + `Contents/MacOS/resources/app.asar`,实际路径是 + `Contents/Resources/app.asar`。 +- 修复后由外层 platform-aware `packagedResourcesDirectory()` 只计算一次资源 + 目录,并通过 `PI_PROBE_RESOURCES_DIRECTORY` 传入 artifact process;closure + inspector 不再二次推导。focused test 已覆盖 darwin executable → + `Contents/Resources`。 +- 修复后的 Windows controlled packaged actual regression 以 1 cold / 1 warm + 样本通过,ready 为 650/641 ms;130 个适用 package、6 个 runtime asset 与 + unpacked native asset 均无缺失,证明显式资源目录传递没有破坏 Windows。 + 这只验证修法和 Windows 回归,不是 macOS 运行证据。 + +未验证但由用户于 2026-08-22 明确接受、且不再阻断 `PI-000` 或 release 的风险: + +- 四协议 loopback 只证明 Pi request serialization、SSE parsing、图片/header/ + model/credential plumbing、worker 重叠与 abort 隔离。它没有发起外部请求或 + 产生费用,也不证明任何真实 Provider Account、认证、proxy、rate-limit 或 + provider-specific response variance;四协议真实 endpoint/account/credential/ + successful settled/concurrency/abort/reopen/image-where-supported 样本仍缺失。 +- 用户明确接受真实认证/endpoint/proxy/rate-limit/provider-specific response + variation,base URL/header/environment credential/model/image 的真实协议兼容, + 真实 provider 并发或串行/限流,以及跨 worker abort/event/session/model/ + credential 隔离风险。`QG-004`、`QG-005` 因此标记为 **Explicitly Waived / + Accepted Risk**,不是 Pass;所有资格/发布报告继续保留 + `realTurnVerified: false`。 + +用户明确豁免并延期的 Phase-0 项(不是 Pass,也不再阻断 `PI-000`): + +- 用户于 2026-08-22 决定跳过 `PI-000` 的 macOS 实跑。macOS x64 与 macOS + arm64 标记为 **Explicitly Waived / Deferred by user**;workspace、staged + production closure、controlled packaged seam、packaged loopback、资源和 + metrics 全部移至 `PI-150`。 +- darwin 资源路径 preflight defect 已修并有静态测试,但两个架构都尚未实际 + 运行,所以不得标记 macOS Pass。延后可能使 packaging/native/resource/ + performance defect 到 release stage 才暴露并造成返工;这是用户接受的风险, + 不是已消除风险。 + +范围限定和后续约束: + +- 当前 Linux 证据来自真实 Ubuntu 24.04 x64 用户空间、Linux executable/native + selection、asar layout 和 process seam,但 kernel 为 WSL2 且 artifact 是 + controlled Builder `dir`。正式 Spec 的 `QG-002` 只明确要求 Linux packaged + app,没有额外要求 non-WSL kernel、native compositor、AppImage 或 deb, + 因此这些不是新增的 `PI-000` blocker;若它们属于最终发行格式,仍必须在 + `PI-150` 以实际 release artifact 验收。 +- 已发布包的 shrinkwrap 是 production-only,但 package root 仍列出 10 个 + devDependencies;npm 11 的直接 `ci --omit=dev` 会在 omit 前因不同步失败。 + 当前资格 probe 只在临时副本移除该字段后复用原 shrinkwrap。`PI-030` + 必须把永久 staging 处理定义为显式、可复现的实现与验收项,不能把临时 + workaround 当成最终打包已通过。 +- `PI-150` 的 macOS handoff 要求 x64/arm64 各使用独立 checkout、对应架构 + Node/Electron download 与 frozen install,依次运行 workspace 5+5、staged + 5+5、controlled packaged 5+5、packaged loopback、closure/native/resource + 检查和 metrics,再验证最终目标 artifact;不得跨架构复用 `node_modules` + 或 artifact。`PI-000` waiver 不得在 `PI-150` 被当作证据。 +- 当前执行环境没有仓库 CI definition、macOS runner/SSH host、GitLab runner + CLI 或 Provider credential environment variable。internal GitLab remote 只能 + 提供源码远端,不能生成真实 Provider 证据,也不能提前补齐 `PI-150` 延期的 + macOS 证据;前者已由用户接受为非阻断风险,后者仍将在 release stage 阻断 + `PI-150`。 + +当前 QG 状态(编号严格以 `ML-CODE-PI-001` 第 8.1 节为准;实施证据中的 +task-local 状态表不是规范编号来源): + +| Gate | 状态 | 规划解释 | +| --- | --- | --- | +| `QG-001` | Pass | 精确 identity、pin、engine、CLI 已确认 | +| `QG-002` | Pass | 修订后的 Phase-0 平台范围为 Windows x64 + Linux x64;两者 packaged qualification artifact 的 spawn/RPC/session/prompt/abort/settle/reopen/exit 已通过。macOS x64/arm64 Explicitly Waived / Deferred,不是 Pass | +| `QG-003` | Pass | 修订后的 Phase-0 平台范围内,Windows/Linux controlled qualification artifact 的完整适用 package、WASM/native asset 可解析;最终产品 artifact 与 macOS 实跑留在 `PI-150` | +| `QG-004` | Explicitly Waived / Accepted Risk | 不是 Pass。Windows/Linux packaged 四协议 loopback 合同通过且 `realTurnVerified=false`;真实 Account/auth/endpoint/proxy/rate-limit/response variance 与真实 base URL/header/environment credential/model/image 兼容未验证,由用户接受风险 | +| `QG-005` | Explicitly Waived / Accepted Risk | 不是 Pass。provider-shaped loopback two-worker overlap/单侧 abort 通过;真实 provider 并发或串行/限流及跨 worker abort/event/session/model/credential 隔离未验证,由用户接受风险 | +| `QG-006` | Pass | 修订后的 Phase-0 平台范围内,Windows/Linux cold/warm/RSS/exit 的 p50/p95/max 与样本数已报告;macOS metrics Explicitly Waived / Deferred 到 `PI-150`,不是 Pass | +| `QG-007` | Pending / 未触发 | 尚无已确认失败;只有未豁免的 packaging/provider-shaped contract/concurrency/abort/预算门确认失败才触发 utility-process SDK 修订。用户 waiver 不是失败 | + +规划判断:逐项审计 `PI-000` Required work、Acceptance 与 Stop condition 后, +所有未豁免要求均已有证据且没有已确认失败。macOS Phase-0 明确 deferred 到 +`PI-150`;`QG-004`/`QG-005` 明确 Accepted Risk;两类均不是 Pass。`QG-007` +保持 Pending / not triggered。因此 `PI-000` 标记为 **Done with explicit +waivers**。按未修改的 DAG 重新计算,精确新 Ready Frontier 是 `PI-010`、 +`PI-020`、`PI-030`。真实外部 Provider 不再是 `PI-000`/`PI-150`/release +blocker;macOS x64/arm64 在 `PI-150` 仍是不可豁免的 release blocker。若未来 +任何未豁免的 packaging、provider-shaped contract/concurrency/abort 或预算门 +出现确认失败,继续按 Stop condition 修订为 SDK-in-Electron-utility-process; +仍禁止 OpenCode fallback 或 RPC/SDK 双生产路径。 + +## 1. 执行规则 + +1. 所有产品代码票据属于同一个 OpenCode → Pi 硬切换 release unit。中间提交可以存在新旧源码,但任何部分不得以双 runtime 形式发布。 +2. `PI-000` 是阻断式资格门;当前已 Done with explicit waivers。任何历史或未来执行中,它未完成前仍不得开始会制造迁移成本的产品实现;本图将所有实施票据显式依赖它。 +3. 只有依赖全部 `Done` 的票据才进入 Ready Frontier。依赖不满足时,不得以“先写一点”绕过。 +4. 每张票据只拥有其“主要文件/责任区”。共享文件必须由依赖更靠前的票据定义合同,后续票据按合同消费;需要改变合同必须先更新上游测试和关联 Spec。 +5. 一张票据只有在自身 acceptance 和 verification 全部通过后才能标 `Done`。仅编译、仅单测、仅人工体验均不充分。 +6. `PI-000`~`PI-150` 构成一个实施分支/PR 的完整代码范围;不得把未删除 OpenCode 的中间状态合入 release。 +7. `PI-160` 是终端 Integration/Release Gate。它在实现 PR 完成后执行最终审阅、集成、canonical promotion 和发布阻断检查。 +8. 本票据图不要求任何特定协作方式。当前文档任务按用户要求不创建子智能体;未来执行也必须遵守当时用户和仓库并发所有权规则。 + +## 2. 依赖图 + +```mermaid +flowchart TD + P000[PI-000 Runtime qualification gate] + P010[PI-010 Conversation contracts & reducer] + P020[PI-020 Schema v2 & migration] + P030[PI-030 Pi process/RPC foundation] + P040[PI-040 Provider & managed resources] + P050[PI-050 Worker pool & session registry] + P060[PI-060 Event projector & hydration] + P070[PI-070 Extension host, interactions & lease] + P080[PI-080 Subagent scheduler] + P090[PI-090 Product tools & change tracker] + P100[PI-100 Core /api/coding Host API] + P105[PI-105 Files/changes/skills Host API] + P110[PI-110 Renderer protocol store] + P120[PI-120 Core Chat & first-Conversation UX] + P130[PI-130 Feature-complete Coding UI] + P140[PI-140 OpenCode removal & cutover migration] + P150[PI-150 Packaging, E2E & performance release proof] + P160[PI-160 Integration and release gate] + + P000 --> P010 + P000 --> P020 + P000 --> P030 + P020 --> P040 + P030 --> P040 + P010 --> P050 + P020 --> P050 + P030 --> P050 + P040 --> P050 + P010 --> P060 + P050 --> P060 + P060 --> P070 + P040 --> P070 + P070 --> P080 + P070 --> P090 + P020 --> P090 + P020 --> P100 + P060 --> P100 + P070 --> P100 + P020 --> P105 + P090 --> P105 + P010 --> P110 + P100 --> P110 + P110 --> P120 + P080 --> P130 + P090 --> P130 + P105 --> P130 + P120 --> P130 + P130 --> P140 + P140 --> P150 + P150 --> P160 +``` + +## 3. 票据总览 + +| ID | 标题 | Depends on | 主要责任区 | 完成后解锁 | +| --- | --- | --- | --- | --- | +| `PI-000` | Runtime qualification gate | — | 精确版本、packaged probe、Provider/并发/RSS 证据 | `010`, `020`, `030` | +| `PI-010` | Conversation contracts & reducer | `000` | shared contracts、fixtures、in-memory runtime | `050`, `060`, `110` | +| `PI-020` | Schema v2 & migration | `000` | `coding-projects`、project/conversation schema、backup | `040`, `050`, `090`, `100`, `105` | +| `PI-030` | Pi process/RPC foundation | `000` | bundler、worker process、LF framer、RPC client | `040`, `050` | +| `PI-040` | Provider & managed resources | `020`, `030` | provider catalog、secret、prompt/skill/resource loader | `050`, `070` | +| `PI-050` | Worker pool & session registry | `010`, `020`, `030`, `040` | runtime、pool、registry、lifecycle、telemetry base | `060` | +| `PI-060` | Event projector & hydration | `010`, `050` | live assembly、active leaf、snapshot/reconcile/recover | `070`, `100` | +| `PI-070` | Extension host, interactions & lease | `040`, `060` | managed extension bridge、ask_user、write lease | `080`, `090`, `100` | +| `PI-080` | Subagent scheduler | `070` | `subagent.v1`、child process、caps、abort | `130` | +| `PI-090` | Product tools & change tracker | `020`, `070` | browser/game/task/changed-file/skills、change tracker | `105`, `130` | +| `PI-100` | Core `/api/coding` Host API | `020`, `060`, `070` | project/conversation routes、SSE、interactions | `110` | +| `PI-105` | Files/changes/skills Host API | `020`, `090` | file/search/change/skill routes | `130` | +| `PI-110` | Renderer protocol store | `010`, `100` | typed facade、per-Conversation store、snapshot/patch | `120` | +| `PI-120` | Core Chat & first-Conversation UX | `110` | Composer、timeline、optimistic prompt、render batching | `130` | +| `PI-130` | Feature-complete Coding UI | `080`, `090`, `105`, `120` | model/thinking/queue/compaction/interactions/subagents/changes | `140` | +| `PI-140` | OpenCode removal & cutover migration | `130` | 删除旧 runtime/routes/deps/UI/tests/resources、migration notice | `150` | +| `PI-150` | Packaging, E2E & performance proof | `140` | artifact、real smoke、full E2E、Windows/Linux/macOS x64+arm64、perf report | `160` | +| `PI-160` | Integration and release gate | `150` | final review、zero residue、canonical docs、release decision | Release | + +## 4. Ready Frontier 演进 + +下面是依赖完成后的预期 frontier,不是必须串行执行的阶段清单: + +| 条件 | 新 Ready Frontier | +| --- | --- | +| 初始,以及 `PI-000` 完成前 | `PI-000`(唯一 frontier;未 Done,无下游解锁) | +| 2026-08-22 `PI-000` Done with explicit waivers | `PI-010`, `PI-020`, `PI-030` | +| `PI-020` + `PI-030` Done | `PI-040`(同时等待中的 `PI-010` 可继续) | +| `PI-010` + `PI-020` + `PI-030` + `PI-040` Done | `PI-050` | +| `PI-050` Done | `PI-060` | +| `PI-060` Done | `PI-070`;`PI-100` 仍等 `PI-070` | +| `PI-070` Done | `PI-080`, `PI-090`, `PI-100` | +| `PI-090` Done | `PI-105` | +| `PI-100` Done | `PI-110` | +| `PI-110` Done | `PI-120` | +| `PI-080` + `PI-090` + `PI-105` + `PI-120` Done | `PI-130` | +| `PI-130` Done | `PI-140` | +| `PI-140` Done | `PI-150` | +| `PI-150` Done | `PI-160` | + +## 5. 票据详情 + +## PI-000 — Runtime qualification gate + +### Objective + +在写业务迁移前,证明选定 Pi 版本能在 Makelore qualification 打包边界中满足 runtime、四协议 packaged loopback/provider-shaped 并发、资源和退出要求;对明确未验证的真实外部 Provider 风险作出显式人类决定,确认失败时尽早终止错误架构。 + +### Dependencies + +- None。 + +### Current planning status + +- **Done with explicit waivers**;实施任务为 + `20260822-pi-runtime-qualification-c52e8a14`。这不是全部 QG Pass。 +- Windows x64 与 WSL2 Linux x64 的 workspace/staged/controlled packaged RPC、 + session、process、closure、loopback 四协议合同与预算证据通过;受控 artifact + 不是最终 bundler 或 release installer。 +- 用户于 2026-08-22 将 macOS x64/arm64 明确标记为 **Explicitly Waived / + Deferred** for `PI-000`。packaged resource path defect 已修并通过 focused path + test 与 Windows actual regression,但 macOS 没有实跑、不是 Pass;workspace/ + staged/controlled packaged/loopback/metrics 全部移交 `PI-150`。 +- 用户于 2026-08-22 明确接受四协议真实 Provider Account/兼容与真实双 worker + 并发/隔离风险,`QG-004`、`QG-005` 为 Explicitly Waived / Accepted Risk, + 不是 Pass。loopback 不得被归类为 Provider Account qualification,报告保持 + `realTurnVerified=false`。 +- Linux 最终 release artifact 与 macOS 全套实跑属于 `PI-150`;macOS 仍是 + release blocker。当前 `PI-000` 无剩余 Phase-0 blocker,已解锁 `PI-010`、 + `PI-020`、`PI-030`。 + +### Owns + +- 精确 Pi version/tag/commit 决策。 +- 最小 package pin 与 lockfile 变更。 +- 最小 `scripts/probe-pi-runtime.*`、临时/受控 packaged probe、结果报告。 +- 资格证据,不拥有最终 Chat、Host route 或迁移实现。 + +### Required work + +1. 复核候选 `@earendil-works/pi-coding-agent@0.84.2`;锁定确切 package version、CLI entry、Node engine、RPC/session docs 对应 tag/commit。 +2. 以 Electron Node 在 Phase-0 平台范围 Windows x64、Linux x64 的 unpackaged 与 packaged qualification seam 启动 RPC。 +3. 验证 `get_state`、persistent session create/open、prompt accepted、abort、`agent_settled`、reopen、clean shutdown。 +4. 从 frozen dependency graph stage Windows x64、Linux x64 的最小完整 production closure,验证 packaged qualification artifact 的 WASM/native/optional resources;最终产品 artifact 留给 `PI-150`。 +5. 用 Windows/Linux packaged loopback 覆盖所有受支持协议的 base URL/path、headers、environment credential plumbing、模型、图片 request/SSE 合同;记录真实外部 Provider Account 验证已 Explicitly Waived / Accepted Risk,`realTurnVerified=false`。 +6. 两个独立 worker 同时运行 provider-shaped loopback turn,证明受控时间窗口重叠、单侧 abort 与另一侧 settled;记录真实 provider 并发/串行/限流及跨 worker abort/event/session/model/credential 隔离为 Accepted Risk。 +7. 在 Windows x64、Linux x64 运行 cold/warm/RSS/exit samples,报告 p50/p95/max、样本数、平台和 artifact 状态。 +8. 明确 `AGENTS.md`/context、offline、trust、resource discovery、shell 的锁定版本行为。 + +macOS x64/arm64 在本票据中 Explicitly Waived / Deferred by user on +2026-08-22;这不是平台通过证据。对应 workspace/staged/controlled packaged/ +loopback/metrics 工作由 `PI-150` 接管。 + +### Closure audit(2026-08-22) + +| Required work | 审计状态 | 依据 | +| --- | --- | --- | +| 1. identity/pin/engine/entry | Pass | Pi 0.84.2、tag/commit、CLI、Electron Node engine 与 frozen lockfile 已确认 | +| 2. platform process seam | Pass + explicit macOS deferral | Windows x64 与 Linux x64 workspace/staged/controlled packaged 已通过;macOS x64/arm64 不是 Pass,转 `PI-150` | +| 3. session/RPC lifecycle | Pass | get_state、create/open、prompt accepted、abort、settled、reopen、clean shutdown 已通过 | +| 4. production closure/assets | Pass | Windows/Linux 适用 packages、WASM/native/optional assets 在 qualification artifact 可解析;最终 artifact 留 `PI-150` | +| 5. protocol compatibility | Explicitly Waived / Accepted Risk;替代 smoke Pass | 四协议 packaged loopback 的 base URL/path/header/environment credential plumbing/model/image request/SSE 已通过;真实外部 Provider 未验证且 `realTurnVerified=false` | +| 6. two-worker concurrency/isolation | Explicitly Waived / Accepted Risk;替代 smoke Pass | provider-shaped two-worker overlap、单侧 abort、另一侧 settled 与受控路由已通过;真实 provider 行为未验证 | +| 7. latency/RSS/exit budgets | Pass | Windows/Linux cold/warm/RSS/exit 样本与 p50/p95/max 已记录并满足预算 | +| 8. context/offline/trust/resources/shell | Pass | 锁定版本以 explicit no-discovery/no-context/no-approve/offline flags 运行,shell 与 abort 行为已验证 | + +审计未发现除上述两类显式 waiver 外的未完成 Required work。Acceptance 的 +非豁免证据均成立,Stop condition 没有已确认失败,因此 `QG-007` 不触发。 + +### Acceptance + +- Spec `QG-001`、`QG-002`、`QG-003`、`QG-006` 在修订后的 Phase-0 平台范围内有可重复证据;`QG-004`、`QG-005` 有明确人类 waiver、完整风险记录和不可误标 Pass 的证据状态。 +- cold `rpc.ready` p95 ≤ 3,000 ms;warm p95 ≤ 1,500 ms;10 秒 operational deadline 内确定性失败。 +- 两 worker packaged provider-shaped loopback turn 重叠;一个 worker abort/crash 不改变另一个受控状态。真实外部 Provider turn 不属于 hard gate。 +- 完整 dependency closure 从 Windows/Linux packaged qualification artifact resolve,退出无遗留 Pi child;最终产品 artifact closure 属于 `PI-150`。 +- Packaged loopback matrix 覆盖当前实际支持的每种协议类型;真实外部 Account 兼容保持 `realTurnVerified=false` 和 Accepted Risk,不 silently fallback 或伪报 Pass。 +- 精确 pin,不使用 `^`/`~`。 +- macOS waiver 只允许 `PI-000` 不等待该平台,既不构成 macOS Pass,也不满足 `PI-150` 的 cross-platform acceptance。 +- `QG-004`/`QG-005` waiver 只移除真实外部 Provider hard gate;Windows/Linux 及最终 artifact 的 packaged loopback/provider-shaped overlap/abort smoke 仍必须通过。 + +### Stop condition + +若修订后 Phase-0 范围内未豁免的 packaging、provider-shaped contract/concurrency/abort 或预算不通过:本票据保持或重新进入 Blocked,停止后续实现。只能通过修订 Spec/ADR 选择“Pi SDK in Electron utility process”后重新验收;不得引入 OpenCode fallback 或 RPC/SDK 双生产路径。缺少已豁免的真实外部 Provider 样本不是失败,不触发 `QG-007`。延期的 macOS 验证若在 `PI-150` 失败,则必须阻断 release 并返工或修订架构,不得以本 waiver 放行。 + +### Verification + +- Probe command 和结果可以在干净安装依赖后重跑。 +- artifact dependency resolve verifier。 +- 两 worker packaged provider-shaped loopback overlap/abort smoke;结果明确 `realTurnVerified=false`。 +- process-tree clean-exit 检查。 + +## PI-010 — Conversation contracts and reducer + +### Objective + +先固化 vendor-neutral Conversation 领域协议,使 Main、Renderer 和测试不再围绕 OpenCode/Pi wire 结构耦合。 + +### Dependencies + +- `PI-000`。 + +### Owns + +- `electron/coding-runtime/contracts.ts` 或 shared 等价位置。 +- `ConversationSnapshot`、`ConversationPatchEnvelope`、node/run/queue/context/interaction/model/error DTO。 +- 统一 normalization reducer。 +- `InMemoryConversationRuntime`。 +- 锁定 Pi 版本的 wire fixture → product fixture 边界定义。 + +### Required work + +1. 实现 Spec 第 5、10 节全部公开类型,公共 export 不导入 Pi/OpenCode。 +2. reducer 支持 snapshot replace、严格 generation/seq、gap invalidation、message/block/tool/queue/interaction/compaction/subagent/context ops。 +3. UI ID 与 durable source ID 分离;optimistic user node 通过 `clientRequestId` reconcile。 +4. 用同一个 reducer 处理 live fixture 和 hydrated fixture。 +5. 实现 in-memory runtime,供 Renderer/Host product tests 使用。 +6. 明确 unknown op/schema 的 fail-closed 行为。 + +### Acceptance + +- 混合 text/thinking/tool、parallel tools、tool error、retry、compaction willRetry、queue、interaction、subagent、unknown custom fixture 全通过。 +- seq gap 只 invalidates 目标 Conversation;旧 generation patch 被丢弃。 +- `toolResult` 不成为 message;cumulative tool output 不重复。 +- `agent_end` fixture 不产生 idle;`agent_settled` 才产生 idle。 +- Renderer contract test 没有任何 Pi/OpenCode import。 + +### Verification + +- Focused contract/reducer unit tests。 +- Type-level no-vendor import check 或 scoped `rg` assertion。 +- Snapshot → patches → snapshot equivalence fixture。 + +## PI-020 — Project and Conversation schema v2 migration + +### Objective + +把项目/伙伴/Conversation 产品数据从 OpenCode runtime identity 中解耦,并保证一次性迁移可回退、不会误删用户数据。 + +### Dependencies + +- `PI-000`。 + +### Owns + +- `electron/coding-projects/project-config.ts`。 +- `project-store.ts`、`conversation-store.ts`、migration/backup helpers。 +- schema v2 types/tests。 +- 项目、伙伴、空 Conversation 的 local-only create flow。 + +### Required work + +1. 定义 project schema v2 `ProductModelRef` 和 model unresolved 状态。 +2. 定义 Conversation schema v2、产品 UUID、optional Pi binding、opaque sessionKey。 +3. 实现 schema v1 backup → model mapping → v2 atomic write。 +4. 无唯一 model mapping 时保留 Agent,标记用户选择;禁止默认模型。 +5. v1 Conversation metadata 备份后初始化空 v2,不将旧 OpenCode Session 放入 sidebar。 +6. 只清理已证明未修改的 generated `.opencode/agent`;uncertain/modified 移到 backup。 +7. project/partner/Conversation create 不调用 runtime。 + +### Acceptance + +- stable Agent ID/name/prompt/skills/archive 完整保持。 +- 无 backup 时绝不覆写 v1;任意写失败保留可重试原状态。 +- `sessionKey` path traversal/absolute path 被拒绝。 +- 两个并发首发只建立一个 Pi session binding(通过 store single-flight contract fixture;实际 runtime 在后续票据接入)。 +- 旧 Conversation 不显示;一次性 notice 状态可被 UI 后续消费。 +- userData/opencode 和不确定 `.opencode` 内容未被递归删除。 +- 项目、伙伴、Conversation metadata 性能可单独测量,且无 child process。 + +### Verification + +- Migration table tests:unique/unresolved/no-account/corrupt write/retry/modified Agent file。 +- Atomic persistence tests。 +- Local-create integration test with spawn spy = 0。 + +## PI-030 — Pi process and RPC foundation + +### Objective + +提供可打包、可测试、可恢复的 Pi child process/RPC 底座,不掺入项目 CRUD 或 Renderer 逻辑。 + +### Dependencies + +- `PI-000`。 + +### Owns + +- `electron/coding-runtime/pi/worker-process.ts`。 +- `rpc-framer.ts`、`rpc-client.ts`、process errors。 +- 初版 `scripts/bundle-pi-runtime.mjs` 与 runtime manifest。 +- programmable fake Pi child test fixture。 + +### Required work + +1. Electron Node + `ELECTRON_RUN_AS_NODE=1` 启动锁定 Pi entry。 +2. 显式 offline/no-approve/no-auto-resource 参数;stdout RPC only、stderr log only。 +3. 自有 strict LF framer,支持 chunk 边界和 U+2028/U+2029。 +4. command ID correlation、out-of-order response、event dispatch、timeout/AbortSignal。 +5. malformed/oversized stdout、unexpected exit、stderr sanitizer、backpressure。 +6. process graceful shutdown 3 秒 + forced kill,确保整个 child tree 清理。 +7. stage 完整 production closure 与 manifest。 +8. 为已发布 Pi package 的 production-only shrinkwrap 与 root 10 个 + devDependencies 不同步选择并测试一个显式、可复现的永久 staging 机制; + 不把 `PI-000` 临时副本处理或受控 Electron Builder probe 直接当成最终 + bundler。artifact verifier 按可解析 package/version/asset 验证 closure, + 不把 Electron Builder 的同版本依赖扁平化误判为缺失。 + +### Acceptance + +- Spec `RPC-001`~`RPC-005` 全部通过。 +- stdout 任意非 JSONL 导致目标 worker protocol error,不污染其他 worker。 +- pending command 在 exit/abort/generation invalidation 时全部 settle,无 promise leak。 +- prompt 类 command timeout 不自动重发。 +- fake child 可脚本化输出 response/event/crash/partial line/large line,供后续 Main integration 使用。 +- bundler 在 frozen install 上确定性生成相同 manifest 内容(不要求额外 checksum 文件)。 +- 永久 staging 能在 npm 11 下从发布 package/shrinkwrap 可重复生成 production + closure,且测试覆盖 root devDependencies 不同步与同版本 nested-to-root + flattening;不依赖开发机已有 `node_modules` 或受控 probe 的临时目录。 + +### Verification + +- Focused process/RPC unit tests。 +- Real Pi `get_state` protocol smoke。 +- Packaged entry load/exit smoke。 + +## PI-040 — Provider and managed resources + +### Objective + +把当前 Provider Account、Works gateway、credential、伙伴 prompt 和 skills 映射为锁定 Pi 版本的受管输入,同时保持 Main-owned secret/trust 边界。 + +### Dependencies + +- `PI-020`。 +- `PI-030`。 + +### Owns + +- `electron/coding-runtime/pi/provider-config.ts`。 +- `resource-loader.ts`、prompt/Agent manifest cache。 +- `resources/coding-skills` 与 registry 映射。 +- Provider revision/staleness contract。 + +### Required work + +1. 实现 `PiProviderDescriptor` 和 account → runtime catalog 投影。 +2. 迁移 Works `/v1`、proxy token、custom header/base URL 语义。 +3. credential 只通过 worker environment/受管 provider 注入,完整日志脱敏。 +4. account refresh single-flight;认证恢复最多一次。 +5. provider/resource revision:idle stale 下次 prompt 前重建,running settled 后重建。 +6. 生成受管伙伴 prompt/Agent manifest,不生成 `.pi/agents`。 +7. 移动内建 skills 到 vendor-neutral resources,仅显式传 selected paths。 + +### Acceptance + +- 当前支持的每种 Provider Account/协议配置 fixture 都能构建有效 catalog,并通过 + packaged provider-shaped loopback 完成 prompt/settled;真实外部 Provider prompt + 保持可选、非阻断,未取得证据时 `realTurnVerified=false`。 +- 两个同 vendor account 的 runtime ID 不冲突。 +- model/image capability 校验准确;不可用模型原子失败。 +- token/header/prompt 内容不出现在 argv、snapshot、SSE、Renderer、diagnostics 或 test snapshot。 +- 项目/用户 `.pi` resources 不被自动加载。 +- provider revision 不打断 running run;settled 后才重建。 + +### Verification + +- Provider catalog fixtures。 +- Secret-redaction tests over argv/env-safe projection/stderr/API DTO。 +- Resource discovery negative tests。 +- Packaged provider-shaped loopback smoke from qualification matrix;可选 real account + smoke 只能作为非阻断诊断,不得把 waiver 改写为 Pass。 + +## PI-050 — Worker pool and session registry + +### Objective + +实现一 Conversation 一 worker 的真实并发、session 持久绑定、资源调度和生命周期隔离。 + +### Dependencies + +- `PI-010`。 +- `PI-020`。 +- `PI-030`。 +- `PI-040`。 + +### Owns + +- `electron/coding-runtime/pi/runtime.ts`。 +- `worker-pool.ts`、session registry、permit queue、idle LRU。 +- prepare/prompt/queue/abort/model/thinking/compact/fork/dispose lifecycle orchestration。 +- telemetry base/span correlation。 + +### Required work + +1. per-Conversation `prepare` single-flight 和 lazy session open/create。 +2. worker state machine、generation、pending commands/interactions/children references。 +3. top-level running cap 4、warm idle cap 4、total process soft cap integration。 +4. running 不 eviction,idle LRU,app quit shutdown。 +5. prompt/steer/follow-up acceptance,不等待 settle。 +6. abort/model/thinking/compact/fork/recover/dispose 目标隔离。 +7. Provider/resource stale 在安全边界重建。 +8. `worker.queue_wait/spawn/rpc.ready/session.open/resources.ready/prompt.accepted` telemetry。 + +### Acceptance + +- 一 worker 永不并行承载两个 Conversation。 +- 第 5 个 top-level run 进入自己的 queue;前四个 settled 后按公平顺序继续。 +- running worker 不被 LRU kill;idle 超限准确 eviction/reopen。 +- crash generation 清理 pending RPC/permit/interaction/child reference,并不影响其他 worker。 +- prompt HTTP/RPC acceptance 与 run completion 分离。 +- model/thinking 只改变目标 Conversation;不可用模型不改变旧状态。 +- app quit 3 秒后无遗留 parent worker。 + +### Verification + +- Worker-pool focused tests:single-flight、caps、fair queue、LRU、crash、quit、stale revision。 +- Two-worker fake + packaged provider-shaped loopback concurrent run integration;真实外部 Provider 非阻断且不得写成已验证。 +- Process-tree cleanup test。 + +## PI-060 — Event projector, hydration, and recovery + +### Objective + +把 Pi live RPC 和 persisted session tree 正确投影为 Makelore Snapshot/Patch,解决 streaming identity、active branch、settled reconciliation 和恢复。 + +### Dependencies + +- `PI-010`。 +- `PI-050`。 + +### Owns + +- `event-projector.ts`。 +- `session-projector.ts`。 +- attachment projection hooks。 +- snapshot creation、durable reconcile、recover orchestration integration。 + +### Required work + +1. 按 `contentIndex` 组装 text/thinking/toolcall;`message_end` 权威替换。 +2. cumulative `partialResult` replace;`toolResult` attach tool card。 +3. run/turn/retry/compaction/queue/interaction/usage/context 映射。 +4. `agent_end` checkpoint、`agent_settled` idle。 +5. entries + leafId + parentId active path hydration;废弃分支排除。 +6. live UI ID 与 durable entry ID reconcile。 +7. generation/seq envelope、gap invalidation、snapshot rebuild。 +8. unknown/custom bounded diagnostics、large image attachment ref。 +9. recover:旧 generation close、资源清理、reopen、hydrate、新 snapshot。 + +### Acceptance + +- Spec 第 10、11、18 节 event/session/failure 规则全部有 fixture。 +- live run 完成后的 Snapshot 与从 session 冷 hydrate 的 Snapshot 等价。 +- compaction `willRetry` 不 idle;queue continuation 不提前释放 permit。 +- abandoned branch 永不进入 active timeline。 +- tool partial output 不重复;camel-case `toolResult` 不变 assistant。 +- SSE gap/recover 只替换目标 Conversation。 +- unreadable session 保留原文件并进入 recoverable error,不自动截断。 + +### Verification + +- Projector/session unit fixtures from exact Pi version。 +- Fake Pi end-to-end run → settle → reopen comparison。 +- Small real Pi drift smoke。 + +## PI-070 — Managed extension host, interactions, and write lease + +### Objective + +建立唯一受管 extension entry、最小 child→Main seam、用户 interaction 和同项目 mutation lease。 + +### Dependencies + +- `PI-040`。 +- `PI-060`。 + +### Owns + +- `extensions/makelore-runtime.ts`。 +- extension internal endpoint/IPC bridge 与 worker token registry。 +- `interaction.ts`、`write-lease.ts`。 +- interaction Host-side store and projector integration。 + +### Required work + +1. 只显式加载一个 versioned extension bundle。 +2. 每 worker loopback/IPC token,dispose/recover 后失效。 +3. 验证 conversationId/generation/runId 与 worker registry。 +4. `ask_user` select/confirm/input/editor;notify/status/widget/title/editor-text 安全投影。 +5. 同项目 mutation write lease、可取消等待、所有终态释放。 +6. worker crash/abort 清理 interaction/lease。 +7. unknown UI/widget/details 只诊断,不 raw dump。 + +### Acceptance + +- 项目/用户第三方 Pi extension 不会被加载。 +- 伪造旧 generation/token 的 request 被拒绝且不影响当前 worker。 +- interaction response 严格按 ID;crash/abort 后全部 cancelled。 +- 同项目 mutation 串行;不同项目并行;read-only 不等待 lease。 +- complete/error/abort/timeout/crash 全部释放 lease,无永久占用。 +- Textarea draft revision 已变化时 `set_editor_text` 不覆盖。 + +### Verification + +- Extension integration tests through real bridge, not direct function-only mocks。 +- Lease concurrency/cancellation matrix。 +- Interaction lifecycle and stale-token tests。 + +## PI-080 — Subagent scheduler and nested protocol + +### Objective + +用 Makelore-owned `subagent.v1` 实现多 child Pi 调度,不依赖官方示例的非稳定 GUI details。 + +### Dependencies + +- `PI-070`。 + +### Owns + +- `extensions/subagent.ts`。 +- child process supervisor、global semaphore、parent-child registry。 +- `subagent.v1` schema/projector fixtures。 + +### Required work + +1. `single`、`parallel`、`chain` dispatch validation;单次最多 8 tasks。 +2. 每 child 独立 Pi process/context,使用受管 Agent manifest/model/tool profile。 +3. 全应用最多 4 running child,和 parent process 组成 soft cap 8。 +4. read-only/coding tool profile;coding child 接入 write lease。 +5. parent abort/crash 传播 child;child exit 清理 permit。 +6. parallel 保留其他完成结果;chain 首失败停止并标 skipped。 +7. nested progress/details 只输出 `subagent.v1`。 + +### Acceptance + +- 1、4、8 tasks 的 single/parallel/chain fixture 正确;第 9 个 schema validation 拒绝。 +- 两个 parent 争用全局 child semaphore 时总 running child ≤ 4。 +- parent abort 后无 child orphan;permit 全释放。 +- 一个 parallel child failure 不删除其他 summary/usage;chain 后续标 skipped。 +- unknown schema/version 不崩 UI、不 dump raw JSON。 +- 同项目 coding child 与 parent mutation 遵守同一 lease。 + +### Verification + +- Fake child scheduler integration tests。 +- One real subagent child packaged smoke。 +- Process-tree and semaphore leak check。 + +## PI-090 — Product tools and Conversation change tracker + +### Objective + +迁移 Makelore 真实使用的 browser、game asset、task/changed-file 和 skill 能力,并以 Main-owned tracker 替代 OpenCode Session diff。 + +### Dependencies + +- `PI-020`。 +- `PI-070`。 + +### Owns + +- `extensions/agent-browser.ts`、`game-assets.ts`、`task-state.ts`、`changed-file.ts`。 +- `electron/coding-projects/skill-registry.ts`。 +- `conversation-change-tracker.ts`。 +- Main browser/attachment integration at the existing seam。 + +### Required work + +1. `agent_browser` extension 直连现有 Main browser service;删除对 MCP 的运行时依赖。 +2. 迁移现有 game asset browse/review 产品语义,不复制 OpenCode plugin API。 +3. `task_state` 使用版本化 typed details;不暴露旧 todos endpoint。 +4. `changed_file` 上报 relative touched paths。 +5. run start 记录 git head/status baseline;settled 后刷新 status/diff,bash 触发 project-level refresh。 +6. 只读 changed path diff;untracked 有界 preview;无 per-file hash/checkpoint。 +7. selected skills registry 与 command catalog 安全投影。 + +### Acceptance + +- browser tool 不启动 `@playwright/mcp`/MCP child,screenshot 使用 attachment ID。 +- game asset/product tool 有 real/fake integration coverage。 +- changes panel 数据不来自 Pi message 文本猜测。 +- write/edit touched path 精准刷新;bash 后能发现未上报改动。 +- untracked preview 有界且无绝对路径。 +- fork UI 不暗示文件回滚。 +- 没有为 diff 增加 hash/checkpoint framework。 + +### Verification + +- Browser/game tool packaged smoke。 +- Git/no-git/dirty/untracked/bash change tracker tests。 +- Skill command and explicit-path tests。 + +## PI-100 — Core `/api/coding` Host API + +### Objective + +用产品域 Host API 连接 project/conversation service、runtime Snapshot/Patch 和 interactions,完全移除 Renderer 对 OpenCode route contract 的依赖。 + +### Dependencies + +- `PI-020`。 +- `PI-060`。 +- `PI-070`。 + +### Owns + +- `electron/api/routes/coding-projects.ts`。 +- `electron/api/routes/coding-conversations.ts`。 +- `/api/coding/events` SSE。 +- interactions route、runtime diagnostics 安全投影。 +- Renderer typed facade 的 Main contract half。 + +### Required work + +1. 实现 Spec 第 12.1 节除 files/skills/changes 外的核心 routes。 +2. prompt/steer/follow-up 返回 HTTP 202 acceptance;不等待 settle。 +3. `clientRequestId` bounded dedupe 与 uncertain request 行为。 +4. SSE envelope、per-Conversation seq/generation、snapshot-first reconnect。 +5. request validation、conversation/project ownership、sanitized errors。 +6. diagnostics 只给 worker state/revision/stage summary,无 secret/absolute session path。 +7. interactions list/respond ID correlation。 + +### Acceptance + +- Renderer-facing core route 中不出现 `/api/opencode` 或 Pi wire field。 +- project/partner/Conversation create route 不启动 runtime。 +- prompt 202 在 provider first token/settled 前返回。 +- seq gap test 只 GET 目标 snapshot;SSE reconnect 不重发 prompt。 +- duplicate accepted `clientRequestId` 返回相同 acceptance;uncertain mutation 不二次发送。 +- raw stderr/provider/extension path/credential 不穿透。 + +### Verification + +- Route contract tests with in-memory and fake Pi runtime。 +- Host API → SSE → snapshot integration。 +- Error redaction and timeout tests。 + +## PI-105 — Files, changes, skills, and command Host API + +### Objective + +提供 vendor-neutral project file、search、changes、skills 和 command API,使 Renderer 不再依赖 OpenCode 的文件/Session diff/todo 路由。 + +### Dependencies + +- `PI-020`。 +- `PI-090`。 + +### Owns + +- `electron/api/routes/coding-files.ts`。 +- file/status/find/content/search facade。 +- Conversation changes、skills、commands route integration。 +- Renderer typed facade 的 file/tool catalog contract half。 + +### Required work + +1. 迁移现有受支持 file status/find/content/search 行为到 product service。 +2. 所有 path 相对 active project,保持现有内容/搜索边界。 +3. expose Conversation changes snapshot,不 expose Pi/session diff。 +4. 合并 Pi extension/skill commands 与 Makelore commands,稳定 DTO。 +5. 删除 todos/revert/unrevert/share 相关 API 外形。 + +### Acceptance + +- Renderer 无绝对项目路径和任意 filesystem proxy 能力。 +- files/search 行为与现有受支持 UX 等价。 +- changes 与 target Conversation/run 关联,数据来自 tracker。 +- skills/commands 只包含显式受管资源。 +- route/type/test 中无旧 todos/share/revert contract。 + +### Verification + +- Focused file/search/change/command route tests。 +- Path boundary and redaction tests。 +- Renderer facade contract tests。 + +## PI-110 — Renderer Snapshot/Patch store + +### Objective + +建立按 Conversation 分片、可 gap recovery、无 vendor import 的 Renderer 状态层和 typed Host facade。 + +### Dependencies + +- `PI-010`。 +- `PI-100`。 + +### Owns + +- `src/stores/coding-conversations.ts`。 +- `src/types/coding-conversation.ts` 或 shared re-export。 +- `src/lib/coding-conversations.ts`/existing Host API facade integration。 +- SSE subscription/reconnect、snapshot loader、draft state。 + +### Required work + +1. 每 Conversation 独立 snapshot/run/error/queue/model/draft/unread/cursor。 +2. Snapshot first load + SSE live,generation/seq/gap reducer。 +3. hidden Conversation 只更新 lightweight summary。 +4. optimistic user node/draft revision/clientRequestId reconciliation。 +5. target-only error/recover;top-level error 只用于真实全局错误。 +6. Selector 粒度避免 token 时全 store rerender。 + +### Acceptance + +- 两 Conversation simultaneous patch 不串 node/run/error/queue/model/draft。 +- seq gap 只刷新目标;旧 generation 丢弃。 +- optimistic accept/reject/uncertain state 可恢复 draft/附件。 +- hidden Conversation streaming 不触发其 Markdown component render。 +- store/facade/types 无 Pi/OpenCode import/name。 +- 旧 `src/stores/opencode.ts` 不再被新 UI 读取;实际删除留 `PI-140`。 + +### Verification + +- Store reducer/selector tests。 +- Two-Conversation SSE integration。 +- Render-count fixture for hidden/current Conversation。 + +## PI-120 — Core Chat and first-Conversation UX + +### Objective + +交付 vendor-neutral Chat core,彻底修复首次 Conversation 输入框被全局 runtime/loading 阻塞,并控制流式渲染成本。 + +### Dependencies + +- `PI-110`。 + +### Owns + +- `CodingChatPanel` 核心组合层。 +- Composer、message/timeline/tool blocks 的 vendor-neutral components。 +- optimistic prompt/recover UI。 +- delta batching/attachment preview/render performance。 + +### Required work + +1. Textarea enable rule严格实现 `UX-010`~`UX-014`。 +2. create/select Conversation 不等待 runtime;prewarm 仅显示局部状态。 +3. first send optimistic node、202 acceptance、preparing/queued/running/error/recover UI。 +4. message/thinking/tool/compaction/boundary 基础渲染。 +5. 16–33 ms Main batching 的 Renderer局部 block 更新;memo/折叠/虚拟化。 +6. attachment ID/preview URL,不持有重复 base64。 +7. 组件拆分,避免把旧 4k 行面板机械改名。 + +### Acceptance + +- Runtime/metadata response 被测试故意 hold 时,selected Agent 的 Textarea 仍可输入。 +- fresh userData 选择首 Conversation → Composer p95 ≤ 500 ms。 +- cold local prepare 超过 10 秒显示 recoverable error,Textarea 仍可编辑。 +- prompt accepted 前后 user node不丢;Provider error 不回滚 user message。 +- 100 KB cumulative tool/长 thinking 下 Main delta → Renderer commit p95 ≤ 50 ms,输出不重复。 +- hidden Conversation 不做全文 Markdown rerender。 + +### Verification + +- Red-capable first-Conversation component test。 +- Core Chat focused tests。 +- Render commit/IPC payload benchmark fixture。 +- First prompt Electron E2E。 + +## PI-130 — Feature-complete Coding UI + +### Objective + +在新协议上补齐需要保留的 Code 产品能力和多会话/subagent 交互,使旧 OpenCode UI 可以完整拆除。 + +### Dependencies + +- `PI-080`。 +- `PI-090`。 +- `PI-105`。 +- `PI-120`。 + +### Owns + +- model/thinking controls。 +- steer/follow-up queue UI。 +- compaction/retry/context UI。 +- interactions、subagent nested graph、changes/files/browser/skills/commands UI。 +- Conversation fork/title/archive/unread/recover/diagnostics UI。 + +### Required work + +1. model/thinking Conversation setting,与伙伴默认分离。 +2. queue 两种 mode、position、等待和 abort。 +3. retry/compaction willRetry/settled 正确生命周期。 +4. select/confirm/input/editor interactions 与 stale/cancelled 状态。 +5. `subagent.v1` nested nodes、parallel/chain error/abort。 +6. Conversation changes/file preview、browser attachment、skills/commands。 +7. “从这里创建新对话分支”替代 revert 文案,不暗示文件回滚。 +8. 删除 share/todo/global runtime controls 的产品入口。 + +### Acceptance + +- 文本、图片、thinking、tools、retry、compaction、queue、interaction、model/thinking、files/changes、browser、skills 全部可在新 UI 使用。 +- 两 Conversation 同时 streaming,切换/隐藏/abort/model 互不污染。 +- subagent single/parallel/chain nested UI 正确;child error/abort 可理解。 +- `agent_end` 不清 busy;`agent_settled` 才释放 queue。 +- tool result 不独立 bubble;compaction summary/raw custom details 不泄露。 +- share/unshare/revert/unrevert/todos/global runtime controls UI 不存在。 + +### Verification + +- Focused UI tests for every retained/removed behavior。 +- Electron E2E:concurrency、model、queue、compaction、interaction、subagent、image、browser/skill、recover。 + +## PI-140 — OpenCode removal and cutover migration + +### Objective + +在新链路达到 feature completeness 后,删除所有生产 OpenCode 源码、依赖、route、资源、脚本和旧 UI,并接通最终一次性迁移/提示。 + +### Dependencies + +- `PI-130`。 + +### Owns + +- 删除 `electron/opencode`、OpenCode route/manager/client/materialization/provider/plugin code。 +- 删除/重命名 `src/*opencode*`、`shared/opencode-*`、旧 tests/E2E。 +- package/lockfile OpenCode 与 MCP dependency removal。 +- `.opencode` packaged resources、bundle/smoke script removal。 +- 迁移 notice、legacy data inert policy、release-facing removed-feature copy。 + +### Required work + +1. 删除 `/api/opencode/*` 与 Renderer 调用。 +2. 删除 `opencode-ai`、`@opencode-ai/plugin`、仅服务 OpenCode 的 `@playwright/mcp`。 +3. 删除 manager/client/project-agent-runtime/provider-config/playwright-mcp/plugin materialization。 +4. 删除旧 store/types/normalizer/panel/runtime controls/diff preview 和浅 Implementation tests。 +5. 删除 share/unshare/revert/unrevert/todos/global runtime controls 的 route/type/test/doc copy。 +6. 移动保留的 product skills/assets 到 vendor-neutral 资源。 +7. 接通 schema migration、一次性 notice、userData inert/no-read。 +8. 保持 app id/protocol/storage identifiers。 + +### Acceptance + +- 生产源码/route/dependency/runtime resource 不包含 OpenCode。 +- `rg -i` 只命中明确允许的历史/迁移文本,并逐项分类记录。 +- package/lockfile 无 OpenCode package/plugin/MCP dependency。 +- 新应用不读取项目 `.opencode` 或 `/opencode`。 +- uncertain/modified `.opencode/agent` 被 backup,未误删。 +- stable Agent IDs/app id/protocol/storage 保持。 +- 全部 Code UI/Host tests 已指向 `/api/coding` 和新 store。 + +### Verification + +- Zero-residue source/package search。 +- Migration E2E from schema v1 fixture。 +- Full focused Code tests + typecheck/build before `PI-150`。 + +## PI-150 — Packaging, E2E, and performance release proof + +### Objective + +把硬切换打成可发布 artifact,运行完整自动化、真实 Pi、三平台和性能验收,产出可审阅证据。 + +### Dependencies + +- `PI-140`。 + +### Owns + +- 最终 `bundle-pi-runtime`、electron-builder staging、artifact verifier。 +- `smoke:pi:real`。 +- rewritten/new Electron E2E suite。 +- performance harness/report、release note/runbook draft、README current product update。 +- feature task verification record与 promotion candidates;canonical docs 留 `PI-160` Integration Gate。 + +### Required work + +1. stage exact Pi production closure、extensions、skills、manifest。 +2. artifact verifier 检查 version/engine/resources/resolve/get_state/no-OpenCode/no-dev-path。 +3. actual packaged Pi smoke:通过受控 loopback/provider-shaped endpoint 覆盖 session、prompt、tool、abort、settle、reopen、two-worker overlap/isolation、subagent、shutdown;不要求真实外部 Provider Account,报告保持 `realTurnVerified=false`。 +4. 完整 typecheck/lint/unit/build/E2E。 +5. Windows x64、Linux x64 最终产品 artifact 的 packaged verifier/smoke。 +6. 对 macOS x64 与 arm64 分别使用独立 checkout/frozen install,运行 workspace 5+5、staged 5+5、controlled packaged 5+5、packaged loopback、closure/native/resource 检查和 metrics,再验证各自最终目标 artifact;不得用静态 path test、其他平台结果或 `PI-000` waiver 替代。 +7. Spec 第 17.3 节全部场景,报告 p50/p95/max/samples/RSS/IPC/commit。 +8. release note:旧 Conversation、删除能力、完整版本回退、Pi 新 Conversation 不兼容旧版。 +9. README 同步当前 runtime/功能,不写历史迁移日记。 + +### Acceptance + +- `pnpm run typecheck`、`pnpm run lint:check`、`pnpm test`、`pnpm run build:vite`、`pnpm run test:e2e` 全通过。 +- artifact verifier 与 real Pi smoke 全通过。 +- 最终 packaged 四协议 loopback、provider-shaped two-worker overlap、单侧 abort 与另一侧 settled 全通过;真实外部 Provider waiver 不是 Pass,也不是 release blocker。 +- Windows x64、Linux x64、macOS x64、macOS arm64 目标 artifact 与对应验证全部通过;macOS 任一架构缺失即不允许宣称 cross-platform release-ready。 +- `PI-000` 的 macOS Explicitly Waived / Deferred 到本票据即失效;延期造成 release-stage packaging/native/resource/performance 返工的风险已经接受,但任何实际失败仍必须阻断 release。 +- Composer、worker、acceptance、render p95 全达 Spec 预算。 +- 4 parent + 4 child 压力下 UI 可交互,无 orphan process/permit/lease。 +- 最终 artifact 无 OpenCode package/binary/plugin/resource、无开发机绝对路径。 +- 性能报告分离本地 overhead 与受控 provider-shaped first-event,不使用一次体验替代统计;可选真实外部 Provider 样本只能作为非阻断诊断。 + +### Verification + +- 保存命令、版本、artifact、平台、样本数和结果摘要到 task evidence。 +- macOS x64/arm64 分架构保存 workspace/staged/controlled packaged/loopback/metrics 与最终 artifact 实跑证据;不得只记录 waiver 或静态测试。 +- 保存最终 packaged loopback/provider-shaped concurrency/abort 证据并明确 `realTurnVerified=false`;不得因缺少真实外部 Account/credential 把 release 标为 Blocked,也不得把 waiver 写成 Pass。 +- 对允许的 `opencode` 搜索命中逐项解释。 +- 更新 task record 的验证和未完成外部环境项;任何 release 阻断不得藏在 follow-up。 + +## PI-160 — Integration and release gate + +### Objective + +以 Integration Task 所有权完成最终 Spec/Standards 审阅、合并验证、canonical memory 提升和 release go/no-go;不让 feature branch 的自证替代集成事实。 + +### Dependencies + +- `PI-150`。 + +### Owns + +- Integration Task record、merge/rebase conflict handling。 +- merge tree 的最终验证与独立 Spec/Standards review。 +- accepted ADR、canonical current state/architecture/data flow/domain/success/evidence/commitments 清理。 +- release decision、tag/runbook handoff;不自动 push/release,除非用户另行授权。 + +### Required work + +1. 重新运行 Concurrent Task Gate,确认 main/worktree ownership 和最新 base。 +2. 集成完整 feature tip,不选择性合并会留下双 runtime 的部分提交。 +3. 在 merge tree 重跑风险相称的 focused/full/build/E2E/artifact/real smoke/perf checks。 +4. 对照 `ML-CODE-PI-001` 逐条审阅,不以 ticket 自报 Done 代替证据。 +5. 新增 accepted ADR:Pi 唯一 runtime、无 OpenCode fallback。 +6. 更新 system overview/module map/data flow/business rules/success criteria/current state/README/evidence。 +7. 关闭或改写仅对 OpenCode 有意义的 model-switch、dual-session、compaction、saturation commitments。 +8. 执行零残留和 release note/rollback runbook 审阅。 + +### Acceptance + +- 全部 Spec requirements 和 DoD 有可追踪证据,无未解释偏离。 +- merge tree 与 feature tree 验证一致,无 main 回归。 +- accepted ADR 和 canonical docs 描述实际 Pi 架构,不提前声称未跑的 macOS 或已豁免的真实外部 Provider 证据通过。 +- accepted ADR、release note 与风险记录明确真实外部 Provider compatibility/concurrency/isolation 是用户接受的未验证风险,不声称 Pass;macOS x64/arm64 仍须有实际发布证据。 +- OpenCode commitments 不再伪装为当前待办;需要保留的历史证据仍可追踪。 +- 无 runtime fallback/compat layer/old route/dependency/artifact。 +- release go/no-go 明确;若任一硬门不通过,状态保持 Blocked,不发布。 + +### Verification + +- Integration Gate scripts、documentation drift gate。 +- Final full command suite + artifact/real/runtime/performance evidence review。 +- Independent human/reviewer Spec and Standards review;不要求特定智能体机制。 + +## 6. 票据状态模板 + +后续若同步为 task/issue,每张票据至少维护: + +```text +Status: Pending | Ready | In progress | Blocked | Done +Depends on: +Owner/worktree: +Owned files/responsibility: +Acceptance evidence: +Contract changes: +Blockers: +``` + +禁止使用“代码基本完成”“看起来正常”“后续再测”作为 Done 证据。阻断项如果属于 Spec 硬门,必须留在当前 ticket,不得降级为 release 后 follow-up。 diff --git a/.project-docs/30-worklog/tasks/20260822-pi-runtime-spec-b6e2c9a4.md b/.project-docs/30-worklog/tasks/20260822-pi-runtime-spec-b6e2c9a4.md new file mode 100644 index 0000000..a7d2360 --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260822-pi-runtime-spec-b6e2c9a4.md @@ -0,0 +1,357 @@ +# Task: Write Pi runtime hard-cutover implementation spec + +## Identity + +- Task ID: 20260822-pi-runtime-spec-b6e2c9a4 +- Mode: Feature +- Branch: codex/20260822-pi-runtime-spec-b6e2c9a4-pi-runtime-spec +- Worktree: D:\Datas\OthersProjects\makelore-pi-runtime-spec-b6e2c9a4 +- Base commit: fba68e86d93c66d2c50f53d215de76a7c69c97a7 +- Owner: codex +- Status: Ready for integration + +## Scope + +- Convert the approved OpenCode-to-Pi hard-cutover design into a normative, + implementation-ready specification. +- Define product behavior, architecture and ownership boundaries, runtime and + process contracts, data schemas, Host API and Renderer protocols, migration, + packaging, performance, observability, verification, and Definition of Done. +- Produce an associated dependency ticket graph with explicit ready frontiers, + ownership areas, outputs, and acceptance criteria suitable for a later + implementation run. +- Track task-scoped qualification results against that graph without promoting + partial platform evidence into a `PI-000` Go decision. +- Apply the user's 2026-08-22 scope amendment: Phase-0 platform qualification is + Windows x64 + Linux x64; macOS x64/arm64 is Explicitly Waived / Deferred for + `PI-000` only and moves to mandatory `PI-150` release validation. +- Apply the user's later 2026-08-22 Provider-risk decision: real external + Provider Account/protocol and real-provider two-worker qualification are + Explicitly Waived / Accepted Risk, never Pass; retain packaged loopback/ + provider-shaped evidence with `realTurnVerified=false`. +- Documentation only: do not change product code, dependencies, canonical + memory, runtime behavior, or external issue/PR state. + +## Intent And Constraints + +- Pi is the sole target coding runtime. Do not retain an OpenCode fallback, + feature flag, compatibility Adapter, old Host API facade, or dual runtime. +- Preserve `.niancode/project.json`, stable Agent IDs, existing app/protocol/ + storage compatibility identifiers, and Main ownership of runtime, provider, + secret, file, and system access. +- Keep Pi wire types and session paths out of Renderer; specify a Makelore-owned + Snapshot/Patch contract and deep Main-owned runtime Module. +- Treat packaged runtime qualification, packaged provider-shaped compatibility/ + concurrency/abort, and resource budgets as a blocking Phase-0 gate rather + than assumed facts. +- Treat `QG-004`/`QG-005` as explicit human waivers, not Pass: the user accepts + the unverified real Provider Account protocol compatibility and real + two-worker provider overlap/isolation risks, so external credentials are no + longer a `PI-000`, `PI-150`, or release blocker. +- Make first-Conversation Composer readiness independent of runtime and metadata + readiness; distinguish local runtime overhead from Provider latency. +- Specify old OpenCode Session handling as backup plus non-continuation, not as + compatibility behavior. +- The named `implement-spec` skill assumes an existing spec and tickets. For + this request, use its execution-graph discipline to create the missing spec + and tickets, but do not start implementation, create a PR, or create + subagents. The user explicitly prohibited subagents. + +## Project Context Loaded + +- The `maintain-project-docs` Concurrent Task Gate and Planning Gate passed in + the isolated worktree above at base `fba68e8`. +- The main worktree remains owned by a blocked OpenCode model-switch integration + task and contains a separately owned untracked task record; neither is + modified here. +- Required canonical positioning/current-state/decision/architecture/domain/ + evidence/reflection/commitment records and the relevant Pi/OpenCode peer task + records were read before writing. +- No accepted ADR conflicts with the hard-cutover proposal. Current OpenCode + behavior is integrated state that a future Pi implementation will supersede. +- The canonical project-positioning document remains a placeholder, so product + scope is taken only from repository guidance and confirmed current state. +- Gate result: Passed. +- Human scope decision: on 2026-08-22 the user explicitly accepted deferring + macOS x64/arm64 qualification from `PI-000` to `PI-150`. This does not create + macOS Pass evidence and does not waive the final cross-platform release gate. +- Human Provider-risk decision: on 2026-08-22 the user explicitly accepted the + missing real external Provider compatibility, concurrency and isolation + evidence. `QG-004`/`QG-005` are Explicitly Waived / Accepted Risk, not Pass; + `QG-007` remains Pending / not triggered because a waiver is not a failure. + +## Plan + +1. Translate the detailed design into normative requirements and stable product + contracts without copying vendor wire types into public interfaces. +2. Define schemas, lifecycle/state rules, event mapping, migration, packaging, + performance budgets, failure behavior, and traceable acceptance criteria. +3. Build a dependency ticket graph whose initial ready frontier begins with the + packaged Pi qualification gate and whose terminal ticket proves zero + OpenCode runtime residue. +4. Validate cross-references, dependency closure, Markdown structure, task + ownership, and project-documentation gates. + +## Outcome + +- Added the normative implementation specification + `.project-docs/10-decisions/proposals/20260822-pi-runtime-spec-b6e2c9a4__pi-runtime-cutover-spec.md`. +- Added the associated execution graph + `.project-docs/10-decisions/proposals/20260822-pi-runtime-spec-b6e2c9a4__pi-runtime-cutover-tickets.md`. +- The Spec defines 134 unique normative requirements across 19 groups for the + hard cutover, product behavior, Main/Renderer boundary, schema v2, Provider + and managed resources, Pi process/RPC/worker pool, extensions, Snapshot/Patch + event projection, Host API, changes/browser, migration, packaging, + observability, performance, recovery, verification, and Definition of Done. +- The ticket document defines 18 bounded tickets and 31 dependency edges. The + graph is acyclic, its initial ready frontier is only `PI-000`, and every + ticket includes dependencies, ownership, required work, acceptance, and + verification. +- Packaged Pi qualification is a blocking gate. If RPC, packaged provider-shaped + contract/concurrency/abort, resource use, or cold/warm budgets fail, the + implementation must stop and amend the architecture before product migration; + it cannot add an OpenCode fallback or dual RPC/SDK production path. Missing + explicitly waived real external Provider evidence is not a confirmed failure. +- The first-Conversation correction is a hard UX requirement: project/partner/ + Conversation creation is local-only, Composer editability is independent of + runtime/metadata readiness, and local preparation has bounded, observable + failure instead of minute-long disabled state. +- No product code, dependencies, external tickets, PR, canonical project memory, + or runtime behavior changed. No subagents were created. +- Synchronized the first `PI-000` implementation result from task + `20260822-pi-runtime-qualification-c52e8a14` into the ticket graph. The + final planning state is Done with explicit waivers: the exact Pi 0.84.2 identity, + Windows x64 and WSL2 Linux x64 workspace/staged/controlled packaged RPC/ + session/process/closure behavior, four-protocol packaged loopback, + provider-shaped overlap/abort and local budgets have evidence. Real external + turns for all four protocols remain missing and are Accepted Risk, not Pass. +- Synchronized the controlled Electron Builder `app.asar` and four-protocol + loopback results. The packaged seam resolves all 130 Windows-applicable + production packages and six runtime assets, passes cold/warm budgets and + leaves no process residue. The loopback matrix proves request/SSE/image/ + header/model/environment-credential plumbing plus provider-shaped overlap + and abort isolation, but is explicitly not real Provider Account evidence. +- Synchronized the WSL2 Ubuntu 24.04 Linux x64 evidence. Workspace, staged + closure, and controlled Electron Builder `dir/app.asar` all meet the local + cold/warm budgets; the controlled artifact resolves 131 applicable packages, + six runtime assets and unpacked native assets, and leaves no Pi process + residue. Linux packaged loopback overlap was 292/290/277/288 ms for the four + protocols, with exactly four POSTs per protocol and abort isolation passing. +- Interpreted the accepted `QG-002` wording narrowly and explicitly: it requires + a Linux packaged app seam but does not state a non-WSL kernel, compositor, + AppImage, or deb requirement. The WSL2 controlled artifact therefore counts + as PI-000 Linux seam evidence with a recorded limitation; final Linux release + formats remain `PI-150` evidence and are not claimed here. +- Synchronized the macOS packaged-resource preflight defect and fix. The old + artifact-side derivation would resolve + `/Contents/MacOS/resources/app.asar`; the actual location is + `/Contents/Resources/app.asar`. The outer platform-aware + `packagedResourcesDirectory()` now computes the directory once and passes it + through `PI_PROBE_RESOURCES_DIRECTORY`, so the closure inspector no longer + derives it independently. +- Kept macOS explicitly unqualified. A darwin path test and a Windows actual + controlled-packaged regression establish the fix and absence of a Windows + regression, but neither is a macOS x64/arm64 execution sample. +- Applied the user's 2026-08-22 amendment: macOS x64/arm64 is now Explicitly + Waived / Deferred for `PI-000`, not Pass. Workspace/staged/controlled + packaged/loopback/metrics move to `PI-150`, where both architectures remain + mandatory. The user accepted the risk of later packaging/native/resource/ + performance defects and release-stage rework; the risk was not eliminated. +- Clarified the pre-existing artifact wording: `PI-000` proves the controlled + packaged qualification artifact on Windows/Linux, while `PI-150` owns final + product artifacts. The controlled probe is not the final bundler. +- After the later Provider waiver and a full PI-000/DAG audit, marked `PI-000` + Done with explicit waivers and advanced the exact Ready Frontier to `PI-010`, + `PI-020`, and `PI-030`. The published shrinkwrap/devDependencies mismatch + remains an explicit `PI-030` permanent-staging constraint rather than final + packaging proof; it is work owned by the now-ready ticket, not an unresolved + PI-000 gate. +- Bound the final planning documents to the verified PI-000 implementation + baseline `2bc423ebc58589307442ebdaf1c81d83ec9dc2d5` (`feat: qualify Pi + runtime cutover foundation`). The implementation task is + `ready_for_integration`, its worktree is clean, and the PI-010 task + `20260822-pi-conversation-contracts-a61d9c42` has been created from that exact + commit with status `planning`. +- Corrected two stale planning statements before final commit: the Spec now + records the user's implementation authorization instead of saying it awaits + acceptance, and PI-040 now verifies Provider catalog behavior with packaged + provider-shaped fixtures rather than making waived real-account prompts a + hard acceptance requirement. + +## Verification + +- Read the complete source design and the relevant Pi evaluation, concurrency, + message-mapping, first-session diagnosis, and current OpenCode integration + task records after passing the planning gate. +- Requirement audit: 134 definitions, 134 unique IDs, no duplicate definitions; + all 19 requirement groups appear in the ticket traceability section. +- Ticket graph audit: 18 declared nodes, 31 edges, all graph nodes have matching + detail sections, no missing dependencies, and a complete acyclic topological + order from `PI-000` through `PI-160`. +- Ticket shape audit: every ticket contains Objective, Dependencies, Owns, + Required work, Acceptance, and Verification. +- Markdown code fences are balanced: 34 in the Spec and 4 in the ticket graph. +- Placeholder scan found no unfinished TODO/TBD/template markers; the literal + `todo/todos` occurrences only describe intentional removal of the old API. +- Trailing-whitespace/final-newline check passed for all three owned files. +- Task-scoped `git diff --no-index --check` produced only expected LF-to-CRLF + notices and no whitespace errors. +- `check_project_docs.py`: passed. +- `check_doc_drift.py --task-id 20260822-pi-runtime-spec-b6e2c9a4`: passed and + reported only the two task-scoped proposal files and this task record. +- Product tests were intentionally not run because this task changes only + task-scoped documentation and no product behavior. +- Read the complete source task record and task-scoped evidence for + `20260822-pi-runtime-qualification-c52e8a14`. Its Markdown status is + `Completed — Done with explicit waivers`, the ownership registry reports + `ready_for_integration`, and its clean worktree is at verified commit + `2bc423ebc58589307442ebdaf1c81d83ec9dc2d5`. +- Rechecked the qualification result against `PI-000` Acceptance and Stop + condition: every non-waived requirement is satisfied by Windows/WSL2-Linux + controlled packaged evidence. Real Provider Accounts/turn concurrency remain + unverified Accepted Risk; macOS remains unqualified and mandatory at + `PI-150`. +- Reviewed the controlled packaged evidence: 5 cold samples reported + p50/p95/max 668/682/682 ms, 5 warm samples 660/663/663 ms, 10 RSS samples + 138404/141320/141320 KiB, and exit 16/18/18 ms. Session/reopen/failure/local + overlap/abort/stdin-close passed with no controlled artifact or Pi RPC + process residue. +- Reviewed all four packaged loopback protocol contracts: each made exactly + four POSTs + and passed base URL/path, protocol credential header, custom header, model, + PNG image, two-worker settled overlap, one-worker abort isolation, and clean + stdin close. The final tightened rerun reported overlap of + 304/309/290/297 ms. No external Provider request or cost occurred, so the + normative `QG-004` and `QG-005` are Explicitly Waived / Accepted Risk, not + Pass. Under the user-amended Phase-0 platform scope, `QG-002`, `QG-003`, and + `QG-006` are Pass for Windows x64 + Linux x64; macOS remains Explicitly + Waived / Deferred, not Pass. +- Verified that the implementation report fields now match this amended + planning contract. `buildMissingEvidence()` reports Phase-0 aggregation as + Windows x64 + Linux x64, labels macOS x64/arm64 as user-deferred to `PI-150`, + and retains the four missing real-provider protocol samples. The focused test + asserts that exact wording; the final implementation task records 9 focused tests, + scoped ESLint, typecheck, and a 1-sample actual probe as passing. The retained + missing samples and `realTurnVerified=false` now document Accepted Risk rather + than a blocker. The 18-node/31-edge DAG is unchanged; `PI-000` Done unlocks + `PI-010`, `PI-020`, and `PI-030`. +- Recorded the Provider URL rule discovered by the fixture: Anthropic receives + the Provider root and appends `/v1/messages`; OpenAI-style protocols receive + a `/v1` base and append `chat/completions` or `responses`. +- Reviewed the Linux 5+5 samples: workspace cold/warm p95 853/437 ms, staged + 428/440 ms, controlled packaged 545/569 ms; respective RSS p95 values were + 170388/175956/181928 KiB and exit p95 values 8/8/7 ms. All 131 applicable + Linux packages and six runtime assets resolved, including unpacked native + assets, with no residual artifact/Pi RPC process. +- Reviewed the Linux packaged loopback matrix: exactly four POSTs per protocol, + overlap 292/290/277/288 ms, correct path/auth/custom-header/model/image, abort + isolation and clean exit. The accompanying 7 tests, scoped ESLint, typecheck, + and 1-sample integration probe passed in the implementation task. +- Corrected the planning QG status table to use the normative Spec numbering: + `QG-003` is dependency closure, `QG-004` Provider Accounts, `QG-005` real + provider concurrency, `QG-006` metrics, and `QG-007` the SDK-switch stop + condition. The implementation evidence's task-local labels are retained only + as source context and do not redefine the Spec. +- Reviewed the portability-fix verification: 8 focused tests passed, including + darwin executable → `Contents/Resources`; scoped ESLint and typecheck passed. + The post-fix Windows 1-sample controlled packaged regression reported cold/ + warm ready 650/641 ms, with all 130 applicable packages, six runtime assets, + and unpacked native assets present. +- Reviewed the execution-environment inventory: there is no repository CI + definition, macOS runner/SSH host, GitLab runner CLI, or Provider credential + environment variable. The missing Provider environment is now Accepted Risk + and non-blocking; the missing macOS environment remains a deferred `PI-150` + release gap. +- Post-sync graph audit still reports 18 nodes, 31 edges, only `PI-000` at + indegree zero, and a complete acyclic topology; no dependency edge changed. +- Scope-amendment audit preserved all 18 nodes and 31 edges. The Ready Frontier + after `PI-000` Done is exactly `PI-010`, `PI-020`, `PI-030`; all other nodes + retain at least one unfinished dependency. +- Post-amendment requirement audit still reports 134 definitions, 134 unique + IDs, and no duplicates. The ticket audit reports 18 Mermaid nodes, 18 detail + sections, 31 edges, only `PI-000` at indegree zero, and all 18 nodes visited + by the acyclic topology check. +- Post-amendment contradiction scan found no remaining statement that treats + missing macOS evidence as a `PI-000` blocker or Pass. The Spec and tickets + consistently make the waiver Phase-0-only and `PI-150` release-blocking. +- Provider-waiver audit found no remaining proposal statement requiring a real + external Provider Account/credential to close `PI-000` or release. The exact + accepted risks are auth/endpoint/proxy/rate-limit/response variance; real + base URL/header/environment credential/model/image compatibility; real + provider concurrency/serialization/limiting; and cross-worker abort/event/ + session/model/credential isolation. `QG-004`/`QG-005` are never labeled Pass, + and `QG-007` remains Pending / not triggered. +- PI-000 closure audit covered all eight Required work items plus Acceptance and + Stop condition. Items 1/2/3/4/7/8 pass on the revised Windows/Linux scope; + items 5/6 retain passing packaged loopback/provider-shaped substitute smoke + and explicit real-Provider Accepted Risk. No third incomplete item or + confirmed failure remains, so Done with explicit waivers is consistent with + the ticket contract. +- Post-sync `check_project_docs.py` and + `check_doc_drift.py --task-id 20260822-pi-runtime-spec-b6e2c9a4` passed; + trailing-whitespace inspection reported zero findings. +- Re-ran both project-documentation gates after the user-authorized scope + amendment; both passed against only the two task-scoped proposal files and + this task record. Markdown fences remained balanced and trailing-whitespace + inspection reported zero findings. +- Final Provider-waiver audit preserved 134 unique requirements across all 19 + traced groups and the unchanged 18-node/31-edge acyclic DAG. Recomputing + readiness with only `PI-000` Done yields exactly `PI-010`, `PI-020`, + `PI-030`; every other ticket retains an unfinished predecessor. Ticket-shape, + QG-state, explicit-risk, macOS-release-gate, hard-cutover, cross-reference, + stale-blocker and formatting assertions all passed. +- Verified the PI-000 implementation commit metadata, parent and changed-file + set. Commit `2bc423e` is based on the planning baseline `fba68e8` and contains + only the PI-000 dependency/lockfile, probe/test, task-record and evidence + changes expected from the qualification ticket. +- Verified the PI-010 ownership handoff through the task registry and worktree: + task `20260822-pi-conversation-contracts-a61d9c42` is based exactly on + `2bc423e`, remains in `planning`, and its only untracked file is its own task + record. The planner's three-document write set does not overlap PI-010's + product-code ownership. + +## Follow-ups + +- Continue `PI-010`, which has entered planning from the verified PI-000 + baseline; `PI-020` and `PI-030` remain the other eligible Ready Frontier + tickets. The dependency-qualified frontier remains exactly `PI-010`, + `PI-020`, `PI-030` until a later status update recomputes it. + Real external Provider fixtures for `openai-completions`, `openai-responses`, + `anthropic-messages`, and `openrouter` are optional non-blocking risk-reduction + work; if collected, retain `realTurnVerified=false` until actual evidence + exists and never retroactively relabel the waiver as Pass. +- At `PI-150`, obtain the deferred macOS x64 and macOS arm64 evidence. For each + architecture, use an independent checkout and frozen install; run workspace + 5+5, staged 5+5, controlled packaged 5+5, packaged loopback, closure/native/ + resource checks, metrics, and final target artifact verifier/smoke. Do not + reuse x64/arm64 `node_modules`, Electron downloads, or artifacts. Missing + either architecture blocks cross-platform release-ready status. +- Treat non-WSL Linux, compositor, AppImage/deb, and the final product packaging + wiring as `PI-150` release evidence unless the accepted PI-000 Spec is amended + to require them earlier; do not imply the controlled WSL2 artifact proves + those release boundaries. +- If a non-waived packaging, packaged provider-shaped contract/concurrency/ + abort-isolation, image-serialization, or budget gate fails, return to the + planner for the specified SDK-in-Electron-utility-process amendment; do not + add an OpenCode fallback or RPC/SDK dual production path. If deferred macOS + validation fails at `PI-150`, + block release and perform the required packaging/runtime/architecture rework; + the Phase-0 waiver cannot be reused as a release waiver. +- If old OpenCode chat history must remain readable, create a separate exporter + in the last OpenCode-containing release; do not add an OpenCode reader/runtime + to the Pi release. + +## Promotion Candidates + +- Accepted ADR: Pi is Makelore Code's sole runtime and no OpenCode fallback is + shipped. +- Canonical architecture: one Main-owned worker per active/warm Conversation, + a product-only `CodingConversationRuntime` Interface, and Renderer-only + Snapshot/Patch state. +- Domain rules: product Conversation IDs, structured model selections, explicit + unresolved migration state, same-project mutation lease, and old OpenCode + Session backup/non-continuation policy. +- Success criteria: first-Conversation Composer readiness, cold/warm runtime and + prompt acceptance budgets, independent two-Conversation workers with packaged + provider-shaped overlap/abort, explicit real-Provider Accepted Risk, + subagent caps, and zero residual OpenCode runtime in source/package/artifact.