pi-subagents 0.65.1 → 0.67.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.
Files changed (148) hide show
  1. package/CHANGELOG.md +123 -0
  2. package/README.md +5 -4
  3. package/agents/evidence-auditor.md +34 -0
  4. package/agents/researcher.md +23 -13
  5. package/agents/reviewer.md +3 -2
  6. package/docs/agents.md +20 -3
  7. package/docs/configuration.md +25 -5
  8. package/docs/extension-api.md +124 -18
  9. package/docs/missions.md +8 -0
  10. package/docs/models.md +59 -2
  11. package/docs/observability.md +46 -6
  12. package/docs/standalone-background.md +49 -0
  13. package/docs/tool-reference.md +20 -10
  14. package/docs/watchdog.md +35 -4
  15. package/docs/workflows.md +40 -19
  16. package/inspector-runner.mjs +2 -2
  17. package/package.json +2 -1
  18. package/prompts/parallel-review.md +1 -1
  19. package/{runner-server-preload.mjs → runner-peer-preload.mjs} +8 -3
  20. package/skills/pi-subagents/SKILL.md +14 -0
  21. package/skills/pi-subagents/references/execution-controls.md +20 -5
  22. package/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
  23. package/skills/pi-subagents/references/prompting-and-roles.md +2 -2
  24. package/src/agents/advertised-agent-prompt.ts +94 -0
  25. package/src/agents/agent-management.ts +14 -1
  26. package/src/agents/agent-serializer.ts +2 -0
  27. package/src/agents/agents.ts +14 -0
  28. package/src/agents/builtin-names.ts +1 -0
  29. package/src/api/delegation.ts +4 -0
  30. package/src/api/preflight.ts +76 -45
  31. package/src/api/shared-types.ts +3 -1
  32. package/src/api/workflow-resources.ts +6 -0
  33. package/src/extension/fanout-child.ts +63 -4
  34. package/src/extension/index.ts +58 -8
  35. package/src/extension/public-execution.ts +4 -3
  36. package/src/extension/rpc.ts +8 -21
  37. package/src/extension/schemas.ts +71 -80
  38. package/src/extension/tool-description.ts +29 -81
  39. package/src/inspectors/actions.ts +148 -0
  40. package/src/inspectors/ghostty/actions.ts +74 -0
  41. package/src/inspectors/ghostty/plugin.ts +17 -0
  42. package/src/inspectors/herdr/actions.ts +99 -179
  43. package/src/inspectors/herdr/plugin.ts +20 -0
  44. package/src/inspectors/herdr/project-panes.ts +1 -1
  45. package/src/inspectors/{herdr/inspector-runner.ts → inspector-runner.ts} +12 -12
  46. package/src/inspectors/plugins.ts +8 -0
  47. package/src/inspectors/{herdr/session-roots-codec.ts → session-roots-codec.ts} +3 -14
  48. package/src/inspectors/types.ts +51 -0
  49. package/src/intercom/intercom-bridge.ts +50 -8
  50. package/src/intercom/native-supervisor-channel.ts +104 -67
  51. package/src/runs/background/active-async-capacity.ts +22 -18
  52. package/src/runs/background/async-execution.ts +45 -56
  53. package/src/runs/background/async-job-tracker.ts +35 -3
  54. package/src/runs/background/async-resume.ts +5 -9
  55. package/src/runs/background/async-status-snapshot.ts +10 -12
  56. package/src/runs/background/async-status.ts +17 -9
  57. package/src/runs/background/auto-drain.ts +44 -30
  58. package/src/runs/background/binary-bootstrap.ts +33 -0
  59. package/src/runs/background/chain-root-attachment.ts +8 -0
  60. package/src/runs/background/control-channel.ts +78 -44
  61. package/src/runs/background/fleet-view.ts +30 -2
  62. package/src/runs/background/notify.ts +117 -13
  63. package/src/runs/background/owned-process-tree.ts +35 -8
  64. package/src/runs/background/process-terminal.ts +23 -23
  65. package/src/runs/background/run-child-session.ts +121 -36
  66. package/src/runs/background/run-status.ts +78 -5
  67. package/src/runs/background/runner-aliases.ts +28 -9
  68. package/src/runs/background/runner-child-launch.ts +88 -0
  69. package/src/runs/background/runner-child-sessions.ts +5 -4
  70. package/src/runs/background/scheduled-runs.ts +40 -13
  71. package/src/runs/background/stale-run-reconciler.ts +3 -1
  72. package/src/runs/background/steering.ts +20 -2
  73. package/src/runs/background/subagent-runner.ts +458 -239
  74. package/src/runs/background/subagent-wait.ts +54 -8
  75. package/src/runs/background/wait-completions.ts +4 -0
  76. package/src/runs/background/wait-tool.ts +1 -1
  77. package/src/runs/foreground/async-steering-action.ts +37 -7
  78. package/src/runs/foreground/execution.ts +145 -56
  79. package/src/runs/foreground/prompt-audit.ts +3 -1
  80. package/src/runs/foreground/subagent-executor.ts +584 -297
  81. package/src/runs/foreground/workflow-detach-reconcile.ts +10 -5
  82. package/src/runs/foreground/workflow-foreground-steering.ts +57 -2
  83. package/src/runs/shared/acceptance.ts +7 -4
  84. package/src/runs/shared/agent-contract.ts +1 -1
  85. package/src/runs/shared/async-status-projection.ts +51 -47
  86. package/src/runs/shared/capability-ceiling.ts +2 -0
  87. package/src/runs/shared/child-hooks.ts +167 -3
  88. package/src/runs/shared/child-launch.ts +28 -13
  89. package/src/runs/shared/child-lifecycle.ts +6 -3
  90. package/src/runs/shared/child-runtime-config.ts +3 -1
  91. package/src/runs/shared/child-session.ts +75 -8
  92. package/src/runs/shared/child-tool-plan.ts +124 -5
  93. package/src/runs/shared/completion-evidence.ts +2 -2
  94. package/src/runs/shared/completion-guard.ts +6 -3
  95. package/src/runs/shared/effective-system-prompt.ts +33 -0
  96. package/src/runs/shared/external-cli-runner.ts +9 -7
  97. package/src/runs/shared/host-step-status.ts +11 -11
  98. package/src/runs/shared/llm-intent-arbiter.ts +21 -11
  99. package/src/runs/shared/model-fallback.ts +12 -6
  100. package/src/runs/shared/nested-events.ts +5 -5
  101. package/src/runs/shared/orca-progress-tabs.ts +7 -1
  102. package/src/runs/shared/parallel-handoff.ts +57 -12
  103. package/src/runs/shared/parallel-utils.ts +2 -2
  104. package/src/runs/shared/pi-spawn.ts +10 -0
  105. package/src/runs/shared/readonly-drain-observation.ts +42 -0
  106. package/src/runs/shared/readonly-model-continuation.ts +69 -0
  107. package/src/runs/shared/readonly-session-evidence.ts +307 -0
  108. package/src/runs/shared/run-fanout-budget.ts +8 -8
  109. package/src/runs/shared/runtime-acknowledged-extensions.ts +3 -3
  110. package/src/runs/shared/subagent-prompt-runtime.ts +20 -4
  111. package/src/runs/shared/task-intent.ts +46 -13
  112. package/src/runs/shared/workflow-async-child-guidance.ts +18 -0
  113. package/src/runs/shared/worktree-setup-command.ts +190 -0
  114. package/src/runs/shared/worktree.ts +366 -208
  115. package/src/shared/fork-context.ts +15 -72
  116. package/src/shared/launch-contract.ts +65 -2
  117. package/src/shared/opencode-session-headers.ts +30 -0
  118. package/src/shared/types.ts +85 -61
  119. package/src/shared/utils.ts +7 -2
  120. package/src/shared/workflow-child-permit.ts +18 -13
  121. package/src/slash/delegation-adapters.ts +3 -1
  122. package/src/slash/delegation-request.ts +14 -0
  123. package/src/slash/slash-commands.ts +2 -1
  124. package/src/slash/subagents-admin.ts +11 -4
  125. package/src/tui/fleet-status.ts +164 -19
  126. package/src/tui/fleet.ts +27 -19
  127. package/src/tui/render.ts +172 -33
  128. package/src/watchdog/child-status.ts +8 -0
  129. package/src/watchdog/model-selection.ts +20 -0
  130. package/src/watchdog/permission-arbiter.ts +3 -1
  131. package/src/watchdog/register-child.ts +1 -0
  132. package/src/watchdog/register-main.ts +31 -27
  133. package/src/watchdog/review.ts +132 -67
  134. package/src/watchdog/runtime.ts +82 -20
  135. package/src/watchdog/scope.ts +1 -1
  136. package/src/watchdog/settings.ts +9 -3
  137. package/src/watchdog/tool-actions.ts +13 -12
  138. package/src/watchdog/turn-delta.ts +23 -0
  139. package/src/watchdog/types.ts +4 -0
  140. package/src/workflows/chat-progress.ts +3 -3
  141. package/src/workflows/scripted-workflow.ts +275 -17
  142. package/src/workflows/workflow-checklist.ts +13 -17
  143. package/src/workflows/workflow-child-summary.ts +57 -8
  144. package/src/workflows/workflow-preflight.ts +19 -19
  145. package/src/workflows/workflow-receipt.ts +3 -3
  146. package/src/workflows/workflow-resources.ts +96 -21
  147. package/src/workflows/workflow-settlement.ts +3 -0
  148. /package/src/inspectors/{herdr/shell-command.ts → shell-command.ts} +0 -0
