@narumitw/pi-subagents 0.49.2 → 0.51.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/README.md +313 -53
- package/package.json +11 -8
- package/src/adaptive-scheduler.ts +196 -0
- package/src/admission-benchmark.ts +95 -0
- package/src/admission-policy.ts +78 -0
- package/src/agent-projection.ts +53 -0
- package/src/agents.ts +58 -1
- package/src/auto-transport.ts +114 -0
- package/src/blocking-status.ts +63 -0
- package/src/capabilities.ts +145 -0
- package/src/capability-grant.ts +115 -0
- package/src/capability-router.ts +107 -0
- package/src/completion-delivery.ts +257 -0
- package/src/config-status.ts +221 -0
- package/src/config-ui.ts +215 -236
- package/src/consult-resources.ts +4 -27
- package/src/consult.ts +9 -1
- package/src/create-stateful-transport.ts +55 -0
- package/src/delegation-contract.ts +417 -0
- package/src/execution-plan.ts +322 -0
- package/src/execution-profiles.ts +95 -0
- package/src/execution-ui.ts +320 -0
- package/src/execution.ts +848 -158
- package/src/in-process-transport.ts +269 -25
- package/src/inspect-render.ts +101 -1
- package/src/inspect.ts +296 -3
- package/src/integration-controller.ts +98 -0
- package/src/limits.ts +3 -0
- package/src/orchestration-metrics.ts +78 -0
- package/src/outcome.ts +61 -0
- package/src/panel-child-group.ts +35 -0
- package/src/panel-contract.ts +343 -0
- package/src/panel-evidence.ts +59 -0
- package/src/panel-execution.ts +772 -0
- package/src/panel-failure.ts +56 -0
- package/src/panel-planning.ts +175 -0
- package/src/panel-prompts.ts +132 -0
- package/src/panel-reconciliation.ts +57 -0
- package/src/panel-render.ts +103 -0
- package/src/parallel-limit-ui.ts +112 -0
- package/src/params.ts +172 -3
- package/src/persistence.ts +182 -32
- package/src/prompt-resources.ts +38 -0
- package/src/registry-types.ts +175 -0
- package/src/registry.ts +466 -143
- package/src/render.ts +72 -6
- package/src/result-contract.ts +416 -0
- package/src/retained-semantic-state.ts +100 -0
- package/src/rpc-timeout-finalization.ts +207 -0
- package/src/rpc-transport-metadata.ts +65 -0
- package/src/rpc-transport.ts +990 -0
- package/src/rpc-turn-capture.ts +142 -0
- package/src/runner-result.ts +55 -0
- package/src/runner-usage.ts +48 -0
- package/src/runner.ts +325 -73
- package/src/semantic-snapshot.ts +214 -0
- package/src/settings.ts +254 -35
- package/src/spawn-idempotency.ts +61 -0
- package/src/stateful-config.ts +13 -0
- package/src/stateful-guidance.ts +1 -0
- package/src/stateful-lifecycle.ts +45 -2
- package/src/stateful-limit-ui.ts +246 -0
- package/src/stateful-limits.ts +96 -0
- package/src/stateful-prompt.ts +11 -2
- package/src/stateful-render.ts +48 -3
- package/src/stateful.ts +467 -357
- package/src/subagents.ts +114 -46
- package/src/subprocess-transport.ts +64 -5
- package/src/supervision.ts +103 -0
- package/src/timeout-checkpoint.ts +305 -0
- package/src/timeout-finalization.ts +75 -0
- package/src/transport-types.ts +68 -0
- package/src/transport-ui.ts +169 -0
- package/src/transport.ts +16 -4
- package/src/turn-budget.ts +109 -0
- package/src/verification-policy.ts +17 -0
- package/src/work-item-ledger.ts +682 -0
- package/src/work-item-persistence.ts +218 -0
- package/src/workflow-planning.ts +150 -0
- package/src/workflow-ui.ts +61 -0
- package/src/workspace.ts +69 -12
package/src/runner.ts
CHANGED
|
@@ -5,8 +5,12 @@ import * as path from "node:path";
|
|
|
5
5
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
6
6
|
import type { Message } from "@earendil-works/pi-ai";
|
|
7
7
|
import { withFileMutationQueue } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import type { SchedulingDecision } from "./adaptive-scheduler.js";
|
|
8
9
|
import type { AgentConfig, AgentScope, AgentSource, SubagentThinkingLevel } from "./agents.js";
|
|
10
|
+
import { type CapabilityGrant, revokeCapabilityGrant } from "./capability-grant.js";
|
|
9
11
|
import type { TargetPolicyAudit } from "./cwd-policy.js";
|
|
12
|
+
import type { DelegationContract } from "./delegation-contract.js";
|
|
13
|
+
import type { ExecutionPlan } from "./execution-plan.js";
|
|
10
14
|
import {
|
|
11
15
|
appendBounded,
|
|
12
16
|
DEFAULT_MAX_CONTEXT_BYTES,
|
|
@@ -16,25 +20,50 @@ import {
|
|
|
16
20
|
MAX_SUBAGENT_TIMEOUT_MS,
|
|
17
21
|
truncateUtf8,
|
|
18
22
|
} from "./limits.js";
|
|
23
|
+
import type { OrchestrationMetrics } from "./orchestration-metrics.js";
|
|
24
|
+
import { type ClassifiedSubagentOutcome, classifyStructuredOutcome } from "./outcome.js";
|
|
25
|
+
import type { PanelSynthesis } from "./panel-contract.js";
|
|
26
|
+
import type { PanelEvidenceArtifact } from "./panel-evidence.js";
|
|
27
|
+
import type { PanelFailure } from "./panel-failure.js";
|
|
28
|
+
import type { PanelPhaseBudgets, PanelPreset } from "./panel-planning.js";
|
|
19
29
|
import { resolvePiInvocation } from "./pi-invocation.js";
|
|
20
30
|
import { JsonLineDecoder } from "./protocol.js";
|
|
31
|
+
import {
|
|
32
|
+
type AnyStructuredSubagentResult,
|
|
33
|
+
parseAnyStructuredSubagentResult,
|
|
34
|
+
type SubagentResultFormat,
|
|
35
|
+
} from "./result-contract.js";
|
|
36
|
+
import {
|
|
37
|
+
formatResultFailure as formatBaseResultFailure,
|
|
38
|
+
getResultFinalOutput as getBaseResultFinalOutput,
|
|
39
|
+
getFinalOutput,
|
|
40
|
+
isResultError as isBaseResultError,
|
|
41
|
+
} from "./runner-result.js";
|
|
42
|
+
import {
|
|
43
|
+
addUsageValue,
|
|
44
|
+
mergeUsageStats,
|
|
45
|
+
protocolUsageCost,
|
|
46
|
+
protocolUsageCount,
|
|
47
|
+
type UsageStats,
|
|
48
|
+
} from "./runner-usage.js";
|
|
49
|
+
import {
|
|
50
|
+
formatTimeoutCheckpoint,
|
|
51
|
+
formatTurnTerminationMessage,
|
|
52
|
+
journalMessages,
|
|
53
|
+
TimeoutProgressJournal,
|
|
54
|
+
TURN_TERMINATION_VERSION,
|
|
55
|
+
type TurnTerminationReport,
|
|
56
|
+
} from "./timeout-checkpoint.js";
|
|
57
|
+
import {
|
|
58
|
+
buildTimeoutFinalizationPrompt,
|
|
59
|
+
resolveTimeoutFinalizationMs,
|
|
60
|
+
} from "./timeout-finalization.js";
|
|
61
|
+
import { TurnBudgetMonitor, type TurnBudgetStop, type TurnLimits } from "./turn-budget.js";
|
|
62
|
+
import type { WorkItemLedgerSnapshot } from "./work-item-ledger.js";
|
|
21
63
|
|
|
22
64
|
export const KILL_GRACE_MS = 5000;
|
|
23
65
|
|
|
24
|
-
export
|
|
25
|
-
input: number;
|
|
26
|
-
output: number;
|
|
27
|
-
cacheRead: number;
|
|
28
|
-
cacheWrite: number;
|
|
29
|
-
cost: number;
|
|
30
|
-
costInput?: number;
|
|
31
|
-
costOutput?: number;
|
|
32
|
-
costCacheRead?: number;
|
|
33
|
-
costCacheWrite?: number;
|
|
34
|
-
totalTokens?: number;
|
|
35
|
-
contextTokens: number;
|
|
36
|
-
turns: number;
|
|
37
|
-
}
|
|
66
|
+
export type { UsageStats } from "./runner-usage.js";
|
|
38
67
|
export type RecentActivityItem =
|
|
39
68
|
| { type: "text"; text: string }
|
|
40
69
|
| { type: "toolCall"; name: string; args: Record<string, unknown> };
|
|
@@ -42,21 +71,6 @@ export type RecentActivityItem =
|
|
|
42
71
|
const MAX_RECENT_ACTIVITY_ITEMS = 10;
|
|
43
72
|
const MAX_RECENT_ACTIVITY_BYTES = 8 * 1024;
|
|
44
73
|
const MAX_RECENT_ACTIVITY_ARGUMENT_BYTES = 1024;
|
|
45
|
-
const MAX_USAGE_VALUE = Number.MAX_SAFE_INTEGER;
|
|
46
|
-
|
|
47
|
-
function protocolUsageCount(value: unknown): number {
|
|
48
|
-
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : 0;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function protocolUsageCost(value: unknown): number {
|
|
52
|
-
return typeof value === "number" && Number.isFinite(value) && value >= 0
|
|
53
|
-
? Math.min(value, MAX_USAGE_VALUE)
|
|
54
|
-
: 0;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function addUsageValue(current: number, addition: number): number {
|
|
58
|
-
return Math.min(MAX_USAGE_VALUE, current + addition);
|
|
59
|
-
}
|
|
60
74
|
|
|
61
75
|
export interface SingleResult {
|
|
62
76
|
agent: string;
|
|
@@ -76,6 +90,10 @@ export interface SingleResult {
|
|
|
76
90
|
errorMessage?: string;
|
|
77
91
|
step?: number;
|
|
78
92
|
finalOutput?: string;
|
|
93
|
+
partialOutput?: string;
|
|
94
|
+
timeoutSummary?: string;
|
|
95
|
+
timeoutSummaryError?: string;
|
|
96
|
+
termination?: TurnTerminationReport;
|
|
79
97
|
timedOut?: boolean;
|
|
80
98
|
timeoutMs?: number;
|
|
81
99
|
aborted?: boolean;
|
|
@@ -89,51 +107,71 @@ export interface SingleResult {
|
|
|
89
107
|
overridden: string[];
|
|
90
108
|
unsupported: string[];
|
|
91
109
|
};
|
|
110
|
+
contract?: DelegationContract;
|
|
111
|
+
resultFormat?: SubagentResultFormat;
|
|
112
|
+
structuredResult?: AnyStructuredSubagentResult;
|
|
113
|
+
resultContractInvalid?: boolean;
|
|
114
|
+
outcome?: ClassifiedSubagentOutcome;
|
|
115
|
+
attemptCount?: number;
|
|
116
|
+
hedged?: boolean;
|
|
117
|
+
executionPlan?: ExecutionPlan;
|
|
118
|
+
capabilityGrant?: CapabilityGrant;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface PanelDetails {
|
|
122
|
+
id: string;
|
|
123
|
+
preset: PanelPreset;
|
|
124
|
+
sharedTaskPreview: string;
|
|
125
|
+
state: "running" | "completed" | "degraded" | "insufficient-panel" | "failed" | "cancelled";
|
|
126
|
+
reviewerIds: string[];
|
|
127
|
+
validReviewCount: number;
|
|
128
|
+
failedReviewCount: number;
|
|
129
|
+
blockingObjectionCount: number;
|
|
130
|
+
dissentCount: number;
|
|
131
|
+
budgets: PanelPhaseBudgets;
|
|
132
|
+
evidence: PanelEvidenceArtifact[];
|
|
133
|
+
failures: PanelFailure[];
|
|
134
|
+
synthesis?: PanelSynthesis;
|
|
135
|
+
synthesizerResult?: SingleResult;
|
|
136
|
+
cleanupComplete: boolean;
|
|
92
137
|
}
|
|
93
138
|
|
|
94
139
|
export interface SubagentDetails {
|
|
95
|
-
mode: "single" | "parallel" | "chain";
|
|
140
|
+
mode: "single" | "parallel" | "chain" | "workflow" | "panel";
|
|
96
141
|
agentScope: AgentScope;
|
|
97
142
|
projectAgentsDir: string | null;
|
|
98
143
|
results: SingleResult[];
|
|
99
144
|
aggregator?: SingleResult;
|
|
145
|
+
workflow?: WorkItemLedgerSnapshot;
|
|
146
|
+
schedulerDecisions?: SchedulingDecision[];
|
|
147
|
+
metrics?: OrchestrationMetrics;
|
|
148
|
+
panel?: PanelDetails;
|
|
100
149
|
isError?: boolean;
|
|
101
150
|
}
|
|
102
151
|
|
|
103
|
-
function getFinalOutput(messages: Message[]): string {
|
|
104
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
105
|
-
const msg = messages[i];
|
|
106
|
-
if (msg.role === "assistant") {
|
|
107
|
-
const text = msg.content
|
|
108
|
-
.filter((part) => part.type === "text")
|
|
109
|
-
.map((part) => part.text)
|
|
110
|
-
.join("\n");
|
|
111
|
-
if (text) return text;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return "";
|
|
115
|
-
}
|
|
116
|
-
|
|
117
152
|
export function getResultFinalOutput(result: SingleResult): string {
|
|
118
|
-
return
|
|
153
|
+
return getBaseResultFinalOutput(result);
|
|
119
154
|
}
|
|
120
155
|
|
|
121
156
|
export function isResultError(result: SingleResult): boolean {
|
|
122
157
|
return (
|
|
123
|
-
(result
|
|
124
|
-
result.
|
|
125
|
-
result.
|
|
126
|
-
|
|
127
|
-
|
|
158
|
+
isBaseResultError(result) ||
|
|
159
|
+
result.resultContractInvalid === true ||
|
|
160
|
+
(result.outcome !== undefined &&
|
|
161
|
+
result.outcome.status !== "completed" &&
|
|
162
|
+
result.outcome.status !== "partial")
|
|
128
163
|
);
|
|
129
164
|
}
|
|
130
165
|
|
|
131
166
|
export function formatResultFailure(result: SingleResult): string {
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
167
|
+
const contractError = result.resultContractInvalid
|
|
168
|
+
? `Subagent returned an invalid ${result.resultFormat ?? "structured"} result contract`
|
|
169
|
+
: result.outcome && !["completed", "partial"].includes(result.outcome.status)
|
|
170
|
+
? `Subagent outcome ${result.outcome.status}${result.outcome.reasonCode ? ` (${result.outcome.reasonCode})` : ""}; recovery: ${result.outcome.recoveryActions.join(", ") || "none"}`
|
|
171
|
+
: undefined;
|
|
172
|
+
return contractError
|
|
173
|
+
? formatBaseResultFailure({ ...result, errorMessage: contractError })
|
|
174
|
+
: formatBaseResultFailure(result);
|
|
137
175
|
}
|
|
138
176
|
|
|
139
177
|
function boundMessageText(
|
|
@@ -262,9 +300,11 @@ export function buildFanInContext(
|
|
|
262
300
|
const error = result.errorMessage || result.stderr.trim();
|
|
263
301
|
const resultText = failed
|
|
264
302
|
? `${error ? "Error" : output ? "Partial output" : "Error"}:\n${formatResultFailure(result)}`
|
|
265
|
-
:
|
|
266
|
-
? `
|
|
267
|
-
:
|
|
303
|
+
: result.structuredResult
|
|
304
|
+
? `Structured result:\n${JSON.stringify(result.structuredResult)}`
|
|
305
|
+
: output
|
|
306
|
+
? `Output:\n${output}`
|
|
307
|
+
: "Output: (no output)";
|
|
268
308
|
return [
|
|
269
309
|
`## Result ${index + 1}: ${result.agent} (${status})`,
|
|
270
310
|
`Task: ${result.task}`,
|
|
@@ -405,6 +445,30 @@ export interface ChildLaunchPolicy {
|
|
|
405
445
|
projectTrust?: boolean;
|
|
406
446
|
baseSystemPrompt?: string;
|
|
407
447
|
appendSystemPromptPaths?: string[];
|
|
448
|
+
/** Internal timeout recovery control; omitted means enabled. */
|
|
449
|
+
finalizeOnTimeout?: boolean;
|
|
450
|
+
/** Internal hard deadline for the summary attempt. */
|
|
451
|
+
timeoutFinalizationMs?: number;
|
|
452
|
+
/** Optional stateful result contract retained during timeout finalization. */
|
|
453
|
+
timeoutResultFormat?: SubagentResultFormat;
|
|
454
|
+
/** Optional non-wall-clock limits for this turn. */
|
|
455
|
+
turnLimits?: TurnLimits;
|
|
456
|
+
/** Override the timeout reason when an orchestration deadline caps this child. */
|
|
457
|
+
workTimeoutReason?: "work_timeout" | "orchestration_timeout";
|
|
458
|
+
/** Public limit value reported when the effective child timeout is only the remaining budget. */
|
|
459
|
+
workTimeoutReportLimit?: number;
|
|
460
|
+
/** Absolute blocking-workflow deadline that also caps model finalization. */
|
|
461
|
+
orchestrationDeadlineAt?: number;
|
|
462
|
+
/** Completion contract requested for this turn. */
|
|
463
|
+
resultFormat?: SubagentResultFormat;
|
|
464
|
+
/** Normalized request contract retained in result details. */
|
|
465
|
+
contract?: DelegationContract;
|
|
466
|
+
/** Original task summary shown in result details when the executed prompt has contract metadata. */
|
|
467
|
+
displayTask?: string;
|
|
468
|
+
/** Immutable audit or enforcement decision made before launch. */
|
|
469
|
+
executionPlan?: ExecutionPlan;
|
|
470
|
+
/** Executor-owned authority lifetime bound to the accepted plan generation. */
|
|
471
|
+
capabilityGrant?: CapabilityGrant;
|
|
408
472
|
}
|
|
409
473
|
|
|
410
474
|
export async function runSingleAgent(
|
|
@@ -455,10 +519,11 @@ export async function runSingleAgent(
|
|
|
455
519
|
let latestAssistantOutput = "";
|
|
456
520
|
let terminalAssistantOutput: string | undefined;
|
|
457
521
|
|
|
522
|
+
const progressJournal = new TimeoutProgressJournal();
|
|
458
523
|
const currentResult: SingleResult = {
|
|
459
524
|
agent: agentName,
|
|
460
525
|
agentSource: agent.source,
|
|
461
|
-
task,
|
|
526
|
+
task: launchPolicy?.displayTask ?? task,
|
|
462
527
|
exitCode: 0,
|
|
463
528
|
messages: [],
|
|
464
529
|
stderr: "",
|
|
@@ -475,6 +540,10 @@ export async function runSingleAgent(
|
|
|
475
540
|
thinkingLevel,
|
|
476
541
|
step,
|
|
477
542
|
timeoutMs,
|
|
543
|
+
contract: launchPolicy?.contract,
|
|
544
|
+
resultFormat: launchPolicy?.resultFormat,
|
|
545
|
+
executionPlan: launchPolicy?.executionPlan,
|
|
546
|
+
capabilityGrant: launchPolicy?.capabilityGrant,
|
|
478
547
|
};
|
|
479
548
|
const selectedAssistantOutput = () =>
|
|
480
549
|
terminalAssistantOutput !== undefined
|
|
@@ -579,17 +648,25 @@ export async function runSingleAgent(
|
|
|
579
648
|
}
|
|
580
649
|
let wasAborted = false;
|
|
581
650
|
let timedOut = false;
|
|
651
|
+
let budgetStop:
|
|
652
|
+
| TurnBudgetStop
|
|
653
|
+
| { reason: "work_timeout" | "orchestration_timeout"; limit: number }
|
|
654
|
+
| undefined;
|
|
582
655
|
|
|
583
656
|
const exitCode = await new Promise<number>((resolve) => {
|
|
584
657
|
let settled = false;
|
|
585
658
|
let cleanupTermination: (() => void) | undefined;
|
|
586
659
|
let timeout: NodeJS.Timeout | undefined;
|
|
660
|
+
let terminationDeadline: NodeJS.Timeout | undefined;
|
|
587
661
|
let abortHandler: (() => void) | undefined;
|
|
662
|
+
let budgetMonitor: TurnBudgetMonitor | undefined;
|
|
588
663
|
const finish = (code: number) => {
|
|
589
664
|
if (settled) return;
|
|
590
665
|
settled = true;
|
|
591
666
|
if (timeout) clearTimeout(timeout);
|
|
667
|
+
if (terminationDeadline) clearTimeout(terminationDeadline);
|
|
592
668
|
cleanupTermination?.();
|
|
669
|
+
budgetMonitor?.dispose();
|
|
593
670
|
if (signal && abortHandler) signal.removeEventListener("abort", abortHandler);
|
|
594
671
|
resolve(code);
|
|
595
672
|
};
|
|
@@ -647,6 +724,7 @@ export async function runSingleAgent(
|
|
|
647
724
|
const event = raw as { type?: string; message?: Message };
|
|
648
725
|
if (event.type === "message_end" && event.message) {
|
|
649
726
|
const msg = event.message;
|
|
727
|
+
journalMessages(progressJournal, [msg]);
|
|
650
728
|
if (msg.role === "assistant") {
|
|
651
729
|
const output = truncateUtf8(getFinalOutput([msg]), DEFAULT_MAX_OUTPUT_BYTES);
|
|
652
730
|
currentResult.truncated ||= output.truncated;
|
|
@@ -659,6 +737,10 @@ export async function runSingleAgent(
|
|
|
659
737
|
addMessage(msg);
|
|
660
738
|
if (msg.role === "assistant") {
|
|
661
739
|
currentResult.usage.turns++;
|
|
740
|
+
budgetMonitor?.recordToolCalls(
|
|
741
|
+
msg.content.filter((part) => part.type === "toolCall").length,
|
|
742
|
+
);
|
|
743
|
+
budgetMonitor?.recordAssistantTurn(msg.stopReason);
|
|
662
744
|
const usage = msg.usage;
|
|
663
745
|
if (usage && typeof usage === "object") {
|
|
664
746
|
const input = protocolUsageCount(usage.input);
|
|
@@ -719,6 +801,8 @@ export async function runSingleAgent(
|
|
|
719
801
|
}
|
|
720
802
|
emitUpdate();
|
|
721
803
|
} else if (event.type === "tool_result_end" && event.message) {
|
|
804
|
+
journalMessages(progressJournal, [event.message]);
|
|
805
|
+
budgetMonitor?.recordActivity();
|
|
722
806
|
addMessage(event.message);
|
|
723
807
|
emitUpdate();
|
|
724
808
|
}
|
|
@@ -732,21 +816,47 @@ export async function runSingleAgent(
|
|
|
732
816
|
currentResult.truncated = true;
|
|
733
817
|
},
|
|
734
818
|
});
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
819
|
+
const beginTermination = (exitCode: number) => {
|
|
820
|
+
if (cleanupTermination || settled) return;
|
|
821
|
+
cleanupTermination = terminateProcess(proc);
|
|
822
|
+
terminationDeadline = setTimeout(() => {
|
|
823
|
+
decoder.finish();
|
|
824
|
+
proc.stdin?.destroy();
|
|
825
|
+
proc.stdout?.destroy();
|
|
826
|
+
proc.stderr?.destroy();
|
|
827
|
+
finish(exitCode);
|
|
828
|
+
}, KILL_GRACE_MS + 1_000);
|
|
829
|
+
};
|
|
830
|
+
const stopForBudget = (
|
|
831
|
+
stop: TurnBudgetStop | { reason: "work_timeout" | "orchestration_timeout"; limit: number },
|
|
832
|
+
) => {
|
|
833
|
+
if (budgetStop || settled || wasAborted) return;
|
|
834
|
+
budgetStop = stop;
|
|
835
|
+
timedOut = stop.reason.endsWith("timeout");
|
|
836
|
+
currentResult.timedOut = timedOut || undefined;
|
|
837
|
+
currentResult.stopReason = timedOut ? "timeout" : "limit";
|
|
838
|
+
const message = formatTurnTerminationMessage(stop.reason, stop.limit);
|
|
839
|
+
setErrorMessage(message);
|
|
741
840
|
const bounded = appendBounded(
|
|
742
841
|
currentResult.stderr,
|
|
743
|
-
`\
|
|
842
|
+
`\n${message}.`,
|
|
744
843
|
DEFAULT_MAX_STDERR_BYTES,
|
|
745
844
|
);
|
|
746
845
|
currentResult.stderr = bounded.text;
|
|
747
846
|
currentResult.truncated ||= bounded.truncated;
|
|
748
847
|
emitUpdate();
|
|
749
|
-
|
|
848
|
+
beginTermination(124);
|
|
849
|
+
};
|
|
850
|
+
budgetMonitor = new TurnBudgetMonitor({
|
|
851
|
+
...launchPolicy?.turnLimits,
|
|
852
|
+
onExceeded: stopForBudget,
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
timeout = setTimeout(() => {
|
|
856
|
+
stopForBudget({
|
|
857
|
+
reason: launchPolicy?.workTimeoutReason ?? "work_timeout",
|
|
858
|
+
limit: launchPolicy?.workTimeoutReportLimit ?? timeoutMs,
|
|
859
|
+
});
|
|
750
860
|
}, timeoutMs);
|
|
751
861
|
timeout.unref();
|
|
752
862
|
|
|
@@ -765,10 +875,10 @@ export async function runSingleAgent(
|
|
|
765
875
|
});
|
|
766
876
|
proc.on("close", (code) => {
|
|
767
877
|
decoder.finish();
|
|
768
|
-
finish(
|
|
878
|
+
finish(budgetStop ? 124 : wasAborted ? 130 : (code ?? 0));
|
|
769
879
|
});
|
|
770
880
|
proc.on("error", (error) => {
|
|
771
|
-
currentResult.launchFailed = true;
|
|
881
|
+
currentResult.launchFailed = currentResult.processStarted ? undefined : true;
|
|
772
882
|
const message = setErrorMessage(error.message);
|
|
773
883
|
const bounded = appendBounded(
|
|
774
884
|
currentResult.stderr,
|
|
@@ -777,27 +887,162 @@ export async function runSingleAgent(
|
|
|
777
887
|
);
|
|
778
888
|
currentResult.stderr = bounded.text;
|
|
779
889
|
currentResult.truncated ||= bounded.truncated;
|
|
780
|
-
|
|
890
|
+
if (currentResult.processStarted) beginTermination(1);
|
|
891
|
+
else finish(1);
|
|
781
892
|
});
|
|
782
893
|
|
|
783
894
|
if (signal) {
|
|
784
895
|
abortHandler = () => {
|
|
785
|
-
if (
|
|
896
|
+
if (budgetStop || settled) return;
|
|
786
897
|
wasAborted = true;
|
|
787
898
|
currentResult.aborted = true;
|
|
788
899
|
currentResult.stopReason = "aborted";
|
|
789
900
|
setErrorMessage("Subagent was aborted");
|
|
790
|
-
|
|
901
|
+
beginTermination(130);
|
|
791
902
|
};
|
|
792
903
|
if (signal.aborted) abortHandler();
|
|
793
904
|
else signal.addEventListener("abort", abortHandler, { once: true });
|
|
794
905
|
}
|
|
795
906
|
});
|
|
796
907
|
|
|
797
|
-
|
|
908
|
+
if (signal?.aborted && budgetStop) {
|
|
909
|
+
budgetStop = undefined;
|
|
910
|
+
timedOut = false;
|
|
911
|
+
currentResult.timedOut = undefined;
|
|
912
|
+
currentResult.aborted = true;
|
|
913
|
+
currentResult.stopReason = "aborted";
|
|
914
|
+
setErrorMessage("Subagent was aborted");
|
|
915
|
+
}
|
|
916
|
+
currentResult.exitCode = currentResult.aborted ? 130 : exitCode;
|
|
798
917
|
const final = truncateUtf8(selectedAssistantOutput(), DEFAULT_MAX_OUTPUT_BYTES);
|
|
799
918
|
currentResult.finalOutput = final.text;
|
|
800
919
|
currentResult.truncated ||= final.truncated;
|
|
920
|
+
if (budgetStop) {
|
|
921
|
+
currentResult.partialOutput = currentResult.finalOutput || undefined;
|
|
922
|
+
currentResult.termination = {
|
|
923
|
+
version: TURN_TERMINATION_VERSION,
|
|
924
|
+
reason: budgetStop.reason,
|
|
925
|
+
limit: budgetStop.limit,
|
|
926
|
+
checkpoint: progressJournal.checkpoint(task, currentResult.partialOutput),
|
|
927
|
+
finalization: { attempted: false, status: "skipped", durationMs: 0 },
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
const remainingFinalizationMs = launchPolicy?.orchestrationDeadlineAt
|
|
931
|
+
? Math.floor(launchPolicy.orchestrationDeadlineAt - Date.now())
|
|
932
|
+
: undefined;
|
|
933
|
+
if (
|
|
934
|
+
budgetStop &&
|
|
935
|
+
budgetStop.reason !== "orchestration_timeout" &&
|
|
936
|
+
launchPolicy?.finalizeOnTimeout !== false &&
|
|
937
|
+
!signal?.aborted &&
|
|
938
|
+
(remainingFinalizationMs === undefined || remainingFinalizationMs > 0)
|
|
939
|
+
) {
|
|
940
|
+
const finalizationStartedAt = Date.now();
|
|
941
|
+
const requestedFinalizationMs = resolveTimeoutFinalizationMs(
|
|
942
|
+
timeoutMs,
|
|
943
|
+
launchPolicy?.timeoutFinalizationMs,
|
|
944
|
+
);
|
|
945
|
+
const finalizationMs = Math.min(
|
|
946
|
+
requestedFinalizationMs,
|
|
947
|
+
remainingFinalizationMs ?? requestedFinalizationMs,
|
|
948
|
+
);
|
|
949
|
+
const summary = await runSingleAgent(
|
|
950
|
+
defaultCwd,
|
|
951
|
+
agents,
|
|
952
|
+
agentName,
|
|
953
|
+
buildTimeoutFinalizationPrompt({
|
|
954
|
+
task,
|
|
955
|
+
partialOutput: currentResult.partialOutput,
|
|
956
|
+
recentActivity: currentResult.recentActivity,
|
|
957
|
+
checkpoint: currentResult.termination?.checkpoint,
|
|
958
|
+
terminationReason: budgetStop.reason,
|
|
959
|
+
resultFormat: launchPolicy?.timeoutResultFormat,
|
|
960
|
+
}),
|
|
961
|
+
cwd,
|
|
962
|
+
step,
|
|
963
|
+
signal,
|
|
964
|
+
thinkingLevel,
|
|
965
|
+
finalizationMs,
|
|
966
|
+
undefined,
|
|
967
|
+
makeDetails,
|
|
968
|
+
invocationOverride,
|
|
969
|
+
{
|
|
970
|
+
...launchPolicy,
|
|
971
|
+
tools: [],
|
|
972
|
+
disableExtensions: true,
|
|
973
|
+
disableSkills: true,
|
|
974
|
+
disablePromptTemplates: true,
|
|
975
|
+
disableContextFiles: true,
|
|
976
|
+
appendSystemPromptPaths: undefined,
|
|
977
|
+
finalizeOnTimeout: false,
|
|
978
|
+
turnLimits: undefined,
|
|
979
|
+
workTimeoutReason: "work_timeout",
|
|
980
|
+
workTimeoutReportLimit: finalizationMs,
|
|
981
|
+
orchestrationDeadlineAt: undefined,
|
|
982
|
+
},
|
|
983
|
+
);
|
|
984
|
+
mergeUsageStats(currentResult.usage, summary.usage);
|
|
985
|
+
const summaryOutput = getResultFinalOutput(summary).trim();
|
|
986
|
+
if (summary.exitCode === 0 && summaryOutput) {
|
|
987
|
+
currentResult.timeoutSummary = summaryOutput;
|
|
988
|
+
currentResult.finalOutput = summaryOutput;
|
|
989
|
+
if (currentResult.termination) {
|
|
990
|
+
currentResult.termination.finalization = {
|
|
991
|
+
attempted: true,
|
|
992
|
+
status: "completed",
|
|
993
|
+
durationMs: Date.now() - finalizationStartedAt,
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
} else {
|
|
997
|
+
currentResult.timeoutSummaryError =
|
|
998
|
+
summary.errorMessage || summary.stderr.trim() || "Summary produced no final text";
|
|
999
|
+
if (currentResult.termination) {
|
|
1000
|
+
currentResult.termination.finalization = {
|
|
1001
|
+
attempted: true,
|
|
1002
|
+
status: summary.timedOut ? "timed_out" : "failed",
|
|
1003
|
+
durationMs: Date.now() - finalizationStartedAt,
|
|
1004
|
+
error: currentResult.timeoutSummaryError,
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
currentResult.truncated ||= summary.truncated;
|
|
1009
|
+
}
|
|
1010
|
+
if (currentResult.termination && !currentResult.finalOutput.trim()) {
|
|
1011
|
+
currentResult.finalOutput = formatTimeoutCheckpoint(currentResult.termination.checkpoint);
|
|
1012
|
+
}
|
|
1013
|
+
if (currentResult.exitCode === 0 && launchPolicy?.resultFormat !== undefined) {
|
|
1014
|
+
currentResult.structuredResult = parseAnyStructuredSubagentResult(
|
|
1015
|
+
currentResult.finalOutput ?? "",
|
|
1016
|
+
launchPolicy.resultFormat,
|
|
1017
|
+
);
|
|
1018
|
+
currentResult.resultContractInvalid =
|
|
1019
|
+
launchPolicy.resultFormat !== "text" && currentResult.structuredResult === undefined;
|
|
1020
|
+
if (
|
|
1021
|
+
currentResult.structuredResult?.version === "pi-subagents:result:v2" &&
|
|
1022
|
+
launchPolicy.executionPlan
|
|
1023
|
+
) {
|
|
1024
|
+
currentResult.structuredResult.provenance = {
|
|
1025
|
+
...currentResult.structuredResult.provenance,
|
|
1026
|
+
...(launchPolicy.executionPlan.taskId
|
|
1027
|
+
? { taskId: launchPolicy.executionPlan.taskId }
|
|
1028
|
+
: {}),
|
|
1029
|
+
taskGeneration: launchPolicy.executionPlan.taskGeneration,
|
|
1030
|
+
executionPlanId: launchPolicy.executionPlan.id,
|
|
1031
|
+
cancellationLineage: [...launchPolicy.executionPlan.cancellationLineage],
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
if (currentResult.structuredResult?.version === "pi-subagents:result:v2") {
|
|
1035
|
+
currentResult.outcome = classifyStructuredOutcome(
|
|
1036
|
+
currentResult.structuredResult.status,
|
|
1037
|
+
currentResult.structuredResult.reasonCode,
|
|
1038
|
+
);
|
|
1039
|
+
} else if (currentResult.resultContractInvalid) {
|
|
1040
|
+
currentResult.outcome = classifyStructuredOutcome(
|
|
1041
|
+
"contract-invalid",
|
|
1042
|
+
"malformed-structured-result",
|
|
1043
|
+
);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
801
1046
|
if (
|
|
802
1047
|
currentResult.exitCode === 0 &&
|
|
803
1048
|
currentResult.stopReason !== "error" &&
|
|
@@ -807,6 +1052,13 @@ export async function runSingleAgent(
|
|
|
807
1052
|
currentResult.stopReason = "error";
|
|
808
1053
|
setErrorMessage("Subagent completed without final text");
|
|
809
1054
|
}
|
|
1055
|
+
if (currentResult.capabilityGrant?.state === "active") {
|
|
1056
|
+
currentResult.capabilityGrant = revokeCapabilityGrant(
|
|
1057
|
+
currentResult.capabilityGrant,
|
|
1058
|
+
"turn-settled",
|
|
1059
|
+
Date.now(),
|
|
1060
|
+
);
|
|
1061
|
+
}
|
|
810
1062
|
currentResult.policy = {
|
|
811
1063
|
inherited: ["environment"],
|
|
812
1064
|
overridden: [
|