@xopcai/xopc 0.0.197 → 0.0.200

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 (244) 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-BwmdJ5pv.js → activity-store-DYVN1hRp.js} +1 -1
  4. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-C5st7lt5.js → agent-avatar-dicebear-eMarz29u.js} +1 -1
  5. package/dist/gateway/static/root/assets/agents-DeLW-454.js +68 -0
  6. package/dist/gateway/static/root/assets/{api-BM1hNzjw.js → api-B8mzbAle.js} +1 -1
  7. package/dist/gateway/static/root/assets/app-management-settings-panel-yfb68wq9.js +1 -0
  8. package/dist/gateway/static/root/assets/appearance-settings-B8SPknFM.js +1 -0
  9. package/dist/gateway/static/root/assets/apps-page-Bmor22Ny.js +1 -0
  10. package/dist/gateway/static/root/assets/{archive-plugin-qL07mwnY.js → archive-plugin-DmYRiOP3.js} +1 -1
  11. package/dist/gateway/static/root/assets/{attachment-load-CqSobpdi.js → attachment-load-D66JvcPi.js} +1 -1
  12. package/dist/gateway/static/root/assets/automations-page-Q4e8m11h.js +6 -0
  13. package/dist/gateway/static/root/assets/{binary-plugins-DBRPyLYE.js → binary-plugins-ChIsZArT.js} +1 -1
  14. package/dist/gateway/static/root/assets/{block-editor-6wG1LU7t.js → block-editor-Ck-k_d0Z.js} +4 -4
  15. package/dist/gateway/static/root/assets/browser-settings-page--uVL73Sb.js +8 -0
  16. package/dist/gateway/static/root/assets/browser-workflow-inputs-_ndaecsf.js +1 -0
  17. package/dist/gateway/static/root/assets/{browser-workflows-page-BEu3E0et.js → browser-workflows-page-BC5jm1zL.js} +2 -2
  18. package/dist/gateway/static/root/assets/button-CSEbl4zT.js +1 -0
  19. package/dist/gateway/static/root/assets/capabilities-settings-panel-DXpf3QYN.js +7 -0
  20. package/dist/gateway/static/root/assets/{capability-presets-api-BoOzFotX.js → capability-presets-api-Dy1jZJKs.js} +1 -1
  21. package/dist/gateway/static/root/assets/capability-presets-settings-panel-NAjCsrTi.js +3 -0
  22. package/dist/gateway/static/root/assets/{channel-recipient-api-_8IiTxRB.js → channel-recipient-api-D-9qiuio.js} +1 -1
  23. package/dist/gateway/static/root/assets/channels-settings-CiWUo7jW.js +1 -0
  24. package/dist/gateway/static/root/assets/channels-status-swr-B4Rbh3bM.js +1 -0
  25. package/dist/gateway/static/root/assets/connectors-api-5xiCRVUS.js +1 -0
  26. package/dist/gateway/static/root/assets/connectors-page-DOwlMseU.js +2 -0
  27. package/dist/gateway/static/root/assets/copy-Dv6d4Dvw.js +1 -0
  28. package/dist/gateway/static/root/assets/cron-expression-B3PebXKZ.js +2 -0
  29. package/dist/gateway/static/root/assets/date-picker-C1Vke7Xn.js +1 -0
  30. package/dist/gateway/static/root/assets/dependency-picker-FrQ0tsBC.js +1 -0
  31. package/dist/gateway/static/root/assets/{desktop-pet-CsLR4pWB.js → desktop-pet-CPBAzNJi.js} +1 -1
  32. package/dist/gateway/static/root/assets/desktop-pet-settings-D6s3glDP.js +1 -0
  33. package/dist/gateway/static/root/assets/directory-picker-path-field-CbNQ7QQS.js +1 -0
  34. package/dist/gateway/static/root/assets/dist-BPoKqzeH.js +1 -0
  35. package/dist/gateway/static/root/assets/dist-DY4Af0fY.js +1 -0
  36. package/dist/gateway/static/root/assets/{extension-debug-page-CD73euAM.js → extension-debug-page-dTK14hUv.js} +1 -1
  37. package/dist/gateway/static/root/assets/{extension-page-DAKjpNwd.js → extension-page-Cpdj1Kip.js} +1 -1
  38. package/dist/gateway/static/root/assets/extension-settings-page-iNHUZGd5.js +1 -0
  39. package/dist/gateway/static/root/assets/external-link-DjqjBmc3.js +1 -0
  40. package/dist/gateway/static/root/assets/form-field-width-DJw_jULe.js +1 -0
  41. package/dist/gateway/static/root/assets/gateway-config-api-SvMvWqMM.js +1 -0
  42. package/dist/gateway/static/root/assets/gateway-config-swr-jB0hAhWO.js +1 -0
  43. package/dist/gateway/static/root/assets/gateway-settings-BilR3dGZ.js +1 -0
  44. package/dist/gateway/static/root/assets/{gateway-startup-retry--2njcDpC.js → gateway-startup-retry-C-fRlHCL.js} +1 -1
  45. package/dist/gateway/static/root/assets/globe-DSSGFWFn.js +1 -0
  46. package/dist/gateway/static/root/assets/heartbeat-settings-zK40dcQ5.js +1 -0
  47. package/dist/gateway/static/root/assets/home-page-NyXy5MLr.js +3 -0
  48. package/dist/gateway/static/root/assets/index-Bp8I_eX4.css +1 -0
  49. package/dist/gateway/static/root/assets/index-CCK8zdlS.js +152 -0
  50. package/dist/gateway/static/root/assets/interaction-BmQogeqz.js +1 -0
  51. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-HMhxgOuD.js → keyboard-shortcuts-settings-nKzsroud.js} +1 -1
  52. package/dist/gateway/static/root/assets/loader-circle-CifKLtgG.js +1 -0
  53. package/dist/gateway/static/root/assets/local-app-workbench-page-Ds3sPXTh.js +2 -0
  54. package/dist/gateway/static/root/assets/local-apps-page-DV1s_FYh.js +1 -0
  55. package/dist/gateway/static/root/assets/{locale-store-Bjvw7jtg.js → locale-store-xULEcFVs.js} +1 -1
  56. package/dist/gateway/static/root/assets/logs-page-Biz4eA02.js +2 -0
  57. package/dist/gateway/static/root/assets/management-settings-D7KjYgHu.js +1 -0
  58. package/dist/gateway/static/root/assets/{markdown-split-DvVBewS4.js → markdown-split-CTYbpkrd.js} +1 -1
  59. package/dist/gateway/static/root/assets/{media-plugins-YVj7NVzD.js → media-plugins-BXYnaiZ6.js} +1 -1
  60. package/dist/gateway/static/root/assets/messages-3qRsLZuX.js +3 -0
  61. package/dist/gateway/static/root/assets/{notes-page-BDzuL2I9.js → notes-page-CWWVXpw5.js} +1 -1
  62. package/dist/gateway/static/root/assets/notes-workbench-DnBkQihH.js +4 -0
  63. package/dist/gateway/static/root/assets/page-tabs-BaEQMYm_.js +1 -0
  64. package/dist/gateway/static/root/assets/popover-select-BzdnTy3h.js +1 -0
  65. package/dist/gateway/static/root/assets/preview-open-alternatives-pimQAkeI.js +1 -0
  66. package/dist/gateway/static/root/assets/{product-open-page-Bwx_CQht.js → product-open-page-BoYSS_YR.js} +1 -1
  67. package/dist/gateway/static/root/assets/project-detail-page-ZO7t1UkB.js +1 -0
  68. package/dist/gateway/static/root/assets/projects-page-By-vq41g.js +1 -0
  69. package/dist/gateway/static/root/assets/remote-access-hub-C0C_Y9d0.js +1 -0
  70. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-Fe05Nus6.js → runtime-tools-settings-panel-DT8flaX1.js} +4 -4
  71. package/dist/gateway/static/root/assets/schema-form-DabEP-UI.js +1 -0
  72. package/dist/gateway/static/root/assets/sessions-page-BNlVwUzV.js +1 -0
  73. package/dist/gateway/static/root/assets/settings-advanced-gate-CxYBQDaM.js +1 -0
  74. package/dist/gateway/static/root/assets/settings-form-draft-Dy10ldo8.js +1 -0
  75. package/dist/gateway/static/root/assets/settings-form-section-DCsfZAtC.js +1 -0
  76. package/dist/gateway/static/root/assets/{settings-loading-skeleton-BRE_-7Da.js → settings-loading-skeleton-Dac6Yd0T.js} +1 -1
  77. package/dist/gateway/static/root/assets/{settings-page-C1zxCpqB.js → settings-page-BDoYoEar.js} +1 -1
  78. package/dist/gateway/static/root/assets/settings-page-layout-BRQNel6r.js +1 -0
  79. package/dist/gateway/static/root/assets/settings-shell-dialog-layer-BU3UuiJr.js +1 -0
  80. package/dist/gateway/static/root/assets/setup-status-panel-DWijJ1rw.js +1 -0
  81. package/dist/gateway/static/root/assets/share-preview-page-ie9I2_7h.js +2 -0
  82. package/dist/gateway/static/root/assets/shares-settings-DU9VrJLf.js +1 -0
  83. package/dist/gateway/static/root/assets/skill-reload-api-C5RaIvoE.js +1 -0
  84. package/dist/gateway/static/root/assets/skills-page-N0YhaR5S.js +2 -0
  85. package/dist/gateway/static/root/assets/{skills-page.utils-CYwFWTso.js → skills-page.utils-C639DLXr.js} +1 -1
  86. package/dist/gateway/static/root/assets/system-settings-panel-CpshpQtC.js +1 -0
  87. package/dist/gateway/static/root/assets/task-detail-page-DsTocYkK.js +1 -0
  88. package/dist/gateway/static/root/assets/{theme-store-DtxpMC5S.js → theme-store-CHQRiuCF.js} +1 -1
  89. package/dist/gateway/static/root/assets/tool-activity-R8V9l6nk.js +1 -0
  90. package/dist/gateway/static/root/assets/typed-models-lib-q5-Vfqd8.js +1 -0
  91. package/dist/gateway/static/root/assets/use-autosave-ms6rDy59.js +1 -0
  92. package/dist/gateway/static/root/assets/user-context-page-BUxIemwy.js +1 -0
  93. package/dist/gateway/static/root/assets/voice-api-key-field-CObrQWh2.js +1 -0
  94. package/dist/gateway/static/root/assets/work-discovery-overlay-BBxrnEFt.js +1 -0
  95. package/dist/gateway/static/root/assets/work-discovery-page-DN3l-HW3.js +1 -0
  96. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-DqIb1oBB.js → workflow-run-setup-panel-BxXhe3IV.js} +2 -2
  97. package/dist/gateway/static/root/assets/workflows-page-BCHkjd1i.js +5 -0
  98. package/dist/gateway/static/root/assets/working-directory-picker-modal-D-IgcHPp.js +1 -0
  99. package/dist/gateway/static/root/index.html +23 -8
  100. package/dist/package.js +1 -1
  101. package/dist/packages/gateway-contract/src/tasks.js +103 -9
  102. package/dist/src/agent/agent-manager.d.ts +1 -0
  103. package/dist/src/agent/agent-manager.js +1 -0
  104. package/dist/src/agent/context/execution-scope.js +3 -1
  105. package/dist/src/agent/service.js +1 -0
  106. package/dist/src/agent/service.types.d.ts +1 -0
  107. package/dist/src/agent/tasks/task-judge-service.d.ts +1 -1
  108. package/dist/src/agent/tasks/task-judge-service.js +3 -2
  109. package/dist/src/agent/tools/factory.d.ts +2 -0
  110. package/dist/src/agent/tools/factory.js +2 -1
  111. package/dist/src/agent/tools/xopc-use-tool.d.ts +1 -0
  112. package/dist/src/agent/tools/xopc-use-tool.js +69 -9
  113. package/dist/src/automations/domain/validation.d.ts +40 -12
  114. package/dist/src/automations/draft/automation-draft-validator.d.ts +4 -4
  115. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  116. package/dist/src/config/rules.js +2 -2
  117. package/dist/src/gateway/hono/app.d.ts +2 -0
  118. package/dist/src/gateway/hono/app.js +10 -3
  119. package/dist/src/gateway/hono/middleware/strict-rate-limit.d.ts +4 -0
  120. package/dist/src/gateway/hono/middleware/strict-rate-limit.js +8 -1
  121. package/dist/src/gateway/hono/routes/agent-stream.js +2 -74
  122. package/dist/src/gateway/hono/routes/config-patch/misc.js +2 -1
  123. package/dist/src/gateway/hono/routes/config.js +9 -0
  124. package/dist/src/gateway/hono/routes/deps.d.ts +1 -0
  125. package/dist/src/gateway/hono/routes/projects.js +55 -0
  126. package/dist/src/gateway/hono/routes/session-input-handler.d.ts +62 -0
  127. package/dist/src/gateway/hono/routes/session-input-handler.js +82 -0
  128. package/dist/src/gateway/hono/routes/tasks.js +272 -20
  129. package/dist/src/gateway/host.d.ts +9 -1
  130. package/dist/src/gateway/host.js +22 -5
  131. package/dist/src/gateway/rate-limit/buckets.d.ts +3 -0
  132. package/dist/src/gateway/rate-limit/buckets.js +12 -0
  133. package/dist/src/gateway/runtime-config.js +6 -5
  134. package/dist/src/gateway/security/audit.js +0 -7
  135. package/dist/src/gateway/server.js +4 -1
  136. package/dist/src/gateway/service/task-conversation-query-service.d.ts +58 -0
  137. package/dist/src/gateway/service/task-conversation-query-service.js +84 -0
  138. package/dist/src/gateway/service.d.ts +11 -0
  139. package/dist/src/gateway/service.js +75 -38
  140. package/dist/src/proactive/actions/service.d.ts +2 -0
  141. package/dist/src/proactive/actions/service.js +80 -0
  142. package/dist/src/proactive/execution/insight.js +20 -0
  143. package/dist/src/proactive/execution/repository.js +8 -2
  144. package/dist/src/proactive/execution/types.d.ts +14 -0
  145. package/dist/src/proactive/inbox/repository.js +59 -9
  146. package/dist/src/proactive/inbox/service.d.ts +2 -2
  147. package/dist/src/proactive/inbox/service.js +7 -2
  148. package/dist/src/proactive/inbox/types.d.ts +14 -0
  149. package/dist/src/proactive/inbox/worker.js +2 -0
  150. package/dist/src/proactive/scenarios/prompt-composer.js +2 -2
  151. package/dist/src/projects/project-agent.js +1 -1
  152. package/dist/src/storage/sqlite/migrations/118_task_board_rank.sql +6 -0
  153. package/dist/src/storage/sqlite/migrations/119_proactive_disposition.sql +13 -0
  154. package/dist/src/storage/sqlite/migrations/120_task_conversation.sql +112 -0
  155. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  156. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  157. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  158. package/dist/src/storage/sqlite/schema.js +1 -1
  159. package/dist/src/tasks/execution-context.js +8 -4
  160. package/dist/src/tasks/home-query-service.d.ts +5 -0
  161. package/dist/src/tasks/home-query-service.js +7 -2
  162. package/dist/src/tasks/index.d.ts +3 -0
  163. package/dist/src/tasks/index.js +6 -3
  164. package/dist/src/tasks/project-monitoring-service.d.ts +9 -0
  165. package/dist/src/tasks/project-monitoring-service.js +41 -3
  166. package/dist/src/tasks/project-operating-view-service.d.ts +1 -2
  167. package/dist/src/tasks/project-operating-view-service.js +26 -23
  168. package/dist/src/tasks/task-application-service.js +107 -21
  169. package/dist/src/tasks/task-change-events.d.ts +13 -0
  170. package/dist/src/tasks/task-change-events.js +29 -0
  171. package/dist/src/tasks/task-context-assembler.js +9 -4
  172. package/dist/src/tasks/task-context-repository.d.ts +0 -6
  173. package/dist/src/tasks/task-context-repository.js +0 -4
  174. package/dist/src/tasks/task-conversation-repository.d.ts +71 -0
  175. package/dist/src/tasks/task-conversation-repository.js +194 -0
  176. package/dist/src/tasks/task-handoff-service.d.ts +31 -0
  177. package/dist/src/tasks/task-handoff-service.js +143 -0
  178. package/dist/src/tasks/task-read-model-projector.js +3 -6
  179. package/dist/src/tasks/task-repository.d.ts +7 -2
  180. package/dist/src/tasks/task-repository.js +31 -15
  181. package/dist/src/tasks/task-run-coordinator.js +3 -1
  182. package/dist/src/tunnel/enable-lan-pairing.js +15 -26
  183. package/dist/src/tunnel/pair-context.js +1 -1
  184. package/dist/src/tunnel/pair-url.js +1 -1
  185. package/dist/src/tunnel/tunnel-qr.d.ts +0 -7
  186. package/dist/src/tunnel/tunnel-qr.js +2 -19
  187. package/dist/src/workflows/service/workflow-run-service.js +4 -2
  188. package/package.json +5 -3
  189. package/dist/gateway/static/root/assets/agents-Baxfj44y.js +0 -68
  190. package/dist/gateway/static/root/assets/app-management-settings-panel-S8LhoD6G.js +0 -1
  191. package/dist/gateway/static/root/assets/appearance-settings-D8Cd5vDP.js +0 -1
  192. package/dist/gateway/static/root/assets/apps-page-0uCxKyE5.js +0 -1
  193. package/dist/gateway/static/root/assets/automations-page-XyDat125.js +0 -6
  194. package/dist/gateway/static/root/assets/browser-settings-page-CrYNMR4w.js +0 -8
  195. package/dist/gateway/static/root/assets/browser-workflow-inputs-Abil4puY.js +0 -1
  196. package/dist/gateway/static/root/assets/capabilities-settings-panel-CvMcDhGf.js +0 -7
  197. package/dist/gateway/static/root/assets/capability-presets-settings-panel-CHJDY-Ih.js +0 -3
  198. package/dist/gateway/static/root/assets/channels-settings-Ds9BJbcY.js +0 -1
  199. package/dist/gateway/static/root/assets/channels-status-swr-DPaJSYX_.js +0 -1
  200. package/dist/gateway/static/root/assets/connectors-api-CgWjgPSI.js +0 -1
  201. package/dist/gateway/static/root/assets/connectors-page-NUs-Zf7F.js +0 -2
  202. package/dist/gateway/static/root/assets/cron-expression-ZdDBAoqA.js +0 -2
  203. package/dist/gateway/static/root/assets/dependency-picker-BGeB1jpn.js +0 -1
  204. package/dist/gateway/static/root/assets/desktop-pet-settings-CCjT1bdo.js +0 -1
  205. package/dist/gateway/static/root/assets/directory-picker-path-field-C-Te1QN3.js +0 -1
  206. package/dist/gateway/static/root/assets/extension-settings-page-BLSNdlgd.js +0 -1
  207. package/dist/gateway/static/root/assets/gateway-config-api-CQrf0k3D.js +0 -1
  208. package/dist/gateway/static/root/assets/gateway-settings-CvgH8IJI.js +0 -1
  209. package/dist/gateway/static/root/assets/heartbeat-settings-SPpnqbjl.js +0 -1
  210. package/dist/gateway/static/root/assets/home-page-BFoD6047.js +0 -3
  211. package/dist/gateway/static/root/assets/index-84ys9u-K.js +0 -152
  212. package/dist/gateway/static/root/assets/index-D-srnucP.css +0 -1
  213. package/dist/gateway/static/root/assets/local-app-workbench-page-D5uL2_Fd.js +0 -2
  214. package/dist/gateway/static/root/assets/local-apps-page-DIWkFa3g.js +0 -1
  215. package/dist/gateway/static/root/assets/logs-page--6v8qq6y.js +0 -2
  216. package/dist/gateway/static/root/assets/management-settings-DtEuS-kR.js +0 -1
  217. package/dist/gateway/static/root/assets/messages-DQ5X6cXp.js +0 -3
  218. package/dist/gateway/static/root/assets/notes-workbench-IdjuNXvH.js +0 -4
  219. package/dist/gateway/static/root/assets/page-tabs-7bSEWfSX.js +0 -1
  220. package/dist/gateway/static/root/assets/preview-open-alternatives-tgLbHgcV.js +0 -1
  221. package/dist/gateway/static/root/assets/project-detail-page-DnJXSuM4.js +0 -1
  222. package/dist/gateway/static/root/assets/projects-page-DckZKlBI.js +0 -1
  223. package/dist/gateway/static/root/assets/remote-access-hub-CfbUHBUH.js +0 -1
  224. package/dist/gateway/static/root/assets/schema-form-CFfyC4CB.js +0 -1
  225. package/dist/gateway/static/root/assets/sessions-page-BCfCh4My.js +0 -1
  226. package/dist/gateway/static/root/assets/settings-advanced-gate-Cbi4FvzO.js +0 -1
  227. package/dist/gateway/static/root/assets/settings-form-section-gEnEYT_3.js +0 -1
  228. package/dist/gateway/static/root/assets/settings-page-layout-CRpXF_05.js +0 -1
  229. package/dist/gateway/static/root/assets/setup-status-panel-BcHdpmFf.js +0 -1
  230. package/dist/gateway/static/root/assets/share-preview-page-DU8L68A5.js +0 -2
  231. package/dist/gateway/static/root/assets/shares-settings-kjTqH9yY.js +0 -1
  232. package/dist/gateway/static/root/assets/skill-reload-api-QAGpwzIM.js +0 -1
  233. package/dist/gateway/static/root/assets/skills-page-lE1-YRdK.js +0 -2
  234. package/dist/gateway/static/root/assets/system-settings-panel-CVDsiPKn.js +0 -1
  235. package/dist/gateway/static/root/assets/task-detail-page-DsM13-cy.js +0 -1
  236. package/dist/gateway/static/root/assets/typed-models-lib-DelyYgoc.js +0 -1
  237. package/dist/gateway/static/root/assets/use-autosave-C50Mq5yQ.js +0 -1
  238. package/dist/gateway/static/root/assets/user-context-page-D-VEOt2A.js +0 -1
  239. package/dist/gateway/static/root/assets/voice-api-key-field-DYfgTe3Y.js +0 -1
  240. package/dist/gateway/static/root/assets/work-discovery-overlay-DPbujsp3.js +0 -1
  241. package/dist/gateway/static/root/assets/work-discovery-page-D77opady.js +0 -1
  242. package/dist/gateway/static/root/assets/workflows-page-B9-PLmlX.js +0 -5
  243. package/dist/src/gateway/ensure-network-cors.d.ts +0 -3
  244. package/dist/src/gateway/ensure-network-cors.js +0 -23
