@gavin-hu/my-pi-agent 0.21.0 → 0.22.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 +46 -31
- package/README.zh-CN.md +40 -26
- package/assets/screens/board.png +0 -0
- package/assets/screens/chrome.png +0 -0
- package/assets/screens/file-browse.png +0 -0
- package/assets/screens/plan-review.png +0 -0
- package/assets/screens/plan.png +0 -0
- package/docs/extensions/README.md +22 -24
- package/docs/extensions/board.md +361 -147
- package/docs/extensions/bytefly.md +152 -111
- package/docs/extensions/check.md +15 -17
- package/docs/extensions/context.md +35 -15
- package/docs/extensions/file-browse.md +30 -31
- package/docs/extensions/file-open.md +9 -11
- package/docs/extensions/file-read.md +12 -14
- package/docs/extensions/goal.md +15 -17
- package/docs/extensions/job.md +54 -42
- package/docs/extensions/memory.md +17 -19
- package/docs/extensions/plan.md +166 -126
- package/docs/extensions/question.md +16 -18
- package/docs/extensions/rewind.md +17 -19
- package/docs/extensions/serve.md +34 -18
- package/docs/extensions/session-bus.md +38 -16
- package/docs/extensions/subagent.md +23 -25
- package/docs/extensions/team.md +72 -33
- package/docs/extensions/todo.md +18 -18
- package/docs/extensions/watch.md +31 -31
- package/docs/extensions/web-access.md +2 -4
- package/docs/extensions/wechat.md +34 -35
- package/docs/extensions/worktree.md +56 -50
- package/extensions/board/commands.ts +56 -15
- package/extensions/board/format.ts +43 -24
- package/extensions/board/index.ts +5 -4
- package/extensions/board/input.ts +4 -9
- package/extensions/board/join.ts +4 -40
- package/extensions/board/lazy-page.ts +40 -0
- package/extensions/board/page.ts +13 -11
- package/extensions/board/result.ts +30 -16
- package/extensions/board/rows.ts +62 -25
- package/extensions/board/schema.ts +42 -10
- package/extensions/board/snapshot.ts +2 -1
- package/extensions/board/tone.ts +22 -0
- package/extensions/board/tools.ts +131 -44
- package/extensions/board/types.ts +36 -32
- package/extensions/board/view/card.ts +229 -0
- package/extensions/board/view/client.ts +33 -15
- package/extensions/board/view/dialog.ts +169 -6
- package/extensions/board/view/empty.ts +47 -0
- package/extensions/board/view/keys.ts +13 -2
- package/extensions/board/view/live.ts +89 -21
- package/extensions/board/view/names.ts +13 -4
- package/extensions/board/view/page.ts +23 -9
- package/extensions/board/view/render.ts +208 -271
- package/extensions/board/view/styles.ts +123 -31
- package/extensions/board/view/team.ts +297 -76
- package/extensions/bytefly/block/fallback.ts +1 -1
- package/extensions/bytefly/doctor/runtime.ts +5 -2
- package/extensions/bytefly/footer/collect/label.ts +60 -0
- package/extensions/bytefly/footer/collect/snapshot.ts +8 -1
- package/extensions/bytefly/footer/config.ts +4 -2
- package/extensions/bytefly/footer/register.ts +33 -4
- package/extensions/bytefly/footer/render/lines.ts +7 -4
- package/extensions/bytefly/footer/render/segments.ts +37 -10
- package/extensions/bytefly/footer/render/status.ts +0 -11
- package/extensions/bytefly/footer/types.ts +7 -0
- package/extensions/bytefly/header/config.ts +1 -1
- package/extensions/bytefly/index.ts +32 -6
- package/extensions/bytefly/mode/editor.ts +115 -0
- package/extensions/bytefly/panel/commands.ts +210 -120
- package/extensions/bytefly/panel/format.ts +4 -4
- package/extensions/bytefly/panel/settings.ts +73 -6
- package/extensions/bytefly/panel/tui/keys.ts +2 -6
- package/extensions/bytefly/update/cache.ts +1 -1
- package/extensions/bytefly/update/register.ts +4 -4
- package/extensions/bytefly/working/config.ts +5 -7
- package/extensions/bytefly/working/format.ts +10 -13
- package/extensions/bytefly/working/register.ts +22 -27
- package/extensions/check/commands.ts +3 -2
- package/extensions/check/{tui.ts → view/check-screens.ts} +14 -19
- package/extensions/context/commands.ts +2 -2
- package/extensions/context/{tui.ts → view/context-report.ts} +51 -19
- package/extensions/file-browse/index.ts +67 -36
- package/extensions/file-read/render.ts +4 -4
- package/extensions/goal/plan-route.ts +2 -2
- package/extensions/job/child.ts +527 -0
- package/extensions/job/clock.ts +142 -0
- package/extensions/job/commands.ts +4 -2
- package/extensions/job/config.ts +8 -1
- package/extensions/job/format.ts +22 -1
- package/extensions/job/log-writer.ts +13 -2
- package/extensions/job/registry.ts +63 -7
- package/extensions/job/render.ts +34 -0
- package/extensions/job/runtime.ts +296 -655
- package/extensions/job/session.ts +132 -19
- package/extensions/job/store.ts +7 -7
- package/extensions/job/tools.ts +3 -3
- package/extensions/job/types.ts +47 -0
- package/extensions/job/{tui.ts → view/jobs-screen.ts} +19 -73
- package/extensions/job/waiters.ts +44 -1
- package/extensions/memory/commands.ts +4 -2
- package/extensions/memory/{tui.ts → view/memory-picker.ts} +12 -12
- package/extensions/plan/commands.ts +12 -9
- package/extensions/plan/format.ts +8 -2
- package/extensions/plan/git-bash.ts +115 -16
- package/extensions/plan/index.ts +53 -52
- package/extensions/plan/plan-index.ts +4 -2
- package/extensions/plan/plans.ts +5 -54
- package/extensions/plan/policy.ts +8 -10
- package/extensions/plan/review-lazy.ts +85 -0
- package/extensions/plan/review.ts +2 -2
- package/extensions/plan/runtime.ts +49 -39
- package/extensions/plan/sections.ts +1 -1
- package/extensions/plan/steps.ts +3 -3
- package/extensions/plan/todos.ts +1 -1
- package/extensions/plan/tools/cancel.ts +27 -62
- package/extensions/plan/tools/enter.ts +55 -81
- package/extensions/plan/tools/exit.ts +221 -238
- package/extensions/plan/tools/refusal.ts +18 -0
- package/extensions/plan/tools/steps.ts +75 -92
- package/extensions/plan/tools/write.ts +77 -120
- package/extensions/plan/tools.ts +188 -30
- package/extensions/plan/tui.ts +5 -5
- package/extensions/plan/types.ts +48 -5
- package/extensions/plan/web-client.ts +3 -0
- package/extensions/plan/web-lazy.ts +53 -0
- package/extensions/plan/web-markdown.ts +9 -67
- package/extensions/plan/web-payload.ts +3 -3
- package/extensions/plan/web-router.ts +18 -9
- package/extensions/plan/web.ts +1 -12
- package/extensions/question/tools.ts +6 -2
- package/extensions/question/{tui.ts → view/question-screen.ts} +11 -11
- package/extensions/rewind/commands/commands.ts +6 -3
- package/extensions/rewind/state/runtime.ts +1 -1
- package/extensions/rewind/{view/status.ts → status.ts} +4 -4
- package/extensions/rewind/view/review-tui.ts +4 -4
- package/extensions/serve/config.ts +10 -8
- package/extensions/serve/index.ts +17 -3
- package/extensions/serve/registry.ts +26 -17
- package/extensions/session-bus/commands.ts +11 -6
- package/extensions/session-bus/join-request.ts +2 -1
- package/extensions/session-bus/mail-watch.ts +26 -17
- package/extensions/session-bus/protocol.ts +15 -3
- package/extensions/session-bus/resolve.ts +5 -7
- package/extensions/session-bus/runtime.ts +40 -12
- package/extensions/session-bus/store.ts +101 -19
- package/extensions/session-bus/types.ts +19 -0
- package/extensions/session-bus/{tui.ts → view/session-list.ts} +16 -16
- package/extensions/subagent/agents.ts +1 -1
- package/extensions/subagent/commands.ts +4 -2
- package/extensions/subagent/format.ts +15 -15
- package/extensions/subagent/index.ts +1 -1
- package/extensions/subagent/render.ts +3 -3
- package/extensions/subagent/{background-tui.ts → view/agents-board.ts} +11 -11
- package/extensions/team/commands.ts +24 -7
- package/extensions/team/dispatch.ts +18 -4
- package/extensions/team/format.ts +17 -2
- package/extensions/team/index.ts +9 -8
- package/extensions/team/schema.ts +6 -5
- package/extensions/team/tools.ts +107 -45
- package/extensions/team/types.ts +3 -2
- package/extensions/todo/commands.ts +4 -2
- package/extensions/todo/format.ts +11 -0
- package/extensions/todo/render.ts +57 -0
- package/extensions/todo/runtime.ts +1 -1
- package/extensions/todo/tools.ts +1 -1
- package/extensions/todo/view/todo-screen.ts +122 -0
- package/extensions/todo/widget.ts +47 -0
- package/extensions/watch/commands.ts +4 -2
- package/extensions/watch/format.ts +36 -7
- package/extensions/watch/render.ts +57 -0
- package/extensions/watch/runtime.ts +27 -22
- package/extensions/watch/tools.ts +4 -4
- package/extensions/watch/{tui.ts → view/watch-screen.ts} +18 -80
- package/extensions/web-access/fetch/tool.ts +12 -4
- package/extensions/wechat/commands/commands.ts +18 -14
- package/extensions/wechat/index.ts +72 -37
- package/extensions/wechat/state/credentials.ts +1 -1
- package/extensions/wechat/state/state.ts +1 -1
- package/extensions/wechat/text/status.ts +112 -0
- package/extensions/wechat/types.ts +2 -2
- package/extensions/wechat/view/status-screen.ts +4 -103
- package/extensions/worktree/commands.ts +11 -9
- package/extensions/worktree/enter.ts +2 -2
- package/extensions/worktree/exit.ts +2 -2
- package/extensions/worktree/index.ts +5 -5
- package/extensions/worktree/screen.ts +3 -3
- package/extensions/worktree/status.ts +2 -2
- package/extensions/worktree/tools.ts +113 -116
- package/lib/README.md +27 -13
- package/lib/board/actions.ts +111 -0
- package/lib/board/artifact.ts +149 -0
- package/lib/board/card.ts +163 -52
- package/lib/board/config.ts +58 -0
- package/lib/board/contract.ts +113 -0
- package/lib/board/join.ts +38 -0
- package/lib/board/lanes.ts +182 -0
- package/lib/board/lead.ts +130 -0
- package/lib/board/limits.ts +78 -0
- package/lib/board/links.ts +31 -10
- package/lib/board/page-pointer.ts +1 -1
- package/lib/board/read.ts +83 -0
- package/lib/board/reason.ts +102 -0
- package/lib/board/round-text.ts +68 -6
- package/lib/board/round.ts +31 -15
- package/lib/board/rules.ts +151 -307
- package/lib/board/seat.ts +35 -0
- package/lib/board/session.ts +136 -54
- package/lib/board/skipped.ts +22 -0
- package/lib/board/steps.ts +487 -0
- package/lib/board/store.ts +87 -30
- package/lib/board/tone.ts +92 -0
- package/lib/board/view.ts +70 -58
- package/{extensions/watch → lib}/child.ts +40 -21
- package/lib/env.ts +65 -7
- package/lib/format.ts +13 -0
- package/lib/http/index.ts +14 -12
- package/lib/http/port.ts +15 -10
- package/lib/lazy.ts +45 -0
- package/lib/plan-entry.ts +39 -10
- package/lib/plans/paths.ts +82 -0
- package/lib/policy.ts +9 -6
- package/lib/report.ts +3 -2
- package/lib/requirements.ts +8 -8
- package/lib/session-label.ts +111 -5
- package/lib/session-roster.ts +43 -1
- package/lib/session-tool.ts +5 -4
- package/lib/stray-fields.ts +7 -2
- package/lib/tool-names.ts +5 -10
- package/lib/tui/README.md +23 -8
- package/lib/tui/chrome.ts +16 -15
- package/lib/tui/cursor.ts +18 -3
- package/lib/tui/glyphs.ts +9 -7
- package/lib/tui/tokens.ts +33 -2
- package/lib/tui/tool.ts +3 -3
- package/lib/web/README.md +21 -0
- package/lib/web/components.ts +5 -1
- package/lib/web/index.ts +1 -0
- package/lib/web/markdown.ts +83 -0
- package/lib/web/markup.ts +2 -1
- package/lib/web/runtime.ts +9 -10
- package/package.json +6 -1
- package/skills/init/SKILL.md +5 -5
- package/skills/next/SKILL.md +3 -5
- package/extensions/board/seat.ts +0 -28
- package/extensions/todo/tui.ts +0 -212
- package/lib/tui/index.ts +0 -34
- package/skills/project-docs/SKILL.md +0 -133
- package/skills/project-docs/references/audit.md +0 -131
- package/skills/project-docs/references/changelog.md +0 -105
- package/skills/project-docs/references/discovery.md +0 -127
- package/skills/project-docs/references/readme.md +0 -80
- package/skills/project-docs/references/release.md +0 -84
- package/skills/project-docs/references/roadmap.md +0 -93
- package/skills/project-docs/scripts/state.ts +0 -316
package/docs/extensions/team.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# team — the lead's round over the board
|
|
2
2
|
|
|
3
|
-
`board` owns the cards. `team` owns the lead's
|
|
3
|
+
`board` owns the cards. `team` owns the lead's four actions: `assign` hands a card to a
|
|
4
4
|
session that is **already running**, `offer` hands one to whoever is free, and `reclaim`
|
|
5
5
|
takes one back when its owner is gone or has gone quiet. `assign` writes the card and puts
|
|
6
6
|
a handoff on the session bus, `offer` writes only the moment it was made — never an owner,
|
|
@@ -11,32 +11,44 @@ The report says what the peer will actually do with it.
|
|
|
11
11
|
Nothing starts a process. A message lands in the peer's inbox and is reported at its next
|
|
12
12
|
turn, so `assign` and `offer` are messages, not launches.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
Both halves are **opt-in**, because the board is a workflow a session either runs or does
|
|
15
|
+
not. A lead names both — `PI_ENABLED_EXTENSIONS=board,team` — and a worker session that
|
|
16
|
+
only works cards names neither. They stay independent at runtime: naming one never loads
|
|
17
|
+
the other, and `team` is refused by `PI_DISABLED_EXTENSIONS` like every extension.
|
|
18
|
+
|
|
19
|
+
Naming `team` alone is legal and stays so — the factory needs no board to register — but
|
|
20
|
+
it is the crippled case: every card it moves is one no `board` tool can read. `doctor`
|
|
21
|
+
reports the board row as opt-in and names the switch that adds it, which is the one place
|
|
22
|
+
a reader who took that shortcut is told.
|
|
23
|
+
|
|
24
|
+
<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 of one-line rows, each a state dot, a label, a you chip, a mail badge, a not listening chip on a peer whose watch is off, and what that teammate holds at the row's right; and one muted Done line under both" width="100%">
|
|
15
25
|
|
|
16
26
|
*The Team tab: the cards that need the lead, then who is on the team — including an
|
|
17
|
-
owner whose label nothing answers to, and a teammate that is not listening
|
|
27
|
+
owner whose label nothing answers to, and a teammate that is not listening. A row is one
|
|
28
|
+
line; the owners a bus has no record of collapse to one row*
|
|
18
29
|
|
|
19
30
|
## Capabilities
|
|
20
31
|
|
|
21
32
|
| Capability | How | What you get |
|
|
22
33
|
|---|---|---|
|
|
23
34
|
| 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 |
|
|
24
|
-
| Hand a feature to a running session | `team` action `assign` | The card is `
|
|
35
|
+
| Hand a feature to a running session | `team` action `assign` | The card is `plan`, owned by that session, and a handoff is in its inbox |
|
|
25
36
|
| Tell the peer what to do first | `team` action `assign` with `note` | The handoff carries the card, its paths, its plan, and that line |
|
|
26
37
|
| 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 |
|
|
27
38
|
| 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 |
|
|
28
|
-
| Take a card back | `team` action `reclaim` | The owner clears, the card returns to
|
|
29
|
-
| Keep the round to the team | `team` | Only `assign`, `offer`, and `
|
|
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 |
|
|
39
|
+
| Take a card back | `team` action `reclaim` | The owner clears, the card returns to Backlog, and its notes stay |
|
|
40
|
+
| Keep the round to the team | `team` | Only `assign`, `offer`, `reclaim`, and `claim`: no card is created, contracted, approved, or finished here — that is `board`'s surface, which is where a claimed card is planned and moved |
|
|
41
|
+
| 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 owners the bus has no record of and that hold nothing are one line, `18 owners with no record · 76 done`, in the Team tab's own words — the fold is the same rule and the same sentence on both surfaces, and it keeps a long history from spending the round's cap. 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 |
|
|
31
42
|
|
|
32
43
|
## Quickstart
|
|
33
44
|
|
|
34
45
|
```
|
|
35
|
-
lead: /board → on the team as
|
|
46
|
+
lead: /board → on the team as lead, listening
|
|
36
47
|
peer: /team join billing-core → on the team as billing-core, listening
|
|
37
48
|
lead: team({action:"assign", card:"4", to:"billing-core", note:"start with the webhook"})
|
|
38
49
|
→ #4 handed off to billing-core.
|
|
39
|
-
peer: board({action:"
|
|
50
|
+
peer: board({action:"contract", card:"4", path:"…", goal:"…", acceptance:"…", scopeOut:"…"})
|
|
51
|
+
→ the card records its plan and its contract
|
|
40
52
|
peer: board({action:"note", card:"4", text:"…"}) → a phase, as it happens
|
|
41
53
|
later: team({action:"reclaim", card:"4"}) → #4 reclaimed from billing-core
|
|
42
54
|
```
|
|
@@ -49,6 +61,13 @@ lead: team({action:"offer", card:"5", note:"whoever is free"})
|
|
|
49
61
|
peer: board({action:"claim", card:"5"}) → the offer becomes work
|
|
50
62
|
```
|
|
51
63
|
|
|
64
|
+
Nobody free, so the lead takes it:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
lead: team({action:"claim", card:"5"}) → #5 is yours, in plan.
|
|
68
|
+
→ design it in plan mode, `plan` action `write`, then `board` action `contract`
|
|
69
|
+
```
|
|
70
|
+
|
|
52
71
|
## Joining the team
|
|
53
72
|
|
|
54
73
|
You do not configure a team; there are no members to add. The team is the session bus,
|
|
@@ -57,20 +76,22 @@ its surface is the point: `board` and `team` join this session for you.
|
|
|
57
76
|
|
|
58
77
|
| You are | Action | What happens |
|
|
59
78
|
|---|---|---|
|
|
60
|
-
| The lead | `/board` | The session joins as `
|
|
61
|
-
| A teammate | the first card it claims | The session joins
|
|
79
|
+
| The lead | `/board` | The session joins as the board's lead (`board.lead`, `lead` by default) — the name the page serves as — and listens, so a handoff or an offer is reported at its next turn |
|
|
80
|
+
| A teammate | the first card it claims | The session joins under the name it already holds, or a preset teammate name (`tom`, `lily`, `ben`) when it has none, and listens; Pi's footer keeps the name it already had |
|
|
62
81
|
| 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
82
|
| 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 |
|
|
64
83
|
|
|
65
84
|
Joining is listening: mail is reported at that session's next turn, and **no turn is
|
|
66
|
-
raised** for it, so being on the team never spends a turn. `/team`
|
|
85
|
+
raised** for it, so being on the team never spends a turn. `/team join` with no name draws
|
|
86
|
+
one when Pi has no name for the session — `lily` rather than the repository's basename — and
|
|
87
|
+
renames nothing; a session Pi already names keeps that name. `/team` says who is live and
|
|
67
88
|
who is live but not listening. `/board status` reports the team this session is on — which
|
|
68
89
|
label, and whether mail reaches it — whichever door put it there, and prints no team clause
|
|
69
90
|
only when this session never joined at all.
|
|
70
91
|
|
|
71
|
-
**The seat is not a session.** The page is a view: it has one name
|
|
72
|
-
of its own
|
|
73
|
-
back. Its cards come from the session that serves it, whose label is the same and whose id
|
|
92
|
+
**The seat is not a session.** The page is a view: it has one name — the board's lead — and
|
|
93
|
+
an id of its own, `board:<lead>`, and it writes nothing, so it holds no card for a lead to
|
|
94
|
+
take back. Its cards come from the session that serves it, whose label is the same and whose id
|
|
74
95
|
is not: a card that session claimed is the session's, and `release` returns it.
|
|
75
96
|
|
|
76
97
|
## Tool
|
|
@@ -80,9 +101,10 @@ is not: a card that session claimed is the session's, and `release` returns it.
|
|
|
80
101
|
|
|
81
102
|
| Action | Parameters | Behaviour |
|
|
82
103
|
|---|---|---|
|
|
83
|
-
| `assign` | `card`, `to`, `note?` | Checks the card's preconditions, sends the handoff as a `handoff` message, then records the owner |
|
|
104
|
+
| `assign` | `card`, `to`, `note?` | Checks the card's preconditions, sends the handoff as a `handoff` message, then records the owner. `to` that names this session is refused — a card you will take yourself is `claim` |
|
|
84
105
|
| `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 |
|
|
85
|
-
| `reclaim` | `card` | Clears the owner and returns the card to
|
|
106
|
+
| `reclaim` | `card` | Clears the owner and returns the card to Backlog, keeping its notes, contract, and plan |
|
|
107
|
+
| `claim` | `card` | Takes a pool card for this session: it enters `plan`, owned by the round's session, and the claim asks to join the team once — the same operation and the same join `board`'s claim makes. A card somebody holds, one that waits on unfinished work, and a full `plan` lane are refused with the board's own sentence |
|
|
86
108
|
|
|
87
109
|
`card` takes `4` or `#4`. `to` is a teammate's label or id, as `/team` lists them — a
|
|
88
110
|
label nobody answers to cannot be handed a card, and the result says so.
|
|
@@ -108,7 +130,7 @@ the sentence, and a test holds every published parameter to an action that acts
|
|
|
108
130
|
| What failed | What the card is | What the lead reads |
|
|
109
131
|
|---|---|---|
|
|
110
132
|
| the card is missing, `done`, held by a third session, or waiting on unfinished work | untouched | the refusal, with no message sent at all |
|
|
111
|
-
| the send | untouched — `
|
|
133
|
+
| the send | untouched — `backlog`, no owner | `#4 was not handed off to billing-core: …`, plus `The card is unchanged.` |
|
|
112
134
|
| 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 |
|
|
113
135
|
|
|
114
136
|
`offer` broadcasts **before** it records, for the same reason: an offer nobody received
|
|
@@ -136,15 +158,18 @@ not name — goes back to `reclaim` if its holder renames.
|
|
|
136
158
|
A handoff is an instruction, not a notification: the peer's notice is headlined
|
|
137
159
|
`1 handoff`, carries enough of the body to be actionable, and says the work is theirs —
|
|
138
160
|
read it with `session inbox`, record the plan, and start. The card is already theirs
|
|
139
|
-
(`assign` wrote the owner), so there is no claim step; the peer
|
|
140
|
-
moves it
|
|
161
|
+
(`assign` wrote the owner), so there is no claim step; the peer writes the contract, works,
|
|
162
|
+
notes, and moves it on — into `execute` once the lead has approved the contract, and to
|
|
163
|
+
`done` once the lead has spot-checked it.
|
|
141
164
|
|
|
142
165
|
An offer is advice: nothing is theirs until they claim it, and a card nobody claims stays
|
|
143
|
-
`
|
|
166
|
+
`backlog`, so it keeps showing up as unassigned in the round — with its age, which is the one
|
|
144
167
|
thing an event cannot carry. `board` prints it as `offered 1m ago, and nobody took it`, the
|
|
145
168
|
card face says `offered 1m ago · nobody took it`, and the Team tab's queue says
|
|
146
169
|
`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
|
|
170
|
+
decision the plain unassigned row never offered: re-offer, `assign` it, or take it with
|
|
171
|
+
`claim`, which is yours then — the card sits in `plan` waiting on you, and its contract,
|
|
172
|
+
moves, and finish are `board`'s in this same session.
|
|
148
173
|
|
|
149
174
|
## Security
|
|
150
175
|
|
|
@@ -161,13 +186,19 @@ decision the plain unassigned row never offered: re-offer, `assign` it, or take
|
|
|
161
186
|
turn; a session that is not listening at all reads it only when it reads its inbox.
|
|
162
187
|
- **`to` must be on the team.** A session that never opened the board, joined, or claimed a
|
|
163
188
|
card cannot be handed a card, and an offer to nobody says so.
|
|
164
|
-
-
|
|
165
|
-
|
|
166
|
-
|
|
189
|
+
- **A held card is not assignable.** `assign` moves a pool card; one somebody already holds
|
|
190
|
+
is refused, because a handoff cannot take work away from its owner — `reclaim` it, then
|
|
191
|
+
assign.
|
|
192
|
+
- **The lead's name is a label, not a person.** The seat is not a session, so its actor id
|
|
193
|
+
`board:<lead>` answers no message; the label is the one the session serving the page joins
|
|
194
|
+
under, so a handoff to it reaches whoever is serving the page just now —
|
|
167
195
|
and is refused, like any other label, when nobody is. The seat's cards come back with
|
|
168
196
|
`reclaim`.
|
|
169
197
|
- **A label-only owner is fragile.** If the bus could not name the session behind `to`,
|
|
170
198
|
the card records the label, and a rename would need a `reclaim` and a fresh handoff.
|
|
199
|
+
- **A preset name is drawn, not chosen.** An unnamed session that joins draws one of the
|
|
200
|
+
preset teammate names and Pi's footer keeps whatever it had; type `/team join <label>` to
|
|
201
|
+
pick the name and make it Pi's.
|
|
171
202
|
- **An offer is advice.** A card can sit offered indefinitely; the round keeps counting it
|
|
172
203
|
as unassigned, which is the honest signal that nobody took it.
|
|
173
204
|
- **An offer arrives as an `event`.** A burst that mixes one with ordinary messages is
|
|
@@ -179,7 +210,7 @@ decision the plain unassigned row never offered: re-offer, `assign` it, or take
|
|
|
179
210
|
- **Plan mode makes this unavailable.** Neither `team` nor `session` is a read-only tool, so
|
|
180
211
|
plan mode refuses both by name and tells the model to exit and get approval; the board
|
|
181
212
|
stays readable, since `list` is the one action it admits. Assign after the plan is
|
|
182
|
-
approved
|
|
213
|
+
approved by the lead — or the person, with `/board approve`.
|
|
183
214
|
- **Ownership is advisory.** `assign` does not create a path claim; the peer still claims
|
|
184
215
|
the paths it edits through `session`.
|
|
185
216
|
|
|
@@ -188,7 +219,8 @@ decision the plain unassigned row never offered: re-offer, `assign` it, or take
|
|
|
188
219
|
- **`board`**: the same store, the same cards, the same rules — `team` calls `lib/board/`
|
|
189
220
|
directly, so a card it hands over is one `board` can see. Either extension works without
|
|
190
221
|
the other: without `team` the page still reclaims, and without `board` there is nothing
|
|
191
|
-
to assign
|
|
222
|
+
to assign — which is why the lead enables both, and why enabling one never loads the
|
|
223
|
+
other.
|
|
192
224
|
- **`session-bus`**: the transport, and the one extension `team` talks to — through
|
|
193
225
|
`lib/session-tool.ts`, whose `join` is an in-process request (a command context cannot
|
|
194
226
|
call a tool) and whose `peers` and `send` are nested tool calls. The bus's actions and
|
|
@@ -221,10 +253,17 @@ decision the plain unassigned row never offered: re-offer, `assign` it, or take
|
|
|
221
253
|
guessing from the send.
|
|
222
254
|
- **Send first.** A card assigned without a message is a card nobody knows about, while a
|
|
223
255
|
message for a card that then changed is one the peer can still claim.
|
|
224
|
-
- **`reclaim`
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
256
|
+
- **`reclaim` and `claim` are here, and in `board`.** Every one of them is one store
|
|
257
|
+
operation, so the lane rules, the WIP limit, the refusal a waiting card gets, and the
|
|
258
|
+
offer record cleared are the store's answer rather than a second one the round keeps in
|
|
259
|
+
step by hand. `reclaim` belongs here because it is the compensation for a dispatch that
|
|
260
|
+
did not land; `claim` belongs here because the round is where a lead sees work nobody
|
|
261
|
+
took, and a lead told to "take it" should not have to leave the tool to do it. The
|
|
262
|
+
card's *life* after a claim — plan, contract, moves, finish — stays `board`'s: the round
|
|
263
|
+
hands work out and takes it, and the card is lived in one place.
|
|
264
|
+
- **Rejected: `assign` to yourself.** Handing a card to your own label records you as the
|
|
265
|
+
owner and mails your own inbox, which is a claim with an extra message and no next step;
|
|
266
|
+
it is refused, naming `claim`.
|
|
228
267
|
- **Rejected: `reclaim` messaging the previous owner.** A compensation path must not depend
|
|
229
268
|
on a second delivery — the reclaim is what you do when the owner is gone, so its message
|
|
230
269
|
would fail exactly when it is needed. The card is the record.
|
|
@@ -236,8 +275,8 @@ decision the plain unassigned row never offered: re-offer, `assign` it, or take
|
|
|
236
275
|
|
|
237
276
|
| File | Responsibility |
|
|
238
277
|
|---|---|
|
|
239
|
-
| `index.ts` | The factory: gate on `PI_DISABLED_EXTENSIONS`, build the session, register the tool and the command |
|
|
240
|
-
| `schema.ts` | The tool's
|
|
278
|
+
| `index.ts` | The factory: gate on the opt-in switch (`PI_ENABLED_EXTENSIONS`) and `PI_DISABLED_EXTENSIONS`, build the session, register the tool and the command |
|
|
279
|
+
| `schema.ts` | The tool's four actions and their parameters |
|
|
241
280
|
| `tools.ts` | The dispatch order, the refusals, and the report |
|
|
242
281
|
| `dispatch.ts` | The handoff and offer text, the bus behind a seam, and who is not listening |
|
|
243
282
|
| `commands.ts` | `/team` — the round, and `/team join [<label>]` |
|
package/docs/extensions/todo.md
CHANGED
|
@@ -10,21 +10,19 @@ one update when work lands without one.
|
|
|
10
10
|
|
|
11
11
|
## Capabilities
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
|
16
|
-
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
|
|
|
21
|
-
|
|
|
22
|
-
|
|
|
23
|
-
|
|
|
24
|
-
|
|
|
25
|
-
|
|
|
26
|
-
| Lag reminder | the `agent_before_settle` nudge | One hidden reminder and one forced continuation when mutating work lands without a todo update. | Shipped |
|
|
27
|
-
| Structured result | `structuredContent` | Codemode scripts read the list as data. | Shipped |
|
|
13
|
+
| Capability | How | What you get |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| Whole-list tracking | `todo` | One call replaces the list; an empty list clears it. |
|
|
16
|
+
| Live progress rail | the `todo` widget | Progress and the active item, one line above the editor. |
|
|
17
|
+
| Atomic validation | `todo` validation | At most one `in_progress`, at most 50 items, unique non-empty content, and a known status; a violation returns an error with the unchanged list. |
|
|
18
|
+
| Terminal-safe normalization | sanitized `content` and `activeForm` | Control characters, newlines, tabs, and repeated spaces collapse to one terminal-safe line before the list is stored. |
|
|
19
|
+
| Compact model result | the `todo` result | The model gets progress and the active item, not an echo of the whole list, so every update does not repeat the table. |
|
|
20
|
+
| Transcript rendering | the `todo` transcript block | The call line carries progress; the collapsed rows lead with active work, the expanded rows keep submission order, and a cap ends with `… N more`. |
|
|
21
|
+
| Full list on demand | `/todos` | A scrollable screen in the order the model submitted. |
|
|
22
|
+
| Headless listing | `/todos` outside `tui` | RPC, print, and JSON modes get the same list as a notification, with no terminal screen. |
|
|
23
|
+
| Branch-following state | tool-result `details` | The list survives `/resume` and `/tree`, and abandoned branches never leak in. |
|
|
24
|
+
| Lag reminder | the `agent_before_settle` nudge | One hidden reminder and one forced continuation when mutating work lands without a todo update. |
|
|
25
|
+
| Structured result | `structuredContent` | Codemode scripts read the list as data. |
|
|
28
26
|
|
|
29
27
|
## Quickstart
|
|
30
28
|
|
|
@@ -238,10 +236,12 @@ depends on config.
|
|
|
238
236
|
| `schema.ts` | TypeBox parameters; the parser and limits live in `lib/todo-state.ts`. |
|
|
239
237
|
| `state.ts` | Status queries over the replayed list (pure). |
|
|
240
238
|
| `nudge.ts` | Lag detection: mutating and file-write classification, due check, reminder text (pure). |
|
|
241
|
-
| `format.ts` | Model-facing and transcript text (pure). |
|
|
242
|
-
| `
|
|
239
|
+
| `format.ts` | Model-facing and transcript text, plus the item row both surfaces draw (pure). |
|
|
240
|
+
| `render.ts` | The `TodoResult` transcript block over the memoized rail, drawn from `lib/tui/render-cache.ts`. |
|
|
241
|
+
| `widget.ts` | The persistent one-line widget and its `WIDGET_KEY`, drawn from the `lib/tui/` kit. |
|
|
242
|
+
| `view/todo-screen.ts` | The scrollable `/todos` screen, loaded on first use (`titleRule`, `dockListFrame`, `emptyState`). |
|
|
243
243
|
| `config.ts` | Widget config load/validation (the `todo` section of `bytefly.json`). |
|
|
244
244
|
| `runtime.ts` | Session-scoped state, lag tracking, and widget synchronization. |
|
|
245
245
|
| `tools.ts` | `todo` tool registration and rendering. |
|
|
246
246
|
| `commands.ts` | `/todos`. |
|
|
247
|
-
| `tools.test.ts`, `runtime.test.ts`, `
|
|
247
|
+
| `tools.test.ts`, `runtime.test.ts`, `view/todo-screen.test.ts` | The capability suites, declared in `test/suite-manifest.ts`. |
|
package/docs/extensions/watch.md
CHANGED
|
@@ -8,32 +8,30 @@ after a running turn settles.
|
|
|
8
8
|
|
|
9
9
|
## Capabilities
|
|
10
10
|
|
|
11
|
-
Status records what a reader can rely on: **Shipped** is in a tagged release, **Unreleased** is complete in the current tree but not yet tagged, and **Partial** has one released part and one unreleased part. Rows are ordered by user value, highest first.
|
|
12
|
-
|
|
13
11
|
### Watching
|
|
14
12
|
|
|
15
|
-
| Capability | How | What you get |
|
|
16
|
-
|
|
17
|
-
| Tail a source | `watch add` | A file is followed from its current end unless `fromStart` is set, and a shrinking file is re-read from the start; a command's merged stdout/stderr is streamed. |
|
|
18
|
-
| Line matching | `watch add` | An include pattern with an optional exclude list, case-insensitive unless `caseSensitive` is set. |
|
|
19
|
-
| Match delivery | `watch add`, `watch matches` | Pending matches are reported to the model at the start of the next turn. |
|
|
20
|
-
| Wake turn | `watch add`, the `wakeOnMatch` key | One triggered turn as soon as the agent is idle, including right after a running turn settles. |
|
|
21
|
-
| Desktop alert | `my-pi-agent/watch-match`, `notify.enabled`, `notify.matchFound` | A wake match leaves the terminal as an OS notification. |
|
|
22
|
-
| Live status | `watch add`, the `showStatus` key | A `⊚ N` running chip and a `! N` unreported-match chip, plus one alert per unreported burst. |
|
|
23
|
-
| Command outcome | `watch status`, `watch list` | A command watcher reports its exit code or error, including the captured signal, instead of only "stopped". |
|
|
24
|
-
| Liveness detail | `watch list`, `watch status` | A running watcher names its pid and elapsed time; list rows sort running watchers first, newest next. |
|
|
25
|
-
| Transcript rendering | the `watch` transcript block | The outcome line, at most eight rows, and a `(ctrl+o to expand)` hint keep the transcript compact. |
|
|
26
|
-
| Sanitized output | `watch matches`, `/watch matches` | Model- and remote-authored output is stripped of control characters before it reaches a terminal or a notification. |
|
|
13
|
+
| Capability | How | What you get |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| Tail a source | `watch add` | A file is followed from its current end unless `fromStart` is set, and a shrinking file is re-read from the start; a command's merged stdout/stderr is streamed. |
|
|
16
|
+
| Line matching | `watch add` | An include pattern with an optional exclude list, case-insensitive unless `caseSensitive` is set. |
|
|
17
|
+
| Match delivery | `watch add`, `watch matches` | Pending matches are reported to the model at the start of the next turn. |
|
|
18
|
+
| Wake turn | `watch add`, the `wakeOnMatch` key | One triggered turn as soon as the agent is idle, including right after a running turn settles. |
|
|
19
|
+
| Desktop alert | `my-pi-agent/watch-match`, `notify.enabled`, `notify.matchFound` | A wake match leaves the terminal as an OS notification. |
|
|
20
|
+
| Live status | `watch add`, the `showStatus` key | A `⊚ N` running chip and a `! N` unreported-match chip, plus one alert per unreported burst. |
|
|
21
|
+
| Command outcome | `watch status`, `watch list` | A command watcher reports its exit code or error, including the captured signal, instead of only "stopped". |
|
|
22
|
+
| Liveness detail | `watch list`, `watch status` | A running watcher names its pid and elapsed time; list rows sort running watchers first, newest next. |
|
|
23
|
+
| Transcript rendering | the `watch` transcript block | The outcome line, at most eight rows, and a `(ctrl+o to expand)` hint keep the transcript compact. |
|
|
24
|
+
| Sanitized output | `watch matches`, `/watch matches` | Model- and remote-authored output is stripped of control characters before it reaches a terminal or a notification. |
|
|
27
25
|
|
|
28
26
|
### Reading and managing
|
|
29
27
|
|
|
30
|
-
| Capability | How | What you get |
|
|
31
|
-
|
|
32
|
-
| Watcher inventory | `watch list`, `watch status`, `/watch list` | Each watcher's source, state, and line and match counts, singly or as a table. |
|
|
33
|
-
| Matched lines | `watch matches`, `/watch matches` | The matching lines, sanitized and read from a bounded history. |
|
|
34
|
-
| Raw output | `watch logs`, `/watch logs` | The merged output tail, for checking a pattern that matches nothing. |
|
|
35
|
-
| Watcher screen | `/watch` | A dock screen with the watcher list, the focused watcher's detail, and its matches pane. |
|
|
36
|
-
| Stopping and cleanup | `watch stop`, `watch clear`, `/watch stop`, `/watch clear` | A running watcher stops, stopped ones are removed, and `all` takes the running ones too. |
|
|
28
|
+
| Capability | How | What you get |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| Watcher inventory | `watch list`, `watch status`, `/watch list` | Each watcher's source, state, and line and match counts, singly or as a table. |
|
|
31
|
+
| Matched lines | `watch matches`, `/watch matches` | The matching lines, sanitized and read from a bounded history. |
|
|
32
|
+
| Raw output | `watch logs`, `/watch logs` | The merged output tail, for checking a pattern that matches nothing. |
|
|
33
|
+
| Watcher screen | `/watch` | A dock screen with the watcher list, the focused watcher's detail, and its matches pane. |
|
|
34
|
+
| Stopping and cleanup | `watch stop`, `watch clear`, `/watch stop`, `/watch clear` | A running watcher stops, stopped ones are removed, and `all` takes the running ones too. |
|
|
37
35
|
|
|
38
36
|
## Quickstart
|
|
39
37
|
|
|
@@ -224,7 +222,8 @@ The shared seams are `lib/`'s: `lib/spawn.ts` (spawn, tree-kill), `lib/lines.ts`
|
|
|
224
222
|
`lib/tui/status-controller.ts` (chips and the alert), and
|
|
225
223
|
`lib/tui/dock-screen.ts` (the `/watch` frame, navigation, and confirm layer).
|
|
226
224
|
What stays here is the watcher policy: the tailer, the matcher, the source
|
|
227
|
-
model, the command-child lifecycle
|
|
225
|
+
model, and the watcher vocabulary; the command-child lifecycle is
|
|
226
|
+
`lib/child.ts`, which `job` drives too.
|
|
228
227
|
|
|
229
228
|
The file tailer polls and exposes `tick()` publicly; the interval is a thin
|
|
230
229
|
wrapper over `tick()`, so tests drive matching deterministically with no real
|
|
@@ -248,18 +247,19 @@ outlive the session.
|
|
|
248
247
|
| `tools.ts` | The `watch` tool definition, execution, and transcript rendering. |
|
|
249
248
|
| `schema.ts` | TypeBox parameters and pure `normalizeCall` validation. |
|
|
250
249
|
| `types.ts` | `WatchAction`/`WatchStatus`, `WatchRecord`, `WatchDetails`, `PendingMatch`. |
|
|
251
|
-
| `runtime.ts` | The session-scoped watcher table, matcher, poll tick, and shutdown; spawning and tree-kill come from `child.ts`, line splitting from `lib/lines.ts`. |
|
|
252
|
-
| `
|
|
253
|
-
| `format.ts` | Pure text formatting; sanitization is `lib/format.ts`'s `sanitizeUntrustedLine`. |
|
|
250
|
+
| `runtime.ts` | The session-scoped watcher table, matcher, poll tick, and shutdown; spawning and tree-kill come from `lib/child.ts`, line splitting from `lib/lines.ts`. |
|
|
251
|
+
| `format.ts` | Pure text formatting, including the watcher row both surfaces draw; sanitization is `lib/format.ts`'s `sanitizeUntrustedLine`. |
|
|
254
252
|
| `ui.ts` | Status chips and the match alert, over the shared `lib/tui/status-controller.ts` (chips, attached context, `notify`). |
|
|
255
|
-
| `
|
|
253
|
+
| `render.ts` | The `WatchResult` transcript block over the shared `TailResult`. |
|
|
254
|
+
| `view/watch-screen.ts` | The `/watch` screen as a `DockListScreen` subclass: watcher rows, focus block, counts, verbs, loaded on first use. |
|
|
256
255
|
| `commands.ts` | The `/watch` command and its completions. |
|
|
257
256
|
| `config.ts` | The `watch` config section load and normalization. |
|
|
258
257
|
|
|
259
258
|
## Testing
|
|
260
259
|
|
|
261
|
-
`runtime.test.ts` covers config, schema, the
|
|
262
|
-
|
|
263
|
-
`
|
|
264
|
-
through the shared `createFakePi`/`fakeCtx` doubles. The test
|
|
265
|
-
`test/helpers/fixtures/watch.ts
|
|
260
|
+
`runtime.test.ts` covers config, schema, and the tailer with a fake clock and
|
|
261
|
+
fake children; `tools.test.ts` covers the tool and command; `format.test.ts` covers
|
|
262
|
+
the text `format.ts` builds; `view/watch-screen.test.ts` covers registration, the status
|
|
263
|
+
chips, and the screen through the shared `createFakePi`/`fakeCtx` doubles. The test
|
|
264
|
+
harness is `test/helpers/fixtures/watch.ts`, which also builds the `WatchRecord` a
|
|
265
|
+
test varies one field of.
|
|
@@ -8,8 +8,6 @@ reads a URL as text. Both are `direct`, active on registration, and read the
|
|
|
8
8
|
|
|
9
9
|
## Capabilities
|
|
10
10
|
|
|
11
|
-
Status records what a reader can rely on: **Shipped** is in a tagged release, **Unreleased** is complete in the current tree but not yet tagged, and **Partial** has one released part and one unreleased part. Rows are ordered by user value, highest first.
|
|
12
|
-
|
|
13
11
|
| Capability | How | What you get | Status |
|
|
14
12
|
|---|---|---|---|
|
|
15
13
|
| Web search | `web_search` | Titles, URLs, snippets, and a direct answer when the provider has one. | Shipped |
|
|
@@ -410,11 +408,11 @@ the content type and size.
|
|
|
410
408
|
| `fetch/tool.ts` | `web_fetch` definition, activation, rendering |
|
|
411
409
|
| `fetch/config.ts` | Fetch config type, defaults, clamping |
|
|
412
410
|
| `fetch/schema.ts` | Params/output schema, `resolveRequest()` |
|
|
413
|
-
| `fetch/page.ts` | Fetch + redirect-following + content-type routing; PDF extraction is injected via `FetchDeps` from the shared `lib/pdf.ts
|
|
411
|
+
| `fetch/page.ts` | Fetch + redirect-following + content-type routing; PDF extraction is injected via `FetchDeps` from the shared `lib/pdf.ts`. Loaded by the tool's execute, so a session that never fetches pays nothing |
|
|
414
412
|
| `fetch/structured.ts` | JSON/CSV/XML body → readable text, or `undefined` to keep the raw body |
|
|
415
413
|
| `fetch/ssrf.ts` | URL/host validation and private-address blocking |
|
|
416
414
|
| `fetch/extract.ts` | HTML → title + readable text |
|
|
417
|
-
| `fetch/find.ts` | Passage search (exact/insensitive/fuzzy) |
|
|
415
|
+
| `fetch/find.ts` | Passage search (exact/insensitive/fuzzy); loaded by the tool's execute |
|
|
418
416
|
| `fetch/cache.ts` | Per-registration LRU/TTL page cache (`createPageCache`) |
|
|
419
417
|
| `fetch/format.ts`, `fetch/types.ts` | Result text and data types |
|
|
420
418
|
|
|
@@ -8,8 +8,6 @@ subprocess per peer.
|
|
|
8
8
|
|
|
9
9
|
## Capabilities
|
|
10
10
|
|
|
11
|
-
Status records what a reader can rely on: **Shipped** is in a tagged release, **Unreleased** is complete in the current tree but not yet in a tagged release, and **Partial** has one released part and one unreleased part. Rows are ordered by user value, highest first.
|
|
12
|
-
|
|
13
11
|
### Conversation
|
|
14
12
|
|
|
15
13
|
| Capability | How | What you get | Status |
|
|
@@ -28,22 +26,22 @@ Status records what a reader can rely on: **Shipped** is in a tagged release, **
|
|
|
28
26
|
|
|
29
27
|
### Remote control
|
|
30
28
|
|
|
31
|
-
| Capability | How | What you get |
|
|
32
|
-
|
|
33
|
-
| Plan review over chat | automatic:
|
|
34
|
-
| Forwarded dialogs | `remotePrompts` | A dialog raised inside a WeChat turn is asked and answered in that peer's chat. |
|
|
35
|
-
| Peer questions | `ask_user_question` | The peer answers their own question, numbered, and a reply cannot reach another peer's prompt. |
|
|
36
|
-
| Control screen | `/wechat` | Account, connection, per-peer rows, activity, and any waiting prompt on one live screen. |
|
|
37
|
-
| Prompt cancellation | `/wechat`, `c` on the control screen | One waiting question is withdrawn with a notice, without stopping the peer's turn. |
|
|
38
|
-
| Peer steering | `/wechat stop <peer>`, `/wechat reset <peer>` | One peer's running turn stops, or their conversation starts fresh. |
|
|
29
|
+
| Capability | How | What you get |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| Plan review over chat | automatic: a `plan` action `exit` on a WeChat turn | The plan arrives as a file, then one compact editor prompt with three actions. |
|
|
32
|
+
| Forwarded dialogs | `remotePrompts` | A dialog raised inside a WeChat turn is asked and answered in that peer's chat. |
|
|
33
|
+
| Peer questions | `ask_user_question` | The peer answers their own question, numbered, and a reply cannot reach another peer's prompt. |
|
|
34
|
+
| Control screen | `/wechat` | Account, connection, per-peer rows, activity, and any waiting prompt on one live screen. |
|
|
35
|
+
| Prompt cancellation | `/wechat`, `c` on the control screen | One waiting question is withdrawn with a notice, without stopping the peer's turn. |
|
|
36
|
+
| Peer steering | `/wechat stop <peer>`, `/wechat reset <peer>` | One peer's running turn stops, or their conversation starts fresh. |
|
|
39
37
|
|
|
40
38
|
### Security and admission
|
|
41
39
|
|
|
42
|
-
| Capability | How | What you get |
|
|
43
|
-
|
|
44
|
-
| Owner-only admission | `allowedPeers`, `/wechat allow [peer]` | Only the owner who scanned the QR drives the agent until another sender is named. |
|
|
45
|
-
| Per-peer tool allowlist | `peers[<id>].tools` | An extra admitted peer can be narrowed to named tools; an empty list leaves none. |
|
|
46
|
-
| One poller per account | `poller.lock` | A second `pi` process is refused instead of stealing the account's messages. |
|
|
40
|
+
| Capability | How | What you get |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| Owner-only admission | `allowedPeers`, `/wechat allow [peer]` | Only the owner who scanned the QR drives the agent until another sender is named. |
|
|
43
|
+
| Per-peer tool allowlist | `peers[<id>].tools` | An extra admitted peer can be narrowed to named tools; an empty list leaves none. |
|
|
44
|
+
| One poller per account | `poller.lock` | A second `pi` process is refused instead of stealing the account's messages. |
|
|
47
45
|
|
|
48
46
|
### Delivery and reliability
|
|
49
47
|
|
|
@@ -57,23 +55,23 @@ Status records what a reader can rely on: **Shipped** is in a tagged release, **
|
|
|
57
55
|
|
|
58
56
|
### Pairing and transport
|
|
59
57
|
|
|
60
|
-
| Capability | How | What you get |
|
|
61
|
-
|
|
62
|
-
| QR login | `/wechat login` | One scan stores the account and the bridge starts polling it, replacing a running poller. |
|
|
63
|
-
| Reconnect after expiry | `/wechat login`, `l` on the control screen | A fresh login clears the expired chip and starts a new worker instead of leaving a dead token. |
|
|
64
|
-
| Automatic start | `session_start`, a stored account | The bridge runs whenever credentials exist: the account is the on/off switch, so there is no start command. |
|
|
65
|
-
| Pause and resume | `/wechat pause`, `/wechat resume` | Polling stops while running turns finish, the lock is released, and resume takes it back. |
|
|
66
|
-
| Status chip | `/wechat status` | The status bar names the state — `✆ wechat`, `✆ wechat paused`, or a persistent `✆ wechat expired`. |
|
|
67
|
-
| Logout | `/wechat logout` | One confirmation discards the credentials and stops polling. |
|
|
58
|
+
| Capability | How | What you get |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| QR login | `/wechat login` | One scan stores the account and the bridge starts polling it, replacing a running poller. |
|
|
61
|
+
| Reconnect after expiry | `/wechat login`, `l` on the control screen | A fresh login clears the expired chip and starts a new worker instead of leaving a dead token. |
|
|
62
|
+
| Automatic start | `session_start`, a stored account | The bridge runs whenever credentials exist: the account is the on/off switch, so there is no start command. |
|
|
63
|
+
| Pause and resume | `/wechat pause`, `/wechat resume` | Polling stops while running turns finish, the lock is released, and resume takes it back. |
|
|
64
|
+
| Status chip | `/wechat status` | The status bar names the state — `✆ wechat`, `✆ wechat paused`, or a persistent `✆ wechat expired`. |
|
|
65
|
+
| Logout | `/wechat logout` | One confirmation discards the credentials and stops polling. |
|
|
68
66
|
|
|
69
67
|
### Upkeep
|
|
70
68
|
|
|
71
|
-
| Capability | How | What you get |
|
|
72
|
-
|
|
73
|
-
| Health report | `/wechat doctor` | Connection, token age, last poll, lock holder, per-peer queues, and recent failures in one read-only report. |
|
|
74
|
-
| Idle session cleanup | automatic: the 30-minute idle sweep | An idle peer's Pi session is disposed, so idle conversations do not keep costing context or memory. |
|
|
75
|
-
| Peer history | `/wechat history <peer> [n]` | That peer's recent messages, ten by default and twenty at most, without a live session. |
|
|
76
|
-
| Command help | `/wechat help` | Every subcommand with a line each, printed in the transcript. |
|
|
69
|
+
| Capability | How | What you get |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| Health report | `/wechat doctor` | Connection, token age, last poll, lock holder, per-peer queues, and recent failures in one read-only report. |
|
|
72
|
+
| Idle session cleanup | automatic: the 30-minute idle sweep | An idle peer's Pi session is disposed, so idle conversations do not keep costing context or memory. |
|
|
73
|
+
| Peer history | `/wechat history <peer> [n]` | That peer's recent messages, ten by default and twenty at most, without a live session. |
|
|
74
|
+
| Command help | `/wechat help` | Every subcommand with a line each, printed in the transcript. |
|
|
77
75
|
|
|
78
76
|
## Quickstart
|
|
79
77
|
|
|
@@ -383,7 +381,7 @@ plan file is sent before one compact editor prompt with the title, first steps,
|
|
|
383
381
|
mode status, and three actions. Reply 2 to keep planning or 3 to exit; any other
|
|
384
382
|
text is a refinement. If the upload fails, the plan text is sent instead. The
|
|
385
383
|
plan file stays on disk. After a Keep decision the user can say “exit plan mode”; the model
|
|
386
|
-
calls `
|
|
384
|
+
calls `plan` action `cancel`, which needs no plan path and records no todo or goal.
|
|
387
385
|
The flow mirrors the
|
|
388
386
|
reference client: `getuploadurl` → AES-128-ECB encrypt → `POST` the ciphertext
|
|
389
387
|
(`Content-Type: application/octet-stream`) → read `x-encrypted-param` → send a
|
|
@@ -415,7 +413,7 @@ The same run answers a second question: every message it receives is printed wit
|
|
|
415
413
|
the `group_id` and `session_id` the protocol supplied, when it supplied them.
|
|
416
414
|
Nothing in the bridge routes on either — a conversation is keyed by sender — so a
|
|
417
415
|
message posted in a group is the only way to learn whether rooms are delivered at
|
|
418
|
-
all,
|
|
416
|
+
all, a question a group-chat design would have to answer first.
|
|
419
417
|
|
|
420
418
|
## Troubleshooting
|
|
421
419
|
|
|
@@ -520,7 +518,7 @@ in-process).
|
|
|
520
518
|
| Peer dialogs | `bindExtensions({ mode: "rpc", uiContext })` on each peer session, with `createPeerUIContext(channel)` routing `select`/`confirm`/`input`/`editor` to that peer and marking the context for `askHuman`'s file/text preface. The terminal's shared `ctx.ui` is deliberately not adapted: a host turn is never answered remotely, so its dialogs stay local even while a peer turn runs. |
|
|
521
519
|
| Status | The bridge's connection state (`running` / `paused` / `expired` / `stopped`) is published through `onStateChange` to `ctx.ui.setStatus(STATUS_KEYS.wechat, …)`; an expiry keeps a persistent error chip, pause keeps a warning chip, and the status bar renders it on line 2. |
|
|
522
520
|
| State | Files under `<agent-dir>/wechat/`; the bridge is not authoritative UI, so nothing is stored in the transcript. |
|
|
523
|
-
| Lifecycle | The factory only registers. The poll loop starts on `session_start` when an account is stored (and after `/wechat login`); `session_shutdown` closes it. One bridge per extension runtime: it is built once, `open()` is a no-op while running or paused (so the RPC double `session_start` on a session switch cannot start a second poller or resume a paused one), and shutdown is idempotent, so the reload and replacement paths (`/reload`, `/new`, resume, fork — all of which re-run the factory and emit shutdown before the new start) each end with exactly one poller. `pause()` stops the loop and releases the lock while keeping the worker, so a running turn can finish; `resume()` reacquires the lock and starts a new loop. Pause is runtime-local, so a reload or restart starts a fresh running bridge. `/wechat` screens use `ctx.ui.custom`, so they degrade (status) or decline (login, logout) while a peer turn runs. |
|
|
521
|
+
| Lifecycle | The factory only registers, and the bridge module itself loads on that start: the import is deferred behind the account check, so a session with no account never evaluates the bridge, the peer worker, the transport or the lock. The poll loop starts on `session_start` when an account is stored (and after `/wechat login`); `session_shutdown` closes it. One bridge per extension runtime: it is built once, `open()` is a no-op while running or paused (so the RPC double `session_start` on a session switch cannot start a second poller or resume a paused one), and shutdown is idempotent, so the reload and replacement paths (`/reload`, `/new`, resume, fork — all of which re-run the factory and emit shutdown before the new start) each end with exactly one poller. `pause()` stops the loop and releases the lock while keeping the worker, so a running turn can finish; `resume()` reacquires the lock and starts a new loop. Pause is runtime-local, so a reload or restart starts a fresh running bridge. `/wechat` screens use `ctx.ui.custom`, so they degrade (status) or decline (login, logout) while a peer turn runs. |
|
|
524
522
|
|
|
525
523
|
## Design notes
|
|
526
524
|
|
|
@@ -594,7 +592,7 @@ status, which is what the old start/stop guidance used to do.
|
|
|
594
592
|
| File | Responsibility |
|
|
595
593
|
|---|---|
|
|
596
594
|
| `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, the status snapshot, and `sendToOwner`. |
|
|
595
|
+
| `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`. Loaded on `session_start` when an account is stored, never at registration, so a session with no account evaluates none of it. |
|
|
598
596
|
| `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. |
|
|
599
597
|
| `tools.ts` | The `send_wechat` tool: owner-only proactive text or one image, with the neither-or-both refusal. |
|
|
600
598
|
| `config.ts` | The `wechat` section of `bytefly.json` loading and normalization. |
|
|
@@ -608,7 +606,7 @@ status, which is what the old start/stop guidance used to do.
|
|
|
608
606
|
| `transport/inbound.ts` | Inbound media resolution (download, decrypt, save, attach) and the hand-off to a peer's session. |
|
|
609
607
|
| `transport/media.ts` | Media key/encrypt/decrypt, padded size, upload/download URL, image MIME sniff (undefined when the bytes are no image), filename sanitize, and save helpers. |
|
|
610
608
|
| `state/state.ts` | `state.json` (cursor, owner, per-peer tokens, recent message ids). |
|
|
611
|
-
| `state/credentials.ts` | `credentials.json` (`0600`, every stored account and the active one). |
|
|
609
|
+
| `state/credentials.ts` | `<agent-dir>/wechat/` and its `credentials.json` (`0600`, every stored account and the active one). |
|
|
612
610
|
| `state/lock.ts` | Poller lockfile. |
|
|
613
611
|
| `peer/peer-session.ts` | One SDK `AgentSession` per peer: workspace, model, history under the state dir, dialogs bound to that peer, this bridge excluded from its resources, and the channel prompt appended. |
|
|
614
612
|
| `peer/peer-worker.ts` | Per-peer queue, two peers' turns at once (`PEER_CONCURRENCY`), the fair slot hand-off, prompt/abort/reset, streamed reply parts, idle-session sweep, and the notice an abandoning close sends. |
|
|
@@ -628,7 +626,8 @@ status, which is what the old start/stop guidance used to do.
|
|
|
628
626
|
| `dialog/prompt.ts` | Pure prompt formatting and reply parsing. |
|
|
629
627
|
| `dialog/remote-ui.ts` | The peer session's UI context: dialogs and notices bound to that peer, the `askHuman` marker that delivers the file/text preface, and the `remotePrompts` switch. |
|
|
630
628
|
| `view/login-screen.ts` | The `/wechat login` screen (`ctx.ui.custom`), drawn from the `lib/tui/` kit (`titleRule`, `clip`, `isCloseKey`). |
|
|
631
|
-
| `
|
|
629
|
+
| `text/status.ts` | The status facts (`statusView`) and the one-line summary (`statusLine`) the text modes print. |
|
|
630
|
+
| `view/status-screen.ts` | The `/wechat` control screen (`ctx.ui.custom`): live facts, a focusable peer list with stop/reset/history keys and the body renderer, loaded on first use, drawn from the `lib/tui/` kit (`titleRule`, `keyValueRow`, `listRow`, `paint`, `IdCursor`). |
|
|
632
631
|
|
|
633
632
|
Modules are grouped by the role they play: `transport/` is the only place that talks to iLink, `state/` owns the three files a session leaves on disk, `peer/` runs one peer's session and its queue, `commands/` holds a slash verb, `text/` words everything a peer reads, `dialog/` asks a question over WeChat, and `view/` draws a full-screen surface. `index.ts`, `bridge.ts`, `tools.ts`, `config.ts`, `types.ts`, `errors.ts`, and `timer.ts` stay at the root — the factory, the composition root, the public surface, and the seams the directories import.
|
|
634
633
|
|