@amodalai/runtime 0.1.26 → 0.2.0

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 (347) hide show
  1. package/dist/src/__fixtures__/README.md +84 -0
  2. package/dist/src/__fixtures__/smoke-agent/amodal.json +11 -0
  3. package/dist/src/__fixtures__/smoke-agent/automations/test-auto.md +5 -0
  4. package/dist/src/__fixtures__/smoke-agent/connections/mock-api/access.json +11 -0
  5. package/dist/src/__fixtures__/smoke-agent/connections/mock-api/spec.json +4 -0
  6. package/dist/src/__fixtures__/smoke-agent/connections/mock-api/surface.md +9 -0
  7. package/dist/src/__fixtures__/smoke-agent/connections/mock-mcp/access.json +3 -0
  8. package/dist/src/__fixtures__/smoke-agent/connections/mock-mcp/spec.json +8 -0
  9. package/dist/src/__fixtures__/smoke-agent/evals/basic-eval.md +12 -0
  10. package/dist/src/__fixtures__/smoke-agent/knowledge/test-knowledge.md +3 -0
  11. package/dist/src/__fixtures__/smoke-agent/skills/test-skill/SKILL.md +11 -0
  12. package/dist/src/__fixtures__/smoke-agent/stores/test-items.json +11 -0
  13. package/dist/src/__fixtures__/smoke-agent/tools/echo_tool/handler.d.ts +18 -0
  14. package/dist/src/__fixtures__/smoke-agent/tools/echo_tool/handler.js +22 -0
  15. package/dist/src/__fixtures__/smoke-agent/tools/echo_tool/handler.js.map +1 -0
  16. package/dist/src/__fixtures__/smoke-agent/tools/echo_tool/tool.json +17 -0
  17. package/dist/src/__fixtures__/smoke.test.js +718 -0
  18. package/dist/src/__fixtures__/smoke.test.js.map +1 -0
  19. package/dist/src/__tests__/test-providers.d.ts +40 -0
  20. package/dist/src/__tests__/test-providers.js +61 -0
  21. package/dist/src/__tests__/test-providers.js.map +1 -0
  22. package/dist/src/agent/local-server.d.ts +3 -3
  23. package/dist/src/agent/local-server.js +213 -122
  24. package/dist/src/agent/local-server.js.map +1 -1
  25. package/dist/src/agent/loop-types.d.ts +175 -0
  26. package/dist/src/agent/loop-types.js +20 -0
  27. package/dist/src/agent/loop-types.js.map +1 -0
  28. package/dist/src/agent/loop.d.ts +31 -0
  29. package/dist/src/agent/loop.js +139 -0
  30. package/dist/src/agent/loop.js.map +1 -0
  31. package/dist/src/agent/loop.test.js +1030 -0
  32. package/dist/src/agent/loop.test.js.map +1 -0
  33. package/dist/src/agent/mcp-config.d.ts +28 -0
  34. package/dist/src/agent/mcp-config.js +57 -0
  35. package/dist/src/agent/mcp-config.js.map +1 -0
  36. package/dist/src/agent/page-builder.js +6 -1
  37. package/dist/src/agent/page-builder.js.map +1 -1
  38. package/dist/src/agent/proactive/proactive-runner.d.ts +24 -8
  39. package/dist/src/agent/proactive/proactive-runner.js +30 -32
  40. package/dist/src/agent/proactive/proactive-runner.js.map +1 -1
  41. package/dist/src/agent/proactive/proactive-runner.test.d.ts +1 -1
  42. package/dist/src/agent/proactive/proactive-runner.test.js +75 -87
  43. package/dist/src/agent/proactive/proactive-runner.test.js.map +1 -1
  44. package/dist/src/agent/routes/admin-chat.d.ts +15 -3
  45. package/dist/src/agent/routes/admin-chat.js +63 -18
  46. package/dist/src/agent/routes/admin-chat.js.map +1 -1
  47. package/dist/src/agent/routes/automations.js +5 -6
  48. package/dist/src/agent/routes/automations.js.map +1 -1
  49. package/dist/src/agent/routes/evals.d.ts +3 -2
  50. package/dist/src/agent/routes/evals.js +25 -12
  51. package/dist/src/agent/routes/evals.js.map +1 -1
  52. package/dist/src/agent/routes/files.js +7 -9
  53. package/dist/src/agent/routes/files.js.map +1 -1
  54. package/dist/src/agent/routes/inspect.d.ts +6 -2
  55. package/dist/src/agent/routes/inspect.js +31 -17
  56. package/dist/src/agent/routes/inspect.js.map +1 -1
  57. package/dist/src/agent/routes/inspect.test.js +18 -42
  58. package/dist/src/agent/routes/inspect.test.js.map +1 -1
  59. package/dist/src/agent/routes/stores.js +9 -12
  60. package/dist/src/agent/routes/stores.js.map +1 -1
  61. package/dist/src/agent/routes/task.d.ts +15 -3
  62. package/dist/src/agent/routes/task.js +16 -7
  63. package/dist/src/agent/routes/task.js.map +1 -1
  64. package/dist/src/agent/routes/task.test.d.ts +1 -1
  65. package/dist/src/agent/routes/task.test.js +70 -53
  66. package/dist/src/agent/routes/task.test.js.map +1 -1
  67. package/dist/src/agent/routes/webhooks.js +12 -3
  68. package/dist/src/agent/routes/webhooks.js.map +1 -1
  69. package/dist/src/agent/session-store.d.ts +11 -2
  70. package/dist/src/agent/session-store.js +1 -1
  71. package/dist/src/agent/session-store.js.map +1 -1
  72. package/dist/src/agent/snapshot-server.d.ts +2 -22
  73. package/dist/src/agent/snapshot-server.js +50 -27
  74. package/dist/src/agent/snapshot-server.js.map +1 -1
  75. package/dist/src/agent/states/compacting.d.ts +14 -0
  76. package/dist/src/agent/states/compacting.js +258 -0
  77. package/dist/src/agent/states/compacting.js.map +1 -0
  78. package/dist/src/agent/states/confirming.d.ts +10 -0
  79. package/dist/src/agent/states/confirming.js +76 -0
  80. package/dist/src/agent/states/confirming.js.map +1 -0
  81. package/dist/src/agent/states/dispatching.d.ts +18 -0
  82. package/dist/src/agent/states/dispatching.js +241 -0
  83. package/dist/src/agent/states/dispatching.js.map +1 -0
  84. package/dist/src/agent/states/executing.d.ts +21 -0
  85. package/dist/src/agent/states/executing.js +308 -0
  86. package/dist/src/agent/states/executing.js.map +1 -0
  87. package/dist/src/agent/states/streaming.d.ts +10 -0
  88. package/dist/src/agent/states/streaming.js +155 -0
  89. package/dist/src/agent/states/streaming.js.map +1 -0
  90. package/dist/src/agent/states/thinking.d.ts +13 -0
  91. package/dist/src/agent/states/thinking.js +233 -0
  92. package/dist/src/agent/states/thinking.js.map +1 -0
  93. package/dist/src/agent/token-estimate.d.ts +17 -0
  94. package/dist/src/agent/token-estimate.js +13 -0
  95. package/dist/src/agent/token-estimate.js.map +1 -0
  96. package/dist/src/agent/tool-executor-local.js +9 -18
  97. package/dist/src/agent/tool-executor-local.js.map +1 -1
  98. package/dist/src/agent/tool-executor-local.test.js +3 -5
  99. package/dist/src/agent/tool-executor-local.test.js.map +1 -1
  100. package/dist/src/api/create-agent.d.ts +15 -0
  101. package/dist/src/api/create-agent.js +137 -0
  102. package/dist/src/api/create-agent.js.map +1 -0
  103. package/dist/src/api/types.d.ts +68 -0
  104. package/dist/src/api/types.js +7 -0
  105. package/dist/src/api/types.js.map +1 -0
  106. package/dist/src/context/compiler.d.ts +13 -0
  107. package/dist/src/context/compiler.js +358 -0
  108. package/dist/src/context/compiler.js.map +1 -0
  109. package/dist/src/context/compiler.test.js +532 -0
  110. package/dist/src/context/compiler.test.js.map +1 -0
  111. package/dist/src/context/types.d.ts +110 -0
  112. package/dist/src/context/types.js +7 -0
  113. package/dist/src/context/types.js.map +1 -0
  114. package/dist/src/index.d.ts +33 -6
  115. package/dist/src/index.js +35 -21
  116. package/dist/src/index.js.map +1 -1
  117. package/dist/src/providers/create-provider.d.ts +23 -0
  118. package/dist/src/providers/create-provider.js +185 -0
  119. package/dist/src/providers/create-provider.js.map +1 -0
  120. package/dist/src/{agent/stores-e2e.test.d.ts → providers/create-provider.test.d.ts} +1 -1
  121. package/dist/src/providers/create-provider.test.js +95 -0
  122. package/dist/src/providers/create-provider.test.js.map +1 -0
  123. package/dist/src/providers/failover.d.ts +38 -0
  124. package/dist/src/providers/failover.js +147 -0
  125. package/dist/src/providers/failover.js.map +1 -0
  126. package/dist/src/providers/failover.test.d.ts +6 -0
  127. package/dist/src/providers/failover.test.js +169 -0
  128. package/dist/src/providers/failover.test.js.map +1 -0
  129. package/dist/src/providers/types.d.ts +110 -0
  130. package/dist/src/providers/types.js +7 -0
  131. package/dist/src/providers/types.js.map +1 -0
  132. package/dist/src/routes/ai-stream.d.ts +13 -10
  133. package/dist/src/routes/ai-stream.js +76 -41
  134. package/dist/src/routes/ai-stream.js.map +1 -1
  135. package/dist/src/routes/chat-new.test.d.ts +6 -0
  136. package/dist/src/routes/chat-new.test.js +107 -0
  137. package/dist/src/routes/chat-new.test.js.map +1 -0
  138. package/dist/src/routes/chat-stream-new.test.d.ts +6 -0
  139. package/dist/src/routes/chat-stream-new.test.js +135 -0
  140. package/dist/src/routes/chat-stream-new.test.js.map +1 -0
  141. package/dist/src/routes/chat-stream.d.ts +14 -4
  142. package/dist/src/routes/chat-stream.js +47 -29
  143. package/dist/src/routes/chat-stream.js.map +1 -1
  144. package/dist/src/routes/chat.d.ts +13 -4
  145. package/dist/src/routes/chat.js +60 -23
  146. package/dist/src/routes/chat.js.map +1 -1
  147. package/dist/src/routes/health.d.ts +3 -2
  148. package/dist/src/routes/health.js.map +1 -1
  149. package/dist/src/routes/route-helpers.d.ts +50 -0
  150. package/dist/src/routes/route-helpers.js +80 -0
  151. package/dist/src/routes/route-helpers.js.map +1 -0
  152. package/dist/src/routes/session-resolver.d.ts +72 -0
  153. package/dist/src/routes/session-resolver.js +123 -0
  154. package/dist/src/routes/session-resolver.js.map +1 -0
  155. package/dist/src/routes/session-resolver.test.d.ts +6 -0
  156. package/dist/src/routes/session-resolver.test.js +206 -0
  157. package/dist/src/routes/session-resolver.test.js.map +1 -0
  158. package/dist/src/routes/webhooks.d.ts +3 -1
  159. package/dist/src/routes/webhooks.js +12 -4
  160. package/dist/src/routes/webhooks.js.map +1 -1
  161. package/dist/src/security/permission-checker.d.ts +80 -0
  162. package/dist/src/security/permission-checker.js +75 -0
  163. package/dist/src/security/permission-checker.js.map +1 -0
  164. package/dist/src/security/permission-checker.test.d.ts +6 -0
  165. package/dist/src/security/permission-checker.test.js +208 -0
  166. package/dist/src/security/permission-checker.test.js.map +1 -0
  167. package/dist/src/server.d.ts +12 -11
  168. package/dist/src/server.js +44 -46
  169. package/dist/src/server.js.map +1 -1
  170. package/dist/src/server.test.d.ts +1 -1
  171. package/dist/src/server.test.js +6 -144
  172. package/dist/src/server.test.js.map +1 -1
  173. package/dist/src/session/manager.d.ts +98 -0
  174. package/dist/src/session/manager.js +364 -0
  175. package/dist/src/session/manager.js.map +1 -0
  176. package/dist/src/session/manager.test.d.ts +6 -0
  177. package/dist/src/session/manager.test.js +315 -0
  178. package/dist/src/session/manager.test.js.map +1 -0
  179. package/dist/src/session/session-builder.d.ts +71 -0
  180. package/dist/src/session/session-builder.js +364 -0
  181. package/dist/src/session/session-builder.js.map +1 -0
  182. package/dist/src/session/session-builder.test.d.ts +6 -0
  183. package/dist/src/session/session-builder.test.js +352 -0
  184. package/dist/src/session/session-builder.test.js.map +1 -0
  185. package/dist/src/session/store.d.ts +57 -0
  186. package/dist/src/session/store.js +167 -0
  187. package/dist/src/session/store.js.map +1 -0
  188. package/dist/src/session/store.test.d.ts +6 -0
  189. package/dist/src/session/store.test.js +145 -0
  190. package/dist/src/session/store.test.js.map +1 -0
  191. package/dist/src/session/stream-hooks.d.ts +39 -0
  192. package/dist/src/session/stream-hooks.js +7 -0
  193. package/dist/src/session/stream-hooks.js.map +1 -0
  194. package/dist/src/session/tool-context-factory.d.ts +60 -0
  195. package/dist/src/session/tool-context-factory.js +190 -0
  196. package/dist/src/session/tool-context-factory.js.map +1 -0
  197. package/dist/src/session/tool-context-factory.test.d.ts +6 -0
  198. package/dist/src/session/tool-context-factory.test.js +287 -0
  199. package/dist/src/session/tool-context-factory.test.js.map +1 -0
  200. package/dist/src/session/types.d.ts +188 -0
  201. package/dist/src/session/types.js +7 -0
  202. package/dist/src/session/types.js.map +1 -0
  203. package/dist/src/stores/drizzle-store-backend.d.ts +49 -0
  204. package/dist/src/stores/drizzle-store-backend.js +306 -0
  205. package/dist/src/stores/drizzle-store-backend.js.map +1 -0
  206. package/dist/src/stores/drizzle-store-backend.test.d.ts +6 -0
  207. package/dist/src/stores/drizzle-store-backend.test.js +215 -0
  208. package/dist/src/stores/drizzle-store-backend.test.js.map +1 -0
  209. package/dist/src/stores/index.d.ts +4 -0
  210. package/dist/src/stores/index.js +2 -0
  211. package/dist/src/stores/index.js.map +1 -1
  212. package/dist/src/stores/pglite-store-backend.d.ts +16 -19
  213. package/dist/src/stores/pglite-store-backend.js +85 -239
  214. package/dist/src/stores/pglite-store-backend.js.map +1 -1
  215. package/dist/src/stores/postgres-store-backend.d.ts +30 -0
  216. package/dist/src/stores/postgres-store-backend.js +100 -0
  217. package/dist/src/stores/postgres-store-backend.js.map +1 -0
  218. package/dist/src/stores/schema.d.ts +491 -0
  219. package/dist/src/stores/schema.js +57 -0
  220. package/dist/src/stores/schema.js.map +1 -0
  221. package/dist/src/tools/admin-file-tools.d.ts +13 -0
  222. package/dist/src/tools/admin-file-tools.js +200 -0
  223. package/dist/src/tools/admin-file-tools.js.map +1 -0
  224. package/dist/src/tools/admin-file-tools.test.d.ts +6 -0
  225. package/dist/src/tools/admin-file-tools.test.js +152 -0
  226. package/dist/src/tools/admin-file-tools.test.js.map +1 -0
  227. package/dist/src/tools/custom-tool-adapter.d.ts +41 -0
  228. package/dist/src/tools/custom-tool-adapter.js +190 -0
  229. package/dist/src/tools/custom-tool-adapter.js.map +1 -0
  230. package/dist/src/tools/custom-tool-adapter.test.d.ts +6 -0
  231. package/dist/src/tools/custom-tool-adapter.test.js +244 -0
  232. package/dist/src/tools/custom-tool-adapter.test.js.map +1 -0
  233. package/dist/src/tools/dispatch-tool.d.ts +52 -0
  234. package/dist/src/tools/dispatch-tool.js +71 -0
  235. package/dist/src/tools/dispatch-tool.js.map +1 -0
  236. package/dist/src/tools/dispatch-tool.test.d.ts +6 -0
  237. package/dist/src/tools/dispatch-tool.test.js +75 -0
  238. package/dist/src/tools/dispatch-tool.test.js.map +1 -0
  239. package/dist/src/tools/mcp-tool-adapter.d.ts +18 -0
  240. package/dist/src/tools/mcp-tool-adapter.js +135 -0
  241. package/dist/src/tools/mcp-tool-adapter.js.map +1 -0
  242. package/dist/src/tools/mcp-tool-adapter.test.d.ts +6 -0
  243. package/dist/src/tools/mcp-tool-adapter.test.js +227 -0
  244. package/dist/src/tools/mcp-tool-adapter.test.js.map +1 -0
  245. package/dist/src/tools/registry.d.ts +25 -0
  246. package/dist/src/tools/registry.js +72 -0
  247. package/dist/src/tools/registry.js.map +1 -0
  248. package/dist/src/tools/registry.test.d.ts +6 -0
  249. package/dist/src/tools/registry.test.js +121 -0
  250. package/dist/src/tools/registry.test.js.map +1 -0
  251. package/dist/src/tools/request-tool.d.ts +42 -0
  252. package/dist/src/tools/request-tool.js +190 -0
  253. package/dist/src/tools/request-tool.js.map +1 -0
  254. package/dist/src/tools/request-tool.test.d.ts +6 -0
  255. package/dist/src/tools/request-tool.test.js +254 -0
  256. package/dist/src/tools/request-tool.test.js.map +1 -0
  257. package/dist/src/tools/store-tools.d.ts +29 -0
  258. package/dist/src/tools/store-tools.js +224 -0
  259. package/dist/src/tools/store-tools.js.map +1 -0
  260. package/dist/src/tools/store-tools.test.d.ts +6 -0
  261. package/dist/src/tools/store-tools.test.js +216 -0
  262. package/dist/src/tools/store-tools.test.js.map +1 -0
  263. package/dist/src/tools/types.d.ts +111 -0
  264. package/dist/src/tools/types.js +7 -0
  265. package/dist/src/tools/types.js.map +1 -0
  266. package/dist/src/types.d.ts +20 -12
  267. package/dist/src/types.js +3 -2
  268. package/dist/src/types.js.map +1 -1
  269. package/dist/tsconfig.tsbuildinfo +1 -1
  270. package/package.json +13 -4
  271. package/dist/src/__tests__/sse-contract.test.js +0 -464
  272. package/dist/src/__tests__/sse-contract.test.js.map +0 -1
  273. package/dist/src/__tests__/tools.test.js +0 -583
  274. package/dist/src/__tests__/tools.test.js.map +0 -1
  275. package/dist/src/agent/agent-runner.d.ts +0 -33
  276. package/dist/src/agent/agent-runner.js +0 -1040
  277. package/dist/src/agent/agent-runner.js.map +0 -1
  278. package/dist/src/agent/custom-tools-e2e.test.d.ts +0 -6
  279. package/dist/src/agent/custom-tools-e2e.test.js +0 -566
  280. package/dist/src/agent/custom-tools-e2e.test.js.map +0 -1
  281. package/dist/src/agent/request-helper.d.ts +0 -16
  282. package/dist/src/agent/request-helper.js +0 -96
  283. package/dist/src/agent/request-helper.js.map +0 -1
  284. package/dist/src/agent/stores-e2e.test.js +0 -433
  285. package/dist/src/agent/stores-e2e.test.js.map +0 -1
  286. package/dist/src/agent/tool-context-builder.d.ts +0 -11
  287. package/dist/src/agent/tool-context-builder.js +0 -102
  288. package/dist/src/agent/tool-context-builder.js.map +0 -1
  289. package/dist/src/agent/tool-context-builder.test.d.ts +0 -6
  290. package/dist/src/agent/tool-context-builder.test.js +0 -152
  291. package/dist/src/agent/tool-context-builder.test.js.map +0 -1
  292. package/dist/src/agent/write-repo-file.test.js +0 -270
  293. package/dist/src/agent/write-repo-file.test.js.map +0 -1
  294. package/dist/src/cron/heartbeat-runner.d.ts +0 -21
  295. package/dist/src/cron/heartbeat-runner.js +0 -79
  296. package/dist/src/cron/heartbeat-runner.js.map +0 -1
  297. package/dist/src/cron/heartbeat-runner.test.d.ts +0 -6
  298. package/dist/src/cron/heartbeat-runner.test.js +0 -120
  299. package/dist/src/cron/heartbeat-runner.test.js.map +0 -1
  300. package/dist/src/cron/heartbeat-scheduler.d.ts +0 -26
  301. package/dist/src/cron/heartbeat-scheduler.js +0 -55
  302. package/dist/src/cron/heartbeat-scheduler.js.map +0 -1
  303. package/dist/src/cron/heartbeat-scheduler.test.d.ts +0 -6
  304. package/dist/src/cron/heartbeat-scheduler.test.js +0 -61
  305. package/dist/src/cron/heartbeat-scheduler.test.js.map +0 -1
  306. package/dist/src/routes/ai-stream.test.d.ts +0 -6
  307. package/dist/src/routes/ai-stream.test.js +0 -586
  308. package/dist/src/routes/ai-stream.test.js.map +0 -1
  309. package/dist/src/routes/ask-user-response.d.ts +0 -30
  310. package/dist/src/routes/ask-user-response.js +0 -61
  311. package/dist/src/routes/ask-user-response.js.map +0 -1
  312. package/dist/src/routes/ask-user-response.test.d.ts +0 -6
  313. package/dist/src/routes/ask-user-response.test.js +0 -88
  314. package/dist/src/routes/ask-user-response.test.js.map +0 -1
  315. package/dist/src/routes/chat-stream.test.d.ts +0 -6
  316. package/dist/src/routes/chat-stream.test.js +0 -155
  317. package/dist/src/routes/chat-stream.test.js.map +0 -1
  318. package/dist/src/routes/chat.test.d.ts +0 -6
  319. package/dist/src/routes/chat.test.js +0 -99
  320. package/dist/src/routes/chat.test.js.map +0 -1
  321. package/dist/src/routes/widget-actions.d.ts +0 -49
  322. package/dist/src/routes/widget-actions.js +0 -78
  323. package/dist/src/routes/widget-actions.js.map +0 -1
  324. package/dist/src/session/custom-tool-adapter.d.ts +0 -74
  325. package/dist/src/session/custom-tool-adapter.js +0 -180
  326. package/dist/src/session/custom-tool-adapter.js.map +0 -1
  327. package/dist/src/session/history-converter.d.ts +0 -21
  328. package/dist/src/session/history-converter.js +0 -59
  329. package/dist/src/session/history-converter.js.map +0 -1
  330. package/dist/src/session/history-converter.test.d.ts +0 -6
  331. package/dist/src/session/history-converter.test.js +0 -130
  332. package/dist/src/session/history-converter.test.js.map +0 -1
  333. package/dist/src/session/session-manager.d.ts +0 -219
  334. package/dist/src/session/session-manager.js +0 -915
  335. package/dist/src/session/session-manager.js.map +0 -1
  336. package/dist/src/session/session-manager.test.d.ts +0 -6
  337. package/dist/src/session/session-manager.test.js +0 -455
  338. package/dist/src/session/session-manager.test.js.map +0 -1
  339. package/dist/src/session/session-runner.d.ts +0 -45
  340. package/dist/src/session/session-runner.js +0 -719
  341. package/dist/src/session/session-runner.js.map +0 -1
  342. package/dist/src/session/session-runner.test.d.ts +0 -6
  343. package/dist/src/session/session-runner.test.js +0 -834
  344. package/dist/src/session/session-runner.test.js.map +0 -1
  345. /package/dist/src/{__tests__/sse-contract.test.d.ts → __fixtures__/smoke.test.d.ts} +0 -0
  346. /package/dist/src/{__tests__/tools.test.d.ts → agent/loop.test.d.ts} +0 -0
  347. /package/dist/src/{agent/write-repo-file.test.d.ts → context/compiler.test.d.ts} +0 -0