@@ -3,6 +3,7 @@ import { TaskRepository } from "./task-repository.js";
3
3
  import { TaskRunRepository } from "./task-run-repository.js";
4
4
  import { TaskReadModelProjector } from "./task-read-model-projector.js";
5
5
  import { TaskContextRepository } from "./task-context-repository.js";
6
+ import { enqueueTaskChangedEvent } from "./task-change-events.js";
6
7
  import { TaskDependencyService } from "./task-dependency-service.js";
7
8
  import { createHash, randomUUID } from "node:crypto";
8
9
  //#region src/tasks/task-application-service.ts
@@ -24,6 +25,27 @@ function executorRef(executor) {
24
25
  };
25
26
  }
26
27
  }
28
+ function commandChangedFields(command) {
29
+ switch (command.type) {
30
+ case "move":
31
+ case "mark_ready":
32
+ case "request_review":
33
+ case "close":
34
+ case "reopen": return [
35
+ "phase",
36
+ "resolution",
37
+ "attention"
38
+ ];
39
+ case "start": return [
40
+ "phase",
41
+ "runs",
42
+ "attention"
43
+ ];
44
+ case "add_wait":
45
+ case "resolve_wait": return ["runs", "attention"];
46
+ case "revise_contract": return ["contract"];
47
+ }
48
+ }
27
49
  var TaskApplicationService = class {
28
50
  #tasks = new TaskRepository();
29
51
  #runs = new TaskRunRepository();
@@ -58,6 +80,10 @@ var TaskApplicationService = class {
58
80
  ...input.contract,
59
81
  createdBy: actor
60
82
  });
