@xopcai/xopc 0.0.269 → 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.
Files changed (204) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/telegram/xopc.extension.json +1 -1
  3. package/dist/gateway/static/root/assets/{activity-store-DcwCSY6s.js → activity-store-D_0FmdRj.js} +1 -1
  4. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-D0NBOWC4.js → agent-avatar-dicebear-IZvEXak_.js} +1 -1
  5. package/dist/gateway/static/root/assets/agent-defaults-settings-panel-CE3ygr3l.js +1 -0
  6. package/dist/gateway/static/root/assets/agents-DzG2qw-c.js +1 -0
  7. package/dist/gateway/static/root/assets/{api-eaPO-mdr.js → api-O3TTQ4qN.js} +1 -1
  8. package/dist/gateway/static/root/assets/{app-management-settings-panel-E3xm2teY.js → app-management-settings-panel-Cqqsbzri.js} +1 -1
  9. package/dist/gateway/static/root/assets/{appearance-settings-CiZOjgls.js → appearance-settings-DaJyf32s.js} +1 -1
  10. package/dist/gateway/static/root/assets/apps-page-B4lX6yoT.js +1 -0
  11. package/dist/gateway/static/root/assets/{archive-plugin-D09yo_Hc.js → archive-plugin-kDPnDTJI.js} +1 -1
  12. package/dist/gateway/static/root/assets/{attachment-load-CNYb8Qru.js → attachment-load-CJ3VceKS.js} +1 -1
  13. package/dist/gateway/static/root/assets/{automations-page-DsrfW5ii.js → automations-page-JwMtU8XB.js} +1 -1
  14. package/dist/gateway/static/root/assets/automations-workspace-BiNBewg0.js +6 -0
  15. package/dist/gateway/static/root/assets/{binary-plugins-OWlaGqlY.js → binary-plugins-DzyZXLBX.js} +1 -1
  16. package/dist/gateway/static/root/assets/{block-editor-CVND3M6r.js → block-editor-kTt3gYIJ.js} +3 -3
  17. package/dist/gateway/static/root/assets/{browser-automation-inputs-D7IzVK_J.js → browser-automation-inputs-BKohPYR4.js} +1 -1
  18. package/dist/gateway/static/root/assets/{browser-automations-page-CQ6cCpIv.js → browser-automations-page-ktQfFPk-.js} +2 -2
  19. package/dist/gateway/static/root/assets/browser-settings-page-Dgdt5uVL.js +8 -0
  20. package/dist/gateway/static/root/assets/capabilities-settings-panel-BpDW-R9f.js +7 -0
  21. package/dist/gateway/static/root/assets/channels-settings-DU4EQnR0.js +1 -0
  22. package/dist/gateway/static/root/assets/{channels-status-swr-D4JrgJKa.js → channels-status-swr-DrqzYmDr.js} +1 -1
  23. package/dist/gateway/static/root/assets/{chat-terminal-dock-BUjnhT7C.js → chat-terminal-dock-CIFGGLGa.js} +2 -2
  24. package/dist/gateway/static/root/assets/connectors-page-CfJqy284.js +2 -0
  25. package/dist/gateway/static/root/assets/{cron-expression-DlyRIyUn.js → cron-expression-BNwbnrEb.js} +1 -1
  26. package/dist/gateway/static/root/assets/{date-picker-CU-ij_yE.js → date-picker-DKyirU4K.js} +1 -1
  27. package/dist/gateway/static/root/assets/{dependency-picker-Z6Nf0Wjq.js → dependency-picker-D3vNIjRI.js} +1 -1
  28. package/dist/gateway/static/root/assets/{desktop-pet-C0F5Iqnc.js → desktop-pet-fQwRb_3p.js} +1 -1
  29. package/dist/gateway/static/root/assets/{desktop-pet-settings-Bj_W6HL1.js → desktop-pet-settings-DfOJ-GvT.js} +1 -1
  30. package/dist/gateway/static/root/assets/{extension-debug-page-Ba09EnAn.js → extension-debug-page-Cn53jB5-.js} +1 -1
  31. package/dist/gateway/static/root/assets/{extension-page-C88aGXJs.js → extension-page-IDpLCxpC.js} +1 -1
  32. package/dist/gateway/static/root/assets/{extension-settings-page-CMyt0A32.js → extension-settings-page-B1dds2bP.js} +1 -1
  33. package/dist/gateway/static/root/assets/{gateway-config-swr-bjzk_4hn.js → gateway-config-swr-B7dcmP7Q.js} +1 -1
  34. package/dist/gateway/static/root/assets/gateway-settings-pp9jN0wl.js +1 -0
  35. package/dist/gateway/static/root/assets/{gateway-startup-retry-DrbIzFyB.js → gateway-startup-retry-JGhGMhVZ.js} +1 -1
  36. package/dist/gateway/static/root/assets/{heartbeat-settings-BnrMRKaZ.js → heartbeat-settings-B8_zTyev.js} +1 -1
  37. package/dist/gateway/static/root/assets/home-page-g1KdAdLb.js +1 -0
  38. package/dist/gateway/static/root/assets/{image-generation-api-CBWU9Ner.js → image-generation-api-BJLX7Qrh.js} +1 -1
  39. package/dist/gateway/static/root/assets/index-1zRt56Zt.css +2 -0
  40. package/dist/gateway/static/root/assets/{index-CfyyA9-g.js → index-OMnem2nr.js} +47 -47
  41. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-B8Z7kKQY.js → keyboard-shortcuts-settings-CBvMTFFx.js} +1 -1
  42. package/dist/gateway/static/root/assets/local-app-workbench-page-bj75v2ZE.js +2 -0
  43. package/dist/gateway/static/root/assets/{local-apps-page-CLSfzkRp.js → local-apps-page-APCMNDvU.js} +1 -1
  44. package/dist/gateway/static/root/assets/{locale-store-BQ2HKTWb.js → locale-store-BThkG0W5.js} +1 -1
  45. package/dist/gateway/static/root/assets/{logs-page-CWDU1AGN.js → logs-page-BX7n39zV.js} +2 -2
  46. package/dist/gateway/static/root/assets/{management-settings-CuFNgQUy.js → management-settings-CM62j8ZX.js} +1 -1
  47. package/dist/gateway/static/root/assets/{markdown-view-DIcfrLmG.js → markdown-view-BwjpJVPs.js} +1 -1
  48. package/dist/gateway/static/root/assets/{media-plugins-CSv6-xTA.js → media-plugins-C10byeaa.js} +1 -1
  49. package/dist/gateway/static/root/assets/{messages-BRjoarIr.js → messages-D0V_bgRP.js} +2 -2
  50. package/dist/gateway/static/root/assets/{mobile-pairing-wizard-C7d_ztgp.js → mobile-pairing-wizard-BCWfwVx3.js} +1 -1
  51. package/dist/gateway/static/root/assets/note-detail-panel-koOqb0gX.js +5 -0
  52. package/dist/gateway/static/root/assets/notes-page-BH54rY3B.js +8 -0
  53. package/dist/gateway/static/root/assets/{page-tabs-BfV-PQa5.js → page-tabs-qQ36zncF.js} +1 -1
  54. package/dist/gateway/static/root/assets/{preview-open-alternatives-DQS7NFpg.js → preview-open-alternatives-BiAJvvCo.js} +1 -1
  55. package/dist/gateway/static/root/assets/proactive-card-CcNBqHoH.js +1 -0
  56. package/dist/gateway/static/root/assets/proactive-page-C3K61jve.js +1 -0
  57. package/dist/gateway/static/root/assets/{product-open-page-BIhjZLKg.js → product-open-page-CunnDP4w.js} +1 -1
  58. package/dist/gateway/static/root/assets/project-detail-page-SL2g8MZY.js +1 -0
  59. package/dist/gateway/static/root/assets/projects-page-BtxazT5J.js +1 -0
  60. package/dist/gateway/static/root/assets/{registry-api-CwveUfe8.js → registry-api-0JSegbNl.js} +1 -1
  61. package/dist/gateway/static/root/assets/remote-access-hub-BSb7b67A.js +1 -0
  62. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-DMQ0wpNg.js → runtime-tools-settings-panel-bu_mwBTr.js} +2 -2
  63. package/dist/gateway/static/root/assets/{schema-form-C91g2PRU.js → schema-form-BE0sgZyz.js} +1 -1
  64. package/dist/gateway/static/root/assets/sessions-page-ljcS2KVz.js +1 -0
  65. package/dist/gateway/static/root/assets/settings-advanced-gate-CR9Y9LQm.js +1 -0
  66. package/dist/gateway/static/root/assets/{settings-form-section-Cem6L49Y.js → settings-form-section-CBo517lV.js} +1 -1
  67. package/dist/gateway/static/root/assets/{settings-page-afDUxqlV.js → settings-page-DBPimx93.js} +1 -1
  68. package/dist/gateway/static/root/assets/{setup-status-panel-Cc_iP_vv.js → setup-status-panel-J3AnooPW.js} +1 -1
  69. package/dist/gateway/static/root/assets/{share-preview-page-CVEVHksg.js → share-preview-page-DdV75VTe.js} +1 -1
  70. package/dist/gateway/static/root/assets/shares-settings-AAm4Iwcp.js +1 -0
  71. package/dist/gateway/static/root/assets/skill-reload-api-pRCjP5sm.js +1 -0
  72. package/dist/gateway/static/root/assets/{skills-page-CNrq1W6C.js → skills-page-Cn_-nvRo.js} +1 -1
  73. package/dist/gateway/static/root/assets/{skills-page.utils-D1wGfm22.js → skills-page.utils-CfVAc_lu.js} +1 -1
  74. package/dist/gateway/static/root/assets/{system-settings-panel-BLE8PxWE.js → system-settings-panel-B-pzalp-.js} +1 -1
  75. package/dist/gateway/static/root/assets/task-detail-page-CmrLy3PO.js +1 -0
  76. package/dist/gateway/static/root/assets/{theme-store-BejLVjIT.js → theme-store-1wryLgBQ.js} +1 -1
  77. package/dist/gateway/static/root/assets/{tunnel-api-Dur80zSQ.js → tunnel-api-Cgpnm2fL.js} +1 -1
  78. package/dist/gateway/static/root/assets/{use-autosave-DmgEA8Qb.js → use-autosave-N_lm4FdD.js} +1 -1
  79. package/dist/gateway/static/root/assets/user-model-page-u3QLDX6L.js +2 -0
  80. package/dist/gateway/static/root/assets/{voice-api-key-field-sZ0J83SF.js → voice-api-key-field-DMsyVfri.js} +1 -1
  81. package/dist/gateway/static/root/assets/{voice-config-api-BEbz3avx.js → voice-config-api-QWVmiAp3.js} +1 -1
  82. package/dist/gateway/static/root/assets/{work-discovery-overlay-BXnAlbmO.js → work-discovery-overlay-BC0vAxp1.js} +1 -1
  83. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-BkwFey0a.js → workflow-run-setup-panel-Cz9jQIlP.js} +2 -2
  84. package/dist/gateway/static/root/assets/workflows-page-DA6OOK6X.js +6 -0
  85. package/dist/gateway/static/root/index.html +10 -10
  86. package/dist/package.js +1 -1
  87. package/dist/packages/gateway-contract/src/notifications.js +2 -2
  88. package/dist/packages/gateway-contract/src/proactive.js +22 -6
  89. package/dist/src/agent/tool-manuals/xopc-use.d.ts +1 -1
  90. package/dist/src/agent/tool-manuals/xopc-use.js +19 -1
  91. package/dist/src/agent/tools/factory.js +1 -0
  92. package/dist/src/agent/tools/xopc-use-tool.d.ts +3 -2
  93. package/dist/src/agent/tools/xopc-use-tool.js +48 -4
  94. package/dist/src/browser/automations/schema.d.ts +1 -1
  95. package/dist/src/browser/automations/service.d.ts +1 -1
  96. package/dist/src/cli/bin.js +2 -2
  97. package/dist/src/cli/commands/agent.js +2 -2
  98. package/dist/src/cli/commands/auth.js +1 -1
  99. package/dist/src/cli/commands/connectors.js +1 -1
  100. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  101. package/dist/src/cli/commands/extension-dev.js +1 -1
  102. package/dist/src/cli/commands/extension-marketplace.js +1 -1
  103. package/dist/src/cli/commands/extension.js +1 -1
  104. package/dist/src/cli/commands/gateway/lifecycle.js +1 -1
  105. package/dist/src/cli/commands/gateway-ssh-tunnel.js +1 -1
  106. package/dist/src/cli/commands/models.js +2 -2
  107. package/dist/src/cli/commands/onboard.js +1 -1
  108. package/dist/src/cli/commands/platform.js +1 -1
  109. package/dist/src/cli/commands/profile.js +1 -1
  110. package/dist/src/cli/commands/tunnel.js +1 -1
  111. package/dist/src/cli/commands/update.js +1 -1
  112. package/dist/src/cli/extension-cli-register.js +1 -1
  113. package/dist/src/config/index.js +1 -1
  114. package/dist/src/connectors/connected-source-ingestion.js +1 -1
  115. package/dist/src/connectors/connected-source-normalizers.js +3 -0
  116. package/dist/src/connectors/connection-recovery-service.js +1 -1
  117. package/dist/src/connectors/health.js +1 -1
  118. package/dist/src/gateway/hono/routes/capabilities.js +1 -1
  119. package/dist/src/gateway/hono/routes/connectors.js +1 -1
  120. package/dist/src/gateway/hono/routes/mcp.js +1 -1
  121. package/dist/src/gateway/hono/routes/proactive-control.js +31 -24
  122. package/dist/src/gateway/run-loop.js +1 -1
  123. package/dist/src/gateway/server.js +1 -1
  124. package/dist/src/gateway/service.js +10 -10
  125. package/dist/src/notifications/web-push.js +1 -1
  126. package/dist/src/proactive/actions/service.js +4 -2
  127. package/dist/src/proactive/execution/authorization.d.ts +2 -0
  128. package/dist/src/proactive/execution/authorization.js +39 -1
  129. package/dist/src/proactive/execution/context.d.ts +4 -0
  130. package/dist/src/proactive/execution/context.js +51 -5
  131. package/dist/src/proactive/execution/insight.js +3 -1
  132. package/dist/src/proactive/execution/repository.js +15 -5
  133. package/dist/src/proactive/execution/types.d.ts +2 -0
  134. package/dist/src/proactive/execution/worker.js +7 -2
  135. package/dist/src/proactive/experience.d.ts +69 -0
  136. package/dist/src/proactive/experience.js +90 -0
  137. package/dist/src/proactive/follow-ups.d.ts +123 -0
  138. package/dist/src/proactive/follow-ups.js +169 -0
  139. package/dist/src/proactive/inbox/cards.js +35 -6
  140. package/dist/src/proactive/inbox/delivery.js +4 -2
  141. package/dist/src/proactive/inbox/digest.js +2 -2
  142. package/dist/src/proactive/inbox/lifecycle.js +4 -1
  143. package/dist/src/proactive/inbox/notification-policy.js +4 -2
  144. package/dist/src/proactive/inbox/repository.js +17 -11
  145. package/dist/src/proactive/inbox/types.d.ts +2 -0
  146. package/dist/src/proactive/inbox/worker.js +1 -1
  147. package/dist/src/proactive/index.js +2 -2
  148. package/dist/src/proactive/mail-thread.d.ts +13 -0
  149. package/dist/src/proactive/mail-thread.js +54 -0
  150. package/dist/src/proactive/policy/service.d.ts +2 -4
  151. package/dist/src/proactive/policy/service.js +3 -5
  152. package/dist/src/proactive/scenarios/control.d.ts +4 -7
  153. package/dist/src/proactive/scenarios/control.js +3 -4
  154. package/dist/src/proactive/scenarios/preview.js +1 -1
  155. package/dist/src/proactive/scenarios/prompt-composer.js +2 -0
  156. package/dist/src/proactive/service.js +1 -1
  157. package/dist/src/proactive/temporal/worker.d.ts +1 -0
  158. package/dist/src/proactive/temporal/worker.js +18 -4
  159. package/dist/src/storage/sqlite/migrations/162_proactive_product.sql +7 -0
  160. package/dist/src/storage/sqlite/migrations/163_proactive_follow_ups.sql +29 -0
  161. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  162. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  163. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  164. package/dist/src/storage/sqlite/schema.js +1 -1
  165. package/dist/src/tasks/home-query-service.js +1 -1
  166. package/dist/src/tasks/index.js +4 -4
  167. package/dist/src/tui/tui-startup-resources.js +1 -1
  168. package/dist/src/voice/dashscope/streaming-stt-session.js +1 -0
  169. package/dist/src/voice/dashscope/streaming-tts-stream.d.ts +1 -0
  170. package/dist/src/voice/dashscope/streaming-tts-stream.js +8 -0
  171. package/dist/src/voice/realtime/agentBroker.js +1 -1
  172. package/dist/src/voice/realtime/agentEngine.js +1 -1
  173. package/dist/src/voice/realtime/pcm-output-gain.d.ts +2 -0
  174. package/dist/src/voice/realtime/pcm-output-gain.js +16 -0
  175. package/dist/src/voice/realtime/runtime.js +4 -2
  176. package/dist/src/workflows/service/workflow-run-service.js +1 -1
  177. package/package.json +2 -2
  178. package/dist/gateway/static/root/assets/agent-defaults-settings-panel-qKCezlBg.js +0 -1
  179. package/dist/gateway/static/root/assets/agents-BMhnZKhP.js +0 -1
  180. package/dist/gateway/static/root/assets/apps-page-DduJDWGY.js +0 -1
  181. package/dist/gateway/static/root/assets/automations-workspace-X7hNHCjW.js +0 -6
  182. package/dist/gateway/static/root/assets/browser-settings-page-BZ7nK_pN.js +0 -8
  183. package/dist/gateway/static/root/assets/capabilities-settings-panel-BfKvJO1C.js +0 -7
  184. package/dist/gateway/static/root/assets/channels-settings-D1UDxJuA.js +0 -1
  185. package/dist/gateway/static/root/assets/connectors-page-C2nuqs_O.js +0 -2
  186. package/dist/gateway/static/root/assets/gateway-settings-ogv8ZbvG.js +0 -1
  187. package/dist/gateway/static/root/assets/home-page-CbcJCREd.js +0 -1
  188. package/dist/gateway/static/root/assets/index-7PZ6jmhy.css +0 -2
  189. package/dist/gateway/static/root/assets/local-app-workbench-page-D5XVZnK1.js +0 -2
  190. package/dist/gateway/static/root/assets/note-detail-panel-D5JuBLyt.js +0 -5
  191. package/dist/gateway/static/root/assets/notes-page-C5-ccCi4.js +0 -8
  192. package/dist/gateway/static/root/assets/proactive-page-fHjPigmx.js +0 -1
  193. package/dist/gateway/static/root/assets/project-detail-page-6v_khvEt.js +0 -1
  194. package/dist/gateway/static/root/assets/projects-page-CCy1aK4V.js +0 -1
  195. package/dist/gateway/static/root/assets/remote-access-hub-pkqKRnch.js +0 -1
  196. package/dist/gateway/static/root/assets/sessions-page-Cr4E4cap.js +0 -1
  197. package/dist/gateway/static/root/assets/settings-advanced-gate-D1JRDRyL.js +0 -1
  198. package/dist/gateway/static/root/assets/shares-settings-BrMECMAB.js +0 -1
  199. package/dist/gateway/static/root/assets/skill-reload-api-DuqIDYjc.js +0 -1
  200. package/dist/gateway/static/root/assets/task-detail-page-m0oXUKl2.js +0 -1
  201. package/dist/gateway/static/root/assets/user-model-page-Dl10D_5q.js +0 -2
  202. package/dist/gateway/static/root/assets/workflows-page-CswgoIFU.js +0 -6
  203. package/dist/src/proactive/actions/workflow.d.ts +0 -15
  204. package/dist/src/proactive/actions/workflow.js +0 -88
