@ryan_nookpi/pi-extension-subagent 0.5.7 → 0.5.9
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/async-task-lifecycle.ts +281 -0
- package/async-task-provider.ts +623 -0
- package/commands.ts +446 -416
- package/index.ts +20 -2
- package/package.json +3 -1
- package/runner.ts +33 -1
- package/store.ts +1 -0
- package/tool-execute.ts +72 -24
package/index.ts
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
30
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
31
|
+
import { SubagentAsyncTasks } from "./async-task-lifecycle.js";
|
|
31
32
|
import { HANG_CHECK_INTERVAL_MS } from "./constants.js";
|
|
32
33
|
import { SUBAGENT_COMMANDS, SUBAGENT_SHORTCUTS, type SubagentCommandName } from "./registration-manifest.js";
|
|
33
34
|
|
|
@@ -40,6 +41,7 @@ interface SubagentCore {
|
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
export default function (pi: ExtensionAPI) {
|
|
44
|
+
const asyncTasks = new SubagentAsyncTasks(pi);
|
|
43
45
|
let core: SubagentCore | null = null;
|
|
44
46
|
let corePromise: Promise<SubagentCore> | null = null;
|
|
45
47
|
/** Serializes session lifecycle work so events apply in dispatch order. */
|
|
@@ -54,6 +56,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
54
56
|
import("./store.js"),
|
|
55
57
|
]);
|
|
56
58
|
const store = storeMod.createStore();
|
|
59
|
+
store.asyncTasks = asyncTasks;
|
|
57
60
|
const registrations = commands.registerAll(pi, store);
|
|
58
61
|
core = { store, commands, registrations, escalation, lifecycle };
|
|
59
62
|
return core;
|
|
@@ -116,11 +119,24 @@ export default function (pi: ExtensionAPI) {
|
|
|
116
119
|
}, 0);
|
|
117
120
|
|
|
118
121
|
let hangCheckTimer: ReturnType<typeof setInterval> | undefined;
|
|
122
|
+
let sessionGeneration = 0;
|
|
119
123
|
|
|
120
124
|
pi.on("session_start", (_event, ctx) => {
|
|
121
|
-
|
|
125
|
+
const generation = ++sessionGeneration;
|
|
126
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
127
|
+
// Fence tool admission synchronously, before lazy lifecycle work can yield.
|
|
128
|
+
try {
|
|
129
|
+
if (pi.events) asyncTasks.bind(sessionId);
|
|
130
|
+
} catch (error) {
|
|
131
|
+
process.stderr.write(`[subagent] session binding failed: ${error instanceof Error ? error.message : error}\n`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
enqueue(async (c) => {
|
|
135
|
+
if (generation !== sessionGeneration) return;
|
|
136
|
+
if (pi.events) await asyncTasks.provider.whenDiscovered();
|
|
137
|
+
if (generation !== sessionGeneration || ctx.sessionManager.getSessionId() !== sessionId) return;
|
|
122
138
|
c.store.disposed = false;
|
|
123
|
-
c.commands.handleSessionStart(pi, c.store, ctx as unknown as ExtensionContext);
|
|
139
|
+
c.commands.handleSessionStart(asyncTasks.wrap(pi), c.store, ctx as unknown as ExtensionContext);
|
|
124
140
|
c.escalation.maybeRegisterAskMaster(pi, ctx);
|
|
125
141
|
});
|
|
126
142
|
if (!hangCheckTimer) {
|
|
@@ -131,6 +147,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
131
147
|
});
|
|
132
148
|
|
|
133
149
|
pi.on("session_shutdown", async (event) => {
|
|
150
|
+
++sessionGeneration;
|
|
151
|
+
asyncTasks.shutdown();
|
|
134
152
|
if (hangCheckTimer) {
|
|
135
153
|
clearInterval(hangCheckTimer);
|
|
136
154
|
hangCheckTimer = undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ryan_nookpi/pi-extension-subagent",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.9",
|
|
4
4
|
"description": "Asynchronous subagent delegation for pi with run, batch, chain, and continuation workflows.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -39,6 +39,8 @@
|
|
|
39
39
|
"format.ts",
|
|
40
40
|
"group-pending.ts",
|
|
41
41
|
"index.ts",
|
|
42
|
+
"async-task-provider.ts",
|
|
43
|
+
"async-task-lifecycle.ts",
|
|
42
44
|
"lifecycle.ts",
|
|
43
45
|
"invocation-queue.ts",
|
|
44
46
|
"live-preview.ts",
|
package/runner.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { spawn } from "node:child_process";
|
|
|
7
7
|
import * as fs from "node:fs";
|
|
8
8
|
import type { Message } from "@earendil-works/pi-ai";
|
|
9
9
|
import type { AgentConfig } from "./agents.js";
|
|
10
|
+
import { currentResourceObserver, trackSubagentRunner } from "./async-task-lifecycle.js";
|
|
10
11
|
import { buildClaudeArgs } from "./claude-args.js";
|
|
11
12
|
import { createSidecarWriter } from "./claude-sidecar-writer.js";
|
|
12
13
|
import {
|
|
@@ -43,6 +44,7 @@ import {
|
|
|
43
44
|
} from "./utils/agent-utils.js";
|
|
44
45
|
|
|
45
46
|
export interface RunSingleAgentSessionConfig {
|
|
47
|
+
asyncRunId?: number;
|
|
46
48
|
sessionFile?: string;
|
|
47
49
|
resumeSessionId?: string;
|
|
48
50
|
sidecarSessionFile?: string;
|
|
@@ -198,7 +200,21 @@ export function computeAgentAliasHints(agents: AgentConfig[]): string {
|
|
|
198
200
|
|
|
199
201
|
// ─── Single Agent Execution ──────────────────────────────────────────────────
|
|
200
202
|
|
|
201
|
-
export async function runSingleAgent(
|
|
203
|
+
export async function runSingleAgent(...args: Parameters<typeof runSingleAgentUntracked>): Promise<SingleResult> {
|
|
204
|
+
const config = typeof args[8] === "object" ? args[8] : undefined;
|
|
205
|
+
return trackSubagentRunner(
|
|
206
|
+
args[2],
|
|
207
|
+
args[5],
|
|
208
|
+
(signal) => {
|
|
209
|
+
const invocation: Parameters<typeof runSingleAgentUntracked> = [...args];
|
|
210
|
+
invocation[5] = signal;
|
|
211
|
+
return runSingleAgentUntracked(...invocation);
|
|
212
|
+
},
|
|
213
|
+
config?.asyncRunId,
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async function runSingleAgentUntracked(
|
|
202
218
|
defaultCwd: string,
|
|
203
219
|
agents: AgentConfig[],
|
|
204
220
|
agentName: string,
|
|
@@ -209,6 +225,7 @@ export async function runSingleAgent(
|
|
|
209
225
|
makeDetails: (results: SingleResult[]) => SubagentDetails,
|
|
210
226
|
sessionConfig?: string | RunSingleAgentSessionConfig,
|
|
211
227
|
): Promise<SingleResult> {
|
|
228
|
+
currentResourceObserver()("settled");
|
|
212
229
|
const normalizedSessionConfig = normalizeRunSessionConfig(sessionConfig);
|
|
213
230
|
const sessionFile = normalizedSessionConfig.sessionFile;
|
|
214
231
|
const resumeSessionId = normalizedSessionConfig.resumeSessionId;
|
|
@@ -246,7 +263,10 @@ export async function runSingleAgent(
|
|
|
246
263
|
|
|
247
264
|
if (agent.runtime === "claude") {
|
|
248
265
|
if (resolveClaudeRuntimeMode(defaultCwd) === "sdk") {
|
|
266
|
+
// SDK query.close() has no process-exit acknowledgement.
|
|
267
|
+
currentResourceObserver()("unknown");
|
|
249
268
|
const { runClaudeAgentViaSdk } = await import("./claude-sdk-runner.js");
|
|
269
|
+
if (signal?.aborted) throw new Error("Subagent was aborted");
|
|
250
270
|
return runClaudeAgentViaSdk(
|
|
251
271
|
defaultCwd,
|
|
252
272
|
agent,
|
|
@@ -300,6 +320,7 @@ async function runClaudeAgent(
|
|
|
300
320
|
sidecarSessionFile?: string,
|
|
301
321
|
onDiagnostic?: RunnerDiagnosticSink,
|
|
302
322
|
): Promise<SingleResult> {
|
|
323
|
+
const observeResource = currentResourceObserver();
|
|
303
324
|
try {
|
|
304
325
|
validateClaudeRuntimeModel(agent.model);
|
|
305
326
|
} catch (err: any) {
|
|
@@ -392,7 +413,9 @@ async function runClaudeAgent(
|
|
|
392
413
|
});
|
|
393
414
|
|
|
394
415
|
const exitCode = await new Promise<number>((resolve) => {
|
|
416
|
+
if (signal?.aborted) throw new Error("Subagent was aborted");
|
|
395
417
|
const proc = spawn("claude", args, { cwd: defaultCwd, shell: false, stdio: ["ignore", "pipe", "pipe"] });
|
|
418
|
+
observeResource("active");
|
|
396
419
|
let buffer = "";
|
|
397
420
|
let procExited = false;
|
|
398
421
|
let settled = false;
|
|
@@ -564,6 +587,7 @@ async function runClaudeAgent(
|
|
|
564
587
|
});
|
|
565
588
|
|
|
566
589
|
proc.on("exit", (code, terminationSignal) => {
|
|
590
|
+
observeResource("settled");
|
|
567
591
|
procExited = true;
|
|
568
592
|
lastExitCode = normalizeChildExitCode(code, terminationSignal, lastExitCode);
|
|
569
593
|
lastProcessSignal = terminationSignal;
|
|
@@ -583,6 +607,7 @@ async function runClaudeAgent(
|
|
|
583
607
|
});
|
|
584
608
|
|
|
585
609
|
proc.on("close", (code, terminationSignal) => {
|
|
610
|
+
observeResource("settled");
|
|
586
611
|
procExited = true;
|
|
587
612
|
lastProcessSignal = terminationSignal ?? lastProcessSignal;
|
|
588
613
|
diagnose({ event: "close", childPid: proc.pid, code, signal: terminationSignal });
|
|
@@ -593,6 +618,7 @@ async function runClaudeAgent(
|
|
|
593
618
|
});
|
|
594
619
|
|
|
595
620
|
proc.on("error", (error) => {
|
|
621
|
+
observeResource(proc.pid ? "unknown" : "settled");
|
|
596
622
|
procExited = true;
|
|
597
623
|
stderrBuf += `[runner] process error: ${error?.message || String(error)}\n`;
|
|
598
624
|
diagnose({
|
|
@@ -658,6 +684,7 @@ async function runPiAgent(
|
|
|
658
684
|
persistedSessionBaseOffset = 0,
|
|
659
685
|
onDiagnostic?: RunnerDiagnosticSink,
|
|
660
686
|
): Promise<SingleResult> {
|
|
687
|
+
const observeResource = currentResourceObserver();
|
|
661
688
|
const args: string[] = ["--mode", "json", "-p"];
|
|
662
689
|
if (sessionFile) args.push("--session", sessionFile);
|
|
663
690
|
else args.push("--no-session");
|
|
@@ -721,7 +748,9 @@ async function runPiAgent(
|
|
|
721
748
|
args.push(normalizeTaskForSubagentPrompt(task));
|
|
722
749
|
|
|
723
750
|
const exitCode = await new Promise<number>((resolve) => {
|
|
751
|
+
if (signal?.aborted) throw new Error("Subagent was aborted");
|
|
724
752
|
const proc = spawn("pi", args, { cwd: defaultCwd, shell: false, stdio: ["ignore", "pipe", "pipe"] });
|
|
753
|
+
observeResource("active");
|
|
725
754
|
let buffer = "";
|
|
726
755
|
let procExited = false;
|
|
727
756
|
let settled = false;
|
|
@@ -1089,6 +1118,7 @@ async function runPiAgent(
|
|
|
1089
1118
|
});
|
|
1090
1119
|
|
|
1091
1120
|
proc.on("exit", (code, terminationSignal) => {
|
|
1121
|
+
observeResource("settled");
|
|
1092
1122
|
procExited = true;
|
|
1093
1123
|
lastExitCode = normalizeChildExitCode(code, terminationSignal, lastExitCode);
|
|
1094
1124
|
lastProcessSignal = terminationSignal;
|
|
@@ -1105,6 +1135,7 @@ async function runPiAgent(
|
|
|
1105
1135
|
});
|
|
1106
1136
|
|
|
1107
1137
|
proc.on("close", (code, terminationSignal) => {
|
|
1138
|
+
observeResource("settled");
|
|
1108
1139
|
procExited = true;
|
|
1109
1140
|
lastProcessSignal = terminationSignal ?? lastProcessSignal;
|
|
1110
1141
|
diagnose({ event: "close", childPid: proc.pid, code, signal: terminationSignal });
|
|
@@ -1113,6 +1144,7 @@ async function runPiAgent(
|
|
|
1113
1144
|
});
|
|
1114
1145
|
|
|
1115
1146
|
proc.on("error", (error) => {
|
|
1147
|
+
observeResource(proc.pid ? "unknown" : "settled");
|
|
1116
1148
|
procExited = true;
|
|
1117
1149
|
appendStderrDiagnostic(currentResult, `process error: ${error?.message || String(error)}`);
|
|
1118
1150
|
diagnose({
|
package/store.ts
CHANGED
|
@@ -18,6 +18,7 @@ import type { WidgetRenderCtx } from "./widget.js";
|
|
|
18
18
|
export const COLLAPSED_ITEM_COUNT = 10;
|
|
19
19
|
|
|
20
20
|
export interface SubagentStore {
|
|
21
|
+
asyncTasks?: import("./async-task-lifecycle.js").SubagentAsyncTasks;
|
|
21
22
|
/** True after session_shutdown; async callbacks must not use captured runtime APIs. */
|
|
22
23
|
disposed: boolean;
|
|
23
24
|
commandRuns: Map<number, CommandRunState>;
|
package/tool-execute.ts
CHANGED
|
@@ -10,6 +10,14 @@ import * as fs from "node:fs";
|
|
|
10
10
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
11
11
|
import { createRunActivityRecorder } from "./activity.js";
|
|
12
12
|
import { type AgentConfig, discoverAgents } from "./agents.js";
|
|
13
|
+
import {
|
|
14
|
+
assertAsyncAdmission,
|
|
15
|
+
asyncInvocationDetails,
|
|
16
|
+
completeAsyncInvocation,
|
|
17
|
+
discardRemovedAsyncInvocation,
|
|
18
|
+
isExpectedTrackedCancellation,
|
|
19
|
+
retainAsyncCompletion,
|
|
20
|
+
} from "./async-task-lifecycle.js";
|
|
13
21
|
import { parseSubagentToolCommand, SUBAGENT_CLI_HELP_TEXT } from "./cli.js";
|
|
14
22
|
import {
|
|
15
23
|
DEFAULT_TURN_COUNT,
|
|
@@ -453,6 +461,7 @@ function buildRunStartMessage(runState: CommandRunState, status: "started" | "re
|
|
|
453
461
|
`\nContext: ${contextLabel} · turn ${runState.turnCount}`,
|
|
454
462
|
display: false,
|
|
455
463
|
details: {
|
|
464
|
+
...asyncInvocationDetails(),
|
|
456
465
|
runId: runState.id,
|
|
457
466
|
agent: runState.agent,
|
|
458
467
|
task: runState.task,
|
|
@@ -515,7 +524,8 @@ function buildRunCompletionMessage(finalized: FinalizedRun, options?: { display?
|
|
|
515
524
|
const { runState, result, rawOutput } = finalized;
|
|
516
525
|
const usage = result ? formatUsageStats(result.usage, result.model) : "";
|
|
517
526
|
const terminalLabel = getFinalizedRunLabel(finalized);
|
|
518
|
-
|
|
527
|
+
completeAsyncInvocation(terminalLabel);
|
|
528
|
+
return retainAsyncCompletion({
|
|
519
529
|
customType: "subagent-tool" as const,
|
|
520
530
|
content:
|
|
521
531
|
`[subagent:${runState.agent}#${runState.id}] ${terminalLabel}` +
|
|
@@ -525,6 +535,7 @@ function buildRunCompletionMessage(finalized: FinalizedRun, options?: { display?
|
|
|
525
535
|
`\n\n${rawOutput}`,
|
|
526
536
|
display: options?.display ?? true,
|
|
527
537
|
details: {
|
|
538
|
+
...asyncInvocationDetails(),
|
|
528
539
|
runId: runState.id,
|
|
529
540
|
agent: runState.agent,
|
|
530
541
|
task: runState.task,
|
|
@@ -555,12 +566,13 @@ function buildRunCompletionMessage(finalized: FinalizedRun, options?: { display?
|
|
|
555
566
|
claudeSessionId: runState.claudeSessionId,
|
|
556
567
|
claudeProjectDir: runState.claudeProjectDir,
|
|
557
568
|
},
|
|
558
|
-
};
|
|
569
|
+
});
|
|
559
570
|
}
|
|
560
571
|
|
|
561
572
|
function buildEscalationMessage(runState: CommandRunState, escalationMessage: string, result: SingleResult) {
|
|
573
|
+
completeAsyncInvocation("completed");
|
|
562
574
|
const usage = formatUsageStats(result.usage, result.model);
|
|
563
|
-
return {
|
|
575
|
+
return retainAsyncCompletion({
|
|
564
576
|
customType: "subagent-tool" as const,
|
|
565
577
|
content:
|
|
566
578
|
`[subagent:${runState.agent}#${runState.id}] escalated` +
|
|
@@ -569,6 +581,7 @@ function buildEscalationMessage(runState: CommandRunState, escalationMessage: st
|
|
|
569
581
|
`\n\n[ESCALATION] ${escalationMessage}`,
|
|
570
582
|
display: true,
|
|
571
583
|
details: {
|
|
584
|
+
...asyncInvocationDetails(),
|
|
572
585
|
runId: runState.id,
|
|
573
586
|
agent: runState.agent,
|
|
574
587
|
task: runState.task,
|
|
@@ -592,7 +605,7 @@ function buildEscalationMessage(runState: CommandRunState, escalationMessage: st
|
|
|
592
605
|
claudeSessionId: runState.claudeSessionId,
|
|
593
606
|
claudeProjectDir: runState.claudeProjectDir,
|
|
594
607
|
},
|
|
595
|
-
};
|
|
608
|
+
});
|
|
596
609
|
}
|
|
597
610
|
|
|
598
611
|
function buildStrongWaitMessage(runId: number): string {
|
|
@@ -660,6 +673,7 @@ function formatBatchSummary(
|
|
|
660
673
|
runs: CommandRunState[],
|
|
661
674
|
terminalStatus: "completed" | "error" | "aborted",
|
|
662
675
|
): string {
|
|
676
|
+
completeAsyncInvocation(terminalStatus);
|
|
663
677
|
const statuses = runs.map((run) => getSubagentRunStatusLabel(run.status, run.errorClass));
|
|
664
678
|
const headerStatus = runs.map((run, index) => `#${run.id} ${statuses[index]}`).join(", ");
|
|
665
679
|
const outcomeCounts = formatOutcomeCounts(
|
|
@@ -680,6 +694,7 @@ function formatPipelineSummary(
|
|
|
680
694
|
stepResults: PipelineStepResult[],
|
|
681
695
|
terminalStatus: "completed" | "stopped" | "error" | "aborted",
|
|
682
696
|
): string {
|
|
697
|
+
completeAsyncInvocation(terminalStatus);
|
|
683
698
|
const outcomeCounts = formatOutcomeCounts(
|
|
684
699
|
stepResults.filter((step) => step.status === "error").length,
|
|
685
700
|
stepResults.filter((step) => step.status === "aborted").length,
|
|
@@ -817,6 +832,7 @@ function finalizeRunError(runState: CommandRunState, error: unknown): FinalizedR
|
|
|
817
832
|
}
|
|
818
833
|
|
|
819
834
|
export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore) {
|
|
835
|
+
pi = store.asyncTasks?.wrap(pi) ?? pi;
|
|
820
836
|
const execute = async (
|
|
821
837
|
_toolCallId: string,
|
|
822
838
|
params: Record<string, any>,
|
|
@@ -931,7 +947,7 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
931
947
|
const hasChain = asyncAction === "chain";
|
|
932
948
|
const hasSingle = asyncAction === "run" || asyncAction === "continue";
|
|
933
949
|
const mode: LaunchMode = hasBatch ? "batch" : hasChain ? "chain" : "single";
|
|
934
|
-
const shouldRunAsync = isInteractiveTuiContext(ctx);
|
|
950
|
+
const shouldRunAsync = isInteractiveTuiContext(ctx) || store.asyncTasks?.provider.accepting === true;
|
|
935
951
|
const makeDetails = (
|
|
936
952
|
modeOverride: LaunchMode = mode,
|
|
937
953
|
results: SingleResult[] = [],
|
|
@@ -1389,6 +1405,7 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1389
1405
|
}
|
|
1390
1406
|
|
|
1391
1407
|
function registerRunLaunch(config: RunLaunchConfig): CommandRunState {
|
|
1408
|
+
assertAsyncAdmission();
|
|
1392
1409
|
const initialDisplayTask = buildSubagentDisplayTaskFallback(config.taskForDisplay);
|
|
1393
1410
|
let runState: CommandRunState;
|
|
1394
1411
|
if (config.existingRunState) {
|
|
@@ -1523,6 +1540,7 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1523
1540
|
resumeSessionId: runState.claudeSessionId,
|
|
1524
1541
|
sidecarSessionFile: runState.sessionFile,
|
|
1525
1542
|
persistedSessionBaseOffset: runState.persistedSessionBaseOffset,
|
|
1543
|
+
...(store.asyncTasks?.provider.supported ? { asyncRunId: runState.id } : {}),
|
|
1526
1544
|
onDiagnostic: createRunDiagnosticSink(pi, runState),
|
|
1527
1545
|
},
|
|
1528
1546
|
);
|
|
@@ -1678,7 +1696,10 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1678
1696
|
void (async () => {
|
|
1679
1697
|
try {
|
|
1680
1698
|
const finalized = await launchRunInBackground(runState, taskForAgent);
|
|
1681
|
-
if (runState.removed || store.disposed)
|
|
1699
|
+
if (runState.removed || store.disposed) {
|
|
1700
|
+
discardRemovedAsyncInvocation(finalized.rawOutput);
|
|
1701
|
+
return;
|
|
1702
|
+
}
|
|
1682
1703
|
updateCommandRunsWidget(store);
|
|
1683
1704
|
|
|
1684
1705
|
if (finalized.result?.exitCode === ESCALATION_EXIT_CODE) {
|
|
@@ -1704,12 +1725,16 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1704
1725
|
if (entry) entry.pendingCompletion = makePendingCompletion(completionMessage, true);
|
|
1705
1726
|
}
|
|
1706
1727
|
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1728
|
+
if (terminalLabel !== "aborted" || !isExpectedTrackedCancellation())
|
|
1729
|
+
ctx.ui?.notify?.(
|
|
1730
|
+
`subagent tool run #${runState.id} (${resolvedAgent}) ${terminalLabel}`,
|
|
1731
|
+
terminalLabel === "completed" ? "info" : terminalLabel === "aborted" ? "warning" : "error",
|
|
1732
|
+
);
|
|
1711
1733
|
} catch (error: unknown) {
|
|
1712
|
-
if (runState.removed || store.disposed)
|
|
1734
|
+
if (runState.removed || store.disposed) {
|
|
1735
|
+
discardRemovedAsyncInvocation(String(error));
|
|
1736
|
+
return;
|
|
1737
|
+
}
|
|
1713
1738
|
const finalized = finalizeRunError(runState, error);
|
|
1714
1739
|
const terminalLabel = getFinalizedRunLabel(finalized);
|
|
1715
1740
|
const errorMessage = buildRunCompletionMessage(finalized);
|
|
@@ -1720,10 +1745,11 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1720
1745
|
const entry = store.globalLiveRuns.get(runState.id);
|
|
1721
1746
|
if (entry) entry.pendingCompletion = makePendingCompletion(errorMessage, true);
|
|
1722
1747
|
}
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1748
|
+
if (terminalLabel !== "aborted" || !isExpectedTrackedCancellation())
|
|
1749
|
+
ctx.ui?.notify?.(
|
|
1750
|
+
`subagent tool run #${runState.id} ${terminalLabel}: ${runState.lastLine}`,
|
|
1751
|
+
terminalLabel === "aborted" ? "warning" : "error",
|
|
1752
|
+
);
|
|
1727
1753
|
updateCommandRunsWidget(store);
|
|
1728
1754
|
} finally {
|
|
1729
1755
|
clearRunAbortState(runState);
|
|
@@ -1872,12 +1898,14 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1872
1898
|
content,
|
|
1873
1899
|
display: true,
|
|
1874
1900
|
details: {
|
|
1901
|
+
...asyncInvocationDetails(),
|
|
1875
1902
|
batchId,
|
|
1876
1903
|
runIds: batch.runIds,
|
|
1877
1904
|
status: batchTerminalStatus === "completed" ? "done" : batchTerminalStatus,
|
|
1878
1905
|
runSummaries: orderedRuns.map((run) => buildRunAnalyticsSummary(run)),
|
|
1879
1906
|
},
|
|
1880
1907
|
};
|
|
1908
|
+
retainAsyncCompletion(message);
|
|
1881
1909
|
retireFinishedGroup(store, snapshotBatchGroup(store, batch, batchTerminalStatus));
|
|
1882
1910
|
if (isInOriginSession(ctx, batch.originSessionFile)) {
|
|
1883
1911
|
pi.sendMessage(message, { deliverAs: "followUp", triggerTurn: true });
|
|
@@ -1901,14 +1929,15 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1901
1929
|
updateWidget: false,
|
|
1902
1930
|
removalReason: "trim",
|
|
1903
1931
|
});
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1932
|
+
if (batchTerminalStatus !== "aborted" || !isExpectedTrackedCancellation())
|
|
1933
|
+
ctx.ui?.notify?.(
|
|
1934
|
+
batchTerminalStatus === "error"
|
|
1935
|
+
? `subagent batch ${batchId} finished with errors`
|
|
1936
|
+
: batchTerminalStatus === "aborted"
|
|
1937
|
+
? `subagent batch ${batchId} aborted`
|
|
1938
|
+
: `subagent batch ${batchId} completed`,
|
|
1939
|
+
batchTerminalStatus === "error" ? "error" : batchTerminalStatus === "aborted" ? "warning" : "info",
|
|
1940
|
+
);
|
|
1912
1941
|
}
|
|
1913
1942
|
} catch (error: unknown) {
|
|
1914
1943
|
const finalized = finalizeRunError(runState, error);
|
|
@@ -1923,12 +1952,14 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1923
1952
|
content: formatBatchSummary(batchId, orderedRuns, batchTerminalStatus),
|
|
1924
1953
|
display: true,
|
|
1925
1954
|
details: {
|
|
1955
|
+
...asyncInvocationDetails(),
|
|
1926
1956
|
batchId,
|
|
1927
1957
|
runIds: batch.runIds,
|
|
1928
1958
|
status: batchTerminalStatus,
|
|
1929
1959
|
runSummaries: orderedRuns.map((run) => buildRunAnalyticsSummary(run)),
|
|
1930
1960
|
},
|
|
1931
1961
|
};
|
|
1962
|
+
retainAsyncCompletion(message);
|
|
1932
1963
|
retireFinishedGroup(store, snapshotBatchGroup(store, batch, batchTerminalStatus));
|
|
1933
1964
|
if (isInOriginSession(ctx, batch.originSessionFile)) {
|
|
1934
1965
|
pi.sendMessage(message, { deliverAs: "followUp", triggerTurn: true });
|
|
@@ -2247,12 +2278,14 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
2247
2278
|
content: formatPipelineSummary(pipelineId, pipeline.stepResults, terminalStatus),
|
|
2248
2279
|
display: true,
|
|
2249
2280
|
details: {
|
|
2281
|
+
...asyncInvocationDetails(),
|
|
2250
2282
|
pipelineId,
|
|
2251
2283
|
stepRunIds: pipeline.stepRunIds,
|
|
2252
2284
|
status: terminalStatus === "completed" ? "done" : terminalStatus,
|
|
2253
2285
|
runSummaries: orderedRuns.map((run) => buildRunAnalyticsSummary(run)),
|
|
2254
2286
|
},
|
|
2255
2287
|
};
|
|
2288
|
+
retainAsyncCompletion(message);
|
|
2256
2289
|
retireFinishedGroup(store, snapshotPipeline(pipeline, terminalStatus));
|
|
2257
2290
|
if (isInOriginSession(ctx, pipeline.originSessionFile)) {
|
|
2258
2291
|
pi.sendMessage(message, { deliverAs: "followUp", triggerTurn: true });
|
|
@@ -2301,5 +2334,20 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
2301
2334
|
};
|
|
2302
2335
|
};
|
|
2303
2336
|
|
|
2304
|
-
return execute
|
|
2337
|
+
return (...args: Parameters<typeof execute>) => {
|
|
2338
|
+
const parsed = parseSubagentToolCommand(args[1].command, { knownRunIds: [...store.commandRuns.keys()] });
|
|
2339
|
+
if (
|
|
2340
|
+
parsed.type !== "params" ||
|
|
2341
|
+
!["run", "continue", "batch", "chain"].includes(String(parsed.params.asyncAction ?? "run")) ||
|
|
2342
|
+
!store.asyncTasks
|
|
2343
|
+
)
|
|
2344
|
+
return execute(...args);
|
|
2345
|
+
return store.asyncTasks.invoke(
|
|
2346
|
+
String(args[1].command).slice(0, 500),
|
|
2347
|
+
args[0],
|
|
2348
|
+
() => execute(...args),
|
|
2349
|
+
false,
|
|
2350
|
+
args[2],
|
|
2351
|
+
);
|
|
2352
|
+
};
|
|
2305
2353
|
}
|