@ryan_nookpi/pi-extension-subagent 0.5.5 → 0.5.7
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 +4 -4
- package/cli.ts +9 -2
- package/failure-telemetry.ts +12 -0
- package/package.json +1 -1
- package/retry.ts +10 -0
- package/run-utils.ts +3 -3
- package/starter-pack.ts +1 -2
- package/tool-execute.ts +156 -81
- package/types.ts +4 -1
- package/seeds/agents/simplifier.md +0 -68
package/README.md
CHANGED
|
@@ -68,7 +68,7 @@ Run `/subagents`, `subagent agents`, or the `list-agents` tool to inspect discov
|
|
|
68
68
|
|
|
69
69
|
### Optional starter pack
|
|
70
70
|
|
|
71
|
-
If no agents are found, `/subagents` can offer an optional, opinionated starter pack. It copies
|
|
71
|
+
If no agents are found, `/subagents` can offer an optional, opinionated starter pack. It copies eight agent templates, two example workflow skills, and missing global `subagent` settings. Existing files and configured values are not overwritten.
|
|
72
72
|
|
|
73
73
|
The starter pack is not required. Decline it if you prefer to define agents manually. It fills a missing `claudeRuntime` setting with `cli`; without that setting, the extension default is `sdk`.
|
|
74
74
|
|
|
@@ -98,9 +98,9 @@ subagent run <agent> [--main|--isolated] -- <task>
|
|
|
98
98
|
subagent continue <runId> [--agent <agent>] [--main|--isolated] -- <task>
|
|
99
99
|
subagent batch [--main|--isolated] --agent <agent> --task <task> ...
|
|
100
100
|
subagent chain [--main|--isolated] --agent <agent> --task <task> ...
|
|
101
|
-
subagent status <runId>
|
|
102
|
-
subagent detail <runId>
|
|
103
|
-
subagent abort <runId|runId,runId|all>
|
|
101
|
+
subagent status <runId|groupId>
|
|
102
|
+
subagent detail <runId|groupId>
|
|
103
|
+
subagent abort <runId|groupId|runId,runId|all>
|
|
104
104
|
subagent remove <runId|runId,runId|all>
|
|
105
105
|
```
|
|
106
106
|
|
package/cli.ts
CHANGED
|
@@ -64,7 +64,7 @@ export const SUBAGENT_CLI_HELP_TEXT = [
|
|
|
64
64
|
" subagent chain [--main|--isolated] --agent <agent> --task <task> --agent <agent> --task <task> ...",
|
|
65
65
|
"",
|
|
66
66
|
" Cleanup:",
|
|
67
|
-
" subagent abort <runId|runId,runId|all>",
|
|
67
|
+
" subagent abort <runId|groupId|runId,runId|all>",
|
|
68
68
|
" subagent remove <runId|runId,runId|all>",
|
|
69
69
|
"",
|
|
70
70
|
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
|
|
@@ -88,6 +88,7 @@ export const SUBAGENT_CLI_HELP_TEXT = [
|
|
|
88
88
|
" subagent status 22",
|
|
89
89
|
" subagent detail 22",
|
|
90
90
|
" subagent abort 22",
|
|
91
|
+
" subagent abort p_1712...",
|
|
91
92
|
" subagent remove all",
|
|
92
93
|
"",
|
|
93
94
|
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
|
|
@@ -508,8 +509,14 @@ export function parseSubagentToolCommand(
|
|
|
508
509
|
if (!target)
|
|
509
510
|
return {
|
|
510
511
|
type: "error",
|
|
511
|
-
message:
|
|
512
|
+
message:
|
|
513
|
+
verb === "abort"
|
|
514
|
+
? "❌ abort requires <runId|groupId|runId,runId|all>\n\n✓ Examples:\n subagent abort 22\n subagent abort p_1712_abcd\n subagent abort 22,23,24\n subagent abort all"
|
|
515
|
+
: "❌ remove requires <runId|runId,runId|all>\n\n✓ Examples:\n subagent remove 22\n subagent remove 22,23,24\n subagent remove all",
|
|
512
516
|
};
|
|
517
|
+
if (verb === "abort" && isGroupId(target)) {
|
|
518
|
+
return { type: "params", params: { asyncAction: "abort", groupId: target } };
|
|
519
|
+
}
|
|
513
520
|
const parsedTarget = parseRunTarget(target, options.knownRunIds);
|
|
514
521
|
if ("error" in parsedTarget)
|
|
515
522
|
return {
|
package/failure-telemetry.ts
CHANGED
|
@@ -5,6 +5,7 @@ export type SubagentErrorClass =
|
|
|
5
5
|
| "overloaded"
|
|
6
6
|
| "rate_limit"
|
|
7
7
|
| "tool_error"
|
|
8
|
+
| "network_error"
|
|
8
9
|
| "aborted"
|
|
9
10
|
| "process_error"
|
|
10
11
|
| "unknown";
|
|
@@ -64,6 +65,9 @@ const TOOL_ERROR_PATTERNS = [
|
|
|
64
65
|
/invalid tool/i,
|
|
65
66
|
];
|
|
66
67
|
|
|
68
|
+
const NETWORK_PATTERNS = [/\bwebsocket\b/i, /\bconnection error\b/i];
|
|
69
|
+
const PROCESS_SIGNAL_TERMINATION_PATTERN = /\bprocess terminated by signal\b/i;
|
|
70
|
+
|
|
67
71
|
export function classifySubagentFailure(input: FailureTelemetryInput): SubagentErrorClass | undefined {
|
|
68
72
|
if (!input.failed) return undefined;
|
|
69
73
|
const text = [input.errorMessage, input.stderr, input.output].filter(Boolean).join("\n");
|
|
@@ -73,6 +77,14 @@ export function classifySubagentFailure(input: FailureTelemetryInput): SubagentE
|
|
|
73
77
|
if (OVERLOADED_PATTERNS.some((pattern) => pattern.test(text))) return "overloaded";
|
|
74
78
|
if (RATE_LIMIT_PATTERNS.some((pattern) => pattern.test(text))) return "rate_limit";
|
|
75
79
|
if (TOOL_ERROR_PATTERNS.some((pattern) => pattern.test(text))) return "tool_error";
|
|
80
|
+
const signalTerminated = PROCESS_SIGNAL_TERMINATION_PATTERN.test(input.stderr ?? "");
|
|
81
|
+
if (
|
|
82
|
+
!signalTerminated &&
|
|
83
|
+
(NETWORK_PATTERNS.some((pattern) => pattern.test(text)) ||
|
|
84
|
+
/^terminated\.?$/i.test(input.errorMessage?.trim() ?? ""))
|
|
85
|
+
) {
|
|
86
|
+
return "network_error";
|
|
87
|
+
}
|
|
76
88
|
if ((input.exitCode ?? 0) !== 0) return "process_error";
|
|
77
89
|
return "unknown";
|
|
78
90
|
}
|
package/package.json
CHANGED
package/retry.ts
CHANGED
|
@@ -34,9 +34,13 @@ function buildFailureText(result: SingleResult): string {
|
|
|
34
34
|
.join("\n");
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
const PROCESS_SIGNAL_TERMINATION_PATTERN = /\bprocess terminated by signal\b/i;
|
|
38
|
+
|
|
37
39
|
const TRANSIENT_FAILURE_PATTERNS: RegExp[] = [
|
|
38
40
|
/\bnetwork\b/i,
|
|
41
|
+
/\bwebsocket\b/i,
|
|
39
42
|
/\bfetch failed\b/i,
|
|
43
|
+
/\bconnection error\b/i,
|
|
40
44
|
/\bconnection reset\b/i,
|
|
41
45
|
/\bsocket hang up\b/i,
|
|
42
46
|
/\btemporar(?:y|ily) unavailable\b/i,
|
|
@@ -70,12 +74,18 @@ function matchesTransientPattern(text: string): boolean {
|
|
|
70
74
|
|
|
71
75
|
export function diagnoseRetryableResult(result: SingleResult): RetryDecision {
|
|
72
76
|
if (result.stopReason === "aborted") return { retryable: false };
|
|
77
|
+
if (PROCESS_SIGNAL_TERMINATION_PATTERN.test(result.stderr)) return { retryable: false };
|
|
73
78
|
|
|
74
79
|
const finalOutput = getFinalOutput(result.messages).trim();
|
|
75
80
|
if (result.exitCode === 0 && result.stopReason !== "error" && finalOutput) {
|
|
76
81
|
return { retryable: false };
|
|
77
82
|
}
|
|
78
83
|
|
|
84
|
+
const errorMessage = result.errorMessage?.trim();
|
|
85
|
+
if (errorMessage && /^terminated\.?$/i.test(errorMessage)) {
|
|
86
|
+
return { retryable: true, reason: errorMessage };
|
|
87
|
+
}
|
|
88
|
+
|
|
79
89
|
const failureText = buildFailureText(result);
|
|
80
90
|
if (!failureText) return { retryable: false };
|
|
81
91
|
if (!matchesTransientPattern(failureText)) return { retryable: false };
|
package/run-utils.ts
CHANGED
|
@@ -242,18 +242,18 @@ export function snapshotBatchGroup(
|
|
|
242
242
|
let failed = 0;
|
|
243
243
|
let aborted = 0;
|
|
244
244
|
const members: FinishedGroupMember[] = batch.runIds.map((runId) => {
|
|
245
|
-
const run = store.commandRuns.get(runId);
|
|
245
|
+
const run = batch.completedRuns.get(runId) ?? store.commandRuns.get(runId);
|
|
246
246
|
if (!run) {
|
|
247
247
|
if (batch.abortedRunIds.has(runId)) aborted++;
|
|
248
248
|
else if (batch.failedRunIds.has(runId)) failed++;
|
|
249
249
|
return {
|
|
250
250
|
summaryLine: `#${runId} [gone] (run no longer available)`,
|
|
251
|
-
output:
|
|
251
|
+
output: "(no output)",
|
|
252
252
|
};
|
|
253
253
|
}
|
|
254
254
|
if (run.errorClass === "aborted" || batch.abortedRunIds.has(runId)) aborted++;
|
|
255
255
|
else if (run.status === "error" || batch.failedRunIds.has(runId)) failed++;
|
|
256
|
-
return { summaryLine: formatCommandRunSummary(run), output: memberOutput(run
|
|
256
|
+
return { summaryLine: formatCommandRunSummary(run), output: memberOutput(run) };
|
|
257
257
|
});
|
|
258
258
|
return {
|
|
259
259
|
groupId: batch.batchId,
|
package/starter-pack.ts
CHANGED
|
@@ -11,7 +11,6 @@ export const STARTER_AGENT_NAMES = [
|
|
|
11
11
|
"reviewer",
|
|
12
12
|
"searcher",
|
|
13
13
|
"security-auditor",
|
|
14
|
-
"simplifier",
|
|
15
14
|
"verifier",
|
|
16
15
|
"worker",
|
|
17
16
|
] as const;
|
|
@@ -220,7 +219,7 @@ export async function offerStarterPackIfEmpty(
|
|
|
220
219
|
|
|
221
220
|
const accepted = await ctx.ui.confirm(
|
|
222
221
|
"Install starter subagents?",
|
|
223
|
-
"No subagent definitions were found. Install
|
|
222
|
+
"No subagent definitions were found. Install 8 portable English agents, the stress-interview and self-healing skills, and missing subagent settings? Existing files and configured values will not be overwritten.",
|
|
224
223
|
);
|
|
225
224
|
if (!accepted) return { status: "declined", discovery };
|
|
226
225
|
|
package/tool-execute.ts
CHANGED
|
@@ -49,6 +49,7 @@ import {
|
|
|
49
49
|
import { clearPendingGroupCompletion, upsertPendingGroupCompletion } from "./group-pending.js";
|
|
50
50
|
import { enqueueSubagentInvocation } from "./invocation-queue.js";
|
|
51
51
|
import { appendDisplayTaskUpdate, getSessionFileSize } from "./persisted-session.js";
|
|
52
|
+
import { invokeWithAutoRetry, MAX_SUBAGENT_AUTO_RETRIES } from "./retry.js";
|
|
52
53
|
import {
|
|
53
54
|
clearFinishedRuns,
|
|
54
55
|
evictStaleFinishedGroups,
|
|
@@ -501,7 +502,13 @@ function recordBatchResult(batch: BatchGroupState, finalized: FinalizedRun): voi
|
|
|
501
502
|
batch.completedRunIds.add(runId);
|
|
502
503
|
if (status === "aborted") batch.abortedRunIds.add(runId);
|
|
503
504
|
else if (status === "error") batch.failedRunIds.add(runId);
|
|
504
|
-
batch.
|
|
505
|
+
batch.completedRuns.set(runId, { ...finalized.runState, abortController: undefined });
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function getCompletedBatchRuns(batch: BatchGroupState): CommandRunState[] {
|
|
509
|
+
return batch.runIds
|
|
510
|
+
.map((runId) => batch.completedRuns.get(runId))
|
|
511
|
+
.filter((run): run is CommandRunState => Boolean(run));
|
|
505
512
|
}
|
|
506
513
|
|
|
507
514
|
function buildRunCompletionMessage(finalized: FinalizedRun, options?: { display?: boolean }) {
|
|
@@ -1039,6 +1046,45 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1039
1046
|
};
|
|
1040
1047
|
}
|
|
1041
1048
|
|
|
1049
|
+
let abortGroupTarget: { groupId: string; kind: "batch" | "chain" } | undefined;
|
|
1050
|
+
if (asyncAction === "abort" && typeof cmdParams.groupId === "string") {
|
|
1051
|
+
evictStaleFinishedGroups(store);
|
|
1052
|
+
const groupId = cmdParams.groupId;
|
|
1053
|
+
const finished = store.finishedGroups.get(groupId);
|
|
1054
|
+
if (finished) {
|
|
1055
|
+
return {
|
|
1056
|
+
content: [{ type: "text", text: `Subagent ${finished.kind} ${groupId} has already finished.` }],
|
|
1057
|
+
details: makeDetails("single"),
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
const batch = store.batchGroups.get(groupId);
|
|
1062
|
+
const pipeline = store.pipelines.get(groupId);
|
|
1063
|
+
if (!batch && !pipeline) {
|
|
1064
|
+
return {
|
|
1065
|
+
content: [{ type: "text", text: `Unknown subagent group "${groupId}".` }],
|
|
1066
|
+
details: makeDetails("single"),
|
|
1067
|
+
isError: true,
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
abortGroupTarget = { groupId, kind: batch ? "batch" : "chain" };
|
|
1072
|
+
if (pipeline) pipeline.abortRequested = true;
|
|
1073
|
+
const groupRunIds = (batch?.runIds ?? pipeline?.stepRunIds ?? []).filter((runId) => {
|
|
1074
|
+
const run = store.globalLiveRuns.get(runId)?.runState ?? store.commandRuns.get(runId);
|
|
1075
|
+
return batch ? run?.batchId === groupId : run?.pipelineId === groupId;
|
|
1076
|
+
});
|
|
1077
|
+
if (groupRunIds.length === 0) {
|
|
1078
|
+
return {
|
|
1079
|
+
content: [
|
|
1080
|
+
{ type: "text", text: `Subagent ${abortGroupTarget.kind} ${groupId} has no started runs to abort.` },
|
|
1081
|
+
],
|
|
1082
|
+
details: makeDetails("single"),
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
cmdParams.runIds = groupRunIds;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1042
1088
|
if ((asyncAction === "status" || asyncAction === "detail") && typeof cmdParams.groupId === "string") {
|
|
1043
1089
|
evictStaleFinishedGroups(store);
|
|
1044
1090
|
const groupId = cmdParams.groupId;
|
|
@@ -1177,7 +1223,9 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1177
1223
|
const unknown: number[] = [];
|
|
1178
1224
|
|
|
1179
1225
|
for (const runId of targetRunIds) {
|
|
1180
|
-
const run =
|
|
1226
|
+
const run = abortGroupTarget
|
|
1227
|
+
? (store.globalLiveRuns.get(runId)?.runState ?? store.commandRuns.get(runId))
|
|
1228
|
+
: store.commandRuns.get(runId);
|
|
1181
1229
|
if (!run) {
|
|
1182
1230
|
unknown.push(runId);
|
|
1183
1231
|
continue;
|
|
@@ -1199,7 +1247,13 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1199
1247
|
updateCommandRunsWidget(store, ctx as WidgetRenderCtx);
|
|
1200
1248
|
}
|
|
1201
1249
|
|
|
1202
|
-
if (
|
|
1250
|
+
if (
|
|
1251
|
+
!abortGroupTarget &&
|
|
1252
|
+
targetRunIds.length === 1 &&
|
|
1253
|
+
aborting.length === 1 &&
|
|
1254
|
+
notRunning.length === 0 &&
|
|
1255
|
+
unknown.length === 0
|
|
1256
|
+
) {
|
|
1203
1257
|
return {
|
|
1204
1258
|
content: [{ type: "text", text: `Aborting subagent run #${aborting[0]}...` }],
|
|
1205
1259
|
details: makeDetails("single"),
|
|
@@ -1207,6 +1261,9 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1207
1261
|
}
|
|
1208
1262
|
|
|
1209
1263
|
const lines: string[] = [];
|
|
1264
|
+
if (abortGroupTarget) {
|
|
1265
|
+
lines.push(`Subagent ${abortGroupTarget.kind} ${abortGroupTarget.groupId}:`);
|
|
1266
|
+
}
|
|
1210
1267
|
if (aborting.length > 0) lines.push(`Aborting: ${aborting.map((id) => `#${id}`).join(", ")}.`);
|
|
1211
1268
|
if (notRunning.length > 0) lines.push(`Not running: ${notRunning.map((id) => `#${id}`).join(", ")}.`);
|
|
1212
1269
|
if (unknown.length > 0) lines.push(`Unknown: ${unknown.map((id) => `#${id}`).join(", ")}.`);
|
|
@@ -1412,48 +1469,71 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1412
1469
|
store.recentLaunchTimestamps.delete(runId);
|
|
1413
1470
|
}
|
|
1414
1471
|
|
|
1472
|
+
function cleanupGroupRunAfterFinalDelivery(runId: number, groupId: string, kind: "batch" | "chain") {
|
|
1473
|
+
const run = store.globalLiveRuns.get(runId)?.runState ?? store.commandRuns.get(runId);
|
|
1474
|
+
const ownsGroup = kind === "batch" ? run?.batchId === groupId : run?.pipelineId === groupId;
|
|
1475
|
+
if (run && !ownsGroup) return;
|
|
1476
|
+
cleanupRunAfterFinalDelivery(runId);
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1415
1479
|
function launchRunInBackground(runState: CommandRunState, taskForAgent: string): Promise<FinalizedRun> {
|
|
1416
1480
|
let claudeCheckpointSent = !!runState.claudeSessionId;
|
|
1417
1481
|
const abortSignal = runState.abortController?.signal;
|
|
1418
1482
|
const recordActivity = createRunActivityRecorder(pi, runState, (model) => resolveContextWindow(ctx, model));
|
|
1419
|
-
return
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
runState.
|
|
1425
|
-
|
|
1426
|
-
runState.
|
|
1427
|
-
|
|
1428
|
-
(
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1483
|
+
return invokeWithAutoRetry({
|
|
1484
|
+
maxRetries: MAX_SUBAGENT_AUTO_RETRIES,
|
|
1485
|
+
signal: abortSignal,
|
|
1486
|
+
onRetryScheduled: ({ retryIndex, maxRetries, delayMs, reason }) => {
|
|
1487
|
+
runState.retryCount = retryIndex;
|
|
1488
|
+
runState.lastRetryReason = reason;
|
|
1489
|
+
runState.lastActivityAt = Date.now();
|
|
1490
|
+
runState.lastLine = `Auto-retrying ${retryIndex}/${maxRetries} in ${Math.ceil(delayMs / 1000)}s: ${reason}`;
|
|
1491
|
+
runState.lastOutput = runState.lastLine;
|
|
1492
|
+
updateCommandRunsWidget(store);
|
|
1493
|
+
},
|
|
1494
|
+
invoke: () => {
|
|
1495
|
+
runState.persistedSessionBaseOffset = getSessionFileSize(runState.sessionFile);
|
|
1496
|
+
return enqueueSubagentInvocation(async () => {
|
|
1497
|
+
if (runState.removed || abortSignal?.aborted) throw new Error("Subagent was aborted");
|
|
1498
|
+
return runSingleAgent(
|
|
1499
|
+
ctx.cwd,
|
|
1500
|
+
agents,
|
|
1501
|
+
runState.agent,
|
|
1502
|
+
taskForAgent,
|
|
1503
|
+
runState.pipelineStepIndex,
|
|
1504
|
+
abortSignal,
|
|
1505
|
+
(partial) => {
|
|
1506
|
+
if (runState.removed || store.disposed) return;
|
|
1507
|
+
const current = partial.details?.results?.[0];
|
|
1508
|
+
if (!current) return;
|
|
1509
|
+
updateRunFromResult(runState, current);
|
|
1510
|
+
recordActivity();
|
|
1511
|
+
if (shouldRunAsync && !claudeCheckpointSent && runState.claudeSessionId) {
|
|
1512
|
+
claudeCheckpointSent = true;
|
|
1513
|
+
pi.sendMessage(buildRunStartMessage(runState, "started"), {
|
|
1514
|
+
deliverAs: "followUp",
|
|
1515
|
+
triggerTurn: false,
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
updateCommandRunsWidget(store);
|
|
1519
|
+
},
|
|
1520
|
+
(results) => makeDetails(mode, results),
|
|
1521
|
+
{
|
|
1522
|
+
sessionFile: runState.sessionFile,
|
|
1523
|
+
resumeSessionId: runState.claudeSessionId,
|
|
1524
|
+
sidecarSessionFile: runState.sessionFile,
|
|
1525
|
+
persistedSessionBaseOffset: runState.persistedSessionBaseOffset,
|
|
1526
|
+
onDiagnostic: createRunDiagnosticSink(pi, runState),
|
|
1527
|
+
},
|
|
1528
|
+
);
|
|
1529
|
+
});
|
|
1530
|
+
},
|
|
1531
|
+
}).then(({ result, retryCount }) => {
|
|
1532
|
+
runState.retryCount = retryCount;
|
|
1533
|
+
return runState.removed || abortSignal?.aborted
|
|
1454
1534
|
? finalizeRunError(runState, new Error("Subagent was aborted"))
|
|
1455
|
-
: finalizeRunState(runState, result)
|
|
1456
|
-
);
|
|
1535
|
+
: finalizeRunState(runState, result);
|
|
1536
|
+
});
|
|
1457
1537
|
}
|
|
1458
1538
|
|
|
1459
1539
|
if (hasSingle) {
|
|
@@ -1717,7 +1797,7 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1717
1797
|
abortedRunIds: new Set(),
|
|
1718
1798
|
originSessionFile,
|
|
1719
1799
|
createdAt: Date.now(),
|
|
1720
|
-
|
|
1800
|
+
completedRuns: new Map(),
|
|
1721
1801
|
});
|
|
1722
1802
|
updateCommandRunsWidget(store, ctx as WidgetRenderCtx);
|
|
1723
1803
|
|
|
@@ -1733,11 +1813,13 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1733
1813
|
}
|
|
1734
1814
|
}),
|
|
1735
1815
|
);
|
|
1736
|
-
const orderedRuns = runStates.map(({ runState }) => runState);
|
|
1737
1816
|
const batchForSnapshot = store.batchGroups.get(batchId);
|
|
1738
1817
|
if (batchForSnapshot) {
|
|
1739
1818
|
for (const finalized of finalizedRuns) recordBatchResult(batchForSnapshot, finalized);
|
|
1740
1819
|
}
|
|
1820
|
+
const orderedRuns = batchForSnapshot
|
|
1821
|
+
? getCompletedBatchRuns(batchForSnapshot)
|
|
1822
|
+
: runStates.map(({ runState }) => runState);
|
|
1741
1823
|
const terminalStatus = resolveGroupTerminalStatus(
|
|
1742
1824
|
batchForSnapshot?.failedRunIds.size ?? 0,
|
|
1743
1825
|
batchForSnapshot?.abortedRunIds.size ?? 0,
|
|
@@ -1746,7 +1828,7 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1746
1828
|
if (batchForSnapshot) {
|
|
1747
1829
|
retireFinishedGroup(store, snapshotBatchGroup(store, batchForSnapshot, terminalStatus));
|
|
1748
1830
|
}
|
|
1749
|
-
for (const { runState } of runStates)
|
|
1831
|
+
for (const { runState } of runStates) cleanupGroupRunAfterFinalDelivery(runState.id, batchId, "batch");
|
|
1750
1832
|
clearPendingGroupCompletion("batch", batchId);
|
|
1751
1833
|
store.batchGroups.delete(batchId);
|
|
1752
1834
|
trimCommandRunHistory(store, {
|
|
@@ -1782,9 +1864,7 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1782
1864
|
updateCommandRunsWidget(store);
|
|
1783
1865
|
|
|
1784
1866
|
if (batch.completedRunIds.size === batch.runIds.length) {
|
|
1785
|
-
const orderedRuns = batch
|
|
1786
|
-
.map((runId) => store.commandRuns.get(runId))
|
|
1787
|
-
.filter((run): run is CommandRunState => Boolean(run));
|
|
1867
|
+
const orderedRuns = getCompletedBatchRuns(batch);
|
|
1788
1868
|
const batchTerminalStatus = resolveGroupTerminalStatus(batch.failedRunIds.size, batch.abortedRunIds.size);
|
|
1789
1869
|
const content = formatBatchSummary(batchId, orderedRuns, batchTerminalStatus);
|
|
1790
1870
|
const message = {
|
|
@@ -1802,7 +1882,7 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1802
1882
|
if (isInOriginSession(ctx, batch.originSessionFile)) {
|
|
1803
1883
|
pi.sendMessage(message, { deliverAs: "followUp", triggerTurn: true });
|
|
1804
1884
|
clearPendingGroupCompletion("batch", batchId);
|
|
1805
|
-
for (const runId of batch.runIds)
|
|
1885
|
+
for (const runId of batch.runIds) cleanupGroupRunAfterFinalDelivery(runId, batchId, "batch");
|
|
1806
1886
|
store.batchGroups.delete(batchId);
|
|
1807
1887
|
} else {
|
|
1808
1888
|
batch.pendingCompletion = makePendingCompletion(message, true);
|
|
@@ -1836,9 +1916,7 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1836
1916
|
if (!batch) return;
|
|
1837
1917
|
recordBatchResult(batch, finalized);
|
|
1838
1918
|
if (batch.completedRunIds.size === batch.runIds.length) {
|
|
1839
|
-
const orderedRuns = batch
|
|
1840
|
-
.map((runId) => store.commandRuns.get(runId))
|
|
1841
|
-
.filter((run): run is CommandRunState => Boolean(run));
|
|
1919
|
+
const orderedRuns = getCompletedBatchRuns(batch);
|
|
1842
1920
|
const batchTerminalStatus = resolveGroupTerminalStatus(batch.failedRunIds.size, batch.abortedRunIds.size);
|
|
1843
1921
|
const message = {
|
|
1844
1922
|
customType: "subagent-tool" as const,
|
|
@@ -1855,7 +1933,7 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1855
1933
|
if (isInOriginSession(ctx, batch.originSessionFile)) {
|
|
1856
1934
|
pi.sendMessage(message, { deliverAs: "followUp", triggerTurn: true });
|
|
1857
1935
|
clearPendingGroupCompletion("batch", batchId);
|
|
1858
|
-
for (const runId of batch.runIds)
|
|
1936
|
+
for (const runId of batch.runIds) cleanupGroupRunAfterFinalDelivery(runId, batchId, "batch");
|
|
1859
1937
|
store.batchGroups.delete(batchId);
|
|
1860
1938
|
} else {
|
|
1861
1939
|
batch.pendingCompletion = makePendingCompletion(message, true);
|
|
@@ -1914,6 +1992,7 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1914
1992
|
store.pipelines.set(pipelineId, {
|
|
1915
1993
|
pipelineId,
|
|
1916
1994
|
currentIndex: 0,
|
|
1995
|
+
abortRequested: false,
|
|
1917
1996
|
stepRunIds: [],
|
|
1918
1997
|
stepResults: [],
|
|
1919
1998
|
originSessionFile,
|
|
@@ -1935,6 +2014,10 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1935
2014
|
|
|
1936
2015
|
try {
|
|
1937
2016
|
for (let index = 0; index < steps.length; index++) {
|
|
2017
|
+
if (pipeline.abortRequested) {
|
|
2018
|
+
terminalStatus = "aborted";
|
|
2019
|
+
break;
|
|
2020
|
+
}
|
|
1938
2021
|
pipeline.currentIndex = index;
|
|
1939
2022
|
const step = steps[index];
|
|
1940
2023
|
const pipelineReferenceSection =
|
|
@@ -1988,26 +2071,20 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1988
2071
|
}
|
|
1989
2072
|
finalizedRuns.push(finalized);
|
|
1990
2073
|
|
|
1991
|
-
|
|
1992
|
-
terminalStatus = "aborted";
|
|
1993
|
-
pipeline.stepResults.push({
|
|
1994
|
-
runId: runState.id,
|
|
1995
|
-
agent: runState.agent,
|
|
1996
|
-
task: step.task,
|
|
1997
|
-
output: finalized.rawOutput || "Run removed before pipeline completion.",
|
|
1998
|
-
status: "aborted",
|
|
1999
|
-
});
|
|
2000
|
-
break;
|
|
2001
|
-
}
|
|
2002
|
-
|
|
2003
|
-
const stepStatus = getFinalizedRunStatus(finalized);
|
|
2074
|
+
const stepStatus = runState.removed ? "aborted" : getFinalizedRunStatus(finalized);
|
|
2004
2075
|
pipeline.stepResults.push({
|
|
2005
2076
|
runId: runState.id,
|
|
2006
2077
|
agent: runState.agent,
|
|
2007
2078
|
task: step.task,
|
|
2008
|
-
output:
|
|
2079
|
+
output: runState.removed
|
|
2080
|
+
? finalized.rawOutput || "Run removed before pipeline completion."
|
|
2081
|
+
: finalized.rawOutput,
|
|
2009
2082
|
status: stepStatus,
|
|
2010
2083
|
});
|
|
2084
|
+
if (runState.removed || pipeline.abortRequested) {
|
|
2085
|
+
terminalStatus = "aborted";
|
|
2086
|
+
break;
|
|
2087
|
+
}
|
|
2011
2088
|
previousOutput = finalized.rawOutput;
|
|
2012
2089
|
updateCommandRunsWidget(store);
|
|
2013
2090
|
|
|
@@ -2033,7 +2110,7 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
2033
2110
|
else if (terminalStatus === "completed" && hasAborted) terminalStatus = "aborted";
|
|
2034
2111
|
const content = formatPipelineSummary(pipelineId, pipeline.stepResults, terminalStatus);
|
|
2035
2112
|
retireFinishedGroup(store, snapshotPipeline(pipeline, terminalStatus));
|
|
2036
|
-
for (const runId of pipeline.stepRunIds)
|
|
2113
|
+
for (const runId of pipeline.stepRunIds) cleanupGroupRunAfterFinalDelivery(runId, pipelineId, "chain");
|
|
2037
2114
|
clearPendingGroupCompletion("chain", pipelineId);
|
|
2038
2115
|
store.pipelines.delete(pipelineId);
|
|
2039
2116
|
trimCommandRunHistory(store, {
|
|
@@ -2065,6 +2142,10 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
2065
2142
|
for (let index = 0; index < steps.length; index++) {
|
|
2066
2143
|
const pipeline = store.pipelines.get(pipelineId);
|
|
2067
2144
|
if (!pipeline) return;
|
|
2145
|
+
if (pipeline.abortRequested) {
|
|
2146
|
+
terminalStatus = "aborted";
|
|
2147
|
+
break;
|
|
2148
|
+
}
|
|
2068
2149
|
pipeline.currentIndex = index;
|
|
2069
2150
|
|
|
2070
2151
|
const step = steps[index];
|
|
@@ -2117,26 +2198,20 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
2117
2198
|
} finally {
|
|
2118
2199
|
clearRunAbortState(runState);
|
|
2119
2200
|
}
|
|
2120
|
-
if (runState.removed) {
|
|
2121
|
-
terminalStatus = "aborted";
|
|
2122
|
-
pipeline.stepResults.push({
|
|
2123
|
-
runId: runState.id,
|
|
2124
|
-
agent: runState.agent,
|
|
2125
|
-
task: step.task,
|
|
2126
|
-
output: finalized.rawOutput || "Run removed before pipeline completion.",
|
|
2127
|
-
status: "aborted",
|
|
2128
|
-
});
|
|
2129
|
-
break;
|
|
2130
|
-
}
|
|
2131
|
-
|
|
2132
2201
|
const stepResult: PipelineStepResult = {
|
|
2133
2202
|
runId: runState.id,
|
|
2134
2203
|
agent: runState.agent,
|
|
2135
2204
|
task: step.task,
|
|
2136
|
-
output:
|
|
2137
|
-
|
|
2205
|
+
output: runState.removed
|
|
2206
|
+
? finalized.rawOutput || "Run removed before pipeline completion."
|
|
2207
|
+
: finalized.rawOutput,
|
|
2208
|
+
status: runState.removed ? "aborted" : getFinalizedRunStatus(finalized),
|
|
2138
2209
|
};
|
|
2139
2210
|
pipeline.stepResults.push(stepResult);
|
|
2211
|
+
if (runState.removed || pipeline.abortRequested) {
|
|
2212
|
+
terminalStatus = "aborted";
|
|
2213
|
+
break;
|
|
2214
|
+
}
|
|
2140
2215
|
previousOutput = finalized.rawOutput;
|
|
2141
2216
|
updateCommandRunsWidget(store);
|
|
2142
2217
|
|
|
@@ -2182,7 +2257,7 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
2182
2257
|
if (isInOriginSession(ctx, pipeline.originSessionFile)) {
|
|
2183
2258
|
pi.sendMessage(message, { deliverAs: "followUp", triggerTurn: true });
|
|
2184
2259
|
clearPendingGroupCompletion("chain", pipelineId);
|
|
2185
|
-
for (const runId of pipeline.stepRunIds)
|
|
2260
|
+
for (const runId of pipeline.stepRunIds) cleanupGroupRunAfterFinalDelivery(runId, pipelineId, "chain");
|
|
2186
2261
|
store.pipelines.delete(pipelineId);
|
|
2187
2262
|
} else {
|
|
2188
2263
|
pipeline.pendingCompletion = makePendingCompletion(message, true);
|
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;
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: simplifier
|
|
3
|
-
description: Code simplification specialist that improves clarity while preserving behavior
|
|
4
|
-
runtime: pi
|
|
5
|
-
thinking: high
|
|
6
|
-
tools: read,grep,find,ls,bash,edit,write
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
<system_prompt agent="simplifier">
|
|
10
|
-
<identity>
|
|
11
|
-
You simplify recently modified code for clarity, consistency, and maintainability without changing observable behavior.
|
|
12
|
-
</identity>
|
|
13
|
-
|
|
14
|
-
<scope_rule>
|
|
15
|
-
<rule>Only simplify the requested or clearly identified changed scope.</rule>
|
|
16
|
-
<rule>Do not broaden into unrelated cleanup or architecture changes.</rule>
|
|
17
|
-
<rule>Preserve outputs, side effects, public contracts, and data flow.</rule>
|
|
18
|
-
<rule>Handle each supplied finding independently; one blocked item must not stop unrelated safe items.</rule>
|
|
19
|
-
</scope_rule>
|
|
20
|
-
|
|
21
|
-
<principles>
|
|
22
|
-
<rule>Prefer explicit, readable control flow over clever compression.</rule>
|
|
23
|
-
<rule>Reduce unnecessary nesting, indirection, and dead intermediates.</rule>
|
|
24
|
-
<rule>Keep abstractions that improve organization, testing, or reuse.</rule>
|
|
25
|
-
<rule>Follow existing repository patterns before introducing a new style.</rule>
|
|
26
|
-
<rule>Choose a no-op over churn when the code is already clear.</rule>
|
|
27
|
-
</principles>
|
|
28
|
-
|
|
29
|
-
<allowed_efficiency_changes>
|
|
30
|
-
<item>Parallelize operations only when independence and ordering are proven.</item>
|
|
31
|
-
<item>Add change-detection guards to avoid no-op updates.</item>
|
|
32
|
-
<item>Remove preflight existence checks when direct operation plus error handling is safer.</item>
|
|
33
|
-
<item>Use an existing batch API when it is behaviorally equivalent.</item>
|
|
34
|
-
<item>Narrow overly broad reads when equivalence is clear.</item>
|
|
35
|
-
</allowed_efficiency_changes>
|
|
36
|
-
|
|
37
|
-
<workflow>
|
|
38
|
-
<step index="1">Identify the exact file regions or enumerate supplied findings.</step>
|
|
39
|
-
<step index="2">Read surrounding code and any utility proposed for reuse.</step>
|
|
40
|
-
<step index="3">Choose the smallest behavior-preserving change for each item.</step>
|
|
41
|
-
<step index="4">Edit only the necessary files.</step>
|
|
42
|
-
<step index="5">Run targeted tests, type checking, linting, and build checks.</step>
|
|
43
|
-
<step index="6">Report each item as applied, skipped, or escalated.</step>
|
|
44
|
-
</workflow>
|
|
45
|
-
|
|
46
|
-
<rules>
|
|
47
|
-
<rule>Do not force a utility substitution when null handling or edge behavior differs.</rule>
|
|
48
|
-
<rule>Do not suppress type errors.</rule>
|
|
49
|
-
<rule>Escalate items that require behavior change or cross-module redesign.</rule>
|
|
50
|
-
<rule>Fix only issues introduced by the requested change.</rule>
|
|
51
|
-
</rules>
|
|
52
|
-
|
|
53
|
-
<output_template>
|
|
54
|
-
<![CDATA[
|
|
55
|
-
### Applied
|
|
56
|
-
- `path:start-end` — {change}
|
|
57
|
-
|
|
58
|
-
### Skipped
|
|
59
|
-
- {item} — {reason}
|
|
60
|
-
|
|
61
|
-
### Escalate
|
|
62
|
-
- {item} — {reason}
|
|
63
|
-
|
|
64
|
-
### Residual Risk
|
|
65
|
-
- {risk or none}
|
|
66
|
-
]]>
|
|
67
|
-
</output_template>
|
|
68
|
-
</system_prompt>
|