@xopcai/xopc 0.0.197 → 0.0.199

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 (224) 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-Bt70bZjv.js} +1 -1
  5. package/dist/gateway/static/root/assets/agents-D19uNmJf.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-B4x7JfL8.js +1 -0
  8. package/dist/gateway/static/root/assets/appearance-settings-Dwdagf-Q.js +1 -0
  9. package/dist/gateway/static/root/assets/apps-page-DkTiesxe.js +1 -0
  10. package/dist/gateway/static/root/assets/{archive-plugin-qL07mwnY.js → archive-plugin-Cv0VXKLm.js} +1 -1
  11. package/dist/gateway/static/root/assets/{attachment-load-CqSobpdi.js → attachment-load-CgidKrXn.js} +1 -1
  12. package/dist/gateway/static/root/assets/automations-page-BR6qMOVr.js +6 -0
  13. package/dist/gateway/static/root/assets/{binary-plugins-DBRPyLYE.js → binary-plugins-DN3CQgIq.js} +1 -1
  14. package/dist/gateway/static/root/assets/{block-editor-6wG1LU7t.js → block-editor-BwkSl1F_.js} +5 -5
  15. package/dist/gateway/static/root/assets/browser-settings-page-CDFQWAxI.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-C_I31wLm.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-D41mssOq.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-BXgCyuu2.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-DV5BhAeu.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-5FEBYguL.js +1 -0
  26. package/dist/gateway/static/root/assets/connectors-page-C_qkEN0T.js +2 -0
  27. package/dist/gateway/static/root/assets/copy-Dv6d4Dvw.js +1 -0
  28. package/dist/gateway/static/root/assets/cron-expression-SEFx5rnr.js +2 -0
  29. package/dist/gateway/static/root/assets/date-picker-D4zCUQJL.js +1 -0
  30. package/dist/gateway/static/root/assets/dependency-picker-DpQAxS2k.js +1 -0
  31. package/dist/gateway/static/root/assets/{desktop-pet-CsLR4pWB.js → desktop-pet-B_5G3lQl.js} +1 -1
  32. package/dist/gateway/static/root/assets/desktop-pet-settings-ClEyoUJe.js +1 -0
  33. package/dist/gateway/static/root/assets/directory-picker-path-field-AsFWmOjG.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-C22Y2o5l.js} +1 -1
  37. package/dist/gateway/static/root/assets/{extension-page-DAKjpNwd.js → extension-page-C61Ikawk.js} +1 -1
  38. package/dist/gateway/static/root/assets/extension-settings-page-WlWWcDFr.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-ByIOMQdc.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-CzzS9A17.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-B4A_CXUL.js +1 -0
  47. package/dist/gateway/static/root/assets/home-page-FVGYYPWo.js +3 -0
  48. package/dist/gateway/static/root/assets/index-CGbKTCST.js +152 -0
  49. package/dist/gateway/static/root/assets/index-CR233S0Q.css +1 -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-BD_TFz0_.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-BNpkigNb.js +2 -0
  54. package/dist/gateway/static/root/assets/local-apps-page-BwbUeLfm.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-DXP9OQQt.js +2 -0
  57. package/dist/gateway/static/root/assets/management-settings-CW2VpqtT.js +1 -0
  58. package/dist/gateway/static/root/assets/{markdown-split-DvVBewS4.js → markdown-split-B1M9KO16.js} +1 -1
  59. package/dist/gateway/static/root/assets/{media-plugins-YVj7NVzD.js → media-plugins-B_QlNdqZ.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-BTbD4GmH.js} +1 -1
  62. package/dist/gateway/static/root/assets/notes-workbench-JRiq7QDW.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-CeqtArlZ.js +1 -0
  66. package/dist/gateway/static/root/assets/{product-open-page-Bwx_CQht.js → product-open-page-DnalBZZs.js} +1 -1
  67. package/dist/gateway/static/root/assets/project-detail-page-CjWA13aE.js +1 -0
  68. package/dist/gateway/static/root/assets/projects-page-jsCkScbG.js +1 -0
  69. package/dist/gateway/static/root/assets/remote-access-hub-CGPJTEkM.js +1 -0
  70. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-Fe05Nus6.js → runtime-tools-settings-panel-cGkBEfg4.js} +4 -4
  71. package/dist/gateway/static/root/assets/schema-form-CmsrLlxu.js +1 -0
  72. package/dist/gateway/static/root/assets/sessions-page-BBHzziMd.js +1 -0
  73. package/dist/gateway/static/root/assets/settings-advanced-gate-2cGg8-9h.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-DUWCbaMJ.js +1 -0
  76. package/dist/gateway/static/root/assets/{settings-loading-skeleton-BRE_-7Da.js → settings-loading-skeleton-Bvy5rdMY.js} +1 -1
  77. package/dist/gateway/static/root/assets/{settings-page-C1zxCpqB.js → settings-page-cZSAwsRR.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-Cf06b66-.js +1 -0
  81. package/dist/gateway/static/root/assets/share-preview-page-BzA2NCAH.js +2 -0
  82. package/dist/gateway/static/root/assets/shares-settings-xlNKydYY.js +1 -0
  83. package/dist/gateway/static/root/assets/skill-reload-api-C5Xu93c_.js +1 -0
  84. package/dist/gateway/static/root/assets/skills-page-Bvde7_i5.js +2 -0
  85. package/dist/gateway/static/root/assets/{skills-page.utils-CYwFWTso.js → skills-page.utils-vXSqHR7g.js} +1 -1
  86. package/dist/gateway/static/root/assets/system-settings-panel-9cQGOGCK.js +1 -0
  87. package/dist/gateway/static/root/assets/task-detail-page-Bkno4tTe.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-7XfKULPI.js +1 -0
  92. package/dist/gateway/static/root/assets/user-context-page-CZ9m9aC_.js +1 -0
  93. package/dist/gateway/static/root/assets/voice-api-key-field-DXr21v-3.js +1 -0
  94. package/dist/gateway/static/root/assets/work-discovery-overlay-CeG7g_DZ.js +1 -0
  95. package/dist/gateway/static/root/assets/work-discovery-page-CQWZD03N.js +1 -0
  96. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-DqIb1oBB.js → workflow-run-setup-panel-DSQQZP9T.js} +2 -2
  97. package/dist/gateway/static/root/assets/workflows-page-Cs-MXFJ1.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 +54 -2
  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/service.js +1 -0
  105. package/dist/src/agent/service.types.d.ts +1 -0
  106. package/dist/src/agent/tools/factory.d.ts +2 -0
  107. package/dist/src/agent/tools/factory.js +2 -1
  108. package/dist/src/agent/tools/xopc-use-tool.d.ts +1 -0
  109. package/dist/src/agent/tools/xopc-use-tool.js +56 -6
  110. package/dist/src/automations/domain/validation.d.ts +40 -0
  111. package/dist/src/automations/draft/automation-draft-validator.d.ts +4 -1
  112. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  113. package/dist/src/config/rules.js +2 -2
  114. package/dist/src/gateway/hono/app.d.ts +2 -0
  115. package/dist/src/gateway/hono/app.js +10 -3
  116. package/dist/src/gateway/hono/middleware/strict-rate-limit.d.ts +4 -0
  117. package/dist/src/gateway/hono/middleware/strict-rate-limit.js +8 -1
  118. package/dist/src/gateway/hono/routes/config-patch/misc.js +2 -1
  119. package/dist/src/gateway/hono/routes/config.js +9 -0
  120. package/dist/src/gateway/hono/routes/deps.d.ts +1 -0
  121. package/dist/src/gateway/hono/routes/projects.js +55 -0
  122. package/dist/src/gateway/hono/routes/tasks.js +112 -19
  123. package/dist/src/gateway/host.d.ts +9 -1
  124. package/dist/src/gateway/host.js +22 -5
  125. package/dist/src/gateway/rate-limit/buckets.d.ts +3 -0
  126. package/dist/src/gateway/rate-limit/buckets.js +12 -0
  127. package/dist/src/gateway/runtime-config.js +6 -5
  128. package/dist/src/gateway/security/audit.js +0 -7
  129. package/dist/src/gateway/server.js +4 -1
  130. package/dist/src/gateway/service.d.ts +1 -0
  131. package/dist/src/gateway/service.js +5 -1
  132. package/dist/src/proactive/actions/service.d.ts +2 -0
  133. package/dist/src/proactive/actions/service.js +80 -0
  134. package/dist/src/proactive/execution/insight.js +20 -0
  135. package/dist/src/proactive/execution/repository.js +8 -2
  136. package/dist/src/proactive/execution/types.d.ts +14 -0
  137. package/dist/src/proactive/inbox/repository.js +59 -9
  138. package/dist/src/proactive/inbox/service.d.ts +2 -2
  139. package/dist/src/proactive/inbox/service.js +7 -2
  140. package/dist/src/proactive/inbox/types.d.ts +14 -0
  141. package/dist/src/proactive/inbox/worker.js +2 -0
  142. package/dist/src/proactive/scenarios/prompt-composer.js +2 -2
  143. package/dist/src/storage/sqlite/migrations/118_task_board_rank.sql +6 -0
  144. package/dist/src/storage/sqlite/migrations/119_proactive_disposition.sql +13 -0
  145. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  146. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  147. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  148. package/dist/src/storage/sqlite/schema.js +1 -1
  149. package/dist/src/tasks/home-query-service.d.ts +5 -0
  150. package/dist/src/tasks/home-query-service.js +7 -2
  151. package/dist/src/tasks/index.d.ts +1 -0
  152. package/dist/src/tasks/index.js +4 -3
  153. package/dist/src/tasks/project-monitoring-service.d.ts +9 -0
  154. package/dist/src/tasks/project-monitoring-service.js +41 -3
  155. package/dist/src/tasks/project-operating-view-service.d.ts +1 -2
  156. package/dist/src/tasks/project-operating-view-service.js +26 -23
  157. package/dist/src/tasks/task-application-service.js +99 -6
  158. package/dist/src/tasks/task-change-events.d.ts +13 -0
  159. package/dist/src/tasks/task-change-events.js +29 -0
  160. package/dist/src/tasks/task-read-model-projector.js +2 -1
  161. package/dist/src/tasks/task-repository.d.ts +7 -0
  162. package/dist/src/tasks/task-repository.js +29 -6
  163. package/dist/src/tunnel/enable-lan-pairing.js +15 -26
  164. package/dist/src/tunnel/pair-context.js +1 -1
  165. package/dist/src/tunnel/pair-url.js +1 -1
  166. package/dist/src/tunnel/tunnel-qr.d.ts +0 -7
  167. package/dist/src/tunnel/tunnel-qr.js +2 -19
  168. package/package.json +3 -1
  169. package/dist/gateway/static/root/assets/agents-Baxfj44y.js +0 -68
  170. package/dist/gateway/static/root/assets/app-management-settings-panel-S8LhoD6G.js +0 -1
  171. package/dist/gateway/static/root/assets/appearance-settings-D8Cd5vDP.js +0 -1
  172. package/dist/gateway/static/root/assets/apps-page-0uCxKyE5.js +0 -1
  173. package/dist/gateway/static/root/assets/automations-page-XyDat125.js +0 -6
  174. package/dist/gateway/static/root/assets/browser-settings-page-CrYNMR4w.js +0 -8
  175. package/dist/gateway/static/root/assets/browser-workflow-inputs-Abil4puY.js +0 -1
  176. package/dist/gateway/static/root/assets/capabilities-settings-panel-CvMcDhGf.js +0 -7
  177. package/dist/gateway/static/root/assets/capability-presets-settings-panel-CHJDY-Ih.js +0 -3
  178. package/dist/gateway/static/root/assets/channels-settings-Ds9BJbcY.js +0 -1
  179. package/dist/gateway/static/root/assets/channels-status-swr-DPaJSYX_.js +0 -1
  180. package/dist/gateway/static/root/assets/connectors-api-CgWjgPSI.js +0 -1
  181. package/dist/gateway/static/root/assets/connectors-page-NUs-Zf7F.js +0 -2
  182. package/dist/gateway/static/root/assets/cron-expression-ZdDBAoqA.js +0 -2
  183. package/dist/gateway/static/root/assets/dependency-picker-BGeB1jpn.js +0 -1
  184. package/dist/gateway/static/root/assets/desktop-pet-settings-CCjT1bdo.js +0 -1
  185. package/dist/gateway/static/root/assets/directory-picker-path-field-C-Te1QN3.js +0 -1
  186. package/dist/gateway/static/root/assets/extension-settings-page-BLSNdlgd.js +0 -1
  187. package/dist/gateway/static/root/assets/gateway-config-api-CQrf0k3D.js +0 -1
  188. package/dist/gateway/static/root/assets/gateway-settings-CvgH8IJI.js +0 -1
  189. package/dist/gateway/static/root/assets/heartbeat-settings-SPpnqbjl.js +0 -1
  190. package/dist/gateway/static/root/assets/home-page-BFoD6047.js +0 -3
  191. package/dist/gateway/static/root/assets/index-84ys9u-K.js +0 -152
  192. package/dist/gateway/static/root/assets/index-D-srnucP.css +0 -1
  193. package/dist/gateway/static/root/assets/local-app-workbench-page-D5uL2_Fd.js +0 -2
  194. package/dist/gateway/static/root/assets/local-apps-page-DIWkFa3g.js +0 -1
  195. package/dist/gateway/static/root/assets/logs-page--6v8qq6y.js +0 -2
  196. package/dist/gateway/static/root/assets/management-settings-DtEuS-kR.js +0 -1
  197. package/dist/gateway/static/root/assets/messages-DQ5X6cXp.js +0 -3
  198. package/dist/gateway/static/root/assets/notes-workbench-IdjuNXvH.js +0 -4
  199. package/dist/gateway/static/root/assets/page-tabs-7bSEWfSX.js +0 -1
  200. package/dist/gateway/static/root/assets/preview-open-alternatives-tgLbHgcV.js +0 -1
  201. package/dist/gateway/static/root/assets/project-detail-page-DnJXSuM4.js +0 -1
  202. package/dist/gateway/static/root/assets/projects-page-DckZKlBI.js +0 -1
  203. package/dist/gateway/static/root/assets/remote-access-hub-CfbUHBUH.js +0 -1
  204. package/dist/gateway/static/root/assets/schema-form-CFfyC4CB.js +0 -1
  205. package/dist/gateway/static/root/assets/sessions-page-BCfCh4My.js +0 -1
  206. package/dist/gateway/static/root/assets/settings-advanced-gate-Cbi4FvzO.js +0 -1
  207. package/dist/gateway/static/root/assets/settings-form-section-gEnEYT_3.js +0 -1
  208. package/dist/gateway/static/root/assets/settings-page-layout-CRpXF_05.js +0 -1
  209. package/dist/gateway/static/root/assets/setup-status-panel-BcHdpmFf.js +0 -1
  210. package/dist/gateway/static/root/assets/share-preview-page-DU8L68A5.js +0 -2
  211. package/dist/gateway/static/root/assets/shares-settings-kjTqH9yY.js +0 -1
  212. package/dist/gateway/static/root/assets/skill-reload-api-QAGpwzIM.js +0 -1
  213. package/dist/gateway/static/root/assets/skills-page-lE1-YRdK.js +0 -2
  214. package/dist/gateway/static/root/assets/system-settings-panel-CVDsiPKn.js +0 -1
  215. package/dist/gateway/static/root/assets/task-detail-page-DsM13-cy.js +0 -1
  216. package/dist/gateway/static/root/assets/typed-models-lib-DelyYgoc.js +0 -1
  217. package/dist/gateway/static/root/assets/use-autosave-C50Mq5yQ.js +0 -1
  218. package/dist/gateway/static/root/assets/user-context-page-D-VEOt2A.js +0 -1
  219. package/dist/gateway/static/root/assets/voice-api-key-field-DYfgTe3Y.js +0 -1
  220. package/dist/gateway/static/root/assets/work-discovery-overlay-DPbujsp3.js +0 -1
  221. package/dist/gateway/static/root/assets/work-discovery-page-D77opady.js +0 -1
  222. package/dist/gateway/static/root/assets/workflows-page-B9-PLmlX.js +0 -5
  223. package/dist/src/gateway/ensure-network-cors.d.ts +0 -3
  224. package/dist/src/gateway/ensure-network-cors.js +0 -23
