@xopcai/xopc 0.0.227 → 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 (200) 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-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-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--SjycrXk.js} +1 -1
  8. package/dist/gateway/static/root/assets/{appearance-settings-BoH2NAUX.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-DnceUyxm.js → archive-plugin-7_DXaMTP.js} +1 -1
  11. package/dist/gateway/static/root/assets/{attachment-load-Bh8_9gkr.js → attachment-load-Bx44zSpz.js} +1 -1
  12. package/dist/gateway/static/root/assets/{automations-page-ZKeAAVxD.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-Cy9er6w-.js → binary-plugins-DjDwnHAQ.js} +1 -1
  15. package/dist/gateway/static/root/assets/{block-editor-Cc0wWvNX.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-BS7rpLLl.js → browser-workflow-inputs-8NC2qDRF.js} +1 -1
  18. package/dist/gateway/static/root/assets/{browser-workflows-page-BdBVUZXm.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-D1Lqz0Tt.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-CGuEl-l2.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-CdX_7aQh.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-BnSQc4Ek.js → cron-expression-D8-c-Tkw.js} +1 -1
  28. package/dist/gateway/static/root/assets/{date-picker-BbuG7zQ_.js → date-picker-kV-Jyl9J.js} +1 -1
  29. package/dist/gateway/static/root/assets/{dependency-picker-snZeqOmG.js → dependency-picker-CwKcrfGj.js} +1 -1
  30. package/dist/gateway/static/root/assets/{desktop-pet-DJQ8JoR1.js → desktop-pet-D9rnchu3.js} +1 -1
  31. package/dist/gateway/static/root/assets/{desktop-pet-settings-B9txsRtv.js → desktop-pet-settings-CTs5WGzk.js} +1 -1
  32. package/dist/gateway/static/root/assets/{directory-picker-path-field-D7WKWkNB.js → directory-picker-path-field-CCl0aaDm.js} +1 -1
  33. package/dist/gateway/static/root/assets/{extension-debug-page-D_7cOCj2.js → extension-debug-page-Cau40SvC.js} +1 -1
  34. package/dist/gateway/static/root/assets/{extension-page-Bwu47iUb.js → extension-page-BsJXdvIL.js} +1 -1
  35. package/dist/gateway/static/root/assets/{extension-settings-page-Dwg4dSZM.js → extension-settings-page-qWqJyF7-.js} +1 -1
  36. package/dist/gateway/static/root/assets/{gateway-config-api-jURUSte_.js → gateway-config-api-BI0SELXm.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-6ZdOIC7F.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-KBwhXeFD.js} +1 -1
  41. package/dist/gateway/static/root/assets/home-page-BA2M4CLi.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-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-ByVsCH3d.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-D19R-xsz.js → local-apps-page-BCXa0oUa.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-D3sNst0K.js +2 -0
  50. package/dist/gateway/static/root/assets/{management-settings-G0y7zmfY.js → management-settings-tHnlfOAQ.js} +1 -1
  51. package/dist/gateway/static/root/assets/{markdown-split-B69d2HWt.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-6TWEQgN_.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-DLsnnZCS.js → notes-api-BFsOGYkd.js} +1 -1
  57. package/dist/gateway/static/root/assets/{notes-page-CkRJJKDm.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-HxArIvoN.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-lEar5FOV.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-Bf03UNGD.js → runtime-tools-settings-panel-BPEGTip5.js} +1 -1
  65. package/dist/gateway/static/root/assets/{schema-form-CIXMBgx5.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-DgdB26mE.js → settings-form-section-ChXh3Bwk.js} +1 -1
  69. package/dist/gateway/static/root/assets/{settings-loading-skeleton-efk5W6lG.js → settings-loading-skeleton-BCAUqIe2.js} +1 -1
  70. package/dist/gateway/static/root/assets/{settings-page-BHVf4gEP.js → settings-page-D25W9Okr.js} +1 -1
  71. package/dist/gateway/static/root/assets/{setup-status-panel-WK0dGNMv.js → setup-status-panel-DsN7Z4Fl.js} +1 -1
  72. package/dist/gateway/static/root/assets/{share-preview-page-Cod-w2qm.js → share-preview-page-CRFnUaNb.js} +1 -1
  73. package/dist/gateway/static/root/assets/{shares-settings-CUqQ31gt.js → shares-settings-CAX0eNaP.js} +1 -1
  74. package/dist/gateway/static/root/assets/skill-reload-api-Doy1gghe.js +1 -0
  75. package/dist/gateway/static/root/assets/{skills-page-DwCcipqK.js → skills-page-Cl7jjTK7.js} +1 -1
  76. package/dist/gateway/static/root/assets/{skills-page.utils-3WSbtl3S.js → skills-page.utils-BiCl4nb5.js} +1 -1
  77. package/dist/gateway/static/root/assets/{system-settings-panel-C5VxqC_s.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-Dd89g_ki.js → theme-store-DOdCjyuw.js} +1 -1
  80. package/dist/gateway/static/root/assets/{use-autosave-CeV8VJh2.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-EmJ-8aZu.js → voice-api-key-field-BDNexlVR.js} +1 -1
  83. package/dist/gateway/static/root/assets/{work-discovery-overlay-zsk6E3Mi.js → work-discovery-overlay-BHTfeaWL.js} +1 -1
  84. package/dist/gateway/static/root/assets/workflow-run-setup-panel-Cf_w-NZy.js +3 -0
  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/lifecycle/timeout-wrapper.d.ts +1 -0
  92. package/dist/src/agent/lifecycle/timeout-wrapper.js +7 -6
  93. package/dist/src/agent/mcp/mcp-transport-config.d.ts +1 -1
  94. package/dist/src/agent/mcp/mcp-transport-config.js +1 -1
  95. package/dist/src/agent/orchestration/run-agent-turn-with-timeout.d.ts +2 -2
  96. package/dist/src/agent/orchestration/run-agent-turn-with-timeout.js +4 -4
  97. package/dist/src/agent/tools/create-share-tool.js +14 -1
  98. package/dist/src/agent/tools/exec-command.js +3 -3
  99. package/dist/src/agent/tools/execute-code-tool.js +1 -1
  100. package/dist/src/agent/tools/executor.js +2 -2
  101. package/dist/src/agent/tools/image-generate-tool.js +12 -0
  102. package/dist/src/agent/tools/send-media.js +11 -0
  103. package/dist/src/agent/tools/xopc-use-tool.js +1 -1
  104. package/dist/src/cli/command-loaders.js +1 -0
  105. package/dist/src/cli/command-manifest.js +4 -0
  106. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  107. package/dist/src/cli/commands/history.d.ts +1 -0
  108. package/dist/src/cli/commands/history.js +50 -0
  109. package/dist/src/config/agent-profile.js +1 -1
  110. package/dist/src/gateway/agents-admin.js +1 -1
  111. package/dist/src/gateway/chat-stream/protocol.d.ts +3 -2
  112. package/dist/src/gateway/chat-stream/turn-outcome-collector.d.ts +16 -0
  113. package/dist/src/gateway/chat-stream/turn-outcome-collector.js +111 -0
  114. package/dist/src/gateway/hono/lib/static-ui.js +7 -5
  115. package/dist/src/gateway/hono/routes/index.js +2 -0
  116. package/dist/src/gateway/hono/routes/mobile.js +18 -40
  117. package/dist/src/gateway/hono/routes/notifications.d.ts +3 -0
  118. package/dist/src/gateway/hono/routes/notifications.js +45 -0
  119. package/dist/src/gateway/hono/routes/sessions.js +31 -0
  120. package/dist/src/gateway/hono/routes/tasks.js +1 -1
  121. package/dist/src/gateway/service/agent-runner.js +6 -2
  122. package/dist/src/gateway/service/run-gateway-agent.js +53 -1
  123. package/dist/src/gateway/service/sessions-api.d.ts +6 -0
  124. package/dist/src/gateway/service/sessions-api.js +60 -3
  125. package/dist/src/gateway/service.d.ts +2 -2
  126. package/dist/src/gateway/service.js +8 -7
  127. package/dist/src/history/ctx/exporter.d.ts +14 -0
  128. package/dist/src/history/ctx/exporter.js +99 -0
  129. package/dist/src/history/ctx/format.d.ts +25 -0
  130. package/dist/src/history/ctx/format.js +282 -0
  131. package/dist/src/media/session-references.d.ts +6 -0
  132. package/dist/src/media/session-references.js +24 -3
  133. package/dist/src/notifications/device-store.d.ts +16 -0
  134. package/dist/src/notifications/device-store.js +96 -0
  135. package/dist/src/notifications/planner.d.ts +6 -0
  136. package/dist/src/notifications/planner.js +156 -0
  137. package/dist/src/notifications/service.d.ts +18 -0
  138. package/dist/src/notifications/service.js +184 -0
  139. package/dist/src/notifications/store.d.ts +53 -0
  140. package/dist/src/notifications/store.js +184 -0
  141. package/dist/src/notifications/types.d.ts +29 -0
  142. package/dist/src/notifications/types.js +14 -0
  143. package/dist/src/session/client-history.d.ts +3 -1
  144. package/dist/src/session/client-history.js +13 -0
  145. package/dist/src/session/index.d.ts +1 -1
  146. package/dist/src/session/manager.d.ts +1 -0
  147. package/dist/src/session/manager.js +6 -0
  148. package/dist/src/session/store.d.ts +15 -0
  149. package/dist/src/session/store.js +58 -0
  150. package/dist/src/storage/sqlite/index.d.ts +1 -1
  151. package/dist/src/storage/sqlite/index.js +2 -2
  152. package/dist/src/storage/sqlite/migrations/136_product_notifications.sql +79 -0
  153. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  154. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  155. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  156. package/dist/src/storage/sqlite/schema.js +1 -1
  157. package/dist/src/storage/sqlite/session-input-repository.d.ts +5 -0
  158. package/dist/src/storage/sqlite/session-input-repository.js +9 -1
  159. package/dist/src/tasks/index.js +2 -2
  160. package/dist/src/tasks/task-application-service.js +42 -6
  161. package/dist/src/tasks/task-change-events.d.ts +9 -0
  162. package/dist/src/tasks/task-change-events.js +17 -1
  163. package/dist/src/tasks/task-outbox-dispatcher.js +5 -1
  164. package/dist/src/tasks/task-run-dispatcher.js +4 -2
  165. package/dist/src/tasks/task-run-repository.js +13 -0
  166. package/package.json +2 -2
  167. package/dist/gateway/static/root/assets/agents-TJpjRHnr.js +0 -68
  168. package/dist/gateway/static/root/assets/apps-page-CZ4Da51l.js +0 -1
  169. package/dist/gateway/static/root/assets/automations-workspace-2Y00PvJU.js +0 -6
  170. package/dist/gateway/static/root/assets/browser-settings-page-CbJD27_j.js +0 -8
  171. package/dist/gateway/static/root/assets/capabilities-settings-panel-Dv8y6OuP.js +0 -7
  172. package/dist/gateway/static/root/assets/capability-presets-settings-panel-TZmjzWzt.js +0 -3
  173. package/dist/gateway/static/root/assets/channels-settings-g-68dutA.js +0 -1
  174. package/dist/gateway/static/root/assets/channels-status-swr-D7KsAeWh.js +0 -1
  175. package/dist/gateway/static/root/assets/connectors-page-DXA0rAvQ.js +0 -2
  176. package/dist/gateway/static/root/assets/home-page-i6bXCHdo.js +0 -1
  177. package/dist/gateway/static/root/assets/index-BBBE89vm.css +0 -2
  178. package/dist/gateway/static/root/assets/index-BsD5wygl.js +0 -74
  179. package/dist/gateway/static/root/assets/local-app-workbench-page-BCKEDMlP.js +0 -2
  180. package/dist/gateway/static/root/assets/logs-page-Cy9YQK7j.js +0 -2
  181. package/dist/gateway/static/root/assets/markdown-view-CBM-v2ph.js +0 -69
  182. package/dist/gateway/static/root/assets/mermaid-render-BbqDc_LQ.js +0 -134
  183. package/dist/gateway/static/root/assets/messages-CtOJwGW3.js +0 -3
  184. package/dist/gateway/static/root/assets/notes-workbench-B10T3hGX.js +0 -4
  185. package/dist/gateway/static/root/assets/project-detail-page-DsqRlgsy.js +0 -1
  186. package/dist/gateway/static/root/assets/projects-page-Bu2PN8OK.js +0 -1
  187. package/dist/gateway/static/root/assets/remote-access-hub-CXACdJ2f.js +0 -1
  188. package/dist/gateway/static/root/assets/sessions-page-CS2knWDT.js +0 -1
  189. package/dist/gateway/static/root/assets/settings-advanced-gate-ByFGk0q_.js +0 -1
  190. package/dist/gateway/static/root/assets/skill-reload-api-DweAPjI8.js +0 -1
  191. package/dist/gateway/static/root/assets/task-detail-page-DEj-BFMP.js +0 -1
  192. package/dist/gateway/static/root/assets/user-context-page-f7yh7aD5.js +0 -1
  193. package/dist/gateway/static/root/assets/workflow-run-setup-panel-CBAYvm3G.js +0 -3
  194. package/dist/gateway/static/root/assets/workflows-page-BN0JmHrG.js +0 -5
  195. package/dist/src/mobile/notification-service.d.ts +0 -9
  196. package/dist/src/mobile/notification-service.js +0 -156
  197. package/dist/src/mobile/notification-store.d.ts +0 -28
  198. package/dist/src/mobile/notification-store.js +0 -132
  199. package/dist/src/mobile/notification-types.d.ts +0 -37
  200. package/dist/src/mobile/notification-types.js +0 -9
