@agent-native/dispatch 0.6.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (341) hide show
  1. package/README.md +38 -1
  2. package/dist/actions/apply-dream-proposal.d.ts +3 -0
  3. package/dist/actions/apply-dream-proposal.d.ts.map +1 -0
  4. package/dist/actions/apply-dream-proposal.js +11 -0
  5. package/dist/actions/apply-dream-proposal.js.map +1 -0
  6. package/dist/actions/create-dream-report.d.ts +3 -0
  7. package/dist/actions/create-dream-report.d.ts.map +1 -0
  8. package/dist/actions/create-dream-report.js +67 -0
  9. package/dist/actions/create-dream-report.js.map +1 -0
  10. package/dist/actions/create-pylon-ticket.d.ts +3 -0
  11. package/dist/actions/create-pylon-ticket.d.ts.map +1 -0
  12. package/dist/actions/create-pylon-ticket.js +94 -0
  13. package/dist/actions/create-pylon-ticket.js.map +1 -0
  14. package/dist/actions/create-vault-grant.js +1 -1
  15. package/dist/actions/create-vault-grant.js.map +1 -1
  16. package/dist/actions/create-vault-secret.d.ts.map +1 -1
  17. package/dist/actions/create-vault-secret.js +4 -3
  18. package/dist/actions/create-vault-secret.js.map +1 -1
  19. package/dist/actions/create-workspace-resource.js +3 -3
  20. package/dist/actions/create-workspace-resource.js.map +1 -1
  21. package/dist/actions/delete-workspace-resource.js +1 -1
  22. package/dist/actions/delete-workspace-resource.js.map +1 -1
  23. package/dist/actions/ensure-dream-job.d.ts +3 -0
  24. package/dist/actions/ensure-dream-job.d.ts.map +1 -0
  25. package/dist/actions/ensure-dream-job.js +73 -0
  26. package/dist/actions/ensure-dream-job.js.map +1 -0
  27. package/dist/actions/get-dream-settings.d.ts +3 -0
  28. package/dist/actions/get-dream-settings.d.ts.map +1 -0
  29. package/dist/actions/get-dream-settings.js +11 -0
  30. package/dist/actions/get-dream-settings.js.map +1 -0
  31. package/dist/actions/get-dream.d.ts +3 -0
  32. package/dist/actions/get-dream.d.ts.map +1 -0
  33. package/dist/actions/get-dream.js +13 -0
  34. package/dist/actions/get-dream.js.map +1 -0
  35. package/dist/actions/get-vault-access-settings.d.ts +3 -0
  36. package/dist/actions/get-vault-access-settings.d.ts.map +1 -0
  37. package/dist/actions/get-vault-access-settings.js +10 -0
  38. package/dist/actions/get-vault-access-settings.js.map +1 -0
  39. package/dist/actions/get-workspace-resource-effective-context.d.ts +3 -0
  40. package/dist/actions/get-workspace-resource-effective-context.d.ts.map +1 -0
  41. package/dist/actions/get-workspace-resource-effective-context.js +27 -0
  42. package/dist/actions/get-workspace-resource-effective-context.js.map +1 -0
  43. package/dist/actions/grant-vault-secrets-to-app.js +1 -1
  44. package/dist/actions/grant-vault-secrets-to-app.js.map +1 -1
  45. package/dist/actions/index.d.ts.map +1 -1
  46. package/dist/actions/index.js +38 -4
  47. package/dist/actions/index.js.map +1 -1
  48. package/dist/actions/list-dream-candidates.d.ts +3 -0
  49. package/dist/actions/list-dream-candidates.d.ts.map +1 -0
  50. package/dist/actions/list-dream-candidates.js +68 -0
  51. package/dist/actions/list-dream-candidates.js.map +1 -0
  52. package/dist/actions/list-dreams.d.ts +3 -0
  53. package/dist/actions/list-dreams.d.ts.map +1 -0
  54. package/dist/actions/list-dreams.js +17 -0
  55. package/dist/actions/list-dreams.js.map +1 -0
  56. package/dist/actions/list-integrations-catalog.js +1 -1
  57. package/dist/actions/list-integrations-catalog.js.map +1 -1
  58. package/dist/actions/list-vault-grants.js +1 -1
  59. package/dist/actions/list-vault-grants.js.map +1 -1
  60. package/dist/actions/list-workspace-apps.d.ts.map +1 -1
  61. package/dist/actions/list-workspace-apps.js +5 -1
  62. package/dist/actions/list-workspace-apps.js.map +1 -1
  63. package/dist/actions/list-workspace-resources-for-app.d.ts +3 -0
  64. package/dist/actions/list-workspace-resources-for-app.d.ts.map +1 -0
  65. package/dist/actions/list-workspace-resources-for-app.js +12 -0
  66. package/dist/actions/list-workspace-resources-for-app.js.map +1 -0
  67. package/dist/actions/list-workspace-resources.js +1 -1
  68. package/dist/actions/list-workspace-resources.js.map +1 -1
  69. package/dist/actions/navigate.d.ts +1 -0
  70. package/dist/actions/navigate.d.ts.map +1 -1
  71. package/dist/actions/navigate.js +2 -1
  72. package/dist/actions/navigate.js.map +1 -1
  73. package/dist/actions/preview-dream-proposal.d.ts +3 -0
  74. package/dist/actions/preview-dream-proposal.d.ts.map +1 -0
  75. package/dist/actions/preview-dream-proposal.js +13 -0
  76. package/dist/actions/preview-dream-proposal.js.map +1 -0
  77. package/dist/actions/preview-workspace-resource-change.d.ts +3 -0
  78. package/dist/actions/preview-workspace-resource-change.d.ts.map +1 -0
  79. package/dist/actions/preview-workspace-resource-change.js +24 -0
  80. package/dist/actions/preview-workspace-resource-change.js.map +1 -0
  81. package/dist/actions/reject-dream-proposal.d.ts +3 -0
  82. package/dist/actions/reject-dream-proposal.d.ts.map +1 -0
  83. package/dist/actions/reject-dream-proposal.js +12 -0
  84. package/dist/actions/reject-dream-proposal.js.map +1 -0
  85. package/dist/actions/restore-starter-workspace-resources.d.ts +3 -0
  86. package/dist/actions/restore-starter-workspace-resources.d.ts.map +1 -0
  87. package/dist/actions/restore-starter-workspace-resources.js +14 -0
  88. package/dist/actions/restore-starter-workspace-resources.js.map +1 -0
  89. package/dist/actions/send-code-agent-remote-command.d.ts +3 -0
  90. package/dist/actions/send-code-agent-remote-command.d.ts.map +1 -0
  91. package/dist/actions/send-code-agent-remote-command.js +53 -0
  92. package/dist/actions/send-code-agent-remote-command.js.map +1 -0
  93. package/dist/actions/set-dream-settings.d.ts +3 -0
  94. package/dist/actions/set-dream-settings.d.ts.map +1 -0
  95. package/dist/actions/set-dream-settings.js +41 -0
  96. package/dist/actions/set-dream-settings.js.map +1 -0
  97. package/dist/actions/set-vault-access-settings.d.ts +3 -0
  98. package/dist/actions/set-vault-access-settings.d.ts.map +1 -0
  99. package/dist/actions/set-vault-access-settings.js +13 -0
  100. package/dist/actions/set-vault-access-settings.js.map +1 -0
  101. package/dist/actions/start-workspace-app-creation.d.ts.map +1 -1
  102. package/dist/actions/start-workspace-app-creation.js +6 -0
  103. package/dist/actions/start-workspace-app-creation.js.map +1 -1
  104. package/dist/actions/sync-vault-to-app.js +1 -1
  105. package/dist/actions/sync-vault-to-app.js.map +1 -1
  106. package/dist/actions/update-workspace-app-metadata.d.ts +3 -0
  107. package/dist/actions/update-workspace-app-metadata.d.ts.map +1 -0
  108. package/dist/actions/update-workspace-app-metadata.js +30 -0
  109. package/dist/actions/update-workspace-app-metadata.js.map +1 -0
  110. package/dist/actions/update-workspace-resource.js +1 -1
  111. package/dist/actions/update-workspace-resource.js.map +1 -1
  112. package/dist/actions/view-screen.d.ts.map +1 -1
  113. package/dist/actions/view-screen.js +77 -4
  114. package/dist/actions/view-screen.js.map +1 -1
  115. package/dist/components/app-keys-popover.js +16 -5
  116. package/dist/components/app-keys-popover.js.map +1 -1
  117. package/dist/components/approval-value-block.d.ts +7 -0
  118. package/dist/components/approval-value-block.d.ts.map +1 -0
  119. package/dist/components/approval-value-block.js +22 -0
  120. package/dist/components/approval-value-block.js.map +1 -0
  121. package/dist/components/create-app-popover.d.ts.map +1 -1
  122. package/dist/components/create-app-popover.js +41 -16
  123. package/dist/components/create-app-popover.js.map +1 -1
  124. package/dist/components/dispatch-shell.d.ts +4 -4
  125. package/dist/components/dispatch-shell.d.ts.map +1 -1
  126. package/dist/components/dispatch-shell.js +6 -6
  127. package/dist/components/dispatch-shell.js.map +1 -1
  128. package/dist/components/layout/Layout.d.ts.map +1 -1
  129. package/dist/components/layout/Layout.js +18 -4
  130. package/dist/components/layout/Layout.js.map +1 -1
  131. package/dist/components/messaging-setup-panel.d.ts.map +1 -1
  132. package/dist/components/messaging-setup-panel.js +2 -2
  133. package/dist/components/messaging-setup-panel.js.map +1 -1
  134. package/dist/components/ui/chart.d.ts +1 -1
  135. package/dist/components/workspace-app-card.d.ts.map +1 -1
  136. package/dist/components/workspace-app-card.js +63 -3
  137. package/dist/components/workspace-app-card.js.map +1 -1
  138. package/dist/components/workspace-resource-effective-stack.d.ts +11 -0
  139. package/dist/components/workspace-resource-effective-stack.d.ts.map +1 -0
  140. package/dist/components/workspace-resource-effective-stack.js +59 -0
  141. package/dist/components/workspace-resource-effective-stack.js.map +1 -0
  142. package/dist/components/workspace-resource-impact-preview.d.ts +9 -0
  143. package/dist/components/workspace-resource-impact-preview.d.ts.map +1 -0
  144. package/dist/components/workspace-resource-impact-preview.js +39 -0
  145. package/dist/components/workspace-resource-impact-preview.js.map +1 -0
  146. package/dist/db/migrations.d.ts.map +1 -1
  147. package/dist/db/migrations.js +59 -0
  148. package/dist/db/migrations.js.map +1 -1
  149. package/dist/db/schema.d.ts +714 -0
  150. package/dist/db/schema.d.ts.map +1 -1
  151. package/dist/db/schema.js +44 -2
  152. package/dist/db/schema.js.map +1 -1
  153. package/dist/hooks/use-navigation-state.d.ts +3 -0
  154. package/dist/hooks/use-navigation-state.d.ts.map +1 -1
  155. package/dist/hooks/use-navigation-state.js +35 -8
  156. package/dist/hooks/use-navigation-state.js.map +1 -1
  157. package/dist/lib/catch-all-target.d.ts +2 -0
  158. package/dist/lib/catch-all-target.d.ts.map +1 -0
  159. package/dist/lib/catch-all-target.js +95 -0
  160. package/dist/lib/catch-all-target.js.map +1 -0
  161. package/dist/lib/utils.d.ts +2 -1
  162. package/dist/lib/utils.d.ts.map +1 -1
  163. package/dist/lib/utils.js +5 -1
  164. package/dist/lib/utils.js.map +1 -1
  165. package/dist/lib/workspace-apps.d.ts +9 -0
  166. package/dist/lib/workspace-apps.d.ts.map +1 -1
  167. package/dist/lib/workspace-apps.js.map +1 -1
  168. package/dist/routes/index.d.ts.map +1 -1
  169. package/dist/routes/index.js +1 -0
  170. package/dist/routes/index.js.map +1 -1
  171. package/dist/routes/pages/$appId.d.ts +2 -2
  172. package/dist/routes/pages/$appId.d.ts.map +1 -1
  173. package/dist/routes/pages/$appId.js +17 -8
  174. package/dist/routes/pages/$appId.js.map +1 -1
  175. package/dist/routes/pages/approval.d.ts.map +1 -1
  176. package/dist/routes/pages/approval.js +4 -1
  177. package/dist/routes/pages/approval.js.map +1 -1
  178. package/dist/routes/pages/approvals.js +1 -1
  179. package/dist/routes/pages/approvals.js.map +1 -1
  180. package/dist/routes/pages/dream-settings.d.ts +34 -0
  181. package/dist/routes/pages/dream-settings.d.ts.map +1 -0
  182. package/dist/routes/pages/dream-settings.js +68 -0
  183. package/dist/routes/pages/dream-settings.js.map +1 -0
  184. package/dist/routes/pages/dreams.d.ts +5 -0
  185. package/dist/routes/pages/dreams.d.ts.map +1 -0
  186. package/dist/routes/pages/dreams.js +435 -0
  187. package/dist/routes/pages/dreams.js.map +1 -0
  188. package/dist/routes/pages/integrations.d.ts.map +1 -1
  189. package/dist/routes/pages/integrations.js +20 -15
  190. package/dist/routes/pages/integrations.js.map +1 -1
  191. package/dist/routes/pages/new-app.js +1 -1
  192. package/dist/routes/pages/new-app.js.map +1 -1
  193. package/dist/routes/pages/overview.d.ts.map +1 -1
  194. package/dist/routes/pages/overview.js +5 -1
  195. package/dist/routes/pages/overview.js.map +1 -1
  196. package/dist/routes/pages/vault.d.ts.map +1 -1
  197. package/dist/routes/pages/vault.js +23 -5
  198. package/dist/routes/pages/vault.js.map +1 -1
  199. package/dist/routes/pages/workspace.d.ts.map +1 -1
  200. package/dist/routes/pages/workspace.js +187 -35
  201. package/dist/routes/pages/workspace.js.map +1 -1
  202. package/dist/server/lib/app-creation-store.d.ts +13 -0
  203. package/dist/server/lib/app-creation-store.d.ts.map +1 -1
  204. package/dist/server/lib/app-creation-store.js +298 -11
  205. package/dist/server/lib/app-creation-store.js.map +1 -1
  206. package/dist/server/lib/dispatch-integrations.d.ts +1 -1
  207. package/dist/server/lib/dispatch-integrations.d.ts.map +1 -1
  208. package/dist/server/lib/dispatch-integrations.js +9 -4
  209. package/dist/server/lib/dispatch-integrations.js.map +1 -1
  210. package/dist/server/lib/dispatch-remote-commands.d.ts +83 -0
  211. package/dist/server/lib/dispatch-remote-commands.d.ts.map +1 -0
  212. package/dist/server/lib/dispatch-remote-commands.js +256 -0
  213. package/dist/server/lib/dispatch-remote-commands.js.map +1 -0
  214. package/dist/server/lib/dispatch-store.d.ts +26 -0
  215. package/dist/server/lib/dispatch-store.d.ts.map +1 -1
  216. package/dist/server/lib/dispatch-store.js +17 -1
  217. package/dist/server/lib/dispatch-store.js.map +1 -1
  218. package/dist/server/lib/dreams-store.d.ts +398 -0
  219. package/dist/server/lib/dreams-store.d.ts.map +1 -0
  220. package/dist/server/lib/dreams-store.js +2330 -0
  221. package/dist/server/lib/dreams-store.js.map +1 -0
  222. package/dist/server/lib/env-config.d.ts.map +1 -1
  223. package/dist/server/lib/env-config.js +5 -0
  224. package/dist/server/lib/env-config.js.map +1 -1
  225. package/dist/server/lib/onboarding-steps.d.ts +12 -0
  226. package/dist/server/lib/onboarding-steps.d.ts.map +1 -0
  227. package/dist/server/lib/onboarding-steps.js +47 -0
  228. package/dist/server/lib/onboarding-steps.js.map +1 -0
  229. package/dist/server/lib/thread-debug-store.d.ts +2 -2
  230. package/dist/server/lib/vault-store.d.ts +55 -0
  231. package/dist/server/lib/vault-store.d.ts.map +1 -1
  232. package/dist/server/lib/vault-store.js +210 -41
  233. package/dist/server/lib/vault-store.js.map +1 -1
  234. package/dist/server/lib/workspace-resources-store.d.ts +181 -17
  235. package/dist/server/lib/workspace-resources-store.d.ts.map +1 -1
  236. package/dist/server/lib/workspace-resources-store.js +737 -108
  237. package/dist/server/lib/workspace-resources-store.js.map +1 -1
  238. package/dist/server/plugins/agent-chat.d.ts.map +1 -1
  239. package/dist/server/plugins/agent-chat.js +2 -1
  240. package/dist/server/plugins/agent-chat.js.map +1 -1
  241. package/dist/server/plugins/core-routes.d.ts.map +1 -1
  242. package/dist/server/plugins/core-routes.js +4 -0
  243. package/dist/server/plugins/core-routes.js.map +1 -1
  244. package/dist/server/plugins/integrations.js +2 -2
  245. package/dist/server/plugins/integrations.js.map +1 -1
  246. package/package.json +15 -11
  247. package/src/actions/apply-dream-proposal.ts +12 -0
  248. package/src/actions/create-dream-report.ts +76 -0
  249. package/src/actions/create-pylon-ticket.ts +109 -0
  250. package/src/actions/create-vault-grant.ts +1 -1
  251. package/src/actions/create-vault-secret.ts +4 -3
  252. package/src/actions/create-workspace-resource.ts +3 -3
  253. package/src/actions/delete-workspace-resource.ts +1 -1
  254. package/src/actions/ensure-dream-job.ts +76 -0
  255. package/src/actions/get-dream-settings.ts +12 -0
  256. package/src/actions/get-dream.ts +14 -0
  257. package/src/actions/get-vault-access-settings.ts +11 -0
  258. package/src/actions/get-workspace-resource-effective-context.ts +34 -0
  259. package/src/actions/grant-vault-secrets-to-app.ts +1 -1
  260. package/src/actions/index.spec.ts +26 -0
  261. package/src/actions/index.ts +39 -4
  262. package/src/actions/list-dream-candidates.ts +77 -0
  263. package/src/actions/list-dreams.ts +17 -0
  264. package/src/actions/list-integrations-catalog.ts +1 -1
  265. package/src/actions/list-vault-grants.ts +1 -1
  266. package/src/actions/list-workspace-apps.ts +5 -1
  267. package/src/actions/list-workspace-resources-for-app.ts +13 -0
  268. package/src/actions/list-workspace-resources.ts +1 -1
  269. package/src/actions/navigate.ts +2 -1
  270. package/src/actions/preview-dream-proposal.ts +14 -0
  271. package/src/actions/preview-workspace-resource-change.ts +25 -0
  272. package/src/actions/reject-dream-proposal.ts +12 -0
  273. package/src/actions/restore-starter-workspace-resources.ts +17 -0
  274. package/src/actions/send-code-agent-remote-command.ts +59 -0
  275. package/src/actions/set-dream-settings.spec.ts +81 -0
  276. package/src/actions/set-dream-settings.ts +44 -0
  277. package/src/actions/set-vault-access-settings.ts +16 -0
  278. package/src/actions/start-workspace-app-creation.ts +8 -0
  279. package/src/actions/sync-vault-to-app.ts +1 -1
  280. package/src/actions/update-workspace-app-metadata.ts +32 -0
  281. package/src/actions/update-workspace-resource.ts +1 -1
  282. package/src/actions/view-screen.ts +94 -3
  283. package/src/components/app-keys-popover.tsx +23 -7
  284. package/src/components/approval-value-block.spec.tsx +59 -0
  285. package/src/components/approval-value-block.tsx +33 -0
  286. package/src/components/create-app-popover.tsx +50 -16
  287. package/src/components/dispatch-shell.tsx +16 -15
  288. package/src/components/layout/Layout.tsx +19 -5
  289. package/src/components/messaging-setup-panel.tsx +54 -39
  290. package/src/components/workspace-app-card.tsx +268 -1
  291. package/src/components/workspace-resource-effective-stack.spec.tsx +125 -0
  292. package/src/components/workspace-resource-effective-stack.tsx +141 -0
  293. package/src/components/workspace-resource-impact-preview.spec.tsx +147 -0
  294. package/src/components/workspace-resource-impact-preview.tsx +116 -0
  295. package/src/db/migrations.ts +59 -0
  296. package/src/db/schema.ts +46 -2
  297. package/src/hooks/use-navigation-state.ts +34 -9
  298. package/src/lib/catch-all-target.spec.ts +218 -0
  299. package/src/lib/catch-all-target.ts +99 -0
  300. package/src/lib/utils.ts +6 -1
  301. package/src/lib/workspace-apps.ts +9 -0
  302. package/src/routes/index.ts +1 -0
  303. package/src/routes/pages/$appId.tsx +21 -8
  304. package/src/routes/pages/approval.tsx +14 -1
  305. package/src/routes/pages/approvals.tsx +1 -1
  306. package/src/routes/pages/dream-settings.spec.ts +130 -0
  307. package/src/routes/pages/dream-settings.ts +103 -0
  308. package/src/routes/pages/dreams.tsx +1828 -0
  309. package/src/routes/pages/integrations.tsx +57 -18
  310. package/src/routes/pages/new-app.tsx +1 -1
  311. package/src/routes/pages/overview.tsx +11 -3
  312. package/src/routes/pages/vault.tsx +76 -9
  313. package/src/routes/pages/workspace.tsx +577 -97
  314. package/src/server/lib/app-creation-store.spec.ts +61 -2
  315. package/src/server/lib/app-creation-store.ts +389 -13
  316. package/src/server/lib/dispatch-integrations.ts +10 -3
  317. package/src/server/lib/dispatch-remote-commands.spec.ts +167 -0
  318. package/src/server/lib/dispatch-remote-commands.ts +375 -0
  319. package/src/server/lib/dispatch-store.ts +37 -1
  320. package/src/server/lib/dreams-store.spec.ts +1492 -0
  321. package/src/server/lib/dreams-store.ts +3168 -0
  322. package/src/server/lib/env-config.ts +5 -0
  323. package/src/server/lib/onboarding-steps.ts +49 -0
  324. package/src/server/lib/vault-store.spec.ts +69 -0
  325. package/src/server/lib/vault-store.ts +266 -49
  326. package/src/server/lib/workspace-resource-approval-lifecycle.spec.ts +236 -0
  327. package/src/server/lib/workspace-resources-store.spec.ts +1106 -0
  328. package/src/server/lib/workspace-resources-store.ts +1001 -134
  329. package/src/server/plugins/agent-chat.ts +2 -1
  330. package/src/server/plugins/core-routes.ts +5 -0
  331. package/src/server/plugins/integrations.ts +2 -2
  332. package/dist/actions/sync-workspace-resources-to-all.d.ts +0 -3
  333. package/dist/actions/sync-workspace-resources-to-all.d.ts.map +0 -1
  334. package/dist/actions/sync-workspace-resources-to-all.js +0 -9
  335. package/dist/actions/sync-workspace-resources-to-all.js.map +0 -1
  336. package/dist/actions/sync-workspace-resources-to-app.d.ts +0 -3
  337. package/dist/actions/sync-workspace-resources-to-app.d.ts.map +0 -1
  338. package/dist/actions/sync-workspace-resources-to-app.js +0 -11
  339. package/dist/actions/sync-workspace-resources-to-app.js.map +0 -1
  340. package/src/actions/sync-workspace-resources-to-all.ts +0 -10
  341. package/src/actions/sync-workspace-resources-to-app.ts +0 -12
