59 KiB
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 配置格式。
系统必须同时达成以下结果:
- 删除 OpenCode runtime、依赖、插件、Host API、Renderer store、运行资源与打包产物。
- 项目、伙伴和空 Conversation 的创建仅写本地产品元数据,不启动 Pi,也不阻塞输入框。
- 每个运行中或温热 Conversation 使用一个独立 Pi worker;多个 Conversation 可以真正并行。
- Renderer 只消费 Makelore-owned Snapshot/Patch 协议;Pi wire 只存在于 Electron Main 的 Adapter 内。
- Pi subagent 由 Makelore 自有 extension 编排,具有全局并发上限、父子 abort 和同项目写租约。
- 保留有产品价值的文本、图片、thinking、tool、retry、compaction、queue、interaction、模型、thinking level、文件、changes、browser 和 skill 能力。
- 旧 OpenCode Conversation 不在 Pi 中继续或展示;迁移前备份,发布说明明确告知。
- 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.jsonMUST 继续是项目、伙伴、默认模型和 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-ownedPiConversationRuntimeModule 后;公共CodingConversationRuntimeInterface 只表达 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 的 Piset_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: subagentchat 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:conversationIdMUST 是 Makelore 生成的稳定 UUID;Renderer、URL、archive、unread、selection、Zustand key 只使用该 ID。DAT-002:PisessionId、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;不得用 Nodereadline,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/compacttimeout 后禁止自动重发。
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:
- 读取
entries与当前leafId。 - 建立
id → entrymap。 - 从
leafId沿parentId回溯到 root。 - 反转为 active path。
- 应用 compaction/retained-tail 规则。
- 通过统一 reducer 生成 Snapshot。
- 在
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_statsreconcile。- 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:接受时返回 HTTP202;产品校验未通过时返回 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 按顺序执行:
- 在
.niancode/migration-backups/opencode-cutover-<timestamp>/复制原project.json与conversations.json。 - 通过 Provider Service 把旧 Agent model string 唯一映射为结构化 model ref。
- 不能唯一映射的 Agent 标记 model required,保留其他字段。
- 原子写 project schema v2,保持 Agent identity、prompt、skills、archive。
- 写空的 Conversation schema v2;旧 OpenCode Session 不进入新 sidebar。
- 显示一次性说明:“旧版 OpenCode 对话不能在 Pi 中继续,原数据已保留用于回退或独立导出。”
- 成功后写明确 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 版本回退
- 发布 Pi 版本前保留上一版安装包和 release tag。
- schema migration backup 是回退旧 metadata 的唯一输入。
- 阻断问题发生时安装上一版,并按 runbook 恢复 backup。
- Pi 新建 Conversation 不能在 OpenCode 版本继续,release note 必须明确。
- 普通项目文件不自动回滚;用户自行使用 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 必须证明:
- OpenCode package/binary/plugin 不存在。
- Pi manifest、package.json 与 lockfile 版本一致。
- CLI/RPC entry 可由 Electron Node 加载。
- production closure 全部 resolve。
- Node engine 相容。
- extension/skills paths 存在。
- 临时空项目
get_state成功。 - 无 credential protocol smoke 可启动/退出;四协议受控 loopback/provider-shaped endpoint 使用环境凭据引用完成 prompt、settled、双 worker overlap 与 abort isolation。真实外部 Provider Account 不属于 release hard gate,
realTurnVerified保持false。 - 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 必测场景
- 全新 userData:创建项目、首伙伴、首 Conversation,不发送,测 Composer。
- 首次 prompt:拆分 spawn/RPC/session/resources/accepted/provider/first commit。
- 重启同一 Conversation,测 warm restore。
- 两个不同项目 Conversation 同时 prompt,通过最终 packaged provider-shaped loopback 验证窗口重叠、事件路由和单侧 abort 隔离;不要求真实外部 Provider turn。
- 同项目两个 Conversation 同时只读 tool,验证并行。
- 同项目两个 mutation tool,验证 lease 串行和可取消。
- parent 并行四个只读 subagent,同时另一个 Conversation 运行,验证全局 cap。
- 100 KB cumulative tool output、长 thinking、混合 blocks,测 patch/commit/bytes。
- 大图片输入,确认 SSE/Renderer 不重复搬运 base64。
- 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
至少覆盖:
- 首项目/伙伴/Conversation Composer readiness;
- 首 prompt cold path;
- 两个 Conversation 同时 streaming;
- 模型切换只影响一个 Conversation;
- steer/follow-up queue;
- compaction retry + settled;
- interaction select/confirm/input;
- parallel subagent nested UI;
- worker crash/recover;
- image attachment;
- packaged skill/browser tool;
- 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、文件所有权和每张票据验收条件见关联票据文档。