@xopcai/xopc 0.0.225 → 0.0.228

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 (239) 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-CtTYnku4.js → activity-store-M0nAY5XB.js} +1 -1
  4. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-dizuqu_4.js → agent-avatar-dicebear-CjGZBs4C.js} +1 -1
  5. package/dist/gateway/static/root/assets/agents-B0NI4_NI.js +68 -0
  6. package/dist/gateway/static/root/assets/{api-CLRMz2BT.js → api-BywYxpaS.js} +1 -1
  7. package/dist/gateway/static/root/assets/{app-management-settings-panel-Cnk-DtQh.js → app-management-settings-panel--SjycrXk.js} +1 -1
  8. package/dist/gateway/static/root/assets/{appearance-settings-wP0rogVI.js → appearance-settings-8G-TVUtg.js} +1 -1
  9. package/dist/gateway/static/root/assets/apps-page-2NiqD-YP.js +1 -0
  10. package/dist/gateway/static/root/assets/{archive-plugin-Cpzsm-DM.js → archive-plugin-7_DXaMTP.js} +1 -1
  11. package/dist/gateway/static/root/assets/{attachment-load-Bd2OOoEK.js → attachment-load-Bx44zSpz.js} +1 -1
  12. package/dist/gateway/static/root/assets/{automations-page-DNqhguyk.js → automations-page-CrKAHqc0.js} +1 -1
  13. package/dist/gateway/static/root/assets/automations-workspace-Q3g9cf0h.js +6 -0
  14. package/dist/gateway/static/root/assets/{binary-plugins-Dcma5HdI.js → binary-plugins-DjDwnHAQ.js} +1 -1
  15. package/dist/gateway/static/root/assets/{block-editor-D-B8ThwK.js → block-editor-7aMOKoZM.js} +3 -3
  16. package/dist/gateway/static/root/assets/browser-settings-page-DTwPkmAC.js +8 -0
  17. package/dist/gateway/static/root/assets/{browser-workflow-inputs-BAPZIXrs.js → browser-workflow-inputs-8NC2qDRF.js} +1 -1
  18. package/dist/gateway/static/root/assets/{browser-workflows-page-DHtaSKEh.js → browser-workflows-page-DVLrWKz_.js} +2 -2
  19. package/dist/gateway/static/root/assets/capabilities-settings-panel-YcP4FbN7.js +7 -0
  20. package/dist/gateway/static/root/assets/{capability-presets-api-zsZQ7im3.js → capability-presets-api-o0y9WgAI.js} +1 -1
  21. package/dist/gateway/static/root/assets/capability-presets-settings-panel-AcPjHHia.js +3 -0
  22. package/dist/gateway/static/root/assets/{channel-recipient-api-Cp8iaaXp.js → channel-recipient-api-C5ABr73b.js} +1 -1
  23. package/dist/gateway/static/root/assets/channels-settings-Ca94Q1D-.js +1 -0
  24. package/dist/gateway/static/root/assets/channels-status-swr-38FsOnR0.js +1 -0
  25. package/dist/gateway/static/root/assets/{chat-terminal-dock-Dr6Rq2ln.js → chat-terminal-dock-BR-_7KC0.js} +2 -2
  26. package/dist/gateway/static/root/assets/connectors-page-SGxfLTd6.js +2 -0
  27. package/dist/gateway/static/root/assets/{cron-expression-BiXrpzmo.js → cron-expression-D8-c-Tkw.js} +1 -1
  28. package/dist/gateway/static/root/assets/{date-picker-BPuRtwKt.js → date-picker-kV-Jyl9J.js} +1 -1
  29. package/dist/gateway/static/root/assets/{dependency-picker-BNC_W7wI.js → dependency-picker-CwKcrfGj.js} +1 -1
  30. package/dist/gateway/static/root/assets/{desktop-pet-DzOpt5nI.js → desktop-pet-D9rnchu3.js} +1 -1
  31. package/dist/gateway/static/root/assets/{desktop-pet-settings-BW0brCvH.js → desktop-pet-settings-CTs5WGzk.js} +1 -1
  32. package/dist/gateway/static/root/assets/{directory-picker-path-field-Baml0uTS.js → directory-picker-path-field-CCl0aaDm.js} +1 -1
  33. package/dist/gateway/static/root/assets/{extension-debug-page-CBXyKmfS.js → extension-debug-page-Cau40SvC.js} +1 -1
  34. package/dist/gateway/static/root/assets/{extension-page-BBhcr_SZ.js → extension-page-BsJXdvIL.js} +1 -1
  35. package/dist/gateway/static/root/assets/{extension-settings-page-4rwWpN9_.js → extension-settings-page-qWqJyF7-.js} +1 -1
  36. package/dist/gateway/static/root/assets/{gateway-config-api-Dg-BlBe4.js → gateway-config-api-BI0SELXm.js} +1 -1
  37. package/dist/gateway/static/root/assets/{gateway-config-swr-Du0MvK9s.js → gateway-config-swr-BWGjnmDl.js} +1 -1
  38. package/dist/gateway/static/root/assets/{gateway-settings-BOuYZOYe.js → gateway-settings-6ZdOIC7F.js} +1 -1
  39. package/dist/gateway/static/root/assets/{gateway-startup-retry-JbjcHCaN.js → gateway-startup-retry-B_zrtccP.js} +1 -1
  40. package/dist/gateway/static/root/assets/heartbeat-settings-KBwhXeFD.js +1 -0
  41. package/dist/gateway/static/root/assets/home-page-BA2M4CLi.js +1 -0
  42. package/dist/gateway/static/root/assets/{image-generation-api-G2tfPvKP.js → image-generation-api-YKP2me5Z.js} +1 -1
  43. package/dist/gateway/static/root/assets/index-BX0dz1JK.js +70 -0
  44. package/dist/gateway/static/root/assets/index-D_hrEKXM.css +2 -0
  45. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-L1Vtxgp9.js → keyboard-shortcuts-settings-kHtpnyEm.js} +1 -1
  46. package/dist/gateway/static/root/assets/local-app-workbench-page-D2nkn09R.js +2 -0
  47. package/dist/gateway/static/root/assets/{local-apps-page--thlkBLS.js → local-apps-page-BCXa0oUa.js} +1 -1
  48. package/dist/gateway/static/root/assets/{locale-store-DqJz-cOR.js → locale-store-CXhjfe4q.js} +1 -1
  49. package/dist/gateway/static/root/assets/logs-page-D3sNst0K.js +2 -0
  50. package/dist/gateway/static/root/assets/{management-settings-p55fR4XO.js → management-settings-tHnlfOAQ.js} +1 -1
  51. package/dist/gateway/static/root/assets/{markdown-split-DwMcRlBR.js → markdown-split-BMLqHHr5.js} +1 -1
  52. package/dist/gateway/static/root/assets/{markdown-view-DD_PgVue.css → markdown-view-CLzTdVLW.css} +1 -1
  53. package/dist/gateway/static/root/assets/markdown-view-DvM5PWEH.js +201 -0
  54. package/dist/gateway/static/root/assets/{media-plugins-O6H3GZn7.js → media-plugins-DybwqTgv.js} +1 -1
  55. package/dist/gateway/static/root/assets/messages-BMdD0LWk.js +3 -0
  56. package/dist/gateway/static/root/assets/{notes-api-CrjQbmk1.js → notes-api-BFsOGYkd.js} +1 -1
  57. package/dist/gateway/static/root/assets/{notes-page-miO9ig9C.js → notes-page-C24SfF6r.js} +1 -1
  58. package/dist/gateway/static/root/assets/notes-workbench-CTdZc3ov.js +4 -0
  59. package/dist/gateway/static/root/assets/{product-open-page-BOu07nXf.js → product-open-page-DBfCVlJq.js} +1 -1
  60. package/dist/gateway/static/root/assets/project-detail-page-COHaEpbA.js +1 -0
  61. package/dist/gateway/static/root/assets/projects-page-lMnST7D_.js +1 -0
  62. package/dist/gateway/static/root/assets/{registry-api-Bf_HDLUJ.js → registry-api-BFZrVYfy.js} +1 -1
  63. package/dist/gateway/static/root/assets/remote-access-hub-BIRKBvO_.js +1 -0
  64. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-DthuRqUH.js → runtime-tools-settings-panel-BPEGTip5.js} +2 -2
  65. package/dist/gateway/static/root/assets/{schema-form-8W8PhXxa.js → schema-form-Dxlu60Td.js} +1 -1
  66. package/dist/gateway/static/root/assets/sessions-page-CATO9G3y.js +1 -0
  67. package/dist/gateway/static/root/assets/settings-advanced-gate-Cco_W41x.js +1 -0
  68. package/dist/gateway/static/root/assets/{settings-form-section-CrsKGYiG.js → settings-form-section-ChXh3Bwk.js} +1 -1
  69. package/dist/gateway/static/root/assets/{settings-loading-skeleton-WJtI0AAq.js → settings-loading-skeleton-BCAUqIe2.js} +1 -1
  70. package/dist/gateway/static/root/assets/{settings-page-PCjwRF47.js → settings-page-D25W9Okr.js} +1 -1
  71. package/dist/gateway/static/root/assets/{setup-status-panel-D65CwlNC.js → setup-status-panel-DsN7Z4Fl.js} +1 -1
  72. package/dist/gateway/static/root/assets/{share-preview-page-BGtvLLeZ.js → share-preview-page-CRFnUaNb.js} +1 -1
  73. package/dist/gateway/static/root/assets/shares-settings-CAX0eNaP.js +1 -0
  74. package/dist/gateway/static/root/assets/skill-reload-api-Doy1gghe.js +1 -0
  75. package/dist/gateway/static/root/assets/{skills-page-C_npyryI.js → skills-page-Cl7jjTK7.js} +1 -1
  76. package/dist/gateway/static/root/assets/{skills-page.utils-C46Sd78k.js → skills-page.utils-BiCl4nb5.js} +1 -1
  77. package/dist/gateway/static/root/assets/{system-settings-panel-DomTLwki.js → system-settings-panel-BuESR7ZT.js} +1 -1
  78. package/dist/gateway/static/root/assets/task-detail-page-BRiy8NL3.js +1 -0
  79. package/dist/gateway/static/root/assets/{theme-store-Ce7wq__X.js → theme-store-DOdCjyuw.js} +1 -1
  80. package/dist/gateway/static/root/assets/{use-autosave-Do9mCRcY.js → use-autosave-Dg1mgJ_l.js} +1 -1
  81. package/dist/gateway/static/root/assets/user-context-page-CqoADNKu.js +1 -0
  82. package/dist/gateway/static/root/assets/{voice-api-key-field-Bc5p-0tX.js → voice-api-key-field-BDNexlVR.js} +1 -1
  83. package/dist/gateway/static/root/assets/{work-discovery-overlay-BieqsmT1.js → work-discovery-overlay-BHTfeaWL.js} +1 -1
  84. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-DVj8iYvm.js → workflow-run-setup-panel-Cf_w-NZy.js} +2 -2
  85. package/dist/gateway/static/root/assets/workflows-page-j0P-uECz.js +5 -0
  86. package/dist/gateway/static/root/index.html +12 -12
  87. package/dist/gateway/static/root/notification-sw.js +1 -1
  88. package/dist/package.js +1 -1
  89. package/dist/packages/gateway-contract/src/notifications.js +56 -0
  90. package/dist/packages/gateway-contract/src/turn-outcome.js +114 -0
  91. package/dist/src/agent/agent-manager.d.ts +3 -1
  92. package/dist/src/agent/agent-manager.js +22 -5
  93. package/dist/src/agent/lifecycle/timeout-wrapper.d.ts +1 -0
  94. package/dist/src/agent/lifecycle/timeout-wrapper.js +7 -6
  95. package/dist/src/agent/mcp/mcp-transport-config.d.ts +1 -1
  96. package/dist/src/agent/mcp/mcp-transport-config.js +1 -1
  97. package/dist/src/agent/memory/understanding/service.js +1 -0
  98. package/dist/src/agent/memory/understanding/types.d.ts +1 -0
  99. package/dist/src/agent/orchestration/run-agent-turn-with-timeout.d.ts +2 -2
  100. package/dist/src/agent/orchestration/run-agent-turn-with-timeout.js +4 -4
  101. package/dist/src/agent/service.d.ts +2 -0
  102. package/dist/src/agent/service.js +6 -0
  103. package/dist/src/agent/skills/index.d.ts +22 -2
  104. package/dist/src/agent/skills/index.js +74 -32
  105. package/dist/src/agent/skills/skill-sources.d.ts +0 -1
  106. package/dist/src/agent/skills/skill-sources.js +1 -11
  107. package/dist/src/agent/tools/create-share-tool.js +14 -1
  108. package/dist/src/agent/tools/exec-command.js +3 -3
  109. package/dist/src/agent/tools/execute-code-tool.js +1 -1
  110. package/dist/src/agent/tools/executor.js +2 -2
  111. package/dist/src/agent/tools/image-generate-tool.js +12 -0
  112. package/dist/src/agent/tools/send-media.js +11 -0
  113. package/dist/src/agent/tools/xopc-use-tool.js +1 -1
  114. package/dist/src/cli/command-loaders.js +1 -0
  115. package/dist/src/cli/command-manifest.js +4 -0
  116. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  117. package/dist/src/cli/commands/gateway/lifecycle.js +1 -1
  118. package/dist/src/cli/commands/history.d.ts +1 -0
  119. package/dist/src/cli/commands/history.js +50 -0
  120. package/dist/src/config/agent-profile.js +1 -1
  121. package/dist/src/connectors/connected-source-understanding.js +1 -1
  122. package/dist/src/gateway/agents-admin.js +1 -1
  123. package/dist/src/gateway/chat-stream/protocol.d.ts +3 -2
  124. package/dist/src/gateway/chat-stream/turn-outcome-collector.d.ts +16 -0
  125. package/dist/src/gateway/chat-stream/turn-outcome-collector.js +111 -0
  126. package/dist/src/gateway/heartbeat/service.js +1 -1
  127. package/dist/src/gateway/hono/lib/static-ui.js +7 -5
  128. package/dist/src/gateway/hono/routes/connectors.js +18 -16
  129. package/dist/src/gateway/hono/routes/index.js +2 -0
  130. package/dist/src/gateway/hono/routes/mobile.js +18 -40
  131. package/dist/src/gateway/hono/routes/notifications.d.ts +3 -0
  132. package/dist/src/gateway/hono/routes/notifications.js +45 -0
  133. package/dist/src/gateway/hono/routes/project-skills.js +30 -2
  134. package/dist/src/gateway/hono/routes/sessions.js +31 -0
  135. package/dist/src/gateway/hono/routes/tasks.js +1 -1
  136. package/dist/src/gateway/hono/routes/voice.js +1 -1
  137. package/dist/src/gateway/hono/routes/you.js +52 -0
  138. package/dist/src/gateway/service/agent-runner.js +6 -2
  139. package/dist/src/gateway/service/run-gateway-agent.js +53 -1
  140. package/dist/src/gateway/service/sessions-api.d.ts +6 -0
  141. package/dist/src/gateway/service/sessions-api.js +60 -3
  142. package/dist/src/gateway/service.d.ts +2 -2
  143. package/dist/src/gateway/service.js +8 -7
  144. package/dist/src/heartbeat/index.js +1 -1
  145. package/dist/src/history/ctx/exporter.d.ts +14 -0
  146. package/dist/src/history/ctx/exporter.js +99 -0
  147. package/dist/src/history/ctx/format.d.ts +25 -0
  148. package/dist/src/history/ctx/format.js +282 -0
  149. package/dist/src/infra/update-restart.js +1 -1
  150. package/dist/src/knowledge/connected-understanding-pipeline.js +12 -3
  151. package/dist/src/knowledge/index.d.ts +0 -1
  152. package/dist/src/knowledge/index.js +1 -2
  153. package/dist/src/media/session-references.d.ts +6 -0
  154. package/dist/src/media/session-references.js +24 -3
  155. package/dist/src/notifications/device-store.d.ts +16 -0
  156. package/dist/src/notifications/device-store.js +96 -0
  157. package/dist/src/notifications/planner.d.ts +6 -0
  158. package/dist/src/notifications/planner.js +156 -0
  159. package/dist/src/notifications/service.d.ts +18 -0
  160. package/dist/src/notifications/service.js +184 -0
  161. package/dist/src/notifications/store.d.ts +53 -0
  162. package/dist/src/notifications/store.js +184 -0
  163. package/dist/src/notifications/types.d.ts +29 -0
  164. package/dist/src/notifications/types.js +14 -0
  165. package/dist/src/projects/project-skill-service.d.ts +28 -6
  166. package/dist/src/projects/project-skill-service.js +76 -29
  167. package/dist/src/session/client-history.d.ts +3 -1
  168. package/dist/src/session/client-history.js +13 -0
  169. package/dist/src/session/index.d.ts +1 -1
  170. package/dist/src/session/manager.d.ts +1 -0
  171. package/dist/src/session/manager.js +6 -0
  172. package/dist/src/session/store.d.ts +15 -0
  173. package/dist/src/session/store.js +58 -0
  174. package/dist/src/storage/sqlite/index.d.ts +2 -1
  175. package/dist/src/storage/sqlite/index.js +3 -2
  176. package/dist/src/storage/sqlite/migrations/135_user_people.sql +59 -0
  177. package/dist/src/storage/sqlite/migrations/136_product_notifications.sql +79 -0
  178. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  179. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  180. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  181. package/dist/src/storage/sqlite/schema.js +1 -1
  182. package/dist/src/storage/sqlite/session-input-repository.d.ts +5 -0
  183. package/dist/src/storage/sqlite/session-input-repository.js +9 -1
  184. package/dist/src/storage/sqlite/user-people-repository.d.ts +32 -0
  185. package/dist/src/storage/sqlite/user-people-repository.js +246 -0
  186. package/dist/src/tasks/index.js +2 -2
  187. package/dist/src/tasks/task-application-service.js +42 -6
  188. package/dist/src/tasks/task-change-events.d.ts +9 -0
  189. package/dist/src/tasks/task-change-events.js +17 -1
  190. package/dist/src/tasks/task-outbox-dispatcher.js +5 -1
  191. package/dist/src/tasks/task-run-dispatcher.js +4 -2
  192. package/dist/src/tasks/task-run-repository.js +13 -0
  193. package/dist/src/user-context/relationships/indexer.d.ts +13 -0
  194. package/dist/src/user-context/relationships/indexer.js +185 -0
  195. package/dist/src/user-context/relationships/service.d.ts +22 -0
  196. package/dist/src/user-context/relationships/service.js +50 -0
  197. package/dist/src/user-context/relationships/types.d.ts +55 -0
  198. package/dist/src/user-context/relationships/types.js +10 -0
  199. package/dist/src/user-context/sources/repository.d.ts +1 -1
  200. package/dist/src/user-context/sources/repository.js +8 -2
  201. package/dist/src/work-discovery/service.js +1 -1
  202. package/package.json +2 -2
  203. package/dist/gateway/static/root/assets/agents-DeEEFZJ4.js +0 -68
  204. package/dist/gateway/static/root/assets/apps-page-Db5YNHVM.js +0 -1
  205. package/dist/gateway/static/root/assets/automations-workspace-CNT05YTb.js +0 -6
  206. package/dist/gateway/static/root/assets/browser-settings-page-UbDj3LIq.js +0 -8
  207. package/dist/gateway/static/root/assets/capabilities-settings-panel-CiO152QS.js +0 -7
  208. package/dist/gateway/static/root/assets/capability-presets-settings-panel-Bc_hRwfL.js +0 -3
  209. package/dist/gateway/static/root/assets/channels-settings-CFW7zBoT.js +0 -1
  210. package/dist/gateway/static/root/assets/channels-status-swr-5rNVq7iI.js +0 -1
  211. package/dist/gateway/static/root/assets/connectors-page-DLBr5nre.js +0 -2
  212. package/dist/gateway/static/root/assets/heartbeat-settings-Dq4YAlSx.js +0 -1
  213. package/dist/gateway/static/root/assets/home-page-BIXkO0Ck.js +0 -1
  214. package/dist/gateway/static/root/assets/index-Csccu8sa.js +0 -74
  215. package/dist/gateway/static/root/assets/index-D3_xSYKd.css +0 -2
  216. package/dist/gateway/static/root/assets/local-app-workbench-page-D-eacBcO.js +0 -2
  217. package/dist/gateway/static/root/assets/logs-page-BRCglHvd.js +0 -2
  218. package/dist/gateway/static/root/assets/markdown-view-CTB9u15e.js +0 -69
  219. package/dist/gateway/static/root/assets/mermaid-render-BbqDc_LQ.js +0 -134
  220. package/dist/gateway/static/root/assets/messages-CeydamsI.js +0 -3
  221. package/dist/gateway/static/root/assets/notes-workbench-_13lmvmF.js +0 -4
  222. package/dist/gateway/static/root/assets/project-detail-page-BStRkOQr.js +0 -1
  223. package/dist/gateway/static/root/assets/projects-page-CY03RxVr.js +0 -1
  224. package/dist/gateway/static/root/assets/remote-access-hub-B19llkld.js +0 -1
  225. package/dist/gateway/static/root/assets/sessions-page-BDA2qbTd.js +0 -1
  226. package/dist/gateway/static/root/assets/settings-advanced-gate-BONIDX4v.js +0 -1
  227. package/dist/gateway/static/root/assets/shares-settings-CdzVKrBl.js +0 -1
  228. package/dist/gateway/static/root/assets/skill-reload-api-CsRWid5h.js +0 -1
  229. package/dist/gateway/static/root/assets/task-detail-page-B6YFX7kK.js +0 -1
  230. package/dist/gateway/static/root/assets/user-context-page-CiCQA40E.js +0 -1
  231. package/dist/gateway/static/root/assets/workflows-page-AG04TTuH.js +0 -5
  232. package/dist/src/knowledge/people-graph.d.ts +0 -25
  233. package/dist/src/knowledge/people-graph.js +0 -92
  234. package/dist/src/mobile/notification-service.d.ts +0 -9
  235. package/dist/src/mobile/notification-service.js +0 -156
  236. package/dist/src/mobile/notification-store.d.ts +0 -28
  237. package/dist/src/mobile/notification-store.js +0 -132
  238. package/dist/src/mobile/notification-types.d.ts +0 -37
  239. package/dist/src/mobile/notification-types.js +0 -9
