@gavin-hu/my-pi-agent 0.17.0 → 0.18.0

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 (112) hide show
  1. package/README.md +17 -9
  2. package/README.zh-CN.md +16 -9
  3. package/extensions/bytefly/README.md +25 -18
  4. package/extensions/bytefly/notify/types.ts +1 -1
  5. package/extensions/bytefly/panel/settings.ts +25 -18
  6. package/extensions/file-browse/README.md +60 -21
  7. package/extensions/file-browse/chrome.ts +83 -0
  8. package/extensions/file-browse/client.ts +62 -0
  9. package/extensions/file-browse/config.ts +12 -3
  10. package/extensions/file-browse/diff-html.ts +94 -0
  11. package/extensions/file-browse/html.ts +19 -147
  12. package/extensions/file-browse/live-client.ts +47 -1
  13. package/extensions/file-browse/live.ts +35 -14
  14. package/extensions/file-browse/rev.ts +42 -0
  15. package/extensions/file-browse/router.ts +25 -4
  16. package/extensions/file-browse/server.ts +7 -0
  17. package/extensions/file-browse/styles.ts +2 -0
  18. package/extensions/goal/plan-route.ts +8 -2
  19. package/extensions/job/README.md +7 -5
  20. package/extensions/job/index.ts +15 -7
  21. package/extensions/memory/README.md +3 -3
  22. package/extensions/plan/README.md +280 -86
  23. package/extensions/plan/commands.ts +56 -5
  24. package/extensions/plan/feedback.ts +300 -63
  25. package/extensions/plan/index.ts +1 -1
  26. package/extensions/plan/plan-index.ts +1 -1
  27. package/extensions/plan/plan-text.ts +18 -1
  28. package/extensions/plan/plans.ts +165 -57
  29. package/extensions/plan/review-prompt.ts +93 -0
  30. package/extensions/plan/review.ts +78 -98
  31. package/extensions/plan/runtime.ts +1 -1
  32. package/extensions/plan/split.ts +87 -0
  33. package/extensions/plan/steps.ts +80 -93
  34. package/extensions/plan/tools/enter.ts +8 -3
  35. package/extensions/plan/tools/exit.ts +101 -23
  36. package/extensions/plan/tools/write.ts +8 -3
  37. package/extensions/plan/tools.ts +1 -1
  38. package/extensions/plan/tui.ts +51 -8
  39. package/extensions/plan/types.ts +37 -3
  40. package/extensions/plan/web-chrome.ts +2 -1
  41. package/extensions/plan/web-client.ts +121 -177
  42. package/extensions/plan/web-collab.ts +207 -0
  43. package/extensions/plan/web-comments.ts +269 -0
  44. package/extensions/plan/web-page.ts +103 -17
  45. package/extensions/plan/web-router.ts +146 -30
  46. package/extensions/plan/web-share-session.ts +197 -0
  47. package/extensions/plan/web-share.ts +169 -0
  48. package/extensions/plan/web-streams.ts +124 -0
  49. package/extensions/plan/web-tokens.ts +63 -0
  50. package/extensions/plan/web.ts +193 -45
  51. package/extensions/question/README.md +3 -0
  52. package/extensions/question/dialogs.ts +91 -1
  53. package/extensions/question/tools.ts +2 -1
  54. package/extensions/session-bus/README.md +247 -0
  55. package/extensions/session-bus/commands.ts +165 -0
  56. package/extensions/session-bus/format.ts +124 -0
  57. package/extensions/session-bus/index.ts +69 -0
  58. package/extensions/session-bus/notice.ts +58 -0
  59. package/extensions/session-bus/protocol.ts +134 -0
  60. package/extensions/session-bus/result.ts +243 -0
  61. package/extensions/session-bus/rows.ts +169 -0
  62. package/extensions/session-bus/runtime.ts +575 -0
  63. package/extensions/session-bus/store.ts +329 -0
  64. package/extensions/session-bus/team.ts +102 -0
  65. package/extensions/session-bus/tools.ts +227 -0
  66. package/extensions/session-bus/tui.ts +394 -0
  67. package/extensions/session-bus/types.ts +98 -0
  68. package/extensions/subagent/README.md +1 -1
  69. package/extensions/watch/README.md +9 -5
  70. package/extensions/watch/index.ts +15 -9
  71. package/extensions/wechat/README.md +20 -33
  72. package/extensions/wechat/bridge.ts +8 -8
  73. package/extensions/wechat/commands.ts +2 -2
  74. package/extensions/wechat/deliver.ts +57 -37
  75. package/extensions/wechat/interaction.ts +31 -16
  76. package/extensions/wechat/notices.ts +7 -7
  77. package/extensions/wechat/peer-worker.ts +19 -34
  78. package/extensions/wechat/presence.ts +3 -4
  79. package/extensions/wechat/prompt.ts +39 -24
  80. package/extensions/wechat/reply-pipeline.ts +12 -4
  81. package/extensions/worktree/git.ts +5 -0
  82. package/extensions/worktree/registry.ts +11 -5
  83. package/extensions/worktree/status.ts +1 -3
  84. package/lib/README.md +6 -2
  85. package/lib/claims/overlap.ts +43 -0
  86. package/lib/http/router.ts +40 -13
  87. package/lib/http/server.ts +27 -13
  88. package/lib/interaction.ts +10 -0
  89. package/lib/notice.ts +29 -2
  90. package/lib/plan-entry.ts +7 -2
  91. package/lib/plans/sections.ts +172 -0
  92. package/lib/plans/setting.ts +52 -0
  93. package/lib/plans/split.ts +215 -0
  94. package/lib/plans/store.ts +19 -0
  95. package/lib/plans/team.ts +151 -0
  96. package/lib/qr.ts +64 -0
  97. package/lib/repo-key.ts +74 -0
  98. package/lib/report.ts +52 -33
  99. package/lib/requirements.ts +1 -2
  100. package/lib/tui/README.md +16 -2
  101. package/lib/tui/dock-screen.ts +85 -17
  102. package/lib/tui/line-pane.ts +15 -4
  103. package/lib/tui/tool.ts +23 -4
  104. package/lib/web/components.ts +1 -1
  105. package/package.json +3 -2
  106. package/skills/init/SKILL.md +80 -45
  107. package/skills/init/references/agents-md-quality.md +26 -2
  108. package/skills/init/references/interview-questions.md +36 -14
  109. package/skills/next/SKILL.md +78 -53
  110. package/skills/parallel-dev/SKILL.md +150 -0
  111. package/extensions/plan/config.ts +0 -27
  112. package/extensions/wechat/qrcode.ts +0 -46
