@maxiaochao/pi-toolkit 0.9.2 → 0.9.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.
Files changed (66) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +40 -90
  3. package/extensions/{btw.ts → btw/index.ts} +1 -1
  4. package/extensions/{tests/btw-history.mjs → btw/tests/history.mjs} +1 -1
  5. package/global/AGENTS.md +24 -49
  6. package/package.json +19 -10
  7. package/packages/cache-export/CHANGELOG.md +5 -0
  8. package/packages/cache-export/README.md +70 -0
  9. package/packages/cache-export/TESTING.md +17 -0
  10. package/packages/cache-export/docs/demo.gif +0 -0
  11. package/packages/cache-export/docs/screenshot.png +0 -0
  12. package/{extensions/cache-export/index.ts → packages/cache-export/extensions/cache-export.ts} +1 -1
  13. package/packages/cache-export/package.json +22 -0
  14. package/{extensions/cache-export → packages/cache-export/src}/render.ts +1 -1
  15. package/{extensions → packages}/cache-export/tests/e2e-browser.mjs +2 -2
  16. package/{extensions → packages}/cache-export/tests/run-tests.mjs +2 -2
  17. package/packages/codex-edit/CHANGELOG.md +32 -0
  18. package/packages/codex-edit/README.md +123 -0
  19. package/packages/codex-edit/TESTING.md +20 -0
  20. package/{extensions → packages}/codex-edit/codex-edit-explainer.html +41 -20
  21. package/packages/codex-edit/docs/demo.gif +0 -0
  22. package/packages/codex-edit/docs/screenshot.png +0 -0
  23. package/{extensions/codex-edit/index.ts → packages/codex-edit/extensions/codex-edit.ts} +3 -3
  24. package/packages/codex-edit/package.json +48 -0
  25. package/{extensions/codex-edit → packages/codex-edit/src}/parser.ts +1 -2
  26. package/{extensions → packages}/codex-edit/summary.md +1 -1
  27. package/{extensions → packages}/codex-edit/tests/package.test.mjs +12 -7
  28. package/{extensions → packages}/codex-edit/tests/parser.test.mjs +100 -3
  29. package/packages/tiny-subagent/CHANGELOG.md +12 -0
  30. package/packages/tiny-subagent/README.md +128 -0
  31. package/packages/tiny-subagent/TESTING.md +20 -0
  32. package/packages/tiny-subagent/agents/worker.md +9 -0
  33. package/packages/tiny-subagent/docs/demo.gif +0 -0
  34. package/packages/tiny-subagent/docs/screenshot.png +0 -0
  35. package/packages/tiny-subagent/extensions/tiny-subagent.ts +228 -0
  36. package/packages/tiny-subagent/package.json +44 -0
  37. package/packages/tiny-subagent/src/personas.ts +110 -0
  38. package/packages/tiny-subagent/src/runner.ts +319 -0
  39. package/packages/tiny-subagent/tests/run-tests.mjs +111 -0
  40. package/packages/todo/CHANGELOG.md +5 -0
  41. package/packages/todo/README.md +63 -0
  42. package/packages/todo/TESTING.md +20 -0
  43. package/packages/todo/docs/demo.gif +0 -0
  44. package/packages/todo/docs/screenshot.png +0 -0
  45. package/{extensions → packages/todo/extensions}/todo.ts +1 -1
  46. package/packages/todo/package.json +25 -0
  47. package/{extensions → packages/todo}/tests/todo-state.mjs +1 -1
  48. package/packages/worktree/CHANGELOG.md +5 -0
  49. package/packages/worktree/README.md +60 -0
  50. package/packages/worktree/TESTING.md +21 -0
  51. package/packages/worktree/docs/demo.gif +0 -0
  52. package/packages/worktree/docs/screenshot.png +0 -0
  53. package/packages/worktree/package.json +20 -0
  54. package/packages/worktree/scripts/install.mjs +26 -0
  55. package/packages/worktree/tests/install.test.mjs +25 -0
  56. package/packages/worktree/tests/run-tests.sh +46 -0
  57. package/scripts/install-agents.mjs +8 -18
  58. package/extensions/codex-edit/README.md +0 -53
  59. /package/extensions/{btw-scroll.ts → btw/scroll.ts} +0 -0
  60. /package/{extensions/cache-export → packages/cache-export/src}/tau-assets.ts +0 -0
  61. /package/{extensions → packages}/codex-edit/config.json +0 -0
  62. /package/{extensions/codex-edit → packages/codex-edit/src}/model-routing.ts +0 -0
  63. /package/{extensions → packages/todo/src}/todo-state.ts +0 -0
  64. /package/{bin → packages/worktree/bin}/pi-worktree +0 -0
  65. /package/{bin → packages/worktree/bin}/pi-worktree-completion.bash +0 -0
  66. /package/{skills → packages/worktree/skills}/pi-worktree/SKILL.md +0 -0
