@ashx-j/lunr 0.2.20 → 0.2.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/CHANGELOG.md +13 -1
  2. package/dist/builtin-extensions/lunr-todos.d.ts +6 -5
  3. package/dist/builtin-extensions/lunr-todos.d.ts.map +1 -1
  4. package/dist/builtin-extensions/lunr-todos.js +9 -7
  5. package/dist/builtin-extensions/lunr-todos.js.map +1 -1
  6. package/dist/builtin-extensions/pi-intercom/broker/spawn.d.ts.map +1 -1
  7. package/dist/builtin-extensions/pi-intercom/broker/spawn.js +30 -21
  8. package/dist/builtin-extensions/pi-intercom/broker/spawn.js.map +1 -1
  9. package/dist/builtin-extensions/pi-intercom/index.d.ts.map +1 -1
  10. package/dist/builtin-extensions/pi-intercom/index.js +12 -3
  11. package/dist/builtin-extensions/pi-intercom/index.js.map +1 -1
  12. package/dist/builtin-extensions/pi-subagents/src/extension/fanout-child.d.ts.map +1 -1
  13. package/dist/builtin-extensions/pi-subagents/src/extension/fanout-child.js +2 -2
  14. package/dist/builtin-extensions/pi-subagents/src/extension/fanout-child.js.map +1 -1
  15. package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts +7 -6
  16. package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts.map +1 -1
  17. package/dist/builtin-extensions/pi-subagents/src/extension/index.js +63 -29
  18. package/dist/builtin-extensions/pi-subagents/src/extension/index.js.map +1 -1
  19. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts.map +1 -1
  20. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js +2 -2
  21. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js.map +1 -1
  22. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts +3 -3
  23. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts.map +1 -1
  24. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js +3 -2
  25. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js.map +1 -1
  26. package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.d.ts +1 -0
  27. package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.d.ts.map +1 -1
  28. package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.js +26 -7
  29. package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.js.map +1 -1
  30. package/dist/builtin-extensions/pi-subagents/src/intercom/result-intercom.d.ts +1 -0
  31. package/dist/builtin-extensions/pi-subagents/src/intercom/result-intercom.d.ts.map +1 -1
  32. package/dist/builtin-extensions/pi-subagents/src/intercom/result-intercom.js +1 -1
  33. package/dist/builtin-extensions/pi-subagents/src/intercom/result-intercom.js.map +1 -1
  34. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.d.ts +2 -0
  35. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.d.ts.map +1 -1
  36. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js +4 -3
  37. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js.map +1 -1
  38. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-job-tracker.d.ts.map +1 -1
  39. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-job-tracker.js +2 -0
  40. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-job-tracker.js.map +1 -1
  41. package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.d.ts +6 -2
  42. package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.d.ts.map +1 -1
  43. package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.js +23 -1
  44. package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.js.map +1 -1
  45. package/dist/builtin-extensions/pi-subagents/src/runs/background/result-watcher.d.ts.map +1 -1
  46. package/dist/builtin-extensions/pi-subagents/src/runs/background/result-watcher.js +1 -0
  47. package/dist/builtin-extensions/pi-subagents/src/runs/background/result-watcher.js.map +1 -1
  48. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.d.ts +100 -0
  49. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.d.ts.map +1 -1
  50. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js +46 -31
  51. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js.map +1 -1
  52. package/dist/builtin-extensions/pi-subagents/src/runs/background/top-level-async.d.ts +15 -2
  53. package/dist/builtin-extensions/pi-subagents/src/runs/background/top-level-async.d.ts.map +1 -1
  54. package/dist/builtin-extensions/pi-subagents/src/runs/background/top-level-async.js +14 -4
  55. package/dist/builtin-extensions/pi-subagents/src/runs/background/top-level-async.js.map +1 -1
  56. package/dist/builtin-extensions/pi-subagents/src/runs/background/wait-tool.d.ts.map +1 -1
  57. package/dist/builtin-extensions/pi-subagents/src/runs/background/wait-tool.js +1 -1
  58. package/dist/builtin-extensions/pi-subagents/src/runs/background/wait-tool.js.map +1 -1
  59. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts.map +1 -1
  60. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js +1 -1
  61. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js.map +1 -1
  62. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts +0 -1
  63. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts.map +1 -1
  64. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js +9 -9
  65. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js.map +1 -1
  66. package/dist/builtin-extensions/pi-subagents/src/runs/shared/completion-guard.d.ts +1 -1
  67. package/dist/builtin-extensions/pi-subagents/src/runs/shared/completion-guard.d.ts.map +1 -1
  68. package/dist/builtin-extensions/pi-subagents/src/runs/shared/completion-guard.js +1 -1
  69. package/dist/builtin-extensions/pi-subagents/src/runs/shared/completion-guard.js.map +1 -1
  70. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts +1 -0
  71. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts.map +1 -1
  72. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.js.map +1 -1
  73. package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.d.ts +3 -0
  74. package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.d.ts.map +1 -1
  75. package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.js +2 -0
  76. package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.js.map +1 -1
  77. package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.d.ts +2 -2
  78. package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.d.ts.map +1 -1
  79. package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.js +5 -10
  80. package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.js.map +1 -1
  81. package/dist/builtin-extensions/pi-subagents/src/shared/async-guidance.d.ts +2 -0
  82. package/dist/builtin-extensions/pi-subagents/src/shared/async-guidance.d.ts.map +1 -0
  83. package/dist/builtin-extensions/pi-subagents/src/shared/async-guidance.js +3 -0
  84. package/dist/builtin-extensions/pi-subagents/src/shared/async-guidance.js.map +1 -0
  85. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts +5 -0
  86. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts.map +1 -1
  87. package/dist/builtin-extensions/pi-subagents/src/shared/types.js.map +1 -1
  88. package/dist/builtin-extensions/pi-subagents/src/slash/delegation-adapters.d.ts.map +1 -1
  89. package/dist/builtin-extensions/pi-subagents/src/slash/delegation-adapters.js.map +1 -1
  90. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.d.ts +7 -0
  91. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.d.ts.map +1 -1
  92. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js +11 -7
  93. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js.map +1 -1
  94. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts +4 -0
  95. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts.map +1 -1
  96. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js +22 -23
  97. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js.map +1 -1
  98. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts +2 -0
  99. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts.map +1 -1
  100. package/dist/builtin-extensions/pi-subagents/src/tui/render.js +237 -374
  101. package/dist/builtin-extensions/pi-subagents/src/tui/render.js.map +1 -1
  102. package/dist/core/customize.d.ts +2 -0
  103. package/dist/core/customize.d.ts.map +1 -1
  104. package/dist/core/customize.js +3 -0
  105. package/dist/core/customize.js.map +1 -1
  106. package/dist/core/settings-manager.d.ts +4 -0
  107. package/dist/core/settings-manager.d.ts.map +1 -1
  108. package/dist/core/settings-manager.js +11 -0
  109. package/dist/core/settings-manager.js.map +1 -1
  110. package/dist/core/subagent-spinner-setting.d.ts +6 -0
  111. package/dist/core/subagent-spinner-setting.d.ts.map +1 -0
  112. package/dist/core/subagent-spinner-setting.js +12 -0
  113. package/dist/core/subagent-spinner-setting.js.map +1 -0
  114. package/dist/core/subagent-spinner.d.ts +4 -0
  115. package/dist/core/subagent-spinner.d.ts.map +1 -0
  116. package/dist/core/subagent-spinner.js +6 -0
  117. package/dist/core/subagent-spinner.js.map +1 -0
  118. package/dist/modes/interactive/components/settings-selector.d.ts +4 -0
  119. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  120. package/dist/modes/interactive/components/settings-selector.js +73 -1
  121. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  122. package/dist/modes/interactive/extension-widget-order.d.ts +8 -0
  123. package/dist/modes/interactive/extension-widget-order.d.ts.map +1 -0
  124. package/dist/modes/interactive/extension-widget-order.js +37 -0
  125. package/dist/modes/interactive/extension-widget-order.js.map +1 -0
  126. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  127. package/dist/modes/interactive/interactive-mode.js +15 -22
  128. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  129. package/dist/node-runtime/{agent-session-services-75JKASCW.js → agent-session-services-C743TYQE.js} +4 -3
  130. package/dist/node-runtime/{builtin-extensions-X4JMM4ZS.js → builtin-extensions-JCOPAAB5.js} +2 -1
  131. package/dist/node-runtime/{chunk-QKFMBRAL.js → chunk-2BUAYZ7K.js} +94 -25
  132. package/dist/node-runtime/chunk-5ZH3ZFIZ.js +308 -0
  133. package/dist/node-runtime/{chunk-BBUVIEZB.js → chunk-A3KMVBZV.js} +10 -1
  134. package/dist/node-runtime/{chunk-K3646JTM.js → chunk-AWVD5IEY.js} +7 -307
  135. package/dist/node-runtime/{chunk-ELWEXVO5.js → chunk-GB2MGFLI.js} +11 -11
  136. package/dist/node-runtime/chunk-LISUTZS3.js +19 -0
  137. package/dist/node-runtime/{chunk-AL2FMKKM.js → chunk-PDNXVXMD.js} +12 -12
  138. package/dist/node-runtime/{chunk-46ECNOLF.js → chunk-SY2TFMLO.js} +57 -17
  139. package/dist/node-runtime/{chunk-HQCKFM2O.js → chunk-VGYZ3APQ.js} +3 -0
  140. package/dist/node-runtime/{chunk-4DJ6HEGH.js → chunk-VZR3NCM7.js} +3 -3
  141. package/dist/node-runtime/{chunk-AOFXZQKA.js → chunk-W3SILANP.js} +1 -1
  142. package/dist/node-runtime/{chunk-CBGB57XT.js → chunk-WLLDXJRT.js} +10 -0
  143. package/dist/node-runtime/{chunk-JYNNB7DU.js → chunk-XI77KVKA.js} +2 -2
  144. package/dist/node-runtime/{chunk-4CSFRBCN.js → chunk-Y5KOEMBH.js} +1 -1
  145. package/dist/node-runtime/{chunk-AROOY4BB.js → chunk-YMEAYRBE.js} +1 -1
  146. package/dist/node-runtime/{command-RBTRL55A.js → command-P4NYCJ2M.js} +1 -1
  147. package/dist/node-runtime/{customize-XEXL2DGQ.js → customize-LF5VST47.js} +1 -1
  148. package/dist/node-runtime/{gateway-L4ANRTVE.js → gateway-BM6NLOH4.js} +1 -1
  149. package/dist/node-runtime/index.js +13 -11
  150. package/dist/node-runtime/{interactive-mode-HPUNH7SV.js → interactive-mode-EVVBA64D.js} +9 -7
  151. package/dist/node-runtime/{lunr-cron-NNYTWTGF.js → lunr-cron-4VHP4E4B.js} +1 -1
  152. package/dist/node-runtime/main.js +11 -9
  153. package/dist/node-runtime/{package-manager-cli-7RKSZGEN.js → package-manager-cli-N37546V5.js} +5 -4
  154. package/dist/node-runtime/{pi-intercom-DDQMRNPJ.js → pi-intercom-NV2LW6X7.js} +34 -33
  155. package/dist/node-runtime/{pi-subagents-T5WMMLBB.js → pi-subagents-6MUIFNDM.js} +286 -345
  156. package/dist/node-runtime/rpc-entry.js +11 -9
  157. package/dist/node-runtime/{runtime-bridges-NJQERQ72.js → runtime-bridges-2VQTLPOZ.js} +2 -2
  158. package/dist/node-runtime/{settings-manager-GHPTUN7R.js → settings-manager-CUGAZWAB.js} +2 -1
  159. package/dist/node-runtime/{subagent-executor-SRFC26SJ.js → subagent-executor-MH4A7CCF.js} +14 -46
  160. package/dist/node-runtime/{update-cli-L3JIBH2U.js → update-cli-XTZMZGOL.js} +2 -1
  161. package/docs/features.md +6 -2
  162. package/docs/settings.md +2 -1
  163. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  164. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  165. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  166. package/examples/extensions/gondolin/package-lock.json +2 -2
  167. package/examples/extensions/gondolin/package.json +1 -1
  168. package/examples/extensions/sandbox/package-lock.json +2 -2
  169. package/examples/extensions/sandbox/package.json +1 -1
  170. package/examples/extensions/with-deps/package-lock.json +2 -2
  171. package/examples/extensions/with-deps/package.json +1 -1
  172. package/package.json +4 -4
package/CHANGELOG.md CHANGED
@@ -1,9 +1,21 @@
1
1
  # Changelog
2
2
 
