@maxiaochao/pi-toolkit 0.8.2 → 0.9.1

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/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 2026-09-30 - v0.9.1
2
+
3
+ - redesign the todo lifecycle with plan replacement, cancellation, automatic advancement, active-only widget rendering, concise model guidance, and deterministic state tests
4
+
5
+ ## 2026-09-30 - v0.9.0
6
+
7
+ - merge the Codex-style apply_patch extension into the toolkit (from the standalone @maxiaochao/pi-codex-edit v0.1.6): it now ships as extensions/codex-edit/ registered in pi.extensions, its parser and model-routing tests join npm test, and the separate package, release script and publish workflow are removed
8
+
1
9
  ## 2026-09-24 - v0.8.2
2
10
 
3
11
  - tapd skill: document the safe attachment-download flow for the short-lived signed URLs (re-fetch the URL for every attempt, download to .part with curl --fail --location --retry 3, reject text/error bodies before resuming, accept only 206 with a matching Content-Range, then verify size and zip integrity before renaming); it is a local addition on top of the `tapd skill init` output, recorded in AGENTS.md
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # pi-toolkit
2
2
 
3
- Personal Pi toolkit with the `/todos`, `/btw`, and `/cache_export` extensions, bundled agent skills including Chrome/Chromium automation, the Night Owl theme, and the `pi-worktree` CLI.
3
+ Personal Pi toolkit with the `/todos`, `/btw`, `/cache_export`, and `apply_patch` extensions, bundled agent skills including Chrome/Chromium automation, the Night Owl theme, and the `pi-worktree` CLI.
4
4
 
5
5
  ## Install
6
6
 
@@ -38,6 +38,8 @@ To synchronize the file directly from a checkout instead:
38
38
  bash scripts/install.sh
