@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
|
@@ -3,9 +3,20 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Two panels and one strip. **Needs a decision** is the queue — one row per card,
|
|
5
5
|
* carrying why it is there and the action that fixes it — and **Teammates** is the
|
|
6
|
-
* people: the sessions this repository's bus knows, plus every owner a card names,
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* people: the sessions this repository's bus knows, plus every owner a card names, so a
|
|
7
|
+
* session holding nothing and a label that stopped answering are both visible. Done is a
|
|
8
|
+
* quiet strip of ids that open their cards.
|
|
9
|
+
*
|
|
10
|
+
* Two of those lists only grow: an owner a card names stays one, and a finished card is
|
|
11
|
+
* finished for good. Both therefore fold — the owners the bus has no record of into one row
|
|
12
|
+
* that states what they finished, and the strip past its sixth id behind its own count — and
|
|
13
|
+
* the three slots are refilled only when what they print has moved, so a peer's write does
|
|
14
|
+
* not take the reader's scroll or their focus with it.
|
|
15
|
+
*
|
|
16
|
+
* Which owners are history, and the sentence that states them, are not decided here: the
|
|
17
|
+
* terminal's round prints the same fold, so both read `lib/board/round-text.ts`'s two bodies
|
|
18
|
+
* through {@link teamJs}. A second copy in this fragment is how the page and `/team` came to
|
|
19
|
+
* describe one board two ways.
|
|
9
20
|
*
|
|
10
21
|
* The roster is optional throughout: `view.teammates` is absent when `session-bus`
|
|
11
22
|
* has no records here, and every state then reads `—` rather than claiming the
|
|
@@ -16,30 +27,90 @@
|
|
|
16
27
|
* Same rule as the rest of the client: every value arrives as `textContent`.
|
|
17
28
|
*/
|
|
18
29
|
|
|
19
|
-
|
|
30
|
+
/**
|
|
31
|
+
* The tab's fragment, with the fold's rule and its sentence spliced in.
|
|
32
|
+
*
|
|
33
|
+
* The source crosses as text, because a page's bundle has no import graph, and the two
|
|
34
|
+
* `__NAME__` placeholders are rules a served script must never carry — `pages.test` fails an
|
|
35
|
+
* unfilled one.
|
|
36
|
+
*/
|
|
37
|
+
export function teamJs(rules: { historyOwner: string; historyLine: string }): string {
|
|
38
|
+
return teamTemplate.replace("__HISTORY_OWNER__", rules.historyOwner).replace("__HISTORY_LINE__", rules.historyLine);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const teamTemplate = String.raw`(function () {
|
|
20
42
|
"use strict";
|
|
21
43
|
var board = window.bfBoard || (window.bfBoard = {});
|
|
22
44
|
var el = board.el;
|
|
23
45
|
var chip = board.chip;
|
|
24
46
|
var ago = board.ago;
|
|
25
47
|
var plural = board.plural;
|
|
48
|
+
var rankOf = board.rankOf;
|
|
49
|
+
var decisionTone = board.decisionTone;
|
|
50
|
+
|
|
51
|
+
__HISTORY_OWNER__
|
|
52
|
+
|
|
53
|
+
__HISTORY_LINE__
|
|
26
54
|
|
|
27
55
|
/** The word a teammate's state is read as; never a colour alone. */
|
|
28
56
|
var STATE_WORD = { live: "live", quiet: "quiet", gone: "gone", absent: "no record", unknown: "\u2014" };
|
|
29
57
|
|
|
30
58
|
/**
|
|
31
|
-
* A
|
|
59
|
+
* A slot's heading row: the panel's name and the count a reader came for.
|
|
32
60
|
*
|
|
33
|
-
* With a filter on, the count reads shown/total the way a lane head does, so a
|
|
34
|
-
*
|
|
61
|
+
* With a filter on, the count reads shown/total the way a lane head does, so a reader can
|
|
62
|
+
* tell "nothing here" from "nothing matches".
|
|
35
63
|
*/
|
|
36
|
-
function
|
|
37
|
-
var
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
64
|
+
function caption(slot, text, count, total) {
|
|
65
|
+
var filtered = total !== undefined && total !== count;
|
|
66
|
+
var head = el("h2", N.teamCaption, text);
|
|
67
|
+
head.appendChild(chip(filtered ? count + "/" + total : String(count)));
|
|
68
|
+
// The count is the reason the panel is read, but glued to its name a heading announces
|
|
69
|
+
// "Needs a decision 4" as one word. The label says the same fact in a sentence and the
|
|
70
|
+
// pill keeps the shape a reader scans.
|
|
71
|
+
head.setAttribute("aria-label", text + ", " + (filtered ? count + " of " + total : String(count)));
|
|
72
|
+
slot.setAttribute("aria-label", text);
|
|
73
|
+
slot.appendChild(head);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The tab's three slots, built once and then refilled in place.
|
|
78
|
+
*
|
|
79
|
+
* A lane has always been rebuilt only when its cards moved; the Team tab used to clear
|
|
80
|
+
* itself and start again on every frame, which threw away the scroll offset of a capped
|
|
81
|
+
* roster and put the reader's focus back on the document. A slot is a fixed element, and
|
|
82
|
+
* what a signature guards is its contents.
|
|
83
|
+
*/
|
|
84
|
+
function slots() {
|
|
85
|
+
if (board.teamPanels !== undefined) return board.teamPanels;
|
|
86
|
+
var root = document.getElementById("board-team");
|
|
87
|
+
if (!root) return undefined;
|
|
88
|
+
root.textContent = "";
|
|
89
|
+
var grid = el("div", N.teamGrid);
|
|
90
|
+
var queue = el("section", N.teamGroup);
|
|
91
|
+
var people = el("section", N.teamGroup);
|
|
92
|
+
grid.appendChild(queue);
|
|
93
|
+
grid.appendChild(people);
|
|
94
|
+
root.appendChild(grid);
|
|
95
|
+
var strip = el("p", N.teamDone);
|
|
96
|
+
root.appendChild(strip);
|
|
97
|
+
board.teamPanels = { queue: queue, people: people, strip: strip };
|
|
98
|
+
return board.teamPanels;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Refill one slot, but only when what it draws has moved.
|
|
103
|
+
*
|
|
104
|
+
* The signature is the facts the slot prints — ids, revisions, states, counts — plus the
|
|
105
|
+
* filter and the slot's own folded state, so a frame that changed nothing here leaves the
|
|
106
|
+
* rows, the scroll offset, and whatever the reader has focused alone. The data-sig hook is
|
|
107
|
+
* the lanes' own, not a second one.
|
|
108
|
+
*/
|
|
109
|
+
function fill(slot, sig, build) {
|
|
110
|
+
if (slot.getAttribute("data-sig") === sig) return;
|
|
111
|
+
slot.setAttribute("data-sig", sig);
|
|
112
|
+
slot.textContent = "";
|
|
113
|
+
build(slot);
|
|
43
114
|
}
|
|
44
115
|
|
|
45
116
|
/**
|
|
@@ -48,13 +119,11 @@ export const BOARD_TEAM_JS = String.raw`(function () {
|
|
|
48
119
|
* The renderer filters this list, so it has to be the data: filtering the built
|
|
49
120
|
* rows would test the element instead of the card.
|
|
50
121
|
*
|
|
51
|
-
* The round's groups overlap on purpose —
|
|
122
|
+
* The round's groups overlap on purpose — an executing card untouched for half an hour
|
|
52
123
|
* is both idle and unplanned — so a card can arrive twice. On the page one card is
|
|
53
124
|
* one row, and the strongest reason is the one it keeps: a blocker outranks silence,
|
|
54
125
|
* which outranks a missing plan, which outranks nobody holding it.
|
|
55
126
|
*/
|
|
56
|
-
var KIND_RANK = { blocked: 0, gone: 1, waiting: 2, idle: 3, unplanned: 4, unassigned: 5 };
|
|
57
|
-
|
|
58
127
|
/** The labels of the sessions whose process is gone, in the roster's own spelling. */
|
|
59
128
|
function goneLabels(view) {
|
|
60
129
|
var roster = view.teammates;
|
|
@@ -78,19 +147,22 @@ export const BOARD_TEAM_JS = String.raw`(function () {
|
|
|
78
147
|
if (seen === undefined) {
|
|
79
148
|
byCard[card.id] = entry;
|
|
80
149
|
entries.push(entry);
|
|
81
|
-
} else if (
|
|
150
|
+
} else if (rankOf(kind) < rankOf(seen.kind)) {
|
|
82
151
|
seen.why = entry.why;
|
|
83
152
|
seen.kind = kind;
|
|
84
153
|
}
|
|
85
154
|
}
|
|
86
155
|
}
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}, "blocked");
|
|
156
|
+
// The card's own sentence, from the one rule: the page's faces and the transcript read
|
|
157
|
+
// the same body, so a block and a contract cannot be described two ways.
|
|
158
|
+
function why(card, fallback) {
|
|
159
|
+
var reason = board.reasonFor(card, ago);
|
|
160
|
+
return reason === undefined ? fallback : reason.text;
|
|
161
|
+
}
|
|
162
|
+
add(round.blocked, function (card) { return why(card, "blocked"); }, "blocked");
|
|
163
|
+
// A complete contract nobody has read is the lead's decision, and the one the flow
|
|
164
|
+
// stops on: the card cannot be executed until it is approved.
|
|
165
|
+
add(round.unapproved, function (card) { return why(card, "contract ready — needs approval"); }, "approval");
|
|
94
166
|
// A dead owner is a fact, not an inference from silence: the card is anyone's now.
|
|
95
167
|
var gone = goneLabels(view);
|
|
96
168
|
add(
|
|
@@ -102,7 +174,7 @@ export const BOARD_TEAM_JS = String.raw`(function () {
|
|
|
102
174
|
);
|
|
103
175
|
add(round.idle, function (card) { return "no touch for " + ago(card.updatedAt); }, "idle");
|
|
104
176
|
// The board card says the same words for the same fact: one voice for one state.
|
|
105
|
-
add(round.unplanned, function () { return "no
|
|
177
|
+
add(round.unplanned, function (card) { return why(card, "no contract yet"); }, "unplanned");
|
|
106
178
|
add(round.unassigned, function () { return "nobody holds it"; }, "unassigned");
|
|
107
179
|
// An order outranks nobody holding it: the claim would be refused, so the queue says
|
|
108
180
|
// what it waits for instead of offering a button the rules would turn down.
|
|
@@ -126,7 +198,11 @@ export const BOARD_TEAM_JS = String.raw`(function () {
|
|
|
126
198
|
function decision(entry, view) {
|
|
127
199
|
var card = entry.card;
|
|
128
200
|
var node = el("article", N.teamDecision);
|
|
201
|
+
// What the row is, and how it wears: data-kind names it for a test and a hook, and
|
|
202
|
+
// data-tone is the colour its edge is drawn in, from the table the page's stylesheet
|
|
203
|
+
// and the terminal share.
|
|
129
204
|
node.setAttribute("data-kind", entry.kind);
|
|
205
|
+
node.setAttribute("data-tone", decisionTone(entry.kind));
|
|
130
206
|
|
|
131
207
|
var open = el("button", N.teamOpen);
|
|
132
208
|
open.type = "button";
|
|
@@ -150,16 +226,20 @@ export const BOARD_TEAM_JS = String.raw`(function () {
|
|
|
150
226
|
// rest, because this sentence is the lead's whole reason to look at the row.
|
|
151
227
|
var why = el("p", N.teamWhy, entry.why);
|
|
152
228
|
why.title = entry.why;
|
|
153
|
-
if (entry.kind === "blocked") why.className = N.teamWhy + " " + N.teamWhyBlocked;
|
|
154
229
|
node.appendChild(why);
|
|
155
230
|
return node;
|
|
156
231
|
}
|
|
157
232
|
|
|
158
233
|
/**
|
|
159
|
-
* Every teammate: the bus's sessions and the owners a card names,
|
|
234
|
+
* Every teammate: the bus's sessions and the owners a card names, split in two.
|
|
160
235
|
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
236
|
+
* The head is who is here to read: the bus's sessions, and any owner a card names that the
|
|
237
|
+
* bus has no record of **and** that holds no card — that second kind is the one the queue
|
|
238
|
+
* reports, and folding it would hide the row the tab exists for.
|
|
239
|
+
*
|
|
240
|
+
* The tail is history: an owner the bus has no record of, holding nothing. The interesting
|
|
241
|
+
* case is why the row stays at all, but the set grows with every label that ever claimed a
|
|
242
|
+
* card, so the page states it once and unfolds it on a click.
|
|
163
243
|
*/
|
|
164
244
|
function teammates(view, roster) {
|
|
165
245
|
var round = view.round;
|
|
@@ -202,7 +282,21 @@ export const BOARD_TEAM_JS = String.raw`(function () {
|
|
|
202
282
|
return 4;
|
|
203
283
|
}
|
|
204
284
|
rows.sort(function (a, b) { return rank(a) - rank(b) || a.label.localeCompare(b.label); });
|
|
205
|
-
|
|
285
|
+
|
|
286
|
+
var head = [];
|
|
287
|
+
var tail = [];
|
|
288
|
+
for (var r = 0; r < rows.length; r++) {
|
|
289
|
+
// The rule is the round text's own (isHistoryOwner), so the tab and /team cannot answer
|
|
290
|
+
// "which owners are only history" two ways. A null roster is a machine with no bus: the
|
|
291
|
+
// rule folds nobody there, because nobody can be called absent.
|
|
292
|
+
if (isHistoryOwner(rows[r].label, rows[r].cards.live.length, roster === null ? undefined : roster)) {
|
|
293
|
+
tail.push(rows[r]);
|
|
294
|
+
} else head.push(rows[r]);
|
|
295
|
+
}
|
|
296
|
+
// Unfolded, history is read by what it finished and then by name, so the list says which
|
|
297
|
+
// labels did the work rather than which sorts first.
|
|
298
|
+
tail.sort(function (a, b) { return b.cards.done.length - a.cards.done.length || a.label.localeCompare(b.label); });
|
|
299
|
+
return { head: head, tail: tail };
|
|
206
300
|
}
|
|
207
301
|
|
|
208
302
|
/** A text node carrying its stamp, so refreshAges rewrites it with every other age. */
|
|
@@ -254,10 +348,106 @@ export const BOARD_TEAM_JS = String.raw`(function () {
|
|
|
254
348
|
return node;
|
|
255
349
|
}
|
|
256
350
|
|
|
351
|
+
/**
|
|
352
|
+
* The history row: how many owners nothing answers to, and what they finished.
|
|
353
|
+
*
|
|
354
|
+
* One row instead of eighteen, because a label that ever claimed a card stays an owner for
|
|
355
|
+
* good: eighteen rows of "no record · no cards · 1 done" are one fact printed eighteen
|
|
356
|
+
* times. The row wears the same absence its rows do, so its dot is the hollow ring.
|
|
357
|
+
*/
|
|
358
|
+
function tailRow(tail, folded) {
|
|
359
|
+
var finished = 0;
|
|
360
|
+
for (var i = 0; i < tail.length; i++) finished += tail[i].cards.done.length;
|
|
361
|
+
var row = el("li", N.teamMate);
|
|
362
|
+
row.setAttribute("data-state", "absent");
|
|
363
|
+
var button = el("button", N.teamTail);
|
|
364
|
+
button.type = "button";
|
|
365
|
+
button.setAttribute("data-fold", "team-roster");
|
|
366
|
+
button.setAttribute("aria-expanded", String(!folded));
|
|
367
|
+
var dot = el("span", N.teamDot);
|
|
368
|
+
dot.setAttribute("aria-hidden", "true");
|
|
369
|
+
button.appendChild(dot);
|
|
370
|
+
// The row counts in the rows' own words — "no record", "11 done" — so the summary is not
|
|
371
|
+
// a second way of saying what a teammate row says.
|
|
372
|
+
button.appendChild(document.createTextNode(historyLine(tail.length, finished)));
|
|
373
|
+
button.addEventListener("click", function () { board.toggleFold("team-roster"); });
|
|
374
|
+
row.appendChild(button);
|
|
375
|
+
return row;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/** Whether the history row starts folded: once, like the Done lane, and the reader decides after. */
|
|
379
|
+
function historyFolded(tail) {
|
|
380
|
+
if (tail.length < board.foldAt.roster) return false;
|
|
381
|
+
board.collapsed = board.collapsed || {};
|
|
382
|
+
if (board.collapsed["team-roster"] === undefined) board.collapsed["team-roster"] = true;
|
|
383
|
+
return board.collapsed["team-roster"] === true;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Whether the strip starts folded, and stays as the reader left it.
|
|
388
|
+
*
|
|
389
|
+
* The lane folds at six because a tray of finished cards is history the moment it reaches
|
|
390
|
+
* one more than a reader checks; the strip shows six ids and folds the seventh on, because
|
|
391
|
+
* it is one line and six is what fits before it becomes a wall of underlined numbers.
|
|
392
|
+
*/
|
|
393
|
+
function doneFolded(count) {
|
|
394
|
+
if (count <= board.foldAt.done) return false;
|
|
395
|
+
board.collapsed = board.collapsed || {};
|
|
396
|
+
if (board.collapsed["team-done"] === undefined) board.collapsed["team-done"] = true;
|
|
397
|
+
return board.collapsed["team-done"] === true;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/** One finished id in the strip: a button, because it opens its card. */
|
|
401
|
+
function doneId(card) {
|
|
402
|
+
var id = el("button", N.teamId, "#" + card.id);
|
|
403
|
+
id.type = "button";
|
|
404
|
+
id.setAttribute("aria-haspopup", "dialog");
|
|
405
|
+
id.addEventListener("click", function () { board.openCard(card.id); });
|
|
406
|
+
return id;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* What a slot prints, in one string — the facts its rows are drawn from, plus the filter and
|
|
411
|
+
* the slot's own fold. A signature that leaves a fact out is a row that goes stale, so each
|
|
412
|
+
* one is built from the same expressions the rows read.
|
|
413
|
+
*/
|
|
414
|
+
function queueSig(decisions, total, query) {
|
|
415
|
+
var parts = [query, String(total)];
|
|
416
|
+
for (var i = 0; i < decisions.length; i++) {
|
|
417
|
+
var card = decisions[i].card;
|
|
418
|
+
parts.push(card.id + ":" + card.rev + ":" + decisions[i].kind + ":" + decisions[i].why);
|
|
419
|
+
}
|
|
420
|
+
return parts.join("|");
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function peopleSig(head, tail, query, folded, noBus) {
|
|
424
|
+
function row(mate) {
|
|
425
|
+
var ids = [];
|
|
426
|
+
for (var i = 0; i < mate.cards.live.length; i++) ids.push(mate.cards.live[i].id + ":" + mate.cards.live[i].rev);
|
|
427
|
+
// No seen: the bus moves it every few seconds, and it is churn without a fact. A live row
|
|
428
|
+
// prints no age — only a quiet one does, and a quiet session that pings is live again on the
|
|
429
|
+
// next snapshot, which moves its state and refills the row anyway. Putting the stamp here
|
|
430
|
+
// rebuilt the roster on the first frame after every heartbeat, taking the reader's scroll
|
|
431
|
+
// offset and focus with it, which is the one thing this signature exists to prevent.
|
|
432
|
+
return mate.label + "," + mate.state + "," + mate.mail + "," + mate.listening +
|
|
433
|
+
"," + (mate.you ? 1 : 0) + "," + ids.join("+") + "," + mate.cards.done.length;
|
|
434
|
+
}
|
|
435
|
+
var parts = [query, folded ? "folded" : "open", noBus ? "no bus" : "bus"];
|
|
436
|
+
for (var i = 0; i < head.length; i++) parts.push(row(head[i]));
|
|
437
|
+
parts.push("--");
|
|
438
|
+
for (var j = 0; j < tail.length; j++) parts.push(row(tail[j]));
|
|
439
|
+
return parts.join("|");
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function stripSig(done, filtering, folded, total) {
|
|
443
|
+
var ids = [];
|
|
444
|
+
for (var i = 0; i < done.length; i++) ids.push(done[i].id);
|
|
445
|
+
return ids.join(",") + "|" + (filtering ? "filtered" : "all") + "|" + (folded ? "folded" : "open") + "|" + total;
|
|
446
|
+
}
|
|
447
|
+
|
|
257
448
|
board.renderTeam = function (view) {
|
|
258
|
-
var
|
|
259
|
-
if (
|
|
260
|
-
root.textContent = "";
|
|
449
|
+
var shell = slots();
|
|
450
|
+
if (shell === undefined) return;
|
|
261
451
|
var round = view.round;
|
|
262
452
|
// Absent, not empty: null means no bus here, so every state reads unknown.
|
|
263
453
|
var roster = view.teammates === undefined ? null : view.teammates;
|
|
@@ -268,60 +458,91 @@ export const BOARD_TEAM_JS = String.raw`(function () {
|
|
|
268
458
|
var filtering = query !== "";
|
|
269
459
|
var noMatch = "no card here matches \"" + query + "\"";
|
|
270
460
|
|
|
271
|
-
var grid = el("div", N.teamGrid);
|
|
272
|
-
|
|
273
461
|
var all = needs(view);
|
|
274
462
|
var decisions = all.filter(function (entry) { return keep(entry.card); });
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
463
|
+
fill(shell.queue, queueSig(decisions, all.length, query), function (slot) {
|
|
464
|
+
caption(slot, "Needs a decision", decisions.length, all.length);
|
|
465
|
+
if (decisions.length === 0) {
|
|
466
|
+
slot.appendChild(el("p", N.teamEmpty, filtering ? noMatch : "nothing needs you: no blockers, no contract waiting to be read, nothing idle"));
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
279
469
|
var list = el("div", N.teamQueue);
|
|
280
470
|
for (var i = 0; i < decisions.length; i++) list.appendChild(decision(decisions[i], view));
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
grid.appendChild(queue);
|
|
471
|
+
slot.appendChild(list);
|
|
472
|
+
});
|
|
284
473
|
|
|
285
474
|
var everyone = teammates(view, roster);
|
|
286
|
-
var
|
|
475
|
+
var keepRow = function (mate) {
|
|
287
476
|
if (!filtering) return true;
|
|
288
|
-
if (matchesLabel(
|
|
289
|
-
var held =
|
|
477
|
+
if (matchesLabel(mate.label)) return true;
|
|
478
|
+
var held = mate.cards.live.concat(mate.cards.done);
|
|
290
479
|
for (var k = 0; k < held.length; k++) if (keep(held[k])) return true;
|
|
291
480
|
return false;
|
|
481
|
+
};
|
|
482
|
+
var head = everyone.head.filter(keepRow);
|
|
483
|
+
var history = everyone.tail.filter(keepRow);
|
|
484
|
+
var people = everyone.head.length + everyone.tail.length;
|
|
485
|
+
// A filter is a view of the rows, so it unfolds the history: a matching owner hidden
|
|
486
|
+
// inside a fold would make the panel say "nothing matches" about somebody who is here.
|
|
487
|
+
var folded = filtering ? false : historyFolded(everyone.tail);
|
|
488
|
+
fill(shell.people, peopleSig(head, history, query, folded, roster === null), function (slot) {
|
|
489
|
+
caption(slot, "Teammates", head.length + history.length, people);
|
|
490
|
+
if (head.length + history.length === 0) {
|
|
491
|
+
slot.appendChild(el("p", N.teamEmpty, filtering ? noMatch : roster === null ? "session-bus is not loaded here" : "nobody is here yet"));
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
var list = el("ul", N.teamRoster);
|
|
495
|
+
for (var i = 0; i < head.length; i++) list.appendChild(mate(head[i]));
|
|
496
|
+
if (history.length > 0) {
|
|
497
|
+
// While a filter asks for people by name, the matches are rows like any other; unfolded,
|
|
498
|
+
// they follow the one row that states them.
|
|
499
|
+
if (!filtering) {
|
|
500
|
+
list.appendChild(tailRow(history, folded));
|
|
501
|
+
if (folded) {
|
|
502
|
+
slot.appendChild(list);
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
for (var j = 0; j < history.length; j++) list.appendChild(mate(history[j]));
|
|
507
|
+
}
|
|
508
|
+
slot.appendChild(list);
|
|
292
509
|
});
|
|
293
|
-
var people = panel("Teammates", shown.length, everyone.length);
|
|
294
|
-
if (shown.length === 0) {
|
|
295
|
-
people.appendChild(el("p", N.teamEmpty, filtering ? noMatch : roster === null ? "session-bus is not loaded here" : "nobody is here yet"));
|
|
296
|
-
} else {
|
|
297
|
-
var rosterList = el("ul", N.teamRoster);
|
|
298
|
-
for (var j = 0; j < shown.length; j++) rosterList.appendChild(mate(shown[j]));
|
|
299
|
-
people.appendChild(rosterList);
|
|
300
|
-
}
|
|
301
|
-
grid.appendChild(people);
|
|
302
|
-
root.appendChild(grid);
|
|
303
510
|
|
|
304
511
|
var done = round.done.filter(keep);
|
|
305
|
-
var
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
strip.appendChild(document.createTextNode("Done \u00B7 "));
|
|
313
|
-
for (var m = 0; m < done.length; m++) {
|
|
314
|
-
(function (card) {
|
|
315
|
-
var id = el("button", N.teamId, "#" + card.id);
|
|
316
|
-
id.type = "button";
|
|
317
|
-
id.setAttribute("aria-haspopup", "dialog");
|
|
318
|
-
id.addEventListener("click", function () { board.openCard(card.id); });
|
|
319
|
-
strip.appendChild(id);
|
|
320
|
-
})(done[m]);
|
|
512
|
+
var stripFolded = doneFolded(done.length);
|
|
513
|
+
fill(shell.strip, stripSig(done, filtering, stripFolded, round.done.length), function (slot) {
|
|
514
|
+
if (done.length === 0) {
|
|
515
|
+
// While filtering, a third "no card here matches" line adds nothing; the two panels
|
|
516
|
+
// above already said it.
|
|
517
|
+
if (!filtering) slot.textContent = "nothing finished yet";
|
|
518
|
+
return;
|
|
321
519
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
520
|
+
slot.appendChild(document.createTextNode("Done \u00B7 "));
|
|
521
|
+
var shown = Math.min(done.length, board.foldAt.done);
|
|
522
|
+
for (var i = 0; i < shown; i++) slot.appendChild(doneId(done[i]));
|
|
523
|
+
if (done.length > board.foldAt.done) {
|
|
524
|
+
var rest = el("span", N.teamDoneIds);
|
|
525
|
+
rest.id = "team-done-ids";
|
|
526
|
+
rest.hidden = stripFolded;
|
|
527
|
+
for (var j = board.foldAt.done; j < done.length; j++) rest.appendChild(doneId(done[j]));
|
|
528
|
+
slot.appendChild(rest);
|
|
529
|
+
}
|
|
530
|
+
slot.appendChild(document.createTextNode(" \u00B7 "));
|
|
531
|
+
var count = plural(done.length, "card") +
|
|
532
|
+
(filtering && done.length !== round.done.length ? " of " + round.done.length : "");
|
|
533
|
+
if (done.length > board.foldAt.done) {
|
|
534
|
+
// The count is the fold's control here, the way a lane's head is its own: the strip has
|
|
535
|
+
// no room for a second button, and the number is what says how much is out of sight.
|
|
536
|
+
var toggle = el("button", N.teamFold, count);
|
|
537
|
+
toggle.type = "button";
|
|
538
|
+
toggle.setAttribute("data-fold", "team-done");
|
|
539
|
+
toggle.setAttribute("aria-expanded", String(!stripFolded));
|
|
540
|
+
toggle.setAttribute("aria-controls", "team-done-ids");
|
|
541
|
+
toggle.addEventListener("click", function () { board.toggleFold("team-done"); });
|
|
542
|
+
slot.appendChild(toggle);
|
|
543
|
+
} else {
|
|
544
|
+
slot.appendChild(document.createTextNode(count));
|
|
545
|
+
}
|
|
546
|
+
});
|
|
326
547
|
};
|
|
327
548
|
})();`;
|
|
@@ -114,7 +114,7 @@ export function resultFallback(
|
|
|
114
114
|
if (!text) return "";
|
|
115
115
|
const lines = text.split("\n");
|
|
116
116
|
const shown = expanded ? lines : lines.slice(0, FALLBACK_PREVIEW_LINES);
|
|
117
|
-
let output = theme
|
|
117
|
+
let output = paint(theme, "toolOutput", shown.join("\n"));
|
|
118
118
|
const remaining = lines.length - shown.length;
|
|
119
119
|
if (remaining > 0) output += `\n${paint(theme, "meta", `... (${remaining} more lines)`)}`;
|
|
120
120
|
return output;
|
|
@@ -12,7 +12,7 @@ import { CONFIG_DIR_NAME, getAgentDir, type ExtensionAPI } from "@earendil-works
|
|
|
12
12
|
import type { WebSearchProviderEvent } from "../../../lib/events.ts";
|
|
13
13
|
import { PRICE_VERIFIED_AT } from "../footer/price/config.ts";
|
|
14
14
|
import { readJsonResult } from "../../../lib/config.ts";
|
|
15
|
-
import {
|
|
15
|
+
import { extensionDisabledReason } from "../../../lib/env.ts";
|
|
16
16
|
import {
|
|
17
17
|
hardRequirementExtensions,
|
|
18
18
|
probeAll as probeDeclared,
|
|
@@ -91,7 +91,10 @@ export function createDoctorRuntime(
|
|
|
91
91
|
readJson: readJsonResult,
|
|
92
92
|
dirs: { agentDir: getAgentDir(), cwd, configDirName: CONFIG_DIR_NAME },
|
|
93
93
|
platform: process.platform,
|
|
94
|
-
|
|
94
|
+
// The doctor's own skip list is this report's decision, not the environment's,
|
|
95
|
+
// so it answers for the extension it holds back.
|
|
96
|
+
disabledReason: (extension) =>
|
|
97
|
+
config.skip.includes(extension) ? "skipped by `doctor.skip`" : extensionDisabledReason(extension),
|
|
95
98
|
notify: notifySeam(cwd),
|
|
96
99
|
isTTY: process.stdout.isTTY === true,
|
|
97
100
|
searchProvider: () => searchProvider,
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Following this session's roster label, for the bar.
|
|
3
|
+
*
|
|
4
|
+
* The footer renders on a frame timer, so the label cannot be a bus read per frame. It is
|
|
5
|
+
* one read of the record the bus wrote — a resumed pane can already have one — and a
|
|
6
|
+
* subscription to `SESSION_LABEL_CHANNEL` for every label the bus settles afterwards. Both
|
|
7
|
+
* report through the sink the footer handed in, so the renderer reads one field and the I/O
|
|
8
|
+
* happens once.
|
|
9
|
+
*
|
|
10
|
+
* No record is the ordinary answer, not a failure: bytefly is on by default and the bus is
|
|
11
|
+
* opt-in, so a session that never joined keeps Pi's own name.
|
|
12
|
+
*
|
|
13
|
+
* Its caller loads this module on the first footer install rather than at registration,
|
|
14
|
+
* because the read needs the bus's layout on disk and the registration graph is a budget in
|
|
15
|
+
* `test/dependencies.test.ts` that may only fall.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
19
|
+
import { SESSION_LABEL_CHANNEL, type SessionLabelEvent, sessionIdOf } from "../../../../lib/session-label.ts";
|
|
20
|
+
import { readSessionLabel } from "../../../../lib/session-roster.ts";
|
|
21
|
+
|
|
22
|
+
/** What to draw: the label, or `null` for "no record, keep Pi's session name". */
|
|
23
|
+
export type RosterLabelSink = (label: string | null) => void;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Read this session's label once, then report every label the bus publishes for it.
|
|
27
|
+
*
|
|
28
|
+
* The subscription is here rather than beside the footer's other listeners because the
|
|
29
|
+
* channel and the record walk are one contract, and only this module is deferred. It is
|
|
30
|
+
* never unsubscribed: Pi drops an extension's listeners when it unloads, and a footer that
|
|
31
|
+
* re-installs (a session tree change) is the same session under the same label.
|
|
32
|
+
*
|
|
33
|
+
* Answers when the record has been read, so a caller can wait for the first frame that
|
|
34
|
+
* draws a label rather than guess at a timer.
|
|
35
|
+
*/
|
|
36
|
+
export function followRosterLabel(input: {
|
|
37
|
+
pi: Pick<ExtensionAPI, "events">;
|
|
38
|
+
ctx: Pick<ExtensionContext, "sessionManager">;
|
|
39
|
+
agentDir: string;
|
|
40
|
+
sink: RosterLabelSink;
|
|
41
|
+
}): Promise<void> {
|
|
42
|
+
const { pi, ctx, agentDir, sink } = input;
|
|
43
|
+
const own = sessionIdOf(ctx);
|
|
44
|
+
if (own === undefined) return Promise.resolve();
|
|
45
|
+
pi.events.on(SESSION_LABEL_CHANNEL, (event) => {
|
|
46
|
+
// The channel reaches every session in this process, and only one of them is this one:
|
|
47
|
+
// a peer's label would put its name on this session's bar.
|
|
48
|
+
const published = event as SessionLabelEvent | undefined;
|
|
49
|
+
if (published === undefined || published.id !== own) return;
|
|
50
|
+
sink(published.label);
|
|
51
|
+
});
|
|
52
|
+
// The record the bus wrote before this process started, if any. A failure is the same
|
|
53
|
+
// answer as no record: the bar keeps Pi's own name.
|
|
54
|
+
return readSessionLabel(agentDir, own).then(
|
|
55
|
+
(label) => {
|
|
56
|
+
if (label !== undefined) sink(label);
|
|
57
|
+
},
|
|
58
|
+
() => {},
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -42,7 +42,11 @@ export interface SnapshotReader {
|
|
|
42
42
|
peek(): PricedUsage | undefined;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
export function createSnapshotReader(
|
|
45
|
+
export function createSnapshotReader(
|
|
46
|
+
config: ByteflyConfig,
|
|
47
|
+
delegation: DelegationLedger,
|
|
48
|
+
rosterLabel: () => string | null = () => null,
|
|
49
|
+
): SnapshotReader {
|
|
46
50
|
const price = createPricer(config);
|
|
47
51
|
let key: string | null = null;
|
|
48
52
|
let usage: PricedUsage = { ...zeroUsage(), unpriced: 0, unpricedModels: [] };
|
|
@@ -90,6 +94,9 @@ export function createSnapshotReader(config: ByteflyConfig, delegation: Delegati
|
|
|
90
94
|
return {
|
|
91
95
|
cwd: ctx.cwd,
|
|
92
96
|
sessionName,
|
|
97
|
+
// Read every frame, because the bus settles a label without the session moving: a
|
|
98
|
+
// cached field, not a read — nothing here touches the bus.
|
|
99
|
+
rosterLabel: rosterLabel(),
|
|
93
100
|
branch: footerData.getGitBranch(),
|
|
94
101
|
statuses: footerData.getExtensionStatuses(),
|
|
95
102
|
model,
|
|
@@ -30,12 +30,14 @@ export const CONFIG = {
|
|
|
30
30
|
worktree: GLYPHS.worktree,
|
|
31
31
|
http: GLYPHS.http,
|
|
32
32
|
delegation: GLYPHS.delegation,
|
|
33
|
+
autoMode: GLYPHS.autoMode,
|
|
34
|
+
planMode: GLYPHS.plan,
|
|
33
35
|
},
|
|
34
36
|
/** Status key the worktree extension uses; routed to line 1's right zone. */
|
|
35
37
|
worktreeStatusKey: STATUS_KEYS.worktree,
|
|
36
38
|
/** Status key `serve` publishes the session's one page port under. */
|
|
37
39
|
httpStatusKey: STATUS_KEYS.http,
|
|
38
|
-
/** Status key whose
|
|
40
|
+
/** Status key whose chip is the plan state; the mode segment renders it. */
|
|
39
41
|
planStatusKey: STATUS_KEYS.planMode,
|
|
40
42
|
/**
|
|
41
43
|
* Status keys that are alerts rather than modes, and so are never dropped.
|
|
@@ -43,7 +45,7 @@ export const CONFIG = {
|
|
|
43
45
|
* once its chip is off the bar.
|
|
44
46
|
*/
|
|
45
47
|
alertStatusKeys: ALERT_STATUS_KEYS,
|
|
46
|
-
labels: { noModel: "no model", detached: "detached" },
|
|
48
|
+
labels: { noModel: "no model", detached: "detached", autoMode: "auto", planMode: "plan" },
|
|
47
49
|
/** Minimum gap kept between the left and right zones. */
|
|
48
50
|
minGap: 2,
|
|
49
51
|
/** Worktree label length before the compact form truncates it. */
|