@gavin-hu/my-pi-agent 0.13.1 → 0.14.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.
Files changed (42) hide show
  1. package/README.md +14 -11
  2. package/README.zh-CN.md +12 -12
  3. package/extensions/bytefly/README.md +140 -58
  4. package/extensions/bytefly/doctor/config.ts +4 -1
  5. package/extensions/bytefly/footer/price/config.ts +3 -2
  6. package/extensions/bytefly/header/component.ts +30 -8
  7. package/extensions/bytefly/header/config.ts +22 -0
  8. package/extensions/bytefly/header/register.ts +2 -1
  9. package/extensions/bytefly/index.ts +3 -3
  10. package/extensions/bytefly/notify/config.ts +1 -1
  11. package/extensions/bytefly/panel/commands.ts +1 -1
  12. package/extensions/bytefly/panel/format.ts +12 -19
  13. package/extensions/bytefly/panel/settings-runtime.ts +62 -18
  14. package/extensions/bytefly/panel/settings.ts +239 -43
  15. package/extensions/bytefly/panel/tui/settings-view.ts +49 -3
  16. package/extensions/bytefly/panel/tui.ts +87 -19
  17. package/extensions/bytefly/separator/component.ts +7 -6
  18. package/extensions/bytefly/separator/config.ts +15 -0
  19. package/extensions/bytefly/separator/format.ts +60 -6
  20. package/extensions/bytefly/separator/register.ts +68 -9
  21. package/extensions/bytefly/separator/types.ts +8 -0
  22. package/extensions/bytefly/working/config.ts +4 -5
  23. package/extensions/bytefly/working/format.ts +24 -7
  24. package/extensions/bytefly/working/register.ts +34 -3
  25. package/extensions/memory/README.md +2 -0
  26. package/extensions/plan/README.md +12 -0
  27. package/extensions/subagent/README.md +60 -5
  28. package/extensions/subagent/config.ts +58 -0
  29. package/extensions/subagent/index.ts +27 -1
  30. package/extensions/subagent/orchestrate.ts +16 -3
  31. package/extensions/subagent/render.ts +12 -0
  32. package/extensions/subagent/run.ts +100 -37
  33. package/extensions/subagent/transcripts.ts +202 -0
  34. package/extensions/subagent/types.ts +12 -0
  35. package/extensions/watch/README.md +2 -0
  36. package/extensions/wechat/README.md +2 -0
  37. package/lib/README.md +1 -1
  38. package/lib/config.ts +33 -9
  39. package/lib/format.ts +6 -0
  40. package/package.json +2 -1
  41. package/themes/bytefly-dark.json +2 -3
  42. package/themes/bytefly-light.json +2 -3
package/README.md CHANGED
@@ -31,13 +31,16 @@ want the agent to *think first and work safely*:
31
31
  - **Plan, then execute.** A read-only planning mode turns a task into a reviewed
32
32
  plan file that the agent then follows step by step.
33
33
  - **Delegate and parallelize.** Run specialist subagents in their own Pi
34
- processes to explore, review, test, or implement while you keep the thread.
35
- - **Never wreck the checkout.** Work happens in an isolated `git worktree`, and
36
- every prompt is snapshotted so a bad turn is one `/rewind` away.
34
+ processes, push a long build into a background job, and let a watcher tail a
35
+ log until it matters — all while you keep the thread.
36
+ - **Never wreck the checkout.** Isolation is one command: the task moves onto its
37
+ own branch in a `git worktree`, and every prompt is snapshotted so a bad turn
38
+ is one `/rewind` away.
37
39
  - **Remember across sessions.** Durable notes live in plain markdown that you
38
40
  can read, edit, and version.
39
41
  - **Reach the web and your files.** Search, fetch, browse, open, and read
40
- documents — all from inside the session.
42
+ documents from inside the session, and answer a WeChat chat through a bridge to
43
+ the live session.
41
44
 
42
45
  Everything is optional and independently toggleable. Nothing is patched or
43
46
  forked: Pi discovers the resources through the `pi` manifest in