@@ -6,6 +6,7 @@ export const KNOWN_FIELDS = new Set([
6
6
  "name",
7
7
  "package",
8
8
  "description",
9
+ "advertise",
9
10
  "alias",
10
11
  "aliases",
11
12
  "tools",
@@ -62,6 +63,7 @@ export function serializeAgent(config: AgentConfig, options: SerializeAgentOptio
62
63
  lines.push(`name: ${frontmatterNameForConfig(config)}`);
63
64
  if (config.packageName) lines.push(`package: ${config.packageName}`);
64
65
  lines.push(`description: ${config.description}`);
66
+ if (config.advertise === true || preserve("advertise")) lines.push(`advertise: ${config.advertise === true ? "true" : "false"}`);
65
67
  const aliasesValue = joinComma(config.aliases);
66
68
  if (aliasesValue || preserve("alias", "aliases")) lines.push(`aliases: ${aliasesValue ?? ""}`);
67
69
 
@@ -135,6 +135,7 @@ export interface AgentConfig {
135
135
  packageSourceVersion?: string;
136
136
  packageSourceRoot?: string;
137
137
  description: string;
138
+ advertise?: boolean;
138
139
  aliases?: string[];
139
140
  tools?: string[];
140
141
  excludeTools?: string[];
@@ -180,6 +181,12 @@ export interface AgentConfig {
180
181
  override?: BuiltinAgentOverrideInfo;
181
182
  modelSource?: AgentModelSourceInfo;
182
183
  maxThinking?: ThinkingLevel;
184
+ /**
185
+ * Digest of the parsed definition, set when a runtime overlay such as the
186
+ * Intercom bridge rewrites launch-affecting fields. Launch identity reads
187
+ * this instead of re-hashing the overlaid copy.
188
+ */
189
+ definitionDigest?: string;
183
190
  }
184
191
 
185
192
  type ProjectRootResolution = "nearest" | "git-root";
@@ -1981,6 +1988,12 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
1981
1988
 
1982
1989
  const runner = parseAgentRunnerFrontmatter(frontmatter.runner, localName);
1983
1990
  validateExternalRunnerProfile(frontmatter, localName, runner);
1991
+ let advertise: boolean | undefined;
1992
+ if (frontmatter.advertise !== undefined) {
1993
+ if (frontmatter.advertise === "true") advertise = true;
1994
+ else if (frontmatter.advertise === "false") advertise = false;
1995
+ else throw new Error(`Agent '${localName}' has invalid advertise frontmatter; expected true or false.`);
1996
+ }
1984
1997
  const rawTools = parseFrontmatterList(frontmatter.tools);
1985
1998
  const parsedTools = splitToolList(rawTools);
1986
1999
  const tools = parsedTools.tools ?? [];
@@ -2105,6 +2118,7 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
2105
2118
  ...(packageSource?.packageVersion ? { packageSourceVersion: packageSource.packageVersion } : {}),
2106
2119
  ...(packageSource?.packageRoot ? { packageSourceRoot: packageSource.packageRoot } : {}),
2107
2120
  description: frontmatter.description,
2121
+ ...(advertise !== undefined ? { advertise } : {}),
2108
2122
  ...(aliases !== undefined ? { aliases } : {}),
2109
2123
  ...(rawTools !== undefined ? { tools } : {}),
2110
2124
  ...(excludeTools !== undefined ? { excludeTools } : {}),
@@ -7,6 +7,7 @@ export const BUILTIN_AGENT_NAMES = [
7
7
  "cursor-agent",
8
8
  "cursor-agent-writer",
9
9
  "delegate",
10
+ "evidence-auditor",
10
11
  "oracle",
11
12
  "researcher",
12
13
  "reviewer",
@@ -1,3 +1,5 @@
1
+ import type { IntercomBridgeConfig } from "../shared/types.ts";
2
+
1
3
  // This is the established extension-to-extension transport. The structured
2
4
  // delegation API intentionally reuses it instead of adding a second event
3
5
  // protocol. Unstructured legacy direct payloads are rejected.
@@ -35,6 +37,8 @@ export interface SubagentDelegationRequest {
35
37
  toolBudget?: SubagentDelegationToolBudget;
36
38
  skill?: string | string[] | boolean;
37
39
  artifacts?: boolean;
40
+ /** Per-launch bridge config; replaces the global `intercomBridge` config. Pass the same value to preflight to compare digests. */
41
+ intercomBridge?: IntercomBridgeConfig;
38
42
  result: SubagentDelegationResultRequest;
39
43
  }
40
44
 
@@ -3,30 +3,36 @@ import * as path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { discoverAgentSnapshot, findBlockingAgentDiagnostic, formatUnknownAgentError, resolveAgentName, unknownAgentDiagnosticContext, type AgentConfig, type AgentDiscoveryAllResult, type AgentScope, type AgentSource } from "../agents/agents.ts";
5
5
  import { resolveExecutionAgentScope } from "../agents/agent-scope.ts";
6
- import { buildSkillInjection, normalizeSkillInput, resolveSkillsWithFallback } from "../agents/skills.ts";
7
- import { buildAgentMemoryInjection } from "../agents/agent-memory.ts";
6
+ import { normalizeSkillInput, resolveSkillsWithFallback } from "../agents/skills.ts";
8
7
  import { buildModelCandidates, inheritsParentModel, resolveEffectiveSubagentModel, resolveModelOrigin, type AvailableModelInfo, type ParentModel } from "../runs/shared/model-fallback.ts";
9
8
  import { resolveModelScopesForAgent } from "../runs/shared/model-scope.ts";
10
9
  import { applyThinkingSuffix, resolvePiLaunchToolPlan, type PiLaunchToolPlan } from "../runs/shared/child-tool-plan.ts";
11
- import { injectOutputPathSystemPrompt, normalizeSingleOutputOverride, resolveSingleOutputPath } from "../runs/shared/single-output.ts";
10
+ import { buildEffectiveSystemPrompt } from "../runs/shared/effective-system-prompt.ts";
11
+ import { 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
14
  import { assertThinkingWithinCeiling, intersectThinkingCeilings, type ThinkingLevel } from "../shared/thinking-ceiling.ts";
15
- import { SUBAGENT_LIFECYCLE_ARTIFACT_VERSION, type ArtifactDirPreference, type ArtifactPaths, type JsonSchemaObject, type OutputMode } from "../shared/types.ts";
15
+ import { SUBAGENT_LIFECYCLE_ARTIFACT_VERSION, type ArtifactDirPreference, type ArtifactPaths, type IntercomBridgeConfig, type IntercomBridgeMode, 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";
18
18
  import type { ResolvedMcpDirectToolSelection } from "../runs/shared/mcp-direct-tool-allowlist.ts";
19
19
  import { resolveStepBehavior } from "../shared/settings.ts";
20
20
  import { canPreferForkFromSnapshot, resolveSubagentLaunchContext } from "../shared/fork-context.ts";
21
21
  import { loadConfig } from "../extension/config.ts";
22
- import { agentDefinitionDigest, AGENT_DEFINITION_PROJECTION_VERSION, launchBindingDigest, stableJsonDigest } from "../shared/launch-contract.ts";
22
+ import { applyIntercomBridgeToAgent, resolveIntercomBridge, validateIntercomBridgeConfig } from "../intercom/intercom-bridge.ts";
23
+ import { AGENT_DEFINITION_PROJECTION_VERSION, resolveLaunchBinding, stableJsonDigest } from "../shared/launch-contract.ts";
23
24
  import { DIRS, TEMP_ROOT_DIR } from "../shared/types.ts";
24
25
  import { processTerminalCandidatePath, processTerminalPath } from "../runs/background/process-terminal.ts";
25
26
  import { resultFilePath } from "../runs/background/result-files.ts";
26
27
  import { nestedResultsPath } from "../runs/shared/nested-events.ts";
27
28
  import { normalizeExtensionBindings, type ExtensionBindings } from "../runs/shared/extension-bindings.ts";
28
29
 
29
- export const SUBAGENT_LAUNCH_CONTRACT_VERSION = 2 as const;
30
+ // v3: the contract reports the resolved Intercom bridge state and binds its
31
+ // prompt and tools into launchContractDigest, matching execution (#2127).
32
+ export const SUBAGENT_LAUNCH_CONTRACT_VERSION = 3 as const;
33
+
34
+ /** Stands in for the parent session target when the host does not supply one; only custom templates that name the session read it. */
35
+ const PREFLIGHT_ORCHESTRATOR_TARGET = "preflight";
30
36
 
31
37
  export type SubagentLaunchContractReasonCode =
32
38
  | "missing_agent"
@@ -38,7 +44,8 @@ export type SubagentLaunchContractReasonCode =
38
44
  | "unsupported_mode"
39
45
  | "restricted_agent"
40
46
  | "thinking_ceiling"
41
- | "invalid_extension_bindings";
47
+ | "invalid_extension_bindings"
48
+ | "invalid_intercom_bridge";
42
49
 
43
50
  export type SubagentLaunchContractDiagnosticCode = SubagentLaunchContractReasonCode | "host_required" | "snapshot_warning" | "workspace_scope_authority";
44
51
 
@@ -80,8 +87,23 @@ export interface SubagentLaunchContractInput {
80
87
  nestedRootRunId?: string;
81
88
  capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
82
89
  inheritedCapabilityCeiling?: ResolvedSubagentCapabilityCeiling;
90
+ /** Builtin tool names the host runtime provides; used to intersect agent-declared tools. */
91
+ hostAvailableBuiltins?: readonly string[];
92
+ /** Per-launch bridge config; replaces the global `intercomBridge` config exactly as the tool and delegation overrides do. */
93
+ intercomBridge?: IntercomBridgeConfig;
94
+ /**
95
+ * Supervisor session target the host will hand to the child. Only a custom
96
+ * bridge instruction file that names the session needs it; the default
97
+ * template is session-independent.
98
+ */
99
+ orchestratorTarget?: string;
83
100
  }
84
101
 
102
+ /** Bridge activation before tool capability ceilings are applied. */
103
+ export type SubagentLaunchContractIntercomBridge =
104
+ | { active: true; mode: Exclude<IntercomBridgeMode, "off"> }
105
+ | { active: false; mode: IntercomBridgeMode };
106
+
85
107
  export interface SubagentLaunchContractAgentCandidate {
86
108
  name: string;
87
109
  localName?: string;
@@ -162,6 +184,7 @@ export interface SubagentLaunchContract {
162
184
  inheritSkills: boolean;
163
185
  skills: SubagentLaunchContractSkills;
164
186
  tools: SubagentLaunchContractTools;
187
+ intercomBridge: SubagentLaunchContractIntercomBridge;
165
188
  roots: SubagentLaunchContractRoots;
166
189
  protocol: {
167
190
  lifecycleArtifactVersion: number;
@@ -257,6 +280,15 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
257
280
  if (input.artifactDir !== undefined && input.artifactDir !== "project" && input.artifactDir !== "session" && input.artifactDir !== "temp") {
258
281
  return { ok: false, code: "invalid_artifact_dir", message: `Unsupported artifactDir '${String(input.artifactDir)}'; expected 'project', 'session', or 'temp'.`, diagnostics };
259
282
  }
283
+ const bridgeOverride = input.intercomBridge === undefined ? undefined : validateIntercomBridgeConfig({ value: input.intercomBridge, label: "intercomBridge" });
284
+ if (bridgeOverride && !bridgeOverride.ok) {
285
+ return { ok: false, code: "invalid_intercom_bridge", message: bridgeOverride.error, diagnostics };
286
+ }
287
+ // Execution always derives a non-empty target, so an empty one here would
288
+ // silently deactivate the bridge and break parity instead of proving it.
289
+ if (input.orchestratorTarget !== undefined && (typeof input.orchestratorTarget !== "string" || !input.orchestratorTarget.trim())) {
290
+ return { ok: false, code: "invalid_intercom_bridge", message: "orchestratorTarget must be a non-empty string when provided.", diagnostics };
291
+ }
260
292
  const scope = resolveExecutionAgentScope(input.agentScope);
261
293
  const parentProvider = input.preferredProvider ?? input.parentModel?.provider;
262
294
  const discovery = discoverAgentSnapshot(effectiveCwd, scope, parentProvider, { includeChains: false });
@@ -276,20 +308,32 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
276
308
  if (!resolvedAgent.agent) {
277
309
  return { ok: false, code: "missing_agent", message: formatUnknownAgentError(input.agent, unknownAgentDiagnosticContext(discovered)), diagnostics };
278
310
  }
279
- const agent = resolvedAgent.agent;
311
+ const definitionAgent = resolvedAgent.agent;
280
312
  let extensionBindings: ExtensionBindings | undefined;
281
313
  try {
282
314
  extensionBindings = normalizeExtensionBindings(input.extensionBindings)?.value;
283
315
  } catch (error) {
284
316
  return { ok: false, code: "invalid_extension_bindings", message: error instanceof Error ? error.message : String(error), diagnostics };
285
317
  }
286
- if (extensionBindings !== undefined && (agent.runner?.type === "external-cli" || agent.runner?.type === "external-job")) {
287
- return { ok: false, code: "unsupported_mode", message: `extensionBindings is not supported for runner.type='${agent.runner.type}'.`, diagnostics };
318
+ if (extensionBindings !== undefined && (definitionAgent.runner?.type === "external-cli" || definitionAgent.runner?.type === "external-job")) {
319
+ return { ok: false, code: "unsupported_mode", message: `extensionBindings is not supported for runner.type='${definitionAgent.runner.type}'.`, diagnostics };
288
320
  }
289
- const context = resolveLaunchContractContext(input, agent);
321
+ const context = resolveLaunchContractContext(input, definitionAgent);
290
322
  if (context === "fork") {
291
- diagnostics.push({ code: "host_required", severity: "host-required", message: "Exact fork session branching and fork-thinking downgrade checks require Pi host session and model-registry snapshots." });
323
+ diagnostics.push({ code: "host_required", severity: "host-required", message: "Exact fork session branching requires Pi host session snapshots." });
324
+ }
325
+ // Execution rewrites the discovered agent through the bridge before any
326
+ // other launch resolution, so preflight must hash the same rewritten agent.
327
+ const bridge = resolveIntercomBridge({
328
+ config: loadConfig().intercomBridge,
329
+ ...(bridgeOverride ? { override: bridgeOverride.value } : {}),
330
+ context,
331
+ orchestratorTarget: input.orchestratorTarget ?? PREFLIGHT_ORCHESTRATOR_TARGET,
332
+ });
333
+ if (bridge.active && bridge.interpolatesOrchestratorTarget && input.orchestratorTarget === undefined) {
334
+ diagnostics.push({ code: "host_required", severity: "host-required", message: "The intercomBridge instruction file names the supervisor session; supply orchestratorTarget to bind the exact child prompt." });
292
335
  }
336
+ const agent = applyIntercomBridgeToAgent(definitionAgent, bridge);
293
337
  const effectiveCapabilityCeiling = intersectSubagentCapabilityCeilings(input.capabilityCeiling, input.inheritedCapabilityCeiling);
294
338
  const restrictionMessage = capabilityCeilingAgentRestrictionMessage(agent.name, effectiveCapabilityCeiling);
295
339
  if (restrictionMessage) return { ok: false, code: "restricted_agent", message: restrictionMessage, diagnostics };
@@ -371,6 +415,7 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
371
415
  capabilityCeiling: effectiveCapabilityCeiling,
372
416
  agentName: agent.name,
373
417
  permissionRules,
418
+ hostAvailableBuiltins: input.hostAvailableBuiltins,
374
419
  });
375
420
  } catch (error) {
376
421
  const message = error instanceof Error ? error.message : String(error);
@@ -399,17 +444,23 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
399
444
  if (resolvedSkills.missing.length > 0) {
400
445
  return { ok: false, code: "missing_skill", message: `Missing skills: ${resolvedSkills.missing.join(", ")}`, diagnostics };
401
446
  }
402
- let effectiveSystemPrompt = agent.systemPrompt?.trim() ?? "";
403
- if (resolvedSkills.resolved.length > 0) {
404
- const skillInjection = buildSkillInjection(resolvedSkills.resolved);
405
- effectiveSystemPrompt = effectiveSystemPrompt ? `${effectiveSystemPrompt}\n\n${skillInjection}` : skillInjection;
406
- }
407
- const memoryInjection = buildAgentMemoryInjection(agent, effectiveCwd);
408
- if (memoryInjection) effectiveSystemPrompt = effectiveSystemPrompt ? `${effectiveSystemPrompt}\n\n${memoryInjection}` : memoryInjection;
409
- effectiveSystemPrompt = injectOutputPathSystemPrompt(effectiveSystemPrompt, outputPath, agent);
447
+ const effectiveThinking = resolveEffectiveThinking(model, effectiveThinkingConfig);
448
+ const binding = resolveLaunchBinding({
449
+ agent,
450
+ task: input.task ?? "",
451
+ modelCandidates,
452
+ ...(fast !== undefined ? { fast } : {}),
453
+ ...(effectiveThinking ? { thinking: effectiveThinking } : {}),
454
+ systemPrompt: buildEffectiveSystemPrompt({ agent, resolvedSkills: resolvedSkills.resolved, cwd: effectiveCwd, ...(outputPath ? { outputPath } : {}) }),
455
+ skills: requestedSkills,
456
+ toolPlan,
457
+ ...(outputPath ? { outputPath } : {}),
458
+ outputMode: behavior.outputMode,
459
+ ...(input.outputSchema ? { structuredOutputSchema: input.outputSchema } : {}),
460
+ ...(extensionBindings ? { extensionBindings } : {}),
461
+ });
410
462
  const candidates = candidateList(input.agent, agent, discovery.all);
411
463
  const shadowedCandidates = candidates.filter((candidate) => !candidate.selected);
412
- const definitionDigest = agentDefinitionDigest(agent);
413
464
  const contractBase: Omit<SubagentLaunchContract, "digest"> = {
414
465
  version: SUBAGENT_LAUNCH_CONTRACT_VERSION,
415
466
  runId,
@@ -420,13 +471,13 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
420
471
  source: agent.source,
421
472
  filePath: agent.filePath,
422
473
  definitionProjectionVersion: AGENT_DEFINITION_PROJECTION_VERSION,
423
- definitionDigest,
474
+ definitionDigest: binding.definitionDigest,
424
475
  shadowedCandidates,
425
476
  },
426
477
  context,
427
478
  ...(model ? { model } : {}),
428
479
  modelCandidates,
429
- ...(resolveEffectiveThinking(model, effectiveThinkingConfig) ? { thinking: resolveEffectiveThinking(model, effectiveThinkingConfig) } : {}),
480
+ ...(effectiveThinking ? { thinking: effectiveThinking } : {}),
430
481
  ...(thinkingCeiling ? { thinkingCeiling } : {}),
431
482
  systemPromptMode: agent.systemPromptMode,
432
483
  inheritProjectContext: agent.inheritProjectContext,
@@ -456,6 +507,7 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
456
507
  ...(toolPlan.capabilityCeiling ? { capabilityCeiling: toolPlan.capabilityCeiling } : {}),
457
508
  ...(toolPlan.capabilityAudit ? { capabilityAudit: toolPlan.capabilityAudit } : {}),
458
509
  },
510
+ intercomBridge: bridge.active && bridge.mode !== "off" ? { active: true, mode: bridge.mode } : { active: false, mode: bridge.mode },
459
511
  roots: {
460
512
  cwd: effectiveCwd,
461
513
  ...(sessionRoot ? { sessionRoot } : {}),
@@ -477,28 +529,7 @@ export async function resolveSubagentLaunchContract(input: SubagentLaunchContrac
477
529
  packageVersion: packageVersion(),
478
530
  },
479
531
  diagnostics,
480
- launchContractDigest: launchBindingDigest({
481
- task: input.task ?? "",
482
- definitionDigest,
483
- ...(model ? { model } : {}),
484
- modelCandidates,
485
- ...(fast !== undefined ? { fast } : {}),
486
- ...(resolveEffectiveThinking(model, effectiveThinkingConfig) ? { thinking: resolveEffectiveThinking(model, effectiveThinkingConfig) } : {}),
487
- systemPrompt: effectiveSystemPrompt,
488
- systemPromptMode: agent.systemPromptMode,
489
- inheritProjectContext: agent.inheritProjectContext,
490
- inheritGlobalContext: agent.inheritGlobalContext,
491
- inheritSkills: agent.inheritSkills,
492
- skills: requestedSkills,
493
- tools: toolPlan.effectiveToolAllowlist,
494
- ...(toolPlan.excludeTools.length > 0 ? { excludeTools: toolPlan.excludeTools } : {}),
495
- extensions: toolPlan.extensionArgs,
496
- mcpDirectTools: toolPlan.effectiveMcpTools,
497
- ...(outputPath ? { outputPath } : {}),
498
- outputMode: behavior.outputMode,
499
- ...(input.outputSchema ? { structuredOutputSchema: input.outputSchema } : {}),
500
- ...(extensionBindings ? { extensionBindings } : {}),
501
- }),
532
+ launchContractDigest: binding.launchContractDigest,
502
533
  };
503
534
  return { ok: true, contract: { ...contractBase, digest: digestContract(contractBase) } };
504
535
  }
@@ -13,6 +13,8 @@ export {
13
13
  type ExecutionProjection,
14
14
  type ExternalJobRunnerStatus,
15
15
  type ExternalJobStatus,
16
+ type IntercomBridgeConfig,
17
+ type IntercomBridgeMode,
16
18
  type JsonSchemaObject,
17
19
  type OutputMode,
18
20
  type ReviewProjection,
@@ -23,5 +25,5 @@ export {
23
25
  type ManagedWorktreeProvider,
24
26
  type WorktreeNaming,
25
27
  type WorktreeProvider,
26
- type WorkflowResourceProvenanceV1,
28
+ type WorkflowResourceProvenance,
27
29
  } from "../shared/types.ts";
@@ -0,0 +1,6 @@
1
+ export { registerWorkflowResource } from "../workflows/workflow-resources.ts";
2
+ export type {
3
+ RegisterWorkflowResourceInput,
4
+ WorkflowResourceDefinition,
5
+ WorkflowResourceRegistration,
6
+ } from "../workflows/workflow-resources.ts";
@@ -9,11 +9,13 @@ import { resolveWaitToolConfig } from "../runs/background/wait-config.ts";
9
9
  import type { ChildRuntimeConfig } from "../runs/shared/child-runtime-config.ts";
10
10
  import { readNestedControlRequests, resolveInheritedNestedRoute, type NestedRoute, writeNestedControlResult } from "../runs/shared/nested-events.ts";
11
11
  import { deliverSubagentIntercomMessageEvent } from "../intercom/result-intercom.ts";
12
+ import { createNativeSupervisorChannel, NATIVE_SUPERVISOR_TOOL_NAME, resolveSupervisorChannelDir } from "../intercom/native-supervisor-channel.ts";
13
+ import { readStatus } from "../shared/utils.ts";
12
14
  import { resolveSubagentIntercomTarget } from "../intercom/intercom-bridge.ts";
13
15
  import { createSubagentParamsSchema } from "./schemas.ts";
14
16
  import { finalizeToolResult } from "./tool-result.ts";
15
17
  import { loadConfig, resolveAsyncByDefault } from "./config.ts";
16
- import { type Details, type SubagentState } from "../shared/types.ts";
18
+ import { SUBAGENT_ASYNC_STARTED_EVENT, type AsyncStartedEvent, type Details, type SubagentState } from "../shared/types.ts";
17
19
 
18
20
  function getSubagentSessionRoot(parentSessionFile: string | null): string {
19
21
  if (parentSessionFile) {
@@ -28,7 +30,7 @@ function expandTilde(p: string): string {
28
30
  return p.startsWith("~/") ? path.join(os.homedir(), p.slice(2)) : p;
29
31
  }
30
32
 
31
- function createChildSafeState(): SubagentState {
33
+ export function createChildSafeState(): SubagentState {
32
34
  return {
33
35
  baseCwd: "",
34
36
  currentSessionId: null,
@@ -141,7 +143,7 @@ function startNestedControlInboxListener(pi: ExtensionAPI, state: SubagentState,
141
143
  return () => clearInterval(timer);
142
144
  }
143
145
 
144
- /** Register the child-side `subagent` tool for fanout-authorized children. */
146
+ /** Register delegation and supervisor replies for fanout-authorized children. */
145
147
  export default function registerFanoutChildSubagentExtension(pi: ExtensionAPI, childConfig: ChildRuntimeConfig): void {
146
148
  if (!childConfig.fanoutChild) return;
147
149
 
@@ -156,7 +158,38 @@ export default function registerFanoutChildSubagentExtension(pi: ExtensionAPI, c
156
158
 
157
159
  const config = loadConfig();
158
160
  const waitToolConfig = resolveWaitToolConfig(config.waitTool);
159
- const state = createChildSafeState();
161
+ const state = childConfig.runtimeState ?? createChildSafeState();
162
+ const asyncChildren = new Map<string, { dir: string; agents: string[] }>();
163
+ const foregroundChannels = new Set<string>();
164
+ const supervisorChannel = createNativeSupervisorChannel(pi, state, {
165
+ getChannelDirs: () => {
166
+ const dirs = new Set<string>();
167
+ for (const run of state.foregroundControls.values()) {
168
+ for (const child of run.activeChildren?.values() ?? []) dirs.add(resolveSupervisorChannelDir(run.runId, child.agent, child.index));
169
+ }
170
+ for (const run of state.foregroundRuns?.values() ?? []) {
171
+ for (const child of run.children) {
172
+ if (child.status === "detached") dirs.add(resolveSupervisorChannelDir(run.runId, child.agent, child.index));
173
+ }
174
+ }
175
+ const retiringForeground = [...foregroundChannels].filter(dir => !dirs.has(dir));
176
+ for (const dir of dirs) foregroundChannels.add(dir);
177
+ for (const dir of retiringForeground) dirs.add(dir);
178
+ const retiringAsync: string[] = [];
179
+ for (const [id, child] of asyncChildren) {
180
+ const status = readStatus(child.dir);
181
+ if (status && status.state !== "queued" && status.state !== "running") retiringAsync.push(id);
182
+ for (const [index, agent] of child.agents.entries()) dirs.add(resolveSupervisorChannelDir(id, agent, index));
183
+ }
184
+ return {
185
+ dirs: [...dirs],
186
+ retire: () => {
187
+ for (const dir of retiringForeground) foregroundChannels.delete(dir);
188
+ for (const id of retiringAsync) asyncChildren.delete(id);
189
+ },
190
+ };
191
+ },
192
+ });
160
193
  const executor = createSubagentExecutor({
161
194
  pi,
162
195
  state,
@@ -170,6 +203,8 @@ export default function registerFanoutChildSubagentExtension(pi: ExtensionAPI, c
170
203
  discoverAgents,
171
204
  allowMutatingManagementActions: false,
172
205
  childRuntime: childConfig,
206
+ activateSupervisorTransport: supervisorChannel.activateTransport,
207
+ findPendingAsks: supervisorChannel.findPendingAsks,
173
208
  });
174
209
 
175
210
  const params = createSubagentParamsSchema();
@@ -188,6 +223,30 @@ export default function registerFanoutChildSubagentExtension(pi: ExtensionAPI, c
188
223
  };
189
224
 
190
225
  pi.registerTool(tool);
226
+ let unsubscribeAsyncStarted: (() => void) | undefined;
227
+ pi.on("session_start", (_event, ctx) => {
228
+ supervisorChannel.registerTools();
229
+ // The host applies the explicit allowlist to dynamic registration too.
230
+ if (!pi.getAllTools().some(tool => tool.name === NATIVE_SUPERVISOR_TOOL_NAME)) return;
231
+ // Downward asks belong to this coordinator, not its parent or persisted session file.
232
+ state.supervisorOwnerSessionId = ctx.sessionManager.getSessionId() || null;
233
+ unsubscribeAsyncStarted = pi.events.on(SUBAGENT_ASYNC_STARTED_EVENT, (payload: unknown) => {
234
+ const info = payload as AsyncStartedEvent;
235
+ if (!info.id || !info.asyncDir || info.sessionId !== state.currentSessionId) return;
236
+ const agents = info.agents ?? (info.agent ? [info.agent] : []);
237
+ asyncChildren.set(info.id, { dir: info.asyncDir, agents });
238
+ supervisorChannel.activateTransport();
239
+ });
240
+ supervisorChannel.start();
241
+ supervisorChannel.activateTransport();
242
+ });
243
+ pi.on("session_shutdown", () => {
244
+ unsubscribeAsyncStarted?.();
245
+ asyncChildren.clear();
246
+ foregroundChannels.clear();
247
+ supervisorChannel.dispose();
248
+ state.supervisorOwnerSessionId = null;
249
+ });
191
250
  const route = resolveNestedControlRoute(childConfig);
192
251
  if (!route) return;
193
252
  const listenerCleanupKey = "__piSubagentFanoutChildNestedControlInboxCleanups";
@@ -19,7 +19,8 @@ import * as path from "node:path";
19
19
  import type { AgentToolResult } from "@earendil-works/pi-agent-core";
20
20
  import { keyText, type ExtensionAPI, type ExtensionContext, type ToolDefinition } from "@earendil-works/pi-coding-agent";
21
21
  import { Box, Container, Spacer, Text, truncateToWidth, visibleWidth, wrapTextWithAnsi, type Component } from "@earendil-works/pi-tui";
22
- import { discoverAgentSnapshot, discoverAgents, type AgentConfig, type AgentScope } from "../agents/agents.ts";
22
+ import { clearAgentDiscoveryCache, discoverAgentSnapshot, discoverAgents, type AgentConfig, type AgentScope } from "../agents/agents.ts";
23
+ import { appendAdvertisedAgentPrompt, buildAdvertisedAgentPrompt } from "../agents/advertised-agent-prompt.ts";
23
24
  import { clearRuntimeAgentsForPi, listRuntimeAgentConfigs, mergeRuntimeAgents } from "../agents/runtime-agent-registry.ts";
24
25
  import { registerRuntimeAgentEventListener } from "../agents/runtime-agent-events.ts";
25
26
  import { ensureAccessibleDir } from "../shared/accessible-dir.ts";
@@ -29,8 +30,9 @@ import { getAgentDir } from "../shared/utils.ts";
29
30
  import { isStaleExtensionContextError, withCachedUiContext } from "../shared/extension-context.ts";
30
31
  import { currentCompletionOwnerId } from "../shared/completion-owner.ts";
31
32
  import { cleanupOldChainDirs } from "../shared/settings.ts";
32
- import { clearLegacyResultAnimationTimer, renderSubagentResult, renderSubagentSummary } from "../tui/render.ts";
33
+ import { clearLegacyResultAnimationTimer, renderSubagentResult, renderSubagentSummary, setInlineWorkflowCoverage } from "../tui/render.ts";
33
34
  import { openSubagentFleet } from "../tui/fleet.ts";
35
+ import { createBuiltinInspectorPlugins } from "../inspectors/plugins.ts";
34
36
  import { SubagentFleetStatus, resolveFleetViewPlacement } from "../tui/fleet-status.ts";
35
37
  import { createSubagentParamsSchema } from "./schemas.ts";
36
38
  import { createSubagentExecutor, type SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
@@ -335,14 +337,18 @@ function createSlashResultComponent(
335
337
  theme: ExtensionContext["ui"]["theme"],
336
338
  rendererConfig?: MainWindowRendererConfig,
337
339
  foregroundDetachShortcut?: string,
340
+ getCurrentTheme: () => ExtensionContext["ui"]["theme"] = () => theme,
338
341
  ): Container {
339
342
  const container = new Container();
340
343
  let lastVersion = -1;
344
+ let lastTheme: ExtensionContext["ui"]["theme"] | undefined;
341
345
  container.render = (width: number): string[] => {
342
346
  const snapshot = getSlashRenderableSnapshot(details);
343
- if (snapshot.version !== lastVersion || isSlashResultRunning(snapshot.result)) {
347
+ const currentTheme = getCurrentTheme();
348
+ if (snapshot.version !== lastVersion || currentTheme !== lastTheme || isSlashResultRunning(snapshot.result)) {
344
349
  lastVersion = snapshot.version;
345
- rebuildSlashResultContainer(container, snapshot.result, options, theme, rendererConfig, foregroundDetachShortcut);
350
+ lastTheme = currentTheme;
351
+ rebuildSlashResultContainer(container, snapshot.result, options, currentTheme, rendererConfig, foregroundDetachShortcut);
346
352
  }
347
353
  return Container.prototype.render.call(container, width);
348
354
  };
@@ -489,7 +495,9 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
489
495
  }, run);
490
496
  };
491
497
 
492
- const supervisorChannel = createNativeSupervisorChannel(pi, state);
498
+ const supervisorChannel = createNativeSupervisorChannel(pi, state, {
499
+ getCurrentOwnerStates: () => executor.getCurrentSupervisorOwnerStates(),
500
+ });
493
501
  const waitSubscriptionManager = createWaitSubscriptionManager(pi, state);
494
502
  const mainWatchdog = registerMainWatchdog(pi);
495
503
  const resultDeliveryOwnership = createResultDeliveryOwnership(state);
@@ -500,7 +508,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
500
508
  const ctx = withLastUiContext((current) => current);
501
509
  if (!ctx) return;
502
510
  try {
503
- await openSubagentFleet(ctx, state, { initialKey: itemKey, asyncDirRoot: DIRS.async, resultsDir: DIRS.results, fleetKeybindings: config.fleetKeybindings });
511
+ await openSubagentFleet(ctx, state, { initialKey: itemKey, asyncDirRoot: DIRS.async, resultsDir: DIRS.results, fleetKeybindings: config.fleetKeybindings, inspectorPlugins: createBuiltinInspectorPlugins() });
504
512
  } catch (error) {
505
513
  if (isStaleExtensionContextError(error)) {
506
514
  if (state.lastUiContext === ctx) state.lastUiContext = null;
@@ -508,7 +516,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
508
516
  }
509
517
  throw error;
510
518
  }
511
- }, { placement: fleetViewPlacement })
519
+ }, { placement: fleetViewPlacement, onWorkflowCoverageChange: setInlineWorkflowCoverage })
512
520
  : undefined;
513
521
  let executorScheduled: ((id: string, params: SubagentParamsLike, signal: AbortSignal, ctx: ExtensionContext) => Promise<AgentToolResult<Details>>) | undefined;
514
522
  let goalTurnId = 0;
@@ -531,6 +539,15 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
531
539
  resolveCapabilityCeiling: (sessionId) => resolveCurrentSubagentCapabilityCeiling(sessionId),
532
540
  });
533
541
  let refreshResultDelivery = () => {};
542
+ let advertisedAgents: AgentConfig[] = [];
543
+ let advertisedContext: Pick<ExtensionContext, "cwd" | "model"> | undefined;
544
+ const refreshAdvertisedAgents = () => {
545
+ advertisedAgents = [];
546
+ if (!advertisedContext) return;
547
+ clearAgentDiscoveryCache();
548
+ advertisedAgents = discoverAgents(advertisedContext.cwd, "both", advertisedContext.model?.provider).agents
549
+ .filter((agent) => agent.advertise === true);
550
+ };
534
551
  const hasResultDeliveryDemand = () => {
535
552
  if ([...state.asyncJobs.values()].some((job) => job.status === "queued" || job.status === "running")) return true;
536
553
  if (state.foregroundControls.size > 0) return true;
@@ -609,7 +626,16 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
609
626
  getSubagentSessionRoot,
610
627
  expandTilde,
611
628
  discoverAgents: discoverAgentsForRuntime,
629
+ onAgentsChanged: () => {
630
+ try {
631
+ refreshAdvertisedAgents();
632
+ } catch (error) {
633
+ // The mutation already persisted. Withdraw stale guidance, not its result.
634
+ console.error("Failed to refresh advertised agents; catalog withdrawn until refresh:", error);
635
+ }
636
+ },
612
637
  activateSupervisorTransport: () => supervisorChannel.activateTransport(),
638
+ findPendingAsks: (target) => supervisorChannel.findPendingAsks(target),
613
639
  refreshResultDelivery: () => refreshResultDelivery(),
614
640
  trackRetainedNestedRoute: undefined,
615
641
  };
@@ -627,7 +653,14 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
627
653
  pi.registerMessageRenderer<SlashMessageDetails>(SLASH_RESULT_TYPE, (message, options, theme) => {
628
654
  const details = resolveSlashMessageDetails(message.details);
629
655
  if (!details) return undefined;
630
- return createSlashResultComponent(details, options, theme, config.mainWindowRenderer, config.foregroundDetachShortcut);
656
+ return createSlashResultComponent(
657
+ details,
658
+ options,
659
+ theme,
660
+ config.mainWindowRenderer,
661
+ config.foregroundDetachShortcut,
662
+ () => state.lastUiContext?.ui.theme ?? theme,
663
+ );
631
664
  });
632
665
 
633
666
  pi.registerMessageRenderer<undefined>(SLASH_TEXT_RESULT_TYPE, (message, _options, _theme) => {
@@ -778,6 +811,16 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
778
811
 
779
812
  pi.registerTool(tool);
780
813
 
814
+ pi.on("before_agent_start", (event, ctx) => {
815
+ const selectedTools = event.systemPromptOptions?.selectedTools ?? (typeof pi.getActiveTools === "function" ? pi.getActiveTools() : []);
816
+ const sessionId = state.currentSessionId ?? resolveCurrentSessionId(ctx.sessionManager);
817
+ const advertisedPrompt = Array.isArray(selectedTools) && selectedTools.includes("subagent")
818
+ ? buildAdvertisedAgentPrompt(advertisedAgents, resolveCurrentSubagentCapabilityCeiling(sessionId))
819
+ : undefined;
820
+ const systemPrompt = appendAdvertisedAgentPrompt(event.systemPrompt, advertisedPrompt);
821
+ if (systemPrompt !== event.systemPrompt) return { systemPrompt };
822
+ });
823
+
781
824
  registerWaitTool(pi, state, waitToolConfig.enabled, waitSubscriptionManager, waitToolConfig.defaultTimeoutMs);
782
825
 
783
826
  pi.on("agent_end", async (_event, ctx) => {
@@ -912,6 +955,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
912
955
  const previousRuntimeSessionId = state.currentSessionId;
913
956
  resultDeliveryOwnership.claimPredecessor(previousSessionFile, previousRuntimeSessionId);
914
957
  state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
958
+ state.supervisorOwnerSessionId = ctx.sessionManager.getSessionId() || null;
915
959
  transitionResultDelivery();
916
960
  state.parentSessionFile = ctx.sessionManager.getSessionFile();
917
961
  state.trustedSessionFileRoot = state.parentSessionFile ? path.join(getAgentDir(), "sessions") : undefined;
@@ -1029,6 +1073,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
1029
1073
  promptTemplateBridge.dispose();
1030
1074
  state.widgetsSuspended = false;
1031
1075
  state.currentSessionId = null;
1076
+ state.supervisorOwnerSessionId = null;
1032
1077
  state.statusProjectionSessionId = null;
1033
1078
  state.parentSessionFile = null;
1034
1079
  parentSessionEnvValue = null;
@@ -1141,4 +1186,9 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
1141
1186
  }
1142
1187
  await herdrStatusBridge.flush();
1143
1188
  });
1189
+
1190
+ pi.on("session_start", (_event, ctx) => {
1191
+ advertisedContext = { cwd: ctx.cwd, model: ctx.model };
1192
+ refreshAdvertisedAgents();
1193
+ });
1144
1194
  }
@@ -73,7 +73,6 @@ export function normalizePublicSubagentExecution<T extends PublicSubagentExecuti
73
73
  }
74
74
  }
75
75
  if (params.baseRef !== undefined) {
76
- if (typeof params.baseRef !== "string") return { ok: false, error: "baseRef must be a valid Git ref.", mode: params.action === undefined ? "workflow" : "management" };
77
76
  try {
78
77
  normalizeWorktreeBaseRef(params.baseRef);
79
78
  } catch (error) {
@@ -98,8 +97,10 @@ export function normalizePublicSubagentExecution<T extends PublicSubagentExecuti
98
97
  if (hasCapacityOverride) {
99
98
  const capacityOverrideError = validateWorkflowCapacityOverrides(params);
100
99
  if (capacityOverrideError) return { ok: false, error: capacityOverrideError, mode: params.action === undefined ? "workflow" : "management" };
101
- if (params.action !== undefined || hasNamedWorkflow || (params.workflowScript === undefined && params.workflowScriptPath === undefined)) {
102
- return { ok: false, error: "Workflow capacity overrides are only supported on top-level workflowScript or workflowScriptPath calls.", mode: params.action === undefined ? "workflow" : "management" };
100
+ const validatesSpawnBudget = typeof params.action === "string" && params.action.trim() === "validate"
101
+ && params.globalConcurrencyLimit === undefined && params.maxSubagentSpawnsPerRun !== undefined;
102
+ if ((params.action !== undefined && !validatesSpawnBudget) || hasNamedWorkflow || (params.workflowScript === undefined && params.workflowScriptPath === undefined)) {
103
+ return { ok: false, error: "Workflow capacity overrides are only supported on top-level workflowScript or workflowScriptPath calls; validate accepts maxSubagentSpawnsPerRun for static budget checks.", mode: params.action === undefined ? "workflow" : "management" };
103
104
  }
104
105
  }
105
106
  if (params.preflight !== undefined && !hasWorkflowInput) {