@evermore.work/server 2026.509.0-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1045) hide show
  1. package/dist/adapters/builtin-adapter-types.d.ts +5 -0
  2. package/dist/adapters/builtin-adapter-types.d.ts.map +1 -0
  3. package/dist/adapters/builtin-adapter-types.js +17 -0
  4. package/dist/adapters/builtin-adapter-types.js.map +1 -0
  5. package/dist/adapters/codex-models.d.ts +5 -0
  6. package/dist/adapters/codex-models.d.ts.map +1 -0
  7. package/dist/adapters/codex-models.js +105 -0
  8. package/dist/adapters/codex-models.js.map +1 -0
  9. package/dist/adapters/cursor-models.d.ts +13 -0
  10. package/dist/adapters/cursor-models.d.ts.map +1 -0
  11. package/dist/adapters/cursor-models.js +148 -0
  12. package/dist/adapters/cursor-models.js.map +1 -0
  13. package/dist/adapters/http/execute.d.ts +3 -0
  14. package/dist/adapters/http/execute.d.ts.map +1 -0
  15. package/dist/adapters/http/execute.js +51 -0
  16. package/dist/adapters/http/execute.js.map +1 -0
  17. package/dist/adapters/http/execute.test.d.ts +2 -0
  18. package/dist/adapters/http/execute.test.d.ts.map +1 -0
  19. package/dist/adapters/http/execute.test.js +40 -0
  20. package/dist/adapters/http/execute.test.js.map +1 -0
  21. package/dist/adapters/http/index.d.ts +3 -0
  22. package/dist/adapters/http/index.d.ts.map +1 -0
  23. package/dist/adapters/http/index.js +20 -0
  24. package/dist/adapters/http/index.js.map +1 -0
  25. package/dist/adapters/http/test.d.ts +3 -0
  26. package/dist/adapters/http/test.d.ts.map +1 -0
  27. package/dist/adapters/http/test.js +106 -0
  28. package/dist/adapters/http/test.js.map +1 -0
  29. package/dist/adapters/index.d.ts +4 -0
  30. package/dist/adapters/index.d.ts.map +1 -0
  31. package/dist/adapters/index.js +3 -0
  32. package/dist/adapters/index.js.map +1 -0
  33. package/dist/adapters/plugin-loader.d.ts +28 -0
  34. package/dist/adapters/plugin-loader.d.ts.map +1 -0
  35. package/dist/adapters/plugin-loader.js +196 -0
  36. package/dist/adapters/plugin-loader.js.map +1 -0
  37. package/dist/adapters/process/execute.d.ts +3 -0
  38. package/dist/adapters/process/execute.d.ts.map +1 -0
  39. package/dist/adapters/process/execute.js +70 -0
  40. package/dist/adapters/process/execute.js.map +1 -0
  41. package/dist/adapters/process/index.d.ts +3 -0
  42. package/dist/adapters/process/index.d.ts.map +1 -0
  43. package/dist/adapters/process/index.js +23 -0
  44. package/dist/adapters/process/index.js.map +1 -0
  45. package/dist/adapters/process/test.d.ts +3 -0
  46. package/dist/adapters/process/test.d.ts.map +1 -0
  47. package/dist/adapters/process/test.js +77 -0
  48. package/dist/adapters/process/test.js.map +1 -0
  49. package/dist/adapters/registry.d.ts +69 -0
  50. package/dist/adapters/registry.d.ts.map +1 -0
  51. package/dist/adapters/registry.js +566 -0
  52. package/dist/adapters/registry.js.map +1 -0
  53. package/dist/adapters/types.d.ts +2 -0
  54. package/dist/adapters/types.d.ts.map +1 -0
  55. package/dist/adapters/types.js +2 -0
  56. package/dist/adapters/types.js.map +1 -0
  57. package/dist/adapters/utils.d.ts +43 -0
  58. package/dist/adapters/utils.d.ts.map +1 -0
  59. package/dist/adapters/utils.js +52 -0
  60. package/dist/adapters/utils.js.map +1 -0
  61. package/dist/agent-auth-jwt.d.ts +14 -0
  62. package/dist/agent-auth-jwt.d.ts.map +1 -0
  63. package/dist/agent-auth-jwt.js +117 -0
  64. package/dist/agent-auth-jwt.js.map +1 -0
  65. package/dist/app.d.ts +43 -0
  66. package/dist/app.d.ts.map +1 -0
  67. package/dist/app.js +373 -0
  68. package/dist/app.js.map +1 -0
  69. package/dist/attachment-types.d.ts +23 -0
  70. package/dist/attachment-types.d.ts.map +1 -0
  71. package/dist/attachment-types.js +91 -0
  72. package/dist/attachment-types.js.map +1 -0
  73. package/dist/auth/better-auth.d.ts +33 -0
  74. package/dist/auth/better-auth.d.ts.map +1 -0
  75. package/dist/auth/better-auth.js +133 -0
  76. package/dist/auth/better-auth.js.map +1 -0
  77. package/dist/board-claim.d.ts +23 -0
  78. package/dist/board-claim.d.ts.map +1 -0
  79. package/dist/board-claim.js +115 -0
  80. package/dist/board-claim.js.map +1 -0
  81. package/dist/config-file.d.ts +3 -0
  82. package/dist/config-file.d.ts.map +1 -0
  83. package/dist/config-file.js +16 -0
  84. package/dist/config-file.js.map +1 -0
  85. package/dist/config.d.ts +44 -0
  86. package/dist/config.d.ts.map +1 -0
  87. package/dist/config.js +226 -0
  88. package/dist/config.js.map +1 -0
  89. package/dist/dev-runner-worktree.d.ts +15 -0
  90. package/dist/dev-runner-worktree.d.ts.map +1 -0
  91. package/dist/dev-runner-worktree.js +68 -0
  92. package/dist/dev-runner-worktree.js.map +1 -0
  93. package/dist/dev-server-status.d.ts +27 -0
  94. package/dist/dev-server-status.d.ts.map +1 -0
  95. package/dist/dev-server-status.js +74 -0
  96. package/dist/dev-server-status.js.map +1 -0
  97. package/dist/dev-watch-ignore.d.ts +2 -0
  98. package/dist/dev-watch-ignore.d.ts.map +1 -0
  99. package/dist/dev-watch-ignore.js +36 -0
  100. package/dist/dev-watch-ignore.js.map +1 -0
  101. package/dist/errors.d.ts +12 -0
  102. package/dist/errors.d.ts.map +1 -0
  103. package/dist/errors.js +28 -0
  104. package/dist/errors.js.map +1 -0
  105. package/dist/home-paths.d.ts +17 -0
  106. package/dist/home-paths.d.ts.map +1 -0
  107. package/dist/home-paths.js +75 -0
  108. package/dist/home-paths.js.map +1 -0
  109. package/dist/index.d.ts +10 -0
  110. package/dist/index.d.ts.map +1 -0
  111. package/dist/index.js +753 -0
  112. package/dist/index.js.map +1 -0
  113. package/dist/lib/join-request-dedupe.d.ts +11 -0
  114. package/dist/lib/join-request-dedupe.d.ts.map +1 -0
  115. package/dist/lib/join-request-dedupe.js +49 -0
  116. package/dist/lib/join-request-dedupe.js.map +1 -0
  117. package/dist/log-redaction.d.ts +11 -0
  118. package/dist/log-redaction.d.ts.map +1 -0
  119. package/dist/log-redaction.js +122 -0
  120. package/dist/log-redaction.js.map +1 -0
  121. package/dist/middleware/auth.d.ts +12 -0
  122. package/dist/middleware/auth.d.ts.map +1 -0
  123. package/dist/middleware/auth.js +302 -0
  124. package/dist/middleware/auth.js.map +1 -0
  125. package/dist/middleware/board-mutation-guard.d.ts +3 -0
  126. package/dist/middleware/board-mutation-guard.d.ts.map +1 -0
  127. package/dist/middleware/board-mutation-guard.js +67 -0
  128. package/dist/middleware/board-mutation-guard.js.map +1 -0
  129. package/dist/middleware/error-handler.d.ts +17 -0
  130. package/dist/middleware/error-handler.d.ts.map +1 -0
  131. package/dist/middleware/error-handler.js +45 -0
  132. package/dist/middleware/error-handler.js.map +1 -0
  133. package/dist/middleware/http-log-policy.d.ts +2 -0
  134. package/dist/middleware/http-log-policy.d.ts.map +1 -0
  135. package/dist/middleware/http-log-policy.js +52 -0
  136. package/dist/middleware/http-log-policy.js.map +1 -0
  137. package/dist/middleware/index.d.ts +4 -0
  138. package/dist/middleware/index.d.ts.map +1 -0
  139. package/dist/middleware/index.js +4 -0
  140. package/dist/middleware/index.js.map +1 -0
  141. package/dist/middleware/logger.d.ts +4 -0
  142. package/dist/middleware/logger.d.ts.map +1 -0
  143. package/dist/middleware/logger.js +92 -0
  144. package/dist/middleware/logger.js.map +1 -0
  145. package/dist/middleware/private-hostname-guard.d.ts +11 -0
  146. package/dist/middleware/private-hostname-guard.d.ts.map +1 -0
  147. package/dist/middleware/private-hostname-guard.js +78 -0
  148. package/dist/middleware/private-hostname-guard.js.map +1 -0
  149. package/dist/middleware/validate.d.ts +4 -0
  150. package/dist/middleware/validate.d.ts.map +1 -0
  151. package/dist/middleware/validate.js +7 -0
  152. package/dist/middleware/validate.js.map +1 -0
  153. package/dist/onboarding-assets/ceo/AGENTS.md +59 -0
  154. package/dist/onboarding-assets/ceo/HEARTBEAT.md +85 -0
  155. package/dist/onboarding-assets/ceo/SOUL.md +33 -0
  156. package/dist/onboarding-assets/ceo/TOOLS.md +3 -0
  157. package/dist/onboarding-assets/default/AGENTS.md +17 -0
  158. package/dist/paths.d.ts +3 -0
  159. package/dist/paths.d.ts.map +1 -0
  160. package/dist/paths.js +31 -0
  161. package/dist/paths.js.map +1 -0
  162. package/dist/realtime/live-events-ws.d.ts +28 -0
  163. package/dist/realtime/live-events-ws.d.ts.map +1 -0
  164. package/dist/realtime/live-events-ws.js +187 -0
  165. package/dist/realtime/live-events-ws.js.map +1 -0
  166. package/dist/redaction.d.ts +5 -0
  167. package/dist/redaction.d.ts.map +1 -0
  168. package/dist/redaction.js +98 -0
  169. package/dist/redaction.js.map +1 -0
  170. package/dist/routes/access.d.ts +82 -0
  171. package/dist/routes/access.d.ts.map +1 -0
  172. package/dist/routes/access.js +3411 -0
  173. package/dist/routes/access.js.map +1 -0
  174. package/dist/routes/activity.d.ts +3 -0
  175. package/dist/routes/activity.d.ts.map +1 -0
  176. package/dist/routes/activity.js +90 -0
  177. package/dist/routes/activity.js.map +1 -0
  178. package/dist/routes/adapters.d.ts +16 -0
  179. package/dist/routes/adapters.d.ts.map +1 -0
  180. package/dist/routes/adapters.js +527 -0
  181. package/dist/routes/adapters.js.map +1 -0
  182. package/dist/routes/agents.d.ts +6 -0
  183. package/dist/routes/agents.d.ts.map +1 -0
  184. package/dist/routes/agents.js +2753 -0
  185. package/dist/routes/agents.js.map +1 -0
  186. package/dist/routes/approvals.d.ts +6 -0
  187. package/dist/routes/approvals.d.ts.map +1 -0
  188. package/dist/routes/approvals.js +300 -0
  189. package/dist/routes/approvals.js.map +1 -0
  190. package/dist/routes/assets.d.ts +4 -0
  191. package/dist/routes/assets.d.ts.map +1 -0
  192. package/dist/routes/assets.js +309 -0
  193. package/dist/routes/assets.js.map +1 -0
  194. package/dist/routes/auth.d.ts +3 -0
  195. package/dist/routes/auth.d.ts.map +1 -0
  196. package/dist/routes/auth.js +82 -0
  197. package/dist/routes/auth.js.map +1 -0
  198. package/dist/routes/authz.d.ts +19 -0
  199. package/dist/routes/authz.d.ts.map +1 -0
  200. package/dist/routes/authz.js +75 -0
  201. package/dist/routes/authz.js.map +1 -0
  202. package/dist/routes/companies.d.ts +4 -0
  203. package/dist/routes/companies.d.ts.map +1 -0
  204. package/dist/routes/companies.js +359 -0
  205. package/dist/routes/companies.js.map +1 -0
  206. package/dist/routes/company-skills.d.ts +3 -0
  207. package/dist/routes/company-skills.d.ts.map +1 -0
  208. package/dist/routes/company-skills.js +258 -0
  209. package/dist/routes/company-skills.js.map +1 -0
  210. package/dist/routes/costs.d.ts +11 -0
  211. package/dist/routes/costs.d.ts.map +1 -0
  212. package/dist/routes/costs.js +285 -0
  213. package/dist/routes/costs.js.map +1 -0
  214. package/dist/routes/dashboard.d.ts +3 -0
  215. package/dist/routes/dashboard.d.ts.map +1 -0
  216. package/dist/routes/dashboard.js +15 -0
  217. package/dist/routes/dashboard.js.map +1 -0
  218. package/dist/routes/environment-selection.d.ts +13 -0
  219. package/dist/routes/environment-selection.d.ts.map +1 -0
  220. package/dist/routes/environment-selection.js +30 -0
  221. package/dist/routes/environment-selection.js.map +1 -0
  222. package/dist/routes/environments.d.ts +6 -0
  223. package/dist/routes/environments.d.ts.map +1 -0
  224. package/dist/routes/environments.js +401 -0
  225. package/dist/routes/environments.js.map +1 -0
  226. package/dist/routes/execution-workspaces.d.ts +3 -0
  227. package/dist/routes/execution-workspaces.d.ts.map +1 -0
  228. package/dist/routes/execution-workspaces.js +536 -0
  229. package/dist/routes/execution-workspaces.js.map +1 -0
  230. package/dist/routes/goals.d.ts +3 -0
  231. package/dist/routes/goals.d.ts.map +1 -0
  232. package/dist/routes/goals.js +101 -0
  233. package/dist/routes/goals.js.map +1 -0
  234. package/dist/routes/health.d.ts +9 -0
  235. package/dist/routes/health.d.ts.map +1 -0
  236. package/dist/routes/health.js +114 -0
  237. package/dist/routes/health.js.map +1 -0
  238. package/dist/routes/inbox-dismissals.d.ts +3 -0
  239. package/dist/routes/inbox-dismissals.d.ts.map +1 -0
  240. package/dist/routes/inbox-dismissals.js +58 -0
  241. package/dist/routes/inbox-dismissals.js.map +1 -0
  242. package/dist/routes/index.d.ts +22 -0
  243. package/dist/routes/index.d.ts.map +1 -0
  244. package/dist/routes/index.js +22 -0
  245. package/dist/routes/index.js.map +1 -0
  246. package/dist/routes/instance-database-backups.d.ts +15 -0
  247. package/dist/routes/instance-database-backups.d.ts.map +1 -0
  248. package/dist/routes/instance-database-backups.js +12 -0
  249. package/dist/routes/instance-database-backups.js.map +1 -0
  250. package/dist/routes/instance-settings.d.ts +3 -0
  251. package/dist/routes/instance-settings.d.ts.map +1 -0
  252. package/dist/routes/instance-settings.js +110 -0
  253. package/dist/routes/instance-settings.js.map +1 -0
  254. package/dist/routes/issue-tree-control.d.ts +3 -0
  255. package/dist/routes/issue-tree-control.d.ts.map +1 -0
  256. package/dist/routes/issue-tree-control.js +363 -0
  257. package/dist/routes/issue-tree-control.js.map +1 -0
  258. package/dist/routes/issues-checkout-wakeup.d.ts +9 -0
  259. package/dist/routes/issues-checkout-wakeup.d.ts.map +1 -0
  260. package/dist/routes/issues-checkout-wakeup.js +12 -0
  261. package/dist/routes/issues-checkout-wakeup.js.map +1 -0
  262. package/dist/routes/issues.d.ts +23 -0
  263. package/dist/routes/issues.d.ts.map +1 -0
  264. package/dist/routes/issues.js +3886 -0
  265. package/dist/routes/issues.js.map +1 -0
  266. package/dist/routes/llms.d.ts +3 -0
  267. package/dist/routes/llms.d.ts.map +1 -0
  268. package/dist/routes/llms.js +80 -0
  269. package/dist/routes/llms.js.map +1 -0
  270. package/dist/routes/org-chart-svg.d.ts +25 -0
  271. package/dist/routes/org-chart-svg.d.ts.map +1 -0
  272. package/dist/routes/org-chart-svg.js +656 -0
  273. package/dist/routes/org-chart-svg.js.map +1 -0
  274. package/dist/routes/plugin-ui-static.d.ts +69 -0
  275. package/dist/routes/plugin-ui-static.d.ts.map +1 -0
  276. package/dist/routes/plugin-ui-static.js +411 -0
  277. package/dist/routes/plugin-ui-static.js.map +1 -0
  278. package/dist/routes/plugins.d.ts +121 -0
  279. package/dist/routes/plugins.d.ts.map +1 -0
  280. package/dist/routes/plugins.js +2192 -0
  281. package/dist/routes/plugins.js.map +1 -0
  282. package/dist/routes/projects.d.ts +3 -0
  283. package/dist/routes/projects.d.ts.map +1 -0
  284. package/dist/routes/projects.js +566 -0
  285. package/dist/routes/projects.js.map +1 -0
  286. package/dist/routes/routines.d.ts +6 -0
  287. package/dist/routes/routines.d.ts.map +1 -0
  288. package/dist/routes/routines.js +417 -0
  289. package/dist/routes/routines.js.map +1 -0
  290. package/dist/routes/secrets.d.ts +3 -0
  291. package/dist/routes/secrets.d.ts.map +1 -0
  292. package/dist/routes/secrets.js +128 -0
  293. package/dist/routes/secrets.js.map +1 -0
  294. package/dist/routes/sidebar-badges.d.ts +3 -0
  295. package/dist/routes/sidebar-badges.d.ts.map +1 -0
  296. package/dist/routes/sidebar-badges.js +68 -0
  297. package/dist/routes/sidebar-badges.js.map +1 -0
  298. package/dist/routes/sidebar-preferences.d.ts +3 -0
  299. package/dist/routes/sidebar-preferences.d.ts.map +1 -0
  300. package/dist/routes/sidebar-preferences.js +63 -0
  301. package/dist/routes/sidebar-preferences.js.map +1 -0
  302. package/dist/routes/user-profiles.d.ts +3 -0
  303. package/dist/routes/user-profiles.d.ts.map +1 -0
  304. package/dist/routes/user-profiles.js +337 -0
  305. package/dist/routes/user-profiles.js.map +1 -0
  306. package/dist/routes/workspace-command-authz.d.ts +14 -0
  307. package/dist/routes/workspace-command-authz.d.ts.map +1 -0
  308. package/dist/routes/workspace-command-authz.js +83 -0
  309. package/dist/routes/workspace-command-authz.js.map +1 -0
  310. package/dist/routes/workspace-runtime-service-authz.d.ts +12 -0
  311. package/dist/routes/workspace-runtime-service-authz.d.ts.map +1 -0
  312. package/dist/routes/workspace-runtime-service-authz.js +96 -0
  313. package/dist/routes/workspace-runtime-service-authz.js.map +1 -0
  314. package/dist/runtime-api.d.ts +19 -0
  315. package/dist/runtime-api.d.ts.map +1 -0
  316. package/dist/runtime-api.js +137 -0
  317. package/dist/runtime-api.js.map +1 -0
  318. package/dist/secrets/external-stub-providers.d.ts +5 -0
  319. package/dist/secrets/external-stub-providers.d.ts.map +1 -0
  320. package/dist/secrets/external-stub-providers.js +21 -0
  321. package/dist/secrets/external-stub-providers.js.map +1 -0
  322. package/dist/secrets/local-encrypted-provider.d.ts +3 -0
  323. package/dist/secrets/local-encrypted-provider.d.ts.map +1 -0
  324. package/dist/secrets/local-encrypted-provider.js +116 -0
  325. package/dist/secrets/local-encrypted-provider.js.map +1 -0
  326. package/dist/secrets/provider-registry.d.ts +5 -0
  327. package/dist/secrets/provider-registry.d.ts.map +1 -0
  328. package/dist/secrets/provider-registry.js +20 -0
  329. package/dist/secrets/provider-registry.js.map +1 -0
  330. package/dist/secrets/types.d.ts +21 -0
  331. package/dist/secrets/types.d.ts.map +1 -0
  332. package/dist/secrets/types.js +2 -0
  333. package/dist/secrets/types.js.map +1 -0
  334. package/dist/services/access.d.ts +171 -0
  335. package/dist/services/access.d.ts.map +1 -0
  336. package/dist/services/access.js +522 -0
  337. package/dist/services/access.js.map +1 -0
  338. package/dist/services/activity-log.d.ts +19 -0
  339. package/dist/services/activity-log.d.ts.map +1 -0
  340. package/dist/services/activity-log.js +99 -0
  341. package/dist/services/activity-log.js.map +1 -0
  342. package/dist/services/activity.d.ts +462 -0
  343. package/dist/services/activity.d.ts.map +1 -0
  344. package/dist/services/activity.js +443 -0
  345. package/dist/services/activity.js.map +1 -0
  346. package/dist/services/adapter-plugin-store.d.ts +36 -0
  347. package/dist/services/adapter-plugin-store.d.ts.map +1 -0
  348. package/dist/services/adapter-plugin-store.js +154 -0
  349. package/dist/services/adapter-plugin-store.js.map +1 -0
  350. package/dist/services/agent-instructions.d.ts +91 -0
  351. package/dist/services/agent-instructions.d.ts.map +1 -0
  352. package/dist/services/agent-instructions.js +580 -0
  353. package/dist/services/agent-instructions.js.map +1 -0
  354. package/dist/services/agent-permissions.d.ts +6 -0
  355. package/dist/services/agent-permissions.d.ts.map +1 -0
  356. package/dist/services/agent-permissions.js +18 -0
  357. package/dist/services/agent-permissions.js.map +1 -0
  358. package/dist/services/agent-start-lock.d.ts +2 -0
  359. package/dist/services/agent-start-lock.d.ts.map +1 -0
  360. package/dist/services/agent-start-lock.js +43 -0
  361. package/dist/services/agent-start-lock.js.map +1 -0
  362. package/dist/services/agents.d.ts +2253 -0
  363. package/dist/services/agents.d.ts.map +1 -0
  364. package/dist/services/agents.js +609 -0
  365. package/dist/services/agents.js.map +1 -0
  366. package/dist/services/approvals.d.ts +546 -0
  367. package/dist/services/approvals.d.ts.map +1 -0
  368. package/dist/services/approvals.js +212 -0
  369. package/dist/services/approvals.js.map +1 -0
  370. package/dist/services/assets.d.ts +33 -0
  371. package/dist/services/assets.d.ts.map +1 -0
  372. package/dist/services/assets.js +17 -0
  373. package/dist/services/assets.js.map +1 -0
  374. package/dist/services/board-auth.d.ts +239 -0
  375. package/dist/services/board-auth.d.ts.map +1 -0
  376. package/dist/services/board-auth.js +300 -0
  377. package/dist/services/board-auth.js.map +1 -0
  378. package/dist/services/budgets.d.ts +38 -0
  379. package/dist/services/budgets.d.ts.map +1 -0
  380. package/dist/services/budgets.js +784 -0
  381. package/dist/services/budgets.js.map +1 -0
  382. package/dist/services/companies.d.ts +154 -0
  383. package/dist/services/companies.d.ts.map +1 -0
  384. package/dist/services/companies.js +267 -0
  385. package/dist/services/companies.js.map +1 -0
  386. package/dist/services/company-export-readme.d.ts +17 -0
  387. package/dist/services/company-export-readme.d.ts.map +1 -0
  388. package/dist/services/company-export-readme.js +148 -0
  389. package/dist/services/company-export-readme.js.map +1 -0
  390. package/dist/services/company-member-roles.d.ts +9 -0
  391. package/dist/services/company-member-roles.d.ts.map +1 -0
  392. package/dist/services/company-member-roles.js +46 -0
  393. package/dist/services/company-member-roles.js.map +1 -0
  394. package/dist/services/company-portability.d.ts +24 -0
  395. package/dist/services/company-portability.d.ts.map +1 -0
  396. package/dist/services/company-portability.js +4076 -0
  397. package/dist/services/company-portability.js.map +1 -0
  398. package/dist/services/company-search-rate-limit.d.ts +22 -0
  399. package/dist/services/company-search-rate-limit.d.ts.map +1 -0
  400. package/dist/services/company-search-rate-limit.js +38 -0
  401. package/dist/services/company-search-rate-limit.js.map +1 -0
  402. package/dist/services/company-search.d.ts +8 -0
  403. package/dist/services/company-search.d.ts.map +1 -0
  404. package/dist/services/company-search.js +626 -0
  405. package/dist/services/company-search.js.map +1 -0
  406. package/dist/services/company-skills.d.ts +77 -0
  407. package/dist/services/company-skills.d.ts.map +1 -0
  408. package/dist/services/company-skills.js +2120 -0
  409. package/dist/services/company-skills.js.map +1 -0
  410. package/dist/services/costs.d.ts +127 -0
  411. package/dist/services/costs.d.ts.map +1 -0
  412. package/dist/services/costs.js +409 -0
  413. package/dist/services/costs.js.map +1 -0
  414. package/dist/services/cron.d.ts +80 -0
  415. package/dist/services/cron.d.ts.map +1 -0
  416. package/dist/services/cron.js +300 -0
  417. package/dist/services/cron.js.map +1 -0
  418. package/dist/services/dashboard.d.ts +34 -0
  419. package/dist/services/dashboard.d.ts.map +1 -0
  420. package/dist/services/dashboard.js +142 -0
  421. package/dist/services/dashboard.js.map +1 -0
  422. package/dist/services/default-agent-instructions.d.ts +9 -0
  423. package/dist/services/default-agent-instructions.d.ts.map +1 -0
  424. package/dist/services/default-agent-instructions.js +20 -0
  425. package/dist/services/default-agent-instructions.js.map +1 -0
  426. package/dist/services/documents.d.ts +199 -0
  427. package/dist/services/documents.d.ts.map +1 -0
  428. package/dist/services/documents.js +411 -0
  429. package/dist/services/documents.js.map +1 -0
  430. package/dist/services/environment-config.d.ts +43 -0
  431. package/dist/services/environment-config.d.ts.map +1 -0
  432. package/dist/services/environment-config.js +388 -0
  433. package/dist/services/environment-config.js.map +1 -0
  434. package/dist/services/environment-execution-target.d.ts +21 -0
  435. package/dist/services/environment-execution-target.d.ts.map +1 -0
  436. package/dist/services/environment-execution-target.js +119 -0
  437. package/dist/services/environment-execution-target.js.map +1 -0
  438. package/dist/services/environment-probe.d.ts +9 -0
  439. package/dist/services/environment-probe.d.ts.map +1 -0
  440. package/dist/services/environment-probe.js +106 -0
  441. package/dist/services/environment-probe.js.map +1 -0
  442. package/dist/services/environment-run-orchestrator.d.ts +124 -0
  443. package/dist/services/environment-run-orchestrator.d.ts.map +1 -0
  444. package/dist/services/environment-run-orchestrator.js +392 -0
  445. package/dist/services/environment-run-orchestrator.js.map +1 -0
  446. package/dist/services/environment-runtime.d.ts +90 -0
  447. package/dist/services/environment-runtime.d.ts.map +1 -0
  448. package/dist/services/environment-runtime.js +934 -0
  449. package/dist/services/environment-runtime.js.map +1 -0
  450. package/dist/services/environments.d.ts +36 -0
  451. package/dist/services/environments.d.ts.map +1 -0
  452. package/dist/services/environments.js +260 -0
  453. package/dist/services/environments.js.map +1 -0
  454. package/dist/services/execution-workspace-policy.d.ts +30 -0
  455. package/dist/services/execution-workspace-policy.d.ts.map +1 -0
  456. package/dist/services/execution-workspace-policy.js +195 -0
  457. package/dist/services/execution-workspace-policy.js.map +1 -0
  458. package/dist/services/execution-workspaces.d.ts +30 -0
  459. package/dist/services/execution-workspaces.d.ts.map +1 -0
  460. package/dist/services/execution-workspaces.js +635 -0
  461. package/dist/services/execution-workspaces.js.map +1 -0
  462. package/dist/services/feedback-redaction.d.ts +23 -0
  463. package/dist/services/feedback-redaction.d.ts.map +1 -0
  464. package/dist/services/feedback-redaction.js +150 -0
  465. package/dist/services/feedback-redaction.js.map +1 -0
  466. package/dist/services/feedback-share-client.d.ts +9 -0
  467. package/dist/services/feedback-share-client.d.ts.map +1 -0
  468. package/dist/services/feedback-share-client.js +46 -0
  469. package/dist/services/feedback-share-client.js.map +1 -0
  470. package/dist/services/feedback.d.ts +93 -0
  471. package/dist/services/feedback.d.ts.map +1 -0
  472. package/dist/services/feedback.js +1717 -0
  473. package/dist/services/feedback.js.map +1 -0
  474. package/dist/services/finance.d.ts +93 -0
  475. package/dist/services/finance.d.ts.map +1 -0
  476. package/dist/services/finance.js +120 -0
  477. package/dist/services/finance.js.map +1 -0
  478. package/dist/services/github-fetch.d.ts +4 -0
  479. package/dist/services/github-fetch.d.ts.map +1 -0
  480. package/dist/services/github-fetch.js +23 -0
  481. package/dist/services/github-fetch.js.map +1 -0
  482. package/dist/services/goals.d.ts +433 -0
  483. package/dist/services/goals.d.ts.map +1 -0
  484. package/dist/services/goals.js +54 -0
  485. package/dist/services/goals.js.map +1 -0
  486. package/dist/services/heartbeat-run-summary.d.ts +7 -0
  487. package/dist/services/heartbeat-run-summary.d.ts.map +1 -0
  488. package/dist/services/heartbeat-run-summary.js +84 -0
  489. package/dist/services/heartbeat-run-summary.js.map +1 -0
  490. package/dist/services/heartbeat-stop-metadata.d.ts +28 -0
  491. package/dist/services/heartbeat-stop-metadata.d.ts.map +1 -0
  492. package/dist/services/heartbeat-stop-metadata.js +86 -0
  493. package/dist/services/heartbeat-stop-metadata.js.map +1 -0
  494. package/dist/services/heartbeat-stop-metadata.test.d.ts +2 -0
  495. package/dist/services/heartbeat-stop-metadata.test.d.ts.map +1 -0
  496. package/dist/services/heartbeat-stop-metadata.test.js +93 -0
  497. package/dist/services/heartbeat-stop-metadata.test.js.map +1 -0
  498. package/dist/services/heartbeat.d.ts +1484 -0
  499. package/dist/services/heartbeat.d.ts.map +1 -0
  500. package/dist/services/heartbeat.js +7557 -0
  501. package/dist/services/heartbeat.js.map +1 -0
  502. package/dist/services/hire-hook.d.ts +14 -0
  503. package/dist/services/hire-hook.d.ts.map +1 -0
  504. package/dist/services/hire-hook.js +85 -0
  505. package/dist/services/hire-hook.js.map +1 -0
  506. package/dist/services/inbox-dismissals.d.ts +22 -0
  507. package/dist/services/inbox-dismissals.d.ts.map +1 -0
  508. package/dist/services/inbox-dismissals.js +33 -0
  509. package/dist/services/inbox-dismissals.js.map +1 -0
  510. package/dist/services/index.d.ts +44 -0
  511. package/dist/services/index.d.ts.map +1 -0
  512. package/dist/services/index.js +44 -0
  513. package/dist/services/index.js.map +1 -0
  514. package/dist/services/instance-settings.d.ts +11 -0
  515. package/dist/services/instance-settings.d.ts.map +1 -0
  516. package/dist/services/instance-settings.js +138 -0
  517. package/dist/services/instance-settings.js.map +1 -0
  518. package/dist/services/invite-grants.d.ts +15 -0
  519. package/dist/services/invite-grants.d.ts.map +1 -0
  520. package/dist/services/invite-grants.js +50 -0
  521. package/dist/services/invite-grants.js.map +1 -0
  522. package/dist/services/issue-approvals.d.ts +56 -0
  523. package/dist/services/issue-approvals.d.ts.map +1 -0
  524. package/dist/services/issue-approvals.js +153 -0
  525. package/dist/services/issue-approvals.js.map +1 -0
  526. package/dist/services/issue-assignment-wakeup.d.ts +29 -0
  527. package/dist/services/issue-assignment-wakeup.d.ts.map +1 -0
  528. package/dist/services/issue-assignment-wakeup.js +22 -0
  529. package/dist/services/issue-assignment-wakeup.js.map +1 -0
  530. package/dist/services/issue-continuation-summary.d.ts +66 -0
  531. package/dist/services/issue-continuation-summary.d.ts.map +1 -0
  532. package/dist/services/issue-continuation-summary.js +212 -0
  533. package/dist/services/issue-continuation-summary.js.map +1 -0
  534. package/dist/services/issue-execution-policy.d.ts +93 -0
  535. package/dist/services/issue-execution-policy.d.ts.map +1 -0
  536. package/dist/services/issue-execution-policy.js +838 -0
  537. package/dist/services/issue-execution-policy.js.map +1 -0
  538. package/dist/services/issue-goal-fallback.d.ts +18 -0
  539. package/dist/services/issue-goal-fallback.d.ts.map +1 -0
  540. package/dist/services/issue-goal-fallback.js +33 -0
  541. package/dist/services/issue-goal-fallback.js.map +1 -0
  542. package/dist/services/issue-liveness.d.ts +3 -0
  543. package/dist/services/issue-liveness.d.ts.map +1 -0
  544. package/dist/services/issue-liveness.js +2 -0
  545. package/dist/services/issue-liveness.js.map +1 -0
  546. package/dist/services/issue-references.d.ts +21 -0
  547. package/dist/services/issue-references.d.ts.map +1 -0
  548. package/dist/services/issue-references.js +318 -0
  549. package/dist/services/issue-references.js.map +1 -0
  550. package/dist/services/issue-thread-interactions.d.ts +76 -0
  551. package/dist/services/issue-thread-interactions.d.ts.map +1 -0
  552. package/dist/services/issue-thread-interactions.js +923 -0
  553. package/dist/services/issue-thread-interactions.js.map +1 -0
  554. package/dist/services/issue-thread-interactions.test.d.ts +2 -0
  555. package/dist/services/issue-thread-interactions.test.d.ts.map +1 -0
  556. package/dist/services/issue-thread-interactions.test.js +195 -0
  557. package/dist/services/issue-thread-interactions.test.js.map +1 -0
  558. package/dist/services/issue-tree-control.d.ts +89 -0
  559. package/dist/services/issue-tree-control.d.ts.map +1 -0
  560. package/dist/services/issue-tree-control.js +933 -0
  561. package/dist/services/issue-tree-control.js.map +1 -0
  562. package/dist/services/issues.d.ts +710 -0
  563. package/dist/services/issues.d.ts.map +1 -0
  564. package/dist/services/issues.js +3199 -0
  565. package/dist/services/issues.js.map +1 -0
  566. package/dist/services/json-schema-secret-refs.d.ts +5 -0
  567. package/dist/services/json-schema-secret-refs.d.ts.map +1 -0
  568. package/dist/services/json-schema-secret-refs.js +67 -0
  569. package/dist/services/json-schema-secret-refs.js.map +1 -0
  570. package/dist/services/live-events.d.ts +17 -0
  571. package/dist/services/live-events.d.ts.map +1 -0
  572. package/dist/services/live-events.js +33 -0
  573. package/dist/services/live-events.js.map +1 -0
  574. package/dist/services/local-service-supervisor.d.ts +56 -0
  575. package/dist/services/local-service-supervisor.d.ts.map +1 -0
  576. package/dist/services/local-service-supervisor.js +284 -0
  577. package/dist/services/local-service-supervisor.js.map +1 -0
  578. package/dist/services/plugin-capability-validator.d.ts +108 -0
  579. package/dist/services/plugin-capability-validator.d.ts.map +1 -0
  580. package/dist/services/plugin-capability-validator.js +313 -0
  581. package/dist/services/plugin-capability-validator.js.map +1 -0
  582. package/dist/services/plugin-config-validator.d.ts +26 -0
  583. package/dist/services/plugin-config-validator.d.ts.map +1 -0
  584. package/dist/services/plugin-config-validator.js +41 -0
  585. package/dist/services/plugin-config-validator.js.map +1 -0
  586. package/dist/services/plugin-database.d.ts +49 -0
  587. package/dist/services/plugin-database.d.ts.map +1 -0
  588. package/dist/services/plugin-database.js +440 -0
  589. package/dist/services/plugin-database.js.map +1 -0
  590. package/dist/services/plugin-dev-watcher.d.ts +30 -0
  591. package/dist/services/plugin-dev-watcher.d.ts.map +1 -0
  592. package/dist/services/plugin-dev-watcher.js +241 -0
  593. package/dist/services/plugin-dev-watcher.js.map +1 -0
  594. package/dist/services/plugin-environment-driver.d.ts +124 -0
  595. package/dist/services/plugin-environment-driver.d.ts.map +1 -0
  596. package/dist/services/plugin-environment-driver.js +224 -0
  597. package/dist/services/plugin-environment-driver.js.map +1 -0
  598. package/dist/services/plugin-event-bus.d.ts +149 -0
  599. package/dist/services/plugin-event-bus.d.ts.map +1 -0
  600. package/dist/services/plugin-event-bus.js +258 -0
  601. package/dist/services/plugin-event-bus.js.map +1 -0
  602. package/dist/services/plugin-host-service-cleanup.d.ts +14 -0
  603. package/dist/services/plugin-host-service-cleanup.d.ts.map +1 -0
  604. package/dist/services/plugin-host-service-cleanup.js +37 -0
  605. package/dist/services/plugin-host-service-cleanup.js.map +1 -0
  606. package/dist/services/plugin-host-services.d.ts +17 -0
  607. package/dist/services/plugin-host-services.d.ts.map +1 -0
  608. package/dist/services/plugin-host-services.js +1861 -0
  609. package/dist/services/plugin-host-services.js.map +1 -0
  610. package/dist/services/plugin-job-coordinator.d.ts +81 -0
  611. package/dist/services/plugin-job-coordinator.d.ts.map +1 -0
  612. package/dist/services/plugin-job-coordinator.js +172 -0
  613. package/dist/services/plugin-job-coordinator.js.map +1 -0
  614. package/dist/services/plugin-job-scheduler.d.ts +163 -0
  615. package/dist/services/plugin-job-scheduler.d.ts.map +1 -0
  616. package/dist/services/plugin-job-scheduler.js +454 -0
  617. package/dist/services/plugin-job-scheduler.js.map +1 -0
  618. package/dist/services/plugin-job-store.d.ts +208 -0
  619. package/dist/services/plugin-job-store.d.ts.map +1 -0
  620. package/dist/services/plugin-job-store.js +350 -0
  621. package/dist/services/plugin-job-store.js.map +1 -0
  622. package/dist/services/plugin-lifecycle.d.ts +203 -0
  623. package/dist/services/plugin-lifecycle.d.ts.map +1 -0
  624. package/dist/services/plugin-lifecycle.js +476 -0
  625. package/dist/services/plugin-lifecycle.js.map +1 -0
  626. package/dist/services/plugin-loader.d.ts +445 -0
  627. package/dist/services/plugin-loader.d.ts.map +1 -0
  628. package/dist/services/plugin-loader.js +1273 -0
  629. package/dist/services/plugin-loader.js.map +1 -0
  630. package/dist/services/plugin-local-folders.d.ts +48 -0
  631. package/dist/services/plugin-local-folders.d.ts.map +1 -0
  632. package/dist/services/plugin-local-folders.js +461 -0
  633. package/dist/services/plugin-local-folders.js.map +1 -0
  634. package/dist/services/plugin-log-retention.d.ts +20 -0
  635. package/dist/services/plugin-log-retention.d.ts.map +1 -0
  636. package/dist/services/plugin-log-retention.js +63 -0
  637. package/dist/services/plugin-log-retention.js.map +1 -0
  638. package/dist/services/plugin-managed-agents.d.ts +15 -0
  639. package/dist/services/plugin-managed-agents.d.ts.map +1 -0
  640. package/dist/services/plugin-managed-agents.js +414 -0
  641. package/dist/services/plugin-managed-agents.js.map +1 -0
  642. package/dist/services/plugin-managed-routines.d.ts +41 -0
  643. package/dist/services/plugin-managed-routines.d.ts.map +1 -0
  644. package/dist/services/plugin-managed-routines.js +416 -0
  645. package/dist/services/plugin-managed-routines.js.map +1 -0
  646. package/dist/services/plugin-manifest-validator.d.ts +79 -0
  647. package/dist/services/plugin-manifest-validator.d.ts.map +1 -0
  648. package/dist/services/plugin-manifest-validator.js +84 -0
  649. package/dist/services/plugin-manifest-validator.js.map +1 -0
  650. package/dist/services/plugin-registry.d.ts +2550 -0
  651. package/dist/services/plugin-registry.d.ts.map +1 -0
  652. package/dist/services/plugin-registry.js +581 -0
  653. package/dist/services/plugin-registry.js.map +1 -0
  654. package/dist/services/plugin-runtime-sandbox.d.ts +40 -0
  655. package/dist/services/plugin-runtime-sandbox.d.ts.map +1 -0
  656. package/dist/services/plugin-runtime-sandbox.js +154 -0
  657. package/dist/services/plugin-runtime-sandbox.js.map +1 -0
  658. package/dist/services/plugin-secrets-handler.d.ts +81 -0
  659. package/dist/services/plugin-secrets-handler.d.ts.map +1 -0
  660. package/dist/services/plugin-secrets-handler.js +231 -0
  661. package/dist/services/plugin-secrets-handler.js.map +1 -0
  662. package/dist/services/plugin-state-store.d.ts +92 -0
  663. package/dist/services/plugin-state-store.d.ts.map +1 -0
  664. package/dist/services/plugin-state-store.js +190 -0
  665. package/dist/services/plugin-state-store.js.map +1 -0
  666. package/dist/services/plugin-stream-bus.d.ts +29 -0
  667. package/dist/services/plugin-stream-bus.d.ts.map +1 -0
  668. package/dist/services/plugin-stream-bus.js +48 -0
  669. package/dist/services/plugin-stream-bus.js.map +1 -0
  670. package/dist/services/plugin-tool-dispatcher.d.ts +180 -0
  671. package/dist/services/plugin-tool-dispatcher.d.ts.map +1 -0
  672. package/dist/services/plugin-tool-dispatcher.js +224 -0
  673. package/dist/services/plugin-tool-dispatcher.js.map +1 -0
  674. package/dist/services/plugin-tool-registry.d.ts +192 -0
  675. package/dist/services/plugin-tool-registry.d.ts.map +1 -0
  676. package/dist/services/plugin-tool-registry.js +224 -0
  677. package/dist/services/plugin-tool-registry.js.map +1 -0
  678. package/dist/services/plugin-worker-manager.d.ts +262 -0
  679. package/dist/services/plugin-worker-manager.d.ts.map +1 -0
  680. package/dist/services/plugin-worker-manager.js +836 -0
  681. package/dist/services/plugin-worker-manager.js.map +1 -0
  682. package/dist/services/productivity-review.d.ts +83 -0
  683. package/dist/services/productivity-review.d.ts.map +1 -0
  684. package/dist/services/productivity-review.js +652 -0
  685. package/dist/services/productivity-review.js.map +1 -0
  686. package/dist/services/project-workspace-runtime-config.d.ts +4 -0
  687. package/dist/services/project-workspace-runtime-config.d.ts.map +1 -0
  688. package/dist/services/project-workspace-runtime-config.js +54 -0
  689. package/dist/services/project-workspace-runtime-config.js.map +1 -0
  690. package/dist/services/projects.d.ts +99 -0
  691. package/dist/services/projects.d.ts.map +1 -0
  692. package/dist/services/projects.js +879 -0
  693. package/dist/services/projects.js.map +1 -0
  694. package/dist/services/quota-windows.d.ts +9 -0
  695. package/dist/services/quota-windows.d.ts.map +1 -0
  696. package/dist/services/quota-windows.js +56 -0
  697. package/dist/services/quota-windows.js.map +1 -0
  698. package/dist/services/recovery/index.d.ts +10 -0
  699. package/dist/services/recovery/index.d.ts.map +1 -0
  700. package/dist/services/recovery/index.js +6 -0
  701. package/dist/services/recovery/index.js.map +1 -0
  702. package/dist/services/recovery/issue-graph-liveness.d.ts +85 -0
  703. package/dist/services/recovery/issue-graph-liveness.d.ts.map +1 -0
  704. package/dist/services/recovery/issue-graph-liveness.js +343 -0
  705. package/dist/services/recovery/issue-graph-liveness.js.map +1 -0
  706. package/dist/services/recovery/model-profile-hint.d.ts +8 -0
  707. package/dist/services/recovery/model-profile-hint.d.ts.map +1 -0
  708. package/dist/services/recovery/model-profile-hint.js +11 -0
  709. package/dist/services/recovery/model-profile-hint.js.map +1 -0
  710. package/dist/services/recovery/origins.d.ts +36 -0
  711. package/dist/services/recovery/origins.d.ts.map +1 -0
  712. package/dist/services/recovery/origins.js +45 -0
  713. package/dist/services/recovery/origins.js.map +1 -0
  714. package/dist/services/recovery/pause-hold-guard.d.ts +6 -0
  715. package/dist/services/recovery/pause-hold-guard.d.ts.map +1 -0
  716. package/dist/services/recovery/pause-hold-guard.js +6 -0
  717. package/dist/services/recovery/pause-hold-guard.js.map +1 -0
  718. package/dist/services/recovery/run-liveness-continuations.d.ts +50 -0
  719. package/dist/services/recovery/run-liveness-continuations.d.ts.map +1 -0
  720. package/dist/services/recovery/run-liveness-continuations.js +117 -0
  721. package/dist/services/recovery/run-liveness-continuations.js.map +1 -0
  722. package/dist/services/recovery/service.d.ts +195 -0
  723. package/dist/services/recovery/service.d.ts.map +1 -0
  724. package/dist/services/recovery/service.js +2210 -0
  725. package/dist/services/recovery/service.js.map +1 -0
  726. package/dist/services/recovery/successful-run-handoff.d.ts +87 -0
  727. package/dist/services/recovery/successful-run-handoff.d.ts.map +1 -0
  728. package/dist/services/recovery/successful-run-handoff.js +297 -0
  729. package/dist/services/recovery/successful-run-handoff.js.map +1 -0
  730. package/dist/services/recovery/successful-run-handoff.test.d.ts +2 -0
  731. package/dist/services/recovery/successful-run-handoff.test.d.ts.map +1 -0
  732. package/dist/services/recovery/successful-run-handoff.test.js +267 -0
  733. package/dist/services/recovery/successful-run-handoff.test.js.map +1 -0
  734. package/dist/services/routines.d.ts +166 -0
  735. package/dist/services/routines.d.ts.map +1 -0
  736. package/dist/services/routines.js +1937 -0
  737. package/dist/services/routines.js.map +1 -0
  738. package/dist/services/run-continuations.d.ts +3 -0
  739. package/dist/services/run-continuations.d.ts.map +1 -0
  740. package/dist/services/run-continuations.js +2 -0
  741. package/dist/services/run-continuations.js.map +1 -0
  742. package/dist/services/run-liveness.d.ts +46 -0
  743. package/dist/services/run-liveness.d.ts.map +1 -0
  744. package/dist/services/run-liveness.js +275 -0
  745. package/dist/services/run-liveness.js.map +1 -0
  746. package/dist/services/run-log-store.d.ts +34 -0
  747. package/dist/services/run-log-store.d.ts.map +1 -0
  748. package/dist/services/run-log-store.js +111 -0
  749. package/dist/services/run-log-store.js.map +1 -0
  750. package/dist/services/sandbox-provider-runtime.d.ts +132 -0
  751. package/dist/services/sandbox-provider-runtime.d.ts.map +1 -0
  752. package/dist/services/sandbox-provider-runtime.js +216 -0
  753. package/dist/services/sandbox-provider-runtime.js.map +1 -0
  754. package/dist/services/secrets.d.ts +515 -0
  755. package/dist/services/secrets.d.ts.map +1 -0
  756. package/dist/services/secrets.js +290 -0
  757. package/dist/services/secrets.js.map +1 -0
  758. package/dist/services/sidebar-badges.d.ts +14 -0
  759. package/dist/services/sidebar-badges.d.ts.map +1 -0
  760. package/dist/services/sidebar-badges.js +48 -0
  761. package/dist/services/sidebar-badges.js.map +1 -0
  762. package/dist/services/sidebar-preferences.d.ts +9 -0
  763. package/dist/services/sidebar-preferences.d.ts.map +1 -0
  764. package/dist/services/sidebar-preferences.js +82 -0
  765. package/dist/services/sidebar-preferences.js.map +1 -0
  766. package/dist/services/work-products.d.ts +14 -0
  767. package/dist/services/work-products.d.ts.map +1 -0
  768. package/dist/services/work-products.js +100 -0
  769. package/dist/services/work-products.js.map +1 -0
  770. package/dist/services/workspace-operation-log-store.d.ts +33 -0
  771. package/dist/services/workspace-operation-log-store.d.ts.map +1 -0
  772. package/dist/services/workspace-operation-log-store.js +110 -0
  773. package/dist/services/workspace-operation-log-store.js.map +1 -0
  774. package/dist/services/workspace-operations.d.ts +44 -0
  775. package/dist/services/workspace-operations.d.ts.map +1 -0
  776. package/dist/services/workspace-operations.js +211 -0
  777. package/dist/services/workspace-operations.js.map +1 -0
  778. package/dist/services/workspace-realization.d.ts +33 -0
  779. package/dist/services/workspace-realization.d.ts.map +1 -0
  780. package/dist/services/workspace-realization.js +221 -0
  781. package/dist/services/workspace-realization.js.map +1 -0
  782. package/dist/services/workspace-runtime-read-model.d.ts +92 -0
  783. package/dist/services/workspace-runtime-read-model.d.ts.map +1 -0
  784. package/dist/services/workspace-runtime-read-model.js +67 -0
  785. package/dist/services/workspace-runtime-read-model.js.map +1 -0
  786. package/dist/services/workspace-runtime.d.ts +238 -0
  787. package/dist/services/workspace-runtime.d.ts.map +1 -0
  788. package/dist/services/workspace-runtime.js +2388 -0
  789. package/dist/services/workspace-runtime.js.map +1 -0
  790. package/dist/startup-banner.d.ts +32 -0
  791. package/dist/startup-banner.d.ts.map +1 -0
  792. package/dist/startup-banner.js +118 -0
  793. package/dist/startup-banner.js.map +1 -0
  794. package/dist/storage/index.d.ts +6 -0
  795. package/dist/storage/index.d.ts.map +1 -0
  796. package/dist/storage/index.js +29 -0
  797. package/dist/storage/index.js.map +1 -0
  798. package/dist/storage/local-disk-provider.d.ts +3 -0
  799. package/dist/storage/local-disk-provider.d.ts.map +1 -0
  800. package/dist/storage/local-disk-provider.js +79 -0
  801. package/dist/storage/local-disk-provider.js.map +1 -0
  802. package/dist/storage/provider-registry.d.ts +4 -0
  803. package/dist/storage/provider-registry.d.ts.map +1 -0
  804. package/dist/storage/provider-registry.js +15 -0
  805. package/dist/storage/provider-registry.js.map +1 -0
  806. package/dist/storage/s3-provider.d.ts +11 -0
  807. package/dist/storage/s3-provider.d.ts.map +1 -0
  808. package/dist/storage/s3-provider.js +123 -0
  809. package/dist/storage/s3-provider.js.map +1 -0
  810. package/dist/storage/service.d.ts +3 -0
  811. package/dist/storage/service.d.ts.map +1 -0
  812. package/dist/storage/service.js +120 -0
  813. package/dist/storage/service.js.map +1 -0
  814. package/dist/storage/types.d.ts +55 -0
  815. package/dist/storage/types.d.ts.map +1 -0
  816. package/dist/storage/types.js +2 -0
  817. package/dist/storage/types.js.map +1 -0
  818. package/dist/telemetry.d.ts +6 -0
  819. package/dist/telemetry.d.ts.map +1 -0
  820. package/dist/telemetry.js +20 -0
  821. package/dist/telemetry.js.map +1 -0
  822. package/dist/ui-branding.d.ts +13 -0
  823. package/dist/ui-branding.d.ts.map +1 -0
  824. package/dist/ui-branding.js +187 -0
  825. package/dist/ui-branding.js.map +1 -0
  826. package/dist/version.d.ts +2 -0
  827. package/dist/version.d.ts.map +1 -0
  828. package/dist/version.js +5 -0
  829. package/dist/version.js.map +1 -0
  830. package/dist/vite-html-renderer.d.ts +18 -0
  831. package/dist/vite-html-renderer.d.ts.map +1 -0
  832. package/dist/vite-html-renderer.js +61 -0
  833. package/dist/vite-html-renderer.js.map +1 -0
  834. package/dist/worktree-config.d.ts +19 -0
  835. package/dist/worktree-config.d.ts.map +1 -0
  836. package/dist/worktree-config.js +368 -0
  837. package/dist/worktree-config.js.map +1 -0
  838. package/package.json +90 -0
  839. package/skills/diagnose-why-work-stopped/SKILL.md +161 -0
  840. package/skills/evermore/SKILL.md +366 -0
  841. package/skills/evermore/references/api-reference.md +899 -0
  842. package/skills/evermore/references/company-skills.md +193 -0
  843. package/skills/evermore/references/issue-workspaces.md +80 -0
  844. package/skills/evermore/references/routines.md +187 -0
  845. package/skills/evermore/references/workflows.md +141 -0
  846. package/skills/evermore-converting-plans-to-tasks/SKILL.md +42 -0
  847. package/skills/evermore-create-agent/SKILL.md +163 -0
  848. package/skills/evermore-create-agent/references/agent-instruction-templates.md +123 -0
  849. package/skills/evermore-create-agent/references/agents/coder.md +64 -0
  850. package/skills/evermore-create-agent/references/agents/qa.md +88 -0
  851. package/skills/evermore-create-agent/references/agents/securityengineer.md +135 -0
  852. package/skills/evermore-create-agent/references/agents/uxdesigner.md +115 -0
  853. package/skills/evermore-create-agent/references/api-reference.md +110 -0
  854. package/skills/evermore-create-agent/references/baseline-role-guide.md +168 -0
  855. package/skills/evermore-create-agent/references/draft-review-checklist.md +95 -0
  856. package/skills/evermore-create-plugin/SKILL.md +101 -0
  857. package/skills/evermore-dev/SKILL.md +267 -0
  858. package/skills/para-memory-files/SKILL.md +104 -0
  859. package/skills/para-memory-files/references/schemas.md +35 -0
  860. package/skills/terminal-bench-loop/SKILL.md +236 -0
  861. package/ui-dist/android-chrome-192x192.png +0 -0
  862. package/ui-dist/android-chrome-512x512.png +0 -0
  863. package/ui-dist/apple-touch-icon.png +0 -0
  864. package/ui-dist/assets/_basePickBy-Ds9oHp1_.js +1 -0
  865. package/ui-dist/assets/_baseUniq-CHYwQyJ_.js +1 -0
  866. package/ui-dist/assets/apl-B4CMkyY2.js +1 -0
  867. package/ui-dist/assets/arc-CiUKtBzk.js +1 -0
  868. package/ui-dist/assets/architectureDiagram-VXUJARFQ-CAW2b6pT.js +36 -0
  869. package/ui-dist/assets/asciiarmor-Df11BRmG.js +1 -0
  870. package/ui-dist/assets/asn1-EdZsLKOL.js +1 -0
  871. package/ui-dist/assets/asterisk-B-8jnY81.js +1 -0
  872. package/ui-dist/assets/blockDiagram-VD42YOAC-1Rk6YCcn.js +122 -0
  873. package/ui-dist/assets/brainfuck-C4LP7Hcl.js +1 -0
  874. package/ui-dist/assets/c4Diagram-YG6GDRKO-DF5RJtyZ.js +10 -0
  875. package/ui-dist/assets/channel-D7SqxhNi.js +1 -0
  876. package/ui-dist/assets/chunk-4BX2VUAB-BSZDJAxk.js +1 -0
  877. package/ui-dist/assets/chunk-55IACEB6-DgVOW-V3.js +1 -0
  878. package/ui-dist/assets/chunk-B4BG7PRW-C1sGAq6t.js +165 -0
  879. package/ui-dist/assets/chunk-DI55MBZ5-DZyfq3VK.js +220 -0
  880. package/ui-dist/assets/chunk-FMBD7UC4-D6K9nYXi.js +15 -0
  881. package/ui-dist/assets/chunk-QN33PNHL-BJ0Ni2l9.js +1 -0
  882. package/ui-dist/assets/chunk-QZHKN3VN-Cwjr0vxG.js +1 -0
  883. package/ui-dist/assets/chunk-TZMSLE5B-C2RGCkyV.js +1 -0
  884. package/ui-dist/assets/classDiagram-2ON5EDUG-Cx1PlXXb.js +1 -0
  885. package/ui-dist/assets/classDiagram-v2-WZHVMYZB-Cx1PlXXb.js +1 -0
  886. package/ui-dist/assets/clike-B9uivgTg.js +1 -0
  887. package/ui-dist/assets/clojure-BMjYHr_A.js +1 -0
  888. package/ui-dist/assets/clone-5ZE-SRxk.js +1 -0
  889. package/ui-dist/assets/cmake-BQqOBYOt.js +1 -0
  890. package/ui-dist/assets/cobol-CWcv1MsR.js +1 -0
  891. package/ui-dist/assets/coffeescript-S37ZYGWr.js +1 -0
  892. package/ui-dist/assets/commonlisp-DBKNyK5s.js +1 -0
  893. package/ui-dist/assets/cose-bilkent-S5V4N54A-CW0Mh3DC.js +1 -0
  894. package/ui-dist/assets/crystal-SjHAIU92.js +1 -0
  895. package/ui-dist/assets/css-BnMrqG3P.js +1 -0
  896. package/ui-dist/assets/cypher-C_CwsFkJ.js +1 -0
  897. package/ui-dist/assets/cytoscape.esm-jbPEKk2Y.js +321 -0
  898. package/ui-dist/assets/d-pRatUO7H.js +1 -0
  899. package/ui-dist/assets/dagre-6UL2VRFP-BXDbyGyw.js +4 -0
  900. package/ui-dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  901. package/ui-dist/assets/diagram-PSM6KHXK-BHW-b3P9.js +24 -0
  902. package/ui-dist/assets/diagram-QEK2KX5R-vjGsaMmX.js +43 -0
  903. package/ui-dist/assets/diagram-S2PKOQOG-BHVhRqBj.js +24 -0
  904. package/ui-dist/assets/diff-DbItnlRl.js +1 -0
  905. package/ui-dist/assets/dockerfile-BKs6k2Af.js +1 -0
  906. package/ui-dist/assets/dtd-DF_7sFjM.js +1 -0
  907. package/ui-dist/assets/dylan-DwRh75JA.js +1 -0
  908. package/ui-dist/assets/ebnf-CDyGwa7X.js +1 -0
  909. package/ui-dist/assets/ecl-Cabwm37j.js +1 -0
  910. package/ui-dist/assets/eiffel-CnydiIhH.js +1 -0
  911. package/ui-dist/assets/elm-vLlmbW-K.js +1 -0
  912. package/ui-dist/assets/erDiagram-Q2GNP2WA-DwsdEDB2.js +60 -0
  913. package/ui-dist/assets/erlang-BNw1qcRV.js +1 -0
  914. package/ui-dist/assets/factor-kuTfRLto.js +1 -0
  915. package/ui-dist/assets/fcl-Kvtd6kyn.js +1 -0
  916. package/ui-dist/assets/flowDiagram-NV44I4VS-Bv7drkZu.js +162 -0
  917. package/ui-dist/assets/forth-Ffai-XNe.js +1 -0
  918. package/ui-dist/assets/fortran-DYz_wnZ1.js +1 -0
  919. package/ui-dist/assets/ganttDiagram-JELNMOA3-DyYAGyjV.js +267 -0
  920. package/ui-dist/assets/gas-Bneqetm1.js +1 -0
  921. package/ui-dist/assets/gherkin-heZmZLOM.js +1 -0
  922. package/ui-dist/assets/gitGraphDiagram-V2S2FVAM-DmKJJ2X0.js +65 -0
  923. package/ui-dist/assets/graph-BI12oqz9.js +1 -0
  924. package/ui-dist/assets/groovy-D9Dt4D0W.js +1 -0
  925. package/ui-dist/assets/haskell-Cw1EW3IL.js +1 -0
  926. package/ui-dist/assets/haxe-H-WmDvRZ.js +1 -0
  927. package/ui-dist/assets/http-DBlCnlav.js +1 -0
  928. package/ui-dist/assets/idl-BEugSyMb.js +1 -0
  929. package/ui-dist/assets/index-B1YmgKZD.js +1 -0
  930. package/ui-dist/assets/index-B5EG1mbW.js +1 -0
  931. package/ui-dist/assets/index-B63DPkk7.js +2 -0
  932. package/ui-dist/assets/index-BAalUM2u.js +1 -0
  933. package/ui-dist/assets/index-BCyQqUEQ.js +1 -0
  934. package/ui-dist/assets/index-BSRsyahM.js +1 -0
  935. package/ui-dist/assets/index-B_Iu4zUd.js +1 -0
  936. package/ui-dist/assets/index-C-BdZUIH.js +1 -0
  937. package/ui-dist/assets/index-CGgUnSQj.js +6 -0
  938. package/ui-dist/assets/index-COTIEysQ.js +1 -0
  939. package/ui-dist/assets/index-ChSqseHR.js +1 -0
  940. package/ui-dist/assets/index-Ckf1hADU.js +534 -0
  941. package/ui-dist/assets/index-D4M1TSCO.js +3 -0
  942. package/ui-dist/assets/index-DCTk2CN-.js +7 -0
  943. package/ui-dist/assets/index-DI-wyUUr.js +1 -0
  944. package/ui-dist/assets/index-DNtLqZ-D.js +1 -0
  945. package/ui-dist/assets/index-DSRR_614.css +1 -0
  946. package/ui-dist/assets/index-DarmgkJv.js +1 -0
  947. package/ui-dist/assets/index-DdcxF71a.js +1 -0
  948. package/ui-dist/assets/index-DfwWaMga.js +1 -0
  949. package/ui-dist/assets/index-SzUviW57.js +13 -0
  950. package/ui-dist/assets/index-VjZhELb6.js +1 -0
  951. package/ui-dist/assets/index-mEcmy8wG.js +1 -0
  952. package/ui-dist/assets/index-q7ldlDv6.js +1 -0
  953. package/ui-dist/assets/infoDiagram-HS3SLOUP-CqAxMRbC.js +2 -0
  954. package/ui-dist/assets/init-Gi6I4Gst.js +1 -0
  955. package/ui-dist/assets/javascript-iXu5QeM3.js +1 -0
  956. package/ui-dist/assets/journeyDiagram-XKPGCS4Q-DUJiudtY.js +139 -0
  957. package/ui-dist/assets/julia-DuME0IfC.js +1 -0
  958. package/ui-dist/assets/kanban-definition-3W4ZIXB7-CiRgJ4X2.js +89 -0
  959. package/ui-dist/assets/katex-B95LWT_Q.js +261 -0
  960. package/ui-dist/assets/layout-DJ8V2pqt.js +1 -0
  961. package/ui-dist/assets/linear-BwnNUuyZ.js +1 -0
  962. package/ui-dist/assets/livescript-BwQOo05w.js +1 -0
  963. package/ui-dist/assets/lua-BgMRiT3U.js +1 -0
  964. package/ui-dist/assets/mathematica-DTrFuWx2.js +1 -0
  965. package/ui-dist/assets/mbox-CNhZ1qSd.js +1 -0
  966. package/ui-dist/assets/mermaid.core-DLCiCWj4.js +250 -0
  967. package/ui-dist/assets/mindmap-definition-VGOIOE7T-epIiGA01.js +68 -0
  968. package/ui-dist/assets/mirc-CjQqDB4T.js +1 -0
  969. package/ui-dist/assets/mllike-CXdrOF99.js +1 -0
  970. package/ui-dist/assets/modelica-Dc1JOy9r.js +1 -0
  971. package/ui-dist/assets/mscgen-BA5vi2Kp.js +1 -0
  972. package/ui-dist/assets/mumps-BT43cFF4.js +1 -0
  973. package/ui-dist/assets/nginx-DdIZxoE0.js +1 -0
  974. package/ui-dist/assets/nsis-LdVXkNf5.js +1 -0
  975. package/ui-dist/assets/ntriples-BfvgReVJ.js +1 -0
  976. package/ui-dist/assets/octave-Ck1zUtKM.js +1 -0
  977. package/ui-dist/assets/ordinal-Cboi1Yqb.js +1 -0
  978. package/ui-dist/assets/oz-BzwKVEFT.js +1 -0
  979. package/ui-dist/assets/pascal--L3eBynH.js +1 -0
  980. package/ui-dist/assets/perl-CdXCOZ3F.js +1 -0
  981. package/ui-dist/assets/pieDiagram-ADFJNKIX-CHx4pJ9Y.js +30 -0
  982. package/ui-dist/assets/pig-CevX1Tat.js +1 -0
  983. package/ui-dist/assets/powershell-CFHJl5sT.js +1 -0
  984. package/ui-dist/assets/properties-C78fOPTZ.js +1 -0
  985. package/ui-dist/assets/protobuf-ChK-085T.js +1 -0
  986. package/ui-dist/assets/pug-DeIclll2.js +1 -0
  987. package/ui-dist/assets/puppet-DMA9R1ak.js +1 -0
  988. package/ui-dist/assets/python-BuPzkPfP.js +1 -0
  989. package/ui-dist/assets/q-pXgVlZs6.js +1 -0
  990. package/ui-dist/assets/quadrantDiagram-AYHSOK5B-D0tXmGxE.js +7 -0
  991. package/ui-dist/assets/r-B6wPVr8A.js +1 -0
  992. package/ui-dist/assets/requirementDiagram-UZGBJVZJ-BgFc9Xc4.js +64 -0
  993. package/ui-dist/assets/rpm-CTu-6PCP.js +1 -0
  994. package/ui-dist/assets/ruby-B2Rjki9n.js +1 -0
  995. package/ui-dist/assets/sankeyDiagram-TZEHDZUN-DqntX5wV.js +10 -0
  996. package/ui-dist/assets/sas-B4kiWyti.js +1 -0
  997. package/ui-dist/assets/scheme-C41bIUwD.js +1 -0
  998. package/ui-dist/assets/sequenceDiagram-WL72ISMW-BfIJeGAT.js +145 -0
  999. package/ui-dist/assets/shell-CjFT_Tl9.js +1 -0
  1000. package/ui-dist/assets/sieve-C3Gn_uJK.js +1 -0
  1001. package/ui-dist/assets/simple-mode-GW_nhZxv.js +1 -0
  1002. package/ui-dist/assets/smalltalk-CnHTOXQT.js +1 -0
  1003. package/ui-dist/assets/solr-DehyRSwq.js +1 -0
  1004. package/ui-dist/assets/sparql-DkYu6x3z.js +1 -0
  1005. package/ui-dist/assets/spreadsheet-BCZA_wO0.js +1 -0
  1006. package/ui-dist/assets/sql-D0XecflT.js +1 -0
  1007. package/ui-dist/assets/stateDiagram-FKZM4ZOC-CBlsmcoW.js +1 -0
  1008. package/ui-dist/assets/stateDiagram-v2-4FDKWEC3-DJtH2tCS.js +1 -0
  1009. package/ui-dist/assets/stex-C3f8Ysf7.js +1 -0
  1010. package/ui-dist/assets/stylus-B533Al4x.js +1 -0
  1011. package/ui-dist/assets/swift-BzpIVaGY.js +1 -0
  1012. package/ui-dist/assets/tcl-DVfN8rqt.js +1 -0
  1013. package/ui-dist/assets/textile-CnDTJFAw.js +1 -0
  1014. package/ui-dist/assets/tiddlywiki-DO-Gjzrf.js +1 -0
  1015. package/ui-dist/assets/tiki-DGYXhP31.js +1 -0
  1016. package/ui-dist/assets/timeline-definition-IT6M3QCI-C2XyT4Lo.js +61 -0
  1017. package/ui-dist/assets/toml-Bm5Em-hy.js +1 -0
  1018. package/ui-dist/assets/treemap-GDKQZRPO-DvTrxs0e.js +154 -0
  1019. package/ui-dist/assets/troff-wAsdV37c.js +1 -0
  1020. package/ui-dist/assets/ttcn-CfJYG6tj.js +1 -0
  1021. package/ui-dist/assets/ttcn-cfg-B9xdYoR4.js +1 -0
  1022. package/ui-dist/assets/turtle-B1tBg_DP.js +1 -0
  1023. package/ui-dist/assets/vb-CmGdzxic.js +1 -0
  1024. package/ui-dist/assets/vbscript-BuJXcnF6.js +1 -0
  1025. package/ui-dist/assets/velocity-D8B20fx6.js +1 -0
  1026. package/ui-dist/assets/verilog-C6RDOZhf.js +1 -0
  1027. package/ui-dist/assets/vhdl-lSbBsy5d.js +1 -0
  1028. package/ui-dist/assets/webidl-ZXfAyPTL.js +1 -0
  1029. package/ui-dist/assets/xquery-DzFWVndE.js +1 -0
  1030. package/ui-dist/assets/xychartDiagram-PRI3JC2R-pTizTbG0.js +7 -0
  1031. package/ui-dist/assets/yacas-BJ4BC0dw.js +1 -0
  1032. package/ui-dist/assets/z80-Hz9HOZM7.js +1 -0
  1033. package/ui-dist/brands/opencode-logo-dark-square.svg +18 -0
  1034. package/ui-dist/brands/opencode-logo-light-square.svg +18 -0
  1035. package/ui-dist/favicon-16x16.png +0 -0
  1036. package/ui-dist/favicon-32x32.png +0 -0
  1037. package/ui-dist/favicon.ico +0 -0
  1038. package/ui-dist/favicon.svg +9 -0
  1039. package/ui-dist/index.html +49 -0
  1040. package/ui-dist/site.webmanifest +30 -0
  1041. package/ui-dist/sw.js +42 -0
  1042. package/ui-dist/worktree-favicon-16x16.png +0 -0
  1043. package/ui-dist/worktree-favicon-32x32.png +0 -0
  1044. package/ui-dist/worktree-favicon.ico +0 -0
  1045. package/ui-dist/worktree-favicon.svg +9 -0
