@gavin-hu/my-pi-agent 0.12.0 → 0.13.1
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 +162 -70
- package/extensions/bytefly/chrome.ts +7 -2
- package/extensions/bytefly/doctor/register.ts +6 -0
- package/extensions/bytefly/doctor/runtime.ts +38 -6
- package/extensions/bytefly/footer/budget.ts +64 -0
- package/extensions/bytefly/footer/collect/snapshot.ts +33 -5
- package/extensions/bytefly/footer/config.ts +7 -1
- package/extensions/bytefly/footer/delegation.ts +100 -0
- package/extensions/bytefly/footer/price/config.ts +16 -0
- package/extensions/bytefly/footer/price/types.ts +5 -0
- package/extensions/bytefly/footer/register.ts +46 -4
- 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 +8 -5
- package/extensions/bytefly/notify/config.ts +2 -0
- package/extensions/bytefly/notify/format.ts +19 -1
- package/extensions/bytefly/notify/register.ts +52 -14
- package/extensions/bytefly/notify/types.ts +2 -0
- package/extensions/bytefly/panel/commands.ts +34 -13
- package/extensions/bytefly/panel/settings.ts +129 -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/format.ts +4 -7
- 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/todo/nudge.ts +5 -1
- package/extensions/watch/README.md +19 -8
- package/extensions/watch/commands.ts +4 -3
- package/extensions/watch/config.ts +1 -1
- package/extensions/watch/format.ts +4 -7
- package/extensions/watch/index.ts +19 -0
- package/extensions/watch/runtime.ts +83 -12
- 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 +81 -16
- package/extensions/worktree/config.ts +11 -0
- package/extensions/worktree/enter.ts +10 -3
- package/extensions/worktree/exit.ts +55 -3
- package/extensions/worktree/gaps.ts +74 -0
- package/extensions/worktree/git.ts +28 -0
- package/extensions/worktree/guard.ts +50 -4
- package/extensions/worktree/handoff.ts +55 -0
- package/extensions/worktree/index.ts +14 -2
- package/extensions/worktree/root-tools.ts +1 -1
- package/extensions/worktree/runtime.ts +25 -0
- package/extensions/worktree/status.ts +15 -2
- package/extensions/worktree/tools.ts +3 -2
- package/lib/README.md +14 -10
- package/lib/events.ts +86 -1
- package/lib/goal-state.ts +95 -0
- package/lib/requirements.ts +361 -62
- package/lib/tui/README.md +91 -0
- package/lib/tui/columns.ts +17 -0
- package/lib/tui/glyphs.ts +1 -0
- package/lib/tui/tool.ts +6 -2
- 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
|
|
@@ -120,14 +126,26 @@ shows as `¥<0.01` rather than `¥0.00`.
|
|
|
120
126
|
|
|
121
127
|
DeepSeek needs no config file at all: the bar prices it from the published yuan
|
|
122
128
|
rates and peak tariff built into [`footer/price/config.ts`](./footer/price/config.ts).
|
|
123
|
-
|
|
124
|
-
|
|
129
|
+
Those tables record the day they were last checked against the published tariff,
|
|
130
|
+
and `doctor` reports how old that is, so a re-pricing shows up as a warning instead
|
|
131
|
+
of a quietly wrong total. A `bytefly.json` can switch to the published dollar table,
|
|
132
|
+
add models of your own, or replace a built-in entry:
|
|
125
133
|
|
|
126
134
|
```
|
|
127
135
|
~/repo/project ⎇ main
|
|
128
136
|
▰▰▰▰▰▰▱▱▱▱ 62%/200k · ¥2.50 · ↑42k ↓8.0k · R96k CH 87% opus-4.5 · high
|
|
129
137
|
```
|
|
130
138
|
|
|
139
|
+
**Session budget.** `price.budget` puts a threshold under that total, in the same
|
|
140
|
+
currency: when the session's cost reaches it, one `bytefly · Session budget
|
|
141
|
+
reached` notice names what was spent against the budget, and one OS notification
|
|
142
|
+
follows when notify is enabled. It is an alert, not a limit — no turn is blocked or
|
|
143
|
+
aborted — and it fires once per session, on the turn where the crossing becomes
|
|
144
|
+
final (the run settling), because a warning per turn after that is noise. `0`, the
|
|
145
|
+
default, turns it off. The check reads the total the footer already priced, and
|
|
146
|
+
the footer exists only in `tui` sessions, so a print or JSON run has no budget
|
|
147
|
+
alert; the footer's own numbers are visible in every interactive session.
|
|
148
|
+
|
|
131
149
|
**User message.** Every user message opens with a two-row `You` nameplate —
|
|
132
150
|
the label over a rule that spans the width — above the message:
|
|
133
151
|
|
|
@@ -266,21 +284,25 @@ underneath, and `run_doctor` returns the same report as text and typed data.
|
|
|
266
284
|
Only a missing
|
|
267
285
|
*hard* requirement posts a notice at session start; everything else waits in the
|
|
268
286
|
report.
|
|
269
|
-
See [The
|
|
287
|
+
See [The control center](#the-control-center).
|
|
270
288
|
|
|
271
289
|
**Notify.** Off by default, one `notify` section in `bytefly.json` turns on OS
|
|
272
290
|
notifications for unattended runs: when a full agent run settles, when a
|
|
273
|
-
background job finishes,
|
|
291
|
+
background job finishes, when the agent opens a prompt asking for input, and when
|
|
292
|
+
a `watch` wake match fires while the agent is idle. The
|
|
274
293
|
method follows the terminal (OSC 777, OSC 99, a Windows toast) or falls back to
|
|
275
294
|
the platform notifier (`osascript`, `notify-send`); see
|
|
276
295
|
[Notifications](#notifications).
|
|
277
296
|
|
|
278
|
-
**Update check.**
|
|
297
|
+
**Update check.** In the background at an interactive session's start, the loaded
|
|
279
298
|
version is compared with the published `@gavin-hu/my-pi-agent` on npm. A newer
|
|
280
299
|
one puts `↑0.12.0` after the header's chrome version and posts one transcript
|
|
281
|
-
notice naming `pi update --extensions
|
|
282
|
-
|
|
283
|
-
|
|
300
|
+
notice naming `pi update --extensions` and its release URL. The answer is cached
|
|
301
|
+
in `<agentDir>/bytefly/update.json` with the time it was read, so a session
|
|
302
|
+
starting inside the 24-hour window reuses it instead of repeating the request,
|
|
303
|
+
and `/bytefly update` re-runs the check on demand. The check is skipped when
|
|
304
|
+
`PI_OFFLINE` is set, makes no request outside `tui`, and a failed background
|
|
305
|
+
request is silent; see [Update check](#update-check).
|
|
284
306
|
|
|
285
307
|
## Commands
|
|
286
308
|
|
|
@@ -289,9 +311,10 @@ is set, makes no request outside `tui`, and a failed request is silent; see
|
|
|
289
311
|
| `/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
312
|
| `/bytefly settings` | Open the control center on Settings. Outside `tui` it prints the grouped settings listing with each row's value. |
|
|
291
313
|
| `/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. |
|
|
314
|
+
| `/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
315
|
|
|
293
316
|
Tab completes the subcommands, extension names after `doctor`, and `refresh`;
|
|
294
|
-
`/bytefly settings`
|
|
317
|
+
`/bytefly settings` and `/bytefly update` take no name.
|
|
295
318
|
|
|
296
319
|
## Behaviour by mode
|
|
297
320
|
|
|
@@ -328,7 +351,7 @@ separators, icons, status keys), and the turn separator in
|
|
|
328
351
|
colors).
|
|
329
352
|
|
|
330
353
|
Every key of every section is listed with its type, default, and bounds in the
|
|
331
|
-
[configuration reference](../../docs/references/
|
|
354
|
+
[configuration reference](../../docs/references/bytefly-config.md); this section
|
|
332
355
|
covers what that table cannot — when each section is read, and what the cost
|
|
333
356
|
does when no rate matches.
|
|
334
357
|
|
|
@@ -392,7 +415,7 @@ named by `doctor`; a wrong-typed value falls back to its default, and a
|
|
|
392
415
|
wrong-typed timezone drops the peak schedule rather than the session.
|
|
393
416
|
|
|
394
417
|
The notify capability is **off by default**: `enabled: true` is the whole setup
|
|
395
|
-
needed for an unattended run, and the
|
|
418
|
+
needed for an unattended run, and the four trigger flags then narrow what is
|
|
396
419
|
announced (see [Notifications](#notifications)).
|
|
397
420
|
|
|
398
421
|
The proactive doctor check is part of the bytefly startup TUI, so it posts only
|
|
@@ -574,23 +597,29 @@ installs or fetches a package.
|
|
|
574
597
|
without leaving the session.
|
|
575
598
|
|
|
576
599
|
**Settings.** The second section uses the same frame and the same `Tab` scope
|
|
577
|
-
switch, with its own cursor. It lists
|
|
600
|
+
switch, with its own cursor. It lists sixteen preferences in six groups:
|
|
578
601
|
|
|
579
602
|
```
|
|
580
603
|
bytefly ──────────────────────────────────────────────────────────────
|
|
581
|
-
Extensions · Settings Global ·
|
|
604
|
+
Extensions · Settings Global · 16 settings
|
|
582
605
|
|
|
583
606
|
Startup (1)
|
|
584
607
|
❯ ◻ Bytefly startup TUI Off
|
|
585
|
-
Notifications (
|
|
608
|
+
Notifications (7)
|
|
586
609
|
◻ Desktop notifications Off
|
|
610
|
+
◼ Turn finished On
|
|
611
|
+
◼ Needs input On
|
|
612
|
+
◼ Job finished On
|
|
613
|
+
◼ Watcher matched On
|
|
587
614
|
Delivery method Auto
|
|
588
615
|
Notification title Pi
|
|
589
|
-
Diagnostics (
|
|
616
|
+
Diagnostics (2)
|
|
590
617
|
◼ Proactive health check On
|
|
591
|
-
|
|
618
|
+
Probe timeout 2000
|
|
619
|
+
Pricing (3)
|
|
592
620
|
Currency CNY (¥)
|
|
593
621
|
Cost decimals 2
|
|
622
|
+
Session budget 0
|
|
594
623
|
Transcript (2)
|
|
595
624
|
Rail glyph ╎
|
|
596
625
|
Output padding 1
|
|
@@ -607,12 +636,18 @@ bytefly ────────────────────────
|
|
|
607
636
|
|---|---|---|---|
|
|
608
637
|
| Startup | Bytefly startup TUI | Pi `settings.json` (`quietStartup`) | Off, On |
|
|
609
638
|
| Notifications | Desktop notifications | `notify.enabled` | Off, On |
|
|
639
|
+
| Notifications | Turn finished | `notify.turnFinished` | Off, On |
|
|
640
|
+
| Notifications | Needs input | `notify.needsInput` | Off, On |
|
|
641
|
+
| Notifications | Job finished | `notify.jobFinished` | Off, On |
|
|
642
|
+
| Notifications | Watcher matched | `notify.matchFound` | Off, On |
|
|
610
643
|
| Notifications | Delivery method | `notify.method` | Auto, OSC 777, OSC 99, Windows toast, macOS, Linux |
|
|
611
644
|
| Notifications | Notification title | `notify.title` | Text, up to 40 characters |
|
|
612
645
|
| Diagnostics | Proactive health check | `doctor.proactive` | Off, On |
|
|
646
|
+
| Diagnostics | Probe timeout | `doctor.timeoutMs` | Number, 100–30000 ms |
|
|
613
647
|
| Pricing | Currency | `price.currency.code` | CNY, USD |
|
|
614
648
|
| Pricing | Cost decimals | `price.currency.decimals` | 0, 1, 2, 3, 4 |
|
|
615
|
-
|
|
|
649
|
+
| Pricing | Session budget | `price.budget` | Number, `0` = off |
|
|
650
|
+
| Transcript | Rail glyph | `chrome.rail` | Text, exactly one column |
|
|
616
651
|
| Transcript | Output padding | Pi `settings.json` | 0, 1 |
|
|
617
652
|
| Terminal | Hardware cursor | Pi `settings.json` | Off, On |
|
|
618
653
|
|
|
@@ -620,7 +655,8 @@ Choosing **Currency** also writes that currency's default precision. Both rows
|
|
|
620
655
|
stay editable afterwards, so the footer follows the explicit values.
|
|
621
656
|
|
|
622
657
|
`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
|
|
658
|
+
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
|
|
624
660
|
where the value is written — `Global · ~/.pi/agent/settings.json`, `Project ·
|
|
625
661
|
.pi/bytefly.json`, or `Inherited from Global · .pi/bytefly.json`. A successful
|
|
626
662
|
change marks the screen `Saved · reload to apply` — even when the terminal is too
|
|
@@ -635,7 +671,7 @@ with `true` or `false`.
|
|
|
635
671
|
|
|
636
672
|
App rows are **Global only**: Pi's setters for them write the personal
|
|
637
673
|
`settings.json`, and only resource paths have public project setters. Project
|
|
638
|
-
scope therefore lists the
|
|
674
|
+
scope therefore lists the thirteen `bytefly.json` rows and a
|
|
639
675
|
`App settings are Global only.` note rather than promising a write that cannot
|
|
640
676
|
happen. A `bytefly.json` row writes one leaf in the selected scope (a depth-1
|
|
641
677
|
key, or a nested one such as `price.currency.decimals`) through a per-key
|
|
@@ -645,14 +681,19 @@ malformed document is refused with a notice instead of overwritten.
|
|
|
645
681
|
The non-`tui` form prints the headline, the groups, and `Label: Value` rows:
|
|
646
682
|
|
|
647
683
|
```
|
|
648
|
-
bytefly: Settings · Global ·
|
|
684
|
+
bytefly: Settings · Global · 16 settings
|
|
649
685
|
|
|
650
686
|
Startup (1)
|
|
651
687
|
Bytefly startup TUI: Off
|
|
652
688
|
|
|
653
|
-
Notifications (
|
|
689
|
+
Notifications (7)
|
|
654
690
|
Desktop notifications: Off
|
|
691
|
+
Turn finished: On
|
|
692
|
+
Needs input: On
|
|
693
|
+
Job finished: On
|
|
694
|
+
Watcher matched: On
|
|
655
695
|
Delivery method: Auto
|
|
696
|
+
Notification title: Pi
|
|
656
697
|
...
|
|
657
698
|
```
|
|
658
699
|
|
|
@@ -664,6 +705,20 @@ means reduced capability — an optional package that is absent, a config file
|
|
|
664
705
|
that fails to parse, a login that has not happened. Doctor never changes
|
|
665
706
|
anything and never installs anything.
|
|
666
707
|
|
|
708
|
+
It also reports what has **aged**: the built-in DeepSeek rate tables carry the day
|
|
709
|
+
they were last checked against the published tariff (`PRICE_VERIFIED_AT` in
|
|
710
|
+
`footer/price/config.ts`), and the report says how old that is — `ok` inside the
|
|
711
|
+
90-day window, `degraded` beyond it with the source URL to re-check. A tariff change
|
|
712
|
+
therefore becomes a visible warning instead of a quietly wrong cost total.
|
|
713
|
+
|
|
714
|
+
`web-access` publishes what it resolved at `session_start`, so the report names
|
|
715
|
+
the search provider and why it won (an explicit choice, the `auto` rule, or the
|
|
716
|
+
keyless default), and — when the provider takes a key and no literal `apiKey` is
|
|
717
|
+
set — its API-key variable and whether that variable is set. The key value is
|
|
718
|
+
never part of the report, only its variable name. Reachability is the one check
|
|
719
|
+
that touches the network, so it runs only on an explicit `/bytefly refresh`:
|
|
720
|
+
a session-start check stays offline.
|
|
721
|
+
|
|
667
722
|
`/bytefly <extension>` narrows the same screen to that extension; `Enter`
|
|
668
723
|
expands its findings, including the checks that passed, into the free height.
|
|
669
724
|
`/bytefly refresh` ignores the findings cached earlier in the session. Every
|
|
@@ -718,7 +773,7 @@ the same entry in `lib/requirements.ts`, so the two cannot drift.
|
|
|
718
773
|
|
|
719
774
|
The notify capability exists for unattended runs: start a long turn, leave the
|
|
720
775
|
terminal, and get a desktop notification when Pi needs you — or when it is done.
|
|
721
|
-
It fires on
|
|
776
|
+
It fires on four events, each independently switchable in the `notify` section
|
|
722
777
|
of `bytefly.json` (see [Configuration](#configuration)):
|
|
723
778
|
|
|
724
779
|
- **A run settled** (`agent_settled`). Pi finished the turn and waits for the
|
|
@@ -731,6 +786,12 @@ of `bytefly.json` (see [Configuration](#configuration)):
|
|
|
731
786
|
- **A background job finished.** The [job](../job/) extension announces each
|
|
732
787
|
terminal transition on the shared `pi.events` bus
|
|
733
788
|
(`my-pi-agent/job-finished`); bytefly listens and never imports it.
|
|
789
|
+
- **A watcher matched.** A [watch](../watch/) whose `wake` is on matched while the
|
|
790
|
+
agent was idle, and announced it on `my-pi-agent/watch-match` with the cycle's
|
|
791
|
+
count, the watcher, and its newest line. The body names that line, sanitized and
|
|
792
|
+
clipped to 80 characters, because the line is the alert: a watch usually tails a
|
|
793
|
+
build or an error log. A match the agent was busy for does not notify — it is
|
|
794
|
+
collected for the next turn instead.
|
|
734
795
|
|
|
735
796
|
### Delivery method
|
|
736
797
|
|
|
@@ -777,6 +838,7 @@ reports it once, in the two places it already owns:
|
|
|
777
838
|
bytefly · 0.12.0 is available
|
|
778
839
|
╎ ◌ current · v0.11.4
|
|
779
840
|
╎ ◌ update · pi update --extensions
|
|
841
|
+
╎ ◌ release · https://github.com/gavin-hu/my-pi-agent/releases/tag/v0.12.0
|
|
780
842
|
```
|
|
781
843
|
|
|
782
844
|
The marker is the shared `↑` glyph and the newest version — the arrow is one cell
|
|
@@ -796,10 +858,27 @@ fired without awaiting at `session_start` (Pi awaits handlers, and a five-second
|
|
|
796
858
|
registry call must not hold the first prompt) with a five-second abort. Only a
|
|
797
859
|
strictly newer, parseable semver announces anything: a non-ok status, a body
|
|
798
860
|
that is not JSON, a missing version, a network error, or an older or equal
|
|
799
|
-
version is silent.
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
861
|
+
version is silent. `PI_OFFLINE` (Pi's own switch) turns the check off;
|
|
862
|
+
`PI_SKIP_VERSION_CHECK` only governs Pi's `pi.dev` request.
|
|
863
|
+
|
|
864
|
+
The answer is cached in `<agentDir>/bytefly/update.json` as `latest` plus the
|
|
865
|
+
epoch milliseconds it was read at. A session that starts inside the 24-hour
|
|
866
|
+
window reuses that entry — painting the marker and posting the notice from it —
|
|
867
|
+
instead of repeating the request, so a working day of sessions costs one read.
|
|
868
|
+
Past the window the check runs again and rewrites the entry; a failed read writes
|
|
869
|
+
nothing, so the next session tries again. Offline, the cached entry is used with
|
|
870
|
+
no request and no write, because a stale version is still the best answer
|
|
871
|
+
available and the marker is advisory.
|
|
872
|
+
|
|
873
|
+
`/bytefly update` is the on-demand half: it ignores the window, reads the registry
|
|
874
|
+
now, records and paints the result, and prints one line — `up to date — v0.12.0
|
|
875
|
+
is the newest published version`, `v0.13.0 is available (current 0.12.0) —
|
|
876
|
+
https://…/releases/tag/v0.13.0`, `the update check was skipped (PI_OFFLINE is
|
|
877
|
+
set)`, or `could not read the published version`. It resolves no packages, probes
|
|
878
|
+
no extension, and posts no second notice, so a session open for days can see a
|
|
879
|
+
release without a restart. Opening the control center never checks: the screen has
|
|
880
|
+
no update surface, and the only request paths are `session_start` and this
|
|
881
|
+
command.
|
|
803
882
|
|
|
804
883
|
## Limitations
|
|
805
884
|
|
|
@@ -814,15 +893,16 @@ switch) turns the check off; `PI_SKIP_VERSION_CHECK` only governs Pi's
|
|
|
814
893
|
- **Project overrides of app settings are not shown.** App rows read the
|
|
815
894
|
personal file; a `.pi/settings.json` override still wins at runtime, and
|
|
816
895
|
`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
|
|
896
|
+
- **Arrays and tables stay JSON.** A row is one boolean, enum, string, or number.
|
|
897
|
+
`doctor.skip` (an array), the `rates` table, and `price.peak` (a nested object)
|
|
898
|
+
are still `bytefly.json` edits; every scalar preference has a row.
|
|
899
|
+
- **A custom rail glyph must be one column.** The editor refuses anything else,
|
|
900
|
+
because a two-column glyph would push every railed body one cell right; the
|
|
901
|
+
former four-glyph menu is now the description's example list.
|
|
902
|
+
- **`doctor`, `settings`, and `update` are reserved first arguments.**
|
|
903
|
+
`/bytefly doctor` always selects the report, so an extension literally named
|
|
904
|
+
`doctor` cannot be narrowed to by name; `settings` and `update` are reserved
|
|
905
|
+
the same way.
|
|
826
906
|
- **The picker manages only the `extensions` resource type.** Skills, prompts,
|
|
827
907
|
and themes are handled by Pi's own `pi config`.
|
|
828
908
|
- **Built-in extensions are not listed** (`builtin:mcp`, `builtin:codemode`, …):
|
|
@@ -833,17 +913,19 @@ switch) turns the check off; `PI_SKIP_VERSION_CHECK` only governs Pi's
|
|
|
833
913
|
published under another tag, or a checkout ahead of npm reports nothing, and
|
|
834
914
|
there is no channel or prerelease preference: `0.11.4` is behind `0.12.0`
|
|
835
915
|
whether or not a `0.12.0-beta.1` exists.
|
|
836
|
-
- **
|
|
837
|
-
|
|
838
|
-
|
|
916
|
+
- **The cache window is fixed at 24 hours.** It runs at `session_start` in `tui`
|
|
917
|
+
mode and reuses an entry younger than that; there is no setting for it, and
|
|
918
|
+
`/bytefly` and `doctor` do not report the version. A failed or slow background
|
|
919
|
+
request is retried by the next session, or immediately by `/bytefly update`.
|
|
839
920
|
- **The marker needs the bytefly startup TUI.** It lives on the custom header,
|
|
840
921
|
so with `quietStartup` off the notice is the whole signal.
|
|
841
922
|
- **Pi may report the same update.** An npm-installed copy can also show Pi's
|
|
842
923
|
own `Package Updates Available` block; the two checks are independent and
|
|
843
924
|
`pi update --extensions` satisfies both.
|
|
844
|
-
- **No auto-update and
|
|
845
|
-
of running one, and the notice renderer
|
|
846
|
-
changelog
|
|
925
|
+
- **No auto-update, and the release URL is text only.** The notice names a
|
|
926
|
+
command instead of running one, and the notice renderer cannot open a link, so
|
|
927
|
+
the release row prints the URL and nothing more — no changelog body, no way to
|
|
928
|
+
click through from the transcript.
|
|
847
929
|
- **Project toggles are two-state per press** (`enable`/`disable`). `x` clears
|
|
848
930
|
the override, which is the third state; a resource with no project entry in
|
|
849
931
|
`pi config` is still managed there.
|
|
@@ -915,8 +997,8 @@ switch) turns the check off; `PI_SKIP_VERSION_CHECK` only governs Pi's
|
|
|
915
997
|
- No status chip for the doctor report: it is on demand plus one notice, not a
|
|
916
998
|
permanent footer segment.
|
|
917
999
|
- No notification for individual tool calls, message turns, or subagent steps:
|
|
918
|
-
the
|
|
919
|
-
whole surface.
|
|
1000
|
+
the four triggers — a settled run, a prompt mid-run, a finished job, a watcher
|
|
1001
|
+
match — are the whole surface.
|
|
920
1002
|
- No sound, action buttons, notification history, or de-duplication; a
|
|
921
1003
|
notification is a fire-and-forget message, not an inbox.
|
|
922
1004
|
- No frozen per-run working duration: the counter is live and is handed back to
|
|
@@ -947,10 +1029,10 @@ rather than showing a wrong value.
|
|
|
947
1029
|
| 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
1030
|
| 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
1031
|
| 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. |
|
|
1032
|
+
| 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. |
|
|
1033
|
+
| 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
1034
|
| 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
|
|
1035
|
+
| 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
1036
|
| 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
1037
|
|
|
956
1038
|
## Design notes
|
|
@@ -1041,6 +1123,10 @@ rather than showing a wrong value.
|
|
|
1041
1123
|
- **Two lines, four zones.** Line 1 is identity (`pwd` left, branch + worktree
|
|
1042
1124
|
right); line 2 is resources (meters left, model + level right). This keeps
|
|
1043
1125
|
"where am I / what state" separate from "how much am I burning".
|
|
1126
|
+
- **Child spend is a second figure.** The footer never adds a subagent's tokens to
|
|
1127
|
+
the session's; it prices the `my-pi-agent/subagent-finished` events with the
|
|
1128
|
+
same pricer and renders them as `↳`, so "this session" keeps meaning the
|
|
1129
|
+
dispatching process and the delegated work stays visible beside it.
|
|
1044
1130
|
- **The gauge is anchored at column 0.** A leading mode slot would shift the
|
|
1045
1131
|
context percentage left and right as plan mode came and went; trailing it
|
|
1046
1132
|
after a `╎` keeps the percentage in a fixed place while the mode stays
|
|
@@ -1137,16 +1223,18 @@ rather than showing a wrong value.
|
|
|
1137
1223
|
- **The install hint has one home.** `document` and `web-access` format their
|
|
1138
1224
|
tool errors from the same `REQUIREMENTS` entries `doctor` prints, so a user
|
|
1139
1225
|
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
|
-
|
|
1226
|
+
- **Settings are a curated table, not a schema walker.** The rows cover every
|
|
1227
|
+
scalar preference across four kinds — a boolean, an enum, a string with
|
|
1228
|
+
its own rule, and a number — both scopes, both write paths, and one nested leaf
|
|
1229
|
+
(`price.currency.decimals`). A row states its section and the path to its leaf,
|
|
1230
|
+
and one reader per section resolves it project-over-global-over-defaults through
|
|
1231
|
+
the same normalizer the extension loads with, so the screen and the runtime
|
|
1232
|
+
cannot disagree about a value; a text row's rule calls the same predicate the
|
|
1233
|
+
loader uses, so an accepted value is never replaced on the next read. A generic
|
|
1234
|
+
walker would expose keys the screen cannot edit safely — arrays, nested
|
|
1235
|
+
tariffs, credentials — so the catalogue grows one proven family at a time. App
|
|
1236
|
+
rows are Global-only because Pi's setters write the personal file; Project
|
|
1237
|
+
scope lists only the `bytefly.json` rows it can actually write.
|
|
1150
1238
|
- **The doctor is quiet unless the bytefly startup TUI is on.** Only `missing`
|
|
1151
1239
|
findings post a notice, only for enabled extensions, only with a UI, only once
|
|
1152
1240
|
per session, and only when Pi's `quietStartup` is `true` — the same state that
|
|
@@ -1190,7 +1278,7 @@ elapsed counter on Pi's working row), `panel/` (the
|
|
|
1190
1278
|
unified screen and its merged model),
|
|
1191
1279
|
`picker/` (discovery and the settings store), `doctor/` (the `run_doctor` tool
|
|
1192
1280
|
and its probe runtime), `notify/`, and `update/` (the published-version check
|
|
1193
|
-
behind the header marker). The chrome and the screen are drawn from
|
|
1281
|
+
behind the header marker, its disk cache, and the on-demand re-check). The chrome and the screen are drawn from
|
|
1194
1282
|
the package's [`lib/tui/`](../../lib/tui/) design system (tokens, rules, rows,
|
|
1195
1283
|
columns, gauge), so the look follows the active theme.
|
|
1196
1284
|
|
|
@@ -1257,13 +1345,14 @@ columns, gauge), so the look follows the active theme.
|
|
|
1257
1345
|
| `notify/types.ts` | `NotifyConfig`, `NotifyMethod`, and the method list. |
|
|
1258
1346
|
| `notify/config.ts` | The `notify` section of `bytefly.json`: defaults, validation, loader. |
|
|
1259
1347
|
| `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
|
|
1348
|
+
| `notify/format.ts` | Pure notification text: session label and the turn / input / job / watch bodies. |
|
|
1349
|
+
| `notify/register.ts` | Registers the four triggers, loads the config at `session_start`, and owns the job- and watch-bus subscriptions. |
|
|
1262
1350
|
| `update/check.ts` | The one registry read: the `latest` URL, the abort timeout, the `FetchLike` seam, and the failure-to-`undefined` mapping. |
|
|
1263
1351
|
| `update/version.ts` | Pure semver parsing and `isNewerVersion`, including prerelease precedence. |
|
|
1264
|
-
| `update/state.ts` | `UpdateSource` and `createUpdateState
|
|
1265
|
-
| `update/
|
|
1266
|
-
| `update/
|
|
1352
|
+
| `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. |
|
|
1353
|
+
| `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. |
|
|
1354
|
+
| `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. |
|
|
1355
|
+
| `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
1356
|
|
|
1268
1357
|
## Testing
|
|
1269
1358
|
|
|
@@ -1303,13 +1392,16 @@ formatting (`doctor/format`), its config loader (`doctor/config`), and
|
|
|
1303
1392
|
registration, the proactive notice, the session cache, and `run_doctor`
|
|
1304
1393
|
through the factory (`doctor/register`). The notify capability is covered by its section loader
|
|
1305
1394
|
(`notify/config`), the pure text builders (`notify/format`), method selection and
|
|
1306
|
-
delivery over recording seams (`notify/dispatch`), and its
|
|
1395
|
+
delivery over recording seams (`notify/dispatch`), and its four triggers,
|
|
1307
1396
|
reload, and shutdown through `registerNotify` (`notify/register`). The update
|
|
1308
1397
|
check is covered by the version-ordering table (`update/version`), the transport
|
|
1309
1398
|
with an injected fetch (`update/check`), the per-load store (`update/state`), the
|
|
1310
|
-
marker and
|
|
1311
|
-
|
|
1312
|
-
|
|
1399
|
+
marker, notice, and release-URL wording (`update/format`), the cache read, write,
|
|
1400
|
+
and window (`update/cache`), and its lifecycle — a fresh entry reused without a
|
|
1401
|
+
request, a stale one re-read and rewritten, silent on failure or an older version,
|
|
1402
|
+
cache-only offline, skipped outside `tui`, an explicit `recheck()` that ignores
|
|
1403
|
+
the window, and immunity to a reply that lands after shutdown
|
|
1404
|
+
(`update/register`).
|
|
1313
1405
|
The user-message nameplate is covered by its pure transform
|
|
1314
1406
|
(`message/format`, including a render through pi-tui's real `Markdown`) and its
|
|
1315
1407
|
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,8 @@
|
|
|
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";
|
|
13
|
+
import { PRICE_VERIFIED_AT } from "../footer/price/config.ts";
|
|
12
14
|
import { readJsonResult } from "../../../lib/config.ts";
|
|
13
15
|
import { isExtensionEnabled } from "../../../lib/env.ts";
|
|
14
16
|
import {
|
|
@@ -47,10 +49,28 @@ export interface DoctorRuntime {
|
|
|
47
49
|
probeAll(options?: { refresh?: boolean }): Promise<ExtensionReport[]>;
|
|
48
50
|
/** The cached reports from the last probe, if any. */
|
|
49
51
|
peek(): ExtensionReport[] | undefined;
|
|
50
|
-
/**
|
|
52
|
+
/** Remember the search provider `web-access` published for this session. */
|
|
53
|
+
noteSearchFacts(facts: unknown): void;
|
|
54
|
+
/** Drop the cached reports and the published facts; the configured cwd is kept. */
|
|
51
55
|
reset(): void;
|
|
52
56
|
}
|
|
53
57
|
|
|
58
|
+
/** How long a reachability probe may take; a doctor refresh is not a fetch. */
|
|
59
|
+
const REACH_TIMEOUT_MS = 3_000;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Probe a URL's reachability. Any HTTP answer proves the host is there; a 5xx is
|
|
63
|
+
* reported because it usually means the server is up but broken.
|
|
64
|
+
*/
|
|
65
|
+
async function probeReach(url: string): Promise<{ ok: boolean; error?: string }> {
|
|
66
|
+
try {
|
|
67
|
+
const response = await fetch(url, { method: "GET", signal: AbortSignal.timeout(REACH_TIMEOUT_MS) });
|
|
68
|
+
return response.status < 500 ? { ok: true } : { ok: false, error: `HTTP ${response.status}` };
|
|
69
|
+
} catch (error) {
|
|
70
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
54
74
|
/** Build a runtime over the host's `exec`, with `deps.seams` for tests. */
|
|
55
75
|
export function createDoctorRuntime(
|
|
56
76
|
pi: Pick<ExtensionAPI, "exec">,
|
|
@@ -59,9 +79,11 @@ export function createDoctorRuntime(
|
|
|
59
79
|
let cwd = process.cwd();
|
|
60
80
|
let config: DoctorConfig = DEFAULT_CONFIG;
|
|
61
81
|
let cached: ExtensionReport[] | undefined;
|
|
82
|
+
/** What `web-access` published at session start; absent when it did not load. */
|
|
83
|
+
let searchProvider: WebSearchProviderEvent | undefined;
|
|
62
84
|
|
|
63
|
-
/** The full seam set for the current session. */
|
|
64
|
-
const seams = (): ProbeSeams => ({
|
|
85
|
+
/** The full seam set for the current session; `refresh` is the caller's. */
|
|
86
|
+
const seams = (refresh: boolean): ProbeSeams => ({
|
|
65
87
|
exec: async (command, args) => {
|
|
66
88
|
const result = await pi.exec(command, args, { timeout: config.timeoutMs });
|
|
67
89
|
return { code: result.code };
|
|
@@ -72,6 +94,10 @@ export function createDoctorRuntime(
|
|
|
72
94
|
isEnabled: (extension) => isExtensionEnabled(extension) && !config.skip.includes(extension),
|
|
73
95
|
notify: notifySeam(cwd),
|
|
74
96
|
isTTY: process.stdout.isTTY === true,
|
|
97
|
+
searchProvider: () => searchProvider,
|
|
98
|
+
reach: probeReach,
|
|
99
|
+
refresh,
|
|
100
|
+
priceVerifiedAt: () => PRICE_VERIFIED_AT,
|
|
75
101
|
...deps.seams,
|
|
76
102
|
});
|
|
77
103
|
|
|
@@ -84,20 +110,26 @@ export function createDoctorRuntime(
|
|
|
84
110
|
|
|
85
111
|
async probeRequired() {
|
|
86
112
|
// Concurrent: a probe is a short-lived process, and session start must
|
|
87
|
-
// not wait on the sum of every timeout.
|
|
88
|
-
|
|
113
|
+
// not wait on the sum of every timeout. Never a refresh: this runs before
|
|
114
|
+
// anyone asked, so no probe may touch the network.
|
|
115
|
+
return Promise.all(hardRequirementExtensions().map((name) => probeExtension(name, seams(false))));
|
|
89
116
|
},
|
|
90
117
|
|
|
91
118
|
async probeAll(options = {}) {
|
|
92
119
|
if (cached && !options.refresh) return cached;
|
|
93
|
-
cached = await probeDeclared(seams(), Object.keys(REQUIREMENTS));
|
|
120
|
+
cached = await probeDeclared(seams(options.refresh === true), Object.keys(REQUIREMENTS));
|
|
94
121
|
return cached;
|
|
95
122
|
},
|
|
96
123
|
|
|
97
124
|
peek: () => cached,
|
|
98
125
|
|
|
126
|
+
noteSearchFacts(facts) {
|
|
127
|
+
searchProvider = facts && typeof facts === "object" ? (facts as WebSearchProviderEvent) : undefined;
|
|
128
|
+
},
|
|
129
|
+
|
|
99
130
|
reset() {
|
|
100
131
|
cached = undefined;
|
|
132
|
+
searchProvider = undefined;
|
|
101
133
|
},
|
|
102
134
|
};
|
|
103
135
|
}
|