@@ -0,0 +1,206 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
7
+ import { resolveBundle, resolveSession } from './session-resolver.js';
8
+ import { SessionError } from '../errors.js';
9
+ // ---------------------------------------------------------------------------
10
+ // Mock buildSessionComponents — we test the resolver, not the builder
11
+ // ---------------------------------------------------------------------------
12
+ const mockBuildSessionComponents = vi.fn();
13
+ vi.mock('../session/session-builder.js', () => ({
14
+ buildSessionComponents: (...args) => mockBuildSessionComponents(...args),
15
+ }));
16
+ // ---------------------------------------------------------------------------
17
+ // Fixtures
18
+ // ---------------------------------------------------------------------------
19
+ function stubBundle(name = 'test-agent') {
20
+ return {
21
+ config: { name, models: { main: { provider: 'test', model: 'test-model' } } },
22
+ connections: new Map(),
23
+ resolvedCredentials: {},
24
+ stores: [],
25
+ skills: [],
26
+ knowledge: [],
27
+ tools: [],
28
+ agents: { main: undefined },
29
+ };
30
+ }
31
+ function stubComponents() {
32
+ const factory = vi.fn();
33
+ return {
34
+ provider: { model: 'test-model', provider: 'test' },
35
+ toolRegistry: { size: 0 },
36
+ permissionChecker: { check: () => ({ allowed: true }) },
37
+ systemPrompt: 'test prompt',
38
+ toolContextFactory: factory,
39
+ userRoles: [],
40
+ };
41
+ }
42
+ function stubSession(id, hasFactory = true) {
43
+ return {
44
+ id,
45
+ tenantId: 'tenant-1',
46
+ userId: 'user-1',
47
+ model: 'test-model',
48
+ providerName: 'test',
49
+ toolContextFactory: hasFactory ? vi.fn() : undefined,
50
+ };
51
+ }
52
+ function stubSessionManager(overrides = {}) {
53
+ return {
54
+ get: vi.fn(),
55
+ resume: vi.fn(),
56
+ create: vi.fn(),
57
+ ...overrides,
58
+ };
59
+ }
60
+ function stubShared() {
61
+ return {
62
+ storeBackend: null,
63
+ mcpManager: null,
64
+ logger: { info: vi.fn(), warn: vi.fn(), debug: vi.fn(), error: vi.fn(), fatal: vi.fn(), child: vi.fn() },
65
+ };
66
+ }
67
+ // ---------------------------------------------------------------------------
68
+ // resolveBundle
69
+ // ---------------------------------------------------------------------------
70
+ describe('resolveBundle', () => {
71
+ it('returns static bundle when no deployId', async () => {
72
+ const bundle = stubBundle();
73
+ const result = await resolveBundle({ staticBundle: bundle });
74
+ expect(result).toBe(bundle);
75
+ });
76
+ it('returns null when no bundle available', async () => {
77
+ const result = await resolveBundle({});
78
+ expect(result).toBeNull();
79
+ });
80
+ it('calls bundleProvider when deployId is provided', async () => {
81
+ const bundle = stubBundle();
82
+ const provider = vi.fn().mockResolvedValue(bundle);
83
+ const result = await resolveBundle({ bundleProvider: provider }, 'deploy-1', 'token-abc');
84
+ expect(provider).toHaveBeenCalledWith('deploy-1', 'token-abc');
85
+ expect(result).toBe(bundle);
86
+ });
87
+ it('falls back to static bundle when no deployId even if bundleProvider exists', async () => {
88
+ const staticBundle = stubBundle('static');
89
+ const provider = vi.fn();
90
+ const result = await resolveBundle({ staticBundle, bundleProvider: provider });
91
+ expect(provider).not.toHaveBeenCalled();
92
+ expect(result).toBe(staticBundle);
93
+ });
94
+ });
95
+ // ---------------------------------------------------------------------------
96
+ // resolveSession
97
+ // ---------------------------------------------------------------------------
98
+ describe('resolveSession', () => {
99
+ beforeEach(() => {
100
+ vi.clearAllMocks();
101
+ mockBuildSessionComponents.mockReturnValue(stubComponents());
102
+ });
103
+ it('returns in-memory session with cached factory (no rebuild)', async () => {
104
+ const session = stubSession('sess-1');
105
+ const mgr = stubSessionManager({ get: vi.fn().mockReturnValue(session) });
106
+ const result = await resolveSession('sess-1', {
107
+ sessionManager: mgr,
108
+ bundleResolver: { staticBundle: stubBundle() },
109
+ shared: stubShared(),
110
+ });
111
+ expect(result.session).toBe(session);
112
+ expect(result.toolContextFactory).toBe(session.toolContextFactory);
113
+ expect(mockBuildSessionComponents).not.toHaveBeenCalled();
114
+ });
115
+ it('resumes from store when not in memory', async () => {
116
+ const resumed = stubSession('sess-2');
117
+ const mgr = stubSessionManager({
118
+ get: vi.fn().mockReturnValue(undefined),
119
+ resume: vi.fn().mockResolvedValue(resumed),
120
+ });
121
+ const result = await resolveSession('sess-2', {
122
+ sessionManager: mgr,
123
+ bundleResolver: { staticBundle: stubBundle() },
124
+ shared: stubShared(),
125
+ });
126
+ expect(result.session).toBe(resumed);
127
+ expect(mockBuildSessionComponents).toHaveBeenCalledOnce();
128
+ expect(mgr.resume).toHaveBeenCalledWith('sess-2', expect.objectContaining({ tenantId: expect.any(String) }));
129
+ });
130
+ it('creates new session when session_id not found', async () => {
131
+ const created = stubSession('sess-3');
132
+ const mgr = stubSessionManager({
133
+ get: vi.fn().mockReturnValue(undefined),
134
+ resume: vi.fn().mockResolvedValue(null),
135
+ create: vi.fn().mockReturnValue(created),
136
+ });
137
+ const result = await resolveSession('sess-not-found', {
138
+ sessionManager: mgr,
139
+ bundleResolver: { staticBundle: stubBundle() },
140
+ shared: stubShared(),
141
+ });
142
+ expect(result.session).toBe(created);
143
+ expect(mgr.create).toHaveBeenCalledOnce();
144
+ });
145
+ it('creates new session when no session_id provided', async () => {
146
+ const created = stubSession('sess-new');
147
+ const mgr = stubSessionManager({
148
+ create: vi.fn().mockReturnValue(created),
149
+ });
150
+ const result = await resolveSession(undefined, {
151
+ sessionManager: mgr,
152
+ bundleResolver: { staticBundle: stubBundle() },
153
+ shared: stubShared(),
154
+ });
155
+ expect(result.session).toBe(created);
156
+ expect(mgr.get).not.toHaveBeenCalled();
157
+ });
158
+ it('throws SessionError when no bundle available', async () => {
159
+ const mgr = stubSessionManager();
160
+ await expect(resolveSession(undefined, {
161
+ sessionManager: mgr,
162
+ bundleResolver: {},
163
+ shared: stubShared(),
164
+ })).rejects.toThrow(SessionError);
165
+ });
166
+ it('passes userRoles from role option', async () => {
167
+ const created = stubSession('sess-roles');
168
+ const mgr = stubSessionManager({
169
+ create: vi.fn().mockReturnValue(created),
170
+ });
171
+ await resolveSession(undefined, {
172
+ sessionManager: mgr,
173
+ bundleResolver: { staticBundle: stubBundle() },
174
+ shared: stubShared(),
175
+ role: 'analyst',
176
+ });
177
+ expect(mockBuildSessionComponents).toHaveBeenCalledWith(expect.objectContaining({ userRoles: ['analyst'] }));
178
+ });
179
+ it('passes auth context for tenantId and userId', async () => {
180
+ const created = stubSession('sess-auth');
181
+ const mgr = stubSessionManager({
182
+ create: vi.fn().mockReturnValue(created),
183
+ });
184
+ await resolveSession(undefined, {
185
+ sessionManager: mgr,
186
+ bundleResolver: { staticBundle: stubBundle() },
187
+ shared: stubShared(),
188
+ auth: { orgId: 'org-42', applicationId: 'app-1', authMethod: 'api_key', actor: 'user-99' },
189
+ });
190
+ expect(mgr.create).toHaveBeenCalledWith(expect.objectContaining({ tenantId: 'org-42', userId: 'user-99' }));
191
+ });
192
+ it('stores toolContextFactory on created session via CreateSessionOptions', async () => {
193
+ const created = stubSession('sess-factory');
194
+ const mockCreate = vi.fn().mockReturnValue(created);
195
+ const mgr = stubSessionManager({ create: mockCreate });
196
+ await resolveSession(undefined, {
197
+ sessionManager: mgr,
198
+ bundleResolver: { staticBundle: stubBundle() },
199
+ shared: stubShared(),
200
+ });
201
+ const createArg = mockCreate.mock.calls[0][0];
202
+ expect(createArg['toolContextFactory']).toBeDefined();
203
+ expect(typeof createArg['toolContextFactory']).toBe('function');
204
+ });
205
+ });
206
+ //# sourceMappingURL=session-resolver.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-resolver.test.js","sourceRoot":"","sources":["../../../src/routes/session-resolver.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAC,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAC,MAAM,QAAQ,CAAC;AAC5D,OAAO,EAAC,aAAa,EAAE,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAKpE,OAAO,EAAC,YAAY,EAAC,MAAM,cAAc,CAAC;AAE1C,8EAA8E;AAC9E,sEAAsE;AACtE,8EAA8E;AAE9E,MAAM,0BAA0B,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;AAC3C,EAAE,CAAC,IAAI,CAAC,+BAA+B,EAAE,GAAG,EAAE,CAAC,CAAC;IAC9C,sBAAsB,EAAE,CAAC,GAAG,IAAe,EAAE,EAAE,CAAC,0BAA0B,CAAC,GAAG,IAAI,CAAC;CACpF,CAAC,CAAC,CAAC;AAEJ,8EAA8E;AAC9E,WAAW;AACX,8EAA8E;AAE9E,SAAS,UAAU,CAAC,IAAI,GAAG,YAAY;IACrC,OAAO;QACL,MAAM,EAAE,EAAC,IAAI,EAAE,MAAM,EAAE,EAAC,IAAI,EAAE,EAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAC,EAAC,EAA0B;QAChG,WAAW,EAAE,IAAI,GAAG,EAAE;QACtB,mBAAmB,EAAE,EAAE;QACvB,MAAM,EAAE,EAAE;QACV,MAAM,EAAE,EAAE;QACV,SAAS,EAAE,EAAE;QACb,KAAK,EAAE,EAAE;QACT,MAAM,EAAE,EAAC,IAAI,EAAE,SAAS,EAAC;KACA,CAAC;AAC9B,CAAC;AAED,SAAS,cAAc;IACrB,MAAM,OAAO,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;IACxB,OAAO;QACL,QAAQ,EAAE,EAAC,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAC;QACjD,YAAY,EAAE,EAAC,IAAI,EAAE,CAAC,EAAC;QACvB,iBAAiB,EAAE,EAAC,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,EAAC,OAAO,EAAE,IAAa,EAAC,CAAC,EAAC;QAC5D,YAAY,EAAE,aAAa;QAC3B,kBAAkB,EAAE,OAAO;QAC3B,SAAS,EAAE,EAAE;KACd,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,EAAU,EAAE,UAAU,GAAG,IAAI;IAChD,OAAO;QACL,EAAE;QACF,QAAQ,EAAE,UAAU;QACpB,MAAM,EAAE,QAAQ;QAChB,KAAK,EAAE,YAAY;QACnB,YAAY,EAAE,MAAM;QACpB,kBAAkB,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS;KAC/B,CAAC;AAC1B,CAAC;AAED,SAAS,kBAAkB,CAAC,YAA8C,EAAE;IAC1E,OAAO;QACL,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE;QACZ,MAAM,EAAE,EAAE,CAAC,EAAE,EAAE;QACf,MAAM,EAAE,EAAE,CAAC,EAAE,EAAE;QACf,GAAG,SAAS;KAC0B,CAAC;AAC3C,CAAC;AAED,SAAS,UAAU;IACjB,OAAO;QACL,YAAY,EAAE,IAAI;QAClB,UAAU,EAAE,IAAI;QAChB,MAAM,EAAE,EAAC,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE,EAAyC;KAC/I,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,gBAAgB;AAChB,8EAA8E;AAE9E,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,EAAC,YAAY,EAAE,MAAM,EAAC,CAAC,CAAC;QAC3D,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;QACrD,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,EAAE,CAAC,CAAC;QACvC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,KAAK,IAAI,EAAE;QAC9D,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC;QACnD,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,EAAC,cAAc,EAAE,QAAQ,EAAC,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC;QACxF,MAAM,CAAC,QAAQ,CAAC,CAAC,oBAAoB,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;QAC/D,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4EAA4E,EAAE,KAAK,IAAI,EAAE;QAC1F,MAAM,YAAY,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;QAC1C,MAAM,QAAQ,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,EAAC,YAAY,EAAE,cAAc,EAAE,QAAQ,EAAC,CAAC,CAAC;QAC7E,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QACxC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,8EAA8E;AAC9E,iBAAiB;AACjB,8EAA8E;AAE9E,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,UAAU,CAAC,GAAG,EAAE;QACd,EAAE,CAAC,aAAa,EAAE,CAAC;QACnB,0BAA0B,CAAC,eAAe,CAAC,cAAc,EAAE,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4DAA4D,EAAE,KAAK,IAAI,EAAE;QAC1E,MAAM,OAAO,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;QACtC,MAAM,GAAG,GAAG,kBAAkB,CAAC,EAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,OAAO,CAAC,EAAC,CAAC,CAAC;QAExE,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,QAAQ,EAAE;YAC5C,cAAc,EAAE,GAAG;YACnB,cAAc,EAAE,EAAC,YAAY,EAAE,UAAU,EAAE,EAAC;YAC5C,MAAM,EAAE,UAAU,EAAE;SACrB,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACrC,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;QACnE,MAAM,CAAC,0BAA0B,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;QACrD,MAAM,OAAO,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;QACtC,MAAM,GAAG,GAAG,kBAAkB,CAAC;YAC7B,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,SAAS,CAAC;YACvC,MAAM,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,OAAO,CAAC;SAC3C,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,QAAQ,EAAE;YAC5C,cAAc,EAAE,GAAG;YACnB,cAAc,EAAE,EAAC,YAAY,EAAE,UAAU,EAAE,EAAC;YAC5C,MAAM,EAAE,UAAU,EAAE;SACrB,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACrC,MAAM,CAAC,0BAA0B,CAAC,CAAC,oBAAoB,EAAE,CAAC;QAC1D,MAAM,CAAE,GAAG,CAAC,MAAmC,CAAC,CAAC,oBAAoB,CACnE,QAAQ,EACR,MAAM,CAAC,gBAAgB,CAAC,EAAC,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAC,CAAC,CACxD,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,KAAK,IAAI,EAAE;QAC7D,MAAM,OAAO,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;QACtC,MAAM,GAAG,GAAG,kBAAkB,CAAC;YAC7B,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,SAAS,CAAC;YACvC,MAAM,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,IAAI,CAAC;YACvC,MAAM,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,OAAO,CAAC;SACzC,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,gBAAgB,EAAE;YACpD,cAAc,EAAE,GAAG;YACnB,cAAc,EAAE,EAAC,YAAY,EAAE,UAAU,EAAE,EAAC;YAC5C,MAAM,EAAE,UAAU,EAAE;SACrB,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACrC,MAAM,CAAE,GAAG,CAAC,MAAmC,CAAC,CAAC,oBAAoB,EAAE,CAAC;IAC1E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;QAC/D,MAAM,OAAO,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;QACxC,MAAM,GAAG,GAAG,kBAAkB,CAAC;YAC7B,MAAM,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,OAAO,CAAC;SACzC,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,SAAS,EAAE;YAC7C,cAAc,EAAE,GAAG;YACnB,cAAc,EAAE,EAAC,YAAY,EAAE,UAAU,EAAE,EAAC;YAC5C,MAAM,EAAE,UAAU,EAAE;SACrB,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACrC,MAAM,CAAE,GAAG,CAAC,GAAgC,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IACvE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;QAC5D,MAAM,GAAG,GAAG,kBAAkB,EAAE,CAAC;QAEjC,MAAM,MAAM,CACV,cAAc,CAAC,SAAS,EAAE;YACxB,cAAc,EAAE,GAAG;YACnB,cAAc,EAAE,EAAE;YAClB,MAAM,EAAE,UAAU,EAAE;SACrB,CAAC,CACH,CAAC,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mCAAmC,EAAE,KAAK,IAAI,EAAE;QACjD,MAAM,OAAO,GAAG,WAAW,CAAC,YAAY,CAAC,CAAC;QAC1C,MAAM,GAAG,GAAG,kBAAkB,CAAC;YAC7B,MAAM,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,OAAO,CAAC;SACzC,CAAC,CAAC;QAEH,MAAM,cAAc,CAAC,SAAS,EAAE;YAC9B,cAAc,EAAE,GAAG;YACnB,cAAc,EAAE,EAAC,YAAY,EAAE,UAAU,EAAE,EAAC;YAC5C,MAAM,EAAE,UAAU,EAAE;YACpB,IAAI,EAAE,SAAS;SAChB,CAAC,CAAC;QAEH,MAAM,CAAC,0BAA0B,CAAC,CAAC,oBAAoB,CACrD,MAAM,CAAC,gBAAgB,CAAC,EAAC,SAAS,EAAE,CAAC,SAAS,CAAC,EAAC,CAAC,CAClD,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;QAC3D,MAAM,OAAO,GAAG,WAAW,CAAC,WAAW,CAAC,CAAC;QACzC,MAAM,GAAG,GAAG,kBAAkB,CAAC;YAC7B,MAAM,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,OAAO,CAAC;SACzC,CAAC,CAAC;QAEH,MAAM,cAAc,CAAC,SAAS,EAAE;YAC9B,cAAc,EAAE,GAAG;YACnB,cAAc,EAAE,EAAC,YAAY,EAAE,UAAU,EAAE,EAAC;YAC5C,MAAM,EAAE,UAAU,EAAE;YACpB,IAAI,EAAE,EAAC,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAC;SACzF,CAAC,CAAC;QAEH,MAAM,CAAE,GAAG,CAAC,MAAmC,CAAC,CAAC,oBAAoB,CACnE,MAAM,CAAC,gBAAgB,CAAC,EAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAC,CAAC,CACjE,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uEAAuE,EAAE,KAAK,IAAI,EAAE;QACrF,MAAM,OAAO,GAAG,WAAW,CAAC,cAAc,CAAC,CAAC;QAC5C,MAAM,UAAU,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;QACpD,MAAM,GAAG,GAAG,kBAAkB,CAAC,EAAC,MAAM,EAAE,UAAU,EAAC,CAAC,CAAC;QAErD,MAAM,cAAc,CAAC,SAAS,EAAE;YAC9B,cAAc,EAAE,GAAG;YACnB,cAAc,EAAE,EAAC,YAAY,EAAE,UAAU,EAAE,EAAC;YAC5C,MAAM,EAAE,UAAU,EAAE;SACrB,CAAC,CAAC;QAEH,MAAM,SAAS,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAA4B,CAAC;QACzE,MAAM,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QACtD,MAAM,CAAC,OAAO,SAAS,CAAC,oBAAoB,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -5,9 +5,11 @@
5
5
  */
