pi-multi-viewers 0.10.2 → 0.10.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -69,7 +69,7 @@ tests/ 测试(unittest discover tests)
69
69
 
70
70
  | 档 | 唤醒命令 | 用途 |
71
71
  |---|---|---|
72
- | **mc-tools**(默认) | 四个 `--no-*` + `-e <MC 的 subagent-entry.js>` + `-e builtin:mcp`(pi **内置** MCP,零第三方依赖) | 给 agents **按需检索**(`ctx_search` 查项目历史、web_search 等查外部)——协议模板里带一句「需要项目历史时用 ctx_search」的条件指引(仅在工具真到位时出现)。**降级/严格/生效值语义见 docs/design.md 决策 20 的语义清单**(唯一权威段) |
72
+ | **mc-tools**(默认) | 四个 `--no-*` + `-e builtin:mcp` + `-e builtin:codemode` + `-e <MC 的 subagent-entry.js>`(两份内置常量 + 一份可失败入口,零第三方依赖) | 给 agents **按需检索**(`ctx_search` 查项目历史、web_search 等查外部)——协议模板里带一句「需要项目历史时用 ctx_search」的条件指引(仅在工具真到位时出现)。**降级/严格/生效值语义见 docs/design.md 决策 20 的语义清单**(唯一权威段) |
73
73
  | **none** | 四个 `--no-*` | 零扩展、**零依赖**(无 MC 的机器/CI 用这档) |
74
74
  | **all** | 不加任何 `--no-*`(pi 默认发现)| A/B 实验与显式 opt-in |
75
75
 
package/docs/design.md CHANGED
@@ -37,6 +37,10 @@ compaction 的 `firstKeptEntryId` 起 + 其后的条目"——窗口内含 compa
37
37
  时,锚点之前的条目(含我们的 preface)会被静默丢弃。因此 budget 模式
38
38
  **移除窗口内全部 compaction 并桥接 parentId**(不变量 I4);compaction
39
39
  模式的锚点由构造保证在产物内;full 模式是忠实拷贝,可见性同源会话。
40
+ **同理**(2026-10-03 修):剔除旧会话的 `thinking_level_change` 也必须
41
+ **桥接其子条目的 parentId**——pi 的上下文构建从 leaf 沿 parentId 上溯、
42
+ 遇缺失父节点**静默停止**,只过滤不修链 ⇒ 断点之前的全部条目对模型不可见
43
+ (实测可达 65/13454 条)。**删除类操作必配桥接**是本项目的一条硬纪律。
40
44
 
41
45
  无 compaction 的源(如引导 session):`compaction` 全量兜底(标记 `full`),
42
46
  `budget` 仍跑折叠与统计(干净源下几乎无操作)。
@@ -510,7 +514,7 @@ commit 是溯源记录、本节是长期引用点——不并存两份权威值
510
514
 
511
515
  | 档 | 唤醒命令 | 语义 |
512
516
  |---|---|---|
513
- | `mc-tools`(默认) | 四个 `--no-*` + `-e <MC subagent-entry.js>` + `-e builtin:mcp`(MC 入口缺 → 可见降级:少它那份 `-e`、生效=none;内置 MCP 恒在)
517
+ | `mc-tools`(默认) | 四个 `--no-*` + `-e builtin:mcp` + `-e builtin:codemode` + `-e <MC subagent-entry.js>`(MC 入口缺 → 可见降级:少它那份 `-e`、生效=none;**两份内置常量恒在**)
514
518
  | `none` | 四个 `--no-*` | 零扩展:最快、**零依赖** |
515
519
  | `all` | 不加任何 `--no-*` | pi 默认发现(A/B 与显式 opt-in)|
516
520
 
@@ -573,22 +577,41 @@ commit 是溯源记录、本节是长期引用点——不并存两份权威值
573
577
  **唯一"可失败"的入口**;内置 MCP 是常量入口、恒在(无第三方、无解析)。
574
578
  ② `MV_MC_TOOLS_STRICT=1`(测试/探针保真)→ MC 入口缺失即报错退出
575
579
  (否则测试可能在"没装 MC"的环境里通过,而 `ctx_search` 从未生效);
576
- 内置 MCP 不参与该判定(名字由 pi 注册、无需解析)。
580
+ 两份**内置常量**入口(`builtin:mcp` / `builtin:codemode`)不参与该判定
581
+ (名字由 pi 注册、无需解析、不会失败)。
577
582
  ③ 生效值语义:**以本档核心能力为准** —— MC 入口解析成功 → `生效=mc-tools`;失败 →
578
583
  `生效=none`(本档核心 `ctx_search` 缺失);原因字段 = **入口层失败原因**(不写
579
584
  工具可用性——那件事本档观测不到,见下方「平台能力的可见性」)。
580
585
  *(旧措辞「部分降级仍生效=mc-tools」出自「两份入口都会被解析」的时代,已随第二份
581
586
  改为常量入口而失效。)*
582
587
  ④ 为什么必须显式 `-e`:`--no-extensions` 关的是"扩展发现**与内置扩展**"
583
- (pi `--help` 原文)——内置 MCP 也在关停范围,不显式加载 = agents 完全没有
584
- MCP 工具;`-e <path>` 接受 `builtin:<name>`(同一份 help)。上游证据:
588
+ (pi `--help` 原文)——内置 MCP **与 codemode** 都在关停范围,不显式加载 =
589
+ agents 完全没有 MCP 工具(或被注册但调不到,见 ⑤);`-e <path>` 接受
590
+ `builtin:<name>`(同一份 help)。上游证据:
585
591
  `core/extensions/index.ts` 里 `{ name: "mcp", builtin: true }`,
586
592
  `core/resource-loader.ts` 在 `noExtensions` 时只保留 CLI 显式 `-e` 的扩展。
