pi-subagents 0.29.0 → 0.31.0
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 +43 -0
- package/README.md +125 -19
- package/agents/context-builder.md +3 -3
- package/agents/planner.md +1 -1
- package/agents/researcher.md +1 -1
- package/agents/scout.md +1 -1
- package/package.json +7 -7
- package/skills/pi-subagents/SKILL.md +30 -0
- package/src/agents/agent-management.ts +189 -8
- package/src/agents/agent-serializer.ts +35 -12
- package/src/agents/agents.ts +243 -24
- package/src/agents/frontmatter.ts +66 -2
- package/src/agents/proactive-skills.ts +191 -0
- package/src/agents/skills.ts +117 -20
- package/src/extension/doctor.ts +20 -0
- package/src/extension/fanout-child.ts +2 -1
- package/src/extension/index.ts +50 -5
- package/src/extension/schemas.ts +40 -79
- package/src/intercom/intercom-bridge.ts +2 -3
- package/src/runs/background/async-execution.ts +180 -67
- package/src/runs/background/async-job-tracker.ts +56 -11
- package/src/runs/background/async-resume.ts +53 -5
- package/src/runs/background/async-status.ts +4 -1
- package/src/runs/background/chain-append.ts +282 -0
- package/src/runs/background/chain-root-attachment.ts +161 -0
- package/src/runs/background/result-watcher.ts +11 -2
- package/src/runs/background/run-status.ts +1 -0
- package/src/runs/background/stale-run-reconciler.ts +9 -4
- package/src/runs/background/subagent-runner.ts +158 -11
- package/src/runs/foreground/chain-execution.ts +26 -2
- package/src/runs/foreground/execution.ts +114 -8
- package/src/runs/foreground/subagent-executor.ts +611 -87
- package/src/runs/shared/acceptance.ts +285 -34
- package/src/runs/shared/chain-outputs.ts +23 -8
- package/src/runs/shared/completion-guard.ts +1 -1
- package/src/runs/shared/dynamic-fanout.ts +5 -3
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +2 -2
- package/src/runs/shared/parallel-utils.ts +13 -1
- package/src/runs/shared/pi-args.ts +12 -3
- package/src/runs/shared/single-output.ts +15 -1
- package/src/runs/shared/subagent-control.ts +8 -11
- package/src/shared/settings.ts +1 -0
- package/src/shared/types.ts +17 -2
- package/src/shared/utils.ts +19 -1
- package/src/slash/prompt-template-bridge.ts +26 -3
- package/src/slash/slash-bridge.ts +3 -1
- package/src/slash/slash-commands.ts +34 -4
- package/src/tui/render.ts +265 -13
|
@@ -23,6 +23,8 @@ import {
|
|
|
23
23
|
DEFAULT_MAX_OUTPUT,
|
|
24
24
|
INTERCOM_DETACH_REQUEST_EVENT,
|
|
25
25
|
INTERCOM_DETACH_RESPONSE_EVENT,
|
|
26
|
+
type AcceptanceLedger,
|
|
27
|
+
type ResolvedAcceptanceConfig,
|
|
26
28
|
truncateOutput,
|
|
27
29
|
getSubagentDepthEnv,
|
|
28
30
|
} from "../../shared/types.ts";
|
|
@@ -47,7 +49,7 @@ import { createJsonlWriter } from "../../shared/jsonl-writer.ts";
|
|
|
47
49
|
import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
|
|
48
50
|
import { applyThinkingSuffix, buildPiArgs, cleanupTempDir } from "../shared/pi-args.ts";
|
|
49
51
|
import { readStructuredOutput } from "../shared/structured-output.ts";
|
|
50
|
-
import { captureSingleOutputSnapshot, formatSavedOutputReference, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from "../shared/single-output.ts";
|
|
52
|
+
import { captureSingleOutputSnapshot, formatSavedOutputReference, injectOutputPathSystemPrompt, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from "../shared/single-output.ts";
|
|
51
53
|
import {
|
|
52
54
|
buildModelCandidates,
|
|
53
55
|
formatModelAttemptNote,
|
|
@@ -82,6 +84,34 @@ function sumUsage(target: Usage, source: Usage): void {
|
|
|
82
84
|
target.turns += source.turns;
|
|
83
85
|
}
|
|
84
86
|
|
|
87
|
+
function formatTimeoutMessage(timeoutMs: number): string {
|
|
88
|
+
return `Subagent timed out after ${timeoutMs}ms.`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function resolveAttemptTimeout(options: RunSyncOptions): { timeoutMs: number; remainingMs: number; message: string } | undefined {
|
|
92
|
+
if (options.timeoutMs === undefined) return undefined;
|
|
93
|
+
const deadlineAt = options.deadlineAt ?? Date.now() + options.timeoutMs;
|
|
94
|
+
return {
|
|
95
|
+
timeoutMs: options.timeoutMs,
|
|
96
|
+
remainingMs: Math.max(0, deadlineAt - Date.now()),
|
|
97
|
+
message: formatTimeoutMessage(options.timeoutMs),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function buildTimedOutAcceptanceLedger(acceptance: ResolvedAcceptanceConfig): AcceptanceLedger {
|
|
102
|
+
return {
|
|
103
|
+
status: acceptance.level === "none" ? "not-required" : "rejected",
|
|
104
|
+
explicit: acceptance.explicit,
|
|
105
|
+
effectiveAcceptance: acceptance,
|
|
106
|
+
inferredReason: acceptance.inferredReason,
|
|
107
|
+
criteria: acceptance.criteria,
|
|
108
|
+
runtimeChecks: acceptance.level === "none"
|
|
109
|
+
? []
|
|
110
|
+
: [{ id: "timeout", status: "failed", message: "Acceptance was not evaluated because the subagent timed out." }],
|
|
111
|
+
verifyRuns: [],
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
85
115
|
function appendRecentOutput(progress: AgentProgress, lines: string[]): void {
|
|
86
116
|
if (lines.length === 0) return;
|
|
87
117
|
progress.recentOutput.push(...lines.filter((line) => line.trim()));
|
|
@@ -162,8 +192,10 @@ async function runSingleAttempt(
|
|
|
162
192
|
systemPromptMode: agent.systemPromptMode,
|
|
163
193
|
inheritProjectContext: agent.inheritProjectContext,
|
|
164
194
|
inheritSkills: agent.inheritSkills,
|
|
195
|
+
requireReadTool: Boolean(shared.resolvedSkillNames?.length),
|
|
165
196
|
tools: agent.tools,
|
|
166
197
|
extensions: agent.extensions,
|
|
198
|
+
subagentOnlyExtensions: agent.subagentOnlyExtensions,
|
|
167
199
|
systemPrompt: shared.systemPrompt,
|
|
168
200
|
mcpDirectTools: agent.mcpDirectTools,
|
|
169
201
|
cwd: options.cwd ?? runtimeCwd,
|
|
@@ -177,6 +209,7 @@ async function runSingleAttempt(
|
|
|
177
209
|
parentControlInbox: options.nestedRoute?.controlInbox,
|
|
178
210
|
parentRootRunId: options.nestedRoute?.rootRunId,
|
|
179
211
|
parentCapabilityToken: options.nestedRoute?.capabilityToken,
|
|
212
|
+
parentSessionId: options.parentSessionId,
|
|
180
213
|
structuredOutput: options.structuredOutput,
|
|
181
214
|
});
|
|
182
215
|
|
|
@@ -226,6 +259,21 @@ async function runSingleAttempt(
|
|
|
226
259
|
lastActivityAt: startTime,
|
|
227
260
|
};
|
|
228
261
|
result.progress = progress;
|
|
262
|
+
const attemptTimeout = resolveAttemptTimeout(options);
|
|
263
|
+
if (attemptTimeout?.remainingMs === 0) {
|
|
264
|
+
result.exitCode = 1;
|
|
265
|
+
result.timedOut = true;
|
|
266
|
+
result.error = attemptTimeout.message;
|
|
267
|
+
result.finalOutput = attemptTimeout.message;
|
|
268
|
+
progress.status = "failed";
|
|
269
|
+
progress.error = attemptTimeout.message;
|
|
270
|
+
result.progressSummary = {
|
|
271
|
+
toolCount: progress.toolCount,
|
|
272
|
+
tokens: progress.tokens,
|
|
273
|
+
durationMs: progress.durationMs,
|
|
274
|
+
};
|
|
275
|
+
return result;
|
|
276
|
+
}
|
|
229
277
|
const spawnEnv = { ...process.env, ...sharedEnv, ...getSubagentDepthEnv(options.maxSubagentDepth) };
|
|
230
278
|
let observedMutationAttempt = false;
|
|
231
279
|
|
|
@@ -247,6 +295,23 @@ async function runSingleAttempt(
|
|
|
247
295
|
let removeAbortListener: (() => void) | undefined;
|
|
248
296
|
let removeInterruptListener: (() => void) | undefined;
|
|
249
297
|
let activityTimer: NodeJS.Timeout | undefined;
|
|
298
|
+
let timeoutTimer: NodeJS.Timeout | undefined;
|
|
299
|
+
let timeoutTerminationTimer: NodeJS.Timeout | undefined;
|
|
300
|
+
let timeoutHardKillTimer: NodeJS.Timeout | undefined;
|
|
301
|
+
const clearTimeoutTimers = () => {
|
|
302
|
+
if (timeoutTimer) {
|
|
303
|
+
clearTimeout(timeoutTimer);
|
|
304
|
+
timeoutTimer = undefined;
|
|
305
|
+
}
|
|
306
|
+
if (timeoutTerminationTimer) {
|
|
307
|
+
clearTimeout(timeoutTerminationTimer);
|
|
308
|
+
timeoutTerminationTimer = undefined;
|
|
309
|
+
}
|
|
310
|
+
if (timeoutHardKillTimer) {
|
|
311
|
+
clearTimeout(timeoutHardKillTimer);
|
|
312
|
+
timeoutHardKillTimer = undefined;
|
|
313
|
+
}
|
|
314
|
+
};
|
|
250
315
|
|
|
251
316
|
const detachForIntercom = () => {
|
|
252
317
|
detached = true;
|
|
@@ -315,6 +380,7 @@ async function runSingleAttempt(
|
|
|
315
380
|
settled = true;
|
|
316
381
|
clearFinalDrainTimers();
|
|
317
382
|
clearStdioGuard();
|
|
383
|
+
clearTimeoutTimers();
|
|
318
384
|
if (activityTimer) {
|
|
319
385
|
clearInterval(activityTimer);
|
|
320
386
|
activityTimer = undefined;
|
|
@@ -428,7 +494,8 @@ async function runSingleAttempt(
|
|
|
428
494
|
const fireUpdate = () => {
|
|
429
495
|
if (!options.onUpdate || processClosed) return;
|
|
430
496
|
progress.durationMs = Date.now() - startTime;
|
|
431
|
-
|
|
497
|
+
const output = result.timedOut && result.finalOutput ? result.finalOutput : getFinalOutput(result.messages);
|
|
498
|
+
emitUpdateSnapshot(output || "(running...)");
|
|
432
499
|
};
|
|
433
500
|
|
|
434
501
|
const processLine = (line: string) => {
|
|
@@ -554,6 +621,31 @@ async function runSingleAttempt(
|
|
|
554
621
|
activityTimer.unref?.();
|
|
555
622
|
}
|
|
556
623
|
|
|
624
|
+
if (attemptTimeout) {
|
|
625
|
+
timeoutTimer = setTimeout(() => {
|
|
626
|
+
if (processClosed || settled || detached || interruptedByControl) return;
|
|
627
|
+
result.timedOut = true;
|
|
628
|
+
result.error = attemptTimeout.message;
|
|
629
|
+
result.finalOutput = attemptTimeout.message;
|
|
630
|
+
progress.status = "failed";
|
|
631
|
+
progress.error = attemptTimeout.message;
|
|
632
|
+
progress.durationMs = Date.now() - startTime;
|
|
633
|
+
fireUpdate();
|
|
634
|
+
trySignalChild(proc, "SIGINT");
|
|
635
|
+
timeoutTerminationTimer = setTimeout(() => {
|
|
636
|
+
if (processClosed || settled || detached) return;
|
|
637
|
+
trySignalChild(proc, "SIGTERM");
|
|
638
|
+
}, 1000);
|
|
639
|
+
timeoutTerminationTimer.unref?.();
|
|
640
|
+
timeoutHardKillTimer = setTimeout(() => {
|
|
641
|
+
if (processClosed || settled || detached) return;
|
|
642
|
+
trySignalChild(proc, "SIGKILL");
|
|
643
|
+
}, 4000);
|
|
644
|
+
timeoutHardKillTimer.unref?.();
|
|
645
|
+
}, attemptTimeout.remainingMs);
|
|
646
|
+
timeoutTimer.unref?.();
|
|
647
|
+
}
|
|
648
|
+
|
|
557
649
|
let stderrBuf = "";
|
|
558
650
|
|
|
559
651
|
const clearStdioGuard = attachPostExitStdioGuard(proc, { idleMs: 2000, hardMs: 8000 });
|
|
@@ -624,7 +716,9 @@ async function runSingleAttempt(
|
|
|
624
716
|
if (options.interruptSignal) {
|
|
625
717
|
const interrupt = () => {
|
|
626
718
|
if (processClosed || detached || settled) return;
|
|
719
|
+
if (result.timedOut) return;
|
|
627
720
|
interruptedByControl = true;
|
|
721
|
+
clearTimeoutTimers();
|
|
628
722
|
progress.status = "running";
|
|
629
723
|
progress.durationMs = Date.now() - startTime;
|
|
630
724
|
result.interrupted = true;
|
|
@@ -709,8 +803,14 @@ async function runSingleAttempt(
|
|
|
709
803
|
durationMs: progress.durationMs,
|
|
710
804
|
};
|
|
711
805
|
|
|
712
|
-
|
|
713
|
-
|
|
806
|
+
const acceptanceOutput = getFinalOutput(result.messages);
|
|
807
|
+
let fullOutput = stripAcceptanceReport(acceptanceOutput);
|
|
808
|
+
if (result.timedOut) {
|
|
809
|
+
const timeoutMessage = formatTimeoutMessage(options.timeoutMs ?? 0);
|
|
810
|
+
fullOutput = fullOutput.trim()
|
|
811
|
+
? `${timeoutMessage}\n\nPartial output before timeout:\n${fullOutput}`
|
|
812
|
+
: timeoutMessage;
|
|
813
|
+
}
|
|
714
814
|
const completionGuard = result.exitCode === 0 && !result.error && agent.completionGuard !== false
|
|
715
815
|
? evaluateCompletionMutationGuard({
|
|
716
816
|
agent: agent.name,
|
|
@@ -834,6 +934,7 @@ export async function runSync(
|
|
|
834
934
|
const skillInjection = buildSkillInjection(resolvedSkills);
|
|
835
935
|
systemPrompt = systemPrompt ? `${systemPrompt}\n\n${skillInjection}` : skillInjection;
|
|
836
936
|
}
|
|
937
|
+
systemPrompt = injectOutputPathSystemPrompt(systemPrompt, options.outputPath);
|
|
837
938
|
|
|
838
939
|
const candidates = buildModelCandidates(
|
|
839
940
|
options.modelOverride ?? agent.model,
|
|
@@ -891,6 +992,9 @@ export async function runSync(
|
|
|
891
992
|
usage: { ...result.usage },
|
|
892
993
|
};
|
|
893
994
|
modelAttempts.push(attempt);
|
|
995
|
+
if (result.timedOut) {
|
|
996
|
+
break;
|
|
997
|
+
}
|
|
894
998
|
if (attemptSucceeded) {
|
|
895
999
|
break;
|
|
896
1000
|
}
|
|
@@ -966,14 +1070,16 @@ export async function runSync(
|
|
|
966
1070
|
if (sessionFile) result.sessionFile = sessionFile;
|
|
967
1071
|
}
|
|
968
1072
|
|
|
969
|
-
|
|
1073
|
+
result.acceptance = result.timedOut
|
|
1074
|
+
? buildTimedOutAcceptanceLedger(effectiveAcceptance)
|
|
1075
|
+
: await evaluateAcceptance({
|
|
970
1076
|
acceptance: effectiveAcceptance,
|
|
971
1077
|
output: acceptanceOutputByResult.get(result) ?? result.finalOutput ?? "",
|
|
972
1078
|
cwd: options.cwd ?? runtimeCwd,
|
|
973
1079
|
});
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
1080
|
+
const acceptanceFailure = acceptanceFailureMessage(result.acceptance);
|
|
1081
|
+
stripAcceptanceReportsFromMessages(result.messages);
|
|
1082
|
+
if (acceptanceFailure && result.acceptance.explicit && result.exitCode === 0 && !result.detached && !result.interrupted && !result.timedOut) {
|
|
977
1083
|
result.exitCode = 1;
|
|
978
1084
|
result.error = result.error ? `${result.error}\n${acceptanceFailure}` : acceptanceFailure;
|
|
979
1085
|
if (result.progress) {
|