@xopcai/xopc 0.0.268 → 0.0.270
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/{activity-store-Bhyy6ENz.js → activity-store-D_0FmdRj.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-B9WObxjr.js → agent-avatar-dicebear-IZvEXak_.js} +1 -1
- package/dist/gateway/static/root/assets/agent-defaults-settings-panel-CE3ygr3l.js +1 -0
- package/dist/gateway/static/root/assets/{agents-BJuTIRPD.js → agents-DzG2qw-c.js} +1 -1
- package/dist/gateway/static/root/assets/{api-B2y7ZjZk.js → api-O3TTQ4qN.js} +1 -1
- package/dist/gateway/static/root/assets/{app-management-settings-panel-Bbn9mDfL.js → app-management-settings-panel-Cqqsbzri.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-Deo5EsIc.js → appearance-settings-DaJyf32s.js} +1 -1
- package/dist/gateway/static/root/assets/{apps-page-Cz4d7ErX.js → apps-page-B4lX6yoT.js} +1 -1
- package/dist/gateway/static/root/assets/{archive-plugin-CxJTU8MU.js → archive-plugin-kDPnDTJI.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-C71Cukgp.js → attachment-load-CJ3VceKS.js} +1 -1
- package/dist/gateway/static/root/assets/{automations-page-_J7UZ55Q.js → automations-page-JwMtU8XB.js} +1 -1
- package/dist/gateway/static/root/assets/automations-workspace-BiNBewg0.js +6 -0
- package/dist/gateway/static/root/assets/{binary-plugins-CX8fC8Zt.js → binary-plugins-DzyZXLBX.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-Ci9fjoQL.js → block-editor-kTt3gYIJ.js} +3 -3
- package/dist/gateway/static/root/assets/{browser-automation-inputs-s5I78Iko.js → browser-automation-inputs-BKohPYR4.js} +1 -1
- package/dist/gateway/static/root/assets/{browser-automations-page-rDSx0ozx.js → browser-automations-page-ktQfFPk-.js} +1 -1
- package/dist/gateway/static/root/assets/{browser-settings-page-B9RA6x4x.js → browser-settings-page-Dgdt5uVL.js} +3 -3
- package/dist/gateway/static/root/assets/capabilities-settings-panel-BpDW-R9f.js +7 -0
- package/dist/gateway/static/root/assets/{channels-settings-CEhVLJNa.js → channels-settings-DU4EQnR0.js} +1 -1
- package/dist/gateway/static/root/assets/{channels-status-swr-_OsWr_vS.js → channels-status-swr-DrqzYmDr.js} +1 -1
- package/dist/gateway/static/root/assets/{chat-terminal-dock-DMSl8KxS.js → chat-terminal-dock-CIFGGLGa.js} +2 -2
- package/dist/gateway/static/root/assets/connectors-page-CfJqy284.js +2 -0
- package/dist/gateway/static/root/assets/{cron-expression-BByq6Uh8.js → cron-expression-BNwbnrEb.js} +1 -1
- package/dist/gateway/static/root/assets/{date-picker-m6JZCUEW.js → date-picker-DKyirU4K.js} +1 -1
- package/dist/gateway/static/root/assets/{dependency-picker-CwCAjbaJ.js → dependency-picker-D3vNIjRI.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-DSmeEREd.js → desktop-pet-fQwRb_3p.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-MfKceMoe.js → desktop-pet-settings-DfOJ-GvT.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-CwvWy26h.js → extension-debug-page-Cn53jB5-.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-DLrbVBHM.js → extension-page-IDpLCxpC.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-BI6C4PM-.js → extension-settings-page-B1dds2bP.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-swr-OObCK8jM.js → gateway-config-swr-B7dcmP7Q.js} +1 -1
- package/dist/gateway/static/root/assets/gateway-settings-pp9jN0wl.js +1 -0
- package/dist/gateway/static/root/assets/{gateway-startup-retry-D_TxKN_A.js → gateway-startup-retry-JGhGMhVZ.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-settings-CUl832Tb.js → heartbeat-settings-B8_zTyev.js} +1 -1
- package/dist/gateway/static/root/assets/home-page-g1KdAdLb.js +1 -0
- package/dist/gateway/static/root/assets/{image-generation-api-fPU9yGsw.js → image-generation-api-BJLX7Qrh.js} +1 -1
- package/dist/gateway/static/root/assets/index-1zRt56Zt.css +2 -0
- package/dist/gateway/static/root/assets/{index-wKE8Q2FX.js → index-OMnem2nr.js} +47 -47
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-r4DCS19O.js → keyboard-shortcuts-settings-CBvMTFFx.js} +1 -1
- package/dist/gateway/static/root/assets/{local-app-workbench-page-qwLuir8A.js → local-app-workbench-page-bj75v2ZE.js} +2 -2
- package/dist/gateway/static/root/assets/{local-apps-page-DkfCRLUh.js → local-apps-page-APCMNDvU.js} +1 -1
- package/dist/gateway/static/root/assets/{locale-store-DFQX9kTl.js → locale-store-BThkG0W5.js} +1 -1
- package/dist/gateway/static/root/assets/logs-page-BX7n39zV.js +2 -0
- package/dist/gateway/static/root/assets/{management-settings-Cfx4ATTa.js → management-settings-CM62j8ZX.js} +1 -1
- package/dist/gateway/static/root/assets/{markdown-view-D-5jHRWg.js → markdown-view-BwjpJVPs.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-DOLjWd3y.js → media-plugins-C10byeaa.js} +1 -1
- package/dist/gateway/static/root/assets/{messages-Dj8QuS6e.js → messages-D0V_bgRP.js} +2 -2
- package/dist/gateway/static/root/assets/{mobile-pairing-wizard-BGW9joAb.js → mobile-pairing-wizard-BCWfwVx3.js} +1 -1
- package/dist/gateway/static/root/assets/note-detail-panel-koOqb0gX.js +5 -0
- package/dist/gateway/static/root/assets/notes-page-BH54rY3B.js +8 -0
- package/dist/gateway/static/root/assets/{page-tabs-_SGXMT2O.js → page-tabs-qQ36zncF.js} +1 -1
- package/dist/gateway/static/root/assets/{preview-open-alternatives-lVetfCWk.js → preview-open-alternatives-BiAJvvCo.js} +1 -1
- package/dist/gateway/static/root/assets/proactive-card-CcNBqHoH.js +1 -0
- package/dist/gateway/static/root/assets/proactive-page-C3K61jve.js +1 -0
- package/dist/gateway/static/root/assets/{product-open-page-CIGLPFm7.js → product-open-page-CunnDP4w.js} +1 -1
- package/dist/gateway/static/root/assets/project-detail-page-SL2g8MZY.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-BtxazT5J.js +1 -0
- package/dist/gateway/static/root/assets/{registry-api-CsZLatKo.js → registry-api-0JSegbNl.js} +1 -1
- package/dist/gateway/static/root/assets/remote-access-hub-BSb7b67A.js +1 -0
- package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-BDqVrUEj.js → runtime-tools-settings-panel-bu_mwBTr.js} +1 -1
- package/dist/gateway/static/root/assets/{schema-form-8RQgFJJO.js → schema-form-BE0sgZyz.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-ljcS2KVz.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-CR9Y9LQm.js +1 -0
- package/dist/gateway/static/root/assets/{settings-form-section-j4UjGM-f.js → settings-form-section-CBo517lV.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-lMi50B_s.js → settings-page-DBPimx93.js} +1 -1
- package/dist/gateway/static/root/assets/{setup-status-panel-Dx4Fy3rU.js → setup-status-panel-J3AnooPW.js} +1 -1
- package/dist/gateway/static/root/assets/{share-preview-page-aSyKRH5y.js → share-preview-page-DdV75VTe.js} +1 -1
- package/dist/gateway/static/root/assets/shares-settings-AAm4Iwcp.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-pRCjP5sm.js +1 -0
- package/dist/gateway/static/root/assets/{skills-page-C2_CAvxW.js → skills-page-Cn_-nvRo.js} +1 -1
- package/dist/gateway/static/root/assets/{skills-page.utils-B00e4Q30.js → skills-page.utils-CfVAc_lu.js} +1 -1
- package/dist/gateway/static/root/assets/{system-settings-panel-CulAkrsV.js → system-settings-panel-B-pzalp-.js} +1 -1
- package/dist/gateway/static/root/assets/task-detail-page-CmrLy3PO.js +1 -0
- package/dist/gateway/static/root/assets/{theme-store-zRT6vaZc.js → theme-store-1wryLgBQ.js} +1 -1
- package/dist/gateway/static/root/assets/{tunnel-api-C01xgCWR.js → tunnel-api-Cgpnm2fL.js} +1 -1
- package/dist/gateway/static/root/assets/{use-autosave-DfSJ3c2p.js → use-autosave-N_lm4FdD.js} +1 -1
- package/dist/gateway/static/root/assets/user-model-page-u3QLDX6L.js +2 -0
- package/dist/gateway/static/root/assets/{voice-api-key-field-9v-miN8o.js → voice-api-key-field-DMsyVfri.js} +1 -1
- package/dist/gateway/static/root/assets/{voice-config-api-DpJEu2RQ.js → voice-config-api-QWVmiAp3.js} +1 -1
- package/dist/gateway/static/root/assets/{work-discovery-overlay-CREYgh-1.js → work-discovery-overlay-BC0vAxp1.js} +1 -1
- package/dist/gateway/static/root/assets/workflow-run-setup-panel-Cz9jQIlP.js +3 -0
- package/dist/gateway/static/root/assets/workflows-page-DA6OOK6X.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 +120 -0
- package/dist/packages/realtime-protocol/src/voice.js +2 -1
- package/dist/src/agent/tool-manuals/xopc-use.d.ts +1 -1
- package/dist/src/agent/tool-manuals/xopc-use.js +19 -1
- package/dist/src/agent/tools/factory.js +1 -0
- package/dist/src/agent/tools/xopc-use-tool.d.ts +3 -2
- package/dist/src/agent/tools/xopc-use-tool.js +48 -4
- package/dist/src/agent/workflow/subagent-runner.d.ts +1 -0
- package/dist/src/agent/workflow/subagent-runner.js +2 -2
- package/dist/src/browser/automations/schema.d.ts +1 -1
- package/dist/src/browser/automations/service.d.ts +1 -1
- package/dist/src/cli/bin.js +2 -2
- package/dist/src/cli/commands/agent.js +2 -2
- package/dist/src/cli/commands/auth.js +1 -1
- package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
- package/dist/src/cli/commands/extension-dev.js +1 -1
- package/dist/src/cli/commands/extension-marketplace.js +1 -1
- package/dist/src/cli/commands/extension.js +1 -1
- package/dist/src/cli/commands/gateway/lifecycle.js +1 -1
- package/dist/src/cli/commands/gateway-ssh-tunnel.js +1 -1
- package/dist/src/cli/commands/models.js +2 -2
- package/dist/src/cli/commands/onboard.js +1 -1
- package/dist/src/cli/commands/platform.js +1 -1
- package/dist/src/cli/commands/profile.js +1 -1
- package/dist/src/cli/commands/tunnel.js +1 -1
- package/dist/src/cli/commands/update.js +1 -1
- package/dist/src/cli/extension-cli-register.js +1 -1
- package/dist/src/config/index.js +1 -1
- package/dist/src/connectors/connected-source-normalizers.js +3 -0
- 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/proactive-control.d.ts +3 -0
- package/dist/src/gateway/hono/routes/proactive-control.js +168 -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/run-loop.js +1 -1
- package/dist/src/gateway/security/gateway-scopes.js +5 -0
- package/dist/src/gateway/server.js +1 -1
- package/dist/src/gateway/service.d.ts +0 -1
- package/dist/src/gateway/service.js +21 -23
- 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 +12 -2
- 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 +5 -0
- package/dist/src/proactive/execution/authorization.js +61 -0
- package/dist/src/proactive/execution/context.d.ts +4 -0
- package/dist/src/proactive/execution/context.js +51 -14
- package/dist/src/proactive/execution/insight.d.ts +7 -0
- package/dist/src/proactive/execution/insight.js +36 -3
- package/dist/src/proactive/execution/repository.d.ts +2 -0
- package/dist/src/proactive/execution/repository.js +53 -9
- package/dist/src/proactive/execution/types.d.ts +7 -0
- package/dist/src/proactive/execution/worker.js +43 -7
- package/dist/src/proactive/experience.d.ts +69 -0
- package/dist/src/proactive/experience.js +90 -0
- package/dist/src/proactive/follow-ups.d.ts +123 -0
- package/dist/src/proactive/follow-ups.js +169 -0
- package/dist/src/proactive/inbox/cards.d.ts +21 -0
- package/dist/src/proactive/inbox/cards.js +266 -0
- package/dist/src/proactive/inbox/delivery.d.ts +6 -0
- package/dist/src/proactive/inbox/delivery.js +54 -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 +61 -0
- package/dist/src/proactive/inbox/notification-policy.d.ts +1 -0
- package/dist/src/proactive/inbox/notification-policy.js +22 -0
- package/dist/src/proactive/inbox/repository.d.ts +1 -0
- package/dist/src/proactive/inbox/repository.js +75 -12
- package/dist/src/proactive/inbox/types.d.ts +11 -1
- package/dist/src/proactive/inbox/worker.js +9 -3
- package/dist/src/proactive/index.js +2 -2
- package/dist/src/proactive/mail-thread.d.ts +13 -0
- package/dist/src/proactive/mail-thread.js +54 -0
- 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 +51 -0
- package/dist/src/proactive/policy/service.js +120 -0
- package/dist/src/proactive/routing/matcher.js +3 -2
- package/dist/src/proactive/scenarios/control.d.ts +87 -0
- package/dist/src/proactive/scenarios/control.js +108 -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 +4 -2
- package/dist/src/proactive/scenarios/repository.js +5 -1
- package/dist/src/proactive/service.js +1 -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.d.ts +1 -0
- package/dist/src/proactive/temporal/worker.js +24 -6
- 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/162_proactive_product.sql +7 -0
- package/dist/src/storage/sqlite/migrations/163_proactive_follow_ups.sql +29 -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/tasks/home-query-service.js +1 -1
- package/dist/src/tasks/index.js +4 -4
- package/dist/src/voice/dashscope/streaming-stt-session.js +1 -0
- package/dist/src/voice/dashscope/streaming-tts-stream.d.ts +1 -0
- package/dist/src/voice/dashscope/streaming-tts-stream.js +8 -0
- package/dist/src/voice/realtime/agentBroker.js +1 -1
- package/dist/src/voice/realtime/agentEngine.js +1 -1
- package/dist/src/voice/realtime/pcm-output-gain.d.ts +2 -0
- package/dist/src/voice/realtime/pcm-output-gain.js +16 -0
- package/dist/src/voice/realtime/runtime.d.ts +2 -1
- package/dist/src/voice/realtime/runtime.js +25 -7
- 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 +7 -5
- package/dist/gateway/static/root/assets/agent-defaults-settings-panel-J1YFNG26.js +0 -1
- package/dist/gateway/static/root/assets/automations-workspace-Bn8nwUOL.js +0 -6
- package/dist/gateway/static/root/assets/capabilities-settings-panel-C1GUih8m.js +0 -7
- package/dist/gateway/static/root/assets/connectors-page-IQAQDSmO.js +0 -2
- package/dist/gateway/static/root/assets/gateway-settings-B3H7Vfbh.js +0 -1
- package/dist/gateway/static/root/assets/home-page-CT4JL2jm.js +0 -1
- package/dist/gateway/static/root/assets/index-CRJrO4GI.css +0 -2
- package/dist/gateway/static/root/assets/logs-page-DrNaaPWc.js +0 -2
- package/dist/gateway/static/root/assets/note-detail-panel-Dbj5OvXE.js +0 -5
- package/dist/gateway/static/root/assets/notes-page-5hRB3WXc.js +0 -8
- package/dist/gateway/static/root/assets/project-detail-page-CblWLA8k.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-CGk-kzqs.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-Du8PweyU.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-BFm94dJC.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-C0H-cToA.js +0 -1
- package/dist/gateway/static/root/assets/shares-settings-CqF9s0pl.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-BBL_ZUSC.js +0 -1
- package/dist/gateway/static/root/assets/task-detail-page-D1FmRFIn.js +0 -1
- package/dist/gateway/static/root/assets/user-model-page-BGC_5baJ.js +0 -2
- package/dist/gateway/static/root/assets/workflow-run-setup-panel-BfCWdw8F.js +0 -3
- package/dist/gateway/static/root/assets/workflows-page-CvmKQkD0.js +0 -6
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { getSqliteDatabase, runSqliteWriteTransaction } from "../../storage/sqlite/transaction.js";
|
|
2
|
+
import { insightSourcesAuthorized } from "../execution/authorization.js";
|
|
3
|
+
import { effectiveProactivePolicy, localProactiveDay, nextDigestTime, proactivePreferences, quietHoursEnd } from "../policy/service.js";
|
|
4
|
+
import { getInboxItem } from "./repository.js";
|
|
5
|
+
import { getCard } from "./cards.js";
|
|
6
|
+
import { viewingProactiveCards } from "../policy/presence.js";
|
|
7
|
+
import { randomUUID } from "node:crypto";
|
|
8
|
+
//#region src/proactive/inbox/digest.ts
|
|
9
|
+
function queueDigest(item, mode, dueAt) {
|
|
10
|
+
const policy = effectiveProactivePolicy(item.subscriptionId);
|
|
11
|
+
getSqliteDatabase().prepare(`INSERT INTO proactive_digest_queue(inbox_item_id, workspace_id, notification_revision, mode, due_at)
|
|
12
|
+
VALUES (?, ?, ?, ?, ?) ON CONFLICT(inbox_item_id) DO UPDATE SET notification_revision = excluded.notification_revision,
|
|
13
|
+
mode = excluded.mode, due_at = excluded.due_at, consumed_at = NULL
|
|
14
|
+
WHERE proactive_digest_queue.notification_revision <> excluded.notification_revision`).run(item.id, policy.workspaceId, item.notificationRevision, mode, dueAt.toISOString());
|
|
15
|
+
}
|
|
16
|
+
function digestCards(id, workspaceId) {
|
|
17
|
+
const digest = getSqliteDatabase().prepare("SELECT * FROM proactive_digests WHERE digest_id = ? AND workspace_id = ?").get(id, workspaceId);
|
|
18
|
+
if (!digest) throw new Error("Digest not found");
|
|
19
|
+
return getSqliteDatabase().prepare("SELECT inbox_item_id, notification_revision FROM proactive_digest_members WHERE digest_id = ?").all(id).flatMap((member) => {
|
|
20
|
+
const item = getInboxItem(member.inbox_item_id);
|
|
21
|
+
if (!item || !eligibleDigestItem(item, digest.occurrence_key.startsWith("daily:") ? "daily" : "quiet") || item.notificationRevision !== member.notification_revision) return [];
|
|
22
|
+
const card = getCard(item.id, workspaceId);
|
|
23
|
+
return card.status === "withdrawn" ? [] : [card];
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function eligibleDigestItem(item, mode) {
|
|
27
|
+
const policy = effectiveProactivePolicy(item.subscriptionId);
|
|
28
|
+
const daily = policy.preferences.digestEnabled || policy.settings.delivery === "digest";
|
|
29
|
+
if (mode === "daily" ? !daily : policy.level === "quiet" && !daily) return false;
|
|
30
|
+
return Boolean(policy.enabled && policy.settings.delivery !== "inbox" && !item.withdrawnAt && !["resolved", "snoozed"].includes(item.status) && (!item.expiresAt || Date.parse(item.expiresAt) > Date.now()) && insightSourcesAuthorized(item.insightId));
|
|
31
|
+
}
|
|
32
|
+
function reserveWorkspaceAttention(workspaceId, key, now = /* @__PURE__ */ new Date()) {
|
|
33
|
+
const db = getSqliteDatabase();
|
|
34
|
+
const preferences = proactivePreferences(workspaceId);
|
|
35
|
+
if (preferences.level === "off" || preferences.pausedUntil && Date.parse(preferences.pausedUntil) > now.getTime()) return false;
|
|
36
|
+
if (db.prepare("SELECT 1 FROM proactive_notification_budget WHERE dedupe_key = ?").get(key)) return true;
|
|
37
|
+
const day = localProactiveDay(now, preferences.timezone);
|
|
38
|
+
const { count } = db.prepare("SELECT COUNT(*) AS count FROM proactive_notification_budget WHERE workspace_id = ? AND local_day = ?").get(workspaceId, day);
|
|
39
|
+
if (count >= preferences.dailyNotificationLimit) return false;
|
|
40
|
+
db.prepare("INSERT INTO proactive_notification_budget VALUES (?, ?, ?, ?)").run(key, workspaceId, day, now.toISOString());
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
function flushDueDigests(persist, now = /* @__PURE__ */ new Date()) {
|
|
44
|
+
return runSqliteWriteTransaction((db) => {
|
|
45
|
+
const groups = db.prepare(`SELECT workspace_id, mode FROM proactive_digest_queue WHERE consumed_at IS NULL AND due_at <= ? GROUP BY workspace_id, mode LIMIT 20`).all(now.toISOString());
|
|
46
|
+
const notifications = [];
|
|
47
|
+
for (const group of groups) {
|
|
48
|
+
const preferences = proactivePreferences(group.workspace_id);
|
|
49
|
+
const until = quietHoursEnd(preferences, now);
|
|
50
|
+
if (until) {
|
|
51
|
+
db.prepare("UPDATE proactive_digest_queue SET due_at = ? WHERE workspace_id = ? AND mode = ? AND consumed_at IS NULL").run(until.toISOString(), group.workspace_id, group.mode);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const queued = db.prepare("SELECT inbox_item_id FROM proactive_digest_queue WHERE workspace_id = ? AND mode = ? AND consumed_at IS NULL AND due_at <= ? ORDER BY due_at LIMIT 500").all(group.workspace_id, group.mode, now.toISOString());
|
|
55
|
+
const seen = /* @__PURE__ */ new Set();
|
|
56
|
+
const items = queued.flatMap((row) => {
|
|
57
|
+
const item = getInboxItem(row.inbox_item_id);
|
|
58
|
+
if (!item || !eligibleDigestItem(item, group.mode)) return [];
|
|
59
|
+
const key = item.correlationKey ?? item.id;
|
|
60
|
+
if (seen.has(key)) return [];
|
|
61
|
+
seen.add(key);
|
|
62
|
+
return [item];
|
|
63
|
+
});
|
|
64
|
+
const occurrence = `${group.mode}:${localProactiveDay(now, preferences.timezone)}`;
|
|
65
|
+
if (db.prepare("SELECT 1 FROM proactive_digests WHERE workspace_id = ? AND occurrence_key = ?").get(group.workspace_id, occurrence) && items.length) {
|
|
66
|
+
const tomorrow = nextDigestTime(preferences, new Date(now.getTime() + 6e4));
|
|
67
|
+
db.prepare("UPDATE proactive_digest_queue SET due_at = ? WHERE workspace_id = ? AND mode = ? AND consumed_at IS NULL").run(tomorrow.toISOString(), group.workspace_id, group.mode);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (!items.length || preferences.suppressWhileViewing && viewingProactiveCards(group.workspace_id, now.getTime())) {
|
|
71
|
+
for (const row of queued) db.prepare("UPDATE proactive_digest_queue SET consumed_at = ? WHERE inbox_item_id = ?").run(now.toISOString(), row.inbox_item_id);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const key = `proactive.digest:${group.workspace_id}:${occurrence}`;
|
|
75
|
+
if (!reserveWorkspaceAttention(group.workspace_id, key, now)) {
|
|
76
|
+
db.prepare("UPDATE proactive_digest_queue SET due_at = ? WHERE workspace_id = ? AND mode = ? AND consumed_at IS NULL").run(nextDigestTime(preferences, new Date(now.getTime() + 6e4)).toISOString(), group.workspace_id, group.mode);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const id = randomUUID();
|
|
80
|
+
db.prepare("INSERT INTO proactive_digests(digest_id, workspace_id, occurrence_key, created_at) VALUES (?, ?, ?, ?)").run(id, group.workspace_id, occurrence, now.toISOString());
|
|
81
|
+
for (const item of items) db.prepare("INSERT INTO proactive_digest_members VALUES (?, ?, ?)").run(id, item.id, item.notificationRevision);
|
|
82
|
+
const notification = persist({
|
|
83
|
+
dedupeKey: key,
|
|
84
|
+
notification: {
|
|
85
|
+
type: "proactive.insight",
|
|
86
|
+
target: {
|
|
87
|
+
kind: "proactive_digest",
|
|
88
|
+
digestId: id
|
|
89
|
+
},
|
|
90
|
+
priority: "normal",
|
|
91
|
+
title: {
|
|
92
|
+
en: "Your work digest is ready",
|
|
93
|
+
zh: "你的工作摘要已就绪"
|
|
94
|
+
},
|
|
95
|
+
body: {
|
|
96
|
+
en: `${items.length} updates to review in xopc.`,
|
|
97
|
+
zh: `有 ${items.length} 项变化,打开 xopc 查看。`
|
|
98
|
+
},
|
|
99
|
+
payload: { digestId: id }
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
if (notification) {
|
|
103
|
+
db.prepare("UPDATE proactive_digests SET notification_id = ? WHERE digest_id = ?").run(notification.id, id);
|
|
104
|
+
notifications.push(notification);
|
|
105
|
+
}
|
|
106
|
+
for (const row of queued) db.prepare("UPDATE proactive_digest_queue SET consumed_at = ? WHERE inbox_item_id = ?").run(now.toISOString(), row.inbox_item_id);
|
|
107
|
+
}
|
|
108
|
+
return notifications;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
//#endregion
|
|
112
|
+
export { digestCards, flushDueDigests, nextDigestTime, queueDigest, reserveWorkspaceAttention };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function withdrawCard(id: string, now?: Date): void;
|
|
2
|
+
/** Conservative correlation: the same scope, evidence subjects and requested outcome. */
|
|
3
|
+
export declare function insightCorrelation(insightId: string): string;
|
|
4
|
+
export declare function reconcileCards(now?: Date): number;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { getSqliteDatabase, runSqliteWriteTransaction } from "../../storage/sqlite/transaction.js";
|
|
2
|
+
import { insightSourcesAuthorized, insightSourcesChanged } from "../execution/authorization.js";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
//#region src/proactive/inbox/lifecycle.ts
|
|
5
|
+
function withdrawCard(id, now = /* @__PURE__ */ new Date()) {
|
|
6
|
+
runSqliteWriteTransaction((db) => {
|
|
7
|
+
db.prepare("UPDATE proactive_inbox_items SET withdrawn_at = ?, updated_at = ? WHERE inbox_item_id = ? AND withdrawn_at IS NULL").run(now.toISOString(), now.toISOString(), id);
|
|
8
|
+
db.prepare("UPDATE proactive_delivery_outbox SET status = 'delivered', error_message = 'source_withdrawn' WHERE inbox_item_id = ? AND status <> 'delivered'").run(id);
|
|
9
|
+
db.prepare("UPDATE proactive_insights SET action_status = 'rejected', action_error = 'source_withdrawn' WHERE insight_id = (SELECT insight_id FROM proactive_inbox_items WHERE inbox_item_id = ?) AND action_status IN ('pending', 'approval_required', 'failed')").run(id);
|
|
10
|
+
db.prepare(`UPDATE notification_events SET title_en = 'Update withdrawn', title_zh = '消息已撤回', body_en = NULL, body_zh = NULL
|
|
11
|
+
WHERE event_type = 'proactive.insight' AND json_extract(target_json, '$.inboxItemId') = ?`).run(id);
|
|
12
|
+
db.prepare(`UPDATE proactive_web_push_deliveries SET status = 'failed', lease_until = NULL, last_error = 'source_withdrawn'
|
|
13
|
+
WHERE inbox_item_id = ? AND status IN ('pending', 'sending')`).run(id);
|
|
14
|
+
db.prepare(`UPDATE notification_deliveries SET status = 'dead', updated_at = ? WHERE status IN ('pending', 'accepted') AND event_id IN
|
|
15
|
+
(SELECT event_id FROM notification_events WHERE event_type = 'proactive.insight' AND json_extract(target_json, '$.inboxItemId') = ?)`).run(now.getTime(), id);
|
|
16
|
+
db.prepare(`UPDATE proactive_channel_deliveries SET status = 'cancelled', lease_until = NULL, last_error = 'source_withdrawn' WHERE status IN ('pending', 'sending') AND notification_id IN
|
|
17
|
+
(SELECT event_id FROM notification_events WHERE event_type = 'proactive.insight' AND json_extract(target_json, '$.inboxItemId') = ?)`).run(id);
|
|
18
|
+
db.prepare("DELETE FROM proactive_digest_queue WHERE inbox_item_id = ?").run(id);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/** Conservative correlation: the same scope, evidence subjects and requested outcome. */
|
|
22
|
+
function insightCorrelation(insightId) {
|
|
23
|
+
const row = getSqliteDatabase().prepare(`SELECT s.workspace_id, b.aggregation_key, x.evidence_ids_json, x.proposed_action_json, x.recommendation
|
|
24
|
+
FROM proactive_insights x JOIN proactive_runs r USING(run_id) JOIN proactive_signal_batches b USING(batch_id)
|
|
25
|
+
JOIN proactive_scenario_subscriptions s ON s.subscription_id = x.subscription_id WHERE x.insight_id = ?`).get(insightId);
|
|
26
|
+
if (!row) return insightId;
|
|
27
|
+
const normalize = (value) => value.normalize("NFKC").trim().toLowerCase().replace(/\s+/g, " ");
|
|
28
|
+
const evidence = JSON.parse(row.evidence_ids_json).filter((id) => /^(task|project|source-item|automation-run):/.test(id)).sort();
|
|
29
|
+
if (!evidence.length) return insightId;
|
|
30
|
+
const action = row.proposed_action_json ? JSON.parse(row.proposed_action_json) : null;
|
|
31
|
+
return createHash("sha256").update(JSON.stringify([
|
|
32
|
+
row.workspace_id,
|
|
33
|
+
row.aggregation_key,
|
|
34
|
+
evidence,
|
|
35
|
+
normalize(action ? JSON.stringify([action.id, action.input]) : row.recommendation)
|
|
36
|
+
])).digest("hex");
|
|
37
|
+
}
|
|
38
|
+
function reconcileCards(now = /* @__PURE__ */ new Date()) {
|
|
39
|
+
return runSqliteWriteTransaction((db) => {
|
|
40
|
+
const rows = db.prepare(`SELECT i.inbox_item_id, i.insight_id, i.correlation_key FROM proactive_inbox_items i
|
|
41
|
+
LEFT JOIN proactive_card_review_state r USING(inbox_item_id) WHERE i.withdrawn_at IS NULL
|
|
42
|
+
ORDER BY COALESCE(r.checked_at, ''), i.created_at LIMIT 200`).all();
|
|
43
|
+
let withdrawn = 0;
|
|
44
|
+
for (const row of rows) {
|
|
45
|
+
if (!insightSourcesAuthorized(row.insight_id)) {
|
|
46
|
+
withdrawCard(row.inbox_item_id, now);
|
|
47
|
+
withdrawn++;
|
|
48
|
+
} else {
|
|
49
|
+
if (insightSourcesChanged(row.insight_id)) db.prepare(`UPDATE proactive_inbox_items SET expires_at = ?, updated_at = ?
|
|
50
|
+
WHERE inbox_item_id = ? AND (expires_at IS NULL OR expires_at > ?)
|
|
51
|
+
AND insight_id IN (SELECT insight_id FROM proactive_insights WHERE action_status IS NULL OR action_status <> 'completed')`).run(now.toISOString(), now.toISOString(), row.inbox_item_id, now.toISOString());
|
|
52
|
+
const correlation = insightCorrelation(row.insight_id);
|
|
53
|
+
if (row.correlation_key !== correlation) db.prepare("UPDATE proactive_inbox_items SET correlation_key = ? WHERE inbox_item_id = ?").run(correlation, row.inbox_item_id);
|
|
54
|
+
}
|
|
55
|
+
db.prepare(`INSERT INTO proactive_card_review_state VALUES (?, ?) ON CONFLICT(inbox_item_id) DO UPDATE SET checked_at = excluded.checked_at`).run(row.inbox_item_id, now.toISOString());
|
|
56
|
+
}
|
|
57
|
+
return withdrawn;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
export { insightCorrelation, reconcileCards, withdrawCard };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function recheckProactiveNotification(itemId: string, now?: Date, notificationRevision?: unknown): 'send' | 'cancel' | Date;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { insightSourcesAuthorized, insightSourcesChanged } from "../execution/authorization.js";
|
|
2
|
+
import { effectiveProactivePolicy, quietHoursEnd } from "../policy/service.js";
|
|
3
|
+
import { getInboxItem } from "./repository.js";
|
|
4
|
+
//#region src/proactive/inbox/notification-policy.ts
|
|
5
|
+
function recheckProactiveNotification(itemId, now = /* @__PURE__ */ new Date(), notificationRevision) {
|
|
6
|
+
const item = getInboxItem(itemId);
|
|
7
|
+
if (!item || item.withdrawnAt || !insightSourcesAuthorized(item.insightId) || item.status === "resolved" || item.expiresAt && Date.parse(item.expiresAt) <= now.getTime()) return "cancel";
|
|
8
|
+
if (item.insight.actionStatus !== "completed" && insightSourcesChanged(item.insightId)) return "cancel";
|
|
9
|
+
if (typeof notificationRevision === "number" && notificationRevision !== item.notificationRevision) return "cancel";
|
|
10
|
+
const policy = effectiveProactivePolicy(item.subscriptionId, now);
|
|
11
|
+
if (!policy.enabled || policy.preferences.digestEnabled && item.insight.attentionKind !== "receipt") return "cancel";
|
|
12
|
+
if (policy.level !== "active" && ["low", "medium"].includes(item.insight.urgency) && !item.insight.decision) return "cancel";
|
|
13
|
+
if (item.status === "snoozed") return new Date(item.snoozedUntil);
|
|
14
|
+
{
|
|
15
|
+
if (policy.level === "quiet" || policy.settings.delivery !== "important") return "cancel";
|
|
16
|
+
const until = quietHoursEnd(policy.preferences, now);
|
|
17
|
+
if (until) return until;
|
|
18
|
+
}
|
|
19
|
+
return "send";
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { recheckProactiveNotification };
|
|
@@ -22,3 +22,4 @@ export interface OutboxClaim {
|
|
|
22
22
|
export declare function claimDelivery(now?: Date): OutboxClaim | null;
|
|
23
23
|
export declare function finishDelivery(id: string, error?: unknown, attempt?: number, now?: Date): void;
|
|
24
24
|
export declare function recoverExpiredDeliveries(now?: Date): number;
|
|
25
|
+
export declare function deferDelivery(id: string, attempt: number, retryAt: string): void;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { getSqliteDatabase, runSqliteWriteTransaction } from "../../storage/sqlite/transaction.js";
|
|
2
|
+
import { insightSourcesAuthorized, insightSourcesChanged } from "../execution/authorization.js";
|
|
3
|
+
import { effectiveProactivePolicy } from "../policy/service.js";
|
|
2
4
|
import { decideProactiveDisposition, nextQuietHoursEnd, proactiveDispositionReason } from "../../tasks/project-monitoring-service.js";
|
|
3
5
|
import { randomUUID } from "node:crypto";
|
|
4
6
|
//#region src/proactive/inbox/repository.ts
|
|
@@ -8,6 +10,12 @@ function itemFromRow(row) {
|
|
|
8
10
|
return {
|
|
9
11
|
id: s(row, "inbox_item_id"),
|
|
10
12
|
insightId: s(row, "insight_id"),
|
|
13
|
+
subscriptionId: s(row, "subscription_id"),
|
|
14
|
+
withdrawnAt: row.withdrawn_at ? s(row, "withdrawn_at") : void 0,
|
|
15
|
+
correlationKey: row.correlation_key ? s(row, "correlation_key") : void 0,
|
|
16
|
+
revision: Number(row.revision),
|
|
17
|
+
notificationRevision: Number(row.notification_revision),
|
|
18
|
+
...row.expires_at ? { expiresAt: s(row, "expires_at") } : {},
|
|
11
19
|
status: s(row, "status"),
|
|
12
20
|
...row.snoozed_until ? { snoozedUntil: s(row, "snoozed_until") } : {},
|
|
13
21
|
...row.resolution ? { resolution: s(row, "resolution") } : {},
|
|
@@ -17,6 +25,7 @@ function itemFromRow(row) {
|
|
|
17
25
|
scenarioKey: s(row, "scenario_key"),
|
|
18
26
|
title: s(row, "title"),
|
|
19
27
|
summary: s(row, "summary"),
|
|
28
|
+
...row.artifact_json ? { artifact: JSON.parse(s(row, "artifact_json")) } : {},
|
|
20
29
|
whyNow: s(row, "why_now"),
|
|
21
30
|
impact: s(row, "impact"),
|
|
22
31
|
recommendation: s(row, "recommendation"),
|
|
@@ -37,12 +46,12 @@ function itemFromRow(row) {
|
|
|
37
46
|
};
|
|
38
47
|
}
|
|
39
48
|
const SELECT_ITEM = `SELECT i.*, x.subscription_id, x.scenario_key, x.title, x.summary, x.why_now, x.impact, x.recommendation,
|
|
40
|
-
x.work_done, x.decision_json, x.proposed_action_json, x.disposition, x.disposition_reason,
|
|
49
|
+
x.work_done, x.artifact_json, x.decision_json, x.proposed_action_json, x.disposition, x.disposition_reason,
|
|
41
50
|
x.action_status, x.action_result_json, x.action_error, x.urgency, x.confidence, x.value_score, x.evidence_ids_json FROM proactive_inbox_items i
|
|
42
51
|
JOIN proactive_insights x ON x.insight_id = i.insight_id`;
|
|
43
52
|
function projectInsightsToInbox(now = /* @__PURE__ */ new Date()) {
|
|
44
53
|
return runSqliteWriteTransaction((db) => {
|
|
45
|
-
const missing = db.prepare(`SELECT x.insight_id, x.confidence, x.value_score, x.decision_json,
|
|
54
|
+
const missing = db.prepare(`SELECT x.insight_id, x.subscription_id, x.scenario_key, x.urgency, x.confidence, x.value_score, x.decision_json,
|
|
46
55
|
x.proposed_action_json, b.aggregation_key, p.project_id, p.mode, p.quiet_hours_json,
|
|
47
56
|
p.allowed_actions_json, p.confidence_threshold FROM proactive_insights x
|
|
48
57
|
JOIN proactive_runs r ON r.run_id = x.run_id
|
|
@@ -52,6 +61,14 @@ function projectInsightsToInbox(now = /* @__PURE__ */ new Date()) {
|
|
|
52
61
|
WHERE x.disposition IS NULL ORDER BY x.created_at`).all();
|
|
53
62
|
let projected = 0;
|
|
54
63
|
for (const row of missing) {
|
|
64
|
+
if (!insightSourcesAuthorized(String(row.insight_id)) || insightSourcesChanged(String(row.insight_id))) {
|
|
65
|
+
db.prepare("UPDATE proactive_insights SET disposition = 'record_silently', disposition_reason = 'Source changed before delivery' WHERE insight_id = ?").run(String(row.insight_id));
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (!effectiveProactivePolicy(String(row.subscription_id), now).enabled) {
|
|
69
|
+
db.prepare("UPDATE proactive_insights SET disposition = 'record_silently', disposition_reason = 'Proactive subscription paused' WHERE insight_id = ?").run(String(row.insight_id));
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
55
72
|
const aggregationKey = String(row.aggregation_key);
|
|
56
73
|
const projectId = aggregationKey.startsWith("project:") ? aggregationKey.slice(8) : void 0;
|
|
57
74
|
const policy = projectId ? row.project_id ? {
|
|
@@ -85,19 +102,54 @@ function projectInsightsToInbox(now = /* @__PURE__ */ new Date()) {
|
|
|
85
102
|
...action ? { actionId: action.id } : {},
|
|
86
103
|
requiresApproval: !action && Boolean(row.decision_json)
|
|
87
104
|
};
|
|
88
|
-
const
|
|
105
|
+
const requestedDisposition = action && !projectId ? "show_in_work" : decideProactiveDisposition(policy, dispositionInput);
|
|
106
|
+
const disposition = requestedDisposition === "record_silently" && Number(row.confidence) >= policy.confidenceThreshold ? action && projectId ? "request_approval" : "show_in_work" : requestedDisposition;
|
|
89
107
|
const reason = action && !projectId ? "Proposed project action cannot run outside a project scope" : proactiveDispositionReason(policy, dispositionInput, disposition);
|
|
90
108
|
const actionStatus = action ? disposition === "auto_execute" ? "pending" : disposition === "request_approval" ? "approval_required" : disposition === "show_in_work" ? "not_authorized" : null : null;
|
|
91
109
|
const nowIso = now.toISOString();
|
|
92
110
|
db.prepare(`UPDATE proactive_insights SET disposition = ?, disposition_reason = ?, action_status = ?,
|
|
93
111
|
disposition_at = ?, action_updated_at = ? WHERE insight_id = ? AND disposition IS NULL`).run(disposition, reason, actionStatus, nowIso, actionStatus ? nowIso : null, String(row.insight_id));
|
|
94
112
|
if (disposition === "record_silently") continue;
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
113
|
+
const previous = db.prepare(`SELECT i.inbox_item_id, i.expires_at, x.urgency, x.decision_json,
|
|
114
|
+
o.delivered_at FROM proactive_inbox_items i JOIN proactive_insights x USING(insight_id)
|
|
115
|
+
JOIN proactive_runs r USING(run_id) JOIN proactive_signal_batches b USING(batch_id)
|
|
116
|
+
LEFT JOIN proactive_delivery_outbox o USING(inbox_item_id)
|
|
117
|
+
WHERE x.subscription_id = ? AND x.scenario_key = ? AND b.aggregation_key = ?
|
|
118
|
+
AND i.withdrawn_at IS NULL AND i.status IN ('unread', 'read', 'snoozed') AND (i.expires_at IS NULL OR i.expires_at > ? OR x.scenario_key = 'meeting_preparation')
|
|
119
|
+
AND (x.action_status IS NULL OR x.action_status IN ('not_authorized', 'rejected', 'failed'))
|
|
120
|
+
AND x.artifact_edited_at IS NULL
|
|
121
|
+
ORDER BY i.updated_at DESC LIMIT 1`).get(String(row.subscription_id), String(row.scenario_key), aggregationKey, nowIso);
|
|
122
|
+
const id = previous?.inbox_item_id ?? randomUUID();
|
|
123
|
+
const urgencyRank = {
|
|
124
|
+
low: 0,
|
|
125
|
+
medium: 1,
|
|
126
|
+
high: 2,
|
|
127
|
+
critical: 3
|
|
128
|
+
};
|
|
129
|
+
const materialChange = previous && ((urgencyRank[String(row.urgency)] ?? 0) > (urgencyRank[previous.urgency] ?? 0) || row.decision_json !== previous.decision_json || previous.expires_at && previous.expires_at <= nowIso);
|
|
130
|
+
if (previous) db.prepare(`UPDATE proactive_inbox_items SET insight_id = ?, updated_at = ?,
|
|
131
|
+
notification_revision = notification_revision + ? WHERE inbox_item_id = ?`).run(String(row.insight_id), nowIso, materialChange ? 1 : 0, id);
|
|
132
|
+
else db.prepare(`INSERT INTO proactive_inbox_items (inbox_item_id, insight_id, status, created_at, updated_at)
|
|
133
|
+
VALUES (?, ?, 'unread', ?, ?)`).run(id, String(row.insight_id), nowIso, nowIso);
|
|
134
|
+
{
|
|
135
|
+
const meeting = db.prepare(`SELECT e.payload_json FROM proactive_events e
|
|
136
|
+
JOIN proactive_batch_events be ON be.event_id = e.event_id
|
|
137
|
+
JOIN proactive_runs r ON r.batch_id = be.batch_id JOIN proactive_insights x ON x.run_id = r.run_id
|
|
138
|
+
WHERE x.insight_id = ? AND e.type = 'connected_source.calendar_window.v1' ORDER BY e.occurred_at DESC LIMIT 1`).get(String(row.insight_id));
|
|
139
|
+
const meetingStart = meeting ? JSON.parse(meeting.payload_json).meetingStartsAt : void 0;
|
|
140
|
+
const expiresAt = typeof meetingStart === "string" && Number.isFinite(Date.parse(meetingStart)) ? meetingStart : new Date(now.getTime() + 7 * 864e5).toISOString();
|
|
141
|
+
db.prepare("UPDATE proactive_inbox_items SET expires_at = ? WHERE inbox_item_id = ?").run(expiresAt, id);
|
|
142
|
+
}
|
|
143
|
+
const quietEnd = nextQuietHoursEnd(policy.quietHours, now)?.getTime() ?? now.getTime();
|
|
144
|
+
const cooldownEnd = previous?.delivered_at ? Date.parse(previous.delivered_at) + 4 * 36e5 : now.getTime();
|
|
145
|
+
const nextAttemptAt = new Date(Math.max(quietEnd, cooldownEnd)).toISOString();
|
|
146
|
+
if (previous && !materialChange) {
|
|
147
|
+
projected += 1;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
99
150
|
db.prepare(`INSERT INTO proactive_delivery_outbox (delivery_id, inbox_item_id, status, attempt, next_attempt_at, created_at, updated_at)
|
|
100
|
-
VALUES (?, ?, 'pending', 0, ?, ?, ?)
|
|
151
|
+
VALUES (?, ?, 'pending', 0, ?, ?, ?)
|
|
152
|
+
ON CONFLICT(inbox_item_id) DO UPDATE SET delivery_id = excluded.delivery_id, status = 'pending', attempt = 0, next_attempt_at = excluded.next_attempt_at, lease_expires_at = NULL, updated_at = excluded.updated_at`).run(randomUUID(), id, nextAttemptAt, nowIso, nowIso);
|
|
101
153
|
projected += 1;
|
|
102
154
|
}
|
|
103
155
|
return projected;
|
|
@@ -109,28 +161,36 @@ function wakeSnoozedItems(now = /* @__PURE__ */ new Date()) {
|
|
|
109
161
|
}
|
|
110
162
|
function listInbox(input = {}) {
|
|
111
163
|
const limit = Math.min(200, Math.max(1, input.limit ?? 50));
|
|
112
|
-
return (input.status ? getSqliteDatabase().prepare(`${SELECT_ITEM} WHERE i.status = ? ORDER BY i.updated_at DESC LIMIT ?`).all(input.status, limit) : getSqliteDatabase().prepare(`${SELECT_ITEM} ORDER BY i.updated_at DESC LIMIT ?`).all(limit)).map(itemFromRow);
|
|
164
|
+
return (input.status ? getSqliteDatabase().prepare(`${SELECT_ITEM} WHERE i.status = ? ORDER BY i.updated_at DESC LIMIT ?`).all(input.status, limit) : getSqliteDatabase().prepare(`${SELECT_ITEM} ORDER BY i.updated_at DESC LIMIT ?`).all(limit)).map(itemFromRow).filter((item) => !item.withdrawnAt && insightSourcesAuthorized(item.insightId));
|
|
113
165
|
}
|
|
114
166
|
function getInboxItem(id) {
|
|
115
167
|
const row = getSqliteDatabase().prepare(`${SELECT_ITEM} WHERE i.inbox_item_id = ?`).get(id);
|
|
116
168
|
return row ? itemFromRow(row) : null;
|
|
117
169
|
}
|
|
118
170
|
function transitionInboxItem(id, input, now = /* @__PURE__ */ new Date()) {
|
|
119
|
-
if (input.status === "snoozed" && (!input.snoozedUntil || Date.parse(input.snoozedUntil) <= now.getTime())) throw new Error("snoozedUntil must be in the future");
|
|
171
|
+
if (input.status === "snoozed" && (!input.snoozedUntil || !Number.isFinite(Date.parse(input.snoozedUntil)) || Date.parse(input.snoozedUntil) <= now.getTime())) throw new Error("snoozedUntil must be in the future");
|
|
120
172
|
if (input.status === "resolved" && !input.resolution?.trim()) throw new Error("resolution is required");
|
|
121
173
|
runSqliteWriteTransaction((db) => {
|
|
174
|
+
const current = getInboxItem(id);
|
|
175
|
+
if (!current || current.withdrawnAt || !insightSourcesAuthorized(current.insightId)) throw new Error("Inbox item not found");
|
|
176
|
+
if ((current.status === "resolved" || current.expiresAt && Date.parse(current.expiresAt) <= now.getTime()) && input.status !== "resolved") throw new Error("Card is no longer actionable");
|
|
122
177
|
if (db.prepare(`UPDATE proactive_inbox_items SET status = ?, snoozed_until = ?, resolution = ?, updated_at = ? WHERE inbox_item_id = ?`).run(input.status, input.status === "snoozed" ? input.snoozedUntil : null, input.status === "resolved" ? input.resolution.trim() : null, now.toISOString(), id).changes !== 1) throw new Error("Inbox item not found");
|
|
123
178
|
});
|
|
124
179
|
return getInboxItem(id);
|
|
125
180
|
}
|
|
126
181
|
function recordDecision(id, choice, note = "", now = /* @__PURE__ */ new Date()) {
|
|
127
182
|
if (!choice.trim()) throw new Error("choice is required");
|
|
183
|
+
const activeItem = getInboxItem(id);
|
|
184
|
+
if (!activeItem || activeItem.withdrawnAt || !insightSourcesAuthorized(activeItem.insightId)) throw new Error("Inbox item not found");
|
|
185
|
+
if (activeItem?.expiresAt && Date.parse(activeItem.expiresAt) <= now.getTime()) throw new Error("Card expired");
|
|
186
|
+
if (choice === "approve" && activeItem?.subscriptionId && !effectiveProactivePolicy(activeItem.subscriptionId, now).enabled) throw new Error("Subscription paused");
|
|
128
187
|
runSqliteWriteTransaction((db) => {
|
|
129
188
|
const row = db.prepare(`SELECT i.status, x.decision_json, x.action_status FROM proactive_inbox_items i
|
|
130
189
|
JOIN proactive_insights x ON x.insight_id = i.insight_id WHERE i.inbox_item_id = ?`).get(id);
|
|
131
190
|
if (!row) throw new Error("Inbox item not found");
|
|
132
191
|
if (row.status === "resolved") throw new Error("Inbox item is already resolved");
|
|
133
192
|
if (!row.decision_json) throw new Error("Inbox item does not require a decision");
|
|
193
|
+
if (row.action_status && row.action_status !== "approval_required") throw new Error("Action is no longer awaiting approval");
|
|
134
194
|
if (!JSON.parse(row.decision_json).options.some((option) => option.id === choice.trim())) throw new Error("choice is not a valid decision option");
|
|
135
195
|
db.prepare("INSERT INTO proactive_decisions (decision_id, inbox_item_id, choice, note, created_at) VALUES (?, ?, ?, ?, ?)").run(randomUUID(), id, choice.trim().slice(0, 200), note.trim().slice(0, 2e3), now.toISOString());
|
|
136
196
|
db.prepare("UPDATE proactive_inbox_items SET status = 'resolved', resolution = ?, updated_at = ? WHERE inbox_item_id = ?").run(choice.trim().slice(0, 200), now.toISOString(), id);
|
|
@@ -168,12 +228,15 @@ function finishDelivery(id, error, attempt = 1, now = /* @__PURE__ */ new Date()
|
|
|
168
228
|
runSqliteWriteTransaction((db) => {
|
|
169
229
|
const retry = Boolean(error) && attempt < 5;
|
|
170
230
|
db.prepare(`UPDATE proactive_delivery_outbox SET status = ?, next_attempt_at = ?, lease_expires_at = NULL,
|
|
171
|
-
error_message = ?, delivered_at = ?, updated_at = ? WHERE delivery_id = ?`).run(error ? retry ? "retryable" : "failed" : "delivered", retry ? new Date(now.getTime() + attempt * 3e4).toISOString() : now.toISOString(), error ? String(error instanceof Error ? error.message : error).slice(0, 1e3) : null, error ? null : now.toISOString(), now.toISOString(), id);
|
|
231
|
+
error_message = ?, delivered_at = ?, updated_at = ? WHERE delivery_id = ? AND status = 'delivering' AND attempt = ?`).run(error ? retry ? "retryable" : "failed" : "delivered", retry ? new Date(now.getTime() + attempt * 3e4).toISOString() : now.toISOString(), error ? String(error instanceof Error ? error.message : error).slice(0, 1e3) : null, error ? null : now.toISOString(), now.toISOString(), id, attempt);
|
|
172
232
|
});
|
|
173
233
|
}
|
|
174
234
|
function recoverExpiredDeliveries(now = /* @__PURE__ */ new Date()) {
|
|
175
235
|
return Number(runSqliteWriteTransaction((db) => db.prepare(`UPDATE proactive_delivery_outbox SET status = 'retryable', next_attempt_at = ?, lease_expires_at = NULL, updated_at = ?
|
|
176
236
|
WHERE status = 'delivering' AND lease_expires_at <= ?`).run(now.toISOString(), now.toISOString(), now.toISOString()).changes));
|
|
177
237
|
}
|
|
238
|
+
function deferDelivery(id, attempt, retryAt) {
|
|
239
|
+
getSqliteDatabase().prepare("UPDATE proactive_delivery_outbox SET status = 'pending', attempt = attempt - 1, next_attempt_at = ?, lease_expires_at = NULL WHERE delivery_id = ? AND status = 'delivering' AND attempt = ?").run(retryAt, id, attempt);
|
|
240
|
+
}
|
|
178
241
|
//#endregion
|
|
179
|
-
export { claimDelivery, finishDelivery, getInboxItem, getInboxSubscriptionId, listInbox, projectInsightsToInbox, recordDecision, recordFeedback, recoverExpiredDeliveries, transitionInboxItem, wakeSnoozedItems };
|
|
242
|
+
export { claimDelivery, deferDelivery, finishDelivery, getInboxItem, getInboxSubscriptionId, listInbox, projectInsightsToInbox, recordDecision, recordFeedback, recoverExpiredDeliveries, transitionInboxItem, wakeSnoozedItems };
|
|
@@ -1,13 +1,21 @@
|
|
|
1
|
+
import type { ProactiveArtifact } from '@xopcai/gateway-contract';
|
|
1
2
|
export type InboxStatus = 'unread' | 'read' | 'snoozed' | 'resolved';
|
|
2
3
|
export interface InboxItem {
|
|
3
4
|
id: string;
|
|
4
5
|
insightId: string;
|
|
6
|
+
subscriptionId?: string;
|
|
7
|
+
revision?: number;
|
|
8
|
+
notificationRevision?: number;
|
|
9
|
+
expiresAt?: string;
|
|
10
|
+
withdrawnAt?: string;
|
|
11
|
+
correlationKey?: string;
|
|
5
12
|
status: InboxStatus;
|
|
6
13
|
snoozedUntil?: string;
|
|
7
14
|
resolution?: string;
|
|
8
15
|
createdAt: string;
|
|
9
16
|
updatedAt: string;
|
|
10
17
|
insight: {
|
|
18
|
+
artifact?: ProactiveArtifact;
|
|
11
19
|
scenarioKey: string;
|
|
12
20
|
title: string;
|
|
13
21
|
summary: string;
|
|
@@ -49,5 +57,7 @@ export interface InboxDelivery {
|
|
|
49
57
|
deliveryId: string;
|
|
50
58
|
}
|
|
51
59
|
export interface InboxDeliveryAdapter {
|
|
52
|
-
deliver(delivery: InboxDelivery): Promise<void
|
|
60
|
+
deliver(delivery: InboxDelivery): Promise<void | {
|
|
61
|
+
retryAt: string;
|
|
62
|
+
}>;
|
|
53
63
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createLogger } from "../../utils/logger/index.js";
|
|
2
2
|
import { init_logger } from "../../utils/logger.js";
|
|
3
|
-
import {
|
|
3
|
+
import { reconcileCards } from "./lifecycle.js";
|
|
4
4
|
import { executePendingProactiveActions } from "../actions/service.js";
|
|
5
|
-
import { claimDelivery, finishDelivery, projectInsightsToInbox, recoverExpiredDeliveries, wakeSnoozedItems } from "./repository.js";
|
|
5
|
+
import { claimDelivery, deferDelivery, finishDelivery, projectInsightsToInbox, recoverExpiredDeliveries, wakeSnoozedItems } from "./repository.js";
|
|
6
|
+
import { pruneProactiveHistory } from "../maintenance.js";
|
|
6
7
|
//#region src/proactive/inbox/worker.ts
|
|
7
8
|
init_logger();
|
|
8
9
|
const log = createLogger("ProactiveInboxWorker");
|
|
@@ -35,6 +36,7 @@ var ProactiveInboxWorker = class {
|
|
|
35
36
|
pruneProactiveHistory(new Date(now));
|
|
36
37
|
this.lastMaintenanceAt = now;
|
|
37
38
|
}
|
|
39
|
+
reconcileCards();
|
|
38
40
|
projectInsightsToInbox();
|
|
39
41
|
executePendingProactiveActions();
|
|
40
42
|
wakeSnoozedItems();
|
|
@@ -42,10 +44,14 @@ var ProactiveInboxWorker = class {
|
|
|
42
44
|
const claim = claimDelivery();
|
|
43
45
|
if (!claim) return;
|
|
44
46
|
try {
|
|
45
|
-
await this.delivery.deliver({
|
|
47
|
+
const result = await this.delivery.deliver({
|
|
46
48
|
deliveryId: claim.id,
|
|
47
49
|
inboxItem: claim.item
|
|
48
50
|
});
|
|
51
|
+
if (result && result.retryAt) {
|
|
52
|
+
deferDelivery(claim.id, claim.attempt, result.retryAt);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
49
55
|
finishDelivery(claim.id, void 0, claim.attempt);
|
|
50
56
|
} catch (error) {
|
|
51
57
|
finishDelivery(claim.id, error, claim.attempt);
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { ProactiveScenarioService } from "./scenarios/service.js";
|
|
2
2
|
import { normalizeEventEnvelope } from "./events/envelope.js";
|
|
3
|
-
import { mapProductEventToProactive } from "./events/product-event-bridge.js";
|
|
4
3
|
import { matchScenario, matchesCondition } from "./routing/matcher.js";
|
|
5
4
|
import { ProactiveEventService } from "./service.js";
|
|
5
|
+
import { ProactiveInboxService } from "./inbox/service.js";
|
|
6
|
+
import { mapProductEventToProactive } from "./events/product-event-bridge.js";
|
|
6
7
|
import { pruneProactiveHistory } from "./maintenance.js";
|
|
7
8
|
import { AutomationStateContextProvider, ConnectedSourceContextProvider, ContextProviderRegistry, EventBatchContextProvider, InternalObjectContextProvider, MeetingWorkspaceContextProvider, ProjectStateContextProvider, UserModelContextProvider } from "./execution/context.js";
|
|
8
9
|
import { ReadonlyProactiveAgentExecutor } from "./execution/agent-executor.js";
|
|
9
10
|
import { listInsights } from "./execution/repository.js";
|
|
10
11
|
import { ProactiveWorker } from "./execution/worker.js";
|
|
11
12
|
import { ProactiveTemporalWorker } from "./temporal/worker.js";
|
|
12
|
-
import { ProactiveInboxService } from "./inbox/service.js";
|
|
13
13
|
import { ProactiveInboxWorker } from "./inbox/worker.js";
|
|
14
14
|
export { AutomationStateContextProvider, ConnectedSourceContextProvider, ContextProviderRegistry, EventBatchContextProvider, InternalObjectContextProvider, MeetingWorkspaceContextProvider, ProactiveEventService, ProactiveInboxService, ProactiveInboxWorker, ProactiveScenarioService, ProactiveTemporalWorker, ProactiveWorker, ProjectStateContextProvider, ReadonlyProactiveAgentExecutor, UserModelContextProvider, listInsights, mapProductEventToProactive, matchScenario, matchesCondition, normalizeEventEnvelope, pruneProactiveHistory };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare function emailFields(text: string | undefined | null): {
|
|
2
|
+
threadId?: string;
|
|
3
|
+
subject?: string;
|
|
4
|
+
sender?: string;
|
|
5
|
+
labels?: string[];
|
|
6
|
+
};
|
|
7
|
+
/** Thread identity includes the account and collection; provider thread IDs are not globally unique. */
|
|
8
|
+
export declare function readMailThread(sourceItemId: string): {
|
|
9
|
+
origin: import("../knowledge/types.js").KnowledgeSourceItem;
|
|
10
|
+
items: import("../knowledge/types.js").KnowledgeSourceItem[];
|
|
11
|
+
threadKey: string;
|
|
12
|
+
fingerprint: string;
|
|
13
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { getSqliteDatabase } from "../storage/sqlite/transaction.js";
|
|
2
|
+
import { getKnowledgeSourceItem } from "../storage/sqlite/knowledge-repository.js";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
//#region src/proactive/mail-thread.ts
|
|
5
|
+
function emailFields(text) {
|
|
6
|
+
try {
|
|
7
|
+
const value = JSON.parse(text ?? "{}");
|
|
8
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
9
|
+
const row = value;
|
|
10
|
+
return {
|
|
11
|
+
...typeof row.threadId === "string" ? { threadId: row.threadId } : {},
|
|
12
|
+
...typeof row.subject === "string" ? { subject: row.subject } : {},
|
|
13
|
+
...typeof row.sender === "string" ? { sender: row.sender } : {},
|
|
14
|
+
...Array.isArray(row.labels) ? { labels: row.labels.filter((label) => typeof label === "string") } : {}
|
|
15
|
+
};
|
|
16
|
+
} catch {
|
|
17
|
+
return {};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** Thread identity includes the account and collection; provider thread IDs are not globally unique. */
|
|
21
|
+
function readMailThread(sourceItemId) {
|
|
22
|
+
const origin = getKnowledgeSourceItem(sourceItemId);
|
|
23
|
+
if (!origin || origin.deletedAt || origin.itemType !== "email") return null;
|
|
24
|
+
const threadId = emailFields(origin.normalizedText)?.threadId;
|
|
25
|
+
if (typeof threadId !== "string" || !threadId.trim()) return null;
|
|
26
|
+
if (emailFields(origin.normalizedText).labels?.some((label) => [
|
|
27
|
+
"DRAFT",
|
|
28
|
+
"TRASH",
|
|
29
|
+
"SPAM"
|
|
30
|
+
].includes(label))) return null;
|
|
31
|
+
const items = getSqliteDatabase().prepare(`SELECT item_id FROM knowledge_source_items
|
|
32
|
+
WHERE source_instance_id = ? AND collection_scope = ? AND item_type = 'email' AND deleted_at IS NULL
|
|
33
|
+
AND json_extract(CASE WHEN json_valid(normalized_text) THEN normalized_text ELSE '{}' END, '$.threadId') = ?
|
|
34
|
+
AND json_extract(metadata_json, '$.connectionId') = ?
|
|
35
|
+
AND NOT EXISTS (SELECT 1 FROM json_each(CASE WHEN json_valid(normalized_text) THEN normalized_text ELSE '{}' END, '$.labels') WHERE value IN ('DRAFT', 'TRASH', 'SPAM'))
|
|
36
|
+
ORDER BY occurred_at DESC, item_id DESC LIMIT 30`).all(origin.sourceInstanceId, origin.collectionScope, threadId, String(origin.metadata.connectionId)).map((row) => getKnowledgeSourceItem(row.item_id)).reverse();
|
|
37
|
+
return {
|
|
38
|
+
origin,
|
|
39
|
+
items,
|
|
40
|
+
threadKey: createHash("sha256").update(JSON.stringify([
|
|
41
|
+
origin.sourceInstanceId,
|
|
42
|
+
origin.collectionScope,
|
|
43
|
+
origin.metadata.connectionId,
|
|
44
|
+
threadId
|
|
45
|
+
])).digest("hex"),
|
|
46
|
+
fingerprint: createHash("sha256").update(JSON.stringify(items.map((item) => [
|
|
47
|
+
item.id,
|
|
48
|
+
item.contentHash,
|
|
49
|
+
item.occurredAt
|
|
50
|
+
]))).digest("hex")
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
export { emailFields, readMailThread };
|
|
@@ -11,6 +11,13 @@ function pruneProactiveHistory(now = /* @__PURE__ */ new Date()) {
|
|
|
11
11
|
SELECT 1 FROM proactive_batch_events batch_event
|
|
12
12
|
WHERE batch_event.event_id = proactive_events.event_id
|
|
13
13
|
)`).run(cutoff).changes;
|
|
14
|
+
db.prepare("DELETE FROM proactive_notification_budget WHERE created_at < ?").run(cutoff);
|
|
15
|
+
db.prepare("DELETE FROM proactive_presence WHERE expires_at <= ?").run(now.getTime());
|
|
16
|
+
db.prepare("DELETE FROM proactive_delivery_decisions WHERE created_at < ?").run(cutoff);
|
|
17
|
+
db.prepare("DELETE FROM proactive_preview_runs WHERE created_at < ?").run(cutoff);
|
|
18
|
+
db.prepare("DELETE FROM proactive_push_probes WHERE created_at < ?").run(Date.parse(cutoff));
|
|
19
|
+
db.prepare("DELETE FROM proactive_digest_queue WHERE consumed_at < ?").run(cutoff);
|
|
20
|
+
db.prepare("DELETE FROM proactive_digests WHERE created_at < ?").run(cutoff);
|
|
14
21
|
return {
|
|
15
22
|
batches: Number(batches),
|
|
16
23
|
events: Number(events)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function proactiveMetrics(workspace: string, now?: number): {
|
|
2
|
+
usage: Record<string, import("node:sqlite").SQLOutputValue>;
|
|
3
|
+
since: string;
|
|
4
|
+
cards: Record<string, import("node:sqlite").SQLOutputValue>;
|
|
5
|
+
feedback: Record<string, import("node:sqlite").SQLOutputValue>[];
|
|
6
|
+
interruptions: Record<string, import("node:sqlite").SQLOutputValue>[];
|
|
7
|
+
runs: Record<string, import("node:sqlite").SQLOutputValue>[];
|
|
8
|
+
browser: Record<string, import("node:sqlite").SQLOutputValue>[];
|
|
9
|
+
telegram: Record<string, import("node:sqlite").SQLOutputValue>[];
|
|
10
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { getSqliteDatabase } from "../storage/sqlite/transaction.js";
|
|
2
|
+
//#region src/proactive/metrics.ts
|
|
3
|
+
function proactiveMetrics(workspace, now = Date.now()) {
|
|
4
|
+
const db = getSqliteDatabase();
|
|
5
|
+
const since = (/* @__PURE__ */ new Date(now - 30 * 864e5)).toISOString();
|
|
6
|
+
const cards = db.prepare(`SELECT COUNT(*) AS total, SUM(CASE WHEN i.status = 'resolved' THEN 1 ELSE 0 END) AS resolved,
|
|
7
|
+
SUM(CASE WHEN i.withdrawn_at IS NOT NULL THEN 1 ELSE 0 END) AS withdrawn FROM proactive_inbox_items i
|
|
8
|
+
JOIN proactive_insights x USING(insight_id) JOIN proactive_scenario_subscriptions s USING(subscription_id) WHERE s.workspace_id = ? AND i.created_at >= ?`).get(workspace, since);
|
|
9
|
+
const feedback = db.prepare(`SELECT f.rating, COUNT(*) AS count FROM proactive_feedback f JOIN proactive_inbox_items i USING(inbox_item_id)
|
|
10
|
+
JOIN proactive_insights x USING(insight_id) JOIN proactive_scenario_subscriptions s USING(subscription_id) WHERE s.workspace_id = ? AND f.created_at >= ? AND NOT EXISTS (SELECT 1 FROM proactive_feedback newer WHERE newer.inbox_item_id = f.inbox_item_id AND (newer.created_at > f.created_at OR (newer.created_at = f.created_at AND newer.rowid > f.rowid))) GROUP BY f.rating`).all(workspace, since);
|
|
11
|
+
const interruptions = db.prepare("SELECT local_day AS day, COUNT(*) AS count FROM proactive_notification_budget WHERE workspace_id = ? AND created_at >= ? GROUP BY local_day ORDER BY local_day DESC").all(workspace, since);
|
|
12
|
+
const runs = db.prepare(`SELECT r.status, r.outcome_reason AS reason, COUNT(*) AS count FROM proactive_runs r JOIN proactive_scenario_subscriptions s USING(subscription_id) WHERE s.workspace_id = ? AND r.started_at >= ? GROUP BY r.status, r.outcome_reason`).all(workspace, since);
|
|
13
|
+
const browser = db.prepare(`SELECT d.status, COUNT(*) AS count FROM proactive_web_push_deliveries d JOIN proactive_web_push_subscriptions s ON s.id = d.subscription_id WHERE s.workspace_id = ? GROUP BY d.status`).all(workspace);
|
|
14
|
+
const telegram = db.prepare("SELECT status, COUNT(*) AS count FROM proactive_channel_deliveries WHERE workspace_id = ? GROUP BY status").all(workspace);
|
|
15
|
+
return {
|
|
16
|
+
usage: db.prepare(`SELECT SUM(r.input_tokens) AS inputTokens, SUM(r.output_tokens) AS outputTokens, SUM(r.estimated_cost_usd) AS estimatedCostUsd, COUNT(r.estimated_cost_usd) AS pricedRuns, COUNT(*) AS totalRuns FROM proactive_runs r JOIN proactive_scenario_subscriptions s USING(subscription_id) WHERE s.workspace_id = ? AND r.started_at >= ?`).get(workspace, since),
|
|
17
|
+
since,
|
|
18
|
+
cards,
|
|
19
|
+
feedback,
|
|
20
|
+
interruptions,
|
|
21
|
+
runs,
|
|
22
|
+
browser,
|
|
23
|
+
telegram
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { proactiveMetrics };
|