@ashx-j/lunr 0.2.25 → 0.2.27

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 (729) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/LICENSE +22 -0
  3. package/dist/builtin-extensions/ashxj-thinking.d.ts +1 -1
  4. package/dist/builtin-extensions/ashxj-thinking.d.ts.map +1 -1
  5. package/dist/builtin-extensions/ashxj-thinking.js +1 -1
  6. package/dist/builtin-extensions/ashxj-thinking.js.map +1 -1
  7. package/dist/builtin-extensions/ashxj-tui.d.ts +11 -1
  8. package/dist/builtin-extensions/ashxj-tui.d.ts.map +1 -1
  9. package/dist/builtin-extensions/ashxj-tui.js +6 -10
  10. package/dist/builtin-extensions/ashxj-tui.js.map +1 -1
  11. package/dist/builtin-extensions/index.d.ts.map +1 -1
  12. package/dist/builtin-extensions/index.js +1 -0
  13. package/dist/builtin-extensions/index.js.map +1 -1
  14. package/dist/builtin-extensions/lunr-computer-use.d.ts +3 -0
  15. package/dist/builtin-extensions/lunr-computer-use.d.ts.map +1 -0
  16. package/dist/builtin-extensions/lunr-computer-use.js +179 -0
  17. package/dist/builtin-extensions/lunr-computer-use.js.map +1 -0
  18. package/dist/builtin-extensions/lunr-cron.d.ts +4 -5
  19. package/dist/builtin-extensions/lunr-cron.d.ts.map +1 -1
  20. package/dist/builtin-extensions/lunr-cron.js +94 -108
  21. package/dist/builtin-extensions/lunr-cron.js.map +1 -1
  22. package/dist/builtin-extensions/lunr-local-providers/local-servers.d.ts.map +1 -1
  23. package/dist/builtin-extensions/lunr-local-providers/local-servers.js +3 -1
  24. package/dist/builtin-extensions/lunr-local-providers/local-servers.js.map +1 -1
  25. package/dist/builtin-extensions/narumiruna-pi-goal/src/commands.d.ts.map +1 -1
  26. package/dist/builtin-extensions/narumiruna-pi-goal/src/commands.js +2 -2
  27. package/dist/builtin-extensions/narumiruna-pi-goal/src/commands.js.map +1 -1
  28. package/dist/builtin-extensions/narumiruna-pi-goal/src/goal.d.ts.map +1 -1
  29. package/dist/builtin-extensions/narumiruna-pi-goal/src/goal.js +1 -1
  30. package/dist/builtin-extensions/narumiruna-pi-goal/src/goal.js.map +1 -1
  31. package/dist/builtin-extensions/narumiruna-pi-goal/src/runtime.d.ts.map +1 -1
  32. package/dist/builtin-extensions/narumiruna-pi-goal/src/runtime.js +1 -1
  33. package/dist/builtin-extensions/narumiruna-pi-goal/src/runtime.js.map +1 -1
  34. package/dist/builtin-extensions/pi-intercom/index.d.ts.map +1 -1
  35. package/dist/builtin-extensions/pi-intercom/index.js +2 -2
  36. package/dist/builtin-extensions/pi-intercom/index.js.map +1 -1
  37. package/dist/builtin-extensions/pi-lsp-extension/bemol-extension/index.d.ts +1 -1
  38. package/dist/builtin-extensions/pi-lsp-extension/bemol-extension/index.d.ts.map +1 -1
  39. package/dist/builtin-extensions/pi-lsp-extension/bemol-extension/index.js +1 -1
  40. package/dist/builtin-extensions/pi-lsp-extension/bemol-extension/index.js.map +1 -1
  41. package/dist/builtin-extensions/pi-lsp-extension/src/index.d.ts.map +1 -1
  42. package/dist/builtin-extensions/pi-lsp-extension/src/index.js +1 -15
  43. package/dist/builtin-extensions/pi-lsp-extension/src/index.js.map +1 -1
  44. package/dist/builtin-extensions/pi-lsp-extension/src/lsp-client.d.ts +6 -0
  45. package/dist/builtin-extensions/pi-lsp-extension/src/lsp-client.d.ts.map +1 -1
  46. package/dist/builtin-extensions/pi-lsp-extension/src/lsp-client.js +187 -132
  47. package/dist/builtin-extensions/pi-lsp-extension/src/lsp-client.js.map +1 -1
  48. package/dist/builtin-extensions/pi-lsp-extension/src/lsp-manager.d.ts +8 -0
  49. package/dist/builtin-extensions/pi-lsp-extension/src/lsp-manager.d.ts.map +1 -1
  50. package/dist/builtin-extensions/pi-lsp-extension/src/lsp-manager.js +142 -112
  51. package/dist/builtin-extensions/pi-lsp-extension/src/lsp-manager.js.map +1 -1
  52. package/dist/builtin-extensions/pi-lsp-extension/src/tools/code-rewrite.d.ts +12 -1
  53. package/dist/builtin-extensions/pi-lsp-extension/src/tools/code-rewrite.d.ts.map +1 -1
  54. package/dist/builtin-extensions/pi-lsp-extension/src/tools/code-rewrite.js +14 -7
  55. package/dist/builtin-extensions/pi-lsp-extension/src/tools/code-rewrite.js.map +1 -1
  56. package/dist/builtin-extensions/pi-lsp-extension/src/tree-sitter/rewrite-engine.d.ts +11 -3
  57. package/dist/builtin-extensions/pi-lsp-extension/src/tree-sitter/rewrite-engine.d.ts.map +1 -1
  58. package/dist/builtin-extensions/pi-lsp-extension/src/tree-sitter/rewrite-engine.js +67 -41
  59. package/dist/builtin-extensions/pi-lsp-extension/src/tree-sitter/rewrite-engine.js.map +1 -1
  60. package/dist/builtin-extensions/pi-lsp-extension/src/tree-sitter/search-engine.d.ts +4 -2
  61. package/dist/builtin-extensions/pi-lsp-extension/src/tree-sitter/search-engine.d.ts.map +1 -1
  62. package/dist/builtin-extensions/pi-lsp-extension/src/tree-sitter/search-engine.js +3 -0
  63. package/dist/builtin-extensions/pi-lsp-extension/src/tree-sitter/search-engine.js.map +1 -1
  64. package/dist/builtin-extensions/pi-mcp-adapter/commands.d.ts +2 -1
  65. package/dist/builtin-extensions/pi-mcp-adapter/commands.d.ts.map +1 -1
  66. package/dist/builtin-extensions/pi-mcp-adapter/commands.js +6 -6
  67. package/dist/builtin-extensions/pi-mcp-adapter/commands.js.map +1 -1
  68. package/dist/builtin-extensions/pi-mcp-adapter/config.d.ts +1 -1
  69. package/dist/builtin-extensions/pi-mcp-adapter/config.d.ts.map +1 -1
  70. package/dist/builtin-extensions/pi-mcp-adapter/config.js +11 -5
  71. package/dist/builtin-extensions/pi-mcp-adapter/config.js.map +1 -1
  72. package/dist/builtin-extensions/pi-mcp-adapter/direct-tool-executor.d.ts.map +1 -1
  73. package/dist/builtin-extensions/pi-mcp-adapter/direct-tool-executor.js +2 -2
  74. package/dist/builtin-extensions/pi-mcp-adapter/direct-tool-executor.js.map +1 -1
  75. package/dist/builtin-extensions/pi-mcp-adapter/host-html-template.d.ts.map +1 -1
  76. package/dist/builtin-extensions/pi-mcp-adapter/host-html-template.js +1 -1
  77. package/dist/builtin-extensions/pi-mcp-adapter/host-html-template.js.map +1 -1
  78. package/dist/builtin-extensions/pi-mcp-adapter/index.d.ts.map +1 -1
  79. package/dist/builtin-extensions/pi-mcp-adapter/index.js +48 -40
  80. package/dist/builtin-extensions/pi-mcp-adapter/index.js.map +1 -1
  81. package/dist/builtin-extensions/pi-mcp-adapter/init.d.ts.map +1 -1
  82. package/dist/builtin-extensions/pi-mcp-adapter/init.js +3 -1
  83. package/dist/builtin-extensions/pi-mcp-adapter/init.js.map +1 -1
  84. package/dist/builtin-extensions/pi-mcp-adapter/mcp-auth-flow.d.ts +12 -40
  85. package/dist/builtin-extensions/pi-mcp-adapter/mcp-auth-flow.d.ts.map +1 -1
  86. package/dist/builtin-extensions/pi-mcp-adapter/mcp-auth-flow.js +166 -235
  87. package/dist/builtin-extensions/pi-mcp-adapter/mcp-auth-flow.js.map +1 -1
  88. package/dist/builtin-extensions/pi-mcp-adapter/mcp-callback-server.d.ts +5 -0
  89. package/dist/builtin-extensions/pi-mcp-adapter/mcp-callback-server.d.ts.map +1 -1
  90. package/dist/builtin-extensions/pi-mcp-adapter/mcp-callback-server.js +43 -13
  91. package/dist/builtin-extensions/pi-mcp-adapter/mcp-callback-server.js.map +1 -1
  92. package/dist/builtin-extensions/pi-mcp-adapter/mcp-oauth-provider.d.ts +14 -3
  93. package/dist/builtin-extensions/pi-mcp-adapter/mcp-oauth-provider.d.ts.map +1 -1
  94. package/dist/builtin-extensions/pi-mcp-adapter/mcp-oauth-provider.js +73 -21
  95. package/dist/builtin-extensions/pi-mcp-adapter/mcp-oauth-provider.js.map +1 -1
  96. package/dist/builtin-extensions/pi-mcp-adapter/proxy-modes.d.ts.map +1 -1
  97. package/dist/builtin-extensions/pi-mcp-adapter/proxy-modes.js +4 -4
  98. package/dist/builtin-extensions/pi-mcp-adapter/proxy-modes.js.map +1 -1
  99. package/dist/builtin-extensions/pi-mcp-adapter/server-manager.d.ts.map +1 -1
  100. package/dist/builtin-extensions/pi-mcp-adapter/server-manager.js +2 -2
  101. package/dist/builtin-extensions/pi-mcp-adapter/server-manager.js.map +1 -1
  102. package/dist/builtin-extensions/pi-mcp-adapter/state.d.ts +2 -0
  103. package/dist/builtin-extensions/pi-mcp-adapter/state.d.ts.map +1 -1
  104. package/dist/builtin-extensions/pi-mcp-adapter/state.js.map +1 -1
  105. package/dist/builtin-extensions/pi-ollama-cloud/index.d.ts +1 -1
  106. package/dist/builtin-extensions/pi-ollama-cloud/index.d.ts.map +1 -1
  107. package/dist/builtin-extensions/pi-ollama-cloud/index.js +1 -1
  108. package/dist/builtin-extensions/pi-ollama-cloud/index.js.map +1 -1
  109. package/dist/builtin-extensions/pi-ollama-cloud/models.d.ts +11 -5
  110. package/dist/builtin-extensions/pi-ollama-cloud/models.d.ts.map +1 -1
  111. package/dist/builtin-extensions/pi-ollama-cloud/models.js +12 -32
  112. package/dist/builtin-extensions/pi-ollama-cloud/models.js.map +1 -1
  113. package/dist/builtin-extensions/pi-prompt-template-model/index.d.ts.map +1 -1
  114. package/dist/builtin-extensions/pi-prompt-template-model/index.js +5 -3
  115. package/dist/builtin-extensions/pi-prompt-template-model/index.js.map +1 -1
  116. package/dist/builtin-extensions/pi-prompt-template-model/prompt-loader.d.ts +2 -1
  117. package/dist/builtin-extensions/pi-prompt-template-model/prompt-loader.d.ts.map +1 -1
  118. package/dist/builtin-extensions/pi-prompt-template-model/prompt-loader.js +21 -12
  119. package/dist/builtin-extensions/pi-prompt-template-model/prompt-loader.js.map +1 -1
  120. package/dist/builtin-extensions/pi-prompt-template-model/tool-manager.d.ts.map +1 -1
  121. package/dist/builtin-extensions/pi-prompt-template-model/tool-manager.js +7 -24
  122. package/dist/builtin-extensions/pi-prompt-template-model/tool-manager.js.map +1 -1
  123. package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts.map +1 -1
  124. package/dist/builtin-extensions/pi-subagents/src/extension/index.js +39 -77
  125. package/dist/builtin-extensions/pi-subagents/src/extension/index.js.map +1 -1
  126. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts.map +1 -1
  127. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js +1 -1
  128. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js.map +1 -1
  129. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts +4 -4
  130. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts.map +1 -1
  131. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js +8 -5
  132. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js.map +1 -1
  133. package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.d.ts +1 -1
  134. package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.d.ts.map +1 -1
  135. package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.js +15 -31
  136. package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.js.map +1 -1
  137. package/dist/builtin-extensions/pi-subagents/src/runs/background/result-watcher.d.ts.map +1 -1
  138. package/dist/builtin-extensions/pi-subagents/src/runs/background/result-watcher.js +7 -2
  139. package/dist/builtin-extensions/pi-subagents/src/runs/background/result-watcher.js.map +1 -1
  140. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.d.ts +1 -0
  141. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.d.ts.map +1 -1
  142. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js +21 -4
  143. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js.map +1 -1
  144. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.d.ts.map +1 -1
  145. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js +11 -4
  146. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js.map +1 -1
  147. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts.map +1 -1
  148. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js +121 -25
  149. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js.map +1 -1
  150. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts.map +1 -1
  151. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js +25 -5
  152. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js.map +1 -1
  153. package/dist/builtin-extensions/pi-subagents/src/runs/shared/chain-outputs.d.ts +1 -0
  154. package/dist/builtin-extensions/pi-subagents/src/runs/shared/chain-outputs.d.ts.map +1 -1
  155. package/dist/builtin-extensions/pi-subagents/src/runs/shared/chain-outputs.js +5 -0
  156. package/dist/builtin-extensions/pi-subagents/src/runs/shared/chain-outputs.js.map +1 -1
  157. package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.d.ts +1 -1
  158. package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.d.ts.map +1 -1
  159. package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.js +2 -0
  160. package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.js.map +1 -1
  161. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.d.ts +1 -1
  162. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.d.ts.map +1 -1
  163. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.js +1 -1
  164. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.js.map +1 -1
  165. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts +7 -8
  166. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts.map +1 -1
  167. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.js +60 -32
  168. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.js.map +1 -1
  169. package/dist/builtin-extensions/pi-subagents/src/shared/utils.d.ts +1 -1
  170. package/dist/builtin-extensions/pi-subagents/src/shared/utils.d.ts.map +1 -1
  171. package/dist/builtin-extensions/pi-subagents/src/shared/utils.js +15 -5
  172. package/dist/builtin-extensions/pi-subagents/src/shared/utils.js.map +1 -1
  173. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.d.ts.map +1 -1
  174. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js +4 -3
  175. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js.map +1 -1
  176. package/dist/builtin-extensions/pi-subagents/src/tui/communication.d.ts.map +1 -1
  177. package/dist/builtin-extensions/pi-subagents/src/tui/communication.js +1 -1
  178. package/dist/builtin-extensions/pi-subagents/src/tui/communication.js.map +1 -1
  179. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts +2 -1
  180. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts.map +1 -1
  181. package/dist/builtin-extensions/pi-subagents/src/tui/render.js +22 -11
  182. package/dist/builtin-extensions/pi-subagents/src/tui/render.js.map +1 -1
  183. package/dist/builtin-extensions/pi-subagents/src/watchdog/review.d.ts.map +1 -1
  184. package/dist/builtin-extensions/pi-subagents/src/watchdog/review.js +3 -3
  185. package/dist/builtin-extensions/pi-subagents/src/watchdog/review.js.map +1 -1
  186. package/dist/builtin-extensions/pi-tps.d.ts.map +1 -1
  187. package/dist/builtin-extensions/pi-tps.js +20 -17
  188. package/dist/builtin-extensions/pi-tps.js.map +1 -1
  189. package/dist/builtin-extensions/pi-web-access/activity.d.ts +1 -0
  190. package/dist/builtin-extensions/pi-web-access/activity.d.ts.map +1 -1
  191. package/dist/builtin-extensions/pi-web-access/activity.js +27 -5
  192. package/dist/builtin-extensions/pi-web-access/activity.js.map +1 -1
  193. package/dist/builtin-extensions/pi-web-access/bounded-body.d.ts +3 -0
  194. package/dist/builtin-extensions/pi-web-access/bounded-body.d.ts.map +1 -0
  195. package/dist/builtin-extensions/pi-web-access/bounded-body.js +43 -0
  196. package/dist/builtin-extensions/pi-web-access/bounded-body.js.map +1 -0
  197. package/dist/builtin-extensions/pi-web-access/content-page.d.ts +24 -0
  198. package/dist/builtin-extensions/pi-web-access/content-page.d.ts.map +1 -0
  199. package/dist/builtin-extensions/pi-web-access/content-page.js +57 -0
  200. package/dist/builtin-extensions/pi-web-access/content-page.js.map +1 -0
  201. package/dist/builtin-extensions/pi-web-access/extract.d.ts.map +1 -1
  202. package/dist/builtin-extensions/pi-web-access/extract.js +7 -2
  203. package/dist/builtin-extensions/pi-web-access/extract.js.map +1 -1
  204. package/dist/builtin-extensions/pi-web-access/github-extract.d.ts +0 -1
  205. package/dist/builtin-extensions/pi-web-access/github-extract.d.ts.map +1 -1
  206. package/dist/builtin-extensions/pi-web-access/github-extract.js +147 -36
  207. package/dist/builtin-extensions/pi-web-access/github-extract.js.map +1 -1
  208. package/dist/builtin-extensions/pi-web-access/index.d.ts.map +1 -1
  209. package/dist/builtin-extensions/pi-web-access/index.js +48 -73
  210. package/dist/builtin-extensions/pi-web-access/index.js.map +1 -1
  211. package/dist/builtin-extensions/pi-web-access/session-cleanup.d.ts +12 -4
  212. package/dist/builtin-extensions/pi-web-access/session-cleanup.d.ts.map +1 -1
  213. package/dist/builtin-extensions/pi-web-access/session-cleanup.js +35 -12
  214. package/dist/builtin-extensions/pi-web-access/session-cleanup.js.map +1 -1
  215. package/dist/builtin-extensions/pi-web-access/storage.d.ts +9 -6
  216. package/dist/builtin-extensions/pi-web-access/storage.d.ts.map +1 -1
  217. package/dist/builtin-extensions/pi-web-access/storage.js +36 -24
  218. package/dist/builtin-extensions/pi-web-access/storage.js.map +1 -1
  219. package/dist/builtin-extensions/simple-pi-memory.d.ts +1 -1
  220. package/dist/builtin-extensions/simple-pi-memory.d.ts.map +1 -1
  221. package/dist/builtin-extensions/simple-pi-memory.js +1 -1
  222. package/dist/builtin-extensions/simple-pi-memory.js.map +1 -1
  223. package/dist/cli/install-cli.d.ts.map +1 -1
  224. package/dist/cli/install-cli.js +38 -30
  225. package/dist/cli/install-cli.js.map +1 -1
  226. package/dist/core/agent-session-runtime.d.ts +13 -0
  227. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  228. package/dist/core/agent-session-runtime.js +75 -11
  229. package/dist/core/agent-session-runtime.js.map +1 -1
  230. package/dist/core/agent-session-services.d.ts +1 -0
  231. package/dist/core/agent-session-services.d.ts.map +1 -1
  232. package/dist/core/agent-session-services.js +1 -0
  233. package/dist/core/agent-session-services.js.map +1 -1
  234. package/dist/core/agent-session.d.ts +40 -6
  235. package/dist/core/agent-session.d.ts.map +1 -1
  236. package/dist/core/agent-session.js +309 -76
  237. package/dist/core/agent-session.js.map +1 -1
  238. package/dist/core/browser/runtime.d.ts +2 -0
  239. package/dist/core/browser/runtime.d.ts.map +1 -1
  240. package/dist/core/browser/runtime.js +80 -45
  241. package/dist/core/browser/runtime.js.map +1 -1
  242. package/dist/core/browser/setup.d.ts.map +1 -1
  243. package/dist/core/browser/setup.js +12 -1
  244. package/dist/core/browser/setup.js.map +1 -1
  245. package/dist/core/computer-use/adapter.d.ts +28 -0
  246. package/dist/core/computer-use/adapter.d.ts.map +1 -0
  247. package/dist/core/computer-use/adapter.js +138 -0
  248. package/dist/core/computer-use/adapter.js.map +1 -0
  249. package/dist/core/computer-use/image.d.ts +27 -0
  250. package/dist/core/computer-use/image.d.ts.map +1 -0
  251. package/dist/core/computer-use/image.js +84 -0
  252. package/dist/core/computer-use/image.js.map +1 -0
  253. package/dist/core/computer-use/lease.d.ts +18 -0
  254. package/dist/core/computer-use/lease.d.ts.map +1 -0
  255. package/dist/core/computer-use/lease.js +129 -0
  256. package/dist/core/computer-use/lease.js.map +1 -0
  257. package/dist/core/computer-use/macos-runtime.d.ts +14 -0
  258. package/dist/core/computer-use/macos-runtime.d.ts.map +1 -0
  259. package/dist/core/computer-use/macos-runtime.js +148 -0
  260. package/dist/core/computer-use/macos-runtime.js.map +1 -0
  261. package/dist/core/computer-use/policy.d.ts +19 -0
  262. package/dist/core/computer-use/policy.d.ts.map +1 -0
  263. package/dist/core/computer-use/policy.js +67 -0
  264. package/dist/core/computer-use/policy.js.map +1 -0
  265. package/dist/core/computer-use/release.generated.d.ts +31 -0
  266. package/dist/core/computer-use/release.generated.d.ts.map +1 -0
  267. package/dist/core/computer-use/release.generated.js +36 -0
  268. package/dist/core/computer-use/release.generated.js.map +1 -0
  269. package/dist/core/computer-use/runtime.d.ts +13 -0
  270. package/dist/core/computer-use/runtime.d.ts.map +1 -0
  271. package/dist/core/computer-use/runtime.js +158 -0
  272. package/dist/core/computer-use/runtime.js.map +1 -0
  273. package/dist/core/computer-use/schemas.d.ts +100 -0
  274. package/dist/core/computer-use/schemas.d.ts.map +1 -0
  275. package/dist/core/computer-use/schemas.js +96 -0
  276. package/dist/core/computer-use/schemas.js.map +1 -0
  277. package/dist/core/computer-use/windows-desktop.d.ts +3 -0
  278. package/dist/core/computer-use/windows-desktop.d.ts.map +1 -0
  279. package/dist/core/computer-use/windows-desktop.js +48 -0
  280. package/dist/core/computer-use/windows-desktop.js.map +1 -0
  281. package/dist/core/computer-use/workflow.d.ts +38 -0
  282. package/dist/core/computer-use/workflow.d.ts.map +1 -0
  283. package/dist/core/computer-use/workflow.js +900 -0
  284. package/dist/core/computer-use/workflow.js.map +1 -0
  285. package/dist/core/cron/jobs.d.ts +26 -8
  286. package/dist/core/cron/jobs.d.ts.map +1 -1
  287. package/dist/core/cron/jobs.js +357 -232
  288. package/dist/core/cron/jobs.js.map +1 -1
  289. package/dist/core/cron/origin-context.d.ts +1 -0
  290. package/dist/core/cron/origin-context.d.ts.map +1 -1
  291. package/dist/core/cron/origin-context.js.map +1 -1
  292. package/dist/core/cron/scheduler.d.ts +22 -17
  293. package/dist/core/cron/scheduler.d.ts.map +1 -1
  294. package/dist/core/cron/scheduler.js +153 -73
  295. package/dist/core/cron/scheduler.js.map +1 -1
  296. package/dist/core/extensions/index.d.ts +1 -1
  297. package/dist/core/extensions/index.d.ts.map +1 -1
  298. package/dist/core/extensions/index.js.map +1 -1
  299. package/dist/core/extensions/loader.d.ts.map +1 -1
  300. package/dist/core/extensions/loader.js +4 -0
  301. package/dist/core/extensions/loader.js.map +1 -1
  302. package/dist/core/extensions/runner.d.ts +1 -0
  303. package/dist/core/extensions/runner.d.ts.map +1 -1
  304. package/dist/core/extensions/runner.js +8 -0
  305. package/dist/core/extensions/runner.js.map +1 -1
  306. package/dist/core/extensions/types.d.ts +8 -0
  307. package/dist/core/extensions/types.d.ts.map +1 -1
  308. package/dist/core/extensions/types.js.map +1 -1
  309. package/dist/core/footer-data-provider.d.ts +4 -0
  310. package/dist/core/footer-data-provider.d.ts.map +1 -1
  311. package/dist/core/footer-data-provider.js +22 -1
  312. package/dist/core/footer-data-provider.js.map +1 -1
  313. package/dist/core/large-subagent-launch.d.ts +1 -1
  314. package/dist/core/large-subagent-launch.d.ts.map +1 -1
  315. package/dist/core/large-subagent-launch.js +7 -7
  316. package/dist/core/large-subagent-launch.js.map +1 -1
  317. package/dist/core/model-registry.d.ts +2 -0
  318. package/dist/core/model-registry.d.ts.map +1 -1
  319. package/dist/core/model-registry.js +5 -0
  320. package/dist/core/model-registry.js.map +1 -1
  321. package/dist/core/model-runtime.d.ts +4 -1
  322. package/dist/core/model-runtime.d.ts.map +1 -1
  323. package/dist/core/model-runtime.js +40 -4
  324. package/dist/core/model-runtime.js.map +1 -1
  325. package/dist/core/package-manager.d.ts.map +1 -1
  326. package/dist/core/package-manager.js.map +1 -1
  327. package/dist/core/permissions.d.ts +4 -4
  328. package/dist/core/permissions.d.ts.map +1 -1
  329. package/dist/core/permissions.js +25 -38
  330. package/dist/core/permissions.js.map +1 -1
  331. package/dist/core/plan-mode.d.ts +5 -13
  332. package/dist/core/plan-mode.d.ts.map +1 -1
  333. package/dist/core/plan-mode.js +135 -116
  334. package/dist/core/plan-mode.js.map +1 -1
  335. package/dist/core/project-trust.d.ts.map +1 -1
  336. package/dist/core/project-trust.js +1 -1
  337. package/dist/core/project-trust.js.map +1 -1
  338. package/dist/core/prompt-resource-bridge.d.ts +12 -0
  339. package/dist/core/prompt-resource-bridge.d.ts.map +1 -0
  340. package/dist/core/prompt-resource-bridge.js +9 -0
  341. package/dist/core/prompt-resource-bridge.js.map +1 -0
  342. package/dist/core/protected-targets.d.ts +6 -0
  343. package/dist/core/protected-targets.d.ts.map +1 -0
  344. package/dist/core/protected-targets.js +45 -0
  345. package/dist/core/protected-targets.js.map +1 -0
  346. package/dist/core/resource-loader.d.ts.map +1 -1
  347. package/dist/core/resource-loader.js +7 -0
  348. package/dist/core/resource-loader.js.map +1 -1
  349. package/dist/core/rollback.d.ts +19 -3
  350. package/dist/core/rollback.d.ts.map +1 -1
  351. package/dist/core/rollback.js +172 -56
  352. package/dist/core/rollback.js.map +1 -1
  353. package/dist/core/runtime-credentials.d.ts +3 -0
  354. package/dist/core/runtime-credentials.d.ts.map +1 -1
  355. package/dist/core/runtime-credentials.js +14 -1
  356. package/dist/core/runtime-credentials.js.map +1 -1
  357. package/dist/core/sdk.d.ts +3 -0
  358. package/dist/core/sdk.d.ts.map +1 -1
  359. package/dist/core/sdk.js +1 -0
  360. package/dist/core/sdk.js.map +1 -1
  361. package/dist/core/session-manager.d.ts +6 -0
  362. package/dist/core/session-manager.d.ts.map +1 -1
  363. package/dist/core/session-manager.js +95 -32
  364. package/dist/core/session-manager.js.map +1 -1
  365. package/dist/core/session-ownership.d.ts.map +1 -1
  366. package/dist/core/session-ownership.js +8 -3
  367. package/dist/core/session-ownership.js.map +1 -1
  368. package/dist/core/session-retention.d.ts.map +1 -1
  369. package/dist/core/session-retention.js +18 -7
  370. package/dist/core/session-retention.js.map +1 -1
  371. package/dist/core/session-startup-settings.d.ts +10 -0
  372. package/dist/core/session-startup-settings.d.ts.map +1 -0
  373. package/dist/core/session-startup-settings.js +70 -0
  374. package/dist/core/session-startup-settings.js.map +1 -0
  375. package/dist/core/settings-manager.d.ts +6 -0
  376. package/dist/core/settings-manager.d.ts.map +1 -1
  377. package/dist/core/settings-manager.js +19 -0
  378. package/dist/core/settings-manager.js.map +1 -1
  379. package/dist/core/subagent-event-transport.d.ts +3 -0
  380. package/dist/core/subagent-event-transport.d.ts.map +1 -0
  381. package/dist/core/subagent-event-transport.js +61 -0
  382. package/dist/core/subagent-event-transport.js.map +1 -0
  383. package/dist/core/subscriptions.d.ts +16 -7
  384. package/dist/core/subscriptions.d.ts.map +1 -1
  385. package/dist/core/subscriptions.js +96 -55
  386. package/dist/core/subscriptions.js.map +1 -1
  387. package/dist/core/trust-manager.d.ts +2 -1
  388. package/dist/core/trust-manager.d.ts.map +1 -1
  389. package/dist/core/trust-manager.js +6 -1
  390. package/dist/core/trust-manager.js.map +1 -1
  391. package/dist/core/update-check.d.ts.map +1 -1
  392. package/dist/core/update-check.js.map +1 -1
  393. package/dist/core/usage-adapters/kimi-coding.d.ts +2 -2
  394. package/dist/core/usage-adapters/kimi-coding.d.ts.map +1 -1
  395. package/dist/core/usage-adapters/kimi-coding.js +2 -2
  396. package/dist/core/usage-adapters/kimi-coding.js.map +1 -1
  397. package/dist/core/usage-adapters/openai-codex.d.ts +3 -2
  398. package/dist/core/usage-adapters/openai-codex.d.ts.map +1 -1
  399. package/dist/core/usage-adapters/openai-codex.js +3 -3
  400. package/dist/core/usage-adapters/openai-codex.js.map +1 -1
  401. package/dist/core/usage-adapters/xai.d.ts +2 -2
  402. package/dist/core/usage-adapters/xai.d.ts.map +1 -1
  403. package/dist/core/usage-adapters/xai.js +3 -3
  404. package/dist/core/usage-adapters/xai.js.map +1 -1
  405. package/dist/core/usage-adapters/zai.d.ts +2 -2
  406. package/dist/core/usage-adapters/zai.d.ts.map +1 -1
  407. package/dist/core/usage-adapters/zai.js +2 -2
  408. package/dist/core/usage-adapters/zai.js.map +1 -1
  409. package/dist/core/usage-service.d.ts +9 -2
  410. package/dist/core/usage-service.d.ts.map +1 -1
  411. package/dist/core/usage-service.js +95 -50
  412. package/dist/core/usage-service.js.map +1 -1
  413. package/dist/gateway/adapters/discord.d.ts.map +1 -1
  414. package/dist/gateway/adapters/discord.js +2 -2
  415. package/dist/gateway/adapters/discord.js.map +1 -1
  416. package/dist/gateway/agent-bridge.d.ts +9 -1
  417. package/dist/gateway/agent-bridge.d.ts.map +1 -1
  418. package/dist/gateway/agent-bridge.js +91 -37
  419. package/dist/gateway/agent-bridge.js.map +1 -1
  420. package/dist/gateway/approval.d.ts.map +1 -1
  421. package/dist/gateway/approval.js +3 -3
  422. package/dist/gateway/approval.js.map +1 -1
  423. package/dist/gateway/authz.d.ts +4 -4
  424. package/dist/gateway/authz.d.ts.map +1 -1
  425. package/dist/gateway/authz.js +8 -12
  426. package/dist/gateway/authz.js.map +1 -1
  427. package/dist/gateway/buttons.d.ts +8 -1
  428. package/dist/gateway/buttons.d.ts.map +1 -1
  429. package/dist/gateway/buttons.js +76 -18
  430. package/dist/gateway/buttons.js.map +1 -1
  431. package/dist/gateway/commands.d.ts +1 -0
  432. package/dist/gateway/commands.d.ts.map +1 -1
  433. package/dist/gateway/commands.js +12 -10
  434. package/dist/gateway/commands.js.map +1 -1
  435. package/dist/gateway/cron.d.ts +12 -11
  436. package/dist/gateway/cron.d.ts.map +1 -1
  437. package/dist/gateway/cron.js +85 -62
  438. package/dist/gateway/cron.js.map +1 -1
  439. package/dist/gateway/index.d.ts.map +1 -1
  440. package/dist/gateway/index.js +5 -4
  441. package/dist/gateway/index.js.map +1 -1
  442. package/dist/gateway/mobile-commands.d.ts +1 -0
  443. package/dist/gateway/mobile-commands.d.ts.map +1 -1
  444. package/dist/gateway/mobile-commands.js +64 -22
  445. package/dist/gateway/mobile-commands.js.map +1 -1
  446. package/dist/gateway/presenter.d.ts +12 -2
  447. package/dist/gateway/presenter.d.ts.map +1 -1
  448. package/dist/gateway/presenter.js +74 -39
  449. package/dist/gateway/presenter.js.map +1 -1
  450. package/dist/gateway/router.d.ts +2 -2
  451. package/dist/gateway/router.d.ts.map +1 -1
  452. package/dist/gateway/router.js +22 -9
  453. package/dist/gateway/router.js.map +1 -1
  454. package/dist/gateway/service.d.ts +2 -0
  455. package/dist/gateway/service.d.ts.map +1 -1
  456. package/dist/gateway/service.js +153 -0
  457. package/dist/gateway/service.js.map +1 -1
  458. package/dist/gateway/setup.d.ts.map +1 -1
  459. package/dist/gateway/setup.js +6 -4
  460. package/dist/gateway/setup.js.map +1 -1
  461. package/dist/main.d.ts.map +1 -1
  462. package/dist/main.js +86 -33
  463. package/dist/main.js.map +1 -1
  464. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  465. package/dist/modes/interactive/components/assistant-message.js +6 -4
  466. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  467. package/dist/modes/interactive/components/copyable-text.d.ts.map +1 -1
  468. package/dist/modes/interactive/components/copyable-text.js +3 -2
  469. package/dist/modes/interactive/components/copyable-text.js.map +1 -1
  470. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  471. package/dist/modes/interactive/components/extension-editor.js +1 -1
  472. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  473. package/dist/modes/interactive/components/settings-selector.d.ts +4 -0
  474. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  475. package/dist/modes/interactive/components/settings-selector.js +20 -0
  476. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  477. package/dist/modes/interactive/interactive-mode.d.ts +2 -2
  478. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  479. package/dist/modes/interactive/interactive-mode.js +104 -109
  480. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  481. package/dist/modes/interactive/smooth-streaming.d.ts.map +1 -1
  482. package/dist/modes/interactive/smooth-streaming.js.map +1 -1
  483. package/dist/modes/print-mode.d.ts.map +1 -1
  484. package/dist/modes/print-mode.js +9 -1
  485. package/dist/modes/print-mode.js.map +1 -1
  486. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  487. package/dist/modes/rpc/rpc-mode.js +2 -1
  488. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  489. package/dist/node-runtime/adapter-247RTYLS.js +16 -0
  490. package/dist/node-runtime/agent-session-services-KDMSPQDY.js +53 -0
  491. package/dist/node-runtime/{brave-UWDA3I22.js → brave-2AFFAXNN.js} +1 -1
  492. package/dist/node-runtime/{builtin-extensions-ZLAX7TUH.js → builtin-extensions-OX6KELNX.js} +9 -7
  493. package/dist/node-runtime/{chunk-QH6YL7VK.js → chunk-252ZVPPF.js} +3 -3
  494. package/dist/node-runtime/{chunk-PCEW76PM.js → chunk-2OIPYKLZ.js} +6 -6
  495. package/dist/node-runtime/{chunk-XBWPBZBN.js → chunk-2SWW7KMW.js} +1 -1
  496. package/dist/node-runtime/{chunk-UQUCFAJ3.js → chunk-2VIW3IMF.js} +40 -7
  497. package/dist/node-runtime/{chunk-UOEDI7RP.js → chunk-4KNKQGHM.js} +2 -2
  498. package/dist/node-runtime/{chunk-FQMM2EZ2.js → chunk-64XTQNLA.js} +29 -7
  499. package/dist/node-runtime/{chunk-NRKGMJYU.js → chunk-6O3IW3IA.js} +2 -2
  500. package/dist/node-runtime/{chunk-5ZQXRPMC.js → chunk-6ZZHGUSA.js} +2 -2
  501. package/dist/node-runtime/chunk-A2WO6WIN.js +1197 -0
  502. package/dist/node-runtime/{chunk-ZXSGTGJ6.js → chunk-APVM5NEU.js} +169 -148
  503. package/dist/node-runtime/{chunk-RC7DMV45.js → chunk-AYHH7YSD.js} +205 -144
  504. package/dist/node-runtime/{chunk-LWYIVUGV.js → chunk-B2IBAXNX.js} +48 -31
  505. package/dist/node-runtime/{chunk-M3LZLQW2.js → chunk-BDRGN3L2.js} +1 -0
  506. package/dist/node-runtime/{chunk-GJ2RDBRL.js → chunk-BYC6RKCN.js} +4 -2
  507. package/dist/node-runtime/{chunk-KWIYUUZK.js → chunk-C3P6Q3B2.js} +469 -283
  508. package/dist/node-runtime/{chunk-YCUZEGJG.js → chunk-DQSCFU7R.js} +17 -8
  509. package/dist/node-runtime/chunk-EBLAZVSA.js +4170 -0
  510. package/dist/node-runtime/{chunk-2KIPT4HJ.js → chunk-EPR2F3GU.js} +68 -20
  511. package/dist/node-runtime/{chunk-CSFI6YXL.js → chunk-ES6PBSIE.js} +1 -1
  512. package/dist/node-runtime/{chunk-IF24A3FH.js → chunk-EVM653DD.js} +4 -4
  513. package/dist/node-runtime/{chunk-HTFRRDSF.js → chunk-F2SMQ6ES.js} +3 -2
  514. package/dist/node-runtime/chunk-F2V2F4ZG.js +114 -0
  515. package/dist/node-runtime/chunk-FE3WZI7F.js +290 -0
  516. package/dist/node-runtime/{chunk-GYIXXQAO.js → chunk-FIRA54AC.js} +2 -5
  517. package/dist/node-runtime/{chunk-6VYZ5UFP.js → chunk-FLGEEH27.js} +1 -1
  518. package/dist/node-runtime/{chunk-5EFIU3OM.js → chunk-FTKEZL27.js} +1 -1
  519. package/dist/node-runtime/chunk-FY4A63RK.js +73 -0
  520. package/dist/node-runtime/chunk-FYIFPFXE.js +98 -0
  521. package/dist/node-runtime/{chunk-HMBGROQK.js → chunk-GLDXBBL6.js} +3 -3
  522. package/dist/node-runtime/{chunk-KWA4OC2J.js → chunk-H6YBCWKG.js} +1 -1
  523. package/dist/node-runtime/{chunk-2MI5OPMN.js → chunk-HNNHHJAB.js} +68 -50
  524. package/dist/node-runtime/{chunk-6XL67VVY.js → chunk-HUD2RFJW.js} +17 -2
  525. package/dist/node-runtime/{chunk-I54LGHWM.js → chunk-I2H5QMWW.js} +3 -3
  526. package/dist/node-runtime/chunk-IAZSK7KK.js +108 -0
  527. package/dist/node-runtime/{chunk-2HHLJWGD.js → chunk-IHHTWSXN.js} +9 -9
  528. package/dist/node-runtime/{chunk-DAPBPCBP.js → chunk-IPP5BIV5.js} +25 -3
  529. package/dist/node-runtime/{chunk-QFWG2RVL.js → chunk-IXG6F4NT.js} +11 -0
  530. package/dist/node-runtime/{chunk-RDDAZOMU.js → chunk-JL4HYQXK.js} +1 -1
  531. package/dist/node-runtime/{chunk-CDVHZOOK.js → chunk-JQYNHAKX.js} +1 -1
  532. package/dist/node-runtime/{chunk-WF7PGWRZ.js → chunk-JWEVT363.js} +37 -28
  533. package/dist/node-runtime/{chunk-2HMIRK7W.js → chunk-LHV3V67B.js} +5 -5
  534. package/dist/node-runtime/{chunk-UAEOTO52.js → chunk-MNQXDFPV.js} +6 -2
  535. package/dist/node-runtime/{chunk-IJIFHRTB.js → chunk-MQPKUHRN.js} +165 -85
  536. package/dist/node-runtime/{chunk-MPKSOJBT.js → chunk-NHATCSOI.js} +4 -7
  537. package/dist/node-runtime/{chunk-RMQZU2YU.js → chunk-NNNR3QIY.js} +1 -1
  538. package/dist/node-runtime/{chunk-RWTKOC3B.js → chunk-QBTJG3UM.js} +1 -1
  539. package/dist/node-runtime/{chunk-E4D3IGP2.js → chunk-QNVMKNNN.js} +20 -17
  540. package/dist/node-runtime/{chunk-GS42T6MX.js → chunk-QW3AAHWN.js} +1 -1
  541. package/dist/node-runtime/{chunk-7S3T6YEA.js → chunk-R4WSB2MQ.js} +5 -5
  542. package/dist/node-runtime/{chunk-B4HP6ZWM.js → chunk-RHCGYWO7.js} +1 -1
  543. package/dist/node-runtime/{chunk-3XPLBUEM.js → chunk-RLGY2QIL.js} +49 -59
  544. package/dist/node-runtime/{chunk-KKUTQQV7.js → chunk-RYRGL7P4.js} +1 -1
  545. package/dist/node-runtime/{chunk-AD6VXAO7.js → chunk-TCT6PCUR.js} +47 -115
  546. package/dist/node-runtime/chunk-U5F4IC7R.js +53 -0
  547. package/dist/node-runtime/{chunk-OCVIXVTZ.js → chunk-UBWW2PID.js} +13 -2
  548. package/dist/node-runtime/{chunk-HFVZUPFJ.js → chunk-UNUZICC4.js} +3 -3
  549. package/dist/node-runtime/{chunk-LQP3RB67.js → chunk-UYRKUKFE.js} +1 -1
  550. package/dist/node-runtime/{chunk-DSIKTUPW.js → chunk-VSUEAI7O.js} +2993 -3820
  551. package/dist/node-runtime/{chunk-NAY6UUQZ.js → chunk-WDIVLMOH.js} +1 -1
  552. package/dist/node-runtime/{chunk-E24EO34J.js → chunk-WPXUR3HE.js} +1 -1
  553. package/dist/node-runtime/{chunk-SLVOAQOY.js → chunk-WXYNALHD.js} +113 -1
  554. package/dist/node-runtime/{chunk-KJ6DWS3K.js → chunk-XEOCUEUJ.js} +121 -60
  555. package/dist/node-runtime/chunk-YD3IZAZ7.js +47 -0
  556. package/dist/node-runtime/chunk-YSB6URMT.js +41 -0
  557. package/dist/node-runtime/{chunk-VQFSUJJG.js → chunk-ZDJUR45D.js} +430 -129
  558. package/dist/node-runtime/{chunk-YDO2F2IA.js → chunk-ZEVFBFNF.js} +95 -142
  559. package/dist/node-runtime/chunk-ZIQUS5QO.js +180 -0
  560. package/dist/node-runtime/cli-runtime.js +4 -4
  561. package/dist/node-runtime/{command-6WDOYKS6.js → command-3HMSY6W2.js} +5 -5
  562. package/dist/node-runtime/{commands-NZLSOHPE.js → commands-D6A6H4TS.js} +11 -16
  563. package/dist/node-runtime/commands-OCSSEEWJ.js +37 -0
  564. package/dist/node-runtime/{config-QDD5PUE2.js → config-CU7M7BTK.js} +1 -1
  565. package/dist/node-runtime/{config-D4QUPAHX.js → config-DS7XLMEM.js} +2 -2
  566. package/dist/node-runtime/{direct-tool-executor-77GW4F5P.js → direct-tool-executor-2QJXVQD2.js} +8 -10
  567. package/dist/node-runtime/{exa-LL64QZR2.js → exa-6VBZNPGE.js} +1 -1
  568. package/dist/node-runtime/{export-html-VM4J2MNR.js → export-html-BRWF5AHI.js} +4 -4
  569. package/dist/node-runtime/{extension-input-E7BJZ35H.js → extension-input-VBBMX7YV.js} +5 -5
  570. package/dist/node-runtime/{extension-selector-WU42XDTU.js → extension-selector-T5AOG5XB.js} +5 -5
  571. package/dist/node-runtime/{extract-EQJE7FZZ.js → extract-BFX2N35A.js} +3 -3
  572. package/dist/node-runtime/{gateway-INAYBF4Q.js → gateway-6623C2U3.js} +61 -53
  573. package/dist/node-runtime/{gemini-search-35OCRFLN.js → gemini-search-C66QHDT6.js} +2 -2
  574. package/dist/node-runtime/{gemini-url-context-UQY3PHHW.js → gemini-url-context-T7DEK73R.js} +3 -3
  575. package/dist/node-runtime/{github-extract-F64RZOEA.js → github-extract-HOGE3D6F.js} +107 -38
  576. package/dist/node-runtime/{goal-NJEE5CTU.js → goal-AIHEDUYA.js} +5 -5
  577. package/dist/node-runtime/index.js +54 -48
  578. package/dist/node-runtime/{init-HS6NQXSK.js → init-JBQJIPYI.js} +3 -4
  579. package/dist/node-runtime/{install-cli-KL4UDDAD.js → install-cli-INGHIVBY.js} +30 -25
  580. package/dist/node-runtime/interactive-mode-FM5DNUSW.js +74 -0
  581. package/dist/node-runtime/{interactive-view-NGFAMHPW.js → interactive-view-K33PM4OS.js} +8 -8
  582. package/dist/node-runtime/{lsp-manager-3ASUSO4T.js → lsp-manager-S554V5HR.js} +240 -155
  583. package/dist/node-runtime/{lunr-browser-IR7TL3J7.js → lunr-browser-H4FDOR3C.js} +7 -7
  584. package/dist/node-runtime/lunr-computer-use-VDP2QNRQ.js +158 -0
  585. package/dist/node-runtime/{lunr-cron-LWIZJM2N.js → lunr-cron-LKVA5CZZ.js} +94 -79
  586. package/dist/node-runtime/main.js +42 -39
  587. package/dist/node-runtime/{mobile-commands-J7OJEKFO.js → mobile-commands-XT3NQXLD.js} +13 -12
  588. package/dist/node-runtime/{model-runtime-OO5HFLWE.js → model-runtime-MZ2JMDV4.js} +4 -4
  589. package/dist/node-runtime/{openai-search-B3MSNCQA.js → openai-search-K72LJDYN.js} +1 -1
  590. package/dist/node-runtime/{package-manager-cli-VYZM5WHN.js → package-manager-cli-CYM262SI.js} +20 -19
  591. package/dist/node-runtime/{parallel-AI2MNSCU.js → parallel-X724NCNU.js} +1 -1
  592. package/dist/node-runtime/{perplexity-QVJW3I35.js → perplexity-RDHQHVMK.js} +2 -2
  593. package/dist/node-runtime/{pi-intercom-QM53RLTS.js → pi-intercom-NHHEPBMI.js} +2 -2
  594. package/dist/node-runtime/{pi-mcp-adapter-5UKBZP44.js → pi-mcp-adapter-PP3GQJQ2.js} +98 -94
  595. package/dist/node-runtime/{pi-ollama-cloud-2F3Y2QNM.js → pi-ollama-cloud-4MNWIMFN.js} +11 -24
  596. package/dist/node-runtime/{pi-prompt-template-model-QWTCGLJU.js → pi-prompt-template-model-FTGELLS5.js} +32 -36
  597. package/dist/node-runtime/{pi-subagents-DNY7EAIG.js → pi-subagents-LT3CTO4E.js} +114 -116
  598. package/dist/node-runtime/{pi-web-access-WTGMNZ32.js → pi-web-access-Z3LP246C.js} +144 -108
  599. package/dist/node-runtime/{print-mode-XSFM26EL.js → print-mode-2XHN5SUA.js} +4 -3
  600. package/dist/node-runtime/{process-registry-4FAKZQOL.js → process-registry-Q4QWIU33.js} +3 -3
  601. package/dist/node-runtime/{proxy-modes-LGEUYD7R.js → proxy-modes-VEGC6QG5.js} +9 -13
  602. package/dist/node-runtime/rpc-entry.js +42 -39
  603. package/dist/node-runtime/{rpc-mode-O3XV4SID.js → rpc-mode-32DHJPEJ.js} +4 -4
  604. package/dist/node-runtime/{runtime-4VA6JKNF.js → runtime-EDUUGAQD.js} +56 -46
  605. package/dist/node-runtime/runtime-UGXM4NPQ.js +21 -0
  606. package/dist/node-runtime/{runtime-bridges-VRDDR73P.js → runtime-bridges-DNKMNJCF.js} +8 -7
  607. package/dist/node-runtime/{service-MDQIPQ26.js → service-IHLGFLBT.js} +6 -4
  608. package/dist/node-runtime/{session-handoff-GWARVEWT.js → session-handoff-LNSPDTKY.js} +4 -4
  609. package/dist/node-runtime/{session-manager-JB5J6XBN.js → session-manager-YIFIAVGC.js} +3 -3
  610. package/dist/node-runtime/{session-selector-GIM4DHQ2.js → session-selector-I73U3EIO.js} +7 -7
  611. package/dist/node-runtime/{settings-manager-J4SPQPDJ.js → settings-manager-P2TKGYTR.js} +6 -5
  612. package/dist/node-runtime/{setup-JBKOLJ6F.js → setup-H7CCELIL.js} +15 -1
  613. package/dist/node-runtime/{setup-CQ2NLVK5.js → setup-OLQ4GXWS.js} +22 -21
  614. package/dist/node-runtime/{src-IVPNZ2BX.js → src-6RV4PXKJ.js} +64 -36
  615. package/dist/node-runtime/{subagent-executor-KZZQGAKV.js → subagent-executor-Q5F7DBVM.js} +141 -62
  616. package/dist/node-runtime/{tavily-WEZFVFP7.js → tavily-KZBFYQB6.js} +1 -1
  617. package/dist/node-runtime/{theme-UOO5RVFN.js → theme-YLVZKKZD.js} +2 -2
  618. package/dist/node-runtime/{trust-manager-MMISNCYO.js → trust-manager-ZCU76D23.js} +2 -2
  619. package/dist/node-runtime/{update-cli-GTFJC7XK.js → update-cli-F3XXJANQ.js} +7 -6
  620. package/dist/node-runtime/{usage-service-LFB2SWYV.js → usage-service-CPAGBED5.js} +1 -1
  621. package/dist/node-runtime/{video-extract-OOZHR7SU.js → video-extract-UPKYIBW3.js} +3 -3
  622. package/dist/node-runtime/workflow-ICDI4N3J.js +767 -0
  623. package/dist/node-runtime/{youtube-extract-FNPAUNHU.js → youtube-extract-77NT7FWH.js} +4 -4
  624. package/dist/utils/clipboard-image.d.ts.map +1 -1
  625. package/dist/utils/clipboard-image.js.map +1 -1
  626. package/dist/utils/exif-orientation.d.ts.map +1 -1
  627. package/dist/utils/exif-orientation.js +25 -10
  628. package/dist/utils/exif-orientation.js.map +1 -1
  629. package/dist/utils/image-resize-core.d.ts +2 -0
  630. package/dist/utils/image-resize-core.d.ts.map +1 -1
  631. package/dist/utils/image-resize-core.js +12 -0
  632. package/dist/utils/image-resize-core.js.map +1 -1
  633. package/dist/utils/paths.d.ts +4 -0
  634. package/dist/utils/paths.d.ts.map +1 -1
  635. package/dist/utils/paths.js +33 -2
  636. package/dist/utils/paths.js.map +1 -1
  637. package/dist/utils/sanitize-terminal-text.d.ts +7 -0
  638. package/dist/utils/sanitize-terminal-text.d.ts.map +1 -0
  639. package/dist/utils/sanitize-terminal-text.js +67 -0
  640. package/dist/utils/sanitize-terminal-text.js.map +1 -0
  641. package/docs/browser.md +1 -1
  642. package/docs/computer-use.md +362 -0
  643. package/docs/custom-provider.md +3 -3
  644. package/docs/extensions.md +8 -4
  645. package/docs/features.md +22 -12
  646. package/docs/keybindings.md +1 -1
  647. package/docs/models.md +5 -5
  648. package/docs/packages.md +1 -1
  649. package/docs/providers.md +2 -2
  650. package/docs/rpc.md +7 -7
  651. package/docs/security.md +10 -5
  652. package/docs/sessions.md +2 -2
  653. package/docs/skills.md +1 -1
  654. package/docs/terminal-setup.md +7 -7
  655. package/docs/themes.md +1 -1
  656. package/docs/usage.md +1 -1
  657. package/examples/extensions/auto-commit-on-exit.ts +1 -1
  658. package/examples/extensions/bash-spawn-hook.ts +1 -1
  659. package/examples/extensions/built-in-tool-renderer.ts +1 -1
  660. package/examples/extensions/claude-rules.ts +1 -1
  661. package/examples/extensions/commands.ts +1 -1
  662. package/examples/extensions/custom-compaction.ts +1 -1
  663. package/examples/extensions/custom-provider-anthropic/index.ts +3 -3
  664. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  665. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  666. package/examples/extensions/custom-provider-gitlab-duo/index.ts +1 -1
  667. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  668. package/examples/extensions/doom-overlay/index.ts +1 -1
  669. package/examples/extensions/git-merge-and-resolve.ts +2 -2
  670. package/examples/extensions/gondolin/index.ts +1 -1
  671. package/examples/extensions/gondolin/package-lock.json +2 -2
  672. package/examples/extensions/gondolin/package.json +1 -1
  673. package/examples/extensions/hidden-thinking-label.ts +1 -1
  674. package/examples/extensions/inline-bash.ts +2 -2
  675. package/examples/extensions/input-transform-streaming.ts +2 -2
  676. package/examples/extensions/input-transform.ts +2 -2
  677. package/examples/extensions/interactive-shell.ts +1 -1
  678. package/examples/extensions/kimi-deferred-tools.ts +1 -1
  679. package/examples/extensions/mac-system-theme.ts +1 -1
  680. package/examples/extensions/minimal-mode.ts +1 -1
  681. package/examples/extensions/modal-editor.ts +1 -1
  682. package/examples/extensions/model-status.ts +1 -1
  683. package/examples/extensions/notify.ts +3 -3
  684. package/examples/extensions/overlay-qa-tests.ts +1 -1
  685. package/examples/extensions/overlay-test.ts +1 -1
  686. package/examples/extensions/pirate.ts +1 -1
  687. package/examples/extensions/preset.ts +3 -3
  688. package/examples/extensions/project-trust.ts +4 -4
  689. package/examples/extensions/prompt-customizer.ts +1 -1
  690. package/examples/extensions/rainbow-editor.ts +1 -1
  691. package/examples/extensions/rpc-demo.ts +1 -1
  692. package/examples/extensions/sandbox/index.ts +7 -7
  693. package/examples/extensions/sandbox/package-lock.json +2 -2
  694. package/examples/extensions/sandbox/package.json +1 -1
  695. package/examples/extensions/shutdown-command.ts +5 -5
  696. package/examples/extensions/ssh.ts +2 -2
  697. package/examples/extensions/subagent/README.md +3 -3
  698. package/examples/extensions/subagent/index.ts +2 -2
  699. package/examples/extensions/titlebar-spinner.ts +1 -1
  700. package/examples/extensions/tool-override.ts +1 -1
  701. package/examples/extensions/tools.ts +1 -1
  702. package/examples/extensions/with-deps/package-lock.json +2 -2
  703. package/examples/extensions/with-deps/package.json +1 -1
  704. package/examples/extensions/working-indicator.ts +1 -1
  705. package/examples/extensions/working-message-test.ts +1 -1
  706. package/examples/sdk/01-minimal.ts +1 -1
  707. package/examples/sdk/03-custom-prompt.ts +1 -1
  708. package/examples/sdk/04-skills.ts +1 -1
  709. package/examples/sdk/06-extensions.ts +2 -2
  710. package/examples/sdk/08-prompt-templates.ts +1 -1
  711. package/install-lock/package-lock.json +3841 -0
  712. package/install-lock/package.json +18 -0
  713. package/native/computer-use/LICENSE.md +21 -0
  714. package/npm-shrinkwrap.json +3858 -0
  715. package/package.json +15 -9
  716. package/dist/modes/interactive/components/exit-card.d.ts +0 -22
  717. package/dist/modes/interactive/components/exit-card.d.ts.map +0 -1
  718. package/dist/modes/interactive/components/exit-card.js +0 -69
  719. package/dist/modes/interactive/components/exit-card.js.map +0 -1
  720. package/dist/node-runtime/agent-session-services-YXXFCP5J.js +0 -49
  721. package/dist/node-runtime/chunk-4HXHDDCF.js +0 -16
  722. package/dist/node-runtime/chunk-4TLO4PO2.js +0 -3427
  723. package/dist/node-runtime/chunk-AUXVGBO4.js +0 -19
  724. package/dist/node-runtime/chunk-OVX6X377.js +0 -50
  725. package/dist/node-runtime/chunk-QTU6AKNJ.js +0 -773
  726. package/dist/node-runtime/chunk-TORR5XBV.js +0 -356
  727. package/dist/node-runtime/commands-QC6VUZJQ.js +0 -36
  728. package/dist/node-runtime/interactive-mode-Z5M7WYYZ.js +0 -72
  729. package/dist/node-runtime/mcp-auth-flow-QIXHW6QN.js +0 -32
