@ashx-j/lunr 0.2.25 → 0.2.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/CHANGELOG.md +16 -1
  2. package/dist/builtin-extensions/index.d.ts.map +1 -1
  3. package/dist/builtin-extensions/index.js +1 -0
  4. package/dist/builtin-extensions/index.js.map +1 -1
  5. package/dist/builtin-extensions/lunr-computer-use.d.ts +3 -0
  6. package/dist/builtin-extensions/lunr-computer-use.d.ts.map +1 -0
  7. package/dist/builtin-extensions/lunr-computer-use.js +179 -0
  8. package/dist/builtin-extensions/lunr-computer-use.js.map +1 -0
  9. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts.map +1 -1
  10. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js +1 -1
  11. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js.map +1 -1
  12. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts +4 -4
  13. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts.map +1 -1
  14. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js +8 -5
  15. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js.map +1 -1
  16. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.d.ts +1 -0
  17. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.d.ts.map +1 -1
  18. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js +11 -2
  19. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js.map +1 -1
  20. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.d.ts.map +1 -1
  21. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js +3 -3
  22. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js.map +1 -1
  23. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts.map +1 -1
  24. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js +15 -1
  25. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js.map +1 -1
  26. package/dist/builtin-extensions/pi-subagents/src/runs/shared/chain-outputs.d.ts +1 -0
  27. package/dist/builtin-extensions/pi-subagents/src/runs/shared/chain-outputs.d.ts.map +1 -1
  28. package/dist/builtin-extensions/pi-subagents/src/runs/shared/chain-outputs.js +5 -0
  29. package/dist/builtin-extensions/pi-subagents/src/runs/shared/chain-outputs.js.map +1 -1
  30. package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.d.ts +1 -1
  31. package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.d.ts.map +1 -1
  32. package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.js +2 -0
  33. package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.js.map +1 -1
  34. package/dist/builtin-extensions/pi-subagents/src/tui/communication.d.ts.map +1 -1
  35. package/dist/builtin-extensions/pi-subagents/src/tui/communication.js +1 -1
  36. package/dist/builtin-extensions/pi-subagents/src/tui/communication.js.map +1 -1
  37. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts +1 -0
  38. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts.map +1 -1
  39. package/dist/builtin-extensions/pi-subagents/src/tui/render.js +5 -3
  40. package/dist/builtin-extensions/pi-subagents/src/tui/render.js.map +1 -1
  41. package/dist/core/computer-use/adapter.d.ts +28 -0
  42. package/dist/core/computer-use/adapter.d.ts.map +1 -0
  43. package/dist/core/computer-use/adapter.js +133 -0
  44. package/dist/core/computer-use/adapter.js.map +1 -0
  45. package/dist/core/computer-use/image.d.ts +27 -0
  46. package/dist/core/computer-use/image.d.ts.map +1 -0
  47. package/dist/core/computer-use/image.js +72 -0
  48. package/dist/core/computer-use/image.js.map +1 -0
  49. package/dist/core/computer-use/lease.d.ts +18 -0
  50. package/dist/core/computer-use/lease.d.ts.map +1 -0
  51. package/dist/core/computer-use/lease.js +129 -0
  52. package/dist/core/computer-use/lease.js.map +1 -0
  53. package/dist/core/computer-use/macos-runtime.d.ts +14 -0
  54. package/dist/core/computer-use/macos-runtime.d.ts.map +1 -0
  55. package/dist/core/computer-use/macos-runtime.js +148 -0
  56. package/dist/core/computer-use/macos-runtime.js.map +1 -0
  57. package/dist/core/computer-use/policy.d.ts +19 -0
  58. package/dist/core/computer-use/policy.d.ts.map +1 -0
  59. package/dist/core/computer-use/policy.js +67 -0
  60. package/dist/core/computer-use/policy.js.map +1 -0
  61. package/dist/core/computer-use/release.generated.d.ts +31 -0
  62. package/dist/core/computer-use/release.generated.d.ts.map +1 -0
  63. package/dist/core/computer-use/release.generated.js +36 -0
  64. package/dist/core/computer-use/release.generated.js.map +1 -0
  65. package/dist/core/computer-use/runtime.d.ts +13 -0
  66. package/dist/core/computer-use/runtime.d.ts.map +1 -0
  67. package/dist/core/computer-use/runtime.js +158 -0
  68. package/dist/core/computer-use/runtime.js.map +1 -0
  69. package/dist/core/computer-use/schemas.d.ts +100 -0
  70. package/dist/core/computer-use/schemas.d.ts.map +1 -0
  71. package/dist/core/computer-use/schemas.js +79 -0
  72. package/dist/core/computer-use/schemas.js.map +1 -0
  73. package/dist/core/computer-use/windows-desktop.d.ts +3 -0
  74. package/dist/core/computer-use/windows-desktop.d.ts.map +1 -0
  75. package/dist/core/computer-use/windows-desktop.js +48 -0
  76. package/dist/core/computer-use/windows-desktop.js.map +1 -0
  77. package/dist/core/computer-use/workflow.d.ts +38 -0
  78. package/dist/core/computer-use/workflow.d.ts.map +1 -0
  79. package/dist/core/computer-use/workflow.js +560 -0
  80. package/dist/core/computer-use/workflow.js.map +1 -0
  81. package/dist/core/extensions/loader.d.ts.map +1 -1
  82. package/dist/core/extensions/loader.js +4 -0
  83. package/dist/core/extensions/loader.js.map +1 -1
  84. package/dist/core/permissions.d.ts +1 -1
  85. package/dist/core/permissions.d.ts.map +1 -1
  86. package/dist/core/permissions.js +14 -3
  87. package/dist/core/permissions.js.map +1 -1
  88. package/dist/core/plan-mode.d.ts.map +1 -1
  89. package/dist/core/plan-mode.js +9 -0
  90. package/dist/core/plan-mode.js.map +1 -1
  91. package/dist/core/settings-manager.d.ts +6 -0
  92. package/dist/core/settings-manager.d.ts.map +1 -1
  93. package/dist/core/settings-manager.js +19 -0
  94. package/dist/core/settings-manager.js.map +1 -1
  95. package/dist/core/subagent-event-transport.d.ts +3 -0
  96. package/dist/core/subagent-event-transport.d.ts.map +1 -0
  97. package/dist/core/subagent-event-transport.js +61 -0
  98. package/dist/core/subagent-event-transport.js.map +1 -0
  99. package/dist/gateway/adapters/discord.d.ts.map +1 -1
  100. package/dist/gateway/adapters/discord.js +2 -2
  101. package/dist/gateway/adapters/discord.js.map +1 -1
  102. package/dist/gateway/approval.d.ts.map +1 -1
  103. package/dist/gateway/approval.js +3 -3
  104. package/dist/gateway/approval.js.map +1 -1
  105. package/dist/gateway/authz.d.ts +4 -4
  106. package/dist/gateway/authz.d.ts.map +1 -1
  107. package/dist/gateway/authz.js +8 -12
  108. package/dist/gateway/authz.js.map +1 -1
  109. package/dist/gateway/commands.d.ts.map +1 -1
  110. package/dist/gateway/commands.js +2 -6
  111. package/dist/gateway/commands.js.map +1 -1
  112. package/dist/gateway/cron.d.ts.map +1 -1
  113. package/dist/gateway/cron.js +20 -6
  114. package/dist/gateway/cron.js.map +1 -1
  115. package/dist/gateway/index.d.ts.map +1 -1
  116. package/dist/gateway/index.js +3 -3
  117. package/dist/gateway/index.js.map +1 -1
  118. package/dist/gateway/mobile-commands.d.ts.map +1 -1
  119. package/dist/gateway/mobile-commands.js +8 -8
  120. package/dist/gateway/mobile-commands.js.map +1 -1
  121. package/dist/gateway/presenter.d.ts.map +1 -1
  122. package/dist/gateway/presenter.js +3 -3
  123. package/dist/gateway/presenter.js.map +1 -1
  124. package/dist/gateway/router.d.ts.map +1 -1
  125. package/dist/gateway/router.js +5 -7
  126. package/dist/gateway/router.js.map +1 -1
  127. package/dist/gateway/setup.d.ts.map +1 -1
  128. package/dist/gateway/setup.js +6 -4
  129. package/dist/gateway/setup.js.map +1 -1
  130. package/dist/modes/interactive/components/settings-selector.d.ts +4 -0
  131. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  132. package/dist/modes/interactive/components/settings-selector.js +20 -0
  133. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  134. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  135. package/dist/modes/interactive/interactive-mode.js +8 -0
  136. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  137. package/dist/modes/print-mode.d.ts.map +1 -1
  138. package/dist/modes/print-mode.js +9 -1
  139. package/dist/modes/print-mode.js.map +1 -1
  140. package/dist/node-runtime/adapter-VFG7WNPX.js +17 -0
  141. package/dist/node-runtime/{agent-session-services-YXXFCP5J.js → agent-session-services-5YXS2PLF.js} +10 -8
  142. package/dist/node-runtime/{builtin-extensions-ZLAX7TUH.js → builtin-extensions-I2HIFEMY.js} +3 -2
  143. package/dist/node-runtime/{chunk-3XPLBUEM.js → chunk-2CFDTH5B.js} +7 -3
  144. package/dist/node-runtime/chunk-2IDTZAG5.js +1415 -0
  145. package/dist/node-runtime/{chunk-QTU6AKNJ.js → chunk-3MISGG4S.js} +11 -15
  146. package/dist/node-runtime/chunk-4SBQAI4T.js +2185 -0
  147. package/dist/node-runtime/{chunk-TORR5XBV.js → chunk-5B5QN3ZQ.js} +12 -12
  148. package/dist/node-runtime/chunk-5B6VKFEV.js +180 -0
  149. package/dist/node-runtime/{chunk-AD6VXAO7.js → chunk-5Q4A37RB.js} +15 -104
  150. package/dist/node-runtime/{chunk-4TLO4PO2.js → chunk-7IQGBKBJ.js} +7 -1401
  151. package/dist/node-runtime/{chunk-OCVIXVTZ.js → chunk-D5UCKPBQ.js} +12 -1
  152. package/dist/node-runtime/{chunk-7S3T6YEA.js → chunk-DVQSHCVP.js} +1 -1
  153. package/dist/node-runtime/{chunk-HFVZUPFJ.js → chunk-E75WFZ33.js} +2 -2
  154. package/dist/node-runtime/chunk-FY4A63RK.js +73 -0
  155. package/dist/node-runtime/{chunk-6XL67VVY.js → chunk-I74LN5JG.js} +15 -0
  156. package/dist/node-runtime/chunk-IKU6Y7DL.js +287 -0
  157. package/dist/node-runtime/{chunk-WF7PGWRZ.js → chunk-L24TPBS4.js} +5 -4
  158. package/dist/node-runtime/{chunk-VQFSUJJG.js → chunk-LJTIVM7I.js} +7 -7
  159. package/dist/node-runtime/{chunk-DSIKTUPW.js → chunk-LWJXTA6Z.js} +274 -2433
  160. package/dist/node-runtime/{chunk-E4D3IGP2.js → chunk-P7XBVXGT.js} +6 -7
  161. package/dist/node-runtime/{chunk-OVX6X377.js → chunk-PTFBPO3C.js} +1 -1
  162. package/dist/node-runtime/{chunk-RC7DMV45.js → chunk-RCR3XAQY.js} +34 -6
  163. package/dist/node-runtime/chunk-RGQAEOM5.js +81 -0
  164. package/dist/node-runtime/{chunk-YCUZEGJG.js → chunk-SN2KSMPQ.js} +6 -3
  165. package/dist/node-runtime/{chunk-NRKGMJYU.js → chunk-SPVDBUOS.js} +1 -1
  166. package/dist/node-runtime/{chunk-IJIFHRTB.js → chunk-T4DA4XE7.js} +19 -19
  167. package/dist/node-runtime/chunk-U5F4IC7R.js +53 -0
  168. package/dist/node-runtime/{chunk-KWIYUUZK.js → chunk-UVWCDQYF.js} +27 -18
  169. package/dist/node-runtime/{chunk-ZXSGTGJ6.js → chunk-WIF24I7G.js} +21 -5
  170. package/dist/node-runtime/{chunk-2HHLJWGD.js → chunk-X3PGBLQ7.js} +2 -2
  171. package/dist/node-runtime/chunk-XHIRAZ26.js +108 -0
  172. package/dist/node-runtime/{chunk-YDO2F2IA.js → chunk-YWOJIGFY.js} +1 -1
  173. package/dist/node-runtime/{command-6WDOYKS6.js → command-FSBU2EVZ.js} +3 -3
  174. package/dist/node-runtime/{commands-NZLSOHPE.js → commands-EMBQQVVT.js} +4 -2
  175. package/dist/node-runtime/{commands-QC6VUZJQ.js → commands-NAAL7KNT.js} +5 -4
  176. package/dist/node-runtime/{direct-tool-executor-77GW4F5P.js → direct-tool-executor-4D7UXYU4.js} +7 -4
  177. package/dist/node-runtime/{gateway-INAYBF4Q.js → gateway-DQM333YV.js} +18 -19
  178. package/dist/node-runtime/index.js +23 -19
  179. package/dist/node-runtime/{init-HS6NQXSK.js → init-P5AUV77I.js} +4 -2
  180. package/dist/node-runtime/{install-cli-KL4UDDAD.js → install-cli-NN4YAU44.js} +1 -1
  181. package/dist/node-runtime/{interactive-mode-Z5M7WYYZ.js → interactive-mode-YOHZVMKX.js} +11 -9
  182. package/dist/node-runtime/lunr-computer-use-Y23CCDIJ.js +158 -0
  183. package/dist/node-runtime/{lunr-cron-LWIZJM2N.js → lunr-cron-USHC4LXH.js} +6 -4
  184. package/dist/node-runtime/main.js +16 -14
  185. package/dist/node-runtime/{mcp-auth-flow-QIXHW6QN.js → mcp-auth-flow-QVLFVLEO.js} +2 -1
  186. package/dist/node-runtime/{mobile-commands-J7OJEKFO.js → mobile-commands-O5LCHL32.js} +4 -3
  187. package/dist/node-runtime/{package-manager-cli-VYZM5WHN.js → package-manager-cli-Q7BL6KZO.js} +7 -6
  188. package/dist/node-runtime/{pi-mcp-adapter-5UKBZP44.js → pi-mcp-adapter-3PHF4KZ4.js} +5 -5
  189. package/dist/node-runtime/{pi-subagents-DNY7EAIG.js → pi-subagents-7IBSSGEW.js} +24 -18
  190. package/dist/node-runtime/{print-mode-XSFM26EL.js → print-mode-XQ76CYMT.js} +2 -1
  191. package/dist/node-runtime/{proxy-modes-LGEUYD7R.js → proxy-modes-5FAPSZEZ.js} +7 -4
  192. package/dist/node-runtime/rpc-entry.js +16 -14
  193. package/dist/node-runtime/runtime-EFPPD2UW.js +21 -0
  194. package/dist/node-runtime/{runtime-bridges-VRDDR73P.js → runtime-bridges-7WVCLI76.js} +3 -2
  195. package/dist/node-runtime/{settings-manager-J4SPQPDJ.js → settings-manager-DJDM5AHM.js} +2 -1
  196. package/dist/node-runtime/{setup-CQ2NLVK5.js → setup-PFBWRNUI.js} +10 -9
  197. package/dist/node-runtime/{subagent-executor-KZZQGAKV.js → subagent-executor-LPZ4BHWE.js} +29 -11
  198. package/dist/node-runtime/{update-cli-GTFJC7XK.js → update-cli-HNMOTNGX.js} +2 -1
  199. package/dist/node-runtime/workflow-OHPBEFO4.js +637 -0
  200. package/dist/utils/image-resize-core.d.ts +2 -0
  201. package/dist/utils/image-resize-core.d.ts.map +1 -1
  202. package/dist/utils/image-resize-core.js +10 -0
  203. package/dist/utils/image-resize-core.js.map +1 -1
  204. package/docs/computer-use.md +362 -0
  205. package/docs/features.md +8 -6
  206. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  207. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  208. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  209. package/examples/extensions/gondolin/package-lock.json +2 -2
  210. package/examples/extensions/gondolin/package.json +1 -1
  211. package/examples/extensions/sandbox/package-lock.json +2 -2
  212. package/examples/extensions/sandbox/package.json +1 -1
  213. package/examples/extensions/with-deps/package-lock.json +2 -2
  214. package/examples/extensions/with-deps/package.json +1 -1
  215. package/install-lock/package-lock.json +3841 -0
  216. package/install-lock/package.json +18 -0
  217. package/native/computer-use/LICENSE.md +21 -0
  218. package/npm-shrinkwrap.json +3822 -0
  219. package/package.json +15 -9
  220. package/dist/node-runtime/{chunk-LQP3RB67.js → chunk-RAK2QA2B.js} +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/gateway/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAUH,OAAO,EAGN,KAAK,cAAc,EAInB,MAAM,aAAa,CAAC;AAQrB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,eAAe,CAGtF,CAAC;AAyOF,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAchE","sourcesContent":["/**\n * lunR: gateway daemon entry (Phase 2a skeleton; Phase 2b registered the\n * Telegram adapter, Phase 3 the Discord adapter — both via ADAPTER_FACTORIES).\n *\n * `lunr gateway` runs the daemon: load gateway.json, instantiate every\n * enabled platform that has a registered adapter factory AND a resolvable\n * token, connect, route messages, and stay alive until SIGINT/SIGTERM.\n * With no runnable adapters it prints per-platform setup instructions and\n * exits 1.\n *\n * Sub-commands:\n * lunr gateway pair approve <platform> <code> approve a pairing code\n * lunr gateway pair list show pending + paired\n * lunr gateway status config/token/session summary\n */\n\nimport { isFeatureEnabled } from \"../core/install-features.ts\";\nimport { DiscordAdapter } from \"./adapters/discord.ts\";\nimport { TelegramAdapter } from \"./adapters/telegram.ts\";\nimport { AgentBridge } from \"./agent-bridge.ts\";\nimport { handleApprovalCallback } from \"./approval.ts\";\nimport { addAllowedUser } from \"./authz.ts\";\nimport { handleCallback, startButtonSweeper, stopButtonSweeper } from \"./buttons.ts\";\nimport { botCommandSpecs } from \"./commands.ts\";\nimport {\n\ttype DiscordConfig,\n\tloadGatewayConfig,\n\ttype PlatformConfig,\n\tplatformConfigFor,\n\tresolvePlatformToken,\n\tsaveGatewayConfig,\n} from \"./config.ts\";\nimport { startGatewayCron } from \"./cron.ts\";\nimport { createPairingStore } from \"./pairing.ts\";\nimport { startGatewayPresenter, stopGatewayPresenter } from \"./presenter.ts\";\nimport { createRouter } from \"./router.ts\";\nimport { claimGateway } from \"./service.ts\";\nimport { buildSessionKey } from \"./session-keys.ts\";\nimport { listSessions } from \"./store.ts\";\nimport type { PlatformAdapter } from \"./types.ts\";\n\n/**\n * Adapter registry — the seam adapters plug into:\n *\n * ADAPTER_FACTORIES.<platform> = (cfg) => new SomeAdapter(cfg);\n *\n * A factory receives the sanitized platform config with the token already\n * resolved into cfg.token (env beats file — see resolvePlatformToken and\n * runDaemon below). The registry type is the PlatformConfig base; adapters\n * with extra fields (Discord) narrow with a cast — the daemon always passes\n * the full sanitized per-platform object.\n */\nexport const ADAPTER_FACTORIES: Record<string, (cfg: PlatformConfig) => PlatformAdapter> = {\n\ttelegram: (cfg) => new TelegramAdapter(cfg),\n\tdiscord: (cfg) => new DiscordAdapter(cfg as DiscordConfig),\n};\n\nconst KNOWN_PLATFORMS = [\"telegram\", \"discord\"] as const;\n\nconst SETUP_INSTRUCTIONS: Record<string, string[]> = {\n\ttelegram: [\"Telegram: run lunr gateway setup locally to configure the bot token and owner ID.\"],\n\tdiscord: [\"Discord: run lunr gateway setup locally to configure the bot token and owner ID.\"],\n};\n\nfunction printSetupInstructions(enabledButUnrunnable: string[]): void {\n\tconsole.log(\"lunR gateway: no runnable platforms configured.\");\n\tfor (const platform of enabledButUnrunnable) {\n\t\tconsole.log(` (platform \"${platform}\" is enabled but has no registered adapter or resolvable token)`);\n\t}\n\tfor (const platform of KNOWN_PLATFORMS) {\n\t\tfor (const line of SETUP_INSTRUCTIONS[platform]) {\n\t\t\tconsole.log(line);\n\t\t}\n\t}\n}\n\nasync function runPairApprove(platform: string, code: string | undefined, owner = false): Promise<number> {\n\tif (!code) {\n\t\tconsole.error(\"Usage: lunr gateway pair approve <platform> <code> [--owner]\");\n\t\treturn 1;\n\t}\n\tconst pairing = createPairingStore();\n\tconst userId = pairing.approve(platform, code);\n\tif (userId === null) {\n\t\tconsole.error(`Pairing failed: no valid pending code \"${code}\" for ${platform} (wrong, expired, or locked out).`);\n\t\treturn 1;\n\t}\n\ttry {\n\t\tif (owner) {\n\t\t\tconst cfg = loadGatewayConfig();\n\t\t\tif (platform !== \"telegram\" && platform !== \"discord\") throw new Error(\"Unknown platform.\");\n\t\t\tcfg.owners ??= { telegram: [], discord: [] };\n\t\t\tcfg.owners[platform] = [...new Set([...cfg.owners[platform], userId])];\n\t\t\tcfg[platform].allowedUsers = [...new Set([...cfg[platform].allowedUsers, userId])];\n\t\t\tsaveGatewayConfig(cfg);\n\t\t} else addAllowedUser(platform, userId);\n\t} catch (err) {\n\t\tconsole.error(`Paired ${platform} user ${userId}, but could not persist allowedUsers: ${err}`);\n\t\treturn 1;\n\t}\n\tconsole.log(\n\t\t`Approved ${platform} user ${userId}${owner ? \" as owner with local project and TUI access\" : \" for chat only; owner access requires local --owner approval\"}.`,\n\t);\n\treturn 0;\n}\n\nfunction runPairList(): number {\n\tconst pairing = createPairingStore();\n\tconst pending = pairing.listPending();\n\tconst approved = pairing.listApproved();\n\tconsole.log(\"Pending pairing codes:\");\n\tif (pending.length === 0) {\n\t\tconsole.log(\" (none)\");\n\t} else {\n\t\tfor (const p of pending) {\n\t\t\tconsole.log(` ${p.platform} ${p.code} user=${p.userId} expires ${new Date(p.expiresAt).toISOString()}`);\n\t\t}\n\t}\n\tconsole.log(\"Paired users:\");\n\tif (approved.length === 0) {\n\t\tconsole.log(\" (none)\");\n\t} else {\n\t\tfor (const u of approved) {\n\t\t\tconsole.log(` ${u.platform} ${u.userId} approved ${new Date(u.approvedAt).toISOString()}`);\n\t\t}\n\t}\n\treturn 0;\n}\n\nfunction runStatus(): number {\n\tconst cfg = loadGatewayConfig();\n\tconsole.log(\n\t\t`chat-platforms: ${isFeatureEnabled(\"chat-platforms\") ? \"enabled\" : \"disabled\"} (install-features.json)`,\n\t);\n\tconsole.log(\"lunR gateway status:\");\n\tfor (const platform of KNOWN_PLATFORMS) {\n\t\tconst platformCfg = platformConfigFor(cfg, platform);\n\t\tif (!platformCfg) continue;\n\t\tconst token = resolvePlatformToken(platform, platformCfg);\n\t\tconst adapter = ADAPTER_FACTORIES[platform] === undefined ? \"no adapter\" : \"adapter registered\";\n\t\tconsole.log(\n\t\t\t` ${platform}: ${platformCfg.enabled ? \"enabled\" : \"disabled\"} · token ${token ? \"resolved\" : \"missing\"} · ${adapter}`,\n\t\t);\n\t}\n\tconst sessions = listSessions();\n\tconsole.log(` sessions: ${Object.keys(sessions).length} stored`);\n\treturn 0;\n}\n\nasync function runDaemon(): Promise<number> {\n\tif (!isFeatureEnabled(\"chat-platforms\")) {\n\t\tconsole.error(\"lunR gateway: chat platforms are not enabled.\");\n\t\tconsole.error(\"Enable with: lunr features enable chat-platforms\");\n\t\treturn 1;\n\t}\n\tconst cfg = loadGatewayConfig();\n\tconst adapters = new Map<string, PlatformAdapter>();\n\tconst skipped: string[] = [];\n\n\tfor (const platform of KNOWN_PLATFORMS) {\n\t\tconst platformCfg = platformConfigFor(cfg, platform);\n\t\tif (!platformCfg?.enabled) continue;\n\t\tconst factory: ((cfg: PlatformConfig) => PlatformAdapter) | undefined = ADAPTER_FACTORIES[platform];\n\t\tif (factory === undefined) {\n\t\t\tskipped.push(platform);\n\t\t\tcontinue;\n\t\t}\n\t\tconst token = resolvePlatformToken(platform, platformCfg);\n\t\tif (!token) {\n\t\t\tskipped.push(platform);\n\t\t\tcontinue;\n\t\t}\n\t\tadapters.set(platform, factory({ ...platformCfg, token }));\n\t}\n\n\tif (adapters.size === 0) {\n\t\tprintSetupInstructions(skipped);\n\t\treturn 1;\n\t}\n\n\tconst pairing = createPairingStore();\n\tconst bridge = new AgentBridge();\n\tfor (const adapter of adapters.values())\n\t\tadapter.setCommandSuggestions?.(async (source, command) => {\n\t\t\tconst session = bridge.peekSession(buildSessionKey(source, loadGatewayConfig()));\n\t\t\tif (!session) return [];\n\t\t\tif (command === \"thinking\") return session.getAvailableThinkingLevels();\n\t\t\tif (command === \"model\")\n\t\t\t\treturn (await session.modelRuntime.getAvailable()).map((model) => `${model.provider}/${model.id}`);\n\t\t\treturn [];\n\t\t});\n\tconst router = createRouter({ adapters, cfg, pairing, bridge, reloadConfig: true, remoteControls: true });\n\n\tlet finish!: () => void;\n\tconst stopped = new Promise<void>((resolve) => {\n\t\tfinish = resolve;\n\t});\n\tlet stopping = false;\n\tlet cron: ReturnType<typeof startGatewayCron> | undefined;\n\tconst platformStatus: Record<string, string> = {};\n\tconst retries = new Set<ReturnType<typeof setTimeout>>();\n\tconst shutdown = () => {\n\t\tif (stopping) return;\n\t\tstopping = true;\n\t\towner.update(platformStatus, \"stopping\");\n\t\tcron?.stop();\n\t\tfor (const timer of retries) clearTimeout(timer);\n\t\tstopButtonSweeper();\n\t\tvoid (async () => {\n\t\t\tawait bridge.shutdown();\n\t\t\tawait Promise.all([...adapters.values()].map((adapter) => adapter.disconnect().catch(() => {})));\n\t\t\tstopGatewayPresenter();\n\t\t\towner.release();\n\t\t\tprocess.removeListener(\"SIGINT\", shutdown);\n\t\t\tprocess.removeListener(\"SIGTERM\", shutdown);\n\t\t\tfinish();\n\t\t})().catch((error) => {\n\t\t\tconsole.error(\"Gateway shutdown failed:\", error instanceof Error ? error.message : String(error));\n\t\t\tstopping = false;\n\t\t\towner.update(platformStatus, Object.values(platformStatus).includes(\"connected\") ? \"ready\" : \"starting\");\n\t\t\tstartButtonSweeper();\n\t\t\tcron = startGatewayCron({ adapters, cfg });\n\t\t\tfor (const [platform, adapter] of adapters)\n\t\t\t\tif (platformStatus[platform] !== \"connected\") void connect(platform, adapter);\n\t\t});\n\t};\n\tconst owner = claimGateway(shutdown);\n\tprocess.on(\"SIGINT\", shutdown);\n\tprocess.on(\"SIGTERM\", shutdown);\n\tstartGatewayPresenter(adapters);\n\tstartButtonSweeper();\n\tconst connect = async (platform: string, adapter: PlatformAdapter): Promise<void> => {\n\t\tif (stopping) return;\n\t\ttry {\n\t\t\tplatformStatus[platform] = \"connecting\";\n\t\t\towner.update(platformStatus, Object.values(platformStatus).includes(\"connected\") ? \"ready\" : \"starting\");\n\t\t\tconst ok = await adapter.connect();\n\t\t\tif (!ok) {\n\t\t\t\tthrow new Error(\"Connection failed\");\n\t\t\t}\n\t\t\tadapter.onMessage((event) => {\n\t\t\t\tvoid router.handleEvent(event);\n\t\t\t});\n\t\t\tadapter.onCallback(async (event) => {\n\t\t\t\tconst consumed = await handleApprovalCallback(event, adapter);\n\t\t\t\tif (!consumed) {\n\t\t\t\t\tvoid handleCallback(event, { adapters, cfg: loadGatewayConfig(), pairing, bridge, adapter });\n\t\t\t\t}\n\t\t\t});\n\t\t\ttry {\n\t\t\t\tawait adapter.registerCommands?.(botCommandSpecs());\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`[gateway] ${platform}: command menu registration failed: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (stopping) {\n\t\t\t\tawait adapter.disconnect();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tplatformStatus[platform] = \"connected\";\n\t\t\tconsole.log(`lunR gateway: ${platform} connected`);\n\t\t} catch {\n\t\t\tplatformStatus[platform] = \"disconnected, retrying (check gateway logs)\";\n\t\t\tawait adapter.disconnect().catch(() => {});\n\t\t\tif (!stopping) {\n\t\t\t\tconst retry = setTimeout(() => {\n\t\t\t\t\tretries.delete(retry);\n\t\t\t\t\tvoid connect(platform, adapter);\n\t\t\t\t}, 5000);\n\t\t\t\tretries.add(retry);\n\t\t\t}\n\t\t}\n\t\tif (!stopping)\n\t\t\towner.update(platformStatus, Object.values(platformStatus).includes(\"connected\") ? \"ready\" : \"starting\");\n\t};\n\tfor (const [platform, adapter] of adapters) void connect(platform, adapter);\n\n\t// Phase 4: cron jobs fire inside the daemon and deliver back to chats.\n\t// Starts even with zero stored jobs — jobs can be created later from chats.\n\tcron = startGatewayCron({ adapters, cfg });\n\tconsole.log(`lunR gateway: cron scheduler started (${cron.intervalMs / 1000}s interval)`);\n\n\towner.update(platformStatus, \"starting\");\n\tawait stopped;\n\treturn 0;\n}\n\nexport async function runGateway(args: string[]): Promise<number> {\n\tconst [sub, ...rest] = args;\n\tif (sub === \"pair\") {\n\t\tconst [action, platform, code, flag] = rest;\n\t\tif (action === \"approve\" && platform && (flag === undefined || flag === \"--owner\"))\n\t\t\treturn runPairApprove(platform, code, flag === \"--owner\");\n\t\tif (action === \"list\") return runPairList();\n\t\tconsole.error(\"Usage: lunr gateway pair approve <platform> <code> [--owner] | lunr gateway pair list\");\n\t\treturn 1;\n\t}\n\tif (sub === \"status\") return runStatus();\n\tif (sub === undefined) return runDaemon();\n\tconsole.error(`Unknown gateway sub-command \"${sub}\". Expected: pair approve | pair list | status`);\n\treturn 1;\n}\n"]}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/gateway/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAUH,OAAO,EAGN,KAAK,cAAc,EAInB,MAAM,aAAa,CAAC;AAQrB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,eAAe,CAGtF,CAAC;AAuOF,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAchE","sourcesContent":["/**\n * lunR: gateway daemon entry (Phase 2a skeleton; Phase 2b registered the\n * Telegram adapter, Phase 3 the Discord adapter — both via ADAPTER_FACTORIES).\n *\n * `lunr gateway` runs the daemon: load gateway.json, instantiate every\n * enabled platform that has a registered adapter factory AND a resolvable\n * token, connect, route messages, and stay alive until SIGINT/SIGTERM.\n * With no runnable adapters it prints per-platform setup instructions and\n * exits 1.\n *\n * Sub-commands:\n * lunr gateway pair approve <platform> <code> approve a pairing code\n * lunr gateway pair list show pending + paired\n * lunr gateway status config/token/session summary\n */\n\nimport { isFeatureEnabled } from \"../core/install-features.ts\";\nimport { DiscordAdapter } from \"./adapters/discord.ts\";\nimport { TelegramAdapter } from \"./adapters/telegram.ts\";\nimport { AgentBridge } from \"./agent-bridge.ts\";\nimport { handleApprovalCallback } from \"./approval.ts\";\nimport { addAllowedUser } from \"./authz.ts\";\nimport { handleCallback, startButtonSweeper, stopButtonSweeper } from \"./buttons.ts\";\nimport { botCommandSpecs } from \"./commands.ts\";\nimport {\n\ttype DiscordConfig,\n\tloadGatewayConfig,\n\ttype PlatformConfig,\n\tplatformConfigFor,\n\tresolvePlatformToken,\n\tsaveGatewayConfig,\n} from \"./config.ts\";\nimport { startGatewayCron } from \"./cron.ts\";\nimport { createPairingStore } from \"./pairing.ts\";\nimport { startGatewayPresenter, stopGatewayPresenter } from \"./presenter.ts\";\nimport { createRouter } from \"./router.ts\";\nimport { claimGateway } from \"./service.ts\";\nimport { buildSessionKey } from \"./session-keys.ts\";\nimport { listSessions } from \"./store.ts\";\nimport type { PlatformAdapter } from \"./types.ts\";\n\n/**\n * Adapter registry — the seam adapters plug into:\n *\n * ADAPTER_FACTORIES.<platform> = (cfg) => new SomeAdapter(cfg);\n *\n * A factory receives the sanitized platform config with the token already\n * resolved into cfg.token (env beats file — see resolvePlatformToken and\n * runDaemon below). The registry type is the PlatformConfig base; adapters\n * with extra fields (Discord) narrow with a cast — the daemon always passes\n * the full sanitized per-platform object.\n */\nexport const ADAPTER_FACTORIES: Record<string, (cfg: PlatformConfig) => PlatformAdapter> = {\n\ttelegram: (cfg) => new TelegramAdapter(cfg),\n\tdiscord: (cfg) => new DiscordAdapter(cfg as DiscordConfig),\n};\n\nconst KNOWN_PLATFORMS = [\"telegram\", \"discord\"] as const;\n\nconst SETUP_INSTRUCTIONS: Record<string, string[]> = {\n\ttelegram: [\"Telegram: run lunr gateway setup locally to configure the bot token and approved user ID.\"],\n\tdiscord: [\"Discord: run lunr gateway setup locally to configure the bot token and approved user ID.\"],\n};\n\nfunction printSetupInstructions(enabledButUnrunnable: string[]): void {\n\tconsole.log(\"lunR gateway: no runnable platforms configured.\");\n\tfor (const platform of enabledButUnrunnable) {\n\t\tconsole.log(` (platform \"${platform}\" is enabled but has no registered adapter or resolvable token)`);\n\t}\n\tfor (const platform of KNOWN_PLATFORMS) {\n\t\tfor (const line of SETUP_INSTRUCTIONS[platform]) {\n\t\t\tconsole.log(line);\n\t\t}\n\t}\n}\n\nasync function runPairApprove(platform: string, code: string | undefined, owner = false): Promise<number> {\n\tif (!code) {\n\t\tconsole.error(\"Usage: lunr gateway pair approve <platform> <code> [--owner]\");\n\t\treturn 1;\n\t}\n\tconst pairing = createPairingStore();\n\tconst userId = pairing.approve(platform, code);\n\tif (userId === null) {\n\t\tconsole.error(`Pairing failed: no valid pending code \"${code}\" for ${platform} (wrong, expired, or locked out).`);\n\t\treturn 1;\n\t}\n\ttry {\n\t\tif (owner) {\n\t\t\tconst cfg = loadGatewayConfig();\n\t\t\tif (platform !== \"telegram\" && platform !== \"discord\") throw new Error(\"Unknown platform.\");\n\t\t\tcfg.owners ??= { telegram: [], discord: [] };\n\t\t\tcfg.owners[platform] = [...new Set([...cfg.owners[platform], userId])];\n\t\t\tcfg[platform].allowedUsers = [...new Set([...cfg[platform].allowedUsers, userId])];\n\t\t\tsaveGatewayConfig(cfg);\n\t\t} else addAllowedUser(platform, userId);\n\t} catch (err) {\n\t\tconsole.error(`Paired ${platform} user ${userId}, but could not persist allowedUsers: ${err}`);\n\t\treturn 1;\n\t}\n\tconsole.log(`Approved ${platform} user ${userId}. Gateway access includes local projects and saved sessions.`);\n\treturn 0;\n}\n\nfunction runPairList(): number {\n\tconst pairing = createPairingStore();\n\tconst pending = pairing.listPending();\n\tconst approved = pairing.listApproved();\n\tconsole.log(\"Pending pairing codes:\");\n\tif (pending.length === 0) {\n\t\tconsole.log(\" (none)\");\n\t} else {\n\t\tfor (const p of pending) {\n\t\t\tconsole.log(` ${p.platform} ${p.code} user=${p.userId} expires ${new Date(p.expiresAt).toISOString()}`);\n\t\t}\n\t}\n\tconsole.log(\"Paired users:\");\n\tif (approved.length === 0) {\n\t\tconsole.log(\" (none)\");\n\t} else {\n\t\tfor (const u of approved) {\n\t\t\tconsole.log(` ${u.platform} ${u.userId} approved ${new Date(u.approvedAt).toISOString()}`);\n\t\t}\n\t}\n\treturn 0;\n}\n\nfunction runStatus(): number {\n\tconst cfg = loadGatewayConfig();\n\tconsole.log(\n\t\t`chat-platforms: ${isFeatureEnabled(\"chat-platforms\") ? \"enabled\" : \"disabled\"} (install-features.json)`,\n\t);\n\tconsole.log(\"lunR gateway status:\");\n\tfor (const platform of KNOWN_PLATFORMS) {\n\t\tconst platformCfg = platformConfigFor(cfg, platform);\n\t\tif (!platformCfg) continue;\n\t\tconst token = resolvePlatformToken(platform, platformCfg);\n\t\tconst adapter = ADAPTER_FACTORIES[platform] === undefined ? \"no adapter\" : \"adapter registered\";\n\t\tconsole.log(\n\t\t\t` ${platform}: ${platformCfg.enabled ? \"enabled\" : \"disabled\"} · token ${token ? \"resolved\" : \"missing\"} · ${adapter}`,\n\t\t);\n\t}\n\tconst sessions = listSessions();\n\tconsole.log(` sessions: ${Object.keys(sessions).length} stored`);\n\treturn 0;\n}\n\nasync function runDaemon(): Promise<number> {\n\tif (!isFeatureEnabled(\"chat-platforms\")) {\n\t\tconsole.error(\"lunR gateway: chat platforms are not enabled.\");\n\t\tconsole.error(\"Enable with: lunr features enable chat-platforms\");\n\t\treturn 1;\n\t}\n\tconst cfg = loadGatewayConfig();\n\tconst adapters = new Map<string, PlatformAdapter>();\n\tconst skipped: string[] = [];\n\n\tfor (const platform of KNOWN_PLATFORMS) {\n\t\tconst platformCfg = platformConfigFor(cfg, platform);\n\t\tif (!platformCfg?.enabled) continue;\n\t\tconst factory: ((cfg: PlatformConfig) => PlatformAdapter) | undefined = ADAPTER_FACTORIES[platform];\n\t\tif (factory === undefined) {\n\t\t\tskipped.push(platform);\n\t\t\tcontinue;\n\t\t}\n\t\tconst token = resolvePlatformToken(platform, platformCfg);\n\t\tif (!token) {\n\t\t\tskipped.push(platform);\n\t\t\tcontinue;\n\t\t}\n\t\tadapters.set(platform, factory({ ...platformCfg, token }));\n\t}\n\n\tif (adapters.size === 0) {\n\t\tprintSetupInstructions(skipped);\n\t\treturn 1;\n\t}\n\n\tconst pairing = createPairingStore();\n\tconst bridge = new AgentBridge();\n\tfor (const adapter of adapters.values())\n\t\tadapter.setCommandSuggestions?.(async (source, command) => {\n\t\t\tconst session = bridge.peekSession(buildSessionKey(source, loadGatewayConfig()));\n\t\t\tif (!session) return [];\n\t\t\tif (command === \"thinking\") return session.getAvailableThinkingLevels();\n\t\t\tif (command === \"model\")\n\t\t\t\treturn (await session.modelRuntime.getAvailable()).map((model) => `${model.provider}/${model.id}`);\n\t\t\treturn [];\n\t\t});\n\tconst router = createRouter({ adapters, cfg, pairing, bridge, reloadConfig: true, remoteControls: true });\n\n\tlet finish!: () => void;\n\tconst stopped = new Promise<void>((resolve) => {\n\t\tfinish = resolve;\n\t});\n\tlet stopping = false;\n\tlet cron: ReturnType<typeof startGatewayCron> | undefined;\n\tconst platformStatus: Record<string, string> = {};\n\tconst retries = new Set<ReturnType<typeof setTimeout>>();\n\tconst shutdown = () => {\n\t\tif (stopping) return;\n\t\tstopping = true;\n\t\towner.update(platformStatus, \"stopping\");\n\t\tcron?.stop();\n\t\tfor (const timer of retries) clearTimeout(timer);\n\t\tstopButtonSweeper();\n\t\tvoid (async () => {\n\t\t\tawait bridge.shutdown();\n\t\t\tawait Promise.all([...adapters.values()].map((adapter) => adapter.disconnect().catch(() => {})));\n\t\t\tstopGatewayPresenter();\n\t\t\towner.release();\n\t\t\tprocess.removeListener(\"SIGINT\", shutdown);\n\t\t\tprocess.removeListener(\"SIGTERM\", shutdown);\n\t\t\tfinish();\n\t\t})().catch((error) => {\n\t\t\tconsole.error(\"Gateway shutdown failed:\", error instanceof Error ? error.message : String(error));\n\t\t\tstopping = false;\n\t\t\towner.update(platformStatus, Object.values(platformStatus).includes(\"connected\") ? \"ready\" : \"starting\");\n\t\t\tstartButtonSweeper();\n\t\t\tcron = startGatewayCron({ adapters, cfg });\n\t\t\tfor (const [platform, adapter] of adapters)\n\t\t\t\tif (platformStatus[platform] !== \"connected\") void connect(platform, adapter);\n\t\t});\n\t};\n\tconst owner = claimGateway(shutdown);\n\tprocess.on(\"SIGINT\", shutdown);\n\tprocess.on(\"SIGTERM\", shutdown);\n\tstartGatewayPresenter(adapters);\n\tstartButtonSweeper();\n\tconst connect = async (platform: string, adapter: PlatformAdapter): Promise<void> => {\n\t\tif (stopping) return;\n\t\ttry {\n\t\t\tplatformStatus[platform] = \"connecting\";\n\t\t\towner.update(platformStatus, Object.values(platformStatus).includes(\"connected\") ? \"ready\" : \"starting\");\n\t\t\tconst ok = await adapter.connect();\n\t\t\tif (!ok) {\n\t\t\t\tthrow new Error(\"Connection failed\");\n\t\t\t}\n\t\t\tadapter.onMessage((event) => {\n\t\t\t\tvoid router.handleEvent(event);\n\t\t\t});\n\t\t\tadapter.onCallback(async (event) => {\n\t\t\t\tconst consumed = await handleApprovalCallback(event, adapter);\n\t\t\t\tif (!consumed) {\n\t\t\t\t\tvoid handleCallback(event, { adapters, cfg: loadGatewayConfig(), pairing, bridge, adapter });\n\t\t\t\t}\n\t\t\t});\n\t\t\ttry {\n\t\t\t\tawait adapter.registerCommands?.(botCommandSpecs());\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`[gateway] ${platform}: command menu registration failed: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (stopping) {\n\t\t\t\tawait adapter.disconnect();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tplatformStatus[platform] = \"connected\";\n\t\t\tconsole.log(`lunR gateway: ${platform} connected`);\n\t\t} catch {\n\t\t\tplatformStatus[platform] = \"disconnected, retrying (check gateway logs)\";\n\t\t\tawait adapter.disconnect().catch(() => {});\n\t\t\tif (!stopping) {\n\t\t\t\tconst retry = setTimeout(() => {\n\t\t\t\t\tretries.delete(retry);\n\t\t\t\t\tvoid connect(platform, adapter);\n\t\t\t\t}, 5000);\n\t\t\t\tretries.add(retry);\n\t\t\t}\n\t\t}\n\t\tif (!stopping)\n\t\t\towner.update(platformStatus, Object.values(platformStatus).includes(\"connected\") ? \"ready\" : \"starting\");\n\t};\n\tfor (const [platform, adapter] of adapters) void connect(platform, adapter);\n\n\t// Phase 4: cron jobs fire inside the daemon and deliver back to chats.\n\t// Starts even with zero stored jobs — jobs can be created later from chats.\n\tcron = startGatewayCron({ adapters, cfg });\n\tconsole.log(`lunR gateway: cron scheduler started (${cron.intervalMs / 1000}s interval)`);\n\n\towner.update(platformStatus, \"starting\");\n\tawait stopped;\n\treturn 0;\n}\n\nexport async function runGateway(args: string[]): Promise<number> {\n\tconst [sub, ...rest] = args;\n\tif (sub === \"pair\") {\n\t\tconst [action, platform, code, flag] = rest;\n\t\tif (action === \"approve\" && platform && (flag === undefined || flag === \"--owner\"))\n\t\t\treturn runPairApprove(platform, code, flag === \"--owner\");\n\t\tif (action === \"list\") return runPairList();\n\t\tconsole.error(\"Usage: lunr gateway pair approve <platform> <code> [--owner] | lunr gateway pair list\");\n\t\treturn 1;\n\t}\n\tif (sub === \"status\") return runStatus();\n\tif (sub === undefined) return runDaemon();\n\tconsole.error(`Unknown gateway sub-command \"${sub}\". Expected: pair approve | pair list | status`);\n\treturn 1;\n}\n"]}
