@gavin-hu/my-pi-agent 0.14.0 → 0.15.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 +54 -34
- package/README.zh-CN.md +44 -28
- package/extensions/bytefly/README.md +193 -140
- package/extensions/bytefly/block/component.ts +7 -15
- package/extensions/bytefly/block/register.ts +6 -1
- package/extensions/bytefly/doctor/register.ts +24 -5
- package/extensions/bytefly/doctor/tools.ts +4 -0
- package/extensions/bytefly/footer/collect/snapshot.ts +6 -1
- package/extensions/bytefly/footer/config.ts +10 -6
- package/extensions/bytefly/footer/price/price.ts +5 -3
- package/extensions/bytefly/footer/price/rates.ts +63 -26
- package/extensions/bytefly/footer/render/status.ts +3 -3
- package/extensions/bytefly/header/component.ts +3 -2
- package/extensions/bytefly/index.ts +3 -3
- package/extensions/bytefly/notice/component.ts +8 -3
- package/extensions/bytefly/notify/config.ts +6 -11
- package/extensions/bytefly/notify/dispatch.ts +13 -5
- package/extensions/bytefly/panel/commands.ts +21 -1
- package/extensions/bytefly/panel/format.ts +5 -2
- package/extensions/bytefly/panel/settings-runtime.ts +26 -6
- package/extensions/bytefly/panel/settings.ts +41 -4
- package/extensions/bytefly/panel/themes.ts +32 -0
- package/extensions/bytefly/panel/tui/editing.ts +77 -0
- package/extensions/bytefly/panel/tui/entries.ts +24 -0
- package/extensions/bytefly/panel/tui/extensions-view.ts +13 -8
- package/extensions/bytefly/panel/tui/frame.ts +121 -4
- package/extensions/bytefly/panel/tui/settings-view.ts +54 -15
- package/extensions/bytefly/panel/tui.ts +94 -152
- package/extensions/bytefly/picker/discovery.ts +1 -0
- package/extensions/bytefly/picker/types.ts +2 -0
- package/extensions/bytefly/separator/component.ts +14 -4
- package/extensions/bytefly/update/cache.ts +58 -19
- package/extensions/bytefly/update/check.ts +35 -0
- package/extensions/bytefly/update/format.ts +76 -35
- package/extensions/bytefly/update/register.ts +144 -55
- package/extensions/bytefly/update/state.ts +4 -2
- package/extensions/bytefly/update/suppress.ts +66 -0
- package/extensions/bytefly/update/types.ts +15 -0
- package/extensions/check/README.md +253 -0
- package/extensions/check/commands.ts +128 -0
- package/extensions/check/config.ts +39 -0
- package/extensions/check/detect.ts +269 -0
- package/extensions/check/failures.ts +230 -0
- package/extensions/check/format.ts +135 -0
- package/extensions/check/index.ts +26 -0
- package/extensions/check/render.ts +185 -0
- package/extensions/check/runner.ts +176 -0
- package/extensions/check/runtime.ts +244 -0
- package/extensions/check/source.ts +80 -0
- package/extensions/check/tools.ts +173 -0
- package/extensions/check/tui.ts +195 -0
- package/extensions/check/types.ts +99 -0
- package/extensions/document/README.md +1 -1
- package/extensions/document/extract/pdf.ts +8 -16
- package/extensions/document/formats.ts +2 -2
- package/extensions/file-browse/README.md +126 -37
- package/extensions/file-browse/client.ts +98 -33
- package/extensions/file-browse/config.ts +24 -9
- package/extensions/file-browse/files.ts +183 -48
- package/extensions/file-browse/fuzzy.ts +65 -11
- package/extensions/file-browse/git.ts +37 -47
- package/extensions/file-browse/html.ts +97 -171
- package/extensions/file-browse/live-client.ts +158 -0
- package/extensions/file-browse/live.ts +222 -0
- package/extensions/file-browse/markdown.ts +110 -92
- package/extensions/file-browse/raw-html.ts +478 -0
- package/extensions/file-browse/router.ts +65 -78
- package/extensions/file-browse/server.ts +75 -59
- package/extensions/file-browse/styles.ts +124 -0
- package/extensions/file-browse/urls.ts +80 -0
- package/extensions/goal/README.md +103 -47
- package/extensions/goal/clarify-parse.ts +4 -9
- package/extensions/goal/clarify.ts +27 -4
- package/extensions/goal/commands.ts +24 -11
- package/extensions/goal/format.ts +41 -10
- package/extensions/goal/index.ts +39 -16
- package/extensions/goal/plan-route.ts +51 -0
- package/extensions/goal/schema.ts +6 -23
- package/extensions/goal/state.ts +39 -57
- package/extensions/goal/tools.ts +26 -20
- package/extensions/goal/types.ts +11 -1
- package/extensions/job/README.md +20 -6
- package/extensions/job/format.ts +18 -68
- package/extensions/job/index.ts +26 -41
- package/extensions/job/log-writer.ts +208 -0
- package/extensions/job/logs.ts +3 -2
- package/extensions/job/process.ts +6 -99
- package/extensions/job/runtime.ts +42 -181
- package/extensions/job/schema.ts +1 -4
- package/extensions/job/tools.ts +1 -15
- package/extensions/job/tui.ts +117 -307
- package/extensions/job/ui.ts +7 -33
- package/extensions/memory/README.md +1 -1
- package/extensions/memory/errors.ts +5 -6
- package/extensions/memory/tui.ts +14 -24
- package/extensions/plan/README.md +94 -18
- package/extensions/plan/commands.ts +9 -5
- package/extensions/plan/format.ts +87 -0
- package/extensions/plan/index.ts +31 -15
- package/extensions/plan/plans.ts +2 -3
- package/extensions/plan/policy.ts +0 -6
- package/extensions/plan/runtime.ts +4 -2
- package/extensions/plan/steps.ts +37 -8
- package/extensions/plan/todos.ts +44 -3
- package/extensions/plan/tools/enter.ts +94 -0
- package/extensions/plan/tools/exit.ts +296 -0
- package/extensions/plan/tools/nested.ts +21 -0
- package/extensions/plan/tools/steps.ts +136 -0
- package/extensions/plan/tools/todo-seed.ts +76 -0
- package/extensions/plan/tools/write.ts +127 -0
- package/extensions/plan/tools.ts +18 -703
- package/extensions/plan/web-client.ts +6 -22
- package/extensions/plan/web-page.ts +19 -30
- package/extensions/plan/web-router.ts +32 -79
- package/extensions/plan/web.ts +21 -42
- package/extensions/question/answers.ts +3 -8
- package/extensions/question/tui.ts +6 -6
- package/extensions/rewind/README.md +95 -22
- package/extensions/rewind/checklist.ts +192 -0
- package/extensions/rewind/commands.ts +8 -0
- package/extensions/rewind/config.ts +3 -11
- package/extensions/rewind/format.ts +220 -2
- package/extensions/rewind/git.ts +101 -21
- package/extensions/rewind/hunk-picker.ts +114 -0
- package/extensions/rewind/patch.ts +88 -0
- package/extensions/rewind/path-picker.ts +67 -100
- package/extensions/rewind/paths.ts +93 -19
- package/extensions/rewind/restore.ts +53 -10
- package/extensions/rewind/review-tui.ts +320 -0
- package/extensions/rewind/review.ts +397 -0
- package/extensions/rewind/rewind-tui.ts +4 -3
- package/extensions/rewind/runtime.ts +94 -17
- package/extensions/rewind/snapshot.ts +13 -4
- package/extensions/subagent/README.md +61 -6
- package/extensions/subagent/background-tui.ts +208 -0
- package/extensions/subagent/background.ts +470 -0
- package/extensions/subagent/commands.ts +80 -0
- package/extensions/subagent/config.ts +4 -0
- package/extensions/subagent/format.ts +24 -1
- package/extensions/subagent/index.ts +91 -2
- package/extensions/subagent/render.ts +13 -0
- package/extensions/subagent/run.ts +17 -16
- package/extensions/subagent/schema.ts +12 -0
- package/extensions/subagent/types.ts +11 -0
- package/extensions/subagent/ui.ts +50 -0
- package/extensions/todo/README.md +18 -13
- package/extensions/todo/format.ts +3 -2
- package/extensions/todo/runtime.ts +3 -2
- package/extensions/todo/schema.ts +8 -84
- package/extensions/todo/state.ts +5 -46
- package/extensions/todo/tools.ts +2 -1
- package/extensions/todo/types.ts +6 -15
- package/extensions/watch/README.md +14 -6
- package/extensions/watch/child.ts +150 -0
- package/extensions/watch/format.ts +8 -48
- package/extensions/watch/index.ts +42 -58
- package/extensions/watch/runtime.ts +49 -128
- package/extensions/watch/schema.ts +1 -4
- package/extensions/watch/tools.ts +1 -11
- package/extensions/watch/tui.ts +103 -273
- package/extensions/watch/ui.ts +5 -32
- package/extensions/web-access/README.md +1 -1
- package/extensions/web-access/fetch/page.ts +1 -1
- package/extensions/web-access/fetch/tool.ts +6 -2
- package/extensions/web-access/search/search.ts +2 -5
- package/extensions/wechat/README.md +1 -1
- package/extensions/wechat/client.ts +23 -0
- package/extensions/wechat/commands.ts +57 -42
- package/extensions/wechat/deliver.ts +5 -9
- package/extensions/wechat/errors.ts +3 -6
- package/extensions/wechat/inbound.ts +2 -5
- package/extensions/wechat/index.ts +2 -8
- package/extensions/wechat/outbound.ts +2 -4
- package/extensions/wechat/peer-worker.ts +3 -8
- package/extensions/wechat/poll.ts +10 -9
- package/extensions/wechat/presence.ts +35 -62
- package/extensions/wechat/reply-pipeline.ts +3 -1
- package/extensions/wechat/status-screen.ts +1 -1
- package/extensions/wechat/timer.ts +57 -0
- package/extensions/worktree/config.ts +8 -20
- package/extensions/worktree/enter.ts +7 -12
- package/extensions/worktree/git.ts +38 -69
- package/extensions/worktree/index.ts +5 -4
- package/extensions/worktree/registry.ts +7 -7
- package/extensions/worktree/root-tools.ts +2 -2
- package/extensions/worktree/status.ts +15 -9
- package/lib/README.md +23 -8
- package/lib/config.ts +50 -3
- package/lib/errors.ts +12 -0
- package/lib/format.ts +50 -0
- package/lib/git/read.ts +28 -0
- package/lib/goal-state.ts +23 -8
- package/lib/http/headers.ts +74 -0
- package/lib/http/index.ts +13 -0
- package/lib/http/response.ts +46 -0
- package/lib/http/router.ts +52 -0
- package/lib/http/server.ts +109 -0
- package/lib/lines.ts +77 -0
- package/lib/pdf.ts +82 -0
- package/lib/plan-entry.ts +71 -0
- package/lib/plan-state.ts +34 -0
- package/lib/pool.ts +62 -0
- package/lib/report.ts +99 -0
- package/lib/requirements.ts +25 -19
- package/lib/spawn.ts +170 -0
- package/lib/todo-state.ts +110 -23
- package/lib/tool-names.ts +7 -0
- package/lib/tool-result.ts +33 -0
- package/lib/tui/README.md +68 -3
- package/lib/tui/chrome.ts +28 -4
- package/lib/tui/columns.ts +6 -1
- package/lib/tui/confirm.ts +65 -0
- package/lib/tui/dock-screen.ts +418 -0
- package/lib/tui/glyphs.ts +2 -1
- package/lib/tui/indent.ts +1 -1
- package/lib/tui/index.ts +2 -0
- package/lib/tui/rows.ts +21 -0
- package/lib/tui/status-controller.ts +35 -1
- package/lib/tui/tool.ts +59 -1
- package/lib/web/components.ts +20 -1
- package/lib/web/markup.ts +32 -0
- package/lib/web/runtime.ts +50 -0
- package/package.json +22 -5
- package/skills/init/SKILL.md +165 -0
- package/skills/init/references/agents-md-quality.md +103 -0
- package/skills/init/references/interview-questions.md +81 -0
- package/skills/refactor/SKILL.md +141 -0
- package/extensions/watch/process.ts +0 -63
- package/extensions/web-access/fetch/pdf.ts +0 -45
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
A `subagent` tool that hands a task to a specialized agent running in its own
|
|
4
4
|
`pi --mode json` process. The subagent gets a fresh context window, so broad
|
|
5
|
-
exploration or parallel work does not fill the main conversation.
|
|
5
|
+
exploration or parallel work does not fill the main conversation. A single run
|
|
6
|
+
can also start in the background and be inspected, collected, or cancelled with
|
|
7
|
+
`/agents`.
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
10
|
pi --extension ./extensions/subagent # load just this extension
|
|
@@ -16,6 +18,10 @@ How the `subagent` call and its result look in the transcript — the call line
|
|
|
16
18
|
|
|
17
19
|
<img src="../../assets/tools/subagent.png" alt="The subagent tool's call and result: the call line and an indented body with the two tasks, each agent's section rule, its summary, and the usage total" width="100%">
|
|
18
20
|
|
|
21
|
+
## Screens
|
|
22
|
+
|
|
23
|
+
<img src="../../assets/agents.png" alt="The /agents screen: running and finished background helpers with status, task, elapsed time, and usage" width="100%">
|
|
24
|
+
|
|
19
25
|
## What it does
|
|
20
26
|
|
|
21
27
|
- **Isolated context.** Each subagent is a separate `pi` process with its own
|
|
@@ -25,7 +31,10 @@ How the `subagent` call and its result look in the transcript — the call line
|
|
|
25
31
|
- **External agents.** Optionally load markdown agents from `~/.pi/agents`
|
|
26
32
|
(user) and the nearest `.pi/agents` (project). They are opt-in per scope and
|
|
27
33
|
never replace the built-ins silently.
|
|
28
|
-
- **Three modes.** One agent, a parallel batch, or a sequential
|
|
34
|
+
- **Three modes, plus background.** One agent, a parallel batch, or a sequential
|
|
35
|
+
chain; a single run can also continue after the tool returns and be collected
|
|
36
|
+
from `/agents`. A running helper adds a `↳ N` footer chip, and a finish notice
|
|
37
|
+
points back at `/agents`.
|
|
29
38
|
- **Streaming.** Tool calls and progress update live in the transcript while the
|
|
30
39
|
subagent runs; the `app.tools.expand` key (`ctrl+o` by default) expands the
|
|
31
40
|
full task, tool calls, Markdown output, and usage.
|
|
@@ -44,6 +53,7 @@ How the `subagent` call and its result look in the transcript — the call line
|
|
|
44
53
|
| Field | Value |
|
|
45
54
|
|---|---|
|
|
46
55
|
| `agent` + `task` | Single mode: one built-in/external agent and its task. |
|
|
56
|
+
| `background` | Single mode only: start the run and return immediately; manage it with `/agents`. |
|
|
47
57
|
| `tasks[]` | Parallel mode: independent `{agent, task, cwd?}` entries, max 8. |
|
|
48
58
|
| `chain[]` | Chain mode: ordered `{agent, task, cwd?}` steps, max 8; `{previous}` is replaced by the prior step's final output. |
|
|
49
59
|
| `cwd` | Optional working directory for the spawned process (single mode; per-step for the others). A relative path resolves against the active root. |
|
|
@@ -60,6 +70,21 @@ How the `subagent` call and its result look in the transcript — the call line
|
|
|
60
70
|
{ "agent": "explorer", "task": "Find every place the HTTP client is retried", "cwd": "." }
|
|
61
71
|
```
|
|
62
72
|
|
|
73
|
+
### Background
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{ "agent": "explorer", "task": "Map the auth module", "background": true }
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The tool returns immediately with a run id such as `a2`. `/agents` lists every
|
|
80
|
+
run with its status, elapsed time, and usage; `Enter` opens the run, `c` collects
|
|
81
|
+
a finished result into the conversation, and `x` cancels a running one. While a
|
|
82
|
+
run is active, its detail pane follows a bounded preview of the latest output,
|
|
83
|
+
and a `↳ N` chip in the footer counts running helpers. A finish notice points
|
|
84
|
+
back at `/agents`; the finalized result replaces the preview when the run
|
|
85
|
+
finishes. A background run is session-owned and is bounded by the same timeout
|
|
86
|
+
and transcript settings as a foreground run.
|
|
87
|
+
|
|
63
88
|
### Parallel
|
|
64
89
|
|
|
65
90
|
Runs up to 8 tasks, at most 4 at once. Independent tasks only.
|
|
@@ -92,11 +117,20 @@ at most 8 steps (like the parallel task cap).
|
|
|
92
117
|
}
|
|
93
118
|
```
|
|
94
119
|
|
|
120
|
+
## Command
|
|
121
|
+
|
|
122
|
+
| Command | What it does |
|
|
123
|
+
|---|---|
|
|
124
|
+
| `/agents` | Open the background-run board: inspect, collect, or cancel. |
|
|
125
|
+
|
|
126
|
+
The board is TUI-only for actions; RPC, print, and JSON print the run list.
|
|
127
|
+
Collecting sends the result as a user message, which starts the next turn.
|
|
128
|
+
|
|
95
129
|
## Configuration
|
|
96
130
|
|
|
97
131
|
Read from the shared `bytefly.json` section `subagent` (global then project),
|
|
98
132
|
once per session. Keys and bounds live in the
|
|
99
|
-
[configuration
|
|
133
|
+
[configuration page](../../docs/configuration.md#subagent--delegated-runs);
|
|
100
134
|
the behaviour is here.
|
|
101
135
|
|
|
102
136
|
- **`timeoutMs`** (default ten minutes, `0` disables) bounds one child run. The
|
|
@@ -109,6 +143,10 @@ the behaviour is here.
|
|
|
109
143
|
runs on disk, all of them or only the failed ones.
|
|
110
144
|
- **`retentionDays`** (default 14) and **`maxRuns`** (default 100) bound the
|
|
111
145
|
store. It is pruned at `session_start`, and only while transcripts are on.
|
|
146
|
+
- **`backgroundMax`** (default 4, `1`–`8`) bounds concurrently running
|
|
147
|
+
background children. Finished runs do not consume a slot. A run that is still
|
|
148
|
+
running when the session ends is interrupted; the next `/agents` shows the
|
|
149
|
+
start entry without a finish.
|
|
112
150
|
|
|
113
151
|
### Transcripts
|
|
114
152
|
|
|
@@ -289,6 +327,19 @@ step's `Output`.
|
|
|
289
327
|
starts from zero, and a run that never produced an assistant message publishes
|
|
290
328
|
nothing.
|
|
291
329
|
|
|
330
|
+
## Limitations
|
|
331
|
+
|
|
332
|
+
- Background runs are single-mode only. Parallel and chain calls wait as before.
|
|
333
|
+
- A background run is session-owned: `/tree` does not move it, and a process
|
|
334
|
+
restart cannot resume it. A start entry without a finish is shown as
|
|
335
|
+
`interrupted`.
|
|
336
|
+
- The collected output is capped at 50 KiB; the optional transcript keeps the
|
|
337
|
+
full stream. The `/agents` detail keeps only a bounded live preview, and a
|
|
338
|
+
finished run keeps its bounded output in the session record rather than the
|
|
339
|
+
child's full result.
|
|
340
|
+
- A finish notice appears in the TUI, but there is no OS notification; the run
|
|
341
|
+
remains listed in `/agents`.
|
|
342
|
+
|
|
292
343
|
## Pi integration
|
|
293
344
|
|
|
294
345
|
| Aspect | Detail |
|
|
@@ -298,8 +349,8 @@ step's `Output`.
|
|
|
298
349
|
| Annotations | `readOnlyHint: false`, `openWorldHint: true` |
|
|
299
350
|
| Output schema | none; the full result lives in tool-result `details` |
|
|
300
351
|
| Execution | spawns `pi --mode json` subprocesses; external agents discovered from `<agent-dir>/agents` and `.pi/agents` |
|
|
301
|
-
| State storage | tool-result `details`; optional per-run transcripts under `.pi/subagents
|
|
302
|
-
| Lifecycle | `session_start` loads the config
|
|
352
|
+
| State storage | tool-result `details`; optional per-run transcripts under `.pi/subagents/`; background start/finish entries (`subagent-background`) on the session branch |
|
|
353
|
+
| Lifecycle | `session_start` loads the config, prunes expired transcripts, and attaches the footer chip; `session_shutdown` aborts every live background child and clears the chip |
|
|
303
354
|
|
|
304
355
|
## Design notes
|
|
305
356
|
|
|
@@ -373,7 +424,11 @@ step's `Output`.
|
|
|
373
424
|
| `stream.ts` | JSON-event parsing, usage accounting, output helpers (pure). |
|
|
374
425
|
| `transcripts.ts` | Per-run transcript buffer, run ids, the store directory, and pruning. |
|
|
375
426
|
| `config.ts` | The `subagent` section of `bytefly.json`: deadline, transcripts, retention. |
|
|
376
|
-
| `run.ts` | Runs one subagent process and captures its result. |
|
|
427
|
+
| `run.ts` | Runs one subagent process and captures its result (including the background abort-as-result path). |
|
|
428
|
+
| `background.ts` | Background run entries, replay, capacity, cancel, collect, and shutdown. |
|
|
429
|
+
| `background-tui.ts` | The `/agents` board: a `DockListScreen` with a live output pane and width-aware facts. |
|
|
430
|
+
| `commands.ts` | The `/agents` command. |
|
|
431
|
+
| `ui.ts` | The running-count footer chip and finish notices. |
|
|
377
432
|
| `format.ts` | Token/usage/tool-call formatting (pure). |
|
|
378
433
|
| `render.ts` | `renderCall` / `renderResult` transcript rendering. |
|
|
379
434
|
| `types.ts` | Shared result and process types. |
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `/agents` board: background runs, live status, and a per-run detail.
|
|
3
|
+
*
|
|
4
|
+
* The board reads runs from a getter on every render, so a finish or cancel is
|
|
5
|
+
* reflected without rebuilding the component. The frame, the id-anchored cursor,
|
|
6
|
+
* the navigation and wheel keys, and the pane lifecycle come from the shared
|
|
7
|
+
* `DockListScreen`, so this module owns only the run vocabulary: the row, the
|
|
8
|
+
* status view, the key/value facts, the hints, and what `c` and `x` do.
|
|
9
|
+
*
|
|
10
|
+
* Two of that base's optional members are why the board can be one: the pane is
|
|
11
|
+
* live (`liveDetail`), because a running child's output arrives between frames
|
|
12
|
+
* and nothing polls it, and the facts are a `facts` hook, because the key/value
|
|
13
|
+
* rows are built from the current width.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
17
|
+
import { sanitizeLine, stripControlChars } from "../../lib/format.ts";
|
|
18
|
+
import type { ScreenHint, ViewportRowsSource } from "../../lib/tui/chrome.ts";
|
|
19
|
+
import { DockListScreen, type DetailContents } from "../../lib/tui/dock-screen.ts";
|
|
20
|
+
import { GLYPHS, SEPARATORS } from "../../lib/tui/glyphs.ts";
|
|
21
|
+
import { keyValueRow, statusRow } from "../../lib/tui/rows.ts";
|
|
22
|
+
import { paint, type TuiRole } from "../../lib/tui/tokens.ts";
|
|
23
|
+
import type { BackgroundRun, BackgroundStatus } from "./background.ts";
|
|
24
|
+
import { canCollect } from "./background.ts";
|
|
25
|
+
import { formatBackgroundDuration, formatUsageStats } from "./format.ts";
|
|
26
|
+
|
|
27
|
+
/** One status's glyph, role, and readable label. */
|
|
28
|
+
function statusView(status: BackgroundStatus): { glyph: string; role: TuiRole; label: string } {
|
|
29
|
+
switch (status) {
|
|
30
|
+
case "running":
|
|
31
|
+
return { glyph: GLYPHS.jobsRunning, role: "warn", label: "running" };
|
|
32
|
+
case "finished":
|
|
33
|
+
return { glyph: GLYPHS.check, role: "ok", label: "finished" };
|
|
34
|
+
case "failed":
|
|
35
|
+
return { glyph: GLYPHS.jobsFailure, role: "bad", label: "failed" };
|
|
36
|
+
case "cancelled":
|
|
37
|
+
return { glyph: GLYPHS.stopped, role: "warn", label: "cancelled" };
|
|
38
|
+
case "timedout":
|
|
39
|
+
return { glyph: GLYPHS.warn, role: "bad", label: "timed out" };
|
|
40
|
+
case "interrupted":
|
|
41
|
+
return { glyph: GLYPHS.unknown, role: "faint", label: "interrupted" };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** A short task label for a list row. */
|
|
46
|
+
function taskLabel(run: BackgroundRun): string {
|
|
47
|
+
return sanitizeLine(run.task).split("\n")[0] ?? "";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The output rows for the detail pane. */
|
|
51
|
+
function outputLines(run: BackgroundRun): string[] {
|
|
52
|
+
const text =
|
|
53
|
+
run.output?.trim() ||
|
|
54
|
+
(run.status === "running"
|
|
55
|
+
? "Still running…"
|
|
56
|
+
: run.status === "interrupted"
|
|
57
|
+
? "Interrupted before it finished."
|
|
58
|
+
: "(no output recorded)");
|
|
59
|
+
return text.split("\n").map((line) => stripControlChars(line).replaceAll("\t", " ").slice(0, 2_000));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The detail key/value rows. */
|
|
63
|
+
function detailRows(run: BackgroundRun, now: number, theme: Theme, width: number): string[] {
|
|
64
|
+
const usage = run.usage ? formatUsageStats(run.usage) : "(not reported)";
|
|
65
|
+
const rows = [
|
|
66
|
+
keyValueRow(theme, width, { label: "Task", value: taskLabel(run), labelWidth: 12 }),
|
|
67
|
+
keyValueRow(theme, width, { label: "Cwd", value: sanitizeLine(run.cwd), labelWidth: 12 }),
|
|
68
|
+
keyValueRow(theme, width, { label: "Status", value: statusView(run.status).label, labelWidth: 12 }),
|
|
69
|
+
keyValueRow(theme, width, { label: "Duration", value: formatBackgroundDuration(run, now), labelWidth: 12 }),
|
|
70
|
+
keyValueRow(theme, width, { label: "Usage", value: usage, labelWidth: 12 }),
|
|
71
|
+
];
|
|
72
|
+
if (run.errorMessage) {
|
|
73
|
+
rows.push(keyValueRow(theme, width, { label: "Error", value: sanitizeLine(run.errorMessage), labelWidth: 12 }));
|
|
74
|
+
}
|
|
75
|
+
if (run.artifactPath) {
|
|
76
|
+
rows.push(
|
|
77
|
+
keyValueRow(theme, width, { label: "Transcript", value: sanitizeLine(run.artifactPath), labelWidth: 12 }),
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
return rows;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface AgentsBoardOptions {
|
|
84
|
+
runs: () => readonly BackgroundRun[];
|
|
85
|
+
theme: Theme;
|
|
86
|
+
now?: () => number;
|
|
87
|
+
/** Collect a finished run; the command closes the board and sends it. */
|
|
88
|
+
onCollect: (runId: string) => void;
|
|
89
|
+
/** Cancel a running run; the board stays open. */
|
|
90
|
+
onCancel: (runId: string) => void;
|
|
91
|
+
onClose: () => void;
|
|
92
|
+
requestRender: () => void;
|
|
93
|
+
viewportRows?: ViewportRowsSource;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** The list/detail board for background subagent runs. */
|
|
97
|
+
export class AgentsBoardComponent extends DockListScreen<BackgroundRun> {
|
|
98
|
+
constructor(private readonly board: AgentsBoardOptions) {
|
|
99
|
+
super({
|
|
100
|
+
theme: board.theme,
|
|
101
|
+
title: "Agents",
|
|
102
|
+
detailTitle: "Run",
|
|
103
|
+
emptyList: "No background subagents yet.",
|
|
104
|
+
emptyDetail: "(no output recorded)",
|
|
105
|
+
requestRender: board.requestRender,
|
|
106
|
+
onClose: board.onClose,
|
|
107
|
+
viewportRowsSource: board.viewportRows,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private get now(): number {
|
|
112
|
+
return (this.board.now ?? Date.now)();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** `c` collects a finished run and `x` cancels a running one, in either mode. */
|
|
116
|
+
private act(key: string, run: BackgroundRun | undefined): boolean {
|
|
117
|
+
if (!run) return false;
|
|
118
|
+
if (key === "c" && canCollect(run)) {
|
|
119
|
+
this.board.onCollect(run.id);
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
if (key === "x" && run.status === "running") {
|
|
123
|
+
this.board.onCancel(run.id);
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
protected rows(): readonly BackgroundRun[] {
|
|
130
|
+
return this.board.runs();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
protected idOf(run: BackgroundRun): string {
|
|
134
|
+
return run.id;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
protected rowLine(run: BackgroundRun, width: number, selected: boolean): string {
|
|
138
|
+
const view = statusView(run.status);
|
|
139
|
+
const usage = run.usage ? formatUsageStats(run.usage) : "";
|
|
140
|
+
const trailing = [view.label, formatBackgroundDuration(run, this.now), usage].filter(Boolean).join(SEPARATORS.item);
|
|
141
|
+
return statusRow(this.theme, width, {
|
|
142
|
+
selected,
|
|
143
|
+
glyph: view.glyph,
|
|
144
|
+
glyphRole: view.role,
|
|
145
|
+
label: `${run.id} ${sanitizeLine(run.agent)} ${taskLabel(run)}`,
|
|
146
|
+
labelRole: selected ? "accent" : "label",
|
|
147
|
+
trailing,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** The board has never carried a focus block: the detail pane states the facts. */
|
|
152
|
+
protected detailBlock(): string[] {
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
protected summaryCells(rows: readonly BackgroundRun[]): string[] {
|
|
157
|
+
const running = rows.filter((run) => run.status === "running").length;
|
|
158
|
+
const cells = [paint(this.theme, "meta", `${rows.length} run${rows.length === 1 ? "" : "s"}`)];
|
|
159
|
+
if (running > 0) cells.push(paint(this.theme, "meta", `${running} running`));
|
|
160
|
+
return cells;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
protected listHints(): ScreenHint[] {
|
|
164
|
+
const focused = this.focusedRow;
|
|
165
|
+
const hints: ScreenHint[] = ["Esc close", { text: "Enter detail", color: "accent" }];
|
|
166
|
+
if (focused && canCollect(focused)) hints.push({ text: "c collect", color: "accent" });
|
|
167
|
+
if (focused?.status === "running") hints.push({ text: "x cancel", color: "warning" });
|
|
168
|
+
hints.push("↑/↓ select");
|
|
169
|
+
return hints;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
protected detailHints(run: BackgroundRun | undefined): ScreenHint[] {
|
|
173
|
+
const hints: ScreenHint[] = ["Esc back"];
|
|
174
|
+
if (run && canCollect(run)) hints.push({ text: "c collect", color: "accent" });
|
|
175
|
+
if (run?.status === "running") hints.push({ text: "x cancel", color: "warning" });
|
|
176
|
+
hints.push("j/k scroll");
|
|
177
|
+
return hints;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
protected detailFor(run: BackgroundRun): DetailContents {
|
|
181
|
+
return {
|
|
182
|
+
title: `${run.id} ${sanitizeLine(run.agent)}`,
|
|
183
|
+
subtitle: `${statusView(run.status).label}${SEPARATORS.item}${formatBackgroundDuration(run, this.now)}`,
|
|
184
|
+
// The key/value rows need the current width, so they are rebuilt per frame.
|
|
185
|
+
facts: (width) => detailRows(run, this.now, this.theme, width),
|
|
186
|
+
lines: outputLines(run),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** The output a live run keeps producing; a removed run has none, which closes the pane. */
|
|
191
|
+
protected detailById(id: string): { lines: string[] } | undefined {
|
|
192
|
+
const run = this.rows().find((entry) => entry.id === id);
|
|
193
|
+
return run ? { lines: outputLines(run) } : undefined;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Re-read the pane on every frame: a child's output arrives without a poll. */
|
|
197
|
+
protected liveDetail(): boolean {
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
protected handleVerb(key: string, focused: BackgroundRun | undefined): void {
|
|
202
|
+
this.act(key, focused);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
protected handleDetailVerb(key: string, run: BackgroundRun): boolean {
|
|
206
|
+
return this.act(key, run);
|
|
207
|
+
}
|
|
208
|
+
}
|