@xopcai/xopc 0.0.267 → 0.0.269
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/dist/assets/{auth-D_IL6naL.js → auth-cwEj8rFb.js} +140 -29
- package/dist/browser-ext/dist/assets/sidepanel-C86O7q5Y.css +299 -0
- package/dist/browser-ext/dist/background.js +2 -2
- package/dist/browser-ext/dist/sidepanel.html +2 -2
- package/dist/browser-ext/dist/sidepanel.js +9501 -427
- 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-Bhyy6ENz.js → activity-store-DcwCSY6s.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-ZrSAMchm.js → agent-avatar-dicebear-D0NBOWC4.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-defaults-settings-panel-DiNFN8qS.js → agent-defaults-settings-panel-qKCezlBg.js} +1 -1
- package/dist/gateway/static/root/assets/agents-BMhnZKhP.js +1 -0
- package/dist/gateway/static/root/assets/{api-B2y7ZjZk.js → api-eaPO-mdr.js} +1 -1
- package/dist/gateway/static/root/assets/{app-management-settings-panel-D-L6yjS7.js → app-management-settings-panel-E3xm2teY.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-BW5eb53j.js → appearance-settings-CiZOjgls.js} +1 -1
- package/dist/gateway/static/root/assets/apps-page-DduJDWGY.js +1 -0
- package/dist/gateway/static/root/assets/{archive-plugin-DwXvu1p9.js → archive-plugin-D09yo_Hc.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-DqU2BYBp.js → attachment-load-CNYb8Qru.js} +1 -1
- package/dist/gateway/static/root/assets/{automations-page-C7qoj9Sd.js → automations-page-DsrfW5ii.js} +1 -1
- package/dist/gateway/static/root/assets/automations-workspace-X7hNHCjW.js +6 -0
- package/dist/gateway/static/root/assets/{binary-plugins-CfplDak2.js → binary-plugins-OWlaGqlY.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-CzLs38ru.js → block-editor-CVND3M6r.js} +3 -3
- package/dist/gateway/static/root/assets/{browser-automation-inputs-s5I78Iko.js → browser-automation-inputs-D7IzVK_J.js} +1 -1
- package/dist/gateway/static/root/assets/{browser-automations-page-_SKi8s9b.js → browser-automations-page-CQ6cCpIv.js} +2 -2
- package/dist/gateway/static/root/assets/browser-settings-page-BZ7nK_pN.js +8 -0
- package/dist/gateway/static/root/assets/capabilities-settings-panel-BfKvJO1C.js +7 -0
- package/dist/gateway/static/root/assets/channels-settings-D1UDxJuA.js +1 -0
- package/dist/gateway/static/root/assets/{channels-status-swr-_OsWr_vS.js → channels-status-swr-D4JrgJKa.js} +1 -1
- package/dist/gateway/static/root/assets/{chat-terminal-dock-snvAu3hJ.js → chat-terminal-dock-BUjnhT7C.js} +2 -2
- package/dist/gateway/static/root/assets/connectors-page-C2nuqs_O.js +2 -0
- package/dist/gateway/static/root/assets/{cron-expression-BhGZSbT6.js → cron-expression-DlyRIyUn.js} +1 -1
- package/dist/gateway/static/root/assets/{date-picker-Cep5f9b-.js → date-picker-CU-ij_yE.js} +1 -1
- package/dist/gateway/static/root/assets/{dependency-picker-BzSEBQku.js → dependency-picker-Z6Nf0Wjq.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-BwX_6Gah.js → desktop-pet-C0F5Iqnc.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-WrbVsU2b.js → desktop-pet-settings-Bj_W6HL1.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-BdXSwtAZ.js → extension-debug-page-Ba09EnAn.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-I95ukbRb.js → extension-page-C88aGXJs.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-8zUisBay.js → extension-settings-page-CMyt0A32.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-swr-OObCK8jM.js → gateway-config-swr-bjzk_4hn.js} +1 -1
- package/dist/gateway/static/root/assets/gateway-settings-ogv8ZbvG.js +1 -0
- package/dist/gateway/static/root/assets/{gateway-startup-retry-D_TxKN_A.js → gateway-startup-retry-DrbIzFyB.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-settings-BGQxU-Ug.js → heartbeat-settings-BnrMRKaZ.js} +1 -1
- package/dist/gateway/static/root/assets/home-page-CbcJCREd.js +1 -0
- package/dist/gateway/static/root/assets/{image-generation-api-fPU9yGsw.js → image-generation-api-CBWU9Ner.js} +1 -1
- package/dist/gateway/static/root/assets/index-7PZ6jmhy.css +2 -0
- package/dist/gateway/static/root/assets/{index-D2OtD9Wa.js → index-CfyyA9-g.js} +47 -47
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-BUe_om99.js → keyboard-shortcuts-settings-B8Z7kKQY.js} +1 -1
- package/dist/gateway/static/root/assets/local-app-workbench-page-D5XVZnK1.js +2 -0
- package/dist/gateway/static/root/assets/{local-apps-page-BZaLzfms.js → local-apps-page-CLSfzkRp.js} +1 -1
- package/dist/gateway/static/root/assets/{locale-store-DFQX9kTl.js → locale-store-BQ2HKTWb.js} +1 -1
- package/dist/gateway/static/root/assets/logs-page-CWDU1AGN.js +2 -0
- package/dist/gateway/static/root/assets/{management-settings-Duf8w-Bl.js → management-settings-CuFNgQUy.js} +1 -1
- package/dist/gateway/static/root/assets/{markdown-view-D-5jHRWg.js → markdown-view-DIcfrLmG.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-2-QfzHmg.js → media-plugins-CSv6-xTA.js} +1 -1
- package/dist/gateway/static/root/assets/{messages-Dj8QuS6e.js → messages-BRjoarIr.js} +2 -2
- package/dist/gateway/static/root/assets/{mobile-pairing-wizard-DRaVIAt1.js → mobile-pairing-wizard-C7d_ztgp.js} +1 -1
- package/dist/gateway/static/root/assets/note-detail-panel-D5JuBLyt.js +5 -0
- package/dist/gateway/static/root/assets/notes-page-C5-ccCi4.js +8 -0
- package/dist/gateway/static/root/assets/{page-tabs-DaZ7QpNO.js → page-tabs-BfV-PQa5.js} +1 -1
- package/dist/gateway/static/root/assets/{preview-open-alternatives-CWEuqCmv.js → preview-open-alternatives-DQS7NFpg.js} +1 -1
- package/dist/gateway/static/root/assets/proactive-page-fHjPigmx.js +1 -0
- package/dist/gateway/static/root/assets/{product-open-page-CIGLPFm7.js → product-open-page-BIhjZLKg.js} +1 -1
- package/dist/gateway/static/root/assets/project-detail-page-6v_khvEt.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-CCy1aK4V.js +1 -0
- package/dist/gateway/static/root/assets/{registry-api-CsZLatKo.js → registry-api-CwveUfe8.js} +1 -1
- package/dist/gateway/static/root/assets/remote-access-hub-pkqKRnch.js +1 -0
- package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-CslkSM-m.js → runtime-tools-settings-panel-DMQ0wpNg.js} +2 -2
- package/dist/gateway/static/root/assets/{schema-form-B2gvVrBq.js → schema-form-C91g2PRU.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-Cr4E4cap.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-D1JRDRyL.js +1 -0
- package/dist/gateway/static/root/assets/{settings-form-section-CmlnuSO3.js → settings-form-section-Cem6L49Y.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-DWWgyelT.js → settings-page-afDUxqlV.js} +1 -1
- package/dist/gateway/static/root/assets/{setup-status-panel-CJrN3m1o.js → setup-status-panel-Cc_iP_vv.js} +1 -1
- package/dist/gateway/static/root/assets/{share-preview-page-C12oyIKk.js → share-preview-page-CVEVHksg.js} +1 -1
- package/dist/gateway/static/root/assets/shares-settings-BrMECMAB.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-DuqIDYjc.js +1 -0
- package/dist/gateway/static/root/assets/{skills-page-yhW7LLqk.js → skills-page-CNrq1W6C.js} +1 -1
- package/dist/gateway/static/root/assets/{skills-page.utils-FaTlq8t-.js → skills-page.utils-D1wGfm22.js} +1 -1
- package/dist/gateway/static/root/assets/{system-settings-panel-CMrkoJLN.js → system-settings-panel-BLE8PxWE.js} +1 -1
- package/dist/gateway/static/root/assets/task-detail-page-m0oXUKl2.js +1 -0
- package/dist/gateway/static/root/assets/{theme-store-zRT6vaZc.js → theme-store-BejLVjIT.js} +1 -1
- package/dist/gateway/static/root/assets/{tunnel-api-C2tAZaJz.js → tunnel-api-Dur80zSQ.js} +1 -1
- package/dist/gateway/static/root/assets/{use-autosave-BsDRt4QI.js → use-autosave-DmgEA8Qb.js} +1 -1
- package/dist/gateway/static/root/assets/user-model-page-Dl10D_5q.js +2 -0
- package/dist/gateway/static/root/assets/{voice-api-key-field-CS5heYMP.js → voice-api-key-field-sZ0J83SF.js} +1 -1
- package/dist/gateway/static/root/assets/{voice-config-api-DpJEu2RQ.js → voice-config-api-BEbz3avx.js} +1 -1
- package/dist/gateway/static/root/assets/{work-discovery-overlay-BeSz5Th5.js → work-discovery-overlay-BXnAlbmO.js} +1 -1
- package/dist/gateway/static/root/assets/workflow-run-setup-panel-BkwFey0a.js +3 -0
- package/dist/gateway/static/root/assets/workflows-page-CswgoIFU.js +6 -0
- package/dist/gateway/static/root/index.html +10 -10
- package/dist/gateway/static/root/notification-sw.js +15 -0
- package/dist/package.js +1 -1
- package/dist/packages/gateway-contract/src/notifications.js +15 -1
- package/dist/packages/gateway-contract/src/proactive.js +104 -0
- package/dist/packages/realtime-protocol/src/voice.js +2 -1
- package/dist/src/agent/agent-manager.js +4 -4
- package/dist/src/agent/background-review/run-background-review.js +1 -1
- package/dist/src/agent/embedded/model-runtime.js +1 -1
- package/dist/src/agent/embedded/run-for-session.js +1 -1
- package/dist/src/agent/image/tool-model-config.js +1 -1
- package/dist/src/agent/mcp/oauth/mcp-oauth-store.js +1 -1
- package/dist/src/agent/service.js +2 -2
- package/dist/src/agent/session/session-inspector.js +1 -1
- package/dist/src/agent/skills/hub-lock.js +1 -1
- package/dist/src/agent/tasks/task-judge-service.js +3 -3
- package/dist/src/agent/workflow/subagent-runner.d.ts +1 -0
- package/dist/src/agent/workflow/subagent-runner.js +2 -2
- package/dist/src/ai-assist/text-assist.js +1 -1
- package/dist/src/auth/credentials.js +2 -2
- package/dist/src/auth/oauth/xopc-cloud.js +1 -1
- package/dist/src/auth/profiles/store.js +1 -1
- package/dist/src/auth/sync-provider-auth.js +2 -2
- package/dist/src/browser/providers/browser-ext-install.js +1 -1
- package/dist/src/browser/runtime/browser-runtime.js +3 -2
- package/dist/src/cli/commands/connectors.js +1 -1
- package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
- package/dist/src/cli/commands/doctor/checks/provider-auth.js +1 -1
- package/dist/src/cli/commands/models.js +1 -1
- package/dist/src/config/index.js +4 -4
- package/dist/src/config/loader.js +2 -2
- package/dist/src/config/models-json.js +1 -1
- package/dist/src/connectors/connected-source-ingestion.js +1 -1
- package/dist/src/connectors/connection-recovery-service.js +1 -1
- package/dist/src/connectors/health.js +1 -1
- package/dist/src/extensions/lockfile.js +1 -1
- package/dist/src/gateway/agents-admin.js +1 -1
- package/dist/src/gateway/file-path-classifier.js +1 -1
- package/dist/src/gateway/heartbeat/service.js +1 -1
- package/dist/src/gateway/hono/lib/config-payload.js +1 -1
- package/dist/src/gateway/hono/routes/capabilities.js +1 -1
- package/dist/src/gateway/hono/routes/connectors.js +1 -1
- package/dist/src/gateway/hono/routes/index.js +0 -2
- package/dist/src/gateway/hono/routes/lazy-bundles.js +12 -0
- package/dist/src/gateway/hono/routes/mcp.js +1 -1
- package/dist/src/gateway/hono/routes/models.js +1 -1
- package/dist/src/gateway/hono/routes/proactive-control.d.ts +3 -0
- package/dist/src/gateway/hono/routes/proactive-control.js +161 -0
- package/dist/src/gateway/hono/routes/proactive.d.ts +1 -1
- package/dist/src/gateway/hono/routes/proactive.js +12 -5
- package/dist/src/gateway/hono/routes/voice.js +1 -1
- package/dist/src/gateway/security/gateway-scopes.js +5 -0
- package/dist/src/gateway/service/config-coordinator.js +1 -1
- package/dist/src/gateway/service.d.ts +0 -1
- package/dist/src/gateway/service.js +28 -30
- package/dist/src/heartbeat/index.js +1 -1
- package/dist/src/notifications/planner.js +4 -3
- package/dist/src/notifications/proactive-channel.d.ts +8 -0
- package/dist/src/notifications/proactive-channel.js +57 -0
- package/dist/src/notifications/proactive-policy.d.ts +3 -0
- package/dist/src/notifications/proactive-policy.js +32 -0
- package/dist/src/notifications/service.d.ts +78 -0
- package/dist/src/notifications/service.js +71 -13
- package/dist/src/notifications/store.d.ts +2 -0
- package/dist/src/notifications/store.js +8 -1
- package/dist/src/notifications/web-push.d.ts +19 -0
- package/dist/src/notifications/web-push.js +182 -0
- package/dist/src/proactive/actions/service.js +10 -2
- package/dist/src/proactive/actions/workflow.d.ts +15 -0
- package/dist/src/proactive/actions/workflow.js +88 -0
- package/dist/src/proactive/execution/agent-executor.d.ts +5 -0
- package/dist/src/proactive/execution/agent-executor.js +21 -0
- package/dist/src/proactive/execution/authorization.d.ts +3 -0
- package/dist/src/proactive/execution/authorization.js +23 -0
- package/dist/src/proactive/execution/context.js +2 -11
- package/dist/src/proactive/execution/insight.d.ts +7 -0
- package/dist/src/proactive/execution/insight.js +33 -2
- package/dist/src/proactive/execution/repository.d.ts +2 -0
- package/dist/src/proactive/execution/repository.js +38 -4
- package/dist/src/proactive/execution/types.d.ts +5 -0
- package/dist/src/proactive/execution/worker.js +38 -7
- package/dist/src/proactive/inbox/cards.d.ts +21 -0
- package/dist/src/proactive/inbox/cards.js +237 -0
- package/dist/src/proactive/inbox/delivery.d.ts +6 -0
- package/dist/src/proactive/inbox/delivery.js +52 -0
- package/dist/src/proactive/inbox/digest.d.ts +8 -0
- package/dist/src/proactive/inbox/digest.js +112 -0
- package/dist/src/proactive/inbox/lifecycle.d.ts +4 -0
- package/dist/src/proactive/inbox/lifecycle.js +58 -0
- package/dist/src/proactive/inbox/notification-policy.d.ts +1 -0
- package/dist/src/proactive/inbox/notification-policy.js +20 -0
- package/dist/src/proactive/inbox/repository.d.ts +1 -0
- package/dist/src/proactive/inbox/repository.js +68 -11
- package/dist/src/proactive/inbox/types.d.ts +9 -1
- package/dist/src/proactive/inbox/worker.js +9 -3
- package/dist/src/proactive/index.js +1 -1
- package/dist/src/proactive/maintenance.js +7 -0
- package/dist/src/proactive/metrics.d.ts +10 -0
- package/dist/src/proactive/metrics.js +27 -0
- package/dist/src/proactive/policy/presence.d.ts +2 -0
- package/dist/src/proactive/policy/presence.js +27 -0
- package/dist/src/proactive/policy/service.d.ts +53 -0
- package/dist/src/proactive/policy/service.js +122 -0
- package/dist/src/proactive/routing/matcher.js +3 -2
- package/dist/src/proactive/scenarios/control.d.ts +90 -0
- package/dist/src/proactive/scenarios/control.js +109 -0
- package/dist/src/proactive/scenarios/preview.d.ts +13 -0
- package/dist/src/proactive/scenarios/preview.js +81 -0
- package/dist/src/proactive/scenarios/prompt-composer.js +2 -2
- package/dist/src/proactive/scenarios/repository.js +5 -1
- package/dist/src/proactive/temporal/schedule.d.ts +3 -0
- package/dist/src/proactive/temporal/schedule.js +67 -0
- package/dist/src/proactive/temporal/worker.js +8 -4
- package/dist/src/providers/auth-runtime/resolve-auth.js +1 -1
- package/dist/src/providers/index.js +2 -2
- package/dist/src/providers/model-registry.js +1 -1
- package/dist/src/providers/register-runtime-providers.js +1 -1
- package/dist/src/storage/sqlite/migrations/159_proactive_controls.sql +68 -0
- package/dist/src/storage/sqlite/migrations/160_proactive_web_push.sql +27 -0
- package/dist/src/storage/sqlite/migrations/161_proactive_delivery_and_lifecycle.sql +51 -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/tui/tui-startup-resources.js +2 -2
- package/dist/src/tui/tui.js +1 -1
- package/dist/src/voice/realtime/runtime.d.ts +2 -1
- package/dist/src/voice/realtime/runtime.js +21 -5
- package/dist/src/work-discovery/service.js +1 -1
- package/dist/src/workflows/domain/run.d.ts +1 -0
- package/dist/src/workflows/index.js +2 -2
- package/dist/src/workflows/service/workflow-run-service.d.ts +3 -0
- package/dist/src/workflows/service/workflow-run-service.js +27 -6
- package/dist/src/workflows/service/workflow-run-service.types.d.ts +1 -0
- package/package.json +3 -1
- package/dist/browser-ext/dist/assets/sidepanel-CEPaVLjr.css +0 -155
- package/dist/gateway/static/root/assets/agents-Bu6Hf59K.js +0 -1
- package/dist/gateway/static/root/assets/apps-page-CiQIdz3N.js +0 -1
- package/dist/gateway/static/root/assets/automations-workspace-CIARTLHO.js +0 -6
- package/dist/gateway/static/root/assets/browser-settings-page-DTdx66sX.js +0 -8
- package/dist/gateway/static/root/assets/capabilities-settings-panel-Ck9U0Olx.js +0 -7
- package/dist/gateway/static/root/assets/channels-settings-I_vDpbnl.js +0 -1
- package/dist/gateway/static/root/assets/connectors-page-BmKTnzo1.js +0 -2
- package/dist/gateway/static/root/assets/gateway-settings-DGj5b9Yv.js +0 -1
- package/dist/gateway/static/root/assets/home-page-BZPpK0A3.js +0 -1
- package/dist/gateway/static/root/assets/index-CRJrO4GI.css +0 -2
- package/dist/gateway/static/root/assets/local-app-workbench-page-CbQnffS9.js +0 -2
- package/dist/gateway/static/root/assets/logs-page-WWCHqW_x.js +0 -2
- package/dist/gateway/static/root/assets/note-detail-panel-BT-fWGxX.js +0 -5
- package/dist/gateway/static/root/assets/notes-page-C4705PzN.js +0 -8
- package/dist/gateway/static/root/assets/project-detail-page-BZegJ6an.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-B7hUHeLI.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-BE9hZpdh.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-DmAAPMo1.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-CvBocGDA.js +0 -1
- package/dist/gateway/static/root/assets/shares-settings-D4BtSiLq.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-CzKGn056.js +0 -1
- package/dist/gateway/static/root/assets/task-detail-page-BUaO62cy.js +0 -1
- package/dist/gateway/static/root/assets/user-model-page-Dy8h1SSJ.js +0 -2
- package/dist/gateway/static/root/assets/workflow-run-setup-panel-BebW0CDK.js +0 -3
- package/dist/gateway/static/root/assets/workflows-page-CU8jFgPF.js +0 -6
|
@@ -57,7 +57,7 @@ function parseInsightCandidate(raw, allowedEvidenceIds) {
|
|
|
57
57
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Insight must be a JSON object");
|
|
58
58
|
const row = value;
|
|
59
59
|
if (!URGENCY.has(String(row.urgency))) throw new Error("Insight urgency is invalid");
|
|
60
|
-
if (typeof row.confidence !== "number" || row.confidence < 0 || row.confidence > 1) throw new Error("Insight confidence is invalid");
|
|
60
|
+
if (typeof row.confidence !== "number" || !Number.isFinite(row.confidence) || row.confidence < 0 || row.confidence > 1) throw new Error("Insight confidence is invalid");
|
|
61
61
|
if (!Array.isArray(row.evidenceIds) || row.evidenceIds.length === 0 || row.evidenceIds.length > 20) throw new Error("Insight evidenceIds are invalid");
|
|
62
62
|
const evidenceIds = [...new Set(row.evidenceIds.map(String))];
|
|
63
63
|
if (evidenceIds.some((id) => !allowedEvidenceIds.has(id))) throw new Error("Insight cites unknown evidence");
|
|
@@ -93,5 +93,36 @@ function isValuableInsight(candidate, policy = {
|
|
|
93
93
|
}) {
|
|
94
94
|
return candidate.confidence >= policy.minConfidence && candidate.urgency !== "low" && scoreInsight(candidate) >= policy.minScore;
|
|
95
95
|
}
|
|
96
|
+
function parseAnalysisResult(raw, allowedEvidenceIds) {
|
|
97
|
+
let value;
|
|
98
|
+
try {
|
|
99
|
+
value = JSON.parse(stripCodeFences(raw));
|
|
100
|
+
} catch {
|
|
101
|
+
throw new Error("Model output is not valid JSON");
|
|
102
|
+
}
|
|
103
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
104
|
+
const row = value;
|
|
105
|
+
if (row.result === "no_insight") {
|
|
106
|
+
if (![
|
|
107
|
+
"unchanged",
|
|
108
|
+
"routine",
|
|
109
|
+
"insufficient_evidence",
|
|
110
|
+
"duplicate"
|
|
111
|
+
].includes(String(row.reason)) || Object.keys(row).some((key) => key !== "result" && key !== "reason")) throw new Error("Invalid no_insight result");
|
|
112
|
+
return {
|
|
113
|
+
result: "no_insight",
|
|
114
|
+
reason: String(row.reason)
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (row.result === "insight") return {
|
|
118
|
+
result: "insight",
|
|
119
|
+
candidate: parseInsightCandidate(JSON.stringify(row.candidate), allowedEvidenceIds)
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
result: "insight",
|
|
124
|
+
candidate: parseInsightCandidate(raw, allowedEvidenceIds)
|
|
125
|
+
};
|
|
126
|
+
}
|
|
96
127
|
//#endregion
|
|
97
|
-
export { isValuableInsight, parseInsightCandidate, scoreInsight };
|
|
128
|
+
export { isValuableInsight, parseAnalysisResult, parseInsightCandidate, scoreInsight };
|
|
@@ -7,6 +7,7 @@ export interface ClaimedRun {
|
|
|
7
7
|
scenarioVersion: number;
|
|
8
8
|
promptRevisionId?: string;
|
|
9
9
|
attempt: number;
|
|
10
|
+
subscriptionRevision?: number;
|
|
10
11
|
}
|
|
11
12
|
export declare function claimNextRun(owner: string, now?: Date, leaseSeconds?: number): ClaimedRun | null;
|
|
12
13
|
export declare function eventIdsForBatch(batchId: string): string[];
|
|
@@ -18,6 +19,7 @@ export declare function finishRun(input: {
|
|
|
18
19
|
valueScore?: number;
|
|
19
20
|
cooldownSeconds?: number;
|
|
20
21
|
rawOutput: string;
|
|
22
|
+
outcomeReason?: string;
|
|
21
23
|
modelRef?: string;
|
|
22
24
|
}, now?: Date): ProactiveInsight | null;
|
|
23
25
|
export declare function failRun(run: ClaimedRun, error: unknown, retryable: boolean, now?: Date): void;
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { getSqliteDatabase, runSqliteWriteTransaction } from "../../storage/sqlite/transaction.js";
|
|
2
|
+
import { effectiveProactivePolicy } from "../policy/service.js";
|
|
2
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
4
|
//#region src/proactive/execution/repository.ts
|
|
4
5
|
const str = (row, key) => String(row[key]);
|
|
5
6
|
function claimNextRun(owner, now = /* @__PURE__ */ new Date(), leaseSeconds = 120) {
|
|
6
7
|
return runSqliteWriteTransaction((db) => {
|
|
7
8
|
const nowIso = now.toISOString();
|
|
9
|
+
for (const row of db.prepare("SELECT run_id, batch_id, subscription_id FROM proactive_runs WHERE status IN ('retryable', 'running')").all()) if (!effectiveProactivePolicy(str(row, "subscription_id"), now).enabled) {
|
|
10
|
+
db.prepare("UPDATE proactive_runs SET status = 'discarded', outcome_reason = 'disabled', updated_at = ? WHERE run_id = ?").run(nowIso, str(row, "run_id"));
|
|
11
|
+
db.prepare("UPDATE proactive_signal_batches SET status = 'ignored', updated_at = ? WHERE batch_id = ?").run(nowIso, str(row, "batch_id"));
|
|
12
|
+
}
|
|
13
|
+
for (const row of db.prepare("SELECT batch_id, subscription_id FROM proactive_signal_batches WHERE status = 'ready'").all()) if (!effectiveProactivePolicy(str(row, "subscription_id"), now).enabled) db.prepare("UPDATE proactive_signal_batches SET status = 'ignored' WHERE batch_id = ?").run(str(row, "batch_id"));
|
|
8
14
|
const dayStart = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())).toISOString();
|
|
9
15
|
db.prepare(`UPDATE proactive_signal_batches SET status = 'ignored', updated_at = ? WHERE status = 'ready'
|
|
10
16
|
AND subscription_id IN (SELECT subscription_id FROM proactive_scenario_subscriptions WHERE enabled = 0)`).run(nowIso);
|
|
@@ -38,9 +44,14 @@ function claimNextRun(owner, now = /* @__PURE__ */ new Date(), leaseSeconds = 12
|
|
|
38
44
|
WHERE b.status = 'ready' ORDER BY b.ready_at LIMIT 1`).get();
|
|
39
45
|
if (!batch) return null;
|
|
40
46
|
const id = randomUUID();
|
|
47
|
+
const policy = effectiveProactivePolicy(str(batch, "subscription_id"), now);
|
|
41
48
|
db.prepare(`INSERT INTO proactive_runs (run_id, batch_id, subscription_id, scenario_key, scenario_version,
|
|
42
|
-
prompt_revision_id, status, attempt, lease_owner, lease_expires_at, started_at, updated_at)
|
|
43
|
-
VALUES (?, ?, ?, ?, ?, ?, 'running', 1, ?, ?, ?, ?)`).run(id, str(batch, "batch_id"), str(batch, "subscription_id"), str(batch, "scenario_key"), Number(batch.scenario_version), batch.active_prompt_revision_id ? String(batch.active_prompt_revision_id) : null, owner, new Date(now.getTime() + leaseSeconds * 1e3).toISOString(), nowIso, nowIso
|
|
49
|
+
prompt_revision_id, status, attempt, lease_owner, lease_expires_at, started_at, updated_at, policy_revision, subscription_revision, policy_snapshot_json)
|
|
50
|
+
VALUES (?, ?, ?, ?, ?, ?, 'running', 1, ?, ?, ?, ?, ?, ?, ?)`).run(id, str(batch, "batch_id"), str(batch, "subscription_id"), str(batch, "scenario_key"), Number(batch.scenario_version), batch.active_prompt_revision_id ? String(batch.active_prompt_revision_id) : null, owner, new Date(now.getTime() + leaseSeconds * 1e3).toISOString(), nowIso, nowIso, policy.preferences.revision, policy.settings.revision, JSON.stringify({
|
|
51
|
+
level: policy.level,
|
|
52
|
+
delivery: policy.settings.delivery,
|
|
53
|
+
scanIntervalMinutes: policy.scanIntervalMinutes
|
|
54
|
+
}));
|
|
44
55
|
db.prepare("UPDATE proactive_signal_batches SET status = 'processing', updated_at = ? WHERE batch_id = ?").run(nowIso, str(batch, "batch_id"));
|
|
45
56
|
return runFromRow(db.prepare("SELECT * FROM proactive_runs WHERE run_id = ?").get(id));
|
|
46
57
|
});
|
|
@@ -53,6 +64,7 @@ function runFromRow(row) {
|
|
|
53
64
|
scenarioKey: str(row, "scenario_key"),
|
|
54
65
|
scenarioVersion: Number(row.scenario_version),
|
|
55
66
|
attempt: Number(row.attempt),
|
|
67
|
+
subscriptionRevision: Number(row.subscription_revision),
|
|
56
68
|
...row.prompt_revision_id ? { promptRevisionId: str(row, "prompt_revision_id") } : {}
|
|
57
69
|
};
|
|
58
70
|
}
|
|
@@ -77,6 +89,19 @@ function attachSnapshot(runId, snapshotId) {
|
|
|
77
89
|
function finishRun(input, now = /* @__PURE__ */ new Date()) {
|
|
78
90
|
return runSqliteWriteTransaction((db) => {
|
|
79
91
|
const nowIso = now.toISOString();
|
|
92
|
+
const current = db.prepare("SELECT status, attempt FROM proactive_runs WHERE run_id = ?").get(input.run.id);
|
|
93
|
+
if (!current || current.status !== "running" || current.attempt !== input.run.attempt) return null;
|
|
94
|
+
const currentPolicy = effectiveProactivePolicy(input.run.subscriptionId, now);
|
|
95
|
+
if (!currentPolicy.enabled) input = {
|
|
96
|
+
...input,
|
|
97
|
+
candidate: void 0,
|
|
98
|
+
outcomeReason: "disabled"
|
|
99
|
+
};
|
|
100
|
+
else if (input.run.subscriptionRevision !== void 0 && input.run.subscriptionRevision !== currentPolicy.settings.revision) input = {
|
|
101
|
+
...input,
|
|
102
|
+
candidate: void 0,
|
|
103
|
+
outcomeReason: "policy_changed"
|
|
104
|
+
};
|
|
80
105
|
const subjectScope = db.prepare(`SELECT DISTINCT e.subject_kind, e.subject_id
|
|
81
106
|
FROM proactive_batch_events be
|
|
82
107
|
JOIN proactive_events e ON e.event_id = be.event_id
|
|
@@ -90,8 +115,17 @@ function finishRun(input, now = /* @__PURE__ */ new Date()) {
|
|
|
90
115
|
const duplicate = fingerprint ? db.prepare(`SELECT 1 FROM proactive_insights
|
|
91
116
|
WHERE subscription_id = ? AND scenario_key = ? AND content_fingerprint = ? AND created_at >= ? LIMIT 1`).get(input.run.subscriptionId, input.run.scenarioKey, fingerprint, (/* @__PURE__ */ new Date(now.getTime() - (input.cooldownSeconds ?? 10080 * 60) * 1e3)).toISOString()) : void 0;
|
|
92
117
|
const valuable = Boolean(input.candidate) && !duplicate;
|
|
118
|
+
const reason = duplicate ? "duplicate" : input.outcomeReason;
|
|
119
|
+
const completed = valuable || [
|
|
120
|
+
"unchanged",
|
|
121
|
+
"routine",
|
|
122
|
+
"insufficient_evidence",
|
|
123
|
+
"duplicate",
|
|
124
|
+
"below_threshold"
|
|
125
|
+
].includes(reason ?? "");
|
|
126
|
+
db.prepare("UPDATE proactive_runs SET outcome_reason = ? WHERE run_id = ?").run(reason ?? null, input.run.id);
|
|
93
127
|
db.prepare(`UPDATE proactive_runs SET status = ?, raw_output = ?, model_ref = ?, lease_owner = NULL,
|
|
94
|
-
lease_expires_at = NULL, completed_at = ?, updated_at = ? WHERE run_id = ?`).run(
|
|
128
|
+
lease_expires_at = NULL, completed_at = ?, updated_at = ? WHERE run_id = ?`).run(completed ? "completed" : "discarded", input.rawOutput.slice(0, 2e4), input.modelRef ?? null, nowIso, nowIso, input.run.id);
|
|
95
129
|
db.prepare(`UPDATE proactive_signal_batches SET status = ?, updated_at = ? WHERE batch_id = ?`).run(valuable ? "processed" : "ignored", nowIso, input.run.batchId);
|
|
96
130
|
if (!input.candidate || duplicate) return null;
|
|
97
131
|
const insight = {
|
|
@@ -115,7 +149,7 @@ function failRun(run, error, retryable, now = /* @__PURE__ */ new Date()) {
|
|
|
115
149
|
const finalRetryable = retryable && run.attempt < 3;
|
|
116
150
|
const nowIso = now.toISOString();
|
|
117
151
|
const nextAttemptAt = finalRetryable ? new Date(now.getTime() + 15e3 * run.attempt).toISOString() : null;
|
|
118
|
-
db.prepare(`UPDATE proactive_runs SET status = ?, error_message = ?, lease_owner = NULL, lease_expires_at = NULL, next_attempt_at = ?, updated_at = ? WHERE run_id = ?`).run(finalRetryable ? "retryable" : "failed", String(error instanceof Error ? error.message : error).slice(0, 2e3), nextAttemptAt, nowIso, run.id);
|
|
152
|
+
if (!db.prepare(`UPDATE proactive_runs SET status = ?, error_message = ?, lease_owner = NULL, lease_expires_at = NULL, next_attempt_at = ?, updated_at = ? WHERE run_id = ? AND status = 'running' AND attempt = ?`).run(finalRetryable ? "retryable" : "failed", String(error instanceof Error ? error.message : error).slice(0, 2e3), nextAttemptAt, nowIso, run.id, run.attempt).changes) return;
|
|
119
153
|
db.prepare(`UPDATE proactive_signal_batches SET status = ?, updated_at = ? WHERE batch_id = ?`).run(finalRetryable ? "failed_retryable" : "failed_permanent", nowIso, run.batchId);
|
|
120
154
|
});
|
|
121
155
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { createLogger } from "../../utils/logger/index.js";
|
|
2
2
|
import { init_logger } from "../../utils/logger.js";
|
|
3
|
+
import { getSqliteDatabase } from "../../storage/sqlite/transaction.js";
|
|
4
|
+
import { effectiveProactivePolicy } from "../policy/service.js";
|
|
3
5
|
import { getPromptRevision, getScenario } from "../scenarios/repository.js";
|
|
4
6
|
import { composeScenarioPrompt } from "../scenarios/prompt-composer.js";
|
|
5
7
|
import { markReadyBatches } from "../routing/batch-repository.js";
|
|
6
8
|
import { ContextProviderRegistry } from "./context.js";
|
|
7
9
|
import { attachSnapshot, claimNextRun, eventIdsForBatch, failRun, finishRun, saveSnapshot } from "./repository.js";
|
|
8
|
-
import { isValuableInsight,
|
|
10
|
+
import { isValuableInsight, parseAnalysisResult, scoreInsight } from "./insight.js";
|
|
9
11
|
import { randomUUID } from "node:crypto";
|
|
10
12
|
//#region src/proactive/execution/worker.ts
|
|
11
13
|
init_logger();
|
|
@@ -42,6 +44,14 @@ var ProactiveWorker = class {
|
|
|
42
44
|
try {
|
|
43
45
|
const scenario = getScenario(run.scenarioKey, run.scenarioVersion);
|
|
44
46
|
if (!scenario) throw new Error("Pinned scenario version is unavailable");
|
|
47
|
+
if (!effectiveProactivePolicy(run.subscriptionId).enabled) {
|
|
48
|
+
finishRun({
|
|
49
|
+
run,
|
|
50
|
+
rawOutput: "",
|
|
51
|
+
outcomeReason: "disabled"
|
|
52
|
+
});
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
45
55
|
const revision = run.promptRevisionId ? getPromptRevision(run.promptRevisionId) ?? void 0 : void 0;
|
|
46
56
|
const eventIds = eventIdsForBatch(run.batchId);
|
|
47
57
|
const context = await this.contexts.collect(scenario, {
|
|
@@ -49,6 +59,14 @@ var ProactiveWorker = class {
|
|
|
49
59
|
eventIds,
|
|
50
60
|
subscriptionId: run.subscriptionId
|
|
51
61
|
});
|
|
62
|
+
if (context.evidenceIds.length === 0) {
|
|
63
|
+
finishRun({
|
|
64
|
+
run,
|
|
65
|
+
rawOutput: "",
|
|
66
|
+
outcomeReason: "source_unavailable"
|
|
67
|
+
});
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
52
70
|
const snapshot = saveSnapshot(run.batchId, context.snapshotContent ?? context.content, context.evidenceIds);
|
|
53
71
|
attachSnapshot(run.id, snapshot.id);
|
|
54
72
|
const prompt = composeScenarioPrompt({
|
|
@@ -61,9 +79,21 @@ var ProactiveWorker = class {
|
|
|
61
79
|
userPrompt: prompt.text,
|
|
62
80
|
authorizedContext: context.content
|
|
63
81
|
});
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
82
|
+
if (output.usage) getSqliteDatabase().prepare("UPDATE proactive_runs SET input_tokens = ?, output_tokens = ?, estimated_cost_usd = ? WHERE run_id = ? AND attempt = ?").run(output.usage.inputTokens, output.usage.outputTokens, output.usage.estimatedCostUsd ?? null, run.id, run.attempt);
|
|
83
|
+
const latestContext = await this.contexts.collect(scenario, {
|
|
84
|
+
batchId: run.batchId,
|
|
85
|
+
eventIds,
|
|
86
|
+
subscriptionId: run.subscriptionId
|
|
87
|
+
});
|
|
88
|
+
const revoked = context.evidenceIds.some((id) => !latestContext.evidenceIds.includes(id));
|
|
89
|
+
const result = revoked ? {
|
|
90
|
+
result: "no_insight",
|
|
91
|
+
reason: "source_unavailable"
|
|
92
|
+
} : parseAnalysisResult(output.text, new Set(latestContext.evidenceIds));
|
|
93
|
+
const candidate = result.result === "insight" ? result.candidate : void 0;
|
|
94
|
+
const valueScore = candidate ? scoreInsight(candidate) : 0;
|
|
95
|
+
const enabled = effectiveProactivePolicy(run.subscriptionId).enabled;
|
|
96
|
+
const valuable = enabled && candidate && isValuableInsight(candidate, scenario.valuePolicy);
|
|
67
97
|
const insight = finishRun({
|
|
68
98
|
run,
|
|
69
99
|
...valuable ? {
|
|
@@ -71,16 +101,17 @@ var ProactiveWorker = class {
|
|
|
71
101
|
valueScore
|
|
72
102
|
} : {},
|
|
73
103
|
cooldownSeconds: scenario.valuePolicy.cooldownSeconds,
|
|
74
|
-
|
|
104
|
+
outcomeReason: !enabled ? "disabled" : result.result === "no_insight" ? result.reason : valuable ? "insight" : "below_threshold",
|
|
105
|
+
rawOutput: revoked ? "" : output.text,
|
|
75
106
|
modelRef: output.modelRef
|
|
76
107
|
});
|
|
77
|
-
const disposition = insight ? "created" : valuable ? "duplicate_suppressed" : "value_gate_discarded";
|
|
108
|
+
const disposition = insight ? "created" : valuable ? "duplicate_suppressed" : result.result === "no_insight" ? result.reason : "value_gate_discarded";
|
|
78
109
|
log.info({
|
|
79
110
|
runId: run.id,
|
|
80
111
|
scenarioKey: run.scenarioKey,
|
|
81
112
|
disposition,
|
|
82
113
|
insightId: insight?.id
|
|
83
|
-
}, insight ? "Proactive insight created" : "Proactive
|
|
114
|
+
}, insight ? "Proactive insight created" : "Proactive check completed without a card");
|
|
84
115
|
} catch (error) {
|
|
85
116
|
failRun(run, error, !(error instanceof Error && /Pinned scenario/.test(error.message)));
|
|
86
117
|
log.warn({
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ProactiveCard } from '@xopcai/gateway-contract';
|
|
2
|
+
export declare function requireCardScope(id: string, workspaceId: string): void;
|
|
3
|
+
export declare function getCard(id: string, workspaceId: string): ProactiveCard;
|
|
4
|
+
export declare function listCards(workspaceId: string, input?: {
|
|
5
|
+
status?: string;
|
|
6
|
+
before?: string;
|
|
7
|
+
limit?: number;
|
|
8
|
+
}): {
|
|
9
|
+
cards: ProactiveCard[];
|
|
10
|
+
nextCursor: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function cardChanges(workspaceId: string, cursor?: number): {
|
|
13
|
+
items: {
|
|
14
|
+
id: string;
|
|
15
|
+
deleted: boolean;
|
|
16
|
+
card?: ProactiveCard;
|
|
17
|
+
}[];
|
|
18
|
+
nextCursor: number;
|
|
19
|
+
hasMore: boolean;
|
|
20
|
+
};
|
|
21
|
+
export declare function performCardAction(id: string, workspaceId: string, value: unknown): ProactiveCard;
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { getSqliteDatabase, runSqliteWriteTransaction } from "../../storage/sqlite/transaction.js";
|
|
2
|
+
import { ProactiveCardActionSchema } from "../../../packages/gateway-contract/src/proactive.js";
|
|
3
|
+
import { getKnowledgeSourceItem } from "../../storage/sqlite/knowledge-repository.js";
|
|
4
|
+
import { ProactiveConflict, effectiveProactivePolicy, subscriptionSettings } from "../policy/service.js";
|
|
5
|
+
import { insightSourcesAuthorized } from "../execution/authorization.js";
|
|
6
|
+
import { withdrawCard } from "./lifecycle.js";
|
|
7
|
+
import { executePendingProactiveActions } from "../actions/service.js";
|
|
8
|
+
import { updateControlledSubscription } from "../scenarios/control.js";
|
|
9
|
+
import { getInboxItem } from "./repository.js";
|
|
10
|
+
import { ProactiveInboxService } from "./service.js";
|
|
11
|
+
//#region src/proactive/inbox/cards.ts
|
|
12
|
+
function requireCardScope(id, workspaceId) {
|
|
13
|
+
if (!getSqliteDatabase().prepare(`SELECT 1 FROM proactive_inbox_items i JOIN proactive_insights x USING(insight_id)
|
|
14
|
+
JOIN proactive_scenario_subscriptions s USING(subscription_id) WHERE i.inbox_item_id = ? AND s.workspace_id = ?`).get(id, workspaceId)) throw new Error("Card not found");
|
|
15
|
+
}
|
|
16
|
+
function getCard(id, workspaceId) {
|
|
17
|
+
requireCardScope(id, workspaceId);
|
|
18
|
+
const item = getInboxItem(id);
|
|
19
|
+
if (!insightSourcesAuthorized(item.insightId)) {
|
|
20
|
+
withdrawCard(id);
|
|
21
|
+
item.withdrawnAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
22
|
+
}
|
|
23
|
+
if (item.withdrawnAt) return {
|
|
24
|
+
schemaVersion: 1,
|
|
25
|
+
id,
|
|
26
|
+
revision: getInboxItem(id).revision,
|
|
27
|
+
notificationRevision: item.notificationRevision,
|
|
28
|
+
subscriptionId: item.subscriptionId,
|
|
29
|
+
scenarioKey: item.insight.scenarioKey,
|
|
30
|
+
kind: "briefing",
|
|
31
|
+
status: "withdrawn",
|
|
32
|
+
title: "Source unavailable / 来源已撤回",
|
|
33
|
+
summary: "",
|
|
34
|
+
whyNow: "",
|
|
35
|
+
recommendation: "",
|
|
36
|
+
workDone: "",
|
|
37
|
+
evidence: [],
|
|
38
|
+
createdAt: item.createdAt,
|
|
39
|
+
updatedAt: item.withdrawnAt,
|
|
40
|
+
fallbackText: "Source unavailable / 来源已撤回"
|
|
41
|
+
};
|
|
42
|
+
const insight = item.insight;
|
|
43
|
+
const expired = item.expiresAt && Date.parse(item.expiresAt) <= Date.now();
|
|
44
|
+
return {
|
|
45
|
+
schemaVersion: 1,
|
|
46
|
+
id,
|
|
47
|
+
revision: item.revision,
|
|
48
|
+
notificationRevision: item.notificationRevision,
|
|
49
|
+
subscriptionId: item.subscriptionId,
|
|
50
|
+
preparationAvailable: Boolean(subscriptionSettings(item.subscriptionId).preparationWorkflowId),
|
|
51
|
+
relatedCardIds: item.correlationKey ? getSqliteDatabase().prepare("SELECT inbox_item_id AS id FROM proactive_inbox_items WHERE correlation_key = ? AND inbox_item_id <> ? AND withdrawn_at IS NULL").all(item.correlationKey, id).map((row) => row.id) : [],
|
|
52
|
+
scenarioKey: insight.scenarioKey,
|
|
53
|
+
kind: insight.attentionKind === "receipt" ? "receipt" : insight.decision ? "decision" : insight.scenarioKey === "meeting_preparation" ? "briefing" : insight.scenarioKey.includes("risk") || insight.scenarioKey.includes("failure") ? "risk" : insight.scenarioKey === "blocked_work" ? "reminder" : "recommendation",
|
|
54
|
+
status: expired ? "expired" : item.status,
|
|
55
|
+
title: insight.title,
|
|
56
|
+
summary: insight.summary,
|
|
57
|
+
whyNow: insight.whyNow,
|
|
58
|
+
recommendation: insight.recommendation,
|
|
59
|
+
workDone: insight.workDone,
|
|
60
|
+
evidence: insight.evidenceIds.map((evidenceId) => cardEvidence(evidenceId, workspaceId)),
|
|
61
|
+
...insight.decision ? { decision: insight.decision } : {},
|
|
62
|
+
actionStatus: insight.actionStatus,
|
|
63
|
+
actionResult: insight.actionResult,
|
|
64
|
+
actionError: insight.actionError,
|
|
65
|
+
createdAt: item.createdAt,
|
|
66
|
+
updatedAt: item.updatedAt,
|
|
67
|
+
expiresAt: item.expiresAt,
|
|
68
|
+
fallbackText: `${insight.title}\n${insight.summary}\n${insight.whyNow}`
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function listCards(workspaceId, input = {}) {
|
|
72
|
+
const limit = Math.min(100, Math.max(1, input.limit ?? 30));
|
|
73
|
+
const rows = getSqliteDatabase().prepare(`SELECT i.inbox_item_id, i.created_at FROM proactive_inbox_items i
|
|
74
|
+
JOIN proactive_insights x USING(insight_id) JOIN proactive_scenario_subscriptions s USING(subscription_id)
|
|
75
|
+
WHERE i.withdrawn_at IS NULL AND s.workspace_id = ? AND (? IS NULL OR i.status = ?) AND (? IS NULL OR i.created_at || ':' || i.inbox_item_id < ?)
|
|
76
|
+
ORDER BY i.created_at DESC, i.inbox_item_id DESC LIMIT ?`).all(workspaceId, input.status || null, input.status || null, input.before || null, input.before || null, limit + 1);
|
|
77
|
+
const page = rows.slice(0, limit);
|
|
78
|
+
const last = page.at(-1);
|
|
79
|
+
return {
|
|
80
|
+
cards: page.flatMap((row) => {
|
|
81
|
+
try {
|
|
82
|
+
const card = getCard(row.inbox_item_id, workspaceId);
|
|
83
|
+
return card.status === "withdrawn" ? [] : [card];
|
|
84
|
+
} catch {
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
}),
|
|
88
|
+
nextCursor: rows.length > limit && last ? `${last.created_at}:${last.inbox_item_id}` : null
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function cardChanges(workspaceId, cursor = 0) {
|
|
92
|
+
const rows = getSqliteDatabase().prepare("SELECT sequence, inbox_item_id, deleted FROM proactive_card_changes WHERE sequence > ? AND workspace_id = ? ORDER BY sequence LIMIT 200").all(cursor, workspaceId);
|
|
93
|
+
const items = [];
|
|
94
|
+
for (const row of rows) {
|
|
95
|
+
if (row.deleted) {
|
|
96
|
+
items.push({
|
|
97
|
+
id: row.inbox_item_id,
|
|
98
|
+
deleted: true
|
|
99
|
+
});
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
const card = getCard(row.inbox_item_id, workspaceId);
|
|
104
|
+
items.push(card.status === "withdrawn" ? {
|
|
105
|
+
id: row.inbox_item_id,
|
|
106
|
+
deleted: true
|
|
107
|
+
} : {
|
|
108
|
+
id: row.inbox_item_id,
|
|
109
|
+
deleted: false,
|
|
110
|
+
card
|
|
111
|
+
});
|
|
112
|
+
} catch {
|
|
113
|
+
items.push({
|
|
114
|
+
id: row.inbox_item_id,
|
|
115
|
+
deleted: true
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
items,
|
|
121
|
+
nextCursor: rows.at(-1)?.sequence ?? cursor,
|
|
122
|
+
hasMore: rows.length === 200
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function performCardAction(id, workspaceId, value) {
|
|
126
|
+
const input = ProactiveCardActionSchema.parse(value);
|
|
127
|
+
return runSqliteWriteTransaction((db) => {
|
|
128
|
+
requireCardScope(id, workspaceId);
|
|
129
|
+
const request = JSON.stringify({
|
|
130
|
+
id,
|
|
131
|
+
...input
|
|
132
|
+
});
|
|
133
|
+
const prior = db.prepare("SELECT request_json, response_json FROM proactive_card_actions WHERE idempotency_key = ?").get(input.idempotencyKey);
|
|
134
|
+
if (prior) {
|
|
135
|
+
if (prior.request_json !== request) throw new ProactiveConflict("Idempotency key was already used for a different action");
|
|
136
|
+
return getCard(id, workspaceId);
|
|
137
|
+
}
|
|
138
|
+
const card = getCard(id, workspaceId);
|
|
139
|
+
if (card.revision !== input.expectedRevision) throw new ProactiveConflict("Card changed; refresh before acting");
|
|
140
|
+
if (card.status === "withdrawn" || card.status === "expired" || card.status === "resolved" && ![
|
|
141
|
+
"useful",
|
|
142
|
+
"not_useful",
|
|
143
|
+
"retry"
|
|
144
|
+
].includes(input.actionId)) throw new ProactiveConflict("Card is no longer actionable");
|
|
145
|
+
applyCardAction(card, workspaceId, input);
|
|
146
|
+
const result = getCard(id, workspaceId);
|
|
147
|
+
db.prepare("INSERT INTO proactive_card_actions(idempotency_key, inbox_item_id, request_json, response_json) VALUES (?, ?, ?, ?)").run(input.idempotencyKey, id, request, JSON.stringify(result));
|
|
148
|
+
return result;
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
function applyCardAction(card, workspaceId, input) {
|
|
152
|
+
const service = new ProactiveInboxService();
|
|
153
|
+
switch (input.actionId) {
|
|
154
|
+
case "retry":
|
|
155
|
+
if (!effectiveProactivePolicy(card.subscriptionId).enabled) throw new Error("Subscription paused");
|
|
156
|
+
if (!getSqliteDatabase().prepare("UPDATE proactive_insights SET action_status = 'pending', action_error = NULL, action_updated_at = ? WHERE insight_id = (SELECT insight_id FROM proactive_inbox_items WHERE inbox_item_id = ?) AND action_status = 'failed'").run((/* @__PURE__ */ new Date()).toISOString(), card.id).changes) throw new Error("Only failed actions can be retried");
|
|
157
|
+
executePendingProactiveActions();
|
|
158
|
+
break;
|
|
159
|
+
case "useful":
|
|
160
|
+
case "not_useful":
|
|
161
|
+
service.feedback(card.id, input.actionId);
|
|
162
|
+
break;
|
|
163
|
+
case "read":
|
|
164
|
+
service.transition(card.id, { status: "read" });
|
|
165
|
+
break;
|
|
166
|
+
case "resolve":
|
|
167
|
+
service.transition(card.id, {
|
|
168
|
+
status: "resolved",
|
|
169
|
+
resolution: "dismissed"
|
|
170
|
+
});
|
|
171
|
+
break;
|
|
172
|
+
case "snooze":
|
|
173
|
+
service.transition(card.id, {
|
|
174
|
+
status: "snoozed",
|
|
175
|
+
snoozedUntil: input.snoozedUntil ?? new Date(Date.now() + 36e5).toISOString()
|
|
176
|
+
});
|
|
177
|
+
break;
|
|
178
|
+
case "decide":
|
|
179
|
+
if (!input.choice) throw new Error("Decision choice required");
|
|
180
|
+
if (!effectiveProactivePolicy(card.subscriptionId).enabled) throw new Error("Subscription paused");
|
|
181
|
+
service.decide(card.id, input.choice);
|
|
182
|
+
break;
|
|
183
|
+
case "less":
|
|
184
|
+
case "pause": {
|
|
185
|
+
const settings = subscriptionSettings(card.subscriptionId);
|
|
186
|
+
updateControlledSubscription(workspaceId, card.subscriptionId, {
|
|
187
|
+
expectedRevision: settings.revision,
|
|
188
|
+
...input.actionId === "less" ? { delivery: "inbox" } : { enabled: false }
|
|
189
|
+
});
|
|
190
|
+
service.transition(card.id, {
|
|
191
|
+
status: "resolved",
|
|
192
|
+
resolution: input.actionId
|
|
193
|
+
});
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function cardEvidence(id, workspaceId) {
|
|
199
|
+
const db = getSqliteDatabase();
|
|
200
|
+
const split = id.indexOf(":");
|
|
201
|
+
const kind = id.slice(0, split);
|
|
202
|
+
const objectId = id.slice(split + 1);
|
|
203
|
+
if (kind === "task") {
|
|
204
|
+
const task = db.prepare("SELECT title FROM tasks WHERE task_id = ?").get(objectId);
|
|
205
|
+
if (task) return {
|
|
206
|
+
id,
|
|
207
|
+
label: task.title,
|
|
208
|
+
route: `/tasks/${encodeURIComponent(objectId)}`
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
if (kind === "project") {
|
|
212
|
+
const project = db.prepare("SELECT name FROM projects WHERE project_id = ?").get(objectId);
|
|
213
|
+
if (project) return {
|
|
214
|
+
id,
|
|
215
|
+
label: project.name,
|
|
216
|
+
route: `/projects/${encodeURIComponent(objectId)}`
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
if (kind === "source-item") {
|
|
220
|
+
const source = getKnowledgeSourceItem(objectId);
|
|
221
|
+
if (source && !source.deletedAt && source.metadata.workspaceId === workspaceId) {
|
|
222
|
+
const excerpt = source.normalizedText?.slice(0, 600) ?? "";
|
|
223
|
+
return {
|
|
224
|
+
id,
|
|
225
|
+
label: typeof source.metadata.title === "string" ? source.metadata.title.slice(0, 120) : excerpt.split("\n")[0]?.slice(0, 120) || source.itemType,
|
|
226
|
+
excerpt
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const event = db.prepare("SELECT type, occurred_at FROM proactive_events WHERE event_id = ? AND workspace_id = ?").get(id, workspaceId);
|
|
231
|
+
return {
|
|
232
|
+
id,
|
|
233
|
+
label: event ? `${event.type} · ${event.occurred_at}` : kind || "Evidence"
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
//#endregion
|
|
237
|
+
export { cardChanges, getCard, listCards, performCardAction, requireCardScope };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { NotificationService } from '../../notifications/service.js';
|
|
2
|
+
import type { InboxItem } from './types.js';
|
|
3
|
+
/** The outbox is acknowledged only after a notification or suppression is persisted. */
|
|
4
|
+
export declare function deliverProactiveCard(item: InboxItem, notifications: NotificationService, publish: (type: string, payload: unknown) => void): void | {
|
|
5
|
+
retryAt: string;
|
|
6
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { getSqliteDatabase, runSqliteWriteTransaction } from "../../storage/sqlite/transaction.js";
|
|
2
|
+
import { effectiveProactivePolicy, localProactiveDay, nextDigestTime, quietHoursEnd, reserveProactiveNotification } from "../policy/service.js";
|
|
3
|
+
import { viewingProactiveCards } from "../policy/presence.js";
|
|
4
|
+
import { insightSourcesAuthorized } from "../execution/authorization.js";
|
|
5
|
+
import { insightCorrelation } from "./lifecycle.js";
|
|
6
|
+
import { getInboxItem } from "./repository.js";
|
|
7
|
+
import { queueDigest } from "./digest.js";
|
|
8
|
+
import { notificationPlanFromGatewayEvent } from "../../notifications/planner.js";
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
10
|
+
//#region src/proactive/inbox/delivery.ts
|
|
11
|
+
/** The outbox is acknowledged only after a notification or suppression is persisted. */
|
|
12
|
+
function deliverProactiveCard(item, notifications, publish) {
|
|
13
|
+
const result = runSqliteWriteTransaction(() => {
|
|
14
|
+
const current = getInboxItem(item.id);
|
|
15
|
+
if (!current || current.withdrawnAt || !insightSourcesAuthorized(current.insightId) || current.status === "resolved" || current.expiresAt && Date.parse(current.expiresAt) <= Date.now()) return null;
|
|
16
|
+
if (current.status === "snoozed") return { retryAt: current.snoozedUntil };
|
|
17
|
+
const policy = effectiveProactivePolicy(current.subscriptionId);
|
|
18
|
+
if (!policy.enabled || policy.settings.delivery === "inbox") return null;
|
|
19
|
+
if ((policy.preferences.digestEnabled || policy.settings.delivery === "digest") && current.insight.attentionKind !== "receipt") {
|
|
20
|
+
queueDigest(current, "daily", nextDigestTime(policy.preferences, /* @__PURE__ */ new Date()));
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
if (!notificationPlanFromGatewayEvent("proactive.inbox.created", current)) return null;
|
|
24
|
+
if (policy.level === "quiet") return null;
|
|
25
|
+
const quietUntil = quietHoursEnd(policy.preferences, /* @__PURE__ */ new Date());
|
|
26
|
+
if (quietUntil) {
|
|
27
|
+
queueDigest(current, "quiet", quietUntil);
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const db = getSqliteDatabase();
|
|
31
|
+
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)}`;
|
|
32
|
+
if (db.prepare("SELECT 1 FROM proactive_delivery_decisions WHERE notification_key = ?").get(key)) return null;
|
|
33
|
+
if (policy.preferences.suppressWhileViewing && viewingProactiveCards(policy.workspaceId)) {
|
|
34
|
+
db.prepare("INSERT INTO proactive_delivery_decisions VALUES (?, ?, ?, ?)").run(key, policy.workspaceId, "visible_in_app", (/* @__PURE__ */ new Date()).toISOString());
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const decision = reserveProactiveNotification(current.subscriptionId, key);
|
|
38
|
+
if (decision instanceof Date) return { retryAt: decision.toISOString() };
|
|
39
|
+
if (decision === "suppressed") return null;
|
|
40
|
+
const result = notifications.persistGatewayEvent("proactive.inbox.created", current);
|
|
41
|
+
db.prepare("INSERT INTO proactive_delivery_decisions VALUES (?, ?, ?, ?)").run(key, policy.workspaceId, "immediate", (/* @__PURE__ */ new Date()).toISOString());
|
|
42
|
+
return result;
|
|
43
|
+
});
|
|
44
|
+
publish("proactive.card.changed", {
|
|
45
|
+
cardId: item.id,
|
|
46
|
+
revision: item.revision
|
|
47
|
+
});
|
|
48
|
+
if (result && "retryAt" in result) return result;
|
|
49
|
+
if (result) publish("notification.created", result);
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
export { deliverProactiveCard };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ProductNotification } from '@xopcai/gateway-contract';
|
|
2
|
+
import type { NotificationPlan } from '../../notifications/planner.js';
|
|
3
|
+
import type { InboxItem } from './types.js';
|
|
4
|
+
export { nextDigestTime } from '../policy/service.js';
|
|
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[];
|
|
7
|
+
export declare function reserveWorkspaceAttention(workspaceId: string, key: string, now?: Date): boolean;
|
|
8
|
+
export declare function flushDueDigests(persist: (plan: NotificationPlan) => ProductNotification | null, now?: Date): ProductNotification[];
|