nodal-agents 0.7.9 → 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 (224) hide show
  1. package/README.md +6 -5
  2. package/cli.js +38 -2
  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 +277 -154
  8. package/web/.next/BUILD_ID +1 -1
  9. package/web/.next/app-path-routes-manifest.json +11 -11
  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 +1 -1
  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 +165 -165
  102. package/web/.next/server/app/onboarding/page_client-reference-manifest.js +1 -1
  103. package/web/.next/server/app-paths-manifest.json +11 -11
  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/4574.js +1 -1
  108. package/web/.next/server/chunks/5300.js +1 -0
  109. package/web/.next/server/chunks/6184.js +1 -0
  110. package/web/.next/server/chunks/6539.js +1 -0
  111. package/web/.next/server/chunks/7124.js +20 -20
  112. package/web/.next/server/chunks/8380.js +1 -0
  113. package/web/.next/server/chunks/9137.js +1 -0
  114. package/web/.next/server/chunks/{3847.js → 9507.js} +7 -7
  115. package/web/.next/server/chunks/9982.js +1 -0
  116. package/web/.next/server/middleware-build-manifest.js +1 -1
  117. package/web/.next/server/pages/404.html +1 -1
  118. package/web/.next/server/pages/500.html +1 -1
  119. package/web/.next/server/server-reference-manifest.js +1 -1
  120. package/web/.next/server/server-reference-manifest.json +1 -1
  121. package/web/.next/static/chunks/{7357-df6d26fbdbae296b.js → 1498-d0d434749bb18cd2.js} +1 -1
  122. package/web/.next/static/chunks/{7536-169aab9ed2feeab6.js → 2491-b14243a2db419dd9.js} +1 -1
  123. package/web/.next/static/chunks/4095-f76de40c53ae3d47.js +1 -0
  124. package/web/.next/static/chunks/4766-d80c537321cca448.js +1 -0
  125. package/web/.next/static/chunks/5876-4423499f49214575.js +1 -0
  126. package/web/.next/static/chunks/5953-54a29fc1e2d84c0c.js +1 -0
  127. package/web/.next/static/chunks/7829-78f753c50b408cde.js +1 -0
  128. package/web/.next/static/chunks/8542-ba178b7c68e14cf3.js +1 -0
  129. package/web/.next/static/chunks/9387-f5283e69fe4d4e83.js +1 -0
  130. package/web/.next/static/chunks/9567-db1512a42580d07c.js +1 -0
  131. package/web/.next/static/chunks/{8097-bad68cd78aa59a10.js → 9583-f93faba69d4c1f57.js} +1 -1
  132. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/channels/page-d40d5db150472c4f.js +1 -0
  133. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/edit/page-74934ff99c3b1261.js +6 -0
  134. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/telegram/page-d40d5db150472c4f.js +1 -0
  135. package/web/.next/static/chunks/app/(dashboard)/agents/page-8ec50d6f48425813.js +1 -0
  136. package/web/.next/static/chunks/app/(dashboard)/approvals/page-d907a7c12917ab8a.js +1 -0
  137. package/web/.next/static/chunks/app/(dashboard)/automations/page-dd20cb7f04d22cca.js +1 -0
  138. package/web/.next/static/chunks/app/(dashboard)/chat/page-e9ffd3d09a46ad38.js +1 -0
  139. package/web/.next/static/chunks/app/(dashboard)/connectors/page-52828e317e7a63e6.js +1 -0
  140. package/web/.next/static/chunks/app/(dashboard)/credentials/page-f84a3f991cf69228.js +1 -0
  141. package/web/.next/static/chunks/app/(dashboard)/jobs/[id]/page-a0866da7952de417.js +1 -0
  142. package/web/.next/static/chunks/app/(dashboard)/jobs/page-aade90b389efb824.js +1 -0
  143. package/web/.next/static/chunks/app/(dashboard)/layout-10486dd8640fb6a0.js +1 -0
  144. package/web/.next/static/chunks/app/(dashboard)/learned-skills/page-e22d1e586ac8579c.js +1 -0
  145. package/web/.next/static/chunks/app/(dashboard)/llm-providers/page-46858f7d737ee49b.js +1 -0
  146. package/web/.next/static/chunks/app/(dashboard)/logs/page-17dd2080f6196b01.js +1 -0
  147. package/web/.next/static/chunks/app/(dashboard)/mcp/page-81e83a08c7b65719.js +1 -0
  148. package/web/.next/static/chunks/app/(dashboard)/memories/page-0336ba541b5ab006.js +1 -0
  149. package/web/.next/static/chunks/app/(dashboard)/page-3bf3a72ec95863d8.js +1 -0
  150. package/web/.next/static/chunks/app/(dashboard)/settings/page-0ee25e256bc3097f.js +1 -0
  151. package/web/.next/static/chunks/app/(dashboard)/settings/root-context/page-76653439bca06d8c.js +1 -0
  152. package/web/.next/static/chunks/app/(dashboard)/skills/[id]/edit/page-de1bf934a2c5e8b2.js +1 -0
  153. package/web/.next/static/chunks/app/(dashboard)/skills/new/page-a9640274b845190a.js +1 -0
  154. package/web/.next/static/chunks/app/(dashboard)/skills/page-928b6f005ba4dafb.js +1 -0
  155. package/web/.next/static/chunks/app/_global-error/page-d40d5db150472c4f.js +1 -0
  156. package/web/.next/static/chunks/app/_not-found/page-d40d5db150472c4f.js +1 -0
  157. package/web/.next/static/chunks/app/api/auth/[...all]/route-d40d5db150472c4f.js +1 -0
  158. package/web/.next/static/chunks/app/api/health/route-d40d5db150472c4f.js +1 -0
  159. package/web/.next/static/chunks/app/api/oauth/[provider]/callback/route-d40d5db150472c4f.js +1 -0
  160. package/web/.next/static/chunks/app/api/oauth/[provider]/start/route-d40d5db150472c4f.js +1 -0
  161. package/web/.next/static/chunks/app/auth/callback/route-d40d5db150472c4f.js +1 -0
  162. package/web/.next/static/chunks/app/error-ec052ef28a67f9b0.js +1 -0
  163. package/web/.next/static/chunks/app/login/{page-1063bbdf158045cf.js → page-97a658fa01e36c3f.js} +1 -1
  164. package/web/.next/static/chunks/app/onboarding/page-d7c5a220f9244b45.js +26 -0
  165. package/web/.next/static/chunks/next/dist/client/components/builtin/app-error-d40d5db150472c4f.js +1 -0
  166. package/web/.next/static/chunks/next/dist/client/components/builtin/forbidden-d40d5db150472c4f.js +1 -0
  167. package/web/.next/static/chunks/next/dist/client/components/builtin/unauthorized-d40d5db150472c4f.js +1 -0
  168. package/web/.next/static/css/0f40c037c3aa9ff2.css +3 -0
  169. package/web/server.js +1 -1
  170. package/web/.next/server/chunks/1957.js +0 -1
  171. package/web/.next/server/chunks/2313.js +0 -1
  172. package/web/.next/server/chunks/2314.js +0 -1
  173. package/web/.next/server/chunks/2747.js +0 -1
  174. package/web/.next/server/chunks/3422.js +0 -1
  175. package/web/.next/server/chunks/4761.js +0 -1
  176. package/web/.next/server/chunks/5128.js +0 -1
  177. package/web/.next/server/chunks/6128.js +0 -1
  178. package/web/.next/server/chunks/9857.js +0 -1
  179. package/web/.next/static/chunks/2281-928529ce483ceb72.js +0 -1
  180. package/web/.next/static/chunks/3114-d846062bc8434b58.js +0 -1
  181. package/web/.next/static/chunks/3763-2fa54c0e83122050.js +0 -1
  182. package/web/.next/static/chunks/4766-0433c4d93110e059.js +0 -1
  183. package/web/.next/static/chunks/7831-b5f0e41f306942cc.js +0 -1
  184. package/web/.next/static/chunks/8447-ec639f4e1d42d912.js +0 -1
  185. package/web/.next/static/chunks/8943-8619f44ee3282119.js +0 -1
  186. package/web/.next/static/chunks/8992-1b711ff6a77d023a.js +0 -1
  187. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/channels/page-5c6067025edafbc0.js +0 -1
  188. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/edit/page-fc4002bde145810b.js +0 -6
  189. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/telegram/page-9397b9b7fccbb444.js +0 -1
  190. package/web/.next/static/chunks/app/(dashboard)/agents/page-be616beb457238ca.js +0 -1
  191. package/web/.next/static/chunks/app/(dashboard)/approvals/page-ca87808aaa42a079.js +0 -1
  192. package/web/.next/static/chunks/app/(dashboard)/automations/page-88c5b2061235936a.js +0 -1
  193. package/web/.next/static/chunks/app/(dashboard)/chat/page-197ee8afc4ef48b2.js +0 -1
  194. package/web/.next/static/chunks/app/(dashboard)/connectors/page-7bee350e3c9d4682.js +0 -1
  195. package/web/.next/static/chunks/app/(dashboard)/credentials/page-567d0e2e3c255976.js +0 -1
  196. package/web/.next/static/chunks/app/(dashboard)/jobs/[id]/page-2a5d3e0fd5248864.js +0 -1
  197. package/web/.next/static/chunks/app/(dashboard)/jobs/page-0d50850f43cb7ccb.js +0 -1
  198. package/web/.next/static/chunks/app/(dashboard)/layout-7f9bdb14405ef107.js +0 -1
  199. package/web/.next/static/chunks/app/(dashboard)/learned-skills/page-17878b5d662400b2.js +0 -1
  200. package/web/.next/static/chunks/app/(dashboard)/llm-providers/page-e38d2642e44278b1.js +0 -1
  201. package/web/.next/static/chunks/app/(dashboard)/logs/page-7ac6dbdcd9cf7042.js +0 -1
  202. package/web/.next/static/chunks/app/(dashboard)/mcp/page-a5b036571bb575b8.js +0 -1
  203. package/web/.next/static/chunks/app/(dashboard)/memories/page-1d7007962535c0db.js +0 -1
  204. package/web/.next/static/chunks/app/(dashboard)/page-55efec02eb59c413.js +0 -1
  205. package/web/.next/static/chunks/app/(dashboard)/settings/page-db76b2ad9934ead0.js +0 -1
  206. package/web/.next/static/chunks/app/(dashboard)/settings/root-context/page-5ffdba723e59a369.js +0 -1
  207. package/web/.next/static/chunks/app/(dashboard)/skills/[id]/edit/page-eaa016de6c6d8b02.js +0 -1
  208. package/web/.next/static/chunks/app/(dashboard)/skills/new/page-07669c2c3c9e3b0e.js +0 -1
  209. package/web/.next/static/chunks/app/(dashboard)/skills/page-393ec6f04d00e50f.js +0 -1
  210. package/web/.next/static/chunks/app/_global-error/page-9397b9b7fccbb444.js +0 -1
  211. package/web/.next/static/chunks/app/_not-found/page-9397b9b7fccbb444.js +0 -1
  212. package/web/.next/static/chunks/app/api/auth/[...all]/route-9397b9b7fccbb444.js +0 -1
  213. package/web/.next/static/chunks/app/api/health/route-9397b9b7fccbb444.js +0 -1
  214. package/web/.next/static/chunks/app/api/oauth/[provider]/callback/route-9397b9b7fccbb444.js +0 -1
  215. package/web/.next/static/chunks/app/api/oauth/[provider]/start/route-9397b9b7fccbb444.js +0 -1
  216. package/web/.next/static/chunks/app/auth/callback/route-9397b9b7fccbb444.js +0 -1
  217. package/web/.next/static/chunks/app/error-5e6b38c936ae1a78.js +0 -1
  218. package/web/.next/static/chunks/app/onboarding/page-799f8658aea94b41.js +0 -26
  219. package/web/.next/static/chunks/next/dist/client/components/builtin/app-error-9397b9b7fccbb444.js +0 -1
  220. package/web/.next/static/chunks/next/dist/client/components/builtin/forbidden-9397b9b7fccbb444.js +0 -1
  221. package/web/.next/static/chunks/next/dist/client/components/builtin/unauthorized-9397b9b7fccbb444.js +0 -1
  222. package/web/.next/static/css/110d6e5ec0dc635f.css +0 -3
  223. /package/web/.next/static/{xZ_yeD2bqK-_EOEqMMFYk → TWzSfBwiIIU81SgNxXcIw}/_buildManifest.js +0 -0
  224. /package/web/.next/static/{xZ_yeD2bqK-_EOEqMMFYk → TWzSfBwiIIU81SgNxXcIw}/_ssgManifest.js +0 -0