587
- ⑤ **exposure 是配置层的事**(不属于本档):`mcp.json` 里每个 server 的
588
- `exposure` 默认 `codemode`(工具**不声明给模型**,只能从 codemode 脚本调用);
589
- 要让 agents 直接调用(= 先前 adapter 的体验)需在 `mcp.json` 写
590
- `"exposure": "direct"`——**该文件是用户级配置、也影响主 pi**(2026-09-30 用户裁
591
- 决选 direct)。
593
+ ⑤ **exposure 回到 pi 默认(`codemode`)**,agents 也走**原生调用方式**(2026-09-30 用户裁决;
594
+ 此前一天曾短暂改成 `direct`,理由 = "更贴合 pi 的原生设计"):
595
+ - `codemode` = 工具**不声明**给模型,列在 **codemode 工具的描述**里,模型**写脚本**调用
596
+ (`tools` / `ALL_TOOLS` / `text()` / `return` …);pi 在 server 连上时**自动激活**
597
+ codemode 工具。好处:大工具面不进模型声明、脚本内可**并行调多个工具**、大结果先筛后回
598
+ (直调 >20KB 会被掐中间)。
599
+ - **代价**:脚本比直调多一步;且**必须显式加载 codemode 扩展**(见 ④)——`codemode` 是
600
+ 独立的内置扩展,`--no-extensions` 会关掉它;少了它,MCP 扩展只发一条
601
+ `ui.notify("…they cannot be called.")`,而我们的非交互模式里 notify 是 **no-op**
602
+ ⇒ 工具注册了却调不到、且**静默**(2026-09-30 读 `extensions/mcp/index.ts:336-359`
603
+ + `core/extensions/runner.ts` 的 `noOpUIContext`)。**这是本档必须同时 `-e builtin:codemode`
604
+ 的全部理由。**
605
+ - 该设置住在 `~/.pi/agent/mcp.json`(**用户级、也影响主 pi**);项目级 `.pi/mcp.json`
606
+ 只按 **server 同名覆盖**且需 cwd 被 trust。**没有 env 覆盖**(2026-09-30 读 `config.ts`)。
607
+ - **已实测(2026-09-30 探针,n=2,生产形态源与命令)**:agents 走 codemode 路径**真调通**
608
+ MCP —— 工具调用 = `codemode`,脚本里 `await tools.mcp__web_search_prime__web_search_prime({…})`,
609
+ 两次都取回真实搜索结果,墙钟 **25–26s**(同款任务 direct 时代 16–31s,同量级)。
610
+ *口径*:只验"能调通"(不验多工具并行 / 大结果过滤);token **未做配对测量**——
611
+ direct 那笔 +1,025 是同源配对所得,两轮之间 fork 源已变大,**数字不可跨轮比**。
612
+ - 命令形状(三档都只是 `-e` 的有无):
613
+ `none` = 四个 `--no-*`;`mc-tools` = 四个 `--no-*` + 两份**内置常量**入口 + MC 入口;
614
+ `all` = 不加任何 `--no-*`、也不加 `-e`(pi 默认发现)。
592
615
  不新增档位(保持简单)。`none` 零依赖(无 MC 的机器/CI 显式选它)。
593
616
  **实测(2026-09-30 真场,38 次唤醒)**:内置 MCP 的**每唤醒成本 ≈ 0**——逐唤醒「启
594
617
  动前」中位 **−1.3s**(测量偏置)、三 agent 合计 −2s / −5s / +19s、收尾中位 1.1s;
@@ -619,9 +642,11 @@ commit 是溯源记录、本节是长期引用点——不并存两份权威值
619
642
  **cwd = fork_cwd** 启动(项目级 `.pi/mcp.json` + trust 会改变 agents 所见,别把近似当事实)、
620
643
  文档写明只覆盖**持续性**失效(不覆盖"启动正常、中途断")。**两条已证伪的错路**:成功路径
621
644
  stderr(notify 是 no-op)与 session 文件(工具集不上盘)——别再捡。
622
- - **exposure 的成本与口径**:`direct` 的代价实测 = **+1,025 tok/请求**(差值口径;n=2,
623
- 配置 = 3 server / 5 工具全 direct;人口 = agent 侧探针会话;**机制外推**:主 pi 每请求同量级
624
- ≈ +1k,**未测**)。数字随 server 数 / exposure 变化而作废;写**差值**不写绝对值。
645
+ - **exposure 的成本与口径**:`direct` 时代实测 **+1,025 tok/请求**(差值口径;n=2;配置 =
646
+ 3 server / 5 工具全 direct;人口 = agent 侧探针会话)——该配置**已于 2026-09-30 回退**为
647
+ pi 默认 `codemode`(见语义清单 ⑤),所以这笔数字**只作历史对照**,不代表现行成本;
648
+ `codemode` 的现行成本(工具描述 + 脚本往返)**未测**。数字随 server 数 / exposure 变化而
649
+ 作废;写**差值**不写绝对值。
625
650
  - **三档边界**(可复用的判据):① 上游**文档化结构接口**(`pi mcp list --json` 字段、
626
651
  session 条目)→ **可作机器判据**,字段缺失/形状变 ⇒ 记"未知";② 上游**人类 prose**
