nodal-agents 0.7.8 → 0.7.95

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 (226) hide show
  1. package/README.md +7 -6
  2. package/cli.js +73 -17
  3. package/migrations/0066_schedule_notify_channel.sql +19 -0
  4. package/migrations/0067_webhook_notify_channel.sql +12 -0
  5. package/migrations/meta/_journal.json +14 -0
  6. package/package.json +1 -1
  7. package/runner.js +407 -178
  8. package/web/.next/BUILD_ID +1 -1
  9. package/web/.next/app-path-routes-manifest.json +8 -8
  10. package/web/.next/build-manifest.json +2 -2
  11. package/web/.next/prerender-manifest.json +3 -3
  12. package/web/.next/required-server-files.json +1 -0
  13. package/web/.next/server/app/(dashboard)/agents/[id]/channels/page.js +2 -2
  14. package/web/.next/server/app/(dashboard)/agents/[id]/channels/page.js.nft.json +1 -1
  15. package/web/.next/server/app/(dashboard)/agents/[id]/channels/page_client-reference-manifest.js +1 -1
  16. package/web/.next/server/app/(dashboard)/agents/[id]/edit/page.js +5 -5
  17. package/web/.next/server/app/(dashboard)/agents/[id]/edit/page.js.nft.json +1 -1
  18. package/web/.next/server/app/(dashboard)/agents/[id]/edit/page_client-reference-manifest.js +1 -1
  19. package/web/.next/server/app/(dashboard)/agents/[id]/telegram/page.js +1 -1
  20. package/web/.next/server/app/(dashboard)/agents/[id]/telegram/page.js.nft.json +1 -1
  21. package/web/.next/server/app/(dashboard)/agents/[id]/telegram/page_client-reference-manifest.js +1 -1
  22. package/web/.next/server/app/(dashboard)/agents/page.js +2 -2
  23. package/web/.next/server/app/(dashboard)/agents/page.js.nft.json +1 -1
  24. package/web/.next/server/app/(dashboard)/agents/page_client-reference-manifest.js +1 -1
  25. package/web/.next/server/app/(dashboard)/approvals/page.js +2 -2
  26. package/web/.next/server/app/(dashboard)/approvals/page.js.nft.json +1 -1
  27. package/web/.next/server/app/(dashboard)/approvals/page_client-reference-manifest.js +1 -1
  28. package/web/.next/server/app/(dashboard)/automations/page.js +2 -2
  29. package/web/.next/server/app/(dashboard)/automations/page.js.nft.json +1 -1
  30. package/web/.next/server/app/(dashboard)/automations/page_client-reference-manifest.js +1 -1
  31. package/web/.next/server/app/(dashboard)/chat/page.js +2 -2
  32. package/web/.next/server/app/(dashboard)/chat/page.js.nft.json +1 -1
  33. package/web/.next/server/app/(dashboard)/chat/page_client-reference-manifest.js +1 -1
  34. package/web/.next/server/app/(dashboard)/connectors/page.js +2 -2
  35. package/web/.next/server/app/(dashboard)/connectors/page.js.nft.json +1 -1
  36. package/web/.next/server/app/(dashboard)/connectors/page_client-reference-manifest.js +1 -1
  37. package/web/.next/server/app/(dashboard)/credentials/page.js +2 -2
  38. package/web/.next/server/app/(dashboard)/credentials/page.js.nft.json +1 -1
  39. package/web/.next/server/app/(dashboard)/credentials/page_client-reference-manifest.js +1 -1
  40. package/web/.next/server/app/(dashboard)/jobs/[id]/page.js +2 -2
  41. package/web/.next/server/app/(dashboard)/jobs/[id]/page.js.nft.json +1 -1
  42. package/web/.next/server/app/(dashboard)/jobs/[id]/page_client-reference-manifest.js +1 -1
  43. package/web/.next/server/app/(dashboard)/jobs/page.js +2 -2
  44. package/web/.next/server/app/(dashboard)/jobs/page.js.nft.json +1 -1
  45. package/web/.next/server/app/(dashboard)/jobs/page_client-reference-manifest.js +1 -1
  46. package/web/.next/server/app/(dashboard)/learned-skills/page.js +2 -2
  47. package/web/.next/server/app/(dashboard)/learned-skills/page.js.nft.json +1 -1
  48. package/web/.next/server/app/(dashboard)/learned-skills/page_client-reference-manifest.js +1 -1
  49. package/web/.next/server/app/(dashboard)/llm-providers/page.js +2 -2
  50. package/web/.next/server/app/(dashboard)/llm-providers/page.js.nft.json +1 -1
  51. package/web/.next/server/app/(dashboard)/llm-providers/page_client-reference-manifest.js +1 -1
  52. package/web/.next/server/app/(dashboard)/logs/page.js +2 -2
  53. package/web/.next/server/app/(dashboard)/logs/page.js.nft.json +1 -1
  54. package/web/.next/server/app/(dashboard)/logs/page_client-reference-manifest.js +1 -1
  55. package/web/.next/server/app/(dashboard)/mcp/page.js +2 -2
  56. package/web/.next/server/app/(dashboard)/mcp/page.js.nft.json +1 -1
  57. package/web/.next/server/app/(dashboard)/mcp/page_client-reference-manifest.js +1 -1
  58. package/web/.next/server/app/(dashboard)/memories/page.js +2 -2
  59. package/web/.next/server/app/(dashboard)/memories/page.js.nft.json +1 -1
  60. package/web/.next/server/app/(dashboard)/memories/page_client-reference-manifest.js +1 -1
  61. package/web/.next/server/app/(dashboard)/page.js +4 -4
  62. package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
  63. package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  64. package/web/.next/server/app/(dashboard)/settings/page.js +2 -2
  65. package/web/.next/server/app/(dashboard)/settings/page.js.nft.json +1 -1
  66. package/web/.next/server/app/(dashboard)/settings/page_client-reference-manifest.js +1 -1
  67. package/web/.next/server/app/(dashboard)/settings/root-context/page.js +2 -2
  68. package/web/.next/server/app/(dashboard)/settings/root-context/page.js.nft.json +1 -1
  69. package/web/.next/server/app/(dashboard)/settings/root-context/page_client-reference-manifest.js +1 -1
  70. package/web/.next/server/app/(dashboard)/skills/[id]/edit/page.js +2 -2
  71. package/web/.next/server/app/(dashboard)/skills/[id]/edit/page.js.nft.json +1 -1
  72. package/web/.next/server/app/(dashboard)/skills/[id]/edit/page_client-reference-manifest.js +1 -1
  73. package/web/.next/server/app/(dashboard)/skills/new/page.js +1 -1
  74. package/web/.next/server/app/(dashboard)/skills/new/page.js.nft.json +1 -1
  75. package/web/.next/server/app/(dashboard)/skills/new/page_client-reference-manifest.js +1 -1
  76. package/web/.next/server/app/(dashboard)/skills/page.js +2 -2
  77. package/web/.next/server/app/(dashboard)/skills/page.js.nft.json +1 -1
  78. package/web/.next/server/app/(dashboard)/skills/page_client-reference-manifest.js +1 -1
  79. package/web/.next/server/app/_global-error/page.js +2 -2
  80. package/web/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  81. package/web/.next/server/app/_global-error.html +1 -1
  82. package/web/.next/server/app/_global-error.rsc +3 -3
  83. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +3 -3
  84. package/web/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  85. package/web/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  86. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  87. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  88. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  89. package/web/.next/server/app/_not-found/page.js +1 -1
  90. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  91. package/web/.next/server/app/_not-found.html +1 -1
  92. package/web/.next/server/app/_not-found.rsc +4 -4
  93. package/web/.next/server/app/_not-found.segments/_full.segment.rsc +4 -4
  94. package/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  95. package/web/.next/server/app/_not-found.segments/_index.segment.rsc +4 -4
  96. package/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  97. package/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  98. package/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  99. package/web/.next/server/app/login/page.js +2 -2
  100. package/web/.next/server/app/login/page_client-reference-manifest.js +1 -1
  101. package/web/.next/server/app/onboarding/page.js +192 -192
  102. package/web/.next/server/app/onboarding/page_client-reference-manifest.js +1 -1
  103. package/web/.next/server/app-paths-manifest.json +8 -8
  104. package/web/.next/server/chunks/1466.js +1 -0
  105. package/web/.next/server/chunks/1767.js +1 -0
  106. package/web/.next/server/chunks/3308.js +1 -0
  107. package/web/.next/server/chunks/4162.js +1 -1
  108. package/web/.next/server/chunks/4574.js +1 -1
  109. package/web/.next/server/chunks/5300.js +1 -0
  110. package/web/.next/server/chunks/6184.js +1 -0
  111. package/web/.next/server/chunks/6539.js +1 -0
  112. package/web/.next/server/chunks/7124.js +20 -20
  113. package/web/.next/server/chunks/8380.js +1 -0
  114. package/web/.next/server/chunks/9000.js +1 -0
  115. package/web/.next/server/chunks/9137.js +1 -0
  116. package/web/.next/server/chunks/{3847.js → 9507.js} +4 -4
  117. package/web/.next/server/chunks/9982.js +1 -0
  118. package/web/.next/server/middleware-build-manifest.js +1 -1
  119. package/web/.next/server/pages/404.html +1 -1
  120. package/web/.next/server/pages/500.html +1 -1
  121. package/web/.next/server/server-reference-manifest.js +1 -1
  122. package/web/.next/server/server-reference-manifest.json +1 -1
  123. package/web/.next/static/chunks/1498-d0d434749bb18cd2.js +1 -0
  124. package/web/.next/static/chunks/2406-66d8aa55b97d75c3.js +1 -0
  125. package/web/.next/static/chunks/{2491-cc8dee1fc55e1e38.js → 2491-b14243a2db419dd9.js} +1 -1
  126. package/web/.next/static/chunks/3966-679a4c07d6db8ee6.js +1 -0
  127. package/web/.next/static/chunks/4095-f76de40c53ae3d47.js +1 -0
  128. package/web/.next/static/chunks/4766-d80c537321cca448.js +1 -0
  129. package/web/.next/static/chunks/5876-4423499f49214575.js +1 -0
  130. package/web/.next/static/chunks/5953-54a29fc1e2d84c0c.js +1 -0
  131. package/web/.next/static/chunks/7829-78f753c50b408cde.js +1 -0
  132. package/web/.next/static/chunks/8542-ba178b7c68e14cf3.js +1 -0
  133. package/web/.next/static/chunks/8719-8ab59c5faa49c9ab.js +1 -0
  134. package/web/.next/static/chunks/9387-f5283e69fe4d4e83.js +1 -0
  135. package/web/.next/static/chunks/9567-db1512a42580d07c.js +1 -0
  136. package/web/.next/static/chunks/{8097-bad68cd78aa59a10.js → 9583-f93faba69d4c1f57.js} +1 -1
  137. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/channels/page-d40d5db150472c4f.js +1 -0
  138. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/edit/page-74934ff99c3b1261.js +6 -0
  139. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/telegram/page-d40d5db150472c4f.js +1 -0
  140. package/web/.next/static/chunks/app/(dashboard)/agents/page-8ec50d6f48425813.js +1 -0
  141. package/web/.next/static/chunks/app/(dashboard)/approvals/page-d907a7c12917ab8a.js +1 -0
  142. package/web/.next/static/chunks/app/(dashboard)/automations/page-dd20cb7f04d22cca.js +1 -0
  143. package/web/.next/static/chunks/app/(dashboard)/chat/page-e9ffd3d09a46ad38.js +1 -0
  144. package/web/.next/static/chunks/app/(dashboard)/connectors/page-52828e317e7a63e6.js +1 -0
  145. package/web/.next/static/chunks/app/(dashboard)/credentials/page-f84a3f991cf69228.js +1 -0
  146. package/web/.next/static/chunks/app/(dashboard)/jobs/[id]/page-a0866da7952de417.js +1 -0
  147. package/web/.next/static/chunks/app/(dashboard)/jobs/page-aade90b389efb824.js +1 -0
  148. package/web/.next/static/chunks/app/(dashboard)/layout-10486dd8640fb6a0.js +1 -0
  149. package/web/.next/static/chunks/app/(dashboard)/learned-skills/page-e22d1e586ac8579c.js +1 -0
  150. package/web/.next/static/chunks/app/(dashboard)/llm-providers/page-46858f7d737ee49b.js +1 -0
  151. package/web/.next/static/chunks/app/(dashboard)/logs/page-17dd2080f6196b01.js +1 -0
  152. package/web/.next/static/chunks/app/(dashboard)/mcp/page-81e83a08c7b65719.js +1 -0
  153. package/web/.next/static/chunks/app/(dashboard)/memories/page-0336ba541b5ab006.js +1 -0
  154. package/web/.next/static/chunks/app/(dashboard)/page-3bf3a72ec95863d8.js +1 -0
  155. package/web/.next/static/chunks/app/(dashboard)/settings/page-0ee25e256bc3097f.js +1 -0
  156. package/web/.next/static/chunks/app/(dashboard)/settings/root-context/page-76653439bca06d8c.js +1 -0
  157. package/web/.next/static/chunks/app/(dashboard)/skills/[id]/edit/page-de1bf934a2c5e8b2.js +1 -0
  158. package/web/.next/static/chunks/app/(dashboard)/skills/new/page-a9640274b845190a.js +1 -0
  159. package/web/.next/static/chunks/app/(dashboard)/skills/page-928b6f005ba4dafb.js +1 -0
  160. package/web/.next/static/chunks/app/_global-error/page-d40d5db150472c4f.js +1 -0
  161. package/web/.next/static/chunks/app/_not-found/page-d40d5db150472c4f.js +1 -0
  162. package/web/.next/static/chunks/app/api/auth/[...all]/route-d40d5db150472c4f.js +1 -0
  163. package/web/.next/static/chunks/app/api/health/route-d40d5db150472c4f.js +1 -0
  164. package/web/.next/static/chunks/app/api/oauth/[provider]/callback/route-d40d5db150472c4f.js +1 -0
  165. package/web/.next/static/chunks/app/api/oauth/[provider]/start/route-d40d5db150472c4f.js +1 -0
  166. package/web/.next/static/chunks/app/auth/callback/route-d40d5db150472c4f.js +1 -0
  167. package/web/.next/static/chunks/app/error-ec052ef28a67f9b0.js +1 -0
  168. package/web/.next/static/chunks/app/login/page-97a658fa01e36c3f.js +1 -0
  169. package/web/.next/static/chunks/app/onboarding/page-d7c5a220f9244b45.js +26 -0
  170. package/web/.next/static/chunks/next/dist/client/components/builtin/app-error-d40d5db150472c4f.js +1 -0
  171. package/web/.next/static/chunks/next/dist/client/components/builtin/forbidden-d40d5db150472c4f.js +1 -0
  172. package/web/.next/static/chunks/next/dist/client/components/builtin/unauthorized-d40d5db150472c4f.js +1 -0
  173. package/web/.next/static/css/0f40c037c3aa9ff2.css +3 -0
  174. package/web/server.js +1 -1
  175. package/web/.next/server/chunks/6823.js +0 -1
  176. package/web/.next/server/chunks/715.js +0 -1
  177. package/web/.next/server/chunks/8308.js +0 -1
  178. package/web/.next/server/chunks/8430.js +0 -1
  179. package/web/.next/static/chunks/1361-5bc59efe9312f6ad.js +0 -1
  180. package/web/.next/static/chunks/2716-55970bc6e3910ccf.js +0 -1
  181. package/web/.next/static/chunks/3114-d846062bc8434b58.js +0 -1
  182. package/web/.next/static/chunks/370-554f47928c2648ee.js +0 -1
  183. package/web/.next/static/chunks/3763-2fa54c0e83122050.js +0 -1
  184. package/web/.next/static/chunks/3865-9aa6dedee678d08f.js +0 -1
  185. package/web/.next/static/chunks/8447-ec639f4e1d42d912.js +0 -1
  186. package/web/.next/static/chunks/9775-dad785c12d658539.js +0 -1
  187. package/web/.next/static/chunks/9836-5beba80ab70ed698.js +0 -1
  188. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/channels/page-f2413d131f4e0bdf.js +0 -1
  189. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/edit/page-5ab1dcbc58720645.js +0 -6
  190. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/telegram/page-9397b9b7fccbb444.js +0 -1
  191. package/web/.next/static/chunks/app/(dashboard)/agents/page-6ac1ebd6598d7da7.js +0 -1
  192. package/web/.next/static/chunks/app/(dashboard)/approvals/page-76247d88c06bf203.js +0 -1
  193. package/web/.next/static/chunks/app/(dashboard)/automations/page-901227b6d047b2b7.js +0 -1
  194. package/web/.next/static/chunks/app/(dashboard)/chat/page-72f0688180411e7e.js +0 -1
  195. package/web/.next/static/chunks/app/(dashboard)/connectors/page-59b11d68fd1107c3.js +0 -1
  196. package/web/.next/static/chunks/app/(dashboard)/credentials/page-445228b74058e9c2.js +0 -1
  197. package/web/.next/static/chunks/app/(dashboard)/jobs/[id]/page-d7153b1b30dc6b78.js +0 -1
  198. package/web/.next/static/chunks/app/(dashboard)/jobs/page-65cb4b9970d04210.js +0 -1
  199. package/web/.next/static/chunks/app/(dashboard)/layout-16531dffd6974eb5.js +0 -1
  200. package/web/.next/static/chunks/app/(dashboard)/learned-skills/page-0211eb7caae5531d.js +0 -1
  201. package/web/.next/static/chunks/app/(dashboard)/llm-providers/page-c06f48cf0fbe5d63.js +0 -1
  202. package/web/.next/static/chunks/app/(dashboard)/logs/page-5080105f905f2d05.js +0 -1
  203. package/web/.next/static/chunks/app/(dashboard)/mcp/page-1aed0a1ab4459e69.js +0 -1
  204. package/web/.next/static/chunks/app/(dashboard)/memories/page-addb7eb81a34188b.js +0 -1
  205. package/web/.next/static/chunks/app/(dashboard)/page-5e99a1628397f82c.js +0 -1
  206. package/web/.next/static/chunks/app/(dashboard)/settings/page-ffff6582f6a33a65.js +0 -1
  207. package/web/.next/static/chunks/app/(dashboard)/settings/root-context/page-f42cc0aff627bd35.js +0 -1
  208. package/web/.next/static/chunks/app/(dashboard)/skills/[id]/edit/page-6022f41d61c64f4f.js +0 -1
  209. package/web/.next/static/chunks/app/(dashboard)/skills/new/page-a421b5020aa53715.js +0 -1
  210. package/web/.next/static/chunks/app/(dashboard)/skills/page-e8f83c423960e91d.js +0 -1
  211. package/web/.next/static/chunks/app/_global-error/page-9397b9b7fccbb444.js +0 -1
  212. package/web/.next/static/chunks/app/_not-found/page-9397b9b7fccbb444.js +0 -1
  213. package/web/.next/static/chunks/app/api/auth/[...all]/route-9397b9b7fccbb444.js +0 -1
  214. package/web/.next/static/chunks/app/api/health/route-9397b9b7fccbb444.js +0 -1
  215. package/web/.next/static/chunks/app/api/oauth/[provider]/callback/route-9397b9b7fccbb444.js +0 -1
  216. package/web/.next/static/chunks/app/api/oauth/[provider]/start/route-9397b9b7fccbb444.js +0 -1
  217. package/web/.next/static/chunks/app/auth/callback/route-9397b9b7fccbb444.js +0 -1
  218. package/web/.next/static/chunks/app/error-dc3b2db3bdb5b34e.js +0 -1
  219. package/web/.next/static/chunks/app/login/page-a6993d175f94d322.js +0 -1
  220. package/web/.next/static/chunks/app/onboarding/page-9619ad033b220394.js +0 -26
  221. package/web/.next/static/chunks/next/dist/client/components/builtin/app-error-9397b9b7fccbb444.js +0 -1
  222. package/web/.next/static/chunks/next/dist/client/components/builtin/forbidden-9397b9b7fccbb444.js +0 -1
  223. package/web/.next/static/chunks/next/dist/client/components/builtin/unauthorized-9397b9b7fccbb444.js +0 -1
  224. package/web/.next/static/css/637b48680bc0d57a.css +0 -3
  225. /package/web/.next/static/{29LavAHsyomtNE9YjHNlk → TWzSfBwiIIU81SgNxXcIw}/_buildManifest.js +0 -0
  226. /package/web/.next/static/{29LavAHsyomtNE9YjHNlk → TWzSfBwiIIU81SgNxXcIw}/_ssgManifest.js +0 -0