@@ -0,0 +1,282 @@
1
+ import { createHash } from "node:crypto";
2
+ //#region src/history/ctx/format.ts
3
+ const CTX_HISTORY_SCHEMA_VERSION = "ctx-history-jsonl-v2";
4
+ const CTX_PROVIDER_KEY = "xopc";
5
+ const CTX_SOURCE_ID = "default";
6
+ const CTX_SOURCE_FORMAT = "xopc-session-history-v1";
7
+ const EVENT_INDEX_STRIDE = 65536;
8
+ const MAX_TEXT_CHUNK_BYTES = 1024 * 1024;
9
+ const MAX_JSONL_LINE_BYTES = 16 * 1024 * 1024;
10
+ function asRecord(value) {
11
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
12
+ }
13
+ function stringValue(value) {
14
+ return typeof value === "string" ? value : void 0;
15
+ }
16
+ function timestamp(value, label) {
17
+ const date = new Date(value);
18
+ if (!Number.isFinite(value) || Number.isNaN(date.getTime())) throw new Error(`Invalid ${label}: ${String(value)}`);
19
+ return date.toISOString();
20
+ }
21
+ function stringifyValue(value) {
22
+ if (typeof value === "string") return value;
23
+ if (value === void 0) return "";
24
+ return JSON.stringify(value) ?? "";
25
+ }
26
+ function textParts(content) {
27
+ if (typeof content === "string") return content.length > 0 ? [content] : [];
28
+ if (!Array.isArray(content)) return [];
29
+ return content.flatMap((block) => {
30
+ const record = asRecord(block);
31
+ return record?.type === "text" && typeof record.text === "string" && record.text.length > 0 ? [record.text] : [];
32
+ });
33
+ }
34
+ function outputText(output) {
35
+ if (typeof output === "string") return output;
36
+ return textParts(output).join("");
37
+ }
38
+ function toolCallDetails(block) {
39
+ const toolName = stringValue(block.name);
40
+ if (!toolName) return null;
41
+ const toolCallId = stringValue(block.id);
42
+ const argumentsText = stringifyValue(block.arguments);
43
+ return {
44
+ eventType: "tool_call",
45
+ role: "assistant",
46
+ text: argumentsText ? `${toolName} ${argumentsText}` : toolName,
47
+ details: {
48
+ tool_name: toolName,
49
+ ...toolCallId ? { tool_call_id: toolCallId } : {}
50
+ }
51
+ };
52
+ }
53
+ function normalizeAssistant(row) {
54
+ if (typeof row.content === "string") return row.content.length > 0 ? [{
55
+ eventType: "message",
56
+ role: "assistant",
57
+ text: row.content
58
+ }] : [];
59
+ if (!Array.isArray(row.content)) return [];
60
+ const events = [];
61
+ for (const block of row.content) {
62
+ const record = asRecord(block);
63
+ if (!record) continue;
64
+ if (record.type === "text" && typeof record.text === "string" && record.text.length > 0) {
65
+ events.push({
66
+ eventType: "message",
67
+ role: "assistant",
68
+ text: record.text
69
+ });
70
+ continue;
71
+ }
72
+ if (record.type === "toolCall") {
73
+ const event = toolCallDetails(record);
74
+ if (event) events.push(event);
75
+ }
76
+ }
77
+ return events;
78
+ }
79
+ function normalizeToolResult(row) {
80
+ const text = textParts(row.content).join("");
81
+ if (!text) return [];
82
+ const toolName = stringValue(row.toolName);
83
+ const toolCallId = stringValue(row.toolCallId);
84
+ return [{
85
+ eventType: "tool_output",
86
+ role: "tool",
87
+ text,
88
+ details: {
89
+ ...toolName ? { tool_name: toolName } : {},
90
+ ...toolCallId ? { tool_call_id: toolCallId } : {},
91
+ ...typeof row.isError === "boolean" ? { is_error: row.isError } : {}
92
+ }
93
+ }];
94
+ }
95
+ function normalizeBashExecution(row) {
96
+ const command = stringValue(row.command);
97
+ if (!command) return [];
98
+ const events = [{
99
+ eventType: "command_started",
100
+ role: "user",
101
+ text: command
102
+ }];
103
+ const output = outputText(row.output);
104
+ if (output) events.push({
105
+ eventType: "command_output",
106
+ role: "tool",
107
+ text: output
108
+ });
109
+ events.push({
110
+ eventType: "command_finished",
111
+ role: "tool",
112
+ text: row.exitCode == null ? "Command finished" : `Command exited with code ${String(row.exitCode)}`,
113
+ details: {
114
+ ...typeof row.exitCode === "number" ? { exit_code: row.exitCode } : {},
115
+ ...typeof row.signal === "string" ? { signal: row.signal } : {},
116
+ ...typeof row.truncated === "boolean" ? { truncated: row.truncated } : {}
117
+ }
118
+ });
119
+ return events;
120
+ }
121
+ function normalizeStoredRow(row) {
122
+ if (row.kind === "context" || row.role === "system") return [];
123
+ if (row.type === "compaction" && typeof row.summary === "string" && row.summary.length > 0) return [{
124
+ eventType: "summary",
125
+ role: "assistant",
126
+ text: row.summary
127
+ }];
128
+ if ((row.role === "branchSummary" || row.role === "compactionSummary") && typeof row.summary === "string" && row.summary.length > 0) return [{
129
+ eventType: "summary",
130
+ role: "assistant",
131
+ text: row.summary
132
+ }];
133
+ if (row.role === "bashExecution") return normalizeBashExecution(row);
134
+ if (row.role === "custom" || row.type === "custom_message") {
135
+ if (row.display === false) return [];
136
+ return textParts(row.content).map((text) => ({
137
+ eventType: "message",
138
+ role: "user",
139
+ text
140
+ }));
141
+ }
142
+ if (row.role === "user") return textParts(row.content).map((text) => ({
143
+ eventType: "message",
144
+ role: "user",
145
+ text
146
+ }));
147
+ if (row.role === "assistant") return normalizeAssistant(row);
148
+ if (row.role === "toolResult" || row.role === "tool") return normalizeToolResult(row);
149
+ return [];
150
+ }
151
+ function splitTextByUtf8Bytes(text) {
152
+ if (Buffer.byteLength(text, "utf8") <= MAX_TEXT_CHUNK_BYTES) return [text];
153
+ const chunks = [];
154
+ let start = 0;
155
+ while (start < text.length) {
156
+ let low = start + 1;
157
+ let high = text.length;
158
+ let end = low;
159
+ while (low <= high) {
160
+ const middle = Math.floor((low + high) / 2);
161
+ if (Buffer.byteLength(text.slice(start, middle), "utf8") <= MAX_TEXT_CHUNK_BYTES) {
162
+ end = middle;
163
+ low = middle + 1;
164
+ } else high = middle - 1;
165
+ }
166
+ const previous = text.charCodeAt(end - 1);
167
+ const next = text.charCodeAt(end);
168
+ if (previous >= 55296 && previous <= 56319 && next >= 56320 && next <= 57343) end -= 1;
169
+ chunks.push(text.slice(start, end));
170
+ start = end;
171
+ }
172
+ return chunks;
173
+ }
174
+ function stableEventId(sessionId, entryId, eventOffset) {
175
+ return `xopc:${createHash("sha256").update(sessionId).update("\0").update(entryId).update("\0").update(String(eventOffset)).digest("hex")}`;
176
+ }
177
+ function serializeRecord(record) {
178
+ const line = JSON.stringify(record);
179
+ const size = Buffer.byteLength(line, "utf8");
180
+ if (size > MAX_JSONL_LINE_BYTES) throw new Error(`ctx history record exceeds 16 MiB (${size} bytes)`);
181
+ return line;
182
+ }
183
+ function sessionRecord(session) {
184
+ const active = session.status === "active";
185
+ return {
186
+ record_type: "session",
187
+ source_id: CTX_SOURCE_ID,
188
+ provider_session_id: session.sessionId,
189
+ started_at: timestamp(session.createdAt, `session timestamp for ${session.sessionId}`),
190
+ external_agent_id: session.agentId,
191
+ cwd: session.cwd,
192
+ ...session.archivedAt === null ? {} : { ended_at: timestamp(session.archivedAt, `session end timestamp for ${session.sessionId}`) },
193
+ status: active ? "active" : "completed",
194
+ agent_scope: session.sessionType === "workflow-subagent" ? "subagent" : "primary"
195
+ };
196
+ }
197
+ function entryRecords(session, entry) {
198
+ let parsed;
199
+ try {
200
+ parsed = JSON.parse(entry.payloadJson);
201
+ } catch (error) {
202
+ const message = error instanceof Error ? error.message : String(error);
203
+ throw new Error(`Invalid transcript payload for entry ${entry.entryId}: ${message}`);
204
+ }
205
+ const row = asRecord(parsed);
206
+ if (!row) throw new Error(`Transcript payload for entry ${entry.entryId} is not an object`);
207
+ const occurredAt = timestamp(entry.createdAt, `entry timestamp for ${entry.entryId}`);
208
+ const records = [];
209
+ let offset = 0;
210
+ for (const event of normalizeStoredRow(row)) {
211
+ const chunks = splitTextByUtf8Bytes(event.text);
212
+ for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex += 1) {
213
+ if (offset >= EVENT_INDEX_STRIDE) throw new Error(`Transcript entry ${entry.entryId} expands to too many ctx events`);
214
+ const eventIndex = entry.seq * EVENT_INDEX_STRIDE + offset;
215
+ if (!Number.isSafeInteger(eventIndex) || eventIndex < 0) throw new Error(`Transcript sequence is outside the supported range: ${entry.seq}`);
216
+ records.push({
217
+ record_type: "event",
218
+ source_id: CTX_SOURCE_ID,
219
+ provider_session_id: session.sessionId,
220
+ event_index: eventIndex,
221
+ event_id: stableEventId(session.sessionId, entry.entryId, offset),
222
+ occurred_at: occurredAt,
223
+ event_type: event.eventType,
224
+ ...event.role ? { role: event.role } : {},
225
+ payload: {
226
+ text: chunks[chunkIndex],
227
+ ...event.details,
228
+ ...chunks.length > 1 ? {
229
+ chunk_index: chunkIndex,
230
+ chunk_count: chunks.length
231
+ } : {}
232
+ }
233
+ });
234
+ offset += 1;
235
+ }
236
+ }
237
+ return records;
238
+ }
239
+ function buildCtxHistoryJsonl(sessions) {
240
+ const lines = [serializeRecord({
241
+ record_type: "manifest",
242
+ schema_version: CTX_HISTORY_SCHEMA_VERSION,
243
+ producer: "xopc"
244
+ }), serializeRecord({
245
+ record_type: "source",
246
+ source_id: CTX_SOURCE_ID,
247
+ provider_key: CTX_PROVIDER_KEY,
248
+ source_format: CTX_SOURCE_FORMAT
249
+ })];
250
+ let eventCount = 0;
251
+ for (const session of sessions) {
252
+ lines.push(serializeRecord(sessionRecord(session)));
253
+ for (const entry of session.entries) {
254
+ const records = entryRecords(session, entry);
255
+ eventCount += records.length;
256
+ lines.push(...records.map(serializeRecord));
257
+ }
258
+ }
259
+ return {
260
+ contents: `${lines.join("\n")}\n`,
261
+ eventCount
262
+ };
263
+ }
264
+ function buildCtxPluginManifest() {
265
+ return `${JSON.stringify({
266
+ schema_version: 1,
267
+ name: "xopc",
268
+ display_name: "XOPC history",
269
+ version: "1.0.0",
270
+ history_sources: [{
271
+ id: CTX_SOURCE_ID,
272
+ provider_key: CTX_PROVIDER_KEY,
273
+ source_id: CTX_SOURCE_ID,
274
+ source_format: CTX_SOURCE_FORMAT,
275
+ path: "history.jsonl",
276
+ enabled: true,
277
+ refresh: "manual"
278
+ }]
279
+ }, null, 2)}\n`;
280
+ }
281
+ //#endregion
282
+ export { CTX_HISTORY_SCHEMA_VERSION, CTX_PROVIDER_KEY, CTX_SOURCE_FORMAT, CTX_SOURCE_ID, buildCtxHistoryJsonl, buildCtxPluginManifest };
@@ -2,8 +2,8 @@ import { createLogger } from "../utils/logger/index.js";
2
2
  import { init_logger } from "../utils/logger.js";
