pi-subagents 0.45.2 → 0.46.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/CHANGELOG.md +24 -0
- package/README.md +2 -0
- package/docs/agents.md +342 -0
- package/docs/configuration.md +320 -0
- package/docs/extension-api.md +308 -0
- package/docs/missions.md +117 -0
- package/docs/models.md +190 -0
- package/docs/observability.md +174 -0
- package/docs/tool-reference.md +343 -0
- package/docs/watchdog.md +176 -0
- package/docs/workflows.md +163 -0
- package/package.json +4 -2
- package/skills/pi-subagents/references/execution-controls.md +2 -2
- package/src/agents/agents.ts +17 -8
- package/src/agents/frontmatter.ts +7 -3
- package/src/agents/skills.ts +2 -9
- package/src/api/project-panes.ts +30 -0
- package/src/extension/config.ts +15 -1
- package/src/extension/index.ts +36 -16
- package/src/extension/schemas.ts +3 -2
- package/src/extension/subagent-guide.ts +39 -0
- package/src/extension/tool-description.ts +4 -4
- package/src/inspectors/herdr/project-panes.ts +457 -62
- package/src/missions/actions.ts +25 -2
- package/src/missions/lifecycle.ts +21 -2
- package/src/missions/store.ts +77 -1
- package/src/missions/types.ts +33 -0
- package/src/runs/background/async-execution.ts +7 -1
- package/src/runs/background/completion-replay.ts +267 -0
- package/src/runs/background/result-watcher.ts +12 -4
- package/src/runs/background/wait-completions.ts +39 -5
- package/src/runs/background/wait-subscriptions.ts +18 -3
- package/src/runs/foreground/execution.ts +4 -0
- package/src/runs/foreground/foreground-history.ts +137 -0
- package/src/runs/foreground/subagent-executor.ts +310 -44
- package/src/shared/fork-context.ts +13 -0
- package/src/shared/prompt-resources.ts +51 -0
- package/src/shared/types.ts +30 -1
- package/src/shared/utf8.ts +11 -0
- package/src/slash/prompt-workflows.ts +2 -15
- package/src/slash/slash-commands.ts +19 -1
- package/src/tui/fleet-status.ts +8 -2
- package/src/tui/fleet.ts +135 -25
- package/src/tui/render.ts +120 -7
- package/src/workflows/scripted-workflow.ts +167 -10
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Workflows and orchestration
|
|
2
|
+
|
|
3
|
+
How to compose subagents: the recommended pattern, packaged prompt shortcuts, scripted workflows, direct commands, worktree isolation, and child-to-parent coordination.
|
|
4
|
+
|
|
5
|
+
## Recommended orchestration pattern
|
|
6
|
+
|
|
7
|
+
Use orchestration as parent-agent guidance, not as a runtime workflow mode. For implementation work, the recommended loop is:
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
clarify → scout → worker → fresh reviewers → worker
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Packaged `worker`, `oracle`, and `advisor` default to forked context when a launch omits `context`; pass `context: "fresh"` when you intentionally want a fresh child run.
|
|
14
|
+
|
|
15
|
+
Child-safety boundaries are enforced at runtime:
|
|
16
|
+
|
|
17
|
+
- Spawned child sessions do not receive the bundled `pi-subagents` skill.
|
|
18
|
+
- Forked child context filtering removes parent-only subagent artifacts (including old hidden orchestration-instruction messages, slash/status/control messages, and prior parent `subagent` tool-call/tool-result history) while preserving ordinary prose and unrelated tool calls/results.
|
|
19
|
+
- By default, children do not register the `subagent` tool and receive boundary instructions that they are not the parent orchestrator and must not propose or run subagents.
|
|
20
|
+
- The explicit exception is an agent whose resolved builtin `tools` includes `subagent`; that child gets a child-safe `subagent` tool for the fanout work the parent assigned, still bounded by `maxSubagentDepth`.
|
|
21
|
+
|
|
22
|
+
## Prompt shortcuts
|
|
23
|
+
|
|
24
|
+
The package includes reusable prompt templates for common workflows. You do not need them, but they are handy when you want the same shape every time:
|
|
25
|
+
|
|
26
|
+
| Prompt | Use it for |
|
|
27
|
+
|--------|------------|
|
|
28
|
+
| `/parallel-review` | Launch fresh-context reviewers with distinct angles, then synthesize what to fix. |
|
|
29
|
+
| `/review-loop` | Run parent-controlled worker, reviewer, and fix-worker cycles until clean or capped. |
|
|
30
|
+
| `/parallel-research` | Combine `researcher` and `scout` for external evidence, local code context, and practical tradeoffs. |
|
|
31
|
+
| `/gather-context-and-clarify` | Scout/research first, then ask the user the clarification questions that matter. |
|
|
32
|
+
| `/parallel-cleanup` | Run review-only cleanup passes after implementation. |
|
|
33
|
+
|
|
34
|
+
Add `autofix` to `/parallel-review` or `/parallel-cleanup` to apply only the synthesized fixes worth doing now after reviewers return.
|
|
35
|
+
|
|
36
|
+
## Scripted workflows (workflowScript)
|
|
37
|
+
|
|
38
|
+
All model-facing subagent execution is expressed through `workflowScript` in the `subagent` tool. Use stable keys and ordinary JavaScript for one child, sequence, and parallelism. Scripts are ordinary JavaScript statement bodies. Use an explicit `return` for a useful result:
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
subagent({ workflowScript: `
|
|
42
|
+
const scan = await runs.run("scan", { agent: "scout", task: "Scan the codebase" });
|
|
43
|
+
const reviews = await runs.all([
|
|
44
|
+
{ key: "correctness", agent: "reviewer", task: "Review correctness: " + scan.output },
|
|
45
|
+
{ key: "tests", agent: "reviewer", task: "Review tests: " + scan.output }
|
|
46
|
+
]);
|
|
47
|
+
return reviews.map(result => result.output);
|
|
48
|
+
` });
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
For long task text with Markdown fences or shell blocks, use quoted lines instead of a raw template literal:
|
|
52
|
+
|
|
53
|
+
````js
|
|
54
|
+
const task = [
|
|
55
|
+
"Run this command:",
|
|
56
|
+
"```bash",
|
|
57
|
+
"npm test",
|
|
58
|
+
"```"
|
|
59
|
+
].join("\n");
|
|
60
|
+
return runs.run("test", { agent: "worker", task });
|
|
61
|
+
````
|
|
62
|
+
|
|
63
|
+
A plain workflow creates one enclosing mission by default. Its children do not create separate missions. The result exposes the id as `details.missionId`, and human-readable output ends with `Mission: <id> (<status>)`. Pass `mission:false` for an ephemeral workflow with no mission or durable `state` global.
|
|
64
|
+
|
|
65
|
+
For watched same-repo workflows, pass `async:false` to show the live in-chat workflow card. `chatProgress` can force `off` or `live-card` when the automatic policy is not what you want. Foreground workflows default to a 30-minute timeout; async workflows have no default timeout. See the [tool reference](tool-reference.md) for the full parameter list.
|
|
66
|
+
|
|
67
|
+
The legacy `/chain`, `/parallel`, and `/run-chain` commands are not registered.
|
|
68
|
+
|
|
69
|
+
## Direct commands
|
|
70
|
+
|
|
71
|
+
Use `/run <agent> [task] [--bg] [--fork]` for one child.
|
|
72
|
+
|
|
73
|
+
## Worktree isolation
|
|
74
|
+
|
|
75
|
+
Scripted workflows can give each writing child a separate managed git worktree by setting `worktree: true` on each `runs.run` / `runs.all` item:
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
const [api, ui] = await runs.all([
|
|
79
|
+
{ key: "api", agent: "worker", task: "Implement the API", worktree: true },
|
|
80
|
+
{ key: "ui", agent: "worker", task: "Implement the UI", worktree: true }
|
|
81
|
+
]);
|
|
82
|
+
return { api: api.artifactPaths, ui: ui.artifactPaths };
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Each child uses the existing worktree lifecycle: it branches from clean HEAD, journals ownership before launch, captures a patch and handoff manifest, then removes cleanly captured temporary worktrees and branches. The handoff manifest path remains available in the child's `artifactPaths`; return or emit it when the orchestrator needs to apply or inspect the patches. `runs.ref` stays concise and intentionally omits full paths.
|
|
86
|
+
|
|
87
|
+
A top-level `{ workflowScript, worktree: true }` makes isolation the default for every workflow child. An individual child can override that default with `worktree: false`. Keep one writer when parallel writes are not intentionally isolated.
|
|
88
|
+
|
|
89
|
+
Configure the worktree base directory and setup hook in [configuration.md](configuration.md).
|
|
90
|
+
|
|
91
|
+
## Supervisor coordination (child asks parent)
|
|
92
|
+
|
|
93
|
+
Child agents can talk back to the parent Pi session without installing `pi-intercom`. `pi-subagents` provides the child-facing `contact_supervisor` tool and the parent-facing `subagent_supervisor({ action: "reply" })` path natively. If no external `pi-intercom` tool owns the `intercom` name, the native channel also exposes `intercom` as a compatibility fallback.
|
|
94
|
+
|
|
95
|
+
Use it for work where the child might need a decision instead of guessing:
|
|
96
|
+
|
|
97
|
+
```text
|
|
98
|
+
Run this implementation in the background. If the worker gets blocked or needs a product decision, have it ask me through intercom.
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```text
|
|
102
|
+
Ask oracle to review this plan. If it sees a decision I need to make, have it ask me instead of assuming.
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The child uses one dedicated coordination tool, `contact_supervisor`, with a `reason`:
|
|
106
|
+
|
|
107
|
+
- `need_decision` — blocking decisions or clarification
|
|
108
|
+
- `interview_request` — structured input
|
|
109
|
+
- `progress_update` — short non-blocking updates when a discovery changes the plan
|
|
110
|
+
|
|
111
|
+
Children should not ask for clarification when the only conflict is review-only/no-edit versus progress-writing or artifact-writing instructions; no-edit wins.
|
|
112
|
+
|
|
113
|
+
The parent replies with `subagent_supervisor({ action: "reply", replyTo, message })` or checks pending requests with `subagent_supervisor({ action: "pending" })`. Supervisor messages are scoped to the exact Pi session id that spawned the child. A second Pi session in the same repository does not receive those requests.
|
|
114
|
+
|
|
115
|
+
Child-side routine completion handoffs are not expected. If a child appears stalled, needs-attention notices show up in the parent session with useful next actions, such as checking `subagent({ action: "status" })`, interrupting the run, or nudging the child.
|
|
116
|
+
|
|
117
|
+
If messages do not show up, run `/subagents-doctor`. Advanced users can tune the bridge with `intercomBridge` in [configuration.md](configuration.md).
|
|
118
|
+
|
|
119
|
+
## Recursion guard
|
|
120
|
+
|
|
121
|
+
Subagents can call `subagent` only when their resolved builtin tools explicitly include `subagent`. That is meant for delegated fanout agents, not ordinary worker/reviewer children. A depth guard prevents unbounded nesting.
|
|
122
|
+
|
|
123
|
+
By default, nesting is limited to two levels: main session → subagent → sub-subagent. Deeper calls are blocked with guidance to complete the current task directly. Nested runs appear in the parent status widget and `status` output as a tree, and `status`, `interrupt`, and `resume` can target a nested run by its id.
|
|
124
|
+
|
|
125
|
+
Configure the limit with:
|
|
126
|
+
|
|
127
|
+
1. `PI_SUBAGENT_MAX_DEPTH` before starting Pi
|
|
128
|
+
2. `config.maxSubagentDepth`
|
|
129
|
+
3. `maxSubagentDepth` in agent frontmatter, which can only tighten the inherited limit
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
export PI_SUBAGENT_MAX_DEPTH=3
|
|
133
|
+
export PI_SUBAGENT_MAX_DEPTH=1
|
|
134
|
+
export PI_SUBAGENT_MAX_DEPTH=0
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`PI_SUBAGENT_DEPTH` is internal and propagated automatically. Do not set it manually.
|
|
138
|
+
|
|
139
|
+
## Prompt-template integration
|
|
140
|
+
|
|
141
|
+
`pi-subagents` includes a native prompt-workflow adapter for reusable subagent prompt templates, so you do not need `pi-prompt-template-model` for the common subagent workflow path.
|
|
142
|
+
|
|
143
|
+
Create a prompt in `.pi/prompts/` or `~/.pi/agent/prompts/`:
|
|
144
|
+
|
|
145
|
+
```md
|
|
146
|
+
---
|
|
147
|
+
description: Take a screenshot
|
|
148
|
+
model: claude-sonnet-4-20250514
|
|
149
|
+
subagent: browser-screenshoter
|
|
150
|
+
cwd: /tmp/screenshots
|
|
151
|
+
---
|
|
152
|
+
Use url in the prompt to take screenshot: $@
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Then run it through the native adapter:
|
|
156
|
+
|
|
157
|
+
```text
|
|
158
|
+
/prompt-workflow take-screenshot https://example.com
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The adapter delegates to the named subagent, applies `model`, `skill`, `cwd`, and fork/fresh context metadata, and supports runtime overrides such as `--subagent reviewer`, `--fork`, `--fresh`, and `--bg`.
|
|
162
|
+
|
|
163
|
+
Prompt templates with `chain:` frontmatter are translated into `workflowScript` and launched through `/prompt-workflow`; `/chain-prompts` is no longer registered.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-subagents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.46.0",
|
|
4
4
|
"description": "Pi extension for single-agent delegation and scripted multi-agent workflows",
|
|
5
5
|
"author": "Nico Bailon",
|
|
6
6
|
"license": "MIT",
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"./control-channel": "./src/api/control-channel.ts",
|
|
16
16
|
"./intercom-bridge": "./src/api/intercom-bridge.ts",
|
|
17
17
|
"./pi-args": "./src/api/pi-args.ts",
|
|
18
|
-
"./shared-types": "./src/api/shared-types.ts"
|
|
18
|
+
"./shared-types": "./src/api/shared-types.ts",
|
|
19
|
+
"./project-panes": "./src/api/project-panes.ts"
|
|
19
20
|
},
|
|
20
21
|
"repository": {
|
|
21
22
|
"type": "git",
|
|
@@ -44,6 +45,7 @@
|
|
|
44
45
|
"agents/",
|
|
45
46
|
"skills/**/*",
|
|
46
47
|
"prompts/**/*",
|
|
48
|
+
"docs/**/*",
|
|
47
49
|
"README.md",
|
|
48
50
|
"CHANGELOG.md"
|
|
49
51
|
],
|
|
@@ -68,11 +68,11 @@ subagent({
|
|
|
68
68
|
})
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
Scripts run in a timed worker with only `runs.run`, `runs.all`, `runs.status`, `runs.ref/refs`, `emit`, captured `console`, and standard JavaScript. Mission-attached workflows also get `await state.get(key)` and `await state.set(key, value)` for durable JSON state shared across workflows on the same mission; `mission: false` workflows have no `state` global. Stable keys are required. Child launches follow ordinary single-agent execution controls. Give each child a distinct decision and output path when reports must outlive the workflow, then consume the aggregate workflow result before opening individual reports.
|
|
71
|
+
Scripts run in a timed worker with only `runs.run`, `runs.all`, `runs.status`, `runs.ref/refs`, `prompts.render`, `emit`, captured `console`, and standard JavaScript. `await prompts.render("package:<name>" | "user:<name>" | "project:<name>", vars?)` reads a named Markdown fragment through the host resolver, applies simple scalar `{{name}}` interpolation, and returns plain task text. It does not give the script filesystem access. Pass the rendered text explicitly as `task` to `runs.run`. Mission-attached workflows also get `await state.get(key)` and `await state.set(key, value)` for durable JSON state shared across workflows on the same mission; `mission: false` workflows have no `state` global. Stable keys are required. Child launches follow ordinary single-agent execution controls. Give each child a distinct decision and output path when reports must outlive the workflow, then consume the aggregate workflow result before opening individual reports.
|
|
72
72
|
|
|
73
73
|
For one host-run verification command, pass `gate: "npm test"` on a `runs.run`/`runs.all` item (or at the top level as a workflow default). It is shorthand for verified acceptance with that single command: the runtime executes it on the host, records the result as evidence, and memoizes it per tracked workspace state and effective environment. `gate` cannot be combined with `acceptance`; use explicit `acceptance.verify` for multiple commands or custom criteria.
|
|
74
74
|
|
|
75
|
-
Completed workflow children from this parent session stay addressable as retained children. `subagent({ action: "children.list" })` lists up to the last 10 with run ids, and a later workflow continues one with `runs.run(key, { resume: "<run-id>", task: "follow-up" })`. `resume` and `agent` are mutually exclusive, the revived child keeps its stored agent/model/tool contract, and `gate` is rejected on retained resume items.
|
|
75
|
+
Completed workflow children from this parent session stay addressable as retained children. `subagent({ action: "children.list" })` lists up to the last 10 with run ids, and a later workflow continues one with `runs.run(key, { resume: "<run-id>", task: "follow-up" })`. Inside `workflowScript`, awaiting that call waits for the revived child to finish and returns its completed output and new `runId`; top-level `{ action: "resume" }` remains detached. A follow-up loop can render each task with `await prompts.render(...)`. Assign each returned child result back to the loop variable because every resume can return a new retained `runId`; always resume the latest returned id. `resume` and `agent` are mutually exclusive, the revived child keeps its stored agent/model/tool contract, and `gate` is rejected on retained resume items.
|
|
76
76
|
|
|
77
77
|
### Async/background
|
|
78
78
|
|
package/src/agents/agents.ts
CHANGED
|
@@ -659,16 +659,25 @@ function findConfiguredProjectRoot(cwd: string): string | null {
|
|
|
659
659
|
const nearestRoot = candidates[0];
|
|
660
660
|
if (!nearestRoot) return null;
|
|
661
661
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
662
|
+
let policyRoot: string | undefined;
|
|
663
|
+
let policyRootIndex = -1;
|
|
664
|
+
for (const [index, candidate] of candidates.entries()) {
|
|
665
|
+
const mode = readProjectRootResolution(candidate);
|
|
666
|
+
if (mode === "nearest") return nearestRoot;
|
|
667
|
+
if (mode === "git-root") {
|
|
668
|
+
policyRoot = candidate;
|
|
669
|
+
policyRootIndex = index;
|
|
670
|
+
break;
|
|
671
|
+
}
|
|
669
672
|
}
|
|
673
|
+
if (!policyRoot) return nearestRoot;
|
|
670
674
|
|
|
671
|
-
|
|
675
|
+
const gitRoot = findNearestGitRoot(cwd);
|
|
676
|
+
const gitProjectRoot = gitRoot
|
|
677
|
+
? candidates.slice(policyRootIndex).find((candidate) => path.resolve(candidate) === path.resolve(gitRoot))
|
|
678
|
+
: undefined;
|
|
679
|
+
const configuredGitRoot = fs.existsSync(path.join(policyRoot, ".git")) ? policyRoot : undefined;
|
|
680
|
+
return gitProjectRoot ?? configuredGitRoot ?? nearestRoot;
|
|
672
681
|
}
|
|
673
682
|
|
|
674
683
|
function getUserAgentSettingsPath(): string {
|
|
@@ -83,12 +83,13 @@ export function parseFrontmatter(content: string): { frontmatter: Record<string,
|
|
|
83
83
|
let currentBlockLines: string[] | null = null;
|
|
84
84
|
let currentIndent: number | null = null;
|
|
85
85
|
let currentFolded = false;
|
|
86
|
+
let currentLiteral = false;
|
|
86
87
|
|
|
87
88
|
for (const line of lines) {
|
|
88
89
|
const indent = line.search(/\S|$/); // position of first non-whitespace char
|
|
89
90
|
const trimmed = line.trim();
|
|
90
91
|
|
|
91
|
-
if (currentKey !== null && currentBlockLines !== null && (indent > (currentIndent ?? 0) || (currentFolded && trimmed === ""))) {
|
|
92
|
+
if (currentKey !== null && currentBlockLines !== null && (indent > (currentIndent ?? 0) || ((currentFolded || currentLiteral) && trimmed === ""))) {
|
|
92
93
|
// This line is part of the current block value
|
|
93
94
|
currentBlockLines.push(line);
|
|
94
95
|
continue;
|
|
@@ -109,6 +110,7 @@ export function parseFrontmatter(content: string): { frontmatter: Record<string,
|
|
|
109
110
|
currentBlockLines = null;
|
|
110
111
|
currentIndent = null;
|
|
111
112
|
currentFolded = false;
|
|
113
|
+
currentLiteral = false;
|
|
112
114
|
}
|
|
113
115
|
|
|
114
116
|
const match = line.match(/^([\w-]+):\s*(.*)$/);
|
|
@@ -119,13 +121,15 @@ export function parseFrontmatter(content: string): { frontmatter: Record<string,
|
|
|
119
121
|
const isQuoted = (rawValue.startsWith('"') && rawValue.endsWith('"')) || (rawValue.startsWith("'") && rawValue.endsWith("'"));
|
|
120
122
|
const value = isQuoted ? rawValue.slice(1, -1) : rawValue;
|
|
121
123
|
const isFolded = !isQuoted && (rawValue === ">" || rawValue === ">-");
|
|
124
|
+
const isLiteral = !isQuoted && (rawValue === "|" || rawValue === "|-");
|
|
122
125
|
|
|
123
|
-
if (value === "" || isFolded) {
|
|
124
|
-
// Key with empty value or
|
|
126
|
+
if (value === "" || isFolded || isLiteral) {
|
|
127
|
+
// Key with empty value or block scalar indicator — defer storing until we see indent
|
|
125
128
|
currentKey = key;
|
|
126
129
|
currentBlockLines = [];
|
|
127
130
|
currentIndent = indent;
|
|
128
131
|
currentFolded = isFolded;
|
|
132
|
+
currentLiteral = isLiteral;
|
|
129
133
|
} else {
|
|
130
134
|
// Simple key: value
|
|
131
135
|
frontmatter[key] = value;
|
package/src/agents/skills.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { execSync } from "node:child_process";
|
|
|
6
6
|
import * as fs from "node:fs";
|
|
7
7
|
import * as os from "node:os";
|
|
8
8
|
import * as path from "node:path";
|
|
9
|
+
import { parseFrontmatter } from "./frontmatter.ts";
|
|
9
10
|
import { getAgentDir, getProjectConfigDir } from "../shared/utils.ts";
|
|
10
11
|
|
|
11
12
|
export type SkillSource =
|
|
@@ -395,15 +396,7 @@ function chooseHigherPrioritySkill(existing: CachedSkillEntry | undefined, candi
|
|
|
395
396
|
}
|
|
396
397
|
|
|
397
398
|
function parseSkillDescription(content: string): string | undefined {
|
|
398
|
-
|
|
399
|
-
if (!normalized.startsWith("---")) return undefined;
|
|
400
|
-
|
|
401
|
-
const endIndex = normalized.indexOf("\n---", 3);
|
|
402
|
-
if (endIndex === -1) return undefined;
|
|
403
|
-
|
|
404
|
-
const frontmatter = normalized.slice(3, endIndex).trim();
|
|
405
|
-
const match = frontmatter.match(/^description:\s*(.+)$/m);
|
|
406
|
-
return match?.[1]?.trim().replace(/^['\"]|['\"]$/g, "");
|
|
399
|
+
return parseFrontmatter(content).frontmatter.description;
|
|
407
400
|
}
|
|
408
401
|
|
|
409
402
|
function maybeReadSkillDescription(filePath: string): string | undefined {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public project-owned Herdr pane lifecycle API for other Pi extensions.
|
|
3
|
+
*
|
|
4
|
+
* This is the supported extension-to-extension surface. Callers must not
|
|
5
|
+
* import `src/inspectors/herdr/*` directly.
|
|
6
|
+
*/
|
|
7
|
+
export {
|
|
8
|
+
PROJECT_PANES_API_VERSION,
|
|
9
|
+
PROJECT_PANE_TRUST_STATUS,
|
|
10
|
+
createProjectPaneManager,
|
|
11
|
+
openProjectPane,
|
|
12
|
+
getProjectPaneStatus,
|
|
13
|
+
closeProjectPane,
|
|
14
|
+
readProjectPaneBinding,
|
|
15
|
+
projectPaneBindingPath,
|
|
16
|
+
type ProjectPaneManager,
|
|
17
|
+
type ProjectPaneManagerOptions,
|
|
18
|
+
type ProjectPaneCommandClient,
|
|
19
|
+
type OpenProjectPaneOptions,
|
|
20
|
+
type GetProjectPaneStatusOptions,
|
|
21
|
+
type CloseProjectPaneOptions,
|
|
22
|
+
type OpenProjectPaneData,
|
|
23
|
+
type ProjectPaneStatusData,
|
|
24
|
+
type CloseProjectPaneData,
|
|
25
|
+
type ProjectPaneRuntime,
|
|
26
|
+
type ProjectPaneError,
|
|
27
|
+
type ProjectPaneErrorCode,
|
|
28
|
+
type ProjectPaneResult,
|
|
29
|
+
type HerdrProjectPaneBinding,
|
|
30
|
+
} from "../inspectors/herdr/project-panes.ts";
|
package/src/extension/config.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import type
|
|
4
|
+
import { FLEET_KEYBINDING_ACTIONS, type ArtifactDirPreference, type ExtensionConfig } from "../shared/types.ts";
|
|
5
5
|
import { validateMissionStoreConfig } from "../missions/store.ts";
|
|
6
6
|
import { validateAuthorityPolicy } from "../policy/authority.ts";
|
|
7
7
|
import { getAgentDir } from "../shared/utils.ts";
|
|
8
8
|
import { validatePermissionConfig } from "../runs/shared/permissions.ts";
|
|
9
9
|
|
|
10
10
|
const ARTIFACT_DIR_PREFERENCES = new Set<ArtifactDirPreference>(["project", "session", "temp"]);
|
|
11
|
+
const FLEET_KEYBINDING_ACTION_SET = new Set<string>(FLEET_KEYBINDING_ACTIONS);
|
|
11
12
|
|
|
12
13
|
export function resolveScheduledStoreRoot(value: string): string {
|
|
13
14
|
const expanded = value.startsWith("~/") ? path.join(os.homedir(), value.slice(2)) : value;
|
|
@@ -24,6 +25,18 @@ function validateScheduledRunsConfig(value: unknown): void {
|
|
|
24
25
|
resolveScheduledStoreRoot(storeRoot);
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
function validateFleetKeybindingsConfig(value: unknown): void {
|
|
29
|
+
if (value === undefined) return;
|
|
30
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("config.fleetKeybindings must be a JSON object");
|
|
31
|
+
for (const [action, bindings] of Object.entries(value)) {
|
|
32
|
+
if (!FLEET_KEYBINDING_ACTION_SET.has(action)) throw new Error(`config.fleetKeybindings.${action} is not a supported Fleet action`);
|
|
33
|
+
if (!Array.isArray(bindings) || bindings.length === 0) throw new Error(`config.fleetKeybindings.${action} must be a non-empty array of strings`);
|
|
34
|
+
for (const binding of bindings) {
|
|
35
|
+
if (typeof binding !== "string" || !binding.trim()) throw new Error(`config.fleetKeybindings.${action} entries must be non-empty strings`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
27
40
|
function validateConfig(config: Record<string, unknown>): void {
|
|
28
41
|
if (config.artifactDir !== undefined && !ARTIFACT_DIR_PREFERENCES.has(config.artifactDir as ArtifactDirPreference)) {
|
|
29
42
|
throw new Error(`config.artifactDir must be "project", "session", or "temp"`);
|
|
@@ -32,6 +45,7 @@ function validateConfig(config: Record<string, unknown>): void {
|
|
|
32
45
|
validateAuthorityPolicy(config.authorityPolicy);
|
|
33
46
|
validatePermissionConfig(config.permissions);
|
|
34
47
|
validateScheduledRunsConfig(config.scheduledRuns);
|
|
48
|
+
validateFleetKeybindingsConfig(config.fleetKeybindings);
|
|
35
49
|
}
|
|
36
50
|
|
|
37
51
|
export function getConfigPath(): string {
|
package/src/extension/index.ts
CHANGED
|
@@ -37,7 +37,7 @@ import { registerPromptTemplateDelegationBridge } from "../slash/prompt-template
|
|
|
37
37
|
import { registerMainWatchdog } from "../watchdog/register-main.ts";
|
|
38
38
|
import { registerSlashSubagentBridge } from "../slash/slash-bridge.ts";
|
|
39
39
|
import { createNativeSupervisorChannel } from "../intercom/native-supervisor-channel.ts";
|
|
40
|
-
import { registerHerdrStatusBridge } from "../integrations/herdr-status.ts";
|
|
40
|
+
import { registerHerdrStatusBridge, type HerdrStatusRun } from "../integrations/herdr-status.ts";
|
|
41
41
|
import { registerSubagentRpcBridge } from "./rpc.ts";
|
|
42
42
|
import { clearSlashSnapshots, getSlashRenderableSnapshot, resolveSlashMessageDetails, restoreSlashFinalSnapshots, type SlashMessageDetails } from "../slash/slash-live-state.ts";
|
|
43
43
|
import { inspectSubagentStatus } from "../runs/background/run-status.ts";
|
|
@@ -53,7 +53,7 @@ import { formatDuration, shortenPath } from "../shared/formatters.ts";
|
|
|
53
53
|
import { loadConfig, resolveAsyncByDefault, resolveScheduledStoreRoot } from "./config.ts";
|
|
54
54
|
import { buildSubagentToolDescription } from "./tool-description.ts";
|
|
55
55
|
import { collectGoalContinuationNotices } from "../missions/goal-driver.ts";
|
|
56
|
-
import {
|
|
56
|
+
import { restoreForegroundRunHistory } from "../runs/foreground/foreground-history.ts";
|
|
57
57
|
import { resolveMissionStoreLocation } from "../missions/store.ts";
|
|
58
58
|
import { listRetainedChildren } from "../runs/background/retained-children.ts";
|
|
59
59
|
import {
|
|
@@ -309,6 +309,36 @@ class SubagentControlNoticeComponent implements Component {
|
|
|
309
309
|
}
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
+
export function projectActiveHerdrRuns(state: SubagentState): HerdrStatusRun[] {
|
|
313
|
+
const active = (status: string) => status === "queued" || status === "running";
|
|
314
|
+
const foregroundChildrenByWorkflow = new Map<string, Array<{ agent: string; needsAttention: boolean }>>();
|
|
315
|
+
for (const control of state.foregroundControls.values()) {
|
|
316
|
+
if (!control.parentWorkflowRunId) continue;
|
|
317
|
+
const children = control.activeChildren?.size
|
|
318
|
+
? [...control.activeChildren.values()].map((child) => ({
|
|
319
|
+
agent: child.agent,
|
|
320
|
+
needsAttention: child.currentActivityState === "needs_attention",
|
|
321
|
+
}))
|
|
322
|
+
: control.currentAgent
|
|
323
|
+
? [{ agent: control.currentAgent, needsAttention: control.currentActivityState === "needs_attention" }]
|
|
324
|
+
: [];
|
|
325
|
+
if (children.length === 0) continue;
|
|
326
|
+
const existing = foregroundChildrenByWorkflow.get(control.parentWorkflowRunId) ?? [];
|
|
327
|
+
existing.push(...children);
|
|
328
|
+
foregroundChildrenByWorkflow.set(control.parentWorkflowRunId, existing);
|
|
329
|
+
}
|
|
330
|
+
return [...state.asyncJobs.values()]
|
|
331
|
+
.filter((job) => active(job.status))
|
|
332
|
+
.map((job) => {
|
|
333
|
+
const children = job.mode === "workflow" ? foregroundChildrenByWorkflow.get(job.asyncId) : undefined;
|
|
334
|
+
return {
|
|
335
|
+
id: job.asyncId,
|
|
336
|
+
agents: children?.length ? children.map((child) => child.agent) : job.agents,
|
|
337
|
+
needsAttention: job.activityState === "needs_attention" || children?.some((child) => child.needsAttention),
|
|
338
|
+
};
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
312
342
|
export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
313
343
|
if (process.env[SUBAGENT_CHILD_ENV] === "1") {
|
|
314
344
|
return;
|
|
@@ -378,7 +408,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
378
408
|
? new SubagentFleetStatus(state, async (itemKey) => {
|
|
379
409
|
const ctx = state.lastUiContext;
|
|
380
410
|
if (!ctx?.hasUI) return;
|
|
381
|
-
await openSubagentFleet(ctx, state, { initialKey: itemKey, asyncDirRoot: DIRS.async, resultsDir: DIRS.results });
|
|
411
|
+
await openSubagentFleet(ctx, state, { initialKey: itemKey, asyncDirRoot: DIRS.async, resultsDir: DIRS.results, fleetKeybindings: config.fleetKeybindings });
|
|
382
412
|
}, { placement: fleetViewPlacement })
|
|
383
413
|
: undefined;
|
|
384
414
|
let executorScheduled: ((id: string, params: SubagentParamsLike, signal: AbortSignal, ctx: ExtensionContext) => Promise<AgentToolResult<Details>>) | undefined;
|
|
@@ -603,7 +633,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
603
633
|
}
|
|
604
634
|
});
|
|
605
635
|
|
|
606
|
-
registerSlashCommands(pi, state);
|
|
636
|
+
registerSlashCommands(pi, state, { fleetKeybindings: config.fleetKeybindings });
|
|
607
637
|
|
|
608
638
|
const eventUnsubscribeStoreKey = "__piSubagentEventUnsubscribes";
|
|
609
639
|
const controlNoticeSeenStoreKey = "__piSubagentVisibleControlNotices";
|
|
@@ -621,13 +651,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
621
651
|
const existingVisibleControlNotices = globalStore[controlNoticeSeenStoreKey];
|
|
622
652
|
const visibleControlNotices = existingVisibleControlNotices instanceof Set ? existingVisibleControlNotices as Set<string> : new Set<string>();
|
|
623
653
|
globalStore[controlNoticeSeenStoreKey] = visibleControlNotices;
|
|
624
|
-
const activeHerdrRuns = () =>
|
|
625
|
-
.filter((job) => job.status === "queued" || job.status === "running")
|
|
626
|
-
.map((job) => ({
|
|
627
|
-
id: job.asyncId,
|
|
628
|
-
agents: job.agents,
|
|
629
|
-
needsAttention: job.activityState === "needs_attention",
|
|
630
|
-
}));
|
|
654
|
+
const activeHerdrRuns = () => projectActiveHerdrRuns(state);
|
|
631
655
|
const herdrStatusBridge = registerHerdrStatusBridge({
|
|
632
656
|
events: pi.events,
|
|
633
657
|
getRuns: activeHerdrRuns,
|
|
@@ -653,11 +677,6 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
653
677
|
const asyncCompleteHandler = (payload: unknown) => {
|
|
654
678
|
handleComplete(payload);
|
|
655
679
|
scheduledRunManager.handleAsyncCompletion(payload);
|
|
656
|
-
try {
|
|
657
|
-
syncMissionFromAsyncCompletion(payload);
|
|
658
|
-
} catch (error) {
|
|
659
|
-
console.error("Failed to update mission from async completion:", error);
|
|
660
|
-
}
|
|
661
680
|
fleetStatus?.refresh();
|
|
662
681
|
};
|
|
663
682
|
const eventUnsubscribes = [
|
|
@@ -722,6 +741,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
722
741
|
state.foregroundControls.clear();
|
|
723
742
|
state.lastForegroundControlId = null;
|
|
724
743
|
resetJobs(ctx);
|
|
744
|
+
restoreForegroundRunHistory(state, { resultsDir: DIRS.results });
|
|
725
745
|
restoreActiveJobs(ctx);
|
|
726
746
|
scheduledRunManager.bindSession(ctx);
|
|
727
747
|
restoreSlashFinalSnapshots(ctx.sessionManager.getEntries());
|
package/src/extension/schemas.ts
CHANGED
|
@@ -263,7 +263,7 @@ const SubagentParamsSchema = Type.Object({
|
|
|
263
263
|
})),
|
|
264
264
|
name: Type.Optional(Type.String({ description: "Human-readable name for action='schedule.create'." })),
|
|
265
265
|
id: Type.Optional(Type.String({
|
|
266
|
-
description: "Run id or prefix for status, interrupt, stop, resume, steer, append-step, approve-checkpoint, reject-checkpoint,
|
|
266
|
+
description: "Run id or prefix for status, interrupt, stop, resume, steer, append-step, approve-checkpoint, reject-checkpoint, mission.attach-run, or the decision id for mission.resolve-decision."
|
|
267
267
|
})),
|
|
268
268
|
runId: Type.Optional(Type.String({
|
|
269
269
|
description: "Target run ID for interrupt, stop, resume, steer, append-step, approve-checkpoint, reject-checkpoint, or mission.attach-run. Prefer id for new calls."
|
|
@@ -278,6 +278,7 @@ const SubagentParamsSchema = Type.Object({
|
|
|
278
278
|
description: "Optional status view. Use view='fleet' for a read-only active foreground/async fleet surface, or view='transcript' with id/dir (and optional index) to tail a run transcript.",
|
|
279
279
|
})),
|
|
280
280
|
lines: Type.Optional(Type.Integer({ minimum: 1, maximum: 500, description: "Maximum transcript lines for action='status', view='transcript'. Defaults to 80." })),
|
|
281
|
+
topic: Type.Optional(Type.String()),
|
|
281
282
|
message: Type.Optional(Type.String({ description: "Follow-up message for resume, live guidance for steer, or optional startup prompt for project.open." })),
|
|
282
283
|
mode: Type.Optional(Type.String({ enum: ["steer", "follow_up", "auto"], description: "Delivery mode for action='steer'. steer interrupts at the next safe point (default), follow_up waits for the next turn boundary, and auto follows up mid-turn but delivers immediately between turns." })),
|
|
283
284
|
steeringRecovery: Type.Optional(Type.Boolean({ description: "For action='steer', allow pause-and-revive recovery after a missed acknowledgment. Defaults true for direct tool calls in steer mode; extension RPC steering forces false so callers retain exact child ownership." })),
|
|
@@ -314,7 +315,7 @@ const SubagentParamsSchema = Type.Object({
|
|
|
314
315
|
],
|
|
315
316
|
description: "Agent/chain config for create/update. Object or JSON string; presence of steps creates a chain."
|
|
316
317
|
})),
|
|
317
|
-
workflowScript: Type.Optional(Type.String({ minLength: 1, description: "Trusted inline JavaScript statement body. Starts async by default; pass async:false for a small foreground run. Use explicit return for output. Use await runs.run(key, {agent, task, worktree?, gate?}) or runs.run(key, {resume, task}), runs.all([...]), runs.status(id), runs.ref(s), emit(value), console, and return. Mission workflows also have async state.get(key) and state.set(key, JSONValue).
|
|
318
|
+
workflowScript: Type.Optional(Type.String({ minLength: 1, description: "Trusted inline JavaScript statement body. Starts async by default; pass async:false for a small foreground run. Use explicit return for output. Use await prompts.render(ref, vars?) for task text. Use await runs.run(key, {agent, task, worktree?, gate?}) or runs.run(key, {resume, task}), runs.all([...]), runs.status(id), runs.ref(s), emit(value), console, and return. Mission workflows also have async state.get(key) and state.set(key, JSONValue). Compose sequential and parallel phases dynamically. Set worktree:true at workflow or child level for a separate managed worktree; child fields override workflow defaults. gate is one host-run command and cannot be combined with acceptance. runs.run accepts one child only. No filesystem, shell, Pi tools, or host globals." })),
|
|
318
319
|
chatProgress: Type.Optional(Type.String({ enum: ["auto", "off", "live-card"], description: "WorkflowScript chat progress projection. auto shows a live in-chat card only for watched foreground workflows in the same Git repository; it is off otherwise." })),
|
|
319
320
|
worktree: Type.Optional(Type.Boolean({ description: "Managed child isolation. true gives each workflow child a separate git worktree; an individual runs.run/runs.all item can override a workflow default with worktree:false." })),
|
|
320
321
|
step: Type.Optional(Type.Unsafe({ ...ChainItem, description: "One chain step for action='append-step' only. Not an execution mode." })),
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
export const SUBAGENT_GUIDE_TOPICS = [
|
|
6
|
+
"overview",
|
|
7
|
+
"workflows",
|
|
8
|
+
"agents",
|
|
9
|
+
"missions",
|
|
10
|
+
"observability",
|
|
11
|
+
"tool-reference",
|
|
12
|
+
"configuration",
|
|
13
|
+
"models",
|
|
14
|
+
"watchdog",
|
|
15
|
+
"extension-api",
|
|
16
|
+
] as const;
|
|
17
|
+
|
|
18
|
+
export type SubagentGuideTopic = (typeof SUBAGENT_GUIDE_TOPICS)[number];
|
|
19
|
+
|
|
20
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
21
|
+
|
|
22
|
+
function isGuideTopic(value: string): value is SubagentGuideTopic {
|
|
23
|
+
return (SUBAGENT_GUIDE_TOPICS as readonly string[]).includes(value);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function readSubagentGuide(topic = "overview", root = packageRoot): string {
|
|
27
|
+
if (!isGuideTopic(topic)) {
|
|
28
|
+
return `Unknown subagents guide topic '${topic}'. Valid topics: ${SUBAGENT_GUIDE_TOPICS.join(", ")}. No files were changed.`;
|
|
29
|
+
}
|
|
30
|
+
const filePath = topic === "overview"
|
|
31
|
+
? path.join(root, "README.md")
|
|
32
|
+
: path.join(root, "docs", `${topic}.md`);
|
|
33
|
+
try {
|
|
34
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
35
|
+
} catch (error) {
|
|
36
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
37
|
+
throw new Error(`Failed to read packaged subagents guide '${topic}': ${message}`, { cause: error instanceof Error ? error : undefined });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -18,14 +18,14 @@ export const FULL_SUBAGENT_TOOL_DESCRIPTION = `Run subagents only through { work
|
|
|
18
18
|
|
|
19
19
|
EXECUTION:
|
|
20
20
|
• Before executing, use { action: "list" } and run only executable/non-disabled configured agents.
|
|
21
|
-
• WORKFLOW SCRIPT: { workflowScript: "return runs.run('main', {agent:'worker', task:'...'})" }. Every execution is a workflow. Use stable-key runs.run for one child and runs.all for parallel children; ordinary JavaScript provides sequence, branching, filtering, retries, and aggregation. workflowScript is an ordinary JavaScript statement body, so use an explicit return for a useful result. For task text with Markdown fences or shell blocks, build quoted lines instead of nesting raw template literals: \`const task=["Run:","\`\`\`bash","npm test","\`\`\`"].join("\\n")\`. Scripts start asynchronously by default; pass async:false only for a small foreground run. Same-repo foreground workflows default to a live in-chat card; set chatProgress to auto, off, or live-card to control that projection. Workflow-level child controls default onto each runs.run launch, and explicit child fields override them. Use {action:"children.list"} to list up to 10 completed retained children from this parent session, then continue one with runs.run(key, {resume:"run-id", task:"follow-up"}); resume and agent are mutually exclusive,
|
|
21
|
+
• WORKFLOW SCRIPT: { workflowScript: "return runs.run('main', {agent:'worker', task:'...'})" }. Every execution is a workflow. Use stable-key runs.run for one child and runs.all for parallel children; ordinary JavaScript provides sequence, branching, filtering, retries, and aggregation. workflowScript is an ordinary JavaScript statement body, so use an explicit return for a useful result. For task text with Markdown fences or shell blocks, build quoted lines instead of nesting raw template literals: \`const task=["Run:","\`\`\`bash","npm test","\`\`\`"].join("\\n")\`. Scripts start asynchronously by default; pass async:false only for a small foreground run. Same-repo foreground workflows default to a live in-chat card; set chatProgress to auto, off, or live-card to control that projection. Workflow-level child controls default onto each runs.run launch, and explicit child fields override them. Use await prompts.render("package:name" | "user:name" | "project:name", vars?) for reusable plain task text, then pass the result explicitly as task. Use {action:"children.list"} to list up to 10 completed retained children from this parent session, then continue one with runs.run(key, {resume:"run-id", task:"follow-up"}); resume and agent are mutually exclusive, resume keeps the stored agent/model/tool contract, workflow resumes wait for completed output, and loops must continue from each latest returned runId. For repository mutation lanes, set worktree:true on the workflow or individual runs.run/runs.all item for managed isolation; each parallel child gets a separate worktree and handoff artifact. A workflow usageBudget is enforced once across the workflow. Available globals are runs.run, runs.all, runs.status, runs.ref/refs, prompts.render, emit, console, and standard JavaScript only. Workflows get async state.get(key) and state.set(key, JSONValue) through their automatic or explicit mission; mission:false workflows do not have a state global. Scripts cannot access filesystem, shell, arbitrary Pi tools, or host globals.
|
|
22
22
|
• Sequential example: { workflowScript: "const a = await runs.run('analyze', {agent:'agent-a', task:'Analyze the request'}); return (await runs.run('plan', {agent:'agent-b', task:'Plan from: '+a.output})).output" }
|
|
23
23
|
• Parallel example: { workflowScript: "const [a,b] = await runs.all([{key:'correctness',agent:'agent-a',task:'Review correctness'},{key:'tests',agent:'agent-b',task:'Review tests'}]); return {correctness:a.output,tests:b.output}" }
|
|
24
24
|
• Optional context is "fresh" or "fork". timeoutMs/maxRuntimeMs apply to foreground and async workflows; foreground workflows default to 30 minutes and async workflows have no default timeout. Omit acceptance for reviewer/read-only calls; evidence levels end at verified, and acceptance.review.required requests independent writer review.
|
|
25
25
|
• Durable mission attachment is automatic by default. Use missionId to attach an existing mission, mission:{...} to override auto-create, or mission:false for ephemeral work.
|
|
26
26
|
|
|
27
27
|
MANAGEMENT / CONTROL (use action; omit execution fields):
|
|
28
|
-
• list, get, models, children.list, create, update, delete, eject, disable, enable, reset, doctor, grant-spawn-budget, worktree.discard, refine/refine.show/refine.rollback, mission.create/list/show/update/attach-run/close, inspector.open/status/close, project.open/status/close, and watchdog actions remain available.
|
|
28
|
+
• list, get, models, guide, children.list, create, update, delete, eject, disable, enable, reset, doctor, grant-spawn-budget, worktree.discard, refine/refine.show/refine.rollback, mission.create/list/show/update/resolve-decision/attach-run/close, inspector.open/status/close, project.open/status/close, and watchdog actions remain available. Use {action:"guide", topic:"overview"} for packaged current-version help; topics are overview, workflows, agents, missions, observability, tool-reference, configuration, models, watchdog, and extension-api.
|
|
29
29
|
• status, interrupt, stop, resume, and steer manage live or persisted runs. Use status view:"fleet" for an overview or view:"transcript" with id and optional index to tail output.
|
|
30
30
|
• { action: "append-step", id: "...", step: {agent:"agent-c", task:"Use {previous}"} } appends one step to an already-running durable legacy chain. step is control-only, not an execution mode.
|
|
31
31
|
• approve-checkpoint and reject-checkpoint decide a paused durable legacy chain checkpoint.
|
|
@@ -37,12 +37,12 @@ export const COMPACT_SUBAGENT_TOOL_DESCRIPTION = `Run subagents only through { w
|
|
|
37
37
|
|
|
38
38
|
EXECUTE:
|
|
39
39
|
• Call { action:"list" } first and use only executable/non-disabled agents.
|
|
40
|
-
• SCRIPT {workflowScript:"return runs.run('main', {agent:'worker', task:'...'})"}. Use stable-key runs.run for one child and runs.all for parallel work. Use {action:"children.list"} for the last 10 retained children in this parent session, then runs.run(key,{resume:"run-id",task:"follow-up"}) to continue one with its stored contract. Workflows get async state.get/state.set through their automatic or explicit mission; mission:false does not. Scripts are ordinary JavaScript statement bodies; use explicit return for a useful result. For task text with Markdown fences or shell blocks, build quoted lines instead of nesting raw template literals: \`const task=["Run:","\`\`\`bash","npm test","\`\`\`"].join("\\n")\`. Use JavaScript for sequence, branching, retries, and aggregation. For repository mutation lanes, use worktree:true on the workflow or runs.run/runs.all item for managed isolation. Scripts start async by default; async:false is the foreground escape hatch and auto-enables a same-repo live chat card unless chatProgress is off.
|
|
40
|
+
• SCRIPT {workflowScript:"return runs.run('main', {agent:'worker', task:'...'})"}. Use stable-key runs.run for one child and runs.all for parallel work. Use await prompts.render("package:name" | "user:name" | "project:name", vars?) for reusable task text and pass it explicitly to runs.run. Use {action:"children.list"} for the last 10 retained children in this parent session, then runs.run(key,{resume:"run-id",task:"follow-up"}) to continue one with its stored contract; workflow resumes wait for completion and loops continue from the latest returned runId. Workflows get async state.get/state.set through their automatic or explicit mission; mission:false does not. Scripts are ordinary JavaScript statement bodies; use explicit return for a useful result. For task text with Markdown fences or shell blocks, build quoted lines instead of nesting raw template literals: \`const task=["Run:","\`\`\`bash","npm test","\`\`\`"].join("\\n")\`. Use JavaScript for sequence, branching, retries, and aggregation. For repository mutation lanes, use worktree:true on the workflow or runs.run/runs.all item for managed isolation. Scripts start async by default; async:false is the foreground escape hatch and auto-enables a same-repo live chat card unless chatProgress is off.
|
|
41
41
|
• Example: {workflowScript:"const [a,b]=await runs.all([{key:'a',agent:'agent-a',task:'Implement A',worktree:true},{key:'b',agent:'agent-b',task:'Implement B',worktree:true}]); return [a.output,b.output]"}
|
|
42
42
|
• context can be fresh or fork. timeoutMs/maxRuntimeMs apply to foreground and async workflows; foreground workflows default to 30 minutes and async workflows have no default timeout. Omit acceptance for reviewer/read-only calls.
|
|
43
43
|
|
|
44
44
|
MANAGE / CONTROL:
|
|
45
|
-
• Use action without execution fields for list/get/models/authoring, refine/refine.show/refine.rollback, mission, watchdog, status, interrupt, stop, resume, steer, script-only scheduling, diagnostics, and other management actions.
|
|
45
|
+
• Use action without execution fields for list/get/models/guide/authoring, refine/refine.show/refine.rollback, mission, watchdog, status, interrupt, stop, resume, steer, script-only scheduling, diagnostics, and other management actions. guide reads shipped current-version docs by topic.
|
|
46
46
|
• append-step uses step:{...} only for an already-running durable legacy chain; step is not an execution mode.
|
|
47
47
|
|
|
48
48
|
ASYNC / SAFETY:
|