@ashx-j/lunr 0.2.23 → 0.2.25
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 +22 -1
- package/README.md +10 -10
- package/dist/builtin-extensions/ashxj-tui.d.ts.map +1 -1
- package/dist/builtin-extensions/ashxj-tui.js +2 -2
- package/dist/builtin-extensions/ashxj-tui.js.map +1 -1
- package/dist/builtin-extensions/lunr-plan-tools.d.ts +7 -7
- package/dist/builtin-extensions/lunr-plan-tools.d.ts.map +1 -1
- package/dist/builtin-extensions/lunr-plan-tools.js +12 -12
- package/dist/builtin-extensions/lunr-plan-tools.js.map +1 -1
- package/dist/builtin-extensions/pi-intercom/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-intercom/index.js +2 -0
- package/dist/builtin-extensions/pi-intercom/index.js.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/direct-tools.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/direct-tools.js +1 -0
- package/dist/builtin-extensions/pi-mcp-adapter/direct-tools.js.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/index.js +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/index.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js +1 -1
- 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 -4
- 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 +6 -4
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/intercom-bridge.d.ts +2 -0
- package/dist/builtin-extensions/pi-subagents/src/intercom/intercom-bridge.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/intercom-bridge.js +6 -3
- package/dist/builtin-extensions/pi-subagents/src/intercom/intercom-bridge.js.map +1 -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 +7 -2
- package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.d.ts +2 -0
- 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 +4 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js.map +1 -1
- 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 +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.d.ts +1 -0
- 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 +5 -1
- 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 +3 -2
- 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.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js +19 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.d.ts +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.js +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.d.ts +2 -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 +17 -15
- 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 +3 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.js.map +1 -1
- 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 +2 -2
- package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts +1 -0
- package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/types.js.map +1 -1
- package/dist/core/agent-session.d.ts +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +5 -4
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/browser/schema.d.ts +1 -1
- package/dist/core/browser/schema.d.ts.map +1 -1
- package/dist/core/browser/schema.js +1 -1
- package/dist/core/browser/schema.js.map +1 -1
- package/dist/core/catalog-cache-scope.d.ts.map +1 -1
- package/dist/core/catalog-cache-scope.js +3 -1
- package/dist/core/catalog-cache-scope.js.map +1 -1
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +3 -2
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/permissions.d.ts +16 -23
- package/dist/core/permissions.d.ts.map +1 -1
- package/dist/core/permissions.js +66 -141
- package/dist/core/permissions.js.map +1 -1
- package/dist/core/plan-mode.d.ts +10 -12
- package/dist/core/plan-mode.d.ts.map +1 -1
- package/dist/core/plan-mode.js +16 -18
- 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 +2 -0
- package/dist/core/runtime-bridges.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +13 -6
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +9 -2
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +9 -1
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +40 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +4 -4
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/subagent-permission-inherit.d.ts +6 -10
- package/dist/core/subagent-permission-inherit.d.ts.map +1 -1
- package/dist/core/subagent-permission-inherit.js +14 -14
- package/dist/core/subagent-permission-inherit.js.map +1 -1
- package/dist/core/system-prompt.d.ts +2 -0
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +9 -4
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/gateway/adapters/discord.d.ts.map +1 -1
- package/dist/gateway/adapters/discord.js +3 -8
- package/dist/gateway/adapters/discord.js.map +1 -1
- package/dist/gateway/adapters/telegram.d.ts.map +1 -1
- package/dist/gateway/adapters/telegram.js +7 -0
- package/dist/gateway/adapters/telegram.js.map +1 -1
- package/dist/gateway/agent-bridge.d.ts +4 -1
- package/dist/gateway/agent-bridge.d.ts.map +1 -1
- package/dist/gateway/agent-bridge.js +57 -17
- package/dist/gateway/agent-bridge.js.map +1 -1
- package/dist/gateway/approval.d.ts +2 -6
- package/dist/gateway/approval.d.ts.map +1 -1
- package/dist/gateway/approval.js +3 -7
- package/dist/gateway/approval.js.map +1 -1
- package/dist/gateway/buttons.d.ts.map +1 -1
- package/dist/gateway/buttons.js +29 -19
- package/dist/gateway/buttons.js.map +1 -1
- package/dist/gateway/commands.d.ts +2 -0
- package/dist/gateway/commands.d.ts.map +1 -1
- package/dist/gateway/commands.js +39 -18
- package/dist/gateway/commands.js.map +1 -1
- package/dist/gateway/config.d.ts +1 -0
- package/dist/gateway/config.d.ts.map +1 -1
- package/dist/gateway/config.js +2 -0
- package/dist/gateway/config.js.map +1 -1
- package/dist/gateway/cron.d.ts +1 -1
- package/dist/gateway/cron.d.ts.map +1 -1
- package/dist/gateway/cron.js +2 -2
- package/dist/gateway/cron.js.map +1 -1
- package/dist/gateway/index.d.ts.map +1 -1
- package/dist/gateway/index.js +26 -26
- package/dist/gateway/index.js.map +1 -1
- package/dist/gateway/mobile-commands.d.ts.map +1 -1
- package/dist/gateway/mobile-commands.js +15 -14
- package/dist/gateway/mobile-commands.js.map +1 -1
- package/dist/gateway/presenter.d.ts +12 -1
- package/dist/gateway/presenter.d.ts.map +1 -1
- package/dist/gateway/presenter.js +146 -48
- package/dist/gateway/presenter.js.map +1 -1
- package/dist/gateway/router.d.ts +5 -5
- package/dist/gateway/router.d.ts.map +1 -1
- package/dist/gateway/router.js +96 -42
- package/dist/gateway/router.js.map +1 -1
- package/dist/gateway/service.d.ts.map +1 -1
- package/dist/gateway/service.js +13 -4
- package/dist/gateway/service.js.map +1 -1
- package/dist/gateway/setup.d.ts +5 -0
- package/dist/gateway/setup.d.ts.map +1 -1
- package/dist/gateway/setup.js +98 -42
- package/dist/gateway/setup.js.map +1 -1
- package/dist/gateway/stream.d.ts +6 -5
- package/dist/gateway/stream.d.ts.map +1 -1
- package/dist/gateway/stream.js +24 -11
- package/dist/gateway/stream.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +1 -2
- package/dist/main.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 +23 -3
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/thinking-tail.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-tail.js +2 -2
- package/dist/modes/interactive/components/thinking-tail.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +1 -0
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +12 -2
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -2
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +76 -90
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/node-runtime/{agent-session-services-GZVSOGYR.js → agent-session-services-YXXFCP5J.js} +12 -12
- package/dist/node-runtime/{builtin-extensions-QVCXNQT4.js → builtin-extensions-ZLAX7TUH.js} +3 -3
- package/dist/node-runtime/{chunk-W64JC4E7.js → chunk-2HHLJWGD.js} +3 -87
- package/dist/node-runtime/{chunk-VWCE2HSZ.js → chunk-2HMIRK7W.js} +4 -4
- package/dist/node-runtime/{chunk-XLTL257W.js → chunk-2KIPT4HJ.js} +5 -1
- package/dist/node-runtime/{chunk-RIR5EDDJ.js → chunk-3XPLBUEM.js} +17 -12
- package/dist/node-runtime/{chunk-QAZOULTU.js → chunk-6XL67VVY.js} +40 -6
- package/dist/node-runtime/chunk-7S3T6YEA.js +97 -0
- package/dist/node-runtime/{chunk-R3K2F5GI.js → chunk-B4HP6ZWM.js} +2 -0
- package/dist/node-runtime/{chunk-TQGPP4SC.js → chunk-CDVHZOOK.js} +1 -1
- package/dist/node-runtime/{chunk-YJMMG7C6.js → chunk-DSIKTUPW.js} +1 -1
- package/dist/node-runtime/{chunk-LFZDVKSP.js → chunk-E4D3IGP2.js} +41 -25
- package/dist/node-runtime/{chunk-OFKSVRV7.js → chunk-GYIXXQAO.js} +1 -1
- package/dist/node-runtime/{chunk-BX2RB477.js → chunk-HFVZUPFJ.js} +1 -1
- package/dist/node-runtime/{chunk-AZJHNQKQ.js → chunk-I54LGHWM.js} +1 -1
- package/dist/node-runtime/{chunk-YHQ3NBB3.js → chunk-IJIFHRTB.js} +20 -18
- package/dist/node-runtime/{chunk-GS66LV4M.js → chunk-KJ6DWS3K.js} +4 -4
- package/dist/node-runtime/{chunk-HPUP7E4T.js → chunk-KWIYUUZK.js} +68 -42
- package/dist/node-runtime/{chunk-TVJUOJZJ.js → chunk-LQP3RB67.js} +184 -1527
- package/dist/node-runtime/{chunk-VSELYKWU.js → chunk-LWYIVUGV.js} +2 -1
- package/dist/node-runtime/{chunk-JHYKXIMM.js → chunk-MPKSOJBT.js} +1 -1
- package/dist/node-runtime/{chunk-GMHJCCEN.js → chunk-NRKGMJYU.js} +4 -1
- package/dist/node-runtime/{chunk-CSUZVNYE.js → chunk-OVX6X377.js} +10 -6
- package/dist/node-runtime/{chunk-DD5YWFSJ.js → chunk-QTU6AKNJ.js} +130 -65
- package/dist/node-runtime/{chunk-LLUIPGDR.js → chunk-RC7DMV45.js} +110 -77
- package/dist/node-runtime/{chunk-UWPJL22I.js → chunk-SLVOAQOY.js} +15 -4
- package/dist/node-runtime/{chunk-EKVFP6YF.js → chunk-TORR5XBV.js} +17 -16
- package/dist/node-runtime/{chunk-UP2XBVH5.js → chunk-UOEDI7RP.js} +1 -1
- package/dist/node-runtime/{chunk-DQMGU675.js → chunk-VQFSUJJG.js} +20 -17
- package/dist/node-runtime/{chunk-X3URCYCU.js → chunk-WF7PGWRZ.js} +14 -14
- package/dist/node-runtime/chunk-YCUZEGJG.js +1361 -0
- package/dist/node-runtime/{chunk-5D7LC2ZQ.js → chunk-ZXSGTGJ6.js} +54 -85
- package/dist/node-runtime/cli-runtime.js +1 -1
- package/dist/node-runtime/{command-PDVTBRWA.js → command-6WDOYKS6.js} +5 -5
- package/dist/node-runtime/{commands-KAMO2T6P.js → commands-NZLSOHPE.js} +2 -2
- package/dist/node-runtime/{commands-XDCFGIGW.js → commands-QC6VUZJQ.js} +9 -9
- package/dist/node-runtime/{config-RORQUQFG.js → config-D4QUPAHX.js} +1 -1
- package/dist/node-runtime/{direct-tool-executor-YAVOLDFY.js → direct-tool-executor-77GW4F5P.js} +3 -3
- package/dist/node-runtime/{export-html-KSOIMVPI.js → export-html-VM4J2MNR.js} +1 -1
- package/dist/node-runtime/{gateway-AYWM5DIO.js → gateway-INAYBF4Q.js} +141 -85
- package/dist/node-runtime/index.js +23 -21
- package/dist/node-runtime/{init-X2LIUB2H.js → init-HS6NQXSK.js} +2 -2
- package/dist/node-runtime/{install-cli-3BO4MMQ6.js → install-cli-KL4UDDAD.js} +3 -3
- package/dist/node-runtime/{interactive-mode-4MFRKAHG.js → interactive-mode-Z5M7WYYZ.js} +14 -14
- package/dist/node-runtime/{interactive-view-TUZHCQM3.js → interactive-view-NGFAMHPW.js} +2 -2
- package/dist/node-runtime/{lunr-browser-RYFIIVJN.js → lunr-browser-IR7TL3J7.js} +4 -4
- package/dist/node-runtime/{lunr-cron-YWXEPZMQ.js → lunr-cron-LWIZJM2N.js} +8 -8
- package/dist/node-runtime/main.js +19 -18
- package/dist/node-runtime/{mobile-commands-LVHDAUDP.js → mobile-commands-J7OJEKFO.js} +6 -6
- package/dist/node-runtime/{model-runtime-PLQKAG4B.js → model-runtime-OO5HFLWE.js} +1 -1
- package/dist/node-runtime/{package-manager-cli-6GT3Z3CK.js → package-manager-cli-VYZM5WHN.js} +10 -8
- package/dist/node-runtime/{pi-intercom-HBPK5HSJ.js → pi-intercom-QM53RLTS.js} +2 -0
- package/dist/node-runtime/{pi-mcp-adapter-E6WULBKF.js → pi-mcp-adapter-5UKBZP44.js} +7 -6
- package/dist/node-runtime/{pi-subagents-CDWK2AKY.js → pi-subagents-DNY7EAIG.js} +23 -13
- package/dist/node-runtime/{proxy-modes-OSQCWRBI.js → proxy-modes-LGEUYD7R.js} +3 -3
- package/dist/node-runtime/rpc-entry.js +19 -18
- package/dist/node-runtime/{runtime-bridges-3XUQ46XW.js → runtime-bridges-VRDDR73P.js} +8 -1
- package/dist/node-runtime/{service-ISSP6VHE.js → service-MDQIPQ26.js} +1 -1
- package/dist/node-runtime/{session-handoff-HC7R5E24.js → session-handoff-GWARVEWT.js} +2 -2
- package/dist/node-runtime/{session-manager-CFN4UFM2.js → session-manager-JB5J6XBN.js} +1 -1
- package/dist/node-runtime/{settings-manager-6VEMXRDL.js → settings-manager-J4SPQPDJ.js} +10 -7
- package/dist/node-runtime/setup-CQ2NLVK5.js +214 -0
- package/dist/node-runtime/{subagent-executor-M5VWB3D3.js → subagent-executor-KZZQGAKV.js} +40 -7
- package/dist/node-runtime/{update-cli-UVKJWRRJ.js → update-cli-GTFJC7XK.js} +4 -5
- package/dist/startup/interactive-view.d.ts.map +1 -1
- package/dist/startup/interactive-view.js +5 -1
- package/dist/startup/interactive-view.js.map +1 -1
- package/docs/browser.md +1 -1
- package/docs/features.md +19 -18
- package/docs/index.md +1 -1
- package/docs/keybindings.md +1 -1
- package/docs/providers.md +6 -2
- package/docs/quickstart.md +2 -2
- package/docs/settings.md +1 -1
- package/docs/usage.md +4 -4
- 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/plan-mode/README.md +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/node-runtime/chunk-LISUTZS3.js +0 -19
- package/dist/node-runtime/setup-5SR2J35A.js +0 -155
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mobile-commands.d.ts","sourceRoot":"","sources":["../../src/gateway/mobile-commands.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,aAAa,EAAqB,MAAM,aAAa,CAAC;AAGpE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEhE,eAAO,MAAM,eAAe;;;GAc3B,CAAC;AAEF,eAAO,MAAM,kBAAkB;;;GAQ9B,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAiBxE;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAazF;AAED,UAAU,aAAa;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,YAAY,CAAC;IACpB,OAAO,EAAE,eAAe,CAAC;IACzB,MAAM,EAAE,UAAU,CAAC;IACnB,GAAG,EAAE,aAAa,CAAC;CACnB;AA8ED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEtD;AA6JD,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAiJ7G","sourcesContent":["import { mkdirSync, readdirSync, realpathSync, statSync } from \"node:fs\";\nimport { basename, dirname, isAbsolute, join, relative, resolve } from \"node:path\";\nimport { getPermissionMode, PERMISSION_MODES, setPermissionMode } from \"../core/permissions.ts\";\nimport { SessionManager } from \"../core/session-manager.ts\";\nimport { getSubagentCancellation } from \"../core/subagent-cancellation.ts\";\nimport { isGatewayOwner, requireGatewayOwner } from \"./authz.ts\";\nimport { createPicker, type PickerItem, type PickerResolveResult } from \"./buttons.ts\";\nimport { type GatewayConfig, loadGatewayConfig } from \"./config.ts\";\nimport { bindConversation, conversationBinding } from \"./conversations.ts\";\nimport { gatewayInput, gatewaySelect } from \"./presenter.ts\";\nimport type { BridgeLike } from \"./router.ts\";\nimport type { MessageEvent, PlatformAdapter } from \"./types.ts\";\n\nexport const MOBILE_COMMANDS = [\n\t{ name: \"project\", description: \"Browse folders and choose a project\" },\n\t{ name: \"continue\", description: \"Continue a handoff or the latest TUI session\" },\n\t{ name: \"sessions\", description: \"Browse saved sessions across projects\" },\n\t{ name: \"mode\", description: \"Choose manual, yolo, plan, or auto permissions\" },\n\t{ name: \"plan\", description: \"Plan work and approve it from this chat\" },\n\t{ name: \"settings\", description: \"Change this session's model, thinking, or permissions\" },\n\t{ name: \"stopall\", description: \"Stop this session and its background subagents\" },\n\t{ name: \"download\", description: \"Send a file from the current project\" },\n\t{ name: \"usage\", description: \"Show session tokens and subscription usage\" },\n\t{ name: \"processes\", description: \"List or stop this session's shell processes\" },\n\t{ name: \"refresh\", description: \"Refresh available models\" },\n\t{ name: \"skill\", description: \"Choose a skill and send it a task\" },\n\t{ name: \"fast\", description: \"Toggle OpenAI Codex fast mode\" },\n];\n\nexport const FORWARDED_COMMANDS = [\n\t{ name: \"goal\", description: \"Set or manage the session goal\" },\n\t{ name: \"cron\", description: \"Schedule prompts and manage scheduled jobs\" },\n\t{ name: \"run\", description: \"Launch a subagent task\" },\n\t{ name: \"chain\", description: \"Run a sequence of subagent tasks\" },\n\t{ name: \"parallel\", description: \"Run independent subagent tasks\" },\n\t{ name: \"mcp\", description: \"Show configured MCP servers\" },\n\t{ name: \"lsp\", description: \"Show language server status\" },\n];\n\nexport function resolveWithinRoots(path: string, roots: string[]): string {\n\tconst actual = realpathSync(resolve(path));\n\tif (\n\t\t!roots.some((root) => {\n\t\t\ttry {\n\t\t\t\tconst rel = relative(realpathSync(root), actual);\n\t\t\t\treturn (\n\t\t\t\t\trel === \"\" ||\n\t\t\t\t\t(!rel.startsWith(`..${process.platform === \"win32\" ? \"\\\\\" : \"/\"}`) && rel !== \"..\" && !isAbsolute(rel))\n\t\t\t\t);\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t})\n\t)\n\t\tthrow new Error(\"That path is outside the approved folders.\");\n\treturn actual;\n}\n\nexport function createProjectFolder(parent: string, name: string, roots: string[]): string {\n\tif (\n\t\t!name ||\n\t\tname === \".\" ||\n\t\tname === \"..\" ||\n\t\t/[\\\\/\\x00-\\x1f<>:\"|?*]/.test(name) ||\n\t\t/[. ]$/.test(name) ||\n\t\t/^(con|prn|aux|nul|com\\d|lpt\\d)(\\.|$)/i.test(name)\n\t)\n\t\tthrow new Error(\"Use a simple folder name without slashes or reserved characters.\");\n\tconst actual = resolveWithinRoots(parent, roots);\n\tmkdirSync(join(actual, name));\n\treturn resolveWithinRoots(join(actual, name), roots);\n}\n\ninterface MobileContext {\n\tkey: string;\n\tevent: MessageEvent;\n\tadapter: PlatformAdapter;\n\tbridge: BridgeLike;\n\tcfg: GatewayConfig;\n}\n\nasync function browseProject(ctx: MobileContext, initialPath?: string): Promise<void> {\n\tconst { key, event, adapter, bridge } = ctx;\n\tconst roots = ctx.cfg.projectRoots ?? [];\n\tif (!roots.length) throw new Error(\"Add approved project folders with lunr gateway setup first.\");\n\tlet current: string | undefined = initialPath\n\t\t? resolveWithinRoots(initialPath, ctx.cfg.projectRoots ?? [])\n\t\t: undefined;\n\tconst view = (): { title: string; items: PickerItem[] } => {\n\t\tif (!current)\n\t\t\treturn {\n\t\t\t\ttitle: \"Choose a project folder\",\n\t\t\t\titems: [...new Set([...(conversationBinding(key)?.recentProjects ?? []), ...roots])]\n\t\t\t\t\t.filter((p) => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tresolveWithinRoots(p, roots);\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\t.map((p) => ({ label: p, value: p })),\n\t\t\t};\n\t\tcurrent = resolveWithinRoots(current, roots);\n\t\tconst items: PickerItem[] = [\n\t\t\t{ label: \"Use this folder\", value: \"use\" },\n\t\t\t{ label: \"New folder\", value: \"new\" },\n\t\t\t{ label: \"Recent and roots\", value: \"roots\" },\n\t\t];\n\t\ttry {\n\t\t\tconst up = resolveWithinRoots(dirname(current), roots);\n\t\t\tif (up !== current) items.push({ label: \"Up\", value: up });\n\t\t} catch {}\n\t\tfor (const item of readdirSync(current, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {\n\t\t\tif (!item.isDirectory() && !item.isSymbolicLink()) continue;\n\t\t\ttry {\n\t\t\t\tconst path = resolveWithinRoots(join(current, item.name), roots);\n\t\t\t\tif (statSync(path).isDirectory()) items.push({ label: item.name, value: path });\n\t\t\t} catch {}\n\t\t}\n\t\treturn { title: current, items };\n\t};\n\tconst initial = view();\n\tawait createPicker(\n\t\tadapter,\n\t\tevent.source,\n\t\t{\n\t\t\tkind: \"project\",\n\t\t\tsessionKey: key,\n\t\t\tinvokerId: event.source.userId,\n\t\t\t...initial,\n\t\t\tvalidate: () => isGatewayOwner(event.source, loadGatewayConfig()),\n\t\t\tresolve: async (item): Promise<PickerResolveResult> => {\n\t\t\t\trequireGatewayOwner(event.source);\n\t\t\t\tif (item.value === \"use\" && current) {\n\t\t\t\t\tconst cwd = resolveWithinRoots(current, loadGatewayConfig().projectRoots ?? []);\n\t\t\t\t\tif (bridge.getStatus(key).busy)\n\t\t\t\t\t\tthrow new Error(\"Stop the current turn or wait before switching projects.\");\n\t\t\t\t\tawait bridge.reset(key);\n\t\t\t\t\tbindConversation(key, event.source, { cwd, owner: event.source.userId });\n\t\t\t\t\treturn {\n\t\t\t\t\t\tdone: true,\n\t\t\t\t\t\ttext: `Project selected: ${cwd}\\nSend a task to begin. Existing sessions are still available through /sessions.`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (item.value === \"new\" && current) {\n\t\t\t\t\tconst name = await gatewayInput(key, \"New folder name\");\n\t\t\t\t\tif (name) current = createProjectFolder(current, name, loadGatewayConfig().projectRoots ?? []);\n\t\t\t\t} else current = item.value === \"roots\" ? undefined : item.value;\n\t\t\t\treturn { done: false, ...view() };\n\t\t\t},\n\t\t},\n\t\t{ threadId: event.source.threadId },\n\t);\n}\n\nconst transfers = new Map<string, AbortController>();\nexport function cancelMobileTransfer(key: string): void {\n\ttransfers.get(key)?.abort();\n}\n\nasync function continueSession(ctx: MobileContext, file: string): Promise<void> {\n\tif (transfers.has(ctx.key)) throw new Error(\"A session transfer is already pending. Use /stop to cancel it.\");\n\tconst controller = new AbortController();\n\ttransfers.set(ctx.key, controller);\n\ttry {\n\t\tawait performContinue(ctx, file, controller.signal);\n\t} finally {\n\t\ttransfers.delete(ctx.key);\n\t}\n}\n\nasync function performContinue(ctx: MobileContext, file: string, signal: AbortSignal): Promise<void> {\n\trequireGatewayOwner(ctx.event.source);\n\tconst { requestSessionTransfer, SessionTransferError } = await import(\"../core/session-handoff.ts\");\n\tconst { key, bridge } = ctx;\n\tconst current = await bridge.getSession(key);\n\tif (current?.sessionManager?.getSessionFile() === file) {\n\t\tawait ctx.adapter.send(ctx.event.source.chatId, \"This session is already open here.\");\n\t\treturn;\n\t}\n\tif (bridge.getStatus(key).busy) throw new Error(\"Stop the current phone session before switching.\");\n\ttry {\n\t\tawait requestSessionTransfer(file, { timeoutMs: 3000, signal });\n\t} catch (error) {\n\t\tif (!(error instanceof SessionTransferError) || error.code !== \"busy\") throw error;\n\t\tconst choice = await gatewaySelect(\n\t\t\tkey,\n\t\t\t`${error instanceof Error ? error.message : \"Session is in use\"}\\nHow should lunR continue?`,\n\t\t\t[\"Wait until idle\", \"Stop and continue\", \"Cancel\"],\n\t\t\t{ signal },\n\t\t);\n\t\tif (!choice || choice === \"Cancel\") return;\n\t\tif (choice === \"Stop and continue\") await requestSessionTransfer(file, { stop: true, timeoutMs: 30_000, signal });\n\t\telse {\n\t\t\tconst deadline = Date.now() + 120_000;\n\t\t\tfor (;;) {\n\t\t\t\tsignal.throwIfAborted();\n\t\t\t\trequireGatewayOwner(ctx.event.source);\n\t\t\t\ttry {\n\t\t\t\t\tawait requestSessionTransfer(file, { timeoutMs: 3000, signal });\n\t\t\t\t\tbreak;\n\t\t\t\t} catch (retryError) {\n\t\t\t\t\tif (\n\t\t\t\t\t\t!(retryError instanceof SessionTransferError) ||\n\t\t\t\t\t\tretryError.code !== \"busy\" ||\n\t\t\t\t\t\tDate.now() >= deadline\n\t\t\t\t\t)\n\t\t\t\t\t\tthrow retryError;\n\t\t\t\t}\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, 500));\n\t\t\t}\n\t\t}\n\t}\n\tsignal.throwIfAborted();\n\trequireGatewayOwner(ctx.event.source);\n\tawait bridge.switchSession(key, file);\n\tconst session = await bridge.getSession(key);\n\tconst cwd = session?.sessionManager?.getCwd();\n\tbindConversation(key, ctx.event.source, { cwd, owner: ctx.event.source.userId });\n\tconst id = session?.sessionManager?.getSessionId();\n\tif (id && [\"auto\", \"yolo\"].includes(getPermissionMode(id))) {\n\t\tconst requested = getPermissionMode(id);\n\t\tsetPermissionMode(\"manual\", id);\n\t\tsession?.sessionManager?.setPermissionMode?.(\"manual\");\n\t\tif (\n\t\t\t(await gatewaySelect(\n\t\t\t\tkey,\n\t\t\t\t`Continue with ${requested} permissions? Tools can change files and run commands without individual approval.`,\n\t\t\t\t[\"Keep manual\", `Use ${requested}`],\n\t\t\t)) === `Use ${requested}`\n\t\t) {\n\t\t\tsetPermissionMode(requested, id);\n\t\t\tsession?.sessionManager?.setPermissionMode?.(requested);\n\t\t}\n\t}\n\tawait ctx.adapter.send(\n\t\tctx.event.source.chatId,\n\t\t`Continuing ${session?.sessionManager?.getSessionName() ?? basename(file)}\\nProject: ${cwd}\\nSend your next instruction.`,\n\t\t{ threadId: ctx.event.source.threadId },\n\t);\n}\n\nasync function browseSessions(ctx: MobileContext, args: string, quick: boolean): Promise<void> {\n\tconst handoff = await import(\"../core/session-handoff.ts\");\n\tif (quick) {\n\t\tconst candidates = handoff.listHandoffCandidates();\n\t\tif (!candidates.length) {\n\t\t\tconst latest = handoff.latestTuiSession();\n\t\t\tif (!latest)\n\t\t\t\tthrow new Error(\"No recent TUI session found. Use /sessions or mark one in the terminal with /handoff.\");\n\t\t\tawait continueSession(ctx, latest.sessionFile);\n\t\t\treturn;\n\t\t}\n\t\tif (candidates.length === 1) {\n\t\t\tawait continueSession(ctx, candidates[0].sessionFile);\n\t\t\treturn;\n\t\t}\n\t\tawait sessionPicker(\n\t\t\tctx,\n\t\t\tcandidates.map((s) => ({ label: `${s.cwd} · ${basename(s.sessionFile)}`, value: s.sessionFile })),\n\t\t);\n\t\treturn;\n\t}\n\tconst sessions = await SessionManager.listAll();\n\tconst known = new Set(sessions.map((s) => s.path));\n\tfor (const file of handoff.listRegisteredSessionPaths()) {\n\t\tif (known.has(file)) continue;\n\t\ttry {\n\t\t\tconst manager = SessionManager.openReadOnly(file);\n\t\t\tconst header = manager.getHeader();\n\t\t\tif (header)\n\t\t\t\tsessions.push({\n\t\t\t\t\tid: header.id,\n\t\t\t\t\tpath: file,\n\t\t\t\t\tcwd: manager.getCwd(),\n\t\t\t\t\tname: manager.getSessionName(),\n\t\t\t\t\tcreated: new Date(header.timestamp),\n\t\t\t\t\tmodified: statSync(file).mtime,\n\t\t\t\t\tmessageCount: manager.getEntries().length,\n\t\t\t\t\tfirstMessage: \"\",\n\t\t\t\t\tallMessagesText: \"\",\n\t\t\t\t});\n\t\t} catch {}\n\t}\n\tconst query = args.toLowerCase();\n\tconst items = sessions\n\t\t.sort((a, b) => b.modified.getTime() - a.modified.getTime())\n\t\t.filter((s) => !query || `${s.name ?? \"\"} ${s.cwd} ${s.id}`.toLowerCase().includes(query))\n\t\t.map((s) => ({\n\t\t\tlabel: `${s.name || s.firstMessage.slice(0, 35) || s.id.slice(0, 8)} · ${basename(s.cwd)} · ${s.modified.toLocaleDateString()}`,\n\t\t\tvalue: s.path,\n\t\t}));\n\tawait sessionPicker(ctx, items);\n}\n\nasync function sessionPicker(ctx: MobileContext, items: PickerItem[]): Promise<void> {\n\tawait createPicker(\n\t\tctx.adapter,\n\t\tctx.event.source,\n\t\t{\n\t\t\tkind: \"sessions\",\n\t\t\tsessionKey: ctx.key,\n\t\t\tinvokerId: ctx.event.source.userId,\n\t\t\ttitle: \"Choose a session. Use /sessions <search> to filter.\",\n\t\t\titems,\n\t\t\tvalidate: () => isGatewayOwner(ctx.event.source, loadGatewayConfig()),\n\t\t\tresolve: async (item) => {\n\t\t\t\tawait continueSession(ctx, item.value);\n\t\t\t\treturn { done: true, text: \"Session selection finished.\" };\n\t\t\t},\n\t\t},\n\t\t{ threadId: ctx.event.source.threadId },\n\t);\n}\n\nexport async function handleMobileCommand(ctx: MobileContext, command: string, args: string): Promise<boolean> {\n\tif (!MOBILE_COMMANDS.some((c) => c.name === command) && command !== \"resume\") return false;\n\trequireGatewayOwner(ctx.event.source, ctx.cfg);\n\tbindConversation(ctx.key, ctx.event.source, { owner: ctx.event.source.userId });\n\tif (command === \"project\") {\n\t\tawait browseProject(ctx, args.trim() || undefined);\n\t\treturn true;\n\t}\n\tif (command === \"sessions\" || command === \"resume\" || command === \"continue\") {\n\t\tawait browseSessions(ctx, args, command === \"continue\");\n\t\treturn true;\n\t}\n\tconst session = await ctx.bridge.getSession(ctx.key);\n\tif (!session) throw new Error(\"Select a project and send a task, or use /continue first.\");\n\tconst id = session.sessionManager?.getSessionId();\n\tif (command === \"usage\") {\n\t\tconst stats = session.getSessionStats();\n\t\tconst plans = await (await import(\"../core/usage-service.ts\")).getAllPlanUsageResults(\n\t\t\tsession.model?.provider,\n\t\t\tsession.modelRuntime,\n\t\t);\n\t\tawait ctx.adapter.send(\n\t\t\tctx.event.source.chatId,\n\t\t\t[\n\t\t\t\t`Tokens: ${stats.tokens.total} · cost: $${stats.cost.toFixed(4)}`,\n\t\t\t\t...plans.usages.flatMap((p) =>\n\t\t\t\t\tp.windows.map((w) => `${p.provider} ${w.label}: ${w.usedPercent.toFixed(1)}% used`),\n\t\t\t\t),\n\t\t\t\t...plans.errors,\n\t\t\t].join(\"\\n\"),\n\t\t);\n\t\treturn true;\n\t}\n\tif (command === \"processes\") {\n\t\tif (!id) throw new Error(\"No session identity.\");\n\t\tconst processes = await import(\"../core/process-registry.ts\");\n\t\tconst own = processes.list(id);\n\t\tif (args) {\n\t\t\tconst [action, raw] = args.split(/\\s+/);\n\t\t\tconst pid = Number(raw);\n\t\t\tif (action !== \"stop\" || !own.some((p) => p.pid === pid))\n\t\t\t\tthrow new Error(\"Usage: /processes stop <pid> from this session's process list.\");\n\t\t\tprocesses.kill(pid);\n\t\t\tawait ctx.adapter.send(ctx.event.source.chatId, `Stop requested for process ${pid}.`);\n\t\t} else\n\t\t\tawait ctx.adapter.send(\n\t\t\t\tctx.event.source.chatId,\n\t\t\t\town.length\n\t\t\t\t\t? own\n\t\t\t\t\t\t\t.map((p) => `${p.pid} · ${p.status} · ${p.command}`)\n\t\t\t\t\t\t\t.join(\"\\n\")\n\t\t\t\t\t\t\t.slice(0, ctx.adapter.maxMessageLength)\n\t\t\t\t\t: \"No tracked processes.\",\n\t\t\t);\n\t\treturn true;\n\t}\n\tif (command === \"stopall\") {\n\t\tawait ctx.bridge.abort(ctx.key);\n\t\tconst result = id ? await getSubagentCancellation(id)?.stop() : undefined;\n\t\tconst processes = await import(\"../core/process-registry.ts\");\n\t\tconst count = id ? processes.list(id).filter((p) => p.status !== \"exited\").length : 0;\n\t\tif (id) processes.killAll(id);\n\t\tawait ctx.adapter.send(\n\t\t\tctx.event.source.chatId,\n\t\t\t`Stopped the current turn. Requested stop for ${result?.requested ?? 0} background runs and ${count} tracked processes${result?.failed ? `; ${result.failed} requests failed` : \"\"}. Wait for them to exit before transferring.`,\n\t\t);\n\t\treturn true;\n\t}\n\tif (ctx.bridge.getStatus(ctx.key).busy) throw new Error(\"Wait for the current turn or use /stop first.\");\n\tif (command === \"refresh\") {\n\t\tawait session.modelRuntime.refresh();\n\t\tawait ctx.adapter.send(ctx.event.source.chatId, \"Model catalog refreshed. Use /model to choose.\");\n\t\treturn true;\n\t}\n\tif (command === \"fast\") {\n\t\tif (session.model?.provider !== \"openai-codex\" || !session.settingsManager)\n\t\t\tthrow new Error(\"Fast mode is available only for OpenAI Codex subscriptions.\");\n\t\tconst current = session.settingsManager.getOpenAIFastMode();\n\t\tif (args && ![\"on\", \"off\", \"status\"].includes(args)) throw new Error(\"Usage: /fast on|off|status\");\n\t\tif (args !== \"status\") session.settingsManager.setOpenAIFastMode(args ? args === \"on\" : !current);\n\t\tawait ctx.adapter.send(\n\t\t\tctx.event.source.chatId,\n\t\t\t`Fast mode: ${session.settingsManager.getOpenAIFastMode() ? \"on\" : \"off\"}`,\n\t\t);\n\t\treturn true;\n\t}\n\tif (command === \"skill\") {\n\t\tconst names = session.resourceLoader?.getSkills().skills.map((s) => s.name) ?? [];\n\t\tconst [name, ...task] = args.split(/\\s+/).filter(Boolean);\n\t\tconst chosen = name || (await gatewaySelect(ctx.key, \"Choose a skill\", names));\n\t\tif (!chosen) return true;\n\t\tif (!names.includes(chosen)) throw new Error(\"That skill is not loaded for this project.\");\n\t\tctx.event.text = `/skill:${chosen} ${task.join(\" \") || (await gatewayInput(ctx.key, \"What should this skill do?\")) || \"\"}`;\n\t\treturn false;\n\t}\n\tif (command === \"download\") {\n\t\tif (!ctx.adapter.sendFile) throw new Error(\"This platform cannot send files.\");\n\t\tif (!args) throw new Error(\"Usage: /download <project-relative path>\");\n\t\tconst cwd = session.sessionManager?.getCwd();\n\t\tif (!cwd) throw new Error(\"No project selected.\");\n\t\tconst file = resolveWithinRoots(resolve(cwd, args), [cwd]);\n\t\tconst stat = statSync(file);\n\t\tif (!stat.isFile() || stat.size > 8 * 1024 * 1024) throw new Error(\"Choose a file no larger than 8 MB.\");\n\t\tif (\n\t\t\t/(^|[\\\\/])(?:\\.env(?:\\.[^\\\\/]*)?|auth\\.json|gateway\\.json|credentials[^\\\\/]*|id_rsa|id_ed25519)([\\\\/]|$)/i.test(\n\t\t\t\tfile,\n\t\t\t)\n\t\t)\n\t\t\tthrow new Error(\"Credential files cannot be sent through chat.\");\n\t\tconst result = await ctx.adapter.sendFile(ctx.event.source.chatId, file, { threadId: ctx.event.source.threadId });\n\t\tif (!result.success) throw new Error(result.error ?? \"File delivery failed.\");\n\t\treturn true;\n\t}\n\tif (command === \"settings\") {\n\t\tconst choice = await gatewaySelect(ctx.key, \"Session settings\", [\"Model\", \"Thinking\", \"Permissions\"]);\n\t\tif (choice === \"Model\" || choice === \"Thinking\") {\n\t\t\tconst { CHAT_COMMANDS, runChatCommand } = await import(\"./commands.ts\");\n\t\t\tconst cmd = CHAT_COMMANDS.find((c) => c.name === choice.toLowerCase());\n\t\t\tif (cmd)\n\t\t\t\tawait runChatCommand(cmd, {\n\t\t\t\t\t...ctx,\n\t\t\t\t\targs: \"\",\n\t\t\t\t\treply: async (text) => {\n\t\t\t\t\t\tawait ctx.adapter.send(ctx.event.source.chatId, text);\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\treturn true;\n\t\t}\n\t\tif (choice !== \"Permissions\") return true;\n\t}\n\tconst requested =\n\t\tcommand === \"plan\"\n\t\t\t? \"plan\"\n\t\t\t: args || (await gatewaySelect(ctx.key, `Permission mode: ${getPermissionMode(id)}`, [...PERMISSION_MODES]));\n\tif (!requested) return true;\n\tif (!PERMISSION_MODES.includes(requested as (typeof PERMISSION_MODES)[number]))\n\t\tthrow new Error(\"Choose manual, yolo, plan, or auto.\");\n\tsetPermissionMode(requested as (typeof PERMISSION_MODES)[number], id);\n\tsession.sessionManager?.setPermissionMode?.(requested as (typeof PERMISSION_MODES)[number]);\n\tawait ctx.adapter.send(ctx.event.source.chatId, `Permission mode: ${requested}`);\n\tif (command === \"plan\" && args) {\n\t\tctx.event.text = args;\n\t\treturn false;\n\t}\n\treturn true;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"mobile-commands.d.ts","sourceRoot":"","sources":["../../src/gateway/mobile-commands.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,aAAa,EAAqB,MAAM,aAAa,CAAC;AAGpE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEhE,eAAO,MAAM,eAAe;;;GAc3B,CAAC;AAEF,eAAO,MAAM,kBAAkB;;;GAQ9B,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAiBxE;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAazF;AAED,UAAU,aAAa;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,YAAY,CAAC;IACpB,OAAO,EAAE,eAAe,CAAC;IACzB,MAAM,EAAE,UAAU,CAAC;IACnB,GAAG,EAAE,aAAa,CAAC;CACnB;AA8ED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEtD;AA6JD,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAkJ7G","sourcesContent":["import { mkdirSync, readdirSync, realpathSync, statSync } from \"node:fs\";\nimport { basename, dirname, isAbsolute, join, relative, resolve } from \"node:path\";\nimport { getPermissionMode, PERMISSION_MODES, setPermissionMode } from \"../core/permissions.ts\";\nimport { SessionManager } from \"../core/session-manager.ts\";\nimport { getSubagentCancellation } from \"../core/subagent-cancellation.ts\";\nimport { isGatewayOwner, requireGatewayOwner } from \"./authz.ts\";\nimport { createPicker, type PickerItem, type PickerResolveResult } from \"./buttons.ts\";\nimport { type GatewayConfig, loadGatewayConfig } from \"./config.ts\";\nimport { bindConversation, conversationBinding } from \"./conversations.ts\";\nimport { gatewayInput, gatewaySelect } from \"./presenter.ts\";\nimport type { BridgeLike } from \"./router.ts\";\nimport type { MessageEvent, PlatformAdapter } from \"./types.ts\";\n\nexport const MOBILE_COMMANDS = [\n\t{ name: \"project\", description: \"Browse folders and choose a project\" },\n\t{ name: \"continue\", description: \"Continue a handoff or the latest TUI session\" },\n\t{ name: \"sessions\", description: \"Browse saved sessions across projects\" },\n\t{ name: \"mode\", description: \"Choose yolo, auto, or read-only permissions\" },\n\t{ name: \"plan\", description: \"Plan work and approve it from this chat\" },\n\t{ name: \"settings\", description: \"Change this session's model, thinking, or permissions\" },\n\t{ name: \"stopall\", description: \"Stop this session and its background subagents\" },\n\t{ name: \"download\", description: \"Send a file from the current project\" },\n\t{ name: \"usage\", description: \"Show session tokens and subscription usage\" },\n\t{ name: \"processes\", description: \"List or stop this session's shell processes\" },\n\t{ name: \"refresh\", description: \"Refresh available models\" },\n\t{ name: \"skill\", description: \"Choose a skill and send it a task\" },\n\t{ name: \"fast\", description: \"Toggle OpenAI Codex fast mode\" },\n];\n\nexport const FORWARDED_COMMANDS = [\n\t{ name: \"goal\", description: \"Set or manage the session goal\" },\n\t{ name: \"cron\", description: \"Schedule prompts and manage scheduled jobs\" },\n\t{ name: \"run\", description: \"Launch a subagent task\" },\n\t{ name: \"chain\", description: \"Run a sequence of subagent tasks\" },\n\t{ name: \"parallel\", description: \"Run independent subagent tasks\" },\n\t{ name: \"mcp\", description: \"Show configured MCP servers\" },\n\t{ name: \"lsp\", description: \"Show language server status\" },\n];\n\nexport function resolveWithinRoots(path: string, roots: string[]): string {\n\tconst actual = realpathSync(resolve(path));\n\tif (\n\t\t!roots.some((root) => {\n\t\t\ttry {\n\t\t\t\tconst rel = relative(realpathSync(root), actual);\n\t\t\t\treturn (\n\t\t\t\t\trel === \"\" ||\n\t\t\t\t\t(!rel.startsWith(`..${process.platform === \"win32\" ? \"\\\\\" : \"/\"}`) && rel !== \"..\" && !isAbsolute(rel))\n\t\t\t\t);\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t})\n\t)\n\t\tthrow new Error(\"That path is outside the approved folders.\");\n\treturn actual;\n}\n\nexport function createProjectFolder(parent: string, name: string, roots: string[]): string {\n\tif (\n\t\t!name ||\n\t\tname === \".\" ||\n\t\tname === \"..\" ||\n\t\t/[\\\\/\\x00-\\x1f<>:\"|?*]/.test(name) ||\n\t\t/[. ]$/.test(name) ||\n\t\t/^(con|prn|aux|nul|com\\d|lpt\\d)(\\.|$)/i.test(name)\n\t)\n\t\tthrow new Error(\"Use a simple folder name without slashes or reserved characters.\");\n\tconst actual = resolveWithinRoots(parent, roots);\n\tmkdirSync(join(actual, name));\n\treturn resolveWithinRoots(join(actual, name), roots);\n}\n\ninterface MobileContext {\n\tkey: string;\n\tevent: MessageEvent;\n\tadapter: PlatformAdapter;\n\tbridge: BridgeLike;\n\tcfg: GatewayConfig;\n}\n\nasync function browseProject(ctx: MobileContext, initialPath?: string): Promise<void> {\n\tconst { key, event, adapter, bridge } = ctx;\n\tconst roots = ctx.cfg.projectRoots ?? [];\n\tif (!roots.length) throw new Error(\"Add approved project folders with lunr gateway setup first.\");\n\tlet current: string | undefined = initialPath\n\t\t? resolveWithinRoots(initialPath, ctx.cfg.projectRoots ?? [])\n\t\t: undefined;\n\tconst view = (): { title: string; items: PickerItem[] } => {\n\t\tif (!current)\n\t\t\treturn {\n\t\t\t\ttitle: \"Choose a project folder\",\n\t\t\t\titems: [...new Set([...(conversationBinding(key)?.recentProjects ?? []), ...roots])]\n\t\t\t\t\t.filter((p) => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tresolveWithinRoots(p, roots);\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\t.map((p) => ({ label: p, value: p })),\n\t\t\t};\n\t\tcurrent = resolveWithinRoots(current, roots);\n\t\tconst items: PickerItem[] = [\n\t\t\t{ label: \"Use this folder\", value: \"use\" },\n\t\t\t{ label: \"New folder\", value: \"new\" },\n\t\t\t{ label: \"Recent and roots\", value: \"roots\" },\n\t\t];\n\t\ttry {\n\t\t\tconst up = resolveWithinRoots(dirname(current), roots);\n\t\t\tif (up !== current) items.push({ label: \"Up\", value: up });\n\t\t} catch {}\n\t\tfor (const item of readdirSync(current, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {\n\t\t\tif (!item.isDirectory() && !item.isSymbolicLink()) continue;\n\t\t\ttry {\n\t\t\t\tconst path = resolveWithinRoots(join(current, item.name), roots);\n\t\t\t\tif (statSync(path).isDirectory()) items.push({ label: item.name, value: path });\n\t\t\t} catch {}\n\t\t}\n\t\treturn { title: current, items };\n\t};\n\tconst initial = view();\n\tawait createPicker(\n\t\tadapter,\n\t\tevent.source,\n\t\t{\n\t\t\tkind: \"project\",\n\t\t\tsessionKey: key,\n\t\t\tinvokerId: event.source.userId,\n\t\t\t...initial,\n\t\t\tvalidate: () => isGatewayOwner(event.source, loadGatewayConfig()),\n\t\t\tresolve: async (item): Promise<PickerResolveResult> => {\n\t\t\t\trequireGatewayOwner(event.source);\n\t\t\t\tif (item.value === \"use\" && current) {\n\t\t\t\t\tconst cwd = resolveWithinRoots(current, loadGatewayConfig().projectRoots ?? []);\n\t\t\t\t\tif (bridge.getStatus(key).busy)\n\t\t\t\t\t\tthrow new Error(\"Stop the current turn or wait before switching projects.\");\n\t\t\t\t\tawait bridge.reset(key);\n\t\t\t\t\tbindConversation(key, event.source, { cwd, owner: event.source.userId });\n\t\t\t\t\treturn {\n\t\t\t\t\t\tdone: true,\n\t\t\t\t\t\ttext: `Project selected: ${cwd}\\nSend a task to begin. Existing sessions are still available through /sessions.`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (item.value === \"new\" && current) {\n\t\t\t\t\tconst name = await gatewayInput(key, \"New folder name\");\n\t\t\t\t\tif (name) current = createProjectFolder(current, name, loadGatewayConfig().projectRoots ?? []);\n\t\t\t\t} else current = item.value === \"roots\" ? undefined : item.value;\n\t\t\t\treturn { done: false, ...view() };\n\t\t\t},\n\t\t},\n\t\t{ threadId: event.source.threadId },\n\t);\n}\n\nconst transfers = new Map<string, AbortController>();\nexport function cancelMobileTransfer(key: string): void {\n\ttransfers.get(key)?.abort();\n}\n\nasync function continueSession(ctx: MobileContext, file: string): Promise<void> {\n\tif (transfers.has(ctx.key)) throw new Error(\"A session transfer is already pending. Use /stop to cancel it.\");\n\tconst controller = new AbortController();\n\ttransfers.set(ctx.key, controller);\n\ttry {\n\t\tawait performContinue(ctx, file, controller.signal);\n\t} finally {\n\t\ttransfers.delete(ctx.key);\n\t}\n}\n\nasync function performContinue(ctx: MobileContext, file: string, signal: AbortSignal): Promise<void> {\n\trequireGatewayOwner(ctx.event.source);\n\tconst { requestSessionTransfer, SessionTransferError } = await import(\"../core/session-handoff.ts\");\n\tconst { key, bridge } = ctx;\n\tconst current = bridge.peekSession?.(key);\n\tif (current?.sessionManager?.getSessionFile() === file) {\n\t\tawait ctx.adapter.send(ctx.event.source.chatId, \"This session is already open here.\");\n\t\treturn;\n\t}\n\tif (bridge.getStatus(key).busy) throw new Error(\"Stop the current phone session before switching.\");\n\ttry {\n\t\tawait requestSessionTransfer(file, { timeoutMs: 3000, signal });\n\t} catch (error) {\n\t\tif (!(error instanceof SessionTransferError) || ![\"busy\", \"timeout\"].includes(error.code)) throw error;\n\t\tconst choice = await gatewaySelect(\n\t\t\tkey,\n\t\t\t`${error instanceof Error ? error.message : \"Session is in use\"}\\nHow should lunR continue?`,\n\t\t\t[\"Wait until idle\", \"Stop and continue\", \"Cancel\"],\n\t\t\t{ signal },\n\t\t);\n\t\tif (!choice || choice === \"Cancel\") return;\n\t\tif (choice === \"Stop and continue\") await requestSessionTransfer(file, { stop: true, timeoutMs: 30_000, signal });\n\t\telse {\n\t\t\tconst deadline = Date.now() + 120_000;\n\t\t\tfor (;;) {\n\t\t\t\tsignal.throwIfAborted();\n\t\t\t\trequireGatewayOwner(ctx.event.source);\n\t\t\t\ttry {\n\t\t\t\t\tawait requestSessionTransfer(file, { timeoutMs: 3000, signal });\n\t\t\t\t\tbreak;\n\t\t\t\t} catch (retryError) {\n\t\t\t\t\tif (\n\t\t\t\t\t\t!(retryError instanceof SessionTransferError) ||\n\t\t\t\t\t\t![\"busy\", \"timeout\"].includes(retryError.code) ||\n\t\t\t\t\t\tDate.now() >= deadline\n\t\t\t\t\t)\n\t\t\t\t\t\tthrow retryError;\n\t\t\t\t}\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, 500));\n\t\t\t}\n\t\t}\n\t}\n\tsignal.throwIfAborted();\n\trequireGatewayOwner(ctx.event.source);\n\tawait bridge.switchSession(key, file);\n\tconst session = await bridge.getSession(key);\n\tconst cwd = session?.sessionManager?.getCwd();\n\tbindConversation(key, ctx.event.source, { cwd, owner: ctx.event.source.userId });\n\tconst id = session?.sessionManager?.getSessionId();\n\tif (id && [\"auto\", \"yolo\"].includes(getPermissionMode(id))) {\n\t\tconst requested = getPermissionMode(id);\n\t\tsetPermissionMode(\"read-only\", id);\n\t\tsession?.sessionManager?.setPermissionMode?.(\"read-only\");\n\t\tif (\n\t\t\t(await gatewaySelect(\n\t\t\t\tkey,\n\t\t\t\t`Continue with ${requested} permissions? Tools can change files and run commands without individual approval.`,\n\t\t\t\t[\"Keep read-only\", `Use ${requested}`],\n\t\t\t)) === `Use ${requested}`\n\t\t) {\n\t\t\tsetPermissionMode(requested, id);\n\t\t\tsession?.sessionManager?.setPermissionMode?.(requested);\n\t\t}\n\t}\n\tawait ctx.adapter.send(\n\t\tctx.event.source.chatId,\n\t\t`Continuing ${session?.sessionManager?.getSessionName() ?? basename(file)}\\nProject: ${cwd}\\nSend your next instruction.`,\n\t\t{ threadId: ctx.event.source.threadId },\n\t);\n}\n\nasync function browseSessions(ctx: MobileContext, args: string, quick: boolean): Promise<void> {\n\tconst handoff = await import(\"../core/session-handoff.ts\");\n\tif (quick) {\n\t\tconst candidates = handoff.listHandoffCandidates();\n\t\tif (!candidates.length) {\n\t\t\tconst latest = handoff.latestTuiSession();\n\t\t\tif (!latest)\n\t\t\t\tthrow new Error(\"No recent TUI session found. Use /sessions or mark one in the terminal with /handoff.\");\n\t\t\tawait continueSession(ctx, latest.sessionFile);\n\t\t\treturn;\n\t\t}\n\t\tif (candidates.length === 1) {\n\t\t\tawait continueSession(ctx, candidates[0].sessionFile);\n\t\t\treturn;\n\t\t}\n\t\tawait sessionPicker(\n\t\t\tctx,\n\t\t\tcandidates.map((s) => ({ label: `${s.cwd} · ${basename(s.sessionFile)}`, value: s.sessionFile })),\n\t\t);\n\t\treturn;\n\t}\n\tconst sessions = await SessionManager.listAll();\n\tconst known = new Set(sessions.map((s) => s.path));\n\tfor (const file of handoff.listRegisteredSessionPaths()) {\n\t\tif (known.has(file)) continue;\n\t\ttry {\n\t\t\tconst manager = SessionManager.openReadOnly(file);\n\t\t\tconst header = manager.getHeader();\n\t\t\tif (header)\n\t\t\t\tsessions.push({\n\t\t\t\t\tid: header.id,\n\t\t\t\t\tpath: file,\n\t\t\t\t\tcwd: manager.getCwd(),\n\t\t\t\t\tname: manager.getSessionName(),\n\t\t\t\t\tcreated: new Date(header.timestamp),\n\t\t\t\t\tmodified: statSync(file).mtime,\n\t\t\t\t\tmessageCount: manager.getEntries().length,\n\t\t\t\t\tfirstMessage: \"\",\n\t\t\t\t\tallMessagesText: \"\",\n\t\t\t\t});\n\t\t} catch {}\n\t}\n\tconst query = args.toLowerCase();\n\tconst items = sessions\n\t\t.sort((a, b) => b.modified.getTime() - a.modified.getTime())\n\t\t.filter((s) => !query || `${s.name ?? \"\"} ${s.cwd} ${s.id}`.toLowerCase().includes(query))\n\t\t.map((s) => ({\n\t\t\tlabel: `${s.name || s.firstMessage.slice(0, 35) || s.id.slice(0, 8)} · ${basename(s.cwd)} · ${s.modified.toLocaleDateString()}`,\n\t\t\tvalue: s.path,\n\t\t}));\n\tawait sessionPicker(ctx, items);\n}\n\nasync function sessionPicker(ctx: MobileContext, items: PickerItem[]): Promise<void> {\n\tawait createPicker(\n\t\tctx.adapter,\n\t\tctx.event.source,\n\t\t{\n\t\t\tkind: \"sessions\",\n\t\t\tsessionKey: ctx.key,\n\t\t\tinvokerId: ctx.event.source.userId,\n\t\t\ttitle: \"Choose a session. Use /sessions <search> to filter.\",\n\t\t\titems,\n\t\t\tvalidate: () => isGatewayOwner(ctx.event.source, loadGatewayConfig()),\n\t\t\tresolve: async (item) => {\n\t\t\t\tawait continueSession(ctx, item.value);\n\t\t\t\treturn { done: true, text: \"Session selection finished.\" };\n\t\t\t},\n\t\t},\n\t\t{ threadId: ctx.event.source.threadId },\n\t);\n}\n\nexport async function handleMobileCommand(ctx: MobileContext, command: string, args: string): Promise<boolean> {\n\tif (!MOBILE_COMMANDS.some((c) => c.name === command) && command !== \"resume\") return false;\n\trequireGatewayOwner(ctx.event.source, ctx.cfg);\n\tbindConversation(ctx.key, ctx.event.source, { owner: ctx.event.source.userId });\n\tif (command === \"project\") {\n\t\tawait browseProject(ctx, args.trim() || undefined);\n\t\treturn true;\n\t}\n\tif (command === \"sessions\" || command === \"resume\" || command === \"continue\") {\n\t\tawait browseSessions(ctx, args, command === \"continue\");\n\t\treturn true;\n\t}\n\tconst session = await ctx.bridge.getSession(ctx.key);\n\tif (!session) throw new Error(\"Select a project and send a task, or use /continue first.\");\n\tconst id = session.sessionManager?.getSessionId();\n\tif (command === \"usage\") {\n\t\tconst stats = session.getSessionStats();\n\t\tconst plans = await (await import(\"../core/usage-service.ts\")).getAllPlanUsageResults(\n\t\t\tsession.model?.provider,\n\t\t\tsession.modelRuntime,\n\t\t);\n\t\tawait ctx.adapter.send(\n\t\t\tctx.event.source.chatId,\n\t\t\t[\n\t\t\t\t`Tokens: ${stats.tokens.total} · cost: $${stats.cost.toFixed(4)}`,\n\t\t\t\t...plans.usages.flatMap((p) =>\n\t\t\t\t\tp.windows.map((w) => `${p.provider} ${w.label}: ${w.usedPercent.toFixed(1)}% used`),\n\t\t\t\t),\n\t\t\t\t...plans.errors,\n\t\t\t].join(\"\\n\"),\n\t\t);\n\t\treturn true;\n\t}\n\tif (command === \"processes\") {\n\t\tif (!id) throw new Error(\"No session identity.\");\n\t\tconst processes = await import(\"../core/process-registry.ts\");\n\t\tconst own = processes.list(id);\n\t\tif (args) {\n\t\t\tconst [action, raw] = args.split(/\\s+/);\n\t\t\tconst pid = Number(raw);\n\t\t\tif (action !== \"stop\" || !own.some((p) => p.pid === pid))\n\t\t\t\tthrow new Error(\"Usage: /processes stop <pid> from this session's process list.\");\n\t\t\tprocesses.kill(pid);\n\t\t\tawait ctx.adapter.send(ctx.event.source.chatId, `Stop requested for process ${pid}.`);\n\t\t} else\n\t\t\tawait ctx.adapter.send(\n\t\t\t\tctx.event.source.chatId,\n\t\t\t\town.length\n\t\t\t\t\t? own\n\t\t\t\t\t\t\t.map((p) => `${p.pid} · ${p.status} · ${p.command}`)\n\t\t\t\t\t\t\t.join(\"\\n\")\n\t\t\t\t\t\t\t.slice(0, ctx.adapter.maxMessageLength)\n\t\t\t\t\t: \"No tracked processes.\",\n\t\t\t);\n\t\treturn true;\n\t}\n\tif (command === \"stopall\") {\n\t\tawait ctx.bridge.abort(ctx.key);\n\t\tconst result = id ? await getSubagentCancellation(id)?.stop() : undefined;\n\t\tconst processes = await import(\"../core/process-registry.ts\");\n\t\tconst count = id ? processes.list(id).filter((p) => p.status !== \"exited\").length : 0;\n\t\tif (id) processes.killAll(id);\n\t\tawait ctx.adapter.send(\n\t\t\tctx.event.source.chatId,\n\t\t\t`Stopped the current turn. Requested stop for ${result?.requested ?? 0} background runs and ${count} tracked processes${result?.failed ? `; ${result.failed} requests failed` : \"\"}. Wait for them to exit before transferring.`,\n\t\t);\n\t\treturn true;\n\t}\n\tif (ctx.bridge.getStatus(ctx.key).busy) throw new Error(\"Wait for the current turn or use /stop first.\");\n\tif (command === \"refresh\") {\n\t\tawait session.modelRuntime.refresh();\n\t\tawait ctx.adapter.send(ctx.event.source.chatId, \"Model catalog refreshed. Use /model to choose.\");\n\t\treturn true;\n\t}\n\tif (command === \"fast\") {\n\t\tif (session.model?.provider !== \"openai-codex\" || !session.settingsManager)\n\t\t\tthrow new Error(\"Fast mode is available only for OpenAI Codex subscriptions.\");\n\t\tconst current = session.settingsManager.getOpenAIFastMode();\n\t\tif (args && ![\"on\", \"off\", \"status\"].includes(args)) throw new Error(\"Usage: /fast on|off|status\");\n\t\tif (args !== \"status\") session.settingsManager.setOpenAIFastMode(args ? args === \"on\" : !current);\n\t\tawait ctx.adapter.send(\n\t\t\tctx.event.source.chatId,\n\t\t\t`Fast mode: ${session.settingsManager.getOpenAIFastMode() ? \"on\" : \"off\"}`,\n\t\t);\n\t\treturn true;\n\t}\n\tif (command === \"skill\") {\n\t\tconst names = session.resourceLoader?.getSkills().skills.map((s) => s.name) ?? [];\n\t\tconst [name, ...task] = args.split(/\\s+/).filter(Boolean);\n\t\tconst chosen = name || (await gatewaySelect(ctx.key, \"Choose a skill\", names));\n\t\tif (!chosen) return true;\n\t\tif (!names.includes(chosen)) throw new Error(\"That skill is not loaded for this project.\");\n\t\tctx.event.text = `/skill:${chosen} ${task.join(\" \") || (await gatewayInput(ctx.key, \"What should this skill do?\")) || \"\"}`;\n\t\treturn false;\n\t}\n\tif (command === \"download\") {\n\t\tif (!ctx.adapter.sendFile) throw new Error(\"This platform cannot send files.\");\n\t\tif (!args) throw new Error(\"Usage: /download <project-relative path>\");\n\t\tconst cwd = session.sessionManager?.getCwd();\n\t\tif (!cwd) throw new Error(\"No project selected.\");\n\t\tconst file = resolveWithinRoots(resolve(cwd, args), [cwd]);\n\t\tconst stat = statSync(file);\n\t\tif (!stat.isFile() || stat.size > 8 * 1024 * 1024) throw new Error(\"Choose a file no larger than 8 MB.\");\n\t\tif (\n\t\t\t/(^|[\\\\/])(?:\\.env(?:\\.[^\\\\/]*)?|auth\\.json|gateway\\.json|credentials[^\\\\/]*|id_rsa|id_ed25519)([\\\\/]|$)/i.test(\n\t\t\t\tfile,\n\t\t\t)\n\t\t)\n\t\t\tthrow new Error(\"Credential files cannot be sent through chat.\");\n\t\tconst result = await ctx.adapter.sendFile(ctx.event.source.chatId, file, { threadId: ctx.event.source.threadId });\n\t\tif (!result.success) throw new Error(result.error ?? \"File delivery failed.\");\n\t\treturn true;\n\t}\n\tif (command === \"settings\") {\n\t\tconst choice = await gatewaySelect(ctx.key, \"Session settings\", [\"Model\", \"Thinking\", \"Permissions\"]);\n\t\tif (choice === \"Model\" || choice === \"Thinking\") {\n\t\t\tconst { CHAT_COMMANDS, runChatCommand } = await import(\"./commands.ts\");\n\t\t\tconst cmd = CHAT_COMMANDS.find((c) => c.name === choice.toLowerCase());\n\t\t\tif (cmd)\n\t\t\t\tawait runChatCommand(cmd, {\n\t\t\t\t\t...ctx,\n\t\t\t\t\targs: \"\",\n\t\t\t\t\treply: async (text) => {\n\t\t\t\t\t\tawait ctx.adapter.send(ctx.event.source.chatId, text);\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\treturn true;\n\t\t}\n\t\tif (choice !== \"Permissions\") return true;\n\t}\n\tconst requested =\n\t\tcommand === \"plan\"\n\t\t\t? \"read-only\"\n\t\t\t: args || (await gatewaySelect(ctx.key, `Permission mode: ${getPermissionMode(id)}`, [...PERMISSION_MODES]));\n\tif (!requested) return true;\n\tconst mode = requested === \"read\" ? \"read-only\" : requested;\n\tif (!PERMISSION_MODES.includes(mode as (typeof PERMISSION_MODES)[number]))\n\t\tthrow new Error(\"Choose yolo, auto, or read-only.\");\n\tsetPermissionMode(mode as (typeof PERMISSION_MODES)[number], id);\n\tsession.sessionManager?.setPermissionMode?.(mode as (typeof PERMISSION_MODES)[number]);\n\tawait ctx.adapter.send(ctx.event.source.chatId, `Permission mode: ${mode}`);\n\tif (command === \"plan\" && args) {\n\t\tctx.event.text = `Create a plan for: ${args}`;\n\t\treturn false;\n\t}\n\treturn true;\n}\n"]}
|
|
@@ -12,7 +12,7 @@ export const MOBILE_COMMANDS = [
|
|
|
12
12
|
{ name: "project", description: "Browse folders and choose a project" },
|
|
13
13
|
{ name: "continue", description: "Continue a handoff or the latest TUI session" },
|
|
14
14
|
{ name: "sessions", description: "Browse saved sessions across projects" },
|
|
15
|
-
{ name: "mode", description: "Choose
|
|
15
|
+
{ name: "mode", description: "Choose yolo, auto, or read-only permissions" },
|
|
16
16
|
{ name: "plan", description: "Plan work and approve it from this chat" },
|
|
17
17
|
{ name: "settings", description: "Change this session's model, thinking, or permissions" },
|
|
18
18
|
{ name: "stopall", description: "Stop this session and its background subagents" },
|
|
@@ -158,7 +158,7 @@ async function performContinue(ctx, file, signal) {
|
|
|
158
158
|
requireGatewayOwner(ctx.event.source);
|
|
159
159
|
const { requestSessionTransfer, SessionTransferError } = await import("../core/session-handoff.js");
|
|
160
160
|
const { key, bridge } = ctx;
|
|
161
|
-
const current =
|
|
161
|
+
const current = bridge.peekSession?.(key);
|
|
162
162
|
if (current?.sessionManager?.getSessionFile() === file) {
|
|
163
163
|
await ctx.adapter.send(ctx.event.source.chatId, "This session is already open here.");
|
|
164
164
|
return;
|
|
@@ -169,7 +169,7 @@ async function performContinue(ctx, file, signal) {
|
|
|
169
169
|
await requestSessionTransfer(file, { timeoutMs: 3000, signal });
|
|
170
170
|
}
|
|
171
171
|
catch (error) {
|
|
172
|
-
if (!(error instanceof SessionTransferError) || error.code
|
|
172
|
+
if (!(error instanceof SessionTransferError) || !["busy", "timeout"].includes(error.code))
|
|
173
173
|
throw error;
|
|
174
174
|
const choice = await gatewaySelect(key, `${error instanceof Error ? error.message : "Session is in use"}\nHow should lunR continue?`, ["Wait until idle", "Stop and continue", "Cancel"], { signal });
|
|
175
175
|
if (!choice || choice === "Cancel")
|
|
@@ -187,7 +187,7 @@ async function performContinue(ctx, file, signal) {
|
|
|
187
187
|
}
|
|
188
188
|
catch (retryError) {
|
|
189
189
|
if (!(retryError instanceof SessionTransferError) ||
|
|
190
|
-
retryError.code
|
|
190
|
+
!["busy", "timeout"].includes(retryError.code) ||
|
|
191
191
|
Date.now() >= deadline)
|
|
192
192
|
throw retryError;
|
|
193
193
|
}
|
|
@@ -204,9 +204,9 @@ async function performContinue(ctx, file, signal) {
|
|
|
204
204
|
const id = session?.sessionManager?.getSessionId();
|
|
205
205
|
if (id && ["auto", "yolo"].includes(getPermissionMode(id))) {
|
|
206
206
|
const requested = getPermissionMode(id);
|
|
207
|
-
setPermissionMode("
|
|
208
|
-
session?.sessionManager?.setPermissionMode?.("
|
|
209
|
-
if ((await gatewaySelect(key, `Continue with ${requested} permissions? Tools can change files and run commands without individual approval.`, ["Keep
|
|
207
|
+
setPermissionMode("read-only", id);
|
|
208
|
+
session?.sessionManager?.setPermissionMode?.("read-only");
|
|
209
|
+
if ((await gatewaySelect(key, `Continue with ${requested} permissions? Tools can change files and run commands without individual approval.`, ["Keep read-only", `Use ${requested}`])) === `Use ${requested}`) {
|
|
210
210
|
setPermissionMode(requested, id);
|
|
211
211
|
session?.sessionManager?.setPermissionMode?.(requested);
|
|
212
212
|
}
|
|
@@ -404,17 +404,18 @@ export async function handleMobileCommand(ctx, command, args) {
|
|
|
404
404
|
return true;
|
|
405
405
|
}
|
|
406
406
|
const requested = command === "plan"
|
|
407
|
-
? "
|
|
407
|
+
? "read-only"
|
|
408
408
|
: args || (await gatewaySelect(ctx.key, `Permission mode: ${getPermissionMode(id)}`, [...PERMISSION_MODES]));
|
|
409
409
|
if (!requested)
|
|
410
410
|
return true;
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
411
|
+
const mode = requested === "read" ? "read-only" : requested;
|
|
412
|
+
if (!PERMISSION_MODES.includes(mode))
|
|
413
|
+
throw new Error("Choose yolo, auto, or read-only.");
|
|
414
|
+
setPermissionMode(mode, id);
|
|
415
|
+
session.sessionManager?.setPermissionMode?.(mode);
|
|
416
|
+
await ctx.adapter.send(ctx.event.source.chatId, `Permission mode: ${mode}`);
|
|
416
417
|
if (command === "plan" && args) {
|
|
417
|
-
ctx.event.text = args
|
|
418
|
+
ctx.event.text = `Create a plan for: ${args}`;
|
|
418
419
|
return false;
|
|
419
420
|
}
|
|
420
421
|
return true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mobile-commands.js","sourceRoot":"","sources":["../../src/gateway/mobile-commands.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdirSync, readdirSync, realpathSync, statSync } from \"node:fs\";\nimport { basename, dirname, isAbsolute, join, relative, resolve } from \"node:path\";\nimport { getPermissionMode, PERMISSION_MODES, setPermissionMode } from \"../core/permissions.ts\";\nimport { SessionManager } from \"../core/session-manager.ts\";\nimport { getSubagentCancellation } from \"../core/subagent-cancellation.ts\";\nimport { isGatewayOwner, requireGatewayOwner } from \"./authz.ts\";\nimport { createPicker, type PickerItem, type PickerResolveResult } from \"./buttons.ts\";\nimport { type GatewayConfig, loadGatewayConfig } from \"./config.ts\";\nimport { bindConversation, conversationBinding } from \"./conversations.ts\";\nimport { gatewayInput, gatewaySelect } from \"./presenter.ts\";\nimport type { BridgeLike } from \"./router.ts\";\nimport type { MessageEvent, PlatformAdapter } from \"./types.ts\";\n\nexport const MOBILE_COMMANDS = [\n\t{ name: \"project\", description: \"Browse folders and choose a project\" },\n\t{ name: \"continue\", description: \"Continue a handoff or the latest TUI session\" },\n\t{ name: \"sessions\", description: \"Browse saved sessions across projects\" },\n\t{ name: \"mode\", description: \"Choose manual, yolo, plan, or auto permissions\" },\n\t{ name: \"plan\", description: \"Plan work and approve it from this chat\" },\n\t{ name: \"settings\", description: \"Change this session's model, thinking, or permissions\" },\n\t{ name: \"stopall\", description: \"Stop this session and its background subagents\" },\n\t{ name: \"download\", description: \"Send a file from the current project\" },\n\t{ name: \"usage\", description: \"Show session tokens and subscription usage\" },\n\t{ name: \"processes\", description: \"List or stop this session's shell processes\" },\n\t{ name: \"refresh\", description: \"Refresh available models\" },\n\t{ name: \"skill\", description: \"Choose a skill and send it a task\" },\n\t{ name: \"fast\", description: \"Toggle OpenAI Codex fast mode\" },\n];\n\nexport const FORWARDED_COMMANDS = [\n\t{ name: \"goal\", description: \"Set or manage the session goal\" },\n\t{ name: \"cron\", description: \"Schedule prompts and manage scheduled jobs\" },\n\t{ name: \"run\", description: \"Launch a subagent task\" },\n\t{ name: \"chain\", description: \"Run a sequence of subagent tasks\" },\n\t{ name: \"parallel\", description: \"Run independent subagent tasks\" },\n\t{ name: \"mcp\", description: \"Show configured MCP servers\" },\n\t{ name: \"lsp\", description: \"Show language server status\" },\n];\n\nexport function resolveWithinRoots(path: string, roots: string[]): string {\n\tconst actual = realpathSync(resolve(path));\n\tif (\n\t\t!roots.some((root) => {\n\t\t\ttry {\n\t\t\t\tconst rel = relative(realpathSync(root), actual);\n\t\t\t\treturn (\n\t\t\t\t\trel === \"\" ||\n\t\t\t\t\t(!rel.startsWith(`..${process.platform === \"win32\" ? \"\\\\\" : \"/\"}`) && rel !== \"..\" && !isAbsolute(rel))\n\t\t\t\t);\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t})\n\t)\n\t\tthrow new Error(\"That path is outside the approved folders.\");\n\treturn actual;\n}\n\nexport function createProjectFolder(parent: string, name: string, roots: string[]): string {\n\tif (\n\t\t!name ||\n\t\tname === \".\" ||\n\t\tname === \"..\" ||\n\t\t/[\\\\/\\x00-\\x1f<>:\"|?*]/.test(name) ||\n\t\t/[. ]$/.test(name) ||\n\t\t/^(con|prn|aux|nul|com\\d|lpt\\d)(\\.|$)/i.test(name)\n\t)\n\t\tthrow new Error(\"Use a simple folder name without slashes or reserved characters.\");\n\tconst actual = resolveWithinRoots(parent, roots);\n\tmkdirSync(join(actual, name));\n\treturn resolveWithinRoots(join(actual, name), roots);\n}\n\ninterface MobileContext {\n\tkey: string;\n\tevent: MessageEvent;\n\tadapter: PlatformAdapter;\n\tbridge: BridgeLike;\n\tcfg: GatewayConfig;\n}\n\nasync function browseProject(ctx: MobileContext, initialPath?: string): Promise<void> {\n\tconst { key, event, adapter, bridge } = ctx;\n\tconst roots = ctx.cfg.projectRoots ?? [];\n\tif (!roots.length) throw new Error(\"Add approved project folders with lunr gateway setup first.\");\n\tlet current: string | undefined = initialPath\n\t\t? resolveWithinRoots(initialPath, ctx.cfg.projectRoots ?? [])\n\t\t: undefined;\n\tconst view = (): { title: string; items: PickerItem[] } => {\n\t\tif (!current)\n\t\t\treturn {\n\t\t\t\ttitle: \"Choose a project folder\",\n\t\t\t\titems: [...new Set([...(conversationBinding(key)?.recentProjects ?? []), ...roots])]\n\t\t\t\t\t.filter((p) => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tresolveWithinRoots(p, roots);\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\t.map((p) => ({ label: p, value: p })),\n\t\t\t};\n\t\tcurrent = resolveWithinRoots(current, roots);\n\t\tconst items: PickerItem[] = [\n\t\t\t{ label: \"Use this folder\", value: \"use\" },\n\t\t\t{ label: \"New folder\", value: \"new\" },\n\t\t\t{ label: \"Recent and roots\", value: \"roots\" },\n\t\t];\n\t\ttry {\n\t\t\tconst up = resolveWithinRoots(dirname(current), roots);\n\t\t\tif (up !== current) items.push({ label: \"Up\", value: up });\n\t\t} catch {}\n\t\tfor (const item of readdirSync(current, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {\n\t\t\tif (!item.isDirectory() && !item.isSymbolicLink()) continue;\n\t\t\ttry {\n\t\t\t\tconst path = resolveWithinRoots(join(current, item.name), roots);\n\t\t\t\tif (statSync(path).isDirectory()) items.push({ label: item.name, value: path });\n\t\t\t} catch {}\n\t\t}\n\t\treturn { title: current, items };\n\t};\n\tconst initial = view();\n\tawait createPicker(\n\t\tadapter,\n\t\tevent.source,\n\t\t{\n\t\t\tkind: \"project\",\n\t\t\tsessionKey: key,\n\t\t\tinvokerId: event.source.userId,\n\t\t\t...initial,\n\t\t\tvalidate: () => isGatewayOwner(event.source, loadGatewayConfig()),\n\t\t\tresolve: async (item): Promise<PickerResolveResult> => {\n\t\t\t\trequireGatewayOwner(event.source);\n\t\t\t\tif (item.value === \"use\" && current) {\n\t\t\t\t\tconst cwd = resolveWithinRoots(current, loadGatewayConfig().projectRoots ?? []);\n\t\t\t\t\tif (bridge.getStatus(key).busy)\n\t\t\t\t\t\tthrow new Error(\"Stop the current turn or wait before switching projects.\");\n\t\t\t\t\tawait bridge.reset(key);\n\t\t\t\t\tbindConversation(key, event.source, { cwd, owner: event.source.userId });\n\t\t\t\t\treturn {\n\t\t\t\t\t\tdone: true,\n\t\t\t\t\t\ttext: `Project selected: ${cwd}\\nSend a task to begin. Existing sessions are still available through /sessions.`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (item.value === \"new\" && current) {\n\t\t\t\t\tconst name = await gatewayInput(key, \"New folder name\");\n\t\t\t\t\tif (name) current = createProjectFolder(current, name, loadGatewayConfig().projectRoots ?? []);\n\t\t\t\t} else current = item.value === \"roots\" ? undefined : item.value;\n\t\t\t\treturn { done: false, ...view() };\n\t\t\t},\n\t\t},\n\t\t{ threadId: event.source.threadId },\n\t);\n}\n\nconst transfers = new Map<string, AbortController>();\nexport function cancelMobileTransfer(key: string): void {\n\ttransfers.get(key)?.abort();\n}\n\nasync function continueSession(ctx: MobileContext, file: string): Promise<void> {\n\tif (transfers.has(ctx.key)) throw new Error(\"A session transfer is already pending. Use /stop to cancel it.\");\n\tconst controller = new AbortController();\n\ttransfers.set(ctx.key, controller);\n\ttry {\n\t\tawait performContinue(ctx, file, controller.signal);\n\t} finally {\n\t\ttransfers.delete(ctx.key);\n\t}\n}\n\nasync function performContinue(ctx: MobileContext, file: string, signal: AbortSignal): Promise<void> {\n\trequireGatewayOwner(ctx.event.source);\n\tconst { requestSessionTransfer, SessionTransferError } = await import(\"../core/session-handoff.ts\");\n\tconst { key, bridge } = ctx;\n\tconst current = await bridge.getSession(key);\n\tif (current?.sessionManager?.getSessionFile() === file) {\n\t\tawait ctx.adapter.send(ctx.event.source.chatId, \"This session is already open here.\");\n\t\treturn;\n\t}\n\tif (bridge.getStatus(key).busy) throw new Error(\"Stop the current phone session before switching.\");\n\ttry {\n\t\tawait requestSessionTransfer(file, { timeoutMs: 3000, signal });\n\t} catch (error) {\n\t\tif (!(error instanceof SessionTransferError) || error.code !== \"busy\") throw error;\n\t\tconst choice = await gatewaySelect(\n\t\t\tkey,\n\t\t\t`${error instanceof Error ? error.message : \"Session is in use\"}\\nHow should lunR continue?`,\n\t\t\t[\"Wait until idle\", \"Stop and continue\", \"Cancel\"],\n\t\t\t{ signal },\n\t\t);\n\t\tif (!choice || choice === \"Cancel\") return;\n\t\tif (choice === \"Stop and continue\") await requestSessionTransfer(file, { stop: true, timeoutMs: 30_000, signal });\n\t\telse {\n\t\t\tconst deadline = Date.now() + 120_000;\n\t\t\tfor (;;) {\n\t\t\t\tsignal.throwIfAborted();\n\t\t\t\trequireGatewayOwner(ctx.event.source);\n\t\t\t\ttry {\n\t\t\t\t\tawait requestSessionTransfer(file, { timeoutMs: 3000, signal });\n\t\t\t\t\tbreak;\n\t\t\t\t} catch (retryError) {\n\t\t\t\t\tif (\n\t\t\t\t\t\t!(retryError instanceof SessionTransferError) ||\n\t\t\t\t\t\tretryError.code !== \"busy\" ||\n\t\t\t\t\t\tDate.now() >= deadline\n\t\t\t\t\t)\n\t\t\t\t\t\tthrow retryError;\n\t\t\t\t}\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, 500));\n\t\t\t}\n\t\t}\n\t}\n\tsignal.throwIfAborted();\n\trequireGatewayOwner(ctx.event.source);\n\tawait bridge.switchSession(key, file);\n\tconst session = await bridge.getSession(key);\n\tconst cwd = session?.sessionManager?.getCwd();\n\tbindConversation(key, ctx.event.source, { cwd, owner: ctx.event.source.userId });\n\tconst id = session?.sessionManager?.getSessionId();\n\tif (id && [\"auto\", \"yolo\"].includes(getPermissionMode(id))) {\n\t\tconst requested = getPermissionMode(id);\n\t\tsetPermissionMode(\"manual\", id);\n\t\tsession?.sessionManager?.setPermissionMode?.(\"manual\");\n\t\tif (\n\t\t\t(await gatewaySelect(\n\t\t\t\tkey,\n\t\t\t\t`Continue with ${requested} permissions? Tools can change files and run commands without individual approval.`,\n\t\t\t\t[\"Keep manual\", `Use ${requested}`],\n\t\t\t)) === `Use ${requested}`\n\t\t) {\n\t\t\tsetPermissionMode(requested, id);\n\t\t\tsession?.sessionManager?.setPermissionMode?.(requested);\n\t\t}\n\t}\n\tawait ctx.adapter.send(\n\t\tctx.event.source.chatId,\n\t\t`Continuing ${session?.sessionManager?.getSessionName() ?? basename(file)}\\nProject: ${cwd}\\nSend your next instruction.`,\n\t\t{ threadId: ctx.event.source.threadId },\n\t);\n}\n\nasync function browseSessions(ctx: MobileContext, args: string, quick: boolean): Promise<void> {\n\tconst handoff = await import(\"../core/session-handoff.ts\");\n\tif (quick) {\n\t\tconst candidates = handoff.listHandoffCandidates();\n\t\tif (!candidates.length) {\n\t\t\tconst latest = handoff.latestTuiSession();\n\t\t\tif (!latest)\n\t\t\t\tthrow new Error(\"No recent TUI session found. Use /sessions or mark one in the terminal with /handoff.\");\n\t\t\tawait continueSession(ctx, latest.sessionFile);\n\t\t\treturn;\n\t\t}\n\t\tif (candidates.length === 1) {\n\t\t\tawait continueSession(ctx, candidates[0].sessionFile);\n\t\t\treturn;\n\t\t}\n\t\tawait sessionPicker(\n\t\t\tctx,\n\t\t\tcandidates.map((s) => ({ label: `${s.cwd} · ${basename(s.sessionFile)}`, value: s.sessionFile })),\n\t\t);\n\t\treturn;\n\t}\n\tconst sessions = await SessionManager.listAll();\n\tconst known = new Set(sessions.map((s) => s.path));\n\tfor (const file of handoff.listRegisteredSessionPaths()) {\n\t\tif (known.has(file)) continue;\n\t\ttry {\n\t\t\tconst manager = SessionManager.openReadOnly(file);\n\t\t\tconst header = manager.getHeader();\n\t\t\tif (header)\n\t\t\t\tsessions.push({\n\t\t\t\t\tid: header.id,\n\t\t\t\t\tpath: file,\n\t\t\t\t\tcwd: manager.getCwd(),\n\t\t\t\t\tname: manager.getSessionName(),\n\t\t\t\t\tcreated: new Date(header.timestamp),\n\t\t\t\t\tmodified: statSync(file).mtime,\n\t\t\t\t\tmessageCount: manager.getEntries().length,\n\t\t\t\t\tfirstMessage: \"\",\n\t\t\t\t\tallMessagesText: \"\",\n\t\t\t\t});\n\t\t} catch {}\n\t}\n\tconst query = args.toLowerCase();\n\tconst items = sessions\n\t\t.sort((a, b) => b.modified.getTime() - a.modified.getTime())\n\t\t.filter((s) => !query || `${s.name ?? \"\"} ${s.cwd} ${s.id}`.toLowerCase().includes(query))\n\t\t.map((s) => ({\n\t\t\tlabel: `${s.name || s.firstMessage.slice(0, 35) || s.id.slice(0, 8)} · ${basename(s.cwd)} · ${s.modified.toLocaleDateString()}`,\n\t\t\tvalue: s.path,\n\t\t}));\n\tawait sessionPicker(ctx, items);\n}\n\nasync function sessionPicker(ctx: MobileContext, items: PickerItem[]): Promise<void> {\n\tawait createPicker(\n\t\tctx.adapter,\n\t\tctx.event.source,\n\t\t{\n\t\t\tkind: \"sessions\",\n\t\t\tsessionKey: ctx.key,\n\t\t\tinvokerId: ctx.event.source.userId,\n\t\t\ttitle: \"Choose a session. Use /sessions <search> to filter.\",\n\t\t\titems,\n\t\t\tvalidate: () => isGatewayOwner(ctx.event.source, loadGatewayConfig()),\n\t\t\tresolve: async (item) => {\n\t\t\t\tawait continueSession(ctx, item.value);\n\t\t\t\treturn { done: true, text: \"Session selection finished.\" };\n\t\t\t},\n\t\t},\n\t\t{ threadId: ctx.event.source.threadId },\n\t);\n}\n\nexport async function handleMobileCommand(ctx: MobileContext, command: string, args: string): Promise<boolean> {\n\tif (!MOBILE_COMMANDS.some((c) => c.name === command) && command !== \"resume\") return false;\n\trequireGatewayOwner(ctx.event.source, ctx.cfg);\n\tbindConversation(ctx.key, ctx.event.source, { owner: ctx.event.source.userId });\n\tif (command === \"project\") {\n\t\tawait browseProject(ctx, args.trim() || undefined);\n\t\treturn true;\n\t}\n\tif (command === \"sessions\" || command === \"resume\" || command === \"continue\") {\n\t\tawait browseSessions(ctx, args, command === \"continue\");\n\t\treturn true;\n\t}\n\tconst session = await ctx.bridge.getSession(ctx.key);\n\tif (!session) throw new Error(\"Select a project and send a task, or use /continue first.\");\n\tconst id = session.sessionManager?.getSessionId();\n\tif (command === \"usage\") {\n\t\tconst stats = session.getSessionStats();\n\t\tconst plans = await (await import(\"../core/usage-service.ts\")).getAllPlanUsageResults(\n\t\t\tsession.model?.provider,\n\t\t\tsession.modelRuntime,\n\t\t);\n\t\tawait ctx.adapter.send(\n\t\t\tctx.event.source.chatId,\n\t\t\t[\n\t\t\t\t`Tokens: ${stats.tokens.total} · cost: $${stats.cost.toFixed(4)}`,\n\t\t\t\t...plans.usages.flatMap((p) =>\n\t\t\t\t\tp.windows.map((w) => `${p.provider} ${w.label}: ${w.usedPercent.toFixed(1)}% used`),\n\t\t\t\t),\n\t\t\t\t...plans.errors,\n\t\t\t].join(\"\\n\"),\n\t\t);\n\t\treturn true;\n\t}\n\tif (command === \"processes\") {\n\t\tif (!id) throw new Error(\"No session identity.\");\n\t\tconst processes = await import(\"../core/process-registry.ts\");\n\t\tconst own = processes.list(id);\n\t\tif (args) {\n\t\t\tconst [action, raw] = args.split(/\\s+/);\n\t\t\tconst pid = Number(raw);\n\t\t\tif (action !== \"stop\" || !own.some((p) => p.pid === pid))\n\t\t\t\tthrow new Error(\"Usage: /processes stop <pid> from this session's process list.\");\n\t\t\tprocesses.kill(pid);\n\t\t\tawait ctx.adapter.send(ctx.event.source.chatId, `Stop requested for process ${pid}.`);\n\t\t} else\n\t\t\tawait ctx.adapter.send(\n\t\t\t\tctx.event.source.chatId,\n\t\t\t\town.length\n\t\t\t\t\t? own\n\t\t\t\t\t\t\t.map((p) => `${p.pid} · ${p.status} · ${p.command}`)\n\t\t\t\t\t\t\t.join(\"\\n\")\n\t\t\t\t\t\t\t.slice(0, ctx.adapter.maxMessageLength)\n\t\t\t\t\t: \"No tracked processes.\",\n\t\t\t);\n\t\treturn true;\n\t}\n\tif (command === \"stopall\") {\n\t\tawait ctx.bridge.abort(ctx.key);\n\t\tconst result = id ? await getSubagentCancellation(id)?.stop() : undefined;\n\t\tconst processes = await import(\"../core/process-registry.ts\");\n\t\tconst count = id ? processes.list(id).filter((p) => p.status !== \"exited\").length : 0;\n\t\tif (id) processes.killAll(id);\n\t\tawait ctx.adapter.send(\n\t\t\tctx.event.source.chatId,\n\t\t\t`Stopped the current turn. Requested stop for ${result?.requested ?? 0} background runs and ${count} tracked processes${result?.failed ? `; ${result.failed} requests failed` : \"\"}. Wait for them to exit before transferring.`,\n\t\t);\n\t\treturn true;\n\t}\n\tif (ctx.bridge.getStatus(ctx.key).busy) throw new Error(\"Wait for the current turn or use /stop first.\");\n\tif (command === \"refresh\") {\n\t\tawait session.modelRuntime.refresh();\n\t\tawait ctx.adapter.send(ctx.event.source.chatId, \"Model catalog refreshed. Use /model to choose.\");\n\t\treturn true;\n\t}\n\tif (command === \"fast\") {\n\t\tif (session.model?.provider !== \"openai-codex\" || !session.settingsManager)\n\t\t\tthrow new Error(\"Fast mode is available only for OpenAI Codex subscriptions.\");\n\t\tconst current = session.settingsManager.getOpenAIFastMode();\n\t\tif (args && ![\"on\", \"off\", \"status\"].includes(args)) throw new Error(\"Usage: /fast on|off|status\");\n\t\tif (args !== \"status\") session.settingsManager.setOpenAIFastMode(args ? args === \"on\" : !current);\n\t\tawait ctx.adapter.send(\n\t\t\tctx.event.source.chatId,\n\t\t\t`Fast mode: ${session.settingsManager.getOpenAIFastMode() ? \"on\" : \"off\"}`,\n\t\t);\n\t\treturn true;\n\t}\n\tif (command === \"skill\") {\n\t\tconst names = session.resourceLoader?.getSkills().skills.map((s) => s.name) ?? [];\n\t\tconst [name, ...task] = args.split(/\\s+/).filter(Boolean);\n\t\tconst chosen = name || (await gatewaySelect(ctx.key, \"Choose a skill\", names));\n\t\tif (!chosen) return true;\n\t\tif (!names.includes(chosen)) throw new Error(\"That skill is not loaded for this project.\");\n\t\tctx.event.text = `/skill:${chosen} ${task.join(\" \") || (await gatewayInput(ctx.key, \"What should this skill do?\")) || \"\"}`;\n\t\treturn false;\n\t}\n\tif (command === \"download\") {\n\t\tif (!ctx.adapter.sendFile) throw new Error(\"This platform cannot send files.\");\n\t\tif (!args) throw new Error(\"Usage: /download <project-relative path>\");\n\t\tconst cwd = session.sessionManager?.getCwd();\n\t\tif (!cwd) throw new Error(\"No project selected.\");\n\t\tconst file = resolveWithinRoots(resolve(cwd, args), [cwd]);\n\t\tconst stat = statSync(file);\n\t\tif (!stat.isFile() || stat.size > 8 * 1024 * 1024) throw new Error(\"Choose a file no larger than 8 MB.\");\n\t\tif (\n\t\t\t/(^|[\\\\/])(?:\\.env(?:\\.[^\\\\/]*)?|auth\\.json|gateway\\.json|credentials[^\\\\/]*|id_rsa|id_ed25519)([\\\\/]|$)/i.test(\n\t\t\t\tfile,\n\t\t\t)\n\t\t)\n\t\t\tthrow new Error(\"Credential files cannot be sent through chat.\");\n\t\tconst result = await ctx.adapter.sendFile(ctx.event.source.chatId, file, { threadId: ctx.event.source.threadId });\n\t\tif (!result.success) throw new Error(result.error ?? \"File delivery failed.\");\n\t\treturn true;\n\t}\n\tif (command === \"settings\") {\n\t\tconst choice = await gatewaySelect(ctx.key, \"Session settings\", [\"Model\", \"Thinking\", \"Permissions\"]);\n\t\tif (choice === \"Model\" || choice === \"Thinking\") {\n\t\t\tconst { CHAT_COMMANDS, runChatCommand } = await import(\"./commands.ts\");\n\t\t\tconst cmd = CHAT_COMMANDS.find((c) => c.name === choice.toLowerCase());\n\t\t\tif (cmd)\n\t\t\t\tawait runChatCommand(cmd, {\n\t\t\t\t\t...ctx,\n\t\t\t\t\targs: \"\",\n\t\t\t\t\treply: async (text) => {\n\t\t\t\t\t\tawait ctx.adapter.send(ctx.event.source.chatId, text);\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\treturn true;\n\t\t}\n\t\tif (choice !== \"Permissions\") return true;\n\t}\n\tconst requested =\n\t\tcommand === \"plan\"\n\t\t\t? \"plan\"\n\t\t\t: args || (await gatewaySelect(ctx.key, `Permission mode: ${getPermissionMode(id)}`, [...PERMISSION_MODES]));\n\tif (!requested) return true;\n\tif (!PERMISSION_MODES.includes(requested as (typeof PERMISSION_MODES)[number]))\n\t\tthrow new Error(\"Choose manual, yolo, plan, or auto.\");\n\tsetPermissionMode(requested as (typeof PERMISSION_MODES)[number], id);\n\tsession.sessionManager?.setPermissionMode?.(requested as (typeof PERMISSION_MODES)[number]);\n\tawait ctx.adapter.send(ctx.event.source.chatId, `Permission mode: ${requested}`);\n\tif (command === \"plan\" && args) {\n\t\tctx.event.text = args;\n\t\treturn false;\n\t}\n\treturn true;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"mobile-commands.js","sourceRoot":"","sources":["../../src/gateway/mobile-commands.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdirSync, readdirSync, realpathSync, statSync } from \"node:fs\";\nimport { basename, dirname, isAbsolute, join, relative, resolve } from \"node:path\";\nimport { getPermissionMode, PERMISSION_MODES, setPermissionMode } from \"../core/permissions.ts\";\nimport { SessionManager } from \"../core/session-manager.ts\";\nimport { getSubagentCancellation } from \"../core/subagent-cancellation.ts\";\nimport { isGatewayOwner, requireGatewayOwner } from \"./authz.ts\";\nimport { createPicker, type PickerItem, type PickerResolveResult } from \"./buttons.ts\";\nimport { type GatewayConfig, loadGatewayConfig } from \"./config.ts\";\nimport { bindConversation, conversationBinding } from \"./conversations.ts\";\nimport { gatewayInput, gatewaySelect } from \"./presenter.ts\";\nimport type { BridgeLike } from \"./router.ts\";\nimport type { MessageEvent, PlatformAdapter } from \"./types.ts\";\n\nexport const MOBILE_COMMANDS = [\n\t{ name: \"project\", description: \"Browse folders and choose a project\" },\n\t{ name: \"continue\", description: \"Continue a handoff or the latest TUI session\" },\n\t{ name: \"sessions\", description: \"Browse saved sessions across projects\" },\n\t{ name: \"mode\", description: \"Choose yolo, auto, or read-only permissions\" },\n\t{ name: \"plan\", description: \"Plan work and approve it from this chat\" },\n\t{ name: \"settings\", description: \"Change this session's model, thinking, or permissions\" },\n\t{ name: \"stopall\", description: \"Stop this session and its background subagents\" },\n\t{ name: \"download\", description: \"Send a file from the current project\" },\n\t{ name: \"usage\", description: \"Show session tokens and subscription usage\" },\n\t{ name: \"processes\", description: \"List or stop this session's shell processes\" },\n\t{ name: \"refresh\", description: \"Refresh available models\" },\n\t{ name: \"skill\", description: \"Choose a skill and send it a task\" },\n\t{ name: \"fast\", description: \"Toggle OpenAI Codex fast mode\" },\n];\n\nexport const FORWARDED_COMMANDS = [\n\t{ name: \"goal\", description: \"Set or manage the session goal\" },\n\t{ name: \"cron\", description: \"Schedule prompts and manage scheduled jobs\" },\n\t{ name: \"run\", description: \"Launch a subagent task\" },\n\t{ name: \"chain\", description: \"Run a sequence of subagent tasks\" },\n\t{ name: \"parallel\", description: \"Run independent subagent tasks\" },\n\t{ name: \"mcp\", description: \"Show configured MCP servers\" },\n\t{ name: \"lsp\", description: \"Show language server status\" },\n];\n\nexport function resolveWithinRoots(path: string, roots: string[]): string {\n\tconst actual = realpathSync(resolve(path));\n\tif (\n\t\t!roots.some((root) => {\n\t\t\ttry {\n\t\t\t\tconst rel = relative(realpathSync(root), actual);\n\t\t\t\treturn (\n\t\t\t\t\trel === \"\" ||\n\t\t\t\t\t(!rel.startsWith(`..${process.platform === \"win32\" ? \"\\\\\" : \"/\"}`) && rel !== \"..\" && !isAbsolute(rel))\n\t\t\t\t);\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t})\n\t)\n\t\tthrow new Error(\"That path is outside the approved folders.\");\n\treturn actual;\n}\n\nexport function createProjectFolder(parent: string, name: string, roots: string[]): string {\n\tif (\n\t\t!name ||\n\t\tname === \".\" ||\n\t\tname === \"..\" ||\n\t\t/[\\\\/\\x00-\\x1f<>:\"|?*]/.test(name) ||\n\t\t/[. ]$/.test(name) ||\n\t\t/^(con|prn|aux|nul|com\\d|lpt\\d)(\\.|$)/i.test(name)\n\t)\n\t\tthrow new Error(\"Use a simple folder name without slashes or reserved characters.\");\n\tconst actual = resolveWithinRoots(parent, roots);\n\tmkdirSync(join(actual, name));\n\treturn resolveWithinRoots(join(actual, name), roots);\n}\n\ninterface MobileContext {\n\tkey: string;\n\tevent: MessageEvent;\n\tadapter: PlatformAdapter;\n\tbridge: BridgeLike;\n\tcfg: GatewayConfig;\n}\n\nasync function browseProject(ctx: MobileContext, initialPath?: string): Promise<void> {\n\tconst { key, event, adapter, bridge } = ctx;\n\tconst roots = ctx.cfg.projectRoots ?? [];\n\tif (!roots.length) throw new Error(\"Add approved project folders with lunr gateway setup first.\");\n\tlet current: string | undefined = initialPath\n\t\t? resolveWithinRoots(initialPath, ctx.cfg.projectRoots ?? [])\n\t\t: undefined;\n\tconst view = (): { title: string; items: PickerItem[] } => {\n\t\tif (!current)\n\t\t\treturn {\n\t\t\t\ttitle: \"Choose a project folder\",\n\t\t\t\titems: [...new Set([...(conversationBinding(key)?.recentProjects ?? []), ...roots])]\n\t\t\t\t\t.filter((p) => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tresolveWithinRoots(p, roots);\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\t.map((p) => ({ label: p, value: p })),\n\t\t\t};\n\t\tcurrent = resolveWithinRoots(current, roots);\n\t\tconst items: PickerItem[] = [\n\t\t\t{ label: \"Use this folder\", value: \"use\" },\n\t\t\t{ label: \"New folder\", value: \"new\" },\n\t\t\t{ label: \"Recent and roots\", value: \"roots\" },\n\t\t];\n\t\ttry {\n\t\t\tconst up = resolveWithinRoots(dirname(current), roots);\n\t\t\tif (up !== current) items.push({ label: \"Up\", value: up });\n\t\t} catch {}\n\t\tfor (const item of readdirSync(current, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {\n\t\t\tif (!item.isDirectory() && !item.isSymbolicLink()) continue;\n\t\t\ttry {\n\t\t\t\tconst path = resolveWithinRoots(join(current, item.name), roots);\n\t\t\t\tif (statSync(path).isDirectory()) items.push({ label: item.name, value: path });\n\t\t\t} catch {}\n\t\t}\n\t\treturn { title: current, items };\n\t};\n\tconst initial = view();\n\tawait createPicker(\n\t\tadapter,\n\t\tevent.source,\n\t\t{\n\t\t\tkind: \"project\",\n\t\t\tsessionKey: key,\n\t\t\tinvokerId: event.source.userId,\n\t\t\t...initial,\n\t\t\tvalidate: () => isGatewayOwner(event.source, loadGatewayConfig()),\n\t\t\tresolve: async (item): Promise<PickerResolveResult> => {\n\t\t\t\trequireGatewayOwner(event.source);\n\t\t\t\tif (item.value === \"use\" && current) {\n\t\t\t\t\tconst cwd = resolveWithinRoots(current, loadGatewayConfig().projectRoots ?? []);\n\t\t\t\t\tif (bridge.getStatus(key).busy)\n\t\t\t\t\t\tthrow new Error(\"Stop the current turn or wait before switching projects.\");\n\t\t\t\t\tawait bridge.reset(key);\n\t\t\t\t\tbindConversation(key, event.source, { cwd, owner: event.source.userId });\n\t\t\t\t\treturn {\n\t\t\t\t\t\tdone: true,\n\t\t\t\t\t\ttext: `Project selected: ${cwd}\\nSend a task to begin. Existing sessions are still available through /sessions.`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (item.value === \"new\" && current) {\n\t\t\t\t\tconst name = await gatewayInput(key, \"New folder name\");\n\t\t\t\t\tif (name) current = createProjectFolder(current, name, loadGatewayConfig().projectRoots ?? []);\n\t\t\t\t} else current = item.value === \"roots\" ? undefined : item.value;\n\t\t\t\treturn { done: false, ...view() };\n\t\t\t},\n\t\t},\n\t\t{ threadId: event.source.threadId },\n\t);\n}\n\nconst transfers = new Map<string, AbortController>();\nexport function cancelMobileTransfer(key: string): void {\n\ttransfers.get(key)?.abort();\n}\n\nasync function continueSession(ctx: MobileContext, file: string): Promise<void> {\n\tif (transfers.has(ctx.key)) throw new Error(\"A session transfer is already pending. Use /stop to cancel it.\");\n\tconst controller = new AbortController();\n\ttransfers.set(ctx.key, controller);\n\ttry {\n\t\tawait performContinue(ctx, file, controller.signal);\n\t} finally {\n\t\ttransfers.delete(ctx.key);\n\t}\n}\n\nasync function performContinue(ctx: MobileContext, file: string, signal: AbortSignal): Promise<void> {\n\trequireGatewayOwner(ctx.event.source);\n\tconst { requestSessionTransfer, SessionTransferError } = await import(\"../core/session-handoff.ts\");\n\tconst { key, bridge } = ctx;\n\tconst current = bridge.peekSession?.(key);\n\tif (current?.sessionManager?.getSessionFile() === file) {\n\t\tawait ctx.adapter.send(ctx.event.source.chatId, \"This session is already open here.\");\n\t\treturn;\n\t}\n\tif (bridge.getStatus(key).busy) throw new Error(\"Stop the current phone session before switching.\");\n\ttry {\n\t\tawait requestSessionTransfer(file, { timeoutMs: 3000, signal });\n\t} catch (error) {\n\t\tif (!(error instanceof SessionTransferError) || ![\"busy\", \"timeout\"].includes(error.code)) throw error;\n\t\tconst choice = await gatewaySelect(\n\t\t\tkey,\n\t\t\t`${error instanceof Error ? error.message : \"Session is in use\"}\\nHow should lunR continue?`,\n\t\t\t[\"Wait until idle\", \"Stop and continue\", \"Cancel\"],\n\t\t\t{ signal },\n\t\t);\n\t\tif (!choice || choice === \"Cancel\") return;\n\t\tif (choice === \"Stop and continue\") await requestSessionTransfer(file, { stop: true, timeoutMs: 30_000, signal });\n\t\telse {\n\t\t\tconst deadline = Date.now() + 120_000;\n\t\t\tfor (;;) {\n\t\t\t\tsignal.throwIfAborted();\n\t\t\t\trequireGatewayOwner(ctx.event.source);\n\t\t\t\ttry {\n\t\t\t\t\tawait requestSessionTransfer(file, { timeoutMs: 3000, signal });\n\t\t\t\t\tbreak;\n\t\t\t\t} catch (retryError) {\n\t\t\t\t\tif (\n\t\t\t\t\t\t!(retryError instanceof SessionTransferError) ||\n\t\t\t\t\t\t![\"busy\", \"timeout\"].includes(retryError.code) ||\n\t\t\t\t\t\tDate.now() >= deadline\n\t\t\t\t\t)\n\t\t\t\t\t\tthrow retryError;\n\t\t\t\t}\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, 500));\n\t\t\t}\n\t\t}\n\t}\n\tsignal.throwIfAborted();\n\trequireGatewayOwner(ctx.event.source);\n\tawait bridge.switchSession(key, file);\n\tconst session = await bridge.getSession(key);\n\tconst cwd = session?.sessionManager?.getCwd();\n\tbindConversation(key, ctx.event.source, { cwd, owner: ctx.event.source.userId });\n\tconst id = session?.sessionManager?.getSessionId();\n\tif (id && [\"auto\", \"yolo\"].includes(getPermissionMode(id))) {\n\t\tconst requested = getPermissionMode(id);\n\t\tsetPermissionMode(\"read-only\", id);\n\t\tsession?.sessionManager?.setPermissionMode?.(\"read-only\");\n\t\tif (\n\t\t\t(await gatewaySelect(\n\t\t\t\tkey,\n\t\t\t\t`Continue with ${requested} permissions? Tools can change files and run commands without individual approval.`,\n\t\t\t\t[\"Keep read-only\", `Use ${requested}`],\n\t\t\t)) === `Use ${requested}`\n\t\t) {\n\t\t\tsetPermissionMode(requested, id);\n\t\t\tsession?.sessionManager?.setPermissionMode?.(requested);\n\t\t}\n\t}\n\tawait ctx.adapter.send(\n\t\tctx.event.source.chatId,\n\t\t`Continuing ${session?.sessionManager?.getSessionName() ?? basename(file)}\\nProject: ${cwd}\\nSend your next instruction.`,\n\t\t{ threadId: ctx.event.source.threadId },\n\t);\n}\n\nasync function browseSessions(ctx: MobileContext, args: string, quick: boolean): Promise<void> {\n\tconst handoff = await import(\"../core/session-handoff.ts\");\n\tif (quick) {\n\t\tconst candidates = handoff.listHandoffCandidates();\n\t\tif (!candidates.length) {\n\t\t\tconst latest = handoff.latestTuiSession();\n\t\t\tif (!latest)\n\t\t\t\tthrow new Error(\"No recent TUI session found. Use /sessions or mark one in the terminal with /handoff.\");\n\t\t\tawait continueSession(ctx, latest.sessionFile);\n\t\t\treturn;\n\t\t}\n\t\tif (candidates.length === 1) {\n\t\t\tawait continueSession(ctx, candidates[0].sessionFile);\n\t\t\treturn;\n\t\t}\n\t\tawait sessionPicker(\n\t\t\tctx,\n\t\t\tcandidates.map((s) => ({ label: `${s.cwd} · ${basename(s.sessionFile)}`, value: s.sessionFile })),\n\t\t);\n\t\treturn;\n\t}\n\tconst sessions = await SessionManager.listAll();\n\tconst known = new Set(sessions.map((s) => s.path));\n\tfor (const file of handoff.listRegisteredSessionPaths()) {\n\t\tif (known.has(file)) continue;\n\t\ttry {\n\t\t\tconst manager = SessionManager.openReadOnly(file);\n\t\t\tconst header = manager.getHeader();\n\t\t\tif (header)\n\t\t\t\tsessions.push({\n\t\t\t\t\tid: header.id,\n\t\t\t\t\tpath: file,\n\t\t\t\t\tcwd: manager.getCwd(),\n\t\t\t\t\tname: manager.getSessionName(),\n\t\t\t\t\tcreated: new Date(header.timestamp),\n\t\t\t\t\tmodified: statSync(file).mtime,\n\t\t\t\t\tmessageCount: manager.getEntries().length,\n\t\t\t\t\tfirstMessage: \"\",\n\t\t\t\t\tallMessagesText: \"\",\n\t\t\t\t});\n\t\t} catch {}\n\t}\n\tconst query = args.toLowerCase();\n\tconst items = sessions\n\t\t.sort((a, b) => b.modified.getTime() - a.modified.getTime())\n\t\t.filter((s) => !query || `${s.name ?? \"\"} ${s.cwd} ${s.id}`.toLowerCase().includes(query))\n\t\t.map((s) => ({\n\t\t\tlabel: `${s.name || s.firstMessage.slice(0, 35) || s.id.slice(0, 8)} · ${basename(s.cwd)} · ${s.modified.toLocaleDateString()}`,\n\t\t\tvalue: s.path,\n\t\t}));\n\tawait sessionPicker(ctx, items);\n}\n\nasync function sessionPicker(ctx: MobileContext, items: PickerItem[]): Promise<void> {\n\tawait createPicker(\n\t\tctx.adapter,\n\t\tctx.event.source,\n\t\t{\n\t\t\tkind: \"sessions\",\n\t\t\tsessionKey: ctx.key,\n\t\t\tinvokerId: ctx.event.source.userId,\n\t\t\ttitle: \"Choose a session. Use /sessions <search> to filter.\",\n\t\t\titems,\n\t\t\tvalidate: () => isGatewayOwner(ctx.event.source, loadGatewayConfig()),\n\t\t\tresolve: async (item) => {\n\t\t\t\tawait continueSession(ctx, item.value);\n\t\t\t\treturn { done: true, text: \"Session selection finished.\" };\n\t\t\t},\n\t\t},\n\t\t{ threadId: ctx.event.source.threadId },\n\t);\n}\n\nexport async function handleMobileCommand(ctx: MobileContext, command: string, args: string): Promise<boolean> {\n\tif (!MOBILE_COMMANDS.some((c) => c.name === command) && command !== \"resume\") return false;\n\trequireGatewayOwner(ctx.event.source, ctx.cfg);\n\tbindConversation(ctx.key, ctx.event.source, { owner: ctx.event.source.userId });\n\tif (command === \"project\") {\n\t\tawait browseProject(ctx, args.trim() || undefined);\n\t\treturn true;\n\t}\n\tif (command === \"sessions\" || command === \"resume\" || command === \"continue\") {\n\t\tawait browseSessions(ctx, args, command === \"continue\");\n\t\treturn true;\n\t}\n\tconst session = await ctx.bridge.getSession(ctx.key);\n\tif (!session) throw new Error(\"Select a project and send a task, or use /continue first.\");\n\tconst id = session.sessionManager?.getSessionId();\n\tif (command === \"usage\") {\n\t\tconst stats = session.getSessionStats();\n\t\tconst plans = await (await import(\"../core/usage-service.ts\")).getAllPlanUsageResults(\n\t\t\tsession.model?.provider,\n\t\t\tsession.modelRuntime,\n\t\t);\n\t\tawait ctx.adapter.send(\n\t\t\tctx.event.source.chatId,\n\t\t\t[\n\t\t\t\t`Tokens: ${stats.tokens.total} · cost: $${stats.cost.toFixed(4)}`,\n\t\t\t\t...plans.usages.flatMap((p) =>\n\t\t\t\t\tp.windows.map((w) => `${p.provider} ${w.label}: ${w.usedPercent.toFixed(1)}% used`),\n\t\t\t\t),\n\t\t\t\t...plans.errors,\n\t\t\t].join(\"\\n\"),\n\t\t);\n\t\treturn true;\n\t}\n\tif (command === \"processes\") {\n\t\tif (!id) throw new Error(\"No session identity.\");\n\t\tconst processes = await import(\"../core/process-registry.ts\");\n\t\tconst own = processes.list(id);\n\t\tif (args) {\n\t\t\tconst [action, raw] = args.split(/\\s+/);\n\t\t\tconst pid = Number(raw);\n\t\t\tif (action !== \"stop\" || !own.some((p) => p.pid === pid))\n\t\t\t\tthrow new Error(\"Usage: /processes stop <pid> from this session's process list.\");\n\t\t\tprocesses.kill(pid);\n\t\t\tawait ctx.adapter.send(ctx.event.source.chatId, `Stop requested for process ${pid}.`);\n\t\t} else\n\t\t\tawait ctx.adapter.send(\n\t\t\t\tctx.event.source.chatId,\n\t\t\t\town.length\n\t\t\t\t\t? own\n\t\t\t\t\t\t\t.map((p) => `${p.pid} · ${p.status} · ${p.command}`)\n\t\t\t\t\t\t\t.join(\"\\n\")\n\t\t\t\t\t\t\t.slice(0, ctx.adapter.maxMessageLength)\n\t\t\t\t\t: \"No tracked processes.\",\n\t\t\t);\n\t\treturn true;\n\t}\n\tif (command === \"stopall\") {\n\t\tawait ctx.bridge.abort(ctx.key);\n\t\tconst result = id ? await getSubagentCancellation(id)?.stop() : undefined;\n\t\tconst processes = await import(\"../core/process-registry.ts\");\n\t\tconst count = id ? processes.list(id).filter((p) => p.status !== \"exited\").length : 0;\n\t\tif (id) processes.killAll(id);\n\t\tawait ctx.adapter.send(\n\t\t\tctx.event.source.chatId,\n\t\t\t`Stopped the current turn. Requested stop for ${result?.requested ?? 0} background runs and ${count} tracked processes${result?.failed ? `; ${result.failed} requests failed` : \"\"}. Wait for them to exit before transferring.`,\n\t\t);\n\t\treturn true;\n\t}\n\tif (ctx.bridge.getStatus(ctx.key).busy) throw new Error(\"Wait for the current turn or use /stop first.\");\n\tif (command === \"refresh\") {\n\t\tawait session.modelRuntime.refresh();\n\t\tawait ctx.adapter.send(ctx.event.source.chatId, \"Model catalog refreshed. Use /model to choose.\");\n\t\treturn true;\n\t}\n\tif (command === \"fast\") {\n\t\tif (session.model?.provider !== \"openai-codex\" || !session.settingsManager)\n\t\t\tthrow new Error(\"Fast mode is available only for OpenAI Codex subscriptions.\");\n\t\tconst current = session.settingsManager.getOpenAIFastMode();\n\t\tif (args && ![\"on\", \"off\", \"status\"].includes(args)) throw new Error(\"Usage: /fast on|off|status\");\n\t\tif (args !== \"status\") session.settingsManager.setOpenAIFastMode(args ? args === \"on\" : !current);\n\t\tawait ctx.adapter.send(\n\t\t\tctx.event.source.chatId,\n\t\t\t`Fast mode: ${session.settingsManager.getOpenAIFastMode() ? \"on\" : \"off\"}`,\n\t\t);\n\t\treturn true;\n\t}\n\tif (command === \"skill\") {\n\t\tconst names = session.resourceLoader?.getSkills().skills.map((s) => s.name) ?? [];\n\t\tconst [name, ...task] = args.split(/\\s+/).filter(Boolean);\n\t\tconst chosen = name || (await gatewaySelect(ctx.key, \"Choose a skill\", names));\n\t\tif (!chosen) return true;\n\t\tif (!names.includes(chosen)) throw new Error(\"That skill is not loaded for this project.\");\n\t\tctx.event.text = `/skill:${chosen} ${task.join(\" \") || (await gatewayInput(ctx.key, \"What should this skill do?\")) || \"\"}`;\n\t\treturn false;\n\t}\n\tif (command === \"download\") {\n\t\tif (!ctx.adapter.sendFile) throw new Error(\"This platform cannot send files.\");\n\t\tif (!args) throw new Error(\"Usage: /download <project-relative path>\");\n\t\tconst cwd = session.sessionManager?.getCwd();\n\t\tif (!cwd) throw new Error(\"No project selected.\");\n\t\tconst file = resolveWithinRoots(resolve(cwd, args), [cwd]);\n\t\tconst stat = statSync(file);\n\t\tif (!stat.isFile() || stat.size > 8 * 1024 * 1024) throw new Error(\"Choose a file no larger than 8 MB.\");\n\t\tif (\n\t\t\t/(^|[\\\\/])(?:\\.env(?:\\.[^\\\\/]*)?|auth\\.json|gateway\\.json|credentials[^\\\\/]*|id_rsa|id_ed25519)([\\\\/]|$)/i.test(\n\t\t\t\tfile,\n\t\t\t)\n\t\t)\n\t\t\tthrow new Error(\"Credential files cannot be sent through chat.\");\n\t\tconst result = await ctx.adapter.sendFile(ctx.event.source.chatId, file, { threadId: ctx.event.source.threadId });\n\t\tif (!result.success) throw new Error(result.error ?? \"File delivery failed.\");\n\t\treturn true;\n\t}\n\tif (command === \"settings\") {\n\t\tconst choice = await gatewaySelect(ctx.key, \"Session settings\", [\"Model\", \"Thinking\", \"Permissions\"]);\n\t\tif (choice === \"Model\" || choice === \"Thinking\") {\n\t\t\tconst { CHAT_COMMANDS, runChatCommand } = await import(\"./commands.ts\");\n\t\t\tconst cmd = CHAT_COMMANDS.find((c) => c.name === choice.toLowerCase());\n\t\t\tif (cmd)\n\t\t\t\tawait runChatCommand(cmd, {\n\t\t\t\t\t...ctx,\n\t\t\t\t\targs: \"\",\n\t\t\t\t\treply: async (text) => {\n\t\t\t\t\t\tawait ctx.adapter.send(ctx.event.source.chatId, text);\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\treturn true;\n\t\t}\n\t\tif (choice !== \"Permissions\") return true;\n\t}\n\tconst requested =\n\t\tcommand === \"plan\"\n\t\t\t? \"read-only\"\n\t\t\t: args || (await gatewaySelect(ctx.key, `Permission mode: ${getPermissionMode(id)}`, [...PERMISSION_MODES]));\n\tif (!requested) return true;\n\tconst mode = requested === \"read\" ? \"read-only\" : requested;\n\tif (!PERMISSION_MODES.includes(mode as (typeof PERMISSION_MODES)[number]))\n\t\tthrow new Error(\"Choose yolo, auto, or read-only.\");\n\tsetPermissionMode(mode as (typeof PERMISSION_MODES)[number], id);\n\tsession.sessionManager?.setPermissionMode?.(mode as (typeof PERMISSION_MODES)[number]);\n\tawait ctx.adapter.send(ctx.event.source.chatId, `Permission mode: ${mode}`);\n\tif (command === \"plan\" && args) {\n\t\tctx.event.text = `Create a plan for: ${args}`;\n\t\treturn false;\n\t}\n\treturn true;\n}\n"]}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { ExtensionUIContext, ExtensionUIDialogOptions } from "../core/extensions/types.ts";
|
|
2
|
+
import { type GatewayConfig } from "./config.ts";
|
|
2
3
|
import type { MessageEvent, PlatformAdapter } from "./types.ts";
|
|
4
|
+
export declare function rememberGatewayRole(key: string, source: MessageEvent["source"]): void;
|
|
3
5
|
export declare function gatewayDestination(key: string): {
|
|
4
6
|
adapter: PlatformAdapter;
|
|
5
7
|
source: MessageEvent["source"];
|
|
@@ -7,7 +9,16 @@ export declare function gatewayDestination(key: string): {
|
|
|
7
9
|
export declare function withGatewayPresentation<T>(key: string, run: () => Promise<T>): Promise<T>;
|
|
8
10
|
export declare function startGatewayPresenter(connected: Map<string, PlatformAdapter>): void;
|
|
9
11
|
export declare function stopGatewayPresenter(): void;
|
|
10
|
-
export declare function
|
|
12
|
+
export declare function gatewayEpoch(key: string): number;
|
|
13
|
+
export declare function sendGatewayNotice(key: string, text: string, kind?: "notice" | "result", epoch?: number): Promise<void>;
|
|
14
|
+
export declare function queueGatewayText(key: string, source: MessageEvent["source"], adapter: PlatformAdapter, text: string, options: {
|
|
15
|
+
kind: "result" | "notice";
|
|
16
|
+
replyTo?: string;
|
|
17
|
+
cfg?: GatewayConfig;
|
|
18
|
+
chunks?: string[];
|
|
19
|
+
epoch?: number;
|
|
20
|
+
}): Promise<void>;
|
|
21
|
+
export declare function flushGatewayOutbox(): Promise<void>;
|
|
11
22
|
export declare function invalidateGatewayDialogs(key: string): void;
|
|
12
23
|
export declare function acceptGatewayInput(key: string, event: MessageEvent): boolean;
|
|
13
24
|
export declare function gatewaySelect(key: string, title: string, options: string[], opts?: ExtensionUIDialogOptions): Promise<string | undefined>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presenter.d.ts","sourceRoot":"","sources":["../../src/gateway/presenter.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AAShG,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAyBhE,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAA;CAAE,CAM5G;AAED,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAGzF;AAED,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,IAAI,CAenF;AAED,wBAAgB,oBAAoB,IAAI,IAAI,CAK3C;AAED,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQhF;AA+BD,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAK1D;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAM5E;AAqCD,wBAAgB,aAAa,CAC5B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EAAE,EACjB,IAAI,CAAC,EAAE,wBAAwB,GAC7B,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAuB7B;AAED,wBAAgB,YAAY,CAC3B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,WAAW,CAAC,EAAE,MAAM,EACpB,IAAI,CAAC,EAAE,wBAAwB,GAC7B,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAO7B;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,CAuB/D","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { getAgentDir } from \"../config.ts\";\nimport { noOpUIContext } from \"../core/extensions/runner.ts\";\nimport type { ExtensionUIContext, ExtensionUIDialogOptions } from \"../core/extensions/types.ts\";\nimport { runWithApprovalContext } from \"./approval.ts\";\nimport { isAuthorized, isGatewayOwner } from \"./authz.ts\";\nimport { createPicker } from \"./buttons.ts\";\nimport { loadGatewayConfig } from \"./config.ts\";\nimport { conversationBinding } from \"./conversations.ts\";\nimport { createPairingStore } from \"./pairing.ts\";\nimport { atomicJson } from \"./service.ts\";\nimport { splitMessage } from \"./text.ts\";\nimport type { MessageEvent, PlatformAdapter } from \"./types.ts\";\n\nconst epochs = new Map<string, number>();\nconst inputs = new Map<string, { userId: string; resolve: (value: string | undefined) => void }>();\nconst dialogs = new Map<string, Set<() => void>>();\nlet adapters = new Map<string, PlatformAdapter>();\nlet timer: ReturnType<typeof setInterval> | undefined;\nlet sending = false;\ninterface Outbound {\n\tid: string;\n\tkey: string;\n\tepoch: number;\n\ttext: string;\n}\nconst path = () => join(getAgentDir(), \"gateway-outbox.json\");\nlet outbox: Outbound[] = [];\n\nfunction canDeliver(key: string): boolean {\n\tconst binding = conversationBinding(key);\n\tif (!binding) return false;\n\tconst cfg = loadGatewayConfig();\n\tif (binding.owner && (binding.owner !== binding.source.userId || !isGatewayOwner(binding.source, cfg))) return false;\n\treturn isAuthorized(binding.source, cfg, createPairingStore());\n}\n\nexport function gatewayDestination(key: string): { adapter: PlatformAdapter; source: MessageEvent[\"source\"] } {\n\tconst binding = conversationBinding(key);\n\tconst adapter = binding && adapters.get(binding.source.platform);\n\tif (!binding || !adapter || !canDeliver(key))\n\t\tthrow new Error(\"This chat is no longer authorized or its platform is disconnected.\");\n\treturn { adapter, source: binding.source };\n}\n\nexport function withGatewayPresentation<T>(key: string, run: () => Promise<T>): Promise<T> {\n\tconst { adapter, source } = gatewayDestination(key);\n\treturn runWithApprovalContext({ adapter, source, key }, run);\n}\n\nexport function startGatewayPresenter(connected: Map<string, PlatformAdapter>): void {\n\tadapters = connected;\n\tif (existsSync(path())) {\n\t\ttry {\n\t\t\toutbox = JSON.parse(readFileSync(path(), \"utf8\")) as Outbound[];\n\t\t} catch {\n\t\t\tthrow new Error(\"Gateway outbox is invalid. Restore it before starting.\");\n\t\t}\n\t}\n\tif (timer) clearInterval(timer);\n\ttimer = setInterval(() => {\n\t\tvoid flushOutbox();\n\t}, 3000);\n\ttimer.unref();\n\tvoid flushOutbox();\n}\n\nexport function stopGatewayPresenter(): void {\n\tif (timer) clearInterval(timer);\n\ttimer = undefined;\n\tfor (const key of dialogs.keys()) invalidateGatewayDialogs(key);\n\tadapters = new Map();\n}\n\nexport async function sendGatewayNotice(key: string, text: string): Promise<void> {\n\tconst clean = text.replace(/\\x1b\\[[0-9;]*[a-zA-Z]/g, \"\").trim();\n\tif (!clean || !canDeliver(key)) return;\n\tconst { adapter } = gatewayDestination(key);\n\tfor (const chunk of splitMessage(clean, adapter.maxMessageLength))\n\t\toutbox.push({ id: randomUUID(), key, epoch: epochs.get(key) ?? 0, text: chunk });\n\tatomicJson(path(), outbox);\n\tawait flushOutbox();\n}\n\nasync function flushOutbox(): Promise<void> {\n\tif (sending) return;\n\tsending = true;\n\ttry {\n\t\tfor (const item of [...outbox]) {\n\t\t\tif (!canDeliver(item.key)) {\n\t\t\t\toutbox = outbox.filter((v) => v.id !== item.id);\n\t\t\t\tatomicJson(path(), outbox);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tlet target: ReturnType<typeof gatewayDestination>;\n\t\t\ttry {\n\t\t\t\ttarget = gatewayDestination(item.key);\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst result = await target.adapter\n\t\t\t\t.send(target.source.chatId, item.text, { threadId: target.source.threadId })\n\t\t\t\t.catch(() => ({ success: false }));\n\t\t\tif (result.success) {\n\t\t\t\toutbox = outbox.filter((v) => v.id !== item.id);\n\t\t\t\tatomicJson(path(), outbox);\n\t\t\t} else break;\n\t\t}\n\t} finally {\n\t\tsending = false;\n\t}\n}\n\nexport function invalidateGatewayDialogs(key: string): void {\n\tepochs.set(key, (epochs.get(key) ?? 0) + 1);\n\tfor (const cancel of dialogs.get(key) ?? []) cancel();\n\tdialogs.delete(key);\n\tinputs.delete(key);\n}\n\nexport function acceptGatewayInput(key: string, event: MessageEvent): boolean {\n\tconst input = inputs.get(key);\n\tif (!input || input.userId !== event.source.userId || !canDeliver(key)) return false;\n\tif (event.text.startsWith(\"/\") && event.text !== \"/cancel\") return false;\n\tinput.resolve(event.text === \"/cancel\" ? undefined : event.text);\n\treturn true;\n}\n\nfunction dialog(\n\tkey: string,\n\topts: ExtensionUIDialogOptions | undefined,\n\tshow: (done: (value: string | undefined) => void, valid: () => boolean) => Promise<void>,\n): Promise<string | undefined> {\n\tconst epoch = epochs.get(key) ?? 0;\n\treturn new Promise((resolve, reject) => {\n\t\tlet settled = false;\n\t\tconst finish = (value: string | undefined, error?: unknown) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tclearTimeout(timeout);\n\t\t\topts?.signal?.removeEventListener(\"abort\", cancel);\n\t\t\tinputs.delete(key);\n\t\t\tdialogs.get(key)?.delete(cancel);\n\t\t\tif (error !== undefined) reject(error);\n\t\t\telse resolve(value);\n\t\t};\n\t\tconst done = (value: string | undefined) => finish(value);\n\t\tconst cancel = () => done(undefined);\n\t\tconst timeout = setTimeout(cancel, opts?.timeout ?? 300_000);\n\t\tconst set = dialogs.get(key) ?? new Set();\n\t\tset.add(cancel);\n\t\tdialogs.set(key, set);\n\t\topts?.signal?.addEventListener(\"abort\", cancel, { once: true });\n\t\tif (opts?.signal?.aborted) {\n\t\t\tcancel();\n\t\t\treturn;\n\t\t}\n\t\tvoid show(done, () => !settled && (epochs.get(key) ?? 0) === epoch && canDeliver(key)).catch((error) => {\n\t\t\tfinish(undefined, error);\n\t\t});\n\t});\n}\n\nexport function gatewaySelect(\n\tkey: string,\n\ttitle: string,\n\toptions: string[],\n\topts?: ExtensionUIDialogOptions,\n): Promise<string | undefined> {\n\treturn dialog(key, opts, async (done, valid) => {\n\t\tconst { adapter, source } = gatewayDestination(key);\n\t\tconst result = await createPicker(\n\t\t\tadapter,\n\t\t\tsource,\n\t\t\t{\n\t\t\t\tkind: \"dialog\",\n\t\t\t\tsessionKey: key,\n\t\t\t\tinvokerId: source.userId,\n\t\t\t\ttitle,\n\t\t\t\titems: options.map((value) => ({ label: value, value })),\n\t\t\t\tvalidate: valid,\n\t\t\t\tonCancel: () => done(undefined),\n\t\t\t\tresolve: async (item) => {\n\t\t\t\t\tdone(item.value);\n\t\t\t\t\treturn { done: true, text: `${title}\\n${item.value}` };\n\t\t\t\t},\n\t\t\t},\n\t\t\t{ threadId: source.threadId },\n\t\t);\n\t\tif (!result.success) throw new Error(result.error ?? \"Could not send picker.\");\n\t});\n}\n\nexport function gatewayInput(\n\tkey: string,\n\ttitle: string,\n\tplaceholder?: string,\n\topts?: ExtensionUIDialogOptions,\n): Promise<string | undefined> {\n\tif (inputs.has(key)) return Promise.reject(new Error(\"Answer or cancel the existing question first.\"));\n\treturn dialog(key, opts, async (done) => {\n\t\tconst { source } = gatewayDestination(key);\n\t\tinputs.set(key, { userId: source.userId, resolve: done });\n\t\tawait sendGatewayNotice(key, `${title}${placeholder ? `\\n${placeholder}` : \"\"}\\nReply with text, or /cancel.`);\n\t});\n}\n\nexport function createGatewayUI(key: string): ExtensionUIContext {\n\treturn {\n\t\t...noOpUIContext,\n\t\tselect: (title, options, opts) => gatewaySelect(key, title, options, opts),\n\t\tconfirm: async (title, message, opts) =>\n\t\t\t(await gatewaySelect(key, `${title}\\n${message}`, [\"Approve\", \"Decline\"], opts)) === \"Approve\",\n\t\tinput: (title, placeholder, opts) => gatewayInput(key, title, placeholder, opts),\n\t\teditor: (title, prefill) => gatewayInput(key, title, prefill),\n\t\tnotify: (message) => {\n\t\t\tvoid sendGatewayNotice(key, message).catch(() => {});\n\t\t},\n\t\tcustom: async () => {\n\t\t\tthrow new Error(\n\t\t\t\t\"This command requires the terminal interface. Use the gateway's text commands or button pickers.\",\n\t\t\t);\n\t\t},\n\t\tpasteToEditor: (text) => {\n\t\t\tvoid sendGatewayNotice(key, text).catch(() => {});\n\t\t},\n\t\tsetEditorText: (text) => {\n\t\t\tvoid sendGatewayNotice(key, text).catch(() => {});\n\t\t},\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"presenter.d.ts","sourceRoot":"","sources":["../../src/gateway/presenter.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AAIhG,OAAO,EAAE,KAAK,aAAa,EAAwC,MAAM,aAAa,CAAC;AAKvF,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAe,MAAM,YAAY,CAAC;AAoC7E,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,GAAG,IAAI,CAIrF;AAUD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAA;CAAE,CAM5G;AAED,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAGzF;AAED,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,IAAI,CA2BnF;AAED,wBAAgB,oBAAoB,IAAI,IAAI,CAO3C;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,wBAAsB,iBAAiB,CACtC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,QAAQ,GAAG,QAAmB,EACpC,KAAK,SAAoB,GACvB,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,wBAAsB,gBAAgB,CACrC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,EAC9B,OAAO,EAAE,eAAe,EACxB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;IAAE,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,aAAa,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9G,OAAO,CAAC,IAAI,CAAC,CAqBf;AAED,wBAAgB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAElD;AAsED,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAU1D;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAM5E;AAqCD,wBAAgB,aAAa,CAC5B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EAAE,EACjB,IAAI,CAAC,EAAE,wBAAwB,GAC7B,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAuB7B;AAED,wBAAgB,YAAY,CAC3B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,WAAW,CAAC,EAAE,MAAM,EACpB,IAAI,CAAC,EAAE,wBAAwB,GAC7B,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAa7B;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,CA4B/D","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { getAgentDir } from \"../config.ts\";\nimport { noOpUIContext } from \"../core/extensions/runner.ts\";\nimport type { ExtensionUIContext, ExtensionUIDialogOptions } from \"../core/extensions/types.ts\";\nimport { runWithApprovalContext } from \"./approval.ts\";\nimport { isAuthorized, isGatewayOwner } from \"./authz.ts\";\nimport { createPicker } from \"./buttons.ts\";\nimport { type GatewayConfig, gatewayConfigPath, loadGatewayConfig } from \"./config.ts\";\nimport { conversationBinding } from \"./conversations.ts\";\nimport { createPairingStore } from \"./pairing.ts\";\nimport { atomicJson } from \"./service.ts\";\nimport { splitMessage } from \"./text.ts\";\nimport type { MessageEvent, PlatformAdapter, SendOptions } from \"./types.ts\";\n\nconst epochs = new Map<string, number>();\nconst inputs = new Map<string, { userId: string; resolve: (value: string | undefined) => void }>();\nconst dialogs = new Map<string, Set<() => void>>();\nlet adapters = new Map<string, PlatformAdapter>();\nconst deliveryConfigs = new Map<string, GatewayConfig>();\nconst roleGrants = new Map<string, { userId: string; chatId: string; threadId?: string }>();\nlet timer: ReturnType<typeof setInterval> | undefined;\nconst activeDeliveries = new Map<string, Promise<void>>();\ninterface Outbound {\n\tid: string;\n\tkey: string;\n\tepoch: number;\n\ttext: string;\n\tsource?: MessageEvent[\"source\"];\n\treplyTo?: string;\n\tkind?: \"result\" | \"notice\";\n\tattempts?: number;\n\tnextAttempt?: number;\n\tfailed?: boolean;\n\tbatchId?: string;\n}\nconst MAX_ATTEMPTS = 5;\nconst path = () => join(getAgentDir(), \"gateway-outbox.json\");\nlet outbox: Outbound[] = [];\n\nfunction sourceWithCurrentRole(key: string, source: MessageEvent[\"source\"]): MessageEvent[\"source\"] {\n\tconst grant = roleGrants.get(key);\n\treturn {\n\t\t...source,\n\t\troleAuthorized:\n\t\t\tgrant?.userId === source.userId && grant.chatId === source.chatId && grant.threadId === source.threadId,\n\t};\n}\n\nexport function rememberGatewayRole(key: string, source: MessageEvent[\"source\"]): void {\n\tif (source.roleAuthorized === true)\n\t\troleGrants.set(key, { userId: source.userId, chatId: source.chatId, threadId: source.threadId });\n\telse roleGrants.delete(key);\n}\n\nfunction canDeliver(key: string): boolean {\n\tconst binding = conversationBinding(key);\n\tif (!binding) return false;\n\tconst cfg = loadGatewayConfig();\n\tif (binding.owner && (binding.owner !== binding.source.userId || !isGatewayOwner(binding.source, cfg))) return false;\n\treturn isAuthorized(sourceWithCurrentRole(key, binding.source), cfg, createPairingStore());\n}\n\nexport function gatewayDestination(key: string): { adapter: PlatformAdapter; source: MessageEvent[\"source\"] } {\n\tconst binding = conversationBinding(key);\n\tconst adapter = binding && adapters.get(binding.source.platform);\n\tif (!binding || !adapter || !canDeliver(key))\n\t\tthrow new Error(\"This chat is no longer authorized or its platform is disconnected.\");\n\treturn { adapter, source: binding.source };\n}\n\nexport function withGatewayPresentation<T>(key: string, run: () => Promise<T>): Promise<T> {\n\tconst { adapter, source } = gatewayDestination(key);\n\treturn runWithApprovalContext({ adapter, source, key }, run);\n}\n\nexport function startGatewayPresenter(connected: Map<string, PlatformAdapter>): void {\n\tadapters = connected;\n\tif (existsSync(path())) {\n\t\ttry {\n\t\t\tconst raw: unknown = JSON.parse(readFileSync(path(), \"utf8\"));\n\t\t\tif (\n\t\t\t\t!Array.isArray(raw) ||\n\t\t\t\t!raw.every(\n\t\t\t\t\t(item) =>\n\t\t\t\t\t\titem && typeof item.id === \"string\" && typeof item.key === \"string\" && typeof item.text === \"string\",\n\t\t\t\t)\n\t\t\t)\n\t\t\t\tthrow new Error(\"Invalid outbox\");\n\t\t\toutbox = raw as Outbound[];\n\t\t} catch {\n\t\t\tthrow new Error(\"Gateway outbox is invalid. Restore it before starting.\");\n\t\t}\n\t} else outbox = [];\n\tfor (const item of outbox) {\n\t\tif (item.kind === \"notice\") epochs.set(item.key, Math.max(epochs.get(item.key) ?? 0, item.epoch ?? 0));\n\t}\n\tif (timer) clearInterval(timer);\n\ttimer = setInterval(() => {\n\t\tvoid flushOutbox().catch((error) => console.error(\"[gateway] outbox flush failed:\", error));\n\t}, 3000);\n\ttimer.unref();\n\tvoid flushOutbox().catch((error) => console.error(\"[gateway] outbox flush failed:\", error));\n}\n\nexport function stopGatewayPresenter(): void {\n\tif (timer) clearInterval(timer);\n\ttimer = undefined;\n\tfor (const key of dialogs.keys()) invalidateGatewayDialogs(key);\n\tadapters = new Map();\n\tdeliveryConfigs.clear();\n\troleGrants.clear();\n}\n\nexport function gatewayEpoch(key: string): number {\n\treturn epochs.get(key) ?? 0;\n}\n\nexport async function sendGatewayNotice(\n\tkey: string,\n\ttext: string,\n\tkind: \"notice\" | \"result\" = \"notice\",\n\tepoch = gatewayEpoch(key),\n): Promise<void> {\n\tconst clean = text.replace(/\\x1b\\[[0-9;]*[a-zA-Z]/g, \"\").trim();\n\tif (!clean || !canDeliver(key)) return;\n\tconst { adapter, source } = gatewayDestination(key);\n\tawait queueGatewayText(key, source, adapter, clean, { kind, epoch });\n}\n\nexport async function queueGatewayText(\n\tkey: string,\n\tsource: MessageEvent[\"source\"],\n\tadapter: PlatformAdapter,\n\ttext: string,\n\toptions: { kind: \"result\" | \"notice\"; replyTo?: string; cfg?: GatewayConfig; chunks?: string[]; epoch?: number },\n): Promise<void> {\n\tif (!text || (options.kind === \"notice\" && options.epoch !== undefined && options.epoch !== gatewayEpoch(key)))\n\t\treturn;\n\tadapters.set(source.platform, adapter);\n\tif (options.cfg) deliveryConfigs.set(key, options.cfg);\n\tconst chunks = options.chunks ?? splitMessage(text, adapter.maxMessageLength);\n\tconst batchId = randomUUID();\n\tfor (const [index, chunk] of chunks.entries())\n\t\toutbox.push({\n\t\t\tid: randomUUID(),\n\t\t\tbatchId,\n\t\t\tkey,\n\t\t\tsource: { ...source, roleAuthorized: undefined },\n\t\t\tepoch: options.epoch ?? gatewayEpoch(key),\n\t\t\ttext: chunk,\n\t\t\tkind: options.kind,\n\t\t\treplyTo: index === 0 ? options.replyTo : undefined,\n\t\t});\n\tatomicJson(path(), outbox);\n\tif (activeDeliveries.has(key)) await activeDeliveries.get(key);\n\tawait flushKey(key);\n}\n\nexport function flushGatewayOutbox(): Promise<void> {\n\treturn flushOutbox();\n}\n\nfunction flushOutbox(): Promise<void> {\n\treturn Promise.all([...new Set(outbox.map((item) => item.key))].map(flushKey)).then(() => {});\n}\n\nfunction flushKey(key: string): Promise<void> {\n\tconst current = activeDeliveries.get(key);\n\tif (current) return current;\n\tconst running = drainKey(key).finally(() => {\n\t\tactiveDeliveries.delete(key);\n\t});\n\tactiveDeliveries.set(key, running);\n\treturn running;\n}\n\nasync function drainKey(key: string): Promise<void> {\n\tconst failedBatches = new Set(outbox.filter((item) => item.failed && item.batchId).map((item) => item.batchId));\n\tfor (const item of [...outbox].filter((item) => item.key === key)) {\n\t\tif (item.failed || (item.batchId && failedBatches.has(item.batchId))) continue;\n\t\tif (item.kind === \"notice\" && item.epoch !== (epochs.get(item.key) ?? 0)) {\n\t\t\toutbox = outbox.filter((v) => v.id !== item.id);\n\t\t\tatomicJson(path(), outbox);\n\t\t\tcontinue;\n\t\t}\n\t\tconst binding = conversationBinding(item.key);\n\t\tconst source = item.source ?? binding?.source;\n\t\tif (!source) break;\n\t\tconst cfg = existsSync(gatewayConfigPath())\n\t\t\t? loadGatewayConfig()\n\t\t\t: (deliveryConfigs.get(item.key) ?? loadGatewayConfig());\n\t\tif (\n\t\t\t(binding &&\n\t\t\t\t(binding.source.platform !== source.platform ||\n\t\t\t\t\tbinding.source.chatId !== source.chatId ||\n\t\t\t\t\tbinding.source.userId !== source.userId ||\n\t\t\t\t\tbinding.source.threadId !== source.threadId)) ||\n\t\t\t!isAuthorized(sourceWithCurrentRole(key, source), cfg, createPairingStore()) ||\n\t\t\t(binding?.owner && (binding.owner !== source.userId || !isGatewayOwner(source, cfg)))\n\t\t) {\n\t\t\toutbox = outbox.filter((v) => v.id !== item.id);\n\t\t\tatomicJson(path(), outbox);\n\t\t\tcontinue;\n\t\t}\n\t\tconst adapter = adapters.get(source.platform);\n\t\tif (!adapter || (item.nextAttempt ?? 0) > Date.now()) break;\n\t\tconst opts: SendOptions = { threadId: source.threadId, replyTo: item.replyTo };\n\t\tconst result = await adapter\n\t\t\t.send(source.chatId, item.text, opts)\n\t\t\t.catch((error: unknown) => ({ success: false as const, error: String(error), retryable: true }));\n\t\tif (result.success) {\n\t\t\toutbox = outbox.filter((v) => v.id !== item.id);\n\t\t\tatomicJson(path(), outbox);\n\t\t} else {\n\t\t\titem.attempts = (item.attempts ?? 0) + 1;\n\t\t\titem.failed = result.retryable === false || item.attempts >= MAX_ATTEMPTS;\n\t\t\titem.nextAttempt = Date.now() + Math.min(60_000, 3000 * 2 ** (item.attempts - 1));\n\t\t\tif (item.failed && item.batchId) {\n\t\t\t\tfailedBatches.add(item.batchId);\n\t\t\t\tfor (const pending of outbox) if (pending.batchId === item.batchId) pending.failed = true;\n\t\t\t}\n\t\t\tatomicJson(path(), outbox);\n\t\t\tconsole.error(\n\t\t\t\t`[gateway] delivery ${item.failed ? \"failed permanently\" : \"retry scheduled\"} for ${source.platform}: ${result.error ?? \"unknown error\"}`,\n\t\t\t);\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\nexport function invalidateGatewayDialogs(key: string): void {\n\tepochs.set(key, (epochs.get(key) ?? 0) + 1);\n\tconst remaining = outbox.filter((item) => item.key !== key || item.kind !== \"notice\");\n\tif (remaining.length !== outbox.length) {\n\t\toutbox = remaining;\n\t\tatomicJson(path(), outbox);\n\t}\n\tfor (const cancel of dialogs.get(key) ?? []) cancel();\n\tdialogs.delete(key);\n\tinputs.delete(key);\n}\n\nexport function acceptGatewayInput(key: string, event: MessageEvent): boolean {\n\tconst input = inputs.get(key);\n\tif (!input || input.userId !== event.source.userId || !canDeliver(key)) return false;\n\tif (event.text.startsWith(\"/\") && event.text !== \"/cancel\") return false;\n\tinput.resolve(event.text === \"/cancel\" ? undefined : event.text);\n\treturn true;\n}\n\nfunction dialog(\n\tkey: string,\n\topts: ExtensionUIDialogOptions | undefined,\n\tshow: (done: (value: string | undefined) => void, valid: () => boolean) => Promise<void>,\n): Promise<string | undefined> {\n\tconst epoch = epochs.get(key) ?? 0;\n\treturn new Promise((resolve, reject) => {\n\t\tlet settled = false;\n\t\tconst finish = (value: string | undefined, error?: unknown) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tclearTimeout(timeout);\n\t\t\topts?.signal?.removeEventListener(\"abort\", cancel);\n\t\t\tinputs.delete(key);\n\t\t\tdialogs.get(key)?.delete(cancel);\n\t\t\tif (error !== undefined) reject(error);\n\t\t\telse resolve(value);\n\t\t};\n\t\tconst done = (value: string | undefined) => finish(value);\n\t\tconst cancel = () => done(undefined);\n\t\tconst timeout = setTimeout(cancel, opts?.timeout ?? 300_000);\n\t\tconst set = dialogs.get(key) ?? new Set();\n\t\tset.add(cancel);\n\t\tdialogs.set(key, set);\n\t\topts?.signal?.addEventListener(\"abort\", cancel, { once: true });\n\t\tif (opts?.signal?.aborted) {\n\t\t\tcancel();\n\t\t\treturn;\n\t\t}\n\t\tvoid show(done, () => !settled && (epochs.get(key) ?? 0) === epoch && canDeliver(key)).catch((error) => {\n\t\t\tfinish(undefined, error);\n\t\t});\n\t});\n}\n\nexport function gatewaySelect(\n\tkey: string,\n\ttitle: string,\n\toptions: string[],\n\topts?: ExtensionUIDialogOptions,\n): Promise<string | undefined> {\n\treturn dialog(key, opts, async (done, valid) => {\n\t\tconst { adapter, source } = gatewayDestination(key);\n\t\tconst result = await createPicker(\n\t\t\tadapter,\n\t\t\tsource,\n\t\t\t{\n\t\t\t\tkind: \"dialog\",\n\t\t\t\tsessionKey: key,\n\t\t\t\tinvokerId: source.userId,\n\t\t\t\ttitle,\n\t\t\t\titems: options.map((value) => ({ label: value, value })),\n\t\t\t\tvalidate: valid,\n\t\t\t\tonCancel: () => done(undefined),\n\t\t\t\tresolve: async (item) => {\n\t\t\t\t\tdone(item.value);\n\t\t\t\t\treturn { done: true, text: `${title}\\n${item.value}` };\n\t\t\t\t},\n\t\t\t},\n\t\t\t{ threadId: source.threadId },\n\t\t);\n\t\tif (!result.success) throw new Error(result.error ?? \"Could not send picker.\");\n\t});\n}\n\nexport function gatewayInput(\n\tkey: string,\n\ttitle: string,\n\tplaceholder?: string,\n\topts?: ExtensionUIDialogOptions,\n): Promise<string | undefined> {\n\tif (inputs.has(key)) return Promise.reject(new Error(\"Answer or cancel the existing question first.\"));\n\tconst epoch = gatewayEpoch(key);\n\treturn dialog(key, opts, async (done) => {\n\t\tconst { source } = gatewayDestination(key);\n\t\tinputs.set(key, { userId: source.userId, resolve: done });\n\t\tawait sendGatewayNotice(\n\t\t\tkey,\n\t\t\t`${title}${placeholder ? `\\n${placeholder}` : \"\"}\\nReply with text, or /cancel.`,\n\t\t\t\"notice\",\n\t\t\tepoch,\n\t\t);\n\t});\n}\n\nexport function createGatewayUI(key: string): ExtensionUIContext {\n\tconst epoch = gatewayEpoch(key);\n\treturn {\n\t\t...noOpUIContext,\n\t\tselect: (title, options, opts) =>\n\t\t\tgatewayEpoch(key) === epoch ? gatewaySelect(key, title, options, opts) : Promise.resolve(undefined),\n\t\tconfirm: async (title, message, opts) =>\n\t\t\tgatewayEpoch(key) === epoch &&\n\t\t\t(await gatewaySelect(key, `${title}\\n${message}`, [\"Approve\", \"Decline\"], opts)) === \"Approve\",\n\t\tinput: (title, placeholder, opts) =>\n\t\t\tgatewayEpoch(key) === epoch ? gatewayInput(key, title, placeholder, opts) : Promise.resolve(undefined),\n\t\teditor: (title, prefill) =>\n\t\t\tgatewayEpoch(key) === epoch ? gatewayInput(key, title, prefill) : Promise.resolve(undefined),\n\t\tnotify: (message) => {\n\t\t\tvoid sendGatewayNotice(key, message, \"notice\", epoch).catch(() => {});\n\t\t},\n\t\tcustom: async () => {\n\t\t\tthrow new Error(\n\t\t\t\t\"This command requires the terminal interface. Use the gateway's text commands or button pickers.\",\n\t\t\t);\n\t\t},\n\t\tpasteToEditor: (text) => {\n\t\t\tvoid sendGatewayNotice(key, text, \"notice\", epoch).catch(() => {});\n\t\t},\n\t\tsetEditorText: (text) => {\n\t\t\tvoid sendGatewayNotice(key, text, \"notice\", epoch).catch(() => {});\n\t\t},\n\t};\n}\n"]}
|