@@ -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();
@@ -11,7 +11,7 @@ export { finishContextConsolidationRun, getContextConsolidationRun, listContextC
11
11
  export { summarizeUnderstandingFeedback, summarizeUserUnderstandingQuality, type UnderstandingFeedbackSummary, type UserUnderstandingQualityMetrics, } from './user-context-quality-repository.js';
12
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';
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';
@@ -12,7 +12,7 @@ import { finishContextConsolidationRun, getContextConsolidationRun, listContextC
12
12
  import { summarizeUnderstandingFeedback, summarizeUserUnderstandingQuality } from "./user-context-quality-repository.js";
13
13
  import { closeUnderstandingValidity, consumeContextConsent, createCollaborationRule, createContextEvidence, createUnderstanding, decideContextConsent, deleteCollaborationRule, deleteUnderstanding, ensureContextConsent, getCollaborationRule, getTurnPersonalization, getUnderstanding, getUserProfile, hasContextConsent, isUnderstandingSuppressed, linkUnderstandingEvidence, listCollaborationRules, listUnderstandingEvidence, listUnderstandings, recordContextFeedback, recordContextRun, rejectUnderstanding, reviseCollaborationRule, reviseUnderstanding, searchActiveUnderstandings, setCollaborationRuleStatus, setUnderstandingStatus, updateUserProfile } from "./user-context-repository.js";
14
14
  import { getUserPeopleIndexState, getUserPerson, listUserPeople, mergeUserPeople, replaceUserPeopleIndex, summarizeUserPeople, updateUserPerson } from "./user-people-repository.js";
15
- import { cancelQueuedSessionInput, claimNextSessionInput, findSessionInput, finishSessionInputRun, getSessionInputById, getSessionInputState, insertSessionInput, mutateQueuedSessionInput, recoverSessionInputState, setSessionInputStatus } from "./session-input-repository.js";
15
+ import { cancelQueuedSessionInput, claimNextSessionInput, findSessionInput, finishSessionInputRun, getSessionInputById, getSessionInputState, insertSessionInput, listActiveSessionInputRuns, mutateQueuedSessionInput, recoverSessionInputState, setSessionInputStatus } from "./session-input-repository.js";
16
16
  import { buildDefaultSessionMetadata } from "./session-metadata.js";
17
17
  import { buildGlobalSessionStats, classifyStoredRow, estimateTokensFromMessages, extractFtsContent, metadataToSessionInsert, sessionConfigRowToConfig, sessionRowToMetadata, transcriptEntryRowToStoredRow } from "./row-mappers.js";
18
18
  import { replaceLatestSessionTurnAndQueueInput, validateLatestSessionTurnTarget } from "./session-turn-replace-repository.js";
@@ -36,4 +36,4 @@ import { appendConnectorExecutionAudit, claimConnectorWebhookDelivery, completeC
36
36
  import { getConnectorAccount, listConnectorAccounts, reconcileConnectorAccount, refreshConnectorAccountCurrent } from "./connector-account-repository.js";
37
37
  import { getConnectorSyncPolicy, getConnectorSyncPolicyForConnection, listConnectorSyncPolicies, upsertConnectorSyncPolicy } from "./connector-sync-policy-repository.js";
38
38
  import { getUserTrustPolicy, setUserTrustPolicy } from "./user-trust-repository.js";
39
- export { COMPOSER_INPUT_HISTORY_LIMIT, DatabaseSchemaMigrationGapError, DatabaseSchemaTooNewError, SCHEMA_META_SCHEMA_VERSION_KEY, XOPC_DB_BASELINE_SCHEMA_VERSION, XOPC_DB_FILENAME, XOPC_DB_SCHEMA_VERSION, acknowledgeHomeAttention, appendCompactionBoundaryIfUnchanged, appendComposerInputHistory, appendConnectorExecutionAudit, appendMemorySignal, appendMemoryTraceEvent, appendTranscriptEntry, applyPendingMigrations, attachMemoryEvidence, bindEndpointPrincipal, buildDefaultSessionMetadata, buildGlobalSessionStats, buildRelationshipPrompt, cancelQueuedSessionInput, claimConnectorWebhookDelivery, claimContextExtractionRun, claimKnowledgeSourceItems, claimNextConnectorLearningJob, claimNextSessionInput, classifyStoredRow, clearComposerInputHistory, closeTemporalAssertions, closeUnderstandingValidity, closeXopcDatabase, completeConnectorWebhookDelivery, completeKnowledgeSourceItemSynthesis, consumeConnectorApproval, consumeContextConsent, countKnowledgeSourceItems, countMemoryRecordsBySourceInstanceId, createCollaborationRule, createConnectorApproval, createContextEvidence, createEndpointPrincipal, createObjectLinkRecord, createTemporalAssertion, createUnderstanding, decideConnectorApproval, decideContextConsent, deleteCollaborationRule, deleteConnectorConnection, deleteConnectorInstallation, deleteKnowledgeSourceItems, deleteMemoryEvidenceForRecord, deleteMemoryRecord, deleteMemoryRecordsBySourceInstanceId, deleteNoteAgentContextRecord, deleteNoteRecord, deleteObjectLinkRecord, deleteSessionConfig, deleteSessionRecord, deleteUnderstanding, discoverSqlMigrations, enqueueConnectorLearningJob, ensureContextConsent, ensureSessionRecord, ensureXopcDatabaseSchema, estimateTokensFromMessages, extractFtsContent, findSessionInput, findSessionKeyBySessionId, finishContextConsolidationRun, finishContextExtractionRun, finishEndpointToolInvocationAudit, finishKnowledgeSyncRun, finishSessionInputRun, getActivityEventRecord, getCachedConnectorCatalogEntry, getCollaborationRule, getConnectorAccount, getConnectorApproval, getConnectorConnection, getConnectorInstallation, getConnectorSyncPolicy, getConnectorSyncPolicyForConnection, getContextConsolidationRun, getContextExtractionRun, getCurrentSessionId, getEndpointPrincipal, getGlobalSessionStats, getInteractionState, getKnowledgeConsumerWatermark, getKnowledgeSourceCursor, getKnowledgeSourceItem, getMemoryProviderState, getMemoryRecord, getMemoryTurnFeedback, getNoteAgentContextRecord, getNoteRecord, getRelationshipSettings, getSessionConfig, getSessionInputById, getSessionInputState, getSessionMetadata, getSessionPersistedLevels, getSessionTaskPlan, getSqliteDatabase, getTurnPersonalization, getUnderstanding, getUserPeopleIndexState, getUserPerson, getUserProfile, getUserTrustPolicy, getXopcDatabase, hasContextConsent, hasIndependentExtractionOutput, hasSessionConfig, hasUnresolvedMemoryConflict, incrementSessionStatsOnAppend, insertSessionInput, inspectSchemaMigrationStatus, isHomeAttentionAcknowledged, isProactiveSupportAllowed, isUnderstandingSuppressed, isXopcDatabaseOpen, linkContextObjects, linkUnderstandingEvidence, listActivityRecords, listCachedConnectorCatalogEntries, listCollaborationRules, listCompactionBoundaries, listComposerInputHistory, listConnectorAccounts, listConnectorActionMetadata, listConnectorApprovals, listConnectorConnections, listConnectorExecutionAudit, listConnectorInstallations, listConnectorLearningJobs, listConnectorSyncPolicies, listContextConsolidationDecisions, listContextConsolidationRuns, listContextExtractionOutputs, listContextExtractionRuns, listContextObjectRelations, listEndpointPrincipals, listEndpointToolInvocationAudits, listKnowledgeSourceChanges, listKnowledgeSourceItems, listKnowledgeSyncRuns, listMemoryEvidence, listMemoryRecords, listMemorySignals, listMemoryTraceEvents, listNoteRecords, listObjectActivityRecords, listObjectLinkRecords, listProjectActivityRecords, listRegisteredMigrationTargets, listSessionMetadata, listSessionsByAgent, listTemporalAssertions, listUnderstandingEvidence, listUnderstandings, listUserPeople, loadCompactionSourceSnapshot, loadLlmMessagesForSession, loadTranscriptHistoryRowsForSession, loadTranscriptRows, loadTranscriptRowsForSession, markMemoryRecordsConflicted, mergeUserPeople, metadataToSessionInsert, mutateQueuedSessionInput, openXopcDatabase, paginateTranscriptMessages, patchSessionMetadata, pruneBoundedKnowledgeSourceItems, readSchemaVersion, readSchemaVersionForTest, reconcileConnectorAccount, recordActivityEvent, recordContextConsolidationDecision, recordContextFeedback, recordContextRun, recoverSessionInputState, recoverStaleConnectorLearningJobs, refreshConnectorAccountCurrent, rejectUnderstanding, releaseConnectorWebhookDelivery, replaceConnectorCatalogEntries, replaceLatestSessionTurnAndQueueInput, replaceTranscriptRows, replaceUserPeopleIndex, requireXopcDatabase, resetSessionRecord, resetXopcDatabaseSingletonForTest, resolveMigrationsDir, resolveSessionAgentId, resolveXopcDatabasePath, restoreBeforeCompactionBoundary, reviseCollaborationRule, reviseUnderstanding, revokeEndpointPrincipal, runSqliteWriteTransaction, searchActiveUnderstandings, searchMemoryRecords, searchSessionTranscript, sessionConfigRowToConfig, sessionRowToMetadata, setCollaborationRuleStatus, setConnectorLearningPaused, setInteractionState, setKnowledgeConsumerWatermark, setKnowledgeSourceCursor, setKnowledgeSourceItemSynthesisStatus, setMemoryProviderState, setMemoryRecordStatus, setMemoryTurnFeedback, setSessionConfig, setSessionInputStatus, setSessionTaskPlan, setUnderstandingStatus, setUserTrustPolicy, startContextConsolidationRun, startEndpointToolInvocationAudit, startKnowledgeSyncRun, summarizeMemoryRecallFeedback, summarizeUnderstandingFeedback, summarizeUserPeople, summarizeUserUnderstandingQuality, touchEndpointPrincipal, transcriptEntryRowToStoredRow, updateConnectorLearningJob, updateInteractionStateFromMessage, updateRelationshipSettings, updateSessionConfig, updateSessionStats, updateUserPerson, updateUserProfile, upsertConnectorActionMetadata, upsertConnectorCatalogEntry, upsertConnectorConnection, upsertConnectorInstallation, upsertConnectorSyncPolicy, upsertKnowledgeSourceItems, upsertMemoryRecord, upsertNoteAgentContextRecord, upsertNoteRecord, validateLatestSessionTurnTarget };
39
+ export { COMPOSER_INPUT_HISTORY_LIMIT, DatabaseSchemaMigrationGapError, DatabaseSchemaTooNewError, SCHEMA_META_SCHEMA_VERSION_KEY, XOPC_DB_BASELINE_SCHEMA_VERSION, XOPC_DB_FILENAME, XOPC_DB_SCHEMA_VERSION, acknowledgeHomeAttention, appendCompactionBoundaryIfUnchanged, appendComposerInputHistory, appendConnectorExecutionAudit, appendMemorySignal, appendMemoryTraceEvent, appendTranscriptEntry, applyPendingMigrations, attachMemoryEvidence, bindEndpointPrincipal, buildDefaultSessionMetadata, buildGlobalSessionStats, buildRelationshipPrompt, cancelQueuedSessionInput, claimConnectorWebhookDelivery, claimContextExtractionRun, claimKnowledgeSourceItems, claimNextConnectorLearningJob, claimNextSessionInput, classifyStoredRow, clearComposerInputHistory, closeTemporalAssertions, closeUnderstandingValidity, closeXopcDatabase, completeConnectorWebhookDelivery, completeKnowledgeSourceItemSynthesis, consumeConnectorApproval, consumeContextConsent, countKnowledgeSourceItems, countMemoryRecordsBySourceInstanceId, createCollaborationRule, createConnectorApproval, createContextEvidence, createEndpointPrincipal, createObjectLinkRecord, createTemporalAssertion, createUnderstanding, decideConnectorApproval, decideContextConsent, deleteCollaborationRule, deleteConnectorConnection, deleteConnectorInstallation, deleteKnowledgeSourceItems, deleteMemoryEvidenceForRecord, deleteMemoryRecord, deleteMemoryRecordsBySourceInstanceId, deleteNoteAgentContextRecord, deleteNoteRecord, deleteObjectLinkRecord, deleteSessionConfig, deleteSessionRecord, deleteUnderstanding, discoverSqlMigrations, enqueueConnectorLearningJob, ensureContextConsent, ensureSessionRecord, ensureXopcDatabaseSchema, estimateTokensFromMessages, extractFtsContent, findSessionInput, findSessionKeyBySessionId, finishContextConsolidationRun, finishContextExtractionRun, finishEndpointToolInvocationAudit, finishKnowledgeSyncRun, finishSessionInputRun, getActivityEventRecord, getCachedConnectorCatalogEntry, getCollaborationRule, getConnectorAccount, getConnectorApproval, getConnectorConnection, getConnectorInstallation, getConnectorSyncPolicy, getConnectorSyncPolicyForConnection, getContextConsolidationRun, getContextExtractionRun, getCurrentSessionId, getEndpointPrincipal, getGlobalSessionStats, getInteractionState, getKnowledgeConsumerWatermark, getKnowledgeSourceCursor, getKnowledgeSourceItem, getMemoryProviderState, getMemoryRecord, getMemoryTurnFeedback, getNoteAgentContextRecord, getNoteRecord, getRelationshipSettings, getSessionConfig, getSessionInputById, getSessionInputState, getSessionMetadata, getSessionPersistedLevels, getSessionTaskPlan, getSqliteDatabase, getTurnPersonalization, getUnderstanding, getUserPeopleIndexState, getUserPerson, getUserProfile, getUserTrustPolicy, getXopcDatabase, hasContextConsent, hasIndependentExtractionOutput, hasSessionConfig, hasUnresolvedMemoryConflict, incrementSessionStatsOnAppend, insertSessionInput, inspectSchemaMigrationStatus, isHomeAttentionAcknowledged, isProactiveSupportAllowed, isUnderstandingSuppressed, isXopcDatabaseOpen, linkContextObjects, linkUnderstandingEvidence, listActiveSessionInputRuns, listActivityRecords, listCachedConnectorCatalogEntries, listCollaborationRules, listCompactionBoundaries, listComposerInputHistory, listConnectorAccounts, listConnectorActionMetadata, listConnectorApprovals, listConnectorConnections, listConnectorExecutionAudit, listConnectorInstallations, listConnectorLearningJobs, listConnectorSyncPolicies, listContextConsolidationDecisions, listContextConsolidationRuns, listContextExtractionOutputs, listContextExtractionRuns, listContextObjectRelations, listEndpointPrincipals, listEndpointToolInvocationAudits, listKnowledgeSourceChanges, listKnowledgeSourceItems, listKnowledgeSyncRuns, listMemoryEvidence, listMemoryRecords, listMemorySignals, listMemoryTraceEvents, listNoteRecords, listObjectActivityRecords, listObjectLinkRecords, listProjectActivityRecords, listRegisteredMigrationTargets, listSessionMetadata, listSessionsByAgent, listTemporalAssertions, listUnderstandingEvidence, listUnderstandings, listUserPeople, loadCompactionSourceSnapshot, loadLlmMessagesForSession, loadTranscriptHistoryRowsForSession, loadTranscriptRows, loadTranscriptRowsForSession, markMemoryRecordsConflicted, mergeUserPeople, metadataToSessionInsert, mutateQueuedSessionInput, openXopcDatabase, paginateTranscriptMessages, patchSessionMetadata, pruneBoundedKnowledgeSourceItems, readSchemaVersion, readSchemaVersionForTest, reconcileConnectorAccount, recordActivityEvent, recordContextConsolidationDecision, recordContextFeedback, recordContextRun, recoverSessionInputState, recoverStaleConnectorLearningJobs, refreshConnectorAccountCurrent, rejectUnderstanding, releaseConnectorWebhookDelivery, replaceConnectorCatalogEntries, replaceLatestSessionTurnAndQueueInput, replaceTranscriptRows, replaceUserPeopleIndex, requireXopcDatabase, resetSessionRecord, resetXopcDatabaseSingletonForTest, resolveMigrationsDir, resolveSessionAgentId, resolveXopcDatabasePath, restoreBeforeCompactionBoundary, reviseCollaborationRule, reviseUnderstanding, revokeEndpointPrincipal, runSqliteWriteTransaction, searchActiveUnderstandings, searchMemoryRecords, searchSessionTranscript, sessionConfigRowToConfig, sessionRowToMetadata, setCollaborationRuleStatus, setConnectorLearningPaused, setInteractionState, setKnowledgeConsumerWatermark, setKnowledgeSourceCursor, setKnowledgeSourceItemSynthesisStatus, setMemoryProviderState, setMemoryRecordStatus, setMemoryTurnFeedback, setSessionConfig, setSessionInputStatus, setSessionTaskPlan, setUnderstandingStatus, setUserTrustPolicy, startContextConsolidationRun, startEndpointToolInvocationAudit, startKnowledgeSyncRun, summarizeMemoryRecallFeedback, summarizeUnderstandingFeedback, summarizeUserPeople, summarizeUserUnderstandingQuality, touchEndpointPrincipal, transcriptEntryRowToStoredRow, updateConnectorLearningJob, updateInteractionStateFromMessage, updateRelationshipSettings, updateSessionConfig, updateSessionStats, updateUserPerson, updateUserProfile, upsertConnectorActionMetadata, upsertConnectorCatalogEntry, upsertConnectorConnection, upsertConnectorInstallation, upsertConnectorSyncPolicy, upsertKnowledgeSourceItems, upsertMemoryRecord, upsertNoteAgentContextRecord, upsertNoteRecord, validateLatestSessionTurnTarget };
@@ -0,0 +1,79 @@
1
+ CREATE TABLE notification_devices (
2
+ device_id TEXT PRIMARY KEY,
3
+ platform TEXT NOT NULL CHECK (platform IN ('ios', 'android')),
4
+ push_token TEXT NOT NULL,
5
+ push_provider TEXT NOT NULL DEFAULT 'expo',
6
+ enabled INTEGER NOT NULL DEFAULT 1 CHECK (enabled IN (0, 1)),
7
+ permissions TEXT NOT NULL DEFAULT 'unknown' CHECK (permissions IN ('granted', 'denied', 'unknown')),
8
+ preferences_json TEXT NOT NULL DEFAULT '{}',
9
+ locale TEXT NOT NULL DEFAULT 'en' CHECK (locale IN ('en', 'zh')),
10
+ app_version TEXT,
11
+ lease_expires_at INTEGER NOT NULL,
12
+ last_seen_at INTEGER NOT NULL,
13
+ created_at INTEGER NOT NULL,
14
+ updated_at INTEGER NOT NULL
15
+ );
16
+
17
+ INSERT INTO notification_devices (
18
+ device_id, platform, push_token, push_provider, enabled, permissions,
19
+ preferences_json, locale, app_version, lease_expires_at,
20
+ last_seen_at, created_at, updated_at
21
+ )
22
+ SELECT
23
+ device_id, platform, push_token, push_provider, enabled, permissions,
24
+ preferences_json, 'en', app_version,
25
+ (unixepoch() * 1000) + 604800000,
26
+ last_seen_at, created_at, updated_at
27
+ FROM mobile_devices;
28
+
29
+ CREATE UNIQUE INDEX idx_notification_devices_push_token
30
+ ON notification_devices(push_provider, push_token);
31
+ CREATE INDEX idx_notification_devices_delivery
32
+ ON notification_devices(enabled, permissions, lease_expires_at);
33
+
34
+ CREATE TABLE notification_events (
35
+ event_id TEXT PRIMARY KEY,
36
+ dedupe_key TEXT NOT NULL UNIQUE,
37
+ event_type TEXT NOT NULL,
38
+ target_json TEXT NOT NULL,
39
+ priority TEXT NOT NULL CHECK (priority IN ('normal', 'high')),
40
+ title_en TEXT NOT NULL,
41
+ title_zh TEXT NOT NULL,
42
+ body_en TEXT,
43
+ body_zh TEXT,
44
+ payload_json TEXT NOT NULL DEFAULT '{}',
45
+ created_at INTEGER NOT NULL
46
+ );
47
+
48
+ CREATE INDEX idx_notification_events_created
49
+ ON notification_events(created_at, event_id);
50
+
51
+ CREATE TABLE notification_deliveries (
52
+ event_id TEXT NOT NULL,
53
+ device_id TEXT NOT NULL,
54
+ status TEXT NOT NULL CHECK (status IN ('pending', 'accepted', 'delivered', 'dead')),
55
+ attempts INTEGER NOT NULL DEFAULT 0,
56
+ next_attempt_at INTEGER NOT NULL,
57
+ provider_ticket_id TEXT,
58
+ last_error TEXT,
59
+ updated_at INTEGER NOT NULL,
60
+ PRIMARY KEY (event_id, device_id),
61
+ FOREIGN KEY (event_id) REFERENCES notification_events(event_id) ON DELETE CASCADE,
62
+ FOREIGN KEY (device_id) REFERENCES notification_devices(device_id) ON DELETE CASCADE
63
+ );
64
+
65
+ CREATE INDEX idx_notification_deliveries_due
66
+ ON notification_deliveries(status, next_attempt_at);
67
+
68
+ CREATE TABLE notification_acknowledgements (
69
+ event_id TEXT NOT NULL,
70
+ consumer_id TEXT NOT NULL,
71
+ surface TEXT NOT NULL CHECK (surface IN ('web', 'electron', 'mobile')),
72
+ acknowledged_at INTEGER NOT NULL,
73
+ PRIMARY KEY (event_id, consumer_id),
74
+ FOREIGN KEY (event_id) REFERENCES notification_events(event_id) ON DELETE CASCADE
75
+ );
76
+
77
+ DROP TABLE mobile_activity_acknowledgements;
78
+ DROP TABLE mobile_activity_events;
79
+ DROP TABLE mobile_devices;
@@ -3,7 +3,7 @@ import type { ApplyMigrationsOptions } from './types.js';
3
3
  /** Baseline schema version applied from schema.sql on first open. */
4
4
  export declare const XOPC_DB_BASELINE_SCHEMA_VERSION = 11;
5
5
  /** Latest schema version this release supports (increment when adding migrations). */
6
- export declare const XOPC_DB_SCHEMA_VERSION = 135;
6
+ export declare const XOPC_DB_SCHEMA_VERSION = 136;
7
7
  export declare function resolveMigrationsDir(override?: string): string;
8
8
  /**
9
9
  * Apply sequential SQL migrations from (currentVersion + 1) through targetVersion.
@@ -12,7 +12,7 @@ const log = createLogger("Sqlite:Migrations");
12
12
  /** Baseline schema version applied from schema.sql on first open. */
13
13
  const XOPC_DB_BASELINE_SCHEMA_VERSION = 11;
14
14
  /** Latest schema version this release supports (increment when adding migrations). */
15
- const XOPC_DB_SCHEMA_VERSION = 135;
15
+ const XOPC_DB_SCHEMA_VERSION = 136;
16
16
  const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
17
17
  function backupBeforeTaskCutover(db, databasePath) {
18
18
  db.exec("PRAGMA wal_checkpoint(FULL)");
@@ -57,7 +57,7 @@ function applySingleMigration(db, migration) {
57
57
  * Each step runs in its own transaction and updates schema_meta on success.
58
58
  */
59
59
  function applyPendingMigrations(db, options = {}) {
60
- const targetVersion = options.targetVersion ?? 135;
60
+ const targetVersion = options.targetVersion ?? 136;
61
61
  let currentVersion = readSchemaVersion(db);
62
62
  if (currentVersion > targetVersion) throw new DatabaseSchemaTooNewError(currentVersion, targetVersion);
63
63
  if (currentVersion >= targetVersion) return currentVersion;
@@ -94,7 +94,7 @@ function applyPendingMigrations(db, options = {}) {
94
94
  }
95
95
  /** Inspect schema version without mutating the database. */
96
96
  function inspectSchemaMigrationStatus(db, options = {}) {
97
- const appVersion = options.targetVersion ?? 135;
97
+ const appVersion = options.targetVersion ?? 136;
98
98
  const dbVersion = readSchemaVersion(db);
99
99
  if (dbVersion > appVersion) return {
100
100
  dbVersion,
@@ -19,7 +19,7 @@ const log = createLogger('Sqlite:Migrations');
19
19
  export const XOPC_DB_BASELINE_SCHEMA_VERSION = 11;
20
20
 
21
21
  /** Latest schema version this release supports (increment when adding migrations). */
22
- export const XOPC_DB_SCHEMA_VERSION = 135;
22
+ export const XOPC_DB_SCHEMA_VERSION = 136;
23
23
 
24
24
  const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
25
25
 
@@ -32,7 +32,7 @@ function ensureXopcDatabaseSchema(db, options = {}) {
32
32
  currentVersion = 11;
33
33
  }
34
34
  applyPendingMigrations(db, {
35
- targetVersion: 135,
35
+ targetVersion: 136,
36
36
  databasePath: options.databasePath
37
37
  });
38
38
  }
@@ -31,6 +31,11 @@ export type SessionInputState = {
31
31
  inputs: SessionInput[];
32
32
  };
33
33
  export declare function getSessionInputState(sessionKey: string): SessionInputState;
34
+ /** Durable active webchat runs claimed by the session-input coordinator. */
35
+ export declare function listActiveSessionInputRuns(): Array<{
36
+ sessionKey: string;
37
+ runId: string;
38
+ }>;
34
39
  export declare function findSessionInput(sessionKey: string, clientMessageId: string): SessionInput | undefined;
35
40
  export declare function getSessionInputById(sessionKey: string, id: string): SessionInput | undefined;
36
41
  export declare function insertSessionInput(input: {
@@ -56,6 +56,14 @@ function getSessionInputState(sessionKey) {
56
56
  })
57
57
  };
58
58
  }
59
+ /** Durable active webchat runs claimed by the session-input coordinator. */
60
+ function listActiveSessionInputRuns() {
61
+ return getSqliteDatabase().prepare(`SELECT session_key, active_run_id
62
+ FROM session_input_runtime WHERE active_run_id IS NOT NULL`).all().map((row) => ({
63
+ sessionKey: row.session_key,
64
+ runId: row.active_run_id
65
+ }));
66
+ }
59
67
  function findSessionInput(sessionKey, clientMessageId) {
60
68
  const row = getSqliteDatabase().prepare(`${SELECT_INPUTS} WHERE session_key = ? AND client_message_id = ?`).get(sessionKey, clientMessageId);
61
69
  return row ? mapInput(row) : void 0;
@@ -158,4 +166,4 @@ function recoverSessionInputState() {
158
166
  });
159
167
  }
160
168
  //#endregion
161
- export { cancelQueuedSessionInput, claimNextSessionInput, findSessionInput, finishSessionInputRun, getSessionInputById, getSessionInputState, insertSessionInput, mutateQueuedSessionInput, recoverSessionInputState, setSessionInputStatus };
169
+ export { cancelQueuedSessionInput, claimNextSessionInput, findSessionInput, finishSessionInputRun, getSessionInputById, getSessionInputState, insertSessionInput, listActiveSessionInputRuns, mutateQueuedSessionInput, recoverSessionInputState, setSessionInputStatus };
@@ -1,12 +1,12 @@
1
1
  import { TaskRepository } from "./task-repository.js";
2
2
  import { TaskConversationConflictError, TaskConversationRepository } from "./task-conversation-repository.js";
3
+ import { enqueueTaskAttentionRequiredEvent, enqueueTaskChanged, enqueueTaskChangedEvent } from "./task-change-events.js";
3
4
  import { TaskRunRepository } from "./task-run-repository.js";
4
5
  import { TaskReadModelProjector } from "./task-read-model-projector.js";
5
6
  import { resolveExecutionContext } from "./execution-context.js";
6
7
  import { AttentionGovernor } from "./attention-governor.js";
7
8
  import { HomeQueryService, buildHomeWorkbench, decisionFromTask } from "./home-query-service.js";
8
9
  import { TaskContextRepository } from "./task-context-repository.js";
9
- import { enqueueTaskChanged, enqueueTaskChangedEvent } from "./task-change-events.js";
10
10
  import { TaskDependencyError, TaskDependencyService } from "./task-dependency-service.js";
11
11
  import { TaskApplicationService } from "./task-application-service.js";
12
12
  import { TaskWorkflowCoordinator } from "./task-workflow-coordinator.js";
@@ -22,4 +22,4 @@ import { TaskDeletionService } from "./task-deletion-service.js";
22
22
  import { TaskOutboxDispatcher } from "./task-outbox-dispatcher.js";
23
23
  import { TaskRunDispatcher } from "./task-run-dispatcher.js";
24
24
  import { TaskSignalService } from "./task-signal-service.js";
25
- export { AttentionGovernor, HomeQueryService, ModelTaskContractPlanner, ProjectMonitoringService, ProjectOperatingViewService, TaskApplicationService, TaskContextRepository, TaskConversationConflictError, TaskConversationRepository, TaskDeletionService, TaskDependencyError, TaskDependencyService, TaskHandoffService, TaskOutboxDispatcher, TaskReadModelProjector, TaskRepository, TaskRunCoordinator, TaskRunDispatcher, TaskRunRepository, TaskSignalService, TaskValueMetricsService, TaskWorkflowCoordinator, assembleTaskContext, buildHomeWorkbench, buildTaskExecutionDirective, decideProactiveDisposition, decisionFromTask, defineTaskContract, enqueueTaskChanged, enqueueTaskChangedEvent, getTaskContextManifest, getTaskExecutionBrief, isInQuietHours, nextQuietHoursEnd, parseTaskContractResponse, proactiveDispositionReason, resolveExecutionContext, summarizeProjectOperatingView };
25
+ export { AttentionGovernor, HomeQueryService, ModelTaskContractPlanner, ProjectMonitoringService, ProjectOperatingViewService, TaskApplicationService, TaskContextRepository, TaskConversationConflictError, TaskConversationRepository, TaskDeletionService, TaskDependencyError, TaskDependencyService, TaskHandoffService, TaskOutboxDispatcher, TaskReadModelProjector, TaskRepository, TaskRunCoordinator, TaskRunDispatcher, TaskRunRepository, TaskSignalService, TaskValueMetricsService, TaskWorkflowCoordinator, assembleTaskContext, buildHomeWorkbench, buildTaskExecutionDirective, decideProactiveDisposition, decisionFromTask, defineTaskContract, enqueueTaskAttentionRequiredEvent, enqueueTaskChanged, enqueueTaskChangedEvent, getTaskContextManifest, getTaskExecutionBrief, isInQuietHours, nextQuietHoursEnd, parseTaskContractResponse, proactiveDispositionReason, resolveExecutionContext, summarizeProjectOperatingView };
@@ -1,9 +1,9 @@
1
1
  import { getSqliteDatabase, runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
2
2
  import { TaskRepository } from "./task-repository.js";
3
+ import { enqueueTaskAttentionRequiredEvent, enqueueTaskChangedEvent } from "./task-change-events.js";
3
4
  import { TaskRunRepository } from "./task-run-repository.js";
4
5
  import { TaskReadModelProjector } from "./task-read-model-projector.js";
5
6
  import { TaskContextRepository } from "./task-context-repository.js";
6
- import { enqueueTaskChangedEvent } from "./task-change-events.js";
7
7
  import { TaskDependencyService } from "./task-dependency-service.js";
8
8
  import { createHash, randomUUID } from "node:crypto";
9
9
  //#region src/tasks/task-application-service.ts
@@ -153,6 +153,15 @@ var TaskApplicationService = class {
153
153
  ],
154
154
  actor
155
155
  });
156
+ else if (started.ok === false && started.reason === "blocked") {
157
+ if (!this.#runs.listActiveWaits(current.id).some((wait) => wait.kind === "approval" || wait.kind === "user_input")) enqueueTaskAttentionRequiredEvent(db, {
158
+ taskId: current.id,
159
+ taskTitle: current.title,
160
+ ...current.projectId ? { projectId: current.projectId } : {},
161
+ reason: "blocked",
162
+ correlationId: input.idempotencyKey
163
+ });
164
+ }
156
165
  return started;
157
166
  });
158
167
  }
