@maxiaochao/pi-toolkit 0.10.1 → 0.10.3
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/CHANGELOG.md +12 -0
- package/README.md +1 -1
- package/global/AGENTS.md +33 -50
- package/package.json +2 -2
- package/packages/herdr-subagents/CHANGELOG.md +14 -0
- package/packages/herdr-subagents/README.md +20 -18
- package/packages/herdr-subagents/TESTING.md +6 -4
- package/packages/herdr-subagents/docs/demo.webm +0 -0
- package/packages/herdr-subagents/docs/screenshot.png +0 -0
- package/packages/herdr-subagents/extensions/herdr-subagents.ts +91 -51
- package/packages/herdr-subagents/package.json +1 -1
- package/packages/herdr-subagents/src/batch-runner.ts +5 -5
- package/packages/herdr-subagents/src/blocking.ts +4 -4
- package/packages/herdr-subagents/src/fleet-controller.ts +164 -16
- package/packages/herdr-subagents/src/fleet.ts +119 -8
- package/packages/herdr-subagents/src/history.ts +11 -5
- package/packages/herdr-subagents/src/parameters.ts +16 -13
- package/packages/herdr-subagents/src/queued-runs.ts +3 -3
- package/packages/herdr-subagents/src/records.ts +53 -1
- package/packages/herdr-subagents/src/tool.ts +40 -6
- package/packages/herdr-subagents/src/validation.ts +16 -29
- package/packages/herdr-subagents/tests/extension-contract.ts +27 -9
- package/packages/herdr-subagents/tests/run-tests.mjs +153 -18
- package/packages/todo/CHANGELOG.md +4 -0
- package/packages/todo/README.md +19 -9
- package/packages/todo/TESTING.md +9 -7
- package/packages/todo/docs/demo.gif +0 -0
- package/packages/todo/docs/screenshot.png +0 -0
- package/packages/todo/extensions/todo.ts +120 -90
- package/packages/todo/package.json +2 -2
- package/packages/todo/src/todo-state.ts +48 -49
- package/packages/todo/tests/todo-extension.mjs +134 -0
- package/packages/todo/tests/todo-state.mjs +80 -70
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
## 2026-10-08 - v0.10.3
|
|
2
|
+
|
|
3
|
+
- bundle herdr-subagents v0.4.0: add the live compact Fleet footer with authoritative metrics and refreshed media
|
|
4
|
+
- Add the responsive below-editor Herdr Fleet monitor with real personas, task states, cumulative active durations, and authoritative child Pi token totals, including event-driven live updates and safe partial-JSONL handling.
|
|
5
|
+
- Color running Fleet rows yellow and completed rows green, compact the shared metrics column instead of pushing it to the terminal edge, and update the published media with focus restoration after capture.
|
|
6
|
+
|
|
7
|
+
## 2026-10-08 - v0.10.2
|
|
8
|
+
|
|
9
|
+
- bundle herdr-subagents v0.3.0: split execution and control tools and unify task runs
|
|
10
|
+
- Bundle the Herdr Subagents execution/control tool split and unified non-empty `tasks` input for single and multi-task runs.
|
|
11
|
+
|
|
1
12
|
## 2026-10-08 - v0.10.1
|
|
2
13
|
|
|
3
14
|
- bundle herdr-subagents v0.2.1: add default thinking configuration and inline Herdr workflow video
|
|
@@ -178,6 +189,7 @@
|
|
|
178
189
|
|
|
179
190
|
## Unreleased
|
|
180
191
|
|
|
192
|
+
- redesign bundled Todo as a branch-local execution plan with lifecycle-only model actions, expected-current guards, serialized calls, preserved branch history, and a live current-plan widget
|
|
181
193
|
- let `/cache_export` switch between all history and individual compaction-defined Context segments, defaulting to the latest segment; cache-hit cumulative lines reset at compaction or model boundaries
|
|
182
194
|
- add the independent `@maxiaochao/pi-codex-edit` package with model-aware Codex-style `apply_patch`
|
|
183
195
|
- include the benchmark summary and interactive HTML architecture explainer
|
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ The root package embeds the child-package source snapshots listed below. Install
|
|
|
22
22
|
|
|
23
23
|
| Package | Capability | Install | Update |
|
|
24
24
|
|---|---|---|---|
|
|
25
|
-
| [pi-todo](packages/todo/README.md) |
|
|
25
|
+
| [pi-todo](packages/todo/README.md) | Branch-local execution plans, guarded advancement, `/todos` history | `pi install npm:@maxiaochao/pi-todo` | `pi update npm:@maxiaochao/pi-todo` |
|
|
26
26
|
| [pi-cache-export](packages/cache-export/README.md) | Tau-aligned interactive cache dashboard for Pi sessions | `pi install npm:@maxiaochao/pi-cache-export` | `pi update npm:@maxiaochao/pi-cache-export` |
|
|
27
27
|
| [pi-codex-edit](packages/codex-edit/README.md) | Codex-harness `apply_patch` routing for selected GPT models | `pi install npm:@maxiaochao/pi-codex-edit` | `pi update npm:@maxiaochao/pi-codex-edit` |
|
|
28
28
|
| [pi-herdr-subagents](packages/herdr-subagents/README.md) | Interactive Herdr Pi Agents with bounded concurrency, direct navigation, and recovery | `pi install npm:@maxiaochao/pi-herdr-subagents` | `pi update npm:@maxiaochao/pi-herdr-subagents` |
|
package/global/AGENTS.md
CHANGED
|
@@ -4,71 +4,54 @@ Behavioral guidelines to reduce common LLM coding mistakes. Project-specific ins
|
|
|
4
4
|
|
|
5
5
|
## 1. Think Before Coding
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- Surface material assumptions, interpretations, and tradeoffs.
|
|
8
|
+
- If unresolved ambiguity would materially change the result, ask. When no human is reachable, choose a defensible interpretation, state it, and proceed with verifiable criteria.
|
|
9
|
+
- Prefer the simpler approach. Name conflicting requirements rather than silently compromising between them.
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
- If multiple interpretations exist, present them rather than picking one silently.
|
|
11
|
-
- If a simpler approach exists, say so. Push back when warranted.
|
|
12
|
-
- If requirements conflict (durability vs latency, correctness vs speed), name the conflict explicitly. Never average conflicting requirements into something that satisfies neither.
|
|
13
|
-
- When unresolved ambiguity would materially change the result, ask. When no human is reachable (headless, async, subagent), choose the interpretation you can defend, state it as an assumption, and proceed under Rule 4.
|
|
11
|
+
## 2. Minimal Solution
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
Understand the problem and relevant code flow before choosing the first rung that works:
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
1. Skip functionality that is speculative or was not requested.
|
|
16
|
+
2. Reuse an existing project abstraction when it encodes the required semantics.
|
|
17
|
+
3. Otherwise use the standard library or a native platform feature.
|
|
18
|
+
4. Otherwise use a direct dependency already declared by the project.
|
|
19
|
+
5. Only then write the minimum custom code that works.
|
|
18
20
|
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
+
- Introduce abstractions only after repeated use reveals a stable concept.
|
|
22
|
+
- Prefer boring, focused code; review long or deeply nested functions for splitting.
|
|
23
|
+
- Never simplify away explicit requirements, trust-boundary validation, data-loss protection, security, or accessibility.
|
|
21
24
|
- For states excluded by enforced invariants, fail visibly instead of inventing fallback behavior.
|
|
22
|
-
-
|
|
25
|
+
- State the ceiling and upgrade trigger of any deliberate simplification that cuts a real corner.
|
|
23
26
|
|
|
24
|
-
## 3.
|
|
27
|
+
## 3. Skill Boundaries
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
After loading a skill, treat its documented interface as sufficient for normal use. Do not inspect the skill's implementation, installed executable, or package source unless the user asks to review, debug, or modify it; its documented behavior conflicts with observed behavior; or required usage details are missing from the skill.
|
|
27
30
|
|
|
28
|
-
|
|
31
|
+
## 4. Read, Then Change Surgically
|
|
29
32
|
|
|
30
|
-
|
|
31
|
-
- Remove imports, variables, and functions that YOUR changes made unused. Leave pre-existing dead code in place and mention it.
|
|
33
|
+
Before editing, read the affected public API and its immediate callers. For a bug, find every statically discoverable caller and relevant implementation.
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
- Find every statically discoverable caller and relevant implementation; a report names a symptom, and patching only the path the ticket mentions leaves sibling callers still broken.
|
|
35
|
-
- If the shared function is wrong, fix it once - do not scatter per-caller guards. Name the sibling callers the fix now affects.
|
|
36
|
-
- If one caller is wrong, fix that caller - do not add defensive handling to a correct shared function.
|
|
35
|
+
Every changed line traces to the user's request or to a defect that request reveals.
|
|
37
36
|
|
|
38
|
-
|
|
37
|
+
- Match existing style.
|
|
38
|
+
- Remove imports, variables, and functions that your changes make unused. Leave pre-existing dead code in place and mention it.
|
|
39
|
+
- Fix a faulty shared function once and name affected sibling callers; if only one caller is wrong, fix that caller instead of guarding a correct shared function.
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
## 5. Goal-Driven Execution
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
- "Refactor X" -> "Ensure tests pass before and after"
|
|
46
|
-
- "Make X fast" -> "Write and test a simple reference implementation, then optimize against its results"
|
|
43
|
+
- Turn work into checkable outcomes: reproduce bugs before fixing them, preserve behavior during refactors, and optimize against a tested reference implementation.
|
|
44
|
+
- For multi-step or materially ambiguous work, state a brief plan and give every step its own verification.
|
|
45
|
+
- Leave the smallest relevant runnable check for new or changed non-trivial logic. Do not introduce a test framework solely for that check.
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
1. [Step] -> verify: [check]
|
|
47
|
+
## 6. Fail Visibly
|
|
50
48
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
## 5. Read Before You Write
|
|
54
|
-
|
|
55
|
-
**Understand adjacent code before changing it. "Looks orthogonal" is a dangerous assumption.**
|
|
56
|
-
|
|
57
|
-
Before changing a file:
|
|
58
|
-
- Read the file's exports and public API.
|
|
59
|
-
- Read the immediate callers of the function you're modifying.
|
|
60
|
-
|
|
61
|
-
## 6. Fail Visibly, Not Silently
|
|
62
|
-
|
|
63
|
-
**Silent skips are failures.**
|
|
64
|
-
|
|
65
|
-
- "Completed" is wrong if anything was silently skipped.
|
|
49
|
+
- "Completed" is wrong if anything required was silently skipped.
|
|
66
50
|
- "Tests pass" is wrong if any required test was skipped or not run.
|
|
67
|
-
- If a required input, file, or tool is missing
|
|
51
|
+
- If a required input, file, or tool is missing, or a step is impossible under the stated constraints, report what is missing and what you need. Never fabricate it to make a check pass.
|
|
52
|
+
|
|
53
|
+
## 7. Worktree Isolation
|
|
68
54
|
|
|
69
|
-
|
|
55
|
+
For a Git-repository task that coordinates substantial changes across multiple files or modules, or implements a feature intended for release, ask before editing whether to isolate it in a worktree. Skip the question when already in a linked worktree.
|
|
70
56
|
|
|
71
|
-
|
|
72
|
-
1. Reuse an existing project abstraction when it encodes project semantics. (If it is itself the bug, fix it once - see Rule 3.)
|
|
73
|
-
2. Otherwise use the standard library or a direct dependency already declared by the project.
|
|
74
|
-
3. Only then write it yourself.
|
|
57
|
+
If the user agrees, read the `pi-worktree` skill and establish the worktree before changing code. When no human is reachable, create a worktree only when the task explicitly requests one.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maxiaochao/pi-toolkit",
|
|
3
|
-
"version": "0.10.
|
|
4
|
-
"description": "Personal pi coding agent toolkit:
|
|
3
|
+
"version": "0.10.3",
|
|
4
|
+
"description": "Personal pi coding agent toolkit: execution plans, /btw, /cache_export and apply_patch extensions, browser automation, and bundled theme/skills.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
|
7
7
|
"packages/*"
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## 2026-10-08 - v0.4.0
|
|
2
|
+
|
|
3
|
+
- add the live compact Fleet footer with authoritative metrics and refreshed media
|
|
4
|
+
- Replace the below-editor task list with a compact responsive Fleet monitor showing authoritative persona, task status, cumulative active time, and true child Pi usage from assistant, tool-result, standalone usage, compaction, and branch-summary entries.
|
|
5
|
+
- Update Fleet metrics from child-session file events, repaint elapsed time only while work runs, safely ignore malformed or partial JSONL tails, and dispose every watcher and timer with the widget.
|
|
6
|
+
- Preserve exact-pane keyboard focus while extending blocked-response and historical-resume activity projections with durable timing and session metadata.
|
|
7
|
+
- Use warning/yellow indicators for running Fleet rows and success/green indicators for completed rows, compact the shared metrics column instead of pushing it to the terminal edge, and publish media that demonstrates the live parent footer while restoring focus after capture.
|
|
8
|
+
|
|
9
|
+
## 2026-10-08 - v0.3.0
|
|
10
|
+
|
|
11
|
+
- split execution and control tools and unify task runs
|
|
12
|
+
- Split execution and control into `herdr_subagents` and `herdr_subagents_control`; require a non-empty `tasks` array for new runs so one-item runs and batches share background and concurrency semantics.
|
|
13
|
+
- Normalize legacy top-level single-task prompts into one-item task arrays before schema validation.
|
|
14
|
+
|
|
1
15
|
## 2026-10-08 - v0.2.1
|
|
2
16
|
|
|
3
17
|
- add default thinking configuration and inline Herdr workflow video
|
|
@@ -2,16 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
A Pi extension that runs focused foreground or background work as normal interactive Pi Agents in visible Herdr panes. Full answers and sessions stay in project-local records while the parent receives only concise summaries and document paths.
|
|
4
4
|
|
|
5
|
-

