pi-subagents 0.45.2 → 0.47.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 +47 -0
- package/README.md +2 -0
- package/docs/agents.md +342 -0
- package/docs/configuration.md +328 -0
- package/docs/extension-api.md +308 -0
- package/docs/missions.md +119 -0
- package/docs/models.md +192 -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 +6 -6
- package/skills/pi-subagents/references/management-authoring-rpc.md +1 -1
- 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 +18 -1
- package/src/extension/fanout-child.ts +5 -4
- package/src/extension/index.ts +66 -19
- package/src/extension/rpc.ts +3 -6
- package/src/extension/schemas.ts +28 -7
- package/src/extension/subagent-guide.ts +39 -0
- package/src/extension/tool-description.ts +30 -12
- package/src/inspectors/herdr/project-panes.ts +459 -63
- package/src/missions/actions.ts +25 -2
- package/src/missions/lifecycle.ts +21 -2
- package/src/missions/store.ts +79 -2
- package/src/missions/types.ts +33 -0
- package/src/missions/workflow-state.ts +19 -13
- package/src/runs/background/async-execution.ts +17 -6
- package/src/runs/background/async-job-tracker.ts +15 -0
- package/src/runs/background/async-resume.ts +19 -3
- package/src/runs/background/async-status.ts +6 -1
- package/src/runs/background/completion-replay.ts +267 -0
- package/src/runs/background/control-channel.ts +36 -0
- package/src/runs/background/result-watcher.ts +28 -6
- package/src/runs/background/scheduled-runs.ts +2 -1
- package/src/runs/background/stale-run-reconciler.ts +2 -21
- package/src/runs/background/subagent-runner.ts +47 -6
- package/src/runs/background/wait-completions.ts +39 -5
- package/src/runs/background/wait-subscriptions.ts +18 -3
- package/src/runs/foreground/async-steering-action.ts +1 -1
- package/src/runs/foreground/chain-execution.ts +3 -0
- package/src/runs/foreground/execution.ts +7 -0
- package/src/runs/foreground/foreground-history.ts +137 -0
- package/src/runs/foreground/subagent-executor.ts +403 -54
- package/src/runs/foreground/workflow-foreground-steering.ts +187 -0
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/model-fallback.ts +8 -4
- package/src/runs/shared/model-scope.ts +12 -2
- package/src/runs/shared/parallel-utils.ts +1 -0
- package/src/runs/shared/worktree.ts +3 -2
- package/src/shared/artifacts.ts +14 -14
- package/src/shared/display-text.ts +100 -0
- package/src/shared/fork-context.ts +13 -0
- package/src/shared/formatters.ts +4 -6
- package/src/shared/prompt-resources.ts +51 -0
- package/src/shared/settings.ts +15 -2
- package/src/shared/types.ts +41 -2
- package/src/shared/utf8.ts +11 -0
- package/src/shared/utils.ts +43 -33
- package/src/slash/prompt-workflows.ts +2 -15
- package/src/slash/slash-commands.ts +22 -2
- package/src/tui/fleet-status.ts +22 -12
- package/src/tui/fleet.ts +135 -25
- package/src/tui/render.ts +150 -33
- package/src/watchdog/change-signature.ts +4 -3
- package/src/workflows/scripted-workflow.ts +167 -10
package/src/extension/rpc.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
SUBAGENT_PROCESS_TERMINAL_EVENT,
|
|
17
17
|
SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
18
18
|
} from "../shared/types.ts";
|
|
19
|
+
import { sanitizeDisplayText, truncateDisplayText } from "../shared/display-text.ts";
|
|
19
20
|
import { readStatus } from "../shared/utils.ts";
|
|
20
21
|
import { SubagentParams } from "./schemas.ts";
|
|
21
22
|
import { formatWorkflowJsonPreview } from "../workflows/scripted-workflow.ts";
|
|
@@ -101,12 +102,8 @@ const MAX_METADATA_LENGTH = 128;
|
|
|
101
102
|
|
|
102
103
|
function displayText(value: unknown, maxLength: number): string | undefined {
|
|
103
104
|
if (typeof value !== "string") return undefined;
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const normalized = value.slice(0, 4_096)
|
|
107
|
-
.replace(/\x1b\[[0-?]*[ -/]*[@-~]|\x9b[0-?]*[ -/]*[@-~]|\x1b][\s\S]*?(?:\x07|\x1b\\)|\x1b[PX^_][\s\S]*?\x1b\\|[\u0000-\u001f\u007f-\u009f]/g, " ")
|
|
108
|
-
.replace(/\s+/g, " ").trim();
|
|
109
|
-
return normalized ? normalized.slice(0, maxLength) : undefined;
|
|
105
|
+
const normalized = sanitizeDisplayText(value.slice(0, 4_096));
|
|
106
|
+
return normalized ? truncateDisplayText(normalized, maxLength) : undefined;
|
|
110
107
|
}
|
|
111
108
|
|
|
112
109
|
function publicTokens(value: unknown): { input: number; output: number; total: number } {
|
package/src/extension/schemas.ts
CHANGED
|
@@ -254,7 +254,7 @@ const ControlOverrides = Type.Object({
|
|
|
254
254
|
})),
|
|
255
255
|
});
|
|
256
256
|
|
|
257
|
-
const
|
|
257
|
+
const SubagentParamProperties = {
|
|
258
258
|
agent: Type.Optional(Type.String({ description: "Agent target for management actions such as get, update, delete, and models." })),
|
|
259
259
|
resume: Type.Optional(Type.String({ description: "Retained child run id for a workflowScript runs.run/runs.all item. Mutually exclusive with agent; task supplies the follow-up." })),
|
|
260
260
|
// Management action (when present, tool operates in management mode)
|
|
@@ -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." })),
|
|
@@ -295,7 +296,7 @@ const SubagentParamsSchema = Type.Object({
|
|
|
295
296
|
overlap: Type.Optional(Type.String({ enum: ["skip"], description: "Overlap policy. This slice supports skip only." })),
|
|
296
297
|
catchUp: Type.Optional(Type.String({ enum: ["none", "latest"], description: "Missed occurrence policy for recurring schedules. Defaults to latest." })),
|
|
297
298
|
missionId: Type.Optional(Type.String({ description: "Mission id." })),
|
|
298
|
-
mission: Type.Optional(Type.Unsafe({ ...MissionLaunchOverride, description: "Mission object, or false for no mission.
|
|
299
|
+
mission: Type.Optional(Type.Unsafe({ ...MissionLaunchOverride, description: "Mission object, or false for no mission. Set exactly one non-empty title or summary; objective and labels are optional. goal may only be true and then requires budget.tokens." })),
|
|
299
300
|
missionUpdate: Type.Optional(Type.Unsafe({ ...MissionUpdateOverride, description: "Mission update: objective, goal false or {paused:boolean}, budget, summary, labels, decisions, artifacts, or delivery receipts." })),
|
|
300
301
|
missionStatus: Type.Optional(Type.String({ description: "Mission status." })),
|
|
301
302
|
missionScope: Type.Optional(Type.String({ description: "Mission list scope: project (default) or global pointer index." })),
|
|
@@ -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." })),
|
|
@@ -323,8 +324,8 @@ const SubagentParamsSchema = Type.Object({
|
|
|
323
324
|
description: "'fresh' or 'fork' to branch from parent session. Explicit context overrides every child in the invocation. If omitted, each requested agent uses its own defaultContext; agents without defaultContext: 'fork' run fresh.",
|
|
324
325
|
})),
|
|
325
326
|
async: Type.Optional(Type.Boolean({ description: "Run in background (default: false, or per config)" })),
|
|
326
|
-
timeoutMs: Type.Optional(Type.Integer({ minimum: 1, description: "
|
|
327
|
-
maxRuntimeMs: Type.Optional(Type.Integer({ minimum: 1, description: "Alias timeoutMs
|
|
327
|
+
timeoutMs: Type.Optional(Type.Integer({ minimum: 1, description: "Run timeout. Foreground runs and async children default to 30m; async composites have no default parent deadline. Alias maxRuntimeMs." })),
|
|
328
|
+
maxRuntimeMs: Type.Optional(Type.Integer({ minimum: 1, description: "Alias timeoutMs. Foreground runs and async children default to 30m; async composites have no default parent deadline." })),
|
|
328
329
|
turnBudget: Type.Optional(TurnBudgetOverride),
|
|
329
330
|
toolBudget: Type.Optional(ToolBudgetOverride),
|
|
330
331
|
usageBudget: Type.Optional(UsageBudgetOverride),
|
|
@@ -352,9 +353,29 @@ const SubagentParamsSchema = Type.Object({
|
|
|
352
353
|
agentContract: Type.Optional(AgentContractOverride),
|
|
353
354
|
acceptance: Type.Optional(AcceptanceOverride),
|
|
354
355
|
gate: Type.Optional(Type.String({ minLength: 1, description: "Host gate command. Cannot be combined with acceptance." })),
|
|
355
|
-
}
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const { step: _legacyChainStep, ...subagentParamPropertiesWithoutStep } = SubagentParamProperties;
|
|
359
|
+
const trimmedSubagentParamProperties = {
|
|
360
|
+
...subagentParamPropertiesWithoutStep,
|
|
361
|
+
id: Type.Optional(Type.String({
|
|
362
|
+
description: "Run id or prefix for status, interrupt, stop, resume, steer, mission.attach-run, or the decision id for mission.resolve-decision."
|
|
363
|
+
})),
|
|
364
|
+
runId: Type.Optional(Type.String({
|
|
365
|
+
description: "Target run ID for interrupt, stop, resume, steer, or mission.attach-run. Prefer id for new calls."
|
|
366
|
+
})),
|
|
367
|
+
};
|
|
368
|
+
const SubagentParamsSchema = Type.Object(SubagentParamProperties);
|
|
369
|
+
const TrimmedSubagentParamsSchema = Type.Object(trimmedSubagentParamProperties);
|
|
356
370
|
|
|
357
371
|
export const SubagentParams = keepTopLevelParameterDescriptions(SubagentParamsSchema);
|
|
372
|
+
export const SubagentParamsWithoutLegacyChainControls = keepTopLevelParameterDescriptions(TrimmedSubagentParamsSchema);
|
|
373
|
+
|
|
374
|
+
export function createSubagentParamsSchema(options: { legacyChainControls?: boolean } = {}): typeof SubagentParams | typeof SubagentParamsWithoutLegacyChainControls {
|
|
375
|
+
return options.legacyChainControls === true
|
|
376
|
+
? SubagentParams
|
|
377
|
+
: SubagentParamsWithoutLegacyChainControls;
|
|
378
|
+
}
|
|
358
379
|
|
|
359
380
|
const SubagentWaitParamsSchema = Type.Object({
|
|
360
381
|
id: Type.Optional(Type.String({
|
|
@@ -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
|
-
• 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.
|
|
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. A mission object needs exactly one non-empty title or summary; objective and labels are optional. goal may only be true and requires budget:{tokens}.
|
|
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,13 +37,14 @@ 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
|
+
• A mission object needs exactly one non-empty title or summary; objective and labels are optional. goal may only be true and requires budget:{tokens}.
|
|
47
48
|
|
|
48
49
|
ASYNC / SAFETY:
|
|
49
50
|
• Omitted async detaches background work. Do not sleep or poll merely to wait; use subagent_wait only when this turn must receive results.
|
|
@@ -152,13 +153,30 @@ function withMandatorySafetyGuidance(description: string): string {
|
|
|
152
153
|
: SUBAGENT_SAFETY_GUIDANCE;
|
|
153
154
|
}
|
|
154
155
|
|
|
155
|
-
|
|
156
|
+
const LEGACY_CHAIN_CONTROL_GUIDANCE_LINES = new Set([
|
|
157
|
+
'• { 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.',
|
|
158
|
+
"• approve-checkpoint and reject-checkpoint decide a paused durable legacy chain checkpoint.",
|
|
159
|
+
"• append-step uses step:{...} only for an already-running durable legacy chain; step is not an execution mode.",
|
|
160
|
+
]);
|
|
161
|
+
|
|
162
|
+
function withoutLegacyChainControlGuidance(description: string): string {
|
|
163
|
+
return description
|
|
164
|
+
.split("\n")
|
|
165
|
+
.filter((line) => !LEGACY_CHAIN_CONTROL_GUIDANCE_LINES.has(line.trim()))
|
|
166
|
+
.join("\n");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function buildSubagentToolDescription(config: Pick<ExtensionConfig, "toolDescriptionMode" | "legacyChainControls"> = {}, options?: ToolDescriptionOptions): string {
|
|
156
170
|
const mode = resolveToolDescriptionMode(config, options);
|
|
157
|
-
|
|
158
|
-
if (mode === "
|
|
171
|
+
let description: string;
|
|
172
|
+
if (mode === "compact") description = COMPACT_SUBAGENT_TOOL_DESCRIPTION;
|
|
173
|
+
else if (mode === "custom") {
|
|
159
174
|
const custom = loadCustomToolDescription(options);
|
|
160
|
-
if (custom)
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
175
|
+
if (custom) description = withMandatorySafetyGuidance(custom);
|
|
176
|
+
else {
|
|
177
|
+
warn(options, `${CUSTOM_TOOL_DESCRIPTION_FILE} was not found or valid for toolDescriptionMode "custom"; using full description.`);
|
|
178
|
+
description = FULL_SUBAGENT_TOOL_DESCRIPTION;
|
|
179
|
+
}
|
|
180
|
+
} else description = FULL_SUBAGENT_TOOL_DESCRIPTION;
|
|
181
|
+
return config.legacyChainControls === true ? description : withoutLegacyChainControlGuidance(description);
|
|
164
182
|
}
|