@amodalai/runtime 0.1.26 → 0.2.1

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 (445) hide show
  1. package/dist/src/__fixtures__/README.md +88 -0
  2. package/dist/src/__fixtures__/e2e.test.js +211 -0
  3. package/dist/src/__fixtures__/e2e.test.js.map +1 -0
  4. package/dist/src/__fixtures__/smoke-agent/amodal.json +11 -0
  5. package/dist/src/__fixtures__/smoke-agent/automations/delivery-callback-test.json +9 -0
  6. package/dist/src/__fixtures__/smoke-agent/automations/test-auto.md +5 -0
  7. package/dist/src/__fixtures__/smoke-agent/connections/mock-api/access.json +11 -0
  8. package/dist/src/__fixtures__/smoke-agent/connections/mock-api/spec.json +4 -0
  9. package/dist/src/__fixtures__/smoke-agent/connections/mock-api/surface.md +9 -0
  10. package/dist/src/__fixtures__/smoke-agent/connections/mock-mcp/access.json +3 -0
  11. package/dist/src/__fixtures__/smoke-agent/connections/mock-mcp/spec.json +8 -0
  12. package/dist/src/__fixtures__/smoke-agent/evals/basic-eval.md +12 -0
  13. package/dist/src/__fixtures__/smoke-agent/knowledge/test-knowledge.md +3 -0
  14. package/dist/src/__fixtures__/smoke-agent/skills/test-skill/SKILL.md +11 -0
  15. package/dist/src/__fixtures__/smoke-agent/stores/test-items.json +11 -0
  16. package/dist/src/__fixtures__/smoke-agent/tools/echo_tool/handler.d.ts +18 -0
  17. package/dist/src/__fixtures__/smoke-agent/tools/echo_tool/handler.js +22 -0
  18. package/dist/src/__fixtures__/smoke-agent/tools/echo_tool/handler.js.map +1 -0
  19. package/dist/src/__fixtures__/smoke-agent/tools/echo_tool/tool.json +17 -0
  20. package/dist/src/__fixtures__/smoke.test.js +1404 -0
  21. package/dist/src/__fixtures__/smoke.test.js.map +1 -0
  22. package/dist/src/__fixtures__/test-env.d.ts +27 -0
  23. package/dist/src/__fixtures__/test-env.js +64 -0
  24. package/dist/src/__fixtures__/test-env.js.map +1 -0
  25. package/dist/src/__fixtures__/test-helpers.d.ts +30 -0
  26. package/dist/src/__fixtures__/test-helpers.js +120 -0
  27. package/dist/src/__fixtures__/test-helpers.js.map +1 -0
  28. package/dist/src/__tests__/test-providers.d.ts +40 -0
  29. package/dist/src/__tests__/test-providers.js +61 -0
  30. package/dist/src/__tests__/test-providers.js.map +1 -0
  31. package/dist/src/agent/agent-types.d.ts +22 -0
  32. package/dist/src/agent/agent-types.js.map +1 -1
  33. package/dist/src/agent/automation-bridge.d.ts +9 -0
  34. package/dist/src/agent/automation-bridge.js +26 -0
  35. package/dist/src/agent/automation-bridge.js.map +1 -1
  36. package/dist/src/agent/automation-bridge.test.js +63 -0
  37. package/dist/src/agent/automation-bridge.test.js.map +1 -1
  38. package/dist/src/agent/local-server.d.ts +1 -8
  39. package/dist/src/agent/local-server.js +398 -163
  40. package/dist/src/agent/local-server.js.map +1 -1
  41. package/dist/src/agent/local-server.test.js +14 -8
  42. package/dist/src/agent/local-server.test.js.map +1 -1
  43. package/dist/src/agent/loop-types.d.ts +254 -0
  44. package/dist/src/agent/loop-types.js +24 -0
  45. package/dist/src/agent/loop-types.js.map +1 -0
  46. package/dist/src/agent/loop.d.ts +31 -0
  47. package/dist/src/agent/loop.js +152 -0
  48. package/dist/src/agent/loop.js.map +1 -0
  49. package/dist/src/agent/loop.test.js +1594 -0
  50. package/dist/src/agent/loop.test.js.map +1 -0
  51. package/dist/src/agent/mcp-config.d.ts +28 -0
  52. package/dist/src/agent/mcp-config.js +57 -0
  53. package/dist/src/agent/mcp-config.js.map +1 -0
  54. package/dist/src/agent/page-builder.js +6 -1
  55. package/dist/src/agent/page-builder.js.map +1 -1
  56. package/dist/src/agent/proactive/delivery-router.d.ts +68 -0
  57. package/dist/src/agent/proactive/delivery-router.js +337 -0
  58. package/dist/src/agent/proactive/delivery-router.js.map +1 -0
  59. package/dist/src/agent/{stores-e2e.test.d.ts → proactive/delivery-router.test.d.ts} +1 -1
  60. package/dist/src/agent/proactive/delivery-router.test.js +455 -0
  61. package/dist/src/agent/proactive/delivery-router.test.js.map +1 -0
  62. package/dist/src/agent/proactive/proactive-runner.d.ts +46 -8
  63. package/dist/src/agent/proactive/proactive-runner.js +67 -37
  64. package/dist/src/agent/proactive/proactive-runner.js.map +1 -1
  65. package/dist/src/agent/proactive/proactive-runner.test.d.ts +1 -1
  66. package/dist/src/agent/proactive/proactive-runner.test.js +73 -87
  67. package/dist/src/agent/proactive/proactive-runner.test.js.map +1 -1
  68. package/dist/src/agent/routes/admin-chat-abort.test.d.ts +6 -0
  69. package/dist/src/agent/routes/admin-chat-abort.test.js +206 -0
  70. package/dist/src/agent/routes/admin-chat-abort.test.js.map +1 -0
  71. package/dist/src/agent/routes/admin-chat.d.ts +15 -3
  72. package/dist/src/agent/routes/admin-chat.js +61 -18
  73. package/dist/src/agent/routes/admin-chat.js.map +1 -1
  74. package/dist/src/agent/routes/automations.js +5 -6
  75. package/dist/src/agent/routes/automations.js.map +1 -1
  76. package/dist/src/agent/routes/evals.d.ts +3 -2
  77. package/dist/src/agent/routes/evals.js +25 -12
  78. package/dist/src/agent/routes/evals.js.map +1 -1
  79. package/dist/src/agent/routes/files.js +7 -9
  80. package/dist/src/agent/routes/files.js.map +1 -1
  81. package/dist/src/agent/routes/inspect.d.ts +6 -2
  82. package/dist/src/agent/routes/inspect.js +31 -17
  83. package/dist/src/agent/routes/inspect.js.map +1 -1
  84. package/dist/src/agent/routes/inspect.test.js +18 -42
  85. package/dist/src/agent/routes/inspect.test.js.map +1 -1
  86. package/dist/src/agent/routes/stores.js +9 -12
  87. package/dist/src/agent/routes/stores.js.map +1 -1
  88. package/dist/src/agent/routes/task.d.ts +15 -3
  89. package/dist/src/agent/routes/task.js +16 -7
  90. package/dist/src/agent/routes/task.js.map +1 -1
  91. package/dist/src/agent/routes/task.test.d.ts +1 -1
  92. package/dist/src/agent/routes/task.test.js +68 -53
  93. package/dist/src/agent/routes/task.test.js.map +1 -1
  94. package/dist/src/agent/routes/webhooks.js +12 -3
  95. package/dist/src/agent/routes/webhooks.js.map +1 -1
  96. package/dist/src/agent/snapshot-server.d.ts +2 -22
  97. package/dist/src/agent/snapshot-server.js +48 -27
  98. package/dist/src/agent/snapshot-server.js.map +1 -1
  99. package/dist/src/agent/states/compacting.d.ts +14 -0
  100. package/dist/src/agent/states/compacting.js +260 -0
  101. package/dist/src/agent/states/compacting.js.map +1 -0
  102. package/dist/src/agent/states/confirming.d.ts +10 -0
  103. package/dist/src/agent/states/confirming.js +79 -0
  104. package/dist/src/agent/states/confirming.js.map +1 -0
  105. package/dist/src/agent/states/dispatching.d.ts +18 -0
  106. package/dist/src/agent/states/dispatching.js +285 -0
  107. package/dist/src/agent/states/dispatching.js.map +1 -0
  108. package/dist/src/agent/states/executing.d.ts +21 -0
  109. package/dist/src/agent/states/executing.js +452 -0
  110. package/dist/src/agent/states/executing.js.map +1 -0
  111. package/dist/src/agent/states/streaming.d.ts +10 -0
  112. package/dist/src/agent/states/streaming.js +169 -0
  113. package/dist/src/agent/states/streaming.js.map +1 -0
  114. package/dist/src/agent/states/thinking.d.ts +13 -0
  115. package/dist/src/agent/states/thinking.js +450 -0
  116. package/dist/src/agent/states/thinking.js.map +1 -0
  117. package/dist/src/agent/token-estimate.d.ts +31 -0
  118. package/dist/src/agent/token-estimate.js +34 -0
  119. package/dist/src/agent/token-estimate.js.map +1 -0
  120. package/dist/src/agent/token-estimate.test.d.ts +6 -0
  121. package/dist/src/agent/token-estimate.test.js +44 -0
  122. package/dist/src/agent/token-estimate.test.js.map +1 -0
  123. package/dist/src/agent/tool-executor-local.js +9 -18
  124. package/dist/src/agent/tool-executor-local.js.map +1 -1
  125. package/dist/src/agent/tool-executor-local.test.js +3 -5
  126. package/dist/src/agent/tool-executor-local.test.js.map +1 -1
  127. package/dist/src/api/create-agent.d.ts +15 -0
  128. package/dist/src/api/create-agent.js +134 -0
  129. package/dist/src/api/create-agent.js.map +1 -0
  130. package/dist/src/api/types.d.ts +66 -0
  131. package/dist/src/api/types.js +7 -0
  132. package/dist/src/api/types.js.map +1 -0
  133. package/dist/src/context/compiler.d.ts +13 -0
  134. package/dist/src/context/compiler.js +358 -0
  135. package/dist/src/context/compiler.js.map +1 -0
  136. package/dist/src/context/compiler.test.d.ts +6 -0
  137. package/dist/src/context/compiler.test.js +532 -0
  138. package/dist/src/context/compiler.test.js.map +1 -0
  139. package/dist/src/context/types.d.ts +110 -0
  140. package/dist/src/context/types.js +7 -0
  141. package/dist/src/context/types.js.map +1 -0
  142. package/dist/src/env-ref.d.ts +13 -0
  143. package/dist/src/env-ref.js +31 -0
  144. package/dist/src/env-ref.js.map +1 -0
  145. package/dist/src/env-ref.test.d.ts +6 -0
  146. package/dist/src/env-ref.test.js +34 -0
  147. package/dist/src/env-ref.test.js.map +1 -0
  148. package/dist/src/errors.d.ts +15 -0
  149. package/dist/src/errors.js +22 -0
  150. package/dist/src/errors.js.map +1 -1
  151. package/dist/src/errors.test.js +2 -2
  152. package/dist/src/errors.test.js.map +1 -1
  153. package/dist/src/events/event-bus.d.ts +54 -0
  154. package/dist/src/events/event-bus.js +84 -0
  155. package/dist/src/events/event-bus.js.map +1 -0
  156. package/dist/src/events/event-bus.test.d.ts +6 -0
  157. package/dist/src/events/event-bus.test.js +112 -0
  158. package/dist/src/events/event-bus.test.js.map +1 -0
  159. package/dist/src/events/events-route.d.ts +36 -0
  160. package/dist/src/events/events-route.js +80 -0
  161. package/dist/src/events/events-route.js.map +1 -0
  162. package/dist/src/events/events-route.test.d.ts +6 -0
  163. package/dist/src/events/events-route.test.js +134 -0
  164. package/dist/src/events/events-route.test.js.map +1 -0
  165. package/dist/src/events/store-event-wrapper.d.ts +19 -0
  166. package/dist/src/events/store-event-wrapper.js +57 -0
  167. package/dist/src/events/store-event-wrapper.js.map +1 -0
  168. package/dist/src/events/store-event-wrapper.test.d.ts +6 -0
  169. package/dist/src/events/store-event-wrapper.test.js +91 -0
  170. package/dist/src/events/store-event-wrapper.test.js.map +1 -0
  171. package/dist/src/index.d.ts +33 -6
  172. package/dist/src/index.js +35 -21
  173. package/dist/src/index.js.map +1 -1
  174. package/dist/src/middleware/auth.d.ts +0 -2
  175. package/dist/src/middleware/auth.js.map +1 -1
  176. package/dist/src/providers/create-provider.d.ts +23 -0
  177. package/dist/src/providers/create-provider.js +185 -0
  178. package/dist/src/providers/create-provider.js.map +1 -0
  179. package/dist/src/providers/create-provider.test.d.ts +6 -0
  180. package/dist/src/providers/create-provider.test.js +95 -0
  181. package/dist/src/providers/create-provider.test.js.map +1 -0
  182. package/dist/src/providers/failover.d.ts +38 -0
  183. package/dist/src/providers/failover.js +147 -0
  184. package/dist/src/providers/failover.js.map +1 -0
  185. package/dist/src/providers/failover.test.d.ts +6 -0
  186. package/dist/src/providers/failover.test.js +169 -0
  187. package/dist/src/providers/failover.test.js.map +1 -0
  188. package/dist/src/providers/search-provider.d.ts +64 -0
  189. package/dist/src/providers/search-provider.js +174 -0
  190. package/dist/src/providers/search-provider.js.map +1 -0
  191. package/dist/src/providers/types.d.ts +118 -0
  192. package/dist/src/providers/types.js +7 -0
  193. package/dist/src/providers/types.js.map +1 -0
  194. package/dist/src/routes/ai-stream.d.ts +28 -10
  195. package/dist/src/routes/ai-stream.js +85 -41
  196. package/dist/src/routes/ai-stream.js.map +1 -1
  197. package/dist/src/routes/chat-new.test.d.ts +6 -0
  198. package/dist/src/routes/chat-new.test.js +107 -0
  199. package/dist/src/routes/chat-new.test.js.map +1 -0
  200. package/dist/src/routes/chat-stream-new.test.d.ts +6 -0
  201. package/dist/src/routes/chat-stream-new.test.js +135 -0
  202. package/dist/src/routes/chat-stream-new.test.js.map +1 -0
  203. package/dist/src/routes/chat-stream.d.ts +20 -4
  204. package/dist/src/routes/chat-stream.js +49 -29
  205. package/dist/src/routes/chat-stream.js.map +1 -1
  206. package/dist/src/routes/chat.d.ts +19 -4
  207. package/dist/src/routes/chat.js +62 -23
  208. package/dist/src/routes/chat.js.map +1 -1
  209. package/dist/src/routes/health.d.ts +3 -2
  210. package/dist/src/routes/health.js.map +1 -1
  211. package/dist/src/routes/route-helpers.d.ts +50 -0
  212. package/dist/src/routes/route-helpers.js +80 -0
  213. package/dist/src/routes/route-helpers.js.map +1 -0
  214. package/dist/src/routes/session-resolver.d.ts +77 -0
  215. package/dist/src/routes/session-resolver.js +109 -0
  216. package/dist/src/routes/session-resolver.js.map +1 -0
  217. package/dist/src/routes/session-resolver.test.d.ts +6 -0
  218. package/dist/src/routes/session-resolver.test.js +207 -0
  219. package/dist/src/routes/session-resolver.test.js.map +1 -0
  220. package/dist/src/routes/webhooks.d.ts +3 -1
  221. package/dist/src/routes/webhooks.js +12 -4
  222. package/dist/src/routes/webhooks.js.map +1 -1
  223. package/dist/src/security/permission-checker.d.ts +80 -0
  224. package/dist/src/security/permission-checker.js +75 -0
  225. package/dist/src/security/permission-checker.js.map +1 -0
  226. package/dist/src/security/permission-checker.test.d.ts +6 -0
  227. package/dist/src/security/permission-checker.test.js +208 -0
  228. package/dist/src/security/permission-checker.test.js.map +1 -0
  229. package/dist/src/server.d.ts +18 -11
  230. package/dist/src/server.js +46 -46
  231. package/dist/src/server.js.map +1 -1
  232. package/dist/src/server.test.d.ts +1 -1
  233. package/dist/src/server.test.js +6 -144
  234. package/dist/src/server.test.js.map +1 -1
  235. package/dist/src/session/drizzle-session-store.d.ts +56 -0
  236. package/dist/src/session/drizzle-session-store.js +203 -0
  237. package/dist/src/session/drizzle-session-store.js.map +1 -0
  238. package/dist/src/session/manager.d.ts +101 -0
  239. package/dist/src/session/manager.js +394 -0
  240. package/dist/src/session/manager.js.map +1 -0
  241. package/dist/src/session/manager.test.d.ts +6 -0
  242. package/dist/src/session/manager.test.js +309 -0
  243. package/dist/src/session/manager.test.js.map +1 -0
  244. package/dist/src/session/pglite-session-store.d.ts +23 -0
  245. package/dist/src/session/pglite-session-store.js +70 -0
  246. package/dist/src/session/pglite-session-store.js.map +1 -0
  247. package/dist/src/session/postgres-session-store.d.ts +44 -0
  248. package/dist/src/session/postgres-session-store.js +138 -0
  249. package/dist/src/session/postgres-session-store.js.map +1 -0
  250. package/dist/src/session/session-builder.d.ts +69 -0
  251. package/dist/src/session/session-builder.js +384 -0
  252. package/dist/src/session/session-builder.js.map +1 -0
  253. package/dist/src/session/session-builder.test.d.ts +6 -0
  254. package/dist/src/session/session-builder.test.js +350 -0
  255. package/dist/src/session/session-builder.test.js.map +1 -0
  256. package/dist/src/session/session-store-selector.d.ts +49 -0
  257. package/dist/src/session/session-store-selector.js +60 -0
  258. package/dist/src/session/session-store-selector.js.map +1 -0
  259. package/dist/src/session/session-store-selector.test.d.ts +6 -0
  260. package/dist/src/session/session-store-selector.test.js +79 -0
  261. package/dist/src/session/session-store-selector.test.js.map +1 -0
  262. package/dist/src/session/store.d.ts +171 -0
  263. package/dist/src/session/store.js +155 -0
  264. package/dist/src/session/store.js.map +1 -0
  265. package/dist/src/session/store.test.d.ts +6 -0
  266. package/dist/src/session/store.test.js +423 -0
  267. package/dist/src/session/store.test.js.map +1 -0
  268. package/dist/src/session/stream-hooks.d.ts +39 -0
  269. package/dist/src/session/stream-hooks.js +7 -0
  270. package/dist/src/session/stream-hooks.js.map +1 -0
  271. package/dist/src/session/tool-context-factory.d.ts +61 -0
  272. package/dist/src/session/tool-context-factory.js +189 -0
  273. package/dist/src/session/tool-context-factory.js.map +1 -0
  274. package/dist/src/session/tool-context-factory.test.d.ts +6 -0
  275. package/dist/src/session/tool-context-factory.test.js +284 -0
  276. package/dist/src/session/tool-context-factory.test.js.map +1 -0
  277. package/dist/src/session/types.d.ts +195 -0
  278. package/dist/src/session/types.js +7 -0
  279. package/dist/src/session/types.js.map +1 -0
  280. package/dist/src/stores/drizzle-store-backend.d.ts +49 -0
  281. package/dist/src/stores/drizzle-store-backend.js +306 -0
  282. package/dist/src/stores/drizzle-store-backend.js.map +1 -0
  283. package/dist/src/stores/drizzle-store-backend.test.d.ts +6 -0
  284. package/dist/src/stores/drizzle-store-backend.test.js +215 -0
  285. package/dist/src/stores/drizzle-store-backend.test.js.map +1 -0
  286. package/dist/src/stores/index.d.ts +4 -0
  287. package/dist/src/stores/index.js +2 -0
  288. package/dist/src/stores/index.js.map +1 -1
  289. package/dist/src/stores/pglite-store-backend.d.ts +16 -19
  290. package/dist/src/stores/pglite-store-backend.js +85 -239
  291. package/dist/src/stores/pglite-store-backend.js.map +1 -1
  292. package/dist/src/stores/postgres-store-backend.d.ts +30 -0
  293. package/dist/src/stores/postgres-store-backend.js +100 -0
  294. package/dist/src/stores/postgres-store-backend.js.map +1 -0
  295. package/dist/src/stores/schema.d.ts +457 -0
  296. package/dist/src/stores/schema.js +59 -0
  297. package/dist/src/stores/schema.js.map +1 -0
  298. package/dist/src/tools/admin-file-tools.d.ts +42 -0
  299. package/dist/src/tools/admin-file-tools.js +714 -0
  300. package/dist/src/tools/admin-file-tools.js.map +1 -0
  301. package/dist/src/tools/admin-file-tools.test.d.ts +6 -0
  302. package/dist/src/tools/admin-file-tools.test.js +521 -0
  303. package/dist/src/tools/admin-file-tools.test.js.map +1 -0
  304. package/dist/src/tools/custom-tool-adapter.d.ts +41 -0
  305. package/dist/src/tools/custom-tool-adapter.js +190 -0
  306. package/dist/src/tools/custom-tool-adapter.js.map +1 -0
  307. package/dist/src/tools/custom-tool-adapter.test.d.ts +6 -0
  308. package/dist/src/tools/custom-tool-adapter.test.js +243 -0
  309. package/dist/src/tools/custom-tool-adapter.test.js.map +1 -0
  310. package/dist/src/tools/dispatch-tool.d.ts +52 -0
  311. package/dist/src/tools/dispatch-tool.js +71 -0
  312. package/dist/src/tools/dispatch-tool.js.map +1 -0
  313. package/dist/src/tools/dispatch-tool.test.d.ts +6 -0
  314. package/dist/src/tools/dispatch-tool.test.js +75 -0
  315. package/dist/src/tools/dispatch-tool.test.js.map +1 -0
  316. package/dist/src/tools/fetch-url-tool.d.ts +23 -0
  317. package/dist/src/tools/fetch-url-tool.js +333 -0
  318. package/dist/src/tools/fetch-url-tool.js.map +1 -0
  319. package/dist/src/tools/fetch-url-tool.test.d.ts +6 -0
  320. package/dist/src/tools/fetch-url-tool.test.js +228 -0
  321. package/dist/src/tools/fetch-url-tool.test.js.map +1 -0
  322. package/dist/src/tools/mcp-tool-adapter.d.ts +18 -0
  323. package/dist/src/tools/mcp-tool-adapter.js +135 -0
  324. package/dist/src/tools/mcp-tool-adapter.js.map +1 -0
  325. package/dist/src/tools/mcp-tool-adapter.test.d.ts +6 -0
  326. package/dist/src/tools/mcp-tool-adapter.test.js +226 -0
  327. package/dist/src/tools/mcp-tool-adapter.test.js.map +1 -0
  328. package/dist/src/tools/registry.d.ts +25 -0
  329. package/dist/src/tools/registry.js +72 -0
  330. package/dist/src/tools/registry.js.map +1 -0
  331. package/dist/src/tools/registry.test.d.ts +6 -0
  332. package/dist/src/tools/registry.test.js +120 -0
  333. package/dist/src/tools/registry.test.js.map +1 -0
  334. package/dist/src/tools/request-tool.d.ts +42 -0
  335. package/dist/src/tools/request-tool.js +190 -0
  336. package/dist/src/tools/request-tool.js.map +1 -0
  337. package/dist/src/tools/request-tool.test.d.ts +6 -0
  338. package/dist/src/tools/request-tool.test.js +253 -0
  339. package/dist/src/tools/request-tool.test.js.map +1 -0
  340. package/dist/src/tools/store-tools.d.ts +29 -0
  341. package/dist/src/tools/store-tools.js +224 -0
  342. package/dist/src/tools/store-tools.js.map +1 -0
  343. package/dist/src/tools/store-tools.test.d.ts +6 -0
  344. package/dist/src/tools/store-tools.test.js +215 -0
  345. package/dist/src/tools/store-tools.test.js.map +1 -0
  346. package/dist/src/tools/types.d.ts +129 -0
  347. package/dist/src/tools/types.js +7 -0
  348. package/dist/src/tools/types.js.map +1 -0
  349. package/dist/src/tools/web-search-tool.d.ts +31 -0
  350. package/dist/src/tools/web-search-tool.js +170 -0
  351. package/dist/src/tools/web-search-tool.js.map +1 -0
  352. package/dist/src/tools/web-search-tool.test.d.ts +6 -0
  353. package/dist/src/tools/web-search-tool.test.js +153 -0
  354. package/dist/src/tools/web-search-tool.test.js.map +1 -0
  355. package/dist/src/tools/web-tools-shared.d.ts +21 -0
  356. package/dist/src/tools/web-tools-shared.js +32 -0
  357. package/dist/src/tools/web-tools-shared.js.map +1 -0
  358. package/dist/src/types.d.ts +40 -12
  359. package/dist/src/types.js +16 -2
  360. package/dist/src/types.js.map +1 -1
  361. package/dist/tsconfig.tsbuildinfo +1 -1
  362. package/package.json +27 -4
  363. package/dist/src/__tests__/sse-contract.test.js +0 -464
  364. package/dist/src/__tests__/sse-contract.test.js.map +0 -1
  365. package/dist/src/__tests__/tools.test.js +0 -583
  366. package/dist/src/__tests__/tools.test.js.map +0 -1
  367. package/dist/src/agent/agent-runner.d.ts +0 -33
  368. package/dist/src/agent/agent-runner.js +0 -1040
  369. package/dist/src/agent/agent-runner.js.map +0 -1
  370. package/dist/src/agent/custom-tools-e2e.test.d.ts +0 -6
  371. package/dist/src/agent/custom-tools-e2e.test.js +0 -566
  372. package/dist/src/agent/custom-tools-e2e.test.js.map +0 -1
  373. package/dist/src/agent/request-helper.d.ts +0 -16
  374. package/dist/src/agent/request-helper.js +0 -96
  375. package/dist/src/agent/request-helper.js.map +0 -1
  376. package/dist/src/agent/session-store.d.ts +0 -62
  377. package/dist/src/agent/session-store.js +0 -151
  378. package/dist/src/agent/session-store.js.map +0 -1
  379. package/dist/src/agent/stores-e2e.test.js +0 -433
  380. package/dist/src/agent/stores-e2e.test.js.map +0 -1
  381. package/dist/src/agent/tool-context-builder.d.ts +0 -11
  382. package/dist/src/agent/tool-context-builder.js +0 -102
  383. package/dist/src/agent/tool-context-builder.js.map +0 -1
  384. package/dist/src/agent/tool-context-builder.test.d.ts +0 -6
  385. package/dist/src/agent/tool-context-builder.test.js +0 -152
  386. package/dist/src/agent/tool-context-builder.test.js.map +0 -1
  387. package/dist/src/agent/write-repo-file.test.js +0 -270
  388. package/dist/src/agent/write-repo-file.test.js.map +0 -1
  389. package/dist/src/cron/heartbeat-runner.d.ts +0 -21
  390. package/dist/src/cron/heartbeat-runner.js +0 -79
  391. package/dist/src/cron/heartbeat-runner.js.map +0 -1
  392. package/dist/src/cron/heartbeat-runner.test.d.ts +0 -6
  393. package/dist/src/cron/heartbeat-runner.test.js +0 -120
  394. package/dist/src/cron/heartbeat-runner.test.js.map +0 -1
  395. package/dist/src/cron/heartbeat-scheduler.d.ts +0 -26
  396. package/dist/src/cron/heartbeat-scheduler.js +0 -55
  397. package/dist/src/cron/heartbeat-scheduler.js.map +0 -1
  398. package/dist/src/cron/heartbeat-scheduler.test.d.ts +0 -6
  399. package/dist/src/cron/heartbeat-scheduler.test.js +0 -61
  400. package/dist/src/cron/heartbeat-scheduler.test.js.map +0 -1
  401. package/dist/src/routes/ai-stream.test.d.ts +0 -6
  402. package/dist/src/routes/ai-stream.test.js +0 -586
  403. package/dist/src/routes/ai-stream.test.js.map +0 -1
  404. package/dist/src/routes/ask-user-response.d.ts +0 -30
  405. package/dist/src/routes/ask-user-response.js +0 -61
  406. package/dist/src/routes/ask-user-response.js.map +0 -1
  407. package/dist/src/routes/ask-user-response.test.d.ts +0 -6
  408. package/dist/src/routes/ask-user-response.test.js +0 -88
  409. package/dist/src/routes/ask-user-response.test.js.map +0 -1
  410. package/dist/src/routes/chat-stream.test.d.ts +0 -6
  411. package/dist/src/routes/chat-stream.test.js +0 -155
  412. package/dist/src/routes/chat-stream.test.js.map +0 -1
  413. package/dist/src/routes/chat.test.d.ts +0 -6
  414. package/dist/src/routes/chat.test.js +0 -99
  415. package/dist/src/routes/chat.test.js.map +0 -1
  416. package/dist/src/routes/widget-actions.d.ts +0 -49
  417. package/dist/src/routes/widget-actions.js +0 -78
  418. package/dist/src/routes/widget-actions.js.map +0 -1
  419. package/dist/src/session/admin-file-tools.d.ts +0 -136
  420. package/dist/src/session/admin-file-tools.js +0 -240
  421. package/dist/src/session/admin-file-tools.js.map +0 -1
  422. package/dist/src/session/custom-tool-adapter.d.ts +0 -74
  423. package/dist/src/session/custom-tool-adapter.js +0 -180
  424. package/dist/src/session/custom-tool-adapter.js.map +0 -1
  425. package/dist/src/session/history-converter.d.ts +0 -21
  426. package/dist/src/session/history-converter.js +0 -59
  427. package/dist/src/session/history-converter.js.map +0 -1
  428. package/dist/src/session/history-converter.test.d.ts +0 -6
  429. package/dist/src/session/history-converter.test.js +0 -130
  430. package/dist/src/session/history-converter.test.js.map +0 -1
  431. package/dist/src/session/session-manager.d.ts +0 -219
  432. package/dist/src/session/session-manager.js +0 -915
  433. package/dist/src/session/session-manager.js.map +0 -1
  434. package/dist/src/session/session-manager.test.d.ts +0 -6
  435. package/dist/src/session/session-manager.test.js +0 -455
  436. package/dist/src/session/session-manager.test.js.map +0 -1
  437. package/dist/src/session/session-runner.d.ts +0 -45
  438. package/dist/src/session/session-runner.js +0 -719
  439. package/dist/src/session/session-runner.js.map +0 -1
  440. package/dist/src/session/session-runner.test.d.ts +0 -6
  441. package/dist/src/session/session-runner.test.js +0 -834
  442. package/dist/src/session/session-runner.test.js.map +0 -1
  443. /package/dist/src/{__tests__/sse-contract.test.d.ts → __fixtures__/e2e.test.d.ts} +0 -0
  444. /package/dist/src/{__tests__/tools.test.d.ts → __fixtures__/smoke.test.d.ts} +0 -0
  445. /package/dist/src/agent/{write-repo-file.test.d.ts → loop.test.d.ts} +0 -0