@@ -1,10 +1,12 @@
1
+ import { runSqliteWriteTransaction } from "../../../storage/sqlite/transaction.js";
1
2
  import { TaskRepository } from "../../../tasks/task-repository.js";
2
3
  import { TaskRunRepository } from "../../../tasks/task-run-repository.js";
3
4
  import { TaskReadModelProjector } from "../../../tasks/task-read-model-projector.js";
4
5
  import { TaskPhaseSchema } from "../../../../packages/gateway-contract/src/task-lifecycle.js";
5
- import { TaskCommandRequestSchema, TaskContextInputSchema, TaskCreateRequestSchema, TaskDependencyUpdateRequestSchema, TaskPatchRequestSchema } from "../../../../packages/gateway-contract/src/tasks.js";
6
+ import { TaskBoardPositionRequestSchema, TaskCommandRequestSchema, TaskContextInputSchema, TaskCreateRequestSchema, TaskDependencyUpdateRequestSchema, TaskPatchRequestSchema } from "../../../../packages/gateway-contract/src/tasks.js";
6
7
  import { ProjectMonitoringUpdateSchema } from "../../../../packages/gateway-contract/src/project-monitoring.js";
7
8
  import { TaskContextRepository } from "../../../tasks/task-context-repository.js";
9
+ import { enqueueTaskChangedEvent } from "../../../tasks/task-change-events.js";
8
10
  import { TaskDependencyError, TaskDependencyService } from "../../../tasks/task-dependency-service.js";