@@ -49,13 +52,13 @@ other.
49
52
  Requires a working [Pi](https://pi.dev) installation.
50
53
 
51
54
  ```bash
52
- pi install npm:@gavin-hu/my-pi-agent # install from npm
53
- pi install git:github.com/gavin-hu/my-pi-agent # install from git
54
- pi install git:github.com/gavin-hu/my-pi-agent@v0.13.1 # pin a release
55
- pi install ./ -l # project-local (.pi/settings.json)
56
- pi -e . # try it for one run
55
+ pi install npm:@gavin-hu/my-pi-agent
57
56
  ```
58
57
 
58
+ [Getting started](./docs/getting-started.md) has the other sources — git, a
59
+ pinned release, a project-local install, and a trial that writes nothing — plus
60
+ the fit decision, the first workflows, and how to remove the pack.
61
+
59
62
  `git` on `PATH` is needed by [`worktree`](./extensions/worktree/) and
60
63
  [`rewind`](./extensions/rewind/); everything else degrades or disables cleanly,
61
64
  and `/bytefly doctor` names what is missing, including optional runtime pieces.
@@ -121,7 +124,7 @@ extension's own `README.md` has the full tool and command surface.
121
124
 
122
125
  | Extension | Tools and commands | What it does |
123
126
  |---|---|---|
124
- | [`bytefly`](./extensions/bytefly/) | `/bytefly` (with the `doctor` and `settings` subcommands), `run_doctor` | The visible layer: a themed header with a width-adaptive footer, a live elapsed counter on Pi's working row, and a transcript where every tool block leads with its call line and rails its result under it. `/bytefly` switches extensions and holds the curated settings; opt-in `notify` raises OS notifications when a run, a job, or a question needs you. `bytefly.json` is the config document every extension reads. |
127
+ | [`bytefly`](./extensions/bytefly/) | `/bytefly` (with the `doctor` and `settings` subcommands), `run_doctor` | The visible layer: a themed header with a width-adaptive footer, a live run line on Pi's working row (tool, tool count, and elapsed time), and a transcript where every tool block leads with its call line and rails its result under it. `/bytefly` switches extensions and holds the curated settings; opt-in `notify` raises OS notifications when a run, a job, or a question needs you. `bytefly.json` is the config document every extension reads. |
125
128
 
126
129
  ### Themes
127
130
 
@@ -184,7 +187,7 @@ stops the factories from registering; to drop the whole package, use Pi's
184
187
 
185
188
  | Guide | What it covers |
186
189
  |---|---|
187
- | [Getting started](./docs/getting-started.md) | Requirements, install options, verifying the load, themes. |
190
+ | [Getting started](./docs/getting-started.md) | Whether the pack fits, a trial run, install options, verifying the load, first workflows, and removal. |
188
191
  | [Extensions](./docs/extensions.md) | The full catalog of every extension and theme. |
189
192
  | [Configuration](./docs/configuration.md) | Config-file resolution, the per-extension index, environment variables. |
190
193
  | [Configuration reference](./docs/references/bytefly-config.md) | Every `bytefly.json` section and key, with its type, default, and bounds. |
package/README.zh-CN.md CHANGED
@@ -29,12 +29,13 @@ Pi 提供一个精简而锐利的内核。这个包是在其之上的一层,
29
29
 
30
30
  - **先规划,再执行。** 只读的规划模式把一次任务变成一份可审阅的计划文件,智能体
31
31
  随后按步骤执行。
32
- - **委派与并行。** 在各自的 Pi 进程中运行专职子智能体来探索、审阅、测试或实现,
33
- 你继续把握主线。
34
- - **绝不搞乱检出区。** 工作发生在隔离的 `git worktree` 中,每次提示都会生成快照,
35
- 因此一次糟糕的回合只需一个 `/rewind` 就能回退。
32
+ - **委派与并行。** 在各自的 Pi 进程中运行专职子智能体,把耗时构建交给后台任务,
33
+ 让 watcher 盯住日志直到出现关键行 —— 你继续把握主线。
34
+ - **绝不搞乱检出区。** 隔离只需一条命令:任务切到自己分支的 `git worktree` 中,
35
+ 每次提示还会生成快照,因此一次糟糕的回合只需一个 `/rewind` 就能回退。
36
36
  - **跨会话记忆。** 持久笔记以纯 Markdown 保存,可读、可改、可纳入版本控制。
37
- - **连通网络与文件。** 搜索、抓取、浏览、打开和读取文档 —— 全部在会话内完成。
37
+ - **连通网络与文件。** 在会话内搜索、抓取、浏览、打开和读取文档,还可通过桥接
38
+ 在微信里直接回答当前会话。
38
39
 
39
40
  一切都是可选的,且可独立开关。没有任何东西被补丁或分叉:Pi 通过
40
41
  [`package.json`](./package.json) 里的 `pi` manifest 发现这些资源,因此该包能像
@@ -45,13 +46,12 @@ Pi 提供一个精简而锐利的内核。这个包是在其之上的一层,
45
46
  需要一个可用的 [Pi](https://pi.dev) 安装。
46
47
 
47
48
  ```bash
48
- pi install npm:@gavin-hu/my-pi-agent # 从 npm 安装
49
- pi install git:github.com/gavin-hu/my-pi-agent # 从 git 安装
50
- pi install git:github.com/gavin-hu/my-pi-agent@v0.13.1 # 固定到某个版本
51
- pi install ./ -l # 仅项目本地(.pi/settings.json)
52
- pi -e . # 试运行一次
49
+ pi install npm:@gavin-hu/my-pi-agent
53
50
  ```
54
51
 
52
+ [快速开始](./docs/getting-started.md) 介绍其余安装方式——git、固定版本、
53
+ 仅项目本地安装,以及不写入任何内容的试用——还有适用性判断、首个工作流和移除方式。
54
+
55
55
  `git` 需在 `PATH` 上,供 [`worktree`](./extensions/worktree/) 与
56
56
  [`rewind`](./extensions/rewind/) 使用;其余部分会优雅降级或禁用,
57
57
  `/bytefly doctor` 会指出缺少什么,包括可选的运行时组件。
@@ -112,7 +112,7 @@ Windows、macOS 和 Linux 均可运行,完整检查套件在每个平台上都
112
112
 
113
113
  | 扩展 | 工具与命令 | 说明 |
114
114
  |---|---|---|
115
- | [`bytefly`](./extensions/bytefly/) | `/bytefly`(`doctor`、`settings` 子命令)、`run_doctor` | 可见的那一层:主题化头部配宽度自适应页脚,Pi 工作行上实时计时,转录中每个工具块以调用行为标题、结果正文挂在其下方。`/bytefly` 可开关扩展并集中存放精选设置;可选的 `notify` 会在一次运行、后台任务或待回答的问题需要你时发出系统通知。`bytefly.json` 是每个扩展都会读取的配置文档。 |
115
+ | [`bytefly`](./extensions/bytefly/) | `/bytefly`(`doctor`、`settings` 子命令)、`run_doctor` | 可见的那一层:主题化头部配宽度自适应页脚,Pi 工作行上实时显示工具、调用次数与计时,转录中每个工具块以调用行为标题、结果正文挂在其下方。`/bytefly` 可开关扩展并集中存放精选设置;可选的 `notify` 会在一次运行、后台任务或待回答的问题需要你时发出系统通知。`bytefly.json` 是每个扩展都会读取的配置文档。 |
116
116
 
117
117
  ### 主题
118
118
 
@@ -171,7 +171,7 @@ Pi 的 `--no-extensions`,或从你的设置中删除该包。
171
171
 
172
172
  | 指南 | 内容 |
173
173
  |---|---|
174
- | [快速开始](./docs/getting-started.md) | 环境要求、安装选项、验证加载、主题。 |
174
+ | [快速开始](./docs/getting-started.md) | 是否适合你、试用、安装选项、验证加载、首个工作流、移除。 |
175
175
  | [扩展](./docs/extensions.md) | 所有扩展与主题的完整目录。 |
176
176
  | [配置](./docs/configuration.md) | 配置文件解析、按扩展索引、环境变量。 |
177
177
  | [配置参考](./docs/references/bytefly-config.md) | 列出 `bytefly.json` 的每个 section 与键,包括类型、默认值和取值范围。 |
@@ -3,8 +3,8 @@
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 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 with two
6
+ footer, a two-row `You` nameplate that opens each user message, a receipt rule that
7
+ records each completed turn in the transcript, one keyboard-driven screen with two
8
8
  sections — Extensions, every resolved Pi extension with its enable checkbox and
9
9
  its health, and Settings, the common app and `bytefly.json` preferences — and
10
10
  an opt-in notify capability that raises an OS notification when a run finishes,
@@ -33,7 +33,7 @@ How the `run_doctor` call and its result look in the transcript — the call lin
33
33
 
34
34
  The extension owns ten surfaces: the startup header, the footer, the
35
35
  user-message marker, the rule between completed turns, the notice block for
36
- shared transcript notices, the tool-block frame, the live working counter, the
36
+ shared transcript notices, the tool-block frame, the live working line, the
37
37
  two-section control center, notifications, and the published-version check.
38
38
 
39
39
  **Header.** A nameplate: a two-tone half-block mark from the active theme's
@@ -59,10 +59,15 @@ that cannot render half blocks the mark falls back to a two-tone `Pi` wordmark.
59
59
 
60
60
  As the terminal narrows, the identity sheds the update marker, then the chrome
61
61
  version, then the chrome line, then the agent name, and the help rows drop whole
62
- keys, so nothing wraps or clips mid-word. The nameplate sits on the transcript margin, which follows Pi's
63
- `outputPad` (the value the tool frame and the notice use too), and the help hangs
64
- one body indent inside it. The mark, tokens, identity, and grouped help are tuned
65
- in [`header/config.ts`](./header/config.ts).
62
+ keys, so nothing wraps or clips mid-word. Height sheds the same way: at 28 rows
63
+ all three help groups print, below 28 the middle group (the expand and model
64
+ chords) goes first, below 22 only the `/` commands row survives, and below 16 the
65
+ help block and the leading blank row go, leaving the nameplate and its rule. An
66
+ unknown height keeps the full header. The nameplate sits on the transcript
67
+ margin, which follows Pi's `outputPad` (the value the tool frame and the notice
68
+ use too), and the help hangs one body indent inside it. The mark, tokens,
69
+ identity, and grouped help are tuned in
70
+ [`header/config.ts`](./header/config.ts).
66
71
 
67
72
  **Footer.** Two lines, each with a left and right zone:
68
73
 
@@ -168,26 +173,36 @@ as it would without it, and a soft wrap of one long line stays at the margin
168
173
  because the renderer wraps it, not bytefly. The label and the rule are tuned
169
174
  in [`message/config.ts`](./message/config.ts).
170
175
 
171
- **Working timer.** Pi's working row shows a static `Working`, so a long run
176
+ **Working line.** Pi's working row shows a static `Working`, so a long run
172
177
  reads the same as a short one. While the row is up, bytefly advances its
173
- message once a second and hands it back to Pi when the row comes down:
178
+ message once a second and hands it back to Pi when the row comes down. Before
179
+ the first tool starts it is a count:
174
180
 
175
181
  ```
176
182
  Working… 12s
177
183
  ```
178
184
 
179
- The count is tied to the row's visibility: it starts on `turn_start`, the event
180
- the host shows the working indicator on, and stops on `agent_end`, where the
181
- host clears it, then again on `session_shutdown`. Turns inside one run do not
182
- restart it — the host keeps the same row up across turns — so the count covers
183
- the whole run, tool calls included. A retry is a new run, so the count restarts
184
- when its row reappears. The word and the interval are tuned in
185
- [`working/config.ts`](./working/config.ts).
185
+ Once a top-level tool starts, the row becomes a live run line: the tool, how
186
+ many top-level tools this run has started, and the elapsed time.
187
+
188
+ ```
189
+ edit · 3 tools · 12s
190
+ ```
191
+
192
+ A nested tool call (one carrying `parentToolCallId`, such as a codemode script's
193
+ inner call) neither counts nor takes over the line, so the number matches the
194
+ tool blocks in the transcript. The line keeps the last tool name while the
195
+ model streams after it, and a tool event repaints the row immediately instead
196
+ of waiting for the next tick. The line covers the whole run, tool calls
197
+ included. It is tied to the row's visibility: it starts on `turn_start`, stops
198
+ on `agent_end`, and is reset on `session_shutdown`; turns inside one run do not
199
+ restart it, and a retry starts a new run. The words and the interval are tuned
200
+ in [`working/config.ts`](./working/config.ts).
186
201
 
187
202
  **Turn separator.** After each completed turn the extension appends an inert
188
- entry and draws a centered `╌╌╌ turn N ╌╌╌` rule across the transcript; see
189
- [Turn separator](#turn-separator). No config file is needed and there is no
190
- command.
203
+ entry and draws a centered receipt rule — `╌╌╌ turn N · duration · N tools ╌╌╌`
204
+ — across the transcript; see [Turn separator](#turn-separator). No config file is
205
+ needed and there is no command.
191
206
 
192
207
  **Notices.** Any extension in the package can post a transcript notice under the
193
208
  shared `my-pi-agent/notice` type; bytefly draws it at the transcript's margin —
@@ -219,6 +234,15 @@ are tuned in [`notice/config.ts`](./notice/config.ts). Every field is sanitized
219
234
  and clipped at the boundary, and a message without `details` keeps Pi's default
220
235
  custom-message box.
221
236
 
237
+ **Compaction.** `/compact` is Pi's own surface: its summary block is a built-in
238
+ message component, so bytefly cannot replace, hide, or shorten it. The shipped
239
+ themes only remove its fill — `customMessageBg` is the terminal default — and
240
+ paint the `[compaction]` label in the accent, so the block sits on the canvas
241
+ like the rest of the transcript. Pi's branch summaries and skill invocations
242
+ share the same tokens.
243
+
244
+ <img src="../../assets/compaction.png" alt="The /compact summary block: a [compaction] label, the compacted token count, and the summary Markdown on the terminal canvas, with no background band" width="100%">
245
+
222
246
  **Tool blocks.** Every tool block leads with the tool's own call line as the
223
247
  headline — the tool name in the theme's tool-title colour — and its result body
224
248
  sits two columns in directly beneath it, led by the transcript rail, instead of
@@ -348,7 +372,7 @@ identity tokens, tagline, grouped help), the footer in
348
372
  [`footer/config.ts`](./footer/config.ts) (thresholds, gauge glyphs and widths,
349
373
  separators, icons, status keys), and the turn separator in
350
374
  [`separator/config.ts`](./separator/config.ts) (glyph, label prefix, padding,
351
- colors).
375
+ minimum duration, outcome wording, colors).
352
376
 
353
377
  Every key of every section is listed with its type, default, and bounds in the
354
378
  [configuration reference](../../docs/references/bytefly-config.md); this section
@@ -507,32 +531,46 @@ the model, which sits at the end of the line.
507
531
  `agent_settled` fires once after an agent run fully settles — exactly one user
508
532
  turn — which is why it is used instead of `turn_end` (that fires once per
509
533
  assistant message, so tool-calling rounds would each get a line). On each
510
- settled run the extension appends an inert custom entry and a renderer draws
511
- it:
534
+ settled run the extension appends an inert custom entry and a renderer draws it
535
+ as a receipt: the turn number, then the run's duration, top-level tool count,
536
+ and outcome as the width allows.
512
537
 
513
538
  ```
514
- ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ turn 3 ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
539
+ ╌╌╌╌╌╌╌╌╌╌╌╌ turn 3 · 1m12s · 7 tools ╌╌╌╌╌╌╌╌╌╌╌╌
515
540
  ```
516
541
 
517
542
  - The turn number is `count(separators on the current branch) + 1`, so a
518
543
  resumed or forked session continues the parent's numbering and abandoned
519
544
  branches do not leak counts.
545
+ - The receipt opens on the run's first `agent_start` and closes on
546
+ `agent_settled`, so a retry stays inside one duration. It counts only
547
+ top-level tool calls; a nested call (one carrying `parentToolCallId`) is an
548
+ implementation detail of its parent and does not inflate the number.
549
+ - The outcome is part of the receipt: a failed tool adds `✕N`, and an aborted
550
+ run adds `aborted` instead of the failure count. A sub-second run prints no
551
+ duration, so quick turns stay quiet. Entries written before the receipt
552
+ existed have only a turn number and still render as `turn N`.
553
+ - The label sheds fields rather than overflowing: the tool count first, then
554
+ the duration, then the outcome. When even the turn number does not fit, the
555
+ line becomes a plain dashed rule instead of clipping.
556
+ - A clean turn paints the label `meta`, a turn with a failed tool paints it
557
+ `bad`, and an aborted turn paints it `warn`.
520
558
  - The entry is excluded from LLM context, so it costs no tokens, and it
521
559
  survives resume and fork. It is drawn under the `bytefly-separator` custom
522
560
  entry type.
523
- - The line is centered and width-adaptive: the label is filled out with the
524
- glyph, and when it cannot fit the line becomes a plain dashed rule rather
525
- than overflowing.
526
561
  - The rule is installed only in interactive (`tui`) sessions; RPC, JSON, and
527
562
  print sessions append nothing.
528
563
  - Separators recorded by the retired `turn-separator` extension are not
529
564
  rendered, and `PI_DISABLED_EXTENSIONS=turn-separator` no longer affects the
530
565
  rule; disable the whole extension with `bytefly` instead.
531
- - There is no runtime command; the glyph, label prefix, padding, and colors are
532
- tuned in [`separator/config.ts`](./separator/config.ts).
566
+ - There is no runtime command; the glyph, label prefix, padding, colors, and
567
+ minimum duration are tuned in
568
+ [`separator/config.ts`](./separator/config.ts).
533
569
 
534
570
  ## The control center
535
571
 
572
+ <img src="../../assets/bytefly.png" alt="The /bytefly control center: the Extensions and Settings section bar with the `Global · 10 extensions · 2 off · 1 missing` counts, one checkbox row per extension with its verdict, the focused extension's report, and the key hints" width="100%">
573
+
536
574
  `/bytefly [extension] [refresh]` opens the control center on **Extensions**: a
537
575
  keyboard-driven dock list of every extension Pi resolves for the session, so the
538
576
  list matches what actually loads. `←`/`→` switch to **Settings**, where the same
@@ -597,11 +635,11 @@ installs or fetches a package.
597
635
  without leaving the session.
598
636
 
599
637
  **Settings.** The second section uses the same frame and the same `Tab` scope
600
- switch, with its own cursor. It lists sixteen preferences in six groups:
638
+ switch, with its own cursor. It lists twenty-eight preferences in ten groups:
601
639
 
602
640
  ```
603
641
  bytefly ──────────────────────────────────────────────────────────────
604
- Extensions · Settings Global · 16 settings
642
+ Extensions · Settings Global · 28 settings
605
643
 
606
644
  Startup (1)
607
645
  ❯ ◻ Bytefly startup TUI Off
@@ -620,16 +658,32 @@ bytefly ────────────────────────
620
658
  Currency CNY (¥)
621
659
  Cost decimals 2
622
660
  Session budget 0
623
- Transcript (2)
661
+ Transcript (3)
624
662
  Rail glyph ╎
663
+ Achieved goal Hide
625
664
  Output padding 1
626
665
  Terminal (1)
627
666
  ◻ Hardware cursor Off
667
+ Worktree (4)
668
+ ◼ Fetch remote On
669
+ Base ref Fresh
670
+ Branch prefix worktree-
671
+ Prune after (days) 7
672
+ Snapshots (3)
673
+ ◼ Automatic snapshots On
674
+ Snapshots kept 20
675
+ ◼ Safety snapshot On
676
+ Jobs and watchers (3)
677
+ ◻ Wake when a job finishes Off
678
+ ◻ Keep jobs after exit Off
679
+ ◻ Wake on a match Off
680
+ Memory (1)
681
+ ◼ Inject notes On
628
682
  Bytefly startup TUI — Pi setting
629
683
  Use bytefly's startup header in place of Pi's startup TUI.
630
684
  Default · ~/.pi/agent/settings.json
631
685
 
632
- Esc close · ←/→ · Space change · Tab scope · ↑/↓ select
686
+ Esc close · ←/→ · Space change · Tab scope · u reset · J JSON · [ ] group
633
687
  ```
634
688
 
635
689
  | Group | Row | Source | Values |
@@ -648,21 +702,43 @@ bytefly ────────────────────────
648
702
  | Pricing | Cost decimals | `price.currency.decimals` | 0, 1, 2, 3, 4 |
649
703
  | Pricing | Session budget | `price.budget` | Number, `0` = off |
650
704
  | Transcript | Rail glyph | `chrome.rail` | Text, exactly one column |
705
+ | Transcript | Achieved goal | `goal.achieved` | Show, Hide |
651
706
  | Transcript | Output padding | Pi `settings.json` | 0, 1 |
652
707
  | Terminal | Hardware cursor | Pi `settings.json` | Off, On |
708
+ | Worktree | Base ref | `worktree.baseRef` | Fresh, Head |
709
+ | Worktree | Branch prefix | `worktree.branchPrefix` | Text, trimmed, required |
710
+ | Worktree | Fetch remote | `worktree.fetchRemote` | Off, On |
711
+ | Worktree | Prune after (days) | `worktree.pruneAfterDays` | Number, 0–3650 |
712
+ | Snapshots | Automatic snapshots | `rewind.autoSnapshots` | Off, On |
713
+ | Snapshots | Snapshots kept | `rewind.max` | Number, 0–1000 |
714
+ | Snapshots | Safety snapshot | `rewind.safetySnapshot` | Off, On |
715
+ | Jobs and watchers | Wake when a job finishes | `job.wakeOnFinish` | Off, On |
716
+ | Jobs and watchers | Keep jobs after exit | `job.detachedByDefault` | Off, On |
717
+ | Jobs and watchers | Wake on a match | `watch.wakeOnMatch` | Off, On |
718
+ | Memory | Inject notes | `memory.inject` | Off, On |
653
719
 
654
720
  Choosing **Currency** also writes that currency's default precision. Both rows
655
721
  stay editable afterwards, so the footer follows the explicit values.
656
722
 
657
723
  `Space` (or `Enter`) changes the focused row: a boolean toggles, an enum cycles
658
724
  forward, and a text row opens an inline editor on its value — the notification
659
- title, the probe timeout, and the rail glyph. The detail pane names the source, describes the preference, and shows
725
+ title, the probe timeout, the branch prefix, and the rail glyph. `u` deletes the
726
+ focused row's key so its default applies again (`Already at its default.` when
727
+ nothing is stored), `J` swaps the detail pane for the focused row's whole section
728
+ as the active scope stores it — the only view of the keys this screen leaves to
729
+ JSON — and `[` / `]` jump to the previous or next group. The detail pane names
730
+ the source, describes the preference, and shows
660
731
  where the value is written — `Global · ~/.pi/agent/settings.json`, `Project ·
661
732
  .pi/bytefly.json`, or `Inherited from Global · .pi/bytefly.json`. A successful
