@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
@@ -1,18 +1,26 @@
1
+ import { runSqliteWriteTransaction } from "../../../storage/sqlite/transaction.js";
1
2
  import { TaskRepository } from "../../../tasks/task-repository.js";
3
+ import { TaskConversationRepository } from "../../../tasks/task-conversation-repository.js";
2
4
  import { TaskRunRepository } from "../../../tasks/task-run-repository.js";
3
5
  import { TaskReadModelProjector } from "../../../tasks/task-read-model-projector.js";
4
6
  import { TaskPhaseSchema } from "../../../../packages/gateway-contract/src/task-lifecycle.js";
5
- import { TaskCommandRequestSchema, TaskContextInputSchema, TaskCreateRequestSchema, TaskDependencyUpdateRequestSchema, TaskPatchRequestSchema } from "../../../../packages/gateway-contract/src/tasks.js";
7
+ import { TaskBoardPositionRequestSchema, TaskCommandRequestSchema, TaskContextInputSchema, TaskCreateRequestSchema, TaskDependencyUpdateRequestSchema, TaskHandoffRequestSchema, TaskPatchRequestSchema } from "../../../../packages/gateway-contract/src/tasks.js";
6
8
  import { ProjectMonitoringUpdateSchema } from "../../../../packages/gateway-contract/src/project-monitoring.js";
9
+ import { resolveProjectAgentId } from "../../../projects/project-agent.js";
7
10
  import { TaskContextRepository } from "../../../tasks/task-context-repository.js";
11
+ import { enqueueTaskChangedEvent } from "../../../tasks/task-change-events.js";
8
12
  import { TaskDependencyError, TaskDependencyService } from "../../../tasks/task-dependency-service.js";
9
13
  import { TaskApplicationService } from "../../../tasks/task-application-service.js";
10
14
  import { ProjectMonitoringService } from "../../../tasks/project-monitoring-service.js";
11
15
  import { ProjectOperatingViewService } from "../../../tasks/project-operating-view-service.js";
12
16
  import { TaskValueMetricsService } from "../../../tasks/task-value-metrics-service.js";
17
+ import { TaskHandoffService } from "../../../tasks/task-handoff-service.js";
13
18
  import { TaskSignalService } from "../../../tasks/task-signal-service.js";
19
+ import { submitSessionInput } from "./session-input-handler.js";
20
+ import { TaskConversationQueryService } from "../../service/task-conversation-query-service.js";
14
21
  //#region src/gateway/hono/routes/tasks.ts
15
22
  function registerTaskRoutes(authenticated, deps) {
23
+ const taskRateLimit = deps.taskRateLimitMiddleware ?? deps.strictRateLimitMiddleware;
16
24
  const application = new TaskApplicationService();
17
25
  const operatingViews = new ProjectOperatingViewService(deps.service.projects);
18
26
  const monitoring = new ProjectMonitoringService();
@@ -20,9 +28,17 @@ function registerTaskRoutes(authenticated, deps) {
20
28
  const tasks = new TaskRepository();
21
29
  const runs = new TaskRunRepository();
22
30
  const context = new TaskContextRepository();
31
+ const conversations = new TaskConversationRepository();
32
+ const conversationQuery = new TaskConversationQueryService(deps.service.sessions);
23
33
  const projector = new TaskReadModelProjector();
24
34
  const signals = new TaskSignalService(() => deps.service.dispatchTaskRuns());
25
35
  const dependencies = new TaskDependencyService();
36
+ const handoffs = new TaskHandoffService({
37
+ getConfig: () => deps.service.currentConfig,
38
+ sessionIndex: deps.service.sessionIndexInstance,
39
+ getActiveRunId: (sessionKey) => deps.service.getActiveWebchatRunId(sessionKey),
40
+ abortRun: (runId) => deps.service.abortAgentRun(runId)
41
+ });
26
42
  authenticated.get("/api/tasks", (c) => {
27
43
  const phase = TaskPhaseSchema.safeParse(c.req.query("phase"));
28
44
  const rawLimit = Number(c.req.query("limit") ?? 50);
@@ -42,7 +58,7 @@ function registerTaskRoutes(authenticated, deps) {
42
58
  })
43
59
  });
44
60
  });
