@gavin-hu/my-pi-agent 0.19.0 → 0.21.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 +19 -12
- package/README.zh-CN.md +15 -9
- package/docs/extensions/README.md +2 -1
- package/docs/extensions/board.md +329 -151
- package/docs/extensions/bytefly.md +427 -273
- package/docs/extensions/file-browse.md +46 -44
- package/docs/extensions/job.md +1 -2
- package/docs/extensions/plan.md +86 -24
- package/docs/extensions/serve.md +129 -0
- package/docs/extensions/session-bus.md +23 -10
- package/docs/extensions/team.md +56 -28
- package/docs/extensions/wechat.md +2 -1
- package/docs/extensions/worktree.md +30 -15
- package/extensions/board/commands.ts +48 -17
- package/extensions/board/format.ts +107 -9
- package/extensions/board/index.ts +14 -2
- package/extensions/board/join.ts +31 -10
- package/extensions/board/page.ts +202 -156
- package/extensions/board/result.ts +10 -24
- package/extensions/board/rows.ts +1 -1
- package/extensions/board/schema.ts +31 -10
- package/extensions/board/seat.ts +9 -10
- package/extensions/board/snapshot.ts +7 -5
- package/extensions/board/tools.ts +211 -34
- package/extensions/board/transport/router.ts +27 -110
- package/extensions/board/transport/server.ts +76 -56
- package/extensions/board/types.ts +57 -0
- package/extensions/board/view/client.ts +24 -13
- package/extensions/board/view/dialog.ts +43 -0
- package/extensions/board/view/keys.ts +28 -24
- package/extensions/board/view/live.ts +6 -178
- package/extensions/board/view/names.ts +91 -0
- package/extensions/board/view/page.ts +60 -89
- package/extensions/board/view/render.ts +228 -200
- package/extensions/board/view/styles.ts +72 -49
- 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 -11
- package/extensions/bytefly/footer/price/price.ts +30 -8
- package/extensions/bytefly/footer/register.ts +5 -5
- package/extensions/bytefly/footer/render/footer.ts +18 -4
- package/extensions/bytefly/footer/render/format.ts +18 -0
- package/extensions/bytefly/footer/render/key.ts +46 -0
- package/extensions/bytefly/footer/render/layout.ts +13 -3
- package/extensions/bytefly/footer/render/lines.ts +23 -7
- package/extensions/bytefly/footer/render/segments.ts +93 -31
- package/extensions/bytefly/footer/types.ts +29 -5
- package/extensions/bytefly/footer/unpriced.ts +42 -17
- package/extensions/bytefly/header/component.ts +4 -20
- 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/check/render.ts +10 -1
- package/extensions/file-browse/config/config.ts +4 -19
- 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/index.ts +27 -48
- package/extensions/file-browse/live/live.ts +1 -1
- package/extensions/file-browse/transport/router.ts +12 -6
- package/extensions/file-browse/transport/server.ts +61 -28
- package/extensions/file-browse/view/chrome.ts +4 -4
- package/extensions/file-browse/view/client.ts +3 -1
- package/extensions/file-browse/view/emit.ts +51 -0
- package/extensions/file-browse/view/html.ts +78 -46
- package/extensions/file-browse/view/live-client.ts +2 -2
- package/extensions/file-browse/view/markdown.ts +23 -26
- package/extensions/file-browse/view/palette.ts +2 -2
- package/extensions/file-browse/view/raw-html.ts +27 -22
- package/extensions/file-browse/view/tree.ts +1 -1
- package/extensions/file-browse/view/urls.ts +20 -6
- package/extensions/goal/tui.ts +12 -2
- package/extensions/job/runtime.ts +4 -4
- package/extensions/job/tools.ts +19 -7
- package/extensions/job/tui.ts +2 -28
- package/extensions/plan/commands.ts +2 -2
- package/extensions/plan/feedback.ts +88 -18
- package/extensions/plan/format.ts +10 -3
- package/extensions/plan/index.ts +24 -2
- package/extensions/plan/plan-files.ts +88 -0
- package/extensions/plan/plan-text.ts +114 -0
- package/extensions/plan/plans.ts +19 -23
- package/extensions/plan/policy.ts +10 -5
- package/extensions/plan/runtime.ts +43 -4
- 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/tools.ts +2 -2
- package/extensions/plan/types.ts +15 -0
- package/extensions/plan/web-blocks.ts +160 -0
- package/extensions/plan/web-chrome.ts +7 -5
- package/extensions/plan/web-client.ts +271 -325
- package/extensions/plan/web-collab.ts +7 -7
- package/extensions/plan/web-comments.ts +57 -16
- package/extensions/plan/web-composer.ts +91 -0
- package/extensions/plan/web-draft.ts +40 -0
- package/extensions/plan/web-keys.ts +258 -0
- package/extensions/plan/web-markdown.ts +1 -1
- package/extensions/plan/web-outline.ts +212 -0
- package/extensions/plan/web-page.ts +59 -86
- package/extensions/plan/web-payload.ts +188 -0
- package/extensions/plan/web-plan.ts +160 -0
- package/extensions/plan/web-router.ts +0 -1
- package/extensions/plan/web-settle.ts +201 -0
- package/extensions/plan/web-share-session.ts +24 -55
- package/extensions/plan/web-share.ts +19 -37
- package/extensions/plan/web-write.ts +112 -0
- package/extensions/plan/web.ts +77 -130
- package/extensions/rewind/commands/review.ts +2 -1
- package/extensions/serve/config.ts +37 -0
- package/extensions/serve/index.ts +71 -0
- package/extensions/serve/registry.ts +251 -0
- package/extensions/session-bus/join-request.ts +16 -9
- package/extensions/session-bus/result.ts +1 -27
- 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/tools.ts +5 -4
- 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 +44 -9
- 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/todo/tui.ts +21 -3
- 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 +9 -7
- package/lib/board/card.ts +192 -3
- package/lib/board/links.ts +26 -2
- package/lib/board/page-pointer.ts +111 -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/session.ts +84 -18
- package/lib/board/store.ts +268 -55
- package/lib/board/view.ts +114 -24
- package/lib/bus-request.ts +83 -0
- package/lib/format.ts +22 -0
- package/lib/http/index.ts +11 -7
- package/lib/http/mount.ts +142 -0
- package/lib/http/port.ts +25 -0
- package/lib/http/serve.ts +170 -43
- package/lib/http/server.ts +3 -3
- package/lib/http/sink.ts +34 -9
- package/lib/plan-entry.ts +20 -13
- package/lib/policy.ts +14 -1
- package/lib/requirements.ts +2 -2
- package/lib/session-label.ts +23 -7
- package/lib/session-roster.ts +74 -3
- package/lib/session-tool.ts +37 -21
- package/lib/spawn.ts +37 -3
- package/lib/stray-fields.ts +45 -0
- package/lib/tool-names.ts +13 -0
- package/lib/tui/README.md +6 -3
- package/lib/tui/glyphs.ts +3 -6
- package/lib/tui/render-cache.ts +29 -0
- package/lib/tui/tool.ts +30 -1
- package/lib/web/README.md +52 -2
- package/lib/web/client-runtime.ts +149 -0
- package/lib/web/index.ts +4 -2
- package/lib/web/markup.ts +106 -2
- package/lib/web/names.ts +42 -0
- package/package.json +3 -2
- package/extensions/board/config.ts +0 -36
- 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
|
@@ -18,13 +18,13 @@ Status records what a reader can rely on: **Shipped** is in a tagged release, **
|
|
|
18
18
|
|---|---|---|---|
|
|
19
19
|
| One-command browsing | `/browse` | The server starts at the effective working directory, prints its URL, and opens the browser. | Shipped |
|
|
20
20
|
| Read-only serving | `/browse` | Nothing is edited or written, and the server binds `127.0.0.1` only. | Shipped |
|
|
21
|
-
| Two-pane page |
|
|
21
|
+
| Two-pane page | `/files/`, `/files/browse[/<rel>]` | A collapsible tree beside the current folder's listing, with the tree, filter, and quick-open trigger pinned in their own pane. | Shipped |
|
|
22
22
|
| File previews | `/view/<rel>` | Text, rendered Markdown, image thumbnails, and download cards for binary or over-size files, each with a per-language icon. | Shipped |
|
|
23
|
-
| Quick open |
|
|
23
|
+
| Quick open | `/files/`, `Ctrl/Cmd+K`, `/files/api/files` | A fuzzy path finder over the tree opens a file without walking the tree. | Shipped |
|
|
24
24
|
| Git diff view | `/diff`, `/diff/<rel>` | A change overview and one file's unified diff, scoped to `head`, `staged`, or `worktree`. | Shipped |
|
|
25
25
|
| Live status | `/events`, the `◉ live` chip | An open page keeps its branch, change markers, and footer up to date without a reload. | Shipped |
|
|
26
26
|
| Reload hint | `/api/dir-rev`, `⟳ reload` | A page whose folder gained or lost a file offers a reload control instead of changing its rows. | Shipped |
|
|
27
|
-
|
|
|
27
|
+
| The session's page port | `serve`'s `◉ <port>` chip | The footer names the one port every page of the session answers on, this one included. | Shipped |
|
|
28
28
|
| Listing controls | the `Name`, `Size`, and `Modified` headers | The current folder sorts by name, size, or modified time, directories first, and the choice is remembered per browser. | Shipped |
|
|
29
29
|
| Keyboard navigation | the tree's keymap, `?` | The tree is one tab stop with arrow, fold, and open keys; a shortcut sheet lists the page's own keys. | Shipped |
|
|
30
30
|
| Diff hunk navigation | `n`/`p` in `/diff` | Jump between changed hunks without scrolling the whole file. | Shipped |
|
|
@@ -35,7 +35,7 @@ Status records what a reader can rely on: **Shipped** is in a tagged release, **
|
|
|
35
35
|
| Markdown preview | `/view/<rel>`, `/view/<rel>?raw=1` | A README renders as headings, lists, tables, code, and links, with a one-click raw view. | Shipped |
|
|
36
36
|
| Copy and wrap | the page toolbar, `y`, `u` | Copy a file's path or contents or a `path:12-30` line reference, and toggle line wrapping for text and raw views. | Shipped |
|
|
37
37
|
| Theme toggle | the header button, `/theme.js` | Dark and light palettes, following the system until an explicit choice is remembered per browser. | Shipped |
|
|
38
|
-
| Idle stop and
|
|
38
|
+
| Idle stop and prefix reuse | `/browse stop`, the idle grace | The page gives its prefix back when nobody is reading it, so a closed tab never holds the session's server. | Shipped |
|
|
39
39
|
|
|
40
40
|
## Quickstart
|
|
41
41
|
|
|
@@ -66,34 +66,32 @@ selects the root.
|
|
|
66
66
|
|
|
67
67
|
- The root is the effective cwd, so an active `worktree` session serves the
|
|
68
68
|
worktree. `<path>` must resolve inside it.
|
|
69
|
-
- The port is
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
port; a busy port is reported, not silently changed.
|
|
69
|
+
- The port is the session's: `serve` picks one in `4780`–`5779` (and `serve.port` pins
|
|
70
|
+
it), so the origin stays stable across roots and restarts. A busy pinned port is
|
|
71
|
+
reported, not silently changed.
|
|
73
72
|
- Starting while already running with the same root reprints the URL without
|
|
74
|
-
restarting; a different root
|
|
73
|
+
restarting; a different root restarts the page.
|
|
75
74
|
- The server is closed when its last page has been gone for ten seconds, so
|
|
76
75
|
closing the browser window ends the session's browse server and `/browse`
|
|
77
|
-
starts it again on the
|
|
76
|
+
starts it again on the session's port. A reload or a new tab inside that grace keeps
|
|
78
77
|
it. `session_shutdown` closes it too, and `/browse stop` closes it at once.
|
|
79
78
|
|
|
80
79
|
## Routes
|
|
81
80
|
|
|
82
81
|
| Route | Result |
|
|
83
82
|
|---|---|
|
|
84
|
-
|
|
|
85
|
-
| `/view/<rel>` | tree + file page (text, Markdown, image, binary, or over-size) |
|
|
86
|
-
| `/view/<rel>?raw=1` | raw Markdown/text source instead of the rendered view |
|
|
87
|
-
| `/raw/<rel>` | raw bytes, streamed; downloads and image sources |
|
|
88
|
-
| `/diff[?mode=head\|staged\|worktree]` | changed-file overview with `--stat` |
|
|
89
|
-
| `/diff/<rel>[?mode=…]` | one file's unified diff |
|
|
90
|
-
| `/api/tree?path=<rel>` | JSON children for lazy tree expansion |
|
|
91
|
-
| `/api/files?q=<query>` | JSON fuzzy path matches for the quick-open palette |
|
|
92
|
-
| `/api/dir-rev?path=<rel>` | JSON listing revision for the reload check |
|
|
93
|
-
| `/events` | Server-Sent Events stream of `status` frames for the open page |
|
|
94
|
-
| `/app.js` | the first-party client script |
|
|
95
|
-
| `/theme.js` | the pre-paint theme bootstrap |
|
|
96
|
-
| `/favicon.ico` | `204` (the icon is a data URI) |
|
|
83
|
+
| `/files/`, `/files/browse[/<rel>]` | tree + directory listing |
|
|
84
|
+
| `/files/view/<rel>` | tree + file page (text, Markdown, image, binary, or over-size) |
|
|
85
|
+
| `/files/view/<rel>?raw=1` | raw Markdown/text source instead of the rendered view |
|
|
86
|
+
| `/files/raw/<rel>` | raw bytes, streamed; downloads and image sources |
|
|
87
|
+
| `/files/diff[?mode=head\|staged\|worktree]` | changed-file overview with `--stat` |
|
|
88
|
+
| `/files/diff/<rel>[?mode=…]` | one file's unified diff |
|
|
89
|
+
| `/files/api/tree?path=<rel>` | JSON children for lazy tree expansion |
|
|
90
|
+
| `/files/api/files?q=<query>` | JSON fuzzy path matches for the quick-open palette |
|
|
91
|
+
| `/files/api/dir-rev?path=<rel>` | JSON listing revision for the reload check |
|
|
92
|
+
| `/files/events` | Server-Sent Events stream of `status` frames for the open page |
|
|
93
|
+
| `/files/app.js` | the first-party client script |
|
|
94
|
+
| `/files/theme.js` | the pre-paint theme bootstrap |
|
|
97
95
|
|
|
98
96
|
## Configuration
|
|
99
97
|
|
|
@@ -103,7 +101,6 @@ global document then project document, project wins — are in
|
|
|
103
101
|
|
|
104
102
|
| Key | Default | Meaning |
|
|
105
103
|
|---|---|---|
|
|
106
|
-
| `port` | `0` | `0` picks one random port fixed for the session; a positive value pins it. |
|
|
107
104
|
| `autoOpen` | `true` | Open the browser on start; `false` for headless. |
|
|
108
105
|
| `maxFileBytes` | `1048576` | Larger files show a download card only. |
|
|
109
106
|
| `maxThumbBytes` | `5242880` | Larger images get an icon, not a thumbnail. |
|
|
@@ -171,7 +168,10 @@ global document then project document, project wins — are in
|
|
|
171
168
|
## The page
|
|
172
169
|
|
|
173
170
|
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`).
|
|
171
|
+
icons, progressively enhanced by one first-party script (`client.ts`). Each page
|
|
172
|
+
names its own subject in the tab — `browse · <folder>`, `browse · <rel>` for a file, the
|
|
173
|
+
`<page> · <subject>` shape the kit's `tabTitle` gives every served page — and links the
|
|
174
|
+
brand mark as an SVG data URI, in the appearance the system is in. A folder
|
|
175
175
|
folds from its twisty, and a `fold all` / `expand all` button beside the filter
|
|
176
176
|
folds the whole tree at once. The tree scrolls in its own pane with quick open
|
|
177
177
|
and the filter pinned above it; below 860px it collapses into a `Files`
|
|
@@ -340,12 +340,11 @@ old and new sides for `lib/highlight`.
|
|
|
340
340
|
|
|
341
341
|
## Live status
|
|
342
342
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
[`bytefly`](./bytefly.md)
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
server, with no cross-session sharing.
|
|
343
|
+
The page answers at `/files` on the session's one server, so the footer's one
|
|
344
|
+
`◉ <port>` chip is the session's, not this page's: [`serve`](./serve.md) publishes it
|
|
345
|
+
while anything is mounted, and [`bytefly`](./bytefly.md) routes it to line 1's right
|
|
346
|
+
zone, after `⎇ branch` and `⑂ worktree`. It is cleared when the session's server closes,
|
|
347
|
+
so a session with no page open shows nothing at all.
|
|
349
348
|
|
|
350
349
|
The page has its own live status, which is what a left-open tab reads. A `◉ live`
|
|
351
350
|
chip beside `read-only` says whether status is flowing (green), still
|
|
@@ -450,22 +449,20 @@ stops at the first difference because the control, not the checker, is the fix.
|
|
|
450
449
|
| Integration point | How |
|
|
451
450
|
|---|---|
|
|
452
451
|
| Command | `/browse` registers a single command from the factory; no tools are exposed. |
|
|
453
|
-
| State | The running
|
|
452
|
+
| State | The running mount, its root, and the session id live in a module-local closure; nothing is persisted and no session entries are written. The port is `serve`'s, not this extension's. |
|
|
454
453
|
| Lifecycle | The factory opens no socket; the server is created on `/browse` and closed idempotently by `/browse stop`, by `session_shutdown`, and once its last page has been gone for the idle grace, which also ends every attached status stream. |
|
|
455
454
|
|
|
456
455
|
## Design notes
|
|
457
456
|
|
|
458
457
|
- **Lifecycle.** `/browse` resolves `resolveEffectiveCwd(ctx.cwd)`, loads config,
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
`IDLE_STOP_MS` and the command closes the server and clears the chip — the
|
|
468
|
-
cached port survives, so the next `/browse` binds the same one.
|
|
458
|
+
parses args, and mounts the page through `requestMount`: `realpath` the root, claim
|
|
459
|
+
`/files` on the session's server, return `{ url, port, root, close }`. The
|
|
460
|
+
port belongs to `serve` — `serve.port` pins the session's origin — so nothing here
|
|
461
|
+
picks or retries one. `/browse stop` and `session_shutdown` await the idempotent
|
|
462
|
+
`close()`, which gives the prefix back. The page is the mount's only reader, so the
|
|
463
|
+
channel reports the last page's departure after `IDLE_STOP_MS` and the command
|
|
464
|
+
releases the prefix — and the session's port is `serve`'s, remembered per session,
|
|
465
|
+
so the next `/browse` answers on the same origin.
|
|
469
466
|
- **Pure routing.** `handleRequest(context, method, rawUrl, headers)` validates
|
|
470
467
|
method and `Host`, parses the URL, matches a route, and runs every path
|
|
471
468
|
through `paths.ts`. It returns a plain `ServeResponse`, so routing is tested
|
|
@@ -502,12 +499,17 @@ stops at the first difference because the control, not the checker, is the fix.
|
|
|
502
499
|
| [`../../lib/escape.ts`](../../lib/escape.ts) | `escapeHtml`, the shared HTML escaping used by every renderer |
|
|
503
500
|
| [`../../lib/highlight.ts`](../../lib/highlight.ts) | First-party escape-first syntax tokenizer for the code view (shared with `plan`) |
|
|
504
501
|
| [`../../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` |
|
|
502
|
+
| `files/files.ts` | The barrel over the four reads: one surface for the callers, four modules behind it |
|
|
503
|
+
| `files/classify.ts` | What a name is: kind, Markdown, image, MIME type, and the text sample |
|
|
504
|
+
| `files/list.ts` | Capped directory listings and one file's bytes, over injected filesystem reads |
|
|
505
|
+
| `files/tree.ts` | The sidebar tree and the page-level walk (`directoryView`) that returns it with the listing |
|
|
506
|
+
| `files/index.ts` | The quick-open file index, walked once per root up to a cap |
|
|
506
507
|
| `text/diff.ts` | Pure unified-diff and porcelain-status parsing for the diff view |
|
|
507
508
|
| `text/fuzzy.ts` | Fuzzy path matching and ranking for the quick-open palette |
|
|
508
509
|
| `view/markdown.ts` | First-party escape-first Markdown-to-HTML renderer (fenced code highlighted; raw HTML through `raw-html.ts`) |
|
|
509
510
|
| `view/raw-html.ts` | Allowlisted raw-HTML sanitizer: element/attribute policy, suppressed bodies, and cross-block balancing |
|
|
510
511
|
| `view/urls.ts` | Shared link/image URL policy: scheme allowlist and `/view`/`/raw` rewriting |
|
|
512
|
+
| `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
513
|
| `git/git.ts` | Read-only branch/dirty provider for the header chip |
|
|
512
514
|
| `view/icons.ts` | Inline SVG sprite and the per-language mapping |
|
|
513
515
|
| `view/client.ts` | The `/app.js` source: sort, wrap, copy, hunk navigation, reading progress, and the page keymap |
|
|
@@ -522,7 +524,7 @@ stops at the first difference because the control, not the checker, is the fix.
|
|
|
522
524
|
| `view/diff-html.ts` | The unified-diff body: the gutter, the sign, the hunk sections, and per-hunk highlighting |
|
|
523
525
|
| `transport/router.ts` | Pure request handler: routes, the `/raw` sandbox policy, and the error page |
|
|
524
526
|
| `transport/server.ts` | This extension's routes over `lib/http`'s loopback server: the request count, the stream cap, and the idle policy |
|
|
525
|
-
| [`../../lib/http/index.ts`](../../lib/http/index.ts) | Loopback HTTP layer shared with `plan`: one server entry (`
|
|
527
|
+
| [`../../lib/http/index.ts`](../../lib/http/index.ts) | Loopback HTTP layer shared with `plan`: one server entry (`serveHttp`) and the mount contract a page claims a prefix with (`requestMount`), page/script/JSON/event-stream headers, `ServeResponse`, the request-envelope helpers, the event-stream sink and its idle grace |
|
|
526
528
|
| [`../../lib/browser.ts`](../../lib/browser.ts) | Cross-platform browser opener (shared with `plan`) |
|
|
527
529
|
|
|
528
530
|
## Testing
|
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,11 +36,13 @@ 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 |
|
|
43
43
|
| Changed sections | the `exit_plan_mode` review screen, the `/plan review` outline | After a refinement, only the sections that changed are marked. | Shipped |
|
|
44
|
+
| Block-level change marks | the `/plan review` page's **Changed** view | After a refinement, each block that is new or edited is marked — an inserted step marks itself and leaves the rest of its section alone — and a section that lost blocks says how much shorter it became. **All** / **Changed** / **Comments** switch what the page shows, `v` cycles them, `n`/`N` walk the blocks that carry a comment, and a printed review is always the whole plan. | Unreleased |
|
|
45
|
+
| Settled comments | the page's `Resolve` / `Reopen` on a note you wrote | The note keeps its place, its quote and its history and is written `> [!REVIEW resolved]`, where a later review and the model read it as closed; the panel counts `2 open · 1 resolved`, and every card names who wrote it. | Unreleased |
|
|
44
46
|
| Section navigation | the review screen, `n`/`p` | The screen names the section you are in and jumps between headings. | Shipped |
|
|
45
47
|
| Remote review | `exit_plan_mode` on a WeChat turn | The plan file is sent, then one compact prompt to approve, keep planning, or exit. | Shipped |
|
|
46
48
|
|
|
@@ -140,7 +142,15 @@ the cap and the fix named, and nothing is written.
|
|
|
140
142
|
|
|
141
143
|
The file is the source of truth. On approval the model follows the plan file's
|
|
142
144
|
steps, and refinements rewrite the same file (`write_plan` with `plan_path`), so
|
|
143
|
-
the reviewed artifact is the executed artifact.
|
|
145
|
+
the reviewed artifact is the executed artifact. The text a refinement replaces is
|
|
146
|
+
kept beside it, as a dotfile named for the plan and the revision
|
|
147
|
+
(`.2026-10-26-1008-add-rate-limiting-r001.md`), up to the newest five: that is
|
|
148
|
+
what makes two revisions diffable, and what lets a comment `carryFeedback` moved
|
|
149
|
+
onto another block be checked against the text the reviewer actually read. A
|
|
150
|
+
revision is a dotfile, so no scan lists it as a plan, no `plan_path` opens it, and
|
|
151
|
+
the review never offers it for approval — and a refinement that changed nothing
|
|
152
|
+
keeps nothing, because the copy would be the plan itself. The write's result names
|
|
153
|
+
the revision it kept.
|
|
144
154
|
|
|
145
155
|
### `exit_plan_mode`
|
|
146
156
|
|
|
@@ -171,6 +181,13 @@ the todo list:
|
|
|
171
181
|
todos, and its verification bullets are already covered.
|
|
172
182
|
- Each seeded item is matched back to its step by text, so ticking a step with
|
|
173
183
|
`set_plan_steps` completes the item too.
|
|
184
|
+
- An approval records the step texts it seeded, per plan path, as a `plan-seeded`
|
|
185
|
+
entry. A plan refined after approval is approved again — `write_plan` needs plan
|
|
186
|
+
mode, so a second round is the way back — and that second approval **replaces the
|
|
187
|
+
leftovers its own earlier approval made**: an untouched item whose step was
|
|
188
|
+
renamed or dropped goes, while one somebody started or finished is kept and
|
|
189
|
+
named, because deleting it would erase work a reader did. `write_plan` says the
|
|
190
|
+
list is stale when it refines a plan that was approved before.
|
|
174
191
|
|
|
175
192
|
**Goal-aware.** While planning, the injected context restates a pursued session
|
|
176
193
|
goal as one bullet, so the plan is designed against the objective instead of
|
|
@@ -265,7 +282,8 @@ its plans hold and how old the oldest one is, both derived from the walk that ju
|
|
|
265
282
|
happened. The directory stays the index: no registry file, nothing deletes a plan,
|
|
266
283
|
and one bounded read per plan.
|
|
267
284
|
|
|
268
|
-
`/plan review` is the only subcommand that takes
|
|
285
|
+
`/plan review` is the only subcommand that takes a path; `/plan share` takes the
|
|
286
|
+
one word `stop` after it, and `list` and `cancel` take nothing. Bare `/plan review`
|
|
269
287
|
reviews the plan this session wrote, or, in a later session, the newest plan file
|
|
270
288
|
in the plans directory (newest modification time); `/plan review <path>.md` (or a
|
|
271
289
|
path with a separator) reviews that plan. A line that merely starts with the word
|
|
@@ -531,8 +549,12 @@ line range it shows, and the footer lists only the keys that apply.
|
|
|
531
549
|
<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
550
|
|
|
533
551
|
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
|
-
|
|
552
|
+
in the default browser on `127.0.0.1`. Its tab reads `plan review · <the plan's
|
|
553
|
+
title>` — the page's name as the header's lockup says it, then the subject, the shape
|
|
554
|
+
`lib/web/markup.ts`'s `tabTitle` gives every served page — rendered with the document
|
|
555
|
+
rather than set by a script, so a strip of tabs names the plan before the page has
|
|
556
|
+
finished loading. The page is a
|
|
557
|
+
reading-first review surface: a sticky header carrying the plan's H1 (falling back to the file-name
|
|
536
558
|
slug), a live status chip (connection, comment count, and unsaved changes), the
|
|
537
559
|
theme toggle, and the review actions, over a hairline reading-progress bar. A
|
|
538
560
|
sticky side panel shows the plan's `Steps` section progress and comment count above a
|
|
@@ -542,6 +564,12 @@ reveals a **+ Comment** action on hover; select
|
|
|
542
564
|
text first to quote it. **Save feedback** writes the comments into the plan
|
|
543
565
|
file and returns them to the model.
|
|
544
566
|
|
|
567
|
+
- **A comment can be settled.** **Resolve** (on your own note) keeps it — its place, its
|
|
568
|
+
quote, its history — and marks it `> [!REVIEW resolved]` in the file, where a later review
|
|
569
|
+
and the model both read it as closed; **Reopen** puts it back. The panel counts
|
|
570
|
+
`2 open · 1 resolved`. A note you wrote is labelled **You** on your page and **The owner**
|
|
571
|
+
on somebody else's, a reviewer's is `@name`, and one sentence beside the facts says what
|
|
572
|
+
**Approve & run** and **Save feedback** each do.
|
|
545
573
|
- **Shared theme.** The page uses the same Bytefly tokens, base rules, and
|
|
546
574
|
syntax-token colours as `file-browse` (`lib/web/`), follows the system light/dark
|
|
547
575
|
preference until the header toggle sets an explicit choice, and remembers that
|
|
@@ -601,7 +629,9 @@ file and returns them to the model.
|
|
|
601
629
|
`DOMParser`, so a code line is never parsed as markup.
|
|
602
630
|
- **The file is the store.** Each comment is written as
|
|
603
631
|
`> [!REVIEW] <comment>` on the line after the block it anchors to, tagged with
|
|
604
|
-
its author when a teammate wrote it (`> [!REVIEW] @alice: <comment>`)
|
|
632
|
+
its author when a teammate wrote it (`> [!REVIEW] @alice: <comment>`), and
|
|
633
|
+
marked `> [!REVIEW moved]` when a refinement carried it off the text it was
|
|
634
|
+
written on. Saving is
|
|
605
635
|
idempotent per writer: that writer's previous markers are replaced and everyone
|
|
606
636
|
else's are kept, so two people reviewing the same plan cannot erase each other.
|
|
607
637
|
Stripping is fence-aware: a `> [!REVIEW]` line inside a code block is
|
|
@@ -617,7 +647,11 @@ file and returns them to the model.
|
|
|
617
647
|
comment whose section is gone too moves to the plan's last block rather than
|
|
618
648
|
disappearing, and every one of those placements lands beside a block, never after
|
|
619
649
|
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.
|
|
650
|
+
block at all leaves a comment in the unattached list. Everything past the first
|
|
651
|
+
step is a guess, so a placement that is not the anchored text itself is marked:
|
|
652
|
+
the card carries a `moved` chip whose tooltip says the block changed and the
|
|
653
|
+
note was placed here, and the marker records it in the file, so the fact
|
|
654
|
+
survives a reload, a hand-off, and a save by its own author.
|
|
621
655
|
- **Anchored to source.** `blocks.ts` splits the plan (fence-aware) into
|
|
622
656
|
source-line ranges; a comment is placed after the whole anchored block, so a
|
|
623
657
|
fenced code block is never split. A save re-checks the file against what the
|
|
@@ -628,14 +662,15 @@ file and returns them to the model.
|
|
|
628
662
|
save that carries no comments asks for nothing, so the review screen comes back
|
|
629
663
|
instead. `/plan review` sends them as a user message (a plan-mode revision or a
|
|
630
664
|
plain update, depending on the mode).
|
|
631
|
-
- **Loopback first, tokenized.** The
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
saved theme
|
|
665
|
+
- **Loopback first, tokenized.** The review mounts `/plan/<id>` on the session's one
|
|
666
|
+
server, which binds `127.0.0.1`; the URL carries a random per-session token, a `Host`
|
|
667
|
+
header the server was not bound on is rejected, a cross-origin POST is refused, only
|
|
668
|
+
`GET`, `HEAD`, and `POST` are served, and the request body is capped. Sharing asks that
|
|
669
|
+
mount for one LAN listener (below); it never widens the loopback socket. A save or
|
|
670
|
+
cancel resolves the session only after its HTTP response is delivered, so the browser
|
|
671
|
+
never sees a false "Failed to fetch". The prefix is the review's, on the session's one
|
|
672
|
+
origin, so the browser's saved theme survives a review. It is closed on the owner's
|
|
673
|
+
save or cancel, on the owner's
|
|
639
674
|
page going away, or on `session_shutdown`; a reload reconnects inside that grace,
|
|
640
675
|
so refreshing or reloading a conflicted page does not end the review. A
|
|
641
676
|
reviewer's tab opening or closing is not the review's lifetime.
|
|
@@ -705,6 +740,12 @@ both.
|
|
|
705
740
|
Git Bash (`bash`), which must be installed for any shell command to run.
|
|
706
741
|
- The read-only guarantee is a guard rail against accidental writes, not a
|
|
707
742
|
security boundary (see [Security](#security)).
|
|
743
|
+
- **A carried comment's new home is a guess, and only the page says so.** A note a
|
|
744
|
+
refinement could not pin to surviving text is placed by wording overlap, by the
|
|
745
|
+
gap it left, or at the plan's last block; the `moved` chip is what tells a
|
|
746
|
+
reviewer that. A reader of the plan file alone sees `> [!REVIEW moved]`, and the
|
|
747
|
+
terminal review shows the comment as a quote with no chip, so the marker is the
|
|
748
|
+
fact and the chip is one reader's way of showing it.
|
|
708
749
|
- The plans directory is the only index. `/plan list` walks it on every open and
|
|
709
750
|
keeps no registry, so it sees an external edit or a hand-copied plan
|
|
710
751
|
immediately; it lists a file over the 256 KiB plan limit without reading it,
|
|
@@ -734,6 +775,10 @@ both.
|
|
|
734
775
|
refuses a file it did not render (`409`). It is a poll rather than a file
|
|
735
776
|
watcher, so a change on a filesystem that reports nothing at all is still seen
|
|
736
777
|
at the next request, because every page read re-compares the file.
|
|
778
|
+
- **A settled note is a flag in the marker.** `> [!REVIEW resolved]` (and
|
|
779
|
+
`moved resolved`) is read as open when absent; a build from before this one does not know
|
|
780
|
+
the flag and shows the marker line as body text, so a file reviewed on both sides of the
|
|
781
|
+
upgrade reads differently there.
|
|
737
782
|
- A shared review holds at most eight links. Each is a live bearer credential, so
|
|
738
783
|
a ninth is refused with the cap named rather than minted.
|
|
739
784
|
|
|
@@ -897,7 +942,8 @@ both.
|
|
|
897
942
|
| `types.ts` | `PlanModeEntry`, `EnterPlanModeDetails`, `WritePlanDetails`, `ExitPlanModeDetails`, `CancelPlanModeDetails`, `SetPlanStepsDetails`. |
|
|
898
943
|
| `policy.ts` | Plan mode's read-only policy, the shared prompt summary, and the bash-refusal guidance. |
|
|
899
944
|
| `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,
|
|
945
|
+
| `plans.ts` | Plan-file store: slug/stamp naming, directory resolution (repository, active worktree, or agent directory), containment, `write`/`setSteps`/`saveFeedback`, and `read`/`latest`. |
|
|
946
|
+
| `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
947
|
| `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
948
|
| `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
949
|
| `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. |
|
|
@@ -913,18 +959,27 @@ both.
|
|
|
913
959
|
| `plan-index.ts` | `/plan list`: the notice text and the docked index screen (store size and oldest plan, title, step progress, age, focused path; Enter reviews). |
|
|
914
960
|
| `blocks.ts` | Source-line block model (fence-aware headings, paragraphs, list items, code, quotes, tables, rules, blanks), and `contentBlockAt`: the block with content a comment beside blank space belongs to. |
|
|
915
961
|
| `feedback.ts` | Comment codec: authored markers, `stripFeedback`, `readFeedback`/`parseFeedback` (body, blocks, and comments out of one parse), `formatFeedback` (writer-scoped), `displayFeedback`, `carryFeedback`, `feedbackSummary`, and payload validation. |
|
|
916
|
-
| `web.ts` | `createPlanWebReview`: the review session (per-token roles, the change stream, the plan-file poll, the body cap, settle-after-flush, abort) over `lib/http`'s one loopback server. |
|
|
962
|
+
| `web.ts` | `createPlanWebReview`: the review session (per-token roles, the change stream, the plan-file poll, the body cap, settle-after-flush, abort) over `lib/http`'s one loopback server, and the router wiring — the island is `web-payload.ts` and the writes are `web-write.ts`. |
|
|
917
963
|
| `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. |
|
|
918
964
|
| `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. |
|
|
919
965
|
| `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. |
|
|
920
|
-
| `web-page.ts` | The review page shell (header, reading progress, review facts, section outline, shortcut sheet, approve confirmation, share and name dialogs)
|
|
966
|
+
| `web-page.ts` | The review page shell (header, reading progress, review facts, section outline, shortcut sheet, approve confirmation, share and name dialogs) and inline CSS. |
|
|
967
|
+
| `web-payload.ts` | The page's data island: `ReviewPageData` and `reviewPageData`, which builds every fact the browser renders — the blocks, the stored comments, the outline and its change marks, the step progress, the highlighted code, the caps, and who is asking — from one read of the plan, with no server or socket in sight. |
|
|
968
|
+
| `web-write.ts` | The review's two write paths over the plan store: `saveOwnerAnnotations` (the owner's save, which returns the plan it wrote) and `postReviewerAnnotations` (a reviewer's post, signed and scoped to its link), sharing one 404/409 check so a comment can never land beside text nobody read. |
|
|
921
969
|
| `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
970
|
| `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-
|
|
971
|
+
| `web-keys.ts` | The client's keyboard as typed source (`WEB_KEYS_JS`), and the one table the page's keys come from: `webKeys()` gives each binding its keys, the shortcut sheet's sentence, the sidebar hint's words and whether the page offers it — a function rather than a constant, because the bundle carries a body and `String()` of a table would drop the closures — read by `handleKey` (Esc peels the composer before closing, a field and the sheet own their keys, `s` is the write the page offers) and by the sheet and hint line the page renders; plus `rememberSelection`, which keeps the text a reader selected as the next comment's quote, and the `keyRows`/`surfaceOf` the handler and the page's own sheet both read, spliced with them for the same reason. |
|
|
972
|
+
| `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. |
|
|
973
|
+
| `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. |
|
|
974
|
+
| `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`. |
|
|
975
|
+
| `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. |
|
|
976
|
+
| `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. |
|
|
977
|
+
| `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
978
|
| `web-comments.ts` | The client's comment half: selection quotes, the composer, authored cards and who may edit them, delete-with-undo. |
|
|
925
979
|
| `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
980
|
| `web-markdown.ts` | The client's document half: `safeHref` and the inline/table/code/list rendering (interpolated into `web-client.ts`). |
|
|
927
981
|
| `web-chrome.ts` | The client's chrome half: the state chip, the status line, the header, and reading progress (interpolated into `web-client.ts`). |
|
|
982
|
+
| `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
983
|
| `path-guard.ts` | Recursive path-argument detection for the read-only backstop. |
|
|
929
984
|
| [`../../lib/browser.ts`](../../lib/browser.ts) | Cross-platform default-browser opener (shared with `file-browse`). |
|
|
930
985
|
| [`../../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 +998,7 @@ both.
|
|
|
943
998
|
bun test extensions/plan
|
|
944
999
|
```
|
|
945
1000
|
|
|
946
|
-
|
|
1001
|
+
Twelve co-located suites, declared in `test/suite-manifest.ts`:
|
|
947
1002
|
|
|
948
1003
|
- `plans.test.ts` — the plan-file store: naming, directory resolution, atomic
|
|
949
1004
|
writes, `read`/`latest`/`index`, containment, `setSteps`, `saveFeedback`, and
|
|
@@ -955,9 +1010,16 @@ Eleven co-located suites, declared in `test/suite-manifest.ts`:
|
|
|
955
1010
|
`todo`, the session goal, and the call/result renderings.
|
|
956
1011
|
- `review-prompt.test.ts` — the compact remote prompt and its reply parser
|
|
957
1012
|
against one action table.
|
|
958
|
-
- `web
|
|
959
|
-
|
|
960
|
-
|
|
1013
|
+
- `web-*.test.ts` — the browser review, one capability per suite: the comment
|
|
1014
|
+
codec and the router (`web-router`), the page roles and its shell (`web-page`),
|
|
1015
|
+
the client script's rules (`web-client`, `web-outline`, `web-composer`,
|
|
1016
|
+
`web-settlement`), the stream and the plan-file poll (`web-stream`), the
|
|
1017
|
+
loopback session (`web-session`), and the share rules (`web-shared-review`).
|
|
1018
|
+
- `web-behaviour.test.ts` — the served review, driven in a real browser through
|
|
1019
|
+
the router: the blocks and the plan's name, a block's comment control opening the
|
|
1020
|
+
composer with Escape peeling it before the review, a stored comment in its block's
|
|
1021
|
+
rail and the header's count, and the outline jumping to the section it names. It
|
|
1022
|
+
skips where no browser is installed, naming `PI_CHROME`.
|
|
961
1023
|
- `web-share.test.ts` — the LAN address selector, `shareUrl`, the link table with
|
|
962
1024
|
its cap, and the reasons a share is unavailable.
|
|
963
1025
|
- `commands.test.ts` — `/plan review`, `/plan list`, `/plan share`, and the one
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# serve — the session's one loopback server
|
|
2
|
+
|
|
3
|
+
## Capabilities
|
|
4
|
+
|
|
5
|
+
| Capability | How | What you get |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| One port for every page of a session | automatic: the first page that opens binds it | the file browser at `/files/`, the board at `/board/`, and a review at `/plan/<id>/` answer on one origin, so a bookmark, a firewall rule, and a port to remember are one thing |
|
|
8
|
+
| One chip for that port | `◉ <port>` in the status bar | the session's port while anything is served on it, and nothing when no page is open |
|
|
9
|
+
| A port that survives a restart | `serve.port` in `bytefly.json` | the same address after `/resume`, so a saved URL still opens the page it names |
|
|
10
|
+
| A second session on its own port | automatic: servers are keyed by Pi session | a WeChat peer conversation never answers on this session's port |
|
|
11
|
+
| One page reachable on the LAN, on request | the asking page calls `mount.share()` | a teammate's link names exactly one address, and only that page answers there |
|
|
12
|
+
|
|
13
|
+
## Quickstart
|
|
14
|
+
|
|
15
|
+
There is nothing to run: `serve` is infrastructure, and the pages that use it are
|
|
16
|
+
`/board`, `/browse`, and `/plan review`.
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
/board # http://127.0.0.1:4780/board/?token=…
|
|
20
|
+
/browse # http://127.0.0.1:4780/files/
|
|
21
|
+
/plan review # http://127.0.0.1:4780/plan/3f9c21aa/?token=…
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
All three answer on `:4780`; the footer shows `◉ 4780` while any of them is open
|
|
25
|
+
and drops the chip when the last one closes.
|
|
26
|
+
|
|
27
|
+
## Routes
|
|
28
|
+
|
|
29
|
+
The server holds prefixes, not pages. Each one is claimed by the extension that
|
|
30
|
+
serves it, and a request reaches that extension with its prefix stripped:
|
|
31
|
+
|
|
32
|
+
| Prefix | Served by | Note |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| `/files/` | `file-browse` | the file browser: its tree, its file pages, its own script, and the links it rewrites out of Markdown, all built from this prefix; `/files/browse/<path>` is one directory |
|
|
35
|
+
| `/board/` | `board` | the read-only board page, with its token in the query |
|
|
36
|
+
| `/plan/<id>/` | `plan` | one review; the id is the review's, so two reviews never share a namespace |
|
|
37
|
+
|
|
38
|
+
Anything else is a `404` with a plain-text body, and a prefix named without its
|
|
39
|
+
trailing slash (`/board`) is redirected to the slashed form, because a page's own
|
|
40
|
+
links are relative to its directory. A prefix another page already holds is
|
|
41
|
+
refused by name rather than served twice.
|
|
42
|
+
|
|
43
|
+
## Behaviour by mode
|
|
44
|
+
|
|
45
|
+
The server runs in every mode, but the chip needs a UI: RPC and JSON sessions serve
|
|
46
|
+
their pages and skip it. Nothing here reads or writes the transcript.
|
|
47
|
+
|
|
48
|
+
## Configuration
|
|
49
|
+
|
|
50
|
+
The `serve` section of the shared `bytefly.json` (global, then project, project wins:
|
|
51
|
+
|
|
52
|
+
| Key | Default | What it does |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `port` | `0` | `0` takes one free port in `4780`–`5779` for the session and remembers it; a positive value pins the session's server so a bookmarked address survives a restart |
|
|
55
|
+
|
|
56
|
+
The pin belongs to the first session in a process that binds: a second session — a
|
|
57
|
+
WeChat peer conversation loads every extension except the bridge itself — takes an
|
|
58
|
+
OS-chosen port instead of failing on a port the host already holds. A second
|
|
59
|
+
*process* that finds the pinned port taken is told so rather than moved silently off
|
|
60
|
+
the address a bookmark names.
|
|
61
|
+
|
|
62
|
+
## Security
|
|
63
|
+
|
|
64
|
+
The server binds `127.0.0.1`. The unit of exposure is the mount: a prefix answers
|
|
65
|
+
only what its page routes, and each page keeps its own `Host` allowlist, its own
|
|
66
|
+
token, and its own method list in `lib/http`.
|
|
67
|
+
|
|
68
|
+
A shared review is the one exception, and it is the reviewing page's own: it asks
|
|
69
|
+
for a listener on a single LAN address, and that listener carries only the prefixes
|
|
70
|
+
that asked for it. `file-browse` and `board` are never reachable that way, and a page
|
|
71
|
+
that stops sharing leaves the listener at once — the socket closes with the last page
|
|
72
|
+
that was still sharing it.
|
|
73
|
+
|
|
74
|
+
## Limitations
|
|
75
|
+
|
|
76
|
+
- A page needs `serve` loaded. Without it every page refuses with a sentence naming
|
|
77
|
+
the extension, instead of binding a rival socket.
|
|
78
|
+
- One pin is one port for the whole session: two repositories running pages on this
|
|
79
|
+
machine at once need a project-level `serve.port` each, or neither pinned.
|
|
80
|
+
- The port moves to an OS-chosen one when the pinned port is taken, so an old
|
|
81
|
+
bookmark keeps failing until the setting or the other process is dealt with.
|
|
82
|
+
|
|
83
|
+
## Non-goals
|
|
84
|
+
|
|
85
|
+
- It serves no page of its own, and registers no tool and no command.
|
|
86
|
+
- It is not a general web server: loopback, a page's own routes, one optional LAN
|
|
87
|
+
address per shared page, and nothing else.
|
|
88
|
+
- It does not proxy or rewrite; a page's paths are its own, under its prefix.
|
|
89
|
+
|
|
90
|
+
## Pi integration
|
|
91
|
+
|
|
92
|
+
- Registers `pi.events.on(HTTP_MOUNT_CHANNEL, …)` in the factory — registration only.
|
|
93
|
+
Binding happens on the first mount, never at load and never on `session_start`.
|
|
94
|
+
- `session_start` records the session's working directory and its context, so the
|
|
95
|
+
pin is read from that session's config and the chip is drawn in its UI.
|
|
96
|
+
- `session_shutdown` closes the session's socket, its LAN listener, and its chip.
|
|
97
|
+
It is idempotent.
|
|
98
|
+
|
|
99
|
+
## Design notes
|
|
100
|
+
|
|
101
|
+
A session has one port, so the socket cannot belong to a page. The page asks for a
|
|
102
|
+
prefix over `pi.events` — the mechanism the repository already uses for a
|
|
103
|
+
cross-extension request that must be answered — and `lib/http` owns the socket, the
|
|
104
|
+
dispatch, the body cap per mount, and the idle grace per mount.
|
|
105
|
+
|
|
106
|
+
The handshake is *claimed*, not awaited: `server.listen` cannot answer synchronously,
|
|
107
|
+
so the owner responds with the pending bind and only a request nobody claimed is
|
|
108
|
+
refused. Without that distinction a bind that is still starting looks exactly like a
|
|
109
|
+
session with no server.
|
|
110
|
+
|
|
111
|
+
## Files
|
|
112
|
+
|
|
113
|
+
| Path | Responsibility |
|
|
114
|
+
|---|---|
|
|
115
|
+
| [`../../extensions/serve/index.ts`](../../extensions/serve/index.ts) | The Pi wiring: the bus listener, the per-session directory and context, the port chip, and the shutdown |
|
|
116
|
+
| [`../../extensions/serve/registry.ts`](../../extensions/serve/registry.ts) | The session's servers: binding on first mount, the pin's owner, the remembered port, prefix refusals, the LAN listener a shared page borrows, and closing with the last mount |
|
|
117
|
+
| [`../../extensions/serve/config.ts`](../../extensions/serve/config.ts) | The `serve` section: `port`, its clamp, and the two documents it resolves over |
|
|
118
|
+
|
|
119
|
+
## Testing
|
|
120
|
+
|
|
121
|
+
`config.test.ts`, `registry.test.ts`, and `extension.test.ts`, declared under
|
|
122
|
+
`serve` in `test/suite-manifest.ts`.
|
|
123
|
+
|
|
124
|
+
The registry cases bind real sockets on `127.0.0.1`: one port for two pages of a
|
|
125
|
+
session, a second session on its own port, a refused duplicate prefix, the socket
|
|
126
|
+
closing with its last page and reopening where it was, the pin belonging to the
|
|
127
|
+
first session that binds, and a LAN share reaching one page only. The extension
|
|
128
|
+
cases drive the shipped bus path through `requestMount`, including the chip and the
|
|
129
|
+
shutdown.
|