@xopcai/xopc 0.0.227 → 0.0.229

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 (249) 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-BevwE16n.js → activity-store-M0nAY5XB.js} +1 -1
  4. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-BggVQrEZ.js → agent-avatar-dicebear-CsFAJTIN.js} +1 -1
  5. package/dist/gateway/static/root/assets/agents-DA4b6_0C.js +68 -0
  6. package/dist/gateway/static/root/assets/{api-Bx_BpIt8.js → api-BywYxpaS.js} +1 -1
  7. package/dist/gateway/static/root/assets/{app-management-settings-panel-BtKP9n2r.js → app-management-settings-panel-7KcMWuah.js} +1 -1
  8. package/dist/gateway/static/root/assets/{appearance-settings-BoH2NAUX.js → appearance-settings-Cqlw7Fif.js} +1 -1
  9. package/dist/gateway/static/root/assets/apps-page-CsnhdGZ2.js +1 -0
  10. package/dist/gateway/static/root/assets/{archive-plugin-DnceUyxm.js → archive-plugin-7_DXaMTP.js} +1 -1
  11. package/dist/gateway/static/root/assets/{attachment-load-Bh8_9gkr.js → attachment-load-q4pDpqkF.js} +1 -1
  12. package/dist/gateway/static/root/assets/{automations-page-ZKeAAVxD.js → automations-page-BuTqjKGX.js} +1 -1
  13. package/dist/gateway/static/root/assets/automations-workspace-CD0E2ThS.js +6 -0
  14. package/dist/gateway/static/root/assets/{binary-plugins-Cy9er6w-.js → binary-plugins-DUce1Bsc.js} +1 -1
  15. package/dist/gateway/static/root/assets/{block-editor-Cc0wWvNX.js → block-editor-D8Ejp-QR.js} +3 -3
  16. package/dist/gateway/static/root/assets/browser-settings-page-CY1k3MMd.js +8 -0
  17. package/dist/gateway/static/root/assets/{browser-workflow-inputs-BS7rpLLl.js → browser-workflow-inputs-8NC2qDRF.js} +1 -1
  18. package/dist/gateway/static/root/assets/{browser-workflows-page-BdBVUZXm.js → browser-workflows-page-BDvZbR9v.js} +2 -2
  19. package/dist/gateway/static/root/assets/capabilities-settings-panel-D1VfotHQ.js +7 -0
  20. package/dist/gateway/static/root/assets/{capability-presets-api-D1Lqz0Tt.js → capability-presets-api-o0y9WgAI.js} +1 -1
  21. package/dist/gateway/static/root/assets/capability-presets-settings-panel-DXHqRLpU.js +3 -0
  22. package/dist/gateway/static/root/assets/{channel-recipient-api-CGuEl-l2.js → channel-recipient-api-C5ABr73b.js} +1 -1
  23. package/dist/gateway/static/root/assets/channels-settings-DrFL57M_.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-CdX_7aQh.js → chat-terminal-dock-h1CKexNz.js} +2 -2
  26. package/dist/gateway/static/root/assets/connectors-page-DUAyfFCF.js +2 -0
  27. package/dist/gateway/static/root/assets/{cron-expression-BnSQc4Ek.js → cron-expression-DlaQQOId.js} +1 -1
  28. package/dist/gateway/static/root/assets/{date-picker-BbuG7zQ_.js → date-picker-8R1sXJKb.js} +1 -1
  29. package/dist/gateway/static/root/assets/{dependency-picker-snZeqOmG.js → dependency-picker-CWur0821.js} +1 -1
  30. package/dist/gateway/static/root/assets/{desktop-pet-DJQ8JoR1.js → desktop-pet-Knl-zNDU.js} +1 -1
  31. package/dist/gateway/static/root/assets/{desktop-pet-settings-B9txsRtv.js → desktop-pet-settings-CAQMLVtd.js} +1 -1
  32. package/dist/gateway/static/root/assets/{directory-picker-path-field-D7WKWkNB.js → directory-picker-path-field-Caaz4Rw5.js} +1 -1
  33. package/dist/gateway/static/root/assets/{extension-debug-page-D_7cOCj2.js → extension-debug-page-B965Mc15.js} +1 -1
  34. package/dist/gateway/static/root/assets/{extension-page-Bwu47iUb.js → extension-page-C0UnkBi5.js} +1 -1
  35. package/dist/gateway/static/root/assets/{extension-settings-page-Dwg4dSZM.js → extension-settings-page-BUSIAZCR.js} +1 -1
  36. package/dist/gateway/static/root/assets/{gateway-config-api-jURUSte_.js → gateway-config-api-DkH0eEgy.js} +1 -1
  37. package/dist/gateway/static/root/assets/{gateway-config-swr-6SQXF1yR.js → gateway-config-swr-BWGjnmDl.js} +1 -1
  38. package/dist/gateway/static/root/assets/{gateway-settings-UklJOMLX.js → gateway-settings-BBgLDm6I.js} +1 -1
  39. package/dist/gateway/static/root/assets/{gateway-startup-retry-DqWaxyvZ.js → gateway-startup-retry-B_zrtccP.js} +1 -1
  40. package/dist/gateway/static/root/assets/{heartbeat-settings-Djs_nduD.js → heartbeat-settings-BtEHVXWD.js} +1 -1
  41. package/dist/gateway/static/root/assets/home-page-5SU2TNJj.js +1 -0
  42. package/dist/gateway/static/root/assets/{image-generation-api-Bxmt6LEr.js → image-generation-api-YKP2me5Z.js} +1 -1
  43. package/dist/gateway/static/root/assets/index-BNWhgmjs.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-ByVsCH3d.js → keyboard-shortcuts-settings-CsRz8NrS.js} +1 -1
  46. package/dist/gateway/static/root/assets/local-app-workbench-page-DYPEUgpP.js +2 -0
  47. package/dist/gateway/static/root/assets/{local-apps-page-D19R-xsz.js → local-apps-page-CTBNUG4a.js} +1 -1
  48. package/dist/gateway/static/root/assets/{locale-store-Ci1Qi0Q-.js → locale-store-CXhjfe4q.js} +1 -1
  49. package/dist/gateway/static/root/assets/logs-page-B0P7rG2B.js +2 -0
  50. package/dist/gateway/static/root/assets/{management-settings-G0y7zmfY.js → management-settings-YqUAEwQo.js} +1 -1
  51. package/dist/gateway/static/root/assets/{markdown-split-B69d2HWt.js → markdown-split-dxpSCXb8.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-6TWEQgN_.js → media-plugins-CJ00i1Lg.js} +1 -1
  55. package/dist/gateway/static/root/assets/messages-BMdD0LWk.js +3 -0
  56. package/dist/gateway/static/root/assets/{notes-api-DLsnnZCS.js → notes-api-BFsOGYkd.js} +1 -1
  57. package/dist/gateway/static/root/assets/{notes-page-CkRJJKDm.js → notes-page-DEnbnkIt.js} +1 -1
  58. package/dist/gateway/static/root/assets/notes-workbench-DmVYu_Oc.js +4 -0
  59. package/dist/gateway/static/root/assets/{product-open-page-HxArIvoN.js → product-open-page-BHxo9U95.js} +1 -1
  60. package/dist/gateway/static/root/assets/project-detail-page-DV2nGmsq.js +1 -0
  61. package/dist/gateway/static/root/assets/projects-page-BNIFwFfc.js +1 -0
  62. package/dist/gateway/static/root/assets/{registry-api-lEar5FOV.js → registry-api-BFZrVYfy.js} +1 -1
  63. package/dist/gateway/static/root/assets/remote-access-hub-DLXqKiA_.js +1 -0
  64. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-Bf03UNGD.js → runtime-tools-settings-panel-BpQ2GI-0.js} +1 -1
  65. package/dist/gateway/static/root/assets/{schema-form-CIXMBgx5.js → schema-form-HE1G26Lx.js} +1 -1
  66. package/dist/gateway/static/root/assets/sessions-page-DWaXf_25.js +1 -0
  67. package/dist/gateway/static/root/assets/settings-advanced-gate-JFCbnCL2.js +1 -0
  68. package/dist/gateway/static/root/assets/{settings-form-section-DgdB26mE.js → settings-form-section-CXAn0Xhy.js} +1 -1
  69. package/dist/gateway/static/root/assets/{settings-loading-skeleton-efk5W6lG.js → settings-loading-skeleton-DsFu7Bcv.js} +1 -1
  70. package/dist/gateway/static/root/assets/{settings-page-BHVf4gEP.js → settings-page-C4iib9nO.js} +1 -1
  71. package/dist/gateway/static/root/assets/{setup-status-panel-WK0dGNMv.js → setup-status-panel-BA83Wr1d.js} +1 -1
  72. package/dist/gateway/static/root/assets/{share-preview-page-Cod-w2qm.js → share-preview-page-BOGl3s6M.js} +1 -1
  73. package/dist/gateway/static/root/assets/{shares-settings-CUqQ31gt.js → shares-settings-Q2MdGaBi.js} +1 -1
  74. package/dist/gateway/static/root/assets/skill-reload-api-cFtfVeFU.js +1 -0
  75. package/dist/gateway/static/root/assets/{skills-page-DwCcipqK.js → skills-page-CdaFtNw9.js} +1 -1
  76. package/dist/gateway/static/root/assets/{skills-page.utils-3WSbtl3S.js → skills-page.utils-DYaAddUL.js} +1 -1
  77. package/dist/gateway/static/root/assets/{system-settings-panel-C5VxqC_s.js → system-settings-panel-Co7gilRd.js} +1 -1
  78. package/dist/gateway/static/root/assets/task-detail-page-5B1pswu8.js +1 -0
  79. package/dist/gateway/static/root/assets/{theme-store-Dd89g_ki.js → theme-store-DOdCjyuw.js} +1 -1
  80. package/dist/gateway/static/root/assets/{use-autosave-CeV8VJh2.js → use-autosave-DnnZtMpA.js} +1 -1
  81. package/dist/gateway/static/root/assets/user-context-page-Dr1FKjXv.js +1 -0
  82. package/dist/gateway/static/root/assets/{voice-api-key-field-EmJ-8aZu.js → voice-api-key-field-wetUpi3i.js} +1 -1
  83. package/dist/gateway/static/root/assets/{work-discovery-overlay-zsk6E3Mi.js → work-discovery-overlay-tbTWASJJ.js} +1 -1
  84. package/dist/gateway/static/root/assets/workflow-run-setup-panel-Dy0fDs8w.js +3 -0
  85. package/dist/gateway/static/root/assets/workflows-page-IVP3GSkI.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-instance-gateway.d.ts +1 -1
  92. package/dist/src/agent/agent-manager.d.ts +1 -1
  93. package/dist/src/agent/agent-manager.js +18 -3
  94. package/dist/src/agent/background-review/prompts.d.ts +8 -2
  95. package/dist/src/agent/background-review/prompts.js +25 -12
  96. package/dist/src/agent/background-review/run-background-review.d.ts +7 -2
  97. package/dist/src/agent/background-review/run-background-review.js +175 -160
  98. package/dist/src/agent/lifecycle/timeout-wrapper.d.ts +1 -0
  99. package/dist/src/agent/lifecycle/timeout-wrapper.js +7 -6
  100. package/dist/src/agent/mcp/mcp-transport-config.d.ts +1 -1
  101. package/dist/src/agent/mcp/mcp-transport-config.js +1 -1
  102. package/dist/src/agent/memory/index.d.ts +1 -1
  103. package/dist/src/agent/memory/index.js +2 -2
  104. package/dist/src/agent/memory/manager.d.ts +1 -11
  105. package/dist/src/agent/memory/manager.js +0 -16
  106. package/dist/src/agent/memory/understanding/service.d.ts +0 -11
  107. package/dist/src/agent/memory/understanding/service.js +7 -173
  108. package/dist/src/agent/memory/user-context-coordinator.d.ts +1 -3
  109. package/dist/src/agent/memory/user-context-coordinator.js +3 -66
  110. package/dist/src/agent/orchestration/agent-orchestrator.js +1 -1
  111. package/dist/src/agent/orchestration/run-agent-turn-with-timeout.d.ts +2 -2
  112. package/dist/src/agent/orchestration/run-agent-turn-with-timeout.js +4 -4
  113. package/dist/src/agent/service/direct-turn-helpers.js +1 -1
  114. package/dist/src/agent/tools/create-share-tool.js +14 -1
  115. package/dist/src/agent/tools/exec-command.js +3 -3
  116. package/dist/src/agent/tools/execute-code-tool.js +1 -1
  117. package/dist/src/agent/tools/executor.js +2 -2
  118. package/dist/src/agent/tools/image-generate-tool.js +12 -0
  119. package/dist/src/agent/tools/send-media.js +11 -0
  120. package/dist/src/agent/tools/xopc-use-tool.js +1 -1
  121. package/dist/src/cli/command-loaders.js +1 -0
  122. package/dist/src/cli/command-manifest.js +4 -0
  123. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  124. package/dist/src/cli/commands/history.d.ts +1 -0
  125. package/dist/src/cli/commands/history.js +50 -0
  126. package/dist/src/config/agent-profile.js +1 -1
  127. package/dist/src/config/schema.d.ts +4 -0
  128. package/dist/src/config/schema.js +1 -0
  129. package/dist/src/connectors/connected-source-understanding.js +35 -21
  130. package/dist/src/connectors/learning-coordinator.js +1 -42
  131. package/dist/src/gateway/agents-admin.js +1 -1
  132. package/dist/src/gateway/chat-stream/mapper.d.ts +2 -0
  133. package/dist/src/gateway/chat-stream/mapper.js +5 -0
  134. package/dist/src/gateway/chat-stream/protocol.d.ts +3 -2
  135. package/dist/src/gateway/chat-stream/turn-outcome-collector.d.ts +16 -0
  136. package/dist/src/gateway/chat-stream/turn-outcome-collector.js +111 -0
  137. package/dist/src/gateway/hono/lib/static-ui.js +7 -5
  138. package/dist/src/gateway/hono/routes/index.js +2 -0
  139. package/dist/src/gateway/hono/routes/mobile.js +18 -40
  140. package/dist/src/gateway/hono/routes/notifications.d.ts +3 -0
  141. package/dist/src/gateway/hono/routes/notifications.js +45 -0
  142. package/dist/src/gateway/hono/routes/sessions.js +31 -0
  143. package/dist/src/gateway/hono/routes/tasks.js +1 -1
  144. package/dist/src/gateway/hono/routes/understanding-sources.js +4 -1
  145. package/dist/src/gateway/hono/routes/you.js +29 -11
  146. package/dist/src/gateway/service/agent-runner.js +6 -2
  147. package/dist/src/gateway/service/run-gateway-agent.js +58 -2
  148. package/dist/src/gateway/service/sessions-api.d.ts +6 -0
  149. package/dist/src/gateway/service/sessions-api.js +60 -3
  150. package/dist/src/gateway/service.d.ts +2 -2
  151. package/dist/src/gateway/service.js +8 -7
  152. package/dist/src/history/ctx/exporter.d.ts +14 -0
  153. package/dist/src/history/ctx/exporter.js +99 -0
  154. package/dist/src/history/ctx/format.d.ts +25 -0
  155. package/dist/src/history/ctx/format.js +282 -0
  156. package/dist/src/knowledge/index.d.ts +0 -1
  157. package/dist/src/knowledge/index.js +1 -2
  158. package/dist/src/media/session-references.d.ts +6 -0
  159. package/dist/src/media/session-references.js +24 -3
  160. package/dist/src/notifications/device-store.d.ts +16 -0
  161. package/dist/src/notifications/device-store.js +96 -0
  162. package/dist/src/notifications/planner.d.ts +6 -0
  163. package/dist/src/notifications/planner.js +160 -0
  164. package/dist/src/notifications/service.d.ts +18 -0
  165. package/dist/src/notifications/service.js +184 -0
  166. package/dist/src/notifications/store.d.ts +53 -0
  167. package/dist/src/notifications/store.js +184 -0
  168. package/dist/src/notifications/types.d.ts +29 -0
  169. package/dist/src/notifications/types.js +14 -0
  170. package/dist/src/session/client-history.d.ts +3 -1
  171. package/dist/src/session/client-history.js +13 -0
  172. package/dist/src/session/index.d.ts +1 -1
  173. package/dist/src/session/manager.d.ts +1 -0
  174. package/dist/src/session/manager.js +6 -0
  175. package/dist/src/session/store.d.ts +15 -0
  176. package/dist/src/session/store.js +58 -0
  177. package/dist/src/storage/sqlite/index.d.ts +2 -2
  178. package/dist/src/storage/sqlite/index.js +3 -3
  179. package/dist/src/storage/sqlite/migrations/136_product_notifications.sql +79 -0
  180. package/dist/src/storage/sqlite/migrations/137_understanding_status_audit.sql +71 -0
  181. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  182. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  183. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  184. package/dist/src/storage/sqlite/schema.js +1 -1
  185. package/dist/src/storage/sqlite/session-input-repository.d.ts +5 -0
  186. package/dist/src/storage/sqlite/session-input-repository.js +9 -1
  187. package/dist/src/storage/sqlite/user-context-repository.d.ts +16 -4
  188. package/dist/src/storage/sqlite/user-context-repository.js +56 -12
  189. package/dist/src/tasks/index.js +2 -2
  190. package/dist/src/tasks/task-application-service.js +42 -6
  191. package/dist/src/tasks/task-change-events.d.ts +9 -0
  192. package/dist/src/tasks/task-change-events.js +17 -1
  193. package/dist/src/tasks/task-outbox-dispatcher.js +5 -1
  194. package/dist/src/tasks/task-run-dispatcher.js +4 -2
  195. package/dist/src/tasks/task-run-repository.js +13 -0
  196. package/dist/src/user-context/config.d.ts +8 -0
  197. package/dist/src/user-context/config.js +3 -0
  198. package/dist/src/user-context/consolidation.js +16 -4
  199. package/dist/src/user-context/extraction/executor.d.ts +22 -0
  200. package/dist/src/user-context/extraction/executor.js +142 -0
  201. package/dist/src/user-context/extraction/registry.d.ts +1 -1
  202. package/dist/src/user-context/extraction/registry.js +5 -34
  203. package/dist/src/user-context/extraction/repair.js +4 -1
  204. package/dist/src/user-context/extraction/semantic.d.ts +18 -0
  205. package/dist/src/user-context/extraction/semantic.js +148 -0
  206. package/dist/src/user-context/understandingQuality.js +4 -0
  207. package/dist/src/work-discovery/analyzer.js +9 -4
  208. package/dist/src/work-discovery/service.d.ts +1 -0
  209. package/dist/src/work-discovery/service.js +5 -4
  210. package/dist/src/work-discovery/types.d.ts +1 -0
  211. package/package.json +14 -8
  212. package/dist/gateway/static/root/assets/agents-TJpjRHnr.js +0 -68
  213. package/dist/gateway/static/root/assets/apps-page-CZ4Da51l.js +0 -1
  214. package/dist/gateway/static/root/assets/automations-workspace-2Y00PvJU.js +0 -6
  215. package/dist/gateway/static/root/assets/browser-settings-page-CbJD27_j.js +0 -8
  216. package/dist/gateway/static/root/assets/capabilities-settings-panel-Dv8y6OuP.js +0 -7
  217. package/dist/gateway/static/root/assets/capability-presets-settings-panel-TZmjzWzt.js +0 -3
  218. package/dist/gateway/static/root/assets/channels-settings-g-68dutA.js +0 -1
  219. package/dist/gateway/static/root/assets/channels-status-swr-D7KsAeWh.js +0 -1
  220. package/dist/gateway/static/root/assets/connectors-page-DXA0rAvQ.js +0 -2
  221. package/dist/gateway/static/root/assets/home-page-i6bXCHdo.js +0 -1
  222. package/dist/gateway/static/root/assets/index-BBBE89vm.css +0 -2
  223. package/dist/gateway/static/root/assets/index-BsD5wygl.js +0 -74
  224. package/dist/gateway/static/root/assets/local-app-workbench-page-BCKEDMlP.js +0 -2
  225. package/dist/gateway/static/root/assets/logs-page-Cy9YQK7j.js +0 -2
  226. package/dist/gateway/static/root/assets/markdown-view-CBM-v2ph.js +0 -69
  227. package/dist/gateway/static/root/assets/mermaid-render-BbqDc_LQ.js +0 -134
  228. package/dist/gateway/static/root/assets/messages-CtOJwGW3.js +0 -3
  229. package/dist/gateway/static/root/assets/notes-workbench-B10T3hGX.js +0 -4
  230. package/dist/gateway/static/root/assets/project-detail-page-DsqRlgsy.js +0 -1
  231. package/dist/gateway/static/root/assets/projects-page-Bu2PN8OK.js +0 -1
  232. package/dist/gateway/static/root/assets/remote-access-hub-CXACdJ2f.js +0 -1
  233. package/dist/gateway/static/root/assets/sessions-page-CS2knWDT.js +0 -1
  234. package/dist/gateway/static/root/assets/settings-advanced-gate-ByFGk0q_.js +0 -1
  235. package/dist/gateway/static/root/assets/skill-reload-api-DweAPjI8.js +0 -1
  236. package/dist/gateway/static/root/assets/task-detail-page-DEj-BFMP.js +0 -1
  237. package/dist/gateway/static/root/assets/user-context-page-f7yh7aD5.js +0 -1
  238. package/dist/gateway/static/root/assets/workflow-run-setup-panel-CBAYvm3G.js +0 -3
  239. package/dist/gateway/static/root/assets/workflows-page-BN0JmHrG.js +0 -5
  240. package/dist/src/agent/memory/understanding/correction.d.ts +0 -3
  241. package/dist/src/agent/memory/understanding/correction.js +0 -39
  242. package/dist/src/knowledge/connected-understanding-pipeline.d.ts +0 -17
  243. package/dist/src/knowledge/connected-understanding-pipeline.js +0 -278
  244. package/dist/src/mobile/notification-service.d.ts +0 -9
  245. package/dist/src/mobile/notification-service.js +0 -156
  246. package/dist/src/mobile/notification-store.d.ts +0 -28
  247. package/dist/src/mobile/notification-store.js +0 -132
  248. package/dist/src/mobile/notification-types.d.ts +0 -37
  249. package/dist/src/mobile/notification-types.js +0 -9