6
6
  import { Router } from 'express';
7
7
  import type { AutomationDefinition } from '@amodalai/core';
8
- import type { AutomationRunnerFn } from '../cron/heartbeat-runner.js';
8
+ import type { AutomationResult } from '../types.js';
9
+ type AutomationRunnerFn = (automation: AutomationDefinition, payload?: Record<string, unknown>) => Promise<AutomationResult>;
9
10
  export interface WebhookRouterOptions {
10
11
  automations: AutomationDefinition[];
11
12
  runAutomation: AutomationRunnerFn;
12
13
  }
13
14
  export declare function createWebhookRouter(options: WebhookRouterOptions): Router;
15
+ export {};
@@ -4,9 +4,11 @@
4
4
  * SPDX-License-Identifier: MIT
5
5
  */
6
6
  import { Router } from 'express';
7
+ import rateLimit from 'express-rate-limit';
7
8
  import { WebhookPayloadSchema } from '../types.js';
8
9
  import { validate } from '../middleware/request-validation.js';
9
10
  import { AppError } from '../middleware/error-handler.js';
11
+ import { asyncHandler } from './route-helpers.js';
10
12
  export function createWebhookRouter(options) {
11
13
  const router = Router();
12
14
  // Build a lookup map: webhook source name → automation definition
@@ -16,9 +18,15 @@ export function createWebhookRouter(options) {
16
18
  webhookAutomations.set(a.name, a);
17
19
  }
18
20
  }
