pi-subagents 0.49.0 → 0.50.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 +35 -0
- package/agents/oracle.md +7 -5
- package/agents/researcher.md +1 -1
- package/agents/reviewer.md +2 -2
- package/agents/scout.md +1 -1
- package/agents/worker.md +1 -1
- package/docs/agents.md +2 -0
- package/docs/configuration.md +47 -3
- package/docs/extension-api.md +36 -0
- package/docs/missions.md +1 -1
- package/docs/observability.md +16 -0
- package/docs/tool-reference.md +19 -2
- package/docs/workflows.md +2 -2
- package/package.json +1 -1
- package/skills/pi-subagents/references/constraints-and-recipes.md +3 -2
- package/skills/pi-subagents/references/execution-controls.md +5 -4
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
- package/skills/pi-subagents/references/prompting-and-roles.md +31 -15
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +10 -0
- package/src/api/external-runs.ts +174 -84
- package/src/api/preflight.ts +12 -6
- package/src/extension/config.ts +33 -0
- package/src/extension/index.ts +48 -5
- package/src/extension/public-execution.ts +3 -1
- package/src/extension/rpc.ts +5 -1
- package/src/extension/schemas.ts +2 -1
- package/src/extension/tool-description.ts +4 -2
- package/src/inspectors/herdr/actions.ts +3 -7
- package/src/inspectors/herdr/project-panes.ts +2 -6
- package/src/inspectors/herdr/shell-command.ts +16 -0
- package/src/intercom/intercom-bridge.ts +2 -3
- package/src/intercom/native-supervisor-channel.ts +19 -42
- package/src/missions/goal-driver.ts +3 -1
- package/src/runs/background/active-run-index.ts +71 -1
- package/src/runs/background/async-execution.ts +49 -7
- package/src/runs/background/async-job-tracker.ts +1 -0
- package/src/runs/background/async-resume.ts +3 -5
- package/src/runs/background/async-status-snapshot.ts +277 -0
- package/src/runs/background/async-status.ts +8 -3
- package/src/runs/background/chain-root-attachment.ts +2 -2
- package/src/runs/background/completion-replay.ts +11 -1
- package/src/runs/background/fleet-view.ts +3 -1
- package/src/runs/background/result-files.ts +437 -0
- package/src/runs/background/result-watcher.ts +188 -41
- package/src/runs/background/retained-children.ts +69 -20
- package/src/runs/background/run-id-resolver.ts +30 -24
- package/src/runs/background/run-status.ts +7 -2
- package/src/runs/background/scheduled-runs.ts +54 -28
- package/src/runs/background/stale-run-reconciler.ts +27 -13
- package/src/runs/background/subagent-runner.ts +294 -32
- package/src/runs/background/subagent-wait.ts +2 -0
- package/src/runs/background/wait-completions.ts +5 -2
- package/src/runs/foreground/async-dismiss-action.ts +2 -1
- package/src/runs/foreground/chain-execution.ts +16 -0
- package/src/runs/foreground/execution.ts +219 -15
- package/src/runs/foreground/subagent-executor.ts +114 -28
- package/src/runs/shared/completion-guard.ts +96 -6
- package/src/runs/shared/external-cli-runner.ts +4 -0
- package/src/runs/shared/model-fallback.ts +16 -2
- package/src/runs/shared/nested-events.ts +66 -62
- package/src/runs/shared/orca-progress-tabs.ts +375 -0
- package/src/runs/shared/parallel-utils.ts +2 -0
- package/src/runs/shared/subagent-control.ts +15 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +1 -9
- package/src/runs/shared/subagent-startup-retry.ts +12 -0
- package/src/runs/shared/tool-timeout.ts +93 -0
- package/src/shared/types.ts +29 -2
- package/src/slash/slash-commands.ts +34 -25
- package/src/slash/slash-live-state.ts +3 -0
- package/src/tui/fleet-status.ts +160 -45
- package/src/tui/fleet.ts +125 -15
- package/src/tui/render.ts +41 -10
- package/src/workflows/chat-progress.ts +6 -4
- package/src/workflows/scripted-workflow.ts +227 -70
package/src/extension/rpc.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { sanitizeDisplayText, truncateDisplayText } from "../shared/display-text
|
|
|
20
20
|
import { readStatus } from "../shared/utils.ts";
|
|
21
21
|
import { SubagentParams } from "./schemas.ts";
|
|
22
22
|
import { normalizePublicSubagentExecution } from "./public-execution.ts";
|
|
23
|
+
import { ASYNC_STATUS_SNAPSHOT_KIND, ASYNC_STATUS_SNAPSHOT_VERSION, buildAsyncStatusSnapshotForState } from "../runs/background/async-status-snapshot.ts";
|
|
23
24
|
|
|
24
25
|
export const SUBAGENT_RPC_PROTOCOL_VERSION = 1;
|
|
25
26
|
export const SUBAGENT_RPC_REQUEST_EVENT = "subagents:rpc:v1:request";
|
|
@@ -375,6 +376,7 @@ function pingData(ctx: ExtensionContext | null) {
|
|
|
375
376
|
capabilities: {
|
|
376
377
|
status: true,
|
|
377
378
|
fleetStatus: { version: 1 },
|
|
379
|
+
asyncStatusSnapshot: { kind: ASYNC_STATUS_SNAPSHOT_KIND, version: ASYNC_STATUS_SNAPSHOT_VERSION },
|
|
378
380
|
asyncSpawn: true,
|
|
379
381
|
steer: true,
|
|
380
382
|
nonRecoveringSteer: true,
|
|
@@ -544,13 +546,15 @@ async function handleRequest(
|
|
|
544
546
|
request.method,
|
|
545
547
|
{ action: "status", ...normalizeTargetParams(request.params, "status") },
|
|
546
548
|
);
|
|
549
|
+
const sessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
547
550
|
return {
|
|
548
551
|
...status,
|
|
549
552
|
fleet: buildFleetStatus(
|
|
550
553
|
options.state,
|
|
551
554
|
fleetKeys,
|
|
552
|
-
|
|
555
|
+
sessionId,
|
|
553
556
|
),
|
|
557
|
+
asyncSnapshot: buildAsyncStatusSnapshotForState(options.state, sessionId),
|
|
554
558
|
};
|
|
555
559
|
}
|
|
556
560
|
if (request.method === "steer") {
|
package/src/extension/schemas.ts
CHANGED
|
@@ -93,7 +93,7 @@ const AcceptanceOverride = Type.Unsafe({
|
|
|
93
93
|
});
|
|
94
94
|
|
|
95
95
|
const AgentContractOverride = Type.Object({
|
|
96
|
-
version: Type.Integer({
|
|
96
|
+
version: Type.Integer({ minimum: 1, maximum: 1, description: "Enable compatibility behavior for this run/child." }),
|
|
97
97
|
}, { additionalProperties: false, description: "Compatibility behavior. Omit for the default behavior." });
|
|
98
98
|
|
|
99
99
|
const ChainGateOverride = Type.String({
|
|
@@ -327,6 +327,7 @@ const SubagentParamProperties = {
|
|
|
327
327
|
async: Type.Optional(Type.Boolean({ description: "Run in background (default: false, or per config)" })),
|
|
328
328
|
timeoutMs: Type.Optional(Type.Integer({ minimum: 1, description: "Timeout. Foreground and single async runs use config timeoutMs, else 30m; async composites have no default parent deadline. Alias maxRuntimeMs." })),
|
|
329
329
|
maxRuntimeMs: Type.Optional(Type.Integer({ minimum: 1, description: "Alias timeoutMs. Foreground and single async runs use config timeoutMs, else 30m; async composites have no default parent deadline." })),
|
|
330
|
+
toolTimeoutMs: Type.Optional(Type.Integer({ minimum: 1, description: "Optional hard per-tool-call timeout in milliseconds; known-fast built-in tools have a five-minute default." })),
|
|
330
331
|
turnBudget: Type.Optional(TurnBudgetOverride),
|
|
331
332
|
toolBudget: Type.Optional(ToolBudgetOverride),
|
|
332
333
|
usageBudget: Type.Optional(UsageBudgetOverride),
|
|
@@ -11,6 +11,7 @@ export const SUBAGENT_SAFETY_GUIDANCE = `SAFETY-CRITICAL SUBAGENT GUIDANCE:
|
|
|
11
11
|
• Keep execution and management separate: omit action for structured single-child or workflowScript execution; use action only for management/control.
|
|
12
12
|
• Async/background runs are the default. Use async:false only when a blocking foreground result is needed. After an async launch, continue independent work only until its next dependency barrier; consume the result before work that depends on it. Do not sleep or poll status just to wait; use subagent_wait only when the current request must finish in this turn.
|
|
13
13
|
• Ordinary child subagents are not orchestrators. Only explicitly configured fanout children may use the child-safe subagent tool, still bounded by depth/session limits.
|
|
14
|
+
• Oracle/advisor consultations should use supervisor dialogue for material unknowns when available; request one-shot only when desired.
|
|
14
15
|
• Keep one writer for the same cwd/worktree. Use fresh-context read-only reviewers for independent review, then have the parent synthesize and apply fixes.
|
|
15
16
|
• Async runs expose asyncId/asyncDir with status.json, events.jsonl, output logs, status via { action: "status", id }, and lifecycle diagnostics via { action: "debug.run", id }. Include output paths and residual risks when reporting results.`;
|
|
16
17
|
|
|
@@ -19,7 +20,7 @@ export const FULL_SUBAGENT_TOOL_DESCRIPTION = `Run one child with { agent, task?
|
|
|
19
20
|
EXECUTION:
|
|
20
21
|
• Before executing, use { action: "list" } and run only executable/non-disabled configured agents.
|
|
21
22
|
• SINGLE CHILD: { agent:"worker", task:"..." }. This structured form starts exactly one child through the workflow runtime. Workflow-level fields such as model, context, cwd, worktree, output, budgets, acceptance, and async remain defaults for that child. Do not combine agent/task with action or workflowScript.
|
|
22
|
-
• WORKFLOW SCRIPT: { workflowScript: "return runs.run('main', {agent:'worker', task:'...'})" }. 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
|
|
23
|
+
• WORKFLOW SCRIPT: { workflowScript: "return runs.run('main', {agent:'worker', task:'...'})" }. 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 recent retained workflow children with resumable/not-resumable reasons. Resume only rows reported resumable. For a simple follow-up or implementation challenge, use {action:"resume", id:"run-id", message:"..."}. Resume keeps the stored agent/model/tool contract. If no resumable child is listed, launch a same-role fallback challenge and label it as fallback. Inside workflowScript, continue one with runs.run(key, {resume:"run-id", task:"follow-up"}); 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.
|
|
23
24
|
• 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" }
|
|
24
25
|
• 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}" }
|
|
25
26
|
• 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.
|
|
@@ -39,7 +40,7 @@ export const COMPACT_SUBAGENT_TOOL_DESCRIPTION = `Run one child with { agent, ta
|
|
|
39
40
|
EXECUTE:
|
|
40
41
|
• Call { action:"list" } first and use only executable/non-disabled agents.
|
|
41
42
|
• SINGLE {agent:"worker",task:"..."} starts exactly one child through the workflow runtime. Workflow-level fields remain child defaults. Do not combine agent/task with action or workflowScript.
|
|
42
|
-
• 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
|
|
43
|
+
• 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 recent retained workflow children and resume only rows reported resumable. Use {action:"resume",id:"run-id",message:"..."} for a simple follow-up or challenge; resume keeps the stored agent/model/tool contract. If none is resumable, launch a same-role fallback challenge and label it as fallback. Inside workflowScript use runs.run(key,{resume:"run-id",task:"follow-up"}) when the script must wait for completion and 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.
|
|
43
44
|
• 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]"}
|
|
44
45
|
• 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.
|
|
45
46
|
|
|
@@ -51,6 +52,7 @@ MANAGE / CONTROL:
|
|
|
51
52
|
ASYNC / SAFETY:
|
|
52
53
|
• Omitted async detaches background work. Continue independent work only until its next dependency barrier; consume the result before work that depends on it. Do not sleep or poll merely to wait; use subagent_wait only when this turn must receive results.
|
|
53
54
|
• Ordinary children are not orchestrators. Keep one writer per cwd/worktree and use fresh read-only reviewers for independent checks.
|
|
55
|
+
• Oracle/advisor consultations use available supervisor dialogue for material unknowns; request one-shot when desired.
|
|
54
56
|
• Status and artifacts live under asyncId/asyncDir with status.json, events.jsonl, output logs, and {action:"status",id:"..."}.`;
|
|
55
57
|
|
|
56
58
|
|
|
@@ -12,6 +12,7 @@ import { readStatus } from "../../shared/utils.ts";
|
|
|
12
12
|
import { resolveSubagentRunId } from "../../runs/background/run-id-resolver.ts";
|
|
13
13
|
import { resolveNodeExecutable } from "../../shared/node-executable.ts";
|
|
14
14
|
import { createHerdrClient, detectHerdr, type HerdrClient, type HerdrErrorCode, type HerdrResult } from "./client.ts";
|
|
15
|
+
import { formatShellCommand } from "./shell-command.ts";
|
|
15
16
|
|
|
16
17
|
export const HERDR_INSPECTOR_ACTIONS = ["inspector.open", "inspector.status", "inspector.close"] as const;
|
|
17
18
|
export type HerdrInspectorAction = typeof HERDR_INSPECTOR_ACTIONS[number];
|
|
@@ -86,16 +87,11 @@ function extractPaneId(value: unknown): string | undefined {
|
|
|
86
87
|
return undefined;
|
|
87
88
|
}
|
|
88
89
|
|
|
89
|
-
function shellQuote(value: string): string {
|
|
90
|
-
if (process.platform === "win32") return `"${value.replaceAll('"', '\\"')}"`;
|
|
91
|
-
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
90
|
function inspectorCommand(input: { runnerPath: string; asyncDir: string; runId: string; index?: number; missionPath?: string; allowSteer: boolean; allowStop: boolean; sessionRoots: string[] }): string {
|
|
95
|
-
const args = [
|
|
91
|
+
const args = [input.runnerPath, "--async-dir", input.asyncDir, "--run-id", input.runId, "--allow-steer", String(input.allowSteer), "--allow-stop", String(input.allowStop), "--session-roots", JSON.stringify(input.sessionRoots)];
|
|
96
92
|
if (input.index !== undefined) args.push("--index", String(input.index));
|
|
97
93
|
if (input.missionPath) args.push("--mission-path", input.missionPath);
|
|
98
|
-
return
|
|
94
|
+
return formatShellCommand(resolveNodeExecutable(), args);
|
|
99
95
|
}
|
|
100
96
|
|
|
101
97
|
function missionForRun(asyncDir: string, cwd: string, config: MissionStoreConfig | undefined, runId: string): { id: string; path: string } | undefined {
|
|
@@ -6,6 +6,7 @@ import { getProjectSubagentsDir } from "../../shared/artifacts.ts";
|
|
|
6
6
|
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
7
7
|
import type { Details } from "../../shared/types.ts";
|
|
8
8
|
import { createHerdrClient, detectHerdr, type HerdrClient, type HerdrErrorCode } from "./client.ts";
|
|
9
|
+
import { formatShellCommand } from "./shell-command.ts";
|
|
9
10
|
|
|
10
11
|
export const HERDR_PROJECT_PANE_ACTIONS = ["project.open", "project.status", "project.close"] as const;
|
|
11
12
|
export type HerdrProjectPaneAction = typeof HERDR_PROJECT_PANE_ACTIONS[number];
|
|
@@ -257,11 +258,6 @@ function projectPaneRuntime(value: unknown): ProjectPaneRuntime | undefined {
|
|
|
257
258
|
};
|
|
258
259
|
}
|
|
259
260
|
|
|
260
|
-
function shellQuote(value: string): string {
|
|
261
|
-
if (process.platform === "win32") return `"${value.replaceAll('"', '\\"')}"`;
|
|
262
|
-
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
261
|
function resolveProjectRoot(requested: string): ProjectPaneResult<string> {
|
|
266
262
|
const resolved = path.resolve(requested);
|
|
267
263
|
try {
|
|
@@ -284,7 +280,7 @@ async function inspectPane(client: HerdrClient, paneId: string, signal?: AbortSi
|
|
|
284
280
|
function projectPaneCommand(message: string | undefined): string {
|
|
285
281
|
const args = message?.trim() ? [message.trim()] : [];
|
|
286
282
|
const command = getPiSpawnCommand(args);
|
|
287
|
-
return
|
|
283
|
+
return formatShellCommand(command.command, command.args);
|
|
288
284
|
}
|
|
289
285
|
|
|
290
286
|
function canonicalRuntimePath(value: string | undefined): string | undefined {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
function shellQuote(value: string, platform: NodeJS.Platform): string {
|
|
2
|
+
if (platform === "win32") return `"${value.replaceAll('"', '\\"')}"`;
|
|
3
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
function isBareExecutable(value: string): boolean {
|
|
7
|
+
return /^[\w./@:-]+$/.test(value);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function formatShellCommand(exe: string, args: readonly string[], platform: NodeJS.Platform = process.platform): string {
|
|
11
|
+
const quotedArgs = args.map((arg) => shellQuote(arg, platform));
|
|
12
|
+
if (platform === "win32") return `& ${[shellQuote(exe, platform), ...quotedArgs].join(" ")}`;
|
|
13
|
+
// Nushell treats a leading quoted token as a string, so use a bare invoker for paths that need quoting.
|
|
14
|
+
const invocation = isBareExecutable(exe) ? exe : `sh -c 'exec "$0" "$@"' ${shellQuote(exe, platform)}`;
|
|
15
|
+
return [invocation, ...quotedArgs].join(" ");
|
|
16
|
+
}
|
|
@@ -26,9 +26,8 @@ Use contact_supervisor first. It resolves the supervisor session "{orchestratorT
|
|
|
26
26
|
- After contact_supervisor with reason "need_decision" or "interview_request", stay alive and continue only after the reply arrives. Do not finish your final response with a choose-one question.
|
|
27
27
|
- Do not ask for clarification when the only conflict is review-only/no-edit versus progress-writing or artifact-writing instructions. If an output path is configured but no write-capable tool is available, return the complete artifact in your final response; the runtime will persist it. Do not contact the supervisor merely because you cannot write that output path directly.
|
|
28
28
|
- Meaningful progress or unexpected discoveries that change the plan: contact_supervisor({ reason: "progress_update", message: "UPDATE: <summary>" })
|
|
29
|
-
- Generic intercom is lower-level plumbing/fallback only: intercom({ action: "ask", to: "{orchestratorTarget}", message: "<question>" })
|
|
30
29
|
|
|
31
|
-
Do not use contact_supervisor
|
|
30
|
+
Do not use contact_supervisor for routine completion handoffs. If no coordination is needed, return a focused task result.`;
|
|
32
31
|
|
|
33
32
|
export interface IntercomBridgeState {
|
|
34
33
|
active: boolean;
|
|
@@ -171,7 +170,7 @@ export function resolveIntercomBridge(input: ResolveIntercomBridgeInput): Interc
|
|
|
171
170
|
export function applyIntercomBridgeToAgent(agent: AgentConfig, bridge: IntercomBridgeState): AgentConfig {
|
|
172
171
|
if (!bridge.active || !bridge.orchestratorTarget) return agent;
|
|
173
172
|
|
|
174
|
-
const bridgeTools = ["
|
|
173
|
+
const bridgeTools = ["contact_supervisor"];
|
|
175
174
|
const tools = agent.tools && agent.tools.length > 0
|
|
176
175
|
? [...agent.tools, ...bridgeTools.filter((tool) => !agent.tools?.includes(tool))]
|
|
177
176
|
: agent.tools;
|
|
@@ -295,38 +295,18 @@ function hasTool(pi: ExtensionAPI, name: string): boolean {
|
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
-
export function registerNativeSupervisorClient(pi: ExtensionAPI
|
|
299
|
-
if (!readChildMetadata()) return;
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
};
|
|
311
|
-
pi.registerTool(tool);
|
|
312
|
-
}
|
|
313
|
-
if (includeIntercomFallback && !hasTool(pi, "intercom")) {
|
|
314
|
-
const tool: ToolDefinition<typeof IntercomParamsSchema, Record<string, unknown>> = {
|
|
315
|
-
name: "intercom",
|
|
316
|
-
label: "Intercom",
|
|
317
|
-
description: "Native supervisor-channel intercom fallback for subagents. Prefer contact_supervisor when available.",
|
|
318
|
-
parameters: IntercomParamsSchema,
|
|
319
|
-
async execute(_id, params, signal) {
|
|
320
|
-
const action = (params as IntercomParams).action;
|
|
321
|
-
if (action === "status") return { content: [{ type: "text", text: "Native supervisor channel is active." }], details: { active: true } };
|
|
322
|
-
if (action === "list") return { content: [{ type: "text", text: "Supervisor session available through contact_supervisor." }], details: { sessions: [] } };
|
|
323
|
-
if (action === "send") return sendSupervisorRequest({ reason: "progress_update", message: (params as IntercomParams).message ?? "" }, signal);
|
|
324
|
-
if (action === "ask") return sendSupervisorRequest({ reason: "need_decision", message: (params as IntercomParams).message ?? "" }, signal);
|
|
325
|
-
throw new Error("Native child intercom supports status, list, send, and ask. Use parent intercom reply from the supervisor session.");
|
|
326
|
-
},
|
|
327
|
-
};
|
|
328
|
-
pi.registerTool(tool);
|
|
329
|
-
}
|
|
298
|
+
export function registerNativeSupervisorClient(pi: ExtensionAPI): void {
|
|
299
|
+
if (!readChildMetadata() || hasTool(pi, "contact_supervisor")) return;
|
|
300
|
+
const tool: ToolDefinition<typeof ContactSupervisorParamsSchema, Record<string, unknown>> = {
|
|
301
|
+
name: "contact_supervisor",
|
|
302
|
+
label: "Contact Supervisor",
|
|
303
|
+
description: "Contact the parent/supervisor session for a blocking decision, structured interview, or progress update.",
|
|
304
|
+
parameters: ContactSupervisorParamsSchema,
|
|
305
|
+
execute(_id, params, signal) {
|
|
306
|
+
return sendSupervisorRequest(params as ContactSupervisorParams, signal);
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
pi.registerTool(tool);
|
|
330
310
|
}
|
|
331
311
|
|
|
332
312
|
function parseRequestFile(file: string, channelDir: string): PendingSupervisorRequest | undefined {
|
|
@@ -601,13 +581,11 @@ function publicPendingRequests(pending: Map<string, PendingSupervisorRequest>):
|
|
|
601
581
|
}));
|
|
602
582
|
}
|
|
603
583
|
|
|
604
|
-
function
|
|
584
|
+
function buildParentSupervisorTool(pending: Map<string, PendingSupervisorRequest>, state: SubagentState): ToolDefinition<typeof IntercomParamsSchema, Record<string, unknown>> {
|
|
605
585
|
return {
|
|
606
|
-
name,
|
|
607
|
-
label:
|
|
608
|
-
description:
|
|
609
|
-
? "Native pi-subagents supervisor channel. Use reply/pending/status to answer child subagent requests."
|
|
610
|
-
: "Native pi-subagents supervisor channel. Use reply/pending/status to answer child subagent requests without overriding pi-intercom.",
|
|
586
|
+
name: NATIVE_SUPERVISOR_TOOL_NAME,
|
|
587
|
+
label: "Subagent Supervisor",
|
|
588
|
+
description: "Native pi-subagents supervisor channel. Use reply/pending/status to answer child subagent requests without overriding pi-intercom.",
|
|
611
589
|
parameters: IntercomParamsSchema,
|
|
612
590
|
async execute(_id, params) {
|
|
613
591
|
refreshPendingRequests(pending, state, state.lastUiContext ?? undefined);
|
|
@@ -627,9 +605,9 @@ function buildParentIntercomTool(pending: Map<string, PendingSupervisorRequest>,
|
|
|
627
605
|
return { content: [{ type: "text", text: `Replied to supervisor request ${request.id}.` }], details: { replyTo: request.id, runId: request.runId, agent: request.agent } };
|
|
628
606
|
}
|
|
629
607
|
if (input.action === "send" || input.action === "ask") {
|
|
630
|
-
throw new Error("
|
|
608
|
+
throw new Error("The native subagent supervisor handles replies only. Child agents initiate asks with contact_supervisor.");
|
|
631
609
|
}
|
|
632
|
-
throw new Error(`Unsupported
|
|
610
|
+
throw new Error(`Unsupported supervisor action: ${input.action}`);
|
|
633
611
|
},
|
|
634
612
|
};
|
|
635
613
|
}
|
|
@@ -647,8 +625,7 @@ export function createNativeSupervisorChannel(pi: ExtensionAPI, state: SubagentS
|
|
|
647
625
|
let lastStaleCleanupAt = 0;
|
|
648
626
|
|
|
649
627
|
const registerParentTools = (): void => {
|
|
650
|
-
if (!hasTool(pi, NATIVE_SUPERVISOR_TOOL_NAME)) pi.registerTool(
|
|
651
|
-
if (!hasTool(pi, "intercom")) pi.registerTool(buildParentIntercomTool(pending, state));
|
|
628
|
+
if (!hasTool(pi, NATIVE_SUPERVISOR_TOOL_NAME)) pi.registerTool(buildParentSupervisorTool(pending, state));
|
|
652
629
|
};
|
|
653
630
|
|
|
654
631
|
const cleanupStaleChannelsIfDue = (): void => {
|
|
@@ -99,7 +99,9 @@ function missionStateAction(location: MissionStoreLocation, record: MissionRecor
|
|
|
99
99
|
function retainedResumeTarget(record: MissionRecord, retainedChildren: RetainedChild[]): RetainedChild | undefined {
|
|
100
100
|
const latestRun = record.runs.at(-1);
|
|
101
101
|
if (!latestRun) return undefined;
|
|
102
|
-
return retainedChildren.find((child) =>
|
|
102
|
+
return retainedChildren.find((child) =>
|
|
103
|
+
child.resumability.state === "resumable" && (child.runId === latestRun.runId || child.parentRunId === latestRun.runId)
|
|
104
|
+
);
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
function nextReadyAction(location: MissionStoreLocation, record: MissionRecord, retainedChildren: RetainedChild[]): string {
|
|
@@ -3,37 +3,96 @@ import * as path from "node:path";
|
|
|
3
3
|
import type { AsyncStatus } from "../../shared/types.ts";
|
|
4
4
|
|
|
5
5
|
export const ACTIVE_RUN_INDEX_DIR = ".active-runs";
|
|
6
|
+
export const DEFAULT_STALE_TERMINAL_ACTIVE_MARKER_MS = 24 * 60 * 60 * 1000;
|
|
7
|
+
|
|
8
|
+
const TOOL_CALL_INDEX_DIR = "tool-calls";
|
|
6
9
|
|
|
7
10
|
function indexDir(asyncDirRoot: string): string {
|
|
8
11
|
return path.join(asyncDirRoot, ACTIVE_RUN_INDEX_DIR);
|
|
9
12
|
}
|
|
10
13
|
|
|
14
|
+
function toolCallIndexDir(asyncDirRoot: string, toolCallId: string): string {
|
|
15
|
+
return path.join(indexDir(asyncDirRoot), TOOL_CALL_INDEX_DIR, encodeURIComponent(toolCallId));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function toolCallIndexPath(asyncDir: string, toolCallId: string): string {
|
|
19
|
+
return path.join(toolCallIndexDir(path.dirname(asyncDir), toolCallId), path.basename(asyncDir));
|
|
20
|
+
}
|
|
21
|
+
|
|
11
22
|
function markerPath(asyncDir: string): string {
|
|
12
23
|
return path.join(indexDir(path.dirname(asyncDir)), path.basename(asyncDir));
|
|
13
24
|
}
|
|
14
25
|
|
|
26
|
+
function removeEmptyAncestors(start: string, stop: string): void {
|
|
27
|
+
let current = start;
|
|
28
|
+
while (current !== stop && current.startsWith(`${stop}${path.sep}`)) {
|
|
29
|
+
try {
|
|
30
|
+
fs.rmdirSync(current);
|
|
31
|
+
} catch {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
current = path.dirname(current);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
15
38
|
export function isActiveAsyncState(state: AsyncStatus["state"]): boolean {
|
|
16
39
|
return state === "queued" || state === "running";
|
|
17
40
|
}
|
|
18
41
|
|
|
42
|
+
function releaseToolCallAliases(asyncDir: string): void {
|
|
43
|
+
const root = path.join(indexDir(path.dirname(asyncDir)), TOOL_CALL_INDEX_DIR);
|
|
44
|
+
let entries: fs.Dirent[];
|
|
45
|
+
try {
|
|
46
|
+
entries = fs.readdirSync(root, { withFileTypes: true });
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return;
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
if (!entry.isDirectory()) continue;
|
|
53
|
+
const aliasMarker = path.join(root, entry.name, path.basename(asyncDir));
|
|
54
|
+
try {
|
|
55
|
+
fs.rmSync(aliasMarker, { force: true });
|
|
56
|
+
removeEmptyAncestors(path.dirname(aliasMarker), root);
|
|
57
|
+
} catch {
|
|
58
|
+
// Alias cleanup must not affect the authoritative active-run marker.
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
19
63
|
export function releaseActiveRunIndex(asyncDir: string): void {
|
|
20
64
|
try {
|
|
21
65
|
fs.rmSync(markerPath(asyncDir));
|
|
22
66
|
} catch (error) {
|
|
23
67
|
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
24
68
|
}
|
|
69
|
+
releaseToolCallAliases(asyncDir);
|
|
25
70
|
}
|
|
26
71
|
|
|
27
|
-
export function updateActiveRunIndex(asyncDir: string, state: AsyncStatus["state"]): void {
|
|
72
|
+
export function updateActiveRunIndex(asyncDir: string, state: AsyncStatus["state"], toolCallId?: string): void {
|
|
28
73
|
const marker = markerPath(asyncDir);
|
|
29
74
|
if (isActiveAsyncState(state)) {
|
|
30
75
|
fs.mkdirSync(path.dirname(marker), { recursive: true });
|
|
31
76
|
fs.writeFileSync(marker, "", { flag: "a" });
|
|
77
|
+
if (toolCallId) {
|
|
78
|
+
const toolCallMarker = toolCallIndexPath(asyncDir, toolCallId);
|
|
79
|
+
fs.mkdirSync(path.dirname(toolCallMarker), { recursive: true });
|
|
80
|
+
fs.writeFileSync(toolCallMarker, "", { flag: "a" });
|
|
81
|
+
}
|
|
32
82
|
return;
|
|
33
83
|
}
|
|
34
84
|
releaseActiveRunIndex(asyncDir);
|
|
35
85
|
}
|
|
36
86
|
|
|
87
|
+
export function activeRunMarkerAgeMs(asyncDir: string, now = Date.now()): number | undefined {
|
|
88
|
+
try {
|
|
89
|
+
return Math.max(0, now - fs.statSync(markerPath(asyncDir)).mtimeMs);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
37
96
|
export function readActiveRunIndex(asyncDirRoot: string): string[] | undefined {
|
|
38
97
|
try {
|
|
39
98
|
return fs.readdirSync(indexDir(asyncDirRoot), { withFileTypes: true })
|
|
@@ -44,3 +103,14 @@ export function readActiveRunIndex(asyncDirRoot: string): string[] | undefined {
|
|
|
44
103
|
throw error;
|
|
45
104
|
}
|
|
46
105
|
}
|
|
106
|
+
|
|
107
|
+
export function readActiveRunToolCallIndex(asyncDirRoot: string, toolCallId: string): string[] {
|
|
108
|
+
try {
|
|
109
|
+
return fs.readdirSync(toolCallIndexDir(asyncDirRoot, toolCallId), { withFileTypes: true })
|
|
110
|
+
.filter((entry) => entry.isFile())
|
|
111
|
+
.map((entry) => entry.name);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return [];
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -24,6 +24,7 @@ import { buildSkillInjection, normalizeSkillInput, resolveSkillsWithFallback } f
|
|
|
24
24
|
import { buildAgentMemoryInjection } from "../../agents/agent-memory.ts";
|
|
25
25
|
import { PI_CODING_AGENT_PACKAGE_ROOT_ENV, PROMPT_REDACTED, resolveChildCwd } from "../../shared/utils.ts";
|
|
26
26
|
import { buildModelCandidates, resolveEffectiveSubagentModel, resolveModelCandidate, resolveSubagentModelOverride, type AvailableModelInfo, type ParentModel } from "../shared/model-fallback.ts";
|
|
27
|
+
import { resolveToolTimeoutMs, toolTimeoutFromEnv } from "../shared/tool-timeout.ts";
|
|
27
28
|
import type { ModelScopeConfig } from "../shared/model-scope.ts";
|
|
28
29
|
import { resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
29
30
|
import { resolveExpectedWorktreeAgentCwd } from "../shared/worktree.ts";
|
|
@@ -58,6 +59,7 @@ import {
|
|
|
58
59
|
resolveChildMaxSubagentDepth,
|
|
59
60
|
} from "../../shared/types.ts";
|
|
60
61
|
import { nestedResultsPath, nestedSummaryFromAsyncStatus, resolveInheritedNestedRouteFromEnv, resolveNestedParentAddressFromEnv, writeNestedEvent } from "../shared/nested-events.ts";
|
|
62
|
+
import { resultFilePath } from "./result-files.ts";
|
|
61
63
|
import { appendTurnBudgetSystemPrompt, initialTurnBudgetState } from "../shared/turn-budget.ts";
|
|
62
64
|
import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
|
|
63
65
|
import { usageBudgetState } from "../shared/usage-budget.ts";
|
|
@@ -174,6 +176,12 @@ interface AsyncChainParams {
|
|
|
174
176
|
toolBudget?: ResolvedToolBudget;
|
|
175
177
|
usageBudget?: UsageBudgetConfig;
|
|
176
178
|
configToolBudget?: ResolvedToolBudget;
|
|
179
|
+
/** Optional per-call hard toolTimeoutMs override (highest precedence). */
|
|
180
|
+
callToolTimeoutMs?: number;
|
|
181
|
+
/** Global config.toolTimeoutMs (third precedence, after agent frontmatter). */
|
|
182
|
+
configToolTimeoutMs?: number;
|
|
183
|
+
/** PI_SUBAGENT_TOOL_TIMEOUT_MS override (lowest precedence). */
|
|
184
|
+
toolTimeoutMsEnv?: string | undefined;
|
|
177
185
|
/** Global cap on simultaneously-running subagent tasks within the async run. */
|
|
178
186
|
globalConcurrencyLimit?: number;
|
|
179
187
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
@@ -225,10 +233,16 @@ interface AsyncSingleParams {
|
|
|
225
233
|
acceptance?: AcceptanceInput;
|
|
226
234
|
timeoutMs?: number;
|
|
227
235
|
absoluteDeadlineAt?: number;
|
|
236
|
+
/** Optional per-call hard toolTimeoutMs override (highest precedence). */
|
|
237
|
+
toolTimeoutMs?: number;
|
|
228
238
|
turnBudget?: { maxTurns: number; graceTurns?: number };
|
|
229
239
|
toolBudget?: ResolvedToolBudget | ToolBudgetConfig;
|
|
230
240
|
usageBudget?: UsageBudgetConfig;
|
|
231
241
|
configToolBudget?: ResolvedToolBudget;
|
|
242
|
+
/** Global config.toolTimeoutMs (third precedence, after agent frontmatter). */
|
|
243
|
+
configToolTimeoutMs?: number;
|
|
244
|
+
/** PI_SUBAGENT_TOOL_TIMEOUT_MS override (lowest precedence). */
|
|
245
|
+
toolTimeoutMsEnv?: string | undefined;
|
|
232
246
|
allowZeroToolBudget?: boolean;
|
|
233
247
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
234
248
|
runFanoutBudget?: RunFanoutBudgetDescriptor;
|
|
@@ -267,6 +281,12 @@ export interface AsyncRunnerStepBuildParams {
|
|
|
267
281
|
validateOutputBindings?: boolean;
|
|
268
282
|
toolBudget?: ResolvedToolBudget;
|
|
269
283
|
configToolBudget?: ResolvedToolBudget;
|
|
284
|
+
/** Optional per-call hard toolTimeoutMs override from the subagent invocation. */
|
|
285
|
+
callToolTimeoutMs?: number;
|
|
286
|
+
/** Global config.toolTimeoutMs (third precedence, after agent frontmatter). */
|
|
287
|
+
configToolTimeoutMs?: number;
|
|
288
|
+
/** PI_SUBAGENT_TOOL_TIMEOUT_MS override (lowest precedence). */
|
|
289
|
+
toolTimeoutMsEnv?: string | undefined;
|
|
270
290
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
271
291
|
}
|
|
272
292
|
|
|
@@ -714,6 +734,13 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
714
734
|
const toolBudgetInput = s.toolBudget ?? params.toolBudget ?? a.toolBudget ?? params.configToolBudget;
|
|
715
735
|
const resolvedToolBudget = validateToolBudgetConfig(toolBudgetInput, s.toolBudget ? "toolBudget" : a.toolBudget ? "agent.toolBudget" : "config.toolBudget");
|
|
716
736
|
if (resolvedToolBudget.error) throw new AsyncStartValidationError(resolvedToolBudget.error);
|
|
737
|
+
const resolvedToolTimeout = resolveToolTimeoutMs({
|
|
738
|
+
callValue: params.callToolTimeoutMs,
|
|
739
|
+
agentValue: a.defaultToolTimeoutMs,
|
|
740
|
+
configValue: params.configToolTimeoutMs,
|
|
741
|
+
envValue: params.toolTimeoutMsEnv ?? toolTimeoutFromEnv(),
|
|
742
|
+
});
|
|
743
|
+
if (resolvedToolTimeout.error) throw new AsyncStartValidationError(resolvedToolTimeout.error);
|
|
717
744
|
const stepCwd = resolveChildCwd(runnerCwd, s.cwd);
|
|
718
745
|
const instructionCwd = behaviorCwd ?? stepCwd;
|
|
719
746
|
const readExistenceCwd = behaviorCwd ? stepCwd : instructionCwd;
|
|
@@ -827,6 +854,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
827
854
|
sessionFile,
|
|
828
855
|
maxSubagentDepth: resolveChildMaxSubagentDepth(maxSubagentDepth, a.maxSubagentDepth),
|
|
829
856
|
timeoutMs: a.defaultTimeoutMs ?? DEFAULT_ASYNC_TIMEOUT_MS,
|
|
857
|
+
toolTimeoutMs: resolvedToolTimeout.toolTimeoutMs,
|
|
830
858
|
waitToolEnabled: params.waitToolEnabled,
|
|
831
859
|
effectiveAcceptance: resolveEffectiveAcceptance({
|
|
832
860
|
explicit: s.acceptance,
|
|
@@ -1050,6 +1078,9 @@ export function executeAsyncChain(
|
|
|
1050
1078
|
asyncDir,
|
|
1051
1079
|
toolBudget: params.toolBudget,
|
|
1052
1080
|
configToolBudget: params.configToolBudget,
|
|
1081
|
+
callToolTimeoutMs: params.callToolTimeoutMs,
|
|
1082
|
+
configToolTimeoutMs: params.configToolTimeoutMs,
|
|
1083
|
+
toolTimeoutMsEnv: params.toolTimeoutMsEnv ?? toolTimeoutFromEnv(),
|
|
1053
1084
|
capabilityCeiling,
|
|
1054
1085
|
});
|
|
1055
1086
|
if ("error" in built) {
|
|
@@ -1086,7 +1117,7 @@ export function executeAsyncChain(
|
|
|
1086
1117
|
{
|
|
1087
1118
|
id,
|
|
1088
1119
|
steps,
|
|
1089
|
-
resultPath: inheritedNestedRoute ? nestedResultsPath(inheritedNestedRoute.rootRunId, id) :
|
|
1120
|
+
resultPath: inheritedNestedRoute ? nestedResultsPath(inheritedNestedRoute.rootRunId, id) : resultFilePath(DIRS.results, id),
|
|
1090
1121
|
cwd: runnerCwd,
|
|
1091
1122
|
placeholder: "{previous}",
|
|
1092
1123
|
maxOutput,
|
|
@@ -1384,17 +1415,27 @@ export function executeAsyncSingle(
|
|
|
1384
1415
|
? deadlineAt - Date.now()
|
|
1385
1416
|
: params.timeoutMs;
|
|
1386
1417
|
if (timeoutMs !== undefined && timeoutMs <= 0) return formatAsyncStartError("single", "The source run's absolute deadline expired before recovery could launch.");
|
|
1418
|
+
const resolvedToolTimeout = resolveToolTimeoutMs({
|
|
1419
|
+
callValue: params.toolTimeoutMs,
|
|
1420
|
+
agentValue: agentConfig.defaultToolTimeoutMs,
|
|
1421
|
+
configValue: params.configToolTimeoutMs,
|
|
1422
|
+
envValue: params.toolTimeoutMsEnv ?? toolTimeoutFromEnv(),
|
|
1423
|
+
});
|
|
1424
|
+
if (resolvedToolTimeout.error) return formatAsyncStartError("single", resolvedToolTimeout.error);
|
|
1425
|
+
const toolTimeoutMs = resolvedToolTimeout.toolTimeoutMs;
|
|
1387
1426
|
const initialTurnBudget = params.turnBudget ? initialTurnBudgetState(params.turnBudget) : undefined;
|
|
1388
1427
|
const initialUsageBudget = usageBudgetState(params.usageBudget, undefined);
|
|
1389
1428
|
const resolvedSessionDir = params.sessionDir ?? (sessionRoot ? path.join(sessionRoot, `async-${id}`) : undefined);
|
|
1390
1429
|
const structuredOutput = params.structuredOutputSchema
|
|
1391
1430
|
? createStructuredOutputRuntime(params.structuredOutputSchema, path.join(asyncDir, "structured-output"))
|
|
1392
1431
|
: undefined;
|
|
1393
|
-
const modelCandidates =
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1432
|
+
const modelCandidates = externalRunner
|
|
1433
|
+
? []
|
|
1434
|
+
: buildModelCandidates(primaryModel, agentConfig.fallbackModels, availableModels, ctx.currentModelProvider, { scope: ctx.modelScope })
|
|
1435
|
+
.flatMap((candidate) => {
|
|
1436
|
+
const resolved = applyThinkingSuffix(candidate, effectiveThinking, params.thinkingOverride !== undefined);
|
|
1437
|
+
return resolved ? [resolved] : [];
|
|
1438
|
+
});
|
|
1398
1439
|
const effectiveSystemPrompt = appendTurnBudgetSystemPrompt(systemPrompt, params.turnBudget);
|
|
1399
1440
|
const toolPlan = resolvePiLaunchToolPlan({
|
|
1400
1441
|
tools: agentConfig.tools,
|
|
@@ -1533,7 +1574,7 @@ export function executeAsyncSingle(
|
|
|
1533
1574
|
],
|
|
1534
1575
|
resultPath: params.parentWorkflowRunId !== undefined && params.revivalLease !== undefined
|
|
1535
1576
|
? workflowAwaitedAsyncResultPath(asyncDir)
|
|
1536
|
-
: inheritedNestedRoute ? nestedResultsPath(inheritedNestedRoute.rootRunId, id) :
|
|
1577
|
+
: inheritedNestedRoute ? nestedResultsPath(inheritedNestedRoute.rootRunId, id) : resultFilePath(DIRS.results, id),
|
|
1537
1578
|
cwd: runnerCwd,
|
|
1538
1579
|
placeholder: "{previous}",
|
|
1539
1580
|
maxOutput,
|
|
@@ -1552,6 +1593,7 @@ export function executeAsyncSingle(
|
|
|
1552
1593
|
controlConfig,
|
|
1553
1594
|
timeoutMs,
|
|
1554
1595
|
deadlineAt,
|
|
1596
|
+
toolTimeoutMs,
|
|
1555
1597
|
turnBudget: params.turnBudget,
|
|
1556
1598
|
toolBudget: params.toolBudget,
|
|
1557
1599
|
usageBudget: params.usageBudget,
|
|
@@ -125,6 +125,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
125
125
|
return {
|
|
126
126
|
asyncId: run.id,
|
|
127
127
|
asyncDir: run.asyncDir,
|
|
128
|
+
toolCallId: run.toolCallId,
|
|
128
129
|
status: run.state,
|
|
129
130
|
sessionId: run.sessionId,
|
|
130
131
|
activityState: run.activityState,
|
|
@@ -8,6 +8,7 @@ import { intersectSubagentCapabilityCeilings, parseSubagentCapabilityCeiling, ty
|
|
|
8
8
|
import { validateRunFanoutBudgetDescriptor } from "../shared/run-fanout-budget.ts";
|
|
9
9
|
import { resolveTurnBudgetConfig } from "../shared/turn-budget.ts";
|
|
10
10
|
import { reconcileAsyncRun } from "./stale-run-reconciler.ts";
|
|
11
|
+
import { resultFilePath } from "./result-files.ts";
|
|
11
12
|
|
|
12
13
|
export interface AsyncResumeParams {
|
|
13
14
|
id?: string;
|
|
@@ -172,7 +173,7 @@ function prefixedRunIds(dir: string, prefix: string, suffix = ""): string[] {
|
|
|
172
173
|
}
|
|
173
174
|
|
|
174
175
|
function exactResultPath(resultsDir: string, runId: string): string | null {
|
|
175
|
-
const resultPath =
|
|
176
|
+
const resultPath = resultFilePath(resultsDir, runId);
|
|
176
177
|
assertInsideRoot(resultsDir, resultPath, "Async result file");
|
|
177
178
|
return fs.existsSync(resultPath) ? resultPath : null;
|
|
178
179
|
}
|
|
@@ -182,10 +183,7 @@ export function findAsyncRunPrefixMatches(prefix: string, asyncDirRoot: string,
|
|
|
182
183
|
if (!requestedId) return [];
|
|
183
184
|
const asyncRoot = path.resolve(asyncDirRoot);
|
|
184
185
|
const resultRoot = path.resolve(resultsDir);
|
|
185
|
-
const matchingIds =
|
|
186
|
-
...prefixedRunIds(asyncRoot, requestedId),
|
|
187
|
-
...prefixedRunIds(resultRoot, requestedId, ".json"),
|
|
188
|
-
])].sort();
|
|
186
|
+
const matchingIds = prefixedRunIds(asyncRoot, requestedId).sort();
|
|
189
187
|
return matchingIds.map((id) => {
|
|
190
188
|
const asyncDir = path.join(asyncRoot, id);
|
|
191
189
|
assertInsideRoot(asyncRoot, asyncDir, "Async run directory");
|