@ashx-j/lunr 0.2.19 → 0.2.20

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 (170) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +2 -2
  3. package/dist/builtin-extensions/index.d.ts.map +1 -1
  4. package/dist/builtin-extensions/index.js +0 -2
  5. package/dist/builtin-extensions/index.js.map +1 -1
  6. package/dist/builtin-extensions/pi-intercom/broker/spawn.d.ts +6 -4
  7. package/dist/builtin-extensions/pi-intercom/broker/spawn.d.ts.map +1 -1
  8. package/dist/builtin-extensions/pi-intercom/broker/spawn.js +93 -49
  9. package/dist/builtin-extensions/pi-intercom/broker/spawn.js.map +1 -1
  10. package/dist/builtin-extensions/pi-intercom/index.d.ts.map +1 -1
  11. package/dist/builtin-extensions/pi-intercom/index.js +351 -351
  12. package/dist/builtin-extensions/pi-intercom/index.js.map +1 -1
  13. package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts +2 -1
  14. package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts.map +1 -1
  15. package/dist/builtin-extensions/pi-subagents/src/extension/index.js +80 -43
  16. package/dist/builtin-extensions/pi-subagents/src/extension/index.js.map +1 -1
  17. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts +18 -8
  18. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts.map +1 -1
  19. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js +36 -11
  20. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js.map +1 -1
  21. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts +4 -3
  22. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts.map +1 -1
  23. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js +21 -12
  24. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js.map +1 -1
  25. package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.d.ts +7 -1
  26. package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.d.ts.map +1 -1
  27. package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.js +225 -15
  28. package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.js.map +1 -1
  29. package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-ask.d.ts +29 -0
  30. package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-ask.d.ts.map +1 -0
  31. package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-ask.js +179 -0
  32. package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-ask.js.map +1 -0
  33. package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-questions.d.ts +89 -0
  34. package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-questions.d.ts.map +1 -0
  35. package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-questions.js +541 -0
  36. package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-questions.js.map +1 -0
  37. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.d.ts +9 -1
  38. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.d.ts.map +1 -1
  39. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js +34 -9
  40. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js.map +1 -1
  41. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-job-tracker.d.ts.map +1 -1
  42. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-job-tracker.js +1 -2
  43. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-job-tracker.js.map +1 -1
  44. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.d.ts +2 -1
  45. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.d.ts.map +1 -1
  46. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.js +35 -8
  47. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.js.map +1 -1
  48. package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.d.ts +1 -1
  49. package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.d.ts.map +1 -1
  50. package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.js +3 -3
  51. package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.js.map +1 -1
  52. package/dist/builtin-extensions/pi-subagents/src/runs/background/result-watcher.d.ts +1 -0
  53. package/dist/builtin-extensions/pi-subagents/src/runs/background/result-watcher.d.ts.map +1 -1
  54. package/dist/builtin-extensions/pi-subagents/src/runs/background/result-watcher.js +54 -38
  55. package/dist/builtin-extensions/pi-subagents/src/runs/background/result-watcher.js.map +1 -1
  56. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-wait.d.ts +3 -6
  57. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-wait.d.ts.map +1 -1
  58. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-wait.js +64 -5
  59. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-wait.js.map +1 -1
  60. package/dist/builtin-extensions/pi-subagents/src/runs/background/wait-tool.d.ts.map +1 -1
  61. package/dist/builtin-extensions/pi-subagents/src/runs/background/wait-tool.js +1 -0
  62. package/dist/builtin-extensions/pi-subagents/src/runs/background/wait-tool.js.map +1 -1
  63. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.d.ts.map +1 -1
  64. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js +21 -8
  65. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js.map +1 -1
  66. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts.map +1 -1
  67. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js +62 -16
  68. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js.map +1 -1
  69. package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.d.ts +2 -0
  70. package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.d.ts.map +1 -1
  71. package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.js +75 -18
  72. package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.js.map +1 -1
  73. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.d.ts +13 -1
  74. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.d.ts.map +1 -1
  75. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.js +67 -11
  76. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.js.map +1 -1
  77. package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.d.ts.map +1 -1
  78. package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.js +5 -2
  79. package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.js.map +1 -1
  80. package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.d.ts.map +1 -1
  81. package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.js +13 -3
  82. package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.js.map +1 -1
  83. package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.d.ts +3 -0
  84. package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.d.ts.map +1 -1
  85. package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.js +68 -11
  86. package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.js.map +1 -1
  87. package/dist/builtin-extensions/pi-subagents/src/shared/formatters.d.ts +12 -1
  88. package/dist/builtin-extensions/pi-subagents/src/shared/formatters.d.ts.map +1 -1
  89. package/dist/builtin-extensions/pi-subagents/src/shared/formatters.js +21 -2
  90. package/dist/builtin-extensions/pi-subagents/src/shared/formatters.js.map +1 -1
  91. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts +16 -4
  92. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts.map +1 -1
  93. package/dist/builtin-extensions/pi-subagents/src/shared/types.js +1 -1
  94. package/dist/builtin-extensions/pi-subagents/src/shared/types.js.map +1 -1
  95. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts +2 -0
  96. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts.map +1 -1
  97. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js +24 -6
  98. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js.map +1 -1
  99. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts +44 -4
  100. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts.map +1 -1
  101. package/dist/builtin-extensions/pi-subagents/src/tui/render.js +227 -133
  102. package/dist/builtin-extensions/pi-subagents/src/tui/render.js.map +1 -1
  103. package/dist/builtin-extensions/pi-subagents/src/watchdog/register-main.d.ts.map +1 -1
  104. package/dist/builtin-extensions/pi-subagents/src/watchdog/register-main.js +1 -1
  105. package/dist/builtin-extensions/pi-subagents/src/watchdog/register-main.js.map +1 -1
  106. package/dist/builtin-extensions/pi-subagents/src/watchdog/tool-actions.d.ts +2 -13
  107. package/dist/builtin-extensions/pi-subagents/src/watchdog/tool-actions.d.ts.map +1 -1
  108. package/dist/builtin-extensions/pi-subagents/src/watchdog/tool-actions.js +5 -91
  109. package/dist/builtin-extensions/pi-subagents/src/watchdog/tool-actions.js.map +1 -1
  110. package/dist/core/permissions.d.ts +1 -0
  111. package/dist/core/permissions.d.ts.map +1 -1
  112. package/dist/core/permissions.js +8 -4
  113. package/dist/core/permissions.js.map +1 -1
  114. package/dist/core/plan-mode.d.ts.map +1 -1
  115. package/dist/core/plan-mode.js +0 -5
  116. package/dist/core/plan-mode.js.map +1 -1
  117. package/dist/core/runtime-bridges.d.ts.map +1 -1
  118. package/dist/core/runtime-bridges.js +0 -2
  119. package/dist/core/runtime-bridges.js.map +1 -1
  120. package/dist/core/subagent-cancellation.d.ts +21 -0
  121. package/dist/core/subagent-cancellation.d.ts.map +1 -0
  122. package/dist/core/subagent-cancellation.js +53 -0
  123. package/dist/core/subagent-cancellation.js.map +1 -0
  124. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  125. package/dist/modes/interactive/components/assistant-message.js +12 -18
  126. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  127. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  128. package/dist/modes/interactive/components/tool-execution.js +12 -0
  129. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  130. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  131. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  132. package/dist/modes/interactive/interactive-mode.js +29 -0
  133. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  134. package/dist/node-runtime/{agent-session-services-35WNVGMR.js → agent-session-services-75JKASCW.js} +3 -3
  135. package/dist/node-runtime/{builtin-extensions-322XDDEM.js → builtin-extensions-X4JMM4ZS.js} +2 -2
  136. package/dist/node-runtime/{chunk-XATUM2HK.js → chunk-25I2BHAA.js} +1 -1
  137. package/dist/node-runtime/{chunk-MCUNIRD5.js → chunk-46ECNOLF.js} +477 -246
  138. package/dist/node-runtime/{chunk-J26TSGDL.js → chunk-6N22HULO.js} +8 -5
  139. package/dist/node-runtime/{chunk-TFBIJTM3.js → chunk-AL2FMKKM.js} +7 -7
  140. package/dist/node-runtime/chunk-AOFXZQKA.js +24 -0
  141. package/dist/node-runtime/chunk-BBUVIEZB.js +51 -0
  142. package/dist/node-runtime/{chunk-ZRCBN365.js → chunk-ELWEXVO5.js} +7 -7
  143. package/dist/node-runtime/{chunk-ATGA2WLC.js → chunk-JYNNB7DU.js} +397 -34
  144. package/dist/node-runtime/{chunk-4FX25LWP.js → chunk-K3646JTM.js} +16 -117
  145. package/dist/node-runtime/{chunk-PNJSG3IZ.js → chunk-LTYWTR3V.js} +1 -369
  146. package/dist/node-runtime/{chunk-TXHKBRHZ.js → chunk-QKFMBRAL.js} +45 -31
  147. package/dist/node-runtime/{command-DWUM5X3U.js → command-RBTRL55A.js} +1 -1
  148. package/dist/node-runtime/{gateway-UZXMFGWY.js → gateway-L4ANRTVE.js} +2 -2
  149. package/dist/node-runtime/index.js +9 -8
  150. package/dist/node-runtime/{interactive-mode-QT5IKGQ5.js → interactive-mode-HPUNH7SV.js} +5 -4
  151. package/dist/node-runtime/{lunr-cron-QPBTKBGI.js → lunr-cron-NNYTWTGF.js} +2 -2
  152. package/dist/node-runtime/main.js +7 -7
  153. package/dist/node-runtime/{pi-intercom-EEMBGQID.js → pi-intercom-DDQMRNPJ.js} +96 -76
  154. package/dist/node-runtime/{pi-subagents-7YJ3I4VE.js → pi-subagents-T5WMMLBB.js} +1040 -318
  155. package/dist/node-runtime/rpc-entry.js +7 -7
  156. package/dist/node-runtime/{runtime-bridges-3RKBDLNA.js → runtime-bridges-NJQERQ72.js} +2 -4
  157. package/dist/node-runtime/{subagent-executor-5Q5A4HJ2.js → subagent-executor-SRFC26SJ.js} +168 -249
  158. package/docs/features.md +17 -3
  159. package/docs/keybindings.md +3 -1
  160. package/docs/settings.md +1 -1
  161. package/package.json +4 -4
  162. package/dist/builtin-extensions/lunr-settings-tools.d.ts +0 -4
  163. package/dist/builtin-extensions/lunr-settings-tools.d.ts.map +0 -1
  164. package/dist/builtin-extensions/lunr-settings-tools.js +0 -120
  165. package/dist/builtin-extensions/lunr-settings-tools.js.map +0 -1
  166. package/dist/core/settings-tools-bridge.d.ts +0 -35
  167. package/dist/core/settings-tools-bridge.d.ts.map +0 -1
  168. package/dist/core/settings-tools-bridge.js +0 -56
  169. package/dist/core/settings-tools-bridge.js.map +0 -1
  170. package/dist/node-runtime/chunk-3NMWLTPV.js +0 -64
