@xopcai/xopc 0.0.284 → 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 (224) 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-CwbIMGLY.js → activity-store-B0BTIk9j.js} +1 -1
  4. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-DjiOld-S.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-3lphBVuY.js → api-Coly9ab6.js} +1 -1
  9. package/dist/gateway/static/root/assets/{app-management-settings-panel-B2nwCXxE.js → app-management-settings-panel-TWBO-msg.js} +1 -1
  10. package/dist/gateway/static/root/assets/{appearance-settings-BXO4Ej88.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-6ZRqNjla.js → archive-plugin-z_u6alob.js} +1 -1
  13. package/dist/gateway/static/root/assets/{attachment-load-CmPZalcW.js → attachment-load-C2nS04TC.js} +1 -1
  14. package/dist/gateway/static/root/assets/{automations-page-Bc3WbK5n.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-OWR6cxpc.js → binary-plugins-CGmXoZnl.js} +1 -1
  17. package/dist/gateway/static/root/assets/{block-editor-Ca-tDQXm.js → block-editor-CtvzCZhC.js} +3 -3
  18. package/dist/gateway/static/root/assets/{browser-automation-inputs-Dxy2_p3U.js → browser-automation-inputs-BOTU1isH.js} +1 -1
  19. package/dist/gateway/static/root/assets/{browser-automations-page-CibQIxpi.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-Bh-G9mdZ.js → channels-status-swr-Dfi0Desh.js} +1 -1
  24. package/dist/gateway/static/root/assets/{chat-terminal-dock-lbzbSpV3.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-tYvbTfZT.js → date-picker-DXOvbHCr.js} +1 -1
  27. package/dist/gateway/static/root/assets/{dependency-picker-B8yo7-7V.js → dependency-picker-BEnC-eS0.js} +1 -1
  28. package/dist/gateway/static/root/assets/{desktop-pet-BUGkGB4-.js → desktop-pet-DtPNvznL.js} +1 -1
  29. package/dist/gateway/static/root/assets/{desktop-pet-settings-DNeVGZNp.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-CiK0QI2p.js → extension-debug-page-H_aPMGXW.js} +1 -1
  32. package/dist/gateway/static/root/assets/{extension-page-CirPNHJd.js → extension-page-C1JnpeMt.js} +1 -1
  33. package/dist/gateway/static/root/assets/{extension-settings-page-Baua1_Le.js → extension-settings-page-C75kCOqe.js} +1 -1
  34. package/dist/gateway/static/root/assets/{gateway-config-swr-Co4hHe0i.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-DBPNJG-e.js → gateway-startup-retry-GP31PUrG.js} +1 -1
  37. package/dist/gateway/static/root/assets/{heartbeat-settings-DTM_kYpE.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-D0rMTumD.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-C9aQA4Dr.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-CMUaF61L.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-CHxyT09t.js → local-apps-page-BtgDJ92Q.js} +1 -1
  46. package/dist/gateway/static/root/assets/{locale-store-DO6JJ1l2.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-jXbA2S54.js → management-settings-DasZxCyD.js} +1 -1
  49. package/dist/gateway/static/root/assets/{markdown-view-CKInRsDh.js → markdown-view-6oYdBahT.js} +1 -1
  50. package/dist/gateway/static/root/assets/{media-plugins-BaPkSvW7.js → media-plugins-CxthbBvk.js} +1 -1
  51. package/dist/gateway/static/root/assets/{messages-BgZRsCkG.js → messages-Ls1Aaiqd.js} +1 -1
  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-CdYemUOu.js → page-tabs-BG39KidE.js} +1 -1
  55. package/dist/gateway/static/root/assets/{preview-open-alternatives-DzgF1yYO.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-iQ-eW6Aj.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-DMNudT60.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-u_wVepf7.js → runtime-tools-settings-panel-Wkru8_My.js} +2 -2
  63. package/dist/gateway/static/root/assets/{schema-form-YdZj2xdI.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-Bw6xKOX5.js → settings-form-section-wcAdCkK8.js} +1 -1
  67. package/dist/gateway/static/root/assets/{settings-page-CV8kVdnE.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-BHwjHlrV.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-DRKdb6O5.js → skills-page-AmqtBJo7.js} +1 -1
  73. package/dist/gateway/static/root/assets/{skills-page.utils-DYfaLtts.js → skills-page.utils-DtlfCgJp.js} +1 -1
  74. package/dist/gateway/static/root/assets/{system-settings-panel-CWrWpKV-.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-CDc7Wqy5.js → theme-store-ng8KJsAT.js} +1 -1
  77. package/dist/gateway/static/root/assets/{tunnel-api-Bmsy-5Fs.js → tunnel-api-Boajol0M.js} +1 -1
  78. package/dist/gateway/static/root/assets/{use-autosave-D-FRib85.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-iQ7EBzII.js → voice-api-key-field-SE3OQI1s.js} +1 -1
  81. package/dist/gateway/static/root/assets/{voice-config-api-DNMWA2tx.js → voice-config-api-VNuCmdFe.js} +1 -1
  82. package/dist/gateway/static/root/assets/{work-discovery-overlay-BesuhDlH.js → work-discovery-overlay-Dn1vy2oq.js} +1 -1
  83. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-Du81FoCy.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-manifest.js +2 -2
  95. package/dist/src/cli/commands/agent.js +2 -2
  96. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  97. package/dist/src/cli/commands/gateway/lifecycle.js +2 -2
  98. package/dist/src/cli/commands/gateway/run-foreground.js +3 -0
  99. package/dist/src/cli/commands/import.js +54 -6
  100. package/dist/src/cli/commands/models.js +1 -1
  101. package/dist/src/cli/commands/onboard.js +1 -1
  102. package/dist/src/cli/commands/platform.js +1 -1
  103. package/dist/src/cli/commands/profile.js +1 -1
  104. package/dist/src/cli/commands/tunnel.js +1 -1
  105. package/dist/src/cli/commands/update.js +1 -1
  106. package/dist/src/cli/extension-cli-register.js +1 -1
  107. package/dist/src/config/index.js +1 -1
  108. package/dist/src/discussions/action-tasks.js +1 -1
  109. package/dist/src/discussions/edits.js +1 -1
  110. package/dist/src/discussions/index.js +1 -1
  111. package/dist/src/discussions/organizer.js +1 -1
  112. package/dist/src/discussions/recording.js +1 -1
  113. package/dist/src/discussions/sealer.js +1 -1
  114. package/dist/src/discussions/service.js +2 -2
  115. package/dist/src/endpoint-tools/invocation-service.d.ts +2 -0
  116. package/dist/src/endpoint-tools/invocation-service.js +48 -19
  117. package/dist/src/gateway/heartbeat/check-store.d.ts +18 -0
  118. package/dist/src/gateway/heartbeat/check-store.js +73 -0
  119. package/dist/src/gateway/heartbeat/service.d.ts +12 -3
  120. package/dist/src/gateway/heartbeat/service.js +62 -45
  121. package/dist/src/gateway/hono/routes/discussions.js +2 -2
  122. package/dist/src/gateway/hono/routes/imports.js +18 -6
  123. package/dist/src/gateway/hono/routes/proactive-control.js +2 -1
  124. package/dist/src/gateway/run-loop.js +1 -1
  125. package/dist/src/gateway/service.d.ts +7 -0
  126. package/dist/src/gateway/service.js +24 -9
  127. package/dist/src/heartbeat/index.js +1 -1
  128. package/dist/src/imports/importService.d.ts +17 -12
  129. package/dist/src/imports/importService.js +67 -70
  130. package/dist/src/imports/inventory.d.ts +11 -0
  131. package/dist/src/imports/inventory.js +202 -0
  132. package/dist/src/imports/productImport.d.ts +4 -4
  133. package/dist/src/imports/productImport.js +201 -108
  134. package/dist/src/imports/runtime.js +7 -2
  135. package/dist/src/imports/scanner.d.ts +1 -1
  136. package/dist/src/imports/scanner.js +4 -4
  137. package/dist/src/imports/types.d.ts +53 -0
  138. package/dist/src/infra/background-task.d.ts +2 -0
  139. package/dist/src/infra/background-task.js +25 -0
  140. package/dist/src/infra/process-diagnostics.d.ts +5 -0
  141. package/dist/src/infra/process-diagnostics.js +97 -0
  142. package/dist/src/infra/unhandled-rejections.js +6 -3
  143. package/dist/src/infra/update-restart.js +1 -1
  144. package/dist/src/knowledge/ingestion-service.js +1 -0
  145. package/dist/src/media-shared/http/ssrf-guard.js +1 -1
  146. package/dist/src/notifications/proactive-policy.js +7 -5
  147. package/dist/src/proactive/execution/authorization.d.ts +2 -0
  148. package/dist/src/proactive/execution/authorization.js +10 -1
  149. package/dist/src/proactive/execution/context.js +1 -0
  150. package/dist/src/proactive/execution/repository.d.ts +2 -0
  151. package/dist/src/proactive/execution/repository.js +19 -2
  152. package/dist/src/proactive/execution/worker.js +14 -4
  153. package/dist/src/proactive/experience.d.ts +4 -1
  154. package/dist/src/proactive/experience.js +3 -0
  155. package/dist/src/proactive/follow-ups.d.ts +5 -0
  156. package/dist/src/proactive/follow-ups.js +18 -7
  157. package/dist/src/proactive/inbox/cards.js +2 -1
  158. package/dist/src/proactive/inbox/delivery.js +6 -7
  159. package/dist/src/proactive/inbox/digest.d.ts +1 -1
  160. package/dist/src/proactive/inbox/digest.js +23 -13
  161. package/dist/src/proactive/inbox/notification-policy.d.ts +1 -1
  162. package/dist/src/proactive/inbox/notification-policy.js +9 -6
  163. package/dist/src/proactive/inbox/repository.js +3 -2
  164. package/dist/src/proactive/inbox/service.d.ts +6 -0
  165. package/dist/src/proactive/inbox/service.js +18 -0
  166. package/dist/src/proactive/inbox/types.d.ts +1 -0
  167. package/dist/src/proactive/policy/presence.d.ts +1 -1
  168. package/dist/src/proactive/policy/presence.js +7 -5
  169. package/dist/src/proactive/policy/service.d.ts +8 -5
  170. package/dist/src/proactive/policy/service.js +12 -12
  171. package/dist/src/proactive/scenarios/control.d.ts +3 -3
  172. package/dist/src/proactive/scenarios/control.js +12 -2
  173. package/dist/src/proactive/source-freshness.d.ts +8 -0
  174. package/dist/src/proactive/source-freshness.js +27 -0
  175. package/dist/src/proactive/status.d.ts +2 -0
  176. package/dist/src/proactive/status.js +21 -0
  177. package/dist/src/proactive/temporal/worker.js +2 -1
  178. package/dist/src/realtime/runtime.js +31 -5
  179. package/dist/src/realtime/writer.js +16 -9
  180. package/dist/src/storage/sqlite/import-repository.d.ts +2 -3
  181. package/dist/src/storage/sqlite/import-repository.js +3 -3
  182. package/dist/src/storage/sqlite/import-selection-repository.d.ts +15 -0
  183. package/dist/src/storage/sqlite/import-selection-repository.js +28 -0
  184. package/dist/src/storage/sqlite/knowledge-repository.d.ts +1 -0
  185. package/dist/src/storage/sqlite/knowledge-repository.js +2 -2
  186. package/dist/src/storage/sqlite/migrations/175_import_selections.sql +8 -0
  187. package/dist/src/storage/sqlite/migrations/176_proactive_quality.sql +5 -0
  188. package/dist/src/storage/sqlite/migrations/177_proactive_check_controls.sql +25 -0
  189. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  190. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  191. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  192. package/dist/src/storage/sqlite/schema.js +1 -1
  193. package/dist/src/work-discovery/candidate-discovery.js +1 -1
  194. package/dist/src/work-discovery/probe.js +1 -1
  195. package/package.json +2 -2
  196. package/dist/gateway/static/root/assets/agent-defaults-settings-panel-D9qP10BK.js +0 -1
  197. package/dist/gateway/static/root/assets/agents-D-bkOuAl.js +0 -1
  198. package/dist/gateway/static/root/assets/apps-page-BKwBBwJN.js +0 -1
  199. package/dist/gateway/static/root/assets/automations-workspace-B2-f9qZu.js +0 -6
  200. package/dist/gateway/static/root/assets/browser-settings-page-Dtn4rMeq.js +0 -8
  201. package/dist/gateway/static/root/assets/capabilities-settings-panel-DiwtR9MD.js +0 -8
  202. package/dist/gateway/static/root/assets/channels-settings-pTdR1q1K.js +0 -1
  203. package/dist/gateway/static/root/assets/connectors-page-BSQ7Hs2w.js +0 -2
  204. package/dist/gateway/static/root/assets/device-pairing-wizard-CYkT7ecp.js +0 -1
  205. package/dist/gateway/static/root/assets/gateway-settings-CzpSYWex.js +0 -1
  206. package/dist/gateway/static/root/assets/home-page-CSSJvdd6.js +0 -1
  207. package/dist/gateway/static/root/assets/imports-page-XqVVYbAp.js +0 -1
  208. package/dist/gateway/static/root/assets/index-B_Sg5BKz.css +0 -2
  209. package/dist/gateway/static/root/assets/local-app-workbench-page-CZSgTYuI.js +0 -2
  210. package/dist/gateway/static/root/assets/logs-page-z5ktnEzX.js +0 -2
  211. package/dist/gateway/static/root/assets/note-detail-panel-DSaZfkna.js +0 -5
  212. package/dist/gateway/static/root/assets/notes-page-CePEyv_C.js +0 -8
  213. package/dist/gateway/static/root/assets/proactive-page-Cy2Gm2f8.js +0 -1
  214. package/dist/gateway/static/root/assets/project-detail-page-BliwYpns.js +0 -1
  215. package/dist/gateway/static/root/assets/projects-page-Dm-BqnQn.js +0 -1
  216. package/dist/gateway/static/root/assets/remote-access-hub-C_XuQ9XR.js +0 -1
  217. package/dist/gateway/static/root/assets/sessions-page-Drt1aS2G.js +0 -1
  218. package/dist/gateway/static/root/assets/settings-advanced-gate-BAUd06_9.js +0 -1
  219. package/dist/gateway/static/root/assets/setup-status-panel-BHI9e5Tw.js +0 -1
  220. package/dist/gateway/static/root/assets/shares-settings-DOAOjZBQ.js +0 -1
  221. package/dist/gateway/static/root/assets/skill-reload-api-DU0VQpHj.js +0 -1
  222. package/dist/gateway/static/root/assets/task-detail-page-DiPgMmBM.js +0 -1
  223. package/dist/gateway/static/root/assets/user-model-page-DxRaRdA0.js +0 -2
  224. package/dist/gateway/static/root/assets/workflows-page-CZikdgNw.js +0 -6