@@ -0,0 +1,184 @@
1
+ import { createLogger } from "../utils/logger/index.js";
2
+ import { init_logger } from "../utils/logger.js";
3
+ import { localizeNotification } from "../../packages/gateway-contract/src/notifications.js";
4
+ import { disableNotificationDeviceForPushToken, listDeliverableNotificationDevices } from "./device-store.js";
5
+ import { notificationPlanFromGatewayEvent } from "./planner.js";
6
+ import { createNotificationEvent, expireUndeliverableNotificationDeliveries, listDueNotificationDeliveries, markNotificationDeliveryAccepted, markNotificationDeliveryDead, markNotificationDeliveryDelivered, pruneNotificationEvents, rescheduleNotificationDelivery } from "./store.js";
7
+ //#region src/notifications/service.ts
8
+ init_logger();
9
+ const log = createLogger("Notifications");
10
+ const EXPO_PUSH_URL = "https://exp.host/--/api/v2/push/send";
11
+ const EXPO_RECEIPTS_URL = "https://exp.host/--/api/v2/push/getReceipts";
12
+ const MAX_DELIVERY_ATTEMPTS = 5;
13
+ const RETRY_DELAYS_MS = [
14
+ 3e4,
15
+ 12e4,
16
+ 6e5,
17
+ 36e5,
18
+ 216e5
19
+ ];
20
+ const RECEIPT_DELAY_MS = 15 * 6e4;
21
+ function preferenceAllows(type, preferences) {
22
+ switch (type) {
23
+ case "chat.completed": return preferences.chatCompleted;
24
+ case "chat.failed": return preferences.chatFailed;
25
+ case "task.needs_input": return preferences.taskNeedsInput;
26
+ case "task.blocked": return preferences.taskBlocked;
27
+ case "task.failed": return preferences.taskFailed;
28
+ case "task.completed": return preferences.taskCompleted;
29
+ case "automation.completed": return preferences.automationCompleted;
30
+ case "automation.failed": return preferences.automationFailed;
31
+ case "proactive.insight": return preferences.proactiveInsight;
32
+ }
33
+ }
34
+ function retryAt(attempts, now) {
35
+ return now + RETRY_DELAYS_MS[Math.min(attempts, RETRY_DELAYS_MS.length - 1)];
36
+ }
37
+ function expoError(result) {
38
+ return result.details?.error || result.message || "Expo rejected the notification";
39
+ }
40
+ var NotificationService = class {
41
+ timer = null;
42
+ draining = false;
43
+ lastMaintenanceAt = 0;
44
+ constructor(options) {
45
+ this.options = options;
46
+ }
47
+ start() {
48
+ if (this.timer) return;
49
+ this.drain();
50
+ this.timer = setInterval(() => void this.drain(), 3e4);
51
+ this.timer.unref?.();
52
+ }
53
+ stop() {
54
+ if (this.timer) clearInterval(this.timer);
55
+ this.timer = null;
56
+ }
57
+ handleGatewayEvent(type, payload) {
58
+ try {
59
+ const plan = notificationPlanFromGatewayEvent(type, payload);
60
+ if (!plan) return;
61
+ const devices = listDeliverableNotificationDevices().filter((device) => preferenceAllows(plan.notification.type, device.preferences));
62
+ const result = createNotificationEvent({
63
+ ...plan,
64
+ deviceIds: devices.map((device) => device.id)
65
+ });
66
+ if (!result.created) return;
67
+ this.options.publish("notification.created", result.notification);
68
+ this.drain();
69
+ } catch (err) {
70
+ log.error({
71
+ err,
72
+ eventType: type
73
+ }, "Notification event persistence failed");
74
+ }
75
+ }
76
+ async drain() {
77
+ if (this.draining) return;
78
+ this.draining = true;
79
+ try {
80
+ const now = Date.now();
81
+ if (now - this.lastMaintenanceAt >= 3600 * 1e3) {
82
+ expireUndeliverableNotificationDeliveries(now);
83
+ pruneNotificationEvents(now - 720 * 60 * 60 * 1e3);
84
+ this.lastMaintenanceAt = now;
85
+ }
86
+ await this.deliverPending();
87
+ await this.checkReceipts();
88
+ } catch (err) {
89
+ log.warn({ err }, "Notification delivery pass failed");
90
+ } finally {
91
+ this.draining = false;
92
+ }
93
+ }
94
+ async deliverPending() {
95
+ const deliveries = listDueNotificationDeliveries("pending");
96
+ await Promise.all(deliveries.map((delivery) => this.send(delivery)));
97
+ }
98
+ async send(delivery) {
99
+ const fetchImpl = this.options.fetch ?? fetch;
100
+ const localized = localizeNotification(delivery.event, delivery.locale);
101
+ try {
102
+ const response = await fetchImpl(EXPO_PUSH_URL, {
103
+ method: "POST",
104
+ headers: {
105
+ "content-type": "application/json",
106
+ accept: "application/json"
107
+ },
108
+ signal: AbortSignal.timeout(1e4),
109
+ body: JSON.stringify({
110
+ to: delivery.pushToken,
111
+ title: localized.localizedTitle,
112
+ body: localized.localizedBody,
113
+ sound: delivery.event.priority === "high" ? "default" : void 0,
114
+ priority: delivery.event.priority,
115
+ data: {
116
+ eventId: delivery.event.id,
117
+ target: delivery.event.target,
118
+ ...delivery.event.payload
119
+ }
120
+ })
121
+ });
122
+ if (!response.ok) throw new Error(`Expo push request failed (${response.status})`);
123
+ const body = await response.json();
124
+ const result = Array.isArray(body.data) ? body.data[0] : body.data;
125
+ if (result?.status !== "ok" || !result.id) {
126
+ const error = expoError(result ?? {});
127
+ if (result?.details?.error === "DeviceNotRegistered") {
128
+ disableNotificationDeviceForPushToken(delivery.pushToken);
129
+ markNotificationDeliveryDead(delivery.event.id, delivery.deviceId, error);
130
+ return;
131
+ }
132
+ throw new Error(error);
133
+ }
134
+ markNotificationDeliveryAccepted(delivery.event.id, delivery.deviceId, result.id, Date.now() + RECEIPT_DELAY_MS);
135
+ } catch (err) {
136
+ this.retryOrFail(delivery, err);
137
+ }
138
+ }
139
+ async checkReceipts() {
140
+ const withTickets = listDueNotificationDeliveries("accepted").filter((delivery) => delivery.providerTicketId);
141
+ if (withTickets.length === 0) return;
142
+ const fetchImpl = this.options.fetch ?? fetch;
143
+ try {
144
+ const response = await fetchImpl(EXPO_RECEIPTS_URL, {
145
+ method: "POST",
146
+ headers: {
147
+ "content-type": "application/json",
148
+ accept: "application/json"
149
+ },
150
+ signal: AbortSignal.timeout(1e4),
151
+ body: JSON.stringify({ ids: withTickets.map((delivery) => delivery.providerTicketId) })
152
+ });
153
+ if (!response.ok) throw new Error(`Expo receipt request failed (${response.status})`);
154
+ const body = await response.json();
155
+ for (const delivery of withTickets) {
156
+ const result = body.data?.[delivery.providerTicketId];
157
+ if (!result) this.retryOrFail(delivery, /* @__PURE__ */ new Error("Expo receipt is not ready"), "accepted");
158
+ else if (result.status === "ok") markNotificationDeliveryDelivered(delivery.event.id, delivery.deviceId);
159
+ else {
160
+ const error = expoError(result);
161
+ if (result.details?.error === "DeviceNotRegistered") disableNotificationDeviceForPushToken(delivery.pushToken);
162
+ markNotificationDeliveryDead(delivery.event.id, delivery.deviceId, error);
163
+ }
164
+ }
165
+ } catch (err) {
166
+ for (const delivery of withTickets) this.retryOrFail(delivery, err, "accepted");
167
+ }
168
+ }
169
+ retryOrFail(delivery, error, status = "pending") {
170
+ const message = error instanceof Error ? error.message : String(error);
171
+ if (delivery.attempts + 1 >= MAX_DELIVERY_ATTEMPTS) {
172
+ markNotificationDeliveryDead(delivery.event.id, delivery.deviceId, message);
173
+ log.warn({
174
+ eventId: delivery.event.id,
175
+ deviceId: delivery.deviceId,
176
+ errorMessage: message
177
+ }, `Notification delivery exhausted retries: ${message}`);
178
+ return;
179
+ }
180
+ rescheduleNotificationDelivery(delivery.event.id, delivery.deviceId, status, retryAt(delivery.attempts, Date.now()), message);
181
+ }
182
+ };
183
+ //#endregion
184
+ export { NotificationService };
@@ -0,0 +1,53 @@
1
+ import { type ProductNotification } from '@xopcai/gateway-contract';
2
+ export type NotificationDelivery = {
3
+ event: ProductNotification;
4
+ deviceId: string;
5
+ pushToken: string;
6
+ locale: 'en' | 'zh';
7
+ attempts: number;
8
+ providerTicketId?: string;
9
+ };
10
+ export declare function createNotificationEvent(input: {
11
+ dedupeKey: string;
12
+ notification: Omit<ProductNotification, 'id' | 'createdAt' | 'schemaVersion'>;
13
+ deviceIds: string[];
14
+ createdAt?: number;
15
+ }): {
16
+ notification: ProductNotification;
17
+ created: boolean;
18
+ };
19
+ export declare function listNotificationEvents(options?: {
20
+ afterId?: string;
21
+ since?: number;
22
+ limit?: number;
23
+ }): {
24
+ items: ProductNotification[];
25
+ nextCursor: string | null;
26
+ };
27
+ export declare function acknowledgeNotification(eventId: string, consumerId: string, surface: 'web' | 'electron' | 'mobile'): boolean;
28
+ export declare function listDueNotificationDeliveries(status: 'pending' | 'accepted', now?: number, limit?: number): NotificationDelivery[];
29
+ export declare function markNotificationDeliveryAccepted(eventId: string, deviceId: string, ticketId: string, receiptAt: number): void;
30
+ export declare function markNotificationDeliveryDelivered(eventId: string, deviceId: string): void;
31
+ export declare function markNotificationDeliveryDead(eventId: string, deviceId: string, error: string): void;
32
+ export declare function rescheduleNotificationDelivery(eventId: string, deviceId: string, status: 'pending' | 'accepted', nextAttemptAt: number, error: string): void;
33
+ export declare function notificationDeliveryMetrics(): {
34
+ pending: number;
35
+ accepted: number;
36
+ delivered: number;
37
+ dead: number;
38
+ due: number;
39
+ devices: {
40
+ deliverable: number;
41
+ expired: number;
42
+ disabled: number;
43
+ };
44
+ oldestPendingAt: number | null;
45
+ latestError: {
46
+ eventId: string;
47
+ deviceId: string;
48
+ message: string;
49
+ updatedAt: number;
50
+ } | null;
51
+ };
52
+ export declare function expireUndeliverableNotificationDeliveries(now?: number): number;
53
+ export declare function pruneNotificationEvents(before: number): number;
@@ -0,0 +1,184 @@
1
+ import { getSqliteDatabase, runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
2
+ import { ProductNotificationSchema } from "../../packages/gateway-contract/src/notifications.js";
3
+ import { randomUUID } from "node:crypto";
4
+ //#region src/notifications/store.ts
5
+ function parseJson(raw) {
6
+ try {
7
+ return JSON.parse(raw);
8
+ } catch {
9
+ return null;
10
+ }
11
+ }
12
+ function eventFromRow(row) {
13
+ return ProductNotificationSchema.parse({
14
+ schemaVersion: 1,
15
+ id: row.event_id,
16
+ type: row.event_type,
17
+ target: parseJson(row.target_json),
18
+ priority: row.priority,
19
+ title: {
20
+ en: row.title_en,
21
+ zh: row.title_zh
22
+ },
23
+ ...row.body_en && row.body_zh ? { body: {
24
+ en: row.body_en,
25
+ zh: row.body_zh
26
+ } } : {},
27
+ payload: parseJson(row.payload_json),
28
+ createdAt: row.created_at
29
+ });
30
+ }
31
+ function deliveryFromRow(row) {
32
+ return {
33
+ event: eventFromRow(row),
34
+ deviceId: row.device_id,
35
+ pushToken: row.push_token,
36
+ locale: row.locale === "zh" ? "zh" : "en",
37
+ attempts: row.attempts,
38
+ ...row.provider_ticket_id ? { providerTicketId: row.provider_ticket_id } : {}
39
+ };
40
+ }
41
+ function createNotificationEvent(input) {
42
+ const id = randomUUID();
43
+ const createdAt = input.createdAt ?? Date.now();
44
+ const created = runSqliteWriteTransaction((db) => {
45
+ const inserted = db.prepare(`INSERT OR IGNORE INTO notification_events (
46
+ event_id, dedupe_key, event_type, target_json, priority,
47
+ title_en, title_zh, body_en, body_zh, payload_json, created_at
48
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, input.dedupeKey, input.notification.type, JSON.stringify(input.notification.target), input.notification.priority, input.notification.title.en, input.notification.title.zh, input.notification.body?.en ?? null, input.notification.body?.zh ?? null, JSON.stringify(input.notification.payload), createdAt).changes > 0;
49
+ if (inserted) {
50
+ const enqueue = db.prepare(`INSERT INTO notification_deliveries (
51
+ event_id, device_id, status, next_attempt_at, updated_at
52
+ ) VALUES (?, ?, 'pending', ?, ?)`);
53
+ for (const deviceId of input.deviceIds) enqueue.run(id, deviceId, createdAt, createdAt);
54
+ }
55
+ return inserted;
56
+ });
57
+ return {
58
+ notification: eventFromRow(getSqliteDatabase().prepare("SELECT * FROM notification_events WHERE dedupe_key = ?").get(input.dedupeKey)),
59
+ created
60
+ };
61
+ }
62
+ function listNotificationEvents(options = {}) {
63
+ const limit = Math.max(1, Math.min(100, options.limit ?? 50));
64
+ const after = options.afterId ? getSqliteDatabase().prepare("SELECT created_at, event_id FROM notification_events WHERE event_id = ?").get(options.afterId) : void 0;
65
+ const items = (after ? getSqliteDatabase().prepare(`SELECT * FROM notification_events
66
+ WHERE created_at > ? OR (created_at = ? AND event_id > ?)
67
+ ORDER BY created_at, event_id LIMIT ?`).all(after.created_at, after.created_at, after.event_id, limit) : getSqliteDatabase().prepare(`SELECT * FROM notification_events WHERE created_at >= ?
68
+ ORDER BY created_at, event_id LIMIT ?`).all(options.since ?? Date.now(), limit)).map(eventFromRow);
69
+ return {
70
+ items,
71
+ nextCursor: items.at(-1)?.id ?? null
72
+ };
73
+ }
74
+ function acknowledgeNotification(eventId, consumerId, surface) {
75
+ if (!getSqliteDatabase().prepare("SELECT 1 FROM notification_events WHERE event_id = ?").get(eventId)) return false;
76
+ runSqliteWriteTransaction((db) => {
77
+ db.prepare(`INSERT INTO notification_acknowledgements (
78
+ event_id, consumer_id, surface, acknowledged_at
79
+ ) VALUES (?, ?, ?, ?)
80
+ ON CONFLICT(event_id, consumer_id) DO UPDATE SET
81
+ surface = excluded.surface,
82
+ acknowledged_at = excluded.acknowledged_at`).run(eventId, consumerId, surface, Date.now());
83
+ });
84
+ return true;
85
+ }
86
+ const DELIVERY_JOIN = `
87
+ SELECT e.*, d.device_id, d.attempts, d.provider_ticket_id,
88
+ n.push_token, n.locale
89
+ FROM notification_deliveries d
90
+ JOIN notification_events e ON e.event_id = d.event_id
91
+ JOIN notification_devices n ON n.device_id = d.device_id
92
+ `;
93
+ function listDueNotificationDeliveries(status, now = Date.now(), limit = 100) {
94
+ return getSqliteDatabase().prepare(`${DELIVERY_JOIN}
95
+ WHERE d.status = ? AND d.next_attempt_at <= ?
96
+ AND n.enabled = 1 AND n.permissions = 'granted' AND n.lease_expires_at > ?
97
+ ORDER BY d.next_attempt_at LIMIT ?`).all(status, now, now, Math.max(1, Math.min(1e3, limit))).map(deliveryFromRow);
98
+ }
99
+ function markNotificationDeliveryAccepted(eventId, deviceId, ticketId, receiptAt) {
100
+ runSqliteWriteTransaction((db) => {
101
+ db.prepare(`UPDATE notification_deliveries
102
+ SET status = 'accepted', attempts = attempts + 1, provider_ticket_id = ?,
103
+ next_attempt_at = ?, last_error = NULL, updated_at = ?
104
+ WHERE event_id = ? AND device_id = ?`).run(ticketId, receiptAt, Date.now(), eventId, deviceId);
105
+ });
106
+ }
107
+ function markNotificationDeliveryDelivered(eventId, deviceId) {
108
+ runSqliteWriteTransaction((db) => {
109
+ db.prepare(`UPDATE notification_deliveries
110
+ SET status = 'delivered', last_error = NULL, updated_at = ?
111
+ WHERE event_id = ? AND device_id = ?`).run(Date.now(), eventId, deviceId);
112
+ });
113
+ }
114
+ function markNotificationDeliveryDead(eventId, deviceId, error) {
115
+ runSqliteWriteTransaction((db) => {
116
+ db.prepare(`UPDATE notification_deliveries
117
+ SET status = 'dead', attempts = attempts + 1, last_error = ?, updated_at = ?
118
+ WHERE event_id = ? AND device_id = ?`).run(error.slice(0, 500), Date.now(), eventId, deviceId);
119
+ });
120
+ }
121
+ function rescheduleNotificationDelivery(eventId, deviceId, status, nextAttemptAt, error) {
122
+ runSqliteWriteTransaction((db) => {
123
+ db.prepare(`UPDATE notification_deliveries
124
+ SET status = ?, attempts = attempts + 1, next_attempt_at = ?, last_error = ?, updated_at = ?
125
+ WHERE event_id = ? AND device_id = ?`).run(status, nextAttemptAt, error.slice(0, 500), Date.now(), eventId, deviceId);
126
+ });
127
+ }
128
+ function notificationDeliveryMetrics() {
129
+ const rows = getSqliteDatabase().prepare("SELECT status, COUNT(*) AS count FROM notification_deliveries GROUP BY status").all();
130
+ const counts = Object.fromEntries(rows.map((row) => [row.status, row.count]));
131
+ const now = Date.now();
132
+ const due = getSqliteDatabase().prepare(`SELECT COUNT(*) AS count FROM notification_deliveries
133
+ WHERE status IN ('pending', 'accepted') AND next_attempt_at <= ?`).get(now);
134
+ const devices = getSqliteDatabase().prepare(`SELECT
135
+ SUM(CASE WHEN enabled = 1 AND permissions = 'granted' AND lease_expires_at > ? THEN 1 ELSE 0 END) AS deliverable,
136
+ SUM(CASE WHEN enabled = 1 AND lease_expires_at <= ? THEN 1 ELSE 0 END) AS expired,
137
+ SUM(CASE WHEN enabled = 0 OR permissions <> 'granted' THEN 1 ELSE 0 END) AS disabled
138
+ FROM notification_devices`).get(now, now);
139
+ const oldest = getSqliteDatabase().prepare(`SELECT MIN(next_attempt_at) AS value FROM notification_deliveries
140
+ WHERE status IN ('pending', 'accepted')`).get();
141
+ const latestError = getSqliteDatabase().prepare(`SELECT event_id, device_id, last_error, updated_at
142
+ FROM notification_deliveries WHERE last_error IS NOT NULL
143
+ ORDER BY updated_at DESC LIMIT 1`).get();
144
+ return {
145
+ pending: counts.pending ?? 0,
146
+ accepted: counts.accepted ?? 0,
147
+ delivered: counts.delivered ?? 0,
148
+ dead: counts.dead ?? 0,
149
+ due: due.count,
150
+ devices: {
151
+ deliverable: devices.deliverable ?? 0,
152
+ expired: devices.expired ?? 0,
153
+ disabled: devices.disabled ?? 0
154
+ },
155
+ oldestPendingAt: oldest.value,
156
+ latestError: latestError ? {
157
+ eventId: latestError.event_id,
158
+ deviceId: latestError.device_id,
159
+ message: latestError.last_error,
160
+ updatedAt: latestError.updated_at
161
+ } : null
162
+ };
163
+ }
164
+ function expireUndeliverableNotificationDeliveries(now = Date.now()) {
165
+ return Number(runSqliteWriteTransaction((db) => db.prepare(`UPDATE notification_deliveries
166
+ SET status = 'dead', last_error = 'Device registration is disabled or expired', updated_at = ?
167
+ WHERE status IN ('pending', 'accepted')
168
+ AND EXISTS (
169
+ SELECT 1 FROM notification_devices n
170
+ WHERE n.device_id = notification_deliveries.device_id
171
+ AND (n.enabled = 0 OR n.permissions <> 'granted' OR n.lease_expires_at <= ?)
172
+ )`).run(now, now).changes));
173
+ }
174
+ function pruneNotificationEvents(before) {
175
+ return Number(runSqliteWriteTransaction((db) => db.prepare(`DELETE FROM notification_events
176
+ WHERE created_at < ?
177
+ AND NOT EXISTS (
178
+ SELECT 1 FROM notification_deliveries d
179
+ WHERE d.event_id = notification_events.event_id
180
+ AND d.status IN ('pending', 'accepted')
181
+ )`).run(before).changes));
182
+ }
183
+ //#endregion
184
+ export { acknowledgeNotification, createNotificationEvent, expireUndeliverableNotificationDeliveries, listDueNotificationDeliveries, listNotificationEvents, markNotificationDeliveryAccepted, markNotificationDeliveryDead, markNotificationDeliveryDelivered, notificationDeliveryMetrics, pruneNotificationEvents, rescheduleNotificationDelivery };
@@ -0,0 +1,29 @@
1
+ export type NotificationDevicePlatform = 'ios' | 'android';
2
+ export type NotificationPermission = 'granted' | 'denied' | 'unknown';
3
+ export type NotificationLanguage = 'en' | 'zh';
4
+ export type NotificationPreferences = {
5
+ chatCompleted: boolean;
6
+ chatFailed: boolean;
7
+ taskNeedsInput: boolean;
8
+ taskBlocked: boolean;
9
+ taskFailed: boolean;
10
+ taskCompleted: boolean;
11
+ automationCompleted: boolean;
12
+ automationFailed: boolean;
13
+ proactiveInsight: boolean;
14
+ };
15
+ export type NotificationDevice = {
16
+ id: string;
17
+ platform: NotificationDevicePlatform;
18
+ pushToken: string;
19
+ enabled: boolean;
20
+ permissions: NotificationPermission;
21
+ preferences: NotificationPreferences;
22
+ locale: NotificationLanguage;
23
+ appVersion?: string;
24
+ leaseExpiresAt: number;
25
+ lastSeenAt: number;
26
+ createdAt: number;
27
+ updatedAt: number;
28
+ };
29
+ export declare const DEFAULT_NOTIFICATION_PREFERENCES: NotificationPreferences;
@@ -0,0 +1,14 @@
1
+ //#region src/notifications/types.ts
2
+ const DEFAULT_NOTIFICATION_PREFERENCES = {
3
+ chatCompleted: true,
4
+ chatFailed: true,
5
+ taskNeedsInput: true,
6
+ taskBlocked: true,
7
+ taskFailed: true,
8
+ taskCompleted: false,
9
+ automationCompleted: false,
10
+ automationFailed: true,
11
+ proactiveInsight: true
12
+ };
13
+ //#endregion
14
+ export { DEFAULT_NOTIFICATION_PREFERENCES };
@@ -1,3 +1,4 @@
1
+ import { type TurnOutcome } from '@xopcai/gateway-contract';
1
2
  import type { Message } from './types.js';
2
3
  import type { TranscriptStoredRow } from './session-context-for-llm.js';
3
4
  /** Transcript row for TUI and HTTP clients (flattened from persisted session messages). */
@@ -13,7 +14,7 @@ export interface ClientHistoryMessage {
13
14
  timestamp?: number;
14
15
  /** Whitelisted display metadata; never includes source snapshot text. */
15
16
  metadata?: {
16
- sourceContexts: Array<{
17
+ sourceContexts?: Array<{
17
18
  kind: 'note';
18
19
  sourceId: string;
19
20
  version: string;
@@ -21,6 +22,7 @@ export interface ClientHistoryMessage {
21
22
  tokenEstimate?: number;
22
23
  truncated?: boolean;
23
24
  }>;
25
+ turnOutcome?: TurnOutcome;
24
26
  };
25
27
  kind?: 'message' | 'compaction' | 'context' | 'bash' | 'custom' | 'branch';
26
28
  tokensBefore?: number;
@@ -1,3 +1,4 @@
1
+ import { parseTurnOutcome } from "../../packages/gateway-contract/src/turn-outcome.js";
1
2
  import { stripRuntimeContextFromUserMessage } from "./user-message-display.js";
2
3
  import { buildTranscriptOutline } from "./transcript-outline.js";
3
4
  //#region src/session/client-history.ts
@@ -286,6 +287,18 @@ function customStateRowToClientHistory(row) {
286
287
  if (r.type !== "custom") return null;
287
288
  const customType = optionalString(r.customType)?.trim();
288
289
  if (!customType) return null;
290
+ if (customType === "turn_outcome") {
291
+ const outcome = parseTurnOutcome(r.data);
292
+ if (!outcome) return null;
293
+ return {
294
+ role: "assistant",
295
+ kind: "message",
296
+ turnId: outcome.turnId,
297
+ content: "",
298
+ timestamp: parseTimestampValue(typeof r.timestamp === "string" || typeof r.timestamp === "number" ? r.timestamp : void 0),
299
+ metadata: { turnOutcome: outcome }
300
+ };
301
+ }
289
302
  return {
290
303
  role: "system",
291
304
  kind: "custom",
@@ -1,7 +1,7 @@
1
1
  export { SessionIndex, type SessionIndexConfig } from './manager.js';
2
2
  export { onSessionTranscriptUpdate, emitSessionTranscriptUpdate, type SessionTranscriptUpdate, } from './transcript-events.js';
3
3
  export { SessionStore } from './store.js';
4
- export type { SessionStoreOptions } from './store.js';
4
+ export type { SessionStoreOptions, ForkSessionAtTurnOptions, ForkSessionResult, } from './store.js';
5
5
  export { SessionConfigStore, resolveThinkingLevel, resolveReasoningLevel, resolveVerboseLevel } from './config-store.js';
6
6
  export { DEFAULT_ACTIVITY_DETAIL_LEVEL, resolveConfiguredActivityDetailDefault, resolveEffectiveThinkingLevel, resolveEffectiveReasoningLevel, } from './thinking-resolve.js';
7
7
  export type { SessionAgentConfig } from './config-types.js';
@@ -89,6 +89,7 @@ export declare class SessionIndex extends EventEmitter {
89
89
  sessionKey: string;
90
90
  rowCount: number;
91
91
  }>;
92
+ forkSessionAtTurn(sourceKey: string, options: import('./store.js').ForkSessionAtTurnOptions): Promise<import('./store.js').ForkSessionResult>;
92
93
  getStats(): Promise<GlobalSessionStats>;
93
94
  archiveOldSessions(olderThanDays: number): Promise<number>;
94
95
  onSessionCreated(callback: (metadata: SessionMetadata) => void): void;
@@ -171,6 +171,12 @@ var SessionIndex = class extends EventEmitter$1 {
171
171
  if (metadata) this.emit("sessionCreated", metadata);
172
172
  return result;
173
173
  }
174
+ async forkSessionAtTurn(sourceKey, options) {
175
+ const result = await this.store.forkSessionAtTurn(sourceKey, options);
176
+ const metadata = await this.store.getMetadata(result.sessionKey);
177
+ if (metadata) this.emit("sessionCreated", metadata);
178
+ return result;
179
+ }
174
180
  async getStats() {
175
181
  return this.store.getStats();
176
182
  }
@@ -27,6 +27,16 @@ export interface SessionCompactionHooks {
27
27
  }) => Promise<void> | void;
28
28
  }
29
29
  export type ModelFallbackPreparation = 'prompt' | 'resume' | 'unsafe';
30
+ export interface ForkSessionAtTurnOptions {
31
+ targetKey: string;
32
+ lastTurnId: string;
33
+ targetMetadata: SessionMetadataSeed;
34
+ }
35
+ export interface ForkSessionResult {
36
+ sessionKey: string;
37
+ rowCount: number;
38
+ lastTurnId?: string;
39
+ }
30
40
  export declare class SessionStore {
31
41
  private options;
32
42
  private window;
@@ -182,6 +192,11 @@ export declare class SessionStore {
182
192
  sessionKey: string;
183
193
  rowCount: number;
184
194
  }>;
195
+ /**
196
+ * Atomically copy a completed turn prefix into a new session, including the
197
+ * source session's per-session agent configuration.
198
+ */
199
+ forkSessionAtTurn(sourceKey: string, options: ForkSessionAtTurnOptions): Promise<ForkSessionResult>;
185
200
  getStats(): Promise<GlobalSessionStats>;
186
201
  archiveOld(olderThanDays: number): Promise<number>;
187
202
  estimateTokens(messages: AgentMessage[]): number;
@@ -2,12 +2,14 @@ import { init_paths_state, resolveStateDir } from "../config/paths-state.js";
2
2
  import { createLogger } from "../utils/logger/index.js";
3
3
  import { init_logger } from "../utils/logger.js";
4
4
  import { requireXopcDatabase } from "../storage/sqlite/connection.js";
5
+ import { runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
5
6
  import { buildSessionContextForLlm, buildSessionDisplayMessages, isRuntimeOnlyTranscriptMessage, isTranscriptCompactionEntry, mergeLlmMessagesPreservingContextRows, transcriptRowsFromJsonArray } from "./session-context-for-llm.js";
6
7
  import "./types.js";
7
8
  import { init_agent_session_key, resolveAgentIdFromSessionKey } from "../routing/agent-session-key.js";
8
9
  import { estimateTokensFromMessages } from "../storage/sqlite/row-mappers.js";
9
10
  import { deleteSessionRecord, ensureSessionRecord, findSessionKeyBySessionId, getGlobalSessionStats, getSessionMetadata, listSessionMetadata, listSessionsByAgent, patchSessionMetadata, resetSessionRecord } from "../storage/sqlite/session-repository.js";
10
11
  import { appendCompactionBoundaryIfUnchanged, appendTranscriptEntry, listCompactionBoundaries, loadCompactionSourceSnapshot, loadLlmMessagesForSession, loadTranscriptHistoryRowsForSession, loadTranscriptRowsForSession, paginateTranscriptMessages, replaceTranscriptRows, restoreBeforeCompactionBoundary, searchSessionTranscript } from "../storage/sqlite/transcript-repository.js";
12
+ import { getSessionConfig, setSessionConfig } from "../storage/sqlite/config-repository.js";
11
13
  import "../storage/sqlite/index.js";
12
14
  import { resolveEffectiveAgentProfileForSession } from "../config/agent-profile.js";
13
15
  import { readPostCompactionContext } from "../agent/reply/post-compaction-context.js";
@@ -657,6 +659,62 @@ var SessionStore = class {
657
659
  };
658
660
  });
659
661
  }
662
+ /**
663
+ * Atomically copy a completed turn prefix into a new session, including the
664
+ * source session's per-session agent configuration.
665
+ */
666
+ async forkSessionAtTurn(sourceKey, options) {
667
+ requireXopcDatabase();
668
+ const targetKey = options.targetKey.trim();
669
+ const lastTurnId = options.lastTurnId.trim();
670
+ if (!targetKey) throw new Error("Target session key is required");
671
+ if (!lastTurnId) throw new Error("lastTurnId is required");
672
+ return runSqliteWriteTransaction(() => {
673
+ const sourceMetadata = getSessionMetadata(sourceKey);
674
+ if (!sourceMetadata) throw new Error(`Session not found: ${sourceKey}`);
675
+ if (getSessionMetadata(targetKey)) throw new Error(`Target session already exists: ${targetKey}`);
676
+ const rows = loadTranscriptHistoryRowsForSession(sourceKey);
677
+ let lastTurnRow = -1;
678
+ let hasAssistantOutput = false;
679
+ for (let index = 0; index < rows.length; index += 1) {
680
+ const row = rows[index];
681
+ if (row.turnId !== lastTurnId) continue;
682
+ lastTurnRow = index;
683
+ if (row.role === "assistant") hasAssistantOutput = true;
684
+ }
685
+ if (lastTurnRow < 0 || !hasAssistantOutput) throw new Error(`Completed turn not found: ${lastTurnId}`);
686
+ const selectedRows = rows.slice(0, lastTurnRow + 1);
687
+ const sourceLabel = sourceMetadata.name?.trim() || sourceKey;
688
+ const forkedAt = (/* @__PURE__ */ new Date()).toISOString();
689
+ const cwd = sourceMetadata.cwd?.trim() || this.resolveWorkspaceCwd(targetKey);
690
+ ensureSessionRecord(targetKey, cwd, {
691
+ ...options.targetMetadata,
692
+ name: `Fork of ${sourceLabel}`,
693
+ tags: [...new Set([...sourceMetadata.tags ?? [], "fork"])],
694
+ projectId: sourceMetadata.projectId,
695
+ parentSessionKey: sourceKey,
696
+ hiddenFromSessionList: false,
697
+ customData: {
698
+ ...sourceMetadata.customData ?? {},
699
+ ...options.targetMetadata.customData ?? {},
700
+ genericNewChatShell: false,
701
+ forkedFromSessionKey: sourceKey,
702
+ forkedFromSessionId: sourceMetadata.sessionId,
703
+ forkedFromSessionName: sourceMetadata.name,
704
+ forkedFromTurnId: lastTurnId,
705
+ forkedAt
706
+ }
707
+ });
708
+ replaceTranscriptRows(targetKey, selectedRows);
709
+ const sourceConfig = getSessionConfig(sourceKey);
710
+ if (sourceConfig) setSessionConfig(targetKey, sourceConfig, cwd);
711
+ return {
712
+ sessionKey: targetKey,
713
+ rowCount: selectedRows.length,
714
+ lastTurnId
715
+ };
716
+ });
717
+ }
660
718
  async getStats() {
661
719
  requireXopcDatabase();
662
720
  return getGlobalSessionStats();
@@ -9,9 +9,9 @@ export { claimContextExtractionRun, finishContextExtractionRun, getContextExtrac
9
9
  export { closeTemporalAssertions, createTemporalAssertion, linkContextObjects, listContextObjectRelations, listTemporalAssertions, type ContextObjectRelation, type ContextTemporalAssertion, } from './context-relation-repository.js';
10
10
  export { finishContextConsolidationRun, getContextConsolidationRun, listContextConsolidationDecisions, listContextConsolidationRuns, recordContextConsolidationDecision, startContextConsolidationRun, type ContextConsolidationDecision, type ContextConsolidationRun, } from './context-consolidation-repository.js';
11
11
  export { summarizeUnderstandingFeedback, summarizeUserUnderstandingQuality, type UnderstandingFeedbackSummary, type UserUnderstandingQualityMetrics, } from './user-context-quality-repository.js';
12
- export { createCollaborationRule, createContextEvidence, createUnderstanding, closeUnderstandingValidity, consumeContextConsent, deleteCollaborationRule, deleteUnderstanding, decideContextConsent, ensureContextConsent, getCollaborationRule, getTurnPersonalization, getUnderstanding, getUserProfile, hasContextConsent, isUnderstandingSuppressed, listCollaborationRules, listUnderstandingEvidence, listUnderstandings, linkUnderstandingEvidence, recordContextFeedback, recordContextRun, rejectUnderstanding, reviseCollaborationRule, reviseUnderstanding, searchActiveUnderstandings, setCollaborationRuleStatus, setUnderstandingStatus, updateUserProfile, type CreateUnderstandingInput, } from './user-context-repository.js';
12
+ export { createCollaborationRule, createContextEvidence, createUnderstanding, closeUnderstandingValidity, consumeContextConsent, deleteCollaborationRule, deleteUnderstanding, decideContextConsent, ensureContextConsent, getCollaborationRule, getTurnPersonalization, getUnderstanding, getUserProfile, hasContextConsent, isUnderstandingSuppressed, listCollaborationRules, listUnderstandingEvidence, listUnderstandingStatusEvents, listUnderstandings, linkUnderstandingEvidence, recordContextFeedback, recordContextRun, rejectUnderstanding, reviseCollaborationRule, reviseUnderstanding, searchActiveUnderstandings, setCollaborationRuleStatus, setUnderstandingStatus, updateUserProfile, type CreateUnderstandingInput, type UnderstandingStatusEvent, } from './user-context-repository.js';
13
13
  export { getUserPerson, getUserPeopleIndexState, listUserPeople, mergeUserPeople, replaceUserPeopleIndex, summarizeUserPeople, updateUserPerson, } from './user-people-repository.js';
14
- export { cancelQueuedSessionInput, claimNextSessionInput, findSessionInput, finishSessionInputRun, getSessionInputState, getSessionInputById, insertSessionInput, mutateQueuedSessionInput, recoverSessionInputState, setSessionInputStatus, type SessionInput, type SessionInputDelivery, type SessionInputState, type SessionInputStatus, } from './session-input-repository.js';
14
+ export { cancelQueuedSessionInput, claimNextSessionInput, findSessionInput, finishSessionInputRun, getSessionInputState, getSessionInputById, insertSessionInput, listActiveSessionInputRuns, mutateQueuedSessionInput, recoverSessionInputState, setSessionInputStatus, type SessionInput, type SessionInputDelivery, type SessionInputState, type SessionInputStatus, } from './session-input-repository.js';
15
15
  export { replaceLatestSessionTurnAndQueueInput, validateLatestSessionTurnTarget, type ReplaceLatestSessionTurnInput, type ReplaceLatestSessionTurnResult, } from './session-turn-replace-repository.js';
16
16
  export { acknowledgeHomeAttention, isHomeAttentionAcknowledged, type HomeAttentionSubjectKind, } from './home-attention-repository.js';
17
17
  export { claimNextConnectorLearningJob, enqueueConnectorLearningJob, listConnectorLearningJobs, recoverStaleConnectorLearningJobs, setConnectorLearningPaused, updateConnectorLearningJob, type ConnectorLearningJob, type ConnectorLearningMode, type ConnectorLearningPhase, type ConnectorLearningStatus, } from './connector-learning-repository.js';