9
11
  import { TaskApplicationService } from "../../../tasks/task-application-service.js";
10
12
  import { ProjectMonitoringService } from "../../../tasks/project-monitoring-service.js";
@@ -13,6 +15,7 @@ import { TaskValueMetricsService } from "../../../tasks/task-value-metrics-servi
13
15
  import { TaskSignalService } from "../../../tasks/task-signal-service.js";
14
16
  //#region src/gateway/hono/routes/tasks.ts
15
17
  function registerTaskRoutes(authenticated, deps) {
18
+ const taskRateLimit = deps.taskRateLimitMiddleware ?? deps.strictRateLimitMiddleware;
16
19
  const application = new TaskApplicationService();
17
20
  const operatingViews = new ProjectOperatingViewService(deps.service.projects);
18
21
  const monitoring = new ProjectMonitoringService();
@@ -42,7 +45,7 @@ function registerTaskRoutes(authenticated, deps) {
42
45
  })
43
46
  });
44
47
  });
45
- authenticated.post("/api/tasks", deps.strictRateLimitMiddleware, async (c) => {
48
+ authenticated.post("/api/tasks", taskRateLimit, async (c) => {
46
49
  const body = await c.req.json().catch(() => ({}));
47
50
  const parsed = TaskCreateRequestSchema.safeParse(body);
48
51
  if (!parsed.success) return c.json({
@@ -57,6 +60,7 @@ function registerTaskRoutes(authenticated, deps) {
57
60
  error: created.reason
58
61
  }, 409);
59
62
  if (created.runId) deps.service.dispatchTaskRuns();
63
+ else deps.service.dispatchTaskEvents();
60
64
  return c.json({
61
65
  ok: true,
62
66
  task: created.model.task,
@@ -97,33 +101,57 @@ function registerTaskRoutes(authenticated, deps) {
97
101
  allowedCommands: model.allowedCommands
98
102
  });
99
103
  });
100
- authenticated.patch("/api/tasks/:id", deps.strictRateLimitMiddleware, async (c) => {
104
+ authenticated.patch("/api/tasks/:id", taskRateLimit, async (c) => {
101
105
  const parsed = TaskPatchRequestSchema.safeParse(await c.req.json().catch(() => ({})));
102
106
  if (!parsed.success) return c.json({
103
107
  ok: false,
104
108
  error: "Invalid task patch"
105
109
  }, 400);
106
- const updated = tasks.update(c.req.param("id"), parsed.data);
110
+ const updated = runSqliteWriteTransaction((db) => {
111
+ const task = tasks.update(c.req.param("id"), parsed.data);
112
+ if (!task) return void 0;
113
+ const changedFields = Object.keys(parsed.data).filter((field) => field !== "expectedVersion");
114
+ enqueueTaskChangedEvent(db, {
115
+ taskId: task.id,
116
+ projectId: task.projectId,
117
+ version: task.version,
118
+ changedFields,
119
+ actor: { kind: "user" }
120
+ });
121
+ return task;
122
+ });
107
123
  if (!updated) return c.json({
108
124
  ok: false,
109
125
  error: "Task changed or was not found"
110
126
  }, 409);
127
+ deps.service.dispatchTaskEvents();
111
128
  return c.json({
112
129
  ok: true,
113
130
  task: updated
114
131
  });
115
132
  });
116
- authenticated.put("/api/tasks/:id/dependencies", deps.strictRateLimitMiddleware, async (c) => {
133
+ authenticated.put("/api/tasks/:id/dependencies", taskRateLimit, async (c) => {
117
134
  const parsed = TaskDependencyUpdateRequestSchema.safeParse(await c.req.json().catch(() => ({})));
118
135
  if (!parsed.success) return c.json({
119
136
  ok: false,
120
137
  error: "Invalid task dependencies"
121
138
  }, 400);
122
139
  try {
123
- const task = dependencies.replace({
124
- taskId: c.req.param("id"),
125
- ...parsed.data
140
+ const task = runSqliteWriteTransaction((db) => {
141
+ const changed = dependencies.replace({
142
+ taskId: c.req.param("id"),
143
+ ...parsed.data
144
+ });
145
+ enqueueTaskChangedEvent(db, {
146
+ taskId: changed.id,
147
+ projectId: changed.projectId,
148
+ version: changed.version,
149
+ changedFields: ["dependencies"],
150
+ actor: { kind: "user" }
151
+ });
152
+ return changed;
126
153
  });
154
+ deps.service.dispatchTaskEvents();
127
155
  return c.json({
128
156
  ok: true,
129
157
  task,
@@ -140,7 +168,45 @@ function registerTaskRoutes(authenticated, deps) {
140
168
  }, status);
141
169
  }
142
170
  });
143
- authenticated.post("/api/tasks/:id/commands", deps.strictRateLimitMiddleware, async (c) => {
171
+ authenticated.put("/api/tasks/:id/board-position", taskRateLimit, async (c) => {
172
+ const parsed = TaskBoardPositionRequestSchema.safeParse(await c.req.json().catch(() => ({})));
173
+ if (!parsed.success) return c.json({
174
+ ok: false,
175
+ error: "Invalid task board position"
176
+ }, 400);
177
+ try {
178
+ const task = runSqliteWriteTransaction((db) => {
179
+ const reordered = tasks.reorder({
180
+ taskId: c.req.param("id"),
181
+ ...parsed.data
182
+ });
183
+ if (!reordered) return void 0;
184
+ enqueueTaskChangedEvent(db, {
185
+ taskId: reordered.id,
186
+ projectId: reordered.projectId,
187
+ version: reordered.version,
188
+ changedFields: ["boardRank"],
189
+ actor: { kind: "user" }
190
+ });
191
+ return reordered;
192
+ });
193
+ if (!task) return c.json({
194
+ ok: false,
195
+ error: "Task changed or was not found"
196
+ }, 409);
197
+ deps.service.dispatchTaskEvents();
198
+ return c.json({
199
+ ok: true,
200
+ task
201
+ });
202
+ } catch (error) {
203
+ return c.json({
204
+ ok: false,
205
+ error: error instanceof Error ? error.message : String(error)
206
+ }, 400);
207
+ }
208
+ });
209
+ authenticated.post("/api/tasks/:id/commands", taskRateLimit, async (c) => {
144
210
  const parsed = TaskCommandRequestSchema.safeParse(await c.req.json().catch(() => ({})));
145
211
  if (!parsed.success) return c.json({
146
212
  ok: false,
@@ -165,6 +231,7 @@ function registerTaskRoutes(authenticated, deps) {
165
231
  }, 409);
166
232
  }
167
233
  if (result.runId || parsed.data.command?.type === "resolve_wait") deps.service.dispatchTaskRuns();
234
+ else deps.service.dispatchTaskEvents();
168
235
  if (parsed.data.command?.type === "close" && parsed.data.command.resolution === "done") signals.dependencyClosed(c.req.param("id"));
169
236
  return c.json({
170
237
  ok: true,
@@ -172,7 +239,7 @@ function registerTaskRoutes(authenticated, deps) {
172
239
  ...result.runId ? { run: runs.get(result.runId) } : {}
173
240
  });
174
241
  });
175
- authenticated.post("/api/tasks/:id/context", deps.strictRateLimitMiddleware, async (c) => {
242
+ authenticated.post("/api/tasks/:id/context", taskRateLimit, async (c) => {
176
243
  const task = tasks.get(c.req.param("id"));
177
244
  if (!task) return c.json({
178
245
  ok: false,
@@ -183,17 +250,42 @@ function registerTaskRoutes(authenticated, deps) {
183
250
  ok: false,
184
251
  error: "Invalid task context edge"
185
252
  }, 400);
186
- return c.json({
187
- ok: true,
188
- edge: context.add({
253
+ const edge = runSqliteWriteTransaction((db) => {
254
+ const created = context.add({
189
255
  taskId: task.id,
190
256
  ...parsed.data,
191
257
  createdBy: { kind: "user" }
192
- })
258
+ });
259
+ enqueueTaskChangedEvent(db, {
260
+ taskId: task.id,
261
+ projectId: task.projectId,
262
+ version: task.version,
263
+ changedFields: ["context"],
264
+ actor: { kind: "user" }
265
+ });
266
+ return created;
267
+ });
268
+ deps.service.dispatchTaskEvents();
269
+ return c.json({
270
+ ok: true,
271
+ edge
193
272
  }, 201);
194
273
  });
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({
274
+ authenticated.delete("/api/tasks/:id/context/:edgeId", taskRateLimit, (c) => {
275
+ const task = tasks.get(c.req.param("id"));
276
+ const removed = task ? runSqliteWriteTransaction((db) => {
277
+ const didRemove = context.remove(task.id, c.req.param("edgeId"));
278
+ if (didRemove) enqueueTaskChangedEvent(db, {
279
+ taskId: task.id,
280
+ projectId: task.projectId,
281
+ version: task.version,
282
+ changedFields: ["context"],
283
+ actor: { kind: "user" }
284
+ });
285
+ return didRemove;
286
+ }) : false;
287
+ if (removed) deps.service.dispatchTaskEvents();
288
+ return removed ? c.json({ ok: true }) : c.json({
197
289
  ok: false,
198
290
  error: "Task context edge not found"
199
291
  }, 404);
@@ -219,7 +311,7 @@ function registerTaskRoutes(authenticated, deps) {
219
311
  error: "TaskRun not found"
220
312
  }, 404);
221
313
  });
222
- authenticated.post("/api/task-runs/:runId/cancel", deps.strictRateLimitMiddleware, async (c) => {
314
+ authenticated.post("/api/task-runs/:runId/cancel", taskRateLimit, async (c) => {
223
315
  const body = await c.req.json().catch(() => ({}));
224
316
  const run = runs.get(c.req.param("runId"));
225
317
  if (!run) return c.json({
@@ -256,13 +348,14 @@ function registerTaskRoutes(authenticated, deps) {
256
348
  ok: false,
257
349
  error: result.reason
258
350
  }, 409);
351
+ deps.service.dispatchTaskEvents();
259
352
  return c.json({
260
353
  ok: true,
261
354
  run: runs.get(run.id),
262
355
  receipt: runs.getReceipt(run.id)
263
356
  });
264
357
  });
265
- authenticated.post("/api/task-runs/:runId/feedback", deps.strictRateLimitMiddleware, async (c) => {
358
+ authenticated.post("/api/task-runs/:runId/feedback", taskRateLimit, async (c) => {
266
359
  const body = await c.req.json().catch(() => ({}));
267
360
  if (body.rating !== "helpful" && body.rating !== "not_helpful") return c.json({
268
361
  ok: false,
@@ -306,7 +399,7 @@ function registerTaskRoutes(authenticated, deps) {
306
399
  policy: monitoring.get(projectId)
307
400
  });
308
401
  });
309
- authenticated.patch("/api/projects/:projectId/monitoring", deps.strictRateLimitMiddleware, async (c) => {
402
+ authenticated.patch("/api/projects/:projectId/monitoring", taskRateLimit, async (c) => {
310
403
  const projectId = c.req.param("projectId");
311
404
  if (!deps.service.projects.get(projectId)) return c.json({
312
405
  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) => {
@@ -120,6 +120,7 @@ export declare class GatewayService {
120
120
  private createTaskRunDispatcher;
121
121
  private createMobileNotificationService;
122
122
  dispatchTaskRuns(): void;
123
+ dispatchTaskEvents(): void;
123
124
  runAgent(...args: Parameters<GatewayAgentRunner['runAgent']>): ReturnType<GatewayAgentRunner['runAgent']>;
124
125
  submitSessionInput(...args: Parameters<GatewayAgentRunner['submitSessionInput']>): Promise<{
125
126
  ok: true;
@@ -460,6 +460,7 @@ var GatewayService = class {
460
460
  getNotesService: () => this.notesService,
461
461
  getProjectService: () => this.projects,
462
462
  getLocalAppService: () => this.localApps,
463
+ dispatchTaskEvents: () => this.dispatchTaskEvents(),
463
464
  dispatchTaskRuns: () => this.dispatchTaskRuns(),
464
465
  getWorkflowRunService: () => this.createWorkflowRunService(),
465
466
  sourceContextResolver: async (binding) => {
@@ -593,10 +594,13 @@ var GatewayService = class {
593
594
  }
594
595
  dispatchTaskRuns() {
595
596
  new TaskSignalService().tick();
596
- new TaskOutboxDispatcher(publishAutomationProductEvent).drain();
597
+ this.dispatchTaskEvents();
597
598
  this.createTaskRunDispatcher().dispatch();
598
599
  this.createWorkflowRunService().dispatchTaskRuns();
599
600
  }
601
+ dispatchTaskEvents() {
602
+ new TaskOutboxDispatcher(publishAutomationProductEvent).drain();
603
+ }
600
604
  runAgent(...args) {
601
605
  return this.agentRunner.runAgent(...args);
602
606
  }
@@ -0,0 +1,2 @@
1
+ export declare function executePendingProactiveActions(now?: Date): number;
2
+ export declare function resolveProactiveActionDecision(inboxItemId: string, choice: string, now?: Date): void;
@@ -0,0 +1,80 @@
1
+ import { getSqliteDatabase, runSqliteWriteTransaction } from "../../storage/sqlite/transaction.js";
2
+ import { TaskApplicationService } from "../../tasks/task-application-service.js";
3
+ //#region src/proactive/actions/service.ts
4
+ function projectIdFromAggregationKey(value) {
5
+ if (!value.startsWith("project:") || value.length === 8) throw new Error("Proactive action has no project scope");
6
+ return value.slice(8);
7
+ }
8
+ function executeClaimedAction(row, now = /* @__PURE__ */ new Date()) {
9
+ const action = JSON.parse(row.proposed_action_json);
10
+ try {
11
+ if (action.id !== "create_project_task") throw new Error(`Unsupported proactive action: ${action.id}`);
12
+ const result = new TaskApplicationService().create({
13
+ idempotencyKey: `proactive:${row.insight_id}:${action.id}`,
14
+ title: action.input.title,
15
+ projectId: projectIdFromAggregationKey(row.aggregation_key),
16
+ priority: "normal",
17
+ contract: {
18
+ objective: action.input.objective,
19
+ expectedOutputs: [],
20
+ acceptanceCriteria: [],
21
+ constraints: [],
22
+ approvalRequired: [],
23
+ assumptions: [],
24
+ risks: [],
25
+ acceptancePolicy: "manual",
26
+ outputDestinations: []
27
+ },
28
+ dependencies: [],
29
+ context: [],
30
+ authorityGrants: [],
31
+ activation: {
32
+ mode: "capture",
33
+ phase: "backlog"
34
+ }
35
+ }, {
36
+ kind: "system",
37
+ id: "proactive"
38
+ });
39
+ if (!result.ok) throw new Error(`Task creation failed: ${"reason" in result ? result.reason : "unknown"}`);
40
+ getSqliteDatabase().prepare(`UPDATE proactive_insights
41
+ SET action_status = 'completed', action_result_json = ?, action_error = NULL, action_updated_at = ?
42
+ WHERE insight_id = ? AND action_status = 'executing'`).run(JSON.stringify({ taskId: result.model.task.id }), now.toISOString(), row.insight_id);
43
+ } catch (error) {
44
+ getSqliteDatabase().prepare(`UPDATE proactive_insights
45
+ SET action_status = 'failed', action_error = ?, action_updated_at = ?
46
+ WHERE insight_id = ? AND action_status = 'executing'`).run(String(error instanceof Error ? error.message : error).slice(0, 2e3), now.toISOString(), row.insight_id);
47
+ }
48
+ }
49
+ function executePendingProactiveActions(now = /* @__PURE__ */ new Date()) {
50
+ const expired = (/* @__PURE__ */ new Date(now.getTime() - 5 * 6e4)).toISOString();
51
+ getSqliteDatabase().prepare(`UPDATE proactive_insights SET action_status = 'pending', action_updated_at = ?
52
+ WHERE action_status = 'executing' AND action_updated_at <= ?`).run(now.toISOString(), expired);
53
+ let executed = 0;
54
+ while (true) {
55
+ const row = runSqliteWriteTransaction((db) => {
56
+ const candidate = db.prepare(`SELECT x.insight_id, b.aggregation_key, x.proposed_action_json
57
+ FROM proactive_insights x
58
+ JOIN proactive_runs r ON r.run_id = x.run_id
59
+ JOIN proactive_signal_batches b ON b.batch_id = r.batch_id
60
+ WHERE x.action_status = 'pending' ORDER BY x.created_at LIMIT 1`).get();
61
+ if (!candidate) return void 0;
62
+ return db.prepare(`UPDATE proactive_insights SET action_status = 'executing', action_updated_at = ?
63
+ WHERE insight_id = ? AND action_status = 'pending'`).run(now.toISOString(), candidate.insight_id).changes === 1 ? candidate : void 0;
64
+ });
65
+ if (!row) return executed;
66
+ executeClaimedAction(row, now);
67
+ executed += 1;
68
+ }
69
+ }
70
+ function resolveProactiveActionDecision(inboxItemId, choice, now = /* @__PURE__ */ new Date()) {
71
+ const row = getSqliteDatabase().prepare(`SELECT x.insight_id, x.action_status
72
+ FROM proactive_inbox_items i JOIN proactive_insights x ON x.insight_id = i.insight_id
73
+ WHERE i.inbox_item_id = ?`).get(inboxItemId);
74
+ if (!row || row.action_status !== "approval_required") return;
75
+ getSqliteDatabase().prepare(`UPDATE proactive_insights SET action_status = ?, action_updated_at = ?
76
+ WHERE insight_id = ? AND action_status = 'approval_required'`).run(choice === "approve" ? "pending" : "rejected", now.toISOString(), row.insight_id);
77
+ if (choice === "approve") executePendingProactiveActions(now);
78
+ }
79
+ //#endregion
80
+ export { executePendingProactiveActions, resolveProactiveActionDecision };
@@ -30,6 +30,23 @@ function parseDecision(value) {
30
30
  options
31
31
  };
32
32
  }
33
+ function parseProposedAction(value) {
34
+ if (value == null) return void 0;
35
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Insight proposedAction is invalid");
36
+ const row = value;
37
+ if (row.id !== "create_project_task" || row.risk !== "low") throw new Error("Insight proposedAction is not supported");
38
+ if (!row.input || typeof row.input !== "object" || Array.isArray(row.input)) throw new Error("Insight proposedAction input is invalid");
39
+ const input = row.input;
40
+ return {
41
+ id: "create_project_task",
42
+ risk: "low",
43
+ rationale: boundedText(row.rationale, "proposedAction rationale", 600),
44
+ input: {
45
+ title: boundedText(input.title, "proposedAction title", 160),
46
+ objective: boundedText(input.objective, "proposedAction objective", 1200)
47
+ }
48
+ };
49
+ }
33
50
  function parseInsightCandidate(raw, allowedEvidenceIds) {
34
51
  let value;
35
52
  try {
@@ -45,6 +62,8 @@ function parseInsightCandidate(raw, allowedEvidenceIds) {
45
62
  const evidenceIds = [...new Set(row.evidenceIds.map(String))];
46
63
  if (evidenceIds.some((id) => !allowedEvidenceIds.has(id))) throw new Error("Insight cites unknown evidence");
47
64
  const decision = parseDecision(row.decision);
65
+ const proposedAction = parseProposedAction(row.proposedAction);
66
+ if (proposedAction && (!decision || decision.options.length !== 2 || decision.options[0]?.id !== "approve" || decision.options[1]?.id !== "reject")) throw new Error("Insight proposedAction requires approve and reject decision options");
48
67
  return {
49
68
  title: boundedText(row.title, "title", 160),
50
69
  summary: boundedText(row.summary, "summary", 1200),
@@ -53,6 +72,7 @@ function parseInsightCandidate(raw, allowedEvidenceIds) {
53
72
  recommendation: boundedText(row.recommendation, "recommendation", 800),
54
73
  workDone: boundedText(row.workDone, "workDone", 800),
55
74
  ...decision ? { decision } : {},
75
+ ...proposedAction ? { proposedAction } : {},
56
76
  urgency: String(row.urgency),
57
77
  confidence: row.confidence,
58
78
  evidenceIds
@@ -96,9 +96,9 @@ function finishRun(input, now = /* @__PURE__ */ new Date()) {
96
96
  createdAt: nowIso
97
97
  };
98
98
  db.prepare(`INSERT INTO proactive_insights (insight_id, run_id, subscription_id, scenario_key, title, summary,
99
- why_now, impact, recommendation, work_done, decision_json, urgency, confidence, value_score,
99
+ why_now, impact, recommendation, work_done, decision_json, proposed_action_json, urgency, confidence, value_score,
100
100
  evidence_ids_json, content_fingerprint, created_at)
101
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(insight.id, insight.runId, insight.subscriptionId, insight.scenarioKey, insight.title, insight.summary, insight.whyNow, insight.impact, insight.recommendation, insight.workDone, insight.decision ? JSON.stringify(insight.decision) : null, insight.urgency, insight.confidence, insight.valueScore, JSON.stringify(insight.evidenceIds), fingerprint, insight.createdAt);
101
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(insight.id, insight.runId, insight.subscriptionId, insight.scenarioKey, insight.title, insight.summary, insight.whyNow, insight.impact, insight.recommendation, insight.workDone, insight.decision ? JSON.stringify(insight.decision) : null, insight.proposedAction ? JSON.stringify(insight.proposedAction) : null, insight.urgency, insight.confidence, insight.valueScore, JSON.stringify(insight.evidenceIds), fingerprint, insight.createdAt);
102
102
  return insight;
103
103
  });
104
104
  }
@@ -124,9 +124,15 @@ function listInsights(limit = 50) {
124
124
  recommendation: str(row, "recommendation"),
125
125
  workDone: str(row, "work_done"),
126
126
  ...row.decision_json ? { decision: JSON.parse(str(row, "decision_json")) } : {},
127
+ ...row.proposed_action_json ? { proposedAction: JSON.parse(str(row, "proposed_action_json")) } : {},
127
128
  urgency: str(row, "urgency"),
128
129
  confidence: Number(row.confidence),
129
130
  valueScore: Number(row.value_score),
131
+ ...row.disposition ? { disposition: str(row, "disposition") } : {},
132
+ ...row.disposition_reason ? { dispositionReason: str(row, "disposition_reason") } : {},
133
+ ...row.action_status ? { actionStatus: str(row, "action_status") } : {},
134
+ ...row.action_result_json ? { actionResult: JSON.parse(str(row, "action_result_json")) } : {},
135
+ ...row.action_error ? { actionError: str(row, "action_error") } : {},
130
136
  evidenceIds: JSON.parse(str(row, "evidence_ids_json")),
131
137
  createdAt: str(row, "created_at")
132
138
  }));