@penclipai/server 2026.426.0 → 2026.505.0

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 (253) hide show
  1. package/dist/adapters/builtin-adapter-types.d.ts.map +1 -1
  2. package/dist/adapters/builtin-adapter-types.js +3 -0
  3. package/dist/adapters/builtin-adapter-types.js.map +1 -1
  4. package/dist/adapters/index.d.ts +2 -2
  5. package/dist/adapters/index.d.ts.map +1 -1
  6. package/dist/adapters/index.js +1 -1
  7. package/dist/adapters/index.js.map +1 -1
  8. package/dist/adapters/registry.d.ts +2 -1
  9. package/dist/adapters/registry.d.ts.map +1 -1
  10. package/dist/adapters/registry.js +76 -6
  11. package/dist/adapters/registry.js.map +1 -1
  12. package/dist/adapters/types.d.ts +1 -1
  13. package/dist/adapters/types.d.ts.map +1 -1
  14. package/dist/adapters/utils.d.ts.map +1 -1
  15. package/dist/adapters/utils.js +2 -1
  16. package/dist/adapters/utils.js.map +1 -1
  17. package/dist/attachment-types.d.ts +1 -16
  18. package/dist/attachment-types.d.ts.map +1 -1
  19. package/dist/attachment-types.js +7 -0
  20. package/dist/attachment-types.js.map +1 -1
  21. package/dist/auth/better-auth.d.ts +3 -1
  22. package/dist/auth/better-auth.d.ts.map +1 -1
  23. package/dist/auth/better-auth.js +8 -2
  24. package/dist/auth/better-auth.js.map +1 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +27 -13
  27. package/dist/index.js.map +1 -1
  28. package/dist/middleware/auth.d.ts.map +1 -1
  29. package/dist/middleware/auth.js +143 -2
  30. package/dist/middleware/auth.js.map +1 -1
  31. package/dist/onboarding-assets/ceo/AGENTS.md +1 -1
  32. package/dist/onboarding-assets/ceo/HEARTBEAT.md +5 -5
  33. package/dist/redaction.d.ts.map +1 -1
  34. package/dist/redaction.js +30 -12
  35. package/dist/redaction.js.map +1 -1
  36. package/dist/routes/access.d.ts.map +1 -1
  37. package/dist/routes/access.js +10 -0
  38. package/dist/routes/access.js.map +1 -1
  39. package/dist/routes/activity.d.ts.map +1 -1
  40. package/dist/routes/activity.js +4 -2
  41. package/dist/routes/activity.js.map +1 -1
  42. package/dist/routes/adapters.d.ts.map +1 -1
  43. package/dist/routes/adapters.js +1 -0
  44. package/dist/routes/adapters.js.map +1 -1
  45. package/dist/routes/agents.d.ts.map +1 -1
  46. package/dist/routes/agents.js +317 -56
  47. package/dist/routes/agents.js.map +1 -1
  48. package/dist/routes/costs.d.ts.map +1 -1
  49. package/dist/routes/costs.js +21 -2
  50. package/dist/routes/costs.js.map +1 -1
  51. package/dist/routes/instance-settings.d.ts.map +1 -1
  52. package/dist/routes/instance-settings.js +37 -2
  53. package/dist/routes/instance-settings.js.map +1 -1
  54. package/dist/routes/issue-tree-control.d.ts.map +1 -1
  55. package/dist/routes/issue-tree-control.js +3 -1
  56. package/dist/routes/issue-tree-control.js.map +1 -1
  57. package/dist/routes/issues.d.ts.map +1 -1
  58. package/dist/routes/issues.js +257 -32
  59. package/dist/routes/issues.js.map +1 -1
  60. package/dist/routes/projects.d.ts.map +1 -1
  61. package/dist/routes/projects.js +10 -3
  62. package/dist/routes/projects.js.map +1 -1
  63. package/dist/routes/routines.d.ts.map +1 -1
  64. package/dist/routes/routines.js +6 -1
  65. package/dist/routes/routines.js.map +1 -1
  66. package/dist/routes/workspace-command-authz.d.ts +1 -1
  67. package/dist/routes/workspace-command-authz.d.ts.map +1 -1
  68. package/dist/routes/workspace-command-authz.js +2 -2
  69. package/dist/routes/workspace-command-authz.js.map +1 -1
  70. package/dist/runtime-api.d.ts +4 -0
  71. package/dist/runtime-api.d.ts.map +1 -1
  72. package/dist/runtime-api.js +38 -10
  73. package/dist/runtime-api.js.map +1 -1
  74. package/dist/services/companies.d.ts +6 -0
  75. package/dist/services/companies.d.ts.map +1 -1
  76. package/dist/services/companies.js +1 -0
  77. package/dist/services/companies.js.map +1 -1
  78. package/dist/services/company-portability.d.ts.map +1 -1
  79. package/dist/services/company-portability.js +16 -15
  80. package/dist/services/company-portability.js.map +1 -1
  81. package/dist/services/costs.d.ts +9 -0
  82. package/dist/services/costs.d.ts.map +1 -1
  83. package/dist/services/costs.js +45 -1
  84. package/dist/services/costs.js.map +1 -1
  85. package/dist/services/environment-execution-target.d.ts.map +1 -1
  86. package/dist/services/environment-execution-target.js +7 -13
  87. package/dist/services/environment-execution-target.js.map +1 -1
  88. package/dist/services/environment-run-orchestrator.d.ts.map +1 -1
  89. package/dist/services/environment-run-orchestrator.js +56 -0
  90. package/dist/services/environment-run-orchestrator.js.map +1 -1
  91. package/dist/services/environment-runtime.d.ts +2 -0
  92. package/dist/services/environment-runtime.d.ts.map +1 -1
  93. package/dist/services/environment-runtime.js +80 -39
  94. package/dist/services/environment-runtime.js.map +1 -1
  95. package/dist/services/heartbeat-stop-metadata.d.ts +2 -1
  96. package/dist/services/heartbeat-stop-metadata.d.ts.map +1 -1
  97. package/dist/services/heartbeat-stop-metadata.js +10 -1
  98. package/dist/services/heartbeat-stop-metadata.js.map +1 -1
  99. package/dist/services/heartbeat-stop-metadata.test.js +24 -0
  100. package/dist/services/heartbeat-stop-metadata.test.js.map +1 -1
  101. package/dist/services/heartbeat.d.ts +156 -5
  102. package/dist/services/heartbeat.d.ts.map +1 -1
  103. package/dist/services/heartbeat.js +1384 -112
  104. package/dist/services/heartbeat.js.map +1 -1
  105. package/dist/services/index.d.ts +1 -0
  106. package/dist/services/index.d.ts.map +1 -1
  107. package/dist/services/index.js +1 -0
  108. package/dist/services/index.js.map +1 -1
  109. package/dist/services/instance-settings.d.ts.map +1 -1
  110. package/dist/services/instance-settings.js +4 -1
  111. package/dist/services/instance-settings.js.map +1 -1
  112. package/dist/services/issue-execution-policy.d.ts +56 -1
  113. package/dist/services/issue-execution-policy.d.ts.map +1 -1
  114. package/dist/services/issue-execution-policy.js +400 -2
  115. package/dist/services/issue-execution-policy.js.map +1 -1
  116. package/dist/services/issue-thread-interactions.d.ts +5 -1
  117. package/dist/services/issue-thread-interactions.d.ts.map +1 -1
  118. package/dist/services/issue-thread-interactions.js +44 -1
  119. package/dist/services/issue-thread-interactions.js.map +1 -1
  120. package/dist/services/issue-tree-control.d.ts +1 -0
  121. package/dist/services/issue-tree-control.d.ts.map +1 -1
  122. package/dist/services/issue-tree-control.js +84 -4
  123. package/dist/services/issue-tree-control.js.map +1 -1
  124. package/dist/services/issues.d.ts +10 -1
  125. package/dist/services/issues.d.ts.map +1 -1
  126. package/dist/services/issues.js +452 -48
  127. package/dist/services/issues.js.map +1 -1
  128. package/dist/services/plugin-environment-driver.d.ts +4 -0
  129. package/dist/services/plugin-environment-driver.d.ts.map +1 -1
  130. package/dist/services/plugin-environment-driver.js +18 -1
  131. package/dist/services/plugin-environment-driver.js.map +1 -1
  132. package/dist/services/productivity-review.d.ts +83 -0
  133. package/dist/services/productivity-review.d.ts.map +1 -0
  134. package/dist/services/productivity-review.js +650 -0
  135. package/dist/services/productivity-review.js.map +1 -0
  136. package/dist/services/recovery/index.d.ts +1 -1
  137. package/dist/services/recovery/index.d.ts.map +1 -1
  138. package/dist/services/recovery/index.js +1 -1
  139. package/dist/services/recovery/index.js.map +1 -1
  140. package/dist/services/recovery/issue-graph-liveness.d.ts +13 -1
  141. package/dist/services/recovery/issue-graph-liveness.d.ts.map +1 -1
  142. package/dist/services/recovery/issue-graph-liveness.js +212 -92
  143. package/dist/services/recovery/issue-graph-liveness.js.map +1 -1
  144. package/dist/services/recovery/origins.d.ts +2 -0
  145. package/dist/services/recovery/origins.d.ts.map +1 -1
  146. package/dist/services/recovery/origins.js +4 -0
  147. package/dist/services/recovery/origins.js.map +1 -1
  148. package/dist/services/recovery/run-liveness-continuations.d.ts.map +1 -1
  149. package/dist/services/recovery/run-liveness-continuations.js.map +1 -1
  150. package/dist/services/recovery/service.d.ts +20 -2
  151. package/dist/services/recovery/service.d.ts.map +1 -1
  152. package/dist/services/recovery/service.js +405 -63
  153. package/dist/services/recovery/service.js.map +1 -1
  154. package/dist/services/routines.d.ts +5 -2
  155. package/dist/services/routines.d.ts.map +1 -1
  156. package/dist/services/routines.js +47 -3
  157. package/dist/services/routines.js.map +1 -1
  158. package/dist/worktree-config.d.ts.map +1 -1
  159. package/dist/worktree-config.js +2 -5
  160. package/dist/worktree-config.js.map +1 -1
  161. package/package.json +16 -15
  162. package/skills/diagnose-why-work-stopped/SKILL.md +161 -0
  163. package/skills/paperclip/SKILL.md +37 -26
  164. package/skills/paperclip/references/api-reference.md +6 -2
  165. package/skills/paperclip-converting-plans-to-tasks/SKILL.md +42 -0
  166. package/skills/paperclip-create-agent/SKILL.md +3 -2
  167. package/skills/paperclip-create-agent/references/agent-instruction-templates.md +1 -1
  168. package/skills/paperclip-create-agent/references/api-reference.md +7 -2
  169. package/skills/paperclip-create-agent/references/baseline-role-guide.md +1 -1
  170. package/skills/paperclip-create-agent/references/draft-review-checklist.md +2 -2
  171. package/skills/paperclip-dev/SKILL.md +267 -0
  172. package/skills/terminal-bench-loop/SKILL.md +236 -0
  173. package/ui-dist/assets/{_basePickBy-BRqa7PJ5.js → _basePickBy-BS0Fg_DB.js} +1 -1
  174. package/ui-dist/assets/{_baseUniq-DhE2yrXC.js → _baseUniq-Dtnt_4SE.js} +1 -1
  175. package/ui-dist/assets/{arc-7qnikTQ3.js → arc-BCoOPxh5.js} +1 -1
  176. package/ui-dist/assets/{architectureDiagram-VXUJARFQ-CH0wVUOM.js → architectureDiagram-VXUJARFQ-C6eX2QUo.js} +1 -1
  177. package/ui-dist/assets/{blockDiagram-VD42YOAC-CeeRyJQX.js → blockDiagram-VD42YOAC-aUueUD4B.js} +1 -1
  178. package/ui-dist/assets/browser-ponyfill-BlAfsWm_.js +2 -0
  179. package/ui-dist/assets/{c4Diagram-YG6GDRKO-C_cV0CGo.js → c4Diagram-YG6GDRKO-CfPWRlOF.js} +1 -1
  180. package/ui-dist/assets/channel-ChNSCFJf.js +1 -0
  181. package/ui-dist/assets/{chunk-4BX2VUAB-DQ6pxPVT.js → chunk-4BX2VUAB-BTD1apA4.js} +1 -1
  182. package/ui-dist/assets/{chunk-55IACEB6-L8pS0IoX.js → chunk-55IACEB6-BXXF_ClN.js} +1 -1
  183. package/ui-dist/assets/{chunk-B4BG7PRW-BZKGE88E.js → chunk-B4BG7PRW-hAZeWGP8.js} +1 -1
  184. package/ui-dist/assets/{chunk-DI55MBZ5-CefSoZ_K.js → chunk-DI55MBZ5-cOH3UoEl.js} +1 -1
  185. package/ui-dist/assets/{chunk-FMBD7UC4-Bc3qTTHB.js → chunk-FMBD7UC4-Cu2yZOcl.js} +1 -1
  186. package/ui-dist/assets/{chunk-QN33PNHL-CjWBr5bI.js → chunk-QN33PNHL-0DNN5aRU.js} +1 -1
  187. package/ui-dist/assets/{chunk-QZHKN3VN-C0JUdmmz.js → chunk-QZHKN3VN-B9_bhK2n.js} +1 -1
  188. package/ui-dist/assets/{chunk-TZMSLE5B-D4d4I82z.js → chunk-TZMSLE5B-Cr5xwxio.js} +1 -1
  189. package/ui-dist/assets/classDiagram-2ON5EDUG-4aK1QZU3.js +1 -0
  190. package/ui-dist/assets/classDiagram-v2-WZHVMYZB-4aK1QZU3.js +1 -0
  191. package/ui-dist/assets/clone-C8lk5Qbc.js +1 -0
  192. package/ui-dist/assets/{cose-bilkent-S5V4N54A-B09h9XGZ.js → cose-bilkent-S5V4N54A-6_Dw6gpQ.js} +1 -1
  193. package/ui-dist/assets/{dagre-6UL2VRFP-CA02PXuX.js → dagre-6UL2VRFP-CFBhlh5H.js} +1 -1
  194. package/ui-dist/assets/{diagram-PSM6KHXK-DaT9cnrY.js → diagram-PSM6KHXK-C88ftcah.js} +1 -1
  195. package/ui-dist/assets/{diagram-QEK2KX5R-Drwc3gBw.js → diagram-QEK2KX5R-9EUupcuH.js} +1 -1
  196. package/ui-dist/assets/{diagram-S2PKOQOG-CpsGCaT6.js → diagram-S2PKOQOG-Dsml0wWh.js} +1 -1
  197. package/ui-dist/assets/{erDiagram-Q2GNP2WA-CVkBh9TY.js → erDiagram-Q2GNP2WA-sM-XdfHS.js} +1 -1
  198. package/ui-dist/assets/{flowDiagram-NV44I4VS-De9sXvPR.js → flowDiagram-NV44I4VS-qll7oaoW.js} +1 -1
  199. package/ui-dist/assets/{ganttDiagram-JELNMOA3-CSFa0gXS.js → ganttDiagram-JELNMOA3-VWnJMcjC.js} +1 -1
  200. package/ui-dist/assets/{gitGraphDiagram-V2S2FVAM-DEJaChxa.js → gitGraphDiagram-V2S2FVAM-DFnocrfl.js} +1 -1
  201. package/ui-dist/assets/{graph-D2R4DCtu.js → graph-nq3Qye4Z.js} +1 -1
  202. package/ui-dist/assets/{index-DEG-9CFs.js → index-3Owzaheh.js} +1 -1
  203. package/ui-dist/assets/{index-DHnKx9xX.js → index-B2A-a635.js} +1 -1
  204. package/ui-dist/assets/{index-C1I0SGDm.js → index-BGFrRiqa.js} +1 -1
  205. package/ui-dist/assets/{index-B44EtLRv.js → index-BVC5UhRK.js} +1 -1
  206. package/ui-dist/assets/{index-C_dAXwxT.js → index-BrP1U_Hy.js} +1 -1
  207. package/ui-dist/assets/{index-flZjKn_n.js → index-CXXHGqM8.js} +1 -1
  208. package/ui-dist/assets/{index-ssM_UKPW.js → index-CgyPAauR.js} +1 -1
  209. package/ui-dist/assets/{index-Ct1AraKR.js → index-CksQ4Ytv.js} +1 -1
  210. package/ui-dist/assets/{index-DQ6I_vpd.js → index-CrNzj2vZ.js} +1 -1
  211. package/ui-dist/assets/{index-DzZID5RY.js → index-CxbZBH3M.js} +1 -1
  212. package/ui-dist/assets/{index-Cn6_RRY5.js → index-D-dSSrf-.js} +1 -1
  213. package/ui-dist/assets/{index-CVa2OHgx.js → index-D6uZ_7Vh.js} +1 -1
  214. package/ui-dist/assets/{index-BzjWQd50.js → index-D7JGmxas.js} +1 -1
  215. package/ui-dist/assets/{index-CnT1_9UF.js → index-DDqO9GAq.js} +1 -1
  216. package/ui-dist/assets/index-DEUtmlPm.js +513 -0
  217. package/ui-dist/assets/{index-D2fEhyQg.js → index-DF5RDSoK.js} +1 -1
  218. package/ui-dist/assets/{index-CZGNe8K3.js → index-DfI92epU.js} +1 -1
  219. package/ui-dist/assets/{index-ByamXtyB.js → index-Dukb9MDQ.js} +1 -1
  220. package/ui-dist/assets/index-HP73_6Vr.css +1 -0
  221. package/ui-dist/assets/{index-BJS4rvUh.js → index-NXDTW2n4.js} +1 -1
  222. package/ui-dist/assets/{index-Bad5Hy7e.js → index-SxPPG9ig.js} +1 -1
  223. package/ui-dist/assets/{index-CC51mhhA.js → index-lC4Yz3Gw.js} +1 -1
  224. package/ui-dist/assets/{index-BFzkl36p.js → index-q2RXGI2V.js} +1 -1
  225. package/ui-dist/assets/{index-40icqWwg.js → index-qjfdrS96.js} +1 -1
  226. package/ui-dist/assets/{infoDiagram-HS3SLOUP-CJcjzWkM.js → infoDiagram-HS3SLOUP-CTrK5xoS.js} +1 -1
  227. package/ui-dist/assets/{journeyDiagram-XKPGCS4Q-ByITI00s.js → journeyDiagram-XKPGCS4Q-YFC7FykG.js} +1 -1
  228. package/ui-dist/assets/{kanban-definition-3W4ZIXB7-DvEjKke-.js → kanban-definition-3W4ZIXB7-B3dlyva0.js} +1 -1
  229. package/ui-dist/assets/{layout-CZcd66hi.js → layout-DefunPTK.js} +1 -1
  230. package/ui-dist/assets/{linear-jTUy3iHu.js → linear-CIPvzeMv.js} +1 -1
  231. package/ui-dist/assets/{mermaid.core-DECSZPbJ.js → mermaid.core-zKYhmnnR.js} +4 -4
  232. package/ui-dist/assets/{mindmap-definition-VGOIOE7T-Twtu17_c.js → mindmap-definition-VGOIOE7T-BlU-ebRa.js} +1 -1
  233. package/ui-dist/assets/{pieDiagram-ADFJNKIX-DlbgZ010.js → pieDiagram-ADFJNKIX-Ceto4LXH.js} +1 -1
  234. package/ui-dist/assets/{quadrantDiagram-AYHSOK5B-CMAa3qAT.js → quadrantDiagram-AYHSOK5B-C6M6hkuE.js} +1 -1
  235. package/ui-dist/assets/{requirementDiagram-UZGBJVZJ-CXRTfJOe.js → requirementDiagram-UZGBJVZJ-B-bcG938.js} +1 -1
  236. package/ui-dist/assets/{sankeyDiagram-TZEHDZUN-DeyO4fer.js → sankeyDiagram-TZEHDZUN-CIqty6Qi.js} +1 -1
  237. package/ui-dist/assets/{sequenceDiagram-WL72ISMW-Ch8wlJIL.js → sequenceDiagram-WL72ISMW-CIt2R5tk.js} +1 -1
  238. package/ui-dist/assets/{stateDiagram-FKZM4ZOC-BgL_AAl9.js → stateDiagram-FKZM4ZOC-BC1RFlfg.js} +1 -1
  239. package/ui-dist/assets/stateDiagram-v2-4FDKWEC3-Iy6tYSSw.js +1 -0
  240. package/ui-dist/assets/{timeline-definition-IT6M3QCI-D1QWd7TQ.js → timeline-definition-IT6M3QCI-DZqvoU94.js} +1 -1
  241. package/ui-dist/assets/{treemap-GDKQZRPO-B5RkmUv8.js → treemap-GDKQZRPO-CSeKauwA.js} +1 -1
  242. package/ui-dist/assets/{xychartDiagram-PRI3JC2R-WtDhjZfk.js → xychartDiagram-PRI3JC2R-Ut3mCiEd.js} +1 -1
  243. package/ui-dist/index.html +2 -2
  244. package/ui-dist/locales/en/common.json +137 -1
  245. package/ui-dist/locales/zh-CN/common.json +111 -1
  246. package/ui-dist/assets/browser-ponyfill-Ct3hGqsr.js +0 -2
  247. package/ui-dist/assets/channel-pHFjGZL-.js +0 -1
  248. package/ui-dist/assets/classDiagram-2ON5EDUG-X4ZksqXl.js +0 -1
  249. package/ui-dist/assets/classDiagram-v2-WZHVMYZB-X4ZksqXl.js +0 -1
  250. package/ui-dist/assets/clone-DZzimpfG.js +0 -1
  251. package/ui-dist/assets/index-C1oE3J7o.css +0 -1
  252. package/ui-dist/assets/index-fSIlEIHr.js +0 -510
  253. package/ui-dist/assets/stateDiagram-v2-4FDKWEC3-gnLzrhSv.js +0 -1