@@ -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;
@@ -8,7 +8,9 @@ const PresenceSchema = z.object({
8
8
  "web",
9
9
  "electron",
10
10
  "mobile"
11
- ])
11
+ ]),
12
+ inboxItemId: z.string().min(1).max(128).optional(),
13
+ notificationRevision: z.number().int().positive().optional()
12
14
  }).strict();
13
15
  function recordProactivePresence(workspaceId, value, now = Date.now()) {
14
16
  const input = PresenceSchema.parse(value);
@@ -18,10 +20,10 @@ function recordProactivePresence(workspaceId, value, now = Date.now()) {
18
20
  db.prepare("DELETE FROM proactive_presence WHERE workspace_id = ? AND client_id = ?").run(workspaceId, input.clientId);
19
21
  return;
20
22
  }
21
- db.prepare(`INSERT INTO proactive_presence VALUES (?, ?, ?, ?) ON CONFLICT(workspace_id, client_id) DO UPDATE SET surface = excluded.surface, expires_at = excluded.expires_at`).run(workspaceId, input.clientId, input.surface, now + 75e3);
23
+ db.prepare(`INSERT INTO proactive_presence(workspace_id, client_id, surface, expires_at, inbox_item_id, notification_revision) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT(workspace_id, client_id) DO UPDATE SET surface = excluded.surface, expires_at = excluded.expires_at, inbox_item_id = excluded.inbox_item_id, notification_revision = excluded.notification_revision`).run(workspaceId, input.clientId, input.surface, now + 75e3, input.inboxItemId ?? null, input.notificationRevision ?? null);
22
24
  }
