@ashx-j/lunr 0.2.22 → 0.2.23

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 (132) hide show
  1. package/CHANGELOG.md +3 -1
  2. package/README.md +5 -3
  3. package/dist/builtin-extensions/lunr-browser.d.ts +7 -1
  4. package/dist/builtin-extensions/lunr-browser.d.ts.map +1 -1
  5. package/dist/builtin-extensions/lunr-browser.js +116 -3
  6. package/dist/builtin-extensions/lunr-browser.js.map +1 -1
  7. package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts.map +1 -1
  8. package/dist/builtin-extensions/pi-subagents/src/extension/index.js +1 -1
  9. package/dist/builtin-extensions/pi-subagents/src/extension/index.js.map +1 -1
  10. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-wait.d.ts +2 -0
  11. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-wait.d.ts.map +1 -1
  12. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-wait.js +9 -0
  13. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-wait.js.map +1 -1
  14. package/dist/builtin-extensions/pi-subagents/src/runs/background/wait-tool.d.ts.map +1 -1
  15. package/dist/builtin-extensions/pi-subagents/src/runs/background/wait-tool.js +41 -3
  16. package/dist/builtin-extensions/pi-subagents/src/runs/background/wait-tool.js.map +1 -1
  17. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts.map +1 -1
  18. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js +4 -0
  19. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js.map +1 -1
  20. package/dist/builtin-extensions/pi-subagents/src/tui/communication.d.ts +1 -1
  21. package/dist/builtin-extensions/pi-subagents/src/tui/communication.d.ts.map +1 -1
  22. package/dist/builtin-extensions/pi-subagents/src/tui/communication.js +3 -2
  23. package/dist/builtin-extensions/pi-subagents/src/tui/communication.js.map +1 -1
  24. package/dist/core/agent-session.d.ts +19 -0
  25. package/dist/core/agent-session.d.ts.map +1 -1
  26. package/dist/core/agent-session.js +230 -103
  27. package/dist/core/agent-session.js.map +1 -1
  28. package/dist/core/settings-manager.d.ts +4 -0
  29. package/dist/core/settings-manager.d.ts.map +1 -1
  30. package/dist/core/settings-manager.js +9 -0
  31. package/dist/core/settings-manager.js.map +1 -1
  32. package/dist/core/subagent-wait-interruption.d.ts +11 -0
  33. package/dist/core/subagent-wait-interruption.d.ts.map +1 -0
  34. package/dist/core/subagent-wait-interruption.js +19 -0
  35. package/dist/core/subagent-wait-interruption.js.map +1 -0
  36. package/dist/core/system-prompt.d.ts.map +1 -1
  37. package/dist/core/system-prompt.js +3 -1
  38. package/dist/core/system-prompt.js.map +1 -1
  39. package/dist/core/tools/render-utils.d.ts +3 -3
  40. package/dist/core/tools/render-utils.d.ts.map +1 -1
  41. package/dist/core/tools/render-utils.js +3 -3
  42. package/dist/core/tools/render-utils.js.map +1 -1
  43. package/dist/modes/interactive/components/assistant-message.d.ts +10 -2
  44. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  45. package/dist/modes/interactive/components/assistant-message.js +29 -5
  46. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  47. package/dist/modes/interactive/components/copyable-text.d.ts +2 -6
  48. package/dist/modes/interactive/components/copyable-text.d.ts.map +1 -1
  49. package/dist/modes/interactive/components/copyable-text.js +26 -35
  50. package/dist/modes/interactive/components/copyable-text.js.map +1 -1
  51. package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
  52. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  53. package/dist/modes/interactive/components/settings-selector.js +13 -0
  54. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  55. package/dist/modes/interactive/components/thinking-tail.d.ts +9 -2
  56. package/dist/modes/interactive/components/thinking-tail.d.ts.map +1 -1
  57. package/dist/modes/interactive/components/thinking-tail.js +43 -1
  58. package/dist/modes/interactive/components/thinking-tail.js.map +1 -1
  59. package/dist/modes/interactive/components/tool-execution.d.ts +4 -3
  60. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  61. package/dist/modes/interactive/components/tool-execution.js +34 -45
  62. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  63. package/dist/modes/interactive/interactive-mode.d.ts +6 -0
  64. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  65. package/dist/modes/interactive/interactive-mode.js +118 -39
  66. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  67. package/dist/modes/interactive/theme/theme.d.ts +1 -0
  68. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  69. package/dist/modes/interactive/theme/theme.js +23 -0
  70. package/dist/modes/interactive/theme/theme.js.map +1 -1
  71. package/dist/node-runtime/{agent-session-services-NQ6GKOIK.js → agent-session-services-GZVSOGYR.js} +7 -7
  72. package/dist/node-runtime/{builtin-extensions-GVC6IFDU.js → builtin-extensions-QVCXNQT4.js} +3 -3
  73. package/dist/node-runtime/{chunk-6B2POMK7.js → chunk-6VYZ5UFP.js} +1 -1
  74. package/dist/node-runtime/{chunk-VJPTNWAP.js → chunk-DD5YWFSJ.js} +1 -1
  75. package/dist/node-runtime/{chunk-TDL7UWJ7.js → chunk-DQMGU675.js} +173 -57
  76. package/dist/node-runtime/{chunk-V2A7CEXK.js → chunk-EKVFP6YF.js} +2 -2
  77. package/dist/node-runtime/{chunk-5BNJ4JUH.js → chunk-GJ2RDBRL.js} +1 -1
  78. package/dist/node-runtime/{chunk-GNN7H3B7.js → chunk-GS42T6MX.js} +1 -1
  79. package/dist/node-runtime/{chunk-PC2Z5TKZ.js → chunk-HMBGROQK.js} +3 -3
  80. package/dist/node-runtime/{chunk-GJSAD4AB.js → chunk-HPUP7E4T.js} +10 -10
  81. package/dist/node-runtime/{chunk-IVXYUAJE.js → chunk-IF24A3FH.js} +1 -1
  82. package/dist/node-runtime/{chunk-BFNXONG3.js → chunk-LFZDVKSP.js} +2 -2
  83. package/dist/node-runtime/{chunk-EQ63ANHL.js → chunk-LLUIPGDR.js} +210 -125
  84. package/dist/node-runtime/{chunk-ORH324EP.js → chunk-PCEW76PM.js} +3 -3
  85. package/dist/node-runtime/{chunk-WADV2VDS.js → chunk-QAZOULTU.js} +7 -0
  86. package/dist/node-runtime/{chunk-6427XAWG.js → chunk-QH6YL7VK.js} +3 -3
  87. package/dist/node-runtime/{chunk-7QWNYZDI.js → chunk-RDDAZOMU.js} +16 -0
  88. package/dist/node-runtime/{chunk-QDGKKKUM.js → chunk-RIR5EDDJ.js} +1 -1
  89. package/dist/node-runtime/{chunk-H3XF4UVQ.js → chunk-RWTKOC3B.js} +1 -1
  90. package/dist/node-runtime/{chunk-IQIEU7SB.js → chunk-TVJUOJZJ.js} +2 -2
  91. package/dist/node-runtime/{chunk-UAFLNHSH.js → chunk-W64JC4E7.js} +8 -8
  92. package/dist/node-runtime/{chunk-LMBNLFXQ.js → chunk-X3URCYCU.js} +4 -4
  93. package/dist/node-runtime/{chunk-VSSXXL36.js → chunk-YDO2F2IA.js} +2 -2
  94. package/dist/node-runtime/{chunk-IWOWXRWU.js → chunk-YHQ3NBB3.js} +15 -15
  95. package/dist/node-runtime/cli-runtime.js +1 -1
  96. package/dist/node-runtime/{command-3JHHXOPX.js → command-PDVTBRWA.js} +3 -3
  97. package/dist/node-runtime/{commands-2JVNFGMQ.js → commands-XDCFGIGW.js} +5 -5
  98. package/dist/node-runtime/{export-html-KBFVV63J.js → export-html-KSOIMVPI.js} +1 -1
  99. package/dist/node-runtime/{extension-input-K24TVKVW.js → extension-input-E7BJZ35H.js} +4 -4
  100. package/dist/node-runtime/{extension-selector-SPNF7EDR.js → extension-selector-WU42XDTU.js} +4 -4
  101. package/dist/node-runtime/{gateway-VFO6SFKK.js → gateway-AYWM5DIO.js} +7 -7
  102. package/dist/node-runtime/index.js +17 -17
  103. package/dist/node-runtime/{install-cli-C3CF7O7N.js → install-cli-3BO4MMQ6.js} +1 -1
  104. package/dist/node-runtime/{interactive-mode-LOM6AIF6.js → interactive-mode-4MFRKAHG.js} +13 -13
  105. package/dist/node-runtime/{interactive-view-IDSCPPK7.js → interactive-view-TUZHCQM3.js} +5 -5
  106. package/dist/node-runtime/{lunr-browser-YNW7FTYX.js → lunr-browser-RYFIIVJN.js} +91 -4
  107. package/dist/node-runtime/{lunr-cron-BKHGNBKT.js → lunr-cron-YWXEPZMQ.js} +4 -4
  108. package/dist/node-runtime/main.js +14 -14
  109. package/dist/node-runtime/{mobile-commands-HMP25WBC.js → mobile-commands-LVHDAUDP.js} +4 -4
  110. package/dist/node-runtime/{package-manager-cli-VCK4E2RC.js → package-manager-cli-6GT3Z3CK.js} +9 -9
  111. package/dist/node-runtime/{pi-subagents-FEUDLJOE.js → pi-subagents-CDWK2AKY.js} +44 -10
  112. package/dist/node-runtime/rpc-entry.js +14 -14
  113. package/dist/node-runtime/{rpc-mode-P5ODXPEG.js → rpc-mode-O3XV4SID.js} +2 -2
  114. package/dist/node-runtime/{session-selector-LHRMJOKT.js → session-selector-GIM4DHQ2.js} +4 -4
  115. package/dist/node-runtime/{settings-manager-UUZ6NUXI.js → settings-manager-6VEMXRDL.js} +1 -1
  116. package/dist/node-runtime/{setup-NRXVZBLJ.js → setup-5SR2J35A.js} +1 -1
  117. package/dist/node-runtime/{subagent-executor-MLUMBWCP.js → subagent-executor-M5VWB3D3.js} +8 -4
  118. package/dist/node-runtime/{theme-U7IGOA7O.js → theme-UOO5RVFN.js} +1 -1
  119. package/dist/node-runtime/{update-cli-4TP25CNW.js → update-cli-UVKJWRRJ.js} +1 -1
  120. package/docs/features.md +2 -0
  121. package/docs/settings.md +1 -0
  122. package/docs/usage.md +2 -2
  123. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  124. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  125. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  126. package/examples/extensions/gondolin/package-lock.json +2 -2
  127. package/examples/extensions/gondolin/package.json +1 -1
  128. package/examples/extensions/sandbox/package-lock.json +2 -2
  129. package/examples/extensions/sandbox/package.json +1 -1
  130. package/examples/extensions/with-deps/package-lock.json +2 -2
  131. package/examples/extensions/with-deps/package.json +1 -1
  132. package/package.json +4 -4