@@ -0,0 +1,90 @@
1
+ import { getSqliteDatabase, runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
2
+ import { ProactiveConflict, effectiveProactivePolicy } from "./policy/service.js";
3
+ import { controlledSubscriptions, createControlledSubscription, requireSubscription, updateControlledSubscription } from "./scenarios/control.js";
4
+ import { ProactiveScenarioService } from "./scenarios/service.js";
5
+ import { ProactiveEventService } from "./service.js";
6
+ import { listMailFollowUps } from "./follow-ups.js";
7
+ import { getCard } from "./inbox/cards.js";
8
+ import { scanDueProjects } from "./temporal/schedule.js";
9
+ import { z } from "zod";
10
+ //#region src/proactive/experience.ts
11
+ const DelegateSchema = z.object({
12
+ scenarioKey: z.enum([
13
+ "project_delivery_risk",
14
+ "meeting_preparation",
15
+ "discussion_follow_up"
16
+ ]),
17
+ projectId: z.string().min(1).optional(),
18
+ instructions: z.string().trim().min(1).max(12e3)
19
+ }).strict();
20
+ function startDelegation(workspace, value) {
21
+ const input = DelegateSchema.parse(value);
22
+ if (input.scenarioKey === "project_delivery_risk" && !input.projectId) throw new Error("Choose a project");
23
+ if (input.scenarioKey !== "project_delivery_risk" && input.projectId) throw new Error("This service uses workspace sources");
24
+ const subscription = createControlledSubscription(workspace, {
25
+ scenarioKey: input.scenarioKey,
26
+ scopeKind: input.projectId ? "project" : "workspace",
27
+ scopeId: input.projectId ?? workspace,
28
+ userInstructions: input.instructions
29
+ });
30
+ if (input.projectId && effectiveProactivePolicy(subscription.id).enabled) checkDelegation(workspace, subscription.id);
31
+ return subscription;
32
+ }
33
+ /** Queue work in the existing worker; HTTP and conversation requests do not own model runs. */
34
+ function checkDelegation(workspace, id) {
35
+ const sub = requireSubscription(id, workspace);
36
+ if (!effectiveProactivePolicy(id).enabled) throw new Error("Resume this service before checking");
37
+ if (sub.scopeKind !== "project") throw new Error("This service checks when connected sources change");
38
+ return runSqliteWriteTransaction((db) => {
39
+ if (db.prepare("SELECT 1 FROM proactive_signal_batches WHERE subscription_id = ? AND status IN ('collecting', 'ready', 'processing')").get(id)) return { status: "queued" };
40
+ const last = db.prepare("SELECT MAX(started_at) AS at FROM proactive_runs WHERE subscription_id = ?").get(id);
41
+ if (last.at && Date.parse(last.at) > Date.now() - 6e4) throw new ProactiveConflict("A check just finished; try again in a minute");
42
+ db.prepare(`INSERT INTO proactive_schedule_state(subscription_id, next_due_at) VALUES (?, ?)
43
+ ON CONFLICT(subscription_id) DO UPDATE SET next_due_at = excluded.next_due_at, last_fingerprint = NULL`).run(id, (/* @__PURE__ */ new Date()).toISOString());
44
+ scanDueProjects(new ProactiveEventService(() => new ProactiveScenarioService().routes()));
45
+ db.prepare("UPDATE proactive_signal_batches SET ready_at = ?, status = 'ready' WHERE subscription_id = ? AND status = 'collecting'").run((/* @__PURE__ */ new Date()).toISOString(), id);
46
+ return { status: "queued" };
47
+ });
48
+ }
49
+ function delegationOverview(workspace) {
50
+ const db = getSqliteDatabase();
51
+ const now = (/* @__PURE__ */ new Date()).toISOString();
52
+ const cards = db.prepare(`SELECT i.inbox_item_id FROM proactive_inbox_items i
53
+ JOIN proactive_insights x USING(insight_id) JOIN proactive_scenario_subscriptions s USING(subscription_id)
54
+ WHERE s.workspace_id = ? AND i.withdrawn_at IS NULL AND i.status IN ('unread', 'read')
55
+ AND (i.expires_at IS NULL OR i.expires_at > ?)
56
+ ORDER BY CASE WHEN x.action_status = 'approval_required' THEN 0 WHEN x.artifact_json IS NOT NULL THEN 1 ELSE 2 END,
57
+ x.value_score DESC, i.updated_at DESC LIMIT 100`).all(workspace, now).map((row) => getCard(row.inbox_item_id, workspace)).filter((card) => !["withdrawn", "expired"].includes(card.status));
58
+ return {
59
+ followUps: listMailFollowUps(workspace),
60
+ needsDecision: cards.filter((card) => card.decision && card.actionStatus !== "completed"),
61
+ prepared: cards.filter((card) => card.artifact && !card.decision),
62
+ updates: cards.filter((card) => !card.artifact && !card.decision),
63
+ delegations: controlledSubscriptions(workspace).map((sub) => ({
64
+ ...sub,
65
+ effectiveEnabled: effectiveProactivePolicy(sub.id).enabled,
66
+ project: sub.scopeKind === "project" ? db.prepare("SELECT name, status FROM projects WHERE project_id = ?").get(sub.scopeId) ?? null : null,
67
+ latestRun: db.prepare(`SELECT status, outcome_reason AS reason, completed_at AS completedAt, started_at AS startedAt, error_message AS error
68
+ FROM proactive_runs WHERE subscription_id = ? ORDER BY started_at DESC LIMIT 1`).get(sub.id) ?? null,
69
+ pending: Boolean(db.prepare("SELECT 1 FROM proactive_signal_batches WHERE subscription_id = ? AND status IN ('collecting', 'ready', 'processing')").get(sub.id))
70
+ }))
71
+ };
72
+ }
73
+ function completeDeliveredProjects(now = /* @__PURE__ */ new Date()) {
74
+ const db = getSqliteDatabase();
75
+ const rows = db.prepare(`SELECT s.subscription_id, s.workspace_id FROM proactive_scenario_subscriptions s
76
+ JOIN projects p ON p.project_id = s.scope_id WHERE s.scope_kind = 'project' AND s.enabled = 1
77
+ AND p.status IN ('completed', 'cancelled', 'archived')`).all();
78
+ for (const row of rows) {
79
+ const current = controlledSubscriptions(row.workspace_id).find((sub) => sub.id === row.subscription_id);
80
+ updateControlledSubscription(row.workspace_id, row.subscription_id, {
81
+ expectedRevision: current.revision,
82
+ enabled: false,
83
+ completedAt: now.toISOString()
84
+ });
85
+ db.prepare(`UPDATE proactive_inbox_items SET status = 'resolved', resolution = 'project_ended', updated_at = ?
86
+ WHERE insight_id IN (SELECT insight_id FROM proactive_insights WHERE subscription_id = ?) AND status <> 'resolved'`).run(now.toISOString(), row.subscription_id);
87
+ }
88
+ }
89
+ //#endregion
90
+ export { checkDelegation, completeDeliveredProjects, delegationOverview, startDelegation };
@@ -0,0 +1,123 @@
1
+ import { ProactiveEventService } from './service.js';
2
+ export declare const COMMUNICATION_SCENARIO = "communication_follow_up";
3
+ export type MailFollowUp = {
4
+ id: string;
5
+ subscription_id: string;
6
+ workspace_id: string;
7
+ source_item_id: string;
8
+ instructions: string;
9
+ due_at: string;
10
+ status: 'watching' | 'paused' | 'completed';
11
+ revision: number;
12
+ last_fingerprint: string | null;
13
+ last_checked_at: string | null;
14
+ session_key: string | null;
15
+ };
16
+ export declare function requireMailFollowUp(workspace: string, id: string): MailFollowUp;
17
+ export declare function authorizedMailThread(follow: MailFollowUp): {
18
+ origin: import("../knowledge/types.js").KnowledgeSourceItem;
19
+ items: import("../knowledge/types.js").KnowledgeSourceItem[];
20
+ threadKey: string;
21
+ fingerprint: string;
22
+ };
23
+ export declare function mailFollowUpView(follow: MailFollowUp): {
24
+ id: string;
25
+ subscriptionId: string;
26
+ instructions: string;
27
+ dueAt: string;
28
+ status: "completed" | "paused" | "watching";
29
+ revision: number;
30
+ lastCheckedAt: string;
31
+ sessionKey: string;
32
+ sourceAvailable: boolean;
33
+ enabled: boolean;
34
+ subject: string;
35
+ latestMessageAt: string;
36
+ latestDirection: string;
37
+ lastSyncedAt: string;
38
+ syncFailed: boolean;
39
+ };
40
+ export declare function listMailFollowUps(workspace: string): {
41
+ id: string;
42
+ subscriptionId: string;
43
+ instructions: string;
44
+ dueAt: string;
45
+ status: "completed" | "paused" | "watching";
46
+ revision: number;
47
+ lastCheckedAt: string;
48
+ sessionKey: string;
49
+ sourceAvailable: boolean;
50
+ enabled: boolean;
51
+ subject: string;
52
+ latestMessageAt: string;
53
+ latestDirection: string;
54
+ lastSyncedAt: string;
55
+ syncFailed: boolean;
56
+ }[];
57
+ export declare function mailFollowUpSources(workspace: string): {
58
+ id: string;
59
+ subject: string;
60
+ sender: string;
61
+ occurredAt: string;
62
+ accountLabel: string;
63
+ }[];
64
+ export declare function startMailFollowUp(workspace: string, value: unknown): {
65
+ id: string;
66
+ subscriptionId: string;
67
+ instructions: string;
68
+ dueAt: string;
69
+ status: "completed" | "paused" | "watching";
70
+ revision: number;
71
+ lastCheckedAt: string;
72
+ sessionKey: string;
73
+ sourceAvailable: boolean;
74
+ enabled: boolean;
75
+ subject: string;
76
+ latestMessageAt: string;
77
+ latestDirection: string;
78
+ lastSyncedAt: string;
79
+ syncFailed: boolean;
80
+ };
81
+ export declare function updateMailFollowUp(workspace: string, id: string, value: unknown): {
82
+ id: string;
83
+ subscriptionId: string;
84
+ instructions: string;
85
+ dueAt: string;
86
+ status: "completed" | "paused" | "watching";
87
+ revision: number;
88
+ lastCheckedAt: string;
89
+ sessionKey: string;
90
+ sourceAvailable: boolean;
91
+ enabled: boolean;
92
+ subject: string;
93
+ latestMessageAt: string;
94
+ latestDirection: string;
95
+ lastSyncedAt: string;
96
+ syncFailed: boolean;
97
+ };
98
+ /** Keep the existing chat and its exact connector confirmation flow as the write boundary. */
99
+ export declare function continueMailFollowUp(workspace: string, id: string, sessionKey: string): {
100
+ followUp: {
101
+ id: string;
102
+ subscriptionId: string;
103
+ instructions: string;
104
+ dueAt: string;
105
+ status: "completed" | "paused" | "watching";
106
+ revision: number;
107
+ lastCheckedAt: string;
108
+ sessionKey: string;
109
+ sourceAvailable: boolean;
110
+ enabled: boolean;
111
+ subject: string;
112
+ latestMessageAt: string;
113
+ latestDirection: string;
114
+ lastSyncedAt: string;
115
+ syncFailed: boolean;
116
+ };
117
+ connectionId: unknown;
118
+ sourceItemId: string;
119
+ threadId: string;
120
+ instruction: string;
121
+ };
122
+ /** Poll only delegated threads; unchanged threads and passed deadlines produce no repeat signals. */
123
+ export declare function scanMailFollowUps(events: ProactiveEventService, now?: Date): number;
@@ -0,0 +1,169 @@
1
+ import { getSqliteDatabase, runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
2
+ import { listKnowledgeSourceItems } from "../storage/sqlite/knowledge-repository.js";
3
+ import { getConnectorConnection } from "../storage/sqlite/connector-repository.js";
4
+ import { emailFields, readMailThread } from "./mail-thread.js";
5
+ import { authorizedConnectedSource } from "./execution/authorization.js";
6
+ import { ProactiveConflict, effectiveProactivePolicy } from "./policy/service.js";
7
+ import { listSubscriptions } from "./scenarios/repository.js";
8
+ import { createControlledSubscription } from "./scenarios/control.js";
9
+ import { ProactiveScenarioService } from "./scenarios/service.js";
10
+ import { ProactiveEventService } from "./service.js";
11
+ import { randomUUID } from "node:crypto";
12
+ import { z } from "zod";
13
+ //#region src/proactive/follow-ups.ts
14
+ const COMMUNICATION_SCENARIO = "communication_follow_up";
15
+ const CreateSchema = z.object({
16
+ sourceItemId: z.string().min(1),
17
+ instructions: z.string().trim().min(1).max(12e3),
18
+ dueAt: z.string().datetime()
19
+ }).strict();
20
+ const UpdateSchema = z.object({
21
+ expectedRevision: z.number().int().positive(),
22
+ instructions: z.string().trim().min(1).max(12e3).optional(),
23
+ dueAt: z.string().datetime().optional(),
24
+ status: z.enum([
25
+ "watching",
26
+ "paused",
27
+ "completed"
28
+ ]).optional()
29
+ }).strict();
30
+ function requireMailFollowUp(workspace, id) {
31
+ const row = getSqliteDatabase().prepare("SELECT * FROM proactive_follow_ups WHERE id = ? AND workspace_id = ?").get(id, workspace);
32
+ if (!row) throw new Error("Follow-up not found");
33
+ return row;
34
+ }
35
+ function authorizedMailThread(follow) {
36
+ if (!authorizedConnectedSource(follow.source_item_id, follow.workspace_id, "communication_follow_up")) return null;
37
+ const thread = readMailThread(follow.source_item_id);
38
+ if (!thread || thread.items.some((item) => !authorizedConnectedSource(item.id, follow.workspace_id, "communication_follow_up") || item.metadata.connectionId !== thread.origin.metadata.connectionId)) return null;
39
+ return thread;
40
+ }
41
+ function mailFollowUpView(follow) {
42
+ const thread = authorizedMailThread(follow);
43
+ const latest = thread?.items.at(-1);
44
+ const sync = thread ? getSqliteDatabase().prepare("SELECT status, error, finished_at FROM knowledge_sync_runs WHERE source_instance_id = ? ORDER BY started_at DESC LIMIT 1").get(thread.origin.sourceInstanceId) : void 0;
45
+ return {
46
+ id: follow.id,
47
+ subscriptionId: follow.subscription_id,
48
+ instructions: follow.instructions,
49
+ dueAt: follow.due_at,
50
+ status: follow.status,
51
+ revision: follow.revision,
52
+ lastCheckedAt: follow.last_checked_at,
53
+ sessionKey: follow.session_key,
54
+ sourceAvailable: Boolean(thread),
55
+ enabled: effectiveProactivePolicy(follow.subscription_id).enabled,
56
+ subject: thread ? emailFields(thread.origin.normalizedText).subject ?? "Email follow-up" : null,
57
+ latestMessageAt: latest?.occurredAt ?? null,
58
+ latestDirection: latest ? emailFields(latest.normalizedText).labels?.includes("SENT") ? "sent" : emailFields(latest.normalizedText).labels?.includes("INBOX") ? "received" : "unknown" : null,
59
+ lastSyncedAt: sync?.finished_at ? new Date(sync.finished_at).toISOString() : null,
60
+ syncFailed: Boolean(sync?.error)
61
+ };
62
+ }
63
+ function listMailFollowUps(workspace) {
64
+ return getSqliteDatabase().prepare("SELECT * FROM proactive_follow_ups WHERE workspace_id = ? ORDER BY updated_at DESC").all(workspace).map(mailFollowUpView);
65
+ }
66
+ function mailFollowUpSources(workspace) {
67
+ const seen = /* @__PURE__ */ new Set();
68
+ return listKnowledgeSourceItems({
69
+ itemType: "email",
70
+ limit: 500
71
+ }).flatMap((item) => {
72
+ if (!authorizedConnectedSource(item.id, workspace, "communication_follow_up")) return [];
73
+ const thread = readMailThread(item.id);
74
+ if (!thread || seen.has(thread.threadKey)) return [];
75
+ seen.add(thread.threadKey);
76
+ const fields = emailFields(item.normalizedText);
77
+ const connection = getConnectorConnection(String(item.metadata.connectionId));
78
+ const account = connection?.alias ?? connection?.identity.email ?? connection?.identity.name;
79
+ return [{
80
+ id: item.id,
81
+ subject: fields.subject ?? "Email",
82
+ sender: fields.sender ?? "",
83
+ occurredAt: item.occurredAt,
84
+ accountLabel: typeof account === "string" ? account : "Connected email account"
85
+ }];
86
+ }).slice(0, 100);
87
+ }
88
+ function startMailFollowUp(workspace, value) {
89
+ const input = CreateSchema.parse(value);
90
+ if (Date.parse(input.dueAt) <= Date.now()) throw new Error("Choose a future follow-up time");
91
+ if (!authorizedConnectedSource(input.sourceItemId, workspace, "communication_follow_up")) throw new Error("Authorize this email account for proactive follow-up first");
92
+ const thread = readMailThread(input.sourceItemId);
93
+ if (!thread) throw new Error("This email has no available thread");
94
+ return runSqliteWriteTransaction((db) => {
95
+ if (db.prepare("SELECT 1 FROM proactive_follow_ups WHERE workspace_id = ? AND thread_key = ?").get(workspace, thread.threadKey)) throw new ProactiveConflict("This conversation is already delegated; update the existing follow-up");
96
+ const sub = listSubscriptions("communication_follow_up").find((item) => item.workspaceId === workspace) ?? createControlledSubscription(workspace, {
97
+ scenarioKey: "communication_follow_up",
98
+ scopeKind: "workspace",
99
+ scopeId: workspace
100
+ });
101
+ const id = randomUUID();
102
+ const now = (/* @__PURE__ */ new Date()).toISOString();
103
+ db.prepare(`INSERT INTO proactive_follow_ups(id, subscription_id, workspace_id, source_item_id, thread_key, instructions, due_at, created_at, updated_at)
104
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, sub.id, workspace, input.sourceItemId, thread.threadKey, input.instructions, input.dueAt, now, now);
105
+ scanMailFollowUps(new ProactiveEventService(() => new ProactiveScenarioService().routes()));
106
+ return mailFollowUpView(requireMailFollowUp(workspace, id));
107
+ });
108
+ }
109
+ function updateMailFollowUp(workspace, id, value) {
110
+ const input = UpdateSchema.parse(value);
111
+ return runSqliteWriteTransaction((db) => {
112
+ const row = requireMailFollowUp(workspace, id);
113
+ if (row.revision !== input.expectedRevision) throw new ProactiveConflict("Follow-up changed; refresh before editing");
114
+ if (input.dueAt && Date.parse(input.dueAt) <= Date.now()) throw new Error("Choose a future follow-up time");
115
+ db.prepare(`UPDATE proactive_follow_ups SET instructions = ?, due_at = ?, status = ?, revision = revision + 1,
116
+ last_fingerprint = NULL, updated_at = ? WHERE id = ?`).run(input.instructions ?? row.instructions, input.dueAt ?? row.due_at, input.status ?? row.status, (/* @__PURE__ */ new Date()).toISOString(), id);
117
+ scanMailFollowUps(new ProactiveEventService(() => new ProactiveScenarioService().routes()));
118
+ return mailFollowUpView(requireMailFollowUp(workspace, id));
119
+ });
120
+ }
121
+ /** Keep the existing chat and its exact connector confirmation flow as the write boundary. */
122
+ function continueMailFollowUp(workspace, id, sessionKey) {
123
+ const follow = requireMailFollowUp(workspace, id);
124
+ const thread = authorizedMailThread(follow);
125
+ if (!thread || follow.status !== "watching" || !effectiveProactivePolicy(follow.subscription_id).enabled) throw new Error("Resume and authorize this follow-up before continuing");
126
+ getSqliteDatabase().prepare("UPDATE proactive_follow_ups SET session_key = ? WHERE id = ?").run(sessionKey, id);
127
+ return {
128
+ followUp: mailFollowUpView(requireMailFollowUp(workspace, id)),
129
+ connectionId: thread.origin.metadata.connectionId,
130
+ sourceItemId: thread.origin.id,
131
+ threadId: emailFields(thread.origin.normalizedText).threadId,
132
+ instruction: "Read the current card and latest thread using this exact connection. Review the recipients and complete draft with the user before sending through the connector approval flow. Never infer successful delivery from a draft or approval. Use provider results, then continue watching synchronized replies."
133
+ };
134
+ }
135
+ /** Poll only delegated threads; unchanged threads and passed deadlines produce no repeat signals. */
136
+ function scanMailFollowUps(events, now = /* @__PURE__ */ new Date()) {
137
+ const db = getSqliteDatabase();
138
+ let published = 0;
139
+ for (const follow of db.prepare("SELECT * FROM proactive_follow_ups WHERE status = 'watching'").all()) {
140
+ if (!effectiveProactivePolicy(follow.subscription_id, now).enabled) continue;
141
+ const thread = authorizedMailThread(follow);
142
+ if (!thread) continue;
143
+ const fingerprint = `${follow.revision}:${thread.fingerprint}:${Date.parse(follow.due_at) <= now.getTime() ? "due" : "waiting"}`;
144
+ if (fingerprint !== follow.last_fingerprint) {
145
+ if (events.publish({
146
+ type: "proactive.follow_up.v1",
147
+ schemaVersion: 1,
148
+ source: {
149
+ kind: "connector",
150
+ id: String(thread.origin.metadata.connectionId)
151
+ },
152
+ subject: {
153
+ kind: "mail_follow_up",
154
+ id: follow.id
155
+ },
156
+ actor: { kind: "system" },
157
+ scope: { workspaceId: follow.workspace_id },
158
+ occurredAt: now.toISOString(),
159
+ dedupeKey: `mail-follow-up:${follow.id}:${fingerprint}`,
160
+ sensitivity: "personal",
161
+ payload: { followUpId: follow.id }
162
+ }, now).inserted) published++;
163
+ }
164
+ db.prepare("UPDATE proactive_follow_ups SET last_fingerprint = ?, last_checked_at = ? WHERE id = ?").run(fingerprint, now.toISOString(), follow.id);
165
+ }
166
+ return published;
167
+ }
168
+ //#endregion
169
+ export { COMMUNICATION_SCENARIO, authorizedMailThread, continueMailFollowUp, listMailFollowUps, mailFollowUpSources, mailFollowUpView, requireMailFollowUp, scanMailFollowUps, startMailFollowUp, updateMailFollowUp };
@@ -1,11 +1,11 @@
1
1
  import { getSqliteDatabase, runSqliteWriteTransaction } from "../../storage/sqlite/transaction.js";
2
2
  import { ProactiveCardActionSchema } from "../../../packages/gateway-contract/src/proactive.js";
3
3
  import { getKnowledgeSourceItem } from "../../storage/sqlite/knowledge-repository.js";
4
+ import { insightSourcesAuthorized, insightSourcesChanged } from "../execution/authorization.js";
4
5
  import { ProactiveConflict, effectiveProactivePolicy, subscriptionSettings } from "../policy/service.js";
5
- import { insightSourcesAuthorized } from "../execution/authorization.js";
6
+ import { updateControlledSubscription } from "../scenarios/control.js";
6
7
  import { withdrawCard } from "./lifecycle.js";
7
8
  import { executePendingProactiveActions } from "../actions/service.js";
8
- import { updateControlledSubscription } from "../scenarios/control.js";
9
9
  import { getInboxItem } from "./repository.js";
10
10
  import { ProactiveInboxService } from "./service.js";
11
11
  //#region src/proactive/inbox/cards.ts
@@ -16,6 +16,11 @@ function requireCardScope(id, workspaceId) {
16
16
  function getCard(id, workspaceId) {
17
17
  requireCardScope(id, workspaceId);
18
18
  const item = getInboxItem(id);
19
+ if (!item.withdrawnAt && item.insight.actionStatus !== "completed" && (!item.expiresAt || Date.parse(item.expiresAt) > Date.now()) && insightSourcesChanged(item.insightId)) {
20
+ item.expiresAt = (/* @__PURE__ */ new Date()).toISOString();
21
+ getSqliteDatabase().prepare("UPDATE proactive_inbox_items SET expires_at = ? WHERE inbox_item_id = ?").run(item.expiresAt, id);
22
+ item.revision = getInboxItem(id).revision;
23
+ }
19
24
  if (!insightSourcesAuthorized(item.insightId)) {
20
25
  withdrawCard(id);
21
26
  item.withdrawnAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -47,7 +52,12 @@ function getCard(id, workspaceId) {
47
52
  revision: item.revision,
48
53
  notificationRevision: item.notificationRevision,
49
54
  subscriptionId: item.subscriptionId,
50
- preparationAvailable: Boolean(subscriptionSettings(item.subscriptionId).preparationWorkflowId),
55
+ ...insight.artifact ? { artifact: insight.artifact } : {},
56
+ ...insight.proposedAction && insight.actionStatus === "approval_required" ? { taskDraft: insight.proposedAction.input } : {},
57
+ ...typeof insight.actionResult?.taskId === "string" ? { followUp: getSqliteDatabase().prepare("SELECT task_id AS taskId, title, phase, resolution FROM tasks WHERE task_id = ?").get(insight.actionResult.taskId) } : {},
58
+ ...insight.scenarioKey === "communication_follow_up" ? { communication: getSqliteDatabase().prepare(`SELECT f.id, f.session_key AS sessionKey
59
+ FROM proactive_runs r JOIN proactive_context_snapshots c ON c.snapshot_id = r.context_snapshot_id
60
+ JOIN proactive_follow_ups f ON f.id = json_extract(c.content_json, '$.follow_up.followUpId') WHERE r.run_id = (SELECT run_id FROM proactive_insights WHERE insight_id = ?)`).get(item.insightId) } : {},
51
61
  relatedCardIds: item.correlationKey ? getSqliteDatabase().prepare("SELECT inbox_item_id AS id FROM proactive_inbox_items WHERE correlation_key = ? AND inbox_item_id <> ? AND withdrawn_at IS NULL").all(item.correlationKey, id).map((row) => row.id) : [],
52
62
  scenarioKey: insight.scenarioKey,
53
63
  kind: insight.attentionKind === "receipt" ? "receipt" : insight.decision ? "decision" : insight.scenarioKey === "meeting_preparation" ? "briefing" : insight.scenarioKey.includes("risk") || insight.scenarioKey.includes("failure") ? "risk" : insight.scenarioKey === "blocked_work" ? "reminder" : "recommendation",
@@ -58,7 +68,7 @@ function getCard(id, workspaceId) {
58
68
  recommendation: insight.recommendation,
59
69
  workDone: insight.workDone,
60
70
  evidence: insight.evidenceIds.map((evidenceId) => cardEvidence(evidenceId, workspaceId)),
61
- ...insight.decision ? { decision: insight.decision } : {},
71
+ ...insight.decision && !["completed", "rejected"].includes(insight.actionStatus ?? "") ? { decision: insight.decision } : {},
62
72
  actionStatus: insight.actionStatus,
63
73
  actionResult: insight.actionResult,
64
74
  actionError: insight.actionError,
@@ -150,7 +160,14 @@ function performCardAction(id, workspaceId, value) {
150
160
  }
151
161
  function applyCardAction(card, workspaceId, input) {
152
162
  const service = new ProactiveInboxService();
163
+ if (input.taskDraft && (input.actionId !== "decide" || input.choice !== "approve" || !card.taskDraft)) throw new Error("Task draft requires a pending approval");
164
+ if (input.artifact && input.actionId !== "edit_artifact") throw new Error("Artifact requires an edit action");
153
165
  switch (input.actionId) {
166
+ case "edit_artifact":
167
+ if (!input.artifact || !card.artifact || input.artifact.kind !== card.artifact.kind) throw new Error("An existing artifact is required");
168
+ getSqliteDatabase().prepare(`UPDATE proactive_insights SET artifact_json = ?, artifact_edited_at = ?
169
+ WHERE insight_id = (SELECT insight_id FROM proactive_inbox_items WHERE inbox_item_id = ?)`).run(JSON.stringify(input.artifact), (/* @__PURE__ */ new Date()).toISOString(), card.id);
170
+ break;
154
171
  case "retry":
155
172
  if (!effectiveProactivePolicy(card.subscriptionId).enabled) throw new Error("Subscription paused");
156
173
  if (!getSqliteDatabase().prepare("UPDATE proactive_insights SET action_status = 'pending', action_error = NULL, action_updated_at = ? WHERE insight_id = (SELECT insight_id FROM proactive_inbox_items WHERE inbox_item_id = ?) AND action_status = 'failed'").run((/* @__PURE__ */ new Date()).toISOString(), card.id).changes) throw new Error("Only failed actions can be retried");
@@ -169,6 +186,12 @@ function applyCardAction(card, workspaceId, input) {
169
186
  resolution: "dismissed"
170
187
  });
171
188
  break;
189
+ case "handled":
190
+ service.transition(card.id, {
191
+ status: "resolved",
192
+ resolution: "user_reported_done"
193
+ });
194
+ break;
172
195
  case "snooze":
173
196
  service.transition(card.id, {
174
197
  status: "snoozed",
@@ -178,14 +201,20 @@ function applyCardAction(card, workspaceId, input) {
178
201
  case "decide":
179
202
  if (!input.choice) throw new Error("Decision choice required");
180
203
  if (!effectiveProactivePolicy(card.subscriptionId).enabled) throw new Error("Subscription paused");
204
+ if (input.taskDraft) {
205
+ const item = getInboxItem(card.id);
206
+ getSqliteDatabase().prepare("UPDATE proactive_insights SET proposed_action_json = ? WHERE insight_id = ?").run(JSON.stringify({
207
+ ...item.insight.proposedAction,
208
+ input: input.taskDraft
209
+ }), item.insightId);
210
+ }
181
211
  service.decide(card.id, input.choice);
182
212
  break;
183
- case "less":
184
213
  case "pause": {
185
214
  const settings = subscriptionSettings(card.subscriptionId);
186
215
  updateControlledSubscription(workspaceId, card.subscriptionId, {
187
216
  expectedRevision: settings.revision,
188
- ...input.actionId === "less" ? { delivery: "inbox" } : { enabled: false }
217
+ enabled: false
189
218
  });
190
219
  service.transition(card.id, {
191
220
  status: "resolved",
@@ -1,9 +1,9 @@
1
1
  import { getSqliteDatabase, runSqliteWriteTransaction } from "../../storage/sqlite/transaction.js";
2
+ import { insightSourcesAuthorized, insightSourcesChanged } from "../execution/authorization.js";
2
3
  import { effectiveProactivePolicy, localProactiveDay, nextDigestTime, quietHoursEnd, reserveProactiveNotification } from "../policy/service.js";
3
- import { viewingProactiveCards } from "../policy/presence.js";
4
- import { insightSourcesAuthorized } from "../execution/authorization.js";
5
4
  import { insightCorrelation } from "./lifecycle.js";
6
5
  import { getInboxItem } from "./repository.js";
6
+ import { viewingProactiveCards } from "../policy/presence.js";
7
7
  import { queueDigest } from "./digest.js";
8
8
  import { notificationPlanFromGatewayEvent } from "../../notifications/planner.js";
9
9
  import { createHash } from "node:crypto";
@@ -13,6 +13,7 @@ function deliverProactiveCard(item, notifications, publish) {
13
13
  const result = runSqliteWriteTransaction(() => {
14
14
  const current = getInboxItem(item.id);
15
15
  if (!current || current.withdrawnAt || !insightSourcesAuthorized(current.insightId) || current.status === "resolved" || current.expiresAt && Date.parse(current.expiresAt) <= Date.now()) return null;
16
+ if (current.insight.actionStatus !== "completed" && insightSourcesChanged(current.insightId)) return null;
16
17
  if (current.status === "snoozed") return { retryAt: current.snoozedUntil };
17
18
  const policy = effectiveProactivePolicy(current.subscriptionId);
18
19
  if (!policy.enabled || policy.settings.delivery === "inbox") return null;
@@ -22,6 +23,7 @@ function deliverProactiveCard(item, notifications, publish) {
22
23
  }
23
24
  if (!notificationPlanFromGatewayEvent("proactive.inbox.created", current)) return null;
24
25
  if (policy.level === "quiet") return null;
26
+ if (policy.level !== "active" && ["low", "medium"].includes(current.insight.urgency) && !current.insight.decision) return null;
25
27
  const quietUntil = quietHoursEnd(policy.preferences, /* @__PURE__ */ new Date());
26
28
  if (quietUntil) {
27
29
  queueDigest(current, "quiet", quietUntil);
@@ -1,9 +1,9 @@
1
1
  import { getSqliteDatabase, runSqliteWriteTransaction } from "../../storage/sqlite/transaction.js";
2
- import { effectiveProactivePolicy, localProactiveDay, nextDigestTime, proactivePreferences, quietHoursEnd } from "../policy/service.js";
3
- import { viewingProactiveCards } from "../policy/presence.js";
4
2
  import { insightSourcesAuthorized } from "../execution/authorization.js";
3
+ import { effectiveProactivePolicy, localProactiveDay, nextDigestTime, proactivePreferences, quietHoursEnd } from "../policy/service.js";
5
4
  import { getInboxItem } from "./repository.js";
6
5
  import { getCard } from "./cards.js";
6
+ import { viewingProactiveCards } from "../policy/presence.js";
7
7
  import { randomUUID } from "node:crypto";
8
8
  //#region src/proactive/inbox/digest.ts
9
9
  function queueDigest(item, mode, dueAt) {
@@ -1,5 +1,5 @@
1
1
  import { getSqliteDatabase, runSqliteWriteTransaction } from "../../storage/sqlite/transaction.js";
2
- import { insightSourcesAuthorized } from "../execution/authorization.js";
2
+ import { insightSourcesAuthorized, insightSourcesChanged } from "../execution/authorization.js";
3
3
  import { createHash } from "node:crypto";
4
4
  //#region src/proactive/inbox/lifecycle.ts
5
5
  function withdrawCard(id, now = /* @__PURE__ */ new Date()) {
@@ -46,6 +46,9 @@ function reconcileCards(now = /* @__PURE__ */ new Date()) {
46
46
  withdrawCard(row.inbox_item_id, now);
47
47
  withdrawn++;
48
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());
49
52
  const correlation = insightCorrelation(row.insight_id);
50
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);
51
54
  }
@@ -1,15 +1,17 @@
1
+ import { insightSourcesAuthorized, insightSourcesChanged } from "../execution/authorization.js";
1
2
  import { effectiveProactivePolicy, quietHoursEnd } from "../policy/service.js";
2
- import { insightSourcesAuthorized } from "../execution/authorization.js";
3
3
  import { getInboxItem } from "./repository.js";
4
4
  //#region src/proactive/inbox/notification-policy.ts
5
5
  function recheckProactiveNotification(itemId, now = /* @__PURE__ */ new Date(), notificationRevision) {
6
6
  const item = getInboxItem(itemId);
7
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";
8
9
  if (typeof notificationRevision === "number" && notificationRevision !== item.notificationRevision) return "cancel";
9
10
  const policy = effectiveProactivePolicy(item.subscriptionId, now);
10
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";
11
13
  if (item.status === "snoozed") return new Date(item.snoozedUntil);
12
- if (policy.settings.managed || policy.preferences.revision > 0) {
14
+ {
13
15
  if (policy.level === "quiet" || policy.settings.delivery !== "important") return "cancel";
14
16
  const until = quietHoursEnd(policy.preferences, now);
15
17
  if (until) return until;