@gavin-hu/my-pi-agent 0.19.0 → 0.20.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.
- package/README.md +17 -11
- package/README.zh-CN.md +13 -8
- package/docs/extensions/README.md +1 -1
- package/docs/extensions/board.md +286 -123
- package/docs/extensions/bytefly.md +381 -223
- package/docs/extensions/file-browse.md +14 -7
- package/docs/extensions/job.md +1 -2
- package/docs/extensions/plan.md +56 -11
- package/docs/extensions/session-bus.md +23 -10
- package/docs/extensions/team.md +45 -17
- package/docs/extensions/wechat.md +2 -1
- package/docs/extensions/worktree.md +30 -15
- package/extensions/board/commands.ts +23 -5
- package/extensions/board/format.ts +107 -9
- package/extensions/board/join.ts +1 -1
- package/extensions/board/page.ts +170 -94
- package/extensions/board/result.ts +9 -0
- package/extensions/board/schema.ts +22 -7
- package/extensions/board/seat.ts +9 -10
- package/extensions/board/snapshot.ts +1 -0
- package/extensions/board/tools.ts +186 -21
- package/extensions/board/transport/router.ts +27 -108
- package/extensions/board/transport/server.ts +18 -29
- package/extensions/board/types.ts +67 -0
- package/extensions/board/view/client.ts +23 -13
- package/extensions/board/view/dialog.ts +43 -0
- package/extensions/board/view/keys.ts +28 -24
- package/extensions/board/view/live.ts +5 -171
- package/extensions/board/view/names.ts +91 -0
- package/extensions/board/view/page.ts +48 -88
- package/extensions/board/view/render.ts +213 -190
- package/extensions/board/view/styles.ts +65 -48
- package/extensions/board/view/team.ts +63 -41
- package/extensions/bytefly/footer/budget.ts +6 -5
- package/extensions/bytefly/footer/collect/entries.ts +12 -4
- package/extensions/bytefly/footer/collect/snapshot.ts +8 -17
- package/extensions/bytefly/footer/config.ts +19 -8
- package/extensions/bytefly/footer/price/price.ts +30 -8
- package/extensions/bytefly/footer/register.ts +5 -5
- package/extensions/bytefly/footer/render/format.ts +18 -0
- package/extensions/bytefly/footer/render/lines.ts +23 -7
- package/extensions/bytefly/footer/render/segments.ts +117 -31
- package/extensions/bytefly/footer/types.ts +29 -5
- package/extensions/bytefly/footer/unpriced.ts +42 -17
- package/extensions/bytefly/panel/commands.ts +28 -1
- package/extensions/bytefly/panel/filter.ts +68 -0
- package/extensions/bytefly/panel/format.ts +16 -15
- package/extensions/bytefly/panel/join.ts +56 -13
- package/extensions/bytefly/panel/prices.ts +43 -0
- package/extensions/bytefly/panel/settings.ts +72 -23
- package/extensions/bytefly/panel/tui/entries.ts +26 -8
- package/extensions/bytefly/panel/tui/frame.ts +130 -93
- package/extensions/bytefly/panel/tui/keys.ts +352 -0
- package/extensions/bytefly/panel/tui/list-view.ts +25 -46
- package/extensions/bytefly/panel/tui/panes.ts +76 -0
- package/extensions/bytefly/panel/tui/section-view.ts +126 -0
- package/extensions/bytefly/panel/tui/settings-section.ts +54 -125
- package/extensions/bytefly/panel/tui/settings-view.ts +30 -131
- package/extensions/bytefly/panel/tui/side-pane.ts +224 -0
- package/extensions/bytefly/panel/tui.ts +306 -259
- package/extensions/check/commands.ts +2 -1
- package/extensions/file-browse/files/classify.ts +136 -0
- package/extensions/file-browse/files/files.ts +8 -547
- package/extensions/file-browse/files/index.ts +90 -0
- package/extensions/file-browse/files/list.ts +261 -0
- package/extensions/file-browse/files/tree.ts +101 -0
- package/extensions/file-browse/transport/router.ts +0 -2
- package/extensions/file-browse/view/emit.ts +51 -0
- package/extensions/file-browse/view/html.ts +6 -7
- package/extensions/file-browse/view/markdown.ts +11 -13
- package/extensions/file-browse/view/raw-html.ts +18 -13
- package/extensions/job/runtime.ts +4 -4
- package/extensions/plan/feedback.ts +40 -16
- package/extensions/plan/format.ts +10 -3
- package/extensions/plan/index.ts +17 -1
- package/extensions/plan/plan-files.ts +88 -0
- package/extensions/plan/plans.ts +19 -23
- package/extensions/plan/policy.ts +10 -5
- package/extensions/plan/runtime.ts +31 -2
- package/extensions/plan/todos.ts +51 -3
- package/extensions/plan/tools/exit.ts +10 -1
- package/extensions/plan/tools/todo-seed.ts +4 -2
- package/extensions/plan/tools/write.ts +12 -1
- package/extensions/plan/types.ts +15 -0
- package/extensions/plan/web-blocks.ts +89 -0
- package/extensions/plan/web-chrome.ts +3 -5
- package/extensions/plan/web-client.ts +179 -330
- package/extensions/plan/web-collab.ts +3 -3
- package/extensions/plan/web-comments.ts +18 -8
- package/extensions/plan/web-composer.ts +80 -0
- package/extensions/plan/web-draft.ts +40 -0
- package/extensions/plan/web-keys.ts +134 -0
- package/extensions/plan/web-outline.ts +192 -0
- package/extensions/plan/web-page.ts +4 -1
- package/extensions/plan/web-plan.ts +150 -0
- package/extensions/plan/web-router.ts +0 -1
- package/extensions/plan/web-settle.ts +194 -0
- package/extensions/rewind/commands/review.ts +2 -1
- package/extensions/session-bus/rows.ts +7 -3
- package/extensions/session-bus/runtime.ts +100 -38
- package/extensions/session-bus/store.ts +14 -2
- package/extensions/session-bus/tui.ts +1 -1
- package/extensions/session-bus/types.ts +10 -1
- package/extensions/subagent/commands.ts +2 -1
- package/extensions/team/commands.ts +41 -8
- package/extensions/team/format.ts +14 -0
- package/extensions/team/index.ts +2 -1
- package/extensions/team/tools.ts +30 -9
- package/extensions/team/types.ts +17 -0
- package/extensions/todo/index.ts +4 -1
- package/extensions/todo/nudge.ts +8 -2
- package/extensions/wechat/bridge.ts +43 -172
- package/extensions/wechat/intake.ts +245 -0
- package/extensions/worktree/commands.ts +24 -17
- package/extensions/worktree/exit.ts +12 -6
- package/extensions/worktree/git.ts +22 -14
- package/extensions/worktree/guard.ts +15 -285
- package/extensions/worktree/handoff.ts +10 -2
- package/extensions/worktree/index.ts +2 -1
- package/extensions/worktree/lifecycle.ts +2 -0
- package/extensions/worktree/registry.ts +20 -11
- package/extensions/worktree/root-tools.ts +13 -3
- package/extensions/worktree/runtime.ts +8 -2
- package/extensions/worktree/screen.ts +15 -8
- package/extensions/worktree/shell.ts +303 -0
- package/extensions/worktree/state.ts +1 -1
- package/extensions/worktree/status.ts +42 -9
- package/extensions/worktree/tools.ts +19 -13
- package/lib/README.md +6 -5
- package/lib/board/card.ts +192 -3
- package/lib/board/links.ts +26 -2
- package/lib/board/page-pointer.ts +86 -32
- package/lib/board/round-text.ts +11 -2
- package/lib/board/round.ts +25 -20
- package/lib/board/rules.ts +144 -11
- package/lib/board/store.ts +239 -52
- package/lib/board/view.ts +94 -10
- package/lib/format.ts +22 -0
- package/lib/http/serve.ts +3 -0
- package/lib/http/sink.ts +34 -9
- package/lib/plan-entry.ts +11 -0
- package/lib/policy.ts +14 -1
- package/lib/session-roster.ts +74 -3
- package/lib/spawn.ts +37 -3
- package/lib/stray-fields.ts +45 -0
- package/lib/tool-names.ts +13 -0
- package/lib/tui/glyphs.ts +2 -3
- package/lib/web/README.md +52 -2
- package/lib/web/client-runtime.ts +123 -0
- package/lib/web/index.ts +4 -2
- package/lib/web/markup.ts +88 -1
- package/lib/web/names.ts +42 -0
- package/package.json +1 -1
- package/extensions/board/view/actions.ts +0 -306
- package/extensions/board/writes.ts +0 -99
- package/extensions/bytefly/panel/tui/extensions-view.ts +0 -40
- package/extensions/bytefly/panel/tui/skills-view.ts +0 -50
- package/extensions/job/process.ts +0 -35
|
@@ -93,7 +93,6 @@ selects the root.
|
|
|
93
93
|
| `/events` | Server-Sent Events stream of `status` frames for the open page |
|
|
94
94
|
| `/app.js` | the first-party client script |
|
|
95
95
|
| `/theme.js` | the pre-paint theme bootstrap |
|
|
96
|
-
| `/favicon.ico` | `204` (the icon is a data URI) |
|
|
97
96
|
|
|
98
97
|
## Configuration
|
|
99
98
|
|
|
@@ -171,7 +170,10 @@ global document then project document, project wins — are in
|
|
|
171
170
|
## The page
|
|
172
171
|
|
|
173
172
|
The page is server-rendered HTML with an inline style block and inline SVG
|
|
174
|
-
icons, progressively enhanced by one first-party script (`client.ts`).
|
|
173
|
+
icons, progressively enhanced by one first-party script (`client.ts`). Each page
|
|
174
|
+
names its own subject in the tab — `browse · <folder>`, `browse · <rel>` for a file, the
|
|
175
|
+
`<page> · <subject>` shape the kit's `tabTitle` gives every served page — and links the
|
|
176
|
+
brand mark as an SVG data URI, in the appearance the system is in. A folder
|
|
175
177
|
folds from its twisty, and a `fold all` / `expand all` button beside the filter
|
|
176
178
|
folds the whole tree at once. The tree scrolls in its own pane with quick open
|
|
177
179
|
and the filter pinned above it; below 860px it collapses into a `Files`
|
|
@@ -342,10 +344,10 @@ old and new sides for `lib/highlight`.
|
|
|
342
344
|
|
|
343
345
|
While a server is running, the extension publishes a two-tone `◉ <port>` chip
|
|
344
346
|
(a green circle-dot and a blue port) under the shared `browse` status key. The
|
|
345
|
-
[`bytefly`](./bytefly.md) extension routes it to line 1's right zone
|
|
346
|
-
|
|
347
|
-
`session_shutdown`. The chip is session-local: it
|
|
348
|
-
server, with no cross-session sharing.
|
|
347
|
+
[`bytefly`](./bytefly.md) extension routes it to line 1's right zone, after
|
|
348
|
+
`⎇ branch` and `⑂ worktree` and in the same run as the board page's port chip;
|
|
349
|
+
it is cleared on stop and on `session_shutdown`. The chip is session-local: it
|
|
350
|
+
reflects only this session's server, with no cross-session sharing.
|
|
349
351
|
|
|
350
352
|
The page has its own live status, which is what a left-open tab reads. A `◉ live`
|
|
351
353
|
chip beside `read-only` says whether status is flowing (green), still
|
|
@@ -502,12 +504,17 @@ stops at the first difference because the control, not the checker, is the fix.
|
|
|
502
504
|
| [`../../lib/escape.ts`](../../lib/escape.ts) | `escapeHtml`, the shared HTML escaping used by every renderer |
|
|
503
505
|
| [`../../lib/highlight.ts`](../../lib/highlight.ts) | First-party escape-first syntax tokenizer for the code view (shared with `plan`) |
|
|
504
506
|
| [`../../lib/web/index.ts`](../../lib/web/index.ts) | The shared Bytefly web brand system: tokens, base, components, layout, runtime, and markup |
|
|
505
|
-
| `files/files.ts` |
|
|
507
|
+
| `files/files.ts` | The barrel over the four reads: one surface for the callers, four modules behind it |
|
|
508
|
+
| `files/classify.ts` | What a name is: kind, Markdown, image, MIME type, and the text sample |
|
|
509
|
+
| `files/list.ts` | Capped directory listings and one file's bytes, over injected filesystem reads |
|
|
510
|
+
| `files/tree.ts` | The sidebar tree and the page-level walk (`directoryView`) that returns it with the listing |
|
|
511
|
+
| `files/index.ts` | The quick-open file index, walked once per root up to a cap |
|
|
506
512
|
| `text/diff.ts` | Pure unified-diff and porcelain-status parsing for the diff view |
|
|
507
513
|
| `text/fuzzy.ts` | Fuzzy path matching and ranking for the quick-open palette |
|
|
508
514
|
| `view/markdown.ts` | First-party escape-first Markdown-to-HTML renderer (fenced code highlighted; raw HTML through `raw-html.ts`) |
|
|
509
515
|
| `view/raw-html.ts` | Allowlisted raw-HTML sanitizer: element/attribute policy, suppressed bodies, and cross-block balancing |
|
|
510
516
|
| `view/urls.ts` | Shared link/image URL policy: scheme allowlist and `/view`/`/raw` rewriting |
|
|
517
|
+
| `view/emit.ts` | The anchor and image elements both renderers emit, so a link written in Markdown and the same one written as HTML are one element |
|
|
511
518
|
| `git/git.ts` | Read-only branch/dirty provider for the header chip |
|
|
512
519
|
| `view/icons.ts` | Inline SVG sprite and the per-language mapping |
|
|
513
520
|
| `view/client.ts` | The `/app.js` source: sort, wrap, copy, hunk navigation, reading progress, and the page keymap |
|
package/docs/extensions/job.md
CHANGED
|
@@ -365,7 +365,7 @@ rewritten.
|
|
|
365
365
|
cached from the stdout stream for `status`/`wait`/completion notes; only the
|
|
366
366
|
`/jobs` log pane reads the file.
|
|
367
367
|
- **Pure logic split from IO.** `format.ts` and the reconciliation in
|
|
368
|
-
`registry.ts` are pure;
|
|
368
|
+
`registry.ts` are pure; the runtime takes injectable
|
|
369
369
|
spawn/liveness/kill/clock functions, so the whole suite runs without
|
|
370
370
|
launching a process.
|
|
371
371
|
- **The transcript reuses the `/jobs` grammar.** `job list` renders the same
|
|
@@ -388,7 +388,6 @@ rewritten.
|
|
|
388
388
|
| `schema.ts` | Parameter schema and pure validation (a `JobCall` union). |
|
|
389
389
|
| `config.ts` | The `job` section of `bytefly.json`: loading and clamping. |
|
|
390
390
|
| `format.ts` | Pure formatting, duration, and transcript outcome-line helpers; sanitization is `lib/format.ts`'s `sanitizeUntrustedLine`. |
|
|
391
|
-
| `process.ts` | The job-only process remainder: the `LivenessFn` seam and the `ps` start-time token. Spawn, tree-kill, and liveness itself come from `lib/spawn.ts`. |
|
|
392
391
|
| `paths.ts` | Session/marker/registry/status file-name helpers. |
|
|
393
392
|
| `registry.ts` | Per-session registry load/merge, legacy migration, and pure reconciliation. |
|
|
394
393
|
| `reservations.ts` | Atomic `O_EXCL` job-id reservation and release. |
|
package/docs/extensions/plan.md
CHANGED
|
@@ -22,7 +22,7 @@ Status records what a reader can rely on: **Shipped** is in a tagged release, **
|
|
|
22
22
|
| One writable artifact | `write_plan` | The plan lands as markdown under the agent directory, one store per repository, shared by its worktrees. | Shipped |
|
|
23
23
|
| Approval gate | `exit_plan_mode` | The plan is read back and presented; nothing is written until the user approves it. | Shipped |
|
|
24
24
|
| Session goal at approval | `goal` at approval | A `planning` goal is promoted to the plan's objective, and a session with none gets it, without a second dialog; an active goal is left alone. | Shipped |
|
|
25
|
-
| Steps as todos | `exit_plan_mode` | Approval seeds the plan's steps into the todo list, with the verification section as the last item. | Shipped |
|
|
25
|
+
| Steps as todos | `exit_plan_mode` | Approval seeds the plan's steps into the todo list, with the verification section as the last item; approving the same plan again replaces the items its earlier approval seeded. | Shipped |
|
|
26
26
|
| Plan kept current | `set_plan_steps` | Ticking a step rewrites its checkbox in the file and its todo item in one call. | Shipped |
|
|
27
27
|
| Stale-step protection | `expected` in `set_plan_steps` | A step whose number moved, is out of range, or no longer matches its text refuses the whole batch instead of ticking the wrong line. | Shipped |
|
|
28
28
|
| Cancel without a plan | `cancel_plan_mode`, `/plan cancel` | Write access returns, and no todo items or goal are recorded. | Shipped |
|
|
@@ -36,7 +36,7 @@ Status records what a reader can rely on: **Shipped** is in a tagged release, **
|
|
|
36
36
|
| Capability | How | What you get | Status |
|
|
37
37
|
|---|---|---|---|
|
|
38
38
|
| Review screen | `exit_plan_mode` | A scrollable, themed plan with approve, refine, keep, browser, and exit in the footer. | Shipped |
|
|
39
|
-
| Browser page | `/plan review`, `w` on the review screen | The plan opens in a local browser as a reading-first page with live section and step progress. |
|
|
39
|
+
| Browser page | `/plan review`, `w` on the review screen | The plan opens in a local browser as a reading-first page with live section and step progress. | Partial |
|
|
40
40
|
| Approval in the browser | the `/plan review` page's **Approve & run** | The plan is approved without returning to the terminal, and the steps are seeded. | Shipped |
|
|
41
41
|
| Inline comments | the page's `+ Comment`, **Save feedback** | Comments are saved into the plan file as `> [!REVIEW]` lines and returned to the model. | Shipped |
|
|
42
42
|
| Live page | the page's `events` stream, the plan-file poll | An edit made elsewhere repaints every open page without a reload. | Shipped |
|
|
@@ -140,7 +140,15 @@ the cap and the fix named, and nothing is written.
|
|
|
140
140
|
|
|
141
141
|
The file is the source of truth. On approval the model follows the plan file's
|
|
142
142
|
steps, and refinements rewrite the same file (`write_plan` with `plan_path`), so
|
|
143
|
-
the reviewed artifact is the executed artifact.
|
|
143
|
+
the reviewed artifact is the executed artifact. The text a refinement replaces is
|
|
144
|
+
kept beside it, as a dotfile named for the plan and the revision
|
|
145
|
+
(`.2026-10-26-1008-add-rate-limiting-r001.md`), up to the newest five: that is
|
|
146
|
+
what makes two revisions diffable, and what lets a comment `carryFeedback` moved
|
|
147
|
+
onto another block be checked against the text the reviewer actually read. A
|
|
148
|
+
revision is a dotfile, so no scan lists it as a plan, no `plan_path` opens it, and
|
|
149
|
+
the review never offers it for approval — and a refinement that changed nothing
|
|
150
|
+
keeps nothing, because the copy would be the plan itself. The write's result names
|
|
151
|
+
the revision it kept.
|
|
144
152
|
|
|
145
153
|
### `exit_plan_mode`
|
|
146
154
|
|
|
@@ -171,6 +179,13 @@ the todo list:
|
|
|
171
179
|
todos, and its verification bullets are already covered.
|
|
172
180
|
- Each seeded item is matched back to its step by text, so ticking a step with
|
|
173
181
|
`set_plan_steps` completes the item too.
|
|
182
|
+
- An approval records the step texts it seeded, per plan path, as a `plan-seeded`
|
|
183
|
+
entry. A plan refined after approval is approved again — `write_plan` needs plan
|
|
184
|
+
mode, so a second round is the way back — and that second approval **replaces the
|
|
185
|
+
leftovers its own earlier approval made**: an untouched item whose step was
|
|
186
|
+
renamed or dropped goes, while one somebody started or finished is kept and
|
|
187
|
+
named, because deleting it would erase work a reader did. `write_plan` says the
|
|
188
|
+
list is stale when it refines a plan that was approved before.
|
|
174
189
|
|
|
175
190
|
**Goal-aware.** While planning, the injected context restates a pursued session
|
|
176
191
|
goal as one bullet, so the plan is designed against the objective instead of
|
|
@@ -265,7 +280,8 @@ its plans hold and how old the oldest one is, both derived from the walk that ju
|
|
|
265
280
|
happened. The directory stays the index: no registry file, nothing deletes a plan,
|
|
266
281
|
and one bounded read per plan.
|
|
267
282
|
|
|
268
|
-
`/plan review` is the only subcommand that takes
|
|
283
|
+
`/plan review` is the only subcommand that takes a path; `/plan share` takes the
|
|
284
|
+
one word `stop` after it, and `list` and `cancel` take nothing. Bare `/plan review`
|
|
269
285
|
reviews the plan this session wrote, or, in a later session, the newest plan file
|
|
270
286
|
in the plans directory (newest modification time); `/plan review <path>.md` (or a
|
|
271
287
|
path with a separator) reviews that plan. A line that merely starts with the word
|
|
@@ -531,8 +547,12 @@ line range it shows, and the footer lists only the keys that apply.
|
|
|
531
547
|
<img src="../../assets/screens/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%">
|
|
532
548
|
|
|
533
549
|
Press `w` on the review screen — or run `/plan review [path]` — to open the plan
|
|
534
|
-
in the default browser on `127.0.0.1`.
|
|
535
|
-
|
|
550
|
+
in the default browser on `127.0.0.1`. Its tab reads `plan review · <the plan's
|
|
551
|
+
title>` — the page's name as the header's lockup says it, then the subject, the shape
|
|
552
|
+
`lib/web/markup.ts`'s `tabTitle` gives every served page — rendered with the document
|
|
553
|
+
rather than set by a script, so a strip of tabs names the plan before the page has
|
|
554
|
+
finished loading. The page is a
|
|
555
|
+
reading-first review surface: a sticky header carrying the plan's H1 (falling back to the file-name
|
|
536
556
|
slug), a live status chip (connection, comment count, and unsaved changes), the
|
|
537
557
|
theme toggle, and the review actions, over a hairline reading-progress bar. A
|
|
538
558
|
sticky side panel shows the plan's `Steps` section progress and comment count above a
|
|
@@ -601,7 +621,9 @@ file and returns them to the model.
|
|
|
601
621
|
`DOMParser`, so a code line is never parsed as markup.
|
|
602
622
|
- **The file is the store.** Each comment is written as
|
|
603
623
|
`> [!REVIEW] <comment>` on the line after the block it anchors to, tagged with
|
|
604
|
-
its author when a teammate wrote it (`> [!REVIEW] @alice: <comment>`)
|
|
624
|
+
its author when a teammate wrote it (`> [!REVIEW] @alice: <comment>`), and
|
|
625
|
+
marked `> [!REVIEW moved]` when a refinement carried it off the text it was
|
|
626
|
+
written on. Saving is
|
|
605
627
|
idempotent per writer: that writer's previous markers are replaced and everyone
|
|
606
628
|
else's are kept, so two people reviewing the same plan cannot erase each other.
|
|
607
629
|
Stripping is fence-aware: a `> [!REVIEW]` line inside a code block is
|
|
@@ -617,7 +639,11 @@ file and returns them to the model.
|
|
|
617
639
|
comment whose section is gone too moves to the plan's last block rather than
|
|
618
640
|
disappearing, and every one of those placements lands beside a block, never after
|
|
619
641
|
a blank line, so the page keeps the thread under its block. Only a plan with no
|
|
620
|
-
block at all leaves a comment in the unattached list.
|
|
642
|
+
block at all leaves a comment in the unattached list. Everything past the first
|
|
643
|
+
step is a guess, so a placement that is not the anchored text itself is marked:
|
|
644
|
+
the card carries a `moved` chip whose tooltip says the block changed and the
|
|
645
|
+
note was placed here, and the marker records it in the file, so the fact
|
|
646
|
+
survives a reload, a hand-off, and a save by its own author.
|
|
621
647
|
- **Anchored to source.** `blocks.ts` splits the plan (fence-aware) into
|
|
622
648
|
source-line ranges; a comment is placed after the whole anchored block, so a
|
|
623
649
|
fenced code block is never split. A save re-checks the file against what the
|
|
@@ -705,6 +731,12 @@ both.
|
|
|
705
731
|
Git Bash (`bash`), which must be installed for any shell command to run.
|
|
706
732
|
- The read-only guarantee is a guard rail against accidental writes, not a
|
|
707
733
|
security boundary (see [Security](#security)).
|
|
734
|
+
- **A carried comment's new home is a guess, and only the page says so.** A note a
|
|
735
|
+
refinement could not pin to surviving text is placed by wording overlap, by the
|
|
736
|
+
gap it left, or at the plan's last block; the `moved` chip is what tells a
|
|
737
|
+
reviewer that. A reader of the plan file alone sees `> [!REVIEW moved]`, and the
|
|
738
|
+
terminal review shows the comment as a quote with no chip, so the marker is the
|
|
739
|
+
fact and the chip is one reader's way of showing it.
|
|
708
740
|
- The plans directory is the only index. `/plan list` walks it on every open and
|
|
709
741
|
keeps no registry, so it sees an external edit or a hand-copied plan
|
|
710
742
|
immediately; it lists a file over the 256 KiB plan limit without reading it,
|
|
@@ -897,7 +929,8 @@ both.
|
|
|
897
929
|
| `types.ts` | `PlanModeEntry`, `EnterPlanModeDetails`, `WritePlanDetails`, `ExitPlanModeDetails`, `CancelPlanModeDetails`, `SetPlanStepsDetails`. |
|
|
898
930
|
| `policy.ts` | Plan mode's read-only policy, the shared prompt summary, and the bash-refusal guidance. |
|
|
899
931
|
| `git-bash.ts` | `checkReadOnlyGit`: the pure allowlist that restricts `bash` to read-only git commands. |
|
|
900
|
-
| `plans.ts` | Plan-file store: slug/stamp naming, directory resolution (repository, active worktree, or agent directory), containment,
|
|
932
|
+
| `plans.ts` | Plan-file store: slug/stamp naming, directory resolution (repository, active worktree, or agent directory), containment, `write`/`setSteps`/`saveFeedback`, and `read`/`latest`. |
|
|
933
|
+
| `plan-files.ts` | How a plan file reaches the disk: `writeAtomic` (a dotfile temp, then a rename, so a crash cannot truncate the plan a review is reading) and `keepRevision`, which keeps the text a refinement replaces beside it, numbered, up to the newest five. |
|
|
901
934
|
| `todos.ts` | Approval-time todo merge (pure): the last list replayed from the branch, and the merge of the plan's steps into it with the cap respected. |
|
|
902
935
|
| `steps.ts` | Plan-document structure: the outline, step extraction and the `Steps`-section scope, the plan's own goal (`goalSection`), and the checkbox edit. |
|
|
903
936
|
| `sections.ts` | The one fence-aware heading scan: `extractSections`, `sectionKeys` (the key that tells two identically titled sections apart), and the `HEADING` / `FENCE` / `normalizeHeadingText` vocabulary `blocks.ts` reads a heading through. |
|
|
@@ -920,11 +953,18 @@ both.
|
|
|
920
953
|
| `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. |
|
|
921
954
|
| `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. |
|
|
922
955
|
| `web-share-session.ts` | The shared listener: 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. |
|
|
923
|
-
| `web-
|
|
956
|
+
| `web-keys.ts` | The client's keyboard as typed source (`WEB_KEYS_JS`): `handleKey` — Esc peels the composer before closing, a field and the shortcut sheet own their keys, and `s` is the write the page offers — and `rememberSelection`, which keeps the text a reader selected as the next comment's quote. |
|
|
957
|
+
| `web-settle.ts` | The client's settlement as typed source (`WEB_SETTLE_JS`): `submitAnnotations` — one POST shape whose 409 disables the writes, offers a reload and says the server's own sentence — and the four ways a review ends, of which only the owner's save, approve and close settle it. |
|
|
958
|
+
| `web-plan.ts` | The client's frame as typed source (`WEB_PLAN_JS`, `renderPlanFrame`): it draws every block and its comment rail, then puts the reader back — focus only when it was inside the plan, the active block in place rather than by scrolling. Reads arrive as getters, mutations leave as callbacks, so the bundle keeps every variable. |
|
|
959
|
+
| `web-composer.ts` | The client's composer lifecycle as typed source (`WEB_COMPOSER_JS`): `closeComposer` (over one `ComposerDeps` the client builds where the state is), `reopenComposer` for the draft a reload interrupted, and `commentOnActive`, which returns the index so the client keeps its one `activeIndex`. |
|
|
960
|
+
| `web-draft.ts` | The client's draft guard as typed source (`WEB_DRAFT_JS`): `draftTextarea` answers the open composer's textarea when it holds text, and `guardDraft` refuses the ways off the page, names the reason once, and puts the cursor back in it. |
|
|
961
|
+
| `web-blocks.ts` | The client's block navigation as typed source (`WEB_BLOCKS_JS`): `blockElements`, `setActive` and `moveActive` — which return the index they computed, so the client keeps the one `activeIndex` — and `blockAtLine`, which needs both the rendered block and its island entry. |
|
|
962
|
+
| `web-client.ts` | The `/client.js` source: the composition root, and nothing else by decision — the element handles, the state each rule is handed, one zero-argument adapter per rule, the listeners that attach them, the `EventSource` that keeps a tab's presence known, and the boot order. Every rule it used to hold lives in the `web-*.ts` modules below, where it can be typed and driven. |
|
|
924
963
|
| `web-comments.ts` | The client's comment half: selection quotes, the composer, authored cards and who may edit them, delete-with-undo. |
|
|
925
964
|
| `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. |
|
|
926
965
|
| `web-markdown.ts` | The client's document half: `safeHref` and the inline/table/code/list rendering (interpolated into `web-client.ts`). |
|
|
927
966
|
| `web-chrome.ts` | The client's chrome half: the state chip, the status line, the header, and reading progress (interpolated into `web-client.ts`). |
|
|
967
|
+
| `web-outline.ts` | The client's outline as typed source (`WEB_OUTLINE_JS`): `sectionFor` — the last heading at or before a line — `renderOutline`, which builds the list and hands back the counters it made, `observeSections`, which watches the blocks and returns the one watcher the client keeps, and `renderFacts`, which paints the step progress and counts each comment under its heading. |
|
|
928
968
|
| `path-guard.ts` | Recursive path-argument detection for the read-only backstop. |
|
|
929
969
|
| [`../../lib/browser.ts`](../../lib/browser.ts) | Cross-platform default-browser opener (shared with `file-browse`). |
|
|
930
970
|
| [`../../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. |
|
|
@@ -943,7 +983,7 @@ both.
|
|
|
943
983
|
bun test extensions/plan
|
|
944
984
|
```
|
|
945
985
|
|
|
946
|
-
|
|
986
|
+
Twelve co-located suites, declared in `test/suite-manifest.ts`:
|
|
947
987
|
|
|
948
988
|
- `plans.test.ts` — the plan-file store: naming, directory resolution, atomic
|
|
949
989
|
writes, `read`/`latest`/`index`, containment, `setSteps`, `saveFeedback`, and
|
|
@@ -958,6 +998,11 @@ Eleven co-located suites, declared in `test/suite-manifest.ts`:
|
|
|
958
998
|
- `web.test.ts` — the browser review: the comment codec, the router, the page
|
|
959
999
|
roles and page shell, the client script, the stream, the plan-file poll, the
|
|
960
1000
|
share rules, and a real loopback session.
|
|
1001
|
+
- `web-behaviour.test.ts` — the served review, driven in a real browser through
|
|
1002
|
+
the router: the blocks and the plan's name, a block's comment control opening the
|
|
1003
|
+
composer with Escape peeling it before the review, a stored comment in its block's
|
|
1004
|
+
rail and the header's count, and the outline jumping to the section it names. It
|
|
1005
|
+
skips where no browser is installed, naming `PI_CHROME`.
|
|
961
1006
|
- `web-share.test.ts` — the LAN address selector, `shareUrl`, the link table with
|
|
962
1007
|
its cap, and the reasons a share is unavailable.
|
|
963
1008
|
- `commands.test.ts` — `/plan review`, `/plan list`, `/plan share`, and the one
|
|
@@ -16,15 +16,15 @@ Status records what a reader can rely on: **Shipped** is in a tagged release, **
|
|
|
16
16
|
|
|
17
17
|
| Capability | How | What you get | Status |
|
|
18
18
|
|---|---|---|---|
|
|
19
|
-
| Membership | `session join [label]`, `/team join [<label>]`, the board's `w` | The join: this session is on the bus under a stable label peers address, and listens for mail unless told not to. A label given to `session join` or `/team join` is also Pi's session name, so the footer and `/resume` agree with the record; `w` joins under the name the bus derives and writes nothing to Pi. |
|
|
20
|
-
| Peer discovery | `session status` | Every active session with its unread count and claims, plus this session's identity, in one read. |
|
|
19
|
+
| Membership | `session join [label]`, `/team join [<label>]`, `/board`, any `board` action that writes, the board's `w` | The join: this session is on the bus under a stable label peers address, and listens for mail unless told not to. A label given to `session join` or `/team join` is also Pi's session name, so the footer and `/resume` agree with the record; `/board` joins as the page's seat (`team-lead`) or, on a page it is only watching, under its own label; the first `board` action that writes joins under the name the session already has; `w` joins under the name the bus derives and writes nothing to Pi. | Shipped |
|
|
20
|
+
| Peer discovery | `session status` | Every active session with its unread count and claims, plus this session's identity, in one read — and a session whose process is gone says so, its paths free from that read on. | Unreleased |
|
|
21
21
|
| Live board | `/sessions` | Sessions, this session's unread mail, and every claim held on the repository, on one screen. | Shipped |
|
|
22
22
|
| Board actions | `/sessions` (`w`, `x`) | Start or stop listening from the board, or release an own claim behind a confirmation, without leaving it. | Shipped |
|
|
23
23
|
| Short messages | `session send` | One message per recipient, addressed by label or id, with a broadcast for events. | Shipped |
|
|
24
24
|
| Message kinds | `session send` with `kind` | A `message`, `event`, `ask`, or `handoff` keeps its meaning; `ask` does not block the sender, and a `handoff` is named as the work it assigns. | Shipped |
|
|
25
25
|
| Message paths | `session send` with `paths` | A message can name the repository paths it concerns, echoed in the inbox row. | Shipped |
|
|
26
26
|
| Inbox on demand | `session inbox` | Up to `limit` messages, oldest first, consumed only when the model asks. | Shipped |
|
|
27
|
-
| Listening | `session join` | New mail reported at the next turn; `join({wake: true})` raises one turn once the agent is idle, and `join({listen: false})` stops it. |
|
|
27
|
+
| Listening | `session join` | New mail reported at the next turn; `join({wake: true})` raises one turn once the agent is idle, and `join({listen: false})` stops it. The record carries whether this session listens, so a peer's status read answers it without asking. | Shipped |
|
|
28
28
|
| Path claims | `session claim`, `session release` | A file or directory prefix held by one session at a time. | Shipped |
|
|
29
29
|
|
|
30
30
|
## Quickstart
|
|
@@ -55,12 +55,12 @@ collapsed line that expands to the board's rows.
|
|
|
55
55
|
|
|
56
56
|
| Action | Inputs | Result |
|
|
57
57
|
|---|---|---|
|
|
58
|
-
| `status` | — | Active sessions with unread counts, claims, and this session's identity. Prunes stale records. A read: it never adds this session to the bus. |
|
|
58
|
+
| `status` | — | Active sessions with unread counts, their state, claims, and this session's identity. A session whose process is gone is listed as `gone` until its heartbeat ages out, and its claims are not listed at all. Prunes stale records. A read: it never adds this session to the bus. |
|
|
59
59
|
| `send` | `to`, `text`, optional `kind`, optional `paths` | Writes one message per recipient. `to: "*"` is only valid with `kind: "event"`. |
|
|
60
60
|
| `inbox` | optional `limit` | Returns and deletes up to `limit` messages, oldest first (default 20, max 50). |
|
|
61
61
|
| `claim` | `paths` | Claims repository-relative files or directory prefixes; returns conflicts with their owner. |
|
|
62
62
|
| `release` | `paths` | Removes claims owned by this session; reports paths owned by someone else. |
|
|
63
|
-
| `join` | optional `label`, `wake`, `listen` | Joins the bus, names this session on it and in Pi when a label is given — the same path and uniqueness rule as `/team join`, so the footer and `/resume` agree with the record peers address — and starts listening for mail. `wake: true` raises one turn per burst once the agent is idle; `wake: false` reports at the next turn; `listen: false` joins without listening. A label a live session holds is refused
|
|
63
|
+
| `join` | optional `label`, `wake`, `listen` | Joins the bus, names this session on it and in Pi when a label is given — the same path and uniqueness rule as `/team join`, so the footer and `/resume` agree with the record peers address — and starts listening for mail. `wake: true` raises one turn per burst once the agent is idle; `wake: false` reports at the next turn; `listen: false` joins without listening. A label a live session holds is refused, naming the session that holds it — its id, since the label it holds is the one you asked for — and the refusal leaves both names and the watch alone. The result reports the label the bus settled on and whether mail will reach this session. |
|
|
64
64
|
|
|
65
65
|
A claim is all-or-nothing: if one path conflicts, nothing is claimed. A claim on
|
|
66
66
|
a directory conflicts with claims below it and above it, so `src/api` and
|
|
@@ -167,8 +167,11 @@ listening watch is what raises the turn.
|
|
|
167
167
|
session to the bus, so an unjoined session reads a board it is not on until it
|
|
168
168
|
joins or acts.
|
|
169
169
|
- **Membership is per session and session-only.** It survives neither a resume nor a
|
|
170
|
-
new process
|
|
171
|
-
|
|
170
|
+
new process: the record and every claim it held go when the session stops, and a
|
|
171
|
+
resumed pane is off the bus until it joins again. What does survive is everything
|
|
172
|
+
stored as a file — unread mail is previewed once more after `/resume`, and the
|
|
173
|
+
label a rejoining session takes is the same one, because it derives from Pi's
|
|
174
|
+
session name (`runtime.test.ts`, *a resume*).
|
|
172
175
|
- **The board acts on your own state only.** `w` starts or stops listening and `x`
|
|
173
176
|
releases a claim it holds. Mail is a preview, not a consume: the board never
|
|
174
177
|
deletes a message, never marks one reported, and never touches a peer's inbox or
|
|
@@ -178,8 +181,18 @@ listening watch is what raises the turn.
|
|
|
178
181
|
both sides to keep replying; the answer is to stop listening on one of them.
|
|
179
182
|
- **No broadcast history.** A broadcast reaches the sessions active at send time;
|
|
180
183
|
a session that joins later does not receive it.
|
|
181
|
-
- **No forced claims.**
|
|
182
|
-
|
|
184
|
+
- **No forced claims.** There is no takeover while an owner can still answer: a
|
|
185
|
+
claim is released by its owner, or by the next bus call once the owner's process
|
|
186
|
+
is gone. A *hung* owner still blocks its paths until the session TTL expires,
|
|
187
|
+
because a running process is not evidence of a working one.
|
|
188
|
+
- **A dead process is named, not waited out.** A record carries its `pid` and, where
|
|
189
|
+
the host can name one, that process's start time. A pid that is no longer running,
|
|
190
|
+
or one whose start time is not the record's (a reused pid), reads `gone` at once:
|
|
191
|
+
its label is free, the paths it held are nobody's, and `session status` and
|
|
192
|
+
`/sessions` count it. It keeps its row until its heartbeat ages out, so a lead can
|
|
193
|
+
see what died and reclaim it. A record with no `pid` — and a platform without
|
|
194
|
+
`ps`, such as Windows — falls back to the heartbeat rather than being called dead
|
|
195
|
+
on no evidence.
|
|
183
196
|
- **Bounded by TTL.** An idle session with a stopped heartbeat is pruned after
|
|
184
197
|
ten minutes; its record, its claims, and an inbox directory left empty go with
|
|
185
198
|
it, and its unread mail expires after 24 hours. A claim belongs to a record
|
|
@@ -202,7 +215,7 @@ listening watch is what raises the turn.
|
|
|
202
215
|
| Tools | `session` — one action-shaped tool; `readOnlyHint: false`, `destructiveHint: true`, `openWorldHint: false`, `executionMode: "sequential"`. |
|
|
203
216
|
| Commands | `/sessions` — the board in `tui`, the listing elsewhere; no arguments and no sub-commands. |
|
|
204
217
|
| UI | `tui` mounts `SessionListComponent` through `withPolledScreen` (rails suppressed, a 1 s read-only poll of `snapshot()` + `peek()`); every other mode prints through `ctx.ui.notify`. No status chip, so listening never changes the footer. |
|
|
205
|
-
| Events | `session_start` resolves the repository and arms the mail trigger, without joining; `before_agent_start` reports queued non-wake mail at the next turn
|
|
218
|
+
| Events | `session_start` resolves the repository and arms the mail trigger, without joining; `session_tree` only repaints, since navigating the tree changes no pending record; `agent_start` marks the run busy; `before_agent_start` reports queued non-wake mail at the next turn; `agent_settled` retries a wake the run suppressed; `context` prunes stale notices; `session_shutdown` clears the heartbeat, the watch ticker, and the record. The seven hooks are `lib/report.ts`'s `registerPendingChannel`, not a local copy. |
|
|
206
219
|
| State | Files under `<agent-dir>/session-bus/<repo-key>/`; nothing in the session branch. Listening is session-only. `/resume` re-registers the same session id. |
|
|
207
220
|
| Lifecycle | The factory only registers. The runtime starts on `session_start`; heartbeat, watch ticker, and cleanup belong to it. Disabling `session-bus` through `PI_DISABLED_EXTENSIONS` registers nothing. |
|
|
208
221
|
| Mode | Uses `ctx.mode` to decide whether `/sessions` mounts the board and how a label is derived; nothing joins at start in any mode; `askHuman` is not involved and no status chip is published. |
|
package/docs/extensions/team.md
CHANGED
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
`board` owns the cards. `team` owns the lead's three actions: `assign` hands a card to a
|
|
4
4
|
session that is **already running**, `offer` hands one to whoever is free, and `reclaim`
|
|
5
|
-
takes one back when its owner has gone quiet.
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
takes one back when its owner is gone or has gone quiet. `assign` writes the card and puts
|
|
6
|
+
a handoff on the session bus, `offer` writes only the moment it was made — never an owner,
|
|
7
|
+
because the first peer to claim is what makes an offer into work — and `reclaim` writes the
|
|
8
|
+
card alone, so it needs no bus.
|
|
9
|
+
The report says what the peer will actually do with it.
|
|
8
10
|
|
|
9
11
|
Nothing starts a process. A message lands in the peer's inbox and is reported at its next
|
|
10
12
|
turn, so `assign` and `offer` are messages, not launches.
|
|
11
13
|
|
|
12
|
-
<img src="../../assets/screens/team.png" alt="The Team tab as a dashboard: on the left, a Needs a decision queue of rows with a coloured kind edge, the reason on its own line, and
|
|
14
|
+
<img src="../../assets/screens/team.png" alt="The Team tab as a dashboard: on the left, a Needs a decision queue of rows with a coloured kind edge, the reason on its own line, and the owner as a chip on the row's right; on the right, a Teammates list with a state dot, label, you chip, mail badge, a not listening chip on a peer whose watch is off, and a line of what each holds; and one muted Done line under both" width="100%">
|
|
13
15
|
|
|
14
16
|
*The Team tab: the cards that need the lead, then who is on the team — including an
|
|
15
17
|
owner whose label nothing answers to, and a teammate that is not listening.*
|
|
@@ -21,11 +23,11 @@ owner whose label nothing answers to, and a teammate that is not listening.*
|
|
|
21
23
|
| Join the team | `/board` as the lead, `/team join <label>` as a teammate, or any card you work | The session is on the team under a label peers address, listening, and `/team` and the Team tab list it |
|
|
22
24
|
| Hand a feature to a running session | `team` action `assign` | The card is `doing`, owned by that session, and a handoff is in its inbox |
|
|
23
25
|
| Tell the peer what to do first | `team` action `assign` with `note` | The handoff carries the card, its paths, its plan, and that line |
|
|
24
|
-
| Hand a feature to whoever is free | `team` action `offer` | Every other live session gets an event naming the card; the card stays unowned until one claims it |
|
|
26
|
+
| Hand a feature to whoever is free | `team` action `offer` | Every other live session gets an event naming the card; the card stays unowned until one claims it, and it records when the offer was made, so a lead can tell an offer nobody took from a card nobody has looked at |
|
|
25
27
|
| Know whether a message will be woken for | an `assign` or `offer` result, `/team`, and the Team tab | All three name the peers whose record says their next turn is when it lands: the result as `Not listening: <labels>`, `/team` as a lowercase `not listening:` inside its presence line, the Team tab as a chip |
|
|
26
28
|
| Take a card back | `team` action `reclaim` | The owner clears, the card returns to To do, and its notes stay |
|
|
27
29
|
| Keep the round to the team | `team` | Only `assign`, `offer`, and `reclaim`: no card is created, moved, planned, or finished here — that is `board`'s surface |
|
|
28
|
-
| See the round without a browser | `/team` | `buildRound`'s summary — the one the page's Team tab is served — laid out as text by `formatRound`: what needs a decision, who holds what, then one presence line for who is live, who is not listening,
|
|
30
|
+
| See the round without a browser | `/team` | `buildRound`'s summary — the one the page's Team tab is served — laid out as text by `formatRound`: what needs a decision, who holds what, then one line naming the cards a dead owner left behind, and one presence line for who is live, who is not listening, who has gone quiet, and who is gone. The rollup itself is capped at ten lines, then `… N more`, and the gone and presence lines follow the cap rather than counting against it — the cap is a notify's limit, not a screen's. An empty board is one line |
|
|
29
31
|
|
|
30
32
|
## Quickstart
|
|
31
33
|
|
|
@@ -66,9 +68,10 @@ who is live but not listening. `/board status` repeats only what the last `/boar
|
|
|
66
68
|
session did about the team — a join made by `/team join` or by a card write is not on that
|
|
67
69
|
line, and a page that is not running prints no team clause at all.
|
|
68
70
|
|
|
69
|
-
**The seat is not a session.** The page
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
**The seat is not a session.** The page is a view: it has one name, `team-lead`, and an id
|
|
72
|
+
of its own derived from it, and it writes nothing, so it holds no card for a lead to take
|
|
73
|
+
back. Its cards come from the session that serves it, whose label is the same and whose id
|
|
74
|
+
is not: a card that session claimed is the session's, and `release` returns it.
|
|
72
75
|
|
|
73
76
|
## Tool
|
|
74
77
|
|
|
@@ -78,12 +81,26 @@ label is shared with the session that serves the page; the ids are not.
|
|
|
78
81
|
| Action | Parameters | Behaviour |
|
|
79
82
|
|---|---|---|
|
|
80
83
|
| `assign` | `card`, `to`, `note?` | Checks the card's preconditions, sends the handoff as a `handoff` message, then records the owner |
|
|
81
|
-
| `offer` | `card`, `note?` | Checks the preconditions a claim would meet, broadcasts the card as an `event` to every other live session, and
|
|
84
|
+
| `offer` | `card`, `note?` | Checks the preconditions a claim would meet, broadcasts the card as an `event` to every other live session, then records the offer on the card — the moment, and nothing else: status and owner are untouched |
|
|
82
85
|
| `reclaim` | `card` | Clears the owner and returns the card to To do, keeping its notes and plan |
|
|
83
86
|
|
|
84
87
|
`card` takes `4` or `#4`. `to` is a teammate's label or id, as `/team` lists them — a
|
|
85
88
|
label nobody answers to cannot be handed a card, and the result says so.
|
|
86
89
|
|
|
90
|
+
The parameters are one flat object, so a field is published to every action at once.
|
|
91
|
+
An action reads only the fields in its row above, and a call carrying one that its
|
|
92
|
+
action does not read is **refused**, naming the action that takes it — not ignored.
|
|
93
|
+
`to` is the one that matters: an `offer` that named a peer was broadcast to every live
|
|
94
|
+
session while the lead believed it had named one, and a `reclaim` that named one took
|
|
95
|
+
the card back from its holder anyway.
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
offer does not act on to: `assign` takes it.
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`ACTION_FIELDS` in `types.ts` is the table that answers this, `lib/stray-fields.ts` is
|
|
102
|
+
the sentence, and a test holds every published parameter to an action that acts on it.
|
|
103
|
+
|
|
87
104
|
### The order is the point
|
|
88
105
|
|
|
89
106
|
`assign` sends **before** it assigns, and each failure leaves a state the report names:
|
|
@@ -94,10 +111,15 @@ label nobody answers to cannot be handed a card, and the result says so.
|
|
|
94
111
|
| the send | untouched — `todo`, no owner | `#4 was not handed off to billing-core: …`, plus `The card is unchanged.` |
|
|
95
112
|
| the assignment, after a successful send | whatever the other writer left | `#4 was handed off to billing-core, but the card changed before it could be assigned: …`, and the peer is told to `board claim` it |
|
|
96
113
|
|
|
97
|
-
`offer`
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
114
|
+
`offer` broadcasts **before** it records, for the same reason: an offer nobody received
|
|
115
|
+
must not leave a record claiming one happened. A refused broadcast leaves the card exactly
|
|
116
|
+
as the rules read it, and the write that follows changes one field — when the offer was
|
|
117
|
+
made. If the card changed in between, the peers already have the message, so the refusal
|
|
118
|
+
says the offer stands as a message and the board is what it now says. The first `board
|
|
119
|
+
claim` is what turns an offer into work, and the claim is what clears the record: every
|
|
120
|
+
answer to an offer — a claim, an assignment, a release — takes it with it, so a record
|
|
121
|
+
here means the offer is still open. Two peers claiming at once are settled by the store's
|
|
122
|
+
compare-and-set; one claim wins, and the other is refused with the owner named.
|
|
101
123
|
|
|
102
124
|
`delivered` in the details means a message file was written for that label — not that the
|
|
103
125
|
peer read it. `notListening` names the peers it reached whose record says they are not
|
|
@@ -118,7 +140,11 @@ read it with `session inbox`, record the plan, and start. The card is already th
|
|
|
118
140
|
moves it to `done` or `blocked`.
|
|
119
141
|
|
|
120
142
|
An offer is advice: nothing is theirs until they claim it, and a card nobody claims stays
|
|
121
|
-
`todo`, so it keeps showing up as unassigned in the round
|
|
143
|
+
`todo`, so it keeps showing up as unassigned in the round — with its age, which is the one
|
|
144
|
+
thing an event cannot carry. `board` prints it as `offered 1m ago, and nobody took it`, the
|
|
145
|
+
card face says `offered 1m ago · nobody took it`, and the Team tab's queue says
|
|
146
|
+
`offered 1m ago — nobody took it` in place of "nobody holds it". A lead reading that has a
|
|
147
|
+
decision the plain unassigned row never offered: re-offer, `assign` it, or take it.
|
|
122
148
|
|
|
123
149
|
## Security
|
|
124
150
|
|
|
@@ -150,8 +176,10 @@ An offer is advice: nothing is theirs until they claim it, and a card nobody cla
|
|
|
150
176
|
every ten seconds and the record is believed for ten minutes, so `quiet:` means the
|
|
151
177
|
record went stale rather than that the peer died, while `not listening` is the separate
|
|
152
178
|
`watching` field, written when the session joins or arms and disarms its watch.
|
|
153
|
-
- **Plan mode makes this unavailable.** `session` is a
|
|
154
|
-
|
|
179
|
+
- **Plan mode makes this unavailable.** Neither `team` nor `session` is a read-only tool, so
|
|
180
|
+
plan mode refuses both by name and tells the model to exit and get approval; the board
|
|
181
|
+
stays readable, since `list` is the one action it admits. Assign after the plan is
|
|
182
|
+
approved.
|
|
155
183
|
- **Ownership is advisory.** `assign` does not create a path claim; the peer still claims
|
|
156
184
|
the paths it edits through `session`.
|
|
157
185
|
|
|
@@ -594,7 +594,8 @@ status, which is what the old start/stop guidance used to do.
|
|
|
594
594
|
| File | Responsibility |
|
|
595
595
|
|---|---|
|
|
596
596
|
| `index.ts` | Factory: gate, register the command and tool, wire the lifecycle events, build the bridge, open peer sessions from the host context. |
|
|
597
|
-
| `bridge.ts` | The composition root: wires the poll loop, delivery, presence, admission, and inbound modules to the live client/account/cursor/state; owns the connection lifecycle,
|
|
597
|
+
| `bridge.ts` | The composition root: wires the poll loop, delivery, presence, admission, and inbound modules to the live client/account/cursor/state; owns the connection lifecycle, the status snapshot, and `sendToOwner`. |
|
|
598
|
+
| `intake.ts` | The inbound path: who is refused, what is a replay, what is a command, what answers a pending prompt, and what is queued or turned away; the admission predicates live here too. |
|
|
598
599
|
| `tools.ts` | The `send_wechat` tool: owner-only proactive text or one image, with the neither-or-both refusal. |
|
|
599
600
|
| `config.ts` | The `wechat` section of `bytefly.json` loading and normalization. |
|
|
600
601
|
| `types.ts` | Wire and domain types. |
|
|
@@ -65,7 +65,7 @@ four are `executionMode: "sequential"`, and only `list_worktrees` is read-only.
|
|
|
65
65
|
| Tool | Usage |
|
|
66
66
|
|---|---|
|
|
67
67
|
| `enter_worktree` | `name` to create a worktree, or `path` to enter an existing one. Errors if already isolated. |
|
|
68
|
-
| `exit_worktree` | optional `remove` and `keepBranch`. Returns the clean/keep/remove decision and a `handoff` (`branch`, `base`, `ahead`, `merged`). |
|
|
68
|
+
| `exit_worktree` | optional `remove` and `keepBranch`. Returns the clean/keep/remove decision and a `handoff` (`branch`, `base`, `ahead`, `commits`, `merged`). |
|
|
69
69
|
| `prune_worktrees` | no parameters. Removes eligible managed worktrees and reports the rest. |
|
|
70
70
|
| `list_worktrees` | no parameters. Read-only status: active root, inactive overrides, isolation gaps, managed worktrees. |
|
|
71
71
|
|
|
@@ -77,11 +77,14 @@ it.
|
|
|
77
77
|
`exit_worktree` returns to the main checkout. A clean worktree is removed
|
|
78
78
|
automatically; one with uncommitted changes or new commits prompts (or is kept
|
|
79
79
|
in non-interactive mode). Its result ends with a **handoff**: the branch, how
|
|
80
|
-
many commits it holds beyond the merge target,
|
|
81
|
-
contains them, and the `git merge --no-ff`
|
|
82
|
-
finish the task.
|
|
83
|
-
|
|
84
|
-
|
|
80
|
+
many commits it holds beyond the merge target, how many it made since it was
|
|
81
|
+
created, whether that target already contains them, and the `git merge --no-ff`
|
|
82
|
+
and `gh pr create` commands that finish the task. A branch with no commits of its
|
|
83
|
+
own says so rather than claiming a merge — a worktree that never committed is an
|
|
84
|
+
ancestor of its base, and "merged" would read as work that landed. The tool
|
|
85
|
+
never commits, merges, pushes, or opens the PR. A branch that still holds work
|
|
86
|
+
(uncommitted changes, or commits not reachable from the recorded base) is never
|
|
87
|
+
force-deleted, even with `remove: true`.
|
|
85
88
|
|
|
86
89
|
While isolated, `read`/`write`/`edit`/`bash`/`grep`/`find`/`ls` all resolve
|
|
87
90
|
relative paths inside the worktree, and:
|
|
@@ -113,6 +116,12 @@ instead, so there is no gap to report. `unverifiable` appears only while
|
|
|
113
116
|
reset at session start. `doctor` reports the `powershell` switch as `degraded`
|
|
114
117
|
when it is off and notes the two structural gaps, which no setting closes.
|
|
115
118
|
|
|
119
|
+
Managed worktrees follow the header, one row each, as `path`, branch, and the
|
|
120
|
+
marks in brackets: `current`, `missing`, `dirty (2)`, `ahead 3`, `behind 1`, `no
|
|
121
|
+
commits`, `commits 4 merged`, `locked`, `used 2d ago`. A row with nothing else to
|
|
122
|
+
report says `clean` — the same word the `/worktree` screen prints, because both
|
|
123
|
+
read `statusMarks` rather than each deciding what an empty row means.
|
|
124
|
+
|
|
116
125
|
## Commands and flags
|
|
117
126
|
|
|
118
127
|
| Command / flag | What it does |
|
|
@@ -136,10 +145,14 @@ being ignored.
|
|
|
136
145
|
<img src="../../assets/screens/worktree.png" alt="The /worktree screen: a Worktrees title rule with a `4 worktrees · 1 dirty · 1 ahead` summary, the current dark-mode row carrying its ahead count, a dirty row, a merged row, and a stale-locked row, the focused row's path and base with the Not isolated gap under it, and the key hints" width="100%">
|
|
137
146
|
|
|
138
147
|
`/worktree` on its own opens a list screen: one row per managed worktree with its
|
|
139
|
-
state (current, dirty, ahead, behind, merged, locked, last used), the
|
|
140
|
-
worktree's base and isolation gaps under the focused row, and a detail
|
|
141
|
-
the selected one.
|
|
142
|
-
|
|
148
|
+
state (current, dirty, ahead, behind, commits, merged, locked, last used), the
|
|
149
|
+
active worktree's base and isolation gaps under the focused row, and a detail
|
|
150
|
+
pane for the selected one. A worktree that has not committed since it was created
|
|
151
|
+
says `no commits` instead of `merged`: it is an ancestor of its base, which git
|
|
152
|
+
alone cannot tell from a branch that landed. When work did land, the row says how
|
|
153
|
+
much — `commits 2 merged`. Outside a terminal — print, RPC, JSON — the same
|
|
154
|
+
facts print as text instead, which is also what the model's `list_worktrees`
|
|
155
|
+
reads.
|
|
143
156
|
|
|
144
157
|
| Key | What it does |
|
|
145
158
|
|---|---|
|
|
@@ -298,10 +311,11 @@ use, falling back to directory mtime for a worktree with no record.
|
|
|
298
311
|
writes are guarded (relative paths are not isolated), and `list_worktrees`
|
|
299
312
|
reports it. `skipOverrides` is read at load time, from the process working
|
|
300
313
|
directory.
|
|
301
|
-
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
one
|
|
314
|
+
- The commit count behind `no commits` comes from the commit a worktree was
|
|
315
|
+
created at, which the registry records. A worktree this package did not create
|
|
316
|
+
has no fork point on record, so it falls back to what `git branch --merged`
|
|
317
|
+
says: for one of those, a worktree that never committed still reads as merged.
|
|
318
|
+
Read `git -C <path> status` before removing one.
|
|
305
319
|
- The guard is best-effort at the shell level: command substitution, `eval`, and
|
|
306
320
|
`sh -c` are only covered when `blockUnparsableCommands` is enabled, and real
|
|
307
321
|
isolation of arbitrary shell writes needs an OS sandbox. `/worktree` names the
|
|
@@ -330,7 +344,7 @@ use, falling back to directory mtime for a worktree with no record.
|
|
|
330
344
|
| Contract | Detail |
|
|
331
345
|
|---|---|
|
|
332
346
|
| Registration | The default export calls `registerWorktree`: root-tool overrides, the `--worktree` flag, lifecycle listeners, model tools, and the `/worktree` command. Nothing long-lived starts at load. |
|
|
333
|
-
| Tools | `enter_worktree`, `exit_worktree`, `prune_worktrees`, `list_worktrees` via `registerTool`. All `executionMode: "sequential"`. Annotations: `enter_worktree` destructive + open-world; `exit_worktree`/`prune_worktrees` destructive; `list_worktrees` read-only. No `outputSchema`; structured state is returned in tool-result `details` — `exit_worktree` carries `{ state, removed, handoff }`, where `handoff` is `{ branch, base, ahead, merged }
|
|
347
|
+
| Tools | `enter_worktree`, `exit_worktree`, `prune_worktrees`, `list_worktrees` via `registerTool`. All `executionMode: "sequential"`. Annotations: `enter_worktree` destructive + open-world; `exit_worktree`/`prune_worktrees` destructive; `list_worktrees` read-only. No `outputSchema`; structured state is returned in tool-result `details` — `exit_worktree` carries `{ state, removed, handoff }`, where `handoff` is `{ branch, base, ahead, commits, merged }` (`commits` absent when the worktree has no recorded base commit). |
|
|
334
348
|
| Built-in overrides | `read`/`write`/`edit`/`bash`/`grep`/`find`/`ls` are re-registered under the same names with `defaultActive: false`; each spreads the built-in definition and replaces only `execute`. `skipOverrides` is applied at load time. `powershell` is deliberately not overridden: it cannot be re-rooted without a guard, so the `tool_call` handler refuses it (see `guard.blockPowershell`). |
|
|
335
349
|
| Commands | `/worktree` via `registerCommand`, with argument completions for `enter`, `exit`, and `prune`. The bare command mounts `screen.ts` in a terminal and prints the status text otherwise; `enter`/`exit`/`prune` refuse while plan mode is read-only. |
|
|
336
350
|
| Flags | `--worktree` via `registerFlag`. |
|
|
@@ -407,6 +421,7 @@ one read and two renderings.
|
|
|
407
421
|
| `root-tools.ts` | Load-time built-in overrides bound to the active root |
|
|
408
422
|
| `git.ts` | Git plumbing (shared runner + read helpers) |
|
|
409
423
|
| `guard.ts` | Path, shell, and git-redirect isolation checks, plus the `powershell` refusal |
|
|
424
|
+
| `shell.ts` | The command-line reader those checks ask: words, segments, and redirection targets |
|
|
410
425
|
| `gaps.ts` | The isolation gaps a session carries, and how they read (pure) |
|
|
411
426
|
| `include.ts` | `.worktreeinclude` matcher and copy |
|
|
412
427
|
| `config.ts` / `state.ts` / `status.ts` | Config, session entry shape, status text and the shared snapshot |
|