|
|
6
6
|
|
|
7
7
|
## Demo
|
|
8
8
|
|
|
9
|
-
The recording shows a real Pi conversation invoking a three-task read-only batch, Herdr opening one interactive Agent pane per task, and the compact results returning to the parent.
|
|
9
|
+
The recording shows a real Pi conversation invoking a three-task read-only batch, the active Fleet footer in the parent, Herdr opening one interactive Agent pane per task, and the compact results returning to the parent.
|
|
10
10
|
|
|
11
11
|
https://github.com/user-attachments/assets/8ddf3ae6-a11a-492b-9cae-d9496ba65b67
|
|
12
12
|
|
|
13
|
-
The video starts a three-agent batch, focuses each live child pane in turn, then returns
|
|
14
|
-
to the parent summary. The reproducible source recording is retained as [`docs/demo.webm`](docs/demo.webm).
|
|
13
|
+
The video starts a three-agent batch, focuses each live child pane in turn, captures the parent Fleet footer with yellow running indicators and true usage, then returns to the parent summary. Completed Fleet rows use green indicators. The reproducible source recording is retained as [`docs/demo.webm`](docs/demo.webm).
|
|
15
14
|
|
|
16
15
|
## Requirements and install
|
|
17
16
|
|
|
@@ -29,7 +28,7 @@ pi update npm:@maxiaochao/pi-herdr-subagents
|
|
|
29
28
|
|
|
30
29
|
The full `@maxiaochao/pi-toolkit` package also bundles this extension. Install the full toolkit or this standalone child package, not both.
|
|
31
30
|
|
|
32
|
-
Inside a Herdr workspace, the package registers `herdr_subagents` and `/herdr-subagents`. Outside Herdr (or without a workspace ID), the extension registers
|
|
31
|
+
Inside a Herdr workspace, the package registers `herdr_subagents`, `herdr_subagents_control`, and `/herdr-subagents`. Outside Herdr (or without a workspace ID), the extension registers none of them, so these capabilities are absent from the model context. Start Pi inside Herdr or reload the extension there to enable them.
|
|
33
32
|
|
|
34
33
|
The design replaces hidden print-mode subprocesses with ordinary interactive Pi Agents managed through Herdr, so delegated work stays visible, inspectable, resumable, and durably recorded.
|
|
35
34
|
|
|
@@ -38,16 +37,17 @@ The design replaces hidden print-mode subprocesses with ordinary interactive Pi
|
|
|
38
37
|
Discover the effective personas first:
|
|
39
38
|
|
|
40
39
|
```text
|
|
41
|
-
|
|
40
|
+
herdr_subagents_control({ action: "list" })
|
|
42
41
|
```
|
|
43
42
|
|
|
44
43
|
Then run one self-contained task:
|
|
45
44
|
|
|
46
45
|
```text
|
|
47
46
|
herdr_subagents({
|
|
48
|
-
agent: "worker",
|
|
49
47
|
label: "auth-review",
|
|
50
|
-
|
|
48
|
+
tasks: [
|
|
49
|
+
{ name: "auth", agent: "worker", prompt: "Inspect the authentication flow and summarize the risks" }
|
|
50
|
+
]
|
|
51
51
|
})
|
|
52
52
|
```
|
|
53
53
|
|
|
@@ -64,25 +64,27 @@ herdr_subagents({
|
|
|
64
64
|
})
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
Set `background: true` to return a stable run ID immediately while the queue continues. One run is dispatched at a time by an extension instance; later runs remain queued and start FIFO. Each background run sends one compact parent notification only when the whole run completes, partially fails, fails, or blocks. Individual successful tasks do not wake the parent model.
|
|
67
|
+
`tasks` is always a non-empty array: one item is a single task and multiple items form a batch. Set `background: true` for either form to return a stable run ID immediately while the queue continues. One run is dispatched at a time by an extension instance; later runs remain queued and start FIFO. Each background run sends one compact parent notification only when the whole run completes, partially fails, fails, or blocks. Individual successful tasks do not wake the parent model.
|
|
68
68
|
|
|
69
|
-
A child missing required information reports `needs-input` with an exact question. The run becomes blocked, keeps its Herdr tab, pane, partial report, and Pi session, and returns control to the parent. Answer through the
|
|
69
|
+
A child missing required information reports `needs-input` with an exact question. The run becomes blocked, keeps its Herdr tab, pane, partial report, and Pi session, and returns control to the parent. Answer through the control tool to continue the original session:
|
|
70
70
|
|
|
71
71
|
```text
|
|
72
|
-
|
|
72
|
+
herdr_subagents_control({ action: "respond", runId: "<run-id>", answer: "Use the main branch." })
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
-
While a batch is active,
|
|
75
|
+
While a batch is active, a compact Fleet monitor stays below the editor. Its divider/title row summarizes the run, and each task row shows a hollow or filled status circle, the real persona and task name, status, live active duration, and cumulative child Pi token usage in one compact shared metrics column after the longest row rather than at the terminal edge. Running work uses Pi's warning/yellow semantic color and completed work uses success/green. Token totals use the child session's persisted assistant, tool-result, standalone usage, compaction, and branch-summary usage; they are never estimated. Queued tasks show neither fabricated time nor tokens. The monitor follows Pi theme semantics, truncates by visible terminal columns, and updates session usage from file events while one lightweight repaint timer advances running durations.
|
|
76
|
+
|
|
77
|
+
With an empty editor, press `↓` to enter the list, `↑`/`↓` to choose a task, `Enter` to focus its exact pane, and `Esc` to return. `/herdr-subagents focus <task-number>` remains available as a fallback.
|
|
76
78
|
|
|
77
79
|
## History
|
|
78
80
|
|
|
79
|
-
Run `/herdr-subagents history` or `
|
|
81
|
+
Run `/herdr-subagents history` or `herdr_subagents_control({ action: "history" })` to list compact project-local run and task reports after Herdr tabs close. Full results, described documents, artifacts, and persistent Pi sessions remain separate on disk and are not loaded into parent context.
|
|
80
82
|
|
|
81
|
-
Continue a saved conversation in a new Herdr tab with `
|
|
83
|
+
Continue a saved conversation in a new Herdr tab with `herdr_subagents_control({ action: "resume", runId: "<run-id>", task: 1, prompt: "Follow up..." })`. Remove only a selected archive with `herdr_subagents_control({ action: "cleanup", runId: "<run-id>" })`. History has no automatic count-based eviction. The extension adds the runtime path to local Git excludes when available (and otherwise uses `.pi/.gitignore`) without overwriting unrelated rules.
|
|
82
84
|
|
|
83
85
|
Read-only tasks start FIFO up to the configured concurrency limit. A pane is created only when its task starts, belongs exclusively to that task, and is never reused. Completed panes remain inspectable while another task in the batch is unsettled. Results remain in input order, and one failed task does not cancel its siblings. Any batch containing a write-capable persona is forced to concurrency one.
|
|
84
86
|
|
|
85
|
-
The widget shows one row per queued, running, blocked, failed, or completed task. Answering a blocked task refreshes
|
|
87
|
+
The widget shows one row per queued, running, blocked, failed, or completed task. Answering a blocked task or resuming saved history refreshes authoritative persona, timing, session, status, and pane projections as work advances. Active duration accumulates only while the task is running, excluding time spent queued or waiting for an answer. Completed rows remain selectable until the whole batch settles. `/herdr-subagents active` shows the same compact state. Native labels stay short: `SA · <batch>` for the tab and `<order> · <task>` for each pane. When all tasks settle, the tab closes immediately if unfocused or after the user leaves it; a deferred close failure is persisted and shown as a warning.
|
|
86
88
|
|
|
87
89
|
Foreground calls block until all children report completion, failure, or a request for input. Every child is started with Herdr's Agent lifecycle as a normal Pi TUI and prompted with wait semantics; no print-mode command or completion marker is injected into the pane.
|
|
88
90
|
|
|
@@ -250,9 +252,9 @@ Override the model for one task:
|
|
|
250
252
|
|
|
251
253
|
```text
|
|
252
254
|
herdr_subagents({
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
255
|
+
tasks: [
|
|
256
|
+
{ name: "authorization", agent: "reviewer", model: "anthropic/claude-opus-4-1:high", prompt: "Review the authorization boundary" }
|
|
257
|
+
]
|
|
256
258
|
})
|
|
257
259
|
```
|
|
258
260
|
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
1. `npm --prefix packages/herdr-subagents test` runs deterministic scheduler, recovery, and persona tests.
|
|
6
6
|
2. `pi -ne -ns --no-session -e ./packages/herdr-subagents/extensions/herdr-subagents.ts` verifies extension loading.
|
|
7
|
-
3. From a visible X11 Herdr client with the GStreamer X11 and VP8 plugins installed, run `HERDR_MEDIA_WINDOW_ID=<window-id> node scripts/capture-package-media.mjs`. The Herdr capture must show a parent Pi conversation, a three-task run tab, each of the three real interactive Agent panes receiving focus in turn, and the final compact parent response.
|
|
7
|
+
3. From a dedicated visible X11 Herdr client/window with the GStreamer X11 and VP8 plugins installed, run `HERDR_MEDIA_WINDOW_ID=<window-id> node scripts/capture-package-media.mjs`. Capture intentionally cycles through the live child panes, so avoid using that client/window for other work while recording; the script restores the previously focused pane afterward when it still exists. The Herdr capture must show a parent Pi conversation, a three-task run tab, each of the three real interactive Agent panes receiving focus in turn, the active parent Fleet footer with yellow running indicators and real metrics, and the final compact parent response. The published screenshot must be the parent Fleet view, while the full WebM preserves the parent → three focused children → Fleet → parent sequence.
|
|
8
8
|
4. `npm pack ./packages/herdr-subagents --dry-run` verifies package contents.
|
|
9
|
-
5. Verify the README renders the GitHub attachment video directly and live Herdr UI media `docs/screenshot.png` and `docs/demo.webm` are present in the tarball; `docs/demo.gif` must be absent. Inspect the WebM metadata and play it through once to confirm the parent → three focused children → parent sequence.
|
|
9
|
+
5. Verify the README renders the GitHub attachment video directly and live Herdr UI media `docs/screenshot.png` and `docs/demo.webm` are present in the tarball; `docs/demo.gif` must be absent. Inspect the WebM metadata and play it through once to confirm the parent → three focused children → Fleet → parent sequence.
|
|
10
10
|
|
|
11
11
|
## Cases
|
|
12
12
|
|
|
@@ -16,10 +16,12 @@
|
|
|
16
16
|
- Force write-capable batches to serial execution while allowing bounded read-only parallelism.
|
|
17
17
|
- Preserve compact reports, full results, Pi sessions, blocked questions, and historical follow-ups.
|
|
18
18
|
- Recover queued/running work after interruption without retrying post-prompt side effects, including truncated reports and recovery errors.
|
|
19
|
-
- Verify the
|
|
19
|
+
- Verify the split public contracts (`herdr_subagents` with required `tasks`, plus `herdr_subagents_control` with required `action`) and injectable extension wiring, including conditional registration only inside Herdr, Fleet rendering, editor focus ownership, terminal-input passthrough, exact-pane navigation, and stable display while another background run queues.
|
|
20
|
+
- Verify Fleet divider/row formatting, warning/yellow running indicators, success/green completed indicators, persona and task projections, cumulative active duration across blocking/resume, exact Pi usage aggregation from assistant messages, tool results, standalone usage, compaction, and branch summaries, malformed/truncated JSONL tolerance, queued metric omission, and visible-column bounds at narrow widths.
|
|
21
|
+
- Verify live token updates are file-event driven, elapsed repainting exists only while work runs, and widget disposal closes watchers and timers.
|
|
20
22
|
- Verify blocked-response live Fleet updates, authoritative stale-field replacement, parent-shutdown cancellation, cleanup notifications, atomic state replacement, non-retried uncertain startup operations, and stalled warnings.
|
|
21
23
|
- Verify resumed batches retain newly allocated pane IDs and historical follow-ups exclude simultaneous session writers.
|
|
22
24
|
|
|
23
25
|
## Latest baseline
|
|
24
26
|
|
|
25
|
-
All
|
|
27
|
+
All 159 deterministic scheduler, recovery, process-boundary, schema-validation, navigation, metrics, responsive-formatting, and persona cases must pass. Release validation also requires extension loading and a real Herdr read-only batch smoke with distinct task panes and final tab cleanup. The latest smoke completed two concurrent tasks, retained its focused run tab through settlement, and closed it automatically after focus moved away.
|
|
Binary file
|
|
Binary file
|
|
@@ -9,10 +9,10 @@ import { RunDispatcher } from "../src/dispatcher.ts";
|
|
|
9
9
|
import { historyText, listSubagentHistory } from "../src/history.ts";
|
|
10
10
|
import { cancelQueuedRuns, loadQueuedRuns, persistQueuedRun, removeQueuedRun } from "../src/queued-runs.ts";
|
|
11
11
|
import { cancelActiveSubagentRuns, reconcileSubagentRuns } from "../src/reconcile.ts";
|
|
12
|
-
import { executeHerdrSubagents, type HerdrSubagentsBatchDetails, type HerdrSubagentsDetails, type
|
|
13
|
-
import { HerdrSubagentsParams } from "../src/parameters.ts";
|
|
12
|
+
import { executeHerdrSubagents, executeHerdrSubagentsControl, type HerdrSubagentsBatchDetails, type HerdrSubagentsControlToolParams, type HerdrSubagentsDetails, type HerdrSubagentsRunParams } from "../src/tool.ts";
|
|
13
|
+
import { HerdrSubagentsControlParams, HerdrSubagentsParams } from "../src/parameters.ts";
|
|
14
14
|
import { HERDR_ACTIONS } from "../src/validation.ts";
|
|
15
|
-
import { FleetController } from "../src/fleet-controller.ts";
|
|
15
|
+
import { FleetController, type FleetWidgetRuntime } from "../src/fleet-controller.ts";
|
|
16
16
|
import { isSettledRunStatus } from "../src/records.ts";
|
|
17
17
|
|
|
18
18
|
const packageAgentsDir = fileURLToPath(new URL("../agents/", import.meta.url));
|
|
@@ -25,18 +25,32 @@ function statusVisual(status: string): { color: "success" | "warning" | "error"
|
|
|
25
25
|
return { color: "error", glyph: "✗" };
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
function renderToolCall(args:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
28
|
+
function renderToolCall(args: HerdrSubagentsRunParams, theme: Theme): Text {
|
|
29
|
+
return new Text(`${theme.fg("toolTitle", theme.bold("herdr_subagents"))} ${theme.fg("accent", `${args.tasks.length} task${args.tasks.length === 1 ? "" : "s"}`)}${theme.fg("muted", ` · concurrency ${args.concurrency ?? "default"}`)}`, 0, 0);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function renderControlToolCall(args: HerdrSubagentsControlToolParams, theme: Theme): Text {
|
|
33
|
+
const action = HERDR_ACTIONS[args.action];
|
|
34
|
+
const runId = action.includeRunId ? ` ${theme.fg("muted", String(args.runId ?? ""))}` : "";
|
|
35
|
+
return new Text(`${theme.fg("toolTitle", theme.bold(`herdr_subagents_control ${args.action}`))}${runId}`, 0, 0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function normalizeHerdrSubagentsArguments(args: unknown): HerdrSubagentsRunParams {
|
|
39
|
+
if (!args || typeof args !== "object") return args as HerdrSubagentsRunParams;
|
|
40
|
+
const legacy = args as Record<string, unknown>;
|
|
41
|
+
if (Array.isArray(legacy.tasks) || typeof legacy.prompt !== "string" || legacy.action !== undefined) return args as HerdrSubagentsRunParams;
|
|
42
|
+
if (Object.keys(legacy).some((key) => !["prompt", "agent", "label", "model", "background"].includes(key))) return args as HerdrSubagentsRunParams;
|
|
43
|
+
const label = typeof legacy.label === "string" ? legacy.label : undefined;
|
|
44
|
+
return {
|
|
45
|
+
tasks: [{
|
|
46
|
+
name: label || "task",
|
|
47
|
+
prompt: legacy.prompt,
|
|
48
|
+
...(typeof legacy.agent === "string" ? { agent: legacy.agent } : {}),
|
|
49
|
+
...(typeof legacy.model === "string" ? { model: legacy.model } : {}),
|
|
50
|
+
}],
|
|
51
|
+
...(label ? { label } : {}),
|
|
52
|
+
...(typeof legacy.background === "boolean" ? { background: legacy.background } : {}),
|
|
53
|
+
};
|
|
40
54
|
}
|
|
41
55
|
|
|
42
56
|
function renderToolResult(result: AgentToolResult<unknown>, _options: ToolRenderResultOptions, theme: Theme): Container | Text {
|
|
@@ -49,20 +63,20 @@ function renderToolResult(result: AgentToolResult<unknown>, _options: ToolRender
|
|
|
49
63
|
return container;
|
|
50
64
|
}
|
|
51
65
|
const details = result.details as HerdrSubagentsDetails | undefined;
|
|
52
|
-
if (
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
container.addChild(new
|
|
61
|
-
|
|
66
|
+
if (details?.agent) {
|
|
67
|
+
const container = new Container();
|
|
68
|
+
container.addChild(new Text(`${theme.fg("toolTitle", theme.bold(details.agent))} ${theme.fg(statusVisual(details.status).color, details.status)}`, 0, 0));
|
|
69
|
+
container.addChild(new Text(details.summary, 0, 0));
|
|
70
|
+
if (details.documents.length > 0) {
|
|
71
|
+
container.addChild(new Spacer(1));
|
|
72
|
+
for (const document of details.documents) container.addChild(new Text(theme.fg("muted", `${document.description}: ${document.path}`), 0, 0));
|
|
73
|
+
}
|
|
74
|
+
if (details.recordDirectory) container.addChild(new Text(theme.fg("dim", `Record: ${details.recordDirectory}`), 0, 0));
|
|
75
|
+
if (details.errorMessage) container.addChild(new Text(theme.fg("error", `Error: ${details.errorMessage}`), 0, 0));
|
|
76
|
+
return container;
|
|
62
77
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return container;
|
|
78
|
+
const content = result.content[0];
|
|
79
|
+
return new Text(content?.type === "text" ? content.text : "(no output)", 0, 0);
|
|
66
80
|
}
|
|
67
81
|
|
|
68
82
|
async function handleHerdrCommand(args: string, ctx: ExtensionCommandContext, fleet: FleetController): Promise<void> {
|
|
@@ -104,8 +118,10 @@ async function handleHerdrCommand(args: string, ctx: ExtensionCommandContext, fl
|
|
|
104
118
|
export interface HerdrSubagentsExtensionDependencies {
|
|
105
119
|
herdr?: HerdrAutomation;
|
|
106
120
|
execute?: typeof executeHerdrSubagents;
|
|
121
|
+
executeControl?: typeof executeHerdrSubagentsControl;
|
|
107
122
|
isEditor?: (value: unknown) => boolean;
|
|
108
123
|
createFleetWidget?: (lines: string[]) => Container;
|
|
124
|
+
fleetWidgetRuntime?: FleetWidgetRuntime;
|
|
109
125
|
}
|
|
110
126
|
|
|
111
127
|
async function recoverParentSession(cwd: string, herdr: HerdrAutomation, owner: { sessionId?: string; processId: number }, notify: (message: string) => void): Promise<void> {
|
|
@@ -139,9 +155,10 @@ export function registerHerdrSubagents(pi: ExtensionAPI, dependencies: HerdrSuba
|
|
|
139
155
|
if (process.env.PI_HERDR_SUBAGENTS_CHILD === "1" || process.env.HERDR_ENV !== "1" || !process.env.HERDR_WORKSPACE_ID) return;
|
|
140
156
|
const herdr = dependencies.herdr ?? new CliHerdrAutomation();
|
|
141
157
|
const execute = dependencies.execute ?? executeHerdrSubagents;
|
|
158
|
+
const executeControl = dependencies.executeControl ?? executeHerdrSubagentsControl;
|
|
142
159
|
const isEditorComponent = dependencies.isEditor ?? ((value: unknown) => value instanceof Editor);
|
|
143
160
|
const dispatcher = new RunDispatcher();
|
|
144
|
-
const fleet = new FleetController(herdr, isEditorComponent, dependencies.createFleetWidget);
|
|
161
|
+
const fleet = new FleetController(herdr, isEditorComponent, dependencies.createFleetWidget, dependencies.fleetWidgetRuntime);
|
|
145
162
|
const runControllers = new Map<string, AbortController>();
|
|
146
163
|
const inFlight = new Set<Promise<unknown>>();
|
|
147
164
|
let sessionController = new AbortController();
|
|
@@ -177,17 +194,17 @@ export function registerHerdrSubagents(pi: ExtensionAPI, dependencies: HerdrSuba
|
|
|
177
194
|
pi.registerTool({
|
|
178
195
|
name: "herdr_subagents",
|
|
179
196
|
label: "Herdr Subagents",
|
|
180
|
-
description: "Run one
|
|
197
|
+
description: "Run one or more ordered tasks in visible interactive Herdr Pi Agents. Use one tasks item for a single task. Independent read-only tasks may run concurrently; write-capable runs are serialized. Foreground calls block, while background calls return a run ID immediately. Complete results and Pi sessions stay in project-local records.",
|
|
181
198
|
promptSnippet: "Run focused work in visible Herdr subagents",
|
|
182
|
-
promptGuidelines: ["Call action=list once before the first execution. Submit
|
|
199
|
+
promptGuidelines: ["Call herdr_subagents_control with action=list once before the first execution. Submit an ordered tasks list; use one item for a single task and concurrency only for independent read-only tasks. Foreground calls block until the run settles; background calls return a run ID immediately."],
|
|
183
200
|
parameters: HerdrSubagentsParams,
|
|
201
|
+
prepareArguments: normalizeHerdrSubagentsArguments,
|
|
184
202
|
|
|
185
|
-
async execute(_toolCallId, params:
|
|
203
|
+
async execute(_toolCallId, params: HerdrSubagentsRunParams, signal, onUpdate, ctx) {
|
|
186
204
|
const generation = sessionGeneration;
|
|
187
|
-
const runId =
|
|
188
|
-
const executionId = runId ?? params.runId ?? randomUUID();
|
|
205
|
+
const runId = randomUUID();
|
|
189
206
|
const controller = new AbortController();
|
|
190
|
-
runControllers.set(
|
|
207
|
+
runControllers.set(runId, controller);
|
|
191
208
|
const runSignal = AbortSignal.any([controller.signal, sessionController.signal, ...(!params.background && signal ? [signal] : [])]);
|
|
192
209
|
const executeRun = async () => {
|
|
193
210
|
const result = await execute(params, runSignal, (update) => {
|
|
@@ -201,21 +218,6 @@ export function registerHerdrSubagents(pi: ExtensionAPI, dependencies: HerdrSuba
|
|
|
201
218
|
fleet.sync(result.details);
|
|
202
219
|
return result;
|
|
203
220
|
};
|
|
204
|
-
if (!params.tasks) {
|
|
205
|
-
const promise = executeRun();
|
|
206
|
-
inFlight.add(promise);
|
|
207
|
-
const result = await promise.finally(() => { inFlight.delete(promise); runControllers.delete(executionId); });
|
|
208
|
-
if (params.action === "respond") {
|
|
209
|
-
const details = result.details as { runId?: string; status?: string };
|
|
210
|
-
if (details.status === "blocked") fleet.sync(result.details);
|
|
211
|
-
else if (isSettledRunStatus(details.status)) {
|
|
212
|
-
fleet.clearRun(params.runId);
|
|
213
|
-
if (params.runId) dispatcher.release(params.runId);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
return result;
|
|
217
|
-
}
|
|
218
|
-
if (!runId) throw new Error("Batch run ID was not allocated.");
|
|
219
221
|
if (params.background) await persistQueuedRun(ctx.cwd, runId, params, { sessionId: ownerSessionId, processId: process.pid });
|
|
220
222
|
const promise = dispatcher.submit(runId, async () => {
|
|
221
223
|
const result = await executeRun().finally(async () => { if (params.background) await removeQueuedRun(ctx.cwd, runId); });
|
|
@@ -227,7 +229,7 @@ export function registerHerdrSubagents(pi: ExtensionAPI, dependencies: HerdrSuba
|
|
|
227
229
|
return result;
|
|
228
230
|
});
|
|
229
231
|
inFlight.add(promise);
|
|
230
|
-
void promise.finally(() => { inFlight.delete(promise); runControllers.delete(
|
|
232
|
+
void promise.finally(() => { inFlight.delete(promise); runControllers.delete(runId); }).catch(() => undefined);
|
|
231
233
|
if (params.background) {
|
|
232
234
|
void promise.then((result) => {
|
|
233
235
|
if (generation !== sessionGeneration) return;
|
|
@@ -248,6 +250,44 @@ export function registerHerdrSubagents(pi: ExtensionAPI, dependencies: HerdrSuba
|
|
|
248
250
|
renderResult: renderToolResult,
|
|
249
251
|
});
|
|
250
252
|
|
|
253
|
+
pi.registerTool({
|
|
254
|
+
name: "herdr_subagents_control",
|
|
255
|
+
label: "Herdr Subagents Control",
|
|
256
|
+
description: "Inspect Herdr personas and run history, answer a blocked task, resume a saved task, or remove a selected archived run. Use herdr_subagents to start new work.",
|
|
257
|
+
promptSnippet: "Inspect and control Herdr subagent runs",
|
|
258
|
+
parameters: HerdrSubagentsControlParams,
|
|
259
|
+
|
|
260
|
+
async execute(_toolCallId, params: HerdrSubagentsControlToolParams, signal, onUpdate, ctx) {
|
|
261
|
+
const executionId = params.runId ?? randomUUID();
|
|
262
|
+
const controller = new AbortController();
|
|
263
|
+
runControllers.set(executionId, controller);
|
|
264
|
+
const runSignal = AbortSignal.any([controller.signal, sessionController.signal, ...(signal ? [signal] : [])]);
|
|
265
|
+
const promise = executeControl(params, runSignal, (update) => {
|
|
266
|
+
const details = update.details as HerdrSubagentsBatchDetails | undefined;
|
|
267
|
+
if (details?.activity) fleet.sync(details);
|
|
268
|
+
const updateText = update.content.find((item) => item.type === "text")?.text;
|
|
269
|
+
const updateDetails = update.details as { warning?: string };
|
|
270
|
+
if (updateDetails.warning || updateText?.includes("cleanup failed")) ctx.ui.notify(updateDetails.warning ?? updateText ?? "Subagent warning", "warning");
|
|
271
|
+
onUpdate?.(update);
|
|
272
|
+
}, ctx, { agentsDirectory: packageAgentsDir, owner: { sessionId: ownerSessionId, processId: process.pid } });
|
|
273
|
+
inFlight.add(promise);
|
|
274
|
+
const result = await promise.finally(() => { inFlight.delete(promise); runControllers.delete(executionId); });
|
|
275
|
+
fleet.sync(result.details);
|
|
276
|
+
if (params.action === "respond") {
|
|
277
|
+
const details = result.details as { status?: string };
|
|
278
|
+
if (details.status === "blocked") fleet.sync(result.details);
|
|
279
|
+
else if (isSettledRunStatus(details.status)) {
|
|
280
|
+
fleet.clearRun(params.runId);
|
|
281
|
+
if (params.runId) dispatcher.release(params.runId);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return result;
|
|
285
|
+
},
|
|
286
|
+
|
|
287
|
+
renderCall: renderControlToolCall,
|
|
288
|
+
renderResult: renderToolResult,
|
|
289
|
+
});
|
|
290
|
+
|
|
251
291
|
pi.registerCommand("herdr-subagents", {
|
|
252
292
|
description: "Inspect effective subagent agents, models, and settings",
|
|
253
293
|
getArgumentCompletions: (prefix) => ["active", "history", "agents", "models", "settings", "focus "].filter((value) => value.startsWith(prefix)).map((value) => ({ value, label: value })),
|
|
@@ -10,7 +10,7 @@ import { writeJsonAtomic } from "./state.ts";
|
|
|
10
10
|
import type { OwnerIdentity } from "./ownership.ts";
|
|
11
11
|
import { ownerRecord } from "./ownership.ts";
|
|
12
12
|
import { RunPaneAllocator } from "./run-pane-allocator.ts";
|
|
13
|
-
import { applyReport, applyTaskStatus, settleAttemptFailure, type PersistedReport, type RunRecord, type TaskRecord, type TaskStatus } from "./records.ts";
|
|
13
|
+
import { applyReport, applyTaskStatus, projectTaskActivity, settleAttemptFailure, type PersistedReport, type RunRecord, type TaskActivity, type TaskRecord, type TaskStatus } from "./records.ts";
|
|
14
14
|
import { reportProtocolPrompt } from "./protocol.ts";
|
|
15
15
|
import { cleanupRunTab } from "./tab-cleanup.ts";
|
|
16
16
|
import { applyRunStatus } from "./run-status.ts";
|
|
@@ -63,7 +63,7 @@ export interface BatchResult {
|
|
|
63
63
|
requestedConcurrency: number;
|
|
64
64
|
effectiveConcurrency: number;
|
|
65
65
|
tasks: BatchTaskResult[];
|
|
66
|
-
activity:
|
|
66
|
+
activity: TaskActivity[];
|
|
67
67
|
summary: string;
|
|
68
68
|
documents: HerdrSubagentsDocument[];
|
|
69
69
|
recordDirectory: string;
|
|
@@ -114,7 +114,7 @@ export async function runHerdrSubagentsBatch(tasks: BatchTask[], options: BatchO
|
|
|
114
114
|
await writeJsonAtomic(runFile, runRecord);
|
|
115
115
|
const herdr = options.herdr ?? new CliHerdrAutomation();
|
|
116
116
|
const results = new Array<BatchTaskResult>(tasks.length);
|
|
117
|
-
const activity: BatchResult["activity"] =
|
|
117
|
+
const activity: BatchResult["activity"] = contexts.map((context, index) => projectTaskActivity(context.taskRecord, index));
|
|
118
118
|
let tabId: string | undefined;
|
|
119
119
|
const paneAllocator = new RunPaneAllocator({ herdr, run: runRecord, runFile, signal: options.signal, onCleanupError: options.onCleanupError });
|
|
120
120
|
const emit = (status: BatchResult["status"], summary: string) => options.onUpdate?.({
|
|
@@ -146,7 +146,7 @@ export async function runHerdrSubagentsBatch(tasks: BatchTask[], options: BatchO
|
|
|
146
146
|
applyTaskStatus(context.taskRecord, "running");
|
|
147
147
|
Object.assign(context.taskRecord, { tabId, paneId, paneLabel, agentName, startedAt: timestamp() });
|
|
148
148
|
await writeJsonAtomic(context.taskFile, context.taskRecord);
|
|
149
|
-
activity[index] =
|
|
149
|
+
activity[index] = projectTaskActivity(context.taskRecord, index);
|
|
150
150
|
emit("running", `${settled}/${tasks.length} tasks settled.`);
|
|
151
151
|
const prompted = await promptLiveAgent({ herdr, task: { ...task, id: context.taskId, sessionFile: context.sessionFile }, paneId, prompt: task.prompt, systemPromptFile: context.systemPromptFile, agentName, signal: options.signal, stalledWarningMs: options.stalledWarningSeconds ? options.stalledWarningSeconds * 1000 : undefined, onStalled: () => emit("running", `${task.name} appears stalled; it is still running.`) });
|
|
152
152
|
agentName = prompted.agentName;
|
|
@@ -164,7 +164,7 @@ export async function runHerdrSubagentsBatch(tasks: BatchTask[], options: BatchO
|
|
|
164
164
|
Object.assign(context.taskRecord, { paneId, agentName });
|
|
165
165
|
await writeJsonAtomic(context.taskFile, context.taskRecord);
|
|
166
166
|
results[index] = { index, name: task.name, status, summary: report.summary, documents: report.documents, ...(report.error ? { error: report.error } : {}), ...(report.question ? { question: report.question } : {}), paneId, recordDirectory: context.taskDirectory, sessionFile: context.sessionFile };
|
|
167
|
-
activity[index] =
|
|
167
|
+
activity[index] = projectTaskActivity(context.taskRecord, index);
|
|
168
168
|
settled += 1;
|
|
169
169
|
emit("running", `${settled}/${tasks.length} tasks settled.`);
|
|
170
170
|
};
|