pi-subagents 0.66.0 → 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 (115) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +4 -3
  3. package/agents/evidence-auditor.md +34 -0
  4. package/agents/reviewer.md +3 -2
  5. package/docs/agents.md +6 -3
  6. package/docs/configuration.md +7 -5
  7. package/docs/extension-api.md +33 -18
  8. package/docs/missions.md +8 -0
  9. package/docs/models.md +1 -1
  10. package/docs/observability.md +4 -4
  11. package/docs/standalone-background.md +49 -0
  12. package/docs/tool-reference.md +18 -8
  13. package/docs/watchdog.md +35 -4
  14. package/docs/workflows.md +26 -12
  15. package/inspector-runner.mjs +2 -2
  16. package/package.json +1 -1
  17. package/prompts/parallel-review.md +1 -1
  18. package/{runner-server-preload.mjs → runner-peer-preload.mjs} +8 -3
  19. package/skills/pi-subagents/SKILL.md +14 -0
  20. package/skills/pi-subagents/references/execution-controls.md +7 -5
  21. package/skills/pi-subagents/references/prompting-and-roles.md +2 -2
  22. package/src/agents/advertised-agent-prompt.ts +34 -3
  23. package/src/agents/agents.ts +6 -0
  24. package/src/agents/builtin-names.ts +1 -0
  25. package/src/api/delegation.ts +4 -0
  26. package/src/api/preflight.ts +76 -45
  27. package/src/api/shared-types.ts +2 -0
  28. package/src/extension/fanout-child.ts +63 -4
  29. package/src/extension/index.ts +20 -8
  30. package/src/extension/public-execution.ts +4 -2
  31. package/src/extension/rpc.ts +4 -0
  32. package/src/extension/schemas.ts +67 -78
  33. package/src/extension/tool-description.ts +29 -82
  34. package/src/inspectors/actions.ts +148 -0
  35. package/src/inspectors/ghostty/actions.ts +74 -0
  36. package/src/inspectors/ghostty/plugin.ts +17 -0
  37. package/src/inspectors/herdr/actions.ts +99 -179
  38. package/src/inspectors/herdr/plugin.ts +20 -0
  39. package/src/inspectors/herdr/project-panes.ts +1 -1
  40. package/src/inspectors/{herdr/inspector-runner.ts → inspector-runner.ts} +12 -12
  41. package/src/inspectors/plugins.ts +8 -0
  42. package/src/inspectors/{herdr/session-roots-codec.ts → session-roots-codec.ts} +3 -14
  43. package/src/inspectors/types.ts +51 -0
  44. package/src/intercom/intercom-bridge.ts +50 -8
  45. package/src/intercom/native-supervisor-channel.ts +22 -13
  46. package/src/runs/background/active-async-capacity.ts +4 -0
  47. package/src/runs/background/async-execution.ts +45 -56
  48. package/src/runs/background/async-resume.ts +5 -9
  49. package/src/runs/background/auto-drain.ts +6 -3
  50. package/src/runs/background/binary-bootstrap.ts +33 -0
  51. package/src/runs/background/fleet-view.ts +30 -2
  52. package/src/runs/background/notify.ts +31 -1
  53. package/src/runs/background/owned-process-tree.ts +29 -2
  54. package/src/runs/background/run-child-session.ts +61 -4
  55. package/src/runs/background/run-status.ts +3 -0
  56. package/src/runs/background/runner-aliases.ts +11 -3
  57. package/src/runs/background/runner-child-launch.ts +2 -0
  58. package/src/runs/background/runner-child-sessions.ts +5 -4
  59. package/src/runs/background/scheduled-runs.ts +40 -13
  60. package/src/runs/background/steering.ts +20 -2
  61. package/src/runs/background/subagent-runner.ts +47 -31
  62. package/src/runs/background/subagent-wait.ts +51 -8
  63. package/src/runs/background/wait-tool.ts +1 -1
  64. package/src/runs/foreground/async-steering-action.ts +18 -7
  65. package/src/runs/foreground/execution.ts +44 -31
  66. package/src/runs/foreground/prompt-audit.ts +3 -1
  67. package/src/runs/foreground/subagent-executor.ts +110 -100
  68. package/src/runs/foreground/workflow-detach-reconcile.ts +2 -0
  69. package/src/runs/foreground/workflow-foreground-steering.ts +2 -1
  70. package/src/runs/shared/acceptance.ts +5 -2
  71. package/src/runs/shared/async-status-projection.ts +4 -0
  72. package/src/runs/shared/capability-ceiling.ts +2 -0
  73. package/src/runs/shared/child-hooks.ts +25 -10
  74. package/src/runs/shared/child-launch.ts +12 -2
  75. package/src/runs/shared/child-lifecycle.ts +6 -3
  76. package/src/runs/shared/child-runtime-config.ts +3 -1
  77. package/src/runs/shared/child-session.ts +33 -2
  78. package/src/runs/shared/child-tool-plan.ts +122 -3
  79. package/src/runs/shared/completion-guard.ts +5 -3
  80. package/src/runs/shared/effective-system-prompt.ts +33 -0
  81. package/src/runs/shared/external-cli-runner.ts +9 -7
  82. package/src/runs/shared/llm-intent-arbiter.ts +12 -3
  83. package/src/runs/shared/model-fallback.ts +2 -0
  84. package/src/runs/shared/orca-progress-tabs.ts +1 -1
  85. package/src/runs/shared/pi-spawn.ts +10 -0
  86. package/src/runs/shared/subagent-prompt-runtime.ts +9 -3
  87. package/src/runs/shared/task-intent.ts +46 -13
  88. package/src/runs/shared/workflow-async-child-guidance.ts +18 -0
  89. package/src/runs/shared/worktree.ts +42 -12
  90. package/src/shared/fork-context.ts +15 -72
  91. package/src/shared/launch-contract.ts +65 -2
  92. package/src/shared/opencode-session-headers.ts +30 -0
  93. package/src/shared/types.ts +4 -1
  94. package/src/slash/delegation-adapters.ts +3 -1
  95. package/src/slash/delegation-request.ts +14 -0
  96. package/src/slash/slash-commands.ts +2 -1
  97. package/src/slash/subagents-admin.ts +11 -4
  98. package/src/tui/fleet-status.ts +164 -19
  99. package/src/tui/fleet.ts +16 -14
  100. package/src/tui/render.ts +149 -28
  101. package/src/watchdog/child-status.ts +8 -0
  102. package/src/watchdog/model-selection.ts +20 -0
  103. package/src/watchdog/permission-arbiter.ts +3 -1
  104. package/src/watchdog/register-child.ts +1 -0
  105. package/src/watchdog/register-main.ts +31 -27
  106. package/src/watchdog/review.ts +132 -67
  107. package/src/watchdog/runtime.ts +82 -20
  108. package/src/watchdog/scope.ts +1 -1
  109. package/src/watchdog/settings.ts +9 -3
  110. package/src/watchdog/tool-actions.ts +13 -12
  111. package/src/watchdog/turn-delta.ts +23 -0
  112. package/src/watchdog/types.ts +4 -0
  113. package/src/workflows/scripted-workflow.ts +237 -7
  114. package/src/workflows/workflow-checklist.ts +2 -2
  115. /package/src/inspectors/{herdr/shell-command.ts → shell-command.ts} +0 -0