@@ -275,11 +284,15 @@ var TaskApplicationService = class {
275
284
  }
276
285
  }
277
286
  if (result.ok === false) {
278
- if (result.reason === "blocked") this.enqueueEvent(db, "task.attention_required.v2", task.id, input.idempotencyKey, {
279
- taskId: task.id,
280
- reason: "blocked",
281
- projectId: task.projectId
282
- });
287
+ if (result.reason === "blocked") {
288
+ if (!this.#runs.listActiveWaits(task.id).some((wait) => wait.kind === "approval" || wait.kind === "user_input")) enqueueTaskAttentionRequiredEvent(db, {
289
+ taskId: task.id,
290
+ taskTitle: task.title,
291
+ ...task.projectId ? { projectId: task.projectId } : {},
292
+ reason: "blocked",
293
+ correlationId: input.idempotencyKey
294
+ });
295
+ }
283
296
  return result;
284
297
  }
285
298
  this.recordCommand(db, {
@@ -301,6 +314,10 @@ var TaskApplicationService = class {
301
314
  runId: result.runId
302
315
  });
303
316
  if (result.model.task.phase !== task.phase) this.enqueueEvent(db, "task.phase_changed.v2", task.id, input.idempotencyKey, {
317
+ task: {
318
+ id: result.model.task.id,
319
+ title: result.model.task.title
320
+ },
304
321
  taskId: task.id,
305
322
  from: task.phase,
306
323
  to: result.model.task.phase,
@@ -344,6 +361,14 @@ var TaskApplicationService = class {
344
361
  const task = this.#tasks.require(run.taskId);
345
362
  if (input.receipt.status !== "succeeded") {
346
363
  const model = this.#projector.project(task);
364
+ enqueueTaskAttentionRequiredEvent(db, {
365
+ taskId: task.id,
366
+ taskTitle: task.title,
367
+ ...task.projectId ? { projectId: task.projectId } : {},
368
+ reason: "failed",
369
+ detail: input.terminalMessage ?? input.receipt.summary,
370
+ correlationId: run.id
371
+ });
347
372
  enqueueTaskChangedEvent(db, {
348
373
  taskId: task.id,
349
374
  projectId: task.projectId,
@@ -370,6 +395,17 @@ var TaskApplicationService = class {
370
395
  ...phase === "closed" ? { resolution: "done" } : {}
371
396
  });
372
397
  if (!updated) throw new Error("Task changed while its run was being finalized");
398
+ this.enqueueEvent(db, "task.phase_changed.v2", updated.id, run.id, {
399
+ task: {
400
+ id: updated.id,
401
+ title: updated.title
402
+ },
403
+ taskId: updated.id,
404
+ from: task.phase,
405
+ to: updated.phase,
406
+ resolution: updated.resolution,
407
+ projectId: updated.projectId
408
+ });
373
409
  enqueueTaskChangedEvent(db, {
374
410
  taskId: updated.id,
375
411
  projectId: updated.projectId,
@@ -10,4 +10,13 @@ export type TaskChangeInput = {
10
10
  occurredAt?: number;
11
11
  };
12
12
  export declare function enqueueTaskChangedEvent(db: DatabaseSync, input: TaskChangeInput): void;
13
+ export declare function enqueueTaskAttentionRequiredEvent(db: DatabaseSync, input: {
14
+ taskId: string;
15
+ taskTitle: string;
16
+ projectId?: string;
17
+ reason: 'blocked' | 'failed' | 'user_input' | 'approval';
18
+ detail?: string;
19
+ correlationId: string;
20
+ occurredAt?: number;
21
+ }): void;
13
22
  export declare function enqueueTaskChanged(input: TaskChangeInput): void;
@@ -22,8 +22,24 @@ function enqueueTaskChangedEvent(db, input) {
22
22
  occurredAt
23
23
  }), occurredAt);
24
24
  }
25
+ function enqueueTaskAttentionRequiredEvent(db, input) {
26
+ const occurredAt = input.occurredAt ?? Date.now();
27
+ db.prepare(`INSERT INTO domain_outbox (
28
+ event_id, event_type, subject_kind, subject_id, correlation_id,
29
+ payload_json, created_at
30
+ ) VALUES (?, 'task.attention_required.v2', 'task', ?, ?, ?, ?)`).run(randomUUID(), input.taskId, input.correlationId, JSON.stringify({
31
+ task: {
32
+ id: input.taskId,
33
+ title: input.taskTitle
34
+ },
35
+ taskId: input.taskId,
36
+ reason: input.reason,
37
+ ...input.detail ? { detail: input.detail } : {},
38
+ ...input.projectId ? { projectId: input.projectId } : {}
39
+ }), occurredAt);
40
+ }
25
41
  function enqueueTaskChanged(input) {
26
42
  runSqliteWriteTransaction((db) => enqueueTaskChangedEvent(db, input));
27
43
  }
28
44
  //#endregion
29
- export { enqueueTaskChanged, enqueueTaskChangedEvent };
45
+ export { enqueueTaskAttentionRequiredEvent, enqueueTaskChanged, enqueueTaskChangedEvent };
@@ -10,10 +10,14 @@ var TaskOutboxDispatcher = class {
10
10
  ORDER BY created_at, event_id LIMIT ?`).all(Math.max(1, Math.min(500, Math.floor(limit))));
11
11
  let published = 0;
12
12
  for (const row of rows) try {
13
+ const payload = JSON.parse(row.payload_json);
13
14
  this.publish({
14
15
  type: row.event_type,
15
16
  source: "tasks",
16
- payload: JSON.parse(row.payload_json),
17
+ payload: {
18
+ ...payload,
19
+ sourceEventId: row.event_id
20
+ },
17
21
  occurredAtMs: row.created_at
18
22
  });
19
23
  runSqliteWriteTransaction((db) => {
@@ -2,12 +2,14 @@ import { createLogger } from "../utils/logger/index.js";
2
2
  import { init_logger } from "../utils/logger.js";
3
3
  import { TaskRepository } from "./task-repository.js";
4
4
  import { TaskRunRepository } from "./task-run-repository.js";
5
+ import { TaskApplicationService } from "./task-application-service.js";
5
6
  //#region src/tasks/task-run-dispatcher.ts
6
7
  init_logger();
7
8
  const log = createLogger("TaskRunDispatcher");
8
9
  var TaskRunDispatcher = class {
9
10
  #runs = new TaskRunRepository();
10
11
  #tasks = new TaskRepository();
12
+ #application = new TaskApplicationService();
11
13
  #draining = /* @__PURE__ */ new Set();
12
14
  constructor(deps) {
13
15
  this.deps = deps;
@@ -49,9 +51,9 @@ var TaskRunDispatcher = class {
49
51
  "running",
50
52
  "waiting",
51
53
  "verifying"
52
- ].includes(current.status)) this.#runs.finalize({
54
+ ].includes(current.status)) this.#application.completeRun({
53
55
  runId: current.id,
54
- expectedVersion: current.version,
56
+ expectedRunVersion: current.version,
55
57
  terminalCode: "dispatch_failed",
56
58
  terminalMessage: error instanceof Error ? error.message : String(error),
57
59
  receipt: {
@@ -1,4 +1,5 @@
1
1
  import { getSqliteDatabase, runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
2
+ import { enqueueTaskAttentionRequiredEvent } from "./task-change-events.js";
2
3
  import { randomUUID } from "node:crypto";
3
4
  //#region src/tasks/task-run-repository.ts
4
5
  const ACTIVE_RUN_STATUSES = [
@@ -237,6 +238,18 @@ var TaskRunRepository = class {
237
238
  reason: input.reason.trim(),
238
239
  resumeAt: input.resumeAt
239
240
  }, input.actor ?? { kind: "system" }, now);
241
+ if (input.kind === "user_input" || input.kind === "approval") {
242
+ const task = db.prepare("SELECT title, project_id FROM tasks WHERE task_id = ?").get(input.taskId);
243
+ if (task) enqueueTaskAttentionRequiredEvent(db, {
244
+ taskId: input.taskId,
245
+ taskTitle: task.title,
246
+ ...task.project_id ? { projectId: task.project_id } : {},
247
+ reason: input.kind,
248
+ detail: input.reason.trim(),
249
+ correlationId: id,
250
+ occurredAt: now
251
+ });
252
+ }
240
253
  });
241
254
  return this.requireWait(id);
242
255
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xopcai/xopc",
3
- "version": "0.0.227",
3
+ "version": "0.0.228",
4
4
  "description": "Local-first personal AI assistant with reviewable user understanding, durable tasks, workflows, automations, and local or cloud models.",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
@@ -158,8 +158,8 @@
158
158
  "vitest": "4.1.5",
159
159
  "vue": "^3.5.35",
160
160
  "@xopcai/endpoint-tools-protocol": "0.0.0",
161
- "@xopcai/gateway-contract": "0.0.0",
162
161
  "@xopcai/realtime-protocol": "0.0.0",
162
+ "@xopcai/gateway-contract": "0.0.0",
163
163
  "@xopcai/realtime-client": "0.0.0"
164
164
  },
165
165
  "scripts": {