@xopcai/xopc 0.0.283 → 0.0.285

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 (234) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/telegram/xopc.extension.json +1 -1
  3. package/dist/gateway/static/root/assets/{activity-store-iPtG94qr.js → activity-store-B0BTIk9j.js} +1 -1
  4. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-DQ74xqLi.js → agent-avatar-dicebear-Dc-0QZTQ.js} +1 -1
  5. package/dist/gateway/static/root/assets/agent-defaults-settings-panel-CcmqK1Sc.js +1 -0
  6. package/dist/gateway/static/root/assets/agents-B_7PVt7S.js +1 -0
  7. package/dist/gateway/static/root/assets/api-COG_Tzpv.js +1 -0
  8. package/dist/gateway/static/root/assets/{api-KMJzMqvZ.js → api-Coly9ab6.js} +1 -1
  9. package/dist/gateway/static/root/assets/{app-management-settings-panel-B42TpqIb.js → app-management-settings-panel-TWBO-msg.js} +1 -1
  10. package/dist/gateway/static/root/assets/{appearance-settings-D9Lfe7Sk.js → appearance-settings-CpTZ2KuT.js} +1 -1
  11. package/dist/gateway/static/root/assets/apps-page-6qxxKFsK.js +1 -0
  12. package/dist/gateway/static/root/assets/{archive-plugin-BdmQwj4x.js → archive-plugin-z_u6alob.js} +1 -1
  13. package/dist/gateway/static/root/assets/{attachment-load-B4S0_nji.js → attachment-load-C2nS04TC.js} +1 -1
  14. package/dist/gateway/static/root/assets/{automations-page-B3TmHNjQ.js → automations-page-aIBsw9zR.js} +1 -1
  15. package/dist/gateway/static/root/assets/automations-workspace-BRQlU4qk.js +6 -0
  16. package/dist/gateway/static/root/assets/{binary-plugins-B3cXF_DP.js → binary-plugins-CGmXoZnl.js} +1 -1
  17. package/dist/gateway/static/root/assets/{block-editor-05zyBXBe.js → block-editor-CtvzCZhC.js} +3 -3
  18. package/dist/gateway/static/root/assets/{browser-automation-inputs-BTIufVbM.js → browser-automation-inputs-BOTU1isH.js} +1 -1
  19. package/dist/gateway/static/root/assets/{browser-automations-page-DRBA9nwe.js → browser-automations-page-BwU5V8J0.js} +2 -2
  20. package/dist/gateway/static/root/assets/browser-settings-page-DDjbDeHQ.js +8 -0
  21. package/dist/gateway/static/root/assets/capabilities-settings-panel-DM1Bcf1x.js +8 -0
  22. package/dist/gateway/static/root/assets/channels-settings-Bq36CUxL.js +1 -0
  23. package/dist/gateway/static/root/assets/{channels-status-swr-DMYQNs-J.js → channels-status-swr-Dfi0Desh.js} +1 -1
  24. package/dist/gateway/static/root/assets/{chat-terminal-dock-Dx5KQyo5.js → chat-terminal-dock-B0MvcO6j.js} +2 -2
  25. package/dist/gateway/static/root/assets/connectors-page-L8JehrxF.js +2 -0
  26. package/dist/gateway/static/root/assets/{date-picker-CkDROZoe.js → date-picker-DXOvbHCr.js} +1 -1
  27. package/dist/gateway/static/root/assets/{dependency-picker-Bl8HcvzL.js → dependency-picker-BEnC-eS0.js} +1 -1
  28. package/dist/gateway/static/root/assets/{desktop-pet-BcknAjbW.js → desktop-pet-DtPNvznL.js} +1 -1
  29. package/dist/gateway/static/root/assets/{desktop-pet-settings-BuCpXQPm.js → desktop-pet-settings-CYyjcSn6.js} +1 -1
  30. package/dist/gateway/static/root/assets/device-pairing-wizard-BPMGvAKE.js +1 -0
  31. package/dist/gateway/static/root/assets/{extension-debug-page-qRpb2FFa.js → extension-debug-page-H_aPMGXW.js} +1 -1
  32. package/dist/gateway/static/root/assets/{extension-page-C7gs9wu4.js → extension-page-C1JnpeMt.js} +1 -1
  33. package/dist/gateway/static/root/assets/{extension-settings-page-CyJP1CMZ.js → extension-settings-page-C75kCOqe.js} +1 -1
  34. package/dist/gateway/static/root/assets/{gateway-config-swr-Cpmyrr6j.js → gateway-config-swr-G2lQt7Ak.js} +1 -1
  35. package/dist/gateway/static/root/assets/gateway-settings-BCF732TU.js +1 -0
  36. package/dist/gateway/static/root/assets/{gateway-startup-retry-DoiPeb65.js → gateway-startup-retry-GP31PUrG.js} +1 -1
  37. package/dist/gateway/static/root/assets/{heartbeat-settings-CV6Dolcn.js → heartbeat-settings-C7fjiowt.js} +1 -1
  38. package/dist/gateway/static/root/assets/home-page-DZXeXdUL.js +1 -0
  39. package/dist/gateway/static/root/assets/{image-generation-api-CqjHWl2T.js → image-generation-api-CKFhfHSw.js} +1 -1
  40. package/dist/gateway/static/root/assets/imports-page-GROumhj8.js +1 -0
  41. package/dist/gateway/static/root/assets/{index-CHgOG1aR.js → index-ChIrvv3t.js} +47 -47
  42. package/dist/gateway/static/root/assets/index-R1VNlHjj.css +2 -0
  43. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-B3XqA41D.js → keyboard-shortcuts-settings-zcI822m_.js} +1 -1
  44. package/dist/gateway/static/root/assets/local-app-workbench-page-CAEKGTAg.js +2 -0
  45. package/dist/gateway/static/root/assets/{local-apps-page-BcIVYp0J.js → local-apps-page-BtgDJ92Q.js} +1 -1
  46. package/dist/gateway/static/root/assets/{locale-store-1urrBBCQ.js → locale-store-DlOv7896.js} +1 -1
  47. package/dist/gateway/static/root/assets/logs-page-DHZ4BEkG.js +2 -0
  48. package/dist/gateway/static/root/assets/{management-settings-DNFCdgzR.js → management-settings-DasZxCyD.js} +1 -1
  49. package/dist/gateway/static/root/assets/{markdown-view-BZmcFIWa.js → markdown-view-6oYdBahT.js} +1 -1
  50. package/dist/gateway/static/root/assets/{media-plugins-BDxNhoBC.js → media-plugins-CxthbBvk.js} +1 -1
  51. package/dist/gateway/static/root/assets/messages-Ls1Aaiqd.js +3 -0
  52. package/dist/gateway/static/root/assets/note-detail-panel-BcWq3ddw.js +5 -0
  53. package/dist/gateway/static/root/assets/notes-page-DfZ8QWyv.js +8 -0
  54. package/dist/gateway/static/root/assets/{page-tabs-agH3a2PI.js → page-tabs-BG39KidE.js} +1 -1
  55. package/dist/gateway/static/root/assets/{preview-open-alternatives-XEZ7zDOP.js → preview-open-alternatives-Bevpbq9f.js} +1 -1
  56. package/dist/gateway/static/root/assets/proactive-page-D2U_xQaS.js +1 -0
  57. package/dist/gateway/static/root/assets/{product-open-page-lXAbj85Z.js → product-open-page-Bi1seZpM.js} +1 -1
  58. package/dist/gateway/static/root/assets/project-detail-page-CzlGTW4F.js +1 -0
  59. package/dist/gateway/static/root/assets/projects-page-mQsM-TsP.js +1 -0
  60. package/dist/gateway/static/root/assets/{registry-api-CxQstD00.js → registry-api-D0ddwBFj.js} +1 -1
  61. package/dist/gateway/static/root/assets/remote-access-hub-Cb56JcO1.js +1 -0
  62. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-DErKZlVJ.js → runtime-tools-settings-panel-Wkru8_My.js} +2 -2
  63. package/dist/gateway/static/root/assets/{schema-form-DhCmJQVJ.js → schema-form-CSMRzj9Y.js} +1 -1
  64. package/dist/gateway/static/root/assets/sessions-page-Hj9uFcIO.js +1 -0
  65. package/dist/gateway/static/root/assets/settings-advanced-gate-CV4-_EVA.js +1 -0
  66. package/dist/gateway/static/root/assets/{settings-form-section-Dg6chotg.js → settings-form-section-wcAdCkK8.js} +1 -1
  67. package/dist/gateway/static/root/assets/{settings-page-LyuN5N3M.js → settings-page-BcQGAnCs.js} +1 -1
  68. package/dist/gateway/static/root/assets/setup-status-panel-DzxmsaQG.js +1 -0
  69. package/dist/gateway/static/root/assets/{share-preview-page-CZvmf4Ob.js → share-preview-page-DQocUl-1.js} +1 -1
  70. package/dist/gateway/static/root/assets/shares-settings-xlJh1ccG.js +1 -0
  71. package/dist/gateway/static/root/assets/skill-reload-api-wSXJRSxX.js +1 -0
  72. package/dist/gateway/static/root/assets/skills-page-AmqtBJo7.js +2 -0
  73. package/dist/gateway/static/root/assets/{skills-page.utils-Eu0WVHTS.js → skills-page.utils-DtlfCgJp.js} +1 -1
  74. package/dist/gateway/static/root/assets/{system-settings-panel-DQKkM7B2.js → system-settings-panel-Ct698Nf-.js} +1 -1
  75. package/dist/gateway/static/root/assets/task-detail-page-sQpwq5-c.js +1 -0
  76. package/dist/gateway/static/root/assets/{theme-store-Dc5s20V3.js → theme-store-ng8KJsAT.js} +1 -1
  77. package/dist/gateway/static/root/assets/{tunnel-api-TKQpPVd6.js → tunnel-api-Boajol0M.js} +1 -1
  78. package/dist/gateway/static/root/assets/{use-autosave-CkVlrFk1.js → use-autosave-BSyxRHt2.js} +1 -1
  79. package/dist/gateway/static/root/assets/user-model-page-DIEHnAER.js +2 -0
  80. package/dist/gateway/static/root/assets/{voice-api-key-field-B12fFN-L.js → voice-api-key-field-SE3OQI1s.js} +1 -1
  81. package/dist/gateway/static/root/assets/{voice-config-api-Mk78BrpP.js → voice-config-api-VNuCmdFe.js} +1 -1
  82. package/dist/gateway/static/root/assets/{work-discovery-overlay-CT9Zy36i.js → work-discovery-overlay-Dn1vy2oq.js} +1 -1
  83. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-BwefQliE.js → workflow-run-setup-panel-BJEfGY-k.js} +2 -2
  84. package/dist/gateway/static/root/assets/workflows-page-DgRNIyr7.js +6 -0
  85. package/dist/gateway/static/root/index.html +10 -10
  86. package/dist/package.js +1 -1
  87. package/dist/packages/gateway-contract/src/proactive.js +13 -4
  88. package/dist/src/agent/coding/repository-instructions.js +1 -1
  89. package/dist/src/agent/coding/workspace-revision.js +1 -1
  90. package/dist/src/agent/commands/command-isolation.js +1 -1
  91. package/dist/src/agent/tools/factory.js +7 -3
  92. package/dist/src/agent/tools/review-workspace.js +1 -1
  93. package/dist/src/browser/providers/browser-ext-install.js +10 -2
  94. package/dist/src/cli/command-loaders.js +1 -0
  95. package/dist/src/cli/command-manifest.js +4 -0
  96. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  97. package/dist/src/cli/commands/gateway/lifecycle.js +1 -1
  98. package/dist/src/cli/commands/gateway/run-foreground.js +3 -0
  99. package/dist/src/cli/commands/import.d.ts +2 -0
  100. package/dist/src/cli/commands/import.js +78 -0
  101. package/dist/src/discussions/action-tasks.js +1 -1
  102. package/dist/src/discussions/edits.js +1 -1
  103. package/dist/src/discussions/index.js +1 -1
  104. package/dist/src/discussions/organizer.js +1 -1
  105. package/dist/src/discussions/recording.js +1 -1
  106. package/dist/src/discussions/sealer.js +1 -1
  107. package/dist/src/discussions/service.js +2 -2
  108. package/dist/src/endpoint-tools/invocation-service.d.ts +2 -0
  109. package/dist/src/endpoint-tools/invocation-service.js +48 -19
  110. package/dist/src/gateway/heartbeat/check-store.d.ts +18 -0
  111. package/dist/src/gateway/heartbeat/check-store.js +73 -0
  112. package/dist/src/gateway/heartbeat/service.d.ts +12 -3
  113. package/dist/src/gateway/heartbeat/service.js +61 -44
  114. package/dist/src/gateway/hono/routes/discussions.js +2 -2
  115. package/dist/src/gateway/hono/routes/imports.d.ts +3 -0
  116. package/dist/src/gateway/hono/routes/imports.js +56 -0
  117. package/dist/src/gateway/hono/routes/lazy-bundles.js +8 -0
  118. package/dist/src/gateway/hono/routes/proactive-control.js +2 -1
  119. package/dist/src/gateway/service.d.ts +7 -0
  120. package/dist/src/gateway/service.js +26 -9
  121. package/dist/src/imports/compatibility.d.ts +7 -0
  122. package/dist/src/imports/compatibility.js +174 -0
  123. package/dist/src/imports/contextImport.d.ts +3 -0
  124. package/dist/src/imports/contextImport.js +56 -0
  125. package/dist/src/imports/files.d.ts +13 -0
  126. package/dist/src/imports/files.js +77 -0
  127. package/dist/src/imports/importService.d.ts +50 -0
  128. package/dist/src/imports/importService.js +414 -0
  129. package/dist/src/imports/inventory.d.ts +11 -0
  130. package/dist/src/imports/inventory.js +202 -0
  131. package/dist/src/imports/planner.d.ts +11 -0
  132. package/dist/src/imports/planner.js +98 -0
  133. package/dist/src/imports/productImport.d.ts +10 -0
  134. package/dist/src/imports/productImport.js +241 -0
  135. package/dist/src/imports/runtime.d.ts +5 -0
  136. package/dist/src/imports/runtime.js +61 -0
  137. package/dist/src/imports/scanner.d.ts +14 -0
  138. package/dist/src/imports/scanner.js +159 -0
  139. package/dist/src/imports/secrets.d.ts +2 -0
  140. package/dist/src/imports/secrets.js +9 -0
  141. package/dist/src/imports/sources.d.ts +37 -0
  142. package/dist/src/imports/sources.js +75 -0
  143. package/dist/src/imports/types.d.ts +144 -0
  144. package/dist/src/imports/types.js +11 -0
  145. package/dist/src/infra/background-task.d.ts +2 -0
  146. package/dist/src/infra/background-task.js +25 -0
  147. package/dist/src/infra/process-diagnostics.d.ts +5 -0
  148. package/dist/src/infra/process-diagnostics.js +97 -0
  149. package/dist/src/infra/unhandled-rejections.js +6 -3
  150. package/dist/src/infra/update-restart.js +1 -1
  151. package/dist/src/knowledge/ingestion-service.js +1 -0
  152. package/dist/src/knowledge-memory/repository.js +4 -2
  153. package/dist/src/media-shared/http/ssrf-guard.js +1 -1
  154. package/dist/src/notifications/proactive-policy.js +7 -5
  155. package/dist/src/proactive/execution/authorization.d.ts +2 -0
  156. package/dist/src/proactive/execution/authorization.js +10 -1
  157. package/dist/src/proactive/execution/context.js +1 -0
  158. package/dist/src/proactive/execution/repository.d.ts +2 -0
  159. package/dist/src/proactive/execution/repository.js +19 -2
  160. package/dist/src/proactive/execution/worker.js +14 -4
  161. package/dist/src/proactive/experience.d.ts +4 -1
  162. package/dist/src/proactive/experience.js +3 -0
  163. package/dist/src/proactive/follow-ups.d.ts +5 -0
  164. package/dist/src/proactive/follow-ups.js +18 -7
  165. package/dist/src/proactive/inbox/cards.js +2 -1
  166. package/dist/src/proactive/inbox/delivery.js +6 -7
  167. package/dist/src/proactive/inbox/digest.d.ts +1 -1
  168. package/dist/src/proactive/inbox/digest.js +23 -13
  169. package/dist/src/proactive/inbox/notification-policy.d.ts +1 -1
  170. package/dist/src/proactive/inbox/notification-policy.js +9 -6
  171. package/dist/src/proactive/inbox/repository.js +3 -2
  172. package/dist/src/proactive/inbox/service.d.ts +6 -0
  173. package/dist/src/proactive/inbox/service.js +18 -0
  174. package/dist/src/proactive/inbox/types.d.ts +1 -0
  175. package/dist/src/proactive/policy/presence.d.ts +1 -1
  176. package/dist/src/proactive/policy/presence.js +7 -5
  177. package/dist/src/proactive/policy/service.d.ts +8 -5
  178. package/dist/src/proactive/policy/service.js +12 -12
  179. package/dist/src/proactive/scenarios/control.d.ts +3 -3
  180. package/dist/src/proactive/scenarios/control.js +12 -2
  181. package/dist/src/proactive/source-freshness.d.ts +8 -0
  182. package/dist/src/proactive/source-freshness.js +27 -0
  183. package/dist/src/proactive/status.d.ts +2 -0
  184. package/dist/src/proactive/status.js +21 -0
  185. package/dist/src/proactive/temporal/worker.js +2 -1
  186. package/dist/src/realtime/runtime.js +31 -5
  187. package/dist/src/realtime/writer.js +16 -9
  188. package/dist/src/storage/sqlite/import-repository.d.ts +18 -0
  189. package/dist/src/storage/sqlite/import-repository.js +35 -0
  190. package/dist/src/storage/sqlite/import-selection-repository.d.ts +15 -0
  191. package/dist/src/storage/sqlite/import-selection-repository.js +28 -0
  192. package/dist/src/storage/sqlite/knowledge-repository.d.ts +1 -0
  193. package/dist/src/storage/sqlite/knowledge-repository.js +2 -2
  194. package/dist/src/storage/sqlite/migrations/174_capability_imports.sql +11 -0
  195. package/dist/src/storage/sqlite/migrations/175_import_selections.sql +8 -0
  196. package/dist/src/storage/sqlite/migrations/176_proactive_quality.sql +5 -0
  197. package/dist/src/storage/sqlite/migrations/177_proactive_check_controls.sql +25 -0
  198. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  199. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  200. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  201. package/dist/src/storage/sqlite/schema.js +1 -1
  202. package/dist/src/work-discovery/candidate-discovery.js +1 -1
  203. package/dist/src/work-discovery/probe.js +1 -1
  204. package/package.json +4 -3
  205. package/dist/gateway/static/root/assets/agent-defaults-settings-panel-SElFkQd6.js +0 -1
  206. package/dist/gateway/static/root/assets/agents-DYazUaZ6.js +0 -1
  207. package/dist/gateway/static/root/assets/apps-page-BzrodN8C.js +0 -1
  208. package/dist/gateway/static/root/assets/automations-workspace--A4I0jMu.js +0 -6
  209. package/dist/gateway/static/root/assets/browser-settings-page-DDTXUNWK.js +0 -8
  210. package/dist/gateway/static/root/assets/capabilities-settings-panel-Ll5UeXq5.js +0 -8
  211. package/dist/gateway/static/root/assets/channels-settings-BzL15bkU.js +0 -1
  212. package/dist/gateway/static/root/assets/connectors-page-RPw1AOMb.js +0 -2
  213. package/dist/gateway/static/root/assets/device-pairing-wizard-D0_tCX49.js +0 -1
  214. package/dist/gateway/static/root/assets/gateway-settings-DOeeTdWE.js +0 -1
  215. package/dist/gateway/static/root/assets/home-page-Djw1H_Go.js +0 -1
  216. package/dist/gateway/static/root/assets/index-B_Sg5BKz.css +0 -2
  217. package/dist/gateway/static/root/assets/local-app-workbench-page-BPqgyYJq.js +0 -2
  218. package/dist/gateway/static/root/assets/logs-page-BHax3XlS.js +0 -2
  219. package/dist/gateway/static/root/assets/messages-DYFcjtmG.js +0 -3
  220. package/dist/gateway/static/root/assets/note-detail-panel-Cs46LFXO.js +0 -5
  221. package/dist/gateway/static/root/assets/notes-page-BUkcn_0t.js +0 -8
  222. package/dist/gateway/static/root/assets/proactive-page-Bn484J9v.js +0 -1
  223. package/dist/gateway/static/root/assets/project-detail-page-LcH21ixD.js +0 -1
  224. package/dist/gateway/static/root/assets/projects-page-DelBiwyB.js +0 -1
  225. package/dist/gateway/static/root/assets/remote-access-hub-3BjFYXCf.js +0 -1
  226. package/dist/gateway/static/root/assets/sessions-page-CVFirgVd.js +0 -1
  227. package/dist/gateway/static/root/assets/settings-advanced-gate-DyfBpqkY.js +0 -1
  228. package/dist/gateway/static/root/assets/setup-status-panel-CrXhTzHr.js +0 -1
  229. package/dist/gateway/static/root/assets/shares-settings-DyM_D8hM.js +0 -1
  230. package/dist/gateway/static/root/assets/skill-reload-api-Bys58-15.js +0 -1
  231. package/dist/gateway/static/root/assets/skills-page-sWNvFo6D.js +0 -2
  232. package/dist/gateway/static/root/assets/task-detail-page-B18NcZQJ.js +0 -1
  233. package/dist/gateway/static/root/assets/user-model-page-ul-zr58b.js +0 -2
  234. package/dist/gateway/static/root/assets/workflows-page-BY_AWs7S.js +0 -6
