@xopcai/xopc 0.0.268 → 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/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-B9WObxjr.js → agent-avatar-dicebear-D0NBOWC4.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-defaults-settings-panel-J1YFNG26.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-Bbn9mDfL.js → app-management-settings-panel-E3xm2teY.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-Deo5EsIc.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-CxJTU8MU.js → archive-plugin-D09yo_Hc.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-C71Cukgp.js → attachment-load-CNYb8Qru.js} +1 -1
- package/dist/gateway/static/root/assets/{automations-page-_J7UZ55Q.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-CX8fC8Zt.js → binary-plugins-OWlaGqlY.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-Ci9fjoQL.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-rDSx0ozx.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-DMSl8KxS.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-BByq6Uh8.js → cron-expression-DlyRIyUn.js} +1 -1
- package/dist/gateway/static/root/assets/{date-picker-m6JZCUEW.js → date-picker-CU-ij_yE.js} +1 -1
- package/dist/gateway/static/root/assets/{dependency-picker-CwCAjbaJ.js → dependency-picker-Z6Nf0Wjq.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-DSmeEREd.js → desktop-pet-C0F5Iqnc.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-MfKceMoe.js → desktop-pet-settings-Bj_W6HL1.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-CwvWy26h.js → extension-debug-page-Ba09EnAn.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-DLrbVBHM.js → extension-page-C88aGXJs.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-BI6C4PM-.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-CUl832Tb.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-wKE8Q2FX.js → index-CfyyA9-g.js} +47 -47
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-r4DCS19O.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-DkfCRLUh.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-Cfx4ATTa.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-DOLjWd3y.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-BGW9joAb.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-_SGXMT2O.js → page-tabs-BfV-PQa5.js} +1 -1
- package/dist/gateway/static/root/assets/{preview-open-alternatives-lVetfCWk.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-BDqVrUEj.js → runtime-tools-settings-panel-DMQ0wpNg.js} +2 -2
- package/dist/gateway/static/root/assets/{schema-form-8RQgFJJO.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-j4UjGM-f.js → settings-form-section-Cem6L49Y.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-lMi50B_s.js → settings-page-afDUxqlV.js} +1 -1
- package/dist/gateway/static/root/assets/{setup-status-panel-Dx4Fy3rU.js → setup-status-panel-Cc_iP_vv.js} +1 -1
- package/dist/gateway/static/root/assets/{share-preview-page-aSyKRH5y.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-C2_CAvxW.js → skills-page-CNrq1W6C.js} +1 -1
- package/dist/gateway/static/root/assets/{skills-page.utils-B00e4Q30.js → skills-page.utils-D1wGfm22.js} +1 -1
- package/dist/gateway/static/root/assets/{system-settings-panel-CulAkrsV.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-C01xgCWR.js → tunnel-api-Dur80zSQ.js} +1 -1
- package/dist/gateway/static/root/assets/{use-autosave-DfSJ3c2p.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-9v-miN8o.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-CREYgh-1.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/workflow/subagent-runner.d.ts +1 -0
- package/dist/src/agent/workflow/subagent-runner.js +2 -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/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/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/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.d.ts +0 -1
- package/dist/src/gateway/service.js +24 -26
- 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/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 +1 -1
- package/dist/src/voice/realtime/runtime.d.ts +2 -1
- package/dist/src/voice/realtime/runtime.js +21 -5
- 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 +26 -5
- package/dist/src/workflows/service/workflow-run-service.types.d.ts +1 -0
- package/package.json +6 -4
- package/dist/gateway/static/root/assets/agents-BJuTIRPD.js +0 -1
- package/dist/gateway/static/root/assets/apps-page-Cz4d7ErX.js +0 -1
- package/dist/gateway/static/root/assets/automations-workspace-Bn8nwUOL.js +0 -6
- package/dist/gateway/static/root/assets/browser-settings-page-B9RA6x4x.js +0 -8
- package/dist/gateway/static/root/assets/capabilities-settings-panel-C1GUih8m.js +0 -7
- package/dist/gateway/static/root/assets/channels-settings-CEhVLJNa.js +0 -1
- 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/local-app-workbench-page-qwLuir8A.js +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
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { getSqliteDatabase, runSqliteWriteTransaction } from "../../storage/sqlite/transaction.js";
|
|
2
|
+
import { effectiveProactivePolicy } from "../policy/service.js";
|
|
2
3
|
import { decideProactiveDisposition, nextQuietHoursEnd, proactiveDispositionReason } from "../../tasks/project-monitoring-service.js";
|
|
4
|
+
import { insightSourcesAuthorized } from "../execution/authorization.js";
|
|
3
5
|
import { randomUUID } from "node:crypto";
|
|
4
6
|
//#region src/proactive/inbox/repository.ts
|
|
5
7
|
const s = (row, key) => String(row[key]);
|
|
@@ -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") } : {},
|
|
@@ -42,7 +50,7 @@ const SELECT_ITEM = `SELECT i.*, x.subscription_id, x.scenario_key, x.title, x.s
|
|
|
42
50
|
JOIN proactive_insights x ON x.insight_id = i.insight_id`;
|
|
43
51
|
function projectInsightsToInbox(now = /* @__PURE__ */ new Date()) {
|
|
44
52
|
return runSqliteWriteTransaction((db) => {
|
|
45
|
-
const missing = db.prepare(`SELECT x.insight_id, x.confidence, x.value_score, x.decision_json,
|
|
53
|
+
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
54
|
x.proposed_action_json, b.aggregation_key, p.project_id, p.mode, p.quiet_hours_json,
|
|
47
55
|
p.allowed_actions_json, p.confidence_threshold FROM proactive_insights x
|
|
48
56
|
JOIN proactive_runs r ON r.run_id = x.run_id
|
|
@@ -52,6 +60,11 @@ function projectInsightsToInbox(now = /* @__PURE__ */ new Date()) {
|
|
|
52
60
|
WHERE x.disposition IS NULL ORDER BY x.created_at`).all();
|
|
53
61
|
let projected = 0;
|
|
54
62
|
for (const row of missing) {
|
|
63
|
+
const attention = effectiveProactivePolicy(String(row.subscription_id), now);
|
|
64
|
+
if (!attention.enabled) {
|
|
65
|
+
db.prepare("UPDATE proactive_insights SET disposition = 'record_silently', disposition_reason = 'Proactive subscription paused' WHERE insight_id = ?").run(String(row.insight_id));
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
55
68
|
const aggregationKey = String(row.aggregation_key);
|
|
56
69
|
const projectId = aggregationKey.startsWith("project:") ? aggregationKey.slice(8) : void 0;
|
|
57
70
|
const policy = projectId ? row.project_id ? {
|
|
@@ -85,19 +98,53 @@ function projectInsightsToInbox(now = /* @__PURE__ */ new Date()) {
|
|
|
85
98
|
...action ? { actionId: action.id } : {},
|
|
86
99
|
requiresApproval: !action && Boolean(row.decision_json)
|
|
87
100
|
};
|
|
88
|
-
const
|
|
101
|
+
const legacyDisposition = action && !projectId ? "show_in_work" : decideProactiveDisposition(policy, dispositionInput);
|
|
102
|
+
const disposition = attention.settings.managed && legacyDisposition === "record_silently" ? action && projectId ? "request_approval" : "show_in_work" : legacyDisposition;
|
|
89
103
|
const reason = action && !projectId ? "Proposed project action cannot run outside a project scope" : proactiveDispositionReason(policy, dispositionInput, disposition);
|
|
90
104
|
const actionStatus = action ? disposition === "auto_execute" ? "pending" : disposition === "request_approval" ? "approval_required" : disposition === "show_in_work" ? "not_authorized" : null : null;
|
|
91
105
|
const nowIso = now.toISOString();
|
|
92
106
|
db.prepare(`UPDATE proactive_insights SET disposition = ?, disposition_reason = ?, action_status = ?,
|
|
93
107
|
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
108
|
if (disposition === "record_silently") continue;
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
109
|
+
const previous = attention.settings.managed ? db.prepare(`SELECT i.inbox_item_id, x.urgency, x.decision_json,
|
|
110
|
+
o.delivered_at FROM proactive_inbox_items i JOIN proactive_insights x USING(insight_id)
|
|
111
|
+
JOIN proactive_runs r USING(run_id) JOIN proactive_signal_batches b USING(batch_id)
|
|
112
|
+
LEFT JOIN proactive_delivery_outbox o USING(inbox_item_id)
|
|
113
|
+
WHERE x.subscription_id = ? AND x.scenario_key = ? AND b.aggregation_key = ?
|
|
114
|
+
AND i.withdrawn_at IS NULL AND i.status IN ('unread', 'read', 'snoozed') AND (i.expires_at IS NULL OR i.expires_at > ?)
|
|
115
|
+
AND (x.action_status IS NULL OR x.action_status IN ('not_authorized', 'rejected', 'failed'))
|
|
116
|
+
ORDER BY i.updated_at DESC LIMIT 1`).get(String(row.subscription_id), String(row.scenario_key), aggregationKey, nowIso) : void 0;
|
|
117
|
+
const id = previous?.inbox_item_id ?? randomUUID();
|
|
118
|
+
const urgencyRank = {
|
|
119
|
+
low: 0,
|
|
120
|
+
medium: 1,
|
|
121
|
+
high: 2,
|
|
122
|
+
critical: 3
|
|
123
|
+
};
|
|
124
|
+
const materialChange = previous && ((urgencyRank[String(row.urgency)] ?? 0) > (urgencyRank[previous.urgency] ?? 0) || row.decision_json !== previous.decision_json);
|
|
125
|
+
if (previous) db.prepare(`UPDATE proactive_inbox_items SET insight_id = ?, updated_at = ?,
|
|
126
|
+
notification_revision = notification_revision + ? WHERE inbox_item_id = ?`).run(String(row.insight_id), nowIso, materialChange ? 1 : 0, id);
|
|
127
|
+
else db.prepare(`INSERT INTO proactive_inbox_items (inbox_item_id, insight_id, status, created_at, updated_at)
|
|
128
|
+
VALUES (?, ?, 'unread', ?, ?)`).run(id, String(row.insight_id), nowIso, nowIso);
|
|
129
|
+
if (attention.settings.managed) {
|
|
130
|
+
const meeting = db.prepare(`SELECT e.payload_json FROM proactive_events e
|
|
131
|
+
JOIN proactive_batch_events be ON be.event_id = e.event_id
|
|
132
|
+
JOIN proactive_runs r ON r.batch_id = be.batch_id JOIN proactive_insights x ON x.run_id = r.run_id
|
|
133
|
+
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));
|
|
134
|
+
const meetingStart = meeting ? JSON.parse(meeting.payload_json).meetingStartsAt : void 0;
|
|
135
|
+
const expiresAt = typeof meetingStart === "string" && Number.isFinite(Date.parse(meetingStart)) ? meetingStart : new Date(now.getTime() + 7 * 864e5).toISOString();
|
|
136
|
+
db.prepare("UPDATE proactive_inbox_items SET expires_at = ? WHERE inbox_item_id = ?").run(expiresAt, id);
|
|
137
|
+
}
|
|
138
|
+
const quietEnd = nextQuietHoursEnd(policy.quietHours, now)?.getTime() ?? now.getTime();
|
|
139
|
+
const cooldownEnd = previous?.delivered_at ? Date.parse(previous.delivered_at) + 4 * 36e5 : now.getTime();
|
|
140
|
+
const nextAttemptAt = new Date(Math.max(quietEnd, cooldownEnd)).toISOString();
|
|
141
|
+
if (previous && !materialChange) {
|
|
142
|
+
projected += 1;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
99
145
|
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, ?, ?, ?)
|
|
146
|
+
VALUES (?, ?, 'pending', 0, ?, ?, ?)
|
|
147
|
+
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
148
|
projected += 1;
|
|
102
149
|
}
|
|
103
150
|
return projected;
|
|
@@ -109,22 +156,29 @@ function wakeSnoozedItems(now = /* @__PURE__ */ new Date()) {
|
|
|
109
156
|
}
|
|
110
157
|
function listInbox(input = {}) {
|
|
111
158
|
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);
|
|
159
|
+
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
160
|
}
|
|
114
161
|
function getInboxItem(id) {
|
|
115
162
|
const row = getSqliteDatabase().prepare(`${SELECT_ITEM} WHERE i.inbox_item_id = ?`).get(id);
|
|
116
163
|
return row ? itemFromRow(row) : null;
|
|
117
164
|
}
|
|
118
165
|
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");
|
|
166
|
+
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
167
|
if (input.status === "resolved" && !input.resolution?.trim()) throw new Error("resolution is required");
|
|
121
168
|
runSqliteWriteTransaction((db) => {
|
|
169
|
+
const current = getInboxItem(id);
|
|
170
|
+
if (!current || current.withdrawnAt || !insightSourcesAuthorized(current.insightId)) throw new Error("Inbox item not found");
|
|
171
|
+
if ((current.status === "resolved" || current.expiresAt && Date.parse(current.expiresAt) <= now.getTime()) && input.status !== "resolved") throw new Error("Card is no longer actionable");
|
|
122
172
|
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
173
|
});
|
|
124
174
|
return getInboxItem(id);
|
|
125
175
|
}
|
|
126
176
|
function recordDecision(id, choice, note = "", now = /* @__PURE__ */ new Date()) {
|
|
127
177
|
if (!choice.trim()) throw new Error("choice is required");
|
|
178
|
+
const activeItem = getInboxItem(id);
|
|
179
|
+
if (!activeItem || activeItem.withdrawnAt || !insightSourcesAuthorized(activeItem.insightId)) throw new Error("Inbox item not found");
|
|
180
|
+
if (activeItem?.expiresAt && Date.parse(activeItem.expiresAt) <= now.getTime()) throw new Error("Card expired");
|
|
181
|
+
if (choice === "approve" && activeItem?.subscriptionId && !effectiveProactivePolicy(activeItem.subscriptionId, now).enabled) throw new Error("Subscription paused");
|
|
128
182
|
runSqliteWriteTransaction((db) => {
|
|
129
183
|
const row = db.prepare(`SELECT i.status, x.decision_json, x.action_status FROM proactive_inbox_items i
|
|
130
184
|
JOIN proactive_insights x ON x.insight_id = i.insight_id WHERE i.inbox_item_id = ?`).get(id);
|
|
@@ -168,12 +222,15 @@ function finishDelivery(id, error, attempt = 1, now = /* @__PURE__ */ new Date()
|
|
|
168
222
|
runSqliteWriteTransaction((db) => {
|
|
169
223
|
const retry = Boolean(error) && attempt < 5;
|
|
170
224
|
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);
|
|
225
|
+
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
226
|
});
|
|
173
227
|
}
|
|
174
228
|
function recoverExpiredDeliveries(now = /* @__PURE__ */ new Date()) {
|
|
175
229
|
return Number(runSqliteWriteTransaction((db) => db.prepare(`UPDATE proactive_delivery_outbox SET status = 'retryable', next_attempt_at = ?, lease_expires_at = NULL, updated_at = ?
|
|
176
230
|
WHERE status = 'delivering' AND lease_expires_at <= ?`).run(now.toISOString(), now.toISOString(), now.toISOString()).changes));
|
|
177
231
|
}
|
|
232
|
+
function deferDelivery(id, attempt, retryAt) {
|
|
233
|
+
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);
|
|
234
|
+
}
|
|
178
235
|
//#endregion
|
|
179
|
-
export { claimDelivery, finishDelivery, getInboxItem, getInboxSubscriptionId, listInbox, projectInsightsToInbox, recordDecision, recordFeedback, recoverExpiredDeliveries, transitionInboxItem, wakeSnoozedItems };
|
|
236
|
+
export { claimDelivery, deferDelivery, finishDelivery, getInboxItem, getInboxSubscriptionId, listInbox, projectInsightsToInbox, recordDecision, recordFeedback, recoverExpiredDeliveries, transitionInboxItem, wakeSnoozedItems };
|
|
@@ -2,6 +2,12 @@ export type InboxStatus = 'unread' | 'read' | 'snoozed' | 'resolved';
|
|
|
2
2
|
export interface InboxItem {
|
|
3
3
|
id: string;
|
|
4
4
|
insightId: string;
|
|
5
|
+
subscriptionId?: string;
|
|
6
|
+
revision?: number;
|
|
7
|
+
notificationRevision?: number;
|
|
8
|
+
expiresAt?: string;
|
|
9
|
+
withdrawnAt?: string;
|
|
10
|
+
correlationKey?: string;
|
|
5
11
|
status: InboxStatus;
|
|
6
12
|
snoozedUntil?: string;
|
|
7
13
|
resolution?: string;
|
|
@@ -49,5 +55,7 @@ export interface InboxDelivery {
|
|
|
49
55
|
deliveryId: string;
|
|
50
56
|
}
|
|
51
57
|
export interface InboxDeliveryAdapter {
|
|
52
|
-
deliver(delivery: InboxDelivery): Promise<void
|
|
58
|
+
deliver(delivery: InboxDelivery): Promise<void | {
|
|
59
|
+
retryAt: string;
|
|
60
|
+
}>;
|
|
53
61
|
}
|
|
@@ -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");
|
|
@@ -36,16 +37,21 @@ var ProactiveInboxWorker = class {
|
|
|
36
37
|
this.lastMaintenanceAt = now;
|
|
37
38
|
}
|
|
38
39
|
projectInsightsToInbox();
|
|
40
|
+
reconcileCards();
|
|
39
41
|
executePendingProactiveActions();
|
|
40
42
|
wakeSnoozedItems();
|
|
41
43
|
recoverExpiredDeliveries();
|
|
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,4 +1,5 @@
|
|
|
1
1
|
import { ProactiveScenarioService } from "./scenarios/service.js";
|
|
2
|
+
import { ProactiveInboxService } from "./inbox/service.js";
|
|
2
3
|
import { normalizeEventEnvelope } from "./events/envelope.js";
|
|
3
4
|
import { mapProductEventToProactive } from "./events/product-event-bridge.js";
|
|
4
5
|
import { matchScenario, matchesCondition } from "./routing/matcher.js";
|
|
@@ -9,6 +10,5 @@ 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 };
|
|
@@ -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 };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { getSqliteDatabase } from "../../storage/sqlite/transaction.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
//#region src/proactive/policy/presence.ts
|
|
4
|
+
const PresenceSchema = z.object({
|
|
5
|
+
clientId: z.string().min(8).max(128),
|
|
6
|
+
active: z.boolean(),
|
|
7
|
+
surface: z.enum([
|
|
8
|
+
"web",
|
|
9
|
+
"electron",
|
|
10
|
+
"mobile"
|
|
11
|
+
])
|
|
12
|
+
}).strict();
|
|
13
|
+
function recordProactivePresence(workspaceId, value, now = Date.now()) {
|
|
14
|
+
const input = PresenceSchema.parse(value);
|
|
15
|
+
const db = getSqliteDatabase();
|
|
16
|
+
db.prepare("DELETE FROM proactive_presence WHERE expires_at <= ?").run(now);
|
|
17
|
+
if (!input.active) {
|
|
18
|
+
db.prepare("DELETE FROM proactive_presence WHERE workspace_id = ? AND client_id = ?").run(workspaceId, input.clientId);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
db.prepare(`INSERT INTO proactive_presence VALUES (?, ?, ?, ?) ON CONFLICT(workspace_id, client_id) DO UPDATE SET surface = excluded.surface, expires_at = excluded.expires_at`).run(workspaceId, input.clientId, input.surface, now + 75e3);
|
|
22
|
+
}
|
|
23
|
+
function viewingProactiveCards(workspaceId, now = Date.now()) {
|
|
24
|
+
return Boolean(getSqliteDatabase().prepare("SELECT 1 FROM proactive_presence WHERE workspace_id = ? AND expires_at > ?").get(workspaceId, now));
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { recordProactivePresence, viewingProactiveCards };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type ProactivePreferences } from '@xopcai/gateway-contract';
|
|
2
|
+
export declare class ProactiveConflict extends Error {
|
|
3
|
+
}
|
|
4
|
+
export declare function subscriptionSettings(id: string): {
|
|
5
|
+
revision: number;
|
|
6
|
+
managed: boolean;
|
|
7
|
+
level: "off" | "active" | "quiet" | "balanced";
|
|
8
|
+
delivery: "digest" | "inbox" | "important";
|
|
9
|
+
preparationWorkflowId: string;
|
|
10
|
+
scanIntervalMinutes: number;
|
|
11
|
+
userInstructions: string;
|
|
12
|
+
};
|
|
13
|
+
export declare function proactivePreferences(workspaceId: string): ProactivePreferences;
|
|
14
|
+
export declare function updateProactivePreferences(workspaceId: string, value: unknown): ProactivePreferences;
|
|
15
|
+
export declare function effectiveProactivePolicy(subscriptionId: string, now?: Date): {
|
|
16
|
+
enabled: boolean;
|
|
17
|
+
level: "off" | "active" | "quiet" | "balanced";
|
|
18
|
+
settings: {
|
|
19
|
+
revision: number;
|
|
20
|
+
managed: boolean;
|
|
21
|
+
level: "off" | "active" | "quiet" | "balanced";
|
|
22
|
+
delivery: "digest" | "inbox" | "important";
|
|
23
|
+
preparationWorkflowId: string;
|
|
24
|
+
scanIntervalMinutes: number;
|
|
25
|
+
userInstructions: string;
|
|
26
|
+
};
|
|
27
|
+
preferences: {
|
|
28
|
+
level: "off" | "active" | "quiet" | "balanced";
|
|
29
|
+
timezone: string;
|
|
30
|
+
quietStartHour: number;
|
|
31
|
+
quietEndHour: number;
|
|
32
|
+
dailyNotificationLimit: number;
|
|
33
|
+
digestEnabled: boolean;
|
|
34
|
+
digestHour: number;
|
|
35
|
+
digestMinute: number;
|
|
36
|
+
preferredChannel: "mobile" | "all" | "auto" | "browser" | "telegram";
|
|
37
|
+
suppressWhileViewing: boolean;
|
|
38
|
+
telegram: {
|
|
39
|
+
chatId: string;
|
|
40
|
+
publicUrl: string;
|
|
41
|
+
accountId?: string;
|
|
42
|
+
};
|
|
43
|
+
pausedUntil: string;
|
|
44
|
+
revision: number;
|
|
45
|
+
};
|
|
46
|
+
workspaceId: string;
|
|
47
|
+
scanIntervalMinutes: number;
|
|
48
|
+
};
|
|
49
|
+
export declare function localProactiveDay(now: Date, timezone: string): string;
|
|
50
|
+
export declare function quietHoursEnd(preferences: ProactivePreferences, now: Date): Date | null;
|
|
51
|
+
/** Called in the same transaction as notification persistence. Reservations survive retries. */
|
|
52
|
+
export declare function reserveProactiveNotification(subscriptionId: string, dedupeKey: string, now?: Date): 'allowed' | 'suppressed' | Date;
|
|
53
|
+
export declare function nextDigestTime(preferences: ProactivePreferences, after: Date): Date;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { getSqliteDatabase, runSqliteWriteTransaction } from "../../storage/sqlite/transaction.js";
|
|
2
|
+
import { ProactivePreferencesSchema, ProactivePreferencesUpdateSchema, ProactiveSubscriptionSettingsSchema } from "../../../packages/gateway-contract/src/proactive.js";
|
|
3
|
+
//#region src/proactive/policy/service.ts
|
|
4
|
+
var ProactiveConflict = class extends Error {};
|
|
5
|
+
function subscriptionSettings(id) {
|
|
6
|
+
const row = getSqliteDatabase().prepare("SELECT settings_json, revision FROM proactive_subscription_settings WHERE subscription_id = ?").get(id);
|
|
7
|
+
return {
|
|
8
|
+
...ProactiveSubscriptionSettingsSchema.parse(row ? JSON.parse(row.settings_json) : {}),
|
|
9
|
+
revision: row?.revision ?? 0,
|
|
10
|
+
managed: Boolean(row)
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function proactivePreferences(workspaceId) {
|
|
14
|
+
const row = getSqliteDatabase().prepare("SELECT preferences_json, revision FROM proactive_preferences WHERE workspace_id = ?").get(workspaceId);
|
|
15
|
+
return ProactivePreferencesSchema.parse(row ? {
|
|
16
|
+
...JSON.parse(row.preferences_json),
|
|
17
|
+
revision: row.revision
|
|
18
|
+
} : {});
|
|
19
|
+
}
|
|
20
|
+
function updateProactivePreferences(workspaceId, value) {
|
|
21
|
+
const { expectedRevision, ...patch } = ProactivePreferencesUpdateSchema.parse(value);
|
|
22
|
+
return runSqliteWriteTransaction((db) => {
|
|
23
|
+
const current = proactivePreferences(workspaceId);
|
|
24
|
+
if (current.revision !== expectedRevision) throw new ProactiveConflict("Preferences changed; refresh and try again");
|
|
25
|
+
const next = ProactivePreferencesSchema.parse({
|
|
26
|
+
...current,
|
|
27
|
+
...patch,
|
|
28
|
+
revision: current.revision + 1
|
|
29
|
+
});
|
|
30
|
+
if (next.preferredChannel === "telegram" && !next.telegram) throw new Error("Telegram destination is required");
|
|
31
|
+
db.prepare(`INSERT INTO proactive_preferences(workspace_id, preferences_json, revision) VALUES (?, ?, ?)
|
|
32
|
+
ON CONFLICT(workspace_id) DO UPDATE SET preferences_json = excluded.preferences_json, revision = excluded.revision`).run(workspaceId, JSON.stringify(next), next.revision);
|
|
33
|
+
if (next.timezone !== current.timezone || next.digestHour !== current.digestHour || next.digestMinute !== current.digestMinute) db.prepare("UPDATE proactive_digest_queue SET due_at = ? WHERE workspace_id = ? AND mode = 'daily' AND consumed_at IS NULL").run(nextDigestTime(next, /* @__PURE__ */ new Date()).toISOString(), workspaceId);
|
|
34
|
+
if (next.level === "off") {
|
|
35
|
+
db.prepare("DELETE FROM proactive_digest_queue WHERE workspace_id = ?").run(workspaceId);
|
|
36
|
+
db.prepare(`UPDATE proactive_delivery_outbox SET status = 'delivered', error_message = 'proactive_disabled'
|
|
37
|
+
WHERE status IN ('pending', 'retryable') AND inbox_item_id IN (
|
|
38
|
+
SELECT i.inbox_item_id FROM proactive_inbox_items i JOIN proactive_insights x USING(insight_id)
|
|
39
|
+
JOIN proactive_scenario_subscriptions s USING(subscription_id) WHERE s.workspace_id = ?
|
|
40
|
+
)`).run(workspaceId);
|
|
41
|
+
}
|
|
42
|
+
return next;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
function effectiveProactivePolicy(subscriptionId, now = /* @__PURE__ */ new Date()) {
|
|
46
|
+
const sub = getSqliteDatabase().prepare("SELECT workspace_id, enabled FROM proactive_scenario_subscriptions WHERE subscription_id = ?").get(subscriptionId);
|
|
47
|
+
const settings = subscriptionSettings(subscriptionId);
|
|
48
|
+
const preferences = proactivePreferences(sub?.workspace_id ?? "default");
|
|
49
|
+
const level = preferences.level === "off" ? "off" : settings.level ?? preferences.level;
|
|
50
|
+
return {
|
|
51
|
+
enabled: Boolean(sub?.enabled) && level !== "off" && !(preferences.pausedUntil && Date.parse(preferences.pausedUntil) > now.getTime()),
|
|
52
|
+
level,
|
|
53
|
+
settings,
|
|
54
|
+
preferences,
|
|
55
|
+
workspaceId: sub?.workspace_id ?? "",
|
|
56
|
+
scanIntervalMinutes: settings.scanIntervalMinutes ?? (level === "active" ? 30 : level === "quiet" ? 1440 : 120)
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function localProactiveDay(now, timezone) {
|
|
60
|
+
return new Intl.DateTimeFormat("en-CA", {
|
|
61
|
+
timeZone: timezone,
|
|
62
|
+
year: "numeric",
|
|
63
|
+
month: "2-digit",
|
|
64
|
+
day: "2-digit"
|
|
65
|
+
}).format(now);
|
|
66
|
+
}
|
|
67
|
+
function quietHoursEnd(preferences, now) {
|
|
68
|
+
const { quietStartHour: start, quietEndHour: end, timezone } = preferences;
|
|
69
|
+
const quiet = (at) => {
|
|
70
|
+
const hour = Number(new Intl.DateTimeFormat("en-US", {
|
|
71
|
+
timeZone: timezone,
|
|
72
|
+
hour: "2-digit",
|
|
73
|
+
hourCycle: "h23"
|
|
74
|
+
}).format(at));
|
|
75
|
+
return start !== end && (start < end ? hour >= start && hour < end : hour >= start || hour < end);
|
|
76
|
+
};
|
|
77
|
+
if (!quiet(now)) return null;
|
|
78
|
+
for (let minutes = 1; minutes <= 2880; minutes++) {
|
|
79
|
+
const at = /* @__PURE__ */ new Date(Math.floor(now.getTime() / 6e4) * 6e4 + minutes * 6e4);
|
|
80
|
+
if (!quiet(at)) return at;
|
|
81
|
+
}
|
|
82
|
+
return new Date(now.getTime() + 864e5);
|
|
83
|
+
}
|
|
84
|
+
/** Called in the same transaction as notification persistence. Reservations survive retries. */
|
|
85
|
+
function reserveProactiveNotification(subscriptionId, dedupeKey, now = /* @__PURE__ */ new Date()) {
|
|
86
|
+
return runSqliteWriteTransaction((db) => {
|
|
87
|
+
const policy = effectiveProactivePolicy(subscriptionId, now);
|
|
88
|
+
if (!policy.enabled) return "suppressed";
|
|
89
|
+
if (!policy.settings.managed && policy.preferences.revision === 0) return "allowed";
|
|
90
|
+
if (policy.level === "quiet" || policy.settings.delivery === "inbox") return "suppressed";
|
|
91
|
+
const end = quietHoursEnd(policy.preferences, now);
|
|
92
|
+
if (end) return end;
|
|
93
|
+
if (db.prepare("SELECT 1 FROM proactive_notification_budget WHERE dedupe_key = ?").get(dedupeKey)) return "allowed";
|
|
94
|
+
const day = localProactiveDay(now, policy.preferences.timezone);
|
|
95
|
+
if (db.prepare("SELECT COUNT(*) AS count FROM proactive_notification_budget WHERE workspace_id = ? AND local_day = ?").get(policy.workspaceId, day).count >= policy.preferences.dailyNotificationLimit) return "suppressed";
|
|
96
|
+
db.prepare("INSERT INTO proactive_notification_budget(dedupe_key, workspace_id, local_day, created_at) VALUES (?, ?, ?, ?)").run(dedupeKey, policy.workspaceId, day, now.toISOString());
|
|
97
|
+
return "allowed";
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
function nextDigestTime(preferences, after) {
|
|
101
|
+
const formatter = new Intl.DateTimeFormat("en-GB", {
|
|
102
|
+
timeZone: preferences.timezone,
|
|
103
|
+
hour: "2-digit",
|
|
104
|
+
minute: "2-digit",
|
|
105
|
+
hourCycle: "h23"
|
|
106
|
+
});
|
|
107
|
+
const target = preferences.digestHour * 60 + preferences.digestMinute;
|
|
108
|
+
let previousMinute = -1;
|
|
109
|
+
let previousDay = "";
|
|
110
|
+
for (let offset = 0; offset <= 2880; offset++) {
|
|
111
|
+
const date = /* @__PURE__ */ new Date(Math.floor(after.getTime() / 6e4) * 6e4 + offset * 6e4);
|
|
112
|
+
const parts = formatter.formatToParts(date);
|
|
113
|
+
const minute = Number(parts.find((part) => part.type === "hour")?.value) * 60 + Number(parts.find((part) => part.type === "minute")?.value);
|
|
114
|
+
const day = localProactiveDay(date, preferences.timezone);
|
|
115
|
+
if (minute === target || offset > 0 && previousDay === day && previousMinute < target && minute > target) return date;
|
|
116
|
+
previousMinute = minute;
|
|
117
|
+
previousDay = day;
|
|
118
|
+
}
|
|
119
|
+
return new Date(after.getTime() + 864e5);
|
|
120
|
+
}
|
|
121
|
+
//#endregion
|
|
122
|
+
export { ProactiveConflict, effectiveProactivePolicy, localProactiveDay, nextDigestTime, proactivePreferences, quietHoursEnd, reserveProactiveNotification, subscriptionSettings, updateProactivePreferences };
|
|
@@ -46,10 +46,11 @@ function aggregationKey(event, route) {
|
|
|
46
46
|
return `${event.subject.kind}:${event.subject.id}`;
|
|
47
47
|
}
|
|
48
48
|
function matchScenario(event, route) {
|
|
49
|
-
|
|
49
|
+
const scheduled = event.type === "proactive.scan.v1" && event.source.kind === "proactive_scheduler" && event.payload.subscriptionId === route.subscriptionId;
|
|
50
|
+
if (!route.enabled || !scheduled && !route.eventTypes.includes(event.type)) return null;
|
|
50
51
|
if (route.scope?.workspaceId && route.scope.workspaceId !== event.scope.workspaceId) return null;
|
|
51
52
|
if (route.scope?.projectId && route.scope.projectId !== event.scope.projectId) return null;
|
|
52
|
-
if (route.condition && !matchesCondition(event, route.condition)) return null;
|
|
53
|
+
if (!scheduled && route.condition && !matchesCondition(event, route.condition)) return null;
|
|
53
54
|
return aggregationKey(event, route);
|
|
54
55
|
}
|
|
55
56
|
//#endregion
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export declare function requireSubscription(id: string, workspaceId: string): import("./types.js").ScenarioSubscription;
|
|
2
|
+
export declare function templateCatalog(workspaceId?: string): {
|
|
3
|
+
parameterSchema: {
|
|
4
|
+
type: string;
|
|
5
|
+
properties: {
|
|
6
|
+
scanIntervalMinutes: {
|
|
7
|
+
type: string;
|
|
8
|
+
minimum: number;
|
|
9
|
+
maximum: number;
|
|
10
|
+
};
|
|
11
|
+
userInstructions: {
|
|
12
|
+
type: string;
|
|
13
|
+
maxLength: number;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
calendarSource?: {
|
|
18
|
+
status: string;
|
|
19
|
+
lastSyncedAt: string;
|
|
20
|
+
};
|
|
21
|
+
key: string;
|
|
22
|
+
version: number;
|
|
23
|
+
title: string;
|
|
24
|
+
description: string;
|
|
25
|
+
scopeKind: "project" | "workspace";
|
|
26
|
+
eventTypes: string[];
|
|
27
|
+
contextProviderIds: string[];
|
|
28
|
+
scheduled: boolean;
|
|
29
|
+
requiresCalendar: boolean;
|
|
30
|
+
}[];
|
|
31
|
+
export declare function controlledSubscriptions(workspaceId: string): {
|
|
32
|
+
effectiveLevel: "off" | "active" | "quiet" | "balanced";
|
|
33
|
+
schedule: Record<string, import("node:sqlite").SQLOutputValue>;
|
|
34
|
+
revision: number;
|
|
35
|
+
managed: boolean;
|
|
36
|
+
level: "off" | "active" | "quiet" | "balanced";
|
|
37
|
+
delivery: "digest" | "inbox" | "important";
|
|
38
|
+
preparationWorkflowId: string;
|
|
39
|
+
scanIntervalMinutes: number;
|
|
40
|
+
userInstructions: string;
|
|
41
|
+
id: string;
|
|
42
|
+
scenarioKey: string;
|
|
43
|
+
workspaceId: string;
|
|
44
|
+
scopeKind: "workspace" | "project";
|
|
45
|
+
scopeId: string;
|
|
46
|
+
enabled: boolean;
|
|
47
|
+
activePromptRevisionId?: string;
|
|
48
|
+
createdAt: string;
|
|
49
|
+
updatedAt: string;
|
|
50
|
+
}[];
|
|
51
|
+
export declare function createControlledSubscription(workspaceId: string, value: unknown): {
|
|
52
|
+
effectiveLevel: "off" | "active" | "quiet" | "balanced";
|
|
53
|
+
schedule: Record<string, import("node:sqlite").SQLOutputValue>;
|
|
54
|
+
revision: number;
|
|
55
|
+
managed: boolean;
|
|
56
|
+
level: "off" | "active" | "quiet" | "balanced";
|
|
57
|
+
delivery: "digest" | "inbox" | "important";
|
|
58
|
+
preparationWorkflowId: string;
|
|
59
|
+
scanIntervalMinutes: number;
|
|
60
|
+
userInstructions: string;
|
|
61
|
+
id: string;
|
|
62
|
+
scenarioKey: string;
|
|
63
|
+
workspaceId: string;
|
|
64
|
+
scopeKind: "workspace" | "project";
|
|
65
|
+
scopeId: string;
|
|
66
|
+
enabled: boolean;
|
|
67
|
+
activePromptRevisionId?: string;
|
|
68
|
+
createdAt: string;
|
|
69
|
+
updatedAt: string;
|
|
70
|
+
};
|
|
71
|
+
export declare function updateControlledSubscription(workspaceId: string, id: string, value: unknown): {
|
|
72
|
+
effectiveLevel: "off" | "active" | "quiet" | "balanced";
|
|
73
|
+
schedule: Record<string, import("node:sqlite").SQLOutputValue>;
|
|
74
|
+
revision: number;
|
|
75
|
+
managed: boolean;
|
|
76
|
+
level: "off" | "active" | "quiet" | "balanced";
|
|
77
|
+
delivery: "digest" | "inbox" | "important";
|
|
78
|
+
preparationWorkflowId: string;
|
|
79
|
+
scanIntervalMinutes: number;
|
|
80
|
+
userInstructions: string;
|
|
81
|
+
id: string;
|
|
82
|
+
scenarioKey: string;
|
|
83
|
+
workspaceId: string;
|
|
84
|
+
scopeKind: "workspace" | "project";
|
|
85
|
+
scopeId: string;
|
|
86
|
+
enabled: boolean;
|
|
87
|
+
activePromptRevisionId?: string;
|
|
88
|
+
createdAt: string;
|
|
89
|
+
updatedAt: string;
|
|
90
|
+
};
|