pi-subagents 0.49.0 → 0.51.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 +97 -0
- package/agents/gpt-pro.md +17 -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/async-retention-discovery-worker.mjs +180 -0
- package/docs/agents.md +37 -2
- package/docs/configuration.md +76 -14
- package/docs/extension-api.md +78 -1
- package/docs/missions.md +1 -1
- package/docs/observability.md +20 -4
- package/docs/tool-reference.md +55 -39
- package/docs/workflows.md +171 -5
- package/package.json +4 -2
- package/skills/pi-subagents/SKILL.md +5 -4
- package/skills/pi-subagents/references/constraints-and-recipes.md +9 -6
- package/skills/pi-subagents/references/execution-controls.md +22 -18
- package/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
- package/skills/pi-subagents/references/prompting-and-roles.md +33 -17
- package/src/agents/agent-management.ts +100 -345
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +135 -25
- package/src/api/external-job-provider.ts +185 -0
- package/src/api/external-runs.ts +174 -84
- package/src/api/preflight.ts +42 -11
- package/src/api/shared-types.ts +2 -0
- package/src/extension/config.ts +36 -3
- package/src/extension/doctor.ts +3 -6
- package/src/extension/fanout-child.ts +2 -2
- package/src/extension/index.ts +210 -90
- package/src/extension/public-execution.ts +31 -2
- package/src/extension/rpc.ts +5 -1
- package/src/extension/schemas.ts +14 -36
- package/src/extension/tool-description.ts +37 -24
- package/src/inspectors/herdr/actions.ts +5 -9
- package/src/inspectors/herdr/inspector-runner.ts +2 -1
- package/src/inspectors/herdr/project-panes.ts +4 -8
- package/src/inspectors/herdr/shell-command.ts +16 -0
- package/src/intercom/intercom-bridge.ts +2 -3
- package/src/intercom/native-supervisor-channel.ts +49 -51
- package/src/missions/goal-driver.ts +3 -1
- package/src/missions/lifecycle.ts +6 -1
- package/src/missions/store.ts +4 -9
- package/src/profiles/profiles.ts +3 -1
- package/src/runs/background/active-run-index.ts +94 -1
- package/src/runs/background/async-execution.ts +98 -38
- package/src/runs/background/async-job-tracker.ts +21 -4
- package/src/runs/background/async-resume.ts +30 -17
- package/src/runs/background/async-retention.ts +888 -0
- package/src/runs/background/async-status-snapshot.ts +277 -0
- package/src/runs/background/async-status.ts +47 -56
- package/src/runs/background/chain-append.ts +3 -33
- package/src/runs/background/chain-root-attachment.ts +2 -2
- package/src/runs/background/completion-replay.ts +11 -1
- package/src/runs/background/control-channel.ts +14 -68
- package/src/runs/background/fleet-view.ts +3 -1
- package/src/runs/background/index-segment.ts +59 -0
- package/src/runs/background/notify.ts +3 -1
- package/src/runs/background/result-files.ts +505 -0
- package/src/runs/background/result-watcher.ts +250 -51
- package/src/runs/background/retained-children.ts +79 -20
- package/src/runs/background/run-id-query.ts +7 -0
- package/src/runs/background/run-id-resolver.ts +37 -29
- package/src/runs/background/run-status.ts +34 -20
- package/src/runs/background/scheduled-runs.ts +71 -27
- package/src/runs/background/stale-run-reconciler.ts +33 -16
- package/src/runs/background/steering.ts +11 -1
- package/src/runs/background/subagent-runner.ts +535 -161
- package/src/runs/background/subagent-wait.ts +9 -7
- package/src/runs/background/terminal-run-index.ts +129 -0
- package/src/runs/background/wait-completions.ts +22 -2
- package/src/runs/background/wait-subscriptions.ts +80 -1
- package/src/runs/foreground/async-dismiss-action.ts +2 -1
- package/src/runs/foreground/async-steering-action.ts +21 -14
- package/src/runs/foreground/execution.ts +221 -15
- package/src/runs/foreground/subagent-executor.ts +529 -1519
- package/src/runs/foreground/workflow-foreground-steering.ts +6 -5
- package/src/runs/shared/chain-outputs.ts +1 -3
- package/src/runs/shared/completion-guard.ts +96 -6
- package/src/runs/shared/external-cli-runner.ts +4 -0
- package/src/runs/shared/external-job-bridge.ts +450 -0
- package/src/runs/shared/external-job-runner.ts +286 -0
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +14 -0
- package/src/runs/shared/model-fallback.ts +34 -3
- package/src/runs/shared/nested-events.ts +66 -62
- package/src/runs/shared/orca-progress-tabs.ts +437 -0
- package/src/runs/shared/parallel-handoff.ts +46 -4
- package/src/runs/shared/parallel-utils.ts +6 -15
- package/src/runs/shared/permissions.ts +5 -1
- package/src/runs/shared/pi-args.ts +8 -1
- package/src/runs/shared/subagent-control.ts +41 -4
- package/src/runs/shared/subagent-prompt-runtime.ts +13 -15
- package/src/runs/shared/subagent-startup-retry.ts +12 -0
- package/src/runs/shared/tool-timeout.ts +93 -0
- package/src/runs/shared/workflow-graph.ts +1 -23
- package/src/runs/shared/worktree.ts +12 -1
- package/src/shared/atomic-json.ts +22 -2
- package/src/shared/capacity-resilient-json.ts +102 -0
- package/src/shared/completion-owner.ts +14 -0
- package/src/shared/file-system-retry.ts +49 -1
- package/src/shared/fork-context.ts +42 -0
- package/src/shared/prompt-resources.ts +0 -40
- package/src/shared/settings.ts +3 -27
- package/src/shared/types.ts +88 -27
- package/src/shared/utils.ts +8 -0
- package/src/shared/watch-strategy.ts +10 -0
- package/src/slash/slash-commands.ts +45 -28
- package/src/slash/slash-live-state.ts +3 -0
- package/src/tui/fleet-status.ts +160 -45
- package/src/tui/fleet.ts +186 -28
- package/src/tui/render.ts +41 -10
- package/src/workflows/chat-progress.ts +7 -5
- package/src/workflows/scripted-workflow.ts +424 -125
- package/src/runs/foreground/chain-clarify.ts +0 -1354
- package/src/runs/foreground/chain-execution.ts +0 -1565
|
@@ -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 {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
3
4
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
4
5
|
import { writePrivateAtomicJson } from "../shared/atomic-json.ts";
|
|
5
6
|
import { PROMPT_REDACTED } from "../shared/utils.ts";
|
|
@@ -294,13 +295,17 @@ export function syncMissionFromAsyncCompletion(value: unknown): MissionRecord |
|
|
|
294
295
|
current = readMission(binding.location, binding.missionId);
|
|
295
296
|
} catch (error) {
|
|
296
297
|
if (!(error instanceof MissionNotFoundError)) throw error;
|
|
298
|
+
const reason = "mission-record-missing";
|
|
299
|
+
const markerId = createHash("sha256").update(JSON.stringify([runId, binding.missionId, reason])).digest("hex");
|
|
300
|
+
const markerPath = path.join(event.asyncDir, `.mission-sync-skipped-${markerId}.json`);
|
|
297
301
|
try {
|
|
302
|
+
fs.writeFileSync(markerPath, `${JSON.stringify({ runId, missionId: binding.missionId, reason })}\n`, { encoding: "utf-8", mode: 0o600, flag: "wx" });
|
|
298
303
|
fs.appendFileSync(path.join(event.asyncDir, "events.jsonl"), `${JSON.stringify({
|
|
299
304
|
type: "subagent.mission.sync.skipped",
|
|
300
305
|
ts: Date.now(),
|
|
301
306
|
runId,
|
|
302
307
|
missionId: binding.missionId,
|
|
303
|
-
reason
|
|
308
|
+
reason,
|
|
304
309
|
missionPath: missionRecordPath(binding.location, binding.missionId),
|
|
305
310
|
})}\n`, "utf-8");
|
|
306
311
|
} catch {
|
package/src/missions/store.ts
CHANGED
|
@@ -77,11 +77,6 @@ function nonNegativeTokenCount(value: unknown, label: string): number {
|
|
|
77
77
|
return value as number;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
function parseStoredGoal(value: unknown, label: string): { goal?: MissionGoal; legacyObjective?: string } {
|
|
81
|
-
if (typeof value === "string") return { legacyObjective: requiredString(value, label).trim() };
|
|
82
|
-
return { goal: parseGoal(value, label) };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
80
|
function parseGoal(value: unknown, label: string): MissionGoal {
|
|
86
81
|
const input = asObject(value, label);
|
|
87
82
|
if (input.status !== "active" && input.status !== "paused" && input.status !== "budget-exhausted") throw new Error(`${label}.status is invalid`);
|
|
@@ -223,18 +218,18 @@ export function parseMissionRecord(value: unknown, source = "mission record"): M
|
|
|
223
218
|
const decisions = input.decisions as unknown[];
|
|
224
219
|
const artifacts = input.artifacts as unknown[];
|
|
225
220
|
const receipts = (input.receipts ?? []) as unknown[];
|
|
226
|
-
const
|
|
221
|
+
const goal = input.goal !== undefined ? parseGoal(input.goal, `${source}.goal`) : undefined;
|
|
227
222
|
const budget = input.budget !== undefined ? parseBudget(input.budget, `${source}.budget`) : undefined;
|
|
228
223
|
const usage = input.usage !== undefined ? parseUsage(input.usage, `${source}.usage`) : undefined;
|
|
229
|
-
const objective = optionalString(input.objective, `${source}.objective`)?.trim()
|
|
224
|
+
const objective = optionalString(input.objective, `${source}.objective`)?.trim();
|
|
230
225
|
if (!objective) throw new Error(`${source}.objective must be a non-empty string`);
|
|
231
|
-
if (
|
|
226
|
+
if (goal && !budget) throw new Error(`${source}.budget is required for a goal mission`);
|
|
232
227
|
return {
|
|
233
228
|
schemaVersion: 1,
|
|
234
229
|
id: validateMissionId(input.id, `${source}.id`),
|
|
235
230
|
title: requiredString(input.title, `${source}.title`),
|
|
236
231
|
objective,
|
|
237
|
-
...(
|
|
232
|
+
...(goal ? { goal } : {}),
|
|
238
233
|
...(budget ? { budget } : {}),
|
|
239
234
|
...(usage ? { usage } : {}),
|
|
240
235
|
status: missionStatus(input.status, `${source}.status`),
|
package/src/profiles/profiles.ts
CHANGED
|
@@ -3,6 +3,7 @@ import * as os from "node:os";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import { BUILTIN_AGENT_NAMES } from "../agents/agents.ts";
|
|
6
|
+
import { getPiSpawnCommand } from "../runs/shared/pi-spawn.ts";
|
|
6
7
|
import { findModelInfo, getSupportedThinkingLevels, splitKnownThinkingSuffix, toModelInfo } from "../shared/model-info.ts";
|
|
7
8
|
import { getAgentDir } from "../shared/utils.ts";
|
|
8
9
|
|
|
@@ -340,7 +341,8 @@ async function probeModel(
|
|
|
340
341
|
if (typeof pi.exec !== "function") {
|
|
341
342
|
return { status: "skipped", message: "pi.exec is unavailable in this runtime." };
|
|
342
343
|
}
|
|
343
|
-
const
|
|
344
|
+
const spawnSpec = getPiSpawnCommand(["-p", "--model", fullId, "--no-tools", 'Reply with exactly "OK".']);
|
|
345
|
+
const result = await pi.exec(spawnSpec.command, spawnSpec.args, {
|
|
344
346
|
cwd: os.tmpdir(),
|
|
345
347
|
timeout: 45_000,
|
|
346
348
|
} as Record<string, unknown>);
|
|
@@ -1,37 +1,118 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import type { AsyncStatus } from "../../shared/types.ts";
|
|
4
|
+
import { readStatus } from "../../shared/utils.ts";
|
|
5
|
+
import { encodeIndexSegment } from "./index-segment.ts";
|
|
6
|
+
import { updateTerminalRunIndex } from "./terminal-run-index.ts";
|
|
7
|
+
import { isStorageCapacityError } from "../../shared/file-system-retry.ts";
|
|
4
8
|
|
|
5
9
|
export const ACTIVE_RUN_INDEX_DIR = ".active-runs";
|
|
10
|
+
export const DEFAULT_STALE_TERMINAL_ACTIVE_MARKER_MS = 24 * 60 * 60 * 1000;
|
|
11
|
+
|
|
12
|
+
const TOOL_CALL_INDEX_DIR = "tool-calls";
|
|
6
13
|
|
|
7
14
|
function indexDir(asyncDirRoot: string): string {
|
|
8
15
|
return path.join(asyncDirRoot, ACTIVE_RUN_INDEX_DIR);
|
|
9
16
|
}
|
|
10
17
|
|
|
18
|
+
function toolCallIndexDir(asyncDirRoot: string, toolCallId: string): string {
|
|
19
|
+
return path.join(indexDir(asyncDirRoot), TOOL_CALL_INDEX_DIR, encodeIndexSegment(toolCallId));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function toolCallIndexPath(asyncDir: string, toolCallId: string): string {
|
|
23
|
+
return path.join(toolCallIndexDir(path.dirname(asyncDir), toolCallId), path.basename(asyncDir));
|
|
24
|
+
}
|
|
25
|
+
|
|
11
26
|
function markerPath(asyncDir: string): string {
|
|
12
27
|
return path.join(indexDir(path.dirname(asyncDir)), path.basename(asyncDir));
|
|
13
28
|
}
|
|
14
29
|
|
|
30
|
+
function removeEmptyAncestors(start: string, stop: string): void {
|
|
31
|
+
let current = start;
|
|
32
|
+
while (current !== stop && current.startsWith(`${stop}${path.sep}`)) {
|
|
33
|
+
try {
|
|
34
|
+
fs.rmdirSync(current);
|
|
35
|
+
} catch {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
current = path.dirname(current);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
15
42
|
export function isActiveAsyncState(state: AsyncStatus["state"]): boolean {
|
|
16
43
|
return state === "queued" || state === "running";
|
|
17
44
|
}
|
|
18
45
|
|
|
46
|
+
function releaseToolCallAliases(asyncDir: string): void {
|
|
47
|
+
const root = path.join(indexDir(path.dirname(asyncDir)), TOOL_CALL_INDEX_DIR);
|
|
48
|
+
let entries: fs.Dirent[];
|
|
49
|
+
try {
|
|
50
|
+
entries = fs.readdirSync(root, { withFileTypes: true });
|
|
51
|
+
} catch (error) {
|
|
52
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
53
|
+
if (code !== "ENOENT" && code !== "ENOTDIR") {
|
|
54
|
+
console.error(`Failed to inspect async active-run tool-call index root '${root}':`, error);
|
|
55
|
+
}
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
for (const entry of entries) {
|
|
59
|
+
if (!entry.isDirectory()) continue;
|
|
60
|
+
const aliasMarker = path.join(root, entry.name, path.basename(asyncDir));
|
|
61
|
+
try {
|
|
62
|
+
fs.rmSync(aliasMarker, { force: true });
|
|
63
|
+
removeEmptyAncestors(path.dirname(aliasMarker), root);
|
|
64
|
+
} catch {
|
|
65
|
+
// Alias cleanup must not affect the authoritative active-run marker.
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
19
70
|
export function releaseActiveRunIndex(asyncDir: string): void {
|
|
20
71
|
try {
|
|
21
72
|
fs.rmSync(markerPath(asyncDir));
|
|
22
73
|
} catch (error) {
|
|
23
74
|
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
24
75
|
}
|
|
76
|
+
releaseToolCallAliases(asyncDir);
|
|
25
77
|
}
|
|
26
78
|
|
|
27
|
-
export function updateActiveRunIndex(asyncDir: string, state: AsyncStatus["state"]): void {
|
|
79
|
+
export function updateActiveRunIndex(asyncDir: string, state: AsyncStatus["state"], toolCallId?: string, options: { retryCapacityErrors?: boolean } = {}): void {
|
|
28
80
|
const marker = markerPath(asyncDir);
|
|
29
81
|
if (isActiveAsyncState(state)) {
|
|
30
82
|
fs.mkdirSync(path.dirname(marker), { recursive: true });
|
|
31
83
|
fs.writeFileSync(marker, "", { flag: "a" });
|
|
84
|
+
if (toolCallId) {
|
|
85
|
+
try {
|
|
86
|
+
const toolCallMarker = toolCallIndexPath(asyncDir, toolCallId);
|
|
87
|
+
fs.mkdirSync(path.dirname(toolCallMarker), { recursive: true });
|
|
88
|
+
fs.writeFileSync(toolCallMarker, "", { flag: "a" });
|
|
89
|
+
} catch (error) {
|
|
90
|
+
// Tool-call aliases are optional. The authoritative active-run marker
|
|
91
|
+
// must keep the launch discoverable even when alias indexing fails.
|
|
92
|
+
console.error(`Failed to write async active-run tool-call index for '${asyncDir}':`, error);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
32
95
|
return;
|
|
33
96
|
}
|
|
34
97
|
releaseActiveRunIndex(asyncDir);
|
|
98
|
+
const status = readStatus(asyncDir);
|
|
99
|
+
if (status && status.state === state) {
|
|
100
|
+
try {
|
|
101
|
+
updateTerminalRunIndex(asyncDir, status);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (options.retryCapacityErrors && isStorageCapacityError(error)) throw error;
|
|
104
|
+
console.error(`Failed to write async terminal-run index for '${asyncDir}':`, error);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function activeRunMarkerAgeMs(asyncDir: string, now = Date.now()): number | undefined {
|
|
110
|
+
try {
|
|
111
|
+
return Math.max(0, now - fs.statSync(markerPath(asyncDir)).mtimeMs);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
35
116
|
}
|
|
36
117
|
|
|
37
118
|
export function readActiveRunIndex(asyncDirRoot: string): string[] | undefined {
|
|
@@ -44,3 +125,15 @@ export function readActiveRunIndex(asyncDirRoot: string): string[] | undefined {
|
|
|
44
125
|
throw error;
|
|
45
126
|
}
|
|
46
127
|
}
|
|
128
|
+
|
|
129
|
+
export function readActiveRunToolCallIndex(asyncDirRoot: string, toolCallId: string): string[] {
|
|
130
|
+
try {
|
|
131
|
+
return fs.readdirSync(toolCallIndexDir(asyncDirRoot, toolCallId), { withFileTypes: true })
|
|
132
|
+
.filter((entry) => entry.isFile())
|
|
133
|
+
.map((entry) => entry.name);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
136
|
+
if (code === "ENOENT" || code === "ENOTDIR") return [];
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -13,9 +13,10 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
13
13
|
import type { AgentConfig } from "../../agents/agents.ts";
|
|
14
14
|
import { appendAgentRefinementOverlay } from "../../agents/agent-refinements.ts";
|
|
15
15
|
import { writePrivateAtomicJson } from "../../shared/atomic-json.ts";
|
|
16
|
+
import { currentCompletionOwnerId } from "../../shared/completion-owner.ts";
|
|
16
17
|
import { applyThinkingSuffix, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan } from "../shared/pi-args.ts";
|
|
17
18
|
import { injectOutputPathSystemPrompt, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
18
|
-
import { buildChainInstructions,
|
|
19
|
+
import { buildChainInstructions, isDynamicParallelStep, isParallelStep, resolveChainPath, resolveExistingReadPaths, resolveStepBehavior, suppressProgressForReadOnlyTask, writeInitialProgressFile, type ChainStep, type ResolvedStepBehavior, type SequentialStep, type StepOverrides } from "../../shared/settings.ts";
|
|
19
20
|
import type { RunnerStep } from "../shared/parallel-utils.ts";
|
|
20
21
|
import type { ContextMode } from "../shared/context-mode.ts";
|
|
21
22
|
import { resolvePiPackageRoot } from "../shared/pi-spawn.ts";
|
|
@@ -24,6 +25,7 @@ import { buildSkillInjection, normalizeSkillInput, resolveSkillsWithFallback } f
|
|
|
24
25
|
import { buildAgentMemoryInjection } from "../../agents/agent-memory.ts";
|
|
25
26
|
import { PI_CODING_AGENT_PACKAGE_ROOT_ENV, PROMPT_REDACTED, resolveChildCwd } from "../../shared/utils.ts";
|
|
26
27
|
import { buildModelCandidates, resolveEffectiveSubagentModel, resolveModelCandidate, resolveSubagentModelOverride, type AvailableModelInfo, type ParentModel } from "../shared/model-fallback.ts";
|
|
28
|
+
import { resolveToolTimeoutMs, toolTimeoutFromEnv } from "../shared/tool-timeout.ts";
|
|
27
29
|
import type { ModelScopeConfig } from "../shared/model-scope.ts";
|
|
28
30
|
import { resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
29
31
|
import { resolveExpectedWorktreeAgentCwd } from "../shared/worktree.ts";
|
|
@@ -58,6 +60,7 @@ import {
|
|
|
58
60
|
resolveChildMaxSubagentDepth,
|
|
59
61
|
} from "../../shared/types.ts";
|
|
60
62
|
import { nestedResultsPath, nestedSummaryFromAsyncStatus, resolveInheritedNestedRouteFromEnv, resolveNestedParentAddressFromEnv, writeNestedEvent } from "../shared/nested-events.ts";
|
|
63
|
+
import { resultFilePath } from "./result-files.ts";
|
|
61
64
|
import { appendTurnBudgetSystemPrompt, initialTurnBudgetState } from "../shared/turn-budget.ts";
|
|
62
65
|
import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
|
|
63
66
|
import { usageBudgetState } from "../shared/usage-budget.ts";
|
|
@@ -123,6 +126,7 @@ interface AsyncExecutionContext {
|
|
|
123
126
|
pi: ExtensionAPI;
|
|
124
127
|
cwd: string;
|
|
125
128
|
currentSessionId: string;
|
|
129
|
+
completionOwnerId?: string;
|
|
126
130
|
/** Parent session id used by permission-system ask forwarding. */
|
|
127
131
|
parentSessionId?: string;
|
|
128
132
|
permissions?: PermissionConfig;
|
|
@@ -142,7 +146,7 @@ interface AsyncChainParams {
|
|
|
142
146
|
/** Raw caller-facing goal used only by the started event. */
|
|
143
147
|
goal?: string;
|
|
144
148
|
attachRoot?: ImportedAsyncRoot & { agent: string; outputName?: string; label?: string };
|
|
145
|
-
resultMode?:
|
|
149
|
+
resultMode?: SubagentRunMode;
|
|
146
150
|
agents: AgentConfig[];
|
|
147
151
|
ctx: AsyncExecutionContext;
|
|
148
152
|
availableModels?: AvailableModelInfo[];
|
|
@@ -174,6 +178,12 @@ interface AsyncChainParams {
|
|
|
174
178
|
toolBudget?: ResolvedToolBudget;
|
|
175
179
|
usageBudget?: UsageBudgetConfig;
|
|
176
180
|
configToolBudget?: ResolvedToolBudget;
|
|
181
|
+
/** Optional per-call hard toolTimeoutMs override (highest precedence). */
|
|
182
|
+
callToolTimeoutMs?: number;
|
|
183
|
+
/** Global config.toolTimeoutMs (third precedence, after agent frontmatter). */
|
|
184
|
+
configToolTimeoutMs?: number;
|
|
185
|
+
/** PI_SUBAGENT_TOOL_TIMEOUT_MS override (lowest precedence). */
|
|
186
|
+
toolTimeoutMsEnv?: string | undefined;
|
|
177
187
|
/** Global cap on simultaneously-running subagent tasks within the async run. */
|
|
178
188
|
globalConcurrencyLimit?: number;
|
|
179
189
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
@@ -217,6 +227,7 @@ interface AsyncSingleParams {
|
|
|
217
227
|
worktreeSetupHook?: string;
|
|
218
228
|
worktreeSetupHookTimeoutMs?: number;
|
|
219
229
|
worktreeBaseDir?: string;
|
|
230
|
+
worktree?: boolean;
|
|
220
231
|
controlConfig?: ResolvedControlConfig;
|
|
221
232
|
intercomBridge?: IntercomBridgeConfig;
|
|
222
233
|
controlIntercomTarget?: string;
|
|
@@ -225,10 +236,16 @@ interface AsyncSingleParams {
|
|
|
225
236
|
acceptance?: AcceptanceInput;
|
|
226
237
|
timeoutMs?: number;
|
|
227
238
|
absoluteDeadlineAt?: number;
|
|
239
|
+
/** Optional per-call hard toolTimeoutMs override (highest precedence). */
|
|
240
|
+
toolTimeoutMs?: number;
|
|
228
241
|
turnBudget?: { maxTurns: number; graceTurns?: number };
|
|
229
242
|
toolBudget?: ResolvedToolBudget | ToolBudgetConfig;
|
|
230
243
|
usageBudget?: UsageBudgetConfig;
|
|
231
244
|
configToolBudget?: ResolvedToolBudget;
|
|
245
|
+
/** Global config.toolTimeoutMs (third precedence, after agent frontmatter). */
|
|
246
|
+
configToolTimeoutMs?: number;
|
|
247
|
+
/** PI_SUBAGENT_TOOL_TIMEOUT_MS override (lowest precedence). */
|
|
248
|
+
toolTimeoutMsEnv?: string | undefined;
|
|
232
249
|
allowZeroToolBudget?: boolean;
|
|
233
250
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
234
251
|
runFanoutBudget?: RunFanoutBudgetDescriptor;
|
|
@@ -267,6 +284,12 @@ export interface AsyncRunnerStepBuildParams {
|
|
|
267
284
|
validateOutputBindings?: boolean;
|
|
268
285
|
toolBudget?: ResolvedToolBudget;
|
|
269
286
|
configToolBudget?: ResolvedToolBudget;
|
|
287
|
+
/** Optional per-call hard toolTimeoutMs override from the subagent invocation. */
|
|
288
|
+
callToolTimeoutMs?: number;
|
|
289
|
+
/** Global config.toolTimeoutMs (third precedence, after agent frontmatter). */
|
|
290
|
+
configToolTimeoutMs?: number;
|
|
291
|
+
/** PI_SUBAGENT_TOOL_TIMEOUT_MS override (lowest precedence). */
|
|
292
|
+
toolTimeoutMsEnv?: string | undefined;
|
|
270
293
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
271
294
|
}
|
|
272
295
|
|
|
@@ -408,7 +431,7 @@ function terminateRunnerBeforeProceed(pid: number): boolean {
|
|
|
408
431
|
return !runnerIsAlive(pid);
|
|
409
432
|
}
|
|
410
433
|
|
|
411
|
-
function persistPreProceedStartupFailure(asyncDir: string, runId: string, runnerProcessInstanceId: string, sessionId: string | undefined, message: string): void {
|
|
434
|
+
function persistPreProceedStartupFailure(asyncDir: string, runId: string, runnerProcessInstanceId: string, sessionId: string | undefined, completionOwnerId: string | undefined, message: string): void {
|
|
412
435
|
const now = Date.now();
|
|
413
436
|
try {
|
|
414
437
|
const statusPath = path.join(asyncDir, "status.json");
|
|
@@ -420,6 +443,7 @@ function persistPreProceedStartupFailure(asyncDir: string, runId: string, runner
|
|
|
420
443
|
...status,
|
|
421
444
|
runId,
|
|
422
445
|
...(sessionId ? { sessionId } : {}),
|
|
446
|
+
...(completionOwnerId ? { completionOwnerId } : {}),
|
|
423
447
|
state: "failed",
|
|
424
448
|
lastUpdate: now,
|
|
425
449
|
error: message,
|
|
@@ -471,10 +495,11 @@ function spawnRunner(cfg: object, suffix: string, cwd: string, onProcessTerminal
|
|
|
471
495
|
fs.writeFileSync(cfgPath, JSON.stringify(launchConfig));
|
|
472
496
|
const runner = path.join(path.dirname(fileURLToPath(import.meta.url)), "subagent-runner.ts");
|
|
473
497
|
const nodeCommand = resolveNodeExecutable();
|
|
474
|
-
const launchForStartup = launchConfig as typeof launchConfig & { asyncDir?: unknown; id?: unknown; sessionId?: unknown; revivalLease?: unknown };
|
|
498
|
+
const launchForStartup = launchConfig as typeof launchConfig & { asyncDir?: unknown; id?: unknown; sessionId?: unknown; completionOwnerId?: unknown; revivalLease?: unknown };
|
|
475
499
|
const launchAsyncDir = typeof launchForStartup.asyncDir === "string" ? launchForStartup.asyncDir : undefined;
|
|
476
500
|
const launchRunId = typeof launchForStartup.id === "string" ? launchForStartup.id : suffix;
|
|
477
501
|
const launchSessionId = typeof launchForStartup.sessionId === "string" ? launchForStartup.sessionId : undefined;
|
|
502
|
+
const launchCompletionOwnerId = typeof launchForStartup.completionOwnerId === "string" ? launchForStartup.completionOwnerId : undefined;
|
|
478
503
|
const startupPath = typeof launchForStartup.revivalLease === "object" && launchAsyncDir
|
|
479
504
|
? path.join(launchAsyncDir, "runner-startup.json")
|
|
480
505
|
: undefined;
|
|
@@ -564,7 +589,7 @@ function spawnRunner(cfg: object, suffix: string, cwd: string, onProcessTerminal
|
|
|
564
589
|
proc.unref();
|
|
565
590
|
if (startupPath && startupAckPath && startupProceedPath) {
|
|
566
591
|
const persistStartupFailure = (message: string) => {
|
|
567
|
-
if (launchAsyncDir) persistPreProceedStartupFailure(launchAsyncDir, launchRunId, runnerProcessInstanceId, launchSessionId, message);
|
|
592
|
+
if (launchAsyncDir) persistPreProceedStartupFailure(launchAsyncDir, launchRunId, runnerProcessInstanceId, launchSessionId, launchCompletionOwnerId, message);
|
|
568
593
|
};
|
|
569
594
|
const ready = waitForRunnerStartup(startupPath, "ready", RUNNER_STARTUP_TIMEOUT_MS);
|
|
570
595
|
if (ready.ok === false) {
|
|
@@ -649,13 +674,11 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
649
674
|
: chain;
|
|
650
675
|
const firstStep = chain[0];
|
|
651
676
|
const originalTask = params.task ?? (firstStep
|
|
652
|
-
? (
|
|
653
|
-
?
|
|
654
|
-
:
|
|
655
|
-
? firstStep.parallel
|
|
656
|
-
:
|
|
657
|
-
? firstStep.parallel.task
|
|
658
|
-
: (firstStep as SequentialStep).task)
|
|
677
|
+
? (isParallelStep(firstStep)
|
|
678
|
+
? firstStep.parallel[0]?.task
|
|
679
|
+
: isDynamicParallelStep(firstStep)
|
|
680
|
+
? firstStep.parallel.task
|
|
681
|
+
: (firstStep as SequentialStep).task)
|
|
659
682
|
: undefined);
|
|
660
683
|
try {
|
|
661
684
|
if (params.validateOutputBindings !== false) {
|
|
@@ -668,13 +691,11 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
668
691
|
const workflowGraph = buildWorkflowGraphSnapshot({ runId: id, mode: resultMode, steps: graphChain });
|
|
669
692
|
|
|
670
693
|
for (const s of chain) {
|
|
671
|
-
const stepAgents =
|
|
672
|
-
?
|
|
673
|
-
:
|
|
674
|
-
? s.parallel.
|
|
675
|
-
:
|
|
676
|
-
? [s.parallel.agent]
|
|
677
|
-
: [(s as SequentialStep).agent];
|
|
694
|
+
const stepAgents = isParallelStep(s)
|
|
695
|
+
? s.parallel.map((t) => t.agent)
|
|
696
|
+
: isDynamicParallelStep(s)
|
|
697
|
+
? [s.parallel.agent]
|
|
698
|
+
: [(s as SequentialStep).agent];
|
|
678
699
|
for (const agentName of stepAgents) {
|
|
679
700
|
if (!agents.find((x) => x.name === agentName)) {
|
|
680
701
|
return { error: `Unknown agent: ${agentName}` };
|
|
@@ -696,7 +717,8 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
696
717
|
};
|
|
697
718
|
const buildSeqStep = (s: SequentialStep, sessionFile?: string, behaviorCwd?: string, progressPrecreated = false, resolvedBehavior?: ResolvedStepBehavior, flatIndex?: number, parallelOutputNamespace?: { stepIndex: number; taskIndex?: number }, runFanoutPath?: string) => {
|
|
698
719
|
const a = agents.find((x) => x.name === s.agent)!;
|
|
699
|
-
const externalRunner = a.runner?.type === "external-cli";
|
|
720
|
+
const externalRunner = a.runner?.type === "external-cli" || a.runner?.type === "external-job";
|
|
721
|
+
const externalRunnerType = a.runner?.type;
|
|
700
722
|
if (externalRunner) {
|
|
701
723
|
const unsupported: string[] = [];
|
|
702
724
|
if (s.model !== undefined) unsupported.push("model override");
|
|
@@ -704,7 +726,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
704
726
|
if (s.acceptance !== undefined || params.agentContract !== undefined || s.agentContract !== undefined) unsupported.push("acceptance/agent contract");
|
|
705
727
|
if (s.toolBudget !== undefined || params.toolBudget !== undefined || a.toolBudget !== undefined || params.configToolBudget !== undefined) unsupported.push("tool budget");
|
|
706
728
|
if (params.contextForAgent?.(s.agent) === "fork") unsupported.push("fork context");
|
|
707
|
-
if (unsupported.length > 0) throw new AsyncStartValidationError(`Agent '${a.name}' uses runner.type='
|
|
729
|
+
if (unsupported.length > 0) throw new AsyncStartValidationError(`Agent '${a.name}' uses runner.type='${externalRunnerType}' and does not support: ${unsupported.join(", ")}.`);
|
|
708
730
|
}
|
|
709
731
|
try {
|
|
710
732
|
assertAgentAllowedByCapabilityCeiling(a.name, intersectSubagentCapabilityCeilings(params.capabilityCeiling, decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV])));
|
|
@@ -714,6 +736,13 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
714
736
|
const toolBudgetInput = s.toolBudget ?? params.toolBudget ?? a.toolBudget ?? params.configToolBudget;
|
|
715
737
|
const resolvedToolBudget = validateToolBudgetConfig(toolBudgetInput, s.toolBudget ? "toolBudget" : a.toolBudget ? "agent.toolBudget" : "config.toolBudget");
|
|
716
738
|
if (resolvedToolBudget.error) throw new AsyncStartValidationError(resolvedToolBudget.error);
|
|
739
|
+
const resolvedToolTimeout = resolveToolTimeoutMs({
|
|
740
|
+
callValue: params.callToolTimeoutMs,
|
|
741
|
+
agentValue: a.defaultToolTimeoutMs,
|
|
742
|
+
configValue: params.configToolTimeoutMs,
|
|
743
|
+
envValue: params.toolTimeoutMsEnv ?? toolTimeoutFromEnv(),
|
|
744
|
+
});
|
|
745
|
+
if (resolvedToolTimeout.error) throw new AsyncStartValidationError(resolvedToolTimeout.error);
|
|
717
746
|
const stepCwd = resolveChildCwd(runnerCwd, s.cwd);
|
|
718
747
|
const instructionCwd = behaviorCwd ?? stepCwd;
|
|
719
748
|
const readExistenceCwd = behaviorCwd ? stepCwd : instructionCwd;
|
|
@@ -788,7 +817,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
788
817
|
const launchResolvedExtensions = externalRunner ? undefined : projectLaunchResolvedChildExtensions(toolPlan);
|
|
789
818
|
const permissionRules = resolvePermissionRules(ctx.permissions, a.permissions);
|
|
790
819
|
if (externalRunner && permissionRules) {
|
|
791
|
-
throw new AsyncStartValidationError(`Agent '${a.name}' uses runner.type='
|
|
820
|
+
throw new AsyncStartValidationError(`Agent '${a.name}' uses runner.type='${externalRunnerType}', which cannot enforce native Pi child permission rules.`);
|
|
792
821
|
}
|
|
793
822
|
return {
|
|
794
823
|
parentSessionId: ctx.parentSessionId ?? ctx.currentSessionId,
|
|
@@ -827,6 +856,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
827
856
|
sessionFile,
|
|
828
857
|
maxSubagentDepth: resolveChildMaxSubagentDepth(maxSubagentDepth, a.maxSubagentDepth),
|
|
829
858
|
timeoutMs: a.defaultTimeoutMs ?? DEFAULT_ASYNC_TIMEOUT_MS,
|
|
859
|
+
toolTimeoutMs: resolvedToolTimeout.toolTimeoutMs,
|
|
830
860
|
waitToolEnabled: params.waitToolEnabled,
|
|
831
861
|
effectiveAcceptance: resolveEffectiveAcceptance({
|
|
832
862
|
explicit: s.acceptance,
|
|
@@ -844,6 +874,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
844
874
|
...(s.outputSchema ? { structuredOutputSchema: s.outputSchema } : {}),
|
|
845
875
|
...(s.outputSchema ? { structuredOutput: createStructuredOutputRuntime(s.outputSchema, path.join(asyncDir, "structured-output")) } : {}),
|
|
846
876
|
...(resolvedToolBudget.budget ? { toolBudget: resolvedToolBudget.budget } : {}),
|
|
877
|
+
...(s.worktree ? { worktree: true } : {}),
|
|
847
878
|
};
|
|
848
879
|
};
|
|
849
880
|
|
|
@@ -862,9 +893,6 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
862
893
|
|
|
863
894
|
try {
|
|
864
895
|
const builtSteps = chain.map((s, stepIndex) => {
|
|
865
|
-
if (isCheckpointStep(s)) {
|
|
866
|
-
return { checkpoint: s.checkpoint, ...(s.message ? { message: s.message } : {}), phase: s.phase, label: s.label };
|
|
867
|
-
}
|
|
868
896
|
if (isParallelStep(s)) {
|
|
869
897
|
const parallelBehaviors = s.parallel.map((task) => {
|
|
870
898
|
const agent = agents.find((candidate) => candidate.name === task.agent)!;
|
|
@@ -930,8 +958,17 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
|
|
|
930
958
|
...(s.gateOn ? { gateOn: s.gateOn } : {}),
|
|
931
959
|
};
|
|
932
960
|
}
|
|
961
|
+
const sequential = s as SequentialStep;
|
|
962
|
+
let behaviorCwd: string | undefined;
|
|
963
|
+
if (sequential.worktree) {
|
|
964
|
+
try {
|
|
965
|
+
behaviorCwd = resolveExpectedWorktreeAgentCwd(runnerCwd, `${id}-s${stepIndex}`, 0, worktreeBaseDir);
|
|
966
|
+
} catch {
|
|
967
|
+
behaviorCwd = undefined;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
933
970
|
const staticStep = nextFlatStep();
|
|
934
|
-
return buildSeqStep(
|
|
971
|
+
return buildSeqStep(sequential, staticStep.sessionFile, behaviorCwd, false, undefined, staticStep.index, undefined, `chain[${stepIndex}]`);
|
|
935
972
|
});
|
|
936
973
|
const steps = params.attachRoot
|
|
937
974
|
? [{
|
|
@@ -1000,7 +1037,6 @@ export function executeAsyncChain(
|
|
|
1000
1037
|
const resultMode = params.resultMode ?? "chain";
|
|
1001
1038
|
const acceptanceErrors = validateExecutionAcceptance({
|
|
1002
1039
|
chain: chain.map((step) => {
|
|
1003
|
-
if (isCheckpointStep(step)) return {};
|
|
1004
1040
|
if (isParallelStep(step)) return { parallel: step.parallel };
|
|
1005
1041
|
if (isDynamicParallelStep(step)) return { acceptance: step.acceptance, parallel: step.parallel };
|
|
1006
1042
|
return { acceptance: step.acceptance };
|
|
@@ -1050,6 +1086,9 @@ export function executeAsyncChain(
|
|
|
1050
1086
|
asyncDir,
|
|
1051
1087
|
toolBudget: params.toolBudget,
|
|
1052
1088
|
configToolBudget: params.configToolBudget,
|
|
1089
|
+
callToolTimeoutMs: params.callToolTimeoutMs,
|
|
1090
|
+
configToolTimeoutMs: params.configToolTimeoutMs,
|
|
1091
|
+
toolTimeoutMsEnv: params.toolTimeoutMsEnv ?? toolTimeoutFromEnv(),
|
|
1053
1092
|
capabilityCeiling,
|
|
1054
1093
|
});
|
|
1055
1094
|
if ("error" in built) {
|
|
@@ -1086,7 +1125,7 @@ export function executeAsyncChain(
|
|
|
1086
1125
|
{
|
|
1087
1126
|
id,
|
|
1088
1127
|
steps,
|
|
1089
|
-
resultPath: inheritedNestedRoute ? nestedResultsPath(inheritedNestedRoute.rootRunId, id) :
|
|
1128
|
+
resultPath: inheritedNestedRoute ? nestedResultsPath(inheritedNestedRoute.rootRunId, id) : resultFilePath(DIRS.results, id),
|
|
1090
1129
|
cwd: runnerCwd,
|
|
1091
1130
|
placeholder: "{previous}",
|
|
1092
1131
|
maxOutput,
|
|
@@ -1096,6 +1135,7 @@ export function executeAsyncChain(
|
|
|
1096
1135
|
sessionDir: sessionRoot ? path.join(sessionRoot, `async-${id}`) : undefined,
|
|
1097
1136
|
asyncDir,
|
|
1098
1137
|
sessionId: ctx.currentSessionId,
|
|
1138
|
+
completionOwnerId: ctx.completionOwnerId ?? currentCompletionOwnerId(),
|
|
1099
1139
|
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
1100
1140
|
piPackageRoot,
|
|
1101
1141
|
piArgv1: process.argv[1],
|
|
@@ -1222,6 +1262,7 @@ export function executeAsyncChain(
|
|
|
1222
1262
|
id,
|
|
1223
1263
|
pid: spawnResult.pid,
|
|
1224
1264
|
sessionId: ctx.currentSessionId,
|
|
1265
|
+
completionOwnerId: ctx.completionOwnerId ?? currentCompletionOwnerId(),
|
|
1225
1266
|
mode: resultMode,
|
|
1226
1267
|
agent: firstAgents[0],
|
|
1227
1268
|
agents: flatAgents,
|
|
@@ -1292,7 +1333,8 @@ export function executeAsyncSingle(
|
|
|
1292
1333
|
const task = params.task ?? "";
|
|
1293
1334
|
const acceptanceErrors = validateAcceptanceInput(params.acceptance);
|
|
1294
1335
|
if (acceptanceErrors.length > 0) return formatAsyncStartError("single", acceptanceErrors.join(" "));
|
|
1295
|
-
const externalRunner = agentConfig.runner?.type === "external-cli";
|
|
1336
|
+
const externalRunner = agentConfig.runner?.type === "external-cli" || agentConfig.runner?.type === "external-job";
|
|
1337
|
+
const externalRunnerType = agentConfig.runner?.type;
|
|
1296
1338
|
const permissionRules = resolvePermissionRules(ctx.permissions, agentConfig.permissions);
|
|
1297
1339
|
if (externalRunner) {
|
|
1298
1340
|
const unsupported: string[] = [];
|
|
@@ -1304,7 +1346,7 @@ export function executeAsyncSingle(
|
|
|
1304
1346
|
if (params.context === "fork") unsupported.push("fork context");
|
|
1305
1347
|
if ((params.skills?.length ?? 0) > 0) unsupported.push("skills");
|
|
1306
1348
|
if (permissionRules) unsupported.push("native Pi child permissions");
|
|
1307
|
-
if (unsupported.length > 0) return formatAsyncStartError("single", `Agent '${agentConfig.name}' uses runner.type='
|
|
1349
|
+
if (unsupported.length > 0) return formatAsyncStartError("single", `Agent '${agentConfig.name}' uses runner.type='${externalRunnerType}' and does not support: ${unsupported.join(", ")}.`);
|
|
1308
1350
|
}
|
|
1309
1351
|
const capabilityCeiling = intersectSubagentCapabilityCeilings(params.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(ctx.currentSessionId), decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]));
|
|
1310
1352
|
try {
|
|
@@ -1313,6 +1355,10 @@ export function executeAsyncSingle(
|
|
|
1313
1355
|
return formatAsyncStartError("single", error instanceof Error ? error.message : String(error));
|
|
1314
1356
|
}
|
|
1315
1357
|
const runnerCwd = resolveChildCwd(ctx.cwd, cwd);
|
|
1358
|
+
const instructionCwd = params.worktree === true
|
|
1359
|
+
? resolveExpectedWorktreeAgentCwd(runnerCwd, `${id}-s0`, 0, worktreeBaseDir)
|
|
1360
|
+
: runnerCwd;
|
|
1361
|
+
const readExistenceCwd = params.worktree === true ? runnerCwd : instructionCwd;
|
|
1316
1362
|
const skillNames = params.skills ?? agentConfig.skills ?? [];
|
|
1317
1363
|
const availableModels = params.availableModels;
|
|
1318
1364
|
const { resolved: resolvedSkills, missing: missingSkills } = resolveSkillsWithFallback(
|
|
@@ -1354,7 +1400,7 @@ export function executeAsyncSingle(
|
|
|
1354
1400
|
}
|
|
1355
1401
|
|
|
1356
1402
|
const effectiveOutput = normalizeSingleOutputOverride(params.output, agentConfig.output);
|
|
1357
|
-
const outputPath = resolveSingleOutputPath(effectiveOutput, ctx.cwd,
|
|
1403
|
+
const outputPath = resolveSingleOutputPath(effectiveOutput, ctx.cwd, instructionCwd, params.outputBaseDir ?? (artifactsDir ? path.join(artifactsDir, "outputs", id) : undefined));
|
|
1358
1404
|
systemPrompt = injectOutputPathSystemPrompt(systemPrompt, outputPath, agentConfig);
|
|
1359
1405
|
const outputMode = params.outputMode ?? "inline";
|
|
1360
1406
|
const validationError = validateFileOnlyOutputMode(outputMode, outputPath, `Async single run (${agent})`);
|
|
@@ -1363,7 +1409,7 @@ export function executeAsyncSingle(
|
|
|
1363
1409
|
// Reads: caller override > agent defaultReads > none. `~`/`~/` expand to home;
|
|
1364
1410
|
// absolute paths pass through; relative paths resolve against the child cwd.
|
|
1365
1411
|
const reads = params.reads !== undefined ? params.reads : agentConfig.defaultReads ?? false;
|
|
1366
|
-
const readPaths = Array.isArray(reads) ? resolveExistingReadPaths(reads,
|
|
1412
|
+
const readPaths = Array.isArray(reads) ? resolveExistingReadPaths(reads, readExistenceCwd) : [];
|
|
1367
1413
|
const readsInstruction = readPaths.length > 0
|
|
1368
1414
|
? `[Read from: ${readPaths.join(", ")}]\n\n`
|
|
1369
1415
|
: "";
|
|
@@ -1384,17 +1430,27 @@ export function executeAsyncSingle(
|
|
|
1384
1430
|
? deadlineAt - Date.now()
|
|
1385
1431
|
: params.timeoutMs;
|
|
1386
1432
|
if (timeoutMs !== undefined && timeoutMs <= 0) return formatAsyncStartError("single", "The source run's absolute deadline expired before recovery could launch.");
|
|
1433
|
+
const resolvedToolTimeout = resolveToolTimeoutMs({
|
|
1434
|
+
callValue: params.toolTimeoutMs,
|
|
1435
|
+
agentValue: agentConfig.defaultToolTimeoutMs,
|
|
1436
|
+
configValue: params.configToolTimeoutMs,
|
|
1437
|
+
envValue: params.toolTimeoutMsEnv ?? toolTimeoutFromEnv(),
|
|
1438
|
+
});
|
|
1439
|
+
if (resolvedToolTimeout.error) return formatAsyncStartError("single", resolvedToolTimeout.error);
|
|
1440
|
+
const toolTimeoutMs = resolvedToolTimeout.toolTimeoutMs;
|
|
1387
1441
|
const initialTurnBudget = params.turnBudget ? initialTurnBudgetState(params.turnBudget) : undefined;
|
|
1388
1442
|
const initialUsageBudget = usageBudgetState(params.usageBudget, undefined);
|
|
1389
1443
|
const resolvedSessionDir = params.sessionDir ?? (sessionRoot ? path.join(sessionRoot, `async-${id}`) : undefined);
|
|
1390
1444
|
const structuredOutput = params.structuredOutputSchema
|
|
1391
1445
|
? createStructuredOutputRuntime(params.structuredOutputSchema, path.join(asyncDir, "structured-output"))
|
|
1392
1446
|
: undefined;
|
|
1393
|
-
const modelCandidates =
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1447
|
+
const modelCandidates = externalRunner
|
|
1448
|
+
? []
|
|
1449
|
+
: buildModelCandidates(primaryModel, agentConfig.fallbackModels, availableModels, ctx.currentModelProvider, { scope: ctx.modelScope })
|
|
1450
|
+
.flatMap((candidate) => {
|
|
1451
|
+
const resolved = applyThinkingSuffix(candidate, effectiveThinking, params.thinkingOverride !== undefined);
|
|
1452
|
+
return resolved ? [resolved] : [];
|
|
1453
|
+
});
|
|
1398
1454
|
const effectiveSystemPrompt = appendTurnBudgetSystemPrompt(systemPrompt, params.turnBudget);
|
|
1399
1455
|
const toolPlan = resolvePiLaunchToolPlan({
|
|
1400
1456
|
tools: agentConfig.tools,
|
|
@@ -1529,11 +1585,12 @@ export function executeAsyncSingle(
|
|
|
1529
1585
|
...(structuredOutput ? { structuredOutput } : {}),
|
|
1530
1586
|
...(params.structuredOutputSchema ? { structuredOutputSchema: params.structuredOutputSchema } : {}),
|
|
1531
1587
|
...(resolvedToolBudget.budget ? { toolBudget: resolvedToolBudget.budget } : {}),
|
|
1588
|
+
...(params.worktree === true ? { worktree: true } : {}),
|
|
1532
1589
|
},
|
|
1533
1590
|
],
|
|
1534
1591
|
resultPath: params.parentWorkflowRunId !== undefined && params.revivalLease !== undefined
|
|
1535
1592
|
? workflowAwaitedAsyncResultPath(asyncDir)
|
|
1536
|
-
: inheritedNestedRoute ? nestedResultsPath(inheritedNestedRoute.rootRunId, id) :
|
|
1593
|
+
: inheritedNestedRoute ? nestedResultsPath(inheritedNestedRoute.rootRunId, id) : resultFilePath(DIRS.results, id),
|
|
1537
1594
|
cwd: runnerCwd,
|
|
1538
1595
|
placeholder: "{previous}",
|
|
1539
1596
|
maxOutput,
|
|
@@ -1543,6 +1600,7 @@ export function executeAsyncSingle(
|
|
|
1543
1600
|
sessionDir: resolvedSessionDir,
|
|
1544
1601
|
asyncDir,
|
|
1545
1602
|
sessionId: ctx.currentSessionId,
|
|
1603
|
+
completionOwnerId: ctx.completionOwnerId ?? currentCompletionOwnerId(),
|
|
1546
1604
|
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
1547
1605
|
piPackageRoot,
|
|
1548
1606
|
piArgv1: process.argv[1],
|
|
@@ -1552,6 +1610,7 @@ export function executeAsyncSingle(
|
|
|
1552
1610
|
controlConfig,
|
|
1553
1611
|
timeoutMs,
|
|
1554
1612
|
deadlineAt,
|
|
1613
|
+
toolTimeoutMs,
|
|
1555
1614
|
turnBudget: params.turnBudget,
|
|
1556
1615
|
toolBudget: params.toolBudget,
|
|
1557
1616
|
usageBudget: params.usageBudget,
|
|
@@ -1635,6 +1694,7 @@ export function executeAsyncSingle(
|
|
|
1635
1694
|
id,
|
|
1636
1695
|
pid: spawnResult.pid,
|
|
1637
1696
|
sessionId: ctx.currentSessionId,
|
|
1697
|
+
completionOwnerId: ctx.completionOwnerId ?? currentCompletionOwnerId(),
|
|
1638
1698
|
mode: "single",
|
|
1639
1699
|
agent,
|
|
1640
1700
|
task: task?.trim() ? PROMPT_REDACTED : undefined,
|