pi-subagents 0.17.3 → 0.17.5
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 +21 -0
- package/agents/context-builder.md +1 -1
- package/agents/oracle-executor.md +4 -1
- package/agents/oracle.md +6 -2
- package/agents/planner.md +1 -1
- package/agents/researcher.md +1 -1
- package/agents/reviewer.md +1 -1
- package/agents/scout.md +1 -1
- package/agents/worker.md +1 -1
- package/async-execution.ts +7 -0
- package/async-job-tracker.ts +5 -1
- package/async-status.ts +53 -18
- package/chain-execution.ts +137 -26
- package/execution.ts +134 -4
- package/index.ts +12 -3
- package/package.json +5 -1
- package/pi-spawn.ts +9 -6
- package/render.ts +19 -4
- package/schemas.ts +12 -1
- package/skills/pi-subagents/SKILL.md +466 -0
- package/slash-live-state.ts +4 -0
- package/subagent-control.ts +106 -0
- package/subagent-executor.ts +236 -5
- package/subagent-runner.ts +110 -25
- package/subagents-status.ts +4 -1
- package/types.ts +54 -2
- package/utils.ts +1 -0
package/subagent-executor.ts
CHANGED
|
@@ -24,8 +24,9 @@ import { discoverAvailableSkills, normalizeSkillInput } from "./skills.ts";
|
|
|
24
24
|
import { executeAsyncChain, executeAsyncSingle, isAsyncAvailable } from "./async-execution.ts";
|
|
25
25
|
import { createForkContextResolver } from "./fork-context.ts";
|
|
26
26
|
import { applyIntercomBridgeToAgent, resolveIntercomBridge, resolveIntercomSessionTarget } from "./intercom-bridge.ts";
|
|
27
|
+
import { resolveControlConfig } from "./subagent-control.ts";
|
|
27
28
|
import { finalizeSingleOutput, injectSingleOutputInstruction, resolveSingleOutputPath } from "./single-output.ts";
|
|
28
|
-
import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, resolveChildCwd } from "./utils.ts";
|
|
29
|
+
import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, readStatus, resolveChildCwd } from "./utils.ts";
|
|
29
30
|
import { applyForceTopLevelAsyncOverride } from "./top-level-async.ts";
|
|
30
31
|
import {
|
|
31
32
|
cleanupWorktrees,
|
|
@@ -40,9 +41,11 @@ import {
|
|
|
40
41
|
type AgentProgress,
|
|
41
42
|
type ArtifactConfig,
|
|
42
43
|
type ArtifactPaths,
|
|
44
|
+
type ControlConfig,
|
|
43
45
|
type Details,
|
|
44
46
|
type ExtensionConfig,
|
|
45
47
|
type MaxOutputConfig,
|
|
48
|
+
type ResolvedControlConfig,
|
|
46
49
|
type SingleResult,
|
|
47
50
|
type SubagentState,
|
|
48
51
|
DEFAULT_ARTIFACT_CONFIG,
|
|
@@ -54,6 +57,8 @@ import {
|
|
|
54
57
|
wrapForkTask,
|
|
55
58
|
} from "./types.ts";
|
|
56
59
|
|
|
60
|
+
const ASYNC_INTERRUPT_SIGNAL: NodeJS.Signals = process.platform === "win32" ? "SIGBREAK" : "SIGUSR2";
|
|
61
|
+
|
|
57
62
|
interface TaskParam {
|
|
58
63
|
agent: string;
|
|
59
64
|
task: string;
|
|
@@ -65,6 +70,7 @@ interface TaskParam {
|
|
|
65
70
|
|
|
66
71
|
export interface SubagentParamsLike {
|
|
67
72
|
action?: string;
|
|
73
|
+
runId?: string;
|
|
68
74
|
agent?: string;
|
|
69
75
|
task?: string;
|
|
70
76
|
chain?: ChainStep[];
|
|
@@ -75,6 +81,7 @@ export interface SubagentParamsLike {
|
|
|
75
81
|
async?: boolean;
|
|
76
82
|
clarify?: boolean;
|
|
77
83
|
share?: boolean;
|
|
84
|
+
control?: ControlConfig;
|
|
78
85
|
sessionDir?: string;
|
|
79
86
|
cwd?: string;
|
|
80
87
|
maxOutput?: MaxOutputConfig;
|
|
@@ -114,12 +121,73 @@ interface ExecutionContextData {
|
|
|
114
121
|
artifactsDir: string;
|
|
115
122
|
backgroundRequestedWhileClarifying: boolean;
|
|
116
123
|
effectiveAsync: boolean;
|
|
124
|
+
controlConfig: ResolvedControlConfig;
|
|
117
125
|
}
|
|
118
126
|
|
|
119
127
|
function resolveRequestedCwd(runtimeCwd: string, requestedCwd: string | undefined): string {
|
|
120
128
|
return requestedCwd ? path.resolve(runtimeCwd, requestedCwd) : runtimeCwd;
|
|
121
129
|
}
|
|
122
130
|
|
|
131
|
+
function getForegroundControl(state: SubagentState, runId: string | undefined) {
|
|
132
|
+
if (runId) return state.foregroundControls.get(runId);
|
|
133
|
+
if (state.lastForegroundControlId) {
|
|
134
|
+
const latest = state.foregroundControls.get(state.lastForegroundControlId);
|
|
135
|
+
if (latest) return latest;
|
|
136
|
+
}
|
|
137
|
+
let newest: (SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never) | undefined;
|
|
138
|
+
for (const control of state.foregroundControls.values()) {
|
|
139
|
+
if (!newest || control.updatedAt > newest.updatedAt) newest = control;
|
|
140
|
+
}
|
|
141
|
+
return newest;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function getAsyncInterruptTarget(state: SubagentState, runId: string | undefined): { asyncId: string; asyncDir: string } | undefined {
|
|
145
|
+
if (runId) {
|
|
146
|
+
const direct = state.asyncJobs.get(runId);
|
|
147
|
+
if (direct) return { asyncId: direct.asyncId, asyncDir: direct.asyncDir };
|
|
148
|
+
}
|
|
149
|
+
let newest: { asyncId: string; asyncDir: string; updatedAt: number } | undefined;
|
|
150
|
+
for (const job of state.asyncJobs.values()) {
|
|
151
|
+
if (job.status !== "running") continue;
|
|
152
|
+
if (!newest || (job.updatedAt ?? 0) > newest.updatedAt) {
|
|
153
|
+
newest = { asyncId: job.asyncId, asyncDir: job.asyncDir, updatedAt: job.updatedAt ?? 0 };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return newest ? { asyncId: newest.asyncId, asyncDir: newest.asyncDir } : undefined;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function interruptAsyncRun(state: SubagentState, runId: string | undefined): AgentToolResult<Details> | null {
|
|
160
|
+
const target = getAsyncInterruptTarget(state, runId);
|
|
161
|
+
if (!target) return null;
|
|
162
|
+
const status = readStatus(target.asyncDir);
|
|
163
|
+
if (!status || status.state !== "running" || typeof status.pid !== "number") {
|
|
164
|
+
return {
|
|
165
|
+
content: [{ type: "text", text: `No running async run with an interrupt-capable pid was found for '${runId ?? "current"}'.` }],
|
|
166
|
+
isError: true,
|
|
167
|
+
details: { mode: "management", results: [] },
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
process.kill(status.pid, ASYNC_INTERRUPT_SIGNAL);
|
|
172
|
+
const tracked = state.asyncJobs.get(target.asyncId);
|
|
173
|
+
if (tracked) {
|
|
174
|
+
tracked.activityState = "paused";
|
|
175
|
+
tracked.updatedAt = Date.now();
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
content: [{ type: "text", text: `Interrupt requested for async run ${target.asyncId}.` }],
|
|
179
|
+
details: { mode: "management", results: [] },
|
|
180
|
+
};
|
|
181
|
+
} catch (error) {
|
|
182
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
183
|
+
return {
|
|
184
|
+
content: [{ type: "text", text: `Failed to interrupt async run ${target.asyncId}: ${message}` }],
|
|
185
|
+
isError: true,
|
|
186
|
+
details: { mode: "management", results: [] },
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
123
191
|
function validateExecutionInput(
|
|
124
192
|
params: SubagentParamsLike,
|
|
125
193
|
agents: AgentConfig[],
|
|
@@ -346,6 +414,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
346
414
|
artifactConfig,
|
|
347
415
|
artifactsDir,
|
|
348
416
|
effectiveAsync,
|
|
417
|
+
controlConfig,
|
|
349
418
|
} = data;
|
|
350
419
|
const hasChain = (params.chain?.length ?? 0) > 0;
|
|
351
420
|
const hasTasks = (params.tasks?.length ?? 0) > 0;
|
|
@@ -429,6 +498,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
429
498
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
430
499
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
431
500
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
501
|
+
controlConfig,
|
|
432
502
|
});
|
|
433
503
|
}
|
|
434
504
|
|
|
@@ -452,6 +522,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
452
522
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
453
523
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
454
524
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
525
|
+
controlConfig,
|
|
455
526
|
});
|
|
456
527
|
}
|
|
457
528
|
|
|
@@ -489,6 +560,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
489
560
|
maxSubagentDepth,
|
|
490
561
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
491
562
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
563
|
+
controlConfig,
|
|
492
564
|
});
|
|
493
565
|
}
|
|
494
566
|
|
|
@@ -510,7 +582,9 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
510
582
|
artifactConfig,
|
|
511
583
|
onUpdate,
|
|
512
584
|
sessionRoot,
|
|
585
|
+
controlConfig,
|
|
513
586
|
} = data;
|
|
587
|
+
const foregroundControl = deps.state.foregroundControls.get(runId);
|
|
514
588
|
const normalized = normalizeSkillInput(params.skill);
|
|
515
589
|
const chainSkills = normalized === false ? [] : (normalized ?? []);
|
|
516
590
|
const chain = wrapChainTasksForFork(params.chain as ChainStep[], params.context);
|
|
@@ -531,6 +605,8 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
531
605
|
includeProgress: params.includeProgress,
|
|
532
606
|
clarify: params.clarify,
|
|
533
607
|
onUpdate,
|
|
608
|
+
controlConfig,
|
|
609
|
+
foregroundControl,
|
|
534
610
|
chainSkills,
|
|
535
611
|
chainDir: params.chainDir,
|
|
536
612
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
@@ -574,6 +650,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
574
650
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
575
651
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
576
652
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
653
|
+
controlConfig,
|
|
577
654
|
});
|
|
578
655
|
}
|
|
579
656
|
|
|
@@ -599,6 +676,8 @@ interface ForegroundParallelRunInput {
|
|
|
599
676
|
modelOverrides: (string | undefined)[];
|
|
600
677
|
skillOverrides: (string[] | false | undefined)[];
|
|
601
678
|
behaviors: Array<ReturnType<typeof resolveStepBehavior>>;
|
|
679
|
+
controlConfig: ResolvedControlConfig;
|
|
680
|
+
foregroundControl?: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never;
|
|
602
681
|
concurrencyLimit: number;
|
|
603
682
|
liveResults: (SingleResult | undefined)[];
|
|
604
683
|
liveProgress: (AgentProgress | undefined)[];
|
|
@@ -687,9 +766,24 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
687
766
|
const overrideSkills = input.skillOverrides[index];
|
|
688
767
|
const effectiveSkills = overrideSkills === undefined ? input.behaviors[index]?.skills : overrideSkills;
|
|
689
768
|
const taskCwd = resolveParallelTaskCwd(task, input.paramsCwd, input.worktreeSetup, index);
|
|
769
|
+
const interruptController = new AbortController();
|
|
770
|
+
if (input.foregroundControl) {
|
|
771
|
+
input.foregroundControl.currentAgent = task.agent;
|
|
772
|
+
input.foregroundControl.currentIndex = index;
|
|
773
|
+
input.foregroundControl.currentActivityState = "starting";
|
|
774
|
+
input.foregroundControl.updatedAt = Date.now();
|
|
775
|
+
input.foregroundControl.interrupt = () => {
|
|
776
|
+
if (interruptController.signal.aborted) return false;
|
|
777
|
+
interruptController.abort();
|
|
778
|
+
input.foregroundControl!.currentActivityState = "paused";
|
|
779
|
+
input.foregroundControl!.updatedAt = Date.now();
|
|
780
|
+
return true;
|
|
781
|
+
};
|
|
782
|
+
}
|
|
690
783
|
return runSync(input.ctx.cwd, input.agents, task.agent, input.taskTexts[index]!, {
|
|
691
784
|
cwd: taskCwd,
|
|
692
785
|
signal: input.signal,
|
|
786
|
+
interruptSignal: interruptController.signal,
|
|
693
787
|
runId: input.runId,
|
|
694
788
|
index,
|
|
695
789
|
sessionDir: input.sessionDirForIndex(index),
|
|
@@ -699,14 +793,22 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
699
793
|
artifactConfig: input.artifactConfig,
|
|
700
794
|
maxOutput: input.maxOutput,
|
|
701
795
|
maxSubagentDepth: input.maxSubagentDepths[index],
|
|
796
|
+
controlConfig: input.controlConfig,
|
|
702
797
|
modelOverride: input.modelOverrides[index],
|
|
703
798
|
availableModels: input.availableModels,
|
|
704
799
|
preferredModelProvider: input.ctx.model?.provider,
|
|
705
800
|
skills: effectiveSkills === false ? [] : effectiveSkills,
|
|
706
|
-
|
|
707
|
-
|
|
801
|
+
onUpdate: input.onUpdate
|
|
802
|
+
? (progressUpdate) => {
|
|
708
803
|
const stepResults = progressUpdate.details?.results || [];
|
|
709
804
|
const stepProgress = progressUpdate.details?.progress || [];
|
|
805
|
+
if (input.foregroundControl && stepProgress.length > 0) {
|
|
806
|
+
const current = stepProgress[0];
|
|
807
|
+
input.foregroundControl.currentAgent = task.agent;
|
|
808
|
+
input.foregroundControl.currentIndex = index;
|
|
809
|
+
input.foregroundControl.currentActivityState = current?.activityState;
|
|
810
|
+
input.foregroundControl.updatedAt = Date.now();
|
|
811
|
+
}
|
|
710
812
|
if (stepResults.length > 0) input.liveResults[index] = stepResults[0];
|
|
711
813
|
if (stepProgress.length > 0) input.liveProgress[index] = stepProgress[0];
|
|
712
814
|
const mergedResults = input.liveResults.filter((result): result is SingleResult => result !== undefined);
|
|
@@ -717,11 +819,17 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
717
819
|
mode: "parallel",
|
|
718
820
|
results: mergedResults,
|
|
719
821
|
progress: mergedProgress,
|
|
822
|
+
controlEvents: progressUpdate.details?.controlEvents,
|
|
720
823
|
totalSteps: input.tasks.length,
|
|
721
824
|
},
|
|
722
825
|
});
|
|
723
826
|
}
|
|
724
827
|
: undefined,
|
|
828
|
+
}).finally(() => {
|
|
829
|
+
if (input.foregroundControl?.currentIndex === index) {
|
|
830
|
+
input.foregroundControl.interrupt = undefined;
|
|
831
|
+
input.foregroundControl.updatedAt = Date.now();
|
|
832
|
+
}
|
|
725
833
|
});
|
|
726
834
|
});
|
|
727
835
|
}
|
|
@@ -742,6 +850,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
742
850
|
backgroundRequestedWhileClarifying,
|
|
743
851
|
onUpdate,
|
|
744
852
|
sessionRoot,
|
|
853
|
+
controlConfig,
|
|
745
854
|
} = data;
|
|
746
855
|
const allProgress: AgentProgress[] = [];
|
|
747
856
|
const allArtifactPaths: ArtifactPaths[] = [];
|
|
@@ -866,6 +975,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
866
975
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
867
976
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
868
977
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
978
|
+
controlConfig,
|
|
869
979
|
});
|
|
870
980
|
}
|
|
871
981
|
}
|
|
@@ -873,6 +983,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
873
983
|
const behaviors = agentConfigs.map((config) => resolveStepBehavior(config, {}));
|
|
874
984
|
const liveResults: (SingleResult | undefined)[] = new Array(tasks.length).fill(undefined);
|
|
875
985
|
const liveProgress: (AgentProgress | undefined)[] = new Array(tasks.length).fill(undefined);
|
|
986
|
+
const foregroundControl = deps.state.foregroundControls.get(runId);
|
|
876
987
|
const { setup: worktreeSetup, errorResult } = createParallelWorktreeSetup(
|
|
877
988
|
params.worktree,
|
|
878
989
|
effectiveCwd,
|
|
@@ -908,6 +1019,8 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
908
1019
|
modelOverrides,
|
|
909
1020
|
skillOverrides,
|
|
910
1021
|
behaviors,
|
|
1022
|
+
controlConfig,
|
|
1023
|
+
foregroundControl,
|
|
911
1024
|
concurrencyLimit: parallelConcurrency,
|
|
912
1025
|
maxSubagentDepths,
|
|
913
1026
|
liveResults,
|
|
@@ -925,6 +1038,19 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
925
1038
|
if (result.artifactPaths) allArtifactPaths.push(result.artifactPaths);
|
|
926
1039
|
}
|
|
927
1040
|
|
|
1041
|
+
const interrupted = results.find((result) => result.interrupted);
|
|
1042
|
+
if (interrupted) {
|
|
1043
|
+
return {
|
|
1044
|
+
content: [{ type: "text", text: `Parallel run paused after interrupt (${interrupted.agent}). Waiting for explicit next action.` }],
|
|
1045
|
+
details: compactForegroundDetails({
|
|
1046
|
+
mode: "parallel",
|
|
1047
|
+
results,
|
|
1048
|
+
progress: params.includeProgress ? allProgress : undefined,
|
|
1049
|
+
artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
|
|
1050
|
+
}),
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
|
|
928
1054
|
const worktreeSuffix = buildParallelWorktreeSuffix(worktreeSetup, artifactsDir, tasks);
|
|
929
1055
|
const ok = results.filter((result) => result.exitCode === 0).length;
|
|
930
1056
|
const downgradeNote = backgroundRequestedWhileClarifying ? " (background requested, but clarify kept this run foreground)" : "";
|
|
@@ -972,6 +1098,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
972
1098
|
artifactsDir,
|
|
973
1099
|
onUpdate,
|
|
974
1100
|
sessionRoot,
|
|
1101
|
+
controlConfig,
|
|
975
1102
|
} = data;
|
|
976
1103
|
const allProgress: AgentProgress[] = [];
|
|
977
1104
|
const allArtifactPaths: ArtifactPaths[] = [];
|
|
@@ -1068,6 +1195,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
1068
1195
|
maxSubagentDepth,
|
|
1069
1196
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1070
1197
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1198
|
+
controlConfig,
|
|
1071
1199
|
});
|
|
1072
1200
|
}
|
|
1073
1201
|
}
|
|
@@ -1085,10 +1213,39 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
1085
1213
|
} else {
|
|
1086
1214
|
effectiveSkills = skillOverride;
|
|
1087
1215
|
}
|
|
1216
|
+
const interruptController = new AbortController();
|
|
1217
|
+
const foregroundControl = deps.state.foregroundControls.get(runId);
|
|
1218
|
+
if (foregroundControl) {
|
|
1219
|
+
foregroundControl.currentAgent = params.agent;
|
|
1220
|
+
foregroundControl.currentIndex = 0;
|
|
1221
|
+
foregroundControl.currentActivityState = "starting";
|
|
1222
|
+
foregroundControl.updatedAt = Date.now();
|
|
1223
|
+
foregroundControl.interrupt = () => {
|
|
1224
|
+
if (interruptController.signal.aborted) return false;
|
|
1225
|
+
interruptController.abort();
|
|
1226
|
+
foregroundControl.currentActivityState = "paused";
|
|
1227
|
+
foregroundControl.updatedAt = Date.now();
|
|
1228
|
+
return true;
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
const forwardSingleUpdate = onUpdate
|
|
1233
|
+
? (update: AgentToolResult<Details>) => {
|
|
1234
|
+
if (foregroundControl) {
|
|
1235
|
+
const firstProgress = update.details?.progress?.[0];
|
|
1236
|
+
foregroundControl.currentAgent = params.agent;
|
|
1237
|
+
foregroundControl.currentIndex = firstProgress?.index ?? 0;
|
|
1238
|
+
foregroundControl.currentActivityState = firstProgress?.activityState;
|
|
1239
|
+
foregroundControl.updatedAt = Date.now();
|
|
1240
|
+
}
|
|
1241
|
+
onUpdate(update);
|
|
1242
|
+
}
|
|
1243
|
+
: undefined;
|
|
1088
1244
|
|
|
1089
1245
|
const r = await runSync(ctx.cwd, agents, params.agent!, task, {
|
|
1090
1246
|
cwd: effectiveCwd,
|
|
1091
1247
|
signal,
|
|
1248
|
+
interruptSignal: interruptController.signal,
|
|
1092
1249
|
allowIntercomDetach: agentConfig.systemPrompt?.includes("Intercom orchestration channel:") === true,
|
|
1093
1250
|
intercomEvents: deps.pi.events,
|
|
1094
1251
|
runId,
|
|
@@ -1100,12 +1257,18 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
1100
1257
|
maxOutput: params.maxOutput,
|
|
1101
1258
|
outputPath,
|
|
1102
1259
|
maxSubagentDepth,
|
|
1103
|
-
onUpdate,
|
|
1260
|
+
onUpdate: forwardSingleUpdate,
|
|
1261
|
+
controlConfig,
|
|
1104
1262
|
modelOverride,
|
|
1105
1263
|
availableModels,
|
|
1106
1264
|
preferredModelProvider: currentProvider,
|
|
1107
1265
|
skills: effectiveSkills,
|
|
1108
1266
|
});
|
|
1267
|
+
if (foregroundControl?.currentIndex === 0) {
|
|
1268
|
+
foregroundControl.interrupt = undefined;
|
|
1269
|
+
foregroundControl.currentActivityState = r.progress?.activityState;
|
|
1270
|
+
foregroundControl.updatedAt = Date.now();
|
|
1271
|
+
}
|
|
1109
1272
|
recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0);
|
|
1110
1273
|
|
|
1111
1274
|
if (r.progress) allProgress.push(r.progress);
|
|
@@ -1134,6 +1297,19 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
1134
1297
|
};
|
|
1135
1298
|
}
|
|
1136
1299
|
|
|
1300
|
+
if (r.interrupted) {
|
|
1301
|
+
return {
|
|
1302
|
+
content: [{ type: "text", text: `Run paused after interrupt (${params.agent}). Waiting for explicit next action.` }],
|
|
1303
|
+
details: compactForegroundDetails({
|
|
1304
|
+
mode: "single",
|
|
1305
|
+
results: [r],
|
|
1306
|
+
progress: params.includeProgress ? allProgress : undefined,
|
|
1307
|
+
artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
|
|
1308
|
+
truncation: r.truncation,
|
|
1309
|
+
}),
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1137
1313
|
if (r.exitCode !== 0)
|
|
1138
1314
|
return {
|
|
1139
1315
|
content: [{ type: "text", text: r.error || "Failed" }],
|
|
@@ -1175,10 +1351,38 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
1175
1351
|
ctx: ExtensionContext,
|
|
1176
1352
|
): Promise<AgentToolResult<Details>> => {
|
|
1177
1353
|
deps.state.baseCwd = ctx.cwd;
|
|
1354
|
+
deps.state.foregroundControls ??= new Map();
|
|
1355
|
+
deps.state.lastForegroundControlId ??= null;
|
|
1178
1356
|
const requestCwd = resolveRequestedCwd(ctx.cwd, params.cwd);
|
|
1179
1357
|
const paramsWithResolvedCwd = params.cwd === undefined ? params : { ...params, cwd: requestCwd };
|
|
1180
1358
|
if (params.action) {
|
|
1181
|
-
|
|
1359
|
+
if (params.action === "interrupt") {
|
|
1360
|
+
const foreground = getForegroundControl(deps.state, paramsWithResolvedCwd.runId);
|
|
1361
|
+
if (foreground?.interrupt) {
|
|
1362
|
+
const interrupted = foreground.interrupt();
|
|
1363
|
+
if (interrupted) {
|
|
1364
|
+
foreground.updatedAt = Date.now();
|
|
1365
|
+
foreground.currentActivityState = "paused";
|
|
1366
|
+
return {
|
|
1367
|
+
content: [{ type: "text", text: `Interrupt requested for foreground run ${foreground.runId}.` }],
|
|
1368
|
+
details: { mode: "management", results: [] },
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
return {
|
|
1372
|
+
content: [{ type: "text", text: `Foreground run ${foreground.runId} has no active child step to interrupt.` }],
|
|
1373
|
+
isError: true,
|
|
1374
|
+
details: { mode: "management", results: [] },
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
const asyncInterruptResult = interruptAsyncRun(deps.state, paramsWithResolvedCwd.runId);
|
|
1378
|
+
if (asyncInterruptResult) return asyncInterruptResult;
|
|
1379
|
+
return {
|
|
1380
|
+
content: [{ type: "text", text: "No interrupt-capable run found in this session." }],
|
|
1381
|
+
isError: true,
|
|
1382
|
+
details: { mode: "management", results: [] },
|
|
1383
|
+
};
|
|
1384
|
+
}
|
|
1385
|
+
const validActions = ["list", "get", "create", "update", "delete", "interrupt"];
|
|
1182
1386
|
if (!validActions.includes(params.action)) {
|
|
1183
1387
|
return {
|
|
1184
1388
|
content: [{ type: "text", text: `Unknown action: ${params.action}. Valid: ${validActions.join(", ")}` }],
|
|
@@ -1260,6 +1464,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
1260
1464
|
const backgroundRequestedWhileClarifying = hasTasks && requestedAsync && effectiveParams.clarify === true;
|
|
1261
1465
|
const effectiveAsync = requestedAsync
|
|
1262
1466
|
&& (hasChain ? effectiveParams.clarify === false : effectiveParams.clarify !== true);
|
|
1467
|
+
const controlConfig = resolveControlConfig(deps.config.control, effectiveParams.control);
|
|
1263
1468
|
|
|
1264
1469
|
const artifactConfig: ArtifactConfig = {
|
|
1265
1470
|
...DEFAULT_ARTIFACT_CONFIG,
|
|
@@ -1308,8 +1513,27 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
1308
1513
|
artifactsDir,
|
|
1309
1514
|
backgroundRequestedWhileClarifying,
|
|
1310
1515
|
effectiveAsync,
|
|
1516
|
+
controlConfig,
|
|
1311
1517
|
};
|
|
1312
1518
|
|
|
1519
|
+
const foregroundMode: "single" | "parallel" | "chain" = hasChain ? "chain" : hasTasks ? "parallel" : "single";
|
|
1520
|
+
const foregroundControl = effectiveAsync
|
|
1521
|
+
? undefined
|
|
1522
|
+
: {
|
|
1523
|
+
runId,
|
|
1524
|
+
mode: foregroundMode,
|
|
1525
|
+
startedAt: Date.now(),
|
|
1526
|
+
updatedAt: Date.now(),
|
|
1527
|
+
currentAgent: undefined,
|
|
1528
|
+
currentIndex: undefined,
|
|
1529
|
+
currentActivityState: undefined,
|
|
1530
|
+
interrupt: undefined,
|
|
1531
|
+
};
|
|
1532
|
+
if (foregroundControl) {
|
|
1533
|
+
deps.state.foregroundControls.set(runId, foregroundControl);
|
|
1534
|
+
deps.state.lastForegroundControlId = runId;
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1313
1537
|
try {
|
|
1314
1538
|
const asyncResult = runAsyncPath(execData, deps);
|
|
1315
1539
|
if (asyncResult) return withForkContext(asyncResult, effectiveParams.context);
|
|
@@ -1327,6 +1551,13 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
1327
1551
|
}
|
|
1328
1552
|
} catch (error) {
|
|
1329
1553
|
return toExecutionErrorResult(normalizedParams, error);
|
|
1554
|
+
} finally {
|
|
1555
|
+
if (foregroundControl) {
|
|
1556
|
+
deps.state.foregroundControls.delete(runId);
|
|
1557
|
+
if (deps.state.lastForegroundControlId === runId) {
|
|
1558
|
+
deps.state.lastForegroundControlId = null;
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1330
1561
|
}
|
|
1331
1562
|
|
|
1332
1563
|
return withForkContext({
|