@@ -1,17 +1,35 @@
1
1
  /**
2
2
  * @license
3
- * Copyright 2025 Amodal Labs, Inc.
3
+ * Copyright 2026 Amodal Labs, Inc.
4
4
  * SPDX-License-Identifier: MIT
5
5
  */
6
+ /**
7
+ * Local server for repo-based agent mode.
8
+ *
9
+ * Loads the `.amodal/` config from `config.repoPath`, creates a
10
+ * StandaloneSessionManager, mounts all routes, and optionally watches
11
+ * for config changes (hot reload).
12
+ *
13
+ * Replaces the old initialization sequence that depended on gemini-cli-core's
14
+ * Config, GeminiClient, and upstream ToolRegistry.
15
+ */
6
16
  import express from 'express';
7
17
  import { existsSync } from 'node:fs';
8
18
  import path from 'node:path';
9
19
  import { loadRepo } from '@amodalai/core';
10
- import { SessionManager } from '../session/session-manager.js';
11
- import { LocalShellExecutor } from './shell-executor-local.js';
20
+ import { StandaloneSessionManager } from '../session/manager.js';
21
+ import { selectSessionStore } from '../session/session-store-selector.js';
22
+ import { resolveEnvRef } from '../env-ref.js';
23
+ import { buildSessionComponents } from '../session/session-builder.js';
24
+ import { LocalToolExecutor } from './tool-executor-local.js';
25
+ import { buildMcpConfigs } from './mcp-config.js';
12
26
  import { ConfigWatcher } from './config-watcher.js';