@@ -46,8 +46,8 @@ export const ADAPTER_FACTORIES = {
46
46
  };
47
47
  const KNOWN_PLATFORMS = ["telegram", "discord"];
48
48
  const SETUP_INSTRUCTIONS = {
49
- telegram: ["Telegram: run lunr gateway setup locally to configure the bot token and owner ID."],
50
- discord: ["Discord: run lunr gateway setup locally to configure the bot token and owner ID."],
49
+ telegram: ["Telegram: run lunr gateway setup locally to configure the bot token and approved user ID."],
50
+ discord: ["Discord: run lunr gateway setup locally to configure the bot token and approved user ID."],
51
51
  };
52
52
  function printSetupInstructions(enabledButUnrunnable) {
53
53
  console.log("lunR gateway: no runnable platforms configured.");
@@ -88,7 +88,7 @@ async function runPairApprove(platform, code, owner = false) {
88
88
  console.error(`Paired ${platform} user ${userId}, but could not persist allowedUsers: ${err}`);
89
89
  return 1;
90
90
  }
91
- console.log(`Approved ${platform} user ${userId}${owner ? " as owner with local project and TUI access" : " for chat only; owner access requires local --owner approval"}.`);
91
+ console.log(`Approved ${platform} user ${userId}. Gateway access includes local projects and saved sessions.`);
92
92
  return 0;
93
93
  }
