@gavin-hu/my-pi-agent 0.12.0 → 0.13.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 -21
- package/README.zh-CN.md +30 -22
- package/extensions/bytefly/README.md +139 -67
- package/extensions/bytefly/chrome.ts +7 -2
- package/extensions/bytefly/doctor/register.ts +6 -0
- package/extensions/bytefly/doctor/runtime.ts +36 -6
- package/extensions/bytefly/footer/collect/snapshot.ts +12 -2
- package/extensions/bytefly/footer/config.ts +7 -1
- package/extensions/bytefly/footer/delegation.ts +100 -0
- package/extensions/bytefly/footer/register.ts +13 -2
- package/extensions/bytefly/footer/render/lines.ts +2 -0
- package/extensions/bytefly/footer/render/segments.ts +27 -0
- package/extensions/bytefly/footer/types.ts +11 -0
- package/extensions/bytefly/index.ts +4 -3
- package/extensions/bytefly/notify/config.ts +2 -0
- package/extensions/bytefly/notify/format.ts +19 -1
- package/extensions/bytefly/notify/register.ts +37 -13
- package/extensions/bytefly/notify/types.ts +2 -0
- package/extensions/bytefly/panel/commands.ts +34 -13
- package/extensions/bytefly/panel/settings.ts +98 -16
- package/extensions/bytefly/update/cache.ts +73 -0
- package/extensions/bytefly/update/format.ts +65 -10
- package/extensions/bytefly/update/register.ts +99 -13
- package/extensions/bytefly/update/state.ts +13 -1
- package/extensions/document/README.md +22 -12
- package/extensions/document/config.ts +4 -0
- package/extensions/document/deadline.ts +61 -0
- package/extensions/document/tool.ts +13 -2
- package/extensions/file-browse/README.md +12 -8
- package/extensions/file-browse/client.ts +6 -7
- package/extensions/file-browse/files.ts +0 -4
- package/extensions/file-browse/git.ts +9 -10
- package/extensions/file-browse/html.ts +17 -7
- package/extensions/file-browse/router.ts +24 -15
- package/extensions/goal/README.md +7 -1
- package/extensions/goal/schema.ts +6 -2
- package/extensions/goal/types.ts +6 -2
- package/extensions/job/README.md +16 -12
- package/extensions/job/runtime.ts +23 -6
- package/extensions/job/status.ts +4 -2
- package/extensions/memory/README.md +55 -13
- package/extensions/memory/commands.ts +20 -2
- package/extensions/memory/doctor.ts +137 -0
- package/extensions/memory/format.ts +24 -7
- package/extensions/memory/runtime.ts +49 -0
- package/extensions/memory/store.ts +31 -0
- package/extensions/memory/tui.ts +136 -12
- package/extensions/plan/README.md +60 -29
- package/extensions/plan/commands.ts +67 -8
- package/extensions/plan/index.ts +35 -11
- package/extensions/plan/plan-index.ts +234 -0
- package/extensions/plan/plan-text.ts +95 -0
- package/extensions/plan/plans.ts +90 -1
- package/extensions/plan/review.ts +12 -9
- package/extensions/plan/runtime.ts +72 -27
- package/extensions/plan/steps.ts +35 -25
- package/extensions/plan/todos.ts +24 -2
- package/extensions/plan/tools.ts +124 -24
- package/extensions/plan/tui.ts +7 -28
- package/extensions/plan/types.ts +9 -0
- package/extensions/plan/web-page.ts +2 -2
- package/extensions/plan/web.ts +12 -11
- package/extensions/rewind/README.md +28 -8
- package/extensions/rewind/commands.ts +1 -1
- package/extensions/rewind/format.ts +4 -4
- package/extensions/rewind/git.ts +56 -18
- package/extensions/rewind/index.ts +3 -5
- package/extensions/rewind/path-picker.ts +198 -0
- package/extensions/rewind/paths.ts +128 -0
- package/extensions/rewind/restore.ts +69 -28
- package/extensions/rewind/rewind.ts +80 -3
- package/extensions/rewind/runtime.ts +13 -5
- package/extensions/rewind/snapshot-run.ts +5 -2
- package/extensions/rewind/types.ts +2 -0
- package/extensions/subagent/README.md +7 -1
- package/extensions/subagent/accounting.ts +34 -0
- package/extensions/subagent/index.ts +23 -1
- package/extensions/subagent/stream.ts +2 -0
- package/extensions/subagent/types.ts +2 -0
- package/extensions/watch/README.md +8 -3
- package/extensions/watch/index.ts +19 -0
- package/extensions/web-access/README.md +43 -13
- package/extensions/web-access/charset.ts +58 -0
- package/extensions/web-access/fetch/find.ts +11 -7
- package/extensions/web-access/fetch/format.ts +64 -18
- package/extensions/web-access/fetch/page.ts +5 -1
- package/extensions/web-access/fetch/tool.ts +19 -18
- package/extensions/web-access/http.ts +49 -8
- package/extensions/web-access/search/facts.ts +38 -0
- package/extensions/web-access/search/tool.ts +13 -0
- package/extensions/web-access/text.ts +50 -11
- package/extensions/worktree/README.md +74 -15
- package/extensions/worktree/config.ts +11 -0
- package/extensions/worktree/exit.ts +35 -3
- package/extensions/worktree/gaps.ts +74 -0
- package/extensions/worktree/guard.ts +50 -4
- package/extensions/worktree/handoff.ts +55 -0
- package/extensions/worktree/index.ts +12 -2
- package/extensions/worktree/runtime.ts +20 -0
- package/extensions/worktree/status.ts +15 -2
- package/extensions/worktree/tools.ts +3 -2
- package/lib/README.md +10 -8
- package/lib/events.ts +86 -1
- package/lib/goal-state.ts +95 -0
- package/lib/requirements.ts +282 -62
- package/lib/tui/README.md +91 -0
- package/lib/tui/glyphs.ts +1 -0
- package/lib/web/layout.ts +12 -2
- package/package.json +5 -3
- package/themes/bytefly-dark.json +1 -1
- package/assets/chrome.png +0 -0
- package/assets/file-browse.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-cancelled.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-long-subject.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-running.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-rejected.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
|
@@ -69,13 +69,19 @@ in [`header/config.ts`](./header/config.ts).
|
|
|
69
69
|
- **Line 1 — identity:** `pwd` and the session name on the left; git state
|
|
70
70
|
(`⎇ branch · ⑂ worktree`) and the `◉` browse chip on the right.
|
|
71
71
|
- **Line 2 — resources:** the context gauge and usage meters on the left, the
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
child-run roll-up (`↳ 3 · ¥0.12`, present only once a subagent has finished),
|
|
73
|
+
the mode/alert slot (`⋮ plan`) trailing after a `╎` when present, and the model
|
|
74
|
+
and thinking level on the right. The gauge is anchored at column 0, so `%` is
|
|
74
75
|
always in the same place.
|
|
76
|
+
The roll-up is its own number, never folded into the session's cost: a subagent
|
|
77
|
+
runs in its own process, and the footer prices those runs with the same rate
|
|
78
|
+
table and currency as the session so a currency change moves both. It covers
|
|
79
|
+
the last 500 child runs of the current process, and starts from zero after
|
|
80
|
+
`/resume` (the events are not session entries).
|
|
75
81
|
|
|
76
82
|
```
|
|
77
|
-
~/repo/my-pi-agent
|
|
78
|
-
▰▰▰▰▰▰▱▱▱▱ 62%/200k · ¥0.31 · ↑42k ↓8.0k · R96k CH 87% ╎ ⋮ plan opus-4.5 · high
|
|
83
|
+
~/repo/my-pi-agent ⎇ main · ⑂ smoke
|
|
84
|
+
▰▰▰▰▰▰▱▱▱▱ 62%/200k · ¥0.31 · ↳ 2 · ¥0.06 · ↑42k ↓8.0k · R96k CH 87% ╎ ⋮ plan opus-4.5 · high
|
|
79
85
|
```
|
|
80
86
|
|
|
81
87
|
(The examples show the default `¥` symbol. A model that neither `rates` nor the
|
|
@@ -266,21 +272,25 @@ underneath, and `run_doctor` returns the same report as text and typed data.
|
|
|
266
272
|
Only a missing
|
|
267
273
|
*hard* requirement posts a notice at session start; everything else waits in the
|
|
268
274
|
report.
|
|
269
|
-
See [The
|
|
275
|
+
See [The control center](#the-control-center).
|
|
270
276
|
|
|
271
277
|
**Notify.** Off by default, one `notify` section in `bytefly.json` turns on OS
|
|
272
278
|
notifications for unattended runs: when a full agent run settles, when a
|
|
273
|
-
background job finishes,
|
|
279
|
+
background job finishes, when the agent opens a prompt asking for input, and when
|
|
280
|
+
a `watch` wake match fires while the agent is idle. The
|
|
274
281
|
method follows the terminal (OSC 777, OSC 99, a Windows toast) or falls back to
|
|
275
282
|
the platform notifier (`osascript`, `notify-send`); see
|
|
276
283
|
[Notifications](#notifications).
|
|
277
284
|
|
|
278
|
-
**Update check.**
|
|
285
|
+
**Update check.** In the background at an interactive session's start, the loaded
|
|
279
286
|
version is compared with the published `@gavin-hu/my-pi-agent` on npm. A newer
|
|
280
287
|
one puts `↑0.12.0` after the header's chrome version and posts one transcript
|
|
281
|
-
notice naming `pi update --extensions
|
|
282
|
-
|
|
283
|
-
|
|
288
|
+
notice naming `pi update --extensions` and its release URL. The answer is cached
|
|
289
|
+
in `<agentDir>/bytefly/update.json` with the time it was read, so a session
|
|
290
|
+
starting inside the 24-hour window reuses it instead of repeating the request,
|
|
291
|
+
and `/bytefly update` re-runs the check on demand. The check is skipped when
|
|
292
|
+
`PI_OFFLINE` is set, makes no request outside `tui`, and a failed background
|
|
293
|
+
request is silent; see [Update check](#update-check).
|
|
284
294
|
|
|
285
295
|
## Commands
|
|
286
296
|
|
|
@@ -289,9 +299,10 @@ is set, makes no request outside `tui`, and a failed request is silent; see
|
|
|
289
299
|
| `/bytefly [extension] [refresh]` | Open the control center on Extensions. In `tui` it mounts a docked list; every other mode prints the grouped listing, or one extension's full findings when named. |
|
|
290
300
|
| `/bytefly settings` | Open the control center on Settings. Outside `tui` it prints the grouped settings listing with each row's value. |
|
|
291
301
|
| `/bytefly doctor [extension] [refresh]` | Print the doctor report. Outside `tui` it prints `run_doctor`'s text for every extension, or one named extension's findings; in `tui` it mounts the same docked screen. |
|
|
302
|
+
| `/bytefly update` | Re-run the published-version check now, ignoring the cache window, and report what it found — the newest version, an available release with its URL, `PI_OFFLINE`, or an unreadable registry. It resolves no packages and probes no extension. |
|
|
292
303
|
|
|
293
304
|
Tab completes the subcommands, extension names after `doctor`, and `refresh`;
|
|
294
|
-
`/bytefly settings`
|
|
305
|
+
`/bytefly settings` and `/bytefly update` take no name.
|
|
295
306
|
|
|
296
307
|
## Behaviour by mode
|
|
297
308
|
|
|
@@ -328,7 +339,7 @@ separators, icons, status keys), and the turn separator in
|
|
|
328
339
|
colors).
|
|
329
340
|
|
|
330
341
|
Every key of every section is listed with its type, default, and bounds in the
|
|
331
|
-
[configuration reference](../../docs/references/
|
|
342
|
+
[configuration reference](../../docs/references/bytefly-config.md); this section
|
|
332
343
|
covers what that table cannot — when each section is read, and what the cost
|
|
333
344
|
does when no rate matches.
|
|
334
345
|
|
|
@@ -392,7 +403,7 @@ named by `doctor`; a wrong-typed value falls back to its default, and a
|
|
|
392
403
|
wrong-typed timezone drops the peak schedule rather than the session.
|
|
393
404
|
|
|
394
405
|
The notify capability is **off by default**: `enabled: true` is the whole setup
|
|
395
|
-
needed for an unattended run, and the
|
|
406
|
+
needed for an unattended run, and the four trigger flags then narrow what is
|
|
396
407
|
announced (see [Notifications](#notifications)).
|
|
397
408
|
|
|
398
409
|
The proactive doctor check is part of the bytefly startup TUI, so it posts only
|
|
@@ -574,20 +585,25 @@ installs or fetches a package.
|
|
|
574
585
|
without leaving the session.
|
|
575
586
|
|
|
576
587
|
**Settings.** The second section uses the same frame and the same `Tab` scope
|
|
577
|
-
switch, with its own cursor. It lists
|
|
588
|
+
switch, with its own cursor. It lists fifteen preferences in six groups:
|
|
578
589
|
|
|
579
590
|
```
|
|
580
591
|
bytefly ──────────────────────────────────────────────────────────────
|
|
581
|
-
Extensions · Settings Global ·
|
|
592
|
+
Extensions · Settings Global · 15 settings
|
|
582
593
|
|
|
583
594
|
Startup (1)
|
|
584
595
|
❯ ◻ Bytefly startup TUI Off
|
|
585
|
-
Notifications (
|
|
596
|
+
Notifications (7)
|
|
586
597
|
◻ Desktop notifications Off
|
|
598
|
+
◼ Turn finished On
|
|
599
|
+
◼ Needs input On
|
|
600
|
+
◼ Job finished On
|
|
601
|
+
◼ Watcher matched On
|
|
587
602
|
Delivery method Auto
|
|
588
603
|
Notification title Pi
|
|
589
|
-
Diagnostics (
|
|
604
|
+
Diagnostics (2)
|
|
590
605
|
◼ Proactive health check On
|
|
606
|
+
Probe timeout 2000
|
|
591
607
|
Pricing (2)
|
|
592
608
|
Currency CNY (¥)
|
|
593
609
|
Cost decimals 2
|
|
@@ -607,12 +623,17 @@ bytefly ────────────────────────
|
|
|
607
623
|
|---|---|---|---|
|
|
608
624
|
| Startup | Bytefly startup TUI | Pi `settings.json` (`quietStartup`) | Off, On |
|
|
609
625
|
| Notifications | Desktop notifications | `notify.enabled` | Off, On |
|
|
626
|
+
| Notifications | Turn finished | `notify.turnFinished` | Off, On |
|
|
627
|
+
| Notifications | Needs input | `notify.needsInput` | Off, On |
|
|
628
|
+
| Notifications | Job finished | `notify.jobFinished` | Off, On |
|
|
629
|
+
| Notifications | Watcher matched | `notify.matchFound` | Off, On |
|
|
610
630
|
| Notifications | Delivery method | `notify.method` | Auto, OSC 777, OSC 99, Windows toast, macOS, Linux |
|
|
611
631
|
| Notifications | Notification title | `notify.title` | Text, up to 40 characters |
|
|
612
632
|
| Diagnostics | Proactive health check | `doctor.proactive` | Off, On |
|
|
633
|
+
| Diagnostics | Probe timeout | `doctor.timeoutMs` | Number, 100–30000 ms |
|
|
613
634
|
| Pricing | Currency | `price.currency.code` | CNY, USD |
|
|
614
635
|
| Pricing | Cost decimals | `price.currency.decimals` | 0, 1, 2, 3, 4 |
|
|
615
|
-
| Transcript | Rail glyph | `chrome.rail` |
|
|
636
|
+
| Transcript | Rail glyph | `chrome.rail` | Text, exactly one column |
|
|
616
637
|
| Transcript | Output padding | Pi `settings.json` | 0, 1 |
|
|
617
638
|
| Terminal | Hardware cursor | Pi `settings.json` | Off, On |
|
|
618
639
|
|
|
@@ -620,7 +641,8 @@ Choosing **Currency** also writes that currency's default precision. Both rows
|
|
|
620
641
|
stay editable afterwards, so the footer follows the explicit values.
|
|
621
642
|
|
|
622
643
|
`Space` (or `Enter`) changes the focused row: a boolean toggles, an enum cycles
|
|
623
|
-
forward, and a text row opens an inline editor on its value
|
|
644
|
+
forward, and a text row opens an inline editor on its value — the notification
|
|
645
|
+
title, the probe timeout, and the rail glyph. The detail pane names the source, describes the preference, and shows
|
|
624
646
|
where the value is written — `Global · ~/.pi/agent/settings.json`, `Project ·
|
|
625
647
|
.pi/bytefly.json`, or `Inherited from Global · .pi/bytefly.json`. A successful
|
|
626
648
|
change marks the screen `Saved · reload to apply` — even when the terminal is too
|
|
@@ -635,7 +657,7 @@ with `true` or `false`.
|
|
|
635
657
|
|
|
636
658
|
App rows are **Global only**: Pi's setters for them write the personal
|
|
637
659
|
`settings.json`, and only resource paths have public project setters. Project
|
|
638
|
-
scope therefore lists the
|
|
660
|
+
scope therefore lists the twelve `bytefly.json` rows and a
|
|
639
661
|
`App settings are Global only.` note rather than promising a write that cannot
|
|
640
662
|
happen. A `bytefly.json` row writes one leaf in the selected scope (a depth-1
|
|
641
663
|
key, or a nested one such as `price.currency.decimals`) through a per-key
|
|
@@ -645,14 +667,19 @@ malformed document is refused with a notice instead of overwritten.
|
|
|
645
667
|
The non-`tui` form prints the headline, the groups, and `Label: Value` rows:
|
|
646
668
|
|
|
647
669
|
```
|
|
648
|
-
bytefly: Settings · Global ·
|
|
670
|
+
bytefly: Settings · Global · 15 settings
|
|
649
671
|
|
|
650
672
|
Startup (1)
|
|
651
673
|
Bytefly startup TUI: Off
|
|
652
674
|
|
|
653
|
-
Notifications (
|
|
675
|
+
Notifications (7)
|
|
654
676
|
Desktop notifications: Off
|
|
677
|
+
Turn finished: On
|
|
678
|
+
Needs input: On
|
|
679
|
+
Job finished: On
|
|
680
|
+
Watcher matched: On
|
|
655
681
|
Delivery method: Auto
|
|
682
|
+
Notification title: Pi
|
|
656
683
|
...
|
|
657
684
|
```
|
|
658
685
|
|
|
@@ -664,6 +691,14 @@ means reduced capability — an optional package that is absent, a config file
|
|
|
664
691
|
that fails to parse, a login that has not happened. Doctor never changes
|
|
665
692
|
anything and never installs anything.
|
|
666
693
|
|
|
694
|
+
`web-access` publishes what it resolved at `session_start`, so the report names
|
|
695
|
+
the search provider and why it won (an explicit choice, the `auto` rule, or the
|
|
696
|
+
keyless default), and — when the provider takes a key and no literal `apiKey` is
|
|
697
|
+
set — its API-key variable and whether that variable is set. The key value is
|
|
698
|
+
never part of the report, only its variable name. Reachability is the one check
|
|
699
|
+
that touches the network, so it runs only on an explicit `/bytefly refresh`:
|
|
700
|
+
a session-start check stays offline.
|
|
701
|
+
|
|
667
702
|
`/bytefly <extension>` narrows the same screen to that extension; `Enter`
|
|
668
703
|
expands its findings, including the checks that passed, into the free height.
|
|
669
704
|
`/bytefly refresh` ignores the findings cached earlier in the session. Every
|
|
@@ -718,7 +753,7 @@ the same entry in `lib/requirements.ts`, so the two cannot drift.
|
|
|
718
753
|
|
|
719
754
|
The notify capability exists for unattended runs: start a long turn, leave the
|
|
720
755
|
terminal, and get a desktop notification when Pi needs you — or when it is done.
|
|
721
|
-
It fires on
|
|
756
|
+
It fires on four events, each independently switchable in the `notify` section
|
|
722
757
|
of `bytefly.json` (see [Configuration](#configuration)):
|
|
723
758
|
|
|
724
759
|
- **A run settled** (`agent_settled`). Pi finished the turn and waits for the
|
|
@@ -731,6 +766,12 @@ of `bytefly.json` (see [Configuration](#configuration)):
|
|
|
731
766
|
- **A background job finished.** The [job](../job/) extension announces each
|
|
732
767
|
terminal transition on the shared `pi.events` bus
|
|
733
768
|
(`my-pi-agent/job-finished`); bytefly listens and never imports it.
|
|
769
|
+
- **A watcher matched.** A [watch](../watch/) whose `wake` is on matched while the
|
|
770
|
+
agent was idle, and announced it on `my-pi-agent/watch-match` with the cycle's
|
|
771
|
+
count, the watcher, and its newest line. The body names that line, sanitized and
|
|
772
|
+
clipped to 80 characters, because the line is the alert: a watch usually tails a
|
|
773
|
+
build or an error log. A match the agent was busy for does not notify — it is
|
|
774
|
+
collected for the next turn instead.
|
|
734
775
|
|
|
735
776
|
### Delivery method
|
|
736
777
|
|
|
@@ -777,6 +818,7 @@ reports it once, in the two places it already owns:
|
|
|
777
818
|
bytefly · 0.12.0 is available
|
|
778
819
|
╎ ◌ current · v0.11.4
|
|
779
820
|
╎ ◌ update · pi update --extensions
|
|
821
|
+
╎ ◌ release · https://github.com/gavin-hu/my-pi-agent/releases/tag/v0.12.0
|
|
780
822
|
```
|
|
781
823
|
|
|
782
824
|
The marker is the shared `↑` glyph and the newest version — the arrow is one cell
|
|
@@ -796,10 +838,27 @@ fired without awaiting at `session_start` (Pi awaits handlers, and a five-second
|
|
|
796
838
|
registry call must not hold the first prompt) with a five-second abort. Only a
|
|
797
839
|
strictly newer, parseable semver announces anything: a non-ok status, a body
|
|
798
840
|
that is not JSON, a missing version, a network error, or an older or equal
|
|
799
|
-
version is silent.
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
841
|
+
version is silent. `PI_OFFLINE` (Pi's own switch) turns the check off;
|
|
842
|
+
`PI_SKIP_VERSION_CHECK` only governs Pi's `pi.dev` request.
|
|
843
|
+
|
|
844
|
+
The answer is cached in `<agentDir>/bytefly/update.json` as `latest` plus the
|
|
845
|
+
epoch milliseconds it was read at. A session that starts inside the 24-hour
|
|
846
|
+
window reuses that entry — painting the marker and posting the notice from it —
|
|
847
|
+
instead of repeating the request, so a working day of sessions costs one read.
|
|
848
|
+
Past the window the check runs again and rewrites the entry; a failed read writes
|
|
849
|
+
nothing, so the next session tries again. Offline, the cached entry is used with
|
|
850
|
+
no request and no write, because a stale version is still the best answer
|
|
851
|
+
available and the marker is advisory.
|
|
852
|
+
|
|
853
|
+
`/bytefly update` is the on-demand half: it ignores the window, reads the registry
|
|
854
|
+
now, records and paints the result, and prints one line — `up to date — v0.12.0
|
|
855
|
+
is the newest published version`, `v0.13.0 is available (current 0.12.0) —
|
|
856
|
+
https://…/releases/tag/v0.13.0`, `the update check was skipped (PI_OFFLINE is
|
|
857
|
+
set)`, or `could not read the published version`. It resolves no packages, probes
|
|
858
|
+
no extension, and posts no second notice, so a session open for days can see a
|
|
859
|
+
release without a restart. Opening the control center never checks: the screen has
|
|
860
|
+
no update surface, and the only request paths are `session_start` and this
|
|
861
|
+
command.
|
|
803
862
|
|
|
804
863
|
## Limitations
|
|
805
864
|
|
|
@@ -814,15 +873,16 @@ switch) turns the check off; `PI_SKIP_VERSION_CHECK` only governs Pi's
|
|
|
814
873
|
- **Project overrides of app settings are not shown.** App rows read the
|
|
815
874
|
personal file; a `.pi/settings.json` override still wins at runtime, and
|
|
816
875
|
`pi config` is the place to manage it.
|
|
817
|
-
- **
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
- **`doctor`
|
|
824
|
-
report, so an extension literally named
|
|
825
|
-
name
|
|
876
|
+
- **Arrays and tables stay JSON.** A row is one boolean, enum, string, or number.
|
|
877
|
+
`doctor.skip` (an array), the `rates` table, and `price.peak` (a nested object)
|
|
878
|
+
are still `bytefly.json` edits; every scalar preference has a row.
|
|
879
|
+
- **A custom rail glyph must be one column.** The editor refuses anything else,
|
|
880
|
+
because a two-column glyph would push every railed body one cell right; the
|
|
881
|
+
former four-glyph menu is now the description's example list.
|
|
882
|
+
- **`doctor`, `settings`, and `update` are reserved first arguments.**
|
|
883
|
+
`/bytefly doctor` always selects the report, so an extension literally named
|
|
884
|
+
`doctor` cannot be narrowed to by name; `settings` and `update` are reserved
|
|
885
|
+
the same way.
|
|
826
886
|
- **The picker manages only the `extensions` resource type.** Skills, prompts,
|
|
827
887
|
and themes are handled by Pi's own `pi config`.
|
|
828
888
|
- **Built-in extensions are not listed** (`builtin:mcp`, `builtin:codemode`, …):
|
|
@@ -833,17 +893,19 @@ switch) turns the check off; `PI_SKIP_VERSION_CHECK` only governs Pi's
|
|
|
833
893
|
published under another tag, or a checkout ahead of npm reports nothing, and
|
|
834
894
|
there is no channel or prerelease preference: `0.11.4` is behind `0.12.0`
|
|
835
895
|
whether or not a `0.12.0-beta.1` exists.
|
|
836
|
-
- **
|
|
837
|
-
|
|
838
|
-
|
|
896
|
+
- **The cache window is fixed at 24 hours.** It runs at `session_start` in `tui`
|
|
897
|
+
mode and reuses an entry younger than that; there is no setting for it, and
|
|
898
|
+
`/bytefly` and `doctor` do not report the version. A failed or slow background
|
|
899
|
+
request is retried by the next session, or immediately by `/bytefly update`.
|
|
839
900
|
- **The marker needs the bytefly startup TUI.** It lives on the custom header,
|
|
840
901
|
so with `quietStartup` off the notice is the whole signal.
|
|
841
902
|
- **Pi may report the same update.** An npm-installed copy can also show Pi's
|
|
842
903
|
own `Package Updates Available` block; the two checks are independent and
|
|
843
904
|
`pi update --extensions` satisfies both.
|
|
844
|
-
- **No auto-update and
|
|
845
|
-
of running one, and the notice renderer
|
|
846
|
-
changelog
|
|
905
|
+
- **No auto-update, and the release URL is text only.** The notice names a
|
|
906
|
+
command instead of running one, and the notice renderer cannot open a link, so
|
|
907
|
+
the release row prints the URL and nothing more — no changelog body, no way to
|
|
908
|
+
click through from the transcript.
|
|
847
909
|
- **Project toggles are two-state per press** (`enable`/`disable`). `x` clears
|
|
848
910
|
the override, which is the third state; a resource with no project entry in
|
|
849
911
|
`pi config` is still managed there.
|
|
@@ -915,8 +977,8 @@ switch) turns the check off; `PI_SKIP_VERSION_CHECK` only governs Pi's
|
|
|
915
977
|
- No status chip for the doctor report: it is on demand plus one notice, not a
|
|
916
978
|
permanent footer segment.
|
|
917
979
|
- No notification for individual tool calls, message turns, or subagent steps:
|
|
918
|
-
the
|
|
919
|
-
whole surface.
|
|
980
|
+
the four triggers — a settled run, a prompt mid-run, a finished job, a watcher
|
|
981
|
+
match — are the whole surface.
|
|
920
982
|
- No sound, action buttons, notification history, or de-duplication; a
|
|
921
983
|
notification is a fire-and-forget message, not an inbox.
|
|
922
984
|
- No frozen per-run working duration: the counter is live and is handed back to
|
|
@@ -947,10 +1009,10 @@ rather than showing a wrong value.
|
|
|
947
1009
|
| 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. |
|
|
948
1010
|
| 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. |
|
|
949
1011
|
| 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. |
|
|
950
|
-
| Update check | `session_start` in `tui` mode, when `
|
|
951
|
-
| 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. The update check has no section: it is on whenever `PI_OFFLINE` is off. |
|
|
1012
|
+
| Update check | `session_start` in `tui` mode reads `<agentDir>/bytefly/update.json` and, when the entry is inside `UPDATE_CACHE_TTL_MS` (24 h), records it without a request; otherwise it fires `fetchLatestVersion()` without awaiting, so the handler never holds the prompt, and writes the answer back. The registry read is a fixed `GET` of npm's `latest` document over an injected `fetch`, bounded by an abort timeout; a strictly newer, parseable semver is recorded in the per-load `UpdateSource`, `onUpdate` re-installs the header, and `pi.sendMessage(updateNotice(...))` posts the one notice with its release URL. `PI_OFFLINE` uses the cache with no request and no write. `/bytefly update` calls the returned `UpdateHandle.recheck()`, which bypasses the window, records, paints, and returns an `UpdateOutcome`; a generation counter drops a background reply that lands after `session_shutdown`, and a per-load flag keeps it to one announcement. |
|
|
1013
|
+
| 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. The update check has no section: it is on whenever `PI_OFFLINE` is off, and its 24 h window is not configurable. |
|
|
952
1014
|
| Data sources | `footerData.getGitBranch()`, `footerData.getAvailableProviderCount()`, `ctx.getContextUsage()`, `ctx.sessionManager.getEntries()`, `ctx.sessionManager.getSessionName()`, `ctx.model`; subscribes to `footerData.onBranchChange` to repaint. |
|
|
953
|
-
| 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 extension toggles through `settings.json`. The Settings section derives from a snapshot of the personal `settings.json` and both `bytefly.json` documents, and persists through the same `SettingsManager` setters or the per-leaf `bytefly.json` writer. 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. The update check holds one version in the per-load `UpdateSource` and writes
|
|
1015
|
+
| 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 extension toggles through `settings.json`. The Settings section derives from a snapshot of the personal `settings.json` and both `bytefly.json` documents, and persists through the same `SettingsManager` setters or the per-leaf `bytefly.json` writer. 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. The update check holds one version in the per-load `UpdateSource` and writes no session entry; its one file is `<agentDir>/bytefly/update.json`, the cached `latest` plus `checkedAt`, written atomically and read back on the next session. |
|
|
954
1016
|
| Lifecycle | `session_start` installs the header when Pi's `quietStartup` is `true` and the 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 `quietStartup` is `true`, loads the notify config and subscribes to the job bus, and starts the published-version check; `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, drops the notify bus subscription, and clears the recorded version. |
|
|
955
1017
|
|
|
956
1018
|
## Design notes
|
|
@@ -1041,6 +1103,10 @@ rather than showing a wrong value.
|
|
|
1041
1103
|
- **Two lines, four zones.** Line 1 is identity (`pwd` left, branch + worktree
|
|
1042
1104
|
right); line 2 is resources (meters left, model + level right). This keeps
|
|
1043
1105
|
"where am I / what state" separate from "how much am I burning".
|
|
1106
|
+
- **Child spend is a second figure.** The footer never adds a subagent's tokens to
|
|
1107
|
+
the session's; it prices the `my-pi-agent/subagent-finished` events with the
|
|
1108
|
+
same pricer and renders them as `↳`, so "this session" keeps meaning the
|
|
1109
|
+
dispatching process and the delegated work stays visible beside it.
|
|
1044
1110
|
- **The gauge is anchored at column 0.** A leading mode slot would shift the
|
|
1045
1111
|
context percentage left and right as plan mode came and went; trailing it
|
|
1046
1112
|
after a `╎` keeps the percentage in a fixed place while the mode stays
|
|
@@ -1137,16 +1203,18 @@ rather than showing a wrong value.
|
|
|
1137
1203
|
- **The install hint has one home.** `document` and `web-access` format their
|
|
1138
1204
|
tool errors from the same `REQUIREMENTS` entries `doctor` prints, so a user
|
|
1139
1205
|
reads the same command whether a tool failed or the report named it.
|
|
1140
|
-
- **Settings are a curated table, not a schema walker.**
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1206
|
+
- **Settings are a curated table, not a schema walker.** The rows cover every
|
|
1207
|
+
scalar preference across four kinds — a boolean, an enum, a string with
|
|
1208
|
+
its own rule, and a number — both scopes, both write paths, and one nested leaf
|
|
1209
|
+
(`price.currency.decimals`). A row states its section and the path to its leaf,
|
|
1210
|
+
and one reader per section resolves it project-over-global-over-defaults through
|
|
1211
|
+
the same normalizer the extension loads with, so the screen and the runtime
|
|
1212
|
+
cannot disagree about a value; a text row's rule calls the same predicate the
|
|
1213
|
+
loader uses, so an accepted value is never replaced on the next read. A generic
|
|
1214
|
+
walker would expose keys the screen cannot edit safely — arrays, nested
|
|
1215
|
+
tariffs, credentials — so the catalogue grows one proven family at a time. App
|
|
1216
|
+
rows are Global-only because Pi's setters write the personal file; Project
|
|
1217
|
+
scope lists only the `bytefly.json` rows it can actually write.
|
|
1150
1218
|
- **The doctor is quiet unless the bytefly startup TUI is on.** Only `missing`
|
|
1151
1219
|
findings post a notice, only for enabled extensions, only with a UI, only once
|
|
1152
1220
|
per session, and only when Pi's `quietStartup` is `true` — the same state that
|
|
@@ -1190,7 +1258,7 @@ elapsed counter on Pi's working row), `panel/` (the
|
|
|
1190
1258
|
unified screen and its merged model),
|
|
1191
1259
|
`picker/` (discovery and the settings store), `doctor/` (the `run_doctor` tool
|
|
1192
1260
|
and its probe runtime), `notify/`, and `update/` (the published-version check
|
|
1193
|
-
behind the header marker). The chrome and the screen are drawn from
|
|
1261
|
+
behind the header marker, its disk cache, and the on-demand re-check). The chrome and the screen are drawn from
|
|
1194
1262
|
the package's [`lib/tui/`](../../lib/tui/) design system (tokens, rules, rows,
|
|
1195
1263
|
columns, gauge), so the look follows the active theme.
|
|
1196
1264
|
|
|
@@ -1257,13 +1325,14 @@ columns, gauge), so the look follows the active theme.
|
|
|
1257
1325
|
| `notify/types.ts` | `NotifyConfig`, `NotifyMethod`, and the method list. |
|
|
1258
1326
|
| `notify/config.ts` | The `notify` section of `bytefly.json`: defaults, validation, loader. |
|
|
1259
1327
|
| `notify/dispatch.ts` | Method selection and delivery: OSC 777/99, Windows toast, `osascript`, `notify-send`, over injected seams. |
|
|
1260
|
-
| `notify/format.ts` | Pure notification text: session label and the turn / input / job bodies. |
|
|
1261
|
-
| `notify/register.ts` | Registers the
|
|
1328
|
+
| `notify/format.ts` | Pure notification text: session label and the turn / input / job / watch bodies. |
|
|
1329
|
+
| `notify/register.ts` | Registers the four triggers, loads the config at `session_start`, and owns the job- and watch-bus subscriptions. |
|
|
1262
1330
|
| `update/check.ts` | The one registry read: the `latest` URL, the abort timeout, the `FetchLike` seam, and the failure-to-`undefined` mapping. |
|
|
1263
1331
|
| `update/version.ts` | Pure semver parsing and `isNewerVersion`, including prerelease precedence. |
|
|
1264
|
-
| `update/state.ts` | `UpdateSource` and `createUpdateState
|
|
1265
|
-
| `update/
|
|
1266
|
-
| `update/
|
|
1332
|
+
| `update/state.ts` | `UpdateSource` and `createUpdateState` (the newest known version, one record per load), plus `UpdateOutcome`, the four answers an explicit re-check can give. |
|
|
1333
|
+
| `update/cache.ts` | The disk cache: `<agentDir>/bytefly/update.json`, the 24 h window in `isFresh`, a validated read that degrades to a miss, and the atomic write. |
|
|
1334
|
+
| `update/format.ts` | `updateMarker`, `updateNotice`, `releaseUrl`, and `updateOutcomeText`: the header's `↑0.12.0`, the transcript notice with its release row, and the one line `/bytefly update` prints. |
|
|
1335
|
+
| `update/register.ts` | The lifecycle: the cache-first session-start read, the async registry call, the `PI_OFFLINE` and `tui` gates, the once-per-load announcement, the shutdown generation guard, and the `recheck()` handle `/bytefly update` calls. |
|
|
1267
1336
|
|
|
1268
1337
|
## Testing
|
|
1269
1338
|
|
|
@@ -1303,13 +1372,16 @@ formatting (`doctor/format`), its config loader (`doctor/config`), and
|
|
|
1303
1372
|
registration, the proactive notice, the session cache, and `run_doctor`
|
|
1304
1373
|
through the factory (`doctor/register`). The notify capability is covered by its section loader
|
|
1305
1374
|
(`notify/config`), the pure text builders (`notify/format`), method selection and
|
|
1306
|
-
delivery over recording seams (`notify/dispatch`), and its
|
|
1375
|
+
delivery over recording seams (`notify/dispatch`), and its four triggers,
|
|
1307
1376
|
reload, and shutdown through `registerNotify` (`notify/register`). The update
|
|
1308
1377
|
check is covered by the version-ordering table (`update/version`), the transport
|
|
1309
1378
|
with an injected fetch (`update/check`), the per-load store (`update/state`), the
|
|
1310
|
-
marker and
|
|
1311
|
-
|
|
1312
|
-
|
|
1379
|
+
marker, notice, and release-URL wording (`update/format`), the cache read, write,
|
|
1380
|
+
and window (`update/cache`), and its lifecycle — a fresh entry reused without a
|
|
1381
|
+
request, a stale one re-read and rewritten, silent on failure or an older version,
|
|
1382
|
+
cache-only offline, skipped outside `tui`, an explicit `recheck()` that ignores
|
|
1383
|
+
the window, and immunity to a reply that lands after shutdown
|
|
1384
|
+
(`update/register`).
|
|
1313
1385
|
The user-message nameplate is covered by its pure transform
|
|
1314
1386
|
(`message/format`, including a render through pi-tui's real `Markdown`) and its
|
|
1315
1387
|
registration (`message/register`), plus the factory check in `extension`.
|
|
@@ -50,8 +50,13 @@ export const DEFAULT_RAIL_GLYPH: string = GLYPHS.divider;
|
|
|
50
50
|
|
|
51
51
|
export const DEFAULT_CHROME_CONFIG: ChromeConfig = { rail: DEFAULT_RAIL_GLYPH };
|
|
52
52
|
|
|
53
|
-
/**
|
|
54
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Whether `value` is a single, unstyled terminal column.
|
|
55
|
+
*
|
|
56
|
+
* The Settings row validates with this same function, so the editor cannot save
|
|
57
|
+
* a glyph the next read would replace.
|
|
58
|
+
*/
|
|
59
|
+
export function isRailGlyph(value: string): boolean {
|
|
55
60
|
// Control characters first: a bare ESC confuses a width measurement.
|
|
56
61
|
if (/[\u0000-\u001f\u007f]/.test(value)) return false;
|
|
57
62
|
return [...value].length === 1 && visibleWidth(value) === 1;
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
19
|
+
import { WEB_SEARCH_PROVIDER_CHANNEL } from "../../../lib/events.ts";
|
|
19
20
|
import { missingFindings } from "../../../lib/requirements.ts";
|
|
20
21
|
import { loadConfig } from "./config.ts";
|
|
21
22
|
import { warningNotice } from "./format.ts";
|
|
@@ -39,6 +40,11 @@ export function registerDoctor(pi: ExtensionAPI, deps: DoctorDeps = {}): DoctorR
|
|
|
39
40
|
|
|
40
41
|
registerTools(pi, runtime);
|
|
41
42
|
|
|
43
|
+
// `web-access` publishes what it resolved at session start; the doctor reports
|
|
44
|
+
// the provider, its key variable, and (on a refresh) its endpoint from that,
|
|
45
|
+
// without importing the extension.
|
|
46
|
+
pi.events.on(WEB_SEARCH_PROVIDER_CHANNEL, (facts) => runtime.noteSearchFacts(facts));
|
|
47
|
+
|
|
42
48
|
pi.on("session_start", async (_event, ctx) => {
|
|
43
49
|
const config = loadConfig(ctx.cwd);
|
|
44
50
|
runtime.configure({ cwd: ctx.cwd, config });
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { CONFIG_DIR_NAME, getAgentDir, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
12
|
+
import type { WebSearchProviderEvent } from "../../../lib/events.ts";
|
|
12
13
|
import { readJsonResult } from "../../../lib/config.ts";
|
|
13
14
|
import { isExtensionEnabled } from "../../../lib/env.ts";
|
|
14
15
|
import {
|
|
@@ -47,10 +48,28 @@ export interface DoctorRuntime {
|
|
|
47
48
|
probeAll(options?: { refresh?: boolean }): Promise<ExtensionReport[]>;
|
|
48
49
|
/** The cached reports from the last probe, if any. */
|
|
49
50
|
peek(): ExtensionReport[] | undefined;
|
|
50
|
-
/**
|
|
51
|
+
/** Remember the search provider `web-access` published for this session. */
|
|
52
|
+
noteSearchFacts(facts: unknown): void;
|
|
53
|
+
/** Drop the cached reports and the published facts; the configured cwd is kept. */
|
|
51
54
|
reset(): void;
|
|
52
55
|
}
|
|
53
56
|
|
|
57
|
+
/** How long a reachability probe may take; a doctor refresh is not a fetch. */
|
|
58
|
+
const REACH_TIMEOUT_MS = 3_000;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Probe a URL's reachability. Any HTTP answer proves the host is there; a 5xx is
|
|
62
|
+
* reported because it usually means the server is up but broken.
|
|
63
|
+
*/
|
|
64
|
+
async function probeReach(url: string): Promise<{ ok: boolean; error?: string }> {
|
|
65
|
+
try {
|
|
66
|
+
const response = await fetch(url, { method: "GET", signal: AbortSignal.timeout(REACH_TIMEOUT_MS) });
|
|
67
|
+
return response.status < 500 ? { ok: true } : { ok: false, error: `HTTP ${response.status}` };
|
|
68
|
+
} catch (error) {
|
|
69
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
54
73
|
/** Build a runtime over the host's `exec`, with `deps.seams` for tests. */
|
|
55
74
|
export function createDoctorRuntime(
|
|
56
75
|
pi: Pick<ExtensionAPI, "exec">,
|
|
@@ -59,9 +78,11 @@ export function createDoctorRuntime(
|
|
|
59
78
|
let cwd = process.cwd();
|
|
60
79
|
let config: DoctorConfig = DEFAULT_CONFIG;
|
|
61
80
|
let cached: ExtensionReport[] | undefined;
|
|
81
|
+
/** What `web-access` published at session start; absent when it did not load. */
|
|
82
|
+
let searchProvider: WebSearchProviderEvent | undefined;
|
|
62
83
|
|
|
63
|
-
/** The full seam set for the current session. */
|
|
64
|
-
const seams = (): ProbeSeams => ({
|
|
84
|
+
/** The full seam set for the current session; `refresh` is the caller's. */
|
|
85
|
+
const seams = (refresh: boolean): ProbeSeams => ({
|
|
65
86
|
exec: async (command, args) => {
|
|
66
87
|
const result = await pi.exec(command, args, { timeout: config.timeoutMs });
|
|
67
88
|
return { code: result.code };
|
|
@@ -72,6 +93,9 @@ export function createDoctorRuntime(
|
|
|
72
93
|
isEnabled: (extension) => isExtensionEnabled(extension) && !config.skip.includes(extension),
|
|
73
94
|
notify: notifySeam(cwd),
|
|
74
95
|
isTTY: process.stdout.isTTY === true,
|
|
96
|
+
searchProvider: () => searchProvider,
|
|
97
|
+
reach: probeReach,
|
|
98
|
+
refresh,
|
|
75
99
|
...deps.seams,
|
|
76
100
|
});
|
|
77
101
|
|
|
@@ -84,20 +108,26 @@ export function createDoctorRuntime(
|
|
|
84
108
|
|
|
85
109
|
async probeRequired() {
|
|
86
110
|
// Concurrent: a probe is a short-lived process, and session start must
|
|
87
|
-
// not wait on the sum of every timeout.
|
|
88
|
-
|
|
111
|
+
// not wait on the sum of every timeout. Never a refresh: this runs before
|
|
112
|
+
// anyone asked, so no probe may touch the network.
|
|
113
|
+
return Promise.all(hardRequirementExtensions().map((name) => probeExtension(name, seams(false))));
|
|
89
114
|
},
|
|
90
115
|
|
|
91
116
|
async probeAll(options = {}) {
|
|
92
117
|
if (cached && !options.refresh) return cached;
|
|
93
|
-
cached = await probeDeclared(seams(), Object.keys(REQUIREMENTS));
|
|
118
|
+
cached = await probeDeclared(seams(options.refresh === true), Object.keys(REQUIREMENTS));
|
|
94
119
|
return cached;
|
|
95
120
|
},
|
|
96
121
|
|
|
97
122
|
peek: () => cached,
|
|
98
123
|
|
|
124
|
+
noteSearchFacts(facts) {
|
|
125
|
+
searchProvider = facts && typeof facts === "object" ? (facts as WebSearchProviderEvent) : undefined;
|
|
126
|
+
},
|
|
127
|
+
|
|
99
128
|
reset() {
|
|
100
129
|
cached = undefined;
|
|
130
|
+
searchProvider = undefined;
|
|
101
131
|
},
|
|
102
132
|
};
|
|
103
133
|
}
|
|
@@ -7,9 +7,14 @@
|
|
|
7
7
|
* hold thousands of entries and the footer renders on every frame. The config is
|
|
8
8
|
* captured when the reader is built, so re-reading the file means building a new
|
|
9
9
|
* reader (which is what installing the footer does).
|
|
10
|
+
*
|
|
11
|
+
* The child-run ledger's version joins the memo key: a finished subagent invalidates
|
|
12
|
+
* the same way an appended entry does, so the roll-up appears on the next frame
|
|
13
|
+
* without re-pricing every frame.
|
|
10
14
|
*/
|
|
11
15
|
|
|
12
16
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
17
|
+
import type { DelegationLedger } from "../delegation.ts";
|
|
13
18
|
import { createPricer } from "../price/price.ts";
|
|
14
19
|
import type { ByteflyConfig } from "../price/types.ts";
|
|
15
20
|
import type { ContextInfo, FooterData, StatusSnapshot } from "../types.ts";
|
|
@@ -23,22 +28,26 @@ import { emptyUsage, usageRecords } from "./entries.ts";
|
|
|
23
28
|
* are stale; this avoids copying the full entry list and rebuilding the session
|
|
24
29
|
* projection per frame. Pi's own footer memoizes its stats on the same idea.
|
|
25
30
|
*/
|
|
26
|
-
export function createSnapshotReader(config: ByteflyConfig) {
|
|
31
|
+
export function createSnapshotReader(config: ByteflyConfig, delegation: DelegationLedger) {
|
|
27
32
|
const price = createPricer(config);
|
|
28
33
|
let key: string | null = null;
|
|
29
34
|
let usage = emptyUsage();
|
|
35
|
+
let delegationTotals = { runs: 0, usage: emptyUsage() };
|
|
30
36
|
let cacheHitRate: number | null = null;
|
|
31
37
|
let context: ContextInfo = { tokens: null, contextWindow: 0, percent: null };
|
|
32
38
|
|
|
33
39
|
return (ctx: ExtensionContext, footerData: FooterData): StatusSnapshot => {
|
|
34
40
|
const session = ctx.sessionManager;
|
|
35
41
|
const current = ctx.model;
|
|
36
|
-
const nextKey = `${session.getSessionId()}:${session.getLeafId() ?? ""}:${current?.provider ?? ""}/${current?.id ?? ""}`;
|
|
42
|
+
const nextKey = `${session.getSessionId()}:${session.getLeafId() ?? ""}:${current?.provider ?? ""}/${current?.id ?? ""}:${delegation.version()}`;
|
|
37
43
|
if (nextKey !== key) {
|
|
38
44
|
const active = current ? { provider: current.provider, model: current.id } : undefined;
|
|
39
45
|
const scanned = usageRecords(session.getEntries(), active);
|
|
40
46
|
usage = price(scanned.records);
|
|
41
47
|
cacheHitRate = scanned.cacheHitRate;
|
|
48
|
+
// The child runs are priced by the same pricer, so a currency or rate
|
|
49
|
+
// change moves both numbers together — and neither is folded into the other.
|
|
50
|
+
delegationTotals = { runs: delegation.runs(), usage: price(delegation.records()) };
|
|
42
51
|
|
|
43
52
|
const usageInfo = ctx.getContextUsage();
|
|
44
53
|
context = {
|
|
@@ -65,6 +74,7 @@ export function createSnapshotReader(config: ByteflyConfig) {
|
|
|
65
74
|
currency: config.currency,
|
|
66
75
|
cacheHitRate,
|
|
67
76
|
providerCount: footerData.getAvailableProviderCount(),
|
|
77
|
+
delegation: delegationTotals,
|
|
68
78
|
};
|
|
69
79
|
};
|
|
70
80
|
}
|
|
@@ -16,7 +16,13 @@ export const CONFIG = {
|
|
|
16
16
|
gauge: { full: GLYPHS.gaugeFull, empty: GLYPHS.gaugeEmpty, widths: [10, 5, 3, 0] as const },
|
|
17
17
|
/** Separators printed before grouped and other segments. */
|
|
18
18
|
separators: { group: SEPARATORS.group, item: SEPARATORS.item },
|
|
19
|
-
icons: {
|
|
19
|
+
icons: {
|
|
20
|
+
branch: GLYPHS.branch,
|
|
21
|
+
detached: GLYPHS.detached,
|
|
22
|
+
worktree: GLYPHS.worktree,
|
|
23
|
+
browse: GLYPHS.browse,
|
|
24
|
+
delegation: GLYPHS.delegation,
|
|
25
|
+
},
|
|
20
26
|
/** Status key the worktree extension uses; routed to line 1's right zone. */
|
|
21
27
|
worktreeStatusKey: STATUS_KEYS.worktree,
|
|
22
28
|
/** Status key the file-browse extension uses; routed to line 1's right zone. */
|