@@ -3,18 +3,18 @@ import { Router } from "express";
3
3
  import multer from "multer";
4
4
  import { z } from "zod";
5
5
  import { issueExecutionDecisions } from "@penclipai/db";
6
- import { addIssueCommentSchema, acceptIssueThreadInteractionSchema, createIssueAttachmentMetadataSchema, createIssueThreadInteractionSchema, createIssueWorkProductSchema, createIssueLabelSchema, checkoutIssueSchema, createChildIssueSchema, createIssueSchema, feedbackTargetTypeSchema, feedbackTraceStatusSchema, feedbackVoteValueSchema, upsertIssueFeedbackVoteSchema, linkIssueApprovalSchema, issueDocumentKeySchema, ISSUE_CONTINUATION_SUMMARY_DOCUMENT_KEY, rejectIssueThreadInteractionSchema, restoreIssueDocumentRevisionSchema, respondIssueThreadInteractionSchema, updateIssueWorkProductSchema, upsertIssueDocumentSchema, updateIssueSchema, getClosedIsolatedExecutionWorkspaceMessage, isClosedIsolatedExecutionWorkspace, } from "@penclipai/shared";
6
+ import { addIssueCommentSchema, acceptIssueThreadInteractionSchema, cancelIssueThreadInteractionSchema, createIssueAttachmentMetadataSchema, createIssueThreadInteractionSchema, createIssueWorkProductSchema, createIssueLabelSchema, checkoutIssueSchema, createChildIssueSchema, createIssueSchema, feedbackTargetTypeSchema, feedbackTraceStatusSchema, feedbackVoteValueSchema, upsertIssueFeedbackVoteSchema, linkIssueApprovalSchema, issueDocumentKeySchema, ISSUE_CONTINUATION_SUMMARY_DOCUMENT_KEY, rejectIssueThreadInteractionSchema, restoreIssueDocumentRevisionSchema, respondIssueThreadInteractionSchema, updateIssueWorkProductSchema, upsertIssueDocumentSchema, updateIssueSchema, getClosedIsolatedExecutionWorkspaceMessage, isClosedIsolatedExecutionWorkspace, normalizeIssueIdentifier as normalizeIssueReferenceIdentifier, } from "@penclipai/shared";
7
7
  import { getTelemetryClient } from "../telemetry.js";
