@maxiaochao/pi-toolkit 0.10.4 → 0.10.6
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 +10 -0
- package/README.md +26 -4
- package/package.json +3 -5
- package/packages/herdr-subagents/CHANGELOG.md +11 -0
- package/packages/herdr-subagents/README.md +13 -2
- package/packages/herdr-subagents/TESTING.md +5 -4
- package/packages/herdr-subagents/agents/analyst.md +8 -0
- package/packages/herdr-subagents/extensions/herdr-subagents.ts +1 -1
- package/packages/herdr-subagents/extensions/subagent-report.ts +26 -0
- package/packages/herdr-subagents/package.json +1 -1
- package/packages/herdr-subagents/src/history.ts +2 -2
- package/packages/herdr-subagents/src/live-agent.ts +11 -0
- package/packages/herdr-subagents/src/parameters.ts +2 -2
- package/packages/herdr-subagents/src/run-pane-allocator.ts +2 -1
- package/packages/herdr-subagents/src/runner.ts +2 -8
- package/packages/herdr-subagents/src/status.ts +95 -0
- package/packages/herdr-subagents/src/tool.ts +13 -2
- package/packages/herdr-subagents/src/validation.ts +2 -1
- package/packages/herdr-subagents/tests/extension-contract.ts +4 -0
- package/packages/herdr-subagents/tests/run-tests.mjs +61 -4
- package/skills/agent-team/SKILL.md +33 -57
- package/skills/agent-team/models.json +19 -12
- package/packages/todo/CHANGELOG.md +0 -9
- package/packages/todo/README.md +0 -73
- package/packages/todo/TESTING.md +0 -22
- package/packages/todo/docs/demo.gif +0 -0
- package/packages/todo/docs/screenshot.png +0 -0
- package/packages/todo/extensions/todo.ts +0 -279
- package/packages/todo/package.json +0 -25
- package/packages/todo/src/todo-state.ts +0 -159
- package/packages/todo/tests/todo-extension.mjs +0 -134
- package/packages/todo/tests/todo-state.mjs +0 -108
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
## 2026-10-09 - v0.10.6
|
|
2
|
+
|
|
3
|
+
- bundle herdr-subagents v0.4.3: add per-run status, neutral analyst persona, and tool-agnostic Agent Team protocol
|
|
4
|
+
- Add per-run Herdr Subagents status queries so background review gates can inspect one run without polling the full history archive.
|
|
5
|
+
- Make Agent Team a tool-agnostic concurrent review protocol, decouple models from rotating methodological roles, and add a neutral read-only analyst persona.
|
|
6
|
+
- Remove Todo from the default toolkit bundle and loadout; users who want its progress UI can install `@maxiaochao/pi-todo` explicitly.
|
|
7
|
+
|
|
8
|
+
## 2026-10-08 - v0.10.5
|
|
9
|
+
|
|
10
|
+
- bundle herdr-subagents v0.4.2: add Alt+P and /parent controls to return from child Agents
|
|
1
11
|
## 2026-10-08 - v0.10.4
|
|
2
12
|
|
|
3
13
|
- bundle herdr-subagents v0.4.1: targeted cancellation, live token polling, and idempotent tab cleanup
|
package/README.md
CHANGED
|
@@ -16,13 +16,21 @@ Update it:
|
|
|
16
16
|
pi update npm:@maxiaochao/pi-toolkit
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
The root package embeds
|
|
19
|
+
The root package embeds its enabled child-package source snapshots. Todo is intentionally excluded from the default toolkit and remains available as an opt-in standalone package.
|
|
20
|
+
|
|
21
|
+
Existing toolkit users can pick up the change with:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pi update npm:@maxiaochao/pi-toolkit
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Restart Pi after updating. The `todo` tool and widget will no longer load. `pi update --extensions` has the same effect when updating all installed packages. Explicitly versioned sources remain pinned and must be changed or reinstalled without the old version suffix.
|
|
20
28
|
|
|
21
29
|
## Child packages
|
|
22
30
|
|
|
23
31
|
| Package | Capability | Install | Update |
|
|
24
32
|
|---|---|---|---|
|
|
25
|
-
| [pi-todo](packages/todo/README.md) |
|
|
33
|
+
| [pi-todo](packages/todo/README.md) | Optional branch-local execution plan UI; not bundled with pi-toolkit | `pi install npm:@maxiaochao/pi-todo` | `pi update npm:@maxiaochao/pi-todo` |
|
|
26
34
|
| [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
35
|
| [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
36
|
| [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` |
|
|
@@ -30,7 +38,21 @@ The root package embeds the child-package source snapshots listed below. Install
|
|
|
30
38
|
|
|
31
39
|
Each child README owns its feature description, motivation or upstream alignment, examples, screenshots/demos, configuration, and test documentation.
|
|
32
40
|
|
|
33
|
-
Multiple standalone children may be installed together. Do not install
|
|
41
|
+
Multiple standalone children may be installed together. `pi-todo` may also be installed alongside the complete toolkit because Todo is no longer bundled. Do not install another bundled child alongside the complete toolkit because that exposes the same resource twice.
|
|
42
|
+
|
|
43
|
+
To keep Todo after updating the toolkit:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pi install npm:@maxiaochao/pi-todo
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
If Todo was already installed separately and should be removed completely:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pi remove npm:@maxiaochao/pi-todo
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Use `--local` with install or remove when the package declaration belongs to a project's `.pi/settings.json` rather than personal settings.
|
|
34
56
|
|
|
35
57
|
Root and child versions remain independent, but repository release tooling publishes a modified child and a new toolkit snapshot from the same commit. Run `pi update --extensions` to update all installed packages. Explicit version sources such as `npm:@maxiaochao/pi-toolkit@0.9.3` remain pinned.
|
|
36
58
|
|
|
@@ -64,7 +86,7 @@ bash scripts/install.sh
|
|
|
64
86
|
| `html-artifact` | Build self-contained HTML explainers and diagrams |
|
|
65
87
|
| `pdlog` | Decompress and inspect Pudu `.pdlog` files |
|
|
66
88
|
| `herdr` | Control Herdr workspaces, tabs, panes, and agents |
|
|
67
|
-
| `agent-team` | Run cross-vendor adversarial review
|
|
89
|
+
| `agent-team` | Run tool-agnostic, cross-vendor adversarial review with independent concurrent agents |
|
|
68
90
|
| `tapd` | Use the TAPD CLI for requirements, defects, tasks, and wikis |
|
|
69
91
|
|
|
70
92
|
Package-specific skills, such as `pi-worktree`, are documented by their child package.
|
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.6",
|
|
4
|
+
"description": "Personal pi coding agent toolkit: /btw, /cache_export and apply_patch extensions, browser automation, and bundled theme/skills.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
|
7
7
|
"packages/*"
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
],
|
|
13
13
|
"files": [
|
|
14
14
|
"extensions",
|
|
15
|
-
"packages/todo",
|
|
16
15
|
"packages/cache-export",
|
|
17
16
|
"packages/codex-edit",
|
|
18
17
|
"packages/herdr-subagents",
|
|
@@ -30,7 +29,6 @@
|
|
|
30
29
|
},
|
|
31
30
|
"pi": {
|
|
32
31
|
"extensions": [
|
|
33
|
-
"./packages/todo/extensions/todo.ts",
|
|
34
32
|
"./packages/cache-export/extensions/cache-export.ts",
|
|
35
33
|
"./extensions/btw/index.ts",
|
|
36
34
|
"./packages/codex-edit/extensions/codex-edit.ts",
|
|
@@ -58,7 +56,7 @@
|
|
|
58
56
|
},
|
|
59
57
|
"scripts": {
|
|
60
58
|
"test": "node tests/package-layout.mjs && npm --prefix packages/todo test && npm --prefix packages/cache-export test && npm --prefix packages/codex-edit test && npm --prefix packages/herdr-subagents test && npm --prefix packages/worktree test && node --experimental-strip-types extensions/btw/tests/history.mjs",
|
|
61
|
-
"load-test": "pi -ne -e ./packages/
|
|
59
|
+
"load-test": "pi -ne -e ./packages/cache-export/extensions/cache-export.ts && pi -ne -e ./extensions/btw/index.ts && pi -ne -e ./packages/codex-edit/extensions/codex-edit.ts && pi -ne -e ./packages/herdr-subagents/extensions/herdr-subagents.ts",
|
|
62
60
|
"eval:agents": "node evaluations/agents-md/run.mjs",
|
|
63
61
|
"eval:agents:test": "node evaluations/agents-md/tests/run-tests.mjs",
|
|
64
62
|
"postinstall": "node scripts/install-agents.mjs"
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
## 2026-10-09 - v0.4.3
|
|
2
|
+
|
|
3
|
+
- add per-run status, neutral analyst persona, and tool-agnostic Agent Team protocol
|
|
4
|
+
- Add `herdr_subagents_control({ action: "status", runId })` for a compact snapshot of one queued, active, blocked, or settled run without loading the full history archive.
|
|
5
|
+
- Add a neutral read-only `analyst` persona for tasks that assign models and methodological roles dynamically.
|
|
6
|
+
|
|
7
|
+
## 2026-10-08 - v0.4.2
|
|
8
|
+
|
|
9
|
+
- add Alt+P and /parent controls to return from child Agents
|
|
10
|
+
- Add `/parent` and `Alt+P` inside child Agents to return focus to the exact parent Agent pane, with a visible child footer hint.
|
|
11
|
+
|
|
1
12
|
## 2026-10-08 - v0.4.1
|
|
2
13
|
|
|
3
14
|
- targeted cancellation, live token polling, and idempotent tab cleanup
|
|
@@ -66,6 +66,14 @@ herdr_subagents({
|
|
|
66
66
|
|
|
67
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
|
+
Background work normally relies on that terminal notification. When the user asks for progress or the parent must confirm a review gate before continuing, query only that run's compact snapshot:
|
|
70
|
+
|
|
71
|
+
```text
|
|
72
|
+
herdr_subagents_control({ action: "status", runId: "<run-id>" })
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The snapshot reports the aggregate run state and every task's state, including a blocked question when present. It checks durable run state before queued state, never returns unrelated runs, and does not wait or trigger a parent turn. Do not poll full `history` for progress.
|
|
76
|
+
|
|
69
77
|
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
78
|
|
|
71
79
|
```text
|
|
@@ -76,11 +84,11 @@ Cancel one active or queued run with `herdr_subagents_control({ action: "cancel"
|
|
|
76
84
|
|
|
77
85
|
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 refreshes session usage from both file events and the running one-second timer.
|
|
78
86
|
|
|
79
|
-
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.
|
|
87
|
+
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. Inside every child Agent, press `Alt+P` or run `/parent` to return to the exact parent Agent pane; the shortcut is also shown in the child footer.
|
|
80
88
|
|
|
81
89
|
## History
|
|
82
90
|
|
|
83
|
-
Run `/herdr-subagents history` or `herdr_subagents_control({ action: "history" })` to list compact project-local run and task
|
|
91
|
+
Run `/herdr-subagents history` or `herdr_subagents_control({ action: "history" })` to list the full compact project-local run and task archive after Herdr tabs close. History does not accept a run ID and is not a status interface. Full results, described documents, artifacts, and persistent Pi sessions remain separate on disk and are not loaded into parent context.
|
|
84
92
|
|
|
85
93
|
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.
|
|
86
94
|
|
|
@@ -192,6 +200,9 @@ The package includes these personas:
|
|
|
192
200
|
| `worker` | write | `read`, `bash`, `edit`, `write`, `grep`, `find`, `ls` | Focused implementation work |
|
|
193
201
|
| `explorer` | read | `read`, `bash`, `grep`, `find`, `ls` | Fast codebase investigation |
|
|
194
202
|
| `reviewer` | read | `read`, `bash`, `grep`, `find`, `ls` | Correctness and maintainability review |
|
|
203
|
+
| `analyst` | read | `read`, `bash`, `grep`, `find`, `ls` | Neutral analysis with the method and role supplied by each task |
|
|
204
|
+
|
|
205
|
+
`analyst` deliberately does not bind a model or review method. Use the task-level `model` field when independent tasks need different models, and put roles such as auditor, replicator, or adversary in each task prompt.
|
|
195
206
|
|
|
196
207
|
Persona definitions use Markdown frontmatter followed by the child system prompt. For example, save this as `.pi/herdr-subagents/agents/security-reviewer.md`:
|
|
197
208
|
|
|
@@ -10,18 +10,19 @@
|
|
|
10
10
|
|
|
11
11
|
## Cases
|
|
12
12
|
|
|
13
|
-
- Resolve package, global, and project personas and validate frontmatter/configuration.
|
|
13
|
+
- Resolve package, global, and project personas and validate frontmatter/configuration, including the neutral read-only `analyst` persona.
|
|
14
14
|
- Run foreground tasks and FIFO bounded-concurrency batches through an injectable Herdr boundary.
|
|
15
15
|
- Verify all paths use interactive Agent start/prompt without print mode, shell injection, markers, or pane reuse.
|
|
16
|
-
- Force write-capable batches to serial execution while allowing bounded read-only parallelism.
|
|
16
|
+
- Force write-capable batches to serial execution while allowing bounded read-only parallelism, including three concurrent `analyst` tasks with independent task-level models.
|
|
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 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.
|
|
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, child `/parent` and `Alt+P` return controls, and stable display while another background run queues.
|
|
20
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
21
|
- Verify live token updates are file-event driven and polled on each running one-second tick, elapsed repainting exists only while work runs, and widget disposal closes watchers and timers.
|
|
22
22
|
- Verify selected active/queued run cancellation, blocked-response live Fleet updates, authoritative stale-field replacement, parent-shutdown cancellation, idempotent missing-tab cleanup, cleanup notifications, atomic state replacement, non-retried uncertain startup operations, and stalled warnings.
|
|
23
|
+
- Verify `status(runId)` isolates one run, projects queue-only state, prefers durable state over a stale queue record, exposes blocked questions, marks terminal states settled, rejects unknown IDs, and leaves full `history` semantics unchanged.
|
|
23
24
|
- Verify resumed batches retain newly allocated pane IDs and historical follow-ups exclude simultaneous session writers.
|
|
24
25
|
|
|
25
26
|
## Latest baseline
|
|
26
27
|
|
|
27
|
-
All
|
|
28
|
+
All 179 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.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: analyst
|
|
3
|
+
description: Neutral read-only analyst for dynamically assigned roles
|
|
4
|
+
access: read
|
|
5
|
+
tools: read, bash, grep, find, ls
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Act as an independent analyst and follow the role and evaluation method in the task prompt. Do not modify project files. Separate direct evidence, inference, and unknowns, and report concrete evidence with useful paths.
|
|
@@ -259,7 +259,7 @@ export function registerHerdrSubagents(pi: ExtensionAPI, dependencies: HerdrSuba
|
|
|
259
259
|
pi.registerTool({
|
|
260
260
|
name: "herdr_subagents_control",
|
|
261
261
|
label: "Herdr Subagents Control",
|
|
262
|
-
description: "Inspect Herdr personas
|
|
262
|
+
description: "Inspect Herdr personas, query one run by ID, list run history, cancel active or queued work, answer a blocked task, resume a saved task, or remove a selected archived run. Use status for one run and history only for the full archive. Use herdr_subagents to start new work.",
|
|
263
263
|
promptSnippet: "Inspect and control Herdr subagent runs",
|
|
264
264
|
parameters: HerdrSubagentsControlParams,
|
|
265
265
|
|
|
@@ -4,6 +4,21 @@ import { StringEnum } from "@earendil-works/pi-ai";
|
|
|
4
4
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import { Type } from "typebox";
|
|
6
6
|
import { resultPathForAttempt } from "../src/result-path.ts";
|
|
7
|
+
import { PARENT_PANE_ENV } from "../src/live-agent.ts";
|
|
8
|
+
|
|
9
|
+
const RETURN_SHORTCUT = "alt+p";
|
|
10
|
+
|
|
11
|
+
async function returnToParent(pi: ExtensionAPI, ctx: { ui: { notify(message: string, type?: "info" | "warning" | "error"): void } }): Promise<void> {
|
|
12
|
+
const parentPaneId = process.env[PARENT_PANE_ENV]?.trim();
|
|
13
|
+
if (!parentPaneId) {
|
|
14
|
+
ctx.ui.notify("Parent Agent pane is unavailable for this Subagent session.", "error");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const result = await pi.exec("herdr", ["agent", "focus", parentPaneId]);
|
|
18
|
+
if (result.code !== 0) {
|
|
19
|
+
ctx.ui.notify(`Could not return to the parent Agent: ${result.stderr.trim() || result.stdout.trim() || `Herdr exited ${result.code}`}`, "error");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
7
22
|
|
|
8
23
|
const ReportParams = Type.Object({
|
|
9
24
|
status: StringEnum(["completed", "needs-input", "failed"] as const, { description: "Current outcome for this task." }),
|
|
@@ -24,6 +39,17 @@ function writeAtomic(path: string, content: string): void {
|
|
|
24
39
|
}
|
|
25
40
|
|
|
26
41
|
export default function (pi: ExtensionAPI) {
|
|
42
|
+
pi.registerCommand("parent", {
|
|
43
|
+
description: "Return focus to the parent Agent pane",
|
|
44
|
+
async handler(_args, ctx) { await returnToParent(pi, ctx); },
|
|
45
|
+
});
|
|
46
|
+
pi.registerShortcut(RETURN_SHORTCUT, {
|
|
47
|
+
description: "Return to parent Agent",
|
|
48
|
+
async handler(ctx) { await returnToParent(pi, ctx); },
|
|
49
|
+
});
|
|
50
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
51
|
+
if (process.env[PARENT_PANE_ENV]?.trim()) ctx.ui.setStatus("herdr-subagents-parent", `${RETURN_SHORTCUT} parent`);
|
|
52
|
+
});
|
|
27
53
|
pi.registerTool({
|
|
28
54
|
name: "subagent_report",
|
|
29
55
|
label: "Subagent Report",
|
|
@@ -2,7 +2,7 @@ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
|
2
2
|
import { appendFile, mkdir, readFile, rm, rmdir } from "node:fs/promises";
|
|
3
3
|
import { isAbsolute, join } from "node:path";
|
|
4
4
|
import { CliHerdrAutomation, type HerdrAutomation } from "./herdr.ts";
|
|
5
|
-
import { liveAgentName, promptLiveAgent } from "./live-agent.ts";
|
|
5
|
+
import { childEnvironment, liveAgentName, promptLiveAgent } from "./live-agent.ts";
|
|
6
6
|
import { writeJsonAtomic, writeTextAtomic } from "./state.ts";
|
|
7
7
|
import { cleanupRunTab } from "./tab-cleanup.ts";
|
|
8
8
|
import type { OwnerIdentity } from "./ownership.ts";
|
|
@@ -144,7 +144,7 @@ async function resumeHistoricalTaskLocked(cwd: string, runId: string, taskNumber
|
|
|
144
144
|
let report: PersistedReport;
|
|
145
145
|
let status: "blocked" | "completed" | "failed";
|
|
146
146
|
try {
|
|
147
|
-
const env =
|
|
147
|
+
const env = childEnvironment(historical.recordDirectory);
|
|
148
148
|
tab = await herdr.createTab({ workspaceId: process.env.HERDR_WORKSPACE_ID ?? "", cwd, label: `SA · ${run.label}`, env, focus: false, signal: options.signal });
|
|
149
149
|
const paneLabel = `${String(historical.order).padStart(2, "0")} · ${historical.name}`;
|
|
150
150
|
await herdr.renamePane(tab.paneId, paneLabel, options.signal);
|
|
@@ -17,6 +17,17 @@ export interface LiveTaskRecord {
|
|
|
17
17
|
skills?: string[];
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
export const PARENT_PANE_ENV = "PI_HERDR_SUBAGENTS_PARENT_PANE_ID";
|
|
21
|
+
|
|
22
|
+
export function childEnvironment(taskDirectory: string, environment: NodeJS.ProcessEnv = process.env): Record<string, string> {
|
|
23
|
+
const parentPaneId = environment.HERDR_PANE_ID?.trim();
|
|
24
|
+
return {
|
|
25
|
+
PI_HERDR_SUBAGENTS_CHILD: "1",
|
|
26
|
+
PI_HERDR_SUBAGENTS_TASK_DIR: taskDirectory,
|
|
27
|
+
...(parentPaneId ? { [PARENT_PANE_ENV]: parentPaneId } : {}),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
20
31
|
export function childPiArgs(task: LiveTaskRecord, systemPromptFile: string): string[] {
|
|
21
32
|
const args = [
|
|
22
33
|
"--approve",
|
|
@@ -16,8 +16,8 @@ export const HerdrSubagentsParams = Type.Object({
|
|
|
16
16
|
}, { additionalProperties: false });
|
|
17
17
|
|
|
18
18
|
export const HerdrSubagentsControlParams = Type.Object({
|
|
19
|
-
action: StringEnum(["list", "respond", "history", "resume", "cancel", "cleanup"] as const, { description: "Control operation to perform." }),
|
|
20
|
-
runId: Type.Optional(Type.String({ minLength: 1, description: "Durable run ID used by respond, resume, cancel, or cleanup." })),
|
|
19
|
+
action: StringEnum(["list", "status", "respond", "history", "resume", "cancel", "cleanup"] as const, { description: "Control operation to perform." }),
|
|
20
|
+
runId: Type.Optional(Type.String({ minLength: 1, description: "Durable run ID used by status, respond, resume, cancel, or cleanup." })),
|
|
21
21
|
answer: Type.Optional(Type.String({ minLength: 1, description: "Answer sent to a blocked task when action is respond." })),
|
|
22
22
|
task: Type.Optional(Type.Integer({ minimum: 1, description: "One-based task number selected for respond or resume." })),
|
|
23
23
|
prompt: Type.Optional(Type.String({ minLength: 1, description: "Follow-up prompt used by resume." })),
|
|
@@ -3,6 +3,7 @@ import { writeJsonAtomic } from "./state.ts";
|
|
|
3
3
|
import type { RunRecord } from "./records.ts";
|
|
4
4
|
import { applyRunStatus } from "./run-status.ts";
|
|
5
5
|
import { cleanupRunTab } from "./tab-cleanup.ts";
|
|
6
|
+
import { childEnvironment } from "./live-agent.ts";
|
|
6
7
|
|
|
7
8
|
interface RunPaneAllocatorOptions {
|
|
8
9
|
herdr: HerdrAutomation;
|
|
@@ -31,7 +32,7 @@ export class RunPaneAllocator {
|
|
|
31
32
|
let paneId = "";
|
|
32
33
|
this.allocation = this.allocation.then(async () => {
|
|
33
34
|
const { herdr, run, runFile, signal } = this.options;
|
|
34
|
-
const env =
|
|
35
|
+
const env = childEnvironment(taskDirectory);
|
|
35
36
|
let anchorExists = Boolean(run.tabId && this.lastPaneId);
|
|
36
37
|
if (anchorExists && this.options.validateExistingPane) {
|
|
37
38
|
const survivingPaneIds: string[] = [];
|
|
@@ -3,7 +3,7 @@ import { mkdir, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { CliHerdrAutomation, type HerdrAutomation } from "./herdr.ts";
|
|
5
5
|
import { ensureRuntimeIgnored } from "./history.ts";
|
|
6
|
-
import { liveAgentName, promptLiveAgent } from "./live-agent.ts";
|
|
6
|
+
import { childEnvironment, liveAgentName, promptLiveAgent } from "./live-agent.ts";
|
|
7
7
|
import { writeJsonAtomic } from "./state.ts";
|
|
8
8
|
import { cleanupRunTab } from "./tab-cleanup.ts";
|
|
9
9
|
import type { OwnerIdentity } from "./ownership.ts";
|
|
@@ -13,9 +13,6 @@ import { reportProtocolPrompt } from "./protocol.ts";
|
|
|
13
13
|
import { applyRunStatus } from "./run-status.ts";
|
|
14
14
|
import { isAbortError } from "./errors.ts";
|
|
15
15
|
|
|
16
|
-
const CHILD_ENV = "PI_HERDR_SUBAGENTS_CHILD";
|
|
17
|
-
const TASK_DIRECTORY_ENV = "PI_HERDR_SUBAGENTS_TASK_DIR";
|
|
18
|
-
|
|
19
16
|
export type HerdrSubagentsDocument = PersistedDocument;
|
|
20
17
|
export type HerdrSubagentsReport = PersistedReport;
|
|
21
18
|
|
|
@@ -168,10 +165,7 @@ export async function runHerdrSubagents(prompt: string, options: HerdrSubagentsO
|
|
|
168
165
|
workspaceId: process.env.HERDR_WORKSPACE_ID,
|
|
169
166
|
cwd,
|
|
170
167
|
label: `SA · ${label}`,
|
|
171
|
-
env:
|
|
172
|
-
[CHILD_ENV]: "1",
|
|
173
|
-
[TASK_DIRECTORY_ENV]: taskDirectory,
|
|
174
|
-
},
|
|
168
|
+
env: childEnvironment(taskDirectory),
|
|
175
169
|
focus: false,
|
|
176
170
|
signal: options.signal,
|
|
177
171
|
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { loadQueuedRuns } from "./queued-runs.ts";
|
|
4
|
+
import { isSettledRunStatus, readReport, readRunRecord, readTaskRecord, type RunStatus, type TaskStatus } from "./records.ts";
|
|
5
|
+
import { findRunDirectory } from "./run-locator.ts";
|
|
6
|
+
|
|
7
|
+
export interface RunStatusTask {
|
|
8
|
+
order: number;
|
|
9
|
+
name: string;
|
|
10
|
+
agent: string;
|
|
11
|
+
status: TaskStatus;
|
|
12
|
+
summary?: string;
|
|
13
|
+
question?: string;
|
|
14
|
+
error?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface RunStatusSnapshot {
|
|
18
|
+
runId: string;
|
|
19
|
+
label: string;
|
|
20
|
+
status: RunStatus;
|
|
21
|
+
settled: boolean;
|
|
22
|
+
source: "run" | "queue";
|
|
23
|
+
queuedAt?: string;
|
|
24
|
+
startedAt?: string;
|
|
25
|
+
updatedAt?: string;
|
|
26
|
+
completedAt?: string;
|
|
27
|
+
question?: string;
|
|
28
|
+
error?: string;
|
|
29
|
+
tasks: RunStatusTask[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function loadDurableStatus(cwd: string, runId: string): Promise<RunStatusSnapshot | undefined> {
|
|
33
|
+
const runsDirectory = join(cwd, ".pi", "herdr-subagents", "runs");
|
|
34
|
+
const runDirectory = await findRunDirectory(runsDirectory, runId);
|
|
35
|
+
if (!runDirectory) return undefined;
|
|
36
|
+
const run = await readRunRecord(join(runDirectory, "run.json"));
|
|
37
|
+
const tasksDirectory = join(runDirectory, "tasks");
|
|
38
|
+
const tasks: RunStatusTask[] = [];
|
|
39
|
+
if (existsSync(tasksDirectory)) for (const entry of readdirSync(tasksDirectory).sort()) {
|
|
40
|
+
const taskDirectory = join(tasksDirectory, entry);
|
|
41
|
+
const task = await readTaskRecord(join(taskDirectory, "task.json"));
|
|
42
|
+
const reportFile = join(taskDirectory, "report.json");
|
|
43
|
+
const reportIsCurrent = task.status === "blocked" || isSettledRunStatus(task.status);
|
|
44
|
+
const report = reportIsCurrent && existsSync(reportFile) ? await readReport(reportFile) : undefined;
|
|
45
|
+
const question = report?.question ?? task.question;
|
|
46
|
+
const error = report?.error ?? task.error;
|
|
47
|
+
tasks.push({
|
|
48
|
+
order: task.order,
|
|
49
|
+
name: task.name ?? task.agent ?? `Task ${task.order}`,
|
|
50
|
+
agent: task.agent ?? "worker",
|
|
51
|
+
status: task.status,
|
|
52
|
+
...(report?.summary ? { summary: report.summary } : {}),
|
|
53
|
+
...(question ? { question } : {}),
|
|
54
|
+
...(error ? { error } : {}),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
runId: run.id,
|
|
59
|
+
label: run.label,
|
|
60
|
+
status: run.status,
|
|
61
|
+
settled: isSettledRunStatus(run.status),
|
|
62
|
+
source: "run",
|
|
63
|
+
...(run.startedAt ? { startedAt: run.startedAt } : {}),
|
|
64
|
+
...(run.updatedAt ? { updatedAt: run.updatedAt } : {}),
|
|
65
|
+
...(run.completedAt ? { completedAt: run.completedAt } : {}),
|
|
66
|
+
...(run.question ? { question: run.question } : {}),
|
|
67
|
+
...(run.error ? { error: run.error } : {}),
|
|
68
|
+
tasks: tasks.sort((left, right) => left.order - right.order),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function getSubagentRunStatus(cwd: string, runId: string): Promise<RunStatusSnapshot | undefined> {
|
|
73
|
+
const durable = await loadDurableStatus(cwd, runId);
|
|
74
|
+
if (durable) return durable;
|
|
75
|
+
const queued = (await loadQueuedRuns(cwd)).find((candidate) => candidate.runId === runId);
|
|
76
|
+
if (!queued) return undefined;
|
|
77
|
+
return {
|
|
78
|
+
runId: queued.runId,
|
|
79
|
+
label: queued.params.label ?? "batch",
|
|
80
|
+
status: "queued",
|
|
81
|
+
settled: false,
|
|
82
|
+
source: "queue",
|
|
83
|
+
queuedAt: queued.queuedAt,
|
|
84
|
+
tasks: queued.params.tasks.map((task, index) => ({ order: index + 1, name: task.name, agent: task.agent ?? "worker", status: "queued" })),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function runStatusText(status: RunStatusSnapshot): string {
|
|
89
|
+
const lines = [`${status.runId} · ${status.label} · ${status.status} · ${status.tasks.length} tasks`];
|
|
90
|
+
for (const task of status.tasks) {
|
|
91
|
+
const detail = task.question ? ` — ${task.question}` : task.summary ? ` — ${task.summary}` : task.error ? ` — ${task.error}` : "";
|
|
92
|
+
lines.push(` ${task.order}. ${task.name}: ${task.status}${detail}`);
|
|
93
|
+
}
|
|
94
|
+
return lines.join("\n");
|
|
95
|
+
}
|
|
@@ -8,6 +8,7 @@ import { HERDR_ACTIONS, validateControlParams, validateRunParams } from "./valid
|
|
|
8
8
|
import type { OwnerIdentity } from "./ownership.ts";
|
|
9
9
|
import { cancelActiveSubagentRuns } from "./reconcile.ts";
|
|
10
10
|
import { cancelQueuedRuns, loadQueuedRuns } from "./queued-runs.ts";
|
|
11
|
+
import { getSubagentRunStatus, runStatusText } from "./status.ts";
|
|
11
12
|
|
|
12
13
|
export interface HerdrSubagentsTaskParams { name: string; prompt: string; agent?: string; model?: string; }
|
|
13
14
|
|
|
@@ -19,7 +20,7 @@ export interface HerdrSubagentsRunParams {
|
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export interface HerdrSubagentsControlToolParams {
|
|
22
|
-
action: "list" | "respond" | "history" | "resume" | "cancel" | "cleanup";
|
|
23
|
+
action: "list" | "status" | "respond" | "history" | "resume" | "cancel" | "cleanup";
|
|
23
24
|
runId?: string;
|
|
24
25
|
answer?: string;
|
|
25
26
|
task?: number;
|
|
@@ -28,7 +29,7 @@ export interface HerdrSubagentsControlToolParams {
|
|
|
28
29
|
|
|
29
30
|
/** Internal superset used by shared execution helpers. */
|
|
30
31
|
export interface HerdrSubagentsToolParams {
|
|
31
|
-
action?: "list" | "respond" | "history" | "resume" | "cancel" | "cleanup";
|
|
32
|
+
action?: "list" | "status" | "respond" | "history" | "resume" | "cancel" | "cleanup";
|
|
32
33
|
prompt?: string;
|
|
33
34
|
agent?: string;
|
|
34
35
|
label?: string;
|
|
@@ -114,6 +115,16 @@ const errorResponse = (text: string, details: Record<string, unknown> = {}): Too
|
|
|
114
115
|
});
|
|
115
116
|
|
|
116
117
|
const actionHandlers = {
|
|
118
|
+
async status({ params, ctx }: ToolExecutionContext) {
|
|
119
|
+
try {
|
|
120
|
+
const runId = (params.runId as string).trim();
|
|
121
|
+
const status = await getSubagentRunStatus(ctx.cwd, runId);
|
|
122
|
+
if (!status) return errorResponse(`Unknown Subagent run '${runId}'.`, { action: "status", runId });
|
|
123
|
+
return { content: [{ type: "text", text: runStatusText(status) }], details: { action: "status", ...status } };
|
|
124
|
+
} catch (error) {
|
|
125
|
+
return errorResponse(error instanceof Error ? error.message : String(error), { action: "status", runId: params.runId });
|
|
126
|
+
}
|
|
127
|
+
},
|
|
117
128
|
async history({ ctx }: ToolExecutionContext) {
|
|
118
129
|
const history = await listSubagentHistory(ctx.cwd);
|
|
119
130
|
return { content: [{ type: "text", text: historyText(history) }], details: { action: "history", runs: history } };
|
|
@@ -3,6 +3,7 @@ import type { HerdrSubagentsControlToolParams, HerdrSubagentsRunParams } from ".
|
|
|
3
3
|
export const HERDR_ACTIONS = {
|
|
4
4
|
list: { allowed: ["action"], includeRunId: false },
|
|
5
5
|
history: { allowed: ["action"], includeRunId: false },
|
|
6
|
+
status: { allowed: ["action", "runId"], includeRunId: true },
|
|
6
7
|
cancel: { allowed: ["action", "runId"], includeRunId: true },
|
|
7
8
|
cleanup: { allowed: ["action", "runId"], includeRunId: true },
|
|
8
9
|
respond: { allowed: ["action", "runId", "answer", "task"], includeRunId: true },
|
|
@@ -19,7 +20,7 @@ export function validateControlParams(params: HerdrSubagentsControlToolParams):
|
|
|
19
20
|
if (!allowed) return "Provide a valid control action.";
|
|
20
21
|
const extra = Object.keys(params).find((key) => !allowed.includes(key as keyof HerdrSubagentsControlToolParams));
|
|
21
22
|
if (extra) return `Field '${extra}' is not valid with action '${params.action}'.`;
|
|
22
|
-
if ((params.action === "cancel" || params.action === "cleanup") && !params.runId?.trim()) return `runId is required for action '${params.action}'.`;
|
|
23
|
+
if ((params.action === "status" || params.action === "cancel" || params.action === "cleanup") && !params.runId?.trim()) return `runId is required for action '${params.action}'.`;
|
|
23
24
|
if (params.action === "respond" && (!params.runId?.trim() || !params.answer?.trim())) return "runId and answer are required for action 'respond'.";
|
|
24
25
|
if (params.action === "resume" && (!params.runId?.trim() || !params.task || !params.prompt?.trim())) return "runId, task, and prompt are required for action 'resume'.";
|
|
25
26
|
return undefined;
|
|
@@ -251,6 +251,10 @@ export default function (_pi: ExtensionAPI) {
|
|
|
251
251
|
if (!controlSchema.properties?.[name]?.description) throw new Error(`control schema property '${name}' is missing or undocumented`);
|
|
252
252
|
}
|
|
253
253
|
if (!controlSchema.required?.includes("action")) throw new Error("herdr_subagents_control action must be required");
|
|
254
|
+
const controlActions = controlSchema.properties.action.enum ?? controlSchema.properties.action.anyOf?.map((entry: Record<string, unknown>) => entry.const);
|
|
255
|
+
if (!controlActions?.includes("status")) throw new Error("herdr_subagents_control action enum must include status");
|
|
256
|
+
if (!/status/.test(controlSchema.properties.runId.description)) throw new Error("control runId description must document status");
|
|
257
|
+
if (!/Use status for one run/.test(controlTool.description)) throw new Error("control tool description must distinguish status from history");
|
|
254
258
|
if (!/Foreground calls block/.test(tool.description) || !/background calls return a run ID immediately/.test(tool.description)) {
|
|
255
259
|
throw new Error("tool description does not distinguish foreground and background execution");
|
|
256
260
|
}
|