package/runner.js CHANGED
@@ -1438,17 +1438,30 @@ var init_model_catalog = __esm({
1438
1438
  contextWindow: 4e5
1439
1439
  }
1440
1440
  ],
1441
+ // ─── Native Google (generativelanguage.googleapis.com) ──────────────────────
1442
+ // gemini-2.0-flash and gemini-2.5-pro were verified live (2026-07-12, real
1443
+ // key) to 404 / be shut down — dropped per product decision (family 3.x
1444
+ // only in the native catalog; gemini-2.5-flash deliberately NOT added).
1445
+ // Both entries below confirmed alive via ai.google.dev/gemini-api/docs/models
1446
+ // and openrouter.ai/google/… (2026-07-12): 1M-token context window, native
1447
+ // reasoning ("thinking") models, multimodal (text/image/video/audio/PDF).
1448
+ // forcedToolChoice:false — matches this file's reasoning-model convention
1449
+ // (MiniMax M3, Moonshot, GLM 5.2): don't force tool_choice on a thinking
1450
+ // model, let the runtime completion floor relax it to 'auto'. google.ts's
1451
+ // fetch shim injects generationConfig.thinkingConfig for these two entries
1452
+ // (reasoning:true gates it) so the API returns the chain-of-thought the
1453
+ // execute.ts round-trip needs across tool-call turns.
1441
1454
  google: [
1442
1455
  {
1443
- modelId: "gemini-2.0-flash",
1444
- label: "Gemini 2.0 Flash",
1445
- capabilities: { tools: true, forcedToolChoice: true },
1456
+ modelId: "gemini-3.5-flash",
1457
+ label: "Gemini 3.5 Flash",
1458
+ capabilities: { tools: true, forcedToolChoice: false, reasoning: true },
1446
1459
  contextWindow: 1048576
1447
1460
  },
1448
1461
  {
1449
- modelId: "gemini-2.5-pro",
1450
- label: "Gemini 2.5 Pro",
1451
- capabilities: { tools: true, forcedToolChoice: true },
1462
+ modelId: "gemini-3.1-pro-preview",
1463
+ label: "Gemini 3.1 Pro (preview)",
1464
+ capabilities: { tools: true, forcedToolChoice: false, reasoning: true },
1452
1465
  contextWindow: 1048576
1453
1466
  }
1454
1467
  ],