8
8
  import { trackAgentTaskCompleted } from "@penclipai/shared/telemetry";
9
9
  import { validate } from "../middleware/validate.js";
10
10
  import * as serviceIndex from "../services/index.js";
11
- import { accessService, agentService, goalService, heartbeatService, issueApprovalService, issueThreadInteractionService, ISSUE_LIST_DEFAULT_LIMIT, ISSUE_LIST_MAX_LIMIT, issueReferenceService, issueService, clampIssueListLimit, documentService, logActivity, projectService, routineService, workProductService, } from "../services/index.js";
11
+ import { accessService, agentService, companyService, goalService, heartbeatService, issueApprovalService, issueThreadInteractionService, ISSUE_LIST_DEFAULT_LIMIT, ISSUE_LIST_MAX_LIMIT, issueReferenceService, issueService, clampIssueListLimit, documentService, logActivity, projectService, routineService, workProductService, } from "../services/index.js";
12
12
  import { logger } from "../middleware/logger.js";
13
13
  import { conflict, forbidden, HttpError, notFound, unauthorized } from "../errors.js";
14
14
  import { assertBoard, assertCompanyAccess, getActorInfo } from "./authz.js";
15
15
  import { assertNoAgentHostWorkspaceCommandMutation, collectIssueWorkspaceCommandPaths, } from "./workspace-command-authz.js";