27
+ import { RuntimeEventBus } from '../events/event-bus.js';
28
+ import { createEventsRouter } from '../events/events-route.js';
29
+ import { wrapStoreBackendWithEvents } from '../events/store-event-wrapper.js';
13
30
  import { ProactiveRunner } from './proactive/proactive-runner.js';
14
31
  import { createChatStreamRouter } from '../routes/chat-stream.js';
32
+ import { createChatRouter } from '../routes/chat.js';
15
33
  import { createAdminChatRouter } from './routes/admin-chat.js';
16
34
  import { createTaskRouter } from './routes/task.js';
17
35
  import { createInspectRouter } from './routes/inspect.js';
@@ -23,20 +41,21 @@ import { createStoresRouter } from './routes/stores.js';
23
41
  import { createFilesRouter } from './routes/files.js';
24
42
  import { createEvalRouter } from './routes/evals.js';
25
43
  import { errorHandler } from '../middleware/error-handler.js';
44
+ import { asyncHandler } from '../routes/route-helpers.js';
26
45
  import { createPGLiteStoreBackend } from '../stores/pglite-store-backend.js';
27
- import { SessionStore } from './session-store.js';
28
46
  import { EvalStore } from './eval-store.js';
29
47
  import { buildPages } from './page-builder.js';