@@ -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,
@@ -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";
@@ -30,8 +30,9 @@ import { getAgentDir } from "../shared/utils.ts";
30
30
  import { isStaleExtensionContextError, withCachedUiContext } from "../shared/extension-context.ts";
31
31
  import { currentCompletionOwnerId } from "../shared/completion-owner.ts";
32
32
  import { cleanupOldChainDirs } from "../shared/settings.ts";
33
- import { clearLegacyResultAnimationTimer, renderSubagentResult, renderSubagentSummary } from "../tui/render.ts";
33
+ import { clearLegacyResultAnimationTimer, renderSubagentResult, renderSubagentSummary, setInlineWorkflowCoverage } from "../tui/render.ts";
34
34
  import { openSubagentFleet } from "../tui/fleet.ts";
35
+ import { createBuiltinInspectorPlugins } from "../inspectors/plugins.ts";
35
36
  import { SubagentFleetStatus, resolveFleetViewPlacement } from "../tui/fleet-status.ts";
36
37
  import { createSubagentParamsSchema } from "./schemas.ts";
37
38
  import { createSubagentExecutor, type SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
@@ -336,14 +337,18 @@ function createSlashResultComponent(
336
337
  theme: ExtensionContext["ui"]["theme"],
337
338
  rendererConfig?: MainWindowRendererConfig,
338
339
  foregroundDetachShortcut?: string,
340
+ getCurrentTheme: () => ExtensionContext["ui"]["theme"] = () => theme,
339
341
  ): Container {
340
342
  const container = new Container();
341
343
  let lastVersion = -1;
344
+ let lastTheme: ExtensionContext["ui"]["theme"] | undefined;
342
345
  container.render = (width: number): string[] => {
343
346
  const snapshot = getSlashRenderableSnapshot(details);
344
- if (snapshot.version !== lastVersion || isSlashResultRunning(snapshot.result)) {
347
+ const currentTheme = getCurrentTheme();
348
+ if (snapshot.version !== lastVersion || currentTheme !== lastTheme || isSlashResultRunning(snapshot.result)) {
345
349
  lastVersion = snapshot.version;
346
- rebuildSlashResultContainer(container, snapshot.result, options, theme, rendererConfig, foregroundDetachShortcut);
350
+ lastTheme = currentTheme;
351
+ rebuildSlashResultContainer(container, snapshot.result, options, currentTheme, rendererConfig, foregroundDetachShortcut);
347
352
  }
348
353
  return Container.prototype.render.call(container, width);
349
354
  };
@@ -503,7 +508,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
503
508
  const ctx = withLastUiContext((current) => current);
504
509
  if (!ctx) return;
505
510
  try {
506
- 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() });
507
512
  } catch (error) {
508
513
  if (isStaleExtensionContextError(error)) {
509
514
  if (state.lastUiContext === ctx) state.lastUiContext = null;
@@ -511,7 +516,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
511
516
  }
512
517
  throw error;
513
518
  }
514
- }, { placement: fleetViewPlacement })
519
+ }, { placement: fleetViewPlacement, onWorkflowCoverageChange: setInlineWorkflowCoverage })
515
520
  : undefined;