19
- router.post('/webhooks/:name', validate(WebhookPayloadSchema),
20
- // eslint-disable-next-line @typescript-eslint/no-misused-promises -- TODO: wrap async route handler
21
- async (req, res, next) => {
21
+ // Rate-limit inbound webhooks per IP so validation and automation
22
+ // dispatch can't be triggered unboundedly by an attacker.
23
+ const webhookLimiter = rateLimit({
24
+ windowMs: 60 * 1000,
25
+ max: 100,
26
+ standardHeaders: true,
27
+ legacyHeaders: false,
28
+ });
29
+ router.post('/webhooks/:name', webhookLimiter, validate(WebhookPayloadSchema), asyncHandler(async (req, res, next) => {
22
30
  try {
23
31
  const { name } = req.params;
24
32
  if (!name) {
@@ -39,7 +47,7 @@ export function createWebhookRouter(options) {
39
47
  catch (err) {
40
48
  next(err);
41
49
  }
42
- });
50
+ }));
43
51
  return router;
44
52
  }
45
53
  //# sourceMappingURL=webhooks.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"webhooks.js","sourceRoot":"","sources":["../../../src/routes/webhooks.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEjC,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,qCAAqC,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAQ1D,MAAM,UAAU,mBAAmB,CAAC,OAA6B;IAC/D,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC;IAExB,kEAAkE;IAClE,MAAM,kBAAkB,GAAG,IAAI,GAAG,EAAgC,CAAC;IACnE,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;QACpC,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YACjC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IAED,MAAM,CAAC,IAAI,CACT,iBAAiB,EACjB,QAAQ,CAAC,oBAAoB,CAAC;IAC9B,oGAAoG;IACpG,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACvB,IAAI,CAAC;YACH,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC;YAC5B,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,MAAM,IAAI,QAAQ,CAAC,GAAG,EAAE,cAAc,EAAE,6BAA6B,CAAC,CAAC;YACzE,CAAC;YAED,MAAM,UAAU,GAAG,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAChD,IAAI,CAAC,UAAU,EAAE,CAAC;gBAChB,MAAM,IAAI,QAAQ,CAChB,GAAG,EACH,sBAAsB,EACtB,gCAAgC,IAAI,GAAG,CACxC,CAAC;YACJ,CAAC;YAED,0DAA0D;YAC1D,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;YAC9B,KAAK,OAAO,CAAC,aAAa,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;YAEhD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACnB,QAAQ,EAAE,IAAI;gBACd,UAAU,EAAE,IAAI;aACjB,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,GAAG,CAAC,CAAC;QACZ,CAAC;IACH,CAAC,CACF,CAAC;IAEF,OAAO,MAAM,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"webhooks.js","sourceRoot":"","sources":["../../../src/routes/webhooks.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,SAAS,MAAM,oBAAoB,CAAC;AAE3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,qCAAqC,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAC1D,OAAO,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAUhD,MAAM,UAAU,mBAAmB,CAAC,OAA6B;IAC/D,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC;IAExB,kEAAkE;IAClE,MAAM,kBAAkB,GAAG,IAAI,GAAG,EAAgC,CAAC;IACnE,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;QACpC,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YACjC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IAED,kEAAkE;IAClE,0DAA0D;IAC1D,MAAM,cAAc,GAAG,SAAS,CAAC;QAC/B,QAAQ,EAAE,EAAE,GAAG,IAAI;QACnB,GAAG,EAAE,GAAG;QACR,eAAe,EAAE,IAAI;QACrB,aAAa,EAAE,KAAK;KACrB,CAAC,CAAC;IAEH,MAAM,CAAC,IAAI,CACT,iBAAiB,EACjB,cAAc,EACd,QAAQ,CAAC,oBAAoB,CAAC,EAC9B,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACpC,IAAI,CAAC;YACH,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC;YAC5B,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,MAAM,IAAI,QAAQ,CAAC,GAAG,EAAE,cAAc,EAAE,6BAA6B,CAAC,CAAC;YACzE,CAAC;YAED,MAAM,UAAU,GAAG,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAChD,IAAI,CAAC,UAAU,EAAE,CAAC;gBAChB,MAAM,IAAI,QAAQ,CAChB,GAAG,EACH,sBAAsB,EACtB,gCAAgC,IAAI,GAAG,CACxC,CAAC;YACJ,CAAC;YAED,0DAA0D;YAC1D,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;YAC9B,KAAK,OAAO,CAAC,aAAa,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;YAEhD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACnB,QAAQ,EAAE,IAAI;gBACd,UAAU,EAAE,IAAI;aACjB,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,GAAG,CAAC,CAAC;QACZ,CAAC;IACH,CAAC,CAAC,CACH,CAAC;IAEF,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ /**
7
+ * Permission checker interface for tool execution.
8
+ *
9
+ * Extracted from the request tool so that the same permission pipeline
10
+ * can be used by the agent loop (confirmation flow), egress proxy
11
+ * (Roadmap 5.1), async approval (Roadmap 5.3), and PII detection
12
+ * (Roadmap 5.2).
13
+ *
14
+ * The default implementation reads from access.json via ActionGate.
15
+ * Future implementations can add external policy services, audit
16
+ * logging, or rate limiting.
17
+ */
18
+ import type { AccessConfig } from '@amodalai/types';
19
+ /**
20
+ * Result of a permission check on a tool call.
21
+ */
22
+ export type PermissionResult = {
23
+ allowed: true;
24
+ requiresConfirmation?: false;
25
+ } | {
26
+ allowed: true;
27
+ requiresConfirmation: true;
28
+ reason: string;
29
+ } | {
30
+ allowed: false;
31
+ reason: string;
32
+ };
33
+ /**
34
+ * Information about the tool call being checked.
35
+ */
36
+ export interface PermissionCheckRequest {
37
+ /** The connection being accessed */
38
+ connection: string;
39
+ /** Endpoint path (e.g., "POST /articles") */
40
+ endpointPath: string;
41
+ /** Intent declared by the LLM */
42
+ intent: 'read' | 'write' | 'confirmed_write';
43
+ /** HTTP method */
44
+ method: string;
45
+ /** Request parameters (for threshold evaluation) */
46
+ params?: Record<string, unknown>;
47
+ /** Whether this is from a delegated/task agent */
48
+ isDelegated?: boolean;
49
+ /** Whether plan mode is active */
50
+ planModeActive?: boolean;
51
+ /** Whether the caller is read-only (task agents) */
52
+ readOnly?: boolean;
53
+ }
54
+ /**
55
+ * Interface for checking tool execution permissions.
56
+ *
57
+ * Implementations decide whether a tool call is allowed, requires
58
+ * confirmation, or is blocked. The request tool and agent loop both
59
+ * use this interface — the tool doesn't know how permissions are checked.
60
+ */
61
+ export interface PermissionChecker {
62
+ check(request: PermissionCheckRequest): PermissionResult;
63
+ }
64
+ export interface AccessJsonPermissionCheckerConfig {
65
+ accessConfigs: Map<string, AccessConfig>;
66
+ isDelegated: boolean;
67
+ }
68
+ /**
69
+ * Permission checker that reads from access.json configuration.
70
+ *
71
+ * Wraps the existing ActionGate to evaluate confirmation tiers,
72
+ * thresholds, and delegation escalation. Adds the intent/method
73
+ * validation that was previously inline in the request tool.
74
+ */
75
+ export declare class AccessJsonPermissionChecker implements PermissionChecker {
76
+ private readonly gate;
77
+ constructor(config: AccessJsonPermissionCheckerConfig);
78
+ check(request: PermissionCheckRequest): PermissionResult;
79
+ private gateDecisionToResult;
80
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import { ActionGate } from '@amodalai/core';
7
+ /**
8
+ * Permission checker that reads from access.json configuration.
9
+ *
10
+ * Wraps the existing ActionGate to evaluate confirmation tiers,
11
+ * thresholds, and delegation escalation. Adds the intent/method
12
+ * validation that was previously inline in the request tool.
13
+ */
14
+ export class AccessJsonPermissionChecker {
15
+ gate;
16
+ constructor(config) {
17
+ this.gate = new ActionGate({
18
+ accessConfigs: config.accessConfigs,
19
+ isDelegated: config.isDelegated,
20
+ });
21
+ }
22
+ check(request) {
23
+ const { method, intent, endpointPath, connection, params, readOnly, planModeActive } = request;
24
+ const isMutating = ['POST', 'PUT', 'PATCH', 'DELETE'].includes(method.toUpperCase());
25
+ // Block writes in read-only mode (task agents)
26
+ if (readOnly && isMutating) {
27
+ return { allowed: false, reason: 'Write operations are not allowed in read-only mode' };
28
+ }
29
+ // Block writes in plan mode
30
+ if (planModeActive && isMutating && intent !== 'read') {
31
+ return { allowed: false, reason: 'Write operations are blocked in plan mode. Present your plan for approval first.' };
32
+ }
33
+ // Enforce write intent for mutating methods
34
+ if (isMutating && intent === 'read') {
35
+ return {
36
+ allowed: false,
37
+ reason: `${method.toUpperCase()} requires intent "write" or "confirmed_write", not "read"`,
38
+ };
39
+ }
40
+ // For read-only operations, allow without gate check
41
+ if (!isMutating) {
42
+ return { allowed: true };
43
+ }
44
+ // Evaluate action gate for write operations
45
+ const gateResult = this.gate.evaluate(endpointPath, connection, params);
46
+ return this.gateDecisionToResult(gateResult.decision, gateResult.reason);
47
+ }
48
+ gateDecisionToResult(decision, reason) {
49
+ switch (decision) {
50
+ case 'allow':
51
+ return { allowed: true };
52
+ case 'confirm':
53
+ return {
54
+ allowed: true,
55
+ requiresConfirmation: true,
56
+ reason: reason ?? 'This operation requires confirmation',
57
+ };
58
+ case 'review':
59
+ return {
60
+ allowed: false,
61
+ reason: reason ?? 'This operation requires human review and cannot be executed by the agent',
62
+ };
63
+ case 'never':
64
+ return {
65
+ allowed: false,
66
+ reason: reason ?? 'This operation is not allowed',
67
+ };
68
+ default: {
69
+ const _exhaustive = decision;
70
+ return { allowed: false, reason: `Unknown gate decision: ${String(_exhaustive)}` };
71
+ }
72
+ }
73
+ }
74
+ }
75
+ //# sourceMappingURL=permission-checker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission-checker.js","sourceRoot":"","sources":["../../../src/security/permission-checker.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAgBH,OAAO,EAAC,UAAU,EAAC,MAAM,gBAAgB,CAAC;AA6D1C;;;;;;GAMG;AACH,MAAM,OAAO,2BAA2B;IACrB,IAAI,CAAa;IAElC,YAAY,MAAyC;QACnD,IAAI,CAAC,IAAI,GAAG,IAAI,UAAU,CAAC;YACzB,aAAa,EAAE,MAAM,CAAC,aAAa;YACnC,WAAW,EAAE,MAAM,CAAC,WAAW;SAChC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,OAA+B;QACnC,MAAM,EAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAC,GAAG,OAAO,CAAC;QAC7F,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;QAErF,+CAA+C;QAC/C,IAAI,QAAQ,IAAI,UAAU,EAAE,CAAC;YAC3B,OAAO,EAAC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,oDAAoD,EAAC,CAAC;QACxF,CAAC;QAED,4BAA4B;QAC5B,IAAI,cAAc,IAAI,UAAU,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;YACtD,OAAO,EAAC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,kFAAkF,EAAC,CAAC;QACtH,CAAC;QAED,4CAA4C;QAC5C,IAAI,UAAU,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;YACpC,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,MAAM,EAAE,GAAG,MAAM,CAAC,WAAW,EAAE,2DAA2D;aAC3F,CAAC;QACJ,CAAC;QAED,qDAAqD;QACrD,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,OAAO,EAAC,OAAO,EAAE,IAAI,EAAC,CAAC;QACzB,CAAC;QAED,4CAA4C;QAC5C,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;QAExE,OAAO,IAAI,CAAC,oBAAoB,CAAC,UAAU,CAAC,QAAQ,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IAC3E,CAAC;IAEO,oBAAoB,CAAC,QAAsB,EAAE,MAAe;QAClE,QAAQ,QAAQ,EAAE,CAAC;YACjB,KAAK,OAAO;gBACV,OAAO,EAAC,OAAO,EAAE,IAAI,EAAC,CAAC;YACzB,KAAK,SAAS;gBACZ,OAAO;oBACL,OAAO,EAAE,IAAI;oBACb,oBAAoB,EAAE,IAAI;oBAC1B,MAAM,EAAE,MAAM,IAAI,sCAAsC;iBACzD,CAAC;YACJ,KAAK,QAAQ;gBACX,OAAO;oBACL,OAAO,EAAE,KAAK;oBACd,MAAM,EAAE,MAAM,IAAI,0EAA0E;iBAC7F,CAAC;YACJ,KAAK,OAAO;gBACV,OAAO;oBACL,OAAO,EAAE,KAAK;oBACd,MAAM,EAAE,MAAM,IAAI,+BAA+B;iBAClD,CAAC;YACJ,OAAO,CAAC,CAAC,CAAC;gBACR,MAAM,WAAW,GAAU,QAAQ,CAAC;gBACpC,OAAO,EAAC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,0BAA0B,MAAM,CAAC,WAAW,CAAC,EAAE,EAAC,CAAC;YACnF,CAAC;QACH,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ export {};
@@ -0,0 +1,208 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import { describe, it, expect } from 'vitest';
7
+ import { AccessJsonPermissionChecker } from './permission-checker.js';
8
+ function makeAccess(overrides = {}) {
9
+ return {
10
+ endpoints: {
11
+ 'POST /articles': {
12
+ returns: ['article'],
13
+ confirm: true,
14
+ reason: 'Creates a new article',
15
+ ...overrides,
16
+ },
17
+ },
18
+ };
19
+ }
20
+ function makeRequest(overrides = {}) {
21
+ return {
22
+ connection: 'blog-api',
23
+ endpointPath: 'POST /articles',
24
+ intent: 'write',
25
+ method: 'POST',
26
+ ...overrides,
27
+ };
28
+ }
29
+ describe('AccessJsonPermissionChecker', () => {
30
+ describe('read operations', () => {
31
+ it('allows GET requests without gate check', () => {
32
+ const checker = new AccessJsonPermissionChecker({
33
+ accessConfigs: new Map([['blog-api', makeAccess()]]),
34
+ isDelegated: false,
35
+ });
36
+ const result = checker.check(makeRequest({ method: 'GET', intent: 'read', endpointPath: 'GET /articles' }));
37
+ expect(result.allowed).toBe(true);
38
+ });
39
+ });
40
+ describe('write intent enforcement', () => {
41
+ it('rejects mutating methods with read intent', () => {
42
+ const checker = new AccessJsonPermissionChecker({
43
+ accessConfigs: new Map(),
44
+ isDelegated: false,
45
+ });
46
+ const result = checker.check(makeRequest({ method: 'POST', intent: 'read' }));
47
+ expect(result.allowed).toBe(false);
48
+ if (!result.allowed) {
49
+ expect(result.reason).toContain('requires intent "write"');
50
+ }
51
+ });
52
+ it('rejects DELETE with read intent', () => {
53
+ const checker = new AccessJsonPermissionChecker({
54
+ accessConfigs: new Map(),
55
+ isDelegated: false,
56
+ });
57
+ const result = checker.check(makeRequest({ method: 'DELETE', intent: 'read' }));
58
+ expect(result.allowed).toBe(false);
59
+ });
60
+ });
61
+ describe('read-only mode (task agents)', () => {
62
+ it('blocks write operations in read-only mode', () => {
63
+ const checker = new AccessJsonPermissionChecker({
64
+ accessConfigs: new Map(),
65
+ isDelegated: false,
66
+ });
67
+ const result = checker.check(makeRequest({ readOnly: true }));
68
+ expect(result.allowed).toBe(false);
69
+ if (!result.allowed) {
70
+ expect(result.reason).toContain('read-only mode');
71
+ }
72
+ });
73
+ it('allows GET in read-only mode', () => {
74
+ const checker = new AccessJsonPermissionChecker({
75
+ accessConfigs: new Map(),
76
+ isDelegated: false,
77
+ });
78
+ const result = checker.check(makeRequest({ method: 'GET', intent: 'read', readOnly: true }));
79
+ expect(result.allowed).toBe(true);
80
+ });
81
+ });
82
+ describe('plan mode', () => {
83
+ it('blocks writes in plan mode', () => {
84
+ const checker = new AccessJsonPermissionChecker({
85
+ accessConfigs: new Map(),
86
+ isDelegated: false,
87
+ });
88
+ const result = checker.check(makeRequest({ planModeActive: true }));
89
+ expect(result.allowed).toBe(false);
90
+ if (!result.allowed) {
91
+ expect(result.reason).toContain('plan mode');
92
+ }
93
+ });
94
+ });
95
+ describe('confirmation tiers', () => {
96
+ it('returns requiresConfirmation for confirm tier', () => {
97
+ const checker = new AccessJsonPermissionChecker({
98
+ accessConfigs: new Map([['blog-api', makeAccess({ confirm: true })]]),
99
+ isDelegated: false,
100
+ });
101
+ const result = checker.check(makeRequest({ intent: 'write' }));
102
+ expect(result.allowed).toBe(true);
103
+ if (result.allowed) {
104
+ expect(result.requiresConfirmation).toBe(true);
105
+ }
106
+ });
107
+ it('blocks for review tier', () => {
108
+ const checker = new AccessJsonPermissionChecker({
109
+ accessConfigs: new Map([['blog-api', makeAccess({ confirm: 'review' })]]),
110
+ isDelegated: false,
111
+ });
112
+ const result = checker.check(makeRequest({ intent: 'write' }));
113
+ expect(result.allowed).toBe(false);
114
+ if (!result.allowed) {
115
+ expect(result.reason).toBeDefined();
116
+ }
117
+ });
118
+ it('blocks for never tier', () => {
119
+ const checker = new AccessJsonPermissionChecker({
120
+ accessConfigs: new Map([['blog-api', makeAccess({ confirm: 'never' })]]),
121
+ isDelegated: false,
122
+ });
123
+ const result = checker.check(makeRequest({ intent: 'write' }));
124
+ expect(result.allowed).toBe(false);
125
+ });
126
+ it('allows when no confirm field', () => {
127
+ const checker = new AccessJsonPermissionChecker({
128
+ accessConfigs: new Map([['blog-api', makeAccess({ confirm: undefined })]]),
129
+ isDelegated: false,
130
+ });
131
+ const result = checker.check(makeRequest({ intent: 'write' }));
132
+ expect(result.allowed).toBe(true);
133
+ if (result.allowed) {
134
+ expect(result.requiresConfirmation).toBeFalsy();
135
+ }
136
+ });
137
+ it('allows when connection has no access config', () => {
138
+ const checker = new AccessJsonPermissionChecker({
139
+ accessConfigs: new Map(),
140
+ isDelegated: false,
141
+ });
142
+ const result = checker.check(makeRequest({ intent: 'write' }));
143
+ expect(result.allowed).toBe(true);
144
+ });
145
+ });
146
+ describe('delegation escalation', () => {
147
+ it('escalates confirm to review for delegated agents', () => {
148
+ const access = makeAccess({ confirm: true });
149
+ access.delegations = { enabled: true, escalateConfirm: true };
150
+ const checker = new AccessJsonPermissionChecker({
151
+ accessConfigs: new Map([['blog-api', access]]),
152
+ isDelegated: true,
153
+ });
154
+ const result = checker.check(makeRequest({ intent: 'write' }));
155
+ expect(result.allowed).toBe(false);
156
+ if (!result.allowed) {
157
+ expect(result.reason).toContain('Delegated');
158
+ }
159
+ });
160
+ });
161
+ describe('threshold escalation', () => {
162
+ it('escalates when threshold is exceeded', () => {
163
+ const access = makeAccess({
164
+ confirm: true,
165
+ thresholds: [{ field: 'amount', above: 10000, escalate: 'never' }],
166
+ });
167
+ const checker = new AccessJsonPermissionChecker({
168
+ accessConfigs: new Map([['blog-api', access]]),
169
+ isDelegated: false,
170
+ });
171
+ const result = checker.check(makeRequest({
172
+ intent: 'write',
173
+ params: { amount: 50000 },
174
+ }));
175
+ expect(result.allowed).toBe(false);
176
+ });
177
+ it('does not escalate when threshold is not exceeded', () => {
178
+ const access = makeAccess({
179
+ confirm: true,
180
+ thresholds: [{ field: 'amount', above: 10000, escalate: 'never' }],
181
+ });
182
+ const checker = new AccessJsonPermissionChecker({
183
+ accessConfigs: new Map([['blog-api', access]]),
184
+ isDelegated: false,
185
+ });
186
+ const result = checker.check(makeRequest({
187
+ intent: 'write',
188
+ params: { amount: 500 },
189
+ }));
190
+ // Still 'confirm' tier (not escalated to 'never')
191
+ expect(result.allowed).toBe(true);
192
+ if (result.allowed) {
193
+ expect(result.requiresConfirmation).toBe(true);
194
+ }
195
+ });
196
+ });
197
+ describe('exhaustive gate decisions', () => {
198
+ it('handles all four gate decisions', () => {
199
+ // allow — tested above (no confirm field)
200
+ // confirm — tested above (confirm: true)
201
+ // review — tested above (confirm: 'review')
202
+ // never — tested above (confirm: 'never')
203
+ // This test exists to document coverage, not add new assertions
204
+ expect(true).toBe(true);
205
+ });
206
+ });
207
+ });
208
+ //# sourceMappingURL=permission-checker.test.js.map