@@ -3,7 +3,7 @@
3
3
  A read-only exploration mode modelled on Claude Code's `EnterPlanMode` /
4
4
  `ExitPlanMode` pair. While plan mode is on, the model investigates and proposes,
5
5
  but the only thing it may write is the plan file itself. The plan is saved under
6
- `.pi/plans`; the user reviews that file, and it is the artifact the model
6
+ the agent directory; the user reviews that file, and it is the artifact the model
7
7
  executes.
8
8
 
9
9
  ```bash
@@ -44,11 +44,14 @@ How the `write_plan` and `exit_plan_mode` calls and their results look in the tr
44
44
  quality checklist, and writes only after plan approval. It supports existing and
45
45
  empty repositories, and falls back to evidence with recorded assumptions when
46
46
  the interview is unavailable or cancelled; it never invents commands.
47
- - **Plan files.** The model saves the plan with `write_plan`; the file lands in
48
- `<repo-root>/.pi/plans/<YYYY-MM-DD-HHmm>-<slug>.md` — the entered worktree's
49
- root when one is active — or `<agent-dir>/plans` outside a repository. The
50
- plans directory is self-ignoring, so plan files never show up as untracked
51
- files or in `rewind` snapshots.
47
+ - **Plan files, shared by the whole repository.** The model saves the plan with
48
+ `write_plan`; the file lands in
49
+ `<agent-dir>/plans/<repo-key>/<YYYY-MM-DD-HHmm>-<slug>.md`, keyed by the
50
+ repository's common git directory, so a plan approved in one worktree is the
51
+ same plan from the main checkout and from every other session. Outside a
52
+ repository the store is `<agent-dir>/plans`. Plans are outside the project, so
53
+ they are never untracked files and never appear in a `rewind` snapshot; a store
54
+ an older version left in `<repo>/.pi/plans` is moved in once, with a notice.
52
55
  - **A discoverable index.** `/plan list` walks the plans directory and shows
53
56
  every plan with its title, modified time, and step progress, newest first — in
54
57
  the TUI as a docked list where `Enter` opens the plan's review, elsewhere as a
@@ -96,10 +99,10 @@ How the `write_plan` and `exit_plan_mode` calls and their results look in the tr
96
99
  it for approval. The user approves, keeps planning, asks for a refinement, or
97
100
  exits plan mode without approving; the last keeps the plan file for a later
98
101
  review and records no todo items or goal.
99
- On a remote turn (WeChat) the plan file is sent first, then a digest of its
100
- title, step count, and first steps, then the choices, so the review is still
101
- answerable when the attachment is not opened. The choices include **Send the
102
- full plan**, which posts the plan text and asks again.
102
+ On a remote turn (WeChat) the plan file is sent first, then one compact editor
103
+ prompt: the title, step count, first steps, mode status, and three actions
104
+ (approve, keep, exit). Any other reply is a refinement request; if the file
105
+ upload fails, its text is sent instead.
103
106
  - **Browser review on demand.** Press `w` on the review screen (or run
104
107
  `/plan review [path]`) to open the plan in a local browser: a reading-first
105
108
  page with the plan's H1 as its title, GitHub's Markdown content column, a
@@ -115,10 +118,11 @@ How the `write_plan` and `exit_plan_mode` calls and their results look in the tr
115
118
  - **Steps become todos.** On approval, the top-level numbered/bulleted items of
116
119
  the plan's `Steps` section are recorded with the `todo` tool (via
117
120
  `ctx.executeTool`); steps marked `- [x]` (or `[DONE:n]`) seed as completed, and
118
- nested sub-bullets are treated as notes. The list is a merge, not a
119
- replacement: existing items keep their place and status, a step whose text is
120
- already there is skipped, and the tool's item cap defers the rest instead of
121
- dropping anything. The plan's `## Verification` section follows as the **last**
121
+ nested sub-bullets are treated as notes while an indented line continues its
122
+ step. The list is a merge, not a replacement: existing items keep their place
123
+ and status, a step whose text is already there is skipped, and the tool's item
124
+ cap defers the rest instead of dropping anything. The plan's `## Verification`
125
+ section follows as the **last**
122
126
  todo item, summarized on one line (`Verify: …`) — a fenced block contributes its
123
127
  commands, not its fence lines. It is not a plan step, so it is ticked in the todo
124
128
  list rather than with `set_plan_steps`, and it never counts against the 50-step
@@ -132,21 +136,87 @@ How the `write_plan` and `exit_plan_mode` calls and their results look in the tr
132
136
  instead of beside it. On approval, a goal the goal workflow left as a draft
133
137
  (`planning`) is finalized from the plan's own `## Goal` section — falling back
134
138
  to the title — through the `goal` tool, with no extra dialog, because approving
