@ashx-j/lunr 0.2.23 → 0.2.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (272) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/README.md +10 -10
  3. package/dist/builtin-extensions/ashxj-tui.d.ts.map +1 -1
  4. package/dist/builtin-extensions/ashxj-tui.js +2 -2
  5. package/dist/builtin-extensions/ashxj-tui.js.map +1 -1
  6. package/dist/builtin-extensions/lunr-plan-tools.d.ts +7 -7
  7. package/dist/builtin-extensions/lunr-plan-tools.d.ts.map +1 -1
  8. package/dist/builtin-extensions/lunr-plan-tools.js +12 -12
  9. package/dist/builtin-extensions/lunr-plan-tools.js.map +1 -1
  10. package/dist/builtin-extensions/pi-intercom/index.d.ts.map +1 -1
  11. package/dist/builtin-extensions/pi-intercom/index.js +2 -0
  12. package/dist/builtin-extensions/pi-intercom/index.js.map +1 -1
  13. package/dist/builtin-extensions/pi-mcp-adapter/direct-tools.d.ts.map +1 -1
  14. package/dist/builtin-extensions/pi-mcp-adapter/direct-tools.js +1 -0
  15. package/dist/builtin-extensions/pi-mcp-adapter/direct-tools.js.map +1 -1
  16. package/dist/builtin-extensions/pi-mcp-adapter/index.d.ts.map +1 -1
  17. package/dist/builtin-extensions/pi-mcp-adapter/index.js +1 -1
  18. package/dist/builtin-extensions/pi-mcp-adapter/index.js.map +1 -1
  19. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts.map +1 -1
  20. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js +1 -1
  21. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js.map +1 -1
  22. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts +4 -4
  23. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts.map +1 -1
  24. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js +6 -4
  25. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js.map +1 -1
  26. package/dist/builtin-extensions/pi-subagents/src/intercom/intercom-bridge.d.ts +2 -0
  27. package/dist/builtin-extensions/pi-subagents/src/intercom/intercom-bridge.d.ts.map +1 -1
  28. package/dist/builtin-extensions/pi-subagents/src/intercom/intercom-bridge.js +6 -3
  29. package/dist/builtin-extensions/pi-subagents/src/intercom/intercom-bridge.js.map +1 -1
  30. package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.d.ts.map +1 -1
  31. package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.js +7 -2
  32. package/dist/builtin-extensions/pi-subagents/src/intercom/native-supervisor-channel.js.map +1 -1
  33. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.d.ts +2 -0
  34. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.d.ts.map +1 -1
  35. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js +4 -0
  36. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js.map +1 -1
  37. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.d.ts.map +1 -1
  38. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js +1 -1
  39. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js.map +1 -1
  40. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.d.ts +1 -0
  41. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.d.ts.map +1 -1
  42. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js +5 -1
  43. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js.map +1 -1
  44. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts.map +1 -1
  45. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js +3 -2
  46. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js.map +1 -1
  47. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts.map +1 -1
  48. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js +19 -0
  49. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js.map +1 -1
  50. package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.d.ts +1 -1
  51. package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.d.ts.map +1 -1
  52. package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.js +1 -1
  53. package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.js.map +1 -1
  54. package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.d.ts +2 -0
  55. package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.d.ts.map +1 -1
  56. package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.js +17 -15
  57. package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.js.map +1 -1
  58. package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.d.ts.map +1 -1
  59. package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.js +3 -1
  60. package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.js.map +1 -1
  61. package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.d.ts.map +1 -1
  62. package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.js +2 -2
  63. package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.js.map +1 -1
  64. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts +1 -0
  65. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts.map +1 -1
  66. package/dist/builtin-extensions/pi-subagents/src/shared/types.js.map +1 -1
  67. package/dist/core/agent-session.d.ts +1 -1
  68. package/dist/core/agent-session.d.ts.map +1 -1
  69. package/dist/core/agent-session.js +5 -4
  70. package/dist/core/agent-session.js.map +1 -1
  71. package/dist/core/browser/schema.d.ts +1 -1
  72. package/dist/core/browser/schema.d.ts.map +1 -1
  73. package/dist/core/browser/schema.js +1 -1
  74. package/dist/core/browser/schema.js.map +1 -1
  75. package/dist/core/catalog-cache-scope.d.ts.map +1 -1
  76. package/dist/core/catalog-cache-scope.js +3 -1
  77. package/dist/core/catalog-cache-scope.js.map +1 -1
  78. package/dist/core/model-runtime.d.ts.map +1 -1
  79. package/dist/core/model-runtime.js +3 -2
  80. package/dist/core/model-runtime.js.map +1 -1
  81. package/dist/core/permissions.d.ts +16 -23
  82. package/dist/core/permissions.d.ts.map +1 -1
  83. package/dist/core/permissions.js +66 -141
  84. package/dist/core/permissions.js.map +1 -1
  85. package/dist/core/plan-mode.d.ts +10 -12
  86. package/dist/core/plan-mode.d.ts.map +1 -1
  87. package/dist/core/plan-mode.js +16 -18
  88. package/dist/core/plan-mode.js.map +1 -1
  89. package/dist/core/runtime-bridges.d.ts.map +1 -1
  90. package/dist/core/runtime-bridges.js +2 -0
  91. package/dist/core/runtime-bridges.js.map +1 -1
  92. package/dist/core/sdk.d.ts.map +1 -1
  93. package/dist/core/sdk.js +13 -6
  94. package/dist/core/sdk.js.map +1 -1
  95. package/dist/core/session-manager.d.ts.map +1 -1
  96. package/dist/core/session-manager.js +9 -2
  97. package/dist/core/session-manager.js.map +1 -1
  98. package/dist/core/settings-manager.d.ts +9 -1
  99. package/dist/core/settings-manager.d.ts.map +1 -1
  100. package/dist/core/settings-manager.js +40 -1
  101. package/dist/core/settings-manager.js.map +1 -1
  102. package/dist/core/slash-commands.d.ts.map +1 -1
  103. package/dist/core/slash-commands.js +4 -4
  104. package/dist/core/slash-commands.js.map +1 -1
  105. package/dist/core/subagent-permission-inherit.d.ts +6 -10
  106. package/dist/core/subagent-permission-inherit.d.ts.map +1 -1
  107. package/dist/core/subagent-permission-inherit.js +14 -14
  108. package/dist/core/subagent-permission-inherit.js.map +1 -1
  109. package/dist/core/system-prompt.d.ts +2 -0
  110. package/dist/core/system-prompt.d.ts.map +1 -1
  111. package/dist/core/system-prompt.js +9 -4
  112. package/dist/core/system-prompt.js.map +1 -1
  113. package/dist/gateway/adapters/discord.d.ts.map +1 -1
  114. package/dist/gateway/adapters/discord.js +3 -8
  115. package/dist/gateway/adapters/discord.js.map +1 -1
  116. package/dist/gateway/adapters/telegram.d.ts.map +1 -1
  117. package/dist/gateway/adapters/telegram.js +7 -0
  118. package/dist/gateway/adapters/telegram.js.map +1 -1
  119. package/dist/gateway/agent-bridge.d.ts +4 -1
  120. package/dist/gateway/agent-bridge.d.ts.map +1 -1
  121. package/dist/gateway/agent-bridge.js +57 -17
  122. package/dist/gateway/agent-bridge.js.map +1 -1
  123. package/dist/gateway/approval.d.ts +2 -6
  124. package/dist/gateway/approval.d.ts.map +1 -1
  125. package/dist/gateway/approval.js +3 -7
  126. package/dist/gateway/approval.js.map +1 -1
  127. package/dist/gateway/buttons.d.ts.map +1 -1
  128. package/dist/gateway/buttons.js +29 -19
  129. package/dist/gateway/buttons.js.map +1 -1
  130. package/dist/gateway/commands.d.ts +2 -0
  131. package/dist/gateway/commands.d.ts.map +1 -1
  132. package/dist/gateway/commands.js +39 -18
  133. package/dist/gateway/commands.js.map +1 -1
  134. package/dist/gateway/config.d.ts +1 -0
  135. package/dist/gateway/config.d.ts.map +1 -1
  136. package/dist/gateway/config.js +2 -0
  137. package/dist/gateway/config.js.map +1 -1
  138. package/dist/gateway/cron.d.ts +1 -1
  139. package/dist/gateway/cron.d.ts.map +1 -1
  140. package/dist/gateway/cron.js +2 -2
  141. package/dist/gateway/cron.js.map +1 -1
  142. package/dist/gateway/index.d.ts.map +1 -1
  143. package/dist/gateway/index.js +26 -26
  144. package/dist/gateway/index.js.map +1 -1
  145. package/dist/gateway/mobile-commands.d.ts.map +1 -1
  146. package/dist/gateway/mobile-commands.js +15 -14
  147. package/dist/gateway/mobile-commands.js.map +1 -1
  148. package/dist/gateway/presenter.d.ts +12 -1
  149. package/dist/gateway/presenter.d.ts.map +1 -1
  150. package/dist/gateway/presenter.js +146 -48
  151. package/dist/gateway/presenter.js.map +1 -1
  152. package/dist/gateway/router.d.ts +5 -5
  153. package/dist/gateway/router.d.ts.map +1 -1
  154. package/dist/gateway/router.js +96 -42
  155. package/dist/gateway/router.js.map +1 -1
  156. package/dist/gateway/service.d.ts.map +1 -1
  157. package/dist/gateway/service.js +13 -4
  158. package/dist/gateway/service.js.map +1 -1
  159. package/dist/gateway/setup.d.ts +5 -0
  160. package/dist/gateway/setup.d.ts.map +1 -1
  161. package/dist/gateway/setup.js +98 -42
  162. package/dist/gateway/setup.js.map +1 -1
  163. package/dist/gateway/stream.d.ts +6 -5
  164. package/dist/gateway/stream.d.ts.map +1 -1
  165. package/dist/gateway/stream.js +24 -11
  166. package/dist/gateway/stream.js.map +1 -1
  167. package/dist/main.d.ts.map +1 -1
  168. package/dist/main.js +1 -2
  169. package/dist/main.js.map +1 -1
  170. package/dist/modes/interactive/components/settings-selector.d.ts +4 -0
  171. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  172. package/dist/modes/interactive/components/settings-selector.js +23 -3
  173. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  174. package/dist/modes/interactive/components/thinking-tail.d.ts.map +1 -1
  175. package/dist/modes/interactive/components/thinking-tail.js +2 -2
  176. package/dist/modes/interactive/components/thinking-tail.js.map +1 -1
  177. package/dist/modes/interactive/components/tool-execution.d.ts +1 -0
  178. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  179. package/dist/modes/interactive/components/tool-execution.js +12 -2
  180. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  181. package/dist/modes/interactive/interactive-mode.d.ts +1 -2
  182. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  183. package/dist/modes/interactive/interactive-mode.js +76 -90
  184. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  185. package/dist/node-runtime/{agent-session-services-GZVSOGYR.js → agent-session-services-YXXFCP5J.js} +12 -12
  186. package/dist/node-runtime/{builtin-extensions-QVCXNQT4.js → builtin-extensions-ZLAX7TUH.js} +3 -3
  187. package/dist/node-runtime/{chunk-W64JC4E7.js → chunk-2HHLJWGD.js} +3 -87
  188. package/dist/node-runtime/{chunk-VWCE2HSZ.js → chunk-2HMIRK7W.js} +4 -4
  189. package/dist/node-runtime/{chunk-XLTL257W.js → chunk-2KIPT4HJ.js} +5 -1
  190. package/dist/node-runtime/{chunk-RIR5EDDJ.js → chunk-3XPLBUEM.js} +17 -12
  191. package/dist/node-runtime/{chunk-QAZOULTU.js → chunk-6XL67VVY.js} +40 -6
  192. package/dist/node-runtime/chunk-7S3T6YEA.js +97 -0
  193. package/dist/node-runtime/{chunk-R3K2F5GI.js → chunk-B4HP6ZWM.js} +2 -0
  194. package/dist/node-runtime/{chunk-TQGPP4SC.js → chunk-CDVHZOOK.js} +1 -1
  195. package/dist/node-runtime/{chunk-YJMMG7C6.js → chunk-DSIKTUPW.js} +1 -1
  196. package/dist/node-runtime/{chunk-LFZDVKSP.js → chunk-E4D3IGP2.js} +41 -25
  197. package/dist/node-runtime/{chunk-OFKSVRV7.js → chunk-GYIXXQAO.js} +1 -1
  198. package/dist/node-runtime/{chunk-BX2RB477.js → chunk-HFVZUPFJ.js} +1 -1
  199. package/dist/node-runtime/{chunk-AZJHNQKQ.js → chunk-I54LGHWM.js} +1 -1
  200. package/dist/node-runtime/{chunk-YHQ3NBB3.js → chunk-IJIFHRTB.js} +20 -18
  201. package/dist/node-runtime/{chunk-GS66LV4M.js → chunk-KJ6DWS3K.js} +4 -4
  202. package/dist/node-runtime/{chunk-HPUP7E4T.js → chunk-KWIYUUZK.js} +68 -42
  203. package/dist/node-runtime/{chunk-TVJUOJZJ.js → chunk-LQP3RB67.js} +184 -1527
  204. package/dist/node-runtime/{chunk-VSELYKWU.js → chunk-LWYIVUGV.js} +2 -1
  205. package/dist/node-runtime/{chunk-JHYKXIMM.js → chunk-MPKSOJBT.js} +1 -1
  206. package/dist/node-runtime/{chunk-GMHJCCEN.js → chunk-NRKGMJYU.js} +4 -1
  207. package/dist/node-runtime/{chunk-CSUZVNYE.js → chunk-OVX6X377.js} +10 -6
  208. package/dist/node-runtime/{chunk-DD5YWFSJ.js → chunk-QTU6AKNJ.js} +130 -65
  209. package/dist/node-runtime/{chunk-LLUIPGDR.js → chunk-RC7DMV45.js} +110 -77
  210. package/dist/node-runtime/{chunk-UWPJL22I.js → chunk-SLVOAQOY.js} +15 -4
  211. package/dist/node-runtime/{chunk-EKVFP6YF.js → chunk-TORR5XBV.js} +17 -16
  212. package/dist/node-runtime/{chunk-UP2XBVH5.js → chunk-UOEDI7RP.js} +1 -1
  213. package/dist/node-runtime/{chunk-DQMGU675.js → chunk-VQFSUJJG.js} +20 -17
  214. package/dist/node-runtime/{chunk-X3URCYCU.js → chunk-WF7PGWRZ.js} +14 -14
  215. package/dist/node-runtime/chunk-YCUZEGJG.js +1361 -0
  216. package/dist/node-runtime/{chunk-5D7LC2ZQ.js → chunk-ZXSGTGJ6.js} +54 -85
  217. package/dist/node-runtime/cli-runtime.js +1 -1
  218. package/dist/node-runtime/{command-PDVTBRWA.js → command-6WDOYKS6.js} +5 -5
  219. package/dist/node-runtime/{commands-KAMO2T6P.js → commands-NZLSOHPE.js} +2 -2
  220. package/dist/node-runtime/{commands-XDCFGIGW.js → commands-QC6VUZJQ.js} +9 -9
  221. package/dist/node-runtime/{config-RORQUQFG.js → config-D4QUPAHX.js} +1 -1
  222. package/dist/node-runtime/{direct-tool-executor-YAVOLDFY.js → direct-tool-executor-77GW4F5P.js} +3 -3
  223. package/dist/node-runtime/{export-html-KSOIMVPI.js → export-html-VM4J2MNR.js} +1 -1
  224. package/dist/node-runtime/{gateway-AYWM5DIO.js → gateway-INAYBF4Q.js} +141 -85
  225. package/dist/node-runtime/index.js +23 -21
  226. package/dist/node-runtime/{init-X2LIUB2H.js → init-HS6NQXSK.js} +2 -2
  227. package/dist/node-runtime/{install-cli-3BO4MMQ6.js → install-cli-KL4UDDAD.js} +3 -3
  228. package/dist/node-runtime/{interactive-mode-4MFRKAHG.js → interactive-mode-Z5M7WYYZ.js} +14 -14
  229. package/dist/node-runtime/{interactive-view-TUZHCQM3.js → interactive-view-NGFAMHPW.js} +2 -2
  230. package/dist/node-runtime/{lunr-browser-RYFIIVJN.js → lunr-browser-IR7TL3J7.js} +4 -4
  231. package/dist/node-runtime/{lunr-cron-YWXEPZMQ.js → lunr-cron-LWIZJM2N.js} +8 -8
  232. package/dist/node-runtime/main.js +19 -18
  233. package/dist/node-runtime/{mobile-commands-LVHDAUDP.js → mobile-commands-J7OJEKFO.js} +6 -6
  234. package/dist/node-runtime/{model-runtime-PLQKAG4B.js → model-runtime-OO5HFLWE.js} +1 -1
  235. package/dist/node-runtime/{package-manager-cli-6GT3Z3CK.js → package-manager-cli-VYZM5WHN.js} +10 -8
  236. package/dist/node-runtime/{pi-intercom-HBPK5HSJ.js → pi-intercom-QM53RLTS.js} +2 -0
  237. package/dist/node-runtime/{pi-mcp-adapter-E6WULBKF.js → pi-mcp-adapter-5UKBZP44.js} +7 -6
  238. package/dist/node-runtime/{pi-subagents-CDWK2AKY.js → pi-subagents-DNY7EAIG.js} +23 -13
  239. package/dist/node-runtime/{proxy-modes-OSQCWRBI.js → proxy-modes-LGEUYD7R.js} +3 -3
  240. package/dist/node-runtime/rpc-entry.js +19 -18
  241. package/dist/node-runtime/{runtime-bridges-3XUQ46XW.js → runtime-bridges-VRDDR73P.js} +8 -1
  242. package/dist/node-runtime/{service-ISSP6VHE.js → service-MDQIPQ26.js} +1 -1
  243. package/dist/node-runtime/{session-handoff-HC7R5E24.js → session-handoff-GWARVEWT.js} +2 -2
  244. package/dist/node-runtime/{session-manager-CFN4UFM2.js → session-manager-JB5J6XBN.js} +1 -1
  245. package/dist/node-runtime/{settings-manager-6VEMXRDL.js → settings-manager-J4SPQPDJ.js} +10 -7
  246. package/dist/node-runtime/setup-CQ2NLVK5.js +214 -0
  247. package/dist/node-runtime/{subagent-executor-M5VWB3D3.js → subagent-executor-KZZQGAKV.js} +40 -7
  248. package/dist/node-runtime/{update-cli-UVKJWRRJ.js → update-cli-GTFJC7XK.js} +4 -5
  249. package/dist/startup/interactive-view.d.ts.map +1 -1
  250. package/dist/startup/interactive-view.js +5 -1
  251. package/dist/startup/interactive-view.js.map +1 -1
  252. package/docs/browser.md +1 -1
  253. package/docs/features.md +19 -18
  254. package/docs/index.md +1 -1
  255. package/docs/keybindings.md +1 -1
  256. package/docs/providers.md +6 -2
  257. package/docs/quickstart.md +2 -2
  258. package/docs/settings.md +1 -1
  259. package/docs/usage.md +4 -4
  260. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  261. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  262. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  263. package/examples/extensions/gondolin/package-lock.json +2 -2
  264. package/examples/extensions/gondolin/package.json +1 -1
  265. package/examples/extensions/plan-mode/README.md +1 -1
  266. package/examples/extensions/sandbox/package-lock.json +2 -2
  267. package/examples/extensions/sandbox/package.json +1 -1
  268. package/examples/extensions/with-deps/package-lock.json +2 -2
  269. package/examples/extensions/with-deps/package.json +1 -1
  270. package/package.json +4 -4
  271. package/dist/node-runtime/chunk-LISUTZS3.js +0 -19
  272. package/dist/node-runtime/setup-5SR2J35A.js +0 -155
