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