30
48
  import { LOCAL_APP_ID } from '../constants.js';
31
- import { log } from '../logger.js';
49
+ import { log, createLogger } from '../logger.js';
50
+ // Each check must use an endpoint that returns 200 on a valid key and
51
+ // a distinct auth-failure status (typically 401) on a bad key. Do NOT
52
+ // use endpoints with method guards that might return 405 before the
53
+ // auth check — `GET /v1/messages` on Anthropic does exactly that, and
54
+ // makes every key (good or bad) look invalid because Anthropic returns
55
+ // 405 for wrong-method regardless of whether the x-api-key is real.
32
56
  const PROVIDER_CHECKS = [
33
- { provider: 'anthropic', envVar: 'ANTHROPIC_API_KEY', url: 'https://api.anthropic.com/v1/models', authHeader: (k) => ({ 'x-api-key': k, 'anthropic-version': '2023-06-01' }) },
34
- { provider: 'openai', envVar: 'OPENAI_API_KEY', url: 'https://api.openai.com/v1/models?limit=1', authHeader: (k) => ({ Authorization: `Bearer ${k}` }) },
35
- { provider: 'google', envVar: 'GOOGLE_API_KEY', url: 'https://generativelanguage.googleapis.com/v1beta/models?pageSize=1', authHeader: (k) => ({ 'x-goog-api-key': k }) },
36
- { provider: 'deepseek', envVar: 'DEEPSEEK_API_KEY', url: 'https://api.deepseek.com/v1/models', authHeader: (k) => ({ Authorization: `Bearer ${k}` }) },
37
- { provider: 'groq', envVar: 'GROQ_API_KEY', url: 'https://api.groq.com/openai/v1/models', authHeader: (k) => ({ Authorization: `Bearer ${k}` }) },
38
- { provider: 'mistral', envVar: 'MISTRAL_API_KEY', url: 'https://api.mistral.ai/v1/models', authHeader: (k) => ({ Authorization: `Bearer ${k}` }) },
39
- { provider: 'xai', envVar: 'XAI_API_KEY', url: 'https://api.x.ai/v1/models', authHeader: (k) => ({ Authorization: `Bearer ${k}` }) },
57
+ { provider: 'anthropic', envVar: 'ANTHROPIC_API_KEY', url: 'https://api.anthropic.com/v1/models', authHeader: (key) => ({ 'x-api-key': key, 'anthropic-version': '2023-06-01' }) },
58
+ { provider: 'openai', envVar: 'OPENAI_API_KEY', url: 'https://api.openai.com/v1/models', authHeader: (key) => ({ Authorization: `Bearer ${key}` }) },
40
59
  ];