@@ -1 +1 @@
1
- {"version":3,"file":"cron.d.ts","sourceRoot":"","sources":["../../src/gateway/cron.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGnE,OAAO,EAAE,KAAK,aAAa,EAAyB,MAAM,mBAAmB,CAAC;AAC9E,OAAO,EAAE,KAAK,aAAa,EAAqB,MAAM,aAAa,CAAC;AAEpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAIlD,KAAK,cAAc,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AAEhF,qEAAqE;AACrE,MAAM,WAAW,YAAY;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,kBAAkB,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,aAAa,CAAC,EAAE,YAAY,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;AAExG,MAAM,WAAW,kBAAkB;IAClC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACvC,GAAG,EAAE,aAAa,CAAC;IACnB,kCAAkC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iFAAiF;IACjF,cAAc,CAAC,EAAE,kBAAkB,CAAC;IACpC,kHAAkH;IAClH,cAAc,CAAC,EAAE,YAAY,EAAE,CAAC;CAChC;AA+HD,sEAAsE;AACtE,wBAAgB,oBAAoB,CACnC,GAAG,EAAE,aAAa,EAClB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,aAAa,GAAG,IAAI,GAC3B,OAAO,CAMT;AAED,wBAAgB,sBAAsB,CACrC,GAAG,EAAE,aAAa,GAChB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,IAAI,KAAK,MAAM,GAAG,SAAS,CAuBxE;AAED,sDAAsD;AACtD,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAErE;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,EAAE,GAAG,EAAE,aAAa,GAAG,cAAc,CAyClH;AA8CD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,kBAAkB,GAAG;IAAE,IAAI,IAAI,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CA0DlG","sourcesContent":["/**\n * lunR: gateway cron runner + delivery (Phase 4 of the cron/gateway roadmap).\n *\n * startGatewayCron() runs the core/cron scheduler inside the `lunr gateway`\n * daemon:\n *\n * - runJob: each fire gets a FRESH headless agent session (createAgentSession\n * with SessionManager.inMemory — cron runs never write session files and\n * never touch the per-chat gateway sessions; the files under\n * <agentDir>/cron/output/ are the audit trail). The shared fire-guard\n * (core/cron/fire-guard) brackets the turn so the `cron` tool refuses to\n * run inside it (jobs cannot schedule jobs).\n * - deliverResult: replaces the `@lunr/cron-delivery` bridge on globalThis\n * with a platform deliverer. Targets come from job.deliver\n * (comma-separated): \"local\" (no-op — the output file is already written),\n * \"origin\" (the chat that created the job, homeChannel fallback), a bare\n * platform name (that platform's homeChannel), or\n * \"<platform>:<chatId>[:<threadId>]\" (explicit). Content is wrapped as\n * \"☾ Cron: <name>\" and split to the adapter's maxMessageLength.\n *\n * The bridge is re-installed after every cron session creation because loading\n * the builtin extensions (lunr-cron) re-registers its local-notify bridge per\n * session; delivery re-reads the bridge on every call, matching lunr-cron.\n *\n * Fallback models (2026-08-02): settings.json `cronFallbackModels`\n * (\"provider/modelId\" entries) are tried IN ORDER when a fire fails — timeout\n * or any error. Each attempt gets a fresh headless session and\n * jobTimeoutMs/candidates of the budget; a successful fallback run prefixes\n * \"[fell back to provider/modelId]\" to the output. TUI fires are unaffected\n * (they use the live session's model).\n */\n\nimport type { Model } from \"@ashx-j/lunr-ai\";\nimport { beginCronFire, endCronFire } from \"../core/cron/fire-guard.ts\";\nimport type { CronJob, CronJobOrigin } from \"../core/cron/jobs.ts\";\nimport { setCronDeliverValidator } from \"../core/cron/jobs.ts\";\nimport { startScheduler } from \"../core/cron/scheduler.ts\";\nimport { type BridgeSession, shutdownBridgeSession } from \"./agent-bridge.ts\";\nimport { type GatewayConfig, platformConfigFor } from \"./config.ts\";\nimport { splitMessage } from \"./text.ts\";\nimport type { PlatformAdapter } from \"./types.ts\";\n\nconst DELIVERY_BRIDGE_SYMBOL = Symbol.for(\"@lunr/cron-delivery\");\n\ntype DeliveryBridge = (job: CronJob, content: string) => Promise<string | null>;\n\n/** Test seam / default: one fresh headless session per cron fire. */\nexport interface CronModelRef {\n\tprovider: string;\n\tmodelId: string;\n}\n\nexport type CronSessionFactory = (job: CronJob, modelOverride?: CronModelRef) => Promise<BridgeSession>;\n\nexport interface GatewayCronOptions {\n\tadapters: Map<string, PlatformAdapter>;\n\tcfg: GatewayConfig;\n\t/** Tick interval; default 60s. */\n\tintervalMs?: number;\n\t/** Session factory for cron fires; defaults to a real headless agent session. */\n\tsessionFactory?: CronSessionFactory;\n\t/** Test seam: fallback models. When omitted, read fresh from settings.json `cronFallbackModels` on every fire. */\n\tfallbackModels?: CronModelRef[];\n}\n\nconst KNOWN_PLATFORMS = [\"telegram\", \"discord\"] as const;\n\n/** Default factory: fresh in-memory headless session, mirroring agent-bridge's wiring. */\nasync function defaultCronSessionFactory(job: CronJob, modelOverride?: CronModelRef): Promise<BridgeSession> {\n\tconst [\n\t\t{ loadAllBuiltinExtensions },\n\t\t{ getAgentDir },\n\t\t{ registerCustomizeBridge },\n\t\t{ registerMemoryCapBridge },\n\t\t{ registerModelTierBridge },\n\t\t{ createAgentSessionFromServices, createAgentSessionServices },\n\t\t{ SessionManager },\n\t\t{ SettingsManager },\n\t\t{ bindRuntimeBridges },\n\t] = await Promise.all([\n\t\timport(\"../builtin-extensions/index.ts\"),\n\t\timport(\"../config.ts\"),\n\t\timport(\"../core/customize.ts\"),\n\t\timport(\"../core/memory-cap.ts\"),\n\t\timport(\"../core/model-tiers.ts\"),\n\t\timport(\"../core/agent-session-services.ts\"),\n\t\timport(\"../core/session-manager.ts\"),\n\t\timport(\"../core/settings-manager.ts\"),\n\t\timport(\"../core/runtime-bridges.ts\"),\n\t]);\n\tconst cwd = job.workdir ?? process.cwd();\n\tconst agentDir = getAgentDir();\n\tconst settingsManager = SettingsManager.create(cwd, agentDir, { projectTrusted: false });\n\tregisterModelTierBridge(settingsManager);\n\tregisterMemoryCapBridge(settingsManager);\n\tregisterCustomizeBridge(settingsManager);\n\n\t// In-memory on purpose: cron runs must not persist session files or pollute\n\t// the per-chat gateway sessions — <agentDir>/cron/output/ is the audit trail.\n\tconst sessionManager = SessionManager.inMemory(cwd);\n\n\t// Services-first (mirrors main.ts): extension-registered providers (e.g.\n\t// ollama-cloud) must land in the shared ModelRuntime BEFORE session\n\t// creation — otherwise findInitialModel can't resolve the user's default\n\t// model and silently falls back to an arbitrary catalog provider.\n\tconst services = await createAgentSessionServices({\n\t\tcwd,\n\t\tagentDir,\n\t\tsettingsManager,\n\t\tresourceLoaderOptions: { extensionFactories: await loadAllBuiltinExtensions() },\n\t});\n\t// Fallback-model attempts pin the session model explicitly; an unresolvable\n\t// or unauthenticated override fails the attempt so the next fallback runs.\n\tlet model: Model<any> | undefined;\n\tif (modelOverride) {\n\t\tconst resolved = services.modelRuntime.getModel(modelOverride.provider, modelOverride.modelId);\n\t\tif (!resolved) throw new Error(`model not found: ${modelOverride.provider}/${modelOverride.modelId}`);\n\t\tif (!services.modelRuntime.hasConfiguredAuth(resolved.provider)) {\n\t\t\tthrow new Error(`no configured auth for provider: ${modelOverride.provider}`);\n\t\t}\n\t\tmodel = resolved;\n\t}\n\tconst { session } = await createAgentSessionFromServices({ services, sessionManager, model });\n\tbindRuntimeBridges({ session, services });\n\tawait session.bindExtensions({\n\t\tmode: \"print\",\n\t\tonError: (err) => console.error(`[gateway cron] extension error (${err.extensionPath}): ${err.error}`),\n\t});\n\treturn session;\n}\n\n/** Final assistant text, print-mode style; throws on error/aborted stop. */\nfunction extractFinalText(session: BridgeSession): string {\n\tconst messages = session.state.messages;\n\tconst last = messages[messages.length - 1];\n\tif (last?.role !== \"assistant\") return \"\";\n\tconst assistant = last as {\n\t\tstopReason?: string;\n\t\terrorMessage?: string;\n\t\tcontent?: Array<{ type: string; text?: string }>;\n\t};\n\tif (assistant.stopReason === \"error\" || assistant.stopReason === \"aborted\") {\n\t\tthrow new Error(assistant.errorMessage || `Request ${assistant.stopReason}`);\n\t}\n\tlet text = \"\";\n\tfor (const content of assistant.content ?? []) {\n\t\tif (content.type === \"text\") text += content.text ?? \"\";\n\t}\n\treturn text;\n}\n\ninterface ResolvedTarget {\n\tplatform: string;\n\tchatId: string;\n\tthreadId?: string;\n}\n\nconst EXPLICIT_TARGET_RE = /^([a-z0-9_-]+):([^:]+)(?::([^:]+))?$/i;\n\n/** Resolve one deliver target to a concrete destination. Returns an error string on failure. */\nfunction resolveTarget(target: string, job: CronJob, cfg: GatewayConfig): ResolvedTarget | string {\n\tif (target === \"origin\") {\n\t\tconst origin = job.origin;\n\t\tif (origin?.platform && origin.chatId) {\n\t\t\treturn { platform: origin.platform, chatId: origin.chatId, threadId: origin.threadId };\n\t\t}\n\t\t// Missing/incomplete origin → fall back to a homeChannel: the origin's\n\t\t// platform when known, otherwise the first platform that has one.\n\t\tconst platforms = origin?.platform ? [origin.platform] : KNOWN_PLATFORMS;\n\t\tfor (const platform of platforms) {\n\t\t\tconst home = platformConfigFor(cfg, platform)?.homeChannel;\n\t\t\tif (home) return { platform, chatId: home };\n\t\t}\n\t\treturn `deliver target \"origin\": job has no origin and no homeChannel is configured`;\n\t}\n\n\tconst explicit = EXPLICIT_TARGET_RE.exec(target);\n\tconst platform = explicit ? explicit[1].toLowerCase() : target.toLowerCase();\n\tif (platformConfigFor(cfg, platform) === undefined) {\n\t\treturn `unknown platform \"${explicit ? explicit[1] : target}\"`;\n\t}\n\tif (explicit) {\n\t\treturn { platform, chatId: explicit[2], threadId: explicit[3] };\n\t}\n\t// Bare platform name → its homeChannel.\n\tconst home = platformConfigFor(cfg, platform)?.homeChannel;\n\tif (!home) return `no homeChannel configured for ${platform}`;\n\treturn { platform, chatId: home };\n}\n\n/** True when a resolved chat may receive cron output for this job. */\nexport function isAllowedDeliverChat(\n\tcfg: GatewayConfig,\n\tplatform: string,\n\tchatId: string,\n\torigin?: CronJobOrigin | null,\n): boolean {\n\tconst platformCfg = platformConfigFor(cfg, platform);\n\tif (!platformCfg) return false;\n\tif (platformCfg.homeChannel && platformCfg.homeChannel === chatId) return true;\n\tif (platformCfg.allowedChats?.includes(chatId)) return true;\n\treturn origin?.platform === platform && origin.chatId === chatId;\n}\n\nexport function createDeliverValidator(\n\tcfg: GatewayConfig,\n): (deliver: string, origin?: CronJobOrigin | null) => string | undefined {\n\treturn (deliver, origin) => {\n\t\tconst targets = deliver\n\t\t\t.split(\",\")\n\t\t\t.map((t) => t.trim())\n\t\t\t.filter(Boolean);\n\t\tfor (const target of targets) {\n\t\t\tif (target === \"local\") continue;\n\t\t\tif (target === \"origin\") continue; // delivery time resolves/fails with a sensible error\n\t\t\tconst explicit = EXPLICIT_TARGET_RE.exec(target);\n\t\t\tconst platform = explicit ? explicit[1].toLowerCase() : target.toLowerCase();\n\t\t\tconst platformCfg = platformConfigFor(cfg, platform);\n\t\t\tif (!platformCfg) return `unknown platform \"${explicit ? explicit[1] : target}\"`;\n\t\t\tif (explicit) {\n\t\t\t\tif (!isAllowedDeliverChat(cfg, platform, explicit[2], origin)) {\n\t\t\t\t\treturn `deliver target \"${target}\" is not an allowed chat for ${platform}`;\n\t\t\t\t}\n\t\t\t} else if (!platformCfg.homeChannel) {\n\t\t\t\treturn `no homeChannel configured for ${platform}`;\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t};\n}\n\n/** Wrap the run output with a compact cron header. */\nexport function wrapCronContent(job: CronJob, content: string): string {\n\treturn `☾ Cron: ${job.name}\\n———\\n${content}`;\n}\n\n/**\n * The platform deliverer installed at the `@lunr/cron-delivery` bridge.\n * Every target is attempted; the first error is returned (null on success).\n */\nexport function createPlatformDeliverer(adapters: Map<string, PlatformAdapter>, cfg: GatewayConfig): DeliveryBridge {\n\treturn async (job, content) => {\n\t\tconst targets = job.deliver\n\t\t\t.split(\",\")\n\t\t\t.map((t) => t.trim())\n\t\t\t.filter(Boolean);\n\t\tlet firstError: string | null = null;\n\t\tfor (const target of targets) {\n\t\t\tif (target === \"local\") continue; // output file already written by the scheduler\n\t\t\tconst resolved = resolveTarget(target, job, cfg);\n\t\t\tif (typeof resolved === \"string\") {\n\t\t\t\tfirstError ??= resolved;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!isAllowedDeliverChat(cfg, resolved.platform, resolved.chatId, job.origin)) {\n\t\t\t\tfirstError ??= `deliver target \"${target}\" is not an allowed chat for ${resolved.platform}`;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst adapter = adapters.get(resolved.platform);\n\t\t\tif (!adapter) {\n\t\t\t\tfirstError ??= `no adapter connected for ${resolved.platform}`;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst wrapped = wrapCronContent(job, content);\n\t\t\tfor (const chunk of splitMessage(wrapped, adapter.maxMessageLength)) {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await adapter.send(resolved.chatId, chunk, {\n\t\t\t\t\t\tthreadId: resolved.threadId,\n\t\t\t\t\t});\n\t\t\t\t\tif (!result.success) {\n\t\t\t\t\t\tfirstError ??= `${resolved.platform} send failed: ${result.error ?? \"unknown error\"}`;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tfirstError ??= `${resolved.platform} send failed: ${err instanceof Error ? err.message : String(err)}`;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn firstError;\n\t};\n}\n\n/** Race one prompt attempt against its share of the job budget. */\nfunction withAttemptTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {\n\treturn Promise.race([\n\t\tpromise,\n\t\tnew Promise<never>((_, reject) => {\n\t\t\tconst timer = setTimeout(() => reject(new Error(`attempt timed out after ${ms}ms`)), ms);\n\t\t\t// If the attempt settles first this promise is discarded; the timer must not keep the process alive.\n\t\t\ttimer.unref?.();\n\t\t}),\n\t]);\n}\n\n/**\n * Read settings.json `cronFallbackModels` (\"provider/modelId\" entries).\n * Fresh read per fire so edits apply without a daemon restart; invalid\n * entries are skipped with a log line. Never throws.\n */\nasync function readCronFallbackModels(): Promise<CronModelRef[]> {\n\ttry {\n\t\tconst [{ getAgentDir }, { SettingsManager }] = await Promise.all([\n\t\t\timport(\"../config.ts\"),\n\t\t\timport(\"../core/settings-manager.ts\"),\n\t\t]);\n\t\tconst settingsManager = SettingsManager.create(process.cwd(), getAgentDir(), { projectTrusted: false });\n\t\tconst out: CronModelRef[] = [];\n\t\tfor (const entry of settingsManager.getCronFallbackModels()) {\n\t\t\tconst idx = entry.indexOf(\"/\");\n\t\t\tif (idx <= 0 || idx === entry.length - 1) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`[gateway cron] ignoring invalid cronFallbackModels entry \"${entry}\" (want \"provider/modelId\")`,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tout.push({ provider: entry.slice(0, idx), modelId: entry.slice(idx + 1) });\n\t\t}\n\t\treturn out;\n\t} catch (err) {\n\t\tconsole.error(\n\t\t\t`[gateway cron] failed to read cronFallbackModels: ${err instanceof Error ? err.message : String(err)}`,\n\t\t);\n\t\treturn [];\n\t}\n}\n\n/**\n * Start the cron scheduler inside the gateway daemon. Starts even with zero\n * stored jobs — jobs can be created later from chats. stop() halts the loop.\n */\nexport function startGatewayCron(options: GatewayCronOptions): { stop(): void; intervalMs: number } {\n\tconst { adapters, cfg } = options;\n\tconst sessionFactory = options.sessionFactory ?? defaultCronSessionFactory;\n\tconst platformDeliverer = createPlatformDeliverer(adapters, cfg);\n\n\t// Explicit so runJob can split it into per-attempt budgets that sum to (at\n\t// most) the scheduler's outer race (core/cron/scheduler.ts runSchedulerTick).\n\tconst jobTimeoutMs = 5 * 60 * 1000;\n\n\t// Reject deliver targets that are not local, origin, a configured home channel,\n\t// or an explicit chat in the platform allowlist / the job's origin.\n\tsetCronDeliverValidator(createDeliverValidator(cfg));\n\n\t// Replace the local-notify bridge lunr-cron registered with the platform deliverer.\n\t(globalThis as Record<symbol, unknown>)[DELIVERY_BRIDGE_SYMBOL] = platformDeliverer;\n\n\tconst runJob = async (prompt: string, job: CronJob): Promise<string> => {\n\t\tconst fallbacks = options.fallbackModels ?? (await readCronFallbackModels());\n\t\tconst candidates: Array<CronModelRef | undefined> = [undefined, ...fallbacks];\n\t\tconst attemptTimeoutMs = Math.floor(jobTimeoutMs / candidates.length);\n\t\tconst errors: string[] = [];\n\t\tbeginCronFire();\n\t\ttry {\n\t\t\tfor (const candidate of candidates) {\n\t\t\t\tconst label = candidate ? `${candidate.provider}/${candidate.modelId}` : \"default model\";\n\t\t\t\tlet session: BridgeSession | undefined;\n\t\t\t\ttry {\n\t\t\t\t\tsession = await sessionFactory(job, candidate);\n\t\t\t\t\t// Loading the builtin extensions re-registered lunr-cron's local-notify\n\t\t\t\t\t// bridge; restore the platform deliverer before any delivery re-reads it.\n\t\t\t\t\t(globalThis as Record<symbol, unknown>)[DELIVERY_BRIDGE_SYMBOL] = platformDeliverer;\n\t\t\t\t\tawait withAttemptTimeout(session.prompt(prompt, { source: \"extension\" }), attemptTimeoutMs);\n\t\t\t\t\tconst text = extractFinalText(session);\n\t\t\t\t\t// The marker lands in the output file (the audit trail); [SILENT] as\n\t\t\t\t\t// the last line still suppresses delivery (scheduler isSilent check).\n\t\t\t\t\treturn candidate ? `[fell back to ${label}]\\n${text}` : text;\n\t\t\t\t} catch (err) {\n\t\t\t\t\terrors.push(`${label}: ${err instanceof Error ? err.message : String(err)}`);\n\t\t\t\t} finally {\n\t\t\t\t\tif (session) await shutdownBridgeSession(session, \"quit\");\n\t\t\t\t}\n\t\t\t}\n\t\t\tthrow new Error(errors.join(\" | \") || \"no model candidates\");\n\t\t} finally {\n\t\t\tendCronFire();\n\t\t}\n\t};\n\n\tconst deliverResult = async (job: CronJob, content: string): Promise<void> => {\n\t\t// Re-read the bridge on every delivery (same pattern as lunr-cron).\n\t\tconst bridge = (globalThis as Record<symbol, unknown>)[DELIVERY_BRIDGE_SYMBOL] as DeliveryBridge | undefined;\n\t\tif (!bridge) throw new Error(\"no cron delivery bridge registered\");\n\t\tconst err = await bridge(job, content);\n\t\tif (err) throw new Error(err);\n\t};\n\n\tconst scheduler = startScheduler({ runJob, deliverResult, intervalMs: options.intervalMs, jobTimeoutMs });\n\treturn { stop: () => scheduler.stop(), intervalMs: options.intervalMs ?? 60_000 };\n}\n"]}
1
+ {"version":3,"file":"cron.d.ts","sourceRoot":"","sources":["../../src/gateway/cron.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGnE,OAAO,EAAE,KAAK,aAAa,EAAyB,MAAM,mBAAmB,CAAC;AAC9E,OAAO,EAAE,KAAK,aAAa,EAAqB,MAAM,aAAa,CAAC;AAEpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAIlD,KAAK,cAAc,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AAEhF,qEAAqE;AACrE,MAAM,WAAW,YAAY;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,kBAAkB,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,aAAa,CAAC,EAAE,YAAY,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;AAExG,MAAM,WAAW,kBAAkB;IAClC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACvC,GAAG,EAAE,aAAa,CAAC;IACnB,kCAAkC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iFAAiF;IACjF,cAAc,CAAC,EAAE,kBAAkB,CAAC;IACpC,kHAAkH;IAClH,cAAc,CAAC,EAAE,YAAY,EAAE,CAAC;CAChC;AA+HD,sEAAsE;AACtE,wBAAgB,oBAAoB,CACnC,GAAG,EAAE,aAAa,EAClB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,aAAa,GAAG,IAAI,GAC3B,OAAO,CAMT;AAED,wBAAgB,sBAAsB,CACrC,GAAG,EAAE,aAAa,GAChB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,IAAI,KAAK,MAAM,GAAG,SAAS,CAuBxE;AAED,sDAAsD;AACtD,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAErE;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,EAAE,GAAG,EAAE,aAAa,GAAG,cAAc,CAyClH;AA8CD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,kBAAkB,GAAG;IAAE,IAAI,IAAI,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CA0DlG","sourcesContent":["/**\n * lunR: gateway cron runner + delivery (Phase 4 of the cron/gateway roadmap).\n *\n * startGatewayCron() runs the core/cron scheduler inside the `lunr gateway`\n * daemon:\n *\n * - runJob: each fire gets a FRESH headless agent session (createAgentSession\n * with SessionManager.inMemory — cron runs never write session files and\n * never touch the per-chat gateway sessions; the files under\n * <agentDir>/cron/output/ are the audit trail). The shared fire-guard\n * (core/cron/fire-guard) brackets the turn so the `cron` tool refuses to\n * run inside it (jobs cannot schedule jobs).\n * - deliverResult: replaces the `@lunr/cron-delivery` bridge on globalThis\n * with a platform deliverer. Targets come from job.deliver\n * (comma-separated): \"local\" (no-op — the output file is already written),\n * \"origin\" (the chat that created the job, homeChannel fallback), a bare\n * platform name (that platform's homeChannel), or\n * \"<platform>:<chatId>[:<threadId>]\" (explicit). Content is wrapped as\n * \"Cron: <name>\" and split to the adapter's maxMessageLength.\n *\n * The bridge is re-installed after every cron session creation because loading\n * the builtin extensions (lunr-cron) re-registers its local-notify bridge per\n * session; delivery re-reads the bridge on every call, matching lunr-cron.\n *\n * Fallback models (2026-08-02): settings.json `cronFallbackModels`\n * (\"provider/modelId\" entries) are tried IN ORDER when a fire fails — timeout\n * or any error. Each attempt gets a fresh headless session and\n * jobTimeoutMs/candidates of the budget; a successful fallback run prefixes\n * \"[fell back to provider/modelId]\" to the output. TUI fires are unaffected\n * (they use the live session's model).\n */\n\nimport type { Model } from \"@ashx-j/lunr-ai\";\nimport { beginCronFire, endCronFire } from \"../core/cron/fire-guard.ts\";\nimport type { CronJob, CronJobOrigin } from \"../core/cron/jobs.ts\";\nimport { setCronDeliverValidator } from \"../core/cron/jobs.ts\";\nimport { startScheduler } from \"../core/cron/scheduler.ts\";\nimport { type BridgeSession, shutdownBridgeSession } from \"./agent-bridge.ts\";\nimport { type GatewayConfig, platformConfigFor } from \"./config.ts\";\nimport { splitMessage } from \"./text.ts\";\nimport type { PlatformAdapter } from \"./types.ts\";\n\nconst DELIVERY_BRIDGE_SYMBOL = Symbol.for(\"@lunr/cron-delivery\");\n\ntype DeliveryBridge = (job: CronJob, content: string) => Promise<string | null>;\n\n/** Test seam / default: one fresh headless session per cron fire. */\nexport interface CronModelRef {\n\tprovider: string;\n\tmodelId: string;\n}\n\nexport type CronSessionFactory = (job: CronJob, modelOverride?: CronModelRef) => Promise<BridgeSession>;\n\nexport interface GatewayCronOptions {\n\tadapters: Map<string, PlatformAdapter>;\n\tcfg: GatewayConfig;\n\t/** Tick interval; default 60s. */\n\tintervalMs?: number;\n\t/** Session factory for cron fires; defaults to a real headless agent session. */\n\tsessionFactory?: CronSessionFactory;\n\t/** Test seam: fallback models. When omitted, read fresh from settings.json `cronFallbackModels` on every fire. */\n\tfallbackModels?: CronModelRef[];\n}\n\nconst KNOWN_PLATFORMS = [\"telegram\", \"discord\"] as const;\n\n/** Default factory: fresh in-memory headless session, mirroring agent-bridge's wiring. */\nasync function defaultCronSessionFactory(job: CronJob, modelOverride?: CronModelRef): Promise<BridgeSession> {\n\tconst [\n\t\t{ loadAllBuiltinExtensions },\n\t\t{ getAgentDir },\n\t\t{ registerCustomizeBridge },\n\t\t{ registerMemoryCapBridge },\n\t\t{ registerModelTierBridge },\n\t\t{ createAgentSessionFromServices, createAgentSessionServices },\n\t\t{ SessionManager },\n\t\t{ SettingsManager },\n\t\t{ bindRuntimeBridges },\n\t] = await Promise.all([\n\t\timport(\"../builtin-extensions/index.ts\"),\n\t\timport(\"../config.ts\"),\n\t\timport(\"../core/customize.ts\"),\n\t\timport(\"../core/memory-cap.ts\"),\n\t\timport(\"../core/model-tiers.ts\"),\n\t\timport(\"../core/agent-session-services.ts\"),\n\t\timport(\"../core/session-manager.ts\"),\n\t\timport(\"../core/settings-manager.ts\"),\n\t\timport(\"../core/runtime-bridges.ts\"),\n\t]);\n\tconst cwd = job.workdir ?? process.cwd();\n\tconst agentDir = getAgentDir();\n\tconst settingsManager = SettingsManager.create(cwd, agentDir, { projectTrusted: false });\n\tregisterModelTierBridge(settingsManager);\n\tregisterMemoryCapBridge(settingsManager);\n\tregisterCustomizeBridge(settingsManager);\n\n\t// In-memory on purpose: cron runs must not persist session files or pollute\n\t// the per-chat gateway sessions — <agentDir>/cron/output/ is the audit trail.\n\tconst sessionManager = SessionManager.inMemory(cwd);\n\n\t// Services-first (mirrors main.ts): extension-registered providers (e.g.\n\t// ollama-cloud) must land in the shared ModelRuntime BEFORE session\n\t// creation — otherwise findInitialModel can't resolve the user's default\n\t// model and silently falls back to an arbitrary catalog provider.\n\tconst services = await createAgentSessionServices({\n\t\tcwd,\n\t\tagentDir,\n\t\tsettingsManager,\n\t\tresourceLoaderOptions: { extensionFactories: await loadAllBuiltinExtensions() },\n\t});\n\t// Fallback-model attempts pin the session model explicitly; an unresolvable\n\t// or unauthenticated override fails the attempt so the next fallback runs.\n\tlet model: Model<any> | undefined;\n\tif (modelOverride) {\n\t\tconst resolved = services.modelRuntime.getModel(modelOverride.provider, modelOverride.modelId);\n\t\tif (!resolved) throw new Error(`model not found: ${modelOverride.provider}/${modelOverride.modelId}`);\n\t\tif (!services.modelRuntime.hasConfiguredAuth(resolved.provider)) {\n\t\t\tthrow new Error(`no configured auth for provider: ${modelOverride.provider}`);\n\t\t}\n\t\tmodel = resolved;\n\t}\n\tconst { session } = await createAgentSessionFromServices({ services, sessionManager, model });\n\tbindRuntimeBridges({ session, services });\n\tawait session.bindExtensions({\n\t\tmode: \"print\",\n\t\tonError: (err) => console.error(`[gateway cron] extension error (${err.extensionPath}): ${err.error}`),\n\t});\n\treturn session;\n}\n\n/** Final assistant text, print-mode style; throws on error/aborted stop. */\nfunction extractFinalText(session: BridgeSession): string {\n\tconst messages = session.state.messages;\n\tconst last = messages[messages.length - 1];\n\tif (last?.role !== \"assistant\") return \"\";\n\tconst assistant = last as {\n\t\tstopReason?: string;\n\t\terrorMessage?: string;\n\t\tcontent?: Array<{ type: string; text?: string }>;\n\t};\n\tif (assistant.stopReason === \"error\" || assistant.stopReason === \"aborted\") {\n\t\tthrow new Error(assistant.errorMessage || `Request ${assistant.stopReason}`);\n\t}\n\tlet text = \"\";\n\tfor (const content of assistant.content ?? []) {\n\t\tif (content.type === \"text\") text += content.text ?? \"\";\n\t}\n\treturn text;\n}\n\ninterface ResolvedTarget {\n\tplatform: string;\n\tchatId: string;\n\tthreadId?: string;\n}\n\nconst EXPLICIT_TARGET_RE = /^([a-z0-9_-]+):([^:]+)(?::([^:]+))?$/i;\n\n/** Resolve one deliver target to a concrete destination. Returns an error string on failure. */\nfunction resolveTarget(target: string, job: CronJob, cfg: GatewayConfig): ResolvedTarget | string {\n\tif (target === \"origin\") {\n\t\tconst origin = job.origin;\n\t\tif (origin?.platform && origin.chatId) {\n\t\t\treturn { platform: origin.platform, chatId: origin.chatId, threadId: origin.threadId };\n\t\t}\n\t\t// Missing/incomplete origin → fall back to a homeChannel: the origin's\n\t\t// platform when known, otherwise the first platform that has one.\n\t\tconst platforms = origin?.platform ? [origin.platform] : KNOWN_PLATFORMS;\n\t\tfor (const platform of platforms) {\n\t\t\tconst home = platformConfigFor(cfg, platform)?.homeChannel;\n\t\t\tif (home) return { platform, chatId: home };\n\t\t}\n\t\treturn `deliver target \"origin\": job has no origin and no homeChannel is configured`;\n\t}\n\n\tconst explicit = EXPLICIT_TARGET_RE.exec(target);\n\tconst platform = explicit ? explicit[1].toLowerCase() : target.toLowerCase();\n\tif (platformConfigFor(cfg, platform) === undefined) {\n\t\treturn `unknown platform \"${explicit ? explicit[1] : target}\"`;\n\t}\n\tif (explicit) {\n\t\treturn { platform, chatId: explicit[2], threadId: explicit[3] };\n\t}\n\t// Bare platform name → its homeChannel.\n\tconst home = platformConfigFor(cfg, platform)?.homeChannel;\n\tif (!home) return `no homeChannel configured for ${platform}`;\n\treturn { platform, chatId: home };\n}\n\n/** True when a resolved chat may receive cron output for this job. */\nexport function isAllowedDeliverChat(\n\tcfg: GatewayConfig,\n\tplatform: string,\n\tchatId: string,\n\torigin?: CronJobOrigin | null,\n): boolean {\n\tconst platformCfg = platformConfigFor(cfg, platform);\n\tif (!platformCfg) return false;\n\tif (platformCfg.homeChannel && platformCfg.homeChannel === chatId) return true;\n\tif (platformCfg.allowedChats?.includes(chatId)) return true;\n\treturn origin?.platform === platform && origin.chatId === chatId;\n}\n\nexport function createDeliverValidator(\n\tcfg: GatewayConfig,\n): (deliver: string, origin?: CronJobOrigin | null) => string | undefined {\n\treturn (deliver, origin) => {\n\t\tconst targets = deliver\n\t\t\t.split(\",\")\n\t\t\t.map((t) => t.trim())\n\t\t\t.filter(Boolean);\n\t\tfor (const target of targets) {\n\t\t\tif (target === \"local\") continue;\n\t\t\tif (target === \"origin\") continue; // delivery time resolves/fails with a sensible error\n\t\t\tconst explicit = EXPLICIT_TARGET_RE.exec(target);\n\t\t\tconst platform = explicit ? explicit[1].toLowerCase() : target.toLowerCase();\n\t\t\tconst platformCfg = platformConfigFor(cfg, platform);\n\t\t\tif (!platformCfg) return `unknown platform \"${explicit ? explicit[1] : target}\"`;\n\t\t\tif (explicit) {\n\t\t\t\tif (!isAllowedDeliverChat(cfg, platform, explicit[2], origin)) {\n\t\t\t\t\treturn `deliver target \"${target}\" is not an allowed chat for ${platform}`;\n\t\t\t\t}\n\t\t\t} else if (!platformCfg.homeChannel) {\n\t\t\t\treturn `no homeChannel configured for ${platform}`;\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t};\n}\n\n/** Wrap the run output with a compact cron header. */\nexport function wrapCronContent(job: CronJob, content: string): string {\n\treturn `Cron: ${job.name}\\n———\\n${content}`;\n}\n\n/**\n * The platform deliverer installed at the `@lunr/cron-delivery` bridge.\n * Every target is attempted; the first error is returned (null on success).\n */\nexport function createPlatformDeliverer(adapters: Map<string, PlatformAdapter>, cfg: GatewayConfig): DeliveryBridge {\n\treturn async (job, content) => {\n\t\tconst targets = job.deliver\n\t\t\t.split(\",\")\n\t\t\t.map((t) => t.trim())\n\t\t\t.filter(Boolean);\n\t\tlet firstError: string | null = null;\n\t\tfor (const target of targets) {\n\t\t\tif (target === \"local\") continue; // output file already written by the scheduler\n\t\t\tconst resolved = resolveTarget(target, job, cfg);\n\t\t\tif (typeof resolved === \"string\") {\n\t\t\t\tfirstError ??= resolved;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!isAllowedDeliverChat(cfg, resolved.platform, resolved.chatId, job.origin)) {\n\t\t\t\tfirstError ??= `deliver target \"${target}\" is not an allowed chat for ${resolved.platform}`;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst adapter = adapters.get(resolved.platform);\n\t\t\tif (!adapter) {\n\t\t\t\tfirstError ??= `no adapter connected for ${resolved.platform}`;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst wrapped = wrapCronContent(job, content);\n\t\t\tfor (const chunk of splitMessage(wrapped, adapter.maxMessageLength)) {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await adapter.send(resolved.chatId, chunk, {\n\t\t\t\t\t\tthreadId: resolved.threadId,\n\t\t\t\t\t});\n\t\t\t\t\tif (!result.success) {\n\t\t\t\t\t\tfirstError ??= `${resolved.platform} send failed: ${result.error ?? \"unknown error\"}`;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tfirstError ??= `${resolved.platform} send failed: ${err instanceof Error ? err.message : String(err)}`;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn firstError;\n\t};\n}\n\n/** Race one prompt attempt against its share of the job budget. */\nfunction withAttemptTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {\n\treturn Promise.race([\n\t\tpromise,\n\t\tnew Promise<never>((_, reject) => {\n\t\t\tconst timer = setTimeout(() => reject(new Error(`attempt timed out after ${ms}ms`)), ms);\n\t\t\t// If the attempt settles first this promise is discarded; the timer must not keep the process alive.\n\t\t\ttimer.unref?.();\n\t\t}),\n\t]);\n}\n\n/**\n * Read settings.json `cronFallbackModels` (\"provider/modelId\" entries).\n * Fresh read per fire so edits apply without a daemon restart; invalid\n * entries are skipped with a log line. Never throws.\n */\nasync function readCronFallbackModels(): Promise<CronModelRef[]> {\n\ttry {\n\t\tconst [{ getAgentDir }, { SettingsManager }] = await Promise.all([\n\t\t\timport(\"../config.ts\"),\n\t\t\timport(\"../core/settings-manager.ts\"),\n\t\t]);\n\t\tconst settingsManager = SettingsManager.create(process.cwd(), getAgentDir(), { projectTrusted: false });\n\t\tconst out: CronModelRef[] = [];\n\t\tfor (const entry of settingsManager.getCronFallbackModels()) {\n\t\t\tconst idx = entry.indexOf(\"/\");\n\t\t\tif (idx <= 0 || idx === entry.length - 1) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`[gateway cron] ignoring invalid cronFallbackModels entry \"${entry}\" (want \"provider/modelId\")`,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tout.push({ provider: entry.slice(0, idx), modelId: entry.slice(idx + 1) });\n\t\t}\n\t\treturn out;\n\t} catch (err) {\n\t\tconsole.error(\n\t\t\t`[gateway cron] failed to read cronFallbackModels: ${err instanceof Error ? err.message : String(err)}`,\n\t\t);\n\t\treturn [];\n\t}\n}\n\n/**\n * Start the cron scheduler inside the gateway daemon. Starts even with zero\n * stored jobs — jobs can be created later from chats. stop() halts the loop.\n */\nexport function startGatewayCron(options: GatewayCronOptions): { stop(): void; intervalMs: number } {\n\tconst { adapters, cfg } = options;\n\tconst sessionFactory = options.sessionFactory ?? defaultCronSessionFactory;\n\tconst platformDeliverer = createPlatformDeliverer(adapters, cfg);\n\n\t// Explicit so runJob can split it into per-attempt budgets that sum to (at\n\t// most) the scheduler's outer race (core/cron/scheduler.ts runSchedulerTick).\n\tconst jobTimeoutMs = 5 * 60 * 1000;\n\n\t// Reject deliver targets that are not local, origin, a configured home channel,\n\t// or an explicit chat in the platform allowlist / the job's origin.\n\tsetCronDeliverValidator(createDeliverValidator(cfg));\n\n\t// Replace the local-notify bridge lunr-cron registered with the platform deliverer.\n\t(globalThis as Record<symbol, unknown>)[DELIVERY_BRIDGE_SYMBOL] = platformDeliverer;\n\n\tconst runJob = async (prompt: string, job: CronJob): Promise<string> => {\n\t\tconst fallbacks = options.fallbackModels ?? (await readCronFallbackModels());\n\t\tconst candidates: Array<CronModelRef | undefined> = [undefined, ...fallbacks];\n\t\tconst attemptTimeoutMs = Math.floor(jobTimeoutMs / candidates.length);\n\t\tconst errors: string[] = [];\n\t\tbeginCronFire();\n\t\ttry {\n\t\t\tfor (const candidate of candidates) {\n\t\t\t\tconst label = candidate ? `${candidate.provider}/${candidate.modelId}` : \"default model\";\n\t\t\t\tlet session: BridgeSession | undefined;\n\t\t\t\ttry {\n\t\t\t\t\tsession = await sessionFactory(job, candidate);\n\t\t\t\t\t// Loading the builtin extensions re-registered lunr-cron's local-notify\n\t\t\t\t\t// bridge; restore the platform deliverer before any delivery re-reads it.\n\t\t\t\t\t(globalThis as Record<symbol, unknown>)[DELIVERY_BRIDGE_SYMBOL] = platformDeliverer;\n\t\t\t\t\tawait withAttemptTimeout(session.prompt(prompt, { source: \"extension\" }), attemptTimeoutMs);\n\t\t\t\t\tconst text = extractFinalText(session);\n\t\t\t\t\t// The marker lands in the output file (the audit trail); [SILENT] as\n\t\t\t\t\t// the last line still suppresses delivery (scheduler isSilent check).\n\t\t\t\t\treturn candidate ? `[fell back to ${label}]\\n${text}` : text;\n\t\t\t\t} catch (err) {\n\t\t\t\t\terrors.push(`${label}: ${err instanceof Error ? err.message : String(err)}`);\n\t\t\t\t} finally {\n\t\t\t\t\tif (session) await shutdownBridgeSession(session, \"quit\");\n\t\t\t\t}\n\t\t\t}\n\t\t\tthrow new Error(errors.join(\" | \") || \"no model candidates\");\n\t\t} finally {\n\t\t\tendCronFire();\n\t\t}\n\t};\n\n\tconst deliverResult = async (job: CronJob, content: string): Promise<void> => {\n\t\t// Re-read the bridge on every delivery (same pattern as lunr-cron).\n\t\tconst bridge = (globalThis as Record<symbol, unknown>)[DELIVERY_BRIDGE_SYMBOL] as DeliveryBridge | undefined;\n\t\tif (!bridge) throw new Error(\"no cron delivery bridge registered\");\n\t\tconst err = await bridge(job, content);\n\t\tif (err) throw new Error(err);\n\t};\n\n\tconst scheduler = startScheduler({ runJob, deliverResult, intervalMs: options.intervalMs, jobTimeoutMs });\n\treturn { stop: () => scheduler.stop(), intervalMs: options.intervalMs ?? 60_000 };\n}\n"]}
@@ -16,7 +16,7 @@
16
16
  * "origin" (the chat that created the job, homeChannel fallback), a bare
17
17
  * platform name (that platform's homeChannel), or
18
18
  * "<platform>:<chatId>[:<threadId>]" (explicit). Content is wrapped as
19
- * "☾ Cron: <name>" and split to the adapter's maxMessageLength.
19
+ * "Cron: <name>" and split to the adapter's maxMessageLength.
20
20
  *
21
21
  * The bridge is re-installed after every cron session creation because loading
22
22
  * the builtin extensions (lunr-cron) re-registers its local-notify bridge per
@@ -179,7 +179,7 @@ export function createDeliverValidator(cfg) {
179
179
  }
180
180
  /** Wrap the run output with a compact cron header. */
181
181
  export function wrapCronContent(job, content) {
182
- return `☾ Cron: ${job.name}\n———\n${content}`;
182
+ return `Cron: ${job.name}\n———\n${content}`;
183
183
  }
184
184
  /**
185
185
  * The platform deliverer installed at the `@lunr/cron-delivery` bridge.
@@ -1 +1 @@
1
- {"version":3,"file":"cron.js","sourceRoot":"","sources":["../../src/gateway/cron.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * lunR: gateway cron runner + delivery (Phase 4 of the cron/gateway roadmap).\n *\n * startGatewayCron() runs the core/cron scheduler inside the `lunr gateway`\n * daemon:\n *\n * - runJob: each fire gets a FRESH headless agent session (createAgentSession\n * with SessionManager.inMemory — cron runs never write session files and\n * never touch the per-chat gateway sessions; the files under\n * <agentDir>/cron/output/ are the audit trail). The shared fire-guard\n * (core/cron/fire-guard) brackets the turn so the `cron` tool refuses to\n * run inside it (jobs cannot schedule jobs).\n * - deliverResult: replaces the `@lunr/cron-delivery` bridge on globalThis\n * with a platform deliverer. Targets come from job.deliver\n * (comma-separated): \"local\" (no-op — the output file is already written),\n * \"origin\" (the chat that created the job, homeChannel fallback), a bare\n * platform name (that platform's homeChannel), or\n * \"<platform>:<chatId>[:<threadId>]\" (explicit). Content is wrapped as\n * \"☾ Cron: <name>\" and split to the adapter's maxMessageLength.\n *\n * The bridge is re-installed after every cron session creation because loading\n * the builtin extensions (lunr-cron) re-registers its local-notify bridge per\n * session; delivery re-reads the bridge on every call, matching lunr-cron.\n *\n * Fallback models (2026-08-02): settings.json `cronFallbackModels`\n * (\"provider/modelId\" entries) are tried IN ORDER when a fire fails — timeout\n * or any error. Each attempt gets a fresh headless session and\n * jobTimeoutMs/candidates of the budget; a successful fallback run prefixes\n * \"[fell back to provider/modelId]\" to the output. TUI fires are unaffected\n * (they use the live session's model).\n */\n\nimport type { Model } from \"@ashx-j/lunr-ai\";\nimport { beginCronFire, endCronFire } from \"../core/cron/fire-guard.ts\";\nimport type { CronJob, CronJobOrigin } from \"../core/cron/jobs.ts\";\nimport { setCronDeliverValidator } from \"../core/cron/jobs.ts\";\nimport { startScheduler } from \"../core/cron/scheduler.ts\";\nimport { type BridgeSession, shutdownBridgeSession } from \"./agent-bridge.ts\";\nimport { type GatewayConfig, platformConfigFor } from \"./config.ts\";\nimport { splitMessage } from \"./text.ts\";\nimport type { PlatformAdapter } from \"./types.ts\";\n\nconst DELIVERY_BRIDGE_SYMBOL = Symbol.for(\"@lunr/cron-delivery\");\n\ntype DeliveryBridge = (job: CronJob, content: string) => Promise<string | null>;\n\n/** Test seam / default: one fresh headless session per cron fire. */\nexport interface CronModelRef {\n\tprovider: string;\n\tmodelId: string;\n}\n\nexport type CronSessionFactory = (job: CronJob, modelOverride?: CronModelRef) => Promise<BridgeSession>;\n\nexport interface GatewayCronOptions {\n\tadapters: Map<string, PlatformAdapter>;\n\tcfg: GatewayConfig;\n\t/** Tick interval; default 60s. */\n\tintervalMs?: number;\n\t/** Session factory for cron fires; defaults to a real headless agent session. */\n\tsessionFactory?: CronSessionFactory;\n\t/** Test seam: fallback models. When omitted, read fresh from settings.json `cronFallbackModels` on every fire. */\n\tfallbackModels?: CronModelRef[];\n}\n\nconst KNOWN_PLATFORMS = [\"telegram\", \"discord\"] as const;\n\n/** Default factory: fresh in-memory headless session, mirroring agent-bridge's wiring. */\nasync function defaultCronSessionFactory(job: CronJob, modelOverride?: CronModelRef): Promise<BridgeSession> {\n\tconst [\n\t\t{ loadAllBuiltinExtensions },\n\t\t{ getAgentDir },\n\t\t{ registerCustomizeBridge },\n\t\t{ registerMemoryCapBridge },\n\t\t{ registerModelTierBridge },\n\t\t{ createAgentSessionFromServices, createAgentSessionServices },\n\t\t{ SessionManager },\n\t\t{ SettingsManager },\n\t\t{ bindRuntimeBridges },\n\t] = await Promise.all([\n\t\timport(\"../builtin-extensions/index.ts\"),\n\t\timport(\"../config.ts\"),\n\t\timport(\"../core/customize.ts\"),\n\t\timport(\"../core/memory-cap.ts\"),\n\t\timport(\"../core/model-tiers.ts\"),\n\t\timport(\"../core/agent-session-services.ts\"),\n\t\timport(\"../core/session-manager.ts\"),\n\t\timport(\"../core/settings-manager.ts\"),\n\t\timport(\"../core/runtime-bridges.ts\"),\n\t]);\n\tconst cwd = job.workdir ?? process.cwd();\n\tconst agentDir = getAgentDir();\n\tconst settingsManager = SettingsManager.create(cwd, agentDir, { projectTrusted: false });\n\tregisterModelTierBridge(settingsManager);\n\tregisterMemoryCapBridge(settingsManager);\n\tregisterCustomizeBridge(settingsManager);\n\n\t// In-memory on purpose: cron runs must not persist session files or pollute\n\t// the per-chat gateway sessions — <agentDir>/cron/output/ is the audit trail.\n\tconst sessionManager = SessionManager.inMemory(cwd);\n\n\t// Services-first (mirrors main.ts): extension-registered providers (e.g.\n\t// ollama-cloud) must land in the shared ModelRuntime BEFORE session\n\t// creation — otherwise findInitialModel can't resolve the user's default\n\t// model and silently falls back to an arbitrary catalog provider.\n\tconst services = await createAgentSessionServices({\n\t\tcwd,\n\t\tagentDir,\n\t\tsettingsManager,\n\t\tresourceLoaderOptions: { extensionFactories: await loadAllBuiltinExtensions() },\n\t});\n\t// Fallback-model attempts pin the session model explicitly; an unresolvable\n\t// or unauthenticated override fails the attempt so the next fallback runs.\n\tlet model: Model<any> | undefined;\n\tif (modelOverride) {\n\t\tconst resolved = services.modelRuntime.getModel(modelOverride.provider, modelOverride.modelId);\n\t\tif (!resolved) throw new Error(`model not found: ${modelOverride.provider}/${modelOverride.modelId}`);\n\t\tif (!services.modelRuntime.hasConfiguredAuth(resolved.provider)) {\n\t\t\tthrow new Error(`no configured auth for provider: ${modelOverride.provider}`);\n\t\t}\n\t\tmodel = resolved;\n\t}\n\tconst { session } = await createAgentSessionFromServices({ services, sessionManager, model });\n\tbindRuntimeBridges({ session, services });\n\tawait session.bindExtensions({\n\t\tmode: \"print\",\n\t\tonError: (err) => console.error(`[gateway cron] extension error (${err.extensionPath}): ${err.error}`),\n\t});\n\treturn session;\n}\n\n/** Final assistant text, print-mode style; throws on error/aborted stop. */\nfunction extractFinalText(session: BridgeSession): string {\n\tconst messages = session.state.messages;\n\tconst last = messages[messages.length - 1];\n\tif (last?.role !== \"assistant\") return \"\";\n\tconst assistant = last as {\n\t\tstopReason?: string;\n\t\terrorMessage?: string;\n\t\tcontent?: Array<{ type: string; text?: string }>;\n\t};\n\tif (assistant.stopReason === \"error\" || assistant.stopReason === \"aborted\") {\n\t\tthrow new Error(assistant.errorMessage || `Request ${assistant.stopReason}`);\n\t}\n\tlet text = \"\";\n\tfor (const content of assistant.content ?? []) {\n\t\tif (content.type === \"text\") text += content.text ?? \"\";\n\t}\n\treturn text;\n}\n\ninterface ResolvedTarget {\n\tplatform: string;\n\tchatId: string;\n\tthreadId?: string;\n}\n\nconst EXPLICIT_TARGET_RE = /^([a-z0-9_-]+):([^:]+)(?::([^:]+))?$/i;\n\n/** Resolve one deliver target to a concrete destination. Returns an error string on failure. */\nfunction resolveTarget(target: string, job: CronJob, cfg: GatewayConfig): ResolvedTarget | string {\n\tif (target === \"origin\") {\n\t\tconst origin = job.origin;\n\t\tif (origin?.platform && origin.chatId) {\n\t\t\treturn { platform: origin.platform, chatId: origin.chatId, threadId: origin.threadId };\n\t\t}\n\t\t// Missing/incomplete origin → fall back to a homeChannel: the origin's\n\t\t// platform when known, otherwise the first platform that has one.\n\t\tconst platforms = origin?.platform ? [origin.platform] : KNOWN_PLATFORMS;\n\t\tfor (const platform of platforms) {\n\t\t\tconst home = platformConfigFor(cfg, platform)?.homeChannel;\n\t\t\tif (home) return { platform, chatId: home };\n\t\t}\n\t\treturn `deliver target \"origin\": job has no origin and no homeChannel is configured`;\n\t}\n\n\tconst explicit = EXPLICIT_TARGET_RE.exec(target);\n\tconst platform = explicit ? explicit[1].toLowerCase() : target.toLowerCase();\n\tif (platformConfigFor(cfg, platform) === undefined) {\n\t\treturn `unknown platform \"${explicit ? explicit[1] : target}\"`;\n\t}\n\tif (explicit) {\n\t\treturn { platform, chatId: explicit[2], threadId: explicit[3] };\n\t}\n\t// Bare platform name → its homeChannel.\n\tconst home = platformConfigFor(cfg, platform)?.homeChannel;\n\tif (!home) return `no homeChannel configured for ${platform}`;\n\treturn { platform, chatId: home };\n}\n\n/** True when a resolved chat may receive cron output for this job. */\nexport function isAllowedDeliverChat(\n\tcfg: GatewayConfig,\n\tplatform: string,\n\tchatId: string,\n\torigin?: CronJobOrigin | null,\n): boolean {\n\tconst platformCfg = platformConfigFor(cfg, platform);\n\tif (!platformCfg) return false;\n\tif (platformCfg.homeChannel && platformCfg.homeChannel === chatId) return true;\n\tif (platformCfg.allowedChats?.includes(chatId)) return true;\n\treturn origin?.platform === platform && origin.chatId === chatId;\n}\n\nexport function createDeliverValidator(\n\tcfg: GatewayConfig,\n): (deliver: string, origin?: CronJobOrigin | null) => string | undefined {\n\treturn (deliver, origin) => {\n\t\tconst targets = deliver\n\t\t\t.split(\",\")\n\t\t\t.map((t) => t.trim())\n\t\t\t.filter(Boolean);\n\t\tfor (const target of targets) {\n\t\t\tif (target === \"local\") continue;\n\t\t\tif (target === \"origin\") continue; // delivery time resolves/fails with a sensible error\n\t\t\tconst explicit = EXPLICIT_TARGET_RE.exec(target);\n\t\t\tconst platform = explicit ? explicit[1].toLowerCase() : target.toLowerCase();\n\t\t\tconst platformCfg = platformConfigFor(cfg, platform);\n\t\t\tif (!platformCfg) return `unknown platform \"${explicit ? explicit[1] : target}\"`;\n\t\t\tif (explicit) {\n\t\t\t\tif (!isAllowedDeliverChat(cfg, platform, explicit[2], origin)) {\n\t\t\t\t\treturn `deliver target \"${target}\" is not an allowed chat for ${platform}`;\n\t\t\t\t}\n\t\t\t} else if (!platformCfg.homeChannel) {\n\t\t\t\treturn `no homeChannel configured for ${platform}`;\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t};\n}\n\n/** Wrap the run output with a compact cron header. */\nexport function wrapCronContent(job: CronJob, content: string): string {\n\treturn `☾ Cron: ${job.name}\\n———\\n${content}`;\n}\n\n/**\n * The platform deliverer installed at the `@lunr/cron-delivery` bridge.\n * Every target is attempted; the first error is returned (null on success).\n */\nexport function createPlatformDeliverer(adapters: Map<string, PlatformAdapter>, cfg: GatewayConfig): DeliveryBridge {\n\treturn async (job, content) => {\n\t\tconst targets = job.deliver\n\t\t\t.split(\",\")\n\t\t\t.map((t) => t.trim())\n\t\t\t.filter(Boolean);\n\t\tlet firstError: string | null = null;\n\t\tfor (const target of targets) {\n\t\t\tif (target === \"local\") continue; // output file already written by the scheduler\n\t\t\tconst resolved = resolveTarget(target, job, cfg);\n\t\t\tif (typeof resolved === \"string\") {\n\t\t\t\tfirstError ??= resolved;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!isAllowedDeliverChat(cfg, resolved.platform, resolved.chatId, job.origin)) {\n\t\t\t\tfirstError ??= `deliver target \"${target}\" is not an allowed chat for ${resolved.platform}`;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst adapter = adapters.get(resolved.platform);\n\t\t\tif (!adapter) {\n\t\t\t\tfirstError ??= `no adapter connected for ${resolved.platform}`;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst wrapped = wrapCronContent(job, content);\n\t\t\tfor (const chunk of splitMessage(wrapped, adapter.maxMessageLength)) {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await adapter.send(resolved.chatId, chunk, {\n\t\t\t\t\t\tthreadId: resolved.threadId,\n\t\t\t\t\t});\n\t\t\t\t\tif (!result.success) {\n\t\t\t\t\t\tfirstError ??= `${resolved.platform} send failed: ${result.error ?? \"unknown error\"}`;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tfirstError ??= `${resolved.platform} send failed: ${err instanceof Error ? err.message : String(err)}`;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn firstError;\n\t};\n}\n\n/** Race one prompt attempt against its share of the job budget. */\nfunction withAttemptTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {\n\treturn Promise.race([\n\t\tpromise,\n\t\tnew Promise<never>((_, reject) => {\n\t\t\tconst timer = setTimeout(() => reject(new Error(`attempt timed out after ${ms}ms`)), ms);\n\t\t\t// If the attempt settles first this promise is discarded; the timer must not keep the process alive.\n\t\t\ttimer.unref?.();\n\t\t}),\n\t]);\n}\n\n/**\n * Read settings.json `cronFallbackModels` (\"provider/modelId\" entries).\n * Fresh read per fire so edits apply without a daemon restart; invalid\n * entries are skipped with a log line. Never throws.\n */\nasync function readCronFallbackModels(): Promise<CronModelRef[]> {\n\ttry {\n\t\tconst [{ getAgentDir }, { SettingsManager }] = await Promise.all([\n\t\t\timport(\"../config.ts\"),\n\t\t\timport(\"../core/settings-manager.ts\"),\n\t\t]);\n\t\tconst settingsManager = SettingsManager.create(process.cwd(), getAgentDir(), { projectTrusted: false });\n\t\tconst out: CronModelRef[] = [];\n\t\tfor (const entry of settingsManager.getCronFallbackModels()) {\n\t\t\tconst idx = entry.indexOf(\"/\");\n\t\t\tif (idx <= 0 || idx === entry.length - 1) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`[gateway cron] ignoring invalid cronFallbackModels entry \"${entry}\" (want \"provider/modelId\")`,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tout.push({ provider: entry.slice(0, idx), modelId: entry.slice(idx + 1) });\n\t\t}\n\t\treturn out;\n\t} catch (err) {\n\t\tconsole.error(\n\t\t\t`[gateway cron] failed to read cronFallbackModels: ${err instanceof Error ? err.message : String(err)}`,\n\t\t);\n\t\treturn [];\n\t}\n}\n\n/**\n * Start the cron scheduler inside the gateway daemon. Starts even with zero\n * stored jobs — jobs can be created later from chats. stop() halts the loop.\n */\nexport function startGatewayCron(options: GatewayCronOptions): { stop(): void; intervalMs: number } {\n\tconst { adapters, cfg } = options;\n\tconst sessionFactory = options.sessionFactory ?? defaultCronSessionFactory;\n\tconst platformDeliverer = createPlatformDeliverer(adapters, cfg);\n\n\t// Explicit so runJob can split it into per-attempt budgets that sum to (at\n\t// most) the scheduler's outer race (core/cron/scheduler.ts runSchedulerTick).\n\tconst jobTimeoutMs = 5 * 60 * 1000;\n\n\t// Reject deliver targets that are not local, origin, a configured home channel,\n\t// or an explicit chat in the platform allowlist / the job's origin.\n\tsetCronDeliverValidator(createDeliverValidator(cfg));\n\n\t// Replace the local-notify bridge lunr-cron registered with the platform deliverer.\n\t(globalThis as Record<symbol, unknown>)[DELIVERY_BRIDGE_SYMBOL] = platformDeliverer;\n\n\tconst runJob = async (prompt: string, job: CronJob): Promise<string> => {\n\t\tconst fallbacks = options.fallbackModels ?? (await readCronFallbackModels());\n\t\tconst candidates: Array<CronModelRef | undefined> = [undefined, ...fallbacks];\n\t\tconst attemptTimeoutMs = Math.floor(jobTimeoutMs / candidates.length);\n\t\tconst errors: string[] = [];\n\t\tbeginCronFire();\n\t\ttry {\n\t\t\tfor (const candidate of candidates) {\n\t\t\t\tconst label = candidate ? `${candidate.provider}/${candidate.modelId}` : \"default model\";\n\t\t\t\tlet session: BridgeSession | undefined;\n\t\t\t\ttry {\n\t\t\t\t\tsession = await sessionFactory(job, candidate);\n\t\t\t\t\t// Loading the builtin extensions re-registered lunr-cron's local-notify\n\t\t\t\t\t// bridge; restore the platform deliverer before any delivery re-reads it.\n\t\t\t\t\t(globalThis as Record<symbol, unknown>)[DELIVERY_BRIDGE_SYMBOL] = platformDeliverer;\n\t\t\t\t\tawait withAttemptTimeout(session.prompt(prompt, { source: \"extension\" }), attemptTimeoutMs);\n\t\t\t\t\tconst text = extractFinalText(session);\n\t\t\t\t\t// The marker lands in the output file (the audit trail); [SILENT] as\n\t\t\t\t\t// the last line still suppresses delivery (scheduler isSilent check).\n\t\t\t\t\treturn candidate ? `[fell back to ${label}]\\n${text}` : text;\n\t\t\t\t} catch (err) {\n\t\t\t\t\terrors.push(`${label}: ${err instanceof Error ? err.message : String(err)}`);\n\t\t\t\t} finally {\n\t\t\t\t\tif (session) await shutdownBridgeSession(session, \"quit\");\n\t\t\t\t}\n\t\t\t}\n\t\t\tthrow new Error(errors.join(\" | \") || \"no model candidates\");\n\t\t} finally {\n\t\t\tendCronFire();\n\t\t}\n\t};\n\n\tconst deliverResult = async (job: CronJob, content: string): Promise<void> => {\n\t\t// Re-read the bridge on every delivery (same pattern as lunr-cron).\n\t\tconst bridge = (globalThis as Record<symbol, unknown>)[DELIVERY_BRIDGE_SYMBOL] as DeliveryBridge | undefined;\n\t\tif (!bridge) throw new Error(\"no cron delivery bridge registered\");\n\t\tconst err = await bridge(job, content);\n\t\tif (err) throw new Error(err);\n\t};\n\n\tconst scheduler = startScheduler({ runJob, deliverResult, intervalMs: options.intervalMs, jobTimeoutMs });\n\treturn { stop: () => scheduler.stop(), intervalMs: options.intervalMs ?? 60_000 };\n}\n"]}
1
+ {"version":3,"file":"cron.js","sourceRoot":"","sources":["../../src/gateway/cron.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * lunR: gateway cron runner + delivery (Phase 4 of the cron/gateway roadmap).\n *\n * startGatewayCron() runs the core/cron scheduler inside the `lunr gateway`\n * daemon:\n *\n * - runJob: each fire gets a FRESH headless agent session (createAgentSession\n * with SessionManager.inMemory — cron runs never write session files and\n * never touch the per-chat gateway sessions; the files under\n * <agentDir>/cron/output/ are the audit trail). The shared fire-guard\n * (core/cron/fire-guard) brackets the turn so the `cron` tool refuses to\n * run inside it (jobs cannot schedule jobs).\n * - deliverResult: replaces the `@lunr/cron-delivery` bridge on globalThis\n * with a platform deliverer. Targets come from job.deliver\n * (comma-separated): \"local\" (no-op — the output file is already written),\n * \"origin\" (the chat that created the job, homeChannel fallback), a bare\n * platform name (that platform's homeChannel), or\n * \"<platform>:<chatId>[:<threadId>]\" (explicit). Content is wrapped as\n * \"Cron: <name>\" and split to the adapter's maxMessageLength.\n *\n * The bridge is re-installed after every cron session creation because loading\n * the builtin extensions (lunr-cron) re-registers its local-notify bridge per\n * session; delivery re-reads the bridge on every call, matching lunr-cron.\n *\n * Fallback models (2026-08-02): settings.json `cronFallbackModels`\n * (\"provider/modelId\" entries) are tried IN ORDER when a fire fails — timeout\n * or any error. Each attempt gets a fresh headless session and\n * jobTimeoutMs/candidates of the budget; a successful fallback run prefixes\n * \"[fell back to provider/modelId]\" to the output. TUI fires are unaffected\n * (they use the live session's model).\n */\n\nimport type { Model } from \"@ashx-j/lunr-ai\";\nimport { beginCronFire, endCronFire } from \"../core/cron/fire-guard.ts\";\nimport type { CronJob, CronJobOrigin } from \"../core/cron/jobs.ts\";\nimport { setCronDeliverValidator } from \"../core/cron/jobs.ts\";\nimport { startScheduler } from \"../core/cron/scheduler.ts\";\nimport { type BridgeSession, shutdownBridgeSession } from \"./agent-bridge.ts\";\nimport { type GatewayConfig, platformConfigFor } from \"./config.ts\";\nimport { splitMessage } from \"./text.ts\";\nimport type { PlatformAdapter } from \"./types.ts\";\n\nconst DELIVERY_BRIDGE_SYMBOL = Symbol.for(\"@lunr/cron-delivery\");\n\ntype DeliveryBridge = (job: CronJob, content: string) => Promise<string | null>;\n\n/** Test seam / default: one fresh headless session per cron fire. */\nexport interface CronModelRef {\n\tprovider: string;\n\tmodelId: string;\n}\n\nexport type CronSessionFactory = (job: CronJob, modelOverride?: CronModelRef) => Promise<BridgeSession>;\n\nexport interface GatewayCronOptions {\n\tadapters: Map<string, PlatformAdapter>;\n\tcfg: GatewayConfig;\n\t/** Tick interval; default 60s. */\n\tintervalMs?: number;\n\t/** Session factory for cron fires; defaults to a real headless agent session. */\n\tsessionFactory?: CronSessionFactory;\n\t/** Test seam: fallback models. When omitted, read fresh from settings.json `cronFallbackModels` on every fire. */\n\tfallbackModels?: CronModelRef[];\n}\n\nconst KNOWN_PLATFORMS = [\"telegram\", \"discord\"] as const;\n\n/** Default factory: fresh in-memory headless session, mirroring agent-bridge's wiring. */\nasync function defaultCronSessionFactory(job: CronJob, modelOverride?: CronModelRef): Promise<BridgeSession> {\n\tconst [\n\t\t{ loadAllBuiltinExtensions },\n\t\t{ getAgentDir },\n\t\t{ registerCustomizeBridge },\n\t\t{ registerMemoryCapBridge },\n\t\t{ registerModelTierBridge },\n\t\t{ createAgentSessionFromServices, createAgentSessionServices },\n\t\t{ SessionManager },\n\t\t{ SettingsManager },\n\t\t{ bindRuntimeBridges },\n\t] = await Promise.all([\n\t\timport(\"../builtin-extensions/index.ts\"),\n\t\timport(\"../config.ts\"),\n\t\timport(\"../core/customize.ts\"),\n\t\timport(\"../core/memory-cap.ts\"),\n\t\timport(\"../core/model-tiers.ts\"),\n\t\timport(\"../core/agent-session-services.ts\"),\n\t\timport(\"../core/session-manager.ts\"),\n\t\timport(\"../core/settings-manager.ts\"),\n\t\timport(\"../core/runtime-bridges.ts\"),\n\t]);\n\tconst cwd = job.workdir ?? process.cwd();\n\tconst agentDir = getAgentDir();\n\tconst settingsManager = SettingsManager.create(cwd, agentDir, { projectTrusted: false });\n\tregisterModelTierBridge(settingsManager);\n\tregisterMemoryCapBridge(settingsManager);\n\tregisterCustomizeBridge(settingsManager);\n\n\t// In-memory on purpose: cron runs must not persist session files or pollute\n\t// the per-chat gateway sessions — <agentDir>/cron/output/ is the audit trail.\n\tconst sessionManager = SessionManager.inMemory(cwd);\n\n\t// Services-first (mirrors main.ts): extension-registered providers (e.g.\n\t// ollama-cloud) must land in the shared ModelRuntime BEFORE session\n\t// creation — otherwise findInitialModel can't resolve the user's default\n\t// model and silently falls back to an arbitrary catalog provider.\n\tconst services = await createAgentSessionServices({\n\t\tcwd,\n\t\tagentDir,\n\t\tsettingsManager,\n\t\tresourceLoaderOptions: { extensionFactories: await loadAllBuiltinExtensions() },\n\t});\n\t// Fallback-model attempts pin the session model explicitly; an unresolvable\n\t// or unauthenticated override fails the attempt so the next fallback runs.\n\tlet model: Model<any> | undefined;\n\tif (modelOverride) {\n\t\tconst resolved = services.modelRuntime.getModel(modelOverride.provider, modelOverride.modelId);\n\t\tif (!resolved) throw new Error(`model not found: ${modelOverride.provider}/${modelOverride.modelId}`);\n\t\tif (!services.modelRuntime.hasConfiguredAuth(resolved.provider)) {\n\t\t\tthrow new Error(`no configured auth for provider: ${modelOverride.provider}`);\n\t\t}\n\t\tmodel = resolved;\n\t}\n\tconst { session } = await createAgentSessionFromServices({ services, sessionManager, model });\n\tbindRuntimeBridges({ session, services });\n\tawait session.bindExtensions({\n\t\tmode: \"print\",\n\t\tonError: (err) => console.error(`[gateway cron] extension error (${err.extensionPath}): ${err.error}`),\n\t});\n\treturn session;\n}\n\n/** Final assistant text, print-mode style; throws on error/aborted stop. */\nfunction extractFinalText(session: BridgeSession): string {\n\tconst messages = session.state.messages;\n\tconst last = messages[messages.length - 1];\n\tif (last?.role !== \"assistant\") return \"\";\n\tconst assistant = last as {\n\t\tstopReason?: string;\n\t\terrorMessage?: string;\n\t\tcontent?: Array<{ type: string; text?: string }>;\n\t};\n\tif (assistant.stopReason === \"error\" || assistant.stopReason === \"aborted\") {\n\t\tthrow new Error(assistant.errorMessage || `Request ${assistant.stopReason}`);\n\t}\n\tlet text = \"\";\n\tfor (const content of assistant.content ?? []) {\n\t\tif (content.type === \"text\") text += content.text ?? \"\";\n\t}\n\treturn text;\n}\n\ninterface ResolvedTarget {\n\tplatform: string;\n\tchatId: string;\n\tthreadId?: string;\n}\n\nconst EXPLICIT_TARGET_RE = /^([a-z0-9_-]+):([^:]+)(?::([^:]+))?$/i;\n\n/** Resolve one deliver target to a concrete destination. Returns an error string on failure. */\nfunction resolveTarget(target: string, job: CronJob, cfg: GatewayConfig): ResolvedTarget | string {\n\tif (target === \"origin\") {\n\t\tconst origin = job.origin;\n\t\tif (origin?.platform && origin.chatId) {\n\t\t\treturn { platform: origin.platform, chatId: origin.chatId, threadId: origin.threadId };\n\t\t}\n\t\t// Missing/incomplete origin → fall back to a homeChannel: the origin's\n\t\t// platform when known, otherwise the first platform that has one.\n\t\tconst platforms = origin?.platform ? [origin.platform] : KNOWN_PLATFORMS;\n\t\tfor (const platform of platforms) {\n\t\t\tconst home = platformConfigFor(cfg, platform)?.homeChannel;\n\t\t\tif (home) return { platform, chatId: home };\n\t\t}\n\t\treturn `deliver target \"origin\": job has no origin and no homeChannel is configured`;\n\t}\n\n\tconst explicit = EXPLICIT_TARGET_RE.exec(target);\n\tconst platform = explicit ? explicit[1].toLowerCase() : target.toLowerCase();\n\tif (platformConfigFor(cfg, platform) === undefined) {\n\t\treturn `unknown platform \"${explicit ? explicit[1] : target}\"`;\n\t}\n\tif (explicit) {\n\t\treturn { platform, chatId: explicit[2], threadId: explicit[3] };\n\t}\n\t// Bare platform name → its homeChannel.\n\tconst home = platformConfigFor(cfg, platform)?.homeChannel;\n\tif (!home) return `no homeChannel configured for ${platform}`;\n\treturn { platform, chatId: home };\n}\n\n/** True when a resolved chat may receive cron output for this job. */\nexport function isAllowedDeliverChat(\n\tcfg: GatewayConfig,\n\tplatform: string,\n\tchatId: string,\n\torigin?: CronJobOrigin | null,\n): boolean {\n\tconst platformCfg = platformConfigFor(cfg, platform);\n\tif (!platformCfg) return false;\n\tif (platformCfg.homeChannel && platformCfg.homeChannel === chatId) return true;\n\tif (platformCfg.allowedChats?.includes(chatId)) return true;\n\treturn origin?.platform === platform && origin.chatId === chatId;\n}\n\nexport function createDeliverValidator(\n\tcfg: GatewayConfig,\n): (deliver: string, origin?: CronJobOrigin | null) => string | undefined {\n\treturn (deliver, origin) => {\n\t\tconst targets = deliver\n\t\t\t.split(\",\")\n\t\t\t.map((t) => t.trim())\n\t\t\t.filter(Boolean);\n\t\tfor (const target of targets) {\n\t\t\tif (target === \"local\") continue;\n\t\t\tif (target === \"origin\") continue; // delivery time resolves/fails with a sensible error\n\t\t\tconst explicit = EXPLICIT_TARGET_RE.exec(target);\n\t\t\tconst platform = explicit ? explicit[1].toLowerCase() : target.toLowerCase();\n\t\t\tconst platformCfg = platformConfigFor(cfg, platform);\n\t\t\tif (!platformCfg) return `unknown platform \"${explicit ? explicit[1] : target}\"`;\n\t\t\tif (explicit) {\n\t\t\t\tif (!isAllowedDeliverChat(cfg, platform, explicit[2], origin)) {\n\t\t\t\t\treturn `deliver target \"${target}\" is not an allowed chat for ${platform}`;\n\t\t\t\t}\n\t\t\t} else if (!platformCfg.homeChannel) {\n\t\t\t\treturn `no homeChannel configured for ${platform}`;\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t};\n}\n\n/** Wrap the run output with a compact cron header. */\nexport function wrapCronContent(job: CronJob, content: string): string {\n\treturn `Cron: ${job.name}\\n———\\n${content}`;\n}\n\n/**\n * The platform deliverer installed at the `@lunr/cron-delivery` bridge.\n * Every target is attempted; the first error is returned (null on success).\n */\nexport function createPlatformDeliverer(adapters: Map<string, PlatformAdapter>, cfg: GatewayConfig): DeliveryBridge {\n\treturn async (job, content) => {\n\t\tconst targets = job.deliver\n\t\t\t.split(\",\")\n\t\t\t.map((t) => t.trim())\n\t\t\t.filter(Boolean);\n\t\tlet firstError: string | null = null;\n\t\tfor (const target of targets) {\n\t\t\tif (target === \"local\") continue; // output file already written by the scheduler\n\t\t\tconst resolved = resolveTarget(target, job, cfg);\n\t\t\tif (typeof resolved === \"string\") {\n\t\t\t\tfirstError ??= resolved;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!isAllowedDeliverChat(cfg, resolved.platform, resolved.chatId, job.origin)) {\n\t\t\t\tfirstError ??= `deliver target \"${target}\" is not an allowed chat for ${resolved.platform}`;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst adapter = adapters.get(resolved.platform);\n\t\t\tif (!adapter) {\n\t\t\t\tfirstError ??= `no adapter connected for ${resolved.platform}`;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst wrapped = wrapCronContent(job, content);\n\t\t\tfor (const chunk of splitMessage(wrapped, adapter.maxMessageLength)) {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await adapter.send(resolved.chatId, chunk, {\n\t\t\t\t\t\tthreadId: resolved.threadId,\n\t\t\t\t\t});\n\t\t\t\t\tif (!result.success) {\n\t\t\t\t\t\tfirstError ??= `${resolved.platform} send failed: ${result.error ?? \"unknown error\"}`;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tfirstError ??= `${resolved.platform} send failed: ${err instanceof Error ? err.message : String(err)}`;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn firstError;\n\t};\n}\n\n/** Race one prompt attempt against its share of the job budget. */\nfunction withAttemptTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {\n\treturn Promise.race([\n\t\tpromise,\n\t\tnew Promise<never>((_, reject) => {\n\t\t\tconst timer = setTimeout(() => reject(new Error(`attempt timed out after ${ms}ms`)), ms);\n\t\t\t// If the attempt settles first this promise is discarded; the timer must not keep the process alive.\n\t\t\ttimer.unref?.();\n\t\t}),\n\t]);\n}\n\n/**\n * Read settings.json `cronFallbackModels` (\"provider/modelId\" entries).\n * Fresh read per fire so edits apply without a daemon restart; invalid\n * entries are skipped with a log line. Never throws.\n */\nasync function readCronFallbackModels(): Promise<CronModelRef[]> {\n\ttry {\n\t\tconst [{ getAgentDir }, { SettingsManager }] = await Promise.all([\n\t\t\timport(\"../config.ts\"),\n\t\t\timport(\"../core/settings-manager.ts\"),\n\t\t]);\n\t\tconst settingsManager = SettingsManager.create(process.cwd(), getAgentDir(), { projectTrusted: false });\n\t\tconst out: CronModelRef[] = [];\n\t\tfor (const entry of settingsManager.getCronFallbackModels()) {\n\t\t\tconst idx = entry.indexOf(\"/\");\n\t\t\tif (idx <= 0 || idx === entry.length - 1) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`[gateway cron] ignoring invalid cronFallbackModels entry \"${entry}\" (want \"provider/modelId\")`,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tout.push({ provider: entry.slice(0, idx), modelId: entry.slice(idx + 1) });\n\t\t}\n\t\treturn out;\n\t} catch (err) {\n\t\tconsole.error(\n\t\t\t`[gateway cron] failed to read cronFallbackModels: ${err instanceof Error ? err.message : String(err)}`,\n\t\t);\n\t\treturn [];\n\t}\n}\n\n/**\n * Start the cron scheduler inside the gateway daemon. Starts even with zero\n * stored jobs — jobs can be created later from chats. stop() halts the loop.\n */\nexport function startGatewayCron(options: GatewayCronOptions): { stop(): void; intervalMs: number } {\n\tconst { adapters, cfg } = options;\n\tconst sessionFactory = options.sessionFactory ?? defaultCronSessionFactory;\n\tconst platformDeliverer = createPlatformDeliverer(adapters, cfg);\n\n\t// Explicit so runJob can split it into per-attempt budgets that sum to (at\n\t// most) the scheduler's outer race (core/cron/scheduler.ts runSchedulerTick).\n\tconst jobTimeoutMs = 5 * 60 * 1000;\n\n\t// Reject deliver targets that are not local, origin, a configured home channel,\n\t// or an explicit chat in the platform allowlist / the job's origin.\n\tsetCronDeliverValidator(createDeliverValidator(cfg));\n\n\t// Replace the local-notify bridge lunr-cron registered with the platform deliverer.\n\t(globalThis as Record<symbol, unknown>)[DELIVERY_BRIDGE_SYMBOL] = platformDeliverer;\n\n\tconst runJob = async (prompt: string, job: CronJob): Promise<string> => {\n\t\tconst fallbacks = options.fallbackModels ?? (await readCronFallbackModels());\n\t\tconst candidates: Array<CronModelRef | undefined> = [undefined, ...fallbacks];\n\t\tconst attemptTimeoutMs = Math.floor(jobTimeoutMs / candidates.length);\n\t\tconst errors: string[] = [];\n\t\tbeginCronFire();\n\t\ttry {\n\t\t\tfor (const candidate of candidates) {\n\t\t\t\tconst label = candidate ? `${candidate.provider}/${candidate.modelId}` : \"default model\";\n\t\t\t\tlet session: BridgeSession | undefined;\n\t\t\t\ttry {\n\t\t\t\t\tsession = await sessionFactory(job, candidate);\n\t\t\t\t\t// Loading the builtin extensions re-registered lunr-cron's local-notify\n\t\t\t\t\t// bridge; restore the platform deliverer before any delivery re-reads it.\n\t\t\t\t\t(globalThis as Record<symbol, unknown>)[DELIVERY_BRIDGE_SYMBOL] = platformDeliverer;\n\t\t\t\t\tawait withAttemptTimeout(session.prompt(prompt, { source: \"extension\" }), attemptTimeoutMs);\n\t\t\t\t\tconst text = extractFinalText(session);\n\t\t\t\t\t// The marker lands in the output file (the audit trail); [SILENT] as\n\t\t\t\t\t// the last line still suppresses delivery (scheduler isSilent check).\n\t\t\t\t\treturn candidate ? `[fell back to ${label}]\\n${text}` : text;\n\t\t\t\t} catch (err) {\n\t\t\t\t\terrors.push(`${label}: ${err instanceof Error ? err.message : String(err)}`);\n\t\t\t\t} finally {\n\t\t\t\t\tif (session) await shutdownBridgeSession(session, \"quit\");\n\t\t\t\t}\n\t\t\t}\n\t\t\tthrow new Error(errors.join(\" | \") || \"no model candidates\");\n\t\t} finally {\n\t\t\tendCronFire();\n\t\t}\n\t};\n\n\tconst deliverResult = async (job: CronJob, content: string): Promise<void> => {\n\t\t// Re-read the bridge on every delivery (same pattern as lunr-cron).\n\t\tconst bridge = (globalThis as Record<symbol, unknown>)[DELIVERY_BRIDGE_SYMBOL] as DeliveryBridge | undefined;\n\t\tif (!bridge) throw new Error(\"no cron delivery bridge registered\");\n\t\tconst err = await bridge(job, content);\n\t\tif (err) throw new Error(err);\n\t};\n\n\tconst scheduler = startScheduler({ runJob, deliverResult, intervalMs: options.intervalMs, jobTimeoutMs });\n\treturn { stop: () => scheduler.stop(), intervalMs: options.intervalMs ?? 60_000 };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/gateway/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAUH,OAAO,EAGN,KAAK,cAAc,EAGnB,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,CAahE","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} 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: [\n\t\t\"Telegram: talk to @BotFather → /newbot → copy the token into\",\n\t\t\" <agentDir>/gateway.json (telegram.token) or export LUNR_TELEGRAM_BOT_TOKEN,\",\n\t\t\" then set telegram.enabled = true.\",\n\t],\n\tdiscord: [\n\t\t\"Discord: discord.com/developers → New Application → Bot → copy the token into\",\n\t\t\" <agentDir>/gateway.json (discord.token) or export LUNR_DISCORD_BOT_TOKEN,\",\n\t\t\" enable the MESSAGE CONTENT intent, then set discord.enabled = true.\",\n\t],\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): Promise<number> {\n\tif (!code) {\n\t\tconsole.error(\"Usage: lunr gateway pair approve <platform> <code>\");\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\taddAllowedUser(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} (added to ${platform}.allowedUsers in gateway.json).`);\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, \"ready\");\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\tconst connected: PlatformAdapter[] = [];\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, \"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\tconnected.push(adapter);\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\";\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) owner.update(platformStatus);\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);\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] = rest;\n\t\tif (action === \"approve\" && platform) return runPairApprove(platform, code);\n\t\tif (action === \"list\") return runPairList();\n\t\tconsole.error(\"Usage: lunr gateway pair approve <platform> <code> | 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;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"]}
@@ -21,7 +21,7 @@ import { handleApprovalCallback } from "./approval.js";
21
21
  import { addAllowedUser } from "./authz.js";
22
22
  import { handleCallback, startButtonSweeper, stopButtonSweeper } from "./buttons.js";
23
23
  import { botCommandSpecs } from "./commands.js";
24
- import { loadGatewayConfig, platformConfigFor, resolvePlatformToken, } from "./config.js";
24
+ import { loadGatewayConfig, platformConfigFor, resolvePlatformToken, saveGatewayConfig, } from "./config.js";
25
25
  import { startGatewayCron } from "./cron.js";
26
26
  import { createPairingStore } from "./pairing.js";
27
27
  import { startGatewayPresenter, stopGatewayPresenter } from "./presenter.js";
@@ -46,16 +46,8 @@ export const ADAPTER_FACTORIES = {
46
46
  };
47
47
  const KNOWN_PLATFORMS = ["telegram", "discord"];
48
48
  const SETUP_INSTRUCTIONS = {
49
- telegram: [
50
- "Telegram: talk to @BotFather → /newbot → copy the token into",
51
- " <agentDir>/gateway.json (telegram.token) or export LUNR_TELEGRAM_BOT_TOKEN,",
52
- " then set telegram.enabled = true.",
53
- ],
54
- discord: [
55
- "Discord: discord.com/developers → New Application → Bot → copy the token into",
56
- " <agentDir>/gateway.json (discord.token) or export LUNR_DISCORD_BOT_TOKEN,",
57
- " enable the MESSAGE CONTENT intent, then set discord.enabled = true.",
58
- ],
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."],
59
51
  };
60
52
  function printSetupInstructions(enabledButUnrunnable) {
61
53
  console.log("lunR gateway: no runnable platforms configured.");
@@ -68,9 +60,9 @@ function printSetupInstructions(enabledButUnrunnable) {
68
60
  }
69
61
  }
70
62
  }