@@ -0,0 +1,2330 @@
1
+ import crypto from "node:crypto";
2
+ import { and, desc, eq, inArray, isNull, or } from "drizzle-orm";
3
+ import { resourceGetByPath, resourceList, resourcePut, SHARED_OWNER, } from "@agent-native/core/resources/store";
4
+ import { getOrgSetting, getUserSetting, putOrgSetting, putUserSetting, } from "@agent-native/core/settings";
5
+ import { getDb, schema } from "../../db/index.js";
6
+ import { createApprovalRequest, currentOrgId, currentOwnerEmail, getApprovalPolicy, recordAudit, } from "./dispatch-store.js";
7
+ import { getAgentThreadDebug, listThreadDebugSources, searchAgentThreads, } from "./thread-debug-store.js";
8
+ import { createWorkspaceResource, listWorkspaceResources, updateWorkspaceResource, } from "./workspace-resources-store.js";
9
+ const DEFAULT_DREAM_LIMIT = 20;
10
+ const MAX_DREAM_LIMIT = 50;
11
+ const DEFAULT_SOURCE_TIMEOUT_MS = 15_000;
12
+ const MAX_SOURCE_TIMEOUT_MS = 60_000;
13
+ const DEFAULT_SOURCE_CONCURRENCY = 2;
14
+ const MAX_SOURCE_CONCURRENCY = 8;
15
+ const DEFAULT_SOURCE_START_STAGGER_MS = 250;
16
+ const MAX_SOURCE_START_STAGGER_MS = 5_000;
17
+ const DEFAULT_THREAD_CONCURRENCY = 3;
18
+ const MAX_THREAD_CONCURRENCY = 10;
19
+ const DEFAULT_THREAD_TIMEOUT_MS = 8_000;
20
+ const MAX_THREAD_TIMEOUT_MS = 30_000;
21
+ const MEMORY_INDEX_PATH = "memory/MEMORY.md";
22
+ const DREAM_JOB_PATH = "jobs/dispatch-dream.md";
23
+ const DEFAULT_DREAM_CRON = "0 9 * * 1";
24
+ const DREAM_SETTINGS_KEY = "dispatch-dream-settings";
25
+ function id() {
26
+ return crypto.randomUUID();
27
+ }
28
+ function now() {
29
+ return Date.now();
30
+ }
31
+ function today() {
32
+ return new Date().toISOString().slice(0, 10);
33
+ }
34
+ function clampLimit(limit) {
35
+ return Math.max(1, Math.min(MAX_DREAM_LIMIT, limit ?? DEFAULT_DREAM_LIMIT));
36
+ }
37
+ function clampSourceTimeoutMs(timeoutMs) {
38
+ const parsed = Number(timeoutMs ?? DEFAULT_SOURCE_TIMEOUT_MS);
39
+ if (!Number.isFinite(parsed))
40
+ return DEFAULT_SOURCE_TIMEOUT_MS;
41
+ return Math.max(1, Math.min(MAX_SOURCE_TIMEOUT_MS, Math.floor(parsed)));
42
+ }
43
+ function clampSourceConcurrency(value) {
44
+ const parsed = Number(value ?? DEFAULT_SOURCE_CONCURRENCY);
45
+ if (!Number.isFinite(parsed))
46
+ return DEFAULT_SOURCE_CONCURRENCY;
47
+ return Math.max(1, Math.min(MAX_SOURCE_CONCURRENCY, Math.floor(parsed)));
48
+ }
49
+ function clampSourceStartStaggerMs(value) {
50
+ const parsed = Number(value ?? DEFAULT_SOURCE_START_STAGGER_MS);
51
+ if (!Number.isFinite(parsed))
52
+ return DEFAULT_SOURCE_START_STAGGER_MS;
53
+ return Math.max(0, Math.min(MAX_SOURCE_START_STAGGER_MS, Math.floor(parsed)));
54
+ }
55
+ function clampThreadConcurrency(value) {
56
+ const parsed = Number(value ?? DEFAULT_THREAD_CONCURRENCY);
57
+ if (!Number.isFinite(parsed))
58
+ return DEFAULT_THREAD_CONCURRENCY;
59
+ return Math.max(1, Math.min(MAX_THREAD_CONCURRENCY, Math.floor(parsed)));
60
+ }
61
+ function clampThreadTimeoutMs(value, sourceTimeoutMs) {
62
+ const fallback = Math.min(DEFAULT_THREAD_TIMEOUT_MS, Math.max(1_000, Math.floor(sourceTimeoutMs * 0.6)));
63
+ const parsed = Number(value ?? fallback);
64
+ if (!Number.isFinite(parsed))
65
+ return fallback;
66
+ return Math.max(1, Math.min(MAX_THREAD_TIMEOUT_MS, sourceTimeoutMs, Math.floor(parsed)));
67
+ }
68
+ function clampMinCandidateCount(value) {
69
+ const parsed = Number(value ?? 1);
70
+ if (!Number.isFinite(parsed))
71
+ return 1;
72
+ return Math.max(0, Math.min(50, Math.floor(parsed)));
73
+ }
74
+ function dreamSettingsScope() {
75
+ const orgId = currentOrgId();
76
+ if (orgId)
77
+ return { scope: "org", scopeId: orgId };
78
+ return { scope: "user", scopeId: currentOwnerEmail() };
79
+ }
80
+ function normalizeSourceIds(value) {
81
+ if (!Array.isArray(value))
82
+ return [];
83
+ return Array.from(new Set(value
84
+ .map((entry) => String(entry ?? "").trim())
85
+ .filter(Boolean)
86
+ .slice(0, 50)));
87
+ }
88
+ function normalizeDreamSettings(raw) {
89
+ const sourceIds = normalizeSourceIds(raw?.sourceIds);
90
+ const sourceId = String(raw?.sourceId ?? "").trim() || "all";
91
+ return {
92
+ enabled: raw?.enabled === true,
93
+ schedule: typeof raw?.schedule === "string" && cronLooksValid(raw.schedule)
94
+ ? raw.schedule
95
+ : DEFAULT_DREAM_CRON,
96
+ sourceId,
97
+ sourceIds,
98
+ allSources: raw?.allSources === true || sourceId === "all" || sourceIds.length > 0,
99
+ query: typeof raw?.query === "string" && raw.query.trim()
100
+ ? raw.query.trim()
101
+ : null,
102
+ limit: clampLimit(Number(raw?.limit ?? 8)),
103
+ sourceTimeoutMs: clampSourceTimeoutMs(Number(raw?.sourceTimeoutMs ?? 30_000)),
104
+ sourceConcurrency: clampSourceConcurrency(Number(raw?.sourceConcurrency ?? DEFAULT_SOURCE_CONCURRENCY)),
105
+ sourceStartStaggerMs: clampSourceStartStaggerMs(Number(raw?.sourceStartStaggerMs ?? DEFAULT_SOURCE_START_STAGGER_MS)),
106
+ threadConcurrency: clampThreadConcurrency(Number(raw?.threadConcurrency ?? DEFAULT_THREAD_CONCURRENCY)),
107
+ threadTimeoutMs: clampThreadTimeoutMs(Number(raw?.threadTimeoutMs ?? DEFAULT_THREAD_TIMEOUT_MS), clampSourceTimeoutMs(Number(raw?.sourceTimeoutMs ?? 30_000))),
108
+ minCandidateCount: clampMinCandidateCount(Number(raw?.minCandidateCount ?? 1)),
109
+ };
110
+ }
111
+ export async function getDreamSettings() {
112
+ const scope = dreamSettingsScope();
113
+ const raw = scope.scope === "org"
114
+ ? await getOrgSetting(scope.scopeId, DREAM_SETTINGS_KEY)
115
+ : await getUserSetting(scope.scopeId, DREAM_SETTINGS_KEY);
116
+ return {
117
+ ...scope,
118
+ ...normalizeDreamSettings(raw),
119
+ };
120
+ }
121
+ export async function setDreamSettings(input) {
122
+ if (input.schedule && !cronLooksValid(input.schedule)) {
123
+ throw new Error('Invalid cron expression. Use a standard five-field cron like "0 9 * * 1".');
124
+ }
125
+ const current = await getDreamSettings();
126
+ const next = normalizeDreamSettings({
127
+ ...current,
128
+ ...input,
129
+ sourceIds: input.sourceIds !== undefined
130
+ ? normalizeSourceIds(input.sourceIds)
131
+ : current.sourceIds,
132
+ query: input.query === undefined
133
+ ? current.query
134
+ : input.query?.trim()
135
+ ? input.query.trim()
136
+ : null,
137
+ });
138
+ const scope = dreamSettingsScope();
139
+ if (scope.scope === "org") {
140
+ await putOrgSetting(scope.scopeId, DREAM_SETTINGS_KEY, next);
141
+ }
142
+ else {
143
+ await putUserSetting(scope.scopeId, DREAM_SETTINGS_KEY, next);
144
+ }
145
+ await recordAudit({
146
+ action: "dream.settings.updated",
147
+ targetType: "dream-settings",
148
+ targetId: DREAM_SETTINGS_KEY,
149
+ summary: next.enabled
150
+ ? "Updated and enabled recurring Dispatch dream settings"
151
+ : "Updated recurring Dispatch dream settings",
152
+ metadata: next,
153
+ });
154
+ return getDreamSettings();
155
+ }
156
+ class DreamSourceTimeoutError extends Error {
157
+ code = "DREAM_SOURCE_TIMEOUT";
158
+ constructor(sourceId, timeoutMs) {
159
+ super(`Dream source "${sourceId}" timed out after ${timeoutMs}ms.`);
160
+ this.name = "DreamSourceTimeoutError";
161
+ }
162
+ }
163
+ async function withTimeout(promise, sourceId, timeoutMs) {
164
+ let timeout = null;
165
+ try {
166
+ return await Promise.race([
167
+ promise,
168
+ new Promise((_, reject) => {
169
+ timeout = setTimeout(() => reject(new DreamSourceTimeoutError(sourceId, timeoutMs)), timeoutMs);
170
+ }),
171
+ ]);
172
+ }
173
+ finally {
174
+ if (timeout)
175
+ clearTimeout(timeout);
176
+ }
177
+ }
178
+ function isDreamSourceTimeout(error) {
179
+ return (error instanceof DreamSourceTimeoutError ||
180
+ error?.code === "DREAM_SOURCE_TIMEOUT");
181
+ }
182
+ function sleep(ms) {
183
+ if (ms <= 0)
184
+ return Promise.resolve();
185
+ return new Promise((resolve) => setTimeout(resolve, ms));
186
+ }
187
+ function compactErrorMessage(error) {
188
+ return compactText(error?.message ?? error, 320);
189
+ }
190
+ async function mapWithConcurrency(items, concurrency, worker) {
191
+ const results = new Array(items.length);
192
+ let nextIndex = 0;
193
+ const workerCount = Math.max(1, Math.min(concurrency, items.length));
194
+ await Promise.all(Array.from({ length: workerCount }, async () => {
195
+ while (nextIndex < items.length) {
196
+ const index = nextIndex;
197
+ nextIndex += 1;
198
+ results[index] = await worker(items[index], index);
199
+ }
200
+ }));
201
+ return results;
202
+ }
203
+ function scopeFor(table, ctx) {
204
+ if (!ctx.orgId) {
205
+ return and(eq(table.ownerEmail, ctx.ownerEmail), isNull(table.orgId));
206
+ }
207
+ return or(eq(table.ownerEmail, ctx.ownerEmail), eq(table.orgId, ctx.orgId));
208
+ }
209
+ function safeJson(value) {
210
+ try {
211
+ return JSON.stringify(value ?? null);
212
+ }
213
+ catch {
214
+ return "null";
215
+ }
216
+ }
217
+ function safeJsonParse(value, fallback) {
218
+ if (value == null || value === "")
219
+ return fallback;
220
+ try {
221
+ return JSON.parse(String(value));
222
+ }
223
+ catch {
224
+ return fallback;
225
+ }
226
+ }
227
+ function compactText(value, max = 260) {
228
+ const raw = typeof value === "string" ? value : value == null ? "" : safeJson(value);
229
+ const redacted = raw
230
+ .replace(/sk-[A-Za-z0-9_-]{12,}/g, "sk-REDACTED")
231
+ .replace(/anthropic-[A-Za-z0-9_-]{12,}/gi, "anthropic-REDACTED")
232
+ .replace(/Bearer\s+[A-Za-z0-9._~+/=-]{12,}/gi, "Bearer REDACTED")
233
+ .replace(/\b[A-Za-z0-9+/]{32,}={0,2}\b/g, "REDACTED_TOKEN")
234
+ .replace(/\s+/g, " ")
235
+ .trim();
236
+ if (redacted.length <= max)
237
+ return redacted;
238
+ return `${redacted.slice(0, max - 1).trimEnd()}…`;
239
+ }
240
+ function slugify(value) {
241
+ const slug = value
242
+ .toLowerCase()
243
+ .replace(/[^a-z0-9]+/g, "-")
244
+ .replace(/^-+|-+$/g, "")
245
+ .slice(0, 64);
246
+ return slug || "dispatch-dream";
247
+ }
248
+ function parseNumber(value) {
249
+ const parsed = Number(value);
250
+ return Number.isFinite(parsed) ? parsed : null;
251
+ }
252
+ function objectText(value) {
253
+ return compactText(value, 1_200).toLowerCase();
254
+ }
255
+ function isFailureStatus(status) {
256
+ const value = String(status ?? "").toLowerCase();
257
+ return (value.includes("fail") ||
258
+ value.includes("error") ||
259
+ value.includes("abort") ||
260
+ value.includes("cancel") ||
261
+ value.includes("timeout"));
262
+ }
263
+ function isSuccessStatus(status) {
264
+ const value = String(status ?? "").toLowerCase();
265
+ return (value === "success" ||
266
+ value === "succeeded" ||
267
+ value === "completed" ||
268
+ value === "complete");
269
+ }
270
+ function isNegativeFeedback(row) {
271
+ const text = objectText(row);
272
+ const rating = parseNumber(row.rating) ??
273
+ parseNumber(row.score) ??
274
+ parseNumber(row.value);
275
+ return (text.includes("thumbs_down") ||
276
+ text.includes("negative") ||
277
+ text.includes("bad") ||
278
+ text.includes("incorrect") ||
279
+ text.includes("not helpful") ||
280
+ (rating != null && rating <= 2));
281
+ }
282
+ function lowerString(value) {
283
+ return String(value ?? "").toLowerCase();
284
+ }
285
+ function sameOwnerEmail(a, b) {
286
+ const left = String(a ?? "")
287
+ .trim()
288
+ .toLowerCase();
289
+ const right = String(b ?? "")
290
+ .trim()
291
+ .toLowerCase();
292
+ return Boolean(left && right && left === right);
293
+ }
294
+ function isEvalFailure(row) {
295
+ const passed = row.passed;
296
+ const score = parseNumber(row.score);
297
+ const status = lowerString(row.status);
298
+ const result = lowerString(row.result ?? row.outcome);
299
+ return (passed === false ||
300
+ status === "failed" ||
301
+ status === "failure" ||
302
+ status === "regression" ||
303
+ result === "failed" ||
304
+ result === "failure" ||
305
+ result === "regression" ||
306
+ (score != null && score < 0.7));
307
+ }
308
+ function isLowSatisfaction(row) {
309
+ const text = objectText(row);
310
+ const score = parseNumber(row.score) ??
311
+ parseNumber(row.satisfaction) ??
312
+ parseNumber(row.value);
313
+ return (text.includes("frustrat") ||
314
+ text.includes("unsatisfied") ||
315
+ text.includes("low") ||
316
+ (score != null && score < 0.65));
317
+ }
318
+ function reason(code, label, score, evidenceCount) {
319
+ return { code, label, score, evidenceCount };
320
+ }
321
+ function isRecord(value) {
322
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
323
+ }
324
+ function firstString(record, keys) {
325
+ for (const key of keys) {
326
+ const value = record[key];
327
+ if (typeof value === "string" && value.trim())
328
+ return value.trim();
329
+ }
330
+ return null;
331
+ }
332
+ function firstNumber(record, keys) {
333
+ for (const key of keys) {
334
+ const value = parseNumber(record[key]);
335
+ if (value != null)
336
+ return value;
337
+ }
338
+ return null;
339
+ }
340
+ function metadataRecord(value) {
341
+ if (isRecord(value))
342
+ return value;
343
+ if (typeof value !== "string" || !value.trim())
344
+ return {};
345
+ const parsed = safeJsonParse(value, null);
346
+ return isRecord(parsed) ? parsed : {};
347
+ }
348
+ function maybeJsonRecord(value) {
349
+ if (isRecord(value))
350
+ return value;
351
+ if (typeof value !== "string" || !value.trim().startsWith("{"))
352
+ return null;
353
+ const parsed = safeJsonParse(value, null);
354
+ return isRecord(parsed) ? parsed : null;
355
+ }
356
+ function humanizeKey(value) {
357
+ return value
358
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
359
+ .replace(/[_-]+/g, " ")
360
+ .replace(/\s+/g, " ")
361
+ .trim()
362
+ .toLowerCase();
363
+ }
364
+ function formatNumber(value) {
365
+ return Number.isInteger(value) ? String(value) : value.toFixed(3);
366
+ }
367
+ function formatMetadataComparison(metadata) {
368
+ for (const [key, rawActual] of Object.entries(metadata)) {
369
+ if (!/^actual/i.test(key))
370
+ continue;
371
+ const expectedKey = key.replace(/^actual/i, "expected");
372
+ const actual = parseNumber(rawActual);
373
+ const expected = parseNumber(metadata[expectedKey]);
374
+ if (actual == null || expected == null)
375
+ continue;
376
+ const rawMetric = key.replace(/^actual/i, "");
377
+ const normalizedMetric = rawMetric
378
+ .replace(/[^a-z0-9]+/gi, "")
379
+ .toLowerCase();
380
+ const metric = normalizedMetric === "ms"
381
+ ? "latency"
382
+ : normalizedMetric.includes("cx")
383
+ ? "cost"
384
+ : humanizeKey(rawMetric) || "value";
385
+ const unit = /ms$/i.test(key) ? "ms" : "";
386
+ return `${metric}: actual ${formatNumber(actual)}${unit}, expected ${formatNumber(expected)}${unit}`;
387
+ }
388
+ const actualMs = firstNumber(metadata, [
389
+ "actualMs",
390
+ "actual_ms",
391
+ "durationMs",
392
+ "duration_ms",
393
+ ]);
394
+ const expectedMs = firstNumber(metadata, [
395
+ "expectedMs",
396
+ "expected_ms",
397
+ "maxMs",
398
+ "max_ms",
399
+ "thresholdMs",
400
+ "threshold_ms",
401
+ ]);
402
+ if (actualMs != null && expectedMs != null) {
403
+ return `latency: actual ${formatNumber(actualMs)}ms, expected ${formatNumber(expectedMs)}ms`;
404
+ }
405
+ return null;
406
+ }
407
+ function formatEvalFailureEvidence(value) {
408
+ const record = maybeJsonRecord(value);
409
+ if (!record)
410
+ return compactText(value);
411
+ const metadata = metadataRecord(record.metadata ?? record.details);
412
+ const name = firstString(record, [
413
+ "name",
414
+ "criteria",
415
+ "criterion",
416
+ "evalName",
417
+ "eval_id",
418
+ "metric",
419
+ "type",
420
+ ]) ?? "evaluation";
421
+ const score = firstNumber(record, ["score", "value"]);
422
+ const status = firstString(record, ["status", "result", "outcome"]) ??
423
+ (record.passed === false ? "failed" : null);
424
+ const message = firstString(record, [
425
+ "message",
426
+ "error",
427
+ "reason",
428
+ "summary",
429
+ "notes",
430
+ ]);
431
+ const comparison = formatMetadataComparison(metadata);
432
+ const pieces = [`${humanizeKey(name)}${status ? ` ${status}` : " failed"}`];
433
+ if (score != null)
434
+ pieces.push(`score ${formatNumber(score)}`);
435
+ if (comparison)
436
+ pieces.push(comparison);
437
+ if (message)
438
+ pieces.push(compactText(message, 120));
439
+ return compactText(pieces.join("; "), 240);
440
+ }
441
+ function formatToolErrorEvidence(value) {
442
+ const record = maybeJsonRecord(value);
443
+ if (!record)
444
+ return compactText(value);
445
+ const event = isRecord(record.event) ? record.event : record;
446
+ const parsedResult = safeJsonParse(event.result, null);
447
+ const source = isRecord(parsedResult) ? parsedResult : event;
448
+ const code = firstString(source, ["errorCode", "code", "status"]);
449
+ const message = firstString(source, ["error", "message", "reason", "result"]) ??
450
+ firstString(event, ["error", "message", "reason", "result"]);
451
+ if (message && /^tool error\b/i.test(message.trim())) {
452
+ return compactText(message, 240);
453
+ }
454
+ const prefix = code ? `Tool error (${code})` : "Tool error";
455
+ return compactText(`${prefix}: ${message ?? compactText(value, 180)}`, 240);
456
+ }
457
+ function formatFeedbackEvidence(kind, value) {
458
+ const record = maybeJsonRecord(value);
459
+ if (!record)
460
+ return compactText(value);
461
+ const rating = firstNumber(record, ["rating", "score", "satisfaction"]);
462
+ const message = firstString(record, [
463
+ "feedback",
464
+ "comment",
465
+ "message",
466
+ "reason",
467
+ "text",
468
+ "body",
469
+ ]) ?? firstString(record, ["status", "result", "outcome"]);
470
+ const prefix = kind === "low-satisfaction"
471
+ ? "Low satisfaction signal"
472
+ : "Negative feedback";
473
+ const ratingText = rating != null ? ` (score ${formatNumber(rating)})` : "";
474
+ return compactText(`${prefix}${ratingText}: ${message ?? compactText(value, 180)}`, 240);
475
+ }
476
+ function formatSuccessEvidence(value) {
477
+ const record = maybeJsonRecord(value);
478
+ if (!record)
479
+ return compactText(value);
480
+ const label = firstString(record, ["title", "name", "summary", "description", "path"]) ??
481
+ firstString(record, ["run_id", "id"]);
482
+ return compactText(`Successful checkpoint${label ? `: ${label}` : ""}`, 220);
483
+ }
484
+ function formatEvidenceSnippet(kind, value) {
485
+ if (kind === "eval-failure")
486
+ return formatEvalFailureEvidence(value);
487
+ if (kind === "tool-error")
488
+ return formatToolErrorEvidence(value);
489
+ if (kind === "negative-feedback" || kind === "low-satisfaction") {
490
+ return formatFeedbackEvidence(kind, value);
491
+ }
492
+ if (kind === "verified-success")
493
+ return formatSuccessEvidence(value);
494
+ return compactText(value);
495
+ }
496
+ function normalizeEvidenceQuote(value) {
497
+ return value
498
+ .toLowerCase()
499
+ .replace(/[0-9a-f]{8}-[0-9a-f-]{27,}/g, " id ")
500
+ .replace(/\b(?:run|thread|message|event)[_-]?id[:=]?\s*[a-z0-9._:-]+/gi, " id ")
501
+ .replace(/[^a-z0-9]+/g, " ")
502
+ .replace(/\s+/g, " ")
503
+ .trim();
504
+ }
505
+ function evidenceDedupeKey(entry) {
506
+ return [
507
+ entry.threadId,
508
+ entry.kind,
509
+ normalizeEvidenceQuote(entry.snippet),
510
+ ].join("\u0000");
511
+ }
512
+ function normalizeEvidence(input) {
513
+ const snippet = formatEvidenceSnippet(input.kind, input.snippet);
514
+ if (!snippet.trim())
515
+ return null;
516
+ return { ...input, snippet };
517
+ }
518
+ function addEvidence(bucket, input, max = 12) {
519
+ const normalized = normalizeEvidence(input);
520
+ if (!normalized)
521
+ return;
522
+ const key = evidenceDedupeKey(normalized);
523
+ if (bucket.some((entry) => evidenceDedupeKey(entry) === key))
524
+ return;
525
+ if (bucket.length >= max)
526
+ return;
527
+ bucket.push(normalized);
528
+ }
529
+ function stripInjectedUserContext(text) {
530
+ const marker = text.search(/(?:^|\n)\s*<context>/i);
531
+ return (marker >= 0 ? text.slice(0, marker) : text).trim();
532
+ }
533
+ function isCorrectionSignal(lower) {
534
+ return (/\b(actually|wrong|not what i meant|you should)\b/.test(lower) ||
535
+ /\b(don't|do not|never)\b/.test(lower) ||
536
+ /\b(no|instead)\b[,.!?:;-]/.test(lower) ||
537
+ /\bfrom now on\b/.test(lower));
538
+ }
539
+ function isToolErrorEvent(value) {
540
+ const event = value?.event ?? value;
541
+ const type = lowerString(event?.type);
542
+ const status = lowerString(event?.status);
543
+ if (type.includes("error") ||
544
+ type.includes("exception") ||
545
+ type.includes("failed") ||
546
+ status === "error" ||
547
+ status === "failed") {
548
+ return true;
549
+ }
550
+ const result = typeof event?.result === "string"
551
+ ? event.result.trim().toLowerCase()
552
+ : typeof event?.error === "string"
553
+ ? event.error.trim().toLowerCase()
554
+ : "";
555
+ if (!result)
556
+ return false;
557
+ if (result.startsWith("error:") ||
558
+ result.startsWith("failed:") ||
559
+ result.includes("exception") ||
560
+ result.includes("timed out")) {
561
+ return true;
562
+ }
563
+ const parsed = safeJsonParse(event?.result, null);
564
+ const parsedStatus = lowerString(parsed?.status ?? parsed?.state);
565
+ return parsedStatus === "failed" || parsedStatus === "error";
566
+ }
567
+ function analyzeThreadDebug(debug, sourceId) {
568
+ const evidence = [];
569
+ const reasons = [];
570
+ const counts = {
571
+ explicitCorrections: 0,
572
+ rememberRequests: 0,
573
+ failedRuns: 0,
574
+ toolErrors: 0,
575
+ negativeFeedback: 0,
576
+ evalFailures: 0,
577
+ lowSatisfaction: 0,
578
+ frustration: 0,
579
+ verifiedSuccess: 0,
580
+ };
581
+ const thread = debug.thread;
582
+ const threadTitle = thread.title || thread.preview || thread.id;
583
+ const messages = Array.isArray(debug.messages) ? debug.messages : [];
584
+ for (const message of messages) {
585
+ const text = stripInjectedUserContext(String(message?.text ?? ""));
586
+ if (message?.role !== "user" || !text.trim())
587
+ continue;
588
+ const lower = text.toLowerCase();
589
+ const isRemember = /\b(remember|for future|next time|from now on|always)\b/.test(lower);
590
+ const isCorrection = isCorrectionSignal(lower);
591
+ const isFrustration = /\b(frustrat|again|still|why did|keeps? doing|same mistake)\b/.test(lower);
592
+ if (isRemember) {
593
+ counts.rememberRequests += 1;
594
+ addEvidence(evidence, {
595
+ kind: "remember-request",
596
+ label: "User asked the agent to remember something",
597
+ snippet: text,
598
+ threadId: thread.id,
599
+ threadTitle,
600
+ messageIndex: message.index,
601
+ createdAt: parseNumber(message.createdAt),
602
+ });
603
+ }
604
+ if (isCorrection) {
605
+ counts.explicitCorrections += 1;
606
+ addEvidence(evidence, {
607
+ kind: "explicit-correction",
608
+ label: "User corrected the agent",
609
+ snippet: text,
610
+ threadId: thread.id,
611
+ threadTitle,
612
+ messageIndex: message.index,
613
+ createdAt: parseNumber(message.createdAt),
614
+ });
615
+ }
616
+ if (isFrustration) {
617
+ counts.frustration += 1;
618
+ addEvidence(evidence, {
619
+ kind: "frustration",
620
+ label: "User expressed friction or repeated failure",
621
+ snippet: text,
622
+ threadId: thread.id,
623
+ threadTitle,
624
+ messageIndex: message.index,
625
+ createdAt: parseNumber(message.createdAt),
626
+ });
627
+ }
628
+ }
629
+ const runs = Array.isArray(debug.runs) ? debug.runs : [];
630
+ for (const run of runs) {
631
+ if (isFailureStatus(run?.status) || run?.abortReason) {
632
+ counts.failedRuns += 1;
633
+ addEvidence(evidence, {
634
+ kind: "failed-run",
635
+ label: "Run failed or aborted",
636
+ snippet: `${run?.status ?? "unknown"}${run?.abortReason ? `: ${run.abortReason}` : ""}`,
637
+ threadId: thread.id,
638
+ threadTitle,
639
+ runId: run?.id ?? null,
640
+ createdAt: parseNumber(run?.completedAt ?? run?.startedAt),
641
+ });
642
+ }
643
+ const events = Array.isArray(run?.events) ? run.events : [];
644
+ for (const event of events) {
645
+ if (!isToolErrorEvent(event))
646
+ continue;
647
+ counts.toolErrors += 1;
648
+ addEvidence(evidence, {
649
+ kind: "tool-error",
650
+ label: "Tool call reported an error",
651
+ snippet: event,
652
+ threadId: thread.id,
653
+ threadTitle,
654
+ runId: run?.id ?? null,
655
+ });
656
+ }
657
+ }
658
+ const feedback = Array.isArray(debug.feedback) ? debug.feedback : [];
659
+ for (const row of feedback) {
660
+ if (!isNegativeFeedback(row))
661
+ continue;
662
+ counts.negativeFeedback += 1;
663
+ addEvidence(evidence, {
664
+ kind: "negative-feedback",
665
+ label: "Negative feedback was recorded",
666
+ snippet: row,
667
+ threadId: thread.id,
668
+ threadTitle,
669
+ });
670
+ }
671
+ const evals = Array.isArray(debug.evals) ? debug.evals : [];
672
+ for (const row of evals) {
673
+ if (!isEvalFailure(row))
674
+ continue;
675
+ counts.evalFailures += 1;
676
+ addEvidence(evidence, {
677
+ kind: "eval-failure",
678
+ label: "Evaluation failed or scored low",
679
+ snippet: row,
680
+ threadId: thread.id,
681
+ threadTitle,
682
+ runId: typeof row?.run_id === "string" ? row.run_id : null,
683
+ });
684
+ }
685
+ const satisfaction = Array.isArray(debug.satisfaction)
686
+ ? debug.satisfaction
687
+ : [];
688
+ for (const row of satisfaction) {
689
+ if (!isLowSatisfaction(row))
690
+ continue;
691
+ counts.lowSatisfaction += 1;
692
+ addEvidence(evidence, {
693
+ kind: "low-satisfaction",
694
+ label: "Satisfaction signal was low",
695
+ snippet: row,
696
+ threadId: thread.id,
697
+ threadTitle,
698
+ });
699
+ }
700
+ const checkpoints = Array.isArray(debug.checkpoints) ? debug.checkpoints : [];
701
+ const latestRunStatus = runs.length > 0 && typeof runs[0]?.status === "string"
702
+ ? runs[0].status
703
+ : null;
704
+ if (runs.some((run) => isSuccessStatus(run?.status)) &&
705
+ checkpoints.length > 0 &&
706
+ counts.failedRuns === 0) {
707
+ counts.verifiedSuccess += 1;
708
+ addEvidence(evidence, {
709
+ kind: "verified-success",
710
+ label: "Successful run produced a checkpoint",
711
+ snippet: checkpoints[0],
712
+ threadId: thread.id,
713
+ threadTitle,
714
+ runId: typeof checkpoints[0]?.run_id === "string"
715
+ ? checkpoints[0].run_id
716
+ : null,
717
+ });
718
+ }
719
+ if (counts.rememberRequests) {
720
+ reasons.push(reason("remember-request", "User explicitly asked the agent to remember something", counts.rememberRequests * 30, counts.rememberRequests));
721
+ }
722
+ if (counts.explicitCorrections) {
723
+ reasons.push(reason("explicit-correction", "User corrections should be considered for memory", counts.explicitCorrections * 25, counts.explicitCorrections));
724
+ }
725
+ if (counts.failedRuns) {
726
+ reasons.push(reason("failed-run", "Failed or aborted runs are useful dream material", counts.failedRuns * 12, counts.failedRuns));
727
+ }
728
+ if (counts.toolErrors) {
729
+ reasons.push(reason("tool-error", "Tool errors repeated inside the run", Math.min(30, counts.toolErrors * 4), counts.toolErrors));
730
+ }
731
+ if (counts.negativeFeedback) {
732
+ reasons.push(reason("negative-feedback", "User feedback indicates the run may contain a lesson", counts.negativeFeedback * 20, counts.negativeFeedback));
733
+ }
734
+ if (counts.evalFailures) {
735
+ reasons.push(reason("eval-failure", "Evaluation signals found a failure", counts.evalFailures * 20, counts.evalFailures));
736
+ }
737
+ if (counts.lowSatisfaction || counts.frustration) {
738
+ const total = counts.lowSatisfaction + counts.frustration;
739
+ reasons.push(reason("satisfaction-friction", "Satisfaction or user wording suggests friction", total * 10, total));
740
+ }
741
+ if (counts.verifiedSuccess) {
742
+ reasons.push(reason("verified-success", "Successful checkpointed workflow may be worth preserving", 8, counts.verifiedSuccess));
743
+ }
744
+ const score = reasons.reduce((sum, entry) => sum + entry.score, 0);
745
+ return {
746
+ thread: {
747
+ id: thread.id,
748
+ ownerEmail: thread.ownerEmail,
749
+ title: thread.title,
750
+ preview: thread.preview,
751
+ messageCount: thread.messageCount,
752
+ createdAt: thread.createdAt,
753
+ updatedAt: thread.updatedAt,
754
+ },
755
+ sourceId,
756
+ score,
757
+ reasons,
758
+ evidenceCounts: counts,
759
+ evidence: evidence.map((entry) => ({
760
+ ...entry,
761
+ sourceId: entry.sourceId ?? sourceId,
762
+ })),
763
+ latestRunStatus,
764
+ };
765
+ }
766
+ function wantsAllDreamSources(input) {
767
+ return Boolean(input.allSources || input.sourceId?.trim() === "all");
768
+ }
769
+ async function dreamSourceRefs(input) {
770
+ const explicit = Array.from(new Set((input.sourceIds ?? [])
771
+ .map((sourceId) => sourceId.trim())
772
+ .filter(Boolean)));
773
+ if (explicit.length > 0) {
774
+ return explicit.map((sourceId) => ({ id: sourceId, label: sourceId }));
775
+ }
776
+ const sourceId = input.sourceId?.trim() || "current";
777
+ if (!wantsAllDreamSources(input)) {
778
+ return [{ id: sourceId, label: sourceId }];
779
+ }
780
+ const listed = await listThreadDebugSources();
781
+ const sources = listed.sources
782
+ .filter((source) => source.connected)
783
+ .map((source) => ({ id: source.id, label: source.label }));
784
+ return sources.length > 0
785
+ ? sources
786
+ : [{ id: "current", label: "Current Dispatch DB" }];
787
+ }
788
+ async function inspectDreamSource(input, sourceId, sourceTimeoutMs) {
789
+ const limit = clampLimit(input.limit);
790
+ const threadConcurrency = clampThreadConcurrency(input.threadConcurrency);
791
+ const threadTimeoutMs = clampThreadTimeoutMs(input.threadTimeoutMs, sourceTimeoutMs);
792
+ const search = await searchAgentThreads({
793
+ sourceId,
794
+ query: input.query,
795
+ ownerEmail: input.ownerEmail,
796
+ limit,
797
+ });
798
+ const inspected = await mapWithConcurrency(search.threads, threadConcurrency, async (thread) => {
799
+ const startedAt = now();
800
+ try {
801
+ const debug = await withTimeout(getAgentThreadDebug({
802
+ sourceId,
803
+ threadId: thread.id,
804
+ ownerEmail: input.ownerEmail,
805
+ maxRuns: 10,
806
+ maxEvents: 300,
807
+ maxTraceSpans: 200,
808
+ }), `${sourceId}:${thread.id}`, threadTimeoutMs);
809
+ return {
810
+ candidate: analyzeThreadDebug(debug, sourceId),
811
+ error: null,
812
+ };
813
+ }
814
+ catch (error) {
815
+ const timedOut = isDreamSourceTimeout(error);
816
+ return {
817
+ candidate: null,
818
+ error: {
819
+ threadId: thread.id,
820
+ sourceId,
821
+ durationMs: now() - startedAt,
822
+ timedOut,
823
+ message: timedOut
824
+ ? `Timed out after ${threadTimeoutMs}ms`
825
+ : String(error?.message ?? error),
826
+ },
827
+ };
828
+ }
829
+ });
830
+ const candidates = inspected
831
+ .map((entry) => entry.candidate)
832
+ .filter((entry) => Boolean(entry))
833
+ .filter((entry) => entry.score > 0)
834
+ .sort((a, b) => b.score - a.score || b.thread.updatedAt - a.thread.updatedAt);
835
+ return {
836
+ source: search.source,
837
+ access: search.access,
838
+ query: search.query,
839
+ inspectedThreadCount: search.threads.length,
840
+ candidateCount: candidates.length,
841
+ errors: inspected
842
+ .map((entry) => entry.error)
843
+ .filter((entry) => Boolean(entry)),
844
+ candidates,
845
+ };
846
+ }
847
+ async function scanDreamSource(input, source, timeoutMs) {
848
+ const startedAt = now();
849
+ try {
850
+ const result = await withTimeout(inspectDreamSource(input, source.id, timeoutMs), source.id, timeoutMs);
851
+ const completedAt = now();
852
+ const durationMs = completedAt - startedAt;
853
+ const sourceInfo = result.source ?? {};
854
+ const sourceId = String(sourceInfo.id ?? source.id);
855
+ const label = String(sourceInfo.label ?? source.label ?? source.id);
856
+ return {
857
+ ...result,
858
+ sources: [
859
+ {
860
+ sourceId,
861
+ label,
862
+ status: "ok",
863
+ startedAt,
864
+ completedAt,
865
+ durationMs,
866
+ timeoutMs,
867
+ inspectedThreadCount: result.inspectedThreadCount,
868
+ candidateCount: result.candidateCount,
869
+ errorCount: result.errors.length,
870
+ threadErrorCount: result.errors.length,
871
+ },
872
+ ],
873
+ };
874
+ }
875
+ catch (error) {
876
+ const completedAt = now();
877
+ const durationMs = completedAt - startedAt;
878
+ const timedOut = isDreamSourceTimeout(error);
879
+ const message = timedOut
880
+ ? `Timed out after ${timeoutMs}ms`
881
+ : compactErrorMessage(error);
882
+ const status = timedOut ? "timed_out" : "error";
883
+ return {
884
+ source: {
885
+ id: source.id,
886
+ label: source.label ?? source.id,
887
+ kind: "unknown",
888
+ databaseUrlEnv: null,
889
+ },
890
+ access: null,
891
+ query: input.query?.trim() || null,
892
+ inspectedThreadCount: 0,
893
+ candidateCount: 0,
894
+ errors: [{ sourceId: source.id, message }],
895
+ candidates: [],
896
+ sources: [
897
+ {
898
+ sourceId: source.id,
899
+ label: source.label ?? source.id,
900
+ status,
901
+ startedAt,
902
+ completedAt,
903
+ durationMs,
904
+ timeoutMs,
905
+ inspectedThreadCount: 0,
906
+ candidateCount: 0,
907
+ errorCount: 1,
908
+ threadErrorCount: 0,
909
+ message,
910
+ },
911
+ ],
912
+ };
913
+ }
914
+ }
915
+ export async function listDreamCandidates(input) {
916
+ const timeoutMs = clampSourceTimeoutMs(input.sourceTimeoutMs);
917
+ const sources = await dreamSourceRefs(input);
918
+ const sourceConcurrency = clampSourceConcurrency(input.sourceConcurrency);
919
+ const sourceStartStaggerMs = clampSourceStartStaggerMs(input.sourceStartStaggerMs);
920
+ const scanResults = await mapWithConcurrency(sources, sourceConcurrency, async (source, index) => {
921
+ if (sources.length > 1 && sourceStartStaggerMs > 0) {
922
+ await sleep((index % sourceConcurrency) * sourceStartStaggerMs);
923
+ }
924
+ return scanDreamSource(input, source, timeoutMs);
925
+ });
926
+ const aggregate = wantsAllDreamSources(input) || sources.length > 1;
927
+ const candidates = dedupeDreamCandidates(scanResults.flatMap((result) => result.candidates)).sort((a, b) => b.score - a.score || b.thread.updatedAt - a.thread.updatedAt);
928
+ const sourceHealth = scanResults.flatMap((result) => result.sources);
929
+ return {
930
+ source: aggregate
931
+ ? {
932
+ id: "all",
933
+ label: "All configured sources",
934
+ kind: "aggregate",
935
+ databaseUrlEnv: null,
936
+ }
937
+ : scanResults[0]?.source,
938
+ access: aggregate
939
+ ? { scope: "combined", sourceCount: sources.length }
940
+ : scanResults[0]?.access,
941
+ query: input.query?.trim() || scanResults[0]?.query || null,
942
+ inspectedThreadCount: scanResults.reduce((sum, result) => sum + result.inspectedThreadCount, 0),
943
+ candidateCount: candidates.length,
944
+ errors: scanResults.flatMap((result) => result.errors),
945
+ sources: sourceHealth,
946
+ candidates,
947
+ };
948
+ }
949
+ function describeOwnerScope(scope) {
950
+ return scope.includes("@") ? "another user" : `"${scope}"`;
951
+ }
952
+ function personalMemoryBlockReasonForDream(result, creatorEmail) {
953
+ const otherOwnerCount = new Set(result.candidates
954
+ .map((candidate) => candidate.thread.ownerEmail)
955
+ .filter((owner) => owner && !sameOwnerEmail(owner, creatorEmail))
956
+ .map((owner) => owner.trim().toLowerCase())).size;
957
+ if (otherOwnerCount === 1) {
958
+ return "source evidence includes a thread owned by another user";
959
+ }
960
+ if (otherOwnerCount > 1) {
961
+ return `source evidence includes threads owned by ${otherOwnerCount} other users`;
962
+ }
963
+ const scope = String(result.access?.scope ?? "").trim();
964
+ if (scope && !sameOwnerEmail(scope, creatorEmail)) {
965
+ return `the source owner scope is ${describeOwnerScope(scope)} rather than the dream creator's personal scope`;
966
+ }
967
+ return null;
968
+ }
969
+ function evidenceSummary(evidence, max = 6) {
970
+ return evidence
971
+ .slice(0, max)
972
+ .map((entry) => {
973
+ const title = entry.threadTitle || entry.threadId;
974
+ return `- ${entry.label} in ${title} (${entry.threadId}): ${entry.snippet}`;
975
+ })
976
+ .join("\n");
977
+ }
978
+ function collectCandidateEvidence(candidates, kinds) {
979
+ const allowed = new Set(kinds);
980
+ const evidence = [];
981
+ for (const candidate of candidates) {
982
+ for (const entry of candidate.evidence) {
983
+ if (!allowed.has(entry.kind))
984
+ continue;
985
+ addEvidence(evidence, {
986
+ ...entry,
987
+ sourceId: entry.sourceId ?? candidate.sourceId,
988
+ }, 80);
989
+ }
990
+ }
991
+ return evidence;
992
+ }
993
+ function dedupeDreamCandidates(candidates) {
994
+ const byThread = new Map();
995
+ for (const candidate of candidates) {
996
+ const key = `${candidate.thread.ownerEmail.trim().toLowerCase()}:${candidate.thread.id}`;
997
+ const existing = byThread.get(key);
998
+ if (!existing ||
999
+ candidate.score > existing.score ||
1000
+ (candidate.score === existing.score &&
1001
+ candidate.thread.updatedAt > existing.thread.updatedAt)) {
1002
+ byThread.set(key, candidate);
1003
+ }
1004
+ }
1005
+ return [...byThread.values()];
1006
+ }
1007
+ function uniqueEvidenceThreads(evidence) {
1008
+ return new Set(evidence
1009
+ .map((entry) => entry.threadId?.trim())
1010
+ .filter((threadId) => Boolean(threadId)));
1011
+ }
1012
+ function uniqueEvidenceSources(evidence) {
1013
+ return new Set(evidence
1014
+ .map((entry) => entry.sourceId?.trim())
1015
+ .filter((sourceId) => Boolean(sourceId)));
1016
+ }
1017
+ function evidenceSpansMultipleThreadsOrSources(evidence) {
1018
+ return (uniqueEvidenceThreads(evidence).size >= 2 ||
1019
+ uniqueEvidenceSources(evidence).size >= 2);
1020
+ }
1021
+ function evidenceStrengthCount(evidence) {
1022
+ return Math.max(uniqueEvidenceThreads(evidence).size, uniqueEvidenceSources(evidence).size);
1023
+ }
1024
+ const UI_WORDING_TERMS = /\b(ui|screen|page|view|button|label|copy|wording|modal|dialog|dropdown|menu|toolbar|sidebar|composer|sheet|tab|card|toast|form|field|layout|click|open|close)\b/i;
1025
+ function looksLikeSingleAppUiWordingCorrection(evidence) {
1026
+ if (uniqueEvidenceSources(evidence).size > 1)
1027
+ return false;
1028
+ return evidence.some((entry) => {
1029
+ if (!["explicit-correction", "remember-request"].includes(entry.kind)) {
1030
+ return false;
1031
+ }
1032
+ return UI_WORDING_TERMS.test(entry.snippet);
1033
+ });
1034
+ }
1035
+ const NON_DURABLE_FAILURE_TERMS = /\b(credits[- ]?limit|daily ai credits|current plan|missing[_ -]?credentials|no llm provider|provider key|connect builder\.io|email[_ -]?verification[_ -]?required|verify their email|gateway)\b/i;
1036
+ function isDurableFailureEvidence(entry) {
1037
+ if (entry.kind === "eval-failure")
1038
+ return false;
1039
+ if (entry.kind === "tool-error") {
1040
+ return !NON_DURABLE_FAILURE_TERMS.test(entry.snippet);
1041
+ }
1042
+ if (entry.kind === "failed-run") {
1043
+ const normalized = normalizeEvidenceQuote(entry.snippet);
1044
+ return Boolean(normalized && !["errored", "failed", "aborted"].includes(normalized));
1045
+ }
1046
+ return true;
1047
+ }
1048
+ function proposalFailureEvidence(evidence, max = 10) {
1049
+ const durable = evidence.filter(isDurableFailureEvidence);
1050
+ const supporting = evidence.filter((entry) => entry.kind === "eval-failure");
1051
+ return [...durable, ...supporting].slice(0, max);
1052
+ }
1053
+ function evidenceProvenance(date, evidence) {
1054
+ const threads = [...uniqueEvidenceThreads(evidence)].slice(0, 8).join(", ");
1055
+ const sources = [...uniqueEvidenceSources(evidence)].slice(0, 8).join(", ");
1056
+ return `Provenance: Dispatch dream ${date}; source threads: ${threads || "none recorded"}${sources ? `; sources: ${sources}` : ""}`;
1057
+ }
1058
+ function addSuppressionNote(notes, note) {
1059
+ if (!notes.includes(note))
1060
+ notes.push(note);
1061
+ }
1062
+ function noProposalSuppressionNotes(input) {
1063
+ const notes = [];
1064
+ if (input.explicitEvidence.length > 0) {
1065
+ if (!input.personalMemoryAllowed) {
1066
+ if (!evidenceSpansMultipleThreadsOrSources(input.explicitEvidence)) {
1067
+ addSuppressionNote(notes, `Skipped explicit user-correction proposals because personal memory is blocked (${input.personalMemoryBlockReason}) and the remaining correction evidence did not span multiple threads or source apps.`);
1068
+ }
1069
+ else if (input.explicitEvidence.length < 2) {
1070
+ addSuppressionNote(notes, `Skipped explicit user-correction proposals because personal memory is blocked (${input.personalMemoryBlockReason}) and only one source-backed correction survived filtering.`);
1071
+ }
1072
+ }
1073
+ if (looksLikeSingleAppUiWordingCorrection(input.explicitEvidence)) {
1074
+ addSuppressionNote(notes, "Skipped workspace-instruction promotion for explicit corrections because the evidence looked like single-app UI wording rather than an all-app operating rule.");
1075
+ }
1076
+ }
1077
+ if (input.failureEvidence.length > 0) {
1078
+ if (input.durableFailureEvidence.length === 0) {
1079
+ addSuppressionNote(notes, "Skipped failure proposals because the signals were eval-only noise or non-durable setup, quota, provider, or verification issues.");
1080
+ }
1081
+ else if (input.durableFailureEvidence.length < 2) {
1082
+ addSuppressionNote(notes, "Skipped failure proposals because fewer than two durable failure signals survived filtering.");
1083
+ }
1084
+ else if (!evidenceSpansMultipleThreadsOrSources(input.durableFailureEvidence)) {
1085
+ addSuppressionNote(notes, "Skipped failure proposals because durable failure evidence did not span multiple threads or source apps.");
1086
+ }
1087
+ else if (input.durableFailureEvidence.length < 4) {
1088
+ addSuppressionNote(notes, "Skipped workspace-instruction promotion for failures because fewer than four durable failure signals survived filtering.");
1089
+ }
1090
+ }
1091
+ if (input.successEvidence.length > 0 &&
1092
+ (input.successEvidence.length < 2 ||
1093
+ !evidenceSpansMultipleThreadsOrSources(input.successEvidence))) {
1094
+ addSuppressionNote(notes, "Skipped success-pattern proposals because successful checkpoint evidence did not repeat across multiple threads or source apps.");
1095
+ }
1096
+ return notes;
1097
+ }
1098
+ const STOP_WORDS = new Set([
1099
+ "about",
1100
+ "after",
1101
+ "again",
1102
+ "agent",
1103
+ "always",
1104
+ "because",
1105
+ "before",
1106
+ "being",
1107
+ "could",
1108
+ "dispatch",
1109
+ "doing",
1110
+ "dream",
1111
+ "found",
1112
+ "from",
1113
+ "have",
1114
+ "into",
1115
+ "just",
1116
+ "latest",
1117
+ "memory",
1118
+ "next",
1119
+ "note",
1120
+ "please",
1121
+ "proposal",
1122
+ "recent",
1123
+ "remember",
1124
+ "review",
1125
+ "should",
1126
+ "source",
1127
+ "summary",
1128
+ "that",
1129
+ "their",
1130
+ "there",
1131
+ "these",
1132
+ "thing",
1133
+ "thread",
1134
+ "threads",
1135
+ "user",
1136
+ "using",
1137
+ "with",
1138
+ "would",
1139
+ ]);
1140
+ function emptyMemoryContext() {
1141
+ return {
1142
+ personalIndex: "",
1143
+ personalNotes: [],
1144
+ sharedLearnings: "",
1145
+ workspaceResources: [],
1146
+ };
1147
+ }
1148
+ function normalizeMemoryText(value) {
1149
+ return value
1150
+ .toLowerCase()
1151
+ .replace(/`[^`]*`/g, " ")
1152
+ .replace(/\[[^\]]*\]\([^)]*\)/g, " ")
1153
+ .replace(/https?:\/\/\S+/g, " ")
1154
+ .replace(/[^a-z0-9@._/-]+/g, " ")
1155
+ .replace(/\s+/g, " ")
1156
+ .trim();
1157
+ }
1158
+ function tokenSet(value) {
1159
+ const tokens = normalizeMemoryText(value)
1160
+ .split(" ")
1161
+ .filter((token) => token.length >= 4 && !STOP_WORDS.has(token));
1162
+ return new Set(tokens);
1163
+ }
1164
+ function containmentScore(needle, haystack) {
1165
+ const needleTokens = tokenSet(needle);
1166
+ if (needleTokens.size === 0)
1167
+ return 0;
1168
+ const haystackTokens = tokenSet(haystack);
1169
+ let matches = 0;
1170
+ for (const token of needleTokens) {
1171
+ if (haystackTokens.has(token))
1172
+ matches += 1;
1173
+ }
1174
+ return matches / needleTokens.size;
1175
+ }
1176
+ function hasExactSourceMatch(evidence, content) {
1177
+ const normalized = normalizeMemoryText(content);
1178
+ return evidence.some((entry) => {
1179
+ if (entry.threadId && normalized.includes(entry.threadId.toLowerCase())) {
1180
+ return true;
1181
+ }
1182
+ if (entry.runId && normalized.includes(entry.runId.toLowerCase())) {
1183
+ return true;
1184
+ }
1185
+ return false;
1186
+ });
1187
+ }
1188
+ function evidenceSignalText(evidence) {
1189
+ return evidence.map((entry) => entry.snippet).join("\n");
1190
+ }
1191
+ function hasCorrectionLanguage(evidence) {
1192
+ return evidence.some((entry) => {
1193
+ if (entry.kind !== "explicit-correction")
1194
+ return false;
1195
+ const text = entry.snippet.toLowerCase();
1196
+ return /\b(actually|instead|don't|do not|never|wrong|not what i meant|from now on|you should)\b/.test(text);
1197
+ });
1198
+ }
1199
+ function findPersonalMemoryMatch(memoryContext, evidence) {
1200
+ const signal = evidenceSignalText(evidence);
1201
+ let best = null;
1202
+ for (const note of memoryContext.personalNotes) {
1203
+ const exactSource = hasExactSourceMatch(evidence, note.content);
1204
+ const score = exactSource ? 1 : containmentScore(signal, note.content);
1205
+ if (!best || score > best.score) {
1206
+ best = { note, score, exactSource };
1207
+ }
1208
+ }
1209
+ if (!best)
1210
+ return null;
1211
+ if (best.exactSource || best.score >= 0.42)
1212
+ return best;
1213
+ return null;
1214
+ }
1215
+ function sharedLearningLooksCaptured(memoryContext, evidence) {
1216
+ if (!memoryContext.sharedLearnings.trim())
1217
+ return false;
1218
+ if (hasExactSourceMatch(evidence, memoryContext.sharedLearnings))
1219
+ return true;
1220
+ return (containmentScore(evidenceSignalText(evidence), memoryContext.sharedLearnings) >= 0.58);
1221
+ }
1222
+ function workspaceResourceLooksCaptured(memoryContext, evidence) {
1223
+ const resources = memoryContext.workspaceResources ?? [];
1224
+ if (resources.length === 0)
1225
+ return false;
1226
+ return resources.some((resource) => {
1227
+ if (hasExactSourceMatch(evidence, resource.content))
1228
+ return true;
1229
+ return (containmentScore(evidenceSignalText(evidence), resource.content) >= 0.62);
1230
+ });
1231
+ }
1232
+ function personalIndexLooksCaptured(memoryContext, evidence) {
1233
+ if (!memoryContext.personalIndex.trim())
1234
+ return false;
1235
+ if (hasExactSourceMatch(evidence, memoryContext.personalIndex))
1236
+ return true;
1237
+ return (containmentScore(evidenceSignalText(evidence), memoryContext.personalIndex) >= 0.72);
1238
+ }
1239
+ function proposalLooksCaptured(proposal, memoryContext) {
1240
+ if (proposal.targetType === "shared-learnings") {
1241
+ return sharedLearningLooksCaptured(memoryContext, proposal.evidence);
1242
+ }
1243
+ if (proposal.targetType.startsWith("workspace-")) {
1244
+ return workspaceResourceLooksCaptured(memoryContext, proposal.evidence);
1245
+ }
1246
+ if (personalIndexLooksCaptured(memoryContext, proposal.evidence)) {
1247
+ return true;
1248
+ }
1249
+ const match = findPersonalMemoryMatch(memoryContext, proposal.evidence);
1250
+ return Boolean(match?.exactSource || (match && match.score >= 0.72));
1251
+ }
1252
+ async function loadDreamMemoryContext(owner) {
1253
+ const [index, shared, personalMetas, workspaceResources] = await Promise.all([
1254
+ resourceGetByPath(owner, MEMORY_INDEX_PATH),
1255
+ resourceGetByPath(SHARED_OWNER, "LEARNINGS.md"),
1256
+ resourceList(owner, "memory/").catch(() => []),
1257
+ listWorkspaceResources().catch(() => []),
1258
+ ]);
1259
+ const paths = personalMetas
1260
+ .map((entry) => entry.path)
1261
+ .filter((path) => path !== MEMORY_INDEX_PATH && path.endsWith(".md"))
1262
+ .slice(0, 30);
1263
+ const personalNotes = (await Promise.all(paths.map(async (path) => {
1264
+ const resource = await resourceGetByPath(owner, path).catch(() => null);
1265
+ if (!resource?.content)
1266
+ return null;
1267
+ return { path, content: resource.content };
1268
+ }))).filter((entry) => Boolean(entry));
1269
+ return {
1270
+ personalIndex: index?.content ?? "",
1271
+ personalNotes,
1272
+ sharedLearnings: shared?.content ?? "",
1273
+ workspaceResources: workspaceResources.map((resource) => ({
1274
+ path: resource.path,
1275
+ content: resource.content,
1276
+ })),
1277
+ };
1278
+ }
1279
+ function applyMemoryGuardrails(proposals, memoryContext) {
1280
+ const guarded = [];
1281
+ const guardrailNotes = [];
1282
+ for (const proposal of proposals) {
1283
+ if (proposal.targetType === "personal-memory" &&
1284
+ hasCorrectionLanguage(proposal.evidence)) {
1285
+ const match = findPersonalMemoryMatch(memoryContext, proposal.evidence);
1286
+ if (match?.exactSource) {
1287
+ guardrailNotes.push(`Skipped duplicate proposal "${proposal.title}" because existing memory already appears to capture the source evidence.`);
1288
+ continue;
1289
+ }
1290
+ if (match && match.score >= 0.42) {
1291
+ guarded.push({
1292
+ ...proposal,
1293
+ targetPath: match.note.path,
1294
+ title: "Update existing memory from recent corrections",
1295
+ summary: `Existing memory at ${match.note.path} may be stale; update it with the latest explicit correction evidence.`,
1296
+ rationale: `${proposal.rationale} A related personal memory already exists, so the dream should update it instead of creating a parallel note.`,
1297
+ content: [
1298
+ "# Dispatch Dream Memory Update",
1299
+ "",
1300
+ `Existing memory: ${match.note.path}`,
1301
+ "",
1302
+ "Recent agent runs contained newer explicit user-grounded lessons:",
1303
+ "",
1304
+ evidenceSummary(proposal.evidence, 10),
1305
+ ].join("\n"),
1306
+ });
1307
+ guardrailNotes.push(`Retargeted proposal "${proposal.title}" to existing memory ${match.note.path} to avoid creating a stale duplicate.`);
1308
+ continue;
1309
+ }
1310
+ }
1311
+ if (proposalLooksCaptured(proposal, memoryContext)) {
1312
+ guardrailNotes.push(`Skipped duplicate proposal "${proposal.title}" because existing memory already appears to capture the source evidence.`);
1313
+ continue;
1314
+ }
1315
+ guarded.push(proposal);
1316
+ }
1317
+ return { proposals: guarded, guardrailNotes };
1318
+ }
1319
+ function makeReport(input) {
1320
+ const completedSourceCount = input.sourceHealth?.filter((source) => source.status === "ok").length ?? 0;
1321
+ const lines = [
1322
+ `# ${input.title}`,
1323
+ "",
1324
+ `Generated: ${new Date().toISOString()}`,
1325
+ `Source: ${input.sourceId}`,
1326
+ `Query: ${input.query || "(recent threads)"}`,
1327
+ `Inspected threads: ${input.inspectedThreadCount}`,
1328
+ `Candidates: ${input.candidates.length}`,
1329
+ `Proposals: ${input.proposals.length}`,
1330
+ ];
1331
+ if (input.sourceHealth && input.sourceHealth.length > 0) {
1332
+ lines.push(`Sources completed: ${completedSourceCount}/${input.sourceHealth.length}`);
1333
+ lines.push("", "## Source Health");
1334
+ for (const source of input.sourceHealth) {
1335
+ const label = source.label || source.sourceId;
1336
+ const message = source.message
1337
+ ? ` — ${compactText(source.message, 160)}`
1338
+ : "";
1339
+ lines.push(`- ${label} (${source.sourceId}): ${source.status} in ${source.durationMs}ms, ${source.inspectedThreadCount} inspected, ${source.candidateCount} candidates, ${source.errorCount} errors${message}`);
1340
+ }
1341
+ }
1342
+ lines.push("", "## Candidate Signals");
1343
+ if (input.candidates.length === 0) {
1344
+ lines.push("", "No dream-worthy signals were found in this pass.");
1345
+ }
1346
+ else {
1347
+ for (const candidate of input.candidates.slice(0, 12)) {
1348
+ lines.push("", `### ${candidate.thread.title || candidate.thread.id}`, "", `- Thread: ${candidate.thread.id}`, `- Score: ${candidate.score}`, `- Latest run status: ${candidate.latestRunStatus || "unknown"}`, `- Reasons: ${candidate.reasons.map((entry) => entry.label).join("; ")}`, "", "Evidence:", evidenceSummary(candidate.evidence, 4) ||
1349
+ "- No compact evidence available.");
1350
+ }
1351
+ }
1352
+ if (input.guardrailNotes && input.guardrailNotes.length > 0) {
1353
+ lines.push("", "## Proposal Guardrails");
1354
+ for (const note of input.guardrailNotes) {
1355
+ lines.push("", `- ${note}`);
1356
+ }
1357
+ }
1358
+ lines.push("", "## Proposals");
1359
+ if (input.proposals.length === 0) {
1360
+ lines.push("", "No durable changes were proposed.");
1361
+ }
1362
+ else {
1363
+ for (const proposal of input.proposals) {
1364
+ lines.push("", `### ${proposal.title}`, "", `- Target: ${proposal.targetType} at ${proposal.targetPath}`, `- Confidence: ${proposal.confidence}`, `- Risk: ${proposal.risk}`, `- Summary: ${proposal.summary}`, "", "Evidence:", evidenceSummary(proposal.evidence, 5));
1365
+ }
1366
+ }
1367
+ return lines.join("\n").trimEnd() + "\n";
1368
+ }
1369
+ export function buildProposalInputs(candidates, memoryContext = emptyMemoryContext(), options = {}) {
1370
+ const proposals = [];
1371
+ const personalMemoryAllowed = options.personalMemoryAllowed ?? true;
1372
+ const personalMemoryBlockReason = options.personalMemoryBlockReason?.trim() ||
1373
+ "source owner scope differs from the dream creator's personal scope";
1374
+ let routedPersonalMemory = false;
1375
+ const explicitEvidence = collectCandidateEvidence(candidates, [
1376
+ "remember-request",
1377
+ "explicit-correction",
1378
+ ]);
1379
+ const failureEvidence = collectCandidateEvidence(candidates, [
1380
+ "failed-run",
1381
+ "tool-error",
1382
+ "negative-feedback",
1383
+ "eval-failure",
1384
+ "low-satisfaction",
1385
+ "frustration",
1386
+ ]);
1387
+ const durableFailureEvidence = failureEvidence.filter(isDurableFailureEvidence);
1388
+ const failureProposalEvidence = proposalFailureEvidence(failureEvidence, 10);
1389
+ const successEvidence = collectCandidateEvidence(candidates, [
1390
+ "verified-success",
1391
+ ]);
1392
+ const date = today();
1393
+ const explicitStrengthCount = evidenceStrengthCount(explicitEvidence);
1394
+ const durableFailureStrengthCount = evidenceStrengthCount(durableFailureEvidence);
1395
+ const successStrengthCount = evidenceStrengthCount(successEvidence);
1396
+ if (explicitEvidence.length > 0) {
1397
+ const title = `Dispatch dream memory ${date}`;
1398
+ if (personalMemoryAllowed) {
1399
+ proposals.push({
1400
+ targetType: "personal-memory",
1401
+ targetPath: `memory/${slugify(title)}.md`,
1402
+ title: "Save explicit user corrections from recent agent runs",
1403
+ summary: "Recent threads contain explicit user corrections or remember requests that may be worth preserving as personal memory.",
1404
+ rationale: "Explicit user corrections and remember requests are high-signal, user-grounded evidence for personal memory.",
1405
+ content: [
1406
+ "# Dispatch Dream Memory",
1407
+ "",
1408
+ "Recent agent runs contained explicit user-grounded lessons:",
1409
+ "",
1410
+ evidenceSummary(explicitEvidence, 10),
1411
+ ].join("\n"),
1412
+ evidence: explicitEvidence.slice(0, 10),
1413
+ confidence: Math.min(95, 70 + explicitEvidence.length * 5),
1414
+ risk: "low",
1415
+ });
1416
+ }
1417
+ else if (explicitEvidence.length >= 2 &&
1418
+ evidenceSpansMultipleThreadsOrSources(explicitEvidence)) {
1419
+ routedPersonalMemory = true;
1420
+ proposals.push({
1421
+ targetType: "shared-learnings",
1422
+ targetPath: "LEARNINGS.md",
1423
+ title: "Review explicit user corrections as shared learnings",
1424
+ summary: "Recent admin-visible threads contain explicit user corrections or remember requests that should stay reviewable as shared learnings instead of personal memory.",
1425
+ rationale: `Personal memory is blocked because ${personalMemoryBlockReason}. Shared learnings keep the evidence visible for review without writing another user's snippets into the dream creator's memory.`,
1426
+ content: [
1427
+ "Recent Dispatch dream review found explicit user-grounded lessons in admin-visible threads.",
1428
+ "",
1429
+ `Provenance: Personal memory was disabled for this proposal because ${personalMemoryBlockReason}.`,
1430
+ "",
1431
+ evidenceSummary(explicitEvidence, 10),
1432
+ ].join("\n"),
1433
+ evidence: explicitEvidence.slice(0, 10),
1434
+ confidence: Math.min(95, 70 + explicitEvidence.length * 5),
1435
+ risk: "medium",
1436
+ });
1437
+ }
1438
+ }
1439
+ if (durableFailureEvidence.length >= 2 &&
1440
+ evidenceSpansMultipleThreadsOrSources(durableFailureEvidence)) {
1441
+ proposals.push({
1442
+ targetType: "shared-learnings",
1443
+ targetPath: "LEARNINGS.md",
1444
+ title: "Record recurring Dispatch agent-run failure patterns",
1445
+ summary: "Multiple recent threads or source apps show failure, tool-error, eval, or satisfaction signals that should be reviewed as a team learning.",
1446
+ rationale: "Repeated grounded failure signals across more than one thread or source app are good candidates for shared learnings, but remain pending for review.",
1447
+ content: [
1448
+ "Recent Dispatch dream review found recurring agent-run failure signals.",
1449
+ "",
1450
+ evidenceSummary(failureProposalEvidence, 8),
1451
+ ].join("\n"),
1452
+ evidence: failureProposalEvidence.slice(0, 8),
1453
+ confidence: Math.min(85, 50 + durableFailureStrengthCount * 10),
1454
+ risk: "medium",
1455
+ });
1456
+ }
1457
+ if (explicitEvidence.length >= 3 &&
1458
+ evidenceSpansMultipleThreadsOrSources(explicitEvidence) &&
1459
+ !looksLikeSingleAppUiWordingCorrection(explicitEvidence)) {
1460
+ proposals.push({
1461
+ targetType: "workspace-instruction",
1462
+ targetPath: `instructions/${slugify(`dream-corrections-${date}`)}.md`,
1463
+ title: "Create workspace instruction from repeated user corrections",
1464
+ summary: "Repeated explicit user corrections or remember requests should become an all-app workspace instruction after review.",
1465
+ rationale: "Repeated corrections across more than one thread or source app are stronger than one-off memory. A workspace instruction lets every app inherit the reviewed behavior.",
1466
+ content: [
1467
+ "# Dream-Derived Workspace Instruction",
1468
+ "",
1469
+ "Apply this only after reviewing the cited source threads.",
1470
+ "",
1471
+ "Recent agent runs contained repeated user-grounded corrections or remember requests:",
1472
+ "",
1473
+ evidenceSummary(explicitEvidence, 10),
1474
+ "",
1475
+ "Instruction draft:",
1476
+ "",
1477
+ "- Preserve explicit user corrections as durable behavior only when the evidence is user-authored and source-backed.",
1478
+ "- Prefer existing workspace resources, skills, and app conventions before introducing a new pattern.",
1479
+ "- If the same correction appears across apps or threads, treat it as a candidate for a workspace-level instruction instead of only personal memory.",
1480
+ "",
1481
+ evidenceProvenance(date, explicitEvidence),
1482
+ ].join("\n"),
1483
+ evidence: explicitEvidence.slice(0, 10),
1484
+ confidence: Math.min(90, 60 + explicitStrengthCount * 8),
1485
+ risk: "medium",
1486
+ });
1487
+ }
1488
+ if (durableFailureEvidence.length >= 4 &&
1489
+ evidenceSpansMultipleThreadsOrSources(durableFailureEvidence)) {
1490
+ proposals.push({
1491
+ targetType: "workspace-instruction",
1492
+ targetPath: `instructions/${slugify(`dream-run-reliability-${date}`)}.md`,
1493
+ title: "Create workspace instruction for recurring agent-run friction",
1494
+ summary: "Recurring frustration and failure signals across threads or source apps should become a reviewed workspace reliability instruction.",
1495
+ rationale: "Repeated failure or frustration signals across multiple threads or source apps are useful as a shared guardrail, but they need review before they affect all app agents.",
1496
+ content: [
1497
+ "# Dream-Derived Agent Reliability Instruction",
1498
+ "",
1499
+ "Apply this only after reviewing the cited source threads.",
1500
+ "",
1501
+ "Recent agent runs contained recurring friction or failure signals:",
1502
+ "",
1503
+ evidenceSummary(failureProposalEvidence, 10),
1504
+ "",
1505
+ "Instruction draft:",
1506
+ "",
1507
+ "- When a task repeats a prior failure mode, pause to inspect source context, previous attempts, and existing docs before changing code or data.",
1508
+ "- Prefer small, verified changes with provenance over broad rewrites when responding to user frustration.",
1509
+ "- Convert repeated failures into a durable workspace resource only after evidence spans multiple threads or apps.",
1510
+ "",
1511
+ evidenceProvenance(date, failureProposalEvidence),
1512
+ ].join("\n"),
1513
+ evidence: failureProposalEvidence.slice(0, 10),
1514
+ confidence: Math.min(85, 50 + durableFailureStrengthCount * 8),
1515
+ risk: "medium",
1516
+ });
1517
+ }
1518
+ if (proposals.length === 0 &&
1519
+ successEvidence.length >= 2 &&
1520
+ evidenceSpansMultipleThreadsOrSources(successEvidence)) {
1521
+ if (personalMemoryAllowed) {
1522
+ proposals.push({
1523
+ targetType: "personal-memory",
1524
+ targetPath: `memory/${slugify(`dispatch-success-patterns-${date}`)}.md`,
1525
+ title: "Preserve successful Dispatch workflow patterns",
1526
+ summary: "Recent successful checkpointed runs may contain reusable workflow patterns.",
1527
+ rationale: "Checkpointed successful runs are lower-risk candidates for personal memory when no correction or failure proposals are present.",
1528
+ content: [
1529
+ "# Successful Dispatch Patterns",
1530
+ "",
1531
+ "Recent checkpointed runs worth reviewing:",
1532
+ "",
1533
+ evidenceSummary(successEvidence, 8),
1534
+ ].join("\n"),
1535
+ evidence: successEvidence.slice(0, 8),
1536
+ confidence: 60,
1537
+ risk: "low",
1538
+ });
1539
+ }
1540
+ else {
1541
+ routedPersonalMemory = true;
1542
+ proposals.push({
1543
+ targetType: "shared-learnings",
1544
+ targetPath: "LEARNINGS.md",
1545
+ title: "Review successful Dispatch workflow patterns",
1546
+ summary: "Recent successful checkpointed runs may contain reusable workflow patterns, but the source scope is not personal to the dream creator.",
1547
+ rationale: `Personal memory is blocked because ${personalMemoryBlockReason}. Shared learnings keep the cross-owner workflow evidence reviewable.`,
1548
+ content: [
1549
+ "Recent Dispatch dream review found checkpointed runs worth reviewing.",
1550
+ "",
1551
+ `Provenance: Personal memory was disabled for this proposal because ${personalMemoryBlockReason}.`,
1552
+ "",
1553
+ evidenceSummary(successEvidence, 8),
1554
+ ].join("\n"),
1555
+ evidence: successEvidence.slice(0, 8),
1556
+ confidence: 60,
1557
+ risk: "medium",
1558
+ });
1559
+ }
1560
+ }
1561
+ if (successEvidence.length >= 2 &&
1562
+ evidenceSpansMultipleThreadsOrSources(successEvidence)) {
1563
+ proposals.push({
1564
+ targetType: "workspace-skill",
1565
+ targetPath: `skills/${slugify(`dispatch-success-patterns-${date}`)}/SKILL.md`,
1566
+ title: "Create workspace skill from successful Dispatch patterns",
1567
+ summary: "Successful checkpointed workflows across multiple threads may deserve a reusable all-app skill.",
1568
+ rationale: "Repeated successful, checkpointed workflows are candidates for reusable skills when they can be stated as a repeatable procedure and reviewed by a human.",
1569
+ content: [
1570
+ "---",
1571
+ `name: ${slugify(`dispatch-success-patterns-${date}`)}`,
1572
+ "description: >-",
1573
+ " Use when a task resembles recent successful Dispatch workflows and the agent needs a reviewed repeatable procedure.",
1574
+ "---",
1575
+ "",
1576
+ "# Dispatch Success Patterns",
1577
+ "",
1578
+ "This skill was drafted from a Dispatch dream report. Review and tighten it before applying broadly.",
1579
+ "",
1580
+ "## Source Evidence",
1581
+ "",
1582
+ evidenceSummary(successEvidence, 10),
1583
+ "",
1584
+ "## Procedure Draft",
1585
+ "",
1586
+ "1. Identify the source thread or workflow that most closely matches the current task.",
1587
+ "2. Reuse the verified sequence of actions only after checking current app state and available actions.",
1588
+ "3. Run the smallest relevant verification step before presenting the result.",
1589
+ "4. Record any user correction as a new dream candidate rather than silently broadening the skill.",
1590
+ ].join("\n"),
1591
+ evidence: successEvidence.slice(0, 10),
1592
+ confidence: Math.min(80, 50 + successStrengthCount * 10),
1593
+ risk: "medium",
1594
+ });
1595
+ }
1596
+ const suppressionNotes = noProposalSuppressionNotes({
1597
+ explicitEvidence,
1598
+ failureEvidence,
1599
+ durableFailureEvidence,
1600
+ successEvidence,
1601
+ personalMemoryAllowed,
1602
+ personalMemoryBlockReason,
1603
+ });
1604
+ const result = applyMemoryGuardrails(proposals, memoryContext);
1605
+ if (routedPersonalMemory) {
1606
+ result.guardrailNotes.unshift(`Routed personal-memory dream proposals to shared learnings because ${personalMemoryBlockReason}.`);
1607
+ }
1608
+ if (result.proposals.length === 0) {
1609
+ for (const note of suppressionNotes) {
1610
+ addSuppressionNote(result.guardrailNotes, note);
1611
+ }
1612
+ }
1613
+ return result;
1614
+ }
1615
+ function summarizeDream(candidates, proposals, sourceHealth = []) {
1616
+ const failedSources = sourceHealth.filter((source) => source.status !== "ok");
1617
+ const sourcePrefix = sourceHealth.length > 1
1618
+ ? `${sourceHealth.length - failedSources.length}/${sourceHealth.length} sources completed${failedSources.length > 0 ? ` with ${failedSources.length} partial` : ""}. `
1619
+ : "";
1620
+ if (candidates.length === 0) {
1621
+ return `${sourcePrefix}No dream-worthy signals were found in the inspected threads.`;
1622
+ }
1623
+ const topReason = candidates[0]?.reasons[0]?.label ?? "agent-run signals";
1624
+ return `${sourcePrefix}Reviewed ${candidates.length} candidate thread(s), led by ${topReason}. Created ${proposals} proposal(s).`;
1625
+ }
1626
+ function serializeProposal(row) {
1627
+ return {
1628
+ ...row,
1629
+ evidence: safeJsonParse(row.evidence, []),
1630
+ };
1631
+ }
1632
+ function serializeDream(row) {
1633
+ return {
1634
+ ...row,
1635
+ sourceHealth: safeJsonParse(row.sourceHealth, []),
1636
+ };
1637
+ }
1638
+ async function getDreamRow(dreamId, ctx = { ownerEmail: currentOwnerEmail(), orgId: currentOrgId() }) {
1639
+ const db = getDb();
1640
+ const [row] = await db
1641
+ .select()
1642
+ .from(schema.dispatchDreams)
1643
+ .where(and(eq(schema.dispatchDreams.id, dreamId), scopeFor(schema.dispatchDreams, ctx)))
1644
+ .limit(1);
1645
+ return row ?? null;
1646
+ }
1647
+ async function getProposalRow(proposalId, ctx = { ownerEmail: currentOwnerEmail(), orgId: currentOrgId() }) {
1648
+ const db = getDb();
1649
+ const [row] = await db
1650
+ .select()
1651
+ .from(schema.dispatchDreamProposals)
1652
+ .where(and(eq(schema.dispatchDreamProposals.id, proposalId), scopeFor(schema.dispatchDreamProposals, ctx)))
1653
+ .limit(1);
1654
+ return row ?? null;
1655
+ }
1656
+ export async function createDreamReport(input) {
1657
+ const db = getDb();
1658
+ const timestamp = now();
1659
+ const ownerEmail = currentOwnerEmail();
1660
+ const orgId = currentOrgId();
1661
+ const dreamId = id();
1662
+ const allSources = wantsAllDreamSources(input) || (input.sourceIds?.length ?? 0) > 0;
1663
+ const sourceId = allSources ? "all" : input.sourceId?.trim() || "current";
1664
+ const query = input.query?.trim() || null;
1665
+ const title = input.title?.trim() || `Dispatch dream ${today()}`;
1666
+ await db.insert(schema.dispatchDreams).values({
1667
+ id: dreamId,
1668
+ ownerEmail,
1669
+ orgId,
1670
+ sourceId,
1671
+ title,
1672
+ status: "running",
1673
+ query,
1674
+ report: null,
1675
+ summary: null,
1676
+ sourceHealth: null,
1677
+ candidateCount: 0,
1678
+ inspectedThreadCount: 0,
1679
+ createdBy: ownerEmail,
1680
+ error: null,
1681
+ startedAt: timestamp,
1682
+ completedAt: null,
1683
+ createdAt: timestamp,
1684
+ updatedAt: timestamp,
1685
+ });
1686
+ try {
1687
+ const result = await listDreamCandidates({
1688
+ sourceId,
1689
+ sourceIds: input.sourceIds,
1690
+ allSources,
1691
+ query: input.query,
1692
+ ownerEmail: input.ownerEmail,
1693
+ limit: input.limit,
1694
+ sourceTimeoutMs: input.sourceTimeoutMs,
1695
+ sourceConcurrency: input.sourceConcurrency,
1696
+ sourceStartStaggerMs: input.sourceStartStaggerMs,
1697
+ threadConcurrency: input.threadConcurrency,
1698
+ threadTimeoutMs: input.threadTimeoutMs,
1699
+ });
1700
+ const memoryContext = await loadDreamMemoryContext(ownerEmail).catch(() => emptyMemoryContext());
1701
+ const personalMemoryBlockReason = personalMemoryBlockReasonForDream(result, ownerEmail);
1702
+ const proposalBuild = buildProposalInputs(result.candidates, memoryContext, {
1703
+ personalMemoryAllowed: personalMemoryBlockReason == null,
1704
+ personalMemoryBlockReason,
1705
+ });
1706
+ const proposalInputs = proposalBuild.proposals;
1707
+ const report = makeReport({
1708
+ title,
1709
+ sourceId,
1710
+ query,
1711
+ candidates: result.candidates,
1712
+ inspectedThreadCount: result.inspectedThreadCount,
1713
+ proposals: proposalInputs,
1714
+ guardrailNotes: proposalBuild.guardrailNotes,
1715
+ sourceHealth: result.sources,
1716
+ });
1717
+ const summary = summarizeDream(result.candidates, proposalInputs.length, result.sources);
1718
+ const completedAt = now();
1719
+ if (proposalInputs.length > 0) {
1720
+ await db.insert(schema.dispatchDreamProposals).values(proposalInputs.map((proposal) => ({
1721
+ id: id(),
1722
+ dreamId,
1723
+ ownerEmail,
1724
+ orgId,
1725
+ targetType: proposal.targetType,
1726
+ targetPath: proposal.targetPath,
1727
+ title: proposal.title,
1728
+ summary: proposal.summary,
1729
+ rationale: proposal.rationale,
1730
+ content: proposal.content,
1731
+ evidence: safeJson(proposal.evidence),
1732
+ confidence: proposal.confidence,
1733
+ risk: proposal.risk,
1734
+ status: "pending",
1735
+ appliedBy: null,
1736
+ appliedAt: null,
1737
+ rejectedBy: null,
1738
+ rejectedAt: null,
1739
+ createdAt: completedAt,
1740
+ updatedAt: completedAt,
1741
+ })));
1742
+ }
1743
+ await db
1744
+ .update(schema.dispatchDreams)
1745
+ .set({
1746
+ status: "completed",
1747
+ report,
1748
+ summary,
1749
+ sourceHealth: safeJson(result.sources),
1750
+ candidateCount: result.candidateCount,
1751
+ inspectedThreadCount: result.inspectedThreadCount,
1752
+ completedAt,
1753
+ updatedAt: completedAt,
1754
+ })
1755
+ .where(eq(schema.dispatchDreams.id, dreamId));
1756
+ await recordAudit({
1757
+ action: "dream.created",
1758
+ targetType: "dream",
1759
+ targetId: dreamId,
1760
+ summary,
1761
+ metadata: {
1762
+ sourceId,
1763
+ query,
1764
+ candidates: result.candidateCount,
1765
+ inspectedThreads: result.inspectedThreadCount,
1766
+ proposals: proposalInputs.length,
1767
+ sources: result.sources,
1768
+ personalMemoryBlocked: personalMemoryBlockReason,
1769
+ },
1770
+ });
1771
+ return getDream(dreamId);
1772
+ }
1773
+ catch (error) {
1774
+ const failedAt = now();
1775
+ const message = String(error?.message ?? error);
1776
+ await db
1777
+ .update(schema.dispatchDreams)
1778
+ .set({
1779
+ status: "failed",
1780
+ error: message,
1781
+ completedAt: failedAt,
1782
+ updatedAt: failedAt,
1783
+ })
1784
+ .where(eq(schema.dispatchDreams.id, dreamId));
1785
+ await recordAudit({
1786
+ action: "dream.failed",
1787
+ targetType: "dream",
1788
+ targetId: dreamId,
1789
+ summary: `Dream report failed: ${message}`,
1790
+ metadata: { sourceId, query },
1791
+ });
1792
+ throw error;
1793
+ }
1794
+ }
1795
+ export async function listDreams(input = {}) {
1796
+ const db = getDb();
1797
+ const ctx = { ownerEmail: currentOwnerEmail(), orgId: currentOrgId() };
1798
+ const limit = Math.max(1, Math.min(100, input.limit ?? 25));
1799
+ const filters = [scopeFor(schema.dispatchDreams, ctx)];
1800
+ if (input.status && input.status !== "all") {
1801
+ filters.push(eq(schema.dispatchDreams.status, input.status));
1802
+ }
1803
+ const dreams = await db
1804
+ .select()
1805
+ .from(schema.dispatchDreams)
1806
+ .where(and(...filters))
1807
+ .orderBy(desc(schema.dispatchDreams.updatedAt))
1808
+ .limit(limit);
1809
+ const dreamIds = dreams.map((dream) => dream.id);
1810
+ const proposals = dreamIds.length === 0
1811
+ ? []
1812
+ : await db
1813
+ .select()
1814
+ .from(schema.dispatchDreamProposals)
1815
+ .where(inArray(schema.dispatchDreamProposals.dreamId, dreamIds));
1816
+ const counts = new Map();
1817
+ for (const proposal of proposals) {
1818
+ const existing = counts.get(proposal.dreamId) ?? {};
1819
+ existing[proposal.status] = (existing[proposal.status] ?? 0) + 1;
1820
+ existing.total = (existing.total ?? 0) + 1;
1821
+ counts.set(proposal.dreamId, existing);
1822
+ }
1823
+ return {
1824
+ count: dreams.length,
1825
+ dreams: dreams.map((dream) => ({
1826
+ ...serializeDream(dream),
1827
+ proposalCounts: counts.get(dream.id) ?? { total: 0 },
1828
+ })),
1829
+ };
1830
+ }
1831
+ export async function getDream(dreamId) {
1832
+ const db = getDb();
1833
+ const dream = await getDreamRow(dreamId);
1834
+ if (!dream)
1835
+ throw new Error("Dream not found");
1836
+ const proposals = await db
1837
+ .select()
1838
+ .from(schema.dispatchDreamProposals)
1839
+ .where(eq(schema.dispatchDreamProposals.dreamId, dream.id))
1840
+ .orderBy(desc(schema.dispatchDreamProposals.createdAt));
1841
+ return {
1842
+ dream: serializeDream(dream),
1843
+ proposals: proposals.map(serializeProposal),
1844
+ };
1845
+ }
1846
+ async function savePersonalMemory(proposal) {
1847
+ if (proposal.targetType !== "personal-memory") {
1848
+ throw new Error("Proposal is not a personal-memory proposal");
1849
+ }
1850
+ const owner = proposal.ownerEmail;
1851
+ if (owner !== currentOwnerEmail()) {
1852
+ throw new Error("Personal memory proposals can only be applied by owner");
1853
+ }
1854
+ const targetPath = personalMemoryTargetPath(proposal);
1855
+ if (targetPath === MEMORY_INDEX_PATH || !targetPath.endsWith(".md")) {
1856
+ throw new Error("Personal memory proposals must target memory/<name>.md");
1857
+ }
1858
+ const name = targetPath.replace(/^memory\//, "").replace(/\.md$/, "");
1859
+ const date = today();
1860
+ const description = compactText(proposal.summary, 140);
1861
+ const evidence = safeJsonParse(proposal.evidence, []);
1862
+ const fileContent = [
1863
+ "---",
1864
+ "type: feedback",
1865
+ `description: ${JSON.stringify(description.replace(/\n/g, " "))}`,
1866
+ `updated: ${date}`,
1867
+ "---",
1868
+ "",
1869
+ proposal.content.trim(),
1870
+ "",
1871
+ "## Provenance",
1872
+ "",
1873
+ `Dream: ${proposal.dreamId}`,
1874
+ `Proposal: ${proposal.id}`,
1875
+ "",
1876
+ evidenceSummary(evidence, 10),
1877
+ "",
1878
+ ].join("\n");
1879
+ await resourcePut(owner, targetPath, fileContent, "text/markdown", {
1880
+ createdBy: "agent",
1881
+ metadata: { dreamId: proposal.dreamId, proposalId: proposal.id },
1882
+ });
1883
+ const existingIndex = await resourceGetByPath(owner, MEMORY_INDEX_PATH);
1884
+ const index = existingIndex?.content ?? "# Memory Index\n";
1885
+ const entryLine = `- [${name}](${name}.md) — ${description}`;
1886
+ const entryPrefix = `- [${name}]`;
1887
+ let found = false;
1888
+ const lines = index.split("\n").map((line) => {
1889
+ if (line.startsWith(entryPrefix)) {
1890
+ found = true;
1891
+ return entryLine;
1892
+ }
1893
+ return line;
1894
+ });
1895
+ if (!found)
1896
+ lines.push(entryLine);
1897
+ const updatedIndex = lines.join("\n").trimEnd() + "\n";
1898
+ await resourcePut(owner, MEMORY_INDEX_PATH, updatedIndex, "text/markdown", {
1899
+ createdBy: "agent",
1900
+ metadata: { dreamId: proposal.dreamId, proposalId: proposal.id },
1901
+ });
1902
+ return {
1903
+ resourcePath: targetPath,
1904
+ indexPath: MEMORY_INDEX_PATH,
1905
+ indexLineCount: updatedIndex.split("\n").length,
1906
+ };
1907
+ }
1908
+ async function appendSharedLearning(proposal) {
1909
+ if (proposal.targetType !== "shared-learnings") {
1910
+ throw new Error("Proposal is not a shared-learnings proposal");
1911
+ }
1912
+ const evidence = safeJsonParse(proposal.evidence, []);
1913
+ const existing = await resourceGetByPath(SHARED_OWNER, "LEARNINGS.md");
1914
+ const current = existing?.content ??
1915
+ "# Learnings\n\n## Preferences\n\n## Corrections\n\n## Patterns\n";
1916
+ const sources = [...new Set(evidence.map((entry) => entry.threadId))]
1917
+ .slice(0, 6)
1918
+ .join(", ");
1919
+ const entry = [
1920
+ `- ${today()}: ${compactText(proposal.summary, 220)}`,
1921
+ ` Source dream: ${proposal.dreamId}; source threads: ${sources || "none recorded"}.`,
1922
+ ].join("\n");
1923
+ const updated = current.includes("## Patterns")
1924
+ ? current.replace("## Patterns", `## Patterns\n\n${entry}`)
1925
+ : `${current.trimEnd()}\n\n## Patterns\n\n${entry}\n`;
1926
+ await resourcePut(SHARED_OWNER, "LEARNINGS.md", updated, "text/markdown", {
1927
+ createdBy: "agent",
1928
+ metadata: { dreamId: proposal.dreamId, proposalId: proposal.id },
1929
+ });
1930
+ return {
1931
+ resourcePath: "LEARNINGS.md",
1932
+ owner: SHARED_OWNER,
1933
+ };
1934
+ }
1935
+ function workspaceResourceKindForTarget(targetType) {
1936
+ if (targetType === "workspace-instruction")
1937
+ return "instruction";
1938
+ if (targetType === "workspace-skill")
1939
+ return "skill";
1940
+ if (targetType === "workspace-knowledge")
1941
+ return "knowledge";
1942
+ if (targetType === "workspace-agent")
1943
+ return "agent";
1944
+ return null;
1945
+ }
1946
+ function workspaceResourceName(proposal) {
1947
+ return proposal.title
1948
+ .replace(/^Create workspace (instruction|skill|knowledge|agent) from /i, "")
1949
+ .replace(/^Create workspace (instruction|skill|knowledge|agent) /i, "")
1950
+ .trim()
1951
+ .replace(/^./, (char) => char.toUpperCase());
1952
+ }
1953
+ function personalMemoryTargetPath(proposal) {
1954
+ return proposal.targetPath.startsWith("memory/")
1955
+ ? proposal.targetPath
1956
+ : `memory/${slugify(proposal.title)}.md`;
1957
+ }
1958
+ function validateWorkspaceResourcePath(kind, path) {
1959
+ if (!path.trim() || path.includes("..") || path.startsWith("/")) {
1960
+ throw new Error("Workspace resource proposal has an invalid target path");
1961
+ }
1962
+ if (kind === "instruction") {
1963
+ if (path === "AGENTS.md" || path.startsWith("instructions/"))
1964
+ return;
1965
+ throw new Error("Workspace instruction proposals must target AGENTS.md or instructions/<name>.md");
1966
+ }
1967
+ if (kind === "skill") {
1968
+ if (path.startsWith("skills/") && path.endsWith("/SKILL.md"))
1969
+ return;
1970
+ throw new Error("Workspace skill proposals must target skills/<name>/SKILL.md");
1971
+ }
1972
+ if (kind === "knowledge") {
1973
+ if (path.startsWith("context/"))
1974
+ return;
1975
+ throw new Error("Workspace knowledge proposals must target context/<name>.md");
1976
+ }
1977
+ if (kind === "agent") {
1978
+ if (path.startsWith("agents/") && path.endsWith(".md"))
1979
+ return;
1980
+ throw new Error("Workspace agent proposals must target agents/<name>.md");
1981
+ }
1982
+ }
1983
+ async function applyWorkspaceResourceProposal(proposal) {
1984
+ const kind = workspaceResourceKindForTarget(proposal.targetType);
1985
+ if (!kind)
1986
+ throw new Error(`Unsupported workspace target: ${proposal.targetType}`);
1987
+ validateWorkspaceResourcePath(kind, proposal.targetPath);
1988
+ const scope = "all";
1989
+ const resources = await listWorkspaceResources({ kind });
1990
+ const existing = resources.find((resource) => resource.path === proposal.targetPath);
1991
+ const name = workspaceResourceName(proposal);
1992
+ const description = compactText(proposal.summary, 220);
1993
+ if (existing) {
1994
+ const updated = await updateWorkspaceResource(existing.id, {
1995
+ name,
1996
+ description,
1997
+ content: proposal.content,
1998
+ scope,
1999
+ });
2000
+ return {
2001
+ action: "updated",
2002
+ resourceId: updated?.id ?? existing.id,
2003
+ resourcePath: proposal.targetPath,
2004
+ kind,
2005
+ scope,
2006
+ };
2007
+ }
2008
+ const created = await createWorkspaceResource({
2009
+ kind,
2010
+ name,
2011
+ description,
2012
+ path: proposal.targetPath,
2013
+ content: proposal.content,
2014
+ scope,
2015
+ });
2016
+ return {
2017
+ action: "created",
2018
+ resourceId: created?.id ?? null,
2019
+ resourcePath: proposal.targetPath,
2020
+ kind,
2021
+ scope,
2022
+ };
2023
+ }
2024
+ function proposalRequiresApproval(proposal) {
2025
+ return proposal.targetType !== "personal-memory";
2026
+ }
2027
+ export async function previewDreamProposal(proposalId) {
2028
+ const proposal = await getProposalRow(proposalId);
2029
+ if (!proposal)
2030
+ throw new Error("Dream proposal not found");
2031
+ let currentContent = null;
2032
+ let targetExists = false;
2033
+ let operation = "create";
2034
+ let target = {
2035
+ type: proposal.targetType,
2036
+ path: proposal.targetPath,
2037
+ kind: null,
2038
+ resourceId: null,
2039
+ };
2040
+ if (proposal.targetType === "personal-memory") {
2041
+ const targetPath = personalMemoryTargetPath(proposal);
2042
+ const existing = await resourceGetByPath(proposal.ownerEmail, targetPath);
2043
+ currentContent = existing?.content ?? null;
2044
+ targetExists = Boolean(existing);
2045
+ operation = targetExists ? "update" : "create";
2046
+ target = { ...target, path: targetPath };
2047
+ }
2048
+ else if (proposal.targetType === "shared-learnings") {
2049
+ const existing = await resourceGetByPath(SHARED_OWNER, "LEARNINGS.md");
2050
+ currentContent = existing?.content ?? null;
2051
+ targetExists = Boolean(existing);
2052
+ operation = "append";
2053
+ target = { ...target, path: "LEARNINGS.md" };
2054
+ }
2055
+ else if (proposal.targetType.startsWith("workspace-")) {
2056
+ const kind = workspaceResourceKindForTarget(proposal.targetType);
2057
+ if (!kind) {
2058
+ throw new Error(`Unsupported workspace target: ${proposal.targetType}`);
2059
+ }
2060
+ validateWorkspaceResourcePath(kind, proposal.targetPath);
2061
+ const resources = await listWorkspaceResources({ kind });
2062
+ const existing = resources.find((resource) => resource.path === proposal.targetPath);
2063
+ currentContent = existing?.content ?? null;
2064
+ targetExists = Boolean(existing);
2065
+ operation = existing ? "update" : "create";
2066
+ target = {
2067
+ ...target,
2068
+ kind,
2069
+ resourceId: existing?.id ?? null,
2070
+ };
2071
+ }
2072
+ const approvalPolicy = await getApprovalPolicy();
2073
+ const requiresApproval = proposalRequiresApproval(proposal);
2074
+ return {
2075
+ proposal: serializeProposal(proposal),
2076
+ target,
2077
+ operation,
2078
+ targetExists,
2079
+ currentContent,
2080
+ proposedContent: proposal.content,
2081
+ approval: {
2082
+ required: requiresApproval,
2083
+ policyEnabled: approvalPolicy.enabled,
2084
+ willRequestApproval: requiresApproval && approvalPolicy.enabled,
2085
+ },
2086
+ };
2087
+ }
2088
+ async function requestDreamProposalApproval(proposal) {
2089
+ const db = getDb();
2090
+ const timestamp = now();
2091
+ await db
2092
+ .update(schema.dispatchDreamProposals)
2093
+ .set({
2094
+ status: "approval_requested",
2095
+ updatedAt: timestamp,
2096
+ })
2097
+ .where(eq(schema.dispatchDreamProposals.id, proposal.id));
2098
+ const approval = await createApprovalRequest({
2099
+ changeType: "dream-proposal.apply",
2100
+ targetType: "dream-proposal",
2101
+ targetId: proposal.id,
2102
+ summary: `Apply dream proposal: ${proposal.title}`,
2103
+ payload: { proposalId: proposal.id },
2104
+ beforeValue: serializeProposal(proposal),
2105
+ afterValue: {
2106
+ ...serializeProposal(proposal),
2107
+ status: "applied",
2108
+ },
2109
+ });
2110
+ await recordAudit({
2111
+ action: "dream.proposal.approval-requested",
2112
+ targetType: "dream-proposal",
2113
+ targetId: proposal.id,
2114
+ summary: `Requested approval for dream proposal: ${proposal.title}`,
2115
+ metadata: {
2116
+ dreamId: proposal.dreamId,
2117
+ approvalId: approval?.id ?? null,
2118
+ targetType: proposal.targetType,
2119
+ targetPath: proposal.targetPath,
2120
+ },
2121
+ });
2122
+ return {
2123
+ proposal: serializeProposal((await getProposalRow(proposal.id))),
2124
+ approval,
2125
+ result: {
2126
+ approvalRequired: true,
2127
+ approvalId: approval?.id ?? null,
2128
+ },
2129
+ };
2130
+ }
2131
+ async function applyDreamProposalDirect(proposal, actor = currentOwnerEmail()) {
2132
+ const db = getDb();
2133
+ let result;
2134
+ if (proposal.targetType === "personal-memory") {
2135
+ result = await savePersonalMemory(proposal);
2136
+ }
2137
+ else if (proposal.targetType === "shared-learnings") {
2138
+ result = await appendSharedLearning(proposal);
2139
+ }
2140
+ else if (proposal.targetType.startsWith("workspace-")) {
2141
+ result = await applyWorkspaceResourceProposal(proposal);
2142
+ }
2143
+ else {
2144
+ throw new Error(`Unsupported dream proposal target: ${proposal.targetType}`);
2145
+ }
2146
+ const timestamp = now();
2147
+ await db
2148
+ .update(schema.dispatchDreamProposals)
2149
+ .set({
2150
+ status: "applied",
2151
+ appliedBy: actor,
2152
+ appliedAt: timestamp,
2153
+ updatedAt: timestamp,
2154
+ })
2155
+ .where(eq(schema.dispatchDreamProposals.id, proposal.id));
2156
+ await recordAudit({
2157
+ action: "dream.proposal.applied",
2158
+ targetType: "dream-proposal",
2159
+ targetId: proposal.id,
2160
+ summary: `Applied dream proposal: ${proposal.title}`,
2161
+ actor,
2162
+ metadata: {
2163
+ dreamId: proposal.dreamId,
2164
+ targetType: proposal.targetType,
2165
+ targetPath: proposal.targetPath,
2166
+ result,
2167
+ },
2168
+ });
2169
+ return {
2170
+ proposal: serializeProposal((await getProposalRow(proposal.id))),
2171
+ result,
2172
+ };
2173
+ }
2174
+ export async function applyApprovedDreamProposal(proposalId, actor = currentOwnerEmail(), ctx = { ownerEmail: currentOwnerEmail(), orgId: currentOrgId() }) {
2175
+ const proposal = await getProposalRow(proposalId, ctx);
2176
+ if (!proposal)
2177
+ throw new Error("Dream proposal not found");
2178
+ if (!["pending", "approval_requested"].includes(proposal.status)) {
2179
+ throw new Error("Only pending dream proposals can be applied");
2180
+ }
2181
+ return applyDreamProposalDirect(proposal, actor);
2182
+ }
2183
+ export async function applyDreamProposal(proposalId) {
2184
+ const proposal = await getProposalRow(proposalId);
2185
+ if (!proposal)
2186
+ throw new Error("Dream proposal not found");
2187
+ if (proposal.status === "approval_requested") {
2188
+ throw new Error("Dream proposal is already waiting for approval");
2189
+ }
2190
+ if (proposal.status !== "pending") {
2191
+ throw new Error("Only pending dream proposals can be applied");
2192
+ }
2193
+ if (proposalRequiresApproval(proposal)) {
2194
+ const policy = await getApprovalPolicy();
2195
+ if (policy.enabled) {
2196
+ return requestDreamProposalApproval(proposal);
2197
+ }
2198
+ }
2199
+ return applyDreamProposalDirect(proposal);
2200
+ }
2201
+ export async function rejectDreamProposal(proposalId, reason) {
2202
+ const db = getDb();
2203
+ const proposal = await getProposalRow(proposalId);
2204
+ if (!proposal)
2205
+ throw new Error("Dream proposal not found");
2206
+ if (!["pending", "approval_requested"].includes(proposal.status)) {
2207
+ throw new Error("Only pending dream proposals can be rejected");
2208
+ }
2209
+ const timestamp = now();
2210
+ await db
2211
+ .update(schema.dispatchDreamProposals)
2212
+ .set({
2213
+ status: "rejected",
2214
+ rejectedBy: currentOwnerEmail(),
2215
+ rejectedAt: timestamp,
2216
+ updatedAt: timestamp,
2217
+ })
2218
+ .where(eq(schema.dispatchDreamProposals.id, proposal.id));
2219
+ await recordAudit({
2220
+ action: "dream.proposal.rejected",
2221
+ targetType: "dream-proposal",
2222
+ targetId: proposal.id,
2223
+ summary: `Rejected dream proposal: ${proposal.title}`,
2224
+ metadata: {
2225
+ dreamId: proposal.dreamId,
2226
+ reason: reason || null,
2227
+ },
2228
+ });
2229
+ return serializeProposal((await getProposalRow(proposal.id)));
2230
+ }
2231
+ function cronLooksValid(schedule) {
2232
+ const parts = schedule.trim().split(/\s+/);
2233
+ if (parts.length !== 5)
2234
+ return false;
2235
+ return parts.every((part) => /^[\d*/,\-]+$/.test(part));
2236
+ }
2237
+ function dreamJobBody(settings) {
2238
+ const queryLine = settings.query
2239
+ ? `- Use query "${settings.query}" to focus the dream pass.`
2240
+ : "- Review recent threads without a search query.";
2241
+ const sourceLine = settings.sourceIds.length > 0
2242
+ ? `- sourceIds: ${JSON.stringify(settings.sourceIds)}`
2243
+ : `- sourceId: "${settings.sourceId}"`;
2244
+ return `# Dispatch Dream
2245
+
2246
+ Run a safe Dispatch dreaming pass.
2247
+
2248
+ 1. Call \`list-dream-candidates\` with:
2249
+ ${sourceLine}
2250
+ - allSources: ${settings.allSources}
2251
+ ${queryLine}
2252
+ - ownerEmail: "*"
2253
+ - limit: ${settings.limit}
2254
+ - sourceTimeoutMs: ${settings.sourceTimeoutMs}
2255
+ - sourceConcurrency: ${settings.sourceConcurrency}
2256
+ - sourceStartStaggerMs: ${settings.sourceStartStaggerMs}
2257
+ - threadConcurrency: ${settings.threadConcurrency}
2258
+ - threadTimeoutMs: ${settings.threadTimeoutMs}
2259
+ 2. If fewer than ${settings.minCandidateCount} candidate thread(s) are found, stop without creating a report.
2260
+ 3. Call \`create-dream-report\` with the same source/query/limit/timeout settings.
2261
+ 4. Review the returned proposals and evidence.
2262
+ 5. Do not auto-apply shared/team changes, workspace instructions, workspace skills, AGENTS.md changes, or jobs.
2263
+ 6. Only apply personal-memory proposals when the evidence is explicit user-grounded correction or a remember request.
2264
+ 7. Leave all other proposals pending for a human to review.
2265
+ `;
2266
+ }
2267
+ export async function ensureDreamJob(input) {
2268
+ const saved = await getDreamSettings();
2269
+ const settings = normalizeDreamSettings({
2270
+ ...saved,
2271
+ ...input,
2272
+ query: input.query === undefined
2273
+ ? saved.query
2274
+ : input.query?.trim()
2275
+ ? input.query.trim()
2276
+ : null,
2277
+ sourceIds: input.sourceIds !== undefined
2278
+ ? normalizeSourceIds(input.sourceIds)
2279
+ : saved.sourceIds,
2280
+ });
2281
+ const schedule = input.schedule?.trim() || settings.schedule;
2282
+ if (!cronLooksValid(schedule)) {
2283
+ throw new Error('Invalid cron expression. Use a standard five-field cron like "0 9 * * 1".');
2284
+ }
2285
+ const owner = currentOwnerEmail();
2286
+ const orgId = currentOrgId();
2287
+ const jobSettings = { ...settings, schedule, enabled: true };
2288
+ const content = [
2289
+ "---",
2290
+ `schedule: "${schedule}"`,
2291
+ "enabled: true",
2292
+ `createdBy: ${owner}`,
2293
+ ...(orgId ? [`orgId: ${orgId}`] : []),
2294
+ "runAs: creator",
2295
+ "---",
2296
+ "",
2297
+ dreamJobBody(jobSettings),
2298
+ ].join("\n");
2299
+ const resource = await resourcePut(owner, DREAM_JOB_PATH, content, "text/markdown", {
2300
+ createdBy: "agent",
2301
+ metadata: jobSettings,
2302
+ });
2303
+ await setDreamSettings(jobSettings);
2304
+ await recordAudit({
2305
+ action: "dream.job.ensured",
2306
+ targetType: "job",
2307
+ targetId: DREAM_JOB_PATH,
2308
+ summary: "Ensured weekly Dispatch dream recurring job",
2309
+ metadata: jobSettings,
2310
+ });
2311
+ return {
2312
+ path: resource.path,
2313
+ owner: resource.owner,
2314
+ schedule,
2315
+ enabled: true,
2316
+ runAs: "creator",
2317
+ sourceId: jobSettings.sourceId,
2318
+ sourceIds: jobSettings.sourceIds,
2319
+ allSources: jobSettings.allSources,
2320
+ query: jobSettings.query,
2321
+ limit: jobSettings.limit,
2322
+ sourceTimeoutMs: jobSettings.sourceTimeoutMs,
2323
+ sourceConcurrency: jobSettings.sourceConcurrency,
2324
+ sourceStartStaggerMs: jobSettings.sourceStartStaggerMs,
2325
+ threadConcurrency: jobSettings.threadConcurrency,
2326
+ threadTimeoutMs: jobSettings.threadTimeoutMs,
2327
+ minCandidateCount: jobSettings.minCandidateCount,
2328
+ };
2329
+ }
2330
+ //# sourceMappingURL=dreams-store.js.map