94
94
  function runPairList() {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/gateway/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * lunR: gateway daemon entry (Phase 2a skeleton; Phase 2b registered the\n * Telegram adapter, Phase 3 the Discord adapter — both via ADAPTER_FACTORIES).\n *\n * `lunr gateway` runs the daemon: load gateway.json, instantiate every\n * enabled platform that has a registered adapter factory AND a resolvable\n * token, connect, route messages, and stay alive until SIGINT/SIGTERM.\n * With no runnable adapters it prints per-platform setup instructions and\n * exits 1.\n *\n * Sub-commands:\n * lunr gateway pair approve <platform> <code> approve a pairing code\n * lunr gateway pair list show pending + paired\n * lunr gateway status config/token/session summary\n */\n\nimport { isFeatureEnabled } from \"../core/install-features.ts\";\nimport { DiscordAdapter } from \"./adapters/discord.ts\";\nimport { TelegramAdapter } from \"./adapters/telegram.ts\";\nimport { AgentBridge } from \"./agent-bridge.ts\";\nimport { handleApprovalCallback } from \"./approval.ts\";\nimport { addAllowedUser } from \"./authz.ts\";\nimport { handleCallback, startButtonSweeper, stopButtonSweeper } from \"./buttons.ts\";\nimport { botCommandSpecs } from \"./commands.ts\";\nimport {\n\ttype DiscordConfig,\n\tloadGatewayConfig,\n\ttype PlatformConfig,\n\tplatformConfigFor,\n\tresolvePlatformToken,\n\tsaveGatewayConfig,\n} from \"./config.ts\";\nimport { startGatewayCron } from \"./cron.ts\";\nimport { createPairingStore } from \"./pairing.ts\";\nimport { startGatewayPresenter, stopGatewayPresenter } from \"./presenter.ts\";\nimport { createRouter } from \"./router.ts\";\nimport { claimGateway } from \"./service.ts\";\nimport { buildSessionKey } from \"./session-keys.ts\";\nimport { listSessions } from \"./store.ts\";\nimport type { PlatformAdapter } from \"./types.ts\";\n\n/**\n * Adapter registry — the seam adapters plug into:\n *\n * ADAPTER_FACTORIES.<platform> = (cfg) => new SomeAdapter(cfg);\n *\n * A factory receives the sanitized platform config with the token already\n * resolved into cfg.token (env beats file — see resolvePlatformToken and\n * runDaemon below). The registry type is the PlatformConfig base; adapters\n * with extra fields (Discord) narrow with a cast — the daemon always passes\n * the full sanitized per-platform object.\n */\nexport const ADAPTER_FACTORIES: Record<string, (cfg: PlatformConfig) => PlatformAdapter> = {\n\ttelegram: (cfg) => new TelegramAdapter(cfg),\n\tdiscord: (cfg) => new DiscordAdapter(cfg as DiscordConfig),\n};\n\nconst KNOWN_PLATFORMS = [\"telegram\", \"discord\"] as const;\n\nconst SETUP_INSTRUCTIONS: Record<string, string[]> = {\n\ttelegram: [\"Telegram: run lunr gateway setup locally to configure the bot token and owner ID.\"],\n\tdiscord: [\"Discord: run lunr gateway setup locally to configure the bot token and owner ID.\"],\n};\n\nfunction printSetupInstructions(enabledButUnrunnable: string[]): void {\n\tconsole.log(\"lunR gateway: no runnable platforms configured.\");\n\tfor (const platform of enabledButUnrunnable) {\n\t\tconsole.log(` (platform \"${platform}\" is enabled but has no registered adapter or resolvable token)`);\n\t}\n\tfor (const platform of KNOWN_PLATFORMS) {\n\t\tfor (const line of SETUP_INSTRUCTIONS[platform]) {\n\t\t\tconsole.log(line);\n\t\t}\n\t}\n}\n\nasync function runPairApprove(platform: string, code: string | undefined, owner = false): Promise<number> {\n\tif (!code) {\n\t\tconsole.error(\"Usage: lunr gateway pair approve <platform> <code> [--owner]\");\n\t\treturn 1;\n\t}\n\tconst pairing = createPairingStore();\n\tconst userId = pairing.approve(platform, code);\n\tif (userId === null) {\n\t\tconsole.error(`Pairing failed: no valid pending code \"${code}\" for ${platform} (wrong, expired, or locked out).`);\n\t\treturn 1;\n\t}\n\ttry {\n\t\tif (owner) {\n\t\t\tconst cfg = loadGatewayConfig();\n\t\t\tif (platform !== \"telegram\" && platform !== \"discord\") throw new Error(\"Unknown platform.\");\n\t\t\tcfg.owners ??= { telegram: [], discord: [] };\n\t\t\tcfg.owners[platform] = [...new Set([...cfg.owners[platform], userId])];\n\t\t\tcfg[platform].allowedUsers = [...new Set([...cfg[platform].allowedUsers, userId])];\n\t\t\tsaveGatewayConfig(cfg);\n\t\t} else addAllowedUser(platform, userId);\n\t} catch (err) {\n\t\tconsole.error(`Paired ${platform} user ${userId}, but could not persist allowedUsers: ${err}`);\n\t\treturn 1;\n\t}\n\tconsole.log(\n\t\t`Approved ${platform} user ${userId}${owner ? \" as owner with local project and TUI access\" : \" for chat only; owner access requires local --owner approval\"}.`,\n\t);\n\treturn 0;\n}\n\nfunction runPairList(): number {\n\tconst pairing = createPairingStore();\n\tconst pending = pairing.listPending();\n\tconst approved = pairing.listApproved();\n\tconsole.log(\"Pending pairing codes:\");\n\tif (pending.length === 0) {\n\t\tconsole.log(\" (none)\");\n\t} else {\n\t\tfor (const p of pending) {\n\t\t\tconsole.log(` ${p.platform} ${p.code} user=${p.userId} expires ${new Date(p.expiresAt).toISOString()}`);\n\t\t}\n\t}\n\tconsole.log(\"Paired users:\");\n\tif (approved.length === 0) {\n\t\tconsole.log(\" (none)\");\n\t} else {\n\t\tfor (const u of approved) {\n\t\t\tconsole.log(` ${u.platform} ${u.userId} approved ${new Date(u.approvedAt).toISOString()}`);\n\t\t}\n\t}\n\treturn 0;\n}\n\nfunction runStatus(): number {\n\tconst cfg = loadGatewayConfig();\n\tconsole.log(\n\t\t`chat-platforms: ${isFeatureEnabled(\"chat-platforms\") ? \"enabled\" : \"disabled\"} (install-features.json)`,\n\t);\n\tconsole.log(\"lunR gateway status:\");\n\tfor (const platform of KNOWN_PLATFORMS) {\n\t\tconst platformCfg = platformConfigFor(cfg, platform);\n\t\tif (!platformCfg) continue;\n\t\tconst token = resolvePlatformToken(platform, platformCfg);\n\t\tconst adapter = ADAPTER_FACTORIES[platform] === undefined ? \"no adapter\" : \"adapter registered\";\n\t\tconsole.log(\n\t\t\t` ${platform}: ${platformCfg.enabled ? \"enabled\" : \"disabled\"} · token ${token ? \"resolved\" : \"missing\"} · ${adapter}`,\n\t\t);\n\t}\n\tconst sessions = listSessions();\n\tconsole.log(` sessions: ${Object.keys(sessions).length} stored`);\n\treturn 0;\n}\n\nasync function runDaemon(): Promise<number> {\n\tif (!isFeatureEnabled(\"chat-platforms\")) {\n\t\tconsole.error(\"lunR gateway: chat platforms are not enabled.\");\n\t\tconsole.error(\"Enable with: lunr features enable chat-platforms\");\n\t\treturn 1;\n\t}\n\tconst cfg = loadGatewayConfig();\n\tconst adapters = new Map<string, PlatformAdapter>();\n\tconst skipped: string[] = [];\n\n\tfor (const platform of KNOWN_PLATFORMS) {\n\t\tconst platformCfg = platformConfigFor(cfg, platform);\n\t\tif (!platformCfg?.enabled) continue;\n\t\tconst factory: ((cfg: PlatformConfig) => PlatformAdapter) | undefined = ADAPTER_FACTORIES[platform];\n\t\tif (factory === undefined) {\n\t\t\tskipped.push(platform);\n\t\t\tcontinue;\n\t\t}\n\t\tconst token = resolvePlatformToken(platform, platformCfg);\n\t\tif (!token) {\n\t\t\tskipped.push(platform);\n\t\t\tcontinue;\n\t\t}\n\t\tadapters.set(platform, factory({ ...platformCfg, token }));\n\t}\n\n\tif (adapters.size === 0) {\n\t\tprintSetupInstructions(skipped);\n\t\treturn 1;\n\t}\n\n\tconst pairing = createPairingStore();\n\tconst bridge = new AgentBridge();\n\tfor (const adapter of adapters.values())\n\t\tadapter.setCommandSuggestions?.(async (source, command) => {\n\t\t\tconst session = bridge.peekSession(buildSessionKey(source, loadGatewayConfig()));\n\t\t\tif (!session) return [];\n\t\t\tif (command === \"thinking\") return session.getAvailableThinkingLevels();\n\t\t\tif (command === \"model\")\n\t\t\t\treturn (await session.modelRuntime.getAvailable()).map((model) => `${model.provider}/${model.id}`);\n\t\t\treturn [];\n\t\t});\n\tconst router = createRouter({ adapters, cfg, pairing, bridge, reloadConfig: true, remoteControls: true });\n\n\tlet finish!: () => void;\n\tconst stopped = new Promise<void>((resolve) => {\n\t\tfinish = resolve;\n\t});\n\tlet stopping = false;\n\tlet cron: ReturnType<typeof startGatewayCron> | undefined;\n\tconst platformStatus: Record<string, string> = {};\n\tconst retries = new Set<ReturnType<typeof setTimeout>>();\n\tconst shutdown = () => {\n\t\tif (stopping) return;\n\t\tstopping = true;\n\t\towner.update(platformStatus, \"stopping\");\n\t\tcron?.stop();\n\t\tfor (const timer of retries) clearTimeout(timer);\n\t\tstopButtonSweeper();\n\t\tvoid (async () => {\n\t\t\tawait bridge.shutdown();\n\t\t\tawait Promise.all([...adapters.values()].map((adapter) => adapter.disconnect().catch(() => {})));\n\t\t\tstopGatewayPresenter();\n\t\t\towner.release();\n\t\t\tprocess.removeListener(\"SIGINT\", shutdown);\n\t\t\tprocess.removeListener(\"SIGTERM\", shutdown);\n\t\t\tfinish();\n\t\t})().catch((error) => {\n\t\t\tconsole.error(\"Gateway shutdown failed:\", error instanceof Error ? error.message : String(error));\n\t\t\tstopping = false;\n\t\t\towner.update(platformStatus, Object.values(platformStatus).includes(\"connected\") ? \"ready\" : \"starting\");\n\t\t\tstartButtonSweeper();\n\t\t\tcron = startGatewayCron({ adapters, cfg });\n\t\t\tfor (const [platform, adapter] of adapters)\n\t\t\t\tif (platformStatus[platform] !== \"connected\") void connect(platform, adapter);\n\t\t});\n\t};\n\tconst owner = claimGateway(shutdown);\n\tprocess.on(\"SIGINT\", shutdown);\n\tprocess.on(\"SIGTERM\", shutdown);\n\tstartGatewayPresenter(adapters);\n\tstartButtonSweeper();\n\tconst connect = async (platform: string, adapter: PlatformAdapter): Promise<void> => {\n\t\tif (stopping) return;\n\t\ttry {\n\t\t\tplatformStatus[platform] = \"connecting\";\n\t\t\towner.update(platformStatus, Object.values(platformStatus).includes(\"connected\") ? \"ready\" : \"starting\");\n\t\t\tconst ok = await adapter.connect();\n\t\t\tif (!ok) {\n\t\t\t\tthrow new Error(\"Connection failed\");\n\t\t\t}\n\t\t\tadapter.onMessage((event) => {\n\t\t\t\tvoid router.handleEvent(event);\n\t\t\t});\n\t\t\tadapter.onCallback(async (event) => {\n\t\t\t\tconst consumed = await handleApprovalCallback(event, adapter);\n\t\t\t\tif (!consumed) {\n\t\t\t\t\tvoid handleCallback(event, { adapters, cfg: loadGatewayConfig(), pairing, bridge, adapter });\n\t\t\t\t}\n\t\t\t});\n\t\t\ttry {\n\t\t\t\tawait adapter.registerCommands?.(botCommandSpecs());\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`[gateway] ${platform}: command menu registration failed: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (stopping) {\n\t\t\t\tawait adapter.disconnect();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tplatformStatus[platform] = \"connected\";\n\t\t\tconsole.log(`lunR gateway: ${platform} connected`);\n\t\t} catch {\n\t\t\tplatformStatus[platform] = \"disconnected, retrying (check gateway logs)\";\n\t\t\tawait adapter.disconnect().catch(() => {});\n\t\t\tif (!stopping) {\n\t\t\t\tconst retry = setTimeout(() => {\n\t\t\t\t\tretries.delete(retry);\n\t\t\t\t\tvoid connect(platform, adapter);\n\t\t\t\t}, 5000);\n\t\t\t\tretries.add(retry);\n\t\t\t}\n\t\t}\n\t\tif (!stopping)\n\t\t\towner.update(platformStatus, Object.values(platformStatus).includes(\"connected\") ? \"ready\" : \"starting\");\n\t};\n\tfor (const [platform, adapter] of adapters) void connect(platform, adapter);\n\n\t// Phase 4: cron jobs fire inside the daemon and deliver back to chats.\n\t// Starts even with zero stored jobs — jobs can be created later from chats.\n\tcron = startGatewayCron({ adapters, cfg });\n\tconsole.log(`lunR gateway: cron scheduler started (${cron.intervalMs / 1000}s interval)`);\n\n\towner.update(platformStatus, \"starting\");\n\tawait stopped;\n\treturn 0;\n}\n\nexport async function runGateway(args: string[]): Promise<number> {\n\tconst [sub, ...rest] = args;\n\tif (sub === \"pair\") {\n\t\tconst [action, platform, code, flag] = rest;\n\t\tif (action === \"approve\" && platform && (flag === undefined || flag === \"--owner\"))\n\t\t\treturn runPairApprove(platform, code, flag === \"--owner\");\n\t\tif (action === \"list\") return runPairList();\n\t\tconsole.error(\"Usage: lunr gateway pair approve <platform> <code> [--owner] | lunr gateway pair list\");\n\t\treturn 1;\n\t}\n\tif (sub === \"status\") return runStatus();\n\tif (sub === undefined) return runDaemon();\n\tconsole.error(`Unknown gateway sub-command \"${sub}\". Expected: pair approve | pair list | status`);\n\treturn 1;\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/gateway/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * lunR: gateway daemon entry (Phase 2a skeleton; Phase 2b registered the\n * Telegram adapter, Phase 3 the Discord adapter — both via ADAPTER_FACTORIES).\n *\n * `lunr gateway` runs the daemon: load gateway.json, instantiate every\n * enabled platform that has a registered adapter factory AND a resolvable\n * token, connect, route messages, and stay alive until SIGINT/SIGTERM.\n * With no runnable adapters it prints per-platform setup instructions and\n * exits 1.\n *\n * Sub-commands:\n * lunr gateway pair approve <platform> <code> approve a pairing code\n * lunr gateway pair list show pending + paired\n * lunr gateway status config/token/session summary\n */\n\nimport { isFeatureEnabled } from \"../core/install-features.ts\";\nimport { DiscordAdapter } from \"./adapters/discord.ts\";\nimport { TelegramAdapter } from \"./adapters/telegram.ts\";\nimport { AgentBridge } from \"./agent-bridge.ts\";\nimport { handleApprovalCallback } from \"./approval.ts\";\nimport { addAllowedUser } from \"./authz.ts\";\nimport { handleCallback, startButtonSweeper, stopButtonSweeper } from \"./buttons.ts\";\nimport { botCommandSpecs } from \"./commands.ts\";\nimport {\n\ttype DiscordConfig,\n\tloadGatewayConfig,\n\ttype PlatformConfig,\n\tplatformConfigFor,\n\tresolvePlatformToken,\n\tsaveGatewayConfig,\n} from \"./config.ts\";\nimport { startGatewayCron } from \"./cron.ts\";\nimport { createPairingStore } from \"./pairing.ts\";\nimport { startGatewayPresenter, stopGatewayPresenter } from \"./presenter.ts\";\nimport { createRouter } from \"./router.ts\";\nimport { claimGateway } from \"./service.ts\";\nimport { buildSessionKey } from \"./session-keys.ts\";\nimport { listSessions } from \"./store.ts\";\nimport type { PlatformAdapter } from \"./types.ts\";\n\n/**\n * Adapter registry — the seam adapters plug into:\n *\n * ADAPTER_FACTORIES.<platform> = (cfg) => new SomeAdapter(cfg);\n *\n * A factory receives the sanitized platform config with the token already\n * resolved into cfg.token (env beats file — see resolvePlatformToken and\n * runDaemon below). The registry type is the PlatformConfig base; adapters\n * with extra fields (Discord) narrow with a cast — the daemon always passes\n * the full sanitized per-platform object.\n */\nexport const ADAPTER_FACTORIES: Record<string, (cfg: PlatformConfig) => PlatformAdapter> = {\n\ttelegram: (cfg) => new TelegramAdapter(cfg),\n\tdiscord: (cfg) => new DiscordAdapter(cfg as DiscordConfig),\n};\n\nconst KNOWN_PLATFORMS = [\"telegram\", \"discord\"] as const;\n\nconst SETUP_INSTRUCTIONS: Record<string, string[]> = {\n\ttelegram: [\"Telegram: run lunr gateway setup locally to configure the bot token and approved user ID.\"],\n\tdiscord: [\"Discord: run lunr gateway setup locally to configure the bot token and approved user ID.\"],\n};\n\nfunction printSetupInstructions(enabledButUnrunnable: string[]): void {\n\tconsole.log(\"lunR gateway: no runnable platforms configured.\");\n\tfor (const platform of enabledButUnrunnable) {\n\t\tconsole.log(` (platform \"${platform}\" is enabled but has no registered adapter or resolvable token)`);\n\t}\n\tfor (const platform of KNOWN_PLATFORMS) {\n\t\tfor (const line of SETUP_INSTRUCTIONS[platform]) {\n\t\t\tconsole.log(line);\n\t\t}\n\t}\n}\n\nasync function runPairApprove(platform: string, code: string | undefined, owner = false): Promise<number> {\n\tif (!code) {\n\t\tconsole.error(\"Usage: lunr gateway pair approve <platform> <code> [--owner]\");\n\t\treturn 1;\n\t}\n\tconst pairing = createPairingStore();\n\tconst userId = pairing.approve(platform, code);\n\tif (userId === null) {\n\t\tconsole.error(`Pairing failed: no valid pending code \"${code}\" for ${platform} (wrong, expired, or locked out).`);\n\t\treturn 1;\n\t}\n\ttry {\n\t\tif (owner) {\n\t\t\tconst cfg = loadGatewayConfig();\n\t\t\tif (platform !== \"telegram\" && platform !== \"discord\") throw new Error(\"Unknown platform.\");\n\t\t\tcfg.owners ??= { telegram: [], discord: [] };\n\t\t\tcfg.owners[platform] = [...new Set([...cfg.owners[platform], userId])];\n\t\t\tcfg[platform].allowedUsers = [...new Set([...cfg[platform].allowedUsers, userId])];\n\t\t\tsaveGatewayConfig(cfg);\n\t\t} else addAllowedUser(platform, userId);\n\t} catch (err) {\n\t\tconsole.error(`Paired ${platform} user ${userId}, but could not persist allowedUsers: ${err}`);\n\t\treturn 1;\n\t}\n\tconsole.log(`Approved ${platform} user ${userId}. Gateway access includes local projects and saved sessions.`);\n\treturn 0;\n}\n\nfunction runPairList(): number {\n\tconst pairing = createPairingStore();\n\tconst pending = pairing.listPending();\n\tconst approved = pairing.listApproved();\n\tconsole.log(\"Pending pairing codes:\");\n\tif (pending.length === 0) {\n\t\tconsole.log(\" (none)\");\n\t} else {\n\t\tfor (const p of pending) {\n\t\t\tconsole.log(` ${p.platform} ${p.code} user=${p.userId} expires ${new Date(p.expiresAt).toISOString()}`);\n\t\t}\n\t}\n\tconsole.log(\"Paired users:\");\n\tif (approved.length === 0) {\n\t\tconsole.log(\" (none)\");\n\t} else {\n\t\tfor (const u of approved) {\n\t\t\tconsole.log(` ${u.platform} ${u.userId} approved ${new Date(u.approvedAt).toISOString()}`);\n\t\t}\n\t}\n\treturn 0;\n}\n\nfunction runStatus(): number {\n\tconst cfg = loadGatewayConfig();\n\tconsole.log(\n\t\t`chat-platforms: ${isFeatureEnabled(\"chat-platforms\") ? \"enabled\" : \"disabled\"} (install-features.json)`,\n\t);\n\tconsole.log(\"lunR gateway status:\");\n\tfor (const platform of KNOWN_PLATFORMS) {\n\t\tconst platformCfg = platformConfigFor(cfg, platform);\n\t\tif (!platformCfg) continue;\n\t\tconst token = resolvePlatformToken(platform, platformCfg);\n\t\tconst adapter = ADAPTER_FACTORIES[platform] === undefined ? \"no adapter\" : \"adapter registered\";\n\t\tconsole.log(\n\t\t\t` ${platform}: ${platformCfg.enabled ? \"enabled\" : \"disabled\"} · token ${token ? \"resolved\" : \"missing\"} · ${adapter}`,\n\t\t);\n\t}\n\tconst sessions = listSessions();\n\tconsole.log(` sessions: ${Object.keys(sessions).length} stored`);\n\treturn 0;\n}\n\nasync function runDaemon(): Promise<number> {\n\tif (!isFeatureEnabled(\"chat-platforms\")) {\n\t\tconsole.error(\"lunR gateway: chat platforms are not enabled.\");\n\t\tconsole.error(\"Enable with: lunr features enable chat-platforms\");\n\t\treturn 1;\n\t}\n\tconst cfg = loadGatewayConfig();\n\tconst adapters = new Map<string, PlatformAdapter>();\n\tconst skipped: string[] = [];\n\n\tfor (const platform of KNOWN_PLATFORMS) {\n\t\tconst platformCfg = platformConfigFor(cfg, platform);\n\t\tif (!platformCfg?.enabled) continue;\n\t\tconst factory: ((cfg: PlatformConfig) => PlatformAdapter) | undefined = ADAPTER_FACTORIES[platform];\n\t\tif (factory === undefined) {\n\t\t\tskipped.push(platform);\n\t\t\tcontinue;\n\t\t}\n\t\tconst token = resolvePlatformToken(platform, platformCfg);\n\t\tif (!token) {\n\t\t\tskipped.push(platform);\n\t\t\tcontinue;\n\t\t}\n\t\tadapters.set(platform, factory({ ...platformCfg, token }));\n\t}\n\n\tif (adapters.size === 0) {\n\t\tprintSetupInstructions(skipped);\n\t\treturn 1;\n\t}\n\n\tconst pairing = createPairingStore();\n\tconst bridge = new AgentBridge();\n\tfor (const adapter of adapters.values())\n\t\tadapter.setCommandSuggestions?.(async (source, command) => {\n\t\t\tconst session = bridge.peekSession(buildSessionKey(source, loadGatewayConfig()));\n\t\t\tif (!session) return [];\n\t\t\tif (command === \"thinking\") return session.getAvailableThinkingLevels();\n\t\t\tif (command === \"model\")\n\t\t\t\treturn (await session.modelRuntime.getAvailable()).map((model) => `${model.provider}/${model.id}`);\n\t\t\treturn [];\n\t\t});\n\tconst router = createRouter({ adapters, cfg, pairing, bridge, reloadConfig: true, remoteControls: true });\n\n\tlet finish!: () => void;\n\tconst stopped = new Promise<void>((resolve) => {\n\t\tfinish = resolve;\n\t});\n\tlet stopping = false;\n\tlet cron: ReturnType<typeof startGatewayCron> | undefined;\n\tconst platformStatus: Record<string, string> = {};\n\tconst retries = new Set<ReturnType<typeof setTimeout>>();\n\tconst shutdown = () => {\n\t\tif (stopping) return;\n\t\tstopping = true;\n\t\towner.update(platformStatus, \"stopping\");\n\t\tcron?.stop();\n\t\tfor (const timer of retries) clearTimeout(timer);\n\t\tstopButtonSweeper();\n\t\tvoid (async () => {\n\t\t\tawait bridge.shutdown();\n\t\t\tawait Promise.all([...adapters.values()].map((adapter) => adapter.disconnect().catch(() => {})));\n\t\t\tstopGatewayPresenter();\n\t\t\towner.release();\n\t\t\tprocess.removeListener(\"SIGINT\", shutdown);\n\t\t\tprocess.removeListener(\"SIGTERM\", shutdown);\n\t\t\tfinish();\n\t\t})().catch((error) => {\n\t\t\tconsole.error(\"Gateway shutdown failed:\", error instanceof Error ? error.message : String(error));\n\t\t\tstopping = false;\n\t\t\towner.update(platformStatus, Object.values(platformStatus).includes(\"connected\") ? \"ready\" : \"starting\");\n\t\t\tstartButtonSweeper();\n\t\t\tcron = startGatewayCron({ adapters, cfg });\n\t\t\tfor (const [platform, adapter] of adapters)\n\t\t\t\tif (platformStatus[platform] !== \"connected\") void connect(platform, adapter);\n\t\t});\n\t};\n\tconst owner = claimGateway(shutdown);\n\tprocess.on(\"SIGINT\", shutdown);\n\tprocess.on(\"SIGTERM\", shutdown);\n\tstartGatewayPresenter(adapters);\n\tstartButtonSweeper();\n\tconst connect = async (platform: string, adapter: PlatformAdapter): Promise<void> => {\n\t\tif (stopping) return;\n\t\ttry {\n\t\t\tplatformStatus[platform] = \"connecting\";\n\t\t\towner.update(platformStatus, Object.values(platformStatus).includes(\"connected\") ? \"ready\" : \"starting\");\n\t\t\tconst ok = await adapter.connect();\n\t\t\tif (!ok) {\n\t\t\t\tthrow new Error(\"Connection failed\");\n\t\t\t}\n\t\t\tadapter.onMessage((event) => {\n\t\t\t\tvoid router.handleEvent(event);\n\t\t\t});\n\t\t\tadapter.onCallback(async (event) => {\n\t\t\t\tconst consumed = await handleApprovalCallback(event, adapter);\n\t\t\t\tif (!consumed) {\n\t\t\t\t\tvoid handleCallback(event, { adapters, cfg: loadGatewayConfig(), pairing, bridge, adapter });\n\t\t\t\t}\n\t\t\t});\n\t\t\ttry {\n\t\t\t\tawait adapter.registerCommands?.(botCommandSpecs());\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`[gateway] ${platform}: command menu registration failed: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (stopping) {\n\t\t\t\tawait adapter.disconnect();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tplatformStatus[platform] = \"connected\";\n\t\t\tconsole.log(`lunR gateway: ${platform} connected`);\n\t\t} catch {\n\t\t\tplatformStatus[platform] = \"disconnected, retrying (check gateway logs)\";\n\t\t\tawait adapter.disconnect().catch(() => {});\n\t\t\tif (!stopping) {\n\t\t\t\tconst retry = setTimeout(() => {\n\t\t\t\t\tretries.delete(retry);\n\t\t\t\t\tvoid connect(platform, adapter);\n\t\t\t\t}, 5000);\n\t\t\t\tretries.add(retry);\n\t\t\t}\n\t\t}\n\t\tif (!stopping)\n\t\t\towner.update(platformStatus, Object.values(platformStatus).includes(\"connected\") ? \"ready\" : \"starting\");\n\t};\n\tfor (const [platform, adapter] of adapters) void connect(platform, adapter);\n\n\t// Phase 4: cron jobs fire inside the daemon and deliver back to chats.\n\t// Starts even with zero stored jobs — jobs can be created later from chats.\n\tcron = startGatewayCron({ adapters, cfg });\n\tconsole.log(`lunR gateway: cron scheduler started (${cron.intervalMs / 1000}s interval)`);\n\n\towner.update(platformStatus, \"starting\");\n\tawait stopped;\n\treturn 0;\n}\n\nexport async function runGateway(args: string[]): Promise<number> {\n\tconst [sub, ...rest] = args;\n\tif (sub === \"pair\") {\n\t\tconst [action, platform, code, flag] = rest;\n\t\tif (action === \"approve\" && platform && (flag === undefined || flag === \"--owner\"))\n\t\t\treturn runPairApprove(platform, code, flag === \"--owner\");\n\t\tif (action === \"list\") return runPairList();\n\t\tconsole.error(\"Usage: lunr gateway pair approve <platform> <code> [--owner] | lunr gateway pair list\");\n\t\treturn 1;\n\t}\n\tif (sub === \"status\") return runStatus();\n\tif (sub === undefined) return runDaemon();\n\tconsole.error(`Unknown gateway sub-command \"${sub}\". Expected: pair approve | pair list | status`);\n\treturn 1;\n}\n"]}
