pi-subagents 0.34.0 → 0.35.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 +78 -9
- package/README.md +213 -32
- package/index.ts +1 -0
- package/install.mjs +1 -1
- package/package.json +23 -8
- package/prompts/review-loop.md +3 -1
- package/skills/pi-subagents/SKILL.md +87 -25
- package/src/agents/agent-management.ts +82 -15
- package/src/agents/agent-serializer.ts +19 -0
- package/src/agents/agents.ts +91 -49
- package/src/agents/frontmatter.ts +67 -13
- package/src/agents/skills.ts +25 -12
- package/src/api/background-work.ts +197 -0
- package/src/api/delegation.ts +158 -0
- package/src/extension/chain-validation.ts +165 -0
- package/src/extension/doctor.ts +15 -0
- package/src/extension/fanout-child.ts +3 -1
- package/src/extension/index.ts +65 -124
- package/src/extension/rpc.ts +10 -2
- package/src/extension/schemas.ts +18 -14
- package/src/extension/steering-notices.ts +35 -0
- package/src/extension/tool-description.ts +20 -9
- package/src/intercom/intercom-bridge.ts +3 -2
- package/src/intercom/native-supervisor-channel.ts +9 -1
- package/src/intercom/result-intercom.ts +4 -0
- package/src/runs/background/async-execution.ts +293 -44
- package/src/runs/background/async-job-tracker.ts +56 -9
- package/src/runs/background/async-resume.ts +159 -52
- package/src/runs/background/async-status.ts +25 -18
- package/src/runs/background/auto-drain.ts +67 -0
- package/src/runs/background/chain-root-attachment.ts +16 -8
- package/src/runs/background/control-channel.ts +260 -13
- package/src/runs/background/fleet-view.ts +23 -2
- package/src/runs/background/notify.ts +79 -10
- package/src/runs/background/result-watcher.ts +12 -9
- package/src/runs/background/run-id-resolver.ts +14 -2
- package/src/runs/background/run-status.ts +23 -15
- package/src/runs/background/scheduled-runs.ts +3 -0
- package/src/runs/background/stale-run-reconciler.ts +32 -10
- package/src/runs/background/steering.ts +237 -0
- package/src/runs/background/subagent-runner.ts +898 -236
- package/src/runs/background/subagent-wait.ts +484 -0
- package/src/runs/background/top-level-async.ts +2 -1
- package/src/runs/background/wait-config.ts +36 -0
- package/src/runs/background/wait-tool.ts +26 -0
- package/src/runs/foreground/async-steering-action.ts +230 -0
- package/src/runs/foreground/chain-clarify.ts +22 -6
- package/src/runs/foreground/chain-execution.ts +50 -32
- package/src/runs/foreground/execution.ts +308 -94
- package/src/runs/foreground/subagent-executor.ts +592 -268
- package/src/runs/shared/acceptance.ts +355 -97
- package/src/runs/shared/child-protocol.ts +121 -0
- package/src/runs/shared/completion-guard.ts +8 -127
- package/src/runs/shared/dynamic-fanout.ts +6 -4
- package/src/runs/shared/model-fallback.ts +36 -0
- package/src/runs/shared/nested-events.ts +9 -4
- package/src/runs/shared/nested-render.ts +4 -1
- package/src/runs/shared/parallel-utils.ts +7 -0
- package/src/runs/shared/pi-args.ts +34 -7
- package/src/runs/shared/pi-spawn.ts +18 -12
- package/src/runs/shared/session-lease.ts +279 -0
- package/src/runs/shared/single-output.ts +61 -6
- package/src/runs/shared/spawn-budget.ts +128 -0
- package/src/runs/shared/subagent-control.ts +10 -6
- package/src/runs/shared/subagent-prompt-runtime.ts +127 -26
- package/src/runs/shared/task-intent.ts +176 -0
- package/src/runs/shared/tool-availability.ts +65 -0
- package/src/runs/shared/turn-budget.ts +49 -4
- package/src/shared/atomic-json.ts +4 -1
- package/src/shared/fork-context.ts +28 -3
- package/src/shared/model-info.ts +7 -4
- package/src/shared/status-format.ts +7 -1
- package/src/shared/types.ts +203 -25
- package/src/shared/utils.ts +35 -7
- package/src/slash/delegation-adapters.ts +457 -0
- package/src/slash/delegation-request.ts +103 -0
- package/src/slash/prompt-template-bridge.ts +167 -344
- package/src/slash/slash-commands.ts +239 -6
- package/src/slash/subagents-admin.ts +428 -0
- package/src/slash/subagents-editor.ts +86 -0
- package/src/tui/fleet.ts +405 -0
- package/src/tui/render.ts +90 -16
- package/src/watchdog/change-signature.ts +127 -0
- package/src/watchdog/child-status.ts +205 -0
- package/src/watchdog/emission-guard.ts +123 -0
- package/src/watchdog/lsp-diagnostics.ts +532 -0
- package/src/watchdog/model-selection.ts +167 -0
- package/src/watchdog/register-child.ts +117 -0
- package/src/watchdog/register-main.ts +433 -0
- package/src/watchdog/render.ts +54 -0
- package/src/watchdog/review.ts +293 -0
- package/src/watchdog/runtime.ts +712 -0
- package/src/watchdog/settings.ts +528 -0
- package/src/watchdog/tool-actions.ts +155 -0
- package/src/watchdog/turn-delta.ts +161 -0
- package/src/watchdog/types.ts +188 -0
- package/src/watchdog/warning-format.ts +73 -0
- package/src/runs/background/wait.ts +0 -394
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import type { ProtocolOutputLimit } from "../../shared/types.ts";
|
|
3
|
+
|
|
4
|
+
export type { ProtocolOutputLimit } from "../../shared/types.ts";
|
|
5
|
+
|
|
6
|
+
export const MAX_CHILD_PENDING_LINE_BYTES = 4 * 1024 * 1024;
|
|
7
|
+
export const MAX_CHILD_STDERR_BYTES = 128 * 1024;
|
|
8
|
+
const MAX_PROTOCOL_DIAGNOSTIC_BYTES = 4096;
|
|
9
|
+
|
|
10
|
+
export function formatProtocolOutputLimit(limit: ProtocolOutputLimit): string {
|
|
11
|
+
return `${limit.code}: child ${limit.stream} line exceeded ${limit.limitBytes} bytes (observed at least ${limit.observedBytes} bytes without a newline).`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function createBoundedLineReader(options: {
|
|
15
|
+
stream?: "stdout" | "stderr";
|
|
16
|
+
maxPendingLineBytes?: number;
|
|
17
|
+
onLine: (line: string) => void;
|
|
18
|
+
onLimit: (limit: ProtocolOutputLimit) => void;
|
|
19
|
+
}): {
|
|
20
|
+
push(chunk: Buffer | string): void;
|
|
21
|
+
end(): void;
|
|
22
|
+
exceeded(): boolean;
|
|
23
|
+
} {
|
|
24
|
+
const maxPendingLineBytes = options.maxPendingLineBytes ?? MAX_CHILD_PENDING_LINE_BYTES;
|
|
25
|
+
if (!Number.isInteger(maxPendingLineBytes) || maxPendingLineBytes < 1) {
|
|
26
|
+
throw new Error("maxPendingLineBytes must be a positive integer.");
|
|
27
|
+
}
|
|
28
|
+
let pending: Buffer[] = [];
|
|
29
|
+
let pendingBytes = 0;
|
|
30
|
+
let limitExceeded = false;
|
|
31
|
+
|
|
32
|
+
const emitPending = (): void => {
|
|
33
|
+
if (pendingBytes === 0) return;
|
|
34
|
+
options.onLine(Buffer.concat(pending, pendingBytes).toString("utf8"));
|
|
35
|
+
pending = [];
|
|
36
|
+
pendingBytes = 0;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const append = (segment: Buffer): boolean => {
|
|
40
|
+
if (segment.length === 0) return true;
|
|
41
|
+
const observedBytes = pendingBytes + segment.length;
|
|
42
|
+
if (observedBytes > maxPendingLineBytes) {
|
|
43
|
+
const prior = pendingBytes > 0 ? Buffer.concat(pending, pendingBytes) : Buffer.alloc(0);
|
|
44
|
+
const prefixFromPrior = prior.subarray(0, MAX_PROTOCOL_DIAGNOSTIC_BYTES);
|
|
45
|
+
const prefix = prefixFromPrior.length === MAX_PROTOCOL_DIAGNOSTIC_BYTES
|
|
46
|
+
? prefixFromPrior
|
|
47
|
+
: Buffer.concat([prefixFromPrior, segment.subarray(0, MAX_PROTOCOL_DIAGNOSTIC_BYTES - prefixFromPrior.length)]);
|
|
48
|
+
const tailFromSegment = segment.subarray(Math.max(0, segment.length - MAX_PROTOCOL_DIAGNOSTIC_BYTES));
|
|
49
|
+
const tail = tailFromSegment.length === MAX_PROTOCOL_DIAGNOSTIC_BYTES
|
|
50
|
+
? tailFromSegment
|
|
51
|
+
: Buffer.concat([prior.subarray(Math.max(0, prior.length - (MAX_PROTOCOL_DIAGNOSTIC_BYTES - tailFromSegment.length))), tailFromSegment]);
|
|
52
|
+
limitExceeded = true;
|
|
53
|
+
pending = [];
|
|
54
|
+
pendingBytes = 0;
|
|
55
|
+
options.onLimit({
|
|
56
|
+
code: "protocol_output_limit",
|
|
57
|
+
stream: options.stream ?? "stdout",
|
|
58
|
+
limitBytes: maxPendingLineBytes,
|
|
59
|
+
observedBytes,
|
|
60
|
+
diagnosticPrefix: prefix.toString("utf8"),
|
|
61
|
+
diagnosticTail: tail.toString("utf8"),
|
|
62
|
+
});
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
pending.push(segment);
|
|
66
|
+
pendingBytes = observedBytes;
|
|
67
|
+
return true;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
push(chunk) {
|
|
72
|
+
if (limitExceeded) return;
|
|
73
|
+
const bytes = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
74
|
+
let start = 0;
|
|
75
|
+
for (let index = 0; index < bytes.length; index++) {
|
|
76
|
+
if (bytes[index] !== 0x0a) continue;
|
|
77
|
+
if (!append(bytes.subarray(start, index))) return;
|
|
78
|
+
emitPending();
|
|
79
|
+
start = index + 1;
|
|
80
|
+
}
|
|
81
|
+
append(bytes.subarray(start));
|
|
82
|
+
},
|
|
83
|
+
end() {
|
|
84
|
+
if (!limitExceeded) emitPending();
|
|
85
|
+
},
|
|
86
|
+
exceeded: () => limitExceeded,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function trimToUtf8Boundary(buffer: Buffer, maxBytes: number): Buffer {
|
|
91
|
+
if (buffer.length <= maxBytes) return buffer;
|
|
92
|
+
let start = buffer.length - maxBytes;
|
|
93
|
+
while (start < buffer.length && (buffer[start]! & 0xc0) === 0x80) start++;
|
|
94
|
+
return buffer.subarray(start);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function createBoundedByteTail(maxBytes = MAX_CHILD_STDERR_BYTES): {
|
|
98
|
+
push(chunk: Buffer | string): void;
|
|
99
|
+
text(): string;
|
|
100
|
+
byteLength(): number;
|
|
101
|
+
} {
|
|
102
|
+
if (!Number.isInteger(maxBytes) || maxBytes < 1) throw new Error("maxBytes must be a positive integer.");
|
|
103
|
+
let tail = Buffer.alloc(0);
|
|
104
|
+
return {
|
|
105
|
+
push(chunk) {
|
|
106
|
+
const bytes = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
107
|
+
tail = trimToUtf8Boundary(Buffer.concat([tail, bytes]), maxBytes);
|
|
108
|
+
},
|
|
109
|
+
text: () => tail.toString("utf8"),
|
|
110
|
+
byteLength: () => tail.length,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export type ChildLifecycleAction = "start-drain" | "cancel-drain" | "none";
|
|
115
|
+
|
|
116
|
+
export function projectChildLifecycle(event: { type?: string; willRetry?: unknown }, terminalAssistantStop = false): ChildLifecycleAction {
|
|
117
|
+
if (event.type === "agent_end" && event.willRetry === true) return "cancel-drain";
|
|
118
|
+
if (event.type === "agent_settled") return "start-drain";
|
|
119
|
+
if (terminalAssistantStop) return "start-drain";
|
|
120
|
+
return "none";
|
|
121
|
+
}
|
|
@@ -1,76 +1,8 @@
|
|
|
1
1
|
import type { Message } from "@earendil-works/pi-ai";
|
|
2
2
|
import { isMutatingBashCommand } from "./long-running-guard.ts";
|
|
3
|
+
import { expectsImplementationMutation } from "./task-intent.ts";
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
/\breview only\b/i,
|
|
6
|
-
/\bsuggest fixes only\b/i,
|
|
7
|
-
/\bonly return findings\b/i,
|
|
8
|
-
/\breturn findings only\b/i,
|
|
9
|
-
];
|
|
10
|
-
|
|
11
|
-
const REVIEWER_REQUIRED_EDIT_PATTERNS = [
|
|
12
|
-
/\bmust\s+(?:edit|modify|change|fix|patch|apply)\b/i,
|
|
13
|
-
/\brequired\s+to\s+(?:edit|modify|change|fix|patch|apply)\b/i,
|
|
14
|
-
/\bregardless\s+of\s+findings\b/i,
|
|
15
|
-
/\balways\s+(?:edit|modify|change|fix|patch|apply)\b/i,
|
|
16
|
-
/\bapply\s+(?:the\s+)?fix(?:es)?\s+directly\b/i,
|
|
17
|
-
/\bmake\s+(?:the\s+)?code\s+changes\b/i,
|
|
18
|
-
];
|
|
19
|
-
|
|
20
|
-
const EXPLICIT_NO_EDIT_PATTERNS = [
|
|
21
|
-
/\bdo not edit\b/i,
|
|
22
|
-
/\bdon't edit\b/i,
|
|
23
|
-
/\bdo not modify\b/i,
|
|
24
|
-
/\bdo not change files\b/i,
|
|
25
|
-
];
|
|
26
|
-
|
|
27
|
-
const SCOPED_NO_EDIT_CONSTRAINT_PATTERNS = [
|
|
28
|
-
/\bdo not edit files?\s+outside\b/i,
|
|
29
|
-
/\bdo not edit\s+outside\b/i,
|
|
30
|
-
/\bdo not edit\s+unrelated files?\b/i,
|
|
31
|
-
/\bdo not change\s+unrelated files?\b/i,
|
|
32
|
-
/\bdo not modify\s+unrelated files?\b/i,
|
|
33
|
-
];
|
|
34
|
-
|
|
35
|
-
const NO_TOOL_INTENT_PATTERNS = [
|
|
36
|
-
/\bno tools? needed\b/i,
|
|
37
|
-
/\bno tools? required\b/i,
|
|
38
|
-
/\bwithout using tools\b/i,
|
|
39
|
-
/\bdo not use tools\b/i,
|
|
40
|
-
/\bdon't use tools\b/i,
|
|
41
|
-
];
|
|
42
|
-
|
|
43
|
-
const READ_ONLY_DELIVERABLE_PATTERNS = [
|
|
44
|
-
/\b(?:draft|write|compose|prepare|produce)\s+(?:(?:a|an|the)\s+)?(?:github\s+)?(?:issue|bug report|issue draft|issue body|proposal|plan|report|summary|findings?|analysis|recommendations?)\b/i,
|
|
45
|
-
/\b(?:issue|bug report)\s+(?:draft|body|template)\b/i,
|
|
46
|
-
/\b(?:return|provide|produce)\s+(?:text|markdown|answer|findings?|recommendations?)\s+only\b/i,
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
const RESEARCH_AGENT_PATTERNS = [
|
|
50
|
-
/\binvestigate\b/i,
|
|
51
|
-
/\bscout\b/i,
|
|
52
|
-
/\bresearch(?:er)?\b/i,
|
|
53
|
-
];
|
|
54
|
-
|
|
55
|
-
const FIX_OR_PATCH_IMPLEMENTATION_PATTERN = /\b(?:fix|patch)\s+(?:(?:it|this|that|them|each|any|all|these|those)\b|(?:(?:a|an|the|any|all)\s+)?(?:(?:failing|failed|broken|flaky|red|cold|start|current|existing|reported|approved|known|regression|unit|integration|e2e|source|typescript|type-?script|ts|type-?check|compiler)\s+)*(?:bug|defect|issues?|problems?|failures?|regressions?|tests?|errors?|items?|typos?|code|source|implementation|component|function|module|class|method|logic|file|files|readme|docs?|changelog|package\.json|config|manifest|extension|prompt|command|lint(?:ing)?|build|ci|type-?check|type\s+checking)\b)/i;
|
|
56
|
-
|
|
57
|
-
const WORKER_IMPLEMENTATION_PATTERNS = [
|
|
58
|
-
/\b(?:implement|edit|modify|refactor|delete)\b/i,
|
|
59
|
-
FIX_OR_PATCH_IMPLEMENTATION_PATTERN,
|
|
60
|
-
/\b(?:update|add|remove|replace|create)\b(?!\s+(?:(?:a|an|the)\s+)?(?:report|summary|findings?)(?:\b|$))/i,
|
|
61
|
-
/\bapply\s+(?:the\s+)?(?:(?:suggested|proposed|recommended)\s+)?(?:changes?|fix(?:es)?|patch)\b/i,
|
|
62
|
-
/\bmake\s+(?:the\s+)?changes\b/i,
|
|
63
|
-
/\bdo those fixes\b/i,
|
|
64
|
-
];
|
|
65
|
-
|
|
66
|
-
const GENERAL_IMPLEMENTATION_PATTERNS = [
|
|
67
|
-
/\b(?:implement|edit|modify|refactor)\b/i,
|
|
68
|
-
FIX_OR_PATCH_IMPLEMENTATION_PATTERN,
|
|
69
|
-
/\bapply\s+(?:the\s+)?(?:(?:suggested|proposed|recommended)\s+)?(?:changes?|fix(?:es)?|patch)\b/i,
|
|
70
|
-
/\bmake\s+(?:the\s+)?changes\b/i,
|
|
71
|
-
/\bdo those fixes\b/i,
|
|
72
|
-
/\b(?:update|add|remove|replace|delete|create)\s+(?:the\s+)?(?:file|files|code|source|implementation|test|tests|component|function|module|class|method|logic|import|imports|readme|docs?|changelog|package\.json|config|manifest|extension|prompt|command)\b/i,
|
|
73
|
-
];
|
|
5
|
+
export { expectsImplementationMutation };
|
|
74
6
|
|
|
75
7
|
const READ_ONLY_BUILTIN_TOOLS = new Set([
|
|
76
8
|
"read",
|
|
@@ -98,60 +30,9 @@ interface CompletionMutationGuardResult {
|
|
|
98
30
|
triggered: boolean;
|
|
99
31
|
}
|
|
100
32
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
function stripFrameworkInstructions(task: string): string {
|
|
106
|
-
return task
|
|
107
|
-
.split("\n")
|
|
108
|
-
.filter((line) => !/^\s*\[(?:Write to|Read from):/i.test(line))
|
|
109
|
-
.filter((line) => !/^\s*(?:Create and maintain progress at:|Update progress at:|\*\*Output:\*\*|Write your findings to(?: exactly this path)?:|This path is authoritative for this run\.|Ignore any other output filename or output path mentioned elsewhere)/i.test(line))
|
|
110
|
-
.join("\n");
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function stripScopedNoEditConstraints(task: string): string {
|
|
114
|
-
let stripped = task;
|
|
115
|
-
for (const pattern of SCOPED_NO_EDIT_CONSTRAINT_PATTERNS) {
|
|
116
|
-
stripped = stripped.replace(pattern, " ");
|
|
117
|
-
}
|
|
118
|
-
return stripped;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function taskHasExplicitReadOnlyIntent(taskText: string): boolean {
|
|
122
|
-
return REVIEW_ONLY_PATTERNS.some((pattern) => pattern.test(taskText))
|
|
123
|
-
|| EXPLICIT_NO_EDIT_PATTERNS.some((pattern) => pattern.test(taskText))
|
|
124
|
-
|| NO_TOOL_INTENT_PATTERNS.some((pattern) => pattern.test(taskText));
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function taskHasReadOnlyDeliverable(taskText: string): boolean {
|
|
128
|
-
return READ_ONLY_DELIVERABLE_PATTERNS.some((pattern) => pattern.test(taskText));
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function toolMutationCapability(tools: string[] | undefined, mcpDirectTools: string[] | undefined): ToolMutationCapability {
|
|
132
|
-
if (tools === undefined || tools.length === 0 || (mcpDirectTools?.length ?? 0) > 0) return { kind: "mutation-capable" };
|
|
133
|
-
return tools.every((tool) => READ_ONLY_BUILTIN_TOOLS.has(tool)) ? { kind: "read-only" } : { kind: "mutation-capable" };
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function classifyTaskMutationIntent(agent: string, task: string): TaskMutationIntent {
|
|
137
|
-
const taskText = stripFrameworkInstructions(task);
|
|
138
|
-
const taskTextWithoutScopedConstraints = stripScopedNoEditConstraints(taskText);
|
|
139
|
-
if (taskHasExplicitReadOnlyIntent(taskTextWithoutScopedConstraints)) return { kind: "read-only" };
|
|
140
|
-
|
|
141
|
-
if (RESEARCH_AGENT_PATTERNS.some((pattern) => pattern.test(agent))) return { kind: "read-only" };
|
|
142
|
-
if (/\breviewer\b/i.test(agent)) {
|
|
143
|
-
return REVIEWER_REQUIRED_EDIT_PATTERNS.some((pattern) => pattern.test(taskText)) ? { kind: "implementation" } : { kind: "read-only" };
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const workerIntent = agent === "worker" && WORKER_IMPLEMENTATION_PATTERNS.some((pattern) => pattern.test(taskText));
|
|
147
|
-
if (workerIntent) return { kind: "implementation" };
|
|
148
|
-
|
|
149
|
-
if (GENERAL_IMPLEMENTATION_PATTERNS.some((pattern) => pattern.test(taskText))) return { kind: "implementation" };
|
|
150
|
-
return taskHasReadOnlyDeliverable(taskTextWithoutScopedConstraints) ? { kind: "read-only" } : { kind: "unknown" };
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export function expectsImplementationMutation(agent: string, task: string): boolean {
|
|
154
|
-
return classifyTaskMutationIntent(agent, task).kind === "implementation";
|
|
33
|
+
export function hasMutationToolCapability(tools: string[] | undefined, mcpDirectTools: string[] | undefined): boolean {
|
|
34
|
+
if (tools === undefined || tools.length === 0 || (mcpDirectTools?.length ?? 0) > 0) return true;
|
|
35
|
+
return !tools.every((tool) => READ_ONLY_BUILTIN_TOOLS.has(tool));
|
|
155
36
|
}
|
|
156
37
|
|
|
157
38
|
export function hasMutationToolCall(messages: Message[]): boolean {
|
|
@@ -171,9 +52,9 @@ export function hasMutationToolCall(messages: Message[]): boolean {
|
|
|
171
52
|
}
|
|
172
53
|
|
|
173
54
|
export function evaluateCompletionMutationGuard(input: CompletionMutationGuardInput): CompletionMutationGuardResult {
|
|
174
|
-
const expectedMutation =
|
|
175
|
-
?
|
|
176
|
-
:
|
|
55
|
+
const expectedMutation = hasMutationToolCapability(input.tools, input.mcpDirectTools)
|
|
56
|
+
? expectsImplementationMutation(input.agent, input.task)
|
|
57
|
+
: false;
|
|
177
58
|
const attemptedMutation = hasMutationToolCall(input.messages);
|
|
178
59
|
return {
|
|
179
60
|
expectedMutation,
|
|
@@ -27,6 +27,7 @@ export interface DynamicCollectedResult {
|
|
|
27
27
|
structured?: unknown;
|
|
28
28
|
error?: string;
|
|
29
29
|
timedOut?: boolean;
|
|
30
|
+
stopped?: boolean;
|
|
30
31
|
outputPath?: string;
|
|
31
32
|
artifactPaths?: ArtifactPaths;
|
|
32
33
|
}
|
|
@@ -42,15 +43,15 @@ const ITEM_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
|
42
43
|
const ITEM_REF_PATTERN = /\{([A-Za-z_][A-Za-z0-9_]*)(?:\.([^{}]+))?\}/g;
|
|
43
44
|
const RESERVED_TEMPLATE_NAMES = new Set(["task", "previous", "chain_dir", "outputs"]);
|
|
44
45
|
const DYNAMIC_STEP_KEYS = new Set(["expand", "parallel", "collect", "concurrency", "failFast", "phase", "label", "acceptance"]);
|
|
45
|
-
const RUNNER_DYNAMIC_STEP_KEYS = new Set([...DYNAMIC_STEP_KEYS, "effectiveAcceptance", "sessionFiles", "thinkingOverrides"]);
|
|
46
|
+
const RUNNER_DYNAMIC_STEP_KEYS = new Set([...DYNAMIC_STEP_KEYS, "effectiveAcceptance", "acceptanceInput", "acceptanceRole", "sessionFiles", "thinkingOverrides"]);
|
|
46
47
|
const DYNAMIC_EXPAND_KEYS = new Set(["from", "item", "key", "maxItems", "onEmpty"]);
|
|
47
48
|
const DYNAMIC_EXPAND_FROM_KEYS = new Set(["output", "path"]);
|
|
48
49
|
const DYNAMIC_PARALLEL_KEYS = new Set(["agent", "task", "phase", "label", "outputSchema", "cwd", "output", "outputMode", "reads", "progress", "skill", "model", "toolBudget", "acceptance"]);
|
|
49
50
|
const RUNNER_DYNAMIC_PARALLEL_KEYS = new Set([
|
|
50
51
|
...DYNAMIC_PARALLEL_KEYS,
|
|
51
|
-
"outputName", "structured", "inheritProjectContext", "inheritSkills", "skills", "outputPath", "maxSubagentDepth",
|
|
52
|
+
"outputName", "structured", "inheritProjectContext", "inheritSkills", "skills", "outputPath", "namespaceOutputPath", "maxSubagentDepth", "waitToolEnabled",
|
|
52
53
|
"structuredOutput", "structuredOutputSchema", "tools", "extensions", "subagentOnlyExtensions", "mcpDirectTools", "completionGuard", "systemPrompt",
|
|
53
|
-
"systemPromptMode", "thinking", "modelCandidates", "sessionFile", "effectiveAcceptance", "parentSessionId",
|
|
54
|
+
"systemPromptMode", "thinking", "modelCandidates", "sessionFile", "effectiveAcceptance", "acceptanceInput", "acceptanceRole", "parentSessionId",
|
|
54
55
|
]);
|
|
55
56
|
const DYNAMIC_COLLECT_KEYS = new Set(["as", "outputSchema"]);
|
|
56
57
|
|
|
@@ -263,7 +264,7 @@ export function materializeDynamicParallelStep(step: DynamicParallelStep, output
|
|
|
263
264
|
export function collectDynamicResults(
|
|
264
265
|
step: DynamicParallelStep,
|
|
265
266
|
items: DynamicMaterializedItem[],
|
|
266
|
-
results: Array<Pick<SingleResult, "agent" | "exitCode" | "error" | "timedOut" | "structuredOutput" | "artifactPaths" | "savedOutputPath"> & { output?: string; finalOutput?: string }>,
|
|
267
|
+
results: Array<Pick<SingleResult, "agent" | "exitCode" | "error" | "timedOut" | "stopped" | "structuredOutput" | "artifactPaths" | "savedOutputPath"> & { output?: string; finalOutput?: string }>,
|
|
267
268
|
): DynamicCollectedResult[] {
|
|
268
269
|
return items.map((entry, index) => {
|
|
269
270
|
const result = results[index];
|
|
@@ -280,6 +281,7 @@ export function collectDynamicResults(
|
|
|
280
281
|
...(result?.structuredOutput !== undefined ? { structured: result.structuredOutput } : {}),
|
|
281
282
|
...(result?.error ? { error: result.error } : {}),
|
|
282
283
|
...(result?.timedOut ? { timedOut: true } : {}),
|
|
284
|
+
...(result?.stopped ? { stopped: true } : {}),
|
|
283
285
|
...(result?.savedOutputPath ? { outputPath: result.savedOutputPath } : {}),
|
|
284
286
|
...(result?.artifactPaths ? { artifactPaths: result.artifactPaths } : {}),
|
|
285
287
|
};
|
|
@@ -211,6 +211,31 @@ export function resolveSubagentModelOverride(
|
|
|
211
211
|
return resolved;
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
export function resolveEffectiveSubagentModel(
|
|
215
|
+
explicitModel: string | boolean | undefined,
|
|
216
|
+
agentModel: string | boolean | undefined,
|
|
217
|
+
parentModel: ParentModel | undefined,
|
|
218
|
+
availableModels: AvailableModelInfo[] | undefined,
|
|
219
|
+
preferredProvider?: string,
|
|
220
|
+
options?: Omit<ResolveSubagentModelOverrideOptions, "source">,
|
|
221
|
+
): string | undefined {
|
|
222
|
+
const resolved = resolveSubagentModelOverride(
|
|
223
|
+
explicitModel ?? agentModel,
|
|
224
|
+
parentModel,
|
|
225
|
+
availableModels,
|
|
226
|
+
preferredProvider,
|
|
227
|
+
{ ...options, source: explicitModel !== undefined ? "explicit" : "inherited" },
|
|
228
|
+
);
|
|
229
|
+
if (resolved || explicitModel === undefined) return resolved;
|
|
230
|
+
return resolveSubagentModelOverride(
|
|
231
|
+
agentModel,
|
|
232
|
+
parentModel,
|
|
233
|
+
availableModels,
|
|
234
|
+
preferredProvider,
|
|
235
|
+
{ ...options, source: "inherited" },
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
214
239
|
export interface BuildModelCandidatesOptions {
|
|
215
240
|
/** Fallback models are inherited agent config and warn, rather than error, when out of scope. */
|
|
216
241
|
scope?: ModelScopeConfig;
|
|
@@ -267,6 +292,7 @@ const RETRYABLE_MODEL_FAILURE_PATTERNS = [
|
|
|
267
292
|
/fetch failed/i,
|
|
268
293
|
/network error/i,
|
|
269
294
|
/socket hang up/i,
|
|
295
|
+
/stream ended without finish_reason/i,
|
|
270
296
|
/upstream/i,
|
|
271
297
|
/timed? out/i,
|
|
272
298
|
/timeout/i,
|
|
@@ -279,8 +305,18 @@ const RETRYABLE_MODEL_FAILURE_PATTERNS = [
|
|
|
279
305
|
/model.*(?:load|fail|error)/i,
|
|
280
306
|
];
|
|
281
307
|
|
|
308
|
+
/**
|
|
309
|
+
* Failures reported as `<tool> failed (exit N): ...` or `<tool> failed with
|
|
310
|
+
* exit code N` come from a tool call inside the child's task, not from the
|
|
311
|
+
* provider/model, however network-flavored their details read. Retrying a
|
|
312
|
+
* different model cannot fix them and would rerun the whole task. Tool names
|
|
313
|
+
* include namespaced forms like `mcp.server/write`.
|
|
314
|
+
*/
|
|
315
|
+
const TOOL_FAILURE_PREFIX = /^[\w.:@/-]+ failed (?:(?:\(exit \d+\):)|(?:with exit code \d+))(?:\s|$)/i;
|
|
316
|
+
|
|
282
317
|
export function isRetryableModelFailure(error: string | undefined): boolean {
|
|
283
318
|
if (!error) return false;
|
|
319
|
+
if (TOOL_FAILURE_PREFIX.test(error.trim())) return false;
|
|
284
320
|
return RETRYABLE_MODEL_FAILURE_PATTERNS.some((pattern) => pattern.test(error));
|
|
285
321
|
}
|
|
286
322
|
|
|
@@ -202,7 +202,7 @@ function sanitizeTurnBudget(value: unknown): TurnBudgetState | undefined {
|
|
|
202
202
|
const maxTurns = clampNumber(raw.maxTurns);
|
|
203
203
|
const graceTurns = clampNumber(raw.graceTurns);
|
|
204
204
|
const turnCount = clampNumber(raw.turnCount);
|
|
205
|
-
const outcome = raw.outcome === "within-budget" || raw.outcome === "wrap-up-requested" || raw.outcome === "exceeded" ? raw.outcome : undefined;
|
|
205
|
+
const outcome = raw.outcome === "within-budget" || raw.outcome === "wrap-up-requested" || raw.outcome === "termination-deferred" || raw.outcome === "exceeded" ? raw.outcome : undefined;
|
|
206
206
|
if (maxTurns === undefined || graceTurns === undefined || turnCount === undefined || !outcome) return undefined;
|
|
207
207
|
return {
|
|
208
208
|
maxTurns,
|
|
@@ -210,12 +210,13 @@ function sanitizeTurnBudget(value: unknown): TurnBudgetState | undefined {
|
|
|
210
210
|
turnCount,
|
|
211
211
|
outcome,
|
|
212
212
|
...(clampNumber(raw.wrapUpRequestedAtTurn) !== undefined ? { wrapUpRequestedAtTurn: clampNumber(raw.wrapUpRequestedAtTurn) } : {}),
|
|
213
|
+
...(clampNumber(raw.terminationDeferredAtTurn) !== undefined ? { terminationDeferredAtTurn: clampNumber(raw.terminationDeferredAtTurn) } : {}),
|
|
213
214
|
...(clampNumber(raw.exceededAtTurn) !== undefined ? { exceededAtTurn: clampNumber(raw.exceededAtTurn) } : {}),
|
|
214
215
|
};
|
|
215
216
|
}
|
|
216
217
|
|
|
217
218
|
function sanitizeState(value: unknown, fallback: NestedRunState): NestedRunState {
|
|
218
|
-
return value === "queued" || value === "running" || value === "complete" || value === "failed" || value === "paused"
|
|
219
|
+
return value === "queued" || value === "running" || value === "complete" || value === "failed" || value === "paused" || value === "stopped"
|
|
219
220
|
? value
|
|
220
221
|
: fallback;
|
|
221
222
|
}
|
|
@@ -225,7 +226,7 @@ function sanitizeStep(input: unknown, depth: number): NestedStepSummary | undefi
|
|
|
225
226
|
const raw = input as Record<string, unknown>;
|
|
226
227
|
const agent = stringValue(raw.agent, 128);
|
|
227
228
|
if (!agent) return undefined;
|
|
228
|
-
const status = raw.status === "pending" || raw.status === "running" || raw.status === "complete" || raw.status === "completed" || raw.status === "failed" || raw.status === "paused"
|
|
229
|
+
const status = raw.status === "pending" || raw.status === "running" || raw.status === "complete" || raw.status === "completed" || raw.status === "failed" || raw.status === "paused" || raw.status === "stopped"
|
|
229
230
|
? raw.status
|
|
230
231
|
: "pending";
|
|
231
232
|
return {
|
|
@@ -243,6 +244,7 @@ function sanitizeStep(input: unknown, depth: number): NestedStepSummary | undefi
|
|
|
243
244
|
...(clampNumber(raw.endedAt) !== undefined ? { endedAt: clampNumber(raw.endedAt) } : {}),
|
|
244
245
|
...(stringValue(raw.error, 1024) ? { error: stringValue(raw.error, 1024) } : {}),
|
|
245
246
|
...(raw.timedOut === true ? { timedOut: true } : {}),
|
|
247
|
+
...(raw.stopped === true ? { stopped: true } : {}),
|
|
246
248
|
...(sanitizeTurnBudget(raw.turnBudget) ? { turnBudget: sanitizeTurnBudget(raw.turnBudget) } : {}),
|
|
247
249
|
...(raw.turnBudgetExceeded === true ? { turnBudgetExceeded: true } : {}),
|
|
248
250
|
...(raw.wrapUpRequested === true ? { wrapUpRequested: true } : {}),
|
|
@@ -298,6 +300,7 @@ export function sanitizeSummary(input: unknown, depth = 0): NestedRunSummary | u
|
|
|
298
300
|
...(clampNumber(raw.timeoutMs) !== undefined ? { timeoutMs: clampNumber(raw.timeoutMs) } : {}),
|
|
299
301
|
...(clampNumber(raw.deadlineAt) !== undefined ? { deadlineAt: clampNumber(raw.deadlineAt) } : {}),
|
|
300
302
|
...(raw.timedOut === true ? { timedOut: true } : {}),
|
|
303
|
+
...(raw.stopped === true ? { stopped: true } : {}),
|
|
301
304
|
...(sanitizeTurnBudget(raw.turnBudget) ? { turnBudget: sanitizeTurnBudget(raw.turnBudget) } : {}),
|
|
302
305
|
...(raw.turnBudgetExceeded === true ? { turnBudgetExceeded: true } : {}),
|
|
303
306
|
...(raw.wrapUpRequested === true ? { wrapUpRequested: true } : {}),
|
|
@@ -353,7 +356,7 @@ export function parseNestedEventRecords(content: string, route: NestedRoute): Ne
|
|
|
353
356
|
}
|
|
354
357
|
|
|
355
358
|
function terminal(state: NestedRunState): boolean {
|
|
356
|
-
return state === "complete" || state === "failed" || state === "paused";
|
|
359
|
+
return state === "complete" || state === "failed" || state === "paused" || state === "stopped";
|
|
357
360
|
}
|
|
358
361
|
|
|
359
362
|
function mergeSummary(existing: NestedRunSummary | undefined, event: NestedEventRecord): NestedRunSummary {
|
|
@@ -859,6 +862,7 @@ export function nestedSummaryFromAsyncStatus(status: AsyncStatus, asyncDir: stri
|
|
|
859
862
|
...(status.timeoutMs !== undefined ? { timeoutMs: status.timeoutMs } : {}),
|
|
860
863
|
...(status.deadlineAt !== undefined ? { deadlineAt: status.deadlineAt } : {}),
|
|
861
864
|
...(status.timedOut !== undefined ? { timedOut: status.timedOut } : {}),
|
|
865
|
+
...(status.stopped !== undefined ? { stopped: status.stopped } : {}),
|
|
862
866
|
...(status.turnBudget ? { turnBudget: status.turnBudget } : {}),
|
|
863
867
|
...(status.turnBudgetExceeded !== undefined ? { turnBudgetExceeded: status.turnBudgetExceeded } : {}),
|
|
864
868
|
...(status.wrapUpRequested !== undefined ? { wrapUpRequested: status.wrapUpRequested } : {}),
|
|
@@ -882,6 +886,7 @@ export function nestedSummaryFromAsyncStatus(status: AsyncStatus, asyncDir: stri
|
|
|
882
886
|
...(step.endedAt !== undefined ? { endedAt: step.endedAt } : {}),
|
|
883
887
|
...(step.error ? { error: step.error } : {}),
|
|
884
888
|
...(step.timedOut !== undefined ? { timedOut: step.timedOut } : {}),
|
|
889
|
+
...(step.stopped !== undefined ? { stopped: step.stopped } : {}),
|
|
885
890
|
...(step.turnBudget ? { turnBudget: step.turnBudget } : {}),
|
|
886
891
|
...(step.turnBudgetExceeded !== undefined ? { turnBudgetExceeded: step.turnBudgetExceeded } : {}),
|
|
887
892
|
...(step.wrapUpRequested !== undefined ? { wrapUpRequested: step.wrapUpRequested } : {}),
|
|
@@ -8,11 +8,12 @@ export interface NestedRunCounts {
|
|
|
8
8
|
paused: number;
|
|
9
9
|
complete: number;
|
|
10
10
|
failed: number;
|
|
11
|
+
stopped: number;
|
|
11
12
|
queued: number;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export function countNestedRuns(children: NestedRunSummary[] | undefined): NestedRunCounts {
|
|
15
|
-
const counts: NestedRunCounts = { total: 0, running: 0, paused: 0, complete: 0, failed: 0, queued: 0 };
|
|
16
|
+
const counts: NestedRunCounts = { total: 0, running: 0, paused: 0, complete: 0, failed: 0, stopped: 0, queued: 0 };
|
|
16
17
|
for (const child of children ?? []) {
|
|
17
18
|
counts.total++;
|
|
18
19
|
counts[child.state]++;
|
|
@@ -22,6 +23,7 @@ export function countNestedRuns(children: NestedRunSummary[] | undefined): Neste
|
|
|
22
23
|
counts.paused += nested.paused;
|
|
23
24
|
counts.complete += nested.complete;
|
|
24
25
|
counts.failed += nested.failed;
|
|
26
|
+
counts.stopped += nested.stopped;
|
|
25
27
|
counts.queued += nested.queued;
|
|
26
28
|
}
|
|
27
29
|
return counts;
|
|
@@ -34,6 +36,7 @@ export function formatNestedAggregate(children: NestedRunSummary[] | undefined):
|
|
|
34
36
|
counts.running > 0 ? `${counts.running} running` : "",
|
|
35
37
|
counts.paused > 0 ? `${counts.paused} paused` : "",
|
|
36
38
|
counts.failed > 0 ? `${counts.failed} failed` : "",
|
|
39
|
+
counts.stopped > 0 ? `${counts.stopped} stopped` : "",
|
|
37
40
|
counts.complete > 0 ? `${counts.complete} complete` : "",
|
|
38
41
|
counts.queued > 0 ? `${counts.queued} queued` : "",
|
|
39
42
|
].filter(Boolean);
|
|
@@ -28,9 +28,12 @@ export interface RunnerSubagentStep {
|
|
|
28
28
|
inheritSkills: boolean;
|
|
29
29
|
skills?: string[];
|
|
30
30
|
outputPath?: string;
|
|
31
|
+
/** Defer the authoritative output instruction until a dynamic fanout item is materialized. */
|
|
32
|
+
namespaceOutputPath?: boolean;
|
|
31
33
|
outputMode?: "inline" | "file-only";
|
|
32
34
|
sessionFile?: string;
|
|
33
35
|
maxSubagentDepth?: number;
|
|
36
|
+
waitToolEnabled?: boolean;
|
|
34
37
|
structuredOutput?: {
|
|
35
38
|
schema: import("../../shared/types.ts").JsonSchemaObject;
|
|
36
39
|
schemaPath: string;
|
|
@@ -38,6 +41,8 @@ export interface RunnerSubagentStep {
|
|
|
38
41
|
};
|
|
39
42
|
structuredOutputSchema?: import("../../shared/types.ts").JsonSchemaObject;
|
|
40
43
|
effectiveAcceptance?: import("../../shared/types.ts").ResolvedAcceptanceConfig;
|
|
44
|
+
acceptanceInput?: import("../../shared/types.ts").AcceptanceInput;
|
|
45
|
+
acceptanceRole?: import("../../shared/types.ts").AcceptanceRole;
|
|
41
46
|
toolBudget?: import("../../shared/types.ts").ResolvedToolBudget;
|
|
42
47
|
}
|
|
43
48
|
|
|
@@ -59,6 +64,8 @@ export interface DynamicRunnerGroup {
|
|
|
59
64
|
sessionFiles?: (string | undefined)[];
|
|
60
65
|
thinkingOverrides?: (string | undefined)[];
|
|
61
66
|
effectiveAcceptance?: import("../../shared/types.ts").ResolvedAcceptanceConfig;
|
|
67
|
+
acceptanceInput?: import("../../shared/types.ts").AcceptanceInput;
|
|
68
|
+
acceptanceRole?: import("../../shared/types.ts").AcceptanceRole;
|
|
62
69
|
}
|
|
63
70
|
|
|
64
71
|
export type RunnerStep = RunnerSubagentStep | ParallelStepGroup | DynamicRunnerGroup;
|
|
@@ -6,9 +6,12 @@ import { encodeNestedPathEnv, parseNestedPathEnv, type NestedPathEntry } from ".
|
|
|
6
6
|
import { resolveMcpDirectToolNames } from "./mcp-direct-tool-allowlist.ts";
|
|
7
7
|
import { STRUCTURED_OUTPUT_CAPTURE_ENV, STRUCTURED_OUTPUT_SCHEMA_ENV } from "./structured-output.ts";
|
|
8
8
|
import { TEMP_ROOT_DIR, type JsonSchemaObject, type ResolvedToolBudget } from "../../shared/types.ts";
|
|
9
|
+
import { THINKING_LEVELS } from "../../shared/model-info.ts";
|
|
9
10
|
import { TOOL_BUDGET_ENV, encodeToolBudgetEnv } from "./tool-budget.ts";
|
|
11
|
+
import { CHILD_TOOL_DIAGNOSTIC_PATH_ENV, REQUIRED_CHILD_TOOLS_ENV } from "./tool-availability.ts";
|
|
12
|
+
import { CHILD_WATCHDOG_CONFIG_ENV, encodeChildWatchdogConfig, type ChildWatchdogConfig } from "../../watchdog/child-status.ts";
|
|
13
|
+
import { WAIT_TOOL_ENABLED_ENV } from "../background/wait-config.ts";
|
|
10
14
|
|
|
11
|
-
const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"];
|
|
12
15
|
const TASK_ARG_LIMIT = 8000;
|
|
13
16
|
const PROMPT_RUNTIME_EXTENSION_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)), "subagent-prompt-runtime.ts");
|
|
14
17
|
const FANOUT_CHILD_EXTENSION_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "extension", "fanout-child.ts");
|
|
@@ -30,6 +33,8 @@ export const SUBAGENT_PARENT_PATH_ENV = "PI_SUBAGENT_PARENT_PATH";
|
|
|
30
33
|
export const SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV = "PI_SUBAGENT_PARENT_CAPABILITY_TOKEN";
|
|
31
34
|
export const SUBAGENT_PARENT_SESSION_ENV = "PI_SUBAGENT_PARENT_SESSION";
|
|
32
35
|
export const SUBAGENT_STEER_INBOX_ENV = "PI_SUBAGENT_STEER_INBOX";
|
|
36
|
+
export const SUBAGENT_STEER_CAPABILITY_ENV = "PI_SUBAGENT_STEER_CAPABILITY";
|
|
37
|
+
export const SUBAGENT_STEER_ACK_DIR_ENV = "PI_SUBAGENT_STEER_ACK_DIR";
|
|
33
38
|
|
|
34
39
|
interface BuildPiArgsInput {
|
|
35
40
|
parentSessionId?: string;
|
|
@@ -65,18 +70,23 @@ interface BuildPiArgsInput {
|
|
|
65
70
|
parentPath?: NestedPathEntry[];
|
|
66
71
|
parentCapabilityToken?: string;
|
|
67
72
|
steerInboxDir?: string;
|
|
73
|
+
steerCapabilityPath?: string;
|
|
74
|
+
steerAckDir?: string;
|
|
68
75
|
structuredOutput?: {
|
|
69
76
|
schema: JsonSchemaObject;
|
|
70
77
|
schemaPath: string;
|
|
71
78
|
outputPath: string;
|
|
72
79
|
};
|
|
73
80
|
toolBudget?: ResolvedToolBudget;
|
|
81
|
+
childWatchdog?: ChildWatchdogConfig;
|
|
82
|
+
waitToolEnabled?: boolean;
|
|
74
83
|
}
|
|
75
84
|
|
|
76
85
|
interface BuildPiArgsResult {
|
|
77
86
|
args: string[];
|
|
78
87
|
env: Record<string, string | undefined>;
|
|
79
88
|
tempDir?: string;
|
|
89
|
+
toolDiagnosticPath?: string;
|
|
80
90
|
}
|
|
81
91
|
|
|
82
92
|
function sanitizeSupervisorChannelSegment(value: string): string {
|
|
@@ -90,7 +100,7 @@ function supervisorChannelDir(runId: string, agent: string, childIndex: number):
|
|
|
90
100
|
export function applyThinkingSuffix(model: string | undefined, thinking: string | false | undefined, replaceExisting = false): string | undefined {
|
|
91
101
|
if (!model || !thinking) return model;
|
|
92
102
|
const colonIdx = model.lastIndexOf(":");
|
|
93
|
-
if (colonIdx !== -1 && THINKING_LEVELS.
|
|
103
|
+
if (colonIdx !== -1 && THINKING_LEVELS.some((level) => level === model.substring(colonIdx + 1))) {
|
|
94
104
|
return replaceExisting ? `${model.slice(0, colonIdx)}:${thinking}` : model;
|
|
95
105
|
}
|
|
96
106
|
return `${model}:${thinking}`;
|
|
@@ -123,18 +133,21 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
|
|
|
123
133
|
: declaredBuiltinToolsBase;
|
|
124
134
|
const fanoutAuthorized = declaredBuiltinTools.includes("subagent");
|
|
125
135
|
const toolExtensionPaths: string[] = [];
|
|
136
|
+
let requiredChildTools: string[] = [];
|
|
126
137
|
if (input.tools?.length) {
|
|
127
|
-
const
|
|
138
|
+
const allowedTools = [...declaredBuiltinTools];
|
|
128
139
|
for (const tool of input.tools) {
|
|
129
140
|
if (!declaredBuiltinTools.includes(tool) && (tool.includes("/") || tool.endsWith(".ts") || tool.endsWith(".js"))) {
|
|
130
141
|
toolExtensionPaths.push(tool);
|
|
131
142
|
}
|
|
132
143
|
}
|
|
133
|
-
if (
|
|
144
|
+
if (allowedTools.length > 0) {
|
|
134
145
|
if (input.mcpDirectTools?.length) {
|
|
135
|
-
|
|
146
|
+
allowedTools.push(...resolveMcpDirectToolNames(input.mcpDirectTools, input.cwd));
|
|
136
147
|
}
|
|
137
|
-
|
|
148
|
+
if (input.structuredOutput) allowedTools.push("structured_output");
|
|
149
|
+
requiredChildTools = [...new Set(allowedTools)];
|
|
150
|
+
args.push("--tools", requiredChildTools.join(","));
|
|
138
151
|
}
|
|
139
152
|
}
|
|
140
153
|
|
|
@@ -177,8 +190,18 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
|
|
|
177
190
|
}
|
|
178
191
|
|
|
179
192
|
const env: Record<string, string | undefined> = {};
|
|
193
|
+
let toolDiagnosticPath: string | undefined;
|
|
194
|
+
if (requiredChildTools.length > 0) {
|
|
195
|
+
if (!tempDir) tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
|
|
196
|
+
toolDiagnosticPath = path.join(tempDir, "tool-diagnostic.json");
|
|
197
|
+
env[REQUIRED_CHILD_TOOLS_ENV] = JSON.stringify(requiredChildTools);
|
|
198
|
+
env[CHILD_TOOL_DIAGNOSTIC_PATH_ENV] = toolDiagnosticPath;
|
|
199
|
+
}
|
|
180
200
|
env[SUBAGENT_CHILD_ENV] = "1";
|
|
181
201
|
env[SUBAGENT_FANOUT_CHILD_ENV] = fanoutAuthorized ? "1" : "0";
|
|
202
|
+
if (input.waitToolEnabled !== undefined) {
|
|
203
|
+
env[WAIT_TOOL_ENABLED_ENV] = input.waitToolEnabled ? "true" : "false";
|
|
204
|
+
}
|
|
182
205
|
const inheritedNestedRoute = Boolean(process.env[SUBAGENT_PARENT_EVENT_SINK_ENV] && process.env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV] && process.env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV]);
|
|
183
206
|
const parentRunId = input.parentRunId ?? input.runId ?? (inheritedNestedRoute ? process.env[SUBAGENT_RUN_ID_ENV] : undefined) ?? process.env[SUBAGENT_PARENT_RUN_ID_ENV] ?? "";
|
|
184
207
|
const parentChildIndex = input.parentChildIndex !== undefined
|
|
@@ -251,12 +274,16 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
|
|
|
251
274
|
if (input.steerInboxDir) {
|
|
252
275
|
env[SUBAGENT_STEER_INBOX_ENV] = input.steerInboxDir;
|
|
253
276
|
}
|
|
277
|
+
if (input.steerCapabilityPath) env[SUBAGENT_STEER_CAPABILITY_ENV] = input.steerCapabilityPath;
|
|
278
|
+
if (input.steerAckDir) env[SUBAGENT_STEER_ACK_DIR_ENV] = input.steerAckDir;
|
|
254
279
|
const encodedToolBudget = encodeToolBudgetEnv(input.toolBudget);
|
|
255
280
|
if (encodedToolBudget) env[TOOL_BUDGET_ENV] = encodedToolBudget;
|
|
281
|
+
const encodedChildWatchdog = encodeChildWatchdogConfig(input.childWatchdog);
|
|
282
|
+
if (encodedChildWatchdog) env[CHILD_WATCHDOG_CONFIG_ENV] = encodedChildWatchdog;
|
|
256
283
|
|
|
257
284
|
env[SUBAGENT_PARENT_SESSION_ENV] = input.parentSessionId ?? process.env[SUBAGENT_PARENT_SESSION_ENV] ?? "";
|
|
258
285
|
|
|
259
|
-
return { args, env, tempDir };
|
|
286
|
+
return { args, env, tempDir, toolDiagnosticPath };
|
|
260
287
|
}
|
|
261
288
|
|
|
262
289
|
export const parseParentPathEnv = parseNestedPathEnv;
|