@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
|
@@ -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`, the first `board` claim, 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 `team-agent-<id>` name; the first `board` claim joins under that same name, and renames nothing; `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.*
|
|
@@ -18,14 +20,14 @@ owner whose label nothing answers to, and a teammate that is not listening.*
|
|
|
18
20
|
|
|
19
21
|
| Capability | How | What you get |
|
|
20
22
|
|---|---|---|
|
|
21
|
-
| Join the team | `/board` as the lead, `/team join <label>` as a teammate, or
|
|
23
|
+
| Join the team | `/board` as the lead, `/team join <label>` as a teammate, or the first card you claim | The session is on the team under a name 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
|
|
|
@@ -56,19 +58,20 @@ its surface is the point: `board` and `team` join this session for you.
|
|
|
56
58
|
| You are | Action | What happens |
|
|
57
59
|
|---|---|---|
|
|
58
60
|
| The lead | `/board` | The session joins as `team-lead` — the page's own seat name — and listens, so a handoff or an offer is reported at its next turn |
|
|
59
|
-
| A teammate |
|
|
60
|
-
|
|
|
61
|
-
| A viewer | `/board` on a session that is not serving | The page is somebody else's to serve, so this session joins under its own
|
|
61
|
+
| A teammate | the first card it claims | The session joins as its own `team-agent-<id>` name and listens, and Pi's footer keeps the name it already had |
|
|
62
|
+
| A teammate who wants a name | `/team join billing-core` | The session joins as `billing-core`, and Pi's footer and `/resume` show that name — the one join that renames |
|
|
63
|
+
| A viewer | `/board` on a session that is not serving | The page is somebody else's to serve, so this session joins under its own name instead of the seat |
|
|
62
64
|
|
|
63
65
|
Joining is listening: mail is reported at that session's next turn, and **no turn is
|
|
64
66
|
raised** for it, so being on the team never spends a turn. `/team` says who is live and
|
|
65
|
-
who is live but not listening. `/board status`
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
who is live but not listening. `/board status` reports the team this session is on — which
|
|
68
|
+
label, and whether mail reaches it — whichever door put it there, and prints no team clause
|
|
69
|
+
only when this session never joined 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
|
|
|
@@ -133,7 +159,7 @@ An offer is advice: nothing is theirs until they claim it, and a card nobody cla
|
|
|
133
159
|
|
|
134
160
|
- **Nothing here can start a peer's turn.** A message is reported at the recipient's next
|
|
135
161
|
turn; a session that is not listening at all reads it only when it reads its inbox.
|
|
136
|
-
- **`to` must be on the team.** A session that never opened the board, joined, or
|
|
162
|
+
- **`to` must be on the team.** A session that never opened the board, joined, or claimed a
|
|
137
163
|
card cannot be handed a card, and an offer to nobody says so.
|
|
138
164
|
- **`team-lead` is a label, not a person.** The seat is not a session, so its actor id
|
|
139
165
|
`board:team-lead` answers no message; the label is the one the session serving the page
|
|
@@ -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
|
|
|
@@ -179,9 +207,9 @@ An offer is advice: nothing is theirs until they claim it, and a card nobody cla
|
|
|
179
207
|
and must work with the bus disabled; `team` is the lead's round and is the only thing
|
|
180
208
|
that needs the bus. If the round ever stops needing it, `team` folds back in.
|
|
181
209
|
- **Joining is the bus's, wearing `board`'s clothes.** A lead should not have to learn a
|
|
182
|
-
second tool to be on a team, so `/board` joins,
|
|
183
|
-
the one naming action. `wake` stays a bus-level choice and is never
|
|
184
|
-
behalf, because it spends turns.
|
|
210
|
+
second tool to be on a team, so `/board` joins, the first claimed card joins, and
|
|
211
|
+
`/team join` is the one naming action. `wake` stays a bus-level choice and is never
|
|
212
|
+
armed on a user's behalf, because it spends turns.
|
|
185
213
|
- **Push and pull are two verbs.** `assign` writes the owner and sends one message;
|
|
186
214
|
`offer` broadcasts and writes nothing. One verb with an optional `to` would have two
|
|
187
215
|
outcomes and one details shape, and a reader could not tell which happened.
|
|
@@ -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 |
|
|
@@ -9,21 +9,25 @@
|
|
|
9
9
|
|
|
10
10
|
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
11
11
|
import { plural } from "../../lib/board/round-text.ts";
|
|
12
|
+
import type { BoardTeam } from "../../lib/board/session.ts";
|
|
12
13
|
import type { JoinResult } from "../../lib/session-tool.ts";
|
|
13
14
|
import { createTeamJoin, type TeamJoin } from "../../lib/session-tool.ts";
|
|
14
|
-
import {
|
|
15
|
+
import { teamLine } from "./join.ts";
|
|
15
16
|
import type { BoardPage, BoardPageStatus } from "./page.ts";
|
|
16
17
|
|
|
17
18
|
/** The command's name, which is also the word a user types. */
|
|
18
19
|
export const BOARD_COMMAND = "board";
|
|
19
20
|
|
|
20
21
|
/** The words the command accepts, in the order a user is likely to want them. */
|
|
21
|
-
|
|
22
|
+
const BOARD_WORDS = ["status", "stop", "team"] as const;
|
|
22
23
|
|
|
23
|
-
export function registerCommands(
|
|
24
|
+
export function registerCommands(
|
|
25
|
+
pi: ExtensionAPI,
|
|
26
|
+
session: BoardTeam,
|
|
27
|
+
page: BoardPage,
|
|
28
|
+
deps: { team?: TeamJoin } = {},
|
|
29
|
+
): void {
|
|
24
30
|
const team = deps.team ?? createTeamJoin(pi);
|
|
25
|
-
/** What the last `/board` did about the team, for the status line. */
|
|
26
|
-
let joined: JoinResult | undefined;
|
|
27
31
|
pi.registerCommand(BOARD_COMMAND, {
|
|
28
32
|
description: "Open the shared board in a local browser: /board [team | status | stop]",
|
|
29
33
|
getArgumentCompletions: (prefix: string) => {
|
|
@@ -35,12 +39,24 @@ export function registerCommands(pi: ExtensionAPI, page: BoardPage, deps: { team
|
|
|
35
39
|
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
36
40
|
const word = args.trim().toLowerCase();
|
|
37
41
|
if (word === "status") {
|
|
38
|
-
ctx.ui.notify(statusLine(page.status(),
|
|
42
|
+
ctx.ui.notify(statusLine(await page.status(), session.team.last()), "info");
|
|
39
43
|
return;
|
|
40
44
|
}
|
|
41
45
|
if (word === "stop") {
|
|
42
|
-
const status = page.status();
|
|
43
|
-
// A
|
|
46
|
+
const status = await page.status();
|
|
47
|
+
// A note nobody answers is not a page to leave for the next session to trip over:
|
|
48
|
+
// it is dropped here, and the line says whose it was.
|
|
49
|
+
if (status.stale === true) {
|
|
50
|
+
const dropped = await page.dropStale(ctx);
|
|
51
|
+
ctx.ui.notify(
|
|
52
|
+
dropped
|
|
53
|
+
? `board: dropped ${status.seat}'s note for :${status.port} — it answered nobody`
|
|
54
|
+
: `board: nothing to stop here — ${status.seat} is answering on :${status.port}`,
|
|
55
|
+
"info",
|
|
56
|
+
);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
// A page another session serves is that session's to stop, and saying whose it is is the answer.
|
|
44
60
|
if (status.attached === true) {
|
|
45
61
|
ctx.ui.notify(
|
|
46
62
|
`board: nothing to stop here — ${status.seat} serves this repository on :${status.port}`,
|
|
@@ -48,7 +64,11 @@ export function registerCommands(pi: ExtensionAPI, page: BoardPage, deps: { team
|
|
|
48
64
|
);
|
|
49
65
|
return;
|
|
50
66
|
}
|
|
51
|
-
|
|
67
|
+
const stopped = await page.stop();
|
|
68
|
+
// The word is this session saying it serves nothing now: the page clears the seat too
|
|
69
|
+
// when it closes itself (the idle grace), and both writes are the same fact.
|
|
70
|
+
session.serve(undefined);
|
|
71
|
+
ctx.ui.notify(stopped ? "board: stopped" : "board: not running", "info");
|
|
52
72
|
return;
|
|
53
73
|
}
|
|
54
74
|
if (word !== "" && word !== "team") {
|
|
@@ -59,15 +79,21 @@ export function registerCommands(pi: ExtensionAPI, page: BoardPage, deps: { team
|
|
|
59
79
|
return;
|
|
60
80
|
}
|
|
61
81
|
const started = await page.start(ctx, word === "team" ? "team" : "board");
|
|
62
|
-
// A failed start has already said why. Otherwise the
|
|
63
|
-
//
|
|
64
|
-
//
|
|
82
|
+
// A failed start has already said why. Otherwise the seat is set from the page's own
|
|
83
|
+
// answer — the page a session serves is a role, and every name this session writes and
|
|
84
|
+
// is addressed by follows from it — and the join happens here, because opening the page
|
|
85
|
+
// is the lead's one action.
|
|
65
86
|
if (started.kind === "failed") return;
|
|
66
|
-
|
|
87
|
+
session.serve(started.kind === "served" ? started.seat : undefined);
|
|
88
|
+
const joined = await team.join(ctx, { label: session.who(ctx, ctx.cwd).label });
|
|
89
|
+
session.team.record(joined);
|
|
90
|
+
// The address is on the line because the browser is not the only way in: a machine
|
|
91
|
+
// with no default browser prints it here, and a tab closed by mistake is one
|
|
92
|
+
// command from being opened again.
|
|
67
93
|
ctx.ui.notify(
|
|
68
94
|
started.kind === "attached"
|
|
69
|
-
? `board: ${started.seat} is serving this repository on :${started.port} — viewing${teamLine(joined)}`
|
|
70
|
-
: `board: ${started.seat} serving ${started.
|
|
95
|
+
? `board: ${started.seat} is serving this repository on :${started.port} — viewing · open ${started.url}${teamLine(joined)}`
|
|
96
|
+
: `board: ${started.seat} serving ${started.repo} on :${started.port} · open ${started.url}${teamLine(joined)}`,
|
|
71
97
|
"info",
|
|
72
98
|
);
|
|
73
99
|
},
|
|
@@ -78,10 +104,15 @@ export function registerCommands(pi: ExtensionAPI, page: BoardPage, deps: { team
|
|
|
78
104
|
export function statusLine(status: BoardPageStatus, team?: JoinResult): string {
|
|
79
105
|
if (status.running) {
|
|
80
106
|
const watching = plural(status.watchers ?? 0, "tab");
|
|
81
|
-
return `board: ${status.seat} serving on :${status.port} · ${watching} watching · ${status.where}${teamLine(team)}`;
|
|
107
|
+
return `board: ${status.seat} serving ${status.repo} on :${status.port} · ${watching} watching · ${status.where}${teamLine(team)}`;
|
|
108
|
+
}
|
|
109
|
+
if (status.stale === true) {
|
|
110
|
+
return `board: watching ${status.seat} on :${status.port} — that note answers nobody; /board stop drops it${teamLine(team)}`;
|
|
82
111
|
}
|
|
83
112
|
if (status.attached === true) {
|
|
84
113
|
return `board: watching ${status.seat} on :${status.port} — nothing to stop here${teamLine(team)}`;
|
|
85
114
|
}
|
|
86
|
-
|
|
115
|
+
// The team outlives the page: a session that claimed a card is on it with no page open, so
|
|
116
|
+
// the line says which team this session is on whichever door put it there.
|
|
117
|
+
return `board: not running — /board opens it${teamLine(team)}`;
|
|
87
118
|
}
|