83
+ db.prepare(`INSERT INTO task_conversation_state (
84
+ task_id, active_session_key, current_executor_agent_id,
85
+ assignment_epoch, status, updated_at
86
+ ) VALUES (?, NULL, ?, 0, 'idle', ?)`).run(task.id, task.delegateAgentId ?? task.ownerId ?? null, task.createdAt);
61
87
  let current = task;
62
88
  if (input.dependencies.length > 0) current = this.#dependencies.replace({
63
89
  taskId: task.id,
@@ -86,16 +112,48 @@ var TaskApplicationService = class {
86
112
  phase: current.phase,
87
113
  projectId: current.projectId
88
114
  });
115
+ enqueueTaskChangedEvent(db, {
116
+ taskId: current.id,
117
+ projectId: current.projectId,
118
+ version: current.version,
119
+ changedFields: [
120
+ "title",
121
+ "body",
122
+ "phase",
123
+ "priority",
124
+ "contract",
125
+ "dependencies",
126
+ "context"
127
+ ],
128
+ actor
129
+ });
89
130
  if (input.activation.mode === "capture") return {
90
131
  ok: true,
91
132
  model: this.#projector.project(current)
92
133
  };
93
- return this.start(current.id, current.version, input.activation.executor, {
134
+ const executor = input.activation.executor ?? (current.delegateAgentId ? {
135
+ kind: "agent",
136
+ agentId: current.delegateAgentId
137
+ } : void 0);
138
+ if (!executor) throw new Error("Task start executor was not resolved");
139
+ const started = this.start(current.id, current.version, executor, {
94
140
  idempotencyKey: `${input.idempotencyKey}:start`,
95
141
  scheduleAt: input.activation.scheduleAt,
96
142
  actor,
97
143
  correlationId: input.idempotencyKey
98
144
  });
145
+ if (started.ok) enqueueTaskChangedEvent(db, {
146
+ taskId: started.model.task.id,
147
+ projectId: started.model.task.projectId,
148
+ version: started.model.task.version,
149
+ changedFields: [
150
+ "phase",
151
+ "runs",
152
+ "attention"
153
+ ],
154
+ actor
155
+ });
156
+ return started;
99
157
  });