@@ -0,0 +1,3199 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { and, asc, desc, eq, gt, inArray, isNull, like, lt, ne, notInArray, or, sql } from "drizzle-orm";
3
+ import { activityLog, agentWakeupRequests, agents, approvals, assets, companies, companyMemberships, documents, goals, heartbeatRuns, executionWorkspaces, issueApprovals, issueAttachments, issueInboxArchives, issueLabels, issueRelations, issueComments, issueDocuments, issueReadStates, issueThreadInteractions, issues, labels, projectWorkspaces, projects, } from "@evermore.work/db";
4
+ import { clampIssueRequestDepth, extractAgentMentionIds, extractProjectMentionIds, issueCommentAuthorTypeSchema, issueCommentMetadataSchema, issueCommentPresentationSchema, isUuidLike, normalizeIssueIdentifier as normalizeIssueReferenceIdentifier, } from "@evermore.work/shared";
5
+ import { conflict, notFound, unprocessable } from "../errors.js";
6
+ import { parseObject } from "../adapters/utils.js";
7
+ import { defaultIssueExecutionWorkspaceSettingsForProject, gateProjectExecutionWorkspacePolicy, issueExecutionWorkspaceModeForPersistedWorkspace, parseIssueExecutionWorkspaceSettings, parseProjectExecutionWorkspacePolicy, } from "./execution-workspace-policy.js";
8
+ import { mergeExecutionWorkspaceConfig } from "./execution-workspaces.js";
9
+ import { buildInitialIssueMonitorFields, normalizeIssueExecutionPolicy } from "./issue-execution-policy.js";
10
+ import { instanceSettingsService } from "./instance-settings.js";
11
+ import { redactCurrentUserText } from "../log-redaction.js";
12
+ import { resolveIssueGoalId, resolveNextIssueGoalId } from "./issue-goal-fallback.js";
13
+ import { getDefaultCompanyGoal } from "./goals.js";
14
+ import { isVerifiedIssueTreeControlInteractionWake, issueTreeControlService, } from "./issue-tree-control.js";
15
+ import { parseIssueGraphLivenessIncidentKey } from "./recovery/origins.js";
16
+ const ALL_ISSUE_STATUSES = ["backlog", "todo", "in_progress", "in_review", "blocked", "done", "cancelled"];
17
+ const MAX_ISSUE_COMMENT_PAGE_LIMIT = 500;
18
+ export const ISSUE_LIST_DEFAULT_LIMIT = 500;
19
+ export const ISSUE_LIST_MAX_LIMIT = 1000;
20
+ const ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE = 500;
21
+ export const MAX_CHILD_ISSUES_CREATED_BY_HELPER = 25;
22
+ const MAX_CHILD_COMPLETION_SUMMARIES = 20;
23
+ const CHILD_COMPLETION_SUMMARY_BODY_MAX_CHARS = 500;
24
+ function assertTransition(from, to) {
25
+ if (from === to)
26
+ return;
27
+ if (!ALL_ISSUE_STATUSES.includes(to)) {
28
+ throw conflict(`Unknown issue status: ${to}`);
29
+ }
30
+ }
31
+ function applyStatusSideEffects(status, patch) {
32
+ if (!status)
33
+ return patch;
34
+ if (status === "in_progress" && !patch.startedAt) {
35
+ patch.startedAt = new Date();
36
+ }
37
+ if (status === "done") {
38
+ patch.completedAt = new Date();
39
+ }
40
+ if (status === "cancelled") {
41
+ patch.cancelledAt = new Date();
42
+ }
43
+ return patch;
44
+ }
45
+ function readStringFromRecord(record, key) {
46
+ if (!record || typeof record !== "object")
47
+ return null;
48
+ const value = record[key];
49
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
50
+ }
51
+ function buildReusedExecutionWorkspaceConfigPatchFromIssueSettings(settings) {
52
+ return {
53
+ environmentId: settings?.environmentId ?? null,
54
+ provisionCommand: settings?.workspaceStrategy?.provisionCommand ?? null,
55
+ teardownCommand: settings?.workspaceStrategy?.teardownCommand ?? null,
56
+ workspaceRuntime: settings?.workspaceRuntime ?? null,
57
+ };
58
+ }
59
+ function sameRunLock(checkoutRunId, actorRunId) {
60
+ if (actorRunId)
61
+ return checkoutRunId === actorRunId;
62
+ return checkoutRunId == null;
63
+ }
64
+ const TERMINAL_HEARTBEAT_RUN_STATUSES = new Set(["succeeded", "failed", "cancelled", "timed_out"]);
65
+ const ISSUE_LIST_DESCRIPTION_MAX_CHARS = 1200;
66
+ const ISSUE_LIST_DESCRIPTION_MAX_BYTES = ISSUE_LIST_DESCRIPTION_MAX_CHARS * 4;
67
+ function escapeLikePattern(value) {
68
+ return value.replace(/[\\%_]/g, "\\$&");
69
+ }
70
+ export function clampIssueListLimit(limit) {
71
+ return Math.min(ISSUE_LIST_MAX_LIMIT, Math.max(1, Math.floor(limit)));
72
+ }
73
+ function chunkList(values, size) {
74
+ const chunks = [];
75
+ for (let index = 0; index < values.length; index += size) {
76
+ chunks.push(values.slice(index, index + size));
77
+ }
78
+ return chunks;
79
+ }
80
+ function truncateInlineSummary(value, maxChars = CHILD_COMPLETION_SUMMARY_BODY_MAX_CHARS) {
81
+ const normalized = value?.trim();
82
+ if (!normalized)
83
+ return null;
84
+ return normalized.length > maxChars ? `${normalized.slice(0, Math.max(0, maxChars - 15)).trimEnd()} [truncated]` : normalized;
85
+ }
86
+ function truncateByCodePoint(value, maxChars) {
87
+ if (value.length <= maxChars)
88
+ return value;
89
+ return Array.from(value).slice(0, maxChars).join("");
90
+ }
91
+ function decodeDatabaseTextPreview(value, maxChars) {
92
+ if (value == null)
93
+ return null;
94
+ return truncateByCodePoint(Buffer.from(value, "base64").toString("utf8"), maxChars);
95
+ }
96
+ function appendAcceptanceCriteriaToDescription(description, acceptanceCriteria) {
97
+ const criteria = (acceptanceCriteria ?? []).map((item) => item.trim()).filter(Boolean);
98
+ if (criteria.length === 0)
99
+ return description ?? null;
100
+ const base = description?.trim() ?? "";
101
+ const criteriaMarkdown = ["## Acceptance Criteria", "", ...criteria.map((item) => `- ${item}`)].join("\n");
102
+ return base ? `${base}\n\n${criteriaMarkdown}` : criteriaMarkdown;
103
+ }
104
+ function createIssueDependencyReadiness(issueId) {
105
+ return {
106
+ issueId,
107
+ blockerIssueIds: [],
108
+ unresolvedBlockerIssueIds: [],
109
+ unresolvedBlockerCount: 0,
110
+ allBlockersDone: true,
111
+ isDependencyReady: true,
112
+ };
113
+ }
114
+ async function listIssueDependencyReadinessMap(dbOrTx, companyId, issueIds) {
115
+ const uniqueIssueIds = [...new Set(issueIds.filter(Boolean))];
116
+ const readinessMap = new Map();
117
+ for (const issueId of uniqueIssueIds) {
118
+ readinessMap.set(issueId, createIssueDependencyReadiness(issueId));
119
+ }
120
+ if (uniqueIssueIds.length === 0)
121
+ return readinessMap;
122
+ const blockerRows = await dbOrTx
123
+ .select({
124
+ issueId: issueRelations.relatedIssueId,
125
+ blockerIssueId: issueRelations.issueId,
126
+ blockerStatus: issues.status,
127
+ })
128
+ .from(issueRelations)
129
+ .innerJoin(issues, eq(issueRelations.issueId, issues.id))
130
+ .where(and(eq(issueRelations.companyId, companyId), eq(issueRelations.type, "blocks"), inArray(issueRelations.relatedIssueId, uniqueIssueIds)));
131
+ for (const row of blockerRows) {
132
+ const current = readinessMap.get(row.issueId) ?? createIssueDependencyReadiness(row.issueId);
133
+ current.blockerIssueIds.push(row.blockerIssueId);
134
+ // Only done blockers resolve dependents; cancelled blockers stay unresolved
135
+ // until an operator removes or replaces the blocker relationship explicitly.
136
+ if (row.blockerStatus !== "done") {
137
+ current.unresolvedBlockerIssueIds.push(row.blockerIssueId);
138
+ current.unresolvedBlockerCount += 1;
139
+ current.allBlockersDone = false;
140
+ current.isDependencyReady = false;
141
+ }
142
+ readinessMap.set(row.issueId, current);
143
+ }
144
+ return readinessMap;
145
+ }
146
+ async function listUnresolvedBlockerIssueIds(dbOrTx, companyId, blockerIssueIds) {
147
+ const uniqueBlockerIssueIds = [...new Set(blockerIssueIds.filter(Boolean))];
148
+ if (uniqueBlockerIssueIds.length === 0)
149
+ return [];
150
+ return dbOrTx
151
+ .select({ id: issues.id })
152
+ .from(issues)
153
+ .where(and(eq(issues.companyId, companyId), inArray(issues.id, uniqueBlockerIssueIds),
154
+ // Cancelled blockers intentionally remain unresolved until the relation changes.
155
+ ne(issues.status, "done")))
156
+ .then((rows) => rows.map((row) => row.id));
157
+ }
158
+ async function getProjectDefaultGoalId(db, companyId, projectId) {
159
+ if (!projectId)
160
+ return null;
161
+ const row = await db
162
+ .select({ goalId: projects.goalId })
163
+ .from(projects)
164
+ .where(and(eq(projects.id, projectId), eq(projects.companyId, companyId)))
165
+ .then((rows) => rows[0] ?? null);
166
+ return row?.goalId ?? null;
167
+ }
168
+ async function getWorkspaceInheritanceIssue(db, companyId, issueId) {
169
+ const issue = await db
170
+ .select({
171
+ id: issues.id,
172
+ projectId: issues.projectId,
173
+ projectWorkspaceId: issues.projectWorkspaceId,
174
+ executionWorkspaceId: issues.executionWorkspaceId,
175
+ executionWorkspaceSettings: issues.executionWorkspaceSettings,
176
+ })
177
+ .from(issues)
178
+ .where(and(eq(issues.id, issueId), eq(issues.companyId, companyId)))
179
+ .then((rows) => rows[0] ?? null);
180
+ if (!issue) {
181
+ throw notFound("Workspace inheritance issue not found");
182
+ }
183
+ return issue;
184
+ }
185
+ function touchedByUserCondition(companyId, userId) {
186
+ return sql `
187
+ (
188
+ ${issues.createdByUserId} = ${userId}
189
+ OR ${issues.assigneeUserId} = ${userId}
190
+ OR EXISTS (
191
+ SELECT 1
192
+ FROM ${issueReadStates}
193
+ WHERE ${issueReadStates.issueId} = ${issues.id}
194
+ AND ${issueReadStates.companyId} = ${companyId}
195
+ AND ${issueReadStates.userId} = ${userId}
196
+ )
197
+ OR EXISTS (
198
+ SELECT 1
199
+ FROM ${issueComments}
200
+ WHERE ${issueComments.issueId} = ${issues.id}
201
+ AND ${issueComments.companyId} = ${companyId}
202
+ AND ${issueComments.authorUserId} = ${userId}
203
+ )
204
+ )
205
+ `;
206
+ }
207
+ function participatedByAgentCondition(companyId, agentId) {
208
+ return sql `
209
+ (
210
+ ${issues.createdByAgentId} = ${agentId}
211
+ OR ${issues.assigneeAgentId} = ${agentId}
212
+ OR EXISTS (
213
+ SELECT 1
214
+ FROM ${issueComments}
215
+ WHERE ${issueComments.issueId} = ${issues.id}
216
+ AND ${issueComments.companyId} = ${companyId}
217
+ AND ${issueComments.authorAgentId} = ${agentId}
218
+ )
219
+ OR EXISTS (
220
+ SELECT 1
221
+ FROM ${activityLog}
222
+ WHERE ${activityLog.companyId} = ${companyId}
223
+ AND ${activityLog.entityType} = 'issue'
224
+ AND ${activityLog.entityId} = ${issues.id}::text
225
+ AND ${activityLog.agentId} = ${agentId}
226
+ )
227
+ )
228
+ `;
229
+ }
230
+ function myLastCommentAtExpr(companyId, userId) {
231
+ return sql `
232
+ (
233
+ SELECT MAX(${issueComments.createdAt})
234
+ FROM ${issueComments}
235
+ WHERE ${issueComments.issueId} = ${issues.id}
236
+ AND ${issueComments.companyId} = ${companyId}
237
+ AND ${issueComments.authorUserId} = ${userId}
238
+ )
239
+ `;
240
+ }
241
+ function myLastReadAtExpr(companyId, userId) {
242
+ return sql `
243
+ (
244
+ SELECT MAX(${issueReadStates.lastReadAt})
245
+ FROM ${issueReadStates}
246
+ WHERE ${issueReadStates.issueId} = ${issues.id}
247
+ AND ${issueReadStates.companyId} = ${companyId}
248
+ AND ${issueReadStates.userId} = ${userId}
249
+ )
250
+ `;
251
+ }
252
+ function myLastTouchAtExpr(companyId, userId) {
253
+ const myLastCommentAt = myLastCommentAtExpr(companyId, userId);
254
+ const myLastReadAt = myLastReadAtExpr(companyId, userId);
255
+ return sql `
256
+ GREATEST(
257
+ COALESCE(${myLastCommentAt}, to_timestamp(0)),
258
+ COALESCE(${myLastReadAt}, to_timestamp(0)),
259
+ COALESCE(CASE WHEN ${issues.createdByUserId} = ${userId} THEN ${issues.createdAt} ELSE NULL END, to_timestamp(0)),
260
+ COALESCE(CASE WHEN ${issues.assigneeUserId} = ${userId} THEN ${issues.updatedAt} ELSE NULL END, to_timestamp(0))
261
+ )
262
+ `;
263
+ }
264
+ function lastExternalCommentAtExpr(companyId, userId) {
265
+ return sql `
266
+ (
267
+ SELECT MAX(${issueComments.createdAt})
268
+ FROM ${issueComments}
269
+ WHERE ${issueComments.issueId} = ${issues.id}
270
+ AND ${issueComments.companyId} = ${companyId}
271
+ AND (
272
+ ${issueComments.authorUserId} IS NULL
273
+ OR ${issueComments.authorUserId} <> ${userId}
274
+ )
275
+ )
276
+ `;
277
+ }
278
+ function issueLastActivityAtExpr(companyId, userId) {
279
+ const lastExternalCommentAt = lastExternalCommentAtExpr(companyId, userId);
280
+ const myLastTouchAt = myLastTouchAtExpr(companyId, userId);
281
+ return sql `
282
+ GREATEST(
283
+ COALESCE(${lastExternalCommentAt}, to_timestamp(0)),
284
+ CASE
285
+ WHEN ${issues.updatedAt} > COALESCE(${myLastTouchAt}, to_timestamp(0))
286
+ THEN ${issues.updatedAt}
287
+ ELSE to_timestamp(0)
288
+ END
289
+ )
290
+ `;
291
+ }
292
+ const ISSUE_LOCAL_INBOX_ACTIVITY_ACTIONS = [
293
+ "issue.read_marked",
294
+ "issue.read_unmarked",
295
+ "issue.inbox_archived",
296
+ "issue.inbox_unarchived",
297
+ ];
298
+ function issueLatestCommentAtExpr(companyId) {
299
+ return sql `
300
+ (
301
+ SELECT MAX(${issueComments.createdAt})
302
+ FROM ${issueComments}
303
+ WHERE ${issueComments.issueId} = ${issues.id}
304
+ AND ${issueComments.companyId} = ${companyId}
305
+ )
306
+ `;
307
+ }
308
+ function issueLatestLogAtExpr(companyId) {
309
+ return sql `
310
+ (
311
+ SELECT MAX(${activityLog.createdAt})
312
+ FROM ${activityLog}
313
+ WHERE ${activityLog.companyId} = ${companyId}
314
+ AND ${activityLog.entityType} = 'issue'
315
+ AND ${activityLog.entityId} = ${issues.id}::text
316
+ AND ${activityLog.action} NOT IN (${sql.join(ISSUE_LOCAL_INBOX_ACTIVITY_ACTIONS.map((action) => sql `${action}`), sql `, `)})
317
+ )
318
+ `;
319
+ }
320
+ function issueCanonicalLastActivityAtExpr(companyId) {
321
+ const latestCommentAt = issueLatestCommentAtExpr(companyId);
322
+ const latestLogAt = issueLatestLogAtExpr(companyId);
323
+ return sql `
324
+ GREATEST(
325
+ ${issues.updatedAt},
326
+ COALESCE(${latestCommentAt}, to_timestamp(0)),
327
+ COALESCE(${latestLogAt}, to_timestamp(0))
328
+ )
329
+ `;
330
+ }
331
+ function unreadForUserCondition(companyId, userId) {
332
+ const touchedCondition = touchedByUserCondition(companyId, userId);
333
+ const myLastTouchAt = myLastTouchAtExpr(companyId, userId);
334
+ return sql `
335
+ (
336
+ ${touchedCondition}
337
+ AND EXISTS (
338
+ SELECT 1
339
+ FROM ${issueComments}
340
+ WHERE ${issueComments.issueId} = ${issues.id}
341
+ AND ${issueComments.companyId} = ${companyId}
342
+ AND (
343
+ ${issueComments.authorUserId} IS NULL
344
+ OR ${issueComments.authorUserId} <> ${userId}
345
+ )
346
+ AND ${issueComments.createdAt} > ${myLastTouchAt}
347
+ )
348
+ )
349
+ `;
350
+ }
351
+ function inboxVisibleForUserCondition(companyId, userId) {
352
+ const issueLastActivityAt = issueLastActivityAtExpr(companyId, userId);
353
+ return sql `
354
+ NOT EXISTS (
355
+ SELECT 1
356
+ FROM ${issueInboxArchives}
357
+ WHERE ${issueInboxArchives.issueId} = ${issues.id}
358
+ AND ${issueInboxArchives.companyId} = ${companyId}
359
+ AND ${issueInboxArchives.userId} = ${userId}
360
+ AND ${issueInboxArchives.archivedAt} >= ${issueLastActivityAt}
361
+ )
362
+ `;
363
+ }
364
+ function nonPluginOperationIssueCondition() {
365
+ return sql `NOT (${issues.originKind} LIKE 'plugin:%:operation' OR ${issues.originKind} LIKE 'plugin:%:operation:%')`;
366
+ }
367
+ function shouldIncludePluginOperationIssues(filters) {
368
+ return Boolean(filters?.includePluginOperations ||
369
+ filters?.originKind ||
370
+ filters?.originId ||
371
+ filters?.projectId);
372
+ }
373
+ /** Named entities commonly emitted in saved issue bodies; unknown `&name;` sequences are left unchanged. */
374
+ const WELL_KNOWN_NAMED_HTML_ENTITIES = {
375
+ amp: "&",
376
+ apos: "'",
377
+ copy: "\u00A9",
378
+ gt: ">",
379
+ lt: "<",
380
+ nbsp: "\u00A0",
381
+ quot: '"',
382
+ ensp: "\u2002",
383
+ emsp: "\u2003",
384
+ thinsp: "\u2009",
385
+ };
386
+ function decodeNumericHtmlEntity(digits, radix) {
387
+ const n = Number.parseInt(digits, radix);
388
+ if (Number.isNaN(n) || n < 0 || n > 0x10ffff)
389
+ return null;
390
+ try {
391
+ return String.fromCodePoint(n);
392
+ }
393
+ catch {
394
+ return null;
395
+ }
396
+ }
397
+ /** Decodes HTML character references in a raw @mention capture so UI-encoded bodies match agent names. */
398
+ export function normalizeAgentMentionToken(raw) {
399
+ let s = raw.replace(/&#x([0-9a-fA-F]+);/gi, (full, hex) => decodeNumericHtmlEntity(hex, 16) ?? full);
400
+ s = s.replace(/&#([0-9]+);/g, (full, dec) => decodeNumericHtmlEntity(dec, 10) ?? full);
401
+ s = s.replace(/&([a-z][a-z0-9]*);/gi, (full, name) => {
402
+ const decoded = WELL_KNOWN_NAMED_HTML_ENTITIES[name.toLowerCase()];
403
+ return decoded !== undefined ? decoded : full;
404
+ });
405
+ return s.trim();
406
+ }
407
+ export function deriveIssueUserContext(issue, userId, stats) {
408
+ const normalizeDate = (value) => {
409
+ if (!value)
410
+ return null;
411
+ if (value instanceof Date)
412
+ return Number.isNaN(value.getTime()) ? null : value;
413
+ const parsed = new Date(value);
414
+ return Number.isNaN(parsed.getTime()) ? null : parsed;
415
+ };
416
+ const myLastCommentAt = normalizeDate(stats?.myLastCommentAt);
417
+ const myLastReadAt = normalizeDate(stats?.myLastReadAt);
418
+ const createdTouchAt = issue.createdByUserId === userId ? normalizeDate(issue.createdAt) : null;
419
+ const assignedTouchAt = issue.assigneeUserId === userId ? normalizeDate(issue.updatedAt) : null;
420
+ const myLastTouchAt = [myLastCommentAt, myLastReadAt, createdTouchAt, assignedTouchAt]
421
+ .filter((value) => value instanceof Date)
422
+ .sort((a, b) => b.getTime() - a.getTime())[0] ?? null;
423
+ const lastExternalCommentAt = normalizeDate(stats?.lastExternalCommentAt);
424
+ const isUnreadForMe = Boolean(myLastTouchAt &&
425
+ lastExternalCommentAt &&
426
+ lastExternalCommentAt.getTime() > myLastTouchAt.getTime());
427
+ return {
428
+ myLastTouchAt,
429
+ lastExternalCommentAt,
430
+ isUnreadForMe,
431
+ };
432
+ }
433
+ function latestIssueActivityAt(...values) {
434
+ const normalized = values
435
+ .map((value) => {
436
+ if (!value)
437
+ return null;
438
+ if (value instanceof Date)
439
+ return Number.isNaN(value.getTime()) ? null : value;
440
+ const parsed = new Date(value);
441
+ return Number.isNaN(parsed.getTime()) ? null : parsed;
442
+ })
443
+ .filter((value) => value instanceof Date)
444
+ .sort((a, b) => b.getTime() - a.getTime());
445
+ return normalized[0] ?? null;
446
+ }
447
+ async function labelMapForIssues(dbOrTx, issueIds) {
448
+ const map = new Map();
449
+ if (issueIds.length === 0)
450
+ return map;
451
+ for (const issueIdChunk of chunkList(issueIds, ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {
452
+ const rows = await dbOrTx
453
+ .select({
454
+ issueId: issueLabels.issueId,
455
+ label: labels,
456
+ })
457
+ .from(issueLabels)
458
+ .innerJoin(labels, eq(issueLabels.labelId, labels.id))
459
+ .where(inArray(issueLabels.issueId, issueIdChunk))
460
+ .orderBy(asc(labels.name), asc(labels.id));
461
+ for (const row of rows) {
462
+ const existing = map.get(row.issueId);
463
+ if (existing)
464
+ existing.push(row.label);
465
+ else
466
+ map.set(row.issueId, [row.label]);
467
+ }
468
+ }
469
+ return map;
470
+ }
471
+ async function withIssueLabels(dbOrTx, rows) {
472
+ if (rows.length === 0)
473
+ return [];
474
+ const labelsByIssueId = await labelMapForIssues(dbOrTx, rows.map((row) => row.id));
475
+ return rows.map((row) => {
476
+ const issueLabels = labelsByIssueId.get(row.id) ?? [];
477
+ return {
478
+ ...row,
479
+ labels: issueLabels,
480
+ labelIds: issueLabels.map((label) => label.id),
481
+ };
482
+ });
483
+ }
484
+ const ACTIVE_RUN_STATUSES = ["queued", "running"];
485
+ const BLOCKER_ATTENTION_ACTIVE_RUN_STATUSES = ["queued", "running"];
486
+ const BLOCKER_ATTENTION_ACTIVE_WAKE_STATUSES = ["queued", "deferred_issue_execution"];
487
+ const BLOCKER_ATTENTION_PENDING_INTERACTION_STATUSES = ["pending"];
488
+ const BLOCKER_ATTENTION_PENDING_APPROVAL_STATUSES = ["pending", "revision_requested"];
489
+ const BLOCKER_ATTENTION_OPEN_RECOVERY_ORIGIN_KIND = "harness_liveness_escalation";
490
+ const PRODUCTIVITY_REVIEW_ORIGIN_KIND = "issue_productivity_review";
491
+ const PRODUCTIVITY_REVIEW_TERMINAL_STATUSES = ["done", "cancelled"];
492
+ const PRODUCTIVITY_REVIEW_ACTIVITY_ACTIONS = [
493
+ "issue.productivity_review_created",
494
+ "issue.productivity_review_updated",
495
+ ];
496
+ const PRODUCTIVITY_REVIEW_TRIGGERS = [
497
+ "no_comment_streak",
498
+ "long_active_duration",
499
+ "high_churn",
500
+ ];
501
+ const BLOCKER_ATTENTION_OPEN_RECOVERY_TERMINAL_STATUSES = ["done", "cancelled"];
502
+ const BLOCKER_ATTENTION_MAX_DEPTH = 8;
503
+ const BLOCKER_ATTENTION_MAX_NODES = 2000;
504
+ const BLOCKER_ATTENTION_INVOKABLE_AGENT_STATUSES = new Set(["active", "idle", "running", "error"]);
505
+ async function activeRunMapForIssues(dbOrTx, issueRows) {
506
+ const map = new Map();
507
+ const runIds = issueRows
508
+ .map((row) => row.executionRunId)
509
+ .filter((id) => id != null);
510
+ if (runIds.length === 0)
511
+ return map;
512
+ for (const runIdChunk of chunkList([...new Set(runIds)], ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {
513
+ const rows = await dbOrTx
514
+ .select({
515
+ id: heartbeatRuns.id,
516
+ status: heartbeatRuns.status,
517
+ agentId: heartbeatRuns.agentId,
518
+ invocationSource: heartbeatRuns.invocationSource,
519
+ triggerDetail: heartbeatRuns.triggerDetail,
520
+ startedAt: heartbeatRuns.startedAt,
521
+ finishedAt: heartbeatRuns.finishedAt,
522
+ createdAt: heartbeatRuns.createdAt,
523
+ })
524
+ .from(heartbeatRuns)
525
+ .where(and(inArray(heartbeatRuns.id, runIdChunk), inArray(heartbeatRuns.status, ACTIVE_RUN_STATUSES)));
526
+ for (const row of rows) {
527
+ map.set(row.id, row);
528
+ }
529
+ }
530
+ return map;
531
+ }
532
+ function createIssueBlockerAttention(input = {}) {
533
+ return {
534
+ state: input.state ?? "none",
535
+ reason: input.reason ?? null,
536
+ unresolvedBlockerCount: input.unresolvedBlockerCount ?? 0,
537
+ coveredBlockerCount: input.coveredBlockerCount ?? 0,
538
+ stalledBlockerCount: input.stalledBlockerCount ?? 0,
539
+ attentionBlockerCount: input.attentionBlockerCount ?? 0,
540
+ sampleBlockerIdentifier: input.sampleBlockerIdentifier ?? null,
541
+ sampleStalledBlockerIdentifier: input.sampleStalledBlockerIdentifier ?? null,
542
+ };
543
+ }
544
+ function blockerSampleIdentifier(node) {
545
+ return node?.identifier ?? node?.id ?? null;
546
+ }
547
+ function appendBlockerAttentionEdges(edgesByIssueId, rows) {
548
+ for (const row of rows) {
549
+ const existing = edgesByIssueId.get(row.issueId) ?? [];
550
+ if (!existing.some((edge) => edge.blockerIssueId === row.blockerIssueId)) {
551
+ existing.push(row);
552
+ edgesByIssueId.set(row.issueId, existing);
553
+ }
554
+ }
555
+ }
556
+ function summarizeIssueRelationRow(row) {
557
+ return {
558
+ id: row.relatedId,
559
+ identifier: row.identifier,
560
+ title: row.title,
561
+ status: row.status,
562
+ priority: row.priority,
563
+ assigneeAgentId: row.assigneeAgentId,
564
+ assigneeUserId: row.assigneeUserId,
565
+ };
566
+ }
567
+ async function terminalExplicitBlockersByRoot(companyId, roots, dbOrTx) {
568
+ const rootIds = [...new Set(roots.map((root) => root.id))];
569
+ const terminalByRoot = new Map();
570
+ if (rootIds.length === 0)
571
+ return terminalByRoot;
572
+ const nodesById = new Map();
573
+ const edgesByIssueId = new Map();
574
+ for (const root of roots)
575
+ nodesById.set(root.id, root);
576
+ let frontier = rootIds;
577
+ for (let depth = 0; frontier.length > 0 && depth < BLOCKER_ATTENTION_MAX_DEPTH; depth += 1) {
578
+ const nextFrontier = new Set();
579
+ for (const chunk of chunkList([...new Set(frontier)], ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {
580
+ const rows = await dbOrTx
581
+ .select({
582
+ currentIssueId: issueRelations.relatedIssueId,
583
+ relatedId: issues.id,
584
+ identifier: issues.identifier,
585
+ title: issues.title,
586
+ status: issues.status,
587
+ priority: issues.priority,
588
+ assigneeAgentId: issues.assigneeAgentId,
589
+ assigneeUserId: issues.assigneeUserId,
590
+ })
591
+ .from(issueRelations)
592
+ .innerJoin(issues, eq(issueRelations.issueId, issues.id))
593
+ .where(and(eq(issueRelations.companyId, companyId), eq(issueRelations.type, "blocks"), inArray(issueRelations.relatedIssueId, chunk), eq(issues.companyId, companyId), ne(issues.status, "done")));
594
+ for (const row of rows) {
595
+ const existingEdges = edgesByIssueId.get(row.currentIssueId) ?? [];
596
+ if (!existingEdges.includes(row.relatedId)) {
597
+ existingEdges.push(row.relatedId);
598
+ edgesByIssueId.set(row.currentIssueId, existingEdges);
599
+ }
600
+ if (!nodesById.has(row.relatedId)) {
601
+ nodesById.set(row.relatedId, summarizeIssueRelationRow(row));
602
+ nextFrontier.add(row.relatedId);
603
+ }
604
+ }
605
+ }
606
+ if (nodesById.size > BLOCKER_ATTENTION_MAX_NODES)
607
+ break;
608
+ frontier = [...nextFrontier];
609
+ }
610
+ const collectTerminal = (issueId, seen) => {
611
+ if (seen.has(issueId))
612
+ return [];
613
+ const node = nodesById.get(issueId);
614
+ if (!node || node.status === "done")
615
+ return [];
616
+ const nextSeen = new Set(seen);
617
+ nextSeen.add(issueId);
618
+ const downstreamIds = edgesByIssueId.get(issueId) ?? [];
619
+ if (downstreamIds.length === 0)
620
+ return [node];
621
+ return downstreamIds.flatMap((downstreamId) => collectTerminal(downstreamId, nextSeen));
622
+ };
623
+ for (const rootId of rootIds) {
624
+ const deduped = new Map();
625
+ for (const blocker of collectTerminal(rootId, new Set())) {
626
+ if (blocker.id !== rootId)
627
+ deduped.set(blocker.id, blocker);
628
+ }
629
+ if (deduped.size > 0) {
630
+ terminalByRoot.set(rootId, [...deduped.values()].sort((a, b) => a.title.localeCompare(b.title)));
631
+ }
632
+ }
633
+ return terminalByRoot;
634
+ }
635
+ function readProductivityReviewTrigger(value) {
636
+ if (typeof value !== "string")
637
+ return null;
638
+ return PRODUCTIVITY_REVIEW_TRIGGERS.includes(value)
639
+ ? value
640
+ : null;
641
+ }
642
+ function readProductivityReviewStreak(value) {
643
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0)
644
+ return null;
645
+ return Math.floor(value);
646
+ }
647
+ async function listIssueProductivityReviewMap(dbOrTx, companyId, sourceIssueIds) {
648
+ const map = new Map();
649
+ if (sourceIssueIds.length === 0)
650
+ return map;
651
+ const reviewRows = [];
652
+ for (const chunk of chunkList([...new Set(sourceIssueIds)], ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {
653
+ const rows = await dbOrTx
654
+ .select({
655
+ sourceIssueId: issues.originId,
656
+ reviewIssueId: issues.id,
657
+ reviewIdentifier: issues.identifier,
658
+ status: issues.status,
659
+ priority: issues.priority,
660
+ createdAt: issues.createdAt,
661
+ updatedAt: issues.updatedAt,
662
+ })
663
+ .from(issues)
664
+ .where(and(eq(issues.companyId, companyId), eq(issues.originKind, PRODUCTIVITY_REVIEW_ORIGIN_KIND), inArray(issues.originId, chunk), isNull(issues.hiddenAt), notInArray(issues.status, PRODUCTIVITY_REVIEW_TERMINAL_STATUSES)))
665
+ .orderBy(desc(issues.createdAt), desc(issues.id));
666
+ reviewRows.push(...rows);
667
+ }
668
+ if (reviewRows.length === 0)
669
+ return map;
670
+ const reviewIssueIds = reviewRows.map((row) => row.reviewIssueId);
671
+ const triggerByReviewIssueId = new Map();
672
+ for (const chunk of chunkList(reviewIssueIds, ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {
673
+ const detailRows = await dbOrTx
674
+ .select({
675
+ entityId: activityLog.entityId,
676
+ details: activityLog.details,
677
+ createdAt: activityLog.createdAt,
678
+ })
679
+ .from(activityLog)
680
+ .where(and(eq(activityLog.companyId, companyId), eq(activityLog.entityType, "issue"), inArray(activityLog.entityId, chunk), inArray(activityLog.action, PRODUCTIVITY_REVIEW_ACTIVITY_ACTIONS)))
681
+ .orderBy(desc(activityLog.createdAt));
682
+ for (const row of detailRows) {
683
+ if (triggerByReviewIssueId.has(row.entityId))
684
+ continue;
685
+ triggerByReviewIssueId.set(row.entityId, {
686
+ trigger: readProductivityReviewTrigger(row.details?.trigger),
687
+ noCommentStreak: readProductivityReviewStreak(row.details?.noCommentStreak),
688
+ });
689
+ }
690
+ }
691
+ for (const row of reviewRows) {
692
+ if (!row.sourceIssueId)
693
+ continue;
694
+ if (map.has(row.sourceIssueId))
695
+ continue;
696
+ const detail = triggerByReviewIssueId.get(row.reviewIssueId);
697
+ map.set(row.sourceIssueId, {
698
+ reviewIssueId: row.reviewIssueId,
699
+ reviewIdentifier: row.reviewIdentifier,
700
+ status: row.status,
701
+ priority: row.priority,
702
+ trigger: detail?.trigger ?? null,
703
+ noCommentStreak: detail?.noCommentStreak ?? null,
704
+ createdAt: row.createdAt,
705
+ updatedAt: row.updatedAt,
706
+ });
707
+ }
708
+ return map;
709
+ }
710
+ async function listIssueBlockerAttentionMap(dbOrTx, companyId, issueRows) {
711
+ const roots = issueRows.filter((row) => row.companyId === companyId && row.status === "blocked");
712
+ const attentionMap = new Map();
713
+ for (const row of issueRows) {
714
+ if (row.status !== "blocked") {
715
+ attentionMap.set(row.id, createIssueBlockerAttention());
716
+ }
717
+ }
718
+ if (roots.length === 0)
719
+ return attentionMap;
720
+ const nodesById = new Map();
721
+ const edgesByIssueId = new Map();
722
+ for (const root of roots)
723
+ nodesById.set(root.id, { ...root });
724
+ let frontier = roots.map((root) => root.id);
725
+ let truncated = false;
726
+ for (let depth = 0; frontier.length > 0 && depth < BLOCKER_ATTENTION_MAX_DEPTH; depth += 1) {
727
+ const nextFrontier = new Set();
728
+ for (const chunk of chunkList([...new Set(frontier)], ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {
729
+ const explicitBlockerRowsPromise = dbOrTx
730
+ .select({
731
+ issueId: issueRelations.relatedIssueId,
732
+ blockerIssueId: issues.id,
733
+ id: issues.id,
734
+ companyId: issues.companyId,
735
+ parentId: issues.parentId,
736
+ identifier: issues.identifier,
737
+ title: issues.title,
738
+ status: issues.status,
739
+ executionRunId: issues.executionRunId,
740
+ assigneeAgentId: issues.assigneeAgentId,
741
+ assigneeUserId: issues.assigneeUserId,
742
+ })
743
+ .from(issueRelations)
744
+ .innerJoin(issues, eq(issueRelations.issueId, issues.id))
745
+ .where(and(eq(issueRelations.companyId, companyId), eq(issueRelations.type, "blocks"), inArray(issueRelations.relatedIssueId, chunk), eq(issues.companyId, companyId), ne(issues.status, "done")));
746
+ const childRowsPromise = dbOrTx
747
+ .select({
748
+ issueId: issues.parentId,
749
+ blockerIssueId: issues.id,
750
+ id: issues.id,
751
+ companyId: issues.companyId,
752
+ parentId: issues.parentId,
753
+ identifier: issues.identifier,
754
+ title: issues.title,
755
+ status: issues.status,
756
+ executionRunId: issues.executionRunId,
757
+ assigneeAgentId: issues.assigneeAgentId,
758
+ assigneeUserId: issues.assigneeUserId,
759
+ })
760
+ .from(issues)
761
+ .where(and(eq(issues.companyId, companyId), inArray(issues.parentId, chunk), ne(issues.status, "done")));
762
+ const [explicitBlockerRows, childRows] = await Promise.all([
763
+ explicitBlockerRowsPromise,
764
+ childRowsPromise,
765
+ ]);
766
+ appendBlockerAttentionEdges(edgesByIssueId, [
767
+ ...explicitBlockerRows
768
+ .filter((row) => row.issueId !== null)
769
+ .map((row) => ({ issueId: row.issueId, blockerIssueId: row.blockerIssueId })),
770
+ ...childRows
771
+ .filter((row) => row.issueId !== null)
772
+ .map((row) => ({ issueId: row.issueId, blockerIssueId: row.blockerIssueId })),
773
+ ]);
774
+ for (const row of [...explicitBlockerRows, ...childRows]) {
775
+ if (!row.issueId || nodesById.has(row.blockerIssueId))
776
+ continue;
777
+ nodesById.set(row.blockerIssueId, {
778
+ id: row.blockerIssueId,
779
+ companyId: row.companyId,
780
+ parentId: row.parentId,
781
+ identifier: row.identifier,
782
+ title: row.title,
783
+ status: row.status,
784
+ executionRunId: row.executionRunId,
785
+ assigneeAgentId: row.assigneeAgentId,
786
+ assigneeUserId: row.assigneeUserId,
787
+ });
788
+ nextFrontier.add(row.blockerIssueId);
789
+ }
790
+ }
791
+ if (nodesById.size > BLOCKER_ATTENTION_MAX_NODES) {
792
+ truncated = true;
793
+ break;
794
+ }
795
+ frontier = [...nextFrontier];
796
+ }
797
+ if (frontier.length > 0)
798
+ truncated = true;
799
+ const nodeIds = [...nodesById.keys()];
800
+ const activeIssueIds = new Set();
801
+ const agentIds = new Set();
802
+ const issueIdByExecutionRunId = new Map();
803
+ for (const node of nodesById.values()) {
804
+ if (node.assigneeAgentId)
805
+ agentIds.add(node.assigneeAgentId);
806
+ if (node.executionRunId)
807
+ issueIdByExecutionRunId.set(node.executionRunId, node.id);
808
+ }
809
+ for (const chunk of chunkList([...issueIdByExecutionRunId.keys()], ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {
810
+ const runRows = await dbOrTx
811
+ .select({
812
+ id: heartbeatRuns.id,
813
+ })
814
+ .from(heartbeatRuns)
815
+ .where(and(eq(heartbeatRuns.companyId, companyId), inArray(heartbeatRuns.status, BLOCKER_ATTENTION_ACTIVE_RUN_STATUSES), inArray(heartbeatRuns.id, chunk)));
816
+ for (const row of runRows) {
817
+ const issueId = issueIdByExecutionRunId.get(row.id);
818
+ if (issueId)
819
+ activeIssueIds.add(issueId);
820
+ }
821
+ }
822
+ for (const chunk of chunkList(nodeIds, ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {
823
+ const wakeRowsPromise = dbOrTx
824
+ .select({
825
+ issueId: sql `${agentWakeupRequests.payload} ->> 'issueId'`,
826
+ })
827
+ .from(agentWakeupRequests)
828
+ .where(and(eq(agentWakeupRequests.companyId, companyId), inArray(agentWakeupRequests.status, BLOCKER_ATTENTION_ACTIVE_WAKE_STATUSES), sql `${agentWakeupRequests.runId} is null`, inArray(sql `${agentWakeupRequests.payload} ->> 'issueId'`, chunk)));
829
+ const wakeRows = await wakeRowsPromise;
830
+ for (const row of wakeRows) {
831
+ if (row.issueId)
832
+ activeIssueIds.add(row.issueId);
833
+ }
834
+ }
835
+ const explicitWaitCandidateIds = [...nodesById.values()]
836
+ .filter((node) => node.status !== "done")
837
+ .map((node) => node.id);
838
+ const explicitWaitingIssueIds = new Set();
839
+ if (explicitWaitCandidateIds.length > 0) {
840
+ for (const chunk of chunkList(explicitWaitCandidateIds, ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {
841
+ const interactionRows = await dbOrTx
842
+ .select({ issueId: issueThreadInteractions.issueId })
843
+ .from(issueThreadInteractions)
844
+ .where(and(eq(issueThreadInteractions.companyId, companyId), inArray(issueThreadInteractions.status, BLOCKER_ATTENTION_PENDING_INTERACTION_STATUSES), inArray(issueThreadInteractions.issueId, chunk)));
845
+ for (const row of interactionRows)
846
+ explicitWaitingIssueIds.add(row.issueId);
847
+ const approvalRows = await dbOrTx
848
+ .select({ issueId: issueApprovals.issueId })
849
+ .from(issueApprovals)
850
+ .innerJoin(approvals, eq(issueApprovals.approvalId, approvals.id))
851
+ .where(and(eq(issueApprovals.companyId, companyId), inArray(approvals.status, BLOCKER_ATTENTION_PENDING_APPROVAL_STATUSES), inArray(issueApprovals.issueId, chunk)));
852
+ for (const row of approvalRows)
853
+ explicitWaitingIssueIds.add(row.issueId);
854
+ }
855
+ // Recovery rows are intentionally company-wide: a liveness escalation for
856
+ // the same leaf blocker represents an active waiting path even when that
857
+ // blocker is reached through another blocked graph.
858
+ const recoveryRows = await dbOrTx
859
+ .select({ id: issues.id, originId: issues.originId })
860
+ .from(issues)
861
+ .where(and(eq(issues.companyId, companyId), eq(issues.originKind, BLOCKER_ATTENTION_OPEN_RECOVERY_ORIGIN_KIND), isNull(issues.hiddenAt), notInArray(issues.status, BLOCKER_ATTENTION_OPEN_RECOVERY_TERMINAL_STATUSES)));
862
+ for (const row of recoveryRows) {
863
+ const parsed = parseIssueGraphLivenessIncidentKey(row.originId);
864
+ if (!parsed || parsed.companyId !== companyId)
865
+ continue;
866
+ explicitWaitingIssueIds.add(row.id);
867
+ explicitWaitingIssueIds.add(parsed.issueId);
868
+ explicitWaitingIssueIds.add(parsed.leafIssueId);
869
+ }
870
+ }
871
+ const agentRows = agentIds.size > 0
872
+ ? await dbOrTx
873
+ .select({
874
+ id: agents.id,
875
+ companyId: agents.companyId,
876
+ status: agents.status,
877
+ })
878
+ .from(agents)
879
+ .where(and(eq(agents.companyId, companyId), inArray(agents.id, [...agentIds])))
880
+ : [];
881
+ const agentsById = new Map(agentRows.map((agent) => [agent.id, agent]));
882
+ const classifyPath = (nodeId, seen) => {
883
+ const sample = blockerSampleIdentifier(nodesById.get(nodeId));
884
+ if (truncated || seen.has(nodeId)) {
885
+ return { covered: false, stalled: false, sampleBlockerIdentifier: sample, sampleStalledBlockerIdentifier: null };
886
+ }
887
+ const node = nodesById.get(nodeId);
888
+ if (!node || node.companyId !== companyId) {
889
+ return { covered: false, stalled: false, sampleBlockerIdentifier: nodeId, sampleStalledBlockerIdentifier: null };
890
+ }
891
+ const nodeSample = blockerSampleIdentifier(node);
892
+ if (node.status === "done") {
893
+ return { covered: true, stalled: false, sampleBlockerIdentifier: nodeSample, sampleStalledBlockerIdentifier: null };
894
+ }
895
+ if (explicitWaitingIssueIds.has(node.id)) {
896
+ return { covered: true, stalled: false, sampleBlockerIdentifier: nodeSample, sampleStalledBlockerIdentifier: null };
897
+ }
898
+ if (node.status === "in_review") {
899
+ const hasWaitingPath = activeIssueIds.has(node.id) || Boolean(node.assigneeUserId);
900
+ if (hasWaitingPath) {
901
+ return { covered: true, stalled: false, sampleBlockerIdentifier: nodeSample, sampleStalledBlockerIdentifier: null };
902
+ }
903
+ return { covered: false, stalled: true, sampleBlockerIdentifier: nodeSample, sampleStalledBlockerIdentifier: nodeSample };
904
+ }
905
+ if (activeIssueIds.has(node.id)) {
906
+ return { covered: true, stalled: false, sampleBlockerIdentifier: nodeSample, sampleStalledBlockerIdentifier: null };
907
+ }
908
+ if (node.status === "cancelled") {
909
+ return { covered: false, stalled: false, sampleBlockerIdentifier: nodeSample, sampleStalledBlockerIdentifier: null };
910
+ }
911
+ const downstream = (edgesByIssueId.get(node.id) ?? []).filter((edge) => nodesById.get(edge.blockerIssueId)?.status !== "done");
912
+ if (downstream.length > 0) {
913
+ const nextSeen = new Set(seen);
914
+ nextSeen.add(nodeId);
915
+ const classified = downstream.map((edge) => classifyPath(edge.blockerIssueId, nextSeen));
916
+ const stalledChild = classified.find((result) => result.stalled || result.sampleStalledBlockerIdentifier);
917
+ const sampleStalled = stalledChild?.sampleStalledBlockerIdentifier ?? null;
918
+ const hardAttention = classified.find((result) => !result.covered && !result.stalled);
919
+ if (hardAttention) {
920
+ return {
921
+ covered: false,
922
+ stalled: false,
923
+ sampleBlockerIdentifier: hardAttention.sampleBlockerIdentifier,
924
+ sampleStalledBlockerIdentifier: sampleStalled,
925
+ };
926
+ }
927
+ const stalledEntry = classified.find((result) => result.stalled);
928
+ if (stalledEntry) {
929
+ return {
930
+ covered: false,
931
+ stalled: true,
932
+ sampleBlockerIdentifier: stalledEntry.sampleBlockerIdentifier,
933
+ sampleStalledBlockerIdentifier: sampleStalled,
934
+ };
935
+ }
936
+ return {
937
+ covered: true,
938
+ stalled: false,
939
+ sampleBlockerIdentifier: classified[0]?.sampleBlockerIdentifier ?? nodeSample,
940
+ sampleStalledBlockerIdentifier: null,
941
+ };
942
+ }
943
+ if (node.assigneeAgentId) {
944
+ const assignee = agentsById.get(node.assigneeAgentId);
945
+ if (!assignee || assignee.companyId !== companyId || !BLOCKER_ATTENTION_INVOKABLE_AGENT_STATUSES.has(assignee.status)) {
946
+ return { covered: false, stalled: false, sampleBlockerIdentifier: nodeSample, sampleStalledBlockerIdentifier: null };
947
+ }
948
+ }
949
+ return { covered: false, stalled: false, sampleBlockerIdentifier: nodeSample, sampleStalledBlockerIdentifier: null };
950
+ };
951
+ for (const root of roots) {
952
+ const topLevelEdges = (edgesByIssueId.get(root.id) ?? []).filter((edge) => nodesById.get(edge.blockerIssueId)?.status !== "done");
953
+ if (topLevelEdges.length === 0) {
954
+ attentionMap.set(root.id, createIssueBlockerAttention({
955
+ state: "needs_attention",
956
+ reason: "attention_required",
957
+ }));
958
+ continue;
959
+ }
960
+ const classified = topLevelEdges.map((edge) => ({
961
+ edge,
962
+ result: classifyPath(edge.blockerIssueId, new Set([root.id])),
963
+ }));
964
+ const coveredBlockerCount = classified.filter((entry) => entry.result.covered).length;
965
+ const stalledBlockerCount = classified.filter((entry) => entry.result.stalled).length;
966
+ const attentionBlockerCount = classified.length - coveredBlockerCount - stalledBlockerCount;
967
+ const hardAttentionEntry = classified.find((entry) => !entry.result.covered && !entry.result.stalled);
968
+ const stalledEntry = classified.find((entry) => entry.result.stalled);
969
+ const sampleEntry = hardAttentionEntry ?? stalledEntry ?? classified[0] ?? null;
970
+ const sampleNode = sampleEntry ? nodesById.get(sampleEntry.edge.blockerIssueId) : null;
971
+ const sampleStalledFromChain = classified
972
+ .map((entry) => entry.result.sampleStalledBlockerIdentifier)
973
+ .find((value) => value);
974
+ let state;
975
+ let reason;
976
+ if (attentionBlockerCount > 0) {
977
+ state = "needs_attention";
978
+ reason = "attention_required";
979
+ }
980
+ else if (stalledBlockerCount > 0) {
981
+ state = "stalled";
982
+ reason = "stalled_review";
983
+ }
984
+ else {
985
+ state = "covered";
986
+ reason = topLevelEdges.every((edge) => nodesById.get(edge.blockerIssueId)?.parentId === root.id)
987
+ ? "active_child"
988
+ : "active_dependency";
989
+ }
990
+ attentionMap.set(root.id, createIssueBlockerAttention({
991
+ state,
992
+ reason,
993
+ unresolvedBlockerCount: topLevelEdges.length,
994
+ coveredBlockerCount,
995
+ stalledBlockerCount,
996
+ attentionBlockerCount,
997
+ sampleBlockerIdentifier: sampleEntry?.result.sampleBlockerIdentifier ?? blockerSampleIdentifier(sampleNode),
998
+ sampleStalledBlockerIdentifier: stalledEntry?.result.sampleStalledBlockerIdentifier ?? sampleStalledFromChain ?? null,
999
+ }));
1000
+ }
1001
+ return attentionMap;
1002
+ }
1003
+ const issueListSelect = {
1004
+ id: issues.id,
1005
+ companyId: issues.companyId,
1006
+ projectId: issues.projectId,
1007
+ projectWorkspaceId: issues.projectWorkspaceId,
1008
+ goalId: issues.goalId,
1009
+ parentId: issues.parentId,
1010
+ title: issues.title,
1011
+ description: sql `
1012
+ CASE
1013
+ WHEN ${issues.description} IS NULL THEN NULL
1014
+ ELSE encode(
1015
+ substring(
1016
+ convert_to(${issues.description}, current_setting('server_encoding'))
1017
+ FROM 1 FOR ${ISSUE_LIST_DESCRIPTION_MAX_BYTES}
1018
+ ),
1019
+ 'base64'
1020
+ )
1021
+ END
1022
+ `,
1023
+ status: issues.status,
1024
+ workMode: issues.workMode,
1025
+ priority: issues.priority,
1026
+ assigneeAgentId: issues.assigneeAgentId,
1027
+ assigneeUserId: issues.assigneeUserId,
1028
+ checkoutRunId: issues.checkoutRunId,
1029
+ executionRunId: issues.executionRunId,
1030
+ executionAgentNameKey: issues.executionAgentNameKey,
1031
+ executionLockedAt: issues.executionLockedAt,
1032
+ createdByAgentId: issues.createdByAgentId,
1033
+ createdByUserId: issues.createdByUserId,
1034
+ issueNumber: issues.issueNumber,
1035
+ identifier: issues.identifier,
1036
+ originKind: issues.originKind,
1037
+ originId: issues.originId,
1038
+ originRunId: issues.originRunId,
1039
+ originFingerprint: issues.originFingerprint,
1040
+ requestDepth: issues.requestDepth,
1041
+ billingCode: issues.billingCode,
1042
+ assigneeAdapterOverrides: issues.assigneeAdapterOverrides,
1043
+ executionPolicy: sql `null`,
1044
+ executionState: sql `null`,
1045
+ monitorNextCheckAt: issues.monitorNextCheckAt,
1046
+ monitorWakeRequestedAt: issues.monitorWakeRequestedAt,
1047
+ monitorLastTriggeredAt: issues.monitorLastTriggeredAt,
1048
+ monitorAttemptCount: issues.monitorAttemptCount,
1049
+ monitorNotes: issues.monitorNotes,
1050
+ monitorScheduledBy: issues.monitorScheduledBy,
1051
+ executionWorkspaceId: issues.executionWorkspaceId,
1052
+ executionWorkspacePreference: issues.executionWorkspacePreference,
1053
+ executionWorkspaceSettings: sql `null`,
1054
+ startedAt: issues.startedAt,
1055
+ completedAt: issues.completedAt,
1056
+ cancelledAt: issues.cancelledAt,
1057
+ hiddenAt: issues.hiddenAt,
1058
+ createdAt: issues.createdAt,
1059
+ updatedAt: issues.updatedAt,
1060
+ };
1061
+ function withActiveRuns(issueRows, runMap) {
1062
+ return issueRows.map((row) => ({
1063
+ ...row,
1064
+ activeRun: row.executionRunId ? (runMap.get(row.executionRunId) ?? null) : null,
1065
+ }));
1066
+ }
1067
+ async function userCommentStatsForIssues(dbOrTx, companyId, userId, issueIds) {
1068
+ const stats = [];
1069
+ for (const issueIdChunk of chunkList(issueIds, ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {
1070
+ const rows = await dbOrTx
1071
+ .select({
1072
+ issueId: issueComments.issueId,
1073
+ myLastCommentAt: sql `
1074
+ MAX(CASE WHEN ${issueComments.authorUserId} = ${userId} THEN ${issueComments.createdAt} END)
1075
+ `,
1076
+ lastExternalCommentAt: sql `
1077
+ MAX(
1078
+ CASE
1079
+ WHEN ${issueComments.authorUserId} IS NULL OR ${issueComments.authorUserId} <> ${userId}
1080
+ THEN ${issueComments.createdAt}
1081
+ END
1082
+ )
1083
+ `,
1084
+ })
1085
+ .from(issueComments)
1086
+ .where(and(eq(issueComments.companyId, companyId), inArray(issueComments.issueId, issueIdChunk)))
1087
+ .groupBy(issueComments.issueId);
1088
+ stats.push(...rows);
1089
+ }
1090
+ return stats;
1091
+ }
1092
+ async function userReadStatsForIssues(dbOrTx, companyId, userId, issueIds) {
1093
+ const stats = [];
1094
+ for (const issueIdChunk of chunkList(issueIds, ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {
1095
+ const rows = await dbOrTx
1096
+ .select({
1097
+ issueId: issueReadStates.issueId,
1098
+ myLastReadAt: issueReadStates.lastReadAt,
1099
+ })
1100
+ .from(issueReadStates)
1101
+ .where(and(eq(issueReadStates.companyId, companyId), eq(issueReadStates.userId, userId), inArray(issueReadStates.issueId, issueIdChunk)));
1102
+ stats.push(...rows);
1103
+ }
1104
+ return stats;
1105
+ }
1106
+ async function lastActivityStatsForIssues(dbOrTx, companyId, issueIds) {
1107
+ const byIssueId = new Map();
1108
+ for (const issueIdChunk of chunkList(issueIds, ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {
1109
+ const [commentRows, logRows] = await Promise.all([
1110
+ dbOrTx
1111
+ .select({
1112
+ issueId: issueComments.issueId,
1113
+ latestCommentAt: sql `MAX(${issueComments.createdAt})`,
1114
+ })
1115
+ .from(issueComments)
1116
+ .where(and(eq(issueComments.companyId, companyId), inArray(issueComments.issueId, issueIdChunk)))
1117
+ .groupBy(issueComments.issueId),
1118
+ dbOrTx
1119
+ .select({
1120
+ issueId: activityLog.entityId,
1121
+ latestLogAt: sql `MAX(${activityLog.createdAt})`,
1122
+ })
1123
+ .from(activityLog)
1124
+ .where(and(eq(activityLog.companyId, companyId), eq(activityLog.entityType, "issue"), inArray(activityLog.entityId, issueIdChunk), sql `${activityLog.action} NOT IN (${sql.join(ISSUE_LOCAL_INBOX_ACTIVITY_ACTIONS.map((action) => sql `${action}`), sql `, `)})`))
1125
+ .groupBy(activityLog.entityId),
1126
+ ]);
1127
+ for (const row of commentRows) {
1128
+ byIssueId.set(row.issueId, {
1129
+ issueId: row.issueId,
1130
+ latestCommentAt: row.latestCommentAt,
1131
+ latestLogAt: null,
1132
+ });
1133
+ }
1134
+ for (const row of logRows) {
1135
+ const existing = byIssueId.get(row.issueId);
1136
+ if (existing)
1137
+ existing.latestLogAt = row.latestLogAt;
1138
+ else {
1139
+ byIssueId.set(row.issueId, {
1140
+ issueId: row.issueId,
1141
+ latestCommentAt: null,
1142
+ latestLogAt: row.latestLogAt,
1143
+ });
1144
+ }
1145
+ }
1146
+ }
1147
+ return [...byIssueId.values()];
1148
+ }
1149
+ async function blockedByMapForIssues(dbOrTx, companyId, issueIds) {
1150
+ const map = new Map();
1151
+ const uniqueIssueIds = [...new Set(issueIds)];
1152
+ if (uniqueIssueIds.length === 0)
1153
+ return map;
1154
+ for (const issueId of uniqueIssueIds) {
1155
+ map.set(issueId, []);
1156
+ }
1157
+ for (const issueIdChunk of chunkList(uniqueIssueIds, ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {
1158
+ const rows = await dbOrTx
1159
+ .select({
1160
+ currentIssueId: issueRelations.relatedIssueId,
1161
+ relatedId: issues.id,
1162
+ identifier: issues.identifier,
1163
+ title: issues.title,
1164
+ status: issues.status,
1165
+ priority: issues.priority,
1166
+ assigneeAgentId: issues.assigneeAgentId,
1167
+ assigneeUserId: issues.assigneeUserId,
1168
+ })
1169
+ .from(issueRelations)
1170
+ .innerJoin(issues, eq(issueRelations.issueId, issues.id))
1171
+ .where(and(eq(issueRelations.companyId, companyId), eq(issueRelations.type, "blocks"), inArray(issueRelations.relatedIssueId, issueIdChunk)));
1172
+ for (const row of rows) {
1173
+ const blockedBy = map.get(row.currentIssueId);
1174
+ if (!blockedBy)
1175
+ continue;
1176
+ blockedBy.push({
1177
+ id: row.relatedId,
1178
+ identifier: row.identifier,
1179
+ title: row.title,
1180
+ status: row.status,
1181
+ priority: row.priority,
1182
+ assigneeAgentId: row.assigneeAgentId,
1183
+ assigneeUserId: row.assigneeUserId,
1184
+ });
1185
+ }
1186
+ }
1187
+ for (const blockedBy of map.values()) {
1188
+ blockedBy.sort((a, b) => a.title.localeCompare(b.title));
1189
+ }
1190
+ return map;
1191
+ }
1192
+ export function issueService(db) {
1193
+ const instanceSettings = instanceSettingsService(db);
1194
+ const treeControlSvc = issueTreeControlService(db);
1195
+ async function getIssueByUuid(id) {
1196
+ const row = await db
1197
+ .select()
1198
+ .from(issues)
1199
+ .where(eq(issues.id, id))
1200
+ .then((rows) => rows[0] ?? null);
1201
+ if (!row)
1202
+ return null;
1203
+ const [enriched] = await withIssueLabels(db, [row]);
1204
+ return enriched;
1205
+ }
1206
+ async function getIssueByIdentifier(identifier) {
1207
+ const row = await db
1208
+ .select()
1209
+ .from(issues)
1210
+ .where(eq(issues.identifier, identifier.toUpperCase()))
1211
+ .then((rows) => rows[0] ?? null);
1212
+ if (!row)
1213
+ return null;
1214
+ const [enriched] = await withIssueLabels(db, [row]);
1215
+ return enriched;
1216
+ }
1217
+ function deriveIssueCommentAuthorType(comment) {
1218
+ const explicit = issueCommentAuthorTypeSchema.safeParse(comment.authorType);
1219
+ if (explicit.success)
1220
+ return explicit.data;
1221
+ if (comment.authorAgentId)
1222
+ return "agent";
1223
+ if (comment.authorUserId)
1224
+ return "user";
1225
+ return "system";
1226
+ }
1227
+ function assertIssueCommentAuthorTypeAllowed(actor, authorType) {
1228
+ if (actor.agentId && authorType !== "agent") {
1229
+ throw unprocessable("Comment authorType must match authenticated actor");
1230
+ }
1231
+ if (actor.userId && authorType !== "user") {
1232
+ throw unprocessable("Comment authorType must match authenticated actor");
1233
+ }
1234
+ if (!actor.agentId && !actor.userId && authorType !== "system") {
1235
+ throw unprocessable("System comments cannot use user or agent authorType without an author id");
1236
+ }
1237
+ }
1238
+ function redactIssueComment(comment, censorUsernameInLogs) {
1239
+ return {
1240
+ ...comment,
1241
+ authorType: deriveIssueCommentAuthorType(comment),
1242
+ body: redactCurrentUserText(comment.body, { enabled: censorUsernameInLogs }),
1243
+ presentation: issueCommentPresentationSchema.nullable().catch(null).parse(comment.presentation ?? null),
1244
+ metadata: issueCommentMetadataSchema.nullable().catch(null).parse(comment.metadata ?? null),
1245
+ };
1246
+ }
1247
+ async function assertAssignableAgent(companyId, agentId) {
1248
+ const assignee = await db
1249
+ .select({
1250
+ id: agents.id,
1251
+ companyId: agents.companyId,
1252
+ status: agents.status,
1253
+ })
1254
+ .from(agents)
1255
+ .where(eq(agents.id, agentId))
1256
+ .then((rows) => rows[0] ?? null);
1257
+ if (!assignee)
1258
+ throw notFound("Assignee agent not found");
1259
+ if (assignee.companyId !== companyId) {
1260
+ throw unprocessable("Assignee must belong to same company");
1261
+ }
1262
+ if (assignee.status === "pending_approval") {
1263
+ throw conflict("Cannot assign work to pending approval agents");
1264
+ }
1265
+ if (assignee.status === "terminated") {
1266
+ throw conflict("Cannot assign work to terminated agents");
1267
+ }
1268
+ }
1269
+ async function isTreeHoldInteractionCheckoutAllowed(companyId, checkoutRunId, _gate) {
1270
+ if (!checkoutRunId)
1271
+ return false;
1272
+ const run = await db
1273
+ .select({
1274
+ id: heartbeatRuns.id,
1275
+ agentId: heartbeatRuns.agentId,
1276
+ wakeupRequestId: heartbeatRuns.wakeupRequestId,
1277
+ contextSnapshot: heartbeatRuns.contextSnapshot,
1278
+ })
1279
+ .from(heartbeatRuns)
1280
+ .where(and(eq(heartbeatRuns.id, checkoutRunId), eq(heartbeatRuns.companyId, companyId)))
1281
+ .then((rows) => rows[0] ?? null);
1282
+ const issueId = readStringFromRecord(run?.contextSnapshot, "issueId");
1283
+ if (!run || !issueId)
1284
+ return false;
1285
+ return isVerifiedIssueTreeControlInteractionWake(db, {
1286
+ companyId,
1287
+ issueId,
1288
+ agentId: run.agentId,
1289
+ runId: run.id,
1290
+ wakeupRequestId: run.wakeupRequestId,
1291
+ contextSnapshot: run.contextSnapshot,
1292
+ });
1293
+ }
1294
+ async function assertAssignableUser(companyId, userId) {
1295
+ const membership = await db
1296
+ .select({ id: companyMemberships.id })
1297
+ .from(companyMemberships)
1298
+ .where(and(eq(companyMemberships.companyId, companyId), eq(companyMemberships.principalType, "user"), eq(companyMemberships.principalId, userId), eq(companyMemberships.status, "active")))
1299
+ .then((rows) => rows[0] ?? null);
1300
+ if (!membership) {
1301
+ throw notFound("Assignee user not found");
1302
+ }
1303
+ }
1304
+ async function assertValidProjectWorkspace(companyId, projectId, projectWorkspaceId, dbOrTx = db) {
1305
+ const workspace = await dbOrTx
1306
+ .select({
1307
+ id: projectWorkspaces.id,
1308
+ companyId: projectWorkspaces.companyId,
1309
+ projectId: projectWorkspaces.projectId,
1310
+ })
1311
+ .from(projectWorkspaces)
1312
+ .where(eq(projectWorkspaces.id, projectWorkspaceId))
1313
+ .then((rows) => rows[0] ?? null);
1314
+ if (!workspace)
1315
+ throw notFound("Project workspace not found");
1316
+ if (workspace.companyId !== companyId)
1317
+ throw unprocessable("Project workspace must belong to same company");
1318
+ if (projectId && workspace.projectId !== projectId) {
1319
+ throw unprocessable("Project workspace must belong to the selected project");
1320
+ }
1321
+ }
1322
+ async function assertValidExecutionWorkspace(companyId, projectId, executionWorkspaceId, dbOrTx = db) {
1323
+ const workspace = await dbOrTx
1324
+ .select({
1325
+ id: executionWorkspaces.id,
1326
+ companyId: executionWorkspaces.companyId,
1327
+ projectId: executionWorkspaces.projectId,
1328
+ })
1329
+ .from(executionWorkspaces)
1330
+ .where(eq(executionWorkspaces.id, executionWorkspaceId))
1331
+ .then((rows) => rows[0] ?? null);
1332
+ if (!workspace)
1333
+ throw notFound("Execution workspace not found");
1334
+ if (workspace.companyId !== companyId)
1335
+ throw unprocessable("Execution workspace must belong to same company");
1336
+ if (projectId && workspace.projectId !== projectId) {
1337
+ throw unprocessable("Execution workspace must belong to the selected project");
1338
+ }
1339
+ }
1340
+ async function assertValidLabelIds(companyId, labelIds, dbOrTx = db) {
1341
+ if (labelIds.length === 0)
1342
+ return;
1343
+ const existing = await dbOrTx
1344
+ .select({ id: labels.id })
1345
+ .from(labels)
1346
+ .where(and(eq(labels.companyId, companyId), inArray(labels.id, labelIds)));
1347
+ if (existing.length !== new Set(labelIds).size) {
1348
+ throw unprocessable("One or more labels are invalid for this company");
1349
+ }
1350
+ }
1351
+ async function syncIssueLabels(issueId, companyId, labelIds, dbOrTx = db) {
1352
+ const deduped = [...new Set(labelIds)];
1353
+ await assertValidLabelIds(companyId, deduped, dbOrTx);
1354
+ await dbOrTx.delete(issueLabels).where(eq(issueLabels.issueId, issueId));
1355
+ if (deduped.length === 0)
1356
+ return;
1357
+ await dbOrTx.insert(issueLabels).values(deduped.map((labelId) => ({
1358
+ issueId,
1359
+ labelId,
1360
+ companyId,
1361
+ })));
1362
+ }
1363
+ async function getIssueRelationSummaryMap(companyId, issueIds, dbOrTx = db) {
1364
+ const uniqueIssueIds = [...new Set(issueIds)];
1365
+ const empty = new Map();
1366
+ for (const issueId of uniqueIssueIds) {
1367
+ empty.set(issueId, { blockedBy: [], blocks: [] });
1368
+ }
1369
+ if (uniqueIssueIds.length === 0)
1370
+ return empty;
1371
+ const [blockedByRows, blockingRows] = await Promise.all([
1372
+ dbOrTx
1373
+ .select({
1374
+ currentIssueId: issueRelations.relatedIssueId,
1375
+ relatedId: issues.id,
1376
+ identifier: issues.identifier,
1377
+ title: issues.title,
1378
+ status: issues.status,
1379
+ priority: issues.priority,
1380
+ assigneeAgentId: issues.assigneeAgentId,
1381
+ assigneeUserId: issues.assigneeUserId,
1382
+ })
1383
+ .from(issueRelations)
1384
+ .innerJoin(issues, eq(issueRelations.issueId, issues.id))
1385
+ .where(and(eq(issueRelations.companyId, companyId), eq(issueRelations.type, "blocks"), inArray(issueRelations.relatedIssueId, uniqueIssueIds))),
1386
+ dbOrTx
1387
+ .select({
1388
+ currentIssueId: issueRelations.issueId,
1389
+ relatedId: issues.id,
1390
+ identifier: issues.identifier,
1391
+ title: issues.title,
1392
+ status: issues.status,
1393
+ priority: issues.priority,
1394
+ assigneeAgentId: issues.assigneeAgentId,
1395
+ assigneeUserId: issues.assigneeUserId,
1396
+ })
1397
+ .from(issueRelations)
1398
+ .innerJoin(issues, eq(issueRelations.relatedIssueId, issues.id))
1399
+ .where(and(eq(issueRelations.companyId, companyId), eq(issueRelations.type, "blocks"), inArray(issueRelations.issueId, uniqueIssueIds))),
1400
+ ]);
1401
+ for (const row of blockedByRows) {
1402
+ empty.get(row.currentIssueId)?.blockedBy.push(summarizeIssueRelationRow(row));
1403
+ }
1404
+ for (const row of blockingRows) {
1405
+ empty.get(row.currentIssueId)?.blocks.push(summarizeIssueRelationRow(row));
1406
+ }
1407
+ const terminalByRoot = await terminalExplicitBlockersByRoot(companyId, [...empty.values()].flatMap((relations) => relations.blockedBy), dbOrTx);
1408
+ for (const relations of empty.values()) {
1409
+ relations.blockedBy.sort((a, b) => a.title.localeCompare(b.title));
1410
+ for (const blocker of relations.blockedBy) {
1411
+ const terminalBlockers = terminalByRoot.get(blocker.id);
1412
+ if (terminalBlockers && terminalBlockers.length > 0) {
1413
+ blocker.terminalBlockers = terminalBlockers;
1414
+ }
1415
+ }
1416
+ relations.blocks.sort((a, b) => a.title.localeCompare(b.title));
1417
+ }
1418
+ return empty;
1419
+ }
1420
+ async function assertNoBlockingCycles(companyId, issueId, blockerIssueIds, dbOrTx = db) {
1421
+ if (blockerIssueIds.length === 0)
1422
+ return;
1423
+ const rows = await dbOrTx
1424
+ .select({
1425
+ blockerIssueId: issueRelations.issueId,
1426
+ blockedIssueId: issueRelations.relatedIssueId,
1427
+ })
1428
+ .from(issueRelations)
1429
+ .where(and(eq(issueRelations.companyId, companyId), eq(issueRelations.type, "blocks")));
1430
+ const adjacency = new Map();
1431
+ for (const row of rows) {
1432
+ const list = adjacency.get(row.blockerIssueId) ?? [];
1433
+ list.push(row.blockedIssueId);
1434
+ adjacency.set(row.blockerIssueId, list);
1435
+ }
1436
+ for (const blockerIssueId of blockerIssueIds) {
1437
+ const queue = [...(adjacency.get(issueId) ?? [])];
1438
+ const visited = new Set([issueId]);
1439
+ while (queue.length > 0) {
1440
+ const current = queue.shift();
1441
+ if (current === blockerIssueId) {
1442
+ throw unprocessable("Blocking relations cannot contain cycles");
1443
+ }
1444
+ if (visited.has(current))
1445
+ continue;
1446
+ visited.add(current);
1447
+ queue.push(...(adjacency.get(current) ?? []));
1448
+ }
1449
+ }
1450
+ }
1451
+ async function syncBlockedByIssueIds(issueId, companyId, blockedByIssueIds, actor = {}, dbOrTx = db) {
1452
+ const deduped = [...new Set(blockedByIssueIds)];
1453
+ if (deduped.some((candidate) => candidate === issueId)) {
1454
+ throw unprocessable("Issue cannot be blocked by itself");
1455
+ }
1456
+ if (deduped.length > 0) {
1457
+ const lockedIssueIds = [issueId, ...deduped].sort();
1458
+ await dbOrTx.execute(sql `SELECT ${issues.id} FROM ${issues}
1459
+ WHERE ${and(eq(issues.companyId, companyId), inArray(issues.id, lockedIssueIds))}
1460
+ ORDER BY ${issues.id}
1461
+ FOR UPDATE`);
1462
+ const relatedIssues = await dbOrTx
1463
+ .select({ id: issues.id })
1464
+ .from(issues)
1465
+ .where(and(eq(issues.companyId, companyId), inArray(issues.id, deduped)));
1466
+ if (relatedIssues.length !== deduped.length) {
1467
+ throw unprocessable("Blocked-by issues must belong to the same company");
1468
+ }
1469
+ await assertNoBlockingCycles(companyId, issueId, deduped, dbOrTx);
1470
+ }
1471
+ await dbOrTx
1472
+ .delete(issueRelations)
1473
+ .where(and(eq(issueRelations.companyId, companyId), eq(issueRelations.relatedIssueId, issueId), eq(issueRelations.type, "blocks")));
1474
+ if (deduped.length === 0)
1475
+ return;
1476
+ await dbOrTx.insert(issueRelations).values(deduped.map((blockerIssueId) => ({
1477
+ companyId,
1478
+ issueId: blockerIssueId,
1479
+ relatedIssueId: issueId,
1480
+ type: "blocks",
1481
+ createdByAgentId: actor.agentId ?? null,
1482
+ createdByUserId: actor.userId ?? null,
1483
+ })));
1484
+ }
1485
+ async function isTerminalOrMissingHeartbeatRun(runId) {
1486
+ const run = await db
1487
+ .select({ status: heartbeatRuns.status })
1488
+ .from(heartbeatRuns)
1489
+ .where(eq(heartbeatRuns.id, runId))
1490
+ .then((rows) => rows[0] ?? null);
1491
+ if (!run)
1492
+ return true;
1493
+ return TERMINAL_HEARTBEAT_RUN_STATUSES.has(run.status);
1494
+ }
1495
+ async function adoptStaleCheckoutRun(input) {
1496
+ const stale = await isTerminalOrMissingHeartbeatRun(input.expectedCheckoutRunId);
1497
+ if (!stale)
1498
+ return null;
1499
+ const now = new Date();
1500
+ const adopted = await db
1501
+ .update(issues)
1502
+ .set({
1503
+ checkoutRunId: input.actorRunId,
1504
+ executionRunId: input.actorRunId,
1505
+ executionLockedAt: now,
1506
+ updatedAt: now,
1507
+ })
1508
+ .where(and(eq(issues.id, input.issueId), eq(issues.status, "in_progress"), eq(issues.assigneeAgentId, input.actorAgentId), eq(issues.checkoutRunId, input.expectedCheckoutRunId)))
1509
+ .returning({
1510
+ id: issues.id,
1511
+ status: issues.status,
1512
+ assigneeAgentId: issues.assigneeAgentId,
1513
+ checkoutRunId: issues.checkoutRunId,
1514
+ executionRunId: issues.executionRunId,
1515
+ })
1516
+ .then((rows) => rows[0] ?? null);
1517
+ return adopted;
1518
+ }
1519
+ async function adoptUnownedCheckoutRun(input) {
1520
+ const now = new Date();
1521
+ const adopted = await db
1522
+ .update(issues)
1523
+ .set({
1524
+ checkoutRunId: input.actorRunId,
1525
+ executionRunId: input.actorRunId,
1526
+ executionLockedAt: now,
1527
+ updatedAt: now,
1528
+ })
1529
+ .where(and(eq(issues.id, input.issueId), eq(issues.status, "in_progress"), eq(issues.assigneeAgentId, input.actorAgentId), isNull(issues.checkoutRunId), or(isNull(issues.executionRunId), eq(issues.executionRunId, input.actorRunId))))
1530
+ .returning({
1531
+ id: issues.id,
1532
+ status: issues.status,
1533
+ assigneeAgentId: issues.assigneeAgentId,
1534
+ checkoutRunId: issues.checkoutRunId,
1535
+ executionRunId: issues.executionRunId,
1536
+ })
1537
+ .then((rows) => rows[0] ?? null);
1538
+ return adopted;
1539
+ }
1540
+ async function clearExecutionRunIfTerminal(issueId) {
1541
+ return db.transaction(async (tx) => {
1542
+ await tx.execute(sql `select ${issues.id} from ${issues} where ${issues.id} = ${issueId} for update`);
1543
+ const issue = await tx
1544
+ .select({ executionRunId: issues.executionRunId })
1545
+ .from(issues)
1546
+ .where(eq(issues.id, issueId))
1547
+ .then((rows) => rows[0] ?? null);
1548
+ if (!issue?.executionRunId)
1549
+ return false;
1550
+ await tx.execute(sql `select ${heartbeatRuns.id} from ${heartbeatRuns} where ${heartbeatRuns.id} = ${issue.executionRunId} for update`);
1551
+ const run = await tx
1552
+ .select({ status: heartbeatRuns.status })
1553
+ .from(heartbeatRuns)
1554
+ .where(eq(heartbeatRuns.id, issue.executionRunId))
1555
+ .then((rows) => rows[0] ?? null);
1556
+ if (run && !TERMINAL_HEARTBEAT_RUN_STATUSES.has(run.status))
1557
+ return false;
1558
+ const updated = await tx
1559
+ .update(issues)
1560
+ .set({
1561
+ executionRunId: null,
1562
+ executionAgentNameKey: null,
1563
+ executionLockedAt: null,
1564
+ updatedAt: new Date(),
1565
+ })
1566
+ .where(and(eq(issues.id, issueId), eq(issues.executionRunId, issue.executionRunId)))
1567
+ .returning({ id: issues.id })
1568
+ .then((rows) => rows[0] ?? null);
1569
+ return Boolean(updated);
1570
+ });
1571
+ }
1572
+ return {
1573
+ clearExecutionRunIfTerminal,
1574
+ list: async (companyId, filters) => {
1575
+ const conditions = [eq(issues.companyId, companyId)];
1576
+ const limit = typeof filters?.limit === "number" && Number.isFinite(filters.limit)
1577
+ ? Math.max(1, Math.floor(filters.limit))
1578
+ : undefined;
1579
+ const offset = typeof filters?.offset === "number" && Number.isFinite(filters.offset)
1580
+ ? Math.max(0, Math.floor(filters.offset))
1581
+ : 0;
1582
+ const touchedByUserId = filters?.touchedByUserId?.trim() || undefined;
1583
+ const inboxArchivedByUserId = filters?.inboxArchivedByUserId?.trim() || undefined;
1584
+ const unreadForUserId = filters?.unreadForUserId?.trim() || undefined;
1585
+ const contextUserId = unreadForUserId ?? touchedByUserId ?? inboxArchivedByUserId;
1586
+ const includeBlockedBy = filters?.includeBlockedBy === true;
1587
+ const rawSearch = filters?.q?.trim() ?? "";
1588
+ const hasSearch = rawSearch.length > 0;
1589
+ const escapedSearch = hasSearch ? escapeLikePattern(rawSearch) : "";
1590
+ const startsWithPattern = `${escapedSearch}%`;
1591
+ const containsPattern = `%${escapedSearch}%`;
1592
+ const titleStartsWithMatch = sql `${issues.title} ILIKE ${startsWithPattern} ESCAPE '\\'`;
1593
+ const titleContainsMatch = sql `${issues.title} ILIKE ${containsPattern} ESCAPE '\\'`;
1594
+ const identifierStartsWithMatch = sql `${issues.identifier} ILIKE ${startsWithPattern} ESCAPE '\\'`;
1595
+ const identifierContainsMatch = sql `${issues.identifier} ILIKE ${containsPattern} ESCAPE '\\'`;
1596
+ const descriptionContainsMatch = sql `${issues.description} ILIKE ${containsPattern} ESCAPE '\\'`;
1597
+ const commentContainsMatch = sql `
1598
+ EXISTS (
1599
+ SELECT 1
1600
+ FROM ${issueComments}
1601
+ WHERE ${issueComments.issueId} = ${issues.id}
1602
+ AND ${issueComments.companyId} = ${companyId}
1603
+ AND ${issueComments.body} ILIKE ${containsPattern} ESCAPE '\\'
1604
+ )
1605
+ `;
1606
+ if (filters?.descendantOf) {
1607
+ conditions.push(sql `
1608
+ ${issues.id} IN (
1609
+ WITH RECURSIVE descendants(id) AS (
1610
+ SELECT ${issues.id}
1611
+ FROM ${issues}
1612
+ WHERE ${issues.companyId} = ${companyId}
1613
+ AND ${issues.parentId} = ${filters.descendantOf}
1614
+ UNION
1615
+ SELECT ${issues.id}
1616
+ FROM ${issues}
1617
+ JOIN descendants ON ${issues.parentId} = descendants.id
1618
+ WHERE ${issues.companyId} = ${companyId}
1619
+ )
1620
+ SELECT id FROM descendants
1621
+ )
1622
+ `);
1623
+ }
1624
+ if (filters?.status) {
1625
+ const statuses = filters.status.split(",").map((s) => s.trim());
1626
+ conditions.push(statuses.length === 1 ? eq(issues.status, statuses[0]) : inArray(issues.status, statuses));
1627
+ }
1628
+ if (filters?.assigneeAgentId) {
1629
+ conditions.push(eq(issues.assigneeAgentId, filters.assigneeAgentId));
1630
+ }
1631
+ if (filters?.participantAgentId) {
1632
+ conditions.push(participatedByAgentCondition(companyId, filters.participantAgentId));
1633
+ }
1634
+ if (filters?.assigneeUserId) {
1635
+ conditions.push(eq(issues.assigneeUserId, filters.assigneeUserId));
1636
+ }
1637
+ if (touchedByUserId) {
1638
+ conditions.push(touchedByUserCondition(companyId, touchedByUserId));
1639
+ }
1640
+ if (inboxArchivedByUserId) {
1641
+ conditions.push(inboxVisibleForUserCondition(companyId, inboxArchivedByUserId));
1642
+ }
1643
+ if (unreadForUserId) {
1644
+ conditions.push(unreadForUserCondition(companyId, unreadForUserId));
1645
+ }
1646
+ if (filters?.projectId)
1647
+ conditions.push(eq(issues.projectId, filters.projectId));
1648
+ if (filters?.workspaceId) {
1649
+ conditions.push(or(eq(issues.executionWorkspaceId, filters.workspaceId), eq(issues.projectWorkspaceId, filters.workspaceId)));
1650
+ }
1651
+ if (filters?.executionWorkspaceId) {
1652
+ conditions.push(eq(issues.executionWorkspaceId, filters.executionWorkspaceId));
1653
+ }
1654
+ if (filters?.parentId)
1655
+ conditions.push(eq(issues.parentId, filters.parentId));
1656
+ if (filters?.originKind)
1657
+ conditions.push(eq(issues.originKind, filters.originKind));
1658
+ if (filters?.originKindPrefix)
1659
+ conditions.push(like(issues.originKind, `${filters.originKindPrefix}%`));
1660
+ if (filters?.originId)
1661
+ conditions.push(eq(issues.originId, filters.originId));
1662
+ if (!shouldIncludePluginOperationIssues(filters)) {
1663
+ conditions.push(nonPluginOperationIssueCondition());
1664
+ }
1665
+ if (filters?.labelId) {
1666
+ const labeledIssueIds = await db
1667
+ .select({ issueId: issueLabels.issueId })
1668
+ .from(issueLabels)
1669
+ .where(and(eq(issueLabels.companyId, companyId), eq(issueLabels.labelId, filters.labelId)));
1670
+ if (labeledIssueIds.length === 0)
1671
+ return [];
1672
+ conditions.push(inArray(issues.id, labeledIssueIds.map((row) => row.issueId)));
1673
+ }
1674
+ if (hasSearch) {
1675
+ conditions.push(or(titleContainsMatch, identifierContainsMatch, descriptionContainsMatch, commentContainsMatch));
1676
+ }
1677
+ if (filters?.excludeRoutineExecutions && !filters?.originKind && !filters?.originId) {
1678
+ conditions.push(ne(issues.originKind, "routine_execution"));
1679
+ }
1680
+ conditions.push(isNull(issues.hiddenAt));
1681
+ const priorityOrder = sql `CASE ${issues.priority} WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 ELSE 4 END`;
1682
+ const searchOrder = sql `
1683
+ CASE
1684
+ WHEN ${titleStartsWithMatch} THEN 0
1685
+ WHEN ${titleContainsMatch} THEN 1
1686
+ WHEN ${identifierStartsWithMatch} THEN 2
1687
+ WHEN ${identifierContainsMatch} THEN 3
1688
+ WHEN ${commentContainsMatch} THEN 4
1689
+ WHEN ${descriptionContainsMatch} THEN 5
1690
+ ELSE 6
1691
+ END
1692
+ `;
1693
+ const canonicalLastActivityAt = issueCanonicalLastActivityAtExpr(companyId);
1694
+ const baseQuery = db
1695
+ .select(issueListSelect)
1696
+ .from(issues)
1697
+ .where(and(...conditions))
1698
+ .orderBy(hasSearch ? asc(searchOrder) : asc(priorityOrder), asc(priorityOrder), desc(canonicalLastActivityAt), desc(issues.updatedAt), desc(issues.id));
1699
+ const pageQuery = offset > 0
1700
+ ? (limit === undefined ? baseQuery.offset(offset) : baseQuery.limit(limit).offset(offset))
1701
+ : (limit === undefined ? baseQuery : baseQuery.limit(limit));
1702
+ const rows = (await pageQuery).map((row) => ({
1703
+ ...row,
1704
+ description: decodeDatabaseTextPreview(row.description, ISSUE_LIST_DESCRIPTION_MAX_CHARS),
1705
+ }));
1706
+ const withLabels = await withIssueLabels(db, rows);
1707
+ const runMap = await activeRunMapForIssues(db, withLabels);
1708
+ const withRuns = withActiveRuns(withLabels, runMap);
1709
+ if (withRuns.length === 0) {
1710
+ return withRuns;
1711
+ }
1712
+ const issueIds = withRuns.map((row) => row.id);
1713
+ const [statsRows, readRows, lastActivityRows, blockedByMap] = await Promise.all([
1714
+ contextUserId
1715
+ ? userCommentStatsForIssues(db, companyId, contextUserId, issueIds)
1716
+ : Promise.resolve([]),
1717
+ contextUserId
1718
+ ? userReadStatsForIssues(db, companyId, contextUserId, issueIds)
1719
+ : Promise.resolve([]),
1720
+ lastActivityStatsForIssues(db, companyId, issueIds),
1721
+ includeBlockedBy
1722
+ ? blockedByMapForIssues(db, companyId, issueIds)
1723
+ : Promise.resolve(new Map()),
1724
+ ]);
1725
+ const statsByIssueId = new Map(statsRows.map((row) => [row.issueId, row]));
1726
+ const lastActivityByIssueId = new Map(lastActivityRows.map((row) => [row.issueId, row]));
1727
+ const [blockerAttentionByIssueId, productivityReviewByIssueId] = await Promise.all([
1728
+ listIssueBlockerAttentionMap(db, companyId, withRuns),
1729
+ listIssueProductivityReviewMap(db, companyId, issueIds),
1730
+ ]);
1731
+ if (!contextUserId) {
1732
+ return withRuns.map((row) => {
1733
+ const activity = lastActivityByIssueId.get(row.id);
1734
+ const lastActivityAt = latestIssueActivityAt(row.updatedAt, activity?.latestCommentAt ?? null, activity?.latestLogAt ?? null) ?? row.updatedAt;
1735
+ return {
1736
+ ...row,
1737
+ ...(includeBlockedBy ? { blockedBy: blockedByMap.get(row.id) ?? [] } : {}),
1738
+ lastActivityAt,
1739
+ ...(blockerAttentionByIssueId.has(row.id) ? { blockerAttention: blockerAttentionByIssueId.get(row.id) } : {}),
1740
+ ...(productivityReviewByIssueId.has(row.id)
1741
+ ? { productivityReview: productivityReviewByIssueId.get(row.id) }
1742
+ : {}),
1743
+ };
1744
+ });
1745
+ }
1746
+ const readByIssueId = new Map(readRows.map((row) => [row.issueId, row.myLastReadAt]));
1747
+ return withRuns.map((row) => {
1748
+ const activity = lastActivityByIssueId.get(row.id);
1749
+ const lastActivityAt = latestIssueActivityAt(row.updatedAt, activity?.latestCommentAt ?? null, activity?.latestLogAt ?? null) ?? row.updatedAt;
1750
+ return {
1751
+ ...row,
1752
+ ...(includeBlockedBy ? { blockedBy: blockedByMap.get(row.id) ?? [] } : {}),
1753
+ lastActivityAt,
1754
+ ...(blockerAttentionByIssueId.has(row.id) ? { blockerAttention: blockerAttentionByIssueId.get(row.id) } : {}),
1755
+ ...(productivityReviewByIssueId.has(row.id)
1756
+ ? { productivityReview: productivityReviewByIssueId.get(row.id) }
1757
+ : {}),
1758
+ ...deriveIssueUserContext(row, contextUserId, {
1759
+ myLastCommentAt: statsByIssueId.get(row.id)?.myLastCommentAt ?? null,
1760
+ myLastReadAt: readByIssueId.get(row.id) ?? null,
1761
+ lastExternalCommentAt: statsByIssueId.get(row.id)?.lastExternalCommentAt ?? null,
1762
+ }),
1763
+ };
1764
+ });
1765
+ },
1766
+ countUnreadTouchedByUser: async (companyId, userId, status) => {
1767
+ const conditions = [
1768
+ eq(issues.companyId, companyId),
1769
+ isNull(issues.hiddenAt),
1770
+ nonPluginOperationIssueCondition(),
1771
+ unreadForUserCondition(companyId, userId),
1772
+ ];
1773
+ if (status) {
1774
+ const statuses = status.split(",").map((s) => s.trim()).filter(Boolean);
1775
+ if (statuses.length === 1) {
1776
+ conditions.push(eq(issues.status, statuses[0]));
1777
+ }
1778
+ else if (statuses.length > 1) {
1779
+ conditions.push(inArray(issues.status, statuses));
1780
+ }
1781
+ }
1782
+ const [row] = await db
1783
+ .select({ count: sql `count(*)` })
1784
+ .from(issues)
1785
+ .where(and(...conditions));
1786
+ return Number(row?.count ?? 0);
1787
+ },
1788
+ markRead: async (companyId, issueId, userId, readAt = new Date()) => {
1789
+ const now = new Date();
1790
+ const [row] = await db
1791
+ .insert(issueReadStates)
1792
+ .values({
1793
+ companyId,
1794
+ issueId,
1795
+ userId,
1796
+ lastReadAt: readAt,
1797
+ updatedAt: now,
1798
+ })
1799
+ .onConflictDoUpdate({
1800
+ target: [issueReadStates.companyId, issueReadStates.issueId, issueReadStates.userId],
1801
+ set: {
1802
+ lastReadAt: readAt,
1803
+ updatedAt: now,
1804
+ },
1805
+ })
1806
+ .returning();
1807
+ return row;
1808
+ },
1809
+ markUnread: async (companyId, issueId, userId) => {
1810
+ const deleted = await db
1811
+ .delete(issueReadStates)
1812
+ .where(and(eq(issueReadStates.companyId, companyId), eq(issueReadStates.issueId, issueId), eq(issueReadStates.userId, userId)))
1813
+ .returning();
1814
+ return deleted.length > 0;
1815
+ },
1816
+ archiveInbox: async (companyId, issueId, userId, archivedAt = new Date()) => {
1817
+ const now = new Date();
1818
+ const [row] = await db
1819
+ .insert(issueInboxArchives)
1820
+ .values({
1821
+ companyId,
1822
+ issueId,
1823
+ userId,
1824
+ archivedAt,
1825
+ updatedAt: now,
1826
+ })
1827
+ .onConflictDoUpdate({
1828
+ target: [issueInboxArchives.companyId, issueInboxArchives.issueId, issueInboxArchives.userId],
1829
+ set: {
1830
+ archivedAt,
1831
+ updatedAt: now,
1832
+ },
1833
+ })
1834
+ .returning();
1835
+ return row;
1836
+ },
1837
+ unarchiveInbox: async (companyId, issueId, userId) => {
1838
+ const [row] = await db
1839
+ .delete(issueInboxArchives)
1840
+ .where(and(eq(issueInboxArchives.companyId, companyId), eq(issueInboxArchives.issueId, issueId), eq(issueInboxArchives.userId, userId)))
1841
+ .returning();
1842
+ return row ?? null;
1843
+ },
1844
+ getById: async (raw) => {
1845
+ const id = raw.trim();
1846
+ const identifier = normalizeIssueReferenceIdentifier(id);
1847
+ if (identifier) {
1848
+ return getIssueByIdentifier(identifier);
1849
+ }
1850
+ if (!isUuidLike(id)) {
1851
+ return null;
1852
+ }
1853
+ return getIssueByUuid(id);
1854
+ },
1855
+ getByIdentifier: async (identifier) => {
1856
+ return getIssueByIdentifier(identifier);
1857
+ },
1858
+ getRelationSummaries: async (issueId) => {
1859
+ const issue = await db
1860
+ .select({ id: issues.id, companyId: issues.companyId })
1861
+ .from(issues)
1862
+ .where(eq(issues.id, issueId))
1863
+ .then((rows) => rows[0] ?? null);
1864
+ if (!issue)
1865
+ throw notFound("Issue not found");
1866
+ const relations = await getIssueRelationSummaryMap(issue.companyId, [issueId], db);
1867
+ return relations.get(issueId) ?? { blockedBy: [], blocks: [] };
1868
+ },
1869
+ getDependencyReadiness: async (issueId, dbOrTx = db) => {
1870
+ const issue = await dbOrTx
1871
+ .select({ id: issues.id, companyId: issues.companyId })
1872
+ .from(issues)
1873
+ .where(eq(issues.id, issueId))
1874
+ .then((rows) => rows[0] ?? null);
1875
+ if (!issue)
1876
+ throw notFound("Issue not found");
1877
+ const readiness = await listIssueDependencyReadinessMap(dbOrTx, issue.companyId, [issueId]);
1878
+ return readiness.get(issueId) ?? createIssueDependencyReadiness(issueId);
1879
+ },
1880
+ listDependencyReadiness: async (companyId, issueIds, dbOrTx = db) => {
1881
+ return listIssueDependencyReadinessMap(dbOrTx, companyId, issueIds);
1882
+ },
1883
+ listBlockerAttention: async (companyId, issueRows, dbOrTx = db) => {
1884
+ return listIssueBlockerAttentionMap(dbOrTx, companyId, issueRows);
1885
+ },
1886
+ listProductivityReviews: async (companyId, sourceIssueIds, dbOrTx = db) => {
1887
+ return listIssueProductivityReviewMap(dbOrTx, companyId, sourceIssueIds);
1888
+ },
1889
+ listWakeableBlockedDependents: async (blockerIssueId) => {
1890
+ const blockerIssue = await db
1891
+ .select({ id: issues.id, companyId: issues.companyId })
1892
+ .from(issues)
1893
+ .where(eq(issues.id, blockerIssueId))
1894
+ .then((rows) => rows[0] ?? null);
1895
+ if (!blockerIssue)
1896
+ return [];
1897
+ const candidates = await db
1898
+ .select({
1899
+ id: issues.id,
1900
+ assigneeAgentId: issues.assigneeAgentId,
1901
+ status: issues.status,
1902
+ })
1903
+ .from(issueRelations)
1904
+ .innerJoin(issues, eq(issueRelations.relatedIssueId, issues.id))
1905
+ .where(and(eq(issueRelations.companyId, blockerIssue.companyId), eq(issueRelations.type, "blocks"), eq(issueRelations.issueId, blockerIssueId)));
1906
+ if (candidates.length === 0)
1907
+ return [];
1908
+ const candidateIds = candidates.map((candidate) => candidate.id);
1909
+ const blockerRows = await db
1910
+ .select({
1911
+ issueId: issueRelations.relatedIssueId,
1912
+ blockerIssueId: issueRelations.issueId,
1913
+ blockerStatus: issues.status,
1914
+ })
1915
+ .from(issueRelations)
1916
+ .innerJoin(issues, eq(issueRelations.issueId, issues.id))
1917
+ .where(and(eq(issueRelations.companyId, blockerIssue.companyId), eq(issueRelations.type, "blocks"), inArray(issueRelations.relatedIssueId, candidateIds)));
1918
+ const blockersByIssueId = new Map();
1919
+ for (const row of blockerRows) {
1920
+ const list = blockersByIssueId.get(row.issueId) ?? [];
1921
+ list.push({ blockerIssueId: row.blockerIssueId, blockerStatus: row.blockerStatus });
1922
+ blockersByIssueId.set(row.issueId, list);
1923
+ }
1924
+ return candidates
1925
+ .filter((candidate) => candidate.assigneeAgentId && !["backlog", "done", "cancelled"].includes(candidate.status))
1926
+ .map((candidate) => {
1927
+ const blockers = blockersByIssueId.get(candidate.id) ?? [];
1928
+ return {
1929
+ ...candidate,
1930
+ blockerIssueIds: blockers.map((blocker) => blocker.blockerIssueId),
1931
+ allBlockersDone: blockers.length > 0 && blockers.every((blocker) => blocker.blockerStatus === "done"),
1932
+ };
1933
+ })
1934
+ .filter((candidate) => candidate.allBlockersDone)
1935
+ .map((candidate) => ({
1936
+ id: candidate.id,
1937
+ assigneeAgentId: candidate.assigneeAgentId,
1938
+ blockerIssueIds: candidate.blockerIssueIds,
1939
+ }));
1940
+ },
1941
+ getWakeableParentAfterChildCompletion: async (parentIssueId) => {
1942
+ const parent = await db
1943
+ .select({
1944
+ id: issues.id,
1945
+ assigneeAgentId: issues.assigneeAgentId,
1946
+ status: issues.status,
1947
+ companyId: issues.companyId,
1948
+ })
1949
+ .from(issues)
1950
+ .where(eq(issues.id, parentIssueId))
1951
+ .then((rows) => rows[0] ?? null);
1952
+ if (!parent || !parent.assigneeAgentId || ["backlog", "done", "cancelled"].includes(parent.status)) {
1953
+ return null;
1954
+ }
1955
+ const children = await db
1956
+ .select({
1957
+ id: issues.id,
1958
+ identifier: issues.identifier,
1959
+ title: issues.title,
1960
+ status: issues.status,
1961
+ priority: issues.priority,
1962
+ assigneeAgentId: issues.assigneeAgentId,
1963
+ assigneeUserId: issues.assigneeUserId,
1964
+ updatedAt: issues.updatedAt,
1965
+ })
1966
+ .from(issues)
1967
+ .where(and(eq(issues.companyId, parent.companyId), eq(issues.parentId, parentIssueId)))
1968
+ .orderBy(asc(issues.issueNumber), asc(issues.createdAt));
1969
+ if (children.length === 0)
1970
+ return null;
1971
+ if (!children.every((child) => child.status === "done" || child.status === "cancelled")) {
1972
+ return null;
1973
+ }
1974
+ const childIdsForSummaries = children.slice(0, MAX_CHILD_COMPLETION_SUMMARIES).map((child) => child.id);
1975
+ const commentRows = childIdsForSummaries.length > 0
1976
+ ? await db
1977
+ .select({
1978
+ issueId: issueComments.issueId,
1979
+ body: issueComments.body,
1980
+ createdAt: issueComments.createdAt,
1981
+ })
1982
+ .from(issueComments)
1983
+ .where(and(eq(issueComments.companyId, parent.companyId), inArray(issueComments.issueId, childIdsForSummaries)))
1984
+ .orderBy(desc(issueComments.createdAt), desc(issueComments.id))
1985
+ : [];
1986
+ const latestCommentByIssueId = new Map();
1987
+ for (const comment of commentRows) {
1988
+ if (!latestCommentByIssueId.has(comment.issueId)) {
1989
+ latestCommentByIssueId.set(comment.issueId, comment.body);
1990
+ }
1991
+ }
1992
+ const childIssueSummaries = children
1993
+ .slice(0, MAX_CHILD_COMPLETION_SUMMARIES)
1994
+ .map((child) => ({
1995
+ ...child,
1996
+ summary: truncateInlineSummary(latestCommentByIssueId.get(child.id)),
1997
+ }));
1998
+ return {
1999
+ id: parent.id,
2000
+ assigneeAgentId: parent.assigneeAgentId,
2001
+ childIssueIds: children.map((child) => child.id),
2002
+ childIssueSummaries,
2003
+ childIssueSummaryTruncated: children.length > childIssueSummaries.length,
2004
+ };
2005
+ },
2006
+ createChild: async (parentIssueId, data) => {
2007
+ const parent = await db
2008
+ .select()
2009
+ .from(issues)
2010
+ .where(eq(issues.id, parentIssueId))
2011
+ .then((rows) => rows[0] ?? null);
2012
+ if (!parent)
2013
+ throw notFound("Parent issue not found");
2014
+ const [{ childCount }] = await db
2015
+ .select({ childCount: sql `count(*)::int` })
2016
+ .from(issues)
2017
+ .where(and(eq(issues.companyId, parent.companyId), eq(issues.parentId, parent.id)));
2018
+ if (childCount >= MAX_CHILD_ISSUES_CREATED_BY_HELPER) {
2019
+ throw unprocessable(`Parent issue already has the maximum ${MAX_CHILD_ISSUES_CREATED_BY_HELPER} child issues for this helper`);
2020
+ }
2021
+ const { acceptanceCriteria, blockParentUntilDone, actorAgentId, actorUserId, ...issueData } = data;
2022
+ const child = await issueService(db).create(parent.companyId, {
2023
+ ...issueData,
2024
+ parentId: parent.id,
2025
+ projectId: issueData.projectId ?? parent.projectId,
2026
+ goalId: issueData.goalId ?? parent.goalId,
2027
+ requestDepth: clampIssueRequestDepth(Math.max(clampIssueRequestDepth(parent.requestDepth) + 1, issueData.requestDepth ?? 0)),
2028
+ description: appendAcceptanceCriteriaToDescription(issueData.description, acceptanceCriteria),
2029
+ inheritExecutionWorkspaceFromIssueId: parent.id,
2030
+ });
2031
+ if (blockParentUntilDone) {
2032
+ const existingBlockers = await db
2033
+ .select({ blockerIssueId: issueRelations.issueId })
2034
+ .from(issueRelations)
2035
+ .where(and(eq(issueRelations.companyId, parent.companyId), eq(issueRelations.relatedIssueId, parent.id), eq(issueRelations.type, "blocks")));
2036
+ await syncBlockedByIssueIds(parent.id, parent.companyId, [...new Set([...existingBlockers.map((row) => row.blockerIssueId), child.id])], { agentId: actorAgentId ?? null, userId: actorUserId ?? null });
2037
+ }
2038
+ return {
2039
+ issue: child,
2040
+ parentBlockerAdded: Boolean(blockParentUntilDone),
2041
+ };
2042
+ },
2043
+ create: async (companyId, data) => {
2044
+ const { labelIds: inputLabelIds, blockedByIssueIds, inheritExecutionWorkspaceFromIssueId, ...issueData } = data;
2045
+ const isolatedWorkspacesEnabled = (await instanceSettings.getExperimental()).enableIsolatedWorkspaces;
2046
+ if (!isolatedWorkspacesEnabled) {
2047
+ delete issueData.executionWorkspaceId;
2048
+ delete issueData.executionWorkspacePreference;
2049
+ delete issueData.executionWorkspaceSettings;
2050
+ }
2051
+ if (data.assigneeAgentId && data.assigneeUserId) {
2052
+ throw unprocessable("Issue can only have one assignee");
2053
+ }
2054
+ if (data.assigneeAgentId) {
2055
+ await assertAssignableAgent(companyId, data.assigneeAgentId);
2056
+ }
2057
+ if (data.assigneeUserId) {
2058
+ await assertAssignableUser(companyId, data.assigneeUserId);
2059
+ }
2060
+ if (data.status === "in_progress" && !data.assigneeAgentId && !data.assigneeUserId) {
2061
+ throw unprocessable("in_progress issues require an assignee");
2062
+ }
2063
+ return db.transaction(async (tx) => {
2064
+ const defaultCompanyGoal = await getDefaultCompanyGoal(tx, companyId);
2065
+ const projectGoalId = await getProjectDefaultGoalId(tx, companyId, issueData.projectId);
2066
+ let projectWorkspaceId = issueData.projectWorkspaceId ?? null;
2067
+ let executionWorkspaceId = issueData.executionWorkspaceId ?? null;
2068
+ let executionWorkspacePreference = issueData.executionWorkspacePreference ?? null;
2069
+ let executionWorkspaceSettings = issueData.executionWorkspaceSettings ?? null;
2070
+ const workspaceInheritanceIssueId = inheritExecutionWorkspaceFromIssueId ?? issueData.parentId ?? null;
2071
+ const hasExplicitExecutionWorkspaceOverride = issueData.executionWorkspaceId !== undefined ||
2072
+ issueData.executionWorkspacePreference !== undefined ||
2073
+ issueData.executionWorkspaceSettings !== undefined;
2074
+ if (workspaceInheritanceIssueId) {
2075
+ const workspaceSource = await getWorkspaceInheritanceIssue(tx, companyId, workspaceInheritanceIssueId);
2076
+ if (projectWorkspaceId == null && workspaceSource.projectWorkspaceId) {
2077
+ projectWorkspaceId = workspaceSource.projectWorkspaceId;
2078
+ }
2079
+ if (isolatedWorkspacesEnabled &&
2080
+ !hasExplicitExecutionWorkspaceOverride &&
2081
+ workspaceSource.executionWorkspaceId) {
2082
+ const sourceWorkspace = await tx
2083
+ .select({
2084
+ id: executionWorkspaces.id,
2085
+ mode: executionWorkspaces.mode,
2086
+ })
2087
+ .from(executionWorkspaces)
2088
+ .where(eq(executionWorkspaces.id, workspaceSource.executionWorkspaceId))
2089
+ .then((rows) => rows[0] ?? null);
2090
+ if (sourceWorkspace) {
2091
+ executionWorkspaceId = sourceWorkspace.id;
2092
+ executionWorkspacePreference = "reuse_existing";
2093
+ executionWorkspaceSettings = {
2094
+ ...(workspaceSource.executionWorkspaceSettings ?? {}),
2095
+ mode: issueExecutionWorkspaceModeForPersistedWorkspace(sourceWorkspace.mode),
2096
+ };
2097
+ }
2098
+ }
2099
+ }
2100
+ // Cache the project policy lookup for this insert. Both the
2101
+ // default-settings block and the assignee-environment-promotion block
2102
+ // need the same row; without caching they'd issue two round-trips.
2103
+ let projectPolicyCached = null;
2104
+ let projectPolicyLoaded = false;
2105
+ const loadProjectPolicyOnce = async () => {
2106
+ if (projectPolicyLoaded)
2107
+ return projectPolicyCached;
2108
+ projectPolicyLoaded = true;
2109
+ if (!issueData.projectId)
2110
+ return null;
2111
+ const projectRow = await tx
2112
+ .select({ executionWorkspacePolicy: projects.executionWorkspacePolicy })
2113
+ .from(projects)
2114
+ .where(and(eq(projects.id, issueData.projectId), eq(projects.companyId, companyId)))
2115
+ .then((rows) => rows[0] ?? null);
2116
+ projectPolicyCached = parseProjectExecutionWorkspacePolicy(projectRow?.executionWorkspacePolicy);
2117
+ return projectPolicyCached;
2118
+ };
2119
+ if (executionWorkspaceSettings == null &&
2120
+ executionWorkspaceId == null &&
2121
+ issueData.projectId) {
2122
+ executionWorkspaceSettings =
2123
+ defaultIssueExecutionWorkspaceSettingsForProject(gateProjectExecutionWorkspacePolicy(await loadProjectPolicyOnce(), isolatedWorkspacesEnabled));
2124
+ }
2125
+ if (data.assigneeAgentId && isolatedWorkspacesEnabled) {
2126
+ const currentWorkspaceSettings = executionWorkspaceSettings == null
2127
+ ? {}
2128
+ : parseObject(executionWorkspaceSettings);
2129
+ const issueHasEnvironmentSelection = Object.prototype.hasOwnProperty.call(currentWorkspaceSettings, "environmentId");
2130
+ // Don't promote the assignee agent's defaultEnvironmentId if either
2131
+ // the issue or the project policy already specifies an environment.
2132
+ // resolveExecutionWorkspaceEnvironmentId treats issue settings as
2133
+ // higher priority than project policy, so promoting the agent's
2134
+ // default to issue settings would invert the documented priority
2135
+ // (project policy must win over agent default when explicitly set).
2136
+ let projectHasEnvironmentSelection = false;
2137
+ if (!issueHasEnvironmentSelection && issueData.projectId) {
2138
+ const projectPolicy = await loadProjectPolicyOnce();
2139
+ projectHasEnvironmentSelection = projectPolicy?.environmentId !== undefined;
2140
+ }
2141
+ if (!issueHasEnvironmentSelection && !projectHasEnvironmentSelection) {
2142
+ const assigneeAgent = await tx
2143
+ .select({ defaultEnvironmentId: agents.defaultEnvironmentId })
2144
+ .from(agents)
2145
+ .where(and(eq(agents.id, data.assigneeAgentId), eq(agents.companyId, companyId)))
2146
+ .then((rows) => rows[0] ?? null);
2147
+ if (typeof assigneeAgent?.defaultEnvironmentId === "string" && assigneeAgent.defaultEnvironmentId.length > 0) {
2148
+ executionWorkspaceSettings = {
2149
+ ...currentWorkspaceSettings,
2150
+ environmentId: assigneeAgent.defaultEnvironmentId,
2151
+ };
2152
+ }
2153
+ }
2154
+ }
2155
+ if (!projectWorkspaceId && issueData.projectId) {
2156
+ const project = await tx
2157
+ .select({
2158
+ executionWorkspacePolicy: projects.executionWorkspacePolicy,
2159
+ })
2160
+ .from(projects)
2161
+ .where(and(eq(projects.id, issueData.projectId), eq(projects.companyId, companyId)))
2162
+ .then((rows) => rows[0] ?? null);
2163
+ const projectPolicy = parseProjectExecutionWorkspacePolicy(project?.executionWorkspacePolicy);
2164
+ projectWorkspaceId = projectPolicy?.defaultProjectWorkspaceId ?? null;
2165
+ if (!projectWorkspaceId) {
2166
+ projectWorkspaceId = await tx
2167
+ .select({ id: projectWorkspaces.id })
2168
+ .from(projectWorkspaces)
2169
+ .where(and(eq(projectWorkspaces.projectId, issueData.projectId), eq(projectWorkspaces.companyId, companyId)))
2170
+ .orderBy(desc(projectWorkspaces.isPrimary), asc(projectWorkspaces.createdAt), asc(projectWorkspaces.id))
2171
+ .then((rows) => rows[0]?.id ?? null);
2172
+ }
2173
+ }
2174
+ if (projectWorkspaceId) {
2175
+ await assertValidProjectWorkspace(companyId, issueData.projectId, projectWorkspaceId, tx);
2176
+ }
2177
+ if (executionWorkspaceId) {
2178
+ await assertValidExecutionWorkspace(companyId, issueData.projectId, executionWorkspaceId, tx);
2179
+ }
2180
+ // Self-correcting counter: use MAX(issue_number) + 1 if the counter
2181
+ // has drifted below the actual max, preventing identifier collisions.
2182
+ const [maxRow] = await tx
2183
+ .select({ maxNum: sql `coalesce(max(${issues.issueNumber}), 0)` })
2184
+ .from(issues)
2185
+ .where(eq(issues.companyId, companyId));
2186
+ const currentMax = maxRow?.maxNum ?? 0;
2187
+ const [company] = await tx
2188
+ .update(companies)
2189
+ .set({
2190
+ issueCounter: sql `greatest(${companies.issueCounter}, ${currentMax}) + 1`,
2191
+ })
2192
+ .where(eq(companies.id, companyId))
2193
+ .returning({ issueCounter: companies.issueCounter, issuePrefix: companies.issuePrefix });
2194
+ const issueNumber = company.issueCounter;
2195
+ const identifier = `${company.issuePrefix}-${issueNumber}`;
2196
+ const values = {
2197
+ ...issueData,
2198
+ requestDepth: clampIssueRequestDepth(issueData.requestDepth),
2199
+ originKind: issueData.originKind ?? "manual",
2200
+ goalId: resolveIssueGoalId({
2201
+ projectId: issueData.projectId,
2202
+ goalId: issueData.goalId,
2203
+ projectGoalId,
2204
+ defaultGoalId: defaultCompanyGoal?.id ?? null,
2205
+ }),
2206
+ ...(projectWorkspaceId ? { projectWorkspaceId } : {}),
2207
+ ...(executionWorkspaceId ? { executionWorkspaceId } : {}),
2208
+ ...(executionWorkspacePreference ? { executionWorkspacePreference } : {}),
2209
+ ...(executionWorkspaceSettings ? { executionWorkspaceSettings } : {}),
2210
+ companyId,
2211
+ issueNumber,
2212
+ identifier,
2213
+ };
2214
+ if (values.status === "in_progress" && !values.startedAt) {
2215
+ values.startedAt = new Date();
2216
+ }
2217
+ if (values.status === "done") {
2218
+ values.completedAt = new Date();
2219
+ }
2220
+ if (values.status === "cancelled") {
2221
+ values.cancelledAt = new Date();
2222
+ }
2223
+ Object.assign(values, buildInitialIssueMonitorFields({
2224
+ policy: normalizeIssueExecutionPolicy(issueData.executionPolicy ?? null),
2225
+ status: values.status ?? "backlog",
2226
+ assigneeAgentId: values.assigneeAgentId ?? null,
2227
+ assigneeUserId: values.assigneeUserId ?? null,
2228
+ }));
2229
+ const [issue] = await tx.insert(issues).values(values).returning();
2230
+ if (inputLabelIds) {
2231
+ await syncIssueLabels(issue.id, companyId, inputLabelIds, tx);
2232
+ }
2233
+ if (blockedByIssueIds !== undefined) {
2234
+ await syncBlockedByIssueIds(issue.id, companyId, blockedByIssueIds, {
2235
+ agentId: issueData.createdByAgentId ?? null,
2236
+ userId: issueData.createdByUserId ?? null,
2237
+ }, tx);
2238
+ }
2239
+ const [enriched] = await withIssueLabels(tx, [issue]);
2240
+ return enriched;
2241
+ });
2242
+ },
2243
+ update: async (id, data, dbOrTx = db) => {
2244
+ const existing = await dbOrTx
2245
+ .select()
2246
+ .from(issues)
2247
+ .where(eq(issues.id, id))
2248
+ .then((rows) => rows[0] ?? null);
2249
+ if (!existing)
2250
+ return null;
2251
+ const { labelIds: nextLabelIds, blockedByIssueIds, actorAgentId, actorUserId, ...issueData } = data;
2252
+ const isolatedWorkspacesEnabled = (await instanceSettings.getExperimental()).enableIsolatedWorkspaces;
2253
+ if (!isolatedWorkspacesEnabled) {
2254
+ delete issueData.executionWorkspaceId;
2255
+ delete issueData.executionWorkspacePreference;
2256
+ delete issueData.executionWorkspaceSettings;
2257
+ }
2258
+ if (issueData.status) {
2259
+ assertTransition(existing.status, issueData.status);
2260
+ }
2261
+ const patch = {
2262
+ ...issueData,
2263
+ updatedAt: new Date(),
2264
+ };
2265
+ if (issueData.requestDepth !== undefined) {
2266
+ patch.requestDepth = clampIssueRequestDepth(issueData.requestDepth);
2267
+ }
2268
+ const nextAssigneeAgentId = issueData.assigneeAgentId !== undefined ? issueData.assigneeAgentId : existing.assigneeAgentId;
2269
+ const nextAssigneeUserId = issueData.assigneeUserId !== undefined ? issueData.assigneeUserId : existing.assigneeUserId;
2270
+ if (nextAssigneeAgentId && nextAssigneeUserId) {
2271
+ throw unprocessable("Issue can only have one assignee");
2272
+ }
2273
+ if (patch.status === "in_progress" && !nextAssigneeAgentId && !nextAssigneeUserId) {
2274
+ throw unprocessable("in_progress issues require an assignee");
2275
+ }
2276
+ if (patch.status === "in_progress") {
2277
+ const unresolvedBlockerIssueIds = blockedByIssueIds !== undefined
2278
+ ? await listUnresolvedBlockerIssueIds(dbOrTx, existing.companyId, blockedByIssueIds)
2279
+ : (await listIssueDependencyReadinessMap(dbOrTx, existing.companyId, [id])).get(id)?.unresolvedBlockerIssueIds ?? [];
2280
+ if (unresolvedBlockerIssueIds.length > 0) {
2281
+ throw unprocessable("Issue is blocked by unresolved blockers", { unresolvedBlockerIssueIds });
2282
+ }
2283
+ }
2284
+ if (issueData.assigneeAgentId) {
2285
+ await assertAssignableAgent(existing.companyId, issueData.assigneeAgentId);
2286
+ }
2287
+ if (issueData.assigneeUserId) {
2288
+ await assertAssignableUser(existing.companyId, issueData.assigneeUserId);
2289
+ }
2290
+ const nextProjectId = issueData.projectId !== undefined ? issueData.projectId : existing.projectId;
2291
+ const nextProjectWorkspaceId = issueData.projectWorkspaceId !== undefined ? issueData.projectWorkspaceId : existing.projectWorkspaceId;
2292
+ const nextExecutionWorkspaceId = issueData.executionWorkspaceId !== undefined ? issueData.executionWorkspaceId : existing.executionWorkspaceId;
2293
+ const nextExecutionWorkspacePreference = issueData.executionWorkspacePreference !== undefined
2294
+ ? issueData.executionWorkspacePreference
2295
+ : existing.executionWorkspacePreference;
2296
+ const nextExecutionWorkspaceSettings = issueData.executionWorkspaceSettings !== undefined
2297
+ ? parseIssueExecutionWorkspaceSettings(issueData.executionWorkspaceSettings)
2298
+ : parseIssueExecutionWorkspaceSettings(existing.executionWorkspaceSettings);
2299
+ if (nextProjectWorkspaceId) {
2300
+ await assertValidProjectWorkspace(existing.companyId, nextProjectId, nextProjectWorkspaceId);
2301
+ }
2302
+ if (nextExecutionWorkspaceId) {
2303
+ await assertValidExecutionWorkspace(existing.companyId, nextProjectId, nextExecutionWorkspaceId);
2304
+ }
2305
+ applyStatusSideEffects(issueData.status, patch);
2306
+ if (issueData.status && issueData.status !== "done") {
2307
+ patch.completedAt = null;
2308
+ }
2309
+ if (issueData.status && issueData.status !== "cancelled") {
2310
+ patch.cancelledAt = null;
2311
+ }
2312
+ if (issueData.status && issueData.status !== "in_progress") {
2313
+ patch.checkoutRunId = null;
2314
+ // Fix B: also clear the execution lock when leaving in_progress
2315
+ patch.executionRunId = null;
2316
+ patch.executionAgentNameKey = null;
2317
+ patch.executionLockedAt = null;
2318
+ }
2319
+ if ((issueData.assigneeAgentId !== undefined && issueData.assigneeAgentId !== existing.assigneeAgentId) ||
2320
+ (issueData.assigneeUserId !== undefined && issueData.assigneeUserId !== existing.assigneeUserId)) {
2321
+ patch.checkoutRunId = null;
2322
+ // Fix B: clear execution lock on reassignment, matching checkoutRunId clear
2323
+ patch.executionRunId = null;
2324
+ patch.executionAgentNameKey = null;
2325
+ patch.executionLockedAt = null;
2326
+ }
2327
+ const runUpdate = async (tx) => {
2328
+ const defaultCompanyGoal = await getDefaultCompanyGoal(tx, existing.companyId);
2329
+ const [currentProjectGoalId, nextProjectGoalId] = await Promise.all([
2330
+ getProjectDefaultGoalId(tx, existing.companyId, existing.projectId),
2331
+ getProjectDefaultGoalId(tx, existing.companyId, issueData.projectId !== undefined ? issueData.projectId : existing.projectId),
2332
+ ]);
2333
+ // Mirror the create() path: when the assignee changes to a non-null
2334
+ // agent, default the issue's executionWorkspaceSettings.environmentId
2335
+ // to the new agent's defaultEnvironmentId. Skip when:
2336
+ // - this update explicitly sets executionWorkspaceSettings.environmentId
2337
+ // (caller is making a deliberate override; respect it), OR
2338
+ // - the project policy already specifies an environmentId (project
2339
+ // policy must win over agent default per the documented priority
2340
+ // order in resolveExecutionWorkspaceEnvironmentId), OR
2341
+ // - the issue already has an environmentId that was *not* the prior
2342
+ // assignee's default (i.e., the operator set it explicitly in an
2343
+ // earlier update; preserve their choice). When the existing
2344
+ // environmentId matches the prior assignee's default, treat it as
2345
+ // auto-promoted and refresh it to the new assignee's default.
2346
+ const assigneeChanged = issueData.assigneeAgentId !== undefined &&
2347
+ issueData.assigneeAgentId !== null &&
2348
+ issueData.assigneeAgentId !== existing.assigneeAgentId;
2349
+ const explicitEnvInThisUpdate = issueData.executionWorkspaceSettings !== undefined &&
2350
+ Object.prototype.hasOwnProperty.call(parseObject(issueData.executionWorkspaceSettings), "environmentId");
2351
+ if (assigneeChanged && isolatedWorkspacesEnabled && !explicitEnvInThisUpdate) {
2352
+ let projectHasEnvironmentSelection = false;
2353
+ if (nextProjectId) {
2354
+ const projectRow = await tx
2355
+ .select({ executionWorkspacePolicy: projects.executionWorkspacePolicy })
2356
+ .from(projects)
2357
+ .where(and(eq(projects.id, nextProjectId), eq(projects.companyId, existing.companyId)))
2358
+ .then((rows) => rows[0] ?? null);
2359
+ const projectPolicy = parseProjectExecutionWorkspacePolicy(projectRow?.executionWorkspacePolicy);
2360
+ projectHasEnvironmentSelection = projectPolicy?.environmentId !== undefined;
2361
+ }
2362
+ if (!projectHasEnvironmentSelection) {
2363
+ const baseSettings = nextExecutionWorkspaceSettings == null
2364
+ ? {}
2365
+ : parseObject(nextExecutionWorkspaceSettings);
2366
+ const existingEnvId = typeof baseSettings.environmentId === "string"
2367
+ ? baseSettings.environmentId
2368
+ : null;
2369
+ const agentRows = await tx
2370
+ .select({ id: agents.id, defaultEnvironmentId: agents.defaultEnvironmentId })
2371
+ .from(agents)
2372
+ .where(and(eq(agents.companyId, existing.companyId), inArray(agents.id, [issueData.assigneeAgentId, existing.assigneeAgentId].filter((value) => typeof value === "string"))));
2373
+ const newAssignee = agentRows.find((row) => row.id === issueData.assigneeAgentId);
2374
+ const previousAssignee = existing.assigneeAgentId
2375
+ ? agentRows.find((row) => row.id === existing.assigneeAgentId)
2376
+ : null;
2377
+ const newDefaultEnvId = typeof newAssignee?.defaultEnvironmentId === "string" && newAssignee.defaultEnvironmentId.length > 0
2378
+ ? newAssignee.defaultEnvironmentId
2379
+ : null;
2380
+ const previousDefaultEnvId = typeof previousAssignee?.defaultEnvironmentId === "string" && previousAssignee.defaultEnvironmentId.length > 0
2381
+ ? previousAssignee.defaultEnvironmentId
2382
+ : null;
2383
+ const existingEnvWasAutoPromoted = existingEnvId === null ||
2384
+ (previousDefaultEnvId !== null && existingEnvId === previousDefaultEnvId);
2385
+ if (newDefaultEnvId && existingEnvWasAutoPromoted) {
2386
+ patch.executionWorkspaceSettings = {
2387
+ ...baseSettings,
2388
+ environmentId: newDefaultEnvId,
2389
+ };
2390
+ }
2391
+ }
2392
+ }
2393
+ patch.goalId = resolveNextIssueGoalId({
2394
+ currentProjectId: existing.projectId,
2395
+ currentGoalId: existing.goalId,
2396
+ currentProjectGoalId,
2397
+ projectId: issueData.projectId,
2398
+ goalId: issueData.goalId,
2399
+ projectGoalId: nextProjectGoalId,
2400
+ defaultGoalId: defaultCompanyGoal?.id ?? null,
2401
+ });
2402
+ const updated = await tx
2403
+ .update(issues)
2404
+ .set(patch)
2405
+ .where(eq(issues.id, id))
2406
+ .returning()
2407
+ .then((rows) => rows[0] ?? null);
2408
+ if (!updated)
2409
+ return null;
2410
+ if (nextLabelIds !== undefined) {
2411
+ await syncIssueLabels(updated.id, existing.companyId, nextLabelIds, tx);
2412
+ }
2413
+ if (blockedByIssueIds !== undefined) {
2414
+ await syncBlockedByIssueIds(updated.id, existing.companyId, blockedByIssueIds, {
2415
+ agentId: actorAgentId ?? null,
2416
+ userId: actorUserId ?? null,
2417
+ }, tx);
2418
+ }
2419
+ if (issueData.executionWorkspaceSettings !== undefined &&
2420
+ nextExecutionWorkspaceId &&
2421
+ nextExecutionWorkspacePreference === "reuse_existing") {
2422
+ const workspace = await tx
2423
+ .select({
2424
+ id: executionWorkspaces.id,
2425
+ metadata: executionWorkspaces.metadata,
2426
+ })
2427
+ .from(executionWorkspaces)
2428
+ .where(and(eq(executionWorkspaces.id, nextExecutionWorkspaceId), eq(executionWorkspaces.companyId, existing.companyId)))
2429
+ .then((rows) => rows[0] ?? null);
2430
+ if (workspace) {
2431
+ await tx
2432
+ .update(executionWorkspaces)
2433
+ .set({
2434
+ metadata: mergeExecutionWorkspaceConfig(workspace.metadata ?? null, buildReusedExecutionWorkspaceConfigPatchFromIssueSettings(nextExecutionWorkspaceSettings)),
2435
+ updatedAt: new Date(),
2436
+ })
2437
+ .where(eq(executionWorkspaces.id, workspace.id));
2438
+ }
2439
+ }
2440
+ const [enriched] = await withIssueLabels(tx, [updated]);
2441
+ return enriched;
2442
+ };
2443
+ return dbOrTx === db ? db.transaction(runUpdate) : runUpdate(dbOrTx);
2444
+ },
2445
+ clearExecutionWorkspaceEnvironmentSelection: async (companyId, environmentId) => {
2446
+ const rows = await db
2447
+ .select({
2448
+ id: issues.id,
2449
+ executionWorkspaceSettings: issues.executionWorkspaceSettings,
2450
+ })
2451
+ .from(issues)
2452
+ .where(eq(issues.companyId, companyId));
2453
+ let cleared = 0;
2454
+ for (const row of rows) {
2455
+ const settings = parseIssueExecutionWorkspaceSettings(row.executionWorkspaceSettings);
2456
+ if (settings?.environmentId !== environmentId)
2457
+ continue;
2458
+ await db
2459
+ .update(issues)
2460
+ .set({
2461
+ executionWorkspaceSettings: {
2462
+ ...settings,
2463
+ environmentId: null,
2464
+ },
2465
+ updatedAt: new Date(),
2466
+ })
2467
+ .where(eq(issues.id, row.id));
2468
+ cleared += 1;
2469
+ }
2470
+ return cleared;
2471
+ },
2472
+ remove: (id) => db.transaction(async (tx) => {
2473
+ const attachmentAssetIds = await tx
2474
+ .select({ assetId: issueAttachments.assetId })
2475
+ .from(issueAttachments)
2476
+ .where(eq(issueAttachments.issueId, id));
2477
+ const issueDocumentIds = await tx
2478
+ .select({ documentId: issueDocuments.documentId })
2479
+ .from(issueDocuments)
2480
+ .where(eq(issueDocuments.issueId, id));
2481
+ const removedIssue = await tx
2482
+ .delete(issues)
2483
+ .where(eq(issues.id, id))
2484
+ .returning()
2485
+ .then((rows) => rows[0] ?? null);
2486
+ if (removedIssue && attachmentAssetIds.length > 0) {
2487
+ await tx
2488
+ .delete(assets)
2489
+ .where(inArray(assets.id, attachmentAssetIds.map((row) => row.assetId)));
2490
+ }
2491
+ if (removedIssue && issueDocumentIds.length > 0) {
2492
+ await tx
2493
+ .delete(documents)
2494
+ .where(inArray(documents.id, issueDocumentIds.map((row) => row.documentId)));
2495
+ }
2496
+ if (!removedIssue)
2497
+ return null;
2498
+ const [enriched] = await withIssueLabels(tx, [removedIssue]);
2499
+ return enriched;
2500
+ }),
2501
+ checkout: async (id, agentId, expectedStatuses, checkoutRunId) => {
2502
+ const issueCompany = await db
2503
+ .select({ companyId: issues.companyId })
2504
+ .from(issues)
2505
+ .where(eq(issues.id, id))
2506
+ .then((rows) => rows[0] ?? null);
2507
+ if (!issueCompany)
2508
+ throw notFound("Issue not found");
2509
+ await assertAssignableAgent(issueCompany.companyId, agentId);
2510
+ const now = new Date();
2511
+ const activePauseHold = await treeControlSvc.getActivePauseHoldGate(issueCompany.companyId, id);
2512
+ if (activePauseHold &&
2513
+ !(await isTreeHoldInteractionCheckoutAllowed(issueCompany.companyId, checkoutRunId, activePauseHold))) {
2514
+ throw conflict("Issue checkout blocked by active subtree pause hold", {
2515
+ issueId: id,
2516
+ holdId: activePauseHold.holdId,
2517
+ rootIssueId: activePauseHold.rootIssueId,
2518
+ mode: activePauseHold.mode,
2519
+ securityPrinciples: ["Complete Mediation", "Fail Securely", "Secure Defaults"],
2520
+ });
2521
+ }
2522
+ await clearExecutionRunIfTerminal(id);
2523
+ const dependencyReadiness = await listIssueDependencyReadinessMap(db, issueCompany.companyId, [id]);
2524
+ const unresolvedBlockerIssueIds = dependencyReadiness.get(id)?.unresolvedBlockerIssueIds ?? [];
2525
+ if (unresolvedBlockerIssueIds.length > 0) {
2526
+ throw unprocessable("Issue is blocked by unresolved blockers", { unresolvedBlockerIssueIds });
2527
+ }
2528
+ const sameRunAssigneeCondition = checkoutRunId
2529
+ ? and(eq(issues.assigneeAgentId, agentId), or(isNull(issues.checkoutRunId), eq(issues.checkoutRunId, checkoutRunId)))
2530
+ : and(eq(issues.assigneeAgentId, agentId), isNull(issues.checkoutRunId));
2531
+ const executionLockCondition = checkoutRunId
2532
+ ? or(isNull(issues.executionRunId), eq(issues.executionRunId, checkoutRunId))
2533
+ : isNull(issues.executionRunId);
2534
+ const updated = await db
2535
+ .update(issues)
2536
+ .set({
2537
+ assigneeAgentId: agentId,
2538
+ assigneeUserId: null,
2539
+ checkoutRunId,
2540
+ executionRunId: checkoutRunId,
2541
+ status: "in_progress",
2542
+ startedAt: now,
2543
+ updatedAt: now,
2544
+ })
2545
+ .where(and(eq(issues.id, id), inArray(issues.status, expectedStatuses), or(isNull(issues.assigneeAgentId), sameRunAssigneeCondition), executionLockCondition))
2546
+ .returning()
2547
+ .then((rows) => rows[0] ?? null);
2548
+ if (updated) {
2549
+ const [enriched] = await withIssueLabels(db, [updated]);
2550
+ return enriched;
2551
+ }
2552
+ const current = await db
2553
+ .select({
2554
+ id: issues.id,
2555
+ status: issues.status,
2556
+ assigneeAgentId: issues.assigneeAgentId,
2557
+ checkoutRunId: issues.checkoutRunId,
2558
+ executionRunId: issues.executionRunId,
2559
+ })
2560
+ .from(issues)
2561
+ .where(eq(issues.id, id))
2562
+ .then((rows) => rows[0] ?? null);
2563
+ if (!current)
2564
+ throw notFound("Issue not found");
2565
+ if (current.assigneeAgentId === agentId &&
2566
+ current.status === "in_progress" &&
2567
+ current.checkoutRunId == null &&
2568
+ (current.executionRunId == null || current.executionRunId === checkoutRunId) &&
2569
+ checkoutRunId) {
2570
+ const adopted = await db
2571
+ .update(issues)
2572
+ .set({
2573
+ checkoutRunId,
2574
+ executionRunId: checkoutRunId,
2575
+ updatedAt: new Date(),
2576
+ })
2577
+ .where(and(eq(issues.id, id), eq(issues.status, "in_progress"), eq(issues.assigneeAgentId, agentId), isNull(issues.checkoutRunId), or(isNull(issues.executionRunId), eq(issues.executionRunId, checkoutRunId))))
2578
+ .returning()
2579
+ .then((rows) => rows[0] ?? null);
2580
+ if (adopted)
2581
+ return adopted;
2582
+ }
2583
+ if (checkoutRunId &&
2584
+ current.assigneeAgentId === agentId &&
2585
+ current.status === "in_progress" &&
2586
+ current.checkoutRunId &&
2587
+ current.checkoutRunId !== checkoutRunId) {
2588
+ const adopted = await adoptStaleCheckoutRun({
2589
+ issueId: id,
2590
+ actorAgentId: agentId,
2591
+ actorRunId: checkoutRunId,
2592
+ expectedCheckoutRunId: current.checkoutRunId,
2593
+ });
2594
+ if (adopted) {
2595
+ const row = await db.select().from(issues).where(eq(issues.id, id)).then((rows) => rows[0] ?? null);
2596
+ if (!row)
2597
+ throw notFound("Issue not found");
2598
+ const [enriched] = await withIssueLabels(db, [row]);
2599
+ return enriched;
2600
+ }
2601
+ }
2602
+ // If this run already owns it and it's in_progress, return it (no self-409)
2603
+ if (current.assigneeAgentId === agentId &&
2604
+ current.status === "in_progress" &&
2605
+ sameRunLock(current.checkoutRunId, checkoutRunId)) {
2606
+ const row = await db.select().from(issues).where(eq(issues.id, id)).then((rows) => rows[0] ?? null);
2607
+ if (!row)
2608
+ throw notFound("Issue not found");
2609
+ const [enriched] = await withIssueLabels(db, [row]);
2610
+ return enriched;
2611
+ }
2612
+ throw conflict("Issue checkout conflict", {
2613
+ issueId: current.id,
2614
+ status: current.status,
2615
+ assigneeAgentId: current.assigneeAgentId,
2616
+ checkoutRunId: current.checkoutRunId,
2617
+ executionRunId: current.executionRunId,
2618
+ });
2619
+ },
2620
+ assertCheckoutOwner: async (id, actorAgentId, actorRunId) => {
2621
+ await clearExecutionRunIfTerminal(id);
2622
+ const current = await db
2623
+ .select({
2624
+ id: issues.id,
2625
+ status: issues.status,
2626
+ assigneeAgentId: issues.assigneeAgentId,
2627
+ checkoutRunId: issues.checkoutRunId,
2628
+ executionRunId: issues.executionRunId,
2629
+ })
2630
+ .from(issues)
2631
+ .where(eq(issues.id, id))
2632
+ .then((rows) => rows[0] ?? null);
2633
+ if (!current)
2634
+ throw notFound("Issue not found");
2635
+ if (current.status === "in_progress" &&
2636
+ current.assigneeAgentId === actorAgentId &&
2637
+ sameRunLock(current.checkoutRunId, actorRunId)) {
2638
+ return { ...current, adoptedFromRunId: null };
2639
+ }
2640
+ if (actorRunId &&
2641
+ current.status === "in_progress" &&
2642
+ current.assigneeAgentId === actorAgentId &&
2643
+ current.checkoutRunId == null &&
2644
+ (current.executionRunId == null || current.executionRunId === actorRunId)) {
2645
+ const adopted = await adoptUnownedCheckoutRun({
2646
+ issueId: id,
2647
+ actorAgentId,
2648
+ actorRunId,
2649
+ });
2650
+ if (adopted) {
2651
+ return {
2652
+ ...adopted,
2653
+ adoptedFromRunId: null,
2654
+ };
2655
+ }
2656
+ }
2657
+ if (actorRunId &&
2658
+ current.status === "in_progress" &&
2659
+ current.assigneeAgentId === actorAgentId &&
2660
+ current.checkoutRunId &&
2661
+ current.checkoutRunId !== actorRunId) {
2662
+ const adopted = await adoptStaleCheckoutRun({
2663
+ issueId: id,
2664
+ actorAgentId,
2665
+ actorRunId,
2666
+ expectedCheckoutRunId: current.checkoutRunId,
2667
+ });
2668
+ if (adopted) {
2669
+ return {
2670
+ ...adopted,
2671
+ adoptedFromRunId: current.checkoutRunId,
2672
+ };
2673
+ }
2674
+ }
2675
+ throw conflict("Issue run ownership conflict", {
2676
+ issueId: current.id,
2677
+ status: current.status,
2678
+ assigneeAgentId: current.assigneeAgentId,
2679
+ checkoutRunId: current.checkoutRunId,
2680
+ executionRunId: current.executionRunId,
2681
+ actorAgentId,
2682
+ actorRunId,
2683
+ });
2684
+ },
2685
+ release: async (id, actorAgentId, actorRunId) => {
2686
+ await clearExecutionRunIfTerminal(id);
2687
+ const existing = await db
2688
+ .select()
2689
+ .from(issues)
2690
+ .where(eq(issues.id, id))
2691
+ .then((rows) => rows[0] ?? null);
2692
+ if (!existing)
2693
+ return null;
2694
+ if (actorAgentId && existing.assigneeAgentId && existing.assigneeAgentId !== actorAgentId) {
2695
+ throw conflict("Only assignee can release issue");
2696
+ }
2697
+ if (actorAgentId &&
2698
+ existing.status === "in_progress" &&
2699
+ existing.assigneeAgentId === actorAgentId &&
2700
+ existing.checkoutRunId &&
2701
+ !sameRunLock(existing.checkoutRunId, actorRunId ?? null)) {
2702
+ const stale = await isTerminalOrMissingHeartbeatRun(existing.checkoutRunId);
2703
+ if (!stale) {
2704
+ throw conflict("Only checkout run can release issue", {
2705
+ issueId: existing.id,
2706
+ assigneeAgentId: existing.assigneeAgentId,
2707
+ checkoutRunId: existing.checkoutRunId,
2708
+ actorRunId: actorRunId ?? null,
2709
+ });
2710
+ }
2711
+ }
2712
+ const updated = await db
2713
+ .update(issues)
2714
+ .set({
2715
+ status: "todo",
2716
+ assigneeAgentId: null,
2717
+ checkoutRunId: null,
2718
+ executionRunId: null,
2719
+ executionAgentNameKey: null,
2720
+ executionLockedAt: null,
2721
+ updatedAt: new Date(),
2722
+ })
2723
+ .where(eq(issues.id, id))
2724
+ .returning()
2725
+ .then((rows) => rows[0] ?? null);
2726
+ if (!updated)
2727
+ return null;
2728
+ const [enriched] = await withIssueLabels(db, [updated]);
2729
+ return enriched;
2730
+ },
2731
+ adminForceRelease: async (id, options = {}) => db.transaction(async (tx) => {
2732
+ await tx.execute(sql `select ${issues.id} from ${issues} where ${issues.id} = ${id} for update`);
2733
+ const existing = await tx
2734
+ .select({
2735
+ id: issues.id,
2736
+ checkoutRunId: issues.checkoutRunId,
2737
+ executionRunId: issues.executionRunId,
2738
+ })
2739
+ .from(issues)
2740
+ .where(eq(issues.id, id))
2741
+ .then((rows) => rows[0] ?? null);
2742
+ if (!existing)
2743
+ return null;
2744
+ const patch = {
2745
+ checkoutRunId: null,
2746
+ executionRunId: null,
2747
+ executionAgentNameKey: null,
2748
+ executionLockedAt: null,
2749
+ updatedAt: new Date(),
2750
+ };
2751
+ if (options.clearAssignee) {
2752
+ patch.assigneeAgentId = null;
2753
+ }
2754
+ const updated = await tx
2755
+ .update(issues)
2756
+ .set(patch)
2757
+ .where(eq(issues.id, id))
2758
+ .returning()
2759
+ .then((rows) => rows[0] ?? null);
2760
+ if (!updated)
2761
+ return null;
2762
+ const [enriched] = await withIssueLabels(tx, [updated]);
2763
+ return {
2764
+ issue: enriched,
2765
+ previous: {
2766
+ checkoutRunId: existing.checkoutRunId,
2767
+ executionRunId: existing.executionRunId,
2768
+ },
2769
+ };
2770
+ }),
2771
+ listLabels: (companyId) => db.select().from(labels).where(eq(labels.companyId, companyId)).orderBy(asc(labels.name), asc(labels.id)),
2772
+ getLabelById: (id) => db
2773
+ .select()
2774
+ .from(labels)
2775
+ .where(eq(labels.id, id))
2776
+ .then((rows) => rows[0] ?? null),
2777
+ createLabel: async (companyId, data) => {
2778
+ const [created] = await db
2779
+ .insert(labels)
2780
+ .values({
2781
+ companyId,
2782
+ name: data.name.trim(),
2783
+ color: data.color,
2784
+ })
2785
+ .returning();
2786
+ return created;
2787
+ },
2788
+ deleteLabel: async (id) => db
2789
+ .delete(labels)
2790
+ .where(eq(labels.id, id))
2791
+ .returning()
2792
+ .then((rows) => rows[0] ?? null),
2793
+ listComments: async (issueId, opts) => {
2794
+ const order = opts?.order === "asc" ? "asc" : "desc";
2795
+ const afterCommentId = opts?.afterCommentId?.trim() || null;
2796
+ const limit = opts?.limit && opts.limit > 0
2797
+ ? Math.min(Math.floor(opts.limit), MAX_ISSUE_COMMENT_PAGE_LIMIT)
2798
+ : null;
2799
+ const conditions = [eq(issueComments.issueId, issueId)];
2800
+ if (afterCommentId) {
2801
+ const anchor = await db
2802
+ .select({
2803
+ id: issueComments.id,
2804
+ createdAt: issueComments.createdAt,
2805
+ })
2806
+ .from(issueComments)
2807
+ .where(and(eq(issueComments.issueId, issueId), eq(issueComments.id, afterCommentId)))
2808
+ .then((rows) => rows[0] ?? null);
2809
+ if (!anchor)
2810
+ return [];
2811
+ conditions.push(order === "asc"
2812
+ ? or(gt(issueComments.createdAt, anchor.createdAt), and(eq(issueComments.createdAt, anchor.createdAt), gt(issueComments.id, anchor.id)))
2813
+ : or(lt(issueComments.createdAt, anchor.createdAt), and(eq(issueComments.createdAt, anchor.createdAt), lt(issueComments.id, anchor.id))));
2814
+ }
2815
+ const query = db
2816
+ .select()
2817
+ .from(issueComments)
2818
+ .where(and(...conditions))
2819
+ .orderBy(order === "asc" ? asc(issueComments.createdAt) : desc(issueComments.createdAt), order === "asc" ? asc(issueComments.id) : desc(issueComments.id));
2820
+ const comments = limit ? await query.limit(limit) : await query;
2821
+ const { censorUsernameInLogs } = await instanceSettings.getGeneral();
2822
+ return comments.map((comment) => redactIssueComment(comment, censorUsernameInLogs));
2823
+ },
2824
+ getCommentCursor: async (issueId) => {
2825
+ const [latest, countRow] = await Promise.all([
2826
+ db
2827
+ .select({
2828
+ latestCommentId: issueComments.id,
2829
+ latestCommentAt: issueComments.createdAt,
2830
+ })
2831
+ .from(issueComments)
2832
+ .where(eq(issueComments.issueId, issueId))
2833
+ .orderBy(desc(issueComments.createdAt), desc(issueComments.id))
2834
+ .limit(1)
2835
+ .then((rows) => rows[0] ?? null),
2836
+ db
2837
+ .select({
2838
+ totalComments: sql `count(*)::int`,
2839
+ })
2840
+ .from(issueComments)
2841
+ .where(eq(issueComments.issueId, issueId))
2842
+ .then((rows) => rows[0] ?? null),
2843
+ ]);
2844
+ return {
2845
+ totalComments: Number(countRow?.totalComments ?? 0),
2846
+ latestCommentId: latest?.latestCommentId ?? null,
2847
+ latestCommentAt: latest?.latestCommentAt ?? null,
2848
+ };
2849
+ },
2850
+ getComment: (commentId) => instanceSettings.getGeneral().then(({ censorUsernameInLogs }) => db
2851
+ .select()
2852
+ .from(issueComments)
2853
+ .where(eq(issueComments.id, commentId))
2854
+ .then((rows) => {
2855
+ const comment = rows[0] ?? null;
2856
+ return comment ? redactIssueComment(comment, censorUsernameInLogs) : null;
2857
+ })),
2858
+ removeComment: async (commentId) => {
2859
+ const currentUserRedactionOptions = {
2860
+ enabled: (await instanceSettings.getGeneral()).censorUsernameInLogs,
2861
+ };
2862
+ return db.transaction(async (tx) => {
2863
+ const [comment] = await tx
2864
+ .delete(issueComments)
2865
+ .where(eq(issueComments.id, commentId))
2866
+ .returning();
2867
+ if (!comment)
2868
+ return null;
2869
+ await tx
2870
+ .update(issues)
2871
+ .set({ updatedAt: new Date() })
2872
+ .where(eq(issues.id, comment.issueId));
2873
+ return redactIssueComment(comment, currentUserRedactionOptions.enabled);
2874
+ });
2875
+ },
2876
+ addComment: async (issueId, body, actor, options) => {
2877
+ const issue = await db
2878
+ .select({ companyId: issues.companyId })
2879
+ .from(issues)
2880
+ .where(eq(issues.id, issueId))
2881
+ .then((rows) => rows[0] ?? null);
2882
+ if (!issue)
2883
+ throw notFound("Issue not found");
2884
+ const currentUserRedactionOptions = {
2885
+ enabled: (await instanceSettings.getGeneral()).censorUsernameInLogs,
2886
+ };
2887
+ const redactedBody = redactCurrentUserText(body, currentUserRedactionOptions);
2888
+ const authorType = issueCommentAuthorTypeSchema.parse(options?.authorType ?? (actor.agentId ? "agent" : actor.userId ? "user" : "system"));
2889
+ assertIssueCommentAuthorTypeAllowed(actor, authorType);
2890
+ const presentation = issueCommentPresentationSchema.nullable().parse(options?.presentation ?? null);
2891
+ const metadata = issueCommentMetadataSchema.nullable().parse(options?.metadata ?? null);
2892
+ const createdAt = options?.createdAt ? new Date(options.createdAt) : null;
2893
+ const [comment] = await db
2894
+ .insert(issueComments)
2895
+ .values({
2896
+ companyId: issue.companyId,
2897
+ issueId,
2898
+ authorAgentId: actor.agentId ?? null,
2899
+ authorUserId: actor.userId ?? null,
2900
+ authorType,
2901
+ createdByRunId: actor.runId ?? null,
2902
+ body: redactedBody,
2903
+ presentation,
2904
+ metadata,
2905
+ ...(createdAt && !Number.isNaN(createdAt.getTime()) ? { createdAt } : {}),
2906
+ })
2907
+ .returning();
2908
+ // Update issue's updatedAt so comment activity is reflected in recency sorting
2909
+ await db
2910
+ .update(issues)
2911
+ .set({ updatedAt: new Date() })
2912
+ .where(eq(issues.id, issueId));
2913
+ return redactIssueComment(comment, currentUserRedactionOptions.enabled);
2914
+ },
2915
+ createAttachment: async (input) => {
2916
+ const issue = await db
2917
+ .select({ id: issues.id, companyId: issues.companyId })
2918
+ .from(issues)
2919
+ .where(eq(issues.id, input.issueId))
2920
+ .then((rows) => rows[0] ?? null);
2921
+ if (!issue)
2922
+ throw notFound("Issue not found");
2923
+ if (input.issueCommentId) {
2924
+ const comment = await db
2925
+ .select({ id: issueComments.id, companyId: issueComments.companyId, issueId: issueComments.issueId })
2926
+ .from(issueComments)
2927
+ .where(eq(issueComments.id, input.issueCommentId))
2928
+ .then((rows) => rows[0] ?? null);
2929
+ if (!comment)
2930
+ throw notFound("Issue comment not found");
2931
+ if (comment.companyId !== issue.companyId || comment.issueId !== issue.id) {
2932
+ throw unprocessable("Attachment comment must belong to same issue and company");
2933
+ }
2934
+ }
2935
+ return db.transaction(async (tx) => {
2936
+ const [asset] = await tx
2937
+ .insert(assets)
2938
+ .values({
2939
+ companyId: issue.companyId,
2940
+ provider: input.provider,
2941
+ objectKey: input.objectKey,
2942
+ contentType: input.contentType,
2943
+ byteSize: input.byteSize,
2944
+ sha256: input.sha256,
2945
+ originalFilename: input.originalFilename ?? null,
2946
+ createdByAgentId: input.createdByAgentId ?? null,
2947
+ createdByUserId: input.createdByUserId ?? null,
2948
+ })
2949
+ .returning();
2950
+ const [attachment] = await tx
2951
+ .insert(issueAttachments)
2952
+ .values({
2953
+ companyId: issue.companyId,
2954
+ issueId: issue.id,
2955
+ assetId: asset.id,
2956
+ issueCommentId: input.issueCommentId ?? null,
2957
+ })
2958
+ .returning();
2959
+ return {
2960
+ id: attachment.id,
2961
+ companyId: attachment.companyId,
2962
+ issueId: attachment.issueId,
2963
+ issueCommentId: attachment.issueCommentId,
2964
+ assetId: attachment.assetId,
2965
+ provider: asset.provider,
2966
+ objectKey: asset.objectKey,
2967
+ contentType: asset.contentType,
2968
+ byteSize: asset.byteSize,
2969
+ sha256: asset.sha256,
2970
+ originalFilename: asset.originalFilename,
2971
+ createdByAgentId: asset.createdByAgentId,
2972
+ createdByUserId: asset.createdByUserId,
2973
+ createdAt: attachment.createdAt,
2974
+ updatedAt: attachment.updatedAt,
2975
+ };
2976
+ });
2977
+ },
2978
+ listAttachments: async (issueId) => db
2979
+ .select({
2980
+ id: issueAttachments.id,
2981
+ companyId: issueAttachments.companyId,
2982
+ issueId: issueAttachments.issueId,
2983
+ issueCommentId: issueAttachments.issueCommentId,
2984
+ assetId: issueAttachments.assetId,
2985
+ provider: assets.provider,
2986
+ objectKey: assets.objectKey,
2987
+ contentType: assets.contentType,
2988
+ byteSize: assets.byteSize,
2989
+ sha256: assets.sha256,
2990
+ originalFilename: assets.originalFilename,
2991
+ createdByAgentId: assets.createdByAgentId,
2992
+ createdByUserId: assets.createdByUserId,
2993
+ createdAt: issueAttachments.createdAt,
2994
+ updatedAt: issueAttachments.updatedAt,
2995
+ })
2996
+ .from(issueAttachments)
2997
+ .innerJoin(assets, eq(issueAttachments.assetId, assets.id))
2998
+ .where(eq(issueAttachments.issueId, issueId))
2999
+ .orderBy(desc(issueAttachments.createdAt)),
3000
+ getAttachmentById: async (id) => db
3001
+ .select({
3002
+ id: issueAttachments.id,
3003
+ companyId: issueAttachments.companyId,
3004
+ issueId: issueAttachments.issueId,
3005
+ issueCommentId: issueAttachments.issueCommentId,
3006
+ assetId: issueAttachments.assetId,
3007
+ provider: assets.provider,
3008
+ objectKey: assets.objectKey,
3009
+ contentType: assets.contentType,
3010
+ byteSize: assets.byteSize,
3011
+ sha256: assets.sha256,
3012
+ originalFilename: assets.originalFilename,
3013
+ createdByAgentId: assets.createdByAgentId,
3014
+ createdByUserId: assets.createdByUserId,
3015
+ createdAt: issueAttachments.createdAt,
3016
+ updatedAt: issueAttachments.updatedAt,
3017
+ })
3018
+ .from(issueAttachments)
3019
+ .innerJoin(assets, eq(issueAttachments.assetId, assets.id))
3020
+ .where(eq(issueAttachments.id, id))
3021
+ .then((rows) => rows[0] ?? null),
3022
+ removeAttachment: async (id) => db.transaction(async (tx) => {
3023
+ const existing = await tx
3024
+ .select({
3025
+ id: issueAttachments.id,
3026
+ companyId: issueAttachments.companyId,
3027
+ issueId: issueAttachments.issueId,
3028
+ issueCommentId: issueAttachments.issueCommentId,
3029
+ assetId: issueAttachments.assetId,
3030
+ provider: assets.provider,
3031
+ objectKey: assets.objectKey,
3032
+ contentType: assets.contentType,
3033
+ byteSize: assets.byteSize,
3034
+ sha256: assets.sha256,
3035
+ originalFilename: assets.originalFilename,
3036
+ createdByAgentId: assets.createdByAgentId,
3037
+ createdByUserId: assets.createdByUserId,
3038
+ createdAt: issueAttachments.createdAt,
3039
+ updatedAt: issueAttachments.updatedAt,
3040
+ })
3041
+ .from(issueAttachments)
3042
+ .innerJoin(assets, eq(issueAttachments.assetId, assets.id))
3043
+ .where(eq(issueAttachments.id, id))
3044
+ .then((rows) => rows[0] ?? null);
3045
+ if (!existing)
3046
+ return null;
3047
+ await tx.delete(issueAttachments).where(eq(issueAttachments.id, id));
3048
+ await tx.delete(assets).where(eq(assets.id, existing.assetId));
3049
+ return existing;
3050
+ }),
3051
+ findMentionedAgents: async (companyId, body) => {
3052
+ const re = /\B@([^\s@,!?.]+)/g;
3053
+ const tokens = new Set();
3054
+ let m;
3055
+ while ((m = re.exec(body)) !== null) {
3056
+ const normalized = normalizeAgentMentionToken(m[1]);
3057
+ if (normalized)
3058
+ tokens.add(normalized.toLowerCase());
3059
+ }
3060
+ const explicitAgentMentionIds = extractAgentMentionIds(body);
3061
+ if (tokens.size === 0 && explicitAgentMentionIds.length === 0)
3062
+ return [];
3063
+ const rows = await db.select({ id: agents.id, name: agents.name })
3064
+ .from(agents).where(eq(agents.companyId, companyId));
3065
+ const resolved = new Set(explicitAgentMentionIds);
3066
+ for (const agent of rows) {
3067
+ if (tokens.has(agent.name.toLowerCase())) {
3068
+ resolved.add(agent.id);
3069
+ }
3070
+ }
3071
+ return [...resolved];
3072
+ },
3073
+ findMentionedProjectIds: async (issueId, opts) => {
3074
+ const issue = await db
3075
+ .select({
3076
+ companyId: issues.companyId,
3077
+ title: issues.title,
3078
+ description: issues.description,
3079
+ })
3080
+ .from(issues)
3081
+ .where(eq(issues.id, issueId))
3082
+ .then((rows) => rows[0] ?? null);
3083
+ if (!issue)
3084
+ return [];
3085
+ const mentionedIds = new Set();
3086
+ for (const source of [issue.title, issue.description ?? ""]) {
3087
+ for (const projectId of extractProjectMentionIds(source)) {
3088
+ mentionedIds.add(projectId);
3089
+ }
3090
+ }
3091
+ if (opts?.includeCommentBodies !== false) {
3092
+ const comments = await db
3093
+ .select({ body: issueComments.body })
3094
+ .from(issueComments)
3095
+ .where(eq(issueComments.issueId, issueId));
3096
+ for (const comment of comments) {
3097
+ for (const projectId of extractProjectMentionIds(comment.body)) {
3098
+ mentionedIds.add(projectId);
3099
+ }
3100
+ }
3101
+ }
3102
+ if (mentionedIds.size === 0)
3103
+ return [];
3104
+ const rows = await db
3105
+ .select({ id: projects.id })
3106
+ .from(projects)
3107
+ .where(and(eq(projects.companyId, issue.companyId), inArray(projects.id, [...mentionedIds])));
3108
+ const valid = new Set(rows.map((row) => row.id));
3109
+ return [...mentionedIds].filter((projectId) => valid.has(projectId));
3110
+ },
3111
+ getAncestors: async (issueId) => {
3112
+ const raw = [];
3113
+ const visited = new Set([issueId]);
3114
+ const start = await db.select().from(issues).where(eq(issues.id, issueId)).then(r => r[0] ?? null);
3115
+ let currentId = start?.parentId ?? null;
3116
+ while (currentId && !visited.has(currentId) && raw.length < 50) {
3117
+ visited.add(currentId);
3118
+ const parent = await db.select({
3119
+ id: issues.id, identifier: issues.identifier, title: issues.title, description: issues.description,
3120
+ status: issues.status, priority: issues.priority,
3121
+ assigneeAgentId: issues.assigneeAgentId, projectId: issues.projectId,
3122
+ goalId: issues.goalId, parentId: issues.parentId,
3123
+ }).from(issues).where(eq(issues.id, currentId)).then(r => r[0] ?? null);
3124
+ if (!parent)
3125
+ break;
3126
+ raw.push({
3127
+ id: parent.id, identifier: parent.identifier ?? null, title: parent.title, description: parent.description ?? null,
3128
+ status: parent.status, priority: parent.priority,
3129
+ assigneeAgentId: parent.assigneeAgentId ?? null,
3130
+ projectId: parent.projectId ?? null, goalId: parent.goalId ?? null,
3131
+ });
3132
+ currentId = parent.parentId ?? null;
3133
+ }
3134
+ // Batch-fetch referenced projects and goals
3135
+ const projectIds = [...new Set(raw.map(a => a.projectId).filter((id) => id != null))];
3136
+ const goalIds = [...new Set(raw.map(a => a.goalId).filter((id) => id != null))];
3137
+ const projectMap = new Map();
3138
+ const goalMap = new Map();
3139
+ if (projectIds.length > 0) {
3140
+ const workspaceRows = await db
3141
+ .select()
3142
+ .from(projectWorkspaces)
3143
+ .where(inArray(projectWorkspaces.projectId, projectIds))
3144
+ .orderBy(desc(projectWorkspaces.isPrimary), asc(projectWorkspaces.createdAt), asc(projectWorkspaces.id));
3145
+ const workspaceMap = new Map();
3146
+ for (const workspace of workspaceRows) {
3147
+ const existing = workspaceMap.get(workspace.projectId);
3148
+ if (existing)
3149
+ existing.push(workspace);
3150
+ else
3151
+ workspaceMap.set(workspace.projectId, [workspace]);
3152
+ }
3153
+ const rows = await db.select({
3154
+ id: projects.id, name: projects.name, description: projects.description,
3155
+ status: projects.status, goalId: projects.goalId,
3156
+ }).from(projects).where(inArray(projects.id, projectIds));
3157
+ for (const r of rows) {
3158
+ const projectWorkspaceRows = workspaceMap.get(r.id) ?? [];
3159
+ const workspaces = projectWorkspaceRows.map((workspace) => ({
3160
+ id: workspace.id,
3161
+ companyId: workspace.companyId,
3162
+ projectId: workspace.projectId,
3163
+ name: workspace.name,
3164
+ cwd: workspace.cwd,
3165
+ repoUrl: workspace.repoUrl ?? null,
3166
+ repoRef: workspace.repoRef ?? null,
3167
+ metadata: workspace.metadata ?? null,
3168
+ isPrimary: workspace.isPrimary,
3169
+ createdAt: workspace.createdAt,
3170
+ updatedAt: workspace.updatedAt,
3171
+ }));
3172
+ const primaryWorkspace = workspaces.find((workspace) => workspace.isPrimary) ?? workspaces[0] ?? null;
3173
+ projectMap.set(r.id, {
3174
+ ...r,
3175
+ workspaces,
3176
+ primaryWorkspace,
3177
+ });
3178
+ // Also collect goalIds from projects
3179
+ if (r.goalId && !goalIds.includes(r.goalId))
3180
+ goalIds.push(r.goalId);
3181
+ }
3182
+ }
3183
+ if (goalIds.length > 0) {
3184
+ const rows = await db.select({
3185
+ id: goals.id, title: goals.title, description: goals.description,
3186
+ level: goals.level, status: goals.status,
3187
+ }).from(goals).where(inArray(goals.id, goalIds));
3188
+ for (const r of rows)
3189
+ goalMap.set(r.id, r);
3190
+ }
3191
+ return raw.map(a => ({
3192
+ ...a,
3193
+ project: a.projectId ? projectMap.get(a.projectId) ?? null : null,
3194
+ goal: a.goalId ? goalMap.get(a.goalId) ?? null : null,
3195
+ }));
3196
+ },
3197
+ };
3198
+ }
3199
+ //# sourceMappingURL=issues.js.map