@@ -1,6 +1,6 @@
1
1
  import { getSqliteDatabase } from "../storage/sqlite/transaction.js";
2
- import { proactivePreferences, quietHoursEnd } from "../proactive/policy/service.js";
3
- import { viewingProactiveCards } from "../proactive/policy/presence.js";
2
+ import { proactiveChecksAllowed, proactivePreferences, quietHoursEnd } from "../proactive/policy/service.js";
3
+ import { viewingProactiveCard } from "../proactive/policy/presence.js";
4
4
  import { digestCards } from "../proactive/inbox/digest.js";
5
5
  import { recheckProactiveNotification } from "../proactive/inbox/notification-policy.js";
6
6
  //#region src/notifications/proactive-policy.ts
@@ -19,11 +19,13 @@ function recheckNotificationDelivery(notification, channel, now = /* @__PURE__ *
19
19
  "auto",
20
20
  channel
21
21
  ].includes(preferences.preferredChannel)) return "cancel";
22
- if (preferences.level === "off" || preferences.pausedUntil && Date.parse(preferences.pausedUntil) > now.getTime()) return "cancel";
23
- if (preferences.suppressWhileViewing && viewingProactiveCards(workspace, now.getTime())) return "cancel";
22
+ if (!proactiveChecksAllowed(preferences, now) || preferences.notificationsMuted) return "cancel";
23
+ if (preferences.suppressWhileViewing && notification.target.kind === "insight" && viewingProactiveCard(workspace, notification.target.inboxItemId, Number(notification.payload.notificationRevision ?? 1), now.getTime())) return new Date(now.getTime() + 6e4);
24
24
  if (notification.target.kind === "insight") return recheckProactiveNotification(notification.target.inboxItemId, now, notification.payload.notificationRevision);
25
25
  if (notification.target.kind === "proactive_digest") {
26
- if (!digestCards(notification.target.digestId, workspace).length) return "cancel";
26
+ const decisions = digestCards(notification.target.digestId, workspace, true).map((card) => recheckProactiveNotification(card.id, now, card.notificationRevision, true));
27
+ if (!decisions.length || decisions.every((value) => value === "cancel")) return "cancel";
28
+ if (!decisions.includes("send")) return decisions.find((value) => value instanceof Date) ?? "cancel";
27
29
  return quietHoursEnd(preferences, now) ?? "send";
28
30
  }
29
31
  return "cancel";
@@ -3,3 +3,5 @@ export declare function insightSourcesAuthorized(insightId: string): boolean;
3
3
  /** Compare versions, not generated prose, before showing or executing prepared work. */
4
4
  export declare function insightSourcesChanged(insightId: string): boolean;
5
5
  export declare function authorizedConnectedSource(itemId: string, workspaceId: string, scenarioKey: string, agentId?: string | null): import("../../knowledge/types.js").KnowledgeSourceItem;
6
+ /** Delivery eligibility is stricter than reading historical artifacts. */
7
+ export declare function insightSourcesFresh(insightId: string): boolean;
@@ -1,6 +1,7 @@
1
1
  import { getSqliteDatabase } from "../../storage/sqlite/transaction.js";
2
2
  import { getKnowledgeSourceItem } from "../../storage/sqlite/knowledge-repository.js";
3
3
  import { getConnectorSyncPolicyForConnection } from "../../storage/sqlite/connector-sync-policy-repository.js";
4
+ import { sourceFreshness } from "../source-freshness.js";
4
5
  import { readMailThread } from "../mail-thread.js";
5
6
  //#region src/proactive/execution/authorization.ts
6
7
  /** Recheck connector consent for stored cards and actions, not just model input. */
@@ -57,5 +58,13 @@ function authorizedConnectedSource(itemId, workspaceId, scenarioKey, agentId) {
57
58
  const policy = getConnectorSyncPolicyForConnection(connectionId);
58
59
  return policy?.scanEnabled && policy.proactiveEnabled && (!policy.allowedScenarioKeys.length || policy.allowedScenarioKeys.includes(scenarioKey)) ? item : null;
59
60
  }
61
+ /** Delivery eligibility is stricter than reading historical artifacts. */
62
+ function insightSourcesFresh(insightId) {
63
+ const row = getSqliteDatabase().prepare(`SELECT c.evidence_ids_json, c.content_json FROM proactive_insights x JOIN proactive_runs r USING(run_id)
64
+ JOIN proactive_context_snapshots c ON c.snapshot_id = r.context_snapshot_id WHERE x.insight_id = ?`).get(insightId);
65
+ if (!row) return true;
66
+ const follow = JSON.parse(row.content_json).follow_up;
67
+ return JSON.parse(row.evidence_ids_json).every((id) => !id.startsWith("source-item:") || sourceFreshness(id.slice(12), follow?.deadlinePassed ? follow.dueAt : void 0).fresh);
68
+ }
60
69
  //#endregion
61
- export { authorizedConnectedSource, insightSourcesAuthorized, insightSourcesChanged };
70
+ export { authorizedConnectedSource, insightSourcesAuthorized, insightSourcesChanged, insightSourcesFresh };
@@ -31,6 +31,7 @@ function authorizedConnectedSourceItem(event, scenarioKey) {
31
31
  const item = authorizedConnectedSource(event.subject_id, event.workspace_id, scenarioKey, event.agent_id);
32
32
  if (item && event.type === "connected_source.calendar_window.v1") {
33
33
  const payload = JSON.parse(event.payload_json);
34
+ if (!item.occurredAt || Date.parse(item.occurredAt) <= Date.now()) return null;
34
35
  if (payload.meetingStartsAt !== item.occurredAt || payload.contentHash && payload.contentHash !== item.contentHash) return null;
35
36
  }
36
37
  return item;
@@ -22,5 +22,7 @@ export declare function finishRun(input: {
22
22
  outcomeReason?: string;
23
23
  modelRef?: string;
24
24
  }, now?: Date): ProactiveInsight | null;
25
+ /** Stale evidence waits without spending model attempts; never retain work indefinitely. */
26
+ export declare function deferRunForSource(run: ClaimedRun, now?: Date): void;
25
27
  export declare function failRun(run: ClaimedRun, error: unknown, retryable: boolean, now?: Date): void;
26
28
  export declare function listInsights(limit?: number): ProactiveInsight[];
@@ -30,7 +30,7 @@ function claimNextRun(owner, now = /* @__PURE__ */ new Date(), leaseSeconds = 12
30
30
  WHERE status = 'running' AND lease_expires_at <= ? AND attempt >= 3`).run(nowIso, nowIso);
31
31
  const retry = db.prepare(`SELECT * FROM proactive_runs WHERE status = 'retryable' AND next_attempt_at <= ? ORDER BY next_attempt_at LIMIT 1`).get(nowIso);
32
32
  if (retry) {
33
- db.prepare(`UPDATE proactive_runs SET status = 'running', attempt = attempt + 1, lease_owner = ?, lease_expires_at = ?, next_attempt_at = NULL, updated_at = ? WHERE run_id = ?`).run(owner, new Date(now.getTime() + leaseSeconds * 1e3).toISOString(), nowIso, str(retry, "run_id"));
33
+ db.prepare(`UPDATE proactive_runs SET status = 'running', attempt = attempt + CASE WHEN outcome_reason = 'source_stale' THEN 0 ELSE 1 END, lease_owner = ?, lease_expires_at = ?, next_attempt_at = NULL, updated_at = ? WHERE run_id = ?`).run(owner, new Date(now.getTime() + leaseSeconds * 1e3).toISOString(), nowIso, str(retry, "run_id"));
34
34
  db.prepare("UPDATE proactive_signal_batches SET status = 'processing', updated_at = ? WHERE batch_id = ?").run(nowIso, str(retry, "batch_id"));
35
35
  return runFromRow(db.prepare("SELECT * FROM proactive_runs WHERE run_id = ?").get(str(retry, "run_id")));
36
36
  }
@@ -153,6 +153,23 @@ function finishRun(input, now = /* @__PURE__ */ new Date()) {
153
153
  return insight;
154
154
  });
155
155
  }
156
+ /** Stale evidence waits without spending model attempts; never retain work indefinitely. */
157
+ function deferRunForSource(run, now = /* @__PURE__ */ new Date()) {
158
+ runSqliteWriteTransaction((db) => {
159
+ const row = db.prepare("SELECT started_at FROM proactive_runs WHERE run_id = ?").get(run.id);
160
+ if (now.getTime() - Date.parse(row.started_at) >= 864e5) {
161
+ finishRun({
162
+ run,
163
+ rawOutput: "",
164
+ outcomeReason: "source_unavailable"
165
+ }, now);
166
+ return;
167
+ }
168
+ if (db.prepare(`UPDATE proactive_runs SET status = 'retryable', outcome_reason = 'source_stale',
169
+ lease_owner = NULL, lease_expires_at = NULL, next_attempt_at = ?, updated_at = ?
170
+ WHERE run_id = ? AND status = 'running' AND attempt = ?`).run(new Date(now.getTime() + 6e4).toISOString(), now.toISOString(), run.id, run.attempt).changes) db.prepare("UPDATE proactive_signal_batches SET status = 'failed_retryable', updated_at = ? WHERE batch_id = ?").run(now.toISOString(), run.batchId);
171
+ });
172
+ }
156
173
  function failRun(run, error, retryable, now = /* @__PURE__ */ new Date()) {
157
174
  runSqliteWriteTransaction((db) => {
158
175
  const finalRetryable = retryable && run.attempt < 3;
@@ -190,4 +207,4 @@ function listInsights(limit = 50) {
190
207
  }));
191
208
  }
192
209
  //#endregion
193
- export { attachSnapshot, claimNextRun, eventIdsForBatch, failRun, finishRun, listInsights, saveSnapshot };
210
+ export { attachSnapshot, claimNextRun, deferRunForSource, eventIdsForBatch, failRun, finishRun, listInsights, saveSnapshot };
@@ -1,16 +1,17 @@
1
1
  import { createLogger } from "../../utils/logger/index.js";
2
2
  import { init_logger } from "../../utils/logger.js";
3
3
  import { getSqliteDatabase } from "../../storage/sqlite/transaction.js";
4
+ import { evidenceSourcesFresh } from "../source-freshness.js";
4
5
  import { authorizedConnectedSource } from "./authorization.js";
5
6
  import { effectiveProactivePolicy } from "../policy/service.js";
6
7
  import { getPromptRevision, getScenario } from "../scenarios/repository.js";
7
8
  import { composeScenarioPrompt } from "../scenarios/prompt-composer.js";
8
9
  import { markReadyBatches } from "../routing/batch-repository.js";
9
10
  import { ContextProviderRegistry } from "./context.js";
10
- import { attachSnapshot, claimNextRun, eventIdsForBatch, failRun, finishRun, saveSnapshot } from "./repository.js";
11
+ import { attachSnapshot, claimNextRun, deferRunForSource, eventIdsForBatch, failRun, finishRun, saveSnapshot } from "./repository.js";
11
12
  import { isValuableInsight, parseAnalysisResult, scoreInsight } from "./insight.js";
12
- import { randomUUID } from "node:crypto";
13
13
  import { isDeepStrictEqual } from "node:util";
14
+ import { randomUUID } from "node:crypto";
14
15
  //#region src/proactive/execution/worker.ts
15
16
  init_logger();
16
17
  const log = createLogger("ProactiveWorker");
@@ -69,6 +70,10 @@ var ProactiveWorker = class {
69
70
  });
70
71
  return;
71
72
  }
73
+ if (!evidenceSourcesFresh(context.evidenceIds, context.content)) {
74
+ deferRunForSource(run);
75
+ return;
76
+ }
72
77
  const snapshot = saveSnapshot(run.batchId, context.snapshotContent ?? context.content, context.evidenceIds);
73
78
  attachSnapshot(run.id, snapshot.id);
74
79
  const prompt = composeScenarioPrompt({
@@ -89,10 +94,15 @@ var ProactiveWorker = class {
89
94
  });
90
95
  const revoked = context.evidenceIds.some((id) => !latestContext.evidenceIds.includes(id));
91
96
  const changed = !isDeepStrictEqual(context.snapshotContent ?? context.content, latestContext.snapshotContent ?? latestContext.content);
97
+ const stale = !evidenceSourcesFresh(latestContext.evidenceIds, latestContext.content);
98
+ if (stale && !revoked) {
99
+ deferRunForSource(run);
100
+ return;
101
+ }
92
102
  const unavailable = context.evidenceIds.some((id) => id.startsWith("source-item:") && !authorizedConnectedSource(id.slice(12), effectiveProactivePolicy(run.subscriptionId).workspaceId, run.scenarioKey));
93
- const result = revoked || changed ? {
103
+ const result = revoked || changed || stale ? {
94
104
  result: "no_insight",
95
- reason: unavailable ? "source_unavailable" : "source_changed"
105
+ reason: unavailable ? "source_unavailable" : stale ? "source_stale" : "source_changed"
96
106
  } : parseAnalysisResult(output.text, new Set(latestContext.evidenceIds));
97
107
  const candidate = result.result === "insight" ? result.candidate : void 0;
98
108
  if (run.scenarioKey === "communication_follow_up" && (candidate?.decision || candidate?.proposedAction)) throw new Error("Communication preparation must return a draft or update; actions require the conversation confirmation flow");
@@ -21,7 +21,7 @@ interface SceneMoment {
21
21
  }
22
22
  export declare function startDelegation(workspace: string, value: unknown): {
23
23
  revision: number;
24
- level: "off" | "active" | "quiet" | "balanced";
24
+ level: "active" | "quiet" | "balanced";
25
25
  delivery: "digest" | "inbox" | "important";
26
26
  completedAt: string;
27
27
  scanIntervalMinutes: number;
@@ -58,6 +58,7 @@ export declare function delegationOverview(workspace: string): {
58
58
  latestDirection: string;
59
59
  lastSyncedAt: string;
60
60
  syncFailed: boolean;
61
+ sourceFresh: boolean;
61
62
  }[];
62
63
  delegations: {
63
64
  id: string;
@@ -67,6 +68,8 @@ export declare function delegationOverview(workspace: string): {
67
68
  enabled: boolean;
68
69
  revision: number;
69
70
  delivery: "digest" | "inbox" | "important";
71
+ scanIntervalMinutes: number;
72
+ checkStatus: import("@xopcai/gateway-contract/src/proactive.js").ProactiveCheckStatus;
70
73
  completedAt: string;
71
74
  userInstructions: string;
72
75
  updatedAt: string;
@@ -4,6 +4,7 @@ import { controlledSubscriptions, createControlledSubscription, requireSubscript
4
4
  import { ProactiveScenarioService } from "./scenarios/service.js";
5
5
  import { ProactiveEventService } from "./service.js";
6
6
  import { listMailFollowUps } from "./follow-ups.js";
7
+ import { followUpCheckStatus } from "./status.js";
7
8
  import { getCard } from "./inbox/cards.js";
8
9
  import { scanDueProjects } from "./temporal/schedule.js";
9
10
  import { z } from "zod";
@@ -69,6 +70,8 @@ function delegationOverview(workspace) {
69
70
  enabled: sub.enabled,
70
71
  revision: sub.revision,
71
72
  delivery: sub.delivery,
73
+ scanIntervalMinutes: sub.scanIntervalMinutes ?? 120,
74
+ checkStatus: followUpCheckStatus(sub.id, sub.scopeKind === "project"),
72
75
  completedAt: sub.completedAt,
73
76
  userInstructions: sub.userInstructions,
74
77
  updatedAt: sub.updatedAt,
@@ -36,6 +36,7 @@ export declare function mailFollowUpView(follow: MailFollowUp): {
36
36
  latestDirection: string;
37
37
  lastSyncedAt: string;
38
38
  syncFailed: boolean;
39
+ sourceFresh: boolean;
39
40
  };
40
41
  export declare function listMailFollowUps(workspace: string): {
41
42
  id: string;
@@ -53,6 +54,7 @@ export declare function listMailFollowUps(workspace: string): {
53
54
  latestDirection: string;
54
55
  lastSyncedAt: string;
55
56
  syncFailed: boolean;
57
+ sourceFresh: boolean;
56
58
  }[];
57
59
  export declare function mailFollowUpSources(workspace: string): {
58
60
  id: string;
@@ -77,6 +79,7 @@ export declare function startMailFollowUp(workspace: string, value: unknown): {
77
79
  latestDirection: string;
78
80
  lastSyncedAt: string;
79
81
  syncFailed: boolean;
82
+ sourceFresh: boolean;
80
83
  };
81
84
  export declare function updateMailFollowUp(workspace: string, id: string, value: unknown): {
82
85
  id: string;
@@ -94,6 +97,7 @@ export declare function updateMailFollowUp(workspace: string, id: string, value:
94
97
  latestDirection: string;
95
98
  lastSyncedAt: string;
96
99
  syncFailed: boolean;
100
+ sourceFresh: boolean;
97
101
  };
98
102
  /** Keep the existing chat and its exact connector confirmation flow as the write boundary. */
99
103
  export declare function continueMailFollowUp(workspace: string, id: string, sessionKey: string): {
@@ -113,6 +117,7 @@ export declare function continueMailFollowUp(workspace: string, id: string, sess
113
117
  latestDirection: string;
114
118
  lastSyncedAt: string;
115
119
  syncFailed: boolean;
120
+ sourceFresh: boolean;
116
121
  };
117
122
  connectionId: unknown;
118
123
  sourceItemId: string;
@@ -1,6 +1,7 @@
1
1
  import { getSqliteDatabase, runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
2
2
  import { listKnowledgeSourceItems } from "../storage/sqlite/knowledge-repository.js";
3
3
  import { getConnectorConnection } from "../storage/sqlite/connector-repository.js";
4
+ import { sourceFreshness } from "./source-freshness.js";
4
5
  import { emailFields, readMailThread } from "./mail-thread.js";
5
6
  import { authorizedConnectedSource } from "./execution/authorization.js";
6
7
  import { ProactiveConflict, effectiveProactivePolicy } from "./policy/service.js";
@@ -41,7 +42,7 @@ function authorizedMailThread(follow) {
41
42
  function mailFollowUpView(follow) {
42
43
  const thread = authorizedMailThread(follow);
43
44
  const latest = thread?.items.at(-1);
44
- const sync = thread ? getSqliteDatabase().prepare("SELECT status, error, finished_at FROM knowledge_sync_runs WHERE source_instance_id = ? ORDER BY started_at DESC LIMIT 1").get(thread.origin.sourceInstanceId) : void 0;
45
+ const sync = sourceFreshness(follow.source_item_id, Date.parse(follow.due_at) <= Date.now() ? follow.due_at : void 0);
45
46
  return {
46
47
  id: follow.id,
47
48
  subscriptionId: follow.subscription_id,
@@ -52,12 +53,13 @@ function mailFollowUpView(follow) {
52
53
  lastCheckedAt: follow.last_checked_at,
53
54
  sessionKey: follow.session_key,
54
55
  sourceAvailable: Boolean(thread),
55
- enabled: effectiveProactivePolicy(follow.subscription_id).enabled,
56
+ enabled: follow.status === "watching" && effectiveProactivePolicy(follow.subscription_id).enabled,
56
57
  subject: thread ? emailFields(thread.origin.normalizedText).subject ?? "Email follow-up" : null,
57
58
  latestMessageAt: latest?.occurredAt ?? null,
58
59
  latestDirection: latest ? emailFields(latest.normalizedText).labels?.includes("SENT") ? "sent" : emailFields(latest.normalizedText).labels?.includes("INBOX") ? "received" : "unknown" : null,
59
- lastSyncedAt: sync?.finished_at ? new Date(sync.finished_at).toISOString() : null,
60
- syncFailed: Boolean(sync?.error)
60
+ lastSyncedAt: sync.lastSyncedAt,
61
+ syncFailed: sync.syncFailed,
62
+ sourceFresh: sync.fresh
61
63
  };
62
64
  }
63
65
  function listMailFollowUps(workspace) {
@@ -137,11 +139,20 @@ function scanMailFollowUps(events, now = /* @__PURE__ */ new Date()) {
137
139
  const db = getSqliteDatabase();
138
140
  let published = 0;
139
141
  for (const follow of db.prepare("SELECT * FROM proactive_follow_ups WHERE status = 'watching'").all()) {
140
- if (!effectiveProactivePolicy(follow.subscription_id, now).enabled) continue;
142
+ const policy = effectiveProactivePolicy(follow.subscription_id, now);
143
+ if (!policy.enabled) continue;
141
144
  const thread = authorizedMailThread(follow);
142
145
  if (!thread) continue;
146
+ const freshness = sourceFreshness(follow.source_item_id, Date.parse(follow.due_at) <= now.getTime() ? follow.due_at : void 0, now.getTime());
147
+ if (!freshness.fresh) {
148
+ db.prepare("UPDATE proactive_follow_ups SET last_fingerprint = NULL WHERE id = ?").run(follow.id);
149
+ continue;
150
+ }
143
151
  const fingerprint = `${follow.revision}:${thread.fingerprint}:${Date.parse(follow.due_at) <= now.getTime() ? "due" : "waiting"}`;
144
- if (fingerprint !== follow.last_fingerprint) {
152
+ const awaitingSource = db.prepare(`SELECT 1 FROM proactive_runs r JOIN proactive_batch_events be USING(batch_id)
153
+ JOIN proactive_events e USING(event_id) WHERE r.subscription_id = ? AND r.status = 'retryable'
154
+ AND r.outcome_reason = 'source_stale' AND e.subject_kind = 'mail_follow_up' AND e.subject_id = ? LIMIT 1`).get(follow.subscription_id, follow.id);
155
+ if (fingerprint !== follow.last_fingerprint && !awaitingSource) {
145
156
  if (events.publish({
146
157
  type: "proactive.follow_up.v1",
147
158
  schemaVersion: 1,
@@ -156,7 +167,7 @@ function scanMailFollowUps(events, now = /* @__PURE__ */ new Date()) {
156
167
  actor: { kind: "system" },
157
168
  scope: { workspaceId: follow.workspace_id },
158
169
  occurredAt: now.toISOString(),
159
- dedupeKey: `mail-follow-up:${follow.id}:${fingerprint}`,
170
+ dedupeKey: `mail-follow-up:${follow.id}:${fingerprint}:${freshness.version}:${policy.preferences.revision}`,
160
171
  sensitivity: "personal",
161
172
  payload: { followUpId: follow.id }
162
173
  }, now).inserted) published++;
@@ -164,6 +164,7 @@ function applyCardAction(card, workspaceId, input) {
164
164
  if (input.taskDraft && (input.actionId !== "decide" || input.choice !== "approve" || !card.taskDraft)) throw new Error("Task draft requires a pending approval");
165
165
  if (input.artifact && input.actionId !== "edit_artifact") throw new Error("Artifact requires an edit action");
166
166
  if (input.instruction && input.actionId !== "refine") throw new Error("Instruction requires a refine action");
167
+ if (input.feedbackReason && input.actionId !== "not_useful") throw new Error("Feedback reason requires negative feedback");
167
168
  switch (input.actionId) {
168
169
  case "edit_artifact":
169
170
  if (!input.artifact || !card.artifact || input.artifact.kind !== card.artifact.kind) throw new Error("An existing artifact is required");
@@ -177,7 +178,7 @@ function applyCardAction(card, workspaceId, input) {
177
178
  break;
178
179
  case "useful":
179
180
  case "not_useful":
180
- service.feedback(card.id, input.actionId);
181
+ service.feedback(card.id, input.actionId, input.feedbackReason);
181
182
  break;
182
183
  case "refine":
183
184
  if (!input.instruction) throw new Error("Refinement instruction required");
@@ -1,9 +1,9 @@
1
1
  import { getSqliteDatabase, runSqliteWriteTransaction } from "../../storage/sqlite/transaction.js";
2
- import { insightSourcesAuthorized, insightSourcesChanged } from "../execution/authorization.js";
2
+ import { insightSourcesAuthorized, insightSourcesChanged, insightSourcesFresh } from "../execution/authorization.js";
3
3
  import { effectiveProactivePolicy, localProactiveDay, nextDigestTime, quietHoursEnd, reserveProactiveNotification } from "../policy/service.js";
4
4
  import { insightCorrelation } from "./lifecycle.js";
5
5
  import { getInboxItem } from "./repository.js";
6
- import { viewingProactiveCards } from "../policy/presence.js";
6
+ import { viewingProactiveCard } from "../policy/presence.js";
7
7
  import { queueDigest } from "./digest.js";
8
8
  import { notificationPlanFromGatewayEvent } from "../../notifications/planner.js";
9
9
  import { createHash } from "node:crypto";
@@ -12,8 +12,10 @@ import { createHash } from "node:crypto";
12
12
  function deliverProactiveCard(item, notifications, publish) {
13
13
  const result = runSqliteWriteTransaction(() => {
14
14
  const current = getInboxItem(item.id);
15
- if (!current || current.withdrawnAt || !insightSourcesAuthorized(current.insightId) || current.status === "resolved" || current.expiresAt && Date.parse(current.expiresAt) <= Date.now()) return null;
15
+ if (!current || current.withdrawnAt || !insightSourcesAuthorized(current.insightId) || ["resolved", "read"].includes(current.status) || current.expiresAt && Date.parse(current.expiresAt) <= Date.now()) return null;
16
16
  if (current.insight.actionStatus !== "completed" && insightSourcesChanged(current.insightId)) return null;
17
+ if (current.actionableUntil && Date.parse(current.actionableUntil) <= Date.now()) return null;
18
+ if (!insightSourcesFresh(current.insightId)) return { retryAt: new Date(Date.now() + 6e4).toISOString() };
17
19
  if (current.status === "snoozed") return { retryAt: current.snoozedUntil };
18
20
  const policy = effectiveProactivePolicy(current.subscriptionId);
19
21
  if (!policy.enabled || policy.settings.delivery === "inbox") return null;
@@ -32,10 +34,7 @@ function deliverProactiveCard(item, notifications, publish) {
32
34
  const db = getSqliteDatabase();
33
35
  const key = `proactive:${insightCorrelation(current.insightId)}:${createHash("sha256").update(JSON.stringify([current.insight.urgency, current.insight.decision ?? null])).digest("hex")}:${localProactiveDay(/* @__PURE__ */ new Date(), policy.preferences.timezone)}`;
34
36
  if (db.prepare("SELECT 1 FROM proactive_delivery_decisions WHERE notification_key = ?").get(key)) return null;
35
- if (policy.preferences.suppressWhileViewing && viewingProactiveCards(policy.workspaceId)) {
36
- db.prepare("INSERT INTO proactive_delivery_decisions VALUES (?, ?, ?, ?)").run(key, policy.workspaceId, "visible_in_app", (/* @__PURE__ */ new Date()).toISOString());
37
- return null;
38
- }
37
+ if (policy.preferences.suppressWhileViewing && viewingProactiveCard(policy.workspaceId, current.id, current.notificationRevision)) return { retryAt: new Date(Date.now() + 6e4).toISOString() };
39
38
  const decision = reserveProactiveNotification(current.subscriptionId, key);
40
39
  if (decision instanceof Date) return { retryAt: decision.toISOString() };
41
40
  if (decision === "suppressed") return null;
@@ -3,6 +3,6 @@ import type { NotificationPlan } from '../../notifications/planner.js';
3
3
  import type { InboxItem } from './types.js';
4
4
  export { nextDigestTime } from '../policy/service.js';
5
5
  export declare function queueDigest(item: InboxItem, mode: 'daily' | 'quiet', dueAt: Date): void;
6
- export declare function digestCards(id: string, workspaceId: string): import("@xopcai/gateway-contract").ProactiveCard[];
6
+ export declare function digestCards(id: string, workspaceId: string, forDelivery?: boolean): import("@xopcai/gateway-contract").ProactiveCard[];
7
7
  export declare function reserveWorkspaceAttention(workspaceId: string, key: string, now?: Date): boolean;
8
8
  export declare function flushDueDigests(persist: (plan: NotificationPlan) => ProductNotification | null, now?: Date): ProductNotification[];
@@ -1,9 +1,8 @@
1
1
  import { getSqliteDatabase, runSqliteWriteTransaction } from "../../storage/sqlite/transaction.js";
2
- import { insightSourcesAuthorized } from "../execution/authorization.js";
3
- import { effectiveProactivePolicy, localProactiveDay, nextDigestTime, proactivePreferences, quietHoursEnd } from "../policy/service.js";
2
+ import { insightSourcesAuthorized, insightSourcesChanged, insightSourcesFresh } from "../execution/authorization.js";
3
+ import { effectiveProactivePolicy, localProactiveDay, nextDigestTime, proactiveChecksAllowed, proactivePreferences, quietHoursEnd } from "../policy/service.js";
4
4
  import { getInboxItem } from "./repository.js";
5
5
  import { getCard } from "./cards.js";
6
- import { viewingProactiveCards } from "../policy/presence.js";
7
6
  import { randomUUID } from "node:crypto";
8
7
  //#region src/proactive/inbox/digest.ts
9
8
  function queueDigest(item, mode, dueAt) {
@@ -13,26 +12,27 @@ function queueDigest(item, mode, dueAt) {
13
12
  mode = excluded.mode, due_at = excluded.due_at, consumed_at = NULL
14
13
  WHERE proactive_digest_queue.notification_revision <> excluded.notification_revision`).run(item.id, policy.workspaceId, item.notificationRevision, mode, dueAt.toISOString());
15
14
  }
16
- function digestCards(id, workspaceId) {
15
+ function digestCards(id, workspaceId, forDelivery = false) {
17
16
  const digest = getSqliteDatabase().prepare("SELECT * FROM proactive_digests WHERE digest_id = ? AND workspace_id = ?").get(id, workspaceId);
18
17
  if (!digest) throw new Error("Digest not found");
19
18
  return getSqliteDatabase().prepare("SELECT inbox_item_id, notification_revision FROM proactive_digest_members WHERE digest_id = ?").all(id).flatMap((member) => {
20
19
  const item = getInboxItem(member.inbox_item_id);
21
- if (!item || !eligibleDigestItem(item, digest.occurrence_key.startsWith("daily:") ? "daily" : "quiet") || item.notificationRevision !== member.notification_revision) return [];
20
+ if (!item || item.withdrawnAt || item.expiresAt && Date.parse(item.expiresAt) <= Date.now() || item.actionableUntil && Date.parse(item.actionableUntil) <= Date.now() || item.notificationRevision !== member.notification_revision) return [];
21
+ if (forDelivery && !eligibleDigestItem(item, digest.occurrence_key.startsWith("daily:") ? "daily" : "quiet", false)) return [];
22
22
  const card = getCard(item.id, workspaceId);
23
- return card.status === "withdrawn" ? [] : [card];
23
+ return ["withdrawn", "expired"].includes(card.status) ? [] : [card];
24
24
  });
25
25
  }
26
- function eligibleDigestItem(item, mode) {
26
+ function eligibleDigestItem(item, mode, requireFresh = true) {
27
27
  const policy = effectiveProactivePolicy(item.subscriptionId);
28
28
  const daily = policy.preferences.digestEnabled || policy.settings.delivery === "digest";
29
29
  if (mode === "daily" ? !daily : policy.level === "quiet" && !daily) return false;
30
- return Boolean(policy.enabled && policy.settings.delivery !== "inbox" && !item.withdrawnAt && !["resolved", "snoozed"].includes(item.status) && (!item.expiresAt || Date.parse(item.expiresAt) > Date.now()) && insightSourcesAuthorized(item.insightId));
30
+ return Boolean(policy.enabled && policy.settings.delivery !== "inbox" && !item.withdrawnAt && !["resolved", "snoozed"].includes(item.status) && (!item.expiresAt || Date.parse(item.expiresAt) > Date.now()) && (!item.actionableUntil || Date.parse(item.actionableUntil) > Date.now()) && insightSourcesAuthorized(item.insightId) && !insightSourcesChanged(item.insightId) && (!requireFresh || insightSourcesFresh(item.insightId)));
31
31
  }
32
32
  function reserveWorkspaceAttention(workspaceId, key, now = /* @__PURE__ */ new Date()) {
33
33
  const db = getSqliteDatabase();
34
34
  const preferences = proactivePreferences(workspaceId);
35
- if (preferences.level === "off" || preferences.pausedUntil && Date.parse(preferences.pausedUntil) > now.getTime()) return false;
35
+ if (!proactiveChecksAllowed(preferences, now) || preferences.notificationsMuted) return false;
36
36
  if (db.prepare("SELECT 1 FROM proactive_notification_budget WHERE dedupe_key = ?").get(key)) return true;
37
37
  const day = localProactiveDay(now, preferences.timezone);
38
38
  const { count } = db.prepare("SELECT COUNT(*) AS count FROM proactive_notification_budget WHERE workspace_id = ? AND local_day = ?").get(workspaceId, day);
@@ -46,15 +46,25 @@ function flushDueDigests(persist, now = /* @__PURE__ */ new Date()) {
46
46
  const notifications = [];
47
47
  for (const group of groups) {
48
48
  const preferences = proactivePreferences(group.workspace_id);
49
+ if (!proactiveChecksAllowed(preferences, now)) {
50
+ db.prepare("UPDATE proactive_digest_queue SET due_at = ? WHERE workspace_id = ? AND mode = ? AND consumed_at IS NULL").run(new Date(now.getTime() + 6e4).toISOString(), group.workspace_id, group.mode);
51
+ continue;
52
+ }
49
53
  const until = quietHoursEnd(preferences, now);
50
54
  if (until) {
51
55
  db.prepare("UPDATE proactive_digest_queue SET due_at = ? WHERE workspace_id = ? AND mode = ? AND consumed_at IS NULL").run(until.toISOString(), group.workspace_id, group.mode);
52
56
  continue;
53
57
  }
54
58
  const queued = db.prepare("SELECT inbox_item_id FROM proactive_digest_queue WHERE workspace_id = ? AND mode = ? AND consumed_at IS NULL AND due_at <= ? ORDER BY due_at LIMIT 500").all(group.workspace_id, group.mode, now.toISOString());
59
+ const deferred = /* @__PURE__ */ new Set();
55
60
  const seen = /* @__PURE__ */ new Set();
56
61
  const items = queued.flatMap((row) => {
57
62
  const item = getInboxItem(row.inbox_item_id);
63
+ if (item && !item.withdrawnAt && !["read", "resolved"].includes(item.status) && (!item.actionableUntil || Date.parse(item.actionableUntil) > now.getTime()) && (!item.expiresAt || Date.parse(item.expiresAt) > now.getTime()) && insightSourcesAuthorized(item.insightId) && !insightSourcesChanged(item.insightId) && !insightSourcesFresh(item.insightId)) {
64
+ deferred.add(item.id);
65
+ db.prepare("UPDATE proactive_digest_queue SET due_at = ? WHERE inbox_item_id = ?").run(new Date(now.getTime() + 6e4).toISOString(), item.id);
66
+ return [];
67
+ }
58
68
  if (!item || !eligibleDigestItem(item, group.mode)) return [];
59
69
  const key = item.correlationKey ?? item.id;
60
70
  if (seen.has(key)) return [];
@@ -67,12 +77,12 @@ function flushDueDigests(persist, now = /* @__PURE__ */ new Date()) {
67
77
  db.prepare("UPDATE proactive_digest_queue SET due_at = ? WHERE workspace_id = ? AND mode = ? AND consumed_at IS NULL").run(tomorrow.toISOString(), group.workspace_id, group.mode);
68
78
  continue;
69
79
  }
70
- if (!items.length || preferences.suppressWhileViewing && viewingProactiveCards(group.workspace_id, now.getTime())) {
71
- for (const row of queued) db.prepare("UPDATE proactive_digest_queue SET consumed_at = ? WHERE inbox_item_id = ?").run(now.toISOString(), row.inbox_item_id);
80
+ if (!items.length) {
81
+ for (const row of queued.filter((row) => !deferred.has(row.inbox_item_id))) db.prepare("UPDATE proactive_digest_queue SET consumed_at = ? WHERE inbox_item_id = ?").run(now.toISOString(), row.inbox_item_id);
72
82
  continue;
73
83
  }
74
84
  const key = `proactive.digest:${group.workspace_id}:${occurrence}`;
75
- if (!reserveWorkspaceAttention(group.workspace_id, key, now)) {
85
+ if (!preferences.notificationsMuted && !reserveWorkspaceAttention(group.workspace_id, key, now)) {
76
86
  db.prepare("UPDATE proactive_digest_queue SET due_at = ? WHERE workspace_id = ? AND mode = ? AND consumed_at IS NULL").run(nextDigestTime(preferences, new Date(now.getTime() + 6e4)).toISOString(), group.workspace_id, group.mode);
77
87
  continue;
78
88
  }
@@ -103,7 +113,7 @@ function flushDueDigests(persist, now = /* @__PURE__ */ new Date()) {
103
113
  db.prepare("UPDATE proactive_digests SET notification_id = ? WHERE digest_id = ?").run(notification.id, id);
104
114
  notifications.push(notification);
105
115
  }
106
- for (const row of queued) db.prepare("UPDATE proactive_digest_queue SET consumed_at = ? WHERE inbox_item_id = ?").run(now.toISOString(), row.inbox_item_id);
116
+ for (const row of queued.filter((row) => !deferred.has(row.inbox_item_id))) db.prepare("UPDATE proactive_digest_queue SET consumed_at = ? WHERE inbox_item_id = ?").run(now.toISOString(), row.inbox_item_id);
107
117
  }
108
118
  return notifications;
109
119
  });
@@ -1 +1 @@
1
- export declare function recheckProactiveNotification(itemId: string, now?: Date, notificationRevision?: unknown): 'send' | 'cancel' | Date;
1
+ export declare function recheckProactiveNotification(itemId: string, now?: Date, notificationRevision?: unknown, forDigest?: boolean): 'send' | 'cancel' | Date;
@@ -1,18 +1,21 @@
1
- import { insightSourcesAuthorized, insightSourcesChanged } from "../execution/authorization.js";
1
+ import { insightSourcesAuthorized, insightSourcesChanged, insightSourcesFresh } from "../execution/authorization.js";
2
2
  import { effectiveProactivePolicy, quietHoursEnd } from "../policy/service.js";
3
3
  import { getInboxItem } from "./repository.js";
4
4
  //#region src/proactive/inbox/notification-policy.ts
5
- function recheckProactiveNotification(itemId, now = /* @__PURE__ */ new Date(), notificationRevision) {
5
+ function recheckProactiveNotification(itemId, now = /* @__PURE__ */ new Date(), notificationRevision, forDigest = false) {
6
6
  const item = getInboxItem(itemId);
7
- if (!item || item.withdrawnAt || !insightSourcesAuthorized(item.insightId) || item.status === "resolved" || item.expiresAt && Date.parse(item.expiresAt) <= now.getTime()) return "cancel";
7
+ if (!item || item.withdrawnAt || !insightSourcesAuthorized(item.insightId) || ["resolved", "read"].includes(item.status) || item.expiresAt && Date.parse(item.expiresAt) <= now.getTime()) return "cancel";
8
8
  if (item.insight.actionStatus !== "completed" && insightSourcesChanged(item.insightId)) return "cancel";
9
+ if (item.actionableUntil && Date.parse(item.actionableUntil) <= now.getTime()) return "cancel";
10
+ if (!insightSourcesFresh(item.insightId)) return new Date(now.getTime() + 6e4);
9
11
  if (typeof notificationRevision === "number" && notificationRevision !== item.notificationRevision) return "cancel";
10
12
  const policy = effectiveProactivePolicy(item.subscriptionId, now);
11
- if (!policy.enabled || policy.preferences.digestEnabled && item.insight.attentionKind !== "receipt") return "cancel";
12
- if (policy.level !== "active" && ["low", "medium"].includes(item.insight.urgency) && !item.insight.decision) return "cancel";
13
+ if (!policy.enabled || policy.preferences.notificationsMuted || policy.settings.delivery === "inbox") return "cancel";
14
+ if (!forDigest && policy.preferences.digestEnabled && item.insight.attentionKind !== "receipt") return "cancel";
15
+ if (!forDigest && policy.level !== "active" && ["low", "medium"].includes(item.insight.urgency) && !item.insight.decision) return "cancel";
13
16
  if (item.status === "snoozed") return new Date(item.snoozedUntil);
14
17
  {
15
- if (policy.level === "quiet" || policy.settings.delivery !== "important") return "cancel";
18
+ if (!forDigest && (policy.level === "quiet" || policy.settings.delivery !== "important")) return "cancel";
16
19
  const until = quietHoursEnd(policy.preferences, now);
17
20
  if (until) return until;
18
21
  }
@@ -15,6 +15,7 @@ function itemFromRow(row) {
15
15
  correlationKey: row.correlation_key ? s(row, "correlation_key") : void 0,
16
16
  revision: Number(row.revision),
17
17
  notificationRevision: Number(row.notification_revision),
18
+ ...row.actionable_until ? { actionableUntil: s(row, "actionable_until") } : {},
18
19
  ...row.expires_at ? { expiresAt: s(row, "expires_at") } : {},
19
20
  status: s(row, "status"),
20
21
  ...row.snoozed_until ? { snoozedUntil: s(row, "snoozed_until") } : {},
@@ -128,7 +129,7 @@ function projectInsightsToInbox(now = /* @__PURE__ */ new Date()) {
128
129
  };
129
130
  const materialChange = previous && ((urgencyRank[String(row.urgency)] ?? 0) > (urgencyRank[previous.urgency] ?? 0) || row.decision_json !== previous.decision_json || previous.expires_at && previous.expires_at <= nowIso);
130
131
  if (previous) db.prepare(`UPDATE proactive_inbox_items SET insight_id = ?, updated_at = ?,
131
- notification_revision = notification_revision + ? WHERE inbox_item_id = ?`).run(String(row.insight_id), nowIso, materialChange ? 1 : 0, id);
132
+ status = CASE WHEN ? THEN 'unread' ELSE status END, notification_revision = notification_revision + ? WHERE inbox_item_id = ?`).run(String(row.insight_id), nowIso, materialChange ? 1 : 0, materialChange ? 1 : 0, id);
132
133
  else db.prepare(`INSERT INTO proactive_inbox_items (inbox_item_id, insight_id, status, created_at, updated_at)
133
134
  VALUES (?, ?, 'unread', ?, ?)`).run(id, String(row.insight_id), nowIso, nowIso);
134
135
  {
@@ -138,7 +139,7 @@ function projectInsightsToInbox(now = /* @__PURE__ */ new Date()) {
138
139
  WHERE x.insight_id = ? AND e.type = 'connected_source.calendar_window.v1' ORDER BY e.occurred_at DESC LIMIT 1`).get(String(row.insight_id));
139
140
  const meetingStart = meeting ? JSON.parse(meeting.payload_json).meetingStartsAt : void 0;
140
141
  const expiresAt = typeof meetingStart === "string" && Number.isFinite(Date.parse(meetingStart)) ? meetingStart : new Date(now.getTime() + 7 * 864e5).toISOString();
141
- db.prepare("UPDATE proactive_inbox_items SET expires_at = ? WHERE inbox_item_id = ?").run(expiresAt, id);
142
+ db.prepare("UPDATE proactive_inbox_items SET expires_at = ?, actionable_until = ? WHERE inbox_item_id = ?").run(expiresAt, typeof meetingStart === "string" ? meetingStart : null, id);
142
143
  }
143
144
  const quietEnd = nextQuietHoursEnd(policy.quietHours, now)?.getTime() ?? now.getTime();
144
145
  const cooldownEnd = previous?.delivered_at ? Date.parse(previous.delivered_at) + 4 * 36e5 : now.getTime();
@@ -7,7 +7,13 @@ export declare class ProactiveInboxService {
7
7
  feedback: typeof recordFeedback;
8
8
  decide(id: string, choice: string, note?: string): import("./types.js").InboxItem;
9
9
  instruct(id: string, instruction: string): {
10
+ followUpId: string;
11
+ revision: number;
12
+ revisionId?: undefined;
13
+ } | {
10
14
  revisionId: string;
15
+ followUpId?: undefined;
16
+ revision?: undefined;
11
17
  };
12
18
  transition(id: string, input: {
13
19
  status: InboxStatus;
@@ -1,4 +1,6 @@
1
+ import { getSqliteDatabase } from "../../storage/sqlite/transaction.js";
1
2
  import { publishInstructionFeedback } from "../scenarios/repository.js";
3
+ import { requireMailFollowUp, updateMailFollowUp } from "../follow-ups.js";
2
4
  import { executePendingProactiveActions } from "../actions/service.js";
3
5
  import { getInboxItem, getInboxSubscriptionId, listInbox, projectInsightsToInbox, recordDecision, recordFeedback, transitionInboxItem, wakeSnoozedItems } from "./repository.js";
4
6
  //#region src/proactive/inbox/service.ts
@@ -13,6 +15,22 @@ var ProactiveInboxService = class {
13
15
  return getInboxItem(id) ?? item;
14
16
  }
15
17
  instruct(id, instruction) {
18
+ const row = getSqliteDatabase().prepare(`SELECT s.workspace_id, c.content_json FROM proactive_inbox_items i
19
+ JOIN proactive_insights x USING(insight_id) JOIN proactive_runs r USING(run_id)
20
+ JOIN proactive_scenario_subscriptions s ON s.subscription_id = x.subscription_id
21
+ JOIN proactive_context_snapshots c ON c.snapshot_id = r.context_snapshot_id WHERE i.inbox_item_id = ?`).get(id);
22
+ const followId = row && JSON.parse(row.content_json).follow_up?.followUpId;
23
+ if (row && followId) {
24
+ const follow = requireMailFollowUp(row.workspace_id, followId);
25
+ const updated = updateMailFollowUp(row.workspace_id, follow.id, {
26
+ expectedRevision: follow.revision,
27
+ instructions: `${follow.instructions}\n${instruction}`
28
+ });
29
+ return {
30
+ followUpId: updated.id,
31
+ revision: updated.revision
32
+ };
33
+ }
16
34
  return { revisionId: publishInstructionFeedback({
17
35
  subscriptionId: getInboxSubscriptionId(id),
18
36
  inboxItemId: id,
@@ -7,6 +7,7 @@ export interface InboxItem {
7
7
  revision?: number;
8
8
  notificationRevision?: number;
9
9
  expiresAt?: string;
10
+ actionableUntil?: string;
10
11
  withdrawnAt?: string;
11
12
  correlationKey?: string;
12
13
  status: InboxStatus;
@@ -1,2 +1,2 @@
1
1
  export declare function recordProactivePresence(workspaceId: string, value: unknown, now?: number): void;
2
- export declare function viewingProactiveCards(workspaceId: string, now?: number): boolean;
2
+ export declare function viewingProactiveCard(workspaceId: string, itemId: string, revision: number, now?: number): boolean;