627
652
  (stderr / notify 文案)→ **只落盘留痕、不做分支**;③ 上游**内部布局**(第三方包
@@ -799,7 +824,7 @@ fork 机制建立在 pi 的 **session jsonl 文件**上——这是**外部契
799
824
  | CLI | `--session <path>`(须接受**任意路径文件**——我们的 fork 源在分析目录里)、`--session-id`、`--session-dir`、`--name`、`--model`/`--thinking`/`--append-system-prompt`/`--print`/`--approve` |
800
825
  | 文件布局 | `~/.pi/agent/sessions/--<cwd 编码>--/<ts>_<sid>.jsonl`;编码 = 去首尾 `/`、内部 `/`→`-`(`spec_gen.pi_sessions_dir`;`PI_SESSION_FILE` 是更稳的入口) |
801
826
  | 条目 schema | 每行一个 JSON:`type`/`id`/`parentId`/`timestamp`;消息体在 `message.{role,content}`;**未知类型一律原样透传**(`_fold_entry` 默认分支) |
802
- | 语义(**只复刻这两处**) | ① replay 起点 = 路径上最后一个 `compaction` 的 `firstKeptEntryId`;② 可见集合 = 该锚点之后的条目(`_normalize_entries` 据此移除窗口内 compaction 并桥接 `parentId`,不变量 I4) |
827
+ | 语义(**只复刻这三处**) | ① replay 起点 = 路径上最后一个 `compaction` 的 `firstKeptEntryId`;② 可见集合 = 该锚点之后的条目(`_normalize_entries` 据此移除窗口内 compaction 并桥接 `parentId`,不变量 I4);③ **上下文 = 从 leaf 沿 `parentId` 上溯**、遇缺失父节点**静默停止**(`buildContextEntries`/`buildSessionPath`)⇒ 删除条目必须桥接(`_bridge_parents`;2026-10-03 发现 tlc 剔除漏桥接时可达 65/13454 条) |
803
828
  | 条目类型 | `compaction`(读/移除)、`thinking_level_change`(剔除继承值,否则 pi 不写本场生效值)、`session_info.name`、`custom_message`(我们的边界条目) |
804
829
 
805
830
  **触碰面**(适配范围;口径 = 函数体行数,2026-09-22 摸底):`meeting_fs` 456 行
@@ -0,0 +1,226 @@
1
+ <!-- 存档:docs/reviews/2026-10-03-durable-subagent-eval.md
2
+ 来源:一次真实多视角分析的 result.md 原文(未删改,仅加本头与下方说明)。
3
+ 分析场次目录已随 cleanup 删除;文中消息编号不可再核验,仅作溯源线索
4
+ (与代码注释引用约定一致:行为以自描述为准)。 -->
5
+
6
+ # 存档说明
7
+
8
+ - **主题**:评估 pi 1.0.0 的 **durable subagent**(`@earendil-works/pi-durable`)能否用在本项目——
9
+ 它的 session 形态、与我们的 fork + 多进程编排相比如何、有没有真正用得上(或该等待)的部分。
10
+ - **场次**:`mv-mv-main-20261003-144433`(视角:效率 / 简单 / 铁律;档位 `high` ×3;扩展策略
11
+ `mc-tools`;配额 `max-meeting 20`;**终止 = 共识**,RR 全体 pass,无保留分歧)
12
+ |对象 = 本仓 HEAD `7c79dbd` + pi 1.0.0 源码(`/root/research/pi`)
13
+ - **判定**:**现在不采用 durable**(不引依赖、不换会话后端、不改并发模型、不把视角改成 durable
14
+ 子对话、不为未采用的运行时预留接口);**简单性动机 0 条**。四条"越界型"否决:判定纯净性 /
15
+ 单一事实源 / 持久化职责重复 / 并发模型冲突;红线 = **运行时归属不转移**。
16
+ 重估触发分两类两条:A 依赖破裂(见 `docs/design.md` §六 三条)/B 痛苦(G2 失败损失、
17
+ G3 延迟 SLA)。
18
+ - **落地**:**结论未落地为代码改动**(本场结论即"不改")。本场另案两条已落地:
19
+ (丙) 异常路径未提交产出**可见化** + (3) 超时注释与实现对齐 → commit `0bf7fa0`
20
+ (`meeting_engine._uncommitted_slots` + 异常边界日志;500 python + 54 harness 全绿,
21
+ 先红后绿已验证)。
22
+ - **备注**:本场最有价值的**副产品**是它核到的"**两层持久化**"(会话层逐条落盘 + 协议层
23
+ 每唤醒一个提交点)——它纠正了此前"一次唤醒被中断 = 白付全部模型时间"的量级判断,
24
+ 并把失败损失收窄为"会话层未落盘的那一步 + 协议层那条消息"。另:本场也是
25
+ 2026-10-03 fork 链桥接修复(`7c79dbd`)的**首次真场验证**(三个 fork 源 pi 可达 == 产物总数、
26
+ 悬空 parentId = 0)。
27
+
28
+ # result.md —— 多视角分析结论
29
+
30
+ **主题**:评估 pi 1.0.0 的 durable subagent(`@earendil-works/pi-durable`)能否用在本项目——
31
+ 它的 session 形态是什么样、与我们的 fork + 多进程编排相比如何、有没有我们真正用得上(或该等待)的部分?
32
+
33
+ **场次**:`mv-mv-main-20261003-144433`|视角:效率 / 简单 / 铁律|对象 = 本仓 HEAD `7c79dbd` + pi 1.0.0 源码(`/root/research/pi`)
34
+ **纪律**:只读调研(未启动任何真实 pi 运行)、不改代码;引用给 `文件:行`;未核实项已显式标注。
35
+
36
+ ---
37
+
38
+ ## 0. 共识结论(三方一致)
39
+
40
+ > **现在不采用 durable**:不引依赖、不换会话后端、不改并发模型、不把视角改成 durable 子对话、
41
+ > **不为未采用的运行时预留入口或接口**。
42
+ > **简单性动机 0 条;可吸收清单清零。**
43
+ > 重估触发分**两类两条**(A 依赖破裂 / B 痛苦),详见 §5。
44
+
45
+ 分歧在自由讨论阶段已全部收敛(见 §7 收敛轨迹);收尾轮转三方全体 `pass`,**无保留分歧**。
46
+
47
+ ---
48
+
49
+ ## 1. 形态对比(问题 1)
50
+
51
+ | 维度 | durable | 我们 |
52
+ |---|---|---|
53
+ | 形态 | **in-process TS 运行时库**:`Harness.open(storage, {models, registry, settings}, context)`(`packages/durable/README.md` Quick Start) | **进程外编排**:Python 调 `pi` 子进程 + jsonl + git |
54
+ | 规模 | `packages/durable/src` 60 文件 / ≈15.5k 行(不含 chord / pi-ai) | 核心 9 模块 6015 行(`meeting_fs.py` 1488 / `meeting_engine.py` 755 / `meeting_loop.py` 710 …) |
55
+ | 隔离性 | subagent = **同一 Harness 内的 child conversation**(进程内隔离;子代理"Starts as a copy of this conversation's agent",README "Abort and Subagents") | **每视角一个独立 pi 进程**(进程级隔离:失败域 / 并发 / 上下文窗口各自独立) |
56
+ | 上下文来源 | `fork(entryId)` = **同存储内 entry 粒度指针** | **跨存储产物**:从主 pi jsonl 读原始条目 → budget 裁剪 + 折叠 → 尾部注入切换叙事 → 落成 `pi --session <file>` 可打开的文件(`docs/design.md` §一) |
57
+ | 并发 | **单进程独占存储**:"One process owns a storage at a time; there is no cross-process locking"(`packages/durable/README.md:527`) | **N 进程 + git 共享**(git 无独占锁;实测并行度 2.34) |
58
+ | 失败恢复 | per-step checkpoint + `replay:"safe"` + `requestId` 幂等 | **两层**:pi 会话层逐步落盘 + 稳定 sid 续接;协议层一唤醒一提交点(见 §3.3) |
59
+ | 成本 | 新增运行时(TypeBox 峰值 RSS ≈23MB unbundled / ≈4MB bundled,README "Storage")+ 若用其压缩则**每次一次模型调用** | 编排层可省上界 ≈3.7% 墙钟(见 §4) |
60
+
61
+ **durable 能提供我们现在拿不到的能力吗?** 只在一处,且很小:**工作级检查点**(`replay:"safe"`)。
62
+ 而它已被 pi 的会话层**大部分覆盖**(§3.3);净增量只剩**工具副作用幂等**(`replay:"safe"`),
63
+ 而我们的 agent 副作用主要是"读 + 末尾写一个消息文件"。
64
+
65
+ ---
66
+
67
+ ## 2. session 形态(问题 2)
68
+
69
+ - **durable 的会话**(CLI 侧):每会话一个目录,含 `meta.json` + `session.sqlite`,**worker 独占**
70
+ (`packages/coding-agent/src/experimental/session-catalog.ts`:`sessionStoragePath()` = `<dir>/<id>/session.sqlite`,注释 "Workers lock it and own the storage inside it")。
71
+ - **门禁**:`process.env.PI_EXPERIMENTAL === "1"`(`packages/coding-agent/src/core/experimental.ts:2`),
72
+ 且只放行 `server` / `client` 两个子命令(`experimental/commands.ts:86`)。
73
+ - **CLI 主路径仍是 jsonl**:`docs/session-format.md` 仍写 "Sessions are stored as JSONL"、header version 3,
74
+ `--session <path|id>` 仍接受任意文件;内置扩展清单未变(llama.cpp / codemode / tool-search / mcp)。
75
+ - **我们依赖的契约**:`jsonl + --session <任意路径文件>`,逐条见 `docs/design.md` §六(外部契约 / 迁移清单)。
76
+ - **结论**:`docs/design.md` §六 已写下裁定「**现在不改**——对着尚未被 CLI 使用的接口写代码是投机」。
77
+ durable 的存储**技术上可独立 import**(`packages/durable/package.json` exports 有 `./storage/{memory,jsonl,sqlite}`),
78
+ 但**会话层不可独立**(`Harness.open` 一次拿到"存储 + 在其上跑 agent 的机器")。⇒ **选项空间 = {什么都不买} ∪ {买整套运行时},中间为空(存在但价值为零)**。
79
+ 且只借 storage 也不是"零收益的格式迁移"那么轻——它会与 git 形成**两个事实源**(违反"单一事实源 = protocol.json + git")。
80
+
81
+ ---
82
+
83
+ ## 3. 需求对照(问题 3):6 条硬需求只有 2 条落在 durable 范围内
84
+
85
+ | # | 硬需求 | durable 对应 | 判定 |
86
+ |---|---|---|---|
87
+ | ① | N 个视角**各自独立会话**(长思考中保持多视角关注) | child conversation(`tx.createConversation`) | **形态不同**:进程内 N 会话 vs N 个进程 ⇒进程级隔离(失败域/并发/窗口)消失 |
88
+ | ② | fork 主会话(budget 裁剪 + 切换叙事注入) | `fork(entryId)`(同存储、entry 粒度) | **无对应**:我们的 fork 是**跨存储产物文件**(§1) |
89
+ | ③ | cwd = 主项目 | `configure({cwd})` + `env` per conversation | 覆盖,但我们已有(fork 直接继承) |
90
+ | ④ | 消息经 **git** 交换 | 无(自有 inbox/docs/submissions) | **范围之外** |
91
+ | ⑤ | human 插话(文件 viewer/sayer) | `watch()`/`viewState()`(自有观察面) | **范围之外**(换过去等于重建通道) |
92
+ | ⑥ | **确定性 loop**(判定不看 LLM) | 由它的任务图/调度器决定 | **冲突**(见 §4 越界型) |
93
+
94
+ **结论**:durable 只触及 ① 和 ③,而这两条**我们已经具备**;②④⑤⑥ 不在其范围或与其冲突。
95
+
96
+ ### 3.3 两层持久性(本场核到,取代"整唤醒全额重付"的旧量级)
97
+
98
+ 每次唤醒有**两个粒度不同的持久化层**:
99
+
100
+ > ① **会话层** = pi 的 `session.jsonl`,**逐步同步落盘**(`_appendEntry` → `_persist` → **`appendFileSync`**,
101
+ > `packages/coding-agent/src/core/session-manager.ts:1191/1195/1189`;`appendMessage` `:1204-1213` 逐条一写),
102
+ > 失败后经**稳定 sid** 续接(`meeting_loop.py:493-496` 预生成 UUID、`:523` 失败保持)⇒ 承载"**对话工作**";
103
+ > ② **协议层** = git 消息文件,**一次唤醒一个提交点**(`meeting_engine.py:326/332`;`next_msg_id` 源
104
+ > `git_ls_files`,`meeting_fs.py:715`)⇒ 承载"**判定可见性**"。
105
+
106
+ **失败损失 = 会话层未落盘的那一步 + 协议层那条消息(≠ 整唤醒的模型时间)。**
107
+ 失败形态两支:**异常路径**(timeout/被杀,`meeting_loop.py:463` 抛 `TimeoutExpired`)不进 `commit_new_files`
108
+ (由 `meeting_engine.py:747-753` 记 log + 回轮询、**不自动重唤**);**正常返回但无产出**走 `MAX_RETRY`
109
+ (≤3,`meeting_engine.py:52`)。
110
+
111
+ > **已有能力**:durable 的 headline(进程死在 turn 中途、重开从最后提交点续)**大部分已由会话层提供**;
112
+ > 且我们**有直接的崩溃恢复测试**:`tests/test_meeting_concurrency.py:319 test_crash_recovery`(`crash_map` 30%,
113
+ > 口径 `:7`「崩溃恢复:…不丢不重」)——**该属性我们已测过**。
114
+
115
+ ---
116
+
117
+ ## 4. 否决项(按**理由类型**分三型;重估命运不同)
118
+
119
+ ### 越界型(**不可让**,不随数据变化)
120
+ 1. **判定纯净性**:采用它会把**库内状态**掺进判定输入,污染唯一保持纯的那一层——`meeting_core.py:1-5`
121
+ 自述「**纯函数,无 I/O**……判定函数只吃数据结构」。这不是收益/成本问题,是"要不要保住不变式"。
122
+ 2. **单一事实源**:durable 的 `resume()` 以**它自己的存储**为权威("以 pending 为准接着跑"),与我们
123
+ "判定只看 git bare 事实"并存 = 两个事实源(违反"单一事实源 = protocol.json + git")。
124
+ 3. **持久化职责重复**:pi 会话层已在做工作级持久化(§3.3);采用 durable = **两套持久化并存、无一方会被删掉**。
125
+ 4. **并发模型冲突**:durable 存储**独占**(`README.md:527`)vs 我们 N 进程 + git 共享(git 无独占锁);
126
+ 搬进单进程 = 用单点串行 + 单点故障换掉 OS 给的并行。
127
+
128
+ ### 冗余型(已有覆盖;仅在"覆盖物失效"时重评)
129
+ 1. `requestId` 幂等:我们的**消息级幂等是零成本副产品**(`next_msg_id` 源 `git_ls_files`,`meeting_fs.py:715`)
130
+ ——未提交的孤儿占不到新序号,重写天然落回同一槽位。
131
+ 2. "唤醒前记 msg_path 再查"(效率 §5 的原始设想):`_produced`(`meeting_engine.py:233-234`)已覆盖"已写出"
132
+ 那一半;另一半(做了工作未写出)**本地记账也救不了**(没东西可查)⇒ 重复记账。
133
+
134
+ ### 不匹配型(净增,需先证明净减)
135
+ 1. 为将来预留 `--session-backend` 之类抽象层:提前支付 durable 的固定成本,收益现在为 0。
136
+ 判据:**它当下替代或删除了哪段代码?**答不上来即反对。
137
+ 2. "上游压缩替换我们 ~300 行手写裁剪":**不成立**——见 §6「简单性动机 0 条」。
138
+
139
+ ### 红线「运行时归属不转移」+ 两个守卫
140
+ > **线**:我们的 loop / `meeting_core` 拥有运行时;**不引入第二个运行时的入口**。
141
+ > **守卫一(运行期)**:判定输入不得含库内状态(`meeting_core.py:1-5`)。
142
+ > **守卫二(静态代码面)**:不得为未采用的运行时预留抽象/接口。
143
+ 两个守卫**共享同一根因**("谁拥有运行时"),三类越界型否决都挂在同一条线下。
144
+
145
+ ---
146
+
147
+ ## 5. 重估触发条件(**两类两条**;勿合并——合并会把"必须做"与"可以考虑"糊成一句)
148
+
149
+ ### A. 依赖破裂触发(我们的代码会因此坏 ⇒ **必须适配**)
150
+ 引用 `docs/design.md` §六 三条(不重抄):
151
+ 1. `docs/session-format.md` 改写,或 CLI 默认会话落 sqlite/repo 抽象(含 `--session-backend` 类开关);
152
+ 2. `--session <file>` 不再接受任意路径文件,或首唤报「打不开 fork 源 / 上下文为空」;
153
+ 3. CHANGELOG 出现 "migrate sessions" / "sqlite default" 类条目。
154
+ - **A 类第一验证项**:换后**判定事实源是否仍是 git**(若 storage 成权威 ⇒ 需求⑥违约)。
155
+ - **判据**(过滤 A 类表):「这条成立时,我们**哪一行代码会坏**?」——以此排除"上游包状态变化"
156
+ (durable 毕业出 experimental **不改变我们的任何依赖** ⇒ 降格为"采用前置条件",**不进 A 类**)。
157
+
158
+ ### B. 痛苦触发(出现新痛点才**考虑采用**)
159
+ - **G2(失败损失)**:一次失败里「**会话层未落盘的步数 × 单步代价**」占比变得可观——
160
+ **不是**"整唤醒的模型时间"。取数(0 LLM、现有产物):① 失败类型分布(timeout/异常 vs rc≠0/无产出,
161
+ 决定有无 `MAX_RETRY` 重付)② 读失败唤醒的 `session.jsonl`,数"最后一条已落盘条目"到"应完成步"的差
162
+ ③ 报告既有 **`retry` 列**。
163
+ - 当前基线:本场 **rc≠0 0/35、retry 0、stall 0** ⇒ 三项**全无样本**。触发形态包含"单次工作 p95 很长"
164
+ (当前 23–82s/唤醒,相差 1–2 个数量级)。
165
+ - **G3(延迟 SLA)**:要求"human 插话在 <1 个唤醒内被回应" ⇒ steering 成为功能需求(当前 human 消息 0 次)。
166
+
167
+ ---
168
+
169
+ ## 6. 定量结论(附口径)
170
+
171
+ | 项 | 数 | 口径 / 来源 |
172
+ |---|---|---|
173
+ | 模型延迟占比 | **96%**(Δ助手 Σ=4379s / Σ进程跨度 4560s) | `mv-mv-main-20260930-135705-report.txt` 逐唤醒求和(3 视角 / 35 唤 / 墙钟 32m43s / 并行度 2.34) |
174
+ | 编排层可省**上界** | **≈3.7% 墙钟**(收尾 Σ50s=2.5% + 启动 0.68s×35≈1.2%) | 同上;启动沿用探针实测中位 0.68s(报告「启动前」列存在时区偏置,不可用其和) |
175
+ | 我们的裁剪成本 | **29ms / 30ms / 28ms(n=3)**,1.4MB 主会话 | `build_fork_source(budget)`;每 agent 首唤一次 ⇒ ≈3 次/场 ≈0.005% 墙钟 |
176
+ | durable 的压缩成本 | **一次模型调用**(`SummaryRequest{model, thinkingLevel, maxTokens}` + `phase:"summarize"`) | `packages/durable/src/harness/compaction.ts:31-48` |
177
+ | 两层持久性的落盘粒度 | 会话层 = **每条条目**;协议层 = **每唤醒一次** | `session-manager.ts:1189`;`meeting_engine.py:326` |
178
+ | 失败基线 | **0/35**(rc≠0 0、retry 0、stall 0) | 报告各 agent 合计行 |
179
+
180
+ **简单性动机 = 0 条**(明确记录,防误记):
181
+ - "上游压缩替换 300 行"**不成立**:两者**机制种类不同**——durable 的压缩是含模型调用的**摘要任务**,
182
+ 我们的是**确定性流水线**(`meeting_fs._budget_entries`)。把它换来 = **把一条故意确定性的路径重新
183
+ 变成 LLM 路径**(与本项目已撤掉 `prepare` 蒸馏同源,`AGENTS.md:63`),且关键路径上多一次等待 + token。
184
+ - 且那段行**删不掉**:durable 的压缩作用于**它自己渲染的上下文**,我们的作用于**外进程条目集 → 产物文件**。
185
+
186
+ ---
187
+
188
+ ## 7. 收敛轨迹与保留分歧
189
+
190
+ **无保留分歧**(收尾三方全体 `pass`)。自由讨论中出现过并被**核码纠正**的差异,记录如下(供溯源):
191
+
192
+ | 曾出现的主张 | 纠正 | 纠正依据 |
193
+ |---|---|---|
194
+ | "一次唤醒被中断 = 白付**全部**模型时间"(效率、铁律) | 收为"会话层未落盘的**那一步** + 协议层那条消息" | `session-manager.ts:1189` 逐步 `appendFileSync` + 稳定 sid 续接 |
195
+ | "重复付费窗口不存在"(简单) | 窗口存在,只是本地记账救不了 | `_produced` 粒度 = 消息文件数(`meeting_engine.py:233-234`) |
196
+ | "`commit_new_files` 无条件"(简单) | 异常路径到不了它 | `meeting_loop.py:463` 在 `:326` 之前抛 |
197
+ | "FakeHarness ≈ responder 注入同原理"(铁律) | 依据换成"**已有直接崩溃恢复测试**" | `fake_agent.py:4-5`(responder 只管内容决定)vs `test_meeting_concurrency.py:319` |
198
+ | "唯一简单的动机 = 少 300 行"(简单,后自行撤回) | 简单性动机 **0 条** | 机制种类不同(`compaction.ts:31-48` vs 确定性流水线) |
199
+
200
+ ---
201
+
202
+ ## 8. 本场顺带发现(**另案,与是否采用 durable 无关**;仅供 owner 决定)
203
+
204
+ 1. **(丙) 边界:写了合法消息 + 异常路径 ⇒ 既不提交、也不回收、被下一唤醒同槽覆盖(静默丢弃)。**
205
+ 证据:`commit_new_files` 调用点只有 `meeting_engine.py:326/332`(无孤儿回收);超时在 `:326` 之前抛出
206
+ (`meeting_loop.py:463`);`next_msg_id` 源 `git_ls_files`(`meeting_fs.py:715`)⇒ 下次写同一槽位。
207
+ **代价上限**:内容已在会话层(§3.3),下次唤醒同 sid 可重建 ⇒ **≈ 一次短生成**;loop 侧**无任何日志**。
208
+ **修法约束**(简单视角):比例相称(**日志级**或异常路径一次提交尝试),**不得**借机构建"进度记忆 / 恢复机制"
209
+ (那正是已否决的"库内状态进状态机"的本地版本)。
210
+ 2. **事实表述须窄化**:「崩溃恢复:不丢不重(已测)」→「**已提交序号**不丢不重(`test_crash_recovery`,
211
+ 崩溃模拟在**产出前**,`fake_agent.py:104-105`);**产出后未提交((丙))未覆盖**」。
212
+ (不窄化会让 §3.3 的旧量级被重新引入,两处必须一起改才自洽。)
213
+ 3. **注释与实现不符**(文档缺陷,最小对齐 = 改注释一行):
214
+ `meeting_loop.py:428-429` 称超时"上层可恢复重试",而引擎 `meeting_engine.py:747-753` **只 log + 回轮询、不重试**。
215
+ 4. **口径注**:`MAX_WAKE_SEC = 900`(**单次唤醒硬上限**,`meeting_loop.py:27`)≠ `DEFAULT_STALL_TIMEOUT = 600`
216
+ (**引擎无进展阈值**,`meeting_fs.py:67`)——两个旋钮,引用须带口径。
217
+
218
+ ---
219
+
220
+ ## 9. 一句话结论(供产品决策入口)
221
+
222
+ > durable 是**另一层**(进程内持久化运行时)而非我们的替换品:采用它 = **转移运行时归属**,
223
+ > 在判定纯净性 / 单一事实源 / 持久化职责 / 并发模型四处越界(不可让),而它在墙钟(天花板 ≈3.7%)、
224
+ > 简单性(动机 0 条)、失败恢复(净增量仅工具副作用幂等、基线 0/35)三个维度都拿不到净收益。
225
+ > **现在不采用**;把"我们的代码会坏"(A 类,§六)与"出现新痛点"(B 类,G2/G3)两条触发登记下来,
226
+ > 并保留一条红线:**运行时归属不转移**。
@@ -41,6 +41,7 @@
41
41
  | `2026-09-25-patch-audit-review.md` | 审阅 0.8.0 → 0.9.0 一周改动是否有补丁堆叠 / 复杂度失配 / 职责边界问题 | **判定:没有补丁堆叠**;真问题是**文档漂移 F1**(两份入口降级语义改了、6 处复述没跟)、**名实不符 F2/S3**(`_viewer_set_errors` 自称唯一组合点+数量≥2,皆不成立)、**契约只有注释 F4**(cleanup 裸 print 禁令 → 本仓首条 AST 结构断言);顺带:自然使用复测 `ctx_search` 0 次(不可证伪那句指引)、MCP adapter 无收尾尾巴 | `6c024be` |
42
42
  | `2026-09-27-aft-mc-evidence-audit.md` | 复盘「屏蔽 AFT」决策(对照数据 / 结论链 / 现默认扩展策略是否成立) | **判定:成立**(承重换成真场:AFT 收尾 66–78% vs ≈0%;MC historian e2e21 62 次/60 失败 vs e2e23 0 次 + 上游 `#916`)。**最重一条 = 审计自身证据链**:决策 20 把 09-13 那批**装置有缺陷**的探针数字当「受控对照」(#26 早已记录)→ 两笔退出证据位;另订正 entry 结构句(2 个生命周期钩子,非「无 hook」)、adapter 每轮钩子 = 已知盲区、e2e23 的 12m31s 标为源不可复核;新增**数字口径规则**与**回退判据**(带「需求场景在窗口外」前置限定)+ 加回 AFT 的定价程序 | `ffe28aa` |
43
43
  | `2026-09-30-builtin-mcp-audit.md` | 内置 MCP 替换(`-e builtin:mcp`)与 `exposure: direct` 自审 | **per-server 静默失效**(`rc=0` ≠ 工具可用:失败被 allSettled 吞掉 → 只走非交互模式下为 no-op 的 notify → 产物零痕迹);每唤醒成本 ≈ 0(38 唤);agents 自然用 MCP = 0 次;三视角撤回/更正 13 项 | `839ef4f` |
44
+ | `2026-10-03-durable-subagent-eval.md` | pi 1.0.0 durable subagent 能否用在本项目(session 形态 / 需求对照 / 是否采用) | **判定「现在不采用」**(四条越界型否决 + 红线「运行时归属不转移」);副产品 = 核到「两层持久化」(会话层逐条落盘 + 协议层每唤醒一个提交点),纠正了失败量级;另案两条:异常路径未提交产出**静默**、超时注释与实现不符 | 结论未落地(即「不改」);另案两条 → `0bf7fa0`(可见化 + 注释对齐) |
44
45
 
45
46
  ## 环境口径(读报告时的背景)
46
47
 
package/meeting_engine.py CHANGED
@@ -409,6 +409,26 @@ def commit_new_files(workdir, agent, head, mode):
409
409
  return True
410
410
 
411
411
 
412
+ def _uncommitted_slots(workdir, agent):
413
+ """列出**写了但未提交**的消息槽位(`NNNN.md`)——用于异常路径的可见化。
414
+
415
+ 为什么需要它(2026-10-03 自审批 (丙)):异常路径(超时/被杀/git 异常)走不到
416
+ `commit_new_files`,而 agent 可能**已经写出了合法消息**。那条文件留在工作树里,
417
+ 序号又由**已提交**文件推导(`next_msg_id` 源 `git ls-files`)⇒ 下一唤醒会写
418
+ **同一槽位**覆盖它:既不提交、也不回收、**loop 侧零日志**。
419
+
420
+ 代价上限有界(内容已在会话层,下次唤醒同 sid 可重建 ≈ 一次短生成),所以修法
421
+ 按**比例相称**取"日志级":**只记一行**,不建进度记忆 / 恢复机制——那等于把库内
422
+ 状态塞进状态机(本地版),正是"运行时归属不转移"红线禁止的(本场自审批 §4)。
423
+ """
424
+ agent_dir = os.path.join(workdir, agent)
425
+ if not os.path.isdir(agent_dir):
426
+ return []
427
+ return sorted(f for f in os.listdir(agent_dir)
428
+ if re.match(r"^\d{4}\.md$", f)
429
+ and not _is_committed(workdir, f"{agent}/{f}"))
430
+
431
+
412
432
  def _is_committed(workdir, path):
413
433
  """文件是否已提交(tracked)。
414
434
 
@@ -751,5 +771,12 @@ def agent_loop(workdir, agent, responder,
751
771
  # → agent 永久失联 → RR next 链阻塞。单次异常不终止讨论:
752
772
  # 记 log + 下一轮继续(设计 11.4 的异常路径扩展)。
753
773
  log(agent, f"异常(不终止讨论): {type(e).__name__}: {e}")
774
+ # 未提交产出的可见化(2026-10-03 自审批 (丙)):异常路径到不了
775
+ # commit_new_files,而 agent 可能已写出合法消息——那条文件会被下一
776
+ # 唤醒**同槽覆盖**。只记一行(不建恢复机制,理由见 _uncommitted_slots)。
777
+ orphans = _uncommitted_slots(workdir, agent)
778
+ if orphans:
779
+ log(agent, "⚠ 未提交产出将被同槽覆盖: " + "、".join(orphans)
780
+ + "(内容在会话层,下次唤醒可重建)")
754
781
  time.sleep(poll_interval)
755
782
 
package/meeting_fs.py CHANGED
@@ -902,6 +902,18 @@ MC_PACKAGE = "@cortexkit/pi-magic-context"
902
902
  # 只保留 CLI 显式 `-e` 的扩展(2026-09-30 读源码核实)。
903
903
  BUILTIN_MCP_ENTRY = "builtin:mcp"
904
904
 
905
+ # mc-tools 档的**第三份**入口 = pi 的 `codemode` 工具(同样是内置扩展)。
906
+ # 为什么必需:MCP server 若用 pi 默认的 `exposure: codemode`,其工具**不声明**给模型,
907
+ # 只能从 codemode 脚本里调用(pi 在 server 连上时会自动激活 codemode 工具);而
908
+ # `codemode` 是**独立的内置扩展**(上游 `core/extensions/index.ts` 的
909
+ # `{ name: "codemode", builtin: true }`),`--no-extensions` 会一并关掉它 —— 于是
910
+ # MCP 扩展的 `ensureDiscoveryActive` 找不到 codemode 工具,只发一条 `ui.notify`
911
+ # 警告("MCP tools are only reachable from the codemode or tool_search tool…
912
+ # they cannot be called."),而我们的非交互模式里 notify 是 no-op ⇒ 工具注册了却
913
+ # 调不到、且**静默**(2026-09-30 读源码 + 用户裁决走 pi 原生方式)。
914
+ # 同样是常量:名字由 pi 注册,无需解析(零第三方依赖)。
915
+ BUILTIN_CODEMODE_ENTRY = "builtin:codemode"
916
+
905
917
  FORK_MODES = ("budget", "compaction", "full")
906
918
  DEFAULT_FORK_MODE = "budget"
907
919
  #
@@ -1054,7 +1066,39 @@ def _fold_entry(entry, full_result):
1054
1066
  return n
1055
1067
 
1056
1068
 
1069
+ def _bridge_parents(entries, removed_ids):
1070
+ """把指向 `removed_ids` 的 parentId 上溯桥接,并给链首置 `None`。
1071
+
1072
+ **为什么要有这个助手(2026-10-03,1.0.0 核对时抓到的静默 bug)**:
1073
+ pi 构建模型上下文 = 从 leaf 沿 `parentId` 上溯(`core/session-manager.ts`
1074
+ 的 `buildContextEntries` → `buildSessionPath`),**遇到缺失的父节点就静默
1075
+ 停止**(上游文档只承诺"孤儿条目在 getTree 里变成根",**不修复链**)。
1076
+ 所以只要我们**删掉**任何条目而不修链,链就在那里断开,其之前的全部条目
1077
+ 对模型不可见 —— 且**没有任何信号**。
1078
+
1079
+ 实测代价(剔除 `thinking_level_change` 未桥接时,2026-10-03 主会话):
1080
+ 从末条上溯只到 **65 / 13454** 条(≈0.5%)—— "fork 携带主会话历史"这个
1081
+ 产品前提被静默破坏;agents 仍看似能用,是因为它们能直接读项目文件。
1082
+
1083
+ **纪律**:新增/未知条目类型可以"原样透传",但**删除类操作必须配桥接**
1084
+ (本仓有两处删除:compaction 移除、`thinking_level_change` 剔除)。
1085
+
1086
+ `removed_ids`: {被删条目 id: 它的 parentId}(只用于上溯,值可为 None)。
1087
+ 返回新列表(不修改入参);指向产物的 parentId 保持原样,指向产物外的
1088
+ 一律置 `None`(链首语义,与 preface 同型)。
1089
+ """
1090
+ out = []
1091
+ for e in entries:
1092
+ pid = e.get("parentId")
1093
+ while pid in removed_ids: # 上溯桥接(可能连续多个)
1094
+ pid = removed_ids[pid]
1095
+ out.append(dict(e, parentId=pid))
1096
+ ids = {e.get("id") for e in out}
1097
+ return [e if e.get("parentId") in ids else dict(e, parentId=None) for e in out]
1098
+
1099
+
1057
1100
  def _normalize_entries(entries):
1101
+
1058
1102
  """移除窗口内的 compaction 条目,并把指向它们的 parentId 上溯桥接。
1059
1103
 
1060
1104
  为什么必须移除(pi replay 语义,2026-09-10 实测):replay 以**路径上
@@ -1083,20 +1127,10 @@ def _normalize_entries(entries):
1083
1127
  comps = [e for e in entries if e.get("type") == "compaction"]
1084
1128
  if not comps:
1085
1129
  return entries, 0
1086
- removed = {e.get("id"): e for e in comps if e.get("id")}
1087
- out = []
1088
- for e in entries:
1089
- if e.get("type") == "compaction":
1090
- continue
1091
- pid = e.get("parentId")
1092
- while pid in removed: # 上溯桥接(可能连续多个)
1093
- pid = removed[pid].get("parentId")
1094
- out.append(dict(e, parentId=pid))
1095
- # 上溯出产物(祖先被预算裁掉)→ 链首显式 None
1096
- ids = {e.get("id") for e in out}
1097
- out = [e if e.get("parentId") in ids else dict(e, parentId=None)
1098
- for e in out]
1099
- return out, len(comps)
1130
+ # 桥接规则见 _bridge_parents(2026-10-03 起两处删除共用同一实现)
1131
+ removed = {e.get("id"): e.get("parentId") for e in comps if e.get("id")}
1132
+ kept = [e for e in entries if e.get("type") != "compaction"]
1133
+ return _bridge_parents(kept, removed), len(comps)
1100
1134
 
1101
1135
 
1102
1136
  def _budget_entries(entries, keep_tokens, summary=""):
@@ -1189,6 +1223,16 @@ def build_fork_source(src_session, out_path, new_id, new_cwd,
1189
1223
  I2 链连续:从末条上溯可覆盖**全部**条目;除链首外 parentId 均指向
1190
1224
  产物内条目(链首显式;compaction 模式下链首的父在窗口外属边界
1191
1225
  语义——replay 走到此处即停)
1226
+ **上游依据(2026-10-03 读源码核实)**:pi 构建模型上下文 = 从 leaf
1227
+ 沿 `parentId` 上溯(`core/session-manager.ts` 的
1228
+ `buildContextEntries` → `buildSessionPath`),遇**缺失父节点就静默
1229
+ 停止**(文档只承诺"孤儿条目在 getTree 里变成根",不修链)。
1230
+ ⇒ **纪律:删除类操作必须桥接 parentId**(本函数有两处删除:窗口内
1231
+ compaction、旧会话 `thinking_level_change`;两者共用
1232
+ `_bridge_parents`)。违反的代价是**静默**的:2026-09-13 加 tlc
1233
+ 剔除时漏了桥接,直到 2026-10-03 才发现——真实主会话上从末条上溯
1234
+ 只到 65/13454 条(≈0.5%),"fork 携带主会话历史"的产品前提被破坏,
1235
+ 而 agents 仍看似能用(它们能直接读项目文件)。
1192
1236
  I3 replay 所用锚点(路径上最后一个 compaction 的 firstKeptEntryId)
1193
1237
  指向产物内条目
1194
1238
  I4 **声明集合 == pi replay 可见集合**(budget/compaction 两个**窗口
@@ -1285,7 +1329,15 @@ def build_fork_source(src_session, out_path, new_id, new_cwd,
1285
1329
  # pi 靠它回填主 pi 的模型——那是活配置,不是陈旧副本。
1286
1330
  # 记账:配置条目**不计入** dropped(那不是上下文内容;dropped 的口径
1287
1331
  # = 预算丢弃 + 规范化移除)。
1288
- body = [e for e in body if e.get("type") != "thinking_level_change"]
1332
+ # **必须桥接**(2026-10-03 修):只过滤不修链 ⇒ pi 的 leaf→parent 上溯在
1333
+ # 这里静默停止,之前全部条目对模型不可见(实测可达 65/13454 条)。
1334
+ # 剔除后 pi 会在边界之后补写本场档位条目。
1335
+ tlc_removed = {e.get("id"): e.get("parentId") for e in body
1336
+ if e.get("type") == "thinking_level_change" and e.get("id")}
1337
+ if tlc_removed:
1338
+ body = _bridge_parents(
1339
+ [e for e in body if e.get("type") != "thinking_level_change"],
1340
+ tlc_removed)
1289
1341
  new_header = {
1290
1342
  "type": "session",
1291
1343
  "version": header.get("version", 3),
package/meeting_loop.py CHANGED
@@ -330,19 +330,23 @@ def _build_wake_cmd(workdir, agent, sid, cfg, fork_source, fork_cwd,
330
330
  if extension_policy == "none":
331
331
  cmd += no_ext
332
332
  elif extension_policy == "mc-tools":
333
- # mc-tools = 零扩展 + 显式加载**两份能力**(零第三方依赖):
334
- # ① MC 的 subagent-entry(工具注册 + 生命周期钩子,无 historian)→ ctx_search
335
- # ② pi **内置** MCP 扩展 → web_search / web_reader / zread 等
336
- # 为什么必须显式 -e:`--no-extensions` 关的是"扩展发现**与内置扩展**"
337
- # (pi --help 原文)——内置 MCP 也在关停范围,不显式加载 agents 就没有
338
- # MCP 工具;`-e <path>` 同时接受 `builtin:<name>`(pi --help 原文)。
339
- # 为什么用内置而不再用第三方 adapter:pi 0.99+ 自带,少一个外部依赖
340
- # (用户 2026-09-30 定);此前的 adapter 入口解析机制随之删除。
341
- # 降级语义:唯一"可失败"的入口是 MC(解析第三方包的内部文件)——
342
- # 失败则**本档核心能力(ctx_search)不到位** → 生效=none,原因里点名;
343
- # 内置 MCP 是常量入口、恒在,故降级不影响它(原因字段会说明这一点)。
333
+ # mc-tools = 零扩展 + 显式加载**三份能力**(零第三方依赖):
334
+ # ① MC 的 subagent-entry(工具注册 + 生命周期钩子,无 historian)→ ctx_search
335
+ # ② pi **内置** MCP 扩展 → web_search / web_reader / zread 等
336
+ # ③ pi **内置** codemode 扩展 → 让 ② 的工具真正可达(见下方“为什么 ③ 必需”)
337
+ # 为什么必须显式 -e:`--no-extensions` 关的是"扩展发现**与内置扩展**"
338
+ # (pi --help 原文)——内置 MCP 与 codemode 都在关停范围;`-e <path>` 同时
339
+ # 接受 `builtin:<name>`(pi --help 原文)。
340
+ # 为什么 ③ 必需:server 用 pi 默认 `exposure: codemode` 时,其工具不声明给
341
+ # 模型、只能从 codemode 脚本调用;少了 ③,MCP 扩展的 ensureDiscoveryActive
342
+ # 找不到 codemode 工具,只发一条 ui.notify 警告(而我们的非交互模式里 notify
343
+ # 是 no-op)⇒ 工具注册了却调不到、且静默(2026-09-30 读源码 + 用户裁决走原生)。
344
+ # 降级语义:唯一"可失败"的入口是 MC(解析第三方包的内部文件)——失败则
345
+ # **本档核心能力(ctx_search)不到位** → 生效=none,原因里点名;②③ 是常量
346
+ # 入口、恒在,故降级不涉及它们(原因字段只写入口层事实)。
344
347
  cmd += no_ext
345
348
  cmd += ["-e", meeting_fs.BUILTIN_MCP_ENTRY]
349
+ cmd += ["-e", meeting_fs.BUILTIN_CODEMODE_ENTRY]
346
350
  entry, err = meeting_fs.resolve_mc_tools_entry()
347
351
  if entry:
348
352
  cmd += ["-e", entry]
@@ -422,7 +426,10 @@ def _run_wake_proc(cmd, spawn_cwd, workdir, agent):
422
426
  三路径语义(2026-09-01 定,不得改变):
423
427
  ① pi 正常结束 → 返回 CompletedProcess
424
428
  ② 讨论目录被清理(cleanup)→ _kill_proc + SystemExit(0)(干净退出)
425
- ③ 总超时 → _kill_proc + 抛 TimeoutExpired(上层可恢复重试)
429
+ ③ 总超时 → _kill_proc + 抛 TimeoutExpired(上层 = 引擎异常边界:
430
+ **只 log + 回轮询、不自动重试**,见 meeting_engine.py 的 except Exception;
431
+ 本次唤醒产出不提交——若已写出消息文件,下一唤醒写**同一槽位**覆盖它,
432
+ 引擎会记一行「⚠ 未提交产出将被同槽覆盖」)
426
433
  """
427
434
  global _current_proc
428
435
  base = os.path.dirname(workdir)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-multi-viewers",
3
- "version": "0.10.2",
3
+ "version": "0.10.4",
4
4
  "description": "Multi-perspective analysis for Pi: fork the main session into N perspective agents over the meeting protocol.",
5
5
  "type": "module",
6
6
  "private": false,