@zmeel/server 0.3.1

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 (772) hide show
  1. package/LICENSE +21 -0
  2. package/dist/adapters/codex-models.d.ts +4 -0
  3. package/dist/adapters/codex-models.d.ts.map +1 -0
  4. package/dist/adapters/codex-models.js +98 -0
  5. package/dist/adapters/codex-models.js.map +1 -0
  6. package/dist/adapters/cursor-models.d.ts +13 -0
  7. package/dist/adapters/cursor-models.d.ts.map +1 -0
  8. package/dist/adapters/cursor-models.js +148 -0
  9. package/dist/adapters/cursor-models.js.map +1 -0
  10. package/dist/adapters/http/execute.d.ts +3 -0
  11. package/dist/adapters/http/execute.d.ts.map +1 -0
  12. package/dist/adapters/http/execute.js +39 -0
  13. package/dist/adapters/http/execute.js.map +1 -0
  14. package/dist/adapters/http/index.d.ts +3 -0
  15. package/dist/adapters/http/index.d.ts.map +1 -0
  16. package/dist/adapters/http/index.js +20 -0
  17. package/dist/adapters/http/index.js.map +1 -0
  18. package/dist/adapters/http/test.d.ts +3 -0
  19. package/dist/adapters/http/test.d.ts.map +1 -0
  20. package/dist/adapters/http/test.js +106 -0
  21. package/dist/adapters/http/test.js.map +1 -0
  22. package/dist/adapters/index.d.ts +4 -0
  23. package/dist/adapters/index.d.ts.map +1 -0
  24. package/dist/adapters/index.js +3 -0
  25. package/dist/adapters/index.js.map +1 -0
  26. package/dist/adapters/process/execute.d.ts +3 -0
  27. package/dist/adapters/process/execute.d.ts.map +1 -0
  28. package/dist/adapters/process/execute.js +70 -0
  29. package/dist/adapters/process/execute.js.map +1 -0
  30. package/dist/adapters/process/index.d.ts +3 -0
  31. package/dist/adapters/process/index.d.ts.map +1 -0
  32. package/dist/adapters/process/index.js +23 -0
  33. package/dist/adapters/process/index.js.map +1 -0
  34. package/dist/adapters/process/test.d.ts +3 -0
  35. package/dist/adapters/process/test.d.ts.map +1 -0
  36. package/dist/adapters/process/test.js +77 -0
  37. package/dist/adapters/process/test.js.map +1 -0
  38. package/dist/adapters/registry.d.ts +14 -0
  39. package/dist/adapters/registry.d.ts.map +1 -0
  40. package/dist/adapters/registry.js +164 -0
  41. package/dist/adapters/registry.js.map +1 -0
  42. package/dist/adapters/types.d.ts +2 -0
  43. package/dist/adapters/types.d.ts.map +1 -0
  44. package/dist/adapters/types.js +2 -0
  45. package/dist/adapters/types.js.map +1 -0
  46. package/dist/adapters/utils.d.ts +41 -0
  47. package/dist/adapters/utils.d.ts.map +1 -0
  48. package/dist/adapters/utils.js +51 -0
  49. package/dist/adapters/utils.js.map +1 -0
  50. package/dist/agent-auth-jwt.d.ts +14 -0
  51. package/dist/agent-auth-jwt.d.ts.map +1 -0
  52. package/dist/agent-auth-jwt.js +117 -0
  53. package/dist/agent-auth-jwt.js.map +1 -0
  54. package/dist/app.d.ts +32 -0
  55. package/dist/app.d.ts.map +1 -0
  56. package/dist/app.js +281 -0
  57. package/dist/app.js.map +1 -0
  58. package/dist/attachment-types.d.ts +33 -0
  59. package/dist/attachment-types.d.ts.map +1 -0
  60. package/dist/attachment-types.js +67 -0
  61. package/dist/attachment-types.js.map +1 -0
  62. package/dist/auth/better-auth.d.ts +24 -0
  63. package/dist/auth/better-auth.d.ts.map +1 -0
  64. package/dist/auth/better-auth.js +108 -0
  65. package/dist/auth/better-auth.js.map +1 -0
  66. package/dist/board-claim.d.ts +23 -0
  67. package/dist/board-claim.d.ts.map +1 -0
  68. package/dist/board-claim.js +115 -0
  69. package/dist/board-claim.js.map +1 -0
  70. package/dist/config-file.d.ts +3 -0
  71. package/dist/config-file.d.ts.map +1 -0
  72. package/dist/config-file.js +16 -0
  73. package/dist/config-file.js.map +1 -0
  74. package/dist/config.d.ts +41 -0
  75. package/dist/config.d.ts.map +1 -0
  76. package/dist/config.js +173 -0
  77. package/dist/config.js.map +1 -0
  78. package/dist/dev-server-status.d.ts +27 -0
  79. package/dist/dev-server-status.d.ts.map +1 -0
  80. package/dist/dev-server-status.js +70 -0
  81. package/dist/dev-server-status.js.map +1 -0
  82. package/dist/dev-watch-ignore.d.ts +2 -0
  83. package/dist/dev-watch-ignore.d.ts.map +1 -0
  84. package/dist/dev-watch-ignore.js +33 -0
  85. package/dist/dev-watch-ignore.js.map +1 -0
  86. package/dist/errors.d.ts +12 -0
  87. package/dist/errors.d.ts.map +1 -0
  88. package/dist/errors.js +28 -0
  89. package/dist/errors.js.map +1 -0
  90. package/dist/home-paths.d.ts +17 -0
  91. package/dist/home-paths.d.ts.map +1 -0
  92. package/dist/home-paths.js +75 -0
  93. package/dist/home-paths.js.map +1 -0
  94. package/dist/index.d.ts +10 -0
  95. package/dist/index.d.ts.map +1 -0
  96. package/dist/index.js +627 -0
  97. package/dist/index.js.map +1 -0
  98. package/dist/log-redaction.d.ts +11 -0
  99. package/dist/log-redaction.d.ts.map +1 -0
  100. package/dist/log-redaction.js +118 -0
  101. package/dist/log-redaction.js.map +1 -0
  102. package/dist/middleware/auth.d.ts +12 -0
  103. package/dist/middleware/auth.d.ts.map +1 -0
  104. package/dist/middleware/auth.js +144 -0
  105. package/dist/middleware/auth.js.map +1 -0
  106. package/dist/middleware/board-mutation-guard.d.ts +3 -0
  107. package/dist/middleware/board-mutation-guard.d.ts.map +1 -0
  108. package/dist/middleware/board-mutation-guard.js +60 -0
  109. package/dist/middleware/board-mutation-guard.js.map +1 -0
  110. package/dist/middleware/error-handler.d.ts +17 -0
  111. package/dist/middleware/error-handler.d.ts.map +1 -0
  112. package/dist/middleware/error-handler.js +45 -0
  113. package/dist/middleware/error-handler.js.map +1 -0
  114. package/dist/middleware/index.d.ts +4 -0
  115. package/dist/middleware/index.d.ts.map +1 -0
  116. package/dist/middleware/index.js +4 -0
  117. package/dist/middleware/index.js.map +1 -0
  118. package/dist/middleware/logger.d.ts +4 -0
  119. package/dist/middleware/logger.d.ts.map +1 -0
  120. package/dist/middleware/logger.js +87 -0
  121. package/dist/middleware/logger.js.map +1 -0
  122. package/dist/middleware/private-hostname-guard.d.ts +11 -0
  123. package/dist/middleware/private-hostname-guard.d.ts.map +1 -0
  124. package/dist/middleware/private-hostname-guard.js +78 -0
  125. package/dist/middleware/private-hostname-guard.js.map +1 -0
  126. package/dist/middleware/validate.d.ts +4 -0
  127. package/dist/middleware/validate.d.ts.map +1 -0
  128. package/dist/middleware/validate.js +7 -0
  129. package/dist/middleware/validate.js.map +1 -0
  130. package/dist/onboarding-assets/ceo/AGENTS.md +54 -0
  131. package/dist/onboarding-assets/ceo/HEARTBEAT.md +72 -0
  132. package/dist/onboarding-assets/ceo/SOUL.md +33 -0
  133. package/dist/onboarding-assets/ceo/TOOLS.md +3 -0
  134. package/dist/onboarding-assets/default/AGENTS.md +3 -0
  135. package/dist/paths.d.ts +3 -0
  136. package/dist/paths.d.ts.map +1 -0
  137. package/dist/paths.js +31 -0
  138. package/dist/paths.js.map +1 -0
  139. package/dist/realtime/live-events-ws.d.ts +28 -0
  140. package/dist/realtime/live-events-ws.d.ts.map +1 -0
  141. package/dist/realtime/live-events-ws.js +187 -0
  142. package/dist/realtime/live-events-ws.js.map +1 -0
  143. package/dist/redaction.d.ts +4 -0
  144. package/dist/redaction.d.ts.map +1 -0
  145. package/dist/redaction.js +63 -0
  146. package/dist/redaction.js.map +1 -0
  147. package/dist/routes/access.d.ts +62 -0
  148. package/dist/routes/access.d.ts.map +1 -0
  149. package/dist/routes/access.js +2296 -0
  150. package/dist/routes/access.js.map +1 -0
  151. package/dist/routes/activity.d.ts +3 -0
  152. package/dist/routes/activity.d.ts.map +1 -0
  153. package/dist/routes/activity.js +78 -0
  154. package/dist/routes/activity.js.map +1 -0
  155. package/dist/routes/agents.d.ts +3 -0
  156. package/dist/routes/agents.d.ts.map +1 -0
  157. package/dist/routes/agents.js +1918 -0
  158. package/dist/routes/agents.js.map +1 -0
  159. package/dist/routes/approvals.d.ts +3 -0
  160. package/dist/routes/approvals.d.ts.map +1 -0
  161. package/dist/routes/approvals.js +275 -0
  162. package/dist/routes/approvals.js.map +1 -0
  163. package/dist/routes/assets.d.ts +4 -0
  164. package/dist/routes/assets.d.ts.map +1 -0
  165. package/dist/routes/assets.js +309 -0
  166. package/dist/routes/assets.js.map +1 -0
  167. package/dist/routes/authz.d.ts +16 -0
  168. package/dist/routes/authz.d.ts.map +1 -0
  169. package/dist/routes/authz.js +47 -0
  170. package/dist/routes/authz.js.map +1 -0
  171. package/dist/routes/companies.d.ts +4 -0
  172. package/dist/routes/companies.d.ts.map +1 -0
  173. package/dist/routes/companies.js +356 -0
  174. package/dist/routes/companies.js.map +1 -0
  175. package/dist/routes/company-skills.d.ts +3 -0
  176. package/dist/routes/company-skills.d.ts.map +1 -0
  177. package/dist/routes/company-skills.js +228 -0
  178. package/dist/routes/company-skills.js.map +1 -0
  179. package/dist/routes/costs.d.ts +3 -0
  180. package/dist/routes/costs.d.ts.map +1 -0
  181. package/dist/routes/costs.js +268 -0
  182. package/dist/routes/costs.js.map +1 -0
  183. package/dist/routes/dashboard.d.ts +3 -0
  184. package/dist/routes/dashboard.d.ts.map +1 -0
  185. package/dist/routes/dashboard.js +15 -0
  186. package/dist/routes/dashboard.js.map +1 -0
  187. package/dist/routes/execution-workspaces.d.ts +3 -0
  188. package/dist/routes/execution-workspaces.d.ts.map +1 -0
  189. package/dist/routes/execution-workspaces.js +367 -0
  190. package/dist/routes/execution-workspaces.js.map +1 -0
  191. package/dist/routes/goals.d.ts +3 -0
  192. package/dist/routes/goals.d.ts.map +1 -0
  193. package/dist/routes/goals.js +95 -0
  194. package/dist/routes/goals.js.map +1 -0
  195. package/dist/routes/health.d.ts +9 -0
  196. package/dist/routes/health.d.ts.map +1 -0
  197. package/dist/routes/health.js +80 -0
  198. package/dist/routes/health.js.map +1 -0
  199. package/dist/routes/index.d.ts +18 -0
  200. package/dist/routes/index.d.ts.map +1 -0
  201. package/dist/routes/index.js +18 -0
  202. package/dist/routes/index.js.map +1 -0
  203. package/dist/routes/instance-settings.d.ts +3 -0
  204. package/dist/routes/instance-settings.d.ts.map +1 -0
  205. package/dist/routes/instance-settings.js +79 -0
  206. package/dist/routes/instance-settings.js.map +1 -0
  207. package/dist/routes/issues-checkout-wakeup.d.ts +9 -0
  208. package/dist/routes/issues-checkout-wakeup.d.ts.map +1 -0
  209. package/dist/routes/issues-checkout-wakeup.js +12 -0
  210. package/dist/routes/issues-checkout-wakeup.js.map +1 -0
  211. package/dist/routes/issues.d.ts +4 -0
  212. package/dist/routes/issues.d.ts.map +1 -0
  213. package/dist/routes/issues.js +1834 -0
  214. package/dist/routes/issues.js.map +1 -0
  215. package/dist/routes/llms.d.ts +3 -0
  216. package/dist/routes/llms.d.ts.map +1 -0
  217. package/dist/routes/llms.js +78 -0
  218. package/dist/routes/llms.js.map +1 -0
  219. package/dist/routes/org-chart-svg.d.ts +25 -0
  220. package/dist/routes/org-chart-svg.d.ts.map +1 -0
  221. package/dist/routes/org-chart-svg.js +656 -0
  222. package/dist/routes/org-chart-svg.js.map +1 -0
  223. package/dist/routes/plugin-ui-static.d.ts +69 -0
  224. package/dist/routes/plugin-ui-static.d.ts.map +1 -0
  225. package/dist/routes/plugin-ui-static.js +411 -0
  226. package/dist/routes/plugin-ui-static.js.map +1 -0
  227. package/dist/routes/plugins.d.ts +120 -0
  228. package/dist/routes/plugins.d.ts.map +1 -0
  229. package/dist/routes/plugins.js +1784 -0
  230. package/dist/routes/plugins.js.map +1 -0
  231. package/dist/routes/projects.d.ts +3 -0
  232. package/dist/routes/projects.d.ts.map +1 -0
  233. package/dist/routes/projects.js +386 -0
  234. package/dist/routes/projects.js.map +1 -0
  235. package/dist/routes/routines.d.ts +3 -0
  236. package/dist/routes/routines.d.ts.map +1 -0
  237. package/dist/routes/routines.js +277 -0
  238. package/dist/routes/routines.js.map +1 -0
  239. package/dist/routes/secrets.d.ts +3 -0
  240. package/dist/routes/secrets.d.ts.map +1 -0
  241. package/dist/routes/secrets.js +128 -0
  242. package/dist/routes/secrets.js.map +1 -0
  243. package/dist/routes/sidebar-badges.d.ts +3 -0
  244. package/dist/routes/sidebar-badges.d.ts.map +1 -0
  245. package/dist/routes/sidebar-badges.js +45 -0
  246. package/dist/routes/sidebar-badges.js.map +1 -0
  247. package/dist/secrets/external-stub-providers.d.ts +5 -0
  248. package/dist/secrets/external-stub-providers.d.ts.map +1 -0
  249. package/dist/secrets/external-stub-providers.js +21 -0
  250. package/dist/secrets/external-stub-providers.js.map +1 -0
  251. package/dist/secrets/local-encrypted-provider.d.ts +3 -0
  252. package/dist/secrets/local-encrypted-provider.d.ts.map +1 -0
  253. package/dist/secrets/local-encrypted-provider.js +116 -0
  254. package/dist/secrets/local-encrypted-provider.js.map +1 -0
  255. package/dist/secrets/provider-registry.d.ts +5 -0
  256. package/dist/secrets/provider-registry.d.ts.map +1 -0
  257. package/dist/secrets/provider-registry.js +20 -0
  258. package/dist/secrets/provider-registry.js.map +1 -0
  259. package/dist/secrets/types.d.ts +21 -0
  260. package/dist/secrets/types.d.ts.map +1 -0
  261. package/dist/secrets/types.js +2 -0
  262. package/dist/secrets/types.js.map +1 -0
  263. package/dist/services/access.d.ts +113 -0
  264. package/dist/services/access.d.ts.map +1 -0
  265. package/dist/services/access.js +247 -0
  266. package/dist/services/access.js.map +1 -0
  267. package/dist/services/activity-log.d.ts +17 -0
  268. package/dist/services/activity-log.d.ts.map +1 -0
  269. package/dist/services/activity-log.js +74 -0
  270. package/dist/services/activity-log.js.map +1 -0
  271. package/dist/services/activity.d.ts +764 -0
  272. package/dist/services/activity.d.ts.map +1 -0
  273. package/dist/services/activity.js +105 -0
  274. package/dist/services/activity.js.map +1 -0
  275. package/dist/services/agent-instructions.d.ts +91 -0
  276. package/dist/services/agent-instructions.d.ts.map +1 -0
  277. package/dist/services/agent-instructions.js +580 -0
  278. package/dist/services/agent-instructions.js.map +1 -0
  279. package/dist/services/agent-permissions.d.ts +6 -0
  280. package/dist/services/agent-permissions.d.ts.map +1 -0
  281. package/dist/services/agent-permissions.js +18 -0
  282. package/dist/services/agent-permissions.js.map +1 -0
  283. package/dist/services/agents.d.ts +1670 -0
  284. package/dist/services/agents.d.ts.map +1 -0
  285. package/dist/services/agents.js +566 -0
  286. package/dist/services/agents.js.map +1 -0
  287. package/dist/services/approvals.d.ts +546 -0
  288. package/dist/services/approvals.d.ts.map +1 -0
  289. package/dist/services/approvals.js +212 -0
  290. package/dist/services/approvals.js.map +1 -0
  291. package/dist/services/assets.d.ts +33 -0
  292. package/dist/services/assets.d.ts.map +1 -0
  293. package/dist/services/assets.js +17 -0
  294. package/dist/services/assets.js.map +1 -0
  295. package/dist/services/board-auth.d.ts +234 -0
  296. package/dist/services/board-auth.d.ts.map +1 -0
  297. package/dist/services/board-auth.js +295 -0
  298. package/dist/services/board-auth.js.map +1 -0
  299. package/dist/services/budgets.d.ts +38 -0
  300. package/dist/services/budgets.d.ts.map +1 -0
  301. package/dist/services/budgets.js +784 -0
  302. package/dist/services/budgets.js.map +1 -0
  303. package/dist/services/companies.d.ts +148 -0
  304. package/dist/services/companies.d.ts.map +1 -0
  305. package/dist/services/companies.js +260 -0
  306. package/dist/services/companies.js.map +1 -0
  307. package/dist/services/company-export-readme.d.ts +17 -0
  308. package/dist/services/company-export-readme.d.ts.map +1 -0
  309. package/dist/services/company-export-readme.js +148 -0
  310. package/dist/services/company-export-readme.js.map +1 -0
  311. package/dist/services/company-portability.d.ts +24 -0
  312. package/dist/services/company-portability.d.ts.map +1 -0
  313. package/dist/services/company-portability.js +3807 -0
  314. package/dist/services/company-portability.js.map +1 -0
  315. package/dist/services/company-skills.d.ts +77 -0
  316. package/dist/services/company-skills.d.ts.map +1 -0
  317. package/dist/services/company-skills.js +2063 -0
  318. package/dist/services/company-skills.js.map +1 -0
  319. package/dist/services/costs.d.ts +114 -0
  320. package/dist/services/costs.d.ts.map +1 -0
  321. package/dist/services/costs.js +294 -0
  322. package/dist/services/costs.js.map +1 -0
  323. package/dist/services/cron.d.ts +80 -0
  324. package/dist/services/cron.d.ts.map +1 -0
  325. package/dist/services/cron.js +300 -0
  326. package/dist/services/cron.js.map +1 -0
  327. package/dist/services/dashboard.d.ts +26 -0
  328. package/dist/services/dashboard.d.ts.map +1 -0
  329. package/dist/services/dashboard.js +98 -0
  330. package/dist/services/dashboard.js.map +1 -0
  331. package/dist/services/default-agent-instructions.d.ts +9 -0
  332. package/dist/services/default-agent-instructions.d.ts.map +1 -0
  333. package/dist/services/default-agent-instructions.js +20 -0
  334. package/dist/services/default-agent-instructions.js.map +1 -0
  335. package/dist/services/documents.d.ts +195 -0
  336. package/dist/services/documents.d.ts.map +1 -0
  337. package/dist/services/documents.js +409 -0
  338. package/dist/services/documents.js.map +1 -0
  339. package/dist/services/execution-workspace-policy.d.ts +21 -0
  340. package/dist/services/execution-workspace-policy.d.ts.map +1 -0
  341. package/dist/services/execution-workspace-policy.js +177 -0
  342. package/dist/services/execution-workspace-policy.js.map +1 -0
  343. package/dist/services/execution-workspaces.d.ts +22 -0
  344. package/dist/services/execution-workspaces.d.ts.map +1 -0
  345. package/dist/services/execution-workspaces.js +551 -0
  346. package/dist/services/execution-workspaces.js.map +1 -0
  347. package/dist/services/feedback-redaction.d.ts +23 -0
  348. package/dist/services/feedback-redaction.d.ts.map +1 -0
  349. package/dist/services/feedback-redaction.js +150 -0
  350. package/dist/services/feedback-redaction.js.map +1 -0
  351. package/dist/services/feedback-share-client.d.ts +9 -0
  352. package/dist/services/feedback-share-client.d.ts.map +1 -0
  353. package/dist/services/feedback-share-client.js +42 -0
  354. package/dist/services/feedback-share-client.js.map +1 -0
  355. package/dist/services/feedback.d.ts +91 -0
  356. package/dist/services/feedback.d.ts.map +1 -0
  357. package/dist/services/feedback.js +1662 -0
  358. package/dist/services/feedback.js.map +1 -0
  359. package/dist/services/finance.d.ts +93 -0
  360. package/dist/services/finance.d.ts.map +1 -0
  361. package/dist/services/finance.js +120 -0
  362. package/dist/services/finance.js.map +1 -0
  363. package/dist/services/github-fetch.d.ts +4 -0
  364. package/dist/services/github-fetch.d.ts.map +1 -0
  365. package/dist/services/github-fetch.js +23 -0
  366. package/dist/services/github-fetch.js.map +1 -0
  367. package/dist/services/goals.d.ts +433 -0
  368. package/dist/services/goals.d.ts.map +1 -0
  369. package/dist/services/goals.js +54 -0
  370. package/dist/services/goals.js.map +1 -0
  371. package/dist/services/heartbeat-run-summary.d.ts +2 -0
  372. package/dist/services/heartbeat-run-summary.d.ts.map +1 -0
  373. package/dist/services/heartbeat-run-summary.js +30 -0
  374. package/dist/services/heartbeat-run-summary.js.map +1 -0
  375. package/dist/services/heartbeat.d.ts +839 -0
  376. package/dist/services/heartbeat.d.ts.map +1 -0
  377. package/dist/services/heartbeat.js +3287 -0
  378. package/dist/services/heartbeat.js.map +1 -0
  379. package/dist/services/hire-hook.d.ts +14 -0
  380. package/dist/services/hire-hook.d.ts.map +1 -0
  381. package/dist/services/hire-hook.js +85 -0
  382. package/dist/services/hire-hook.js.map +1 -0
  383. package/dist/services/index.d.ts +34 -0
  384. package/dist/services/index.d.ts.map +1 -0
  385. package/dist/services/index.js +34 -0
  386. package/dist/services/index.js.map +1 -0
  387. package/dist/services/instance-settings.d.ts +11 -0
  388. package/dist/services/instance-settings.d.ts.map +1 -0
  389. package/dist/services/instance-settings.js +120 -0
  390. package/dist/services/instance-settings.js.map +1 -0
  391. package/dist/services/issue-approvals.d.ts +56 -0
  392. package/dist/services/issue-approvals.d.ts.map +1 -0
  393. package/dist/services/issue-approvals.js +153 -0
  394. package/dist/services/issue-approvals.js.map +1 -0
  395. package/dist/services/issue-assignment-wakeup.d.ts +29 -0
  396. package/dist/services/issue-assignment-wakeup.d.ts.map +1 -0
  397. package/dist/services/issue-assignment-wakeup.js +22 -0
  398. package/dist/services/issue-assignment-wakeup.js.map +1 -0
  399. package/dist/services/issue-goal-fallback.d.ts +18 -0
  400. package/dist/services/issue-goal-fallback.d.ts.map +1 -0
  401. package/dist/services/issue-goal-fallback.js +33 -0
  402. package/dist/services/issue-goal-fallback.js.map +1 -0
  403. package/dist/services/issues.d.ts +568 -0
  404. package/dist/services/issues.d.ts.map +1 -0
  405. package/dist/services/issues.js +1671 -0
  406. package/dist/services/issues.js.map +1 -0
  407. package/dist/services/live-events.d.ts +17 -0
  408. package/dist/services/live-events.d.ts.map +1 -0
  409. package/dist/services/live-events.js +33 -0
  410. package/dist/services/live-events.js.map +1 -0
  411. package/dist/services/local-service-supervisor.d.ts +55 -0
  412. package/dist/services/local-service-supervisor.d.ts.map +1 -0
  413. package/dist/services/local-service-supervisor.js +240 -0
  414. package/dist/services/local-service-supervisor.js.map +1 -0
  415. package/dist/services/plugin-capability-validator.d.ts +108 -0
  416. package/dist/services/plugin-capability-validator.d.ts.map +1 -0
  417. package/dist/services/plugin-capability-validator.js +269 -0
  418. package/dist/services/plugin-capability-validator.js.map +1 -0
  419. package/dist/services/plugin-config-validator.d.ts +26 -0
  420. package/dist/services/plugin-config-validator.d.ts.map +1 -0
  421. package/dist/services/plugin-config-validator.js +41 -0
  422. package/dist/services/plugin-config-validator.js.map +1 -0
  423. package/dist/services/plugin-dev-watcher.d.ts +30 -0
  424. package/dist/services/plugin-dev-watcher.d.ts.map +1 -0
  425. package/dist/services/plugin-dev-watcher.js +241 -0
  426. package/dist/services/plugin-dev-watcher.js.map +1 -0
  427. package/dist/services/plugin-event-bus.d.ts +149 -0
  428. package/dist/services/plugin-event-bus.d.ts.map +1 -0
  429. package/dist/services/plugin-event-bus.js +258 -0
  430. package/dist/services/plugin-event-bus.js.map +1 -0
  431. package/dist/services/plugin-host-service-cleanup.d.ts +14 -0
  432. package/dist/services/plugin-host-service-cleanup.d.ts.map +1 -0
  433. package/dist/services/plugin-host-service-cleanup.js +37 -0
  434. package/dist/services/plugin-host-service-cleanup.js.map +1 -0
  435. package/dist/services/plugin-host-services.d.ts +13 -0
  436. package/dist/services/plugin-host-services.d.ts.map +1 -0
  437. package/dist/services/plugin-host-services.js +983 -0
  438. package/dist/services/plugin-host-services.js.map +1 -0
  439. package/dist/services/plugin-job-coordinator.d.ts +81 -0
  440. package/dist/services/plugin-job-coordinator.d.ts.map +1 -0
  441. package/dist/services/plugin-job-coordinator.js +172 -0
  442. package/dist/services/plugin-job-coordinator.js.map +1 -0
  443. package/dist/services/plugin-job-scheduler.d.ts +163 -0
  444. package/dist/services/plugin-job-scheduler.d.ts.map +1 -0
  445. package/dist/services/plugin-job-scheduler.js +454 -0
  446. package/dist/services/plugin-job-scheduler.js.map +1 -0
  447. package/dist/services/plugin-job-store.d.ts +208 -0
  448. package/dist/services/plugin-job-store.d.ts.map +1 -0
  449. package/dist/services/plugin-job-store.js +350 -0
  450. package/dist/services/plugin-job-store.js.map +1 -0
  451. package/dist/services/plugin-lifecycle.d.ts +203 -0
  452. package/dist/services/plugin-lifecycle.d.ts.map +1 -0
  453. package/dist/services/plugin-lifecycle.js +476 -0
  454. package/dist/services/plugin-lifecycle.js.map +1 -0
  455. package/dist/services/plugin-loader.d.ts +441 -0
  456. package/dist/services/plugin-loader.d.ts.map +1 -0
  457. package/dist/services/plugin-loader.js +1192 -0
  458. package/dist/services/plugin-loader.js.map +1 -0
  459. package/dist/services/plugin-log-retention.d.ts +20 -0
  460. package/dist/services/plugin-log-retention.d.ts.map +1 -0
  461. package/dist/services/plugin-log-retention.js +63 -0
  462. package/dist/services/plugin-log-retention.js.map +1 -0
  463. package/dist/services/plugin-manifest-validator.d.ts +79 -0
  464. package/dist/services/plugin-manifest-validator.d.ts.map +1 -0
  465. package/dist/services/plugin-manifest-validator.js +84 -0
  466. package/dist/services/plugin-manifest-validator.js.map +1 -0
  467. package/dist/services/plugin-registry.d.ts +2542 -0
  468. package/dist/services/plugin-registry.d.ts.map +1 -0
  469. package/dist/services/plugin-registry.js +539 -0
  470. package/dist/services/plugin-registry.js.map +1 -0
  471. package/dist/services/plugin-runtime-sandbox.d.ts +40 -0
  472. package/dist/services/plugin-runtime-sandbox.d.ts.map +1 -0
  473. package/dist/services/plugin-runtime-sandbox.js +154 -0
  474. package/dist/services/plugin-runtime-sandbox.js.map +1 -0
  475. package/dist/services/plugin-secrets-handler.d.ts +81 -0
  476. package/dist/services/plugin-secrets-handler.d.ts.map +1 -0
  477. package/dist/services/plugin-secrets-handler.js +275 -0
  478. package/dist/services/plugin-secrets-handler.js.map +1 -0
  479. package/dist/services/plugin-state-store.d.ts +92 -0
  480. package/dist/services/plugin-state-store.d.ts.map +1 -0
  481. package/dist/services/plugin-state-store.js +190 -0
  482. package/dist/services/plugin-state-store.js.map +1 -0
  483. package/dist/services/plugin-stream-bus.d.ts +29 -0
  484. package/dist/services/plugin-stream-bus.d.ts.map +1 -0
  485. package/dist/services/plugin-stream-bus.js +48 -0
  486. package/dist/services/plugin-stream-bus.js.map +1 -0
  487. package/dist/services/plugin-tool-dispatcher.d.ts +180 -0
  488. package/dist/services/plugin-tool-dispatcher.d.ts.map +1 -0
  489. package/dist/services/plugin-tool-dispatcher.js +224 -0
  490. package/dist/services/plugin-tool-dispatcher.js.map +1 -0
  491. package/dist/services/plugin-tool-registry.d.ts +192 -0
  492. package/dist/services/plugin-tool-registry.d.ts.map +1 -0
  493. package/dist/services/plugin-tool-registry.js +224 -0
  494. package/dist/services/plugin-tool-registry.js.map +1 -0
  495. package/dist/services/plugin-worker-manager.d.ts +260 -0
  496. package/dist/services/plugin-worker-manager.d.ts.map +1 -0
  497. package/dist/services/plugin-worker-manager.js +835 -0
  498. package/dist/services/plugin-worker-manager.js.map +1 -0
  499. package/dist/services/project-workspace-runtime-config.d.ts +4 -0
  500. package/dist/services/project-workspace-runtime-config.d.ts.map +1 -0
  501. package/dist/services/project-workspace-runtime-config.js +43 -0
  502. package/dist/services/project-workspace-runtime-config.js.map +1 -0
  503. package/dist/services/projects.d.ts +88 -0
  504. package/dist/services/projects.d.ts.map +1 -0
  505. package/dist/services/projects.js +669 -0
  506. package/dist/services/projects.js.map +1 -0
  507. package/dist/services/quota-windows.d.ts +9 -0
  508. package/dist/services/quota-windows.d.ts.map +1 -0
  509. package/dist/services/quota-windows.js +56 -0
  510. package/dist/services/quota-windows.js.map +1 -0
  511. package/dist/services/routines.d.ts +136 -0
  512. package/dist/services/routines.d.ts.map +1 -0
  513. package/dist/services/routines.js +1268 -0
  514. package/dist/services/routines.js.map +1 -0
  515. package/dist/services/run-log-store.d.ts +34 -0
  516. package/dist/services/run-log-store.d.ts.map +1 -0
  517. package/dist/services/run-log-store.js +109 -0
  518. package/dist/services/run-log-store.js.map +1 -0
  519. package/dist/services/secrets.d.ts +511 -0
  520. package/dist/services/secrets.d.ts.map +1 -0
  521. package/dist/services/secrets.js +289 -0
  522. package/dist/services/secrets.js.map +1 -0
  523. package/dist/services/sidebar-badges.d.ts +9 -0
  524. package/dist/services/sidebar-badges.d.ts.map +1 -0
  525. package/dist/services/sidebar-badges.js +33 -0
  526. package/dist/services/sidebar-badges.js.map +1 -0
  527. package/dist/services/work-products.d.ts +14 -0
  528. package/dist/services/work-products.d.ts.map +1 -0
  529. package/dist/services/work-products.js +100 -0
  530. package/dist/services/work-products.js.map +1 -0
  531. package/dist/services/workspace-operation-log-store.d.ts +33 -0
  532. package/dist/services/workspace-operation-log-store.d.ts.map +1 -0
  533. package/dist/services/workspace-operation-log-store.js +110 -0
  534. package/dist/services/workspace-operation-log-store.js.map +1 -0
  535. package/dist/services/workspace-operations.d.ts +44 -0
  536. package/dist/services/workspace-operations.d.ts.map +1 -0
  537. package/dist/services/workspace-operations.js +211 -0
  538. package/dist/services/workspace-operations.js.map +1 -0
  539. package/dist/services/workspace-runtime.d.ts +188 -0
  540. package/dist/services/workspace-runtime.d.ts.map +1 -0
  541. package/dist/services/workspace-runtime.js +1705 -0
  542. package/dist/services/workspace-runtime.js.map +1 -0
  543. package/dist/startup-banner.d.ts +31 -0
  544. package/dist/startup-banner.d.ts.map +1 -0
  545. package/dist/startup-banner.js +117 -0
  546. package/dist/startup-banner.js.map +1 -0
  547. package/dist/storage/index.d.ts +6 -0
  548. package/dist/storage/index.d.ts.map +1 -0
  549. package/dist/storage/index.js +29 -0
  550. package/dist/storage/index.js.map +1 -0
  551. package/dist/storage/local-disk-provider.d.ts +3 -0
  552. package/dist/storage/local-disk-provider.d.ts.map +1 -0
  553. package/dist/storage/local-disk-provider.js +79 -0
  554. package/dist/storage/local-disk-provider.js.map +1 -0
  555. package/dist/storage/provider-registry.d.ts +4 -0
  556. package/dist/storage/provider-registry.d.ts.map +1 -0
  557. package/dist/storage/provider-registry.js +15 -0
  558. package/dist/storage/provider-registry.js.map +1 -0
  559. package/dist/storage/s3-provider.d.ts +11 -0
  560. package/dist/storage/s3-provider.d.ts.map +1 -0
  561. package/dist/storage/s3-provider.js +123 -0
  562. package/dist/storage/s3-provider.js.map +1 -0
  563. package/dist/storage/service.d.ts +3 -0
  564. package/dist/storage/service.d.ts.map +1 -0
  565. package/dist/storage/service.js +120 -0
  566. package/dist/storage/service.js.map +1 -0
  567. package/dist/storage/types.d.ts +55 -0
  568. package/dist/storage/types.d.ts.map +1 -0
  569. package/dist/storage/types.js +2 -0
  570. package/dist/storage/types.js.map +1 -0
  571. package/dist/telemetry.d.ts +6 -0
  572. package/dist/telemetry.d.ts.map +1 -0
  573. package/dist/telemetry.js +20 -0
  574. package/dist/telemetry.js.map +1 -0
  575. package/dist/ui-branding.d.ts +13 -0
  576. package/dist/ui-branding.d.ts.map +1 -0
  577. package/dist/ui-branding.js +187 -0
  578. package/dist/ui-branding.js.map +1 -0
  579. package/dist/version.d.ts +2 -0
  580. package/dist/version.d.ts.map +1 -0
  581. package/dist/version.js +5 -0
  582. package/dist/version.js.map +1 -0
  583. package/dist/worktree-config.d.ts +19 -0
  584. package/dist/worktree-config.d.ts.map +1 -0
  585. package/dist/worktree-config.js +365 -0
  586. package/dist/worktree-config.js.map +1 -0
  587. package/package.json +90 -0
  588. package/ui-dist/android-chrome-192x192.png +0 -0
  589. package/ui-dist/android-chrome-512x512.png +0 -0
  590. package/ui-dist/apple-touch-icon.png +0 -0
  591. package/ui-dist/assets/_basePickBy-D2fZf0o0.js +1 -0
  592. package/ui-dist/assets/_baseUniq-CWLG_Xqf.js +1 -0
  593. package/ui-dist/assets/apl-B4CMkyY2.js +1 -0
  594. package/ui-dist/assets/arc-DeECLtLk.js +1 -0
  595. package/ui-dist/assets/architectureDiagram-VXUJARFQ-a4Gzo-8u.js +36 -0
  596. package/ui-dist/assets/asciiarmor-Df11BRmG.js +1 -0
  597. package/ui-dist/assets/asn1-EdZsLKOL.js +1 -0
  598. package/ui-dist/assets/asterisk-B-8jnY81.js +1 -0
  599. package/ui-dist/assets/blockDiagram-VD42YOAC-tsACr-ke.js +122 -0
  600. package/ui-dist/assets/brainfuck-C4LP7Hcl.js +1 -0
  601. package/ui-dist/assets/c4Diagram-YG6GDRKO-CxIEiGp0.js +10 -0
  602. package/ui-dist/assets/channel-B8aOlvkH.js +1 -0
  603. package/ui-dist/assets/chunk-4BX2VUAB-DXyq1OVE.js +1 -0
  604. package/ui-dist/assets/chunk-55IACEB6-DE0IJwCp.js +1 -0
  605. package/ui-dist/assets/chunk-B4BG7PRW-C4VgkuRm.js +165 -0
  606. package/ui-dist/assets/chunk-DI55MBZ5-DUnVODLP.js +220 -0
  607. package/ui-dist/assets/chunk-FMBD7UC4-D14U1wU8.js +15 -0
  608. package/ui-dist/assets/chunk-QN33PNHL--9Fa_-d7.js +1 -0
  609. package/ui-dist/assets/chunk-QZHKN3VN-6vF9hrbB.js +1 -0
  610. package/ui-dist/assets/chunk-TZMSLE5B-BOKdVMfQ.js +1 -0
  611. package/ui-dist/assets/classDiagram-2ON5EDUG-CNtVQyHf.js +1 -0
  612. package/ui-dist/assets/classDiagram-v2-WZHVMYZB-CNtVQyHf.js +1 -0
  613. package/ui-dist/assets/clike-B9uivgTg.js +1 -0
  614. package/ui-dist/assets/clojure-BMjYHr_A.js +1 -0
  615. package/ui-dist/assets/clone-DbnZAhBs.js +1 -0
  616. package/ui-dist/assets/cmake-BQqOBYOt.js +1 -0
  617. package/ui-dist/assets/cobol-CWcv1MsR.js +1 -0
  618. package/ui-dist/assets/coffeescript-S37ZYGWr.js +1 -0
  619. package/ui-dist/assets/commonlisp-DBKNyK5s.js +1 -0
  620. package/ui-dist/assets/cose-bilkent-S5V4N54A-BSFYrItZ.js +1 -0
  621. package/ui-dist/assets/crystal-SjHAIU92.js +1 -0
  622. package/ui-dist/assets/css-BnMrqG3P.js +1 -0
  623. package/ui-dist/assets/cypher-C_CwsFkJ.js +1 -0
  624. package/ui-dist/assets/cytoscape.esm-BQaXIfA_.js +331 -0
  625. package/ui-dist/assets/d-pRatUO7H.js +1 -0
  626. package/ui-dist/assets/dagre-6UL2VRFP-D8xEb_jz.js +4 -0
  627. package/ui-dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  628. package/ui-dist/assets/diagram-PSM6KHXK-kkdfif_P.js +24 -0
  629. package/ui-dist/assets/diagram-QEK2KX5R-BOA1wTkn.js +43 -0
  630. package/ui-dist/assets/diagram-S2PKOQOG-BMxIrdD_.js +24 -0
  631. package/ui-dist/assets/diff-DbItnlRl.js +1 -0
  632. package/ui-dist/assets/dockerfile-BKs6k2Af.js +1 -0
  633. package/ui-dist/assets/dtd-DF_7sFjM.js +1 -0
  634. package/ui-dist/assets/dylan-DwRh75JA.js +1 -0
  635. package/ui-dist/assets/ebnf-CDyGwa7X.js +1 -0
  636. package/ui-dist/assets/ecl-Cabwm37j.js +1 -0
  637. package/ui-dist/assets/eiffel-CnydiIhH.js +1 -0
  638. package/ui-dist/assets/elm-vLlmbW-K.js +1 -0
  639. package/ui-dist/assets/erDiagram-Q2GNP2WA-O1OwJuJt.js +60 -0
  640. package/ui-dist/assets/erlang-BNw1qcRV.js +1 -0
  641. package/ui-dist/assets/factor-kuTfRLto.js +1 -0
  642. package/ui-dist/assets/fcl-Kvtd6kyn.js +1 -0
  643. package/ui-dist/assets/flowDiagram-NV44I4VS-Ca9TsVEe.js +162 -0
  644. package/ui-dist/assets/forth-Ffai-XNe.js +1 -0
  645. package/ui-dist/assets/fortran-DYz_wnZ1.js +1 -0
  646. package/ui-dist/assets/ganttDiagram-JELNMOA3-CZ3k4lR4.js +267 -0
  647. package/ui-dist/assets/gas-Bneqetm1.js +1 -0
  648. package/ui-dist/assets/gherkin-heZmZLOM.js +1 -0
  649. package/ui-dist/assets/gitGraphDiagram-V2S2FVAM-CYXpu5VF.js +65 -0
  650. package/ui-dist/assets/graph-Ct1Mwc2w.js +1 -0
  651. package/ui-dist/assets/groovy-D9Dt4D0W.js +1 -0
  652. package/ui-dist/assets/haskell-Cw1EW3IL.js +1 -0
  653. package/ui-dist/assets/haxe-H-WmDvRZ.js +1 -0
  654. package/ui-dist/assets/http-DBlCnlav.js +1 -0
  655. package/ui-dist/assets/idl-BEugSyMb.js +1 -0
  656. package/ui-dist/assets/index-1-xGTyG8.js +1 -0
  657. package/ui-dist/assets/index-2BV3lexL.js +1 -0
  658. package/ui-dist/assets/index-7nZVjhgZ.js +1227 -0
  659. package/ui-dist/assets/index-B3cKIXfV.js +1 -0
  660. package/ui-dist/assets/index-BKVrlVnm.js +1 -0
  661. package/ui-dist/assets/index-BoQp7qpa.js +6 -0
  662. package/ui-dist/assets/index-Bso7Ph2z.js +1 -0
  663. package/ui-dist/assets/index-C016T1QA.js +1 -0
  664. package/ui-dist/assets/index-CKShy7qg.js +1 -0
  665. package/ui-dist/assets/index-CTeTAOP4.js +2 -0
  666. package/ui-dist/assets/index-CU0xjnJx.js +1 -0
  667. package/ui-dist/assets/index-CX3xG_nU.js +1 -0
  668. package/ui-dist/assets/index-ChGANpP5.js +1 -0
  669. package/ui-dist/assets/index-Cyi4EPvJ.js +13 -0
  670. package/ui-dist/assets/index-D4ERoZ49.js +1 -0
  671. package/ui-dist/assets/index-DAhE9Tae.js +1 -0
  672. package/ui-dist/assets/index-DYf-Vmt-.js +1 -0
  673. package/ui-dist/assets/index-DZ5z-1W5.js +1 -0
  674. package/ui-dist/assets/index-Dbx0Jq5s.js +1 -0
  675. package/ui-dist/assets/index-DcJU9_Xw.js +3 -0
  676. package/ui-dist/assets/index-DzvAUKSy.js +1 -0
  677. package/ui-dist/assets/index-Ip5TfL79.css +1 -0
  678. package/ui-dist/assets/index-b-wjeoWr.js +7 -0
  679. package/ui-dist/assets/index-zAFj8SiW.js +1 -0
  680. package/ui-dist/assets/infoDiagram-HS3SLOUP-BBoge3td.js +2 -0
  681. package/ui-dist/assets/init-Gi6I4Gst.js +1 -0
  682. package/ui-dist/assets/javascript-iXu5QeM3.js +1 -0
  683. package/ui-dist/assets/journeyDiagram-XKPGCS4Q-D6qBxJvS.js +139 -0
  684. package/ui-dist/assets/julia-DuME0IfC.js +1 -0
  685. package/ui-dist/assets/kanban-definition-3W4ZIXB7-BKw70hff.js +89 -0
  686. package/ui-dist/assets/katex-O9d3_IXG.js +261 -0
  687. package/ui-dist/assets/layout-C-MelZ5a.js +1 -0
  688. package/ui-dist/assets/linear-BDJBF_lN.js +1 -0
  689. package/ui-dist/assets/livescript-BwQOo05w.js +1 -0
  690. package/ui-dist/assets/lua-BgMRiT3U.js +1 -0
  691. package/ui-dist/assets/mathematica-DTrFuWx2.js +1 -0
  692. package/ui-dist/assets/mbox-CNhZ1qSd.js +1 -0
  693. package/ui-dist/assets/mermaid.core-C_LSSro9.js +256 -0
  694. package/ui-dist/assets/mindmap-definition-VGOIOE7T-DKTz8YjL.js +68 -0
  695. package/ui-dist/assets/mirc-CjQqDB4T.js +1 -0
  696. package/ui-dist/assets/mllike-CXdrOF99.js +1 -0
  697. package/ui-dist/assets/modelica-Dc1JOy9r.js +1 -0
  698. package/ui-dist/assets/mscgen-BA5vi2Kp.js +1 -0
  699. package/ui-dist/assets/mumps-BT43cFF4.js +1 -0
  700. package/ui-dist/assets/nginx-DdIZxoE0.js +1 -0
  701. package/ui-dist/assets/nsis-LdVXkNf5.js +1 -0
  702. package/ui-dist/assets/ntriples-BfvgReVJ.js +1 -0
  703. package/ui-dist/assets/octave-Ck1zUtKM.js +1 -0
  704. package/ui-dist/assets/ordinal-Cboi1Yqb.js +1 -0
  705. package/ui-dist/assets/oz-BzwKVEFT.js +1 -0
  706. package/ui-dist/assets/pascal--L3eBynH.js +1 -0
  707. package/ui-dist/assets/perl-CdXCOZ3F.js +1 -0
  708. package/ui-dist/assets/pieDiagram-ADFJNKIX-COzMH2F2.js +30 -0
  709. package/ui-dist/assets/pig-CevX1Tat.js +1 -0
  710. package/ui-dist/assets/powershell-CFHJl5sT.js +1 -0
  711. package/ui-dist/assets/properties-C78fOPTZ.js +1 -0
  712. package/ui-dist/assets/protobuf-ChK-085T.js +1 -0
  713. package/ui-dist/assets/pug-DeIclll2.js +1 -0
  714. package/ui-dist/assets/puppet-DMA9R1ak.js +1 -0
  715. package/ui-dist/assets/python-BuPzkPfP.js +1 -0
  716. package/ui-dist/assets/q-pXgVlZs6.js +1 -0
  717. package/ui-dist/assets/quadrantDiagram-AYHSOK5B-I8FRTFaI.js +7 -0
  718. package/ui-dist/assets/r-B6wPVr8A.js +1 -0
  719. package/ui-dist/assets/requirementDiagram-UZGBJVZJ-DI3nQy2l.js +64 -0
  720. package/ui-dist/assets/rpm-CTu-6PCP.js +1 -0
  721. package/ui-dist/assets/ruby-B2Rjki9n.js +1 -0
  722. package/ui-dist/assets/sankeyDiagram-TZEHDZUN-Pf_PmYIt.js +10 -0
  723. package/ui-dist/assets/sas-B4kiWyti.js +1 -0
  724. package/ui-dist/assets/scheme-C41bIUwD.js +1 -0
  725. package/ui-dist/assets/sequenceDiagram-WL72ISMW-DLgaS7kB.js +145 -0
  726. package/ui-dist/assets/shell-CjFT_Tl9.js +1 -0
  727. package/ui-dist/assets/sieve-C3Gn_uJK.js +1 -0
  728. package/ui-dist/assets/simple-mode-GW_nhZxv.js +1 -0
  729. package/ui-dist/assets/smalltalk-CnHTOXQT.js +1 -0
  730. package/ui-dist/assets/solr-DehyRSwq.js +1 -0
  731. package/ui-dist/assets/sparql-DkYu6x3z.js +1 -0
  732. package/ui-dist/assets/spreadsheet-BCZA_wO0.js +1 -0
  733. package/ui-dist/assets/sql-D0XecflT.js +1 -0
  734. package/ui-dist/assets/stateDiagram-FKZM4ZOC-BvMa1uU-.js +1 -0
  735. package/ui-dist/assets/stateDiagram-v2-4FDKWEC3-BKAZNp5l.js +1 -0
  736. package/ui-dist/assets/stex-C3f8Ysf7.js +1 -0
  737. package/ui-dist/assets/stylus-B533Al4x.js +1 -0
  738. package/ui-dist/assets/swift-BzpIVaGY.js +1 -0
  739. package/ui-dist/assets/tcl-DVfN8rqt.js +1 -0
  740. package/ui-dist/assets/textile-CnDTJFAw.js +1 -0
  741. package/ui-dist/assets/tiddlywiki-DO-Gjzrf.js +1 -0
  742. package/ui-dist/assets/tiki-DGYXhP31.js +1 -0
  743. package/ui-dist/assets/timeline-definition-IT6M3QCI-JQUCAkOQ.js +61 -0
  744. package/ui-dist/assets/toml-Bm5Em-hy.js +1 -0
  745. package/ui-dist/assets/treemap-GDKQZRPO-BK87qA0V.js +162 -0
  746. package/ui-dist/assets/troff-wAsdV37c.js +1 -0
  747. package/ui-dist/assets/ttcn-CfJYG6tj.js +1 -0
  748. package/ui-dist/assets/ttcn-cfg-B9xdYoR4.js +1 -0
  749. package/ui-dist/assets/turtle-B1tBg_DP.js +1 -0
  750. package/ui-dist/assets/vb-CmGdzxic.js +1 -0
  751. package/ui-dist/assets/vbscript-BuJXcnF6.js +1 -0
  752. package/ui-dist/assets/velocity-D8B20fx6.js +1 -0
  753. package/ui-dist/assets/verilog-C6RDOZhf.js +1 -0
  754. package/ui-dist/assets/vhdl-lSbBsy5d.js +1 -0
  755. package/ui-dist/assets/webidl-ZXfAyPTL.js +1 -0
  756. package/ui-dist/assets/xquery-DzFWVndE.js +1 -0
  757. package/ui-dist/assets/xychartDiagram-PRI3JC2R-UI3XP-6x.js +7 -0
  758. package/ui-dist/assets/yacas-BJ4BC0dw.js +1 -0
  759. package/ui-dist/assets/z80-Hz9HOZM7.js +1 -0
  760. package/ui-dist/brands/opencode-logo-dark-square.svg +18 -0
  761. package/ui-dist/brands/opencode-logo-light-square.svg +18 -0
  762. package/ui-dist/favicon-16x16.png +0 -0
  763. package/ui-dist/favicon-32x32.png +0 -0
  764. package/ui-dist/favicon.ico +0 -0
  765. package/ui-dist/favicon.svg +9 -0
  766. package/ui-dist/index.html +49 -0
  767. package/ui-dist/site.webmanifest +30 -0
  768. package/ui-dist/sw.js +42 -0
  769. package/ui-dist/worktree-favicon-16x16.png +0 -0
  770. package/ui-dist/worktree-favicon-32x32.png +0 -0
  771. package/ui-dist/worktree-favicon.ico +0 -0
  772. package/ui-dist/worktree-favicon.svg +9 -0