3
- lunR is derived from pi. The published npm package is **`@ashx-j/lunr@0.2.19`**. 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.21`**. Versioned sections below (`0.80.x` and earlier) are **upstream pi history** and do not describe lunR releases.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.2.21] - 2026-09-13
8
+
9
+ ### Changed
10
+
11
+ - **Async subagents by default.** Single, parallel, and chain launches run in the background when `async` is omitted. Use `async:false` for an immediate result, or `clarify:true` for the interactive preview. `/run`, `/chain`, and `/parallel` accept `--foreground`.
12
+ - **Subagent compact UI.** Mixed runs stay one flat row per child. Todos sit directly above async rows. Four todos show all four. Customize picks one spinner for foreground and async children. Steering headers use the child description.
13
+
14
+ ### Fixed
15
+
16
+ - Intercom starts on Windows by letting the hidden launcher own stderr redirection.
17
+ - Async questions and results no longer fail while the child is still running, and read-only resume no longer fails for making no edits. The owner gets one terminal notification with failure details and recovery paths.
18
+
7
19
  ## [0.2.20] - 2026-09-12
8
20
 
9
21
  ### Added
@@ -10,8 +10,9 @@
10
10
  * cron tool's minimal footprint.
11
11
  * - After every state change the list is mirrored into a `todos` widget above
12
12
  * the editor (component factory, so MAX_WIDGET_LINES string truncation does
13
- * not apply). Collapsed: up to 3 active rows (in-progress first, then
14
- * pending). Completed items never appear collapsed. `+N more` still
13
+ * not apply). Collapsed: four active items fit in full; lists of five or
14
+ * more show three rows (in-progress first, then pending). Completed items
15
+ * never appear collapsed. `+N more` still
15
16
  * applies to hidden active rows. Expanded: every row, completed included
16
17
  * until the next user turn prunes them.
17
18
  * - Click the widget to expand/collapse.
@@ -34,13 +35,13 @@ export interface TodoWidgetLine {
34
35
  status?: TodoStatus;
35
36
  text: string;
36
37
  }
37
- /** Collapsed widget shows at most this many active (non-completed) rows. */
38
+ /** Collapsed widgets with five or more active items show this many rows. */
38
39
  export declare const TODO_WIDGET_COLLAPSED_ROWS = 3;
39
40
  /** Short model-facing summary, e.g. "4 todos: 1 in progress, 2 pending, 1 completed". */
40
41
  export declare function summarizeTodos(todos: TodoItem[]): string;
41
42
  /**
42
- * Widget lines for the current list. Collapsed mode shows up to
43
- * TODO_WIDGET_COLLAPSED_ROWS active rows (in-progress first, then pending)
43
+ * Widget lines for the current list. Collapsed mode shows all four active rows,
44
+ * or TODO_WIDGET_COLLAPSED_ROWS when there are five or more (in-progress first, then pending),
44
45
  * and never emits a `✓ N done` summary. A `+N more (<expandKey> to expand)`
45
46
  * hint is appended when active rows are hidden. Expanded mode shows every
46
47
  * row, completed included. Empty list / all-completed collapsed → no lines.
@@ -1 +1 @@
1
- {"version":3,"file":"lunr-todos.d.ts","sourceRoot":"","sources":["../../src/builtin-extensions/lunr-todos.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,iCAAiC,CAAC;AAStF,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,aAAa,GAAG,WAAW,CAAC;AAEjE,MAAM,WAAW,QAAQ;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,UAAU,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;IAClC,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACb;AAED,4EAA4E;AAC5E,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAQ5C,yFAAyF;AACzF,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,CAWxD;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,SAAU,GAAG,cAAc,EAAE,CAqBhH;AASD,MAAM,CAAC,OAAO,WAAW,EAAE,EAAE,YAAY,GAAG,IAAI,CA8H/C","sourcesContent":["// @ts-nocheck\n/**\n * lunr-todos — lunR-native agent todo list with an above-editor widget.\n *\n * lunR: this file is lunR-native (not an absorbed upstream extension).\n *\n * - One `todo` tool (TypeBox), full-replace semantics: every call sends the\n * ENTIRE list; an empty array clears it. State is per-session, in-memory\n * only (extension closure) — nothing is persisted.\n * - Chat render is a quiet one-liner (renderCall/renderResult), matching the\n * cron tool's minimal footprint.\n * - After every state change the list is mirrored into a `todos` widget above\n * the editor (component factory, so MAX_WIDGET_LINES string truncation does\n * not apply). Collapsed: up to 3 active rows (in-progress first, then\n * pending). Completed items never appear collapsed. `+N more` still\n * applies to hidden active rows. Expanded: every row, completed included\n * until the next user turn prunes them.\n * - Click the widget to expand/collapse.\n *\n * The pure logic (summarizeTodos / buildTodoWidgetLines) is exported for\n * vitest — see test/lunr-todos.test.ts.\n *\n * `// @ts-nocheck` matches the builtin-extension convention (see lunr-cron).\n * Runtime imports stay on concrete core modules — never the package barrel.\n */\n\nimport type { ExtensionAPI, ExtensionContext } from \"@ashx-j/lunr\";\nimport { Text } from \"@ashx-j/lunr-tui\";\nimport { Type } from \"@sinclair/typebox\";\nimport { keyText } from \"../modes/interactive/components/keybinding-hints.ts\";\n\n// ---------------------------------------------------------------------------\n// Pure logic (exported for tests)\n// ---------------------------------------------------------------------------\n\nexport type TodoStatus = \"pending\" | \"in_progress\" | \"completed\";\n\nexport interface TodoItem {\n\tid: string;\n\tcontent: string;\n\tstatus: TodoStatus;\n}\n\nexport interface TodoWidgetLine {\n\tkind: \"todo\" | \"summary\" | \"hint\";\n\tstatus?: TodoStatus;\n\ttext: string;\n}\n\n/** Collapsed widget shows at most this many active (non-completed) rows. */\nexport const TODO_WIDGET_COLLAPSED_ROWS = 3;\n\nconst STATUS_GLYPHS: Record<TodoStatus, string> = {\n\tin_progress: \"●\",\n\tpending: \"○\",\n\tcompleted: \"✓\",\n};\n\n/** Short model-facing summary, e.g. \"4 todos: 1 in progress, 2 pending, 1 completed\". */\nexport function summarizeTodos(todos: TodoItem[]): string {\n\tif (todos.length === 0) return \"No todos.\";\n\tconst count = (s: TodoStatus) => todos.filter((t) => t.status === s).length;\n\tconst parts: string[] = [];\n\tconst inProgress = count(\"in_progress\");\n\tconst pending = count(\"pending\");\n\tconst completed = count(\"completed\");\n\tif (inProgress > 0) parts.push(`${inProgress} in progress`);\n\tif (pending > 0) parts.push(`${pending} pending`);\n\tif (completed > 0) parts.push(`${completed} completed`);\n\treturn `${todos.length} todo${todos.length === 1 ? \"\" : \"s\"}: ${parts.join(\", \")}`;\n}\n\n/**\n * Widget lines for the current list. Collapsed mode shows up to\n * TODO_WIDGET_COLLAPSED_ROWS active rows (in-progress first, then pending)\n * and never emits a `✓ N done` summary. A `+N more (<expandKey> to expand)`\n * hint is appended when active rows are hidden. Expanded mode shows every\n * row, completed included. Empty list / all-completed collapsed → no lines.\n */\nexport function buildTodoWidgetLines(todos: TodoItem[], expanded: boolean, expandKey = \"click\"): TodoWidgetLine[] {\n\tif (todos.length === 0) return [];\n\tconst row = (t: TodoItem): TodoWidgetLine => ({\n\t\tkind: \"todo\",\n\t\tstatus: t.status,\n\t\ttext: `${STATUS_GLYPHS[t.status]} ${t.content}`,\n\t});\n\tconst active = [\n\t\t...todos.filter((t) => t.status === \"in_progress\"),\n\t\t...todos.filter((t) => t.status === \"pending\"),\n\t];\n\tconst done = todos.filter((t) => t.status === \"completed\");\n\tif (expanded) {\n\t\treturn [...active.map(row), ...done.map(row)];\n\t}\n\tconst lines: TodoWidgetLine[] = active.slice(0, TODO_WIDGET_COLLAPSED_ROWS).map(row);\n\tconst hidden = active.length - TODO_WIDGET_COLLAPSED_ROWS;\n\tif (hidden > 0) {\n\t\tlines.push({ kind: \"hint\", text: `+${hidden} more` });\n\t}\n\treturn lines;\n}\n\n// ---------------------------------------------------------------------------\n// Extension\n// ---------------------------------------------------------------------------\n\nconst WIDGET_KEY = \"todos\";\nconst EXPANDED_BRIDGE_SYMBOL = Symbol.for(\"@lunr/tools-expanded-changed\");\n\nexport default function (pi: ExtensionAPI): void {\n\tlet todos: TodoItem[] = [];\n\tlet expanded = false;\n\tlet lastCtx: ExtensionContext | null = null;\n\n\tfunction colorize(line: TodoWidgetLine, theme: any): string {\n\t\tif (line.kind === \"summary\" || line.kind === \"hint\") return theme.fg(\"muted\", line.text);\n\t\tif (line.status === \"in_progress\") return theme.fg(\"accent\", line.text);\n\t\tif (line.status === \"completed\") return theme.fg(\"dim\", line.text);\n\t\treturn line.text;\n\t}\n\n\tfunction refreshWidget(): void {\n\t\tconst ctx = lastCtx;\n\t\tif (!ctx?.hasUI) return;\n\t\tconst lines = buildTodoWidgetLines(todos, expanded, keyText(\"app.tools.expand\"));\n\t\tif (todos.length === 0 || lines.length === 0) {\n\t\t\tctx.ui.setWidget(WIDGET_KEY, undefined);\n\t\t\treturn;\n\t\t}\n\t\tctx.ui.setWidget(\n\t\t\tWIDGET_KEY,\n\t\t\t(_tui: unknown, theme: any) => {\n\t\t\t\tconst text = new Text(lines.map((l) => colorize(l, theme)).join(\"\\n\"), 1, 0);\n\t\t\t\treturn {\n\t\t\t\t\trender: (width: number) => text.render(width),\n\t\t\t\t\tinvalidate: () => text.invalidate?.(),\n\t\t\t\t\thandleClick: () => {\n\t\t\t\t\t\texpanded = !expanded;\n\t\t\t\t\t\trefreshWidget();\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t},\n\t\t\t{ placement: \"aboveEditor\" },\n\t\t);\n\t}\n\n\tfunction pruneCompleted(): void {\n\t\tconst next = todos.filter((t) => t.status !== \"completed\");\n\t\tif (next.length === todos.length) return;\n\t\ttodos = next;\n\t\trefreshWidget();\n\t}\n\n\t// --- Expansion bridge (interactive-mode setToolsExpanded invokes this) ---\n\tfunction registerExpandedBridge(): void {\n\t\t(globalThis as Record<symbol, unknown>)[EXPANDED_BRIDGE_SYMBOL] = (value: boolean) => {\n\t\t\texpanded = Boolean(value);\n\t\t\trefreshWidget();\n\t\t};\n\t}\n\tregisterExpandedBridge();\n\n\t// --- Session lifecycle: per-session in-memory state ---\n\tpi.on(\"session_start\", (_event, ctx) => {\n\t\tlastCtx = ctx;\n\t\ttodos = [];\n\t\texpanded = ctx.hasUI ? (ctx.ui.getToolsExpanded?.() ?? false) : false;\n\t\tregisterExpandedBridge();\n\t\tif (ctx.hasUI) ctx.ui.setWidget(WIDGET_KEY, undefined);\n\t});\n\n\t// lunr: prune completed items on the next real user message, not slash\n\t// commands or agent follow-up turns.\n\tpi.on(\"message_start\", (event, ctx) => {\n\t\tif (event.message?.role !== \"user\") return;\n\t\tlastCtx = ctx;\n\t\tpruneCompleted();\n\t});\n\n\tpi.on(\"session_shutdown\", () => {\n\t\tif (lastCtx?.hasUI) lastCtx.ui.setWidget(WIDGET_KEY, undefined);\n\t\tlastCtx = null;\n\t\tdelete (globalThis as Record<symbol, unknown>)[EXPANDED_BRIDGE_SYMBOL];\n\t});\n\n\t// --- todo tool (agent-facing) ---\n\tpi.registerTool({\n\t\tname: \"todo\",\n\t\tlabel: \"Todo\",\n\t\tdescription: [\n\t\t\t\"Manage the session todo list shown above the user's editor. Full-replace semantics:\",\n\t\t\t\"every call must send the ENTIRE list; send an empty array to clear it.\",\n\t\t\t\"Each item: { id, content, status: 'pending' | 'in_progress' | 'completed' }.\",\n\t\t\t\"Use it to track multi-step work; keep exactly one item in_progress at a time.\",\n\t\t].join(\"\\n\"),\n\t\tparameters: Type.Object({\n\t\t\ttodos: Type.Array(\n\t\t\t\tType.Object({\n\t\t\t\t\tid: Type.String({ description: \"Stable id for the item.\" }),\n\t\t\t\t\tcontent: Type.String({ description: \"Short description of the task.\" }),\n\t\t\t\t\tstatus: Type.Union([\n\t\t\t\t\t\tType.Literal(\"pending\"),\n\t\t\t\t\t\tType.Literal(\"in_progress\"),\n\t\t\t\t\t\tType.Literal(\"completed\"),\n\t\t\t\t\t]),\n\t\t\t\t}),\n\t\t\t),\n\t\t}),\n\t\tasync execute(_toolCallId, params, _signal, _onUpdate, ctx) {\n\t\t\tconst text = (t: string) => ({ content: [{ type: \"text\" as const, text: t }] });\n\t\t\tconst incoming = Array.isArray(params?.todos) ? params.todos : [];\n\t\t\ttodos = incoming\n\t\t\t\t.filter((t: any) => t && typeof t.content === \"string\" && t.content.trim().length > 0)\n\t\t\t\t.map((t: any, i: number) => ({\n\t\t\t\t\tid: typeof t.id === \"string\" && t.id ? t.id : String(i + 1),\n\t\t\t\t\tcontent: t.content.trim(),\n\t\t\t\t\tstatus: t.status === \"in_progress\" || t.status === \"completed\" ? t.status : \"pending\",\n\t\t\t\t}));\n\t\t\tlastCtx = ctx;\n\t\t\trefreshWidget();\n\t\t\treturn text(summarizeTodos(todos));\n\t\t},\n\t\trenderCall(args, theme) {\n\t\t\tconst n = Array.isArray(args?.todos) ? args.todos.length : 0;\n\t\t\tconst detail = n === 0 ? \"clear\" : `${n} item${n === 1 ? \"\" : \"s\"}`;\n\t\t\treturn new Text(theme.fg(\"toolTitle\", theme.bold(\"todo \")) + theme.fg(\"dim\", detail), 0, 0);\n\t\t},\n\t\trenderResult(result, { isPartial }, theme) {\n\t\t\tif (isPartial) return new Text(theme.fg(\"warning\", \"Updating todos...\"), 0, 0);\n\t\t\tconst first = result?.content?.[0];\n\t\t\tconst summary = first?.type === \"text\" ? first.text : \"\";\n\t\t\treturn new Text(theme.fg(\"dim\", summary), 0, 0);\n\t\t},\n\t});\n}\n"]}
1
+ {"version":3,"file":"lunr-todos.d.ts","sourceRoot":"","sources":["../../src/builtin-extensions/lunr-todos.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,iCAAiC,CAAC;AAStF,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,aAAa,GAAG,WAAW,CAAC;AAEjE,MAAM,WAAW,QAAQ;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,UAAU,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;IAClC,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACb;AAED,4EAA4E;AAC5E,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAQ5C,yFAAyF;AACzF,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,CAWxD;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,SAAU,GAAG,cAAc,EAAE,CAsBhH;AASD,MAAM,CAAC,OAAO,WAAW,EAAE,EAAE,YAAY,GAAG,IAAI,CA8H/C","sourcesContent":["// @ts-nocheck\n/**\n * lunr-todos — lunR-native agent todo list with an above-editor widget.\n *\n * lunR: this file is lunR-native (not an absorbed upstream extension).\n *\n * - One `todo` tool (TypeBox), full-replace semantics: every call sends the\n * ENTIRE list; an empty array clears it. State is per-session, in-memory\n * only (extension closure) — nothing is persisted.\n * - Chat render is a quiet one-liner (renderCall/renderResult), matching the\n * cron tool's minimal footprint.\n * - After every state change the list is mirrored into a `todos` widget above\n * the editor (component factory, so MAX_WIDGET_LINES string truncation does\n * not apply). Collapsed: four active items fit in full; lists of five or\n * more show three rows (in-progress first, then pending). Completed items\n * never appear collapsed. `+N more` still\n * applies to hidden active rows. Expanded: every row, completed included\n * until the next user turn prunes them.\n * - Click the widget to expand/collapse.\n *\n * The pure logic (summarizeTodos / buildTodoWidgetLines) is exported for\n * vitest — see test/lunr-todos.test.ts.\n *\n * `// @ts-nocheck` matches the builtin-extension convention (see lunr-cron).\n * Runtime imports stay on concrete core modules — never the package barrel.\n */\n\nimport type { ExtensionAPI, ExtensionContext } from \"@ashx-j/lunr\";\nimport { Text } from \"@ashx-j/lunr-tui\";\nimport { Type } from \"@sinclair/typebox\";\nimport { keyText } from \"../modes/interactive/components/keybinding-hints.ts\";\n\n// ---------------------------------------------------------------------------\n// Pure logic (exported for tests)\n// ---------------------------------------------------------------------------\n\nexport type TodoStatus = \"pending\" | \"in_progress\" | \"completed\";\n\nexport interface TodoItem {\n\tid: string;\n\tcontent: string;\n\tstatus: TodoStatus;\n}\n\nexport interface TodoWidgetLine {\n\tkind: \"todo\" | \"summary\" | \"hint\";\n\tstatus?: TodoStatus;\n\ttext: string;\n}\n\n/** Collapsed widgets with five or more active items show this many rows. */\nexport const TODO_WIDGET_COLLAPSED_ROWS = 3;\n\nconst STATUS_GLYPHS: Record<TodoStatus, string> = {\n\tin_progress: \"●\",\n\tpending: \"○\",\n\tcompleted: \"✓\",\n};\n\n/** Short model-facing summary, e.g. \"4 todos: 1 in progress, 2 pending, 1 completed\". */\nexport function summarizeTodos(todos: TodoItem[]): string {\n\tif (todos.length === 0) return \"No todos.\";\n\tconst count = (s: TodoStatus) => todos.filter((t) => t.status === s).length;\n\tconst parts: string[] = [];\n\tconst inProgress = count(\"in_progress\");\n\tconst pending = count(\"pending\");\n\tconst completed = count(\"completed\");\n\tif (inProgress > 0) parts.push(`${inProgress} in progress`);\n\tif (pending > 0) parts.push(`${pending} pending`);\n\tif (completed > 0) parts.push(`${completed} completed`);\n\treturn `${todos.length} todo${todos.length === 1 ? \"\" : \"s\"}: ${parts.join(\", \")}`;\n}\n\n/**\n * Widget lines for the current list. Collapsed mode shows all four active rows,\n * or TODO_WIDGET_COLLAPSED_ROWS when there are five or more (in-progress first, then pending),\n * and never emits a `✓ N done` summary. A `+N more (<expandKey> to expand)`\n * hint is appended when active rows are hidden. Expanded mode shows every\n * row, completed included. Empty list / all-completed collapsed → no lines.\n */\nexport function buildTodoWidgetLines(todos: TodoItem[], expanded: boolean, expandKey = \"click\"): TodoWidgetLine[] {\n\tif (todos.length === 0) return [];\n\tconst row = (t: TodoItem): TodoWidgetLine => ({\n\t\tkind: \"todo\",\n\t\tstatus: t.status,\n\t\ttext: `${STATUS_GLYPHS[t.status]} ${t.content}`,\n\t});\n\tconst active = [\n\t\t...todos.filter((t) => t.status === \"in_progress\"),\n\t\t...todos.filter((t) => t.status === \"pending\"),\n\t];\n\tconst done = todos.filter((t) => t.status === \"completed\");\n\tif (expanded) {\n\t\treturn [...active.map(row), ...done.map(row)];\n\t}\n\tconst visibleRows = active.length === 4 ? 4 : TODO_WIDGET_COLLAPSED_ROWS;\n\tconst lines: TodoWidgetLine[] = active.slice(0, visibleRows).map(row);\n\tconst hidden = active.length - visibleRows;\n\tif (hidden > 0) {\n\t\tlines.push({ kind: \"hint\", text: `+${hidden} more` });\n\t}\n\treturn lines;\n}\n\n// ---------------------------------------------------------------------------\n// Extension\n// ---------------------------------------------------------------------------\n\nconst WIDGET_KEY = \"todos\";\nconst EXPANDED_BRIDGE_SYMBOL = Symbol.for(\"@lunr/tools-expanded-changed\");\n\nexport default function (pi: ExtensionAPI): void {\n\tlet todos: TodoItem[] = [];\n\tlet expanded = false;\n\tlet lastCtx: ExtensionContext | null = null;\n\n\tfunction colorize(line: TodoWidgetLine, theme: any): string {\n\t\tif (line.kind === \"summary\" || line.kind === \"hint\") return theme.fg(\"muted\", line.text);\n\t\tif (line.status === \"in_progress\") return theme.fg(\"accent\", line.text);\n\t\tif (line.status === \"completed\") return theme.fg(\"dim\", line.text);\n\t\treturn line.text;\n\t}\n\n\tfunction refreshWidget(): void {\n\t\tconst ctx = lastCtx;\n\t\tif (!ctx?.hasUI) return;\n\t\tconst lines = buildTodoWidgetLines(todos, expanded, keyText(\"app.tools.expand\"));\n\t\tif (todos.length === 0 || lines.length === 0) {\n\t\t\tctx.ui.setWidget(WIDGET_KEY, undefined);\n\t\t\treturn;\n\t\t}\n\t\tctx.ui.setWidget(\n\t\t\tWIDGET_KEY,\n\t\t\t(_tui: unknown, theme: any) => {\n\t\t\t\tconst text = new Text(lines.map((l) => colorize(l, theme)).join(\"\\n\"), 1, 0);\n\t\t\t\treturn {\n\t\t\t\t\trender: (width: number) => text.render(width),\n\t\t\t\t\tinvalidate: () => text.invalidate?.(),\n\t\t\t\t\thandleClick: () => {\n\t\t\t\t\t\texpanded = !expanded;\n\t\t\t\t\t\trefreshWidget();\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t},\n\t\t\t{ placement: \"aboveEditor\" },\n\t\t);\n\t}\n\n\tfunction pruneCompleted(): void {\n\t\tconst next = todos.filter((t) => t.status !== \"completed\");\n\t\tif (next.length === todos.length) return;\n\t\ttodos = next;\n\t\trefreshWidget();\n\t}\n\n\t// --- Expansion bridge (interactive-mode setToolsExpanded invokes this) ---\n\tfunction registerExpandedBridge(): void {\n\t\t(globalThis as Record<symbol, unknown>)[EXPANDED_BRIDGE_SYMBOL] = (value: boolean) => {\n\t\t\texpanded = Boolean(value);\n\t\t\trefreshWidget();\n\t\t};\n\t}\n\tregisterExpandedBridge();\n\n\t// --- Session lifecycle: per-session in-memory state ---\n\tpi.on(\"session_start\", (_event, ctx) => {\n\t\tlastCtx = ctx;\n\t\ttodos = [];\n\t\texpanded = ctx.hasUI ? (ctx.ui.getToolsExpanded?.() ?? false) : false;\n\t\tregisterExpandedBridge();\n\t\tif (ctx.hasUI) ctx.ui.setWidget(WIDGET_KEY, undefined);\n\t});\n\n\t// lunr: prune completed items on the next real user message, not slash\n\t// commands or agent follow-up turns.\n\tpi.on(\"message_start\", (event, ctx) => {\n\t\tif (event.message?.role !== \"user\") return;\n\t\tlastCtx = ctx;\n\t\tpruneCompleted();\n\t});\n\n\tpi.on(\"session_shutdown\", () => {\n\t\tif (lastCtx?.hasUI) lastCtx.ui.setWidget(WIDGET_KEY, undefined);\n\t\tlastCtx = null;\n\t\tdelete (globalThis as Record<symbol, unknown>)[EXPANDED_BRIDGE_SYMBOL];\n\t});\n\n\t// --- todo tool (agent-facing) ---\n\tpi.registerTool({\n\t\tname: \"todo\",\n\t\tlabel: \"Todo\",\n\t\tdescription: [\n\t\t\t\"Manage the session todo list shown above the user's editor. Full-replace semantics:\",\n\t\t\t\"every call must send the ENTIRE list; send an empty array to clear it.\",\n\t\t\t\"Each item: { id, content, status: 'pending' | 'in_progress' | 'completed' }.\",\n\t\t\t\"Use it to track multi-step work; keep exactly one item in_progress at a time.\",\n\t\t].join(\"\\n\"),\n\t\tparameters: Type.Object({\n\t\t\ttodos: Type.Array(\n\t\t\t\tType.Object({\n\t\t\t\t\tid: Type.String({ description: \"Stable id for the item.\" }),\n\t\t\t\t\tcontent: Type.String({ description: \"Short description of the task.\" }),\n\t\t\t\t\tstatus: Type.Union([\n\t\t\t\t\t\tType.Literal(\"pending\"),\n\t\t\t\t\t\tType.Literal(\"in_progress\"),\n\t\t\t\t\t\tType.Literal(\"completed\"),\n\t\t\t\t\t]),\n\t\t\t\t}),\n\t\t\t),\n\t\t}),\n\t\tasync execute(_toolCallId, params, _signal, _onUpdate, ctx) {\n\t\t\tconst text = (t: string) => ({ content: [{ type: \"text\" as const, text: t }] });\n\t\t\tconst incoming = Array.isArray(params?.todos) ? params.todos : [];\n\t\t\ttodos = incoming\n\t\t\t\t.filter((t: any) => t && typeof t.content === \"string\" && t.content.trim().length > 0)\n\t\t\t\t.map((t: any, i: number) => ({\n\t\t\t\t\tid: typeof t.id === \"string\" && t.id ? t.id : String(i + 1),\n\t\t\t\t\tcontent: t.content.trim(),\n\t\t\t\t\tstatus: t.status === \"in_progress\" || t.status === \"completed\" ? t.status : \"pending\",\n\t\t\t\t}));\n\t\t\tlastCtx = ctx;\n\t\t\trefreshWidget();\n\t\t\treturn text(summarizeTodos(todos));\n\t\t},\n\t\trenderCall(args, theme) {\n\t\t\tconst n = Array.isArray(args?.todos) ? args.todos.length : 0;\n\t\t\tconst detail = n === 0 ? \"clear\" : `${n} item${n === 1 ? \"\" : \"s\"}`;\n\t\t\treturn new Text(theme.fg(\"toolTitle\", theme.bold(\"todo \")) + theme.fg(\"dim\", detail), 0, 0);\n\t\t},\n\t\trenderResult(result, { isPartial }, theme) {\n\t\t\tif (isPartial) return new Text(theme.fg(\"warning\", \"Updating todos...\"), 0, 0);\n\t\t\tconst first = result?.content?.[0];\n\t\t\tconst summary = first?.type === \"text\" ? first.text : \"\";\n\t\t\treturn new Text(theme.fg(\"dim\", summary), 0, 0);\n\t\t},\n\t});\n}\n"]}
@@ -11,8 +11,9 @@
11
11
  * cron tool's minimal footprint.
12
12
  * - After every state change the list is mirrored into a `todos` widget above
13
13
  * the editor (component factory, so MAX_WIDGET_LINES string truncation does
14
- * not apply). Collapsed: up to 3 active rows (in-progress first, then
15
- * pending). Completed items never appear collapsed. `+N more` still
14
+ * not apply). Collapsed: four active items fit in full; lists of five or
15
+ * more show three rows (in-progress first, then pending). Completed items
16
+ * never appear collapsed. `+N more` still
16
17
  * applies to hidden active rows. Expanded: every row, completed included