39
39
  ```
40
40
 
41
+ The `todo` tool maintains a session checklist for explicit plans and multi-step work. It automatically keeps one task active and advances the next queued task; `replace` starts a clean plan after a change of direction, while `cancelled` records obsolete work honestly. The persistent widget shows active work only, and `/todos` shows active and closed tasks in the current plan.
42
+
41
43
  The `/btw` side chat is a separate agent session. It can inspect the main conversation and repository, and its thread stays out of the main context until you close it and choose to inject a summary. Use `/btw` to open it or `/btw <question>` to ask directly. While the BTW overlay has focus, use `Up` / `Down` to scroll its transcript one line at a time and `PageUp` / `PageDown` to move by one transcript page.
42
44
 
43
45
  Use `/cache_export [path]` to write an interactive per-request cache dashboard. It defaults to the latest Context segment for current-state diagnosis, and the View selector can show any earlier segment or all session history. Compaction and model changes both break context charts into segments; cache-hit accumulation also resets at model changes, while TTL and other inferred interruptions remain diagnostic events rather than segment boundaries.
@@ -87,6 +89,8 @@ The bundled `pdlog` skill decompresses and reads Pudu `.pdlog` proprietary log f
87
89
 
88
90
  The bundled `herdr` skill (from [herdrdev/herdr](https://github.com/herdrdev/herdr) at tag `v0.9.1` with the skill name lowercased, Apache-2.0) drives Herdr, a terminal multiplexer for coding agents: inspecting workspaces, tabs, and panes, starting and prompting other agents, running commands in background panes without stealing focus, reading pane output, and waiting on agent lifecycle states. It activates only when you explicitly mention Herdr, and only when the agent itself runs inside a Herdr pane (`HERDR_ENV=1`); outside a pane it refuses to control the session and stops.
89
91
 
92
+ The bundled `agent-team` skill runs cross-vendor adversarial review: the current session acts as manager, and the roster in `skills/agent-team/models.json` (one auditor, one replicator, one adversary on different vendors) is started as long-lived herdr panes that get re-prompted round after round until no pane can produce a new finding.
93
+
90
94
  The bundled `tapd` skill documents the third-party `tapd` CLI ([studyzy/tapd-ai-cli](https://github.com/studyzy/tapd-ai-cli)) for TAPD: requirements, defects, tasks, iterations, wikis, comments, attachments, timesheets, and workflow status maps, including the `--filter` advanced query syntax. It is the CLI's own command reference, so refresh the file with `tapd skill init` when the CLI changes.
91
95
 
92
96
  The bundled `calldiff` skill (copied verbatim from [tanishqkancharla/calldiff](https://github.com/tanishqkancharla/calldiff), MIT, pinned to a `main` commit because upstream has no release tags) teaches call-stack diffs across git commits: `diff` shows which callees appeared, disappeared, or moved under an entrypoint, `tree` prints one call tree, and `reach` finds call paths between two symbols, all with `--entry`/`--file` entrypoint selection, `--locs` source locations, and `--format json` for agents.
@@ -108,21 +112,13 @@ pi update npm:@maxiaochao/pi-tiny-subagent
108
112
 
109
113
  See [packages/tiny-subagent/README.md](packages/tiny-subagent/README.md) for personas, model selection, child isolation, live transcript rendering, and diagnostics.
110
114
 
111
- ## Codex Edit
112
-
113
- The Codex edit package is a separate npm package. Install it when you want GPT/Codex models to use the freeform `apply_patch` protocol while other models keep Pi's native `edit` tool:
114
-
115
- ```bash
116
- pi install npm:@maxiaochao/pi-codex-edit
117
- ```
115
+ ## Codex Edit (apply_patch)
118
116
 
119
- It includes the extension implementation plus the benchmark summary and interactive architecture explainer. Its versions, release workflow, and tag are independent from the root package and tiny subagent:
117
+ The `apply_patch` extension ships with the toolkit as part of `pi.extensions`. It activates Codex's freeform `apply_patch` protocol for GPT/Codex models while other models keep Pi's native `edit` tool. Routing is by model ID (`gpt-5.6-*`) plus API (`openai-responses` or `openai-codex-responses`); provider does not matter. For every other model the extension only re-syncs an already-active editor tool, so it is a no-op.
120
118
 
121
- ```bash
122
- pi update npm:@maxiaochao/pi-codex-edit
123
- ```
119
+ Because it ships with the toolkit, there is nothing extra to install or update — `pi update npm:@maxiaochao/pi-toolkit` covers it. The model allowlist lives in `extensions/codex-edit/config.json` inside the installed package; edit it there only if you need a different allowlist.
124
120
 
125
- See [packages/codex-edit/README.md](packages/codex-edit/README.md) for the routing rules and [packages/codex-edit/summary.md](packages/codex-edit/summary.md) for the measured tradeoffs.
121
+ See [extensions/codex-edit/README.md](extensions/codex-edit/README.md) for the routing rules and [extensions/codex-edit/summary.md](extensions/codex-edit/summary.md) for the measured tradeoffs, including why it stays limited to the `gpt-5.6-*` allowlist instead of replacing `edit` everywhere.
126
122
 
127
123
  ## Development
128
124
 
@@ -0,0 +1,53 @@
1
+ # Codex Edit (apply_patch)
2
+
3
+ A Pi extension bundled with `@maxiaochao/pi-toolkit` that exposes Codex-style `apply_patch` for GPT/Codex models and keeps Pi's native `edit` tool for other models.
4
+
5
+ ## Install
6
+
7
+ Bundled with the toolkit; install or update the root package and the extension comes along:
8
+
9
+ ```bash
10
+ pi install npm:@maxiaochao/pi-toolkit
11
+ pi update npm:@maxiaochao/pi-toolkit
12
+ ```
13
+
14
+ The extension registers `apply_patch` and selects the editing tool from the resolved model configuration:
15
+
16
+ - Allowlisted GPT/Codex model: `apply_patch`
17
+ - Other models: Pi's native `edit`
18
+
19
+ 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`.
20
+
21
+ 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.
22
+
23
+ ## What `apply_patch` does
24
+
25
+ The tool accepts raw Codex patch text between `*** Begin Patch` and `*** End Patch`. It supports:
26
+
27
+ - `Add File`
28
+ - `Delete File`
29
+ - `Update File`
30
+ - `Move to`
31
+ - multiple update hunks with `@@` context
32
+ - EOF-oriented insertions
33
+
34
+ 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.
35
+
36
+ ## Configuration
37
+
38
+ 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:
39
+
40
+ ```text
41
+ model.api = openai-responses or openai-codex-responses
42
+ ```
43
+
44
+ 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.
45
+
46
+ Edit `extensions/codex-edit/config.json` inside the installed `@maxiaochao/pi-toolkit` package only if you need to change the default allowlist. The implementation keeps both tools registered for session replay and changes only the active tool set on `session_start` and `model_select`.
47
+
48
+ ## Documentation
49
+
50
+ - `summary.md`: benchmark summary and decision record.
51
+ - `codex-edit-explainer.html`: self-contained interactive architecture and configuration explainer.
52
+
53
+ 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,316 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Pi apply_patch 方案说明</title>
7
+ <style>
8
+ :root {
9
+ --ink: #17221f;
10
+ --muted: #65736d;
11
+ --paper: #f5f7f2;
12
+ --panel: #ffffff;
13
+ --line: #d9e1db;
14
+ --green: #0e6b50;
15
+ --green-soft: #dcefe7;
16
+ --amber: #a96518;
17
+ --amber-soft: #fff0d9;
18
+ --red: #a33c32;
19
+ --red-soft: #fde5e0;
20
+ --blue: #245f8c;
21
+ --blue-soft: #e2eff8;
22
+ --shadow: 0 12px 32px rgba(23, 34, 31, .08);
23
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
24
+ }
25
+ * { box-sizing: border-box; }
26
+ html { scroll-behavior: smooth; }
27
+ body { margin: 0; color: var(--ink); background: var(--paper); line-height: 1.55; }
28
+ button { font: inherit; }
29
+ .topbar { position: sticky; top: 0; z-index: 5; background: rgba(245,247,242,.94); border-bottom: 1px solid var(--line); backdrop-filter: blur(12px); }
30
+ .topbar-inner { max-width: 1180px; margin: auto; padding: 14px 26px; display: flex; align-items: center; justify-content: space-between; gap: 18px; }
31
+ .brand { font-weight: 800; letter-spacing: .02em; }
32
+ .brand span { color: var(--green); }
33
+ .nav { display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
34
+ .nav a { color: var(--muted); text-decoration: none; padding: 6px 10px; border-radius: 7px; font-size: 13px; }
35
+ .nav a:hover, .nav a:focus-visible { color: var(--green); background: var(--green-soft); outline: none; }
36
+ main { max-width: 1180px; margin: auto; padding: 58px 26px 90px; }
37
+ .eyebrow { color: var(--green); font-size: 12px; text-transform: uppercase; letter-spacing: .14em; font-weight: 800; }
38
+ h1 { font-size: clamp(34px, 5vw, 66px); line-height: 1.03; max-width: 860px; margin: 12px 0 18px; letter-spacing: -.035em; }
39
+ .lede { max-width: 760px; font-size: 19px; color: #42514a; margin: 0; }
40
+ .hero-grid { display: grid; grid-template-columns: 1.25fr .75fr; gap: 26px; align-items: end; }
41
+ .signal { border-left: 4px solid var(--amber); padding: 18px 20px; background: var(--amber-soft); }
42
+ .signal strong { display: block; font-size: 18px; margin-bottom: 5px; }
43
+ .signal p { margin: 0; color: #66431f; font-size: 14px; }
44
+ .section { margin-top: 70px; }
45
+ .section-head { display: flex; justify-content: space-between; align-items: end; gap: 20px; margin-bottom: 20px; }
46
+ h2 { font-size: 28px; line-height: 1.12; margin: 0; letter-spacing: -.025em; }
47
+ h3 { margin: 0 0 8px; font-size: 17px; }
48
+ .section-note { max-width: 560px; color: var(--muted); margin: 0; font-size: 14px; }
49
+ .metrics { display: grid; grid-template-columns: repeat(4, 1fr); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); background: var(--panel); }
50
+ .metric { padding: 21px 18px; border-right: 1px solid var(--line); }
51
+ .metric:last-child { border-right: 0; }
52
+ .metric .value { font-size: 31px; font-weight: 800; line-height: 1.1; }
53
+ .metric .label { color: var(--muted); font-size: 13px; margin-top: 5px; }
54
+ .metric.good .value { color: var(--green); }
55
+ .metric.neutral .value { color: var(--blue); }
56
+ .metric.warn .value { color: var(--amber); }
57
+ .explain { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
58
+ .panel { background: var(--panel); border: 1px solid var(--line); box-shadow: var(--shadow); padding: 23px; }
59
+ .panel p:last-child { margin-bottom: 0; }
60
+ .badge { display: inline-flex; align-items: center; gap: 7px; padding: 4px 9px; border-radius: 999px; font-weight: 750; font-size: 12px; }
61
+ .badge::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
62
+ .badge.amber { color: var(--amber); background: var(--amber-soft); }
63
+ .badge.green { color: var(--green); background: var(--green-soft); }
64
+ .badge.red { color: var(--red); background: var(--red-soft); }
65
+ .callout { margin-top: 16px; padding: 15px 17px; border-left: 3px solid var(--blue); background: var(--blue-soft); color: #244961; font-size: 14px; }
66
+ .callout code { color: var(--blue); }
67
+ code, pre { font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace; }
68
+ code { background: #edf1ee; padding: 2px 5px; border-radius: 4px; font-size: .92em; }
69
+ pre { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; color: #d8eee5; background: #172522; padding: 20px; border-radius: 7px; font-size: 13px; line-height: 1.65; }
70
+ .code-wrap { position: relative; }
71
+ .copy { position: absolute; right: 10px; top: 10px; border: 1px solid #527269; color: #d8eee5; background: #20352f; padding: 5px 8px; border-radius: 5px; cursor: pointer; font-size: 12px; }
72
+ .copy:hover, .copy:focus-visible { background: #2d4c42; outline: 2px solid #97cdb8; outline-offset: 2px; }
73
+ .tabs { display: flex; gap: 8px; flex-wrap: wrap; border-bottom: 1px solid var(--line); margin-bottom: 18px; }
74
+ .tab { border: 0; background: transparent; color: var(--muted); cursor: pointer; padding: 10px 3px; margin-right: 14px; border-bottom: 3px solid transparent; font-weight: 750; }
75
+ .tab[aria-selected="true"] { color: var(--green); border-color: var(--green); }
76
+ .tab:focus-visible { outline: 2px solid var(--green); outline-offset: 3px; }
77
+ .tab-panel { display: none; }
78
+ .tab-panel.active { display: block; }
79
+ .architecture { background: #e9f0eb; border: 1px solid var(--line); padding: 24px; overflow-x: auto; }
80
+ .arch-row { min-width: 900px; display: grid; grid-template-columns: 1fr 36px 1.15fr 36px 1.25fr 36px 1fr; align-items: stretch; gap: 10px; }
81
+ .arch-node { min-height: 124px; background: var(--panel); border: 1px solid #c6d4ca; padding: 16px; position: relative; }
82
+ .arch-node small { color: var(--muted); display: block; font-size: 11px; text-transform: uppercase; letter-spacing: .09em; margin-bottom: 9px; font-weight: 800; }
83
+ .arch-node strong { display: block; font-size: 16px; margin-bottom: 6px; }
84
+ .arch-node p { color: var(--muted); margin: 0; font-size: 13px; line-height: 1.4; }
85
+ .arch-arrow { display: grid; place-items: center; color: var(--green); font-size: 25px; }
86
+ .boundary { margin-top: 20px; padding: 13px 15px; border: 1px dashed #9eb5a6; color: #456453; background: rgba(255,255,255,.55); font-size: 13px; }
87
+ .flow { display: grid; grid-template-columns: repeat(6, 1fr); gap: 0; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); background: var(--panel); }
88
+ .flow-step { min-height: 150px; padding: 19px 15px 18px; border-right: 1px solid var(--line); position: relative; }
89
+ .flow-step:last-child { border-right: 0; }
90
+ .flow-number { color: var(--green); font-size: 12px; font-weight: 850; letter-spacing: .1em; }
91
+ .flow-step strong { display: block; margin: 15px 0 7px; font-size: 15px; }
92
+ .flow-step p { color: var(--muted); margin: 0; font-size: 13px; }
93
+ .flow-step::after { content: "→"; position: absolute; right: -10px; top: 57px; color: var(--green); background: var(--panel); font-size: 18px; z-index: 1; }
94
+ .flow-step:last-child::after { display: none; }
95
+ .compare { width: 100%; border-collapse: collapse; background: var(--panel); font-size: 14px; }
96
+ .compare th, .compare td { padding: 14px 16px; border-bottom: 1px solid var(--line); text-align: left; }
97
+ .compare th { background: #edf3ee; color: #405148; font-size: 12px; text-transform: uppercase; letter-spacing: .07em; }
98
+ .compare td:not(:first-child), .compare th:not(:first-child) { text-align: right; }
99
+ .compare .positive { color: var(--green); font-weight: 800; }
100
+ .compare .flat { color: var(--muted); }
101
+ .principles { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
102
+ .principle { border-top: 3px solid var(--green); background: var(--panel); padding: 18px; }
103
+ .principle:nth-child(2) { border-top-color: var(--blue); }
104
+ .principle:nth-child(3) { border-top-color: var(--amber); }
105
+ .principle p { margin: 0; color: var(--muted); font-size: 14px; }
106
+ .status { display: inline-flex; align-items: center; gap: 8px; color: var(--red); font-weight: 800; font-size: 14px; }
107
+ .status i { width: 9px; height: 9px; border-radius: 50%; background: currentColor; }
108
+ footer { max-width: 1180px; margin: 0 auto; padding: 0 26px 54px; color: var(--muted); font-size: 13px; }
109
+ @media (max-width: 820px) {
110
+ .topbar-inner { padding: 12px 17px; align-items: flex-start; flex-direction: column; }
111
+ .nav { justify-content: flex-start; }
112
+ main { padding: 40px 17px 70px; }
113
+ .hero-grid, .explain { grid-template-columns: 1fr; }
114
+ .metrics { grid-template-columns: repeat(2, 1fr); }
115
+ .metric:nth-child(2) { border-right: 0; }
116
+ .metric:nth-child(-n+2) { border-bottom: 1px solid var(--line); }
117
+ .flow { display: block; }
118
+ .flow-step { min-height: auto; border-right: 0; border-bottom: 1px solid var(--line); padding-left: 18px; }
119
+ .flow-step::after { content: "↓"; top: auto; bottom: -13px; right: 50%; }
120
+ .flow-step:last-child { border-bottom: 0; }
121
+ .principles { grid-template-columns: 1fr; }
122
+ .section-head { display: block; }
123
+ .section-note { margin-top: 10px; }
124
+ }
125
+ @media print {
126
+ .topbar { position: static; }
127
+ .nav, .copy, .tabs { display: none; }
128
+ .tab-panel { display: block !important; }
129
+ .panel, .metric, .compare, .flow, .architecture { box-shadow: none; break-inside: avoid; }
130
+ }
131
+ </style>
132
+ </head>
133
+ <body>
134
+ <header class="topbar">
135
+ <div class="topbar-inner">
136
+ <div class="brand">Pi <span>apply_patch</span> 方案说明</div>
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>
139
+ </nav>
140
+ </div>
141
+ </header>
142
+ <main>
143
+ <section class="hero-grid" id="answer">
144
+ <div>
145
+ <div class="eyebrow">GPT-5.6 / Pi / Codex-style editing</div>
146
+ <h1>它变好了,但改善主要在“第一次编辑能不能正确开始”。</h1>
147
+ <p class="lede">公平对照下,apply_patch 没有提升最终正确率,却把 GPT-5.6 的首次编辑成功率从 85% 提到 95%,并消除了观察到的编辑协议失败。它适合作为 GPT/Codex 模型的候选工具,不应无条件替代所有模型的 edit。</p>
148
+ </div>
149
+ <aside class="signal">
150
+ <strong>当前包状态:可安装,按模型路由</strong>
151
+ <p>这个 npm 包包含真正的 apply_patch extension。安装后,GPT/Codex 模型按配置使用 apply_patch,其他模型继续使用 edit。</p>
152
+ </aside>
153
+ </section>
154
+
155
+ <section class="section">
156
+ <div class="metrics" aria-label="公平 benchmark 核心指标">
157
+ <div class="metric good"><div class="value">85% → 95%</div><div class="label">首次编辑成功率</div></div>
158
+ <div class="metric neutral"><div class="value">90% = 90%</div><div class="label">最终精确正确率</div></div>
159
+ <div class="metric good"><div class="value">1 → 0</div><div class="label">编辑协议失败</div></div>
160
+ <div class="metric warn"><div class="value">−4.5%</div><div class="label">中位端到端耗时</div></div>
161
+ </div>
162
+ </section>
163
+
164
+ <section class="section">
165
+ <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
+ <div class="explain">
167
+ <article class="panel">
168
+ <span class="badge amber">A · 当前 edit</span>
169
+ <h3 style="margin-top:16px">1 次工具调用失败,但任务后来完成</h3>
170
+ <p>公平 A 组中唯一被归类为协议失败的是 <code>11-bom</code>。模型先读取了带 UTF-8 BOM 的文件,然后调用 Pi <code>edit</code>,把 BOM 放进了 <code>oldText</code>。Pi 的精确匹配没有找到这段文本,于是返回:</p>
171
+ <div class="callout"><code>Could not find the exact text in src/bom.js</code><br>old text 必须逐字匹配,包括空白和换行。</div>
172
+ <p>模型随后使用 bash 做了字节级修改,最终文件正确,所以“最终正确率”仍算成功;但“首次编辑成功率”和“编辑协议失败”仍记录了这次失败。</p>
173
+ </article>
174
+ <article class="panel">
175
+ <span class="badge green">C · apply_patch</span>
176
+ <h3 style="margin-top:16px">工具协议直接匹配模型的 Codex 先验</h3>
177
+ <p>GPT-5.6 在 C 组中以 <code>apply_patch</code> 的名字调用 custom tool,输入是原始 patch 文本,而不是嵌套的 <code>path + edits[]</code> JSON。19/20 个公平用例出现了可识别的 custom call,且没有编辑工具执行失败。</p>
178
+ <div class="callout">因此“1 → 0”表示:首次编辑环节少了一次失败调用。它不是说所有文件修改错误都消失了,也不是说 API 从不失败。</div>
179
+ <p>最终正确率仍然是 90%,因为剩余问题属于内容语义或文本格式细节,例如换行转义和引号选择。</p>
180
+ </article>
181
+ </div>
182
+ </section>
183
+
184
+ <section class="section" id="architecture">
185
+ <div class="section-head"><div><div class="eyebrow">Architecture</div><h2>工具的完整调用链</h2></div><p class="section-note">关键设计是“模型看到哪种工具”与“工具如何安全落盘”分开。</p></div>
186
+ <div class="architecture">
187
+ <div class="arch-row">
188
+ <div class="arch-node"><small>01 · Model state</small><strong>当前模型配置</strong><p>ctx.model.provider、api、id、compat 能力标记。</p></div>
189
+ <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>
191
+ <div class="arch-arrow">→</div>
192
+ <div class="arch-node"><small>03 · Provider</small><strong>Responses serializer</strong><p>Grammar 支持时序列化为 type=custom + Lark definition,否则回退为普通 function tool。</p></div>
193
+ <div class="arch-arrow">→</div>
194
+ <div class="arch-node"><small>04 · Execution</small><strong>parse → validate → apply</strong><p>完整解析、路径检查、预校验,最后才写文件。</p></div>
195
+ </div>
196
+ <div class="boundary"><strong>信任边界:</strong>grammar 只约束模型输出形状,不等于 sandbox。路径、软链接、目录、覆盖和权限必须由执行端再次检查。</div>
197
+ </div>
198
+ </section>
199
+
200
+ <section class="section">
201
+ <div class="section-head"><div><div class="eyebrow">Execution</div><h2>一次 apply_patch 如何落盘</h2></div><p class="section-note">先把 patch 变成可信的 change plan,再进入写入阶段。</p></div>
202
+ <div class="flow">
203
+ <div class="flow-step"><div class="flow-number">01 / RECEIVE</div><strong>接收原始 patch</strong><p>模型发出 Begin Patch 到 End Patch 的自由格式文本。</p></div>
204
+ <div class="flow-step"><div class="flow-number">02 / PARSE</div><strong>解析成 AST</strong><p>Add、Delete、Update、Move 和多个 update chunk。</p></div>
205
+ <div class="flow-step"><div class="flow-number">03 / RESOLVE</div><strong>解析路径</strong><p>相对路径以 ctx.cwd 为根,允许 workspace 内绝对路径。</p></div>
206
+ <div class="flow-step"><div class="flow-number">04 / PREFLIGHT</div><strong>全部预校验</strong><p>读取快照,匹配上下文,计算每个文件的新内容。</p></div>
207
+ <div class="flow-step"><div class="flow-number">05 / MUTATE</div><strong>串行写入</strong><p>通过 mutation queue 写入;移动先写目标再删源。</p></div>
208
+ <div class="flow-step"><div class="flow-number">06 / REPORT</div><strong>返回 diff</strong><p>返回逐文件 diff、unified patch、已提交路径和失败前缀。</p></div>
209
+ </div>
210
+ </section>
211
+
212
+ <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>
214
+ <div class="tabs" role="tablist" aria-label="配置视图">
215
+ <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>
217
+ <button class="tab" role="tab" aria-selected="false" aria-controls="config-route" id="tab-route">路由规则</button>
218
+ </div>
219
+ <div class="tab-panel active" id="config-current" role="tabpanel" aria-labelledby="tab-current">
220
+ <div class="code-wrap"><button class="copy" data-copy="current-config">复制</button><pre id="current-config">{
221
+ "enabled": true,
222
+ "models": [
223
+ { "provider": "pudu-openai-proxy", "model": "gpt-5.6-sol" }
224
+ ]
225
+ }</pre></div>
226
+ <div class="callout">models.json 中的 provider API key 不在页面展示。建议使用环境变量或 Pi auth 存储,并轮换已经出现在配置文件中的 key。</div>
227
+ </div>
228
+ <div class="tab-panel" id="config-recommended" role="tabpanel" aria-labelledby="tab-recommended">
229
+ <div class="code-wrap"><button class="copy" data-copy="recommended-config">复制</button><pre id="recommended-config">{
230
+ "enabled": true,
231
+ "models": [
232
+ { "provider": "pudu-openai-proxy", "model": "gpt-5.6-sol" },
233
+ { "provider": "pudu-openai-proxy", "model": "gpt-5.6-luna" }
234
+ ]
235
+ }</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>
237
+ </div>
238
+ <div class="tab-panel" id="config-route" role="tabpanel" aria-labelledby="tab-route">
239
+ <div class="code-wrap"><button class="copy" data-copy="route-rule">复制</button><pre id="route-rule">model = ctx.model
240
+
241
+ if enabled
242
+ and model.api in ["openai-responses", "openai-codex-responses"]
243
+ and provider/model matches allowlist:
244
+ active = ["apply_patch"]
245
+ else:
246
+ active = ["edit"]
247
+
248
+ # Preserve all unrelated active tools.
249
+ # Register both tools permanently for session replay.</pre></div>
250
+ <p>切换发生在 <code>session_start</code> 和 <code>model_select</code>。不重新定义同名工具,只改变 active tool 集合。</p>
251
+ </div>
252
+ </section>
253
+
254
+ <section class="section" id="principle">
255
+ <div class="section-head"><div><div class="eyebrow">Design principles</div><h2>这个工具的三个核心原则</h2></div></div>
256
+ <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>
258
+ <article class="principle"><h3>执行端独立防护</h3><p>grammar 不是安全策略。执行端仍要做 workspace 边界、符号链接、目录、覆盖冲突、权限和取消检查。</p></article>
259
+ <article class="principle"><h3>按能力选择,不靠 AGENTS 对抗</h3><p>不在 AGENTS 中要求模型“不要使用 apply_patch”。扩展只让模型看到适合自己的一个编辑工具,避免两套协议同时暴露。</p></article>
260
+ </div>
261
+ </section>
262
+
263
+ <section class="section" id="evidence">
264
+ <div class="section-head"><div><div class="eyebrow">Evidence</div><h2>公平 benchmark 对照</h2></div><p class="section-note">20 个固定用例;两组都拥有 read、bash、write,只替换编辑工具。</p></div>
265
+ <table class="compare">
266
+ <thead><tr><th>指标</th><th>A · current edit</th><th>C · apply_patch</th><th>解读</th></tr></thead>
267
+ <tbody>
268
+ <tr><td>最终精确正确率</td><td>18 / 20 · 90%</td><td>18 / 20 · 90%</td><td class="flat">没有下降,也没有提升</td></tr>
269
+ <tr><td>首次编辑成功率</td><td>85%</td><td class="positive">95%</td><td class="positive">+10 个百分点</td></tr>
270
+ <tr><td>中位编辑调用</td><td>1</td><td>1</td><td class="flat">无变化</td></tr>
271
+ <tr><td>中位 agent turns</td><td>4</td><td>4</td><td class="flat">无变化</td></tr>
272
+ <tr><td>中位 input/output tokens</td><td>9732</td><td>9978</td><td class="flat">+2.5%</td></tr>
273
+ <tr><td>中位全部 tokens</td><td>26722</td><td>26503</td><td class="flat">−0.8%,基本持平</td></tr>
274
+ <tr><td>中位耗时</td><td>17.75s</td><td class="positive">16.95s</td><td class="positive">−4.5%,但低于设定门槛</td></tr>
275
+ <tr><td>编辑协议失败</td><td>1</td><td class="positive">0</td><td class="positive">首次协议更稳定</td></tr>
276
+ </tbody>
277
+ </table>
278
+ <div class="panel" style="margin-top:18px">
279
+ <div class="status"><i></i>最终判断:候选实现保留,默认自动启用暂不打开</div>
280
+ <p>本页的耗时比较优先使用同一 fixture 的配对差值,避免两组成功样本构成不同造成中位数偏差。runner 的 180s 进程上限是删失边界,不是 API timeout;本次有效 Luna 重测没有触发该上限。</p>
281
+ </div>
282
+ </section>
283
+
284
+ <section class="section">
285
+ <div class="section-head"><div><div class="eyebrow">Files</div><h2>实现位置</h2></div></div>
286
+ <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>
290
+ <p><code>/home/maxiaochao/.pi/agent/evaluations/gpt-edit-benchmark/report-final.md</code>:最终 benchmark 报告。</p>
291
+ </div>
292
+ </section>
293
+ </main>
294
+ <footer>说明页基于 Pi 0.84.4、Codex apply_patch 源码分析和 20-case GPT-5.6 benchmark。页面内没有包含任何 API key。</footer>
295
+ <script>
296
+ const tabs = [...document.querySelectorAll('.tab')];
297
+ const panels = [...document.querySelectorAll('.tab-panel')];
298
+ function activateTab(tab) {
299
+ tabs.forEach(item => item.setAttribute('aria-selected', String(item === tab)));
300
+ panels.forEach(panel => panel.classList.toggle('active', panel.id === tab.getAttribute('aria-controls')));
301
+ }
302
+ tabs.forEach(tab => tab.addEventListener('click', () => activateTab(tab)));
303
+ tabs.forEach((tab, index) => tab.addEventListener('keydown', event => {
304
+ if (event.key !== 'ArrowRight' && event.key !== 'ArrowLeft') return;
305
+ event.preventDefault();
306
+ const next = tabs[(index + (event.key === 'ArrowRight' ? 1 : tabs.length - 1)) % tabs.length];
307
+ next.focus(); activateTab(next);
308
+ }));
309
+ document.querySelectorAll('.copy').forEach(button => button.addEventListener('click', async () => {
310
+ const text = document.getElementById(button.dataset.copy).textContent;
311
+ try { await navigator.clipboard.writeText(text); button.textContent = '已复制'; setTimeout(() => button.textContent = '复制', 1200); }
312
+ catch { button.textContent = '复制失败'; setTimeout(() => button.textContent = '复制', 1200); }
313
+ }));
314
+ </script>
315
+ </body>
316
+ </html>
@@ -0,0 +1,6 @@
1
+ {
2
+ "enabled": true,
3
+ "models": [
4
+ "gpt-5.6-*"
5
+ ]
6
+ }