@@ -0,0 +1,123 @@
1
+ # Codex Edit (apply_patch)
2
+
3
+ A Pi extension that gives selected GPT models the same `apply_patch` editing protocol used by the Codex harness, while every model outside the routing rules keeps Pi's native `edit` tool.
4
+
5
+ ![Codex Edit production parser screenshot](docs/screenshot.png)
6
+
7
+ ## Why this package exists
8
+
9
+ GPT models used through the Codex harness are trained to edit with OpenAI's `apply_patch` protocol, while Pi's native editor uses a different `oldText/newText` contract. This package exposes the Codex-harness editing interface inside Pi for explicitly allowed models. Models that do not match the rules continue to see Pi's native `edit`; the extension never replaces the editor universally.
10
+
11
+ The implementation follows Codex's ordered hunk matching semantics and supports the same Add/Delete/Update/Move patch shape, then adds workspace, symlink, overwrite, preflight, and mutation-queue safeguards required by Pi.
12
+
13
+ ## Demo
14
+
15
+ ![Codex apply_patch demo](docs/demo.gif)
16
+
17
+ ```text
18
+ *** Begin Patch
19
+ *** Update File: src/app.ts
20
+ @@
21
+ -const enabled = false;
22
+ +const enabled = true;
23
+ *** End Patch
24
+ ```
25
+
26
+ The complete patch is parsed and preflighted before any file is mutated; the result reports per-file diffs and committed paths.
27
+
28
+ ## Install
29
+
30
+ Install it as a separate package:
31
+
32
+ ```bash
33
+ pi install npm:@maxiaochao/pi-codex-edit
34
+ ```
35
+
36
+ This is the standalone child package of `@maxiaochao/pi-toolkit`. The full toolkit already bundles the same implementation, so install this package only when you do not want the rest of the toolkit. Installing both is unnecessary and may register `apply_patch` twice.
37
+
38
+ Update whichever distribution you installed:
39
+
40
+ ```bash
41
+ pi update npm:@maxiaochao/pi-toolkit # full toolkit, including bundled Codex Edit
42
+ pi update npm:@maxiaochao/pi-codex-edit # standalone child package
43
+ ```
44
+
45
+ The two npm packages have independent versions. An implementation or configuration change reaches toolkit users only after a new `@maxiaochao/pi-toolkit` version is published, and reaches standalone users only after a new `@maxiaochao/pi-codex-edit` version is published.
46
+
47
+ The extension registers `apply_patch` and selects the editing tool from the resolved model configuration:
48
+
49
+ - Allowed model ID on a supported Responses API: Codex-harness-compatible `apply_patch`
50
+ - Model ID outside the allowlist: Pi's native `edit`
51
+ - Model on another API, even if its ID matches: Pi's native `edit`
52
+
53
+ The default allowlist enables all `gpt-5.6-*` models regardless of provider. Other model families can be added after separate validation by editing `config.json`.
54
+
55
+ The extension does not add a permanent AGENTS rule telling models not to use `apply_patch`. Only the selected editor tool is active, so the model receives one editing protocol.
56
+
57
+ ## What `apply_patch` does
58
+
59
+ The tool accepts raw Codex patch text between `*** Begin Patch` and `*** End Patch`. It supports:
60
+
61
+ - `Add File`
62
+ - `Delete File`
63
+ - `Update File`
64
+ - `Move to`
65
+ - multiple update hunks with `@@` context
66
+ - EOF-oriented insertions
67
+
68
+ Before writing, it parses the complete patch, resolves paths, rejects workspace escapes and unsafe symlink/parent paths, reads all source files, and computes every update in memory. Only after preflight succeeds does it mutate files. It returns per-file diff details and reports committed paths if a later filesystem operation fails.
69
+
70
+ ## Configuration
71
+
72
+ The extension defaults to a model-ID allowlist in `config.json`: `"gpt-5.6-*"`. Provider does not affect routing. Each entry is a `*` glob, so this matches every `gpt-5.6-` variant. The route additionally requires:
73
+
74
+ ```text
75
+ model.api = openai-responses or openai-codex-responses
76
+ ```
77
+
78
+ The effective routing rule is:
79
+
80
+ ```text
81
+ settings.enabled
82
+ and model.api in ["openai-responses", "openai-codex-responses"]
83
+ and model.id matches at least one settings.models pattern
84
+ ```
85
+
86
+ Patterns are case-sensitive, match the complete model ID, and support `*` as the wildcard. Provider name is intentionally ignored.
87
+
88
+ ### Add model rules
89
+
90
+ Edit `config.json` and add validated model-ID patterns:
91
+
92
+ ```json
93
+ {
94
+ "enabled": true,
95
+ "models": [
96
+ "gpt-5.6-*",
97
+ "gpt-5.7-*",
98
+ "my-codex-model"
99
+ ]
100
+ }
101
+ ```
102
+
103
+ Examples:
104
+
105
+ | Pattern | Matches | Does not match |
106
+ |---|---|---|
107
+ | `gpt-5.6-*` | `gpt-5.6-sol`, `gpt-5.6-terra` | `gpt-5.7-sol` |
108
+ | `gpt-*-codex` | `gpt-5.6-codex` | `gpt-5.6-codex-mini` |
109
+ | `my-codex-model` | exactly `my-codex-model` | `my-codex-model-v2` |
110
+
111
+ Adding a pattern only changes routing; it does not prove that a model handles the Codex protocol correctly. Validate the model before adding it to the default allowlist. A matching model on `anthropic-messages`, `openai-completions`, or another unsupported API still uses Pi's native `edit`.
112
+
113
+ Grammar support is not part of the route. When a provider does not emit Lark grammar tools, Pi falls back to a normal function tool, so `apply_patch` still runs there — only the grammar-enforced output shape is lost.
114
+
115
+ Edit `config.json` in the standalone installed package, or `packages/codex-edit/config.json` inside an installed full toolkit, only if you need to change the default allowlist. Package updates can replace local edits. Set `"enabled": false` to disable all `apply_patch` routing. The implementation keeps both tools registered for session replay and changes only the active tool set on `session_start` and `model_select`.
116
+
117
+ ## Documentation
118
+
119
+ - `summary.md`: benchmark summary and decision record.
120
+ - `codex-edit-explainer.html`: self-contained interactive architecture and configuration explainer.
121
+ - `TESTING.md`: deterministic test flow, regression fixtures, and latest baseline.
122
+
123
+ The benchmark found equal final correctness in the fair 20-case comparison, with first editor-call success improving from 85% to 95%. Overall tool-call count and cache-inclusive token use were effectively unchanged, so the route stays limited to the GPT/Codex allowlist rather than replacing `edit` universally.
@@ -0,0 +1,20 @@
1
+ # Codex Edit 测试方案
2
+
3
+ ## 流程
4
+
5
+ 1. `npm --prefix packages/codex-edit test`:运行 parser、matcher、路由和包结构测试。
6
+ 2. `pi -ne -e ./packages/codex-edit/extensions/codex-edit.ts`:加载权威入口;独立子包与根包 manifest 都指向该实现。
7
+ 3. `npm pack ./packages/codex-edit --dry-run`:检查独立 tarball。
8
+ 4. 确认 README 的协议示例、真实 parser 运行输出 `docs/screenshot.png` 和 `docs/demo.gif` 随包发布。
9
+
10
+ ## 用例数据
11
+
12
+ - Add/Delete/Update/Move、多 hunk、BOM、CRLF、EOF 和 workspace escape。
13
+ - 与 Codex 一致的 cursor 顺序匹配:重复 HTML section 与 Markdown `---`。
14
+ - 多文件结构重构 patch 的解析,以及重复目标拒绝。
15
+ - `gpt-5.6-*` glob、Responses API 路由和 disabled 配置。
16
+ - 自定义精确规则与 `*` glob;相同 model ID 在不支持的 API 上必须回退到 Pi `edit`。
17
+
18
+ ## 最近验证结果
19
+
20
+ 当前基线为 12 个确定性测试全部通过;真实触发过 `Ambiguous ... regions found` 的重复 HTML/Markdown 形态已经加入回归集。发布前必须验证子包 tarball 和根包 tarball 都包含该入口。
@@ -135,20 +135,20 @@ footer { max-width: 1180px; margin: 0 auto; padding: 0 26px 54px; color: var(--m
135
135
  <div class="topbar-inner">
136
136
  <div class="brand">Pi <span>apply_patch</span> 方案说明</div>
137
137
  <nav class="nav" aria-label="页面导航">
138
- <a href="#answer">结论</a><a href="#architecture">架构</a><a href="#config">配置</a><a href="#principle">原理</a><a href="#evidence">数据</a>
138
+ <a href="#answer">结论</a><a href="#install">安装</a><a href="#architecture">架构</a><a href="#config">配置</a><a href="#principle">原理</a><a href="#evidence">数据</a>
139
139
  </nav>
140
140
  </div>
141
141
  </header>
142
142
  <main>
143
143
  <section class="hero-grid" id="answer">
144
144
  <div>
145
- <div class="eyebrow">GPT-5.6 / Pi / Codex-style editing</div>
145
+ <div class="eyebrow">GPT-5.6 / Pi / Codex harness apply_patch</div>
146
146
  <h1>它变好了,但改善主要在“第一次编辑能不能正确开始”。</h1>
147
- <p class="lede">公平对照下,apply_patch 没有提升最终正确率,却把 GPT-5.6 的首次编辑成功率从 85% 提到 95%,并消除了观察到的编辑协议失败。它适合作为 GPT/Codex 模型的候选工具,不应无条件替代所有模型的 edit。</p>
147
+ <p class="lede">这个扩展让选中的 GPT 模型在 Pi 中使用与 Codex harness 相同的 apply_patch 协议;其他模型仍使用 Pi 原生 edit。公平对照下,GPT-5.6 的首次编辑成功率从 85% 提到 95%,并消除了观察到的编辑协议失败。</p>
148
148
  </div>
149
149
  <aside class="signal">
150
- <strong>当前包状态:可安装,按模型路由</strong>
151
- <p>这个 npm 包包含真正的 apply_patch extension。安装后,GPT/Codex 模型按配置使用 apply_patch,其他模型继续使用 edit。</p>
150
+ <strong>当前状态:根包内置,也可单独安装</strong>
151
+ <p><code>@maxiaochao/pi-toolkit</code> 默认包含本扩展;只需要 apply_patch 时可安装 <code>@maxiaochao/pi-codex-edit</code>。两种方式使用同一份实现。</p>
152
152
  </aside>
153
153
  </section>
154
154
 
@@ -161,6 +161,26 @@ footer { max-width: 1180px; margin: 0 auto; padding: 0 26px 54px; color: var(--m
161
161
  </div>
162
162
  </section>
163
163
 
164
+ <section class="section" id="install">
165
+ <div class="section-head"><div><div class="eyebrow">Distribution</div><h2>完整工具包与独立子包二选一</h2></div><p class="section-note">同时安装没有额外收益,还可能重复注册 <code>apply_patch</code>。</p></div>
166
+ <div class="explain">
167
+ <article class="panel">
168
+ <span class="badge green">完整工具包</span>
169
+ <h3 style="margin-top:16px">随 pi-toolkit 默认加载</h3>
170
+ <div class="code-wrap"><button class="copy" data-copy="toolkit-install">复制</button><pre id="toolkit-install">pi install npm:@maxiaochao/pi-toolkit
171
+ pi update npm:@maxiaochao/pi-toolkit</pre></div>
172
+ <p>根 manifest 直接加载 <code>packages/codex-edit/extensions/codex-edit.ts</code>。只有发布了新的 toolkit 版本,根包用户才会收到 Codex Edit 更新。</p>
173
+ </article>
174
+ <article class="panel">
175
+ <span class="badge amber">独立子包</span>
176
+ <h3 style="margin-top:16px">只安装 Codex Edit</h3>
177
+ <div class="code-wrap"><button class="copy" data-copy="child-install">复制</button><pre id="child-install">pi install npm:@maxiaochao/pi-codex-edit
178
+ pi update npm:@maxiaochao/pi-codex-edit</pre></div>
179
+ <p>子包拥有独立版本和发布流程。只发布子包不会更新已安装的 toolkit;实现或配置变更需要分别发布两个 npm 包。</p>
180
+ </article>
181
+ </div>
182
+ </section>
183
+
164
184
  <section class="section">
165
185
  <div class="section-head"><div><div class="eyebrow">先解释指标</div><h2>“编辑协议失败:1 → 0”到底是什么?</h2></div><p class="section-note">这是本次 benchmark 自定义的分类名,不是 HTTP、Responses API 或 custom tool 协议错误。</p></div>
166
186
  <div class="explain">
@@ -187,7 +207,7 @@ footer { max-width: 1180px; margin: 0 auto; padding: 0 26px 54px; color: var(--m
187
207
  <div class="arch-row">
188
208
  <div class="arch-node"><small>01 · Model state</small><strong>当前模型配置</strong><p>ctx.model.provider、api、id、compat 能力标记。</p></div>
189
209
  <div class="arch-arrow">→</div>
190
- <div class="arch-node"><small>02 · Routing</small><strong>syncEditorTools()</strong><p>命中的 GPT/Codex 模型启用 apply_patch;其他模型启用 edit。</p></div>
210
+ <div class="arch-node"><small>02 · Routing</small><strong>syncEditorTools()</strong><p>ID glob 和 Responses API 均命中时启用 apply_patch;其他模型启用 Pi edit。</p></div>
191
211
  <div class="arch-arrow">→</div>
192
212
  <div class="arch-node"><small>03 · Provider</small><strong>Responses serializer</strong><p>Grammar 支持时序列化为 type=custom + Lark definition,否则回退为普通 function tool。</p></div>
193
213
  <div class="arch-arrow">→</div>
@@ -210,51 +230,52 @@ footer { max-width: 1180px; margin: 0 auto; padding: 0 26px 54px; color: var(--m
210
230
  </section>
211
231
 
212
232
  <section class="section" id="config">
213
- <div class="section-head"><div><div class="eyebrow">Configuration</div><h2>当前配置与可选扩展</h2></div><p class="section-note">默认只为已稳定验证的 Sol High 启用;Luna 的性能结果存在长尾波动,需要显式 opt-in。</p></div>
233
+ <div class="section-head"><div><div class="eyebrow">Configuration</div><h2>当前模型路由</h2></div><p class="section-note">默认覆盖 Responses API 上的全部 <code>gpt-5.6-*</code> 变体;provider 不参与匹配。</p></div>
214
234
  <div class="tabs" role="tablist" aria-label="配置视图">
215
235
  <button class="tab" role="tab" aria-selected="true" aria-controls="config-current" id="tab-current">当前实际配置</button>
216
- <button class="tab" role="tab" aria-selected="false" aria-controls="config-recommended" id="tab-recommended">Luna 显式启用</button>
236
+ <button class="tab" role="tab" aria-selected="false" aria-controls="config-recommended" id="tab-recommended">添加模型</button>
217
237
  <button class="tab" role="tab" aria-selected="false" aria-controls="config-route" id="tab-route">路由规则</button>
218
238
  </div>
219
239
  <div class="tab-panel active" id="config-current" role="tabpanel" aria-labelledby="tab-current">
220
240
  <div class="code-wrap"><button class="copy" data-copy="current-config">复制</button><pre id="current-config">{
221
241
  "enabled": true,
222
242
  "models": [
223
- { "provider": "pudu-openai-proxy", "model": "gpt-5.6-sol" }
243
+ "gpt-5.6-*"
224
244
  ]
225
245
  }</pre></div>
226
- <div class="callout">models.json 中的 provider API key 不在页面展示。建议使用环境变量或 Pi auth 存储,并轮换已经出现在配置文件中的 key。</div>
246
+ <div class="callout">路由还要求 <code>model.api</code> 为 <code>openai-responses</code> 或 <code>openai-codex-responses</code>。grammar 不可用时 Pi 回退为普通 function tool。</div>
227
247
  </div>
228
248
  <div class="tab-panel" id="config-recommended" role="tabpanel" aria-labelledby="tab-recommended">
229
249
  <div class="code-wrap"><button class="copy" data-copy="recommended-config">复制</button><pre id="recommended-config">{
230
250
  "enabled": true,
231
251
  "models": [
232
- { "provider": "pudu-openai-proxy", "model": "gpt-5.6-sol" },
233
- { "provider": "pudu-openai-proxy", "model": "gpt-5.6-luna" }
252
+ "gpt-5.6-*",
253
+ "gpt-5.7-*",
254
+ "my-codex-model"
234
255
  ]
235
256
  }</pre></div>
236
- <p>Luna High 的有效重测中,两组都 20/20 正常结束,最终正确率都是 18/20;首次编辑成功率为 edit 90%、apply_patch 100%。按 17 个两组都正确的配对样本,apply_patch 中位总耗时多 2.3s;按全部 20 个配对样本,中位多 1.2s,9 例更快、11 例更慢。因此 Luna 有协议适配收益,但没有稳定的总耗时优势,当前不默认启用。</p>
257
+ <p>每项按完整 <code>model.id</code> 做区分大小写的匹配,<code>*</code> 是通配符。精确 ID 也可以直接作为规则。添加规则只改变路由,不代表模型已经通过协议验证;未命中规则的模型继续使用 Pi 原生 <code>edit</code>。</p>
237
258
  </div>
238
259
  <div class="tab-panel" id="config-route" role="tabpanel" aria-labelledby="tab-route">
239
260
  <div class="code-wrap"><button class="copy" data-copy="route-rule">复制</button><pre id="route-rule">model = ctx.model
240
261
 
241
262
  if enabled
242
263
  and model.api in ["openai-responses", "openai-codex-responses"]
243
- and provider/model matches allowlist:
264
+ and model.id matches any models glob:
244
265
  active = ["apply_patch"]
245
266
  else:
246
267
  active = ["edit"]
247
268
 
248
269
  # Preserve all unrelated active tools.
249
270
  # Register both tools permanently for session replay.</pre></div>
250
- <p>切换发生在 <code>session_start</code> 和 <code>model_select</code>。不重新定义同名工具,只改变 active tool 集合。</p>
271
+ <p><code>provider</code> 不参与匹配;即使 ID 命中,其他 API 上的模型仍使用 Pi <code>edit</code>。切换发生在 <code>session_start</code> 和 <code>model_select</code>。独立包修改 <code>config.json</code>,完整 toolkit 修改 <code>packages/codex-edit/config.json</code>,但包更新可能覆盖本地配置。</p>
251
272
  </div>
252
273
  </section>
253
274
 
254
275
  <section class="section" id="principle">
255
276
  <div class="section-head"><div><div class="eyebrow">Design principles</div><h2>这个工具的三个核心原则</h2></div></div>
256
277
  <div class="principles">
257
- <article class="principle"><h3>模型协议对齐</h3><p>GPT/Codex 模型不再被要求适应 Pi 自定义的 <code>oldText/newText</code> JSON;它看到原生的 <code>apply_patch</code> 名称和 patch grammar。</p></article>
278
+ <article class="principle"><h3>模型协议对齐</h3><p>选中的 GPT 模型不再被要求适应 Pi 自定义的 <code>oldText/newText</code> JSON;它看到与 Codex harness 相同的 <code>apply_patch</code> 名称和 patch grammar。</p></article>
258
279
  <article class="principle"><h3>执行端独立防护</h3><p>grammar 不是安全策略。执行端仍要做 workspace 边界、符号链接、目录、覆盖冲突、权限和取消检查。</p></article>
259
280
  <article class="principle"><h3>按能力选择,不靠 AGENTS 对抗</h3><p>不在 AGENTS 中要求模型“不要使用 apply_patch”。扩展只让模型看到适合自己的一个编辑工具,避免两套协议同时暴露。</p></article>
260
281
  </div>
@@ -276,7 +297,7 @@ else:
276
297
  </tbody>
277
298
  </table>
278
299
  <div class="panel" style="margin-top:18px">
279
- <div class="status"><i></i>最终判断:候选实现保留,默认自动启用暂不打开</div>
300
+ <div class="status"><i></i>最终判断:已投入使用,但只对 allowlist 中的 GPT/Codex 模型路由 apply_patch</div>
280
301
  <p>本页的耗时比较优先使用同一 fixture 的配对差值,避免两组成功样本构成不同造成中位数偏差。runner 的 180s 进程上限是删失边界,不是 API timeout;本次有效 Luna 重测没有触发该上限。</p>
281
302
  </div>
282
303
  </section>
@@ -284,9 +305,9 @@ else:
284
305
  <section class="section">
285
306
  <div class="section-head"><div><div class="eyebrow">Files</div><h2>实现位置</h2></div></div>
286
307
  <div class="panel">
287
- <p><code>extensions/codex-edit/index.ts</code>:工具注册、执行、防护、renderer 和模型路由。</p>
288
- <p><code>extensions/codex-edit/parser.ts</code>:patch grammar 对应的 parser、hunk matcher 和 workspace path resolver。</p>
289
- <p><code>extensions/codex-edit/tests/parser.test.mjs</code>:6 个纯函数/文件操作测试,当前全部通过。</p>
308
+ <p><code>packages/codex-edit/extensions/codex-edit.ts</code>:工具注册、执行、防护、renderer 和模型路由。</p>
309
+ <p><code>packages/codex-edit/src/parser.ts</code>:patch grammar 对应的 parser、hunk matcher 和 workspace path resolver。</p>
310
+ <p><code>packages/codex-edit/tests/parser.test.mjs</code>:12 个 parser、matcher、路径与结构重构测试,当前全部通过。</p>
290
311
  <p><code>/home/maxiaochao/.pi/agent/evaluations/gpt-edit-benchmark/report-final.md</code>:最终 benchmark 报告。</p>
291
312
  </div>
292
313
  </section>
@@ -13,11 +13,11 @@ import {
13
13
  applyUpdate,
14
14
  parsePatch,
15
15
  resolveWorkspacePath,
16
- } from "./parser.ts";
17
- import { modelIsAllowed } from "./model-routing.ts";
16
+ } from "../src/parser.ts";
17
+ import { modelIsAllowed } from "../src/model-routing.ts";
18
18
 
19
19
  const editorToolNames = new Set(["edit", "apply_patch"]);
20
- const settingsUrl = new URL("./config.json", import.meta.url);
20
+ const settingsUrl = new URL("../config.json", import.meta.url);
21
21
 
22
22
  async function loadSettings() {
23
23
  try {
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@maxiaochao/pi-codex-edit",
3
+ "version": "0.1.7",
4
+ "description": "Pi extension that exposes Codex-style apply_patch for GPT/Codex models with model-aware edit fallback.",
5
+ "type": "module",
6
+ "keywords": [
7
+ "pi-package",
8
+ "pi-extension",
9
+ "codex",
10
+ "gpt-5",
11
+ "apply-patch"
12
+ ],
13
+ "files": [
14
+ "extensions",
15
+ "src",
16
+ "docs",
17
+ "config.json",
18
+ "summary.md",
19
+ "codex-edit-explainer.html",
20
+ "README.md",
21
+ "TESTING.md",
22
+ "CHANGELOG.md",
23
+ "package.json"
24
+ ],
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/chaochaoxiaochao/pi-toolkit.git",
28
+ "directory": "packages/codex-edit"
29
+ },
30
+ "pi": {
31
+ "extensions": [
32
+ "./extensions/codex-edit.ts"
33
+ ]
34
+ },
35
+ "peerDependencies": {
36
+ "@earendil-works/pi-ai": "*",
37
+ "@earendil-works/pi-coding-agent": "*",
38
+ "@earendil-works/pi-tui": "*",
39
+ "typebox": "*"
40
+ },
41
+ "scripts": {
42
+ "test": "node --experimental-strip-types --test tests/*.test.mjs",
43
+ "load-test": "pi -ne -e ./extensions/codex-edit.ts"
44
+ },
45
+ "publishConfig": {
46
+ "access": "public"
47
+ }
48
+ }
@@ -115,8 +115,7 @@ function locate(lines, pattern, start, eof, label) {
115
115
  ];
116
116
  for (const compare of comparisons) {
117
117
  const candidates = findCandidates(lines, pattern, start, eof, compare);
118
- if (candidates.length === 1) return candidates[0];
119
- if (candidates.length > 1) throw new Error(`Ambiguous ${label || "patch"} match: ${candidates.length} regions found`);
118
+ if (candidates.length > 0) return candidates[0];
120
119
  }
121
120
  throw new Error(`Failed to find expected lines${label ? ` in ${label}` : ""}: ${pattern.join("\\n")}`);
122
121
  }
@@ -51,6 +51,6 @@ The probe conditions above reflect v0.1.5, when the route still required grammar
51
51
 
52
52
  ## Decision
53
53
 
54
- No-go for universal enablement in the original benchmark environment. The evidence did not justify routing every model through `apply_patch` or making it the default editor for the whole Pi workflow. The shipped route is therefore a model-ID allowlist (`gpt-5.6-*` plus an `openai-responses`/`openai-codex-responses` API check) that keeps `edit` as the fallback everywhere else. The implementation now ships inside `@maxiaochao/pi-toolkit` instead of as a standalone opt-in package, but the allowlist — not the packaging — is what bounds the change: for a non-allowlisted model the extension only re-syncs an already-active editor tool and otherwise returns early.
54
+ No-go for universal routing in the original benchmark environment. The evidence did not justify routing every model through `apply_patch` or making it the default editor for the whole Pi workflow. Ship the extension both with `@maxiaochao/pi-toolkit` and as the standalone `@maxiaochao/pi-codex-edit` child package, while bounding activation with a model-ID allowlist (`gpt-5.6-*` plus an `openai-responses`/`openai-codex-responses` API check) and keeping `edit` as the fallback everywhere else.
55
55
 
56
56
  The production route was validated with `pudu-openai-proxy/gpt-5.6-sol:high` and `pudu-openai-proxy/gpt-5.6-luna:high`. Sol's 20-case fair run improved first editor-call success from 85% to 95% with equal final correctness. A corrected Luna rerun used the fully qualified `pudu-openai-proxy/gpt-5.6-luna:high` in both arms, with 20/20 normally completed runs in each arm: both reached 18/20 final correctness, while first editor-call success was 18/20 for `edit` and 20/20 for `apply_patch`. Among the 17 cases correct in both arms, the paired median total-duration delta was +2.3s for `apply_patch`, with 8 cases faster and 9 slower; across all 20 paired runs, the median delta was +1.2s, with 9 cases faster and 11 slower. The measured tradeoff justified validating Sol first; the shipped `gpt-5.6-*` glob additionally enables Luna and Terra, whose gap is protocol adaptation rather than a stable duration win.
@@ -2,21 +2,23 @@ import assert from "node:assert/strict";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { dirname, join } from "node:path";
4
4
  import test from "node:test";
5
- import { modelIsAllowed } from "../model-routing.ts";
5
+ import { modelIsAllowed } from "../src/model-routing.ts";
6
6
 
7
- const extensionDir = dirname(dirname(new URL(import.meta.url).pathname));
7
+ const packageDir = dirname(dirname(new URL(import.meta.url).pathname));
8
8
 
9
- test("config is next to the extension entry file", async () => {
10
- const source = await readFile(join(extensionDir, "index.ts"), "utf8");
11
- assert.match(source, /new URL\("\.\/config\.json", import\.meta\.url\)/);
9
+ test("package points Pi at the extension and config is reachable from it", async () => {
10
+ const manifest = JSON.parse(await readFile(join(packageDir, "package.json"), "utf8"));
11
+ assert.deepEqual(manifest.pi.extensions, ["./extensions/codex-edit.ts"]);
12
+ const source = await readFile(join(packageDir, "extensions/codex-edit.ts"), "utf8");
13
+ assert.match(source, /new URL\("\.\.\/config\.json", import\.meta\.url\)/);
12
14
  assert.match(source, /model-routing\.ts/);
13
- const config = JSON.parse(await readFile(join(extensionDir, "config.json"), "utf8"));
15
+ const config = JSON.parse(await readFile(join(packageDir, "config.json"), "utf8"));
14
16
  assert.equal(config.enabled, true);
15
17
  assert.deepEqual(config.models, ["gpt-5.6-*"]);
16
18
  });
17
19
 
18
20
  test("matches allowed model IDs with globs independently of provider and grammar support", () => {
19
- const settings = { enabled: true, models: ["gpt-5.6-*"] };
21
+ const settings = { enabled: true, models: ["gpt-5.6-*", "my-codex-model"] };
20
22
  const baseModel = {
21
23
  id: "gpt-5.6-terra",
22
24
  api: "openai-codex-responses",
@@ -34,5 +36,8 @@ test("matches allowed model IDs with globs independently of provider and grammar
34
36
  );
35
37
  assert.equal(modelIsAllowed({ ...baseModel, api: "openai-completions" }, settings), false);
36
38
  assert.equal(modelIsAllowed({ ...baseModel, id: "gpt-5.5" }, settings), false);
39
+ assert.equal(modelIsAllowed({ ...baseModel, id: "my-codex-model" }, settings), true);
40
+ assert.equal(modelIsAllowed({ ...baseModel, id: "my-codex-model-v2" }, settings), false);
41
+ assert.equal(modelIsAllowed({ ...baseModel, id: "GPT-5.6-terra" }, settings), false);
37
42
  assert.equal(modelIsAllowed(baseModel, { enabled: false, models: ["gpt-5.6-*"] }), false);
38
43
  });
@@ -3,7 +3,7 @@ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import test from "node:test";
6
- import { applyUpdate, parsePatch, resolveWorkspacePath } from "../parser.ts";
6
+ import { applyUpdate, parsePatch, resolveWorkspacePath } from "../src/parser.ts";
7
7
 
8
8
  test("parses add, delete, update, and move operations", () => {
9
9
  const operations = parsePatch(`*** Begin Patch
@@ -58,14 +58,111 @@ test("supports insertion chunks and EOF matching", () => {
58
58
  assert.equal(applyUpdate("", operations[0].chunks, "file.txt"), "first\n");
59
59
  });
60
60
 
61
- test("rejects ambiguous matches and workspace escapes", () => {
61
+ test("uses the first matching region at or after the cursor", () => {
62
62
  const operations = parsePatch(`*** Begin Patch
63
63
  *** Update File: file.txt
64
64
  @@
65
65
  -value
66
66
  +changed
67
67
  *** End Patch`);
68
- assert.throws(() => applyUpdate("value\nvalue\n", operations[0].chunks, "file.txt"), /Ambiguous/);
68
+ assert.equal(applyUpdate("value\nvalue\n", operations[0].chunks, "file.txt"), "changed\nvalue\n");
69
+ });
70
+
71
+ test("uses earlier hunks to advance the cursor through repeated HTML regions", () => {
72
+ const operations = parsePatch(`*** Begin Patch
73
+ *** Update File: page.html
74
+ @@
75
+ -old
76
+ +new
77
+ @@
78
+ </section>
79
+ +<section id="inserted"></section>
80
+ <section>
81
+ *** End Patch`);
82
+ const before = `<header>
83
+ old
84
+ </header>
85
+ <section>first</section>
86
+ </section>
87
+ <section>
88
+ one
89
+ </section>
90
+ <section>
91
+ two
92
+ </section>
93
+ `;
94
+ const after = `<header>
95
+ new
96
+ </header>
97
+ <section>first</section>
98
+ </section>
99
+ <section id="inserted"></section>
100
+ <section>
101
+ one
102
+ </section>
103
+ <section>
104
+ two
105
+ </section>
106
+ `;
107
+ assert.equal(applyUpdate(before, operations[0].chunks, "page.html"), after);
108
+ });
109
+
110
+ test("applies a hunk at the first repeated Markdown separator after its cursor", () => {
111
+ const operations = parsePatch(`*** Begin Patch
112
+ *** Update File: SKILL.md
113
+ @@
114
+ -description: old
115
+ +description: new
116
+ @@
117
+ ---
118
+ +Inserted after frontmatter.
119
+ *** End Patch`);
120
+ const before = `---
121
+ name: demo
122
+ description: old
123
+ ---
124
+
125
+ # Demo
126
+
127
+ ---
128
+ Footer
129
+ `;
130
+ const after = `---
131
+ name: demo
132
+ description: new
133
+ ---
134
+ Inserted after frontmatter.
135
+
136
+ # Demo
137
+
138
+ ---
139
+ Footer
140
+ `;
141
+ assert.equal(applyUpdate(before, operations[0].chunks, "SKILL.md"), after);
142
+ });
143
+
144
+ test("parses multiple file operations used by structural refactors", () => {
145
+ const operations = parsePatch(`*** Begin Patch
146
+ *** Update File: extensions/tool.ts
147
+ *** Move to: packages/tool/extensions/tool.ts
148
+ @@
149
+ -export const value = 1;
150
+ +export const value = 2;
151
+ *** Add File: extensions/tool-wrapper.ts
152
+ +export { value } from "../packages/tool/extensions/tool.ts";
153
+ *** Delete File: extensions/tool.test.ts
154
+ *** End Patch`);
155
+ assert.deepEqual(
156
+ operations.map(({ action, path, moveTo }) => ({ action, path, moveTo })),
157
+ [
158
+ { action: "update", path: "extensions/tool.ts", moveTo: "packages/tool/extensions/tool.ts" },
159
+ { action: "add", path: "extensions/tool-wrapper.ts", moveTo: undefined },
160
+ { action: "delete", path: "extensions/tool.test.ts", moveTo: undefined },
161
+ ],
162
+ );
163
+ });
164
+
165
+ test("rejects workspace escapes", () => {
69
166
  assert.equal(resolveWorkspacePath("/tmp/project", "/tmp/project/src/a.js"), "/tmp/project/src/a.js");
70
167
  assert.throws(() => resolveWorkspacePath("/tmp/project", "/tmp/other/a.js"), /escapes/);
71
168
  });
@@ -0,0 +1,12 @@
1
+ ## 2026-09-30 - v0.1.1
2
+
3
+ - Add standalone testing documentation and real production-runner screenshot/demo media.
4
+ - Align the package manifest and root bundle with the shared workspace source.
5
+
6
+ ## 2026-09-01 - v0.1.0
7
+
8
+ - Add the `tiny_subagents` Pi tool for one focused prompt in a fresh child Pi context.
9
+ - Add package-local `worker.md` personas with model and built-in tool selection.
10
+ - Return structured process, Pi protocol, stderr, stop-reason, exit, signal, and cancellation diagnostics.
11
+ - Preserve oversized stdout, stderr, and final assistant output in private temporary artifacts.
12
+ - Stream child event activity to the TUI and expose persona, prompt, and transcript details when expanded.