71
- async function runPairApprove(platform, code) {
63
+ async function runPairApprove(platform, code, owner = false) {
72
64
  if (!code) {
73
- console.error("Usage: lunr gateway pair approve <platform> <code>");
65
+ console.error("Usage: lunr gateway pair approve <platform> <code> [--owner]");
74
66
  return 1;
75
67
  }
76
68
  const pairing = createPairingStore();
@@ -80,13 +72,23 @@ async function runPairApprove(platform, code) {
80
72
  return 1;
81
73
  }
82
74
  try {
83
- addAllowedUser(platform, userId);
75
+ if (owner) {
76
+ const cfg = loadGatewayConfig();
77
+ if (platform !== "telegram" && platform !== "discord")
78
+ throw new Error("Unknown platform.");
79
+ cfg.owners ??= { telegram: [], discord: [] };
80
+ cfg.owners[platform] = [...new Set([...cfg.owners[platform], userId])];
81
+ cfg[platform].allowedUsers = [...new Set([...cfg[platform].allowedUsers, userId])];
82
+ saveGatewayConfig(cfg);
83
+ }
84
+ else
85
+ addAllowedUser(platform, userId);
84
86
  }
85
87
  catch (err) {
86
88
  console.error(`Paired ${platform} user ${userId}, but could not persist allowedUsers: ${err}`);
87
89
  return 1;
88
90
  }
89
- console.log(`Approved ${platform} user ${userId} (added to ${platform}.allowedUsers in gateway.json).`);
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"}.`);
90
92
  return 0;
91
93
  }
92
94
  function runPairList() {
@@ -200,7 +202,7 @@ async function runDaemon() {
200
202
  })().catch((error) => {
201
203
  console.error("Gateway shutdown failed:", error instanceof Error ? error.message : String(error));
202
204
  stopping = false;
203
- owner.update(platformStatus, "ready");
205
+ owner.update(platformStatus, Object.values(platformStatus).includes("connected") ? "ready" : "starting");
204
206
  startButtonSweeper();
205
207
  cron = startGatewayCron({ adapters, cfg });
206
208
  for (const [platform, adapter] of adapters)
@@ -212,14 +214,13 @@ async function runDaemon() {
212
214
  process.on("SIGINT", shutdown);
213
215
  process.on("SIGTERM", shutdown);
214
216
  startGatewayPresenter(adapters);
215
- const connected = [];
216
217
  startButtonSweeper();
217
218
  const connect = async (platform, adapter) => {
218
219
  if (stopping)
219
220
  return;
220
221
  try {
221
222
  platformStatus[platform] = "connecting";
222
- owner.update(platformStatus, "starting");
223
+ owner.update(platformStatus, Object.values(platformStatus).includes("connected") ? "ready" : "starting");
223
224
  const ok = await adapter.connect();
224
225
  if (!ok) {
225
226
  throw new Error("Connection failed");
@@ -243,12 +244,11 @@ async function runDaemon() {
243
244
  await adapter.disconnect();
244
245
  return;
245
246
  }
246
- connected.push(adapter);
247
247
  platformStatus[platform] = "connected";
248
248
  console.log(`lunR gateway: ${platform} connected`);
249
249
  }
250
250
  catch {
251
- platformStatus[platform] = "disconnected, retrying";
251
+ platformStatus[platform] = "disconnected, retrying (check gateway logs)";
252
252
  await adapter.disconnect().catch(() => { });
253
253
  if (!stopping) {
254
254
  const retry = setTimeout(() => {
@@ -259,7 +259,7 @@ async function runDaemon() {
259
259
  }
260
260
  }
261
261
  if (!stopping)
262
- owner.update(platformStatus);
262
+ owner.update(platformStatus, Object.values(platformStatus).includes("connected") ? "ready" : "starting");
263
263
  };
264
264
  for (const [platform, adapter] of adapters)
265
265
  void connect(platform, adapter);
@@ -267,19 +267,19 @@ async function runDaemon() {
267
267
  // Starts even with zero stored jobs — jobs can be created later from chats.
268
268
  cron = startGatewayCron({ adapters, cfg });
269
269
  console.log(`lunR gateway: cron scheduler started (${cron.intervalMs / 1000}s interval)`);
270
- owner.update(platformStatus);
270
+ owner.update(platformStatus, "starting");
271
271
  await stopped;
272
272
  return 0;
273
273
  }
274
274
  export async function runGateway(args) {
275
275
  const [sub, ...rest] = args;
276
276
  if (sub === "pair") {
277
- const [action, platform, code] = rest;
278
- if (action === "approve" && platform)
279
- return runPairApprove(platform, code);
277
+ const [action, platform, code, flag] = rest;
278
+ if (action === "approve" && platform && (flag === undefined || flag === "--owner"))
279
+ return runPairApprove(platform, code, flag === "--owner");
280
280
  if (action === "list")
281
281
  return runPairList();
282
- console.error("Usage: lunr gateway pair approve <platform> <code> | lunr gateway pair list");
282
+ console.error("Usage: lunr gateway pair approve <platform> <code> [--owner] | lunr gateway pair list");
283
283
  return 1;
284
284
  }
285
285
  if (sub === "status")
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/gateway/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACrF,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAEN,iBAAiB,EAEjB,iBAAiB,EACjB,oBAAoB,GACpB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAC7E,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG1C;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAA6D;IAC1F,QAAQ,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,eAAe,CAAC,GAAG,CAAC;IAC3C,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,cAAc,CAAC,GAAoB,CAAC;CAC1D,CAAC;AAEF,MAAM,eAAe,GAAG,CAAC,UAAU,EAAE,SAAS,CAAU,CAAC;AAEzD,MAAM,kBAAkB,GAA6B;IACpD,QAAQ,EAAE;QACT,kEAA8D;QAC9D,+EAA+E;QAC/E,qCAAqC;KACrC;IACD,OAAO,EAAE;QACR,qFAA+E;QAC/E,6EAA6E;QAC7E,uEAAuE;KACvE;CACD,CAAC;AAEF,SAAS,sBAAsB,CAAC,oBAA8B,EAAQ;IACrE,OAAO,CAAC,GAAG,CAAC,iDAAiD,CAAC,CAAC;IAC/D,KAAK,MAAM,QAAQ,IAAI,oBAAoB,EAAE,CAAC;QAC7C,OAAO,CAAC,GAAG,CAAC,gBAAgB,QAAQ,iEAAiE,CAAC,CAAC;IACxG,CAAC;IACD,KAAK,MAAM,QAAQ,IAAI,eAAe,EAAE,CAAC;QACxC,KAAK,MAAM,IAAI,IAAI,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAC;YACjD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC;IACF,CAAC;AAAA,CACD;AAED,KAAK,UAAU,cAAc,CAAC,QAAgB,EAAE,IAAwB,EAAmB;IAC1F,IAAI,CAAC,IAAI,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,oDAAoD,CAAC,CAAC;QACpE,OAAO,CAAC,CAAC;IACV,CAAC;IACD,MAAM,OAAO,GAAG,kBAAkB,EAAE,CAAC;IACrC,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC/C,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACrB,OAAO,CAAC,KAAK,CAAC,0CAA0C,IAAI,SAAS,QAAQ,mCAAmC,CAAC,CAAC;QAClH,OAAO,CAAC,CAAC;IACV,CAAC;IACD,IAAI,CAAC;QACJ,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAClC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,KAAK,CAAC,UAAU,QAAQ,SAAS,MAAM,yCAAyC,GAAG,EAAE,CAAC,CAAC;QAC/F,OAAO,CAAC,CAAC;IACV,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,YAAY,QAAQ,SAAS,MAAM,cAAc,QAAQ,iCAAiC,CAAC,CAAC;IACxG,OAAO,CAAC,CAAC;AAAA,CACT;AAED,SAAS,WAAW,GAAW;IAC9B,MAAM,OAAO,GAAG,kBAAkB,EAAE,CAAC;IACrC,MAAM,OAAO,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IACtC,MAAM,QAAQ,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IACxC,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;IACtC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACzB,CAAC;SAAM,CAAC;QACP,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACzB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,MAAM,aAAa,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QAC5G,CAAC;IACF,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IAC7B,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACzB,CAAC;SAAM,CAAC;QACP,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;YAC1B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,MAAM,cAAc,IAAI,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QAC9F,CAAC;IACF,CAAC;IACD,OAAO,CAAC,CAAC;AAAA,CACT;AAED,SAAS,SAAS,GAAW;IAC5B,MAAM,GAAG,GAAG,iBAAiB,EAAE,CAAC;IAChC,OAAO,CAAC,GAAG,CACV,mBAAmB,gBAAgB,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,0BAA0B,CACxG,CAAC;IACF,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC;IACpC,KAAK,MAAM,QAAQ,IAAI,eAAe,EAAE,CAAC;QACxC,MAAM,WAAW,GAAG,iBAAiB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QACrD,IAAI,CAAC,WAAW;YAAE,SAAS;QAC3B,MAAM,KAAK,GAAG,oBAAoB,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QAC1D,MAAM,OAAO,GAAG,iBAAiB,CAAC,QAAQ,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,oBAAoB,CAAC;QAChG,OAAO,CAAC,GAAG,CACV,KAAK,QAAQ,KAAK,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,aAAY,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,OAAM,OAAO,EAAE,CACvH,CAAC;IACH,CAAC;IACD,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;IAChC,OAAO,CAAC,GAAG,CAAC,eAAe,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,SAAS,CAAC,CAAC;IAClE,OAAO,CAAC,CAAC;AAAA,CACT;AAED,KAAK,UAAU,SAAS,GAAoB;IAC3C,IAAI,CAAC,gBAAgB,CAAC,gBAAgB,CAAC,EAAE,CAAC;QACzC,OAAO,CAAC,KAAK,CAAC,+CAA+C,CAAC,CAAC;QAC/D,OAAO,CAAC,KAAK,CAAC,kDAAkD,CAAC,CAAC;QAClE,OAAO,CAAC,CAAC;IACV,CAAC;IACD,MAAM,GAAG,GAAG,iBAAiB,EAAE,CAAC;IAChC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA2B,CAAC;IACpD,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,KAAK,MAAM,QAAQ,IAAI,eAAe,EAAE,CAAC;QACxC,MAAM,WAAW,GAAG,iBAAiB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QACrD,IAAI,CAAC,WAAW,EAAE,OAAO;YAAE,SAAS;QACpC,MAAM,OAAO,GAA2D,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QACpG,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC3B,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACvB,SAAS;QACV,CAAC;QACD,MAAM,KAAK,GAAG,oBAAoB,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QAC1D,IAAI,CAAC,KAAK,EAAE,CAAC;YACZ,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACvB,SAAS;QACV,CAAC;QACD,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,GAAG,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;IAC5D,CAAC;IAED,IAAI,QAAQ,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACzB,sBAAsB,CAAC,OAAO,CAAC,CAAC;QAChC,OAAO,CAAC,CAAC;IACV,CAAC;IAED,MAAM,OAAO,GAAG,kBAAkB,EAAE,CAAC;IACrC,MAAM,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;IACjC,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,MAAM,EAAE;QACtC,OAAO,CAAC,qBAAqB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,CAAC;YAC1D,MAAM,OAAO,GAAG,MAAM,CAAC,WAAW,CAAC,eAAe,CAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAC;YACjF,IAAI,CAAC,OAAO;gBAAE,OAAO,EAAE,CAAC;YACxB,IAAI,OAAO,KAAK,UAAU;gBAAE,OAAO,OAAO,CAAC,0BAA0B,EAAE,CAAC;YACxE,IAAI,OAAO,KAAK,OAAO;gBACtB,OAAO,CAAC,MAAM,OAAO,CAAC,YAAY,CAAC,YAAY,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;YACpG,OAAO,EAAE,CAAC;QAAA,CACV,CAAC,CAAC;IACJ,MAAM,MAAM,GAAG,YAAY,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC;IAE1G,IAAI,MAAmB,CAAC;IACxB,MAAM,OAAO,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC;QAC9C,MAAM,GAAG,OAAO,CAAC;IAAA,CACjB,CAAC,CAAC;IACH,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,IAAqD,CAAC;IAC1D,MAAM,cAAc,GAA2B,EAAE,CAAC;IAClD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAiC,CAAC;IACzD,MAAM,QAAQ,GAAG,GAAG,EAAE,CAAC;QACtB,IAAI,QAAQ;YAAE,OAAO;QACrB,QAAQ,GAAG,IAAI,CAAC;QAChB,KAAK,CAAC,MAAM,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC;QACzC,IAAI,EAAE,IAAI,EAAE,CAAC;QACb,KAAK,MAAM,KAAK,IAAI,OAAO;YAAE,YAAY,CAAC,KAAK,CAAC,CAAC;QACjD,iBAAiB,EAAE,CAAC;QACpB,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC;YACjB,MAAM,MAAM,CAAC,QAAQ,EAAE,CAAC;YACxB,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACjG,oBAAoB,EAAE,CAAC;YACvB,KAAK,CAAC,OAAO,EAAE,CAAC;YAChB,OAAO,CAAC,cAAc,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YAC3C,OAAO,CAAC,cAAc,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;YAC5C,MAAM,EAAE,CAAC;QAAA,CACT,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;YACrB,OAAO,CAAC,KAAK,CAAC,0BAA0B,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YAClG,QAAQ,GAAG,KAAK,CAAC;YACjB,KAAK,CAAC,MAAM,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;YACtC,kBAAkB,EAAE,CAAC;YACrB,IAAI,GAAG,gBAAgB,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;YAC3C,KAAK,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,QAAQ;gBACzC,IAAI,cAAc,CAAC,QAAQ,CAAC,KAAK,WAAW;oBAAE,KAAK,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAAA,CAC/E,CAAC,CAAC;IAAA,CACH,CAAC;IACF,MAAM,KAAK,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IACrC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IAChC,qBAAqB,CAAC,QAAQ,CAAC,CAAC;IAChC,MAAM,SAAS,GAAsB,EAAE,CAAC;IACxC,kBAAkB,EAAE,CAAC;IACrB,MAAM,OAAO,GAAG,KAAK,EAAE,QAAgB,EAAE,OAAwB,EAAiB,EAAE,CAAC;QACpF,IAAI,QAAQ;YAAE,OAAO;QACrB,IAAI,CAAC;YACJ,cAAc,CAAC,QAAQ,CAAC,GAAG,YAAY,CAAC;YACxC,KAAK,CAAC,MAAM,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC;YACzC,MAAM,EAAE,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;YACnC,IAAI,CAAC,EAAE,EAAE,CAAC;gBACT,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;YACtC,CAAC;YACD,OAAO,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;gBAC5B,KAAK,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YAAA,CAC/B,CAAC,CAAC;YACH,OAAO,CAAC,UAAU,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC;gBACnC,MAAM,QAAQ,GAAG,MAAM,sBAAsB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;gBAC9D,IAAI,CAAC,QAAQ,EAAE,CAAC;oBACf,KAAK,cAAc,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,iBAAiB,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;gBAC9F,CAAC;YAAA,CACD,CAAC,CAAC;YACH,IAAI,CAAC;gBACJ,MAAM,OAAO,CAAC,gBAAgB,EAAE,CAAC,eAAe,EAAE,CAAC,CAAC;YACrD,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACd,OAAO,CAAC,KAAK,CACZ,aAAa,QAAQ,uCAAuC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAC9G,CAAC;YACH,CAAC;YACD,IAAI,QAAQ,EAAE,CAAC;gBACd,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC;gBAC3B,OAAO;YACR,CAAC;YACD,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACxB,cAAc,CAAC,QAAQ,CAAC,GAAG,WAAW,CAAC;YACvC,OAAO,CAAC,GAAG,CAAC,iBAAiB,QAAQ,YAAY,CAAC,CAAC;QACpD,CAAC;QAAC,MAAM,CAAC;YACR,cAAc,CAAC,QAAQ,CAAC,GAAG,wBAAwB,CAAC;YACpD,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC;YAC3C,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACf,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC;oBAC9B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;oBACtB,KAAK,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;gBAAA,CAChC,EAAE,IAAI,CAAC,CAAC;gBACT,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACpB,CAAC;QACF,CAAC;QACD,IAAI,CAAC,QAAQ;YAAE,KAAK,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IAAA,CAC5C,CAAC;IACF,KAAK,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,QAAQ;QAAE,KAAK,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAE5E,uEAAuE;IACvE,8EAA4E;IAC5E,IAAI,GAAG,gBAAgB,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;IAC3C,OAAO,CAAC,GAAG,CAAC,yCAAyC,IAAI,CAAC,UAAU,GAAG,IAAI,aAAa,CAAC,CAAC;IAE1F,KAAK,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IAC7B,MAAM,OAAO,CAAC;IACd,OAAO,CAAC,CAAC;AAAA,CACT;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAc,EAAmB;IACjE,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAC5B,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;QACpB,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,IAAI,CAAC;QACtC,IAAI,MAAM,KAAK,SAAS,IAAI,QAAQ;YAAE,OAAO,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAC5E,IAAI,MAAM,KAAK,MAAM;YAAE,OAAO,WAAW,EAAE,CAAC;QAC5C,OAAO,CAAC,KAAK,CAAC,6EAA6E,CAAC,CAAC;QAC7F,OAAO,CAAC,CAAC;IACV,CAAC;IACD,IAAI,GAAG,KAAK,QAAQ;QAAE,OAAO,SAAS,EAAE,CAAC;IACzC,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,SAAS,EAAE,CAAC;IAC1C,OAAO,CAAC,KAAK,CAAC,gCAAgC,GAAG,gDAAgD,CAAC,CAAC;IACnG,OAAO,CAAC,CAAC;AAAA,CACT","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} 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: [\n\t\t\"Telegram: talk to @BotFather → /newbot → copy the token into\",\n\t\t\" <agentDir>/gateway.json (telegram.token) or export LUNR_TELEGRAM_BOT_TOKEN,\",\n\t\t\" then set telegram.enabled = true.\",\n\t],\n\tdiscord: [\n\t\t\"Discord: discord.com/developers → New Application → Bot → copy the token into\",\n\t\t\" <agentDir>/gateway.json (discord.token) or export LUNR_DISCORD_BOT_TOKEN,\",\n\t\t\" enable the MESSAGE CONTENT intent, then set discord.enabled = true.\",\n\t],\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): Promise<number> {\n\tif (!code) {\n\t\tconsole.error(\"Usage: lunr gateway pair approve <platform> <code>\");\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\taddAllowedUser(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} (added to ${platform}.allowedUsers in gateway.json).`);\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, \"ready\");\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\tconst connected: PlatformAdapter[] = [];\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, \"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\tconnected.push(adapter);\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\";\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) owner.update(platformStatus);\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);\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] = rest;\n\t\tif (action === \"approve\" && platform) return runPairApprove(platform, code);\n\t\tif (action === \"list\") return runPairList();\n\t\tconsole.error(\"Usage: lunr gateway pair approve <platform> <code> | 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 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"]}