package/CHANGELOG.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # Changelog
2
2
 
3
- lunR is derived from pi. The published npm package is **`@ashx-j/lunr@0.2.21`**. Versioned sections below (`0.80.x` and earlier) are **upstream pi history** and do not describe lunR releases.
3
+ lunR is derived from pi. The published npm package is **`@ashx-j/lunr@0.2.23`**. Versioned sections below (`0.80.x` and earlier) are **upstream pi history** and do not describe lunR releases.
4
+
5
+ ## [0.2.23] - 2026-09-21
4
6
 
5
7
  ## [0.2.22] - 2026-09-20
6
8
 
package/README.md CHANGED
@@ -151,7 +151,7 @@ Standard editing keybindings for delete word, undo, etc. See [docs/keybindings.m
151
151
 
152
152
  ### Copyable response blocks
153
153
 
154
- Assistant responses can place a Copy button under a selected reusable excerpt, prompt, or code fragment. Clicking it copies only that block. Success or failure appears beside the button.
154
+ Assistant responses can mark a reusable excerpt, prompt, or code fragment as click-to-copy. The marked section appears in a plain background box. Click anywhere in that box to copy only its contents.
155
155
 
156
156
  The source format is a fenced Markdown block with the exact info string `lunr-copy`:
157
157
 
@@ -161,7 +161,7 @@ npm test
161
161
  ```
162
162
  ````
163
163
 
164
- The payload starts after the opening fence's line break and ends before the line break immediately preceding the closing fence. lunR copies it without trimming whitespace and excludes both fence lines. A longer backtick or tilde fence can contain shorter fenced code. Incomplete streamed blocks show their received text but no Copy button. Print, JSON, RPC, and gateway output keep the raw fences and never write to the clipboard.
164
+ The payload starts after the opening fence's line break and ends before the line break immediately preceding the closing fence. lunR copies it without trimming whitespace and excludes both fence lines. A longer backtick or tilde fence can contain shorter fenced code. Incomplete streamed blocks remain readable but become clickable only after the closing fence arrives. Print, JSON, RPC, and gateway output keep the raw fences and never write to the clipboard.
165
165
 
166
166
  ### Commands
167
167
 
@@ -234,13 +234,15 @@ Click a thinking or tool card to expand or collapse that item. `app.tools.expand
234
234
 
235
235
  Submit messages while the agent is working:
236
236
 
237
- - **Enter** queues a *steering* message, delivered after the current assistant turn finishes executing its tool calls
237
+ - **Enter** queues a *steering* message, delivered after the current assistant turn finishes executing its tool calls. If the parent is executing `subagent_wait`, Enter instead ends only that wait. The current tool batch settles, background children keep running, and the text starts a fresh parent turn without appearing in the steering queue.
238
238
  - **Alt+Enter** queues a *follow-up* message, delivered only after the agent finishes all work
239
239
  - **Escape** aborts and restores queued messages to editor
240
240
  - **Alt+Up** retrieves queued messages back to editor
241
241
 
242
242
  On Windows Terminal, `Alt+Enter` is fullscreen by default. Remap it in [docs/terminal-setup.md](docs/terminal-setup.md) so lunR can receive the follow-up shortcut.
243
243
 
244
+ Parallel tools beside `subagent_wait` finish before the fresh Enter prompt starts. Alt+Enter and Enter outside an executing wait keep their normal queue behavior.
245
+
244
246
  Configure delivery in [settings](docs/settings.md): `steeringMode` and `followUpMode` can be `"one-at-a-time"` (default, waits for response) or `"all"` (delivers all queued at once). `transport` selects provider transport preference (`"sse"`, `"websocket"`, or `"auto"`) for providers that support multiple transports.
245
247
 
246
248
  ---
@@ -1,3 +1,9 @@
1
- import type { ExtensionAPI } from "../core/extensions/types.ts";
1
+ import { Text } from "@ashx-j/lunr-tui";
2
+ import type { ExtensionAPI, ToolRenderContext } from "../core/extensions/types.ts";
3
+ import { type BrowserInput } from "../core/browser/schema.ts";
4
+ import type { Theme } from "../modes/interactive/theme/theme.ts";
5
+ export type BrowserActivityState = "pending" | "success" | "error";
6
+ export declare function describeBrowserActivity(input: BrowserInput, state: BrowserActivityState): string;
7
+ export declare function renderBrowserActivity(args: BrowserInput, theme: Theme, context: ToolRenderContext<unknown, BrowserInput>): Text;
2
8
  export default function browserExtension(pi: ExtensionAPI): void;
3
9
  //# sourceMappingURL=lunr-browser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"lunr-browser.d.ts","sourceRoot":"","sources":["../../src/builtin-extensions/lunr-browser.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAKhE,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAgD/D","sourcesContent":["import { Text } from \"@ashx-j/lunr-tui\";\nimport type { ExtensionAPI } from \"../core/extensions/types.ts\";\nimport { onBrowserEnabledChange, readBrowserSettings } from \"../core/browser/settings.ts\";\nimport { BROWSER_DESCRIPTION, BrowserParams } from \"../core/browser/schema.ts\";\nimport type { BrowserSession } from \"../core/browser/runtime.ts\";\n\nexport default function browserExtension(pi: ExtensionAPI): void {\n\tlet enabled = readBrowserSettings().enabled;\n\tlet unsubscribe: (() => void) | undefined;\n\tlet session: BrowserSession | undefined;\n\tlet generation = 0;\n\tconst close = async () => {\n\t\tgeneration++;\n\t\tconst previous = session;\n\t\tsession = undefined;\n\t\tawait previous?.close();\n\t};\n\tpi.on(\"session_shutdown\", async () => {\n\t\tunsubscribe?.();\n\t\tunsubscribe = undefined;\n\t\tawait close();\n\t});\n\tpi.on(\"session_start\", async () => {\n\t\tawait close();\n\t\tenabled = readBrowserSettings().enabled;\n\t\tunsubscribe?.();\n\t\tunsubscribe = onBrowserEnabledChange((value) => {\n\t\t\tenabled = value;\n\t\t\tif (!enabled) void close().catch(() => undefined);\n\t\t});\n\t});\n\tpi.on(\"agent_end\", async (event) => {\n\t\tif (event.messages.some((message) => message.role === \"assistant\" && message.stopReason === \"aborted\")) await close();\n\t});\n\tpi.registerTool({\n\t\tname: \"browser\",\n\t\tlabel: \"Browser\",\n\t\tdescription: BROWSER_DESCRIPTION,\n\t\tparameters: BrowserParams,\n\t\tasync execute(_id, input, signal) {\n\t\t\tif (!enabled) {\n\t\t\t\tawait close();\n\t\t\t\tthrow new Error(\"Browser is disabled. The user can enable Browser in /settings.\");\n\t\t\t}\n\t\t\tconst current = generation;\n\t\t\tconst { BrowserSession } = await import(\"../core/browser/runtime.ts\");\n\t\t\tif (current !== generation || signal?.aborted) throw new Error(\"Browser call cancelled during initialization.\");\n\t\t\tsession ??= new BrowserSession(readBrowserSettings().allowPrivate);\n\t\t\treturn session.run(input, signal);\n\t\t},\n\t\trenderCall(args, theme) {\n\t\t\treturn new Text(theme.fg(\"toolTitle\", `browser ${args.action ?? \"\"}`), 0, 0);\n\t\t},\n\t});\n}\n"]}
1
+ {"version":3,"file":"lunr-browser.d.ts","sourceRoot":"","sources":["../../src/builtin-extensions/lunr-browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAGnF,OAAO,EAAuB,KAAK,YAAY,EAAiB,MAAM,2BAA2B,CAAC;AAElG,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qCAAqC,CAAC;AAIjE,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAMnE,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,oBAAoB,GAAG,MAAM,CAsDhG;AAoDD,wBAAgB,qBAAqB,CACpC,IAAI,EAAE,YAAY,EAClB,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,iBAAiB,CAAC,OAAO,EAAE,YAAY,CAAC,GAC/C,IAAI,CAcN;AAED,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA8C/D","sourcesContent":["import { Text } from \"@ashx-j/lunr-tui\";\nimport type { ExtensionAPI, ToolRenderContext } from \"../core/extensions/types.ts\";\nimport { toolStatusDotFromContext } from \"../core/tools/render-utils.ts\";\nimport { onBrowserEnabledChange, readBrowserSettings } from \"../core/browser/settings.ts\";\nimport { BROWSER_DESCRIPTION, type BrowserInput, BrowserParams } from \"../core/browser/schema.ts\";\nimport type { BrowserSession } from \"../core/browser/runtime.ts\";\nimport type { Theme } from \"../modes/interactive/theme/theme.ts\";\n\nconst BROWSER_ACTIVITY_TICK_MS = 18;\n\nexport type BrowserActivityState = \"pending\" | \"success\" | \"error\";\n\nfunction browserTarget(input: BrowserInput): string {\n\treturn input.label || input.name || input.role || \"an element\";\n}\n\nexport function describeBrowserActivity(input: BrowserInput, state: BrowserActivityState): string {\n\tconst failed = state === \"error\";\n\tconst pending = state === \"pending\";\n\tif (input.action === \"navigate\") {\n\t\tconst url = input.url || \"the page\";\n\t\treturn failed ? `Failed to open ${url}` : pending ? `Opening ${url}` : `Opened ${url}`;\n\t}\n\tif (input.action === \"inspect\") {\n\t\treturn failed ? \"Failed to inspect the page\" : pending ? \"Inspecting the page\" : \"Inspected the page\";\n\t}\n\tif (input.action === \"screenshot\") {\n\t\treturn failed ? \"Failed to capture a screenshot\" : pending ? \"Capturing a screenshot\" : \"Captured a screenshot\";\n\t}\n\tif (input.action === \"close\") {\n\t\treturn failed ? \"Failed to close the browser\" : pending ? \"Closing the browser\" : \"Closed the browser\";\n\t}\n\tif (input.action === \"tabs\") {\n\t\tif (input.operation === \"create\") {\n\t\t\tconst destination = input.url ? ` at ${input.url}` : \"\";\n\t\t\treturn failed\n\t\t\t\t? `Failed to open a new tab${destination}`\n\t\t\t\t: pending\n\t\t\t\t\t? `Opening a new tab${destination}`\n\t\t\t\t\t: `Opened a new tab${destination}`;\n\t\t}\n\t\tif (input.operation === \"select\") {\n\t\t\tconst tab = input.tab ? ` ${input.tab}` : \"\";\n\t\t\treturn failed ? `Failed to select tab${tab}` : pending ? `Selecting tab${tab}` : `Selected tab${tab}`;\n\t\t}\n\t\tif (input.operation === \"close\") {\n\t\t\tconst tab = input.tab ? ` ${input.tab}` : \"\";\n\t\t\treturn failed ? `Failed to close tab${tab}` : pending ? `Closing tab${tab}` : `Closed tab${tab}`;\n\t\t}\n\t\treturn failed ? \"Failed to list browser tabs\" : pending ? \"Listing browser tabs\" : \"Listed browser tabs\";\n\t}\n\n\tconst target = browserTarget(input);\n\tif (input.interaction === \"fill\") {\n\t\treturn failed ? `Failed to fill ${target}` : pending ? `Filling ${target}` : `Filled ${target}`;\n\t}\n\tif (input.interaction === \"select\") {\n\t\treturn failed ? `Failed to select an option in ${target}` : pending ? `Selecting an option in ${target}` : `Selected an option in ${target}`;\n\t}\n\tif (input.interaction === \"check\") {\n\t\tconst verb = input.checked === false ? \"uncheck\" : \"check\";\n\t\tconst active = input.checked === false ? \"Unchecking\" : \"Checking\";\n\t\tconst done = input.checked === false ? \"Unchecked\" : \"Checked\";\n\t\treturn failed ? `Failed to ${verb} ${target}` : pending ? `${active} ${target}` : `${done} ${target}`;\n\t}\n\tif (input.interaction === \"press\") {\n\t\tconst key = input.value || \"a key\";\n\t\treturn failed ? `Failed to press ${key} on ${target}` : pending ? `Pressing ${key} on ${target}` : `Pressed ${key} on ${target}`;\n\t}\n\treturn failed ? `Failed to click ${target}` : pending ? `Clicking ${target}` : `Clicked ${target}`;\n}\n\nclass BrowserActivityText extends Text {\n\tprivate activity = \"\";\n\tprivate visibleLength = 0;\n\tprivate timer?: ReturnType<typeof setTimeout>;\n\tprivate format: (text: string) => string = (text) => text;\n\tprivate requestRender: () => void = () => {};\n\n\tconstructor() {\n\t\tsuper(\"\", 0, 0);\n\t}\n\n\tsetActivity(activity: string, format: (text: string) => string, animate: boolean, requestRender?: () => void): void {\n\t\tthis.format = format;\n\t\tthis.requestRender = requestRender ?? (() => {});\n\t\tif (activity === this.activity) {\n\t\t\tthis.paint();\n\t\t\treturn;\n\t\t}\n\t\tthis.stop();\n\t\tthis.activity = activity;\n\t\tthis.visibleLength = animate ? Math.min(1, Array.from(activity).length) : Array.from(activity).length;\n\t\tthis.paint();\n\t\tif (animate && this.visibleLength < Array.from(activity).length) this.schedule();\n\t}\n\n\tdispose(): void {\n\t\tthis.stop();\n\t}\n\n\tprivate schedule(): void {\n\t\tthis.timer = setTimeout(() => {\n\t\t\tthis.timer = undefined;\n\t\t\tthis.visibleLength++;\n\t\t\tthis.paint();\n\t\t\tthis.requestRender();\n\t\t\tif (this.visibleLength < Array.from(this.activity).length) this.schedule();\n\t\t}, BROWSER_ACTIVITY_TICK_MS);\n\t\tthis.timer.unref?.();\n\t}\n\n\tprivate paint(): void {\n\t\tthis.setText(this.format(Array.from(this.activity).slice(0, this.visibleLength).join(\"\")));\n\t}\n\n\tprivate stop(): void {\n\t\tif (this.timer) clearTimeout(this.timer);\n\t\tthis.timer = undefined;\n\t}\n}\n\nexport function renderBrowserActivity(\n\targs: BrowserInput,\n\ttheme: Theme,\n\tcontext: ToolRenderContext<unknown, BrowserInput>,\n): Text {\n\tconst component = context.lastComponent instanceof BrowserActivityText\n\t\t? context.lastComponent\n\t\t: new BrowserActivityText();\n\tconst state: BrowserActivityState = context.isPartial ? \"pending\" : context.isError ? \"error\" : \"success\";\n\tconst activity = describeBrowserActivity(args, state);\n\tconst animate = context.executionStarted || context.result === undefined;\n\tcomponent.setActivity(\n\t\tactivity,\n\t\t(text) => `${toolStatusDotFromContext(context, theme)} ${theme.fg(state === \"error\" ? \"error\" : \"toolTitle\", text)}`,\n\t\tanimate,\n\t\tcontext.requestRender,\n\t);\n\treturn component;\n}\n\nexport default function browserExtension(pi: ExtensionAPI): void {\n\tlet enabled = readBrowserSettings().enabled;\n\tlet unsubscribe: (() => void) | undefined;\n\tlet session: BrowserSession | undefined;\n\tlet generation = 0;\n\tconst close = async () => {\n\t\tgeneration++;\n\t\tconst previous = session;\n\t\tsession = undefined;\n\t\tawait previous?.close();\n\t};\n\tpi.on(\"session_shutdown\", async () => {\n\t\tunsubscribe?.();\n\t\tunsubscribe = undefined;\n\t\tawait close();\n\t});\n\tpi.on(\"session_start\", async () => {\n\t\tawait close();\n\t\tenabled = readBrowserSettings().enabled;\n\t\tunsubscribe?.();\n\t\tunsubscribe = onBrowserEnabledChange((value) => {\n\t\t\tenabled = value;\n\t\t\tif (!enabled) void close().catch(() => undefined);\n\t\t});\n\t});\n\tpi.on(\"agent_end\", async (event) => {\n\t\tif (event.messages.some((message) => message.role === \"assistant\" && message.stopReason === \"aborted\")) await close();\n\t});\n\tpi.registerTool({\n\t\tname: \"browser\",\n\t\tlabel: \"Browser\",\n\t\tdescription: BROWSER_DESCRIPTION,\n\t\tparameters: BrowserParams,\n\t\tasync execute(_id, input, signal) {\n\t\t\tif (!enabled) {\n\t\t\t\tawait close();\n\t\t\t\tthrow new Error(\"Browser is disabled. The user can enable Browser in /settings.\");\n\t\t\t}\n\t\t\tconst current = generation;\n\t\t\tconst { BrowserSession } = await import(\"../core/browser/runtime.ts\");\n\t\t\tif (current !== generation || signal?.aborted) throw new Error(\"Browser call cancelled during initialization.\");\n\t\t\tsession ??= new BrowserSession(readBrowserSettings().allowPrivate);\n\t\t\treturn session.run(input, signal);\n\t\t},\n\t\trenderCall: renderBrowserActivity,\n\t});\n}\n"]}
@@ -1,6 +1,121 @@
1
1
  import { Text } from "@ashx-j/lunr-tui";
2
+ import { toolStatusDotFromContext } from "../core/tools/render-utils.js";
2
3
  import { onBrowserEnabledChange, readBrowserSettings } from "../core/browser/settings.js";
3
4
  import { BROWSER_DESCRIPTION, BrowserParams } from "../core/browser/schema.js";
5
+ const BROWSER_ACTIVITY_TICK_MS = 18;
6
+ function browserTarget(input) {
7
+ return input.label || input.name || input.role || "an element";
8
+ }
9
+ export function describeBrowserActivity(input, state) {
10
+ const failed = state === "error";
11
+ const pending = state === "pending";
12
+ if (input.action === "navigate") {
13
+ const url = input.url || "the page";
14
+ return failed ? `Failed to open ${url}` : pending ? `Opening ${url}` : `Opened ${url}`;
15
+ }
16
+ if (input.action === "inspect") {
17
+ return failed ? "Failed to inspect the page" : pending ? "Inspecting the page" : "Inspected the page";
18
+ }
19
+ if (input.action === "screenshot") {
20
+ return failed ? "Failed to capture a screenshot" : pending ? "Capturing a screenshot" : "Captured a screenshot";
21
+ }
22
+ if (input.action === "close") {
23
+ return failed ? "Failed to close the browser" : pending ? "Closing the browser" : "Closed the browser";
24
+ }
25
+ if (input.action === "tabs") {
26
+ if (input.operation === "create") {
27
+ const destination = input.url ? ` at ${input.url}` : "";
28
+ return failed
29
+ ? `Failed to open a new tab${destination}`
30
+ : pending
31
+ ? `Opening a new tab${destination}`
32
+ : `Opened a new tab${destination}`;
33
+ }
34
+ if (input.operation === "select") {
35
+ const tab = input.tab ? ` ${input.tab}` : "";
36
+ return failed ? `Failed to select tab${tab}` : pending ? `Selecting tab${tab}` : `Selected tab${tab}`;
37
+ }
38
+ if (input.operation === "close") {
39
+ const tab = input.tab ? ` ${input.tab}` : "";
40
+ return failed ? `Failed to close tab${tab}` : pending ? `Closing tab${tab}` : `Closed tab${tab}`;
41
+ }
42
+ return failed ? "Failed to list browser tabs" : pending ? "Listing browser tabs" : "Listed browser tabs";
43
+ }
44
+ const target = browserTarget(input);
45
+ if (input.interaction === "fill") {
46
+ return failed ? `Failed to fill ${target}` : pending ? `Filling ${target}` : `Filled ${target}`;
47
+ }
48
+ if (input.interaction === "select") {
49
+ return failed ? `Failed to select an option in ${target}` : pending ? `Selecting an option in ${target}` : `Selected an option in ${target}`;
50
+ }
51
+ if (input.interaction === "check") {
52
+ const verb = input.checked === false ? "uncheck" : "check";
53
+ const active = input.checked === false ? "Unchecking" : "Checking";
54
+ const done = input.checked === false ? "Unchecked" : "Checked";
55
+ return failed ? `Failed to ${verb} ${target}` : pending ? `${active} ${target}` : `${done} ${target}`;
56
+ }
57
+ if (input.interaction === "press") {
58
+ const key = input.value || "a key";
59
+ return failed ? `Failed to press ${key} on ${target}` : pending ? `Pressing ${key} on ${target}` : `Pressed ${key} on ${target}`;
60
+ }
61
+ return failed ? `Failed to click ${target}` : pending ? `Clicking ${target}` : `Clicked ${target}`;
62
+ }
63
+ class BrowserActivityText extends Text {
64
+ activity = "";
65
+ visibleLength = 0;
66
+ timer;
67
+ format = (text) => text;
68
+ requestRender = () => { };
69
+ constructor() {
70
+ super("", 0, 0);
71
+ }
72
+ setActivity(activity, format, animate, requestRender) {
73
+ this.format = format;
74
+ this.requestRender = requestRender ?? (() => { });
75
+ if (activity === this.activity) {
76
+ this.paint();
77
+ return;
78
+ }
79
+ this.stop();
80
+ this.activity = activity;
81
+ this.visibleLength = animate ? Math.min(1, Array.from(activity).length) : Array.from(activity).length;
82
+ this.paint();
83
+ if (animate && this.visibleLength < Array.from(activity).length)
84
+ this.schedule();
85
+ }
86
+ dispose() {
87
+ this.stop();
88
+ }
89
+ schedule() {
90
+ this.timer = setTimeout(() => {
91
+ this.timer = undefined;
92
+ this.visibleLength++;
93
+ this.paint();
94
+ this.requestRender();
95
+ if (this.visibleLength < Array.from(this.activity).length)
96
+ this.schedule();
97
+ }, BROWSER_ACTIVITY_TICK_MS);
98
+ this.timer.unref?.();
99
+ }
100
+ paint() {
101
+ this.setText(this.format(Array.from(this.activity).slice(0, this.visibleLength).join("")));
102
+ }
103
+ stop() {
104
+ if (this.timer)
105
+ clearTimeout(this.timer);
106
+ this.timer = undefined;
107
+ }
108
+ }
109
+ export function renderBrowserActivity(args, theme, context) {
110
+ const component = context.lastComponent instanceof BrowserActivityText
111
+ ? context.lastComponent
112
+ : new BrowserActivityText();
113
+ const state = context.isPartial ? "pending" : context.isError ? "error" : "success";
114
+ const activity = describeBrowserActivity(args, state);
115
+ const animate = context.executionStarted || context.result === undefined;
116
+ component.setActivity(activity, (text) => `${toolStatusDotFromContext(context, theme)} ${theme.fg(state === "error" ? "error" : "toolTitle", text)}`, animate, context.requestRender);
117
+ return component;
118
+ }
4
119
  export default function browserExtension(pi) {
5
120
  let enabled = readBrowserSettings().enabled;
6
121
  let unsubscribe;
@@ -48,9 +163,7 @@ export default function browserExtension(pi) {
48
163
  session ??= new BrowserSession(readBrowserSettings().allowPrivate);
49
164
  return session.run(input, signal);
50
165
  },
51
- renderCall(args, theme) {
52
- return new Text(theme.fg("toolTitle", `browser ${args.action ?? ""}`), 0, 0);
53
- },
166
+ renderCall: renderBrowserActivity,
54
167
  });
55
168
  }