100
158
  }
101
159
  execute(input) {
@@ -118,6 +176,13 @@ var TaskApplicationService = class {
118
176
  return runSqliteWriteTransaction((db) => {
119
177
  let result;
120
178
  switch (input.command.type) {
179
+ case "move":
180
+ result = task.phase !== "closed" && task.phase !== input.command.phase && !this.#runs.getActiveRoot(task.id) && this.#runs.listActiveWaits(task.id).length === 0 ? this.lifecycle(task.id, task.version, input.command.phase) : {
181
+ ok: false,
182
+ reason: "invalid_transition",
183
+ model
184
+ };
185
+ break;
121
186
  case "mark_ready":
122
187
  result = task.phase === "backlog" ? this.lifecycle(task.id, task.version, "ready") : {
123
188
  ok: false,
@@ -191,21 +256,6 @@ var TaskApplicationService = class {
191
256
  }
192
257
  break;
193
258
  }
194
- case "delegate": {
195
- const updated = this.#tasks.update(task.id, {
196
- expectedVersion: task.version,
197
- delegateAgentId: input.command.agentId ?? null
198
- });
199
- result = updated ? {
200
- ok: true,
201
- model: this.#projector.project(updated)
202
- } : {
203
- ok: false,
204
- reason: "conflict",
205
- model: this.#projector.get(task.id)
206
- };
207
- break;
208
- }
209
259
  case "revise_contract": {
210
260
  const updated = this.#tasks.reviseContract({
211
261
  taskId: task.id,
@@ -257,11 +307,18 @@ var TaskApplicationService = class {
257
307
  resolution: result.model.task.resolution,
258
308
  projectId: result.model.task.projectId
259
309
  });
310
+ enqueueTaskChangedEvent(db, {
311
+ taskId: result.model.task.id,
312
+ projectId: result.model.task.projectId,
313
+ version: result.model.task.version,
314
+ changedFields: commandChangedFields(input.command),
315
+ actor
316
+ });
260
317
  return result;
261
318
  });