662
733
  change marks the screen `Saved · reload to apply` — even when the terminal is too
663
734
  short for the detail body — and closing offers the same
664
735
  reload confirm as an extension toggle.
665
736
 
737
+ Six of the ten groups belong to other extensions. The screen cannot import their
738
+ normalizers, so it reports the leaf as the file states it and declares the
739
+ loader's default; `test/settings-coverage.test.ts` holds the catalogue to every
740
+ loader's defaults and bounds in both directions.
741
+
666
742
  **Bytefly startup TUI** is the one row whose label differs from the Pi key it
667
743
  writes: On writes `"quietStartup": true` (bytefly's header replaces Pi's startup
668
744
  detail), and Off writes `false` (Pi's built-in header stays). A hand-set
@@ -671,17 +747,18 @@ with `true` or `false`.
671
747
 
672
748
  App rows are **Global only**: Pi's setters for them write the personal
673
749
  `settings.json`, and only resource paths have public project setters. Project
674
- scope therefore lists the thirteen `bytefly.json` rows and a
750
+ scope therefore lists the twenty-five `bytefly.json` rows and a
675
751
  `App settings are Global only.` note rather than promising a write that cannot
676
752
  happen. A `bytefly.json` row writes one leaf in the selected scope (a depth-1
677
753
  key, or a nested one such as `price.currency.decimals`) through a per-key
678
754
  writer, so every sibling key, section, `$schema`, and `version` is preserved. A
679
- malformed document is refused with a notice instead of overwritten.
755
+ malformed document is refused with a notice instead of overwritten, and `u`
756
+ removes that one leaf through the same writer's `unsetConfigValueAt`.
680
757
 
681
758
  The non-`tui` form prints the headline, the groups, and `Label: Value` rows:
682
759
 
683
760
  ```
684
- bytefly: Settings · Global · 16 settings
761
+ bytefly: Settings · Global · 28 settings
685
762
 
686
763
  Startup (1)
687
764
  Bytefly startup TUI: Off
@@ -966,6 +1043,9 @@ command.
966
1043
  - **Notifications are not focus-aware.** They fire whether or not the terminal
967
1044
  has focus, and once per trigger rather than once per unseen event. There is
968
1045
  no focus API to consult.
1046
+ - **`/compact` is Pi's block.** The summary component is built in, so bytefly
1047
+ cannot render, hide, or shorten it; the themes only choose its fill and label
1048
+ color. Branch summaries and skill invocations share the same theme tokens.
969
1049
  - **A background job cannot ask for input.** The `needsInput` trigger is a
970
1050
  prompt raised during a run, not a process waiting on stdin: jobs are
971
1051
  fire-and-observe, so that state does not exist.
@@ -973,11 +1053,11 @@ command.
973
1053
  host lacks (or an OSC method off a TTY) and the notification is dropped; only
974
1054
  `auto` inspects the environment. `doctor` now reports the mismatch as a
975
1055
  `degraded` finding, which says what to change but does not fix it for you.
976
- - **The working counter is one global row.** `setWorkingMessage` drives the
1056
+ - **The working line is one global row.** `setWorkingMessage` drives the
977
1057
  single working row, so bytefly and any other extension setting it race — last
978
1058
  writer wins — and the message is reset to Pi's default at `agent_end`. If the
979
- row is hidden (`ctx.ui.setWorkingVisible(false)`) the counter has no surface,
980
- and a reload mid-run does not resume it until the next turn.
1059
+ row is hidden (`ctx.ui.setWorkingVisible(false)`) the line has no surface,
1060
+ and a reload mid-run does not resume the count until the next turn.
981
1061
 
982
1062
  ## Non-goals
983
1063
 
@@ -1001,7 +1081,7 @@ command.
1001
1081
  match — are the whole surface.
1002
1082
  - No sound, action buttons, notification history, or de-duplication; a
1003
1083
  notification is a fire-and-forget message, not an inbox.
1004
- - No frozen per-run working duration: the counter is live and is handed back to
1084
+ - No frozen per-run working duration: the line is live and is handed back to
1005
1085
  Pi when the working row comes down, because the extension API exposes no hook
1006
1086
  to keep a finished run's total.
1007
1087
 
@@ -1014,18 +1094,18 @@ rather than showing a wrong value.
1014
1094
 
1015
1095
  | Integration point | Details |
1016
1096
  |---|---|
1017
- | Header | `ctx.ui.setHeader` with a custom component, replacing Pi's built-in header; installed only when `ctx.mode === "tui"` and `pi.getSettings().quietStartup === true` (the **Bytefly startup TUI** row). `false`, `"header"`, and unset call `setHeader(undefined)`, leaving Pi's built-in header in place. 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. The chrome version gains `↑<latest>` from the `UpdateSource`, and `registerHeader` returns a `refresh()` handle that re-installs the last context's header when that value arrives. |
1097
+ | Header | `ctx.ui.setHeader` with a custom component, replacing Pi's built-in header; installed only when `ctx.mode === "tui"` and `pi.getSettings().quietStartup === true` (the **Bytefly startup TUI** row). `false`, `"header"`, and unset call `setHeader(undefined)`, leaving Pi's built-in header in place. 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. The factory reads `tui.terminal.rows` on every render, so the height ladder in `header/config.ts` sheds help groups and then the leading blank row as the terminal shortens (unknown height keeps the full header). The chrome version gains `↑<latest>` from the `UpdateSource`, and `registerHeader` returns a `refresh()` handle that re-installs the last context's header when that value arrives. |
1018
1098
  | Footer | `ctx.ui.setFooter` with a custom component, replacing Pi's built-in footer; installed only when `ctx.mode === "tui"`. |
1019
1099
  | 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. |
1020
1100
  | 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`. `job` and `watch` post one when their work settles, and `doctor` posts one at `session_start` for a missing hard requirement. The type and shape come from `lib/notice.ts`. A message without `details` falls back to Pi's default custom-message box. |
1021
1101
  | 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. |
1022
1102
  | Screen command | `pi.registerCommand("bytefly")` takes `[doctor] [settings] [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, `run_doctor`'s report for `doctor`, or the grouped settings listing with values for `settings`. A named extension is checked against the discovered set before any probe. Tab completes the subcommands, extension names after `doctor`, and `refresh`; `/bytefly settings` rejects a name. |
1023
- | Turn separator | `registerEntryRenderer("bytefly-separator")` draws each rule; `agent_settled` appends `pi.appendEntry("bytefly-separator", { turn })` in `tui` mode only. |
1024
- | 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. |
1103
+ | Turn separator | `registerEntryRenderer("bytefly-separator")` draws each stored receipt; `agent_start` opens it, top-level `tool_execution_start`/`tool_execution_end` count tools and failures, and `agent_settled` appends `pi.appendEntry("bytefly-separator", { turn, durationMs, tools, failures, aborted })` in `tui` mode only. The receipt fields are omitted when no `agent_start` was seen, so an older or partially observed entry stays `{ turn }`. |
1104
+ | Working line | `turn_start` starts a 1s tick that drives `ctx.ui.setWorkingMessage` in `tui` mode; before a tool runs the message is `Working… Ns`, and each top-level `tool_execution_start` repaints it as `tool · N tools · Ns` (nested calls carrying `parentToolCallId` are skipped). `agent_end` restores Pi's default and cancels the tick (also on `session_shutdown`). No config file and no persisted state; the count lives in the register's closure. |
1025
1105
  | 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. |
1026
1106
  | Picker discovery | `SettingsManager.create(...)` plus `DefaultPackageManager.resolve({ onMissing: "skip" })`, both public exports; both views drop `bytefly`'s own entrypoint. |
1027
1107
  | Picker writes | `SettingsManager` setters (`setExtensionPaths`, `setPackages`, `setProjectExtensionPaths`, `setProjectPackages`) then `flush()`; `ctx.reload()` after the screen closes when changes are pending. |
1028
- | Settings writes | App rows call `SettingsManager` setters (`setQuietStartup`, `setOutputPad`, `setShowHardwareCursor`) on the global store and `flush()`, then surface `drainErrors()`; `bytefly.json` rows call `writeConfigValueAt` on the selected scope's document with the row's leaf path, so a nested key (`price.currency.decimals`) and a flat one (`notify.method`) share one writer. A failed or refused write shows a notice in the screen's hint row and leaves the displayed value unchanged; a successful one marks the screen dirty. |
1108
+ | Settings writes | App rows call `SettingsManager` setters (`setQuietStartup`, `setOutputPad`, `setShowHardwareCursor`) on the global store and `flush()`, then surface `drainErrors()`; `bytefly.json` rows call `writeConfigValueAt` on the selected scope's document with the row's leaf path, so a nested key (`price.currency.decimals`) and a flat one (`notify.method`) share one writer, and `u` calls `unsetConfigValueAt` with the same path. A failed or refused write shows a notice in the screen's hint row and leaves the displayed value unchanged; a successful one marks the screen dirty. |
1029
1109
  | Doctor tool | `pi.registerTool("run_doctor")`, read-only (`readOnlyHint: true`, `openWorldHint: false`), direct exposure and active on registration (the defaults, so not declared), 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. |
1030
1110
  | Doctor probes | `run_doctor` and the screen share the session cache created by `registerDoctor`; the screen mounts on that cache and probes on the first Extensions view (the command's `refresh` forces it), while the text modes probe before printing, and `/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. |
1031
1111
  | 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. |
@@ -1118,8 +1198,10 @@ rather than showing a wrong value.
1118
1198
  and a grouped help block. It takes its margin from Pi's `outputPad`, like the
1119
1199
  tool frame and the notice, and hangs the help one body indent inside it, so the
1120
1200
  header follows a non-default pad instead of assuming a column. It adds no
1121
- session or model data — that is the footer's job — and it stays a fixed
1122
- four-plus rows so the startup transcript never reflows.
1201
+ session or model data — that is the footer's job — and its shape follows the
1202
+ terminal: the height ladder drops whole help groups, then the leading blank
1203
+ row, before the nameplate gives up anything, so a short pane spends its rows
1204
+ on the transcript instead of chrome.
1123
1205
  - **Two lines, four zones.** Line 1 is identity (`pwd` left, branch + worktree
1124
1206
  right); line 2 is resources (meters left, model + level right). This keeps
1125
1207
  "where am I / what state" separate from "how much am I burning".
@@ -1274,7 +1356,7 @@ rather than showing a wrong value.
1274
1356
  The surfaces are folders: `header/`, `footer/` (the status bar and its price
1275
1357
  calculation), `message/` (the user-message marker), `notice/` (the shared notice),
1276
1358
  `block/` (the tool-block frame), `separator/`, `working/` (the live
1277
- elapsed counter on Pi's working row), `panel/` (the
1359
+ run line on Pi's working row), `panel/` (the
1278
1360
  unified screen and its merged model),
1279
1361
  `picker/` (discovery and the settings store), `doctor/` (the `run_doctor` tool
1280
1362
  and its probe runtime), `notify/`, and `update/` (the published-version check
@@ -1316,14 +1398,14 @@ columns, gauge), so the look follows the active theme.
1316
1398
  | `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. |
1317
1399
  | `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. |
1318
1400
  | `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). |
1319
- | `separator/config.ts` | Turn-rule tuning: `customType`, glyph, label prefix, padding, and roles. |
1320
- | `separator/register.ts` | Registers the entry renderer and appends a separator on `agent_settled`. |
1401
+ | `separator/config.ts` | Turn-rule tuning: `customType`, glyph, label prefix, padding, receipt minimum duration and outcome wording, and roles. |
1402
+ | `separator/register.ts` | Registers the entry renderer, tracks the run's tools and failures, and appends the receipt on `agent_settled`. |
1321
1403
  | `separator/component.ts` | `SeparatorComponent`: the stateless entry renderer. |
1322
- | `separator/format.ts` | Pure `separatorLine`: the centered turn rule, drawn by the kit's `centeredRule`. |
1323
- | `separator/types.ts` | `TurnSeparatorData`. |
1324
- | `working/config.ts` | Counter tuning (`WORKING_CONFIG`): the word, the ellipsis, and the tick interval. |
1325
- | `working/format.ts` | Pure `formatElapsed` (whole seconds) and the working-message shape. |
1326
- | `working/register.ts` | Starts the tick on `turn_start` and cancels it on `agent_end`/`session_shutdown`. |
1404
+ | `separator/format.ts` | Pure `receiptLabel` (the width ladder), `receiptRole`, and `separatorLine`, drawn by the kit's `centeredRule`. |
1405
+ | `separator/types.ts` | `TurnSeparatorData`: the turn plus the optional receipt fields. |
1406
+ | `working/config.ts` | Counter tuning (`WORKING_CONFIG`): the words, the tool singular/plural, and the tick interval. |
1407
+ | `working/format.ts` | Pure `formatElapsed` (whole seconds) and the working-message shape: the plain count and the `tool · N tools · elapsed` run line. |
1408
+ | `working/register.ts` | Starts the tick on `turn_start`, tracks top-level tools on `tool_execution_start`, and cancels on `agent_end`/`session_shutdown`. |
1327
1409
  | `panel/types.ts` | `PanelSection` (`extensions` | `settings`), `PanelRow`, and `PanelModel`, the merged row model. |
1328
1410
  | `panel/join.ts` | The item/report union with the unambiguous-name health rule and unique row keys, groups, and ordering. |
1329
1411
  | `panel/format.ts` | Non-TUI text: the extension headline and grouped listing with a per-row health suffix, plus the section segments, the Settings counts/headline, and the grouped Settings listing. |
@@ -1375,10 +1457,10 @@ folding across a width sweep from 6 to 120 plus wide characters
1375
1457
  (`footer/render/layout`), exact
1376
1458
  golden output at fixed widths (`footer/render/render`), and the install/skip/
1377
1459
  restore/toggle lifecycle plus a config-driven price (`extension`). The turn
1378
- separator
1379
- is covered by its pure formatting (`separator/format`) and its
1380
- register/append/render/disable lifecycle (`separator/register`). The picker
1381
- helpers are covered by the pure helpers (`picker/resources`), the toggle
1460
+ separator is covered by its pure formatting (`separator/format`, including the
1461
+ receipt's width ladder), its register/append/render/disable lifecycle
1462
+ (`separator/register`), and the run stats it records. The picker helpers are
1463
+ covered by the pure helpers (`picker/resources`), the toggle
1382
1464
  transforms (`picker/toggle`), discovery with an injected factory
1383
1465
  (`picker/discovery`), and the runtime state machine (`picker/runtime`). The
1384
1466
  unified screen is covered by its pure model merge (`panel/join`), its text
@@ -14,6 +14,9 @@ import { clampInteger, loadConfigSection } from "../../../lib/config.ts";
14
14
  /** How long a single probe may take before it counts as unusable. */
15
15
  const DEFAULT_TIMEOUT_MS = 2_000;
16
16
 
17
+ /** The probe deadline range, in milliseconds; the panel's editor offers the same bounds. */
18
+ export const TIMEOUT_MS_RANGE = { min: 100, max: 30_000 } as const;
19
+
17
20
  export interface DoctorConfig {
18
21
  /** Probe enabled extensions at session start and warn when one is broken. */
19
22
  proactive: boolean;
@@ -34,7 +37,7 @@ export function normalizeConfig(raw: Record<string, unknown> | undefined, base:
34
37
  if (!raw) return base;
35
38
  return {
36
39
  proactive: typeof raw.proactive === "boolean" ? raw.proactive : base.proactive,
37
- timeoutMs: clampInteger(raw.timeoutMs, base.timeoutMs, 100, 30_000),
40
+ timeoutMs: clampInteger(raw.timeoutMs, base.timeoutMs, TIMEOUT_MS_RANGE.min, TIMEOUT_MS_RANGE.max),
38
41
  skip: Array.isArray(raw.skip)
39
42
  ? raw.skip.filter((name): name is string => typeof name === "string" && name.trim().length > 0)
40
43
  : base.skip,
@@ -101,9 +101,10 @@ export const DEFAULT_BYTEFLY_CONFIG: ByteflyConfig = {
101
101
  budget: 0,
102
102
  };
103
103
 
104
- const MAX_DECIMALS = 4;
104
+ /** The highest `currency.decimals` the screen and the loader both accept. */
105
+ export const MAX_DECIMALS = 4;
105
106
  /** Upper bound for `budget`; large enough for any real session, small enough to clamp a typo. */
106
- const MAX_BUDGET = 10_000_000;
107
+ export const MAX_BUDGET = 10_000_000;
107
108
  const MAX_RATE = 1_000_000;
108
109
  const MAX_RATE_KEYS = 200;
109
110
  const MAX_WINDOWS = 16;