@xopcai/xopc 0.0.283 → 0.0.285
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/{activity-store-iPtG94qr.js → activity-store-B0BTIk9j.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-DQ74xqLi.js → agent-avatar-dicebear-Dc-0QZTQ.js} +1 -1
- package/dist/gateway/static/root/assets/agent-defaults-settings-panel-CcmqK1Sc.js +1 -0
- package/dist/gateway/static/root/assets/agents-B_7PVt7S.js +1 -0
- package/dist/gateway/static/root/assets/api-COG_Tzpv.js +1 -0
- package/dist/gateway/static/root/assets/{api-KMJzMqvZ.js → api-Coly9ab6.js} +1 -1
- package/dist/gateway/static/root/assets/{app-management-settings-panel-B42TpqIb.js → app-management-settings-panel-TWBO-msg.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-D9Lfe7Sk.js → appearance-settings-CpTZ2KuT.js} +1 -1
- package/dist/gateway/static/root/assets/apps-page-6qxxKFsK.js +1 -0
- package/dist/gateway/static/root/assets/{archive-plugin-BdmQwj4x.js → archive-plugin-z_u6alob.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-B4S0_nji.js → attachment-load-C2nS04TC.js} +1 -1
- package/dist/gateway/static/root/assets/{automations-page-B3TmHNjQ.js → automations-page-aIBsw9zR.js} +1 -1
- package/dist/gateway/static/root/assets/automations-workspace-BRQlU4qk.js +6 -0
- package/dist/gateway/static/root/assets/{binary-plugins-B3cXF_DP.js → binary-plugins-CGmXoZnl.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-05zyBXBe.js → block-editor-CtvzCZhC.js} +3 -3
- package/dist/gateway/static/root/assets/{browser-automation-inputs-BTIufVbM.js → browser-automation-inputs-BOTU1isH.js} +1 -1
- package/dist/gateway/static/root/assets/{browser-automations-page-DRBA9nwe.js → browser-automations-page-BwU5V8J0.js} +2 -2
- package/dist/gateway/static/root/assets/browser-settings-page-DDjbDeHQ.js +8 -0
- package/dist/gateway/static/root/assets/capabilities-settings-panel-DM1Bcf1x.js +8 -0
- package/dist/gateway/static/root/assets/channels-settings-Bq36CUxL.js +1 -0
- package/dist/gateway/static/root/assets/{channels-status-swr-DMYQNs-J.js → channels-status-swr-Dfi0Desh.js} +1 -1
- package/dist/gateway/static/root/assets/{chat-terminal-dock-Dx5KQyo5.js → chat-terminal-dock-B0MvcO6j.js} +2 -2
- package/dist/gateway/static/root/assets/connectors-page-L8JehrxF.js +2 -0
- package/dist/gateway/static/root/assets/{date-picker-CkDROZoe.js → date-picker-DXOvbHCr.js} +1 -1
- package/dist/gateway/static/root/assets/{dependency-picker-Bl8HcvzL.js → dependency-picker-BEnC-eS0.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-BcknAjbW.js → desktop-pet-DtPNvznL.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-BuCpXQPm.js → desktop-pet-settings-CYyjcSn6.js} +1 -1
- package/dist/gateway/static/root/assets/device-pairing-wizard-BPMGvAKE.js +1 -0
- package/dist/gateway/static/root/assets/{extension-debug-page-qRpb2FFa.js → extension-debug-page-H_aPMGXW.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-C7gs9wu4.js → extension-page-C1JnpeMt.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-CyJP1CMZ.js → extension-settings-page-C75kCOqe.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-swr-Cpmyrr6j.js → gateway-config-swr-G2lQt7Ak.js} +1 -1
- package/dist/gateway/static/root/assets/gateway-settings-BCF732TU.js +1 -0
- package/dist/gateway/static/root/assets/{gateway-startup-retry-DoiPeb65.js → gateway-startup-retry-GP31PUrG.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-settings-CV6Dolcn.js → heartbeat-settings-C7fjiowt.js} +1 -1
- package/dist/gateway/static/root/assets/home-page-DZXeXdUL.js +1 -0
- package/dist/gateway/static/root/assets/{image-generation-api-CqjHWl2T.js → image-generation-api-CKFhfHSw.js} +1 -1
- package/dist/gateway/static/root/assets/imports-page-GROumhj8.js +1 -0
- package/dist/gateway/static/root/assets/{index-CHgOG1aR.js → index-ChIrvv3t.js} +47 -47
- package/dist/gateway/static/root/assets/index-R1VNlHjj.css +2 -0
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-B3XqA41D.js → keyboard-shortcuts-settings-zcI822m_.js} +1 -1
- package/dist/gateway/static/root/assets/local-app-workbench-page-CAEKGTAg.js +2 -0
- package/dist/gateway/static/root/assets/{local-apps-page-BcIVYp0J.js → local-apps-page-BtgDJ92Q.js} +1 -1
- package/dist/gateway/static/root/assets/{locale-store-1urrBBCQ.js → locale-store-DlOv7896.js} +1 -1
- package/dist/gateway/static/root/assets/logs-page-DHZ4BEkG.js +2 -0
- package/dist/gateway/static/root/assets/{management-settings-DNFCdgzR.js → management-settings-DasZxCyD.js} +1 -1
- package/dist/gateway/static/root/assets/{markdown-view-BZmcFIWa.js → markdown-view-6oYdBahT.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-BDxNhoBC.js → media-plugins-CxthbBvk.js} +1 -1
- package/dist/gateway/static/root/assets/messages-Ls1Aaiqd.js +3 -0
- package/dist/gateway/static/root/assets/note-detail-panel-BcWq3ddw.js +5 -0
- package/dist/gateway/static/root/assets/notes-page-DfZ8QWyv.js +8 -0
- package/dist/gateway/static/root/assets/{page-tabs-agH3a2PI.js → page-tabs-BG39KidE.js} +1 -1
- package/dist/gateway/static/root/assets/{preview-open-alternatives-XEZ7zDOP.js → preview-open-alternatives-Bevpbq9f.js} +1 -1
- package/dist/gateway/static/root/assets/proactive-page-D2U_xQaS.js +1 -0
- package/dist/gateway/static/root/assets/{product-open-page-lXAbj85Z.js → product-open-page-Bi1seZpM.js} +1 -1
- package/dist/gateway/static/root/assets/project-detail-page-CzlGTW4F.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-mQsM-TsP.js +1 -0
- package/dist/gateway/static/root/assets/{registry-api-CxQstD00.js → registry-api-D0ddwBFj.js} +1 -1
- package/dist/gateway/static/root/assets/remote-access-hub-Cb56JcO1.js +1 -0
- package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-DErKZlVJ.js → runtime-tools-settings-panel-Wkru8_My.js} +2 -2
- package/dist/gateway/static/root/assets/{schema-form-DhCmJQVJ.js → schema-form-CSMRzj9Y.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-Hj9uFcIO.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-CV4-_EVA.js +1 -0
- package/dist/gateway/static/root/assets/{settings-form-section-Dg6chotg.js → settings-form-section-wcAdCkK8.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-LyuN5N3M.js → settings-page-BcQGAnCs.js} +1 -1
- package/dist/gateway/static/root/assets/setup-status-panel-DzxmsaQG.js +1 -0
- package/dist/gateway/static/root/assets/{share-preview-page-CZvmf4Ob.js → share-preview-page-DQocUl-1.js} +1 -1
- package/dist/gateway/static/root/assets/shares-settings-xlJh1ccG.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-wSXJRSxX.js +1 -0
- package/dist/gateway/static/root/assets/skills-page-AmqtBJo7.js +2 -0
- package/dist/gateway/static/root/assets/{skills-page.utils-Eu0WVHTS.js → skills-page.utils-DtlfCgJp.js} +1 -1
- package/dist/gateway/static/root/assets/{system-settings-panel-DQKkM7B2.js → system-settings-panel-Ct698Nf-.js} +1 -1
- package/dist/gateway/static/root/assets/task-detail-page-sQpwq5-c.js +1 -0
- package/dist/gateway/static/root/assets/{theme-store-Dc5s20V3.js → theme-store-ng8KJsAT.js} +1 -1
- package/dist/gateway/static/root/assets/{tunnel-api-TKQpPVd6.js → tunnel-api-Boajol0M.js} +1 -1
- package/dist/gateway/static/root/assets/{use-autosave-CkVlrFk1.js → use-autosave-BSyxRHt2.js} +1 -1
- package/dist/gateway/static/root/assets/user-model-page-DIEHnAER.js +2 -0
- package/dist/gateway/static/root/assets/{voice-api-key-field-B12fFN-L.js → voice-api-key-field-SE3OQI1s.js} +1 -1
- package/dist/gateway/static/root/assets/{voice-config-api-Mk78BrpP.js → voice-config-api-VNuCmdFe.js} +1 -1
- package/dist/gateway/static/root/assets/{work-discovery-overlay-CT9Zy36i.js → work-discovery-overlay-Dn1vy2oq.js} +1 -1
- package/dist/gateway/static/root/assets/{workflow-run-setup-panel-BwefQliE.js → workflow-run-setup-panel-BJEfGY-k.js} +2 -2
- package/dist/gateway/static/root/assets/workflows-page-DgRNIyr7.js +6 -0
- package/dist/gateway/static/root/index.html +10 -10
- package/dist/package.js +1 -1
- package/dist/packages/gateway-contract/src/proactive.js +13 -4
- package/dist/src/agent/coding/repository-instructions.js +1 -1
- package/dist/src/agent/coding/workspace-revision.js +1 -1
- package/dist/src/agent/commands/command-isolation.js +1 -1
- package/dist/src/agent/tools/factory.js +7 -3
- package/dist/src/agent/tools/review-workspace.js +1 -1
- package/dist/src/browser/providers/browser-ext-install.js +10 -2
- package/dist/src/cli/command-loaders.js +1 -0
- package/dist/src/cli/command-manifest.js +4 -0
- package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
- package/dist/src/cli/commands/gateway/lifecycle.js +1 -1
- package/dist/src/cli/commands/gateway/run-foreground.js +3 -0
- package/dist/src/cli/commands/import.d.ts +2 -0
- package/dist/src/cli/commands/import.js +78 -0
- package/dist/src/discussions/action-tasks.js +1 -1
- package/dist/src/discussions/edits.js +1 -1
- package/dist/src/discussions/index.js +1 -1
- package/dist/src/discussions/organizer.js +1 -1
- package/dist/src/discussions/recording.js +1 -1
- package/dist/src/discussions/sealer.js +1 -1
- package/dist/src/discussions/service.js +2 -2
- package/dist/src/endpoint-tools/invocation-service.d.ts +2 -0
- package/dist/src/endpoint-tools/invocation-service.js +48 -19
- package/dist/src/gateway/heartbeat/check-store.d.ts +18 -0
- package/dist/src/gateway/heartbeat/check-store.js +73 -0
- package/dist/src/gateway/heartbeat/service.d.ts +12 -3
- package/dist/src/gateway/heartbeat/service.js +61 -44
- package/dist/src/gateway/hono/routes/discussions.js +2 -2
- package/dist/src/gateway/hono/routes/imports.d.ts +3 -0
- package/dist/src/gateway/hono/routes/imports.js +56 -0
- package/dist/src/gateway/hono/routes/lazy-bundles.js +8 -0
- package/dist/src/gateway/hono/routes/proactive-control.js +2 -1
- package/dist/src/gateway/service.d.ts +7 -0
- package/dist/src/gateway/service.js +26 -9
- package/dist/src/imports/compatibility.d.ts +7 -0
- package/dist/src/imports/compatibility.js +174 -0
- package/dist/src/imports/contextImport.d.ts +3 -0
- package/dist/src/imports/contextImport.js +56 -0
- package/dist/src/imports/files.d.ts +13 -0
- package/dist/src/imports/files.js +77 -0
- package/dist/src/imports/importService.d.ts +50 -0
- package/dist/src/imports/importService.js +414 -0
- package/dist/src/imports/inventory.d.ts +11 -0
- package/dist/src/imports/inventory.js +202 -0
- package/dist/src/imports/planner.d.ts +11 -0
- package/dist/src/imports/planner.js +98 -0
- package/dist/src/imports/productImport.d.ts +10 -0
- package/dist/src/imports/productImport.js +241 -0
- package/dist/src/imports/runtime.d.ts +5 -0
- package/dist/src/imports/runtime.js +61 -0
- package/dist/src/imports/scanner.d.ts +14 -0
- package/dist/src/imports/scanner.js +159 -0
- package/dist/src/imports/secrets.d.ts +2 -0
- package/dist/src/imports/secrets.js +9 -0
- package/dist/src/imports/sources.d.ts +37 -0
- package/dist/src/imports/sources.js +75 -0
- package/dist/src/imports/types.d.ts +144 -0
- package/dist/src/imports/types.js +11 -0
- package/dist/src/infra/background-task.d.ts +2 -0
- package/dist/src/infra/background-task.js +25 -0
- package/dist/src/infra/process-diagnostics.d.ts +5 -0
- package/dist/src/infra/process-diagnostics.js +97 -0
- package/dist/src/infra/unhandled-rejections.js +6 -3
- package/dist/src/infra/update-restart.js +1 -1
- package/dist/src/knowledge/ingestion-service.js +1 -0
- package/dist/src/knowledge-memory/repository.js +4 -2
- package/dist/src/media-shared/http/ssrf-guard.js +1 -1
- package/dist/src/notifications/proactive-policy.js +7 -5
- package/dist/src/proactive/execution/authorization.d.ts +2 -0
- package/dist/src/proactive/execution/authorization.js +10 -1
- package/dist/src/proactive/execution/context.js +1 -0
- package/dist/src/proactive/execution/repository.d.ts +2 -0
- package/dist/src/proactive/execution/repository.js +19 -2
- package/dist/src/proactive/execution/worker.js +14 -4
- package/dist/src/proactive/experience.d.ts +4 -1
- package/dist/src/proactive/experience.js +3 -0
- package/dist/src/proactive/follow-ups.d.ts +5 -0
- package/dist/src/proactive/follow-ups.js +18 -7
- package/dist/src/proactive/inbox/cards.js +2 -1
- package/dist/src/proactive/inbox/delivery.js +6 -7
- package/dist/src/proactive/inbox/digest.d.ts +1 -1
- package/dist/src/proactive/inbox/digest.js +23 -13
- package/dist/src/proactive/inbox/notification-policy.d.ts +1 -1
- package/dist/src/proactive/inbox/notification-policy.js +9 -6
- package/dist/src/proactive/inbox/repository.js +3 -2
- package/dist/src/proactive/inbox/service.d.ts +6 -0
- package/dist/src/proactive/inbox/service.js +18 -0
- package/dist/src/proactive/inbox/types.d.ts +1 -0
- package/dist/src/proactive/policy/presence.d.ts +1 -1
- package/dist/src/proactive/policy/presence.js +7 -5
- package/dist/src/proactive/policy/service.d.ts +8 -5
- package/dist/src/proactive/policy/service.js +12 -12
- package/dist/src/proactive/scenarios/control.d.ts +3 -3
- package/dist/src/proactive/scenarios/control.js +12 -2
- package/dist/src/proactive/source-freshness.d.ts +8 -0
- package/dist/src/proactive/source-freshness.js +27 -0
- package/dist/src/proactive/status.d.ts +2 -0
- package/dist/src/proactive/status.js +21 -0
- package/dist/src/proactive/temporal/worker.js +2 -1
- package/dist/src/realtime/runtime.js +31 -5
- package/dist/src/realtime/writer.js +16 -9
- package/dist/src/storage/sqlite/import-repository.d.ts +18 -0
- package/dist/src/storage/sqlite/import-repository.js +35 -0
- package/dist/src/storage/sqlite/import-selection-repository.d.ts +15 -0
- package/dist/src/storage/sqlite/import-selection-repository.js +28 -0
- package/dist/src/storage/sqlite/knowledge-repository.d.ts +1 -0
- package/dist/src/storage/sqlite/knowledge-repository.js +2 -2
- package/dist/src/storage/sqlite/migrations/174_capability_imports.sql +11 -0
- package/dist/src/storage/sqlite/migrations/175_import_selections.sql +8 -0
- package/dist/src/storage/sqlite/migrations/176_proactive_quality.sql +5 -0
- package/dist/src/storage/sqlite/migrations/177_proactive_check_controls.sql +25 -0
- package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
- package/dist/src/storage/sqlite/migrations/runner.js +3 -3
- package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
- package/dist/src/storage/sqlite/schema.js +1 -1
- package/dist/src/work-discovery/candidate-discovery.js +1 -1
- package/dist/src/work-discovery/probe.js +1 -1
- package/package.json +4 -3
- package/dist/gateway/static/root/assets/agent-defaults-settings-panel-SElFkQd6.js +0 -1
- package/dist/gateway/static/root/assets/agents-DYazUaZ6.js +0 -1
- package/dist/gateway/static/root/assets/apps-page-BzrodN8C.js +0 -1
- package/dist/gateway/static/root/assets/automations-workspace--A4I0jMu.js +0 -6
- package/dist/gateway/static/root/assets/browser-settings-page-DDTXUNWK.js +0 -8
- package/dist/gateway/static/root/assets/capabilities-settings-panel-Ll5UeXq5.js +0 -8
- package/dist/gateway/static/root/assets/channels-settings-BzL15bkU.js +0 -1
- package/dist/gateway/static/root/assets/connectors-page-RPw1AOMb.js +0 -2
- package/dist/gateway/static/root/assets/device-pairing-wizard-D0_tCX49.js +0 -1
- package/dist/gateway/static/root/assets/gateway-settings-DOeeTdWE.js +0 -1
- package/dist/gateway/static/root/assets/home-page-Djw1H_Go.js +0 -1
- package/dist/gateway/static/root/assets/index-B_Sg5BKz.css +0 -2
- package/dist/gateway/static/root/assets/local-app-workbench-page-BPqgyYJq.js +0 -2
- package/dist/gateway/static/root/assets/logs-page-BHax3XlS.js +0 -2
- package/dist/gateway/static/root/assets/messages-DYFcjtmG.js +0 -3
- package/dist/gateway/static/root/assets/note-detail-panel-Cs46LFXO.js +0 -5
- package/dist/gateway/static/root/assets/notes-page-BUkcn_0t.js +0 -8
- package/dist/gateway/static/root/assets/proactive-page-Bn484J9v.js +0 -1
- package/dist/gateway/static/root/assets/project-detail-page-LcH21ixD.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-DelBiwyB.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-3BjFYXCf.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-CVFirgVd.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-DyfBpqkY.js +0 -1
- package/dist/gateway/static/root/assets/setup-status-panel-CrXhTzHr.js +0 -1
- package/dist/gateway/static/root/assets/shares-settings-DyM_D8hM.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-Bys58-15.js +0 -1
- package/dist/gateway/static/root/assets/skills-page-sWNvFo6D.js +0 -2
- package/dist/gateway/static/root/assets/task-detail-page-B18NcZQJ.js +0 -1
- package/dist/gateway/static/root/assets/user-model-page-ul-zr58b.js +0 -2
- package/dist/gateway/static/root/assets/workflows-page-BY_AWs7S.js +0 -6
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getSqliteDatabase } from "../storage/sqlite/transaction.js";
|
|
2
|
-
import { proactivePreferences, quietHoursEnd } from "../proactive/policy/service.js";
|
|
3
|
-
import {
|
|
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
|
|
23
|
-
if (preferences.suppressWhileViewing &&
|
|
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
|
-
|
|
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: "
|
|
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 =
|
|
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
|
|
60
|
-
syncFailed:
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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 &&
|
|
36
|
-
db.prepare("INSERT INTO proactive_delivery_decisions VALUES (?, ?, ?, ?)").run(key, policy.workspaceId, "visible_in_app", (/* @__PURE__ */ new Date()).toISOString());
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
37
|
+
if (policy.preferences.suppressWhileViewing && viewingProactiveCard(policy.workspaceId, current.id, current.notificationRevision)) return { retryAt: new Date(Date.now() + 6e4).toISOString() };
|
|
39
38
|
const decision = reserveProactiveNotification(current.subscriptionId, key);
|
|
40
39
|
if (decision instanceof Date) return { retryAt: decision.toISOString() };
|
|
41
40
|
if (decision === "suppressed") return null;
|
|
@@ -3,6 +3,6 @@ import type { NotificationPlan } from '../../notifications/planner.js';
|
|
|
3
3
|
import type { InboxItem } from './types.js';
|
|
4
4
|
export { nextDigestTime } from '../policy/service.js';
|
|
5
5
|
export declare function queueDigest(item: InboxItem, mode: 'daily' | 'quiet', dueAt: Date): void;
|
|
6
|
-
export declare function digestCards(id: string, workspaceId: string): import("@xopcai/gateway-contract").ProactiveCard[];
|
|
6
|
+
export declare function digestCards(id: string, workspaceId: string, forDelivery?: boolean): import("@xopcai/gateway-contract").ProactiveCard[];
|
|
7
7
|
export declare function reserveWorkspaceAttention(workspaceId: string, key: string, now?: Date): boolean;
|
|
8
8
|
export declare function flushDueDigests(persist: (plan: NotificationPlan) => ProductNotification | null, now?: Date): ProductNotification[];
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { getSqliteDatabase, runSqliteWriteTransaction } from "../../storage/sqlite/transaction.js";
|
|
2
|
-
import { insightSourcesAuthorized } from "../execution/authorization.js";
|
|
3
|
-
import { effectiveProactivePolicy, localProactiveDay, nextDigestTime, proactivePreferences, quietHoursEnd } from "../policy/service.js";
|
|
2
|
+
import { insightSourcesAuthorized, insightSourcesChanged, insightSourcesFresh } from "../execution/authorization.js";
|
|
3
|
+
import { effectiveProactivePolicy, localProactiveDay, nextDigestTime, proactiveChecksAllowed, proactivePreferences, quietHoursEnd } from "../policy/service.js";
|
|
4
4
|
import { getInboxItem } from "./repository.js";
|
|
5
5
|
import { getCard } from "./cards.js";
|
|
6
|
-
import { viewingProactiveCards } from "../policy/presence.js";
|
|
7
6
|
import { randomUUID } from "node:crypto";
|
|
8
7
|
//#region src/proactive/inbox/digest.ts
|
|
9
8
|
function queueDigest(item, mode, dueAt) {
|
|
@@ -13,26 +12,27 @@ function queueDigest(item, mode, dueAt) {
|
|
|
13
12
|
mode = excluded.mode, due_at = excluded.due_at, consumed_at = NULL
|
|
14
13
|
WHERE proactive_digest_queue.notification_revision <> excluded.notification_revision`).run(item.id, policy.workspaceId, item.notificationRevision, mode, dueAt.toISOString());
|
|
15
14
|
}
|
|
16
|
-
function digestCards(id, workspaceId) {
|
|
15
|
+
function digestCards(id, workspaceId, forDelivery = false) {
|
|
17
16
|
const digest = getSqliteDatabase().prepare("SELECT * FROM proactive_digests WHERE digest_id = ? AND workspace_id = ?").get(id, workspaceId);
|
|
18
17
|
if (!digest) throw new Error("Digest not found");
|
|
19
18
|
return getSqliteDatabase().prepare("SELECT inbox_item_id, notification_revision FROM proactive_digest_members WHERE digest_id = ?").all(id).flatMap((member) => {
|
|
20
19
|
const item = getInboxItem(member.inbox_item_id);
|
|
21
|
-
if (!item ||
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
12
|
-
if (
|
|
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,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export declare function recordProactivePresence(workspaceId: string, value: unknown, now?: number): void;
|
|
2
|
-
export declare function
|
|
2
|
+
export declare function viewingProactiveCard(workspaceId: string, itemId: string, revision: number, now?: number): boolean;
|