262
319
  }
263
320
  completeRun(input) {
264
- return runSqliteWriteTransaction(() => {
321
+ return runSqliteWriteTransaction((db) => {
265
322
  const run = this.#runs.get(input.runId);
266
323
  if (!run) return {
267
324
  ok: false,
@@ -285,10 +342,25 @@ var TaskApplicationService = class {
285
342
  model: this.#projector.get(run.taskId)
286
343
  };
287
344
  const task = this.#tasks.require(run.taskId);
288
- if (input.receipt.status !== "succeeded") return {
289
- ok: true,
290
- model: this.#projector.project(task)
291
- };
345
+ if (input.receipt.status !== "succeeded") {
346
+ const model = this.#projector.project(task);
347
+ enqueueTaskChangedEvent(db, {
348
+ taskId: task.id,
349
+ projectId: task.projectId,
350
+ version: task.version,
351
+ changedFields: [
352
+ "runs",
353
+ "receipts",
354
+ "attention"
355
+ ],
356
+ actor: input.actor,
357
+ source: input.actor ? void 0 : "runtime"
358
+ });
359
+ return {
360
+ ok: true,
361
+ model
362
+ };
363
+ }
292
364
  const verified = input.receipt.verification.status === "passed";
293
365
  const phase = task.contract?.acceptancePolicy === "verified_auto" && verified ? "closed" : "review";
294
366
  const updated = this.#tasks.setLifecycle({
@@ -298,6 +370,20 @@ var TaskApplicationService = class {
298
370
  ...phase === "closed" ? { resolution: "done" } : {}
299
371
  });
300
372
  if (!updated) throw new Error("Task changed while its run was being finalized");
373
+ enqueueTaskChangedEvent(db, {
374
+ taskId: updated.id,
375
+ projectId: updated.projectId,
376
+ version: updated.version,
377
+ changedFields: [
378
+ "phase",
379
+ "resolution",
380
+ "runs",
381
+ "receipts",
382
+ "attention"
383
+ ],
384
+ actor: input.actor,
385
+ source: input.actor ? void 0 : "runtime"
386
+ });
301
387
  return {
302
388
  ok: true,
303
389
  model: this.#projector.project(updated)
@@ -0,0 +1,13 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ import type { ActorRef, TaskChangedField } from '@xopcai/gateway-contract';
3
+ export type TaskChangeInput = {
4
+ taskId: string;
5
+ projectId?: string;
6
+ version: number;
7
+ changedFields: TaskChangedField[];
8
+ actor?: ActorRef;
9
+ source?: 'user' | 'agent' | 'runtime';
10
+ occurredAt?: number;
11
+ };
12
+ export declare function enqueueTaskChangedEvent(db: DatabaseSync, input: TaskChangeInput): void;
13
+ export declare function enqueueTaskChanged(input: TaskChangeInput): void;
@@ -0,0 +1,29 @@
1
+ import { runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
2
+ import { randomUUID } from "node:crypto";
3
+ //#region src/tasks/task-change-events.ts
4
+ function eventSource(input) {
5
+ if (input.source) return input.source;
6
+ return input.actor?.kind === "user" || input.actor?.kind === "agent" ? input.actor.kind : "runtime";
7
+ }
8
+ function enqueueTaskChangedEvent(db, input) {
9
+ const occurredAt = input.occurredAt ?? Date.now();
10
+ const changedFields = [...new Set(input.changedFields)];
11
+ if (changedFields.length === 0) return;
12
+ db.prepare(`INSERT INTO domain_outbox (
13
+ event_id, event_type, subject_kind, subject_id, correlation_id,
14
+ payload_json, created_at
15
+ ) VALUES (?, 'task.changed.v2', 'task', ?, ?, ?, ?)`).run(randomUUID(), input.taskId, randomUUID(), JSON.stringify({
16
+ taskId: input.taskId,
17
+ ...input.projectId ? { projectId: input.projectId } : {},
18
+ version: input.version,
19
+ changedFields,
20
+ source: eventSource(input),
21
+ ...input.actor?.id ? { actorId: input.actor.id } : {},
22
+ occurredAt
23
+ }), occurredAt);
24
+ }
25
+ function enqueueTaskChanged(input) {
26
+ runSqliteWriteTransaction((db) => enqueueTaskChangedEvent(db, input));
27
+ }
28
+ //#endregion
29
+ export { enqueueTaskChanged, enqueueTaskChangedEvent };
@@ -1,7 +1,7 @@
1
1
  import { isXopcDatabaseOpen } from "../storage/sqlite/connection.js";
2
- import { getSessionMetadata } from "../storage/sqlite/session-repository.js";
3
2
  import "../storage/sqlite/index.js";
4
3
  import { TaskRepository } from "./task-repository.js";
4
+ import { TaskConversationRepository } from "./task-conversation-repository.js";
5
5
  import { TaskRunRepository } from "./task-run-repository.js";
6
6
  import { TaskContextRepository } from "./task-context-repository.js";
7
7
  //#region src/tasks/task-context-assembler.ts
@@ -12,8 +12,7 @@ const STANDARD = {
12
12
  reason: "No active task requires expanded context."
13
13
  };
14
14
  function taskIdForSession(sessionKey) {
15
- const value = getSessionMetadata(sessionKey)?.customData?.taskId;
16
- return typeof value === "string" && value.trim() ? value.trim() : void 0;
15
+ return new TaskConversationRepository().resolveActiveExecutionSession(sessionKey)?.taskId;
17
16
  }
18
17
  function getTaskExecutionBrief(taskId) {
19
18
  if (!isXopcDatabaseOpen()) return void 0;
@@ -87,6 +86,8 @@ function assembleTaskContext(sessionKey, userQuery) {
87
86
  allocation: STANDARD
88
87
  };
89
88
  const { task, latestReceipt, remainingCriteria, allocation, manifest } = brief;
89
+ const handoff = new TaskConversationRepository().getLatestHandoff(taskId);
90
+ const handoffPayload = handoff?.toSessionKey === sessionKey ? JSON.stringify(handoff.payload).slice(0, 12e3) : "";
90
91
  const contract = task.contract;
91
92
  return {
92
93
  taskId,
@@ -97,7 +98,8 @@ function assembleTaskContext(sessionKey, userQuery) {
97
98
  remainingCriteria.length ? `Remaining acceptance criteria: ${remainingCriteria.join("; ")}` : "",
98
99
  contract.constraints.length ? `Constraints: ${contract.constraints.join("; ")}` : "",
99
100
  contract.risks.length ? `Risks: ${contract.risks.join("; ")}` : "",
100
- latestReceipt?.summary ? `Latest run: ${latestReceipt.summary}` : ""
101
+ latestReceipt?.summary ? `Latest run: ${latestReceipt.summary}` : "",
102
+ handoffPayload ? `Handoff snapshot: ${handoffPayload}` : ""
101
103
  ].filter(Boolean).join("\n"),
102
104
  allocation,
103
105
  manifest
@@ -108,6 +110,8 @@ function buildTaskExecutionDirective(sessionKey) {
108
110
  const brief = taskId ? getTaskExecutionBrief(taskId) : void 0;
109
111
  if (!brief) return "";
110
112
  const { task, remainingCriteria } = brief;
113
+ const handoff = new TaskConversationRepository().getLatestHandoff(task.id);
114
+ const handoffPayload = handoff?.toSessionKey === sessionKey ? JSON.stringify(handoff.payload).slice(0, 12e3) : "";
111
115
  const contract = task.contract;
112
116
  return [
113
117
  "<xopc_task_execution>",
@@ -117,6 +121,7 @@ function buildTaskExecutionDirective(sessionKey) {
117
121
  `Remaining acceptance criteria: ${remainingCriteria.join("; ")}`,
118
122
  contract.constraints.length ? `Constraints: ${contract.constraints.join("; ")}` : "",
119
123
  contract.approvalRequired.length ? `Authority required: ${contract.approvalRequired.join("; ")}` : "",
124
+ handoffPayload ? `Handoff snapshot: ${handoffPayload}` : "",
120
125
  "Take safe in-scope steps and produce inspectable evidence. Do not claim completion without verification.",
121
126
  "</xopc_task_execution>"
122
127
  ].filter(Boolean).join("\n");
@@ -42,11 +42,5 @@ export declare class TaskContextRepository {
42
42
  }): TaskAuthorityGrant;
43
43
  listActiveGrants(taskId: string, now?: number): TaskAuthorityGrant[];
44
44
  revoke(grantId: string, now?: number): boolean;
45
- linkSession(input: {
46
- taskId: string;
47
- sessionKey: string;
48
- role: 'primary' | 'discussion' | 'execution';
49
- now?: number;
50
- }): void;
51
45
  private requireGrant;
52
46
  }
@@ -82,10 +82,6 @@ var TaskContextRepository = class {
82
82
  revoke(grantId, now = Date.now()) {
83
83
  return getSqliteDatabase().prepare(`UPDATE task_authority_grants SET revoked_at = ?
84
84
  WHERE grant_id = ? AND revoked_at IS NULL`).run(now, grantId).changes > 0;
85
- }
86
- linkSession(input) {
87
- getSqliteDatabase().prepare(`INSERT OR IGNORE INTO task_sessions (task_session_id, task_id, session_key, role, created_at)
88
- VALUES (?, ?, ?, ?, ?)`).run(randomUUID(), input.taskId, input.sessionKey, input.role, input.now ?? Date.now());
89
85
  }
90
86
  requireGrant(id) {
91
87
  const row = getSqliteDatabase().prepare("SELECT * FROM task_authority_grants WHERE grant_id = ?").get(id);
@@ -0,0 +1,71 @@
1
+ export type TaskConversationStatus = 'idle' | 'active';
2
+ export type TaskSessionRole = 'primary' | 'discussion' | 'execution';
3
+ export type TaskSessionStatus = 'active' | 'completed' | 'superseded' | 'failed';
4
+ export interface TaskConversationState {
5
+ taskId: string;
6
+ activeSessionKey?: string;
7
+ currentExecutorAgentId?: string;
8
+ assignmentEpoch: number;
9
+ status: TaskConversationStatus;
10
+ updatedAt: number;
11
+ }
12
+ export interface TaskSessionLink {
13
+ id: string;
14
+ taskId: string;
15
+ sessionKey: string;
16
+ role: TaskSessionRole;
17
+ agentId?: string;
18
+ runId?: string;
19
+ assignmentEpoch: number;
20
+ status: TaskSessionStatus;
21
+ startedAt: number;
22
+ endedAt?: number;
23
+ createdAt: number;
24
+ }
25
+ export interface TaskHandoffSnapshot {
26
+ id: string;
27
+ taskId: string;
28
+ fromSessionKey?: string;
29
+ toSessionKey: string;
30
+ fromAgentId?: string;
31
+ toAgentId: string;
32
+ assignmentEpoch: number;
33
+ payload: Record<string, unknown>;
34
+ createdAt: number;
35
+ }
36
+ export declare class TaskConversationConflictError extends Error {
37
+ }
38
+ export declare class TaskConversationRepository {
39
+ findHandoffByIdempotencyKey(input: {
40
+ taskId: string;
41
+ expectedTaskVersion: number;
42
+ toAgentId: string;
43
+ idempotencyKey: string;
44
+ }): TaskHandoffSnapshot | undefined;
45
+ getState(taskId: string): TaskConversationState | undefined;
46
+ requireState(taskId: string): TaskConversationState;
47
+ listSessions(taskId: string): TaskSessionLink[];
48
+ getActiveSession(taskId: string): TaskSessionLink | undefined;
49
+ resolveActiveExecutionSession(sessionKey: string): TaskSessionLink | undefined;
50
+ activateExecutionSession(input: {
51
+ taskId: string;
52
+ sessionKey: string;
53
+ agentId: string;
54
+ runId?: string;
55
+ now?: number;
56
+ }): TaskConversationState;
57
+ completeHandoff(input: {
58
+ taskId: string;
59
+ expectedTaskVersion: number;
60
+ toSessionKey: string;
61
+ toAgentId: string;
62
+ idempotencyKey: string;
63
+ payload: Record<string, unknown>;
64
+ now?: number;
65
+ }): {
66
+ state: TaskConversationState;
67
+ snapshot: TaskHandoffSnapshot;
68
+ taskVersion: number;
69
+ };
70
+ getLatestHandoff(taskId: string): TaskHandoffSnapshot | undefined;
71
+ }
@@ -0,0 +1,194 @@
1
+ import { getSqliteDatabase, runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
2
+ import { createHash, randomUUID } from "node:crypto";
3
+ //#region src/tasks/task-conversation-repository.ts
4
+ var TaskConversationConflictError = class extends Error {};
5
+ function stateFromRow(row) {
6
+ return {
7
+ taskId: row.task_id,
8
+ ...row.active_session_key ? { activeSessionKey: row.active_session_key } : {},
9
+ ...row.current_executor_agent_id ? { currentExecutorAgentId: row.current_executor_agent_id } : {},
10
+ assignmentEpoch: row.assignment_epoch,
11
+ status: row.status,
12
+ updatedAt: row.updated_at
13
+ };
14
+ }
15
+ function linkFromRow(row) {
16
+ if (!row.session_key) return void 0;
17
+ return {
18
+ id: row.task_session_id,
19
+ taskId: row.task_id,
20
+ sessionKey: row.session_key,
21
+ role: row.role,
22
+ ...row.agent_id ? { agentId: row.agent_id } : {},
23
+ ...row.run_id ? { runId: row.run_id } : {},
24
+ assignmentEpoch: row.assignment_epoch,
25
+ status: row.status,
26
+ startedAt: row.started_at ?? row.created_at,
27
+ ...row.ended_at === null ? {} : { endedAt: row.ended_at },
28
+ createdAt: row.created_at
29
+ };
30
+ }
31
+ function snapshotFromRow(row) {
32
+ return {
33
+ id: row.snapshot_id,
34
+ taskId: row.task_id,
35
+ ...row.from_session_key ? { fromSessionKey: row.from_session_key } : {},
36
+ toSessionKey: row.to_session_key,
37
+ ...row.from_agent_id ? { fromAgentId: row.from_agent_id } : {},
38
+ toAgentId: row.to_agent_id,
39
+ assignmentEpoch: row.assignment_epoch,
40
+ payload: JSON.parse(row.payload_json),
41
+ createdAt: row.created_at
42
+ };
43
+ }
44
+ function handoffRequestHash(input) {
45
+ return createHash("sha256").update(JSON.stringify(input)).digest("hex");
46
+ }
47
+ var TaskConversationRepository = class {
48
+ findHandoffByIdempotencyKey(input) {
49
+ const row = getSqliteDatabase().prepare(`SELECT request_hash, result_json FROM command_deduplication
50
+ WHERE idempotency_key = ?`).get(input.idempotencyKey);
51
+ if (!row) return void 0;
52
+ const requestHash = handoffRequestHash(input);
53
+ if (row.request_hash !== requestHash) throw new TaskConversationConflictError("Idempotency key was reused with different input");
54
+ const result = JSON.parse(row.result_json);
55
+ const snapshot = getSqliteDatabase().prepare("SELECT * FROM task_handoff_snapshots WHERE snapshot_id = ?").get(result.snapshotId);
56
+ if (!snapshot || snapshot.task_id !== input.taskId) throw new Error(`Task handoff snapshot not found: ${result.snapshotId}`);
57
+ return snapshotFromRow(snapshot);
58
+ }
59
+ getState(taskId) {
60
+ const row = getSqliteDatabase().prepare("SELECT * FROM task_conversation_state WHERE task_id = ?").get(taskId);
61
+ return row ? stateFromRow(row) : void 0;
62
+ }
63
+ requireState(taskId) {
64
+ const state = this.getState(taskId);
65
+ if (!state) throw new Error(`Task conversation state not found: ${taskId}`);
66
+ return state;
67
+ }
68
+ listSessions(taskId) {
69
+ return getSqliteDatabase().prepare(`SELECT * FROM task_sessions WHERE task_id = ? AND session_key IS NOT NULL
70
+ ORDER BY assignment_epoch DESC, created_at DESC, task_session_id DESC`).all(taskId).flatMap((row) => {
71
+ const link = linkFromRow(row);
72
+ return link ? [link] : [];
73
+ });
74
+ }
75
+ getActiveSession(taskId) {
76
+ const row = getSqliteDatabase().prepare(`SELECT * FROM task_sessions
77
+ WHERE task_id = ? AND role = 'execution' AND status = 'active'
78
+ LIMIT 1`).get(taskId);
79
+ return row ? linkFromRow(row) : void 0;
80
+ }
81
+ resolveActiveExecutionSession(sessionKey) {
82
+ const row = getSqliteDatabase().prepare(`SELECT * FROM task_sessions
83
+ WHERE session_key = ? AND role = 'execution' AND status = 'active'
84
+ LIMIT 1`).get(sessionKey);
85
+ return row ? linkFromRow(row) : void 0;
86
+ }
87
+ activateExecutionSession(input) {
88
+ const now = input.now ?? Date.now();
89
+ return runSqliteWriteTransaction((db) => {
90
+ const existing = db.prepare("SELECT * FROM task_conversation_state WHERE task_id = ?").get(input.taskId);
91
+ const current = db.prepare(`SELECT * FROM task_sessions
92
+ WHERE task_id = ? AND role = 'execution' AND status = 'active'`).get(input.taskId);
93
+ const sameSession = current?.session_key === input.sessionKey;
94
+ const assignmentEpoch = sameSession ? current.assignment_epoch : (existing?.assignment_epoch ?? 0) + 1;
95
+ if (!sameSession) db.prepare(`UPDATE task_sessions SET status = 'superseded', ended_at = ?
96
+ WHERE task_id = ? AND role = 'execution' AND status = 'active'`).run(now, input.taskId);
97
+ db.prepare(`INSERT INTO task_sessions (
98
+ task_session_id, task_id, session_key, role, created_at,
99
+ agent_id, run_id, assignment_epoch, status, started_at, ended_at
100
+ ) VALUES (?, ?, ?, 'execution', ?, ?, ?, ?, 'active', ?, NULL)
101
+ ON CONFLICT(task_id, session_key, role) WHERE session_key IS NOT NULL DO UPDATE SET
102
+ agent_id = excluded.agent_id,
103
+ run_id = COALESCE(excluded.run_id, task_sessions.run_id),
104
+ assignment_epoch = excluded.assignment_epoch,
105
+ status = 'active',
106
+ started_at = excluded.started_at,
107
+ ended_at = NULL`).run(randomUUID(), input.taskId, input.sessionKey, now, input.agentId, input.runId ?? null, assignmentEpoch, sameSession ? current.started_at ?? now : now);
108
+ db.prepare(`INSERT INTO task_conversation_state (
109
+ task_id, active_session_key, current_executor_agent_id,
110
+ assignment_epoch, status, updated_at
111
+ ) VALUES (?, ?, ?, ?, 'active', ?)
112
+ ON CONFLICT(task_id) DO UPDATE SET
113
+ active_session_key = excluded.active_session_key,
114
+ current_executor_agent_id = excluded.current_executor_agent_id,
115
+ assignment_epoch = excluded.assignment_epoch,
116
+ status = 'active',
117
+ updated_at = excluded.updated_at`).run(input.taskId, input.sessionKey, input.agentId, assignmentEpoch, now);
118
+ return stateFromRow(db.prepare("SELECT * FROM task_conversation_state WHERE task_id = ?").get(input.taskId));
119
+ });
120
+ }
121
+ completeHandoff(input) {
122
+ const now = input.now ?? Date.now();
123
+ return runSqliteWriteTransaction((db) => {
124
+ const requestHash = handoffRequestHash({
125
+ taskId: input.taskId,
126
+ expectedTaskVersion: input.expectedTaskVersion,
127
+ toAgentId: input.toAgentId
128
+ });
129
+ const duplicate = db.prepare(`SELECT request_hash, result_json FROM command_deduplication
130
+ WHERE idempotency_key = ?`).get(input.idempotencyKey);
131
+ if (duplicate) {
132
+ if (duplicate.request_hash !== requestHash) throw new TaskConversationConflictError("Idempotency key was reused with different input");
133
+ const result = JSON.parse(duplicate.result_json);
134
+ const snapshotRow = db.prepare("SELECT * FROM task_handoff_snapshots WHERE snapshot_id = ?").get(result.snapshotId);
135
+ const taskRow = db.prepare("SELECT version FROM tasks WHERE task_id = ?").get(input.taskId);
136
+ return {
137
+ state: stateFromRow(db.prepare("SELECT * FROM task_conversation_state WHERE task_id = ?").get(input.taskId)),
138
+ snapshot: snapshotFromRow(snapshotRow),
139
+ taskVersion: taskRow.version
140
+ };
141
+ }
142
+ const existing = db.prepare("SELECT * FROM task_conversation_state WHERE task_id = ?").get(input.taskId);
143
+ if (!existing) throw new Error(`Task conversation state not found: ${input.taskId}`);
144
+ const current = db.prepare(`SELECT * FROM task_sessions
145
+ WHERE task_id = ? AND role = 'execution' AND status = 'active'`).get(input.taskId);
146
+ if (current?.session_key === input.toSessionKey && current.agent_id === input.toAgentId) throw new TaskConversationConflictError("Executor is already active");
147
+ const assignmentEpoch = existing.assignment_epoch + 1;
148
+ if (db.prepare(`UPDATE tasks SET delegate_agent_id = ?, version = version + 1, updated_at = ?
149
+ WHERE task_id = ? AND version = ?`).run(input.toAgentId, now, input.taskId, input.expectedTaskVersion).changes === 0) throw new TaskConversationConflictError("Task changed");
150
+ db.prepare(`UPDATE task_sessions SET status = 'superseded', ended_at = ?
151
+ WHERE task_id = ? AND role = 'execution' AND status = 'active'`).run(now, input.taskId);
152
+ db.prepare(`INSERT INTO task_sessions (
153
+ task_session_id, task_id, session_key, role, created_at,
154
+ agent_id, assignment_epoch, status, started_at, ended_at
155
+ ) VALUES (?, ?, ?, 'execution', ?, ?, ?, 'active', ?, NULL)`).run(randomUUID(), input.taskId, input.toSessionKey, now, input.toAgentId, assignmentEpoch, now);
156
+ const snapshotId = randomUUID();
157
+ db.prepare(`INSERT INTO task_handoff_snapshots (
158
+ snapshot_id, task_id, from_session_key, to_session_key,
159
+ from_agent_id, to_agent_id, assignment_epoch, payload_json, created_at
160
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(snapshotId, input.taskId, current?.session_key ?? null, input.toSessionKey, current?.agent_id ?? existing.current_executor_agent_id ?? null, input.toAgentId, assignmentEpoch, JSON.stringify(input.payload), now);
161
+ db.prepare(`UPDATE task_conversation_state SET
162
+ active_session_key = ?, current_executor_agent_id = ?,
163
+ assignment_epoch = ?, status = 'active', updated_at = ?
164
+ WHERE task_id = ?`).run(input.toSessionKey, input.toAgentId, assignmentEpoch, now, input.taskId);
165
+ db.prepare(`INSERT INTO command_deduplication (
166
+ idempotency_key, command_type, subject_kind, subject_id,
167
+ request_hash, result_json, created_at
168
+ ) VALUES (?, 'task.handoff', 'task', ?, ?, ?, ?)`).run(input.idempotencyKey, input.taskId, requestHash, JSON.stringify({ snapshotId }), now);
169
+ return {
170
+ state: stateFromRow(db.prepare("SELECT * FROM task_conversation_state WHERE task_id = ?").get(input.taskId)),
171
+ taskVersion: input.expectedTaskVersion + 1,
172
+ snapshot: {
173
+ id: snapshotId,
174
+ taskId: input.taskId,
175
+ ...current?.session_key ? { fromSessionKey: current.session_key } : {},
176
+ toSessionKey: input.toSessionKey,
177
+ ...current?.agent_id || existing.current_executor_agent_id ? { fromAgentId: current?.agent_id ?? existing.current_executor_agent_id } : {},
178
+ toAgentId: input.toAgentId,
179
+ assignmentEpoch,
180
+ payload: input.payload,
181
+ createdAt: now
182
+ }
183
+ };
184
+ });
185
+ }
186
+ getLatestHandoff(taskId) {
187
+ const row = getSqliteDatabase().prepare(`SELECT * FROM task_handoff_snapshots
188
+ WHERE task_id = ? ORDER BY assignment_epoch DESC LIMIT 1`).get(taskId);
189
+ if (!row) return void 0;
190
+ return snapshotFromRow(row);
191
+ }
192
+ };
193
+ //#endregion
194
+ export { TaskConversationConflictError, TaskConversationRepository };
@@ -0,0 +1,31 @@
1
+ import type { Config } from '../config/schema.js';
2
+ import type { SessionIndex } from '../session/index.js';
3
+ import { type TaskConversationState, type TaskHandoffSnapshot } from './task-conversation-repository.js';
4
+ import { type TaskAggregate } from './task-repository.js';
5
+ export interface TaskHandoffResult {
6
+ task: TaskAggregate;
7
+ conversation: TaskConversationState;
8
+ snapshot: TaskHandoffSnapshot;
9
+ fromAgentId?: string;
10
+ toAgentId: string;
11
+ activeSessionKey: string;
12
+ assignmentEpoch: number;
13
+ }
14
+ export declare class TaskHandoffService {
15
+ #private;
16
+ private readonly deps;
17
+ constructor(deps: {
18
+ getConfig: () => Config;
19
+ sessionIndex: SessionIndex;
20
+ getActiveRunId: (sessionKey: string) => string | undefined;
21
+ abortRun: (runId: string) => Promise<unknown>;
22
+ });
23
+ handoff(input: {
24
+ taskId: string;
25
+ toAgentId: string;
26
+ expectedVersion: number;
27
+ idempotencyKey: string;
28
+ }): Promise<TaskHandoffResult>;
29
+ private performHandoff;
30
+ private abortPreviousSession;
31
+ }