@maxiaochao/pi-toolkit 0.9.3 → 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 (65) hide show
  1. package/CHANGELOG.md +8 -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/package.json +19 -10
  6. package/packages/cache-export/CHANGELOG.md +5 -0
  7. package/packages/cache-export/README.md +70 -0
  8. package/packages/cache-export/TESTING.md +17 -0
  9. package/packages/cache-export/docs/demo.gif +0 -0
  10. package/packages/cache-export/docs/screenshot.png +0 -0
  11. package/{extensions/cache-export/index.ts → packages/cache-export/extensions/cache-export.ts} +1 -1
  12. package/packages/cache-export/package.json +22 -0
  13. package/{extensions/cache-export → packages/cache-export/src}/render.ts +1 -1
  14. package/{extensions → packages}/cache-export/tests/e2e-browser.mjs +2 -2
  15. package/{extensions → packages}/cache-export/tests/run-tests.mjs +2 -2
  16. package/packages/codex-edit/CHANGELOG.md +32 -0
  17. package/packages/codex-edit/README.md +123 -0
  18. package/packages/codex-edit/TESTING.md +20 -0
  19. package/{extensions → packages}/codex-edit/codex-edit-explainer.html +41 -20
  20. package/packages/codex-edit/docs/demo.gif +0 -0
  21. package/packages/codex-edit/docs/screenshot.png +0 -0
  22. package/{extensions/codex-edit/index.ts → packages/codex-edit/extensions/codex-edit.ts} +3 -3
  23. package/packages/codex-edit/package.json +48 -0
  24. package/{extensions/codex-edit → packages/codex-edit/src}/parser.ts +1 -2
  25. package/{extensions → packages}/codex-edit/summary.md +1 -1
  26. package/{extensions → packages}/codex-edit/tests/package.test.mjs +12 -7
  27. package/{extensions → packages}/codex-edit/tests/parser.test.mjs +100 -3
  28. package/packages/tiny-subagent/CHANGELOG.md +12 -0
  29. package/packages/tiny-subagent/README.md +128 -0
  30. package/packages/tiny-subagent/TESTING.md +20 -0
  31. package/packages/tiny-subagent/agents/worker.md +9 -0
  32. package/packages/tiny-subagent/docs/demo.gif +0 -0
  33. package/packages/tiny-subagent/docs/screenshot.png +0 -0
  34. package/packages/tiny-subagent/extensions/tiny-subagent.ts +228 -0
  35. package/packages/tiny-subagent/package.json +44 -0
  36. package/packages/tiny-subagent/src/personas.ts +110 -0
  37. package/packages/tiny-subagent/src/runner.ts +319 -0
  38. package/packages/tiny-subagent/tests/run-tests.mjs +111 -0
  39. package/packages/todo/CHANGELOG.md +5 -0
  40. package/packages/todo/README.md +63 -0
  41. package/packages/todo/TESTING.md +20 -0
  42. package/packages/todo/docs/demo.gif +0 -0
  43. package/packages/todo/docs/screenshot.png +0 -0
  44. package/{extensions → packages/todo/extensions}/todo.ts +1 -1
  45. package/packages/todo/package.json +25 -0
  46. package/{extensions → packages/todo}/tests/todo-state.mjs +1 -1
  47. package/packages/worktree/CHANGELOG.md +5 -0
  48. package/packages/worktree/README.md +60 -0
  49. package/packages/worktree/TESTING.md +21 -0
  50. package/packages/worktree/docs/demo.gif +0 -0
  51. package/packages/worktree/docs/screenshot.png +0 -0
  52. package/packages/worktree/package.json +20 -0
  53. package/packages/worktree/scripts/install.mjs +26 -0
  54. package/packages/worktree/tests/install.test.mjs +25 -0
  55. package/packages/worktree/tests/run-tests.sh +46 -0
  56. package/scripts/install-agents.mjs +8 -18
  57. package/extensions/codex-edit/README.md +0 -53
  58. /package/extensions/{btw-scroll.ts → btw/scroll.ts} +0 -0
  59. /package/{extensions/cache-export → packages/cache-export/src}/tau-assets.ts +0 -0
  60. /package/{extensions → packages}/codex-edit/config.json +0 -0
  61. /package/{extensions/codex-edit → packages/codex-edit/src}/model-routing.ts +0 -0
  62. /package/{extensions → packages/todo/src}/todo-state.ts +0 -0
  63. /package/{bin → packages/worktree/bin}/pi-worktree +0 -0
  64. /package/{bin → packages/worktree/bin}/pi-worktree-completion.bash +0 -0
  65. /package/{skills → packages/worktree/skills}/pi-worktree/SKILL.md +0 -0
