@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
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { createLogger } from "../utils/logger/index.js";
|
|
2
2
|
import { init_logger } from "../utils/logger.js";
|
|
3
|
+
import { getSqliteDatabase, runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
|
|
3
4
|
import { localizeNotification } from "../../packages/gateway-contract/src/notifications.js";
|
|
4
|
-
import {
|
|
5
|
+
import { proactivePreferences } from "../proactive/policy/service.js";
|
|
6
|
+
import { flushDueDigests } from "../proactive/inbox/digest.js";
|
|
5
7
|
import { notificationPlanFromGatewayEvent } from "./planner.js";
|
|
6
|
-
import {
|
|
8
|
+
import { proactiveNotificationWorkspace, recheckNotificationDelivery } from "./proactive-policy.js";
|
|
9
|
+
import { createNotificationEvent, deferNotificationDelivery, expireUndeliverableNotificationDeliveries, listDueNotificationDeliveries, markNotificationDeliveryAccepted, markNotificationDeliveryDead, markNotificationDeliveryDelivered, pruneNotificationEvents, rescheduleNotificationDelivery } from "./store.js";
|
|
10
|
+
import { drainChannelNotifications, enqueueChannelNotification } from "./proactive-channel.js";
|
|
11
|
+
import { drainBrowserPush, enqueueBrowserPush } from "./web-push.js";
|
|
12
|
+
import { disableNotificationDeviceForPushToken, listDeliverableNotificationDevices } from "./device-store.js";
|
|
7
13
|
//#region src/notifications/service.ts
|
|
8
14
|
init_logger();
|
|
9
15
|
const log = createLogger("Notifications");
|
|
@@ -58,15 +64,9 @@ var NotificationService = class {
|
|
|
58
64
|
}
|
|
59
65
|
handleGatewayEvent(type, payload) {
|
|
60
66
|
try {
|
|
61
|
-
const
|
|
62
|
-
if (!
|
|
63
|
-
|
|
64
|
-
const result = createNotificationEvent({
|
|
65
|
-
...plan,
|
|
66
|
-
deviceIds: devices.map((device) => device.id)
|
|
67
|
-
});
|
|
68
|
-
if (!result.created) return;
|
|
69
|
-
this.options.publish("notification.created", result.notification);
|
|
67
|
+
const notification = this.persistGatewayEvent(type, payload);
|
|
68
|
+
if (!notification) return;
|
|
69
|
+
this.options.publish("notification.created", notification);
|
|
70
70
|
this.drain();
|
|
71
71
|
} catch (err) {
|
|
72
72
|
log.error({
|
|
@@ -75,6 +75,50 @@ var NotificationService = class {
|
|
|
75
75
|
}, "Notification event persistence failed");
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
|
+
/** Throws on persistence failure so durable producers can retry the handoff. */
|
|
79
|
+
persistGatewayEvent(type, payload) {
|
|
80
|
+
const plan = notificationPlanFromGatewayEvent(type, payload);
|
|
81
|
+
if (!plan) return null;
|
|
82
|
+
return this.persistPlan(plan);
|
|
83
|
+
}
|
|
84
|
+
persistPlan(plan) {
|
|
85
|
+
let devices = listDeliverableNotificationDevices().filter((device) => preferenceAllows(plan.notification.type, device.preferences));
|
|
86
|
+
const workspace = proactiveNotificationWorkspace(plan.notification);
|
|
87
|
+
let browserIds;
|
|
88
|
+
let selectedChannel = "all";
|
|
89
|
+
if (workspace) {
|
|
90
|
+
const preferences = proactivePreferences(workspace);
|
|
91
|
+
const browsers = getSqliteDatabase().prepare("SELECT id FROM proactive_web_push_subscriptions WHERE workspace_id = ? ORDER BY created_at DESC, id").all(workspace);
|
|
92
|
+
selectedChannel = preferences.preferredChannel === "auto" ? browsers.length ? "browser" : devices.length ? "mobile" : "browser" : preferences.preferredChannel;
|
|
93
|
+
if (!["all", "mobile"].includes(selectedChannel)) devices = [];
|
|
94
|
+
else if (preferences.preferredChannel === "auto") devices = devices.slice(0, 1);
|
|
95
|
+
browserIds = ["all", "browser"].includes(selectedChannel) ? browsers.map((row) => row.id) : [];
|
|
96
|
+
if (preferences.preferredChannel === "auto") browserIds = browserIds.slice(0, 1);
|
|
97
|
+
plan = {
|
|
98
|
+
...plan,
|
|
99
|
+
notification: {
|
|
100
|
+
...plan.notification,
|
|
101
|
+
payload: {
|
|
102
|
+
...plan.notification.payload,
|
|
103
|
+
deliveryChannel: selectedChannel,
|
|
104
|
+
deliveryMode: preferences.preferredChannel
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const result = runSqliteWriteTransaction(() => {
|
|
110
|
+
const created = createNotificationEvent({
|
|
111
|
+
...plan,
|
|
112
|
+
deviceIds: devices.map((device) => device.id)
|
|
113
|
+
});
|
|
114
|
+
if (created.created) {
|
|
115
|
+
enqueueBrowserPush(created.notification, browserIds);
|
|
116
|
+
if (workspace && selectedChannel === "telegram") enqueueChannelNotification(created.notification, workspace);
|
|
117
|
+
}
|
|
118
|
+
return created;
|
|
119
|
+
});
|
|
120
|
+
return result.created ? result.notification : null;
|
|
121
|
+
}
|
|
78
122
|
async drain() {
|
|
79
123
|
if (this.draining) return;
|
|
80
124
|
this.draining = true;
|
|
@@ -85,8 +129,11 @@ var NotificationService = class {
|
|
|
85
129
|
pruneNotificationEvents(now - 720 * 60 * 60 * 1e3);
|
|
86
130
|
this.lastMaintenanceAt = now;
|
|
87
131
|
}
|
|
132
|
+
for (const notification of flushDueDigests((plan) => this.persistPlan(plan))) this.options.publish("notification.created", notification);
|
|
88
133
|
await this.deliverPending();
|
|
89
134
|
await this.checkReceipts();
|
|
135
|
+
await drainBrowserPush();
|
|
136
|
+
if (this.options.sendChannel) await drainChannelNotifications(this.options.sendChannel);
|
|
90
137
|
} catch (err) {
|
|
91
138
|
log.warn({ err }, "Notification delivery pass failed");
|
|
92
139
|
} finally {
|
|
@@ -98,6 +145,17 @@ var NotificationService = class {
|
|
|
98
145
|
await Promise.all(deliveries.map((delivery) => this.send(delivery)));
|
|
99
146
|
}
|
|
100
147
|
async send(delivery) {
|
|
148
|
+
if (delivery.event.type === "proactive.insight") {
|
|
149
|
+
const policy = recheckNotificationDelivery(delivery.event, "mobile");
|
|
150
|
+
if (policy === "cancel") {
|
|
151
|
+
markNotificationDeliveryDead(delivery.event.id, delivery.deviceId, "Proactive policy changed");
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (policy instanceof Date) {
|
|
155
|
+
deferNotificationDelivery(delivery.event.id, delivery.deviceId, policy.getTime());
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
101
159
|
const fetchImpl = this.options.fetch ?? fetch;
|
|
102
160
|
const localized = localizeNotification(delivery.event, delivery.locale);
|
|
103
161
|
try {
|
|
@@ -110,8 +168,8 @@ var NotificationService = class {
|
|
|
110
168
|
signal: AbortSignal.timeout(1e4),
|
|
111
169
|
body: JSON.stringify({
|
|
112
170
|
to: delivery.pushToken,
|
|
113
|
-
title: localized.localizedTitle,
|
|
114
|
-
body: localized.localizedBody,
|
|
171
|
+
title: delivery.event.type === "proactive.insight" ? delivery.locale.startsWith("zh") ? "有一项工作需要查看" : "A work update is ready" : localized.localizedTitle,
|
|
172
|
+
body: delivery.event.type === "proactive.insight" ? delivery.locale.startsWith("zh") ? "打开 xopc 查看详情。" : "Open xopc to review it." : localized.localizedBody,
|
|
115
173
|
sound: delivery.event.priority === "high" ? "default" : void 0,
|
|
116
174
|
priority: delivery.event.priority,
|
|
117
175
|
data: {
|
|
@@ -51,3 +51,5 @@ export declare function notificationDeliveryMetrics(): {
|
|
|
51
51
|
};
|
|
52
52
|
export declare function expireUndeliverableNotificationDeliveries(now?: number): number;
|
|
53
53
|
export declare function pruneNotificationEvents(before: number): number;
|
|
54
|
+
export declare function deferNotificationDelivery(eventId: string, deviceId: string, nextAttemptAt: number): void;
|
|
55
|
+
export declare function getNotificationEvent(id: string): ProductNotification | null;
|
|
@@ -180,5 +180,12 @@ function pruneNotificationEvents(before) {
|
|
|
180
180
|
AND d.status IN ('pending', 'accepted')
|
|
181
181
|
)`).run(before).changes));
|
|
182
182
|
}
|
|
183
|
+
function deferNotificationDelivery(eventId, deviceId, nextAttemptAt) {
|
|
184
|
+
getSqliteDatabase().prepare("UPDATE notification_deliveries SET next_attempt_at = ?, last_error = 'Proactive quiet hours' WHERE event_id = ? AND device_id = ? AND status = 'pending'").run(nextAttemptAt, eventId, deviceId);
|
|
185
|
+
}
|
|
186
|
+
function getNotificationEvent(id) {
|
|
187
|
+
const row = getSqliteDatabase().prepare("SELECT * FROM notification_events WHERE event_id = ?").get(id);
|
|
188
|
+
return row ? eventFromRow(row) : null;
|
|
189
|
+
}
|
|
183
190
|
//#endregion
|
|
184
|
-
export { acknowledgeNotification, createNotificationEvent, expireUndeliverableNotificationDeliveries, listDueNotificationDeliveries, listNotificationEvents, markNotificationDeliveryAccepted, markNotificationDeliveryDead, markNotificationDeliveryDelivered, notificationDeliveryMetrics, pruneNotificationEvents, rescheduleNotificationDelivery };
|
|
191
|
+
export { acknowledgeNotification, createNotificationEvent, deferNotificationDelivery, expireUndeliverableNotificationDeliveries, getNotificationEvent, listDueNotificationDeliveries, listNotificationEvents, markNotificationDeliveryAccepted, markNotificationDeliveryDead, markNotificationDeliveryDelivered, notificationDeliveryMetrics, pruneNotificationEvents, rescheduleNotificationDelivery };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ProductNotification } from '@xopcai/gateway-contract';
|
|
2
|
+
import webPush from 'web-push';
|
|
3
|
+
export declare function allowedPushEndpoint(value: string): boolean;
|
|
4
|
+
export declare function prepareBrowserPush(): {
|
|
5
|
+
publicKey: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function registerBrowserPush(workspaceId: string, value: unknown): {
|
|
8
|
+
id: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function unregisterBrowserPush(workspaceId: string, id: string): void;
|
|
11
|
+
export declare function enqueueBrowserPush(notification: ProductNotification, selectedIds?: string[]): void;
|
|
12
|
+
export declare function drainBrowserPush(send?: typeof webPush.sendNotification): Promise<void>;
|
|
13
|
+
/** A user-triggered probe to this browser only; provider acceptance is not user delivery. */
|
|
14
|
+
export declare function testBrowserPush(workspaceId: string, subscriptionId: string, send?: typeof webPush.sendNotification): Promise<{
|
|
15
|
+
id: `${string}-${string}-${string}-${string}-${string}`;
|
|
16
|
+
status: string;
|
|
17
|
+
}>;
|
|
18
|
+
export declare function acknowledgeBrowserProbe(workspaceId: string, id: string): void;
|
|
19
|
+
export declare function listBrowserProbes(workspaceId: string): Record<string, import("node:sqlite").SQLOutputValue>[];
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { getSqliteDatabase, runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
|
|
2
|
+
import { ProductNotificationSchema, notificationTargetRoute } from "../../packages/gateway-contract/src/notifications.js";
|
|
3
|
+
import { proactiveNotificationWorkspace, recheckNotificationDelivery } from "./proactive-policy.js";
|
|
4
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import webPush from "web-push";
|
|
7
|
+
//#region src/notifications/web-push.ts
|
|
8
|
+
function allowedPushEndpoint(value) {
|
|
9
|
+
try {
|
|
10
|
+
const url = new URL(value);
|
|
11
|
+
const host = url.hostname;
|
|
12
|
+
return url.protocol === "https:" && !url.username && !url.password && (!url.port || url.port === "443") && (host === "fcm.googleapis.com" || host === "updates.push.services.mozilla.com" || host.endsWith(".push.services.mozilla.com") || host === "web.push.apple.com" || host.endsWith(".notify.windows.com"));
|
|
13
|
+
} catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const BrowserPushSchema = z.object({
|
|
18
|
+
subscription: z.object({
|
|
19
|
+
endpoint: z.string().max(4096).refine(allowedPushEndpoint, "Unsupported push service"),
|
|
20
|
+
expirationTime: z.number().nullable().optional(),
|
|
21
|
+
keys: z.object({
|
|
22
|
+
auth: z.string().regex(/^[A-Za-z0-9_-]{22}$/),
|
|
23
|
+
p256dh: z.string().regex(/^[A-Za-z0-9_-]{87}$/)
|
|
24
|
+
}).strict()
|
|
25
|
+
}).strict(),
|
|
26
|
+
language: z.enum(["en", "zh"]).default("en")
|
|
27
|
+
}).strict();
|
|
28
|
+
function prepareBrowserPush() {
|
|
29
|
+
return runSqliteWriteTransaction((db) => {
|
|
30
|
+
let keys = db.prepare("SELECT public_key, private_key FROM proactive_web_push_keys WHERE id = 1").get();
|
|
31
|
+
if (!keys) {
|
|
32
|
+
const generated = webPush.generateVAPIDKeys();
|
|
33
|
+
db.prepare("INSERT INTO proactive_web_push_keys(id, public_key, private_key) VALUES (1, ?, ?)").run(generated.publicKey, generated.privateKey);
|
|
34
|
+
keys = {
|
|
35
|
+
public_key: generated.publicKey,
|
|
36
|
+
private_key: generated.privateKey
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return { publicKey: keys.public_key };
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function registerBrowserPush(workspaceId, value) {
|
|
43
|
+
const input = BrowserPushSchema.parse(value);
|
|
44
|
+
return runSqliteWriteTransaction((db) => {
|
|
45
|
+
const existing = db.prepare("SELECT id, workspace_id FROM proactive_web_push_subscriptions WHERE endpoint = ?").get(input.subscription.endpoint);
|
|
46
|
+
if (existing && existing.workspace_id !== workspaceId) throw new Error("Push subscription belongs to another workspace");
|
|
47
|
+
const id = existing?.id ?? randomUUID();
|
|
48
|
+
db.prepare(`INSERT INTO proactive_web_push_subscriptions(id, workspace_id, endpoint, subscription_json, language, created_at) VALUES (?, ?, ?, ?, ?, ?)
|
|
49
|
+
ON CONFLICT(endpoint) DO UPDATE SET subscription_json = excluded.subscription_json, language = excluded.language`).run(id, workspaceId, input.subscription.endpoint, JSON.stringify(input.subscription), input.language, (/* @__PURE__ */ new Date()).toISOString());
|
|
50
|
+
return { id };
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function unregisterBrowserPush(workspaceId, id) {
|
|
54
|
+
getSqliteDatabase().prepare("DELETE FROM proactive_web_push_subscriptions WHERE workspace_id = ? AND id = ?").run(workspaceId, id);
|
|
55
|
+
}
|
|
56
|
+
function enqueueBrowserPush(notification, selectedIds) {
|
|
57
|
+
const workspace = proactiveNotificationWorkspace(notification);
|
|
58
|
+
if (!workspace) return;
|
|
59
|
+
const targetId = notification.target.kind === "insight" ? notification.target.inboxItemId : "";
|
|
60
|
+
const subscriptions = getSqliteDatabase().prepare("SELECT id FROM proactive_web_push_subscriptions WHERE workspace_id = ?").all(workspace);
|
|
61
|
+
for (const subscription of subscriptions) {
|
|
62
|
+
if (selectedIds && !selectedIds.includes(subscription.id)) continue;
|
|
63
|
+
getSqliteDatabase().prepare(`INSERT OR IGNORE INTO proactive_web_push_deliveries(notification_id, subscription_id, inbox_item_id, notification_revision, next_attempt_at) VALUES (?, ?, ?, ?, ?)`).run(notification.id, subscription.id, targetId, typeof notification.payload.notificationRevision === "number" ? notification.payload.notificationRevision : 1, Date.now());
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async function drainBrowserPush(send = webPush.sendNotification) {
|
|
67
|
+
const db = getSqliteDatabase();
|
|
68
|
+
const keys = db.prepare("SELECT public_key, private_key FROM proactive_web_push_keys WHERE id = 1").get();
|
|
69
|
+
if (!keys) return;
|
|
70
|
+
const claim = runSqliteWriteTransaction((tx) => {
|
|
71
|
+
tx.prepare("UPDATE proactive_web_push_deliveries SET status = 'failed', last_error = 'Delivery lease exhausted' WHERE status = 'sending' AND lease_until <= ? AND attempt >= 5").run(Date.now());
|
|
72
|
+
const row = tx.prepare(`SELECT d.*, s.subscription_json, s.language FROM proactive_web_push_deliveries d
|
|
73
|
+
JOIN proactive_web_push_subscriptions s ON s.id = d.subscription_id
|
|
74
|
+
WHERE (d.status = 'pending' AND d.next_attempt_at <= ?) OR (d.status = 'sending' AND d.lease_until <= ?)
|
|
75
|
+
ORDER BY d.next_attempt_at LIMIT 1`).get(Date.now(), Date.now());
|
|
76
|
+
if (!row) return null;
|
|
77
|
+
tx.prepare("UPDATE proactive_web_push_deliveries SET status = 'sending', attempt = attempt + 1, lease_until = ? WHERE notification_id = ? AND subscription_id = ?").run(Date.now() + 6e4, row.notification_id, row.subscription_id);
|
|
78
|
+
return {
|
|
79
|
+
...row,
|
|
80
|
+
attempt: row.attempt + 1
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
if (!claim) return;
|
|
84
|
+
const finish = (status, nextAt, error = null) => db.prepare(`UPDATE proactive_web_push_deliveries SET status = ?, next_attempt_at = ?, last_error = ?, lease_until = NULL
|
|
85
|
+
WHERE notification_id = ? AND subscription_id = ? AND attempt = ? AND status = 'sending'`).run(status, nextAt, error, claim.notification_id, claim.subscription_id, claim.attempt);
|
|
86
|
+
try {
|
|
87
|
+
const row = db.prepare("SELECT * FROM notification_events WHERE event_id = ?").get(claim.notification_id);
|
|
88
|
+
const notification = ProductNotificationSchema.parse({
|
|
89
|
+
schemaVersion: 1,
|
|
90
|
+
id: row.event_id,
|
|
91
|
+
type: row.event_type,
|
|
92
|
+
target: JSON.parse(String(row.target_json)),
|
|
93
|
+
priority: row.priority,
|
|
94
|
+
title: {
|
|
95
|
+
en: row.title_en,
|
|
96
|
+
zh: row.title_zh
|
|
97
|
+
},
|
|
98
|
+
payload: JSON.parse(String(row.payload_json)),
|
|
99
|
+
createdAt: row.created_at
|
|
100
|
+
});
|
|
101
|
+
const policy = recheckNotificationDelivery(notification, "browser");
|
|
102
|
+
if (policy === "cancel") {
|
|
103
|
+
finish("failed", Date.now(), "Policy changed or card expired");
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (policy instanceof Date) {
|
|
107
|
+
finish("pending", policy.getTime());
|
|
108
|
+
db.prepare("UPDATE proactive_web_push_deliveries SET attempt = attempt - 1 WHERE notification_id = ? AND subscription_id = ? AND attempt = ?").run(claim.notification_id, claim.subscription_id, claim.attempt);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const subscription = JSON.parse(claim.subscription_json);
|
|
112
|
+
if (!allowedPushEndpoint(subscription.endpoint)) throw new Error("Unsupported push service");
|
|
113
|
+
await send(subscription, JSON.stringify({
|
|
114
|
+
id: claim.notification_id,
|
|
115
|
+
title: claim.language === "zh" ? "有一项工作需要查看" : "A work update is ready",
|
|
116
|
+
body: claim.language === "zh" ? "打开 xopc 查看详情。" : "Open xopc to review it.",
|
|
117
|
+
route: notificationTargetRoute(notification.target, "web")
|
|
118
|
+
}), {
|
|
119
|
+
vapidDetails: {
|
|
120
|
+
subject: "https://xopc.ai",
|
|
121
|
+
publicKey: keys.public_key,
|
|
122
|
+
privateKey: keys.private_key
|
|
123
|
+
},
|
|
124
|
+
TTL: 3600,
|
|
125
|
+
timeout: 1e4,
|
|
126
|
+
topic: createHash("sha256").update(claim.inbox_item_id || claim.notification_id).digest("base64url").slice(0, 32)
|
|
127
|
+
});
|
|
128
|
+
finish("sent", Date.now());
|
|
129
|
+
} catch (error) {
|
|
130
|
+
const status = error.statusCode;
|
|
131
|
+
if (status === 404 || status === 410) {
|
|
132
|
+
db.prepare("DELETE FROM proactive_web_push_subscriptions WHERE id = ?").run(claim.subscription_id);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
finish(claim.attempt >= 5 ? "failed" : "pending", Date.now() + Math.min(36e5, 3e4 * 2 ** claim.attempt), status ? `Push service HTTP ${status}` : "Push delivery failed");
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/** A user-triggered probe to this browser only; provider acceptance is not user delivery. */
|
|
139
|
+
async function testBrowserPush(workspaceId, subscriptionId, send = webPush.sendNotification) {
|
|
140
|
+
const id = randomUUID();
|
|
141
|
+
const claimed = runSqliteWriteTransaction((db) => {
|
|
142
|
+
const row = db.prepare("SELECT subscription_json FROM proactive_web_push_subscriptions WHERE id = ? AND workspace_id = ?").get(subscriptionId, workspaceId);
|
|
143
|
+
if (!row) throw new Error("Browser subscription not found");
|
|
144
|
+
if (db.prepare("SELECT 1 FROM proactive_push_probes WHERE workspace_id = ? AND created_at > ?").get(workspaceId, Date.now() - 6e4)) throw new Error("Wait one minute before another push test");
|
|
145
|
+
db.prepare("INSERT INTO proactive_push_probes(id, workspace_id, subscription_id, status, created_at) VALUES (?, ?, ?, ?, ?)").run(id, workspaceId, subscriptionId, "sending", Date.now());
|
|
146
|
+
return JSON.parse(row.subscription_json);
|
|
147
|
+
});
|
|
148
|
+
const keys = getSqliteDatabase().prepare("SELECT public_key, private_key FROM proactive_web_push_keys WHERE id = 1").get();
|
|
149
|
+
try {
|
|
150
|
+
if (!allowedPushEndpoint(claimed.endpoint)) throw new Error("Unsupported push service");
|
|
151
|
+
await send(claimed, JSON.stringify({
|
|
152
|
+
id,
|
|
153
|
+
title: "xopc 推送测试 / Push test",
|
|
154
|
+
body: "点击此通知确认打开 / Tap to confirm opening",
|
|
155
|
+
route: `/proactive?probe=${encodeURIComponent(id)}`
|
|
156
|
+
}), {
|
|
157
|
+
vapidDetails: {
|
|
158
|
+
subject: "https://xopc.ai",
|
|
159
|
+
publicKey: keys.public_key,
|
|
160
|
+
privateKey: keys.private_key
|
|
161
|
+
},
|
|
162
|
+
TTL: 300,
|
|
163
|
+
timeout: 1e4
|
|
164
|
+
});
|
|
165
|
+
getSqliteDatabase().prepare("UPDATE proactive_push_probes SET status = CASE WHEN opened_at IS NULL THEN 'accepted' ELSE 'opened' END WHERE id = ?").run(id);
|
|
166
|
+
return {
|
|
167
|
+
id,
|
|
168
|
+
status: "accepted"
|
|
169
|
+
};
|
|
170
|
+
} catch {
|
|
171
|
+
getSqliteDatabase().prepare("UPDATE proactive_push_probes SET status = 'failed', error = 'Push service did not accept the test' WHERE id = ?").run(id);
|
|
172
|
+
throw new Error("Push test failed; verify browser subscription and network connectivity");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function acknowledgeBrowserProbe(workspaceId, id) {
|
|
176
|
+
getSqliteDatabase().prepare("UPDATE proactive_push_probes SET status = 'opened', opened_at = ? WHERE id = ? AND workspace_id = ?").run(Date.now(), id, workspaceId);
|
|
177
|
+
}
|
|
178
|
+
function listBrowserProbes(workspaceId) {
|
|
179
|
+
return getSqliteDatabase().prepare("SELECT id, status, created_at AS createdAt, opened_at AS openedAt, error FROM proactive_push_probes WHERE workspace_id = ? ORDER BY created_at DESC LIMIT 10").all(workspaceId);
|
|
180
|
+
}
|
|
181
|
+
//#endregion
|
|
182
|
+
export { acknowledgeBrowserProbe, allowedPushEndpoint, drainBrowserPush, enqueueBrowserPush, listBrowserProbes, prepareBrowserPush, registerBrowserPush, testBrowserPush, unregisterBrowserPush };
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { getSqliteDatabase, runSqliteWriteTransaction } from "../../storage/sqlite/transaction.js";
|
|
2
2
|
import { TaskApplicationService } from "../../tasks/task-application-service.js";
|
|
3
|
+
import { effectiveProactivePolicy } from "../policy/service.js";
|
|
4
|
+
import { insightSourcesAuthorized } from "../execution/authorization.js";
|
|
5
|
+
import { insightCorrelation } from "../inbox/lifecycle.js";
|
|
3
6
|
//#region src/proactive/actions/service.ts
|
|
4
7
|
function projectIdFromAggregationKey(value) {
|
|
5
8
|
if (!value.startsWith("project:") || value.length === 8) throw new Error("Proactive action has no project scope");
|
|
@@ -8,9 +11,14 @@ function projectIdFromAggregationKey(value) {
|
|
|
8
11
|
function executeClaimedAction(row, now = /* @__PURE__ */ new Date()) {
|
|
9
12
|
const action = JSON.parse(row.proposed_action_json);
|
|
10
13
|
try {
|
|
14
|
+
if (getSqliteDatabase().prepare("SELECT 1 FROM proactive_inbox_items WHERE insight_id = ? AND withdrawn_at IS NOT NULL").get(row.insight_id)) throw new Error("Card was withdrawn");
|
|
15
|
+
if (!insightSourcesAuthorized(row.insight_id)) throw new Error("Source permission is no longer available");
|
|
16
|
+
if (!effectiveProactivePolicy(row.subscription_id).enabled) throw new Error("Proactive subscription paused");
|
|
17
|
+
const permission = getSqliteDatabase().prepare(`SELECT mode, allowed_actions_json FROM project_monitoring_policies WHERE project_id = ?`).get(projectIdFromAggregationKey(row.aggregation_key));
|
|
18
|
+
if (!getSqliteDatabase().prepare(`SELECT 1 FROM proactive_decisions d JOIN proactive_inbox_items i USING(inbox_item_id) WHERE i.insight_id = ? AND d.choice = 'approve'`).get(row.insight_id) && !(permission?.mode === "auto_low_risk" && JSON.parse(permission.allowed_actions_json).includes(action.id))) throw new Error("Action permission is no longer available");
|
|
11
19
|
if (action.id !== "create_project_task") throw new Error(`Unsupported proactive action: ${action.id}`);
|
|
12
20
|
const result = new TaskApplicationService().create({
|
|
13
|
-
idempotencyKey: `proactive:${row.insight_id}:${action.id}`,
|
|
21
|
+
idempotencyKey: `proactive:${insightCorrelation(row.insight_id)}:${action.id}`,
|
|
14
22
|
title: action.input.title,
|
|
15
23
|
projectId: projectIdFromAggregationKey(row.aggregation_key),
|
|
16
24
|
priority: "normal",
|
|
@@ -53,7 +61,7 @@ function executePendingProactiveActions(now = /* @__PURE__ */ new Date()) {
|
|
|
53
61
|
let executed = 0;
|
|
54
62
|
while (true) {
|
|
55
63
|
const row = runSqliteWriteTransaction((db) => {
|
|
56
|
-
const candidate = db.prepare(`SELECT x.insight_id, b.aggregation_key, x.proposed_action_json
|
|
64
|
+
const candidate = db.prepare(`SELECT x.insight_id, x.subscription_id, b.aggregation_key, x.proposed_action_json
|
|
57
65
|
FROM proactive_insights x
|
|
58
66
|
JOIN proactive_runs r ON r.run_id = x.run_id
|
|
59
67
|
JOIN proactive_signal_batches b ON b.batch_id = r.batch_id
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { WorkflowRunService } from '../../workflows/service/workflow-run-service.js';
|
|
2
|
+
export declare function prepareCardWorkflow(workspace: string, cardId: string, value: unknown, agentId: string, workflows: WorkflowRunService): Promise<{
|
|
3
|
+
runId: string | null;
|
|
4
|
+
agentId: string;
|
|
5
|
+
sessionKey: string | null;
|
|
6
|
+
status: string;
|
|
7
|
+
error: string | null;
|
|
8
|
+
}>;
|
|
9
|
+
export declare function workflowForCard(cardId: string, workspace: string, workflows: WorkflowRunService): Promise<{
|
|
10
|
+
runId: string | null;
|
|
11
|
+
agentId: string;
|
|
12
|
+
sessionKey: string | null;
|
|
13
|
+
status: string;
|
|
14
|
+
error: string | null;
|
|
15
|
+
}>;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { getSqliteDatabase, runSqliteWriteTransaction } from "../../storage/sqlite/transaction.js";
|
|
2
|
+
import { ProactiveConflict, effectiveProactivePolicy } from "../policy/service.js";
|
|
3
|
+
import { requireSubscription } from "../scenarios/control.js";
|
|
4
|
+
import { getCard } from "../inbox/cards.js";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
//#region src/proactive/actions/workflow.ts
|
|
8
|
+
const StartSchema = z.object({
|
|
9
|
+
expectedRevision: z.number().int().positive(),
|
|
10
|
+
retry: z.boolean().default(false)
|
|
11
|
+
}).strict();
|
|
12
|
+
async function prepareCardWorkflow(workspace, cardId, value, agentId, workflows) {
|
|
13
|
+
const input = StartSchema.parse(value);
|
|
14
|
+
const card = getCard(cardId, workspace);
|
|
15
|
+
const policy = effectiveProactivePolicy(card.subscriptionId);
|
|
16
|
+
const sub = requireSubscription(card.subscriptionId, workspace);
|
|
17
|
+
if (!policy.enabled || card.status === "withdrawn" || card.status === "expired") throw new Error("Card is no longer actionable");
|
|
18
|
+
if (card.revision !== input.expectedRevision) throw new ProactiveConflict("Card changed; refresh before acting");
|
|
19
|
+
const definitionId = policy.settings.preparationWorkflowId;
|
|
20
|
+
if (!definitionId) throw new Error("Choose a preparation workflow in the subscription settings first");
|
|
21
|
+
const claim = runSqliteWriteTransaction((db) => {
|
|
22
|
+
const previous = db.prepare("SELECT * FROM proactive_workflow_links WHERE inbox_item_id = ?").get(cardId);
|
|
23
|
+
if (previous?.lease_until && previous.lease_until > Date.now()) throw new ProactiveConflict("Workflow request is already running");
|
|
24
|
+
if (previous?.run_id && !input.retry) return {
|
|
25
|
+
existing: true,
|
|
26
|
+
...previous
|
|
27
|
+
};
|
|
28
|
+
if (previous && previous.definition_id !== definitionId) throw new Error("Workflow selection changed; open the existing run to recover it");
|
|
29
|
+
const attemptKey = previous?.status === "starting" || previous?.status === "failed" && !previous.run_id ? previous.attempt_key : `proactive-prepare:${cardId}:${randomUUID()}`;
|
|
30
|
+
db.prepare(`INSERT INTO proactive_workflow_links(inbox_item_id, definition_id, agent_id, attempt_key, status, lease_until)
|
|
31
|
+
VALUES (?, ?, ?, ?, 'starting', ?) ON CONFLICT(inbox_item_id) DO UPDATE SET status = 'starting', attempt_key = excluded.attempt_key, lease_until = excluded.lease_until, error = NULL`).run(cardId, definitionId, agentId, attemptKey, Date.now() + 12e4);
|
|
32
|
+
return {
|
|
33
|
+
existing: false,
|
|
34
|
+
run_id: previous?.run_id ?? null,
|
|
35
|
+
agent_id: previous?.agent_id ?? agentId,
|
|
36
|
+
attempt_key: attemptKey
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
if (claim.existing) return workflowForCard(cardId, workspace, workflows);
|
|
40
|
+
try {
|
|
41
|
+
if (input.retry && claim.run_id) {
|
|
42
|
+
const previous = await workflows.createRunStore(claim.agent_id).readRunView(claim.run_id);
|
|
43
|
+
if (previous && ![
|
|
44
|
+
"failed",
|
|
45
|
+
"cancelled",
|
|
46
|
+
"interrupted"
|
|
47
|
+
].includes(previous.run.status)) throw new Error("Only failed or interrupted workflows can be retried");
|
|
48
|
+
}
|
|
49
|
+
if (["withdrawn", "expired"].includes(getCard(cardId, workspace).status) || !effectiveProactivePolicy(card.subscriptionId).enabled) throw new Error("Card permissions changed");
|
|
50
|
+
const result = await workflows.startWorkflowRun({
|
|
51
|
+
agentId: claim.agent_id,
|
|
52
|
+
definitionId,
|
|
53
|
+
preparationOnly: true,
|
|
54
|
+
writebackPolicy: { targets: [] },
|
|
55
|
+
...sub.scopeKind === "project" ? { projectId: sub.scopeId } : {},
|
|
56
|
+
goal: `Prepare a reviewable checklist for this user-selected card. Do not send messages or publish results.\n${card.title}\n${card.summary}\n${card.recommendation}`,
|
|
57
|
+
input: {
|
|
58
|
+
cardId,
|
|
59
|
+
evidence: card.evidence,
|
|
60
|
+
purpose: "preparation_checklist"
|
|
61
|
+
},
|
|
62
|
+
source: { kind: "webui" },
|
|
63
|
+
concurrency: 1,
|
|
64
|
+
tokenBudget: 5e3,
|
|
65
|
+
idempotencyKey: claim.attempt_key,
|
|
66
|
+
...input.retry && claim.run_id ? { retryOfRunId: claim.run_id } : {}
|
|
67
|
+
});
|
|
68
|
+
if (result.ok === false) throw new Error(result.message);
|
|
69
|
+
getSqliteDatabase().prepare("UPDATE proactive_workflow_links SET status = 'started', run_id = ?, session_key = ?, lease_until = NULL WHERE inbox_item_id = ? AND attempt_key = ?").run(result.runId, result.sessionKey, cardId, claim.attempt_key);
|
|
70
|
+
return workflowForCard(cardId, workspace, workflows);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
getSqliteDatabase().prepare("UPDATE proactive_workflow_links SET status = 'failed', lease_until = NULL, error = ? WHERE inbox_item_id = ? AND attempt_key = ?").run(error instanceof Error ? error.message.slice(0, 500) : "Workflow failed", cardId, claim.attempt_key);
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async function workflowForCard(cardId, workspace, workflows) {
|
|
77
|
+
if (getCard(cardId, workspace).status === "withdrawn") return null;
|
|
78
|
+
const row = getSqliteDatabase().prepare("SELECT run_id AS runId, agent_id AS agentId, session_key AS sessionKey, status, error FROM proactive_workflow_links WHERE inbox_item_id = ?").get(cardId);
|
|
79
|
+
if (!row) return null;
|
|
80
|
+
if (!row.runId) return row;
|
|
81
|
+
const view = await workflows.createRunStore(row.agentId).readRunView(row.runId);
|
|
82
|
+
return {
|
|
83
|
+
...row,
|
|
84
|
+
status: view?.run.status ?? row.status
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
88
|
+
export { prepareCardWorkflow, workflowForCard };
|
|
@@ -56,6 +56,7 @@ var ReadonlyProactiveAgentExecutor = class {
|
|
|
56
56
|
this.config = config;
|
|
57
57
|
}
|
|
58
58
|
async execute(input) {
|
|
59
|
+
input.signal?.throwIfAborted();
|
|
59
60
|
const model = resolveModel(getDefaultModelSync(this.config()));
|
|
60
61
|
let toolCalls = 0;
|
|
61
62
|
const agent = new Agent({
|
|
@@ -91,8 +92,28 @@ var ReadonlyProactiveAgentExecutor = class {
|
|
|
91
92
|
const text = assistantText(agent.state.messages);
|
|
92
93
|
if (!text) throw new Error("Proactive agent returned empty output");
|
|
93
94
|
if (toolCalls === 0) throw new Error("Proactive agent did not inspect authorized evidence");
|
|
95
|
+
const usage = {
|
|
96
|
+
inputTokens: 0,
|
|
97
|
+
outputTokens: 0,
|
|
98
|
+
estimatedCostUsd: 0
|
|
99
|
+
};
|
|
100
|
+
for (const message of agent.state.messages) {
|
|
101
|
+
if (message.role !== "assistant") continue;
|
|
102
|
+
const reported = message.usage;
|
|
103
|
+
if (reported) {
|
|
104
|
+
usage.inputTokens += reported.input ?? 0;
|
|
105
|
+
usage.outputTokens += reported.output ?? 0;
|
|
106
|
+
usage.estimatedCostUsd += reported.cost?.total ?? 0;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const priced = Object.values(model.cost).some((value) => value > 0);
|
|
94
110
|
return {
|
|
95
111
|
text,
|
|
112
|
+
usage: {
|
|
113
|
+
inputTokens: usage.inputTokens,
|
|
114
|
+
outputTokens: usage.outputTokens,
|
|
115
|
+
...priced ? { estimatedCostUsd: usage.estimatedCostUsd } : {}
|
|
116
|
+
},
|
|
96
117
|
modelRef: `${model.provider}/${model.id}`
|
|
97
118
|
};
|
|
98
119
|
} finally {
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Recheck connector consent for stored cards and actions, not just model input. */
|
|
2
|
+
export declare function insightSourcesAuthorized(insightId: string): boolean;
|
|
3
|
+
export declare function authorizedConnectedSource(itemId: string, workspaceId: string, scenarioKey: string, agentId?: string | null): import("../../knowledge/types.js").KnowledgeSourceItem;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { getSqliteDatabase } from "../../storage/sqlite/transaction.js";
|
|
2
|
+
import { getKnowledgeSourceItem } from "../../storage/sqlite/knowledge-repository.js";
|
|
3
|
+
import { getConnectorSyncPolicyForConnection } from "../../storage/sqlite/connector-sync-policy-repository.js";
|
|
4
|
+
//#region src/proactive/execution/authorization.ts
|
|
5
|
+
/** Recheck connector consent for stored cards and actions, not just model input. */
|
|
6
|
+
function insightSourcesAuthorized(insightId) {
|
|
7
|
+
return getSqliteDatabase().prepare(`SELECT e.subject_id, e.workspace_id, x.scenario_key
|
|
8
|
+
FROM proactive_insights x JOIN proactive_runs r USING(run_id)
|
|
9
|
+
JOIN proactive_batch_events be ON be.batch_id = r.batch_id JOIN proactive_events e USING(event_id)
|
|
10
|
+
WHERE x.insight_id = ? AND e.type LIKE 'connected_source.%'`).all(insightId).every((row) => Boolean(authorizedConnectedSource(row.subject_id, row.workspace_id, row.scenario_key)));
|
|
11
|
+
}
|
|
12
|
+
function authorizedConnectedSource(itemId, workspaceId, scenarioKey, agentId) {
|
|
13
|
+
const item = getKnowledgeSourceItem(itemId);
|
|
14
|
+
if (!item || item.deletedAt || item.metadata.workspaceId !== workspaceId || item.sensitivity === "secret" || item.sensitivity === "regulated") return null;
|
|
15
|
+
if (agentId && item.metadata.agentId && item.metadata.agentId !== agentId) return null;
|
|
16
|
+
const connectionId = item.metadata.connectionId;
|
|
17
|
+
if (typeof connectionId !== "string") return null;
|
|
18
|
+
if (!getSqliteDatabase().prepare("SELECT 1 FROM connector_connections WHERE id = ? AND status = 'active'").get(connectionId)) return null;
|
|
19
|
+
const policy = getConnectorSyncPolicyForConnection(connectionId);
|
|
20
|
+
return policy?.scanEnabled && policy.proactiveEnabled && (!policy.allowedScenarioKeys.length || policy.allowedScenarioKeys.includes(scenarioKey)) ? item : null;
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { authorizedConnectedSource, insightSourcesAuthorized };
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { getSqliteDatabase } from "../../storage/sqlite/transaction.js";
|
|
2
|
-
import { getKnowledgeSourceItem } from "../../storage/sqlite/knowledge-repository.js";
|
|
3
|
-
import { getConnectorSyncPolicyForConnection } from "../../storage/sqlite/connector-sync-policy-repository.js";
|
|
4
2
|
import { getAssertionSlot, listUserAssertions } from "../../user-model/repository.js";
|
|
5
3
|
import "../../user-model/index.js";
|
|
6
4
|
import { getDiscussionCapture, getLatestDiscussionOrganization } from "../../discussions/repository.js";
|
|
5
|
+
import { authorizedConnectedSource } from "./authorization.js";
|
|
7
6
|
import { wrapExternalContent } from "../../gateway/security/external-content.js";
|
|
8
7
|
//#region src/proactive/execution/context.ts
|
|
9
8
|
const MAX_EVENT_COUNT = 50;
|
|
@@ -28,15 +27,7 @@ function emptyContext() {
|
|
|
28
27
|
};
|
|
29
28
|
}
|
|
30
29
|
function authorizedConnectedSourceItem(event, scenarioKey) {
|
|
31
|
-
|
|
32
|
-
const connectionId = typeof item?.metadata.connectionId === "string" ? item.metadata.connectionId : void 0;
|
|
33
|
-
if (!item || item.deletedAt || !connectionId) return null;
|
|
34
|
-
if (item.sensitivity === "secret" || item.sensitivity === "regulated") return null;
|
|
35
|
-
if (item.metadata.workspaceId !== event.workspace_id) return null;
|
|
36
|
-
if (event.agent_id && item.metadata.agentId && item.metadata.agentId !== event.agent_id) return null;
|
|
37
|
-
const policy = getConnectorSyncPolicyForConnection(connectionId);
|
|
38
|
-
const scenarioAllowed = !policy?.allowedScenarioKeys.length || policy.allowedScenarioKeys.includes(scenarioKey);
|
|
39
|
-
return policy?.scanEnabled && policy.proactiveEnabled && scenarioAllowed ? item : null;
|
|
30
|
+
return authorizedConnectedSource(event.subject_id, event.workspace_id, scenarioKey, event.agent_id);
|
|
40
31
|
}
|
|
41
32
|
var EventBatchContextProvider = class {
|
|
42
33
|
id = "event_batch";
|
|
@@ -5,3 +5,10 @@ export declare function isValuableInsight(candidate: InsightCandidate, policy?:
|
|
|
5
5
|
minConfidence: number;
|
|
6
6
|
minScore: number;
|
|
7
7
|
}): boolean;
|
|
8
|
+
export declare function parseAnalysisResult(raw: string, allowedEvidenceIds: Set<string>): {
|
|
9
|
+
result: 'no_insight';
|
|
10
|
+
reason: string;
|
|
11
|
+
} | {
|
|
12
|
+
result: 'insight';
|
|
13
|
+
candidate: InsightCandidate;
|
|
14
|
+
};
|