@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
@@ -70,10 +70,63 @@ export declare class ImportError extends Error {
70
70
  readonly status: 400 | 403 | 404 | 409 | 413;
71
71
  constructor(code: string, message: string, status?: 400 | 403 | 404 | 409 | 413);
72
72
  }
73
+ export type InventoryStatus = 'ready' | 'existing' | 'conflict' | 'blocked';
74
+ export interface InventoryItem {
75
+ id: string;
76
+ kind: 'skill' | 'context' | 'project';
77
+ parentId?: string;
78
+ name: string;
79
+ description: string;
80
+ displayPath: string;
81
+ scope: ImportScope;
82
+ status: InventoryStatus;
83
+ reason?: string;
84
+ suggested: boolean;
85
+ targetName?: string;
86
+ }
87
+ export interface ImportInventory {
88
+ id: string;
89
+ source: ImportSource;
90
+ createdAt: number;
91
+ expiresAt: number;
92
+ candidates: InventoryItem[];
93
+ notices: string[];
94
+ complete: boolean;
95
+ }
96
+ export interface StoredInventoryItem extends InventoryItem {
97
+ location: string;
98
+ scanId?: string;
99
+ candidate?: ImportCandidate;
100
+ targetRoot?: string;
101
+ projectId?: string;
102
+ }
103
+ export interface StoredInventory extends Omit<ImportInventory, 'candidates'> {
104
+ candidates: StoredInventoryItem[];
105
+ scanIds: string[];
106
+ }
107
+ export interface ImportSelection {
108
+ inventoryId: string;
109
+ candidateIds: string[];
110
+ requestId: string;
111
+ retryOf?: string;
112
+ }
113
+ export interface ImportRunItem {
114
+ candidateId: string;
115
+ kind: InventoryItem['kind'];
116
+ name: string;
117
+ status: 'pending' | 'imported' | 'existing' | 'failed';
118
+ targetId?: string;
119
+ jobId?: string;
120
+ error?: string;
121
+ }
73
122
  export interface ProductImportResult {
74
123
  id: string;
75
124
  source: ImportSource;
76
125
  createdAt: number;
126
+ inventoryId: string;
127
+ selectionHash: string;
128
+ status: 'running' | 'completed' | 'partial';
129
+ items: ImportRunItem[];
77
130
  skills: number;
78
131
  context: number;
79
132
  projects: number;
@@ -0,0 +1,2 @@
1
+ /** Run one background tick at a time, reporting once per consecutive failure period. */
2
+ export declare function createBackgroundTask(run: () => void | Promise<void>, onError: (error: unknown) => void): () => void;
@@ -0,0 +1,25 @@
1
+ //#region src/infra/background-task.ts
2
+ /** Run one background tick at a time, reporting once per consecutive failure period. */
3
+ function createBackgroundTask(run, onError) {
4
+ let running = false;
5
+ let failed = false;
6
+ return () => {
7
+ if (running) return;
8
+ running = true;
9
+ (async () => {
10
+ try {
11
+ await run();
12
+ failed = false;
13
+ } catch (error) {
14
+ if (!failed) {
15
+ failed = true;
16
+ onError(error);
17
+ }
18
+ } finally {
19
+ running = false;
20
+ }
21
+ })();
22
+ };
23
+ }
24
+ //#endregion
25
+ export { createBackgroundTask };
@@ -0,0 +1,5 @@
1
+ export declare function recordProcessDiagnostic(event: string, detail: unknown): void;
2
+ /** Independent of SQLite and async logger transports; safe to use during process failure. */
3
+ export declare function createProcessDiagnosticWriter(path: string): (event: string, detail: unknown) => void;
4
+ /** Observe fatal exceptions without resuming execution in an unknown process state. */
5
+ export declare function installProcessDiagnostics(path: string): () => void;
@@ -0,0 +1,97 @@
1
+ import { init_redact, redactObject, redactSensitiveInfo } from "../utils/logger/redact.js";
2
+ import { dirname } from "node:path";
3
+ import { appendFileSync, mkdirSync, renameSync, rmSync, statSync } from "node:fs";
4
+ import { inspect } from "node:util";
5
+ //#region src/infra/process-diagnostics.ts
6
+ init_redact();
7
+ const MAX_LOG_BYTES = 2 * 1024 * 1024;
8
+ const MAX_DETAIL_CHARS = 64 * 1024;
9
+ function diagnosticValue(value, depth = 0) {
10
+ if (depth > 5) return "[MaxDepth]";
11
+ if (value instanceof Error) return {
12
+ name: value.name,
13
+ message: value.message,
14
+ stack: value.stack,
15
+ ...value.cause === void 0 ? {} : { cause: diagnosticValue(value.cause, depth + 1) }
16
+ };
17
+ if (Array.isArray(value)) return value.slice(0, 30).map((item) => diagnosticValue(item, depth + 1));
18
+ if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).slice(0, 30).map(([key, item]) => [key, diagnosticValue(item, depth + 1)]));
19
+ return value;
20
+ }
21
+ let activeWriter;
22
+ function recordProcessDiagnostic(event, detail) {
23
+ activeWriter?.(event, detail);
24
+ }
25
+ /** Independent of SQLite and async logger transports; safe to use during process failure. */
26
+ function createProcessDiagnosticWriter(path) {
27
+ return (event, detail) => {
28
+ try {
29
+ const text = typeof detail === "string" ? detail : inspect(redactObject(diagnosticValue(detail)), {
30
+ depth: 5,
31
+ maxArrayLength: 30,
32
+ maxStringLength: MAX_DETAIL_CHARS,
33
+ customInspect: false,
34
+ getters: false
35
+ });
36
+ const line = JSON.stringify({
37
+ time: (/* @__PURE__ */ new Date()).toISOString(),
38
+ pid: process.pid,
39
+ event,
40
+ detail: redactSensitiveInfo(text).slice(-MAX_DETAIL_CHARS)
41
+ }) + "\n";
42
+ mkdirSync(dirname(path), {
43
+ recursive: true,
44
+ mode: 448
45
+ });
46
+ let size = 0;
47
+ try {
48
+ size = statSync(path).size;
49
+ } catch (err) {
50
+ if (err.code !== "ENOENT") throw err;
51
+ }
52
+ if (size + Buffer.byteLength(line) > MAX_LOG_BYTES) {
53
+ rmSync(`${path}.2`, { force: true });
54
+ try {
55
+ renameSync(`${path}.1`, `${path}.2`);
56
+ } catch (err) {
57
+ if (err.code !== "ENOENT") throw err;
58
+ }
59
+ renameSync(path, `${path}.1`);
60
+ }
61
+ appendFileSync(path, line, {
62
+ encoding: "utf8",
63
+ mode: 384
64
+ });
65
+ } catch {}
66
+ };
67
+ }
68
+ /** Observe fatal exceptions without resuming execution in an unknown process state. */
69
+ function installProcessDiagnostics(path) {
70
+ const write = createProcessDiagnosticWriter(path);
71
+ const previousWriter = activeWriter;
72
+ activeWriter = write;
73
+ const onFatal = (error, origin) => write("uncaught_exception", {
74
+ origin,
75
+ error,
76
+ uptimeSeconds: process.uptime(),
77
+ memory: process.memoryUsage()
78
+ });
79
+ const onExit = (code) => write("process_exit", {
80
+ code,
81
+ uptimeSeconds: process.uptime()
82
+ });
83
+ process.on("uncaughtExceptionMonitor", onFatal);
84
+ process.on("exit", onExit);
85
+ write("process_start", {
86
+ node: process.version,
87
+ platform: process.platform,
88
+ arch: process.arch
89
+ });
90
+ return () => {
91
+ if (activeWriter === write) activeWriter = previousWriter;
92
+ process.removeListener("uncaughtExceptionMonitor", onFatal);
93
+ process.removeListener("exit", onExit);
94
+ };
95
+ }
96
+ //#endregion
97
+ export { createProcessDiagnosticWriter, installProcessDiagnostics, recordProcessDiagnostic };
@@ -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 { resolveGlobalSingleton } from "../utils/global-singleton.js";
4
+ import { recordProcessDiagnostic } from "./process-diagnostics.js";
4
5
  import { isTransientSqliteError } from "./sqlite-errors.js";
