Files
makelore/.project-docs/10-decisions/proposals/20260822-pi-runtime-spec-b6e2c9a4__pi-runtime-cutover-spec.md
T

59 KiB
Raw Blame History

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 镜像。
  • 自动删除 <userData>/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. 目标架构与模块责任

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 提供等价于下列产品能力的最小接口:

interface CodingConversationRuntime {
  prepare(input: PrepareConversationInput): Promise<ConversationRuntimeState>;
  getSnapshot(conversationId: string): Promise<ConversationSnapshot>;
  prompt(input: PromptConversationInput): Promise<PromptAcceptance>;
  steer(input: QueueMessageInput): Promise<QueueAcceptance>;
  followUp(input: QueueMessageInput): Promise<QueueAcceptance>;
  abort(conversationId: string): Promise<void>;
  setModel(input: SetConversationModelInput): Promise<ConversationModelState>;
  setThinking(input: SetThinkingLevelInput): Promise<ConversationModelState>;
  compact(conversationId: string): Promise<void>;
  fork(input: ForkConversationInput): Promise<ForkResult>;
  recover(conversationId: string): Promise<ConversationRuntimeState>;
  dispose(conversationId: string): Promise<void>;
  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 建议目录

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 迁移为:

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

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 目录

<userData>/coding-runtime/pi/
  config/
  sessions/<project-id>/<opaque-session-key>.jsonl
  prompts/<project-id>/<agent-id>.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 生成等价于下列描述:

interface PiProviderDescriptor {
  accountId: string;
  runtimeProviderId: string;
  api:
    | "openai-completions"
    | "openai-responses"
    | "anthropic-messages"
    | "google-generative-ai";
  baseUrl?: string;
  headers: Record<string, string>;
  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 生命周期

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

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:

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

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

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

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

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

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-<timestamp>/ 复制原 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

<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:

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 文案:

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、文件所有权和每张票据验收条件见关联票据文档。