16
16
  import { shouldWakeAssigneeOnCheckout } from "./issues-checkout-wakeup.js";
17
- import { isInlineAttachmentContentType, MAX_ATTACHMENT_BYTES, normalizeContentType, SVG_CONTENT_TYPE, } from "../attachment-types.js";
17
+ import { isInlineAttachmentContentType, normalizeIssueAttachmentMaxBytes, normalizeContentType, SVG_CONTENT_TYPE, } from "../attachment-types.js";
18
18
  import { queueIssueAssignmentWakeup } from "../services/issue-assignment-wakeup.js";
19
19
  import { resolveExplicitRequestUiLocale } from "../ui-locale.js";
20
20
  import { assertEnvironmentSelectionForCompany } from "./environment-selection.js";
@@ -22,7 +22,7 @@ import { executionWorkspaceService as executionWorkspaceServiceDirect } from "..
22
22
  import { feedbackService } from "../services/feedback.js";
23
23
  import { instanceSettingsService } from "../services/instance-settings.js";
24
24
  import { environmentService } from "../services/environments.js";
25
- import { applyIssueExecutionPolicyTransition, normalizeIssueExecutionPolicy, parseIssueExecutionState, } from "../services/issue-execution-policy.js";
25
+ import { applyIssueExecutionPolicyTransition, normalizeIssueExecutionPolicy, parseIssueExecutionState, redactIssueMonitorExternalRef, setIssueExecutionPolicyMonitorScheduledBy, } from "../services/issue-execution-policy.js";
26
26
  const MAX_ISSUE_COMMENT_LIMIT = 500;