3
3
  import { init_paths, resolveConfigPath } from "../config/paths.js";
4
4
  import { init_loader, loadConfig } from "../config/loader.js";
5
- import { resolveGatewayService } from "../daemon/service.js";
6
5
  import { isRestartEnabled } from "../config/commands.flags.js";
6
+ import { resolveGatewayService } from "../daemon/service.js";
7
7
  import { authorizeGatewaySigusr1Restart, writeGatewayRestartIntentSync } from "./restart.js";
8
8
  import { findVerifiedGatewayListenerPidsOnPortSync, signalVerifiedGatewayPidSync } from "./gateway-processes.js";
9
9
  import { refreshGatewayServiceAfterUpdate } from "./update-service-refresh.js";
@@ -1,6 +1,7 @@
1
1
  import { listKnowledgeSourceItems } from "../storage/sqlite/knowledge-repository.js";
2
2
  import "../storage/sqlite/index.js";
3
3
  import { clusterUnderstandingSignals } from "../user-context/sources/signal-clustering.js";
4
+ import { personIdForIdentity, personIdentityKey, rebuildUserPeopleIndex } from "../user-context/relationships/indexer.js";
4
5
  //#region src/knowledge/connected-understanding-pipeline.ts
5
6
  function logicalEventKey(item) {
6
7
  const value = item.metadata.logicalEventKey;
@@ -96,9 +97,12 @@ function relationshipObservations(items) {
96
97
  const strongIdentity = (signal.email ?? signal.username)?.toLowerCase();
97
98
  if (strongIdentity && owners.has(strongIdentity)) continue;
98
99
  const label = signal.name ?? signal.email ?? signal.username;
99
- const identityKey = signal.email?.toLowerCase() ?? `${String(item.metadata.toolkit ?? item.sourceInstanceId)}:${signal.username?.toLowerCase() ?? signal.name?.toLowerCase()}`;
100
+ const identityKey = personIdentityKey(signal, item.sourceInstanceId);
101
+ const personId = personIdForIdentity(signal, item.sourceInstanceId);
102
+ if (!identityKey || !personId) continue;
100
103
  const entry = grouped.get(identityKey) ?? {
101
104
  label,
105
+ personId,
102
106
  events: /* @__PURE__ */ new Map()
103
107
  };
104
108
  const eventKey = logicalEventKey(item);
@@ -108,8 +112,11 @@ function relationshipObservations(items) {
108
112
  }
109
113
  return [...grouped.values()].sort((left, right) => right.events.size - left.events.size).slice(0, 8).map((entry) => ({
110
114
  class: "relationship",
111
- key: `person:${entry.label.toLocaleLowerCase()}`,
112
- value: { label: entry.label },
115
+ key: `person:${entry.personId}`,
116
+ value: {
117
+ label: entry.label,
118
+ personId: entry.personId
119
+ },
113
120
  items: [...entry.events.values()],
114
121
  windowDays: 90
115
122
  }));
@@ -182,6 +189,7 @@ function renderConnectedObservation(observation) {
182
189
  kind: "relationship",
183
190
  content: `Frequently collaborates with ${label}.`,
184
191
  canonicalKey: `connected:${observation.key}`,
192
+ payload: typeof observation.value.personId === "string" ? { personId: observation.value.personId } : void 0,
185
193
  confidence,
186
194
  importance: .65,
187
195
  explicitness: "inferred",
@@ -227,6 +235,7 @@ var ConnectedUnderstandingPipeline = class {
227
235
  this.memoryManager = memoryManager;
228
236
  }
229
237
  async process(agentId, extractionRunId) {
238
+ rebuildUserPeopleIndex();
230
239
  const observations = deriveConnectedClaimObservations(listKnowledgeSourceItems({
231
240
  agentId,
232
241
  includeDeleted: false,
@@ -4,4 +4,3 @@ export * from './connected-knowledge-pipeline.js';
4
4
  export * from './connected-understanding-pipeline.js';
5
5
  export * from './coordinator.js';
6
6
  export * from './local-folder-adapter.js';
7
- export * from './people-graph.js';
@@ -3,5 +3,4 @@ import { ConnectedKnowledgePipeline } from "./connected-knowledge-pipeline.js";
3
3
  import { ConnectedUnderstandingPipeline, deriveConnectedClaimObservations, renderConnectedObservation } from "./connected-understanding-pipeline.js";
4
4
  import { startConnectedKnowledgeCoordinator } from "./coordinator.js";
5
5
  import { LocalFolderKnowledgeSourceAdapter } from "./local-folder-adapter.js";
6
- import { buildConnectedPeopleGraph } from "./people-graph.js";
7
- export { ConnectedKnowledgePipeline, ConnectedUnderstandingPipeline, KnowledgeIngestionService, LocalFolderKnowledgeSourceAdapter, SqliteKnowledgeSourceStateStore, buildConnectedPeopleGraph, deriveConnectedClaimObservations, renderConnectedObservation, startConnectedKnowledgeCoordinator };
6
+ export { ConnectedKnowledgePipeline, ConnectedUnderstandingPipeline, KnowledgeIngestionService, LocalFolderKnowledgeSourceAdapter, SqliteKnowledgeSourceStateStore, deriveConnectedClaimObservations, renderConnectedObservation, startConnectedKnowledgeCoordinator };
@@ -1,6 +1,12 @@
1
1
  import type { AgentMessage } from '@earendil-works/pi-agent-core';
2
+ export declare function collectMediaUrisFromValues(values: readonly unknown[]): Set<string>;
2
3
  export declare function collectMediaUrisFromMessages(messages: readonly AgentMessage[]): Set<string>;
3
4
  export declare function messagesReferenceMediaUri(messages: readonly AgentMessage[], uri: string): boolean;
5
+ /**
6
+ * A media object remains live while any non-deleted session references it,
7
+ * including archived transcripts retained by a reset of that session key.
8
+ */
9
+ export declare function isMediaUriReferencedByLiveSession(uri: string): boolean;
4
10
  export declare function deleteMediaUris(uris: Iterable<string>): Promise<void>;
5
11
  export declare function deleteMediaUrisNoLongerReferenced(params: {
6
12
  removed: readonly AgentMessage[];
@@ -1,3 +1,5 @@
1
+ import { getSqliteDatabase } from "../storage/sqlite/transaction.js";
2
+ import "../storage/sqlite/index.js";
1
3
  import { tryParseMediaUri } from "./uri.js";
2
4
  import { deleteMediaBuffer } from "./store.js";
3
5
  //#region src/media/session-references.ts
@@ -19,19 +21,38 @@ function collectFromValue(value, out, seen) {
19
21
  const record = value;
20
22
  for (const nested of Object.values(record)) collectFromValue(nested, out, seen);
21
23
  }
22
- function collectMediaUrisFromMessages(messages) {
24
+ function collectMediaUrisFromValues(values) {
23
25
  const out = /* @__PURE__ */ new Set();
24
26
  const seen = /* @__PURE__ */ new WeakSet();
25
- for (const message of messages) collectFromValue(message, out, seen);
27
+ for (const value of values) collectFromValue(value, out, seen);
26
28
  return out;
27
29
  }
30
+ function collectMediaUrisFromMessages(messages) {
31
+ return collectMediaUrisFromValues(messages);
32
+ }
28
33
  function messagesReferenceMediaUri(messages, uri) {
29
34
  return collectMediaUrisFromMessages(messages).has(uri.trim());
30
35
  }
36
+ /**
37
+ * A media object remains live while any non-deleted session references it,
38
+ * including archived transcripts retained by a reset of that session key.
39
+ */
40
+ function isMediaUriReferencedByLiveSession(uri) {
41
+ const normalized = uri.trim();
42
+ if (!normalized) return false;
43
+ const escaped = JSON.stringify(normalized).replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_");
44
+ return getSqliteDatabase().prepare(`SELECT 1 AS referenced
45
+ FROM transcript_entries e
46
+ JOIN transcripts t ON t.session_id = e.session_id
47
+ JOIN sessions s ON s.session_key = t.session_key
48
+ WHERE e.payload_json LIKE ? ESCAPE '\\'
49
+ LIMIT 1`).get(`%${escaped}%`)?.referenced === 1;
50
+ }
31
51
  async function deleteMediaUris(uris) {
32
52
  for (const uri of new Set(uris)) {
33
53
  const parsed = tryParseMediaUri(uri);
34
54
  if (!parsed) continue;
55
+ if (isMediaUriReferencedByLiveSession(uri)) continue;
35
56
  await deleteMediaBuffer(parsed.id, parsed.bucket).catch(() => {});
36
57
  }
37
58
  }
@@ -43,4 +64,4 @@ async function deleteMediaUrisNoLongerReferenced(params) {
43
64
  await deleteMediaUris(removedUris);
44
65
  }
45
66
  //#endregion
46
- export { collectMediaUrisFromMessages, deleteMediaUris, deleteMediaUrisNoLongerReferenced, messagesReferenceMediaUri };
67
+ export { collectMediaUrisFromMessages, collectMediaUrisFromValues, deleteMediaUris, deleteMediaUrisNoLongerReferenced, isMediaUriReferencedByLiveSession, messagesReferenceMediaUri };
@@ -0,0 +1,16 @@
1
+ import { type NotificationDevice, type NotificationDevicePlatform, type NotificationLanguage, type NotificationPermission, type NotificationPreferences } from './types.js';
2
+ export type RegisterNotificationDeviceInput = {
3
+ id: string;
4
+ platform: NotificationDevicePlatform;
5
+ pushToken: string;
6
+ permissions: NotificationPermission;
7
+ preferences?: Partial<NotificationPreferences>;
8
+ locale: NotificationLanguage;
9
+ appVersion?: string;
10
+ };
11
+ export declare function registerNotificationDevice(input: RegisterNotificationDeviceInput): NotificationDevice;
12
+ export declare function getNotificationDevice(deviceId: string): NotificationDevice | null;
13
+ export declare function listDeliverableNotificationDevices(now?: number): NotificationDevice[];
14
+ export declare function updateNotificationDevicePreferences(deviceId: string, patch: Partial<NotificationPreferences>): NotificationDevice | null;
15
+ export declare function removeNotificationDevice(deviceId: string): boolean;
16
+ export declare function disableNotificationDeviceForPushToken(pushToken: string): void;
@@ -0,0 +1,96 @@
1
+ import { getSqliteDatabase, runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
2
+ import { DEFAULT_NOTIFICATION_PREFERENCES } from "./types.js";
3
+ //#region src/notifications/device-store.ts
4
+ const DEVICE_LEASE_MS = 10080 * 60 * 1e3;
5
+ function parsePreferences(raw) {
6
+ try {
7
+ const value = JSON.parse(raw);
8
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
9
+ } catch {
10
+ return {};
11
+ }
12
+ }
13
+ function normalizePreferences(input) {
14
+ return {
15
+ chatCompleted: input?.chatCompleted ?? DEFAULT_NOTIFICATION_PREFERENCES.chatCompleted,
16
+ chatFailed: input?.chatFailed ?? DEFAULT_NOTIFICATION_PREFERENCES.chatFailed,
17
+ taskNeedsInput: input?.taskNeedsInput ?? DEFAULT_NOTIFICATION_PREFERENCES.taskNeedsInput,
18
+ taskBlocked: input?.taskBlocked ?? DEFAULT_NOTIFICATION_PREFERENCES.taskBlocked,
19
+ taskFailed: input?.taskFailed ?? DEFAULT_NOTIFICATION_PREFERENCES.taskFailed,
20
+ taskCompleted: input?.taskCompleted ?? DEFAULT_NOTIFICATION_PREFERENCES.taskCompleted,
21
+ automationCompleted: input?.automationCompleted ?? DEFAULT_NOTIFICATION_PREFERENCES.automationCompleted,
22
+ automationFailed: input?.automationFailed ?? DEFAULT_NOTIFICATION_PREFERENCES.automationFailed,
23
+ proactiveInsight: input?.proactiveInsight ?? DEFAULT_NOTIFICATION_PREFERENCES.proactiveInsight
24
+ };
25
+ }
26
+ function deviceFromRow(row) {
27
+ return {
28
+ id: row.device_id,
29
+ platform: row.platform === "android" ? "android" : "ios",
30
+ pushToken: row.push_token,
31
+ enabled: row.enabled !== 0,
32
+ permissions: row.permissions === "granted" || row.permissions === "denied" ? row.permissions : "unknown",
33
+ preferences: normalizePreferences(parsePreferences(row.preferences_json)),
34
+ locale: row.locale === "zh" ? "zh" : "en",
35
+ appVersion: row.app_version ?? void 0,
36
+ leaseExpiresAt: row.lease_expires_at,
37
+ lastSeenAt: row.last_seen_at,
38
+ createdAt: row.created_at,
39
+ updatedAt: row.updated_at
40
+ };
41
+ }
42
+ function registerNotificationDevice(input) {
43
+ const now = Date.now();
44
+ const preferences = input.preferences ? normalizePreferences(input.preferences) : getNotificationDevice(input.id)?.preferences ?? normalizePreferences();
45
+ runSqliteWriteTransaction((db) => {
46
+ db.prepare(`DELETE FROM notification_devices
47
+ WHERE push_provider = 'expo' AND push_token = ? AND device_id <> ?`).run(input.pushToken, input.id);
48
+ db.prepare(`INSERT INTO notification_devices (
49
+ device_id, platform, push_token, enabled, permissions, preferences_json,
50
+ locale, app_version, lease_expires_at, last_seen_at, created_at, updated_at
51
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
52
+ ON CONFLICT(device_id) DO UPDATE SET
53
+ platform = excluded.platform,
54
+ push_token = excluded.push_token,
55
+ enabled = excluded.enabled,
56
+ permissions = excluded.permissions,
57
+ preferences_json = excluded.preferences_json,
58
+ locale = excluded.locale,
59
+ app_version = excluded.app_version,
60
+ lease_expires_at = excluded.lease_expires_at,
61
+ last_seen_at = excluded.last_seen_at,
62
+ updated_at = excluded.updated_at`).run(input.id, input.platform, input.pushToken, input.permissions === "granted" ? 1 : 0, input.permissions, JSON.stringify(preferences), input.locale, input.appVersion?.trim() || null, now + DEVICE_LEASE_MS, now, now, now);
63
+ });
64
+ return getNotificationDevice(input.id);
65
+ }
66
+ function getNotificationDevice(deviceId) {
67
+ const row = getSqliteDatabase().prepare("SELECT * FROM notification_devices WHERE device_id = ?").get(deviceId);
68
+ return row ? deviceFromRow(row) : null;
69
+ }
70
+ function listDeliverableNotificationDevices(now = Date.now()) {
71
+ return getSqliteDatabase().prepare(`SELECT * FROM notification_devices
72
+ WHERE enabled = 1 AND permissions = 'granted' AND lease_expires_at > ?
73
+ ORDER BY updated_at DESC`).all(now).map(deviceFromRow);
74
+ }
75
+ function updateNotificationDevicePreferences(deviceId, patch) {
76
+ const current = getNotificationDevice(deviceId);
77
+ if (!current) return null;
78
+ const preferences = normalizePreferences({
79
+ ...current.preferences,
80
+ ...patch
81
+ });
82
+ runSqliteWriteTransaction((db) => {
83
+ db.prepare("UPDATE notification_devices SET preferences_json = ?, updated_at = ? WHERE device_id = ?").run(JSON.stringify(preferences), Date.now(), deviceId);
84
+ });
85
+ return getNotificationDevice(deviceId);
86
+ }
87
+ function removeNotificationDevice(deviceId) {
88
+ return runSqliteWriteTransaction((db) => db.prepare("DELETE FROM notification_devices WHERE device_id = ?").run(deviceId).changes > 0);
89
+ }
90
+ function disableNotificationDeviceForPushToken(pushToken) {
91
+ runSqliteWriteTransaction((db) => {
92
+ db.prepare("UPDATE notification_devices SET enabled = 0, updated_at = ? WHERE push_token = ?").run(Date.now(), pushToken);
93
+ });
94
+ }
95
+ //#endregion
96
+ export { disableNotificationDeviceForPushToken, getNotificationDevice, listDeliverableNotificationDevices, registerNotificationDevice, removeNotificationDevice, updateNotificationDevicePreferences };
@@ -0,0 +1,6 @@
1
+ import { type ProductNotification } from '@xopcai/gateway-contract';
2
+ export type NotificationPlan = {
3
+ dedupeKey: string;
4
+ notification: Omit<ProductNotification, 'schemaVersion' | 'id' | 'createdAt'>;
5
+ };
6
+ export declare function notificationPlanFromGatewayEvent(type: string, payload: unknown): NotificationPlan | null;