17
18
  * until the next user turn prunes them.
18
19
  * - Click the widget to expand/collapse.
@@ -26,7 +27,7 @@
26
27
  import { Text } from "@ashx-j/lunr-tui";
27
28
  import { Type } from "@sinclair/typebox";
28
29
  import { keyText } from "../modes/interactive/components/keybinding-hints.js";
29
- /** Collapsed widget shows at most this many active (non-completed) rows. */
30
+ /** Collapsed widgets with five or more active items show this many rows. */
30
31
  export const TODO_WIDGET_COLLAPSED_ROWS = 3;
31
32
  const STATUS_GLYPHS = {
32
33
  in_progress: "●",
@@ -51,8 +52,8 @@ export function summarizeTodos(todos) {
51
52
  return `${todos.length} todo${todos.length === 1 ? "" : "s"}: ${parts.join(", ")}`;
52
53
  }
53
54
  /**
54
- * Widget lines for the current list. Collapsed mode shows up to
55
- * TODO_WIDGET_COLLAPSED_ROWS active rows (in-progress first, then pending)
55
+ * Widget lines for the current list. Collapsed mode shows all four active rows,
56
+ * or TODO_WIDGET_COLLAPSED_ROWS when there are five or more (in-progress first, then pending),
56
57
  * and never emits a `✓ N done` summary. A `+N more (<expandKey> to expand)`
57
58
  * hint is appended when active rows are hidden. Expanded mode shows every
58
59
  * row, completed included. Empty list / all-completed collapsed → no lines.
@@ -73,8 +74,9 @@ export function buildTodoWidgetLines(todos, expanded, expandKey = "click") {
73
74
  if (expanded) {
74
75
  return [...active.map(row), ...done.map(row)];
75
76
  }
76
- const lines = active.slice(0, TODO_WIDGET_COLLAPSED_ROWS).map(row);
77
- const hidden = active.length - TODO_WIDGET_COLLAPSED_ROWS;
77
+ const visibleRows = active.length === 4 ? 4 : TODO_WIDGET_COLLAPSED_ROWS;
78
+ const lines = active.slice(0, visibleRows).map(row);
79
+ const hidden = active.length - visibleRows;
78
80
  if (hidden > 0) {
79
81
  lines.push({ kind: "hint", text: `+${hidden} more` });
80
82
  }
@@ -1 +1 @@
1
- {"version":3,"file":"lunr-todos.js","sourceRoot":"","sources":["../../src/builtin-extensions/lunr-todos.ts"],"names":[],"mappings":"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","sourcesContent":["// @ts-nocheck\n/**\n * lunr-todos — lunR-native agent todo list with an above-editor widget.\n *\n * lunR: this file is lunR-native (not an absorbed upstream extension).\n *\n * - One `todo` tool (TypeBox), full-replace semantics: every call sends the\n * ENTIRE list; an empty array clears it. State is per-session, in-memory\n * only (extension closure) — nothing is persisted.\n * - Chat render is a quiet one-liner (renderCall/renderResult), matching the\n * cron tool's minimal footprint.\n * - After every state change the list is mirrored into a `todos` widget above\n * the editor (component factory, so MAX_WIDGET_LINES string truncation does\n * not apply). Collapsed: up to 3 active rows (in-progress first, then\n * pending). Completed items never appear collapsed. `+N more` still\n * applies to hidden active rows. Expanded: every row, completed included\n * until the next user turn prunes them.\n * - Click the widget to expand/collapse.\n *\n * The pure logic (summarizeTodos / buildTodoWidgetLines) is exported for\n * vitest — see test/lunr-todos.test.ts.\n *\n * `// @ts-nocheck` matches the builtin-extension convention (see lunr-cron).\n * Runtime imports stay on concrete core modules — never the package barrel.\n */\n\nimport type { ExtensionAPI, ExtensionContext } from \"@ashx-j/lunr\";\nimport { Text } from \"@ashx-j/lunr-tui\";\nimport { Type } from \"@sinclair/typebox\";\nimport { keyText } from \"../modes/interactive/components/keybinding-hints.ts\";\n\n// ---------------------------------------------------------------------------\n// Pure logic (exported for tests)\n// ---------------------------------------------------------------------------\n\nexport type TodoStatus = \"pending\" | \"in_progress\" | \"completed\";\n\nexport interface TodoItem {\n\tid: string;\n\tcontent: string;\n\tstatus: TodoStatus;\n}\n\nexport interface TodoWidgetLine {\n\tkind: \"todo\" | \"summary\" | \"hint\";\n\tstatus?: TodoStatus;\n\ttext: string;\n}\n\n/** Collapsed widget shows at most this many active (non-completed) rows. */\nexport const TODO_WIDGET_COLLAPSED_ROWS = 3;\n\nconst STATUS_GLYPHS: Record<TodoStatus, string> = {\n\tin_progress: \"●\",\n\tpending: \"○\",\n\tcompleted: \"✓\",\n};\n\n/** Short model-facing summary, e.g. \"4 todos: 1 in progress, 2 pending, 1 completed\". */\nexport function summarizeTodos(todos: TodoItem[]): string {\n\tif (todos.length === 0) return \"No todos.\";\n\tconst count = (s: TodoStatus) => todos.filter((t) => t.status === s).length;\n\tconst parts: string[] = [];\n\tconst inProgress = count(\"in_progress\");\n\tconst pending = count(\"pending\");\n\tconst completed = count(\"completed\");\n\tif (inProgress > 0) parts.push(`${inProgress} in progress`);\n\tif (pending > 0) parts.push(`${pending} pending`);\n\tif (completed > 0) parts.push(`${completed} completed`);\n\treturn `${todos.length} todo${todos.length === 1 ? \"\" : \"s\"}: ${parts.join(\", \")}`;\n}\n\n/**\n * Widget lines for the current list. Collapsed mode shows up to\n * TODO_WIDGET_COLLAPSED_ROWS active rows (in-progress first, then pending)\n * and never emits a `✓ N done` summary. A `+N more (<expandKey> to expand)`\n * hint is appended when active rows are hidden. Expanded mode shows every\n * row, completed included. Empty list / all-completed collapsed → no lines.\n */\nexport function buildTodoWidgetLines(todos: TodoItem[], expanded: boolean, expandKey = \"click\"): TodoWidgetLine[] {\n\tif (todos.length === 0) return [];\n\tconst row = (t: TodoItem): TodoWidgetLine => ({\n\t\tkind: \"todo\",\n\t\tstatus: t.status,\n\t\ttext: `${STATUS_GLYPHS[t.status]} ${t.content}`,\n\t});\n\tconst active = [\n\t\t...todos.filter((t) => t.status === \"in_progress\"),\n\t\t...todos.filter((t) => t.status === \"pending\"),\n\t];\n\tconst done = todos.filter((t) => t.status === \"completed\");\n\tif (expanded) {\n\t\treturn [...active.map(row), ...done.map(row)];\n\t}\n\tconst lines: TodoWidgetLine[] = active.slice(0, TODO_WIDGET_COLLAPSED_ROWS).map(row);\n\tconst hidden = active.length - TODO_WIDGET_COLLAPSED_ROWS;\n\tif (hidden > 0) {\n\t\tlines.push({ kind: \"hint\", text: `+${hidden} more` });\n\t}\n\treturn lines;\n}\n\n// ---------------------------------------------------------------------------\n// Extension\n// ---------------------------------------------------------------------------\n\nconst WIDGET_KEY = \"todos\";\nconst EXPANDED_BRIDGE_SYMBOL = Symbol.for(\"@lunr/tools-expanded-changed\");\n\nexport default function (pi: ExtensionAPI): void {\n\tlet todos: TodoItem[] = [];\n\tlet expanded = false;\n\tlet lastCtx: ExtensionContext | null = null;\n\n\tfunction colorize(line: TodoWidgetLine, theme: any): string {\n\t\tif (line.kind === \"summary\" || line.kind === \"hint\") return theme.fg(\"muted\", line.text);\n\t\tif (line.status === \"in_progress\") return theme.fg(\"accent\", line.text);\n\t\tif (line.status === \"completed\") return theme.fg(\"dim\", line.text);\n\t\treturn line.text;\n\t}\n\n\tfunction refreshWidget(): void {\n\t\tconst ctx = lastCtx;\n\t\tif (!ctx?.hasUI) return;\n\t\tconst lines = buildTodoWidgetLines(todos, expanded, keyText(\"app.tools.expand\"));\n\t\tif (todos.length === 0 || lines.length === 0) {\n\t\t\tctx.ui.setWidget(WIDGET_KEY, undefined);\n\t\t\treturn;\n\t\t}\n\t\tctx.ui.setWidget(\n\t\t\tWIDGET_KEY,\n\t\t\t(_tui: unknown, theme: any) => {\n\t\t\t\tconst text = new Text(lines.map((l) => colorize(l, theme)).join(\"\\n\"), 1, 0);\n\t\t\t\treturn {\n\t\t\t\t\trender: (width: number) => text.render(width),\n\t\t\t\t\tinvalidate: () => text.invalidate?.(),\n\t\t\t\t\thandleClick: () => {\n\t\t\t\t\t\texpanded = !expanded;\n\t\t\t\t\t\trefreshWidget();\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t},\n\t\t\t{ placement: \"aboveEditor\" },\n\t\t);\n\t}\n\n\tfunction pruneCompleted(): void {\n\t\tconst next = todos.filter((t) => t.status !== \"completed\");\n\t\tif (next.length === todos.length) return;\n\t\ttodos = next;\n\t\trefreshWidget();\n\t}\n\n\t// --- Expansion bridge (interactive-mode setToolsExpanded invokes this) ---\n\tfunction registerExpandedBridge(): void {\n\t\t(globalThis as Record<symbol, unknown>)[EXPANDED_BRIDGE_SYMBOL] = (value: boolean) => {\n\t\t\texpanded = Boolean(value);\n\t\t\trefreshWidget();\n\t\t};\n\t}\n\tregisterExpandedBridge();\n\n\t// --- Session lifecycle: per-session in-memory state ---\n\tpi.on(\"session_start\", (_event, ctx) => {\n\t\tlastCtx = ctx;\n\t\ttodos = [];\n\t\texpanded = ctx.hasUI ? (ctx.ui.getToolsExpanded?.() ?? false) : false;\n\t\tregisterExpandedBridge();\n\t\tif (ctx.hasUI) ctx.ui.setWidget(WIDGET_KEY, undefined);\n\t});\n\n\t// lunr: prune completed items on the next real user message, not slash\n\t// commands or agent follow-up turns.\n\tpi.on(\"message_start\", (event, ctx) => {\n\t\tif (event.message?.role !== \"user\") return;\n\t\tlastCtx = ctx;\n\t\tpruneCompleted();\n\t});\n\n\tpi.on(\"session_shutdown\", () => {\n\t\tif (lastCtx?.hasUI) lastCtx.ui.setWidget(WIDGET_KEY, undefined);\n\t\tlastCtx = null;\n\t\tdelete (globalThis as Record<symbol, unknown>)[EXPANDED_BRIDGE_SYMBOL];\n\t});\n\n\t// --- todo tool (agent-facing) ---\n\tpi.registerTool({\n\t\tname: \"todo\",\n\t\tlabel: \"Todo\",\n\t\tdescription: [\n\t\t\t\"Manage the session todo list shown above the user's editor. Full-replace semantics:\",\n\t\t\t\"every call must send the ENTIRE list; send an empty array to clear it.\",\n\t\t\t\"Each item: { id, content, status: 'pending' | 'in_progress' | 'completed' }.\",\n\t\t\t\"Use it to track multi-step work; keep exactly one item in_progress at a time.\",\n\t\t].join(\"\\n\"),\n\t\tparameters: Type.Object({\n\t\t\ttodos: Type.Array(\n\t\t\t\tType.Object({\n\t\t\t\t\tid: Type.String({ description: \"Stable id for the item.\" }),\n\t\t\t\t\tcontent: Type.String({ description: \"Short description of the task.\" }),\n\t\t\t\t\tstatus: Type.Union([\n\t\t\t\t\t\tType.Literal(\"pending\"),\n\t\t\t\t\t\tType.Literal(\"in_progress\"),\n\t\t\t\t\t\tType.Literal(\"completed\"),\n\t\t\t\t\t]),\n\t\t\t\t}),\n\t\t\t),\n\t\t}),\n\t\tasync execute(_toolCallId, params, _signal, _onUpdate, ctx) {\n\t\t\tconst text = (t: string) => ({ content: [{ type: \"text\" as const, text: t }] });\n\t\t\tconst incoming = Array.isArray(params?.todos) ? params.todos : [];\n\t\t\ttodos = incoming\n\t\t\t\t.filter((t: any) => t && typeof t.content === \"string\" && t.content.trim().length > 0)\n\t\t\t\t.map((t: any, i: number) => ({\n\t\t\t\t\tid: typeof t.id === \"string\" && t.id ? t.id : String(i + 1),\n\t\t\t\t\tcontent: t.content.trim(),\n\t\t\t\t\tstatus: t.status === \"in_progress\" || t.status === \"completed\" ? t.status : \"pending\",\n\t\t\t\t}));\n\t\t\tlastCtx = ctx;\n\t\t\trefreshWidget();\n\t\t\treturn text(summarizeTodos(todos));\n\t\t},\n\t\trenderCall(args, theme) {\n\t\t\tconst n = Array.isArray(args?.todos) ? args.todos.length : 0;\n\t\t\tconst detail = n === 0 ? \"clear\" : `${n} item${n === 1 ? \"\" : \"s\"}`;\n\t\t\treturn new Text(theme.fg(\"toolTitle\", theme.bold(\"todo \")) + theme.fg(\"dim\", detail), 0, 0);\n\t\t},\n\t\trenderResult(result, { isPartial }, theme) {\n\t\t\tif (isPartial) return new Text(theme.fg(\"warning\", \"Updating todos...\"), 0, 0);\n\t\t\tconst first = result?.content?.[0];\n\t\t\tconst summary = first?.type === \"text\" ? first.text : \"\";\n\t\t\treturn new Text(theme.fg(\"dim\", summary), 0, 0);\n\t\t},\n\t});\n}\n"]}
1
+ {"version":3,"file":"lunr-todos.js","sourceRoot":"","sources":["../../src/builtin-extensions/lunr-todos.ts"],"names":[],"mappings":"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","sourcesContent":["// @ts-nocheck\n/**\n * lunr-todos — lunR-native agent todo list with an above-editor widget.\n *\n * lunR: this file is lunR-native (not an absorbed upstream extension).\n *\n * - One `todo` tool (TypeBox), full-replace semantics: every call sends the\n * ENTIRE list; an empty array clears it. State is per-session, in-memory\n * only (extension closure) — nothing is persisted.\n * - Chat render is a quiet one-liner (renderCall/renderResult), matching the\n * cron tool's minimal footprint.\n * - After every state change the list is mirrored into a `todos` widget above\n * the editor (component factory, so MAX_WIDGET_LINES string truncation does\n * not apply). Collapsed: four active items fit in full; lists of five or\n * more show three rows (in-progress first, then pending). Completed items\n * never appear collapsed. `+N more` still\n * applies to hidden active rows. Expanded: every row, completed included\n * until the next user turn prunes them.\n * - Click the widget to expand/collapse.\n *\n * The pure logic (summarizeTodos / buildTodoWidgetLines) is exported for\n * vitest — see test/lunr-todos.test.ts.\n *\n * `// @ts-nocheck` matches the builtin-extension convention (see lunr-cron).\n * Runtime imports stay on concrete core modules — never the package barrel.\n */\n\nimport type { ExtensionAPI, ExtensionContext } from \"@ashx-j/lunr\";\nimport { Text } from \"@ashx-j/lunr-tui\";\nimport { Type } from \"@sinclair/typebox\";\nimport { keyText } from \"../modes/interactive/components/keybinding-hints.ts\";\n\n// ---------------------------------------------------------------------------\n// Pure logic (exported for tests)\n// ---------------------------------------------------------------------------\n\nexport type TodoStatus = \"pending\" | \"in_progress\" | \"completed\";\n\nexport interface TodoItem {\n\tid: string;\n\tcontent: string;\n\tstatus: TodoStatus;\n}\n\nexport interface TodoWidgetLine {\n\tkind: \"todo\" | \"summary\" | \"hint\";\n\tstatus?: TodoStatus;\n\ttext: string;\n}\n\n/** Collapsed widgets with five or more active items show this many rows. */\nexport const TODO_WIDGET_COLLAPSED_ROWS = 3;\n\nconst STATUS_GLYPHS: Record<TodoStatus, string> = {\n\tin_progress: \"●\",\n\tpending: \"○\",\n\tcompleted: \"✓\",\n};\n\n/** Short model-facing summary, e.g. \"4 todos: 1 in progress, 2 pending, 1 completed\". */\nexport function summarizeTodos(todos: TodoItem[]): string {\n\tif (todos.length === 0) return \"No todos.\";\n\tconst count = (s: TodoStatus) => todos.filter((t) => t.status === s).length;\n\tconst parts: string[] = [];\n\tconst inProgress = count(\"in_progress\");\n\tconst pending = count(\"pending\");\n\tconst completed = count(\"completed\");\n\tif (inProgress > 0) parts.push(`${inProgress} in progress`);\n\tif (pending > 0) parts.push(`${pending} pending`);\n\tif (completed > 0) parts.push(`${completed} completed`);\n\treturn `${todos.length} todo${todos.length === 1 ? \"\" : \"s\"}: ${parts.join(\", \")}`;\n}\n\n/**\n * Widget lines for the current list. Collapsed mode shows all four active rows,\n * or TODO_WIDGET_COLLAPSED_ROWS when there are five or more (in-progress first, then pending),\n * and never emits a `✓ N done` summary. A `+N more (<expandKey> to expand)`\n * hint is appended when active rows are hidden. Expanded mode shows every\n * row, completed included. Empty list / all-completed collapsed → no lines.\n */\nexport function buildTodoWidgetLines(todos: TodoItem[], expanded: boolean, expandKey = \"click\"): TodoWidgetLine[] {\n\tif (todos.length === 0) return [];\n\tconst row = (t: TodoItem): TodoWidgetLine => ({\n\t\tkind: \"todo\",\n\t\tstatus: t.status,\n\t\ttext: `${STATUS_GLYPHS[t.status]} ${t.content}`,\n\t});\n\tconst active = [\n\t\t...todos.filter((t) => t.status === \"in_progress\"),\n\t\t...todos.filter((t) => t.status === \"pending\"),\n\t];\n\tconst done = todos.filter((t) => t.status === \"completed\");\n\tif (expanded) {\n\t\treturn [...active.map(row), ...done.map(row)];\n\t}\n\tconst visibleRows = active.length === 4 ? 4 : TODO_WIDGET_COLLAPSED_ROWS;\n\tconst lines: TodoWidgetLine[] = active.slice(0, visibleRows).map(row);\n\tconst hidden = active.length - visibleRows;\n\tif (hidden > 0) {\n\t\tlines.push({ kind: \"hint\", text: `+${hidden} more` });\n\t}\n\treturn lines;\n}\n\n// ---------------------------------------------------------------------------\n// Extension\n// ---------------------------------------------------------------------------\n\nconst WIDGET_KEY = \"todos\";\nconst EXPANDED_BRIDGE_SYMBOL = Symbol.for(\"@lunr/tools-expanded-changed\");\n\nexport default function (pi: ExtensionAPI): void {\n\tlet todos: TodoItem[] = [];\n\tlet expanded = false;\n\tlet lastCtx: ExtensionContext | null = null;\n\n\tfunction colorize(line: TodoWidgetLine, theme: any): string {\n\t\tif (line.kind === \"summary\" || line.kind === \"hint\") return theme.fg(\"muted\", line.text);\n\t\tif (line.status === \"in_progress\") return theme.fg(\"accent\", line.text);\n\t\tif (line.status === \"completed\") return theme.fg(\"dim\", line.text);\n\t\treturn line.text;\n\t}\n\n\tfunction refreshWidget(): void {\n\t\tconst ctx = lastCtx;\n\t\tif (!ctx?.hasUI) return;\n\t\tconst lines = buildTodoWidgetLines(todos, expanded, keyText(\"app.tools.expand\"));\n\t\tif (todos.length === 0 || lines.length === 0) {\n\t\t\tctx.ui.setWidget(WIDGET_KEY, undefined);\n\t\t\treturn;\n\t\t}\n\t\tctx.ui.setWidget(\n\t\t\tWIDGET_KEY,\n\t\t\t(_tui: unknown, theme: any) => {\n\t\t\t\tconst text = new Text(lines.map((l) => colorize(l, theme)).join(\"\\n\"), 1, 0);\n\t\t\t\treturn {\n\t\t\t\t\trender: (width: number) => text.render(width),\n\t\t\t\t\tinvalidate: () => text.invalidate?.(),\n\t\t\t\t\thandleClick: () => {\n\t\t\t\t\t\texpanded = !expanded;\n\t\t\t\t\t\trefreshWidget();\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t},\n\t\t\t{ placement: \"aboveEditor\" },\n\t\t);\n\t}\n\n\tfunction pruneCompleted(): void {\n\t\tconst next = todos.filter((t) => t.status !== \"completed\");\n\t\tif (next.length === todos.length) return;\n\t\ttodos = next;\n\t\trefreshWidget();\n\t}\n\n\t// --- Expansion bridge (interactive-mode setToolsExpanded invokes this) ---\n\tfunction registerExpandedBridge(): void {\n\t\t(globalThis as Record<symbol, unknown>)[EXPANDED_BRIDGE_SYMBOL] = (value: boolean) => {\n\t\t\texpanded = Boolean(value);\n\t\t\trefreshWidget();\n\t\t};\n\t}\n\tregisterExpandedBridge();\n\n\t// --- Session lifecycle: per-session in-memory state ---\n\tpi.on(\"session_start\", (_event, ctx) => {\n\t\tlastCtx = ctx;\n\t\ttodos = [];\n\t\texpanded = ctx.hasUI ? (ctx.ui.getToolsExpanded?.() ?? false) : false;\n\t\tregisterExpandedBridge();\n\t\tif (ctx.hasUI) ctx.ui.setWidget(WIDGET_KEY, undefined);\n\t});\n\n\t// lunr: prune completed items on the next real user message, not slash\n\t// commands or agent follow-up turns.\n\tpi.on(\"message_start\", (event, ctx) => {\n\t\tif (event.message?.role !== \"user\") return;\n\t\tlastCtx = ctx;\n\t\tpruneCompleted();\n\t});\n\n\tpi.on(\"session_shutdown\", () => {\n\t\tif (lastCtx?.hasUI) lastCtx.ui.setWidget(WIDGET_KEY, undefined);\n\t\tlastCtx = null;\n\t\tdelete (globalThis as Record<symbol, unknown>)[EXPANDED_BRIDGE_SYMBOL];\n\t});\n\n\t// --- todo tool (agent-facing) ---\n\tpi.registerTool({\n\t\tname: \"todo\",\n\t\tlabel: \"Todo\",\n\t\tdescription: [\n\t\t\t\"Manage the session todo list shown above the user's editor. Full-replace semantics:\",\n\t\t\t\"every call must send the ENTIRE list; send an empty array to clear it.\",\n\t\t\t\"Each item: { id, content, status: 'pending' | 'in_progress' | 'completed' }.\",\n\t\t\t\"Use it to track multi-step work; keep exactly one item in_progress at a time.\",\n\t\t].join(\"\\n\"),\n\t\tparameters: Type.Object({\n\t\t\ttodos: Type.Array(\n\t\t\t\tType.Object({\n\t\t\t\t\tid: Type.String({ description: \"Stable id for the item.\" }),\n\t\t\t\t\tcontent: Type.String({ description: \"Short description of the task.\" }),\n\t\t\t\t\tstatus: Type.Union([\n\t\t\t\t\t\tType.Literal(\"pending\"),\n\t\t\t\t\t\tType.Literal(\"in_progress\"),\n\t\t\t\t\t\tType.Literal(\"completed\"),\n\t\t\t\t\t]),\n\t\t\t\t}),\n\t\t\t),\n\t\t}),\n\t\tasync execute(_toolCallId, params, _signal, _onUpdate, ctx) {\n\t\t\tconst text = (t: string) => ({ content: [{ type: \"text\" as const, text: t }] });\n\t\t\tconst incoming = Array.isArray(params?.todos) ? params.todos : [];\n\t\t\ttodos = incoming\n\t\t\t\t.filter((t: any) => t && typeof t.content === \"string\" && t.content.trim().length > 0)\n\t\t\t\t.map((t: any, i: number) => ({\n\t\t\t\t\tid: typeof t.id === \"string\" && t.id ? t.id : String(i + 1),\n\t\t\t\t\tcontent: t.content.trim(),\n\t\t\t\t\tstatus: t.status === \"in_progress\" || t.status === \"completed\" ? t.status : \"pending\",\n\t\t\t\t}));\n\t\t\tlastCtx = ctx;\n\t\t\trefreshWidget();\n\t\t\treturn text(summarizeTodos(todos));\n\t\t},\n\t\trenderCall(args, theme) {\n\t\t\tconst n = Array.isArray(args?.todos) ? args.todos.length : 0;\n\t\t\tconst detail = n === 0 ? \"clear\" : `${n} item${n === 1 ? \"\" : \"s\"}`;\n\t\t\treturn new Text(theme.fg(\"toolTitle\", theme.bold(\"todo \")) + theme.fg(\"dim\", detail), 0, 0);\n\t\t},\n\t\trenderResult(result, { isPartial }, theme) {\n\t\t\tif (isPartial) return new Text(theme.fg(\"warning\", \"Updating todos...\"), 0, 0);\n\t\t\tconst first = result?.content?.[0];\n\t\t\tconst summary = first?.type === \"text\" ? first.text : \"\";\n\t\t\treturn new Text(theme.fg(\"dim\", summary), 0, 0);\n\t\t},\n\t});\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"spawn.d.ts","sourceRoot":"","sources":["../../../../src/builtin-extensions/pi-intercom/broker/spawn.ts"],"names":[],"mappings":"AA4BA,KAAK,gBAAgB,GACjB;IACA,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,GACC;IACA,IAAI,EAAE,kBAAkB,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,EAAE,MAAM,CAAC;CAC7B,CAAC;AAMJ,wBAAgB,aAAa,CAAC,YAAY,GAAE,MAAsB,GAAG,MAAM,CAa1E;AAED,wBAAgB,+BAA+B,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAE7F;AAED,wBAAgB,uBAAuB,CAAC,SAAS,GAAE,MAAgD,GAAG,MAAM,CAI3G;AAUD,wBAAgB,4BAA4B,CAAC,WAAW,GAAE,MAAqB,GAAG,MAAM,CAEvF;AA+BD,wBAAgB,2BAA2B,CACzC,UAAU,EAAE,MAAM,EAClB,YAAY,GAAE,MAAsB,EACpC,QAAQ,GAAE,MAAyB,EACnC,aAAa,SAAQ,EACrB,UAAU,GAAE,MAAM,EAA4B,EAC9C,aAAa,CAAC,EAAE,MAAM,GACrB,MAAM,CAIR;AAED,wBAAgB,8BAA8B,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAO1E;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CASpF;AAeD,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,EAAE,EACpB,YAAY,GAAE,MAAsB,EACpC,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAC5C,WAAW,GAAE,MAAqB,EAClC,QAAQ,GAAE,MAAyB,EACnC,aAAa,CAAC,EAAE,MAAM,GACrB,gBAAgB,CAyBlB;AAED,wBAAgB,qBAAqB,CACnC,YAAY,GAAE,MAAsB,EACpC,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,QAAQ,CAAC,EAAE,MAAM,GAChB;IACD,QAAQ,EAAE,IAAI,CAAC;IACf,KAAK,EAAE,QAAQ,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,WAAW,EAAE,IAAI,CAAC;CACnB,CAQA;AAMD,wBAAsB,mBAAmB,CAAC,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA2FpG","sourcesContent":["// @ts-nocheck\nimport { spawn } from \"child_process\";\nimport { closeSync, existsSync, openSync, readFileSync, unlinkSync, writeFileSync } from \"fs\";\nimport { join, dirname } from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { createRequire } from \"module\";\nimport net from \"net\";\nimport { randomUUID } from \"crypto\";\nimport { createMessageReader, writeMessage } from \"./framing.ts\";\nimport {\n ensureIntercomRuntimeDir,\n getAgentDirPath,\n getBrokerConnectTarget,\n getIntercomDirPath,\n INTERCOM_PROTOCOL_NAME,\n INTERCOM_PROTOCOL_VERSION,\n INTERCOM_RUNTIME_FILE_MODE,\n restrictIntercomRuntimeFile,\n type BrokerConnectTarget,\n} from \"./paths.ts\";\n\nconst INTERCOM_DIR = getIntercomDirPath();\nconst EXTENSION_DIR = join(dirname(fileURLToPath(import.meta.url)), \"..\");\nconst BROKER_PID = join(INTERCOM_DIR, \"broker.pid\");\nconst BROKER_SPAWN_LOCK = join(INTERCOM_DIR, \"broker.spawn.lock\");\nconst BROKER_STDERR = join(INTERCOM_DIR, \"broker.stderr.log\");\nconst NATIVE_SUPERVISOR_CHANNEL_DIR_ENV = \"PI_SUBAGENT_SUPERVISOR_CHANNEL_DIR\";\n\ntype BrokerLaunchSpec =\n | {\n kind: \"direct\";\n command: string;\n args: string[];\n }\n | {\n kind: \"windows-launcher\";\n command: string;\n args: string[];\n launcherPath: string;\n launcherCommandLine: string;\n };\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\nexport function getTsxCliPath(extensionDir: string = EXTENSION_DIR): string {\n // Resolve tsx via Node's module resolution so it works regardless of whether\n // tsx is bundled under extensionDir/node_modules or hoisted to a workspace\n // root by npm. We resolve the tsx package main entry (its \"exports\" field\n // does not expose ./dist/cli.mjs as a subpath) and then locate cli.mjs next\n // to it. Falls back to the legacy relative path if resolution fails.\n try {\n const requireFromExtension = createRequire(import.meta.url);\n const tsxMain = requireFromExtension.resolve(\"tsx\");\n return join(dirname(tsxMain), \"cli.mjs\");\n } catch {\n return join(extensionDir, \"node_modules\", \"tsx\", \"dist\", \"cli.mjs\");\n }\n}\n\nexport function isNativeSupervisorChannelActive(env: NodeJS.ProcessEnv = process.env): boolean {\n return Boolean(env[NATIVE_SUPERVISOR_CHANNEL_DIR_ENV]?.trim());\n}\n\nexport function resolveBrokerScriptPath(brokerDir: string = dirname(fileURLToPath(import.meta.url))): string {\n const tsPath = join(brokerDir, \"broker.ts\");\n if (existsSync(tsPath)) return tsPath;\n return join(brokerDir, \"broker.js\");\n}\n\nfunction isCompiledBrokerScript(brokerPath: string): boolean {\n return brokerPath.endsWith(\".js\");\n}\n\nfunction quoteWindowsArg(value: string): string {\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\n}\n\nexport function getWindowsHiddenLauncherPath(intercomDir: string = INTERCOM_DIR): string {\n return join(intercomDir, \"broker-launch.vbs\");\n}\n\nfunction usesDefaultBrokerCommand(brokerCommand: string, brokerArgs: string[]): boolean {\n return brokerCommand === \"npx\"\n && brokerArgs.length === 2\n && brokerArgs[0] === \"--no-install\"\n && brokerArgs[1] === \"tsx\";\n}\n\nfunction defaultBrokerArgv(\n brokerPath: string,\n extensionDir: string,\n nodePath: string,\n brokerCommand: string,\n brokerArgs: string[],\n): { command: string; args: string[] } {\n if (usesDefaultBrokerCommand(brokerCommand, brokerArgs) && isCompiledBrokerScript(brokerPath)) {\n return { command: nodePath, args: [brokerPath] };\n }\n if (usesDefaultBrokerCommand(brokerCommand, brokerArgs)) {\n return { command: nodePath, args: [getTsxCliPath(extensionDir), brokerPath] };\n }\n return { command: brokerCommand, args: [...brokerArgs, brokerPath] };\n}\n\nfunction withWindowsStderr(commandLine: string, stderrLogPath?: string): string {\n if (!stderrLogPath) return commandLine;\n const redirected = `${commandLine} 1>NUL 2>>${quoteWindowsArg(stderrLogPath)}`;\n return `cmd.exe /s /c \"${redirected}\"`;\n}\n\nexport function getWindowsBrokerCommandLine(\n brokerPath: string,\n extensionDir: string = EXTENSION_DIR,\n nodePath: string = process.execPath,\n brokerCommand = \"npx\",\n brokerArgs: string[] = [\"--no-install\", \"tsx\"],\n stderrLogPath?: string,\n): string {\n const argv = defaultBrokerArgv(brokerPath, extensionDir, nodePath, brokerCommand, brokerArgs);\n const commandLine = [quoteWindowsArg(argv.command), ...argv.args.map(quoteWindowsArg)].join(\" \");\n return withWindowsStderr(commandLine, stderrLogPath);\n}\n\nexport function getWindowsHiddenLauncherScript(commandLine: string): string {\n return [\n 'Set WshShell = CreateObject(\"WScript.Shell\")',\n `WshShell.Run \"${commandLine.replace(/\"/g, '\"\"')}\", 0, False`,\n 'Set WshShell = Nothing',\n '',\n ].join(\"\\r\\n\");\n}\n\nexport function isBrokerHealthOkMessage(message: unknown, requestId: string): boolean {\n if (typeof message !== \"object\" || message === null || !(\"type\" in message)) {\n return false;\n }\n const response = message as Record<string, unknown>;\n return response.type === \"health_ok\"\n && response.requestId === requestId\n && response.protocol === INTERCOM_PROTOCOL_NAME\n && response.version === INTERCOM_PROTOCOL_VERSION;\n}\n\nfunction writeWindowsHiddenLauncher(\n commandLine: string,\n launcherPath: string = getWindowsHiddenLauncherPath(),\n): string {\n ensureIntercomRuntimeDir(dirname(launcherPath));\n writeFileSync(launcherPath, getWindowsHiddenLauncherScript(commandLine), {\n encoding: \"utf-8\",\n mode: INTERCOM_RUNTIME_FILE_MODE,\n });\n restrictIntercomRuntimeFile(launcherPath);\n return launcherPath;\n}\n\nexport function getBrokerLaunchSpec(\n brokerPath: string,\n brokerCommand: string,\n brokerArgs: string[],\n extensionDir: string = EXTENSION_DIR,\n platform: NodeJS.Platform = process.platform,\n intercomDir: string = INTERCOM_DIR,\n nodePath: string = process.execPath,\n stderrLogPath?: string,\n): BrokerLaunchSpec {\n if (platform === \"win32\") {\n const launcherPath = getWindowsHiddenLauncherPath(intercomDir);\n return {\n kind: \"windows-launcher\",\n command: \"wscript.exe\",\n args: [launcherPath],\n launcherPath,\n launcherCommandLine: getWindowsBrokerCommandLine(\n brokerPath,\n extensionDir,\n nodePath,\n brokerCommand,\n brokerArgs,\n stderrLogPath,\n ),\n };\n }\n\n const argv = defaultBrokerArgv(brokerPath, extensionDir, nodePath, brokerCommand, brokerArgs);\n return {\n kind: \"direct\",\n command: argv.command,\n args: argv.args,\n };\n}\n\nexport function getBrokerSpawnOptions(\n extensionDir: string = EXTENSION_DIR,\n env: NodeJS.ProcessEnv = process.env,\n stderrFd?: number,\n): {\n detached: true;\n stdio: \"ignore\" | [\"ignore\", \"ignore\", number];\n cwd: string;\n env: NodeJS.ProcessEnv;\n windowsHide: true;\n} {\n return {\n detached: true,\n stdio: stderrFd === undefined ? \"ignore\" : [\"ignore\", \"ignore\", stderrFd],\n cwd: extensionDir,\n env: { ...env, PI_CODING_AGENT_DIR: getAgentDirPath(env), NODE_NO_WARNINGS: \"1\" },\n windowsHide: true,\n };\n}\n\nfunction toError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n}\n\nexport async function spawnBrokerIfNeeded(brokerCommand: string, brokerArgs: string[]): Promise<void> {\n if (isNativeSupervisorChannelActive()) {\n return;\n }\n\n ensureIntercomRuntimeDir(INTERCOM_DIR);\n\n if (await isBrokerRunning()) {\n return;\n }\n\n const ownsLock = acquireSpawnLock();\n if (!ownsLock) {\n await waitForBroker();\n return;\n }\n\n try {\n if (await isBrokerRunning()) {\n return;\n }\n\n const brokerPath = resolveBrokerScriptPath();\n const launch = getBrokerLaunchSpec(\n brokerPath,\n brokerCommand,\n brokerArgs,\n EXTENSION_DIR,\n process.platform,\n INTERCOM_DIR,\n process.execPath,\n BROKER_STDERR,\n );\n if (launch.kind === \"windows-launcher\") {\n writeWindowsHiddenLauncher(launch.launcherCommandLine, launch.launcherPath);\n }\n let stderrFd: number | undefined;\n try {\n stderrFd = openSync(BROKER_STDERR, \"a\");\n } catch {\n stderrFd = undefined;\n }\n try {\n const child = spawn(launch.command, launch.args, getBrokerSpawnOptions(EXTENSION_DIR, process.env, launch.kind === \"windows-launcher\" ? undefined : stderrFd));\n child.unref();\n\n await new Promise<void>((resolve, reject) => {\n const cleanup = () => {\n child.off(\"error\", onError);\n child.off(\"exit\", onExit);\n };\n\n const onError = (error: Error) => {\n cleanup();\n reject(new Error(`Failed to spawn intercom broker: ${error.message}`, { cause: error }));\n };\n\n const onExit = (code: number | null, signal: NodeJS.Signals | null) => {\n if (launch.kind === \"windows-launcher\" && code === 0 && signal === null) {\n return;\n }\n cleanup();\n if (signal) {\n reject(new Error(`Intercom broker exited before startup with signal ${signal}`));\n return;\n }\n reject(new Error(`Intercom broker exited before startup with code ${code ?? \"unknown\"}`));\n };\n\n child.once(\"error\", onError);\n child.once(\"exit\", onExit);\n waitForBroker().then(() => {\n cleanup();\n resolve();\n }, (error) => {\n cleanup();\n reject(toError(error));\n });\n });\n } finally {\n if (stderrFd !== undefined) {\n try {\n closeSync(stderrFd);\n } catch {\n // The child already inherited this fd; failing to close the parent copy is non-fatal.\n }\n }\n }\n } finally {\n releaseSpawnLock();\n }\n}\n\nasync function isBrokerRunning(): Promise<boolean> {\n if (await checkSocketConnectable()) {\n return true;\n }\n\n if (!existsSync(BROKER_PID)) return false;\n\n try {\n const pid = parseInt(readFileSync(BROKER_PID, \"utf-8\").trim(), 10);\n if (!Number.isFinite(pid)) return false;\n process.kill(pid, 0);\n return checkSocketConnectable();\n } catch {\n // Missing or unreadable PID state means there is no live broker to reuse.\n return false;\n }\n}\n\nfunction connectToBrokerTarget(target: BrokerConnectTarget): net.Socket {\n return typeof target === \"string\"\n ? net.connect(target)\n : net.connect({ host: target.host, port: target.port });\n}\n\nfunction checkSocketConnectable(): Promise<boolean> {\n return new Promise((resolve) => {\n let target: BrokerConnectTarget;\n try {\n target = getBrokerConnectTarget();\n } catch {\n resolve(false);\n return;\n }\n\n const socket = connectToBrokerTarget(target);\n const requestId = randomUUID();\n const expectedStateId = typeof target === \"string\" ? undefined : target.stateId;\n let settled = false;\n const finish = (isConnected: boolean) => {\n if (settled) {\n return;\n }\n settled = true;\n clearTimeout(timeout);\n socket.off(\"connect\", onConnect);\n socket.off(\"error\", onError);\n socket.off(\"data\", reader);\n socket.destroy();\n resolve(isConnected);\n };\n const onConnect = () => {\n try {\n writeMessage(socket, {\n type: \"health\",\n requestId,\n ...(expectedStateId ? { stateId: expectedStateId } : {}),\n });\n } catch {\n finish(false);\n }\n };\n const onError = () => finish(false);\n const reader = createMessageReader((message) => {\n finish(isBrokerHealthOkMessage(message, requestId));\n }, () => finish(false));\n socket.on(\"connect\", onConnect);\n socket.on(\"error\", onError);\n socket.on(\"data\", reader);\n const timeout = setTimeout(() => finish(false), 1000);\n });\n}\n\nfunction acquireSpawnLock(): boolean {\n const maxRetries = 5;\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n writeFileSync(BROKER_SPAWN_LOCK, `${process.pid}\\n${Date.now()}\\n`, {\n flag: \"wx\",\n mode: INTERCOM_RUNTIME_FILE_MODE,\n });\n restrictIntercomRuntimeFile(BROKER_SPAWN_LOCK);\n return true;\n } catch (error) {\n if (!(error instanceof Error) || (error as NodeJS.ErrnoException).code !== \"EEXIST\") {\n throw error;\n }\n if (isSpawnLockStale()) {\n try {\n unlinkSync(BROKER_SPAWN_LOCK);\n } catch {\n // If we can't delete the stale lock, retry a few times before giving up\n }\n continue;\n }\n return false;\n }\n }\n return false;\n}\n\nfunction isSpawnLockStale(): boolean {\n if (!existsSync(BROKER_SPAWN_LOCK)) {\n return false;\n }\n\n try {\n const [pidLine = \"\", createdAtLine = \"0\"] = readFileSync(BROKER_SPAWN_LOCK, \"utf-8\").trim().split(\"\\n\");\n const pid = Number.parseInt(pidLine, 10);\n const createdAt = Number.parseInt(createdAtLine, 10);\n const ageMs = Date.now() - createdAt;\n\n if (Number.isFinite(pid)) {\n try {\n process.kill(pid, 0);\n } catch {\n // The process that created the lock is gone.\n return true;\n }\n }\n\n return !Number.isFinite(createdAt) || ageMs > 10_000;\n } catch {\n // Unreadable lock contents are treated as stale so a new broker can start.\n return true;\n }\n}\n\nfunction releaseSpawnLock(): void {\n try {\n unlinkSync(BROKER_SPAWN_LOCK);\n } catch {\n // Another cleanup path may already have removed the lock.\n }\n}\n\nasync function waitForBroker(timeoutMs = 5000): Promise<void> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n if (await checkSocketConnectable()) {\n return;\n }\n await sleep(100);\n }\n throw new Error(\"Broker failed to start within timeout\");\n}\n"]}
1
+ {"version":3,"file":"spawn.d.ts","sourceRoot":"","sources":["../../../../src/builtin-extensions/pi-intercom/broker/spawn.ts"],"names":[],"mappings":"AA6BA,KAAK,gBAAgB,GACjB;IACA,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,GACC;IACA,IAAI,EAAE,kBAAkB,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,EAAE,MAAM,CAAC;CAC7B,CAAC;AAEJ,wBAAgB,aAAa,CAAC,YAAY,GAAE,MAAsB,GAAG,MAAM,CAa1E;AAED,wBAAgB,+BAA+B,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAE7F;AAED,wBAAgB,uBAAuB,CAAC,SAAS,GAAE,MAAgD,GAAG,MAAM,CAI3G;AAUD,wBAAgB,4BAA4B,CAAC,WAAW,GAAE,MAAqB,GAAG,MAAM,CAEvF;AA+BD,wBAAgB,2BAA2B,CACzC,UAAU,EAAE,MAAM,EAClB,YAAY,GAAE,MAAsB,EACpC,QAAQ,GAAE,MAAyB,EACnC,aAAa,SAAQ,EACrB,UAAU,GAAE,MAAM,EAA4B,EAC9C,aAAa,CAAC,EAAE,MAAM,GACrB,MAAM,CAIR;AAED,wBAAgB,8BAA8B,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAQ1E;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CASpF;AAeD,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,EAAE,EACpB,YAAY,GAAE,MAAsB,EACpC,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAC5C,WAAW,GAAE,MAAqB,EAClC,QAAQ,GAAE,MAAyB,EACnC,aAAa,CAAC,EAAE,MAAM,GACrB,gBAAgB,CAyBlB;AAED,wBAAgB,qBAAqB,CACnC,YAAY,GAAE,MAAsB,EACpC,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,QAAQ,CAAC,EAAE,MAAM,GAChB;IACD,QAAQ,EAAE,IAAI,CAAC;IACf,KAAK,EAAE,QAAQ,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,WAAW,EAAE,IAAI,CAAC;CACnB,CAQA;AAMD,wBAAsB,mBAAmB,CAAC,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAiGpG","sourcesContent":["// @ts-nocheck\nimport { spawn } from \"child_process\";\nimport { setTimeout as sleep } from \"node:timers/promises\";\nimport { closeSync, existsSync, openSync, readFileSync, unlinkSync, writeFileSync } from \"fs\";\nimport { join, dirname } from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { createRequire } from \"module\";\nimport net from \"net\";\nimport { randomUUID } from \"crypto\";\nimport { createMessageReader, writeMessage } from \"./framing.ts\";\nimport {\n ensureIntercomRuntimeDir,\n getAgentDirPath,\n getBrokerConnectTarget,\n getIntercomDirPath,\n INTERCOM_PROTOCOL_NAME,\n INTERCOM_PROTOCOL_VERSION,\n INTERCOM_RUNTIME_FILE_MODE,\n restrictIntercomRuntimeFile,\n type BrokerConnectTarget,\n} from \"./paths.ts\";\n\nconst INTERCOM_DIR = getIntercomDirPath();\nconst EXTENSION_DIR = join(dirname(fileURLToPath(import.meta.url)), \"..\");\nconst BROKER_PID = join(INTERCOM_DIR, \"broker.pid\");\nconst BROKER_SPAWN_LOCK = join(INTERCOM_DIR, \"broker.spawn.lock\");\nconst BROKER_STDERR = join(INTERCOM_DIR, \"broker.stderr.log\");\nconst NATIVE_SUPERVISOR_CHANNEL_DIR_ENV = \"PI_SUBAGENT_SUPERVISOR_CHANNEL_DIR\";\n\ntype BrokerLaunchSpec =\n | {\n kind: \"direct\";\n command: string;\n args: string[];\n }\n | {\n kind: \"windows-launcher\";\n command: string;\n args: string[];\n launcherPath: string;\n launcherCommandLine: string;\n };\n\nexport function getTsxCliPath(extensionDir: string = EXTENSION_DIR): string {\n // Resolve tsx via Node's module resolution so it works regardless of whether\n // tsx is bundled under extensionDir/node_modules or hoisted to a workspace\n // root by npm. We resolve the tsx package main entry (its \"exports\" field\n // does not expose ./dist/cli.mjs as a subpath) and then locate cli.mjs next\n // to it. Falls back to the legacy relative path if resolution fails.\n try {\n const requireFromExtension = createRequire(import.meta.url);\n const tsxMain = requireFromExtension.resolve(\"tsx\");\n return join(dirname(tsxMain), \"cli.mjs\");\n } catch {\n return join(extensionDir, \"node_modules\", \"tsx\", \"dist\", \"cli.mjs\");\n }\n}\n\nexport function isNativeSupervisorChannelActive(env: NodeJS.ProcessEnv = process.env): boolean {\n return Boolean(env[NATIVE_SUPERVISOR_CHANNEL_DIR_ENV]?.trim());\n}\n\nexport function resolveBrokerScriptPath(brokerDir: string = dirname(fileURLToPath(import.meta.url))): string {\n const tsPath = join(brokerDir, \"broker.ts\");\n if (existsSync(tsPath)) return tsPath;\n return join(brokerDir, \"broker.js\");\n}\n\nfunction isCompiledBrokerScript(brokerPath: string): boolean {\n return brokerPath.endsWith(\".js\");\n}\n\nfunction quoteWindowsArg(value: string): string {\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\n}\n\nexport function getWindowsHiddenLauncherPath(intercomDir: string = INTERCOM_DIR): string {\n return join(intercomDir, \"broker-launch.vbs\");\n}\n\nfunction usesDefaultBrokerCommand(brokerCommand: string, brokerArgs: string[]): boolean {\n return brokerCommand === \"npx\"\n && brokerArgs.length === 2\n && brokerArgs[0] === \"--no-install\"\n && brokerArgs[1] === \"tsx\";\n}\n\nfunction defaultBrokerArgv(\n brokerPath: string,\n extensionDir: string,\n nodePath: string,\n brokerCommand: string,\n brokerArgs: string[],\n): { command: string; args: string[] } {\n if (usesDefaultBrokerCommand(brokerCommand, brokerArgs) && isCompiledBrokerScript(brokerPath)) {\n return { command: nodePath, args: [brokerPath] };\n }\n if (usesDefaultBrokerCommand(brokerCommand, brokerArgs)) {\n return { command: nodePath, args: [getTsxCliPath(extensionDir), brokerPath] };\n }\n return { command: brokerCommand, args: [...brokerArgs, brokerPath] };\n}\n\nfunction withWindowsStderr(commandLine: string, stderrLogPath?: string): string {\n if (!stderrLogPath) return commandLine;\n const redirected = `${commandLine} 1>NUL 2>>${quoteWindowsArg(stderrLogPath)}`;\n return `cmd.exe /s /c \"${redirected}\"`;\n}\n\nexport function getWindowsBrokerCommandLine(\n brokerPath: string,\n extensionDir: string = EXTENSION_DIR,\n nodePath: string = process.execPath,\n brokerCommand = \"npx\",\n brokerArgs: string[] = [\"--no-install\", \"tsx\"],\n stderrLogPath?: string,\n): string {\n const argv = defaultBrokerArgv(brokerPath, extensionDir, nodePath, brokerCommand, brokerArgs);\n const commandLine = [quoteWindowsArg(argv.command), ...argv.args.map(quoteWindowsArg)].join(\" \");\n return withWindowsStderr(commandLine, stderrLogPath);\n}\n\nexport function getWindowsHiddenLauncherScript(commandLine: string): string {\n return [\n 'Set WshShell = CreateObject(\"WScript.Shell\")',\n `exitCode = WshShell.Run(\"${commandLine.replace(/\"/g, '\"\"')}\", 0, True)`,\n 'Set WshShell = Nothing',\n 'WScript.Quit exitCode',\n '',\n ].join(\"\\r\\n\");\n}\n\nexport function isBrokerHealthOkMessage(message: unknown, requestId: string): boolean {\n if (typeof message !== \"object\" || message === null || !(\"type\" in message)) {\n return false;\n }\n const response = message as Record<string, unknown>;\n return response.type === \"health_ok\"\n && response.requestId === requestId\n && response.protocol === INTERCOM_PROTOCOL_NAME\n && response.version === INTERCOM_PROTOCOL_VERSION;\n}\n\nfunction writeWindowsHiddenLauncher(\n commandLine: string,\n launcherPath: string = getWindowsHiddenLauncherPath(),\n): string {\n ensureIntercomRuntimeDir(dirname(launcherPath));\n writeFileSync(launcherPath, getWindowsHiddenLauncherScript(commandLine), {\n encoding: \"utf-8\",\n mode: INTERCOM_RUNTIME_FILE_MODE,\n });\n restrictIntercomRuntimeFile(launcherPath);\n return launcherPath;\n}\n\nexport function getBrokerLaunchSpec(\n brokerPath: string,\n brokerCommand: string,\n brokerArgs: string[],\n extensionDir: string = EXTENSION_DIR,\n platform: NodeJS.Platform = process.platform,\n intercomDir: string = INTERCOM_DIR,\n nodePath: string = process.execPath,\n stderrLogPath?: string,\n): BrokerLaunchSpec {\n if (platform === \"win32\") {\n const launcherPath = getWindowsHiddenLauncherPath(intercomDir);\n return {\n kind: \"windows-launcher\",\n command: \"wscript.exe\",\n args: [\"//B\", \"//NoLogo\", launcherPath],\n launcherPath,\n launcherCommandLine: getWindowsBrokerCommandLine(\n brokerPath,\n extensionDir,\n nodePath,\n brokerCommand,\n brokerArgs,\n stderrLogPath,\n ),\n };\n }\n\n const argv = defaultBrokerArgv(brokerPath, extensionDir, nodePath, brokerCommand, brokerArgs);\n return {\n kind: \"direct\",\n command: argv.command,\n args: argv.args,\n };\n}\n\nexport function getBrokerSpawnOptions(\n extensionDir: string = EXTENSION_DIR,\n env: NodeJS.ProcessEnv = process.env,\n stderrFd?: number,\n): {\n detached: true;\n stdio: \"ignore\" | [\"ignore\", \"ignore\", number];\n cwd: string;\n env: NodeJS.ProcessEnv;\n windowsHide: true;\n} {\n return {\n detached: true,\n stdio: stderrFd === undefined ? \"ignore\" : [\"ignore\", \"ignore\", stderrFd],\n cwd: extensionDir,\n env: { ...env, PI_CODING_AGENT_DIR: getAgentDirPath(env), NODE_NO_WARNINGS: \"1\" },\n windowsHide: true,\n };\n}\n\nfunction toError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n}\n\nexport async function spawnBrokerIfNeeded(brokerCommand: string, brokerArgs: string[]): Promise<void> {\n if (isNativeSupervisorChannelActive()) {\n return;\n }\n\n ensureIntercomRuntimeDir(INTERCOM_DIR);\n\n if (await isBrokerRunning()) {\n return;\n }\n\n const ownsLock = acquireSpawnLock();\n try {\n if (!ownsLock) {\n await waitForBroker();\n return;\n }\n if (await isBrokerRunning()) {\n return;\n }\n\n const brokerPath = resolveBrokerScriptPath();\n const launch = getBrokerLaunchSpec(\n brokerPath,\n brokerCommand,\n brokerArgs,\n EXTENSION_DIR,\n process.platform,\n INTERCOM_DIR,\n process.execPath,\n BROKER_STDERR,\n );\n if (launch.kind === \"windows-launcher\") {\n writeWindowsHiddenLauncher(launch.launcherCommandLine, launch.launcherPath);\n }\n let stderrFd: number | undefined;\n if (launch.kind === \"windows-launcher\") {\n // cmd.exe cannot redirect into a log held open by the parent on Windows.\n writeFileSync(BROKER_STDERR, \"\", { mode: INTERCOM_RUNTIME_FILE_MODE });\n } else {\n stderrFd = openSync(BROKER_STDERR, \"w\", INTERCOM_RUNTIME_FILE_MODE);\n }\n try {\n const child = spawn(launch.command, launch.args, getBrokerSpawnOptions(EXTENSION_DIR, process.env, stderrFd));\n child.unref();\n\n await new Promise<void>((resolve, reject) => {\n const healthCheck = new AbortController();\n const cleanup = () => {\n healthCheck.abort();\n child.off(\"error\", onError);\n child.off(\"exit\", onExit);\n // The hidden launcher monitors startup only; its broker runs independently.\n if (launch.kind === \"windows-launcher\" && child.pid && child.exitCode === null && child.signalCode === null) {\n child.kill();\n }\n };\n\n const onError = (error: Error) => {\n cleanup();\n reject(new Error(`Failed to spawn intercom broker: ${error.message}`, { cause: error }));\n };\n\n const onExit = (code: number | null, signal: NodeJS.Signals | null) => {\n cleanup();\n if (signal) {\n reject(new Error(`Intercom broker exited before startup with signal ${signal}`));\n return;\n }\n reject(new Error(`Intercom broker exited before startup with code ${code ?? \"unknown\"}`));\n };\n\n child.once(\"error\", onError);\n child.once(\"exit\", onExit);\n waitForBroker(5000, healthCheck.signal).then(() => {\n cleanup();\n resolve();\n }, (error) => {\n cleanup();\n reject(toError(error));\n });\n });\n } finally {\n if (stderrFd !== undefined) {\n try {\n closeSync(stderrFd);\n } catch {\n // The child already inherited this fd; failing to close the parent copy is non-fatal.\n }\n }\n }\n } catch (error) {\n const cause = toError(error);\n throw new Error(`${cause.message}\\nStartup log: ${BROKER_STDERR}`, { cause });\n } finally {\n if (ownsLock) releaseSpawnLock();\n }\n}\n\nasync function isBrokerRunning(): Promise<boolean> {\n if (await checkSocketConnectable()) {\n return true;\n }\n\n if (!existsSync(BROKER_PID)) return false;\n\n try {\n const pid = parseInt(readFileSync(BROKER_PID, \"utf-8\").trim(), 10);\n if (!Number.isFinite(pid)) return false;\n process.kill(pid, 0);\n return checkSocketConnectable();\n } catch {\n // Missing or unreadable PID state means there is no live broker to reuse.\n return false;\n }\n}\n\nfunction connectToBrokerTarget(target: BrokerConnectTarget): net.Socket {\n return typeof target === \"string\"\n ? net.connect(target)\n : net.connect({ host: target.host, port: target.port });\n}\n\nfunction checkSocketConnectable(): Promise<boolean> {\n return new Promise((resolve) => {\n let target: BrokerConnectTarget;\n try {\n target = getBrokerConnectTarget();\n } catch {\n resolve(false);\n return;\n }\n\n const socket = connectToBrokerTarget(target);\n const requestId = randomUUID();\n const expectedStateId = typeof target === \"string\" ? undefined : target.stateId;\n let settled = false;\n const finish = (isConnected: boolean) => {\n if (settled) {\n return;\n }\n settled = true;\n clearTimeout(timeout);\n socket.off(\"connect\", onConnect);\n socket.off(\"error\", onError);\n socket.off(\"data\", reader);\n socket.destroy();\n resolve(isConnected);\n };\n const onConnect = () => {\n try {\n writeMessage(socket, {\n type: \"health\",\n requestId,\n ...(expectedStateId ? { stateId: expectedStateId } : {}),\n });\n } catch {\n finish(false);\n }\n };\n const onError = () => finish(false);\n const reader = createMessageReader((message) => {\n finish(isBrokerHealthOkMessage(message, requestId));\n }, () => finish(false));\n socket.on(\"connect\", onConnect);\n socket.on(\"error\", onError);\n socket.on(\"data\", reader);\n const timeout = setTimeout(() => finish(false), 1000);\n });\n}\n\nfunction acquireSpawnLock(): boolean {\n const maxRetries = 5;\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n writeFileSync(BROKER_SPAWN_LOCK, `${process.pid}\\n${Date.now()}\\n`, {\n flag: \"wx\",\n mode: INTERCOM_RUNTIME_FILE_MODE,\n });\n restrictIntercomRuntimeFile(BROKER_SPAWN_LOCK);\n return true;\n } catch (error) {\n if (!(error instanceof Error) || (error as NodeJS.ErrnoException).code !== \"EEXIST\") {\n throw error;\n }\n if (isSpawnLockStale()) {\n try {\n unlinkSync(BROKER_SPAWN_LOCK);\n } catch {\n // If we can't delete the stale lock, retry a few times before giving up\n }\n continue;\n }\n return false;\n }\n }\n return false;\n}\n\nfunction isSpawnLockStale(): boolean {\n if (!existsSync(BROKER_SPAWN_LOCK)) {\n return false;\n }\n\n try {\n const [pidLine = \"\", createdAtLine = \"0\"] = readFileSync(BROKER_SPAWN_LOCK, \"utf-8\").trim().split(\"\\n\");\n const pid = Number.parseInt(pidLine, 10);\n const createdAt = Number.parseInt(createdAtLine, 10);\n const ageMs = Date.now() - createdAt;\n\n if (Number.isFinite(pid)) {\n try {\n process.kill(pid, 0);\n } catch {\n // The process that created the lock is gone.\n return true;\n }\n }\n\n return !Number.isFinite(createdAt) || ageMs > 10_000;\n } catch {\n // Unreadable lock contents are treated as stale so a new broker can start.\n return true;\n }\n}\n\nfunction releaseSpawnLock(): void {\n try {\n unlinkSync(BROKER_SPAWN_LOCK);\n } catch {\n // Another cleanup path may already have removed the lock.\n }\n}\n\nasync function waitForBroker(timeoutMs = 5000, signal?: AbortSignal): Promise<void> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n signal?.throwIfAborted();\n if (await checkSocketConnectable()) {\n return;\n }\n await sleep(100, undefined, { signal });\n }\n throw new Error(\"Broker failed to start within timeout\");\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  // @ts-nocheck
2
2
  import { spawn } from "child_process";
3
+ import { setTimeout as sleep } from "node:timers/promises";
3
4
  import { closeSync, existsSync, openSync, readFileSync, unlinkSync, writeFileSync } from "fs";
4
5
  import { join, dirname } from "path";
5
6
  import { fileURLToPath } from "url";
@@ -14,9 +15,6 @@ const BROKER_PID = join(INTERCOM_DIR, "broker.pid");
14
15
  const BROKER_SPAWN_LOCK = join(INTERCOM_DIR, "broker.spawn.lock");
15
16
  const BROKER_STDERR = join(INTERCOM_DIR, "broker.stderr.log");
16
17
  const NATIVE_SUPERVISOR_CHANNEL_DIR_ENV = "PI_SUBAGENT_SUPERVISOR_CHANNEL_DIR";
17
- function sleep(ms) {
18
- return new Promise(resolve => setTimeout(resolve, ms));
19
- }
20
18
  export function getTsxCliPath(extensionDir = EXTENSION_DIR) {
21
19
  // Resolve tsx via Node's module resolution so it works regardless of whether
22
20
  // tsx is bundled under extensionDir/node_modules or hoisted to a workspace
@@ -79,8 +77,9 @@ export function getWindowsBrokerCommandLine(brokerPath, extensionDir = EXTENSION
79
77
  export function getWindowsHiddenLauncherScript(commandLine) {
80
78
  return [
81
79
  'Set WshShell = CreateObject("WScript.Shell")',
82
- `WshShell.Run "${commandLine.replace(/"/g, '""')}", 0, False`,
80
+ `exitCode = WshShell.Run("${commandLine.replace(/"/g, '""')}", 0, True)`,
83
81
  'Set WshShell = Nothing',
82
+ 'WScript.Quit exitCode',
84
83
  '',
85
84
  ].join("\r\n");
86
85
  }
@@ -109,7 +108,7 @@ export function getBrokerLaunchSpec(brokerPath, brokerCommand, brokerArgs, exten
109
108
  return {
110
109
  kind: "windows-launcher",
111
110
  command: "wscript.exe",
112
- args: [launcherPath],
111
+ args: ["//B", "//NoLogo", launcherPath],
113
112
  launcherPath,
114
113
  launcherCommandLine: getWindowsBrokerCommandLine(brokerPath, extensionDir, nodePath, brokerCommand, brokerArgs, stderrLogPath),
115
114
  };
@@ -142,11 +141,11 @@ export async function spawnBrokerIfNeeded(brokerCommand, brokerArgs) {
142
141
  return;
143
142
  }
144
143
  const ownsLock = acquireSpawnLock();
145
- if (!ownsLock) {
146
- await waitForBroker();
147
- return;
148
- }
149
144
  try {
145
+ if (!ownsLock) {
146
+ await waitForBroker();
147
+ return;
148
+ }
150
149
  if (await isBrokerRunning()) {
151
150
  return;
152
151
  }
@@ -156,28 +155,32 @@ export async function spawnBrokerIfNeeded(brokerCommand, brokerArgs) {
156
155
  writeWindowsHiddenLauncher(launch.launcherCommandLine, launch.launcherPath);
157
156
  }
158
157
  let stderrFd;
159
- try {
160
- stderrFd = openSync(BROKER_STDERR, "a");
158
+ if (launch.kind === "windows-launcher") {
159
+ // cmd.exe cannot redirect into a log held open by the parent on Windows.
160
+ writeFileSync(BROKER_STDERR, "", { mode: INTERCOM_RUNTIME_FILE_MODE });
161
161
  }
162
- catch {
163
- stderrFd = undefined;
162
+ else {
163
+ stderrFd = openSync(BROKER_STDERR, "w", INTERCOM_RUNTIME_FILE_MODE);
164
164
  }
165
165
  try {
166
- const child = spawn(launch.command, launch.args, getBrokerSpawnOptions(EXTENSION_DIR, process.env, launch.kind === "windows-launcher" ? undefined : stderrFd));
166
+ const child = spawn(launch.command, launch.args, getBrokerSpawnOptions(EXTENSION_DIR, process.env, stderrFd));
167
167
  child.unref();
168
168
  await new Promise((resolve, reject) => {
169
+ const healthCheck = new AbortController();
169
170
  const cleanup = () => {
171
+ healthCheck.abort();
170
172
  child.off("error", onError);
171
173
  child.off("exit", onExit);
174
+ // The hidden launcher monitors startup only; its broker runs independently.
175
+ if (launch.kind === "windows-launcher" && child.pid && child.exitCode === null && child.signalCode === null) {
176
+ child.kill();
177
+ }
172
178
  };
173
179
  const onError = (error) => {
174
180
  cleanup();
175
181
  reject(new Error(`Failed to spawn intercom broker: ${error.message}`, { cause: error }));
176
182
  };
177
183
  const onExit = (code, signal) => {
178
- if (launch.kind === "windows-launcher" && code === 0 && signal === null) {
179
- return;
180
- }
181
184
  cleanup();
182
185
  if (signal) {
183
186
  reject(new Error(`Intercom broker exited before startup with signal ${signal}`));
@@ -187,7 +190,7 @@ export async function spawnBrokerIfNeeded(brokerCommand, brokerArgs) {
187
190
  };
188
191
  child.once("error", onError);
189
192
  child.once("exit", onExit);
190
- waitForBroker().then(() => {
193
+ waitForBroker(5000, healthCheck.signal).then(() => {
191
194
  cleanup();
192
195
  resolve();
193
196
  }, (error) => {
@@ -207,8 +210,13 @@ export async function spawnBrokerIfNeeded(brokerCommand, brokerArgs) {
207
210
  }
208
211
  }
209
212
  }
213
+ catch (error) {
214
+ const cause = toError(error);
215
+ throw new Error(`${cause.message}\nStartup log: ${BROKER_STDERR}`, { cause });
216
+ }
210
217
  finally {
211
- releaseSpawnLock();
218
+ if (ownsLock)
219
+ releaseSpawnLock();
212
220
  }
213
221
  }
214
222
  async function isBrokerRunning() {
@@ -344,13 +352,14 @@ function releaseSpawnLock() {
344
352
  // Another cleanup path may already have removed the lock.
345
353
  }
346
354
  }
347
- async function waitForBroker(timeoutMs = 5000) {
355
+ async function waitForBroker(timeoutMs = 5000, signal) {
348
356
  const start = Date.now();
349
357
  while (Date.now() - start < timeoutMs) {
358
+ signal?.throwIfAborted();
350
359
  if (await checkSocketConnectable()) {
351
360
  return;
352
361
  }
353
- await sleep(100);
362
+ await sleep(100, undefined, { signal });
354
363
  }
355
364
  throw new Error("Broker failed to start within timeout");
356
365
  }
@@ -1 +1 @@
1
- {"version":3,"file":"spawn.js","sourceRoot":"","sources":["../../../../src/builtin-extensions/pi-intercom/broker/spawn.ts"],"names":[],"mappings":"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","sourcesContent":["// @ts-nocheck\nimport { spawn } from \"child_process\";\nimport { closeSync, existsSync, openSync, readFileSync, unlinkSync, writeFileSync } from \"fs\";\nimport { join, dirname } from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { createRequire } from \"module\";\nimport net from \"net\";\nimport { randomUUID } from \"crypto\";\nimport { createMessageReader, writeMessage } from \"./framing.ts\";\nimport {\n ensureIntercomRuntimeDir,\n getAgentDirPath,\n getBrokerConnectTarget,\n getIntercomDirPath,\n INTERCOM_PROTOCOL_NAME,\n INTERCOM_PROTOCOL_VERSION,\n INTERCOM_RUNTIME_FILE_MODE,\n restrictIntercomRuntimeFile,\n type BrokerConnectTarget,\n} from \"./paths.ts\";\n\nconst INTERCOM_DIR = getIntercomDirPath();\nconst EXTENSION_DIR = join(dirname(fileURLToPath(import.meta.url)), \"..\");\nconst BROKER_PID = join(INTERCOM_DIR, \"broker.pid\");\nconst BROKER_SPAWN_LOCK = join(INTERCOM_DIR, \"broker.spawn.lock\");\nconst BROKER_STDERR = join(INTERCOM_DIR, \"broker.stderr.log\");\nconst NATIVE_SUPERVISOR_CHANNEL_DIR_ENV = \"PI_SUBAGENT_SUPERVISOR_CHANNEL_DIR\";\n\ntype BrokerLaunchSpec =\n | {\n kind: \"direct\";\n command: string;\n args: string[];\n }\n | {\n kind: \"windows-launcher\";\n command: string;\n args: string[];\n launcherPath: string;\n launcherCommandLine: string;\n };\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\nexport function getTsxCliPath(extensionDir: string = EXTENSION_DIR): string {\n // Resolve tsx via Node's module resolution so it works regardless of whether\n // tsx is bundled under extensionDir/node_modules or hoisted to a workspace\n // root by npm. We resolve the tsx package main entry (its \"exports\" field\n // does not expose ./dist/cli.mjs as a subpath) and then locate cli.mjs next\n // to it. Falls back to the legacy relative path if resolution fails.\n try {\n const requireFromExtension = createRequire(import.meta.url);\n const tsxMain = requireFromExtension.resolve(\"tsx\");\n return join(dirname(tsxMain), \"cli.mjs\");\n } catch {\n return join(extensionDir, \"node_modules\", \"tsx\", \"dist\", \"cli.mjs\");\n }\n}\n\nexport function isNativeSupervisorChannelActive(env: NodeJS.ProcessEnv = process.env): boolean {\n return Boolean(env[NATIVE_SUPERVISOR_CHANNEL_DIR_ENV]?.trim());\n}\n\nexport function resolveBrokerScriptPath(brokerDir: string = dirname(fileURLToPath(import.meta.url))): string {\n const tsPath = join(brokerDir, \"broker.ts\");\n if (existsSync(tsPath)) return tsPath;\n return join(brokerDir, \"broker.js\");\n}\n\nfunction isCompiledBrokerScript(brokerPath: string): boolean {\n return brokerPath.endsWith(\".js\");\n}\n\nfunction quoteWindowsArg(value: string): string {\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\n}\n\nexport function getWindowsHiddenLauncherPath(intercomDir: string = INTERCOM_DIR): string {\n return join(intercomDir, \"broker-launch.vbs\");\n}\n\nfunction usesDefaultBrokerCommand(brokerCommand: string, brokerArgs: string[]): boolean {\n return brokerCommand === \"npx\"\n && brokerArgs.length === 2\n && brokerArgs[0] === \"--no-install\"\n && brokerArgs[1] === \"tsx\";\n}\n\nfunction defaultBrokerArgv(\n brokerPath: string,\n extensionDir: string,\n nodePath: string,\n brokerCommand: string,\n brokerArgs: string[],\n): { command: string; args: string[] } {\n if (usesDefaultBrokerCommand(brokerCommand, brokerArgs) && isCompiledBrokerScript(brokerPath)) {\n return { command: nodePath, args: [brokerPath] };\n }\n if (usesDefaultBrokerCommand(brokerCommand, brokerArgs)) {\n return { command: nodePath, args: [getTsxCliPath(extensionDir), brokerPath] };\n }\n return { command: brokerCommand, args: [...brokerArgs, brokerPath] };\n}\n\nfunction withWindowsStderr(commandLine: string, stderrLogPath?: string): string {\n if (!stderrLogPath) return commandLine;\n const redirected = `${commandLine} 1>NUL 2>>${quoteWindowsArg(stderrLogPath)}`;\n return `cmd.exe /s /c \"${redirected}\"`;\n}\n\nexport function getWindowsBrokerCommandLine(\n brokerPath: string,\n extensionDir: string = EXTENSION_DIR,\n nodePath: string = process.execPath,\n brokerCommand = \"npx\",\n brokerArgs: string[] = [\"--no-install\", \"tsx\"],\n stderrLogPath?: string,\n): string {\n const argv = defaultBrokerArgv(brokerPath, extensionDir, nodePath, brokerCommand, brokerArgs);\n const commandLine = [quoteWindowsArg(argv.command), ...argv.args.map(quoteWindowsArg)].join(\" \");\n return withWindowsStderr(commandLine, stderrLogPath);\n}\n\nexport function getWindowsHiddenLauncherScript(commandLine: string): string {\n return [\n 'Set WshShell = CreateObject(\"WScript.Shell\")',\n `WshShell.Run \"${commandLine.replace(/\"/g, '\"\"')}\", 0, False`,\n 'Set WshShell = Nothing',\n '',\n ].join(\"\\r\\n\");\n}\n\nexport function isBrokerHealthOkMessage(message: unknown, requestId: string): boolean {\n if (typeof message !== \"object\" || message === null || !(\"type\" in message)) {\n return false;\n }\n const response = message as Record<string, unknown>;\n return response.type === \"health_ok\"\n && response.requestId === requestId\n && response.protocol === INTERCOM_PROTOCOL_NAME\n && response.version === INTERCOM_PROTOCOL_VERSION;\n}\n\nfunction writeWindowsHiddenLauncher(\n commandLine: string,\n launcherPath: string = getWindowsHiddenLauncherPath(),\n): string {\n ensureIntercomRuntimeDir(dirname(launcherPath));\n writeFileSync(launcherPath, getWindowsHiddenLauncherScript(commandLine), {\n encoding: \"utf-8\",\n mode: INTERCOM_RUNTIME_FILE_MODE,\n });\n restrictIntercomRuntimeFile(launcherPath);\n return launcherPath;\n}\n\nexport function getBrokerLaunchSpec(\n brokerPath: string,\n brokerCommand: string,\n brokerArgs: string[],\n extensionDir: string = EXTENSION_DIR,\n platform: NodeJS.Platform = process.platform,\n intercomDir: string = INTERCOM_DIR,\n nodePath: string = process.execPath,\n stderrLogPath?: string,\n): BrokerLaunchSpec {\n if (platform === \"win32\") {\n const launcherPath = getWindowsHiddenLauncherPath(intercomDir);\n return {\n kind: \"windows-launcher\",\n command: \"wscript.exe\",\n args: [launcherPath],\n launcherPath,\n launcherCommandLine: getWindowsBrokerCommandLine(\n brokerPath,\n extensionDir,\n nodePath,\n brokerCommand,\n brokerArgs,\n stderrLogPath,\n ),\n };\n }\n\n const argv = defaultBrokerArgv(brokerPath, extensionDir, nodePath, brokerCommand, brokerArgs);\n return {\n kind: \"direct\",\n command: argv.command,\n args: argv.args,\n };\n}\n\nexport function getBrokerSpawnOptions(\n extensionDir: string = EXTENSION_DIR,\n env: NodeJS.ProcessEnv = process.env,\n stderrFd?: number,\n): {\n detached: true;\n stdio: \"ignore\" | [\"ignore\", \"ignore\", number];\n cwd: string;\n env: NodeJS.ProcessEnv;\n windowsHide: true;\n} {\n return {\n detached: true,\n stdio: stderrFd === undefined ? \"ignore\" : [\"ignore\", \"ignore\", stderrFd],\n cwd: extensionDir,\n env: { ...env, PI_CODING_AGENT_DIR: getAgentDirPath(env), NODE_NO_WARNINGS: \"1\" },\n windowsHide: true,\n };\n}\n\nfunction toError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n}\n\nexport async function spawnBrokerIfNeeded(brokerCommand: string, brokerArgs: string[]): Promise<void> {\n if (isNativeSupervisorChannelActive()) {\n return;\n }\n\n ensureIntercomRuntimeDir(INTERCOM_DIR);\n\n if (await isBrokerRunning()) {\n return;\n }\n\n const ownsLock = acquireSpawnLock();\n if (!ownsLock) {\n await waitForBroker();\n return;\n }\n\n try {\n if (await isBrokerRunning()) {\n return;\n }\n\n const brokerPath = resolveBrokerScriptPath();\n const launch = getBrokerLaunchSpec(\n brokerPath,\n brokerCommand,\n brokerArgs,\n EXTENSION_DIR,\n process.platform,\n INTERCOM_DIR,\n process.execPath,\n BROKER_STDERR,\n );\n if (launch.kind === \"windows-launcher\") {\n writeWindowsHiddenLauncher(launch.launcherCommandLine, launch.launcherPath);\n }\n let stderrFd: number | undefined;\n try {\n stderrFd = openSync(BROKER_STDERR, \"a\");\n } catch {\n stderrFd = undefined;\n }\n try {\n const child = spawn(launch.command, launch.args, getBrokerSpawnOptions(EXTENSION_DIR, process.env, launch.kind === \"windows-launcher\" ? undefined : stderrFd));\n child.unref();\n\n await new Promise<void>((resolve, reject) => {\n const cleanup = () => {\n child.off(\"error\", onError);\n child.off(\"exit\", onExit);\n };\n\n const onError = (error: Error) => {\n cleanup();\n reject(new Error(`Failed to spawn intercom broker: ${error.message}`, { cause: error }));\n };\n\n const onExit = (code: number | null, signal: NodeJS.Signals | null) => {\n if (launch.kind === \"windows-launcher\" && code === 0 && signal === null) {\n return;\n }\n cleanup();\n if (signal) {\n reject(new Error(`Intercom broker exited before startup with signal ${signal}`));\n return;\n }\n reject(new Error(`Intercom broker exited before startup with code ${code ?? \"unknown\"}`));\n };\n\n child.once(\"error\", onError);\n child.once(\"exit\", onExit);\n waitForBroker().then(() => {\n cleanup();\n resolve();\n }, (error) => {\n cleanup();\n reject(toError(error));\n });\n });\n } finally {\n if (stderrFd !== undefined) {\n try {\n closeSync(stderrFd);\n } catch {\n // The child already inherited this fd; failing to close the parent copy is non-fatal.\n }\n }\n }\n } finally {\n releaseSpawnLock();\n }\n}\n\nasync function isBrokerRunning(): Promise<boolean> {\n if (await checkSocketConnectable()) {\n return true;\n }\n\n if (!existsSync(BROKER_PID)) return false;\n\n try {\n const pid = parseInt(readFileSync(BROKER_PID, \"utf-8\").trim(), 10);\n if (!Number.isFinite(pid)) return false;\n process.kill(pid, 0);\n return checkSocketConnectable();\n } catch {\n // Missing or unreadable PID state means there is no live broker to reuse.\n return false;\n }\n}\n\nfunction connectToBrokerTarget(target: BrokerConnectTarget): net.Socket {\n return typeof target === \"string\"\n ? net.connect(target)\n : net.connect({ host: target.host, port: target.port });\n}\n\nfunction checkSocketConnectable(): Promise<boolean> {\n return new Promise((resolve) => {\n let target: BrokerConnectTarget;\n try {\n target = getBrokerConnectTarget();\n } catch {\n resolve(false);\n return;\n }\n\n const socket = connectToBrokerTarget(target);\n const requestId = randomUUID();\n const expectedStateId = typeof target === \"string\" ? undefined : target.stateId;\n let settled = false;\n const finish = (isConnected: boolean) => {\n if (settled) {\n return;\n }\n settled = true;\n clearTimeout(timeout);\n socket.off(\"connect\", onConnect);\n socket.off(\"error\", onError);\n socket.off(\"data\", reader);\n socket.destroy();\n resolve(isConnected);\n };\n const onConnect = () => {\n try {\n writeMessage(socket, {\n type: \"health\",\n requestId,\n ...(expectedStateId ? { stateId: expectedStateId } : {}),\n });\n } catch {\n finish(false);\n }\n };\n const onError = () => finish(false);\n const reader = createMessageReader((message) => {\n finish(isBrokerHealthOkMessage(message, requestId));\n }, () => finish(false));\n socket.on(\"connect\", onConnect);\n socket.on(\"error\", onError);\n socket.on(\"data\", reader);\n const timeout = setTimeout(() => finish(false), 1000);\n });\n}\n\nfunction acquireSpawnLock(): boolean {\n const maxRetries = 5;\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n writeFileSync(BROKER_SPAWN_LOCK, `${process.pid}\\n${Date.now()}\\n`, {\n flag: \"wx\",\n mode: INTERCOM_RUNTIME_FILE_MODE,\n });\n restrictIntercomRuntimeFile(BROKER_SPAWN_LOCK);\n return true;\n } catch (error) {\n if (!(error instanceof Error) || (error as NodeJS.ErrnoException).code !== \"EEXIST\") {\n throw error;\n }\n if (isSpawnLockStale()) {\n try {\n unlinkSync(BROKER_SPAWN_LOCK);\n } catch {\n // If we can't delete the stale lock, retry a few times before giving up\n }\n continue;\n }\n return false;\n }\n }\n return false;\n}\n\nfunction isSpawnLockStale(): boolean {\n if (!existsSync(BROKER_SPAWN_LOCK)) {\n return false;\n }\n\n try {\n const [pidLine = \"\", createdAtLine = \"0\"] = readFileSync(BROKER_SPAWN_LOCK, \"utf-8\").trim().split(\"\\n\");\n const pid = Number.parseInt(pidLine, 10);\n const createdAt = Number.parseInt(createdAtLine, 10);\n const ageMs = Date.now() - createdAt;\n\n if (Number.isFinite(pid)) {\n try {\n process.kill(pid, 0);\n } catch {\n // The process that created the lock is gone.\n return true;\n }\n }\n\n return !Number.isFinite(createdAt) || ageMs > 10_000;\n } catch {\n // Unreadable lock contents are treated as stale so a new broker can start.\n return true;\n }\n}\n\nfunction releaseSpawnLock(): void {\n try {\n unlinkSync(BROKER_SPAWN_LOCK);\n } catch {\n // Another cleanup path may already have removed the lock.\n }\n}\n\nasync function waitForBroker(timeoutMs = 5000): Promise<void> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n if (await checkSocketConnectable()) {\n return;\n }\n await sleep(100);\n }\n throw new Error(\"Broker failed to start within timeout\");\n}\n"]}
1
+ {"version":3,"file":"spawn.js","sourceRoot":"","sources":["../../../../src/builtin-extensions/pi-intercom/broker/spawn.ts"],"names":[],"mappings":"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","sourcesContent":["// @ts-nocheck\nimport { spawn } from \"child_process\";\nimport { setTimeout as sleep } from \"node:timers/promises\";\nimport { closeSync, existsSync, openSync, readFileSync, unlinkSync, writeFileSync } from \"fs\";\nimport { join, dirname } from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { createRequire } from \"module\";\nimport net from \"net\";\nimport { randomUUID } from \"crypto\";\nimport { createMessageReader, writeMessage } from \"./framing.ts\";\nimport {\n ensureIntercomRuntimeDir,\n getAgentDirPath,\n getBrokerConnectTarget,\n getIntercomDirPath,\n INTERCOM_PROTOCOL_NAME,\n INTERCOM_PROTOCOL_VERSION,\n INTERCOM_RUNTIME_FILE_MODE,\n restrictIntercomRuntimeFile,\n type BrokerConnectTarget,\n} from \"./paths.ts\";\n\nconst INTERCOM_DIR = getIntercomDirPath();\nconst EXTENSION_DIR = join(dirname(fileURLToPath(import.meta.url)), \"..\");\nconst BROKER_PID = join(INTERCOM_DIR, \"broker.pid\");\nconst BROKER_SPAWN_LOCK = join(INTERCOM_DIR, \"broker.spawn.lock\");\nconst BROKER_STDERR = join(INTERCOM_DIR, \"broker.stderr.log\");\nconst NATIVE_SUPERVISOR_CHANNEL_DIR_ENV = \"PI_SUBAGENT_SUPERVISOR_CHANNEL_DIR\";\n\ntype BrokerLaunchSpec =\n | {\n kind: \"direct\";\n command: string;\n args: string[];\n }\n | {\n kind: \"windows-launcher\";\n command: string;\n args: string[];\n launcherPath: string;\n launcherCommandLine: string;\n };\n\nexport function getTsxCliPath(extensionDir: string = EXTENSION_DIR): string {\n // Resolve tsx via Node's module resolution so it works regardless of whether\n // tsx is bundled under extensionDir/node_modules or hoisted to a workspace\n // root by npm. We resolve the tsx package main entry (its \"exports\" field\n // does not expose ./dist/cli.mjs as a subpath) and then locate cli.mjs next\n // to it. Falls back to the legacy relative path if resolution fails.\n try {\n const requireFromExtension = createRequire(import.meta.url);\n const tsxMain = requireFromExtension.resolve(\"tsx\");\n return join(dirname(tsxMain), \"cli.mjs\");\n } catch {\n return join(extensionDir, \"node_modules\", \"tsx\", \"dist\", \"cli.mjs\");\n }\n}\n\nexport function isNativeSupervisorChannelActive(env: NodeJS.ProcessEnv = process.env): boolean {\n return Boolean(env[NATIVE_SUPERVISOR_CHANNEL_DIR_ENV]?.trim());\n}\n\nexport function resolveBrokerScriptPath(brokerDir: string = dirname(fileURLToPath(import.meta.url))): string {\n const tsPath = join(brokerDir, \"broker.ts\");\n if (existsSync(tsPath)) return tsPath;\n return join(brokerDir, \"broker.js\");\n}\n\nfunction isCompiledBrokerScript(brokerPath: string): boolean {\n return brokerPath.endsWith(\".js\");\n}\n\nfunction quoteWindowsArg(value: string): string {\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\n}\n\nexport function getWindowsHiddenLauncherPath(intercomDir: string = INTERCOM_DIR): string {\n return join(intercomDir, \"broker-launch.vbs\");\n}\n\nfunction usesDefaultBrokerCommand(brokerCommand: string, brokerArgs: string[]): boolean {\n return brokerCommand === \"npx\"\n && brokerArgs.length === 2\n && brokerArgs[0] === \"--no-install\"\n && brokerArgs[1] === \"tsx\";\n}\n\nfunction defaultBrokerArgv(\n brokerPath: string,\n extensionDir: string,\n nodePath: string,\n brokerCommand: string,\n brokerArgs: string[],\n): { command: string; args: string[] } {\n if (usesDefaultBrokerCommand(brokerCommand, brokerArgs) && isCompiledBrokerScript(brokerPath)) {\n return { command: nodePath, args: [brokerPath] };\n }\n if (usesDefaultBrokerCommand(brokerCommand, brokerArgs)) {\n return { command: nodePath, args: [getTsxCliPath(extensionDir), brokerPath] };\n }\n return { command: brokerCommand, args: [...brokerArgs, brokerPath] };\n}\n\nfunction withWindowsStderr(commandLine: string, stderrLogPath?: string): string {\n if (!stderrLogPath) return commandLine;\n const redirected = `${commandLine} 1>NUL 2>>${quoteWindowsArg(stderrLogPath)}`;\n return `cmd.exe /s /c \"${redirected}\"`;\n}\n\nexport function getWindowsBrokerCommandLine(\n brokerPath: string,\n extensionDir: string = EXTENSION_DIR,\n nodePath: string = process.execPath,\n brokerCommand = \"npx\",\n brokerArgs: string[] = [\"--no-install\", \"tsx\"],\n stderrLogPath?: string,\n): string {\n const argv = defaultBrokerArgv(brokerPath, extensionDir, nodePath, brokerCommand, brokerArgs);\n const commandLine = [quoteWindowsArg(argv.command), ...argv.args.map(quoteWindowsArg)].join(\" \");\n return withWindowsStderr(commandLine, stderrLogPath);\n}\n\nexport function getWindowsHiddenLauncherScript(commandLine: string): string {\n return [\n 'Set WshShell = CreateObject(\"WScript.Shell\")',\n `exitCode = WshShell.Run(\"${commandLine.replace(/\"/g, '\"\"')}\", 0, True)`,\n 'Set WshShell = Nothing',\n 'WScript.Quit exitCode',\n '',\n ].join(\"\\r\\n\");\n}\n\nexport function isBrokerHealthOkMessage(message: unknown, requestId: string): boolean {\n if (typeof message !== \"object\" || message === null || !(\"type\" in message)) {\n return false;\n }\n const response = message as Record<string, unknown>;\n return response.type === \"health_ok\"\n && response.requestId === requestId\n && response.protocol === INTERCOM_PROTOCOL_NAME\n && response.version === INTERCOM_PROTOCOL_VERSION;\n}\n\nfunction writeWindowsHiddenLauncher(\n commandLine: string,\n launcherPath: string = getWindowsHiddenLauncherPath(),\n): string {\n ensureIntercomRuntimeDir(dirname(launcherPath));\n writeFileSync(launcherPath, getWindowsHiddenLauncherScript(commandLine), {\n encoding: \"utf-8\",\n mode: INTERCOM_RUNTIME_FILE_MODE,\n });\n restrictIntercomRuntimeFile(launcherPath);\n return launcherPath;\n}\n\nexport function getBrokerLaunchSpec(\n brokerPath: string,\n brokerCommand: string,\n brokerArgs: string[],\n extensionDir: string = EXTENSION_DIR,\n platform: NodeJS.Platform = process.platform,\n intercomDir: string = INTERCOM_DIR,\n nodePath: string = process.execPath,\n stderrLogPath?: string,\n): BrokerLaunchSpec {\n if (platform === \"win32\") {\n const launcherPath = getWindowsHiddenLauncherPath(intercomDir);\n return {\n kind: \"windows-launcher\",\n command: \"wscript.exe\",\n args: [\"//B\", \"//NoLogo\", launcherPath],\n launcherPath,\n launcherCommandLine: getWindowsBrokerCommandLine(\n brokerPath,\n extensionDir,\n nodePath,\n brokerCommand,\n brokerArgs,\n stderrLogPath,\n ),\n };\n }\n\n const argv = defaultBrokerArgv(brokerPath, extensionDir, nodePath, brokerCommand, brokerArgs);\n return {\n kind: \"direct\",\n command: argv.command,\n args: argv.args,\n };\n}\n\nexport function getBrokerSpawnOptions(\n extensionDir: string = EXTENSION_DIR,\n env: NodeJS.ProcessEnv = process.env,\n stderrFd?: number,\n): {\n detached: true;\n stdio: \"ignore\" | [\"ignore\", \"ignore\", number];\n cwd: string;\n env: NodeJS.ProcessEnv;\n windowsHide: true;\n} {\n return {\n detached: true,\n stdio: stderrFd === undefined ? \"ignore\" : [\"ignore\", \"ignore\", stderrFd],\n cwd: extensionDir,\n env: { ...env, PI_CODING_AGENT_DIR: getAgentDirPath(env), NODE_NO_WARNINGS: \"1\" },\n windowsHide: true,\n };\n}\n\nfunction toError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n}\n\nexport async function spawnBrokerIfNeeded(brokerCommand: string, brokerArgs: string[]): Promise<void> {\n if (isNativeSupervisorChannelActive()) {\n return;\n }\n\n ensureIntercomRuntimeDir(INTERCOM_DIR);\n\n if (await isBrokerRunning()) {\n return;\n }\n\n const ownsLock = acquireSpawnLock();\n try {\n if (!ownsLock) {\n await waitForBroker();\n return;\n }\n if (await isBrokerRunning()) {\n return;\n }\n\n const brokerPath = resolveBrokerScriptPath();\n const launch = getBrokerLaunchSpec(\n brokerPath,\n brokerCommand,\n brokerArgs,\n EXTENSION_DIR,\n process.platform,\n INTERCOM_DIR,\n process.execPath,\n BROKER_STDERR,\n );\n if (launch.kind === \"windows-launcher\") {\n writeWindowsHiddenLauncher(launch.launcherCommandLine, launch.launcherPath);\n }\n let stderrFd: number | undefined;\n if (launch.kind === \"windows-launcher\") {\n // cmd.exe cannot redirect into a log held open by the parent on Windows.\n writeFileSync(BROKER_STDERR, \"\", { mode: INTERCOM_RUNTIME_FILE_MODE });\n } else {\n stderrFd = openSync(BROKER_STDERR, \"w\", INTERCOM_RUNTIME_FILE_MODE);\n }\n try {\n const child = spawn(launch.command, launch.args, getBrokerSpawnOptions(EXTENSION_DIR, process.env, stderrFd));\n child.unref();\n\n await new Promise<void>((resolve, reject) => {\n const healthCheck = new AbortController();\n const cleanup = () => {\n healthCheck.abort();\n child.off(\"error\", onError);\n child.off(\"exit\", onExit);\n // The hidden launcher monitors startup only; its broker runs independently.\n if (launch.kind === \"windows-launcher\" && child.pid && child.exitCode === null && child.signalCode === null) {\n child.kill();\n }\n };\n\n const onError = (error: Error) => {\n cleanup();\n reject(new Error(`Failed to spawn intercom broker: ${error.message}`, { cause: error }));\n };\n\n const onExit = (code: number | null, signal: NodeJS.Signals | null) => {\n cleanup();\n if (signal) {\n reject(new Error(`Intercom broker exited before startup with signal ${signal}`));\n return;\n }\n reject(new Error(`Intercom broker exited before startup with code ${code ?? \"unknown\"}`));\n };\n\n child.once(\"error\", onError);\n child.once(\"exit\", onExit);\n waitForBroker(5000, healthCheck.signal).then(() => {\n cleanup();\n resolve();\n }, (error) => {\n cleanup();\n reject(toError(error));\n });\n });\n } finally {\n if (stderrFd !== undefined) {\n try {\n closeSync(stderrFd);\n } catch {\n // The child already inherited this fd; failing to close the parent copy is non-fatal.\n }\n }\n }\n } catch (error) {\n const cause = toError(error);\n throw new Error(`${cause.message}\\nStartup log: ${BROKER_STDERR}`, { cause });\n } finally {\n if (ownsLock) releaseSpawnLock();\n }\n}\n\nasync function isBrokerRunning(): Promise<boolean> {\n if (await checkSocketConnectable()) {\n return true;\n }\n\n if (!existsSync(BROKER_PID)) return false;\n\n try {\n const pid = parseInt(readFileSync(BROKER_PID, \"utf-8\").trim(), 10);\n if (!Number.isFinite(pid)) return false;\n process.kill(pid, 0);\n return checkSocketConnectable();\n } catch {\n // Missing or unreadable PID state means there is no live broker to reuse.\n return false;\n }\n}\n\nfunction connectToBrokerTarget(target: BrokerConnectTarget): net.Socket {\n return typeof target === \"string\"\n ? net.connect(target)\n : net.connect({ host: target.host, port: target.port });\n}\n\nfunction checkSocketConnectable(): Promise<boolean> {\n return new Promise((resolve) => {\n let target: BrokerConnectTarget;\n try {\n target = getBrokerConnectTarget();\n } catch {\n resolve(false);\n return;\n }\n\n const socket = connectToBrokerTarget(target);\n const requestId = randomUUID();\n const expectedStateId = typeof target === \"string\" ? undefined : target.stateId;\n let settled = false;\n const finish = (isConnected: boolean) => {\n if (settled) {\n return;\n }\n settled = true;\n clearTimeout(timeout);\n socket.off(\"connect\", onConnect);\n socket.off(\"error\", onError);\n socket.off(\"data\", reader);\n socket.destroy();\n resolve(isConnected);\n };\n const onConnect = () => {\n try {\n writeMessage(socket, {\n type: \"health\",\n requestId,\n ...(expectedStateId ? { stateId: expectedStateId } : {}),\n });\n } catch {\n finish(false);\n }\n };\n const onError = () => finish(false);\n const reader = createMessageReader((message) => {\n finish(isBrokerHealthOkMessage(message, requestId));\n }, () => finish(false));\n socket.on(\"connect\", onConnect);\n socket.on(\"error\", onError);\n socket.on(\"data\", reader);\n const timeout = setTimeout(() => finish(false), 1000);\n });\n}\n\nfunction acquireSpawnLock(): boolean {\n const maxRetries = 5;\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n writeFileSync(BROKER_SPAWN_LOCK, `${process.pid}\\n${Date.now()}\\n`, {\n flag: \"wx\",\n mode: INTERCOM_RUNTIME_FILE_MODE,\n });\n restrictIntercomRuntimeFile(BROKER_SPAWN_LOCK);\n return true;\n } catch (error) {\n if (!(error instanceof Error) || (error as NodeJS.ErrnoException).code !== \"EEXIST\") {\n throw error;\n }\n if (isSpawnLockStale()) {\n try {\n unlinkSync(BROKER_SPAWN_LOCK);\n } catch {\n // If we can't delete the stale lock, retry a few times before giving up\n }\n continue;\n }\n return false;\n }\n }\n return false;\n}\n\nfunction isSpawnLockStale(): boolean {\n if (!existsSync(BROKER_SPAWN_LOCK)) {\n return false;\n }\n\n try {\n const [pidLine = \"\", createdAtLine = \"0\"] = readFileSync(BROKER_SPAWN_LOCK, \"utf-8\").trim().split(\"\\n\");\n const pid = Number.parseInt(pidLine, 10);\n const createdAt = Number.parseInt(createdAtLine, 10);\n const ageMs = Date.now() - createdAt;\n\n if (Number.isFinite(pid)) {\n try {\n process.kill(pid, 0);\n } catch {\n // The process that created the lock is gone.\n return true;\n }\n }\n\n return !Number.isFinite(createdAt) || ageMs > 10_000;\n } catch {\n // Unreadable lock contents are treated as stale so a new broker can start.\n return true;\n }\n}\n\nfunction releaseSpawnLock(): void {\n try {\n unlinkSync(BROKER_SPAWN_LOCK);\n } catch {\n // Another cleanup path may already have removed the lock.\n }\n}\n\nasync function waitForBroker(timeoutMs = 5000, signal?: AbortSignal): Promise<void> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n signal?.throwIfAborted();\n if (await checkSocketConnectable()) {\n return;\n }\n await sleep(100, undefined, { signal });\n }\n throw new Error(\"Broker failed to start within timeout\");\n}\n"]}