@gavin-hu/my-pi-agent 0.9.0 → 0.11.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 +28 -2
- package/README.zh-CN.md +27 -2
- package/assets/bytefly.schema.json +17 -1
- package/assets/chrome.png +0 -0
- package/assets/job.png +0 -0
- package/assets/plan-review.png +0 -0
- package/assets/question.png +0 -0
- package/assets/rewind.png +0 -0
- package/assets/todo.png +0 -0
- package/assets/tool-renderings.png +0 -0
- package/assets/tools/ask_user_question.png +0 -0
- package/assets/tools/bash.png +0 -0
- package/assets/tools/edit.png +0 -0
- package/assets/tools/enter_plan_mode.png +0 -0
- package/assets/tools/enter_worktree.png +0 -0
- package/assets/tools/exit_plan_mode.png +0 -0
- package/assets/tools/exit_worktree.png +0 -0
- package/assets/tools/find.png +0 -0
- package/assets/tools/goal.png +0 -0
- package/assets/tools/grep.png +0 -0
- package/assets/tools/job.png +0 -0
- package/assets/tools/list_worktrees.png +0 -0
- package/assets/tools/ls.png +0 -0
- package/assets/tools/memory.png +0 -0
- package/assets/tools/notice.png +0 -0
- package/assets/tools/open_path.png +0 -0
- package/assets/tools/prune_worktrees.png +0 -0
- package/assets/tools/read.png +0 -0
- package/assets/tools/read_doc.png +0 -0
- package/assets/tools/run_doctor.png +0 -0
- package/assets/tools/send_wechat.png +0 -0
- package/assets/tools/set_plan_steps.png +0 -0
- package/assets/tools/subagent.png +0 -0
- package/assets/tools/todo.png +0 -0
- package/assets/tools/turn-rule.png +0 -0
- package/assets/tools/user-message.png +0 -0
- package/assets/tools/watch.png +0 -0
- package/assets/tools/web_fetch.png +0 -0
- package/assets/tools/web_search.png +0 -0
- package/assets/tools/write.png +0 -0
- package/assets/tools/write_plan.png +0 -0
- package/extensions/bytefly/README.md +261 -36
- package/extensions/bytefly/block/component.ts +282 -0
- package/extensions/bytefly/block/config.ts +24 -0
- package/extensions/bytefly/block/fallback.ts +120 -0
- package/extensions/bytefly/block/register.ts +164 -0
- package/extensions/bytefly/chrome.ts +97 -0
- package/extensions/bytefly/doctor/tools.ts +5 -2
- package/extensions/bytefly/header/component.ts +21 -10
- package/extensions/bytefly/header/config.ts +0 -3
- package/extensions/bytefly/header/register.ts +6 -1
- package/extensions/bytefly/index.ts +32 -12
- package/extensions/bytefly/message/config.ts +15 -0
- package/extensions/bytefly/message/format.ts +42 -0
- package/extensions/bytefly/message/register.ts +15 -0
- package/extensions/bytefly/notice/component.ts +107 -0
- package/extensions/bytefly/notice/config.ts +41 -0
- package/extensions/bytefly/notice/register.ts +27 -0
- package/extensions/bytefly/pad.ts +42 -0
- package/extensions/bytefly/separator/component.ts +2 -1
- package/extensions/bytefly/separator/format.ts +17 -10
- package/extensions/bytefly/separator/register.ts +3 -2
- package/extensions/bytefly/working/config.ts +20 -0
- package/extensions/bytefly/working/format.ts +25 -0
- package/extensions/bytefly/working/register.ts +88 -0
- package/extensions/document/README.md +11 -4
- package/extensions/document/paging.ts +10 -10
- package/extensions/document/render.ts +7 -7
- package/extensions/file-browse/README.md +3 -1
- package/extensions/file-browse/client.ts +9 -2
- package/extensions/file-browse/git.ts +4 -7
- package/extensions/file-browse/html.ts +27 -17
- package/extensions/file-open/README.md +6 -0
- package/extensions/goal/README.md +16 -10
- package/extensions/goal/format.ts +7 -20
- package/extensions/goal/tools.ts +5 -17
- package/extensions/goal/tui.ts +12 -40
- package/extensions/job/README.md +113 -35
- package/extensions/job/commands.ts +1 -0
- package/extensions/job/config.ts +4 -0
- package/extensions/job/format.ts +19 -7
- package/extensions/job/index.ts +25 -20
- package/extensions/job/process.ts +24 -2
- package/extensions/job/registry.ts +3 -0
- package/extensions/job/runtime.ts +253 -18
- package/extensions/job/schema.ts +34 -0
- package/extensions/job/store.ts +13 -1
- package/extensions/job/tools.ts +55 -15
- package/extensions/job/tui.ts +41 -15
- package/extensions/job/types.ts +9 -1
- package/extensions/memory/README.md +6 -0
- package/extensions/memory/format.ts +5 -3
- package/extensions/plan/README.md +32 -12
- package/extensions/plan/tools.ts +20 -5
- package/extensions/plan/web-client.ts +58 -17
- package/extensions/plan/web-page.ts +34 -9
- package/extensions/plan/web-router.ts +40 -9
- package/extensions/plan/web.ts +22 -2
- package/extensions/question/README.md +13 -1
- package/extensions/question/answers.ts +25 -18
- package/extensions/question/tools.ts +11 -6
- package/extensions/rewind/README.md +2 -0
- package/extensions/rewind/format.ts +6 -1
- package/extensions/rewind/index.ts +3 -1
- package/extensions/rewind/snapshot-run.ts +1 -1
- package/extensions/subagent/README.md +6 -0
- package/extensions/subagent/index.ts +1 -1
- package/extensions/subagent/render.ts +56 -29
- package/extensions/todo/README.md +19 -12
- package/extensions/todo/format.ts +17 -25
- package/extensions/todo/tools.ts +2 -1
- package/extensions/todo/tui.ts +3 -2
- package/extensions/watch/README.md +14 -6
- package/extensions/watch/format.ts +2 -2
- package/extensions/watch/index.ts +28 -23
- package/extensions/watch/tools.ts +5 -3
- package/extensions/watch/tui.ts +12 -7
- package/extensions/web-access/README.md +16 -7
- package/extensions/web-access/fetch/tool.ts +1 -1
- package/extensions/web-access/search/tool.ts +3 -3
- package/extensions/wechat/README.md +6 -0
- package/extensions/worktree/README.md +6 -0
- package/lib/README.md +2 -1
- package/lib/format.ts +17 -0
- package/lib/notice.ts +87 -0
- package/lib/tui/chrome.ts +8 -4
- package/lib/tui/cursor.ts +2 -1
- package/lib/tui/glyphs.ts +10 -11
- package/lib/tui/indent.ts +166 -0
- package/lib/tui/index.ts +3 -1
- package/lib/tui/rows.ts +39 -5
- package/lib/tui/rules.ts +9 -3
- package/lib/tui/tokens.ts +14 -0
- package/lib/web/base.ts +2 -1
- package/lib/web/components.ts +3 -1
- package/lib/web/layout.ts +5 -1
- package/package.json +7 -3
- package/themes/bytefly-dark.json +7 -8
- package/themes/bytefly-light.json +10 -11
- package/assets/bytefly-chrome.png +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
# bytefly — the TUI header, footer, turn rule, extension screen, and notifications
|
|
1
|
+
# bytefly — the TUI header, footer, user-message marker, turn rule, extension screen, and notifications
|
|
2
2
|
|
|
3
3
|
`bytefly` owns the terminal chrome, configuration, and diagnostics surfaces
|
|
4
4
|
this package adds: one themed startup header whose two-tone nameplate carries
|
|
5
5
|
the agent and chrome identities, one compact, colorful, width-adaptive two-line
|
|
6
|
-
footer, a
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
footer, a two-row `You` nameplate that opens each user message, a labeled rule that
|
|
7
|
+
marks each completed turn in the transcript, one keyboard-driven screen that
|
|
8
|
+
lists every resolved Pi extension with its enable checkbox and its health, and
|
|
9
|
+
an opt-in notify capability that raises an OS notification when a run finishes,
|
|
10
|
+
a job finishes, or the agent asks for input.
|
|
10
11
|
DeepSeek is priced from the provider's published yuan rates and peak tariff by
|
|
11
12
|
default, and any other model can be priced from your own rates when Pi's catalog
|
|
12
13
|
does not match what a provider actually charges.
|
|
@@ -17,10 +18,18 @@ pi -e . # load the whole @gavin-hu/my-pi-agent pac
|
|
|
17
18
|
pi install ./ # install the package
|
|
18
19
|
```
|
|
19
20
|
|
|
21
|
+
## Tool rendering
|
|
22
|
+
|
|
23
|
+
How the `run_doctor` call and its result look in the transcript — the call line as a headline and the body indented under it:
|
|
24
|
+
|
|
25
|
+
<img src="../../assets/tools/run_doctor.png" alt="The run_doctor tool's result: the call line and an indented body with the doctor headline and the missing and degraded findings" width="100%">
|
|
26
|
+
|
|
20
27
|
## What it does
|
|
21
28
|
|
|
22
|
-
The extension owns
|
|
23
|
-
between completed turns, the
|
|
29
|
+
The extension owns nine surfaces: the startup header, the footer, the
|
|
30
|
+
user-message marker, the rule between completed turns, the notice block for
|
|
31
|
+
shared transcript notices, the tool-block frame, the live working counter, the
|
|
32
|
+
extension screen, and notifications.
|
|
24
33
|
|
|
25
34
|
**Header.** A nameplate: a two-tone half-block mark from the active theme's
|
|
26
35
|
`accent` / `warn` roles, the agent identity (`Pi`, bold, and its version), the
|
|
@@ -35,35 +44,37 @@ cannot render half blocks the mark falls back to a two-tone `Pi` wordmark.
|
|
|
35
44
|
█▀█ Pi v1.2.0
|
|
36
45
|
█▀ █ enhanced by bytefly v0.8.1
|
|
37
46
|
─────────────────────────────────────────────────
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
47
|
+
escape to interrupt · ctrl+c to clear · ctrl+d to exit (empty)
|
|
48
|
+
ctrl+o to expand tools · ctrl+t to expand thinking · ctrl+l to select model
|
|
49
|
+
/ commands · ! bash · !! bash (no context) · /bytefly extensions
|
|
41
50
|
```
|
|
42
51
|
|
|
43
52
|
As the terminal narrows, the identity sheds the chrome version, then the chrome
|
|
44
53
|
line, then the agent name, and the help rows drop whole keys, so nothing wraps or
|
|
45
|
-
clips mid-word. The
|
|
46
|
-
|
|
54
|
+
clips mid-word. The nameplate sits on the transcript margin, which follows Pi's
|
|
55
|
+
`outputPad` (the value the tool frame and the notice use too), and the help hangs
|
|
56
|
+
one body indent inside it. The mark, tokens, identity, and grouped help are tuned
|
|
57
|
+
in [`header/config.ts`](./header/config.ts).
|
|
47
58
|
|
|
48
59
|
**Footer.** Two lines, each with a left and right zone:
|
|
49
60
|
|
|
50
61
|
- **Line 1 — identity:** `pwd` and the session name on the left; git state
|
|
51
62
|
(`⎇ branch · ⑂ worktree`) and the `◉` browse chip on the right.
|
|
52
63
|
- **Line 2 — resources:** the context gauge and usage meters on the left, the
|
|
53
|
-
mode/alert slot (`⋮ plan`) trailing after a
|
|
64
|
+
mode/alert slot (`⋮ plan`) trailing after a `╎` when present; the model and
|
|
54
65
|
thinking level on the right. The gauge is anchored at column 0, so `%` is
|
|
55
66
|
always in the same place.
|
|
56
67
|
|
|
57
68
|
```
|
|
58
69
|
~/repo/my-pi-agent ⎇ main · ⑂ smoke
|
|
59
|
-
▰▰▰▰▰▰▱▱▱▱ 62%/200k · ¥0.31 · ↑42k ↓8.0k · R96k CH 87%
|
|
70
|
+
▰▰▰▰▰▰▱▱▱▱ 62%/200k · ¥0.31 · ↑42k ↓8.0k · R96k CH 87% ╎ ⋮ plan opus-4.5 · high
|
|
60
71
|
```
|
|
61
72
|
|
|
62
73
|
(The examples show the default `¥` symbol. For a model the built-in table does
|
|
63
74
|
not cover, the number stays Pi's recorded USD amount — see the currency note
|
|
64
75
|
under [Configuration](#configuration).)
|
|
65
76
|
|
|
66
|
-
With no plan mode there is no mode slot, so the
|
|
77
|
+
With no plan mode there is no mode slot, so the `╎` is omitted:
|
|
67
78
|
|
|
68
79
|
```
|
|
69
80
|
~/repo/my-pi-agent ⎇ main · ⑂ smoke
|
|
@@ -76,7 +87,7 @@ fits, rather than collapsing to a bare glyph:
|
|
|
76
87
|
|
|
77
88
|
```
|
|
78
89
|
~/repo/project ⎇ main · ⑂ smoke
|
|
79
|
-
▰▰▰▰▰▰▱▱▱▱ 62% · ¥0.31
|
|
90
|
+
▰▰▰▰▰▰▱▱▱▱ 62% · ¥0.31 ╎ ⋮ plan opus-4.5 · high
|
|
80
91
|
```
|
|
81
92
|
|
|
82
93
|
```
|
|
@@ -88,7 +99,7 @@ With a named session, the name follows the path and truncates before it drops:
|
|
|
88
99
|
|
|
89
100
|
```
|
|
90
101
|
~/repo/project · feature-refac… ⎇ main
|
|
91
|
-
▰▰▰▰▰▰▱▱▱▱ 62%
|
|
102
|
+
▰▰▰▰▰▰▱▱▱▱ 62% ╎ ⋮ plan opus-4.5
|
|
92
103
|
```
|
|
93
104
|
|
|
94
105
|
Zero-value meters are omitted, so a fresh session shows only `?/window` instead
|
|
@@ -107,11 +118,122 @@ rates and peak tariff built into [`footer/price/config.ts`](./footer/price/confi
|
|
|
107
118
|
▰▰▰▰▰▰▱▱▱▱ 62%/200k · ¥2.50 · ↑42k ↓8.0k · R96k CH 87% opus-4.5 · high
|
|
108
119
|
```
|
|
109
120
|
|
|
121
|
+
**User message.** Every user message opens with a two-row `You` nameplate —
|
|
122
|
+
the label over a rule that spans the width — above the message:
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
You
|
|
126
|
+
────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
the message the user typed
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
It is always on in interactive sessions and has no config file. Pi renders a
|
|
132
|
+
user message as Markdown and owns its colors, so bytefly's only lever is a
|
|
133
|
+
`registerMarkdownTransformer`: the nameplate is a single level-2 heading whose
|
|
134
|
+
text is padded so the rule wraps onto the second row, and both rows carry the
|
|
135
|
+
accent because the themes point `mdHeading` at it. The block carries no fill —
|
|
136
|
+
the shipped themes set `userMessageBg` to the terminal default, so it reads like
|
|
137
|
+
the tool blocks. The nameplate is separated from the message by a blank line, so
|
|
138
|
+
the message's own Markdown (a fence, a list, a second paragraph) parses exactly
|
|
139
|
+
as it would without it, and a soft wrap of one long line stays at the margin
|
|
140
|
+
because the renderer wraps it, not bytefly. The label and the rule are tuned
|
|
141
|
+
in [`message/config.ts`](./message/config.ts).
|
|
142
|
+
|
|
143
|
+
**Working timer.** Pi's working row shows a static `Working`, so a long run
|
|
144
|
+
reads the same as a short one. While the row is up, bytefly advances its
|
|
145
|
+
message once a second and hands it back to Pi when the row comes down:
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
Working… 12s
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The count is tied to the row's visibility: it starts on `turn_start`, the event
|
|
152
|
+
the host shows the working indicator on, and stops on `agent_end`, where the
|
|
153
|
+
host clears it, then again on `session_shutdown`. Turns inside one run do not
|
|
154
|
+
restart it — the host keeps the same row up across turns — so the count covers
|
|
155
|
+
the whole run, tool calls included. A retry is a new run, so the count restarts
|
|
156
|
+
when its row reappears. The word and the interval are tuned in
|
|
157
|
+
[`working/config.ts`](./working/config.ts).
|
|
158
|
+
|
|
110
159
|
**Turn separator.** After each completed turn the extension appends an inert
|
|
111
160
|
entry and draws a centered `╌╌╌ turn N ╌╌╌` rule across the transcript; see
|
|
112
161
|
[Turn separator](#turn-separator). No config file is needed and there is no
|
|
113
162
|
command.
|
|
114
163
|
|
|
164
|
+
**Notices.** Any extension in the package can post a transcript notice under the
|
|
165
|
+
shared `my-pi-agent/notice` type; bytefly draws it at the transcript's margin —
|
|
166
|
+
a `source · title` headline at the margin, then rail-led rows, each with its own
|
|
167
|
+
severity glyph:
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
job · 2 background jobs finished
|
|
171
|
+
╎ ✓ j1 build · exit 0
|
|
172
|
+
╎ ✕ j2 test · exit 1
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The rail is painted by the notice's worst severity, so a failure reads red at the
|
|
176
|
+
edge like an errored tool result.
|
|
177
|
+
|
|
178
|
+
`job` and `watch` post one when a background job finishes or a watched line
|
|
179
|
+
matches, so the report the model reads is visible too. The type and the
|
|
180
|
+
`NoticeDetails` shape are the shared contract in
|
|
181
|
+
[`lib/notice.ts`](../../lib/notice.ts), and the severity glyphs and item cap
|
|
182
|
+
are tuned in [`notice/config.ts`](./notice/config.ts). Every field is sanitized
|
|
183
|
+
and clipped at the boundary, and a message without `details` keeps Pi's default
|
|
184
|
+
custom-message box.
|
|
185
|
+
|
|
186
|
+
**Tool blocks.** Every tool block leads with the tool's own call line as the
|
|
187
|
+
headline — the tool name in the theme's tool-title colour — and its result body
|
|
188
|
+
sits two columns in directly beneath it, led by the transcript rail, instead of
|
|
189
|
+
Pi's filled status background:
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
read src/app.ts
|
|
193
|
+
╎ const answer = 42;
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
The two tools that mutate a file, `edit` and `write`, carry their result on the
|
|
197
|
+
call body — the edit diff, the written file content — so that body is railed
|
|
198
|
+
like any other result. There is no status band: the diff's own red/green line
|
|
199
|
+
colours, with the changed words in reverse video, and the `edit` / `write`
|
|
200
|
+
headline are what mark a file change.
|
|
201
|
+
|
|
202
|
+
A `registerToolRenderer` resolver switches the row to `renderShell: "self"`,
|
|
203
|
+
leaves the call's own first line as the headline, and indents the rest of the
|
|
204
|
+
call and the whole result under it. A shell call's `$` / `>` prompt becomes the
|
|
205
|
+
tool name and the command is repainted in the body colour, and a primary subject
|
|
206
|
+
(a path, URL, query, pattern) is demoted to the body colour too, so the name is
|
|
207
|
+
the call line's only accent (`bash bun run check`, `read src/app.ts`). The
|
|
208
|
+
tool's own
|
|
209
|
+
`renderCall`/`renderResult` are passed through, so syntax highlighting, diffs,
|
|
210
|
+
previews, and truncation notices are unchanged — only the frame is bytefly's.
|
|
211
|
+
The body carries the transcript rail, painted by status (a running result takes
|
|
212
|
+
the accent, a failure red, a finished one neutral) and drawn with the glyph from
|
|
213
|
+
the `chrome.rail` setting. A result frame is always railed; a call frame is railed
|
|
214
|
+
only for `edit` and `write`, whose call body carries the result — every other
|
|
215
|
+
call's headline and continuation lines stay plain-indented. A call line that
|
|
216
|
+
does not fit wraps, and Pi drops the tool name onto its own line when the
|
|
217
|
+
subject it carries (a long path) is one unbreakable token; those continuation
|
|
218
|
+
lines are still the call, so they stay unrailed and lose the accent too, and the
|
|
219
|
+
rail begins with the result underneath. A tool with
|
|
220
|
+
no renderer of its own is framed around a generic call/result fallback, whose
|
|
221
|
+
call line reads like a built-in — the leading string argument bare, the rest as
|
|
222
|
+
trailing options (`open_path src/app.ts`) — rather than dumping raw `key=value`
|
|
223
|
+
pairs. A tool that draws its own shell (`edit`) is framed too:
|
|
224
|
+
`renderShell: "self"` only tells Pi not to draw its box around the renderer, so
|
|
225
|
+
its diff would otherwise sit at the wrong indent; the frame drops the blank
|
|
226
|
+
edges its own `Box` pads itself with, and hands it a theme whose `bg` is inert so
|
|
227
|
+
it never paints a band the rest of the transcript does not have.
|
|
228
|
+
|
|
229
|
+
The call line is one colour grammar, shared with Pi's built-ins: the tool name is
|
|
230
|
+
`toolTitle` and bold and is the line's only accent; a primary subject (a query, a
|
|
231
|
+
URL, a path, a status) reads in the body role (`label`), counts and summaries are
|
|
232
|
+
`muted`, trailing options are `dim`, and the result body is `toolOutput`. The
|
|
233
|
+
shipped themes point `toolTitle` at the accent and `label` at the body colour, so
|
|
234
|
+
the name is the call line's one accent and the subject follows as body text; a
|
|
235
|
+
theme that maps `toolTitle` elsewhere is respected rather than overridden.
|
|
236
|
+
|
|
115
237
|
**Extension screen.** `/bytefly [extension] [refresh]` opens a keyboard-driven
|
|
116
238
|
dock list of every Pi extension the session resolves — across personal settings
|
|
117
239
|
and, when trusted, the project — one row per extension with its enable checkbox
|
|
@@ -152,14 +274,19 @@ TTY, so a print/JSON/RPC run gets an OS-level notification or none.
|
|
|
152
274
|
## Configuration
|
|
153
275
|
|
|
154
276
|
The header is tuned in [`header/config.ts`](./header/config.ts) — the mark and
|
|
155
|
-
identity theme tokens, the
|
|
156
|
-
|
|
277
|
+
identity theme tokens, the tagline, and the grouped help entries (each naming a
|
|
278
|
+
Pi keybinding resolved live or a raw command prefix). The footer's
|
|
157
279
|
content and layout are tuned in [`footer/config.ts`](./footer/config.ts) —
|
|
158
280
|
thresholds, gauge
|
|
159
281
|
glyphs and widths, separators, icons, and status keys. The turn separator has no
|
|
160
282
|
JSON either: its glyph, label prefix, padding, and colors are tuned in
|
|
161
283
|
[`separator/config.ts`](./separator/config.ts).
|
|
162
284
|
|
|
285
|
+
The transcript rail's glyph is the one chrome value that is JSON-configurable:
|
|
286
|
+
the `chrome` section's `rail` key (default `╎`) picks a single-column rail glyph,
|
|
287
|
+
read at `session_start`/`session_tree`. Everything else about the chrome is tuned
|
|
288
|
+
in code.
|
|
289
|
+
|
|
163
290
|
Everything JSON-configurable lives in `bytefly.json`, one shared document with a
|
|
164
291
|
top-level section per extension (see [Configuration](../../docs/configuration.md)).
|
|
165
292
|
It is read from the global document, then `<cwd>/.pi/bytefly.json`. The old
|
|
@@ -353,7 +480,7 @@ its server runs, and shows the two-tone `◉ <port>` (green glyph, accent port).
|
|
|
353
480
|
The trailing slot shows plan-style alerts when present and is omitted otherwise;
|
|
354
481
|
a plan chip's trailing plan file name is dropped, so the slot reads `⋮ plan`
|
|
355
482
|
even though the built-in footer keeps the name. An alert (a failed job) sorts to
|
|
356
|
-
the front of the slot. The slot trails the meters after a
|
|
483
|
+
the front of the slot. The slot trails the meters after a `╎`, so the context
|
|
357
484
|
gauge always starts the line. It keeps each extension's own colour when it
|
|
358
485
|
compacts, and it is droppable: a tight bar drops the slot rather than truncating
|
|
359
486
|
the model, which sits at the end of the line.
|
|
@@ -572,6 +699,11 @@ control-free line before it reaches an escape sequence or a shell argument.
|
|
|
572
699
|
- **An explicit `method` is honored blindly.** Point `method` at a notifier the
|
|
573
700
|
host lacks (or an OSC method off a TTY) and the notification is silently
|
|
574
701
|
dropped; only `auto` inspects the environment.
|
|
702
|
+
- **The working counter is one global row.** `setWorkingMessage` drives the
|
|
703
|
+
single working row, so bytefly and any other extension setting it race — last
|
|
704
|
+
writer wins — and the message is reset to Pi's default at `agent_end`. If the
|
|
705
|
+
row is hidden (`ctx.ui.setWorkingVisible(false)`) the counter has no surface,
|
|
706
|
+
and a reload mid-run does not resume it until the next turn.
|
|
575
707
|
|
|
576
708
|
## Non-goals
|
|
577
709
|
|
|
@@ -595,6 +727,9 @@ control-free line before it reaches an escape sequence or a shell argument.
|
|
|
595
727
|
whole surface.
|
|
596
728
|
- No sound, action buttons, notification history, or de-duplication; a
|
|
597
729
|
notification is a fire-and-forget message, not an inbox.
|
|
730
|
+
- No frozen per-run working duration: the counter is live and is handed back to
|
|
731
|
+
Pi when the working row comes down, because the extension API exposes no hook
|
|
732
|
+
to keep a finished run's total.
|
|
598
733
|
|
|
599
734
|
The built-in footer's `(auto)` auto-compact indicator and `(sub)`
|
|
600
735
|
subscription-cost marker are not reachable from `ExtensionContext` (there is no
|
|
@@ -605,20 +740,24 @@ rather than showing a wrong value.
|
|
|
605
740
|
|
|
606
741
|
| Integration point | Details |
|
|
607
742
|
|---|---|
|
|
608
|
-
| Header | `ctx.ui.setHeader` with a custom component, replacing Pi's built-in header; installed only when `ctx.mode === "tui"`. The mark and text are drawn from the active theme's tokens, the help keys resolve live through `keyText`, and the mark falls back to a wordmark on Apple Terminal (`isAppleTerminalSession`), mirroring Pi's built-in. |
|
|
743
|
+
| Header | `ctx.ui.setHeader` with a custom component, replacing Pi's built-in header; installed only when `ctx.mode === "tui"`. The nameplate insets by Pi's `outputPad` (read through `pi.getSettings()`), so it sits on the transcript margin, and the help hangs one body indent inside it. The mark and text are drawn from the active theme's tokens, the help keys resolve live through `keyText`, and the mark falls back to a wordmark on Apple Terminal (`isAppleTerminalSession`), mirroring Pi's built-in. |
|
|
609
744
|
| Footer | `ctx.ui.setFooter` with a custom component, replacing Pi's built-in footer; installed only when `ctx.mode === "tui"`. |
|
|
745
|
+
| User message | `pi.registerMarkdownTransformer` puts a two-row `You` nameplate (the label over a full-width rule) above each user message; one padded level-2 heading carries the accent through `mdHeading` for both rows, and the shipped themes set `userMessageBg` to the terminal default, so the block has no fill. Pi renders the result as Markdown, so the surface adds no component or color of its own. |
|
|
746
|
+
| Notice | `registerMessageRenderer(NOTICE_TYPE)` draws every shared transcript notice at the margin from its `details`; the headline stays at the margin and its rows hang from the transcript rail, painted by the notice's worst severity, whose glyph comes from the shared `RailSource`. The type and shape come from `lib/notice.ts`. A message without `details` falls back to Pi's default custom-message box. |
|
|
747
|
+
| Tool blocks | `registerToolRenderer` returns, for every tool, `renderShell: "self"`; `renderCall` leaves the tool's own first line as the block's headline (a shell prompt shown as the tool name, its command repainted in the body colour, and a primary subject demoted to the body colour) and indents the rest of the call two columns under it, `renderResult` indents its body the same and leads it with the status-coloured rail, and each wraps the tool's own renderer (or a generic fallback). `edit` and `write` carry their result on the call body, so that body is railed too. |
|
|
610
748
|
| Screen command | `pi.registerCommand("bytefly")` takes `[doctor] [extension] [refresh]`: `tui` mounts a dock screen via `ctx.ui.custom()` with the rails suppressed; other modes print through `ctx.ui.notify()` — the grouped listing, or `run_doctor`'s report for `doctor`. Tab completes `doctor`, extension names, and `refresh`. |
|
|
611
749
|
| Turn separator | `registerEntryRenderer("bytefly-separator")` draws each rule; `agent_settled` appends `pi.appendEntry("bytefly-separator", { turn })` in `tui` mode only. |
|
|
750
|
+
| Working timer | `turn_start` starts a 1s tick that drives `ctx.ui.setWorkingMessage("Working… Ns")` in `tui` mode; `agent_end` restores Pi's default and cancels the tick (also on `session_shutdown`). No config file and no stored state. |
|
|
612
751
|
| Notifications | `agent_settled` (skipped when aborted), `ui_prompt_start` only while `agent_start`…`agent_settled` marks a run active, and `my-pi-agent/job-finished` on `pi.events`; the `notify` section of `bytefly.json` gates each. Delivery writes an OSC 777/99 sequence to stdout or spawns `osascript`, `notify-send`, or `powershell.exe`. The bus subscription is created in `session_start` and released in `session_shutdown`, and the `job` extension never imports bytefly. |
|
|
613
752
|
| Picker discovery | `SettingsManager.create(...)` plus `DefaultPackageManager.resolve({ onMissing: "skip" })`, both public exports; both views drop `bytefly`'s own entrypoint. |
|
|
614
753
|
| Picker writes | `SettingsManager` setters (`setExtensionPaths`, `setPackages`, `setProjectExtensionPaths`, `setProjectPackages`) then `flush()`; `ctx.reload()` after the screen closes when changes are pending. |
|
|
615
754
|
| Doctor tool | `pi.registerTool("run_doctor")`, read-only (`readOnlyHint: true`), `exposure: "direct"`, active by default, with a typed `outputSchema` mirroring `structuredContent`. Its `readOnlyHint` is what the shared default-deny policy in `lib/policy.ts` reads to allow it in plan mode. |
|
|
616
755
|
| Doctor probes | `run_doctor` and the screen share the session cache created by `registerDoctor`; `/bytefly refresh` and `refresh: true` re-probe. Probes spawn `<binary> --version`, lazily import optional packages, and read config files through `lib/requirements.ts`; nothing is ever written. |
|
|
617
756
|
| Status routing | Reads `footerData.getExtensionStatuses()` (a custom footer replaces the built-in one entirely); `worktree` routes to line 1, plan/alert statuses to line 2's trailing slot. |
|
|
618
|
-
| Config | `bytefly.json` from the agent directory and `<cwd>/.pi`; the `price` section is read once per footer install (a new session or a session-tree change),
|
|
757
|
+
| Config | `bytefly.json` from the agent directory and `<cwd>/.pi`; the `price` section is read once per footer install (a new session or a session-tree change), the `notify` and `doctor` sections at `session_start`, and the `chrome` section's rail glyph at `session_start`/`session_tree`. Nothing watches the document. |
|
|
619
758
|
| Data sources | `footerData.getGitBranch()`, `footerData.getAvailableProviderCount()`, `ctx.getContextUsage()`, `ctx.sessionManager.getEntries()`, `ctx.sessionManager.getSessionName()`, `ctx.model`; subscribes to `footerData.onBranchChange` to repaint. |
|
|
620
759
|
| State storage | None besides the inert separator entries. The footer reads session entries and the price config (a memoized scan) and writes no tool-result `details`; it appends `bytefly-separator` entries excluded from LLM context. The screen derives its view from the picker runtime and the current reports and persists only through `settings.json`. Doctor's findings are session-scoped in memory only — cached for the session, never written to the session or a file. Notify holds only the config and the run-active flag in its closure. |
|
|
621
|
-
| Lifecycle | `session_start` installs the header and footer, points the doctor runtime at the session cwd, probes the extensions with hard requirements when `bytefly.json`'s `doctor.proactive` is on, and loads the notify config and subscribes to the job bus; `session_tree` reinstalls the header and footer; `session_shutdown` restores Pi's built-in header and footer, disposes the branch subscription, drops doctor's cached findings, and drops the notify bus subscription. |
|
|
760
|
+
| Lifecycle | `session_start` installs the header and footer, loads the chrome rail glyph, points the doctor runtime at the session cwd, probes the extensions with hard requirements when `bytefly.json`'s `doctor.proactive` is on, and loads the notify config and subscribes to the job bus; `session_tree` reinstalls the header and footer and reloads the rail; `session_shutdown` restores Pi's built-in header and footer, disposes the branch subscription, drops doctor's cached findings, and drops the notify bus subscription. |
|
|
622
761
|
|
|
623
762
|
## Design notes
|
|
624
763
|
|
|
@@ -627,19 +766,74 @@ rather than showing a wrong value.
|
|
|
627
766
|
from `rules.ts`, a list row from `rows.ts`, a meter from `gauge.ts`, and a
|
|
628
767
|
width from `columns.ts`. Nothing in bytefly names a literal color, and a
|
|
629
768
|
theme swap re-skins the whole chrome.
|
|
769
|
+
- **The user message is shaped, not painted.** Pi renders a user message as
|
|
770
|
+
Markdown and owns its colors, so bytefly's only lever is a Markdown
|
|
771
|
+
transformer: the two-row nameplate is one level-2 heading whose text is padded
|
|
772
|
+
to the width so the rule wraps below the label, and both rows take the accent
|
|
773
|
+
from the theme's `mdHeading`, because no extension API can color it directly.
|
|
774
|
+
Markdown has no multi-line heading and Pi inserts a blank line after a
|
|
775
|
+
heading block, so a single wrapped heading is the only way to keep the two
|
|
776
|
+
rows adjacent and in the accent. The block has no fill: the shipped themes set
|
|
777
|
+
`userMessageBg` to the terminal default, so it matches the tool blocks. The
|
|
778
|
+
nameplate is separated from the message by a blank line, so the message's own
|
|
779
|
+
Markdown parses unchanged and a soft wrap stays at the margin; hard-wrapping
|
|
780
|
+
Markdown here would corrupt a break inside an inline construct.
|
|
781
|
+
- **A notice is one shared contract, not a foreign type.** Pi resolves a
|
|
782
|
+
custom-message renderer by exact type with no wildcard, so `lib/notice.ts`
|
|
783
|
+
owns the type and the `NoticeDetails` shape: a producer states model text and
|
|
784
|
+
renderer details once, and bytefly owns only the look. One type for every
|
|
785
|
+
producer also lets `job` and `watch` share one prune
|
|
786
|
+
(`pruneStaleNotices`) keyed on `details.source`, so neither drops the other's
|
|
787
|
+
note from context.
|
|
788
|
+
- **The frame delegates; it does not re-render.** Pi resolves tool renderers
|
|
789
|
+
through the extension chain, and the resolved object is the definition the row
|
|
790
|
+
reads, so a resolver can override `renderShell` and wrap `next()`'s
|
|
791
|
+
`renderCall`/`renderResult`. bytefly replaces only the frame — Pi's filled
|
|
792
|
+
status box for an indent — and keeps every built-in's diff, syntax
|
|
793
|
+
highlighting, and truncation. The call and result are sibling components with
|
|
794
|
+
no spacer between them, so the call's headline and the result read as one
|
|
795
|
+
block; the result body hangs from the rail, and `edit`/`write` rail their call
|
|
796
|
+
body because it carries the result, except for the lines of a call line that
|
|
797
|
+
wrapped onto the body — a 121-column path under a bare `edit` is still the
|
|
798
|
+
call, not diff content, and the rail starts at the diff. A tool that renders
|
|
799
|
+
its own shell (`edit`)
|
|
800
|
+
is framed too: its blank edge lines are trimmed so the body starts under the
|
|
801
|
+
call, and it is handed a theme whose `bg` is inert so it paints no band. Each base renderer is handed its own
|
|
802
|
+
previous component (tracked in the row's `state`), never the frame: Pi gives a
|
|
803
|
+
renderer the component it returned last time, and a built-in calls `setText` on
|
|
804
|
+
it, so handing back the frame would break it. A base renderer that throws is
|
|
805
|
+
caught here too, and the frame falls back to its own call/result line at the
|
|
806
|
+
frame's pad; letting the throw reach Pi would swap in Pi's generic call line,
|
|
807
|
+
which sits a column to the left of the framed one.
|
|
808
|
+
- **One frame, one left edge.** Every transcript block that is not prose shares
|
|
809
|
+
the same frame (`lib/tui/indent.ts`): a tool block, a notice, and a subagent's
|
|
810
|
+
quoted output take one indent, so they hang from one left edge instead of four,
|
|
811
|
+
and a tool result's body sits `BODY_INDENT` columns in under its headline. A
|
|
812
|
+
body and a notice row lead that body with the transcript rail, whose
|
|
813
|
+
glyph is the one configurable part (`chrome.rail`, default `╎`); a result frame
|
|
814
|
+
is always railed, and a call frame only for `edit`/`write`, whose call body
|
|
815
|
+
carries the result. Pi pads its own messages and hands the value to tool renderers
|
|
816
|
+
(`context.outputPad`) and message renderers (`options.outputPad`), but gives an
|
|
817
|
+
entry renderer none and adds its component raw, so bytefly's blocks inset
|
|
818
|
+
themselves by that pad: the tool frame and the notice take it from Pi, and the
|
|
819
|
+
turn separator reads the last value a sibling observed (`pad.ts`). Pi's setting
|
|
820
|
+
is the only knob — bytefly adds no padding of its own. The footer's group
|
|
821
|
+
separator still draws `╎` between chips — a divider, not a rail.
|
|
630
822
|
- **The header is a nameplate, not a dashboard.** Pi's built-in brand colors are
|
|
631
823
|
fixed, so the mark ignores the active theme; bytefly's header draws a two-tone
|
|
632
824
|
mark from the `accent`/`warn` roles, a signature rule whose accent lead sits
|
|
633
825
|
under the mark, and pairs them with the agent identity, the chrome identity,
|
|
634
|
-
and a grouped help block. It
|
|
635
|
-
|
|
636
|
-
|
|
826
|
+
and a grouped help block. It takes its margin from Pi's `outputPad`, like the
|
|
827
|
+
tool frame and the notice, and hangs the help one body indent inside it, so the
|
|
828
|
+
header follows a non-default pad instead of assuming a column. It adds no
|
|
829
|
+
session or model data — that is the footer's job — and it stays a fixed
|
|
830
|
+
four-plus rows so the startup transcript never reflows.
|
|
637
831
|
- **Two lines, four zones.** Line 1 is identity (`pwd` left, branch + worktree
|
|
638
832
|
right); line 2 is resources (meters left, model + level right). This keeps
|
|
639
833
|
"where am I / what state" separate from "how much am I burning".
|
|
640
834
|
- **The gauge is anchored at column 0.** A leading mode slot would shift the
|
|
641
835
|
context percentage left and right as plan mode came and went; trailing it
|
|
642
|
-
after a
|
|
836
|
+
after a `╎` keeps the percentage in a fixed place while the mode stays
|
|
643
837
|
visible as the meters drop.
|
|
644
838
|
- **Statuses are routed, not repeated.** The built-in footer renders every
|
|
645
839
|
`setStatus` value on its own line; the bar splits them by meaning (`worktree`
|
|
@@ -659,19 +853,23 @@ rather than showing a wrong value.
|
|
|
659
853
|
non-zero usage, so it reads full only at 100% and a small percentage never
|
|
660
854
|
disappears; unknown usage draws no gauge at all. Only at extreme widths does
|
|
661
855
|
`truncateToWidth` clip the remaining text.
|
|
662
|
-
- **Separators shrink to a single space.** Padded separators (` · `, `
|
|
856
|
+
- **Separators shrink to a single space.** Padded separators (` · `, ` ╎ `)
|
|
663
857
|
collapse to one space before `truncateToWidth` applies, so adjacent segments
|
|
664
|
-
never collide and the ellipsis never replaces a
|
|
858
|
+
never collide and the ellipsis never replaces a `╎` with nothing after it.
|
|
665
859
|
- **Statuses compact with their colour.** A themed status's compact icon form
|
|
666
860
|
reattaches its opening and closing SGR sequences around the visible text, so a
|
|
667
861
|
red `✗1` stays red instead of losing its colour exactly when the bar tightens.
|
|
668
862
|
A two-token `icon count` badge (for `rewind`: `↺ 2`) compacts to `↺2`, keeping
|
|
669
863
|
the count. The slot sorts an alert (a failed job) ahead of the modes and is
|
|
670
864
|
droppable, so it goes before the model is clipped.
|
|
671
|
-
- **Neutral by default; color means something.**
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
865
|
+
- **Neutral by default; color means something.** A tool block carries no status
|
|
866
|
+
colour except the accent tool name and the status-coloured result rail; an
|
|
867
|
+
informational notice leads with `muted`; a failure is red only where the
|
|
868
|
+
tool's own output says so, so the transcript is not a wall of green.
|
|
869
|
+
The context gauge is the other coloured surface: it stays `muted` below 70%,
|
|
870
|
+
cost is `muted`, the session and branch are `muted`, and tokens/cache/window are
|
|
871
|
+
`dim`, so yellow and red are reserved for a threshold or a failure rather than
|
|
872
|
+
always on.
|
|
675
873
|
- **Data is read fresh, except the usage scan.** The component resolves the
|
|
676
874
|
theme through a getter and reads a fresh snapshot each render. The expensive
|
|
677
875
|
part — summing token/cost totals across the session — is memoized by
|
|
@@ -746,7 +944,10 @@ rather than showing a wrong value.
|
|
|
746
944
|
## Files
|
|
747
945
|
|
|
748
946
|
The surfaces are folders: `header/`, `footer/` (the status bar and its price
|
|
749
|
-
calculation), `
|
|
947
|
+
calculation), `message/` (the user-message marker), `notice/` (the shared notice),
|
|
948
|
+
`block/` (the tool-block frame), `separator/`, `working/` (the live
|
|
949
|
+
elapsed counter on Pi's working row), `panel/` (the
|
|
950
|
+
unified screen and its merged model),
|
|
750
951
|
`picker/` (discovery and the settings store), `doctor/` (the `run_doctor` tool
|
|
751
952
|
and its probe runtime), and `notify/`. The chrome and the screen are drawn from
|
|
752
953
|
the package's [`lib/tui/`](../../lib/tui/) design system (tokens, rules, rows,
|
|
@@ -754,8 +955,10 @@ columns, gauge), so the look follows the active theme.
|
|
|
754
955
|
|
|
755
956
|
| File | Responsibility |
|
|
756
957
|
|---|---|
|
|
757
|
-
| `index.ts` | Composition root: the disable guard
|
|
758
|
-
| `
|
|
958
|
+
| `index.ts` | Composition root: the disable guard, the eleven `register*` calls, the one shared output-pad store, and the rail source. |
|
|
959
|
+
| `pad.ts` | `PadSource` and `createPadStore`: one record of Pi's output pad, so the tool frame, the notice, and the turn separator inset by the same margin. |
|
|
960
|
+
| `chrome.ts` | The transcript rail (`RAIL_ROLES`, `railGlyph`), the `chrome` section's normalizer and loader, and the `RailSource` that `registerChrome` keeps current on `session_start`/`session_tree`. |
|
|
961
|
+
| `header/config.ts` | Header tuning (`HEADER_CONFIG`): mark and identity roles, tagline, and the grouped help entries. |
|
|
759
962
|
| `header/component.ts` | `ByteflyHeader`: the mark, identity rows, signature rule, and grouped help, plus its factory. |
|
|
760
963
|
| `header/register.ts` | Installs/restores the header and owns its session lifecycle. |
|
|
761
964
|
| `footer/register.ts` | Installs/restores the footer and owns its session lifecycle. |
|
|
@@ -774,11 +977,24 @@ columns, gauge), so the look follows the active theme.
|
|
|
774
977
|
| `footer/render/lines.ts` | Snapshot → two `LineSpec`s; status routing. |
|
|
775
978
|
| `footer/render/layout.ts` | Width reduction and right-zone folding. |
|
|
776
979
|
| `footer/render/footer.ts` | `StatusBar` component and footer factory. |
|
|
980
|
+
| `message/config.ts` | Nameplate tuning (`MESSAGE_CONFIG`): the label and the rule. |
|
|
981
|
+
| `message/format.ts` | Pure `nameplate` and `styleUserMessage`: the two-row nameplate (label over a full-width rule) and its placement above the message. |
|
|
982
|
+
| `message/register.ts` | Registers the Markdown transformer. |
|
|
983
|
+
| `notice/config.ts` | Notice tuning (`NOTICE_CONFIG`): source/title/detail roles, the severity glyphs and roles, the shared rail roles, and the item cap. |
|
|
984
|
+
| `notice/component.ts` | Pure `noticeLines` and `NoticeComponent`: the headline at the margin, the rail-led severity rows, sanitized and width-clipped. |
|
|
985
|
+
| `notice/register.ts` | Registers the message renderer for the shared notice type and hands it the rail source. |
|
|
986
|
+
| `block/config.ts` | `BlockStatus` and `BLOCK_CONFIG.roles`: the design role a result's rail takes for each state. |
|
|
987
|
+
| `block/component.ts` | Pure `toolBlockLines`, `headlineWithToolName`, `recolorShellCall`, `recolorCallHeadline`, and `ToolBlock`: the headline, the body's two-column indent (optionally rail-led), the reduced-width inner render, the leading-blank trim, the shell-prompt rewrite (the name keeps the accent, the command reopens in `label`), the subject demotion (a path, URL, query, or pattern reads in `label` so the name is the line's only accent, including the tail a bare `edit <path>` headline wraps onto the body), and the image-line passthrough. |
|
|
988
|
+
| `block/fallback.ts` | Generic `callFallback` / `resultFallback` for a tool with no renderer: the subject in the body colour, its options faint, and a capped result preview. |
|
|
989
|
+
| `block/register.ts` | The `registerToolRenderer` resolver that frames every tool; it recolours a shell call's string before the frame wraps it, rails the result body by status, and rails the call body for `edit`/`write` (the two whose call body carries the result). |
|
|
777
990
|
| `separator/config.ts` | Turn-rule tuning: `customType`, glyph, label prefix, padding, and roles. |
|
|
778
991
|
| `separator/register.ts` | Registers the entry renderer and appends a separator on `agent_settled`. |
|
|
779
992
|
| `separator/component.ts` | `SeparatorComponent`: the stateless entry renderer. |
|
|
780
993
|
| `separator/format.ts` | Pure `separatorLine`: the centered turn rule, drawn by the kit's `centeredRule`. |
|
|
781
994
|
| `separator/types.ts` | `TurnSeparatorData`. |
|
|
995
|
+
| `working/config.ts` | Counter tuning (`WORKING_CONFIG`): the word, the ellipsis, and the tick interval. |
|
|
996
|
+
| `working/format.ts` | Pure `formatElapsed` (whole seconds) and the working-message shape. |
|
|
997
|
+
| `working/register.ts` | Starts the tick on `turn_start` and cancels it on `agent_end`/`session_shutdown`. |
|
|
782
998
|
| `panel/types.ts` | `PanelRow` and `PanelModel`, the merged row model. |
|
|
783
999
|
| `panel/join.ts` | The item/report union with the unambiguous-name health rule and unique row keys, groups, and ordering. |
|
|
784
1000
|
| `panel/format.ts` | Non-TUI text: the headline and the grouped listing with a per-row health suffix. |
|
|
@@ -838,3 +1054,12 @@ through the factory (`doctor/register`). The notify capability is covered by its
|
|
|
838
1054
|
(`notify/config`), the pure text builders (`notify/format`), method selection and
|
|
839
1055
|
delivery over recording seams (`notify/dispatch`), and its three triggers,
|
|
840
1056
|
reload, and shutdown through `registerNotify` (`notify/register`).
|
|
1057
|
+
The user-message nameplate is covered by its pure transform
|
|
1058
|
+
(`message/format`, including a render through pi-tui's real `Markdown`) and its
|
|
1059
|
+
registration (`message/register`), plus the factory check in `extension`.
|
|
1060
|
+
The notice surface is covered by its pure rows (`notice/component`), its
|
|
1061
|
+
renderer registration (`notice/register`), and the shared contract
|
|
1062
|
+
(`lib/notice`), plus the factory check in `extension`.
|
|
1063
|
+
The tool-block frame is covered by its pure `toolBlockLines` (`block/component`)
|
|
1064
|
+
and its generic fallback (`block/fallback`), plus the resolver's shell override,
|
|
1065
|
+
self-shell carve-out, and fallback framing (`block/register`).
|