@@ -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,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"]}
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 { isAuthorized, requireAuthorized } 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: () => isAuthorized(event.source, loadGatewayConfig()),\n\t\t\tresolve: async (item): Promise<PickerResolveResult> => {\n\t\t\t\trequireAuthorized(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\trequireAuthorized(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\trequireAuthorized(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\trequireAuthorized(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: () => isAuthorized(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\trequireAuthorized(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"]}
@@ -3,7 +3,7 @@ import { basename, dirname, isAbsolute, join, relative, resolve } from "node:pat
3
3
  import { getPermissionMode, PERMISSION_MODES, setPermissionMode } from "../core/permissions.js";
4
4
  import { SessionManager } from "../core/session-manager.js";
5
5
  import { getSubagentCancellation } from "../core/subagent-cancellation.js";
6
- import { isGatewayOwner, requireGatewayOwner } from "./authz.js";
6
+ import { isAuthorized, requireAuthorized } from "./authz.js";
7
7
  import { createPicker } from "./buttons.js";
8
8
  import { loadGatewayConfig } from "./config.js";
9
9
  import { bindConversation, conversationBinding } from "./conversations.js";
@@ -113,9 +113,9 @@ async function browseProject(ctx, initialPath) {
113
113
  sessionKey: key,
114
114
  invokerId: event.source.userId,
115
115
  ...initial,
116
- validate: () => isGatewayOwner(event.source, loadGatewayConfig()),
116
+ validate: () => isAuthorized(event.source, loadGatewayConfig()),
117
117
  resolve: async (item) => {
118
- requireGatewayOwner(event.source);
118
+ requireAuthorized(event.source);
119
119
  if (item.value === "use" && current) {
120
120
  const cwd = resolveWithinRoots(current, loadGatewayConfig().projectRoots ?? []);
121
121
  if (bridge.getStatus(key).busy)
@@ -155,7 +155,7 @@ async function continueSession(ctx, file) {
155
155
  }
156
156
  }
157
157
  async function performContinue(ctx, file, signal) {
158
- requireGatewayOwner(ctx.event.source);
158
+ requireAuthorized(ctx.event.source);
159
159
  const { requestSessionTransfer, SessionTransferError } = await import("../core/session-handoff.js");
160
160
  const { key, bridge } = ctx;
161
161
  const current = bridge.peekSession?.(key);
@@ -180,7 +180,7 @@ async function performContinue(ctx, file, signal) {
180
180
  const deadline = Date.now() + 120_000;
181
181
  for (;;) {
182
182
  signal.throwIfAborted();
183
- requireGatewayOwner(ctx.event.source);
183
+ requireAuthorized(ctx.event.source);
184
184
  try {
185
185
  await requestSessionTransfer(file, { timeoutMs: 3000, signal });
186
186
  break;
@@ -196,7 +196,7 @@ async function performContinue(ctx, file, signal) {
196
196
  }
197
197
  }
198
198
  signal.throwIfAborted();
199
- requireGatewayOwner(ctx.event.source);
199
+ requireAuthorized(ctx.event.source);
200
200
  await bridge.switchSession(key, file);
201
201
  const session = await bridge.getSession(key);
202
202
  const cwd = session?.sessionManager?.getCwd();
@@ -271,7 +271,7 @@ async function sessionPicker(ctx, items) {
271
271
  invokerId: ctx.event.source.userId,
272
272
  title: "Choose a session. Use /sessions <search> to filter.",
273
273
  items,
274
- validate: () => isGatewayOwner(ctx.event.source, loadGatewayConfig()),
274
+ validate: () => isAuthorized(ctx.event.source, loadGatewayConfig()),
275
275
  resolve: async (item) => {
276
276
  await continueSession(ctx, item.value);
277
277
  return { done: true, text: "Session selection finished." };
@@ -281,7 +281,7 @@ async function sessionPicker(ctx, items) {
281
281
  export async function handleMobileCommand(ctx, command, args) {
282
282
  if (!MOBILE_COMMANDS.some((c) => c.name === command) && command !== "resume")
283
283
  return false;
284
- requireGatewayOwner(ctx.event.source, ctx.cfg);
284
+ requireAuthorized(ctx.event.source, ctx.cfg);
285
285
  bindConversation(ctx.key, ctx.event.source, { owner: ctx.event.source.userId });
286
286
  if (command === "project") {
287
287
  await browseProject(ctx, args.trim() || undefined);
@@ -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 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
+ {"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 { isAuthorized, requireAuthorized } 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: () => isAuthorized(event.source, loadGatewayConfig()),\n\t\t\tresolve: async (item): Promise<PickerResolveResult> => {\n\t\t\t\trequireAuthorized(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\trequireAuthorized(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\trequireAuthorized(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\trequireAuthorized(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: () => isAuthorized(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\trequireAuthorized(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"]}