@ryan_nookpi/pi-extension-subagent 0.5.6 → 0.5.8
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/README.md +3 -3
- package/async-task-lifecycle.ts +270 -0
- package/async-task-provider.ts +623 -0
- package/cli.ts +9 -2
- package/commands.ts +446 -416
- package/index.ts +20 -2
- package/package.json +3 -1
- package/run-utils.ts +3 -3
- package/runner.ts +33 -1
- package/store.ts +1 -0
- package/tool-execute.ts +174 -68
- package/types.ts +4 -1
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,
|
|
@@ -502,14 +511,21 @@ function recordBatchResult(batch: BatchGroupState, finalized: FinalizedRun): voi
|
|
|
502
511
|
batch.completedRunIds.add(runId);
|
|
503
512
|
if (status === "aborted") batch.abortedRunIds.add(runId);
|
|
504
513
|
else if (status === "error") batch.failedRunIds.add(runId);
|
|
505
|
-
batch.
|
|
514
|
+
batch.completedRuns.set(runId, { ...finalized.runState, abortController: undefined });
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function getCompletedBatchRuns(batch: BatchGroupState): CommandRunState[] {
|
|
518
|
+
return batch.runIds
|
|
519
|
+
.map((runId) => batch.completedRuns.get(runId))
|
|
520
|
+
.filter((run): run is CommandRunState => Boolean(run));
|
|
506
521
|
}
|
|
507
522
|
|
|
508
523
|
function buildRunCompletionMessage(finalized: FinalizedRun, options?: { display?: boolean }) {
|
|
509
524
|
const { runState, result, rawOutput } = finalized;
|
|
510
525
|
const usage = result ? formatUsageStats(result.usage, result.model) : "";
|
|
511
526
|
const terminalLabel = getFinalizedRunLabel(finalized);
|
|
512
|
-
|
|
527
|
+
completeAsyncInvocation(terminalLabel);
|
|
528
|
+
return retainAsyncCompletion({
|
|
513
529
|
customType: "subagent-tool" as const,
|
|
514
530
|
content:
|
|
515
531
|
`[subagent:${runState.agent}#${runState.id}] ${terminalLabel}` +
|
|
@@ -519,6 +535,7 @@ function buildRunCompletionMessage(finalized: FinalizedRun, options?: { display?
|
|
|
519
535
|
`\n\n${rawOutput}`,
|
|
520
536
|
display: options?.display ?? true,
|
|
521
537
|
details: {
|
|
538
|
+
...asyncInvocationDetails(),
|
|
522
539
|
runId: runState.id,
|
|
523
540
|
agent: runState.agent,
|
|
524
541
|
task: runState.task,
|
|
@@ -549,12 +566,13 @@ function buildRunCompletionMessage(finalized: FinalizedRun, options?: { display?
|
|
|
549
566
|
claudeSessionId: runState.claudeSessionId,
|
|
550
567
|
claudeProjectDir: runState.claudeProjectDir,
|
|
551
568
|
},
|
|
552
|
-
};
|
|
569
|
+
});
|
|
553
570
|
}
|
|
554
571
|
|
|
555
572
|
function buildEscalationMessage(runState: CommandRunState, escalationMessage: string, result: SingleResult) {
|
|
573
|
+
completeAsyncInvocation("completed");
|
|
556
574
|
const usage = formatUsageStats(result.usage, result.model);
|
|
557
|
-
return {
|
|
575
|
+
return retainAsyncCompletion({
|
|
558
576
|
customType: "subagent-tool" as const,
|
|
559
577
|
content:
|
|
560
578
|
`[subagent:${runState.agent}#${runState.id}] escalated` +
|
|
@@ -563,6 +581,7 @@ function buildEscalationMessage(runState: CommandRunState, escalationMessage: st
|
|
|
563
581
|
`\n\n[ESCALATION] ${escalationMessage}`,
|
|
564
582
|
display: true,
|
|
565
583
|
details: {
|
|
584
|
+
...asyncInvocationDetails(),
|
|
566
585
|
runId: runState.id,
|
|
567
586
|
agent: runState.agent,
|
|
568
587
|
task: runState.task,
|
|
@@ -586,7 +605,7 @@ function buildEscalationMessage(runState: CommandRunState, escalationMessage: st
|
|
|
586
605
|
claudeSessionId: runState.claudeSessionId,
|
|
587
606
|
claudeProjectDir: runState.claudeProjectDir,
|
|
588
607
|
},
|
|
589
|
-
};
|
|
608
|
+
});
|
|
590
609
|
}
|
|
591
610
|
|
|
592
611
|
function buildStrongWaitMessage(runId: number): string {
|
|
@@ -654,6 +673,7 @@ function formatBatchSummary(
|
|
|
654
673
|
runs: CommandRunState[],
|
|
655
674
|
terminalStatus: "completed" | "error" | "aborted",
|
|
656
675
|
): string {
|
|
676
|
+
completeAsyncInvocation(terminalStatus);
|
|
657
677
|
const statuses = runs.map((run) => getSubagentRunStatusLabel(run.status, run.errorClass));
|
|
658
678
|
const headerStatus = runs.map((run, index) => `#${run.id} ${statuses[index]}`).join(", ");
|
|
659
679
|
const outcomeCounts = formatOutcomeCounts(
|
|
@@ -674,6 +694,7 @@ function formatPipelineSummary(
|
|
|
674
694
|
stepResults: PipelineStepResult[],
|
|
675
695
|
terminalStatus: "completed" | "stopped" | "error" | "aborted",
|
|
676
696
|
): string {
|
|
697
|
+
completeAsyncInvocation(terminalStatus);
|
|
677
698
|
const outcomeCounts = formatOutcomeCounts(
|
|
678
699
|
stepResults.filter((step) => step.status === "error").length,
|
|
679
700
|
stepResults.filter((step) => step.status === "aborted").length,
|
|
@@ -811,6 +832,7 @@ function finalizeRunError(runState: CommandRunState, error: unknown): FinalizedR
|
|
|
811
832
|
}
|
|
812
833
|
|
|
813
834
|
export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore) {
|
|
835
|
+
pi = store.asyncTasks?.wrap(pi) ?? pi;
|
|
814
836
|
const execute = async (
|
|
815
837
|
_toolCallId: string,
|
|
816
838
|
params: Record<string, any>,
|
|
@@ -925,7 +947,7 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
925
947
|
const hasChain = asyncAction === "chain";
|
|
926
948
|
const hasSingle = asyncAction === "run" || asyncAction === "continue";
|
|
927
949
|
const mode: LaunchMode = hasBatch ? "batch" : hasChain ? "chain" : "single";
|
|
928
|
-
const shouldRunAsync = isInteractiveTuiContext(ctx);
|
|
950
|
+
const shouldRunAsync = isInteractiveTuiContext(ctx) || store.asyncTasks?.provider.accepting === true;
|
|
929
951
|
const makeDetails = (
|
|
930
952
|
modeOverride: LaunchMode = mode,
|
|
931
953
|
results: SingleResult[] = [],
|
|
@@ -1040,6 +1062,45 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1040
1062
|
};
|
|
1041
1063
|
}
|
|
1042
1064
|
|
|
1065
|
+
let abortGroupTarget: { groupId: string; kind: "batch" | "chain" } | undefined;
|
|
1066
|
+
if (asyncAction === "abort" && typeof cmdParams.groupId === "string") {
|
|
1067
|
+
evictStaleFinishedGroups(store);
|
|
1068
|
+
const groupId = cmdParams.groupId;
|
|
1069
|
+
const finished = store.finishedGroups.get(groupId);
|
|
1070
|
+
if (finished) {
|
|
1071
|
+
return {
|
|
1072
|
+
content: [{ type: "text", text: `Subagent ${finished.kind} ${groupId} has already finished.` }],
|
|
1073
|
+
details: makeDetails("single"),
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
const batch = store.batchGroups.get(groupId);
|
|
1078
|
+
const pipeline = store.pipelines.get(groupId);
|
|
1079
|
+
if (!batch && !pipeline) {
|
|
1080
|
+
return {
|
|
1081
|
+
content: [{ type: "text", text: `Unknown subagent group "${groupId}".` }],
|
|
1082
|
+
details: makeDetails("single"),
|
|
1083
|
+
isError: true,
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
abortGroupTarget = { groupId, kind: batch ? "batch" : "chain" };
|
|
1088
|
+
if (pipeline) pipeline.abortRequested = true;
|
|
1089
|
+
const groupRunIds = (batch?.runIds ?? pipeline?.stepRunIds ?? []).filter((runId) => {
|
|
1090
|
+
const run = store.globalLiveRuns.get(runId)?.runState ?? store.commandRuns.get(runId);
|
|
1091
|
+
return batch ? run?.batchId === groupId : run?.pipelineId === groupId;
|
|
1092
|
+
});
|
|
1093
|
+
if (groupRunIds.length === 0) {
|
|
1094
|
+
return {
|
|
1095
|
+
content: [
|
|
1096
|
+
{ type: "text", text: `Subagent ${abortGroupTarget.kind} ${groupId} has no started runs to abort.` },
|
|
1097
|
+
],
|
|
1098
|
+
details: makeDetails("single"),
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
cmdParams.runIds = groupRunIds;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1043
1104
|
if ((asyncAction === "status" || asyncAction === "detail") && typeof cmdParams.groupId === "string") {
|
|
1044
1105
|
evictStaleFinishedGroups(store);
|
|
1045
1106
|
const groupId = cmdParams.groupId;
|
|
@@ -1178,7 +1239,9 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1178
1239
|
const unknown: number[] = [];
|
|
1179
1240
|
|
|
1180
1241
|
for (const runId of targetRunIds) {
|
|
1181
|
-
const run =
|
|
1242
|
+
const run = abortGroupTarget
|
|
1243
|
+
? (store.globalLiveRuns.get(runId)?.runState ?? store.commandRuns.get(runId))
|
|
1244
|
+
: store.commandRuns.get(runId);
|
|
1182
1245
|
if (!run) {
|
|
1183
1246
|
unknown.push(runId);
|
|
1184
1247
|
continue;
|
|
@@ -1200,7 +1263,13 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1200
1263
|
updateCommandRunsWidget(store, ctx as WidgetRenderCtx);
|
|
1201
1264
|
}
|
|
1202
1265
|
|
|
1203
|
-
if (
|
|
1266
|
+
if (
|
|
1267
|
+
!abortGroupTarget &&
|
|
1268
|
+
targetRunIds.length === 1 &&
|
|
1269
|
+
aborting.length === 1 &&
|
|
1270
|
+
notRunning.length === 0 &&
|
|
1271
|
+
unknown.length === 0
|
|
1272
|
+
) {
|
|
1204
1273
|
return {
|
|
1205
1274
|
content: [{ type: "text", text: `Aborting subagent run #${aborting[0]}...` }],
|
|
1206
1275
|
details: makeDetails("single"),
|
|
@@ -1208,6 +1277,9 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1208
1277
|
}
|
|
1209
1278
|
|
|
1210
1279
|
const lines: string[] = [];
|
|
1280
|
+
if (abortGroupTarget) {
|
|
1281
|
+
lines.push(`Subagent ${abortGroupTarget.kind} ${abortGroupTarget.groupId}:`);
|
|
1282
|
+
}
|
|
1211
1283
|
if (aborting.length > 0) lines.push(`Aborting: ${aborting.map((id) => `#${id}`).join(", ")}.`);
|
|
1212
1284
|
if (notRunning.length > 0) lines.push(`Not running: ${notRunning.map((id) => `#${id}`).join(", ")}.`);
|
|
1213
1285
|
if (unknown.length > 0) lines.push(`Unknown: ${unknown.map((id) => `#${id}`).join(", ")}.`);
|
|
@@ -1333,6 +1405,7 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1333
1405
|
}
|
|
1334
1406
|
|
|
1335
1407
|
function registerRunLaunch(config: RunLaunchConfig): CommandRunState {
|
|
1408
|
+
assertAsyncAdmission();
|
|
1336
1409
|
const initialDisplayTask = buildSubagentDisplayTaskFallback(config.taskForDisplay);
|
|
1337
1410
|
let runState: CommandRunState;
|
|
1338
1411
|
if (config.existingRunState) {
|
|
@@ -1413,6 +1486,13 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1413
1486
|
store.recentLaunchTimestamps.delete(runId);
|
|
1414
1487
|
}
|
|
1415
1488
|
|
|
1489
|
+
function cleanupGroupRunAfterFinalDelivery(runId: number, groupId: string, kind: "batch" | "chain") {
|
|
1490
|
+
const run = store.globalLiveRuns.get(runId)?.runState ?? store.commandRuns.get(runId);
|
|
1491
|
+
const ownsGroup = kind === "batch" ? run?.batchId === groupId : run?.pipelineId === groupId;
|
|
1492
|
+
if (run && !ownsGroup) return;
|
|
1493
|
+
cleanupRunAfterFinalDelivery(runId);
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1416
1496
|
function launchRunInBackground(runState: CommandRunState, taskForAgent: string): Promise<FinalizedRun> {
|
|
1417
1497
|
let claudeCheckpointSent = !!runState.claudeSessionId;
|
|
1418
1498
|
const abortSignal = runState.abortController?.signal;
|
|
@@ -1460,6 +1540,7 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1460
1540
|
resumeSessionId: runState.claudeSessionId,
|
|
1461
1541
|
sidecarSessionFile: runState.sessionFile,
|
|
1462
1542
|
persistedSessionBaseOffset: runState.persistedSessionBaseOffset,
|
|
1543
|
+
...(store.asyncTasks?.provider.supported ? { asyncRunId: runState.id } : {}),
|
|
1463
1544
|
onDiagnostic: createRunDiagnosticSink(pi, runState),
|
|
1464
1545
|
},
|
|
1465
1546
|
);
|
|
@@ -1615,7 +1696,10 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1615
1696
|
void (async () => {
|
|
1616
1697
|
try {
|
|
1617
1698
|
const finalized = await launchRunInBackground(runState, taskForAgent);
|
|
1618
|
-
if (runState.removed || store.disposed)
|
|
1699
|
+
if (runState.removed || store.disposed) {
|
|
1700
|
+
discardRemovedAsyncInvocation(finalized.rawOutput);
|
|
1701
|
+
return;
|
|
1702
|
+
}
|
|
1619
1703
|
updateCommandRunsWidget(store);
|
|
1620
1704
|
|
|
1621
1705
|
if (finalized.result?.exitCode === ESCALATION_EXIT_CODE) {
|
|
@@ -1641,12 +1725,16 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1641
1725
|
if (entry) entry.pendingCompletion = makePendingCompletion(completionMessage, true);
|
|
1642
1726
|
}
|
|
1643
1727
|
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
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
|
+
);
|
|
1648
1733
|
} catch (error: unknown) {
|
|
1649
|
-
if (runState.removed || store.disposed)
|
|
1734
|
+
if (runState.removed || store.disposed) {
|
|
1735
|
+
discardRemovedAsyncInvocation(String(error));
|
|
1736
|
+
return;
|
|
1737
|
+
}
|
|
1650
1738
|
const finalized = finalizeRunError(runState, error);
|
|
1651
1739
|
const terminalLabel = getFinalizedRunLabel(finalized);
|
|
1652
1740
|
const errorMessage = buildRunCompletionMessage(finalized);
|
|
@@ -1657,10 +1745,11 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1657
1745
|
const entry = store.globalLiveRuns.get(runState.id);
|
|
1658
1746
|
if (entry) entry.pendingCompletion = makePendingCompletion(errorMessage, true);
|
|
1659
1747
|
}
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1748
|
+
if (terminalLabel !== "aborted" || !isExpectedTrackedCancellation())
|
|
1749
|
+
ctx.ui?.notify?.(
|
|
1750
|
+
`subagent tool run #${runState.id} ${terminalLabel}: ${runState.lastLine}`,
|
|
1751
|
+
terminalLabel === "aborted" ? "warning" : "error",
|
|
1752
|
+
);
|
|
1664
1753
|
updateCommandRunsWidget(store);
|
|
1665
1754
|
} finally {
|
|
1666
1755
|
clearRunAbortState(runState);
|
|
@@ -1734,7 +1823,7 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1734
1823
|
abortedRunIds: new Set(),
|
|
1735
1824
|
originSessionFile,
|
|
1736
1825
|
createdAt: Date.now(),
|
|
1737
|
-
|
|
1826
|
+
completedRuns: new Map(),
|
|
1738
1827
|
});
|
|
1739
1828
|
updateCommandRunsWidget(store, ctx as WidgetRenderCtx);
|
|
1740
1829
|
|
|
@@ -1750,11 +1839,13 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1750
1839
|
}
|
|
1751
1840
|
}),
|
|
1752
1841
|
);
|
|
1753
|
-
const orderedRuns = runStates.map(({ runState }) => runState);
|
|
1754
1842
|
const batchForSnapshot = store.batchGroups.get(batchId);
|
|
1755
1843
|
if (batchForSnapshot) {
|
|
1756
1844
|
for (const finalized of finalizedRuns) recordBatchResult(batchForSnapshot, finalized);
|
|
1757
1845
|
}
|
|
1846
|
+
const orderedRuns = batchForSnapshot
|
|
1847
|
+
? getCompletedBatchRuns(batchForSnapshot)
|
|
1848
|
+
: runStates.map(({ runState }) => runState);
|
|
1758
1849
|
const terminalStatus = resolveGroupTerminalStatus(
|
|
1759
1850
|
batchForSnapshot?.failedRunIds.size ?? 0,
|
|
1760
1851
|
batchForSnapshot?.abortedRunIds.size ?? 0,
|
|
@@ -1763,7 +1854,7 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1763
1854
|
if (batchForSnapshot) {
|
|
1764
1855
|
retireFinishedGroup(store, snapshotBatchGroup(store, batchForSnapshot, terminalStatus));
|
|
1765
1856
|
}
|
|
1766
|
-
for (const { runState } of runStates)
|
|
1857
|
+
for (const { runState } of runStates) cleanupGroupRunAfterFinalDelivery(runState.id, batchId, "batch");
|
|
1767
1858
|
clearPendingGroupCompletion("batch", batchId);
|
|
1768
1859
|
store.batchGroups.delete(batchId);
|
|
1769
1860
|
trimCommandRunHistory(store, {
|
|
@@ -1799,9 +1890,7 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1799
1890
|
updateCommandRunsWidget(store);
|
|
1800
1891
|
|
|
1801
1892
|
if (batch.completedRunIds.size === batch.runIds.length) {
|
|
1802
|
-
const orderedRuns = batch
|
|
1803
|
-
.map((runId) => store.commandRuns.get(runId))
|
|
1804
|
-
.filter((run): run is CommandRunState => Boolean(run));
|
|
1893
|
+
const orderedRuns = getCompletedBatchRuns(batch);
|
|
1805
1894
|
const batchTerminalStatus = resolveGroupTerminalStatus(batch.failedRunIds.size, batch.abortedRunIds.size);
|
|
1806
1895
|
const content = formatBatchSummary(batchId, orderedRuns, batchTerminalStatus);
|
|
1807
1896
|
const message = {
|
|
@@ -1809,17 +1898,19 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1809
1898
|
content,
|
|
1810
1899
|
display: true,
|
|
1811
1900
|
details: {
|
|
1901
|
+
...asyncInvocationDetails(),
|
|
1812
1902
|
batchId,
|
|
1813
1903
|
runIds: batch.runIds,
|
|
1814
1904
|
status: batchTerminalStatus === "completed" ? "done" : batchTerminalStatus,
|
|
1815
1905
|
runSummaries: orderedRuns.map((run) => buildRunAnalyticsSummary(run)),
|
|
1816
1906
|
},
|
|
1817
1907
|
};
|
|
1908
|
+
retainAsyncCompletion(message);
|
|
1818
1909
|
retireFinishedGroup(store, snapshotBatchGroup(store, batch, batchTerminalStatus));
|
|
1819
1910
|
if (isInOriginSession(ctx, batch.originSessionFile)) {
|
|
1820
1911
|
pi.sendMessage(message, { deliverAs: "followUp", triggerTurn: true });
|
|
1821
1912
|
clearPendingGroupCompletion("batch", batchId);
|
|
1822
|
-
for (const runId of batch.runIds)
|
|
1913
|
+
for (const runId of batch.runIds) cleanupGroupRunAfterFinalDelivery(runId, batchId, "batch");
|
|
1823
1914
|
store.batchGroups.delete(batchId);
|
|
1824
1915
|
} else {
|
|
1825
1916
|
batch.pendingCompletion = makePendingCompletion(message, true);
|
|
@@ -1838,14 +1929,15 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1838
1929
|
updateWidget: false,
|
|
1839
1930
|
removalReason: "trim",
|
|
1840
1931
|
});
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
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
|
+
);
|
|
1849
1941
|
}
|
|
1850
1942
|
} catch (error: unknown) {
|
|
1851
1943
|
const finalized = finalizeRunError(runState, error);
|
|
@@ -1853,26 +1945,26 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1853
1945
|
if (!batch) return;
|
|
1854
1946
|
recordBatchResult(batch, finalized);
|
|
1855
1947
|
if (batch.completedRunIds.size === batch.runIds.length) {
|
|
1856
|
-
const orderedRuns = batch
|
|
1857
|
-
.map((runId) => store.commandRuns.get(runId))
|
|
1858
|
-
.filter((run): run is CommandRunState => Boolean(run));
|
|
1948
|
+
const orderedRuns = getCompletedBatchRuns(batch);
|
|
1859
1949
|
const batchTerminalStatus = resolveGroupTerminalStatus(batch.failedRunIds.size, batch.abortedRunIds.size);
|
|
1860
1950
|
const message = {
|
|
1861
1951
|
customType: "subagent-tool" as const,
|
|
1862
1952
|
content: formatBatchSummary(batchId, orderedRuns, batchTerminalStatus),
|
|
1863
1953
|
display: true,
|
|
1864
1954
|
details: {
|
|
1955
|
+
...asyncInvocationDetails(),
|
|
1865
1956
|
batchId,
|
|
1866
1957
|
runIds: batch.runIds,
|
|
1867
1958
|
status: batchTerminalStatus,
|
|
1868
1959
|
runSummaries: orderedRuns.map((run) => buildRunAnalyticsSummary(run)),
|
|
1869
1960
|
},
|
|
1870
1961
|
};
|
|
1962
|
+
retainAsyncCompletion(message);
|
|
1871
1963
|
retireFinishedGroup(store, snapshotBatchGroup(store, batch, batchTerminalStatus));
|
|
1872
1964
|
if (isInOriginSession(ctx, batch.originSessionFile)) {
|
|
1873
1965
|
pi.sendMessage(message, { deliverAs: "followUp", triggerTurn: true });
|
|
1874
1966
|
clearPendingGroupCompletion("batch", batchId);
|
|
1875
|
-
for (const runId of batch.runIds)
|
|
1967
|
+
for (const runId of batch.runIds) cleanupGroupRunAfterFinalDelivery(runId, batchId, "batch");
|
|
1876
1968
|
store.batchGroups.delete(batchId);
|
|
1877
1969
|
} else {
|
|
1878
1970
|
batch.pendingCompletion = makePendingCompletion(message, true);
|
|
@@ -1931,6 +2023,7 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1931
2023
|
store.pipelines.set(pipelineId, {
|
|
1932
2024
|
pipelineId,
|
|
1933
2025
|
currentIndex: 0,
|
|
2026
|
+
abortRequested: false,
|
|
1934
2027
|
stepRunIds: [],
|
|
1935
2028
|
stepResults: [],
|
|
1936
2029
|
originSessionFile,
|
|
@@ -1952,6 +2045,10 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1952
2045
|
|
|
1953
2046
|
try {
|
|
1954
2047
|
for (let index = 0; index < steps.length; index++) {
|
|
2048
|
+
if (pipeline.abortRequested) {
|
|
2049
|
+
terminalStatus = "aborted";
|
|
2050
|
+
break;
|
|
2051
|
+
}
|
|
1955
2052
|
pipeline.currentIndex = index;
|
|
1956
2053
|
const step = steps[index];
|
|
1957
2054
|
const pipelineReferenceSection =
|
|
@@ -2005,26 +2102,20 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
2005
2102
|
}
|
|
2006
2103
|
finalizedRuns.push(finalized);
|
|
2007
2104
|
|
|
2008
|
-
|
|
2009
|
-
terminalStatus = "aborted";
|
|
2010
|
-
pipeline.stepResults.push({
|
|
2011
|
-
runId: runState.id,
|
|
2012
|
-
agent: runState.agent,
|
|
2013
|
-
task: step.task,
|
|
2014
|
-
output: finalized.rawOutput || "Run removed before pipeline completion.",
|
|
2015
|
-
status: "aborted",
|
|
2016
|
-
});
|
|
2017
|
-
break;
|
|
2018
|
-
}
|
|
2019
|
-
|
|
2020
|
-
const stepStatus = getFinalizedRunStatus(finalized);
|
|
2105
|
+
const stepStatus = runState.removed ? "aborted" : getFinalizedRunStatus(finalized);
|
|
2021
2106
|
pipeline.stepResults.push({
|
|
2022
2107
|
runId: runState.id,
|
|
2023
2108
|
agent: runState.agent,
|
|
2024
2109
|
task: step.task,
|
|
2025
|
-
output:
|
|
2110
|
+
output: runState.removed
|
|
2111
|
+
? finalized.rawOutput || "Run removed before pipeline completion."
|
|
2112
|
+
: finalized.rawOutput,
|
|
2026
2113
|
status: stepStatus,
|
|
2027
2114
|
});
|
|
2115
|
+
if (runState.removed || pipeline.abortRequested) {
|
|
2116
|
+
terminalStatus = "aborted";
|
|
2117
|
+
break;
|
|
2118
|
+
}
|
|
2028
2119
|
previousOutput = finalized.rawOutput;
|
|
2029
2120
|
updateCommandRunsWidget(store);
|
|
2030
2121
|
|
|
@@ -2050,7 +2141,7 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
2050
2141
|
else if (terminalStatus === "completed" && hasAborted) terminalStatus = "aborted";
|
|
2051
2142
|
const content = formatPipelineSummary(pipelineId, pipeline.stepResults, terminalStatus);
|
|
2052
2143
|
retireFinishedGroup(store, snapshotPipeline(pipeline, terminalStatus));
|
|
2053
|
-
for (const runId of pipeline.stepRunIds)
|
|
2144
|
+
for (const runId of pipeline.stepRunIds) cleanupGroupRunAfterFinalDelivery(runId, pipelineId, "chain");
|
|
2054
2145
|
clearPendingGroupCompletion("chain", pipelineId);
|
|
2055
2146
|
store.pipelines.delete(pipelineId);
|
|
2056
2147
|
trimCommandRunHistory(store, {
|
|
@@ -2082,6 +2173,10 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
2082
2173
|
for (let index = 0; index < steps.length; index++) {
|
|
2083
2174
|
const pipeline = store.pipelines.get(pipelineId);
|
|
2084
2175
|
if (!pipeline) return;
|
|
2176
|
+
if (pipeline.abortRequested) {
|
|
2177
|
+
terminalStatus = "aborted";
|
|
2178
|
+
break;
|
|
2179
|
+
}
|
|
2085
2180
|
pipeline.currentIndex = index;
|
|
2086
2181
|
|
|
2087
2182
|
const step = steps[index];
|
|
@@ -2134,26 +2229,20 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
2134
2229
|
} finally {
|
|
2135
2230
|
clearRunAbortState(runState);
|
|
2136
2231
|
}
|
|
2137
|
-
if (runState.removed) {
|
|
2138
|
-
terminalStatus = "aborted";
|
|
2139
|
-
pipeline.stepResults.push({
|
|
2140
|
-
runId: runState.id,
|
|
2141
|
-
agent: runState.agent,
|
|
2142
|
-
task: step.task,
|
|
2143
|
-
output: finalized.rawOutput || "Run removed before pipeline completion.",
|
|
2144
|
-
status: "aborted",
|
|
2145
|
-
});
|
|
2146
|
-
break;
|
|
2147
|
-
}
|
|
2148
|
-
|
|
2149
2232
|
const stepResult: PipelineStepResult = {
|
|
2150
2233
|
runId: runState.id,
|
|
2151
2234
|
agent: runState.agent,
|
|
2152
2235
|
task: step.task,
|
|
2153
|
-
output:
|
|
2154
|
-
|
|
2236
|
+
output: runState.removed
|
|
2237
|
+
? finalized.rawOutput || "Run removed before pipeline completion."
|
|
2238
|
+
: finalized.rawOutput,
|
|
2239
|
+
status: runState.removed ? "aborted" : getFinalizedRunStatus(finalized),
|
|
2155
2240
|
};
|
|
2156
2241
|
pipeline.stepResults.push(stepResult);
|
|
2242
|
+
if (runState.removed || pipeline.abortRequested) {
|
|
2243
|
+
terminalStatus = "aborted";
|
|
2244
|
+
break;
|
|
2245
|
+
}
|
|
2157
2246
|
previousOutput = finalized.rawOutput;
|
|
2158
2247
|
updateCommandRunsWidget(store);
|
|
2159
2248
|
|
|
@@ -2189,17 +2278,19 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
2189
2278
|
content: formatPipelineSummary(pipelineId, pipeline.stepResults, terminalStatus),
|
|
2190
2279
|
display: true,
|
|
2191
2280
|
details: {
|
|
2281
|
+
...asyncInvocationDetails(),
|
|
2192
2282
|
pipelineId,
|
|
2193
2283
|
stepRunIds: pipeline.stepRunIds,
|
|
2194
2284
|
status: terminalStatus === "completed" ? "done" : terminalStatus,
|
|
2195
2285
|
runSummaries: orderedRuns.map((run) => buildRunAnalyticsSummary(run)),
|
|
2196
2286
|
},
|
|
2197
2287
|
};
|
|
2288
|
+
retainAsyncCompletion(message);
|
|
2198
2289
|
retireFinishedGroup(store, snapshotPipeline(pipeline, terminalStatus));
|
|
2199
2290
|
if (isInOriginSession(ctx, pipeline.originSessionFile)) {
|
|
2200
2291
|
pi.sendMessage(message, { deliverAs: "followUp", triggerTurn: true });
|
|
2201
2292
|
clearPendingGroupCompletion("chain", pipelineId);
|
|
2202
|
-
for (const runId of pipeline.stepRunIds)
|
|
2293
|
+
for (const runId of pipeline.stepRunIds) cleanupGroupRunAfterFinalDelivery(runId, pipelineId, "chain");
|
|
2203
2294
|
store.pipelines.delete(pipelineId);
|
|
2204
2295
|
} else {
|
|
2205
2296
|
pipeline.pendingCompletion = makePendingCompletion(message, true);
|
|
@@ -2243,5 +2334,20 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
2243
2334
|
};
|
|
2244
2335
|
};
|
|
2245
2336
|
|
|
2246
|
-
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
|
+
};
|
|
2247
2353
|
}
|
package/types.ts
CHANGED
|
@@ -177,7 +177,8 @@ export interface BatchGroupState {
|
|
|
177
177
|
abortedRunIds: Set<number>;
|
|
178
178
|
originSessionFile: string;
|
|
179
179
|
createdAt: number;
|
|
180
|
-
|
|
180
|
+
/** Immutable member states captured as each batch run completes. */
|
|
181
|
+
completedRuns: Map<number, CommandRunState>;
|
|
181
182
|
pendingCompletion?: PendingCompletion;
|
|
182
183
|
}
|
|
183
184
|
|
|
@@ -192,6 +193,8 @@ export interface PipelineStepResult {
|
|
|
192
193
|
export interface PipelineState {
|
|
193
194
|
pipelineId: string;
|
|
194
195
|
currentIndex: number;
|
|
196
|
+
/** Set by `abort <pipelineId>` to prevent any subsequent step from launching. */
|
|
197
|
+
abortRequested: boolean;
|
|
195
198
|
stepRunIds: number[];
|
|
196
199
|
stepResults: PipelineStepResult[];
|
|
197
200
|
originSessionFile: string;
|