pi-subagents 0.35.1 → 0.37.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 (89) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/README.md +224 -31
  3. package/agents/advisor.md +73 -0
  4. package/agents/delegate.md +2 -0
  5. package/agents/worker.md +2 -0
  6. package/package.json +11 -13
  7. package/skills/pi-subagents/SKILL.md +42 -13
  8. package/src/agents/agent-management.ts +7 -1
  9. package/src/agents/agents.ts +121 -17
  10. package/src/api/capability-ceiling.ts +17 -0
  11. package/src/api/delegation.ts +127 -0
  12. package/src/api/preflight.ts +399 -0
  13. package/src/extension/config.ts +7 -1
  14. package/src/extension/index.ts +52 -38
  15. package/src/extension/rpc.ts +31 -2
  16. package/src/extension/schemas.ts +29 -3
  17. package/src/extension/tool-description.ts +4 -2
  18. package/src/intercom/intercom-bridge.ts +1 -1
  19. package/src/intercom/native-supervisor-channel.ts +45 -6
  20. package/src/intercom/result-intercom.ts +7 -0
  21. package/src/runs/background/async-execution.ts +158 -17
  22. package/src/runs/background/async-job-tracker.ts +4 -0
  23. package/src/runs/background/async-resume.ts +47 -8
  24. package/src/runs/background/async-status.ts +94 -3
  25. package/src/runs/background/chain-append.ts +2 -0
  26. package/src/runs/background/completion-batcher.ts +6 -4
  27. package/src/runs/background/completion-dedupe.ts +2 -11
  28. package/src/runs/background/fleet-view.ts +9 -4
  29. package/src/runs/background/notify.ts +132 -120
  30. package/src/runs/background/process-terminal.ts +280 -0
  31. package/src/runs/background/result-watcher.ts +138 -78
  32. package/src/runs/background/run-status.ts +10 -2
  33. package/src/runs/background/scheduled-runs.ts +6 -1
  34. package/src/runs/background/stale-run-reconciler.ts +6 -0
  35. package/src/runs/background/subagent-runner.ts +406 -54
  36. package/src/runs/background/subagent-wait.ts +130 -4
  37. package/src/runs/background/wait-tool.ts +2 -2
  38. package/src/runs/foreground/chain-execution.ts +181 -111
  39. package/src/runs/foreground/execution.ts +147 -47
  40. package/src/runs/foreground/foreground-control.ts +90 -0
  41. package/src/runs/foreground/subagent-executor.ts +426 -165
  42. package/src/runs/shared/acceptance.ts +94 -41
  43. package/src/runs/shared/agent-contract.ts +38 -0
  44. package/src/runs/shared/capability-ceiling.ts +177 -0
  45. package/src/runs/shared/child-protocol.ts +1 -1
  46. package/src/runs/shared/completion-guard.ts +36 -5
  47. package/src/runs/shared/context-mode.ts +44 -0
  48. package/src/runs/shared/dynamic-fanout.ts +5 -5
  49. package/src/runs/shared/long-running-guard.ts +4 -0
  50. package/src/runs/shared/mcp-direct-tool-allowlist.ts +12 -6
  51. package/src/runs/shared/nested-events.ts +35 -3
  52. package/src/runs/shared/parallel-handoff.ts +154 -0
  53. package/src/runs/shared/parallel-utils.ts +11 -0
  54. package/src/runs/shared/pi-args.ts +148 -56
  55. package/src/runs/shared/run-history.ts +90 -5
  56. package/src/runs/shared/session-lease.ts +25 -5
  57. package/src/runs/shared/structured-output.ts +112 -7
  58. package/src/runs/shared/subagent-control.ts +4 -0
  59. package/src/runs/shared/subagent-prompt-runtime.ts +31 -19
  60. package/src/runs/shared/task-intent.ts +10 -5
  61. package/src/runs/shared/tool-availability.ts +21 -3
  62. package/src/runs/shared/tool-budget.ts +11 -5
  63. package/src/runs/shared/turn-budget.ts +2 -1
  64. package/src/runs/shared/worktree.ts +63 -14
  65. package/src/shared/accessible-dir.ts +25 -0
  66. package/src/shared/artifacts.ts +37 -7
  67. package/src/shared/atomic-json.ts +14 -42
  68. package/src/shared/child-transcript.ts +52 -0
  69. package/src/shared/file-system-retry.ts +47 -0
  70. package/src/shared/launch-contract.ts +123 -0
  71. package/src/shared/settings.ts +9 -1
  72. package/src/shared/types.ts +314 -28
  73. package/src/shared/utils.ts +17 -42
  74. package/src/slash/delegation-adapters.ts +153 -6
  75. package/src/slash/delegation-json.ts +108 -0
  76. package/src/slash/delegation-request.ts +182 -36
  77. package/src/slash/prompt-template-bridge.ts +222 -37
  78. package/src/slash/selector.ts +147 -0
  79. package/src/slash/slash-commands.ts +15 -6
  80. package/src/slash/slash-live-state.ts +2 -2
  81. package/src/slash/subagents-admin.ts +42 -42
  82. package/src/tui/fleet-status.ts +362 -0
  83. package/src/tui/fleet-transcript.ts +472 -0
  84. package/src/tui/fleet.ts +318 -59
  85. package/src/tui/render.ts +17 -15
  86. package/src/watchdog/change-signature.ts +105 -12
  87. package/src/watchdog/review.ts +7 -2
  88. package/src/watchdog/runtime.ts +5 -3
  89. package/src/slash/subagents-editor.ts +0 -86