@@ -1 +1 @@
1
- {"version":3,"file":"approval.d.ts","sourceRoot":"","sources":["../../src/gateway/approval.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EACN,KAAK,eAAe,EACpB,KAAK,gBAAgB,EAIrB,MAAM,wBAAwB,CAAC;AAMhC,OAAO,KAAK,EAAc,aAAa,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAO5F,UAAU,eAAe;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,eAAe,CAAC;IACzB,MAAM,EAAE,aAAa,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AA6CD,+EAA+E;AAC/E,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,GAAG,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAEhG;AAmED,mDAAmD;AACnD,wBAAsB,4BAA4B,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAMlG;AAED,2EAA2E;AAC3E,wBAAgB,8BAA8B,IAAI,IAAI,CAErD;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CA+C9G;AAED,sEAAsE;AACtE,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CASxD;AAED,wBAAgB,qBAAqB,IAAI,IAAI,CAU5C;AAED,yDAAyD;AACzD,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C","sourcesContent":["/**\n * Gateway approvals for large subagent launches and plans.\n * A missing chat context rejects large launches; plans pass through.\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { randomBytes } from \"node:crypto\";\nimport {\n\ttype ApprovalRequest,\n\ttype ApprovalResponse,\n\tNO_HANDLER_REASON,\n\tregisterApprovalHandler,\n\tsetPermissionMode,\n} from \"../core/permissions.ts\";\nimport { isAuthorized, isGatewayOwner } from \"./authz.ts\";\nimport { loadGatewayConfig } from \"./config.ts\";\nimport { conversationBinding } from \"./conversations.ts\";\nimport { createPairingStore } from \"./pairing.ts\";\nimport { getSession } from \"./store.ts\";\nimport type { ButtonSpec, CallbackEvent, PlatformAdapter, SessionSource } from \"./types.ts\";\n\nconst CALLBACK_PREFIX = \"ap:\";\nconst ID_LENGTH = 8;\nconst CODE_ALPHABET = \"ABCDEFGHJKMNPQRSTUVWXYZ23456789\";\nconst DEFAULT_TIMEOUT_MS = 5 * 60 * 1000;\n\ninterface ApprovalContext {\n\tkey: string;\n\tadapter: PlatformAdapter;\n\tsource: SessionSource;\n\ttimeoutMs?: number;\n}\n\ninterface PendingApproval {\n\tkey?: string;\n\tsource: SessionSource;\n\tplan: boolean;\n\tresolve: (value: ApprovalResponse) => void;\n\treject: (reason?: unknown) => void;\n\ttimeout: ReturnType<typeof setTimeout>;\n\tmessageId?: string;\n\tadapter: PlatformAdapter;\n\tchatId: string;\n\tthreadId?: string;\n\tuserId: string;\n}\n\nconst approvalContext = new AsyncLocalStorage<ApprovalContext>();\nconst pending = new Map<string, PendingApproval>();\n\nfunction generateId(): string {\n\tlet id = \"\";\n\tconst bytes = randomBytes(ID_LENGTH);\n\tfor (let i = 0; i < ID_LENGTH; i++) {\n\t\tid += CODE_ALPHABET[bytes[i] % CODE_ALPHABET.length];\n\t}\n\treturn id;\n}\n\nfunction makeCallbackData(id: string, action: ApprovalResponse | \"cancel\"): string {\n\treturn `${CALLBACK_PREFIX}${id}:${action}`;\n}\n\nfunction parseCallbackData(data: string): { id: string; action: ApprovalResponse | \"cancel\" } | null {\n\tif (!data.startsWith(CALLBACK_PREFIX)) return null;\n\tconst rest = data.slice(CALLBACK_PREFIX.length);\n\tconst colon = rest.indexOf(\":\");\n\tif (colon === -1) return null;\n\tconst id = rest.slice(0, colon);\n\tconst action = rest.slice(colon + 1);\n\tif (action === \"once\" || action === \"session\" || action === \"reject\" || action === \"cancel\") {\n\t\treturn { id, action };\n\t}\n\treturn null;\n}\n\n/** Run code with the gateway approval context available to tool-call gates. */\nexport function runWithApprovalContext<T>(ctx: ApprovalContext, fn: () => Promise<T>): Promise<T> {\n\treturn approvalContext.run(ctx, fn);\n}\n\nasync function createApprovalPrompt(\n\tadapter: PlatformAdapter,\n\tsource: SessionSource,\n\treq: ApprovalRequest,\n\ttimeoutMs = DEFAULT_TIMEOUT_MS,\n\tkey?: string,\n): Promise<ApprovalResponse> {\n\tconst id = generateId();\n\t// Large child launches get a dedicated title; inline buttons cannot capture feedback.\n\tconst title =\n\t\treq.kind === \"large-subagent-launch\"\n\t\t\t? `Approve large subagent launch?\\n${req.detail}`\n\t\t\t: `Approve ${req.toolName}?\\n${req.detail}`;\n\tconst rows: ButtonSpec[][] = [\n\t\t[\n\t\t\t{ label: \"✓ Approve once\", data: makeCallbackData(id, \"once\") },\n\t\t\t{ label: \"✓ Approve session\", data: makeCallbackData(id, \"session\") },\n\t\t],\n\t\t[{ label: \"✗ Reject\", data: makeCallbackData(id, \"reject\") }],\n\t];\n\n\treturn new Promise<ApprovalResponse>((resolve, reject) => {\n\t\tlet messageId: string | undefined;\n\t\tconst timeout = setTimeout(() => {\n\t\t\tpending.delete(id);\n\t\t\treject(new Error(\"Approval request timed out\"));\n\t\t\tif (messageId) {\n\t\t\t\tvoid adapter.editMessage(source.chatId, messageId, \"⏱ Approval expired.\", []).catch(() => {});\n\t\t\t}\n\t\t}, timeoutMs);\n\n\t\tconst entry: PendingApproval = {\n\t\t\tkey,\n\t\t\tsource,\n\t\t\tplan: req.kind === \"plan\",\n\t\t\tresolve,\n\t\t\treject,\n\t\t\ttimeout,\n\t\t\tadapter,\n\t\t\tchatId: source.chatId,\n\t\t\tthreadId: source.threadId,\n\t\t\tuserId: source.userId,\n\t\t};\n\t\tpending.set(id, entry);\n\t\tvoid (async () => {\n\t\t\ttry {\n\t\t\t\tconst result = await adapter.sendButtons(source.chatId, title, rows, { threadId: source.threadId });\n\t\t\t\tif (!result.success || !result.messageId) throw new Error(result.error ?? \"Failed to send approval prompt\");\n\t\t\t\tmessageId = result.messageId;\n\t\t\t\tif (pending.get(id) !== entry) {\n\t\t\t\t\tawait adapter\n\t\t\t\t\t\t.editMessage(source.chatId, messageId, \"Approval cancelled or expired.\", [])\n\t\t\t\t\t\t.catch(() => {});\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tentry.messageId = messageId;\n\t\t\t} catch (err) {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tpending.delete(id);\n\t\t\t\treject(err);\n\t\t\t}\n\t\t})();\n\t});\n}\n\n/** Approval handler passed to core permissions. */\nexport async function createGatewayApprovalRequest(req: ApprovalRequest): Promise<ApprovalResponse> {\n\tconst ctx = approvalContext.getStore();\n\tif (!ctx) {\n\t\tthrow new Error(NO_HANDLER_REASON);\n\t}\n\treturn createApprovalPrompt(ctx.adapter, ctx.source, req, ctx.timeoutMs, ctx.key);\n}\n\n/** Register the gateway approval handler with the core permission gate. */\nexport function registerGatewayApprovalHandler(): void {\n\tregisterApprovalHandler(createGatewayApprovalRequest);\n}\n\n/**\n * Dispatch a callback event to an outstanding approval prompt.\n * Returns true when the data matched the approval prefix (whether or not a\n * pending prompt existed).\n */\nexport async function handleApprovalCallback(event: CallbackEvent, adapter?: PlatformAdapter): Promise<boolean> {\n\tconst parsed = parseCallbackData(event.data);\n\tif (!parsed) return false;\n\n\tconst entry = pending.get(parsed.id);\n\tif (!entry) {\n\t\t// Best-effort answer for stale callbacks.\n\t\tawait adapter?.answerCallback(event.id, \"Approval expired.\").catch(() => {});\n\t\treturn true;\n\t}\n\n\tif (\n\t\tevent.userId !== entry.userId ||\n\t\tevent.chatId !== entry.chatId ||\n\t\tevent.threadId !== entry.threadId ||\n\t\t(adapter && adapter.platform !== entry.adapter.platform)\n\t) {\n\t\tawait entry.adapter.answerCallback(event.id, \"⛔ Not your approval.\").catch(() => {});\n\t\treturn true;\n\t}\n\n\tclearTimeout(entry.timeout);\n\tpending.delete(parsed.id);\n\n\tconst response: ApprovalResponse =\n\t\tparsed.action === \"reject\" || parsed.action === \"cancel\" ? \"reject\" : parsed.action;\n\tconst binding = entry.key ? conversationBinding(entry.key) : undefined;\n\tif (binding?.owner && (!isGatewayOwner(entry.source, loadGatewayConfig()) || binding.owner !== event.userId)) {\n\t\tentry.resolve(\"reject\");\n\t\tawait entry.adapter.answerCallback(event.id, \"Owner access was removed.\").catch(() => {});\n\t\treturn true;\n\t}\n\tif (binding && !isAuthorized(entry.source, loadGatewayConfig(), createPairingStore())) {\n\t\tentry.resolve(\"reject\");\n\t\treturn true;\n\t}\n\tif (entry.plan && response !== \"reject\" && entry.key) {\n\t\tconst sessionId = getSession(entry.key)?.sessionId;\n\t\tif (sessionId) setPermissionMode(\"yolo\", sessionId);\n\t}\n\tentry.resolve(response);\n\n\tconst label =\n\t\tparsed.action === \"reject\" || parsed.action === \"cancel\" ? \"Rejected.\" : `Approved (${parsed.action}).`;\n\tawait entry.adapter.editMessage(entry.chatId, entry.messageId ?? \"\", label, []).catch(() => {});\n\tawait entry.adapter.answerCallback(event.id).catch(() => {});\n\treturn true;\n}\n\n/** Test hook: clear all pending approvals and stop their timeouts. */\nexport function cancelGatewayApprovals(key: string): void {\n\tfor (const [id, entry] of pending) {\n\t\tif (entry.key !== key) continue;\n\t\tclearTimeout(entry.timeout);\n\t\tpending.delete(id);\n\t\tentry.resolve(\"reject\");\n\t\tif (entry.messageId)\n\t\t\tvoid entry.adapter.editMessage(entry.chatId, entry.messageId, \"Approval cancelled.\", []).catch(() => {});\n\t}\n}\n\nexport function resetApprovalRegistry(): void {\n\tfor (const entry of pending.values()) {\n\t\tclearTimeout(entry.timeout);\n\t\ttry {\n\t\t\tentry.reject(new Error(\"Approval registry reset\"));\n\t\t} catch {\n\t\t\t// ignore settled promise\n\t\t}\n\t}\n\tpending.clear();\n}\n\n/** Test hook: number of outstanding approval prompts. */\nexport function activeApprovalCount(): number {\n\treturn pending.size;\n}\n"]}
1
+ {"version":3,"file":"approval.d.ts","sourceRoot":"","sources":["../../src/gateway/approval.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EACN,KAAK,eAAe,EACpB,KAAK,gBAAgB,EAIrB,MAAM,wBAAwB,CAAC;AAMhC,OAAO,KAAK,EAAc,aAAa,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAO5F,UAAU,eAAe;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,eAAe,CAAC;IACzB,MAAM,EAAE,aAAa,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AA6CD,+EAA+E;AAC/E,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,GAAG,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAEhG;AAmED,mDAAmD;AACnD,wBAAsB,4BAA4B,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAMlG;AAED,2EAA2E;AAC3E,wBAAgB,8BAA8B,IAAI,IAAI,CAErD;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CA+C9G;AAED,sEAAsE;AACtE,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CASxD;AAED,wBAAgB,qBAAqB,IAAI,IAAI,CAU5C;AAED,yDAAyD;AACzD,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C","sourcesContent":["/**\n * Gateway approvals for large subagent launches and plans.\n * A missing chat context rejects large launches; plans pass through.\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { randomBytes } from \"node:crypto\";\nimport {\n\ttype ApprovalRequest,\n\ttype ApprovalResponse,\n\tNO_HANDLER_REASON,\n\tregisterApprovalHandler,\n\tsetPermissionMode,\n} from \"../core/permissions.ts\";\nimport { isAuthorized } from \"./authz.ts\";\nimport { loadGatewayConfig } from \"./config.ts\";\nimport { conversationBinding } from \"./conversations.ts\";\nimport { createPairingStore } from \"./pairing.ts\";\nimport { getSession } from \"./store.ts\";\nimport type { ButtonSpec, CallbackEvent, PlatformAdapter, SessionSource } from \"./types.ts\";\n\nconst CALLBACK_PREFIX = \"ap:\";\nconst ID_LENGTH = 8;\nconst CODE_ALPHABET = \"ABCDEFGHJKMNPQRSTUVWXYZ23456789\";\nconst DEFAULT_TIMEOUT_MS = 5 * 60 * 1000;\n\ninterface ApprovalContext {\n\tkey: string;\n\tadapter: PlatformAdapter;\n\tsource: SessionSource;\n\ttimeoutMs?: number;\n}\n\ninterface PendingApproval {\n\tkey?: string;\n\tsource: SessionSource;\n\tplan: boolean;\n\tresolve: (value: ApprovalResponse) => void;\n\treject: (reason?: unknown) => void;\n\ttimeout: ReturnType<typeof setTimeout>;\n\tmessageId?: string;\n\tadapter: PlatformAdapter;\n\tchatId: string;\n\tthreadId?: string;\n\tuserId: string;\n}\n\nconst approvalContext = new AsyncLocalStorage<ApprovalContext>();\nconst pending = new Map<string, PendingApproval>();\n\nfunction generateId(): string {\n\tlet id = \"\";\n\tconst bytes = randomBytes(ID_LENGTH);\n\tfor (let i = 0; i < ID_LENGTH; i++) {\n\t\tid += CODE_ALPHABET[bytes[i] % CODE_ALPHABET.length];\n\t}\n\treturn id;\n}\n\nfunction makeCallbackData(id: string, action: ApprovalResponse | \"cancel\"): string {\n\treturn `${CALLBACK_PREFIX}${id}:${action}`;\n}\n\nfunction parseCallbackData(data: string): { id: string; action: ApprovalResponse | \"cancel\" } | null {\n\tif (!data.startsWith(CALLBACK_PREFIX)) return null;\n\tconst rest = data.slice(CALLBACK_PREFIX.length);\n\tconst colon = rest.indexOf(\":\");\n\tif (colon === -1) return null;\n\tconst id = rest.slice(0, colon);\n\tconst action = rest.slice(colon + 1);\n\tif (action === \"once\" || action === \"session\" || action === \"reject\" || action === \"cancel\") {\n\t\treturn { id, action };\n\t}\n\treturn null;\n}\n\n/** Run code with the gateway approval context available to tool-call gates. */\nexport function runWithApprovalContext<T>(ctx: ApprovalContext, fn: () => Promise<T>): Promise<T> {\n\treturn approvalContext.run(ctx, fn);\n}\n\nasync function createApprovalPrompt(\n\tadapter: PlatformAdapter,\n\tsource: SessionSource,\n\treq: ApprovalRequest,\n\ttimeoutMs = DEFAULT_TIMEOUT_MS,\n\tkey?: string,\n): Promise<ApprovalResponse> {\n\tconst id = generateId();\n\t// Large child launches get a dedicated title; inline buttons cannot capture feedback.\n\tconst title =\n\t\treq.kind === \"large-subagent-launch\"\n\t\t\t? `Approve large subagent launch?\\n${req.detail}`\n\t\t\t: `Approve ${req.toolName}?\\n${req.detail}`;\n\tconst rows: ButtonSpec[][] = [\n\t\t[\n\t\t\t{ label: \"✓ Approve once\", data: makeCallbackData(id, \"once\") },\n\t\t\t{ label: \"✓ Approve session\", data: makeCallbackData(id, \"session\") },\n\t\t],\n\t\t[{ label: \"✗ Reject\", data: makeCallbackData(id, \"reject\") }],\n\t];\n\n\treturn new Promise<ApprovalResponse>((resolve, reject) => {\n\t\tlet messageId: string | undefined;\n\t\tconst timeout = setTimeout(() => {\n\t\t\tpending.delete(id);\n\t\t\treject(new Error(\"Approval request timed out\"));\n\t\t\tif (messageId) {\n\t\t\t\tvoid adapter.editMessage(source.chatId, messageId, \"⏱ Approval expired.\", []).catch(() => {});\n\t\t\t}\n\t\t}, timeoutMs);\n\n\t\tconst entry: PendingApproval = {\n\t\t\tkey,\n\t\t\tsource,\n\t\t\tplan: req.kind === \"plan\",\n\t\t\tresolve,\n\t\t\treject,\n\t\t\ttimeout,\n\t\t\tadapter,\n\t\t\tchatId: source.chatId,\n\t\t\tthreadId: source.threadId,\n\t\t\tuserId: source.userId,\n\t\t};\n\t\tpending.set(id, entry);\n\t\tvoid (async () => {\n\t\t\ttry {\n\t\t\t\tconst result = await adapter.sendButtons(source.chatId, title, rows, { threadId: source.threadId });\n\t\t\t\tif (!result.success || !result.messageId) throw new Error(result.error ?? \"Failed to send approval prompt\");\n\t\t\t\tmessageId = result.messageId;\n\t\t\t\tif (pending.get(id) !== entry) {\n\t\t\t\t\tawait adapter\n\t\t\t\t\t\t.editMessage(source.chatId, messageId, \"Approval cancelled or expired.\", [])\n\t\t\t\t\t\t.catch(() => {});\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tentry.messageId = messageId;\n\t\t\t} catch (err) {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tpending.delete(id);\n\t\t\t\treject(err);\n\t\t\t}\n\t\t})();\n\t});\n}\n\n/** Approval handler passed to core permissions. */\nexport async function createGatewayApprovalRequest(req: ApprovalRequest): Promise<ApprovalResponse> {\n\tconst ctx = approvalContext.getStore();\n\tif (!ctx) {\n\t\tthrow new Error(NO_HANDLER_REASON);\n\t}\n\treturn createApprovalPrompt(ctx.adapter, ctx.source, req, ctx.timeoutMs, ctx.key);\n}\n\n/** Register the gateway approval handler with the core permission gate. */\nexport function registerGatewayApprovalHandler(): void {\n\tregisterApprovalHandler(createGatewayApprovalRequest);\n}\n\n/**\n * Dispatch a callback event to an outstanding approval prompt.\n * Returns true when the data matched the approval prefix (whether or not a\n * pending prompt existed).\n */\nexport async function handleApprovalCallback(event: CallbackEvent, adapter?: PlatformAdapter): Promise<boolean> {\n\tconst parsed = parseCallbackData(event.data);\n\tif (!parsed) return false;\n\n\tconst entry = pending.get(parsed.id);\n\tif (!entry) {\n\t\t// Best-effort answer for stale callbacks.\n\t\tawait adapter?.answerCallback(event.id, \"Approval expired.\").catch(() => {});\n\t\treturn true;\n\t}\n\n\tif (\n\t\tevent.userId !== entry.userId ||\n\t\tevent.chatId !== entry.chatId ||\n\t\tevent.threadId !== entry.threadId ||\n\t\t(adapter && adapter.platform !== entry.adapter.platform)\n\t) {\n\t\tawait entry.adapter.answerCallback(event.id, \"⛔ Not your approval.\").catch(() => {});\n\t\treturn true;\n\t}\n\n\tclearTimeout(entry.timeout);\n\tpending.delete(parsed.id);\n\n\tconst response: ApprovalResponse =\n\t\tparsed.action === \"reject\" || parsed.action === \"cancel\" ? \"reject\" : parsed.action;\n\tconst binding = entry.key ? conversationBinding(entry.key) : undefined;\n\tif (binding?.owner && binding.owner !== event.userId) {\n\t\tentry.resolve(\"reject\");\n\t\tawait entry.adapter.answerCallback(event.id, \"This session belongs to a different user.\").catch(() => {});\n\t\treturn true;\n\t}\n\tif (binding && !isAuthorized(entry.source, loadGatewayConfig(), createPairingStore())) {\n\t\tentry.resolve(\"reject\");\n\t\treturn true;\n\t}\n\tif (entry.plan && response !== \"reject\" && entry.key) {\n\t\tconst sessionId = getSession(entry.key)?.sessionId;\n\t\tif (sessionId) setPermissionMode(\"yolo\", sessionId);\n\t}\n\tentry.resolve(response);\n\n\tconst label =\n\t\tparsed.action === \"reject\" || parsed.action === \"cancel\" ? \"Rejected.\" : `Approved (${parsed.action}).`;\n\tawait entry.adapter.editMessage(entry.chatId, entry.messageId ?? \"\", label, []).catch(() => {});\n\tawait entry.adapter.answerCallback(event.id).catch(() => {});\n\treturn true;\n}\n\n/** Test hook: clear all pending approvals and stop their timeouts. */\nexport function cancelGatewayApprovals(key: string): void {\n\tfor (const [id, entry] of pending) {\n\t\tif (entry.key !== key) continue;\n\t\tclearTimeout(entry.timeout);\n\t\tpending.delete(id);\n\t\tentry.resolve(\"reject\");\n\t\tif (entry.messageId)\n\t\t\tvoid entry.adapter.editMessage(entry.chatId, entry.messageId, \"Approval cancelled.\", []).catch(() => {});\n\t}\n}\n\nexport function resetApprovalRegistry(): void {\n\tfor (const entry of pending.values()) {\n\t\tclearTimeout(entry.timeout);\n\t\ttry {\n\t\t\tentry.reject(new Error(\"Approval registry reset\"));\n\t\t} catch {\n\t\t\t// ignore settled promise\n\t\t}\n\t}\n\tpending.clear();\n}\n\n/** Test hook: number of outstanding approval prompts. */\nexport function activeApprovalCount(): number {\n\treturn pending.size;\n}\n"]}
@@ -5,7 +5,7 @@
5
5
  import { AsyncLocalStorage } from "node:async_hooks";
6
6
  import { randomBytes } from "node:crypto";
7
7
  import { NO_HANDLER_REASON, registerApprovalHandler, setPermissionMode, } from "../core/permissions.js";
8
- import { isAuthorized, isGatewayOwner } from "./authz.js";
8
+ import { isAuthorized } from "./authz.js";
9
9
  import { loadGatewayConfig } from "./config.js";
10
10
  import { conversationBinding } from "./conversations.js";
11
11
  import { createPairingStore } from "./pairing.js";
@@ -140,9 +140,9 @@ export async function handleApprovalCallback(event, adapter) {
140
140
  pending.delete(parsed.id);
141
141
  const response = parsed.action === "reject" || parsed.action === "cancel" ? "reject" : parsed.action;
142
142
  const binding = entry.key ? conversationBinding(entry.key) : undefined;
143
- if (binding?.owner && (!isGatewayOwner(entry.source, loadGatewayConfig()) || binding.owner !== event.userId)) {
143
+ if (binding?.owner && binding.owner !== event.userId) {
144
144
  entry.resolve("reject");
145
- await entry.adapter.answerCallback(event.id, "Owner access was removed.").catch(() => { });
145
+ await entry.adapter.answerCallback(event.id, "This session belongs to a different user.").catch(() => { });
146
146
  return true;
147
147
  }
148
148
  if (binding && !isAuthorized(entry.source, loadGatewayConfig(), createPairingStore())) {
@@ -1 +1 @@
1
- {"version":3,"file":"approval.js","sourceRoot":"","sources":["../../src/gateway/approval.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Gateway approvals for large subagent launches and plans.\n * A missing chat context rejects large launches; plans pass through.\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { randomBytes } from \"node:crypto\";\nimport {\n\ttype ApprovalRequest,\n\ttype ApprovalResponse,\n\tNO_HANDLER_REASON,\n\tregisterApprovalHandler,\n\tsetPermissionMode,\n} from \"../core/permissions.ts\";\nimport { isAuthorized, isGatewayOwner } from \"./authz.ts\";\nimport { loadGatewayConfig } from \"./config.ts\";\nimport { conversationBinding } from \"./conversations.ts\";\nimport { createPairingStore } from \"./pairing.ts\";\nimport { getSession } from \"./store.ts\";\nimport type { ButtonSpec, CallbackEvent, PlatformAdapter, SessionSource } from \"./types.ts\";\n\nconst CALLBACK_PREFIX = \"ap:\";\nconst ID_LENGTH = 8;\nconst CODE_ALPHABET = \"ABCDEFGHJKMNPQRSTUVWXYZ23456789\";\nconst DEFAULT_TIMEOUT_MS = 5 * 60 * 1000;\n\ninterface ApprovalContext {\n\tkey: string;\n\tadapter: PlatformAdapter;\n\tsource: SessionSource;\n\ttimeoutMs?: number;\n}\n\ninterface PendingApproval {\n\tkey?: string;\n\tsource: SessionSource;\n\tplan: boolean;\n\tresolve: (value: ApprovalResponse) => void;\n\treject: (reason?: unknown) => void;\n\ttimeout: ReturnType<typeof setTimeout>;\n\tmessageId?: string;\n\tadapter: PlatformAdapter;\n\tchatId: string;\n\tthreadId?: string;\n\tuserId: string;\n}\n\nconst approvalContext = new AsyncLocalStorage<ApprovalContext>();\nconst pending = new Map<string, PendingApproval>();\n\nfunction generateId(): string {\n\tlet id = \"\";\n\tconst bytes = randomBytes(ID_LENGTH);\n\tfor (let i = 0; i < ID_LENGTH; i++) {\n\t\tid += CODE_ALPHABET[bytes[i] % CODE_ALPHABET.length];\n\t}\n\treturn id;\n}\n\nfunction makeCallbackData(id: string, action: ApprovalResponse | \"cancel\"): string {\n\treturn `${CALLBACK_PREFIX}${id}:${action}`;\n}\n\nfunction parseCallbackData(data: string): { id: string; action: ApprovalResponse | \"cancel\" } | null {\n\tif (!data.startsWith(CALLBACK_PREFIX)) return null;\n\tconst rest = data.slice(CALLBACK_PREFIX.length);\n\tconst colon = rest.indexOf(\":\");\n\tif (colon === -1) return null;\n\tconst id = rest.slice(0, colon);\n\tconst action = rest.slice(colon + 1);\n\tif (action === \"once\" || action === \"session\" || action === \"reject\" || action === \"cancel\") {\n\t\treturn { id, action };\n\t}\n\treturn null;\n}\n\n/** Run code with the gateway approval context available to tool-call gates. */\nexport function runWithApprovalContext<T>(ctx: ApprovalContext, fn: () => Promise<T>): Promise<T> {\n\treturn approvalContext.run(ctx, fn);\n}\n\nasync function createApprovalPrompt(\n\tadapter: PlatformAdapter,\n\tsource: SessionSource,\n\treq: ApprovalRequest,\n\ttimeoutMs = DEFAULT_TIMEOUT_MS,\n\tkey?: string,\n): Promise<ApprovalResponse> {\n\tconst id = generateId();\n\t// Large child launches get a dedicated title; inline buttons cannot capture feedback.\n\tconst title =\n\t\treq.kind === \"large-subagent-launch\"\n\t\t\t? `Approve large subagent launch?\\n${req.detail}`\n\t\t\t: `Approve ${req.toolName}?\\n${req.detail}`;\n\tconst rows: ButtonSpec[][] = [\n\t\t[\n\t\t\t{ label: \"✓ Approve once\", data: makeCallbackData(id, \"once\") },\n\t\t\t{ label: \"✓ Approve session\", data: makeCallbackData(id, \"session\") },\n\t\t],\n\t\t[{ label: \"✗ Reject\", data: makeCallbackData(id, \"reject\") }],\n\t];\n\n\treturn new Promise<ApprovalResponse>((resolve, reject) => {\n\t\tlet messageId: string | undefined;\n\t\tconst timeout = setTimeout(() => {\n\t\t\tpending.delete(id);\n\t\t\treject(new Error(\"Approval request timed out\"));\n\t\t\tif (messageId) {\n\t\t\t\tvoid adapter.editMessage(source.chatId, messageId, \"⏱ Approval expired.\", []).catch(() => {});\n\t\t\t}\n\t\t}, timeoutMs);\n\n\t\tconst entry: PendingApproval = {\n\t\t\tkey,\n\t\t\tsource,\n\t\t\tplan: req.kind === \"plan\",\n\t\t\tresolve,\n\t\t\treject,\n\t\t\ttimeout,\n\t\t\tadapter,\n\t\t\tchatId: source.chatId,\n\t\t\tthreadId: source.threadId,\n\t\t\tuserId: source.userId,\n\t\t};\n\t\tpending.set(id, entry);\n\t\tvoid (async () => {\n\t\t\ttry {\n\t\t\t\tconst result = await adapter.sendButtons(source.chatId, title, rows, { threadId: source.threadId });\n\t\t\t\tif (!result.success || !result.messageId) throw new Error(result.error ?? \"Failed to send approval prompt\");\n\t\t\t\tmessageId = result.messageId;\n\t\t\t\tif (pending.get(id) !== entry) {\n\t\t\t\t\tawait adapter\n\t\t\t\t\t\t.editMessage(source.chatId, messageId, \"Approval cancelled or expired.\", [])\n\t\t\t\t\t\t.catch(() => {});\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tentry.messageId = messageId;\n\t\t\t} catch (err) {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tpending.delete(id);\n\t\t\t\treject(err);\n\t\t\t}\n\t\t})();\n\t});\n}\n\n/** Approval handler passed to core permissions. */\nexport async function createGatewayApprovalRequest(req: ApprovalRequest): Promise<ApprovalResponse> {\n\tconst ctx = approvalContext.getStore();\n\tif (!ctx) {\n\t\tthrow new Error(NO_HANDLER_REASON);\n\t}\n\treturn createApprovalPrompt(ctx.adapter, ctx.source, req, ctx.timeoutMs, ctx.key);\n}\n\n/** Register the gateway approval handler with the core permission gate. */\nexport function registerGatewayApprovalHandler(): void {\n\tregisterApprovalHandler(createGatewayApprovalRequest);\n}\n\n/**\n * Dispatch a callback event to an outstanding approval prompt.\n * Returns true when the data matched the approval prefix (whether or not a\n * pending prompt existed).\n */\nexport async function handleApprovalCallback(event: CallbackEvent, adapter?: PlatformAdapter): Promise<boolean> {\n\tconst parsed = parseCallbackData(event.data);\n\tif (!parsed) return false;\n\n\tconst entry = pending.get(parsed.id);\n\tif (!entry) {\n\t\t// Best-effort answer for stale callbacks.\n\t\tawait adapter?.answerCallback(event.id, \"Approval expired.\").catch(() => {});\n\t\treturn true;\n\t}\n\n\tif (\n\t\tevent.userId !== entry.userId ||\n\t\tevent.chatId !== entry.chatId ||\n\t\tevent.threadId !== entry.threadId ||\n\t\t(adapter && adapter.platform !== entry.adapter.platform)\n\t) {\n\t\tawait entry.adapter.answerCallback(event.id, \"⛔ Not your approval.\").catch(() => {});\n\t\treturn true;\n\t}\n\n\tclearTimeout(entry.timeout);\n\tpending.delete(parsed.id);\n\n\tconst response: ApprovalResponse =\n\t\tparsed.action === \"reject\" || parsed.action === \"cancel\" ? \"reject\" : parsed.action;\n\tconst binding = entry.key ? conversationBinding(entry.key) : undefined;\n\tif (binding?.owner && (!isGatewayOwner(entry.source, loadGatewayConfig()) || binding.owner !== event.userId)) {\n\t\tentry.resolve(\"reject\");\n\t\tawait entry.adapter.answerCallback(event.id, \"Owner access was removed.\").catch(() => {});\n\t\treturn true;\n\t}\n\tif (binding && !isAuthorized(entry.source, loadGatewayConfig(), createPairingStore())) {\n\t\tentry.resolve(\"reject\");\n\t\treturn true;\n\t}\n\tif (entry.plan && response !== \"reject\" && entry.key) {\n\t\tconst sessionId = getSession(entry.key)?.sessionId;\n\t\tif (sessionId) setPermissionMode(\"yolo\", sessionId);\n\t}\n\tentry.resolve(response);\n\n\tconst label =\n\t\tparsed.action === \"reject\" || parsed.action === \"cancel\" ? \"Rejected.\" : `Approved (${parsed.action}).`;\n\tawait entry.adapter.editMessage(entry.chatId, entry.messageId ?? \"\", label, []).catch(() => {});\n\tawait entry.adapter.answerCallback(event.id).catch(() => {});\n\treturn true;\n}\n\n/** Test hook: clear all pending approvals and stop their timeouts. */\nexport function cancelGatewayApprovals(key: string): void {\n\tfor (const [id, entry] of pending) {\n\t\tif (entry.key !== key) continue;\n\t\tclearTimeout(entry.timeout);\n\t\tpending.delete(id);\n\t\tentry.resolve(\"reject\");\n\t\tif (entry.messageId)\n\t\t\tvoid entry.adapter.editMessage(entry.chatId, entry.messageId, \"Approval cancelled.\", []).catch(() => {});\n\t}\n}\n\nexport function resetApprovalRegistry(): void {\n\tfor (const entry of pending.values()) {\n\t\tclearTimeout(entry.timeout);\n\t\ttry {\n\t\t\tentry.reject(new Error(\"Approval registry reset\"));\n\t\t} catch {\n\t\t\t// ignore settled promise\n\t\t}\n\t}\n\tpending.clear();\n}\n\n/** Test hook: number of outstanding approval prompts. */\nexport function activeApprovalCount(): number {\n\treturn pending.size;\n}\n"]}
1
+ {"version":3,"file":"approval.js","sourceRoot":"","sources":["../../src/gateway/approval.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Gateway approvals for large subagent launches and plans.\n * A missing chat context rejects large launches; plans pass through.\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { randomBytes } from \"node:crypto\";\nimport {\n\ttype ApprovalRequest,\n\ttype ApprovalResponse,\n\tNO_HANDLER_REASON,\n\tregisterApprovalHandler,\n\tsetPermissionMode,\n} from \"../core/permissions.ts\";\nimport { isAuthorized } from \"./authz.ts\";\nimport { loadGatewayConfig } from \"./config.ts\";\nimport { conversationBinding } from \"./conversations.ts\";\nimport { createPairingStore } from \"./pairing.ts\";\nimport { getSession } from \"./store.ts\";\nimport type { ButtonSpec, CallbackEvent, PlatformAdapter, SessionSource } from \"./types.ts\";\n\nconst CALLBACK_PREFIX = \"ap:\";\nconst ID_LENGTH = 8;\nconst CODE_ALPHABET = \"ABCDEFGHJKMNPQRSTUVWXYZ23456789\";\nconst DEFAULT_TIMEOUT_MS = 5 * 60 * 1000;\n\ninterface ApprovalContext {\n\tkey: string;\n\tadapter: PlatformAdapter;\n\tsource: SessionSource;\n\ttimeoutMs?: number;\n}\n\ninterface PendingApproval {\n\tkey?: string;\n\tsource: SessionSource;\n\tplan: boolean;\n\tresolve: (value: ApprovalResponse) => void;\n\treject: (reason?: unknown) => void;\n\ttimeout: ReturnType<typeof setTimeout>;\n\tmessageId?: string;\n\tadapter: PlatformAdapter;\n\tchatId: string;\n\tthreadId?: string;\n\tuserId: string;\n}\n\nconst approvalContext = new AsyncLocalStorage<ApprovalContext>();\nconst pending = new Map<string, PendingApproval>();\n\nfunction generateId(): string {\n\tlet id = \"\";\n\tconst bytes = randomBytes(ID_LENGTH);\n\tfor (let i = 0; i < ID_LENGTH; i++) {\n\t\tid += CODE_ALPHABET[bytes[i] % CODE_ALPHABET.length];\n\t}\n\treturn id;\n}\n\nfunction makeCallbackData(id: string, action: ApprovalResponse | \"cancel\"): string {\n\treturn `${CALLBACK_PREFIX}${id}:${action}`;\n}\n\nfunction parseCallbackData(data: string): { id: string; action: ApprovalResponse | \"cancel\" } | null {\n\tif (!data.startsWith(CALLBACK_PREFIX)) return null;\n\tconst rest = data.slice(CALLBACK_PREFIX.length);\n\tconst colon = rest.indexOf(\":\");\n\tif (colon === -1) return null;\n\tconst id = rest.slice(0, colon);\n\tconst action = rest.slice(colon + 1);\n\tif (action === \"once\" || action === \"session\" || action === \"reject\" || action === \"cancel\") {\n\t\treturn { id, action };\n\t}\n\treturn null;\n}\n\n/** Run code with the gateway approval context available to tool-call gates. */\nexport function runWithApprovalContext<T>(ctx: ApprovalContext, fn: () => Promise<T>): Promise<T> {\n\treturn approvalContext.run(ctx, fn);\n}\n\nasync function createApprovalPrompt(\n\tadapter: PlatformAdapter,\n\tsource: SessionSource,\n\treq: ApprovalRequest,\n\ttimeoutMs = DEFAULT_TIMEOUT_MS,\n\tkey?: string,\n): Promise<ApprovalResponse> {\n\tconst id = generateId();\n\t// Large child launches get a dedicated title; inline buttons cannot capture feedback.\n\tconst title =\n\t\treq.kind === \"large-subagent-launch\"\n\t\t\t? `Approve large subagent launch?\\n${req.detail}`\n\t\t\t: `Approve ${req.toolName}?\\n${req.detail}`;\n\tconst rows: ButtonSpec[][] = [\n\t\t[\n\t\t\t{ label: \"✓ Approve once\", data: makeCallbackData(id, \"once\") },\n\t\t\t{ label: \"✓ Approve session\", data: makeCallbackData(id, \"session\") },\n\t\t],\n\t\t[{ label: \"✗ Reject\", data: makeCallbackData(id, \"reject\") }],\n\t];\n\n\treturn new Promise<ApprovalResponse>((resolve, reject) => {\n\t\tlet messageId: string | undefined;\n\t\tconst timeout = setTimeout(() => {\n\t\t\tpending.delete(id);\n\t\t\treject(new Error(\"Approval request timed out\"));\n\t\t\tif (messageId) {\n\t\t\t\tvoid adapter.editMessage(source.chatId, messageId, \"⏱ Approval expired.\", []).catch(() => {});\n\t\t\t}\n\t\t}, timeoutMs);\n\n\t\tconst entry: PendingApproval = {\n\t\t\tkey,\n\t\t\tsource,\n\t\t\tplan: req.kind === \"plan\",\n\t\t\tresolve,\n\t\t\treject,\n\t\t\ttimeout,\n\t\t\tadapter,\n\t\t\tchatId: source.chatId,\n\t\t\tthreadId: source.threadId,\n\t\t\tuserId: source.userId,\n\t\t};\n\t\tpending.set(id, entry);\n\t\tvoid (async () => {\n\t\t\ttry {\n\t\t\t\tconst result = await adapter.sendButtons(source.chatId, title, rows, { threadId: source.threadId });\n\t\t\t\tif (!result.success || !result.messageId) throw new Error(result.error ?? \"Failed to send approval prompt\");\n\t\t\t\tmessageId = result.messageId;\n\t\t\t\tif (pending.get(id) !== entry) {\n\t\t\t\t\tawait adapter\n\t\t\t\t\t\t.editMessage(source.chatId, messageId, \"Approval cancelled or expired.\", [])\n\t\t\t\t\t\t.catch(() => {});\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tentry.messageId = messageId;\n\t\t\t} catch (err) {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tpending.delete(id);\n\t\t\t\treject(err);\n\t\t\t}\n\t\t})();\n\t});\n}\n\n/** Approval handler passed to core permissions. */\nexport async function createGatewayApprovalRequest(req: ApprovalRequest): Promise<ApprovalResponse> {\n\tconst ctx = approvalContext.getStore();\n\tif (!ctx) {\n\t\tthrow new Error(NO_HANDLER_REASON);\n\t}\n\treturn createApprovalPrompt(ctx.adapter, ctx.source, req, ctx.timeoutMs, ctx.key);\n}\n\n/** Register the gateway approval handler with the core permission gate. */\nexport function registerGatewayApprovalHandler(): void {\n\tregisterApprovalHandler(createGatewayApprovalRequest);\n}\n\n/**\n * Dispatch a callback event to an outstanding approval prompt.\n * Returns true when the data matched the approval prefix (whether or not a\n * pending prompt existed).\n */\nexport async function handleApprovalCallback(event: CallbackEvent, adapter?: PlatformAdapter): Promise<boolean> {\n\tconst parsed = parseCallbackData(event.data);\n\tif (!parsed) return false;\n\n\tconst entry = pending.get(parsed.id);\n\tif (!entry) {\n\t\t// Best-effort answer for stale callbacks.\n\t\tawait adapter?.answerCallback(event.id, \"Approval expired.\").catch(() => {});\n\t\treturn true;\n\t}\n\n\tif (\n\t\tevent.userId !== entry.userId ||\n\t\tevent.chatId !== entry.chatId ||\n\t\tevent.threadId !== entry.threadId ||\n\t\t(adapter && adapter.platform !== entry.adapter.platform)\n\t) {\n\t\tawait entry.adapter.answerCallback(event.id, \"⛔ Not your approval.\").catch(() => {});\n\t\treturn true;\n\t}\n\n\tclearTimeout(entry.timeout);\n\tpending.delete(parsed.id);\n\n\tconst response: ApprovalResponse =\n\t\tparsed.action === \"reject\" || parsed.action === \"cancel\" ? \"reject\" : parsed.action;\n\tconst binding = entry.key ? conversationBinding(entry.key) : undefined;\n\tif (binding?.owner && binding.owner !== event.userId) {\n\t\tentry.resolve(\"reject\");\n\t\tawait entry.adapter.answerCallback(event.id, \"This session belongs to a different user.\").catch(() => {});\n\t\treturn true;\n\t}\n\tif (binding && !isAuthorized(entry.source, loadGatewayConfig(), createPairingStore())) {\n\t\tentry.resolve(\"reject\");\n\t\treturn true;\n\t}\n\tif (entry.plan && response !== \"reject\" && entry.key) {\n\t\tconst sessionId = getSession(entry.key)?.sessionId;\n\t\tif (sessionId) setPermissionMode(\"yolo\", sessionId);\n\t}\n\tentry.resolve(response);\n\n\tconst label =\n\t\tparsed.action === \"reject\" || parsed.action === \"cancel\" ? \"Rejected.\" : `Approved (${parsed.action}).`;\n\tawait entry.adapter.editMessage(entry.chatId, entry.messageId ?? \"\", label, []).catch(() => {});\n\tawait entry.adapter.answerCallback(event.id).catch(() => {});\n\treturn true;\n}\n\n/** Test hook: clear all pending approvals and stop their timeouts. */\nexport function cancelGatewayApprovals(key: string): void {\n\tfor (const [id, entry] of pending) {\n\t\tif (entry.key !== key) continue;\n\t\tclearTimeout(entry.timeout);\n\t\tpending.delete(id);\n\t\tentry.resolve(\"reject\");\n\t\tif (entry.messageId)\n\t\t\tvoid entry.adapter.editMessage(entry.chatId, entry.messageId, \"Approval cancelled.\", []).catch(() => {});\n\t}\n}\n\nexport function resetApprovalRegistry(): void {\n\tfor (const entry of pending.values()) {\n\t\tclearTimeout(entry.timeout);\n\t\ttry {\n\t\t\tentry.reject(new Error(\"Approval registry reset\"));\n\t\t} catch {\n\t\t\t// ignore settled promise\n\t\t}\n\t}\n\tpending.clear();\n}\n\n/** Test hook: number of outstanding approval prompts. */\nexport function activeApprovalCount(): number {\n\treturn pending.size;\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * lunR: gateway authorization — fail-closed, layered:
3
+ * 0. legacy owners list (same access as allowedUsers)
3
4
  * 1. LUNR_GATEWAY_ALLOW_ALL_USERS=true env (escape hatch)
4
5
  * 2. chatId in the platform's allowedChats (chat-scoped grant)
5
6
  * 3. adapter-asserted roleAuthorized
@@ -9,11 +10,10 @@
9
10
  * 6. denied
10
11
  */
11
12
  import { type GatewayConfig } from "./config.ts";
12
- import type { PairingStore } from "./pairing.ts";
13
+ import { type PairingStore } from "./pairing.ts";
13
14
  import type { SessionSource } from "./types.ts";
14
- export declare function isGatewayOwner(source: SessionSource, cfg: GatewayConfig): boolean;
15
- export declare function requireGatewayOwner(source: SessionSource, cfg?: GatewayConfig): void;
16
- export declare function isAuthorized(source: SessionSource, cfg: GatewayConfig, pairing: PairingStore): boolean;
15
+ export declare function requireAuthorized(source: SessionSource, cfg?: GatewayConfig): void;
16
+ export declare function isAuthorized(source: SessionSource, cfg: GatewayConfig, pairing?: PairingStore): boolean;
17
17
  /**
18
18
  * Append a user to a platform's allowedUsers in the saved gateway.json
19
19
  * (used after a pairing approval so the grant survives pairing-store resets).
@@ -1 +1 @@
1
- {"version":3,"file":"authz.d.ts","sourceRoot":"","sources":["../../src/gateway/authz.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,KAAK,aAAa,EAA2D,MAAM,aAAa,CAAC;AAC1G,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAiBhD,wBAAgB,cAAc,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,aAAa,GAAG,OAAO,CAIjF;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,gBAAsB,GAAG,IAAI,CAK1F;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,aAAa,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAYtG;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAUrE","sourcesContent":["/**\n * lunR: gateway authorization — fail-closed, layered:\n * 1. LUNR_GATEWAY_ALLOW_ALL_USERS=true env (escape hatch)\n * 2. chatId in the platform's allowedChats (chat-scoped grant)\n * 3. adapter-asserted roleAuthorized\n * 4. paired via the pairing store\n * 5. userId in the platform's allowedUsers or global\n * LUNR_GATEWAY_ALLOWED_USERS (comma-separated env)\n * 6. denied\n */\n\nimport { type GatewayConfig, loadGatewayConfig, platformConfigFor, saveGatewayConfig } from \"./config.ts\";\nimport type { PairingStore } from \"./pairing.ts\";\nimport type { SessionSource } from \"./types.ts\";\n\nfunction envFlagEnabled(name: string): boolean {\n\tconst raw = process.env[name];\n\tif (raw === undefined) return false;\n\treturn [\"1\", \"true\", \"yes\", \"on\"].includes(raw.toLowerCase());\n}\n\nfunction globalAllowedUsers(): string[] {\n\tconst raw = process.env.LUNR_GATEWAY_ALLOWED_USERS;\n\tif (!raw) return [];\n\treturn raw\n\t\t.split(\",\")\n\t\t.map((u) => u.trim())\n\t\t.filter((u) => u.length > 0);\n}\n\nexport function isGatewayOwner(source: SessionSource, cfg: GatewayConfig): boolean {\n\tif (source.chatType !== \"dm\") return false;\n\tif (source.platform !== \"telegram\" && source.platform !== \"discord\") return false;\n\treturn cfg.owners?.[source.platform].includes(source.userId) ?? false;\n}\n\nexport function requireGatewayOwner(source: SessionSource, cfg = loadGatewayConfig()): void {\n\tif (!isGatewayOwner(source, cfg))\n\t\tthrow new Error(\n\t\t\t\"This command is available only to the configured owner in a private chat. Use lunr gateway setup to configure owner access.\",\n\t\t);\n}\n\nexport function isAuthorized(source: SessionSource, cfg: GatewayConfig, pairing: PairingStore): boolean {\n\tif (isGatewayOwner(source, cfg)) return true;\n\tif (envFlagEnabled(\"LUNR_GATEWAY_ALLOW_ALL_USERS\")) return true;\n\tconst platformCfg = platformConfigFor(cfg, source.platform);\n\tif (platformCfg?.allowedChats.includes(source.chatId)) return true;\n\tif (source.roleAuthorized === true) return true;\n\tif (source.userId && pairing.isPaired(source.platform, source.userId)) return true;\n\tif (source.userId) {\n\t\tif (platformCfg?.allowedUsers.includes(source.userId)) return true;\n\t\tif (globalAllowedUsers().includes(source.userId)) return true;\n\t}\n\treturn false;\n}\n\n/**\n * Append a user to a platform's allowedUsers in the saved gateway.json\n * (used after a pairing approval so the grant survives pairing-store resets).\n * No-op when already present.\n */\nexport function addAllowedUser(platform: string, userId: string): void {\n\tconst cfg = loadGatewayConfig();\n\tconst platformCfg = platformConfigFor(cfg, platform);\n\tif (!platformCfg) {\n\t\tthrow new Error(`Unknown platform \"${platform}\"`);\n\t}\n\tif (!platformCfg.allowedUsers.includes(userId)) {\n\t\tplatformCfg.allowedUsers.push(userId);\n\t\tsaveGatewayConfig(cfg);\n\t}\n}\n"]}
1
+ {"version":3,"file":"authz.d.ts","sourceRoot":"","sources":["../../src/gateway/authz.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,KAAK,aAAa,EAA2D,MAAM,aAAa,CAAC;AAC1G,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAiBhD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,gBAAsB,GAAG,IAAI,CAExF;AAED,wBAAgB,YAAY,CAC3B,MAAM,EAAE,aAAa,EACrB,GAAG,EAAE,aAAa,EAClB,OAAO,GAAE,YAAmC,GAC1C,OAAO,CAgBT;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAUrE","sourcesContent":["/**\n * lunR: gateway authorization — fail-closed, layered:\n * 0. legacy owners list (same access as allowedUsers)\n * 1. LUNR_GATEWAY_ALLOW_ALL_USERS=true env (escape hatch)\n * 2. chatId in the platform's allowedChats (chat-scoped grant)\n * 3. adapter-asserted roleAuthorized\n * 4. paired via the pairing store\n * 5. userId in the platform's allowedUsers or global\n * LUNR_GATEWAY_ALLOWED_USERS (comma-separated env)\n * 6. denied\n */\n\nimport { type GatewayConfig, loadGatewayConfig, platformConfigFor, saveGatewayConfig } from \"./config.ts\";\nimport { createPairingStore, type PairingStore } from \"./pairing.ts\";\nimport type { SessionSource } from \"./types.ts\";\n\nfunction envFlagEnabled(name: string): boolean {\n\tconst raw = process.env[name];\n\tif (raw === undefined) return false;\n\treturn [\"1\", \"true\", \"yes\", \"on\"].includes(raw.toLowerCase());\n}\n\nfunction globalAllowedUsers(): string[] {\n\tconst raw = process.env.LUNR_GATEWAY_ALLOWED_USERS;\n\tif (!raw) return [];\n\treturn raw\n\t\t.split(\",\")\n\t\t.map((u) => u.trim())\n\t\t.filter((u) => u.length > 0);\n}\n\nexport function requireAuthorized(source: SessionSource, cfg = loadGatewayConfig()): void {\n\tif (!isAuthorized(source, cfg)) throw new Error(\"Gateway access is not approved for this user or chat.\");\n}\n\nexport function isAuthorized(\n\tsource: SessionSource,\n\tcfg: GatewayConfig,\n\tpairing: PairingStore = createPairingStore(),\n): boolean {\n\tif (\n\t\t(source.platform === \"telegram\" || source.platform === \"discord\") &&\n\t\tcfg.owners?.[source.platform].includes(source.userId)\n\t)\n\t\treturn true;\n\tif (envFlagEnabled(\"LUNR_GATEWAY_ALLOW_ALL_USERS\")) return true;\n\tconst platformCfg = platformConfigFor(cfg, source.platform);\n\tif (platformCfg?.allowedChats.includes(source.chatId)) return true;\n\tif (source.roleAuthorized === true) return true;\n\tif (source.userId && pairing.isPaired(source.platform, source.userId)) return true;\n\tif (source.userId) {\n\t\tif (platformCfg?.allowedUsers.includes(source.userId)) return true;\n\t\tif (globalAllowedUsers().includes(source.userId)) return true;\n\t}\n\treturn false;\n}\n\n/**\n * Append a user to a platform's allowedUsers in the saved gateway.json\n * (used after a pairing approval so the grant survives pairing-store resets).\n * No-op when already present.\n */\nexport function addAllowedUser(platform: string, userId: string): void {\n\tconst cfg = loadGatewayConfig();\n\tconst platformCfg = platformConfigFor(cfg, platform);\n\tif (!platformCfg) {\n\t\tthrow new Error(`Unknown platform \"${platform}\"`);\n\t}\n\tif (!platformCfg.allowedUsers.includes(userId)) {\n\t\tplatformCfg.allowedUsers.push(userId);\n\t\tsaveGatewayConfig(cfg);\n\t}\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * lunR: gateway authorization — fail-closed, layered:
3
+ * 0. legacy owners list (same access as allowedUsers)
3
4
  * 1. LUNR_GATEWAY_ALLOW_ALL_USERS=true env (escape hatch)
4
5
  * 2. chatId in the platform's allowedChats (chat-scoped grant)
5
6
  * 3. adapter-asserted roleAuthorized
@@ -9,6 +10,7 @@
9
10
  * 6. denied
10
11
  */
11
12
  import { loadGatewayConfig, platformConfigFor, saveGatewayConfig } from "./config.js";
13
+ import { createPairingStore } from "./pairing.js";
12
14
  function envFlagEnabled(name) {
13
15
  const raw = process.env[name];
14
16
  if (raw === undefined)
@@ -24,19 +26,13 @@ function globalAllowedUsers() {
24
26
  .map((u) => u.trim())
25
27
  .filter((u) => u.length > 0);
26
28
  }
27
- export function isGatewayOwner(source, cfg) {
28
- if (source.chatType !== "dm")
29
- return false;
30
- if (source.platform !== "telegram" && source.platform !== "discord")
31
- return false;
32
- return cfg.owners?.[source.platform].includes(source.userId) ?? false;
33
- }
34
- export function requireGatewayOwner(source, cfg = loadGatewayConfig()) {
35
- if (!isGatewayOwner(source, cfg))
36
- throw new Error("This command is available only to the configured owner in a private chat. Use lunr gateway setup to configure owner access.");
29
+ export function requireAuthorized(source, cfg = loadGatewayConfig()) {
30
+ if (!isAuthorized(source, cfg))
31
+ throw new Error("Gateway access is not approved for this user or chat.");
37
32
  }
38
- export function isAuthorized(source, cfg, pairing) {
39
- if (isGatewayOwner(source, cfg))
33
+ export function isAuthorized(source, cfg, pairing = createPairingStore()) {
34
+ if ((source.platform === "telegram" || source.platform === "discord") &&
35
+ cfg.owners?.[source.platform].includes(source.userId))
40
36
  return true;
41
37
  if (envFlagEnabled("LUNR_GATEWAY_ALLOW_ALL_USERS"))
42
38
  return true;
@@ -1 +1 @@
1
- {"version":3,"file":"authz.js","sourceRoot":"","sources":["../../src/gateway/authz.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * lunR: gateway authorization — fail-closed, layered:\n * 1. LUNR_GATEWAY_ALLOW_ALL_USERS=true env (escape hatch)\n * 2. chatId in the platform's allowedChats (chat-scoped grant)\n * 3. adapter-asserted roleAuthorized\n * 4. paired via the pairing store\n * 5. userId in the platform's allowedUsers or global\n * LUNR_GATEWAY_ALLOWED_USERS (comma-separated env)\n * 6. denied\n */\n\nimport { type GatewayConfig, loadGatewayConfig, platformConfigFor, saveGatewayConfig } from \"./config.ts\";\nimport type { PairingStore } from \"./pairing.ts\";\nimport type { SessionSource } from \"./types.ts\";\n\nfunction envFlagEnabled(name: string): boolean {\n\tconst raw = process.env[name];\n\tif (raw === undefined) return false;\n\treturn [\"1\", \"true\", \"yes\", \"on\"].includes(raw.toLowerCase());\n}\n\nfunction globalAllowedUsers(): string[] {\n\tconst raw = process.env.LUNR_GATEWAY_ALLOWED_USERS;\n\tif (!raw) return [];\n\treturn raw\n\t\t.split(\",\")\n\t\t.map((u) => u.trim())\n\t\t.filter((u) => u.length > 0);\n}\n\nexport function isGatewayOwner(source: SessionSource, cfg: GatewayConfig): boolean {\n\tif (source.chatType !== \"dm\") return false;\n\tif (source.platform !== \"telegram\" && source.platform !== \"discord\") return false;\n\treturn cfg.owners?.[source.platform].includes(source.userId) ?? false;\n}\n\nexport function requireGatewayOwner(source: SessionSource, cfg = loadGatewayConfig()): void {\n\tif (!isGatewayOwner(source, cfg))\n\t\tthrow new Error(\n\t\t\t\"This command is available only to the configured owner in a private chat. Use lunr gateway setup to configure owner access.\",\n\t\t);\n}\n\nexport function isAuthorized(source: SessionSource, cfg: GatewayConfig, pairing: PairingStore): boolean {\n\tif (isGatewayOwner(source, cfg)) return true;\n\tif (envFlagEnabled(\"LUNR_GATEWAY_ALLOW_ALL_USERS\")) return true;\n\tconst platformCfg = platformConfigFor(cfg, source.platform);\n\tif (platformCfg?.allowedChats.includes(source.chatId)) return true;\n\tif (source.roleAuthorized === true) return true;\n\tif (source.userId && pairing.isPaired(source.platform, source.userId)) return true;\n\tif (source.userId) {\n\t\tif (platformCfg?.allowedUsers.includes(source.userId)) return true;\n\t\tif (globalAllowedUsers().includes(source.userId)) return true;\n\t}\n\treturn false;\n}\n\n/**\n * Append a user to a platform's allowedUsers in the saved gateway.json\n * (used after a pairing approval so the grant survives pairing-store resets).\n * No-op when already present.\n */\nexport function addAllowedUser(platform: string, userId: string): void {\n\tconst cfg = loadGatewayConfig();\n\tconst platformCfg = platformConfigFor(cfg, platform);\n\tif (!platformCfg) {\n\t\tthrow new Error(`Unknown platform \"${platform}\"`);\n\t}\n\tif (!platformCfg.allowedUsers.includes(userId)) {\n\t\tplatformCfg.allowedUsers.push(userId);\n\t\tsaveGatewayConfig(cfg);\n\t}\n}\n"]}
1
+ {"version":3,"file":"authz.js","sourceRoot":"","sources":["../../src/gateway/authz.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * lunR: gateway authorization — fail-closed, layered:\n * 0. legacy owners list (same access as allowedUsers)\n * 1. LUNR_GATEWAY_ALLOW_ALL_USERS=true env (escape hatch)\n * 2. chatId in the platform's allowedChats (chat-scoped grant)\n * 3. adapter-asserted roleAuthorized\n * 4. paired via the pairing store\n * 5. userId in the platform's allowedUsers or global\n * LUNR_GATEWAY_ALLOWED_USERS (comma-separated env)\n * 6. denied\n */\n\nimport { type GatewayConfig, loadGatewayConfig, platformConfigFor, saveGatewayConfig } from \"./config.ts\";\nimport { createPairingStore, type PairingStore } from \"./pairing.ts\";\nimport type { SessionSource } from \"./types.ts\";\n\nfunction envFlagEnabled(name: string): boolean {\n\tconst raw = process.env[name];\n\tif (raw === undefined) return false;\n\treturn [\"1\", \"true\", \"yes\", \"on\"].includes(raw.toLowerCase());\n}\n\nfunction globalAllowedUsers(): string[] {\n\tconst raw = process.env.LUNR_GATEWAY_ALLOWED_USERS;\n\tif (!raw) return [];\n\treturn raw\n\t\t.split(\",\")\n\t\t.map((u) => u.trim())\n\t\t.filter((u) => u.length > 0);\n}\n\nexport function requireAuthorized(source: SessionSource, cfg = loadGatewayConfig()): void {\n\tif (!isAuthorized(source, cfg)) throw new Error(\"Gateway access is not approved for this user or chat.\");\n}\n\nexport function isAuthorized(\n\tsource: SessionSource,\n\tcfg: GatewayConfig,\n\tpairing: PairingStore = createPairingStore(),\n): boolean {\n\tif (\n\t\t(source.platform === \"telegram\" || source.platform === \"discord\") &&\n\t\tcfg.owners?.[source.platform].includes(source.userId)\n\t)\n\t\treturn true;\n\tif (envFlagEnabled(\"LUNR_GATEWAY_ALLOW_ALL_USERS\")) return true;\n\tconst platformCfg = platformConfigFor(cfg, source.platform);\n\tif (platformCfg?.allowedChats.includes(source.chatId)) return true;\n\tif (source.roleAuthorized === true) return true;\n\tif (source.userId && pairing.isPaired(source.platform, source.userId)) return true;\n\tif (source.userId) {\n\t\tif (platformCfg?.allowedUsers.includes(source.userId)) return true;\n\t\tif (globalAllowedUsers().includes(source.userId)) return true;\n\t}\n\treturn false;\n}\n\n/**\n * Append a user to a platform's allowedUsers in the saved gateway.json\n * (used after a pairing approval so the grant survives pairing-store resets).\n * No-op when already present.\n */\nexport function addAllowedUser(platform: string, userId: string): void {\n\tconst cfg = loadGatewayConfig();\n\tconst platformCfg = platformConfigFor(cfg, platform);\n\tif (!platformCfg) {\n\t\tthrow new Error(`Unknown platform \"${platform}\"`);\n\t}\n\tif (!platformCfg.allowedUsers.includes(userId)) {\n\t\tplatformCfg.allowedUsers.push(userId);\n\t\tsaveGatewayConfig(cfg);\n\t}\n}\n"]}
@@ -5,7 +5,7 @@
5
5
  * now; approvals later). Callback payloads stay tiny so they fit Telegram's
6
6
  * 64-byte callback_data limit; all picker state lives server-side in this module.
7
7
  */
8
- import type { GatewayConfig } from "./config.ts";
8
+ import { type GatewayConfig } from "./config.ts";
9
9
  import type { PairingStore } from "./pairing.ts";
10
10
  import type { BridgeLike } from "./router.ts";
11
11
  import type { CallbackEvent, PlatformAdapter, SendOptions, SendResult, SessionSource } from "./types.ts";
@@ -22,7 +22,11 @@ export type PickerResolveResult = {
22
22
  title: string;
23
23
  breadcrumbs?: string;
24
24
  };
25
+ /** An awaited control was stopped or superseded before it could commit. */
26
+ export declare class GatewayPickerCancelled extends Error {
27
+ }
25
28
  export interface PickerSpec {
29
+ generation?: number;
26
30
  kind: "model" | "thinking" | "sessions" | "project" | "dialog";
27
31
  validate?(): boolean;
28
32
  onCancel?(): void;
@@ -40,6 +44,9 @@ export interface ButtonDeps {
40
44
  pairing: PairingStore;
41
45
  bridge: BridgeLike;
42
46
  }
47
+ export declare function gatewayPickerGeneration(key: string): number;
48
+ /** Cancel pending controls when their conversation changes or the user stops them. */
49
+ export declare function invalidateGatewayPickers(key: string): void;
43
50
  /**
44
51
  * Create and send a paginated inline-button picker. On send failure the caller
45
52
  * should fall back to a text-based list.
@@ -1 +1 @@
1
- {"version":3,"file":"buttons.d.ts","sourceRoot":"","sources":["../../src/gateway/buttons.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAc,aAAa,EAAE,eAAe,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAUrH,MAAM,WAAW,UAAU;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,mBAAmB,GAC5B;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC5B;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,UAAU,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7E,MAAM,WAAW,UAAU;IAC1B,IAAI,EAAE,OAAO,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC/D,QAAQ,CAAC,IAAI,OAAO,CAAC;IACrB,QAAQ,CAAC,IAAI,IAAI,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;CACxD;AAeD,MAAM,WAAW,UAAU;IAC1B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACvC,GAAG,EAAE,aAAa,CAAC;IACnB,OAAO,EAAE,YAAY,CAAC;IACtB,MAAM,EAAE,UAAU,CAAC;CACnB;AA0FD;;;GAGG;AACH,wBAAsB,YAAY,CACjC,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE,aAAa,EACrB,IAAI,EAAE,UAAU,EAChB,IAAI,CAAC,EAAE,WAAW,GAChB,OAAO,CAAC,UAAU,CAAC,CAqBrB;AAcD,6DAA6D;AAC7D,wBAAsB,cAAc,CACnC,EAAE,EAAE,aAAa,EACjB,IAAI,EAAE,UAAU,GAAG;IAAE,OAAO,EAAE,eAAe,CAAA;CAAE,GAC7C,OAAO,CAAC,IAAI,CAAC,CAiHf;AAaD,kDAAkD;AAClD,wBAAgB,kBAAkB,IAAI,IAAI,CAezC;AAED,4BAA4B;AAC5B,wBAAgB,iBAAiB,IAAI,IAAI,CAKxC;AAED,0DAA0D;AAC1D,wBAAgB,mBAAmB,IAAI,IAAI,CAG1C;AAED,2CAA2C;AAC3C,wBAAgB,eAAe,IAAI,MAAM,EAAE,CAE1C","sourcesContent":["/**\n * lunR: gateway inline-button picker registry.\n *\n * Single registry for all interactive prompts (model/thinking/sessions pickers\n * now; approvals later). Callback payloads stay tiny so they fit Telegram's\n * 64-byte callback_data limit; all picker state lives server-side in this module.\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport { isAuthorized } from \"./authz.ts\";\nimport type { GatewayConfig } from \"./config.ts\";\nimport type { PairingStore } from \"./pairing.ts\";\nimport type { BridgeLike } from \"./router.ts\";\nimport type { ButtonSpec, CallbackEvent, PlatformAdapter, SendOptions, SendResult, SessionSource } from \"./types.ts\";\n\nconst PICKER_TTL_MS = 15 * 60 * 1000;\nconst SWEEP_INTERVAL_MS = 60 * 1000;\nconst DEFAULT_PER_PAGE = 8;\nconst BUTTONS_PER_ROW = 2;\nconst CODE_ALPHABET = \"ABCDEFGHJKMNPQRSTUVWXYZ23456789\";\nconst CODE_LENGTH = 8;\nconst CALLBACK_PREFIX = \"cp:\";\n\nexport interface PickerItem {\n\tlabel: string;\n\tvalue: string;\n}\n\nexport type PickerResolveResult =\n\t| { done: true; text: string }\n\t| { done: false; items: PickerItem[]; title: string; breadcrumbs?: string };\n\nexport interface PickerSpec {\n\tkind: \"model\" | \"thinking\" | \"sessions\" | \"project\" | \"dialog\";\n\tvalidate?(): boolean;\n\tonCancel?(): void;\n\tsessionKey: string;\n\tinvokerId: string;\n\titems: PickerItem[];\n\tperPage?: number;\n\ttitle: string;\n\tbreadcrumbs?: string;\n\tresolve(item: PickerItem): Promise<PickerResolveResult>;\n}\n\ninterface PendingPicker extends PickerSpec {\n\tresolving?: boolean;\n\tcompleted?: string;\n\tid: string;\n\tchatId: string;\n\tmessageId: string;\n\tsource: SessionSource;\n\tadapter: PlatformAdapter;\n\tpage: number;\n\tperPage: number;\n\tcreatedAt: number;\n}\n\nexport interface ButtonDeps {\n\tadapters: Map<string, PlatformAdapter>;\n\tcfg: GatewayConfig;\n\tpairing: PairingStore;\n\tbridge: BridgeLike;\n}\n\nconst registry = new Map<string, PendingPicker>();\nlet sweeper: ReturnType<typeof setInterval> | undefined;\n\nfunction generatePickerId(): string {\n\tlet id = \"\";\n\tconst bytes = randomBytes(CODE_LENGTH);\n\tfor (let i = 0; i < CODE_LENGTH; i++) {\n\t\tid += CODE_ALPHABET[bytes[i] % CODE_ALPHABET.length];\n\t}\n\treturn id;\n}\n\nfunction makeCallbackData(id: string, action: \"select\" | \"page\" | \"cancel\" | \"noop\", payload?: string): string {\n\tif (action === \"noop\") return `${CALLBACK_PREFIX}${id}:`;\n\tif (action === \"cancel\") return `${CALLBACK_PREFIX}${id}:x`;\n\tif (action === \"page\") return `${CALLBACK_PREFIX}${id}:p${payload ?? \"0\"}`;\n\treturn `${CALLBACK_PREFIX}${id}:${payload ?? \"\"}`;\n}\n\nfunction parseCallbackData(\n\tdata: string,\n): { id: string; action: \"select\" | \"page\" | \"cancel\" | \"noop\"; payload?: string } | null {\n\tif (!data.startsWith(CALLBACK_PREFIX)) return null;\n\tconst rest = data.slice(CALLBACK_PREFIX.length);\n\tconst colon = rest.indexOf(\":\");\n\tif (colon === -1) return null;\n\tconst id = rest.slice(0, colon);\n\tconst tail = rest.slice(colon + 1);\n\tif (tail === \"\") return { id, action: \"noop\" };\n\tif (tail === \"x\") return { id, action: \"cancel\" };\n\tif (tail.startsWith(\"p\")) return { id, action: \"page\", payload: tail.slice(1) };\n\treturn { id, action: \"select\", payload: tail };\n}\n\nfunction isSameChat(a: SessionSource, b: SessionSource): boolean {\n\treturn a.platform === b.platform && a.chatId === b.chatId && a.threadId === b.threadId;\n}\n\nfunction sourceForAuth(picker: PendingPicker, cb: CallbackEvent): SessionSource {\n\treturn {\n\t\tplatform: picker.source.platform,\n\t\tchatId: cb.chatId,\n\t\tchatType: picker.source.chatType,\n\t\tuserId: cb.userId,\n\t\tuserName: cb.userName,\n\t\tthreadId: cb.threadId,\n\t\t// Do NOT inherit the original invoker's role authorization.\n\t\troleAuthorized: undefined,\n\t};\n}\n\nfunction renderPage(picker: PendingPicker): { text: string; buttons: ButtonSpec[][] } {\n\tconst { id, title, breadcrumbs, items, page, perPage } = picker;\n\tconst totalPages = Math.max(1, Math.ceil(items.length / perPage));\n\tconst clampedPage = Math.max(0, Math.min(page, totalPages - 1));\n\tconst start = clampedPage * perPage;\n\tconst pageItems = items.slice(start, start + perPage);\n\n\tconst rows: ButtonSpec[][] = [];\n\tfor (let i = 0; i < pageItems.length; i += BUTTONS_PER_ROW) {\n\t\tconst row: ButtonSpec[] = [];\n\t\tfor (let j = 0; j < BUTTONS_PER_ROW && i + j < pageItems.length; j++) {\n\t\t\tconst index = start + i + j;\n\t\t\trow.push({ label: pageItems[i + j].label, data: makeCallbackData(id, \"select\", String(index)) });\n\t\t}\n\t\trows.push(row);\n\t}\n\n\tif (totalPages > 1) {\n\t\tconst nav: ButtonSpec[] = [];\n\t\tif (clampedPage > 0) {\n\t\t\tnav.push({ label: \"◀ Prev\", data: makeCallbackData(id, \"page\", String(clampedPage - 1)) });\n\t\t}\n\t\tnav.push({ label: `${clampedPage + 1}/${totalPages}`, data: makeCallbackData(id, \"noop\") });\n\t\tif (clampedPage < totalPages - 1) {\n\t\t\tnav.push({ label: \"Next ▶\", data: makeCallbackData(id, \"page\", String(clampedPage + 1)) });\n\t\t}\n\t\trows.push(nav);\n\t}\n\n\trows.push([{ label: \"✗ Cancel\", data: makeCallbackData(id, \"cancel\") }]);\n\n\tconst header = breadcrumbs ? `${title} · ${breadcrumbs}` : title;\n\tconst text =\n\t\titems.length === 0 ? `${header}\\n\\nNo options available.` : `${header} (${clampedPage + 1}/${totalPages}):`;\n\treturn { text, buttons: rows };\n}\n\n/**\n * Create and send a paginated inline-button picker. On send failure the caller\n * should fall back to a text-based list.\n */\nexport async function createPicker(\n\tadapter: PlatformAdapter,\n\tsource: SessionSource,\n\tspec: PickerSpec,\n\topts?: SendOptions,\n): Promise<SendResult> {\n\tconst id = generatePickerId();\n\tconst pending: PendingPicker = {\n\t\t...spec,\n\t\tid,\n\t\tchatId: source.chatId,\n\t\tsource,\n\t\tadapter,\n\t\tmessageId: \"\",\n\t\tpage: 0,\n\t\tperPage: spec.perPage ?? DEFAULT_PER_PAGE,\n\t\tcreatedAt: Date.now(),\n\t};\n\tconst { text, buttons } = renderPage(pending);\n\tconst result = await adapter.sendButtons(source.chatId, text, buttons, opts);\n\tif (!result.success) return result;\n\tif (result.messageId) {\n\t\tpending.messageId = result.messageId;\n\t\tregistry.set(id, pending);\n\t}\n\treturn result;\n}\n\nasync function answerExpired(adapter: PlatformAdapter, cb: CallbackEvent): Promise<void> {\n\tawait adapter.answerCallback(cb.id, \"Picker expired — run the command again.\").catch(() => {});\n}\n\nasync function answerUnauthorized(adapter: PlatformAdapter, cb: CallbackEvent): Promise<void> {\n\tawait adapter.answerCallback(cb.id, \"⛔ Not authorized.\").catch(() => {});\n}\n\nasync function answerNotYourPicker(adapter: PlatformAdapter, cb: CallbackEvent): Promise<void> {\n\tawait adapter.answerCallback(cb.id, \"⛔ Not your picker.\").catch(() => {});\n}\n\n/** Dispatch a CallbackEvent to the matching picker entry. */\nexport async function handleCallback(\n\tcb: CallbackEvent,\n\tdeps: ButtonDeps & { adapter: PlatformAdapter },\n): Promise<void> {\n\tconst { adapter, cfg, pairing } = deps;\n\tconst parsed = parseCallbackData(cb.data);\n\tif (!parsed) return;\n\n\tconst picker = registry.get(parsed.id);\n\tif (!picker) {\n\t\tawait answerExpired(adapter, cb);\n\t\treturn;\n\t}\n\n\tif (!picker.completed && picker.validate && !picker.validate()) {\n\t\tregistry.delete(parsed.id);\n\t\tpicker.onCancel?.();\n\t\tawait answerExpired(adapter, cb);\n\t\treturn;\n\t}\n\n\tif (Date.now() > picker.createdAt + PICKER_TTL_MS) {\n\t\tif (!picker.completed) picker.onCancel?.();\n\t\tregistry.delete(parsed.id);\n\t\ttry {\n\t\t\tawait picker.adapter.editMessage(picker.chatId, picker.messageId, \"⏱ Expired — run the command again.\", []);\n\t\t} catch {\n\t\t\t// best-effort expiry message\n\t\t}\n\t\tawait answerExpired(adapter, cb);\n\t\treturn;\n\t}\n\n\tif (!isSameChat(picker.source, sourceForAuth(picker, cb))) {\n\t\tawait answerExpired(adapter, cb);\n\t\treturn;\n\t}\n\n\tif (cb.userId !== picker.invokerId) {\n\t\tawait answerNotYourPicker(adapter, cb);\n\t\treturn;\n\t}\n\n\tif (!isAuthorized(sourceForAuth(picker, cb), cfg, pairing)) {\n\t\tawait answerUnauthorized(adapter, cb);\n\t\treturn;\n\t}\n\n\tif (picker.completed !== undefined) {\n\t\tawait adapter.answerCallback(cb.id, \"Already completed.\").catch(() => {});\n\t\treturn;\n\t}\n\n\tif (parsed.action === \"cancel\") {\n\t\tregistry.delete(parsed.id);\n\t\tpicker.onCancel?.();\n\t\ttry {\n\t\t\tawait picker.adapter.editMessage(picker.chatId, picker.messageId, \"Cancelled.\", []);\n\t\t} catch {}\n\t\tawait adapter.answerCallback(cb.id).catch(() => {});\n\t\treturn;\n\t}\n\n\tif (parsed.action === \"noop\") {\n\t\tawait adapter.answerCallback(cb.id).catch(() => {});\n\t\treturn;\n\t}\n\n\tif (parsed.action === \"page\") {\n\t\tconst page = Number.parseInt(parsed.payload ?? \"0\", 10);\n\t\tconst totalPages = Math.max(1, Math.ceil(picker.items.length / picker.perPage));\n\t\tpicker.page = Math.max(0, Math.min(page, totalPages - 1));\n\t\tconst { text, buttons } = renderPage(picker);\n\t\ttry {\n\t\t\tawait picker.adapter.editMessage(picker.chatId, picker.messageId, text, buttons);\n\t\t} catch {}\n\t\tawait adapter.answerCallback(cb.id).catch(() => {});\n\t\treturn;\n\t}\n\n\t// select\n\tconst index = Number.parseInt(parsed.payload ?? \"\", 10);\n\tconst item = picker.items[index];\n\tif (!item) {\n\t\tawait adapter.answerCallback(cb.id, \"Invalid selection.\").catch(() => {});\n\t\treturn;\n\t}\n\n\tif (picker.resolving) {\n\t\tawait adapter.answerCallback(cb.id, \"Selection is still running.\").catch(() => {});\n\t\treturn;\n\t}\n\tpicker.resolving = true;\n\tawait adapter.answerCallback(cb.id).catch(() => {});\n\ttry {\n\t\tconst result = await picker.resolve(item);\n\t\tif (!result.done) {\n\t\t\tpicker.items = result.items;\n\t\t\tpicker.title = result.title;\n\t\t\tpicker.breadcrumbs = result.breadcrumbs;\n\t\t\tpicker.page = 0;\n\t\t\tconst { text, buttons } = renderPage(picker);\n\t\t\ttry {\n\t\t\t\tawait picker.adapter.editMessage(picker.chatId, picker.messageId, text, buttons);\n\t\t\t} catch {}\n\t\t\treturn;\n\t\t}\n\t\tpicker.completed = result.text;\n\t\tawait confirmPicker(picker, result.text);\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tpicker.completed = `⚠ ${message}`;\n\t\tawait confirmPicker(picker, picker.completed);\n\t} finally {\n\t\tpicker.resolving = false;\n\t}\n}\n\nasync function confirmPicker(picker: PendingPicker, text: string): Promise<void> {\n\tconst edited = await picker.adapter\n\t\t.editMessage(picker.chatId, picker.messageId, text, [])\n\t\t.catch(() => ({ success: false }));\n\tif (edited.success) return;\n\tconst sent = await picker.adapter\n\t\t.send(picker.chatId, text, { threadId: picker.source.threadId })\n\t\t.catch(() => ({ success: false }));\n\tif (!sent.success) console.error(`[gateway] picker confirmation failed on ${picker.adapter.platform}`);\n}\n\n/** Start the periodic TTL sweeper. Idempotent. */\nexport function startButtonSweeper(): void {\n\tif (sweeper) return;\n\tsweeper = setInterval(() => {\n\t\tconst cutoff = Date.now() - PICKER_TTL_MS;\n\t\tfor (const [id, entry] of registry) {\n\t\t\tif (entry.createdAt > cutoff) continue;\n\t\t\tregistry.delete(id);\n\t\t\tif (entry.completed) continue;\n\t\t\tentry.onCancel?.();\n\t\t\tvoid entry.adapter\n\t\t\t\t.editMessage(entry.chatId, entry.messageId, \"⏱ Expired — run the command again.\", [])\n\t\t\t\t.catch(() => {});\n\t\t}\n\t}, SWEEP_INTERVAL_MS);\n\tsweeper.unref?.();\n}\n\n/** Stop the TTL sweeper. */\nexport function stopButtonSweeper(): void {\n\tif (sweeper) {\n\t\tclearInterval(sweeper);\n\t\tsweeper = undefined;\n\t}\n}\n\n/** Test hook: reset the registry and stop the sweeper. */\nexport function resetButtonRegistry(): void {\n\tregistry.clear();\n\tstopButtonSweeper();\n}\n\n/** Test hook: expose active picker ids. */\nexport function activePickerIds(): string[] {\n\treturn [...registry].filter(([, picker]) => picker.completed === undefined).map(([id]) => id);\n}\n"]}
1
+ {"version":3,"file":"buttons.d.ts","sourceRoot":"","sources":["../../src/gateway/buttons.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,EAAE,KAAK,aAAa,EAAwC,MAAM,aAAa,CAAC;AAEvF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAc,aAAa,EAAE,eAAe,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAUrH,MAAM,WAAW,UAAU;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,mBAAmB,GAC5B;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC5B;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,UAAU,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7E,2EAA2E;AAC3E,qBAAa,sBAAuB,SAAQ,KAAK;CAAG;AAEpD,MAAM,WAAW,UAAU;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,OAAO,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC/D,QAAQ,CAAC,IAAI,OAAO,CAAC;IACrB,QAAQ,CAAC,IAAI,IAAI,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;CACxD;AAgBD,MAAM,WAAW,UAAU;IAC1B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACvC,GAAG,EAAE,aAAa,CAAC;IACnB,OAAO,EAAE,YAAY,CAAC;IACtB,MAAM,EAAE,UAAU,CAAC;CACnB;AAKD,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,sFAAsF;AACtF,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAO1D;AA6GD;;;GAGG;AACH,wBAAsB,YAAY,CACjC,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE,aAAa,EACrB,IAAI,EAAE,UAAU,EAChB,IAAI,CAAC,EAAE,WAAW,GAChB,OAAO,CAAC,UAAU,CAAC,CAgCrB;AAcD,6DAA6D;AAC7D,wBAAsB,cAAc,CACnC,EAAE,EAAE,aAAa,EACjB,IAAI,EAAE,UAAU,GAAG;IAAE,OAAO,EAAE,eAAe,CAAA;CAAE,GAC7C,OAAO,CAAC,IAAI,CAAC,CAwHf;AAcD,kDAAkD;AAClD,wBAAgB,kBAAkB,IAAI,IAAI,CAoBzC;AAED,4BAA4B;AAC5B,wBAAgB,iBAAiB,IAAI,IAAI,CAKxC;AAED,0DAA0D;AAC1D,wBAAgB,mBAAmB,IAAI,IAAI,CAG1C;AAED,2CAA2C;AAC3C,wBAAgB,eAAe,IAAI,MAAM,EAAE,CAE1C","sourcesContent":["/**\n * lunR: gateway inline-button picker registry.\n *\n * Single registry for all interactive prompts (model/thinking/sessions pickers\n * now; approvals later). Callback payloads stay tiny so they fit Telegram's\n * 64-byte callback_data limit; all picker state lives server-side in this module.\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport { existsSync } from \"node:fs\";\nimport { isAuthorized } from \"./authz.ts\";\nimport { type GatewayConfig, gatewayConfigPath, loadGatewayConfig } from \"./config.ts\";\nimport { conversationBinding } from \"./conversations.ts\";\nimport type { PairingStore } from \"./pairing.ts\";\nimport type { BridgeLike } from \"./router.ts\";\nimport type { ButtonSpec, CallbackEvent, PlatformAdapter, SendOptions, SendResult, SessionSource } from \"./types.ts\";\n\nconst PICKER_TTL_MS = 15 * 60 * 1000;\nconst SWEEP_INTERVAL_MS = 60 * 1000;\nconst DEFAULT_PER_PAGE = 8;\nconst BUTTONS_PER_ROW = 2;\nconst CODE_ALPHABET = \"ABCDEFGHJKMNPQRSTUVWXYZ23456789\";\nconst CODE_LENGTH = 8;\nconst CALLBACK_PREFIX = \"cp:\";\n\nexport interface PickerItem {\n\tlabel: string;\n\tvalue: string;\n}\n\nexport type PickerResolveResult =\n\t| { done: true; text: string }\n\t| { done: false; items: PickerItem[]; title: string; breadcrumbs?: string };\n\n/** An awaited control was stopped or superseded before it could commit. */\nexport class GatewayPickerCancelled extends Error {}\n\nexport interface PickerSpec {\n\tgeneration?: number;\n\tkind: \"model\" | \"thinking\" | \"sessions\" | \"project\" | \"dialog\";\n\tvalidate?(): boolean;\n\tonCancel?(): void;\n\tsessionKey: string;\n\tinvokerId: string;\n\titems: PickerItem[];\n\tperPage?: number;\n\ttitle: string;\n\tbreadcrumbs?: string;\n\tresolve(item: PickerItem): Promise<PickerResolveResult>;\n}\n\ninterface PendingPicker extends PickerSpec {\n\tgeneration: number;\n\tresolving?: boolean;\n\tcompleted?: string;\n\tid: string;\n\tchatId: string;\n\tmessageId: string;\n\tsource: SessionSource;\n\tadapter: PlatformAdapter;\n\tpage: number;\n\tperPage: number;\n\tcreatedAt: number;\n}\n\nexport interface ButtonDeps {\n\tadapters: Map<string, PlatformAdapter>;\n\tcfg: GatewayConfig;\n\tpairing: PairingStore;\n\tbridge: BridgeLike;\n}\n\nconst registry = new Map<string, PendingPicker>();\nconst generations = new Map<string, number>();\n\nexport function gatewayPickerGeneration(key: string): number {\n\treturn generations.get(key) ?? 0;\n}\n\n/** Cancel pending controls when their conversation changes or the user stops them. */\nexport function invalidateGatewayPickers(key: string): void {\n\tgenerations.set(key, gatewayPickerGeneration(key) + 1);\n\tfor (const [id, picker] of registry) {\n\t\tif (picker.sessionKey !== key || picker.completed !== undefined) continue;\n\t\tregistry.delete(id);\n\t\tpicker.onCancel?.();\n\t}\n}\n\nfunction pickerRequesterValid(picker: PendingPicker): boolean {\n\tconst binding = conversationBinding(picker.sessionKey);\n\treturn (\n\t\t!binding ||\n\t\t((!binding.owner || binding.owner === picker.invokerId) &&\n\t\t\tbinding.source.platform === picker.source.platform &&\n\t\t\tbinding.source.chatId === picker.source.chatId &&\n\t\t\tbinding.source.userId === picker.invokerId &&\n\t\t\tbinding.source.threadId === picker.source.threadId)\n\t);\n}\n\nfunction pickerValid(picker: PendingPicker): boolean {\n\treturn (\n\t\tpicker.generation === gatewayPickerGeneration(picker.sessionKey) &&\n\t\tpickerRequesterValid(picker) &&\n\t\t(!picker.validate || picker.validate())\n\t);\n}\n\nlet sweeper: ReturnType<typeof setInterval> | undefined;\n\nfunction generatePickerId(): string {\n\tlet id = \"\";\n\tconst bytes = randomBytes(CODE_LENGTH);\n\tfor (let i = 0; i < CODE_LENGTH; i++) {\n\t\tid += CODE_ALPHABET[bytes[i] % CODE_ALPHABET.length];\n\t}\n\treturn id;\n}\n\nfunction makeCallbackData(id: string, action: \"select\" | \"page\" | \"cancel\" | \"noop\", payload?: string): string {\n\tif (action === \"noop\") return `${CALLBACK_PREFIX}${id}:`;\n\tif (action === \"cancel\") return `${CALLBACK_PREFIX}${id}:x`;\n\tif (action === \"page\") return `${CALLBACK_PREFIX}${id}:p${payload ?? \"0\"}`;\n\treturn `${CALLBACK_PREFIX}${id}:${payload ?? \"\"}`;\n}\n\nfunction parseCallbackData(\n\tdata: string,\n): { id: string; action: \"select\" | \"page\" | \"cancel\" | \"noop\"; payload?: string } | null {\n\tif (!data.startsWith(CALLBACK_PREFIX)) return null;\n\tconst rest = data.slice(CALLBACK_PREFIX.length);\n\tconst colon = rest.indexOf(\":\");\n\tif (colon === -1) return null;\n\tconst id = rest.slice(0, colon);\n\tconst tail = rest.slice(colon + 1);\n\tif (tail === \"\") return { id, action: \"noop\" };\n\tif (tail === \"x\") return { id, action: \"cancel\" };\n\tif (tail.startsWith(\"p\")) return { id, action: \"page\", payload: tail.slice(1) };\n\treturn { id, action: \"select\", payload: tail };\n}\n\nfunction isSameChat(a: SessionSource, b: SessionSource): boolean {\n\treturn a.platform === b.platform && a.chatId === b.chatId && a.threadId === b.threadId;\n}\n\nfunction sourceForAuth(picker: PendingPicker, cb: CallbackEvent): SessionSource {\n\treturn {\n\t\tplatform: picker.source.platform,\n\t\tchatId: cb.chatId,\n\t\tchatType: picker.source.chatType,\n\t\tuserId: cb.userId,\n\t\tuserName: cb.userName,\n\t\tthreadId: cb.threadId,\n\t\t// Do NOT inherit the original invoker's role authorization.\n\t\troleAuthorized: undefined,\n\t};\n}\n\nfunction renderPage(picker: PendingPicker): { text: string; buttons: ButtonSpec[][] } {\n\tconst { id, title, breadcrumbs, items, page, perPage } = picker;\n\tconst totalPages = Math.max(1, Math.ceil(items.length / perPage));\n\tconst clampedPage = Math.max(0, Math.min(page, totalPages - 1));\n\tconst start = clampedPage * perPage;\n\tconst pageItems = items.slice(start, start + perPage);\n\n\tconst rows: ButtonSpec[][] = [];\n\tfor (let i = 0; i < pageItems.length; i += BUTTONS_PER_ROW) {\n\t\tconst row: ButtonSpec[] = [];\n\t\tfor (let j = 0; j < BUTTONS_PER_ROW && i + j < pageItems.length; j++) {\n\t\t\tconst index = start + i + j;\n\t\t\trow.push({ label: pageItems[i + j].label, data: makeCallbackData(id, \"select\", String(index)) });\n\t\t}\n\t\trows.push(row);\n\t}\n\n\tif (totalPages > 1) {\n\t\tconst nav: ButtonSpec[] = [];\n\t\tif (clampedPage > 0) {\n\t\t\tnav.push({ label: \"◀ Prev\", data: makeCallbackData(id, \"page\", String(clampedPage - 1)) });\n\t\t}\n\t\tnav.push({ label: `${clampedPage + 1}/${totalPages}`, data: makeCallbackData(id, \"noop\") });\n\t\tif (clampedPage < totalPages - 1) {\n\t\t\tnav.push({ label: \"Next ▶\", data: makeCallbackData(id, \"page\", String(clampedPage + 1)) });\n\t\t}\n\t\trows.push(nav);\n\t}\n\n\trows.push([{ label: \"✗ Cancel\", data: makeCallbackData(id, \"cancel\") }]);\n\n\tconst header = breadcrumbs ? `${title} · ${breadcrumbs}` : title;\n\tconst text =\n\t\titems.length === 0 ? `${header}\\n\\nNo options available.` : `${header} (${clampedPage + 1}/${totalPages}):`;\n\treturn { text, buttons: rows };\n}\n\n/**\n * Create and send a paginated inline-button picker. On send failure the caller\n * should fall back to a text-based list.\n */\nexport async function createPicker(\n\tadapter: PlatformAdapter,\n\tsource: SessionSource,\n\tspec: PickerSpec,\n\topts?: SendOptions,\n): Promise<SendResult> {\n\tconst id = generatePickerId();\n\tconst pending: PendingPicker = {\n\t\t...spec,\n\t\tgeneration: spec.generation ?? gatewayPickerGeneration(spec.sessionKey),\n\t\tid,\n\t\tchatId: source.chatId,\n\t\tsource,\n\t\tadapter,\n\t\tmessageId: \"\",\n\t\tpage: 0,\n\t\tperPage: spec.perPage ?? DEFAULT_PER_PAGE,\n\t\tcreatedAt: Date.now(),\n\t};\n\tif (!pickerValid(pending)) return { success: false, error: \"Picker expired.\" };\n\tif (\n\t\tsource.userId !== spec.invokerId ||\n\t\t(existsSync(gatewayConfigPath()) && !isAuthorized(source, loadGatewayConfig()))\n\t)\n\t\treturn { success: false, error: \"Gateway access is no longer approved.\" };\n\tconst { text, buttons } = renderPage(pending);\n\tconst result = await adapter.sendButtons(source.chatId, text, buttons, opts);\n\tif (!result.success) return result;\n\tif (!pickerValid(pending)) {\n\t\tpending.onCancel?.();\n\t\treturn result;\n\t}\n\tif (result.messageId) {\n\t\tpending.messageId = result.messageId;\n\t\tregistry.set(id, pending);\n\t}\n\treturn result;\n}\n\nasync function answerExpired(adapter: PlatformAdapter, cb: CallbackEvent): Promise<void> {\n\tawait adapter.answerCallback(cb.id, \"Picker expired — run the command again.\").catch(() => {});\n}\n\nasync function answerUnauthorized(adapter: PlatformAdapter, cb: CallbackEvent): Promise<void> {\n\tawait adapter.answerCallback(cb.id, \"⛔ Not authorized.\").catch(() => {});\n}\n\nasync function answerNotYourPicker(adapter: PlatformAdapter, cb: CallbackEvent): Promise<void> {\n\tawait adapter.answerCallback(cb.id, \"⛔ Not your picker.\").catch(() => {});\n}\n\n/** Dispatch a CallbackEvent to the matching picker entry. */\nexport async function handleCallback(\n\tcb: CallbackEvent,\n\tdeps: ButtonDeps & { adapter: PlatformAdapter },\n): Promise<void> {\n\tconst { adapter, pairing } = deps;\n\tconst freshConfig = () => (existsSync(gatewayConfigPath()) ? loadGatewayConfig() : deps.cfg);\n\tconst parsed = parseCallbackData(cb.data);\n\tif (!parsed) return;\n\n\tconst picker = registry.get(parsed.id);\n\tif (!picker) {\n\t\tawait answerExpired(adapter, cb);\n\t\treturn;\n\t}\n\n\tif (picker.completed === undefined && !pickerValid(picker)) {\n\t\tregistry.delete(parsed.id);\n\t\tpicker.onCancel?.();\n\t\tawait answerExpired(adapter, cb);\n\t\treturn;\n\t}\n\n\tif (adapter.platform !== picker.source.platform || !isSameChat(picker.source, sourceForAuth(picker, cb))) {\n\t\tawait answerExpired(adapter, cb);\n\t\treturn;\n\t}\n\n\tif (cb.userId !== picker.invokerId) {\n\t\tawait answerNotYourPicker(adapter, cb);\n\t\treturn;\n\t}\n\n\tif (!isAuthorized(sourceForAuth(picker, cb), freshConfig(), pairing)) {\n\t\tawait answerUnauthorized(adapter, cb);\n\t\treturn;\n\t}\n\n\tconst authorized = () =>\n\t\tpickerRequesterValid(picker) && isAuthorized(sourceForAuth(picker, cb), freshConfig(), pairing);\n\tif (Date.now() > picker.createdAt + PICKER_TTL_MS) {\n\t\tif (!picker.completed) picker.onCancel?.();\n\t\tregistry.delete(parsed.id);\n\t\ttry {\n\t\t\tawait picker.adapter.editMessage(picker.chatId, picker.messageId, \"⏱ Expired — run the command again.\", []);\n\t\t} catch {\n\t\t\t// best-effort expiry message\n\t\t}\n\t\tawait answerExpired(adapter, cb);\n\t\treturn;\n\t}\n\n\tif (picker.completed !== undefined) {\n\t\tawait adapter.answerCallback(cb.id, \"Already completed.\").catch(() => {});\n\t\treturn;\n\t}\n\n\tif (parsed.action === \"cancel\") {\n\t\tregistry.delete(parsed.id);\n\t\tpicker.onCancel?.();\n\t\ttry {\n\t\t\tawait picker.adapter.editMessage(picker.chatId, picker.messageId, \"Cancelled.\", []);\n\t\t} catch {}\n\t\tawait adapter.answerCallback(cb.id).catch(() => {});\n\t\treturn;\n\t}\n\n\tif (parsed.action === \"noop\") {\n\t\tawait adapter.answerCallback(cb.id).catch(() => {});\n\t\treturn;\n\t}\n\n\tif (parsed.action === \"page\") {\n\t\tconst page = Number.parseInt(parsed.payload ?? \"0\", 10);\n\t\tconst totalPages = Math.max(1, Math.ceil(picker.items.length / picker.perPage));\n\t\tpicker.page = Math.max(0, Math.min(page, totalPages - 1));\n\t\tconst { text, buttons } = renderPage(picker);\n\t\ttry {\n\t\t\tawait picker.adapter.editMessage(picker.chatId, picker.messageId, text, buttons);\n\t\t} catch {}\n\t\tawait adapter.answerCallback(cb.id).catch(() => {});\n\t\treturn;\n\t}\n\n\t// select\n\tconst index = Number.parseInt(parsed.payload ?? \"\", 10);\n\tconst item = picker.items[index];\n\tif (!item) {\n\t\tawait adapter.answerCallback(cb.id, \"Invalid selection.\").catch(() => {});\n\t\treturn;\n\t}\n\n\tif (picker.resolving) {\n\t\tawait adapter.answerCallback(cb.id, \"Selection is still running.\").catch(() => {});\n\t\treturn;\n\t}\n\tpicker.resolving = true;\n\tawait adapter.answerCallback(cb.id).catch(() => {});\n\ttry {\n\t\t// A callback acknowledgement can yield to stop, replacement or revocation.\n\t\tif (!pickerValid(picker) || !authorized()) return;\n\t\tconst result = await picker.resolve(item);\n\t\tif (!result.done) {\n\t\t\tif (!pickerValid(picker) || !authorized()) return;\n\t\t\tpicker.items = result.items;\n\t\t\tpicker.title = result.title;\n\t\t\tpicker.breadcrumbs = result.breadcrumbs;\n\t\t\tpicker.page = 0;\n\t\t\tconst { text, buttons } = renderPage(picker);\n\t\t\ttry {\n\t\t\t\tawait picker.adapter.editMessage(picker.chatId, picker.messageId, text, buttons);\n\t\t\t} catch {}\n\t\t\treturn;\n\t\t}\n\t\tpicker.completed = result.text;\n\t\tawait confirmPicker(picker, result.text, authorized);\n\t} catch (err) {\n\t\tif (err instanceof GatewayPickerCancelled) return;\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tpicker.completed = `⚠ ${message}`;\n\t\tawait confirmPicker(picker, picker.completed, authorized);\n\t} finally {\n\t\tpicker.resolving = false;\n\t}\n}\n\nasync function confirmPicker(picker: PendingPicker, text: string, authorized: () => boolean): Promise<void> {\n\tif (!authorized()) return;\n\tconst edited = await picker.adapter\n\t\t.editMessage(picker.chatId, picker.messageId, text, [])\n\t\t.catch(() => ({ success: false }));\n\tif (edited.success || !authorized()) return;\n\tconst sent = await picker.adapter\n\t\t.send(picker.chatId, text, { threadId: picker.source.threadId })\n\t\t.catch(() => ({ success: false }));\n\tif (!sent.success) console.error(`[gateway] picker confirmation failed on ${picker.adapter.platform}`);\n}\n\n/** Start the periodic TTL sweeper. Idempotent. */\nexport function startButtonSweeper(): void {\n\tif (sweeper) return;\n\tsweeper = setInterval(() => {\n\t\tconst cutoff = Date.now() - PICKER_TTL_MS;\n\t\tfor (const [id, entry] of registry) {\n\t\t\tif (entry.createdAt > cutoff) continue;\n\t\t\tregistry.delete(id);\n\t\t\tif (entry.completed) continue;\n\t\t\tentry.onCancel?.();\n\t\t\tif (\n\t\t\t\t!pickerRequesterValid(entry) ||\n\t\t\t\t!isAuthorized({ ...entry.source, roleAuthorized: undefined }, loadGatewayConfig())\n\t\t\t)\n\t\t\t\tcontinue;\n\t\t\tvoid entry.adapter\n\t\t\t\t.editMessage(entry.chatId, entry.messageId, \"⏱ Expired — run the command again.\", [])\n\t\t\t\t.catch(() => {});\n\t\t}\n\t}, SWEEP_INTERVAL_MS);\n\tsweeper.unref?.();\n}\n\n/** Stop the TTL sweeper. */\nexport function stopButtonSweeper(): void {\n\tif (sweeper) {\n\t\tclearInterval(sweeper);\n\t\tsweeper = undefined;\n\t}\n}\n\n/** Test hook: reset the registry and stop the sweeper. */\nexport function resetButtonRegistry(): void {\n\tregistry.clear();\n\tstopButtonSweeper();\n}\n\n/** Test hook: expose active picker ids. */\nexport function activePickerIds(): string[] {\n\treturn [...registry].filter(([, picker]) => picker.completed === undefined).map(([id]) => id);\n}\n"]}
@@ -6,7 +6,10 @@
6
6
  * 64-byte callback_data limit; all picker state lives server-side in this module.
7
7
  */
8
8
  import { randomBytes } from "node:crypto";
9
+ import { existsSync } from "node:fs";
9
10
  import { isAuthorized } from "./authz.js";
11
+ import { gatewayConfigPath, loadGatewayConfig } from "./config.js";
12
+ import { conversationBinding } from "./conversations.js";
10
13
  const PICKER_TTL_MS = 15 * 60 * 1000;
11
14
  const SWEEP_INTERVAL_MS = 60 * 1000;
12
15
  const DEFAULT_PER_PAGE = 8;
@@ -14,7 +17,38 @@ const BUTTONS_PER_ROW = 2;
14
17
  const CODE_ALPHABET = "ABCDEFGHJKMNPQRSTUVWXYZ23456789";
15
18
  const CODE_LENGTH = 8;
16
19
  const CALLBACK_PREFIX = "cp:";
20
+ /** An awaited control was stopped or superseded before it could commit. */
21
+ export class GatewayPickerCancelled extends Error {
22
+ }
17
23
  const registry = new Map();
24
+ const generations = new Map();
25
+ export function gatewayPickerGeneration(key) {
26
+ return generations.get(key) ?? 0;
27
+ }
28
+ /** Cancel pending controls when their conversation changes or the user stops them. */
29
+ export function invalidateGatewayPickers(key) {
30
+ generations.set(key, gatewayPickerGeneration(key) + 1);
31
+ for (const [id, picker] of registry) {
32
+ if (picker.sessionKey !== key || picker.completed !== undefined)
33
+ continue;
34
+ registry.delete(id);
35
+ picker.onCancel?.();
36
+ }
37
+ }
38
+ function pickerRequesterValid(picker) {
39
+ const binding = conversationBinding(picker.sessionKey);
40
+ return (!binding ||
41
+ ((!binding.owner || binding.owner === picker.invokerId) &&
42
+ binding.source.platform === picker.source.platform &&
43
+ binding.source.chatId === picker.source.chatId &&
44
+ binding.source.userId === picker.invokerId &&
45
+ binding.source.threadId === picker.source.threadId));
46
+ }
47
+ function pickerValid(picker) {
48
+ return (picker.generation === gatewayPickerGeneration(picker.sessionKey) &&
49
+ pickerRequesterValid(picker) &&
50
+ (!picker.validate || picker.validate()));
51
+ }
18
52
  let sweeper;
19
53
  function generatePickerId() {
20
54
  let id = "";
@@ -104,6 +138,7 @@ export async function createPicker(adapter, source, spec, opts) {
104
138
  const id = generatePickerId();
105
139
  const pending = {
106
140
  ...spec,
141
+ generation: spec.generation ?? gatewayPickerGeneration(spec.sessionKey),
107
142
  id,
108
143
  chatId: source.chatId,
109
144
  source,
@@ -113,10 +148,19 @@ export async function createPicker(adapter, source, spec, opts) {
113
148
  perPage: spec.perPage ?? DEFAULT_PER_PAGE,
114
149
  createdAt: Date.now(),
115
150
  };
151
+ if (!pickerValid(pending))
152
+ return { success: false, error: "Picker expired." };
153
+ if (source.userId !== spec.invokerId ||
154
+ (existsSync(gatewayConfigPath()) && !isAuthorized(source, loadGatewayConfig())))
155
+ return { success: false, error: "Gateway access is no longer approved." };
116
156
  const { text, buttons } = renderPage(pending);
117
157
  const result = await adapter.sendButtons(source.chatId, text, buttons, opts);
118
158
  if (!result.success)
119
159
  return result;
160
+ if (!pickerValid(pending)) {
161
+ pending.onCancel?.();
162
+ return result;
163
+ }
120
164
  if (result.messageId) {
121
165
  pending.messageId = result.messageId;
122
166
  registry.set(id, pending);
@@ -134,7 +178,8 @@ async function answerNotYourPicker(adapter, cb) {
134
178
  }
135
179
  /** Dispatch a CallbackEvent to the matching picker entry. */
136
180
  export async function handleCallback(cb, deps) {
137
- const { adapter, cfg, pairing } = deps;
181
+ const { adapter, pairing } = deps;
182
+ const freshConfig = () => (existsSync(gatewayConfigPath()) ? loadGatewayConfig() : deps.cfg);
138
183
  const parsed = parseCallbackData(cb.data);
139
184
  if (!parsed)
140
185
  return;
@@ -143,12 +188,25 @@ export async function handleCallback(cb, deps) {
143
188
  await answerExpired(adapter, cb);
144
189
  return;
145
190
  }
146
- if (!picker.completed && picker.validate && !picker.validate()) {
191
+ if (picker.completed === undefined && !pickerValid(picker)) {
147
192
  registry.delete(parsed.id);
148
193
  picker.onCancel?.();
149
194
  await answerExpired(adapter, cb);
150
195
  return;
151
196
  }
197
+ if (adapter.platform !== picker.source.platform || !isSameChat(picker.source, sourceForAuth(picker, cb))) {
198
+ await answerExpired(adapter, cb);
199
+ return;
200
+ }
201
+ if (cb.userId !== picker.invokerId) {
202
+ await answerNotYourPicker(adapter, cb);
203
+ return;
204
+ }
205
+ if (!isAuthorized(sourceForAuth(picker, cb), freshConfig(), pairing)) {
206
+ await answerUnauthorized(adapter, cb);
207
+ return;
208
+ }
209
+ const authorized = () => pickerRequesterValid(picker) && isAuthorized(sourceForAuth(picker, cb), freshConfig(), pairing);
152
210
  if (Date.now() > picker.createdAt + PICKER_TTL_MS) {
153
211
  if (!picker.completed)
154
212
  picker.onCancel?.();
@@ -162,18 +220,6 @@ export async function handleCallback(cb, deps) {
162
220
  await answerExpired(adapter, cb);
163
221
  return;
164
222
  }
165
- if (!isSameChat(picker.source, sourceForAuth(picker, cb))) {
166
- await answerExpired(adapter, cb);
167
- return;
168
- }
169
- if (cb.userId !== picker.invokerId) {
170
- await answerNotYourPicker(adapter, cb);
171
- return;
172
- }
173
- if (!isAuthorized(sourceForAuth(picker, cb), cfg, pairing)) {
174
- await answerUnauthorized(adapter, cb);
175
- return;
176
- }
177
223
  if (picker.completed !== undefined) {
178
224
  await adapter.answerCallback(cb.id, "Already completed.").catch(() => { });
179
225
  return;
@@ -218,8 +264,13 @@ export async function handleCallback(cb, deps) {
218
264
  picker.resolving = true;
219
265
  await adapter.answerCallback(cb.id).catch(() => { });
220
266
  try {
267
+ // A callback acknowledgement can yield to stop, replacement or revocation.
268
+ if (!pickerValid(picker) || !authorized())
269
+ return;
221
270
  const result = await picker.resolve(item);
222
271
  if (!result.done) {
272
+ if (!pickerValid(picker) || !authorized())
273
+ return;
223
274
  picker.items = result.items;
224
275
  picker.title = result.title;
225
276
  picker.breadcrumbs = result.breadcrumbs;
@@ -232,22 +283,26 @@ export async function handleCallback(cb, deps) {
232
283
  return;
233
284
  }
234
285
  picker.completed = result.text;
235
- await confirmPicker(picker, result.text);
286
+ await confirmPicker(picker, result.text, authorized);
236
287
  }
237
288
  catch (err) {
289
+ if (err instanceof GatewayPickerCancelled)
290
+ return;
238
291
  const message = err instanceof Error ? err.message : String(err);
239
292
  picker.completed = `⚠ ${message}`;
240
- await confirmPicker(picker, picker.completed);
293
+ await confirmPicker(picker, picker.completed, authorized);
241
294
  }
242
295
  finally {
243
296
  picker.resolving = false;
244
297
  }
245
298
  }
246
- async function confirmPicker(picker, text) {
299
+ async function confirmPicker(picker, text, authorized) {
300
+ if (!authorized())
301
+ return;
247
302
  const edited = await picker.adapter
248
303
  .editMessage(picker.chatId, picker.messageId, text, [])
249
304
  .catch(() => ({ success: false }));
250
- if (edited.success)
305
+ if (edited.success || !authorized())
251
306
  return;
252
307
  const sent = await picker.adapter
253
308
  .send(picker.chatId, text, { threadId: picker.source.threadId })
@@ -268,6 +323,9 @@ export function startButtonSweeper() {
268
323
  if (entry.completed)
269
324
  continue;
270
325
  entry.onCancel?.();
326
+ if (!pickerRequesterValid(entry) ||
327
+ !isAuthorized({ ...entry.source, roleAuthorized: undefined }, loadGatewayConfig()))
328
+ continue;
271
329
  void entry.adapter
272
330
  .editMessage(entry.chatId, entry.messageId, "⏱ Expired — run the command again.", [])
273
331
  .catch(() => { });