@@ -40,11 +40,11 @@ import {
40
40
  compactNestedResultChildren,
41
41
  deliverStopRequest,
42
42
  deliverSubagentResultIntercomEvent,
43
- flatToLogicalStepIndex,
44
43
  formatActivityLabel,
45
44
  formatAgentRunningLabel,
46
45
  formatAsyncRunProgressLabel,
47
46
  formatAsyncRunTranscript,
47
+ formatCompactModelBadge,
48
48
  formatControlNoticeMessage,
49
49
  formatDuration,
50
50
  formatModelSelection,
@@ -64,12 +64,15 @@ import {
64
64
  normalizeParallelGroups,
65
65
  projectNestedRegistryForRoot,
66
66
  readStatus,
67
+ readSteerCapability,
67
68
  reconcileAsyncRun,
68
69
  reconcileNestedAsyncDescendants,
69
70
  registerMainWatchdog,
71
+ requestAsyncSteer,
70
72
  resolveAsyncRunLocation,
71
73
  resolveCurrentSessionId,
72
74
  resolveSubagentResultStatus,
75
+ resolveSubagentRunId,
73
76
  resolveTurnBudgetConfig,
74
77
  resolveWaitToolConfig,
75
78
  resolveWatchPath,
@@ -82,15 +85,19 @@ import {
82
85
  validateExecutionAcceptance,
83
86
  validateToolBudgetConfig,
84
87
  writeAtomicJson
85
- } from "./chunk-MCUNIRD5.js";
86
- import "./chunk-XATUM2HK.js";
88
+ } from "./chunk-46ECNOLF.js";
89
+ import {
90
+ createSubagentCancellation,
91
+ registerSubagentCancellation
92
+ } from "./chunk-BBUVIEZB.js";
93
+ import "./chunk-25I2BHAA.js";
87
94
  import "./chunk-WKDK4C5P.js";
88
95
  import "./chunk-53YJI7JU.js";
89
96
  import "./chunk-KBKMCJDU.js";
90
97
  import "./chunk-CSFI6YXL.js";
91
98
  import "./chunk-FVDOXRUD.js";
92
99
  import "./chunk-AD6VXAO7.js";
93
- import "./chunk-J26TSGDL.js";
100
+ import "./chunk-6N22HULO.js";
94
101
  import "./chunk-M3LZLQW2.js";
95
102
  import "./chunk-RQ2Q5IYR.js";
96
103
  import {
@@ -104,10 +111,10 @@ import "./chunk-Q4Q6D4IY.js";
104
111
  import "./chunk-BRJX2PRC.js";
105
112
 
106
113
  // packages/coding-agent/dist/builtin-extensions/pi-subagents/src/extension/index.js
107
- import { randomUUID as randomUUID3 } from "node:crypto";
108
- import * as fs9 from "node:fs";
114
+ import { randomUUID as randomUUID4 } from "node:crypto";
115
+ import * as fs10 from "node:fs";
109
116
  import * as os from "node:os";
110
- import * as path11 from "node:path";
117
+ import * as path12 from "node:path";
111
118
  import { Box, Container as Container2, Spacer as Spacer2, Text as Text2, truncateToWidth as truncateToWidth2, visibleWidth as visibleWidth3, wrapTextWithAnsi as wrapTextWithAnsi2 } from "@ashx-j/lunr-tui";
112
119
 
113
120
  // packages/coding-agent/dist/builtin-extensions/pi-subagents/src/tui/render.js
@@ -238,6 +245,16 @@ function statJoin(theme, parts) {
238
245
  function formatTokenStat(tokens) {
239
246
  return `${formatTokens(tokens)} token`;
240
247
  }
248
+ function usageTokenTotal(usage) {
249
+ return usage ? (usage.input ?? 0) + (usage.output ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0) : 0;
250
+ }
251
+ function compactResultTokens(result2) {
252
+ let progress = result2.progress || result2.progressSummary;
253
+ if (progress && progress.tokens > 0)
254
+ return progress.tokens;
255
+ let fromUsage = usageTokenTotal(result2.usage);
256
+ return fromUsage > 0 ? fromUsage : progress?.tokens ?? 0;
257
+ }
241
258
  function formatToolUseStat(count) {
242
259
  return `${count} tool use${count === 1 ? "" : "s"}`;
243
260
  }
@@ -248,7 +265,10 @@ function formatTotalCostStat(totalCost) {
248
265
  return totalCost.inputTokens && parts.push(`in:${formatTokens(totalCost.inputTokens)}`), totalCost.outputTokens && parts.push(`out:${formatTokens(totalCost.outputTokens)}`), totalCost.costUsd && parts.push(`$${totalCost.costUsd.toFixed(4)}`), parts.join(" ");
249
266
  }
250
267
  function liveDurationMs(progress, running, now) {
251
- return progress ? !running || progress.lastActivityAt === void 0 ? progress.durationMs : progress.durationMs + Math.max(0, now - progress.lastActivityAt) : 0;
268
+ if (!progress)
269
+ return 0;
270
+ let durationMs = progress.durationMs ?? 0;
271
+ return !running || progress.lastActivityAt === void 0 ? durationMs : durationMs + Math.max(0, now - progress.lastActivityAt);
252
272
  }
253
273
  function formatProgressStats(theme, progress, includeDuration = !0, options = {}) {
254
274
  if (!progress)
@@ -288,13 +308,16 @@ function resultStatusLine(result2, output) {
288
308
  function resultGlyph(result2, output, theme, running = result2.progress?.status === "running", seed = progressRunningSeed(result2.progress ?? result2.progressSummary), frame) {
289
309
  return running ? frame !== void 0 ? theme.fg("accent", runningGlyph((seed ?? 0) + frame)) : theme.fg("accent", runningGlyph(seed)) : result2.detached || result2.stopped || result2.interrupted ? theme.fg("warning", "\u25A0") : result2.exitCode !== 0 ? theme.fg("error", "\u2717") : hasEmptyTextOutputWithoutOutputTarget(result2.task, output) ? theme.fg("warning", "\u2713") : theme.fg("success", "\u2713");
290
310
  }
291
- function formatCompactStatsHangLine(progress, width, paint = (s) => s, prefix = " \u23BF ", now = Date.now(), running = !1) {
292
- let toolCount = progress?.toolCount ?? 0, tokens = progress?.tokens ?? 0, durationMs = liveDurationMs(progress, running, now), body = [
293
- formatToolUseStat(toolCount),
294
- formatTokenStat(tokens),
295
- durationMs > 0 ? formatDuration(durationMs) : "0ms"
296
- ].join(" \xB7 ");
297
- return truncLine(paint(`${prefix}${body}`), width);
311
+ function formatCompactRowStats(tokens, durationMs) {
312
+ return `${formatTokenStat(tokens)} \xB7 ${durationMs > 0 ? formatDuration(durationMs) : "0ms"}`;
313
+ }
314
+ function formatCompactSubagentRow(theme, width, parts) {
315
+ let stats = formatCompactRowStats(parts.tokens, parts.durationMs), body = [
316
+ `${parts.prefix ?? ""}${parts.glyph} ${themeBold(theme, parts.description)}`,
317
+ parts.modelBadge ? theme.fg("dim", parts.modelBadge) : "",
318
+ theme.fg("dim", stats)
319
+ ].filter(Boolean).join(` ${theme.fg("dim", "\xB7")} `);
320
+ return truncLine(body, width);
298
321
  }
299
322
  function widgetRenderKey(job) {
300
323
  return JSON.stringify({
@@ -330,12 +353,6 @@ function formatWidgetAgents(agents) {
330
353
  function widgetJobName(job) {
331
354
  return job.mode === "parallel" ? "parallel" : job.mode === "chain" ? "chain" : job.mode === "single" && job.agents?.length === 1 ? job.agents[0] : job.agents?.length ? formatWidgetAgents(job.agents) : job.mode ?? "subagent";
332
355
  }
333
- function widgetActivity(job, now = Date.now()) {
334
- let facts = [], snapshotNow = job.status === "running" ? now : job.updatedAt;
335
- job.currentTool && job.currentToolStartedAt !== void 0 && snapshotNow !== void 0 ? facts.push(`${job.currentTool} ${formatDuration(Math.max(0, snapshotNow - job.currentToolStartedAt))}`) : job.currentTool && facts.push(job.currentTool), job.currentPath && facts.push(shortenPath(job.currentPath)), job.turnCount !== void 0 && facts.push(`${job.turnCount} turns`), job.toolCount !== void 0 && facts.push(`${job.toolCount} tools`);
336
- let activity = buildLiveStatusLine(job, snapshotNow);
337
- return activity && facts.length ? `${activity} \xB7 ${facts.join(" \xB7 ")}` : activity || (facts.length ? facts.join(" \xB7 ") : job.status === "running" ? "thinking\u2026" : job.status === "queued" ? "queued\u2026" : job.status === "paused" ? "Paused" : job.status === "stopped" ? "Stopped" : job.status === "failed" ? "Failed" : "Done");
338
- }
339
356
  function widgetStepRunningSeed(step, fallbackIndex) {
340
357
  return runningSeed(fallbackIndex, step.index);
341
358
  }
@@ -549,29 +566,6 @@ function resultRowLabel(details, label, resultIndex, stepNumber) {
549
566
  }
550
567
  return label.itemTitle === "Agent" ? `Agent ${label.activeParallelGroup ? Math.max(1, stepNumber - label.groupStartIndex) : stepNumber}/${label.totalCount}` : `Step ${stepNumber}`;
551
568
  }
552
- function widgetStats(job, theme, now = Date.now()) {
553
- let parts = [], stepsTotal = job.stepsTotal ?? job.agents?.length ?? 1;
554
- if (job.activeParallelGroup) {
555
- let running = job.runningSteps ?? (job.status === "running" ? 1 : 0), done = job.completedSteps ?? (job.status === "complete" ? stepsTotal : 0);
556
- if (job.mode === "parallel")
557
- job.status === "running" && running > 0 && parts.push(formatAgentRunningLabel(running)), stepsTotal > 0 && parts.push(`${done}/${stepsTotal} done`);
558
- else {
559
- let logicalStep = (job.currentStep !== void 0 ? job.parallelGroups?.find((group) => job.currentStep >= group.start && job.currentStep < group.start + group.count) : job.parallelGroups?.find((group) => group.start === 0))?.stepIndex ?? job.currentStep ?? 0, total = job.chainStepCount ?? stepsTotal, groupParts = [`${done}/${stepsTotal} done`];
560
- job.status === "running" && running > 0 && groupParts.unshift(formatAgentRunningLabel(running)), parts.push(`step ${logicalStep + 1}/${total} \xB7 parallel group: ${groupParts.join(" \xB7 ")}`);
561
- }
562
- } else if (job.currentStep !== void 0)
563
- if (job.mode === "chain" && job.parallelGroups?.length) {
564
- let total = job.chainStepCount ?? stepsTotal;
565
- parts.push(`step ${flatToLogicalStepIndex(job.currentStep, total, job.parallelGroups) + 1}/${total}`);
566
- } else
567
- parts.push(`step ${job.currentStep + 1}/${stepsTotal}`);
568
- else stepsTotal > 1 && parts.push(`steps ${stepsTotal}`);
569
- if (job.toolCount !== void 0 && parts.push(formatToolUseStat(job.toolCount)), job.totalTokens?.total && parts.push(formatTokenStat(job.totalTokens.total)), job.startedAt !== void 0) {
570
- let end = job.status === "running" ? now : job.updatedAt;
571
- end !== void 0 && parts.push(`${job.status === "running" ? "" : "ran for "}${formatDuration(Math.max(0, end - job.startedAt))}`);
572
- }
573
- return statJoin(theme, parts);
574
- }
575
569
  function widgetStepStats(theme, step, now = Date.now()) {
576
570
  let running = step.status === "running", durationMs = step.durationMs !== void 0 ? step.durationMs + (running && step.lastActivityAt !== void 0 ? Math.max(0, now - step.lastActivityAt) : 0) : step.startedAt !== void 0 ? Math.max(0, (running ? now : step.endedAt ?? step.lastActivityAt ?? now) - step.startedAt) : void 0;
577
571
  return statJoin(theme, [
@@ -663,12 +657,17 @@ function foregroundStyleWidgetStepLines(job, theme, step, itemTitle, index, tota
663
657
  return lines;
664
658
  }
665
659
  function compactJobProgress(job, step, now = Date.now()) {
666
- let running = (step?.status ?? job.status) === "running", startedAt = step?.startedAt ?? job.startedAt, endedAt = running ? now : step?.endedAt ?? step?.lastActivityAt ?? job.updatedAt ?? now, durationMs = step?.durationMs !== void 0 ? step.durationMs : startedAt !== void 0 ? Math.max(0, endedAt - startedAt) : 0;
667
- return {
668
- toolCount: step?.toolCount ?? job.toolCount ?? 0,
669
- tokens: step?.tokens?.total ?? job.totalTokens?.total ?? 0,
670
- durationMs
671
- };
660
+ let running = (step?.status ?? job.status) === "running", tokens = step?.tokens?.total ?? job.totalTokens?.total ?? 0;
661
+ if (step?.durationMs !== void 0)
662
+ return {
663
+ tokens,
664
+ durationMs: liveDurationMs({ durationMs: step.durationMs, lastActivityAt: step.lastActivityAt }, running, now)
665
+ };
666
+ let startedAt = step?.startedAt ?? job.startedAt;
667
+ if (startedAt === void 0)
668
+ return { tokens, durationMs: 0 };
669
+ let endedAt = running ? now : step?.endedAt ?? step?.lastActivityAt ?? job.updatedAt ?? now;
670
+ return { tokens, durationMs: Math.max(0, endedAt - startedAt) };
672
671
  }
673
672
  function compactJobLead(job, step) {
674
673
  return compactRowLead({
@@ -676,19 +675,30 @@ function compactJobLead(job, step) {
676
675
  task: step?.description ?? step?.agent
677
676
  }) || widgetJobName(job);
678
677
  }
679
- function compactJobLines(job, step, theme, width, now = Date.now()) {
680
- let running = (step?.status ?? job.status) === "running", lead = compactJobLead(job, step), modelBadge = formatModelSelection(step?.modelSelection, step?.model, step?.thinking), permissionBadge = step?.permissions ? theme.fg("dim", step.permissions) : "", glyph = step ? widgetStepGlyph(step.status, theme, running ? (widgetStepRunningSeed(step, step.index) ?? 0) + widgetAnimFrame(now) : widgetStepRunningSeed(step, step.index)) : widgetStatusGlyph(job, theme, now), hang = formatCompactStatsHangLine(compactJobProgress(job, step, now), width, (s) => theme.fg("dim", s), " \u23BF ", now, running);
681
- return [
682
- truncLine(`${glyph} ${themeBold(theme, lead)}${modelBadge ? ` ${theme.fg("dim", "\xB7")} ${theme.fg("dim", modelBadge)}` : ""}${permissionBadge ? ` ${theme.fg("dim", "\xB7")} ${permissionBadge}` : ""}`, width),
683
- hang
684
- ];
678
+ function activeJobStep(job) {
679
+ let steps = job.steps ?? [];
680
+ return steps.find((step) => step.status === "running") ?? steps.find((step) => step.status === "pending" || step.status === "queued") ?? steps[steps.length - 1];
681
+ }
682
+ function compactJobChildRows(job, theme, width, now = Date.now()) {
683
+ let steps = job.steps ?? [];
684
+ return steps.length <= 1 ? compactJobLines(job, steps[0], theme, width, now) : steps.flatMap((step) => compactJobLines(job, step, theme, width, now));
685
685
  }
686
- function buildSingleWidgetLines(job, theme, width, _expanded) {
687
- let now = Date.now(), steps = job.steps ?? [];
686
+ function compactJobLines(job, step, theme, width, now = Date.now()) {
687
+ let running = (step?.status ?? job.status) === "running", lead = compactJobLead(job, step), modelBadge = formatCompactModelBadge(step?.modelSelection, step?.model), glyph = step ? widgetStepGlyph(step.status, theme, running ? (widgetStepRunningSeed(step, step.index) ?? 0) + widgetAnimFrame(now) : widgetStepRunningSeed(step, step.index)) : widgetStatusGlyph(job, theme, now), progress = compactJobProgress(job, step, now);
688
+ return [formatCompactSubagentRow(theme, width, {
689
+ glyph,
690
+ description: lead,
691
+ modelBadge,
692
+ tokens: progress.tokens,
693
+ durationMs: progress.durationMs
694
+ })];
695
+ }
696
+ function buildSingleWidgetLines(job, theme, width, _expanded, now = Date.now()) {
697
+ let steps = job.steps ?? [];
688
698
  return steps.length <= 1 ? compactJobLines(job, steps[0], theme, width, now) : steps.flatMap((step) => compactJobLines(job, step, theme, width, now));
689
699
  }
690
- function compactSingleWidgetLines(job, theme, width) {
691
- return buildSingleWidgetLines(job, theme, width, !1);
700
+ function compactSingleWidgetLines(job, theme, width, now = Date.now()) {
701
+ return buildSingleWidgetLines(job, theme, width, !1, now);
692
702
  }
693
703
  var RESERVED_NON_WIDGET_ROWS = 19, widgetLayoutSession;
694
704
  function resetWidgetLayoutSession() {
@@ -718,7 +728,7 @@ function widgetHeaderCounts(jobs) {
718
728
  };
719
729
  }
720
730
  function buildSingleLineWidgetLines(jobs, theme, width) {
721
- let counts = widgetHeaderCounts(jobs), hasActive = counts.running.length > 0 || counts.queued.length > 0, glyph = counts.running.length > 0 ? runningGlyph(widgetJobsRunningSeed(counts.running)) : hasActive ? "\u25CF" : "\u25CB", parts = [];
731
+ let counts = widgetHeaderCounts(jobs), hasActive = counts.running.length > 0 || counts.queued.length > 0, glyph = counts.running.length > 0 ? runningGlyph((widgetJobsRunningSeed(counts.running) ?? 0) + widgetAnimFrame()) : hasActive ? "\u25CF" : "\u25CB", parts = [];
722
732
  return counts.running.length > 0 && parts.push(`${counts.running.length}/${jobs.length} running`), counts.queued.length > 0 && parts.push(`${counts.queued.length} queued`), counts.failed.length > 0 && parts.push(`${counts.failed.length} failed`), counts.stopped.length > 0 && parts.push(`${counts.stopped.length} stopped`), counts.paused.length > 0 && parts.push(`${counts.paused.length} paused`), !hasActive && counts.complete.length > 0 && parts.push(`${counts.complete.length}/${jobs.length} done`), [truncLine(`${theme.fg(hasActive ? "accent" : "dim", glyph)} ${theme.fg(hasActive ? "accent" : "dim", "subagents")} (${parts.join(", ") || `${jobs.length} total`})`, width)];
723
733
  }
724
734
  function orderedWidgetJobs(jobs) {
@@ -763,17 +773,12 @@ function selectProgressiveJobKeys(jobs, previousKeys, bodyRows) {
763
773
  return selected;
764
774
  }
765
775
  function progressiveHeaderLine(jobs, theme, width) {
766
- let counts = widgetHeaderCounts(jobs), hasActive = counts.running.length > 0 || counts.queued.length > 0, glyph = counts.running.length > 0 ? runningGlyph(widgetJobsRunningSeed(counts.running)) : hasActive ? "\u25CF" : "\u25CB", parts = [];
776
+ let counts = widgetHeaderCounts(jobs), hasActive = counts.running.length > 0 || counts.queued.length > 0, glyph = counts.running.length > 0 ? runningGlyph((widgetJobsRunningSeed(counts.running) ?? 0) + widgetAnimFrame()) : hasActive ? "\u25CF" : "\u25CB", parts = [];
767
777
  return counts.running.length > 0 && parts.push(formatAgentRunningLabel(counts.running.length)), counts.queued.length > 0 && parts.push(`${counts.queued.length} queued`), hasActive || (counts.failed.length > 0 && parts.push(`${counts.failed.length} failed`), counts.stopped.length > 0 && parts.push(`${counts.stopped.length} stopped`), counts.paused.length > 0 && parts.push(`${counts.paused.length} paused`), counts.complete.length > 0 && parts.push(`${counts.complete.length}/${jobs.length} done`)), truncLine(`${theme.fg(hasActive ? "accent" : "dim", glyph)} ${theme.fg(hasActive ? "accent" : "dim", "Async agents")} ${theme.fg("dim", "\xB7")} ${theme.fg("dim", parts.join(", ") || `${jobs.length} total`)}`, width);
768
778
  }
769
- function progressiveJobLine(job, theme, width) {
770
- let stats = widgetStats(job, theme), activity = widgetActivity(job), status = job.status === "complete" ? "done" : job.status, parts = [
771
- themeBold(theme, widgetJobName(job)),
772
- theme.fg("dim", status),
773
- stats,
774
- activity && activity.toLowerCase() !== status ? theme.fg("dim", activity) : ""
775
- ].filter(Boolean);
776
- return truncLine(` ${widgetStatusGlyph(job, theme)} ${parts.join(` ${theme.fg("dim", "\xB7")} `)}`, width);
779
+ function progressiveJobLine(job, theme, width, now = Date.now()) {
780
+ let row = compactJobLines(job, activeJobStep(job), theme, Math.max(1, width - 2), now)[0] ?? "";
781
+ return row ? ` ${row}` : "";
777
782
  }
778
783
  function progressiveHiddenLine(hiddenJobs, theme, width) {
779
784
  let counts = widgetHeaderCounts(hiddenJobs), parts = [];
@@ -822,20 +827,12 @@ function fitAdaptiveWidgetLines(jobs, lines, theme, width, expanded) {
822
827
  let lockedRows = Math.min(availableRows, collapsedWidgetLineBudget(rows)), rendered = buildProgressiveWidgetLines(jobs, theme, width, lockedRows, []);
823
828
  return widgetLayoutSession = { expanded, rows, columns, tier: "progressive", lockedRows, visibleJobKeys: rendered.visibleJobKeys }, rendered.lines;
824
829
  }
825
- function buildWidgetComponent(jobs, expanded) {
826
- return (_tui, theme) => {
827
- let width = getTermWidth(), lines = expanded ? buildWidgetLines(jobs, theme, width, !0) : jobs.length === 1 ? compactSingleWidgetLines(jobs[0], theme, width) : buildWidgetLines(jobs, theme, width, !1), container = new Container();
828
- for (let line of fitAdaptiveWidgetLines(jobs, lines, theme, width, expanded))
829
- container.addChild(new Text(line, 1, 0));
830
- return container;
831
- };
832
- }
833
- function buildWidgetLines(jobs, theme, width = getTermWidth(), expanded = !1) {
830
+ function buildWidgetLines(jobs, theme, width = getTermWidth(), expanded = !1, now = Date.now()) {
834
831
  if (jobs.length === 0)
835
832
  return [];
836
833
  if (jobs.length === 1)
837
- return buildSingleWidgetLines(jobs[0], theme, width, expanded);
838
- let running = jobs.filter((job) => job.status === "running"), queued = jobs.filter((job) => job.status === "queued"), finished = jobs.filter((job) => job.status !== "running" && job.status !== "queued"), lines = [], hasActive = running.length > 0 || queued.length > 0, headerGlyph = running.length > 0 ? runningGlyph(widgetJobsRunningSeed(running)) : hasActive ? "\u25CF" : "\u25CB";
834
+ return buildSingleWidgetLines(jobs[0], theme, width, expanded, now);
835
+ let running = jobs.filter((job) => job.status === "running"), queued = jobs.filter((job) => job.status === "queued"), finished = jobs.filter((job) => job.status !== "running" && job.status !== "queued"), lines = [], hasActive = running.length > 0 || queued.length > 0, headerGlyph = running.length > 0 ? runningGlyph((widgetJobsRunningSeed(running) ?? 0) + widgetAnimFrame(now)) : hasActive ? "\u25CF" : "\u25CB";
839
836
  lines.push(truncLine(`${theme.fg(hasActive ? "accent" : "dim", headerGlyph)} ${theme.fg(hasActive ? "accent" : "dim", "Async agents")} ${theme.fg("dim", "\xB7 background")}`, width));
840
837
  let items = [], hiddenRunning = 0, hiddenFinished = 0, queuedSummaryShown = !1, slots = MAX_WIDGET_JOBS;
841
838
  for (let job of running) {
@@ -843,12 +840,8 @@ function buildWidgetLines(jobs, theme, width = getTermWidth(), expanded = !1) {
843
840
  hiddenRunning++;
844
841
  continue;
845
842
  }
846
- let stats = widgetStats(job, theme);
847
- items.push([
848
- `${widgetStatusGlyph(job, theme)} ${themeBold(theme, widgetJobName(job))}${stats ? ` ${theme.fg("dim", "\xB7")} ${stats}` : ""}`,
849
- ` ${theme.fg("dim", `\u23BF ${widgetActivity(job)}`)}`,
850
- ...widgetParallelAgentDetails(job, theme, expanded, width)
851
- ]), slots--;
843
+ let row = compactJobChildRows(job, theme, Math.max(1, width - 3), now);
844
+ items.push(expanded ? [...row, ...widgetParallelAgentDetails(job, theme, !0, width)] : row), slots--;
852
845
  }
853
846
  queued.length > 0 && slots > 0 && (items.push([`${theme.fg("muted", "\u25E6")} ${theme.fg("dim", `${queued.length} queued`)}`]), queuedSummaryShown = !0, slots--);
854
847
  for (let job of finished) {
@@ -856,12 +849,8 @@ function buildWidgetLines(jobs, theme, width = getTermWidth(), expanded = !1) {
856
849
  hiddenFinished++;
857
850
  continue;
858
851
  }
859
- let stats = widgetStats(job, theme);
860
- items.push([
861
- `${widgetStatusGlyph(job, theme)} ${themeBold(theme, widgetJobName(job))}${stats ? ` ${theme.fg("dim", "\xB7")} ${stats}` : ""}`,
862
- ` ${theme.fg("dim", `\u23BF ${widgetActivity(job)}`)}`,
863
- ...widgetParallelAgentDetails(job, theme, expanded, width)
864
- ]), slots--;
852
+ let row = compactJobChildRows(job, theme, Math.max(1, width - 3), now);
853
+ items.push(expanded ? [...row, ...widgetParallelAgentDetails(job, theme, !0, width)] : row), slots--;
865
854
  }
866
855
  let hiddenQueued = queued.length > 0 && !queuedSummaryShown ? queued.length : 0, hiddenTotal = hiddenRunning + hiddenFinished + hiddenQueued;
867
856
  if (hiddenTotal > 0) {
@@ -876,36 +865,91 @@ function buildWidgetLines(jobs, theme, width = getTermWidth(), expanded = !1) {
876
865
  }
877
866
  return lines;
878
867
  }
879
- var widgetPaintTimer, widgetPaintCtx;
880
- function clearWidgetPaintTimer() {
881
- widgetPaintTimer && (clearInterval(widgetPaintTimer), widgetPaintTimer = void 0), widgetPaintCtx = void 0;
882
- }
883
- function ensureWidgetPaintTimer(ctx, running) {
884
- if (widgetPaintCtx = ctx, !running || !ctx.hasUI) {
885
- clearWidgetPaintTimer();
886
- return;
868
+ var activeWidget, SubagentAsyncWidget = class {
869
+ jobs;
870
+ expanded;
871
+ theme;
872
+ requestRender;
873
+ nowFn;
874
+ timer;
875
+ disposed = !1;
876
+ cachedWidth;
877
+ cachedLines;
878
+ constructor(jobs, theme, options) {
879
+ this.jobs = jobs, this.theme = theme, this.expanded = options?.expanded ?? !1, this.requestRender = options?.requestRender, this.nowFn = options?.now ?? Date.now, this.syncTimer();
887
880
  }
888
- widgetPaintTimer || (widgetPaintTimer = setInterval(() => {
889
- widgetPaintCtx?.ui.requestRender?.();
890
- }, 80), widgetPaintTimer.unref?.());
881
+ setTheme(theme) {
882
+ this.theme = theme, this.invalidate();
883
+ }
884
+ setJobs(jobs) {
885
+ this.jobs = jobs, this.invalidate(), this.syncTimer();
886
+ }
887
+ setExpanded(expanded) {
888
+ this.expanded !== expanded && (this.expanded = expanded, this.invalidate());
889
+ }
890
+ setRequestRender(requestRender) {
891
+ this.requestRender = requestRender;
892
+ }
893
+ hasRunning() {
894
+ return this.jobs.some((job) => job.status === "running");
895
+ }
896
+ syncTimer() {
897
+ this.disposed || (this.hasRunning() ? this.startTimer() : this.stopTimer());
898
+ }
899
+ startTimer() {
900
+ this.timer || (this.timer = setInterval(() => {
901
+ this.disposed || (this.invalidate(), this.requestRender?.());
902
+ }, 80), this.timer.unref?.());
903
+ }
904
+ stopTimer() {
905
+ this.timer && (clearInterval(this.timer), this.timer = void 0);
906
+ }
907
+ invalidate() {
908
+ this.cachedWidth = void 0, this.cachedLines = void 0;
909
+ }
910
+ dispose() {
911
+ this.disposed = !0, this.stopTimer(), activeWidget === this && (activeWidget = void 0);
912
+ }
913
+ render(width) {
914
+ let theme = this.theme;
915
+ if (!theme)
916
+ return [];
917
+ if (this.cachedLines && this.cachedWidth === width && !this.hasRunning())
918
+ return this.cachedLines;
919
+ let now = this.nowFn(), innerWidth = Math.max(1, width - 2), lines = this.expanded ? buildWidgetLines(this.jobs, theme, innerWidth, !0, now) : this.jobs.length === 1 ? compactSingleWidgetLines(this.jobs[0], theme, innerWidth, now) : buildWidgetLines(this.jobs, theme, innerWidth, !1, now), padded = fitAdaptiveWidgetLines(this.jobs, lines, theme, innerWidth, this.expanded).map((line) => {
920
+ let withPad = ` ${line}`, vis = visibleWidth(withPad);
921
+ return withPad + " ".repeat(Math.max(0, width - vis));
922
+ });
923
+ return this.cachedWidth = width, this.cachedLines = padded, padded;
924
+ }
925
+ };
926
+ function disposeSubagentWidget() {
927
+ activeWidget?.dispose(), activeWidget = void 0;
891
928
  }
892
929
  function renderWidget(ctx, jobs) {
893
930
  if (jobs.length === 0) {
894
- resetWidgetLayoutSession(), clearWidgetPaintTimer(), ctx.hasUI && ctx.ui.setWidget(WIDGET_KEY, void 0);
931
+ resetWidgetLayoutSession(), disposeSubagentWidget(), ctx.hasUI && ctx.ui.setWidget(WIDGET_KEY, void 0);
895
932
  return;
896
933
  }
897
- ctx.hasUI && (ensureWidgetPaintTimer(ctx, jobs.some((job) => job.status === "running")), ctx.ui.setWidget(WIDGET_KEY, buildWidgetComponent(jobs, ctx.ui.getToolsExpanded?.() ?? !1)));
934
+ if (!ctx.hasUI)
935
+ return;
936
+ let expanded = ctx.ui.getToolsExpanded?.() ?? !1, requestRender = () => ctx.ui.requestRender?.();
937
+ if (activeWidget) {
938
+ activeWidget.setJobs(jobs), activeWidget.setExpanded(expanded), activeWidget.setRequestRender(requestRender), ctx.ui.requestRender?.();
939
+ return;
940
+ }
941
+ let widget = new SubagentAsyncWidget(jobs, void 0, { expanded, requestRender });
942
+ activeWidget = widget, ctx.ui.setWidget(WIDGET_KEY, (_tui, theme) => (widget.setTheme(theme), widget));
898
943
  }
899
944
  function renderSingleCompact(d, r, theme, frame) {
900
- let output = r.truncation?.text || getSingleResultOutput(r), progress = r.progress || r.progressSummary, isRunning = r.progress?.status === "running", contextBadge = d.context === "fork" ? theme.fg("warning", " [fork]") : "", c = new Container(), width = getTermWidth() - 4, lead = compactRowLead({ description: r.description ?? r.agent, task: r.task }), modelBadge = formatModelSelection(r.modelSelection ?? progress?.modelSelection, r.model ?? progress?.model, r.thinking ?? progress?.thinking), permissionBadge = r.permissions ? theme.fg("dim", r.permissions) : "";
901
- if (c.addChild(animatedLine((f, now) => {
902
- let stats = formatProgressStats(theme, progress, !0, { running: isRunning, terminal: !isRunning, now });
903
- return truncLine(`${resultGlyph(r, output, theme, isRunning, void 0, f)} ${themeBold(theme, lead)}${modelBadge ? ` ${theme.fg("dim", "\xB7")} ${theme.fg("dim", modelBadge)}` : ""}${permissionBadge ? ` ${theme.fg("dim", "\xB7")} ${permissionBadge}` : ""}${contextBadge}${stats ? ` ${theme.fg("dim", "\xB7")} ${stats}` : ""}`, width);
904
- }, frame, isRunning)), isRunning && r.progress)
905
- return c.addChild(animatedLine((_f, now) => formatCompactStatsHangLine(r.progress, width, (s) => theme.fg("dim", s), " \u23BF ", now, !0), frame, !0)), c;
906
- c.addChild(new Text(truncLine(theme.fg("dim", ` \u23BF ${resultStatusLine(r, output)}`), width), 0, 0));
907
- let preview = firstOutputLine(output);
908
- return preview && r.exitCode === 0 && !hasEmptyTextOutputWithoutOutputTarget(r.task, output) && c.addChild(new Text(truncLine(theme.fg("dim", ` ${preview}`), width), 0, 0)), r.sessionFile && c.addChild(new Text(truncLine(theme.fg("dim", ` session: ${shortenPath(r.sessionFile)}`), width), 0, 0)), c;
945
+ let output = r.truncation?.text || getSingleResultOutput(r), progress = r.progress || r.progressSummary, isRunning = r.progress?.status === "running", c = new Container(), width = getTermWidth() - 4, lead = compactRowLead({ description: r.description ?? r.agent, task: r.task }), modelBadge = formatCompactModelBadge(r.modelSelection ?? progress?.modelSelection, r.model ?? progress?.model);
946
+ return c.addChild(animatedLine((f, now) => formatCompactSubagentRow(theme, width, {
947
+ glyph: resultGlyph(r, output, theme, isRunning, void 0, f),
948
+ description: lead,
949
+ modelBadge,
950
+ tokens: compactResultTokens(r),
951
+ durationMs: liveDurationMs(progress, isRunning, now)
952
+ }), frame, isRunning)), !isRunning && (r.exitCode !== 0 || r.interrupted || r.detached || r.stopped) && c.addChild(new Text(truncLine(theme.fg(r.exitCode !== 0 ? "error" : "dim", ` \u23BF ${resultStatusLine(r, output)}`), width), 0, 0)), c;
909
953
  }
910
954
  function renderMultiCompact(d, theme, frame) {
911
955
  let hasRunning = d.progress?.some((p) => p.status === "running") || d.results.some((r) => r.progress?.status === "running") || workflowGraphHasStatus(d, ["running"]), stopped = d.results.some((r) => r.stopped && r.progress?.status !== "running") || workflowGraphHasStatus(d, ["stopped"]), failed = d.results.some((r) => !r.stopped && r.exitCode !== 0 && r.progress?.status !== "running") || workflowGraphHasStatus(d, ["failed"]), paused = d.results.some((r) => (r.interrupted || r.detached) && r.progress?.status !== "running") || workflowGraphHasStatus(d, ["paused", "detached"]), totalSummary = d.progressSummary;
@@ -918,8 +962,8 @@ function renderMultiCompact(d, theme, frame) {
918
962
  sawProgress && (totalSummary = summary);
919
963
  }
920
964
  let multiLabel = buildMultiProgressLabel(d, hasRunning), itemTitle = multiLabel.itemTitle, glyph = (f) => hasRunning ? theme.fg("accent", runningGlyph(f !== void 0 ? (runningSeed(progressRunningSeed(totalSummary), d.currentStepIndex) ?? 0) + f : runningSeed(progressRunningSeed(totalSummary), d.currentStepIndex))) : stopped ? theme.fg("warning", "\u25A0") : failed ? theme.fg("error", "\u2717") : paused ? theme.fg("warning", "\u25A0") : theme.fg("success", "\u2713"), contextBadge = d.context === "fork" ? theme.fg("warning", " [fork]") : "", c = new Container(), width = getTermWidth() - 4;
921
- c.addChild(animatedLine((f, now) => {
922
- let stats = statJoin(theme, [multiLabel.headerLabel, formatProgressStats(theme, totalSummary, !0, { running: hasRunning, terminal: !hasRunning, now }), formatTotalCostStat(d.totalCost)]);
965
+ c.addChild(animatedLine((f) => {
966
+ let stats = statJoin(theme, [multiLabel.headerLabel, formatTotalCostStat(d.totalCost)]);
923
967
  return truncLine(`${glyph(f)} ${theme.fg("toolTitle", theme.bold(d.mode))}${contextBadge}${stats ? ` ${theme.fg("dim", "\xB7")} ${stats}` : ""}`, width);
924
968
  }, frame, hasRunning));
925
969
  let useResultsDirectly = multiLabel.hasParallelInChain || !d.chainAgents?.length, displayStart = multiLabel.showActiveGroupOnly ? multiLabel.groupStartIndex : 0, displayEnd = multiLabel.showActiveGroupOnly ? multiLabel.groupEndIndex : useResultsDirectly ? d.results.length : d.chainAgents.length, chainEntries = buildChainRenderEntries(d, multiLabel), renderEntries = chainEntries ?? Array.from({ length: displayEnd - displayStart }, (_, offset) => {
@@ -934,15 +978,22 @@ function renderMultiCompact(d, theme, frame) {
934
978
  }
935
979
  let i = entry.resultIndex, r = d.results[i], rowNumber = entry.rowNumber, agentName = entry.agentName;
936
980
  if (!r) {
937
- let pendingLabel2 = chainEntries ? resultRowLabel(d, multiLabel, i, rowNumber) : `${itemTitle} ${rowNumber}`;
938
- c.addChild(new Text(truncLine(theme.fg("dim", ` \u25E6 ${pendingLabel2}: ${agentName} \xB7 pending`), width), 0, 0));
981
+ let pendingLabel = chainEntries ? resultRowLabel(d, multiLabel, i, rowNumber) : `${itemTitle} ${rowNumber}`;
982
+ c.addChild(new Text(truncLine(theme.fg("dim", ` \u25E6 ${pendingLabel}: ${agentName} \xB7 pending`), width), 0, 0));
939
983
  continue;
940
984
  }
941
- let output = getSingleResultOutput(r), progressFromArray = d.progress?.find((p) => p.index === i) || d.progress?.find((p) => p.agent === r.agent && p.status === "running"), rProg = r.progress || progressFromArray || r.progressSummary, rRunning = rProg && "status" in rProg && rProg.status === "running", rPending = rProg && "status" in rProg && rProg.status === "pending", pendingLabel = rPending ? ` ${theme.fg("dim", "\xB7 pending")}` : "", lead = compactRowLead({ description: r.description ?? agentName, task: r.task }), permissionBadge = r.permissions ? theme.fg("dim", r.permissions) : "", modelBadge = formatModelSelection(r.modelSelection ?? (rProg && "modelSelection" in rProg ? rProg.modelSelection : void 0), r.model ?? (rProg && "model" in rProg ? rProg.model : void 0), r.thinking ?? (rProg && "thinking" in rProg ? rProg.thinking : void 0));
985
+ let output = getSingleResultOutput(r), progressFromArray = d.progress?.find((p) => p.index === i) || d.progress?.find((p) => p.agent === r.agent && p.status === "running"), rProg = r.progress || progressFromArray || r.progressSummary, rRunning = rProg && "status" in rProg && rProg.status === "running", rPending = rProg && "status" in rProg && rProg.status === "pending", lead = compactRowLead({ description: r.description ?? agentName, task: r.task }), modelBadge = formatCompactModelBadge(r.modelSelection ?? (rProg && "modelSelection" in rProg ? rProg.modelSelection : void 0), r.model ?? (rProg && "model" in rProg ? rProg.model : void 0));
942
986
  c.addChild(animatedLine((f, now) => {
943
- let glyph2 = rPending ? theme.fg("dim", "\u25E6") : resultGlyph(r, output, theme, rRunning, progressRunningSeed(rProg), f), stepStats = formatProgressStats(theme, rProg, !0, { running: !!rRunning, terminal: !rRunning && !rPending, now });
944
- return truncLine(` ${glyph2} ${themeBold(theme, lead)}${modelBadge ? ` ${theme.fg("dim", "\xB7")} ${theme.fg("dim", modelBadge)}` : ""}${permissionBadge ? ` ${theme.fg("dim", "\xB7")} ${permissionBadge}` : ""}${stepStats ? ` ${theme.fg("dim", "\xB7")} ${stepStats}` : ""}${pendingLabel}`, width);
945
- }, frame, !!rRunning)), rRunning && rProg && "status" in rProg ? c.addChild(animatedLine((_f, now) => formatCompactStatsHangLine(rProg, width, (s) => theme.fg("dim", s), " \u23BF ", now, !0), frame, !0)) : !rPending && (r.exitCode !== 0 || r.interrupted || r.detached || hasEmptyTextOutputWithoutOutputTarget(r.task, output)) && c.addChild(new Text(truncLine(theme.fg(r.exitCode !== 0 ? "error" : "dim", ` \u23BF ${resultStatusLine(r, output)}`), width), 0, 0));
987
+ let rowGlyph = rPending ? theme.fg("dim", "\u25E6") : resultGlyph(r, output, theme, rRunning, progressRunningSeed(rProg), f);
988
+ return formatCompactSubagentRow(theme, width, {
989
+ glyph: rowGlyph,
990
+ description: lead,
991
+ modelBadge,
992
+ tokens: compactResultTokens(r),
993
+ durationMs: liveDurationMs(rProg, !!rRunning, now),
994
+ prefix: " "
995
+ });
996
+ }, frame, !!rRunning)), !rPending && !rRunning && (r.exitCode !== 0 || r.interrupted || r.detached || r.stopped) && c.addChild(new Text(truncLine(theme.fg(r.exitCode !== 0 ? "error" : "dim", ` \u23BF ${resultStatusLine(r, output)}`), width), 0, 0));
946
997
  }
947
998
  return c;
948
999
  }
@@ -1057,15 +1108,15 @@ import { Type } from "typebox";
1057
1108
  function keepTopLevelParameterDescriptions(schema) {
1058
1109
  return pruneNestedDescriptions(schema, []);
1059
1110
  }
1060
- function pruneNestedDescriptions(value, path12) {
1111
+ function pruneNestedDescriptions(value, path13) {
1061
1112
  if (!value || typeof value != "object")
1062
1113
  return value;
1063
1114
  let result2 = Array.isArray(value) ? [] : Object.create(Object.getPrototypeOf(value));
1064
1115
  for (let key of Reflect.ownKeys(value)) {
1065
1116
  let descriptor = Object.getOwnPropertyDescriptor(value, key);
1066
- if (descriptor && !(key === "description" && !isPropertiesMap(path12) && !isTopLevelParameterHelpText(path12))) {
1117
+ if (descriptor && !(key === "description" && !isPropertiesMap(path13) && !isTopLevelParameterHelpText(path13))) {
1067
1118
  if ("value" in descriptor) {
1068
- let nextPath = typeof key == "string" ? [...path12, key] : path12;
1119
+ let nextPath = typeof key == "string" ? [...path13, key] : path13;
1069
1120
  descriptor.value = pruneNestedDescriptions(descriptor.value, nextPath);
1070
1121
  }
1071
1122
  Object.defineProperty(result2, key, descriptor);
@@ -1073,11 +1124,11 @@ function pruneNestedDescriptions(value, path12) {
1073
1124
  }
1074
1125
  return result2;
1075
1126
  }
1076
- function isPropertiesMap(path12) {
1077
- return path12[path12.length - 1] === "properties";
1127
+ function isPropertiesMap(path13) {
1128
+ return path13[path13.length - 1] === "properties";
1078
1129
  }
1079
- function isTopLevelParameterHelpText(path12) {
1080
- return path12.length === 2 && path12[0] === "properties";
1130
+ function isTopLevelParameterHelpText(path13) {
1131
+ return path13.length === 2 && path13[0] === "properties";
1081
1132
  }
1082
1133
  var SkillOverride = Type.Unsafe({
1083
1134
  anyOf: [
@@ -1095,10 +1146,16 @@ var SkillOverride = Type.Unsafe({
1095
1146
  }), OutputModeOverride = Type.String({
1096
1147
  enum: ["inline", "file-only"],
1097
1148
  description: "Return saved output inline (default) or only a concise file reference. file-only requires output to be a path."
1098
- }), TierOverride = Type.String({
1149
+ }), TIER_SELECTION_DESCRIPTION = "Choose the lowest model tier that can reliably complete the task: 'light' for quick checks, fact gathering, and small code inspections; 'standard' for everyday coding, clearly described bugs, and focused reviews; 'heavy' for complex implementation, difficult debugging, architectural decisions, and large or high-risk reviews.", TierOverride = Type.Optional(Type.String({
1099
1150
  enum: ["light", "standard", "heavy"],
1100
- description: "Required model tier: 'light' for simple/fast work, 'standard' for typical coding, or 'heavy' for complex reasoning. The configured tier model must be available and authenticated."
1101
- }), ReadsOverride = Type.Unsafe({
1151
+ description: `Required by default. ${TIER_SELECTION_DESCRIPTION} Do not combine with model. The configured tier model must be available and authenticated.`
1152
+ })), ModelOverride = Type.Optional(Type.String({
1153
+ minLength: 1,
1154
+ description: "Explicit provider/model id only when the user names a model. Do not combine with tier. Optional thinking belongs in the thinking field or as a known :level suffix."
1155
+ })), ThinkingOverride = Type.Optional(Type.String({
1156
+ enum: ["off", "minimal", "low", "medium", "high", "xhigh", "max"],
1157
+ description: "Thinking level for an explicit model launch. Omit for tier launches (they use configured tier thinking)."
1158
+ })), ReadsOverride = Type.Unsafe({
1102
1159
  anyOf: [
1103
1160
  { type: "array", items: { type: "string" } },
1104
1161
  { type: "boolean", enum: [!1] }
@@ -1145,6 +1202,8 @@ var SkillOverride = Type.Unsafe({
1145
1202
  reads: Type.Optional(ReadsOverride),
1146
1203
  progress: Type.Optional(Type.Boolean({ description: "Enable progress.md tracking for this task" })),
1147
1204
  tier: TierOverride,
1205
+ model: ModelOverride,
1206
+ thinking: ThinkingOverride,
1148
1207
  skill: Type.Optional(SkillOverride),
1149
1208
  toolBudget: Type.Optional(ToolBudgetOverride),
1150
1209
  acceptance: Type.Optional(AcceptanceOverride)
@@ -1164,6 +1223,8 @@ var SkillOverride = Type.Unsafe({
1164
1223
  progress: Type.Optional(Type.Boolean({ description: "Enable progress.md tracking in {chain_dir}" })),
1165
1224
  skill: Type.Optional(SkillOverride),
1166
1225
  tier: TierOverride,
1226
+ model: ModelOverride,
1227
+ thinking: ThinkingOverride,
1167
1228
  toolBudget: Type.Optional(ToolBudgetOverride),
1168
1229
  acceptance: Type.Optional(AcceptanceOverride)
1169
1230
  }), DynamicExpandSchema = Type.Object({
@@ -1193,6 +1254,8 @@ var SkillOverride = Type.Unsafe({
1193
1254
  progress: Type.Optional(Type.Boolean({ description: "Enable progress.md tracking in {chain_dir}" })),
1194
1255
  skill: Type.Optional(SkillOverride),
1195
1256
  tier: TierOverride,
1257
+ model: ModelOverride,
1258
+ thinking: ThinkingOverride,
1196
1259
  toolBudget: Type.Optional(ToolBudgetOverride),
1197
1260
  acceptance: Type.Optional(AcceptanceOverride)
1198
1261
  }, { additionalProperties: !1 }), DynamicCollectSchema = Type.Object({
@@ -1215,6 +1278,8 @@ var SkillOverride = Type.Unsafe({
1215
1278
  progress: Type.Optional(Type.Boolean({ description: "Enable progress.md tracking in {chain_dir}" })),
1216
1279
  skill: Type.Optional(SkillOverride),
1217
1280
  tier: TierOverride,
1281
+ model: ModelOverride,
1282
+ thinking: ThinkingOverride,
1218
1283
  toolBudget: Type.Optional(ToolBudgetOverride),
1219
1284
  acceptance: Type.Optional(AcceptanceOverride),
1220
1285
  parallel: Type.Optional(Type.Unsafe({
@@ -1232,7 +1297,7 @@ var SkillOverride = Type.Unsafe({
1232
1297
  description: "Create isolated git worktrees for each parallel task."
1233
1298
  }))
1234
1299
  }, {
1235
- description: "Chain step: use {task, description, tier, permissions?} for sequential, {parallel: [...]} for static concurrent execution, or {expand, parallel: {...}, collect} for dynamic fanout. Every executable child requires tier.",
1300
+ description: "Chain step: use {task, description, tier|model, permissions?} for sequential, {parallel: [...]} for static concurrent execution, or {expand, parallel: {...}, collect} for dynamic fanout. Every executable child needs exactly one of tier or model.",
1236
1301
  additionalProperties: !1
1237
1302
  }), ControlOverrides = Type.Object({
1238
1303
  enabled: Type.Optional(Type.Boolean({ description: "Enable/disable subagent control attention tracking for this run" })),
@@ -1271,12 +1336,9 @@ var SkillOverride = Type.Unsafe({
1271
1336
  })),
1272
1337
  lines: Type.Optional(Type.Integer({ minimum: 1, maximum: 500, description: "Maximum transcript lines for action='status', view='transcript'. Defaults to 80." })),
1273
1338
  message: Type.Optional(Type.String({ description: "Follow-up message for action='resume' or non-terminal guidance for action='steer'. Use index to choose a child from multi-child runs." })),
1274
- 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." })),
1275
- target: Type.Optional(Type.String({ enum: ["main", "children"], description: "Target for action='watchdog.configure'. Defaults to main." })),
1276
- thinking: Type.Optional(Type.Unsafe({ anyOf: [{ type: "string" }, { type: "boolean", enum: [!1] }], description: "Thinking level for action='watchdog.configure' (off/minimal/low/medium/high/xhigh/max, inherit, or false for off)." })),
1277
1339
  schedule: Type.Optional(Type.String({ description: "Explicit one-shot schedule for action='schedule'. Only honored when scheduledRuns.enabled is true. Use '+10m' or a future ISO timestamp with timezone; scheduled runs always launch async with fresh context." })),
1278
1340
  scheduleName: Type.Optional(Type.String({ description: "Optional display name for action='schedule'." })),
1279
- tasks: Type.Optional(Type.Array(TaskItem, { description: "PARALLEL mode: [{task, description, tier, permissions?, count?, output?, outputMode?, reads?, progress?}, ...]" })),
1341
+ tasks: Type.Optional(Type.Array(TaskItem, { description: "PARALLEL mode: [{task, description, tier|model, thinking?, permissions?, count?, output?, outputMode?, reads?, progress?}, ...]" })),
1280
1342
  concurrency: Type.Optional(Type.Integer({ minimum: 1, description: "Top-level PARALLEL mode only: max concurrent tasks. Defaults to config.parallel.concurrency or unlimited." })),
1281
1343
  worktree: Type.Optional(Type.Boolean({
1282
1344
  description: "Create isolated git worktrees for parallel tasks; requires clean git state."
@@ -1311,15 +1373,26 @@ var SkillOverride = Type.Unsafe({
1311
1373
  skill: Type.Optional(SkillOverride),
1312
1374
  tier: Type.Optional(Type.String({
1313
1375
  enum: ["light", "standard", "heavy"],
1314
- description: "Required for SINGLE execution. Omit for control actions; PARALLEL and CHAIN children carry their own required tier."
1376
+ description: `Preferred for SINGLE execution. Required by default. Omit for control actions. Do not combine with model; PARALLEL and CHAIN children carry their own tier or model. ${TIER_SELECTION_DESCRIPTION}`
1377
+ })),
1378
+ model: Type.Optional(Type.String({
1379
+ minLength: 1,
1380
+ description: "Explicit provider/model for SINGLE execution only when the user names a model. Do not combine with tier."
1381
+ })),
1382
+ thinking: Type.Optional(Type.String({
1383
+ enum: ["off", "minimal", "low", "medium", "high", "xhigh", "max"],
1384
+ description: "Thinking level for an explicit model launch. Tier launches use configured tier thinking instead."
1315
1385
  })),
1316
1386
  acceptance: Type.Optional(AcceptanceOverride)
1317
1387
  }), SubagentParams = keepTopLevelParameterDescriptions(SubagentParamsSchema), SubagentWaitParamsSchema = Type.Object({
1318
1388
  id: Type.Optional(Type.String({
1319
1389
  description: "Async run or remembered detached foreground run id/prefix to wait for one specific run. Omit to wait across every active async run started in this session."
1320
1390
  })),
1391
+ questionId: Type.Optional(Type.String({
1392
+ description: "Wait for one supervisor question id from subagent_supervisor action='ask'. Returns when that question is answered, expired, or cancelled. Separate from final async run results."
1393
+ })),
1321
1394
  all: Type.Optional(Type.Boolean({
1322
- description: "Wait for ALL active runs to finish. Default false: return as soon as the first run finishes, so a fleet manager can spawn a replacement and wait again. Ignored when id targets a single run."
1395
+ description: "Wait for ALL active runs to finish. Default false: return as soon as the first run finishes, so a fleet manager can spawn a replacement and wait again. Ignored when id or questionId targets one item."
1323
1396
  })),
1324
1397
  timeoutMs: Type.Optional(Type.Integer({
1325
1398
  minimum: 1,
@@ -1344,28 +1417,28 @@ function typeName(value) {
1344
1417
  function quoteList(keys) {
1345
1418
  return keys.map((key) => `"${key}"`).join(", ");
1346
1419
  }
1347
- function disallowedMessage(path12, keys, allowed, example) {
1420
+ function disallowedMessage(path13, keys, allowed, example) {
1348
1421
  let subject = keys.length === 1 ? `property "${keys[0]}" is not allowed` : `properties ${quoteList(keys)} are not allowed`, examplePart = example ? `
1349
1422
 
1350
1423
  Example: ${example}` : "";
1351
- return `subagent ${path12}: ${subject}.
1424
+ return `subagent ${path13}: ${subject}.
1352
1425
  Allowed properties: ${allowed.join(", ")}.${examplePart}`;
1353
1426
  }
1354
- function expectObject(value, path12, allowed, example) {
1427
+ function expectObject(value, path13, allowed, example) {
1355
1428
  if (!isPlainObject(value)) {
1356
1429
  let examplePart = example ? `
1357
1430
 
1358
1431
  Example: ${example}` : "";
1359
- throw new Error(`subagent ${path12}: expected an object, received ${typeName(value)}.
1432
+ throw new Error(`subagent ${path13}: expected an object, received ${typeName(value)}.
1360
1433
  Allowed properties: ${allowed.join(", ")}.${examplePart}`);
1361
1434
  }
1362
1435
  }
1363
- function checkNoExtraKeys(value, path12, schema, allowed, example) {
1436
+ function checkNoExtraKeys(value, path13, schema, allowed, example) {
1364
1437
  if (schema.additionalProperties !== !1)
1365
1438
  return;
1366
1439
  let extra = disallowedKeys(value, allowed);
1367
1440
  if (extra.length > 0)
1368
- throw new Error(disallowedMessage(path12, extra, allowed, example));
1441
+ throw new Error(disallowedMessage(path13, extra, allowed, example));
1369
1442
  }
1370
1443
  function validateChainInput(args) {
1371
1444
  if (!isPlainObject(args))
@@ -1618,8 +1691,7 @@ function createAsyncJobTracker(pi, state, asyncDirRoot, options = {}) {
1618
1691
  }
1619
1692
  widgetRenderKey(job) !== widgetStateBefore && (widgetChanged = !0);
1620
1693
  }
1621
- let timerNeedsPaint = [...state.asyncJobs.values()].some((job) => job.status === "running");
1622
- (widgetChanged || timerNeedsPaint) && state.lastUiContext?.hasUI && rerenderWidget(state.lastUiContext);
1694
+ widgetChanged && state.lastUiContext?.hasUI && rerenderWidget(state.lastUiContext);
1623
1695
  }, pollIntervalMs), state.poller.unref?.());
1624
1696
  };
1625
1697
  return { ensurePoller, refreshWidget, handleStarted: (data) => {
@@ -1759,15 +1831,15 @@ function createFileCoalescer(handler, defaultDelayMs, timerApi = defaultTimerApi
1759
1831
 
1760
1832
  // packages/coding-agent/dist/builtin-extensions/pi-subagents/src/runs/background/result-watcher.js
1761
1833
  var WATCHER_RESTART_DELAY_MS = 3e3, POLL_INTERVAL_MS2 = 3e3;
1762
- function sanitizeNestedResultChildren(value, resultPath, label) {
1834
+ function sanitizeNestedResultChildren(value, resultPath, label, report) {
1763
1835
  if (value === void 0)
1764
1836
  return;
1765
1837
  if (!Array.isArray(value)) {
1766
- console.error(`Ignoring invalid nested children in subagent result file '${resultPath}' at ${label}: expected an array.`);
1838
+ report(`Ignoring invalid nested children in subagent result file '${resultPath}' at ${label}: expected an array.`);
1767
1839
  return;
1768
1840
  }
1769
1841
  let children = value.map((child) => sanitizeSummary(child)).filter((child) => !!child);
1770
- return children.length !== value.length && console.error(`Ignoring ${value.length - children.length} invalid nested child record(s) in subagent result file '${resultPath}' at ${label}.`), children.length ? children : void 0;
1842
+ return children.length !== value.length && report(`Ignoring ${value.length - children.length} invalid nested child record(s) in subagent result file '${resultPath}' at ${label}.`), children.length ? children : void 0;
1771
1843
  }
1772
1844
  function getErrorCode(error) {
1773
1845
  return typeof error == "object" && error !== null && "code" in error ? error.code : void 0;
@@ -1780,27 +1852,27 @@ function shouldFallBackToPolling(error) {
1780
1852
  return code === "EMFILE" || code === "ENOSPC";
1781
1853
  }
1782
1854
  function createResultWatcher(pi, state, resultsDir, completionTtlMs, deps = {}) {
1783
- let fsApi = deps.fs ?? fs2, timers = deps.timers ?? { setTimeout, clearTimeout, setInterval, clearInterval }, handleResult = async (file) => {
1784
- let resultPath = path2.join(resultsDir, file);
1785
- if (fsApi.existsSync(resultPath))
1855
+ let fsApi = deps.fs ?? fs2, timers = deps.timers ?? { setTimeout, clearTimeout, setInterval, clearInterval }, generation = 0, inFlight = /* @__PURE__ */ new Set(), attempts = /* @__PURE__ */ new Map(), report = (message, error) => {
1856
+ pi.appendEntry?.("subagent_result_delivery", { message, error: error instanceof Error ? error.message : error });
1857
+ }, handleResult = async (file) => {
1858
+ if (inFlight.has(file) || (attempts.get(file) ?? 0) >= 3)
1859
+ return;
1860
+ let currentGeneration = generation, sessionId = state.currentSessionId, isCurrent = () => generation === currentGeneration && state.currentSessionId === sessionId, resultPath = path2.join(resultsDir, file);
1861
+ if (fsApi.existsSync(resultPath)) {
1862
+ inFlight.add(file);
1786
1863
  try {
1787
1864
  let data = JSON.parse(fsApi.readFileSync(resultPath, "utf-8"));
1788
1865
  if (typeof data.sessionId != "string" || data.sessionId !== state.currentSessionId)
1789
1866
  return;
1790
- let runId = data.runId ?? data.id ?? file.replace(/\.json$/i, ""), hasExplicitNestedChildren = data.nestedChildren !== void 0, nestedChildren = compactNestedResultChildren(sanitizeNestedResultChildren(data.nestedChildren, resultPath, "nestedChildren"));
1867
+ let runId = data.runId ?? data.id ?? file.replace(/\.json$/i, ""), hasExplicitNestedChildren = data.nestedChildren !== void 0, nestedChildren = compactNestedResultChildren(sanitizeNestedResultChildren(data.nestedChildren, resultPath, "nestedChildren", report));
1791
1868
  if (!nestedChildren?.length && !hasExplicitNestedChildren)
1792
1869
  try {
1793
1870
  nestedChildren = compactNestedResultChildren(projectNestedRegistryForRoot(runId)?.children);
1794
1871
  } catch (error) {
1795
- console.error(`Failed to enrich subagent result file '${resultPath}' with nested registry children; will retry later:`, error);
1872
+ report(`Failed to enrich subagent result file '${resultPath}' with nested registry children; will retry later:`, error);
1796
1873
  return;
1797
1874
  }
1798
- let now = Date.now(), completionKey = buildCompletionKey(data, `result:${file}`);
1799
- if (markSeenWithTtl(state.completionSeen, completionKey, now, completionTtlMs)) {
1800
- fsApi.unlinkSync(resultPath);
1801
- return;
1802
- }
1803
- let hasResultChildren = Array.isArray(data.results) && data.results.length > 0, resultChildren = hasResultChildren ? data.results : [{
1875
+ let now = Date.now(), completionKey = buildCompletionKey(data, `result:${file}`), hasResultChildren = Array.isArray(data.results) && data.results.length > 0, resultChildren = hasResultChildren ? data.results : [{
1804
1876
  agent: data.agent,
1805
1877
  output: data.summary,
1806
1878
  success: data.success
@@ -1808,7 +1880,7 @@ function createResultWatcher(pi, state, resultsDir, completionTtlMs, deps = {})
1808
1880
  let baseOutput = result2.output ?? data.summary, hasRealOutput = typeof baseOutput == "string" && baseOutput.trim().length > 0, output = hasRealOutput ? baseOutput : "(no output)", summary = result2.success === !1 && result2.error ? `${result2.error}${hasRealOutput ? `
1809
1881
 
1810
1882
  Output:
1811
- ${baseOutput}` : ""}` : output, sessionPath = result2.sessionFile ?? (resultChildren.length === 1 ? data.sessionFile : void 0), childNestedChildren = sanitizeNestedResultChildren(result2.children, resultPath, `results[${index}].children`), childState = result2.state === "paused" || result2.state === "stopped" ? result2.state : result2.stopped === !0 ? "stopped" : data.state === "paused" || !hasResultChildren && (data.state === "stopped" || typeof result2.success != "boolean") ? data.state : void 0;
1883
+ ${baseOutput}` : ""}` : output, sessionPath = result2.sessionFile ?? (resultChildren.length === 1 ? data.sessionFile : void 0), childNestedChildren = sanitizeNestedResultChildren(result2.children, resultPath, `results[${index}].children`, report), childState = result2.state === "paused" || result2.state === "stopped" ? result2.state : result2.stopped === !0 ? "stopped" : data.state === "paused" || !hasResultChildren && (data.state === "stopped" || typeof result2.success != "boolean") ? data.state : void 0;
1812
1884
  return {
1813
1885
  agent: result2.agent ?? data.agent ?? `step-${index + 1}`,
1814
1886
  status: resolveSubagentResultStatus({
@@ -1822,7 +1894,24 @@ ${baseOutput}` : ""}` : output, sessionPath = result2.sessionFile ?? (resultChil
1822
1894
  ...result2.intercomTarget ? { intercomTarget: result2.intercomTarget } : {},
1823
1895
  ...childNestedChildren ? { children: childNestedChildren } : {}
1824
1896
  };
1825
- }), nestedChildren), intercomTarget = data.intercomTarget?.trim();
1897
+ }), nestedChildren);
1898
+ if (markSeenWithTtl(state.completionSeen, completionKey, now, completionTtlMs) || pi.events.emit(SUBAGENT_ASYNC_COMPLETE_EVENT, {
1899
+ ...data,
1900
+ runId,
1901
+ ...nestedChildren?.length ? { nestedChildren } : {},
1902
+ ...Array.isArray(data.results) ? { results: hasResultChildren ? normalizedChildren.map((child, index) => ({
1903
+ ...data.results[index],
1904
+ agent: child.agent,
1905
+ status: child.status,
1906
+ summary: child.summary,
1907
+ index: child.index,
1908
+ artifactPath: child.artifactPath,
1909
+ sessionPath: child.sessionPath,
1910
+ children: child.children
1911
+ })) : [] } : {}
1912
+ }), !isCurrent())
1913
+ return;
1914
+ let intercomTarget = data.state === "stopped" ? void 0 : data.intercomTarget?.trim();
1826
1915
  if (intercomTarget) {
1827
1916
  let mode = data.mode === "single" || data.mode === "parallel" || data.mode === "chain" ? data.mode : resultChildren.length > 1 ? "chain" : "single", payload = buildSubagentResultIntercomPayload({
1828
1917
  to: intercomTarget,
@@ -1833,30 +1922,24 @@ ${baseOutput}` : ""}` : output, sessionPath = result2.sessionFile ?? (resultChil
1833
1922
  asyncId: data.id,
1834
1923
  asyncDir: data.asyncDir
1835
1924
  });
1836
- await deliverSubagentResultIntercomEvent(pi.events, payload) || console.error(`Subagent async grouped result intercom delivery was not acknowledged for '${resultPath}'.`);
1925
+ payload.requestId = `async-result:${sessionId}:${completionKey}`, attempts.set(file, (attempts.get(file) ?? 0) + 1);
1926
+ let delivered = await deliverSubagentResultIntercomEvent(pi.events, payload);
1927
+ if (!isCurrent())
1928
+ return;
1929
+ if (!delivered) {
1930
+ report(`Async result delivery pending for '${runId}'; output retained.`, resultPath), attempts.get(file) < 3 && state.resultFileCoalescer.schedule(file, 1e3);
1931
+ return;
1932
+ }
1837
1933
  }
1838
- pi.events.emit(SUBAGENT_ASYNC_COMPLETE_EVENT, {
1839
- ...data,
1840
- runId,
1841
- ...nestedChildren?.length ? { nestedChildren } : {},
1842
- ...Array.isArray(data.results) ? {
1843
- results: hasResultChildren ? normalizedChildren.map((child, index) => ({
1844
- ...data.results[index],
1845
- agent: child.agent,
1846
- status: child.status,
1847
- summary: child.summary,
1848
- index: child.index,
1849
- artifactPath: child.artifactPath,
1850
- sessionPath: child.sessionPath,
1851
- children: child.children
1852
- })) : []
1853
- } : {}
1854
- }), fsApi.unlinkSync(resultPath);
1934
+ isCurrent() && fsApi.unlinkSync(resultPath);
1855
1935
  } catch (error) {
1856
1936
  if (isNotFoundError(error))
1857
1937
  return;
1858
- console.error(`Failed to process subagent result file '${resultPath}':`, error);
1938
+ report(`Failed to process subagent result file '${resultPath}'.`, error);
1939
+ } finally {
1940
+ inFlight.delete(file);
1859
1941
  }
1942
+ }
1860
1943
  };
1861
1944
  state.resultFileCoalescer = createFileCoalescer((file) => {
1862
1945
  handleResult(file);
@@ -1867,10 +1950,10 @@ ${baseOutput}` : ""}` : output, sessionPath = result2.sessionFile ?? (resultChil
1867
1950
  } catch (error) {
1868
1951
  if (isNotFoundError(error))
1869
1952
  return;
1870
- console.error(`Failed to scan subagent result directory '${resultsDir}':`, error);
1953
+ report(`Failed to scan subagent result directory '${resultsDir}':`, error);
1871
1954
  }
1872
1955
  }, startPollingFallback = (reason) => {
1873
- state.watcher?.close(), state.watcher = null, !state.watcherRestartTimer && (console.error(`Subagent result watcher for '${resultsDir}' fell back to polling because native fs.watch is unavailable (${getErrorCode(reason) ?? "unknown error"}).`), primeExistingResults(), state.watcherRestartTimer = timers.setInterval(primeExistingResults, POLL_INTERVAL_MS2), state.watcherRestartTimer.unref?.());
1956
+ state.watcher?.close(), state.watcher = null, !state.watcherRestartTimer && (report(`Subagent result watcher for '${resultsDir}' fell back to polling because native fs.watch is unavailable (${getErrorCode(reason) ?? "unknown error"}).`), primeExistingResults(), state.watcherRestartTimer = timers.setInterval(primeExistingResults, POLL_INTERVAL_MS2), state.watcherRestartTimer.unref?.());
1874
1957
  }, scheduleRestart = () => {
1875
1958
  state.watcherRestartTimer || (state.watcherRestartTimer = timers.setTimeout(() => {
1876
1959
  state.watcherRestartTimer = null;
@@ -1881,7 +1964,7 @@ ${baseOutput}` : ""}` : output, sessionPath = result2.sessionFile ?? (resultChil
1881
1964
  startPollingFallback(error);
1882
1965
  return;
1883
1966
  }
1884
- console.error(`Failed to restart subagent result watcher for '${resultsDir}':`, error), scheduleRestart();
1967
+ report(`Failed to restart subagent result watcher for '${resultsDir}':`, error), scheduleRestart();
1885
1968
  }
1886
1969
  }, WATCHER_RESTART_DELAY_MS), state.watcherRestartTimer.unref?.());
1887
1970
  }, startResultWatcher = () => {
@@ -1899,19 +1982,19 @@ ${baseOutput}` : ""}` : output, sessionPath = result2.sessionFile ?? (resultChil
1899
1982
  startPollingFallback(error);
1900
1983
  return;
1901
1984
  }
1902
- console.error(`Subagent result watcher failed for '${resultsDir}':`, error), state.watcher?.close(), state.watcher = null, scheduleRestart();
1985
+ report(`Subagent result watcher failed for '${resultsDir}':`, error), state.watcher?.close(), state.watcher = null, scheduleRestart();
1903
1986
  }), state.watcher.unref?.();
1904
1987
  } catch (error) {
1905
1988
  if (shouldFallBackToPolling(error)) {
1906
1989
  startPollingFallback(error);
1907
1990
  return;
1908
1991
  }
1909
- console.error(`Failed to start subagent result watcher for '${resultsDir}':`, error), state.watcher = null, scheduleRestart();
1992
+ report(`Failed to start subagent result watcher for '${resultsDir}':`, error), state.watcher = null, scheduleRestart();
1910
1993
  }
1911
1994
  }
1912
1995
  };
1913
1996
  return { startResultWatcher, primeExistingResults, stopResultWatcher: () => {
1914
- state.watcher?.close(), state.watcher = null, state.watcherRestartTimer && (timers.clearTimeout(state.watcherRestartTimer), timers.clearInterval(state.watcherRestartTimer)), state.watcherRestartTimer = null, state.resultFileCoalescer.clear();
1997
+ generation++, attempts.clear(), state.watcher?.close(), state.watcher = null, state.watcherRestartTimer && (timers.clearTimeout(state.watcherRestartTimer), timers.clearInterval(state.watcherRestartTimer)), state.watcherRestartTimer = null, state.resultFileCoalescer.clear();
1915
1998
  } };
1916
1999
  }
1917
2000
 
@@ -3100,6 +3183,12 @@ var parseInlineConfig = (raw) => {
3100
3183
  case "tier":
3101
3184
  (val === "light" || val === "standard" || val === "heavy") && (config.tier = val);
3102
3185
  break;
3186
+ case "model":
3187
+ val && (config.model = val);
3188
+ break;
3189
+ case "thinking":
3190
+ ["off", "minimal", "low", "medium", "high", "xhigh", "max"].includes(val) && (config.thinking = val);
3191
+ break;
3103
3192
  case "permissions":
3104
3193
  (val === "full" || val === "read-only") && (config.permissions = val);
3105
3194
  break;
@@ -3649,12 +3738,16 @@ function loadInlineOutputSchema(baseCwd, agent, value) {
3649
3738
  }
3650
3739
  var mapParsedTaskToStepObject = (step, fallbackTask, isFirst, opts) => {
3651
3740
  let { name, config, task: stepTask } = step;
3652
- if (!config.tier)
3653
- throw new SlashParseError(`Step '${name}' requires tier=light, tier=standard, or tier=heavy.`);
3741
+ if (!config.tier && !config.model)
3742
+ throw new SlashParseError(`Step '${name}' requires tier=light|standard|heavy or model=provider/id.`);
3743
+ if (config.tier && config.model)
3744
+ throw new SlashParseError(`Step '${name}' must choose exactly one of tier or model.`);
3654
3745
  return config.acceptance !== void 0 && validateInlineAcceptanceInput(config.acceptance, name), {
3655
3746
  description: name,
3656
3747
  permissions: config.permissions ?? "full",
3657
- tier: config.tier,
3748
+ ...config.tier ? { tier: config.tier } : {},
3749
+ ...config.model ? { model: config.model } : {},
3750
+ ...config.thinking ? { thinking: config.thinking } : {},
3658
3751
  ...stepTask ? { task: stepTask } : isFirst && fallbackTask ? { task: fallbackTask } : {},
3659
3752
  ...config.output !== void 0 ? { output: config.output } : {},
3660
3753
  ...config.outputMode !== void 0 ? { outputMode: config.outputMode } : {},
@@ -3765,7 +3858,9 @@ ${finalTask}`);
3765
3858
  let tasks = parsed.steps.map(({ name, config, task: stepTask }) => ({
3766
3859
  description: name,
3767
3860
  permissions: config.permissions ?? "full",
3768
- tier: config.tier,
3861
+ ...config.tier ? { tier: config.tier } : {},
3862
+ ...config.model ? { model: config.model } : {},
3863
+ ...config.thinking ? { thinking: config.thinking } : {},
3769
3864
  task: stepTask ?? parsed.task,
3770
3865
  ...config.output !== void 0 ? { output: config.output } : {},
3771
3866
  ...config.outputMode !== void 0 ? { outputMode: config.outputMode } : {},
@@ -3773,8 +3868,12 @@ ${finalTask}`);
3773
3868
  ...config.skill !== void 0 ? { skill: config.skill } : {},
3774
3869
  ...config.progress !== void 0 ? { progress: config.progress } : {}
3775
3870
  }));
3776
- if (tasks.some((task) => !task.tier)) {
3777
- ctx.ui.notify("Every parallel child requires [tier=light|standard|heavy].", "error");
3871
+ if (tasks.some((task) => !task.tier && !task.model)) {
3872
+ ctx.ui.notify("Every parallel child requires [tier=light|standard|heavy] or [model=provider/id].", "error");
3873
+ return;
3874
+ }
3875
+ if (tasks.some((task) => task.tier && task.model)) {
3876
+ ctx.ui.notify("Each parallel child must choose exactly one of tier or model.", "error");
3778
3877
  return;
3779
3878
  }
3780
3879
  let params = { tasks, clarify: !1 };
@@ -4402,28 +4501,506 @@ function registerSlashSubagentBridge(options) {
4402
4501
  }
4403
4502
 
4404
4503
  // packages/coding-agent/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.js
4504
+ import * as fs7 from "node:fs";
4505
+ import * as path8 from "node:path";
4506
+ import { Type as Type2 } from "typebox";
4507
+
4508
+ // packages/coding-agent/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-questions.js
4509
+ import { randomUUID as randomUUID3 } from "node:crypto";
4405
4510
  import * as fs6 from "node:fs";
4406
4511
  import * as path7 from "node:path";
4407
- import { Type as Type2 } from "typebox";
4408
- var SUPERVISOR_CHANNEL_ROOT = path7.join(TEMP_ROOT_DIR, "supervisor-channels"), REQUESTS_DIR = "requests", REPLIES_DIR = "replies", NATIVE_SUPERVISOR_TOOL_NAME = "subagent_supervisor", MAX_MESSAGE_BYTES = 64 * 1024, DEFAULT_ASK_TIMEOUT_MS = 600 * 1e3, CHANNEL_POLL_MS = Math.min(POLL_INTERVAL_MS, 500), STALE_EMPTY_CHANNEL_AGE_MS = 60 * 1e3, STALE_EMPTY_CHANNEL_CLEANUP_INTERVAL_MS = 60 * 1e3, ContactSupervisorParamsSchema = Type2.Object({
4409
- reason: Type2.String({ enum: ["need_decision", "interview_request", "progress_update"] }),
4512
+ var QUESTIONS_DIR = "questions", SUPERVISOR_CHANNEL_ROOT = path7.join(TEMP_ROOT_DIR, "supervisor-channels"), SUBAGENT_SUPERVISOR_ANSWER_TYPE = "subagent_supervisor_answer", SUBAGENT_QUESTION_EVENT = "subagent:supervisor-question", MAX_MESSAGE_BYTES = 64 * 1024, DEFAULT_ASK_TIMEOUT_MS = 600 * 1e3, activeQuestionWaits = /* @__PURE__ */ new Set();
4513
+ function askTimeoutMs() {
4514
+ let parsed = Number(process.env.PI_INTERCOM_ASK_TIMEOUT_MS);
4515
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_ASK_TIMEOUT_MS;
4516
+ }
4517
+ function safeSegment(value) {
4518
+ return value.trim().replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "unknown";
4519
+ }
4520
+ function resolveSupervisorChannelDir(runId, agent, childIndex) {
4521
+ return path7.join(SUPERVISOR_CHANNEL_ROOT, `${safeSegment(runId)}-${safeSegment(agent)}-${childIndex}`);
4522
+ }
4523
+ function questionsDir(channelDir) {
4524
+ return path7.join(channelDir, QUESTIONS_DIR);
4525
+ }
4526
+ function questionFile(channelDir, questionId) {
4527
+ return path7.join(questionsDir(channelDir), `${safeSegment(questionId)}.json`);
4528
+ }
4529
+ function deliveredFile(channelDir, questionId) {
4530
+ return path7.join(questionsDir(channelDir), `${safeSegment(questionId)}.delivered.json`);
4531
+ }
4532
+ function terminalFile(channelDir, questionId) {
4533
+ return path7.join(questionsDir(channelDir), `${safeSegment(questionId)}.terminal.json`);
4534
+ }
4535
+ function notifiedFile(channelDir, questionId) {
4536
+ return path7.join(questionsDir(channelDir), `${safeSegment(questionId)}.notified.json`);
4537
+ }
4538
+ function lateFile(channelDir, questionId) {
4539
+ return path7.join(questionsDir(channelDir), `${safeSegment(questionId)}.late.json`);
4540
+ }
4541
+ function writeExclusiveJson(filePath, payload) {
4542
+ fs6.mkdirSync(path7.dirname(filePath), { recursive: !0, mode: 448 });
4543
+ let temporary = `${filePath}.${randomUUID3()}.tmp`;
4544
+ try {
4545
+ return fs6.writeFileSync(temporary, JSON.stringify(payload), { encoding: "utf-8", flag: "wx", mode: 384 }), fs6.linkSync(temporary, filePath), !0;
4546
+ } catch (error) {
4547
+ if (error.code === "EEXIST")
4548
+ return !1;
4549
+ throw error;
4550
+ } finally {
4551
+ fs6.rmSync(temporary, { force: !0 });
4552
+ }
4553
+ }
4554
+ function readJsonFile(filePath) {
4555
+ return JSON.parse(fs6.readFileSync(filePath, "utf-8"));
4556
+ }
4557
+ function tryReadJson(filePath) {
4558
+ try {
4559
+ return readJsonFile(filePath);
4560
+ } catch (error) {
4561
+ return error.code === "ENOENT", void 0;
4562
+ }
4563
+ }
4564
+ function parseIncarnation(raw) {
4565
+ if (!raw || typeof raw != "object" || Array.isArray(raw))
4566
+ return;
4567
+ let input = raw, pid = input.pid, readyAt = input.readyAt;
4568
+ if (!(!Number.isInteger(pid) || pid === void 0 || pid <= 0) && !(typeof readyAt != "number" || !Number.isFinite(readyAt) || readyAt <= 0))
4569
+ return { pid, readyAt };
4570
+ }
4571
+ function parseQuestionRecord(raw) {
4572
+ if (!raw || typeof raw != "object" || Array.isArray(raw))
4573
+ return;
4574
+ let input = raw;
4575
+ if (input.type !== "subagent.supervisor.question" || typeof input.id != "string" || !input.id.trim() || typeof input.createdAt != "number" || !Number.isFinite(input.createdAt) || typeof input.expiresAt != "number" || !Number.isFinite(input.expiresAt) || typeof input.reason != "string" || !input.reason.trim() || typeof input.message != "string" || !input.message.trim() || typeof input.runId != "string" || !input.runId.trim())
4576
+ return;
4577
+ let childIndex = input.childIndex;
4578
+ if (!Number.isInteger(childIndex) || childIndex === void 0 || childIndex < 0 || typeof input.childId != "string" || !input.childId.trim() || typeof input.parentSessionId != "string" || !input.parentSessionId.trim())
4579
+ return;
4580
+ let parentGeneration = input.parentGeneration;
4581
+ if (!Number.isInteger(parentGeneration) || parentGeneration === void 0 || parentGeneration < 0)
4582
+ return;
4583
+ let incarnation = parseIncarnation(input.childIncarnation);
4584
+ return {
4585
+ type: "subagent.supervisor.question",
4586
+ id: input.id.trim(),
4587
+ createdAt: input.createdAt,
4588
+ expiresAt: input.expiresAt,
4589
+ reason: input.reason.trim(),
4590
+ message: input.message.trim(),
4591
+ runId: input.runId.trim(),
4592
+ childIndex,
4593
+ childId: input.childId.trim(),
4594
+ parentSessionId: input.parentSessionId.trim(),
4595
+ parentGeneration,
4596
+ ...incarnation ? { childIncarnation: incarnation } : {}
4597
+ };
4598
+ }
4599
+ function parseDeliveredRecord(raw) {
4600
+ if (!raw || typeof raw != "object" || Array.isArray(raw))
4601
+ return;
4602
+ let input = raw;
4603
+ if (input.type !== "subagent.supervisor.delivered" || typeof input.id != "string" || !input.id.trim() || typeof input.at != "number" || !Number.isFinite(input.at))
4604
+ return;
4605
+ let incarnation = parseIncarnation(input);
4606
+ if (incarnation)
4607
+ return { type: "subagent.supervisor.delivered", id: input.id.trim(), at: input.at, pid: incarnation.pid, readyAt: incarnation.readyAt };
4608
+ }
4609
+ function parseTerminalRecord(raw) {
4610
+ if (!raw || typeof raw != "object" || Array.isArray(raw))
4611
+ return;
4612
+ let input = raw;
4613
+ if (!(input.type !== "subagent.supervisor.terminal" || typeof input.id != "string" || !input.id.trim()) && !(typeof input.at != "number" || !Number.isFinite(input.at))) {
4614
+ if (input.state === "answered")
4615
+ return typeof input.message != "string" || !input.message.trim() ? void 0 : { type: "subagent.supervisor.terminal", id: input.id.trim(), state: "answered", at: input.at, message: input.message };
4616
+ if (input.state === "expired")
4617
+ return { type: "subagent.supervisor.terminal", id: input.id.trim(), state: "expired", at: input.at };
4618
+ if (input.state === "cancelled")
4619
+ return typeof input.reason != "string" || !input.reason.trim() ? void 0 : { type: "subagent.supervisor.terminal", id: input.id.trim(), state: "cancelled", at: input.at, reason: input.reason.trim() };
4620
+ }
4621
+ }
4622
+ function parseNotifiedRecord(raw) {
4623
+ if (!raw || typeof raw != "object" || Array.isArray(raw))
4624
+ return;
4625
+ let input = raw;
4626
+ if (!(input.type !== "subagent.supervisor.notified" || typeof input.id != "string" || !input.id.trim()) && !(typeof input.at != "number" || !Number.isFinite(input.at)))
4627
+ return { type: "subagent.supervisor.notified", id: input.id.trim(), at: input.at };
4628
+ }
4629
+ function parseLateRecord(raw) {
4630
+ if (!raw || typeof raw != "object" || Array.isArray(raw))
4631
+ return;
4632
+ let input = raw;
4633
+ if (!(input.type !== "subagent.supervisor.late-answer" || typeof input.id != "string" || !input.id.trim()) && !(typeof input.at != "number" || !Number.isFinite(input.at) || typeof input.message != "string"))
4634
+ return { type: "subagent.supervisor.late-answer", id: input.id.trim(), at: input.at, message: input.message };
4635
+ }
4636
+ function composeQuestion(record, channelDir) {
4637
+ let delivered = parseDeliveredRecord(tryReadJson(deliveredFile(channelDir, record.id))), terminal = parseTerminalRecord(tryReadJson(terminalFile(channelDir, record.id))), notified = parseNotifiedRecord(tryReadJson(notifiedFile(channelDir, record.id))), late = parseLateRecord(tryReadJson(lateFile(channelDir, record.id)));
4638
+ return {
4639
+ ...record,
4640
+ ...delivered ? { deliveredAt: delivered.at, childIncarnation: record.childIncarnation ?? { pid: delivered.pid, readyAt: delivered.readyAt } } : {},
4641
+ ...terminal?.state === "answered" ? { answeredAt: terminal.at, answer: terminal.message } : {},
4642
+ ...terminal?.state === "expired" ? { expiredAt: terminal.at } : {},
4643
+ ...terminal?.state === "cancelled" ? { cancelledAt: terminal.at, cancelReason: terminal.reason } : {},
4644
+ ...notified ? { notifiedAt: notified.at } : {},
4645
+ ...late ? { lateAnswerAt: late.at, ...terminal?.state === "answered" ? {} : { answer: late.message } } : {}
4646
+ };
4647
+ }
4648
+ function assertMessageSize(value, label) {
4649
+ if (Buffer.byteLength(value, "utf-8") > MAX_MESSAGE_BYTES)
4650
+ throw new Error(`${label} is too large.`);
4651
+ }
4652
+ function ensureQuestionsDir(channelDir) {
4653
+ fs6.mkdirSync(questionsDir(channelDir), { recursive: !0, mode: 448 });
4654
+ }
4655
+ function supervisorQuestionPublicState(question) {
4656
+ return question.answeredAt ? "answered" : question.expiredAt ? "expired" : question.cancelledAt ? "cancelled" : "pending";
4657
+ }
4658
+ function supervisorQuestionIsTerminal(question) {
4659
+ return supervisorQuestionPublicState(question) !== "pending";
4660
+ }
4661
+ function readSupervisorQuestion(channelDir, questionId) {
4662
+ let record = parseQuestionRecord(tryReadJson(questionFile(channelDir, questionId)));
4663
+ if (!(!record || record.id !== questionId))
4664
+ return composeQuestion(record, channelDir);
4665
+ }
4666
+ function listSupervisorQuestions(channelDir) {
4667
+ let dir = questionsDir(channelDir), entries;
4668
+ try {
4669
+ entries = fs6.readdirSync(dir).filter((name) => name.endsWith(".json") && !name.endsWith(".delivered.json") && !name.endsWith(".terminal.json") && !name.endsWith(".notified.json") && !name.endsWith(".late.json"));
4670
+ } catch (error) {
4671
+ if (error.code === "ENOENT")
4672
+ return [];
4673
+ throw error;
4674
+ }
4675
+ let questions = [];
4676
+ for (let entry of entries.sort()) {
4677
+ let record = parseQuestionRecord(tryReadJson(path7.join(dir, entry)));
4678
+ record && questions.push(composeQuestion(record, channelDir));
4679
+ }
4680
+ return questions;
4681
+ }
4682
+ function writeTerminal(channelDir, record) {
4683
+ return writeExclusiveJson(terminalFile(channelDir, record.id), record);
4684
+ }
4685
+ function createSupervisorQuestion(input) {
4686
+ let reason = input.reason.trim(), message = input.message.trim();
4687
+ if (!reason)
4688
+ throw new Error("reason is required and must name the concrete decision the child's answer will change.");
4689
+ if (!message)
4690
+ throw new Error("message is required for supervisor questions.");
4691
+ assertMessageSize(reason, "reason"), assertMessageSize(message, "message");
4692
+ let createdAt = input.now ?? Date.now(), timeoutMs = input.timeoutMs && input.timeoutMs > 0 ? input.timeoutMs : askTimeoutMs(), record = {
4693
+ type: "subagent.supervisor.question",
4694
+ id: input.id ?? randomUUID3(),
4695
+ createdAt,
4696
+ expiresAt: createdAt + timeoutMs,
4697
+ reason,
4698
+ message,
4699
+ runId: input.runId,
4700
+ childIndex: input.childIndex,
4701
+ childId: input.childId,
4702
+ parentSessionId: input.parentSessionId,
4703
+ parentGeneration: input.parentGeneration,
4704
+ ...input.childIncarnation ? { childIncarnation: input.childIncarnation } : {}
4705
+ };
4706
+ if (assertMessageSize(JSON.stringify(record), "Supervisor question"), ensureQuestionsDir(input.channelDir), !writeExclusiveJson(questionFile(input.channelDir, record.id), record))
4707
+ throw new Error(`Supervisor question '${record.id}' already exists.`);
4708
+ return composeQuestion(record, input.channelDir);
4709
+ }
4710
+ function outstandingQuestionsForChild(channelDir, childIndex, childId) {
4711
+ return refreshSupervisorQuestionLifecycle(channelDir), listSupervisorQuestions(channelDir).filter((question) => !(supervisorQuestionIsTerminal(question) || question.childIndex !== childIndex || childId && question.childId !== childId));
4712
+ }
4713
+ function hasBlockingSupervisorRequest(channelDir, runId, childIndex) {
4714
+ let directory = path7.join(channelDir, "requests");
4715
+ if (!fs6.existsSync(directory))
4716
+ return !1;
4717
+ for (let name of fs6.readdirSync(directory)) {
4718
+ if (!name.endsWith(".json"))
4719
+ continue;
4720
+ let raw = tryReadJson(path7.join(directory, name));
4721
+ if (!raw || typeof raw != "object")
4722
+ continue;
4723
+ let request = raw;
4724
+ if (!(request.type !== "subagent.supervisor.request" || request.expectsReply !== !0) && !(request.runId !== runId || request.childIndex !== childIndex || typeof request.id != "string") && !(typeof request.expiresAt == "number" && request.expiresAt <= Date.now()) && !fs6.existsSync(path7.join(channelDir, "replies", `${safeSegment(request.id)}.json`)))
4725
+ return !0;
4726
+ }
4727
+ return !1;
4728
+ }
4729
+ function hasOutstandingQuestionForChild(channelDir, childIndex, childId) {
4730
+ return outstandingQuestionsForChild(channelDir, childIndex, childId).length > 0;
4731
+ }
4732
+ function incarnationMatches(expected, actual) {
4733
+ return expected ? expected.pid === actual.pid && expected.readyAt === actual.readyAt : !0;
4734
+ }
4735
+ function expireSupervisorQuestion(channelDir, questionId, now = Date.now()) {
4736
+ let current = readSupervisorQuestion(channelDir, questionId);
4737
+ return !current || supervisorQuestionIsTerminal(current) || now <= current.expiresAt ? current && supervisorQuestionIsTerminal(current) ? current : void 0 : (writeTerminal(channelDir, { type: "subagent.supervisor.terminal", id: questionId, state: "expired", at: now }), readSupervisorQuestion(channelDir, questionId));
4738
+ }
4739
+ function cancelSupervisorQuestion(channelDir, questionId, reason, now = Date.now()) {
4740
+ let current = readSupervisorQuestion(channelDir, questionId);
4741
+ return !current || supervisorQuestionIsTerminal(current) ? current : (writeTerminal(channelDir, { type: "subagent.supervisor.terminal", id: questionId, state: "cancelled", at: now, reason: reason.trim() || "cancelled" }), readSupervisorQuestion(channelDir, questionId));
4742
+ }
4743
+ function refreshSupervisorQuestionLifecycle(channelDir, now = Date.now()) {
4744
+ let changed = [];
4745
+ for (let question of listSupervisorQuestions(channelDir))
4746
+ if (!supervisorQuestionIsTerminal(question) && now > question.expiresAt) {
4747
+ let expired = expireSupervisorQuestion(channelDir, question.id, now);
4748
+ expired && expired.expiredAt && changed.push(expired);
4749
+ }
4750
+ return changed;
4751
+ }
4752
+ function formatParentQuestionForChild(question) {
4753
+ return [
4754
+ `Parent question (${question.id})`,
4755
+ `Decision needed: ${question.reason}`,
4756
+ "",
4757
+ question.message,
4758
+ "",
4759
+ `Reply with: contact_supervisor({ action: "reply", replyTo: "${question.id}", message: "..." })`,
4760
+ "Answer from current findings and uncertainty. Do not silently start extra investigation. Continue the assigned task after replying."
4761
+ ].join(`
4762
+ `);
4763
+ }
4764
+ function formatSupervisorQuestionAnswer(question) {
4765
+ let state = supervisorQuestionPublicState(question), lines = [
4766
+ state === "answered" ? `Subagent answer (${question.id})` : `Subagent question ${state} (${question.id})`,
4767
+ `Run: ${question.runId}`,
4768
+ `Child: ${question.childId} [#${question.childIndex}]`,
4769
+ `Decision: ${question.reason}`
4770
+ ];
4771
+ return state === "answered" && question.answer?.trim() ? lines.push("", question.answer.trim()) : question.cancelReason && lines.push("", question.cancelReason), lines.join(`
4772
+ `);
4773
+ }
4774
+ function registerQuestionWait(questionIds) {
4775
+ let ids = [...questionIds];
4776
+ for (let id of ids)
4777
+ activeQuestionWaits.add(id);
4778
+ return () => {
4779
+ for (let id of ids)
4780
+ activeQuestionWaits.delete(id);
4781
+ };
4782
+ }
4783
+ function isQuestionWaitActive(questionId) {
4784
+ return activeQuestionWaits.has(questionId);
4785
+ }
4786
+ function claimQuestionNotification(channelDir, questionId, now = Date.now()) {
4787
+ return writeExclusiveJson(notifiedFile(channelDir, questionId), {
4788
+ type: "subagent.supervisor.notified",
4789
+ id: questionId,
4790
+ at: now
4791
+ });
4792
+ }
4793
+ function listSupervisorChannelDirs() {
4794
+ let entries;
4795
+ try {
4796
+ entries = fs6.readdirSync(SUPERVISOR_CHANNEL_ROOT, { withFileTypes: !0 });
4797
+ } catch (error) {
4798
+ if (error.code === "ENOENT")
4799
+ return [];
4800
+ throw error;
4801
+ }
4802
+ return entries.filter((entry) => entry.isDirectory()).map((entry) => path7.join(SUPERVISOR_CHANNEL_ROOT, entry.name));
4803
+ }
4804
+ function findSupervisorQuestion(questionId) {
4805
+ let id = questionId.trim();
4806
+ if (id)
4807
+ for (let channelDir of listSupervisorChannelDirs()) {
4808
+ let question = readSupervisorQuestion(channelDir, id);
4809
+ if (question)
4810
+ return { channelDir, question };
4811
+ }
4812
+ }
4813
+ function cancelPendingSupervisorQuestionsForOwner(input) {
4814
+ if (!input.parentSessionId?.trim())
4815
+ return [];
4816
+ let now = input.now ?? Date.now(), cancelled = [];
4817
+ for (let channelDir of listSupervisorChannelDirs())
4818
+ for (let question of listSupervisorQuestions(channelDir)) {
4819
+ if (supervisorQuestionIsTerminal(question) || question.parentSessionId !== input.parentSessionId || input.parentGeneration !== void 0 && question.parentGeneration !== input.parentGeneration || input.runId && question.runId !== input.runId)
4820
+ continue;
4821
+ let next = cancelSupervisorQuestion(channelDir, question.id, input.reason, now);
4822
+ next?.cancelledAt && cancelled.push(next);
4823
+ }
4824
+ return cancelled;
4825
+ }
4826
+
4827
+ // packages/coding-agent/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-ask.js
4828
+ function runningSteps(status) {
4829
+ return (status.steps ?? []).map((step, index) => ({
4830
+ index,
4831
+ childId: step.childId ?? step.agent,
4832
+ agent: step.agent,
4833
+ status: step.status
4834
+ })).filter((step) => step.status === "running").map((step) => ({ index: step.index, childId: step.childId, agent: step.agent }));
4835
+ }
4836
+ function resolveAskChild(status, params) {
4837
+ if (params.index !== void 0 && (!Number.isInteger(params.index) || params.index < 0))
4838
+ throw new Error("index must be a non-negative integer.");
4839
+ let running = runningSteps(status);
4840
+ if (params.childId?.trim()) {
4841
+ let childId = params.childId.trim(), matches = (status.steps ?? []).map((step, index) => ({ index, childId: step.childId ?? step.agent, agent: step.agent, status: step.status })).filter((step) => step.childId === childId);
4842
+ if (matches.length === 0)
4843
+ throw new Error(`Async run '${status.runId}' has no child '${childId}'.`);
4844
+ if (matches.length > 1)
4845
+ throw new Error(`Async run '${status.runId}' has multiple children named '${childId}'. Pass index.`);
4846
+ let match = matches[0];
4847
+ if (params.index !== void 0 && params.index !== match.index)
4848
+ throw new Error(`Child '${childId}' is index ${match.index}, not ${params.index}.`);
4849
+ if (match.status !== "running")
4850
+ throw new Error(`Async run '${status.runId}' child ${match.index} is ${match.status} and cannot be asked.`);
4851
+ return { index: match.index, childId: match.childId, agent: match.agent };
4852
+ }
4853
+ if (params.index !== void 0) {
4854
+ let steps = status.steps ?? [];
4855
+ if (params.index >= steps.length)
4856
+ throw new Error(`Async run '${status.runId}' has ${steps.length} children. Index ${params.index} is out of range.`);
4857
+ let step = steps[params.index];
4858
+ if (step.status !== "running")
4859
+ throw new Error(`Async run '${status.runId}' child ${params.index} is ${step.status} and cannot be asked.`);
4860
+ return { index: params.index, childId: step.childId ?? step.agent, agent: step.agent };
4861
+ }
4862
+ if (running.length === 1)
4863
+ return running[0];
4864
+ throw running.length === 0 ? new Error(`Async run '${status.runId}' has no running child to ask.`) : new Error(`Async run '${status.runId}' has ${running.length} running children. Pass index or childId; broadcast asks are not supported.`);
4865
+ }
4866
+ function requestMatchesChild(request, runId, child) {
4867
+ if (!request.expectsReply || typeof request.runId != "string" || request.runId !== runId)
4868
+ return !1;
4869
+ let indexMatches = Number.isInteger(request.childIndex) && request.childIndex === child.index, idMatches = request.agent === child.childId || request.agent === child.agent || request.childTarget === child.childId;
4870
+ return indexMatches && idMatches;
4871
+ }
4872
+ function reconcileAsyncQuestion(channelDir, question, state) {
4873
+ expireSupervisorQuestion(channelDir, question.id);
4874
+ let current = readSupervisorQuestion(channelDir, question.id) ?? question;
4875
+ if (supervisorQuestionIsTerminal(current))
4876
+ return current;
4877
+ let resolved = resolveSubagentRunId(current.runId, { state });
4878
+ if (resolved?.kind !== "async" || !resolved.location.asyncDir)
4879
+ return current;
4880
+ let status = readStatus(resolved.location.asyncDir);
4881
+ if (!status)
4882
+ return current;
4883
+ let step = status.steps?.[current.childIndex], capability = readSteerCapability(resolved.location.asyncDir, current.childIndex), reason;
4884
+ status.sessionId !== current.parentSessionId ? reason = "async run ownership changed" : status.state !== "running" || !step || step.status !== "running" ? reason = "target child is no longer running" : (step.childId ?? step.agent) !== current.childId ? reason = "target child identity changed" : capability && !incarnationMatches(current.childIncarnation, capability) ? reason = "target child process changed" : capability && !capability.supported && (reason = "target child no longer accepts questions");
4885
+ let delivery = status.steering?.recent.find((request) => request.id === current.id)?.targets.find((target) => target.index === current.childIndex);
4886
+ return (delivery?.state === "failed" || delivery?.state === "late") && (reason = delivery.reason ?? "question delivery failed"), reason ? cancelSupervisorQuestion(channelDir, current.id, reason) ?? current : current;
4887
+ }
4888
+ function askRunningAsyncChild(input) {
4889
+ let runId = input.params.id?.trim();
4890
+ if (!runId)
4891
+ throw new Error("action='ask' requires id for the async run.");
4892
+ let reason = input.params.reason?.trim(), message = input.params.message?.trim();
4893
+ if (!reason)
4894
+ throw new Error("action='ask' requires reason naming the concrete decision the child's answer will change.");
4895
+ if (!message)
4896
+ throw new Error("action='ask' requires message.");
4897
+ let sessionId = input.state.currentSessionId;
4898
+ if (!sessionId)
4899
+ throw new Error("action='ask' requires an active session identity.");
4900
+ let resolved = resolveSubagentRunId(runId, { state: input.state });
4901
+ if (!resolved)
4902
+ throw new Error(`No async run matched '${runId}'.`);
4903
+ if (resolved.kind !== "async")
4904
+ throw new Error("action='ask' targets a running async child only. It does not revive, reassign, or broadcast.");
4905
+ if (!resolved.location.asyncDir)
4906
+ throw new Error(`Async run '${resolved.id}' has no live run directory.`);
4907
+ let status = readStatus(resolved.location.asyncDir);
4908
+ if (!status)
4909
+ throw new Error(`Async run '${resolved.id}' has no status.`);
4910
+ if (!status.sessionId || status.sessionId !== sessionId)
4911
+ throw new Error(`Async run '${resolved.id}' was not found in the active session.`);
4912
+ if (status.state !== "running")
4913
+ throw new Error(`Async run '${resolved.id}' is ${status.state} and cannot be asked.`);
4914
+ let child = resolveAskChild(status, input.params), channelDir = resolveSupervisorChannelDir(status.runId, child.childId, child.index);
4915
+ if (hasOutstandingQuestionForChild(channelDir, child.index, child.childId))
4916
+ throw new Error(`A question is already outstanding for async run '${status.runId}' child ${child.index}. Wait for that answer or expiry before asking again.`);
4917
+ for (let request of input.childRequests ?? [])
4918
+ if (requestMatchesChild(request, status.runId, child))
4919
+ throw new Error(`Child ${child.index} is waiting for a supervisor reply. Asking now would deadlock. Reply to the child request first.`);
4920
+ if (hasBlockingSupervisorRequest(channelDir, status.runId, child.index))
4921
+ throw new Error("Child is waiting for a supervisor reply. Reply to its request before asking a question.");
4922
+ let capability = readSteerCapability(resolved.location.asyncDir, child.index);
4923
+ if (!capability?.supported)
4924
+ throw new Error("This child is not ready to receive questions. Continue other work until it is ready; no question was queued.");
4925
+ let incarnation = { pid: capability.pid, readyAt: capability.readyAt }, question = createSupervisorQuestion({
4926
+ channelDir,
4927
+ reason,
4928
+ message,
4929
+ runId: status.runId,
4930
+ childIndex: child.index,
4931
+ childId: child.childId,
4932
+ parentSessionId: sessionId,
4933
+ parentGeneration: input.state.sessionGeneration ?? 0,
4934
+ childIncarnation: incarnation
4935
+ });
4936
+ try {
4937
+ if (hasBlockingSupervisorRequest(channelDir, status.runId, child.index))
4938
+ throw new Error("Child is waiting for a supervisor reply. Reply to its request before asking a question.");
4939
+ requestAsyncSteer(resolved.location.asyncDir, {
4940
+ message: formatParentQuestionForChild(question),
4941
+ targetIndex: child.index,
4942
+ source: "supervisor-question",
4943
+ id: question.id
4944
+ });
4945
+ } catch (error) {
4946
+ throw cancelSupervisorQuestion(channelDir, question.id, "failed to queue delivery to child"), error instanceof Error ? error : new Error(String(error));
4947
+ }
4948
+ return {
4949
+ content: [{ type: "text", text: `Question ${question.id} queued for async run ${status.runId} child ${child.index}. The child's answer is delivered separately from the run result.` }],
4950
+ details: {
4951
+ questionId: question.id,
4952
+ state: "pending",
4953
+ runId: status.runId,
4954
+ childIndex: child.index,
4955
+ childId: child.childId,
4956
+ delivered: !1,
4957
+ answered: !1
4958
+ }
4959
+ };
4960
+ }
4961
+
4962
+ // packages/coding-agent/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.js
4963
+ var REQUESTS_DIR = "requests", REPLIES_DIR = "replies", NATIVE_SUPERVISOR_TOOL_NAME = "subagent_supervisor";
4964
+ var MAX_MESSAGE_BYTES2 = 64 * 1024, DEFAULT_ASK_TIMEOUT_MS2 = 600 * 1e3, CHANNEL_POLL_MS = Math.min(POLL_INTERVAL_MS, 500), STALE_EMPTY_CHANNEL_AGE_MS = 60 * 1e3, STALE_EMPTY_CHANNEL_CLEANUP_INTERVAL_MS = 60 * 1e3, PARENT_ASK_DESCRIPTION = [
4965
+ "Ask a specific running async child, or reply to a child supervisor request.",
4966
+ "Ask only when the child owns missing context, the answer changes a concrete next decision, and waiting risks a block or rework.",
4967
+ "Use available results first, batch related questions, and keep working while awaiting the answer. Do not use ask for status checks, polling, duplicate queries, or step-by-step supervision. Follow up only when the answer leaves the original decision unresolved.",
4968
+ "Returns a question id immediately. The child's explicit answer is delivered separately from the final run result.",
4969
+ 'Ask: { action: "ask", id: "<runId>", index?: 0, childId?: "...", reason: "<concrete decision>", message: "..." }',
4970
+ "Use pending or list to find child requests awaiting your reply; status reports channel availability. Free-form send is unsupported. Asking never revives a child or changes its assignment.",
4971
+ 'Reply: { action: "reply", replyTo: "<requestId>", message: "..." }'
4972
+ ].join(" "), CHILD_CONTACT_DESCRIPTION = [
4973
+ "Contact the parent/supervisor session for a blocking decision, structured interview, progress update, or reply to a parent question.",
4974
+ "For parent questions use action='reply' with replyTo set to the question id. Answer from current findings and uncertainty; do not start extra investigation just to reply. Continue the assigned task after replying.",
4975
+ "Do not open a blocking need_decision while a parent question is already outstanding for this child."
4976
+ ].join(" "), ContactSupervisorParamsSchema = Type2.Object({
4977
+ reason: Type2.Optional(Type2.String({ enum: ["need_decision", "interview_request", "progress_update"] })),
4410
4978
  message: Type2.Optional(Type2.String()),
4411
- interview: Type2.Optional(Type2.Unsafe({ type: "object", additionalProperties: !0 }))
4979
+ interview: Type2.Optional(Type2.Unsafe({ type: "object", additionalProperties: !0 })),
4980
+ action: Type2.Optional(Type2.String({ enum: ["reply"] })),
4981
+ replyTo: Type2.Optional(Type2.String())
4412
4982
  }, { additionalProperties: !1 }), IntercomParamsSchema = Type2.Object({
4413
4983
  action: Type2.String({ enum: ["list", "send", "ask", "reply", "pending", "status"] }),
4414
4984
  to: Type2.Optional(Type2.String()),
4415
4985
  message: Type2.Optional(Type2.String()),
4416
- replyTo: Type2.Optional(Type2.String())
4986
+ replyTo: Type2.Optional(Type2.String()),
4987
+ id: Type2.Optional(Type2.String({ description: "Async run id for action='ask'." })),
4988
+ index: Type2.Optional(Type2.Integer({ minimum: 0, description: "Zero-based child index for action='ask'. Select index or childId when more than one child is running." })),
4989
+ childId: Type2.Optional(Type2.String({ description: "Private child identity for action='ask'." })),
4990
+ reason: Type2.Optional(Type2.String({ description: "Concrete decision the child's answer will change. Required for action='ask'." }))
4991
+ }, { additionalProperties: !1 }), ParentSupervisorParamsSchema = Type2.Object({
4992
+ ...IntercomParamsSchema.properties,
4993
+ action: Type2.String({ enum: ["list", "ask", "reply", "pending", "status"] })
4417
4994
  }, { additionalProperties: !1 });
4418
- function safeSegment(value) {
4995
+ function safeSegment2(value) {
4419
4996
  return value.trim().replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "unknown";
4420
4997
  }
4421
4998
  function replyPath(channelDir, requestId) {
4422
- return path7.join(channelDir, REPLIES_DIR, `${safeSegment(requestId)}.json`);
4999
+ return path8.join(channelDir, REPLIES_DIR, `${safeSegment2(requestId)}.json`);
4423
5000
  }
4424
- function askTimeoutMs() {
5001
+ function askTimeoutMs2() {
4425
5002
  let parsed = Number(process.env.PI_INTERCOM_ASK_TIMEOUT_MS);
4426
- return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_ASK_TIMEOUT_MS;
5003
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_ASK_TIMEOUT_MS2;
4427
5004
  }
4428
5005
  function hasTool(pi, name) {
4429
5006
  try {
@@ -4434,16 +5011,26 @@ function hasTool(pi, name) {
4434
5011
  }
4435
5012
  function parseRequestFile(file, channelDir) {
4436
5013
  try {
4437
- let parsed = JSON.parse(fs6.readFileSync(file, "utf-8"));
5014
+ let parsed = JSON.parse(fs7.readFileSync(file, "utf-8"));
4438
5015
  return parsed.type !== "subagent.supervisor.request" || typeof parsed.id != "string" || !parsed.id || parsed.reason !== "need_decision" && parsed.reason !== "interview_request" && parsed.reason !== "progress_update" || typeof parsed.message != "string" || !parsed.message || typeof parsed.runId != "string" || typeof parsed.agent != "string" || typeof parsed.childIndex != "number" ? void 0 : { ...parsed, channelDir, requestFile: file };
4439
5016
  } catch {
4440
5017
  return;
4441
5018
  }
4442
5019
  }
5020
+ function hasPendingBlockingSupervisorRequest(runId, sessionId, now = Date.now()) {
5021
+ if (!runId)
5022
+ return !1;
5023
+ for (let { channelDir, file } of listRequestFiles()) {
5024
+ let request = parseRequestFile(file, channelDir);
5025
+ if (request?.expectsReply && request.runId === runId && !(sessionId && request.orchestratorSessionId && request.orchestratorSessionId !== sessionId) && !fs7.existsSync(replyPath(request.channelDir, request.id)) && !(now > requestExpiresAt(request, now)))
5026
+ return !0;
5027
+ }
5028
+ return !1;
5029
+ }
4443
5030
  function listRequestFiles() {
4444
5031
  let channelEntries;
4445
5032
  try {
4446
- channelEntries = fs6.readdirSync(SUPERVISOR_CHANNEL_ROOT, { withFileTypes: !0 });
5033
+ channelEntries = fs7.readdirSync(SUPERVISOR_CHANNEL_ROOT, { withFileTypes: !0 });
4447
5034
  } catch (error) {
4448
5035
  if (error.code === "ENOENT")
4449
5036
  return [];
@@ -4453,34 +5040,34 @@ function listRequestFiles() {
4453
5040
  for (let entry of channelEntries) {
4454
5041
  if (!entry.isDirectory())
4455
5042
  continue;
4456
- let channelDir = path7.join(SUPERVISOR_CHANNEL_ROOT, entry.name), requestsDir = path7.join(channelDir, REQUESTS_DIR), requestEntries;
5043
+ let channelDir = path8.join(SUPERVISOR_CHANNEL_ROOT, entry.name), requestsDir = path8.join(channelDir, REQUESTS_DIR), requestEntries;
4457
5044
  try {
4458
- requestEntries = fs6.readdirSync(requestsDir, { withFileTypes: !0 });
5045
+ requestEntries = fs7.readdirSync(requestsDir, { withFileTypes: !0 });
4459
5046
  } catch {
4460
5047
  continue;
4461
5048
  }
4462
5049
  for (let requestEntry of requestEntries)
4463
- requestEntry.isFile() && requestEntry.name.endsWith(".json") && files.push({ channelDir, file: path7.join(requestsDir, requestEntry.name) });
5050
+ requestEntry.isFile() && requestEntry.name.endsWith(".json") && files.push({ channelDir, file: path8.join(requestsDir, requestEntry.name) });
4464
5051
  }
4465
5052
  return files;
4466
5053
  }
4467
5054
  function readDirectoryEntries(dir) {
4468
5055
  try {
4469
- return fs6.readdirSync(dir, { withFileTypes: !0 });
5056
+ return fs7.readdirSync(dir, { withFileTypes: !0 });
4470
5057
  } catch (error) {
4471
5058
  return error.code === "ENOENT" ? [] : void 0;
4472
5059
  }
4473
5060
  }
4474
5061
  function directoryMtimeMs(dir) {
4475
5062
  try {
4476
- return fs6.statSync(dir).mtimeMs;
5063
+ return fs7.statSync(dir).mtimeMs;
4477
5064
  } catch {
4478
5065
  return 0;
4479
5066
  }
4480
5067
  }
4481
5068
  function removeEmptyDirectory(dir) {
4482
5069
  try {
4483
- return fs6.rmdirSync(dir), !0;
5070
+ return fs7.rmdirSync(dir), !0;
4484
5071
  } catch (error) {
4485
5072
  let code = error.code;
4486
5073
  if (code === "ENOENT")
@@ -4491,19 +5078,22 @@ function removeEmptyDirectory(dir) {
4491
5078
  }
4492
5079
  }
4493
5080
  function removeStaleEmptySupervisorChannel(channelDir, nowMs) {
4494
- let requestsDir = path7.join(channelDir, REQUESTS_DIR), repliesDir = path7.join(channelDir, REPLIES_DIR), newestKnownMtimeMs = Math.max(directoryMtimeMs(channelDir), directoryMtimeMs(requestsDir), directoryMtimeMs(repliesDir));
5081
+ let requestsDir = path8.join(channelDir, REQUESTS_DIR), repliesDir = path8.join(channelDir, REPLIES_DIR), questionsPath = path8.join(channelDir, QUESTIONS_DIR), newestKnownMtimeMs = Math.max(directoryMtimeMs(channelDir), directoryMtimeMs(requestsDir), directoryMtimeMs(repliesDir), directoryMtimeMs(questionsPath));
4495
5082
  if (nowMs - newestKnownMtimeMs < STALE_EMPTY_CHANNEL_AGE_MS)
4496
5083
  return !1;
4497
5084
  let requestEntries = readDirectoryEntries(requestsDir);
4498
5085
  if (!requestEntries || requestEntries.length > 0)
4499
5086
  return !1;
4500
5087
  let replyEntries = readDirectoryEntries(repliesDir);
4501
- return !(!replyEntries || replyEntries.length > 0 || !removeEmptyDirectory(requestsDir) || !removeEmptyDirectory(repliesDir) || !removeEmptyDirectory(channelDir));
5088
+ if (!replyEntries || replyEntries.length > 0)
5089
+ return !1;
5090
+ let questionEntries = readDirectoryEntries(questionsPath);
5091
+ return !(!questionEntries || questionEntries.length > 0 || !removeEmptyDirectory(requestsDir) || !removeEmptyDirectory(repliesDir) || !removeEmptyDirectory(questionsPath) || !removeEmptyDirectory(channelDir));
4502
5092
  }
4503
5093
  function cleanupStaleEmptySupervisorChannels(nowMs = Date.now()) {
4504
5094
  let channelEntries;
4505
5095
  try {
4506
- channelEntries = fs6.readdirSync(SUPERVISOR_CHANNEL_ROOT, { withFileTypes: !0 });
5096
+ channelEntries = fs7.readdirSync(SUPERVISOR_CHANNEL_ROOT, { withFileTypes: !0 });
4507
5097
  } catch (error) {
4508
5098
  if (error.code === "ENOENT")
4509
5099
  return 0;
@@ -4513,7 +5103,7 @@ function cleanupStaleEmptySupervisorChannels(nowMs = Date.now()) {
4513
5103
  for (let entry of channelEntries)
4514
5104
  if (entry.isDirectory())
4515
5105
  try {
4516
- removeStaleEmptySupervisorChannel(path7.join(SUPERVISOR_CHANNEL_ROOT, entry.name), nowMs) && removed++;
5106
+ removeStaleEmptySupervisorChannel(path8.join(SUPERVISOR_CHANNEL_ROOT, entry.name), nowMs) && removed++;
4517
5107
  } catch {
4518
5108
  }
4519
5109
  return removed;
@@ -4533,13 +5123,13 @@ function requestMatchesContext(request, state, ctx) {
4533
5123
  }
4534
5124
  function removeRequestFile(file) {
4535
5125
  try {
4536
- fs6.rmSync(file, { force: !0 });
5126
+ fs7.rmSync(file, { force: !0 });
4537
5127
  } catch {
4538
5128
  }
4539
5129
  }
4540
5130
  function requestExpiresAt(request, now) {
4541
5131
  let expiresAt = request.expiresAt;
4542
- return typeof expiresAt == "number" && Number.isFinite(expiresAt) ? expiresAt : Number.isFinite(request.createdAt) ? request.createdAt + askTimeoutMs() : now;
5132
+ return typeof expiresAt == "number" && Number.isFinite(expiresAt) ? expiresAt : Number.isFinite(request.createdAt) ? request.createdAt + askTimeoutMs2() : now;
4543
5133
  }
4544
5134
  function requestRunInactive(request, state) {
4545
5135
  if (state.foregroundControls.has(request.runId))
@@ -4556,7 +5146,7 @@ function requestRunInactive(request, state) {
4556
5146
  return stepStatus === "complete" || stepStatus === "completed" || stepStatus === "failed" || stepStatus === "paused";
4557
5147
  }
4558
5148
  function requestLifecycle(request, state, ctx, now) {
4559
- return ctx && !requestMatchesContext(request, state, ctx) ? "wrong-session" : fs6.existsSync(request.requestFile) ? request.expectsReply && fs6.existsSync(replyPath(request.channelDir, request.id)) ? "resolved" : request.expectsReply && now > requestExpiresAt(request, now) ? "expired" : request.expectsReply && requestRunInactive(request, state) ? "inactive" : "pending" : "missing";
5149
+ return ctx && !requestMatchesContext(request, state, ctx) ? "wrong-session" : fs7.existsSync(request.requestFile) ? request.expectsReply && fs7.existsSync(replyPath(request.channelDir, request.id)) ? "resolved" : request.expectsReply && now > requestExpiresAt(request, now) ? "expired" : request.expectsReply && requestRunInactive(request, state) ? "inactive" : "pending" : "missing";
4560
5150
  }
4561
5151
  function cleanupRequestLifecycle(request, lifecycle) {
4562
5152
  (lifecycle === "resolved" || lifecycle === "expired" || lifecycle === "inactive") && removeRequestFile(request.requestFile);
@@ -4621,8 +5211,8 @@ function buildParentIntercomTool(pending, state, name = "intercom") {
4621
5211
  return {
4622
5212
  name,
4623
5213
  label: name === "intercom" ? "Intercom" : "Subagent Supervisor",
4624
- description: name === "intercom" ? "Native pi-subagents supervisor channel. Use reply/pending/status to answer child subagent requests." : "Native pi-subagents supervisor channel. Use reply/pending/status to answer child subagent requests without overriding pi-intercom.",
4625
- parameters: IntercomParamsSchema,
5214
+ description: PARENT_ASK_DESCRIPTION,
5215
+ parameters: ParentSupervisorParamsSchema,
4626
5216
  async execute(_id, params) {
4627
5217
  refreshPendingRequests(pending, state, state.lastUiContext ?? void 0);
4628
5218
  let input = params;
@@ -4637,10 +5227,59 @@ function buildParentIntercomTool(pending, state, name = "intercom") {
4637
5227
  let request = resolvePendingRequest(pending, input);
4638
5228
  return writeReply(request, input.message ?? ""), pending.delete(request.id), { content: [{ type: "text", text: `Replied to supervisor request ${request.id}.` }], details: { replyTo: request.id, runId: request.runId, agent: request.agent } };
4639
5229
  }
4640
- throw input.action === "send" || input.action === "ask" ? new Error("Native pi-subagents intercom currently handles supervisor replies. Child agents initiate asks with contact_supervisor.") : new Error(`Unsupported intercom action: ${input.action}`);
5230
+ if (input.action === "ask")
5231
+ return askRunningAsyncChild({
5232
+ params: {
5233
+ id: input.id,
5234
+ index: input.index,
5235
+ childId: input.childId,
5236
+ reason: input.reason,
5237
+ message: input.message
5238
+ },
5239
+ state,
5240
+ childRequests: pending.values()
5241
+ });
5242
+ throw input.action === "send" ? new Error("Native pi-subagents intercom does not support free-form send. Use action='ask' for a running async child or action='reply' for a pending child request.") : new Error(`Unsupported intercom action: ${input.action}`);
4641
5243
  }
4642
5244
  };
4643
5245
  }
5246
+ function questionMatchesParentContext(question, state, ctx) {
5247
+ let sessionId = currentContextSessionId(state, ctx);
5248
+ return !(!sessionId || question.parentSessionId !== sessionId || (state.sessionGeneration ?? 0) !== question.parentGeneration);
5249
+ }
5250
+ function notifyQuestionTerminal(pi, question, channelDir) {
5251
+ if (!supervisorQuestionIsTerminal(question) || question.notifiedAt || (pi.events?.emit(SUBAGENT_QUESTION_EVENT, {
5252
+ questionId: question.id,
5253
+ state: supervisorQuestionPublicState(question),
5254
+ runId: question.runId
5255
+ }), isQuestionWaitActive(question.id)) || !claimQuestionNotification(channelDir, question.id))
5256
+ return;
5257
+ let state = supervisorQuestionPublicState(question);
5258
+ pi.sendMessage({
5259
+ customType: SUBAGENT_SUPERVISOR_ANSWER_TYPE,
5260
+ content: formatSupervisorQuestionAnswer(question),
5261
+ display: state === "answered",
5262
+ details: {
5263
+ questionId: question.id,
5264
+ state,
5265
+ runId: question.runId,
5266
+ childIndex: question.childIndex,
5267
+ childId: question.childId,
5268
+ reason: question.reason,
5269
+ answered: state === "answered",
5270
+ delivered: !!question.deliveredAt
5271
+ }
5272
+ }, { triggerTurn: state === "answered" });
5273
+ }
5274
+ function pollSupervisorQuestions(pi, state, ctx) {
5275
+ for (let channelDir of listSupervisorChannelDirs())
5276
+ for (let question of listSupervisorQuestions(channelDir)) {
5277
+ if (!questionMatchesParentContext(question, state, ctx))
5278
+ continue;
5279
+ let current = reconcileAsyncQuestion(channelDir, question, state);
5280
+ notifyQuestionTerminal(pi, current, channelDir);
5281
+ }
5282
+ }
4644
5283
  function createNativeSupervisorChannel(pi, state) {
4645
5284
  let pending = /* @__PURE__ */ new Map(), seenFiles = /* @__PURE__ */ new Set(), poller, lastStaleCleanupAt = 0, registerParentTools = () => {
4646
5285
  hasTool(pi, NATIVE_SUPERVISOR_TOOL_NAME) || pi.registerTool(buildParentIntercomTool(pending, state, NATIVE_SUPERVISOR_TOOL_NAME)), hasTool(pi, "intercom") || pi.registerTool(buildParentIntercomTool(pending, state));
@@ -4653,12 +5292,24 @@ function createNativeSupervisorChannel(pi, state) {
4653
5292
  } catch {
4654
5293
  }
4655
5294
  }
5295
+ }, cancelOwnedQuestions = (reason) => {
5296
+ state.currentSessionId && cancelPendingSupervisorQuestionsForOwner({
5297
+ parentSessionId: state.currentSessionId,
5298
+ parentGeneration: state.sessionGeneration ?? 0,
5299
+ reason
5300
+ });
5301
+ }, settleRunQuestions = (runId, reason) => {
5302
+ state.currentSessionId && cancelPendingSupervisorQuestionsForOwner({
5303
+ parentSessionId: state.currentSessionId,
5304
+ runId,
5305
+ reason
5306
+ });
4656
5307
  }, poll = () => {
4657
5308
  cleanupStaleChannelsIfDue();
4658
5309
  let ctx = state.lastUiContext;
4659
5310
  if (!ctx)
4660
5311
  return;
4661
- refreshPendingRequests(pending, state, ctx);
5312
+ refreshPendingRequests(pending, state, ctx), pollSupervisorQuestions(pi, state, ctx);
4662
5313
  let now = Date.now();
4663
5314
  for (let { channelDir, file } of listRequestFiles()) {
4664
5315
  if (seenFiles.has(file))
@@ -4671,7 +5322,7 @@ function createNativeSupervisorChannel(pi, state) {
4671
5322
  seenFiles.add(file), cleanupRequestLifecycle(request, lifecycle);
4672
5323
  continue;
4673
5324
  }
4674
- seenFiles.add(file), request.expectsReply ? pending.set(request.id, request) : removeRequestFile(request.requestFile), pi.sendMessage({
5325
+ if (seenFiles.add(file), request.expectsReply ? pending.set(request.id, request) : removeRequestFile(request.requestFile), pi.sendMessage({
4675
5326
  customType: "subagent_supervisor_request",
4676
5327
  content: requestVisibleText(request),
4677
5328
  display: !0,
@@ -4683,12 +5334,23 @@ function createNativeSupervisorChannel(pi, state) {
4683
5334
  agent: request.agent,
4684
5335
  childIndex: request.childIndex
4685
5336
  }
4686
- }), request.expectsReply && pi.events?.emit(INTERCOM_DETACH_REQUEST_EVENT, {
4687
- requestId: request.id,
4688
- runId: request.runId,
4689
- agent: request.agent,
4690
- childIndex: request.childIndex
4691
- });
5337
+ }), request.expectsReply) {
5338
+ let job = state.asyncJobs.get(request.runId);
5339
+ job && (job.activityState = "needs_attention");
5340
+ let events = pi.events;
5341
+ events?.emit(INTERCOM_DETACH_REQUEST_EVENT, {
5342
+ requestId: request.id,
5343
+ runId: request.runId,
5344
+ agent: request.agent,
5345
+ childIndex: request.childIndex
5346
+ }), events?.emit(SUBAGENT_CONTROL_EVENT, {
5347
+ type: "needs_attention",
5348
+ to: "needs_attention",
5349
+ runId: request.runId,
5350
+ agent: request.agent,
5351
+ index: request.childIndex
5352
+ });
5353
+ }
4692
5354
  }
4693
5355
  };
4694
5356
  return {
@@ -4696,14 +5358,16 @@ function createNativeSupervisorChannel(pi, state) {
4696
5358
  poller || (registerParentTools(), poll(), poller = setInterval(poll, CHANNEL_POLL_MS), poller.unref?.());
4697
5359
  },
4698
5360
  dispose: () => {
4699
- poller && clearInterval(poller), poller = void 0, pending.clear(), seenFiles.clear();
5361
+ cancelOwnedQuestions("parent supervisor channel disposed"), poller && clearInterval(poller), poller = void 0, pending.clear(), seenFiles.clear();
4700
5362
  },
4701
- pending
5363
+ pending,
5364
+ cancelOwnedQuestions,
5365
+ settleRunQuestions
4702
5366
  };
4703
5367
  }
4704
5368
 
4705
5369
  // packages/coding-agent/dist/builtin-extensions/pi-subagents/src/extension/rpc.js
4706
- import * as path8 from "node:path";
5370
+ import * as path9 from "node:path";
4707
5371
  import { Compile } from "typebox/compile";
4708
5372
  var SUBAGENT_RPC_PROTOCOL_VERSION = 1, SUBAGENT_RPC_REQUEST_EVENT = "subagents:rpc:v1:request", SUBAGENT_RPC_READY_EVENT = "subagents:rpc:v1:ready", SUBAGENT_RPC_REPLY_EVENT_PREFIX = "subagents:rpc:v1:reply:", SUBAGENT_RPC_METHODS = ["ping", "status", "spawn", "interrupt", "stop"], SubagentRpcError = class extends Error {
4709
5373
  code;
@@ -4810,7 +5474,7 @@ function stopAsyncRun(params, options, ctx) {
4810
5474
  }
4811
5475
  if (!location.asyncDir)
4812
5476
  throw new SubagentRpcError("not_found", "Async run not found or already completed; stop requires a live async run directory.");
4813
- let currentSessionId = ctx.sessionManager.getSessionId(), initialStatus = readStatus(location.asyncDir), initialRunId = initialStatus?.runId ?? location.resolvedId ?? path8.basename(location.asyncDir);
5477
+ let currentSessionId = ctx.sessionManager.getSessionId(), initialStatus = readStatus(location.asyncDir), initialRunId = initialStatus?.runId ?? location.resolvedId ?? path9.basename(location.asyncDir);
4814
5478
  if (!initialStatus)
4815
5479
  throw new SubagentRpcError("not_found", `Status file not found for async run '${initialRunId}'.`);
4816
5480
  if (!currentSessionId || initialStatus.sessionId !== currentSessionId)
@@ -5042,7 +5706,9 @@ var ACTIVE_STATES = ["queued", "running"], DEFAULT_TIMEOUT_MS = 1800 * 1e3, MIN_
5042
5706
  SUBAGENT_FOREGROUND_COMPLETE_EVENT,
5043
5707
  SUBAGENT_CONTROL_EVENT,
5044
5708
  SUBAGENT_CONTROL_INTERCOM_EVENT,
5045
- SUBAGENT_RESULT_INTERCOM_EVENT
5709
+ SUBAGENT_RESULT_INTERCOM_EVENT,
5710
+ INTERCOM_DETACH_REQUEST_EVENT,
5711
+ SUBAGENT_QUESTION_EVENT
5046
5712
  ];
5047
5713
  function defaultSleep(ms, signal) {
5048
5714
  return new Promise((resolve3) => {
@@ -5111,8 +5777,8 @@ function summarizeForegroundChildren(run, indices) {
5111
5777
  !indices.has(child.index) || child.status === "detached" || counts.set(child.status, (counts.get(child.status) ?? 0) + 1);
5112
5778
  return [...counts.entries()].map(([status, count]) => `${count} ${status}`).join(", ");
5113
5779
  }
5114
- function needsAttention(run) {
5115
- return run.activityState === "needs_attention";
5780
+ function needsAttention(run, sessionId) {
5781
+ return run.activityState === "needs_attention" || hasPendingBlockingSupervisorRequest(run.id, sessionId);
5116
5782
  }
5117
5783
  function backgroundWorkIdentity(item) {
5118
5784
  return `${item.provider}\0${item.sessionId}\0${item.id}`;
@@ -5134,7 +5800,7 @@ function activeRunsForSession(params, deps) {
5134
5800
  return params.id ? runs.filter((run) => matchesId(run, params.id)) : runs;
5135
5801
  }
5136
5802
  function attentionRunsForSession(params, deps, initialIds) {
5137
- return activeRunsForSession(params, deps).filter((run) => needsAttention(run) && initialIds.has(run.id));
5803
+ return activeRunsForSession(params, deps).filter((run) => needsAttention(run, deps.state.currentSessionId) && initialIds.has(run.id));
5138
5804
  }
5139
5805
  function allRunsForSession(params, deps) {
5140
5806
  let asyncDirRoot = deps.asyncDirRoot ?? ASYNC_DIR, resultsDir = deps.resultsDir ?? RESULTS_DIR, runs = listAsyncRuns(asyncDirRoot, {
@@ -5180,12 +5846,57 @@ async function waitForDetachedForegroundRun(run, signal, deps, startedAt, now, p
5180
5846
  await waitForWake(pollIntervalMs, signal, deps);
5181
5847
  }
5182
5848
  }
5849
+ async function waitForSupervisorQuestion(questionId, signal, deps, startedAt, now, pollIntervalMs, timeoutMs) {
5850
+ let sessionId = deps.state.currentSessionId;
5851
+ if (!sessionId)
5852
+ return result("subagent_wait requires an active session identity to scope background work safely.", !0);
5853
+ let releaseWait = registerQuestionWait([questionId]);
5854
+ try {
5855
+ for (; ; ) {
5856
+ let found = findSupervisorQuestion(questionId);
5857
+ if (!found)
5858
+ return result(`No supervisor question matched "${questionId}".`, !0);
5859
+ let { channelDir } = found, { question } = found;
5860
+ if (question.parentSessionId !== sessionId || deps.state.currentSessionId !== sessionId)
5861
+ return result(`Supervisor question "${questionId}" belongs to another session.`, !0);
5862
+ if ((deps.state.sessionGeneration ?? 0) !== question.parentGeneration)
5863
+ return result(`Supervisor question "${questionId}" was cancelled by a session replacement.`, !0);
5864
+ if (question = reconcileAsyncQuestion(channelDir, question, deps.state), supervisorQuestionIsTerminal(question)) {
5865
+ claimQuestionNotification(channelDir, question.id);
5866
+ let state = supervisorQuestionPublicState(question);
5867
+ return {
5868
+ content: [{ type: "text", text: formatSupervisorQuestionAnswer(question) }],
5869
+ ...state === "answered" ? {} : { isError: !0 },
5870
+ details: {
5871
+ mode: "management",
5872
+ results: [],
5873
+ questionId: question.id,
5874
+ state,
5875
+ runId: question.runId,
5876
+ answered: state === "answered",
5877
+ delivered: !!question.deliveredAt
5878
+ }
5879
+ };
5880
+ }
5881
+ if (signal?.aborted)
5882
+ return result(`Wait aborted after ${formatDuration(now() - startedAt)} for supervisor question "${questionId}".`, !0);
5883
+ if (now() - startedAt >= timeoutMs)
5884
+ return result(`Wait timed out after ${formatDuration(timeoutMs)} for supervisor question "${questionId}". The question remains pending until answered, expired, or cancelled.`, !0);
5885
+ await waitForWake(pollIntervalMs, signal, deps);
5886
+ }
5887
+ } finally {
5888
+ releaseWait();
5889
+ }
5890
+ }
5183
5891
  async function waitForSubagents(params, signal, deps) {
5184
5892
  if (deps.enabled === !1)
5185
5893
  return result('subagent_wait is disabled by config.waitTool or PI_SUBAGENT_WAIT_TOOL_ENABLED; returning immediately without blocking background work. Active work keeps going, and you can inspect subagents with subagent({ action: "status" }) or rely on completion notifications.');
5186
5894
  if (!deps.state.currentSessionId)
5187
5895
  return result("subagent_wait requires an active session identity to scope background work safely.", !0);
5188
- let now = deps.now ?? Date.now, pollIntervalMs = Math.max(MIN_POLL_INTERVAL_MS, deps.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS), timeoutMs = params.timeoutMs !== void 0 && params.timeoutMs > 0 ? params.timeoutMs : DEFAULT_TIMEOUT_MS, startedAt = now(), waitForAll = params.id ? !0 : params.all === !0, active, foreground, providerSnapshot;
5896
+ let now = deps.now ?? Date.now, pollIntervalMs = Math.max(MIN_POLL_INTERVAL_MS, deps.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS), timeoutMs = params.timeoutMs !== void 0 && params.timeoutMs > 0 ? params.timeoutMs : DEFAULT_TIMEOUT_MS, startedAt = now();
5897
+ if (params.questionId?.trim())
5898
+ return waitForSupervisorQuestion(params.questionId.trim(), signal, deps, startedAt, now, pollIntervalMs, timeoutMs);
5899
+ let waitForAll = params.id ? !0 : params.all === !0, active, foreground, providerSnapshot;
5189
5900
  try {
5190
5901
  active = activeRunsForSession(params, deps), foreground = activeDetachedForegroundRuns(params, deps), providerSnapshot = params.id ? { providers: [], items: [] } : backgroundWorkForSession(deps, startedAt);
5191
5902
  } catch (error) {
@@ -5206,7 +5917,7 @@ async function waitForSubagents(params, signal, deps) {
5206
5917
  let providerActive = providerSnapshot.items;
5207
5918
  if (active.length === 0 && providerActive.length === 0)
5208
5919
  return result(params.id ? `No active run matched "${params.id}". Nothing to wait for.` : "No active async runs or registered provider work in this session. Nothing to wait for.");
5209
- let waitParams = params.id ? { ...params, id: active[0].id } : params, initialAsyncIds = new Set(active.map((run) => run.id)), initialProviderIds = new Set(providerActive.map(backgroundWorkIdentity)), initialProviderNames = new Set(providerActive.map((item) => item.provider)), initialCount = initialAsyncIds.size + initialProviderIds.size, stopOnAttention = deps.stopOnAttention !== !1, attention = active.filter((run) => needsAttention(run)), isDone = () => {
5920
+ let waitParams = params.id ? { ...params, id: active[0].id } : params, initialAsyncIds = new Set(active.map((run) => run.id)), initialProviderIds = new Set(providerActive.map(backgroundWorkIdentity)), initialProviderNames = new Set(providerActive.map((item) => item.provider)), initialCount = initialAsyncIds.size + initialProviderIds.size, stopOnAttention = deps.stopOnAttention !== !1, attention = active.filter((run) => needsAttention(run, deps.state.currentSessionId)), isDone = () => {
5210
5921
  if (stopOnAttention && attention.some((run) => initialAsyncIds.has(run.id)))
5211
5922
  return !0;
5212
5923
  let activeAsyncIds = new Set(active.map((run) => run.id)), activeProviderIds2 = new Set(providerActive.map(backgroundWorkIdentity));
@@ -5259,6 +5970,7 @@ In an interactive chat, do not call this merely to wait: return control to the u
5259
5970
  \u2022 { } \u2014 return when the first initially active async run or registered provider item finishes, or when a subagent needs attention.
5260
5971
  \u2022 { all: true } \u2014 wait for every async run and provider item that was active when the call began.
5261
5972
  \u2022 { id: "..." } \u2014 wait for one async or remembered detached foreground subagent run (id or prefix).
5973
+ \u2022 { questionId: "..." } \u2014 wait for one supervisor question from subagent_supervisor action='ask' (answered/expired/cancelled). Separate from final run results.
5262
5974
  \u2022 { timeoutMs: 600000 } \u2014 stop waiting after N ms; active work keeps running.
5263
5975
 
5264
5976
  Provider jobs are session-scoped and identified exactly, so replacing one job with another cannot hide a completion. Provider extensions must be explicitly loaded in this process. In a child agent, keep \`subagent_wait\` in the child tool allowlist and load each provider through the agent's extensions or subagentOnlyExtensions; this tool never loads providers or grants tools itself.${enabled ? "" : `
@@ -5393,7 +6105,7 @@ function formatSingleCompletion(details) {
5393
6105
  }
5394
6106
  function parseSubagentNotifyContent(content) {
5395
6107
  let lines = content.split(`
5396
- `), match = (lines[0] ?? "").match(/^(Background task|Detached foreground task) (completed|failed|paused): \*\*(.+?)\*\*(?:\s+(\([^)]*\)))?$/);
6108
+ `), match = (lines[0] ?? "").match(/^(Background task|Detached foreground task) (completed|failed|paused|stopped): \*\*(.+?)\*\*(?:\s+(\([^)]*\)))?$/);
5397
6109
  if (!match)
5398
6110
  return;
5399
6111
  let body = lines.slice(2), sessionIndex = -1;
@@ -5437,7 +6149,7 @@ function sendCompletion(pi, details) {
5437
6149
  customType: "subagent-notify",
5438
6150
  content,
5439
6151
  display: !0
5440
- }, { triggerTurn: !0 });
6152
+ }, { triggerTurn: details.some((detail) => detail.status !== "stopped") });
5441
6153
  }
5442
6154
  function completionBatchKey(result2) {
5443
6155
  let sessionId = typeof result2.sessionId == "string" ? result2.sessionId.trim() : "";
@@ -5447,7 +6159,7 @@ function completionBatchKey(result2) {
5447
6159
  return cwd ? `cwd:${cwd}` : "unknown";
5448
6160
  }
5449
6161
  function buildCompletionDetails(result2) {
5450
- let agent = result2.agent ?? "unknown", summary = typeof result2.summary == "string" ? result2.summary : "", status = !result2.success && (result2.exitCode === 0 || result2.state === "paused" || summary.startsWith("Paused after interrupt.")) ? "paused" : result2.success ? "completed" : "failed", taskInfo = result2.taskIndex !== void 0 && result2.totalTasks !== void 0 ? ` (${result2.taskIndex + 1}/${result2.totalTasks})` : void 0, session = result2.shareUrl ? { label: "Session", value: result2.shareUrl } : result2.shareError ? { label: "Session share error", value: result2.shareError } : result2.sessionFile ? { label: "Session file", value: result2.sessionFile } : void 0;
6162
+ let agent = result2.agent ?? "unknown", summary = typeof result2.summary == "string" ? result2.summary : "", paused = !result2.success && (result2.exitCode === 0 || result2.state === "paused" || summary.startsWith("Paused after interrupt.")), status = result2.state === "stopped" ? "stopped" : paused ? "paused" : result2.success ? "completed" : "failed", taskInfo = result2.taskIndex !== void 0 && result2.totalTasks !== void 0 ? ` (${result2.taskIndex + 1}/${result2.totalTasks})` : void 0, session = result2.shareUrl ? { label: "Session", value: result2.shareUrl } : result2.shareError ? { label: "Session share error", value: result2.shareError } : result2.sessionFile ? { label: "Session file", value: result2.sessionFile } : void 0;
5451
6163
  return {
5452
6164
  agent,
5453
6165
  status,
@@ -5536,15 +6248,15 @@ function handleSubagentSteeringNotice(input) {
5536
6248
  }
5537
6249
 
5538
6250
  // packages/coding-agent/dist/builtin-extensions/pi-subagents/src/extension/config.js
5539
- import * as fs7 from "node:fs";
5540
- import * as path9 from "node:path";
6251
+ import * as fs8 from "node:fs";
6252
+ import * as path10 from "node:path";
5541
6253
  function getConfigPath() {
5542
- return path9.join(getAgentDir(), "extensions", "subagent", "config.json");
6254
+ return path10.join(getAgentDir(), "extensions", "subagent", "config.json");
5543
6255
  }
5544
6256
  function readConfigForUpdate(configPath = getConfigPath()) {
5545
- if (!fs7.existsSync(configPath))
6257
+ if (!fs8.existsSync(configPath))
5546
6258
  return {};
5547
- let parsed = JSON.parse(fs7.readFileSync(configPath, "utf-8"));
6259
+ let parsed = JSON.parse(fs8.readFileSync(configPath, "utf-8"));
5548
6260
  if (!parsed || typeof parsed != "object" || Array.isArray(parsed))
5549
6261
  throw new Error(`Subagent config at '${configPath}' must be a JSON object`);
5550
6262
  return parsed;
@@ -5560,25 +6272,31 @@ function loadConfig() {
5560
6272
  }
5561
6273
 
5562
6274
  // packages/coding-agent/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js
5563
- import * as fs8 from "node:fs";
5564
- import * as path10 from "node:path";
5565
- var CUSTOM_TOOL_DESCRIPTION_FILE = "subagent-tool-description.md", CUSTOM_TOOL_DESCRIPTION_MAX_BYTES = 50 * 1024, SUBAGENT_SAFETY_GUIDANCE = `SAFETY-CRITICAL SUBAGENT GUIDANCE:
6275
+ import * as fs9 from "node:fs";
6276
+ import * as path11 from "node:path";
6277
+ var CUSTOM_TOOL_DESCRIPTION_FILE = "subagent-tool-description.md", CUSTOM_TOOL_DESCRIPTION_MAX_BYTES = 50 * 1024, SUBAGENT_TIER_GUIDANCE = `TIER SELECTION:
6278
+ \u2022 Choose the lowest tier that can reliably complete the task.
6279
+ \u2022 light: Use for simple, straightforward tasks that require little reasoning. Good for quick checks, finding information, collecting facts for research, inspecting a small amount of code, or verifying a specific fact.
6280
+ \u2022 standard: Use for moderately difficult work and everyday coding tasks. Good for writing simple code, fixing a clearly described bug, reviewing a focused change, or completing work with clear requirements. Use heavy when the task requires deep architectural understanding or complex reasoning.
6281
+ \u2022 heavy: Use for complex or ambiguous tasks that require deep reasoning and strong code understanding. Good for implementing complex plans, debugging difficult or poorly understood problems, reviewing large or high-risk changes, and making architectural decisions.`, SUBAGENT_SAFETY_GUIDANCE = `SAFETY-CRITICAL SUBAGENT GUIDANCE:
5566
6282
  \u2022 Prompt children directly. There is no agent roster. Every execution task needs task (the full child prompt) and description (a concise UI label).
5567
- \u2022 Every executable child also needs tier: "light", "standard", or "heavy". Direct model overrides and parent-model inheritance are not available. A missing, disabled, unconfigured, unavailable, or unauthenticated tier fails before launch.
6283
+ \u2022 Every executable child chooses exactly one of tier: "light"|"standard"|"heavy" (default) or model: "provider/id" when the user names a model. Do not pass both, and do not use inherit. Missing, disabled, unconfigured, unavailable, or unauthenticated selections fail before launch.
6284
+ \u2022 Optional thinking (off/minimal/low/medium/high/xhigh/max) is only valid with an explicit model. Tier launches use the configured tier thinking level.
6285
+ ${SUBAGENT_TIER_GUIDANCE}
5568
6286
  \u2022 permissions is "full" or "read-only". Omitted permissions means full. Plan-mode parents must pass permissions: "read-only"; full or omitted launches are rejected.
5569
- \u2022 Keep execution and control separate: omit action for SINGLE/PARALLEL/CHAIN execution; use action only for status/interrupt/stop/resume/steer/append-step/doctor/watchdog.*/schedule*.
6287
+ \u2022 Keep execution and control separate: omit action for SINGLE/PARALLEL/CHAIN execution; use action only for status/interrupt/stop/resume/steer/append-step/doctor/watchdog.status|check|recommend-model/schedule*.
5570
6288
  \u2022 Async/background runs: launch with async:true only when work can proceed independently. Do not sleep or poll status just to wait. In an interactive session, normally return control and let lunR wake you; use subagent_wait when this request must run to completion in the current turn or skill. Headless sessions auto-drain current-session work.
5571
6289
  \u2022 Child-safety boundary: ordinary children are not orchestrators and must not run subagents. Only explicitly configured fanout children may use the child-safe subagent tool, still bounded by depth/session limits.
5572
6290
  \u2022 Writing/review safety: keep one full-access writer for the same cwd/worktree. Use fresh-context permissions: "read-only" children for independent review, then have the parent synthesize and apply fixes as the sole writer unless an isolated worktree was intentionally requested.
5573
6291
  \u2022 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.`, FULL_SUBAGENT_TOOL_DESCRIPTION = `Delegate work to generic children by prompting them directly. There are no named agent types.
5574
6292
 
5575
6293
  EXECUTION (use exactly ONE mode):
5576
- \u2022 SINGLE: { task, description, tier, permissions? } \u2014 one child. Multiple SINGLE calls in the same turn run concurrently; explicit concurrency/run limits still apply.
5577
- \u2022 PARALLEL: { tasks: [{task, description, tier, permissions?, count?, output?, reads?, progress?}, ...], concurrency?: number, worktree?: true } \u2014 one-call concurrent execution (default: all tasks at once; worktree: isolate each task in a git worktree)
5578
- \u2022 CHAIN: { chain: [{task, description, tier, permissions?}, {parallel:[{task, description, tier, permissions?, count:3}]}] } \u2014 sequential pipeline with optional parallel fan-out. Use chain when a later child needs an earlier result.
6294
+ \u2022 SINGLE: { task, description, tier|model, thinking?, permissions? } \u2014 one child. Multiple SINGLE calls in the same turn run concurrently; explicit concurrency/run limits still apply.
6295
+ \u2022 PARALLEL: { tasks: [{task, description, tier|model, thinking?, permissions?, count?, output?, reads?, progress?}, ...], concurrency?: number, worktree?: true } \u2014 one-call concurrent execution (default: all tasks at once; worktree: isolate each task in a git worktree)
6296
+ \u2022 CHAIN: { chain: [{task, description, tier|model, thinking?, permissions?}, {parallel:[{task, description, tier|model, thinking?, permissions?, count:3}]}] } \u2014 sequential pipeline with optional parallel fan-out. Use chain when a later child needs an earlier result.
5579
6297
  \u2022 description is required, single-line, max 80 characters. It is UI metadata only. task is the complete child prompt.
5580
6298
  \u2022 permissions: "full" (default) or "read-only". Full includes coding tools (read, search, shell, edit, write, web, LSP, MCP) and excludes parent-owned tools (cron, memory, behavior, goals, nested subagents). Plan-mode parents may launch only permissions: "read-only".
5581
- \u2022 Every executable child requires tier: "light", "standard", or "heavy". Direct model overrides and parent-model inheritance are rejected. The configured tier model and credentials must be available.
6299
+ \u2022 Model selection: pass tier by default ("light"|"standard"|"heavy"). Pass model: "provider/id" only when the user specifies a model. Exactly one of tier or model. Optional thinking only with model; tier uses configured thinking. Direct model launches do not require tier mode to be enabled. No inherit and no silent fallback for an explicit model.
5582
6300
  \u2022 Children always start with a fresh session (no inherited parent transcript).
5583
6301
  \u2022 Optional timeout: { timeoutMs } or { maxRuntimeMs } sets a run-level max runtime for foreground and async/background runs
5584
6302
 
@@ -5593,8 +6311,7 @@ CHAIN EXAMPLES:
5593
6311
  \u2022 Mixed: { chain: [{task:"Research {task}", description:"Research request", tier:"standard", permissions:"read-only"}, {parallel: [{task:"Review {previous}", description:"Review findings", tier:"light", permissions:"read-only", count: 2}]}, {task:"Summarize {previous}", description:"Summarize reviews", tier:"standard"}] }
5594
6312
 
5595
6313
  CONTROL (use action field, omit task/chain/tasks):
5596
- \u2022 { action: "watchdog.status" | "watchdog.check" | "watchdog.recommend-model" } - inspect the opt-in subagent watchdog and its strong complementary model recommendation
5597
- \u2022 { action: "watchdog.configure", model: "recommended" | "inherit" | "provider/model[:thinking]", scope?: "session" | "user" | "project", target?: "main" | "children", thinking?: "inherit" | "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max" } - configure watchdog model selection; default scope is session, use persistent scopes only when the user asks
6314
+ \u2022 { action: "watchdog.status" | "watchdog.check" | "watchdog.recommend-model" } - inspect the opt-in subagent watchdog and its strong complementary model recommendation (read-only; there is no configure action)
5598
6315
  \u2022 { action: "status", id: "..." } - inspect an async/background run by id or prefix
5599
6316
  \u2022 { action: "status", view: "fleet" } - read-only active foreground/async fleet view with transcript commands
5600
6317
  \u2022 { action: "status", id: "...", view: "transcript", index?: 0, lines?: 80 } - tail a run or child output/session transcript
@@ -5602,10 +6319,12 @@ CONTROL (use action field, omit task/chain/tasks):
5602
6319
  \u2022 { action: "stop", id: "..." } - stop a current-session top-level async run; stopped runs finish with state "stopped"
5603
6320
  \u2022 { action: "resume", id: "...", message: "...", index?: 0 } - interrupt then follow up with a live async child, or revive a completed async/foreground child from its session
5604
6321
  \u2022 { action: "steer", id: "...", message: "...", index?: 0 } - await correlated child input acceptance for up to 3 seconds; returns delivered, scheduled, pending, partial, recovered, or failed with a request id. Only top-level single runs may recover after a further 15-second pause/revival bound; chain, parallel, and nested runs never auto-interrupt.
6322
+ \u2022 subagent_supervisor({ action: "ask", id, reason, message, index?, childId? }) - ask one running async child; returns question id immediately. Ask only when the child owns missing context, the answer changes a concrete next decision, and waiting risks block/rework. No broadcast, revive, task replacement, status polling, or micromanagement.
6323
+ \u2022 subagent_wait({ questionId }) - wait for that supervisor question answer/expiry/cancel. Answers stay separate from final run results.
5605
6324
  \u2022 { action: "append-step", id: "...", chain: [{task:"Use {previous}", description:"Follow-up step", tier:"standard"}] } - append one step to the tail of a running async chain
5606
6325
 
5607
6326
  SCHEDULE (opt-in; requires { "scheduledRuns": { "enabled": true } } in config.json):
5608
- \u2022 { action: "schedule", task, description, tier, permissions?, schedule: "+10m" | "2030-01-01T09:00:00Z", scheduleName? } - defer a child launch until a future time. Also accepts tasks[] or chain[]. Scheduled runs always launch async with fresh context; they become normal tracked async runs once they fire. Only schedule explicit delayed runs the user asked for.
6327
+ \u2022 { action: "schedule", task, description, tier|model, thinking?, permissions?, schedule: "+10m" | "2030-01-01T09:00:00Z", scheduleName? } - defer a child launch until a future time. Also accepts tasks[] or chain[]. Scheduled runs always launch async with fresh context; they become normal tracked async runs once they fire. Only schedule explicit delayed runs the user asked for.
5609
6328
  \u2022 { action: "schedule-list" } - list scheduled runs for this session
5610
6329
  \u2022 { action: "schedule-status", id: "..." } - inspect one scheduled run
5611
6330
  \u2022 { action: "schedule-cancel", id: "..." } - cancel a scheduled run before it fires
@@ -5616,14 +6335,15 @@ DIAGNOSTICS:
5616
6335
  ${SUBAGENT_SAFETY_GUIDANCE}`, COMPACT_SUBAGENT_TOOL_DESCRIPTION = `Delegate to generic children by prompting them. Use exactly one mode per call.
5617
6336
 
5618
6337
  EXECUTE:
5619
- \u2022 SINGLE {task, description, tier, permissions?} (same-turn singles overlap); PARALLEL {tasks:[{task,description,tier,permissions?,count?,output?,reads?,progress?}], concurrency?, worktree?}; CHAIN {chain:[{task,description,tier,permissions?},{parallel:[...]}]} for sequential work.
6338
+ \u2022 SINGLE {task, description, tier|model, thinking?, permissions?} (same-turn singles overlap); PARALLEL {tasks:[{task,description,tier|model,thinking?,permissions?,count?,output?,reads?,progress?}], concurrency?, worktree?}; CHAIN {chain:[{task,description,tier|model,thinking?,permissions?},{parallel:[...]}]} for sequential work.
5620
6339
  \u2022 description is required (single-line, max 80 chars, UI only). task is the full child prompt. permissions omitted = full. Plan-mode parents must pass permissions:"read-only".
5621
- \u2022 Every executable child requires tier:"light"|"standard"|"heavy". Direct model overrides and parent-model inheritance are rejected; tier resolution fails closed. Children always start with a fresh session. timeoutMs/maxRuntimeMs apply to foreground and async/background runs.
6340
+ \u2022 Exactly one of tier:"light"|"standard"|"heavy" (default) or model:"provider/id" when the user names a model. thinking only with model. No inherit; fail closed on unavailable selections. Children always start fresh. timeoutMs/maxRuntimeMs apply to foreground and async/background runs.
6341
+ ${SUBAGENT_TIER_GUIDANCE}
5622
6342
  \u2022 Chain templates may use {task}, {previous}, {chain_dir}, and named outputs. Parallel worktree isolation requires a clean git repo.
5623
6343
  \u2022 Chain example: { chain: [{task:"Analyze {task}", description:"Analyze request", tier:"standard"}, {parallel: [{task:"Check {previous}", description:"Check prior result", tier:"light", permissions:"read-only", count: 3}]}] }
5624
6344
 
5625
6345
  CONTROL:
5626
- \u2022 Use action without execution fields: doctor, watchdog.status, watchdog.check, watchdog.recommend-model, watchdog.configure, status, interrupt, stop, resume, steer, append-step.
6346
+ \u2022 Use action without execution fields: doctor, watchdog.status, watchdog.check, watchdog.recommend-model, status, interrupt, stop, resume, steer, append-step.
5627
6347
  \u2022 Async control actions: status, interrupt, stop, resume, steer, append-step. Use stop with an id for current-session top-level async runs. Use status view:"fleet" for active-run overview, view:"transcript" to tail child output, and steer for acknowledged live guidance. Steering delivery means lunR accepted the correlated user input, not model compliance; use index for a specific child.
5628
6348
  \u2022 Opt-in schedule actions: schedule, schedule-list, schedule-status, schedule-cancel. Schedule only explicit delayed runs the user asked for.
5629
6349
 
@@ -5647,8 +6367,8 @@ function resolveToolDescriptionMode(config, options) {
5647
6367
  function customDescriptionPaths(options) {
5648
6368
  let cwd = options?.cwd ?? process.cwd(), agentDir = options?.agentDir ?? getAgentDir();
5649
6369
  return [
5650
- path10.join(getProjectConfigDir(cwd), CUSTOM_TOOL_DESCRIPTION_FILE),
5651
- path10.join(agentDir, CUSTOM_TOOL_DESCRIPTION_FILE)
6370
+ path11.join(getProjectConfigDir(cwd), CUSTOM_TOOL_DESCRIPTION_FILE),
6371
+ path11.join(agentDir, CUSTOM_TOOL_DESCRIPTION_FILE)
5652
6372
  ];
5653
6373
  }
5654
6374
  function renderCustomTemplate(template, options) {
@@ -5671,7 +6391,7 @@ function loadCustomToolDescription(options) {
5671
6391
  for (let filePath of customDescriptionPaths(options)) {
5672
6392
  let stat;
5673
6393
  try {
5674
- stat = fs8.statSync(filePath);
6394
+ stat = fs9.statSync(filePath);
5675
6395
  } catch (error) {
5676
6396
  if (typeof error == "object" && error !== null && "code" in error && error.code === "ENOENT")
5677
6397
  continue;
@@ -5687,7 +6407,7 @@ function loadCustomToolDescription(options) {
5687
6407
  continue;
5688
6408
  }
5689
6409
  try {
5690
- let template = fs8.readFileSync(filePath, "utf-8").trim();
6410
+ let template = fs9.readFileSync(filePath, "utf-8").trim();
5691
6411
  if (!template) {
5692
6412
  warn(options, `Ignoring empty custom tool description '${filePath}'.`);
5693
6413
  continue;
@@ -5726,24 +6446,24 @@ function buildSubagentToolDescription(config = {}, options) {
5726
6446
  // packages/coding-agent/dist/builtin-extensions/pi-subagents/src/extension/index.js
5727
6447
  function getSubagentSessionRoot(parentSessionFile) {
5728
6448
  if (parentSessionFile) {
5729
- let baseName = path11.basename(parentSessionFile, ".jsonl"), sessionsDir = path11.dirname(parentSessionFile);
5730
- return path11.join(sessionsDir, baseName);
6449
+ let baseName = path12.basename(parentSessionFile, ".jsonl"), sessionsDir = path12.dirname(parentSessionFile);
6450
+ return path12.join(sessionsDir, baseName);
5731
6451
  }
5732
- return fs9.mkdtempSync(path11.join(os.tmpdir(), "pi-subagent-session-"));
6452
+ return fs10.mkdtempSync(path12.join(os.tmpdir(), "pi-subagent-session-"));
5733
6453
  }
5734
6454
  function expandTilde(p) {
5735
- return p.startsWith("~/") ? path11.join(os.homedir(), p.slice(2)) : p;
6455
+ return p.startsWith("~/") ? path12.join(os.homedir(), p.slice(2)) : p;
5736
6456
  }
5737
6457
  function ensureAccessibleDir(dirPath) {
5738
- fs9.mkdirSync(dirPath, { recursive: !0 });
6458
+ fs10.mkdirSync(dirPath, { recursive: !0 });
5739
6459
  try {
5740
- fs9.accessSync(dirPath, fs9.constants.R_OK | fs9.constants.W_OK);
6460
+ fs10.accessSync(dirPath, fs10.constants.R_OK | fs10.constants.W_OK);
5741
6461
  } catch {
5742
6462
  try {
5743
- fs9.rmSync(dirPath, { recursive: !0, force: !0 });
6463
+ fs10.rmSync(dirPath, { recursive: !0, force: !0 });
5744
6464
  } catch {
5745
6465
  }
5746
- fs9.mkdirSync(dirPath, { recursive: !0 }), fs9.accessSync(dirPath, fs9.constants.R_OK | fs9.constants.W_OK);
6466
+ fs10.mkdirSync(dirPath, { recursive: !0 }), fs10.accessSync(dirPath, fs10.constants.R_OK | fs10.constants.W_OK);
5747
6467
  }
5748
6468
  }
5749
6469
  function isSlashResultRunning(result2) {
@@ -5758,18 +6478,25 @@ function effectiveParallelTaskCount(tasks) {
5758
6478
  return total + count;
5759
6479
  }, 0);
5760
6480
  }
5761
- function renderSubagentCall(args, theme, context) {
6481
+ function callIsAsync(args, asyncByDefault) {
6482
+ return args?.clarify === !0 ? !1 : args?.async === !0 ? !0 : args?.async === !1 ? !1 : asyncByDefault === !0;
6483
+ }
6484
+ function renderSubagentCall(args, theme, context, options) {
5762
6485
  if (args.action) {
5763
6486
  let target = args.id || args.runId || "";
5764
6487
  return new Text2(`${theme.fg("toolTitle", theme.bold("subagent "))}${args.action}${target ? ` ${theme.fg("accent", target)}` : ""}`, 0, 0);
5765
6488
  }
5766
- let isParallel = (args.tasks?.length ?? 0) > 0, parallelCount = effectiveParallelTaskCount(args.tasks), asyncLabel = args.async === !0 && args.clarify !== !0 ? theme.fg("warning", " [async]") : "";
6489
+ let isParallel = (args.tasks?.length ?? 0) > 0, parallelCount = effectiveParallelTaskCount(args.tasks), asyncMark = callIsAsync(args, options?.asyncByDefault) ? `${theme.fg("warning", "async")} ` : "";
5767
6490
  if (args.chain?.length)
5768
- return new Text2(`${theme.fg("toolTitle", theme.bold("subagent "))}chain (${args.chain.length})${asyncLabel}`, 0, 0);
6491
+ return new Text2(`${theme.fg("toolTitle", theme.bold("subagent "))}${asyncMark}chain (${args.chain.length})`, 0, 0);
5769
6492
  if (isParallel)
5770
- return new Text2(`${theme.fg("toolTitle", theme.bold("subagent "))}parallel (${parallelCount})${asyncLabel}`, 0, 0);
5771
- let description = !context?.isPartial && !context?.expanded && !context?.isError ? theme.fg("accent", args.description || args.task || "?") : "";
5772
- return new Text2(`${theme.fg("toolTitle", theme.bold(description ? "subagent " : "subagent"))}${description}${asyncLabel}`, 0, 0);
6493
+ return new Text2(`${theme.fg("toolTitle", theme.bold("subagent "))}${asyncMark}parallel (${parallelCount})`, 0, 0);
6494
+ let description = !context?.result && !context?.isPartial && !context?.expanded && !context?.isError ? theme.fg("accent", args.description || args.task || "?") : "", title = theme.fg("toolTitle", theme.bold("subagent")), rest = [asyncMark.trim() ? theme.fg("warning", "async") : "", description].filter(Boolean).join(" ");
6495
+ return new Text2(rest ? `${title} ${rest}` : title, 0, 0);
6496
+ }
6497
+ function renderSubagentNotify(details, theme) {
6498
+ let status = typeof details?.status == "string" ? details.status : "", icon = status === "completed" ? theme.fg("success", "\u2713") : status === "failed" ? theme.fg("error", "\u2717") : theme.fg("warning", "\u25A0"), title = details?.agent || "child";
6499
+ return new Text2(`${icon} ${theme.bold(title)} ${theme.fg("dim", status)}`, 0, 0);
5773
6500
  }
5774
6501
  function ensureSubagentResultAnimation(context) {
5775
6502
  let state = context.state;
@@ -5821,6 +6548,7 @@ function registerSubagentExtension(pi) {
5821
6548
  let state = {
5822
6549
  baseCwd: "",
5823
6550
  currentSessionId: null,
6551
+ sessionGeneration: 0,
5824
6552
  foregroundSubagentInFlight: 0,
5825
6553
  subagentSpawns: { sessionId: null, count: 0 },
5826
6554
  asyncJobs: /* @__PURE__ */ new Map(),
@@ -5842,15 +6570,19 @@ function registerSubagentExtension(pi) {
5842
6570
  }
5843
6571
  }, supervisorChannel = createNativeSupervisorChannel(pi, state), mainWatchdog = registerMainWatchdog(pi), { startResultWatcher, primeExistingResults, stopResultWatcher } = createResultWatcher(pi, state, RESULTS_DIR, 600 * 1e3);
5844
6572
  startResultWatcher(), primeExistingResults();
5845
- let generation = 0, executorPromise, pendingLaunches = /* @__PURE__ */ new Set(), runtimeCleanup = () => {
5846
- generation++, executorPromise = void 0, pendingLaunches.clear(), mainWatchdog.dispose(), stopResultWatcher(), scheduledRunManager.stop(), supervisorChannel.dispose(), clearPendingForegroundControlNotices(state), state.poller && (clearInterval(state.poller), state.poller = null);
6573
+ let generation = 0, executorPromise, pendingLaunches = /* @__PURE__ */ new Set(), pendingAsyncLaunches = /* @__PURE__ */ new Set(), unregisterCancellation, runtimeCleanup = () => {
6574
+ generation++, supervisorChannel.cancelOwnedQuestions("parent runtime replaced"), state.sessionGeneration = (state.sessionGeneration ?? 0) + 1, executorPromise = void 0, pendingLaunches.clear(), pendingAsyncLaunches.clear(), unregisterCancellation?.(), mainWatchdog.dispose(), stopResultWatcher(), scheduledRunManager.stop(), supervisorChannel.dispose(), clearPendingForegroundControlNotices(state), disposeSubagentWidget(), state.poller && (clearInterval(state.poller), state.poller = null);
5847
6575
  };
5848
6576
  globalStore[runtimeCleanupStoreKey] = runtimeCleanup;
5849
- let { ensurePoller, refreshWidget, handleStarted, handleComplete, resetJobs, restoreActiveJobs } = createAsyncJobTracker(pi, state, ASYNC_DIR, {
6577
+ let { ensurePoller, refreshWidget, handleStarted, handleComplete: trackComplete, resetJobs, restoreActiveJobs } = createAsyncJobTracker(pi, state, ASYNC_DIR, {
5850
6578
  widgetEnabled: config.asyncWidget !== !1
5851
- }), executorExecute, scheduledRunManager = createScheduledRunManager({
6579
+ }), handleComplete = (data) => {
6580
+ trackComplete(data);
6581
+ let result2 = data;
6582
+ typeof state.currentSessionId == "string" && result2.sessionId !== state.currentSessionId || typeof result2.id == "string" && result2.id.trim() && supervisorChannel.settleRunQuestions(result2.id, "async run settled");
6583
+ }, executorExecute, scheduledRunManager = createScheduledRunManager({
5852
6584
  config,
5853
- launch: (params, ctx, signal) => executorExecute ? executorExecute(randomUUID3(), params, signal, void 0, ctx) : Promise.resolve({
6585
+ launch: (params, ctx, signal) => executorExecute ? executorExecute(randomUUID4(), params, signal, void 0, ctx) : Promise.resolve({
5854
6586
  content: [{ type: "text", text: "Scheduled subagent launch is unavailable (executor not ready)." }],
5855
6587
  isError: !0,
5856
6588
  details: { mode: "management", results: [] }
@@ -5860,13 +6592,13 @@ function registerSubagentExtension(pi) {
5860
6592
  let resolveLaunchReady, launchReady = !1, launchReadyPromise = new Promise((resolve3) => {
5861
6593
  resolveLaunchReady = resolve3;
5862
6594
  }), markLaunchReady = () => {
5863
- launchReady || (launchReady = !0, pendingLaunches.delete(launchReadyPromise), resolveLaunchReady());
6595
+ launchReady || (launchReady = !0, pendingLaunches.delete(launchReadyPromise), pendingAsyncLaunches.delete(launchReadyPromise), resolveLaunchReady());
5864
6596
  };
5865
- pendingLaunches.add(launchReadyPromise);
6597
+ pendingLaunches.add(launchReadyPromise), (params.task || params.tasks || params.chain) && !params.clarify && (params.async === !0 || params.async === void 0 && asyncByDefault || config.forceTopLevelAsync === !0) && pendingAsyncLaunches.add(launchReadyPromise);
5866
6598
  try {
5867
6599
  let currentGeneration = generation;
5868
6600
  if (signal?.throwIfAborted(), !executorPromise) {
5869
- let pending = import("./subagent-executor-5Q5A4HJ2.js").then(({ createSubagentExecutor }) => {
6601
+ let pending = import("./subagent-executor-SRFC26SJ.js").then(({ createSubagentExecutor }) => {
5870
6602
  if (generation !== currentGeneration)
5871
6603
  throw new Error("Subagent session changed before initialization");
5872
6604
  return createSubagentExecutor({
@@ -5904,28 +6636,9 @@ function registerSubagentExtension(pi) {
5904
6636
  let content = typeof message.content == "string" ? message.content : message.content.filter((entry) => entry.type === "text").map((entry) => entry.text).join(`
5905
6637
  `);
5906
6638
  return new Text2(content, 0, 0);
5907
- }), pi.registerMessageRenderer("subagent-notify", (message, options, theme) => {
6639
+ }), pi.registerMessageRenderer("subagent-notify", (message, _options, theme) => {
5908
6640
  let content = typeof message.content == "string" ? message.content : "", details = message.details ?? parseSubagentNotifyContent(content);
5909
- if (!details)
5910
- return new Text2(content, 0, 0);
5911
- let icon = details.status === "completed" ? theme.fg("success", "\u2713") : details.status === "paused" ? theme.fg("warning", "\u25A0") : theme.fg("error", "\u2717"), parts = [];
5912
- details.taskInfo && parts.push(details.taskInfo), details.durationMs !== void 0 && parts.push(formatDuration(details.durationMs));
5913
- let notifyLabel = details.taskInfo || details.agent || "child", text = `${icon} ${theme.bold(notifyLabel)} ${theme.fg("dim", details.status)}`;
5914
- parts.length > 0 && (text += ` ${theme.fg("dim", "\xB7")} ${parts.map((part) => theme.fg("dim", part)).join(` ${theme.fg("dim", "\xB7")} `)}`);
5915
- let trimmedPreview = details.resultPreview.trim(), previewLines = options.expanded ? trimmedPreview.split(`
5916
- `).filter((line) => line.trim()) : [trimmedPreview.split(`
5917
- `, 1)[0] ?? ""].filter((line) => line.trim());
5918
- for (let line of previewLines.length > 0 ? previewLines : ["(no output)"])
5919
- text += `
5920
- ${theme.fg("dim", `\u23BF ${line}`)}`;
5921
- if (!options.expanded && trimmedPreview.includes(`
5922
- `)) {
5923
- let expandKey = keyText("app.tools.expand");
5924
- text += `
5925
- ${theme.fg("dim", `${expandKey} full notification`)}`;
5926
- }
5927
- return details.sessionLabel && details.sessionValue && (text += `
5928
- ${theme.fg("muted", `${details.sessionLabel}: ${shortenPath(details.sessionValue)}`)}`), new Text2(text, 0, 0);
6641
+ return details ? renderSubagentNotify(details, theme) : new Text2(content, 0, 0);
5929
6642
  }), pi.registerMessageRenderer(SUBAGENT_STEERING_MESSAGE_TYPE, (message, _options, theme) => {
5930
6643
  let details = message.details;
5931
6644
  if (details)
@@ -5957,7 +6670,7 @@ function registerSubagentExtension(pi) {
5957
6670
  execute(id, params, signal, onUpdate, ctx) {
5958
6671
  return executeSubagentCollapsed(id, params, signal, onUpdate, ctx);
5959
6672
  },
5960
- renderCall: renderSubagentCall,
6673
+ renderCall: (args, theme, context) => renderSubagentCall(args, theme, context, { asyncByDefault }),
5961
6674
  renderResult(result2, options, theme, context) {
5962
6675
  subagentResultIsRunning(result2) ? ensureSubagentResultAnimation(context) : clearLegacyResultAnimationTimer(context);
5963
6676
  let state2 = context.state, entries = [];
@@ -6016,16 +6729,25 @@ function registerSubagentExtension(pi) {
6016
6729
  } catch {
6017
6730
  }
6018
6731
  }, resetSessionState = (ctx) => {
6019
- if (generation++, executorPromise = void 0, pendingLaunches.clear(), state.baseCwd = ctx.cwd, state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager), state.subagentSpawns = { sessionId: state.currentSessionId, count: 0 }, !process.env[SUBAGENT_CHILD_ENV]) {
6020
- let sessionId = ctx.sessionManager.getSessionId();
6021
- sessionId && (process.env[SUBAGENT_PARENT_SESSION_ENV] = sessionId);
6022
- }
6023
- state.lastUiContext = ctx, cleanupSessionArtifacts(ctx), clearPendingForegroundControlNotices(state), resetJobs(ctx), restoreActiveJobs(ctx), scheduledRunManager.bindSession(ctx), restoreSlashFinalSnapshots(ctx.sessionManager.getEntries()), primeExistingResults();
6732
+ if (generation++, supervisorChannel.cancelOwnedQuestions("parent session replaced"), state.sessionGeneration = (state.sessionGeneration ?? 0) + 1, executorPromise = void 0, pendingLaunches.clear(), pendingAsyncLaunches.clear(), unregisterCancellation?.(), state.baseCwd = ctx.cwd, state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager), state.subagentSpawns = { sessionId: state.currentSessionId, count: 0 }, !process.env[SUBAGENT_CHILD_ENV]) {
6733
+ let sessionId2 = ctx.sessionManager.getSessionId();
6734
+ sessionId2 && (process.env[SUBAGENT_PARENT_SESSION_ENV] = sessionId2);
6735
+ }
6736
+ state.lastUiContext = ctx, cleanupSessionArtifacts(ctx), clearPendingForegroundControlNotices(state), resetJobs(ctx), restoreActiveJobs(ctx);
6737
+ let sessionId = state.currentSessionId, sessionGeneration = generation;
6738
+ sessionId && (unregisterCancellation = registerSubagentCancellation(sessionId, createSubagentCancellation({
6739
+ pendingLaunches: pendingAsyncLaunches,
6740
+ isCurrent: () => generation === sessionGeneration && state.currentSessionId === sessionId,
6741
+ getActiveRunIds: () => [...state.asyncJobs.values()].filter((job) => job.sessionId === sessionId && (job.status === "queued" || job.status === "running")).map((job) => job.asyncId),
6742
+ async stopRun(id) {
6743
+ return !(await executor.execute(randomUUID4(), { action: "stop", id }, new AbortController().signal, void 0, ctx)).isError;
6744
+ }
6745
+ }))), scheduledRunManager.bindSession(ctx), restoreSlashFinalSnapshots(ctx.sessionManager.getEntries()), primeExistingResults();
6024
6746
  };
6025
6747
  pi.on("session_start", (_event, ctx) => {
6026
6748
  resetSessionState(ctx), rpcBridge.emitReady(ctx), supervisorChannel.start();
6027
6749
  }), pi.on("session_shutdown", () => {
6028
- generation++, executorPromise = void 0, pendingLaunches.clear(), delete process.env[SUBAGENT_PARENT_SESSION_ENV];
6750
+ generation++, supervisorChannel.cancelOwnedQuestions("parent session shutdown"), state.sessionGeneration = (state.sessionGeneration ?? 0) + 1, executorPromise = void 0, pendingLaunches.clear(), pendingAsyncLaunches.clear(), unregisterCancellation?.(), delete process.env[SUBAGENT_PARENT_SESSION_ENV];
6029
6751
  for (let unsubscribe of eventUnsubscribes)
6030
6752
  try {
6031
6753
  unsubscribe();
@@ -6034,7 +6756,7 @@ function registerSubagentExtension(pi) {
6034
6756
  globalStore[eventUnsubscribeStoreKey] === eventUnsubscribes && delete globalStore[eventUnsubscribeStoreKey], stopResultWatcher(), scheduledRunManager.stop(), state.poller && clearInterval(state.poller), state.poller = null, clearPendingForegroundControlNotices(state);
6035
6757
  for (let timer of state.cleanupTimers.values())
6036
6758
  clearTimeout(timer);
6037
- state.cleanupTimers.clear(), state.asyncJobs.clear(), clearSlashSnapshots(), slashBridge.cancelAll(), slashBridge.dispose(), promptTemplateBridge.cancelAll(), promptTemplateBridge.dispose(), supervisorChannel.dispose(), globalStore[runtimeCleanupStoreKey] === runtimeCleanup && delete globalStore[runtimeCleanupStoreKey];
6759
+ state.cleanupTimers.clear(), state.asyncJobs.clear(), clearSlashSnapshots(), slashBridge.cancelAll(), slashBridge.dispose(), promptTemplateBridge.cancelAll(), promptTemplateBridge.dispose(), supervisorChannel.dispose(), globalStore[runtimeCleanupStoreKey] === runtimeCleanup && delete globalStore[runtimeCleanupStoreKey], disposeSubagentWidget();
6038
6760
  try {
6039
6761
  state.lastUiContext?.hasUI && state.lastUiContext.ui.setWidget(WIDGET_KEY, void 0);
6040
6762
  } catch (error) {