pi-subagents 0.66.0 → 0.68.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 +138 -0
- package/README.md +5 -4
- package/agents/evidence-auditor.md +34 -0
- package/agents/reviewer.md +3 -2
- package/docs/agents.md +43 -15
- package/docs/configuration.md +67 -23
- package/docs/extension-api.md +38 -19
- package/docs/missions.md +10 -2
- package/docs/models.md +11 -79
- package/docs/observability.md +22 -12
- package/docs/standalone-background.md +59 -0
- package/docs/tool-reference.md +33 -20
- package/docs/watchdog.md +39 -10
- package/docs/workflows.md +37 -13
- package/index.ts +5 -2
- package/inspector-runner.mjs +2 -2
- package/package.json +4 -2
- package/prompts/parallel-review.md +1 -1
- package/runner-peer-loader.mjs +24 -0
- package/runner-peer-preload.mjs +32 -0
- package/skills/pi-subagents/SKILL.md +32 -21
- package/skills/pi-subagents/references/constraints-and-recipes.md +3 -2
- package/skills/pi-subagents/references/execution-controls.md +11 -9
- package/skills/pi-subagents/references/management-authoring-rpc.md +0 -1
- package/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +17 -13
- package/skills/pi-subagents/references/review-and-validation.md +3 -3
- package/src/agents/advertised-agent-prompt.ts +34 -3
- package/src/agents/agent-management.ts +57 -58
- package/src/agents/agent-serializer.ts +4 -3
- package/src/agents/agents.ts +190 -71
- package/src/agents/builtin-names.ts +1 -0
- package/src/agents/chain-serializer.ts +5 -0
- package/src/agents/runtime-agent-registry.ts +7 -6
- package/src/api/delegation.ts +4 -0
- package/src/api/preflight.ts +94 -59
- package/src/api/required-child-extensions.ts +6 -0
- package/src/api/shared-types.ts +2 -0
- package/src/extension/config.ts +10 -37
- package/src/extension/fanout-child.ts +66 -4
- package/src/extension/herdr-pi-bridge.ts +160 -0
- package/src/extension/index.ts +62 -39
- package/src/extension/public-execution.ts +7 -5
- package/src/extension/rpc.ts +4 -0
- package/src/extension/schemas.ts +81 -81
- package/src/extension/tool-description.ts +30 -82
- package/src/inspectors/actions.ts +148 -0
- package/src/inspectors/ghostty/actions.ts +74 -0
- package/src/inspectors/ghostty/plugin.ts +17 -0
- package/src/inspectors/herdr/actions.ts +99 -179
- package/src/inspectors/herdr/plugin.ts +20 -0
- package/src/inspectors/herdr/project-panes.ts +1 -1
- package/src/inspectors/{herdr/inspector-runner.ts → inspector-runner.ts} +12 -12
- package/src/inspectors/plugins.ts +8 -0
- package/src/inspectors/{herdr/session-roots-codec.ts → session-roots-codec.ts} +3 -14
- package/src/inspectors/types.ts +51 -0
- package/src/intercom/intercom-bridge.ts +50 -8
- package/src/intercom/native-supervisor-channel.ts +44 -31
- package/src/policy/authority.ts +4 -0
- package/src/profiles/profiles.ts +12 -6
- package/src/runs/background/active-async-capacity.ts +4 -0
- package/src/runs/background/active-run-index.ts +17 -1
- package/src/runs/background/async-execution.ts +348 -176
- package/src/runs/background/async-job-tracker.ts +8 -6
- package/src/runs/background/async-resume.ts +17 -12
- package/src/runs/background/async-status.ts +15 -4
- package/src/runs/background/auto-drain.ts +23 -10
- package/src/runs/background/binary-bootstrap.ts +38 -0
- package/src/runs/background/chain-append.ts +1 -1
- package/src/runs/background/chain-root-attachment.ts +14 -33
- package/src/runs/background/fleet-view.ts +30 -2
- package/src/runs/background/notify.ts +105 -7
- package/src/runs/background/owned-process-tree.ts +29 -2
- package/src/runs/background/result-files.ts +8 -4
- package/src/runs/background/result-watcher.ts +19 -2
- package/src/runs/background/run-child-session.ts +81 -33
- package/src/runs/background/run-status.ts +3 -0
- package/src/runs/background/runner-aliases.ts +12 -33
- package/src/runs/background/runner-child-launch.ts +6 -1
- package/src/runs/background/runner-child-sessions.ts +5 -4
- package/src/runs/background/runner-http-dispatcher.ts +119 -0
- package/src/runs/background/scheduled-runs.ts +51 -18
- package/src/runs/background/stale-run-reconciler.ts +35 -11
- package/src/runs/background/steering.ts +20 -2
- package/src/runs/background/subagent-runner.ts +441 -304
- package/src/runs/background/subagent-wait.ts +176 -28
- package/src/runs/background/wait-completions.ts +75 -27
- package/src/runs/background/wait-subscriptions.ts +9 -3
- package/src/runs/background/wait-tool.ts +5 -3
- package/src/runs/foreground/async-steering-action.ts +18 -7
- package/src/runs/foreground/async-stop-action.ts +93 -3
- package/src/runs/foreground/execution.ts +134 -248
- package/src/runs/foreground/foreground-history.ts +2 -1
- package/src/runs/foreground/prompt-audit.ts +3 -1
- package/src/runs/foreground/subagent-executor.ts +374 -178
- package/src/runs/foreground/workflow-detach-reconcile.ts +2 -0
- package/src/runs/foreground/workflow-foreground-steering.ts +2 -1
- package/src/runs/shared/acceptance.ts +38 -11
- package/src/runs/shared/async-status-projection.ts +127 -33
- package/src/runs/shared/capability-ceiling.ts +2 -0
- package/src/runs/shared/child-hooks.ts +25 -10
- package/src/runs/shared/child-launch-plan.ts +15 -3
- package/src/runs/shared/child-launch.ts +28 -5
- package/src/runs/shared/child-lifecycle.ts +6 -3
- package/src/runs/shared/child-runtime-config.ts +8 -1
- package/src/runs/shared/child-session.ts +127 -52
- package/src/runs/shared/child-tool-plan.ts +142 -11
- package/src/runs/shared/completion-guard.ts +5 -3
- package/src/runs/shared/dynamic-fanout.ts +2 -2
- package/src/runs/shared/effective-system-prompt.ts +33 -0
- package/src/runs/shared/external-cli-contract.ts +11 -1
- package/src/runs/shared/external-cli-preflight.ts +6 -2
- package/src/runs/shared/external-cli-runner.ts +9 -7
- package/src/runs/shared/herdr-connection.ts +134 -0
- package/src/runs/shared/herdr-external-adapters.ts +169 -0
- package/src/runs/shared/herdr-machine.ts +279 -0
- package/src/runs/shared/herdr-pi-protocol.ts +59 -0
- package/src/runs/shared/herdr-placed-run.ts +263 -0
- package/src/runs/shared/llm-intent-arbiter.ts +12 -3
- package/src/runs/shared/model-resolution-diagnostic.ts +76 -0
- package/src/runs/shared/{model-fallback.ts → model-resolution.ts} +22 -235
- package/src/runs/shared/model-scope.ts +1 -1
- package/src/runs/shared/nested-events.ts +11 -2
- package/src/runs/shared/orca-progress-tabs.ts +1 -1
- package/src/runs/shared/parallel-utils.ts +7 -2
- package/src/runs/shared/pi-spawn.ts +10 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +12 -4
- package/src/runs/shared/task-intent.ts +46 -13
- package/src/runs/shared/workflow-async-child-guidance.ts +18 -0
- package/src/runs/shared/worktree-setup-command.ts +27 -4
- package/src/runs/shared/worktree.ts +45 -15
- package/src/shared/child-cache-retention.ts +43 -0
- package/src/shared/fork-context.ts +15 -72
- package/src/shared/launch-contract.ts +68 -8
- package/src/shared/opencode-session-headers.ts +30 -0
- package/src/shared/pruned-fork.ts +1 -1
- package/src/shared/required-child-extensions.ts +81 -0
- package/src/shared/settings.ts +5 -2
- package/src/shared/shortcuts.ts +0 -4
- package/src/shared/types.ts +74 -30
- package/src/slash/delegation-adapters.ts +3 -1
- package/src/slash/delegation-request.ts +14 -0
- package/src/slash/slash-commands.ts +2 -7
- package/src/slash/subagents-admin.ts +24 -13
- package/src/tui/fleet-status.ts +164 -19
- package/src/tui/fleet.ts +16 -14
- package/src/tui/render.ts +168 -37
- package/src/watchdog/child-status.ts +28 -28
- package/src/watchdog/lsp-diagnostics.ts +1 -1
- package/src/watchdog/model-selection.ts +21 -1
- package/src/watchdog/permission-arbiter.ts +3 -1
- package/src/watchdog/register-child.ts +10 -2
- package/src/watchdog/register-main.ts +39 -35
- package/src/watchdog/render.ts +1 -1
- package/src/watchdog/review.ts +123 -74
- package/src/watchdog/rules.ts +1 -1
- package/src/watchdog/runtime.ts +100 -27
- package/src/watchdog/scope.ts +1 -1
- package/src/watchdog/settings.ts +3 -0
- package/src/watchdog/tool-actions.ts +13 -12
- package/src/watchdog/turn-delta.ts +23 -0
- package/src/watchdog/types.ts +5 -3
- package/src/watchdog/warning-format.ts +1 -1
- package/src/workflows/scripted-workflow.ts +279 -10
- package/src/workflows/workflow-checklist.ts +2 -2
- package/src/workflows/workflow-receipt.ts +21 -3
- package/src/workflows/workflow-resources.ts +13 -2
- package/runner-server-preload.mjs +0 -13
- package/src/runs/shared/model-exclusions.ts +0 -374
- package/src/runs/shared/readonly-model-continuation.ts +0 -69
- package/src/runs/shared/readonly-session-evidence.ts +0 -307
- /package/src/inspectors/{herdr/shell-command.ts → shell-command.ts} +0 -0
|
@@ -7,6 +7,7 @@ import { createHash } from "node:crypto";
|
|
|
7
7
|
import * as fs from "node:fs";
|
|
8
8
|
import * as path from "node:path";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
10
11
|
import {
|
|
11
12
|
formatUnresolvedMcpDirectToolSelectors,
|
|
12
13
|
resolveMcpDirectToolResolution,
|
|
@@ -21,6 +22,7 @@ import {
|
|
|
21
22
|
import { THINKING_LEVELS } from "../../shared/model-info.ts";
|
|
22
23
|
import { getAgentDir } from "../../shared/utils.ts";
|
|
23
24
|
import type { PermissionRules } from "./permissions.ts";
|
|
25
|
+
import { snapshotRequiredChildExtensions, type RequiredChildExtensionSnapshot } from "../../shared/required-child-extensions.ts";
|
|
24
26
|
import {
|
|
25
27
|
capabilityCeilingAgentRestrictionSources,
|
|
26
28
|
intersectSubagentCapabilityCeilings,
|
|
@@ -32,18 +34,18 @@ import {
|
|
|
32
34
|
const MAX_LAUNCH_RESOLVED_EXTENSION_IDS = 32;
|
|
33
35
|
const PROMPT_RUNTIME_EXTENSION_PATH = path.join(
|
|
34
36
|
path.dirname(fileURLToPath(import.meta.url)),
|
|
35
|
-
|
|
37
|
+
`subagent-prompt-runtime${path.extname(fileURLToPath(import.meta.url))}`,
|
|
36
38
|
);
|
|
37
39
|
const FANOUT_CHILD_EXTENSION_PATH = path.join(
|
|
38
40
|
path.dirname(fileURLToPath(import.meta.url)),
|
|
39
41
|
"..",
|
|
40
42
|
"..",
|
|
41
43
|
"extension",
|
|
42
|
-
|
|
44
|
+
`fanout-child${path.extname(fileURLToPath(import.meta.url))}`,
|
|
43
45
|
);
|
|
44
46
|
const FAST_MODE_EXTENSION_PATH = path.join(
|
|
45
47
|
path.dirname(fileURLToPath(import.meta.url)),
|
|
46
|
-
|
|
48
|
+
`fast-mode-extension${path.extname(fileURLToPath(import.meta.url))}`,
|
|
47
49
|
);
|
|
48
50
|
const SUBAGENT_RUNTIME_EXTENSION_PATHS = new Set([
|
|
49
51
|
PROMPT_RUNTIME_EXTENSION_PATH,
|
|
@@ -60,6 +62,39 @@ const FAST_MODE_ALLOWED_MODELS = new Set([
|
|
|
60
62
|
"openai-codex/gpt-5.6-sol",
|
|
61
63
|
]);
|
|
62
64
|
const OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH = 64;
|
|
65
|
+
const PI_BUILTIN_TOOL_NAMES = new Set(["read", "bash", "powershell", "edit", "write", "grep", "find", "ls"]);
|
|
66
|
+
const REPOSITORY_INSPECTION_TOOLS = new Set(["read", "grep", "find", "ls", "bash", "powershell"]);
|
|
67
|
+
const REVIEW_OR_SCOUT_AGENT_PATTERN = /\b(?:reviewer|scout)\b/i;
|
|
68
|
+
|
|
69
|
+
export function isReviewOrScoutLaneAgent(agentName: string | undefined): boolean {
|
|
70
|
+
return typeof agentName === "string" && REVIEW_OR_SCOUT_AGENT_PATTERN.test(agentName);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function missingPermittedRepositoryInspectionTools(
|
|
74
|
+
unavailableHostBuiltins: readonly string[],
|
|
75
|
+
excludeTools: readonly string[] = [],
|
|
76
|
+
): string[] {
|
|
77
|
+
const excluded = new Set(excludeTools);
|
|
78
|
+
return unavailableHostBuiltins.filter((tool) => REPOSITORY_INSPECTION_TOOLS.has(tool) && !excluded.has(tool));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function formatReviewLaneToolContractFailure(input: {
|
|
82
|
+
agentName?: string;
|
|
83
|
+
missingTools: readonly string[];
|
|
84
|
+
requestedTools?: readonly string[];
|
|
85
|
+
effectiveTools: readonly string[];
|
|
86
|
+
ceilingSources?: readonly string[];
|
|
87
|
+
excludeTools?: readonly string[];
|
|
88
|
+
}): string {
|
|
89
|
+
const subject = input.agentName ? `Agent '${input.agentName}'` : "Subagent";
|
|
90
|
+
return [
|
|
91
|
+
`${subject}: tool contract could not be satisfied; host runtime does not provide permitted required repository tools [${input.missingTools.join(", ")}].`,
|
|
92
|
+
`Requested tool names: ${input.requestedTools ? `[${input.requestedTools.join(", ")}]` : "not explicitly specified"}; effective tool allowlist: [${input.effectiveTools.join(", ")}].`,
|
|
93
|
+
...(input.ceilingSources?.length ? [`Active capability ceiling sources: [${input.ceilingSources.join(", ")}].`] : []),
|
|
94
|
+
...(input.excludeTools?.length ? [`Explicit excludeTools: [${input.excludeTools.join(", ")}].`] : []),
|
|
95
|
+
"This is a lane infrastructure failure, not a completed review/scout result.",
|
|
96
|
+
].join(" ");
|
|
97
|
+
}
|
|
63
98
|
|
|
64
99
|
export function deriveForkPromptCacheKey(parentSessionId: string | undefined): string | undefined {
|
|
65
100
|
const parent = parentSessionId?.trim();
|
|
@@ -113,9 +148,9 @@ function stripThinkingSuffix(model: string): string {
|
|
|
113
148
|
: model;
|
|
114
149
|
}
|
|
115
150
|
|
|
116
|
-
function resolveFastModeExtension(input: Pick<ResolvePiLaunchToolPlanInput, "fast" | "model" | "
|
|
151
|
+
function resolveFastModeExtension(input: Pick<ResolvePiLaunchToolPlanInput, "fast" | "model" | "agentName">): string[] {
|
|
117
152
|
if (!input.fast) return [];
|
|
118
|
-
const candidates = (input.
|
|
153
|
+
const candidates = (input.model ? [input.model] : [])
|
|
119
154
|
.map(stripThinkingSuffix);
|
|
120
155
|
if (candidates.length === 0) {
|
|
121
156
|
throw new Error(`fast mode requires an explicit supported native OpenAI-Codex model${input.agentName ? ` for agent '${input.agentName}'` : ""}.`);
|
|
@@ -133,6 +168,7 @@ export interface ResolvePiLaunchToolPlanInput {
|
|
|
133
168
|
allowNestedSubagents?: boolean;
|
|
134
169
|
extensions?: string[];
|
|
135
170
|
subagentOnlyExtensions?: string[];
|
|
171
|
+
requiredExtensions?: RequiredChildExtensionSnapshot;
|
|
136
172
|
mcpDirectTools?: string[];
|
|
137
173
|
cwd?: string;
|
|
138
174
|
requireReadTool?: boolean;
|
|
@@ -145,12 +181,21 @@ export interface ResolvePiLaunchToolPlanInput {
|
|
|
145
181
|
};
|
|
146
182
|
fast?: boolean;
|
|
147
183
|
model?: string;
|
|
148
|
-
modelCandidates?: readonly string[];
|
|
149
184
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
150
185
|
inheritedCapabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
151
186
|
agentName?: string;
|
|
152
187
|
permissionRules?: PermissionRules;
|
|
153
188
|
runtimeSnapshotHost?: McpRuntimeSnapshotHost;
|
|
189
|
+
/**
|
|
190
|
+
* When provided, child tool plans intersect known Pi core tool slots with
|
|
191
|
+
* this set. Core tools the agent declares but the host does not provide are
|
|
192
|
+
* omitted with a non-fatal warning (tracked in `unavailableHostBuiltins`).
|
|
193
|
+
* Review/scout lanes fail closed when a requested, still-permitted
|
|
194
|
+
* repository inspection tool is among those host omissions. Intentionally
|
|
195
|
+
* empty or ceiling-restricted allowlists are not a minimum-tool contract.
|
|
196
|
+
* Non-core names remain allowed and are validated in the child's registry.
|
|
197
|
+
*/
|
|
198
|
+
hostAvailableBuiltins?: readonly string[];
|
|
154
199
|
}
|
|
155
200
|
|
|
156
201
|
export interface PiLaunchToolPlan {
|
|
@@ -169,11 +214,14 @@ export interface PiLaunchToolPlan {
|
|
|
169
214
|
fanoutAuthorized: boolean;
|
|
170
215
|
runtimeExtensions: string[];
|
|
171
216
|
configuredExtensions: string[];
|
|
217
|
+
requiredExtensions: RequiredChildExtensionSnapshot;
|
|
172
218
|
extensionArgs: string[];
|
|
173
219
|
disableAmbientExtensions: boolean;
|
|
174
220
|
capabilityAudit?: SubagentCapabilityAudit;
|
|
175
221
|
/** Non-fatal launch warnings; they do not change behavior. */
|
|
176
222
|
warnings: string[];
|
|
223
|
+
/** Builtin tools the agent declared but the host runtime does not provide. */
|
|
224
|
+
unavailableHostBuiltins: string[];
|
|
177
225
|
}
|
|
178
226
|
|
|
179
227
|
function extensionIdentifier(value: string): string {
|
|
@@ -211,6 +259,7 @@ export function projectLaunchResolvedChildExtensions(
|
|
|
211
259
|
PiLaunchToolPlan,
|
|
212
260
|
| "runtimeExtensions"
|
|
213
261
|
| "configuredExtensions"
|
|
262
|
+
| "requiredExtensions"
|
|
214
263
|
| "extensionArgs"
|
|
215
264
|
| "disableAmbientExtensions"
|
|
216
265
|
>,
|
|
@@ -224,10 +273,12 @@ export function projectLaunchResolvedChildExtensions(
|
|
|
224
273
|
disableAmbientExtensions: toolPlan.disableAmbientExtensions,
|
|
225
274
|
runtime: runtime.ids,
|
|
226
275
|
configured: configured.ids,
|
|
276
|
+
required: toolPlan.requiredExtensions.map(({ id }) => id),
|
|
227
277
|
effective: effective.ids,
|
|
228
278
|
omitted: {
|
|
229
279
|
runtime: runtime.omitted,
|
|
230
280
|
configured: configured.omitted,
|
|
281
|
+
required: 0,
|
|
231
282
|
effective: effective.omitted,
|
|
232
283
|
},
|
|
233
284
|
};
|
|
@@ -289,6 +340,32 @@ export function resolvePermissionSystemExtension(): string | undefined {
|
|
|
289
340
|
return undefined;
|
|
290
341
|
}
|
|
291
342
|
|
|
343
|
+
/**
|
|
344
|
+
* Extract the names of builtin tools the host provides. Use this to pass
|
|
345
|
+
* `hostAvailableBuiltins` to `resolvePiLaunchToolPlan` so child tool plans
|
|
346
|
+
* intersect known core slots with what the host actually supports. Wrapped
|
|
347
|
+
* core slots count regardless of source; host-specific builtins count too.
|
|
348
|
+
*
|
|
349
|
+
* Returns `undefined` when builtin tool discovery fails or yields nothing,
|
|
350
|
+
* so callers skip the intersection (fail-safe to allowing all declared tools).
|
|
351
|
+
* This handles test mocks without proper tool registration and hosts whose
|
|
352
|
+
* getAllTools() throws before extensions load.
|
|
353
|
+
*/
|
|
354
|
+
export function getHostBuiltinToolNames(pi: Pick<ExtensionAPI, "getAllTools">): string[] | undefined {
|
|
355
|
+
try {
|
|
356
|
+
const builtins = pi
|
|
357
|
+
.getAllTools()
|
|
358
|
+
.filter((tool) => {
|
|
359
|
+
const source = (tool.sourceInfo as { source?: string } | undefined)?.source;
|
|
360
|
+
return source === "builtin" || PI_BUILTIN_TOOL_NAMES.has(tool.name);
|
|
361
|
+
})
|
|
362
|
+
.map((tool) => tool.name);
|
|
363
|
+
return builtins.length > 0 ? builtins : undefined;
|
|
364
|
+
} catch {
|
|
365
|
+
return undefined;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
292
369
|
export function resolvePiLaunchToolPlan(
|
|
293
370
|
input: ResolvePiLaunchToolPlanInput,
|
|
294
371
|
): PiLaunchToolPlan {
|
|
@@ -296,21 +373,35 @@ export function resolvePiLaunchToolPlan(
|
|
|
296
373
|
input.capabilityCeiling,
|
|
297
374
|
input.inheritedCapabilityCeiling,
|
|
298
375
|
);
|
|
376
|
+
const requiredExtensions = snapshotRequiredChildExtensions(input.requiredExtensions ?? []);
|
|
377
|
+
if (requiredExtensions.length > 0 && capabilityCeiling?.denyExtensions) {
|
|
378
|
+
throw new Error(`Capability ceiling from ${capabilityCeiling.sources.join(", ") || "unknown source"} denies extensions but this host requires: ${requiredExtensions.map(({ id }) => id).join(", ")}.`);
|
|
379
|
+
}
|
|
299
380
|
const allowedToolSet =
|
|
300
381
|
capabilityCeiling?.allowedTools === undefined
|
|
301
382
|
? undefined
|
|
302
383
|
: new Set(capabilityCeiling.allowedTools);
|
|
384
|
+
const hostAvailableSet =
|
|
385
|
+
input.hostAvailableBuiltins === undefined
|
|
386
|
+
? undefined
|
|
387
|
+
: new Set(input.hostAvailableBuiltins);
|
|
303
388
|
const requestedBuiltinTools =
|
|
304
389
|
input.tools?.filter(
|
|
305
390
|
(tool) =>
|
|
306
391
|
!(tool.includes("/") || tool.endsWith(".ts") || tool.endsWith(".js")),
|
|
307
392
|
) ?? [];
|
|
393
|
+
if (input.requireReadTool && hostAvailableSet && !hostAvailableSet.has("read")) {
|
|
394
|
+
const agentLabel = input.agentName ? ` for agent '${input.agentName}'` : "";
|
|
395
|
+
throw new Error(
|
|
396
|
+
`Host runtime does not provide required tool 'read'${agentLabel} for lazy skill loading.`,
|
|
397
|
+
);
|
|
398
|
+
}
|
|
308
399
|
if (input.requireReadTool && allowedToolSet && !allowedToolSet.has("read")) {
|
|
309
400
|
throw new Error(
|
|
310
401
|
`Capability ceiling from ${capabilityCeiling?.sources.join(", ") || "unknown source"} excludes required tool 'read' for lazy skill loading.`,
|
|
311
402
|
);
|
|
312
403
|
}
|
|
313
|
-
const
|
|
404
|
+
const ceilingFilteredBuiltinTools =
|
|
314
405
|
input.tools === undefined
|
|
315
406
|
? allowedToolSet
|
|
316
407
|
? [...allowedToolSet]
|
|
@@ -322,6 +413,12 @@ export function resolvePiLaunchToolPlan(
|
|
|
322
413
|
? ["read", ...requestedBuiltinTools]
|
|
323
414
|
: requestedBuiltinTools
|
|
324
415
|
).filter((tool) => !allowedToolSet || allowedToolSet.has(tool));
|
|
416
|
+
const declaredBuiltinTools = hostAvailableSet
|
|
417
|
+
? ceilingFilteredBuiltinTools.filter((tool) => !PI_BUILTIN_TOOL_NAMES.has(tool) || hostAvailableSet.has(tool))
|
|
418
|
+
: ceilingFilteredBuiltinTools;
|
|
419
|
+
const unavailableHostBuiltins = hostAvailableSet
|
|
420
|
+
? ceilingFilteredBuiltinTools.filter((tool) => PI_BUILTIN_TOOL_NAMES.has(tool) && !hostAvailableSet.has(tool))
|
|
421
|
+
: [];
|
|
325
422
|
const excludeTools = [...new Set((input.excludeTools ?? []).map((tool) => tool.trim()).filter(Boolean))];
|
|
326
423
|
const excludedToolSet = new Set(excludeTools);
|
|
327
424
|
const effectiveDeclaredBuiltinTools = declaredBuiltinTools.filter((tool) => !excludedToolSet.has(tool));
|
|
@@ -330,6 +427,9 @@ export function resolvePiLaunchToolPlan(
|
|
|
330
427
|
!excludedToolSet.has("subagent") &&
|
|
331
428
|
(!allowedToolSet || allowedToolSet.has("subagent"))
|
|
332
429
|
);
|
|
430
|
+
if (effectiveDeclaredBuiltinTools.includes("subagent_supervisor") && !fanoutAuthorized) {
|
|
431
|
+
throw new Error("Tool 'subagent_supervisor' requires fanout authorization: include 'subagent' in the effective tools allowlist or enable allowNestedSubagents.");
|
|
432
|
+
}
|
|
333
433
|
const toolExtensionPaths: string[] = capabilityCeiling?.denyExtensions
|
|
334
434
|
? []
|
|
335
435
|
: (input.tools ?? []).filter(
|
|
@@ -370,8 +470,8 @@ export function resolvePiLaunchToolPlan(
|
|
|
370
470
|
...internalTools,
|
|
371
471
|
]),
|
|
372
472
|
];
|
|
373
|
-
//
|
|
374
|
-
//
|
|
473
|
+
// Upward contact stays in the --tools allowlist but is not a strict
|
|
474
|
+
// requirement: children register contact_supervisor at runtime through
|
|
375
475
|
// the native supervisor channel (or pi-intercom). The pre-0.50 bridge always
|
|
376
476
|
// appended intercom alongside contact_supervisor, so that exact pairing is
|
|
377
477
|
// legacy plumbing, not a user demand for an external intercom provider;
|
|
@@ -392,7 +492,7 @@ export function resolvePiLaunchToolPlan(
|
|
|
392
492
|
? resolvePermissionSystemExtension()
|
|
393
493
|
: undefined;
|
|
394
494
|
if (input.fast && capabilityCeiling?.denyExtensions) throw new Error("fast mode requires a child runtime extension, but this launch denies extensions.");
|
|
395
|
-
const fastModeExtensions = resolveFastModeExtension({ fast: input.fast, model: input.model,
|
|
495
|
+
const fastModeExtensions = resolveFastModeExtension({ fast: input.fast, model: input.model, agentName: input.agentName });
|
|
396
496
|
const runtimeExtensions = [
|
|
397
497
|
PROMPT_RUNTIME_EXTENSION_PATH,
|
|
398
498
|
...fastModeExtensions,
|
|
@@ -419,7 +519,7 @@ export function resolvePiLaunchToolPlan(
|
|
|
419
519
|
...(input.extensions ?? []),
|
|
420
520
|
...(input.subagentOnlyExtensions ?? []),
|
|
421
521
|
];
|
|
422
|
-
const
|
|
522
|
+
const ordinaryExtensionArgs = disableAmbientExtensions
|
|
423
523
|
? [...new Set([...runtimeExtensions, ...configuredExtensions])]
|
|
424
524
|
: [
|
|
425
525
|
...new Set([
|
|
@@ -428,6 +528,8 @@ export function resolvePiLaunchToolPlan(
|
|
|
428
528
|
...(input.subagentOnlyExtensions ?? []),
|
|
429
529
|
]),
|
|
430
530
|
];
|
|
531
|
+
// Host-required paths have final precedence and cannot be removed by agent defaults or overrides.
|
|
532
|
+
const extensionArgs = [...new Set([...ordinaryExtensionArgs, ...requiredExtensions.map(({ path }) => path)])];
|
|
431
533
|
const requestedToolNames =
|
|
432
534
|
input.tools !== undefined
|
|
433
535
|
? [
|
|
@@ -437,6 +539,32 @@ export function resolvePiLaunchToolPlan(
|
|
|
437
539
|
]),
|
|
438
540
|
]
|
|
439
541
|
: undefined;
|
|
542
|
+
const missingPermittedRepositoryTools = input.tools !== undefined
|
|
543
|
+
? missingPermittedRepositoryInspectionTools(unavailableHostBuiltins, excludeTools)
|
|
544
|
+
: [];
|
|
545
|
+
if (missingPermittedRepositoryTools.length > 0 && isReviewOrScoutLaneAgent(input.agentName)) {
|
|
546
|
+
throw new Error(formatReviewLaneToolContractFailure({
|
|
547
|
+
agentName: input.agentName,
|
|
548
|
+
missingTools: missingPermittedRepositoryTools,
|
|
549
|
+
requestedTools: requestedToolNames,
|
|
550
|
+
effectiveTools: effectiveToolAllowlist,
|
|
551
|
+
ceilingSources: capabilityCeiling?.sources,
|
|
552
|
+
excludeTools,
|
|
553
|
+
}));
|
|
554
|
+
}
|
|
555
|
+
// Host pruning also happens without a ceiling (and therefore without an
|
|
556
|
+
// audit). Use the existing non-fatal launch warnings rather than inventing
|
|
557
|
+
// a ceiling or treating the requested allowlist as a minimum requirement.
|
|
558
|
+
if (unavailableHostBuiltins.length > 0) {
|
|
559
|
+
const subject = input.agentName ? `Agent '${input.agentName}'` : "Subagent";
|
|
560
|
+
warnings.push(
|
|
561
|
+
`${subject}: host runtime tool availability omitted [${unavailableHostBuiltins.join(", ")}]. `
|
|
562
|
+
+ `Requested tool names: ${requestedToolNames ? `[${requestedToolNames.join(", ")}]` : "not explicitly specified"}; effective tool allowlist: [${effectiveToolAllowlist.join(", ")}]. `
|
|
563
|
+
+ (capabilityCeiling ? `Active capability ceiling sources: [${capabilityCeiling.sources.join(", ") || "unknown source"}]. ` : "")
|
|
564
|
+
+ (excludeTools.length > 0 ? `Explicit excludeTools: [${excludeTools.join(", ")}]. ` : "")
|
|
565
|
+
+ "This is a non-fatal tool-plan diagnostic, not verification of the child's runtime tool menu.",
|
|
566
|
+
);
|
|
567
|
+
}
|
|
440
568
|
const capabilityAudit = capabilityCeiling
|
|
441
569
|
? ({
|
|
442
570
|
ceiling: capabilityCeiling,
|
|
@@ -474,6 +602,7 @@ export function resolvePiLaunchToolPlan(
|
|
|
474
602
|
capabilityCeilingAgentRestrictionSources(capabilityCeiling),
|
|
475
603
|
}
|
|
476
604
|
: {}),
|
|
605
|
+
...(unavailableHostBuiltins.length > 0 ? { unavailableHostBuiltins } : {}),
|
|
477
606
|
} satisfies SubagentCapabilityAudit)
|
|
478
607
|
: undefined;
|
|
479
608
|
return {
|
|
@@ -492,9 +621,11 @@ export function resolvePiLaunchToolPlan(
|
|
|
492
621
|
fanoutAuthorized,
|
|
493
622
|
runtimeExtensions,
|
|
494
623
|
configuredExtensions,
|
|
624
|
+
requiredExtensions,
|
|
495
625
|
extensionArgs,
|
|
496
626
|
disableAmbientExtensions,
|
|
497
627
|
warnings,
|
|
628
|
+
unavailableHostBuiltins,
|
|
498
629
|
...(capabilityAudit ? { capabilityAudit } : {}),
|
|
499
630
|
};
|
|
500
631
|
}
|
|
@@ -98,10 +98,12 @@ export function validateImplementationToolContract(input: {
|
|
|
98
98
|
const declaredMutationToolsWereRemoved = requestedMutationTools.length > 0 && !hasBuiltinMutationTool(input.tools);
|
|
99
99
|
const configuredExtensionCapability = (input.configuredExtensions?.length ?? 0) > 0 && !declaredMutationToolsWereRemoved;
|
|
100
100
|
if (hasMutationToolCapability(input.tools, input.mcpDirectTools) || configuredExtensionCapability) return undefined;
|
|
101
|
-
const intent = classifyTaskMutationIntent(input.agent, input.task);
|
|
101
|
+
const intent = classifyTaskMutationIntent(input.acceptanceRole === "writer" ? "worker" : input.agent, input.task);
|
|
102
102
|
if (intent.kind === "read-only") return undefined;
|
|
103
|
-
const writerTaskMayMutate =
|
|
104
|
-
|
|
103
|
+
const writerTaskMayMutate = input.acceptanceRole === "writer"
|
|
104
|
+
? true
|
|
105
|
+
: isWriterRole(input.agent, input.acceptanceRole)
|
|
106
|
+
&& (taskMayMutate(input.task) || WRITER_DELIVERY_PATTERN.test(input.task));
|
|
105
107
|
if (intent.kind !== "implementation" && !writerTaskMayMutate && !declaredMutationToolsWereRemoved) return undefined;
|
|
106
108
|
return `Agent '${input.agent}' was given an implementation task, but its tool allowlist has no mutation-capable tools. Add bash, edit, write, or another mutation-capable tool to the agent, or use a read-only task/agent.`;
|
|
107
109
|
}
|
|
@@ -46,12 +46,12 @@ const DYNAMIC_STEP_KEYS = new Set(["expand", "parallel", "collect", "concurrency
|
|
|
46
46
|
const RUNNER_DYNAMIC_STEP_KEYS = new Set([...DYNAMIC_STEP_KEYS, "effectiveAcceptance", "acceptanceInput", "acceptanceRole", "sessionFiles", "thinkingOverrides"]);
|
|
47
47
|
const DYNAMIC_EXPAND_KEYS = new Set(["from", "item", "key", "maxItems", "onEmpty"]);
|
|
48
48
|
const DYNAMIC_EXPAND_FROM_KEYS = new Set(["output", "path"]);
|
|
49
|
-
const DYNAMIC_PARALLEL_KEYS = new Set(["agent", "task", "phase", "label", "outputSchema", "cwd", "output", "outputMode", "reads", "progress", "skill", "model", "fast", "toolBudget", "acceptance", "agentContract", "gateOn"]);
|
|
49
|
+
const DYNAMIC_PARALLEL_KEYS = new Set(["agent", "task", "phase", "label", "outputSchema", "cwd", "machine", "output", "outputMode", "reads", "progress", "skill", "model", "fast", "toolBudget", "acceptance", "agentContract", "gateOn"]);
|
|
50
50
|
const RUNNER_DYNAMIC_PARALLEL_KEYS = new Set([
|
|
51
51
|
...DYNAMIC_PARALLEL_KEYS,
|
|
52
52
|
"outputName", "structured", "inheritProjectContext", "inheritGlobalContext", "inheritSkills", "skills", "outputPath", "namespaceOutputPath", "maxSubagentDepth", "timeoutMs", "waitToolEnabled", "waitToolDefaultTimeoutMs",
|
|
53
53
|
"structuredOutput", "structuredOutputSchema", "tools", "excludeTools", "allowNestedSubagents", "extensions", "subagentOnlyExtensions", "mcpDirectTools", "mutationTools", "capabilityCeiling", "completionGuard", "systemPrompt",
|
|
54
|
-
"systemPromptMode", "thinking", "
|
|
54
|
+
"systemPromptMode", "thinking", "requestedModel", "sessionFile", "effectiveAcceptance", "acceptanceInput", "acceptanceRole", "parentSessionId", "requiredExtensions", "launchResolvedExtensions", "requestedCwd", "machineEnv",
|
|
55
55
|
]);
|
|
56
56
|
const DYNAMIC_COLLECT_KEYS = new Set(["as", "outputSchema"]);
|
|
57
57
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AgentConfig } from "../../agents/agents.ts";
|
|
2
|
+
import { buildAgentMemoryInjection } from "../../agents/agent-memory.ts";
|
|
3
|
+
import { appendAgentRefinementOverlay } from "../../agents/agent-refinements.ts";
|
|
4
|
+
import { buildSkillInjection } from "../../agents/skills.ts";
|
|
5
|
+
import { injectOutputPathSystemPrompt } from "./single-output.ts";
|
|
6
|
+
|
|
7
|
+
export interface EffectiveSystemPromptInput {
|
|
8
|
+
/** Agent as handed to the child, including runtime-declared overlays such as the Intercom bridge. */
|
|
9
|
+
agent: AgentConfig;
|
|
10
|
+
resolvedSkills: Parameters<typeof buildSkillInjection>[0];
|
|
11
|
+
/** Directory that scopes memory and refinement lookups. */
|
|
12
|
+
cwd: string;
|
|
13
|
+
/** Omit when the caller injects the output path through another channel. */
|
|
14
|
+
outputPath?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function appendSection(prompt: string, section: string): string {
|
|
18
|
+
return prompt ? `${prompt}\n\n${section}` : section;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Child system prompt in the order preflight and every execution path hash:
|
|
23
|
+
* base prompt, skills, memory, refinement overlay, output path. Runtime
|
|
24
|
+
* acceptance prose is appended later and stays outside launch identity.
|
|
25
|
+
*/
|
|
26
|
+
export function buildEffectiveSystemPrompt(input: EffectiveSystemPromptInput): string {
|
|
27
|
+
let prompt = input.agent.systemPrompt?.trim() ?? "";
|
|
28
|
+
if (input.resolvedSkills.length > 0) prompt = appendSection(prompt, buildSkillInjection(input.resolvedSkills));
|
|
29
|
+
const memoryInjection = buildAgentMemoryInjection(input.agent, input.cwd);
|
|
30
|
+
if (memoryInjection) prompt = appendSection(prompt, memoryInjection);
|
|
31
|
+
prompt = appendAgentRefinementOverlay(prompt, { cwd: input.cwd, agentName: input.agent.name });
|
|
32
|
+
return injectOutputPathSystemPrompt(prompt, input.outputPath, input.agent);
|
|
33
|
+
}
|
|
@@ -2,7 +2,9 @@ import type {
|
|
|
2
2
|
ExternalCliReceiptMetadata,
|
|
3
3
|
ExternalCliCapabilityNarrowing,
|
|
4
4
|
ExternalCliRunnerStatus,
|
|
5
|
+
ExternalCliMachineStatus,
|
|
5
6
|
ExternalProcessStatus,
|
|
7
|
+
HerdrMachineReference,
|
|
6
8
|
} from "../../shared/types.ts";
|
|
7
9
|
|
|
8
10
|
const UNSUPPORTED = {
|
|
@@ -80,6 +82,7 @@ export function resolveExternalCliRunnerStatus(input: {
|
|
|
80
82
|
args?: string[];
|
|
81
83
|
promptDelivery?: "stdin";
|
|
82
84
|
capabilities?: ExternalCliCapabilityNarrowing;
|
|
85
|
+
machine?: HerdrMachineReference;
|
|
83
86
|
}): ExternalCliRunnerStatus {
|
|
84
87
|
const codexExec = input.adapter === "codex-exec";
|
|
85
88
|
const codexExecWriter = input.adapter === "codex-exec-writer";
|
|
@@ -101,6 +104,7 @@ export function resolveExternalCliRunnerStatus(input: {
|
|
|
101
104
|
...(claudeCodeWriter ? { safety: { access: "workspace-write" as const, authentication: "existing-cli-required" as const, permissionMode: "acceptEdits" as const, tools: "Read,Write,Edit,Glob,Grep" as const, mcp: "empty-strict" as const, settingSources: "user" as const, userSettingsTrust: "required" as const, sessionPersistence: false as const } } : {}),
|
|
102
105
|
...(cursorAgent ? { safety: { access: "read-only" as const, authentication: "cursor-api-key-or-existing-login" as const, mode: "ask" as const, sandbox: "enabled" as const, workspaceTrust: "existing-required" as const, sessionReuse: false as const } } : {}),
|
|
103
106
|
...(cursorAgentWriter ? { safety: { access: "workspace-write" as const, authentication: "cursor-api-key-or-existing-login" as const, mode: "print" as const, sandbox: "enabled" as const, workspaceTrust: "existing-required" as const, sessionReuse: false as const } } : {}),
|
|
107
|
+
...(input.machine ? { machine: input.machine } : {}),
|
|
104
108
|
capabilities: {
|
|
105
109
|
stop: true,
|
|
106
110
|
steer: false,
|
|
@@ -124,6 +128,9 @@ export function normalizeExternalCliRunnerStatus(value: unknown): ExternalCliRun
|
|
|
124
128
|
? input.args
|
|
125
129
|
: undefined;
|
|
126
130
|
const promptDelivery = input.promptDelivery === "stdin" ? "stdin" : undefined;
|
|
131
|
+
const machine = input.machine && typeof input.machine === "object" && !Array.isArray(input.machine)
|
|
132
|
+
? input.machine as HerdrMachineReference
|
|
133
|
+
: undefined;
|
|
127
134
|
const adapterId = input.adapter && typeof input.adapter === "object" && !Array.isArray(input.adapter)
|
|
128
135
|
? (input.adapter as Record<string, unknown>).id
|
|
129
136
|
: undefined;
|
|
@@ -131,6 +138,7 @@ export function normalizeExternalCliRunnerStatus(value: unknown): ExternalCliRun
|
|
|
131
138
|
return {
|
|
132
139
|
type: "external-cli",
|
|
133
140
|
command: input.command,
|
|
141
|
+
...(machine ? { machine } : {}),
|
|
134
142
|
args: args ?? [],
|
|
135
143
|
promptDelivery: "prompt-file",
|
|
136
144
|
adapter: { id: "grok-build", version: 1, executionMode: "one-shot-prompt-file" },
|
|
@@ -140,7 +148,7 @@ export function normalizeExternalCliRunnerStatus(value: unknown): ExternalCliRun
|
|
|
140
148
|
};
|
|
141
149
|
}
|
|
142
150
|
const adapter = isCodeOwnedExternalCliAdapterId(adapterId) ? adapterId : undefined;
|
|
143
|
-
return resolveExternalCliRunnerStatus({ ...(adapter ? { adapter } : {}), command: input.command, ...(args ? { args } : {}), ...(promptDelivery ? { promptDelivery } : {}) });
|
|
151
|
+
return resolveExternalCliRunnerStatus({ ...(adapter ? { adapter } : {}), command: input.command, ...(args ? { args } : {}), ...(promptDelivery ? { promptDelivery } : {}), ...(machine ? { machine } : {}) });
|
|
144
152
|
}
|
|
145
153
|
|
|
146
154
|
export function externalCliReceiptMetadata(input: {
|
|
@@ -149,9 +157,11 @@ export function externalCliReceiptMetadata(input: {
|
|
|
149
157
|
outputReference?: string;
|
|
150
158
|
}): ExternalCliReceiptMetadata {
|
|
151
159
|
const { runner } = input;
|
|
160
|
+
const machine: ExternalCliMachineStatus | undefined = input.externalProcess?.machine ?? runner.machine;
|
|
152
161
|
return {
|
|
153
162
|
adapter: { ...runner.adapter },
|
|
154
163
|
capabilities: { ...runner.capabilities },
|
|
164
|
+
...(machine ? { machine: { ...machine, ...(machine.remoteGit ? { remoteGit: { ...machine.remoteGit } } : {}) } } : {}),
|
|
155
165
|
...(runner.safety ? { safety: { ...runner.safety } } : {}),
|
|
156
166
|
...(input.externalProcess ? {
|
|
157
167
|
outputArtifacts: {
|
|
@@ -4,6 +4,7 @@ import * as path from "node:path";
|
|
|
4
4
|
|
|
5
5
|
const MAX_PROBE_OUTPUT_BYTES = 256 * 1024;
|
|
6
6
|
const MAX_PROBE_TIMEOUT_MS = 5_000;
|
|
7
|
+
const MAX_REMOTE_PROBE_TIMEOUT_MS = 15_000;
|
|
7
8
|
const MAX_CACHE_ENTRIES = 64;
|
|
8
9
|
const MAX_AVAILABILITY_REASON_LENGTH = 256;
|
|
9
10
|
|
|
@@ -16,6 +17,8 @@ export interface ExternalCliPreflightSpec {
|
|
|
16
17
|
evidenceArgs?: readonly string[];
|
|
17
18
|
evidenceLabel?: string;
|
|
18
19
|
probeTimeoutMs?: number;
|
|
20
|
+
/** Remote probes include SSH handshakes; values may only narrow the code-owned remote ceiling. */
|
|
21
|
+
remote?: boolean;
|
|
19
22
|
validate?: (result: ExternalCliPreflightResult) => void;
|
|
20
23
|
}
|
|
21
24
|
|
|
@@ -90,7 +93,7 @@ function narrowPositiveInteger(value: number | undefined, ceiling: number, label
|
|
|
90
93
|
}
|
|
91
94
|
|
|
92
95
|
function specKey(spec: ExternalCliPreflightSpec): string {
|
|
93
|
-
return JSON.stringify([spec.id, spec.versionArgs, spec.helpArgs, spec.evidenceArgs, spec.evidenceLabel, spec.probeTimeoutMs]);
|
|
96
|
+
return JSON.stringify([spec.id, spec.versionArgs, spec.helpArgs, spec.evidenceArgs, spec.evidenceLabel, spec.probeTimeoutMs, spec.remote === true]);
|
|
94
97
|
}
|
|
95
98
|
|
|
96
99
|
export function preflightExternalCli(command: string, spec: ExternalCliPreflightSpec, env: NodeJS.ProcessEnv, cwd?: string): ExternalCliPreflightResult {
|
|
@@ -99,7 +102,8 @@ export function preflightExternalCli(command: string, spec: ExternalCliPreflight
|
|
|
99
102
|
const lookupKey = JSON.stringify([binaryPath, binaryMtimeMs, specKey(spec)]);
|
|
100
103
|
const cachedKey = lookup.get(lookupKey);
|
|
101
104
|
const cached = cachedKey ? cache.get(cachedKey) : undefined;
|
|
102
|
-
const
|
|
105
|
+
const ceiling = spec.remote === true ? MAX_REMOTE_PROBE_TIMEOUT_MS : MAX_PROBE_TIMEOUT_MS;
|
|
106
|
+
const probeTimeoutMs = narrowPositiveInteger(spec.probeTimeoutMs, ceiling, "probeTimeoutMs");
|
|
103
107
|
const base = cached ?? {
|
|
104
108
|
binaryPath,
|
|
105
109
|
binaryMtimeMs,
|
|
@@ -3,7 +3,7 @@ import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
|
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import { finished } from "node:stream/promises";
|
|
6
|
-
import type { ExternalProcessStatus } from "../../shared/types.ts";
|
|
6
|
+
import type { ExternalProcessStatus, ProcessTreeTerminal } from "../../shared/types.ts";
|
|
7
7
|
import { createOwnedProcessTreeController, type OwnedProcessTreeController } from "../background/owned-process-tree.ts";
|
|
8
8
|
import { omitExtensionBindingsEnv } from "./extension-bindings.ts";
|
|
9
9
|
import {
|
|
@@ -141,7 +141,7 @@ function classifyInvalidation(error: string): "auth" | "permission" | "launch" {
|
|
|
141
141
|
return "launch";
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
function terminateExternalProcessTree(pid: number, controller: OwnedProcessTreeController): Promise<
|
|
144
|
+
function terminateExternalProcessTree(pid: number, controller: OwnedProcessTreeController): Promise<ProcessTreeTerminal> {
|
|
145
145
|
if (process.platform !== "win32") return controller.terminate();
|
|
146
146
|
return new Promise((resolve) => {
|
|
147
147
|
const cleanup = spawn("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
|
|
@@ -239,7 +239,7 @@ export function runExternalCli(input: {
|
|
|
239
239
|
let settled = false;
|
|
240
240
|
let processTree: OwnedProcessTreeController | undefined;
|
|
241
241
|
let processPid: number | undefined;
|
|
242
|
-
let termination: Promise<
|
|
242
|
+
let termination: Promise<ProcessTreeTerminal> | undefined;
|
|
243
243
|
const flushProgress = () => {
|
|
244
244
|
if (!latestProgress) return;
|
|
245
245
|
input.onParserProgress?.(latestProgress);
|
|
@@ -381,8 +381,7 @@ export function runExternalCli(input: {
|
|
|
381
381
|
input.registerTimeout?.(undefined);
|
|
382
382
|
input.registerStop?.(undefined);
|
|
383
383
|
void (async () => {
|
|
384
|
-
|
|
385
|
-
else if (processTree) await processTree.finishAfterWriterClose();
|
|
384
|
+
const treeProof = termination ? await termination : processTree ? await processTree.finishAfterWriterClose() : undefined;
|
|
386
385
|
const endedAt = Date.now();
|
|
387
386
|
const externalProcess: ExternalProcessStatus = {
|
|
388
387
|
...initialProcess,
|
|
@@ -398,15 +397,18 @@ export function runExternalCli(input: {
|
|
|
398
397
|
input.onProcess?.(externalProcess);
|
|
399
398
|
const stderr = stderrTail.text().trim();
|
|
400
399
|
const parserFailure = parserError?.message ?? (parserTerminal?.state === "failed" ? parserTerminal.error ?? "External CLI parser reported terminal failure." : undefined);
|
|
400
|
+
const treeFailure = process.platform !== "win32" && treeProof?.state === "unknown"
|
|
401
|
+
? `Process-tree cleanup failed: ${treeProof.reason}.`
|
|
402
|
+
: undefined;
|
|
401
403
|
const error = stopped
|
|
402
404
|
? input.stopMessage ?? "Subagent stopped by user."
|
|
403
405
|
: timedOut
|
|
404
406
|
? input.timeoutMessage ?? "Subagent timed out."
|
|
405
|
-
: spawnError?.message ?? parserFailure ?? (exitCode === 0 ? undefined : stderr || `External CLI exited with code ${exitCode}.`);
|
|
407
|
+
: spawnError?.message ?? parserFailure ?? treeFailure ?? (exitCode === 0 ? undefined : stderr || `External CLI exited with code ${exitCode}.`);
|
|
406
408
|
if (error && input.preflight && !parserError) invalidateExternalCliPreflight(input.command, input.preflight, classifyInvalidation(error));
|
|
407
409
|
const result: ExternalCliRunResult = {
|
|
408
410
|
output: (!parserError && parserTerminal?.state === "completed" ? parserTerminal.output ?? "" : stdoutTail.text()).trim(),
|
|
409
|
-
exitCode: timedOut || stopped || spawnError || parserFailure ? 1 : exitCode,
|
|
411
|
+
exitCode: timedOut || stopped || spawnError || parserFailure || treeFailure ? 1 : exitCode,
|
|
410
412
|
...(error ? { error } : {}),
|
|
411
413
|
...(timedOut ? { timedOut: true } : {}),
|
|
412
414
|
...(stopped ? { stopped: true } : {}),
|