516
521
  let executorScheduled: ((id: string, params: SubagentParamsLike, signal: AbortSignal, ctx: ExtensionContext) => Promise<AgentToolResult<Details>>) | undefined;
517
522
  let goalTurnId = 0;
@@ -648,7 +653,14 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
648
653
  pi.registerMessageRenderer<SlashMessageDetails>(SLASH_RESULT_TYPE, (message, options, theme) => {
649
654
  const details = resolveSlashMessageDetails(message.details);
650
655
  if (!details) return undefined;
651
- 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
+ );
652
664
  });
653
665
 
654
666
  pi.registerMessageRenderer<undefined>(SLASH_TEXT_RESULT_TYPE, (message, _options, _theme) => {
@@ -800,9 +812,9 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
800
812
  pi.registerTool(tool);
801
813
 
802
814
  pi.on("before_agent_start", (event, ctx) => {
803
- const selectedTools = event.systemPromptOptions.selectedTools ?? pi.getActiveTools();
815
+ const selectedTools = event.systemPromptOptions?.selectedTools ?? (typeof pi.getActiveTools === "function" ? pi.getActiveTools() : []);
804
816
  const sessionId = state.currentSessionId ?? resolveCurrentSessionId(ctx.sessionManager);
805
- const advertisedPrompt = selectedTools.includes("subagent")
817
+ const advertisedPrompt = Array.isArray(selectedTools) && selectedTools.includes("subagent")
806
818
  ? buildAdvertisedAgentPrompt(advertisedAgents, resolveCurrentSubagentCapabilityCeiling(sessionId))
807
819
  : undefined;
808
820
  const systemPrompt = appendAdvertisedAgentPrompt(event.systemPrompt, advertisedPrompt);
@@ -97,8 +97,10 @@ export function normalizePublicSubagentExecution<T extends PublicSubagentExecuti
97
97
  if (hasCapacityOverride) {
98
98
  const capacityOverrideError = validateWorkflowCapacityOverrides(params);
99
99
  if (capacityOverrideError) return { ok: false, error: capacityOverrideError, mode: params.action === undefined ? "workflow" : "management" };
100
- if (params.action !== undefined || hasNamedWorkflow || (params.workflowScript === undefined && params.workflowScriptPath === undefined)) {
101
- 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" };
102
104
  }
103
105
  }
104
106
  if (params.preflight !== undefined && !hasWorkflowInput) {
@@ -499,9 +499,13 @@ function manageParams(params: unknown): SubagentParamsLike {
499
499
  if (requiresId && typeof input.id !== "string") {
500
500
  throw new SubagentRpcError("invalid_params", `RPC manage ${action} requires id.`);
501
501
  }
502
+ if (action === "schedule.run" && input.quiet !== undefined && typeof input.quiet !== "boolean") {
503
+ throw new SubagentRpcError("invalid_params", "RPC manage quiet must be a boolean.");
504
+ }
502
505
  const output: SubagentParamsLike = {
503
506
  action,
504
507
  ...(typeof input.id === "string" ? { id: input.id.trim() } : {}),
508
+ ...(action === "schedule.run" && input.quiet === true ? { quiet: true } : {}),
505
509
  };
506
510
  assertSubagentParams(output, "RPC manage params");
507
511
  return output;