56
169
  //# sourceMappingURL=lunr-browser.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"lunr-browser.js","sourceRoot":"","sources":["../../src/builtin-extensions/lunr-browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAE9C,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAC1F,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAG/E,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,EAAgB,EAAQ;IAChE,IAAI,OAAO,GAAG,mBAAmB,EAAE,CAAC,OAAO,CAAC;IAC5C,IAAI,WAAqC,CAAC;IAC1C,IAAI,OAAmC,CAAC;IACxC,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,MAAM,KAAK,GAAG,KAAK,IAAI,EAAE,CAAC;QACzB,UAAU,EAAE,CAAC;QACb,MAAM,QAAQ,GAAG,OAAO,CAAC;QACzB,OAAO,GAAG,SAAS,CAAC;QACpB,MAAM,QAAQ,EAAE,KAAK,EAAE,CAAC;IAAA,CACxB,CAAC;IACF,EAAE,CAAC,EAAE,CAAC,kBAAkB,EAAE,KAAK,IAAI,EAAE,CAAC;QACrC,WAAW,EAAE,EAAE,CAAC;QAChB,WAAW,GAAG,SAAS,CAAC;QACxB,MAAM,KAAK,EAAE,CAAC;IAAA,CACd,CAAC,CAAC;IACH,EAAE,CAAC,EAAE,CAAC,eAAe,EAAE,KAAK,IAAI,EAAE,CAAC;QAClC,MAAM,KAAK,EAAE,CAAC;QACd,OAAO,GAAG,mBAAmB,EAAE,CAAC,OAAO,CAAC;QACxC,WAAW,EAAE,EAAE,CAAC;QAChB,WAAW,GAAG,sBAAsB,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;YAC/C,OAAO,GAAG,KAAK,CAAC;YAChB,IAAI,CAAC,OAAO;gBAAE,KAAK,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAAA,CAClD,CAAC,CAAC;IAAA,CACH,CAAC,CAAC;IACH,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC;QACnC,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS,CAAC;YAAE,MAAM,KAAK,EAAE,CAAC;IAAA,CACtH,CAAC,CAAC;IACH,EAAE,CAAC,YAAY,CAAC;QACf,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,SAAS;QAChB,WAAW,EAAE,mBAAmB;QAChC,UAAU,EAAE,aAAa;QACzB,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE;YACjC,IAAI,CAAC,OAAO,EAAE,CAAC;gBACd,MAAM,KAAK,EAAE,CAAC;gBACd,MAAM,IAAI,KAAK,CAAC,gEAAgE,CAAC,CAAC;YACnF,CAAC;YACD,MAAM,OAAO,GAAG,UAAU,CAAC;YAC3B,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,4BAA4B,CAAC,CAAC;YACtE,IAAI,OAAO,KAAK,UAAU,IAAI,MAAM,EAAE,OAAO;gBAAE,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;YAChH,OAAO,KAAK,IAAI,cAAc,CAAC,mBAAmB,EAAE,CAAC,YAAY,CAAC,CAAC;YACnE,OAAO,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAAA,CAClC;QACD,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE;YACvB,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,WAAW,EAAE,WAAW,IAAI,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAAA,CAC7E;KACD,CAAC,CAAC;AAAA,CACH","sourcesContent":["import { Text } from \"@ashx-j/lunr-tui\";\nimport type { ExtensionAPI } from \"../core/extensions/types.ts\";\nimport { onBrowserEnabledChange, readBrowserSettings } from \"../core/browser/settings.ts\";\nimport { BROWSER_DESCRIPTION, BrowserParams } from \"../core/browser/schema.ts\";\nimport type { BrowserSession } from \"../core/browser/runtime.ts\";\n\nexport default function browserExtension(pi: ExtensionAPI): void {\n\tlet enabled = readBrowserSettings().enabled;\n\tlet unsubscribe: (() => void) | undefined;\n\tlet session: BrowserSession | undefined;\n\tlet generation = 0;\n\tconst close = async () => {\n\t\tgeneration++;\n\t\tconst previous = session;\n\t\tsession = undefined;\n\t\tawait previous?.close();\n\t};\n\tpi.on(\"session_shutdown\", async () => {\n\t\tunsubscribe?.();\n\t\tunsubscribe = undefined;\n\t\tawait close();\n\t});\n\tpi.on(\"session_start\", async () => {\n\t\tawait close();\n\t\tenabled = readBrowserSettings().enabled;\n\t\tunsubscribe?.();\n\t\tunsubscribe = onBrowserEnabledChange((value) => {\n\t\t\tenabled = value;\n\t\t\tif (!enabled) void close().catch(() => undefined);\n\t\t});\n\t});\n\tpi.on(\"agent_end\", async (event) => {\n\t\tif (event.messages.some((message) => message.role === \"assistant\" && message.stopReason === \"aborted\")) await close();\n\t});\n\tpi.registerTool({\n\t\tname: \"browser\",\n\t\tlabel: \"Browser\",\n\t\tdescription: BROWSER_DESCRIPTION,\n\t\tparameters: BrowserParams,\n\t\tasync execute(_id, input, signal) {\n\t\t\tif (!enabled) {\n\t\t\t\tawait close();\n\t\t\t\tthrow new Error(\"Browser is disabled. The user can enable Browser in /settings.\");\n\t\t\t}\n\t\t\tconst current = generation;\n\t\t\tconst { BrowserSession } = await import(\"../core/browser/runtime.ts\");\n\t\t\tif (current !== generation || signal?.aborted) throw new Error(\"Browser call cancelled during initialization.\");\n\t\t\tsession ??= new BrowserSession(readBrowserSettings().allowPrivate);\n\t\t\treturn session.run(input, signal);\n\t\t},\n\t\trenderCall(args, theme) {\n\t\t\treturn new Text(theme.fg(\"toolTitle\", `browser ${args.action ?? \"\"}`), 0, 0);\n\t\t},\n\t});\n}\n"]}
