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-runner.ts
CHANGED
|
@@ -8,15 +8,18 @@ import { appendJsonl, getArtifactPaths } from "./artifacts.ts";
|
|
|
8
8
|
import { getPiSpawnCommand } from "./pi-spawn.ts";
|
|
9
9
|
import { captureSingleOutputSnapshot, resolveSingleOutput } from "./single-output.ts";
|
|
10
10
|
import {
|
|
11
|
+
type ActivityState,
|
|
11
12
|
type ArtifactConfig,
|
|
12
13
|
type ArtifactPaths,
|
|
13
14
|
type ModelAttempt,
|
|
15
|
+
type ResolvedControlConfig,
|
|
14
16
|
type Usage,
|
|
15
17
|
DEFAULT_MAX_OUTPUT,
|
|
16
18
|
type MaxOutputConfig,
|
|
17
19
|
truncateOutput,
|
|
18
20
|
getSubagentDepthEnv,
|
|
19
21
|
} from "./types.ts";
|
|
22
|
+
import { DEFAULT_CONTROL_CONFIG } from "./subagent-control.ts";
|
|
20
23
|
import {
|
|
21
24
|
type RunnerSubagentStep as SubagentStep,
|
|
22
25
|
type RunnerStep,
|
|
@@ -60,6 +63,7 @@ interface SubagentRunConfig {
|
|
|
60
63
|
piArgv1?: string;
|
|
61
64
|
worktreeSetupHook?: string;
|
|
62
65
|
worktreeSetupHookTimeoutMs?: number;
|
|
66
|
+
controlConfig?: ResolvedControlConfig;
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
interface StepResult {
|
|
@@ -75,6 +79,7 @@ interface StepResult {
|
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
const require = createRequire(import.meta.url);
|
|
82
|
+
const ASYNC_INTERRUPT_SIGNAL: NodeJS.Signals = process.platform === "win32" ? "SIGBREAK" : "SIGUSR2";
|
|
78
83
|
|
|
79
84
|
function findLatestSessionFile(sessionDir: string): string | null {
|
|
80
85
|
try {
|
|
@@ -95,6 +100,18 @@ function emptyUsage(): Usage {
|
|
|
95
100
|
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
|
|
96
101
|
}
|
|
97
102
|
|
|
103
|
+
function tokenUsageFromAttempts(attempts: ModelAttempt[] | undefined): TokenUsage | null {
|
|
104
|
+
if (!attempts || attempts.length === 0) return null;
|
|
105
|
+
let input = 0;
|
|
106
|
+
let output = 0;
|
|
107
|
+
for (const attempt of attempts) {
|
|
108
|
+
input += attempt.usage?.input ?? 0;
|
|
109
|
+
output += attempt.usage?.output ?? 0;
|
|
110
|
+
}
|
|
111
|
+
const total = input + output;
|
|
112
|
+
return total > 0 ? { input, output, total } : null;
|
|
113
|
+
}
|
|
114
|
+
|
|
98
115
|
interface ChildEventContext {
|
|
99
116
|
eventsPath: string;
|
|
100
117
|
runId: string;
|
|
@@ -133,6 +150,7 @@ interface RunPiStreamingResult {
|
|
|
133
150
|
model?: string;
|
|
134
151
|
error?: string;
|
|
135
152
|
finalOutput: string;
|
|
153
|
+
interrupted?: boolean;
|
|
136
154
|
}
|
|
137
155
|
|
|
138
156
|
function runPiStreaming(
|
|
@@ -144,6 +162,7 @@ function runPiStreaming(
|
|
|
144
162
|
piArgv1?: string,
|
|
145
163
|
maxSubagentDepth?: number,
|
|
146
164
|
childEventContext?: ChildEventContext,
|
|
165
|
+
registerInterrupt?: (interrupt: (() => void) | undefined) => void,
|
|
147
166
|
): Promise<RunPiStreamingResult> {
|
|
148
167
|
return new Promise((resolve) => {
|
|
149
168
|
const outputStream = fs.createWriteStream(outputFile, { flags: "w" });
|
|
@@ -160,6 +179,7 @@ function runPiStreaming(
|
|
|
160
179
|
const usage = emptyUsage();
|
|
161
180
|
let model: string | undefined;
|
|
162
181
|
let error: string | undefined;
|
|
182
|
+
let interrupted = false;
|
|
163
183
|
const rawStdoutLines: string[] = [];
|
|
164
184
|
|
|
165
185
|
const writeOutputLine = (line: string) => {
|
|
@@ -267,6 +287,15 @@ function runPiStreaming(
|
|
|
267
287
|
child.stderr.on("data", (chunk: Buffer) => {
|
|
268
288
|
processStderrText(chunk.toString());
|
|
269
289
|
});
|
|
290
|
+
registerInterrupt?.(() => {
|
|
291
|
+
if (settled) return;
|
|
292
|
+
interrupted = true;
|
|
293
|
+
if (!error) error = "Interrupted. Waiting for explicit next action.";
|
|
294
|
+
trySignalChild(child, "SIGINT");
|
|
295
|
+
setTimeout(() => {
|
|
296
|
+
if (!settled) trySignalChild(child, "SIGTERM");
|
|
297
|
+
}, 1000).unref?.();
|
|
298
|
+
});
|
|
270
299
|
const clearDrainTimers = () => {
|
|
271
300
|
if (finalDrainTimer) {
|
|
272
301
|
clearTimeout(finalDrainTimer);
|
|
@@ -301,6 +330,7 @@ function runPiStreaming(
|
|
|
301
330
|
});
|
|
302
331
|
child.on("close", (exitCode, signal) => {
|
|
303
332
|
settled = true;
|
|
333
|
+
registerInterrupt?.(undefined);
|
|
304
334
|
clearDrainTimers();
|
|
305
335
|
clearStdioGuard();
|
|
306
336
|
if (stdoutBuf.trim()) processStdoutLine(stdoutBuf);
|
|
@@ -309,17 +339,19 @@ function runPiStreaming(
|
|
|
309
339
|
const finalOutput = getFinalOutput(messages) || rawStdoutLines.join("\n").trim();
|
|
310
340
|
resolve({
|
|
311
341
|
stderr,
|
|
312
|
-
exitCode: forcedTerminationSignal || signal ? (exitCode ?? 1) : exitCode,
|
|
342
|
+
exitCode: interrupted ? 0 : forcedTerminationSignal || signal ? (exitCode ?? 1) : exitCode,
|
|
313
343
|
messages,
|
|
314
344
|
usage,
|
|
315
345
|
model,
|
|
316
|
-
error,
|
|
346
|
+
error: interrupted ? undefined : error,
|
|
317
347
|
finalOutput,
|
|
348
|
+
interrupted,
|
|
318
349
|
});
|
|
319
350
|
});
|
|
320
351
|
|
|
321
352
|
child.on("error", (spawnError) => {
|
|
322
353
|
settled = true;
|
|
354
|
+
registerInterrupt?.(undefined);
|
|
323
355
|
clearDrainTimers();
|
|
324
356
|
clearStdioGuard();
|
|
325
357
|
outputStream.end();
|
|
@@ -481,6 +513,7 @@ interface SingleStepContext {
|
|
|
481
513
|
outputFile: string;
|
|
482
514
|
piPackageRoot?: string;
|
|
483
515
|
piArgv1?: string;
|
|
516
|
+
registerInterrupt?: (interrupt: (() => void) | undefined) => void;
|
|
484
517
|
}
|
|
485
518
|
|
|
486
519
|
/** Run a single pi agent step, returning output and metadata */
|
|
@@ -496,6 +529,7 @@ async function runSingleStep(
|
|
|
496
529
|
attemptedModels?: string[];
|
|
497
530
|
modelAttempts?: ModelAttempt[];
|
|
498
531
|
artifactPaths?: ArtifactPaths;
|
|
532
|
+
interrupted?: boolean;
|
|
499
533
|
}> {
|
|
500
534
|
const placeholderRegex = new RegExp(ctx.placeholder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g");
|
|
501
535
|
const task = step.task.replace(placeholderRegex, () => ctx.previousOutput);
|
|
@@ -551,6 +585,7 @@ async function runSingleStep(
|
|
|
551
585
|
ctx.piArgv1,
|
|
552
586
|
step.maxSubagentDepth,
|
|
553
587
|
{ eventsPath, runId: ctx.id, stepIndex: ctx.flatIndex, agent: step.agent },
|
|
588
|
+
ctx.registerInterrupt,
|
|
554
589
|
);
|
|
555
590
|
cleanupTempDir(tempDir);
|
|
556
591
|
|
|
@@ -627,13 +662,15 @@ async function runSingleStep(
|
|
|
627
662
|
attemptedModels: attemptedModels.length > 0 ? attemptedModels : undefined,
|
|
628
663
|
modelAttempts,
|
|
629
664
|
artifactPaths,
|
|
665
|
+
interrupted: finalResult?.interrupted,
|
|
630
666
|
};
|
|
631
667
|
}
|
|
632
668
|
|
|
633
669
|
type RunnerStatusPayload = {
|
|
634
670
|
runId: string;
|
|
635
671
|
mode: "single" | "chain";
|
|
636
|
-
state: "queued" | "running" | "complete" | "failed";
|
|
672
|
+
state: "queued" | "running" | "complete" | "failed" | "paused";
|
|
673
|
+
activityState?: ActivityState;
|
|
637
674
|
startedAt: number;
|
|
638
675
|
endedAt?: number;
|
|
639
676
|
lastUpdate: number;
|
|
@@ -643,6 +680,7 @@ type RunnerStatusPayload = {
|
|
|
643
680
|
steps: Array<{
|
|
644
681
|
agent: string;
|
|
645
682
|
status: "pending" | "running" | "complete" | "failed";
|
|
683
|
+
activityState?: ActivityState;
|
|
646
684
|
startedAt?: number;
|
|
647
685
|
endedAt?: number;
|
|
648
686
|
durationMs?: number;
|
|
@@ -714,9 +752,11 @@ function markParallelGroupRunning(input: {
|
|
|
714
752
|
for (let taskIndex = 0; taskIndex < input.group.parallel.length; taskIndex++) {
|
|
715
753
|
const flatTaskIndex = input.groupStartFlatIndex + taskIndex;
|
|
716
754
|
input.statusPayload.steps[flatTaskIndex].status = "running";
|
|
755
|
+
input.statusPayload.steps[flatTaskIndex].activityState = "active";
|
|
717
756
|
input.statusPayload.steps[flatTaskIndex].startedAt = input.groupStartTime;
|
|
718
757
|
}
|
|
719
758
|
input.statusPayload.currentStep = input.groupStartFlatIndex;
|
|
759
|
+
input.statusPayload.activityState = "active";
|
|
720
760
|
input.statusPayload.lastUpdate = input.groupStartTime;
|
|
721
761
|
input.statusPayload.outputFile = path.join(input.asyncDir, `output-${input.groupStartFlatIndex}.log`);
|
|
722
762
|
writeJson(input.statusPath, input.statusPayload);
|
|
@@ -769,6 +809,9 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
769
809
|
const statusPath = path.join(asyncDir, "status.json");
|
|
770
810
|
const eventsPath = path.join(asyncDir, "events.jsonl");
|
|
771
811
|
const logPath = path.join(asyncDir, `subagent-log-${id}.md`);
|
|
812
|
+
const controlConfig = config.controlConfig ?? DEFAULT_CONTROL_CONFIG;
|
|
813
|
+
let activeChildInterrupt: (() => void) | undefined;
|
|
814
|
+
let interrupted = false;
|
|
772
815
|
let previousCumulativeTokens: TokenUsage = { input: 0, output: 0, total: 0 };
|
|
773
816
|
let latestSessionFile: string | undefined;
|
|
774
817
|
|
|
@@ -780,6 +823,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
780
823
|
runId: id,
|
|
781
824
|
mode: flatSteps.length > 1 ? "chain" : "single",
|
|
782
825
|
state: "running",
|
|
826
|
+
activityState: controlConfig.enabled ? "starting" : undefined,
|
|
783
827
|
startedAt: overallStartTime,
|
|
784
828
|
lastUpdate: overallStartTime,
|
|
785
829
|
pid: process.pid,
|
|
@@ -788,6 +832,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
788
832
|
steps: flatSteps.map((step) => ({
|
|
789
833
|
agent: step.agent,
|
|
790
834
|
status: "pending",
|
|
835
|
+
activityState: controlConfig.enabled ? "starting" : undefined,
|
|
791
836
|
skills: step.skills,
|
|
792
837
|
model: step.model,
|
|
793
838
|
attemptedModels: step.modelCandidates && step.modelCandidates.length > 0 ? step.modelCandidates : step.model ? [step.model] : undefined,
|
|
@@ -799,6 +844,28 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
799
844
|
|
|
800
845
|
fs.mkdirSync(asyncDir, { recursive: true });
|
|
801
846
|
writeJson(statusPath, statusPayload);
|
|
847
|
+
const interruptRunner = () => {
|
|
848
|
+
if (interrupted || statusPayload.state !== "running") return;
|
|
849
|
+
interrupted = true;
|
|
850
|
+
const now = Date.now();
|
|
851
|
+
statusPayload.state = "paused";
|
|
852
|
+
statusPayload.activityState = "paused";
|
|
853
|
+
statusPayload.lastUpdate = now;
|
|
854
|
+
const current = statusPayload.steps[statusPayload.currentStep];
|
|
855
|
+
if (current?.status === "running") {
|
|
856
|
+
current.activityState = "paused";
|
|
857
|
+
current.endedAt = now;
|
|
858
|
+
current.durationMs = current.startedAt ? now - current.startedAt : undefined;
|
|
859
|
+
}
|
|
860
|
+
writeJson(statusPath, statusPayload);
|
|
861
|
+
appendJsonl(eventsPath, JSON.stringify({
|
|
862
|
+
type: "subagent.run.paused",
|
|
863
|
+
ts: now,
|
|
864
|
+
runId: id,
|
|
865
|
+
}));
|
|
866
|
+
activeChildInterrupt?.();
|
|
867
|
+
};
|
|
868
|
+
process.on(ASYNC_INTERRUPT_SIGNAL, interruptRunner);
|
|
802
869
|
appendJsonl(
|
|
803
870
|
eventsPath,
|
|
804
871
|
JSON.stringify({
|
|
@@ -814,6 +881,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
814
881
|
let flatIndex = 0;
|
|
815
882
|
|
|
816
883
|
for (let stepIndex = 0; stepIndex < steps.length; stepIndex++) {
|
|
884
|
+
if (interrupted) break;
|
|
817
885
|
const step = steps[stepIndex];
|
|
818
886
|
|
|
819
887
|
if (isParallelGroup(step)) {
|
|
@@ -912,6 +980,9 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
912
980
|
outputFile: path.join(asyncDir, `output-${fi}.log`),
|
|
913
981
|
piPackageRoot: config.piPackageRoot,
|
|
914
982
|
piArgv1: config.piArgv1,
|
|
983
|
+
registerInterrupt: (interrupt) => {
|
|
984
|
+
activeChildInterrupt = interrupt;
|
|
985
|
+
},
|
|
915
986
|
});
|
|
916
987
|
if (task.sessionFile) {
|
|
917
988
|
latestSessionFile = task.sessionFile;
|
|
@@ -944,24 +1015,23 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
944
1015
|
|
|
945
1016
|
flatIndex += group.parallel.length;
|
|
946
1017
|
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
}
|
|
961
|
-
statusPayload.totalTokens = { ...previousCumulativeTokens };
|
|
962
|
-
statusPayload.lastUpdate = Date.now();
|
|
963
|
-
writeJson(statusPath, statusPayload);
|
|
1018
|
+
for (let t = 0; t < group.parallel.length; t++) {
|
|
1019
|
+
const fi = groupStartFlatIndex + t;
|
|
1020
|
+
const sessionTokens = config.sessionDir
|
|
1021
|
+
? parseSessionTokens(path.join(config.sessionDir, `parallel-${t}`))
|
|
1022
|
+
: null;
|
|
1023
|
+
const taskTokens = sessionTokens ?? tokenUsageFromAttempts(parallelResults[t]?.modelAttempts);
|
|
1024
|
+
if (!taskTokens) continue;
|
|
1025
|
+
statusPayload.steps[fi].tokens = taskTokens;
|
|
1026
|
+
previousCumulativeTokens = {
|
|
1027
|
+
input: previousCumulativeTokens.input + taskTokens.input,
|
|
1028
|
+
output: previousCumulativeTokens.output + taskTokens.output,
|
|
1029
|
+
total: previousCumulativeTokens.total + taskTokens.total,
|
|
1030
|
+
};
|
|
964
1031
|
}
|
|
1032
|
+
statusPayload.totalTokens = { ...previousCumulativeTokens };
|
|
1033
|
+
statusPayload.lastUpdate = Date.now();
|
|
1034
|
+
writeJson(statusPath, statusPayload);
|
|
965
1035
|
|
|
966
1036
|
for (const pr of parallelResults) {
|
|
967
1037
|
results.push({
|
|
@@ -1007,6 +1077,8 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1007
1077
|
const stepStartTime = Date.now();
|
|
1008
1078
|
statusPayload.currentStep = flatIndex;
|
|
1009
1079
|
statusPayload.steps[flatIndex].status = "running";
|
|
1080
|
+
statusPayload.steps[flatIndex].activityState = "active";
|
|
1081
|
+
statusPayload.activityState = "active";
|
|
1010
1082
|
statusPayload.steps[flatIndex].skills = seqStep.skills;
|
|
1011
1083
|
statusPayload.steps[flatIndex].startedAt = stepStartTime;
|
|
1012
1084
|
statusPayload.lastUpdate = stepStartTime;
|
|
@@ -1029,6 +1101,9 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1029
1101
|
outputFile: path.join(asyncDir, `output-${flatIndex}.log`),
|
|
1030
1102
|
piPackageRoot: config.piPackageRoot,
|
|
1031
1103
|
piArgv1: config.piArgv1,
|
|
1104
|
+
registerInterrupt: (interrupt) => {
|
|
1105
|
+
activeChildInterrupt = interrupt;
|
|
1106
|
+
},
|
|
1032
1107
|
});
|
|
1033
1108
|
if (seqStep.sessionFile) {
|
|
1034
1109
|
latestSessionFile = seqStep.sessionFile;
|
|
@@ -1046,7 +1121,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1046
1121
|
});
|
|
1047
1122
|
|
|
1048
1123
|
const cumulativeTokens = config.sessionDir ? parseSessionTokens(config.sessionDir) : null;
|
|
1049
|
-
|
|
1124
|
+
let stepTokens: TokenUsage | null = cumulativeTokens
|
|
1050
1125
|
? {
|
|
1051
1126
|
input: cumulativeTokens.input - previousCumulativeTokens.input,
|
|
1052
1127
|
output: cumulativeTokens.output - previousCumulativeTokens.output,
|
|
@@ -1055,6 +1130,15 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1055
1130
|
: null;
|
|
1056
1131
|
if (cumulativeTokens) {
|
|
1057
1132
|
previousCumulativeTokens = cumulativeTokens;
|
|
1133
|
+
} else {
|
|
1134
|
+
stepTokens = tokenUsageFromAttempts(singleResult.modelAttempts);
|
|
1135
|
+
if (stepTokens) {
|
|
1136
|
+
previousCumulativeTokens = {
|
|
1137
|
+
input: previousCumulativeTokens.input + stepTokens.input,
|
|
1138
|
+
output: previousCumulativeTokens.output + stepTokens.output,
|
|
1139
|
+
total: previousCumulativeTokens.total + stepTokens.total,
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1058
1142
|
}
|
|
1059
1143
|
|
|
1060
1144
|
const stepEndTime = Date.now();
|
|
@@ -1139,7 +1223,8 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1139
1223
|
|
|
1140
1224
|
const effectiveSessionFile = sessionFile ?? latestSessionFile;
|
|
1141
1225
|
const runEndedAt = Date.now();
|
|
1142
|
-
statusPayload.state = results.every((r) => r.success) ? "complete" : "failed";
|
|
1226
|
+
statusPayload.state = interrupted ? "paused" : results.every((r) => r.success) ? "complete" : "failed";
|
|
1227
|
+
statusPayload.activityState = interrupted ? "paused" : undefined;
|
|
1143
1228
|
statusPayload.endedAt = runEndedAt;
|
|
1144
1229
|
statusPayload.lastUpdate = runEndedAt;
|
|
1145
1230
|
statusPayload.sessionFile = effectiveSessionFile;
|
|
@@ -1186,8 +1271,8 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1186
1271
|
writeJson(resultPath, {
|
|
1187
1272
|
id,
|
|
1188
1273
|
agent: agentName,
|
|
1189
|
-
success: results.every((r) => r.success),
|
|
1190
|
-
summary,
|
|
1274
|
+
success: !interrupted && results.every((r) => r.success),
|
|
1275
|
+
summary: interrupted ? "Paused after interrupt. Waiting for explicit next action." : summary,
|
|
1191
1276
|
results: results.map((r) => ({
|
|
1192
1277
|
agent: r.agent,
|
|
1193
1278
|
output: r.output,
|
|
@@ -1199,7 +1284,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1199
1284
|
artifactPaths: r.artifactPaths,
|
|
1200
1285
|
truncated: r.truncated,
|
|
1201
1286
|
})),
|
|
1202
|
-
exitCode: results.every((r) => r.success) ? 0 : 1,
|
|
1287
|
+
exitCode: interrupted || results.every((r) => r.success) ? 0 : 1,
|
|
1203
1288
|
timestamp: runEndedAt,
|
|
1204
1289
|
durationMs: runEndedAt - overallStartTime,
|
|
1205
1290
|
truncated,
|
package/subagents-status.ts
CHANGED
|
@@ -25,6 +25,7 @@ function statusColor(theme: Theme, status: AsyncRunSummary["state"]): string {
|
|
|
25
25
|
case "queued": return theme.fg("accent", status);
|
|
26
26
|
case "complete": return theme.fg("success", status);
|
|
27
27
|
case "failed": return theme.fg("error", status);
|
|
28
|
+
case "paused": return theme.fg("warning", status);
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
|
|
@@ -33,6 +34,7 @@ function stepStatusColor(theme: Theme, status: string): string {
|
|
|
33
34
|
if (status === "pending") return theme.fg("dim", status);
|
|
34
35
|
if (status === "complete" || status === "completed") return theme.fg("success", status);
|
|
35
36
|
if (status === "failed") return theme.fg("error", status);
|
|
37
|
+
if (status === "paused") return theme.fg("warning", status);
|
|
36
38
|
return status;
|
|
37
39
|
}
|
|
38
40
|
|
|
@@ -170,7 +172,8 @@ export class SubagentsStatusComponent implements Component {
|
|
|
170
172
|
: "";
|
|
171
173
|
const duration = step.durationMs !== undefined ? ` | ${formatDuration(step.durationMs)}` : "";
|
|
172
174
|
const tokens = step.tokens ? ` | ${formatTokens(step.tokens.total)} tok` : "";
|
|
173
|
-
const
|
|
175
|
+
const activity = step.activityState ? `/${step.activityState}` : "";
|
|
176
|
+
const line = ` ${step.index + 1}. ${step.agent} | ${stepStatusColor(this.theme, step.status)}${activity}${model}${attempts}${duration}${tokens}`;
|
|
174
177
|
lines.push(row(truncateToWidth(line, innerW), width, this.theme));
|
|
175
178
|
if (step.error) {
|
|
176
179
|
lines.push(row(truncateToWidth(` ${step.error}`, innerW), width, this.theme));
|
package/types.ts
CHANGED
|
@@ -40,6 +40,35 @@ export interface TokenUsage {
|
|
|
40
40
|
total: number;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
export type ActivityState = "starting" | "active" | "quiet" | "stalled" | "paused";
|
|
44
|
+
export type ControlParentMode = "transitions" | "verbose";
|
|
45
|
+
export type ControlEventType = "stalled" | "recovered" | "paused" | "resumed" | "activity";
|
|
46
|
+
|
|
47
|
+
export interface ControlConfig {
|
|
48
|
+
enabled?: boolean;
|
|
49
|
+
quietAfterMs?: number;
|
|
50
|
+
stalledAfterMs?: number;
|
|
51
|
+
parentMode?: ControlParentMode;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface ResolvedControlConfig {
|
|
55
|
+
enabled: boolean;
|
|
56
|
+
quietAfterMs: number;
|
|
57
|
+
stalledAfterMs: number;
|
|
58
|
+
parentMode: ControlParentMode;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface ControlEvent {
|
|
62
|
+
type: ControlEventType;
|
|
63
|
+
from?: ActivityState;
|
|
64
|
+
to: ActivityState;
|
|
65
|
+
ts: number;
|
|
66
|
+
agent: string;
|
|
67
|
+
index?: number;
|
|
68
|
+
runId: string;
|
|
69
|
+
message: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
43
72
|
// ============================================================================
|
|
44
73
|
// Progress Tracking
|
|
45
74
|
// ============================================================================
|
|
@@ -48,6 +77,7 @@ export interface AgentProgress {
|
|
|
48
77
|
index: number;
|
|
49
78
|
agent: string;
|
|
50
79
|
status: "pending" | "running" | "completed" | "failed" | "detached";
|
|
80
|
+
activityState?: ActivityState;
|
|
51
81
|
task: string;
|
|
52
82
|
skills?: string[];
|
|
53
83
|
lastActivityAt?: number;
|
|
@@ -92,11 +122,13 @@ export interface SingleResult {
|
|
|
92
122
|
exitCode: number;
|
|
93
123
|
detached?: boolean;
|
|
94
124
|
detachedReason?: string;
|
|
125
|
+
interrupted?: boolean;
|
|
95
126
|
messages?: Message[];
|
|
96
127
|
usage: Usage;
|
|
97
128
|
model?: string;
|
|
98
129
|
attemptedModels?: string[];
|
|
99
130
|
modelAttempts?: ModelAttempt[];
|
|
131
|
+
controlEvents?: ControlEvent[];
|
|
100
132
|
error?: string;
|
|
101
133
|
sessionFile?: string;
|
|
102
134
|
skills?: string[];
|
|
@@ -115,6 +147,7 @@ export interface Details {
|
|
|
115
147
|
mode: "single" | "parallel" | "chain" | "management";
|
|
116
148
|
context?: "fresh" | "fork";
|
|
117
149
|
results: SingleResult[];
|
|
150
|
+
controlEvents?: ControlEvent[];
|
|
118
151
|
asyncId?: string;
|
|
119
152
|
asyncDir?: string;
|
|
120
153
|
progress?: AgentProgress[];
|
|
@@ -162,15 +195,18 @@ export interface ArtifactConfig {
|
|
|
162
195
|
export interface AsyncStatus {
|
|
163
196
|
runId: string;
|
|
164
197
|
mode: "single" | "chain";
|
|
165
|
-
state: "queued" | "running" | "complete" | "failed";
|
|
198
|
+
state: "queued" | "running" | "complete" | "failed" | "paused";
|
|
199
|
+
activityState?: ActivityState;
|
|
166
200
|
startedAt: number;
|
|
167
201
|
endedAt?: number;
|
|
168
202
|
lastUpdate?: number;
|
|
203
|
+
pid?: number;
|
|
169
204
|
cwd?: string;
|
|
170
205
|
currentStep?: number;
|
|
171
206
|
steps?: Array<{
|
|
172
207
|
agent: string;
|
|
173
208
|
status: string;
|
|
209
|
+
activityState?: ActivityState;
|
|
174
210
|
durationMs?: number;
|
|
175
211
|
tokens?: TokenUsage;
|
|
176
212
|
skills?: string[];
|
|
@@ -188,7 +224,8 @@ export interface AsyncStatus {
|
|
|
188
224
|
export interface AsyncJobState {
|
|
189
225
|
asyncId: string;
|
|
190
226
|
asyncDir: string;
|
|
191
|
-
status: "queued" | "running" | "complete" | "failed";
|
|
227
|
+
status: "queued" | "running" | "complete" | "failed" | "paused";
|
|
228
|
+
activityState?: ActivityState;
|
|
192
229
|
mode?: "single" | "chain";
|
|
193
230
|
agents?: string[];
|
|
194
231
|
currentStep?: number;
|
|
@@ -205,6 +242,17 @@ export interface SubagentState {
|
|
|
205
242
|
baseCwd: string;
|
|
206
243
|
currentSessionId: string | null;
|
|
207
244
|
asyncJobs: Map<string, AsyncJobState>;
|
|
245
|
+
foregroundControls: Map<string, {
|
|
246
|
+
runId: string;
|
|
247
|
+
mode: "single" | "parallel" | "chain";
|
|
248
|
+
startedAt: number;
|
|
249
|
+
updatedAt: number;
|
|
250
|
+
currentAgent?: string;
|
|
251
|
+
currentIndex?: number;
|
|
252
|
+
currentActivityState?: ActivityState;
|
|
253
|
+
interrupt?: () => boolean;
|
|
254
|
+
}>;
|
|
255
|
+
lastForegroundControlId: string | null;
|
|
208
256
|
cleanupTimers: Map<string, ReturnType<typeof setTimeout>>;
|
|
209
257
|
lastUiContext: ExtensionContext | null;
|
|
210
258
|
poller: NodeJS.Timeout | null;
|
|
@@ -251,9 +299,12 @@ export const INTERCOM_DETACH_RESPONSE_EVENT = "pi-intercom:detach-response";
|
|
|
251
299
|
export interface RunSyncOptions {
|
|
252
300
|
cwd?: string;
|
|
253
301
|
signal?: AbortSignal;
|
|
302
|
+
interruptSignal?: AbortSignal;
|
|
254
303
|
allowIntercomDetach?: boolean;
|
|
255
304
|
intercomEvents?: IntercomEventBus;
|
|
256
305
|
onUpdate?: (r: import("@mariozechner/pi-agent-core").AgentToolResult<Details>) => void;
|
|
306
|
+
onControlEvent?: (event: ControlEvent) => void;
|
|
307
|
+
controlConfig?: ResolvedControlConfig;
|
|
257
308
|
maxOutput?: MaxOutputConfig;
|
|
258
309
|
artifactsDir?: string;
|
|
259
310
|
artifactConfig?: ArtifactConfig;
|
|
@@ -291,6 +342,7 @@ export interface ExtensionConfig {
|
|
|
291
342
|
forceTopLevelAsync?: boolean;
|
|
292
343
|
defaultSessionDir?: string;
|
|
293
344
|
maxSubagentDepth?: number;
|
|
345
|
+
control?: ControlConfig;
|
|
294
346
|
parallel?: TopLevelParallelConfig;
|
|
295
347
|
worktreeSetupHook?: string;
|
|
296
348
|
worktreeSetupHookTimeoutMs?: number;
|
package/utils.ts
CHANGED
|
@@ -231,6 +231,7 @@ function compactCompletedProgress(progress: AgentProgress): AgentProgress {
|
|
|
231
231
|
index: progress.index,
|
|
232
232
|
agent: progress.agent,
|
|
233
233
|
status: progress.status,
|
|
234
|
+
activityState: progress.activityState,
|
|
234
235
|
task: progress.task,
|
|
235
236
|
skills: progress.skills,
|
|
236
237
|
toolCount: progress.toolCount,
|