41
60
  async function checkProviders() {
42
61
  const results = await Promise.allSettled(PROVIDER_CHECKS.map(async (check) => {
@@ -45,7 +64,6 @@ async function checkProviders() {
45
64
  return { provider: check.provider, envVar: check.envVar, keySet: false, verified: false };
46
65
  }
47
66
  try {
48
- // globalThis.fetch is available in Node 18+
49
67
  const res = await globalThis.fetch(check.url, {
50
68
  method: 'GET',
51
69
  headers: check.authHeader(key),
@@ -62,46 +80,75 @@ async function checkProviders() {
62
80
  }));
63
81
  return results.map((r) => r.status === 'fulfilled' ? r.value : { provider: 'unknown', envVar: '', keySet: false, verified: false });
64
82
  }
83
+ // ---------------------------------------------------------------------------
84
+ // Local server
85
+ // ---------------------------------------------------------------------------
65
86
  /**
66
87
  * Creates an Express server for repo-based agent mode.
67
88
  *
68
89
  * Loads the `.amodal/` config from `config.repoPath`, creates a
69
- * `SessionManager`, mounts chat/task/inspect/automation/webhook routes,
70
- * and optionally watches for config changes (hot reload).
90
+ * `StandaloneSessionManager`, mounts all routes, and optionally watches
91
+ * for config changes (hot reload).
71
92
  */
93
+ /**
94
+ * Install a process-level unhandledRejection listener that logs instead
95
+ * of crashing. An escaped rejection is always a bug — we want loud logs,
96
+ * not silent outages. The previous behavior (default Node: print + crash)
97
+ * turned small bugs (one leaked promise) into whole-server downtime for
98
+ * every active session. Logging + continuing preserves service for all
99
+ * other sessions while still surfacing the issue to operators.
100
+ *
101
+ * Idempotent: only installs once per process (the local-server can be
102
+ * created and torn down repeatedly during tests).
103
+ */
104
+ let unhandledRejectionListenerInstalled = false;
105
+ function installUnhandledRejectionLogger() {
106
+ if (unhandledRejectionListenerInstalled)
107
+ return;
108
+ unhandledRejectionListenerInstalled = true;
109
+ process.on('unhandledRejection', (reason) => {
110
+ const err = reason instanceof Error ? reason : new Error(String(reason));
111
+ log.error('unhandled_rejection', {
112
+ name: err.name,
113
+ message: err.message,
114
+ stack: err.stack,
115
+ });
116
+ });
117
+ }
72
118
  export async function createLocalServer(config) {
73
- const repo = await loadRepo({ localPath: config.repoPath });
119
+ installUnhandledRejectionLogger();
120
+ let bundle = await loadRepo({ localPath: config.repoPath });
74
121
  // Check provider API keys in the background at startup
75
122
  let providerStatuses = PROVIDER_CHECKS.map((c) => ({
76
123
  provider: c.provider, envVar: c.envVar, keySet: !!process.env[c.envVar], verified: false,
77
124
  }));
78
- checkProviders().then((results) => {
125
+ void checkProviders().then((results) => {
79
126
  providerStatuses = results;
80
127
  const verified = results.filter((r) => r.verified).map((r) => r.provider);
81
128
  if (verified.length > 0) {
82
- process.stderr.write(`[dev] Provider keys verified: ${verified.join(', ')}\n`);
129
+ log.info('provider_keys_verified', { providers: verified });
83
130
  }
84
131
  const failed = results.filter((r) => r.keySet && !r.verified);
85
132
  for (const f of failed) {
86
- process.stderr.write(`[dev] Provider key invalid: ${f.provider} (${f.error ?? 'unknown'})\n`);
133
+ log.warn('provider_key_invalid', { provider: f.provider, error: f.error });
87
134
  }
88
- }).catch(() => { });
89
- // Create shell executor if sandbox.shellExec is enabled
90
- const shellExecutor = repo.config.sandbox?.shellExec
91
- ? new LocalShellExecutor()
92
- : undefined;
93
- // Create shared store backend if stores are defined
135
+ }).catch((err) => {
136
+ log.error('provider_check_failed', { error: err instanceof Error ? err.message : String(err) });
137
+ });
138
+ // Create custom tool executor
139
+ const toolExecutor = bundle.tools.length > 0 ? new LocalToolExecutor() : undefined;
140
+ // -------------------------------------------------------------------------
141
+ // Store backend
142
+ // -------------------------------------------------------------------------
94
143
  let storeBackend;
95
144
  let storeBackendType = 'none';
96
- if (repo.stores.length > 0) {
97
- const storeConfig = repo.config.stores;
145
+ if (bundle.stores.length > 0) {
146
+ const storeConfig = bundle.config.stores;
98
147
  const backend = storeConfig?.backend ?? 'pglite';
99
148
  if (backend === 'postgres' && storeConfig?.postgresUrl) {
100
- const connUrl = storeConfig.postgresUrl.startsWith('env:')
101
- ? process.env[storeConfig.postgresUrl.slice(4)] ?? ''
102
- : storeConfig.postgresUrl;
149
+ const connUrl = resolveEnvRef(storeConfig.postgresUrl) ?? '';
103
150
  if (!connUrl) {
104
- log.error(`Postgres URL not set (${storeConfig.postgresUrl})`, 'dev');
151
+ log.error('store_postgres_url_missing', { configured: storeConfig.postgresUrl });
105
152
  }
106
153
  else {
107
154
  try {
@@ -109,95 +156,176 @@ export async function createLocalServer(config) {
109
156
  // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- dynamic import for optional postgres backend
110
157
  const mod = await import(pgModPath).catch(() => null);
111
158
  if (mod?.createPostgresStoreBackend) {
112
- storeBackend = await mod.createPostgresStoreBackend(repo.stores, connUrl);
159
+ storeBackend = await mod.createPostgresStoreBackend(bundle.stores, connUrl);
113
160
  storeBackendType = 'postgres';
114
- log.info(`Store backend ready (postgres, ${String(repo.stores.length)} stores)`, 'dev');
161
+ log.info('store_backend_ready', { type: 'postgres', storeCount: bundle.stores.length });
115
162
  }
116
163
  else {
117
- log.error('Postgres backend not available — install @amodalai/store-postgres', 'dev');
164
+ log.error('store_postgres_unavailable', { hint: 'install @amodalai/store-postgres' });
118
165
  }
119
166
  }
120
167
  catch (err) {
121
- log.error(`Postgres store backend failed: ${err instanceof Error ? err.message : String(err)}`, 'dev');
122
- log.info('Falling back to PGLite', 'dev');
168
+ log.error('store_postgres_failed', { error: err instanceof Error ? err.message : String(err) });
169
+ log.info('store_fallback_pglite', {});
123
170
  }
124
171
  }
125
172
  }
126
173
  // Default: PGLite
127
174
  if (!storeBackend) {
128
175
  const dataDir = storeConfig?.dataDir ?? `${config.repoPath}/.amodal/store-data`;
129
- // Check for lock file — another instance may be using this data dir
130
- const lockPath = `${dataDir}/server.lock`;
176
+ // Check for lock file — another instance may be using this data dir.
177
+ // Lock file lives in the PARENT dir (not inside dataDir) so it doesn't
178
+ // conflict with PGLite's own PostgreSQL data files (e.g. postmaster.pid).
179
+ const lockPath = `${dataDir}.lock`;
131
180
  try {
132
181
  const { readFileSync, writeFileSync, mkdirSync, unlinkSync } = await import('node:fs');
133
- mkdirSync(dataDir, { recursive: true });
182
+ const path = await import('node:path');
183
+ mkdirSync(path.dirname(dataDir), { recursive: true });
134
184
  try {
135
185
  const existing = readFileSync(lockPath, 'utf-8').trim();
136
186
  try {
137
187
  process.kill(Number(existing), 0);
138
- log.warn(`Another amodal instance (PID ${existing}) may be using this store directory. PGLite does not support concurrent access.`, 'dev');
188
+ log.warn('store_lock_conflict', { pid: existing });
139
189
  }
140
- catch { /* PID not running, stale lock */ }
190
+ catch { /* PID not running stale lock, safe to overwrite */ }
141
191
  }
142
- catch { /* no lock file */ }
192
+ catch { /* No lock file exists — first run */ }
143
193
  writeFileSync(lockPath, String(process.pid));
144
194
  const lockCleanup = lockPath;
145
195
  process.on('exit', () => { try {
146
196
  unlinkSync(lockCleanup);
147
197
  }
148
- catch { /* */ } });
198
+ catch { /* exit handler — can't log */ } });
199
+ }
200
+ catch (err) {
201
+ log.warn('store_lock_setup_failed', { dataDir, error: err instanceof Error ? err.message : String(err) });
149
202
  }
150
- catch { /* lock file handling failed, proceed anyway */ }
151
203
  try {
152
- storeBackend = await createPGLiteStoreBackend(repo.stores, dataDir);
204
+ storeBackend = await createPGLiteStoreBackend(bundle.stores, dataDir);
153
205
  storeBackendType = 'pglite';
154
- log.info(`Store backend ready (pglite, ${String(repo.stores.length)} stores, dir: ${dataDir})`, 'dev');
206
+ log.info('store_backend_ready', { type: 'pglite', storeCount: bundle.stores.length, dataDir });
155
207
  }
156
208
  catch (err) {
157
- log.error(`Store backend failed to initialize: ${err instanceof Error ? err.message : String(err)}`, 'dev');
158
- log.error(`Try deleting ${dataDir} and restarting`, 'dev');
209
+ const errMsg = err instanceof Error ? err.message : (typeof err === 'object' ? JSON.stringify(err) : String(err));
210
+ log.error('store_backend_init_failed', { error: errMsg, dataDir });
159
211
  }
160
212
  }
161
213
  }
162
- // Session persistence — created before SessionManager so hydration works
163
- const sessionStore = new SessionStore(config.repoPath);
164
- const sessionManager = new SessionManager({
165
- baseParams: {
166
- sessionId: 'local-init',
167
- interactive: false,
168
- noBrowser: true,
169
- debugMode: process.env['DEBUG'] === 'true',
170
- cwd: config.repoPath,
171
- targetDir: config.repoPath,
214
+ // -------------------------------------------------------------------------
215
+ // Runtime event bus (powers /api/events SSE for live UI updates)
216
+ // -------------------------------------------------------------------------
217
+ const eventBus = new RuntimeEventBus({
218
+ onListenerError: (err, event) => {
219
+ log.warn('event_bus_listener_error', {
220
+ seq: event.seq,
221
+ type: event.type,
222
+ error: err instanceof Error ? err.message : String(err),
223
+ });
172
224
  },
225
+ });
226
+ // Wrap the store backend so every write emits store_updated events.
227
+ // Covers every write path through one seam: tools, REST routes, admin
228
+ // file tools, task execution — they all go through this backend.
229
+ if (storeBackend) {
230
+ storeBackend = wrapStoreBackendWithEvents(storeBackend, eventBus);
231
+ }
232
+ // -------------------------------------------------------------------------
233
+ // Session manager (new standalone stack)
234
+ // -------------------------------------------------------------------------
235
+ const sessionLogger = createLogger({ component: 'session-manager' });
236
+ const sessionDataDir = `${config.repoPath}/.amodal/session-data`;
237
+ const sessionStore = await selectSessionStore({
238
+ backend: bundle.config.stores?.backend,
239
+ postgresUrl: resolveEnvRef(bundle.config.stores?.postgresUrl),
240
+ logger: sessionLogger,
241
+ dataDir: sessionDataDir,
242
+ });
243
+ const sessionManager = new StandaloneSessionManager({
244
+ logger: sessionLogger,
245
+ store: sessionStore,
173
246
  ttlMs: config.sessionTtlMs,
174
- bundle: repo,
175
- shellExecutor,
176
- storeBackend,
177
- sessionStore: {
178
- async getSession(sessionId) {
179
- const persisted = sessionStore.load(sessionId);
180
- if (!persisted || !persisted.conversationHistory.length)
181
- return null;
182
- return {
183
- id: persisted.id,
184
- app_id: persisted.appId,
185
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Persisted data
186
- messages: persisted.conversationHistory,
187
- status: 'completed',
188
- };
189
- },
190
- },
247
+ eventBus,
191
248
  });
192
- const runner = new ProactiveRunner(repo, {
249
+ sessionManager.start();
250
+ // -------------------------------------------------------------------------
251
+ // MCP connections (shared across sessions)
252
+ // -------------------------------------------------------------------------
253
+ let mcpManager = null;
254
+ {
255
+ const { McpManager } = await import('@amodalai/core');
256
+ const mcpConfigs = buildMcpConfigs(bundle);
257
+ if (Object.keys(mcpConfigs).length > 0) {
258
+ const manager = new McpManager();
259
+ try {
260
+ await manager.startServers(mcpConfigs);
261
+ if (manager.connectedCount > 0) {
262
+ mcpManager = manager;
263
+ const tools = manager.getDiscoveredTools();
264
+ log.info('mcp_initialized', { servers: manager.connectedCount, tools: tools.length });
265
+ }
266
+ }
267
+ catch (err) {
268
+ log.error('mcp_init_failed', { error: err instanceof Error ? err.message : String(err) });
269
+ }
270
+ }
271
+ }
272
+ // -------------------------------------------------------------------------
273
+ // Shared resources for route handlers
274
+ // -------------------------------------------------------------------------
275
+ const shared = {
276
+ storeBackend: storeBackend ?? null,
277
+ mcpManager,
278
+ logger: log,
279
+ toolExecutor,
280
+ };
281
+ // Helper: get current bundle (updated by config watcher)
282
+ const getBundle = () => bundle;
283
+ // Helper: create automation session components
284
+ const createAutomationSessionComponents = () => {
285
+ const components = buildSessionComponents({
286
+ bundle,
287
+ storeBackend: storeBackend ?? null,
288
+ mcpManager,
289
+ logger: log,
290
+ toolExecutor,
291
+ sessionType: 'automation',
292
+ });
293
+ const session = sessionManager.create({
294
+ provider: components.provider,
295
+ toolRegistry: components.toolRegistry,
296
+ permissionChecker: components.permissionChecker,
297
+ systemPrompt: components.systemPrompt,
298
+ userRoles: components.userRoles,
299
+ toolContextFactory: components.toolContextFactory,
300
+ appId: LOCAL_APP_ID,
301
+ });
302
+ return { session, toolContextFactory: components.toolContextFactory };
303
+ };
304
+ // -------------------------------------------------------------------------
305
+ // Proactive runner
306
+ // -------------------------------------------------------------------------
307
+ const runner = new ProactiveRunner(bundle, {
308
+ sessionManager,
309
+ createSessionComponents: createAutomationSessionComponents,
310
+ logger: log,
193
311
  webhookSecret: config.webhookSecret,
194
- createSession: async () => sessionManager.create(LOCAL_APP_ID),
195
- destroySession: async (id) => sessionManager.destroy(id),
312
+ summarizeToolResult: config.summarizeToolResult,
196
313
  onSessionComplete: (session, automationName) => {
197
- sessionStore.save(session, automationName);
314
+ // Tag the automation name onto metadata so the UI can filter
315
+ // sessions by automation via /sessions?automation=<name>.
316
+ session.metadata.automationName = automationName;
317
+ void sessionManager.persist(session);
198
318
  },
319
+ eventBus,
320
+ onAutomationResult: config.onAutomationResult,
199
321
  });
322
+ // -------------------------------------------------------------------------
323
+ // Config watcher (hot reload)
324
+ // -------------------------------------------------------------------------
200
325
  let watcher = null;
326
+ // -------------------------------------------------------------------------
327
+ // Express app
328
+ // -------------------------------------------------------------------------
201
329
  const app = express();
202
330
  // CORS
203
331
  const corsOrigin = config.corsOrigin ?? '*';
@@ -227,9 +355,9 @@ export async function createLocalServer(config) {
227
355
  app.post('/auth/token', (_req, res) => {
228
356
  res.json({ token: '', expires_at: null });
229
357
  });
230
- // Unified config endpoint — same path as hosted, different response
358
+ // Unified config endpoint
231
359
  app.get('/api/config', (_req, res) => {
232
- const bundleData = sessionManager.getBundle();
360
+ const bundleData = getBundle();
233
361
  const cfg = bundleData.config;
234
362
  // Collect all env:* references from connection specs
235
363
  const envRefs = [];
@@ -241,11 +369,8 @@ export async function createLocalServer(config) {
241
369
  }
242
370
  }
243
371
  res.json({
244
- // Common fields (used by useHostedConfig)
245
372
  appId: LOCAL_APP_ID,
246
373
  appName: cfg?.name ?? '',
247
- // No authMode — signals to the SPA that no auth is needed
248
- // Local dev fields (used by config pages)
249
374
  name: cfg?.name ?? '',
250
375
  version: cfg?.version ?? '',
251
376
  description: cfg?.description ?? '',
@@ -262,57 +387,61 @@ export async function createLocalServer(config) {
262
387
  // Resolve resume session ID
263
388
  let resumeSessionId = config.resumeSessionId;
264
389
  if (resumeSessionId === 'latest') {
265
- resumeSessionId = sessionStore.latest() ?? undefined;
390
+ const { sessions: recent } = await sessionStore.list({
391
+ limit: 1,
392
+ filter: { appId: LOCAL_APP_ID },
393
+ });
394
+ resumeSessionId = recent[0]?.id;
266
395
  }
267
396
  if (resumeSessionId) {
268
- log.debug(`Resume session: ${resumeSessionId}`, 'dev');
397
+ log.debug('resume_session', { sessionId: resumeSessionId });
269
398
  }
270
399
  // Client config — tells the web UI which session to resume
271
400
  app.get('/config', (_req, res) => {
272
401
  res.json({ resumeSessionId: resumeSessionId ?? null });
273
402
  });
274
- // Sessions endpoints
275
- app.get('/sessions', (req, res) => {
403
+ // Sessions endpoints — served directly from the DrizzleSessionStore.
404
+ //
405
+ // Dev-UI consumers (sidebar Recent list, Sessions page, Automation detail
406
+ // page) don't paginate — they render what they get and slice the top N.
407
+ // A 500-session ceiling keeps the response bounded without forcing a
408
+ // cursor API on the client today. If dev sessions regularly exceed this,
409
+ // the store already supports cursor pagination via SessionListOptions.
410
+ const SESSION_LIST_LIMIT = 500;
411
+ app.get('/sessions', asyncHandler(async (req, res) => {
276
412
  const automationFilter = typeof req.query?.['automation'] === 'string' ? String(req.query['automation']) : undefined;
277
- const all = sessionStore.list();
278
- // Filter out eval and admin sessions from chat history
279
- const visible = all.filter((s) => s.appId !== 'eval-runner' && s.appId !== 'admin');
280
- const filtered = automationFilter ? visible.filter((s) => s.automationName === automationFilter) : visible;
281
- res.json({ sessions: filtered });
282
- });
283
- app.get('/session/:id', (req, res) => {
284
- const persisted = sessionStore.load(req.params['id'] ?? '');
413
+ // Automation filter uses metadata.automationName; otherwise restrict
414
+ // to chat sessions by metadata.appId (excludes eval-runner / admin).
415
+ const filter = automationFilter
416
+ ? { automationName: automationFilter }
417
+ : { appId: LOCAL_APP_ID };
418
+ const { sessions: rows } = await sessionStore.list({ limit: SESSION_LIST_LIMIT, filter });
419
+ const sessions = rows.map((s) => {
420
+ const title = typeof s.metadata['title'] === 'string' ? s.metadata['title'] : undefined;
421
+ const appId = typeof s.metadata['appId'] === 'string' ? s.metadata['appId'] : LOCAL_APP_ID;
422
+ const automationName = typeof s.metadata['automationName'] === 'string' ? s.metadata['automationName'] : undefined;
423
+ return {
424
+ id: s.id,
425
+ appId,
426
+ title,
427
+ summary: title ?? extractFirstUserText(s.messages) ?? 'Untitled',
428
+ createdAt: s.createdAt.getTime(),
429
+ lastAccessedAt: s.updatedAt.getTime(),
430
+ automationName,
431
+ };
432
+ });
433
+ res.json({ sessions });
434
+ }));
435
+ app.get('/session/:id', asyncHandler(async (req, res) => {
436
+ const persisted = await sessionStore.load(req.params['id'] ?? '');
285
437
  if (!persisted) {
286
438
  res.status(404).json({ error: 'Session not found' });
287
439
  return;
288
440
  }
289
- // Convert persisted messages to the format the UI expects.
290
- // Supports both SessionMessage format ({type, text}) and legacy LLMMessage ({role, content}).
291
- const messages = persisted.conversationHistory.map((msg) => {
292
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Persisted message
293
- const m = msg;
294
- // SessionMessage format (new): {type: 'user'|'assistant_text'|'error', text, toolCalls?, ...}
295
- if (m['type'] === 'user') {
296
- return { role: 'user', text: String(m['text'] ?? '') };
297
- }
298
- if (m['type'] === 'assistant_text') {
299
- return { role: 'assistant', text: String(m['text'] ?? ''), toolCalls: m['toolCalls'] };
300
- }
301
- // Legacy LLMMessage format: {role: 'user'|'assistant', content: string|Block[]}
302
- if (m['role'] === 'user') {
303
- return { role: 'user', text: typeof m['content'] === 'string' ? m['content'] : '' };
304
- }
305
- if (m['role'] === 'assistant') {
306
- const blocks = Array.isArray(m['content']) ? m['content'] : [];
307
- const isTextBlock = (b) => typeof b === 'object' && b !== null && 'type' in b && b['type'] === 'text' && 'text' in b;
308
- const text = blocks.filter(isTextBlock).map((b) => b.text).join('');
309
- return { role: 'assistant', text };
310
- }
311
- return { role: String(m['role'] ?? m['type'] ?? 'unknown'), text: String(m['text'] ?? '') };
312
- });
441
+ const messages = persisted.messages.map(flattenModelMessage).filter((m) => m !== null);
313
442
  res.json({ session_id: persisted.id, messages });
314
- });
315
- app.patch('/session/:id', express.json(), (req, res) => {
443
+ }));
444
+ app.patch('/session/:id', express.json(), asyncHandler(async (req, res) => {
316
445
  const sessionId = req.params['id'] ?? '';
317
446
  const body = req.body;
318
447
  if (!body || typeof body !== 'object' || !('title' in body) || typeof body['title'] !== 'string') {
@@ -321,56 +450,85 @@ export async function createLocalServer(config) {
321
450
  }
322
451
  // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- validated above
323
452
  const title = body['title'];
324
- const updated = sessionStore.updateTitle(sessionId, title);
325
- if (!updated) {
326
- res.status(404).json({ error: 'Session not found' });
327
- return;
453
+ // Live session: mutate metadata on the shared object and persist so
454
+ // the next /sessions read reflects the new title. A concurrent
455
+ // runMessage may be mid-turn, but JSON.stringify runs atomically in
456
+ // JS's single-threaded event loop — no torn writes. The next
457
+ // end-of-turn persist will overwrite with the completed messages
458
+ // array; metadata.title stays because it's on the live session.
459
+ //
460
+ // Not-live: load → mutate → save. No race possible.
461
+ const live = sessionManager.get(sessionId);
462
+ if (live) {
463
+ live.metadata.title = title;
464
+ await sessionManager.persist(live);
328
465
  }
329
- // Also update in-memory session if active
330
- const activeSession = sessionManager.get(sessionId);
331
- if (activeSession) {
332
- activeSession.title = title;
466
+ else {
467
+ const persisted = await sessionStore.load(sessionId);
468
+ if (!persisted) {
469
+ res.status(404).json({ error: 'Session not found' });
470
+ return;
471
+ }
472
+ persisted.metadata.title = title;
473
+ persisted.updatedAt = new Date();
474
+ await sessionStore.save(persisted);
333
475
  }
476
+ // Emit session_updated so the sidebar picks up the new title live.
477
+ eventBus.emit({ type: 'session_updated', sessionId, appId: LOCAL_APP_ID, title });
334
478
  res.json({ ok: true });
335
- });
336
- app.delete('/session/:id', (req, res) => {
479
+ }));
480
+ app.delete('/session/:id', asyncHandler(async (req, res) => {
337
481
  const sessionId = req.params['id'] ?? '';
338
- // Destroy in-memory session if active
339
- void sessionManager.destroy(sessionId);
340
- const deleted = sessionStore.delete(sessionId);
482
+ await sessionManager.destroy(sessionId);
483
+ const deleted = await sessionStore.delete(sessionId);
341
484
  if (!deleted) {
342
485
  res.status(404).json({ error: 'Session not found' });
343
486
  return;
344
487
  }
488
+ eventBus.emit({ type: 'session_deleted', sessionId });
345
489
  res.json({ ok: true });
346
- });
490
+ }));
347
491
  // File browser/editor
348
492
  app.use(createFilesRouter({ repoPath: config.repoPath }));
493
+ // Event bus SSE stream (live UI updates)
494
+ app.use(createEventsRouter({ bus: eventBus, logger: log }));
349
495
  // Evals
350
496
  const evalStore = new EvalStore(config.repoPath);
351
- app.use(createEvalRouter({ sessionManager, evalStore, repoPath: config.repoPath, getPort: () => config.port }));
497
+ app.use(createEvalRouter({ getBundle, evalStore, repoPath: config.repoPath, getPort: () => config.port }));
352
498
  // Feedback
353
499
  const feedbackStore = new FeedbackStore(config.repoPath);
354
500
  app.use(createFeedbackRouter({ feedbackStore }));
355
- // Routes
501
+ // Chat routes (new stack) — persistence is handled inside runMessage /
502
+ // route-helpers, so no explicit hooks are needed here.
356
503
  app.use(createChatStreamRouter({
357
504
  sessionManager,
358
- createStreamHooks: () => ({
359
- onSessionPersist: (sessionId) => {
360
- const session = sessionManager.get(sessionId);
361
- if (session)
362
- sessionStore.save(session);
363
- },
364
- }),
505
+ bundleResolver: { staticBundle: bundle },
506
+ shared,
507
+ summarizeToolResult: config.summarizeToolResult,
508
+ }));
509
+ app.use(createChatRouter({
510
+ sessionManager,
511
+ bundleResolver: { staticBundle: bundle },
512
+ shared,
513
+ summarizeToolResult: config.summarizeToolResult,
365
514
  }));
366
- app.use(createTaskRouter({ sessionManager }));
367
- app.use(createAdminChatRouter({ sessionManager, getPort: () => config.port }));
368
- app.use(createInspectRouter({ sessionManager, repoPath: config.repoPath }));
515
+ // Task runner
516
+ app.use(createTaskRouter({ sessionManager, createTaskSession: createAutomationSessionComponents }));
517
+ // Admin chat (new stack)
518
+ app.use(createAdminChatRouter({
519
+ sessionManager,
520
+ shared,
521
+ getBundle,
522
+ getPort: () => config.port,
523
+ }));
524
+ // Inspect
525
+ app.use(createInspectRouter({ getBundle, repoPath: config.repoPath }));
526
+ // Automations
369
527
  app.use(createAutomationRouter({ runner }));
370
528
  app.use(createWebhookRouter({ runner, webhookSecret: config.webhookSecret }));
371
529
  // Store REST API (if stores are defined)
372
530
  if (storeBackend) {
373
- app.use(createStoresRouter({ repo, storeBackend, appId: LOCAL_APP_ID }));
531
+ app.use(createStoresRouter({ repo: bundle, storeBackend, appId: LOCAL_APP_ID }));
374
532
  }
375
533
  // Build user pages (if pages/ directory exists)
376
534
  let builtPages = [];
@@ -378,14 +536,12 @@ export async function createLocalServer(config) {
378
536
  const result = await buildPages(config.repoPath);
379
537
  builtPages = result.pages;
380
538
  if (builtPages.length > 0) {
381
- log.info(`Built ${String(builtPages.length)} page(s)`, 'dev');
382
- // Serve compiled page bundles
539
+ log.info('pages_built', { count: builtPages.length });
383
540
  app.use('/pages-bundle', express.static(result.outDir));
384
541
  }
385
542
  }
386
543
  catch (err) {
387
- const msg = err instanceof Error ? err.message : String(err);
388
- log.error(`Page build failed: ${msg}`, 'dev');
544
+ log.error('pages_build_failed', { error: err instanceof Error ? err.message : String(err) });
389
545
  }
390
546
  // Pages list endpoint
391
547
  app.get('/api/pages', (_req, res) => {
@@ -398,11 +554,8 @@ export async function createLocalServer(config) {
398
554
  app.use(config.appMiddleware);
399
555
  }
400
556
  else if (config.staticAppDir && existsSync(config.staticAppDir)) {
401
- // Serve pre-built SPA static assets with index.html fallback
402
557
  app.use(express.static(config.staticAppDir));
403
- // SPA fallback — serve index.html for any non-API, non-static route
404
558
  app.use((_req, res, next) => {
405
- // Don't intercept API or inspect routes (already handled above)
406
559
  if (_req.path.startsWith('/api/') || _req.path.startsWith('/inspect/') || _req.method !== 'GET') {
407
560
  next();
408
561
  return;
@@ -427,17 +580,18 @@ export async function createLocalServer(config) {
427
580
  // Start hot reload watcher
428
581
  if (config.hotReload) {
429
582
  watcher = new ConfigWatcher(config.repoPath, (newBundle) => {
430
- sessionManager.updateBundle(newBundle);
583
+ bundle = newBundle;
584
+ // Shared resources and session components will pick up the new
585
+ // bundle on next session creation via getBundle().
586
+ log.info('config_reloaded', { name: newBundle.config.name });
587
+ eventBus.emit({ type: 'manifest_changed' });
588
+ eventBus.emit({ type: 'files_changed' });
431
589
  });
432
590
  watcher.start();
433
591
  }
434
592
  return new Promise((resolve) => {
435
593
  const httpServer = app.listen(port, host, () => {
436
- log.info(`Repo server listening on ${host}:${port}`);
437
- log.info(`Repo: ${config.repoPath}`);
438
- if (config.hotReload) {
439
- log.info('Hot reload enabled');
440
- }
594
+ log.info('server_started', { host, port, repoPath: config.repoPath, hotReload: !!config.hotReload });
441
595
  resolve(httpServer);
442
596
  });
443
597
  server = httpServer;
@@ -462,11 +616,92 @@ export async function createLocalServer(config) {
462
616
  server = null;
463
617
  }
464
618
  await sessionManager.shutdown();
619
+ if (mcpManager) {
620
+ await mcpManager.shutdown();
621
+ }
465
622
  if (storeBackend) {
466
623
  await storeBackend.close();
467
624
  }
468
- log.info('Repo server stopped');
625
+ log.info('server_stopped', {});
469
626
  },
470
627
  };
471
628
  }
629
+ // ---------------------------------------------------------------------------
630
+ // /sessions + /session/:id response helpers
631
+ // ---------------------------------------------------------------------------
632
+ /** Max length of the first-user-message excerpt shown in session lists. */
633
+ const SUMMARY_EXCERPT_MAX = 80;
634
+ function isRecord(x) {
635
+ return typeof x === 'object' && x !== null;
636
+ }
637
+ function isTextPart(part) {
638
+ return isRecord(part) && part['type'] === 'text' && typeof part['text'] === 'string';
639
+ }
640
+ function isToolCallPart(part) {
641
+ return (isRecord(part) &&
642
+ part['type'] === 'tool-call' &&
643
+ typeof part['toolCallId'] === 'string' &&
644
+ typeof part['toolName'] === 'string');
645
+ }
646
+ function getMessageRole(raw) {
647
+ if (!isRecord(raw))
648
+ return null;
649
+ const role = raw['role'];
650
+ return typeof role === 'string' ? role : null;
651
+ }
652
+ function getMessageContent(raw) {
653
+ if (!isRecord(raw))
654
+ return undefined;
655
+ return raw['content'];
656
+ }
657
+ /** Truncate with an ellipsis when the source exceeds the excerpt budget. */
658
+ function excerpt(s) {
659
+ return s.length > SUMMARY_EXCERPT_MAX ? `${s.slice(0, SUMMARY_EXCERPT_MAX)}…` : s;
660
+ }
661
+ /** Extract the first user-message text from a persisted message array for list summaries. */
662
+ function extractFirstUserText(messages) {
663
+ for (const raw of messages) {
664
+ if (getMessageRole(raw) !== 'user')
665
+ continue;
666
+ const content = getMessageContent(raw);
667
+ if (typeof content === 'string')
668
+ return excerpt(content);
669
+ if (Array.isArray(content)) {
670
+ const firstText = content.find(isTextPart);
671
+ if (firstText)
672
+ return excerpt(firstText.text);
673
+ }
674
+ }
675
+ return undefined;
676
+ }
677
+ /**
678
+ * Flatten a persisted `ModelMessage` (ai SDK v6) into the shape the web UI's
679
+ * /session/:id consumer expects: {role, text, toolCalls?}. Returns null for
680
+ * tool-result messages and for assistant turns with no renderable content
681
+ * (the history panel shows conversation + tool-call chips, not raw tool
682
+ * plumbing).
683
+ */
684
+ function flattenModelMessage(raw) {
685
+ const role = getMessageRole(raw);
686
+ if (role !== 'user' && role !== 'assistant')
687
+ return null;
688
+ const content = getMessageContent(raw);
689
+ if (typeof content === 'string') {
690
+ return { role, text: content };
691
+ }
692
+ if (Array.isArray(content)) {
693
+ const text = content.filter(isTextPart).map((p) => p.text).join('');
694
+ const toolCalls = role === 'assistant'
695
+ ? content.filter(isToolCallPart).map((p) => ({
696
+ toolId: p.toolCallId,
697
+ toolName: p.toolName,
698
+ parameters: isRecord(p.input) ? p.input : {},
699
+ }))
700
+ : [];
701
+ if (text.length === 0 && toolCalls.length === 0)
702
+ return null;
703
+ return toolCalls.length > 0 ? { role, text, toolCalls } : { role, text };
704
+ }
705
+ return null;
706
+ }
472
707
  //# sourceMappingURL=local-server.js.map