@ashx-j/lunr 0.2.20 → 0.2.21

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 (172) hide show
  1. package/CHANGELOG.md +13 -1
  2. package/dist/builtin-extensions/lunr-todos.d.ts +6 -5
  3. package/dist/builtin-extensions/lunr-todos.d.ts.map +1 -1
  4. package/dist/builtin-extensions/lunr-todos.js +9 -7
  5. package/dist/builtin-extensions/lunr-todos.js.map +1 -1
  6. package/dist/builtin-extensions/pi-intercom/broker/spawn.d.ts.map +1 -1
  7. package/dist/builtin-extensions/pi-intercom/broker/spawn.js +30 -21
  8. package/dist/builtin-extensions/pi-intercom/broker/spawn.js.map +1 -1
  9. package/dist/builtin-extensions/pi-intercom/index.d.ts.map +1 -1
  10. package/dist/builtin-extensions/pi-intercom/index.js +12 -3
  11. package/dist/builtin-extensions/pi-intercom/index.js.map +1 -1
  12. package/dist/builtin-extensions/pi-subagents/src/extension/fanout-child.d.ts.map +1 -1
  13. package/dist/builtin-extensions/pi-subagents/src/extension/fanout-child.js +2 -2
  14. package/dist/builtin-extensions/pi-subagents/src/extension/fanout-child.js.map +1 -1
  15. package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts +7 -6
  16. package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts.map +1 -1
  17. package/dist/builtin-extensions/pi-subagents/src/extension/index.js +63 -29
  18. package/dist/builtin-extensions/pi-subagents/src/extension/index.js.map +1 -1
  19. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts.map +1 -1
  20. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js +2 -2
  21. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js.map +1 -1
  22. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts +3 -3
  23. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts.map +1 -1
  24. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js +3 -2
  25. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js.map +1 -1
  26. package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.d.ts +1 -0
  27. package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.d.ts.map +1 -1
  28. package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.js +26 -7
  29. package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.js.map +1 -1
  30. package/dist/builtin-extensions/pi-subagents/src/intercom/result-intercom.d.ts +1 -0
  31. package/dist/builtin-extensions/pi-subagents/src/intercom/result-intercom.d.ts.map +1 -1
  32. package/dist/builtin-extensions/pi-subagents/src/intercom/result-intercom.js +1 -1
  33. package/dist/builtin-extensions/pi-subagents/src/intercom/result-intercom.js.map +1 -1
  34. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.d.ts +2 -0
  35. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.d.ts.map +1 -1
  36. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js +4 -3
  37. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js.map +1 -1
  38. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-job-tracker.d.ts.map +1 -1
  39. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-job-tracker.js +2 -0
  40. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-job-tracker.js.map +1 -1
  41. package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.d.ts +6 -2
  42. package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.d.ts.map +1 -1
  43. package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.js +23 -1
  44. package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.js.map +1 -1
  45. package/dist/builtin-extensions/pi-subagents/src/runs/background/result-watcher.d.ts.map +1 -1
  46. package/dist/builtin-extensions/pi-subagents/src/runs/background/result-watcher.js +1 -0
  47. package/dist/builtin-extensions/pi-subagents/src/runs/background/result-watcher.js.map +1 -1
  48. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.d.ts +100 -0
  49. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.d.ts.map +1 -1
  50. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js +46 -31
  51. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js.map +1 -1
  52. package/dist/builtin-extensions/pi-subagents/src/runs/background/top-level-async.d.ts +15 -2
  53. package/dist/builtin-extensions/pi-subagents/src/runs/background/top-level-async.d.ts.map +1 -1
  54. package/dist/builtin-extensions/pi-subagents/src/runs/background/top-level-async.js +14 -4
  55. package/dist/builtin-extensions/pi-subagents/src/runs/background/top-level-async.js.map +1 -1
  56. package/dist/builtin-extensions/pi-subagents/src/runs/background/wait-tool.d.ts.map +1 -1
  57. package/dist/builtin-extensions/pi-subagents/src/runs/background/wait-tool.js +1 -1
  58. package/dist/builtin-extensions/pi-subagents/src/runs/background/wait-tool.js.map +1 -1
  59. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts.map +1 -1
  60. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js +1 -1
  61. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js.map +1 -1
  62. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts +0 -1
  63. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts.map +1 -1
  64. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js +9 -9
  65. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js.map +1 -1
  66. package/dist/builtin-extensions/pi-subagents/src/runs/shared/completion-guard.d.ts +1 -1
  67. package/dist/builtin-extensions/pi-subagents/src/runs/shared/completion-guard.d.ts.map +1 -1
  68. package/dist/builtin-extensions/pi-subagents/src/runs/shared/completion-guard.js +1 -1
  69. package/dist/builtin-extensions/pi-subagents/src/runs/shared/completion-guard.js.map +1 -1
  70. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts +1 -0
  71. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts.map +1 -1
  72. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.js.map +1 -1
  73. package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.d.ts +3 -0
  74. package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.d.ts.map +1 -1
  75. package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.js +2 -0
  76. package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.js.map +1 -1
  77. package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.d.ts +2 -2
  78. package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.d.ts.map +1 -1
  79. package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.js +5 -10
  80. package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.js.map +1 -1
  81. package/dist/builtin-extensions/pi-subagents/src/shared/async-guidance.d.ts +2 -0
  82. package/dist/builtin-extensions/pi-subagents/src/shared/async-guidance.d.ts.map +1 -0
  83. package/dist/builtin-extensions/pi-subagents/src/shared/async-guidance.js +3 -0
  84. package/dist/builtin-extensions/pi-subagents/src/shared/async-guidance.js.map +1 -0
  85. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts +5 -0
  86. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts.map +1 -1
  87. package/dist/builtin-extensions/pi-subagents/src/shared/types.js.map +1 -1
  88. package/dist/builtin-extensions/pi-subagents/src/slash/delegation-adapters.d.ts.map +1 -1
  89. package/dist/builtin-extensions/pi-subagents/src/slash/delegation-adapters.js.map +1 -1
  90. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.d.ts +7 -0
  91. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.d.ts.map +1 -1
  92. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js +11 -7
  93. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js.map +1 -1
  94. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts +4 -0
  95. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts.map +1 -1
  96. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js +22 -23
  97. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js.map +1 -1
  98. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts +2 -0
  99. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts.map +1 -1
  100. package/dist/builtin-extensions/pi-subagents/src/tui/render.js +237 -374
  101. package/dist/builtin-extensions/pi-subagents/src/tui/render.js.map +1 -1
  102. package/dist/core/customize.d.ts +2 -0
  103. package/dist/core/customize.d.ts.map +1 -1
  104. package/dist/core/customize.js +3 -0
  105. package/dist/core/customize.js.map +1 -1
  106. package/dist/core/settings-manager.d.ts +4 -0
  107. package/dist/core/settings-manager.d.ts.map +1 -1
  108. package/dist/core/settings-manager.js +11 -0
  109. package/dist/core/settings-manager.js.map +1 -1
  110. package/dist/core/subagent-spinner-setting.d.ts +6 -0
  111. package/dist/core/subagent-spinner-setting.d.ts.map +1 -0
  112. package/dist/core/subagent-spinner-setting.js +12 -0
  113. package/dist/core/subagent-spinner-setting.js.map +1 -0
  114. package/dist/core/subagent-spinner.d.ts +4 -0
  115. package/dist/core/subagent-spinner.d.ts.map +1 -0
  116. package/dist/core/subagent-spinner.js +6 -0
  117. package/dist/core/subagent-spinner.js.map +1 -0
  118. package/dist/modes/interactive/components/settings-selector.d.ts +4 -0
  119. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  120. package/dist/modes/interactive/components/settings-selector.js +73 -1
  121. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  122. package/dist/modes/interactive/extension-widget-order.d.ts +8 -0
  123. package/dist/modes/interactive/extension-widget-order.d.ts.map +1 -0
  124. package/dist/modes/interactive/extension-widget-order.js +37 -0
  125. package/dist/modes/interactive/extension-widget-order.js.map +1 -0
  126. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  127. package/dist/modes/interactive/interactive-mode.js +15 -22
  128. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  129. package/dist/node-runtime/{agent-session-services-75JKASCW.js → agent-session-services-C743TYQE.js} +4 -3
  130. package/dist/node-runtime/{builtin-extensions-X4JMM4ZS.js → builtin-extensions-JCOPAAB5.js} +2 -1
  131. package/dist/node-runtime/{chunk-QKFMBRAL.js → chunk-2BUAYZ7K.js} +94 -25
  132. package/dist/node-runtime/chunk-5ZH3ZFIZ.js +308 -0
  133. package/dist/node-runtime/{chunk-BBUVIEZB.js → chunk-A3KMVBZV.js} +10 -1
  134. package/dist/node-runtime/{chunk-K3646JTM.js → chunk-AWVD5IEY.js} +7 -307
  135. package/dist/node-runtime/{chunk-ELWEXVO5.js → chunk-GB2MGFLI.js} +11 -11
  136. package/dist/node-runtime/chunk-LISUTZS3.js +19 -0
  137. package/dist/node-runtime/{chunk-AL2FMKKM.js → chunk-PDNXVXMD.js} +12 -12
  138. package/dist/node-runtime/{chunk-46ECNOLF.js → chunk-SY2TFMLO.js} +57 -17
  139. package/dist/node-runtime/{chunk-HQCKFM2O.js → chunk-VGYZ3APQ.js} +3 -0
  140. package/dist/node-runtime/{chunk-4DJ6HEGH.js → chunk-VZR3NCM7.js} +3 -3
  141. package/dist/node-runtime/{chunk-AOFXZQKA.js → chunk-W3SILANP.js} +1 -1
  142. package/dist/node-runtime/{chunk-CBGB57XT.js → chunk-WLLDXJRT.js} +10 -0
  143. package/dist/node-runtime/{chunk-JYNNB7DU.js → chunk-XI77KVKA.js} +2 -2
  144. package/dist/node-runtime/{chunk-4CSFRBCN.js → chunk-Y5KOEMBH.js} +1 -1
  145. package/dist/node-runtime/{chunk-AROOY4BB.js → chunk-YMEAYRBE.js} +1 -1
  146. package/dist/node-runtime/{command-RBTRL55A.js → command-P4NYCJ2M.js} +1 -1
  147. package/dist/node-runtime/{customize-XEXL2DGQ.js → customize-LF5VST47.js} +1 -1
  148. package/dist/node-runtime/{gateway-L4ANRTVE.js → gateway-BM6NLOH4.js} +1 -1
  149. package/dist/node-runtime/index.js +13 -11
  150. package/dist/node-runtime/{interactive-mode-HPUNH7SV.js → interactive-mode-EVVBA64D.js} +9 -7
  151. package/dist/node-runtime/{lunr-cron-NNYTWTGF.js → lunr-cron-4VHP4E4B.js} +1 -1
  152. package/dist/node-runtime/main.js +11 -9
  153. package/dist/node-runtime/{package-manager-cli-7RKSZGEN.js → package-manager-cli-N37546V5.js} +5 -4
  154. package/dist/node-runtime/{pi-intercom-DDQMRNPJ.js → pi-intercom-NV2LW6X7.js} +34 -33
  155. package/dist/node-runtime/{pi-subagents-T5WMMLBB.js → pi-subagents-6MUIFNDM.js} +286 -345
  156. package/dist/node-runtime/rpc-entry.js +11 -9
  157. package/dist/node-runtime/{runtime-bridges-NJQERQ72.js → runtime-bridges-2VQTLPOZ.js} +2 -2
  158. package/dist/node-runtime/{settings-manager-GHPTUN7R.js → settings-manager-CUGAZWAB.js} +2 -1
  159. package/dist/node-runtime/{subagent-executor-SRFC26SJ.js → subagent-executor-MH4A7CCF.js} +14 -46
  160. package/dist/node-runtime/{update-cli-L3JIBH2U.js → update-cli-XTZMZGOL.js} +2 -1
  161. package/docs/features.md +6 -2
  162. package/docs/settings.md +2 -1
  163. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  164. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  165. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  166. package/examples/extensions/gondolin/package-lock.json +2 -2
  167. package/examples/extensions/gondolin/package.json +1 -1
  168. package/examples/extensions/sandbox/package-lock.json +2 -2
  169. package/examples/extensions/sandbox/package.json +1 -1
  170. package/examples/extensions/with-deps/package-lock.json +2 -2
  171. package/examples/extensions/with-deps/package.json +1 -1
  172. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"fanout-child.d.ts","sourceRoot":"","sources":["../../../../../src/builtin-extensions/pi-subagents/src/extension/fanout-child.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,iCAAiC,CAAC;AAgIpF,MAAM,CAAC,OAAO,UAAU,oCAAoC,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA0CnF","sourcesContent":["// @ts-nocheck\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport type { ExtensionAPI, ToolDefinition } from \"@ashx-j/lunr\";\n\nimport { getArtifactsDir } from \"../shared/artifacts.ts\";\nimport { createSubagentExecutor, type SubagentParamsLike } from \"../runs/foreground/subagent-executor.ts\";\nimport { resolveWaitToolConfig } from \"../runs/background/wait-config.ts\";\nimport { SUBAGENT_CHILD_ENV, SUBAGENT_FANOUT_CHILD_ENV } from \"../runs/shared/pi-args.ts\";\nimport { readNestedControlRequests, resolveNestedRouteFromEnv, writeNestedControlResult } from \"../runs/shared/nested-events.ts\";\nimport { deliverSubagentIntercomMessageEvent } from \"../intercom/result-intercom.ts\";\nimport { resolveSubagentIntercomTarget } from \"../intercom/intercom-bridge.ts\";\nimport { SubagentParams } from \"./schemas.ts\";\nimport { loadConfig } from \"./config.ts\";\nimport { type Details, type SubagentState } from \"../shared/types.ts\";\n\nfunction getSubagentSessionRoot(parentSessionFile: string | null): string {\n\tif (parentSessionFile) {\n\t\tconst baseName = path.basename(parentSessionFile, \".jsonl\");\n\t\tconst sessionsDir = path.dirname(parentSessionFile);\n\t\treturn path.join(sessionsDir, baseName);\n\t}\n\treturn fs.mkdtempSync(path.join(os.tmpdir(), \"pi-subagent-session-\"));\n}\n\nfunction expandTilde(p: string): string {\n\treturn p.startsWith(\"~/\") ? path.join(os.homedir(), p.slice(2)) : p;\n}\n\nfunction createChildSafeState(): SubagentState {\n\treturn {\n\t\tbaseCwd: \"\",\n\t\tcurrentSessionId: null,\n\t\tforegroundSubagentInFlight: 0,\n\t\tsubagentSpawns: { sessionId: null, count: 0 },\n\t\tasyncJobs: new Map(),\n\t\tforegroundRuns: new Map(),\n\t\tforegroundControls: new Map(),\n\t\tlastForegroundControlId: null,\n\t\tpendingForegroundControlNotices: new Map(),\n\t\tcleanupTimers: new Map(),\n\t\tlastUiContext: null,\n\t\tpoller: null,\n\t\tcompletionSeen: new Map(),\n\t\twatcher: null,\n\t\twatcherRestartTimer: null,\n\t\tresultFileCoalescer: {\n\t\t\tschedule: () => false,\n\t\t\tclear: () => {},\n\t\t},\n\t};\n}\n\nfunction startNestedControlInboxListener(pi: ExtensionAPI, state: SubagentState): NodeJS.Timeout | undefined {\n\tlet route;\n\ttry {\n\t\troute = resolveNestedRouteFromEnv();\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (!route) return undefined;\n\tconst seen = new Set<string>();\n\tconst inFlight = new Set<string>();\n\tconst pendingResults = new Map<string, Parameters<typeof writeNestedControlResult>[1]>();\n\tconst timer = setInterval(() => {\n\t\ttry {\n\t\t\tfor (const request of readNestedControlRequests(route)) {\n\t\t\t\tif (seen.has(request.requestId) || inFlight.has(request.requestId)) continue;\n\t\t\t\tinFlight.add(request.requestId);\n\t\t\t\tvoid (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tlet result = pendingResults.get(request.requestId);\n\t\t\t\t\t\tif (!result) {\n\t\t\t\t\t\t\tlet ok = false;\n\t\t\t\t\t\t\tlet message = \"Control request failed.\";\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tconst control = state.foregroundControls.get(request.targetRunId);\n\t\t\t\t\t\t\t\tif (!control) {\n\t\t\t\t\t\t\t\t\tmessage = `Nested run ${request.targetRunId} is not active in this fanout child.`;\n\t\t\t\t\t\t\t\t} else if (request.action === \"interrupt\") {\n\t\t\t\t\t\t\t\t\tok = control.interrupt?.() === true;\n\t\t\t\t\t\t\t\t\tmessage = ok\n\t\t\t\t\t\t\t\t\t\t? `Interrupt requested for nested run ${request.targetRunId}.`\n\t\t\t\t\t\t\t\t\t\t: `Nested run ${request.targetRunId} has no active child step to interrupt.`;\n\t\t\t\t\t\t\t\t} else if (!request.message?.trim()) {\n\t\t\t\t\t\t\t\t\tmessage = \"Nested resume requires message.\";\n\t\t\t\t\t\t\t\t} else if (!control.currentChildId && !control.currentAgent) {\n\t\t\t\t\t\t\t\t\tmessage = `Nested run ${request.targetRunId} has no active child message route.`;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tconst index = control.currentIndex ?? 0;\n\t\t\t\t\t\t\t\t\tconst childId = control.currentChildId ?? control.currentAgent!;\n\t\t\t\t\t\t\t\t\tconst target = resolveSubagentIntercomTarget(request.targetRunId, childId, index);\n\t\t\t\t\t\t\t\t\tok = await deliverSubagentIntercomMessageEvent(\n\t\t\t\t\t\t\t\t\t\tpi.events,\n\t\t\t\t\t\t\t\t\t\ttarget,\n\t\t\t\t\t\t\t\t\t\t`Follow-up for nested run ${request.targetRunId} (${control.currentAgent}):\\n\\n${request.message.trim()}`,\n\t\t\t\t\t\t\t\t\t\t500,\n\t\t\t\t\t\t\t\t\t\t{ source: \"nested-resume\", runId: request.targetRunId, agent: control.currentAgent, index },\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tmessage = ok\n\t\t\t\t\t\t\t\t\t\t? `Delivered follow-up to live nested run ${request.targetRunId}.`\n\t\t\t\t\t\t\t\t\t\t: `Nested child intercom target is not registered: ${target}`;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\tmessage = error instanceof Error ? error.message : String(error);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tresult = { ts: Date.now(), requestId: request.requestId, targetRunId: request.targetRunId, ok, message };\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\twriteNestedControlResult(route, result);\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tpendingResults.set(request.requestId, result);\n\t\t\t\t\t\t\tconsole.error(`Failed to write nested control result for request '${request.requestId}' targeting '${request.targetRunId}' via inbox '${route.controlInbox}'; keeping request for retry:`, error);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpendingResults.delete(request.requestId);\n\t\t\t\t\t\tseen.add(request.requestId);\n\t\t\t\t\t\ttry { fs.unlinkSync(request.filePath); } catch {}\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tinFlight.delete(request.requestId);\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(`Failed to poll nested control inbox '${route.controlInbox}' for root '${route.rootRunId}':`, error);\n\t\t}\n\t}, 200);\n\ttimer.unref?.();\n\treturn timer;\n}\n\nexport default function registerFanoutChildSubagentExtension(pi: ExtensionAPI): void {\n\tif (process.env[SUBAGENT_CHILD_ENV] !== \"1\" || process.env[SUBAGENT_FANOUT_CHILD_ENV] !== \"1\") return;\n\n\tconst globalStore = globalThis as Record<string, unknown>;\n\tconst registeredKey = \"__piSubagentFanoutChildRegisteredApis\";\n\tconst registeredApis = globalStore[registeredKey] instanceof WeakSet\n\t\t? globalStore[registeredKey] as WeakSet<ExtensionAPI>\n\t\t: new WeakSet<ExtensionAPI>();\n\tglobalStore[registeredKey] = registeredApis;\n\tif (registeredApis.has(pi)) return;\n\tregisteredApis.add(pi);\n\n\tconst config = loadConfig();\n\tconst state = createChildSafeState();\n\tconst executor = createSubagentExecutor({\n\t\tpi,\n\t\tstate,\n\t\tconfig,\n\t\tasyncByDefault: config.asyncByDefault === true,\n\t\twaitToolEnabled: resolveWaitToolConfig(config.waitTool).enabled,\n\t\ttempArtifactsDir: getArtifactsDir(null),\n\t\tgetSubagentSessionRoot,\n\t\texpandTilde,\n\t\tallowMutatingManagementActions: false,\n\t});\n\n\tconst tool: ToolDefinition<typeof SubagentParams, Details> = {\n\t\tname: \"subagent\",\n\t\tlabel: \"Subagent\",\n\t\tdescription: [\n\t\t\t\"Delegate to generic children from child-safe fanout mode.\",\n\t\t\t\"Execution uses { task, description, permissions? } (or tasks[] / chain[]).\",\n\t\t\t\"Allowed control actions: status, interrupt, resume, steer, append-step, doctor.\",\n\t\t].join(\"\\n\"),\n\t\tparameters: SubagentParams,\n\t\texecute(id, params, signal, onUpdate, ctx) {\n\t\t\treturn executor.execute(id, params as SubagentParamsLike, signal, onUpdate, ctx);\n\t\t},\n\t};\n\n\tpi.registerTool(tool);\n\tstartNestedControlInboxListener(pi, state);\n}\n"]}