package/runner.js CHANGED
@@ -2372,6 +2372,17 @@ var init_schedules = __esm({
2372
2372
  // forces the agent to send the user a success confirmation before finishing.
2373
2373
  // Default false → the cron runs silently (the user must opt in per schedule).
2374
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"),
2375
2386
  // Per-schedule daily cost ceiling in USD (Event Triggers, Brique 3). Rolled
2376
2387
  // up against agent_jobs.total_cost_usd for this schedule since the start
2377
2388
  // of its local day — see runScheduleTick (apps/runner/src/cron/run-schedules.ts).
@@ -2387,7 +2398,11 @@ var init_schedules = __esm({
2387
2398
  check3("agent_schedules_type_check", sql3`${table.type} IN ('cron','heartbeat')`),
2388
2399
  check3(
2389
2400
  "agent_schedules_last_status_check",
2390
- 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`
2391
2406
  )
2392
2407
  ]
2393
2408
  );
@@ -2918,7 +2933,17 @@ var init_memory2 = __esm({
2918
2933
  });
2919
2934
 
2920
2935
  // ../../packages/db/src/schema/webhooks.ts
2921
- 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";
2922
2947
  var webhookTriggers;
2923
2948
  var init_webhooks = __esm({
2924
2949
  "../../packages/db/src/schema/webhooks.ts"() {
@@ -2939,10 +2964,30 @@ var init_webhooks = __esm({
2939
2964
  secret: text13("secret"),
2940
2965
  lastTriggeredAt: timestamp13("last_triggered_at", { withTimezone: true }),
2941
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"),
2942
2981
  createdAt: timestamp13("created_at", { withTimezone: true }).defaultNow(),
2943
2982
  updatedAt: timestamp13("updated_at", { withTimezone: true }).defaultNow()
2944
2983
  },
2945
- (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
+ ]
2946
2991
  );
2947
2992
  }
2948
2993
  });
@@ -2959,7 +3004,7 @@ import {
2959
3004
  index as index13,
2960
3005
  unique as unique3
2961
3006
  } from "drizzle-orm/pg-core";
2962
- import { sql as sql11 } from "drizzle-orm";
3007
+ import { sql as sql12 } from "drizzle-orm";
2963
3008
  var agentSkills, skillVersions, skillConnectors, agentSkillAssignments;
2964
3009
  var init_skills = __esm({
2965
3010
  "../../packages/db/src/schema/skills.ts"() {
@@ -2979,9 +3024,9 @@ var init_skills = __esm({
2979
3024
  description: text14("description"),
2980
3025
  defaultContent: text14("default_content"),
2981
3026
  contentOverridden: boolean9("content_overridden").default(false),
2982
- requiredConfig: jsonb7("required_config").default(sql11`'[]'::jsonb`),
2983
- operations: jsonb7("operations").default(sql11`'[]'::jsonb`),
2984
- 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[]`),
2985
3030
  // ─── Community-installed skills (open Agent Skills / SKILL.md format) ───
2986
3031
  // is_community = true for skills installed at runtime from an external
2987
3032
  // source (vs system catalog or user-authored custom skills). source is the
@@ -3042,7 +3087,7 @@ var init_skills = __esm({
3042
3087
  },
3043
3088
  (table) => [
3044
3089
  index13("idx_skill_versions_entity_id").on(table.entityId),
3045
- 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`)
3046
3091
  ]
3047
3092
  );
3048
3093
  skillConnectors = pgTable14(
@@ -3064,7 +3109,7 @@ var init_skills = __esm({
3064
3109
  entityId: uuid14("entity_id").notNull().references(() => entities.id, { onDelete: "cascade" }),
3065
3110
  agentId: uuid14("agent_id").notNull().references(() => agents.id, { onDelete: "cascade" }),
3066
3111
  skillId: uuid14("skill_id").notNull().references(() => agentSkills.id, { onDelete: "cascade" }),
3067
- approvalOverrides: jsonb7("approval_overrides").default(sql11`'{}'::jsonb`),
3112
+ approvalOverrides: jsonb7("approval_overrides").default(sql12`'{}'::jsonb`),
3068
3113
  useCustomInstructions: boolean9("use_custom_instructions").notNull().default(false),
3069
3114
  enabledOperations: text14("enabled_operations").array(),
3070
3115
  // Per-skill × per-agent authorization to EXECUTE the skill's bundled scripts
@@ -3105,9 +3150,9 @@ import {
3105
3150
  timestamp as timestamp15,
3106
3151
  index as index14,
3107
3152
  uniqueIndex as uniqueIndex2,
3108
- check as check10
3153
+ check as check11
3109
3154
  } from "drizzle-orm/pg-core";
3110
- import { sql as sql12 } from "drizzle-orm";
3155
+ import { sql as sql13 } from "drizzle-orm";
3111
3156
  var mcpServers, agentMcpServers, mcpConnections;
3112
3157
  var init_mcp = __esm({
3113
3158
  "../../packages/db/src/schema/mcp.ts"() {
@@ -3124,8 +3169,8 @@ var init_mcp = __esm({
3124
3169
  transport: text15("transport").notNull(),
3125
3170
  url: text15("url"),
3126
3171
  command: text15("command"),
3127
- args: text15("args").array().default(sql12`'{}'::text[]`),
3128
- envVars: jsonb8("env_vars").default(sql12`'{}'::jsonb`),
3172
+ args: text15("args").array().default(sql13`'{}'::text[]`),
3173
+ envVars: jsonb8("env_vars").default(sql13`'{}'::jsonb`),
3129
3174
  // Encrypted (enc:v1: blob) credential for HTTP MCP servers — same pattern
3130
3175
  // as connectors.api_key. NULL for servers that need no auth.
3131
3176
  apiKey: text15("api_key"),
@@ -3143,10 +3188,10 @@ var init_mcp = __esm({
3143
3188
  updatedAt: timestamp15("updated_at", { withTimezone: true }).defaultNow()
3144
3189
  },
3145
3190
  (table) => [
3146
- check10("mcp_servers_transport_check", sql12`${table.transport} IN ('http','stdio')`),
3147
- check10(
3191
+ check11("mcp_servers_transport_check", sql13`${table.transport} IN ('http','stdio')`),
3192
+ check11(
3148
3193
  "mcp_servers_auth_scheme_check",
3149
- 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`
3150
3195
  )
3151
3196
  // Multi-instance brique (migration 0017): the (entity_id, slug) UNIQUE
3152
3197
  // index was dropped to allow multiple instances of the same MCP server
@@ -3179,7 +3224,7 @@ var init_mcp = __esm({
3179
3224
  entityId: uuid15("entity_id").notNull().references(() => entities.id, { onDelete: "cascade" }),
3180
3225
  slug: text15("slug").notNull(),
3181
3226
  active: boolean10("active").notNull().default(true),
3182
- toolConfig: jsonb8("tool_config").notNull().default(sql12`'{}'::jsonb`),
3227
+ toolConfig: jsonb8("tool_config").notNull().default(sql13`'{}'::jsonb`),
3183
3228
  createdAt: timestamp15("created_at", { withTimezone: true }).notNull().defaultNow(),
3184
3229
  updatedAt: timestamp15("updated_at", { withTimezone: true }).notNull().defaultNow()
3185
3230
  },
@@ -3205,10 +3250,10 @@ import {
3205
3250
  jsonb as jsonb9,
3206
3251
  timestamp as timestamp16,
3207
3252
  index as index15,
3208
- check as check11,
3253
+ check as check12,
3209
3254
  unique as unique4
3210
3255
  } from "drizzle-orm/pg-core";
3211
- import { sql as sql13 } from "drizzle-orm";
3256
+ import { sql as sql14 } from "drizzle-orm";
3212
3257
  var configuratorSessions, agentPlugins, rateLimits;
3213
3258
  var init_misc = __esm({
3214
3259
  "../../packages/db/src/schema/misc.ts"() {
@@ -3221,7 +3266,7 @@ var init_misc = __esm({
3221
3266
  id: uuid16("id").primaryKey().defaultRandom(),
3222
3267
  entityId: uuid16("entity_id").notNull().references(() => entities.id, { onDelete: "cascade" }),
3223
3268
  agentId: uuid16("agent_id").references(() => agents.id, { onDelete: "cascade" }),
3224
- messages: jsonb9("messages").notNull().default(sql13`'[]'::jsonb`),
3269
+ messages: jsonb9("messages").notNull().default(sql14`'[]'::jsonb`),
3225
3270
  status: text16("status").notNull().default("active"),
3226
3271
  turnCount: integer9("turn_count").notNull().default(0),
3227
3272
  createdAt: timestamp16("created_at", { withTimezone: true }).notNull().defaultNow(),
@@ -3241,7 +3286,7 @@ var init_misc = __esm({
3241
3286
  slug: text16("slug").notNull(),
3242
3287
  description: text16("description"),
3243
3288
  pluginType: text16("plugin_type").notNull(),
3244
- config: jsonb9("config").default(sql13`'{}'::jsonb`),
3289
+ config: jsonb9("config").default(sql14`'{}'::jsonb`),
3245
3290
  active: boolean11("active").default(true),
3246
3291
  hook: text16("hook").notNull(),
3247
3292
  webhookUrl: text16("webhook_url"),
@@ -3249,13 +3294,13 @@ var init_misc = __esm({
3249
3294
  updatedAt: timestamp16("updated_at", { withTimezone: true }).defaultNow()
3250
3295
  },
3251
3296
  (table) => [
3252
- check11(
3297
+ check12(
3253
3298
  "agent_plugins_plugin_type_check",
3254
- sql13`${table.pluginType} IN ('webhook','transform','schedule')`
3299
+ sql14`${table.pluginType} IN ('webhook','transform','schedule')`
3255
3300
  ),
3256
- check11(
3301
+ check12(
3257
3302
  "agent_plugins_hook_check",
3258
- 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')`
3259
3304
  ),
3260
3305
  // R5 (audit #2 follow-up): the pglite test DDL (packages/db/src/tests/
3261
3306
  // helpers.ts) already assumed a per-entity unique slug here — same class
@@ -3385,8 +3430,8 @@ var init_agent_workspaces = __esm({
3385
3430
  });
3386
3431
 
3387
3432
  // ../../packages/db/src/schema/chat-messages.ts
3388
- import { pgTable as pgTable20, text as text20, uuid as uuid20, timestamp as timestamp20, index as index18, check as check12 } from "drizzle-orm/pg-core";
3389
- 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";
3390
3435
  var conversations, chatMessages;
3391
3436
  var init_chat_messages = __esm({
3392
3437
  "../../packages/db/src/schema/chat-messages.ts"() {
@@ -3412,7 +3457,7 @@ var init_chat_messages = __esm({
3412
3457
  },
3413
3458
  (table) => [
3414
3459
  index18("idx_conversations_entity_agent").on(table.entityId, table.agentId, table.updatedAt),
3415
- check12("conversations_origin_check", sql14`${table.origin} IN ('user','onboarding')`)
3460
+ check13("conversations_origin_check", sql15`${table.origin} IN ('user','onboarding')`)
3416
3461
  ]
3417
3462
  );
3418
3463
  chatMessages = pgTable20(
@@ -3433,7 +3478,7 @@ var init_chat_messages = __esm({
3433
3478
  },
3434
3479
  (table) => [
3435
3480
  index18("idx_chat_messages_conversation").on(table.conversationId, table.createdAt),
3436
- check12("chat_messages_role_check", sql14`${table.role} IN ('user','assistant')`)
3481
+ check13("chat_messages_role_check", sql15`${table.role} IN ('user','assistant')`)
3437
3482
  ]
3438
3483
  );
3439
3484
  }
@@ -3471,11 +3516,11 @@ import {
3471
3516
  uuid as uuid22,
3472
3517
  timestamp as timestamp22,
3473
3518
  index as index19,
3474
- check as check13,
3519
+ check as check14,
3475
3520
  unique as unique6,
3476
3521
  uniqueIndex as uniqueIndex4
3477
3522
  } from "drizzle-orm/pg-core";
3478
- import { sql as sql15 } from "drizzle-orm";
3523
+ import { sql as sql16 } from "drizzle-orm";
3479
3524
  var telegramAllowedChats;
3480
3525
  var init_telegram_allowed_chats = __esm({
3481
3526
  "../../packages/db/src/schema/telegram-allowed-chats.ts"() {
@@ -3504,8 +3549,8 @@ var init_telegram_allowed_chats = __esm({
3504
3549
  unique6("telegram_allowed_chats_agent_chat_unique").on(table.agentId, table.chatId),
3505
3550
  index19("idx_telegram_allowed_chats_agent").on(table.agentId),
3506
3551
  index19("idx_telegram_allowed_chats_entity").on(table.entityId),
3507
- check13("telegram_allowed_chats_role_check", sql15`${table.role} IN ('owner','member')`),
3508
- 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')`),
3509
3554
  // F1 (audit #2 remediation follow-up, migration 0060): concurrent
3510
3555
  // first-contact claims could each pass the "no owner yet" check before
3511
3556
  // either row committed, creating two role='owner' rows for one agent —
@@ -3513,7 +3558,7 @@ var init_telegram_allowed_chats = __esm({
3513
3558
  // resolveApprovalDeliveryTarget) does LIMIT 1 assuming uniqueness. A
3514
3559
  // partial unique index (owner rows only; 'member' rows are unrestricted)
3515
3560
  // closes the race at the DB level.
3516
- 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'`)
3517
3562
  ]
3518
3563
  );
3519
3564
  }
@@ -3528,10 +3573,10 @@ import {
3528
3573
  jsonb as jsonb10,
3529
3574
  timestamp as timestamp23,
3530
3575
  index as index20,
3531
- check as check14,
3576
+ check as check15,
3532
3577
  unique as unique7
3533
3578
  } from "drizzle-orm/pg-core";
3534
- import { sql as sql16 } from "drizzle-orm";
3579
+ import { sql as sql17 } from "drizzle-orm";
3535
3580
  var channelBindings;
3536
3581
  var init_channel_bindings = __esm({
3537
3582
  "../../packages/db/src/schema/channel-bindings.ts"() {
@@ -3560,9 +3605,9 @@ var init_channel_bindings = __esm({
3560
3605
  unique7("channel_bindings_agent_channel_unique").on(table.agentId, table.channel),
3561
3606
  index20("idx_channel_bindings_agent").on(table.agentId),
3562
3607
  index20("idx_channel_bindings_entity").on(table.entityId),
3563
- check14(
3608
+ check15(
3564
3609
  "channel_bindings_channel_check",
3565
- sql16`${table.channel} IN ('telegram','discord','slack','whatsapp')`
3610
+ sql17`${table.channel} IN ('telegram','discord','slack','whatsapp')`
3566
3611
  )
3567
3612
  ]
3568
3613
  );
@@ -3576,11 +3621,11 @@ import {
3576
3621
  uuid as uuid24,
3577
3622
  timestamp as timestamp24,
3578
3623
  index as index21,
3579
- check as check15,
3624
+ check as check16,
3580
3625
  unique as unique8,
3581
3626
  uniqueIndex as uniqueIndex5
3582
3627
  } from "drizzle-orm/pg-core";
3583
- import { sql as sql17 } from "drizzle-orm";
3628
+ import { sql as sql18 } from "drizzle-orm";
3584
3629
  var channelAllowedConversations;
3585
3630
  var init_channel_allowed_conversations = __esm({
3586
3631
  "../../packages/db/src/schema/channel-allowed-conversations.ts"() {
@@ -3616,24 +3661,24 @@ var init_channel_allowed_conversations = __esm({
3616
3661
  ),
3617
3662
  index21("idx_channel_allowed_conversations_agent").on(table.agentId),
3618
3663
  index21("idx_channel_allowed_conversations_entity").on(table.entityId),
3619
- check15(
3664
+ check16(
3620
3665
  "channel_allowed_conversations_channel_check",
3621
- sql17`${table.channel} IN ('telegram','discord','slack','whatsapp')`
3666
+ sql18`${table.channel} IN ('telegram','discord','slack','whatsapp')`
3622
3667
  ),
3623
- check15(
3668
+ check16(
3624
3669
  "channel_allowed_conversations_kind_check",
3625
- sql17`${table.kind} IN ('private','group','channel','thread')`
3670
+ sql18`${table.kind} IN ('private','group','channel','thread')`
3626
3671
  ),
3627
- check15("channel_allowed_conversations_role_check", sql17`${table.role} IN ('owner','member')`),
3628
- check15(
3672
+ check16("channel_allowed_conversations_role_check", sql18`${table.role} IN ('owner','member')`),
3673
+ check16(
3629
3674
  "channel_allowed_conversations_status_check",
3630
- sql17`${table.status} IN ('active','pending')`
3675
+ sql18`${table.status} IN ('active','pending')`
3631
3676
  ),
3632
3677
  // Mirrors telegram_allowed_chats_single_owner (migration 0060): at most one
3633
3678
  // active-owner-authority row per (agent, channel) — same race this closes
3634
3679
  // (concurrent first-contact claims), scoped so each channel gets its OWN
3635
3680
  // owner rather than sharing one across an agent's whole multichannel surface.
3636
- 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'`)
3637
3682
  ]
3638
3683
  );
3639
3684
  }
@@ -19084,12 +19129,12 @@ var require_formdata_parser = __commonJS({
19084
19129
  return "failure";
19085
19130
  }
19086
19131
  if (bufferStartsWith(input, filenameBuffer, position)) {
19087
- let check16 = position.position + filenameBuffer.length;
19088
- if (input[check16] === 42) {
19132
+ let check17 = position.position + filenameBuffer.length;
19133
+ if (input[check17] === 42) {
19089
19134
  position.position += 1;
19090
- check16 += 1;
19135
+ check17 += 1;
19091
19136
  }
19092
- if (input[check16] !== 61 || input[check16 + 1] !== 34) {
19137
+ if (input[check17] !== 61 || input[check17 + 1] !== 34) {
19093
19138
  return "failure";
19094
19139
  }
19095
19140
  position.position += 12;
@@ -46574,7 +46619,7 @@ var require_Util = __commonJS({
46574
46619
  await client.rest.patch(route, { body: updatedItems, reason });
46575
46620
  return updatedItems;
46576
46621
  }
46577
- function basename6(path2, ext) {
46622
+ function basename5(path2, ext) {
46578
46623
  const res = parse2(path2);
46579
46624
  return ext && res.ext.startsWith(ext) ? res.name : res.base.split("?")[0];
46580
46625
  }
@@ -46697,7 +46742,7 @@ var require_Util = __commonJS({
46697
46742
  resolveColor,
46698
46743
  discordSort,
46699
46744
  setPosition,
46700
- basename: basename6,
46745
+ basename: basename5,
46701
46746
  cleanContent,
46702
46747
  cleanCodeBlockContent,
46703
46748
  parseWebhookURL,
@@ -65389,7 +65434,7 @@ var require_MessagePayload = __commonJS({
65389
65434
  var { DiscordjsError: DiscordjsError2, DiscordjsRangeError: DiscordjsRangeError2, ErrorCodes: ErrorCodes2 } = require_errors2();
65390
65435
  var { resolveFile } = require_DataResolver();
65391
65436
  var MessageFlagsBitField = require_MessageFlagsBitField();
65392
- var { basename: basename6, verifyString, resolvePartialEmoji } = require_Util();
65437
+ var { basename: basename5, verifyString, resolvePartialEmoji } = require_Util();
65393
65438
  var getBaseInteraction = lazy(() => require_BaseInteraction());
65394
65439
  var MessagePayload = class {
65395
65440
  /**
@@ -65608,10 +65653,10 @@ var require_MessagePayload = __commonJS({
65608
65653
  let name;
65609
65654
  const findName = (thing) => {
65610
65655
  if (typeof thing === "string") {
65611
- return basename6(thing);
65656
+ return basename5(thing);
65612
65657
  }
65613
65658
  if (thing.path) {
65614
- return basename6(thing.path);
65659
+ return basename5(thing.path);
65615
65660
  }
65616
65661
  return "file.jpg";
65617
65662
  };
@@ -88063,7 +88108,7 @@ var require_EmbedBuilder = __commonJS({
88063
88108
  var require_AttachmentBuilder = __commonJS({
88064
88109
  "../../node_modules/.pnpm/discord.js@14.26.5/node_modules/discord.js/src/structures/AttachmentBuilder.js"(exports2, module2) {
88065
88110
  "use strict";
88066
- var { basename: basename6, flatten } = require_Util();
88111
+ var { basename: basename5, flatten } = require_Util();
88067
88112
  var AttachmentBuilder = class _AttachmentBuilder {
88068
88113
  /**
88069
88114
  * @param {BufferResolvable|Stream} attachment The file
@@ -88123,7 +88168,7 @@ var require_AttachmentBuilder = __commonJS({
88123
88168
  * @readonly
88124
88169
  */
88125
88170
  get spoiler() {
88126
- return basename6(this.name).startsWith("SPOILER_");
88171
+ return basename5(this.name).startsWith("SPOILER_");
88127
88172
  }
88128
88173
  toJSON() {
88129
88174
  return flatten(this);
@@ -156730,7 +156775,7 @@ var require_wait2 = __commonJS({
156730
156775
  return;
156731
156776
  }
156732
156777
  let prior = current;
156733
- const check16 = (backoff) => {
156778
+ const check17 = (backoff) => {
156734
156779
  if (Date.now() > max2) {
156735
156780
  done(null, "timed-out");
156736
156781
  } else {
@@ -156738,7 +156783,7 @@ var require_wait2 = __commonJS({
156738
156783
  prior = current;
156739
156784
  current = Atomics.load(state, index22);
156740
156785
  if (current === prior) {
156741
- check16(backoff >= MAX_TIMEOUT ? MAX_TIMEOUT : backoff * 2);
156786
+ check17(backoff >= MAX_TIMEOUT ? MAX_TIMEOUT : backoff * 2);
156742
156787
  } else {
156743
156788
  if (current === expected) done(null, "ok");
156744
156789
  else done(null, "not-equal");
@@ -156746,7 +156791,7 @@ var require_wait2 = __commonJS({
156746
156791
  }, backoff);
156747
156792
  }
156748
156793
  };
156749
- check16(1);
156794
+ check17(1);
156750
156795
  }
156751
156796
  function waitDiff(state, index22, expected, timeout, done) {
156752
156797
  const max2 = Date.now() + timeout;
@@ -156755,7 +156800,7 @@ var require_wait2 = __commonJS({
156755
156800
  done(null, "ok");
156756
156801
  return;
156757
156802
  }
156758
- const check16 = (backoff) => {
156803
+ const check17 = (backoff) => {
156759
156804
  if (Date.now() > max2) {
156760
156805
  done(null, "timed-out");
156761
156806
  } else {
@@ -156764,12 +156809,12 @@ var require_wait2 = __commonJS({
156764
156809
  if (current !== expected) {
156765
156810
  done(null, "ok");
156766
156811
  } else {
156767
- check16(backoff >= MAX_TIMEOUT ? MAX_TIMEOUT : backoff * 2);
156812
+ check17(backoff >= MAX_TIMEOUT ? MAX_TIMEOUT : backoff * 2);
156768
156813
  }
156769
156814
  }, backoff);
156770
156815
  }
156771
156816
  };
156772
- check16(1);
156817
+ check17(1);
156773
156818
  }
156774
156819
  module2.exports = { wait, waitDiff };
156775
156820
  }
@@ -199127,7 +199172,7 @@ var require_view2 = __commonJS({
199127
199172
  var path2 = __require("node:path");
199128
199173
  var fs4 = __require("node:fs");
199129
199174
  var dirname8 = path2.dirname;
199130
- var basename6 = path2.basename;
199175
+ var basename5 = path2.basename;
199131
199176
  var extname2 = path2.extname;
199132
199177
  var join16 = path2.join;
199133
199178
  var resolve3 = path2.resolve;
@@ -199166,7 +199211,7 @@ var require_view2 = __commonJS({
199166
199211
  var root = roots[i];
199167
199212
  var loc = resolve3(root, name);
199168
199213
  var dir = dirname8(loc);
199169
- var file = basename6(loc);
199214
+ var file = basename5(loc);
199170
199215
  path3 = this.resolve(dir, file);
199171
199216
  }
199172
199217
  return path3;
@@ -199196,7 +199241,7 @@ var require_view2 = __commonJS({
199196
199241
  if (stat11 && stat11.isFile()) {
199197
199242
  return path3;
199198
199243
  }
199199
- path3 = join16(dir, basename6(file, ext), "index" + ext);
199244
+ path3 = join16(dir, basename5(file, ext), "index" + ext);
199200
199245
  stat11 = tryStat(path3);
199201
199246
  if (stat11 && stat11.isFile()) {
199202
199247
  return path3;
@@ -202158,9 +202203,9 @@ var require_content_disposition = __commonJS({
202158
202203
  if (typeof fallback === "string" && NON_LATIN1_REGEXP.test(fallback)) {
202159
202204
  throw new TypeError("fallback must be ISO-8859-1 string");
202160
202205
  }
202161
- var name = basename6(filename);
202206
+ var name = basename5(filename);
202162
202207
  var isQuotedString = TEXT_REGEXP.test(name);
202163
- var fallbackName = typeof fallback !== "string" ? fallback && getlatin1(name) : basename6(fallback);
202208
+ var fallbackName = typeof fallback !== "string" ? fallback && getlatin1(name) : basename5(fallback);
202164
202209
  var hasFallback = typeof fallbackName === "string" && fallbackName !== name;
202165
202210
  if (hasFallback || !isQuotedString || hasHexEscape(name)) {
202166
202211
  params["filename*"] = name;
@@ -202280,7 +202325,7 @@ var require_content_disposition = __commonJS({
202280
202325
  this.type = type;
202281
202326
  this.parameters = parameters;
202282
202327
  }
202283
- function basename6(path2) {
202328
+ function basename5(path2) {
202284
202329
  const normalized = path2.replaceAll("\\", "/");
202285
202330
  let end = normalized.length;
202286
202331
  while (end > 0 && normalized[end - 1] === "/") {
@@ -204687,8 +204732,6 @@ var envSchema = z.object({
204687
204732
  EMBEDDING_PROVIDER: z.enum(["ollama", "openai", "keyword"]).default("keyword"),
204688
204733
  EMBEDDING_MODEL: z.string().optional(),
204689
204734
  EMBEDDING_BASE_URL: z.string().optional(),
204690
- // Delivery
204691
- TELEGRAM_BOT_TOKEN: z.string().optional(),
204692
204735
  // Auth
204693
204736
  AUTH_MODE: z.enum(["local-trust", "local-auth", "bearer-token"]).default("local-trust"),
204694
204737
  WORKER_SECRET: z.string().default(""),
@@ -204715,8 +204758,6 @@ var envSchema = z.object({
204715
204758
  // Enable reflection per entity via the dashboard toggle — NOT via this env var.
204716
204759
  // The other knobs gate WHICH jobs qualify and bound the pass.
204717
204760
  REFLECTION_ENABLED: z.string().default(""),
204718
- // Minimum job turns before a completed job is "substantial" enough to reflect on.
204719
- REFLECTION_MIN_TURNS: z.coerce.number().default(3),
204720
204761
  // Per-entity rolling-hour cap on reflection passes (rate limit / cost guard).
204721
204762
  REFLECTION_MAX_PER_HOUR: z.coerce.number().default(6),
204722
204763
  // Max LLM turns inside a single reflection pass (the reflection loop itself).
@@ -204859,7 +204900,7 @@ init_schema();
204859
204900
 
204860
204901
  // ../../packages/db/src/queries/credentials.ts
204861
204902
  init_credentials();
204862
- import { eq, sql as sql18 } from "drizzle-orm";
204903
+ import { eq, sql as sql19 } from "drizzle-orm";
204863
204904
 
204864
204905
  // ../../packages/secrets/src/index.ts
204865
204906
  import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
@@ -205051,7 +205092,7 @@ async function getDecryptedCredentialById(db, credentialId) {
205051
205092
  if (provider?.supportsRefresh && payload.refreshToken) {
205052
205093
  try {
205053
205094
  payload = await db.transaction(async (tx) => {
205054
- await tx.execute(sql18`SELECT pg_advisory_xact_lock(hashtext(${credentialId}))`);
205095
+ await tx.execute(sql19`SELECT pg_advisory_xact_lock(hashtext(${credentialId}))`);
205055
205096
  const [freshRow] = await tx.select().from(credentials).where(eq(credentials.id, credentialId));
205056
205097
  if (!freshRow) return payload;
205057
205098
  const freshPayload = decryptPayload(freshRow.payload);
@@ -205319,7 +205360,7 @@ async function attachAgentToOrchestrator(db, entityId, orchestratorId, agentSlug
205319
205360
  // ../../packages/db/src/repos/skills.ts
205320
205361
  init_agents();
205321
205362
  init_skills();
205322
- 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";
205323
205364
  async function createSkillRepo(db, entityId, input, reservedSlugs = []) {
205324
205365
  if (reservedSlugs.includes(input.slug)) {
205325
205366
  return { error: "slug_reserved" };
@@ -205362,7 +205403,7 @@ async function updateSkillRepo(db, entityId, skillId, patch) {
205362
205403
  }
205363
205404
  if (patch.createdBy === "agent") {
205364
205405
  set.createdBy = "agent";
205365
- set.patchCount = sql19`${agentSkills.patchCount} + 1`;
205406
+ set.patchCount = sql20`${agentSkills.patchCount} + 1`;
205366
205407
  }
205367
205408
  await db.update(agentSkills).set(set).where(eq4(agentSkills.id, skillId));
205368
205409
  return { ok: true };
@@ -205398,7 +205439,7 @@ async function assignSkillRepo(db, entityId, input, systemSkillSlugs2) {
205398
205439
  }
205399
205440
  async function touchSkillsLastUsed(db, skillIds) {
205400
205441
  if (skillIds.length === 0) return;
205401
- 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));
205402
205443
  }
205403
205444
  async function transitionSkillLifecycle(db, thresholds) {
205404
205445
  const now = /* @__PURE__ */ new Date();
@@ -205412,21 +205453,21 @@ async function transitionSkillLifecycle(db, thresholds) {
205412
205453
  and3(
205413
205454
  eq4(agentSkills.createdBy, "agent"),
205414
205455
  eq4(agentSkills.state, "active"),
205415
- sql19`coalesce(${agentSkills.lastUsedAt}, ${agentSkills.createdAt}) < ${staleAt}`
205456
+ sql20`coalesce(${agentSkills.lastUsedAt}, ${agentSkills.createdAt}) < ${staleAt}`
205416
205457
  )
205417
205458
  ).returning({ id: agentSkills.id });
205418
205459
  const archivedRows = await db.update(agentSkills).set({ state: "archived", archivedAt: now, updatedAt: now }).where(
205419
205460
  and3(
205420
205461
  eq4(agentSkills.createdBy, "agent"),
205421
205462
  eq4(agentSkills.state, "stale"),
205422
- sql19`coalesce(${agentSkills.lastUsedAt}, ${agentSkills.createdAt}) < ${archiveAt}`
205463
+ sql20`coalesce(${agentSkills.lastUsedAt}, ${agentSkills.createdAt}) < ${archiveAt}`
205423
205464
  )
205424
205465
  ).returning({ id: agentSkills.id });
205425
205466
  const reactivatedRows = await db.update(agentSkills).set({ state: "active", updatedAt: now }).where(
205426
205467
  and3(
205427
205468
  eq4(agentSkills.createdBy, "agent"),
205428
205469
  eq4(agentSkills.state, "stale"),
205429
- sql19`${agentSkills.lastUsedAt} >= ${staleAt}`
205470
+ sql20`${agentSkills.lastUsedAt} >= ${staleAt}`
205430
205471
  )
205431
205472
  ).returning({ id: agentSkills.id });
205432
205473
  return {
@@ -205445,25 +205486,25 @@ async function archiveAgentSkill(db, entityId, skillId) {
205445
205486
 
205446
205487
  // ../../packages/db/src/repos/retention.ts
205447
205488
  init_schema();
205448
- import { sql as sql20, inArray as inArray3 } from "drizzle-orm";
205489
+ import { sql as sql21, inArray as inArray3 } from "drizzle-orm";
205449
205490
  async function pruneOldJobs(db, retentionDays, batchSize = 1e3) {
205450
205491
  if (retentionDays <= 0) {
205451
205492
  return { jobsDeleted: 0, toolCallsDeleted: 0 };
205452
205493
  }
205453
- const cutoffExpr = sql20`now() - make_interval(days => ${retentionDays})`;
205494
+ const cutoffExpr = sql21`now() - make_interval(days => ${retentionDays})`;
205454
205495
  let jobsDeleted = 0;
205455
205496
  let toolCallsDeleted = 0;
205456
205497
  for (; ; ) {
205457
205498
  const batch = await db.transaction(async (tx) => {
205458
205499
  const prunableRows = await tx.select({ id: agentJobs.id }).from(agentJobs).where(
205459
- sql20`${agentJobs.status} IN ('completed','failed','cancelled')
205500
+ sql21`${agentJobs.status} IN ('completed','failed','cancelled')
205460
205501
  AND ${agentJobs.completedAt} < ${cutoffExpr}`
205461
205502
  ).limit(batchSize);
205462
205503
  if (prunableRows.length === 0) {
205463
205504
  return null;
205464
205505
  }
205465
205506
  const jobIds = prunableRows.map((r) => r.id);
205466
- 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));
205467
205508
  const batchToolCallsDeleted = tcCountRow?.n ?? 0;
205468
205509
  await tx.delete(agentJobs).where(inArray3(agentJobs.id, jobIds));
205469
205510
  return { jobsDeleted: jobIds.length, toolCallsDeleted: batchToolCallsDeleted };
@@ -205478,7 +205519,7 @@ async function pruneOldJobs(db, retentionDays, batchSize = 1e3) {
205478
205519
 
205479
205520
  // ../../packages/db/src/repos/app-settings.ts
205480
205521
  init_schema();
205481
- 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";
205482
205523
  var INSTALL_NOTES_KEY = "install_notes";
205483
205524
  async function getEntitySetting(db, entityId, key) {
205484
205525
  const [row] = await db.select({ value: entitySettings.value }).from(entitySettings).where(and4(eq5(entitySettings.entityId, entityId), eq5(entitySettings.key, key))).limit(1);
@@ -205493,7 +205534,7 @@ import {
205493
205534
  eq as eq6,
205494
205535
  and as and5,
205495
205536
  or as or4,
205496
- sql as sql22,
205537
+ sql as sql23,
205497
205538
  desc,
205498
205539
  asc,
205499
205540
  not,
@@ -207610,8 +207651,8 @@ function rowToMemory(row) {
207610
207651
  // ../../packages/memory/src/access-tracking.ts
207611
207652
  async function touchMemory(db, id, entityId) {
207612
207653
  await db.update(agentMemory).set({
207613
- lastAccessedAt: sql22`now()`,
207614
- accessCount: sql22`${agentMemory.accessCount} + 1`
207654
+ lastAccessedAt: sql23`now()`,
207655
+ accessCount: sql23`${agentMemory.accessCount} + 1`
207615
207656
  }).where(and5(eq6(agentMemory.id, id), eq6(agentMemory.entityId, entityId)));
207616
207657
  }
207617
207658
  async function touchMemories(db, ids, entityId) {
@@ -207629,9 +207670,9 @@ async function keywordSearch(db, query, filters, sort = "importance") {
207629
207670
  const conditions = buildWhereConditions({ entityId, agentId, skillTags, category });
207630
207671
  const orderBy = keywordOrderBy(sort);
207631
207672
  if (orTerms.length > 0) {
207632
- const tsv = sql22`"agent_memory"."search_tsv"`;
207633
- const tsq = sql22`to_tsquery('english', ${orTerms.join(" | ")})`;
207634
- 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);
207635
207676
  return rows2;
207636
207677
  }
207637
207678
  const rows = await db.select().from(agentMemory).where(and5(...conditions)).orderBy(...orderBy).limit(limit);
@@ -207676,10 +207717,10 @@ function buildWhereConditions(filters) {
207676
207717
  if (skillTags && skillTags.length > 0) {
207677
207718
  const tagLiteral = pgTextArray(skillTags);
207678
207719
  conditions.push(
207679
- sql22`(
207720
+ sql23`(
207680
207721
  ${agentMemory.skillTags} = '{}'::text[]
207681
207722
  OR ${agentMemory.skillTags} IS NULL
207682
- OR ${agentMemory.skillTags} && ${sql22.raw(tagLiteral)}::text[]
207723
+ OR ${agentMemory.skillTags} && ${sql23.raw(tagLiteral)}::text[]
207683
207724
  )`
207684
207725
  );
207685
207726
  }
@@ -207720,10 +207761,10 @@ async function transitionMemoryLifecycle(db, opts) {
207720
207761
  const rows = await db.update(agentMemory).set({ archived: true, updatedAt: /* @__PURE__ */ new Date() }).where(
207721
207762
  and5(
207722
207763
  eq6(agentMemory.archived, false),
207723
- sql22`${agentMemory.source} IN ('agent','reflection')`,
207724
- sql22`${agentMemory.importance} <= ${opts.importanceMax}`,
207725
- sql22`coalesce(${agentMemory.accessCount}, 0) = 0`,
207726
- 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`,
207727
207768
  lt(agentMemory.createdAt, cutoff)
207728
207769
  )
207729
207770
  ).returning({ id: agentMemory.id });
@@ -207928,16 +207969,16 @@ async function selectMemoriesForInjection(db, opts) {
207928
207969
  const baseConditions = and5(
207929
207970
  eq6(agentMemory.entityId, opts.entityId),
207930
207971
  eq6(agentMemory.archived, false),
207931
- sql22`(${agentMemory.validTo} IS NULL OR ${agentMemory.validTo} > now())`
207972
+ sql23`(${agentMemory.validTo} IS NULL OR ${agentMemory.validTo} > now())`
207932
207973
  );
207933
207974
  const rawQuery = opts.query ?? "";
207934
207975
  const tokens = tokenizeQuery(rawQuery);
207935
207976
  let packed;
207936
207977
  if (tokens.length > 0) {
207937
- const tsv = sql22`"agent_memory"."search_tsv"`;
207938
- 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(" | ")})`;
207939
207980
  const candidates = await db.select().from(agentMemory).where(baseConditions).orderBy(
207940
- sql22`ts_rank(${tsv}, ${tsq}) DESC`,
207981
+ sql23`ts_rank(${tsv}, ${tsq}) DESC`,
207941
207982
  desc(agentMemory.importance),
207942
207983
  desc(agentMemory.lastAccessedAt)
207943
207984
  ).limit(MAX_CANDIDATES);
@@ -207968,7 +208009,7 @@ async function findMemoriesByFactSubstring(db, entityId, substring, limit = 5) {
207968
208009
  and5(
207969
208010
  eq6(agentMemory.entityId, entityId),
207970
208011
  eq6(agentMemory.archived, false),
207971
- sql22`(${agentMemory.validTo} IS NULL OR ${agentMemory.validTo} > now())`,
208012
+ sql23`(${agentMemory.validTo} IS NULL OR ${agentMemory.validTo} > now())`,
207972
208013
  ilike2(agentMemory.fact, `%${substring}%`)
207973
208014
  )
207974
208015
  ).limit(limit);
@@ -208095,16 +208136,16 @@ var searchHistoryTool = {
208095
208136
  riskLevel: "read",
208096
208137
  execute: async (input, ctx) => {
208097
208138
  const limit = input.limit ?? 6;
208098
- const tsq = sql22`plainto_tsquery('simple', ${input.query})`;
208099
- const tsv = sql22`"agent_jobs"."search_tsv"`;
208139
+ const tsq = sql23`plainto_tsquery('simple', ${input.query})`;
208140
+ const tsv = sql23`"agent_jobs"."search_tsv"`;
208100
208141
  const rows = await ctx.db.select({
208101
208142
  job_id: agentJobs.id,
208102
208143
  agent: agents.slug,
208103
208144
  channel: agentJobs.channel,
208104
208145
  when: agentJobs.createdAt,
208105
208146
  task: agentJobs.task,
208106
- snippet: sql22`ts_headline('simple', coalesce(${agentJobs.searchText}, ${agentJobs.task}, ''), ${tsq}, 'StartSel=«, StopSel=», MaxFragments=2, MinWords=4, MaxWords=18, FragmentDelimiter= … ')`
208107
- }).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);
208108
208149
  return rows.map((r) => ({
208109
208150
  job_id: r.job_id,
208110
208151
  agent: r.agent ?? null,
@@ -208321,7 +208362,7 @@ import { z as z29 } from "zod";
208321
208362
 
208322
208363
  // ../../packages/tools/src/builtin/file-ops/workspace.ts
208323
208364
  import { realpath, stat } from "node:fs/promises";
208324
- import { resolve as resolvePath, sep, isAbsolute, basename } from "node:path";
208365
+ import { resolve as resolvePath, sep, isAbsolute } from "node:path";
208325
208366
  var SHARED_WORKSPACE_LABEL = "shared";
208326
208367
  var MAX_READ_BYTES = 1024 * 1024;
208327
208368
  var MAX_READ_FILE_BYTES = 50 * 1024 * 1024;
@@ -208633,7 +208674,7 @@ var fileReadTool = {
208633
208674
 
208634
208675
  // ../../packages/tools/src/builtin/file-ops/file-write.ts
208635
208676
  import { writeFile, rename, mkdir, unlink } from "node:fs/promises";
208636
- import { dirname as dirname2, basename as basename2 } from "node:path";
208677
+ import { dirname as dirname2, basename } from "node:path";
208637
208678
  import { randomBytes as randomBytes2 } from "node:crypto";
208638
208679
  import { z as z30 } from "zod";
208639
208680
  var FileWriteInputSchema = z30.object({
@@ -208667,7 +208708,7 @@ var fileWriteTool = {
208667
208708
  if (input.create_dirs) {
208668
208709
  await mkdir(dir, { recursive: true });
208669
208710
  }
208670
- const tmp = `${dir}/.${basename2(path2)}.${randomBytes2(6).toString("hex")}.tmp`;
208711
+ const tmp = `${dir}/.${basename(path2)}.${randomBytes2(6).toString("hex")}.tmp`;
208671
208712
  try {
208672
208713
  await writeFile(tmp, input.content, "utf8");
208673
208714
  await rename(tmp, path2);
@@ -208692,7 +208733,7 @@ var fileWriteTool = {
208692
208733
 
208693
208734
  // ../../packages/tools/src/builtin/file-ops/file-edit.ts
208694
208735
  import { readFile as readFile2, writeFile as writeFile2, rename as rename2, unlink as unlink2 } from "node:fs/promises";
208695
- import { dirname as dirname3, basename as basename3 } from "node:path";
208736
+ import { dirname as dirname3, basename as basename2 } from "node:path";
208696
208737
  import { randomBytes as randomBytes3 } from "node:crypto";
208697
208738
  import { z as z31 } from "zod";
208698
208739
  var FileEditInputSchema = z31.object({
@@ -208750,7 +208791,7 @@ var fileEditTool = {
208750
208791
  };
208751
208792
  }
208752
208793
  const dir = dirname3(path2);
208753
- const tmp = `${dir}/.${basename3(path2)}.${randomBytes3(6).toString("hex")}.tmp`;
208794
+ const tmp = `${dir}/.${basename2(path2)}.${randomBytes3(6).toString("hex")}.tmp`;
208754
208795
  try {
208755
208796
  await writeFile2(tmp, updated, "utf8");
208756
208797
  await rename2(tmp, path2);
@@ -208991,7 +209032,7 @@ import { z as z34 } from "zod";
208991
209032
  // ../../packages/tools/src/builtin/office-ops/office-helpers.ts
208992
209033
  var import_jszip = __toESM(require_lib3(), 1);
208993
209034
  import { readFile as readFile4, writeFile as writeFile3, rename as rename3, unlink as unlink3, stat as stat5 } from "node:fs/promises";
208994
- import { dirname as dirname4, basename as basename4 } from "node:path";
209035
+ import { dirname as dirname4, basename as basename3 } from "node:path";
208995
209036
  import { randomBytes as randomBytes4 } from "node:crypto";
208996
209037
  var MAX_OFFICE_BYTES = 25 * 1024 * 1024;
208997
209038
  var MAX_OFFICE_INFLATED_BYTES = 1024 * 1024 * 1024;
@@ -209105,7 +209146,7 @@ async function writeWorkspaceBinary(ctx, path2, buffer2, { overwrite = false } =
209105
209146
  };
209106
209147
  }
209107
209148
  const dir = dirname4(resolvedPath);
209108
- const tmp = `${dir}/.${basename4(resolvedPath)}.${randomBytes4(6).toString("hex")}.tmp`;
209149
+ const tmp = `${dir}/.${basename3(resolvedPath)}.${randomBytes4(6).toString("hex")}.tmp`;
209109
209150
  try {
209110
209151
  await writeFile3(tmp, buffer2);
209111
209152
  await rename3(tmp, resolvedPath);
@@ -211739,8 +211780,8 @@ var createAgentTool = {
211739
211780
  const { dbRole, orchestratorMode } = mapRole(input.role);
211740
211781
  const resolved = await resolveAgentProvider(ctx.db, ctx.entityId, null);
211741
211782
  if (resolved) {
211742
- const check16 = validateModelForProvider(resolved.provider, input.model);
211743
- if (!check16.ok) return check16;
211783
+ const check17 = validateModelForProvider(resolved.provider, input.model);
211784
+ if (!check17.ok) return check17;
211744
211785
  }
211745
211786
  let result;
211746
211787
  try {
@@ -211812,8 +211853,8 @@ var updateAgentTool = {
211812
211853
  if (input.model) {
211813
211854
  const resolved = await resolveAgentProvider(ctx.db, ctx.entityId, agent.llmKeyId);
211814
211855
  if (resolved) {
211815
- const check16 = validateModelForProvider(resolved.provider, input.model);
211816
- if (!check16.ok) return check16;
211856
+ const check17 = validateModelForProvider(resolved.provider, input.model);
211857
+ if (!check17.ok) return check17;
211817
211858
  }
211818
211859
  }
211819
211860
  const patch = { updatedAt: /* @__PURE__ */ new Date() };
@@ -212556,13 +212597,15 @@ var runScheduleTool = {
212556
212597
  task: agentSchedules.task,
212557
212598
  chatId: agentSchedules.chatId,
212558
212599
  notifyOnSuccess: agentSchedules.notifyOnSuccess,
212600
+ // B1 (notify-channel-choice): the schedule's explicit channel choice, if any.
212601
+ notifyChannel: agentSchedules.notifyChannel,
212559
212602
  // Manual "run now": prevRunAt is still "when did this schedule last
212560
212603
  // actually run" — this fire doesn't change that semantic.
212561
212604
  lastRun: agentSchedules.lastRun
212562
212605
  }).from(agentSchedules).where(and5(eq6(agentSchedules.entityId, ctx.entityId), eq6(agentSchedules.name, input.name))).limit(1);
212563
212606
  if (!sched) return { ok: false, error: `No schedule named "${input.name}" in this workspace.` };
212564
212607
  if (!sched.task) return { ok: false, error: `Schedule "${input.name}" has no task to run.` };
212565
- 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;
212566
212609
  const [job] = await ctx.db.insert(agentJobs).values({
212567
212610
  entityId: ctx.entityId,
212568
212611
  agentId: sched.agentId,
@@ -212575,7 +212618,8 @@ var runScheduleTool = {
212575
212618
  triggerContext: {
212576
212619
  type: "cron",
212577
212620
  scheduleName: input.name,
212578
- prevRunAt: sched.lastRun ? sched.lastRun.toISOString() : null
212621
+ prevRunAt: sched.lastRun ? sched.lastRun.toISOString() : null,
212622
+ notifyChannel: sched.notifyChannel ?? null
212579
212623
  }
212580
212624
  }).returning({ id: agentJobs.id });
212581
212625
  if (!job) return { ok: false, error: "Failed to queue the schedule run." };
@@ -321049,7 +321093,7 @@ var generateMessageIDV2 = (userId) => {
321049
321093
  };
321050
321094
  var generateMessageID = () => "3EB0" + randomBytes5(18).toString("hex").toUpperCase();
321051
321095
  function bindWaitForEvent(ev, event) {
321052
- return async (check16, timeoutMs) => {
321096
+ return async (check17, timeoutMs) => {
321053
321097
  let listener;
321054
321098
  let closeListener;
321055
321099
  await promiseTimeout(timeoutMs, (resolve3, reject) => {
@@ -321060,7 +321104,7 @@ function bindWaitForEvent(ev, event) {
321060
321104
  };
321061
321105
  ev.on("connection.update", closeListener);
321062
321106
  listener = async (update) => {
321063
- if (await check16(update)) {
321107
+ if (await check17(update)) {
321064
321108
  resolve3();
321065
321109
  }
321066
321110
  };
@@ -335338,16 +335382,28 @@ function getAdapter3(channel2) {
335338
335382
  }
335339
335383
 
335340
335384
  // ../../packages/delivery/src/transport-channel.ts
335341
- var TRANSPORT_CHANNELS = /* @__PURE__ */ new Set([
335342
- "telegram",
335343
- "discord",
335344
- "slack",
335345
- "whatsapp"
335346
- ]);
335347
- function resolveTransportChannel(channel2) {
335385
+ var CHANNEL_PRIORITY = ["telegram", "discord", "slack", "whatsapp"];
335386
+ var TRANSPORT_CHANNELS = new Set(CHANNEL_PRIORITY);
335387
+ function resolveTransportChannel(channel2, activeChannels) {
335348
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
+ }
335349
335394
  return "telegram";
335350
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
+ }
335351
335407
 
335352
335408
  // ../../packages/tools/src/builtin/list-conversations.ts
335353
335409
  var MAX_ROWS = 150;
@@ -335508,8 +335564,11 @@ import { z as z60 } from "zod";
335508
335564
  import { realpath as realpath3 } from "node:fs/promises";
335509
335565
  import { tmpdir as tmpdir3 } from "node:os";
335510
335566
  import path from "node:path";
335567
+ function defaultChannelForJob(ctx) {
335568
+ return ctx.notifyChannelOverride ?? resolveTransportChannel(ctx.jobChannel, ctx.activeChannels);
335569
+ }
335511
335570
  async function resolveChannelForJob(ctx, explicitChannel) {
335512
- const jobChannel = resolveTransportChannel(ctx.jobChannel);
335571
+ const jobChannel = defaultChannelForJob(ctx);
335513
335572
  if (explicitChannel === void 0 || explicitChannel === jobChannel) {
335514
335573
  return jobChannel;
335515
335574
  }
@@ -335555,7 +335614,7 @@ async function resolveRecipientChatId(explicitChatId, ctx, noRecipientErrorName,
335555
335614
  throw err;
335556
335615
  }
335557
335616
  const channel2 = await resolveChannelForJob(ctx, explicitChannel);
335558
- const crossChannel = explicitChannel !== void 0 && explicitChannel !== resolveTransportChannel(ctx.jobChannel);
335617
+ const crossChannel = explicitChannel !== void 0 && explicitChannel !== defaultChannelForJob(ctx);
335559
335618
  let chatId = explicitChatId ?? (crossChannel ? null : ctx.jobChatId);
335560
335619
  if (explicitChatId === void 0 && chatId === null) {
335561
335620
  chatId = await resolveOwnerConversation(ctx.db, ctx.agentId, channel2);
@@ -336302,7 +336361,7 @@ async function healthRoute(c, deps) {
336302
336361
  // LLM is not pinged on every health check (expensive)
336303
336362
  };
336304
336363
  try {
336305
- await deps.db.execute(sql22`SELECT 1`);
336364
+ await deps.db.execute(sql23`SELECT 1`);
336306
336365
  result.db = "ok";
336307
336366
  } catch (err) {
336308
336367
  console.error("[health] DB ping failed:", err instanceof Error ? err.message : err);
@@ -351699,7 +351758,9 @@ async function walkJobChainToRoot(db, jobId) {
351699
351758
  async function resolveChannelApprovalDeliveryTarget(db, jobId) {
351700
351759
  const chain = await walkJobChainToRoot(db, jobId);
351701
351760
  if (!chain) return null;
351702
- 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);
351703
351764
  if (rootChannel === "telegram") {
351704
351765
  const target = await resolveApprovalDeliveryTarget(db, jobId);
351705
351766
  if (!target) return null;
@@ -351788,7 +351849,7 @@ import { join as join12 } from "node:path";
351788
351849
 
351789
351850
  // src/skills/install.ts
351790
351851
  import { cp, rm as rm2, mkdir as mkdir5, readFile as readFile11 } from "node:fs/promises";
351791
- 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";
351792
351853
 
351793
351854
  // src/skills/source.ts
351794
351855
  var SkillSourceError = class extends Error {
@@ -352568,7 +352629,7 @@ async function pickManifest(extractRoot, subdir, skillName) {
352568
352629
  }
352569
352630
  }
352570
352631
  if (byFrontmatter.length > 0) return byFrontmatter[0];
352571
- const byFolder = manifests.filter((rel) => basename5(dirname7(rel)) === skillName);
352632
+ const byFolder = manifests.filter((rel) => basename4(dirname7(rel)) === skillName);
352572
352633
  if (byFolder.length > 0) return byFolder[0];
352573
352634
  throw new SkillInstallError(
352574
352635
  `Repository has multiple skills but none named "${skillName}". Found: ${manifests.map((m) => dirname7(m)).join(", ")}.`
@@ -353471,13 +353532,15 @@ async function runJob(jobId, deps, runnerEnv, opts) {
353471
353532
  const canSeeImages = llmClient.capabilities.vision === true || modelCanSeeImages(llmClient.config.model);
353472
353533
  const imageCache = /* @__PURE__ */ new Map();
353473
353534
  const isOrchestrator = agent.role === "orchestrator";
353474
- 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;
353475
353538
  const deployment = await getDeploymentContext(db, job.entityId ?? void 0);
353476
353539
  const jobContext = {
353477
353540
  origin: job.channel ?? "unknown",
353478
353541
  ...job.task ? { task: job.task } : {},
353479
353542
  ...job.chatId ? { telegramChatId: job.chatId } : {},
353480
- ...cronWantsConfirmation ? { notifyOnSuccess: true } : {},
353543
+ ...triggerWantsConfirmation ? { notifyOnSuccess: true } : {},
353481
353544
  ...job.parentJobId ? { isDelegated: true } : {},
353482
353545
  ...job.triggerContext ? { triggerContext: job.triggerContext } : {},
353483
353546
  deployment
@@ -353513,6 +353576,11 @@ async function runJob(jobId, deps, runnerEnv, opts) {
353513
353576
  capabilityTools.push(createSendVoiceTool());
353514
353577
  capabilityTools.push(createListConversationsTool());
353515
353578
  }
353579
+ const activeChannels = [
353580
+ ...deliveryBotToken ? ["telegram"] : [],
353581
+ ...hasDiscordBinding ? ["discord"] : [],
353582
+ ...hasSlackBinding ? ["slack"] : []
353583
+ ];
353516
353584
  const mcpClosers = [];
353517
353585
  try {
353518
353586
  const [rootEntityRow] = await db.select({
@@ -353602,7 +353670,10 @@ async function runJob(jobId, deps, runnerEnv, opts) {
353602
353670
  decryptedEnv = Object.fromEntries(
353603
353671
  Object.entries(rawEnv).map(([k, v]) => [k, decrypt(v)])
353604
353672
  );
353605
- } 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
+ );
353606
353677
  continue;
353607
353678
  }
353608
353679
  const connectOpts = {
@@ -353716,8 +353787,8 @@ async function runJob(jobId, deps, runnerEnv, opts) {
353716
353787
  return { status: "failed", error: errorCode };
353717
353788
  }
353718
353789
  const ruleRows = await db.select().from(approvalRules).where(eq6(approvalRules.entityId, job.entityId ?? "")).orderBy(
353719
- sql22`${approvalRules.agentId} IS NULL`,
353720
- sql22`${approvalRules.toolName} = '*'`,
353790
+ sql23`${approvalRules.agentId} IS NULL`,
353791
+ sql23`${approvalRules.toolName} = '*'`,
353721
353792
  approvalRules.id
353722
353793
  );
353723
353794
  let approvalRuleList = ruleRows.map((r) => ({
@@ -353836,6 +353907,8 @@ async function runJob(jobId, deps, runnerEnv, opts) {
353836
353907
  db,
353837
353908
  jobChatId: job.chatId ?? null,
353838
353909
  jobChannel: job.channel,
353910
+ activeChannels,
353911
+ notifyChannelOverride,
353839
353912
  resolvedTelegramBotToken: inheritedRootBotToken ?? void 0,
353840
353913
  embeddingClient: deps.embeddingClient,
353841
353914
  workspaces: agentWorkspacesList,
@@ -353937,7 +354010,7 @@ async function runJob(jobId, deps, runnerEnv, opts) {
353937
354010
  const MAX_EMPTY_TURN_RETRIES = 2;
353938
354011
  let emptyTurnRetries = 0;
353939
354012
  let consecutiveDeliveryOnlyTurns = 0;
353940
- const requiresToolDelivery = TOOL_ONLY_DELIVERY_CHANNELS.has(job.channel ?? "") || cronWantsConfirmation;
354013
+ const requiresToolDelivery = TOOL_ONLY_DELIVERY_CHANNELS.has(job.channel ?? "") || triggerWantsConfirmation;
353941
354014
  const CHANNEL_DISPLAY_NAMES = {
353942
354015
  telegram: "Telegram",
353943
354016
  discord: "Discord",
@@ -354388,6 +354461,8 @@ ${notice}` : notice;
354388
354461
  db,
354389
354462
  jobChatId: job.chatId ?? null,
354390
354463
  jobChannel: job.channel,
354464
+ activeChannels,
354465
+ notifyChannelOverride,
354391
354466
  resolvedTelegramBotToken: inheritedRootBotToken ?? void 0,
354392
354467
  embeddingClient: deps.embeddingClient,
354393
354468
  workspaces: agentWorkspacesList,
@@ -354576,6 +354651,8 @@ ${notice}` : notice;
354576
354651
  db,
354577
354652
  jobChatId: job.chatId ?? null,
354578
354653
  jobChannel: job.channel,
354654
+ activeChannels,
354655
+ notifyChannelOverride,
354579
354656
  resolvedTelegramBotToken: inheritedRootBotToken ?? void 0,
354580
354657
  embeddingClient: deps.embeddingClient,
354581
354658
  workspaces: agentWorkspacesList,
@@ -355732,7 +355809,8 @@ async function notifyBudgetExhausted(db, agentId, scheduleName, dailyBudgetUsd)
355732
355809
  try {
355733
355810
  const ownerChatId = await resolveOwnerChatId(db, agentId);
355734
355811
  if (!ownerChatId) return;
355735
- const channel2 = resolveTransportChannel("cron");
355812
+ const activeChannels = await listActiveChannelsForAgent(db, agentId);
355813
+ const channel2 = resolveTransportChannel("cron", activeChannels);
355736
355814
  const creds = await getBindingCredentials(db, agentId, channel2);
355737
355815
  if (!creds) return;
355738
355816
  const adapter2 = getAdapter3(channel2);
@@ -355766,6 +355844,9 @@ async function runScheduleTick(db, deps, max2 = 5) {
355766
355844
  notifyOnSuccess: agentSchedules.notifyOnSuccess,
355767
355845
  // Explicit delivery target (e.g. "post to #team"), null for the common case.
355768
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,
355769
355850
  // Read BEFORE this tick's writes — "was it already budget_exhausted" is
355770
355851
  // the transition check the notify-once-per-day dedup relies on below.
355771
355852
  lastStatus: agentSchedules.lastStatus,
@@ -355778,6 +355859,7 @@ async function runScheduleTick(db, deps, max2 = 5) {
355778
355859
  ).limit(max2 * 2);
355779
355860
  if (candidates.length === 0) return 0;
355780
355861
  const fires = [];
355862
+ const notifyUnreachableScheduleIds = /* @__PURE__ */ new Set();
355781
355863
  for (const sched of candidates.slice(0, max2)) {
355782
355864
  if (!sched.task || !sched.entityId) continue;
355783
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);
@@ -355795,8 +355877,8 @@ async function runScheduleTick(db, deps, max2 = 5) {
355795
355877
  continue;
355796
355878
  }
355797
355879
  const tz = resolveTimezone(sched.timezone);
355798
- const [spentRow] = await db.select({ spent: sql22`COALESCE(SUM(${agentJobs.totalCostUsd}), 0)` }).from(agentJobs).where(
355799
- 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})`
355800
355882
  );
355801
355883
  const spentToday = Number(spentRow?.spent ?? 0);
355802
355884
  if (spentToday >= sched.dailyBudgetUsd) {
@@ -355837,7 +355919,21 @@ async function runScheduleTick(db, deps, max2 = 5) {
355837
355919
  )
355838
355920
  ).returning({ id: agentSchedules.id });
355839
355921
  if (claimed.length === 0) continue;
355840
- 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
+ }
355841
355937
  const [job] = await db.insert(agentJobs).values({
355842
355938
  entityId: sched.entityId,
355843
355939
  agentId: sched.agentId,
@@ -355850,7 +355946,8 @@ async function runScheduleTick(db, deps, max2 = 5) {
355850
355946
  triggerContext: {
355851
355947
  type: "cron",
355852
355948
  scheduleName: sched.name,
355853
- prevRunAt: sched.lastRun ? sched.lastRun.toISOString() : null
355949
+ prevRunAt: sched.lastRun ? sched.lastRun.toISOString() : null,
355950
+ notifyChannel: sched.notifyChannel ?? null
355854
355951
  }
355855
355952
  }).returning({ id: agentJobs.id });
355856
355953
  if (!job) {
@@ -355879,7 +355976,9 @@ async function runScheduleTick(db, deps, max2 = 5) {
355879
355976
  if (settled.status === "rejected") continue;
355880
355977
  const { fire, result } = settled.value;
355881
355978
  let lastStatus;
355882
- if (result.status === "completed") lastStatus = "success";
355979
+ if (notifyUnreachableScheduleIds.has(fire.scheduleId)) {
355980
+ lastStatus = "notify_unreachable";
355981
+ } else if (result.status === "completed") lastStatus = "success";
355883
355982
  else if (result.status === "failed") lastStatus = "failed";
355884
355983
  else if (result.status === "cancelled") lastStatus = "failed";
355885
355984
  else lastStatus = "no_action";
@@ -355913,7 +356012,8 @@ async function deliverCompletedRoots(db) {
355913
356012
  chatId: agentJobs.chatId,
355914
356013
  agentId: agentJobs.agentId,
355915
356014
  entityId: agentJobs.entityId,
355916
- task: agentJobs.task
356015
+ task: agentJobs.task,
356016
+ triggerContext: agentJobs.triggerContext
355917
356017
  }).from(agentJobs).where(eq6(agentJobs.id, rootJobId)).limit(1);
355918
356018
  const rootJob = rootJobRows[0];
355919
356019
  if (!rootJob) continue;
@@ -355958,7 +356058,10 @@ async function deliverCompletedRoots(db) {
355958
356058
  }
355959
356059
  if (rootJob.chatId && rootJob.agentId && compiledResult.trim()) {
355960
356060
  try {
355961
- 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);
355962
356065
  const allowed = rootJob.entityId !== null && await isConversationAllowed(db, {
355963
356066
  entityId: rootJob.entityId,
355964
356067
  agentId: rootJob.agentId,
@@ -356471,12 +356574,12 @@ async function runCuratorTick(db, _deps, runnerEnv) {
356471
356574
  eq6(agentSkills.state, "active"),
356472
356575
  eq6(entities.reflectionEnabled, true)
356473
356576
  )
356474
- ).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}`);
356475
356578
  for (const c of skillCandidates) if (c.entityId) skillSet.add(c.entityId);
356476
356579
  }
356477
356580
  const memorySet = /* @__PURE__ */ new Set();
356478
356581
  if (e2.MEMORY_CURATION_ENABLED !== "false") {
356479
- 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}`);
356480
356583
  for (const c of memoryCandidates) if (c.entityId) memorySet.add(c.entityId);
356481
356584
  }
356482
356585
  if (skillSet.size === 0 && memorySet.size === 0) {
@@ -357119,18 +357222,38 @@ async function webhookRoute(c, deps, runnerEnv) {
357119
357222
  const triggeredAt = (/* @__PURE__ */ new Date()).toISOString();
357120
357223
  const interpolated = interpolateTemplate(trigger.taskTemplate, bodyResult.value);
357121
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
+ }
357122
357243
  const [job] = await deps.db.insert(agentJobs).values({
357123
357244
  entityId: trigger.entityId ?? void 0,
357124
357245
  agentId,
357125
357246
  channel: "webhook",
357126
357247
  task,
357127
357248
  status: "pending",
357249
+ chatId: notifyChatId,
357128
357250
  messages: [{ role: "user", content: task }],
357129
357251
  triggerContext: {
357130
357252
  type: "webhook",
357131
357253
  webhookName: trigger.name,
357132
357254
  slug: trigger.slug,
357133
- triggeredAt
357255
+ triggeredAt,
357256
+ notifyChannel
357134
357257
  }
357135
357258
  }).returning({ id: agentJobs.id });
357136
357259
  if (!job) {