@@ -1536,23 +1549,30 @@ var init_model_catalog = __esm({
1536
1549
  contextWindow: 1048576,
1537
1550
  providerOrder: ["deepseek"]
1538
1551
  },
1539
- // Google
1552
+ // Google — all three are thinking models on OpenRouter (supported_parameters
1553
+ // includes "reasoning", verified via openrouter.ai/google/… 2026-07-12).
1554
+ // reasoning:true makes the OpenRouter provider enable reasoning + round-trip
1555
+ // reasoning_details across tool-call turns. Unlike the M-series/Kimi/GLM
1556
+ // reasoning entries above, forcedToolChoice stays true here — no evidence
1557
+ // (live or documented) that OpenRouter's Gemini routes reject a forced
1558
+ // tool_choice, matching how deepseek/deepseek-v4-flash (also reasoning:true)
1559
+ // keeps forcedToolChoice:true in this file.
1540
1560
  {
1541
1561
  modelId: "google/gemini-3.1-flash-lite-preview",
1542
1562
  label: "Gemini 3.1 Flash Lite (preview)",
1543
- capabilities: { tools: true, forcedToolChoice: true },
1563
+ capabilities: { tools: true, forcedToolChoice: true, reasoning: true },
1544
1564
  contextWindow: 1048576
1545
1565
  },
1546
1566
  {
1547
1567
  modelId: "google/gemini-3.1-pro-preview",
1548
1568
  label: "Gemini 3.1 Pro (preview)",
1549
- capabilities: { tools: true, forcedToolChoice: true },
1569
+ capabilities: { tools: true, forcedToolChoice: true, reasoning: true },
1550
1570
  contextWindow: 1048576
1551
1571
  },
1552
1572
  {
1553
1573
  modelId: "google/gemini-3.5-flash",
1554
1574
  label: "Gemini 3.5 Flash",
1555
- capabilities: { tools: true, forcedToolChoice: true },
1575
+ capabilities: { tools: true, forcedToolChoice: true, reasoning: true },
1556
1576
  contextWindow: 1048576
1557
1577
  },
1558
1578
  {
@@ -1630,8 +1650,8 @@ var init_model_catalog = __esm({
1630
1650
  "claude-haiku-4-5-20251001",
1631
1651
  "gpt-5",
1632
1652
  "gpt-5-mini",
1633
- "gemini-2.0-flash",
1634
- "gemini-2.5-pro",
1653
+ "gemini-3.5-flash",
1654
+ "gemini-3.1-pro-preview",
1635
1655
  "mistral-large-latest",
1636
1656
  "MiniMax-M3",
1637
1657
  "kimi-k2.6",
@@ -2352,6 +2372,17 @@ var init_schedules = __esm({
2352
2372
  // forces the agent to send the user a success confirmation before finishing.
2353
2373
  // Default false → the cron runs silently (the user must opt in per schedule).
2354
2374
  notifyOnSuccess: boolean5("notify_on_success").notNull().default(false),
2375
+ // Explicit notify channel choice (B1, notify-channel-choice plan). One of
2376
+ // 'telegram'|'discord'|'slack'|'whatsapp', or NULL = auto (the historical
2377
+ // behavior: first active channel by CHANNEL_PRIORITY — see run-schedules.ts).
2378
+ // Choosing a channel here LINKS chatId resolution to it: the owner
2379
+ // conversation resolved at fire time is the owner's conversation ON THIS
2380
+ // CHANNEL, not whichever channel happened to win priority. 'whatsapp' is
2381
+ // accepted at the DB layer for forward-compat but the UI does not offer it
2382
+ // yet — no outbound send tool exists for whatsapp (TOOL_ONLY_DELIVERY_CHANNELS,
2383
+ // execute.ts), so a whatsapp notify would only ever reach the user through
2384
+ // deliver-results.ts's adapter-direct path, never a mid-run agent send.
2385
+ notifyChannel: text5("notify_channel"),
2355
2386
  // Per-schedule daily cost ceiling in USD (Event Triggers, Brique 3). Rolled
2356
2387
  // up against agent_jobs.total_cost_usd for this schedule since the start
2357
2388
  // of its local day — see runScheduleTick (apps/runner/src/cron/run-schedules.ts).
@@ -2367,7 +2398,11 @@ var init_schedules = __esm({
2367
2398
  check3("agent_schedules_type_check", sql3`${table.type} IN ('cron','heartbeat')`),
2368
2399
  check3(
2369
2400
  "agent_schedules_last_status_check",
2370
- sql3`${table.lastStatus} IN ('success','failed','no_action','budget_exhausted') OR ${table.lastStatus} IS NULL`
2401
+ sql3`${table.lastStatus} IN ('success','failed','no_action','budget_exhausted','notify_unreachable') OR ${table.lastStatus} IS NULL`
2402
+ ),
2403
+ check3(
2404
+ "agent_schedules_notify_channel_check",
2405
+ sql3`${table.notifyChannel} IN ('telegram','discord','slack','whatsapp') OR ${table.notifyChannel} IS NULL`
2371
2406
  )
2372
2407
  ]
2373
2408
  );
@@ -2898,7 +2933,17 @@ var init_memory2 = __esm({
2898
2933
  });
2899
2934
 
2900
2935
  // ../../packages/db/src/schema/webhooks.ts
2901
- import { pgTable as pgTable13, text as text13, uuid as uuid13, boolean as boolean8, integer as integer7, timestamp as timestamp13, index as index12 } from "drizzle-orm/pg-core";
2936
+ import {
2937
+ pgTable as pgTable13,
2938
+ text as text13,
2939
+ uuid as uuid13,
2940
+ boolean as boolean8,
2941
+ integer as integer7,
2942
+ timestamp as timestamp13,
2943
+ index as index12,
2944
+ check as check10
2945
+ } from "drizzle-orm/pg-core";
2946
+ import { sql as sql11 } from "drizzle-orm";
2902
2947
  var webhookTriggers;
2903
2948
  var init_webhooks = __esm({
2904
2949
  "../../packages/db/src/schema/webhooks.ts"() {
@@ -2919,10 +2964,30 @@ var init_webhooks = __esm({
2919
2964
  secret: text13("secret"),
2920
2965
  lastTriggeredAt: timestamp13("last_triggered_at", { withTimezone: true }),
2921
2966
  triggerCount: integer7("trigger_count").default(0),
2967
+ // B2 (notify-channel-choice plan): same pair of columns as
2968
+ // agent_schedules (see schedules.ts), same mechanic — the schedules
2969
+ // brique (B1) established the pattern, this just closes the gap the plan
2970
+ // identified: webhooks had no notify at all, not by design, just because
2971
+ // the original webhooks brique scoped delivery to the HTTP caller (202 +
2972
+ // jobId) and never to the owner. Default false → firing stays silent
2973
+ // unless opted in per trigger.
2974
+ notifyOnSuccess: boolean8("notify_on_success").notNull().default(false),
2975
+ // Explicit notify channel choice, NULL = auto (first active channel by
2976
+ // CHANNEL_PRIORITY — see routes/webhook.ts). Unlike agent_schedules,
2977
+ // there is no legacy webhook-notify behavior to preserve, so "auto" here
2978
+ // is a plain first-active-channel resolution from the start (no
2979
+ // telegram-only wrapper).
2980
+ notifyChannel: text13("notify_channel"),
2922
2981
  createdAt: timestamp13("created_at", { withTimezone: true }).defaultNow(),
2923
2982
  updatedAt: timestamp13("updated_at", { withTimezone: true }).defaultNow()
2924
2983
  },
2925
- (table) => [index12("idx_webhook_triggers_entity_id").on(table.entityId)]
2984
+ (table) => [
2985
+ index12("idx_webhook_triggers_entity_id").on(table.entityId),
2986
+ check10(
2987
+ "webhook_triggers_notify_channel_check",
2988
+ sql11`${table.notifyChannel} IN ('telegram','discord','slack','whatsapp') OR ${table.notifyChannel} IS NULL`
2989
+ )
2990
+ ]
2926
2991
  );
2927
2992
  }
2928
2993
  });
@@ -2939,7 +3004,7 @@ import {
2939
3004
  index as index13,
2940
3005
  unique as unique3
2941
3006
  } from "drizzle-orm/pg-core";
2942
- import { sql as sql11 } from "drizzle-orm";
3007
+ import { sql as sql12 } from "drizzle-orm";
2943
3008
  var agentSkills, skillVersions, skillConnectors, agentSkillAssignments;
2944
3009
  var init_skills = __esm({
2945
3010
  "../../packages/db/src/schema/skills.ts"() {
@@ -2959,9 +3024,9 @@ var init_skills = __esm({
2959
3024
  description: text14("description"),
2960
3025
  defaultContent: text14("default_content"),
2961
3026
  contentOverridden: boolean9("content_overridden").default(false),
2962
- requiredConfig: jsonb7("required_config").default(sql11`'[]'::jsonb`),
2963
- operations: jsonb7("operations").default(sql11`'[]'::jsonb`),
2964
- requiredBuiltins: text14("required_builtins").array().notNull().default(sql11`'{}'::text[]`),
3027
+ requiredConfig: jsonb7("required_config").default(sql12`'[]'::jsonb`),
3028
+ operations: jsonb7("operations").default(sql12`'[]'::jsonb`),
3029
+ requiredBuiltins: text14("required_builtins").array().notNull().default(sql12`'{}'::text[]`),
2965
3030
  // ─── Community-installed skills (open Agent Skills / SKILL.md format) ───
2966
3031
  // is_community = true for skills installed at runtime from an external
2967
3032
  // source (vs system catalog or user-authored custom skills). source is the
@@ -3022,7 +3087,7 @@ var init_skills = __esm({
3022
3087
  },
3023
3088
  (table) => [
3024
3089
  index13("idx_skill_versions_entity_id").on(table.entityId),
3025
- index13("idx_skill_versions_skill_id").on(table.skillId, sql11`${table.version} DESC`)
3090
+ index13("idx_skill_versions_skill_id").on(table.skillId, sql12`${table.version} DESC`)
3026
3091
  ]
3027
3092
  );
3028
3093
  skillConnectors = pgTable14(
@@ -3044,7 +3109,7 @@ var init_skills = __esm({
3044
3109
  entityId: uuid14("entity_id").notNull().references(() => entities.id, { onDelete: "cascade" }),
3045
3110
  agentId: uuid14("agent_id").notNull().references(() => agents.id, { onDelete: "cascade" }),
3046
3111
  skillId: uuid14("skill_id").notNull().references(() => agentSkills.id, { onDelete: "cascade" }),
3047
- approvalOverrides: jsonb7("approval_overrides").default(sql11`'{}'::jsonb`),
3112
+ approvalOverrides: jsonb7("approval_overrides").default(sql12`'{}'::jsonb`),
3048
3113
  useCustomInstructions: boolean9("use_custom_instructions").notNull().default(false),
3049
3114
  enabledOperations: text14("enabled_operations").array(),
3050
3115
  // Per-skill × per-agent authorization to EXECUTE the skill's bundled scripts
@@ -3085,9 +3150,9 @@ import {
3085
3150
  timestamp as timestamp15,
3086
3151
  index as index14,
3087
3152
  uniqueIndex as uniqueIndex2,
3088
- check as check10
3153
+ check as check11
3089
3154
  } from "drizzle-orm/pg-core";
3090
- import { sql as sql12 } from "drizzle-orm";
3155
+ import { sql as sql13 } from "drizzle-orm";
3091
3156
  var mcpServers, agentMcpServers, mcpConnections;
3092
3157
  var init_mcp = __esm({
3093
3158
  "../../packages/db/src/schema/mcp.ts"() {
@@ -3104,8 +3169,8 @@ var init_mcp = __esm({
3104
3169
  transport: text15("transport").notNull(),
3105
3170
  url: text15("url"),
3106
3171
  command: text15("command"),
3107
- args: text15("args").array().default(sql12`'{}'::text[]`),
3108
- envVars: jsonb8("env_vars").default(sql12`'{}'::jsonb`),
3172
+ args: text15("args").array().default(sql13`'{}'::text[]`),
3173
+ envVars: jsonb8("env_vars").default(sql13`'{}'::jsonb`),
3109
3174
  // Encrypted (enc:v1: blob) credential for HTTP MCP servers — same pattern
3110
3175
  // as connectors.api_key. NULL for servers that need no auth.
3111
3176
  apiKey: text15("api_key"),
@@ -3123,10 +3188,10 @@ var init_mcp = __esm({
3123
3188
  updatedAt: timestamp15("updated_at", { withTimezone: true }).defaultNow()
3124
3189
  },
3125
3190
  (table) => [
3126
- check10("mcp_servers_transport_check", sql12`${table.transport} IN ('http','stdio')`),
3127
- check10(
3191
+ check11("mcp_servers_transport_check", sql13`${table.transport} IN ('http','stdio')`),
3192
+ check11(
3128
3193
  "mcp_servers_auth_scheme_check",
3129
- sql12`${table.authScheme} IN ('header','query','bearer') OR ${table.authScheme} IS NULL`
3194
+ sql13`${table.authScheme} IN ('header','query','bearer') OR ${table.authScheme} IS NULL`
3130
3195
  )
3131
3196
  // Multi-instance brique (migration 0017): the (entity_id, slug) UNIQUE
3132
3197
  // index was dropped to allow multiple instances of the same MCP server
@@ -3159,7 +3224,7 @@ var init_mcp = __esm({
3159
3224
  entityId: uuid15("entity_id").notNull().references(() => entities.id, { onDelete: "cascade" }),
3160
3225
  slug: text15("slug").notNull(),
3161
3226
  active: boolean10("active").notNull().default(true),
3162
- toolConfig: jsonb8("tool_config").notNull().default(sql12`'{}'::jsonb`),
3227
+ toolConfig: jsonb8("tool_config").notNull().default(sql13`'{}'::jsonb`),
3163
3228
  createdAt: timestamp15("created_at", { withTimezone: true }).notNull().defaultNow(),
3164
3229
  updatedAt: timestamp15("updated_at", { withTimezone: true }).notNull().defaultNow()
3165
3230
  },
@@ -3185,10 +3250,10 @@ import {
3185
3250
  jsonb as jsonb9,
3186
3251
  timestamp as timestamp16,
3187
3252
  index as index15,
3188
- check as check11,
3253
+ check as check12,
3189
3254
  unique as unique4
3190
3255
  } from "drizzle-orm/pg-core";
3191
- import { sql as sql13 } from "drizzle-orm";
3256
+ import { sql as sql14 } from "drizzle-orm";
3192
3257
  var configuratorSessions, agentPlugins, rateLimits;
3193
3258
  var init_misc = __esm({
3194
3259
  "../../packages/db/src/schema/misc.ts"() {
@@ -3201,7 +3266,7 @@ var init_misc = __esm({
3201
3266
  id: uuid16("id").primaryKey().defaultRandom(),
3202
3267
  entityId: uuid16("entity_id").notNull().references(() => entities.id, { onDelete: "cascade" }),
3203
3268
  agentId: uuid16("agent_id").references(() => agents.id, { onDelete: "cascade" }),
3204
- messages: jsonb9("messages").notNull().default(sql13`'[]'::jsonb`),
3269
+ messages: jsonb9("messages").notNull().default(sql14`'[]'::jsonb`),
3205
3270
  status: text16("status").notNull().default("active"),
3206
3271
  turnCount: integer9("turn_count").notNull().default(0),
3207
3272
  createdAt: timestamp16("created_at", { withTimezone: true }).notNull().defaultNow(),
@@ -3221,7 +3286,7 @@ var init_misc = __esm({
3221
3286
  slug: text16("slug").notNull(),
3222
3287
  description: text16("description"),
3223
3288
  pluginType: text16("plugin_type").notNull(),
3224
- config: jsonb9("config").default(sql13`'{}'::jsonb`),
3289
+ config: jsonb9("config").default(sql14`'{}'::jsonb`),
3225
3290
  active: boolean11("active").default(true),
3226
3291
  hook: text16("hook").notNull(),
3227
3292
  webhookUrl: text16("webhook_url"),
@@ -3229,13 +3294,13 @@ var init_misc = __esm({
3229
3294
  updatedAt: timestamp16("updated_at", { withTimezone: true }).defaultNow()
3230
3295
  },
3231
3296
  (table) => [
3232
- check11(
3297
+ check12(
3233
3298
  "agent_plugins_plugin_type_check",
3234
- sql13`${table.pluginType} IN ('webhook','transform','schedule')`
3299
+ sql14`${table.pluginType} IN ('webhook','transform','schedule')`
3235
3300
  ),
3236
- check11(
3301
+ check12(
3237
3302
  "agent_plugins_hook_check",
3238
- sql13`${table.hook} IN ('pre_task','post_task','pre_tool','post_tool','on_memory_save')`
3303
+ sql14`${table.hook} IN ('pre_task','post_task','pre_tool','post_tool','on_memory_save')`
3239
3304
  ),
3240
3305
  // R5 (audit #2 follow-up): the pglite test DDL (packages/db/src/tests/
3241
3306
  // helpers.ts) already assumed a per-entity unique slug here — same class
@@ -3365,8 +3430,8 @@ var init_agent_workspaces = __esm({
3365
3430
  });
3366
3431
 
3367
3432
  // ../../packages/db/src/schema/chat-messages.ts
3368
- import { pgTable as pgTable20, text as text20, uuid as uuid20, timestamp as timestamp20, index as index18, check as check12 } from "drizzle-orm/pg-core";
3369
- import { sql as sql14 } from "drizzle-orm";
3433
+ import { pgTable as pgTable20, text as text20, uuid as uuid20, timestamp as timestamp20, index as index18, check as check13 } from "drizzle-orm/pg-core";
3434
+ import { sql as sql15 } from "drizzle-orm";
3370
3435
  var conversations, chatMessages;
3371
3436
  var init_chat_messages = __esm({
3372
3437
  "../../packages/db/src/schema/chat-messages.ts"() {
@@ -3392,7 +3457,7 @@ var init_chat_messages = __esm({
3392
3457
  },
3393
3458
  (table) => [
3394
3459
  index18("idx_conversations_entity_agent").on(table.entityId, table.agentId, table.updatedAt),
3395
- check12("conversations_origin_check", sql14`${table.origin} IN ('user','onboarding')`)
3460
+ check13("conversations_origin_check", sql15`${table.origin} IN ('user','onboarding')`)
3396
3461
  ]
3397
3462
  );
3398
3463
  chatMessages = pgTable20(
@@ -3413,7 +3478,7 @@ var init_chat_messages = __esm({
3413
3478
  },
3414
3479
  (table) => [
3415
3480
  index18("idx_chat_messages_conversation").on(table.conversationId, table.createdAt),
3416
- check12("chat_messages_role_check", sql14`${table.role} IN ('user','assistant')`)
3481
+ check13("chat_messages_role_check", sql15`${table.role} IN ('user','assistant')`)
3417
3482
  ]
3418
3483
  );
3419
3484
  }
@@ -3451,11 +3516,11 @@ import {
3451
3516
  uuid as uuid22,
3452
3517
  timestamp as timestamp22,
3453
3518
  index as index19,
3454
- check as check13,
3519
+ check as check14,
3455
3520
  unique as unique6,
3456
3521
  uniqueIndex as uniqueIndex4
3457
3522
  } from "drizzle-orm/pg-core";
3458
- import { sql as sql15 } from "drizzle-orm";
3523
+ import { sql as sql16 } from "drizzle-orm";
3459
3524
  var telegramAllowedChats;
3460
3525
  var init_telegram_allowed_chats = __esm({
3461
3526
  "../../packages/db/src/schema/telegram-allowed-chats.ts"() {
@@ -3484,8 +3549,8 @@ var init_telegram_allowed_chats = __esm({
3484
3549
  unique6("telegram_allowed_chats_agent_chat_unique").on(table.agentId, table.chatId),
3485
3550
  index19("idx_telegram_allowed_chats_agent").on(table.agentId),
3486
3551
  index19("idx_telegram_allowed_chats_entity").on(table.entityId),
3487
- check13("telegram_allowed_chats_role_check", sql15`${table.role} IN ('owner','member')`),
3488
- check13("telegram_allowed_chats_status_check", sql15`${table.status} IN ('active','pending')`),
3552
+ check14("telegram_allowed_chats_role_check", sql16`${table.role} IN ('owner','member')`),
3553
+ check14("telegram_allowed_chats_status_check", sql16`${table.status} IN ('active','pending')`),
3489
3554
  // F1 (audit #2 remediation follow-up, migration 0060): concurrent
3490
3555
  // first-contact claims could each pass the "no owner yet" check before
3491
3556
  // either row committed, creating two role='owner' rows for one agent —
@@ -3493,7 +3558,7 @@ var init_telegram_allowed_chats = __esm({
3493
3558
  // resolveApprovalDeliveryTarget) does LIMIT 1 assuming uniqueness. A
3494
3559
  // partial unique index (owner rows only; 'member' rows are unrestricted)
3495
3560
  // closes the race at the DB level.
3496
- uniqueIndex4("telegram_allowed_chats_single_owner").on(table.agentId).where(sql15`${table.role} = 'owner'`)
3561
+ uniqueIndex4("telegram_allowed_chats_single_owner").on(table.agentId).where(sql16`${table.role} = 'owner'`)
3497
3562
  ]
3498
3563
  );
3499
3564
  }
@@ -3508,10 +3573,10 @@ import {
3508
3573
  jsonb as jsonb10,
3509
3574
  timestamp as timestamp23,
3510
3575
  index as index20,
3511
- check as check14,
3576
+ check as check15,
3512
3577
  unique as unique7
3513
3578
  } from "drizzle-orm/pg-core";
3514
- import { sql as sql16 } from "drizzle-orm";
3579
+ import { sql as sql17 } from "drizzle-orm";
3515
3580
  var channelBindings;
3516
3581
  var init_channel_bindings = __esm({
3517
3582
  "../../packages/db/src/schema/channel-bindings.ts"() {
@@ -3540,9 +3605,9 @@ var init_channel_bindings = __esm({
3540
3605
  unique7("channel_bindings_agent_channel_unique").on(table.agentId, table.channel),
3541
3606
  index20("idx_channel_bindings_agent").on(table.agentId),
3542
3607
  index20("idx_channel_bindings_entity").on(table.entityId),
3543
- check14(
3608
+ check15(
3544
3609
  "channel_bindings_channel_check",
3545
- sql16`${table.channel} IN ('telegram','discord','slack','whatsapp')`
3610
+ sql17`${table.channel} IN ('telegram','discord','slack','whatsapp')`
3546
3611
  )
3547
3612
  ]
3548
3613
  );
@@ -3556,11 +3621,11 @@ import {
3556
3621
  uuid as uuid24,
3557
3622
  timestamp as timestamp24,
3558
3623
  index as index21,
3559
- check as check15,
3624
+ check as check16,
3560
3625
  unique as unique8,
3561
3626
  uniqueIndex as uniqueIndex5
3562
3627
  } from "drizzle-orm/pg-core";
3563
- import { sql as sql17 } from "drizzle-orm";
3628
+ import { sql as sql18 } from "drizzle-orm";
3564
3629
  var channelAllowedConversations;
3565
3630
  var init_channel_allowed_conversations = __esm({
3566
3631
  "../../packages/db/src/schema/channel-allowed-conversations.ts"() {
@@ -3596,24 +3661,24 @@ var init_channel_allowed_conversations = __esm({
3596
3661
  ),
3597
3662
  index21("idx_channel_allowed_conversations_agent").on(table.agentId),
3598
3663
  index21("idx_channel_allowed_conversations_entity").on(table.entityId),
3599
- check15(
3664
+ check16(
3600
3665
  "channel_allowed_conversations_channel_check",
3601
- sql17`${table.channel} IN ('telegram','discord','slack','whatsapp')`
3666
+ sql18`${table.channel} IN ('telegram','discord','slack','whatsapp')`
3602
3667
  ),
3603
- check15(
3668
+ check16(
3604
3669
  "channel_allowed_conversations_kind_check",
3605
- sql17`${table.kind} IN ('private','group','channel','thread')`
3670
+ sql18`${table.kind} IN ('private','group','channel','thread')`
3606
3671
  ),
3607
- check15("channel_allowed_conversations_role_check", sql17`${table.role} IN ('owner','member')`),
3608
- check15(
3672
+ check16("channel_allowed_conversations_role_check", sql18`${table.role} IN ('owner','member')`),
3673
+ check16(
3609
3674
  "channel_allowed_conversations_status_check",
3610
- sql17`${table.status} IN ('active','pending')`
3675
+ sql18`${table.status} IN ('active','pending')`
3611
3676
  ),
3612
3677
  // Mirrors telegram_allowed_chats_single_owner (migration 0060): at most one
3613
3678
  // active-owner-authority row per (agent, channel) — same race this closes
3614
3679
  // (concurrent first-contact claims), scoped so each channel gets its OWN
3615
3680
  // owner rather than sharing one across an agent's whole multichannel surface.
3616
- uniqueIndex5("channel_allowed_conversations_single_owner").on(table.agentId, table.channel).where(sql17`${table.role} = 'owner'`)
3681
+ uniqueIndex5("channel_allowed_conversations_single_owner").on(table.agentId, table.channel).where(sql18`${table.role} = 'owner'`)
3617
3682
  ]
3618
3683
  );
3619
3684
  }
@@ -19064,12 +19129,12 @@ var require_formdata_parser = __commonJS({
19064
19129
  return "failure";
19065
19130
  }
19066
19131
  if (bufferStartsWith(input, filenameBuffer, position)) {
19067
- let check16 = position.position + filenameBuffer.length;
19068
- if (input[check16] === 42) {
19132
+ let check17 = position.position + filenameBuffer.length;
19133
+ if (input[check17] === 42) {
19069
19134
  position.position += 1;
19070
- check16 += 1;
19135
+ check17 += 1;
19071
19136
  }
19072
- if (input[check16] !== 61 || input[check16 + 1] !== 34) {
19137
+ if (input[check17] !== 61 || input[check17 + 1] !== 34) {
19073
19138
  return "failure";
19074
19139
  }
19075
19140
  position.position += 12;
@@ -46554,7 +46619,7 @@ var require_Util = __commonJS({
46554
46619
  await client.rest.patch(route, { body: updatedItems, reason });
46555
46620
  return updatedItems;
46556
46621
  }
46557
- function basename6(path2, ext) {
46622
+ function basename5(path2, ext) {
46558
46623
  const res = parse2(path2);
46559
46624
  return ext && res.ext.startsWith(ext) ? res.name : res.base.split("?")[0];
46560
46625
  }
@@ -46677,7 +46742,7 @@ var require_Util = __commonJS({
46677
46742
  resolveColor,
46678
46743
  discordSort,
46679
46744
  setPosition,
46680
- basename: basename6,
46745
+ basename: basename5,
46681
46746
  cleanContent,
46682
46747
  cleanCodeBlockContent,
46683
46748
  parseWebhookURL,
@@ -65369,7 +65434,7 @@ var require_MessagePayload = __commonJS({
65369
65434
  var { DiscordjsError: DiscordjsError2, DiscordjsRangeError: DiscordjsRangeError2, ErrorCodes: ErrorCodes2 } = require_errors2();
65370
65435
  var { resolveFile } = require_DataResolver();
65371
65436
  var MessageFlagsBitField = require_MessageFlagsBitField();
65372
- var { basename: basename6, verifyString, resolvePartialEmoji } = require_Util();
65437
+ var { basename: basename5, verifyString, resolvePartialEmoji } = require_Util();
65373
65438
  var getBaseInteraction = lazy(() => require_BaseInteraction());
65374
65439
  var MessagePayload = class {
65375
65440
  /**
@@ -65588,10 +65653,10 @@ var require_MessagePayload = __commonJS({
65588
65653
  let name;
65589
65654
  const findName = (thing) => {
65590
65655
  if (typeof thing === "string") {
65591
- return basename6(thing);
65656
+ return basename5(thing);
65592
65657
  }
65593
65658
  if (thing.path) {
65594
- return basename6(thing.path);
65659
+ return basename5(thing.path);
65595
65660
  }
65596
65661
  return "file.jpg";
65597
65662
  };
@@ -88043,7 +88108,7 @@ var require_EmbedBuilder = __commonJS({
88043
88108
  var require_AttachmentBuilder = __commonJS({
88044
88109
  "../../node_modules/.pnpm/discord.js@14.26.5/node_modules/discord.js/src/structures/AttachmentBuilder.js"(exports2, module2) {
88045
88110
  "use strict";
88046
- var { basename: basename6, flatten } = require_Util();
88111
+ var { basename: basename5, flatten } = require_Util();
88047
88112
  var AttachmentBuilder = class _AttachmentBuilder {
88048
88113
  /**
88049
88114
  * @param {BufferResolvable|Stream} attachment The file
@@ -88103,7 +88168,7 @@ var require_AttachmentBuilder = __commonJS({
88103
88168
  * @readonly
88104
88169
  */
88105
88170
  get spoiler() {
88106
- return basename6(this.name).startsWith("SPOILER_");
88171
+ return basename5(this.name).startsWith("SPOILER_");
88107
88172
  }
88108
88173
  toJSON() {
88109
88174
  return flatten(this);
@@ -156710,7 +156775,7 @@ var require_wait2 = __commonJS({
156710
156775
  return;
156711
156776
  }
156712
156777
  let prior = current;
156713
- const check16 = (backoff) => {
156778
+ const check17 = (backoff) => {
156714
156779
  if (Date.now() > max2) {
156715
156780
  done(null, "timed-out");
156716
156781
  } else {
@@ -156718,7 +156783,7 @@ var require_wait2 = __commonJS({
156718
156783
  prior = current;
156719
156784
  current = Atomics.load(state, index22);
156720
156785
  if (current === prior) {
156721
- check16(backoff >= MAX_TIMEOUT ? MAX_TIMEOUT : backoff * 2);
156786
+ check17(backoff >= MAX_TIMEOUT ? MAX_TIMEOUT : backoff * 2);
156722
156787
  } else {
156723
156788
  if (current === expected) done(null, "ok");
156724
156789
  else done(null, "not-equal");
@@ -156726,7 +156791,7 @@ var require_wait2 = __commonJS({
156726
156791
  }, backoff);
156727
156792
  }
156728
156793
  };
156729
- check16(1);
156794
+ check17(1);
156730
156795
  }
156731
156796
  function waitDiff(state, index22, expected, timeout, done) {
156732
156797
  const max2 = Date.now() + timeout;
@@ -156735,7 +156800,7 @@ var require_wait2 = __commonJS({
156735
156800
  done(null, "ok");
156736
156801
  return;
156737
156802
  }
156738
- const check16 = (backoff) => {
156803
+ const check17 = (backoff) => {
156739
156804
  if (Date.now() > max2) {
156740
156805
  done(null, "timed-out");
156741
156806
  } else {
@@ -156744,12 +156809,12 @@ var require_wait2 = __commonJS({
156744
156809
  if (current !== expected) {
156745
156810
  done(null, "ok");
156746
156811
  } else {
156747
- check16(backoff >= MAX_TIMEOUT ? MAX_TIMEOUT : backoff * 2);
156812
+ check17(backoff >= MAX_TIMEOUT ? MAX_TIMEOUT : backoff * 2);
156748
156813
  }
156749
156814
  }, backoff);
156750
156815
  }
156751
156816
  };
156752
- check16(1);
156817
+ check17(1);
156753
156818
  }
156754
156819
  module2.exports = { wait, waitDiff };
156755
156820
  }
@@ -199107,7 +199172,7 @@ var require_view2 = __commonJS({
199107
199172
  var path2 = __require("node:path");
199108
199173
  var fs4 = __require("node:fs");
199109
199174
  var dirname8 = path2.dirname;
199110
- var basename6 = path2.basename;
199175
+ var basename5 = path2.basename;
199111
199176
  var extname2 = path2.extname;
199112
199177
  var join16 = path2.join;
199113
199178
  var resolve3 = path2.resolve;
@@ -199146,7 +199211,7 @@ var require_view2 = __commonJS({
199146
199211
  var root = roots[i];
199147
199212
  var loc = resolve3(root, name);
199148
199213
  var dir = dirname8(loc);
199149
- var file = basename6(loc);
199214
+ var file = basename5(loc);
199150
199215
  path3 = this.resolve(dir, file);
199151
199216
  }
199152
199217
  return path3;
@@ -199176,7 +199241,7 @@ var require_view2 = __commonJS({
199176
199241
  if (stat11 && stat11.isFile()) {
199177
199242
  return path3;
199178
199243
  }
199179
- path3 = join16(dir, basename6(file, ext), "index" + ext);
199244
+ path3 = join16(dir, basename5(file, ext), "index" + ext);
199180
199245
  stat11 = tryStat(path3);
199181
199246
  if (stat11 && stat11.isFile()) {
199182
199247
  return path3;
@@ -202138,9 +202203,9 @@ var require_content_disposition = __commonJS({
202138
202203
  if (typeof fallback === "string" && NON_LATIN1_REGEXP.test(fallback)) {
202139
202204
  throw new TypeError("fallback must be ISO-8859-1 string");
202140
202205
  }
202141
- var name = basename6(filename);
202206
+ var name = basename5(filename);
202142
202207
  var isQuotedString = TEXT_REGEXP.test(name);
202143
- var fallbackName = typeof fallback !== "string" ? fallback && getlatin1(name) : basename6(fallback);
202208
+ var fallbackName = typeof fallback !== "string" ? fallback && getlatin1(name) : basename5(fallback);
202144
202209
  var hasFallback = typeof fallbackName === "string" && fallbackName !== name;
202145
202210
  if (hasFallback || !isQuotedString || hasHexEscape(name)) {
202146
202211
  params["filename*"] = name;
@@ -202260,7 +202325,7 @@ var require_content_disposition = __commonJS({
202260
202325
  this.type = type;
202261
202326
  this.parameters = parameters;
202262
202327
  }
202263
- function basename6(path2) {
202328
+ function basename5(path2) {
202264
202329
  const normalized = path2.replaceAll("\\", "/");
202265
202330
  let end = normalized.length;
202266
202331
  while (end > 0 && normalized[end - 1] === "/") {
@@ -204667,8 +204732,6 @@ var envSchema = z.object({
204667
204732
  EMBEDDING_PROVIDER: z.enum(["ollama", "openai", "keyword"]).default("keyword"),
204668
204733
  EMBEDDING_MODEL: z.string().optional(),
204669
204734
  EMBEDDING_BASE_URL: z.string().optional(),
204670
- // Delivery
204671
- TELEGRAM_BOT_TOKEN: z.string().optional(),
204672
204735
  // Auth
204673
204736
  AUTH_MODE: z.enum(["local-trust", "local-auth", "bearer-token"]).default("local-trust"),
204674
204737
  WORKER_SECRET: z.string().default(""),
@@ -204695,8 +204758,6 @@ var envSchema = z.object({
204695
204758
  // Enable reflection per entity via the dashboard toggle — NOT via this env var.
204696
204759
  // The other knobs gate WHICH jobs qualify and bound the pass.
204697
204760
  REFLECTION_ENABLED: z.string().default(""),
204698
- // Minimum job turns before a completed job is "substantial" enough to reflect on.
204699
- REFLECTION_MIN_TURNS: z.coerce.number().default(3),
204700
204761
  // Per-entity rolling-hour cap on reflection passes (rate limit / cost guard).
204701
204762
  REFLECTION_MAX_PER_HOUR: z.coerce.number().default(6),
204702
204763
  // Max LLM turns inside a single reflection pass (the reflection loop itself).
@@ -204839,7 +204900,7 @@ init_schema();
204839
204900
 
204840
204901
  // ../../packages/db/src/queries/credentials.ts
204841
204902
  init_credentials();
204842
- import { eq, sql as sql18 } from "drizzle-orm";
204903
+ import { eq, sql as sql19 } from "drizzle-orm";
204843
204904
 
204844
204905
  // ../../packages/secrets/src/index.ts
204845
204906
  import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
@@ -205031,7 +205092,7 @@ async function getDecryptedCredentialById(db, credentialId) {
205031
205092
  if (provider?.supportsRefresh && payload.refreshToken) {
205032
205093
  try {
205033
205094
  payload = await db.transaction(async (tx) => {
205034
- await tx.execute(sql18`SELECT pg_advisory_xact_lock(hashtext(${credentialId}))`);
205095
+ await tx.execute(sql19`SELECT pg_advisory_xact_lock(hashtext(${credentialId}))`);
205035
205096
  const [freshRow] = await tx.select().from(credentials).where(eq(credentials.id, credentialId));
205036
205097
  if (!freshRow) return payload;
205037
205098
  const freshPayload = decryptPayload(freshRow.payload);
@@ -205299,7 +205360,7 @@ async function attachAgentToOrchestrator(db, entityId, orchestratorId, agentSlug
205299
205360
  // ../../packages/db/src/repos/skills.ts
205300
205361
  init_agents();
205301
205362
  init_skills();
205302
- import { eq as eq4, and as and3, or as or3, inArray as inArray2, sql as sql19 } from "drizzle-orm";
205363
+ import { eq as eq4, and as and3, or as or3, inArray as inArray2, sql as sql20 } from "drizzle-orm";
205303
205364
  async function createSkillRepo(db, entityId, input, reservedSlugs = []) {
205304
205365
  if (reservedSlugs.includes(input.slug)) {
205305
205366
  return { error: "slug_reserved" };
@@ -205342,7 +205403,7 @@ async function updateSkillRepo(db, entityId, skillId, patch) {
205342
205403
  }
205343
205404
  if (patch.createdBy === "agent") {
205344
205405
  set.createdBy = "agent";
205345
- set.patchCount = sql19`${agentSkills.patchCount} + 1`;
205406
+ set.patchCount = sql20`${agentSkills.patchCount} + 1`;
205346
205407
  }
205347
205408
  await db.update(agentSkills).set(set).where(eq4(agentSkills.id, skillId));
205348
205409
  return { ok: true };
@@ -205378,7 +205439,7 @@ async function assignSkillRepo(db, entityId, input, systemSkillSlugs2) {
205378
205439
  }
205379
205440
  async function touchSkillsLastUsed(db, skillIds) {
205380
205441
  if (skillIds.length === 0) return;
205381
- await db.update(agentSkills).set({ lastUsedAt: sql19`now()` }).where(inArray2(agentSkills.id, skillIds));
205442
+ await db.update(agentSkills).set({ lastUsedAt: sql20`now()` }).where(inArray2(agentSkills.id, skillIds));
205382
205443
  }
205383
205444
  async function transitionSkillLifecycle(db, thresholds) {
205384
205445
  const now = /* @__PURE__ */ new Date();
@@ -205392,21 +205453,21 @@ async function transitionSkillLifecycle(db, thresholds) {
205392
205453
  and3(
205393
205454
  eq4(agentSkills.createdBy, "agent"),
205394
205455
  eq4(agentSkills.state, "active"),
205395
- sql19`coalesce(${agentSkills.lastUsedAt}, ${agentSkills.createdAt}) < ${staleAt}`
205456
+ sql20`coalesce(${agentSkills.lastUsedAt}, ${agentSkills.createdAt}) < ${staleAt}`
205396
205457
  )
205397
205458
  ).returning({ id: agentSkills.id });
205398
205459
  const archivedRows = await db.update(agentSkills).set({ state: "archived", archivedAt: now, updatedAt: now }).where(
205399
205460
  and3(
205400
205461
  eq4(agentSkills.createdBy, "agent"),
205401
205462
  eq4(agentSkills.state, "stale"),
205402
- sql19`coalesce(${agentSkills.lastUsedAt}, ${agentSkills.createdAt}) < ${archiveAt}`
205463
+ sql20`coalesce(${agentSkills.lastUsedAt}, ${agentSkills.createdAt}) < ${archiveAt}`
205403
205464
  )
205404
205465
  ).returning({ id: agentSkills.id });
205405
205466
  const reactivatedRows = await db.update(agentSkills).set({ state: "active", updatedAt: now }).where(
205406
205467
  and3(
205407
205468
  eq4(agentSkills.createdBy, "agent"),
205408
205469
  eq4(agentSkills.state, "stale"),
205409
- sql19`${agentSkills.lastUsedAt} >= ${staleAt}`
205470
+ sql20`${agentSkills.lastUsedAt} >= ${staleAt}`
205410
205471
  )
205411
205472
  ).returning({ id: agentSkills.id });
205412
205473
  return {
@@ -205425,25 +205486,25 @@ async function archiveAgentSkill(db, entityId, skillId) {
205425
205486
 
205426
205487
  // ../../packages/db/src/repos/retention.ts
205427
205488
  init_schema();
205428
- import { sql as sql20, inArray as inArray3 } from "drizzle-orm";
205489
+ import { sql as sql21, inArray as inArray3 } from "drizzle-orm";
205429
205490
  async function pruneOldJobs(db, retentionDays, batchSize = 1e3) {
205430
205491
  if (retentionDays <= 0) {
205431
205492
  return { jobsDeleted: 0, toolCallsDeleted: 0 };
205432
205493
  }
205433
- const cutoffExpr = sql20`now() - make_interval(days => ${retentionDays})`;
205494
+ const cutoffExpr = sql21`now() - make_interval(days => ${retentionDays})`;
205434
205495
  let jobsDeleted = 0;
205435
205496
  let toolCallsDeleted = 0;
205436
205497
  for (; ; ) {
205437
205498
  const batch = await db.transaction(async (tx) => {
205438
205499
  const prunableRows = await tx.select({ id: agentJobs.id }).from(agentJobs).where(
205439
- sql20`${agentJobs.status} IN ('completed','failed','cancelled')
205500
+ sql21`${agentJobs.status} IN ('completed','failed','cancelled')
205440
205501
  AND ${agentJobs.completedAt} < ${cutoffExpr}`
205441
205502
  ).limit(batchSize);
205442
205503
  if (prunableRows.length === 0) {
205443
205504
  return null;
205444
205505
  }
205445
205506
  const jobIds = prunableRows.map((r) => r.id);
205446
- const [tcCountRow] = await tx.select({ n: sql20`count(*)::int` }).from(toolCalls).where(inArray3(toolCalls.jobId, jobIds));
205507
+ const [tcCountRow] = await tx.select({ n: sql21`count(*)::int` }).from(toolCalls).where(inArray3(toolCalls.jobId, jobIds));
205447
205508
  const batchToolCallsDeleted = tcCountRow?.n ?? 0;
205448
205509
  await tx.delete(agentJobs).where(inArray3(agentJobs.id, jobIds));
205449
205510
  return { jobsDeleted: jobIds.length, toolCallsDeleted: batchToolCallsDeleted };
@@ -205458,7 +205519,7 @@ async function pruneOldJobs(db, retentionDays, batchSize = 1e3) {
205458
205519
 
205459
205520
  // ../../packages/db/src/repos/app-settings.ts
205460
205521
  init_schema();
205461
- import { and as and4, eq as eq5, sql as sql21 } from "drizzle-orm";
205522
+ import { and as and4, eq as eq5, sql as sql22 } from "drizzle-orm";
205462
205523
  var INSTALL_NOTES_KEY = "install_notes";
205463
205524
  async function getEntitySetting(db, entityId, key) {
205464
205525
  const [row] = await db.select({ value: entitySettings.value }).from(entitySettings).where(and4(eq5(entitySettings.entityId, entityId), eq5(entitySettings.key, key))).limit(1);
@@ -205473,7 +205534,7 @@ import {
205473
205534
  eq as eq6,
205474
205535
  and as and5,
205475
205536
  or as or4,
205476
- sql as sql22,
205537
+ sql as sql23,
205477
205538
  desc,
205478
205539
  asc,
205479
205540
  not,
@@ -206109,11 +206170,51 @@ function buildOpenAICompatibleModel(config) {
206109
206170
  }
206110
206171
 
206111
206172
  // ../../packages/llm/src/providers/google.ts
206173
+ init_src();
206112
206174
  import { createGoogleGenerativeAI } from "@ai-sdk/google";
206175
+ var GOOGLE_HOSTS = /* @__PURE__ */ new Set(["generativelanguage.googleapis.com"]);
206176
+ function requestHost(input) {
206177
+ try {
206178
+ if (typeof input === "string") return new URL(input).host;
206179
+ if (input instanceof URL) return input.host;
206180
+ return new URL(input.url).host;
206181
+ } catch {
206182
+ return void 0;
206183
+ }
206184
+ }
206185
+ function injectGoogleThinking(body) {
206186
+ if (typeof body !== "object" || body === null) return body;
206187
+ const b = body;
206188
+ const generationConfig = typeof b["generationConfig"] === "object" && b["generationConfig"] !== null ? b["generationConfig"] : {};
206189
+ if (generationConfig["thinkingConfig"] === void 0) {
206190
+ generationConfig["thinkingConfig"] = { includeThoughts: true };
206191
+ }
206192
+ b["generationConfig"] = generationConfig;
206193
+ return body;
206194
+ }
206195
+ function createGoogleFetch(opts, baseFetch = globalThis.fetch) {
206196
+ return async (input, init2) => {
206197
+ const host = requestHost(input);
206198
+ if (!opts.injectThinking || !host || !GOOGLE_HOSTS.has(host) || !init2?.body) {
206199
+ return baseFetch(input, init2);
206200
+ }
206201
+ let patchedInit = init2;
206202
+ try {
206203
+ const rawBody = typeof init2.body === "string" ? init2.body : await new Response(init2.body).text();
206204
+ const patched = injectGoogleThinking(JSON.parse(rawBody));
206205
+ patchedInit = { ...init2, body: JSON.stringify(patched) };
206206
+ } catch {
206207
+ }
206208
+ return baseFetch(input, patchedInit);
206209
+ };
206210
+ }
206113
206211
  function buildGoogleModel(config) {
206212
+ const entry = findModelCatalogEntry("google", config.model);
206213
+ const isReasoning = entry?.capabilities.reasoning === true;
206114
206214
  const provider = createGoogleGenerativeAI({
206115
206215
  apiKey: config.apiKey,
206116
- ...config.baseURL ? { baseURL: config.baseURL } : {}
206216
+ ...config.baseURL ? { baseURL: config.baseURL } : {},
206217
+ fetch: createGoogleFetch({ injectThinking: isReasoning })
206117
206218
  });
206118
206219
  return provider(config.model);
206119
206220
  }
@@ -206346,7 +206447,13 @@ var nodalToolCallMiddleware = createNativeToolCallMiddleware(nodalNativeParser);
206346
206447
  // ../../packages/llm/src/providers/moonshot-schema.ts
206347
206448
  var SCHEMA_MAP_KEYS = /* @__PURE__ */ new Set(["properties", "patternProperties", "$defs", "definitions"]);
206348
206449
  var SCHEMA_LIST_KEYS = /* @__PURE__ */ new Set(["anyOf", "oneOf", "allOf", "prefixItems"]);
206349
- var SCHEMA_NODE_KEYS = /* @__PURE__ */ new Set(["items", "contains", "not", "additionalProperties", "propertyNames"]);
206450
+ var SCHEMA_NODE_KEYS = /* @__PURE__ */ new Set([
206451
+ "items",
206452
+ "contains",
206453
+ "not",
206454
+ "additionalProperties",
206455
+ "propertyNames"
206456
+ ]);
206350
206457
  function isPlainObject(value) {
206351
206458
  return typeof value === "object" && value !== null && !Array.isArray(value);
206352
206459
  }
@@ -206712,7 +206819,7 @@ function buildMiniMaxModel(config) {
206712
206819
  init_src();
206713
206820
  import { createOpenAICompatible as createOpenAICompatible2 } from "@ai-sdk/openai-compatible";
206714
206821
  var MOONSHOT_HOSTS = /* @__PURE__ */ new Set(["api.moonshot.ai", "api.moonshot.cn"]);
206715
- function requestHost(input) {
206822
+ function requestHost2(input) {
206716
206823
  try {
206717
206824
  if (typeof input === "string") return new URL(input).host;
206718
206825
  if (input instanceof URL) return input.host;
@@ -206737,7 +206844,7 @@ function patchMoonshotRequestBody(body, opts) {
206737
206844
  function createMoonshotFetch(opts, baseFetch = globalThis.fetch) {
206738
206845
  const tolerant = createTolerantFetch(baseFetch);
206739
206846
  return async (input, init2) => {
206740
- const host = requestHost(input);
206847
+ const host = requestHost2(input);
206741
206848
  if (!host || !MOONSHOT_HOSTS.has(host) || !init2?.body) {
206742
206849
  return tolerant(input, init2);
206743
206850
  }
@@ -207544,8 +207651,8 @@ function rowToMemory(row) {
207544
207651
  // ../../packages/memory/src/access-tracking.ts
207545
207652
  async function touchMemory(db, id, entityId) {
207546
207653
  await db.update(agentMemory).set({
207547
- lastAccessedAt: sql22`now()`,
207548
- accessCount: sql22`${agentMemory.accessCount} + 1`
207654
+ lastAccessedAt: sql23`now()`,
207655
+ accessCount: sql23`${agentMemory.accessCount} + 1`
207549
207656
  }).where(and5(eq6(agentMemory.id, id), eq6(agentMemory.entityId, entityId)));
207550
207657
  }
207551
207658
  async function touchMemories(db, ids, entityId) {
@@ -207563,9 +207670,9 @@ async function keywordSearch(db, query, filters, sort = "importance") {
207563
207670
  const conditions = buildWhereConditions({ entityId, agentId, skillTags, category });
207564
207671
  const orderBy = keywordOrderBy(sort);
207565
207672
  if (orTerms.length > 0) {
207566
- const tsv = sql22`"agent_memory"."search_tsv"`;
207567
- const tsq = sql22`to_tsquery('english', ${orTerms.join(" | ")})`;
207568
- const rows2 = await db.select().from(agentMemory).where(and5(...conditions, sql22`${tsv} @@ ${tsq}`)).orderBy(sql22`ts_rank(${tsv}, ${tsq}) DESC`, ...orderBy).limit(limit);
207673
+ const tsv = sql23`"agent_memory"."search_tsv"`;
207674
+ const tsq = sql23`to_tsquery('english', ${orTerms.join(" | ")})`;
207675
+ const rows2 = await db.select().from(agentMemory).where(and5(...conditions, sql23`${tsv} @@ ${tsq}`)).orderBy(sql23`ts_rank(${tsv}, ${tsq}) DESC`, ...orderBy).limit(limit);
207569
207676
  return rows2;
207570
207677
  }
207571
207678
  const rows = await db.select().from(agentMemory).where(and5(...conditions)).orderBy(...orderBy).limit(limit);
@@ -207610,10 +207717,10 @@ function buildWhereConditions(filters) {
207610
207717
  if (skillTags && skillTags.length > 0) {
207611
207718
  const tagLiteral = pgTextArray(skillTags);
207612
207719
  conditions.push(
207613
- sql22`(
207720
+ sql23`(
207614
207721
  ${agentMemory.skillTags} = '{}'::text[]
207615
207722
  OR ${agentMemory.skillTags} IS NULL
207616
- OR ${agentMemory.skillTags} && ${sql22.raw(tagLiteral)}::text[]
207723
+ OR ${agentMemory.skillTags} && ${sql23.raw(tagLiteral)}::text[]
207617
207724
  )`
207618
207725
  );
207619
207726
  }
@@ -207654,10 +207761,10 @@ async function transitionMemoryLifecycle(db, opts) {
207654
207761
  const rows = await db.update(agentMemory).set({ archived: true, updatedAt: /* @__PURE__ */ new Date() }).where(
207655
207762
  and5(
207656
207763
  eq6(agentMemory.archived, false),
207657
- sql22`${agentMemory.source} IN ('agent','reflection')`,
207658
- sql22`${agentMemory.importance} <= ${opts.importanceMax}`,
207659
- sql22`coalesce(${agentMemory.accessCount}, 0) = 0`,
207660
- sql22`coalesce(${agentMemory.importanceLocked}, false) = false`,
207764
+ sql23`${agentMemory.source} IN ('agent','reflection')`,
207765
+ sql23`${agentMemory.importance} <= ${opts.importanceMax}`,
207766
+ sql23`coalesce(${agentMemory.accessCount}, 0) = 0`,
207767
+ sql23`coalesce(${agentMemory.importanceLocked}, false) = false`,
207661
207768
  lt(agentMemory.createdAt, cutoff)
207662
207769
  )
207663
207770
  ).returning({ id: agentMemory.id });
@@ -207862,16 +207969,16 @@ async function selectMemoriesForInjection(db, opts) {
207862
207969
  const baseConditions = and5(
207863
207970
  eq6(agentMemory.entityId, opts.entityId),
207864
207971
  eq6(agentMemory.archived, false),
207865
- sql22`(${agentMemory.validTo} IS NULL OR ${agentMemory.validTo} > now())`
207972
+ sql23`(${agentMemory.validTo} IS NULL OR ${agentMemory.validTo} > now())`
207866
207973
  );
207867
207974
  const rawQuery = opts.query ?? "";
207868
207975
  const tokens = tokenizeQuery(rawQuery);
207869
207976
  let packed;
207870
207977
  if (tokens.length > 0) {
207871
- const tsv = sql22`"agent_memory"."search_tsv"`;
207872
- const tsq = sql22`to_tsquery('english', ${tokens.join(" | ")})`;
207978
+ const tsv = sql23`"agent_memory"."search_tsv"`;
207979
+ const tsq = sql23`to_tsquery('english', ${tokens.join(" | ")})`;
207873
207980
  const candidates = await db.select().from(agentMemory).where(baseConditions).orderBy(
207874
- sql22`ts_rank(${tsv}, ${tsq}) DESC`,
207981
+ sql23`ts_rank(${tsv}, ${tsq}) DESC`,
207875
207982
  desc(agentMemory.importance),
207876
207983
  desc(agentMemory.lastAccessedAt)
207877
207984
  ).limit(MAX_CANDIDATES);
@@ -207902,7 +208009,7 @@ async function findMemoriesByFactSubstring(db, entityId, substring, limit = 5) {
207902
208009
  and5(
207903
208010
  eq6(agentMemory.entityId, entityId),
207904
208011
  eq6(agentMemory.archived, false),
207905
- sql22`(${agentMemory.validTo} IS NULL OR ${agentMemory.validTo} > now())`,
208012
+ sql23`(${agentMemory.validTo} IS NULL OR ${agentMemory.validTo} > now())`,
207906
208013
  ilike2(agentMemory.fact, `%${substring}%`)
207907
208014
  )
207908
208015
  ).limit(limit);
@@ -208029,16 +208136,16 @@ var searchHistoryTool = {
208029
208136
  riskLevel: "read",
208030
208137
  execute: async (input, ctx) => {
208031
208138
  const limit = input.limit ?? 6;
208032
- const tsq = sql22`plainto_tsquery('simple', ${input.query})`;
208033
- const tsv = sql22`"agent_jobs"."search_tsv"`;
208139
+ const tsq = sql23`plainto_tsquery('simple', ${input.query})`;
208140
+ const tsv = sql23`"agent_jobs"."search_tsv"`;
208034
208141
  const rows = await ctx.db.select({
208035
208142
  job_id: agentJobs.id,
208036
208143
  agent: agents.slug,
208037
208144
  channel: agentJobs.channel,
208038
208145
  when: agentJobs.createdAt,
208039
208146
  task: agentJobs.task,
208040
- snippet: sql22`ts_headline('simple', coalesce(${agentJobs.searchText}, ${agentJobs.task}, ''), ${tsq}, 'StartSel=«, StopSel=», MaxFragments=2, MinWords=4, MaxWords=18, FragmentDelimiter= … ')`
208041
- }).from(agentJobs).leftJoin(agents, eq6(agents.id, agentJobs.agentId)).where(and5(eq6(agentJobs.entityId, ctx.entityId), sql22`${tsv} @@ ${tsq}`)).orderBy(sql22`ts_rank(${tsv}, ${tsq}) DESC`, desc(agentJobs.createdAt)).limit(limit);
208147
+ snippet: sql23`ts_headline('simple', coalesce(${agentJobs.searchText}, ${agentJobs.task}, ''), ${tsq}, 'StartSel=«, StopSel=», MaxFragments=2, MinWords=4, MaxWords=18, FragmentDelimiter= … ')`
208148
+ }).from(agentJobs).leftJoin(agents, eq6(agents.id, agentJobs.agentId)).where(and5(eq6(agentJobs.entityId, ctx.entityId), sql23`${tsv} @@ ${tsq}`)).orderBy(sql23`ts_rank(${tsv}, ${tsq}) DESC`, desc(agentJobs.createdAt)).limit(limit);
208042
208149
  return rows.map((r) => ({
208043
208150
  job_id: r.job_id,
208044
208151
  agent: r.agent ?? null,
@@ -208255,7 +208362,7 @@ import { z as z29 } from "zod";
208255
208362
 
208256
208363
  // ../../packages/tools/src/builtin/file-ops/workspace.ts
208257
208364
  import { realpath, stat } from "node:fs/promises";
208258
- import { resolve as resolvePath, sep, isAbsolute, basename } from "node:path";
208365
+ import { resolve as resolvePath, sep, isAbsolute } from "node:path";
208259
208366
  var SHARED_WORKSPACE_LABEL = "shared";
208260
208367
  var MAX_READ_BYTES = 1024 * 1024;
208261
208368
  var MAX_READ_FILE_BYTES = 50 * 1024 * 1024;
@@ -208567,7 +208674,7 @@ var fileReadTool = {
208567
208674
 
208568
208675
  // ../../packages/tools/src/builtin/file-ops/file-write.ts
208569
208676
  import { writeFile, rename, mkdir, unlink } from "node:fs/promises";
208570
- import { dirname as dirname2, basename as basename2 } from "node:path";
208677
+ import { dirname as dirname2, basename } from "node:path";
208571
208678
  import { randomBytes as randomBytes2 } from "node:crypto";
208572
208679
  import { z as z30 } from "zod";
208573
208680
  var FileWriteInputSchema = z30.object({
@@ -208601,7 +208708,7 @@ var fileWriteTool = {
208601
208708
  if (input.create_dirs) {
208602
208709
  await mkdir(dir, { recursive: true });
208603
208710
  }
208604
- const tmp = `${dir}/.${basename2(path2)}.${randomBytes2(6).toString("hex")}.tmp`;
208711
+ const tmp = `${dir}/.${basename(path2)}.${randomBytes2(6).toString("hex")}.tmp`;
208605
208712
  try {
208606
208713
  await writeFile(tmp, input.content, "utf8");
208607
208714
  await rename(tmp, path2);
@@ -208626,7 +208733,7 @@ var fileWriteTool = {
208626
208733
 
208627
208734
  // ../../packages/tools/src/builtin/file-ops/file-edit.ts
208628
208735
  import { readFile as readFile2, writeFile as writeFile2, rename as rename2, unlink as unlink2 } from "node:fs/promises";
208629
- import { dirname as dirname3, basename as basename3 } from "node:path";
208736
+ import { dirname as dirname3, basename as basename2 } from "node:path";
208630
208737
  import { randomBytes as randomBytes3 } from "node:crypto";
208631
208738
  import { z as z31 } from "zod";
208632
208739
  var FileEditInputSchema = z31.object({
@@ -208684,7 +208791,7 @@ var fileEditTool = {
208684
208791
  };
208685
208792
  }
208686
208793
  const dir = dirname3(path2);
208687
- const tmp = `${dir}/.${basename3(path2)}.${randomBytes3(6).toString("hex")}.tmp`;
208794
+ const tmp = `${dir}/.${basename2(path2)}.${randomBytes3(6).toString("hex")}.tmp`;
208688
208795
  try {
208689
208796
  await writeFile2(tmp, updated, "utf8");
208690
208797
  await rename2(tmp, path2);
@@ -208925,7 +209032,7 @@ import { z as z34 } from "zod";
208925
209032
  // ../../packages/tools/src/builtin/office-ops/office-helpers.ts
208926
209033
  var import_jszip = __toESM(require_lib3(), 1);
208927
209034
  import { readFile as readFile4, writeFile as writeFile3, rename as rename3, unlink as unlink3, stat as stat5 } from "node:fs/promises";
208928
- import { dirname as dirname4, basename as basename4 } from "node:path";
209035
+ import { dirname as dirname4, basename as basename3 } from "node:path";
208929
209036
  import { randomBytes as randomBytes4 } from "node:crypto";
208930
209037
  var MAX_OFFICE_BYTES = 25 * 1024 * 1024;
208931
209038
  var MAX_OFFICE_INFLATED_BYTES = 1024 * 1024 * 1024;
@@ -209039,7 +209146,7 @@ async function writeWorkspaceBinary(ctx, path2, buffer2, { overwrite = false } =
209039
209146
  };
209040
209147
  }
209041
209148
  const dir = dirname4(resolvedPath);
209042
- const tmp = `${dir}/.${basename4(resolvedPath)}.${randomBytes4(6).toString("hex")}.tmp`;
209149
+ const tmp = `${dir}/.${basename3(resolvedPath)}.${randomBytes4(6).toString("hex")}.tmp`;
209043
209150
  try {
209044
209151
  await writeFile3(tmp, buffer2);
209045
209152
  await rename3(tmp, resolvedPath);
@@ -211673,8 +211780,8 @@ var createAgentTool = {
211673
211780
  const { dbRole, orchestratorMode } = mapRole(input.role);
211674
211781
  const resolved = await resolveAgentProvider(ctx.db, ctx.entityId, null);
211675
211782
  if (resolved) {
211676
- const check16 = validateModelForProvider(resolved.provider, input.model);
211677
- if (!check16.ok) return check16;
211783
+ const check17 = validateModelForProvider(resolved.provider, input.model);
211784
+ if (!check17.ok) return check17;
211678
211785
  }
211679
211786
  let result;
211680
211787
  try {
@@ -211746,8 +211853,8 @@ var updateAgentTool = {
211746
211853
  if (input.model) {
211747
211854
  const resolved = await resolveAgentProvider(ctx.db, ctx.entityId, agent.llmKeyId);
211748
211855
  if (resolved) {
211749
- const check16 = validateModelForProvider(resolved.provider, input.model);
211750
- if (!check16.ok) return check16;
211856
+ const check17 = validateModelForProvider(resolved.provider, input.model);
211857
+ if (!check17.ok) return check17;
211751
211858
  }
211752
211859
  }
211753
211860
  const patch = { updatedAt: /* @__PURE__ */ new Date() };
@@ -212490,13 +212597,15 @@ var runScheduleTool = {
212490
212597
  task: agentSchedules.task,
212491
212598
  chatId: agentSchedules.chatId,
212492
212599
  notifyOnSuccess: agentSchedules.notifyOnSuccess,
212600
+ // B1 (notify-channel-choice): the schedule's explicit channel choice, if any.
212601
+ notifyChannel: agentSchedules.notifyChannel,
212493
212602
  // Manual "run now": prevRunAt is still "when did this schedule last
212494
212603
  // actually run" — this fire doesn't change that semantic.
212495
212604
  lastRun: agentSchedules.lastRun
212496
212605
  }).from(agentSchedules).where(and5(eq6(agentSchedules.entityId, ctx.entityId), eq6(agentSchedules.name, input.name))).limit(1);
212497
212606
  if (!sched) return { ok: false, error: `No schedule named "${input.name}" in this workspace.` };
212498
212607
  if (!sched.task) return { ok: false, error: `Schedule "${input.name}" has no task to run.` };
212499
- const resolvedChatId = sched.notifyOnSuccess ? sched.chatId ?? await resolveOwnerChatId(ctx.db, sched.agentId) ?? null : null;
212608
+ const resolvedChatId = sched.notifyOnSuccess ? sched.notifyChannel ? sched.chatId ?? await resolveOwnerConversation(ctx.db, sched.agentId, sched.notifyChannel) : sched.chatId ?? await resolveOwnerChatId(ctx.db, sched.agentId) ?? null : null;
212500
212609
  const [job] = await ctx.db.insert(agentJobs).values({
212501
212610
  entityId: ctx.entityId,
212502
212611
  agentId: sched.agentId,
@@ -212509,7 +212618,8 @@ var runScheduleTool = {
212509
212618
  triggerContext: {
212510
212619
  type: "cron",
212511
212620
  scheduleName: input.name,
212512
- prevRunAt: sched.lastRun ? sched.lastRun.toISOString() : null
212621
+ prevRunAt: sched.lastRun ? sched.lastRun.toISOString() : null,
212622
+ notifyChannel: sched.notifyChannel ?? null
212513
212623
  }
212514
212624
  }).returning({ id: agentJobs.id });
212515
212625
  if (!job) return { ok: false, error: "Failed to queue the schedule run." };
@@ -320983,7 +321093,7 @@ var generateMessageIDV2 = (userId) => {
320983
321093
  };
320984
321094
  var generateMessageID = () => "3EB0" + randomBytes5(18).toString("hex").toUpperCase();
320985
321095
  function bindWaitForEvent(ev, event) {
320986
- return async (check16, timeoutMs) => {
321096
+ return async (check17, timeoutMs) => {
320987
321097
  let listener;
320988
321098
  let closeListener;
320989
321099
  await promiseTimeout(timeoutMs, (resolve3, reject) => {
@@ -320994,7 +321104,7 @@ function bindWaitForEvent(ev, event) {
320994
321104
  };
320995
321105
  ev.on("connection.update", closeListener);
320996
321106
  listener = async (update) => {
320997
- if (await check16(update)) {
321107
+ if (await check17(update)) {
320998
321108
  resolve3();
320999
321109
  }
321000
321110
  };
@@ -335272,16 +335382,28 @@ function getAdapter3(channel2) {
335272
335382
  }
335273
335383
 
335274
335384
  // ../../packages/delivery/src/transport-channel.ts
335275
- var TRANSPORT_CHANNELS = /* @__PURE__ */ new Set([
335276
- "telegram",
335277
- "discord",
335278
- "slack",
335279
- "whatsapp"
335280
- ]);
335281
- function resolveTransportChannel(channel2) {
335385
+ var CHANNEL_PRIORITY = ["telegram", "discord", "slack", "whatsapp"];
335386
+ var TRANSPORT_CHANNELS = new Set(CHANNEL_PRIORITY);
335387
+ function resolveTransportChannel(channel2, activeChannels) {
335282
335388
  if (channel2 && TRANSPORT_CHANNELS.has(channel2)) return channel2;
335389
+ if (activeChannels && activeChannels.length > 0) {
335390
+ const active = new Set(activeChannels);
335391
+ const preferred = CHANNEL_PRIORITY.find((c) => active.has(c));
335392
+ if (preferred) return preferred;
335393
+ }
335283
335394
  return "telegram";
335284
335395
  }
335396
+ async function listActiveChannelsForAgent(db, agentId) {
335397
+ const [agentRow] = await db.select({ telegramBotToken: agents.telegramBotToken }).from(agents).where(eq6(agents.id, agentId)).limit(1);
335398
+ const bindings = await listChannelBindings(db, agentId);
335399
+ const enabled = new Set(bindings.filter((b) => b.enabled).map((b) => b.channel));
335400
+ const active = [];
335401
+ if (agentRow?.telegramBotToken) active.push("telegram");
335402
+ for (const channel2 of CHANNEL_PRIORITY) {
335403
+ if (channel2 !== "telegram" && enabled.has(channel2)) active.push(channel2);
335404
+ }
335405
+ return active;
335406
+ }
335285
335407
 
335286
335408
  // ../../packages/tools/src/builtin/list-conversations.ts
335287
335409
  var MAX_ROWS = 150;
@@ -335442,8 +335564,11 @@ import { z as z60 } from "zod";
335442
335564
  import { realpath as realpath3 } from "node:fs/promises";
335443
335565
  import { tmpdir as tmpdir3 } from "node:os";
335444
335566
  import path from "node:path";
335567
+ function defaultChannelForJob(ctx) {
335568
+ return ctx.notifyChannelOverride ?? resolveTransportChannel(ctx.jobChannel, ctx.activeChannels);
335569
+ }
335445
335570
  async function resolveChannelForJob(ctx, explicitChannel) {
335446
- const jobChannel = resolveTransportChannel(ctx.jobChannel);
335571
+ const jobChannel = defaultChannelForJob(ctx);
335447
335572
  if (explicitChannel === void 0 || explicitChannel === jobChannel) {
335448
335573
  return jobChannel;
335449
335574
  }
@@ -335489,7 +335614,7 @@ async function resolveRecipientChatId(explicitChatId, ctx, noRecipientErrorName,
335489
335614
  throw err;
335490
335615
  }
335491
335616
  const channel2 = await resolveChannelForJob(ctx, explicitChannel);
335492
- const crossChannel = explicitChannel !== void 0 && explicitChannel !== resolveTransportChannel(ctx.jobChannel);
335617
+ const crossChannel = explicitChannel !== void 0 && explicitChannel !== defaultChannelForJob(ctx);
335493
335618
  let chatId = explicitChatId ?? (crossChannel ? null : ctx.jobChatId);
335494
335619
  if (explicitChatId === void 0 && chatId === null) {
335495
335620
  chatId = await resolveOwnerConversation(ctx.db, ctx.agentId, channel2);
@@ -335522,7 +335647,32 @@ async function resolveRecipientChatId(explicitChatId, ctx, noRecipientErrorName,
335522
335647
  return chatId;
335523
335648
  }
335524
335649
  async function assertLocalSourceAllowed(source, ctx) {
335525
- const real3 = await realpath3(source);
335650
+ let candidate = source;
335651
+ if (!path.isAbsolute(source)) {
335652
+ try {
335653
+ candidate = await resolveAndCheckPath(ctx, source);
335654
+ } catch (err) {
335655
+ if (err instanceof WorkspaceError) {
335656
+ const wrapped = new Error(`source_path_not_allowed: ${err.message}`);
335657
+ wrapped.name = "source_path_not_allowed";
335658
+ throw wrapped;
335659
+ }
335660
+ throw err;
335661
+ }
335662
+ }
335663
+ let real3;
335664
+ try {
335665
+ real3 = await realpath3(candidate);
335666
+ } catch (err) {
335667
+ if (err.code === "ENOENT") {
335668
+ const wrapped = new Error(
335669
+ path.isAbsolute(source) ? `source_path_not_allowed: no file found at "${source}".` : `source_path_not_allowed: no file found at workspace-relative path "${source}" (resolved to "${candidate}").`
335670
+ );
335671
+ wrapped.name = "source_path_not_allowed";
335672
+ throw wrapped;
335673
+ }
335674
+ throw err;
335675
+ }
335526
335676
  const roots = [
335527
335677
  ...(ctx.workspaces ?? []).map((w) => w.path),
335528
335678
  ...ctx.skillStoreDir ? [ctx.skillStoreDir] : [],
@@ -336211,7 +336361,7 @@ async function healthRoute(c, deps) {
336211
336361
  // LLM is not pinged on every health check (expensive)
336212
336362
  };
336213
336363
  try {
336214
- await deps.db.execute(sql22`SELECT 1`);
336364
+ await deps.db.execute(sql23`SELECT 1`);
336215
336365
  result.db = "ok";
336216
336366
  } catch (err) {
336217
336367
  console.error("[health] DB ping failed:", err instanceof Error ? err.message : err);
@@ -351608,7 +351758,9 @@ async function walkJobChainToRoot(db, jobId) {
351608
351758
  async function resolveChannelApprovalDeliveryTarget(db, jobId) {
351609
351759
  const chain = await walkJobChainToRoot(db, jobId);
351610
351760
  if (!chain) return null;
351611
- const rootChannel = resolveTransportChannel(chain[chain.length - 1].channel);
351761
+ const rootHop = chain[chain.length - 1];
351762
+ const activeChannels = await listActiveChannelsForAgent(db, rootHop.agentId);
351763
+ const rootChannel = resolveTransportChannel(rootHop.channel, activeChannels);
351612
351764
  if (rootChannel === "telegram") {
351613
351765
  const target = await resolveApprovalDeliveryTarget(db, jobId);
351614
351766
  if (!target) return null;
@@ -351697,7 +351849,7 @@ import { join as join12 } from "node:path";
351697
351849
 
351698
351850
  // src/skills/install.ts
351699
351851
  import { cp, rm as rm2, mkdir as mkdir5, readFile as readFile11 } from "node:fs/promises";
351700
- import { dirname as dirname7, join as join11, basename as basename5, sep as sep5, resolve as resolve2 } from "node:path";
351852
+ import { dirname as dirname7, join as join11, basename as basename4, sep as sep5, resolve as resolve2 } from "node:path";
351701
351853
 
351702
351854
  // src/skills/source.ts
351703
351855
  var SkillSourceError = class extends Error {
@@ -352477,7 +352629,7 @@ async function pickManifest(extractRoot, subdir, skillName) {
352477
352629
  }
352478
352630
  }
352479
352631
  if (byFrontmatter.length > 0) return byFrontmatter[0];
352480
- const byFolder = manifests.filter((rel) => basename5(dirname7(rel)) === skillName);
352632
+ const byFolder = manifests.filter((rel) => basename4(dirname7(rel)) === skillName);
352481
352633
  if (byFolder.length > 0) return byFolder[0];
352482
352634
  throw new SkillInstallError(
352483
352635
  `Repository has multiple skills but none named "${skillName}". Found: ${manifests.map((m) => dirname7(m)).join(", ")}.`
@@ -353380,13 +353532,15 @@ async function runJob(jobId, deps, runnerEnv, opts) {
353380
353532
  const canSeeImages = llmClient.capabilities.vision === true || modelCanSeeImages(llmClient.config.model);
353381
353533
  const imageCache = /* @__PURE__ */ new Map();
353382
353534
  const isOrchestrator = agent.role === "orchestrator";
353383
- const cronWantsConfirmation = job.channel === "cron" && job.chatId != null;
353535
+ const triggerWantsConfirmation = (job.channel === "cron" || job.channel === "webhook") && job.chatId != null;
353536
+ const jobTriggerContext = job.triggerContext;
353537
+ const notifyChannelOverride = jobTriggerContext?.type === "cron" || jobTriggerContext?.type === "webhook" ? jobTriggerContext.notifyChannel ?? void 0 : void 0;
353384
353538
  const deployment = await getDeploymentContext(db, job.entityId ?? void 0);
353385
353539
  const jobContext = {
353386
353540
  origin: job.channel ?? "unknown",
353387
353541
  ...job.task ? { task: job.task } : {},
353388
353542
  ...job.chatId ? { telegramChatId: job.chatId } : {},
353389
- ...cronWantsConfirmation ? { notifyOnSuccess: true } : {},
353543
+ ...triggerWantsConfirmation ? { notifyOnSuccess: true } : {},
353390
353544
  ...job.parentJobId ? { isDelegated: true } : {},
353391
353545
  ...job.triggerContext ? { triggerContext: job.triggerContext } : {},
353392
353546
  deployment
@@ -353422,6 +353576,11 @@ async function runJob(jobId, deps, runnerEnv, opts) {
353422
353576
  capabilityTools.push(createSendVoiceTool());
353423
353577
  capabilityTools.push(createListConversationsTool());
353424
353578
  }
353579
+ const activeChannels = [
353580
+ ...deliveryBotToken ? ["telegram"] : [],
353581
+ ...hasDiscordBinding ? ["discord"] : [],
353582
+ ...hasSlackBinding ? ["slack"] : []
353583
+ ];
353425
353584
  const mcpClosers = [];
353426
353585
  try {
353427
353586
  const [rootEntityRow] = await db.select({
@@ -353511,7 +353670,10 @@ async function runJob(jobId, deps, runnerEnv, opts) {
353511
353670
  decryptedEnv = Object.fromEntries(
353512
353671
  Object.entries(rawEnv).map(([k, v]) => [k, decrypt(v)])
353513
353672
  );
353514
- } catch {
353673
+ } catch (err) {
353674
+ console.error(
353675
+ `[execute] MCP server '${ms.slug}' env vars failed to decrypt (tampered, or wrong/rotated master key): ${err instanceof Error ? err.message : String(err)}`
353676
+ );
353515
353677
  continue;
353516
353678
  }
353517
353679
  const connectOpts = {
@@ -353625,8 +353787,8 @@ async function runJob(jobId, deps, runnerEnv, opts) {
353625
353787
  return { status: "failed", error: errorCode };
353626
353788
  }
353627
353789
  const ruleRows = await db.select().from(approvalRules).where(eq6(approvalRules.entityId, job.entityId ?? "")).orderBy(
353628
- sql22`${approvalRules.agentId} IS NULL`,
353629
- sql22`${approvalRules.toolName} = '*'`,
353790
+ sql23`${approvalRules.agentId} IS NULL`,
353791
+ sql23`${approvalRules.toolName} = '*'`,
353630
353792
  approvalRules.id
353631
353793
  );
353632
353794
  let approvalRuleList = ruleRows.map((r) => ({
@@ -353745,6 +353907,8 @@ async function runJob(jobId, deps, runnerEnv, opts) {
353745
353907
  db,
353746
353908
  jobChatId: job.chatId ?? null,
353747
353909
  jobChannel: job.channel,
353910
+ activeChannels,
353911
+ notifyChannelOverride,
353748
353912
  resolvedTelegramBotToken: inheritedRootBotToken ?? void 0,
353749
353913
  embeddingClient: deps.embeddingClient,
353750
353914
  workspaces: agentWorkspacesList,
@@ -353846,7 +354010,7 @@ async function runJob(jobId, deps, runnerEnv, opts) {
353846
354010
  const MAX_EMPTY_TURN_RETRIES = 2;
353847
354011
  let emptyTurnRetries = 0;
353848
354012
  let consecutiveDeliveryOnlyTurns = 0;
353849
- const requiresToolDelivery = TOOL_ONLY_DELIVERY_CHANNELS.has(job.channel ?? "") || cronWantsConfirmation;
354013
+ const requiresToolDelivery = TOOL_ONLY_DELIVERY_CHANNELS.has(job.channel ?? "") || triggerWantsConfirmation;
353850
354014
  const CHANNEL_DISPLAY_NAMES = {
353851
354015
  telegram: "Telegram",
353852
354016
  discord: "Discord",
@@ -354179,17 +354343,21 @@ ${notice}` : notice;
354179
354343
  return part.providerMetadata != null && part.providerOptions == null ? { ...p, providerOptions: part.providerMetadata } : p;
354180
354344
  });
354181
354345
  const hasText = (response.text ?? "").trim().length > 0;
354346
+ const toolCallParts = rawToolCalls.map((tc) => {
354347
+ const base = {
354348
+ type: "tool-call",
354349
+ toolCallId: tc.toolCallId,
354350
+ toolName: tc.toolName,
354351
+ input: tc.input
354352
+ };
354353
+ return tc.providerMetadata != null ? { ...base, providerOptions: tc.providerMetadata } : base;
354354
+ });
354182
354355
  const assistantMsg = {
354183
354356
  role: "assistant",
354184
354357
  content: rawToolCalls.length > 0 ? [
354185
354358
  ...reasoningParts,
354186
354359
  ...hasText ? [{ type: "text", text: response.text || "" }] : [],
354187
- ...rawToolCalls.map((tc) => ({
354188
- type: "tool-call",
354189
- toolCallId: tc.toolCallId,
354190
- toolName: tc.toolName,
354191
- input: tc.input
354192
- }))
354360
+ ...toolCallParts
354193
354361
  ] : reasoningParts.length > 0 ? [...reasoningParts, { type: "text", text: response.text || "" }] : response.text || ""
354194
354362
  };
354195
354363
  messages = [...messages, assistantMsg];
@@ -354293,6 +354461,8 @@ ${notice}` : notice;
354293
354461
  db,
354294
354462
  jobChatId: job.chatId ?? null,
354295
354463
  jobChannel: job.channel,
354464
+ activeChannels,
354465
+ notifyChannelOverride,
354296
354466
  resolvedTelegramBotToken: inheritedRootBotToken ?? void 0,
354297
354467
  embeddingClient: deps.embeddingClient,
354298
354468
  workspaces: agentWorkspacesList,
@@ -354481,6 +354651,8 @@ ${notice}` : notice;
354481
354651
  db,
354482
354652
  jobChatId: job.chatId ?? null,
354483
354653
  jobChannel: job.channel,
354654
+ activeChannels,
354655
+ notifyChannelOverride,
354484
354656
  resolvedTelegramBotToken: inheritedRootBotToken ?? void 0,
354485
354657
  embeddingClient: deps.embeddingClient,
354486
354658
  workspaces: agentWorkspacesList,
@@ -355637,7 +355809,8 @@ async function notifyBudgetExhausted(db, agentId, scheduleName, dailyBudgetUsd)
355637
355809
  try {
355638
355810
  const ownerChatId = await resolveOwnerChatId(db, agentId);
355639
355811
  if (!ownerChatId) return;
355640
- const channel2 = resolveTransportChannel("cron");
355812
+ const activeChannels = await listActiveChannelsForAgent(db, agentId);
355813
+ const channel2 = resolveTransportChannel("cron", activeChannels);
355641
355814
  const creds = await getBindingCredentials(db, agentId, channel2);
355642
355815
  if (!creds) return;
355643
355816
  const adapter2 = getAdapter3(channel2);
@@ -355671,6 +355844,9 @@ async function runScheduleTick(db, deps, max2 = 5) {
355671
355844
  notifyOnSuccess: agentSchedules.notifyOnSuccess,
355672
355845
  // Explicit delivery target (e.g. "post to #team"), null for the common case.
355673
355846
  chatId: agentSchedules.chatId,
355847
+ // Explicit notify channel choice (B1), null = auto (first active channel
355848
+ // by priority, the historical behavior — see the chatId resolution below).
355849
+ notifyChannel: agentSchedules.notifyChannel,
355674
355850
  // Read BEFORE this tick's writes — "was it already budget_exhausted" is
355675
355851
  // the transition check the notify-once-per-day dedup relies on below.
355676
355852
  lastStatus: agentSchedules.lastStatus,
@@ -355683,6 +355859,7 @@ async function runScheduleTick(db, deps, max2 = 5) {
355683
355859
  ).limit(max2 * 2);
355684
355860
  if (candidates.length === 0) return 0;
355685
355861
  const fires = [];
355862
+ const notifyUnreachableScheduleIds = /* @__PURE__ */ new Set();
355686
355863
  for (const sched of candidates.slice(0, max2)) {
355687
355864
  if (!sched.task || !sched.entityId) continue;
355688
355865
  const [liveJob] = await db.select({ id: agentJobs.id, createdAt: agentJobs.createdAt }).from(agentJobs).where(and5(eq6(agentJobs.scheduleId, sched.id), inArray4(agentJobs.status, LIVE_JOB_STATUSES))).orderBy(desc(agentJobs.createdAt)).limit(1);
@@ -355700,8 +355877,8 @@ async function runScheduleTick(db, deps, max2 = 5) {
355700
355877
  continue;
355701
355878
  }
355702
355879
  const tz = resolveTimezone(sched.timezone);
355703
- const [spentRow] = await db.select({ spent: sql22`COALESCE(SUM(${agentJobs.totalCostUsd}), 0)` }).from(agentJobs).where(
355704
- sql22`${agentJobs.scheduleId} = ${sched.id} AND ${agentJobs.createdAt} >= (date_trunc('day', now() AT TIME ZONE ${tz}) AT TIME ZONE ${tz})`
355880
+ const [spentRow] = await db.select({ spent: sql23`COALESCE(SUM(${agentJobs.totalCostUsd}), 0)` }).from(agentJobs).where(
355881
+ sql23`${agentJobs.scheduleId} = ${sched.id} AND ${agentJobs.createdAt} >= (date_trunc('day', now() AT TIME ZONE ${tz}) AT TIME ZONE ${tz})`
355705
355882
  );
355706
355883
  const spentToday = Number(spentRow?.spent ?? 0);
355707
355884
  if (spentToday >= sched.dailyBudgetUsd) {
@@ -355742,7 +355919,21 @@ async function runScheduleTick(db, deps, max2 = 5) {
355742
355919
  )
355743
355920
  ).returning({ id: agentSchedules.id });
355744
355921
  if (claimed.length === 0) continue;
355745
- const notifyChatId = sched.notifyOnSuccess ? sched.chatId ?? await resolveOwnerChatId(db, sched.agentId) ?? null : null;
355922
+ let notifyChatId = null;
355923
+ if (sched.notifyOnSuccess) {
355924
+ if (sched.notifyChannel) {
355925
+ const channel2 = sched.notifyChannel;
355926
+ notifyChatId = sched.chatId ?? await resolveOwnerConversation(db, sched.agentId, channel2);
355927
+ if (!notifyChatId) {
355928
+ notifyUnreachableScheduleIds.add(sched.id);
355929
+ console.error(
355930
+ `[runScheduleTick] schedule "${sched.name}" (${sched.id}) chose notify channel '${channel2}' but has no owner conversation there yet (never DMed on that channel). Firing WITHOUT a delivery target \u2014 not falling back to another channel. lastStatus will read 'notify_unreachable'.`
355931
+ );
355932
+ }
355933
+ } else {
355934
+ notifyChatId = sched.chatId ?? await resolveOwnerChatId(db, sched.agentId) ?? null;
355935
+ }
355936
+ }
355746
355937
  const [job] = await db.insert(agentJobs).values({
355747
355938
  entityId: sched.entityId,
355748
355939
  agentId: sched.agentId,
@@ -355755,7 +355946,8 @@ async function runScheduleTick(db, deps, max2 = 5) {
355755
355946
  triggerContext: {
355756
355947
  type: "cron",
355757
355948
  scheduleName: sched.name,
355758
- prevRunAt: sched.lastRun ? sched.lastRun.toISOString() : null
355949
+ prevRunAt: sched.lastRun ? sched.lastRun.toISOString() : null,
355950
+ notifyChannel: sched.notifyChannel ?? null
355759
355951
  }
355760
355952
  }).returning({ id: agentJobs.id });
355761
355953
  if (!job) {
@@ -355784,7 +355976,9 @@ async function runScheduleTick(db, deps, max2 = 5) {
355784
355976
  if (settled.status === "rejected") continue;
355785
355977
  const { fire, result } = settled.value;
355786
355978
  let lastStatus;
355787
- if (result.status === "completed") lastStatus = "success";
355979
+ if (notifyUnreachableScheduleIds.has(fire.scheduleId)) {
355980
+ lastStatus = "notify_unreachable";
355981
+ } else if (result.status === "completed") lastStatus = "success";
355788
355982
  else if (result.status === "failed") lastStatus = "failed";
355789
355983
  else if (result.status === "cancelled") lastStatus = "failed";
355790
355984
  else lastStatus = "no_action";
@@ -355818,7 +356012,8 @@ async function deliverCompletedRoots(db) {
355818
356012
  chatId: agentJobs.chatId,
355819
356013
  agentId: agentJobs.agentId,
355820
356014
  entityId: agentJobs.entityId,
355821
- task: agentJobs.task
356015
+ task: agentJobs.task,
356016
+ triggerContext: agentJobs.triggerContext
355822
356017
  }).from(agentJobs).where(eq6(agentJobs.id, rootJobId)).limit(1);
355823
356018
  const rootJob = rootJobRows[0];
355824
356019
  if (!rootJob) continue;
@@ -355863,7 +356058,10 @@ async function deliverCompletedRoots(db) {
355863
356058
  }
355864
356059
  if (rootJob.chatId && rootJob.agentId && compiledResult.trim()) {
355865
356060
  try {
355866
- const channel2 = resolveTransportChannel(rootJob.channel);
356061
+ const activeChannels = await listActiveChannelsForAgent(db, rootJob.agentId);
356062
+ const triggerContext = rootJob.triggerContext;
356063
+ const notifyChannelOverride = triggerContext?.type === "cron" || triggerContext?.type === "webhook" ? triggerContext.notifyChannel ?? void 0 : void 0;
356064
+ const channel2 = notifyChannelOverride ?? resolveTransportChannel(rootJob.channel, activeChannels);
355867
356065
  const allowed = rootJob.entityId !== null && await isConversationAllowed(db, {
355868
356066
  entityId: rootJob.entityId,
355869
356067
  agentId: rootJob.agentId,
@@ -356376,12 +356574,12 @@ async function runCuratorTick(db, _deps, runnerEnv) {
356376
356574
  eq6(agentSkills.state, "active"),
356377
356575
  eq6(entities.reflectionEnabled, true)
356378
356576
  )
356379
- ).groupBy(agentSkills.entityId).having(sql22`count(${agentSkills.id}) >= ${e2.CURATOR_MIN_SKILLS}`);
356577
+ ).groupBy(agentSkills.entityId).having(sql23`count(${agentSkills.id}) >= ${e2.CURATOR_MIN_SKILLS}`);
356380
356578
  for (const c of skillCandidates) if (c.entityId) skillSet.add(c.entityId);
356381
356579
  }
356382
356580
  const memorySet = /* @__PURE__ */ new Set();
356383
356581
  if (e2.MEMORY_CURATION_ENABLED !== "false") {
356384
- const memoryCandidates = await db.select({ entityId: agentMemory.entityId }).from(agentMemory).innerJoin(entities, eq6(entities.id, agentMemory.entityId)).where(and5(eq6(agentMemory.archived, false), eq6(entities.memoryCurationEnabled, true))).groupBy(agentMemory.entityId).having(sql22`count(${agentMemory.id}) >= ${e2.CURATOR_MEMORY_MIN}`);
356582
+ const memoryCandidates = await db.select({ entityId: agentMemory.entityId }).from(agentMemory).innerJoin(entities, eq6(entities.id, agentMemory.entityId)).where(and5(eq6(agentMemory.archived, false), eq6(entities.memoryCurationEnabled, true))).groupBy(agentMemory.entityId).having(sql23`count(${agentMemory.id}) >= ${e2.CURATOR_MEMORY_MIN}`);
356385
356583
  for (const c of memoryCandidates) if (c.entityId) memorySet.add(c.entityId);
356386
356584
  }
356387
356585
  if (skillSet.size === 0 && memorySet.size === 0) {
@@ -357024,18 +357222,38 @@ async function webhookRoute(c, deps, runnerEnv) {
357024
357222
  const triggeredAt = (/* @__PURE__ */ new Date()).toISOString();
357025
357223
  const interpolated = interpolateTemplate(trigger.taskTemplate, bodyResult.value);
357026
357224
  const task = buildWebhookEnvelope(trigger.name, triggeredAt, interpolated);
357225
+ let notifyChatId = null;
357226
+ let notifyChannel = null;
357227
+ if (trigger.notifyOnSuccess) {
357228
+ if (trigger.notifyChannel) {
357229
+ notifyChannel = trigger.notifyChannel;
357230
+ } else {
357231
+ const activeChannels = (await listActiveChannelsForAgent(deps.db, agentId)).filter(
357232
+ (c2) => c2 !== "whatsapp"
357233
+ );
357234
+ notifyChannel = resolveTransportChannel("webhook", activeChannels);
357235
+ }
357236
+ notifyChatId = await resolveOwnerConversation(deps.db, agentId, notifyChannel);
357237
+ if (!notifyChatId) {
357238
+ console.error(
357239
+ `[webhook] trigger "${trigger.name}" (${trigger.slug}) chose notify channel '${notifyChannel}' but has no owner conversation there yet (never DMed on that channel). Firing WITHOUT a delivery target \u2014 not falling back to another channel.`
357240
+ );
357241
+ }
357242
+ }
357027
357243
  const [job] = await deps.db.insert(agentJobs).values({
357028
357244
  entityId: trigger.entityId ?? void 0,
357029
357245
  agentId,
357030
357246
  channel: "webhook",
357031
357247
  task,
357032
357248
  status: "pending",
357249
+ chatId: notifyChatId,
357033
357250
  messages: [{ role: "user", content: task }],
357034
357251
  triggerContext: {
357035
357252
  type: "webhook",
357036
357253
  webhookName: trigger.name,
357037
357254
  slug: trigger.slug,
357038
- triggeredAt
357255
+ triggeredAt,
357256
+ notifyChannel
357039
357257
  }
357040
357258
  }).returning({ id: agentJobs.id });
357041
357259
  if (!job) {
@@ -359351,7 +359569,18 @@ function startSlackSocket(opts) {
359351
359569
  );
359352
359570
  });
359353
359571
  });
359354
- void app.start().catch((err) => {
359572
+ void app.start().then(async () => {
359573
+ try {
359574
+ const identity = await app.client.auth.test();
359575
+ console.warn(
359576
+ `[slack-socket agent=${agentId}] connected (socket mode) as ${identity.user ?? "unknown"} (team=${identity.team ?? "unknown"})`
359577
+ );
359578
+ } catch (err) {
359579
+ console.warn(
359580
+ `[slack-socket agent=${agentId}] connected (socket mode), but auth.test failed: ${err instanceof Error ? err.message : String(err)}`
359581
+ );
359582
+ }
359583
+ }).catch((err) => {
359355
359584
  console.warn(
359356
359585
  `[slack-socket agent=${agentId}] start failed (invalid token(s)?): ${err instanceof Error ? err.message : String(err)}; will retry on the manager's next refresh`
359357
359586
  );