@@ -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.
@@ -0,0 +1,128 @@
1
+ # @maxiaochao/pi-tiny-subagent
2
+
3
+ A small Pi extension that runs one fresh Pi child process for one focused prompt. It does not use the `pi-subagents` package and does not inherit the parent conversation or extensions.
4
+
5
+ ![Tiny Subagent production runner screenshot](docs/screenshot.png)
6
+
7
+ ## Why this package exists
8
+
9
+ This is the toolkit's deliberately small subagent model: one persona, one focused prompt, one isolated child context. It avoids a general orchestration framework and keeps child transcript noise out of the parent context while preserving process and protocol diagnostics.
10
+
11
+ ## Demo
12
+
13
+ ![Tiny Subagent demo](docs/demo.gif)
14
+
15
+ ```text
16
+ tiny_subagents({ action: "list" })
17
+ tiny_subagents({
18
+ agent: "worker",
19
+ prompt: "Inspect the authentication flow and summarize the risks"
20
+ })
21
+ ```
22
+
23
+ The child receives package-local persona instructions, selected built-in tools, and a fresh context; the parent receives the focused result or structured failure details.
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ pi install npm:@maxiaochao/pi-tiny-subagent
29
+ ```
30
+
31
+ The full `@maxiaochao/pi-toolkit` package also bundles this extension. Install the full toolkit or this standalone child package, not both.
32
+
33
+ Update the distribution you installed:
34
+
35
+ ```bash
36
+ pi update npm:@maxiaochao/pi-toolkit # bundled copy
37
+ pi update npm:@maxiaochao/pi-tiny-subagent # standalone child
38
+ ```
39
+
40
+ The versions are independent. Publishing the child does not update an already published toolkit version.
41
+
42
+ The package registers one tool: `tiny_subagents`.
43
+
44
+ ## Personas
45
+
46
+ The default `agents/worker.md` has this shape:
47
+
48
+ ```md
49
+ ---
50
+ name: worker
51
+ description: General-purpose worker for one focused task
52
+ tools: read, bash, edit, write, grep, find, ls
53
+ ---
54
+
55
+ You are a focused worker subagent.
56
+ ```
57
+
58
+ The supported frontmatter fields are:
59
+
60
+ - `name` (required): Persona name passed in the `agent` field.
61
+ - `description` (required): Short description returned by `action: "list"` so the parent model can choose a persona.
62
+ - `model` (optional): Default child model, such as `provider/model:high`. A call-level `model` overrides it; if both are absent, the child inherits the current parent-session model.
63
+ - `tools` (optional): Comma-separated allowlist of Pi built-in tools. Supported values are `read`, `bash`, `edit`, `write`, `grep`, `find`, `ls`, and `powershell` on Windows. Extension tools such as `contact_supervisor` are not allowed. If omitted, the child uses Pi's default built-in tools.
64
+
65
+ For example, a read-only reviewer persona can restrict its tools and select a default model:
66
+
67
+ ```md
68
+ ---
69
+ name: reviewer
70
+ description: Review code and report correctness risks without modifying files
71
+ model: provider/model:high
72
+ tools: read, grep, find, ls
73
+ ---
74
+
75
+ Review the assigned code. Do not edit files.
76
+ ```
77
+
78
+ Persona files are loaded only from this package's `agents/*.md`; they do not share configuration with `pi-subagents`.
79
+
80
+ Before execution, the parent model should discover the package-local personas:
81
+
82
+ ```text
83
+ tiny_subagents({ action: "list" })
84
+ ```
85
+
86
+ It can then match the task to a persona by name or description and execute without `action`:
87
+
88
+ ```text
89
+ tiny_subagents({
90
+ agent: "worker",
91
+ prompt: "Inspect the authentication flow and summarize the risks"
92
+ })
93
+ ```
94
+
95
+ ## Execution
96
+
97
+ The child is started in the current working directory with a fresh context:
98
+
99
+ ```text
100
+ pi --mode json --print --no-session --no-extensions --no-skills
101
+ ```
102
+
103
+ The selected persona body is passed as an additional system prompt. Only built-in Pi tools can be enabled by `tools` in the persona file.
104
+
105
+ ## Live output
106
+
107
+ `tiny_subagents` sends a TUI update when the child starts and after each child `message_end` or `tool_result_end` event. While the child is running, the collapsed row shows the latest activity.
108
+
109
+ Press Pi's `Ctrl+O` tool-output expansion key to inspect the current child execution. The expanded `tiny_subagents` view shows:
110
+
111
+ - selected persona name and description
112
+ - selected model
113
+ - the task prompt
114
+ - the persona system prompt
115
+ - child assistant tool calls, tool results, and assistant text
116
+ - the final output and errors
117
+
118
+ This uses Pi's standard tool-output expansion state, so `Ctrl+O` also changes the expanded/collapsed display state of other tool rows. The added content and event tracking are local to `tiny_subagents`; they do not change other tools or the parent model context.
119
+
120
+ ## Errors and large output
121
+
122
+ Successful calls return the final assistant text. Failures return the Pi error message, stop reason, exit code, signal, stderr, protocol errors, and output produced before failure. Inline output is limited to 16 KiB. If stdout, stderr, or the final answer exceeds that size, the complete files are retained in a private temporary artifact directory and its path, byte counts, and `cleanup: "remove directory when no longer needed"` instruction are returned. Small-result temporary directories are removed automatically.
123
+
124
+ The child uses the same Pi installation, provider configuration, model catalog, and authentication environment as the parent process. The package only selects the model; it does not define providers or credentials.
125
+
126
+ ## Development and tests
127
+
128
+ The authoritative implementation is under `extensions/` and `src/`. See [TESTING.md](TESTING.md) for the test strategy, fixture cases, and latest baseline.
@@ -0,0 +1,20 @@
1
+ # Tiny Subagent test strategy
2
+
3
+ ## Flow
4
+
5
+ 1. `npm --prefix packages/tiny-subagent test` runs deterministic process and persona tests.
6
+ 2. `pi -ne -ns --no-session -e ./packages/tiny-subagent/extensions/tiny-subagent.ts` verifies extension loading.
7
+ 3. `npm pack ./packages/tiny-subagent --dry-run` verifies package contents.
8
+ 4. Verify the README example and the production-runner media `docs/screenshot.png` and `docs/demo.gif` are present in the tarball.
9
+
10
+ ## Cases
11
+
12
+ - Resolve package-local personas and validate frontmatter.
13
+ - Start an isolated child Pi process with selected model and tools.
14
+ - Stream child activity while keeping it outside the parent conversation.
15
+ - Preserve detailed process/protocol failures and oversized artifacts.
16
+ - Render collapsed and expanded tool output.
17
+
18
+ ## Latest baseline
19
+
20
+ All deterministic child-process and persona cases pass. Release validation also requires the extension load check.
@@ -0,0 +1,9 @@
1
+ ---
2
+ name: worker
3
+ description: General-purpose worker for one focused task
4
+ tools: read, bash, edit, write, grep, find, ls
5
+ ---
6
+
7
+ You are a focused worker subagent.
8
+
9
+ Work only on the task in the user prompt. Inspect the current project before making claims. Use the available built-in tools when needed. Do not delegate to another agent. Finish with a concise result, including changes made, validation performed, and any remaining risks.