pi-subagents 0.28.0 → 0.30.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 +31 -0
- package/README.md +26 -62
- package/package.json +1 -1
- package/skills/pi-subagents/SKILL.md +29 -35
- package/src/agents/agent-management.ts +29 -22
- package/src/agents/agent-selection.ts +2 -0
- package/src/agents/agent-serializer.ts +5 -10
- package/src/agents/agents.ts +339 -47
- package/src/agents/chain-serializer.ts +4 -9
- package/src/agents/proactive-skills.ts +191 -0
- package/src/extension/doctor.ts +4 -3
- package/src/extension/fanout-child.ts +1 -3
- package/src/extension/index.ts +6 -9
- package/src/extension/schemas.ts +63 -26
- package/src/intercom/intercom-bridge.ts +11 -1
- package/src/intercom/result-intercom.ts +0 -5
- package/src/runs/background/async-execution.ts +186 -74
- package/src/runs/background/async-resume.ts +53 -5
- package/src/runs/background/async-status.ts +4 -1
- package/src/runs/background/chain-append.ts +282 -0
- package/src/runs/background/chain-root-attachment.ts +161 -0
- package/src/runs/background/run-status.ts +2 -7
- package/src/runs/background/subagent-runner.ts +160 -219
- package/src/runs/foreground/chain-execution.ts +62 -58
- package/src/runs/foreground/execution.ts +39 -343
- package/src/runs/foreground/subagent-executor.ts +316 -111
- package/src/runs/shared/acceptance.ts +605 -22
- package/src/runs/shared/chain-outputs.ts +23 -8
- package/src/runs/shared/completion-guard.ts +3 -26
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/model-fallback.ts +38 -0
- package/src/runs/shared/parallel-utils.ts +13 -10
- package/src/runs/shared/pi-args.ts +3 -2
- package/src/runs/shared/subagent-control.ts +8 -11
- package/src/runs/shared/subagent-prompt-runtime.ts +3 -2
- package/src/runs/shared/workflow-graph.ts +2 -6
- package/src/shared/atomic-json.ts +68 -11
- package/src/shared/settings.ts +1 -0
- package/src/shared/types.ts +20 -49
- package/src/shared/utils.ts +2 -8
- package/src/slash/slash-bridge.ts +3 -1
- package/src/slash/slash-commands.ts +1 -1
- package/src/tui/render.ts +14 -29
- package/src/runs/shared/acceptance-contract.ts +0 -318
- package/src/runs/shared/acceptance-evaluation.ts +0 -221
- package/src/runs/shared/acceptance-finalization.ts +0 -173
- package/src/runs/shared/acceptance-reports.ts +0 -127
|
@@ -13,7 +13,7 @@ import { handleManagementAction } from "../../agents/agent-management.ts";
|
|
|
13
13
|
import { buildDoctorReport } from "../../extension/doctor.ts";
|
|
14
14
|
import { clearPendingForegroundControlNotices } from "../../extension/control-notices.ts";
|
|
15
15
|
import { runSync } from "./execution.ts";
|
|
16
|
-
import { resolveModelCandidate } from "../shared/model-fallback.ts";
|
|
16
|
+
import { resolveModelCandidate, resolveSubagentModelOverride } from "../shared/model-fallback.ts";
|
|
17
17
|
import { aggregateParallelOutputs } from "../shared/parallel-utils.ts";
|
|
18
18
|
import { recordRun } from "../shared/run-history.ts";
|
|
19
19
|
import {
|
|
@@ -31,7 +31,9 @@ import {
|
|
|
31
31
|
type StepOverrides,
|
|
32
32
|
} from "../../shared/settings.ts";
|
|
33
33
|
import { discoverAvailableSkills, normalizeSkillInput } from "../../agents/skills.ts";
|
|
34
|
-
import { executeAsyncChain, executeAsyncSingle, formatAsyncStartedMessage, isAsyncAvailable } from "../background/async-execution.ts";
|
|
34
|
+
import { buildAsyncRunnerSteps, executeAsyncChain, executeAsyncSingle, formatAsyncStartedMessage, isAsyncAvailable } from "../background/async-execution.ts";
|
|
35
|
+
import { enqueueChainAppendRequest, readPendingChainAppendRequests, runnerStepOutputNames } from "../background/chain-append.ts";
|
|
36
|
+
import { ChainOutputValidationError, validateChainOutputBindingsWithContext } from "../shared/chain-outputs.ts";
|
|
35
37
|
import { createForkContextResolver } from "../../shared/fork-context.ts";
|
|
36
38
|
import { resolveCurrentSessionId } from "../../shared/session-identity.ts";
|
|
37
39
|
import { applyIntercomBridgeToAgent, INTERCOM_BRIDGE_MARKER, resolveIntercomBridge, resolveIntercomSessionTarget, resolveSubagentIntercomTarget, type IntercomBridgeState } from "../../intercom/intercom-bridge.ts";
|
|
@@ -47,13 +49,13 @@ import {
|
|
|
47
49
|
resolveSubagentResultStatus,
|
|
48
50
|
stripDetailsOutputsForIntercomReceipt,
|
|
49
51
|
} from "../../intercom/result-intercom.ts";
|
|
50
|
-
import { buildRevivedAsyncTask, resolveAsyncResumeTarget } from "../background/async-resume.ts";
|
|
52
|
+
import { buildRevivedAsyncTask, interruptLiveAsyncResumeTarget, resolveAsyncResumeTarget } from "../background/async-resume.ts";
|
|
53
|
+
import { resolveAsyncRootResultPath } from "../background/chain-root-attachment.ts";
|
|
51
54
|
import { createNestedRoute, readNestedControlResults, resolveInheritedNestedRouteFromEnv, resolveNestedAsyncDir, resolveNestedParentAddressFromEnv, updateForegroundNestedProjection, writeNestedControlRequest, writeNestedEvent, type NestedRunResolutionScope } from "../shared/nested-events.ts";
|
|
52
55
|
import { resolveSubagentRunId, type ResolvedSubagentRunId } from "../background/run-id-resolver.ts";
|
|
53
56
|
import { formatNestedRunStatusLines } from "../shared/nested-render.ts";
|
|
54
57
|
import { inspectSubagentStatus } from "../background/run-status.ts";
|
|
55
58
|
import { applyForceTopLevelAsyncOverride } from "../background/top-level-async.ts";
|
|
56
|
-
import { validateAcceptanceInput } from "../shared/acceptance.ts";
|
|
57
59
|
import {
|
|
58
60
|
cleanupWorktrees,
|
|
59
61
|
createWorktrees,
|
|
@@ -80,7 +82,9 @@ import {
|
|
|
80
82
|
type SingleResult,
|
|
81
83
|
type SubagentRunMode,
|
|
82
84
|
type SubagentState,
|
|
85
|
+
ASYNC_DIR,
|
|
83
86
|
DEFAULT_ARTIFACT_CONFIG,
|
|
87
|
+
RESULTS_DIR,
|
|
84
88
|
SUBAGENT_ACTIONS,
|
|
85
89
|
SUBAGENT_CONTROL_EVENT,
|
|
86
90
|
SUBAGENT_CONTROL_INTERCOM_EVENT,
|
|
@@ -121,8 +125,6 @@ export interface SubagentParamsLike {
|
|
|
121
125
|
chain?: ChainStep[];
|
|
122
126
|
tasks?: TaskParam[];
|
|
123
127
|
concurrency?: number;
|
|
124
|
-
timeoutMs?: number;
|
|
125
|
-
maxRuntimeMs?: number;
|
|
126
128
|
worktree?: boolean;
|
|
127
129
|
context?: "fresh" | "fork";
|
|
128
130
|
async?: boolean;
|
|
@@ -153,6 +155,7 @@ interface ExecutorDeps {
|
|
|
153
155
|
expandTilde: (p: string) => string;
|
|
154
156
|
discoverAgents: (cwd: string, scope: AgentScope) => { agents: AgentConfig[] };
|
|
155
157
|
allowMutatingManagementActions?: boolean;
|
|
158
|
+
kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean;
|
|
156
159
|
}
|
|
157
160
|
|
|
158
161
|
interface ExecutionContextData {
|
|
@@ -171,7 +174,6 @@ interface ExecutionContextData {
|
|
|
171
174
|
artifactsDir: string;
|
|
172
175
|
backgroundRequestedWhileClarifying: boolean;
|
|
173
176
|
effectiveAsync: boolean;
|
|
174
|
-
foregroundTimeoutMs?: number;
|
|
175
177
|
controlConfig: ResolvedControlConfig;
|
|
176
178
|
intercomBridge: IntercomBridgeState;
|
|
177
179
|
nestedRoute?: NestedRouteInfo;
|
|
@@ -253,7 +255,7 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
|
|
|
253
255
|
children: input.results.map((result, index) => ({
|
|
254
256
|
agent: result.agent,
|
|
255
257
|
index,
|
|
256
|
-
status: resolveSubagentResultStatus({ exitCode: result.exitCode, interrupted: result.interrupted, detached: result.detached
|
|
258
|
+
status: resolveSubagentResultStatus({ exitCode: result.exitCode, interrupted: result.interrupted, detached: result.detached }),
|
|
257
259
|
...(result.sessionFile ? { sessionFile: result.sessionFile } : {}),
|
|
258
260
|
})),
|
|
259
261
|
});
|
|
@@ -321,7 +323,7 @@ function isExactResumeError(error: unknown, source: "async" | "foreground", requ
|
|
|
321
323
|
return new RegExp(`\\b${source} run '${escapeRegExp(requested)}'`, "i").test(error.message);
|
|
322
324
|
}
|
|
323
325
|
|
|
324
|
-
function resolveResumeTarget(params: SubagentParamsLike, state: SubagentState): ResumeSourceTarget {
|
|
326
|
+
function resolveResumeTarget(params: SubagentParamsLike, state: SubagentState, options: { asyncRequireSessionFile?: boolean } = {}): ResumeSourceTarget {
|
|
325
327
|
const requested = (params.id ?? params.runId)?.trim() ?? "";
|
|
326
328
|
let foregroundTarget: ForegroundResumeSourceTarget | undefined;
|
|
327
329
|
let foregroundError: unknown;
|
|
@@ -335,7 +337,7 @@ function resolveResumeTarget(params: SubagentParamsLike, state: SubagentState):
|
|
|
335
337
|
foregroundError = error;
|
|
336
338
|
}
|
|
337
339
|
try {
|
|
338
|
-
asyncTarget = { source: "async", ...resolveAsyncResumeTarget(params) };
|
|
340
|
+
asyncTarget = { source: "async", ...resolveAsyncResumeTarget(params, {}, { requireSessionFile: options.asyncRequireSessionFile }) };
|
|
339
341
|
} catch (error) {
|
|
340
342
|
asyncError = error;
|
|
341
343
|
}
|
|
@@ -406,7 +408,7 @@ function emitControlNotification(input: {
|
|
|
406
408
|
}
|
|
407
409
|
}
|
|
408
410
|
|
|
409
|
-
function interruptAsyncRun(state: SubagentState, runId: string | undefined): AgentToolResult<Details> | null {
|
|
411
|
+
function interruptAsyncRun(state: SubagentState, runId: string | undefined, kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean): AgentToolResult<Details> | null {
|
|
410
412
|
const target = getAsyncInterruptTarget(state, runId);
|
|
411
413
|
if (!target) return null;
|
|
412
414
|
const status = readStatus(target.asyncDir);
|
|
@@ -418,7 +420,7 @@ function interruptAsyncRun(state: SubagentState, runId: string | undefined): Age
|
|
|
418
420
|
};
|
|
419
421
|
}
|
|
420
422
|
try {
|
|
421
|
-
process.kill(status.pid, ASYNC_INTERRUPT_SIGNAL);
|
|
423
|
+
(kill ?? process.kill)(status.pid, ASYNC_INTERRUPT_SIGNAL);
|
|
422
424
|
const tracked = state.asyncJobs.get(target.asyncId);
|
|
423
425
|
if (tracked) {
|
|
424
426
|
tracked.activityState = undefined;
|
|
@@ -438,6 +440,186 @@ function interruptAsyncRun(state: SubagentState, runId: string | undefined): Age
|
|
|
438
440
|
}
|
|
439
441
|
}
|
|
440
442
|
|
|
443
|
+
function duplicateNames(names: string[]): string[] {
|
|
444
|
+
const seen = new Set<string>();
|
|
445
|
+
const duplicates = new Set<string>();
|
|
446
|
+
for (const name of names) {
|
|
447
|
+
if (seen.has(name)) duplicates.add(name);
|
|
448
|
+
else seen.add(name);
|
|
449
|
+
}
|
|
450
|
+
return [...duplicates];
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function appendStepToAsyncChain(input: {
|
|
454
|
+
params: SubagentParamsLike;
|
|
455
|
+
requestCwd: string;
|
|
456
|
+
ctx: ExtensionContext;
|
|
457
|
+
deps: ExecutorDeps;
|
|
458
|
+
}): AgentToolResult<Details> {
|
|
459
|
+
const targetRunId = input.params.id ?? input.params.runId;
|
|
460
|
+
if (!targetRunId) {
|
|
461
|
+
return {
|
|
462
|
+
content: [{ type: "text", text: "action='append-step' requires id." }],
|
|
463
|
+
isError: true,
|
|
464
|
+
details: { mode: "management", results: [] },
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
if (!input.params.chain || input.params.chain.length !== 1) {
|
|
468
|
+
return {
|
|
469
|
+
content: [{ type: "text", text: "action='append-step' requires chain with exactly one step." }],
|
|
470
|
+
isError: true,
|
|
471
|
+
details: { mode: "management", results: [] },
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
let resolved: ResolvedSubagentRunId | undefined;
|
|
476
|
+
try {
|
|
477
|
+
resolved = resolveSubagentRunId(targetRunId, { state: input.deps.state, nested: nestedResolutionScopeForExecutor(input.deps) });
|
|
478
|
+
} catch (error) {
|
|
479
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
480
|
+
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
481
|
+
}
|
|
482
|
+
if (!resolved) {
|
|
483
|
+
return {
|
|
484
|
+
content: [{ type: "text", text: `No async chain run found for '${targetRunId}'.` }],
|
|
485
|
+
isError: true,
|
|
486
|
+
details: { mode: "management", results: [] },
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
if (resolved.kind !== "async" || !resolved.location.asyncDir) {
|
|
490
|
+
return {
|
|
491
|
+
content: [{ type: "text", text: `Run '${resolved.id}' is not an append-capable async chain run.` }],
|
|
492
|
+
isError: true,
|
|
493
|
+
details: { mode: "management", results: [] },
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const status = readStatus(resolved.location.asyncDir);
|
|
498
|
+
if (!status) {
|
|
499
|
+
return {
|
|
500
|
+
content: [{ type: "text", text: `No async run status found for '${resolved.id}'.` }],
|
|
501
|
+
isError: true,
|
|
502
|
+
details: { mode: "management", results: [] },
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
if (status.mode !== "chain") {
|
|
506
|
+
return {
|
|
507
|
+
content: [{ type: "text", text: `Run '${resolved.id}' is ${status.mode}; only active chain runs accept appended steps.` }],
|
|
508
|
+
isError: true,
|
|
509
|
+
details: { mode: "management", results: [] },
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
if (status.state !== "running") {
|
|
513
|
+
return {
|
|
514
|
+
content: [{ type: "text", text: `Run '${resolved.id}' is ${status.state}; only running chain runs accept appended steps.` }],
|
|
515
|
+
isError: true,
|
|
516
|
+
details: { mode: "management", results: [] },
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
const stillInProgress = (status.steps ?? []).some((step) => step.status === "running" || step.status === "pending") || (status.pendingAppends ?? 0) > 0;
|
|
520
|
+
if (!stillInProgress) {
|
|
521
|
+
return {
|
|
522
|
+
content: [{ type: "text", text: `Run '${resolved.id}' has no running or pending chain steps left; append-step must target an in-progress chain.` }],
|
|
523
|
+
isError: true,
|
|
524
|
+
details: { mode: "management", results: [] },
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const pendingAppendRequests = readPendingChainAppendRequests(resolved.location.asyncDir);
|
|
529
|
+
const reservedOutputNames = new Set<string>([
|
|
530
|
+
...Object.keys(status.outputs ?? {}),
|
|
531
|
+
...(status.steps ?? []).map((step) => step.outputName).filter((name): name is string => Boolean(name)),
|
|
532
|
+
...pendingAppendRequests.flatMap((request) => runnerStepOutputNames(request.steps)),
|
|
533
|
+
]);
|
|
534
|
+
try {
|
|
535
|
+
validateChainOutputBindingsWithContext(input.params.chain, { maxItems: input.deps.config.chain?.dynamicFanout?.maxItems }, {
|
|
536
|
+
priorOutputNames: reservedOutputNames,
|
|
537
|
+
startStepIndex: status.chainStepCount ?? status.steps?.length ?? 0,
|
|
538
|
+
});
|
|
539
|
+
} catch (error) {
|
|
540
|
+
if (!(error instanceof ChainOutputValidationError)) throw error;
|
|
541
|
+
return {
|
|
542
|
+
content: [{ type: "text", text: `Cannot append step to run '${resolved.id}': ${error.message}` }],
|
|
543
|
+
isError: true,
|
|
544
|
+
details: { mode: "management", results: [] },
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const scope: AgentScope = resolveExecutionAgentScope(input.params.agentScope);
|
|
549
|
+
const agents = input.deps.discoverAgents(input.requestCwd, scope).agents;
|
|
550
|
+
const chainSkillInput = normalizeSkillInput(input.params.skill);
|
|
551
|
+
const chainSkills = chainSkillInput === false ? [] : (chainSkillInput ?? []);
|
|
552
|
+
const asyncCtx = {
|
|
553
|
+
pi: input.deps.pi,
|
|
554
|
+
cwd: input.ctx.cwd,
|
|
555
|
+
currentSessionId: resolveCurrentSessionId(input.ctx.sessionManager),
|
|
556
|
+
currentModelProvider: input.ctx.model?.provider,
|
|
557
|
+
currentModel: input.ctx.model,
|
|
558
|
+
};
|
|
559
|
+
const built = buildAsyncRunnerSteps(resolved.id, {
|
|
560
|
+
chain: wrapChainTasksForFork(input.params.chain, input.params.context),
|
|
561
|
+
task: input.params.task,
|
|
562
|
+
resultMode: "chain",
|
|
563
|
+
agents,
|
|
564
|
+
ctx: asyncCtx,
|
|
565
|
+
availableModels: input.ctx.modelRegistry.getAvailable().map(toModelInfo),
|
|
566
|
+
cwd: status.cwd ?? input.requestCwd,
|
|
567
|
+
chainSkills,
|
|
568
|
+
dynamicFanoutMaxItems: input.deps.config.chain?.dynamicFanout?.maxItems,
|
|
569
|
+
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
570
|
+
asyncDir: resolved.location.asyncDir,
|
|
571
|
+
validateOutputBindings: false,
|
|
572
|
+
});
|
|
573
|
+
if ("error" in built) {
|
|
574
|
+
return {
|
|
575
|
+
content: [{ type: "text", text: built.error }],
|
|
576
|
+
isError: true,
|
|
577
|
+
details: { mode: "management", results: [] },
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
const appendedOutputNames = runnerStepOutputNames(built.steps);
|
|
581
|
+
const duplicateAppendedOutputs = duplicateNames(appendedOutputNames);
|
|
582
|
+
if (duplicateAppendedOutputs.length > 0) {
|
|
583
|
+
return {
|
|
584
|
+
content: [{ type: "text", text: `Cannot append step to run '${resolved.id}': duplicate output name in appended step: ${duplicateAppendedOutputs.join(", ")}.` }],
|
|
585
|
+
isError: true,
|
|
586
|
+
details: { mode: "management", results: [] },
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
const pendingOutputNames = new Set(pendingAppendRequests.flatMap((request) => runnerStepOutputNames(request.steps)));
|
|
590
|
+
const pendingDuplicateOutputs = appendedOutputNames.filter((name) => pendingOutputNames.has(name));
|
|
591
|
+
if (pendingDuplicateOutputs.length > 0) {
|
|
592
|
+
return {
|
|
593
|
+
content: [{ type: "text", text: `Cannot append step to run '${resolved.id}': output name already belongs to a pending append: ${pendingDuplicateOutputs.join(", ")}.` }],
|
|
594
|
+
isError: true,
|
|
595
|
+
details: { mode: "management", results: [] },
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
try {
|
|
600
|
+
const result = enqueueChainAppendRequest({
|
|
601
|
+
asyncDir: resolved.location.asyncDir,
|
|
602
|
+
runId: resolved.id,
|
|
603
|
+
steps: built.steps,
|
|
604
|
+
});
|
|
605
|
+
const stepText = built.steps.length === 1 ? "step" : "steps";
|
|
606
|
+
return {
|
|
607
|
+
content: [{
|
|
608
|
+
type: "text",
|
|
609
|
+
text: `Append queued for chain run ${resolved.id}: ${built.steps.length} ${stepText}. It becomes eligible after the chain's already-queued steps finish. Pending appends: ${result.pendingCount}.`,
|
|
610
|
+
}],
|
|
611
|
+
details: { mode: "management", results: [], asyncId: resolved.id, asyncDir: resolved.location.asyncDir },
|
|
612
|
+
};
|
|
613
|
+
} catch (error) {
|
|
614
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
615
|
+
return {
|
|
616
|
+
content: [{ type: "text", text: `Failed to append step to chain run ${resolved.id}: ${message}` }],
|
|
617
|
+
isError: true,
|
|
618
|
+
details: { mode: "management", results: [] },
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
441
623
|
function nestedRunSessionFile(run: NestedRunSummary): string | undefined {
|
|
442
624
|
return run.sessionFile ?? (run.steps?.length === 1 ? run.steps[0]?.sessionFile : undefined);
|
|
443
625
|
}
|
|
@@ -558,7 +740,8 @@ async function resumeAsyncRun(input: {
|
|
|
558
740
|
deps: ExecutorDeps;
|
|
559
741
|
}): Promise<AgentToolResult<Details>> {
|
|
560
742
|
const followUp = (input.params.message ?? input.params.task ?? "").trim();
|
|
561
|
-
|
|
743
|
+
const attachChain = (input.params.chain?.length ?? 0) > 0 ? input.params.chain as ChainStep[] : undefined;
|
|
744
|
+
if (!followUp && !attachChain) {
|
|
562
745
|
return {
|
|
563
746
|
content: [{ type: "text", text: "action='resume' requires message." }],
|
|
564
747
|
isError: true,
|
|
@@ -572,6 +755,13 @@ async function resumeAsyncRun(input: {
|
|
|
572
755
|
const requestedId = input.params.id ?? input.params.runId;
|
|
573
756
|
const resolved = requestedId ? resolveSubagentRunId(requestedId, { state: input.deps.state, nested: nestedResolutionScopeForExecutor(input.deps) }) : undefined;
|
|
574
757
|
if (resolved?.kind === "nested") {
|
|
758
|
+
if (attachChain) {
|
|
759
|
+
return {
|
|
760
|
+
content: [{ type: "text", text: "Attaching a running subagent as a chain root is currently available for top-level async runs only." }],
|
|
761
|
+
isError: true,
|
|
762
|
+
details: { mode: "management", results: [] },
|
|
763
|
+
};
|
|
764
|
+
}
|
|
575
765
|
if (resolved.match.run.state === "running" || resolved.match.run.state === "queued") {
|
|
576
766
|
return resumeLiveNestedRun({ target: resolved, message: followUp });
|
|
577
767
|
}
|
|
@@ -581,14 +771,26 @@ async function resumeAsyncRun(input: {
|
|
|
581
771
|
];
|
|
582
772
|
target = resolveNestedResumeTarget(resolved, trustedSessionRoots);
|
|
583
773
|
} else {
|
|
584
|
-
target = resolveResumeTarget(input.params, input.deps.state);
|
|
774
|
+
target = resolveResumeTarget(input.params, input.deps.state, { asyncRequireSessionFile: !attachChain });
|
|
585
775
|
}
|
|
586
776
|
} catch (error) {
|
|
587
777
|
const message = error instanceof Error ? error.message : String(error);
|
|
588
778
|
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
589
779
|
}
|
|
590
780
|
|
|
591
|
-
if (target.kind === "live") {
|
|
781
|
+
if (target.kind === "live" && !attachChain) {
|
|
782
|
+
const interrupt = interruptLiveAsyncResumeTarget({
|
|
783
|
+
target,
|
|
784
|
+
state: input.deps.state,
|
|
785
|
+
kill: input.deps.kill,
|
|
786
|
+
});
|
|
787
|
+
if (!interrupt.ok) {
|
|
788
|
+
return {
|
|
789
|
+
content: [{ type: "text", text: interrupt.message }],
|
|
790
|
+
isError: true,
|
|
791
|
+
details: { mode: "management", results: [] },
|
|
792
|
+
};
|
|
793
|
+
}
|
|
592
794
|
const delivered = await deliverSubagentIntercomMessageEvent(
|
|
593
795
|
input.deps.pi.events,
|
|
594
796
|
target.intercomTarget,
|
|
@@ -598,7 +800,7 @@ async function resumeAsyncRun(input: {
|
|
|
598
800
|
);
|
|
599
801
|
if (delivered) {
|
|
600
802
|
return {
|
|
601
|
-
content: [{ type: "text", text: [`
|
|
803
|
+
content: [{ type: "text", text: [`Interrupted live async child, then delivered follow-up.`, `Run: ${target.runId}`, `Intercom target: ${target.intercomTarget}`].join("\n") }],
|
|
602
804
|
details: { mode: "management", results: [] },
|
|
603
805
|
};
|
|
604
806
|
}
|
|
@@ -641,6 +843,73 @@ async function resumeAsyncRun(input: {
|
|
|
641
843
|
};
|
|
642
844
|
}
|
|
643
845
|
|
|
846
|
+
if (attachChain) {
|
|
847
|
+
if (target.source !== "async") {
|
|
848
|
+
return {
|
|
849
|
+
content: [{ type: "text", text: "Attaching a running subagent as a chain root is currently available for async runs only." }],
|
|
850
|
+
isError: true,
|
|
851
|
+
details: { mode: "management", results: [] },
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
if (!isAsyncAvailable()) {
|
|
855
|
+
return {
|
|
856
|
+
content: [{ type: "text", text: "Async mode requires upstream jiti for TypeScript execution but it could not be found. Ensure the pi-subagents package dependencies are installed." }],
|
|
857
|
+
isError: true,
|
|
858
|
+
details: { mode: "chain", results: [] },
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
const runId = randomUUID().slice(0, 8);
|
|
862
|
+
const artifactConfig: ArtifactConfig = { ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false };
|
|
863
|
+
const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
864
|
+
const chain = wrapChainTasksForFork(attachChain, input.params.context);
|
|
865
|
+
const normalized = normalizeSkillInput(input.params.skill);
|
|
866
|
+
const result = executeAsyncChain(runId, {
|
|
867
|
+
chain,
|
|
868
|
+
task: (input.params.task ?? followUp) || undefined,
|
|
869
|
+
attachRoot: {
|
|
870
|
+
runId: target.runId,
|
|
871
|
+
asyncDir: target.asyncDir ?? path.join(ASYNC_DIR, target.runId),
|
|
872
|
+
resultPath: resolveAsyncRootResultPath(RESULTS_DIR, target.runId),
|
|
873
|
+
index: target.index,
|
|
874
|
+
agent: target.agent,
|
|
875
|
+
label: `Attached ${target.runId}`,
|
|
876
|
+
},
|
|
877
|
+
agents,
|
|
878
|
+
ctx: {
|
|
879
|
+
pi: input.deps.pi,
|
|
880
|
+
cwd: input.requestCwd,
|
|
881
|
+
currentSessionId: input.deps.state.currentSessionId,
|
|
882
|
+
currentModelProvider: input.ctx.model?.provider,
|
|
883
|
+
currentModel: input.ctx.model,
|
|
884
|
+
},
|
|
885
|
+
availableModels,
|
|
886
|
+
cwd: effectiveCwd,
|
|
887
|
+
maxOutput: input.params.maxOutput,
|
|
888
|
+
artifactsDir: input.deps.tempArtifactsDir,
|
|
889
|
+
artifactConfig,
|
|
890
|
+
shareEnabled: input.params.share === true,
|
|
891
|
+
sessionRoot: input.deps.getSubagentSessionRoot(parentSessionFile),
|
|
892
|
+
chainSkills: normalized === false ? [] : (normalized ?? []),
|
|
893
|
+
dynamicFanoutMaxItems: input.deps.config.chain?.dynamicFanout?.maxItems,
|
|
894
|
+
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
895
|
+
worktreeSetupHook: input.deps.config.worktreeSetupHook,
|
|
896
|
+
worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
|
|
897
|
+
controlConfig: resolveControlConfig(input.deps.config.control, input.params.control),
|
|
898
|
+
controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
|
|
899
|
+
childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
900
|
+
});
|
|
901
|
+
if (result.isError) return result;
|
|
902
|
+
const attachedId = result.details.asyncId ?? runId;
|
|
903
|
+
const lines = [
|
|
904
|
+
`Attached async subagent ${target.runId} as the first step of a new chain.`,
|
|
905
|
+
`Chain run: ${attachedId}`,
|
|
906
|
+
`Root: ${target.agent} (step ${target.index + 1})`,
|
|
907
|
+
result.details.asyncDir ? `Async dir: ${result.details.asyncDir}` : undefined,
|
|
908
|
+
`Status if needed: subagent({ action: "status", id: "${attachedId}" })`,
|
|
909
|
+
].filter((line): line is string => Boolean(line));
|
|
910
|
+
return { content: [{ type: "text", text: formatAsyncStartedMessage(lines.join("\n")) }], details: result.details };
|
|
911
|
+
}
|
|
912
|
+
|
|
644
913
|
const runId = randomUUID().slice(0, 8);
|
|
645
914
|
const artifactConfig: ArtifactConfig = { ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false };
|
|
646
915
|
const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
@@ -653,6 +922,7 @@ async function resumeAsyncRun(input: {
|
|
|
653
922
|
cwd: input.requestCwd,
|
|
654
923
|
currentSessionId: input.deps.state.currentSessionId,
|
|
655
924
|
currentModelProvider: input.ctx.model?.provider,
|
|
925
|
+
currentModel: input.ctx.model,
|
|
656
926
|
},
|
|
657
927
|
cwd: effectiveCwd,
|
|
658
928
|
maxOutput: input.params.maxOutput,
|
|
@@ -694,6 +964,19 @@ function resultSummaryForIntercom(result: SingleResult): string {
|
|
|
694
964
|
return output || result.error || "(no output)";
|
|
695
965
|
}
|
|
696
966
|
|
|
967
|
+
function formatFailedSingleRunOutput(result: SingleResult, displayOutput: string): string {
|
|
968
|
+
const error = result.error || "Failed";
|
|
969
|
+
const output = displayOutput.trim();
|
|
970
|
+
const lines = [error];
|
|
971
|
+
if (output && output !== error.trim()) {
|
|
972
|
+
lines.push("", "Output:", output);
|
|
973
|
+
}
|
|
974
|
+
if (result.artifactPaths?.outputPath) {
|
|
975
|
+
lines.push("", `Output artifact: ${result.artifactPaths.outputPath}`);
|
|
976
|
+
}
|
|
977
|
+
return lines.join("\n");
|
|
978
|
+
}
|
|
979
|
+
|
|
697
980
|
function createForegroundControlNotifier(data: Pick<ExecutionContextData, "controlConfig" | "intercomBridge">, deps: Pick<ExecutorDeps, "pi">): (event: ControlEvent) => void {
|
|
698
981
|
return (event) => emitControlNotification({
|
|
699
982
|
pi: deps.pi,
|
|
@@ -719,7 +1002,6 @@ async function emitForegroundResultIntercom(input: {
|
|
|
719
1002
|
exitCode: result.exitCode,
|
|
720
1003
|
interrupted: result.interrupted,
|
|
721
1004
|
detached: result.detached,
|
|
722
|
-
timedOut: result.timedOut,
|
|
723
1005
|
}),
|
|
724
1006
|
summary: resultSummaryForIntercom(result),
|
|
725
1007
|
index,
|
|
@@ -765,51 +1047,6 @@ async function maybeBuildForegroundIntercomReceipt(input: {
|
|
|
765
1047
|
};
|
|
766
1048
|
}
|
|
767
1049
|
|
|
768
|
-
function validationErrorResult(mode: Details["mode"], text: string): AgentToolResult<Details> {
|
|
769
|
-
return { content: [{ type: "text", text }], isError: true, details: { mode, results: [] } };
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
function resolveForegroundTimeoutMs(params: SubagentParamsLike): { timeoutMs?: number; error?: string } {
|
|
773
|
-
const rawTimeout = (params as { timeoutMs?: unknown }).timeoutMs;
|
|
774
|
-
const rawMaxRuntime = (params as { maxRuntimeMs?: unknown }).maxRuntimeMs;
|
|
775
|
-
for (const [name, value] of [["timeoutMs", rawTimeout], ["maxRuntimeMs", rawMaxRuntime]] as const) {
|
|
776
|
-
if (value !== undefined && (typeof value !== "number" || !Number.isInteger(value) || value < 1)) {
|
|
777
|
-
return { error: `${name} must be a positive integer.` };
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
if (rawTimeout !== undefined && rawMaxRuntime !== undefined && rawTimeout !== rawMaxRuntime) {
|
|
781
|
-
return { error: "timeoutMs and maxRuntimeMs are aliases; provide only one or use identical values." };
|
|
782
|
-
}
|
|
783
|
-
const timeoutMs = rawTimeout ?? rawMaxRuntime;
|
|
784
|
-
return timeoutMs === undefined ? {} : { timeoutMs };
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
function validateAcceptanceForExecution(params: SubagentParamsLike): AgentToolResult<Details> | null {
|
|
788
|
-
const topLevelErrors = validateAcceptanceInput(params.acceptance);
|
|
789
|
-
if (topLevelErrors.length > 0) return validationErrorResult("single", topLevelErrors.join(" "));
|
|
790
|
-
for (const [index, task] of (params.tasks ?? []).entries()) {
|
|
791
|
-
const errors = validateAcceptanceInput(task.acceptance, `tasks[${index}].acceptance`);
|
|
792
|
-
if (errors.length > 0) return validationErrorResult("parallel", errors.join(" "));
|
|
793
|
-
}
|
|
794
|
-
for (const [stepIndex, step] of (params.chain ?? []).entries()) {
|
|
795
|
-
if (isParallelStep(step)) {
|
|
796
|
-
if (Object.hasOwn(step, "acceptance")) return validationErrorResult("chain", `chain[${stepIndex}].acceptance is not supported on static parallel groups; set acceptance on each parallel task.`);
|
|
797
|
-
for (const [taskIndex, task] of step.parallel.entries()) {
|
|
798
|
-
const errors = validateAcceptanceInput(task.acceptance, `chain[${stepIndex}].parallel[${taskIndex}].acceptance`);
|
|
799
|
-
if (errors.length > 0) return validationErrorResult("chain", errors.join(" "));
|
|
800
|
-
}
|
|
801
|
-
} else if (isDynamicParallelStep(step)) {
|
|
802
|
-
if (Object.hasOwn(step, "acceptance")) return validationErrorResult("chain", `chain[${stepIndex}].acceptance is not supported on dynamic fanout groups; set acceptance on chain[${stepIndex}].parallel.acceptance for each materialized child.`);
|
|
803
|
-
const errors = validateAcceptanceInput(step.parallel.acceptance, `chain[${stepIndex}].parallel.acceptance`);
|
|
804
|
-
if (errors.length > 0) return validationErrorResult("chain", errors.join(" "));
|
|
805
|
-
} else {
|
|
806
|
-
const stepErrors = validateAcceptanceInput(step.acceptance, `chain[${stepIndex}].acceptance`);
|
|
807
|
-
if (stepErrors.length > 0) return validationErrorResult("chain", stepErrors.join(" "));
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
return null;
|
|
811
|
-
}
|
|
812
|
-
|
|
813
1050
|
function validateExecutionInput(
|
|
814
1051
|
params: SubagentParamsLike,
|
|
815
1052
|
agents: AgentConfig[],
|
|
@@ -818,9 +1055,6 @@ function validateExecutionInput(
|
|
|
818
1055
|
hasSingle: boolean,
|
|
819
1056
|
allowClarifyTaskPrompt: boolean,
|
|
820
1057
|
): AgentToolResult<Details> | null {
|
|
821
|
-
const acceptanceError = validateAcceptanceForExecution(params);
|
|
822
|
-
if (acceptanceError) return acceptanceError;
|
|
823
|
-
|
|
824
1058
|
if (Number(hasChain) + Number(hasTasks) + Number(hasSingle) !== 1) {
|
|
825
1059
|
return {
|
|
826
1060
|
content: [
|
|
@@ -834,9 +1068,6 @@ function validateExecutionInput(
|
|
|
834
1068
|
};
|
|
835
1069
|
}
|
|
836
1070
|
|
|
837
|
-
const timeoutResolution = resolveForegroundTimeoutMs(params);
|
|
838
|
-
if (timeoutResolution.error) return validationErrorResult(getRequestedModeLabel(params), timeoutResolution.error);
|
|
839
|
-
|
|
840
1071
|
if (hasSingle && params.agent && !agents.find((agent) => agent.name === params.agent)) {
|
|
841
1072
|
return {
|
|
842
1073
|
content: [{ type: "text", text: `Unknown agent: ${params.agent}` }],
|
|
@@ -1138,6 +1369,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1138
1369
|
cwd: ctx.cwd,
|
|
1139
1370
|
currentSessionId: deps.state.currentSessionId!,
|
|
1140
1371
|
currentModelProvider: ctx.model?.provider,
|
|
1372
|
+
currentModel: ctx.model,
|
|
1141
1373
|
};
|
|
1142
1374
|
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
1143
1375
|
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
@@ -1148,7 +1380,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1148
1380
|
if (hasTasks && params.tasks) {
|
|
1149
1381
|
const agentConfigs = params.tasks.map((task) => agents.find((agent) => agent.name === task.agent));
|
|
1150
1382
|
const modelOverrides = params.tasks.map((task, index) =>
|
|
1151
|
-
|
|
1383
|
+
resolveSubagentModelOverride(task.model ?? agentConfigs[index]?.model, ctx.model, availableModels, currentProvider),
|
|
1152
1384
|
);
|
|
1153
1385
|
const skillOverrides = params.tasks.map((task) => normalizeSkillInput(task.skill));
|
|
1154
1386
|
const parallelTasks = params.tasks.map((task, index) => ({
|
|
@@ -1235,7 +1467,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1235
1467
|
const normalizedSkills = normalizeSkillInput(params.skill);
|
|
1236
1468
|
const skills = normalizedSkills === false ? [] : normalizedSkills;
|
|
1237
1469
|
const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, a.maxSubagentDepth);
|
|
1238
|
-
const modelOverride =
|
|
1470
|
+
const modelOverride = resolveSubagentModelOverride((params.model as string | undefined) ?? a.model, ctx.model, availableModels, currentProvider);
|
|
1239
1471
|
return executeAsyncSingle(id, {
|
|
1240
1472
|
agent: params.agent!,
|
|
1241
1473
|
task: params.context === "fork" ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
|
|
@@ -1310,7 +1542,6 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
1310
1542
|
onUpdate,
|
|
1311
1543
|
onControlEvent,
|
|
1312
1544
|
controlConfig,
|
|
1313
|
-
...(data.foregroundTimeoutMs !== undefined ? { timeoutMs: data.foregroundTimeoutMs } : {}),
|
|
1314
1545
|
childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(runId, agent, index) : undefined,
|
|
1315
1546
|
orchestratorIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
1316
1547
|
foregroundControl,
|
|
@@ -1337,6 +1568,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
1337
1568
|
cwd: ctx.cwd,
|
|
1338
1569
|
currentSessionId: deps.state.currentSessionId!,
|
|
1339
1570
|
currentModelProvider: ctx.model?.provider,
|
|
1571
|
+
currentModel: ctx.model,
|
|
1340
1572
|
};
|
|
1341
1573
|
const asyncChain = wrapChainTasksForFork(chainResult.requestedAsync.chain, params.context);
|
|
1342
1574
|
return executeAsyncChain(id, {
|
|
@@ -1367,7 +1599,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
1367
1599
|
const chainDetails = chainResult.details ? compactForegroundDetails({ ...chainResult.details, runId }) : undefined;
|
|
1368
1600
|
if (foregroundControl) updateForegroundNestedProjection(foregroundControl);
|
|
1369
1601
|
if (chainDetails) rememberForegroundRun(deps.state, { runId, mode: "chain", cwd: effectiveCwd, results: chainDetails.results });
|
|
1370
|
-
const intercomReceipt = chainDetails && !chainDetails.results.some((result) => result.interrupted || result.detached
|
|
1602
|
+
const intercomReceipt = chainDetails && !chainDetails.results.some((result) => result.interrupted || result.detached)
|
|
1371
1603
|
? await maybeBuildForegroundIntercomReceipt({
|
|
1372
1604
|
pi: deps.pi,
|
|
1373
1605
|
intercomBridge: data.intercomBridge,
|
|
@@ -1402,8 +1634,6 @@ interface ForegroundParallelRunInput {
|
|
|
1402
1634
|
artifactConfig: ArtifactConfig;
|
|
1403
1635
|
artifactsDir: string;
|
|
1404
1636
|
maxOutput?: MaxOutputConfig;
|
|
1405
|
-
timeoutMs?: number;
|
|
1406
|
-
timeoutAt?: number;
|
|
1407
1637
|
paramsCwd: string;
|
|
1408
1638
|
maxSubagentDepths: number[];
|
|
1409
1639
|
availableModels: ModelInfo[];
|
|
@@ -1556,7 +1786,6 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
1556
1786
|
cwd: taskCwd,
|
|
1557
1787
|
signal: input.signal,
|
|
1558
1788
|
interruptSignal: interruptController.signal,
|
|
1559
|
-
...(input.timeoutMs !== undefined && input.timeoutAt !== undefined ? { timeoutMs: input.timeoutMs, timeoutAt: input.timeoutAt } : {}),
|
|
1560
1789
|
allowIntercomDetach: agentConfig?.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
|
|
1561
1790
|
intercomEvents: input.intercomEvents,
|
|
1562
1791
|
runId: input.runId,
|
|
@@ -1570,8 +1799,6 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
1570
1799
|
outputPath,
|
|
1571
1800
|
outputMode: behavior?.outputMode,
|
|
1572
1801
|
maxSubagentDepth: input.maxSubagentDepths[index],
|
|
1573
|
-
maxExecutionTimeMs: agentConfig?.maxExecutionTimeMs,
|
|
1574
|
-
maxTokens: agentConfig?.maxTokens,
|
|
1575
1802
|
controlConfig: input.controlConfig,
|
|
1576
1803
|
onControlEvent: input.onControlEvent,
|
|
1577
1804
|
intercomSessionName: input.childIntercomTarget?.(task.agent, index),
|
|
@@ -1697,7 +1924,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
1697
1924
|
...(task.model ? { model: task.model } : {}),
|
|
1698
1925
|
}));
|
|
1699
1926
|
const modelOverrides: (string | undefined)[] = tasks.map((_, i) =>
|
|
1700
|
-
|
|
1927
|
+
resolveSubagentModelOverride(behaviorOverrides[i]?.model ?? agentConfigs[i]?.model, ctx.model, availableModels, currentProvider),
|
|
1701
1928
|
);
|
|
1702
1929
|
|
|
1703
1930
|
if (params.clarify === true && ctx.hasUI) {
|
|
@@ -1758,6 +1985,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
1758
1985
|
cwd: ctx.cwd,
|
|
1759
1986
|
currentSessionId: deps.state.currentSessionId!,
|
|
1760
1987
|
currentModelProvider: ctx.model?.provider,
|
|
1988
|
+
currentModel: ctx.model,
|
|
1761
1989
|
};
|
|
1762
1990
|
const parallelTasks = tasks.map((t, i) => {
|
|
1763
1991
|
const taskText = params.context === "fork" ? wrapForkTask(taskTexts[i]!) : taskTexts[i]!;
|
|
@@ -1839,7 +2067,6 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
1839
2067
|
}
|
|
1840
2068
|
}
|
|
1841
2069
|
|
|
1842
|
-
const timeoutAt = data.foregroundTimeoutMs !== undefined ? Date.now() + data.foregroundTimeoutMs : undefined;
|
|
1843
2070
|
const results = await runForegroundParallelTasks({
|
|
1844
2071
|
tasks,
|
|
1845
2072
|
taskTexts,
|
|
@@ -1854,7 +2081,6 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
1854
2081
|
artifactConfig,
|
|
1855
2082
|
artifactsDir,
|
|
1856
2083
|
maxOutput: params.maxOutput,
|
|
1857
|
-
...(data.foregroundTimeoutMs !== undefined && timeoutAt !== undefined ? { timeoutMs: data.foregroundTimeoutMs, timeoutAt } : {}),
|
|
1858
2084
|
paramsCwd: effectiveCwd,
|
|
1859
2085
|
availableModels,
|
|
1860
2086
|
modelOverrides,
|
|
@@ -1882,7 +2108,6 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
1882
2108
|
if (result.artifactPaths) allArtifactPaths.push(result.artifactPaths);
|
|
1883
2109
|
}
|
|
1884
2110
|
|
|
1885
|
-
const timedOut = results.find((result) => result.timedOut);
|
|
1886
2111
|
const interrupted = results.find((result) => result.interrupted);
|
|
1887
2112
|
const details = compactForegroundDetails({
|
|
1888
2113
|
mode: "parallel",
|
|
@@ -1892,13 +2117,6 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
1892
2117
|
artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
|
|
1893
2118
|
});
|
|
1894
2119
|
rememberForegroundRun(deps.state, { runId, mode: "parallel", cwd: effectiveCwd, results: details.results });
|
|
1895
|
-
if (timedOut) {
|
|
1896
|
-
return {
|
|
1897
|
-
content: [{ type: "text", text: `Parallel run timed out (${timedOut.agent}): ${timedOut.error ?? "timeout expired"}` }],
|
|
1898
|
-
details,
|
|
1899
|
-
isError: true,
|
|
1900
|
-
};
|
|
1901
|
-
}
|
|
1902
2120
|
if (interrupted) {
|
|
1903
2121
|
return {
|
|
1904
2122
|
content: [{ type: "text", text: `Parallel run paused after interrupt (${interrupted.agent}). Waiting for explicit next action.` }],
|
|
@@ -1990,8 +2208,9 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
1990
2208
|
const currentProvider = ctx.model?.provider;
|
|
1991
2209
|
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
1992
2210
|
let task = params.task ?? "";
|
|
1993
|
-
let modelOverride: string | undefined =
|
|
2211
|
+
let modelOverride: string | undefined = resolveSubagentModelOverride(
|
|
1994
2212
|
(params.model as string | undefined) ?? agentConfig.model,
|
|
2213
|
+
ctx.model,
|
|
1995
2214
|
availableModels,
|
|
1996
2215
|
currentProvider,
|
|
1997
2216
|
);
|
|
@@ -2048,6 +2267,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2048
2267
|
cwd: ctx.cwd,
|
|
2049
2268
|
currentSessionId: deps.state.currentSessionId!,
|
|
2050
2269
|
currentModelProvider: ctx.model?.provider,
|
|
2270
|
+
currentModel: ctx.model,
|
|
2051
2271
|
};
|
|
2052
2272
|
return executeAsyncSingle(id, {
|
|
2053
2273
|
agent: params.agent!,
|
|
@@ -2129,12 +2349,10 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2129
2349
|
}
|
|
2130
2350
|
: undefined;
|
|
2131
2351
|
|
|
2132
|
-
const timeoutAt = data.foregroundTimeoutMs !== undefined ? Date.now() + data.foregroundTimeoutMs : undefined;
|
|
2133
2352
|
const r = await runSync(ctx.cwd, agents, params.agent!, task, {
|
|
2134
2353
|
cwd: effectiveCwd,
|
|
2135
2354
|
signal,
|
|
2136
2355
|
interruptSignal: interruptController.signal,
|
|
2137
|
-
...(data.foregroundTimeoutMs !== undefined && timeoutAt !== undefined ? { timeoutMs: data.foregroundTimeoutMs, timeoutAt } : {}),
|
|
2138
2356
|
allowIntercomDetach: agentConfig.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
|
|
2139
2357
|
intercomEvents: deps.pi.events,
|
|
2140
2358
|
runId,
|
|
@@ -2147,8 +2365,6 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2147
2365
|
outputPath,
|
|
2148
2366
|
outputMode: effectiveOutputMode,
|
|
2149
2367
|
maxSubagentDepth,
|
|
2150
|
-
maxExecutionTimeMs: agentConfig.maxExecutionTimeMs,
|
|
2151
|
-
maxTokens: agentConfig.maxTokens,
|
|
2152
2368
|
onUpdate: forwardSingleUpdate,
|
|
2153
2369
|
controlConfig,
|
|
2154
2370
|
onControlEvent,
|
|
@@ -2201,7 +2417,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2201
2417
|
});
|
|
2202
2418
|
rememberForegroundRun(deps.state, { runId, mode: "single", cwd: effectiveCwd, results: details.results });
|
|
2203
2419
|
|
|
2204
|
-
if (!r.detached && !r.interrupted
|
|
2420
|
+
if (!r.detached && !r.interrupted) {
|
|
2205
2421
|
if (foregroundControl) updateForegroundNestedProjection(foregroundControl);
|
|
2206
2422
|
const intercomReceipt = await maybeBuildForegroundIntercomReceipt({
|
|
2207
2423
|
pi: deps.pi,
|
|
@@ -2227,14 +2443,6 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2227
2443
|
};
|
|
2228
2444
|
}
|
|
2229
2445
|
|
|
2230
|
-
if (r.timedOut) {
|
|
2231
|
-
return {
|
|
2232
|
-
content: [{ type: "text", text: `Run timed out (${params.agent}): ${r.error ?? "timeout expired"}` }],
|
|
2233
|
-
details,
|
|
2234
|
-
isError: true,
|
|
2235
|
-
};
|
|
2236
|
-
}
|
|
2237
|
-
|
|
2238
2446
|
if (r.interrupted) {
|
|
2239
2447
|
return {
|
|
2240
2448
|
content: [{ type: "text", text: `Run paused after interrupt (${params.agent}). Waiting for explicit next action.` }],
|
|
@@ -2244,7 +2452,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2244
2452
|
|
|
2245
2453
|
if (r.exitCode !== 0)
|
|
2246
2454
|
return {
|
|
2247
|
-
content: [{ type: "text", text: r.
|
|
2455
|
+
content: [{ type: "text", text: formatFailedSingleRunOutput(r, finalizedOutput.displayOutput) }],
|
|
2248
2456
|
details,
|
|
2249
2457
|
isError: true,
|
|
2250
2458
|
};
|
|
@@ -2333,6 +2541,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2333
2541
|
if (params.action === "resume") {
|
|
2334
2542
|
return resumeAsyncRun({ params: paramsWithResolvedCwd, requestCwd, ctx, deps });
|
|
2335
2543
|
}
|
|
2544
|
+
if (params.action === "append-step") {
|
|
2545
|
+
return appendStepToAsyncChain({ params: paramsWithResolvedCwd, requestCwd, ctx, deps });
|
|
2546
|
+
}
|
|
2336
2547
|
if (params.action === "interrupt") {
|
|
2337
2548
|
const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
|
|
2338
2549
|
let resolved: ResolvedSubagentRunId | undefined;
|
|
@@ -2362,7 +2573,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2362
2573
|
details: { mode: "management", results: [] },
|
|
2363
2574
|
};
|
|
2364
2575
|
}
|
|
2365
|
-
const asyncInterruptResult = interruptAsyncRun(deps.state, resolved?.kind === "async" ? resolved.id : targetRunId);
|
|
2576
|
+
const asyncInterruptResult = interruptAsyncRun(deps.state, resolved?.kind === "async" ? resolved.id : targetRunId, deps.kill);
|
|
2366
2577
|
if (asyncInterruptResult) return asyncInterruptResult;
|
|
2367
2578
|
return {
|
|
2368
2579
|
content: [{ type: "text", text: "No interrupt-capable run found in this session." }],
|
|
@@ -2384,7 +2595,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2384
2595
|
details: { mode: "management" as const, results: [] },
|
|
2385
2596
|
};
|
|
2386
2597
|
}
|
|
2387
|
-
return handleManagementAction(params.action, paramsWithResolvedCwd, { ...ctx, cwd: requestCwd });
|
|
2598
|
+
return handleManagementAction(params.action, paramsWithResolvedCwd, { ...ctx, cwd: requestCwd, config: deps.config });
|
|
2388
2599
|
}
|
|
2389
2600
|
|
|
2390
2601
|
const { blocked, depth, maxDepth } = checkSubagentDepth(deps.config.maxSubagentDepth);
|
|
@@ -2462,11 +2673,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2462
2673
|
const requestedAsync = effectiveParams.async ?? deps.asyncByDefault;
|
|
2463
2674
|
const backgroundRequestedWhileClarifying = (hasChain || hasTasks) && requestedAsync && effectiveParams.clarify === true;
|
|
2464
2675
|
const effectiveAsync = requestedAsync && effectiveParams.clarify !== true;
|
|
2465
|
-
const foregroundTimeout = resolveForegroundTimeoutMs(effectiveParams);
|
|
2466
|
-
if (foregroundTimeout.error) return buildRequestedModeError(effectiveParams, foregroundTimeout.error);
|
|
2467
|
-
if (effectiveAsync && foregroundTimeout.timeoutMs !== undefined) {
|
|
2468
|
-
return buildRequestedModeError(effectiveParams, "timeoutMs/maxRuntimeMs only applies to foreground subagent runs. Omit async:true or use action:'interrupt' for background runs.");
|
|
2469
|
-
}
|
|
2470
2676
|
const controlConfig = resolveControlConfig(deps.config.control, effectiveParams.control);
|
|
2471
2677
|
|
|
2472
2678
|
const artifactConfig: ArtifactConfig = {
|
|
@@ -2518,7 +2724,6 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
2518
2724
|
artifactsDir,
|
|
2519
2725
|
backgroundRequestedWhileClarifying,
|
|
2520
2726
|
effectiveAsync,
|
|
2521
|
-
...(foregroundTimeout.timeoutMs !== undefined ? { foregroundTimeoutMs: foregroundTimeout.timeoutMs } : {}),
|
|
2522
2727
|
controlConfig,
|
|
2523
2728
|
intercomBridge,
|
|
2524
2729
|
nestedRoute,
|