27
27
  const updateIssueRouteSchema = updateIssueSchema.extend({
28
28
  interrupt: z.boolean().optional(),
@@ -73,6 +73,39 @@ function summarizeIssueReferenceActivityDetails(input) {
73
73
  ...(input.currentReferencedIssues.length > 0 ? { currentReferencedIssues: input.currentReferencedIssues } : {}),
74
74
  };
75
75
  }
76
+ function monitorPoliciesEqual(left, right) {
77
+ return JSON.stringify(left?.monitor ?? null) === JSON.stringify(right?.monitor ?? null);
78
+ }
79
+ function applyActorMonitorScheduledBy(policy, actorType) {
80
+ return setIssueExecutionPolicyMonitorScheduledBy(policy, actorType === "user" ? "board" : "assignee");
81
+ }
82
+ function assertCanManageIssueMonitor(req, assigneeAgentId, monitorChanged) {
83
+ if (!monitorChanged)
84
+ return;
85
+ if (req.actor.type === "board")
86
+ return;
87
+ if (req.actor.type === "agent" && req.actor.agentId && req.actor.agentId === assigneeAgentId)
88
+ return;
89
+ throw forbidden("Only the assignee agent or a board user can manage issue monitors");
90
+ }
91
+ function summarizeIssueMonitor(issue, policy) {
92
+ const state = parseIssueExecutionState(issue.executionState);
93
+ return {
94
+ nextCheckAt: issue.monitorNextCheckAt?.toISOString() ?? policy?.monitor?.nextCheckAt ?? null,
95
+ lastTriggeredAt: issue.monitorLastTriggeredAt?.toISOString() ?? state?.monitor?.lastTriggeredAt ?? null,
96
+ attemptCount: issue.monitorAttemptCount ?? state?.monitor?.attemptCount ?? 0,
97
+ notes: policy?.monitor?.notes ?? issue.monitorNotes ?? state?.monitor?.notes ?? null,
98
+ scheduledBy: issue.monitorScheduledBy ?? policy?.monitor?.scheduledBy ?? state?.monitor?.scheduledBy ?? null,
99
+ kind: policy?.monitor?.kind ?? state?.monitor?.kind ?? null,
100
+ serviceName: policy?.monitor?.serviceName ?? state?.monitor?.serviceName ?? null,
101
+ externalRef: redactIssueMonitorExternalRef(policy?.monitor?.externalRef ?? state?.monitor?.externalRef ?? null),
102
+ timeoutAt: policy?.monitor?.timeoutAt ?? state?.monitor?.timeoutAt ?? null,
103
+ maxAttempts: policy?.monitor?.maxAttempts ?? state?.monitor?.maxAttempts ?? null,
104
+ recoveryPolicy: policy?.monitor?.recoveryPolicy ?? state?.monitor?.recoveryPolicy ?? null,
105
+ status: state?.monitor?.status ?? (policy?.monitor ? "scheduled" : null),
106
+ clearReason: state?.monitor?.clearReason ?? null,
107
+ };
108
+ }
76
109
  function activityExecutionParticipantKey(participant) {
77
110
  return participant.type === "agent" ? `agent:${participant.agentId}` : `user:${participant.userId}`;
78
111
  }
@@ -251,6 +284,7 @@ export function issueRoutes(db, storage, opts = {}) {
251
284
  pluginWorkerManager: opts.pluginWorkerManager,
252
285
  });
253
286
  const feedback = feedbackService(db);
287
+ const companiesSvc = companyService(db);
254
288
  const instanceSettings = instanceSettingsService(db);
255
289
  const agentsSvc = agentService(db);
256
290
  const projectsSvc = projectService(db);
@@ -271,10 +305,6 @@ export function issueRoutes(db, storage, opts = {}) {
271
305
  };
272
306
  const feedbackExportService = opts?.feedbackExportService;
273
307
  const environmentsSvc = environmentService(db);