5
6
  //#region src/infra/unhandled-rejections.ts
6
7
  init_logger();
@@ -10,12 +11,14 @@ function installSqliteTransientRejectionHandler() {
10
11
  const state = resolveGlobalSingleton(installStateKey, () => ({ installed: false }));
11
12
  if (state.installed) return;
12
13
  process.on("unhandledRejection", (reason) => {
13
- if (!isTransientSqliteError(reason)) return;
14
+ recordProcessDiagnostic("unhandled_rejection", reason);
14
15
  const em = reason instanceof Error ? reason.message : String(reason);
15
- log.warn({
16
+ const fields = {
16
17
  err: reason instanceof Error ? reason : void 0,
17
18
  errorMessage: em
18
- }, `Transient SQLite rejection: ${em}`);
19
+ };
20
+ if (isTransientSqliteError(reason)) log.warn(fields, `Transient SQLite rejection: ${em}`);
21
+ else log.error(fields, `Unhandled promise rejection: ${em}`);
19
22
  });
20
23
  state.installed = true;
21
24
  }
@@ -3,9 +3,9 @@ import { init_logger } from "../utils/logger.js";
3
3
  import { init_paths, resolveConfigPath } from "../config/paths.js";
4
4
  import { init_loader, loadConfig } from "../config/loader.js";
5
5
  import { isRestartEnabled } from "../config/commands.flags.js";
6
- import { resolveGatewayService } from "../daemon/service.js";
7
6
  import { authorizeGatewaySigusr1Restart, writeGatewayRestartIntentSync } from "./restart.js";
8
7
  import { findVerifiedGatewayListenerPidsOnPortSync, signalVerifiedGatewayPidSync } from "./gateway-processes.js";
8
+ import { resolveGatewayService } from "../daemon/service.js";
9
9
  import { refreshGatewayServiceAfterUpdate } from "./update-service-refresh.js";
10
10
  //#region src/infra/update-restart.ts
11
11
  init_loader();
@@ -89,6 +89,7 @@ var KnowledgeIngestionService = class {
89
89
  const cursorBefore = this.state.getCursor(params.instanceId, params.collectionScope);
90
90
  const run = startKnowledgeSyncRun({
91
91
  sourceInstanceId: params.instanceId,
92
+ collectionScope: params.collectionScope,
92
93
  cursorBefore
93
94
  });
94
95
  const controller = params.signal ? null : new AbortController();
@@ -1,5 +1,5 @@
1
- import { isIP } from "node:net";
2
1
  import { promisify } from "node:util";
2
+ import { isIP } from "node:net";
3
3
  import { lookup } from "node:dns";
4
4
  //#region src/media-shared/http/ssrf-guard.ts
5
5
  /**
@@ -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[];