@ashx-j/lunr 0.2.19 → 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.
- package/CHANGELOG.md +36 -1
- package/README.md +2 -2
- package/dist/builtin-extensions/index.d.ts.map +1 -1
- package/dist/builtin-extensions/index.js +0 -2
- package/dist/builtin-extensions/index.js.map +1 -1
- package/dist/builtin-extensions/lunr-todos.d.ts +6 -5
- package/dist/builtin-extensions/lunr-todos.d.ts.map +1 -1
- package/dist/builtin-extensions/lunr-todos.js +9 -7
- package/dist/builtin-extensions/lunr-todos.js.map +1 -1
- package/dist/builtin-extensions/pi-intercom/broker/spawn.d.ts +6 -4
- package/dist/builtin-extensions/pi-intercom/broker/spawn.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-intercom/broker/spawn.js +114 -61
- package/dist/builtin-extensions/pi-intercom/broker/spawn.js.map +1 -1
- package/dist/builtin-extensions/pi-intercom/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-intercom/index.js +360 -351
- package/dist/builtin-extensions/pi-intercom/index.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/fanout-child.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/fanout-child.js +2 -2
- package/dist/builtin-extensions/pi-subagents/src/extension/fanout-child.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts +8 -6
- package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/index.js +128 -57
- package/dist/builtin-extensions/pi-subagents/src/extension/index.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts +18 -8
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js +38 -13
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts +4 -3
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js +24 -14
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.d.ts +8 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.js +247 -18
- package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/result-intercom.d.ts +1 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/result-intercom.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/result-intercom.js +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/result-intercom.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-ask.d.ts +29 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-ask.d.ts.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-ask.js +179 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-ask.js.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-questions.d.ts +89 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-questions.d.ts.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-questions.js +541 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/supervisor-questions.js.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.d.ts +11 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js +38 -12
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-job-tracker.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-job-tracker.js +3 -2
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-job-tracker.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.d.ts +2 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.js +35 -8
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.d.ts +7 -3
- package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.js +26 -4
- package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/result-watcher.d.ts +1 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/background/result-watcher.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/result-watcher.js +55 -38
- package/dist/builtin-extensions/pi-subagents/src/runs/background/result-watcher.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.d.ts +100 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js +46 -31
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-wait.d.ts +3 -6
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-wait.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-wait.js +64 -5
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-wait.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/top-level-async.d.ts +15 -2
- package/dist/builtin-extensions/pi-subagents/src/runs/background/top-level-async.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/top-level-async.js +14 -4
- package/dist/builtin-extensions/pi-subagents/src/runs/background/top-level-async.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/wait-tool.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/wait-tool.js +2 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/wait-tool.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js +21 -8
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts +0 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js +71 -25
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.d.ts +2 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.js +75 -18
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/completion-guard.d.ts +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/completion-guard.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/completion-guard.js +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/completion-guard.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.d.ts +13 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.js +67 -11
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts +1 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.d.ts +3 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.js +7 -2
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.js +13 -3
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.d.ts +2 -2
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.js +5 -10
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/async-guidance.d.ts +2 -0
- package/dist/builtin-extensions/pi-subagents/src/shared/async-guidance.d.ts.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/shared/async-guidance.js +3 -0
- package/dist/builtin-extensions/pi-subagents/src/shared/async-guidance.js.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.d.ts +3 -0
- package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.js +68 -11
- package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/formatters.d.ts +12 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/formatters.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/formatters.js +21 -2
- package/dist/builtin-extensions/pi-subagents/src/shared/formatters.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts +21 -4
- package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/types.js +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/types.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/delegation-adapters.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/delegation-adapters.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.d.ts +7 -0
- package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js +11 -7
- package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts +6 -0
- package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js +46 -29
- package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts +46 -4
- package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/render.js +407 -450
- package/dist/builtin-extensions/pi-subagents/src/tui/render.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/watchdog/register-main.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/watchdog/register-main.js +1 -1
- package/dist/builtin-extensions/pi-subagents/src/watchdog/register-main.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/watchdog/tool-actions.d.ts +2 -13
- package/dist/builtin-extensions/pi-subagents/src/watchdog/tool-actions.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/watchdog/tool-actions.js +5 -91
- package/dist/builtin-extensions/pi-subagents/src/watchdog/tool-actions.js.map +1 -1
- package/dist/core/customize.d.ts +2 -0
- package/dist/core/customize.d.ts.map +1 -1
- package/dist/core/customize.js +3 -0
- package/dist/core/customize.js.map +1 -1
- package/dist/core/permissions.d.ts +1 -0
- package/dist/core/permissions.d.ts.map +1 -1
- package/dist/core/permissions.js +8 -4
- package/dist/core/permissions.js.map +1 -1
- package/dist/core/plan-mode.d.ts.map +1 -1
- package/dist/core/plan-mode.js +0 -5
- package/dist/core/plan-mode.js.map +1 -1
- package/dist/core/runtime-bridges.d.ts.map +1 -1
- package/dist/core/runtime-bridges.js +0 -2
- package/dist/core/runtime-bridges.js.map +1 -1
- package/dist/core/settings-manager.d.ts +4 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +11 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/subagent-cancellation.d.ts +21 -0
- package/dist/core/subagent-cancellation.d.ts.map +1 -0
- package/dist/core/subagent-cancellation.js +53 -0
- package/dist/core/subagent-cancellation.js.map +1 -0
- package/dist/core/subagent-spinner-setting.d.ts +6 -0
- package/dist/core/subagent-spinner-setting.d.ts.map +1 -0
- package/dist/core/subagent-spinner-setting.js +12 -0
- package/dist/core/subagent-spinner-setting.js.map +1 -0
- package/dist/core/subagent-spinner.d.ts +4 -0
- package/dist/core/subagent-spinner.d.ts.map +1 -0
- package/dist/core/subagent-spinner.js +6 -0
- package/dist/core/subagent-spinner.js.map +1 -0
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +12 -18
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +4 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +73 -1
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +12 -0
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/extension-widget-order.d.ts +8 -0
- package/dist/modes/interactive/extension-widget-order.d.ts.map +1 -0
- package/dist/modes/interactive/extension-widget-order.js +37 -0
- package/dist/modes/interactive/extension-widget-order.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +44 -22
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/node-runtime/{agent-session-services-35WNVGMR.js → agent-session-services-C743TYQE.js} +6 -5
- package/dist/node-runtime/{builtin-extensions-322XDDEM.js → builtin-extensions-JCOPAAB5.js} +3 -2
- package/dist/node-runtime/{chunk-XATUM2HK.js → chunk-25I2BHAA.js} +1 -1
- package/dist/node-runtime/{chunk-TXHKBRHZ.js → chunk-2BUAYZ7K.js} +136 -53
- package/dist/node-runtime/chunk-5ZH3ZFIZ.js +308 -0
- package/dist/node-runtime/{chunk-J26TSGDL.js → chunk-6N22HULO.js} +8 -5
- package/dist/node-runtime/chunk-A3KMVBZV.js +60 -0
- package/dist/node-runtime/{chunk-4FX25LWP.js → chunk-AWVD5IEY.js} +20 -421
- package/dist/node-runtime/{chunk-ZRCBN365.js → chunk-GB2MGFLI.js} +12 -12
- package/dist/node-runtime/chunk-LISUTZS3.js +19 -0
- package/dist/node-runtime/{chunk-PNJSG3IZ.js → chunk-LTYWTR3V.js} +1 -369
- package/dist/node-runtime/{chunk-TFBIJTM3.js → chunk-PDNXVXMD.js} +13 -13
- package/dist/node-runtime/{chunk-MCUNIRD5.js → chunk-SY2TFMLO.js} +532 -261
- package/dist/node-runtime/{chunk-HQCKFM2O.js → chunk-VGYZ3APQ.js} +3 -0
- package/dist/node-runtime/{chunk-4DJ6HEGH.js → chunk-VZR3NCM7.js} +3 -3
- package/dist/node-runtime/chunk-W3SILANP.js +24 -0
- package/dist/node-runtime/{chunk-CBGB57XT.js → chunk-WLLDXJRT.js} +10 -0
- package/dist/node-runtime/{chunk-ATGA2WLC.js → chunk-XI77KVKA.js} +399 -36
- package/dist/node-runtime/{chunk-4CSFRBCN.js → chunk-Y5KOEMBH.js} +1 -1
- package/dist/node-runtime/{chunk-AROOY4BB.js → chunk-YMEAYRBE.js} +1 -1
- package/dist/node-runtime/{command-DWUM5X3U.js → command-P4NYCJ2M.js} +1 -1
- package/dist/node-runtime/{customize-XEXL2DGQ.js → customize-LF5VST47.js} +1 -1
- package/dist/node-runtime/{gateway-UZXMFGWY.js → gateway-BM6NLOH4.js} +2 -2
- package/dist/node-runtime/index.js +16 -13
- package/dist/node-runtime/{interactive-mode-QT5IKGQ5.js → interactive-mode-EVVBA64D.js} +11 -8
- package/dist/node-runtime/{lunr-cron-QPBTKBGI.js → lunr-cron-4VHP4E4B.js} +2 -2
- package/dist/node-runtime/main.js +14 -12
- package/dist/node-runtime/{package-manager-cli-7RKSZGEN.js → package-manager-cli-N37546V5.js} +5 -4
- package/dist/node-runtime/{pi-intercom-EEMBGQID.js → pi-intercom-NV2LW6X7.js} +108 -87
- package/dist/node-runtime/{pi-subagents-7YJ3I4VE.js → pi-subagents-6MUIFNDM.js} +1262 -599
- package/dist/node-runtime/rpc-entry.js +14 -12
- package/dist/node-runtime/{runtime-bridges-3RKBDLNA.js → runtime-bridges-2VQTLPOZ.js} +3 -5
- package/dist/node-runtime/{settings-manager-GHPTUN7R.js → settings-manager-CUGAZWAB.js} +2 -1
- package/dist/node-runtime/{subagent-executor-5Q5A4HJ2.js → subagent-executor-MH4A7CCF.js} +181 -294
- package/dist/node-runtime/{update-cli-L3JIBH2U.js → update-cli-XTZMZGOL.js} +2 -1
- package/docs/features.md +22 -4
- package/docs/keybindings.md +3 -1
- package/docs/settings.md +3 -2
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
- package/dist/builtin-extensions/lunr-settings-tools.d.ts +0 -4
- package/dist/builtin-extensions/lunr-settings-tools.d.ts.map +0 -1
- package/dist/builtin-extensions/lunr-settings-tools.js +0 -120
- package/dist/builtin-extensions/lunr-settings-tools.js.map +0 -1
- package/dist/core/settings-tools-bridge.d.ts +0 -35
- package/dist/core/settings-tools-bridge.d.ts.map +0 -1
- package/dist/core/settings-tools-bridge.js +0 -56
- package/dist/core/settings-tools-bridge.js.map +0 -1
- package/dist/node-runtime/chunk-3NMWLTPV.js +0 -64
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,44 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
lunR is derived from pi. The published npm package is **`@ashx-j/lunr@0.2.
|
|
3
|
+
lunR is derived from pi. The published npm package is **`@ashx-j/lunr@0.2.21`**. Versioned sections below (`0.80.x` and earlier) are **upstream pi history** and do not describe lunR releases.
|
|
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
|
+
|
|
19
|
+
## [0.2.20] - 2026-09-12
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- Parent agents can ask a running async child a decision-specific question without stopping its assignment. Answers can wake the parent or return through `subagent_wait`.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- **Child model selection.** Executable children choose exactly one of `tier: light|standard|heavy` (default) or an explicit `model: provider/id` when the user names a model. Optional `thinking` is only valid with an explicit model. Direct model launches do not require tier mode. Resume keeps user selection separate from the resolved runtime model.
|
|
28
|
+
- **Settings stay user-managed.** Removed `settings_load` and the four agent-managed settings tools. `/settings` still edits configuration. Direct `edit`/`write`/`code_rewrite` of lunR `settings.json` is blocked.
|
|
29
|
+
- **Watchdog configure removed.** Read-only watchdog status/check/recommend-model remain; agent `watchdog.configure` is gone.
|
|
30
|
+
- **Subagent compact rows.** Foreground and async children share one collapsed line: spinner, description, selected tier or model, tokens, and elapsed time. The hang line, tool-use count, thinking, and permission suffix are gone. Completed collapsed cards keep frozen stats. Async tool headers say `subagent async`. Completed notify cards show title and status only.
|
|
31
|
+
|
|
32
|
+
- Tier guidance recommends the lowest tier that can reliably finish the task, with examples for each tier.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- Smooth streaming reveals thinking text incrementally instead of showing the full incoming chunk.
|
|
37
|
+
- Resuming async children restores their saved acceptance and model selection. Compiled installs use the JavaScript intercom broker, while native child channels skip it.
|
|
38
|
+
- Escape stops the parent. A second press within 500ms also stops its async children without clearing drafts or restarting the parent on stopped results.
|
|
39
|
+
- Async result delivery retries preserve undelivered files and avoid duplicate messages or errors printed over the TUI.
|
|
40
|
+
- **Async widget animation.** The 80ms timer now invalidates a stable widget component. It no longer rebuilds the widget from the 250ms status poller, so the spinner actually advances.
|
|
41
|
+
|
|
7
42
|
## [0.2.19] - 2026-09-12
|
|
8
43
|
|
|
9
44
|
### Changed
|
package/README.md
CHANGED
|
@@ -205,7 +205,7 @@ See `/hotkeys` for the full list. Customize via `~/.lunr/agent/keybindings.json`
|
|
|
205
205
|
| Ctrl+C | Clear editor |
|
|
206
206
|
| Ctrl+C twice | Quit |
|
|
207
207
|
| Escape | Cancel/abort |
|
|
208
|
-
| Escape twice |
|
|
208
|
+
| Escape twice within 500ms | Stop this session's active async subagents; otherwise open `/tree` when idle |
|
|
209
209
|
| Ctrl+L | Open model selector |
|
|
210
210
|
| Ctrl+P / Shift+Ctrl+P | Cycle scoped models forward/backward |
|
|
211
211
|
| Shift+Tab | Cycle permission mode (`manual` → `yolo` → `plan` → `auto`) |
|
|
@@ -468,7 +468,7 @@ See [docs/rpc.md](docs/rpc.md) for the protocol.
|
|
|
468
468
|
lunR keeps a small core and still lets you shape the product with [extensions](#extensions), [skills](#skills), and [packages](#packages). Unlike upstream pi, lunR **does** ship the workflows most coding agents expect:
|
|
469
469
|
|
|
470
470
|
- **MCP** — `/mcp`, `/mcp-auth`
|
|
471
|
-
- **Subagents** — always fresh;
|
|
471
|
+
- **Subagents** — always fresh; children use a configured tier by default, or an explicit model when the user names one; 3+ children receive one large-launch confirmation in manual and yolo; default parallel concurrency is unlimited
|
|
472
472
|
- **Permission modes** — `manual | yolo | plan | auto`; Shift+Tab cycles that order
|
|
473
473
|
- **Plan mode** — `/plan` plus the `present_plan` tool
|
|
474
474
|
- **Todos** — lunr-todos (full-replace)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/builtin-extensions/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAoB,eAAe,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/builtin-extensions/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAoB,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAuBrF,yFAAyF;AACzF,eAAO,MAAM,sBAAsB,EAAE,eAAe,EAUnD,CAAC;AAiBF,eAAO,MAAM,gCAAgC,UAAsD,CAAC;AAEpG,6FAA6F;AAC7F,eAAO,MAAM,sBAAsB,yBAA0B,CAAC;AAE9D,6EAA6E;AAC7E,wBAAsB,6BAA6B,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAQhF;AAED,yFAAyF;AACzF,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAE3E;AAED,qFAAqF;AACrF,eAAO,MAAM,iBAAiB,EAAE,eAAe,EAA2B,CAAC","sourcesContent":["/**\n * Built-in extensions for lunR.\n *\n * Light factories stay on the first-paint graph. Heavy factories are imported\n * only when attachDeferredBuiltinExtensions() runs after the TUI is up, or\n * immediately for print/RPC/gateway where there is no first-paint window.\n */\n\nimport type { ExtensionFactory, InlineExtension } from \"../core/extensions/types.ts\";\nimport { measureStartup } from \"../core/timings.ts\";\n\nimport simplePiMemory from \"./simple-pi-memory.ts\";\nimport piTps from \"./pi-tps.ts\";\nimport ashxjTui from \"./ashxj-tui.ts\";\nimport ashxjSpinners from \"./ashxj-spinners.ts\";\nimport ashxjThinking from \"./ashxj-thinking.ts\";\nimport lunrLocalProviders from \"./lunr-local-providers/index.ts\";\nimport lunrTodos from \"./lunr-todos.ts\";\nimport lunrPlanTools from \"./lunr-plan-tools.ts\";\nimport lunrSkillCreator from \"./lunr-skill-creator/index.ts\";\n\n/**\n * Wrap a raw factory function as a named InlineExtension.\n * The cast bridges the gap between the source-level ExtensionFactory type\n * (used here) and the dist-level type that extensions import via the package\n * name. The underlying function signatures are structurally identical.\n */\nfunction ext(name: string, factory: unknown): InlineExtension {\n\treturn { name, factory: factory as ExtensionFactory };\n}\n\n/** UI, local providers, and small lunR-native factories. Safe to import at CLI start. */\nexport const lightBuiltinExtensions: InlineExtension[] = [\n\text(\"simple-pi-memory\", simplePiMemory),\n\text(\"pi-tps\", piTps),\n\text(\"ashxj-tui\", ashxjTui),\n\text(\"ashxj-spinners\", ashxjSpinners),\n\text(\"ashxj-thinking\", ashxjThinking),\n\text(\"lunr-local-providers\", lunrLocalProviders),\n\text(\"lunr-todos\", lunrTodos),\n\text(\"lunr-plan-tools\", lunrPlanTools),\n\text(\"lunr-skill-creator\", lunrSkillCreator),\n];\n\nconst DEFERRED_BUILTIN_LOADERS: Array<{\n\tname: string;\n\tload: () => Promise<{ default: unknown }>;\n}> = [\n\t{ name: \"pi-ollama-cloud\", load: () => import(\"./pi-ollama-cloud/index.ts\") },\n\t{ name: \"narumiruna-pi-goal\", load: () => import(\"./narumiruna-pi-goal/src/goal.ts\") },\n\t{ name: \"lunr-cron\", load: () => import(\"./lunr-cron.ts\") },\n\t{ name: \"pi-intercom\", load: () => import(\"./pi-intercom/index.ts\") },\n\t{ name: \"pi-prompt-template-model\", load: () => import(\"./pi-prompt-template-model/index.ts\") },\n\t{ name: \"pi-subagents\", load: () => import(\"./pi-subagents/index.ts\") },\n\t{ name: \"pi-web-access\", load: () => import(\"./pi-web-access/index.ts\") },\n\t{ name: \"pi-lsp-extension\", load: () => import(\"./pi-lsp-extension/src/index.ts\") },\n\t{ name: \"pi-mcp-adapter\", load: () => import(\"./pi-mcp-adapter/index.ts\") },\n];\n\nexport const DEFERRED_BUILTIN_EXTENSION_NAMES = DEFERRED_BUILTIN_LOADERS.map((entry) => entry.name);\n\n/** Flags registered only after deferred builtins attach. Do not fail CLI parse for these. */\nexport const DEFERRED_BUILTIN_FLAGS = [\"mcp-config\"] as const;\n\n/** Import MCP / LSP / web-access / intercom / subagents only when needed. */\nexport async function loadDeferredBuiltinExtensions(): Promise<InlineExtension[]> {\n\tconst loaded = await Promise.all(\n\t\tDEFERRED_BUILTIN_LOADERS.map(async ({ name, load }) => {\n\t\t\tconst module = await measureStartup(name, load, \"imports\");\n\t\t\treturn ext(name, module.default);\n\t\t}),\n\t);\n\treturn loaded;\n}\n\n/** Full roster. Print / RPC / gateway still need every factory before the first turn. */\nexport async function loadAllBuiltinExtensions(): Promise<InlineExtension[]> {\n\treturn [...lightBuiltinExtensions, ...(await loadDeferredBuiltinExtensions())];\n}\n\n/** Eager full roster. Prefer loadAllBuiltinExtensions() so CLI start stays light. */\nexport const builtinExtensions: InlineExtension[] = lightBuiltinExtensions;\n"]}
|
|
@@ -15,7 +15,6 @@ import lunrLocalProviders from "./lunr-local-providers/index.js";
|
|
|
15
15
|
import lunrTodos from "./lunr-todos.js";
|
|
16
16
|
import lunrPlanTools from "./lunr-plan-tools.js";
|
|
17
17
|
import lunrSkillCreator from "./lunr-skill-creator/index.js";
|
|
18
|
-
import lunrSettingsTools from "./lunr-settings-tools.js";
|
|
19
18
|
/**
|
|
20
19
|
* Wrap a raw factory function as a named InlineExtension.
|
|
21
20
|
* The cast bridges the gap between the source-level ExtensionFactory type
|
|
@@ -36,7 +35,6 @@ export const lightBuiltinExtensions = [
|
|
|
36
35
|
ext("lunr-todos", lunrTodos),
|
|
37
36
|
ext("lunr-plan-tools", lunrPlanTools),
|
|
38
37
|
ext("lunr-skill-creator", lunrSkillCreator),
|
|
39
|
-
ext("lunr-settings-tools", lunrSettingsTools),
|
|
40
38
|
];
|
|
41
39
|
const DEFERRED_BUILTIN_LOADERS = [
|
|
42
40
|
{ name: "pi-ollama-cloud", load: () => import("./pi-ollama-cloud/index.js") },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/builtin-extensions/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,cAAc,MAAM,uBAAuB,CAAC;AACnD,OAAO,KAAK,MAAM,aAAa,CAAC;AAChC,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,aAAa,MAAM,qBAAqB,CAAC;AAChD,OAAO,aAAa,MAAM,qBAAqB,CAAC;AAChD,OAAO,kBAAkB,MAAM,iCAAiC,CAAC;AACjE,OAAO,SAAS,MAAM,iBAAiB,CAAC;AACxC,OAAO,aAAa,MAAM,sBAAsB,CAAC;AACjD,OAAO,gBAAgB,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/builtin-extensions/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Built-in extensions for lunR.\n *\n * Light factories stay on the first-paint graph. Heavy factories are imported\n * only when attachDeferredBuiltinExtensions() runs after the TUI is up, or\n * immediately for print/RPC/gateway where there is no first-paint window.\n */\n\nimport type { ExtensionFactory, InlineExtension } from \"../core/extensions/types.ts\";\nimport { measureStartup } from \"../core/timings.ts\";\n\nimport simplePiMemory from \"./simple-pi-memory.ts\";\nimport piTps from \"./pi-tps.ts\";\nimport ashxjTui from \"./ashxj-tui.ts\";\nimport ashxjSpinners from \"./ashxj-spinners.ts\";\nimport ashxjThinking from \"./ashxj-thinking.ts\";\nimport lunrLocalProviders from \"./lunr-local-providers/index.ts\";\nimport lunrTodos from \"./lunr-todos.ts\";\nimport lunrPlanTools from \"./lunr-plan-tools.ts\";\nimport lunrSkillCreator from \"./lunr-skill-creator/index.ts\";\n\n/**\n * Wrap a raw factory function as a named InlineExtension.\n * The cast bridges the gap between the source-level ExtensionFactory type\n * (used here) and the dist-level type that extensions import via the package\n * name. The underlying function signatures are structurally identical.\n */\nfunction ext(name: string, factory: unknown): InlineExtension {\n\treturn { name, factory: factory as ExtensionFactory };\n}\n\n/** UI, local providers, and small lunR-native factories. Safe to import at CLI start. */\nexport const lightBuiltinExtensions: InlineExtension[] = [\n\text(\"simple-pi-memory\", simplePiMemory),\n\text(\"pi-tps\", piTps),\n\text(\"ashxj-tui\", ashxjTui),\n\text(\"ashxj-spinners\", ashxjSpinners),\n\text(\"ashxj-thinking\", ashxjThinking),\n\text(\"lunr-local-providers\", lunrLocalProviders),\n\text(\"lunr-todos\", lunrTodos),\n\text(\"lunr-plan-tools\", lunrPlanTools),\n\text(\"lunr-skill-creator\", lunrSkillCreator),\n];\n\nconst DEFERRED_BUILTIN_LOADERS: Array<{\n\tname: string;\n\tload: () => Promise<{ default: unknown }>;\n}> = [\n\t{ name: \"pi-ollama-cloud\", load: () => import(\"./pi-ollama-cloud/index.ts\") },\n\t{ name: \"narumiruna-pi-goal\", load: () => import(\"./narumiruna-pi-goal/src/goal.ts\") },\n\t{ name: \"lunr-cron\", load: () => import(\"./lunr-cron.ts\") },\n\t{ name: \"pi-intercom\", load: () => import(\"./pi-intercom/index.ts\") },\n\t{ name: \"pi-prompt-template-model\", load: () => import(\"./pi-prompt-template-model/index.ts\") },\n\t{ name: \"pi-subagents\", load: () => import(\"./pi-subagents/index.ts\") },\n\t{ name: \"pi-web-access\", load: () => import(\"./pi-web-access/index.ts\") },\n\t{ name: \"pi-lsp-extension\", load: () => import(\"./pi-lsp-extension/src/index.ts\") },\n\t{ name: \"pi-mcp-adapter\", load: () => import(\"./pi-mcp-adapter/index.ts\") },\n];\n\nexport const DEFERRED_BUILTIN_EXTENSION_NAMES = DEFERRED_BUILTIN_LOADERS.map((entry) => entry.name);\n\n/** Flags registered only after deferred builtins attach. Do not fail CLI parse for these. */\nexport const DEFERRED_BUILTIN_FLAGS = [\"mcp-config\"] as const;\n\n/** Import MCP / LSP / web-access / intercom / subagents only when needed. */\nexport async function loadDeferredBuiltinExtensions(): Promise<InlineExtension[]> {\n\tconst loaded = await Promise.all(\n\t\tDEFERRED_BUILTIN_LOADERS.map(async ({ name, load }) => {\n\t\t\tconst module = await measureStartup(name, load, \"imports\");\n\t\t\treturn ext(name, module.default);\n\t\t}),\n\t);\n\treturn loaded;\n}\n\n/** Full roster. Print / RPC / gateway still need every factory before the first turn. */\nexport async function loadAllBuiltinExtensions(): Promise<InlineExtension[]> {\n\treturn [...lightBuiltinExtensions, ...(await loadDeferredBuiltinExtensions())];\n}\n\n/** Eager full roster. Prefer loadAllBuiltinExtensions() so CLI start stays light. */\nexport const builtinExtensions: InlineExtension[] = lightBuiltinExtensions;\n"]}
|
|
@@ -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:
|
|
14
|
-
*
|
|
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
|
|
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
|
|
43
|
-
* TODO_WIDGET_COLLAPSED_ROWS
|
|
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:
|
|
15
|
-
*
|
|
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
|
|
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
|
|
55
|
-
* TODO_WIDGET_COLLAPSED_ROWS
|
|
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
|
|
77
|
-
const
|
|
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":"AAAA,cAAc;AACd;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,qDAAqD,CAAC;AAoB9E,4EAA4E;AAC5E,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC;AAE5C,MAAM,aAAa,GAA+B;IACjD,WAAW,EAAE,KAAG;IAChB,OAAO,EAAE,KAAG;IACZ,SAAS,EAAE,KAAG;CACd,CAAC;AAEF,yFAAyF;AACzF,MAAM,UAAU,cAAc,CAAC,KAAiB,EAAU;IACzD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,WAAW,CAAC;IAC3C,MAAM,KAAK,GAAG,CAAC,CAAa,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;IAC5E,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,UAAU,GAAG,KAAK,CAAC,aAAa,CAAC,CAAC;IACxC,MAAM,OAAO,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC;IACjC,MAAM,SAAS,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC;IACrC,IAAI,UAAU,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,GAAG,UAAU,cAAc,CAAC,CAAC;IAC5D,IAAI,OAAO,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,UAAU,CAAC,CAAC;IAClD,IAAI,SAAS,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,GAAG,SAAS,YAAY,CAAC,CAAC;IACxD,OAAO,GAAG,KAAK,CAAC,MAAM,QAAQ,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AAAA,CACnF;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAiB,EAAE,QAAiB,EAAE,SAAS,GAAG,OAAO,EAAoB;IACjH,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAClC,MAAM,GAAG,GAAG,CAAC,CAAW,EAAkB,EAAE,CAAC,CAAC;QAC7C,IAAI,EAAE,MAAM;QACZ,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,IAAI,EAAE,GAAG,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE;KAC/C,CAAC,CAAC;IACH,MAAM,MAAM,GAAG;QACd,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,aAAa,CAAC;QAClD,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC;KAC9C,CAAC;IACF,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC;IAC3D,IAAI,QAAQ,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/C,CAAC;IACD,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,0BAA0B,CAAC;IACzE,MAAM,KAAK,GAAqB,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACtE,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,GAAG,WAAW,CAAC;IAC3C,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;QAChB,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,MAAM,OAAO,EAAE,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,KAAK,CAAC;AAAA,CACb;AAED,8EAA8E;AAC9E,YAAY;AACZ,8EAA8E;AAE9E,MAAM,UAAU,GAAG,OAAO,CAAC;AAC3B,MAAM,sBAAsB,GAAG,MAAM,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;AAE1E,MAAM,CAAC,OAAO,WAAW,EAAgB,EAAQ;IAChD,IAAI,KAAK,GAAe,EAAE,CAAC;IAC3B,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,OAAO,GAA4B,IAAI,CAAC;IAE5C,SAAS,QAAQ,CAAC,IAAoB,EAAE,KAAU,EAAU;QAC3D,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM;YAAE,OAAO,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACzF,IAAI,IAAI,CAAC,MAAM,KAAK,aAAa;YAAE,OAAO,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACxE,IAAI,IAAI,CAAC,MAAM,KAAK,WAAW;YAAE,OAAO,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACnE,OAAO,IAAI,CAAC,IAAI,CAAC;IAAA,CACjB;IAED,SAAS,aAAa,GAAS;QAC9B,MAAM,GAAG,GAAG,OAAO,CAAC;QACpB,IAAI,CAAC,GAAG,EAAE,KAAK;YAAE,OAAO;QACxB,MAAM,KAAK,GAAG,oBAAoB,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC;QACjF,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC9C,GAAG,CAAC,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;YACxC,OAAO;QACR,CAAC;QACD,GAAG,CAAC,EAAE,CAAC,SAAS,CACf,UAAU,EACV,CAAC,IAAa,EAAE,KAAU,EAAE,EAAE,CAAC;YAC9B,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAC7E,OAAO;gBACN,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;gBAC7C,UAAU,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE;gBACrC,WAAW,EAAE,GAAG,EAAE,CAAC;oBAClB,QAAQ,GAAG,CAAC,QAAQ,CAAC;oBACrB,aAAa,EAAE,CAAC;oBAChB,OAAO,IAAI,CAAC;gBAAA,CACZ;aACD,CAAC;QAAA,CACF,EACD,EAAE,SAAS,EAAE,aAAa,EAAE,CAC5B,CAAC;IAAA,CACF;IAED,SAAS,cAAc,GAAS;QAC/B,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC;QAC3D,IAAI,IAAI,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;YAAE,OAAO;QACzC,KAAK,GAAG,IAAI,CAAC;QACb,aAAa,EAAE,CAAC;IAAA,CAChB;IAED,4EAA4E;IAC5E,SAAS,sBAAsB,GAAS;QACtC,UAAsC,CAAC,sBAAsB,CAAC,GAAG,CAAC,KAAc,EAAE,EAAE,CAAC;YACrF,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;YAC1B,aAAa,EAAE,CAAC;QAAA,CAChB,CAAC;IAAA,CACF;IACD,sBAAsB,EAAE,CAAC;IAEzB,yDAAyD;IACzD,EAAE,CAAC,EAAE,CAAC,eAAe,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC;QACvC,OAAO,GAAG,GAAG,CAAC;QACd,KAAK,GAAG,EAAE,CAAC;QACX,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,gBAAgB,EAAE,EAAE,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QACtE,sBAAsB,EAAE,CAAC;QACzB,IAAI,GAAG,CAAC,KAAK;YAAE,GAAG,CAAC,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;IAAA,CACvD,CAAC,CAAC;IAEH,uEAAuE;IACvE,qCAAqC;IACrC,EAAE,CAAC,EAAE,CAAC,eAAe,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC;QACtC,IAAI,KAAK,CAAC,OAAO,EAAE,IAAI,KAAK,MAAM;YAAE,OAAO;QAC3C,OAAO,GAAG,GAAG,CAAC;QACd,cAAc,EAAE,CAAC;IAAA,CACjB,CAAC,CAAC;IAEH,EAAE,CAAC,EAAE,CAAC,kBAAkB,EAAE,GAAG,EAAE,CAAC;QAC/B,IAAI,OAAO,EAAE,KAAK;YAAE,OAAO,CAAC,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;QAChE,OAAO,GAAG,IAAI,CAAC;QACf,OAAQ,UAAsC,CAAC,sBAAsB,CAAC,CAAC;IAAA,CACvE,CAAC,CAAC;IAEH,mCAAmC;IACnC,EAAE,CAAC,YAAY,CAAC;QACf,IAAI,EAAE,MAAM;QACZ,KAAK,EAAE,MAAM;QACb,WAAW,EAAE;YACZ,qFAAqF;YACrF,wEAAwE;YACxE,8EAA8E;YAC9E,+EAA+E;SAC/E,CAAC,IAAI,CAAC,IAAI,CAAC;QACZ,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC;YACvB,KAAK,EAAE,IAAI,CAAC,KAAK,CAChB,IAAI,CAAC,MAAM,CAAC;gBACX,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,yBAAyB,EAAE,CAAC;gBAC3D,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,gCAAgC,EAAE,CAAC;gBACvE,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC;oBAClB,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC;oBACvB,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC;oBAC3B,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC;iBACzB,CAAC;aACF,CAAC,CACF;SACD,CAAC;QACF,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE;YAC3D,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;YAChF,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;YAClE,KAAK,GAAG,QAAQ;iBACd,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;iBACrF,GAAG,CAAC,CAAC,CAAM,EAAE,CAAS,EAAE,EAAE,CAAC,CAAC;gBAC5B,EAAE,EAAE,OAAO,CAAC,CAAC,EAAE,KAAK,QAAQ,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC;gBAC3D,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE;gBACzB,MAAM,EAAE,CAAC,CAAC,MAAM,KAAK,aAAa,IAAI,CAAC,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;aACrF,CAAC,CAAC,CAAC;YACL,OAAO,GAAG,GAAG,CAAC;YACd,aAAa,EAAE,CAAC;YAChB,OAAO,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;QAAA,CACnC;QACD,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE;YACvB,MAAM,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;YAC7D,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;YACpE,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAAA,CAC5F;QACD,YAAY,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE;YAC1C,IAAI,SAAS;gBAAE,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,mBAAmB,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAC/E,MAAM,KAAK,GAAG,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;YACnC,MAAM,OAAO,GAAG,KAAK,EAAE,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACzD,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAAA,CAChD;KACD,CAAC,CAAC;AAAA,CACH","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"]}
|
|
@@ -10,14 +10,16 @@ type BrokerLaunchSpec = {
|
|
|
10
10
|
launcherCommandLine: string;
|
|
11
11
|
};
|
|
12
12
|
export declare function getTsxCliPath(extensionDir?: string): string;
|
|
13
|
+
export declare function isNativeSupervisorChannelActive(env?: NodeJS.ProcessEnv): boolean;
|
|
14
|
+
export declare function resolveBrokerScriptPath(brokerDir?: string): string;
|
|
13
15
|
export declare function getWindowsHiddenLauncherPath(intercomDir?: string): string;
|
|
14
|
-
export declare function getWindowsBrokerCommandLine(brokerPath: string, extensionDir?: string, nodePath?: string, brokerCommand?: string, brokerArgs?: string[]): string;
|
|
16
|
+
export declare function getWindowsBrokerCommandLine(brokerPath: string, extensionDir?: string, nodePath?: string, brokerCommand?: string, brokerArgs?: string[], stderrLogPath?: string): string;
|
|
15
17
|
export declare function getWindowsHiddenLauncherScript(commandLine: string): string;
|
|
16
18
|
export declare function isBrokerHealthOkMessage(message: unknown, requestId: string): boolean;
|
|
17
|
-
export declare function getBrokerLaunchSpec(brokerPath: string, brokerCommand: string, brokerArgs: string[], extensionDir?: string, platform?: NodeJS.Platform, intercomDir?: string, nodePath?: string): BrokerLaunchSpec;
|
|
18
|
-
export declare function getBrokerSpawnOptions(extensionDir?: string, env?: NodeJS.ProcessEnv): {
|
|
19
|
+
export declare function getBrokerLaunchSpec(brokerPath: string, brokerCommand: string, brokerArgs: string[], extensionDir?: string, platform?: NodeJS.Platform, intercomDir?: string, nodePath?: string, stderrLogPath?: string): BrokerLaunchSpec;
|
|
20
|
+
export declare function getBrokerSpawnOptions(extensionDir?: string, env?: NodeJS.ProcessEnv, stderrFd?: number): {
|
|
19
21
|
detached: true;
|
|
20
|
-
stdio: "ignore";
|
|
22
|
+
stdio: "ignore" | ["ignore", "ignore", number];
|
|
21
23
|
cwd: string;
|
|
22
24
|
env: NodeJS.ProcessEnv;
|
|
23
25
|
windowsHide: true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spawn.d.ts","sourceRoot":"","sources":["../../../../src/builtin-extensions/pi-intercom/broker/spawn.ts"],"names":[],"mappings":"AA0BA,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;AAMD,wBAAgB,4BAA4B,CAAC,WAAW,GAAE,MAAqB,GAAG,MAAM,CAEvF;AASD,wBAAgB,2BAA2B,CACzC,UAAU,EAAE,MAAM,EAClB,YAAY,GAAE,MAAsB,EACpC,QAAQ,GAAE,MAAyB,EACnC,aAAa,SAAQ,EACrB,UAAU,GAAE,MAAM,EAA4B,GAC7C,MAAM,CAMR;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,GAClC,gBAAgB,CAyBlB;AAED,wBAAgB,qBAAqB,CACnC,YAAY,GAAE,MAAsB,EACpC,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC;IACD,QAAQ,EAAE,IAAI,CAAC;IACf,KAAK,EAAE,QAAQ,CAAC;IAChB,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,CA8DpG","sourcesContent":["// @ts-nocheck\nimport { spawn } from \"child_process\";\nimport { existsSync, 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\");\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\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\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): string {\n if (usesDefaultBrokerCommand(brokerCommand, brokerArgs)) {\n return [quoteWindowsArg(nodePath), quoteWindowsArg(getTsxCliPath(extensionDir)), quoteWindowsArg(brokerPath)].join(\" \");\n }\n\n return [quoteWindowsArg(brokerCommand), ...brokerArgs.map(quoteWindowsArg), quoteWindowsArg(brokerPath)].join(\" \");\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): 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(brokerPath, extensionDir, nodePath, brokerCommand, brokerArgs),\n };\n }\n\n if (usesDefaultBrokerCommand(brokerCommand, brokerArgs)) {\n return {\n kind: \"direct\",\n command: nodePath,\n args: [getTsxCliPath(extensionDir), brokerPath],\n };\n }\n\n return {\n kind: \"direct\",\n command: brokerCommand,\n args: [...brokerArgs, brokerPath],\n };\n}\n\nexport function getBrokerSpawnOptions(\n extensionDir: string = EXTENSION_DIR,\n env: NodeJS.ProcessEnv = process.env,\n): {\n detached: true;\n stdio: \"ignore\";\n cwd: string;\n env: NodeJS.ProcessEnv;\n windowsHide: true;\n} {\n return {\n detached: true,\n stdio: \"ignore\",\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 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 = join(dirname(fileURLToPath(import.meta.url)), \"broker.ts\");\n const launch = getBrokerLaunchSpec(brokerPath, brokerCommand, brokerArgs);\n if (launch.kind === \"windows-launcher\") {\n writeWindowsHiddenLauncher(launch.launcherCommandLine, launch.launcherPath);\n }\n const child = spawn(launch.command, launch.args, getBrokerSpawnOptions());\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 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"]}
|