@@ -6,7 +6,14 @@ import { resolveAsyncRunLocation } from "../runs/background/async-resume.ts";
6
6
  import { deliverStopRequest } from "../runs/background/control-channel.ts";
7
7
  import { reconcileAsyncRun } from "../runs/background/stale-run-reconciler.ts";
8
8
  import type { SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
9
- import { type Details, ASYNC_DIR, RESULTS_DIR } from "../shared/types.ts";
9
+ import {
10
+ type Details,
11
+ ASYNC_DIR,
12
+ RESULTS_DIR,
13
+ SUBAGENT_ASYNC_COMPLETE_EVENT,
14
+ SUBAGENT_PROCESS_TERMINAL_EVENT,
15
+ SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
16
+ } from "../shared/types.ts";
10
17
  import { readStatus } from "../shared/utils.ts";
11
18
  import { SubagentParams } from "./schemas.ts";
12
19
  import { validateChainInput } from "./chain-validation.ts";
@@ -16,7 +23,7 @@ export const SUBAGENT_RPC_REQUEST_EVENT = "subagents:rpc:v1:request";
16
23
  export const SUBAGENT_RPC_READY_EVENT = "subagents:rpc:v1:ready";
17
24
  export const SUBAGENT_RPC_REPLY_EVENT_PREFIX = "subagents:rpc:v1:reply:";
18
25
 
19
- export const SUBAGENT_RPC_METHODS = ["ping", "status", "spawn", "interrupt", "stop"] as const;
26
+ export const SUBAGENT_RPC_METHODS = ["ping", "status", "spawn", "steer", "interrupt", "stop"] as const;
20
27
  export type SubagentRpcMethod = typeof SUBAGENT_RPC_METHODS[number];
21
28
 
22
29
  export interface SubagentRpcRequestEnvelope {
@@ -172,13 +179,18 @@ function pingData(ctx: ExtensionContext | null) {
172
179
  capabilities: {
173
180
  status: true,
174
181
  asyncSpawn: true,
182
+ steer: true,
183
+ nonRecoveringSteer: true,
175
184
  interrupt: true,
176
185
  stop: true,
186
+ processTerminalProof: { version: 1, lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION },
177
187
  },
178
188
  events: {
179
189
  ready: SUBAGENT_RPC_READY_EVENT,
180
190
  request: SUBAGENT_RPC_REQUEST_EVENT,
181
191
  replyPrefix: SUBAGENT_RPC_REPLY_EVENT_PREFIX,
192
+ asyncComplete: SUBAGENT_ASYNC_COMPLETE_EVENT,
193
+ processTerminal: SUBAGENT_PROCESS_TERMINAL_EVENT,
182
194
  },
183
195
  session: sessionData(ctx),
184
196
  };
@@ -212,6 +224,20 @@ function spawnParams(params: unknown): SubagentParamsLike {
212
224
  return { ...(input as SubagentParamsLike), async: true, clarify: false };
213
225
  }
214
226
 
227
+ function steerParams(params: unknown): SubagentParamsLike {
228
+ const input = assertRecordParams(params, "steer");
229
+ if (typeof input.message !== "string" || !input.message.trim())
230
+ throw new SubagentRpcError("invalid_params", "RPC steer requires a non-empty message.");
231
+ const target = normalizeTargetParams(input, "steer");
232
+ if (!target.id && !target.runId && !target.dir) throw new SubagentRpcError("invalid_params", "RPC steer requires id, runId, or dir.");
233
+ return {
234
+ action: "steer",
235
+ ...target,
236
+ message: input.message.trim(),
237
+ steeringRecovery: false,
238
+ };
239
+ }
240
+
215
241
  function stopAsyncRun(
216
242
  params: unknown,
217
243
  options: RegisterSubagentRpcBridgeOptions,
@@ -289,6 +315,9 @@ async function handleRequest(
289
315
  if (request.method === "status") {
290
316
  return executeChecked(options, ctx, request.requestId, request.method, { action: "status", ...normalizeTargetParams(request.params, "status") });
291
317
  }
318
+ if (request.method === "steer") {
319
+ return executeChecked(options, ctx, request.requestId, request.method, steerParams(request.params));
320
+ }
292
321
  if (request.method === "interrupt") {
293
322
  return executeChecked(options, ctx, request.requestId, request.method, { action: "interrupt", ...normalizeTargetParams(request.params, "interrupt") });
294
323
  }
@@ -67,11 +67,26 @@ const JsonSchemaObject = Type.Unsafe({
67
67
 
68
68
  const AcceptanceOverride = Type.Unsafe({
69
69
  anyOf: [
70
- { type: "string", enum: ["auto", "attested", "checked", "verified", "reviewed"] },
70
+ { type: "string", enum: ["auto", "attested", "checked", "verified"] },
71
+ {
72
+ type: "string",
73
+ enum: ["reviewed"],
74
+ deprecated: true,
75
+ description: "Invalid as an explicit policy. Recognized only so preflight can explain that reviewed is an achieved status.",
76
+ },
71
77
  { type: "boolean", enum: [false] },
72
78
  { type: "object", additionalProperties: true },
73
79
  ],
74
- description: "Optional acceptance policy. Omitted means auto-inferred; verified requires configured runtime commands. Reviewed is inferred-only because explicit runs cannot supply an independent reviewer result. Bare \"none\" requires { level: \"none\", reason: \"...\" }, while false is deprecated.",
80
+ description: "Optional acceptance policy. For reviewer/read-only calls, omit acceptance. Evidence levels end at verified; require independent review with acceptance.review.required. The value reviewed is an achieved status and is accepted by the schema only for actionable preflight recovery. In the current/default contract, omitted means auto-inferred. With agentContract.version=1, omitted means not requested and acceptance failures are reported separately from execution.",
81
+ });
82
+
83
+ const AgentContractOverride = Type.Object({
84
+ version: Type.Integer({ enum: [1], description: "Opt into generic agent contract v1 for this run/child." }),
85
+ }, { additionalProperties: false, description: "Opt-in compatibility contract. Omit to use current default behavior." });
86
+
87
+ const ChainGateOverride = Type.String({
88
+ enum: ["execution", "acceptance"],
89
+ description: "For agentContract.version=1 chain steps, choose whether the chain advances on execution success or acceptance success. Defaults to execution.",
75
90
  });
76
91
 
77
92
  const TurnBudgetOverride = Type.Object({
@@ -104,7 +119,9 @@ const TaskItem = Type.Object({
104
119
  model: Type.Optional(Type.String({ description: "Override model for this task (e.g. 'google/gemini-3-pro')" })),
105
120
  skill: Type.Optional(SkillOverride),
106
121
  toolBudget: Type.Optional(ToolBudgetOverride),
122
+ outputSchema: Type.Optional(JsonSchemaObject),
107
123
  acceptance: Type.Optional(AcceptanceOverride),
124
+ agentContract: Type.Optional(AgentContractOverride),
108
125
  });
109
126
 
110
127
  // Parallel task item (within a parallel step)
@@ -125,6 +142,8 @@ export const ParallelTaskSchema = Type.Object({
125
142
  model: Type.Optional(Type.String({ description: "Override model for this task" })),
126
143
  toolBudget: Type.Optional(ToolBudgetOverride),
127
144
  acceptance: Type.Optional(AcceptanceOverride),
145
+ agentContract: Type.Optional(AgentContractOverride),
146
+ gateOn: Type.Optional(ChainGateOverride),
128
147
  });
129
148
 
130
149
  export const DynamicExpandSchema = Type.Object({
@@ -153,6 +172,8 @@ export const DynamicParallelTemplateSchema = Type.Object({
153
172
  model: Type.Optional(Type.String({ description: "Override model for this task" })),
154
173
  toolBudget: Type.Optional(ToolBudgetOverride),
155
174
  acceptance: Type.Optional(AcceptanceOverride),
175
+ agentContract: Type.Optional(AgentContractOverride),
176
+ gateOn: Type.Optional(ChainGateOverride),
156
177
  }, { additionalProperties: false });
157
178
 
158
179
  export const DynamicCollectSchema = Type.Object({
@@ -179,6 +200,8 @@ export const ChainItem = Type.Object({
179
200
  model: Type.Optional(Type.String({ description: "Override model for this step" })),
180
201
  toolBudget: Type.Optional(ToolBudgetOverride),
181
202
  acceptance: Type.Optional(AcceptanceOverride),
203
+ agentContract: Type.Optional(AgentContractOverride),
204
+ gateOn: Type.Optional(ChainGateOverride),
182
205
  parallel: Type.Optional(Type.Unsafe({
183
206
  anyOf: [
184
207
  Type.Array(ParallelTaskSchema, { minItems: 1, description: "Tasks to run in parallel" }),
@@ -218,7 +241,7 @@ const SubagentParamsSchema = Type.Object({
218
241
  task: Type.Optional(Type.String({ description: "Task (SINGLE mode, optional for self-contained agents)" })),
219
242
  // Management action (when present, tool operates in management mode)
220
243
  action: Type.Optional(Type.String({
221
- description: "Management/control action only. Must be omitted for execution mode (single, parallel, or chain)."
244
+ description: "Optional management/control action. Omit this field entirely for execution/delegation ({agent, task}, {tasks}, or {chain}); use it only for management/control actions."
222
245
  })),
223
246
  id: Type.Optional(Type.String({
224
247
  description: "Run id or prefix for action='status', action='interrupt', action='stop', action='resume', action='steer', or action='append-step'."
@@ -236,6 +259,7 @@ const SubagentParamsSchema = Type.Object({
236
259
  })),
237
260
  lines: Type.Optional(Type.Integer({ minimum: 1, maximum: 500, description: "Maximum transcript lines for action='status', view='transcript'. Defaults to 80." })),
238
261
  message: Type.Optional(Type.String({ description: "Follow-up message for action='resume' (revive paused, completed, or failed children, or reach a routed nested run) or live async guidance for action='steer'. Stopped runs are non-resumable. Use index to choose a child from multi-child runs." })),
262
+ steeringRecovery: Type.Optional(Type.Boolean({ description: "For action='steer', allow pause-and-revive recovery after a missed acknowledgment. Defaults true for direct tool calls; extension RPC steering forces false so callers retain exact child ownership." })),
239
263
  additional: Type.Optional(Type.Integer({ minimum: 1, description: "Positive launches to add with action='grant-spawn-budget'. Root interactive parent with native user confirmation only; total grants cannot exceed the original configured cap." })),
240
264
  scope: Type.Optional(Type.String({ enum: ["session", "user", "project"], description: "Scope for action='watchdog.configure'. Defaults to session to avoid persistent settings writes unless user/project is explicit." })),
241
265
  target: Type.Optional(Type.String({ enum: ["main", "children", "child"], description: "Target for action='watchdog.configure'. Defaults to main. Use target='child' with agent for a per-agent child watchdog override." })),
@@ -292,6 +316,8 @@ const SubagentParamsSchema = Type.Object({
292
316
  outputMode: Type.Optional(OutputModeOverride),
293
317
  skill: Type.Optional(SkillOverride),
294
318
  model: Type.Optional(Type.String({ description: "Override model for single agent (e.g. 'anthropic/claude-sonnet-4')" })),
319
+ outputSchema: Type.Optional(JsonSchemaObject),
320
+ agentContract: Type.Optional(AgentContractOverride),
295
321
  acceptance: Type.Optional(AcceptanceOverride),
296
322
  });
297
323
 
@@ -14,7 +14,7 @@ export const SUBAGENT_SAFETY_GUIDANCE = `SAFETY-CRITICAL SUBAGENT GUIDANCE:
14
14
  • Writing/review safety: keep one writer for the same cwd/worktree. Use fresh-context read-only reviewers/validators for independent review, then have the parent synthesize and apply fixes as the sole writer unless an isolated worktree was intentionally requested.
15
15
  • Artifacts/status essentials: chain outputs live under {chain_dir}; async runs expose asyncId/asyncDir with status.json, events.jsonl, output logs, and status via { action: "status", id }. Include output paths and residual risks when reporting results.`;
16
16
 
17
- export const FULL_SUBAGENT_TOOL_DESCRIPTION = `Delegate to subagents or manage agent definitions.
17
+ export const FULL_SUBAGENT_TOOL_DESCRIPTION = `To delegate work, call with { agent, task }, { tasks }, or { chain }; omit action. Use action only for management/control actions listed below.
18
18
 
19
19
  EXECUTION (use exactly ONE mode):
20
20
  • Before executing, use { action: "list" } to inspect configured agents/chains. Only execute agents listed as executable/non-disabled.
@@ -24,6 +24,7 @@ EXECUTION (use exactly ONE mode):
24
24
  • Optional context: { context: "fresh" | "fork" } (explicit value overrides every child; when omitted, each requested agent uses its own defaultContext, otherwise "fresh"; inspect agent defaults via { action: "list" })
25
25
  • Fork thinking: model strings accept a thinking suffix (provider/model:off|minimal|low|medium|high|xhigh|max). Forking over a parent transcript that carries signed Anthropic thinking blocks forces thinking off only when a child's effective primary or fallback model resolves to the Anthropic provider or anthropic-messages API; unresolved models are treated conservatively. The result notes affected children, including on failures. Use fresh context when an Anthropic child needs thinking.
26
26
  • Optional timeout: { timeoutMs } or { maxRuntimeMs } sets a run-level max runtime for foreground and async/background runs
27
+ • Acceptance: omit acceptance for reviewer/read-only calls. Evidence levels end at verified. Use acceptance.review.required to require independent review of a writer result. Never request acceptance:"reviewed"; reviewed is achieved only after an independent reviewer result.
27
28
  • If { action: "list" } shows proactive skill subagent suggestions, consider a small fresh-context fanout for broad tasks where one of those skills would materially help
28
29
 
29
30
  CHAIN TEMPLATE VARIABLES (use in task strings):
@@ -74,12 +75,13 @@ DIAGNOSTICS:
74
75
 
75
76
  ${SUBAGENT_SAFETY_GUIDANCE}`;
76
77
 
77
- export const COMPACT_SUBAGENT_TOOL_DESCRIPTION = `Delegate to subagents or manage definitions. Use exactly one mode per call.
78
+ export const COMPACT_SUBAGENT_TOOL_DESCRIPTION = `To delegate work, call with { agent, task }, { tasks }, or { chain }; omit action. Use action only for management/control actions listed below. Use exactly one mode per call.
78
79
 
79
80
  EXECUTE:
80
81
  • Before execution, call { action: "list" }; run only executable/non-disabled configured agents/chains.
81
82
  • SINGLE {agent, task?}; PARALLEL {tasks:[{agent,task,count?,output?,reads?,progress?}], concurrency?, worktree?}; CHAIN {chain:[{agent,task?},{parallel:[...]}]}.
82
83
  • context can be "fresh" or "fork"; omitted uses each agent defaultContext, otherwise fresh. timeoutMs/maxRuntimeMs apply to foreground and async/background runs.
84
+ • Omit acceptance for reviewer/read-only calls. Evidence levels end at verified; use acceptance.review.required for independent writer review. reviewed is an achieved status, never an explicit input.
83
85
  • Chain templates may use {task}, {previous}, {chain_dir}, and named outputs. Parallel worktree isolation requires a clean git repo.
84
86
  • Chain example: { chain: [{agent:"agent-a", task:"Analyze {task}"}, {parallel: [{agent:"agent-b", task:"Check {previous}", count: 3}]}] }
85
87
  • If list shows proactive skill subagent suggestions, use a small fresh-context fanout only when the task is broad enough.
@@ -161,7 +161,7 @@ export function applyIntercomBridgeToAgent(agent: AgentConfig, bridge: IntercomB
161
161
  if (!bridge.active || !bridge.orchestratorTarget) return agent;
162
162
 
163
163
  const bridgeTools = ["intercom", "contact_supervisor"];
164
- const tools = agent.tools
164
+ const tools = agent.tools && agent.tools.length > 0
165
165
  ? [...agent.tools, ...bridgeTools.filter((tool) => !agent.tools?.includes(tool))]
166
166
  : agent.tools;
167
167
  const instruction = bridge.instruction;
@@ -447,6 +447,43 @@ function requestMatchesContext(request: SupervisorRequest, state: Pick<SubagentS
447
447
  return Boolean(currentSessionId && request.orchestratorSessionId === currentSessionId);
448
448
  }
449
449
 
450
+ function rememberedForegroundChild(request: SupervisorRequest, state: SubagentState) {
451
+ const run = state.foregroundRuns?.get(request.runId);
452
+ const child = run?.children.find((candidate) => candidate.index === request.childIndex && candidate.agent === request.agent)
453
+ ?? run?.children[request.childIndex];
454
+ return run && child ? { run, child } : undefined;
455
+ }
456
+
457
+ function markForegroundSupervisorAttention(request: SupervisorRequest, state: SubagentState): void {
458
+ const remembered = rememberedForegroundChild(request, state);
459
+ if (!remembered || remembered.child.status !== "detached") return;
460
+ const updatedAt = Date.now();
461
+ remembered.run.updatedAt = updatedAt;
462
+ remembered.child.activityState = "needs_attention";
463
+ remembered.child.lastActivityAt = request.createdAt;
464
+ remembered.child.currentTool = "contact_supervisor";
465
+ remembered.child.currentToolStartedAt = request.createdAt;
466
+ remembered.child.updatedAt = updatedAt;
467
+ }
468
+
469
+ function clearForegroundSupervisorAttention(request: SupervisorRequest, pending: Map<string, PendingSupervisorRequest>, state: SubagentState): void {
470
+ if ([...pending.values()].some((candidate) =>
471
+ candidate.expectsReply
472
+ && candidate.runId === request.runId
473
+ && candidate.agent === request.agent
474
+ && candidate.childIndex === request.childIndex
475
+ )) return;
476
+ const remembered = rememberedForegroundChild(request, state);
477
+ if (!remembered || remembered.child.status !== "detached" || remembered.child.currentTool !== "contact_supervisor") return;
478
+ const updatedAt = Date.now();
479
+ remembered.run.updatedAt = updatedAt;
480
+ remembered.child.activityState = undefined;
481
+ remembered.child.lastActivityAt = updatedAt;
482
+ remembered.child.currentTool = undefined;
483
+ remembered.child.currentToolStartedAt = undefined;
484
+ remembered.child.updatedAt = updatedAt;
485
+ }
486
+
450
487
  function removeRequestFile(file: string): void {
451
488
  try {
452
489
  fs.rmSync(file, { force: true });
@@ -465,10 +502,8 @@ function requestExpiresAt(request: SupervisorRequest, now: number): number {
465
502
 
466
503
  function requestRunInactive(request: SupervisorRequest, state: SubagentState): boolean {
467
504
  if (state.foregroundControls.has(request.runId)) return false;
468
- const foregroundRun = state.foregroundRuns?.get(request.runId);
469
- const foregroundChild = foregroundRun?.children.find((child) => child.index === request.childIndex && child.agent === request.agent)
470
- ?? foregroundRun?.children[request.childIndex];
471
- if (foregroundChild) return foregroundChild.status !== "detached";
505
+ const foreground = rememberedForegroundChild(request, state);
506
+ if (foreground) return foreground.child.status !== "detached";
472
507
 
473
508
  const asyncJob = state.asyncJobs.get(request.runId);
474
509
  if (!asyncJob) return false;
@@ -580,6 +615,7 @@ function buildParentIntercomTool(pending: Map<string, PendingSupervisorRequest>,
580
615
  const request = resolvePendingRequest(pending, input);
581
616
  writeReply(request, input.message ?? "");
582
617
  pending.delete(request.id);
618
+ clearForegroundSupervisorAttention(request, pending, state);
583
619
  return { content: [{ type: "text", text: `Replied to supervisor request ${request.id}.` }], details: { replyTo: request.id, runId: request.runId, agent: request.agent } };
584
620
  }
585
621
  if (input.action === "send" || input.action === "ask") {
@@ -629,7 +665,10 @@ export function createNativeSupervisorChannel(pi: ExtensionAPI, state: SubagentS
629
665
  continue;
630
666
  }
631
667
  seenFiles.add(file);
632
- if (request.expectsReply) pending.set(request.id, request);
668
+ if (request.expectsReply) {
669
+ pending.set(request.id, request);
670
+ markForegroundSupervisorAttention(request, state);
671
+ }
633
672
  else {
634
673
  removeRequestFile(request.requestFile);
635
674
  }
@@ -645,7 +684,7 @@ export function createNativeSupervisorChannel(pi: ExtensionAPI, state: SubagentS
645
684
  agent: request.agent,
646
685
  childIndex: request.childIndex,
647
686
  },
648
- });
687
+ }, { triggerTurn: true });
649
688
  if (request.expectsReply) {
650
689
  (pi as { events?: IntercomEventBus }).events?.emit(INTERCOM_DETACH_REQUEST_EVENT, {
651
690
  requestId: request.id,
@@ -5,6 +5,7 @@ import {
5
5
  type IntercomEventBus,
6
6
  type NestedRunSummary,
7
7
  type PublicNestedRunSummary,
8
+ type ParallelHandoffReference,
8
9
  type SingleResult,
9
10
  type SubagentResultIntercomChild,
10
11
  type SubagentResultIntercomPayload,
@@ -179,6 +180,7 @@ interface GroupedResultIntercomMessageInput {
179
180
  asyncId?: string;
180
181
  asyncDir?: string;
181
182
  chainSteps?: number;
183
+ parallelHandoff?: ParallelHandoffReference;
182
184
  }
183
185
 
184
186
  function asyncResumeGuidance(input: {
@@ -207,6 +209,7 @@ function formatSubagentResultIntercomMessage(input: {
207
209
  asyncId?: string;
208
210
  asyncDir?: string;
209
211
  chainSteps?: number;
212
+ parallelHandoff?: ParallelHandoffReference;
210
213
  }): string {
211
214
  const counts = countStatuses(input.children);
212
215
  const lines: string[] = [
@@ -222,6 +225,7 @@ function formatSubagentResultIntercomMessage(input: {
222
225
  }
223
226
  if (input.asyncId) lines.push(`Async id: ${input.asyncId}`);
224
227
  if (input.asyncDir) lines.push(`Async dir: ${input.asyncDir}`);
228
+ if (input.parallelHandoff) lines.push(`Parallel handoff: ${input.parallelHandoff.path}`);
225
229
  const resumeGuidance = asyncResumeGuidance(input);
226
230
  if (resumeGuidance) lines.push(resumeGuidance);
227
231
  if (input.children.some((child) => child.intercomTarget)) {
@@ -266,6 +270,7 @@ export function buildSubagentResultIntercomPayload(input: GroupedResultIntercomM
266
270
  ...(input.asyncId ? { asyncId: input.asyncId } : {}),
267
271
  ...(input.asyncDir ? { asyncDir: input.asyncDir } : {}),
268
272
  ...(typeof input.chainSteps === "number" ? { chainSteps: input.chainSteps } : {}),
273
+ ...(input.parallelHandoff ? { parallelHandoff: input.parallelHandoff } : {}),
269
274
  ...(firstChild?.agent ? { agent: firstChild.agent } : {}),
270
275
  ...(firstChild?.index !== undefined ? { index: firstChild.index } : {}),
271
276
  ...(firstChild?.artifactPath ? { artifactPath: firstChild.artifactPath } : {}),
@@ -352,6 +357,8 @@ export function formatSubagentResultReceipt(input: {
352
357
  `Children: ${formatStatusCounts(counts)}`,
353
358
  ];
354
359
 
360
+ if (input.payload.parallelHandoff) lines.push(`Parallel handoff: ${input.payload.parallelHandoff.path}`);
361
+
355
362
  const artifacts = input.payload.children.filter((child) => typeof child.artifactPath === "string");
356
363
  if (artifacts.length > 0) {
357
364
  lines.push("Artifacts:");