23
- function viewingProactiveCards(workspaceId, now = Date.now()) {
24
- return Boolean(getSqliteDatabase().prepare("SELECT 1 FROM proactive_presence WHERE workspace_id = ? AND expires_at > ?").get(workspaceId, now));
25
+ function viewingProactiveCard(workspaceId, itemId, revision, now = Date.now()) {
26
+ return Boolean(getSqliteDatabase().prepare("SELECT 1 FROM proactive_presence WHERE workspace_id = ? AND inbox_item_id = ? AND notification_revision = ? AND expires_at > ?").get(workspaceId, itemId, revision, now));
25
27
  }
26
28
  //#endregion
27
- export { recordProactivePresence, viewingProactiveCards };
29
+ export { recordProactivePresence, viewingProactiveCard };
@@ -3,7 +3,7 @@ export declare class ProactiveConflict extends Error {
3
3
  }
4
4
  export declare function subscriptionSettings(id: string): {
5
5
  revision: number;
6
- level: "off" | "active" | "quiet" | "balanced";
6
+ level: "active" | "quiet" | "balanced";
7
7
  delivery: "digest" | "inbox" | "important";
8
8
  completedAt: string;
9
9
  scanIntervalMinutes: number;
@@ -13,17 +13,17 @@ export declare function proactivePreferences(workspaceId: string): ProactivePref
13
13
  export declare function updateProactivePreferences(workspaceId: string, value: unknown): ProactivePreferences;
14
14
  export declare function effectiveProactivePolicy(subscriptionId: string, now?: Date): {
15
15
  enabled: boolean;
16
- level: "off" | "active" | "quiet" | "balanced";
16
+ level: "active" | "quiet" | "balanced";
17
17
  settings: {
18
18
  revision: number;
19
- level: "off" | "active" | "quiet" | "balanced";
19
+ level: "active" | "quiet" | "balanced";
20
20
  delivery: "digest" | "inbox" | "important";
21
21
  completedAt: string;
22
22
  scanIntervalMinutes: number;
23
23
  userInstructions: string;
24
24
  };
25
25
  preferences: {
26
- level: "off" | "active" | "quiet" | "balanced";
26
+ level: "active" | "quiet" | "balanced";
27
27
  timezone: string;
28
28
  quietStartHour: number;
29
29
  quietEndHour: number;
@@ -38,7 +38,9 @@ export declare function effectiveProactivePolicy(subscriptionId: string, now?: D
38
38
  publicUrl: string;
39
39
  accountId?: string;
40
40
  };
41
- pausedUntil: string;
41
+ checksPaused: boolean;
42
+ checksPausedUntil: string;
43
+ notificationsMuted: boolean;
42
44
  revision: number;
43
45
  };
44
46
  workspaceId: string;
@@ -49,3 +51,4 @@ export declare function quietHoursEnd(preferences: ProactivePreferences, now: Da
49
51
  /** Called in the same transaction as notification persistence. Reservations survive retries. */
50
52
  export declare function reserveProactiveNotification(subscriptionId: string, dedupeKey: string, now?: Date): 'allowed' | 'suppressed' | Date;
51
53
  export declare function nextDigestTime(preferences: ProactivePreferences, after: Date): Date;
54
+ export declare function proactiveChecksAllowed(preferences: ProactivePreferences, now?: Date): boolean;
@@ -29,15 +29,12 @@ function updateProactivePreferences(workspaceId, value) {
29
29
  if (next.preferredChannel === "telegram" && !next.telegram) throw new Error("Telegram destination is required");
30
30
  db.prepare(`INSERT INTO proactive_preferences(workspace_id, preferences_json, revision) VALUES (?, ?, ?)
31
31
  ON CONFLICT(workspace_id) DO UPDATE SET preferences_json = excluded.preferences_json, revision = excluded.revision`).run(workspaceId, JSON.stringify(next), next.revision);
32
- if (next.timezone !== current.timezone || next.digestHour !== current.digestHour || next.digestMinute !== current.digestMinute) db.prepare("UPDATE proactive_digest_queue SET due_at = ? WHERE workspace_id = ? AND mode = 'daily' AND consumed_at IS NULL").run(nextDigestTime(next, /* @__PURE__ */ new Date()).toISOString(), workspaceId);
33
- if (next.level === "off") {
34
- db.prepare("DELETE FROM proactive_digest_queue WHERE workspace_id = ?").run(workspaceId);
35
- db.prepare(`UPDATE proactive_delivery_outbox SET status = 'delivered', error_message = 'proactive_disabled'
36
- WHERE status IN ('pending', 'retryable') AND inbox_item_id IN (
37
- SELECT i.inbox_item_id FROM proactive_inbox_items i JOIN proactive_insights x USING(insight_id)
38
- JOIN proactive_scenario_subscriptions s USING(subscription_id) WHERE s.workspace_id = ?
39
- )`).run(workspaceId);
32
+ if (proactiveChecksAllowed(current) && !proactiveChecksAllowed(next)) {
33
+ db.prepare(`UPDATE proactive_schedule_state SET last_fingerprint = NULL WHERE subscription_id IN
34
+ (SELECT subscription_id FROM proactive_scenario_subscriptions WHERE workspace_id = ?)`).run(workspaceId);
35
+ db.prepare("UPDATE proactive_follow_ups SET last_fingerprint = NULL WHERE workspace_id = ? AND status = 'watching'").run(workspaceId);
40
36
  }
37
+ if (next.timezone !== current.timezone || next.digestHour !== current.digestHour || next.digestMinute !== current.digestMinute) db.prepare("UPDATE proactive_digest_queue SET due_at = ? WHERE workspace_id = ? AND mode = 'daily' AND consumed_at IS NULL").run(nextDigestTime(next, /* @__PURE__ */ new Date()).toISOString(), workspaceId);
41
38
  return next;
42
39
  });
43
40
  }
@@ -45,9 +42,9 @@ function effectiveProactivePolicy(subscriptionId, now = /* @__PURE__ */ new Date
45
42
  const sub = getSqliteDatabase().prepare("SELECT workspace_id, enabled FROM proactive_scenario_subscriptions WHERE subscription_id = ?").get(subscriptionId);
46
43
  const settings = subscriptionSettings(subscriptionId);
47
44
  const preferences = proactivePreferences(sub?.workspace_id ?? "default");
48
- const level = preferences.level === "off" ? "off" : settings.level ?? preferences.level;
45
+ const level = settings.level ?? preferences.level;
49
46
  return {
50
- enabled: Boolean(sub?.enabled) && !settings.completedAt && level !== "off" && !(preferences.pausedUntil && Date.parse(preferences.pausedUntil) > now.getTime()),
47
+ enabled: Boolean(sub?.enabled) && !settings.completedAt && proactiveChecksAllowed(preferences, now),
51
48
  level,
52
49
  settings,
53
50
  preferences,
@@ -84,7 +81,7 @@ function quietHoursEnd(preferences, now) {
84
81
  function reserveProactiveNotification(subscriptionId, dedupeKey, now = /* @__PURE__ */ new Date()) {
85
82
  return runSqliteWriteTransaction((db) => {
86
83
  const policy = effectiveProactivePolicy(subscriptionId, now);
87
- if (!policy.enabled) return "suppressed";
84
+ if (!policy.enabled || policy.preferences.notificationsMuted) return "suppressed";
88
85
  if (policy.level === "quiet" || policy.settings.delivery === "inbox") return "suppressed";
89
86
  const end = quietHoursEnd(policy.preferences, now);
90
87
  if (end) return end;
@@ -116,5 +113,8 @@ function nextDigestTime(preferences, after) {
116
113
  }
117
114
  return new Date(after.getTime() + 864e5);
118
115
  }
116
+ function proactiveChecksAllowed(preferences, now = /* @__PURE__ */ new Date()) {
117
+ return !preferences.checksPaused && !(preferences.checksPausedUntil && Date.parse(preferences.checksPausedUntil) > now.getTime());
118
+ }
119
119
  //#endregion
120
- export { ProactiveConflict, effectiveProactivePolicy, localProactiveDay, nextDigestTime, proactivePreferences, quietHoursEnd, reserveProactiveNotification, subscriptionSettings, updateProactivePreferences };
120
+ export { ProactiveConflict, effectiveProactivePolicy, localProactiveDay, nextDigestTime, proactiveChecksAllowed, proactivePreferences, quietHoursEnd, reserveProactiveNotification, subscriptionSettings, updateProactivePreferences };
@@ -1,7 +1,7 @@
1
1
  export declare function requireSubscription(id: string, workspaceId: string): import("./types.js").ScenarioSubscription;
2
2
  export declare function controlledSubscriptions(workspaceId: string): {
3
3
  revision: number;
4
- level: "off" | "active" | "quiet" | "balanced";
4
+ level: "active" | "quiet" | "balanced";
5
5
  delivery: "digest" | "inbox" | "important";
6
6
  completedAt: string;
7
7
  scanIntervalMinutes: number;
@@ -18,7 +18,7 @@ export declare function controlledSubscriptions(workspaceId: string): {
18
18
  }[];
19
19
  export declare function createControlledSubscription(workspaceId: string, value: unknown): {
20
20
  revision: number;
21
- level: "off" | "active" | "quiet" | "balanced";
21
+ level: "active" | "quiet" | "balanced";
22
22
  delivery: "digest" | "inbox" | "important";
23
23
  completedAt: string;
24
24
  scanIntervalMinutes: number;
@@ -35,7 +35,7 @@ export declare function createControlledSubscription(workspaceId: string, value:
35
35
  };
36
36
  export declare function updateControlledSubscription(workspaceId: string, id: string, value: unknown): {
37
37
  revision: number;
38
- level: "off" | "active" | "quiet" | "balanced";
38
+ level: "active" | "quiet" | "balanced";
39
39
  delivery: "digest" | "inbox" | "important";
40
40
  completedAt: string;
41
41
  scanIntervalMinutes: number;
@@ -18,8 +18,6 @@ function saveSettings(id, value, revision) {
18
18
  const settings = ProactiveSubscriptionSettingsSchema.parse(value);
19
19
  getSqliteDatabase().prepare(`INSERT INTO proactive_subscription_settings(subscription_id, settings_json, revision) VALUES (?, ?, ?)
20
20
  ON CONFLICT(subscription_id) DO UPDATE SET settings_json = excluded.settings_json, revision = excluded.revision`).run(id, JSON.stringify(settings), revision);
21
- getSqliteDatabase().prepare(`INSERT INTO proactive_schedule_state(subscription_id, next_due_at) VALUES (?, ?)
22
- ON CONFLICT(subscription_id) DO UPDATE SET next_due_at = excluded.next_due_at`).run(id, (/* @__PURE__ */ new Date()).toISOString());
23
21
  }
24
22
  function createControlledSubscription(workspaceId, value) {
25
23
  const input = ProactiveSubscriptionCreateSchema.parse(value);
@@ -37,6 +35,7 @@ function createControlledSubscription(workspaceId, value) {
37
35
  });
38
36
  if (input.userInstructions || sub.activePromptRevisionId) publishPromptRevision(createPromptDraft(sub.id, input.userInstructions).id);
39
37
  saveSettings(sub.id, input, 1);
38
+ if (scopeKind === "project") getSqliteDatabase().prepare("INSERT INTO proactive_schedule_state(subscription_id, next_due_at) VALUES (?, ?)").run(sub.id, (/* @__PURE__ */ new Date()).toISOString());
40
39
  return controlledSubscriptions(workspaceId).find((item) => item.id === sub.id);
41
40
  });
42
41
  }
@@ -55,6 +54,17 @@ function updateControlledSubscription(workspaceId, id, value) {
55
54
  ...current,
56
55
  ...patch
57
56
  }, current.revision + 1);
57
+ if (sub.scopeKind === "project") {
58
+ const db = getSqliteDatabase();
59
+ const changedInstructions = patch.userInstructions !== void 0 && patch.userInstructions !== current.userInstructions;
60
+ const resumed = patch.enabled === true && !sub.enabled;
61
+ if (changedInstructions || resumed) db.prepare("UPDATE proactive_schedule_state SET next_due_at = ?, last_fingerprint = NULL WHERE subscription_id = ?").run((/* @__PURE__ */ new Date()).toISOString(), id);
62
+ else if (patch.scanIntervalMinutes !== void 0 && patch.scanIntervalMinutes !== current.scanIntervalMinutes) {
63
+ const schedule = db.prepare("SELECT last_checked_at FROM proactive_schedule_state WHERE subscription_id = ?").get(id);
64
+ const next = Math.max(Date.now(), schedule?.last_checked_at ? Date.parse(schedule.last_checked_at) + (patch.scanIntervalMinutes ?? 120) * 6e4 : Date.now());
65
+ db.prepare("UPDATE proactive_schedule_state SET next_due_at = ? WHERE subscription_id = ?").run(new Date(next).toISOString(), id);
66
+ }
67
+ }
58
68
  return controlledSubscriptions(workspaceId).find((item) => item.id === id);
59
69
  });
60
70
  }
@@ -0,0 +1,8 @@
1
+ /** Only a successful pull of this collection proves its observed coverage. */
2
+ export declare function sourceFreshness(itemId: string, notBefore?: string, now?: number): {
3
+ fresh: boolean;
4
+ lastSyncedAt: string;
5
+ syncFailed: boolean;
6
+ version: string;
7
+ };
8
+ export declare function evidenceSourcesFresh(ids: string[], context?: Record<string, unknown>): boolean;
@@ -0,0 +1,27 @@
1
+ import { getSqliteDatabase } from "../storage/sqlite/transaction.js";
2
+ import { getKnowledgeSourceItem } from "../storage/sqlite/knowledge-repository.js";
3
+ //#region src/proactive/source-freshness.ts
4
+ const MAX_SOURCE_AGE_MS = 30 * 6e4;
5
+ /** Only a successful pull of this collection proves its observed coverage. */
6
+ function sourceFreshness(itemId, notBefore, now = Date.now()) {
7
+ const item = getKnowledgeSourceItem(itemId);
8
+ const row = item ? getSqliteDatabase().prepare(`SELECT run_id, status, started_at, finished_at FROM knowledge_sync_runs
9
+ WHERE source_instance_id = ? AND collection_scope = ? ORDER BY started_at DESC, rowid DESC LIMIT 1`).get(item.sourceInstanceId, item.collectionScope) : void 0;
10
+ const lastSuccess = item ? getSqliteDatabase().prepare("SELECT MAX(finished_at) AS at FROM knowledge_sync_runs WHERE source_instance_id = ? AND collection_scope = ? AND status = 'succeeded'").get(item.sourceInstanceId, item.collectionScope) : void 0;
11
+ return {
12
+ fresh: Boolean(row?.status === "succeeded" && row.finished_at && row.started_at <= now && now - row.started_at <= MAX_SOURCE_AGE_MS && (!notBefore || row.started_at >= Date.parse(notBefore))),
13
+ lastSyncedAt: lastSuccess?.at ? new Date(lastSuccess.at).toISOString() : null,
14
+ syncFailed: Boolean(row && [
15
+ "failed",
16
+ "partial",
17
+ "cancelled"
18
+ ].includes(row.status)),
19
+ version: row?.run_id ?? "unknown"
20
+ };
21
+ }
22
+ function evidenceSourcesFresh(ids, context) {
23
+ const follow = context?.follow_up;
24
+ return ids.every((id) => !id.startsWith("source-item:") || sourceFreshness(id.slice(12), follow?.deadlinePassed ? follow.dueAt : void 0).fresh);
25
+ }
26
+ //#endregion
27
+ export { evidenceSourcesFresh, sourceFreshness };
@@ -0,0 +1,2 @@
1
+ import type { ProactiveCheckStatus } from '@xopcai/gateway-contract';
2
+ export declare function followUpCheckStatus(subscriptionId: string, project: boolean): ProactiveCheckStatus;
@@ -0,0 +1,21 @@
1
+ import { getSqliteDatabase } from "../storage/sqlite/transaction.js";
2
+ import { effectiveProactivePolicy } from "./policy/service.js";
3
+ //#region src/proactive/status.ts
4
+ function followUpCheckStatus(subscriptionId, project) {
5
+ const db = getSqliteDatabase();
6
+ const recent = db.prepare(`SELECT run_id AS id, status, outcome_reason AS outcome, started_at AS startedAt,
7
+ completed_at AS completedAt, next_attempt_at AS retryAt, attempt FROM proactive_runs
8
+ WHERE subscription_id = ? ORDER BY started_at DESC LIMIT 5`).all(subscriptionId);
9
+ const schedule = project ? db.prepare("SELECT last_checked_at, next_due_at FROM proactive_schedule_state WHERE subscription_id = ?").get(subscriptionId) : void 0;
10
+ const pending = db.prepare("SELECT status FROM proactive_signal_batches WHERE subscription_id = ? AND status IN ('collecting', 'ready', 'processing') ORDER BY created_at DESC LIMIT 1").get(subscriptionId);
11
+ const latest = recent[0];
12
+ const enabled = effectiveProactivePolicy(subscriptionId).enabled;
13
+ return {
14
+ health: !enabled ? "waiting" : pending?.status === "processing" ? "running" : pending ? "queued" : latest?.status === "retryable" ? "retrying" : latest?.status === "failed" || latest?.outcome === "source_unavailable" || latest?.outcome === "source_stale" ? "blocked" : "waiting",
15
+ recent,
16
+ lastCheckedAt: schedule?.last_checked_at ?? recent.find((run) => run.status === "completed")?.completedAt ?? null,
17
+ nextCheckAt: !enabled ? null : latest?.status === "retryable" ? latest.retryAt : schedule?.next_due_at ?? null
18
+ };
19
+ }
20
+ //#endregion
21
+ export { followUpCheckStatus };
@@ -1,6 +1,7 @@
1
1
  import { createLogger } from "../../utils/logger/index.js";
2
2
  import { init_logger } from "../../utils/logger.js";
3
3
  import { listKnowledgeSourceItems } from "../../storage/sqlite/knowledge-repository.js";
4
+ import { sourceFreshness } from "../source-freshness.js";
4
5
  import { authorizedConnectedSource } from "../execution/authorization.js";
5
6
  import { effectiveProactivePolicy } from "../policy/service.js";
6
7
  import { listSubscriptions } from "../scenarios/repository.js";
@@ -84,7 +85,7 @@ var ProactiveTemporalWorker = class {
84
85
  const connectorId = metadataString(item.metadata, "connectorId");
85
86
  const agentId = metadataString(item.metadata, "agentId");
86
87
  const window = meetingWindow(item.occurredAt ? Date.parse(item.occurredAt) : NaN, nowMs);
87
- if (!connectionId || !workspaceId || !activeWorkspaces.has(workspaceId) || !connectorId || !window || !isMeetingWorthPreparing(item.normalizedText) || !authorizedConnectedSource(item.id, workspaceId, MEETING_SCENARIO, agentId) || item.sensitivity === "secret" || item.sensitivity === "regulated") {
88
+ if (!connectionId || !workspaceId || !activeWorkspaces.has(workspaceId) || !connectorId || !window || !sourceFreshness(item.id, void 0, now.getTime()).fresh || !isMeetingWorthPreparing(item.normalizedText) || !authorizedConnectedSource(item.id, workspaceId, MEETING_SCENARIO, agentId) || item.sensitivity === "secret" || item.sensitivity === "regulated") {
88
89
  result.skipped += 1;
89
90
  continue;
90
91
  }
@@ -119,14 +119,38 @@ var RealtimeRuntime = class {
119
119
  budgetHeld = false;
120
120
  this.preauthBudget.release(clientIp);
121
121
  };
122
+ const guardConnection = (phase, action) => {
123
+ try {
124
+ action();
125
+ } catch (err) {
126
+ const errorMessage = err instanceof Error ? err.message : String(err);
127
+ log.error({
128
+ err,
129
+ errorMessage,
130
+ phase,
131
+ connectionId,
132
+ endpointId,
133
+ principalId
134
+ }, `Realtime connection failed: ${errorMessage}`);
135
+ socket.terminate();
136
+ }
137
+ };
138
+ socket.on("error", (err) => {
139
+ log.warn({
140
+ err,
141
+ connectionId,
142
+ endpointId
143
+ }, `Realtime socket failed: ${err.message}`);
144
+ socket.terminate();
145
+ });
122
146
  const helloTimer = setTimeout(() => socket.close(4401, "Realtime hello timeout"), REALTIME_HELLO_TIMEOUT_MS);
123
- const heartbeatTimer = setInterval(() => {
147
+ const heartbeatTimer = setInterval(() => guardConnection("heartbeat", () => {
124
148
  if (principalId && !this.isPrincipalActive(principalId)) {
125
149
  socket.terminate();
126
150
  return;
127
151
  }
128
152
  if (Date.now() - lastSeenAt > 45e3) socket.close(4408, "Realtime heartbeat timeout");
129
- }, REALTIME_HEARTBEAT_INTERVAL_MS);
153
+ }), REALTIME_HEARTBEAT_INTERVAL_MS);
130
154
  const sendError = (code, message) => {
131
155
  writer.enqueue(serverMessage("realtime.error", {
132
156
  code,
@@ -166,7 +190,7 @@ var RealtimeRuntime = class {
166
190
  }
167
191
  return true;
168
192
  };
169
- socket.on("message", (data, isBinary) => {
193
+ socket.on("message", (data, isBinary) => guardConnection("message", () => {
170
194
  if (isBinary) {
171
195
  socket.close(4400, "Binary realtime frames are not supported");
172
196
  return;
@@ -267,7 +291,7 @@ var RealtimeRuntime = class {
267
291
  }, "Realtime endpoint message failed");
268
292
  socket.close(4400, "Invalid endpoint message");
269
293
  }
270
- });
294
+ }));
271
295
  socket.on("close", (code, reasonBuffer) => {
272
296
  clearTimeout(helloTimer);
273
297
  clearInterval(heartbeatTimer);
@@ -275,7 +299,9 @@ var RealtimeRuntime = class {
275
299
  writer.close();
276
300
  for (const handle of subscriptions.values()) handle.unsubscribe();
277
301
  subscriptions.clear();
278
- if (endpointId && connectionId) this.endpointTools?.remove(endpointId, connectionId);
302
+ guardConnection("endpoint_remove", () => {
303
+ if (endpointId && connectionId) this.endpointTools?.remove(endpointId, connectionId);
304
+ });
279
305
  if (principalId) {
280
306
  const principalSockets = this.socketsByPrincipal.get(principalId);
281
307
  principalSockets?.delete(socket);
@@ -67,16 +67,23 @@ var RealtimeSocketWriter = class {
67
67
  const item = this.critical.shift() ?? this.normal.shift();
68
68
  if (!item) return;
69
69
  this.sending = true;
70
- this.socket.send(item.data, (error) => {
70
+ try {
71
+ this.socket.send(item.data, (error) => {
72
+ if (this.closed) return;
73
+ this.sending = false;
74
+ this.queuedBytes -= item.bytes;
75
+ if (error) {
76
+ this.socket.close(1011, "Realtime delivery failed");
77
+ this.close();
78
+ return;
79
+ }
80
+ this.flush();
81
+ });
82
+ } catch {
71
83
  this.sending = false;
72
- this.queuedBytes -= item.bytes;
73
- if (error) {
74
- this.socket.close(1011, "Realtime delivery failed");
75
- this.close();
76
- return;
77
- }
78
- this.flush();
79
- });
84
+ this.close();
85
+ this.socket.close(1011, "Realtime delivery failed");
86
+ }
80
87
  }
81
88
  };
82
89
  //#endregion
@@ -1,9 +1,8 @@
1
- import type { ImportJob, ImportPlan, ImportScan, ProductImportResult } from '../../imports/types.js';
1
+ import type { ImportJob, ImportPlan, ImportScan } from '../../imports/types.js';
2
2
  type Records = {
3
3
  scan: ImportScan;
4
4
  plan: ImportPlan;
5
5
  job: ImportJob;
6
- run: ProductImportResult;
7
6
  };
8
7
  export declare class ImportRepository {
9
8
  readonly owner: string;
@@ -11,9 +10,9 @@ export declare class ImportRepository {
11
10
  get<K extends keyof Records>(kind: K, id: string): Records[K];
12
11
  save<K extends keyof Records>(kind: K, value: Records[K], key?: string): void;
13
12
  byKey(key: string): ImportJob | undefined;
14
- latestRuns(): ProductImportResult[];
15
13
  static owners(): string[];
16
14
  scansBefore(time: number): ImportScan[];
15
+ deleteScan(id: string): void;
17
16
  jobs(limit?: number): ImportJob[];
18
17
  }
19
18
  export {};