1
+ {"version":3,"file":"lunr-browser.js","sourceRoot":"","sources":["../../src/builtin-extensions/lunr-browser.ts"],"names":[],"mappings":"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","sourcesContent":["import { Text } from \"@ashx-j/lunr-tui\";\nimport type { ExtensionAPI, ToolRenderContext } from \"../core/extensions/types.ts\";\nimport { toolStatusDotFromContext } from \"../core/tools/render-utils.ts\";\nimport { onBrowserEnabledChange, readBrowserSettings } from \"../core/browser/settings.ts\";\nimport { BROWSER_DESCRIPTION, type BrowserInput, BrowserParams } from \"../core/browser/schema.ts\";\nimport type { BrowserSession } from \"../core/browser/runtime.ts\";\nimport type { Theme } from \"../modes/interactive/theme/theme.ts\";\n\nconst BROWSER_ACTIVITY_TICK_MS = 18;\n\nexport type BrowserActivityState = \"pending\" | \"success\" | \"error\";\n\nfunction browserTarget(input: BrowserInput): string {\n\treturn input.label || input.name || input.role || \"an element\";\n}\n\nexport function describeBrowserActivity(input: BrowserInput, state: BrowserActivityState): string {\n\tconst failed = state === \"error\";\n\tconst pending = state === \"pending\";\n\tif (input.action === \"navigate\") {\n\t\tconst url = input.url || \"the page\";\n\t\treturn failed ? `Failed to open ${url}` : pending ? `Opening ${url}` : `Opened ${url}`;\n\t}\n\tif (input.action === \"inspect\") {\n\t\treturn failed ? \"Failed to inspect the page\" : pending ? \"Inspecting the page\" : \"Inspected the page\";\n\t}\n\tif (input.action === \"screenshot\") {\n\t\treturn failed ? \"Failed to capture a screenshot\" : pending ? \"Capturing a screenshot\" : \"Captured a screenshot\";\n\t}\n\tif (input.action === \"close\") {\n\t\treturn failed ? \"Failed to close the browser\" : pending ? \"Closing the browser\" : \"Closed the browser\";\n\t}\n\tif (input.action === \"tabs\") {\n\t\tif (input.operation === \"create\") {\n\t\t\tconst destination = input.url ? ` at ${input.url}` : \"\";\n\t\t\treturn failed\n\t\t\t\t? `Failed to open a new tab${destination}`\n\t\t\t\t: pending\n\t\t\t\t\t? `Opening a new tab${destination}`\n\t\t\t\t\t: `Opened a new tab${destination}`;\n\t\t}\n\t\tif (input.operation === \"select\") {\n\t\t\tconst tab = input.tab ? ` ${input.tab}` : \"\";\n\t\t\treturn failed ? `Failed to select tab${tab}` : pending ? `Selecting tab${tab}` : `Selected tab${tab}`;\n\t\t}\n\t\tif (input.operation === \"close\") {\n\t\t\tconst tab = input.tab ? ` ${input.tab}` : \"\";\n\t\t\treturn failed ? `Failed to close tab${tab}` : pending ? `Closing tab${tab}` : `Closed tab${tab}`;\n\t\t}\n\t\treturn failed ? \"Failed to list browser tabs\" : pending ? \"Listing browser tabs\" : \"Listed browser tabs\";\n\t}\n\n\tconst target = browserTarget(input);\n\tif (input.interaction === \"fill\") {\n\t\treturn failed ? `Failed to fill ${target}` : pending ? `Filling ${target}` : `Filled ${target}`;\n\t}\n\tif (input.interaction === \"select\") {\n\t\treturn failed ? `Failed to select an option in ${target}` : pending ? `Selecting an option in ${target}` : `Selected an option in ${target}`;\n\t}\n\tif (input.interaction === \"check\") {\n\t\tconst verb = input.checked === false ? \"uncheck\" : \"check\";\n\t\tconst active = input.checked === false ? \"Unchecking\" : \"Checking\";\n\t\tconst done = input.checked === false ? \"Unchecked\" : \"Checked\";\n\t\treturn failed ? `Failed to ${verb} ${target}` : pending ? `${active} ${target}` : `${done} ${target}`;\n\t}\n\tif (input.interaction === \"press\") {\n\t\tconst key = input.value || \"a key\";\n\t\treturn failed ? `Failed to press ${key} on ${target}` : pending ? `Pressing ${key} on ${target}` : `Pressed ${key} on ${target}`;\n\t}\n\treturn failed ? `Failed to click ${target}` : pending ? `Clicking ${target}` : `Clicked ${target}`;\n}\n\nclass BrowserActivityText extends Text {\n\tprivate activity = \"\";\n\tprivate visibleLength = 0;\n\tprivate timer?: ReturnType<typeof setTimeout>;\n\tprivate format: (text: string) => string = (text) => text;\n\tprivate requestRender: () => void = () => {};\n\n\tconstructor() {\n\t\tsuper(\"\", 0, 0);\n\t}\n\n\tsetActivity(activity: string, format: (text: string) => string, animate: boolean, requestRender?: () => void): void {\n\t\tthis.format = format;\n\t\tthis.requestRender = requestRender ?? (() => {});\n\t\tif (activity === this.activity) {\n\t\t\tthis.paint();\n\t\t\treturn;\n\t\t}\n\t\tthis.stop();\n\t\tthis.activity = activity;\n\t\tthis.visibleLength = animate ? Math.min(1, Array.from(activity).length) : Array.from(activity).length;\n\t\tthis.paint();\n\t\tif (animate && this.visibleLength < Array.from(activity).length) this.schedule();\n\t}\n\n\tdispose(): void {\n\t\tthis.stop();\n\t}\n\n\tprivate schedule(): void {\n\t\tthis.timer = setTimeout(() => {\n\t\t\tthis.timer = undefined;\n\t\t\tthis.visibleLength++;\n\t\t\tthis.paint();\n\t\t\tthis.requestRender();\n\t\t\tif (this.visibleLength < Array.from(this.activity).length) this.schedule();\n\t\t}, BROWSER_ACTIVITY_TICK_MS);\n\t\tthis.timer.unref?.();\n\t}\n\n\tprivate paint(): void {\n\t\tthis.setText(this.format(Array.from(this.activity).slice(0, this.visibleLength).join(\"\")));\n\t}\n\n\tprivate stop(): void {\n\t\tif (this.timer) clearTimeout(this.timer);\n\t\tthis.timer = undefined;\n\t}\n}\n\nexport function renderBrowserActivity(\n\targs: BrowserInput,\n\ttheme: Theme,\n\tcontext: ToolRenderContext<unknown, BrowserInput>,\n): Text {\n\tconst component = context.lastComponent instanceof BrowserActivityText\n\t\t? context.lastComponent\n\t\t: new BrowserActivityText();\n\tconst state: BrowserActivityState = context.isPartial ? \"pending\" : context.isError ? \"error\" : \"success\";\n\tconst activity = describeBrowserActivity(args, state);\n\tconst animate = context.executionStarted || context.result === undefined;\n\tcomponent.setActivity(\n\t\tactivity,\n\t\t(text) => `${toolStatusDotFromContext(context, theme)} ${theme.fg(state === \"error\" ? \"error\" : \"toolTitle\", text)}`,\n\t\tanimate,\n\t\tcontext.requestRender,\n\t);\n\treturn component;\n}\n\nexport default function browserExtension(pi: ExtensionAPI): void {\n\tlet enabled = readBrowserSettings().enabled;\n\tlet unsubscribe: (() => void) | undefined;\n\tlet session: BrowserSession | undefined;\n\tlet generation = 0;\n\tconst close = async () => {\n\t\tgeneration++;\n\t\tconst previous = session;\n\t\tsession = undefined;\n\t\tawait previous?.close();\n\t};\n\tpi.on(\"session_shutdown\", async () => {\n\t\tunsubscribe?.();\n\t\tunsubscribe = undefined;\n\t\tawait close();\n\t});\n\tpi.on(\"session_start\", async () => {\n\t\tawait close();\n\t\tenabled = readBrowserSettings().enabled;\n\t\tunsubscribe?.();\n\t\tunsubscribe = onBrowserEnabledChange((value) => {\n\t\t\tenabled = value;\n\t\t\tif (!enabled) void close().catch(() => undefined);\n\t\t});\n\t});\n\tpi.on(\"agent_end\", async (event) => {\n\t\tif (event.messages.some((message) => message.role === \"assistant\" && message.stopReason === \"aborted\")) await close();\n\t});\n\tpi.registerTool({\n\t\tname: \"browser\",\n\t\tlabel: \"Browser\",\n\t\tdescription: BROWSER_DESCRIPTION,\n\t\tparameters: BrowserParams,\n\t\tasync execute(_id, input, signal) {\n\t\t\tif (!enabled) {\n\t\t\t\tawait close();\n\t\t\t\tthrow new Error(\"Browser is disabled. The user can enable Browser in /settings.\");\n\t\t\t}\n\t\t\tconst current = generation;\n\t\t\tconst { BrowserSession } = await import(\"../core/browser/runtime.ts\");\n\t\t\tif (current !== generation || signal?.aborted) throw new Error(\"Browser call cancelled during initialization.\");\n\t\t\tsession ??= new BrowserSession(readBrowserSettings().allowPrivate);\n\t\t\treturn session.run(input, signal);\n\t\t},\n\t\trenderCall: renderBrowserActivity,\n\t});\n}\n"]}
@@ -1 +1 @@
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,EAAO,SAAS,EAAU,IAAI,EAAmE,MAAM,wBAAwB,CAAC;AAsCvI,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,oBAqClE;AAED,wBAAgB,oBAAoB,CAAC,OAAO,KAAA,EAAE,KAAK,KAAA,QASlD;AA8FD,MAAM,CAAC,OAAO,UAAU,yBAAyB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAucxE","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, registerSupervisorRenderers } from \"../intercom/native-supervisor-channel.ts\";\nimport { renderCommunicationCall, renderCommunicationResult } from \"../tui/communication.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\tif ((args.action === \"steer\" || args.action === \"resume\") && args.message) {\n\t\t\treturn renderCommunicationCall(\"subagent\", args, theme, context, { direction: \"to\", peer: resolvedTitle || \"Subagent\", kind: \"steer\", message: args.message });\n\t\t}\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\tregisterSupervisorRenderers(pi, state);\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 isCommunication = (params.action === \"steer\" || params.action === \"resume\") && typeof params.message === \"string\";\n\t\tconst displayTitle = isCommunication ? resolveSubagentActionDisplayTitle(params, state) : undefined;\n\t\tconst jobs = isCommunication ? matchingActionJobs(state, (params.id || params.runId || \"\").trim()) : [];\n\t\tconst targetNames = new Map(jobs.length === 1 ? jobs[0]!.steps?.map((step, index) => [step.index ?? index, childRowLabel(step)]) : []);\n\t\tconst result = await executor.execute(id, params, signal, onUpdate, ctx);\n\t\tif (displayTitle && result.details) result.details.displayTitle = displayTitle;\n\t\tif (isCommunication && result.details) {\n\t\t\tconst targets = result.details.steering?.targets.map((target) => targetNames.get(target.index) ?? `Subagent ${target.index + 1}`);\n\t\t\tconst peer = targets?.length ? targets.join(\", \") : displayTitle || \"Subagent\";\n\t\t\tresult.details.communication = { direction: \"to\", peer, kind: \"steer\", message: params.message };\n\t\t}\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 (result.details?.communication) return renderCommunicationResult(result.content, result.details, options.expanded, context.isError, theme);\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, hasPendingMessages: () => ctx.hasPendingMessages() });\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,EAAO,SAAS,EAAU,IAAI,EAAmE,MAAM,wBAAwB,CAAC;AAsCvI,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,oBA4ClE;AAED,wBAAgB,oBAAoB,CAAC,OAAO,KAAA,EAAE,KAAK,KAAA,QASlD;AA8FD,MAAM,CAAC,OAAO,UAAU,yBAAyB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAucxE","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, registerSupervisorRenderers } from \"../intercom/native-supervisor-channel.ts\";\nimport { renderCommunicationCall, renderCommunicationResult } from \"../tui/communication.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\tif ((args.action === \"steer\" || args.action === \"resume\") && args.message) {\n\t\t\treturn renderCommunicationCall(\n\t\t\t\t\"subagent\",\n\t\t\t\targs,\n\t\t\t\ttheme,\n\t\t\t\tcontext,\n\t\t\t\t{ direction: \"to\", peer: resolvedTitle || \"Subagent\", kind: \"steer\", message: args.message },\n\t\t\t\tresolvedTitle,\n\t\t\t);\n\t\t}\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\tregisterSupervisorRenderers(pi, state);\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 isCommunication = (params.action === \"steer\" || params.action === \"resume\") && typeof params.message === \"string\";\n\t\tconst displayTitle = isCommunication ? resolveSubagentActionDisplayTitle(params, state) : undefined;\n\t\tconst jobs = isCommunication ? matchingActionJobs(state, (params.id || params.runId || \"\").trim()) : [];\n\t\tconst targetNames = new Map(jobs.length === 1 ? jobs[0]!.steps?.map((step, index) => [step.index ?? index, childRowLabel(step)]) : []);\n\t\tconst result = await executor.execute(id, params, signal, onUpdate, ctx);\n\t\tif (displayTitle && result.details) result.details.displayTitle = displayTitle;\n\t\tif (isCommunication && result.details) {\n\t\t\tconst targets = result.details.steering?.targets.map((target) => targetNames.get(target.index) ?? `Subagent ${target.index + 1}`);\n\t\t\tconst peer = targets?.length ? targets.join(\", \") : displayTitle || \"Subagent\";\n\t\t\tresult.details.communication = { direction: \"to\", peer, kind: \"steer\", message: params.message };\n\t\t}\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 (result.details?.communication) return renderCommunicationResult(result.content, result.details, options.expanded, context.isError, theme);\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, hasPendingMessages: () => ctx.hasPendingMessages() });\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"]}
@@ -156,7 +156,7 @@ export function renderSubagentCall(rawArgs, theme, context, options) {
156
156
  const resolvedTitle = resultTitle || options?.resolveActionTitle?.(args);
157
157
  const target = resolvedTitle || args.id || args.runId || "";
158
158
  if ((args.action === "steer" || args.action === "resume") && args.message) {
159
- return renderCommunicationCall("subagent", args, theme, context, { direction: "to", peer: resolvedTitle || "Subagent", kind: "steer", message: args.message });
159
+ return renderCommunicationCall("subagent", args, theme, context, { direction: "to", peer: resolvedTitle || "Subagent", kind: "steer", message: args.message }, resolvedTitle);
160
160
  }
161
161
  return new Text(`${theme.fg("toolTitle", theme.bold("subagent "))}${args.action}${target ? ` ${theme.fg("accent", target)}` : ""}`, 0, 0);
162
162
  }