45
- authenticated.post("/api/tasks", deps.strictRateLimitMiddleware, async (c) => {
61
+ authenticated.post("/api/tasks", taskRateLimit, async (c) => {
46
62
  const body = await c.req.json().catch(() => ({}));
47
63
  const parsed = TaskCreateRequestSchema.safeParse(body);
48
64
  if (!parsed.success) return c.json({
@@ -50,13 +66,32 @@ function registerTaskRoutes(authenticated, deps) {
50
66
  error: "Invalid task request"
51
67
  }, 400);
52
68
  try {
53
- const created = application.create(parsed.data);
69
+ const requestedAgentId = parsed.data.delegateAgentId ?? (parsed.data.activation.mode === "start" && parsed.data.activation.executor?.kind === "agent" ? parsed.data.activation.executor.agentId : void 0);
70
+ const agentId = resolveProjectAgentId({
71
+ config: deps.service.currentConfig,
72
+ projects: deps.service.projects,
73
+ explicitAgentId: requestedAgentId,
74
+ projectId: parsed.data.projectId
75
+ });
76
+ const input = {
77
+ ...parsed.data,
78
+ delegateAgentId: agentId,
79
+ activation: parsed.data.activation.mode === "start" ? {
80
+ ...parsed.data.activation,
81
+ executor: parsed.data.activation.executor?.kind === "agent" || !parsed.data.activation.executor ? {
82
+ kind: "agent",
83
+ agentId
84
+ } : parsed.data.activation.executor
85
+ } : parsed.data.activation
86
+ };
87
+ const created = application.create(input);
54
88
  if (created.ok === false) return c.json({
55
89
  ok: false,
56
90
  code: created.reason,
57
91
  error: created.reason
58
92
  }, 409);
59
93
  if (created.runId) deps.service.dispatchTaskRuns();
94
+ else deps.service.dispatchTaskEvents();
60
95
  return c.json({
61
96
  ok: true,
62
97
  task: created.model.task,
@@ -91,39 +126,191 @@ function registerTaskRoutes(authenticated, deps) {
91
126
  runs: runs.listByTask(task.id),
92
127
  receipts: runs.listReceipts(task.id),
93
128
  context: context.list(task.id),
129
+ conversation: conversations.requireState(task.id),
130
+ sessions: conversations.listSessions(task.id),
94
131
  authorityGrants: context.listActiveGrants(task.id),
95
132
  dependencies: dependencies.listDependencies(task.id),
96
133
  dependents: dependencies.listDependents(task.id),
97
134
  allowedCommands: model.allowedCommands
98
135
  });
99
136
  });
100
- authenticated.patch("/api/tasks/:id", deps.strictRateLimitMiddleware, async (c) => {
137
+ authenticated.post("/api/tasks/:id/conversation", taskRateLimit, async (c) => {
138
+ try {
139
+ const result = await deps.service.ensureTaskConversation(c.req.param("id"));
140
+ if (result.created) {
141
+ const task = tasks.require(c.req.param("id"));
142
+ runSqliteWriteTransaction((db) => enqueueTaskChangedEvent(db, {
143
+ taskId: task.id,
144
+ projectId: task.projectId,
145
+ version: task.version,
146
+ changedFields: ["conversation"],
147
+ actor: { kind: "user" }
148
+ }));
149
+ deps.service.dispatchTaskEvents();
150
+ }
151
+ return c.json({
152
+ ok: true,
153
+ ...result
154
+ });
155
+ } catch (error) {
156
+ const message = error instanceof Error ? error.message : String(error);
157
+ return c.json({
158
+ ok: false,
159
+ error: message
160
+ }, message.startsWith("Task not found") ? 404 : 409);
161
+ }
162
+ });
163
+ authenticated.post("/api/tasks/:id/handoff", taskRateLimit, async (c) => {
164
+ const parsed = TaskHandoffRequestSchema.safeParse(await c.req.json().catch(() => ({})));
165
+ if (!parsed.success) return c.json({
166
+ ok: false,
167
+ error: "Invalid task handoff request"
168
+ }, 400);
169
+ try {
170
+ const result = await handoffs.handoff({
171
+ taskId: c.req.param("id"),
172
+ ...parsed.data
173
+ });
174
+ runSqliteWriteTransaction((db) => enqueueTaskChangedEvent(db, {
175
+ taskId: result.task.id,
176
+ projectId: result.task.projectId,
177
+ version: result.task.version,
178
+ changedFields: ["delegateAgentId", "conversation"],
179
+ actor: { kind: "user" }
180
+ }));
181
+ deps.service.dispatchTaskEvents();
182
+ return c.json({
183
+ ok: true,
184
+ task: result.task,
185
+ conversation: result.conversation,
186
+ ...result.fromAgentId ? { fromAgentId: result.fromAgentId } : {},
187
+ toAgentId: result.toAgentId,
188
+ activeSessionKey: result.activeSessionKey,
189
+ assignmentEpoch: result.assignmentEpoch
190
+ });
191
+ } catch (error) {
192
+ const message = error instanceof Error ? error.message : String(error);
193
+ return c.json({
194
+ ok: false,
195
+ error: message
196
+ }, message.startsWith("Agent not found") || message.startsWith("Task not found") ? 404 : 409);
197
+ }
198
+ });
199
+ authenticated.post("/api/tasks/:id/inputs", deps.chatRateLimitMiddleware, async (c) => {
200
+ const active = conversations.getActiveSession(c.req.param("id"));
201
+ if (!active?.sessionKey) return c.json({
202
+ ok: false,
203
+ error: {
204
+ code: "NOT_FOUND",
205
+ message: "Task has no active conversation"
206
+ }
207
+ }, 404);
208
+ const expectedSessionKey = c.req.header("X-Xopc-Expected-Session-Key")?.trim();
209
+ if (expectedSessionKey && expectedSessionKey !== active.sessionKey) return c.json({
210
+ ok: false,
211
+ error: {
212
+ code: "CONFLICT",
213
+ message: "Task executor changed; refresh the conversation"
214
+ }
215
+ }, 409);
216
+ return submitSessionInput(c, deps, active.sessionKey);
217
+ });
218
+ authenticated.patch("/api/tasks/:id/conversation/config", taskRateLimit, async (c) => {
219
+ const body = await c.req.json().catch(() => null);
220
+ const model = typeof body?.model === "string" ? body.model.trim() : "";
221
+ if (!model) return c.json({
222
+ ok: false,
223
+ error: "Missing model"
224
+ }, 400);
225
+ const active = conversations.getActiveSession(c.req.param("id"));
226
+ if (!active?.sessionKey) return c.json({
227
+ ok: false,
228
+ error: "Task has no active conversation"
229
+ }, 404);
230
+ const result = await deps.service.sessions.patchAgentConfig(active.sessionKey, { model });
231
+ if (!result.ok) return c.json({
232
+ ok: false,
233
+ error: result.error
234
+ }, 400);
235
+ return c.json({
236
+ ok: true,
237
+ activeSessionKey: active.sessionKey
238
+ });
239
+ });
240
+ authenticated.get("/api/tasks/:id/conversation/history", async (c) => {
241
+ const parsedLimit = Number.parseInt(c.req.query("limit") ?? "50", 10);
242
+ const parsedOffset = Number.parseInt(c.req.query("offset") ?? "0", 10);
243
+ const rawBefore = c.req.query("before")?.trim();
244
+ const before = rawBefore === void 0 ? void 0 : Number.parseInt(rawBefore, 10);
245
+ if (rawBefore !== void 0 && (!Number.isInteger(before) || before < 0)) return c.json({ error: "Invalid conversation history cursor" }, 400);
246
+ const result = await conversationQuery.getMessagePage(c.req.param("id"), {
247
+ limit: Number.isFinite(parsedLimit) ? Math.min(200, Math.max(1, parsedLimit)) : 50,
248
+ offset: Number.isFinite(parsedOffset) ? Math.max(0, parsedOffset) : 0,
249
+ ...before === void 0 ? {} : { before }
250
+ });
251
+ return result ? c.json(result) : c.json({ error: "Task conversation not found" }, 404);
252
+ });
253
+ authenticated.get("/api/tasks/:id/conversation/timeline", async (c) => {
254
+ const items = await conversationQuery.getTimeline(c.req.param("id"));
255
+ return items ? c.json({
256
+ ok: true,
257
+ items
258
+ }) : c.json({
259
+ ok: false,
260
+ error: "Task conversation not found"
261
+ }, 404);
262
+ });
263
+ authenticated.patch("/api/tasks/:id", taskRateLimit, async (c) => {
101
264
  const parsed = TaskPatchRequestSchema.safeParse(await c.req.json().catch(() => ({})));
102
265
  if (!parsed.success) return c.json({
103
266
  ok: false,
104
267
  error: "Invalid task patch"
105
268
  }, 400);
106
- const updated = tasks.update(c.req.param("id"), parsed.data);
269
+ const updated = runSqliteWriteTransaction((db) => {
270
+ const task = tasks.update(c.req.param("id"), parsed.data);
271
+ if (!task) return void 0;
272
+ const changedFields = Object.keys(parsed.data).filter((field) => field !== "expectedVersion");
273
+ enqueueTaskChangedEvent(db, {
274
+ taskId: task.id,
275
+ projectId: task.projectId,
276
+ version: task.version,
277
+ changedFields,
278
+ actor: { kind: "user" }
279
+ });
280
+ return task;
281
+ });
107
282
  if (!updated) return c.json({
108
283
  ok: false,
109
284
  error: "Task changed or was not found"
110
285
  }, 409);
286
+ deps.service.dispatchTaskEvents();
111
287
  return c.json({
112
288
  ok: true,
113
289
  task: updated
114
290
  });
115
291
  });
116
- authenticated.put("/api/tasks/:id/dependencies", deps.strictRateLimitMiddleware, async (c) => {
292
+ authenticated.put("/api/tasks/:id/dependencies", taskRateLimit, async (c) => {
117
293
  const parsed = TaskDependencyUpdateRequestSchema.safeParse(await c.req.json().catch(() => ({})));
118
294
  if (!parsed.success) return c.json({
119
295
  ok: false,
120
296
  error: "Invalid task dependencies"
121
297
  }, 400);
122
298
  try {
123
- const task = dependencies.replace({
124
- taskId: c.req.param("id"),
125
- ...parsed.data
299
+ const task = runSqliteWriteTransaction((db) => {
300
+ const changed = dependencies.replace({
301
+ taskId: c.req.param("id"),
302
+ ...parsed.data
303
+ });
304
+ enqueueTaskChangedEvent(db, {
305
+ taskId: changed.id,
306
+ projectId: changed.projectId,
307
+ version: changed.version,
308
+ changedFields: ["dependencies"],
309
+ actor: { kind: "user" }
310
+ });
311
+ return changed;
126
312
  });
313
+ deps.service.dispatchTaskEvents();
127
314
  return c.json({
128
315
  ok: true,
129
316
  task,
@@ -140,7 +327,45 @@ function registerTaskRoutes(authenticated, deps) {
140
327
  }, status);
141
328
  }
142
329
  });
143
- authenticated.post("/api/tasks/:id/commands", deps.strictRateLimitMiddleware, async (c) => {
330
+ authenticated.put("/api/tasks/:id/board-position", taskRateLimit, async (c) => {
331
+ const parsed = TaskBoardPositionRequestSchema.safeParse(await c.req.json().catch(() => ({})));
332
+ if (!parsed.success) return c.json({
333
+ ok: false,
334
+ error: "Invalid task board position"
335
+ }, 400);
336
+ try {
337
+ const task = runSqliteWriteTransaction((db) => {
338
+ const reordered = tasks.reorder({
339
+ taskId: c.req.param("id"),
340
+ ...parsed.data
341
+ });
342
+ if (!reordered) return void 0;
343
+ enqueueTaskChangedEvent(db, {
344
+ taskId: reordered.id,
345
+ projectId: reordered.projectId,
346
+ version: reordered.version,
347
+ changedFields: ["boardRank"],
348
+ actor: { kind: "user" }
349
+ });
350
+ return reordered;
351
+ });
352
+ if (!task) return c.json({
353
+ ok: false,
354
+ error: "Task changed or was not found"
355
+ }, 409);
356
+ deps.service.dispatchTaskEvents();
357
+ return c.json({
358
+ ok: true,
359
+ task
360
+ });
361
+ } catch (error) {
362
+ return c.json({
363
+ ok: false,
364
+ error: error instanceof Error ? error.message : String(error)
365
+ }, 400);
366
+ }
367
+ });
368
+ authenticated.post("/api/tasks/:id/commands", taskRateLimit, async (c) => {
144
369
  const parsed = TaskCommandRequestSchema.safeParse(await c.req.json().catch(() => ({})));
145
370
  if (!parsed.success) return c.json({
146
371
  ok: false,
@@ -165,6 +390,7 @@ function registerTaskRoutes(authenticated, deps) {
165
390
  }, 409);
166
391
  }
167
392
  if (result.runId || parsed.data.command?.type === "resolve_wait") deps.service.dispatchTaskRuns();
393
+ else deps.service.dispatchTaskEvents();
168
394
  if (parsed.data.command?.type === "close" && parsed.data.command.resolution === "done") signals.dependencyClosed(c.req.param("id"));
169
395
  return c.json({
170
396
  ok: true,
@@ -172,7 +398,7 @@ function registerTaskRoutes(authenticated, deps) {
172
398
  ...result.runId ? { run: runs.get(result.runId) } : {}
173
399
  });
174
400
  });
175
- authenticated.post("/api/tasks/:id/context", deps.strictRateLimitMiddleware, async (c) => {
401
+ authenticated.post("/api/tasks/:id/context", taskRateLimit, async (c) => {
176
402
  const task = tasks.get(c.req.param("id"));
177
403
  if (!task) return c.json({
178
404
  ok: false,
@@ -183,17 +409,42 @@ function registerTaskRoutes(authenticated, deps) {
183
409
  ok: false,
184
410
  error: "Invalid task context edge"
185
411
  }, 400);
186
- return c.json({
187
- ok: true,
188
- edge: context.add({
412
+ const edge = runSqliteWriteTransaction((db) => {
413
+ const created = context.add({
189
414
  taskId: task.id,
190
415
  ...parsed.data,
191
416
  createdBy: { kind: "user" }
192
- })
417
+ });
418
+ enqueueTaskChangedEvent(db, {
419
+ taskId: task.id,
420
+ projectId: task.projectId,
421
+ version: task.version,
422
+ changedFields: ["context"],
423
+ actor: { kind: "user" }
424
+ });
425
+ return created;
426
+ });
427
+ deps.service.dispatchTaskEvents();
428
+ return c.json({
429
+ ok: true,
430
+ edge
193
431
  }, 201);
194
432
  });
195
- authenticated.delete("/api/tasks/:id/context/:edgeId", deps.strictRateLimitMiddleware, (c) => {
196
- return context.remove(c.req.param("id"), c.req.param("edgeId")) ? c.json({ ok: true }) : c.json({
433
+ authenticated.delete("/api/tasks/:id/context/:edgeId", taskRateLimit, (c) => {
434
+ const task = tasks.get(c.req.param("id"));
435
+ const removed = task ? runSqliteWriteTransaction((db) => {
436
+ const didRemove = context.remove(task.id, c.req.param("edgeId"));
437
+ if (didRemove) enqueueTaskChangedEvent(db, {
438
+ taskId: task.id,
439
+ projectId: task.projectId,
440
+ version: task.version,
441
+ changedFields: ["context"],
442
+ actor: { kind: "user" }
443
+ });
444
+ return didRemove;
445
+ }) : false;
446
+ if (removed) deps.service.dispatchTaskEvents();
447
+ return removed ? c.json({ ok: true }) : c.json({
197
448
  ok: false,
198
449
  error: "Task context edge not found"
199
450
  }, 404);
@@ -219,7 +470,7 @@ function registerTaskRoutes(authenticated, deps) {
219
470
  error: "TaskRun not found"
220
471
  }, 404);
221
472
  });
222
- authenticated.post("/api/task-runs/:runId/cancel", deps.strictRateLimitMiddleware, async (c) => {
473
+ authenticated.post("/api/task-runs/:runId/cancel", taskRateLimit, async (c) => {
223
474
  const body = await c.req.json().catch(() => ({}));
224
475
  const run = runs.get(c.req.param("runId"));
225
476
  if (!run) return c.json({
@@ -256,13 +507,14 @@ function registerTaskRoutes(authenticated, deps) {
256
507
  ok: false,
257
508
  error: result.reason
258
509
  }, 409);
510
+ deps.service.dispatchTaskEvents();
259
511
  return c.json({
260
512
  ok: true,
261
513
  run: runs.get(run.id),
262
514
  receipt: runs.getReceipt(run.id)
263
515
  });
264
516
  });
265
- authenticated.post("/api/task-runs/:runId/feedback", deps.strictRateLimitMiddleware, async (c) => {
517
+ authenticated.post("/api/task-runs/:runId/feedback", taskRateLimit, async (c) => {
266
518
  const body = await c.req.json().catch(() => ({}));
267
519
  if (body.rating !== "helpful" && body.rating !== "not_helpful") return c.json({
268
520
  ok: false,
@@ -306,7 +558,7 @@ function registerTaskRoutes(authenticated, deps) {
306
558
  policy: monitoring.get(projectId)
307
559
  });
308
560
  });
309
- authenticated.patch("/api/projects/:projectId/monitoring", deps.strictRateLimitMiddleware, async (c) => {
561
+ authenticated.patch("/api/projects/:projectId/monitoring", taskRateLimit, async (c) => {
310
562
  const projectId = c.req.param("projectId");
311
563
  if (!deps.service.projects.get(projectId)) return c.json({
312
564
  ok: false,
@@ -6,6 +6,13 @@ export declare function isAllInterfacesHost(host: string | undefined): boolean;
6
6
  export declare const GATEWAY_DEV_CONSOLE_ORIGINS: readonly ["http://localhost:3000", "http://127.0.0.1:3000"];
7
7
  /** Expo / React Native web dev server (Metro defaults to port 8081). */
8
8
  export declare const GATEWAY_EXPO_DEV_ORIGINS: readonly ["http://localhost:8081", "http://127.0.0.1:8081"];
9
+ export type LanGatewayCandidate = {
10
+ url: string;
11
+ address: string;
12
+ interfaceName: string;
13
+ };
14
+ /** Non-internal IPv4 addresses that can serve this gateway on the local network. */
15
+ export declare function enumerateLanGatewayCandidates(port: number): LanGatewayCandidate[];
9
16
  /** Effective HTTP listen port: CLI `--port` override wins over config (default 18790). */
10
17
  export declare function resolveEffectiveGatewayPort(config: {
11
18
  gateway?: {
@@ -27,7 +34,8 @@ export declare function buildDefaultCorsOrigins(params: {
27
34
  }): string[];
28
35
  /**
29
36
  * Effective browser origins for CORS and CSRF checks.
30
- * Custom `gateway.corsOrigins` (e.g. after LAN pairing) still merge loopback Vite dev origins.
37
+ * Configured origins extend the gateway's own browser origins instead of replacing them.
38
+ * This keeps the bundled console reachable while allowing explicit external frontends.
31
39
  */
32
40
  export declare function resolveGatewayCorsOrigins(params: {
33
41
  configuredOrigins?: string[];
@@ -1,4 +1,5 @@
1
1
  import "../daemon/constants.js";
2
+ import { networkInterfaces } from "node:os";
2
3
  //#region src/gateway/host.ts
3
4
  /** True when the bind address is local-only (127.x, localhost, ::1). */
4
5
  function isLoopbackHost(host) {
@@ -17,6 +18,21 @@ const GATEWAY_DEV_CONSOLE_ORIGINS = ["http://localhost:3000", "http://127.0.0.1:
17
18
  /** Expo / React Native web dev server (Metro defaults to port 8081). */
18
19
  const GATEWAY_EXPO_DEV_ORIGINS = ["http://localhost:8081", "http://127.0.0.1:8081"];
19
20
  const GATEWAY_LOOPBACK_DEV_ORIGINS = [...GATEWAY_DEV_CONSOLE_ORIGINS, ...GATEWAY_EXPO_DEV_ORIGINS];
21
+ /** Non-internal IPv4 addresses that can serve this gateway on the local network. */
22
+ function enumerateLanGatewayCandidates(port) {
23
+ const seen = /* @__PURE__ */ new Set();
24
+ const candidates = [];
25
+ for (const [interfaceName, addresses] of Object.entries(networkInterfaces())) for (const address of addresses ?? []) {
26
+ if (address.family !== "IPv4" || address.internal || seen.has(address.address)) continue;
27
+ seen.add(address.address);
28
+ candidates.push({
29
+ address: address.address,
30
+ interfaceName,
31
+ url: `http://${address.address}:${port}`
32
+ });
33
+ }
34
+ return candidates;
35
+ }
20
36
  /** Effective HTTP listen port: CLI `--port` override wins over config (default 18790). */
21
37
  function resolveEffectiveGatewayPort(config, listenPortOverride) {
22
38
  if (typeof listenPortOverride === "number" && Number.isFinite(listenPortOverride)) return listenPortOverride;
@@ -35,19 +51,20 @@ function buildDefaultCorsOrigins(params) {
35
51
  ]);
36
52
  const bindHost = params.bindHost?.trim();
37
53
  if (bindHost && !isLoopbackHost(bindHost) && !isAllInterfacesHost(bindHost)) origins.add(`http://${bindHost}:${params.port}`);
54
+ if (isAllInterfacesHost(bindHost)) for (const candidate of enumerateLanGatewayCandidates(params.port)) origins.add(candidate.url);
38
55
  return [...origins];
39
56
  }
40
57
  /**
41
58
  * Effective browser origins for CORS and CSRF checks.
42
- * Custom `gateway.corsOrigins` (e.g. after LAN pairing) still merge loopback Vite dev origins.
59
+ * Configured origins extend the gateway's own browser origins instead of replacing them.
60
+ * This keeps the bundled console reachable while allowing explicit external frontends.
43
61
  */
44
62
  function resolveGatewayCorsOrigins(params) {
45
63
  const configured = (params.configuredOrigins ?? []).map((origin) => origin.trim()).filter(Boolean);
46
- if (configured.length === 0) return buildDefaultCorsOrigins({
64
+ return [...new Set([...configured, ...buildDefaultCorsOrigins({
47
65
  port: params.port,
48
66
  bindHost: params.bindHost
49
- });
50
- return [...new Set([...configured, ...GATEWAY_LOOPBACK_DEV_ORIGINS])];
67
+ })])];
51
68
  }
52
69
  /** Browser origin (`https://host`) from a gateway public/tunnel root URL. */
53
70
  function originFromGatewayPublicUrl(publicUrl) {
@@ -73,4 +90,4 @@ function resolveAllowedBrowserOrigins(params) {
73
90
  return origins;
74
91
  }
75
92
  //#endregion
76
- export { GATEWAY_DEV_CONSOLE_ORIGINS, GATEWAY_EXPO_DEV_ORIGINS, buildDefaultCorsOrigins, isAllInterfacesHost, isLoopbackHost, originFromGatewayPublicUrl, resolveAllowedBrowserOrigins, resolveEffectiveGatewayPort, resolveGatewayCorsOrigins, resolveGatewayServiceListenPort };
93
+ export { GATEWAY_DEV_CONSOLE_ORIGINS, GATEWAY_EXPO_DEV_ORIGINS, buildDefaultCorsOrigins, enumerateLanGatewayCandidates, isAllInterfacesHost, isLoopbackHost, originFromGatewayPublicUrl, resolveAllowedBrowserOrigins, resolveEffectiveGatewayPort, resolveGatewayCorsOrigins, resolveGatewayServiceListenPort };
@@ -36,6 +36,7 @@ declare class BucketRegistry {
36
36
  private authFailureSignature?;
37
37
  private strictApiLimiter?;
38
38
  private chatApiLimiter?;
39
+ private taskApiLimiter?;
39
40
  private xopcCloudPollLimiter?;
40
41
  private channelApiLimiter?;
41
42
  private tunnelMutateLimiter?;
@@ -52,6 +53,8 @@ declare class BucketRegistry {
52
53
  strictApi(): RateLimiter;
53
54
  /** Interactive chat actions — 180 req / 60 s per client IP. */
54
55
  chatApi(): RateLimiter;
56
+ /** Interactive task and board mutations — 300 req / 60 s per client IP. */
57
+ taskApi(): RateLimiter;
55
58
  /** XOPC Cloud authorization polling — 60 req / 60 s per client IP. */
56
59
  xopcCloudPoll(): RateLimiter;
57
60
  /** Channel mutation/action endpoints — 20 req / 60 s per client IP. */
@@ -48,6 +48,7 @@ var BucketRegistry = class {
48
48
  authFailureSignature;
49
49
  strictApiLimiter;
50
50
  chatApiLimiter;
51
+ taskApiLimiter;
51
52
  xopcCloudPollLimiter;
52
53
  channelApiLimiter;
53
54
  tunnelMutateLimiter;
@@ -89,6 +90,14 @@ var BucketRegistry = class {
89
90
  });
90
91
  return this.chatApiLimiter;
91
92
  }
93
+ /** Interactive task and board mutations — 300 req / 60 s per client IP. */
94
+ taskApi() {
95
+ if (!this.taskApiLimiter) this.taskApiLimiter = new RateLimiter({
96
+ maxRequests: 300,
97
+ windowMs: 6e4
98
+ });
99
+ return this.taskApiLimiter;
100
+ }
92
101
  /** XOPC Cloud authorization polling — 60 req / 60 s per client IP. */
93
102
  xopcCloudPoll() {
94
103
  if (!this.xopcCloudPollLimiter) this.xopcCloudPollLimiter = new RateLimiter({
@@ -142,6 +151,7 @@ var BucketRegistry = class {
142
151
  this.authFailureLimiter?.destroy();
143
152
  this.strictApiLimiter?.destroy();
144
153
  this.chatApiLimiter?.destroy();
154
+ this.taskApiLimiter?.destroy();
145
155
  this.xopcCloudPollLimiter?.destroy();
146
156
  this.channelApiLimiter?.destroy();
147
157
  this.tunnelMutateLimiter?.destroy();
@@ -151,6 +161,7 @@ var BucketRegistry = class {
151
161
  this.authFailureLimiter = void 0;
152
162
  this.strictApiLimiter = void 0;
153
163
  this.chatApiLimiter = void 0;
164
+ this.taskApiLimiter = void 0;
154
165
  this.xopcCloudPollLimiter = void 0;
155
166
  this.channelApiLimiter = void 0;
156
167
  this.tunnelMutateLimiter = void 0;
@@ -164,6 +175,7 @@ var BucketRegistry = class {
164
175
  this.authFailureLimiter?.resetForTests();
165
176
  this.strictApiLimiter?.resetForTests();
166
177
  this.chatApiLimiter?.resetForTests();
178
+ this.taskApiLimiter?.resetForTests();
167
179
  this.xopcCloudPollLimiter?.resetForTests();
168
180
  this.channelApiLimiter?.resetForTests();
169
181
  this.tunnelMutateLimiter?.resetForTests();
@@ -1,12 +1,9 @@
1
- import { isAllInterfacesHost, isLoopbackHost } from "./host.js";
1
+ import { isAllInterfacesHost, isLoopbackHost, resolveGatewayCorsOrigins } from "./host.js";
2
2
  import { isAuthRateLimitGloballyDisabled, isGatewayStrictSecurityEnabled } from "./rate-limit/env-flags.js";
3
3
  import "./rate-limit/index.js";
4
4
  import { resolveGatewayListenPlan } from "./listen.js";
5
5
  import { assertTailscaleExposureCompatible } from "./tailscale-lifecycle.js";
6
6
  //#region src/gateway/runtime-config.ts
7
- function normalizeCorsOrigins(cfg) {
8
- return (cfg.gateway?.corsOrigins ?? []).map((value) => value.trim()).filter(Boolean);
9
- }
10
7
  function hasSharedSecret(auth) {
11
8
  if (auth.mode === "trusted-proxy") return true;
12
9
  if (auth.mode === "token") return typeof auth.token === "string" && auth.token.trim().length > 0;
@@ -23,7 +20,11 @@ function assertGatewayRuntimeConfig(params) {
23
20
  bindOverride: params.bindOverride
24
21
  });
25
22
  const loopback = isLoopbackHost(bindHost);
26
- const corsOrigins = normalizeCorsOrigins(params.cfg);
23
+ const corsOrigins = resolveGatewayCorsOrigins({
24
+ configuredOrigins: params.cfg.gateway?.corsOrigins,
25
+ port: params.port,
26
+ bindHost
27
+ });
27
28
  const dangerouslyAllowHostHeaderOriginFallback = params.cfg.gateway?.dangerouslyAllowHostHeaderOriginFallback === true;
28
29
  if (bindMode === "loopback" && !loopback) throw new Error(`gateway bind=loopback resolved to non-loopback host ${bindHost}; refusing fallback to a network bind`);
29
30
  if (bindMode === "custom") {
@@ -110,13 +110,6 @@ function collectGatewayConfigFindings(params) {
110
110
  title: "Large number of CORS origins configured",
111
111
  detail: `${params.corsOrigins.length} CORS origins configured. Review whether all are necessary.`
112
112
  });
113
- if (!loopback && (!params.corsOrigins || params.corsOrigins.length === 0) && params.dangerouslyAllowHostHeaderOriginFallback !== true) findings.push({
114
- checkId: "gateway.cors.no_explicit_origins",
115
- severity: "info",
116
- title: "No custom CORS origins — loopback browsers only",
117
- detail: "Gateway listens on a network address with empty gateway.corsOrigins. localhost and 127.0.0.1 on the gateway port are allowed by default; LAN or remote browser URLs must be added explicitly.",
118
- remediation: "Add gateway.corsOrigins entries (e.g. http://192.168.x.x:<port>) for non-loopback browser access, or enable gateway.dangerouslyAllowHostHeaderOriginFallback only if you understand the CSRF risk."
119
- });
120
113
  if (!loopback && params.auth.mode !== "none" && params.auth.mode !== "trusted-proxy" && params.rateLimitEnabled === false) findings.push({
121
114
  checkId: "gateway.auth.no_rate_limit",
122
115
  severity: "warn",
@@ -51,7 +51,10 @@ var GatewayServer = class {
51
51
  });
52
52
  const { configureTunnelFromGatewayConfig } = await import("../tunnel/gateway-lifecycle.js");
53
53
  await configureTunnelFromGatewayConfig(this.service.currentConfig, { deferWellKnownFetch: true });
54
- const app = createHonoApp({ service: this.service });
54
+ const app = createHonoApp({
55
+ service: this.service,
56
+ listenHost: bindHost
57
+ });
55
58
  const primaryHost = listenHosts[0] ?? bindHost;
56
59
  const attachUpgrade = (server) => {
57
60
  server.on("upgrade", (req, socket, head) => {