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
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import * as fs from "node:fs";
|
|
8
8
|
import * as path from "node:path";
|
|
9
|
-
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
10
9
|
import type { ChildWatchdogConfig, ChildWatchdogStatusEvent } from "../../watchdog/child-status.ts";
|
|
11
10
|
import type { ThinkingLevel } from "../../shared/model-info.ts";
|
|
12
11
|
import { intersectThinkingCeilings } from "../../shared/thinking-ceiling.ts";
|
|
@@ -15,6 +14,7 @@ import {
|
|
|
15
14
|
type LaunchResolvedChildExtensions,
|
|
16
15
|
type ResolvedToolBudget,
|
|
17
16
|
type RunFanoutBudgetDescriptor,
|
|
17
|
+
type HerdrMachineReference,
|
|
18
18
|
} from "../../shared/types.ts";
|
|
19
19
|
import type { NestedPathEntry } from "./nested-path.ts";
|
|
20
20
|
import type { McpRuntimeSnapshotHost } from "./mcp-direct-tool-allowlist.ts";
|
|
@@ -35,6 +35,8 @@ import type { ChildRuntimeConfig } from "./child-runtime-config.ts";
|
|
|
35
35
|
import { createCapturedChildHooks, withChildSessionErrorReporting } from "./child-hooks.ts";
|
|
36
36
|
import type { ChildTranscriptWriter } from "../../shared/child-transcript.ts";
|
|
37
37
|
import type { ChildSessionLaunch, ChildSessionStorage } from "./child-session.ts";
|
|
38
|
+
import type { ArbiterModelContext } from "./llm-intent-arbiter.ts";
|
|
39
|
+
import { resolveRequiredChildExtensions, type RequiredChildExtensionSnapshot } from "../../shared/required-child-extensions.ts";
|
|
38
40
|
|
|
39
41
|
/** Environment variable pi-mcp-adapter reads for the tools a child may expose. */
|
|
40
42
|
export const MCP_DIRECT_TOOLS_ENV = "MCP_DIRECT_TOOLS";
|
|
@@ -44,7 +46,7 @@ export const MCP_DIRECT_TOOLS_ENV = "MCP_DIRECT_TOOLS";
|
|
|
44
46
|
* launches inherits. Serialized into the background runner config; the
|
|
45
47
|
* foreground path passes the executor's full `ChildRuntimeConfig`.
|
|
46
48
|
*/
|
|
47
|
-
export type InheritedChildRuntime = Pick<ChildRuntimeConfig, "depth" | "maxDepth" | "nestedRoute" | "nestedParent" | "capabilityCeiling" | "thinkingCeiling" | "runFanoutBudget">;
|
|
49
|
+
export type InheritedChildRuntime = Pick<ChildRuntimeConfig, "depth" | "maxDepth" | "nestedRoute" | "nestedParent" | "capabilityCeiling" | "thinkingCeiling" | "runFanoutBudget" | "requiredExtensions">;
|
|
48
50
|
|
|
49
51
|
export function inheritedChildRuntime(config: ChildRuntimeConfig | undefined): InheritedChildRuntime | undefined {
|
|
50
52
|
if (!config) return undefined;
|
|
@@ -56,10 +58,14 @@ export function inheritedChildRuntime(config: ChildRuntimeConfig | undefined): I
|
|
|
56
58
|
...(config.capabilityCeiling ? { capabilityCeiling: config.capabilityCeiling } : {}),
|
|
57
59
|
...(config.thinkingCeiling ? { thinkingCeiling: config.thinkingCeiling } : {}),
|
|
58
60
|
...(config.runFanoutBudget ? { runFanoutBudget: config.runFanoutBudget } : {}),
|
|
61
|
+
...(config.requiredExtensions ? { requiredExtensions: config.requiredExtensions } : {}),
|
|
59
62
|
};
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
export interface BuildInProcessChildLaunchInput {
|
|
66
|
+
machine?: HerdrMachineReference;
|
|
67
|
+
remoteSkillNames?: string[];
|
|
68
|
+
remoteReads?: string[] | false;
|
|
63
69
|
parentSessionId?: string;
|
|
64
70
|
forkCacheKey?: string;
|
|
65
71
|
sessionEnabled: boolean;
|
|
@@ -76,6 +82,8 @@ export interface BuildInProcessChildLaunchInput {
|
|
|
76
82
|
excludeTools?: string[];
|
|
77
83
|
extensions?: string[];
|
|
78
84
|
subagentOnlyExtensions?: string[];
|
|
85
|
+
/** Serialized launch snapshot; omitted only for a top-level parent-process lookup. */
|
|
86
|
+
requiredExtensions?: RequiredChildExtensionSnapshot;
|
|
79
87
|
systemPrompt?: string | null;
|
|
80
88
|
mcpDirectTools?: string[];
|
|
81
89
|
extensionBindings?: ExtensionBindings;
|
|
@@ -90,7 +98,6 @@ export interface BuildInProcessChildLaunchInput {
|
|
|
90
98
|
runFanoutBudget?: RunFanoutBudgetDescriptor;
|
|
91
99
|
structuredOutput?: StructuredOutputRuntime;
|
|
92
100
|
fast?: boolean;
|
|
93
|
-
modelCandidates?: readonly string[];
|
|
94
101
|
toolBudget?: ResolvedToolBudget;
|
|
95
102
|
permissionRules?: PermissionRules;
|
|
96
103
|
permissionAuditPath?: string;
|
|
@@ -112,13 +119,21 @@ export interface BuildInProcessChildLaunchInput {
|
|
|
112
119
|
* them and exposes the child environment external extensions read.
|
|
113
120
|
*/
|
|
114
121
|
host: "parent" | "runner";
|
|
122
|
+
/**
|
|
123
|
+
* Pi core tool names the host runtime provides. When set, child tool plans
|
|
124
|
+
* intersect known core slots with this set; declared non-core names remain
|
|
125
|
+
* for child startup validation. Review/scout lanes fail closed when a requested,
|
|
126
|
+
* still-permitted repository inspection tool is missing from that set.
|
|
127
|
+
*/
|
|
128
|
+
hostAvailableBuiltins?: readonly string[];
|
|
115
129
|
}
|
|
116
130
|
|
|
117
131
|
export interface InProcessChildCapture {
|
|
118
|
-
completionIntentContext?():
|
|
132
|
+
completionIntentContext?(): ArbiterModelContext | undefined;
|
|
119
133
|
structuredOutput(): { called: boolean; value?: unknown; acceptanceReport?: unknown; acceptanceReportProvided: boolean };
|
|
120
134
|
toolDiagnostic(): ChildToolDiagnostic | undefined;
|
|
121
135
|
runtimeAcknowledgedExtensions(): RuntimeAcknowledgedChildExtensions | undefined;
|
|
136
|
+
finalDrainHeld(): boolean;
|
|
122
137
|
}
|
|
123
138
|
|
|
124
139
|
export interface InProcessChildLaunch {
|
|
@@ -174,24 +189,26 @@ function childStorage(input: BuildInProcessChildLaunchInput): ChildSessionStorag
|
|
|
174
189
|
}
|
|
175
190
|
|
|
176
191
|
export function buildInProcessChildLaunch(input: BuildInProcessChildLaunchInput): InProcessChildLaunch {
|
|
192
|
+
const requiredExtensions = input.requiredExtensions ?? input.inherited?.requiredExtensions ?? resolveRequiredChildExtensions(input.parentSessionId);
|
|
177
193
|
const toolPlan = resolvePiLaunchToolPlan({
|
|
178
194
|
tools: input.tools,
|
|
179
195
|
excludeTools: input.excludeTools,
|
|
180
196
|
allowNestedSubagents: input.allowNestedSubagents,
|
|
181
197
|
extensions: input.extensions,
|
|
182
198
|
subagentOnlyExtensions: input.subagentOnlyExtensions,
|
|
199
|
+
requiredExtensions,
|
|
183
200
|
mcpDirectTools: input.mcpDirectTools,
|
|
184
201
|
cwd: input.cwd,
|
|
185
202
|
requireReadTool: input.requireReadTool,
|
|
186
203
|
structuredOutput: Boolean(input.structuredOutput),
|
|
187
204
|
fast: input.fast,
|
|
188
205
|
model: input.model,
|
|
189
|
-
modelCandidates: input.modelCandidates,
|
|
190
206
|
capabilityCeiling: input.capabilityCeiling,
|
|
191
207
|
inheritedCapabilityCeiling: inheritedCapabilityCeiling(input.inherited),
|
|
192
208
|
agentName: input.childAgentName,
|
|
193
209
|
permissionRules: input.permissionRules,
|
|
194
210
|
runtimeSnapshotHost: input.runtimeSnapshotHost,
|
|
211
|
+
hostAvailableBuiltins: input.hostAvailableBuiltins,
|
|
195
212
|
});
|
|
196
213
|
|
|
197
214
|
const inherited = input.inherited;
|
|
@@ -239,6 +256,7 @@ export function buildInProcessChildLaunch(input: BuildInProcessChildLaunchInput)
|
|
|
239
256
|
maxDepth: childDepth.maxDepth,
|
|
240
257
|
...(toolPlan.capabilityCeiling ? { capabilityCeiling: toolPlan.capabilityCeiling } : {}),
|
|
241
258
|
...(thinkingCeiling ? { thinkingCeiling } : {}),
|
|
259
|
+
...(toolPlan.requiredExtensions.length > 0 ? { requiredExtensions: toolPlan.requiredExtensions } : {}),
|
|
242
260
|
inheritProjectContext: input.inheritProjectContext,
|
|
243
261
|
inheritGlobalContext: input.inheritGlobalContext,
|
|
244
262
|
inheritSkills: input.inheritSkills,
|
|
@@ -280,17 +298,21 @@ export function buildInProcessChildLaunch(input: BuildInProcessChildLaunchInput)
|
|
|
280
298
|
configuredExtensions: toolPlan.configuredExtensions,
|
|
281
299
|
extensionArgs: toolPlan.extensionArgs,
|
|
282
300
|
disableAmbientExtensions: !ambientExtensions,
|
|
301
|
+
requiredExtensions: toolPlan.requiredExtensions,
|
|
283
302
|
});
|
|
284
303
|
const taggedPrompt = input.systemPrompt !== undefined && input.systemPrompt !== null
|
|
285
304
|
? `<active_agent name="${escapeXmlAttr(input.childAgentName)}"/>\n\n${input.systemPrompt}`
|
|
286
305
|
: undefined;
|
|
287
306
|
const session: Omit<ChildSessionLaunch, "onExtensionError"> = {
|
|
288
307
|
cwd: input.cwd,
|
|
308
|
+
...(input.machine ? { machine: input.machine } : {}),
|
|
309
|
+
...(input.machine ? { remoteResources: { agent: input.childAgentName, ...(input.remoteSkillNames ? { skills: input.remoteSkillNames } : {}), ...(input.remoteReads !== undefined ? { reads: input.remoteReads } : {}), ...(toolPlan.explicitToolAllowlist ? { toolCeiling: [...toolPlan.effectiveToolAllowlist] } : toolPlan.capabilityCeiling?.allowedTools ? { toolCeiling: [...toolPlan.capabilityCeiling.allowedTools] } : {}) } } : {}),
|
|
289
310
|
storage: childStorage(input),
|
|
290
311
|
...(input.model ? { model: input.model } : {}),
|
|
291
312
|
...(toolPlan.explicitToolAllowlist ? { tools: toolPlan.effectiveToolAllowlist } : {}),
|
|
292
313
|
...(!toolPlan.explicitToolAllowlist && toolPlan.excludeTools.length > 0 ? { excludeTools: toolPlan.excludeTools } : {}),
|
|
293
314
|
extensionPaths,
|
|
315
|
+
requiredExtensions: toolPlan.requiredExtensions,
|
|
294
316
|
ambientExtensions,
|
|
295
317
|
hooks: capturedHooks.hooks,
|
|
296
318
|
...(input.host === "runner" ? { processEnv: childProcessEnv(input, toolPlan) } : {}),
|
|
@@ -311,6 +333,7 @@ export function buildInProcessChildLaunch(input: BuildInProcessChildLaunchInput)
|
|
|
311
333
|
structuredOutput: () => ({ called: structuredCalled, value: structuredValue, acceptanceReport: structuredAcceptanceReport, acceptanceReportProvided: structuredAcceptanceProvided }),
|
|
312
334
|
toolDiagnostic: capturedHooks.toolDiagnostic,
|
|
313
335
|
runtimeAcknowledgedExtensions: capturedHooks.runtimeAcknowledgedExtensions,
|
|
336
|
+
finalDrainHeld: capturedHooks.finalDrainHeld,
|
|
314
337
|
},
|
|
315
338
|
launchResolvedExtensions,
|
|
316
339
|
warnings: toolPlan.warnings,
|
|
@@ -14,11 +14,14 @@ export function projectChildLifecycle(event: { type?: string; willRetry?: unknow
|
|
|
14
14
|
if (state) state.compactionRetryActive = event.willRetry === true;
|
|
15
15
|
return event.willRetry === true ? "cancel-drain" : "none";
|
|
16
16
|
}
|
|
17
|
-
if (event.type === "agent_start" || event.type === "auto_retry_start") {
|
|
17
|
+
if (event.type === "agent_start" || event.type === "auto_retry_start" || event.type === "turn_start") {
|
|
18
18
|
if (state) state.compactionRetryActive = false;
|
|
19
|
+
return "cancel-drain";
|
|
20
|
+
}
|
|
21
|
+
if (event.type === "agent_end") {
|
|
22
|
+
if (event.willRetry !== true && state) state.compactionRetryActive = false;
|
|
23
|
+
return "cancel-drain";
|
|
19
24
|
}
|
|
20
|
-
if (event.type === "agent_end" && event.willRetry === true) return "cancel-drain";
|
|
21
|
-
if (event.type === "agent_end" && state) state.compactionRetryActive = false;
|
|
22
25
|
if (event.type === "agent_settled") return state?.compactionRetryActive ? "none" : "start-drain";
|
|
23
26
|
if (terminalAssistantStop) return "start-drain";
|
|
24
27
|
return "none";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { JsonSchemaObject, ResolvedToolBudget, RunFanoutBudgetDescriptor } from "../../shared/types.ts";
|
|
1
|
+
import type { JsonSchemaObject, ResolvedToolBudget, RunFanoutBudgetDescriptor, SubagentState } from "../../shared/types.ts";
|
|
2
2
|
import type { ThinkingLevel } from "../../shared/model-info.ts";
|
|
3
3
|
import type { NestedPathEntry } from "./nested-path.ts";
|
|
4
4
|
import type { PermissionRules } from "./permissions.ts";
|
|
@@ -6,6 +6,7 @@ import type { ChildWatchdogConfig, ChildWatchdogStatusEvent } from "../../watchd
|
|
|
6
6
|
import type { ResolvedWaitToolConfig } from "../background/wait-config.ts";
|
|
7
7
|
import type { ChildToolDiagnostic } from "./tool-availability.ts";
|
|
8
8
|
import type { ResolvedSubagentCapabilityCeiling } from "./capability-ceiling.ts";
|
|
9
|
+
import type { RequiredChildExtensionSnapshot } from "../../shared/required-child-extensions.ts";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Set in processes that host child sessions (the async runner). The extension
|
|
@@ -75,6 +76,8 @@ export interface ChildRuntimeConfig {
|
|
|
75
76
|
depth: number;
|
|
76
77
|
maxDepth?: number;
|
|
77
78
|
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
79
|
+
/** Immutable root-parent host policy propagated to nested native launches. */
|
|
80
|
+
requiredExtensions?: RequiredChildExtensionSnapshot;
|
|
78
81
|
thinkingCeiling?: ThinkingLevel;
|
|
79
82
|
inheritProjectContext?: boolean;
|
|
80
83
|
inheritGlobalContext?: boolean;
|
|
@@ -86,6 +89,10 @@ export interface ChildRuntimeConfig {
|
|
|
86
89
|
/** Receives child watchdog status events. */
|
|
87
90
|
watchdogStatus?: (event: ChildWatchdogStatusEvent) => void;
|
|
88
91
|
waitTool: ResolvedWaitToolConfig;
|
|
92
|
+
runtimeState?: SubagentState;
|
|
93
|
+
holdFinalDrain?: (held: boolean) => void;
|
|
94
|
+
/** Installation-local downward owner-channel barrier; never inherited or serialized into descendants. */
|
|
95
|
+
hasPendingSupervisorRequest?: () => boolean;
|
|
89
96
|
structuredOutput?: ChildStructuredOutput;
|
|
90
97
|
requiredTools?: string[];
|
|
91
98
|
mcpDirectTools?: string[];
|
|
@@ -5,21 +5,15 @@
|
|
|
5
5
|
* parent pi process for foreground children, the detached runner process for
|
|
6
6
|
* background children. The factory is injectable so tests can script a child
|
|
7
7
|
* without the real runtime; the default implementation wraps
|
|
8
|
-
* `createAgentSession` from a pi package module
|
|
9
|
-
* across every child it creates.
|
|
8
|
+
* `createAgentSession` from a pi package module.
|
|
10
9
|
*/
|
|
11
10
|
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
12
11
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
12
|
+
import { pinChildCacheRetention } from "../../shared/child-cache-retention.ts";
|
|
13
13
|
import { getAgentDir } from "../../shared/utils.ts";
|
|
14
14
|
import type { ChildRuntimeConfig } from "./child-runtime-config.ts";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
// Private runtime authority for host continuation planning; injected factories have none.
|
|
19
|
-
const readonlyModels = new WeakMap<ChildSession, { current: ModelInfo; resolve(reference: string): ModelInfo | undefined; requestBytes: number }>();
|
|
20
|
-
export function getReadonlyChildModels(child: ChildSession) {
|
|
21
|
-
return readonlyModels.get(child);
|
|
22
|
-
}
|
|
15
|
+
import type { RequiredChildExtensionSnapshot } from "../../shared/required-child-extensions.ts";
|
|
16
|
+
import type { HerdrMachineReference, HerdrRemoteGitStatus } from "../../shared/types.ts";
|
|
23
17
|
|
|
24
18
|
export interface ChildSessionEvent {
|
|
25
19
|
type: string;
|
|
@@ -54,6 +48,12 @@ export type ChildSessionStorage =
|
|
|
54
48
|
|
|
55
49
|
export interface ChildSessionLaunch {
|
|
56
50
|
cwd: string;
|
|
51
|
+
/** Resolved pane-native placement. Local launches omit this field. */
|
|
52
|
+
machine?: HerdrMachineReference;
|
|
53
|
+
/** Process-local provider source owned by the invoking foreground parent. */
|
|
54
|
+
parentProviderRegistry?: ParentProviderRegistry;
|
|
55
|
+
/** Logical names resolved only by the remote ambient package. */
|
|
56
|
+
remoteResources?: { agent: string; skills?: string[]; toolCeiling?: string[]; reads?: string[] | false };
|
|
57
57
|
storage: ChildSessionStorage;
|
|
58
58
|
/** Model reference as the agent config names it (`provider/id`, optionally `:thinking`). */
|
|
59
59
|
model?: string;
|
|
@@ -62,6 +62,8 @@ export interface ChildSessionLaunch {
|
|
|
62
62
|
excludeTools?: string[];
|
|
63
63
|
/** Extension files loaded for this child in addition to the inline hooks. */
|
|
64
64
|
extensionPaths: string[];
|
|
65
|
+
/** Canonical required paths and safe evidence identities for fail-closed loading. */
|
|
66
|
+
requiredExtensions?: RequiredChildExtensionSnapshot;
|
|
65
67
|
/**
|
|
66
68
|
* Discover the ambient extensions (agent dir, project, settings) the way a
|
|
67
69
|
* `pi` process would. False loads only `extensionPaths` and `hooks`.
|
|
@@ -93,16 +95,29 @@ export interface ChildSession {
|
|
|
93
95
|
abort(): Promise<void>;
|
|
94
96
|
/** Emits `session_shutdown` to the child's extensions and disposes the session; resolves once that shutdown work is done. */
|
|
95
97
|
dispose(): Promise<void>;
|
|
98
|
+
/** True while Pi still has steering or follow-up input that has not started a turn. */
|
|
99
|
+
hasQueuedMessages?(): boolean;
|
|
96
100
|
readonly messages: readonly AgentMessage[];
|
|
97
101
|
readonly sessionFile: string | undefined;
|
|
98
102
|
readonly sessionId: string;
|
|
99
103
|
readonly modelId: string | undefined;
|
|
104
|
+
readonly machineEvidence?: { machineId: string; initial?: HerdrRemoteGitStatus; final?: HerdrRemoteGitStatus };
|
|
105
|
+
/** Event-updated pane-native status; reading it performs no network work. */
|
|
106
|
+
readonly placementSnapshot?: unknown;
|
|
100
107
|
/** Set by the foreground host once the run detached; `factory.dispose()` leaves such children running. */
|
|
101
108
|
detached?: boolean;
|
|
102
109
|
/** Set by `factory.dispose()` before it aborts the child, so the host can report the stop truthfully. */
|
|
103
110
|
shutDown?: boolean;
|
|
104
111
|
}
|
|
105
112
|
|
|
113
|
+
export function childSessionHasQueuedMessages(session: ChildSession | undefined): boolean {
|
|
114
|
+
try {
|
|
115
|
+
return session?.hasQueuedMessages?.() === true;
|
|
116
|
+
} catch {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
106
121
|
export interface ChildSessionFactory {
|
|
107
122
|
create(launch: ChildSessionLaunch): Promise<ChildSession>;
|
|
108
123
|
/** Abort and dispose every live attached child; detached children keep running and hold the shared runtime. */
|
|
@@ -124,6 +139,49 @@ export interface DefaultChildSessionFactoryOptions {
|
|
|
124
139
|
|
|
125
140
|
type ModelRuntimeInstance = Awaited<ReturnType<PiCodingAgentModule["ModelRuntime"]["create"]>>;
|
|
126
141
|
|
|
142
|
+
export type ParentProviderRegistry = Pick<ModelRuntimeInstance, "getRegisteredProviderIds" | "getRegisteredProviderConfig" | "getRegisteredNativeProvider">;
|
|
143
|
+
|
|
144
|
+
function inheritParentProviders(modelRuntime: ModelRuntimeInstance, parentProviders: ParentProviderRegistry, claimedProviderIds: ReadonlySet<string>, onError: ((error: ChildSessionExtensionError) => void) | undefined): boolean {
|
|
145
|
+
let providerIds: readonly string[];
|
|
146
|
+
try {
|
|
147
|
+
providerIds = parentProviders.getRegisteredProviderIds();
|
|
148
|
+
} catch (error) {
|
|
149
|
+
onError?.({ extensionPath: "<parent-providers>", event: "inherit_provider", error });
|
|
150
|
+
throw new Error(`Failed to enumerate parent providers: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
151
|
+
}
|
|
152
|
+
let registered = false;
|
|
153
|
+
for (const providerId of new Set(providerIds)) {
|
|
154
|
+
if (claimedProviderIds.has(providerId)) continue;
|
|
155
|
+
try {
|
|
156
|
+
const native = parentProviders.getRegisteredNativeProvider(providerId);
|
|
157
|
+
const config = native ? undefined : parentProviders.getRegisteredProviderConfig(providerId);
|
|
158
|
+
if (native) modelRuntime.registerNativeProvider(native);
|
|
159
|
+
else if (config) modelRuntime.registerProvider(providerId, config);
|
|
160
|
+
else throw new Error(`Parent provider '${providerId}' has no registered native provider or config.`);
|
|
161
|
+
registered = true;
|
|
162
|
+
} catch (error) {
|
|
163
|
+
onError?.({ extensionPath: `<parent-provider:${providerId}>`, event: "inherit_provider", error });
|
|
164
|
+
throw new Error(`Failed to inherit parent provider '${providerId}': ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return registered;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const CHILD_PROMPT_RUNTIME_EXTENSION_PATH = "<inline:pi-subagents:prompt-runtime>";
|
|
171
|
+
|
|
172
|
+
/** The prompt runtime filters parent-only context before ambient extensions inspect
|
|
173
|
+
* the child prompt. Other inline hooks keep their normal position after ambient
|
|
174
|
+
* extensions, and ambient extension order stays unchanged. */
|
|
175
|
+
function prioritizeChildPromptRuntime<T extends { extensions: Array<{ path: string }> }>(result: T): T {
|
|
176
|
+
const index = result.extensions.findIndex(({ path }) => path === CHILD_PROMPT_RUNTIME_EXTENSION_PATH);
|
|
177
|
+
if (index <= 0) return result;
|
|
178
|
+
const extensions = [...result.extensions];
|
|
179
|
+
const [promptRuntime] = extensions.splice(index, 1);
|
|
180
|
+
if (!promptRuntime) return result;
|
|
181
|
+
extensions.unshift(promptRuntime);
|
|
182
|
+
return { ...result, extensions };
|
|
183
|
+
}
|
|
184
|
+
|
|
127
185
|
/** One launch at a time from env application through `session_start`, so parallel launches never observe each other's `processEnv` while their extensions load and start. */
|
|
128
186
|
let loading: Promise<unknown> = Promise.resolve();
|
|
129
187
|
|
|
@@ -148,34 +206,39 @@ function applyProcessEnv(values: Record<string, string | undefined> | undefined)
|
|
|
148
206
|
}
|
|
149
207
|
}
|
|
150
208
|
|
|
151
|
-
|
|
152
|
-
|
|
209
|
+
function flushQueuedProviderRegistrations(loader: InstanceType<PiCodingAgentModule["DefaultResourceLoader"]>, modelRuntime: ModelRuntimeInstance, onError: ((error: ChildSessionExtensionError) => void) | undefined, requiredPaths: ReadonlySet<string>): { claimedProviderIds: Set<string>; registered: boolean } {
|
|
210
|
+
const claimedProviderIds = new Set<string>();
|
|
211
|
+
if (!("getExtensions" in loader) || typeof loader.getExtensions !== "function") return { claimedProviderIds, registered: false };
|
|
153
212
|
const { runtime } = loader.getExtensions();
|
|
154
213
|
let registered = false;
|
|
155
214
|
for (const { name, config, extensionPath } of runtime.pendingProviderRegistrations ?? []) {
|
|
215
|
+
claimedProviderIds.add(name);
|
|
156
216
|
try {
|
|
157
217
|
modelRuntime.registerProvider(name, config);
|
|
158
218
|
registered = true;
|
|
159
219
|
} catch (error) {
|
|
160
220
|
onError?.({ extensionPath, event: "register_provider", error });
|
|
221
|
+
if (requiredPaths.has(extensionPath)) throw new Error(`Required child extension provider registration failed for '${extensionPath}': ${error instanceof Error ? error.message : String(error)}`);
|
|
161
222
|
}
|
|
162
223
|
}
|
|
163
224
|
if (Array.isArray(runtime.pendingProviderRegistrations)) runtime.pendingProviderRegistrations = [];
|
|
164
225
|
for (const { provider, extensionPath } of runtime.pendingNativeProviderRegistrations ?? []) {
|
|
226
|
+
claimedProviderIds.add(provider.id);
|
|
165
227
|
try {
|
|
166
228
|
modelRuntime.registerNativeProvider(provider);
|
|
167
229
|
registered = true;
|
|
168
230
|
} catch (error) {
|
|
169
231
|
onError?.({ extensionPath, event: "register_provider", error });
|
|
232
|
+
if (requiredPaths.has(extensionPath)) throw new Error(`Required child extension provider registration failed for '${extensionPath}': ${error instanceof Error ? error.message : String(error)}`);
|
|
170
233
|
}
|
|
171
234
|
}
|
|
172
235
|
if (Array.isArray(runtime.pendingNativeProviderRegistrations)) runtime.pendingNativeProviderRegistrations = [];
|
|
173
|
-
|
|
236
|
+
return { claimedProviderIds, registered };
|
|
174
237
|
}
|
|
175
238
|
|
|
176
239
|
/**
|
|
177
|
-
* Default factory:
|
|
178
|
-
*
|
|
240
|
+
* Default factory: detached/background sessions retain the existing shared
|
|
241
|
+
* runtime; each parent-bound foreground launch gets an isolated runtime.
|
|
179
242
|
*/
|
|
180
243
|
export function createDefaultChildSessionFactory(options: DefaultChildSessionFactoryOptions = {}): ChildSessionFactory {
|
|
181
244
|
const loadPiCodingAgent = options.loadPiCodingAgent ?? (() => import("@earendil-works/pi-coding-agent"));
|
|
@@ -193,13 +256,18 @@ export function createDefaultChildSessionFactory(options: DefaultChildSessionFac
|
|
|
193
256
|
};
|
|
194
257
|
return {
|
|
195
258
|
async create(launch) {
|
|
196
|
-
const observeReadonly = prepareReadonlySessionEvidence(launch);
|
|
197
259
|
const pi = await loadPiCodingAgent();
|
|
198
|
-
const modelRuntime =
|
|
260
|
+
const modelRuntime = launch.parentProviderRegistry
|
|
261
|
+
? await pi.ModelRuntime.create()
|
|
262
|
+
: await sharedRuntime(pi);
|
|
199
263
|
const agentDir = getAgentDir();
|
|
200
264
|
const settingsManager = pi.SettingsManager.create(launch.cwd, agentDir);
|
|
201
|
-
//
|
|
202
|
-
|
|
265
|
+
// Foreground children share Pi's global theme with the parent, so reinitializing it
|
|
266
|
+
// would overwrite the parent's active light/dark appearance. Detached runners have
|
|
267
|
+
// no initialized theme and must initialize one for headless extension renderers.
|
|
268
|
+
const themeKey = Symbol.for("@earendil-works/pi-coding-agent:theme");
|
|
269
|
+
const themeInitialized = Boolean((globalThis as Record<symbol, unknown>)[themeKey]);
|
|
270
|
+
if (!themeInitialized && typeof pi.initTheme === "function") pi.initTheme(settingsManager.getTheme());
|
|
203
271
|
const loader = new pi.DefaultResourceLoader({
|
|
204
272
|
cwd: launch.cwd,
|
|
205
273
|
agentDir,
|
|
@@ -211,17 +279,30 @@ export function createDefaultChildSessionFactory(options: DefaultChildSessionFac
|
|
|
211
279
|
noContextFiles: launch.noContextFiles,
|
|
212
280
|
additionalExtensionPaths: launch.extensionPaths,
|
|
213
281
|
extensionFactories: launch.hooks,
|
|
282
|
+
extensionsOverride: prioritizeChildPromptRuntime,
|
|
214
283
|
...(launch.systemPrompt !== undefined ? { systemPrompt: launch.systemPrompt } : {}),
|
|
215
284
|
...(launch.appendSystemPrompt !== undefined ? { appendSystemPrompt: [launch.appendSystemPrompt] } : {}),
|
|
216
285
|
});
|
|
217
286
|
const open = async () => {
|
|
287
|
+
const requiredPaths = new Set((launch.requiredExtensions ?? []).map(({ path }) => path));
|
|
218
288
|
applyProcessEnv(launch.processEnv);
|
|
219
289
|
if (!resetExtensionCacheOnReload(loader) && (launch.ambientExtensions || launch.extensionPaths.length)) launch.onExtensionError?.({ extensionPath: "<loader>", event: "load", error: new Error("pi's extension cache reset is unavailable; extensions loaded into this child share module state with other sessions in this process.") });
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
290
|
+
await loader.reload();
|
|
291
|
+
const loadErrors = requiredPaths.size > 0
|
|
292
|
+
? loader.getExtensions().errors.filter(({ path }) => requiredPaths.has(path)) : [];
|
|
293
|
+
if (loadErrors.length > 0) throw new Error(`Required child extension failed to load: ${loadErrors.map(({ path, error }) => `${path}: ${error}`).join("; ")}`);
|
|
294
|
+
const queued = flushQueuedProviderRegistrations(loader, modelRuntime, launch.onExtensionError, requiredPaths);
|
|
295
|
+
const inherited = launch.parentProviderRegistry
|
|
296
|
+
? inheritParentProviders(modelRuntime, launch.parentProviderRegistry, queued.claimedProviderIds, launch.onExtensionError)
|
|
297
|
+
: false;
|
|
298
|
+
if (queued.registered || inherited) {
|
|
299
|
+
try {
|
|
300
|
+
await modelRuntime.refresh({ allowNetwork: false });
|
|
301
|
+
} catch (error) {
|
|
302
|
+
launch.onExtensionError?.({ extensionPath: "<provider-refresh>", event: "refresh_providers", error });
|
|
303
|
+
throw new Error(`Failed to refresh child providers: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
304
|
+
}
|
|
305
|
+
}
|
|
225
306
|
const sessionManager = launch.storage.kind === "file"
|
|
226
307
|
? pi.SessionManager.open(launch.storage.sessionFile, undefined, launch.cwd)
|
|
227
308
|
: launch.storage.kind === "dir"
|
|
@@ -229,7 +310,6 @@ export function createDefaultChildSessionFactory(options: DefaultChildSessionFac
|
|
|
229
310
|
: launch.storage.kind === "memory"
|
|
230
311
|
? pi.SessionManager.inMemory(launch.cwd)
|
|
231
312
|
: pi.SessionManager.create(launch.cwd);
|
|
232
|
-
observeReadonly?.opened(sessionManager);
|
|
233
313
|
const resolvedModel = launch.model
|
|
234
314
|
? pi.resolveCliModel({ cliModel: launch.model, modelRuntime })
|
|
235
315
|
: undefined;
|
|
@@ -247,6 +327,7 @@ export function createDefaultChildSessionFactory(options: DefaultChildSessionFac
|
|
|
247
327
|
settingsManager,
|
|
248
328
|
sessionStartEvent: { type: "session_start", reason: "startup" },
|
|
249
329
|
});
|
|
330
|
+
pinChildCacheRetention(session.agent);
|
|
250
331
|
try {
|
|
251
332
|
await session.bindExtensions({
|
|
252
333
|
mode: "print",
|
|
@@ -261,9 +342,6 @@ export function createDefaultChildSessionFactory(options: DefaultChildSessionFac
|
|
|
261
342
|
const opened = loading.catch(() => {}).then(open);
|
|
262
343
|
loading = opened;
|
|
263
344
|
const session = await opened;
|
|
264
|
-
let evidence: ReturnType<NonNullable<typeof observeReadonly>["observe"]>;
|
|
265
|
-
try { evidence = observeReadonly?.observe(pi, modelRuntime, session); }
|
|
266
|
-
catch (error) { session.dispose(); throw error; }
|
|
267
345
|
let pending: Promise<void> | undefined;
|
|
268
346
|
// pi's own hosts emit `session_shutdown` before disposing a session so the
|
|
269
347
|
// extensions loaded into it (ambient extensions included) release their
|
|
@@ -272,28 +350,21 @@ export function createDefaultChildSessionFactory(options: DefaultChildSessionFac
|
|
|
272
350
|
try {
|
|
273
351
|
const runner = session.extensionRunner;
|
|
274
352
|
if (runner.hasHandlers("session_shutdown")) {
|
|
275
|
-
|
|
276
|
-
const settled = await Promise.race([runner.emit({ type: "session_shutdown", reason: "quit" }).then(() => true), new Promise<false>((resolve) => setTimeout(() => resolve(false), shutdownTimeoutMs).unref?.())]);
|
|
277
|
-
if (!settled) evidence?.invalidate();
|
|
353
|
+
await Promise.race([runner.emit({ type: "session_shutdown", reason: "quit" }), new Promise<void>((resolve) => setTimeout(resolve, shutdownTimeoutMs).unref?.())]);
|
|
278
354
|
}
|
|
279
355
|
} catch (error) {
|
|
280
|
-
evidence?.invalidate();
|
|
281
356
|
launch.onExtensionError?.({ extensionPath: "<session>", event: "session_shutdown", error });
|
|
282
357
|
} finally {
|
|
283
358
|
session.dispose();
|
|
284
|
-
evidence?.finish(child);
|
|
285
359
|
}
|
|
286
360
|
};
|
|
287
361
|
const child: ChildSession = {
|
|
288
362
|
subscribe: (listener) => session.subscribe((event) => listener(event as unknown as ChildSessionEvent)),
|
|
289
|
-
prompt: (text) =>
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
steer: (text) => { evidence?.invalidate(); return session.steer(text); },
|
|
295
|
-
followUp: (text) => { evidence?.invalidate(); return session.followUp(text); },
|
|
296
|
-
abort: () => { evidence?.invalidate(); return session.abort(); },
|
|
363
|
+
prompt: (text) => session.prompt(text),
|
|
364
|
+
steer: (text) => session.steer(text),
|
|
365
|
+
followUp: (text) => session.followUp(text),
|
|
366
|
+
abort: () => session.abort(),
|
|
367
|
+
hasQueuedMessages: () => session.agent?.hasQueuedMessages?.() === true,
|
|
297
368
|
dispose: () => {
|
|
298
369
|
if (!pending) {
|
|
299
370
|
live.delete(child);
|
|
@@ -309,16 +380,6 @@ export function createDefaultChildSessionFactory(options: DefaultChildSessionFac
|
|
|
309
380
|
get sessionId() { return session.sessionId; },
|
|
310
381
|
get modelId() { return session.model ? `${session.model.provider}/${session.model.id}` : undefined; },
|
|
311
382
|
};
|
|
312
|
-
if (evidence && session.model) readonlyModels.set(child, {
|
|
313
|
-
current: toModelInfo(session.model),
|
|
314
|
-
requestBytes: Buffer.byteLength(session.systemPrompt) + Buffer.byteLength(JSON.stringify(session.agent.state.tools)),
|
|
315
|
-
resolve(reference) {
|
|
316
|
-
try {
|
|
317
|
-
const resolved = pi.resolveCliModel({ cliModel: reference, modelRuntime });
|
|
318
|
-
return !resolved.error && resolved.model ? toModelInfo(resolved.model) : undefined;
|
|
319
|
-
} catch { return undefined; }
|
|
320
|
-
},
|
|
321
|
-
});
|
|
322
383
|
live.add(child);
|
|
323
384
|
return child;
|
|
324
385
|
},
|
|
@@ -340,10 +401,24 @@ let activeFactoryModule: string | undefined;
|
|
|
340
401
|
|
|
341
402
|
/** The process-wide factory foreground runs use unless a run passes its own. */
|
|
342
403
|
export function childSessionFactory(): ChildSessionFactory {
|
|
343
|
-
activeFactory ??= createDefaultChildSessionFactory();
|
|
404
|
+
activeFactory ??= createLazyPlacementFactory(createDefaultChildSessionFactory());
|
|
344
405
|
return activeFactory;
|
|
345
406
|
}
|
|
346
407
|
|
|
408
|
+
function createLazyPlacementFactory(local: ChildSessionFactory): ChildSessionFactory {
|
|
409
|
+
let placed: ChildSessionFactory | undefined;
|
|
410
|
+
const factory = async () => placed ??= (await import("./herdr-placed-run.ts")).createPlacementAwareChildSessionFactory(local);
|
|
411
|
+
return {
|
|
412
|
+
async create(launch) { return launch.machine ? (await factory()).create(launch) : local.create(launch); },
|
|
413
|
+
async dispose() { if (placed) await placed.dispose(); else await local.dispose(); },
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/** Default factory including pane-native placement; detached runners use the same boundary. */
|
|
418
|
+
export function createPlacementChildSessionFactory(options: DefaultChildSessionFactoryOptions = {}): ChildSessionFactory {
|
|
419
|
+
return createLazyPlacementFactory(createDefaultChildSessionFactory(options));
|
|
420
|
+
}
|
|
421
|
+
|
|
347
422
|
/**
|
|
348
423
|
* Replace the process-wide factory. Tests install a scripted factory; passing
|
|
349
424
|
* undefined restores the default on next use.
|