274
- const upload = multer({
275
- storage: multer.memoryStorage(),
276
- limits: { fileSize: MAX_ATTACHMENT_BYTES, files: 1 },
277
- });
278
308
  function withContentPath(attachment) {
279
309
  return {
280
310
  ...attachment,
@@ -323,7 +353,11 @@ export function issueRoutes(db, storage, opts = {}) {
323
353
  function translateMessage(req, key) {
324
354
  return typeof req.t === "function" ? req.t(key) : key;
325
355
  }
326
- async function runSingleFileUpload(req, res) {
356
+ async function runSingleFileUpload(req, res, fileSizeLimit) {
357
+ const upload = multer({
358
+ storage: multer.memoryStorage(),
359
+ limits: { fileSize: fileSizeLimit, files: 1 },
360
+ });
327
361
  await new Promise((resolve, reject) => {
328
362
  upload.single("file")(req, res, (err) => {
329
363
  if (err)
@@ -431,23 +465,40 @@ export function issueRoutes(db, storage, opts = {}) {
431
465
  res.status(403).json({ error: "Agent authentication required" });
432
466
  return false;
433
467
  }
434
- if (issue.status !== "in_progress" || issue.assigneeAgentId === null) {
468
+ if (issue.assigneeAgentId === null) {
435
469
  return true;
436
470
  }
437
471
  if (issue.assigneeAgentId !== actorAgentId) {
438
472
  if (await hasActiveCheckoutManagementOverride(actorAgentId, issue.companyId, issue.assigneeAgentId)) {
439
473
  return true;
440
474
  }
441
- res.status(409).json({
442
- error: "Issue is checked out by another agent",
443
- details: {
444
- issueId: issue.id,
445
- assigneeAgentId: issue.assigneeAgentId,
446
- actorAgentId,
447
- },
448
- });
475
+ if (issue.status === "in_progress") {
476
+ res.status(409).json({
477
+ error: "Issue is checked out by another agent",
478
+ details: {
479
+ issueId: issue.id,
480
+ assigneeAgentId: issue.assigneeAgentId,
481
+ actorAgentId,
482
+ },
483
+ });
484
+ }
485
+ else {
486
+ res.status(403).json({
487
+ error: "Agent cannot mutate another agent's issue",
488
+ details: {
489
+ issueId: issue.id,
490
+ assigneeAgentId: issue.assigneeAgentId,
491
+ actorAgentId,
492
+ status: issue.status,
493
+ securityPrinciples: ["Least Privilege", "Complete Mediation", "Fail Securely"],
494
+ },
495
+ });
496
+ }
449
497
  return false;
450
498
  }
499
+ if (issue.status !== "in_progress") {
500
+ return true;
501
+ }
451
502
  const runId = requireAgentRunId(req, res);
452
503
  if (!runId)
453
504
  return false;
@@ -479,7 +530,6 @@ export function issueRoutes(db, storage, opts = {}) {
479
530
  details: {
480
531
  issueId: issue.id,
481
532
  status: issue.status,
482
- securityPrinciples: ["Complete Mediation", "Fail Securely"],
483
533
  },
484
534
  });
485
535
  return false;
@@ -500,7 +550,6 @@ export function issueRoutes(db, storage, opts = {}) {
500
550
  holdId: activePauseHold.holdId,
501
551
  rootIssueId: activePauseHold.rootIssueId,
502
552
  mode: activePauseHold.mode,
503
- securityPrinciples: ["Complete Mediation", "Fail Securely", "Secure Defaults"],
504
553
  },
505
554
  });
506
555
  return false;
@@ -609,9 +658,10 @@ export function issueRoutes(db, storage, opts = {}) {
609
658
  executionWorkspace: workspace,
610
659
  });
611
660
  }
612
- async function normalizeIssueIdentifier(rawId) {
613
- if (/^[A-Z]+-\d+$/i.test(rawId)) {
614
- const issue = await svc.getByIdentifier(rawId);
661
+ async function resolveIssueRouteId(rawId) {
662
+ const identifier = normalizeIssueReferenceIdentifier(rawId);
663
+ if (identifier) {
664
+ const issue = await svc.getByIdentifier(identifier);
615
665
  if (issue) {
616
666
  return issue.id;
617
667
  }
@@ -639,7 +689,7 @@ export function issueRoutes(db, storage, opts = {}) {
639
689
  // Resolve issue identifiers (e.g. "PAP-39") to UUIDs for all /issues/:id routes
640
690
  router.param("id", async (req, res, next, rawId) => {
641
691
  try {
642
- req.params.id = await normalizeIssueIdentifier(rawId);
692
+ req.params.id = await resolveIssueRouteId(rawId);
643
693
  next();
644
694
  }
645
695
  catch (err) {
@@ -649,7 +699,7 @@ export function issueRoutes(db, storage, opts = {}) {
649
699
  // Resolve issue identifiers (e.g. "PAP-39") to UUIDs for company-scoped attachment routes.
650
700
  router.param("issueId", async (req, res, next, rawId) => {
651
701
  try {
652
- req.params.issueId = await normalizeIssueIdentifier(rawId);
702
+ req.params.issueId = await resolveIssueRouteId(rawId);
653
703
  next();
654
704
  }
655
705
  catch (err) {
@@ -687,6 +737,10 @@ export function issueRoutes(db, storage, opts = {}) {
687
737
  ? Number.parseInt(rawLimit, 10)
688
738
  : null;
689
739
  const limit = parsedLimit === null ? ISSUE_LIST_DEFAULT_LIMIT : clampIssueListLimit(parsedLimit);
740
+ const rawOffset = req.query.offset;
741
+ const parsedOffset = rawOffset !== undefined && /^\d+$/.test(rawOffset)
742
+ ? Number.parseInt(rawOffset, 10)
743
+ : null;
690
744
  if (assigneeUserFilterRaw === "me" && (!assigneeUserId || req.actor.type !== "board")) {
691
745
  res.status(403).json({ error: "assigneeUserId=me requires board authentication" });
692
746
  return;
@@ -707,6 +761,11 @@ export function issueRoutes(db, storage, opts = {}) {
707
761
  res.status(400).json({ error: `limit must be a positive integer up to ${ISSUE_LIST_MAX_LIMIT}` });
708
762
  return;
709
763
  }
764
+ if (rawOffset !== undefined && (parsedOffset === null || !Number.isInteger(parsedOffset) || parsedOffset < 0)) {
765
+ res.status(400).json({ error: "offset must be a non-negative integer" });
766
+ return;
767
+ }
768
+ const offset = parsedOffset ?? 0;
710
769
  const result = await svc.list(companyId, {
711
770
  status: normalizedStatus,
712
771
  assigneeAgentId: req.query.assigneeAgentId,
@@ -725,8 +784,10 @@ export function issueRoutes(db, storage, opts = {}) {
725
784
  originId: req.query.originId,
726
785
  includeRoutineExecutions: req.query.includeRoutineExecutions === "true" || req.query.includeRoutineExecutions === "1",
727
786
  excludeRoutineExecutions: req.query.excludeRoutineExecutions === "true" || req.query.excludeRoutineExecutions === "1",
787
+ includeBlockedBy: req.query.includeBlockedBy === "true" || req.query.includeBlockedBy === "1",
728
788
  q: req.query.q,
729
789
  limit,
790
+ offset,
730
791
  });
731
792
  res.json(result);
732
793
  });
@@ -795,13 +856,14 @@ export function issueRoutes(db, storage, opts = {}) {
795
856
  const currentExecutionWorkspacePromise = issue.executionWorkspaceId
796
857
  ? executionWorkspacesSvc.getById(issue.executionWorkspaceId)
797
858
  : Promise.resolve(null);
798
- const [{ project, goal }, ancestors, commentCursor, wakeComment, relations, blockerAttention, attachments, continuationSummary, currentExecutionWorkspace,] = await Promise.all([
859
+ const [{ project, goal }, ancestors, commentCursor, wakeComment, relations, blockerAttention, productivityReview, attachments, continuationSummary, currentExecutionWorkspace,] = await Promise.all([
799
860
  resolveIssueProjectAndGoal(issue),
800
861
  svc.getAncestors(issue.id),
801
862
  svc.getCommentCursor(issue.id),
802
863
  wakeCommentId ? svc.getComment(wakeCommentId) : null,
803
864
  svc.getRelationSummaries(issue.id),
804
865
  svc.listBlockerAttention(issue.companyId, [issue]).then((map) => map.get(issue.id) ?? null),
866
+ svc.listProductivityReviews(issue.companyId, [issue.id]).then((map) => map.get(issue.id) ?? null),
805
867
  svc.listAttachments(issue.id),
806
868
  documentsSvc.getIssueDocumentByKey(issue.id, ISSUE_CONTINUATION_SUMMARY_DOCUMENT_KEY),
807
869
  currentExecutionWorkspacePromise,
@@ -814,6 +876,7 @@ export function issueRoutes(db, storage, opts = {}) {
814
876
  description: issue.description,
815
877
  status: issue.status,
816
878
  ...(blockerAttention ? { blockerAttention } : {}),
879
+ productivityReview,
817
880
  priority: issue.priority,
818
881
  projectId: issue.projectId,
819
882
  goalId: goal?.id ?? issue.goalId,
@@ -822,6 +885,8 @@ export function issueRoutes(db, storage, opts = {}) {
822
885
  blocks: relations.blocks,
823
886
  assigneeAgentId: issue.assigneeAgentId,
824
887
  assigneeUserId: issue.assigneeUserId,
888
+ originKind: issue.originKind,
889
+ originId: issue.originId,
825
890
  updatedAt: issue.updatedAt,
826
891
  },
827
892
  ancestors: ancestors.map((ancestor) => ({
@@ -881,13 +946,14 @@ export function issueRoutes(db, storage, opts = {}) {
881
946
  return;
882
947
  }
883
948
  assertCompanyAccess(req, issue.companyId);
884
- const [{ project, goal }, ancestors, mentionedProjectIds, documentPayload, relations, blockerAttention, referenceSummary] = await Promise.all([
949
+ const [{ project, goal }, ancestors, mentionedProjectIds, documentPayload, relations, blockerAttention, productivityReview, referenceSummary,] = await Promise.all([
885
950
  resolveIssueProjectAndGoal(issue),
886
951
  svc.getAncestors(issue.id),
887
952
  svc.findMentionedProjectIds(issue.id, { includeCommentBodies: false }),
888
953
  documentsSvc.getIssueDocumentPayload(issue),
889
954
  svc.getRelationSummaries(issue.id),
890
955
  svc.listBlockerAttention(issue.companyId, [issue]).then((map) => map.get(issue.id) ?? null),
956
+ svc.listProductivityReviews(issue.companyId, [issue.id]).then((map) => map.get(issue.id) ?? null),
891
957
  issueReferencesSvc.listIssueReferenceSummary(issue.id),
892
958
  ]);
893
959
  const mentionedProjects = mentionedProjectIds.length > 0
@@ -902,6 +968,7 @@ export function issueRoutes(db, storage, opts = {}) {
902
968
  goalId: goal?.id ?? issue.goalId,
903
969
  ancestors,
904
970
  ...(blockerAttention ? { blockerAttention } : {}),
971
+ productivityReview,
905
972
  blockedBy: relations.blockedBy,
906
973
  blocks: relations.blocks,
907
974
  relatedWork: referenceSummary,
@@ -1485,7 +1552,8 @@ export function issueRoutes(db, storage, opts = {}) {
1485
1552
  await assertIssueEnvironmentSelection(companyId, req.body.executionWorkspaceSettings?.environmentId);
1486
1553
  const actor = getActorInfo(req);
1487
1554
  const requestedUiLocale = resolveExplicitRequestUiLocale(req);
1488
- const executionPolicy = normalizeIssueExecutionPolicy(req.body.executionPolicy);
1555
+ const executionPolicy = applyActorMonitorScheduledBy(normalizeIssueExecutionPolicy(req.body.executionPolicy), actor.actorType);
1556
+ assertCanManageIssueMonitor(req, req.body.assigneeAgentId ?? null, Boolean(executionPolicy?.monitor));
1489
1557
  const issue = await svc.create(companyId, {
1490
1558
  ...req.body,
1491
1559
  executionPolicy,
@@ -1515,6 +1583,28 @@ export function issueRoutes(db, storage, opts = {}) {
1515
1583
  }),
1516
1584
  },
1517
1585
  });
1586
+ if (executionPolicy?.monitor) {
1587
+ await logActivity(db, {
1588
+ companyId,
1589
+ actorType: actor.actorType,
1590
+ actorId: actor.actorId,
1591
+ agentId: actor.agentId,
1592
+ runId: actor.runId,
1593
+ action: "issue.monitor_scheduled",
1594
+ entityType: "issue",
1595
+ entityId: issue.id,
1596
+ details: {
1597
+ identifier: issue.identifier,
1598
+ nextCheckAt: executionPolicy.monitor.nextCheckAt,
1599
+ notes: executionPolicy.monitor.notes,
1600
+ scheduledBy: executionPolicy.monitor.scheduledBy,
1601
+ serviceName: executionPolicy.monitor.serviceName ?? null,
1602
+ timeoutAt: executionPolicy.monitor.timeoutAt ?? null,
1603
+ maxAttempts: executionPolicy.monitor.maxAttempts ?? null,
1604
+ recoveryPolicy: executionPolicy.monitor.recoveryPolicy ?? null,
1605
+ },
1606
+ });
1607
+ }
1518
1608
  void queueIssueAssignmentWakeup({
1519
1609
  heartbeat,
1520
1610
  issue,
@@ -1545,7 +1635,8 @@ export function issueRoutes(db, storage, opts = {}) {
1545
1635
  }
1546
1636
  await assertIssueEnvironmentSelection(parent.companyId, req.body.executionWorkspaceSettings?.environmentId);
1547
1637
  const actor = getActorInfo(req);
1548
- const executionPolicy = normalizeIssueExecutionPolicy(req.body.executionPolicy);
1638
+ const executionPolicy = applyActorMonitorScheduledBy(normalizeIssueExecutionPolicy(req.body.executionPolicy), actor.actorType);
1639
+ assertCanManageIssueMonitor(req, req.body.assigneeAgentId ?? null, Boolean(executionPolicy?.monitor));
1549
1640
  const { issue, parentBlockerAdded } = await svc.createChild(parent.id, {
1550
1641
  ...req.body,
1551
1642
  executionPolicy,
@@ -1572,6 +1663,29 @@ export function issueRoutes(db, storage, opts = {}) {
1572
1663
  ...(parentBlockerAdded ? { parentBlockerAdded: true } : {}),
1573
1664
  },
1574
1665
  });
1666
+ if (executionPolicy?.monitor) {
1667
+ await logActivity(db, {
1668
+ companyId: parent.companyId,
1669
+ actorType: actor.actorType,
1670
+ actorId: actor.actorId,
1671
+ agentId: actor.agentId,
1672
+ runId: actor.runId,
1673
+ action: "issue.monitor_scheduled",
1674
+ entityType: "issue",
1675
+ entityId: issue.id,
1676
+ details: {
1677
+ identifier: issue.identifier,
1678
+ parentId: parent.id,
1679
+ nextCheckAt: executionPolicy.monitor.nextCheckAt,
1680
+ notes: executionPolicy.monitor.notes,
1681
+ scheduledBy: executionPolicy.monitor.scheduledBy,
1682
+ serviceName: executionPolicy.monitor.serviceName ?? null,
1683
+ timeoutAt: executionPolicy.monitor.timeoutAt ?? null,
1684
+ maxAttempts: executionPolicy.monitor.maxAttempts ?? null,
1685
+ recoveryPolicy: executionPolicy.monitor.recoveryPolicy ?? null,
1686
+ },
1687
+ });
1688
+ }
1575
1689
  void queueIssueAssignmentWakeup({
1576
1690
  heartbeat,
1577
1691
  issue,
@@ -1583,6 +1697,24 @@ export function issueRoutes(db, storage, opts = {}) {
1583
1697
  });
1584
1698
  res.status(201).json(issue);
1585
1699
  });
1700
+ router.post("/issues/:id/monitor/check-now", async (req, res) => {
1701
+ const id = req.params.id;
1702
+ const issue = await svc.getById(id);
1703
+ if (!issue) {
1704
+ res.status(404).json({ error: "Issue not found" });
1705
+ return;
1706
+ }
1707
+ assertCompanyAccess(req, issue.companyId);
1708
+ assertCanManageIssueMonitor(req, issue.assigneeAgentId, true);
1709
+ const actor = getActorInfo(req);
1710
+ await heartbeat.triggerIssueMonitor(issue.id, {
1711
+ actorType: actor.actorType,
1712
+ actorId: actor.actorId,
1713
+ agentId: actor.agentId ?? null,
1714
+ runId: actor.runId ?? null,
1715
+ });
1716
+ res.json({ ok: true });
1717
+ });
1586
1718
  router.patch("/issues/:id", validate(updateIssueRouteSchema), async (req, res) => {
1587
1719
  const id = req.params.id;
1588
1720
  const existing = await svc.getById(id);
@@ -1684,7 +1816,7 @@ export function issueRoutes(db, storage, opts = {}) {
1684
1816
  updateFields.status = "todo";
1685
1817
  }
1686
1818
  if (req.body.executionPolicy !== undefined) {
1687
- updateFields.executionPolicy = normalizeIssueExecutionPolicy(req.body.executionPolicy);
1819
+ updateFields.executionPolicy = applyActorMonitorScheduledBy(normalizeIssueExecutionPolicy(req.body.executionPolicy), actor.actorType);
1688
1820
  }
1689
1821
  const previousExecutionPolicy = normalizeIssueExecutionPolicy(existing.executionPolicy ?? null);
1690
1822
  const nextExecutionPolicy = updateFields.executionPolicy !== undefined
@@ -1693,9 +1825,12 @@ export function issueRoutes(db, storage, opts = {}) {
1693
1825
  if (normalizedAssigneeAgentId !== undefined) {
1694
1826
  updateFields.assigneeAgentId = normalizedAssigneeAgentId;
1695
1827
  }
1828
+ const monitorChanged = monitorPoliciesEqual(previousExecutionPolicy, nextExecutionPolicy) === false;
1829
+ assertCanManageIssueMonitor(req, existing.assigneeAgentId, req.body.executionPolicy !== undefined && monitorChanged);
1696
1830
  const transition = applyIssueExecutionPolicyTransition({
1697
1831
  issue: existing,
1698
1832
  policy: nextExecutionPolicy,
1833
+ previousPolicy: previousExecutionPolicy,
1699
1834
  requestedStatus: typeof updateFields.status === "string" ? updateFields.status : undefined,
1700
1835
  requestedAssigneePatch: {
1701
1836
  assigneeAgentId: normalizedAssigneeAgentId,
@@ -1707,6 +1842,7 @@ export function issueRoutes(db, storage, opts = {}) {
1707
1842
  },
1708
1843
  commentBody,
1709
1844
  reviewRequest: reviewRequest === undefined ? undefined : reviewRequest,
1845
+ monitorExplicitlyUpdated: req.body.executionPolicy !== undefined && monitorChanged,
1710
1846
  });
1711
1847
  const decisionId = transition.decision ? randomUUID() : null;
1712
1848
  if (decisionId) {
@@ -1980,6 +2116,51 @@ export function issueRoutes(db, storage, opts = {}) {
1980
2116
  },
1981
2117
  });
1982
2118
  }
2119
+ const nextStoredExecutionPolicy = normalizeIssueExecutionPolicy(issue.executionPolicy ?? null);
2120
+ const previousMonitor = summarizeIssueMonitor(existing, previousExecutionPolicy);
2121
+ const nextMonitor = summarizeIssueMonitor(issue, nextStoredExecutionPolicy);
2122
+ const monitorScheduledChanged = previousMonitor.nextCheckAt !== nextMonitor.nextCheckAt;
2123
+ if (nextMonitor.nextCheckAt && (monitorScheduledChanged || previousMonitor.notes !== nextMonitor.notes)) {
2124
+ await logActivity(db, {
2125
+ companyId: issue.companyId,
2126
+ actorType: actor.actorType,
2127
+ actorId: actor.actorId,
2128
+ agentId: actor.agentId,
2129
+ runId: actor.runId,
2130
+ action: "issue.monitor_scheduled",
2131
+ entityType: "issue",
2132
+ entityId: issue.id,
2133
+ details: {
2134
+ identifier: issue.identifier,
2135
+ nextCheckAt: nextMonitor.nextCheckAt,
2136
+ previousNextCheckAt: previousMonitor.nextCheckAt,
2137
+ notes: nextMonitor.notes,
2138
+ scheduledBy: nextMonitor.scheduledBy,
2139
+ serviceName: nextMonitor.serviceName,
2140
+ timeoutAt: nextMonitor.timeoutAt,
2141
+ maxAttempts: nextMonitor.maxAttempts,
2142
+ recoveryPolicy: nextMonitor.recoveryPolicy,
2143
+ },
2144
+ });
2145
+ }
2146
+ else if (!nextMonitor.nextCheckAt && previousMonitor.nextCheckAt) {
2147
+ await logActivity(db, {
2148
+ companyId: issue.companyId,
2149
+ actorType: actor.actorType,
2150
+ actorId: actor.actorId,
2151
+ agentId: actor.agentId,
2152
+ runId: actor.runId,
2153
+ action: "issue.monitor_cleared",
2154
+ entityType: "issue",
2155
+ entityId: issue.id,
2156
+ details: {
2157
+ identifier: issue.identifier,
2158
+ previousNextCheckAt: previousMonitor.nextCheckAt,
2159
+ reason: nextMonitor.clearReason ?? "manual",
2160
+ notes: previousMonitor.notes,
2161
+ },
2162
+ });
2163
+ }
1983
2164
  if (issue.status === "done" && existing.status !== "done") {
1984
2165
  const tc = getTelemetryClient();
1985
2166
  if (tc && actor.agentId) {
@@ -2701,6 +2882,48 @@ export function issueRoutes(db, storage, opts = {}) {
2701
2882
  });
2702
2883
  res.json(interaction);
2703
2884
  });
2885
+ router.post("/issues/:id/interactions/:interactionId/cancel", validate(cancelIssueThreadInteractionSchema), async (req, res) => {
2886
+ const id = req.params.id;
2887
+ const interactionId = req.params.interactionId;
2888
+ const issue = await svc.getById(id);
2889
+ if (!issue) {
2890
+ res.status(404).json({ error: "Issue not found" });
2891
+ return;
2892
+ }
2893
+ assertCompanyAccess(req, issue.companyId);
2894
+ assertBoard(req);
2895
+ const actor = getActorInfo(req);
2896
+ const interaction = await issueThreadInteractionService(db).cancelQuestions(issue, interactionId, req.body, {
2897
+ agentId: actor.agentId,
2898
+ userId: actor.actorType === "user" ? actor.actorId : null,
2899
+ });
2900
+ await logActivity(db, {
2901
+ companyId: issue.companyId,
2902
+ actorType: actor.actorType,
2903
+ actorId: actor.actorId,
2904
+ agentId: actor.agentId,
2905
+ runId: actor.runId,
2906
+ action: "issue.thread_interaction_cancelled",
2907
+ entityType: "issue",
2908
+ entityId: issue.id,
2909
+ details: {
2910
+ interactionId: interaction.id,
2911
+ interactionKind: interaction.kind,
2912
+ interactionStatus: interaction.status,
2913
+ cancellationReason: interaction.kind === "ask_user_questions"
2914
+ ? (interaction.result?.cancellationReason ?? null)
2915
+ : null,
2916
+ },
2917
+ });
2918
+ queueResolvedInteractionContinuationWakeup({
2919
+ heartbeat,
2920
+ issue,
2921
+ interaction,
2922
+ actor,
2923
+ source: "issue.interaction.cancel",
2924
+ });
2925
+ res.json(interaction);
2926
+ });
2704
2927
  router.get("/issues/:id/comments/:commentId", async (req, res) => {
2705
2928
  const id = req.params.id;
2706
2929
  const commentId = req.params.commentId;
@@ -3211,13 +3434,15 @@ export function issueRoutes(db, storage, opts = {}) {
3211
3434
  }
3212
3435
  if (!(await assertAgentIssueMutationAllowed(req, res, issue)))
3213
3436
  return;
3437
+ const company = await companiesSvc.getById(companyId);
3438
+ const attachmentMaxBytes = normalizeIssueAttachmentMaxBytes(company?.attachmentMaxBytes);
3214
3439
  try {
3215
- await runSingleFileUpload(req, res);
3440
+ await runSingleFileUpload(req, res, attachmentMaxBytes);
3216
3441
  }
3217
3442
  catch (err) {
3218
3443
  if (err instanceof multer.MulterError) {
3219
3444
  if (err.code === "LIMIT_FILE_SIZE") {
3220
- res.status(422).json({ error: `Attachment exceeds ${MAX_ATTACHMENT_BYTES} bytes` });
3445
+ res.status(422).json({ error: `Attachment exceeds ${attachmentMaxBytes} bytes` });
3221
3446
  return;
3222
3447
  }
3223
3448
  res.status(400).json({ error: err.message });