135
- the plan *is* the decision. A session with no goal at all gets one confirm to
136
- record the same objective, unless `plan.goalPrompt` is off, which records it
137
- without asking. An `active` or `achieved` goal is never rewritten: a final
138
- objective is the user's. No path spends a model turn.
139
+ the plan *is* the decision. A session with no goal at all is given the same
140
+ objective, also without asking. An `active` or `achieved` goal is never
141
+ rewritten: a final objective is the user's. No path spends a model turn.
142
+ - **A split, when the switch is on.** With `plan.split` enabled (off by default;
143
+ a `/bytefly settings` row), `exit_plan_mode` also accepts `units`: the plan's
144
+ slices, each with a name, path prefixes, a test, and the units it depends on.
145
+ The split is validated *before* the review opens, so a refusal costs no review,
146
+ and the review screen shows it as a read-only `Split` section. On approval the
147
+ team is written into the plan as a `## Team` table — a sentence naming the lead's
148
+ unit, then one row per unit — and the result carries a roster card: the exact
149
+ commands for the sessions this package cannot start, the merge order, which unit
150
+ is the lead's own, and the lead's own label or the rename that sets it. The team
151
+ section is what a joining session reads, so it appears only after approval; a
152
+ refinement rewrites the plan, and with it the team ceases to exist until the plan
153
+ is approved again. Refusals name the pair: fewer than two units (with the
154
+ skeleton advice), overlapping prefixes, a `dependsOn` cycle, more than two owners
155
+ plus the lead, a unit with no test, an owner unit that claims a shared file, and a
156
+ value that would break the table. See [Splitting a plan](#splitting-a-plan).
139
157
  - **Branch-aware.** The enabled flag and the review baseline are custom session
140
158
  entries, so `/resume` and `/tree` restore the right mode and keep the
141
159
  changed-section markers.
142
160
 
161
+ ## Splitting a plan
162
+
163
+ Off by default: enable **Split plans into slices** in `/bytefly settings`, or set
164
+ `"plan": { "split": true }` in the project's `.pi/bytefly.json` so a repository
165
+ switches its own team on.
166
+
167
+ ```text
168
+ exit_plan_mode({
169
+ plan_path: "~/.pi/agent/plans/a1f9c02/2026-11-04-1802-billing.md",
170
+ units: [
171
+ { name: "contract", paths: ["src/billing/contract"], test: "bun test src/billing/contract", lead: true },
172
+ { name: "core", paths: ["src/billing/core"], test: "bun test src/billing/core", dependsOn: ["contract"] }
173
+ ]
174
+ })
175
+ ```
176
+
177
+ The result prints the roster card, which is all the package can do for the sessions
178
+ it cannot start — and the one line it can act on for the session it is in:
179
+
180
+ ```text
181
+ Split: 2 units · ~/.pi/agent/plans/a1f9c02/2026-11-04-1802-billing.md
182
+ You start and switch every session; nothing here can.
183
+ core: enter_worktree core · /sessions name core · /sessions watch wake
184
+ session({action:"join", team:"2026-11-04-1802-billing", unit:"core"})
185
+ merge order: contract → core · your unit: contract
186
+ your label: none — name this session: session({action:"name", label:"contract"})
187
+ ```
188
+
189
+ The last line is the lead's own label. The card's other lines are for the sessions
190
+ the user starts; this one is the lead's own, so it names the label when the session
191
+ already has one, and otherwise prints the rename to run. Once set, it reads
192
+ `your label: billing-lead — owners send their report there`, which is what an
193
+ owner addresses its completion report to.
194
+
195
+ What the gate enforces, and why each rule is a refusal rather than a warning:
196
+
197
+ | Refusal | Why |
198
+ |---|---|
199
+ | fewer than two units | One path is not a split: approve as one flow, or build the thinnest end-to-end path first. |
200
+ | overlapping prefixes | Two owners on one path is a merge conflict by construction. |
201
+ | a `dependsOn` cycle | No unit can start. |
202
+ | more than two owners | The user switches to every owner each round; three means the split is wrong. |
203
+ | a unit with no test | The unit's definition of done would be undocumented. |
204
+ | an owner unit claiming a shared file | `package.json`, the changelog, the test manifests, and the shared name modules belong to the lead. |
205
+ | no unit marked lead | The approving session needs a unit of its own, or it is a queue. |
206
+
207
+ The table itself is a wire format: [`session-bus`](../session-bus/) parses it with
208
+ the same module that writes it, so a hand-edited or stale section is refused
209
+ rather than half-read. The browser review cannot approve a split plan — a section
210
+ written at approval has no source lines for its comments to anchor to — so the
211
+ terminal review is where a split is approved.
212
+
143
213
  ## Tools
144
214
 
145
215
  | Tool | Active | Purpose |
146
216
  |---|---|---|
147
217
  | `enter_plan_mode` | normal mode | Ask to enter plan mode; empty schema, confirmed with `ctx.ui.confirm`. |
148
218
  | `write_plan` | plan mode | Save the plan: `title` (file-name slug), `content` (full markdown in the documented outline), optional `plan_path` to overwrite a refinement. Returns the absolute path. A plan with more than 50 top-level steps in its `Steps` section is refused, with the cap and the fix named, and nothing is written. |
149
- | `exit_plan_mode` | plan mode | Read the plan file (`plan_path`) and present it for approval. The review also offers **Exit plan mode (discard)**, which leaves plan mode without approving. Refuses a file whose step list is over the cap, before the review screen. |
219
+ | `exit_plan_mode` | plan mode | Read the plan file (`plan_path`) and present it for approval. Optional `units` proposes a split when `plan.split` is on; it is validated before the review, written into the plan on approval, and ignored with one note while the switch is off. The review also offers **Exit plan mode (discard)**, which leaves plan mode without approving. Refuses a file whose step list is over the cap, before the review screen. |
150
220
  | `cancel_plan_mode` | plan mode | Leave plan mode without a plan file or a review. Restores write access; records no todo items or goal; keeps any saved plan. Empty schema. |
151
221
  | `set_plan_steps` | normal mode | Keep an approved plan current: set the checkboxes of the given top-level steps (`updates`, each a 1-based `step` in the order `exit_plan_mode` listed, plus `status`, and `expected` — the step's text, so a reordered plan is refused) to `pending` or `completed` in one write. Only the checkboxes change, and a reopened step drops its `[DONE:n]` marker. The todo item approval seeded from a step is ticked with it: the file is written first and is authoritative, a renamed item is left alone, and a failed todo write is reported in the result. |
152
222
 
@@ -194,15 +264,15 @@ approval. Its workflow is:
194
264
  ```text
195
265
  /skill:init
196
266
  └─> plan mode (read-only)
197
- └─> recon: manifests, docs, CI, tests, existing instructions
198
- └─> interview: built-in bank, one call of at most 4 questions
267
+ └─> recon: effective instruction file, sourced evidence, existing instructions
268
+ └─> interview: built-in bank, one call of at most 4 questions, minus resolved facts
199
269
  ├─ answered ───────────────> reconcile with evidence
200
270
  └─ unavailable/cancelled ──> evidence + assumptions
201
271
  └─> draft: new file, exact edits, or an honest no-op
202
272
  └─> quality gate
203
273
  └─> write_plan ──> exit_plan_mode
204
274
  ├─ not approved ──> refine the same plan
205
- └─ approved ──────> write/edit AGENTS.md
275
+ └─ approved ──────> write/edit the target
206
276
  └─ read back + quality checklist
207
277
  ```
208
278
 
@@ -210,15 +280,26 @@ The interview comes from a built-in bank in
210
280
  [`skills/init/references/interview-questions.md`](../../skills/init/references/interview-questions.md):
211
281
  core questions cover purpose, verification, non-obvious rules, isolation,
212
282
  completion, sensitive paths, and dependency or network limits, with state-specific
213
- entries for an existing file, an empty repository, and related instruction files.
214
- The first call uses a deterministic four-question set for the repository state;
215
- a second call is allowed only for a material ambiguity. If the tool is inactive,
216
- there is no UI, or the user cancels, the skill records the gaps as assumptions
217
- and continues from repository evidence.
283
+ entries for an existing file, an empty repository, related instruction files, and
284
+ a shadowing override. The first call uses the bank's deterministic set for the
285
+ state, minus every field the evidence sheet resolved; a second call is allowed
286
+ only for a material ambiguity. If the tool is inactive, there is no UI, or the
287
+ user cancels, the skill records the gaps as assumptions and continues from
288
+ repository evidence.
289
+
290
+ Recon resolves the effective instruction file first: a root `AGENTS.override.md`
291
+ replaces `AGENTS.md` and becomes the target, while an override above the project
292
+ root is reported rather than edited. Recon also builds a sourced evidence sheet —
293
+ commands with the file that defines them, generated and vendored paths from
294
+ `.gitignore`, workspace packages and boundaries, and purpose from the README or
295
+ manifest — and the interview asks only what it could not resolve. The plan uses
296
+ the canonical outline, including `## Steps` and `## Verification`, so approval
297
+ seeds the apply actions rather than the draft's bullets.
218
298
 
219
299
  The quality reference is
220
300
  [`skills/init/references/agents-md-quality.md`](../../skills/init/references/agents-md-quality.md).
221
- An existing `AGENTS.md` is reviewed and updated only after approval, an accurate
301
+ An existing instruction file is reviewed and updated only after approval, a root
302
+ override is edited in place or removed explicitly when consolidating, an accurate
222
303
  file can produce a no-op plan, and an empty repository gets only known facts.
223
304
  The skill needs the plan extension for its review gate. It also ships with the
224
305
  package, so load the whole package (`pi -e .`) or install it; loading only
@@ -233,14 +314,15 @@ so headings, lists, inline code, code blocks, links, and quotes carry the active
233
314
  theme's colors; task items get the shared `◼` (done) or `◻` (pending) marker,
234
315
  like the bytefly screen and the question dialog. The footer colors the primary
235
316
  actions (`a approve & run` in `success`, `r refine` in `accent`).
236
- `a` ends plan mode, restores write access, and seeds `todo`; `r` keeps the plan
317
+ `a` asks `y/N` in the footer, then ends plan mode, restores write access, and
318
+ seeds `todo`; `y` or `Enter` approves and `n`, `q`, or `Esc` goes back to
319
+ reading. `r` keeps the plan
237
320
  mounted and opens an inline editor beneath it (`Enter` submits, an empty buffer
238
321
  does nothing, `Esc` returns to the plan); `Esc` keeps planning and tells the
239
322
  model the plan was not approved; `x` leaves plan mode without approving and
240
- keeps the plan file. The dialog path offers the same five outcomes, including
241
- **Exit plan mode (discard)**. Dismissing the dialog (or a timeout) is also an
242
- exit, not a silent `keep`; only the explicit **Keep planning** option stays in
243
- plan mode. When the plan changed since the previous review
323
+ keeps the plan file. The dialog path is one editor prompt: `1` approve, `2` full
324
+ plan, `3` keep, `4` exit; any other text refines the plan. Dismissing or timing
325
+ out the prompt exits without approving. When the plan changed since the previous review
244
326
  on this branch, a summary line under the title names how many sections changed
245
327
  and the scroll row appends `· changed` to the current section. Scroll with `↑`/`↓` or `j`/`k`, `space`/`b` or
246
328
  `PgUp`/`PgDn`, `d`/`u`, `g`/`G`, and the mouse wheel. Press `n`/`p` to jump to
@@ -255,14 +337,12 @@ dropped whole as the terminal narrows, and each footer leads with its safe exit
255
337
  (`Esc keep plan` in the review, `Esc back to plan` in the inline editor) so
256
338
  the exit survives even when only one hint fits.
257
339
 
258
- Dialog-capable non-TUI modes (RPC) show the same five choices as a select menu
259
- plus the refine editor; cancelling the editor returns to the select instead of
260
- being reported as "not approved". Dismissing the plan select is an exit. A WeChat turn takes the same dialog path and
261
- is answered over WeChat, even though the session is in TUI mode: the choice
262
- comes from `askHuman(ctx, { custom, dialogs, file, body })` in
263
- `lib/interaction.ts`, not from `ctx.mode`. A remote turn also receives the plan
264
- **file** first (falling back to the plan text if the upload fails) so the whole
265
- plan is reviewable there; RPC has no channel and still sees only the choices.
340
+ Dialog-capable non-TUI modes (RPC/WeChat) use one editor prompt; a non-action
341
+ reply is the refinement, so “select Refine, then type” is gone. A WeChat turn
342
+ takes the same dialog path and is answered over WeChat, even though the session
343
+ is in TUI mode: the choice comes from `askHuman(ctx, { custom, dialogs, body })`
344
+ in `lib/interaction.ts`, not from `ctx.mode`. No plan file is attached; **full
345
+ plan** posts the text and asks again.
266
346
  Without any UI the tools fail with an actionable message instead of deciding for
267
347
  the user.
268
348
 
@@ -289,13 +369,13 @@ enter_plan_mode requested plan mode
289
369
 
290
370
  ```
291
371
  write_plan Add rate limiting
292
- ✓ Saved plan .pi/plans/2026-10-09-1809-add-rate-limiting.md
372
+ ✓ Saved plan ~/.pi/agent/plans/a1f9c02/2026-10-09-1809-add-rate-limiting.md
293
373
  ```
294
374
 
295
375
  ```
296
376
  exit_plan_mode submitted a plan
297
377
  ✓ Plan approved
298
- .pi/plans/2026-10-09-1809-add-rate-limiting.md
378
+ ~/.pi/agent/plans/a1f9c02/2026-10-09-1809-add-rate-limiting.md
299
379
 
300
380
  # Add rate limiting
301
381
  1. Add a token bucket …
@@ -303,16 +383,6 @@ exit_plan_mode submitted a plan
303
383
  … 12 more lines (ctrl+o to expand)
304
384
  ```
305
385
 
306
- ## Configuration
307
-
308
- The `plan` section of the shared `bytefly.json` (global then project), or the
309
- **Offer the session goal** row in `/bytefly settings`. It is read when a plan is
310
- approved, so a change applies to the next approval without a reload.
311
-
312
- | Key | Default | Meaning |
313
- |---|---|---|
314
- | `goalPrompt` | `true` | Ask before recording an approved plan's objective as the session goal; `false` records it without asking. |
315
-
316
386
  ## Security
317
387
 
318
388
  Plan mode is a **guard rail, not a sandbox**: extensions run with Pi's OS
@@ -409,12 +479,13 @@ Pi's Markdown renderer with the active theme, so headings, lists, code, and
409
479
  links read as they do in the transcript; the checkboxes in `Steps` use the
410
480
  shared `◼`/`◻` vocabulary. A plan taller than the terminal scrolls under a gauge
411
481
  that names the section at the top of the viewport and the line range it shows,
412
- and the footer lists only the keys that apply. `Esc` keeps planning, `a` approves
413
- and runs, `r` refines inline, and `w` opens the browser review below.
482
+ and the footer lists only the keys that apply. `Esc` keeps planning, `a` asks
483
+ `y/N` and then approves and runs, `r` refines inline, and `w` opens the browser
484
+ review below.
414
485
 
415
486
  ## Browser review
416
487
 
417
- <img src="../../assets/plan-review.png" alt="The browser-based plan review: a sticky header with the plan title, a status chip, and the Approve & run, Save feedback, and Back to Pi actions, a side panel with step progress and a section outline, and the plan as one measured column with a comment rail and an inline review thread" width="100%">
488
+ <img src="../../assets/plan-review.png" alt="The browser-based plan review: a sticky header with the plan title, a status chip, and the Approve & run, Save feedback, and Back to Pi actions, a Share action, a side panel with step progress and a section outline, and the plan as one measured column with a comment rail holding the owner's own comment and a teammate's @priya comment" width="100%">
418
489
 
419
490
  Press `w` on the review screen — or run `/plan review [path]` — to open the plan
420
491
  in the default browser on `127.0.0.1`. The page is a reading-first review
@@ -447,17 +518,26 @@ file and returns them to the model.
447
518
  labelled and indented as a thread, so it reads in place and the plan keeps one
448
519
  measure at every width. There is no separate column to reflow.
449
520
  - **Nothing typed is lost.** **Save feedback**, **Approve & run**, and **Back to
450
- Pi** refuse while a composer holds text and put the cursor back in it, and a
451
- deleted comment is offered back in the header for a few seconds. The page keeps
452
- no draft of its own — these are the three ways an open comment could disappear.
521
+ Pi** refuse while a composer holds text and put the cursor back in it, a
522
+ deleted comment is offered back in the header for a few seconds, and this tab's
523
+ unsaved work survives a reload: the comments it added and the text still in its
524
+ composer are kept in `sessionStorage` under a digest of the plan text the page
525
+ rendered, then restored on the next load of that same text with the composer
526
+ reopened at the caret and the selection it quoted, and dropped when the plan
527
+ changed underneath them. A composer closed with `Esc` or **Cancel** discards its
528
+ text, so only an abandoned tab gets it back. Nothing is stored on the server, and
529
+ another writer's comment is never part of the draft.
453
530
  - **Prints as a document.** Printing uses the light palette whatever the screen
454
531
  shows, drops the header, panel, rail, and progress bar, and lets the comments
455
532
  stay with the blocks they belong to.
456
533
  - **Keyboard first.** `j`/`k` move to the next or previous block, `c` comments
457
534
  on the current block, `a` approves (the approval flow only), `s` saves, `Esc`
458
535
  closes the composer or the sheet and then the review, and `?` opens a shortcut
459
- sheet; the header's `?` button opens the same sheet. Either dialog traps
460
- `Tab` and hands focus back to whatever opened it.
536
+ sheet; the header's `?` button opens the same sheet. Adding, editing, or
537
+ deleting a comment rebuilds the document, so the tab stop returns to that
538
+ block's comment control instead of being dropped, and the status line says
539
+ which change landed; a repaint the stream caused never moves focus. Either
540
+ dialog traps `Tab` and hands focus back to whatever opened it.
461
541
  - **Decision in place.** A review opened for approval (`w` on the review screen)
462
542
  shows **Approve & run** beside Save feedback: it persists the current comments,
463
543
  ends plan mode, restores write access, seeds `todo`, and hands control back to
@@ -478,10 +558,20 @@ file and returns them to the model.
478
558
  `DOMParser`, so a code line is never parsed as markup.
479
559
 
480
560
  - **The file is the store.** Each comment is written as
481
- `> [!REVIEW] <comment>` on the line after the block it anchors to. Saving is
482
- idempotent: the previous markers are stripped first, so a second save replaces
483
- rather than appends. Stripping is fence-aware: a `> [!REVIEW]` line inside a
484
- code block is documentation, not feedback. The browser keeps no copy of its own.
561
+ `> [!REVIEW] <comment>` on the line after the block it anchors to, tagged with
562
+ its author when a teammate wrote it (`> [!REVIEW] @alice: <comment>`). Saving is
563
+ idempotent per writer: that writer's previous markers are replaced and everyone
564
+ else's are kept, so two people reviewing the same plan cannot erase each other.
565
+ Stripping is fence-aware: a `> [!REVIEW]` line inside a code block is
566
+ documentation, not feedback. The file is the only copy of a saved comment; the
567
+ ones this tab has not saved yet live in its own `sessionStorage` and nowhere else.
568
+ - **A revision keeps the review.** `write_plan` passes the file it is replacing
569
+ through `carryFeedback`, which re-attaches each comment by the strongest evidence
570
+ that survived: the anchored block's text, per occurrence, so a plan that repeats
571
+ a step keeps the comments apart; then the same section at the same position, so a
572
+ reworded step keeps its note; then the end of that section. A comment whose
573
+ section is gone too moves to the end of the file, where the page shows it as
574
+ unattached, rather than disappearing.
485
575
  - **Anchored to source.** `blocks.ts` splits the plan (fence-aware) into
486
576
  source-line ranges; a comment is placed after the whole anchored block, so a
487
577
  fenced code block is never split. A save re-checks the file against what the
@@ -492,24 +582,89 @@ file and returns them to the model.
492
582
  save that carries no comments asks for nothing, so the review screen comes back
493
583
  instead. `/plan review` sends them as a user message (a plan-mode revision or a
494
584
  plain update, depending on the mode).
495
- - **Loopback and tokenized.** The server binds `127.0.0.1`, carries a random
496
- per-session token in the URL, rejects a missing or foreign `Host` header,
497
- serves only `GET`, `HEAD`, and `POST`, and caps the request body. A save or cancel
585
+ - **Loopback first, tokenized.** The server binds `127.0.0.1`, carries a random
586
+ per-session token in the URL, rejects a `Host` header it was not bound on,
587
+ refuses a cross-origin POST, serves only `GET`, `HEAD`, and `POST`, and caps the
588
+ request body. Sharing adds a second listener (below); it never widens the first. A save or cancel
498
589
  resolves the session only after its HTTP response is delivered, so the browser
499
590
  never sees a false "Failed to fetch". The last bound port is remembered per
500
591
  plan, so reviewing the same plan again reuses the origin (and the browser's
501
- saved theme with it). It is closed on save, cancel, a dropped event stream, or
502
- `session_shutdown`; a reload reconnects inside that grace, so refreshing or
503
- reloading a conflicted page does not end the review.
592
+ saved theme with it). It is closed on the owner's save or cancel, on the owner's
593
+ page going away, or on `session_shutdown`; a reload reconnects inside that grace,
594
+ so refreshing or reloading a conflicted page does not end the review. A
595
+ reviewer's tab opening or closing is not the review's lifetime.
504
596
 
505
597
  | Route | Result |
506
598
  |---|---|
507
599
  | `GET /?token=…` | the review page (a JSON data island plus `/theme.js` and `/client.js`) |
508
600
  | `GET /theme.js` | the shared pre-paint theme bootstrap |
509
601
  | `GET /client.js` | the first-party client script |
510
- | `GET /events?token=…` | Server-Sent Events stream, used only to detect a closed tab |
511
- | `POST /api/feedback?token=…` | validate and write the annotations into the plan |
512
- | `POST /api/cancel?token=…` | end the session without saving |
602
+ | `GET /events?token=…` | Server-Sent Events stream: a `comment`, `plan`, or `share` frame when something changed, `closed` when the review ends, `revoked` when sharing stops, and a dropped stream to detect a closed tab |
603
+ | `POST /api/comment?token=…` | reviewer only: write their own comments into the plan, replacing only their own; never ends the review |
604
+ | `POST /api/feedback?token=…` | owner only: write the owner's comments and return to the model |
605
+ | `POST /api/approve?token=…` | owner only: save, then approve |
606
+ | `POST /api/cancel?token=…` | owner only: end the session without saving |
607
+ | `POST /api/share?token=…` | owner only: mint the first reviewer link and its QR |
608
+ | `POST /api/share/link?token=…` | owner only: mint one more reviewer link, so each teammate holds their own |
609
+ | `POST /api/share/revoke?token=…` | owner only: revoke one link by `{ id }`, leaving the others answering |
610
+ | `POST /api/share/stop?token=…` | owner only: revoke every reviewer link |
611
+
612
+ ## Shared review
613
+
614
+ A review is not only the owner's. **Share** in the review header (or `/plan share`)
615
+ mints a reviewer link, binds a second listener on the machine's first reachable
616
+ IPv4 address, and shows the link with a QR a phone can scan. **New link** in the
617
+ same dialog mints one more, so each teammate holds a link of their own. **Stop
618
+ sharing** revokes every link and closes that listener; closing the review does
619
+ both.
620
+
621
+ - **Two roles, one token each.** The owner's token — the one the browser opened
622
+ with — decides: it may comment, save (which returns to the model), approve,
623
+ cancel, mint links, and revoke them. A reviewer token may read and comment, and
624
+ its one write route is `POST /api/comment`: `/api/feedback`, `/api/approve`,
625
+ `/api/cancel`, and the `/api/share` family answer `403`, and the page renders
626
+ only the name control and **Post comments** for it — and once that link has
627
+ posted, the control shows the bound name instead of offering the dialog.
628
+ Anything the reviewer holds is reachable only while its link is shared.
629
+ - **One link is one person.** The dialog lists every link with the name it has
630
+ bound, and the two things an owner does with one: **Copy**, and **Revoke**, which
631
+ drops that link alone — its page is told `revoked` and its token stops
632
+ answering, while the others keep working. Revoking the last link stops sharing.
633
+ A review holds at most eight links, because each is a live credential.
634
+ - **The owner's page is the review's lifetime.** A reviewer's tab opening or
635
+ closing changes nothing; the review ends when the owner saves, approves, or
636
+ closes it, or when the owner's own page goes away. A reviewer's post writes the
637
+ plan and updates every open page, and leaves the review waiting. A page that is
638
+ still open when the review ends is told why — `closed`, or `revoked` when its
639
+ link was revoked or sharing was stopped — and keeps the plan readable.
640
+ - **Signed comments.** A reviewer names herself once, on the page, and that name
641
+ is bound to her link on its first post: the link stores under it from then on,
642
+ a second tab of the same link that picked another name is refused (`409`) rather
643
+ than silently re-signing, and a name another live link already holds is refused
644
+ when it is first claimed. A bound name is shown, not offered, so a reviewer can
645
+ edit or withdraw her own notes and nobody else's, and her second post replaces
646
+ rather than duplicates the first. The name is a label on a bearer link, not an
647
+ account, which is why the owner still makes the only decision.
648
+ - **One listener, one machine.** Sharing opens exactly the address the dialog
649
+ printed — never `0.0.0.0` — and the `Host` allowlist becomes loopback plus that
650
+ address, however many links answer on it. A machine with no reachable IPv4
651
+ address reports why instead of printing a link that cannot work. There is no
652
+ tunnel, no relay, and no cross-machine sync: this is the same Wi-Fi.
653
+ - **Live.** Every open page follows the stream, so a reviewer's post appears on the
654
+ owner's page without a reload. The review also polls its own plan file once a
655
+ second, so an edit this review did not make — the model rewriting the plan while
656
+ `/plan review` is open in normal mode, or a teammate's editor — repaints every
657
+ reader instead of waiting for the next request, and a save that changed nothing
658
+ but comments stays quiet. A page with a draft open refuses to repaint and says
659
+ what arrived instead. The owner's terminal is told too — `Plan review: @alice
660
+ posted 2 comments.` — because whoever is waiting in the TUI is not looking at
661
+ the page.
662
+ - **What a shared link is.** A bearer credential: anyone who has it reads the plan
663
+ and can comment, under the one name it binds on its first post. The dialog says
664
+ so, `Referrer-Policy: no-referrer` keeps a click from leaking it, and **Revoke**
665
+ (or **Stop sharing**) is the revocation. The plan file is the record — a comment
666
+ arrives as an author-tagged marker, so `/resume` and the terminal show the same
667
+ review the page did.
513
668
 
514
669
  ## Limitations
515
670
 
@@ -536,6 +691,18 @@ file and returns them to the model.
536
691
  - Feedback stripping follows fenced code blocks; an unterminated fence makes the
537
692
  rest of the file code, so a marker-looking line after it is treated as content
538
693
  rather than stored feedback.
694
+ - A comment is one line. Stored feedback is a single blockquote line, so newlines
695
+ and runs of spaces collapse before it is written; a pasted multi-line
696
+ instruction reaches the model as one sentence, which is what keeps the marker
697
+ grammar valid markdown.
698
+ - The plan-file poll runs once a second, so an edit made elsewhere reaches an open
699
+ page within about that long, and a write that leaves the plan's text identical —
700
+ another process appending only a comment — is not announced; the next save still
701
+ refuses a file it did not render (`409`). It is a poll rather than a file
702
+ watcher, so a change on a filesystem that reports nothing at all is still seen
703
+ at the next request, because every page read re-compares the file.
704
+ - A shared review holds at most eight links. Each is a live bearer credential, so
705
+ a ninth is refused with the cap named rather than minted.
539
706
 
540
707
  ## Non-goals
541
708
 
@@ -551,7 +718,7 @@ file and returns them to the model.
551
718
  |---|---|
552
719
  | Tools | `enter_plan_mode`, `write_plan`, `exit_plan_mode`, and `set_plan_steps` registered with `executionMode: "sequential"`. `enter_plan_mode` is active normally with `readOnlyHint: true`; `write_plan` (`readOnlyHint: false`) and `exit_plan_mode` (`readOnlyHint: true`) are `defaultActive: false`. `set_plan_steps` is active normally (`readOnlyHint: false`, `idempotentHint: true`); the read-only policy default-denies it in plan mode. Each returns typed `details` (`EnterPlanModeDetails`, `WritePlanDetails`, `ExitPlanModeDetails`, `SetPlanStepsDetails`), and `ExitPlanModeDetails` carries the review's `annotations` when a browser approval or save supplied comments; no `outputSchema`/`structuredContent`. |
553
720
  | Commands and flags | `/plan` (toggle, or enter and send a task), `/plan list` (plans index), `/plan review [path]` (browser review), `/skill:init` (skill-backed `AGENTS.md` bootstrap), `--plan` launch flag, `Ctrl+Alt+P` shortcut. |
554
- | State | Enabled flag and the tools it hid persisted with `pi.appendEntry("plan-mode", { enabled, removed })`, and each review's baseline persisted with `pi.appendEntry("plan-baseline", { path, sections })` as one digest per heading section; both rebuilt from `ctx.sessionManager.getBranch()`, so `/resume` and `/tree` restore the mode and the changed markers of the branch they land on. Plan writes a goal only at approval, through `ctx.executeTool("goal", { objective })`: a `planning` goal is promoted to the plan's `## Goal`, a session with none is offered one unless `plan.goalPrompt` is off, and an `active`/`achieved` goal is left alone. A review that shows the same content appends nothing. The last plan path lives in runtime memory for the footer chip, and a bare `/plan review` falls back to the newest plan file in the directory. |
721
+ | State | Enabled flag and the tools it hid persisted with `pi.appendEntry("plan-mode", { enabled, removed })`, and each review's baseline persisted with `pi.appendEntry("plan-baseline", { path, sections })` as one digest per heading section; both rebuilt from `ctx.sessionManager.getBranch()`, so `/resume` and `/tree` restore the mode and the changed markers of the branch they land on. Plan writes a goal only at approval, through `ctx.executeTool("goal", { objective })`: a `planning` goal is promoted to the plan's `## Goal`, a session with none is given the same objective, and an `active`/`achieved` goal is left alone — no path asks first. A review that shows the same content appends nothing. The last plan path lives in runtime memory for the footer chip, and a bare `/plan review` falls back to the newest plan file in the directory. |
555
722
  | Entry requests | `pi.events` channel `PLAN_ENTER_REQUEST` from `lib/plan-entry.ts`: the `goal` workflow asks, with the shared consent sentence, and plan enables the mode on the caller's context without a second dialog. Subscribed on `session_start`, released on `session_shutdown`. |
556
723
  | Hooks | `tool_call` blocks non-read-only calls and forces `subagent` read-only; `before_agent_start` injects the hidden `[PLAN MODE ACTIVE]` context, with a pursued session goal read from the branch through `lib/goal-state.ts`; `context` keeps only the newest plan context while enabled and drops stale ones otherwise. |
557
724
  | Lifecycle | `session_start`/`session_tree` restore state; `session_shutdown` clears the footer status and closes any browser review server. |
@@ -595,8 +762,9 @@ file and returns them to the model.
595
762
  `ctx`, so a listener could not refresh the todo widget; calling the real tool
596
763
  runs its validation and widget sync and needs no change to `todo`. Only the
597
764
  top-level list items of the `Steps` section become steps (the whole document
598
- is the fallback when that heading is absent), so the outline's prose lists
599
- never seed the list. The list is whole-list replacement, so the current items
765
+ is the fallback when that heading is absent), and an indented line continues
766
+ its step, so the outline's prose lists never seed the list. The list is
767
+ whole-list replacement, so the current items
600
768
  are read back from the branch's last `todo` result (the published state
