pi-subagents 0.64.0 → 0.65.1
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 +65 -0
- package/README.md +2 -2
- package/agents/reviewer.md +1 -1
- package/agents/scout.md +1 -1
- package/docs/agents.md +19 -17
- package/docs/configuration.md +23 -15
- package/docs/extension-api.md +18 -6
- package/docs/missions.md +2 -0
- package/docs/observability.md +10 -12
- package/docs/tool-reference.md +15 -8
- package/docs/watchdog.md +1 -1
- package/docs/workflows.md +13 -3
- package/package.json +5 -4
- package/runner-server-preload.mjs +13 -0
- package/skills/pi-subagents/SKILL.md +2 -1
- package/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
- package/skills/pi-subagents/references/execution-controls.md +7 -3
- package/skills/pi-subagents/references/multi-lane-orchestration.md +2 -0
- package/src/agents/agent-management.ts +47 -15
- package/src/api/capability-ceiling.ts +0 -1
- package/src/api/{pi-args.ts → child-tool-plan.ts} +1 -1
- package/src/api/preflight.ts +7 -4
- package/src/extension/config.ts +4 -2
- package/src/extension/doctor.ts +2 -10
- package/src/extension/fanout-child.ts +9 -11
- package/src/extension/index.ts +58 -7
- package/src/extension/public-execution.ts +14 -0
- package/src/extension/rpc.ts +3 -2
- package/src/extension/schemas.ts +3 -2
- package/src/extension/tool-description.ts +13 -8
- package/src/integrations/pi-web-session-liveness.ts +73 -0
- package/src/intercom/native-supervisor-channel.ts +159 -95
- package/src/intercom/supervisor-ui.ts +244 -0
- package/src/missions/workflow-state.ts +37 -16
- package/src/runs/background/async-execution.ts +51 -25
- package/src/runs/background/async-job-tracker.ts +11 -0
- package/src/runs/background/async-resume.ts +14 -3
- package/src/runs/background/async-retention.ts +9 -0
- package/src/runs/background/control-channel.ts +2 -204
- package/src/runs/background/notify.ts +2 -0
- package/src/runs/background/process-terminal.ts +1 -1
- package/src/runs/background/retained-nested-route-tracker.ts +96 -0
- package/src/runs/background/run-child-session.ts +614 -0
- package/src/runs/background/run-status.ts +0 -1
- package/src/runs/background/runner-aliases.ts +152 -0
- package/src/runs/background/runner-child-sessions.ts +31 -0
- package/src/runs/background/scheduled-runs.ts +18 -4
- package/src/runs/background/subagent-runner.ts +203 -901
- package/src/runs/foreground/async-steering-action.ts +1 -17
- package/src/runs/foreground/execution.ts +204 -378
- package/src/runs/foreground/foreground-control.ts +4 -0
- package/src/runs/foreground/foreground-history.ts +3 -1
- package/src/runs/foreground/prompt-audit.ts +9 -5
- package/src/runs/foreground/subagent-executor.ts +161 -91
- package/src/runs/foreground/workflow-foreground-steering.ts +24 -98
- package/src/runs/shared/abort-recovery.ts +3 -3
- package/src/runs/shared/acceptance.ts +14 -1
- package/src/runs/shared/capability-ceiling.ts +1 -2
- package/src/runs/shared/child-hooks.ts +25 -0
- package/src/runs/shared/child-identity.ts +13 -2
- package/src/runs/shared/child-launch.ts +314 -0
- package/src/runs/shared/child-lifecycle.ts +25 -0
- package/src/runs/shared/child-runtime-config.ts +126 -0
- package/src/runs/shared/child-session.ts +337 -0
- package/src/runs/shared/child-tool-plan.ts +530 -0
- package/src/runs/shared/claude-code-adapter.ts +5 -1
- package/src/runs/shared/completion-guard.ts +1 -1
- package/src/runs/shared/external-cli-preflight.ts +16 -0
- package/src/runs/shared/llm-intent-arbiter.ts +20 -11
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +5 -4
- package/src/runs/shared/model-exclusions.ts +84 -15
- package/src/runs/shared/model-fallback.ts +78 -6
- package/src/runs/shared/nested-events.ts +32 -48
- package/src/runs/shared/nested-path.ts +0 -14
- package/src/runs/shared/orca-progress-tabs.ts +12 -7
- package/src/runs/shared/parallel-utils.ts +1 -2
- package/src/runs/shared/permissions.ts +0 -13
- package/src/runs/shared/process-signal.ts +4 -1
- package/src/runs/shared/run-fanout-budget.ts +0 -13
- package/src/runs/shared/runtime-acknowledged-extensions.ts +0 -27
- package/src/runs/shared/structured-output.ts +17 -4
- package/src/runs/shared/subagent-control.ts +2 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +87 -384
- package/src/runs/shared/tool-availability.ts +18 -62
- package/src/runs/shared/tool-budget.ts +0 -14
- package/src/runs/shared/worktree-cleanup-plan.ts +31 -9
- package/src/runs/shared/worktree.ts +192 -40
- package/src/shared/child-session-name.ts +1 -1
- package/src/shared/jsonl-writer.ts +11 -0
- package/src/shared/model-response-aliases.ts +13 -0
- package/src/shared/thinking-ceiling.ts +0 -6
- package/src/shared/types.ts +63 -28
- package/src/shared/utils.ts +6 -4
- package/src/shared/watch-strategy.ts +2 -0
- package/src/slash/slash-commands.ts +0 -6
- package/src/tui/fleet-status.ts +1 -1
- package/src/tui/fleet.ts +0 -1
- package/src/tui/render.ts +254 -41
- package/src/watchdog/child-status.ts +0 -1
- package/src/watchdog/register-child.ts +12 -12
- package/src/workflows/scripted-workflow.ts +80 -7
- package/src/workflows/workflow-checklist.ts +4 -3
- package/src/runs/shared/child-protocol.ts +0 -415
- package/src/runs/shared/pi-args.ts +0 -1059
- package/src/runs/shared/subagent-startup-retry.ts +0 -116
- package/src/shared/post-exit-stdio-guard.ts +0 -85
package/docs/workflows.md
CHANGED
|
@@ -14,11 +14,19 @@ Packaged `worker`, `oracle`, and `advisor` default to forked context when a laun
|
|
|
14
14
|
|
|
15
15
|
Child-safety boundaries are enforced at runtime:
|
|
16
16
|
|
|
17
|
-
-
|
|
17
|
+
- Child sessions do not receive the bundled `pi-subagents` skill.
|
|
18
18
|
- Forked child context filtering removes parent-only subagent artifacts (including old hidden orchestration-instruction messages, slash/status/control messages, and prior parent `subagent` tool-call/tool-result history) while preserving ordinary prose and unrelated tool calls/results.
|
|
19
19
|
- By default, children do not register the `subagent` tool and receive boundary instructions that they are not the parent orchestrator and must not propose or run subagents.
|
|
20
20
|
- The explicit exception is an agent whose resolved builtin `tools` includes `subagent`; that child gets a child-safe `subagent` tool for the fanout work the parent assigned, still bounded by `maxSubagentDepth`.
|
|
21
21
|
|
|
22
|
+
### Failed lane recovery and execution-mode boundaries
|
|
23
|
+
|
|
24
|
+
A failure in the subagent workflow, child launch, prompt runtime, extension loading, or child tooling setup is a lane infrastructure blocker. It is not permission to silently retry through `interactive_shell`, `pi -ne`, Codex/Claude/Cursor CLI, a foreground agent, or another external execution mode.
|
|
25
|
+
|
|
26
|
+
Stop and report the exact failure, run/status, and repository/cwd/worktree/branch/ref state. Before a same-protocol retry or asking the owner, verify the worktree is clean or capture the partial diff. Retry or fix the `subagent` path only through a clear same-protocol action. For backlog lanes and other subagent-governed workflows, external/foreground/CLI fallback requires explicit owner approval. `interactive_shell` remains valid when the user explicitly requests visible foreground/CLI work or the task is outside the governed subagent protocol.
|
|
27
|
+
|
|
28
|
+
Pi core may print a generic `pi -ne` extension-load hint; that out-of-repo hint is not protocol-approved fallback. Configured native model/provider fallback remains governed by its own contract and does not authorize an execution-mode switch.
|
|
29
|
+
|
|
22
30
|
## Prompt shortcuts
|
|
23
31
|
|
|
24
32
|
The package includes reusable prompt templates for common workflows. You do not need them, but they are handy when you want the same shape every time:
|
|
@@ -369,7 +377,9 @@ Each child uses the existing worktree lifecycle: it branches from clean HEAD, jo
|
|
|
369
377
|
|
|
370
378
|
A top-level `{ workflowScript, worktree: true }` makes isolation the default for every workflow child. An individual child can override that default with `worktree: false`. Keep one writer when parallel writes are not intentionally isolated.
|
|
371
379
|
|
|
372
|
-
|
|
380
|
+
Use `baseRef` to branch managed worktrees from a named commit or branch instead of the default `HEAD`. For example, `{ workflowScript, worktree: true, baseRef: "refs/heads/release" }` applies the release ref to children unless a child supplies its own `baseRef`. The source checkout must still be clean, and the ref must resolve to a commit before any worktree is allocated.
|
|
381
|
+
|
|
382
|
+
Configure the worktree provider, native path layout, base directory, and setup hook in [configuration.md](configuration.md).
|
|
373
383
|
|
|
374
384
|
### Lane metadata lifecycle
|
|
375
385
|
|
|
@@ -446,7 +456,7 @@ export PI_SUBAGENT_MAX_DEPTH=1
|
|
|
446
456
|
export PI_SUBAGENT_MAX_DEPTH=0
|
|
447
457
|
```
|
|
448
458
|
|
|
449
|
-
`
|
|
459
|
+
`PI_SUBAGENT_MAX_DEPTH` applies to the top-level parent; children inherit their limit through their runtime config, and their own depth is tracked there too.
|
|
450
460
|
|
|
451
461
|
## Prompt-template integration
|
|
452
462
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-subagents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.65.1",
|
|
4
4
|
"description": "Pi extension for single-agent delegation and scripted multi-agent workflows",
|
|
5
5
|
"author": "Nico Bailon",
|
|
6
6
|
"license": "MIT",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"./preflight": "./src/api/preflight.ts",
|
|
17
17
|
"./control-channel": "./src/api/control-channel.ts",
|
|
18
18
|
"./intercom-bridge": "./src/api/intercom-bridge.ts",
|
|
19
|
-
"./
|
|
19
|
+
"./child-tool-plan": "./src/api/child-tool-plan.ts",
|
|
20
20
|
"./shared-types": "./src/api/shared-types.ts",
|
|
21
21
|
"./project-panes": "./src/api/project-panes.ts"
|
|
22
22
|
},
|
|
@@ -56,8 +56,7 @@
|
|
|
56
56
|
"test": "npm run test:unit",
|
|
57
57
|
"test:unit": "node --experimental-strip-types --import ./test/support/isolated-temp-root.mjs --test test/unit/*.test.ts",
|
|
58
58
|
"test:integration": "node --experimental-strip-types --import ./test/support/register-loader.mjs --test test/integration/*.test.ts",
|
|
59
|
-
"test:
|
|
60
|
-
"test:all": "npm run test:unit && npm run test:integration && npm run test:e2e"
|
|
59
|
+
"test:all": "npm run test:unit && npm run test:integration"
|
|
61
60
|
},
|
|
62
61
|
"pi": {
|
|
63
62
|
"extensions": [
|
|
@@ -91,9 +90,11 @@
|
|
|
91
90
|
}
|
|
92
91
|
},
|
|
93
92
|
"dependencies": {
|
|
93
|
+
"@earendil-works/pi-server": "0.85.0",
|
|
94
94
|
"acorn": "8.18.0",
|
|
95
95
|
"jiti": "2.7.0",
|
|
96
96
|
"typebox": "1.1.38",
|
|
97
|
+
"undici": "8.10.0",
|
|
97
98
|
"yaml": "2.8.3"
|
|
98
99
|
},
|
|
99
100
|
"devDependencies": {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Only loaded when the parent supplies Pi 0.85.0's missing server exports.
|
|
2
|
+
import { registerHooks } from "node:module";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
|
|
5
|
+
const aliases = JSON.parse(process.env.JITI_ALIAS);
|
|
6
|
+
registerHooks({
|
|
7
|
+
resolve(specifier, context, nextResolve) {
|
|
8
|
+
if (specifier === "@earendil-works/pi-server" || specifier === "@earendil-works/pi-server/unix") {
|
|
9
|
+
return nextResolve(pathToFileURL(aliases[specifier]).href, context);
|
|
10
|
+
}
|
|
11
|
+
return nextResolve(specifier, context);
|
|
12
|
+
},
|
|
13
|
+
});
|
|
@@ -91,7 +91,8 @@ review.
|
|
|
91
91
|
- Exact model names are deployment policy. Put them in user/project settings or profiles, not package guidance.
|
|
92
92
|
- Give every child a compact meta-prompt checklist: objective; repo/cwd/ref; authority/edit boundary; relevant files/contracts and constraints; success/acceptance criteria; validation; expected output/report; and stop/ask conditions. See `references/prompting-and-roles.md`.
|
|
93
93
|
- For mutation work, use an isolated lane/worktree when isolation, overlap, or concurrent juggling matters; keep one writer per cwd/worktree. See `references/multi-lane-orchestration.md` for lane mechanics.
|
|
94
|
-
- Keep long/high-output validation out of chat: prefer `interactive_shell` dispatch/background monitors, bounded logs, or subagent-owned reports; return a concise summary plus report path unless same-turn output is required.
|
|
94
|
+
- Keep long/high-output validation out of chat: prefer `interactive_shell` dispatch/background monitors, bounded logs, or subagent-owned reports; return a concise summary plus report path unless same-turn output is required. Do not use `interactive_shell` as an implicit fallback for a failed `subagent` lane; see `references/execution-controls.md`.
|
|
95
|
+
- Treat subagent workflow, child launch, prompt runtime, extension load, and child tooling setup failures as lane infrastructure blockers. Stop, report the exact failure and run/worktree state, verify a clean worktree or capture a partial diff, and use only a clear same-protocol retry or an owner-approved execution-mode fallback.
|
|
95
96
|
- For cross-codebase work, record the repo, explicit `cwd`, authority boundary, and expected output before launch.
|
|
96
97
|
- Make parallel prompts distinct by source seam, evidence, and decision. Do not clone prompts with only item numbers swapped.
|
|
97
98
|
- Prefer fresh-context review/validation fanout, then synthesize and apply fixes in the parent.
|
|
@@ -21,7 +21,7 @@ This file is a detailed reference loaded from `skills/pi-subagents/SKILL.md`.
|
|
|
21
21
|
become second decision-makers.
|
|
22
22
|
- **Respect the fixed authority policy.** `authorityPolicy` is a small `auto` / `confirm` / `forbid` map for supported operational actions. Worktree discard, destructive cleanup, and spawn-budget grants default to confirmation; stop, steer, and schedule creation remain automatic. Use `worktree.discard` with the durable `handoffPath`; confirm-required actions refuse safely without an interactive UI and retained paths include manual Git recovery commands.
|
|
23
23
|
|
|
24
|
-
Runtime config can change orchestration behavior. `intercomBridge.resultDelivery: false` disables only external acknowledged grouped-result delivery when native parent notifications own completion; supervisor asks/progress stay active, and enabled transport failures are still reported. `asyncByDefault` and `forceTopLevelAsync` affect whether launches detach; `waitTool` can make direct `bg_wait()` calls return immediately while headless auto-drain remains active, and its effective value is propagated to child runtimes; `globalConcurrencyLimit` bounds concurrent fanout, while a positive `maxSubagentSpawnsPerSession` optionally caps cumulative launches (`0` or unset is unlimited). Status and doctor report the budget; static work preflights declared capacity; only the settled root interactive parent can use `grant-spawn-budget` after native confirmation, with total grants bounded by the original cap. Compaction does not reset usage or grants; `singleRunOutputBaseDir` and `worktreeBaseDir` route outputs and worktrees; `completionBatch` groups async notifications. `artifactDir` is `session` (default), `project`, or `temp` and chooses where subagent artifacts are stored. Set `asyncWidget: false` to hide the above-editor background-run widget when a companion footer or dashboard owns that space (fleet inspector remains available). Per-run `artifacts: false` disables artifact capture for that launch. Async status and result artifacts include `lifecycleArtifactVersion` and fields such as `workflowGraph`, `steps`, `results`, `totalTokens`, `totalCost`, `turnCount`, `toolCount`, and nested `children`.
|
|
24
|
+
Runtime config can change orchestration behavior. `intercomBridge.resultDelivery: false` disables only external acknowledged grouped-result delivery when native parent notifications own completion; supervisor asks/progress stay active, and enabled transport failures are still reported. `asyncByDefault` and `forceTopLevelAsync` affect whether launches detach; `waitTool` can make direct `bg_wait()` calls return immediately while headless auto-drain remains active, and its effective value is propagated to child runtimes; `globalConcurrencyLimit` bounds concurrent fanout, while a positive `maxSubagentSpawnsPerSession` optionally caps cumulative launches (`0` or unset is unlimited). Status and doctor report the budget; static work preflights declared capacity; only the settled root interactive parent can use `grant-spawn-budget` after native confirmation, with total grants bounded by the original cap. Compaction does not reset usage or grants; `singleRunOutputBaseDir` and `worktreeBaseDir` route outputs and worktrees; `completionBatch` groups async notifications. `artifactDir` is `session` (default), `project`, or `temp` and chooses where subagent artifacts are stored. Set `asyncWidget: false` to hide the above-editor background-run widget when a companion footer or dashboard owns that space (fleet inspector remains available). Per-run `artifacts: false` disables artifact capture for that launch. Async status and result artifacts include `lifecycleArtifactVersion` and fields such as `workflowGraph`, `steps`, `results`, `totalTokens`, `totalCost`, `turnCount`, `toolCount`, and nested `children`. Prefer these artifacts and `status` views over scraping terminal output.
|
|
25
25
|
|
|
26
26
|
### Keep report artifacts out of the repository root
|
|
27
27
|
|
|
@@ -236,8 +236,8 @@ Resume behavior:
|
|
|
236
236
|
- Multi-child async runs require `index` unless only one running child is selectable.
|
|
237
237
|
- Completed foreground single, parallel, and chain runs can also be revived by `index` while their run metadata remains in extension state.
|
|
238
238
|
- Nested runs can be resumed by nested id when a live route or persisted nested session metadata is available.
|
|
239
|
-
- Revive starts a new child
|
|
240
|
-
- Direct revival holds an exclusive cross-process lease on the canonical child session file until the new child finishes. Concurrent attempts fail before
|
|
239
|
+
- Revive starts a new child session from the old session context; it does not resume the original child session.
|
|
240
|
+
- Direct revival holds an exclusive cross-process lease on the canonical child session file until the new child finishes. Concurrent attempts fail before the child session starts and identify the owning revived run; stale ownership is reclaimed only when the recorded process is demonstrably gone or reused.
|
|
241
241
|
- If the chosen child has no persisted `.jsonl` session file, resume fails and reports that directly.
|
|
242
242
|
|
|
243
243
|
Use diagnostics when setup or child startup looks wrong:
|
|
@@ -246,9 +246,13 @@ Use diagnostics when setup or child startup looks wrong:
|
|
|
246
246
|
subagent({ action: "doctor" })
|
|
247
247
|
```
|
|
248
248
|
|
|
249
|
+
### Failed lane recovery and execution-mode fallback
|
|
250
|
+
|
|
251
|
+
A failure in the subagent workflow, child launch, prompt runtime, extension loading, or child tooling setup is a lane infrastructure blocker, not permission to silently change execution mode. Stop and report the exact failure, run/status, and repo/cwd/worktree/branch/ref state. Retry or fix the `subagent` path only through a clear same-protocol retry; before retrying or asking the owner, verify the worktree is clean or capture the partial diff. For backlog lanes and other subagent-governed workflows, switching to `interactive_shell`, `pi -ne`, Codex/Claude/Cursor CLI, a foreground agent, or another external mode requires explicit owner approval. Pi core may print a generic `pi -ne` extension-load hint; that hint is outside this package and is not protocol-approved fallback. This execution-mode boundary does not prohibit configured native model/provider fallback.
|
|
252
|
+
|
|
249
253
|
### External terminal work
|
|
250
254
|
|
|
251
|
-
Use native `subagent` runs for unattended implementation, review, and gate work that needs managed isolation, durable artifacts, and process controls. Use `interactive_shell` for visible terminal work, alternate CLIs, trust prompts,
|
|
255
|
+
Use native `subagent` runs for unattended implementation, review, and gate work that needs managed isolation, durable artifacts, and process controls. Use `interactive_shell` for visible terminal work, alternate CLIs, trust prompts, or recovery only when the user explicitly requests that mode or the task is outside the governed subagent protocol; it is not an implicit replacement for a failed `subagent` lane.
|
|
252
256
|
|
|
253
257
|
A cooperating terminal runtime can register read-only external records through `pi-subagents/external-runs`. Records include the source, session, state, optional report path, and completion reason. They are observations only: pi-subagents does not start, stop, steer, or otherwise own the foreign process. Run unattended raw terminal agents in an explicit isolated cwd or worktree; do not use a live project checkout as disposable review space.
|
|
254
258
|
|
|
@@ -46,6 +46,8 @@ After a writer produces a candidate, run the required fresh-context, read-only r
|
|
|
46
46
|
|
|
47
47
|
Use stable lane-qualified artifact paths for reports and review output. A handoff states the lane status, repository and worktree, changed files, validation, open decisions, next action, and artifact or receipt paths. Copy only the final evidence to memory, a mission record, or a PR/comment, then remove scratch files from the active worktree before closing the lane.
|
|
48
48
|
|
|
49
|
+
For backlog lanes and other subagent-governed workflows, a setup/runtime/tooling failure remains an infrastructure blocker. Preserve the exact failure, run/status, and repository/cwd/worktree/branch/ref state; verify a clean worktree or capture any partial diff before a same-protocol retry or asking the owner. Do not start another writer or switch to an external, foreground, or CLI execution mode without explicit owner approval.
|
|
50
|
+
|
|
49
51
|
Keep a worktree until its handoff is durable, no run owns it, and no later gate needs it. Clean up only inside the recorded authority boundary. If a run stops or needs attention, preserve its worktree and artifacts, record the last known state and recovery owner, then resume that run or create one replacement lane from the handoff. Do not start another writer while worktree ownership is uncertain.
|
|
50
52
|
|
|
51
53
|
Before completion, inspect the board. Every lane must be terminal or blocked with a named next action. Confirm one writer per repo/cwd or worktree, required validation, required fresh read-only review, and a durable handoff. The parent reports outcomes, evidence, residual risks, and the next decision.
|
|
@@ -32,6 +32,7 @@ import { resolveSubagentModelOverride, type ParentModel } from "../runs/shared/m
|
|
|
32
32
|
import { validateToolBudgetConfig } from "../runs/shared/tool-budget.ts";
|
|
33
33
|
import { validateAcceptanceInput } from "../runs/shared/acceptance.ts";
|
|
34
34
|
import { CODE_OWNED_EXTERNAL_CLI_ADAPTER_LABEL, isCodeOwnedExternalCliAdapterId, resolveExternalCliRunnerStatus, validateCodeOwnedProfileRunner } from "../runs/shared/external-cli-contract.ts";
|
|
35
|
+
import { resolveExternalCliBinaryAvailability, type ExternalCliBinaryAvailability } from "../runs/shared/external-cli-preflight.ts";
|
|
35
36
|
import type { AcceptanceInput, AgentCapabilitiesSnapshot, AgentCapabilityRow, Details, ExtensionConfig, ToolBudgetConfig } from "../shared/types.ts";
|
|
36
37
|
import { getProjectConfigDir } from "../shared/utils.ts";
|
|
37
38
|
import { previewDisplayText } from "../shared/display-text.ts";
|
|
@@ -704,17 +705,34 @@ function externalJobProviderSuffix(provider: string, names: Set<string> | undefi
|
|
|
704
705
|
return names.has(provider) ? "✓" : "missing";
|
|
705
706
|
}
|
|
706
707
|
|
|
707
|
-
|
|
708
|
+
type ExternalCliAvailabilityByCommand = ReadonlyMap<string, ExternalCliBinaryAvailability>;
|
|
709
|
+
|
|
710
|
+
function externalCliAvailabilityForAgents(agents: readonly AgentConfig[]): ExternalCliAvailabilityByCommand {
|
|
711
|
+
const availability = new Map<string, ExternalCliBinaryAvailability>();
|
|
712
|
+
for (const agent of agents) {
|
|
713
|
+
const runner = agent.runner;
|
|
714
|
+
if (runner?.type === "external-cli" && !availability.has(runner.command)) {
|
|
715
|
+
availability.set(runner.command, resolveExternalCliBinaryAvailability(runner.command, process.env));
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
return availability;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function runnerListBadge(agent: AgentConfig, providerNames: Set<string> | undefined, externalCliAvailability?: ExternalCliAvailabilityByCommand): string | undefined {
|
|
708
722
|
if (agent.runner?.type === "external-job") return `external-job:${agent.runner.provider} ${externalJobProviderSuffix(agent.runner.provider, providerNames)}`;
|
|
709
|
-
if (agent.runner?.type === "external-cli")
|
|
723
|
+
if (agent.runner?.type === "external-cli") {
|
|
724
|
+
const availability = externalCliAvailability?.get(agent.runner.command);
|
|
725
|
+
if (!availability) return "external-cli";
|
|
726
|
+
return `external-cli:${agent.runner.command} ${availability.available ? "✓" : "missing"}`;
|
|
727
|
+
}
|
|
710
728
|
return undefined;
|
|
711
729
|
}
|
|
712
730
|
|
|
713
|
-
function agentListMetadata(agent: AgentConfig, providerNames: Set<string> | undefined): string {
|
|
731
|
+
function agentListMetadata(agent: AgentConfig, providerNames: Set<string> | undefined, externalCliAvailability?: ExternalCliAvailabilityByCommand): string {
|
|
714
732
|
const source = agent.source === "package" ? packageSourceLabel(agent) : agent.source;
|
|
715
733
|
return [
|
|
716
734
|
source,
|
|
717
|
-
runnerListBadge(agent, providerNames),
|
|
735
|
+
runnerListBadge(agent, providerNames, externalCliAvailability),
|
|
718
736
|
agent.defaultContext ? `context: ${agent.defaultContext}` : undefined,
|
|
719
737
|
agent.aliases?.length ? `aliases: ${agent.aliases.join(", ")}` : undefined,
|
|
720
738
|
].filter((part): part is string => Boolean(part)).join(", ");
|
|
@@ -724,7 +742,7 @@ function formatAgentListLine(agent: AgentConfig, providerNames: Set<string> | un
|
|
|
724
742
|
return `- ${agent.name} (${agentListMetadata(agent, providerNames)}): ${agent.description}`;
|
|
725
743
|
}
|
|
726
744
|
|
|
727
|
-
function formatAgentCapabilitiesLine(agent: AgentConfig, providerNames: Set<string> | undefined): string {
|
|
745
|
+
function formatAgentCapabilitiesLine(agent: AgentConfig, providerNames: Set<string> | undefined, externalCliAvailability?: ExternalCliAvailabilityByCommand): string {
|
|
728
746
|
const declaredTools = [
|
|
729
747
|
...(agent.tools ?? []),
|
|
730
748
|
...(agent.mcpDirectTools ?? []).map((tool) => `mcp:${tool}`),
|
|
@@ -742,7 +760,7 @@ function formatAgentCapabilitiesLine(agent: AgentConfig, providerNames: Set<stri
|
|
|
742
760
|
if (agent.modelProvider && !agent.model.includes("/")) model = `${agent.modelProvider}/${agent.model}`;
|
|
743
761
|
}
|
|
744
762
|
const thinking = agent.thinking === false ? "off" : agent.thinking ?? "default";
|
|
745
|
-
return `- ${agent.name} (${agentListMetadata(agent, providerNames)}): Description: ${previewDisplayText(agent.description, 240)}; Tools: ${tools}; Model: ${model}; Thinking: ${thinking}`;
|
|
763
|
+
return `- ${agent.name} (${agentListMetadata(agent, providerNames, externalCliAvailability)}): Description: ${previewDisplayText(agent.description, 240)}; Tools: ${tools}; Model: ${model}; Thinking: ${thinking}`;
|
|
746
764
|
}
|
|
747
765
|
|
|
748
766
|
const EXTERNAL_JOB_CAPABILITIES = { stop: false, steer: false, resume: false, structuredOutput: false, toolEvents: false } as const;
|
|
@@ -752,10 +770,19 @@ function listOrEmpty<T>(values: T[] | undefined): T[] {
|
|
|
752
770
|
return values ?? [];
|
|
753
771
|
}
|
|
754
772
|
|
|
755
|
-
function agentCapabilityRunner(agent: AgentConfig, providerNames: Set<string> | undefined): AgentCapabilityRow["runner"] {
|
|
773
|
+
function agentCapabilityRunner(agent: AgentConfig, providerNames: Set<string> | undefined, externalCliAvailability: ExternalCliAvailabilityByCommand): AgentCapabilityRow["runner"] {
|
|
756
774
|
const runner = agent.runner;
|
|
757
775
|
if (!runner || runner.type === "pi") return PI_AGENT_RUNNER;
|
|
758
|
-
if (runner.type === "external-cli")
|
|
776
|
+
if (runner.type === "external-cli") {
|
|
777
|
+
const availability = externalCliAvailability.get(runner.command)!;
|
|
778
|
+
return {
|
|
779
|
+
type: "external-cli",
|
|
780
|
+
adapter: runner.adapter,
|
|
781
|
+
command: runner.command,
|
|
782
|
+
...availability,
|
|
783
|
+
capabilities: resolveExternalCliRunnerStatus(runner).capabilities,
|
|
784
|
+
};
|
|
785
|
+
}
|
|
759
786
|
return { type: "external-job", provider: runner.provider, available: providerNames?.has(runner.provider), capabilities: EXTERNAL_JOB_CAPABILITIES };
|
|
760
787
|
}
|
|
761
788
|
|
|
@@ -769,7 +796,7 @@ function agentCapabilityTools(agent: AgentConfig): AgentCapabilityRow["tools"] {
|
|
|
769
796
|
};
|
|
770
797
|
}
|
|
771
798
|
|
|
772
|
-
function agentCapabilityRow(agent: AgentConfig, options: { executable: boolean; providerNames?: Set<string>; restrictionSources?: string[] }): AgentCapabilityRow {
|
|
799
|
+
function agentCapabilityRow(agent: AgentConfig, options: { executable: boolean; providerNames?: Set<string>; externalCliAvailability: ExternalCliAvailabilityByCommand; restrictionSources?: string[] }): AgentCapabilityRow {
|
|
773
800
|
return {
|
|
774
801
|
name: agent.name,
|
|
775
802
|
description: previewDisplayText(agent.description, 1000),
|
|
@@ -777,7 +804,7 @@ function agentCapabilityRow(agent: AgentConfig, options: { executable: boolean;
|
|
|
777
804
|
executable: options.executable,
|
|
778
805
|
restrictionSources: options.executable ? undefined : options.restrictionSources ?? [],
|
|
779
806
|
aliases: agent.aliases ? [...agent.aliases] : undefined,
|
|
780
|
-
runner: agentCapabilityRunner(agent, options.providerNames),
|
|
807
|
+
runner: agentCapabilityRunner(agent, options.providerNames, options.externalCliAvailability),
|
|
781
808
|
tools: agentCapabilityTools(agent),
|
|
782
809
|
model: presentDetails({ value: agent.model, fallbackModels: agent.fallbackModels, thinking: agent.thinking }),
|
|
783
810
|
execution: presentDetails({ defaultAsync: agent.defaultAsync, timeoutMs: agent.defaultTimeoutMs }),
|
|
@@ -786,11 +813,11 @@ function agentCapabilityRow(agent: AgentConfig, options: { executable: boolean;
|
|
|
786
813
|
};
|
|
787
814
|
}
|
|
788
815
|
|
|
789
|
-
function agentCapabilitiesSnapshot(input: { agents: AgentConfig[]; restrictedAgents: AgentConfig[]; providerNames?: Set<string>; restrictedSources?: string[] }): AgentCapabilitiesSnapshot {
|
|
816
|
+
function agentCapabilitiesSnapshot(input: { agents: AgentConfig[]; restrictedAgents: AgentConfig[]; providerNames?: Set<string>; externalCliAvailability: ExternalCliAvailabilityByCommand; restrictedSources?: string[] }): AgentCapabilitiesSnapshot {
|
|
790
817
|
return {
|
|
791
818
|
agents: [
|
|
792
|
-
...input.agents.map((agent) => agentCapabilityRow(agent, { executable: true, providerNames: input.providerNames })),
|
|
793
|
-
...input.restrictedAgents.map((agent) => agentCapabilityRow(agent, { executable: false, providerNames: input.providerNames, restrictionSources: input.restrictedSources })),
|
|
819
|
+
...input.agents.map((agent) => agentCapabilityRow(agent, { executable: true, providerNames: input.providerNames, externalCliAvailability: input.externalCliAvailability })),
|
|
820
|
+
...input.restrictedAgents.map((agent) => agentCapabilityRow(agent, { executable: false, providerNames: input.providerNames, externalCliAvailability: input.externalCliAvailability, restrictionSources: input.restrictedSources })),
|
|
794
821
|
],
|
|
795
822
|
restrictedCount: input.restrictedAgents.length,
|
|
796
823
|
...(input.restrictedSources?.length ? { capabilityCeilingSources: [...input.restrictedSources] } : {}),
|
|
@@ -824,13 +851,14 @@ function appendAgentDiagnosticLines(lines: string[], diagnostics: AgentDiscovery
|
|
|
824
851
|
);
|
|
825
852
|
}
|
|
826
853
|
|
|
827
|
-
function agentCapabilityDetails(input: { capabilityMode: boolean; agents: AgentConfig[]; restrictedAgents: AgentConfig[]; providerNames?: Set<string>; restrictedSources?: string[] }): Partial<Details> | undefined {
|
|
854
|
+
function agentCapabilityDetails(input: { capabilityMode: boolean; agents: AgentConfig[]; restrictedAgents: AgentConfig[]; providerNames?: Set<string>; externalCliAvailability: ExternalCliAvailabilityByCommand; restrictedSources?: string[] }): Partial<Details> | undefined {
|
|
828
855
|
if (!input.capabilityMode) return undefined;
|
|
829
856
|
return {
|
|
830
857
|
agentCapabilities: jsonDetails(agentCapabilitiesSnapshot({
|
|
831
858
|
agents: input.agents,
|
|
832
859
|
restrictedAgents: input.restrictedAgents,
|
|
833
860
|
providerNames: input.providerNames,
|
|
861
|
+
externalCliAvailability: input.externalCliAvailability,
|
|
834
862
|
restrictedSources: input.restrictedSources,
|
|
835
863
|
})),
|
|
836
864
|
};
|
|
@@ -936,7 +964,10 @@ export function handleList(params: ManagementParams, ctx: ManagementContext): Ag
|
|
|
936
964
|
const providerStatus = registeredExternalJobProviderStatus();
|
|
937
965
|
const providerNameSet = providerNames(providerStatus);
|
|
938
966
|
const capabilityMode = params.capabilities === true;
|
|
939
|
-
const
|
|
967
|
+
const externalCliAvailability = capabilityMode ? externalCliAvailabilityForAgents([...agents, ...restrictedAgents]) : undefined;
|
|
968
|
+
const formatLine = capabilityMode
|
|
969
|
+
? (agent: AgentConfig, names: Set<string> | undefined) => formatAgentCapabilitiesLine(agent, names, externalCliAvailability)
|
|
970
|
+
: formatAgentListLine;
|
|
940
971
|
const lines = [
|
|
941
972
|
capabilityMode ? "Executable agents (capabilities):" : "Executable agents:",
|
|
942
973
|
...formatAgentListSections(agents, providerNameSet, formatLine),
|
|
@@ -950,6 +981,7 @@ export function handleList(params: ManagementParams, ctx: ManagementContext): Ag
|
|
|
950
981
|
agents,
|
|
951
982
|
restrictedAgents,
|
|
952
983
|
providerNames: providerNameSet,
|
|
984
|
+
externalCliAvailability: externalCliAvailability ?? new Map(),
|
|
953
985
|
restrictedSources,
|
|
954
986
|
}));
|
|
955
987
|
}
|
package/src/api/preflight.ts
CHANGED
|
@@ -7,11 +7,11 @@ import { buildSkillInjection, normalizeSkillInput, resolveSkillsWithFallback } f
|
|
|
7
7
|
import { buildAgentMemoryInjection } from "../agents/agent-memory.ts";
|
|
8
8
|
import { buildModelCandidates, inheritsParentModel, resolveEffectiveSubagentModel, resolveModelOrigin, type AvailableModelInfo, type ParentModel } from "../runs/shared/model-fallback.ts";
|
|
9
9
|
import { resolveModelScopesForAgent } from "../runs/shared/model-scope.ts";
|
|
10
|
-
import { applyThinkingSuffix, resolvePiLaunchToolPlan, type PiLaunchToolPlan } from "../runs/shared/
|
|
10
|
+
import { applyThinkingSuffix, resolvePiLaunchToolPlan, type PiLaunchToolPlan } from "../runs/shared/child-tool-plan.ts";
|
|
11
11
|
import { injectOutputPathSystemPrompt, normalizeSingleOutputOverride, resolveSingleOutputPath } from "../runs/shared/single-output.ts";
|
|
12
12
|
import { getArtifactPaths, getArtifactsDir } from "../shared/artifacts.ts";
|
|
13
13
|
import { resolveEffectiveThinking } from "../shared/model-info.ts";
|
|
14
|
-
import { assertThinkingWithinCeiling,
|
|
14
|
+
import { assertThinkingWithinCeiling, intersectThinkingCeilings, type ThinkingLevel } from "../shared/thinking-ceiling.ts";
|
|
15
15
|
import { SUBAGENT_LIFECYCLE_ARTIFACT_VERSION, type ArtifactDirPreference, type ArtifactPaths, type JsonSchemaObject, type OutputMode } from "../shared/types.ts";
|
|
16
16
|
import { capabilityCeilingAgentRestrictionMessage, intersectSubagentCapabilityCeilings, type ResolvedSubagentCapabilityCeiling, type SubagentCapabilityAudit } from "../runs/shared/capability-ceiling.ts";
|
|
17
17
|
import { resolvePermissionRules } from "../runs/shared/permissions.ts";
|
|
@@ -73,6 +73,7 @@ export interface SubagentLaunchContractInput {
|
|
|
73
73
|
/** Current parent leaf required before an implicit `defaultContext: fork` stays `fork`. */
|
|
74
74
|
parentLeafId?: string | null;
|
|
75
75
|
sessionRoot?: string;
|
|
76
|
+
/** Caller directory used as a root keyed by the child run id ("preflight" placeholder when runId is omitted). */
|
|
76
77
|
sessionDir?: string;
|
|
77
78
|
runId?: string;
|
|
78
79
|
/** Root run id supplied by a host when projecting nested async lifecycle paths. */
|
|
@@ -330,7 +331,6 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
330
331
|
discovered.maxThinking,
|
|
331
332
|
input.thinkingCeiling,
|
|
332
333
|
input.inheritedThinkingCeiling,
|
|
333
|
-
decodeThinkingCeiling(process.env[SUBAGENT_THINKING_CEILING_ENV]),
|
|
334
334
|
);
|
|
335
335
|
const model = externalRunner ? undefined : applyThinkingSuffix(primaryModel, effectiveThinkingConfig, input.thinking !== undefined);
|
|
336
336
|
const modelCandidates = externalRunner
|
|
@@ -381,7 +381,10 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
|
|
|
381
381
|
const artifactsDir = artifactsEnabled ? getArtifactsDir(input.parentSessionFile ?? null, effectiveCwd, input.artifactDir) : undefined;
|
|
382
382
|
const artifactPaths = artifactsDir ? getArtifactPaths(artifactsDir, runId, agent.name, 0) : undefined;
|
|
383
383
|
const outputPath = resolveSingleOutputPath(behavior.output, effectiveCwd, effectiveCwd, artifactsDir ? path.join(artifactsDir, "outputs", runId) : undefined);
|
|
384
|
-
|
|
384
|
+
// An explicit sessionDir is a root keyed by the child run id, matching the
|
|
385
|
+
// sibling sessionRoot derivation; hosts omitting runId get the documented
|
|
386
|
+
// deterministic "preflight" placeholder.
|
|
387
|
+
const sessionRoot = input.sessionDir ? path.join(path.resolve(input.sessionDir), runId) : input.sessionRoot ? path.join(path.resolve(input.sessionRoot), runId) : undefined;
|
|
385
388
|
const sessionDir = sessionRoot ? path.join(sessionRoot, "run-0") : undefined;
|
|
386
389
|
const lifecycleAsyncDir = input.nestedRootRunId
|
|
387
390
|
? path.join(TEMP_ROOT_DIR, "nested-subagent-runs", input.nestedRootRunId, runId)
|
package/src/extension/config.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { DEFAULT_MODEL_EXCLUSION_TTL_MS, MAX_MODEL_EXCLUSION_TTL_MS, setDefaultT
|
|
|
10
10
|
import { validatePermissionConfig } from "../runs/shared/permissions.ts";
|
|
11
11
|
import { MAX_ABANDONED_SLOT_RELEASE_AFTER_MS, MIN_ABANDONED_SLOT_RELEASE_AFTER_MS } from "../runs/background/active-async-capacity.ts";
|
|
12
12
|
import { normalizeWorktreeBranchPrefix } from "../runs/shared/worktree.ts";
|
|
13
|
+
import { validateModelResponseAliases } from "../shared/model-response-aliases.ts";
|
|
13
14
|
|
|
14
15
|
const ARTIFACT_DIR_PREFERENCES = new Set<ArtifactDirPreference>(["project", "session", "temp"]);
|
|
15
16
|
const FLEET_KEYBINDING_ACTION_SET = new Set<string>(FLEET_KEYBINDING_ACTIONS);
|
|
@@ -177,6 +178,7 @@ function validateConfig(config: Record<string, unknown>): void {
|
|
|
177
178
|
validateArtifactConfig(config.artifactConfig);
|
|
178
179
|
validateCapacityConfig(config.capacity);
|
|
179
180
|
validateModelExclusionsConfig(config.modelExclusions);
|
|
181
|
+
validateModelResponseAliases(config.modelResponseAliases);
|
|
180
182
|
validateMainWindowRendererConfig(config.mainWindowRenderer);
|
|
181
183
|
validateOrcaProgressTabsConfig(config.orcaProgressTabs);
|
|
182
184
|
}
|
|
@@ -240,12 +242,12 @@ export function loadConfig(): ExtensionConfig {
|
|
|
240
242
|
return readConfigForUpdate(configPath);
|
|
241
243
|
} catch (error) {
|
|
242
244
|
if (error instanceof PrunedForkConfigError) throw error;
|
|
243
|
-
//
|
|
245
|
+
// Explicit route identity and worktree policies must not be silently
|
|
244
246
|
// discarded and replaced by the built-in defaults after validation fails.
|
|
245
247
|
try {
|
|
246
248
|
const raw = JSON.parse(fs.readFileSync(configPath, "utf-8")) as unknown;
|
|
247
249
|
if (raw && typeof raw === "object" && !Array.isArray(raw)
|
|
248
|
-
&& (Object.hasOwn(raw, "worktreeProvider") || Object.hasOwn(raw, "worktreeBranchPrefix"))) throw error;
|
|
250
|
+
&& (Object.hasOwn(raw, "worktreeProvider") || Object.hasOwn(raw, "worktreeBranchPrefix") || Object.hasOwn(raw, "modelResponseAliases"))) throw error;
|
|
249
251
|
} catch (readError) {
|
|
250
252
|
if (readError === error) throw error;
|
|
251
253
|
}
|
package/src/extension/doctor.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { discoverAgentsAll, type AgentSource } from "../agents/agents.ts";
|
|
|
4
4
|
import { isAsyncAvailable } from "../runs/background/async-execution.ts";
|
|
5
5
|
import { formatSpawnBudgetSummary, getSpawnBudgetSnapshot } from "../runs/shared/spawn-budget.ts";
|
|
6
6
|
import { getActiveAsyncCapacitySnapshot, resolveAbandonedSlotReleaseAfterMs, resolveMaxActiveAsyncRunsPerSession } from "../runs/background/active-async-capacity.ts";
|
|
7
|
-
|
|
7
|
+
|
|
8
8
|
import { diagnoseIntercomBridge, type IntercomBridgeDiagnostic } from "../intercom/intercom-bridge.ts";
|
|
9
9
|
import { discoverAvailableSkills, type SkillSource } from "../agents/skills.ts";
|
|
10
10
|
import {
|
|
@@ -178,14 +178,6 @@ function formatSpawnBudgetSection(input: DoctorReportInput): string[] {
|
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
function formatRunFanoutSection(input: DoctorReportInput): string[] {
|
|
181
|
-
try {
|
|
182
|
-
const inherited = decodeRunFanoutBudgetDescriptor(process.env[RUN_FANOUT_BUDGET_ENV]);
|
|
183
|
-
if (inherited) {
|
|
184
|
-
return [`- usage: ${formatRunFanoutBudget(getRunFanoutBudgetSnapshot(inherited)).replace(/^Run fan-out: /, "")}`, `- root run: ${inherited.rootRunId}`, "- reset boundary: cumulative claims are never released; a new top-level run creates a new budget"];
|
|
185
|
-
}
|
|
186
|
-
} catch (error) {
|
|
187
|
-
return [`- inherited budget: invalid — ${errorText(error)}`];
|
|
188
|
-
}
|
|
189
181
|
const configured = resolveMaxSubagentSpawnsPerRun(input.config.maxSubagentSpawnsPerRun);
|
|
190
182
|
const source = normalizeMaxSubagentSpawnsPerRun(process.env.PI_SUBAGENT_MAX_SPAWNS_PER_RUN) !== undefined
|
|
191
183
|
? "environment"
|
|
@@ -214,7 +206,7 @@ function formatPermissionSystemSection(): string[] {
|
|
|
214
206
|
if (trimmed) {
|
|
215
207
|
lines.push(`- parent session: set (${trimmed})`);
|
|
216
208
|
} else {
|
|
217
|
-
lines.push("- parent session: not set — ask forwarding from
|
|
209
|
+
lines.push("- parent session: not set — ask forwarding from background children will not reach a parent UI");
|
|
218
210
|
}
|
|
219
211
|
const isChild = process.env["PI_SUBAGENT_CHILD"] === "1";
|
|
220
212
|
lines.push(`- subagent process: ${isChild ? "yes (PI_SUBAGENT_CHILD=1)" : "no"}`);
|
|
@@ -6,8 +6,8 @@ import { discoverAgents } from "../agents/agents.ts";
|
|
|
6
6
|
import { getArtifactsDir } from "../shared/artifacts.ts";
|
|
7
7
|
import { createSubagentExecutor, type SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
|
|
8
8
|
import { resolveWaitToolConfig } from "../runs/background/wait-config.ts";
|
|
9
|
-
import {
|
|
10
|
-
import { readNestedControlRequests,
|
|
9
|
+
import type { ChildRuntimeConfig } from "../runs/shared/child-runtime-config.ts";
|
|
10
|
+
import { readNestedControlRequests, resolveInheritedNestedRoute, type NestedRoute, writeNestedControlResult } from "../runs/shared/nested-events.ts";
|
|
11
11
|
import { deliverSubagentIntercomMessageEvent } from "../intercom/result-intercom.ts";
|
|
12
12
|
import { resolveSubagentIntercomTarget } from "../intercom/intercom-bridge.ts";
|
|
13
13
|
import { createSubagentParamsSchema } from "./schemas.ts";
|
|
@@ -51,12 +51,8 @@ function createChildSafeState(): SubagentState {
|
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
function resolveNestedControlRoute(): NestedRoute | undefined {
|
|
55
|
-
|
|
56
|
-
return resolveNestedRouteFromEnv();
|
|
57
|
-
} catch {
|
|
58
|
-
return undefined;
|
|
59
|
-
}
|
|
54
|
+
function resolveNestedControlRoute(config: ChildRuntimeConfig): NestedRoute | undefined {
|
|
55
|
+
return config.nestedRoute ? resolveInheritedNestedRoute(config.nestedRoute) : undefined;
|
|
60
56
|
}
|
|
61
57
|
|
|
62
58
|
function nestedControlRouteKey(route: NestedRoute): string {
|
|
@@ -145,8 +141,9 @@ function startNestedControlInboxListener(pi: ExtensionAPI, state: SubagentState,
|
|
|
145
141
|
return () => clearInterval(timer);
|
|
146
142
|
}
|
|
147
143
|
|
|
148
|
-
|
|
149
|
-
|
|
144
|
+
/** Register the child-side `subagent` tool for fanout-authorized children. */
|
|
145
|
+
export default function registerFanoutChildSubagentExtension(pi: ExtensionAPI, childConfig: ChildRuntimeConfig): void {
|
|
146
|
+
if (!childConfig.fanoutChild) return;
|
|
150
147
|
|
|
151
148
|
const globalStore = globalThis as Record<string, unknown>;
|
|
152
149
|
const registeredKey = "__piSubagentFanoutChildRegisteredApis";
|
|
@@ -172,6 +169,7 @@ export default function registerFanoutChildSubagentExtension(pi: ExtensionAPI):
|
|
|
172
169
|
expandTilde,
|
|
173
170
|
discoverAgents,
|
|
174
171
|
allowMutatingManagementActions: false,
|
|
172
|
+
childRuntime: childConfig,
|
|
175
173
|
});
|
|
176
174
|
|
|
177
175
|
const params = createSubagentParamsSchema();
|
|
@@ -190,7 +188,7 @@ export default function registerFanoutChildSubagentExtension(pi: ExtensionAPI):
|
|
|
190
188
|
};
|
|
191
189
|
|
|
192
190
|
pi.registerTool(tool);
|
|
193
|
-
const route = resolveNestedControlRoute();
|
|
191
|
+
const route = resolveNestedControlRoute(childConfig);
|
|
194
192
|
if (!route) return;
|
|
195
193
|
const listenerCleanupKey = "__piSubagentFanoutChildNestedControlInboxCleanups";
|
|
196
194
|
const listenerCleanups = globalStore[listenerCleanupKey] instanceof Map
|