@gavin-hu/my-pi-agent 0.13.1 → 0.15.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 +66 -41
- package/README.zh-CN.md +53 -36
- package/extensions/bytefly/README.md +321 -186
- package/extensions/bytefly/block/component.ts +7 -15
- package/extensions/bytefly/block/register.ts +6 -1
- package/extensions/bytefly/doctor/config.ts +4 -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/config.ts +3 -2
- 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 +33 -10
- package/extensions/bytefly/header/config.ts +22 -0
- package/extensions/bytefly/header/register.ts +2 -1
- package/extensions/bytefly/index.ts +6 -6
- package/extensions/bytefly/notice/component.ts +8 -3
- package/extensions/bytefly/notify/config.ts +7 -12
- package/extensions/bytefly/notify/dispatch.ts +13 -5
- package/extensions/bytefly/panel/commands.ts +22 -2
- package/extensions/bytefly/panel/format.ts +17 -21
- package/extensions/bytefly/panel/settings-runtime.ts +86 -22
- package/extensions/bytefly/panel/settings.ts +279 -46
- 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 +98 -13
- package/extensions/bytefly/panel/tui.ts +176 -166
- package/extensions/bytefly/picker/discovery.ts +1 -0
- package/extensions/bytefly/picker/types.ts +2 -0
- package/extensions/bytefly/separator/component.ts +17 -6
- package/extensions/bytefly/separator/config.ts +15 -0
- package/extensions/bytefly/separator/format.ts +60 -6
- package/extensions/bytefly/separator/register.ts +68 -9
- package/extensions/bytefly/separator/types.ts +8 -0
- 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/bytefly/working/config.ts +4 -5
- package/extensions/bytefly/working/format.ts +24 -7
- package/extensions/bytefly/working/register.ts +34 -3
- 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 +66 -32
- package/extensions/file-browse/client.ts +95 -32
- package/extensions/file-browse/config.ts +9 -9
- package/extensions/file-browse/git.ts +37 -47
- package/extensions/file-browse/html.ts +39 -58
- package/extensions/file-browse/markdown.ts +110 -92
- package/extensions/file-browse/raw-html.ts +478 -0
- package/extensions/file-browse/router.ts +32 -70
- package/extensions/file-browse/server.ts +18 -56
- 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 +3 -1
- package/extensions/memory/errors.ts +5 -6
- package/extensions/memory/tui.ts +14 -24
- package/extensions/plan/README.md +106 -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 +34 -71
- package/extensions/plan/web.ts +13 -40
- 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 +185 -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 +74 -7
- 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/subagent/README.md +118 -8
- 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 +62 -0
- package/extensions/subagent/format.ts +24 -1
- package/extensions/subagent/index.ts +116 -1
- package/extensions/subagent/orchestrate.ts +16 -3
- package/extensions/subagent/render.ts +25 -0
- package/extensions/subagent/run.ts +113 -49
- package/extensions/subagent/schema.ts +12 -0
- package/extensions/subagent/transcripts.ts +202 -0
- package/extensions/subagent/types.ts +23 -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 +16 -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 +3 -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 +3 -2
- package/lib/README.md +22 -8
- package/lib/config.ts +83 -12
- package/lib/errors.ts +12 -0
- package/lib/format.ts +56 -0
- package/lib/git/read.ts +8 -0
- package/lib/goal-state.ts +23 -8
- package/lib/http/headers.ts +54 -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 +91 -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/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 +410 -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 +23 -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/themes/bytefly-dark.json +2 -3
- package/themes/bytefly-light.json +2 -3
- 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,10 +31,17 @@ 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.
|
|
41
|
+
- **Bounded runs.** A per-run deadline kills a child that hangs, and reports it
|
|
42
|
+
as a failed run instead of rejecting the whole call.
|
|
43
|
+
- **Optional transcripts.** Opt-in per-run stream under `.pi/subagents/`,
|
|
44
|
+
written by the parent and pruned by age and count.
|
|
32
45
|
- **Inherited model.** Subagents use the dispatching session's model and thinking
|
|
33
46
|
level unless an agent overrides them (none do).
|
|
34
47
|
|
|
@@ -40,6 +53,7 @@ How the `subagent` call and its result look in the transcript — the call line
|
|
|
40
53
|
| Field | Value |
|
|
41
54
|
|---|---|
|
|
42
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`. |
|
|
43
57
|
| `tasks[]` | Parallel mode: independent `{agent, task, cwd?}` entries, max 8. |
|
|
44
58
|
| `chain[]` | Chain mode: ordered `{agent, task, cwd?}` steps, max 8; `{previous}` is replaced by the prior step's final output. |
|
|
45
59
|
| `cwd` | Optional working directory for the spawned process (single mode; per-step for the others). A relative path resolves against the active root. |
|
|
@@ -56,6 +70,21 @@ How the `subagent` call and its result look in the transcript — the call line
|
|
|
56
70
|
{ "agent": "explorer", "task": "Find every place the HTTP client is retried", "cwd": "." }
|
|
57
71
|
```
|
|
58
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
|
+
|
|
59
88
|
### Parallel
|
|
60
89
|
|
|
61
90
|
Runs up to 8 tasks, at most 4 at once. Independent tasks only.
|
|
@@ -88,6 +117,55 @@ at most 8 steps (like the parallel task cap).
|
|
|
88
117
|
}
|
|
89
118
|
```
|
|
90
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
|
+
|
|
129
|
+
## Configuration
|
|
130
|
+
|
|
131
|
+
Read from the shared `bytefly.json` section `subagent` (global then project),
|
|
132
|
+
once per session. Keys and bounds live in the
|
|
133
|
+
[configuration page](../../docs/configuration.md#subagent--delegated-runs);
|
|
134
|
+
the behaviour is here.
|
|
135
|
+
|
|
136
|
+
- **`timeoutMs`** (default ten minutes, `0` disables) bounds one child run. The
|
|
137
|
+
limit is per run, not per call, so a chain is bounded step by step and a
|
|
138
|
+
parallel batch bounds each task separately. On expiry the child gets the same
|
|
139
|
+
`SIGTERM`, then `SIGKILL` after 5 s, as a user abort — but the run fails
|
|
140
|
+
instead of throwing: a chain stops at that step, a parallel call counts a
|
|
141
|
+
failure, and the model sees `Timed out after 10m00s`.
|
|
142
|
+
- **`transcripts`** (`off`, `failed`, or `all`; default `off`) keeps finished
|
|
143
|
+
runs on disk, all of them or only the failed ones.
|
|
144
|
+
- **`retentionDays`** (default 14) and **`maxRuns`** (default 100) bound the
|
|
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.
|
|
150
|
+
|
|
151
|
+
### Transcripts
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
.pi/subagents/20261015-143012-a1b2c3/
|
|
155
|
+
meta.json agent, task, cwd, model, timings, exit, status, usage
|
|
156
|
+
events.jsonl the child's raw stdout lines, verbatim
|
|
157
|
+
stderr.log only when the child wrote any
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The store is resolved against the **session** cwd, so inside a worktree it lands
|
|
161
|
+
in the main checkout's `.pi/` and outlives that worktree — the anchoring the
|
|
162
|
+
`job` registry uses. Outside a repository it falls back to
|
|
163
|
+
`<agent-dir>/subagents`. The parent writes it and the child never learns it
|
|
164
|
+
exists, so `readOnly`, the `--tools` allowlist, and the project-agent trust gate
|
|
165
|
+
are unchanged. Writing is best-effort: an unwritable checkout records the failure
|
|
166
|
+
in the result and never fails a run. The stream is buffered in the parent up to
|
|
167
|
+
32 MiB, then truncated with a marker rather than silently dropped.
|
|
168
|
+
|
|
91
169
|
## Security
|
|
92
170
|
|
|
93
171
|
A subagent is a real `pi` process and shares Pi's OS permissions. The built-in
|
|
@@ -206,7 +284,10 @@ Markdown, and per-step/per-task usage with a total.
|
|
|
206
284
|
|
|
207
285
|
A failed or aborted result always shows its `errorMessage`, falling back to the
|
|
208
286
|
subprocess `stderr`, in both the collapsed and expanded views, in every mode. A
|
|
209
|
-
|
|
287
|
+
run that exceeded its deadline reports `Timed out after …` the same way, and the
|
|
288
|
+
expanded view adds the transcript path, the truncation marker, or why the
|
|
289
|
+
transcript could not be written. A failure with neither falls back to
|
|
290
|
+
`Error: Subprocess exited with code N.` and an
|
|
210
291
|
`[exit N]` bracket, so a non-zero exit is never silent. A child terminated by a
|
|
211
292
|
signal (an OOM kill, a crash) has no exit code, so it is recorded as
|
|
212
293
|
`Terminated by <signal>` and reported as a failure. Parallel and chain
|
|
@@ -232,8 +313,10 @@ step's `Output`.
|
|
|
232
313
|
does not reimplement tool execution or model streaming.
|
|
233
314
|
- **No pinned models.** Built-in agents set no `model`, so a subprocess inherits
|
|
234
315
|
the dispatching session's provider/model and thinking level.
|
|
235
|
-
- **No persistence.**
|
|
236
|
-
|
|
316
|
+
- **No persistence by default.** A run's transcript lives in the tool result
|
|
317
|
+
details; keeping it on disk is opt-in, parent-written, and bounded by age and
|
|
318
|
+
count. It is an audit and handoff record, not a resume point: replaying a run
|
|
319
|
+
whose writes already landed is not attempted.
|
|
237
320
|
- **No cost attribution to the parent session.** A subagent's tokens and cost are
|
|
238
321
|
captured in the tool result and shown in the transcript, but they belong to a
|
|
239
322
|
separate process and are not added to the dispatching session's totals. Instead,
|
|
@@ -244,6 +327,19 @@ step's `Output`.
|
|
|
244
327
|
starts from zero, and a run that never produced an assistant message publishes
|
|
245
328
|
nothing.
|
|
246
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
|
+
|
|
247
343
|
## Pi integration
|
|
248
344
|
|
|
249
345
|
| Aspect | Detail |
|
|
@@ -253,8 +349,8 @@ step's `Output`.
|
|
|
253
349
|
| Annotations | `readOnlyHint: false`, `openWorldHint: true` |
|
|
254
350
|
| Output schema | none; the full result lives in tool-result `details` |
|
|
255
351
|
| Execution | spawns `pi --mode json` subprocesses; external agents discovered from `<agent-dir>/agents` and `.pi/agents` |
|
|
256
|
-
| State storage | tool-result `details
|
|
257
|
-
| Lifecycle |
|
|
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 |
|
|
258
354
|
|
|
259
355
|
## Design notes
|
|
260
356
|
|
|
@@ -284,6 +380,14 @@ step's `Output`.
|
|
|
284
380
|
results arrive as `message_end` (messages, usage, model, stop reason) and
|
|
285
381
|
failures as `tool_execution_end`. A tool error does not by itself fail the run,
|
|
286
382
|
because the agent may recover.
|
|
383
|
+
- **A deadline reuses the abort kill path but not its outcome.** One kill
|
|
384
|
+
closure serves both triggers, so the SIGTERM → 5 s SIGKILL escalation is
|
|
385
|
+
identical; only the reporting differs, because a deadline must leave the
|
|
386
|
+
finished steps in `details` while a user abort rejects the call.
|
|
387
|
+
- **The transcript is the parent's.** Teeing the child's stdout and stderr in
|
|
388
|
+
`spawnAndCollect` and flushing in `runSingleAgent`'s `finally` means the
|
|
389
|
+
artifact exists even on the abort path, and the child needs no tool, no prompt
|
|
390
|
+
line, and no writable store.
|
|
287
391
|
- **Bounded parallel output.** Parallel mode runs at most 4 of up to 8 tasks at
|
|
288
392
|
once and caps each model-facing section at 50 KiB, appending an omission notice
|
|
289
393
|
pointing at the tool details. The full results stay in `details` for rendering.
|
|
@@ -318,7 +422,13 @@ step's `Output`.
|
|
|
318
422
|
| `schema.ts` | TypeBox params, limits, and `resolveMode`. |
|
|
319
423
|
| `invocation.ts` | `pi` subprocess invocation and argument construction. |
|
|
320
424
|
| `stream.ts` | JSON-event parsing, usage accounting, output helpers (pure). |
|
|
321
|
-
| `
|
|
425
|
+
| `transcripts.ts` | Per-run transcript buffer, run ids, the store directory, and pruning. |
|
|
426
|
+
| `config.ts` | The `subagent` section of `bytefly.json`: deadline, transcripts, retention. |
|
|
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. |
|
|
322
432
|
| `format.ts` | Token/usage/tool-call formatting (pure). |
|
|
323
433
|
| `render.ts` | `renderCall` / `renderResult` transcript rendering. |
|
|
324
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.focused;
|
|
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
|
+
}
|