1
+ {"version":3,"file":"fanout-child.d.ts","sourceRoot":"","sources":["../../../../../src/builtin-extensions/pi-subagents/src/extension/fanout-child.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,iCAAiC,CAAC;AAiIpF,MAAM,CAAC,OAAO,UAAU,oCAAoC,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAyCnF","sourcesContent":["// @ts-nocheck\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport type { ExtensionAPI, ToolDefinition } from \"@ashx-j/lunr\";\n\nimport { getArtifactsDir } from \"../shared/artifacts.ts\";\nimport { createSubagentExecutor, type SubagentParamsLike } from \"../runs/foreground/subagent-executor.ts\";\nimport { resolveWaitToolConfig } from \"../runs/background/wait-config.ts\";\nimport { normalizeAsyncLaunchConfig } from \"../runs/background/top-level-async.ts\";\nimport { SUBAGENT_CHILD_ENV, SUBAGENT_FANOUT_CHILD_ENV } from \"../runs/shared/pi-args.ts\";\nimport { readNestedControlRequests, resolveNestedRouteFromEnv, writeNestedControlResult } from \"../runs/shared/nested-events.ts\";\nimport { deliverSubagentIntercomMessageEvent } from \"../intercom/result-intercom.ts\";\nimport { resolveSubagentIntercomTarget } from \"../intercom/intercom-bridge.ts\";\nimport { SubagentParams } from \"./schemas.ts\";\nimport { loadConfig } from \"./config.ts\";\nimport { type Details, type SubagentState } from \"../shared/types.ts\";\n\nfunction getSubagentSessionRoot(parentSessionFile: string | null): string {\n\tif (parentSessionFile) {\n\t\tconst baseName = path.basename(parentSessionFile, \".jsonl\");\n\t\tconst sessionsDir = path.dirname(parentSessionFile);\n\t\treturn path.join(sessionsDir, baseName);\n\t}\n\treturn fs.mkdtempSync(path.join(os.tmpdir(), \"pi-subagent-session-\"));\n}\n\nfunction expandTilde(p: string): string {\n\treturn p.startsWith(\"~/\") ? path.join(os.homedir(), p.slice(2)) : p;\n}\n\nfunction createChildSafeState(): SubagentState {\n\treturn {\n\t\tbaseCwd: \"\",\n\t\tcurrentSessionId: null,\n\t\tforegroundSubagentInFlight: 0,\n\t\tsubagentSpawns: { sessionId: null, count: 0 },\n\t\tasyncJobs: new Map(),\n\t\tforegroundRuns: new Map(),\n\t\tforegroundControls: new Map(),\n\t\tlastForegroundControlId: null,\n\t\tpendingForegroundControlNotices: new Map(),\n\t\tcleanupTimers: new Map(),\n\t\tlastUiContext: null,\n\t\tpoller: null,\n\t\tcompletionSeen: new Map(),\n\t\twatcher: null,\n\t\twatcherRestartTimer: null,\n\t\tresultFileCoalescer: {\n\t\t\tschedule: () => false,\n\t\t\tclear: () => {},\n\t\t},\n\t};\n}\n\nfunction startNestedControlInboxListener(pi: ExtensionAPI, state: SubagentState): NodeJS.Timeout | undefined {\n\tlet route;\n\ttry {\n\t\troute = resolveNestedRouteFromEnv();\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (!route) return undefined;\n\tconst seen = new Set<string>();\n\tconst inFlight = new Set<string>();\n\tconst pendingResults = new Map<string, Parameters<typeof writeNestedControlResult>[1]>();\n\tconst timer = setInterval(() => {\n\t\ttry {\n\t\t\tfor (const request of readNestedControlRequests(route)) {\n\t\t\t\tif (seen.has(request.requestId) || inFlight.has(request.requestId)) continue;\n\t\t\t\tinFlight.add(request.requestId);\n\t\t\t\tvoid (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tlet result = pendingResults.get(request.requestId);\n\t\t\t\t\t\tif (!result) {\n\t\t\t\t\t\t\tlet ok = false;\n\t\t\t\t\t\t\tlet message = \"Control request failed.\";\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tconst control = state.foregroundControls.get(request.targetRunId);\n\t\t\t\t\t\t\t\tif (!control) {\n\t\t\t\t\t\t\t\t\tmessage = `Nested run ${request.targetRunId} is not active in this fanout child.`;\n\t\t\t\t\t\t\t\t} else if (request.action === \"interrupt\") {\n\t\t\t\t\t\t\t\t\tok = control.interrupt?.() === true;\n\t\t\t\t\t\t\t\t\tmessage = ok\n\t\t\t\t\t\t\t\t\t\t? `Interrupt requested for nested run ${request.targetRunId}.`\n\t\t\t\t\t\t\t\t\t\t: `Nested run ${request.targetRunId} has no active child step to interrupt.`;\n\t\t\t\t\t\t\t\t} else if (!request.message?.trim()) {\n\t\t\t\t\t\t\t\t\tmessage = \"Nested resume requires message.\";\n\t\t\t\t\t\t\t\t} else if (!control.currentChildId && !control.currentAgent) {\n\t\t\t\t\t\t\t\t\tmessage = `Nested run ${request.targetRunId} has no active child message route.`;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tconst index = control.currentIndex ?? 0;\n\t\t\t\t\t\t\t\t\tconst childId = control.currentChildId ?? control.currentAgent!;\n\t\t\t\t\t\t\t\t\tconst target = resolveSubagentIntercomTarget(request.targetRunId, childId, index);\n\t\t\t\t\t\t\t\t\tok = await deliverSubagentIntercomMessageEvent(\n\t\t\t\t\t\t\t\t\t\tpi.events,\n\t\t\t\t\t\t\t\t\t\ttarget,\n\t\t\t\t\t\t\t\t\t\t`Follow-up for nested run ${request.targetRunId} (${control.currentAgent}):\\n\\n${request.message.trim()}`,\n\t\t\t\t\t\t\t\t\t\t500,\n\t\t\t\t\t\t\t\t\t\t{ source: \"nested-resume\", runId: request.targetRunId, agent: control.currentAgent, index },\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tmessage = ok\n\t\t\t\t\t\t\t\t\t\t? `Delivered follow-up to live nested run ${request.targetRunId}.`\n\t\t\t\t\t\t\t\t\t\t: `Nested child intercom target is not registered: ${target}`;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\tmessage = error instanceof Error ? error.message : String(error);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tresult = { ts: Date.now(), requestId: request.requestId, targetRunId: request.targetRunId, ok, message };\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\twriteNestedControlResult(route, result);\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tpendingResults.set(request.requestId, result);\n\t\t\t\t\t\t\tconsole.error(`Failed to write nested control result for request '${request.requestId}' targeting '${request.targetRunId}' via inbox '${route.controlInbox}'; keeping request for retry:`, error);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpendingResults.delete(request.requestId);\n\t\t\t\t\t\tseen.add(request.requestId);\n\t\t\t\t\t\ttry { fs.unlinkSync(request.filePath); } catch {}\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tinFlight.delete(request.requestId);\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(`Failed to poll nested control inbox '${route.controlInbox}' for root '${route.rootRunId}':`, error);\n\t\t}\n\t}, 200);\n\ttimer.unref?.();\n\treturn timer;\n}\n\nexport default function registerFanoutChildSubagentExtension(pi: ExtensionAPI): void {\n\tif (process.env[SUBAGENT_CHILD_ENV] !== \"1\" || process.env[SUBAGENT_FANOUT_CHILD_ENV] !== \"1\") return;\n\n\tconst globalStore = globalThis as Record<string, unknown>;\n\tconst registeredKey = \"__piSubagentFanoutChildRegisteredApis\";\n\tconst registeredApis = globalStore[registeredKey] instanceof WeakSet\n\t\t? globalStore[registeredKey] as WeakSet<ExtensionAPI>\n\t\t: new WeakSet<ExtensionAPI>();\n\tglobalStore[registeredKey] = registeredApis;\n\tif (registeredApis.has(pi)) return;\n\tregisteredApis.add(pi);\n\n\tconst config = normalizeAsyncLaunchConfig(loadConfig());\n\tconst state = createChildSafeState();\n\tconst executor = createSubagentExecutor({\n\t\tpi,\n\t\tstate,\n\t\tconfig,\n\t\twaitToolEnabled: resolveWaitToolConfig(config.waitTool).enabled,\n\t\ttempArtifactsDir: getArtifactsDir(null),\n\t\tgetSubagentSessionRoot,\n\t\texpandTilde,\n\t\tallowMutatingManagementActions: false,\n\t});\n\n\tconst tool: ToolDefinition<typeof SubagentParams, Details> = {\n\t\tname: \"subagent\",\n\t\tlabel: \"Subagent\",\n\t\tdescription: [\n\t\t\t\"Delegate to generic children from child-safe fanout mode.\",\n\t\t\t\"Execution uses { task, description, permissions? } (or tasks[] / chain[]).\",\n\t\t\t\"Allowed control actions: status, interrupt, resume, steer, append-step, doctor.\",\n\t\t].join(\"\\n\"),\n\t\tparameters: SubagentParams,\n\t\texecute(id, params, signal, onUpdate, ctx) {\n\t\t\treturn executor.execute(id, params as SubagentParamsLike, signal, onUpdate, ctx);\n\t\t},\n\t};\n\n\tpi.registerTool(tool);\n\tstartNestedControlInboxListener(pi, state);\n}\n"]}
@@ -5,6 +5,7 @@ import * as path from "node:path";
5
5
  import { getArtifactsDir } from "../shared/artifacts.js";
6
6
  import { createSubagentExecutor } from "../runs/foreground/subagent-executor.js";
7
7
  import { resolveWaitToolConfig } from "../runs/background/wait-config.js";
8
+ import { normalizeAsyncLaunchConfig } from "../runs/background/top-level-async.js";
8
9
  import { SUBAGENT_CHILD_ENV, SUBAGENT_FANOUT_CHILD_ENV } from "../runs/shared/pi-args.js";
9
10
  import { readNestedControlRequests, resolveNestedRouteFromEnv, writeNestedControlResult } from "../runs/shared/nested-events.js";
10
11
  import { deliverSubagentIntercomMessageEvent } from "../intercom/result-intercom.js";
@@ -142,13 +143,12 @@ export default function registerFanoutChildSubagentExtension(pi) {
142
143
  if (registeredApis.has(pi))
143
144
  return;
144
145
  registeredApis.add(pi);
145
- const config = loadConfig();
146
+ const config = normalizeAsyncLaunchConfig(loadConfig());
146
147
  const state = createChildSafeState();
147
148
  const executor = createSubagentExecutor({
148
149
  pi,
149
150
  state,
150
151
  config,
151
- asyncByDefault: config.asyncByDefault === true,
152
152
  waitToolEnabled: resolveWaitToolConfig(config.waitTool).enabled,
153
153
  tempArtifactsDir: getArtifactsDir(null),
154
154
  getSubagentSessionRoot,
@@ -1 +1 @@
1
- {"version":3,"file":"fanout-child.js","sourceRoot":"","sources":["../../../../../src/builtin-extensions/pi-subagents/src/extension/fanout-child.ts"],"names":[],"mappings":"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","sourcesContent":["// @ts-nocheck\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport type { ExtensionAPI, ToolDefinition } from \"@ashx-j/lunr\";\n\nimport { getArtifactsDir } from \"../shared/artifacts.ts\";\nimport { createSubagentExecutor, type SubagentParamsLike } from \"../runs/foreground/subagent-executor.ts\";\nimport { resolveWaitToolConfig } from \"../runs/background/wait-config.ts\";\nimport { SUBAGENT_CHILD_ENV, SUBAGENT_FANOUT_CHILD_ENV } from \"../runs/shared/pi-args.ts\";\nimport { readNestedControlRequests, resolveNestedRouteFromEnv, writeNestedControlResult } from \"../runs/shared/nested-events.ts\";\nimport { deliverSubagentIntercomMessageEvent } from \"../intercom/result-intercom.ts\";\nimport { resolveSubagentIntercomTarget } from \"../intercom/intercom-bridge.ts\";\nimport { SubagentParams } from \"./schemas.ts\";\nimport { loadConfig } from \"./config.ts\";\nimport { type Details, type SubagentState } from \"../shared/types.ts\";\n\nfunction getSubagentSessionRoot(parentSessionFile: string | null): string {\n\tif (parentSessionFile) {\n\t\tconst baseName = path.basename(parentSessionFile, \".jsonl\");\n\t\tconst sessionsDir = path.dirname(parentSessionFile);\n\t\treturn path.join(sessionsDir, baseName);\n\t}\n\treturn fs.mkdtempSync(path.join(os.tmpdir(), \"pi-subagent-session-\"));\n}\n\nfunction expandTilde(p: string): string {\n\treturn p.startsWith(\"~/\") ? path.join(os.homedir(), p.slice(2)) : p;\n}\n\nfunction createChildSafeState(): SubagentState {\n\treturn {\n\t\tbaseCwd: \"\",\n\t\tcurrentSessionId: null,\n\t\tforegroundSubagentInFlight: 0,\n\t\tsubagentSpawns: { sessionId: null, count: 0 },\n\t\tasyncJobs: new Map(),\n\t\tforegroundRuns: new Map(),\n\t\tforegroundControls: new Map(),\n\t\tlastForegroundControlId: null,\n\t\tpendingForegroundControlNotices: new Map(),\n\t\tcleanupTimers: new Map(),\n\t\tlastUiContext: null,\n\t\tpoller: null,\n\t\tcompletionSeen: new Map(),\n\t\twatcher: null,\n\t\twatcherRestartTimer: null,\n\t\tresultFileCoalescer: {\n\t\t\tschedule: () => false,\n\t\t\tclear: () => {},\n\t\t},\n\t};\n}\n\nfunction startNestedControlInboxListener(pi: ExtensionAPI, state: SubagentState): NodeJS.Timeout | undefined {\n\tlet route;\n\ttry {\n\t\troute = resolveNestedRouteFromEnv();\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (!route) return undefined;\n\tconst seen = new Set<string>();\n\tconst inFlight = new Set<string>();\n\tconst pendingResults = new Map<string, Parameters<typeof writeNestedControlResult>[1]>();\n\tconst timer = setInterval(() => {\n\t\ttry {\n\t\t\tfor (const request of readNestedControlRequests(route)) {\n\t\t\t\tif (seen.has(request.requestId) || inFlight.has(request.requestId)) continue;\n\t\t\t\tinFlight.add(request.requestId);\n\t\t\t\tvoid (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tlet result = pendingResults.get(request.requestId);\n\t\t\t\t\t\tif (!result) {\n\t\t\t\t\t\t\tlet ok = false;\n\t\t\t\t\t\t\tlet message = \"Control request failed.\";\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tconst control = state.foregroundControls.get(request.targetRunId);\n\t\t\t\t\t\t\t\tif (!control) {\n\t\t\t\t\t\t\t\t\tmessage = `Nested run ${request.targetRunId} is not active in this fanout child.`;\n\t\t\t\t\t\t\t\t} else if (request.action === \"interrupt\") {\n\t\t\t\t\t\t\t\t\tok = control.interrupt?.() === true;\n\t\t\t\t\t\t\t\t\tmessage = ok\n\t\t\t\t\t\t\t\t\t\t? `Interrupt requested for nested run ${request.targetRunId}.`\n\t\t\t\t\t\t\t\t\t\t: `Nested run ${request.targetRunId} has no active child step to interrupt.`;\n\t\t\t\t\t\t\t\t} else if (!request.message?.trim()) {\n\t\t\t\t\t\t\t\t\tmessage = \"Nested resume requires message.\";\n\t\t\t\t\t\t\t\t} else if (!control.currentChildId && !control.currentAgent) {\n\t\t\t\t\t\t\t\t\tmessage = `Nested run ${request.targetRunId} has no active child message route.`;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tconst index = control.currentIndex ?? 0;\n\t\t\t\t\t\t\t\t\tconst childId = control.currentChildId ?? control.currentAgent!;\n\t\t\t\t\t\t\t\t\tconst target = resolveSubagentIntercomTarget(request.targetRunId, childId, index);\n\t\t\t\t\t\t\t\t\tok = await deliverSubagentIntercomMessageEvent(\n\t\t\t\t\t\t\t\t\t\tpi.events,\n\t\t\t\t\t\t\t\t\t\ttarget,\n\t\t\t\t\t\t\t\t\t\t`Follow-up for nested run ${request.targetRunId} (${control.currentAgent}):\\n\\n${request.message.trim()}`,\n\t\t\t\t\t\t\t\t\t\t500,\n\t\t\t\t\t\t\t\t\t\t{ source: \"nested-resume\", runId: request.targetRunId, agent: control.currentAgent, index },\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tmessage = ok\n\t\t\t\t\t\t\t\t\t\t? `Delivered follow-up to live nested run ${request.targetRunId}.`\n\t\t\t\t\t\t\t\t\t\t: `Nested child intercom target is not registered: ${target}`;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\tmessage = error instanceof Error ? error.message : String(error);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tresult = { ts: Date.now(), requestId: request.requestId, targetRunId: request.targetRunId, ok, message };\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\twriteNestedControlResult(route, result);\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tpendingResults.set(request.requestId, result);\n\t\t\t\t\t\t\tconsole.error(`Failed to write nested control result for request '${request.requestId}' targeting '${request.targetRunId}' via inbox '${route.controlInbox}'; keeping request for retry:`, error);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpendingResults.delete(request.requestId);\n\t\t\t\t\t\tseen.add(request.requestId);\n\t\t\t\t\t\ttry { fs.unlinkSync(request.filePath); } catch {}\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tinFlight.delete(request.requestId);\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(`Failed to poll nested control inbox '${route.controlInbox}' for root '${route.rootRunId}':`, error);\n\t\t}\n\t}, 200);\n\ttimer.unref?.();\n\treturn timer;\n}\n\nexport default function registerFanoutChildSubagentExtension(pi: ExtensionAPI): void {\n\tif (process.env[SUBAGENT_CHILD_ENV] !== \"1\" || process.env[SUBAGENT_FANOUT_CHILD_ENV] !== \"1\") return;\n\n\tconst globalStore = globalThis as Record<string, unknown>;\n\tconst registeredKey = \"__piSubagentFanoutChildRegisteredApis\";\n\tconst registeredApis = globalStore[registeredKey] instanceof WeakSet\n\t\t? globalStore[registeredKey] as WeakSet<ExtensionAPI>\n\t\t: new WeakSet<ExtensionAPI>();\n\tglobalStore[registeredKey] = registeredApis;\n\tif (registeredApis.has(pi)) return;\n\tregisteredApis.add(pi);\n\n\tconst config = loadConfig();\n\tconst state = createChildSafeState();\n\tconst executor = createSubagentExecutor({\n\t\tpi,\n\t\tstate,\n\t\tconfig,\n\t\tasyncByDefault: config.asyncByDefault === true,\n\t\twaitToolEnabled: resolveWaitToolConfig(config.waitTool).enabled,\n\t\ttempArtifactsDir: getArtifactsDir(null),\n\t\tgetSubagentSessionRoot,\n\t\texpandTilde,\n\t\tallowMutatingManagementActions: false,\n\t});\n\n\tconst tool: ToolDefinition<typeof SubagentParams, Details> = {\n\t\tname: \"subagent\",\n\t\tlabel: \"Subagent\",\n\t\tdescription: [\n\t\t\t\"Delegate to generic children from child-safe fanout mode.\",\n\t\t\t\"Execution uses { task, description, permissions? } (or tasks[] / chain[]).\",\n\t\t\t\"Allowed control actions: status, interrupt, resume, steer, append-step, doctor.\",\n\t\t].join(\"\\n\"),\n\t\tparameters: SubagentParams,\n\t\texecute(id, params, signal, onUpdate, ctx) {\n\t\t\treturn executor.execute(id, params as SubagentParamsLike, signal, onUpdate, ctx);\n\t\t},\n\t};\n\n\tpi.registerTool(tool);\n\tstartNestedControlInboxListener(pi, state);\n}\n"]}
1
+ {"version":3,"file":"fanout-child.js","sourceRoot":"","sources":["../../../../../src/builtin-extensions/pi-subagents/src/extension/fanout-child.ts"],"names":[],"mappings":"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","sourcesContent":["// @ts-nocheck\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport type { ExtensionAPI, ToolDefinition } from \"@ashx-j/lunr\";\n\nimport { getArtifactsDir } from \"../shared/artifacts.ts\";\nimport { createSubagentExecutor, type SubagentParamsLike } from \"../runs/foreground/subagent-executor.ts\";\nimport { resolveWaitToolConfig } from \"../runs/background/wait-config.ts\";\nimport { normalizeAsyncLaunchConfig } from \"../runs/background/top-level-async.ts\";\nimport { SUBAGENT_CHILD_ENV, SUBAGENT_FANOUT_CHILD_ENV } from \"../runs/shared/pi-args.ts\";\nimport { readNestedControlRequests, resolveNestedRouteFromEnv, writeNestedControlResult } from \"../runs/shared/nested-events.ts\";\nimport { deliverSubagentIntercomMessageEvent } from \"../intercom/result-intercom.ts\";\nimport { resolveSubagentIntercomTarget } from \"../intercom/intercom-bridge.ts\";\nimport { SubagentParams } from \"./schemas.ts\";\nimport { loadConfig } from \"./config.ts\";\nimport { type Details, type SubagentState } from \"../shared/types.ts\";\n\nfunction getSubagentSessionRoot(parentSessionFile: string | null): string {\n\tif (parentSessionFile) {\n\t\tconst baseName = path.basename(parentSessionFile, \".jsonl\");\n\t\tconst sessionsDir = path.dirname(parentSessionFile);\n\t\treturn path.join(sessionsDir, baseName);\n\t}\n\treturn fs.mkdtempSync(path.join(os.tmpdir(), \"pi-subagent-session-\"));\n}\n\nfunction expandTilde(p: string): string {\n\treturn p.startsWith(\"~/\") ? path.join(os.homedir(), p.slice(2)) : p;\n}\n\nfunction createChildSafeState(): SubagentState {\n\treturn {\n\t\tbaseCwd: \"\",\n\t\tcurrentSessionId: null,\n\t\tforegroundSubagentInFlight: 0,\n\t\tsubagentSpawns: { sessionId: null, count: 0 },\n\t\tasyncJobs: new Map(),\n\t\tforegroundRuns: new Map(),\n\t\tforegroundControls: new Map(),\n\t\tlastForegroundControlId: null,\n\t\tpendingForegroundControlNotices: new Map(),\n\t\tcleanupTimers: new Map(),\n\t\tlastUiContext: null,\n\t\tpoller: null,\n\t\tcompletionSeen: new Map(),\n\t\twatcher: null,\n\t\twatcherRestartTimer: null,\n\t\tresultFileCoalescer: {\n\t\t\tschedule: () => false,\n\t\t\tclear: () => {},\n\t\t},\n\t};\n}\n\nfunction startNestedControlInboxListener(pi: ExtensionAPI, state: SubagentState): NodeJS.Timeout | undefined {\n\tlet route;\n\ttry {\n\t\troute = resolveNestedRouteFromEnv();\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (!route) return undefined;\n\tconst seen = new Set<string>();\n\tconst inFlight = new Set<string>();\n\tconst pendingResults = new Map<string, Parameters<typeof writeNestedControlResult>[1]>();\n\tconst timer = setInterval(() => {\n\t\ttry {\n\t\t\tfor (const request of readNestedControlRequests(route)) {\n\t\t\t\tif (seen.has(request.requestId) || inFlight.has(request.requestId)) continue;\n\t\t\t\tinFlight.add(request.requestId);\n\t\t\t\tvoid (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tlet result = pendingResults.get(request.requestId);\n\t\t\t\t\t\tif (!result) {\n\t\t\t\t\t\t\tlet ok = false;\n\t\t\t\t\t\t\tlet message = \"Control request failed.\";\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tconst control = state.foregroundControls.get(request.targetRunId);\n\t\t\t\t\t\t\t\tif (!control) {\n\t\t\t\t\t\t\t\t\tmessage = `Nested run ${request.targetRunId} is not active in this fanout child.`;\n\t\t\t\t\t\t\t\t} else if (request.action === \"interrupt\") {\n\t\t\t\t\t\t\t\t\tok = control.interrupt?.() === true;\n\t\t\t\t\t\t\t\t\tmessage = ok\n\t\t\t\t\t\t\t\t\t\t? `Interrupt requested for nested run ${request.targetRunId}.`\n\t\t\t\t\t\t\t\t\t\t: `Nested run ${request.targetRunId} has no active child step to interrupt.`;\n\t\t\t\t\t\t\t\t} else if (!request.message?.trim()) {\n\t\t\t\t\t\t\t\t\tmessage = \"Nested resume requires message.\";\n\t\t\t\t\t\t\t\t} else if (!control.currentChildId && !control.currentAgent) {\n\t\t\t\t\t\t\t\t\tmessage = `Nested run ${request.targetRunId} has no active child message route.`;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tconst index = control.currentIndex ?? 0;\n\t\t\t\t\t\t\t\t\tconst childId = control.currentChildId ?? control.currentAgent!;\n\t\t\t\t\t\t\t\t\tconst target = resolveSubagentIntercomTarget(request.targetRunId, childId, index);\n\t\t\t\t\t\t\t\t\tok = await deliverSubagentIntercomMessageEvent(\n\t\t\t\t\t\t\t\t\t\tpi.events,\n\t\t\t\t\t\t\t\t\t\ttarget,\n\t\t\t\t\t\t\t\t\t\t`Follow-up for nested run ${request.targetRunId} (${control.currentAgent}):\\n\\n${request.message.trim()}`,\n\t\t\t\t\t\t\t\t\t\t500,\n\t\t\t\t\t\t\t\t\t\t{ source: \"nested-resume\", runId: request.targetRunId, agent: control.currentAgent, index },\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tmessage = ok\n\t\t\t\t\t\t\t\t\t\t? `Delivered follow-up to live nested run ${request.targetRunId}.`\n\t\t\t\t\t\t\t\t\t\t: `Nested child intercom target is not registered: ${target}`;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\tmessage = error instanceof Error ? error.message : String(error);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tresult = { ts: Date.now(), requestId: request.requestId, targetRunId: request.targetRunId, ok, message };\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\twriteNestedControlResult(route, result);\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tpendingResults.set(request.requestId, result);\n\t\t\t\t\t\t\tconsole.error(`Failed to write nested control result for request '${request.requestId}' targeting '${request.targetRunId}' via inbox '${route.controlInbox}'; keeping request for retry:`, error);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpendingResults.delete(request.requestId);\n\t\t\t\t\t\tseen.add(request.requestId);\n\t\t\t\t\t\ttry { fs.unlinkSync(request.filePath); } catch {}\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tinFlight.delete(request.requestId);\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(`Failed to poll nested control inbox '${route.controlInbox}' for root '${route.rootRunId}':`, error);\n\t\t}\n\t}, 200);\n\ttimer.unref?.();\n\treturn timer;\n}\n\nexport default function registerFanoutChildSubagentExtension(pi: ExtensionAPI): void {\n\tif (process.env[SUBAGENT_CHILD_ENV] !== \"1\" || process.env[SUBAGENT_FANOUT_CHILD_ENV] !== \"1\") return;\n\n\tconst globalStore = globalThis as Record<string, unknown>;\n\tconst registeredKey = \"__piSubagentFanoutChildRegisteredApis\";\n\tconst registeredApis = globalStore[registeredKey] instanceof WeakSet\n\t\t? globalStore[registeredKey] as WeakSet<ExtensionAPI>\n\t\t: new WeakSet<ExtensionAPI>();\n\tglobalStore[registeredKey] = registeredApis;\n\tif (registeredApis.has(pi)) return;\n\tregisteredApis.add(pi);\n\n\tconst config = normalizeAsyncLaunchConfig(loadConfig());\n\tconst state = createChildSafeState();\n\tconst executor = createSubagentExecutor({\n\t\tpi,\n\t\tstate,\n\t\tconfig,\n\t\twaitToolEnabled: resolveWaitToolConfig(config.waitTool).enabled,\n\t\ttempArtifactsDir: getArtifactsDir(null),\n\t\tgetSubagentSessionRoot,\n\t\texpandTilde,\n\t\tallowMutatingManagementActions: false,\n\t});\n\n\tconst tool: ToolDefinition<typeof SubagentParams, Details> = {\n\t\tname: \"subagent\",\n\t\tlabel: \"Subagent\",\n\t\tdescription: [\n\t\t\t\"Delegate to generic children from child-safe fanout mode.\",\n\t\t\t\"Execution uses { task, description, permissions? } (or tasks[] / chain[]).\",\n\t\t\t\"Allowed control actions: status, interrupt, resume, steer, append-step, doctor.\",\n\t\t].join(\"\\n\"),\n\t\tparameters: SubagentParams,\n\t\texecute(id, params, signal, onUpdate, ctx) {\n\t\t\treturn executor.execute(id, params as SubagentParamsLike, signal, onUpdate, ctx);\n\t\t},\n\t};\n\n\tpi.registerTool(tool);\n\tstartNestedControlInboxListener(pi, state);\n}\n"]}
@@ -1,20 +1,21 @@
1
1
  /**
2
2
  * Subagent Tool
3
3
  *
4
- * Full-featured subagent with sync and async modes.
5
- * - Sync (default): Streams output, renders markdown, tracks usage
6
- * - Async: Background execution, emits events when done
4
+ * Full-featured subagent with foreground and async modes.
5
+ * - Async (default): Background execution, emits events when done
6
+ * - Foreground (async:false or clarify:true): Streams output, renders markdown, tracks usage
7
7
  *
8
8
  * Modes: single (agent + task), parallel (tasks[]), chain (chain[] with {previous})
9
- * Toggle: async parameter (default: false, configurable via config.json)
10
9
  *
11
10
  * Config file: ~/.lunr/agent/extensions/subagent/config.json
12
- * { "asyncByDefault": true, "forceTopLevelAsync": true, "maxSubagentDepth": 1, "intercomBridge": { "mode": "always", "instructionFile": "./intercom-bridge.md" }, "worktreeSetupHook": "./scripts/setup-worktree.mjs" }
11
+ * { "maxSubagentDepth": 1, "intercomBridge": { "mode": "always", "instructionFile": "./intercom-bridge.md" }, "worktreeSetupHook": "./scripts/setup-worktree.mjs" }
13
12
  */
14
13
  import type { ExtensionAPI } from "@ashx-j/lunr";
15
14
  import { Text } from "@ashx-j/lunr-tui";
15
+ import { type SubagentState } from "../shared/types.ts";
16
16
  export { loadConfig } from "./config.ts";
17
- export declare function renderSubagentCall(args: any, theme: any, context: any, options: any): Text;
17
+ export declare function resolveSubagentActionDisplayTitle(args: any, state: SubagentState): string | undefined;
18
+ export declare function renderSubagentCall(rawArgs: any, theme: any, context: any, options: any): Text;
18
19
  export declare function renderSubagentNotify(details: any, theme: any): Text;
19
20
  export default function registerSubagentExtension(pi: ExtensionAPI): void;
20
21
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/builtin-extensions/pi-subagents/src/extension/index.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;GAYG;AAQH,OAAO,KAAK,EAAE,YAAY,EAAoC,MAAM,iCAAiC,CAAC;AAEtG,OAAO,EAA0B,IAAI,EAAmE,MAAM,wBAAwB,CAAC;AAmDvI,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA+EzC,wBAAgB,kBAAkB,CAAC,IAAI,KAAA,EAAE,KAAK,KAAA,EAAE,OAAO,KAAA,EAAE,OAAO,KAAA,QA+B/D;AAED,wBAAgB,oBAAoB,CAAC,OAAO,KAAA,EAAE,KAAK,KAAA,QASlD;AA8FD,MAAM,CAAC,OAAO,UAAU,yBAAyB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA2bxE","sourcesContent":["// @ts-nocheck\n/**\n * Subagent Tool\n *\n * Full-featured subagent with sync and async modes.\n * - Sync (default): Streams output, renders markdown, tracks usage\n * - Async: Background execution, emits events when done\n *\n * Modes: single (agent + task), parallel (tasks[]), chain (chain[] with {previous})\n * Toggle: async parameter (default: false, configurable via config.json)\n *\n * Config file: ~/.lunr/agent/extensions/subagent/config.json\n * { \"asyncByDefault\": true, \"forceTopLevelAsync\": true, \"maxSubagentDepth\": 1, \"intercomBridge\": { \"mode\": \"always\", \"instructionFile\": \"./intercom-bridge.md\" }, \"worktreeSetupHook\": \"./scripts/setup-worktree.mjs\" }\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport { createSubagentCancellation, registerSubagentCancellation } from \"../../../../core/subagent-cancellation.ts\";\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport type { AgentToolResult } from \"@ashx-j/lunr-agent\";\nimport type { ExtensionAPI, ExtensionContext, ToolDefinition } from \"@ashx-j/lunr\";\nimport { awaitWithAbort } from \"../../../../utils/await-with-abort.ts\";\nimport { Box, Container, Spacer, Text, truncateToWidth, visibleWidth, wrapTextWithAnsi, type Component } from \"@ashx-j/lunr-tui\";\n\nimport { cleanupAllArtifactDirs, cleanupOldArtifacts, getArtifactsDir } from \"../shared/artifacts.ts\";\nimport { resolveCurrentSessionId } from \"../shared/session-identity.ts\";\nimport { cleanupOldChainDirs } from \"../shared/settings.ts\";\nimport { clearLegacyResultAnimationTimer, disposeSubagentWidget, renderSubagentResult, subagentAnimSink } from \"../tui/render.ts\";\nimport { SubagentParams } from \"./schemas.ts\";\nimport { validateChainInput } from \"./chain-validation.ts\";\nimport type { createSubagentExecutor, SubagentParamsLike } from \"../runs/foreground/subagent-executor.ts\";\nimport { createAsyncJobTracker } from \"../runs/background/async-job-tracker.ts\";\nimport { createResultWatcher } from \"../runs/background/result-watcher.ts\";\nimport { createScheduledRunManager } from \"../runs/background/scheduled-runs.ts\";\nimport { registerSlashCommands } from \"../slash/slash-commands.ts\";\nimport { registerPromptTemplateDelegationBridge } from \"../slash/prompt-template-bridge.ts\";\nimport { registerMainWatchdog } from \"../watchdog/register-main.ts\";\nimport { registerSlashSubagentBridge } from \"../slash/slash-bridge.ts\";\nimport { createNativeSupervisorChannel } from \"../intercom/native-supervisor-channel.ts\";\nimport { registerSubagentRpcBridge } from \"./rpc.ts\";\nimport { clearSlashSnapshots, getSlashRenderableSnapshot, resolveSlashMessageDetails, restoreSlashFinalSnapshots, type SlashMessageDetails } from \"../slash/slash-live-state.ts\";\nimport { inspectSubagentStatus } from \"../runs/background/run-status.ts\";\nimport { resolveWaitToolConfig } from \"../runs/background/subagent-wait.ts\";\nimport { registerWaitTool } from \"../runs/background/wait-tool.ts\";\nimport { drainOutstandingWork } from \"../runs/background/auto-drain.ts\";\nimport registerSubagentNotify, { parseSubagentNotifyContent, type SubagentNotifyDetails } from \"../runs/background/notify.ts\";\nimport { formatSteeringNotice, handleSubagentSteeringNotice, SUBAGENT_STEERING_MESSAGE_TYPE, type SubagentSteeringMessageDetails } from \"./steering-notices.ts\";\nimport { SUBAGENT_CHILD_ENV, SUBAGENT_PARENT_SESSION_ENV } from \"../runs/shared/pi-args.ts\";\n\nimport { loadConfig } from \"./config.ts\";\nimport { buildSubagentToolDescription } from \"./tool-description.ts\";\nimport {\n\ttype Details,\n\ttype SubagentState,\n\tASYNC_DIR,\n\tDEFAULT_ARTIFACT_CONFIG,\n\tRESULTS_DIR,\n\tSLASH_RESULT_TYPE,\n\tSLASH_TEXT_RESULT_TYPE,\n\tSUBAGENT_ASYNC_COMPLETE_EVENT,\n\tSUBAGENT_ASYNC_STARTED_EVENT,\n\tSUBAGENT_CONTROL_EVENT,\n\tSUBAGENT_STEERING_NOTICE_EVENT,\n\tWIDGET_KEY,\n} from \"../shared/types.ts\";\nimport {\n\tclearPendingForegroundControlNotices,\n\tformatSubagentControlNotice,\n\thandleSubagentControlNotice,\n\tSUBAGENT_CONTROL_MESSAGE_TYPE,\n\ttype SubagentControlMessageDetails,\n} from \"./control-notices.ts\";\n\nexport { loadConfig } from \"./config.ts\";\n\n/**\n * Derive subagent session base directory from parent session file.\n * If parent session is ~/.lunr/agent/sessions/abc123.jsonl,\n * returns ~/.lunr/agent/sessions/abc123/ as the base.\n * Callers add runId to create the actual session root: abc123/{runId}/\n * Falls back to a unique temp directory if no parent session.\n */\nfunction getSubagentSessionRoot(parentSessionFile: string | null): string {\n\tif (parentSessionFile) {\n\t\tconst baseName = path.basename(parentSessionFile, \".jsonl\");\n\t\tconst sessionsDir = path.dirname(parentSessionFile);\n\t\treturn path.join(sessionsDir, baseName);\n\t}\n\treturn fs.mkdtempSync(path.join(os.tmpdir(), \"pi-subagent-session-\"));\n}\n\nfunction expandTilde(p: string): string {\n\treturn p.startsWith(\"~/\") ? path.join(os.homedir(), p.slice(2)) : p;\n}\n\n/**\n * Create a directory and verify it is actually accessible.\n * On Windows with Azure AD/Entra ID, directories created shortly after\n * wake-from-sleep can end up with broken NTFS ACLs (null DACL) when the\n * cloud SID cannot be resolved without network connectivity. This leaves\n * the directory completely inaccessible to the creating user.\n */\nfunction ensureAccessibleDir(dirPath: string): void {\n\tfs.mkdirSync(dirPath, { recursive: true });\n\ttry {\n\t\tfs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);\n\t} catch {\n\t\ttry {\n\t\t\tfs.rmSync(dirPath, { recursive: true, force: true });\n\t\t} catch {\n\t\t\t// Best effort: retry mkdir/access even if cleanup fails.\n\t\t}\n\t\tfs.mkdirSync(dirPath, { recursive: true });\n\t\tfs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);\n\t}\n}\n\nfunction isSlashResultRunning(result: { details?: Details }): boolean {\n\treturn result.details?.progress?.some((entry) => entry.status === \"running\")\n\t\t|| result.details?.results.some((entry) => entry.progress?.status === \"running\")\n\t\t|| false;\n}\n\n// Drives the inline running-indicator braille animation for foreground subagent\n// results. Foreground runs receive progress only on child events, so the glyph\n// (derived from progress fields) would freeze between events. While a result is\n// running we tick a frame counter every 80ms; renderResult registers the\n// running-glyph lines in state.animEntries (via subagentAnimSink) and the timer\n// rewrites just those Text nodes + requests a repaint, instead of invalidate()-ing\n// the whole tool component (a full renderSubagentResult rebuild 12.5x/s made the\n// spinner laggy).\nfunction subagentResultIsRunning(result: { details?: Details }): boolean {\n\treturn result.details?.progress?.some((entry) => entry.status === \"running\")\n\t\t|| result.details?.results.some((entry) => entry.progress?.status === \"running\")\n\t\t|| false;\n}\n\nfunction effectiveParallelTaskCount(tasks: Array<{ count?: unknown }> | undefined): number {\n\tif (!tasks || tasks.length === 0) return 0;\n\treturn tasks.reduce((total, task) => {\n\t\tconst count = typeof task.count === \"number\" && Number.isInteger(task.count) && task.count >= 1 ? task.count : 1;\n\t\treturn total + count;\n\t}, 0);\n}\n\nfunction callIsAsync(args, asyncByDefault) {\n\tif (args?.clarify === true) return false;\n\tif (args?.async === true) return true;\n\tif (args?.async === false) return false;\n\treturn asyncByDefault === true;\n}\n\nexport function renderSubagentCall(args, theme, context, options) {\n\tif (args.action) {\n\t\tconst target = args.id || args.runId || \"\";\n\t\treturn new Text(\n\t\t\t`${theme.fg(\"toolTitle\", theme.bold(\"subagent \"))}${args.action}${target ? ` ${theme.fg(\"accent\", target)}` : \"\"}`,\n\t\t\t0,\n\t\t\t0,\n\t\t);\n\t}\n\tconst isParallel = (args.tasks?.length ?? 0) > 0;\n\tconst parallelCount = effectiveParallelTaskCount(args.tasks as Array<{ count?: unknown }> | undefined);\n\tconst asyncMark = callIsAsync(args, options?.asyncByDefault) ? `${theme.fg(\"warning\", \"async\")} ` : \"\";\n\tif (args.chain?.length) {\n\t\treturn new Text(\n\t\t\t`${theme.fg(\"toolTitle\", theme.bold(\"subagent \"))}${asyncMark}chain (${args.chain.length})`,\n\t\t\t0,\n\t\t\t0,\n\t\t);\n\t}\n\tif (isParallel) {\n\t\treturn new Text(\n\t\t\t`${theme.fg(\"toolTitle\", theme.bold(\"subagent \"))}${asyncMark}parallel (${parallelCount})`,\n\t\t\t0,\n\t\t\t0,\n\t\t);\n\t}\n\tconst showDescription = !context?.result && !context?.isPartial && !context?.expanded && !context?.isError;\n\tconst description = showDescription ? theme.fg(\"accent\", args.description || args.task || \"?\") : \"\";\n\tconst title = theme.fg(\"toolTitle\", theme.bold(\"subagent\"));\n\tconst rest = [asyncMark.trim() ? theme.fg(\"warning\", \"async\") : \"\", description].filter(Boolean).join(\" \");\n\treturn new Text(rest ? `${title} ${rest}` : title, 0, 0);\n}\n\nexport function renderSubagentNotify(details, theme) {\n\tconst status = typeof details?.status === \"string\" ? details.status : \"\";\n\tconst icon = status === \"completed\"\n\t\t? theme.fg(\"success\", \"✓\")\n\t\t: status === \"failed\"\n\t\t\t? theme.fg(\"error\", \"✗\")\n\t\t\t: theme.fg(\"warning\", \"■\");\n\tconst title = details?.agent || \"child\";\n\treturn new Text(`${icon} ${theme.bold(title)} ${theme.fg(\"dim\", status)}`, 0, 0);\n}\n\nfunction ensureSubagentResultAnimation(context: { state: Record<string, unknown>; invalidate?: () => void; requestRender?: () => void }): void {\n\tconst state = context.state as {\n\t\tsubagentResultAnimationTimer?: ReturnType<typeof setInterval>;\n\t\tframe?: number;\n\t\tanimEntries?: Array<{ text: Text; line: (frame: number, now: number) => string }>;\n\t};\n\tif (state.subagentResultAnimationTimer) return;\n\tif (typeof context.invalidate !== \"function\") return;\n\tif (state.frame === undefined) state.frame = 0;\n\tstate.subagentResultAnimationTimer = setInterval(() => {\n\t\tstate.frame = ((state.frame ?? 0) + 1) % 10;\n\t\ttry {\n\t\t\tconst entries = state.animEntries;\n\t\t\tif (entries?.length && typeof context.requestRender === \"function\") {\n\t\t\t\tconst now = Date.now();\n\t\t\t\tfor (const entry of entries) entry.text.setText(entry.line(state.frame ?? 0, now));\n\t\t\t\tcontext.requestRender();\n\t\t\t} else {\n\t\t\t\tcontext.invalidate();\n\t\t\t}\n\t\t} catch {}\n\t}, 80);\n}\n\nfunction isSlashResultError(result: { details?: Details }): boolean {\n\treturn result.details?.results.some((entry) => entry.exitCode !== 0 && entry.progress?.status !== \"running\") || false;\n}\n\nfunction isStaleExtensionContextError(error: unknown): boolean {\n\treturn error instanceof Error && error.message.includes(\"Extension context no longer active\");\n}\n\nfunction rebuildSlashResultContainer(\n\tcontainer: Container,\n\tresult: AgentToolResult<Details>,\n\toptions: { expanded: boolean },\n\ttheme: ExtensionContext[\"ui\"][\"theme\"],\n): void {\n\tcontainer.clear();\n\tcontainer.addChild(new Spacer(1));\n\tconst boxTheme = isSlashResultRunning(result) ? \"toolPendingBg\" : isSlashResultError(result) ? \"toolErrorBg\" : \"toolSuccessBg\";\n\tconst box = new Box(1, 1, (text: string) => theme.bg(boxTheme, text));\n\tbox.addChild(renderSubagentResult(result, options, theme));\n\tcontainer.addChild(box);\n}\n\nfunction createSlashResultComponent(\n\tdetails: SlashMessageDetails,\n\toptions: { expanded: boolean },\n\ttheme: ExtensionContext[\"ui\"][\"theme\"],\n): Container {\n\tconst container = new Container();\n\tlet lastVersion = -1;\n\tcontainer.render = (width: number): string[] => {\n\t\tconst snapshot = getSlashRenderableSnapshot(details);\n\t\tif (snapshot.version !== lastVersion || isSlashResultRunning(snapshot.result)) {\n\t\t\tlastVersion = snapshot.version;\n\t\t\trebuildSlashResultContainer(container, snapshot.result, options, theme);\n\t\t}\n\t\treturn Container.prototype.render.call(container, width);\n\t};\n\treturn container;\n}\n\nclass SubagentControlNoticeComponent implements Component {\n\tconstructor(\n\t\tprivate readonly details: SubagentControlMessageDetails,\n\t\tprivate readonly theme: ExtensionContext[\"ui\"][\"theme\"],\n\t) {}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst eventLabel = this.details.event.type.replaceAll(\"_\", \" \");\n\t\tif (width < 3) return [truncateToWidth(`Subagent ${eventLabel}`, width)];\n\t\tconst bodyWidth = Math.max(1, width - 2);\n\t\tconst borderChar = \"─\";\n\t\tconst header = ` ⚠ Subagent ${eventLabel}: ${this.details.event.agent} `;\n\t\tconst headerText = truncateToWidth(header, bodyWidth, \"\");\n\t\tconst headerPadding = Math.max(0, bodyWidth - visibleWidth(headerText));\n\t\tconst lines = [this.theme.fg(\"accent\", `╭${headerText}${borderChar.repeat(headerPadding)}╮`)];\n\n\t\tfor (const line of wrapTextWithAnsi(formatSubagentControlNotice(this.details), bodyWidth)) {\n\t\t\tconst text = truncateToWidth(line, bodyWidth, \"\");\n\t\t\tconst padding = Math.max(0, bodyWidth - visibleWidth(text));\n\t\t\tlines.push(this.theme.fg(\"accent\", `│${text}${\" \".repeat(padding)}│`));\n\t\t}\n\t\tlines.push(this.theme.fg(\"accent\", `╰${borderChar.repeat(bodyWidth)}╯`));\n\t\treturn lines;\n\t}\n}\n\nexport default function registerSubagentExtension(pi: ExtensionAPI): void {\n\tif (process.env[SUBAGENT_CHILD_ENV] === \"1\") {\n\t\treturn;\n\t}\n\tconst globalStore = globalThis as Record<string, unknown>;\n\tconst runtimeCleanupStoreKey = \"__piSubagentRuntimeCleanup\";\n\tconst previousRuntimeCleanup = globalStore[runtimeCleanupStoreKey];\n\tif (typeof previousRuntimeCleanup === \"function\") {\n\t\ttry {\n\t\t\tpreviousRuntimeCleanup();\n\t\t} catch {\n\t\t\t// Best effort cleanup for stale timers from an older reload.\n\t\t}\n\t}\n\n\tensureAccessibleDir(RESULTS_DIR);\n\tensureAccessibleDir(ASYNC_DIR);\n\tcleanupOldChainDirs();\n\n\tconst config = loadConfig();\n\tconst waitToolConfig = resolveWaitToolConfig(config.waitTool);\n\tconst asyncByDefault = config.asyncByDefault === true;\n\tconst tempArtifactsDir = getArtifactsDir(null);\n\tcleanupAllArtifactDirs(DEFAULT_ARTIFACT_CONFIG.cleanupDays);\n\n\tconst state: SubagentState = {\n\t\tbaseCwd: \"\",\n\t\tcurrentSessionId: null,\n\t\tsessionGeneration: 0,\n\t\tforegroundSubagentInFlight: 0,\n\t\tsubagentSpawns: { sessionId: null, count: 0 },\n\t\tasyncJobs: new Map(),\n\t\tfleetJobs: new Map(),\n\t\tforegroundRuns: new Map(),\n\t\tforegroundControls: new Map(),\n\t\tlastForegroundControlId: null,\n\t\tpendingForegroundControlNotices: new Map(),\n\t\tcleanupTimers: new Map(),\n\t\tlastUiContext: null,\n\t\tpoller: null,\n\t\tcompletionSeen: new Map(),\n\t\twatcher: null,\n\t\twatcherRestartTimer: null,\n\t\tresultFileCoalescer: {\n\t\t\tschedule: () => false,\n\t\t\tclear: () => {},\n\t\t},\n\t};\n\n\tconst supervisorChannel = createNativeSupervisorChannel(pi, state);\n\tconst mainWatchdog = registerMainWatchdog(pi);\n\tconst { startResultWatcher, primeExistingResults, stopResultWatcher } = createResultWatcher(\n\t\tpi,\n\t\tstate,\n\t\tRESULTS_DIR,\n\t\t10 * 60 * 1000,\n\t);\n\tstartResultWatcher();\n\tprimeExistingResults();\n\n\tlet generation = 0;\n\tlet executorPromise: Promise<ReturnType<typeof createSubagentExecutor>> | undefined;\n\tconst pendingLaunches = new Set<Promise<void>>();\n\tconst pendingAsyncLaunches = new Set<Promise<void>>();\n\tlet unregisterCancellation: (() => void) | undefined;\n\tconst runtimeCleanup = () => {\n\t\tgeneration++;\n\t\tsupervisorChannel.cancelOwnedQuestions(\"parent runtime replaced\");\n\t\tstate.sessionGeneration = (state.sessionGeneration ?? 0) + 1;\n\t\texecutorPromise = undefined;\n\t\tpendingLaunches.clear();\n\t\tpendingAsyncLaunches.clear();\n\t\tunregisterCancellation?.();\n\t\tmainWatchdog.dispose();\n\t\tstopResultWatcher();\n\t\tscheduledRunManager.stop();\n\t\tsupervisorChannel.dispose();\n\t\tclearPendingForegroundControlNotices(state);\n\t\tdisposeSubagentWidget();\n\t\tif (state.poller) {\n\t\t\tclearInterval(state.poller);\n\t\t\tstate.poller = null;\n\t\t}\n\t};\n\tglobalStore[runtimeCleanupStoreKey] = runtimeCleanup;\n\n\tconst { ensurePoller, refreshWidget, handleStarted, handleComplete: trackComplete, resetJobs, restoreActiveJobs } = createAsyncJobTracker(pi, state, ASYNC_DIR, {\n\t\twidgetEnabled: config.asyncWidget !== false,\n\t});\n\tconst handleComplete = (data: unknown) => {\n\t\ttrackComplete(data);\n\t\tconst result = data as { id?: string; sessionId?: string };\n\t\tif (typeof state.currentSessionId === \"string\" && result.sessionId !== state.currentSessionId) return;\n\t\tif (typeof result.id === \"string\" && result.id.trim()) {\n\t\t\tsupervisorChannel.settleRunQuestions(result.id, \"async run settled\");\n\t\t}\n\t};\n\tlet executorExecute: ((id: string, params: SubagentParamsLike, signal: AbortSignal, onUpdate: ((r: AgentToolResult<Details>) => void) | undefined, ctx: ExtensionContext) => Promise<AgentToolResult<Details>>) | undefined;\n\tconst scheduledRunManager = createScheduledRunManager({\n\t\tconfig,\n\t\tlaunch: (params, ctx, signal) => {\n\t\t\tif (!executorExecute) {\n\t\t\t\treturn Promise.resolve({\n\t\t\t\t\tcontent: [{ type: \"text\", text: \"Scheduled subagent launch is unavailable (executor not ready).\" }],\n\t\t\t\t\tisError: true,\n\t\t\t\t\tdetails: { mode: \"management\" as const, results: [] },\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn executorExecute(randomUUID(), params, signal, undefined, ctx);\n\t\t},\n\t});\n\tconst executor: ReturnType<typeof createSubagentExecutor> = {\n\t\tasync execute(id, params, signal, onUpdate, ctx) {\n\t\t\tlet resolveLaunchReady!: () => void;\n\t\t\tlet launchReady = false;\n\t\t\tconst launchReadyPromise = new Promise<void>((resolve) => {\n\t\t\t\tresolveLaunchReady = resolve;\n\t\t\t});\n\t\t\tconst markLaunchReady = () => {\n\t\t\t\tif (launchReady) return;\n\t\t\t\tlaunchReady = true;\n\t\t\t\tpendingLaunches.delete(launchReadyPromise);\n\t\t\t\tpendingAsyncLaunches.delete(launchReadyPromise);\n\t\t\t\tresolveLaunchReady();\n\t\t\t};\n\t\t\tpendingLaunches.add(launchReadyPromise);\n\t\t\tif ((params.task || params.tasks || params.chain) && !params.clarify &&\n\t\t\t\t(params.async === true || (params.async === undefined && asyncByDefault) || config.forceTopLevelAsync === true)) {\n\t\t\t\tpendingAsyncLaunches.add(launchReadyPromise);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst currentGeneration = generation;\n\t\t\t\tsignal?.throwIfAborted();\n\t\t\t\tif (!executorPromise) {\n\t\t\t\t\tconst pending = import(\"../runs/foreground/subagent-executor.ts\").then(({ createSubagentExecutor }) => {\n\t\t\t\t\t\tif (generation !== currentGeneration) throw new Error(\"Subagent session changed before initialization\");\n\t\t\t\t\t\treturn createSubagentExecutor({\n\t\t\t\t\t\t\tpi, state, config, asyncByDefault,\n\t\t\t\t\t\t\twaitToolEnabled: waitToolConfig.enabled,\n\t\t\t\t\t\t\thandleScheduledRunAction: (params, ctx) => scheduledRunManager.handleToolCall(params, ctx),\n\t\t\t\t\t\t\twatchdog: mainWatchdog, tempArtifactsDir, getSubagentSessionRoot, expandTilde,\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t\texecutorPromise = pending;\n\t\t\t\t\tvoid pending.catch(() => {\n\t\t\t\t\t\tif (executorPromise === pending) executorPromise = undefined;\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tconst ready = await awaitWithAbort(executorPromise, signal);\n\t\t\t\tsignal?.throwIfAborted();\n\t\t\t\tif (generation !== currentGeneration) throw new Error(\"Subagent session changed before execution\");\n\t\t\t\tconst execution = ready.execute(id, params, signal, onUpdate, ctx);\n\t\t\t\tmarkLaunchReady();\n\t\t\t\treturn await execution;\n\t\t\t} finally {\n\t\t\t\tmarkLaunchReady();\n\t\t\t}\n\t\t},\n\t};\n\texecutorExecute = executor.execute;\n\n\tpi.registerMessageRenderer<SlashMessageDetails>(SLASH_RESULT_TYPE, (message, options, theme) => {\n\t\tconst details = resolveSlashMessageDetails(message.details);\n\t\tif (!details) return undefined;\n\t\treturn createSlashResultComponent(details, options, theme);\n\t});\n\n\tpi.registerMessageRenderer<undefined>(SLASH_TEXT_RESULT_TYPE, (message, _options, _theme) => {\n\t\tconst content = typeof message.content === \"string\"\n\t\t\t? message.content\n\t\t\t: message.content\n\t\t\t\t.filter((entry) => entry.type === \"text\")\n\t\t\t\t.map((entry) => entry.text)\n\t\t\t\t.join(\"\\n\");\n\t\treturn new Text(content, 0, 0);\n\t});\n\n\tpi.registerMessageRenderer<SubagentNotifyDetails>(\"subagent-notify\", (message, _options, theme) => {\n\t\tconst content = typeof message.content === \"string\" ? message.content : \"\";\n\t\tconst details = (message.details as SubagentNotifyDetails | undefined) ?? parseSubagentNotifyContent(content);\n\t\tif (!details) return new Text(content, 0, 0);\n\t\treturn renderSubagentNotify(details, theme);\n\t});\n\n\tpi.registerMessageRenderer<SubagentSteeringMessageDetails>(SUBAGENT_STEERING_MESSAGE_TYPE, (message, _options, theme) => {\n\t\tconst details = message.details as SubagentSteeringMessageDetails | undefined;\n\t\tif (!details) return undefined;\n\t\treturn new Text(theme.fg(details.state === \"recovered\" ? \"warning\" : \"error\", formatSteeringNotice(details)), 0, 0);\n\t});\n\n\tpi.registerMessageRenderer<SubagentControlMessageDetails>(SUBAGENT_CONTROL_MESSAGE_TYPE, () => undefined);\n\n\tconst executeSubagentCollapsed = (id: string, params: SubagentParamsLike, signal: AbortSignal, onUpdate: ((result: AgentToolResult<Details>) => void) | undefined, ctx: ExtensionContext) => {\n\t\tif (ctx.hasUI) ctx.ui.setToolsExpanded(false);\n\t\treturn executor.execute(id, params, signal, onUpdate, ctx);\n\t};\n\n\tconst slashBridge = registerSlashSubagentBridge({\n\t\tevents: pi.events,\n\t\tgetContext: () => state.lastUiContext,\n\t\texecute: (id, params, signal, onUpdate, ctx) =>\n\t\t\texecuteSubagentCollapsed(id, params, signal, onUpdate, ctx),\n\t});\n\n\tconst promptTemplateBridge = registerPromptTemplateDelegationBridge({\n\t\tevents: pi.events,\n\t\tgetContext: () => state.lastUiContext,\n\t\texecute: (requestId, params, signal, ctx, onUpdate) =>\n\t\t\texecuteSubagentCollapsed(requestId, params, signal, onUpdate, ctx),\n\t});\n\n\tconst rpcBridge = registerSubagentRpcBridge({\n\t\tevents: pi.events,\n\t\tgetContext: () => state.lastUiContext,\n\t\texecute: (id, params, signal, onUpdate, ctx) => executor.execute(id, params, signal, onUpdate, ctx),\n\t});\n\n\tconst tool: ToolDefinition<typeof SubagentParams, Details> = {\n\t\tname: \"subagent\",\n\t\tlabel: \"Subagent\",\n\t\tdescription: buildSubagentToolDescription(config),\n\t\tparameters: SubagentParams,\n\t\t// lunr: same-turn SINGLE calls overlap; sequential work uses chain.\n\t\texecutionMode: \"parallel\",\n\n\t\tprepareArguments(args) {\n\t\t\t// Run friendly chain validation before pi-ai's raw TypeBox schema check\n\t\t\t// so the model sees which property is disallowed, what is allowed, and a\n\t\t\t// valid example instead of `chain.N: must not have additional properties`.\n\t\t\tvalidateChainInput(args);\n\t\t\treturn args as never;\n\t\t},\n\n\t\texecute(id, params, signal, onUpdate, ctx) {\n\t\t\treturn executeSubagentCollapsed(id, params, signal, onUpdate, ctx);\n\t\t},\n\n\t\trenderCall: (args, theme, context) => renderSubagentCall(args, theme, context, { asyncByDefault }),\n\n\t\trenderResult(result, options, theme, context) {\n\t\t\tif (subagentResultIsRunning(result)) {\n\t\t\t\tensureSubagentResultAnimation(context);\n\t\t\t} else {\n\t\t\t\tclearLegacyResultAnimationTimer(context);\n\t\t\t}\n\t\t\tconst state = context.state as { frame?: number; animEntries?: Array<{ text: Text; line: (frame: number) => string }> };\n\t\t\t// lunr: collect running-glyph line templates while rendering so the\n\t\t\t// animation timer can rewrite them in place (see ensureSubagentResultAnimation).\n\t\t\tconst entries: Array<{ text: Text; line: (frame: number) => string }> = [];\n\t\t\tstate.animEntries = entries;\n\t\t\tsubagentAnimSink.current = entries;\n\t\t\ttry {\n\t\t\t\treturn renderSubagentResult(result, options, theme, state.frame ?? 0);\n\t\t\t} finally {\n\t\t\t\tsubagentAnimSink.current = null;\n\t\t\t}\n\t\t},\n\n\t};\n\n\tpi.registerTool(tool);\n\n\t// lunr: register a callback so model-tier settings changes rebuild the subagent\n\t// tool description and refresh the live registry.\n\tconst modelTierBridge = (globalThis as Record<symbol, unknown>)[Symbol.for(\"@lunr/model-tiers\")] as\n\t\t| { registerToolDescriptionRefresher?: (refresher: () => void) => void }\n\t\t| undefined;\n\tmodelTierBridge?.registerToolDescriptionRefresher?.(() => {\n\t\ttool.description = buildSubagentToolDescription(config);\n\t\t// pi.runtime is not on ExtensionAPI; registerTool refreshes the live registry.\n\t\tpi.registerTool(tool);\n\t});\n\n\tregisterWaitTool(pi, state, waitToolConfig.enabled, async (signal) => {\n\t\tconst launches = [...pendingLaunches];\n\t\tif (launches.length === 0) return;\n\t\tawait awaitWithAbort(Promise.all(launches).then(() => {}), signal);\n\t});\n\n\tpi.on(\"agent_end\", async (_event, ctx) => {\n\t\tif (ctx.hasUI) return;\n\t\tawait drainOutstandingWork({ state, events: pi.events });\n\t});\n\n\tregisterSlashCommands(pi, state);\n\n\tconst eventUnsubscribeStoreKey = \"__piSubagentEventUnsubscribes\";\n\tconst controlNoticeSeenStoreKey = \"__piSubagentVisibleControlNotices\";\n\tconst previousEventUnsubscribes = globalStore[eventUnsubscribeStoreKey];\n\tif (Array.isArray(previousEventUnsubscribes)) {\n\t\tfor (const unsubscribe of previousEventUnsubscribes) {\n\t\t\tif (typeof unsubscribe !== \"function\") continue;\n\t\t\ttry {\n\t\t\t\tunsubscribe();\n\t\t\t} catch {\n\t\t\t\t// Best effort cleanup for stale handlers from an older reload.\n\t\t\t}\n\t\t}\n\t}\n\tregisterSubagentNotify(pi, state, { batchConfig: config.completionBatch });\n\n\tconst existingVisibleControlNotices = globalStore[controlNoticeSeenStoreKey];\n\tconst visibleControlNotices = existingVisibleControlNotices instanceof Set ? existingVisibleControlNotices as Set<string> : new Set<string>();\n\tglobalStore[controlNoticeSeenStoreKey] = visibleControlNotices;\n\tconst controlEventHandler = (payload: unknown) => {\n\t\thandleSubagentControlNotice({\n\t\t\tpi,\n\t\t\tstate,\n\t\t\tvisibleControlNotices,\n\t\t\tdetails: payload as SubagentControlMessageDetails,\n\t\t});\n\t};\n\tconst steeringNoticeHandler = (payload: unknown) => {\n\t\thandleSubagentSteeringNotice({ pi, state, details: payload as SubagentSteeringMessageDetails });\n\t};\n\tconst eventUnsubscribes = [\n\t\tpi.events.on(SUBAGENT_ASYNC_STARTED_EVENT, handleStarted),\n\t\tpi.events.on(SUBAGENT_ASYNC_COMPLETE_EVENT, handleComplete),\n\t\tpi.events.on(SUBAGENT_CONTROL_EVENT, controlEventHandler),\n\t\tpi.events.on(SUBAGENT_STEERING_NOTICE_EVENT, steeringNoticeHandler),\n\t\trpcBridge.dispose,\n\t];\n\tglobalStore[eventUnsubscribeStoreKey] = eventUnsubscribes;\n\n\tpi.on(\"tool_result\", (event, ctx) => {\n\t\tif (event.toolName !== \"subagent\") return;\n\t\tif (!ctx.hasUI) return;\n\t\tstate.lastUiContext = ctx;\n\t\tif (state.asyncJobs.size > 0) {\n\t\t\trefreshWidget(ctx);\n\t\t\tensurePoller();\n\t\t}\n\t});\n\n\tconst cleanupSessionArtifacts = (ctx: ExtensionContext) => {\n\t\ttry {\n\t\t\tconst sessionFile = ctx.sessionManager.getSessionFile();\n\t\t\tif (sessionFile) {\n\t\t\t\tcleanupOldArtifacts(getArtifactsDir(sessionFile), DEFAULT_ARTIFACT_CONFIG.cleanupDays);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Cleanup failures should not block session lifecycle events.\n\t\t}\n\t};\n\n\tconst resetSessionState = (ctx: ExtensionContext) => {\n\t\tgeneration++;\n\t\tsupervisorChannel.cancelOwnedQuestions(\"parent session replaced\");\n\t\tstate.sessionGeneration = (state.sessionGeneration ?? 0) + 1;\n\t\texecutorPromise = undefined;\n\t\tpendingLaunches.clear();\n\t\tpendingAsyncLaunches.clear();\n\t\tunregisterCancellation?.();\n\t\tstate.baseCwd = ctx.cwd;\n\t\tstate.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);\n\t\tstate.subagentSpawns = { sessionId: state.currentSessionId, count: 0 };\n\t\t// Set PI_SUBAGENT_PARENT_SESSION for permission-system forwarding.\n\t\t// Only set in the root session (the interactive UI session), not in\n\t\t// child subagent processes — children inherit the parent's value\n\t\t// through the process environment at spawn time and must not overwrite\n\t\t// it with their own session identity.\n\t\tif (!process.env[SUBAGENT_CHILD_ENV]) {\n\t\t\tconst sessionId = ctx.sessionManager.getSessionId();\n\t\t\tif (sessionId) {\n\t\t\t\tprocess.env[SUBAGENT_PARENT_SESSION_ENV] = sessionId;\n\t\t\t}\n\t\t}\n\t\tstate.lastUiContext = ctx;\n\t\tcleanupSessionArtifacts(ctx);\n\t\tclearPendingForegroundControlNotices(state);\n\t\tresetJobs(ctx);\n\t\trestoreActiveJobs(ctx);\n\t\tconst sessionId = state.currentSessionId;\n\t\tconst sessionGeneration = generation;\n\t\tif (sessionId) unregisterCancellation = registerSubagentCancellation(sessionId, createSubagentCancellation({\n\t\t\tpendingLaunches: pendingAsyncLaunches,\n\t\t\tisCurrent: () => generation === sessionGeneration && state.currentSessionId === sessionId,\n\t\t\tgetActiveRunIds: () => [...state.asyncJobs.values()]\n\t\t\t\t.filter((job) => job.sessionId === sessionId && (job.status === \"queued\" || job.status === \"running\"))\n\t\t\t\t.map((job) => job.asyncId),\n\t\t\tasync stopRun(id) {\n\t\t\t\tconst result = await executor.execute(randomUUID(), { action: \"stop\", id }, new AbortController().signal, undefined, ctx);\n\t\t\t\treturn !result.isError;\n\t\t\t},\n\t\t}));\n\t\tscheduledRunManager.bindSession(ctx);\n\t\trestoreSlashFinalSnapshots(ctx.sessionManager.getEntries());\n\t\tprimeExistingResults();\n\t};\n\n\tpi.on(\"session_start\", (_event, ctx) => {\n\t\tresetSessionState(ctx);\n\t\trpcBridge.emitReady(ctx);\n\t\tsupervisorChannel.start();\n\t});\n\n\tpi.on(\"session_shutdown\", () => {\n\t\tgeneration++;\n\t\tsupervisorChannel.cancelOwnedQuestions(\"parent session shutdown\");\n\t\tstate.sessionGeneration = (state.sessionGeneration ?? 0) + 1;\n\t\texecutorPromise = undefined;\n\t\tpendingLaunches.clear();\n\t\tpendingAsyncLaunches.clear();\n\t\tunregisterCancellation?.();\n\t\tdelete process.env[SUBAGENT_PARENT_SESSION_ENV];\n\t\tfor (const unsubscribe of eventUnsubscribes) {\n\t\t\ttry {\n\t\t\t\tunsubscribe();\n\t\t\t} catch {\n\t\t\t\t// Best effort cleanup during shutdown.\n\t\t\t}\n\t\t}\n\t\tif (globalStore[eventUnsubscribeStoreKey] === eventUnsubscribes) {\n\t\t\tdelete globalStore[eventUnsubscribeStoreKey];\n\t\t}\n\t\tstopResultWatcher();\n\t\tscheduledRunManager.stop();\n\t\tif (state.poller) clearInterval(state.poller);\n\t\tstate.poller = null;\n\t\tclearPendingForegroundControlNotices(state);\n\t\tfor (const timer of state.cleanupTimers.values()) {\n\t\t\tclearTimeout(timer);\n\t\t}\n\t\tstate.cleanupTimers.clear();\n\t\tstate.asyncJobs.clear();\n\t\tclearSlashSnapshots();\n\t\tslashBridge.cancelAll();\n\t\tslashBridge.dispose();\n\t\tpromptTemplateBridge.cancelAll();\n\t\tpromptTemplateBridge.dispose();\n\t\tsupervisorChannel.dispose();\n\t\tif (globalStore[runtimeCleanupStoreKey] === runtimeCleanup) {\n\t\t\tdelete globalStore[runtimeCleanupStoreKey];\n\t\t}\n\t\tdisposeSubagentWidget();\n\t\ttry {\n\t\t\tif (state.lastUiContext?.hasUI) {\n\t\t\t\tstate.lastUiContext.ui.setWidget(WIDGET_KEY, undefined);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tif (!isStaleExtensionContextError(error)) throw error;\n\t\t}\n\t});\n}\n"]}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/builtin-extensions/pi-subagents/src/extension/index.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;GAWG;AAQH,OAAO,KAAK,EAAE,YAAY,EAAoC,MAAM,iCAAiC,CAAC;AAEtG,OAAO,EAA0B,IAAI,EAAmE,MAAM,wBAAwB,CAAC;AAqCvI,OAAO,EAGN,KAAK,aAAa,EAYlB,MAAM,oBAAoB,CAAC;AAS5B,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAiFzC,wBAAgB,iCAAiC,CAAC,IAAI,KAAA,EAAE,KAAK,EAAE,aAAa,GAAG,MAAM,GAAG,SAAS,CAgBhG;AAED,wBAAgB,kBAAkB,CAAC,OAAO,KAAA,EAAE,KAAK,KAAA,EAAE,OAAO,KAAA,EAAE,OAAO,KAAA,QAkClE;AAED,wBAAgB,oBAAoB,CAAC,OAAO,KAAA,EAAE,KAAK,KAAA,QASlD;AA8FD,MAAM,CAAC,OAAO,UAAU,yBAAyB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA8bxE","sourcesContent":["// @ts-nocheck\n/**\n * Subagent Tool\n *\n * Full-featured subagent with foreground and async modes.\n * - Async (default): Background execution, emits events when done\n * - Foreground (async:false or clarify:true): Streams output, renders markdown, tracks usage\n *\n * Modes: single (agent + task), parallel (tasks[]), chain (chain[] with {previous})\n *\n * Config file: ~/.lunr/agent/extensions/subagent/config.json\n * { \"maxSubagentDepth\": 1, \"intercomBridge\": { \"mode\": \"always\", \"instructionFile\": \"./intercom-bridge.md\" }, \"worktreeSetupHook\": \"./scripts/setup-worktree.mjs\" }\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport { createSubagentCancellation, registerSubagentCancellation } from \"../../../../core/subagent-cancellation.ts\";\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport type { AgentToolResult } from \"@ashx-j/lunr-agent\";\nimport type { ExtensionAPI, ExtensionContext, ToolDefinition } from \"@ashx-j/lunr\";\nimport { awaitWithAbort } from \"../../../../utils/await-with-abort.ts\";\nimport { Box, Container, Spacer, Text, truncateToWidth, visibleWidth, wrapTextWithAnsi, type Component } from \"@ashx-j/lunr-tui\";\n\nimport { cleanupAllArtifactDirs, cleanupOldArtifacts, getArtifactsDir } from \"../shared/artifacts.ts\";\nimport { resolveCurrentSessionId } from \"../shared/session-identity.ts\";\nimport { cleanupOldChainDirs } from \"../shared/settings.ts\";\nimport {\n\tclearLegacyResultAnimationTimer,\n\tdisposeSubagentWidget,\n\trenderSubagentResult,\n\tSUBAGENT_PAINT_INTERVAL_MS,\n\tsubagentAnimSink,\n} from \"../tui/render.ts\";\nimport { SubagentParams } from \"./schemas.ts\";\nimport { validateChainInput } from \"./chain-validation.ts\";\nimport type { createSubagentExecutor, SubagentParamsLike } from \"../runs/foreground/subagent-executor.ts\";\nimport { resolveSubagentRequestParams } from \"../runs/foreground/request-params.ts\";\nimport { createAsyncJobTracker } from \"../runs/background/async-job-tracker.ts\";\nimport { createResultWatcher } from \"../runs/background/result-watcher.ts\";\nimport { createScheduledRunManager } from \"../runs/background/scheduled-runs.ts\";\nimport { isAsyncSubagentExecution, normalizeAsyncLaunchConfig, subagentLaunchRunsAsync } from \"../runs/background/top-level-async.ts\";\nimport { registerSlashCommands } from \"../slash/slash-commands.ts\";\nimport { registerPromptTemplateDelegationBridge } from \"../slash/prompt-template-bridge.ts\";\nimport { registerMainWatchdog } from \"../watchdog/register-main.ts\";\nimport { registerSlashSubagentBridge } from \"../slash/slash-bridge.ts\";\nimport { createNativeSupervisorChannel } from \"../intercom/native-supervisor-channel.ts\";\nimport { registerSubagentRpcBridge } from \"./rpc.ts\";\nimport { clearSlashSnapshots, getSlashRenderableSnapshot, resolveSlashMessageDetails, restoreSlashFinalSnapshots, type SlashMessageDetails } from \"../slash/slash-live-state.ts\";\nimport { inspectSubagentStatus } from \"../runs/background/run-status.ts\";\nimport { resolveWaitToolConfig } from \"../runs/background/subagent-wait.ts\";\nimport { registerWaitTool } from \"../runs/background/wait-tool.ts\";\nimport { drainOutstandingWork } from \"../runs/background/auto-drain.ts\";\nimport registerSubagentNotify, { parseSubagentNotifyContent, type SubagentNotifyDetails } from \"../runs/background/notify.ts\";\nimport { formatSteeringNotice, handleSubagentSteeringNotice, SUBAGENT_STEERING_MESSAGE_TYPE, type SubagentSteeringMessageDetails } from \"./steering-notices.ts\";\nimport { SUBAGENT_CHILD_ENV, SUBAGENT_PARENT_SESSION_ENV } from \"../runs/shared/pi-args.ts\";\n\nimport { loadConfig } from \"./config.ts\";\nimport { buildSubagentToolDescription } from \"./tool-description.ts\";\nimport {\n\ttype AsyncJobState,\n\ttype Details,\n\ttype SubagentState,\n\tASYNC_DIR,\n\tchildRowLabel,\n\tDEFAULT_ARTIFACT_CONFIG,\n\tRESULTS_DIR,\n\tSLASH_RESULT_TYPE,\n\tSLASH_TEXT_RESULT_TYPE,\n\tSUBAGENT_ASYNC_COMPLETE_EVENT,\n\tSUBAGENT_ASYNC_STARTED_EVENT,\n\tSUBAGENT_CONTROL_EVENT,\n\tSUBAGENT_STEERING_NOTICE_EVENT,\n\tWIDGET_KEY,\n} from \"../shared/types.ts\";\nimport {\n\tclearPendingForegroundControlNotices,\n\tformatSubagentControlNotice,\n\thandleSubagentControlNotice,\n\tSUBAGENT_CONTROL_MESSAGE_TYPE,\n\ttype SubagentControlMessageDetails,\n} from \"./control-notices.ts\";\n\nexport { loadConfig } from \"./config.ts\";\n\n/**\n * Derive subagent session base directory from parent session file.\n * If parent session is ~/.lunr/agent/sessions/abc123.jsonl,\n * returns ~/.lunr/agent/sessions/abc123/ as the base.\n * Callers add runId to create the actual session root: abc123/{runId}/\n * Falls back to a unique temp directory if no parent session.\n */\nfunction getSubagentSessionRoot(parentSessionFile: string | null): string {\n\tif (parentSessionFile) {\n\t\tconst baseName = path.basename(parentSessionFile, \".jsonl\");\n\t\tconst sessionsDir = path.dirname(parentSessionFile);\n\t\treturn path.join(sessionsDir, baseName);\n\t}\n\treturn fs.mkdtempSync(path.join(os.tmpdir(), \"pi-subagent-session-\"));\n}\n\nfunction expandTilde(p: string): string {\n\treturn p.startsWith(\"~/\") ? path.join(os.homedir(), p.slice(2)) : p;\n}\n\n/**\n * Create a directory and verify it is actually accessible.\n * On Windows with Azure AD/Entra ID, directories created shortly after\n * wake-from-sleep can end up with broken NTFS ACLs (null DACL) when the\n * cloud SID cannot be resolved without network connectivity. This leaves\n * the directory completely inaccessible to the creating user.\n */\nfunction ensureAccessibleDir(dirPath: string): void {\n\tfs.mkdirSync(dirPath, { recursive: true });\n\ttry {\n\t\tfs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);\n\t} catch {\n\t\ttry {\n\t\t\tfs.rmSync(dirPath, { recursive: true, force: true });\n\t\t} catch {\n\t\t\t// Best effort: retry mkdir/access even if cleanup fails.\n\t\t}\n\t\tfs.mkdirSync(dirPath, { recursive: true });\n\t\tfs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);\n\t}\n}\n\nfunction isSlashResultRunning(result: { details?: Details }): boolean {\n\treturn result.details?.progress?.some((entry) => entry.status === \"running\")\n\t\t|| result.details?.results.some((entry) => entry.progress?.status === \"running\")\n\t\t|| false;\n}\n\n// Drives the inline running-indicator braille animation for foreground subagent\n// results. Foreground runs receive progress only on child events, so the glyph\n// (derived from progress fields) would freeze between events. While a result is\n// running we tick a frame counter every 80ms; renderResult registers the\n// running-glyph lines in state.animEntries (via subagentAnimSink) and the timer\n// rewrites just those Text nodes + requests a repaint, instead of invalidate()-ing\n// the whole tool component (a full renderSubagentResult rebuild 12.5x/s made the\n// spinner laggy).\nfunction subagentResultIsRunning(result: { details?: Details }): boolean {\n\treturn result.details?.progress?.some((entry) => entry.status === \"running\")\n\t\t|| result.details?.results.some((entry) => entry.progress?.status === \"running\")\n\t\t|| false;\n}\n\nfunction effectiveParallelTaskCount(tasks: Array<{ count?: unknown }> | undefined): number {\n\tif (!tasks || tasks.length === 0) return 0;\n\treturn tasks.reduce((total, task) => {\n\t\tconst count = typeof task.count === \"number\" && Number.isInteger(task.count) && task.count >= 1 ? task.count : 1;\n\t\treturn total + count;\n\t}, 0);\n}\n\nfunction matchingActionJobs(state: SubagentState, requestedId: string): AsyncJobState[] {\n\tconst jobs = new Map<string, AsyncJobState>();\n\tfor (const job of state.asyncJobs.values()) jobs.set(job.asyncId, job);\n\tfor (const job of state.fleetJobs?.values() ?? []) jobs.set(job.asyncId, job);\n\tconst exact = jobs.get(requestedId);\n\tif (exact) return [exact];\n\treturn [...jobs.values()].filter((job) => job.asyncId.startsWith(requestedId));\n}\n\nexport function resolveSubagentActionDisplayTitle(args, state: SubagentState): string | undefined {\n\tconst requestedId = (args.id || args.runId || \"\").trim();\n\tif (!requestedId) return undefined;\n\tconst matches = matchingActionJobs(state, requestedId);\n\tif (matches.length !== 1) return undefined;\n\tconst job = matches[0]!;\n\tconst steps = job.steps ?? [];\n\tif (Number.isInteger(args.index)) {\n\t\tconst step = steps.find((candidate) => candidate.index === args.index) ?? steps[args.index];\n\t\treturn step ? childRowLabel(step) : undefined;\n\t}\n\tif (steps.length === 1) return childRowLabel(steps[0]!);\n\tif (steps.length > 1) return `${job.mode ?? \"subagent\"} run`;\n\tif (job.agents?.length === 1) return job.agents[0];\n\tif (job.agents?.length) return `${job.mode ?? \"subagent\"} run`;\n\treturn undefined;\n}\n\nexport function renderSubagentCall(rawArgs, theme, context, options) {\n\tconst args = resolveSubagentRequestParams(rawArgs);\n\tif (args.action) {\n\t\tconst resultTitle = context?.result?.details?.displayTitle;\n\t\tconst resolvedTitle = resultTitle || options?.resolveActionTitle?.(args);\n\t\tconst target = resolvedTitle || args.id || args.runId || \"\";\n\t\treturn new Text(\n\t\t\t`${theme.fg(\"toolTitle\", theme.bold(\"subagent \"))}${args.action}${target ? ` ${theme.fg(\"accent\", target)}` : \"\"}`,\n\t\t\t0,\n\t\t\t0,\n\t\t);\n\t}\n\tconst isParallel = (args.tasks?.length ?? 0) > 0;\n\tconst parallelCount = effectiveParallelTaskCount(args.tasks as Array<{ count?: unknown }> | undefined);\n\tconst asyncMark = subagentLaunchRunsAsync(args) ? `${theme.fg(\"warning\", \"async\")} ` : \"\";\n\tif (args.chain?.length) {\n\t\treturn new Text(\n\t\t\t`${theme.fg(\"toolTitle\", theme.bold(\"subagent \"))}${asyncMark}chain (${args.chain.length})`,\n\t\t\t0,\n\t\t\t0,\n\t\t);\n\t}\n\tif (isParallel) {\n\t\treturn new Text(\n\t\t\t`${theme.fg(\"toolTitle\", theme.bold(\"subagent \"))}${asyncMark}parallel (${parallelCount})`,\n\t\t\t0,\n\t\t\t0,\n\t\t);\n\t}\n\tconst showDescription = !context?.result && !context?.isPartial && !context?.expanded && !context?.isError;\n\tconst description = showDescription ? theme.fg(\"accent\", args.description || args.task || \"?\") : \"\";\n\tconst title = theme.fg(\"toolTitle\", theme.bold(\"subagent\"));\n\tconst rest = [asyncMark.trim() ? theme.fg(\"warning\", \"async\") : \"\", description].filter(Boolean).join(\" \");\n\treturn new Text(rest ? `${title} ${rest}` : title, 0, 0);\n}\n\nexport function renderSubagentNotify(details, theme) {\n\tconst status = typeof details?.status === \"string\" ? details.status : \"\";\n\tconst icon = status === \"completed\"\n\t\t? theme.fg(\"success\", \"✓\")\n\t\t: status === \"failed\"\n\t\t\t? theme.fg(\"error\", \"✗\")\n\t\t\t: theme.fg(\"warning\", \"■\");\n\tconst title = details?.agent || \"child\";\n\treturn new Text(`${icon} ${theme.bold(title)} ${theme.fg(\"dim\", status)}`, 0, 0);\n}\n\nfunction ensureSubagentResultAnimation(context: { state: Record<string, unknown>; invalidate?: () => void; requestRender?: () => void }): void {\n\tconst state = context.state as {\n\t\tsubagentResultAnimationTimer?: ReturnType<typeof setInterval>;\n\t\tframe?: number;\n\t\tanimEntries?: Array<{ text: Text; line: (frame: number, now: number) => string }>;\n\t};\n\tif (state.subagentResultAnimationTimer) return;\n\tif (typeof context.invalidate !== \"function\") return;\n\tif (state.frame === undefined) state.frame = 0;\n\tstate.subagentResultAnimationTimer = setInterval(() => {\n\t\tstate.frame = (state.frame ?? 0) + 1;\n\t\ttry {\n\t\t\tconst entries = state.animEntries;\n\t\t\tif (entries?.length && typeof context.requestRender === \"function\") {\n\t\t\t\tconst now = Date.now();\n\t\t\t\tfor (const entry of entries) entry.text.setText(entry.line(state.frame ?? 0, now));\n\t\t\t\tcontext.requestRender();\n\t\t\t} else {\n\t\t\t\tcontext.invalidate();\n\t\t\t}\n\t\t} catch {}\n\t}, SUBAGENT_PAINT_INTERVAL_MS);\n}\n\nfunction isSlashResultError(result: { details?: Details }): boolean {\n\treturn result.details?.results.some((entry) => entry.exitCode !== 0 && entry.progress?.status !== \"running\") || false;\n}\n\nfunction isStaleExtensionContextError(error: unknown): boolean {\n\treturn error instanceof Error && error.message.includes(\"Extension context no longer active\");\n}\n\nfunction rebuildSlashResultContainer(\n\tcontainer: Container,\n\tresult: AgentToolResult<Details>,\n\toptions: { expanded: boolean },\n\ttheme: ExtensionContext[\"ui\"][\"theme\"],\n): void {\n\tcontainer.clear();\n\tcontainer.addChild(new Spacer(1));\n\tconst boxTheme = isSlashResultRunning(result) ? \"toolPendingBg\" : isSlashResultError(result) ? \"toolErrorBg\" : \"toolSuccessBg\";\n\tconst box = new Box(1, 1, (text: string) => theme.bg(boxTheme, text));\n\tbox.addChild(renderSubagentResult(result, options, theme));\n\tcontainer.addChild(box);\n}\n\nfunction createSlashResultComponent(\n\tdetails: SlashMessageDetails,\n\toptions: { expanded: boolean },\n\ttheme: ExtensionContext[\"ui\"][\"theme\"],\n): Container {\n\tconst container = new Container();\n\tlet lastVersion = -1;\n\tcontainer.render = (width: number): string[] => {\n\t\tconst snapshot = getSlashRenderableSnapshot(details);\n\t\tif (snapshot.version !== lastVersion || isSlashResultRunning(snapshot.result)) {\n\t\t\tlastVersion = snapshot.version;\n\t\t\trebuildSlashResultContainer(container, snapshot.result, options, theme);\n\t\t}\n\t\treturn Container.prototype.render.call(container, width);\n\t};\n\treturn container;\n}\n\nclass SubagentControlNoticeComponent implements Component {\n\tconstructor(\n\t\tprivate readonly details: SubagentControlMessageDetails,\n\t\tprivate readonly theme: ExtensionContext[\"ui\"][\"theme\"],\n\t) {}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst eventLabel = this.details.event.type.replaceAll(\"_\", \" \");\n\t\tif (width < 3) return [truncateToWidth(`Subagent ${eventLabel}`, width)];\n\t\tconst bodyWidth = Math.max(1, width - 2);\n\t\tconst borderChar = \"─\";\n\t\tconst header = ` ⚠ Subagent ${eventLabel}: ${this.details.event.agent} `;\n\t\tconst headerText = truncateToWidth(header, bodyWidth, \"\");\n\t\tconst headerPadding = Math.max(0, bodyWidth - visibleWidth(headerText));\n\t\tconst lines = [this.theme.fg(\"accent\", `╭${headerText}${borderChar.repeat(headerPadding)}╮`)];\n\n\t\tfor (const line of wrapTextWithAnsi(formatSubagentControlNotice(this.details), bodyWidth)) {\n\t\t\tconst text = truncateToWidth(line, bodyWidth, \"\");\n\t\t\tconst padding = Math.max(0, bodyWidth - visibleWidth(text));\n\t\t\tlines.push(this.theme.fg(\"accent\", `│${text}${\" \".repeat(padding)}│`));\n\t\t}\n\t\tlines.push(this.theme.fg(\"accent\", `╰${borderChar.repeat(bodyWidth)}╯`));\n\t\treturn lines;\n\t}\n}\n\nexport default function registerSubagentExtension(pi: ExtensionAPI): void {\n\tif (process.env[SUBAGENT_CHILD_ENV] === \"1\") {\n\t\treturn;\n\t}\n\tconst globalStore = globalThis as Record<string, unknown>;\n\tconst runtimeCleanupStoreKey = \"__piSubagentRuntimeCleanup\";\n\tconst previousRuntimeCleanup = globalStore[runtimeCleanupStoreKey];\n\tif (typeof previousRuntimeCleanup === \"function\") {\n\t\ttry {\n\t\t\tpreviousRuntimeCleanup();\n\t\t} catch {\n\t\t\t// Best effort cleanup for stale timers from an older reload.\n\t\t}\n\t}\n\n\tensureAccessibleDir(RESULTS_DIR);\n\tensureAccessibleDir(ASYNC_DIR);\n\tcleanupOldChainDirs();\n\n\tconst config = normalizeAsyncLaunchConfig(loadConfig());\n\tconst waitToolConfig = resolveWaitToolConfig(config.waitTool);\n\tconst tempArtifactsDir = getArtifactsDir(null);\n\tcleanupAllArtifactDirs(DEFAULT_ARTIFACT_CONFIG.cleanupDays);\n\n\tconst state: SubagentState = {\n\t\tbaseCwd: \"\",\n\t\tcurrentSessionId: null,\n\t\tsessionGeneration: 0,\n\t\tforegroundSubagentInFlight: 0,\n\t\tsubagentSpawns: { sessionId: null, count: 0 },\n\t\tasyncJobs: new Map(),\n\t\tfleetJobs: new Map(),\n\t\tforegroundRuns: new Map(),\n\t\tforegroundControls: new Map(),\n\t\tlastForegroundControlId: null,\n\t\tpendingForegroundControlNotices: new Map(),\n\t\tcleanupTimers: new Map(),\n\t\tlastUiContext: null,\n\t\tpoller: null,\n\t\tcompletionSeen: new Map(),\n\t\twatcher: null,\n\t\twatcherRestartTimer: null,\n\t\tresultFileCoalescer: {\n\t\t\tschedule: () => false,\n\t\t\tclear: () => {},\n\t\t},\n\t};\n\n\tconst supervisorChannel = createNativeSupervisorChannel(pi, state);\n\tconst mainWatchdog = registerMainWatchdog(pi);\n\tconst { startResultWatcher, primeExistingResults, stopResultWatcher } = createResultWatcher(\n\t\tpi,\n\t\tstate,\n\t\tRESULTS_DIR,\n\t\t10 * 60 * 1000,\n\t);\n\tstartResultWatcher();\n\tprimeExistingResults();\n\n\tlet generation = 0;\n\tlet executorPromise: Promise<ReturnType<typeof createSubagentExecutor>> | undefined;\n\tconst pendingLaunches = new Set<Promise<void>>();\n\tconst pendingAsyncLaunches = new Set<Promise<void>>();\n\tlet unregisterCancellation: (() => void) | undefined;\n\tconst runtimeCleanup = () => {\n\t\tgeneration++;\n\t\tsupervisorChannel.cancelOwnedQuestions(\"parent runtime replaced\");\n\t\tstate.sessionGeneration = (state.sessionGeneration ?? 0) + 1;\n\t\texecutorPromise = undefined;\n\t\tpendingLaunches.clear();\n\t\tpendingAsyncLaunches.clear();\n\t\tunregisterCancellation?.();\n\t\tmainWatchdog.dispose();\n\t\tstopResultWatcher();\n\t\tscheduledRunManager.stop();\n\t\tsupervisorChannel.dispose();\n\t\tclearPendingForegroundControlNotices(state);\n\t\tdisposeSubagentWidget();\n\t\tif (state.poller) {\n\t\t\tclearInterval(state.poller);\n\t\t\tstate.poller = null;\n\t\t}\n\t};\n\tglobalStore[runtimeCleanupStoreKey] = runtimeCleanup;\n\n\tconst { ensurePoller, refreshWidget, handleStarted, handleComplete: trackComplete, resetJobs, restoreActiveJobs } = createAsyncJobTracker(pi, state, ASYNC_DIR, {\n\t\twidgetEnabled: config.asyncWidget !== false,\n\t});\n\tconst handleComplete = (data: unknown) => {\n\t\ttrackComplete(data);\n\t\tconst result = data as { id?: string; sessionId?: string };\n\t\tif (typeof state.currentSessionId === \"string\" && result.sessionId !== state.currentSessionId) return;\n\t\tif (typeof result.id === \"string\" && result.id.trim()) {\n\t\t\tsupervisorChannel.settleRunQuestions(result.id, \"async run settled\");\n\t\t}\n\t};\n\tlet executorExecute: ((id: string, params: SubagentParamsLike, signal: AbortSignal, onUpdate: ((r: AgentToolResult<Details>) => void) | undefined, ctx: ExtensionContext) => Promise<AgentToolResult<Details>>) | undefined;\n\tconst scheduledRunManager = createScheduledRunManager({\n\t\tconfig,\n\t\tlaunch: (params, ctx, signal) => {\n\t\t\tif (!executorExecute) {\n\t\t\t\treturn Promise.resolve({\n\t\t\t\t\tcontent: [{ type: \"text\", text: \"Scheduled subagent launch is unavailable (executor not ready).\" }],\n\t\t\t\t\tisError: true,\n\t\t\t\t\tdetails: { mode: \"management\" as const, results: [] },\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn executorExecute(randomUUID(), params, signal, undefined, ctx);\n\t\t},\n\t});\n\tconst executor: ReturnType<typeof createSubagentExecutor> = {\n\t\tasync execute(id, params, signal, onUpdate, ctx) {\n\t\t\tlet resolveLaunchReady!: () => void;\n\t\t\tlet launchReady = false;\n\t\t\tconst launchReadyPromise = new Promise<void>((resolve) => {\n\t\t\t\tresolveLaunchReady = resolve;\n\t\t\t});\n\t\t\tconst markLaunchReady = () => {\n\t\t\t\tif (launchReady) return;\n\t\t\t\tlaunchReady = true;\n\t\t\t\tpendingLaunches.delete(launchReadyPromise);\n\t\t\t\tpendingAsyncLaunches.delete(launchReadyPromise);\n\t\t\t\tresolveLaunchReady();\n\t\t\t};\n\t\t\tpendingLaunches.add(launchReadyPromise);\n\t\t\tif (isAsyncSubagentExecution(resolveSubagentRequestParams(params))) pendingAsyncLaunches.add(launchReadyPromise);\n\t\t\ttry {\n\t\t\t\tconst currentGeneration = generation;\n\t\t\t\tsignal?.throwIfAborted();\n\t\t\t\tif (!executorPromise) {\n\t\t\t\t\tconst pending = import(\"../runs/foreground/subagent-executor.ts\").then(({ createSubagentExecutor }) => {\n\t\t\t\t\t\tif (generation !== currentGeneration) throw new Error(\"Subagent session changed before initialization\");\n\t\t\t\t\t\treturn createSubagentExecutor({\n\t\t\t\t\t\t\tpi, state, config,\n\t\t\t\t\t\t\twaitToolEnabled: waitToolConfig.enabled,\n\t\t\t\t\t\t\thandleScheduledRunAction: (params, ctx) => scheduledRunManager.handleToolCall(params, ctx),\n\t\t\t\t\t\t\twatchdog: mainWatchdog, tempArtifactsDir, getSubagentSessionRoot, expandTilde,\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t\texecutorPromise = pending;\n\t\t\t\t\tvoid pending.catch(() => {\n\t\t\t\t\t\tif (executorPromise === pending) executorPromise = undefined;\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tconst ready = await awaitWithAbort(executorPromise, signal);\n\t\t\t\tsignal?.throwIfAborted();\n\t\t\t\tif (generation !== currentGeneration) throw new Error(\"Subagent session changed before execution\");\n\t\t\t\tconst execution = ready.execute(id, params, signal, onUpdate, ctx);\n\t\t\t\tmarkLaunchReady();\n\t\t\t\treturn await execution;\n\t\t\t} finally {\n\t\t\t\tmarkLaunchReady();\n\t\t\t}\n\t\t},\n\t};\n\texecutorExecute = executor.execute;\n\n\tpi.registerMessageRenderer<SlashMessageDetails>(SLASH_RESULT_TYPE, (message, options, theme) => {\n\t\tconst details = resolveSlashMessageDetails(message.details);\n\t\tif (!details) return undefined;\n\t\treturn createSlashResultComponent(details, options, theme);\n\t});\n\n\tpi.registerMessageRenderer<undefined>(SLASH_TEXT_RESULT_TYPE, (message, _options, _theme) => {\n\t\tconst content = typeof message.content === \"string\"\n\t\t\t? message.content\n\t\t\t: message.content\n\t\t\t\t.filter((entry) => entry.type === \"text\")\n\t\t\t\t.map((entry) => entry.text)\n\t\t\t\t.join(\"\\n\");\n\t\treturn new Text(content, 0, 0);\n\t});\n\n\tpi.registerMessageRenderer<SubagentNotifyDetails>(\"subagent-notify\", (message, _options, theme) => {\n\t\tconst content = typeof message.content === \"string\" ? message.content : \"\";\n\t\tconst details = (message.details as SubagentNotifyDetails | undefined) ?? parseSubagentNotifyContent(content);\n\t\tif (!details) return new Text(content, 0, 0);\n\t\treturn renderSubagentNotify(details, theme);\n\t});\n\n\tpi.registerMessageRenderer<SubagentSteeringMessageDetails>(SUBAGENT_STEERING_MESSAGE_TYPE, (message, _options, theme) => {\n\t\tconst details = message.details as SubagentSteeringMessageDetails | undefined;\n\t\tif (!details) return undefined;\n\t\treturn new Text(theme.fg(details.state === \"recovered\" ? \"warning\" : \"error\", formatSteeringNotice(details)), 0, 0);\n\t});\n\n\tpi.registerMessageRenderer<SubagentControlMessageDetails>(SUBAGENT_CONTROL_MESSAGE_TYPE, () => undefined);\n\n\tconst executeSubagentCollapsed = async (id: string, params: SubagentParamsLike, signal: AbortSignal, onUpdate: ((result: AgentToolResult<Details>) => void) | undefined, ctx: ExtensionContext) => {\n\t\tif (ctx.hasUI) ctx.ui.setToolsExpanded(false);\n\t\tconst displayTitle = params.action === \"steer\"\n\t\t\t? resolveSubagentActionDisplayTitle(params, state)\n\t\t\t: undefined;\n\t\tconst result = await executor.execute(id, params, signal, onUpdate, ctx);\n\t\tif (displayTitle && result.details) result.details.displayTitle = displayTitle;\n\t\treturn result;\n\t};\n\n\tconst slashBridge = registerSlashSubagentBridge({\n\t\tevents: pi.events,\n\t\tgetContext: () => state.lastUiContext,\n\t\texecute: (id, params, signal, onUpdate, ctx) =>\n\t\t\texecuteSubagentCollapsed(id, params, signal, onUpdate, ctx),\n\t});\n\n\tconst promptTemplateBridge = registerPromptTemplateDelegationBridge({\n\t\tevents: pi.events,\n\t\tgetContext: () => state.lastUiContext,\n\t\texecute: (requestId, params, signal, ctx, onUpdate) =>\n\t\t\texecuteSubagentCollapsed(requestId, params, signal, onUpdate, ctx),\n\t});\n\n\tconst rpcBridge = registerSubagentRpcBridge({\n\t\tevents: pi.events,\n\t\tgetContext: () => state.lastUiContext,\n\t\texecute: (id, params, signal, onUpdate, ctx) => executor.execute(id, params, signal, onUpdate, ctx),\n\t});\n\n\tconst tool: ToolDefinition<typeof SubagentParams, Details> = {\n\t\tname: \"subagent\",\n\t\tlabel: \"Subagent\",\n\t\tdescription: buildSubagentToolDescription(config),\n\t\tparameters: SubagentParams,\n\t\t// lunr: same-turn SINGLE calls overlap; sequential work uses chain.\n\t\texecutionMode: \"parallel\",\n\n\t\tprepareArguments(args) {\n\t\t\t// Run friendly chain validation before pi-ai's raw TypeBox schema check\n\t\t\t// so the model sees which property is disallowed, what is allowed, and a\n\t\t\t// valid example instead of `chain.N: must not have additional properties`.\n\t\t\tvalidateChainInput(args);\n\t\t\treturn args as never;\n\t\t},\n\n\t\texecute(id, params, signal, onUpdate, ctx) {\n\t\t\treturn executeSubagentCollapsed(id, params, signal, onUpdate, ctx);\n\t\t},\n\n\t\trenderCall: (args, theme, context) => renderSubagentCall(args, theme, context, {\n\t\t\tresolveActionTitle: (params) => resolveSubagentActionDisplayTitle(params, state),\n\t\t}),\n\n\t\trenderResult(result, options, theme, context) {\n\t\t\tif (subagentResultIsRunning(result)) {\n\t\t\t\tensureSubagentResultAnimation(context);\n\t\t\t} else {\n\t\t\t\tclearLegacyResultAnimationTimer(context);\n\t\t\t}\n\t\t\tconst state = context.state as { frame?: number; animEntries?: Array<{ text: Text; line: (frame: number) => string }> };\n\t\t\t// lunr: collect running-glyph line templates while rendering so the\n\t\t\t// animation timer can rewrite them in place (see ensureSubagentResultAnimation).\n\t\t\tconst entries: Array<{ text: Text; line: (frame: number) => string }> = [];\n\t\t\tstate.animEntries = entries;\n\t\t\tsubagentAnimSink.current = entries;\n\t\t\ttry {\n\t\t\t\treturn renderSubagentResult(result, options, theme, state.frame ?? 0);\n\t\t\t} finally {\n\t\t\t\tsubagentAnimSink.current = null;\n\t\t\t}\n\t\t},\n\n\t};\n\n\tpi.registerTool(tool);\n\n\t// lunr: register a callback so model-tier settings changes rebuild the subagent\n\t// tool description and refresh the live registry.\n\tconst modelTierBridge = (globalThis as Record<symbol, unknown>)[Symbol.for(\"@lunr/model-tiers\")] as\n\t\t| { registerToolDescriptionRefresher?: (refresher: () => void) => void }\n\t\t| undefined;\n\tmodelTierBridge?.registerToolDescriptionRefresher?.(() => {\n\t\ttool.description = buildSubagentToolDescription(config);\n\t\t// pi.runtime is not on ExtensionAPI; registerTool refreshes the live registry.\n\t\tpi.registerTool(tool);\n\t});\n\n\tregisterWaitTool(pi, state, waitToolConfig.enabled, async (signal) => {\n\t\tconst launches = [...pendingLaunches];\n\t\tif (launches.length === 0) return;\n\t\tawait awaitWithAbort(Promise.all(launches).then(() => {}), signal);\n\t});\n\n\tpi.on(\"agent_end\", async (_event, ctx) => {\n\t\tif (ctx.hasUI) return;\n\t\tawait drainOutstandingWork({ state, events: pi.events });\n\t});\n\n\tregisterSlashCommands(pi, state);\n\n\tconst eventUnsubscribeStoreKey = \"__piSubagentEventUnsubscribes\";\n\tconst controlNoticeSeenStoreKey = \"__piSubagentVisibleControlNotices\";\n\tconst previousEventUnsubscribes = globalStore[eventUnsubscribeStoreKey];\n\tif (Array.isArray(previousEventUnsubscribes)) {\n\t\tfor (const unsubscribe of previousEventUnsubscribes) {\n\t\t\tif (typeof unsubscribe !== \"function\") continue;\n\t\t\ttry {\n\t\t\t\tunsubscribe();\n\t\t\t} catch {\n\t\t\t\t// Best effort cleanup for stale handlers from an older reload.\n\t\t\t}\n\t\t}\n\t}\n\tregisterSubagentNotify(pi, state, { batchConfig: config.completionBatch });\n\n\tconst existingVisibleControlNotices = globalStore[controlNoticeSeenStoreKey];\n\tconst visibleControlNotices = existingVisibleControlNotices instanceof Set ? existingVisibleControlNotices as Set<string> : new Set<string>();\n\tglobalStore[controlNoticeSeenStoreKey] = visibleControlNotices;\n\tconst controlEventHandler = (payload: unknown) => {\n\t\thandleSubagentControlNotice({\n\t\t\tpi,\n\t\t\tstate,\n\t\t\tvisibleControlNotices,\n\t\t\tdetails: payload as SubagentControlMessageDetails,\n\t\t});\n\t};\n\tconst steeringNoticeHandler = (payload: unknown) => {\n\t\thandleSubagentSteeringNotice({ pi, state, details: payload as SubagentSteeringMessageDetails });\n\t};\n\tconst eventUnsubscribes = [\n\t\tpi.events.on(SUBAGENT_ASYNC_STARTED_EVENT, handleStarted),\n\t\tpi.events.on(SUBAGENT_ASYNC_COMPLETE_EVENT, handleComplete),\n\t\tpi.events.on(SUBAGENT_CONTROL_EVENT, controlEventHandler),\n\t\tpi.events.on(SUBAGENT_STEERING_NOTICE_EVENT, steeringNoticeHandler),\n\t\trpcBridge.dispose,\n\t];\n\tglobalStore[eventUnsubscribeStoreKey] = eventUnsubscribes;\n\n\tpi.on(\"tool_result\", (event, ctx) => {\n\t\tif (event.toolName !== \"subagent\") return;\n\t\tif (!ctx.hasUI) return;\n\t\tstate.lastUiContext = ctx;\n\t\tif (state.asyncJobs.size > 0) {\n\t\t\trefreshWidget(ctx);\n\t\t\tensurePoller();\n\t\t}\n\t});\n\n\tconst cleanupSessionArtifacts = (ctx: ExtensionContext) => {\n\t\ttry {\n\t\t\tconst sessionFile = ctx.sessionManager.getSessionFile();\n\t\t\tif (sessionFile) {\n\t\t\t\tcleanupOldArtifacts(getArtifactsDir(sessionFile), DEFAULT_ARTIFACT_CONFIG.cleanupDays);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Cleanup failures should not block session lifecycle events.\n\t\t}\n\t};\n\n\tconst resetSessionState = (ctx: ExtensionContext) => {\n\t\tgeneration++;\n\t\tsupervisorChannel.cancelOwnedQuestions(\"parent session replaced\");\n\t\tstate.sessionGeneration = (state.sessionGeneration ?? 0) + 1;\n\t\texecutorPromise = undefined;\n\t\tpendingLaunches.clear();\n\t\tpendingAsyncLaunches.clear();\n\t\tunregisterCancellation?.();\n\t\tstate.baseCwd = ctx.cwd;\n\t\tstate.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);\n\t\tstate.subagentSpawns = { sessionId: state.currentSessionId, count: 0 };\n\t\t// Set PI_SUBAGENT_PARENT_SESSION for permission-system forwarding.\n\t\t// Only set in the root session (the interactive UI session), not in\n\t\t// child subagent processes — children inherit the parent's value\n\t\t// through the process environment at spawn time and must not overwrite\n\t\t// it with their own session identity.\n\t\tif (!process.env[SUBAGENT_CHILD_ENV]) {\n\t\t\tconst sessionId = ctx.sessionManager.getSessionId();\n\t\t\tif (sessionId) {\n\t\t\t\tprocess.env[SUBAGENT_PARENT_SESSION_ENV] = sessionId;\n\t\t\t}\n\t\t}\n\t\tstate.lastUiContext = ctx;\n\t\tcleanupSessionArtifacts(ctx);\n\t\tclearPendingForegroundControlNotices(state);\n\t\tresetJobs(ctx);\n\t\trestoreActiveJobs(ctx);\n\t\tconst sessionId = state.currentSessionId;\n\t\tconst sessionGeneration = generation;\n\t\tif (sessionId) unregisterCancellation = registerSubagentCancellation(sessionId, createSubagentCancellation({\n\t\t\tpendingLaunches: pendingAsyncLaunches,\n\t\t\tisCurrent: () => generation === sessionGeneration && state.currentSessionId === sessionId,\n\t\t\tgetActiveRunIds: () => [...state.asyncJobs.values()]\n\t\t\t\t.filter((job) => job.sessionId === sessionId && (job.status === \"queued\" || job.status === \"running\"))\n\t\t\t\t.map((job) => job.asyncId),\n\t\t\tasync stopRun(id) {\n\t\t\t\tconst result = await executor.execute(randomUUID(), { action: \"stop\", id }, new AbortController().signal, undefined, ctx);\n\t\t\t\treturn !result.isError;\n\t\t\t},\n\t\t}));\n\t\tscheduledRunManager.bindSession(ctx);\n\t\trestoreSlashFinalSnapshots(ctx.sessionManager.getEntries());\n\t\tprimeExistingResults();\n\t};\n\n\tpi.on(\"session_start\", (_event, ctx) => {\n\t\tresetSessionState(ctx);\n\t\trpcBridge.emitReady(ctx);\n\t\tsupervisorChannel.start();\n\t});\n\n\tpi.on(\"session_shutdown\", () => {\n\t\tgeneration++;\n\t\tsupervisorChannel.cancelOwnedQuestions(\"parent session shutdown\");\n\t\tstate.sessionGeneration = (state.sessionGeneration ?? 0) + 1;\n\t\texecutorPromise = undefined;\n\t\tpendingLaunches.clear();\n\t\tpendingAsyncLaunches.clear();\n\t\tunregisterCancellation?.();\n\t\tdelete process.env[SUBAGENT_PARENT_SESSION_ENV];\n\t\tfor (const unsubscribe of eventUnsubscribes) {\n\t\t\ttry {\n\t\t\t\tunsubscribe();\n\t\t\t} catch {\n\t\t\t\t// Best effort cleanup during shutdown.\n\t\t\t}\n\t\t}\n\t\tif (globalStore[eventUnsubscribeStoreKey] === eventUnsubscribes) {\n\t\t\tdelete globalStore[eventUnsubscribeStoreKey];\n\t\t}\n\t\tstopResultWatcher();\n\t\tscheduledRunManager.stop();\n\t\tif (state.poller) clearInterval(state.poller);\n\t\tstate.poller = null;\n\t\tclearPendingForegroundControlNotices(state);\n\t\tfor (const timer of state.cleanupTimers.values()) {\n\t\t\tclearTimeout(timer);\n\t\t}\n\t\tstate.cleanupTimers.clear();\n\t\tstate.asyncJobs.clear();\n\t\tclearSlashSnapshots();\n\t\tslashBridge.cancelAll();\n\t\tslashBridge.dispose();\n\t\tpromptTemplateBridge.cancelAll();\n\t\tpromptTemplateBridge.dispose();\n\t\tsupervisorChannel.dispose();\n\t\tif (globalStore[runtimeCleanupStoreKey] === runtimeCleanup) {\n\t\t\tdelete globalStore[runtimeCleanupStoreKey];\n\t\t}\n\t\tdisposeSubagentWidget();\n\t\ttry {\n\t\t\tif (state.lastUiContext?.hasUI) {\n\t\t\t\tstate.lastUiContext.ui.setWidget(WIDGET_KEY, undefined);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tif (!isStaleExtensionContextError(error)) throw error;\n\t\t}\n\t});\n}\n"]}
@@ -2,15 +2,14 @@
2
2
  /**
3
3
  * Subagent Tool
4
4
  *
5
- * Full-featured subagent with sync and async modes.
6
- * - Sync (default): Streams output, renders markdown, tracks usage
7
- * - Async: Background execution, emits events when done
5
+ * Full-featured subagent with foreground and async modes.
6
+ * - Async (default): Background execution, emits events when done
7
+ * - Foreground (async:false or clarify:true): Streams output, renders markdown, tracks usage
8
8
  *
9
9
  * Modes: single (agent + task), parallel (tasks[]), chain (chain[] with {previous})
10
- * Toggle: async parameter (default: false, configurable via config.json)
11
10
  *
12
11
  * Config file: ~/.lunr/agent/extensions/subagent/config.json
13
- * { "asyncByDefault": true, "forceTopLevelAsync": true, "maxSubagentDepth": 1, "intercomBridge": { "mode": "always", "instructionFile": "./intercom-bridge.md" }, "worktreeSetupHook": "./scripts/setup-worktree.mjs" }
12
+ * { "maxSubagentDepth": 1, "intercomBridge": { "mode": "always", "instructionFile": "./intercom-bridge.md" }, "worktreeSetupHook": "./scripts/setup-worktree.mjs" }
14
13
  */
15
14
  import { randomUUID } from "node:crypto";
16
15
  import { createSubagentCancellation, registerSubagentCancellation } from "../../../../core/subagent-cancellation.js";
@@ -22,12 +21,14 @@ import { Box, Container, Spacer, Text, truncateToWidth, visibleWidth, wrapTextWi
22
21
  import { cleanupAllArtifactDirs, cleanupOldArtifacts, getArtifactsDir } from "../shared/artifacts.js";
23
22
  import { resolveCurrentSessionId } from "../shared/session-identity.js";
24
23
  import { cleanupOldChainDirs } from "../shared/settings.js";
25
- import { clearLegacyResultAnimationTimer, disposeSubagentWidget, renderSubagentResult, subagentAnimSink } from "../tui/render.js";
24
+ import { clearLegacyResultAnimationTimer, disposeSubagentWidget, renderSubagentResult, SUBAGENT_PAINT_INTERVAL_MS, subagentAnimSink, } from "../tui/render.js";
26
25
  import { SubagentParams } from "./schemas.js";
27
26
  import { validateChainInput } from "./chain-validation.js";
27
+ import { resolveSubagentRequestParams } from "../runs/foreground/request-params.js";
28
28
  import { createAsyncJobTracker } from "../runs/background/async-job-tracker.js";
29
29
  import { createResultWatcher } from "../runs/background/result-watcher.js";
30
30
  import { createScheduledRunManager } from "../runs/background/scheduled-runs.js";
31
+ import { isAsyncSubagentExecution, normalizeAsyncLaunchConfig, subagentLaunchRunsAsync } from "../runs/background/top-level-async.js";
31
32
  import { registerSlashCommands } from "../slash/slash-commands.js";
32
33
  import { registerPromptTemplateDelegationBridge } from "../slash/prompt-template-bridge.js";
33
34
  import { registerMainWatchdog } from "../watchdog/register-main.js";
@@ -43,7 +44,7 @@ import { formatSteeringNotice, handleSubagentSteeringNotice, SUBAGENT_STEERING_M
43
44
  import { SUBAGENT_CHILD_ENV, SUBAGENT_PARENT_SESSION_ENV } from "../runs/shared/pi-args.js";
44
45
  import { loadConfig } from "./config.js";
45
46
  import { buildSubagentToolDescription } from "./tool-description.js";
46
- import { ASYNC_DIR, DEFAULT_ARTIFACT_CONFIG, RESULTS_DIR, SLASH_RESULT_TYPE, SLASH_TEXT_RESULT_TYPE, SUBAGENT_ASYNC_COMPLETE_EVENT, SUBAGENT_ASYNC_STARTED_EVENT, SUBAGENT_CONTROL_EVENT, SUBAGENT_STEERING_NOTICE_EVENT, WIDGET_KEY, } from "../shared/types.js";
47
+ import { ASYNC_DIR, childRowLabel, DEFAULT_ARTIFACT_CONFIG, RESULTS_DIR, SLASH_RESULT_TYPE, SLASH_TEXT_RESULT_TYPE, SUBAGENT_ASYNC_COMPLETE_EVENT, SUBAGENT_ASYNC_STARTED_EVENT, SUBAGENT_CONTROL_EVENT, SUBAGENT_STEERING_NOTICE_EVENT, WIDGET_KEY, } from "../shared/types.js";
47
48
  import { clearPendingForegroundControlNotices, formatSubagentControlNotice, handleSubagentControlNotice, SUBAGENT_CONTROL_MESSAGE_TYPE, } from "./control-notices.js";
48
49
  export { loadConfig } from "./config.js";
49
50
  /**
@@ -113,23 +114,51 @@ function effectiveParallelTaskCount(tasks) {
113
114
  return total + count;
114
115
  }, 0);
115
116
  }
116
- function callIsAsync(args, asyncByDefault) {
117
- if (args?.clarify === true)
118
- return false;
119
- if (args?.async === true)
120
- return true;
121
- if (args?.async === false)
122
- return false;
123
- return asyncByDefault === true;
117
+ function matchingActionJobs(state, requestedId) {
118
+ const jobs = new Map();
119
+ for (const job of state.asyncJobs.values())
120
+ jobs.set(job.asyncId, job);
121
+ for (const job of state.fleetJobs?.values() ?? [])
122
+ jobs.set(job.asyncId, job);
123
+ const exact = jobs.get(requestedId);
124
+ if (exact)
125
+ return [exact];
126
+ return [...jobs.values()].filter((job) => job.asyncId.startsWith(requestedId));
124
127
  }
125
- export function renderSubagentCall(args, theme, context, options) {
128
+ export function resolveSubagentActionDisplayTitle(args, state) {
129
+ const requestedId = (args.id || args.runId || "").trim();
130
+ if (!requestedId)
131
+ return undefined;
132
+ const matches = matchingActionJobs(state, requestedId);
133
+ if (matches.length !== 1)
134
+ return undefined;
135
+ const job = matches[0];
136
+ const steps = job.steps ?? [];
137
+ if (Number.isInteger(args.index)) {
138
+ const step = steps.find((candidate) => candidate.index === args.index) ?? steps[args.index];
139
+ return step ? childRowLabel(step) : undefined;
140
+ }
141
+ if (steps.length === 1)
142
+ return childRowLabel(steps[0]);
143
+ if (steps.length > 1)
144
+ return `${job.mode ?? "subagent"} run`;
145
+ if (job.agents?.length === 1)
146
+ return job.agents[0];
147
+ if (job.agents?.length)
148
+ return `${job.mode ?? "subagent"} run`;
149
+ return undefined;
150
+ }
151
+ export function renderSubagentCall(rawArgs, theme, context, options) {
152
+ const args = resolveSubagentRequestParams(rawArgs);
126
153
  if (args.action) {
127
- const target = args.id || args.runId || "";
154
+ const resultTitle = context?.result?.details?.displayTitle;
155
+ const resolvedTitle = resultTitle || options?.resolveActionTitle?.(args);
156
+ const target = resolvedTitle || args.id || args.runId || "";
128
157
  return new Text(`${theme.fg("toolTitle", theme.bold("subagent "))}${args.action}${target ? ` ${theme.fg("accent", target)}` : ""}`, 0, 0);
129
158
  }
130
159
  const isParallel = (args.tasks?.length ?? 0) > 0;
131
160
  const parallelCount = effectiveParallelTaskCount(args.tasks);
132
- const asyncMark = callIsAsync(args, options?.asyncByDefault) ? `${theme.fg("warning", "async")} ` : "";
161
+ const asyncMark = subagentLaunchRunsAsync(args) ? `${theme.fg("warning", "async")} ` : "";
133
162
  if (args.chain?.length) {
134
163
  return new Text(`${theme.fg("toolTitle", theme.bold("subagent "))}${asyncMark}chain (${args.chain.length})`, 0, 0);
135
164
  }
@@ -161,7 +190,7 @@ function ensureSubagentResultAnimation(context) {
161
190
  if (state.frame === undefined)
162
191
  state.frame = 0;
163
192
  state.subagentResultAnimationTimer = setInterval(() => {
164
- state.frame = ((state.frame ?? 0) + 1) % 10;
193
+ state.frame = (state.frame ?? 0) + 1;
165
194
  try {
166
195
  const entries = state.animEntries;
167
196
  if (entries?.length && typeof context.requestRender === "function") {
@@ -175,7 +204,7 @@ function ensureSubagentResultAnimation(context) {
175
204
  }
176
205
  }
177
206
  catch { }
178
- }, 80);
207
+ }, SUBAGENT_PAINT_INTERVAL_MS);
179
208
  }
180
209
  function isSlashResultError(result) {
181
210
  return result.details?.results.some((entry) => entry.exitCode !== 0 && entry.progress?.status !== "running") || false;
@@ -249,9 +278,8 @@ export default function registerSubagentExtension(pi) {
249
278
  ensureAccessibleDir(RESULTS_DIR);
250
279
  ensureAccessibleDir(ASYNC_DIR);
251
280
  cleanupOldChainDirs();
252
- const config = loadConfig();
281
+ const config = normalizeAsyncLaunchConfig(loadConfig());
253
282
  const waitToolConfig = resolveWaitToolConfig(config.waitTool);
254
- const asyncByDefault = config.asyncByDefault === true;
255
283
  const tempArtifactsDir = getArtifactsDir(null);
256
284
  cleanupAllArtifactDirs(DEFAULT_ARTIFACT_CONFIG.cleanupDays);
257
285
  const state = {
@@ -349,10 +377,8 @@ export default function registerSubagentExtension(pi) {
349
377
  resolveLaunchReady();
350
378
  };
351
379
  pendingLaunches.add(launchReadyPromise);
352
- if ((params.task || params.tasks || params.chain) && !params.clarify &&
353
- (params.async === true || (params.async === undefined && asyncByDefault) || config.forceTopLevelAsync === true)) {
380
+ if (isAsyncSubagentExecution(resolveSubagentRequestParams(params)))
354
381
  pendingAsyncLaunches.add(launchReadyPromise);
355
- }
356
382
  try {
357
383
  const currentGeneration = generation;
358
384
  signal?.throwIfAborted();
@@ -361,7 +387,7 @@ export default function registerSubagentExtension(pi) {
361
387
  if (generation !== currentGeneration)
362
388
  throw new Error("Subagent session changed before initialization");
363
389
  return createSubagentExecutor({
364
- pi, state, config, asyncByDefault,
390
+ pi, state, config,
365
391
  waitToolEnabled: waitToolConfig.enabled,
366
392
  handleScheduledRunAction: (params, ctx) => scheduledRunManager.handleToolCall(params, ctx),
367
393
  watchdog: mainWatchdog, tempArtifactsDir, getSubagentSessionRoot, expandTilde,
@@ -416,10 +442,16 @@ export default function registerSubagentExtension(pi) {
416
442
  return new Text(theme.fg(details.state === "recovered" ? "warning" : "error", formatSteeringNotice(details)), 0, 0);
417
443
  });
418
444
  pi.registerMessageRenderer(SUBAGENT_CONTROL_MESSAGE_TYPE, () => undefined);
419
- const executeSubagentCollapsed = (id, params, signal, onUpdate, ctx) => {
445
+ const executeSubagentCollapsed = async (id, params, signal, onUpdate, ctx) => {
420
446
  if (ctx.hasUI)
421
447
  ctx.ui.setToolsExpanded(false);
422
- return executor.execute(id, params, signal, onUpdate, ctx);
448
+ const displayTitle = params.action === "steer"
449
+ ? resolveSubagentActionDisplayTitle(params, state)
450
+ : undefined;
451
+ const result = await executor.execute(id, params, signal, onUpdate, ctx);
452
+ if (displayTitle && result.details)
453
+ result.details.displayTitle = displayTitle;
454
+ return result;
423
455
  };
424
456
  const slashBridge = registerSlashSubagentBridge({
425
457
  events: pi.events,
@@ -453,7 +485,9 @@ export default function registerSubagentExtension(pi) {
453
485
  execute(id, params, signal, onUpdate, ctx) {
454
486
  return executeSubagentCollapsed(id, params, signal, onUpdate, ctx);
455
487
  },
456
- renderCall: (args, theme, context) => renderSubagentCall(args, theme, context, { asyncByDefault }),
488
+ renderCall: (args, theme, context) => renderSubagentCall(args, theme, context, {
489
+ resolveActionTitle: (params) => resolveSubagentActionDisplayTitle(params, state),
490
+ }),
457
491
  renderResult(result, options, theme, context) {
458
492
  if (subagentResultIsRunning(result)) {
459
493
  ensureSubagentResultAnimation(context);