@@ -0,0 +1,1834 @@
1
+ import { Router } from "express";
2
+ import multer from "multer";
3
+ import { z } from "zod";
4
+ import { addIssueCommentSchema, createIssueAttachmentMetadataSchema, createIssueWorkProductSchema, createIssueLabelSchema, checkoutIssueSchema, createIssueSchema, feedbackTargetTypeSchema, feedbackTraceStatusSchema, feedbackVoteValueSchema, upsertIssueFeedbackVoteSchema, linkIssueApprovalSchema, issueDocumentKeySchema, restoreIssueDocumentRevisionSchema, updateIssueWorkProductSchema, upsertIssueDocumentSchema, updateIssueSchema, } from "@zmeel/shared";
5
+ import { trackAgentTaskCompleted } from "@zmeel/shared/telemetry";
6
+ import { getTelemetryClient } from "../telemetry.js";
7
+ import { validate } from "../middleware/validate.js";
8
+ import { accessService, agentService, executionWorkspaceService, feedbackService, goalService, heartbeatService, instanceSettingsService, issueApprovalService, issueService, documentService, logActivity, projectService, routineService, workProductService, } from "../services/index.js";
9
+ import { logger } from "../middleware/logger.js";
10
+ import { forbidden, HttpError, unauthorized } from "../errors.js";
11
+ import { assertCompanyAccess, getActorInfo } from "./authz.js";
12
+ import { shouldWakeAssigneeOnCheckout } from "./issues-checkout-wakeup.js";
13
+ import { isAllowedContentType, MAX_ATTACHMENT_BYTES } from "../attachment-types.js";
14
+ import { queueIssueAssignmentWakeup } from "../services/issue-assignment-wakeup.js";
15
+ const MAX_ISSUE_COMMENT_LIMIT = 500;
16
+ const updateIssueRouteSchema = updateIssueSchema.extend({
17
+ interrupt: z.boolean().optional(),
18
+ });
19
+ export function issueRoutes(db, storage) {
20
+ const router = Router();
21
+ const svc = issueService(db);
22
+ const access = accessService(db);
23
+ const heartbeat = heartbeatService(db);
24
+ const feedback = feedbackService(db);
25
+ const instanceSettings = instanceSettingsService(db);
26
+ const agentsSvc = agentService(db);
27
+ const projectsSvc = projectService(db);
28
+ const goalsSvc = goalService(db);
29
+ const issueApprovalsSvc = issueApprovalService(db);
30
+ const executionWorkspacesSvc = executionWorkspaceService(db);
31
+ const workProductsSvc = workProductService(db);
32
+ const documentsSvc = documentService(db);
33
+ const routinesSvc = routineService(db);
34
+ const upload = multer({
35
+ storage: multer.memoryStorage(),
36
+ limits: { fileSize: MAX_ATTACHMENT_BYTES, files: 1 },
37
+ });
38
+ function withContentPath(attachment) {
39
+ return {
40
+ ...attachment,
41
+ contentPath: `/api/attachments/${attachment.id}/content`,
42
+ };
43
+ }
44
+ function parseBooleanQuery(value) {
45
+ return value === true || value === "true" || value === "1";
46
+ }
47
+ function parseDateQuery(value, field) {
48
+ if (typeof value !== "string" || value.trim().length === 0)
49
+ return undefined;
50
+ const parsed = new Date(value);
51
+ if (Number.isNaN(parsed.getTime())) {
52
+ throw new HttpError(400, `Invalid ${field} query value`);
53
+ }
54
+ return parsed;
55
+ }
56
+ async function runSingleFileUpload(req, res) {
57
+ await new Promise((resolve, reject) => {
58
+ upload.single("file")(req, res, (err) => {
59
+ if (err)
60
+ reject(err);
61
+ else
62
+ resolve();
63
+ });
64
+ });
65
+ }
66
+ async function assertCanManageIssueApprovalLinks(req, res, companyId) {
67
+ assertCompanyAccess(req, companyId);
68
+ if (req.actor.type === "board")
69
+ return true;
70
+ if (!req.actor.agentId) {
71
+ res.status(403).json({ error: "Agent authentication required" });
72
+ return false;
73
+ }
74
+ const actorAgent = await agentsSvc.getById(req.actor.agentId);
75
+ if (!actorAgent || actorAgent.companyId !== companyId) {
76
+ res.status(403).json({ error: "Forbidden" });
77
+ return false;
78
+ }
79
+ if (actorAgent.role === "ceo" || Boolean(actorAgent.permissions?.canCreateAgents))
80
+ return true;
81
+ res.status(403).json({ error: "Missing permission to link approvals" });
82
+ return false;
83
+ }
84
+ function actorCanAccessCompany(req, companyId) {
85
+ if (req.actor.type === "none")
86
+ return false;
87
+ if (req.actor.type === "agent")
88
+ return req.actor.companyId === companyId;
89
+ if (req.actor.source === "local_implicit" || req.actor.isInstanceAdmin)
90
+ return true;
91
+ return (req.actor.companyIds ?? []).includes(companyId);
92
+ }
93
+ function canCreateAgentsLegacy(agent) {
94
+ if (agent.role === "ceo")
95
+ return true;
96
+ if (!agent.permissions || typeof agent.permissions !== "object")
97
+ return false;
98
+ return Boolean(agent.permissions.canCreateAgents);
99
+ }
100
+ async function assertCanAssignTasks(req, companyId) {
101
+ assertCompanyAccess(req, companyId);
102
+ if (req.actor.type === "board") {
103
+ if (req.actor.source === "local_implicit" || req.actor.isInstanceAdmin)
104
+ return;
105
+ const allowed = await access.canUser(companyId, req.actor.userId, "tasks:assign");
106
+ if (!allowed)
107
+ throw forbidden("Missing permission: tasks:assign");
108
+ return;
109
+ }
110
+ if (req.actor.type === "agent") {
111
+ if (!req.actor.agentId)
112
+ throw forbidden("Agent authentication required");
113
+ const allowedByGrant = await access.hasPermission(companyId, "agent", req.actor.agentId, "tasks:assign");
114
+ if (allowedByGrant)
115
+ return;
116
+ const actorAgent = await agentsSvc.getById(req.actor.agentId);
117
+ if (actorAgent && actorAgent.companyId === companyId && canCreateAgentsLegacy(actorAgent))
118
+ return;
119
+ throw forbidden("Missing permission: tasks:assign");
120
+ }
121
+ throw unauthorized();
122
+ }
123
+ function requireAgentRunId(req, res) {
124
+ if (req.actor.type !== "agent")
125
+ return null;
126
+ const runId = req.actor.runId?.trim();
127
+ if (runId)
128
+ return runId;
129
+ res.status(401).json({ error: "Agent run id required" });
130
+ return null;
131
+ }
132
+ async function assertAgentRunCheckoutOwnership(req, res, issue) {
133
+ if (req.actor.type !== "agent")
134
+ return true;
135
+ const actorAgentId = req.actor.agentId;
136
+ if (!actorAgentId) {
137
+ res.status(403).json({ error: "Agent authentication required" });
138
+ return false;
139
+ }
140
+ if (issue.status !== "in_progress" || issue.assigneeAgentId !== actorAgentId) {
141
+ return true;
142
+ }
143
+ const runId = requireAgentRunId(req, res);
144
+ if (!runId)
145
+ return false;
146
+ const ownership = await svc.assertCheckoutOwner(issue.id, actorAgentId, runId);
147
+ if (ownership.adoptedFromRunId) {
148
+ const actor = getActorInfo(req);
149
+ await logActivity(db, {
150
+ companyId: issue.companyId,
151
+ actorType: actor.actorType,
152
+ actorId: actor.actorId,
153
+ agentId: actor.agentId,
154
+ runId: actor.runId,
155
+ action: "issue.checkout_lock_adopted",
156
+ entityType: "issue",
157
+ entityId: issue.id,
158
+ details: {
159
+ previousCheckoutRunId: ownership.adoptedFromRunId,
160
+ checkoutRunId: runId,
161
+ reason: "stale_checkout_run",
162
+ },
163
+ });
164
+ }
165
+ return true;
166
+ }
167
+ async function resolveActiveIssueRun(issue) {
168
+ let runToInterrupt = issue.executionRunId ? await heartbeat.getRun(issue.executionRunId) : null;
169
+ if ((!runToInterrupt || runToInterrupt.status !== "running") && issue.assigneeAgentId) {
170
+ const activeRun = await heartbeat.getActiveRunForAgent(issue.assigneeAgentId);
171
+ const activeIssueId = activeRun &&
172
+ activeRun.contextSnapshot &&
173
+ typeof activeRun.contextSnapshot === "object" &&
174
+ typeof activeRun.contextSnapshot.issueId === "string"
175
+ ? activeRun.contextSnapshot.issueId
176
+ : null;
177
+ if (activeRun && activeRun.status === "running" && activeIssueId === issue.id) {
178
+ runToInterrupt = activeRun;
179
+ }
180
+ }
181
+ return runToInterrupt?.status === "running" ? runToInterrupt : null;
182
+ }
183
+ async function normalizeIssueIdentifier(rawId) {
184
+ if (/^[A-Z]+-\d+$/i.test(rawId)) {
185
+ const issue = await svc.getByIdentifier(rawId);
186
+ if (issue) {
187
+ return issue.id;
188
+ }
189
+ }
190
+ return rawId;
191
+ }
192
+ async function resolveIssueProjectAndGoal(issue) {
193
+ const projectPromise = issue.projectId ? projectsSvc.getById(issue.projectId) : Promise.resolve(null);
194
+ const directGoalPromise = issue.goalId ? goalsSvc.getById(issue.goalId) : Promise.resolve(null);
195
+ const [project, directGoal] = await Promise.all([projectPromise, directGoalPromise]);
196
+ if (directGoal) {
197
+ return { project, goal: directGoal };
198
+ }
199
+ const projectGoalId = project?.goalId ?? project?.goalIds[0] ?? null;
200
+ if (projectGoalId) {
201
+ const projectGoal = await goalsSvc.getById(projectGoalId);
202
+ return { project, goal: projectGoal };
203
+ }
204
+ if (!issue.projectId) {
205
+ const defaultGoal = await goalsSvc.getDefaultCompanyGoal(issue.companyId);
206
+ return { project, goal: defaultGoal };
207
+ }
208
+ return { project, goal: null };
209
+ }
210
+ // Resolve issue identifiers (e.g. "PAP-39") to UUIDs for all /issues/:id routes
211
+ router.param("id", async (req, res, next, rawId) => {
212
+ try {
213
+ req.params.id = await normalizeIssueIdentifier(rawId);
214
+ next();
215
+ }
216
+ catch (err) {
217
+ next(err);
218
+ }
219
+ });
220
+ // Resolve issue identifiers (e.g. "PAP-39") to UUIDs for company-scoped attachment routes.
221
+ router.param("issueId", async (req, res, next, rawId) => {
222
+ try {
223
+ req.params.issueId = await normalizeIssueIdentifier(rawId);
224
+ next();
225
+ }
226
+ catch (err) {
227
+ next(err);
228
+ }
229
+ });
230
+ // Common malformed path when companyId is empty in "/api/companies/{companyId}/issues".
231
+ router.get("/issues", (_req, res) => {
232
+ res.status(400).json({
233
+ error: "Missing companyId in path. Use /api/companies/{companyId}/issues.",
234
+ });
235
+ });
236
+ router.get("/companies/:companyId/issues", async (req, res) => {
237
+ const companyId = req.params.companyId;
238
+ assertCompanyAccess(req, companyId);
239
+ const assigneeUserFilterRaw = req.query.assigneeUserId;
240
+ const touchedByUserFilterRaw = req.query.touchedByUserId;
241
+ const inboxArchivedByUserFilterRaw = req.query.inboxArchivedByUserId;
242
+ const unreadForUserFilterRaw = req.query.unreadForUserId;
243
+ const assigneeUserId = assigneeUserFilterRaw === "me" && req.actor.type === "board"
244
+ ? req.actor.userId
245
+ : assigneeUserFilterRaw;
246
+ const touchedByUserId = touchedByUserFilterRaw === "me" && req.actor.type === "board"
247
+ ? req.actor.userId
248
+ : touchedByUserFilterRaw;
249
+ const inboxArchivedByUserId = inboxArchivedByUserFilterRaw === "me" && req.actor.type === "board"
250
+ ? req.actor.userId
251
+ : inboxArchivedByUserFilterRaw;
252
+ const unreadForUserId = unreadForUserFilterRaw === "me" && req.actor.type === "board"
253
+ ? req.actor.userId
254
+ : unreadForUserFilterRaw;
255
+ if (assigneeUserFilterRaw === "me" && (!assigneeUserId || req.actor.type !== "board")) {
256
+ res.status(403).json({ error: "assigneeUserId=me requires board authentication" });
257
+ return;
258
+ }
259
+ if (touchedByUserFilterRaw === "me" && (!touchedByUserId || req.actor.type !== "board")) {
260
+ res.status(403).json({ error: "touchedByUserId=me requires board authentication" });
261
+ return;
262
+ }
263
+ if (inboxArchivedByUserFilterRaw === "me" && (!inboxArchivedByUserId || req.actor.type !== "board")) {
264
+ res.status(403).json({ error: "inboxArchivedByUserId=me requires board authentication" });
265
+ return;
266
+ }
267
+ if (unreadForUserFilterRaw === "me" && (!unreadForUserId || req.actor.type !== "board")) {
268
+ res.status(403).json({ error: "unreadForUserId=me requires board authentication" });
269
+ return;
270
+ }
271
+ const result = await svc.list(companyId, {
272
+ status: req.query.status,
273
+ assigneeAgentId: req.query.assigneeAgentId,
274
+ participantAgentId: req.query.participantAgentId,
275
+ assigneeUserId,
276
+ touchedByUserId,
277
+ inboxArchivedByUserId,
278
+ unreadForUserId,
279
+ projectId: req.query.projectId,
280
+ executionWorkspaceId: req.query.executionWorkspaceId,
281
+ parentId: req.query.parentId,
282
+ labelId: req.query.labelId,
283
+ originKind: req.query.originKind,
284
+ originId: req.query.originId,
285
+ includeRoutineExecutions: req.query.includeRoutineExecutions === "true" || req.query.includeRoutineExecutions === "1",
286
+ q: req.query.q,
287
+ });
288
+ res.json(result);
289
+ });
290
+ router.get("/companies/:companyId/labels", async (req, res) => {
291
+ const companyId = req.params.companyId;
292
+ assertCompanyAccess(req, companyId);
293
+ const result = await svc.listLabels(companyId);
294
+ res.json(result);
295
+ });
296
+ router.post("/companies/:companyId/labels", validate(createIssueLabelSchema), async (req, res) => {
297
+ const companyId = req.params.companyId;
298
+ assertCompanyAccess(req, companyId);
299
+ const label = await svc.createLabel(companyId, req.body);
300
+ const actor = getActorInfo(req);
301
+ await logActivity(db, {
302
+ companyId,
303
+ actorType: actor.actorType,
304
+ actorId: actor.actorId,
305
+ agentId: actor.agentId,
306
+ runId: actor.runId,
307
+ action: "label.created",
308
+ entityType: "label",
309
+ entityId: label.id,
310
+ details: { name: label.name, color: label.color },
311
+ });
312
+ res.status(201).json(label);
313
+ });
314
+ router.delete("/labels/:labelId", async (req, res) => {
315
+ const labelId = req.params.labelId;
316
+ const existing = await svc.getLabelById(labelId);
317
+ if (!existing) {
318
+ res.status(404).json({ error: "Label not found" });
319
+ return;
320
+ }
321
+ assertCompanyAccess(req, existing.companyId);
322
+ const removed = await svc.deleteLabel(labelId);
323
+ if (!removed) {
324
+ res.status(404).json({ error: "Label not found" });
325
+ return;
326
+ }
327
+ const actor = getActorInfo(req);
328
+ await logActivity(db, {
329
+ companyId: removed.companyId,
330
+ actorType: actor.actorType,
331
+ actorId: actor.actorId,
332
+ agentId: actor.agentId,
333
+ runId: actor.runId,
334
+ action: "label.deleted",
335
+ entityType: "label",
336
+ entityId: removed.id,
337
+ details: { name: removed.name, color: removed.color },
338
+ });
339
+ res.json(removed);
340
+ });
341
+ router.get("/issues/:id", async (req, res) => {
342
+ const id = req.params.id;
343
+ const issue = await svc.getById(id);
344
+ if (!issue) {
345
+ res.status(404).json({ error: "Issue not found" });
346
+ return;
347
+ }
348
+ assertCompanyAccess(req, issue.companyId);
349
+ const [{ project, goal }, ancestors, mentionedProjectIds, documentPayload] = await Promise.all([
350
+ resolveIssueProjectAndGoal(issue),
351
+ svc.getAncestors(issue.id),
352
+ svc.findMentionedProjectIds(issue.id),
353
+ documentsSvc.getIssueDocumentPayload(issue),
354
+ ]);
355
+ const mentionedProjects = mentionedProjectIds.length > 0
356
+ ? await projectsSvc.listByIds(issue.companyId, mentionedProjectIds)
357
+ : [];
358
+ const currentExecutionWorkspace = issue.executionWorkspaceId
359
+ ? await executionWorkspacesSvc.getById(issue.executionWorkspaceId)
360
+ : null;
361
+ const workProducts = await workProductsSvc.listForIssue(issue.id);
362
+ res.json({
363
+ ...issue,
364
+ goalId: goal?.id ?? issue.goalId,
365
+ ancestors,
366
+ ...documentPayload,
367
+ project: project ?? null,
368
+ goal: goal ?? null,
369
+ mentionedProjects,
370
+ currentExecutionWorkspace,
371
+ workProducts,
372
+ });
373
+ });
374
+ router.get("/issues/:id/heartbeat-context", async (req, res) => {
375
+ const id = req.params.id;
376
+ const issue = await svc.getById(id);
377
+ if (!issue) {
378
+ res.status(404).json({ error: "Issue not found" });
379
+ return;
380
+ }
381
+ assertCompanyAccess(req, issue.companyId);
382
+ const wakeCommentId = typeof req.query.wakeCommentId === "string" && req.query.wakeCommentId.trim().length > 0
383
+ ? req.query.wakeCommentId.trim()
384
+ : null;
385
+ const [{ project, goal }, ancestors, commentCursor, wakeComment] = await Promise.all([
386
+ resolveIssueProjectAndGoal(issue),
387
+ svc.getAncestors(issue.id),
388
+ svc.getCommentCursor(issue.id),
389
+ wakeCommentId ? svc.getComment(wakeCommentId) : null,
390
+ ]);
391
+ res.json({
392
+ issue: {
393
+ id: issue.id,
394
+ identifier: issue.identifier,
395
+ title: issue.title,
396
+ description: issue.description,
397
+ status: issue.status,
398
+ priority: issue.priority,
399
+ projectId: issue.projectId,
400
+ goalId: goal?.id ?? issue.goalId,
401
+ parentId: issue.parentId,
402
+ assigneeAgentId: issue.assigneeAgentId,
403
+ assigneeUserId: issue.assigneeUserId,
404
+ updatedAt: issue.updatedAt,
405
+ },
406
+ ancestors: ancestors.map((ancestor) => ({
407
+ id: ancestor.id,
408
+ identifier: ancestor.identifier,
409
+ title: ancestor.title,
410
+ status: ancestor.status,
411
+ priority: ancestor.priority,
412
+ })),
413
+ project: project
414
+ ? {
415
+ id: project.id,
416
+ name: project.name,
417
+ status: project.status,
418
+ targetDate: project.targetDate,
419
+ }
420
+ : null,
421
+ goal: goal
422
+ ? {
423
+ id: goal.id,
424
+ title: goal.title,
425
+ status: goal.status,
426
+ level: goal.level,
427
+ parentId: goal.parentId,
428
+ }
429
+ : null,
430
+ commentCursor,
431
+ wakeComment: wakeComment && wakeComment.issueId === issue.id
432
+ ? wakeComment
433
+ : null,
434
+ });
435
+ });
436
+ router.get("/issues/:id/work-products", async (req, res) => {
437
+ const id = req.params.id;
438
+ const issue = await svc.getById(id);
439
+ if (!issue) {
440
+ res.status(404).json({ error: "Issue not found" });
441
+ return;
442
+ }
443
+ assertCompanyAccess(req, issue.companyId);
444
+ const workProducts = await workProductsSvc.listForIssue(issue.id);
445
+ res.json(workProducts);
446
+ });
447
+ router.get("/issues/:id/documents", async (req, res) => {
448
+ const id = req.params.id;
449
+ const issue = await svc.getById(id);
450
+ if (!issue) {
451
+ res.status(404).json({ error: "Issue not found" });
452
+ return;
453
+ }
454
+ assertCompanyAccess(req, issue.companyId);
455
+ const docs = await documentsSvc.listIssueDocuments(issue.id);
456
+ res.json(docs);
457
+ });
458
+ router.get("/issues/:id/documents/:key", async (req, res) => {
459
+ const id = req.params.id;
460
+ const issue = await svc.getById(id);
461
+ if (!issue) {
462
+ res.status(404).json({ error: "Issue not found" });
463
+ return;
464
+ }
465
+ assertCompanyAccess(req, issue.companyId);
466
+ const keyParsed = issueDocumentKeySchema.safeParse(String(req.params.key ?? "").trim().toLowerCase());
467
+ if (!keyParsed.success) {
468
+ res.status(400).json({ error: "Invalid document key", details: keyParsed.error.issues });
469
+ return;
470
+ }
471
+ const doc = await documentsSvc.getIssueDocumentByKey(issue.id, keyParsed.data);
472
+ if (!doc) {
473
+ res.status(404).json({ error: "Document not found" });
474
+ return;
475
+ }
476
+ res.json(doc);
477
+ });
478
+ router.put("/issues/:id/documents/:key", validate(upsertIssueDocumentSchema), async (req, res) => {
479
+ const id = req.params.id;
480
+ const issue = await svc.getById(id);
481
+ if (!issue) {
482
+ res.status(404).json({ error: "Issue not found" });
483
+ return;
484
+ }
485
+ assertCompanyAccess(req, issue.companyId);
486
+ const keyParsed = issueDocumentKeySchema.safeParse(String(req.params.key ?? "").trim().toLowerCase());
487
+ if (!keyParsed.success) {
488
+ res.status(400).json({ error: "Invalid document key", details: keyParsed.error.issues });
489
+ return;
490
+ }
491
+ const actor = getActorInfo(req);
492
+ const result = await documentsSvc.upsertIssueDocument({
493
+ issueId: issue.id,
494
+ key: keyParsed.data,
495
+ title: req.body.title ?? null,
496
+ format: req.body.format,
497
+ body: req.body.body,
498
+ changeSummary: req.body.changeSummary ?? null,
499
+ baseRevisionId: req.body.baseRevisionId ?? null,
500
+ createdByAgentId: actor.agentId ?? null,
501
+ createdByUserId: actor.actorType === "user" ? actor.actorId : null,
502
+ createdByRunId: actor.runId ?? null,
503
+ });
504
+ const doc = result.document;
505
+ await logActivity(db, {
506
+ companyId: issue.companyId,
507
+ actorType: actor.actorType,
508
+ actorId: actor.actorId,
509
+ agentId: actor.agentId,
510
+ runId: actor.runId,
511
+ action: result.created ? "issue.document_created" : "issue.document_updated",
512
+ entityType: "issue",
513
+ entityId: issue.id,
514
+ details: {
515
+ key: doc.key,
516
+ documentId: doc.id,
517
+ title: doc.title,
518
+ format: doc.format,
519
+ revisionNumber: doc.latestRevisionNumber,
520
+ },
521
+ });
522
+ res.status(result.created ? 201 : 200).json(doc);
523
+ });
524
+ router.get("/issues/:id/documents/:key/revisions", async (req, res) => {
525
+ const id = req.params.id;
526
+ const issue = await svc.getById(id);
527
+ if (!issue) {
528
+ res.status(404).json({ error: "Issue not found" });
529
+ return;
530
+ }
531
+ assertCompanyAccess(req, issue.companyId);
532
+ const keyParsed = issueDocumentKeySchema.safeParse(String(req.params.key ?? "").trim().toLowerCase());
533
+ if (!keyParsed.success) {
534
+ res.status(400).json({ error: "Invalid document key", details: keyParsed.error.issues });
535
+ return;
536
+ }
537
+ const revisions = await documentsSvc.listIssueDocumentRevisions(issue.id, keyParsed.data);
538
+ res.json(revisions);
539
+ });
540
+ router.post("/issues/:id/documents/:key/revisions/:revisionId/restore", validate(restoreIssueDocumentRevisionSchema), async (req, res) => {
541
+ const id = req.params.id;
542
+ const revisionId = req.params.revisionId;
543
+ const issue = await svc.getById(id);
544
+ if (!issue) {
545
+ res.status(404).json({ error: "Issue not found" });
546
+ return;
547
+ }
548
+ assertCompanyAccess(req, issue.companyId);
549
+ const keyParsed = issueDocumentKeySchema.safeParse(String(req.params.key ?? "").trim().toLowerCase());
550
+ if (!keyParsed.success) {
551
+ res.status(400).json({ error: "Invalid document key", details: keyParsed.error.issues });
552
+ return;
553
+ }
554
+ const actor = getActorInfo(req);
555
+ const result = await documentsSvc.restoreIssueDocumentRevision({
556
+ issueId: issue.id,
557
+ key: keyParsed.data,
558
+ revisionId,
559
+ createdByAgentId: actor.agentId ?? null,
560
+ createdByUserId: actor.actorType === "user" ? actor.actorId : null,
561
+ });
562
+ await logActivity(db, {
563
+ companyId: issue.companyId,
564
+ actorType: actor.actorType,
565
+ actorId: actor.actorId,
566
+ agentId: actor.agentId,
567
+ runId: actor.runId,
568
+ action: "issue.document_restored",
569
+ entityType: "issue",
570
+ entityId: issue.id,
571
+ details: {
572
+ key: result.document.key,
573
+ documentId: result.document.id,
574
+ title: result.document.title,
575
+ format: result.document.format,
576
+ revisionNumber: result.document.latestRevisionNumber,
577
+ restoredFromRevisionId: result.restoredFromRevisionId,
578
+ restoredFromRevisionNumber: result.restoredFromRevisionNumber,
579
+ },
580
+ });
581
+ res.json(result.document);
582
+ });
583
+ router.delete("/issues/:id/documents/:key", async (req, res) => {
584
+ const id = req.params.id;
585
+ const issue = await svc.getById(id);
586
+ if (!issue) {
587
+ res.status(404).json({ error: "Issue not found" });
588
+ return;
589
+ }
590
+ assertCompanyAccess(req, issue.companyId);
591
+ if (req.actor.type !== "board") {
592
+ res.status(403).json({ error: "Board authentication required" });
593
+ return;
594
+ }
595
+ const keyParsed = issueDocumentKeySchema.safeParse(String(req.params.key ?? "").trim().toLowerCase());
596
+ if (!keyParsed.success) {
597
+ res.status(400).json({ error: "Invalid document key", details: keyParsed.error.issues });
598
+ return;
599
+ }
600
+ const removed = await documentsSvc.deleteIssueDocument(issue.id, keyParsed.data);
601
+ if (!removed) {
602
+ res.status(404).json({ error: "Document not found" });
603
+ return;
604
+ }
605
+ const actor = getActorInfo(req);
606
+ await logActivity(db, {
607
+ companyId: issue.companyId,
608
+ actorType: actor.actorType,
609
+ actorId: actor.actorId,
610
+ agentId: actor.agentId,
611
+ runId: actor.runId,
612
+ action: "issue.document_deleted",
613
+ entityType: "issue",
614
+ entityId: issue.id,
615
+ details: {
616
+ key: removed.key,
617
+ documentId: removed.id,
618
+ title: removed.title,
619
+ },
620
+ });
621
+ res.json({ ok: true });
622
+ });
623
+ router.post("/issues/:id/work-products", validate(createIssueWorkProductSchema), async (req, res) => {
624
+ const id = req.params.id;
625
+ const issue = await svc.getById(id);
626
+ if (!issue) {
627
+ res.status(404).json({ error: "Issue not found" });
628
+ return;
629
+ }
630
+ assertCompanyAccess(req, issue.companyId);
631
+ const product = await workProductsSvc.createForIssue(issue.id, issue.companyId, {
632
+ ...req.body,
633
+ projectId: req.body.projectId ?? issue.projectId ?? null,
634
+ });
635
+ if (!product) {
636
+ res.status(422).json({ error: "Invalid work product payload" });
637
+ return;
638
+ }
639
+ const actor = getActorInfo(req);
640
+ await logActivity(db, {
641
+ companyId: issue.companyId,
642
+ actorType: actor.actorType,
643
+ actorId: actor.actorId,
644
+ agentId: actor.agentId,
645
+ runId: actor.runId,
646
+ action: "issue.work_product_created",
647
+ entityType: "issue",
648
+ entityId: issue.id,
649
+ details: { workProductId: product.id, type: product.type, provider: product.provider },
650
+ });
651
+ res.status(201).json(product);
652
+ });
653
+ router.patch("/work-products/:id", validate(updateIssueWorkProductSchema), async (req, res) => {
654
+ const id = req.params.id;
655
+ const existing = await workProductsSvc.getById(id);
656
+ if (!existing) {
657
+ res.status(404).json({ error: "Work product not found" });
658
+ return;
659
+ }
660
+ assertCompanyAccess(req, existing.companyId);
661
+ const product = await workProductsSvc.update(id, req.body);
662
+ if (!product) {
663
+ res.status(404).json({ error: "Work product not found" });
664
+ return;
665
+ }
666
+ const actor = getActorInfo(req);
667
+ await logActivity(db, {
668
+ companyId: existing.companyId,
669
+ actorType: actor.actorType,
670
+ actorId: actor.actorId,
671
+ agentId: actor.agentId,
672
+ runId: actor.runId,
673
+ action: "issue.work_product_updated",
674
+ entityType: "issue",
675
+ entityId: existing.issueId,
676
+ details: { workProductId: product.id, changedKeys: Object.keys(req.body).sort() },
677
+ });
678
+ res.json(product);
679
+ });
680
+ router.delete("/work-products/:id", async (req, res) => {
681
+ const id = req.params.id;
682
+ const existing = await workProductsSvc.getById(id);
683
+ if (!existing) {
684
+ res.status(404).json({ error: "Work product not found" });
685
+ return;
686
+ }
687
+ assertCompanyAccess(req, existing.companyId);
688
+ const removed = await workProductsSvc.remove(id);
689
+ if (!removed) {
690
+ res.status(404).json({ error: "Work product not found" });
691
+ return;
692
+ }
693
+ const actor = getActorInfo(req);
694
+ await logActivity(db, {
695
+ companyId: existing.companyId,
696
+ actorType: actor.actorType,
697
+ actorId: actor.actorId,
698
+ agentId: actor.agentId,
699
+ runId: actor.runId,
700
+ action: "issue.work_product_deleted",
701
+ entityType: "issue",
702
+ entityId: existing.issueId,
703
+ details: { workProductId: removed.id, type: removed.type },
704
+ });
705
+ res.json(removed);
706
+ });
707
+ router.post("/issues/:id/read", async (req, res) => {
708
+ const id = req.params.id;
709
+ const issue = await svc.getById(id);
710
+ if (!issue) {
711
+ res.status(404).json({ error: "Issue not found" });
712
+ return;
713
+ }
714
+ assertCompanyAccess(req, issue.companyId);
715
+ if (req.actor.type !== "board") {
716
+ res.status(403).json({ error: "Board authentication required" });
717
+ return;
718
+ }
719
+ if (!req.actor.userId) {
720
+ res.status(403).json({ error: "Board user context required" });
721
+ return;
722
+ }
723
+ const readState = await svc.markRead(issue.companyId, issue.id, req.actor.userId, new Date());
724
+ const actor = getActorInfo(req);
725
+ await logActivity(db, {
726
+ companyId: issue.companyId,
727
+ actorType: actor.actorType,
728
+ actorId: actor.actorId,
729
+ agentId: actor.agentId,
730
+ runId: actor.runId,
731
+ action: "issue.read_marked",
732
+ entityType: "issue",
733
+ entityId: issue.id,
734
+ details: { userId: req.actor.userId, lastReadAt: readState.lastReadAt },
735
+ });
736
+ res.json(readState);
737
+ });
738
+ router.delete("/issues/:id/read", async (req, res) => {
739
+ const id = req.params.id;
740
+ const issue = await svc.getById(id);
741
+ if (!issue) {
742
+ res.status(404).json({ error: "Issue not found" });
743
+ return;
744
+ }
745
+ assertCompanyAccess(req, issue.companyId);
746
+ if (req.actor.type !== "board") {
747
+ res.status(403).json({ error: "Board authentication required" });
748
+ return;
749
+ }
750
+ if (!req.actor.userId) {
751
+ res.status(403).json({ error: "Board user context required" });
752
+ return;
753
+ }
754
+ const removed = await svc.markUnread(issue.companyId, issue.id, req.actor.userId);
755
+ const actor = getActorInfo(req);
756
+ await logActivity(db, {
757
+ companyId: issue.companyId,
758
+ actorType: actor.actorType,
759
+ actorId: actor.actorId,
760
+ agentId: actor.agentId,
761
+ runId: actor.runId,
762
+ action: "issue.read_unmarked",
763
+ entityType: "issue",
764
+ entityId: issue.id,
765
+ details: { userId: req.actor.userId },
766
+ });
767
+ res.json({ id: issue.id, removed });
768
+ });
769
+ router.post("/issues/:id/inbox-archive", async (req, res) => {
770
+ const id = req.params.id;
771
+ const issue = await svc.getById(id);
772
+ if (!issue) {
773
+ res.status(404).json({ error: "Issue not found" });
774
+ return;
775
+ }
776
+ assertCompanyAccess(req, issue.companyId);
777
+ if (req.actor.type !== "board") {
778
+ res.status(403).json({ error: "Board authentication required" });
779
+ return;
780
+ }
781
+ if (!req.actor.userId) {
782
+ res.status(403).json({ error: "Board user context required" });
783
+ return;
784
+ }
785
+ const archiveState = await svc.archiveInbox(issue.companyId, issue.id, req.actor.userId, new Date());
786
+ const actor = getActorInfo(req);
787
+ await logActivity(db, {
788
+ companyId: issue.companyId,
789
+ actorType: actor.actorType,
790
+ actorId: actor.actorId,
791
+ agentId: actor.agentId,
792
+ runId: actor.runId,
793
+ action: "issue.inbox_archived",
794
+ entityType: "issue",
795
+ entityId: issue.id,
796
+ details: { userId: req.actor.userId, archivedAt: archiveState.archivedAt },
797
+ });
798
+ res.json(archiveState);
799
+ });
800
+ router.delete("/issues/:id/inbox-archive", async (req, res) => {
801
+ const id = req.params.id;
802
+ const issue = await svc.getById(id);
803
+ if (!issue) {
804
+ res.status(404).json({ error: "Issue not found" });
805
+ return;
806
+ }
807
+ assertCompanyAccess(req, issue.companyId);
808
+ if (req.actor.type !== "board") {
809
+ res.status(403).json({ error: "Board authentication required" });
810
+ return;
811
+ }
812
+ if (!req.actor.userId) {
813
+ res.status(403).json({ error: "Board user context required" });
814
+ return;
815
+ }
816
+ const removed = await svc.unarchiveInbox(issue.companyId, issue.id, req.actor.userId);
817
+ const actor = getActorInfo(req);
818
+ await logActivity(db, {
819
+ companyId: issue.companyId,
820
+ actorType: actor.actorType,
821
+ actorId: actor.actorId,
822
+ agentId: actor.agentId,
823
+ runId: actor.runId,
824
+ action: "issue.inbox_unarchived",
825
+ entityType: "issue",
826
+ entityId: issue.id,
827
+ details: { userId: req.actor.userId },
828
+ });
829
+ res.json(removed ?? { ok: true });
830
+ });
831
+ router.get("/issues/:id/approvals", async (req, res) => {
832
+ const id = req.params.id;
833
+ const issue = await svc.getById(id);
834
+ if (!issue) {
835
+ res.status(404).json({ error: "Issue not found" });
836
+ return;
837
+ }
838
+ assertCompanyAccess(req, issue.companyId);
839
+ const approvals = await issueApprovalsSvc.listApprovalsForIssue(id);
840
+ res.json(approvals);
841
+ });
842
+ router.post("/issues/:id/approvals", validate(linkIssueApprovalSchema), async (req, res) => {
843
+ const id = req.params.id;
844
+ const issue = await svc.getById(id);
845
+ if (!issue) {
846
+ res.status(404).json({ error: "Issue not found" });
847
+ return;
848
+ }
849
+ if (!(await assertCanManageIssueApprovalLinks(req, res, issue.companyId)))
850
+ return;
851
+ const actor = getActorInfo(req);
852
+ await issueApprovalsSvc.link(id, req.body.approvalId, {
853
+ agentId: actor.agentId,
854
+ userId: actor.actorType === "user" ? actor.actorId : null,
855
+ });
856
+ await logActivity(db, {
857
+ companyId: issue.companyId,
858
+ actorType: actor.actorType,
859
+ actorId: actor.actorId,
860
+ agentId: actor.agentId,
861
+ runId: actor.runId,
862
+ action: "issue.approval_linked",
863
+ entityType: "issue",
864
+ entityId: issue.id,
865
+ details: { approvalId: req.body.approvalId },
866
+ });
867
+ const approvals = await issueApprovalsSvc.listApprovalsForIssue(id);
868
+ res.status(201).json(approvals);
869
+ });
870
+ router.delete("/issues/:id/approvals/:approvalId", async (req, res) => {
871
+ const id = req.params.id;
872
+ const approvalId = req.params.approvalId;
873
+ const issue = await svc.getById(id);
874
+ if (!issue) {
875
+ res.status(404).json({ error: "Issue not found" });
876
+ return;
877
+ }
878
+ if (!(await assertCanManageIssueApprovalLinks(req, res, issue.companyId)))
879
+ return;
880
+ await issueApprovalsSvc.unlink(id, approvalId);
881
+ const actor = getActorInfo(req);
882
+ await logActivity(db, {
883
+ companyId: issue.companyId,
884
+ actorType: actor.actorType,
885
+ actorId: actor.actorId,
886
+ agentId: actor.agentId,
887
+ runId: actor.runId,
888
+ action: "issue.approval_unlinked",
889
+ entityType: "issue",
890
+ entityId: issue.id,
891
+ details: { approvalId },
892
+ });
893
+ res.json({ ok: true });
894
+ });
895
+ router.post("/companies/:companyId/issues", validate(createIssueSchema), async (req, res) => {
896
+ const companyId = req.params.companyId;
897
+ assertCompanyAccess(req, companyId);
898
+ if (req.body.assigneeAgentId || req.body.assigneeUserId) {
899
+ await assertCanAssignTasks(req, companyId);
900
+ }
901
+ const actor = getActorInfo(req);
902
+ const issue = await svc.create(companyId, {
903
+ ...req.body,
904
+ createdByAgentId: actor.agentId,
905
+ createdByUserId: actor.actorType === "user" ? actor.actorId : null,
906
+ });
907
+ await logActivity(db, {
908
+ companyId,
909
+ actorType: actor.actorType,
910
+ actorId: actor.actorId,
911
+ agentId: actor.agentId,
912
+ runId: actor.runId,
913
+ action: "issue.created",
914
+ entityType: "issue",
915
+ entityId: issue.id,
916
+ details: { title: issue.title, identifier: issue.identifier },
917
+ });
918
+ void queueIssueAssignmentWakeup({
919
+ heartbeat,
920
+ issue,
921
+ reason: "issue_assigned",
922
+ mutation: "create",
923
+ contextSource: "issue.create",
924
+ requestedByActorType: actor.actorType,
925
+ requestedByActorId: actor.actorId,
926
+ });
927
+ res.status(201).json(issue);
928
+ });
929
+ router.patch("/issues/:id", validate(updateIssueRouteSchema), async (req, res) => {
930
+ const id = req.params.id;
931
+ const existing = await svc.getById(id);
932
+ if (!existing) {
933
+ res.status(404).json({ error: "Issue not found" });
934
+ return;
935
+ }
936
+ assertCompanyAccess(req, existing.companyId);
937
+ const assigneeWillChange = (req.body.assigneeAgentId !== undefined && req.body.assigneeAgentId !== existing.assigneeAgentId) ||
938
+ (req.body.assigneeUserId !== undefined && req.body.assigneeUserId !== existing.assigneeUserId);
939
+ const isAgentReturningIssueToCreator = req.actor.type === "agent" &&
940
+ !!req.actor.agentId &&
941
+ existing.assigneeAgentId === req.actor.agentId &&
942
+ req.body.assigneeAgentId === null &&
943
+ typeof req.body.assigneeUserId === "string" &&
944
+ !!existing.createdByUserId &&
945
+ req.body.assigneeUserId === existing.createdByUserId;
946
+ if (assigneeWillChange) {
947
+ if (!isAgentReturningIssueToCreator) {
948
+ await assertCanAssignTasks(req, existing.companyId);
949
+ }
950
+ }
951
+ if (!(await assertAgentRunCheckoutOwnership(req, res, existing)))
952
+ return;
953
+ const actor = getActorInfo(req);
954
+ const isClosed = existing.status === "done" || existing.status === "cancelled";
955
+ const { comment: commentBody, reopen: reopenRequested, interrupt: interruptRequested, hiddenAt: hiddenAtRaw, ...updateFields } = req.body;
956
+ let interruptedRunId = null;
957
+ if (interruptRequested) {
958
+ if (!commentBody) {
959
+ res.status(400).json({ error: "Interrupt is only supported when posting a comment" });
960
+ return;
961
+ }
962
+ if (req.actor.type !== "board") {
963
+ res.status(403).json({ error: "Only board users can interrupt active runs from issue comments" });
964
+ return;
965
+ }
966
+ const runToInterrupt = await resolveActiveIssueRun(existing);
967
+ if (runToInterrupt) {
968
+ const cancelled = await heartbeat.cancelRun(runToInterrupt.id);
969
+ if (cancelled) {
970
+ interruptedRunId = cancelled.id;
971
+ await logActivity(db, {
972
+ companyId: cancelled.companyId,
973
+ actorType: actor.actorType,
974
+ actorId: actor.actorId,
975
+ agentId: actor.agentId,
976
+ runId: actor.runId,
977
+ action: "heartbeat.cancelled",
978
+ entityType: "heartbeat_run",
979
+ entityId: cancelled.id,
980
+ details: { agentId: cancelled.agentId, source: "issue_comment_interrupt", issueId: existing.id },
981
+ });
982
+ }
983
+ }
984
+ }
985
+ if (hiddenAtRaw !== undefined) {
986
+ updateFields.hiddenAt = hiddenAtRaw ? new Date(hiddenAtRaw) : null;
987
+ }
988
+ if (commentBody && reopenRequested === true && isClosed && updateFields.status === undefined) {
989
+ updateFields.status = "todo";
990
+ }
991
+ let issue;
992
+ try {
993
+ issue = await svc.update(id, updateFields);
994
+ }
995
+ catch (err) {
996
+ if (err instanceof HttpError && err.status === 422) {
997
+ logger.warn({
998
+ issueId: id,
999
+ companyId: existing.companyId,
1000
+ assigneePatch: {
1001
+ assigneeAgentId: req.body.assigneeAgentId === undefined ? "__omitted__" : req.body.assigneeAgentId,
1002
+ assigneeUserId: req.body.assigneeUserId === undefined ? "__omitted__" : req.body.assigneeUserId,
1003
+ },
1004
+ currentAssignee: {
1005
+ assigneeAgentId: existing.assigneeAgentId,
1006
+ assigneeUserId: existing.assigneeUserId,
1007
+ },
1008
+ error: err.message,
1009
+ details: err.details,
1010
+ }, "issue update rejected with 422");
1011
+ }
1012
+ throw err;
1013
+ }
1014
+ if (!issue) {
1015
+ res.status(404).json({ error: "Issue not found" });
1016
+ return;
1017
+ }
1018
+ await routinesSvc.syncRunStatusForIssue(issue.id);
1019
+ if (actor.runId) {
1020
+ await heartbeat.reportRunActivity(actor.runId).catch((err) => logger.warn({ err, runId: actor.runId }, "failed to clear detached run warning after issue activity"));
1021
+ }
1022
+ // Build activity details with previous values for changed fields
1023
+ const previous = {};
1024
+ for (const key of Object.keys(updateFields)) {
1025
+ if (key in existing && existing[key] !== updateFields[key]) {
1026
+ previous[key] = existing[key];
1027
+ }
1028
+ }
1029
+ const hasFieldChanges = Object.keys(previous).length > 0;
1030
+ const reopened = commentBody &&
1031
+ reopenRequested === true &&
1032
+ isClosed &&
1033
+ previous.status !== undefined &&
1034
+ issue.status === "todo";
1035
+ const reopenFromStatus = reopened ? existing.status : null;
1036
+ await logActivity(db, {
1037
+ companyId: issue.companyId,
1038
+ actorType: actor.actorType,
1039
+ actorId: actor.actorId,
1040
+ agentId: actor.agentId,
1041
+ runId: actor.runId,
1042
+ action: "issue.updated",
1043
+ entityType: "issue",
1044
+ entityId: issue.id,
1045
+ details: {
1046
+ ...updateFields,
1047
+ identifier: issue.identifier,
1048
+ ...(commentBody ? { source: "comment" } : {}),
1049
+ ...(reopened ? { reopened: true, reopenedFrom: reopenFromStatus } : {}),
1050
+ ...(interruptedRunId ? { interruptedRunId } : {}),
1051
+ _previous: hasFieldChanges ? previous : undefined,
1052
+ },
1053
+ });
1054
+ if (issue.status === "done" && existing.status !== "done") {
1055
+ const tc = getTelemetryClient();
1056
+ if (tc && actor.agentId) {
1057
+ const actorAgent = await agentsSvc.getById(actor.agentId);
1058
+ if (actorAgent) {
1059
+ trackAgentTaskCompleted(tc, { agentRole: actorAgent.role });
1060
+ }
1061
+ }
1062
+ }
1063
+ let comment = null;
1064
+ if (commentBody) {
1065
+ comment = await svc.addComment(id, commentBody, {
1066
+ agentId: actor.agentId ?? undefined,
1067
+ userId: actor.actorType === "user" ? actor.actorId : undefined,
1068
+ runId: actor.runId,
1069
+ });
1070
+ await logActivity(db, {
1071
+ companyId: issue.companyId,
1072
+ actorType: actor.actorType,
1073
+ actorId: actor.actorId,
1074
+ agentId: actor.agentId,
1075
+ runId: actor.runId,
1076
+ action: "issue.comment_added",
1077
+ entityType: "issue",
1078
+ entityId: issue.id,
1079
+ details: {
1080
+ commentId: comment.id,
1081
+ bodySnippet: comment.body.slice(0, 120),
1082
+ identifier: issue.identifier,
1083
+ issueTitle: issue.title,
1084
+ ...(reopened ? { reopened: true, reopenedFrom: reopenFromStatus, source: "comment" } : {}),
1085
+ ...(interruptedRunId ? { interruptedRunId } : {}),
1086
+ ...(hasFieldChanges ? { updated: true } : {}),
1087
+ },
1088
+ });
1089
+ }
1090
+ const assigneeChanged = assigneeWillChange;
1091
+ const statusChangedFromBacklog = existing.status === "backlog" &&
1092
+ issue.status !== "backlog" &&
1093
+ req.body.status !== undefined;
1094
+ // Merge all wakeups from this update into one enqueue per agent to avoid duplicate runs.
1095
+ void (async () => {
1096
+ const wakeups = new Map();
1097
+ if (assigneeChanged && issue.assigneeAgentId && issue.status !== "backlog") {
1098
+ wakeups.set(issue.assigneeAgentId, {
1099
+ source: "assignment",
1100
+ triggerDetail: "system",
1101
+ reason: "issue_assigned",
1102
+ payload: {
1103
+ issueId: issue.id,
1104
+ mutation: "update",
1105
+ ...(interruptedRunId ? { interruptedRunId } : {}),
1106
+ },
1107
+ requestedByActorType: actor.actorType,
1108
+ requestedByActorId: actor.actorId,
1109
+ contextSnapshot: {
1110
+ issueId: issue.id,
1111
+ source: "issue.update",
1112
+ ...(interruptedRunId ? { interruptedRunId } : {}),
1113
+ },
1114
+ });
1115
+ }
1116
+ if (!assigneeChanged && statusChangedFromBacklog && issue.assigneeAgentId) {
1117
+ wakeups.set(issue.assigneeAgentId, {
1118
+ source: "automation",
1119
+ triggerDetail: "system",
1120
+ reason: "issue_status_changed",
1121
+ payload: {
1122
+ issueId: issue.id,
1123
+ mutation: "update",
1124
+ ...(interruptedRunId ? { interruptedRunId } : {}),
1125
+ },
1126
+ requestedByActorType: actor.actorType,
1127
+ requestedByActorId: actor.actorId,
1128
+ contextSnapshot: {
1129
+ issueId: issue.id,
1130
+ source: "issue.status_change",
1131
+ ...(interruptedRunId ? { interruptedRunId } : {}),
1132
+ },
1133
+ });
1134
+ }
1135
+ if (commentBody && comment) {
1136
+ let mentionedIds = [];
1137
+ try {
1138
+ mentionedIds = await svc.findMentionedAgents(issue.companyId, commentBody);
1139
+ }
1140
+ catch (err) {
1141
+ logger.warn({ err, issueId: id }, "failed to resolve @-mentions");
1142
+ }
1143
+ for (const mentionedId of mentionedIds) {
1144
+ if (wakeups.has(mentionedId))
1145
+ continue;
1146
+ if (actor.actorType === "agent" && actor.actorId === mentionedId)
1147
+ continue;
1148
+ wakeups.set(mentionedId, {
1149
+ source: "automation",
1150
+ triggerDetail: "system",
1151
+ reason: "issue_comment_mentioned",
1152
+ payload: { issueId: id, commentId: comment.id },
1153
+ requestedByActorType: actor.actorType,
1154
+ requestedByActorId: actor.actorId,
1155
+ contextSnapshot: {
1156
+ issueId: id,
1157
+ taskId: id,
1158
+ commentId: comment.id,
1159
+ wakeCommentId: comment.id,
1160
+ wakeReason: "issue_comment_mentioned",
1161
+ source: "comment.mention",
1162
+ },
1163
+ });
1164
+ }
1165
+ }
1166
+ for (const [agentId, wakeup] of wakeups.entries()) {
1167
+ heartbeat
1168
+ .wakeup(agentId, wakeup)
1169
+ .catch((err) => logger.warn({ err, issueId: issue.id, agentId }, "failed to wake agent on issue update"));
1170
+ }
1171
+ })();
1172
+ res.json({ ...issue, comment });
1173
+ });
1174
+ router.delete("/issues/:id", async (req, res) => {
1175
+ const id = req.params.id;
1176
+ const existing = await svc.getById(id);
1177
+ if (!existing) {
1178
+ res.status(404).json({ error: "Issue not found" });
1179
+ return;
1180
+ }
1181
+ assertCompanyAccess(req, existing.companyId);
1182
+ const attachments = await svc.listAttachments(id);
1183
+ const issue = await svc.remove(id);
1184
+ if (!issue) {
1185
+ res.status(404).json({ error: "Issue not found" });
1186
+ return;
1187
+ }
1188
+ for (const attachment of attachments) {
1189
+ try {
1190
+ await storage.deleteObject(attachment.companyId, attachment.objectKey);
1191
+ }
1192
+ catch (err) {
1193
+ logger.warn({ err, issueId: id, attachmentId: attachment.id }, "failed to delete attachment object during issue delete");
1194
+ }
1195
+ }
1196
+ const actor = getActorInfo(req);
1197
+ await logActivity(db, {
1198
+ companyId: issue.companyId,
1199
+ actorType: actor.actorType,
1200
+ actorId: actor.actorId,
1201
+ agentId: actor.agentId,
1202
+ runId: actor.runId,
1203
+ action: "issue.deleted",
1204
+ entityType: "issue",
1205
+ entityId: issue.id,
1206
+ });
1207
+ res.json(issue);
1208
+ });
1209
+ router.post("/issues/:id/checkout", validate(checkoutIssueSchema), async (req, res) => {
1210
+ const id = req.params.id;
1211
+ const issue = await svc.getById(id);
1212
+ if (!issue) {
1213
+ res.status(404).json({ error: "Issue not found" });
1214
+ return;
1215
+ }
1216
+ assertCompanyAccess(req, issue.companyId);
1217
+ if (issue.projectId) {
1218
+ const project = await projectsSvc.getById(issue.projectId);
1219
+ if (project?.pausedAt) {
1220
+ res.status(409).json({
1221
+ error: project.pauseReason === "budget"
1222
+ ? "Project is paused because its budget hard-stop was reached"
1223
+ : "Project is paused",
1224
+ });
1225
+ return;
1226
+ }
1227
+ }
1228
+ if (req.actor.type === "agent" && req.actor.agentId !== req.body.agentId) {
1229
+ res.status(403).json({ error: "Agent can only checkout as itself" });
1230
+ return;
1231
+ }
1232
+ const checkoutRunId = requireAgentRunId(req, res);
1233
+ if (req.actor.type === "agent" && !checkoutRunId)
1234
+ return;
1235
+ const updated = await svc.checkout(id, req.body.agentId, req.body.expectedStatuses, checkoutRunId);
1236
+ const actor = getActorInfo(req);
1237
+ await logActivity(db, {
1238
+ companyId: issue.companyId,
1239
+ actorType: actor.actorType,
1240
+ actorId: actor.actorId,
1241
+ agentId: actor.agentId,
1242
+ runId: actor.runId,
1243
+ action: "issue.checked_out",
1244
+ entityType: "issue",
1245
+ entityId: issue.id,
1246
+ details: { agentId: req.body.agentId },
1247
+ });
1248
+ if (shouldWakeAssigneeOnCheckout({
1249
+ actorType: req.actor.type,
1250
+ actorAgentId: req.actor.type === "agent" ? req.actor.agentId ?? null : null,
1251
+ checkoutAgentId: req.body.agentId,
1252
+ checkoutRunId,
1253
+ })) {
1254
+ void heartbeat
1255
+ .wakeup(req.body.agentId, {
1256
+ source: "assignment",
1257
+ triggerDetail: "system",
1258
+ reason: "issue_checked_out",
1259
+ payload: { issueId: issue.id, mutation: "checkout" },
1260
+ requestedByActorType: actor.actorType,
1261
+ requestedByActorId: actor.actorId,
1262
+ contextSnapshot: { issueId: issue.id, source: "issue.checkout" },
1263
+ })
1264
+ .catch((err) => logger.warn({ err, issueId: issue.id }, "failed to wake assignee on issue checkout"));
1265
+ }
1266
+ res.json(updated);
1267
+ });
1268
+ router.post("/issues/:id/release", async (req, res) => {
1269
+ const id = req.params.id;
1270
+ const existing = await svc.getById(id);
1271
+ if (!existing) {
1272
+ res.status(404).json({ error: "Issue not found" });
1273
+ return;
1274
+ }
1275
+ assertCompanyAccess(req, existing.companyId);
1276
+ if (!(await assertAgentRunCheckoutOwnership(req, res, existing)))
1277
+ return;
1278
+ const actorRunId = requireAgentRunId(req, res);
1279
+ if (req.actor.type === "agent" && !actorRunId)
1280
+ return;
1281
+ const released = await svc.release(id, req.actor.type === "agent" ? req.actor.agentId : undefined, actorRunId);
1282
+ if (!released) {
1283
+ res.status(404).json({ error: "Issue not found" });
1284
+ return;
1285
+ }
1286
+ const actor = getActorInfo(req);
1287
+ await logActivity(db, {
1288
+ companyId: released.companyId,
1289
+ actorType: actor.actorType,
1290
+ actorId: actor.actorId,
1291
+ agentId: actor.agentId,
1292
+ runId: actor.runId,
1293
+ action: "issue.released",
1294
+ entityType: "issue",
1295
+ entityId: released.id,
1296
+ });
1297
+ res.json(released);
1298
+ });
1299
+ router.get("/issues/:id/comments", async (req, res) => {
1300
+ const id = req.params.id;
1301
+ const issue = await svc.getById(id);
1302
+ if (!issue) {
1303
+ res.status(404).json({ error: "Issue not found" });
1304
+ return;
1305
+ }
1306
+ assertCompanyAccess(req, issue.companyId);
1307
+ const afterCommentId = typeof req.query.after === "string" && req.query.after.trim().length > 0
1308
+ ? req.query.after.trim()
1309
+ : typeof req.query.afterCommentId === "string" && req.query.afterCommentId.trim().length > 0
1310
+ ? req.query.afterCommentId.trim()
1311
+ : null;
1312
+ const order = typeof req.query.order === "string" && req.query.order.trim().toLowerCase() === "asc"
1313
+ ? "asc"
1314
+ : "desc";
1315
+ const limitRaw = typeof req.query.limit === "string" && req.query.limit.trim().length > 0
1316
+ ? Number(req.query.limit)
1317
+ : null;
1318
+ const limit = limitRaw && Number.isFinite(limitRaw) && limitRaw > 0
1319
+ ? Math.min(Math.floor(limitRaw), MAX_ISSUE_COMMENT_LIMIT)
1320
+ : null;
1321
+ const comments = await svc.listComments(id, {
1322
+ afterCommentId,
1323
+ order,
1324
+ limit,
1325
+ });
1326
+ res.json(comments);
1327
+ });
1328
+ router.get("/issues/:id/comments/:commentId", async (req, res) => {
1329
+ const id = req.params.id;
1330
+ const commentId = req.params.commentId;
1331
+ const issue = await svc.getById(id);
1332
+ if (!issue) {
1333
+ res.status(404).json({ error: "Issue not found" });
1334
+ return;
1335
+ }
1336
+ assertCompanyAccess(req, issue.companyId);
1337
+ const comment = await svc.getComment(commentId);
1338
+ if (!comment || comment.issueId !== id) {
1339
+ res.status(404).json({ error: "Comment not found" });
1340
+ return;
1341
+ }
1342
+ res.json(comment);
1343
+ });
1344
+ router.get("/issues/:id/feedback-votes", async (req, res) => {
1345
+ const id = req.params.id;
1346
+ const issue = await svc.getById(id);
1347
+ if (!issue) {
1348
+ res.status(404).json({ error: "Issue not found" });
1349
+ return;
1350
+ }
1351
+ assertCompanyAccess(req, issue.companyId);
1352
+ if (req.actor.type !== "board") {
1353
+ res.status(403).json({ error: "Only board users can view feedback votes" });
1354
+ return;
1355
+ }
1356
+ const votes = await feedback.listIssueVotesForUser(id, req.actor.userId ?? "local-board");
1357
+ res.json(votes);
1358
+ });
1359
+ router.get("/issues/:id/feedback-traces", async (req, res) => {
1360
+ const id = req.params.id;
1361
+ const issue = await svc.getById(id);
1362
+ if (!issue) {
1363
+ res.status(404).json({ error: "Issue not found" });
1364
+ return;
1365
+ }
1366
+ assertCompanyAccess(req, issue.companyId);
1367
+ if (req.actor.type !== "board") {
1368
+ res.status(403).json({ error: "Only board users can view feedback traces" });
1369
+ return;
1370
+ }
1371
+ const targetTypeRaw = typeof req.query.targetType === "string" ? req.query.targetType : undefined;
1372
+ const voteRaw = typeof req.query.vote === "string" ? req.query.vote : undefined;
1373
+ const statusRaw = typeof req.query.status === "string" ? req.query.status : undefined;
1374
+ const targetType = targetTypeRaw ? feedbackTargetTypeSchema.parse(targetTypeRaw) : undefined;
1375
+ const vote = voteRaw ? feedbackVoteValueSchema.parse(voteRaw) : undefined;
1376
+ const status = statusRaw ? feedbackTraceStatusSchema.parse(statusRaw) : undefined;
1377
+ const traces = await feedback.listFeedbackTraces({
1378
+ companyId: issue.companyId,
1379
+ issueId: issue.id,
1380
+ targetType,
1381
+ vote,
1382
+ status,
1383
+ from: parseDateQuery(req.query.from, "from"),
1384
+ to: parseDateQuery(req.query.to, "to"),
1385
+ sharedOnly: parseBooleanQuery(req.query.sharedOnly),
1386
+ includePayload: parseBooleanQuery(req.query.includePayload),
1387
+ });
1388
+ res.json(traces);
1389
+ });
1390
+ router.get("/feedback-traces/:traceId", async (req, res) => {
1391
+ const traceId = req.params.traceId;
1392
+ if (req.actor.type !== "board") {
1393
+ res.status(403).json({ error: "Only board users can view feedback traces" });
1394
+ return;
1395
+ }
1396
+ const includePayload = parseBooleanQuery(req.query.includePayload) || req.query.includePayload === undefined;
1397
+ const trace = await feedback.getFeedbackTraceById(traceId, includePayload);
1398
+ if (!trace || !actorCanAccessCompany(req, trace.companyId)) {
1399
+ res.status(404).json({ error: "Feedback trace not found" });
1400
+ return;
1401
+ }
1402
+ res.json(trace);
1403
+ });
1404
+ router.get("/feedback-traces/:traceId/bundle", async (req, res) => {
1405
+ const traceId = req.params.traceId;
1406
+ if (req.actor.type !== "board") {
1407
+ res.status(403).json({ error: "Only board users can view feedback trace bundles" });
1408
+ return;
1409
+ }
1410
+ const bundle = await feedback.getFeedbackTraceBundle(traceId);
1411
+ if (!bundle || !actorCanAccessCompany(req, bundle.companyId)) {
1412
+ res.status(404).json({ error: "Feedback trace not found" });
1413
+ return;
1414
+ }
1415
+ res.json(bundle);
1416
+ });
1417
+ router.post("/issues/:id/comments", validate(addIssueCommentSchema), async (req, res) => {
1418
+ const id = req.params.id;
1419
+ const issue = await svc.getById(id);
1420
+ if (!issue) {
1421
+ res.status(404).json({ error: "Issue not found" });
1422
+ return;
1423
+ }
1424
+ assertCompanyAccess(req, issue.companyId);
1425
+ if (!(await assertAgentRunCheckoutOwnership(req, res, issue)))
1426
+ return;
1427
+ const actor = getActorInfo(req);
1428
+ const reopenRequested = req.body.reopen === true;
1429
+ const interruptRequested = req.body.interrupt === true;
1430
+ const isClosed = issue.status === "done" || issue.status === "cancelled";
1431
+ let reopened = false;
1432
+ let reopenFromStatus = null;
1433
+ let interruptedRunId = null;
1434
+ let currentIssue = issue;
1435
+ if (reopenRequested && isClosed) {
1436
+ const reopenedIssue = await svc.update(id, { status: "todo" });
1437
+ if (!reopenedIssue) {
1438
+ res.status(404).json({ error: "Issue not found" });
1439
+ return;
1440
+ }
1441
+ reopened = true;
1442
+ reopenFromStatus = issue.status;
1443
+ currentIssue = reopenedIssue;
1444
+ await logActivity(db, {
1445
+ companyId: currentIssue.companyId,
1446
+ actorType: actor.actorType,
1447
+ actorId: actor.actorId,
1448
+ agentId: actor.agentId,
1449
+ runId: actor.runId,
1450
+ action: "issue.updated",
1451
+ entityType: "issue",
1452
+ entityId: currentIssue.id,
1453
+ details: {
1454
+ status: "todo",
1455
+ reopened: true,
1456
+ reopenedFrom: reopenFromStatus,
1457
+ source: "comment",
1458
+ identifier: currentIssue.identifier,
1459
+ },
1460
+ });
1461
+ }
1462
+ if (interruptRequested) {
1463
+ if (req.actor.type !== "board") {
1464
+ res.status(403).json({ error: "Only board users can interrupt active runs from issue comments" });
1465
+ return;
1466
+ }
1467
+ const runToInterrupt = await resolveActiveIssueRun(currentIssue);
1468
+ if (runToInterrupt) {
1469
+ const cancelled = await heartbeat.cancelRun(runToInterrupt.id);
1470
+ if (cancelled) {
1471
+ interruptedRunId = cancelled.id;
1472
+ await logActivity(db, {
1473
+ companyId: cancelled.companyId,
1474
+ actorType: actor.actorType,
1475
+ actorId: actor.actorId,
1476
+ agentId: actor.agentId,
1477
+ runId: actor.runId,
1478
+ action: "heartbeat.cancelled",
1479
+ entityType: "heartbeat_run",
1480
+ entityId: cancelled.id,
1481
+ details: { agentId: cancelled.agentId, source: "issue_comment_interrupt", issueId: currentIssue.id },
1482
+ });
1483
+ }
1484
+ }
1485
+ }
1486
+ const comment = await svc.addComment(id, req.body.body, {
1487
+ agentId: actor.agentId ?? undefined,
1488
+ userId: actor.actorType === "user" ? actor.actorId : undefined,
1489
+ runId: actor.runId,
1490
+ });
1491
+ if (actor.runId) {
1492
+ await heartbeat.reportRunActivity(actor.runId).catch((err) => logger.warn({ err, runId: actor.runId }, "failed to clear detached run warning after issue comment"));
1493
+ }
1494
+ await logActivity(db, {
1495
+ companyId: currentIssue.companyId,
1496
+ actorType: actor.actorType,
1497
+ actorId: actor.actorId,
1498
+ agentId: actor.agentId,
1499
+ runId: actor.runId,
1500
+ action: "issue.comment_added",
1501
+ entityType: "issue",
1502
+ entityId: currentIssue.id,
1503
+ details: {
1504
+ commentId: comment.id,
1505
+ bodySnippet: comment.body.slice(0, 120),
1506
+ identifier: currentIssue.identifier,
1507
+ issueTitle: currentIssue.title,
1508
+ ...(reopened ? { reopened: true, reopenedFrom: reopenFromStatus, source: "comment" } : {}),
1509
+ ...(interruptedRunId ? { interruptedRunId } : {}),
1510
+ },
1511
+ });
1512
+ // Merge all wakeups from this comment into one enqueue per agent to avoid duplicate runs.
1513
+ void (async () => {
1514
+ const wakeups = new Map();
1515
+ const assigneeId = currentIssue.assigneeAgentId;
1516
+ const actorIsAgent = actor.actorType === "agent";
1517
+ const selfComment = actorIsAgent && actor.actorId === assigneeId;
1518
+ const skipWake = selfComment || isClosed;
1519
+ if (assigneeId && (reopened || !skipWake)) {
1520
+ if (reopened) {
1521
+ wakeups.set(assigneeId, {
1522
+ source: "automation",
1523
+ triggerDetail: "system",
1524
+ reason: "issue_reopened_via_comment",
1525
+ payload: {
1526
+ issueId: currentIssue.id,
1527
+ commentId: comment.id,
1528
+ reopenedFrom: reopenFromStatus,
1529
+ mutation: "comment",
1530
+ ...(interruptedRunId ? { interruptedRunId } : {}),
1531
+ },
1532
+ requestedByActorType: actor.actorType,
1533
+ requestedByActorId: actor.actorId,
1534
+ contextSnapshot: {
1535
+ issueId: currentIssue.id,
1536
+ taskId: currentIssue.id,
1537
+ commentId: comment.id,
1538
+ source: "issue.comment.reopen",
1539
+ wakeReason: "issue_reopened_via_comment",
1540
+ reopenedFrom: reopenFromStatus,
1541
+ ...(interruptedRunId ? { interruptedRunId } : {}),
1542
+ },
1543
+ });
1544
+ }
1545
+ else {
1546
+ wakeups.set(assigneeId, {
1547
+ source: "automation",
1548
+ triggerDetail: "system",
1549
+ reason: "issue_commented",
1550
+ payload: {
1551
+ issueId: currentIssue.id,
1552
+ commentId: comment.id,
1553
+ mutation: "comment",
1554
+ ...(interruptedRunId ? { interruptedRunId } : {}),
1555
+ },
1556
+ requestedByActorType: actor.actorType,
1557
+ requestedByActorId: actor.actorId,
1558
+ contextSnapshot: {
1559
+ issueId: currentIssue.id,
1560
+ taskId: currentIssue.id,
1561
+ commentId: comment.id,
1562
+ source: "issue.comment",
1563
+ wakeReason: "issue_commented",
1564
+ ...(interruptedRunId ? { interruptedRunId } : {}),
1565
+ },
1566
+ });
1567
+ }
1568
+ }
1569
+ let mentionedIds = [];
1570
+ try {
1571
+ mentionedIds = await svc.findMentionedAgents(issue.companyId, req.body.body);
1572
+ }
1573
+ catch (err) {
1574
+ logger.warn({ err, issueId: id }, "failed to resolve @-mentions");
1575
+ }
1576
+ for (const mentionedId of mentionedIds) {
1577
+ if (wakeups.has(mentionedId))
1578
+ continue;
1579
+ if (actorIsAgent && actor.actorId === mentionedId)
1580
+ continue;
1581
+ wakeups.set(mentionedId, {
1582
+ source: "automation",
1583
+ triggerDetail: "system",
1584
+ reason: "issue_comment_mentioned",
1585
+ payload: { issueId: id, commentId: comment.id },
1586
+ requestedByActorType: actor.actorType,
1587
+ requestedByActorId: actor.actorId,
1588
+ contextSnapshot: {
1589
+ issueId: id,
1590
+ taskId: id,
1591
+ commentId: comment.id,
1592
+ wakeCommentId: comment.id,
1593
+ wakeReason: "issue_comment_mentioned",
1594
+ source: "comment.mention",
1595
+ },
1596
+ });
1597
+ }
1598
+ for (const [agentId, wakeup] of wakeups.entries()) {
1599
+ heartbeat
1600
+ .wakeup(agentId, wakeup)
1601
+ .catch((err) => logger.warn({ err, issueId: currentIssue.id, agentId }, "failed to wake agent on issue comment"));
1602
+ }
1603
+ })();
1604
+ res.status(201).json(comment);
1605
+ });
1606
+ router.post("/issues/:id/feedback-votes", validate(upsertIssueFeedbackVoteSchema), async (req, res) => {
1607
+ const id = req.params.id;
1608
+ const issue = await svc.getById(id);
1609
+ if (!issue) {
1610
+ res.status(404).json({ error: "Issue not found" });
1611
+ return;
1612
+ }
1613
+ assertCompanyAccess(req, issue.companyId);
1614
+ if (req.actor.type !== "board") {
1615
+ res.status(403).json({ error: "Only board users can vote on AI feedback" });
1616
+ return;
1617
+ }
1618
+ const actor = getActorInfo(req);
1619
+ const result = await feedback.saveIssueVote({
1620
+ issueId: id,
1621
+ targetType: req.body.targetType,
1622
+ targetId: req.body.targetId,
1623
+ vote: req.body.vote,
1624
+ reason: req.body.reason,
1625
+ authorUserId: req.actor.userId ?? "local-board",
1626
+ allowSharing: req.body.allowSharing === true,
1627
+ });
1628
+ await logActivity(db, {
1629
+ companyId: issue.companyId,
1630
+ actorType: actor.actorType,
1631
+ actorId: actor.actorId,
1632
+ agentId: actor.agentId,
1633
+ runId: actor.runId,
1634
+ action: "issue.feedback_vote_saved",
1635
+ entityType: "issue",
1636
+ entityId: issue.id,
1637
+ details: {
1638
+ identifier: issue.identifier,
1639
+ targetType: result.vote.targetType,
1640
+ targetId: result.vote.targetId,
1641
+ vote: result.vote.vote,
1642
+ hasReason: Boolean(result.vote.reason),
1643
+ sharingEnabled: result.sharingEnabled,
1644
+ },
1645
+ });
1646
+ if (result.consentEnabledNow) {
1647
+ await logActivity(db, {
1648
+ companyId: issue.companyId,
1649
+ actorType: actor.actorType,
1650
+ actorId: actor.actorId,
1651
+ agentId: actor.agentId,
1652
+ runId: actor.runId,
1653
+ action: "company.feedback_data_sharing_updated",
1654
+ entityType: "company",
1655
+ entityId: issue.companyId,
1656
+ details: {
1657
+ feedbackDataSharingEnabled: true,
1658
+ source: "issue_feedback_vote",
1659
+ },
1660
+ });
1661
+ }
1662
+ if (result.persistedSharingPreference) {
1663
+ const settings = await instanceSettings.get();
1664
+ const companyIds = await instanceSettings.listCompanyIds();
1665
+ await Promise.all(companyIds.map((companyId) => logActivity(db, {
1666
+ companyId,
1667
+ actorType: actor.actorType,
1668
+ actorId: actor.actorId,
1669
+ agentId: actor.agentId,
1670
+ runId: actor.runId,
1671
+ action: "instance.settings.general_updated",
1672
+ entityType: "instance_settings",
1673
+ entityId: settings.id,
1674
+ details: {
1675
+ general: settings.general,
1676
+ changedKeys: ["feedbackDataSharingPreference"],
1677
+ source: "issue_feedback_vote",
1678
+ },
1679
+ })));
1680
+ }
1681
+ res.status(201).json(result.vote);
1682
+ });
1683
+ router.get("/issues/:id/attachments", async (req, res) => {
1684
+ const issueId = req.params.id;
1685
+ const issue = await svc.getById(issueId);
1686
+ if (!issue) {
1687
+ res.status(404).json({ error: "Issue not found" });
1688
+ return;
1689
+ }
1690
+ assertCompanyAccess(req, issue.companyId);
1691
+ const attachments = await svc.listAttachments(issueId);
1692
+ res.json(attachments.map(withContentPath));
1693
+ });
1694
+ router.post("/companies/:companyId/issues/:issueId/attachments", async (req, res) => {
1695
+ const companyId = req.params.companyId;
1696
+ const issueId = req.params.issueId;
1697
+ assertCompanyAccess(req, companyId);
1698
+ const issue = await svc.getById(issueId);
1699
+ if (!issue) {
1700
+ res.status(404).json({ error: "Issue not found" });
1701
+ return;
1702
+ }
1703
+ if (issue.companyId !== companyId) {
1704
+ res.status(422).json({ error: "Issue does not belong to company" });
1705
+ return;
1706
+ }
1707
+ try {
1708
+ await runSingleFileUpload(req, res);
1709
+ }
1710
+ catch (err) {
1711
+ if (err instanceof multer.MulterError) {
1712
+ if (err.code === "LIMIT_FILE_SIZE") {
1713
+ res.status(422).json({ error: `Attachment exceeds ${MAX_ATTACHMENT_BYTES} bytes` });
1714
+ return;
1715
+ }
1716
+ res.status(400).json({ error: err.message });
1717
+ return;
1718
+ }
1719
+ throw err;
1720
+ }
1721
+ const file = req.file;
1722
+ if (!file) {
1723
+ res.status(400).json({ error: "Missing file field 'file'" });
1724
+ return;
1725
+ }
1726
+ const contentType = (file.mimetype || "").toLowerCase();
1727
+ if (!isAllowedContentType(contentType)) {
1728
+ res.status(422).json({ error: `Unsupported attachment type: ${contentType || "unknown"}` });
1729
+ return;
1730
+ }
1731
+ if (file.buffer.length <= 0) {
1732
+ res.status(422).json({ error: "Attachment is empty" });
1733
+ return;
1734
+ }
1735
+ const parsedMeta = createIssueAttachmentMetadataSchema.safeParse(req.body ?? {});
1736
+ if (!parsedMeta.success) {
1737
+ res.status(400).json({ error: "Invalid attachment metadata", details: parsedMeta.error.issues });
1738
+ return;
1739
+ }
1740
+ const actor = getActorInfo(req);
1741
+ const stored = await storage.putFile({
1742
+ companyId,
1743
+ namespace: `issues/${issueId}`,
1744
+ originalFilename: file.originalname || null,
1745
+ contentType,
1746
+ body: file.buffer,
1747
+ });
1748
+ const attachment = await svc.createAttachment({
1749
+ issueId,
1750
+ issueCommentId: parsedMeta.data.issueCommentId ?? null,
1751
+ provider: stored.provider,
1752
+ objectKey: stored.objectKey,
1753
+ contentType: stored.contentType,
1754
+ byteSize: stored.byteSize,
1755
+ sha256: stored.sha256,
1756
+ originalFilename: stored.originalFilename,
1757
+ createdByAgentId: actor.agentId,
1758
+ createdByUserId: actor.actorType === "user" ? actor.actorId : null,
1759
+ });
1760
+ await logActivity(db, {
1761
+ companyId,
1762
+ actorType: actor.actorType,
1763
+ actorId: actor.actorId,
1764
+ agentId: actor.agentId,
1765
+ runId: actor.runId,
1766
+ action: "issue.attachment_added",
1767
+ entityType: "issue",
1768
+ entityId: issueId,
1769
+ details: {
1770
+ attachmentId: attachment.id,
1771
+ originalFilename: attachment.originalFilename,
1772
+ contentType: attachment.contentType,
1773
+ byteSize: attachment.byteSize,
1774
+ },
1775
+ });
1776
+ res.status(201).json(withContentPath(attachment));
1777
+ });
1778
+ router.get("/attachments/:attachmentId/content", async (req, res, next) => {
1779
+ const attachmentId = req.params.attachmentId;
1780
+ const attachment = await svc.getAttachmentById(attachmentId);
1781
+ if (!attachment) {
1782
+ res.status(404).json({ error: "Attachment not found" });
1783
+ return;
1784
+ }
1785
+ assertCompanyAccess(req, attachment.companyId);
1786
+ const object = await storage.getObject(attachment.companyId, attachment.objectKey);
1787
+ res.setHeader("Content-Type", attachment.contentType || object.contentType || "application/octet-stream");
1788
+ res.setHeader("Content-Length", String(attachment.byteSize || object.contentLength || 0));
1789
+ res.setHeader("Cache-Control", "private, max-age=60");
1790
+ const filename = attachment.originalFilename ?? "attachment";
1791
+ res.setHeader("Content-Disposition", `inline; filename=\"${filename.replaceAll("\"", "")}\"`);
1792
+ object.stream.on("error", (err) => {
1793
+ next(err);
1794
+ });
1795
+ object.stream.pipe(res);
1796
+ });
1797
+ router.delete("/attachments/:attachmentId", async (req, res) => {
1798
+ const attachmentId = req.params.attachmentId;
1799
+ const attachment = await svc.getAttachmentById(attachmentId);
1800
+ if (!attachment) {
1801
+ res.status(404).json({ error: "Attachment not found" });
1802
+ return;
1803
+ }
1804
+ assertCompanyAccess(req, attachment.companyId);
1805
+ try {
1806
+ await storage.deleteObject(attachment.companyId, attachment.objectKey);
1807
+ }
1808
+ catch (err) {
1809
+ logger.warn({ err, attachmentId }, "storage delete failed while removing attachment");
1810
+ }
1811
+ const removed = await svc.removeAttachment(attachmentId);
1812
+ if (!removed) {
1813
+ res.status(404).json({ error: "Attachment not found" });
1814
+ return;
1815
+ }
1816
+ const actor = getActorInfo(req);
1817
+ await logActivity(db, {
1818
+ companyId: removed.companyId,
1819
+ actorType: actor.actorType,
1820
+ actorId: actor.actorId,
1821
+ agentId: actor.agentId,
1822
+ runId: actor.runId,
1823
+ action: "issue.attachment_removed",
1824
+ entityType: "issue",
1825
+ entityId: removed.issueId,
1826
+ details: {
1827
+ attachmentId: removed.id,
1828
+ },
1829
+ });
1830
+ res.json({ ok: true });
1831
+ });
1832
+ return router;
1833
+ }
1834
+ //# sourceMappingURL=issues.js.map