601
769
  channel; no import from that extension) and the steps are merged into them.
602
770
  A rejected seed reports the nested tool's own reason in the approval message
@@ -668,6 +836,25 @@ file and returns them to the model.
668
836
  so annotating never moves the text. That removes the two-column grid, the
669
837
  `bf-review--no-rail` collapse, and the breakpoint ladder, and matches the TUI,
670
838
  where neither review header carries a progress counter.
839
+ - **A poll, not a file watcher.** An open review re-reads its plan once a second
840
+ and announces when the text stopped matching what the page rendered, because the
841
+ review owns the page and not the file: `/plan review` runs in normal mode, so the
842
+ model can rewrite the plan underneath it. The house rule is a poll (see
843
+ `file-browse`'s note): `fs.watch` is the one part of `node:fs` whose availability
844
+ and event semantics differ per platform, and it would add a long-lived resource
845
+ to start and release. The check only announces — it never writes `pageText`,
846
+ which stays the anchor a save is validated against — so a save through the routes
847
+ says nothing, and an announced edit can be announced once more by the read that
848
+ repaints: that read is what updates `pageText`, so it converges instead of
849
+ looping, at the cost of one extra refetch.
850
+ - **One link is one person, and the token table is the roster.** A shared review's
851
+ unit of access is a link, not a room: a token names a role and binds the name its
852
+ owner posts under, so two people on one link are one writer and the second name
853
+ is refused. The owner hands out one link per teammate and takes one back without
854
+ disturbing the rest, and the exit of a link is the exit of one token, one stream,
855
+ and one marker author — which is why the streams are addressed by token rather
856
+ than by role, and why the link table is pure and separate from the listener that
857
+ answers on it.
671
858
 
672
859
  ## Files
673
860
 
@@ -675,7 +862,6 @@ file and returns them to the model.
675
862
  |---|---|
676
863
  | `index.ts` | Wiring: tools, command, flag, shortcut, events, context injection. |
677
864
  | `types.ts` | `PlanModeEntry`, `EnterPlanModeDetails`, `WritePlanDetails`, `ExitPlanModeDetails`, `SetPlanStepsDetails`. |
678
- | `config.ts` | The `plan` section loader: `goalPrompt`, default on. |
679
865
  | `policy.ts` | Plan mode's read-only policy, the shared prompt summary, and the bash-refusal guidance. |
680
866
  | `git-bash.ts` | `checkReadOnlyGit`: the pure allowlist that restricts `bash` to read-only git commands. |
681
867
  | `plans.ts` | Plan-file store: slug/stamp naming, directory resolution (repository, active worktree, or agent directory), containment, atomic `write`/`setSteps`/`saveFeedback`, and `read`/`latest`. |
@@ -687,22 +873,30 @@ file and returns them to the model.
687
873
  | `tools.ts` | Registration entry: composes the four tool registrations in the order the session sees them. |
688
874
  | `tools/` | One module per tool (`enter.ts`, `write.ts`, `exit.ts`, `steps.ts`), plus `todo-seed.ts` (the two nested `todo` writes) and `nested.ts` (reading a nested result). |
689
875
  | `review.ts` | The review gate: one decision from the TUI screen, a dialog, or the browser, the shared browser lifecycle, and the "mark what was shown" baseline rule. |
690
- | `commands.ts` | `/plan` (enter, optional task) and the `/plan review [path]` browser subcommand. |
691
- | `tui.ts` | `PlanViewComponent` (sectioned Markdown body, changed-section summary and marker, section jump, scroll gauge, approve/refine/browser/keep). |
876
+ | `commands.ts` | `/plan` (enter, optional task), `/plan review [path]`, and `/plan share [stop]`. |
877
+ | `tui.ts` | `PlanViewComponent` (sectioned Markdown body, changed-section summary and marker, section jump, scroll gauge, the approve `y/N` question, approve/refine/browser/keep). |
692
878
  | `plan-index.ts` | `/plan list`: the notice text and the docked index screen (title, step progress, age, focused path; Enter reviews). |
693
879
  | `blocks.ts` | Source-line block model (fence-aware headings, paragraphs, list items, code, quotes, tables, rules, blanks). |
694
- | `feedback.ts` | Browser-comment codec: `stripFeedback`, `parseFeedback`, `formatFeedback`, `feedbackSummary`, and payload validation. |
695
- | `web.ts` | `createPlanWebReview`: the review session (SSE close detection, the body cap, settle-after-flush, abort) over the shared loopback bind, writer, and close. |
696
- | `web-router.ts` | Pure request handler: token/`Host`/method checks and the review routes, over the shared page and JSON header sets. |
697
- | `web-page.ts` | The review page shell (header, reading progress, review facts, section outline, shortcut sheet, approve confirmation), its data island, and inline CSS. |
698
- | `web-client.ts` | The `/client.js` source: state and wiring (comment rail, comments, selection quotes, save/approve/back, keyboard nav) over the two fragments below. |
880
+ | `feedback.ts` | Comment codec: authored markers, `stripFeedback`, `parseFeedback`, `formatFeedback` (writer-scoped), `displayFeedback`, `carryFeedback`, `resolveAnchor`, `feedbackSummary`, and payload validation. |
881
+ | `web.ts` | `createPlanWebReview`: the review session (per-token roles, the change stream, the plan-file poll, the body cap, settle-after-flush, abort) over the shared bind, writer, and close. |
882
+ | `web-tokens.ts` | The token table: the role each token names, the collision rule that keeps two roles off one token, and the names the other live links have bound. |
883
+ | `web-streams.ts` | The event streams: who is listening, what a frame is addressed to, and the closed-tab grace that ends the review when the owner's page goes away. |
884
+ | `web-router.ts` | Pure request handler: token role, `Host` allowlist, origin, and method checks through one POST-and-role guard, then the page, comment, owner-save, approve, cancel, and the four share routes. |
885
+ | `web-page.ts` | The review page shell (header, reading progress, review facts, section outline, shortcut sheet, approve confirmation, share and name dialogs), its data island, and inline CSS. |
886
+ | `web-share.ts` | Pure sharing rules: the reachable LAN addresses from an injected snapshot, one tokenized URL, the link table (mint, find, revoke, list, state) with its cap, and the reasons a share is unavailable. |
887
+ | `web-share-session.ts` | The shared listener's lifecycle: its bound address, the link table over the session's tokens, `ShareState`, and invite/revoke/stop/close, injected with the request handler, role map, and frame hooks. |
888
+ | `web-client.ts` | The `/client.js` source: state and wiring (comment rail, save/approve/back, keyboard nav, the stream's lifetime) over the three fragments below. |
889
+ | `web-comments.ts` | The client's comment half: selection quotes, the composer, authored cards and who may edit them, delete-with-undo. |
890
+ | `web-collab.ts` | The client's collaboration half: the link list with copy and per-link revoke, the reviewer's name, and the island refetch a stream frame triggers. |
699
891
  | `web-markdown.ts` | The client's document half: `safeHref` and the inline/table/code/list rendering (interpolated into `web-client.ts`). |
700
- | `web-chrome.ts` | The client's chrome half: the state chip, the status line, the header, reading progress, and the stream's lifetime (interpolated into `web-client.ts`). |
892
+ | `web-chrome.ts` | The client's chrome half: the state chip, the status line, the header, and reading progress (interpolated into `web-client.ts`). |
701
893
  | `path-guard.ts` | Recursive path-argument detection for the read-only backstop. |
702
894
  | [`../../lib/browser.ts`](../../lib/browser.ts) | Cross-platform default-browser opener (shared with `file-browse`). |
703
895
  | [`../../lib/plan-entry.ts`](../../lib/plan-entry.ts) | Plan-mode entry shared with `goal`: the read-only summary, the consent sentence, and the `pi.events` request `plan` answers by enabling the mode. |
704
896
  | [`../../lib/shell.ts`](../../lib/shell.ts) | The shell-escape rule the read-only git guard shares with the worktree isolation guard. |
705
897
  | [`../../lib/highlight.ts`](../../lib/highlight.ts) | First-party escape-first syntax tokenizer (shared with `file-browse`). |
898
+ | [`../../lib/qr.ts`](../../lib/qr.ts) | QR matrix, terminal half-blocks, and inline SVG for the share link (shared with `wechat`). |
899
+ | [`../../lib/http/`](../../lib/http/) | The bind, `Host` allowlist, header sets, and response kit both browser surfaces serve through. |
706
900
  | [`../../lib/web/index.ts`](../../lib/web/index.ts) | Shared Bytefly web brand system: tokens, base rules, `tok-*` colors, layout, the bootstrap, the toggle wiring, the shortcut sheet (`keysSheet`, `keysDialogScript`), and shared markup. |
707
901
  | [`../../lib/http/index.ts`](../../lib/http/index.ts) | Loopback HTTP layer shared with `file-browse`: the page policy and header sets, `ServeResponse`, the request-envelope helpers, bind/respond/close. |
708
902
  | `../../lib/policy.ts` | Shared read-only capability policy (default-deny + `readOnlyHint`). |