@slaw-ai/server 2026.611.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 (1198) hide show
  1. package/LICENSE +26 -0
  2. package/dist/adapters/builtin-adapter-types.d.ts +5 -0
  3. package/dist/adapters/builtin-adapter-types.d.ts.map +1 -0
  4. package/dist/adapters/builtin-adapter-types.js +18 -0
  5. package/dist/adapters/builtin-adapter-types.js.map +1 -0
  6. package/dist/adapters/codex-models.d.ts +5 -0
  7. package/dist/adapters/codex-models.d.ts.map +1 -0
  8. package/dist/adapters/codex-models.js +105 -0
  9. package/dist/adapters/codex-models.js.map +1 -0
  10. package/dist/adapters/cursor-models.d.ts +13 -0
  11. package/dist/adapters/cursor-models.d.ts.map +1 -0
  12. package/dist/adapters/cursor-models.js +148 -0
  13. package/dist/adapters/cursor-models.js.map +1 -0
  14. package/dist/adapters/http/execute.d.ts +3 -0
  15. package/dist/adapters/http/execute.d.ts.map +1 -0
  16. package/dist/adapters/http/execute.js +51 -0
  17. package/dist/adapters/http/execute.js.map +1 -0
  18. package/dist/adapters/http/execute.test.d.ts +2 -0
  19. package/dist/adapters/http/execute.test.d.ts.map +1 -0
  20. package/dist/adapters/http/execute.test.js +40 -0
  21. package/dist/adapters/http/execute.test.js.map +1 -0
  22. package/dist/adapters/http/index.d.ts +3 -0
  23. package/dist/adapters/http/index.d.ts.map +1 -0
  24. package/dist/adapters/http/index.js +20 -0
  25. package/dist/adapters/http/index.js.map +1 -0
  26. package/dist/adapters/http/test.d.ts +3 -0
  27. package/dist/adapters/http/test.d.ts.map +1 -0
  28. package/dist/adapters/http/test.js +106 -0
  29. package/dist/adapters/http/test.js.map +1 -0
  30. package/dist/adapters/index.d.ts +4 -0
  31. package/dist/adapters/index.d.ts.map +1 -0
  32. package/dist/adapters/index.js +3 -0
  33. package/dist/adapters/index.js.map +1 -0
  34. package/dist/adapters/plugin-loader.d.ts +28 -0
  35. package/dist/adapters/plugin-loader.d.ts.map +1 -0
  36. package/dist/adapters/plugin-loader.js +196 -0
  37. package/dist/adapters/plugin-loader.js.map +1 -0
  38. package/dist/adapters/process/execute.d.ts +3 -0
  39. package/dist/adapters/process/execute.d.ts.map +1 -0
  40. package/dist/adapters/process/execute.js +70 -0
  41. package/dist/adapters/process/execute.js.map +1 -0
  42. package/dist/adapters/process/index.d.ts +3 -0
  43. package/dist/adapters/process/index.d.ts.map +1 -0
  44. package/dist/adapters/process/index.js +23 -0
  45. package/dist/adapters/process/index.js.map +1 -0
  46. package/dist/adapters/process/test.d.ts +3 -0
  47. package/dist/adapters/process/test.d.ts.map +1 -0
  48. package/dist/adapters/process/test.js +77 -0
  49. package/dist/adapters/process/test.js.map +1 -0
  50. package/dist/adapters/registry.d.ts +69 -0
  51. package/dist/adapters/registry.d.ts.map +1 -0
  52. package/dist/adapters/registry.js +598 -0
  53. package/dist/adapters/registry.js.map +1 -0
  54. package/dist/adapters/types.d.ts +2 -0
  55. package/dist/adapters/types.d.ts.map +1 -0
  56. package/dist/adapters/types.js +2 -0
  57. package/dist/adapters/types.js.map +1 -0
  58. package/dist/adapters/utils.d.ts +43 -0
  59. package/dist/adapters/utils.d.ts.map +1 -0
  60. package/dist/adapters/utils.js +52 -0
  61. package/dist/adapters/utils.js.map +1 -0
  62. package/dist/agent-auth-jwt.d.ts +14 -0
  63. package/dist/agent-auth-jwt.d.ts.map +1 -0
  64. package/dist/agent-auth-jwt.js +117 -0
  65. package/dist/agent-auth-jwt.js.map +1 -0
  66. package/dist/app.d.ts +39 -0
  67. package/dist/app.d.ts.map +1 -0
  68. package/dist/app.js +386 -0
  69. package/dist/app.js.map +1 -0
  70. package/dist/attachment-types.d.ts +23 -0
  71. package/dist/attachment-types.d.ts.map +1 -0
  72. package/dist/attachment-types.js +98 -0
  73. package/dist/attachment-types.js.map +1 -0
  74. package/dist/auth/better-auth.d.ts +40 -0
  75. package/dist/auth/better-auth.d.ts.map +1 -0
  76. package/dist/auth/better-auth.js +148 -0
  77. package/dist/auth/better-auth.js.map +1 -0
  78. package/dist/config-file.d.ts +24 -0
  79. package/dist/config-file.d.ts.map +1 -0
  80. package/dist/config-file.js +73 -0
  81. package/dist/config-file.js.map +1 -0
  82. package/dist/config.d.ts +44 -0
  83. package/dist/config.d.ts.map +1 -0
  84. package/dist/config.js +247 -0
  85. package/dist/config.js.map +1 -0
  86. package/dist/dev-runner-worktree.d.ts +15 -0
  87. package/dist/dev-runner-worktree.d.ts.map +1 -0
  88. package/dist/dev-runner-worktree.js +101 -0
  89. package/dist/dev-runner-worktree.js.map +1 -0
  90. package/dist/dev-server-status.d.ts +33 -0
  91. package/dist/dev-server-status.d.ts.map +1 -0
  92. package/dist/dev-server-status.js +89 -0
  93. package/dist/dev-server-status.js.map +1 -0
  94. package/dist/dev-watch-ignore.d.ts +2 -0
  95. package/dist/dev-watch-ignore.d.ts.map +1 -0
  96. package/dist/dev-watch-ignore.js +36 -0
  97. package/dist/dev-watch-ignore.js.map +1 -0
  98. package/dist/errors.d.ts +12 -0
  99. package/dist/errors.d.ts.map +1 -0
  100. package/dist/errors.js +28 -0
  101. package/dist/errors.js.map +1 -0
  102. package/dist/first-admin-claim.d.ts +17 -0
  103. package/dist/first-admin-claim.d.ts.map +1 -0
  104. package/dist/first-admin-claim.js +30 -0
  105. package/dist/first-admin-claim.js.map +1 -0
  106. package/dist/home-paths.d.ts +15 -0
  107. package/dist/home-paths.d.ts.map +1 -0
  108. package/dist/home-paths.js +48 -0
  109. package/dist/home-paths.js.map +1 -0
  110. package/dist/http/body-limits.d.ts +4 -0
  111. package/dist/http/body-limits.d.ts.map +1 -0
  112. package/dist/http/body-limits.js +4 -0
  113. package/dist/http/body-limits.js.map +1 -0
  114. package/dist/index.d.ts +10 -0
  115. package/dist/index.d.ts.map +1 -0
  116. package/dist/index.js +786 -0
  117. package/dist/index.js.map +1 -0
  118. package/dist/instance-claim.d.ts +23 -0
  119. package/dist/instance-claim.d.ts.map +1 -0
  120. package/dist/instance-claim.js +126 -0
  121. package/dist/instance-claim.js.map +1 -0
  122. package/dist/lib/join-request-dedupe.d.ts +11 -0
  123. package/dist/lib/join-request-dedupe.d.ts.map +1 -0
  124. package/dist/lib/join-request-dedupe.js +49 -0
  125. package/dist/lib/join-request-dedupe.js.map +1 -0
  126. package/dist/log-redaction.d.ts +11 -0
  127. package/dist/log-redaction.d.ts.map +1 -0
  128. package/dist/log-redaction.js +122 -0
  129. package/dist/log-redaction.js.map +1 -0
  130. package/dist/middleware/auth.d.ts +12 -0
  131. package/dist/middleware/auth.d.ts.map +1 -0
  132. package/dist/middleware/auth.js +302 -0
  133. package/dist/middleware/auth.js.map +1 -0
  134. package/dist/middleware/error-handler.d.ts +17 -0
  135. package/dist/middleware/error-handler.d.ts.map +1 -0
  136. package/dist/middleware/error-handler.js +46 -0
  137. package/dist/middleware/error-handler.js.map +1 -0
  138. package/dist/middleware/http-log-policy.d.ts +2 -0
  139. package/dist/middleware/http-log-policy.d.ts.map +1 -0
  140. package/dist/middleware/http-log-policy.js +52 -0
  141. package/dist/middleware/http-log-policy.js.map +1 -0
  142. package/dist/middleware/index.d.ts +4 -0
  143. package/dist/middleware/index.d.ts.map +1 -0
  144. package/dist/middleware/index.js +4 -0
  145. package/dist/middleware/index.js.map +1 -0
  146. package/dist/middleware/logger.d.ts +4 -0
  147. package/dist/middleware/logger.d.ts.map +1 -0
  148. package/dist/middleware/logger.js +92 -0
  149. package/dist/middleware/logger.js.map +1 -0
  150. package/dist/middleware/operator-mutation-guard.d.ts +3 -0
  151. package/dist/middleware/operator-mutation-guard.d.ts.map +1 -0
  152. package/dist/middleware/operator-mutation-guard.js +70 -0
  153. package/dist/middleware/operator-mutation-guard.js.map +1 -0
  154. package/dist/middleware/private-hostname-guard.d.ts +11 -0
  155. package/dist/middleware/private-hostname-guard.d.ts.map +1 -0
  156. package/dist/middleware/private-hostname-guard.js +78 -0
  157. package/dist/middleware/private-hostname-guard.js.map +1 -0
  158. package/dist/middleware/validate.d.ts +4 -0
  159. package/dist/middleware/validate.d.ts.map +1 -0
  160. package/dist/middleware/validate.js +7 -0
  161. package/dist/middleware/validate.js.map +1 -0
  162. package/dist/onboarding-assets/default/AGENTS.md +18 -0
  163. package/dist/onboarding-assets/squad_lead/AGENTS.md +61 -0
  164. package/dist/onboarding-assets/squad_lead/HEARTBEAT.md +85 -0
  165. package/dist/onboarding-assets/squad_lead/SOUL.md +33 -0
  166. package/dist/onboarding-assets/squad_lead/TOOLS.md +3 -0
  167. package/dist/paths.d.ts +3 -0
  168. package/dist/paths.d.ts.map +1 -0
  169. package/dist/paths.js +31 -0
  170. package/dist/paths.js.map +1 -0
  171. package/dist/realtime/live-events-ws.d.ts +28 -0
  172. package/dist/realtime/live-events-ws.d.ts.map +1 -0
  173. package/dist/realtime/live-events-ws.js +187 -0
  174. package/dist/realtime/live-events-ws.js.map +1 -0
  175. package/dist/redaction.d.ts +5 -0
  176. package/dist/redaction.d.ts.map +1 -0
  177. package/dist/redaction.js +125 -0
  178. package/dist/redaction.js.map +1 -0
  179. package/dist/routes/access.d.ts +75 -0
  180. package/dist/routes/access.d.ts.map +1 -0
  181. package/dist/routes/access.js +3070 -0
  182. package/dist/routes/access.js.map +1 -0
  183. package/dist/routes/activity.d.ts +3 -0
  184. package/dist/routes/activity.d.ts.map +1 -0
  185. package/dist/routes/activity.js +90 -0
  186. package/dist/routes/activity.js.map +1 -0
  187. package/dist/routes/adapters.d.ts +16 -0
  188. package/dist/routes/adapters.d.ts.map +1 -0
  189. package/dist/routes/adapters.js +539 -0
  190. package/dist/routes/adapters.js.map +1 -0
  191. package/dist/routes/agents.d.ts +6 -0
  192. package/dist/routes/agents.d.ts.map +1 -0
  193. package/dist/routes/agents.js +2733 -0
  194. package/dist/routes/agents.js.map +1 -0
  195. package/dist/routes/approvals.d.ts +6 -0
  196. package/dist/routes/approvals.d.ts.map +1 -0
  197. package/dist/routes/approvals.js +300 -0
  198. package/dist/routes/approvals.js.map +1 -0
  199. package/dist/routes/assets.d.ts +4 -0
  200. package/dist/routes/assets.d.ts.map +1 -0
  201. package/dist/routes/assets.js +309 -0
  202. package/dist/routes/assets.js.map +1 -0
  203. package/dist/routes/auth.d.ts +3 -0
  204. package/dist/routes/auth.d.ts.map +1 -0
  205. package/dist/routes/auth.js +82 -0
  206. package/dist/routes/auth.js.map +1 -0
  207. package/dist/routes/authz.d.ts +19 -0
  208. package/dist/routes/authz.d.ts.map +1 -0
  209. package/dist/routes/authz.js +75 -0
  210. package/dist/routes/authz.js.map +1 -0
  211. package/dist/routes/botfather.d.ts +9 -0
  212. package/dist/routes/botfather.d.ts.map +1 -0
  213. package/dist/routes/botfather.js +127 -0
  214. package/dist/routes/botfather.js.map +1 -0
  215. package/dist/routes/cloud-upstreams.d.ts +5 -0
  216. package/dist/routes/cloud-upstreams.d.ts.map +1 -0
  217. package/dist/routes/cloud-upstreams.js +103 -0
  218. package/dist/routes/cloud-upstreams.js.map +1 -0
  219. package/dist/routes/costs.d.ts +11 -0
  220. package/dist/routes/costs.d.ts.map +1 -0
  221. package/dist/routes/costs.js +285 -0
  222. package/dist/routes/costs.js.map +1 -0
  223. package/dist/routes/dashboard.d.ts +3 -0
  224. package/dist/routes/dashboard.d.ts.map +1 -0
  225. package/dist/routes/dashboard.js +15 -0
  226. package/dist/routes/dashboard.js.map +1 -0
  227. package/dist/routes/environment-selection.d.ts +13 -0
  228. package/dist/routes/environment-selection.d.ts.map +1 -0
  229. package/dist/routes/environment-selection.js +30 -0
  230. package/dist/routes/environment-selection.js.map +1 -0
  231. package/dist/routes/environments.d.ts +6 -0
  232. package/dist/routes/environments.d.ts.map +1 -0
  233. package/dist/routes/environments.js +414 -0
  234. package/dist/routes/environments.js.map +1 -0
  235. package/dist/routes/execution-workspaces.d.ts +3 -0
  236. package/dist/routes/execution-workspaces.d.ts.map +1 -0
  237. package/dist/routes/execution-workspaces.js +537 -0
  238. package/dist/routes/execution-workspaces.js.map +1 -0
  239. package/dist/routes/goals.d.ts +3 -0
  240. package/dist/routes/goals.d.ts.map +1 -0
  241. package/dist/routes/goals.js +95 -0
  242. package/dist/routes/goals.js.map +1 -0
  243. package/dist/routes/health.d.ts +9 -0
  244. package/dist/routes/health.d.ts.map +1 -0
  245. package/dist/routes/health.js +143 -0
  246. package/dist/routes/health.js.map +1 -0
  247. package/dist/routes/inbox-dismissals.d.ts +3 -0
  248. package/dist/routes/inbox-dismissals.d.ts.map +1 -0
  249. package/dist/routes/inbox-dismissals.js +58 -0
  250. package/dist/routes/inbox-dismissals.js.map +1 -0
  251. package/dist/routes/index.d.ts +24 -0
  252. package/dist/routes/index.d.ts.map +1 -0
  253. package/dist/routes/index.js +24 -0
  254. package/dist/routes/index.js.map +1 -0
  255. package/dist/routes/instance-database-backups.d.ts +15 -0
  256. package/dist/routes/instance-database-backups.d.ts.map +1 -0
  257. package/dist/routes/instance-database-backups.js +12 -0
  258. package/dist/routes/instance-database-backups.js.map +1 -0
  259. package/dist/routes/instance-settings.d.ts +3 -0
  260. package/dist/routes/instance-settings.d.ts.map +1 -0
  261. package/dist/routes/instance-settings.js +110 -0
  262. package/dist/routes/instance-settings.js.map +1 -0
  263. package/dist/routes/issue-tree-control.d.ts +3 -0
  264. package/dist/routes/issue-tree-control.d.ts.map +1 -0
  265. package/dist/routes/issue-tree-control.js +373 -0
  266. package/dist/routes/issue-tree-control.js.map +1 -0
  267. package/dist/routes/issues-checkout-wakeup.d.ts +9 -0
  268. package/dist/routes/issues-checkout-wakeup.d.ts.map +1 -0
  269. package/dist/routes/issues-checkout-wakeup.js +12 -0
  270. package/dist/routes/issues-checkout-wakeup.js.map +1 -0
  271. package/dist/routes/issues.d.ts +15 -0
  272. package/dist/routes/issues.d.ts.map +1 -0
  273. package/dist/routes/issues.js +5276 -0
  274. package/dist/routes/issues.js.map +1 -0
  275. package/dist/routes/llms.d.ts +3 -0
  276. package/dist/routes/llms.d.ts.map +1 -0
  277. package/dist/routes/llms.js +80 -0
  278. package/dist/routes/llms.js.map +1 -0
  279. package/dist/routes/openapi.d.ts +4 -0
  280. package/dist/routes/openapi.d.ts.map +1 -0
  281. package/dist/routes/openapi.js +3284 -0
  282. package/dist/routes/openapi.js.map +1 -0
  283. package/dist/routes/org-chart-svg.d.ts +25 -0
  284. package/dist/routes/org-chart-svg.d.ts.map +1 -0
  285. package/dist/routes/org-chart-svg.js +656 -0
  286. package/dist/routes/org-chart-svg.js.map +1 -0
  287. package/dist/routes/plugin-ui-static.d.ts +69 -0
  288. package/dist/routes/plugin-ui-static.d.ts.map +1 -0
  289. package/dist/routes/plugin-ui-static.js +411 -0
  290. package/dist/routes/plugin-ui-static.js.map +1 -0
  291. package/dist/routes/plugins.d.ts +121 -0
  292. package/dist/routes/plugins.d.ts.map +1 -0
  293. package/dist/routes/plugins.js +2390 -0
  294. package/dist/routes/plugins.js.map +1 -0
  295. package/dist/routes/projects.d.ts +3 -0
  296. package/dist/routes/projects.d.ts.map +1 -0
  297. package/dist/routes/projects.js +566 -0
  298. package/dist/routes/projects.js.map +1 -0
  299. package/dist/routes/resource-memberships.d.ts +3 -0
  300. package/dist/routes/resource-memberships.d.ts.map +1 -0
  301. package/dist/routes/resource-memberships.js +97 -0
  302. package/dist/routes/resource-memberships.js.map +1 -0
  303. package/dist/routes/routines.d.ts +6 -0
  304. package/dist/routes/routines.d.ts.map +1 -0
  305. package/dist/routes/routines.js +411 -0
  306. package/dist/routes/routines.js.map +1 -0
  307. package/dist/routes/secrets.d.ts +3 -0
  308. package/dist/routes/secrets.d.ts.map +1 -0
  309. package/dist/routes/secrets.js +419 -0
  310. package/dist/routes/secrets.js.map +1 -0
  311. package/dist/routes/sidebar-badges.d.ts +3 -0
  312. package/dist/routes/sidebar-badges.d.ts.map +1 -0
  313. package/dist/routes/sidebar-badges.js +68 -0
  314. package/dist/routes/sidebar-badges.js.map +1 -0
  315. package/dist/routes/sidebar-preferences.d.ts +3 -0
  316. package/dist/routes/sidebar-preferences.d.ts.map +1 -0
  317. package/dist/routes/sidebar-preferences.js +63 -0
  318. package/dist/routes/sidebar-preferences.js.map +1 -0
  319. package/dist/routes/squad-import-paths.d.ts +3 -0
  320. package/dist/routes/squad-import-paths.d.ts.map +1 -0
  321. package/dist/routes/squad-import-paths.js +3 -0
  322. package/dist/routes/squad-import-paths.js.map +1 -0
  323. package/dist/routes/squad-skills.d.ts +3 -0
  324. package/dist/routes/squad-skills.d.ts.map +1 -0
  325. package/dist/routes/squad-skills.js +366 -0
  326. package/dist/routes/squad-skills.js.map +1 -0
  327. package/dist/routes/squads.d.ts +4 -0
  328. package/dist/routes/squads.d.ts.map +1 -0
  329. package/dist/routes/squads.js +450 -0
  330. package/dist/routes/squads.js.map +1 -0
  331. package/dist/routes/user-profiles.d.ts +3 -0
  332. package/dist/routes/user-profiles.d.ts.map +1 -0
  333. package/dist/routes/user-profiles.js +337 -0
  334. package/dist/routes/user-profiles.js.map +1 -0
  335. package/dist/routes/workspace-command-authz.d.ts +14 -0
  336. package/dist/routes/workspace-command-authz.d.ts.map +1 -0
  337. package/dist/routes/workspace-command-authz.js +83 -0
  338. package/dist/routes/workspace-command-authz.js.map +1 -0
  339. package/dist/routes/workspace-runtime-service-authz.d.ts +12 -0
  340. package/dist/routes/workspace-runtime-service-authz.d.ts.map +1 -0
  341. package/dist/routes/workspace-runtime-service-authz.js +96 -0
  342. package/dist/routes/workspace-runtime-service-authz.js.map +1 -0
  343. package/dist/runtime-api.d.ts +19 -0
  344. package/dist/runtime-api.d.ts.map +1 -0
  345. package/dist/runtime-api.js +137 -0
  346. package/dist/runtime-api.js.map +1 -0
  347. package/dist/secrets/aws-secrets-manager-provider.d.ts +87 -0
  348. package/dist/secrets/aws-secrets-manager-provider.d.ts.map +1 -0
  349. package/dist/secrets/aws-secrets-manager-provider.js +964 -0
  350. package/dist/secrets/aws-secrets-manager-provider.js.map +1 -0
  351. package/dist/secrets/configured-provider.d.ts +3 -0
  352. package/dist/secrets/configured-provider.d.ts.map +1 -0
  353. package/dist/secrets/configured-provider.js +8 -0
  354. package/dist/secrets/configured-provider.js.map +1 -0
  355. package/dist/secrets/external-stub-providers.d.ts +5 -0
  356. package/dist/secrets/external-stub-providers.d.ts.map +1 -0
  357. package/dist/secrets/external-stub-providers.js +71 -0
  358. package/dist/secrets/external-stub-providers.js.map +1 -0
  359. package/dist/secrets/local-encrypted-provider.d.ts +3 -0
  360. package/dist/secrets/local-encrypted-provider.d.ts.map +1 -0
  361. package/dist/secrets/local-encrypted-provider.js +244 -0
  362. package/dist/secrets/local-encrypted-provider.js.map +1 -0
  363. package/dist/secrets/provider-registry.d.ts +6 -0
  364. package/dist/secrets/provider-registry.d.ts.map +1 -0
  365. package/dist/secrets/provider-registry.js +24 -0
  366. package/dist/secrets/provider-registry.js.map +1 -0
  367. package/dist/secrets/types.d.ts +138 -0
  368. package/dist/secrets/types.d.ts.map +1 -0
  369. package/dist/secrets/types.js +36 -0
  370. package/dist/secrets/types.js.map +1 -0
  371. package/dist/services/access.d.ts +184 -0
  372. package/dist/services/access.d.ts.map +1 -0
  373. package/dist/services/access.js +542 -0
  374. package/dist/services/access.js.map +1 -0
  375. package/dist/services/activity-log.d.ts +19 -0
  376. package/dist/services/activity-log.d.ts.map +1 -0
  377. package/dist/services/activity-log.js +99 -0
  378. package/dist/services/activity-log.js.map +1 -0
  379. package/dist/services/activity.d.ts +462 -0
  380. package/dist/services/activity.d.ts.map +1 -0
  381. package/dist/services/activity.js +443 -0
  382. package/dist/services/activity.js.map +1 -0
  383. package/dist/services/adapter-plugin-store.d.ts +36 -0
  384. package/dist/services/adapter-plugin-store.d.ts.map +1 -0
  385. package/dist/services/adapter-plugin-store.js +154 -0
  386. package/dist/services/adapter-plugin-store.js.map +1 -0
  387. package/dist/services/agent-instructions.d.ts +91 -0
  388. package/dist/services/agent-instructions.d.ts.map +1 -0
  389. package/dist/services/agent-instructions.js +580 -0
  390. package/dist/services/agent-instructions.js.map +1 -0
  391. package/dist/services/agent-permissions.d.ts +6 -0
  392. package/dist/services/agent-permissions.d.ts.map +1 -0
  393. package/dist/services/agent-permissions.js +20 -0
  394. package/dist/services/agent-permissions.js.map +1 -0
  395. package/dist/services/agent-start-lock.d.ts +2 -0
  396. package/dist/services/agent-start-lock.d.ts.map +1 -0
  397. package/dist/services/agent-start-lock.js +43 -0
  398. package/dist/services/agent-start-lock.js.map +1 -0
  399. package/dist/services/agents.d.ts +2253 -0
  400. package/dist/services/agents.d.ts.map +1 -0
  401. package/dist/services/agents.js +609 -0
  402. package/dist/services/agents.js.map +1 -0
  403. package/dist/services/approvals.d.ts +546 -0
  404. package/dist/services/approvals.d.ts.map +1 -0
  405. package/dist/services/approvals.js +212 -0
  406. package/dist/services/approvals.js.map +1 -0
  407. package/dist/services/assets.d.ts +33 -0
  408. package/dist/services/assets.d.ts.map +1 -0
  409. package/dist/services/assets.js +17 -0
  410. package/dist/services/assets.js.map +1 -0
  411. package/dist/services/authorization.d.ts +67 -0
  412. package/dist/services/authorization.d.ts.map +1 -0
  413. package/dist/services/authorization.js +608 -0
  414. package/dist/services/authorization.js.map +1 -0
  415. package/dist/services/botfather/authoring-lock.d.ts +17 -0
  416. package/dist/services/botfather/authoring-lock.d.ts.map +1 -0
  417. package/dist/services/botfather/authoring-lock.js +23 -0
  418. package/dist/services/botfather/authoring-lock.js.map +1 -0
  419. package/dist/services/botfather/authoring-lock.test.d.ts +2 -0
  420. package/dist/services/botfather/authoring-lock.test.d.ts.map +1 -0
  421. package/dist/services/botfather/authoring-lock.test.js +25 -0
  422. package/dist/services/botfather/authoring-lock.test.js.map +1 -0
  423. package/dist/services/botfather/client.d.ts +26 -0
  424. package/dist/services/botfather/client.d.ts.map +1 -0
  425. package/dist/services/botfather/client.js +113 -0
  426. package/dist/services/botfather/client.js.map +1 -0
  427. package/dist/services/botfather/credentials.d.ts +15 -0
  428. package/dist/services/botfather/credentials.d.ts.map +1 -0
  429. package/dist/services/botfather/credentials.js +39 -0
  430. package/dist/services/botfather/credentials.js.map +1 -0
  431. package/dist/services/botfather/enrollment.d.ts +49 -0
  432. package/dist/services/botfather/enrollment.d.ts.map +1 -0
  433. package/dist/services/botfather/enrollment.js +145 -0
  434. package/dist/services/botfather/enrollment.js.map +1 -0
  435. package/dist/services/botfather/instance-limit-enforcement.d.ts +44 -0
  436. package/dist/services/botfather/instance-limit-enforcement.d.ts.map +1 -0
  437. package/dist/services/botfather/instance-limit-enforcement.js +83 -0
  438. package/dist/services/botfather/instance-limit-enforcement.js.map +1 -0
  439. package/dist/services/botfather/instance-limit-enforcement.test.d.ts +2 -0
  440. package/dist/services/botfather/instance-limit-enforcement.test.d.ts.map +1 -0
  441. package/dist/services/botfather/instance-limit-enforcement.test.js +66 -0
  442. package/dist/services/botfather/instance-limit-enforcement.test.js.map +1 -0
  443. package/dist/services/botfather/limits-store.d.ts +36 -0
  444. package/dist/services/botfather/limits-store.d.ts.map +1 -0
  445. package/dist/services/botfather/limits-store.js +94 -0
  446. package/dist/services/botfather/limits-store.js.map +1 -0
  447. package/dist/services/botfather/limits-store.test.d.ts +2 -0
  448. package/dist/services/botfather/limits-store.test.d.ts.map +1 -0
  449. package/dist/services/botfather/limits-store.test.js +70 -0
  450. package/dist/services/botfather/limits-store.test.js.map +1 -0
  451. package/dist/services/botfather/reporter.d.ts +41 -0
  452. package/dist/services/botfather/reporter.d.ts.map +1 -0
  453. package/dist/services/botfather/reporter.js +448 -0
  454. package/dist/services/botfather/reporter.js.map +1 -0
  455. package/dist/services/botfather/service.d.ts +84 -0
  456. package/dist/services/botfather/service.d.ts.map +1 -0
  457. package/dist/services/botfather/service.js +229 -0
  458. package/dist/services/botfather/service.js.map +1 -0
  459. package/dist/services/botfather/service.test.d.ts +2 -0
  460. package/dist/services/botfather/service.test.d.ts.map +1 -0
  461. package/dist/services/botfather/service.test.js +120 -0
  462. package/dist/services/botfather/service.test.js.map +1 -0
  463. package/dist/services/botfather/skill-catalog.d.ts +28 -0
  464. package/dist/services/botfather/skill-catalog.d.ts.map +1 -0
  465. package/dist/services/botfather/skill-catalog.js +101 -0
  466. package/dist/services/botfather/skill-catalog.js.map +1 -0
  467. package/dist/services/botfather/skill-catalog.test.d.ts +2 -0
  468. package/dist/services/botfather/skill-catalog.test.d.ts.map +1 -0
  469. package/dist/services/botfather/skill-catalog.test.js +151 -0
  470. package/dist/services/botfather/skill-catalog.test.js.map +1 -0
  471. package/dist/services/budgets.d.ts +38 -0
  472. package/dist/services/budgets.d.ts.map +1 -0
  473. package/dist/services/budgets.js +833 -0
  474. package/dist/services/budgets.js.map +1 -0
  475. package/dist/services/catalog-provenance.d.ts +7 -0
  476. package/dist/services/catalog-provenance.d.ts.map +1 -0
  477. package/dist/services/catalog-provenance.js +64 -0
  478. package/dist/services/catalog-provenance.js.map +1 -0
  479. package/dist/services/cloud-upstreams.d.ts +42 -0
  480. package/dist/services/cloud-upstreams.d.ts.map +1 -0
  481. package/dist/services/cloud-upstreams.js +1071 -0
  482. package/dist/services/cloud-upstreams.js.map +1 -0
  483. package/dist/services/costs.d.ts +127 -0
  484. package/dist/services/costs.d.ts.map +1 -0
  485. package/dist/services/costs.js +409 -0
  486. package/dist/services/costs.js.map +1 -0
  487. package/dist/services/cron.d.ts +80 -0
  488. package/dist/services/cron.d.ts.map +1 -0
  489. package/dist/services/cron.js +300 -0
  490. package/dist/services/cron.js.map +1 -0
  491. package/dist/services/dashboard.d.ts +34 -0
  492. package/dist/services/dashboard.d.ts.map +1 -0
  493. package/dist/services/dashboard.js +142 -0
  494. package/dist/services/dashboard.js.map +1 -0
  495. package/dist/services/default-agent-instructions.d.ts +9 -0
  496. package/dist/services/default-agent-instructions.d.ts.map +1 -0
  497. package/dist/services/default-agent-instructions.js +20 -0
  498. package/dist/services/default-agent-instructions.js.map +1 -0
  499. package/dist/services/document-annotations.d.ts +160 -0
  500. package/dist/services/document-annotations.d.ts.map +1 -0
  501. package/dist/services/document-annotations.js +324 -0
  502. package/dist/services/document-annotations.js.map +1 -0
  503. package/dist/services/documents.d.ts +347 -0
  504. package/dist/services/documents.d.ts.map +1 -0
  505. package/dist/services/documents.js +638 -0
  506. package/dist/services/documents.js.map +1 -0
  507. package/dist/services/environment-config.d.ts +55 -0
  508. package/dist/services/environment-config.d.ts.map +1 -0
  509. package/dist/services/environment-config.js +441 -0
  510. package/dist/services/environment-config.js.map +1 -0
  511. package/dist/services/environment-execution-target.d.ts +21 -0
  512. package/dist/services/environment-execution-target.d.ts.map +1 -0
  513. package/dist/services/environment-execution-target.js +121 -0
  514. package/dist/services/environment-execution-target.js.map +1 -0
  515. package/dist/services/environment-probe.d.ts +9 -0
  516. package/dist/services/environment-probe.d.ts.map +1 -0
  517. package/dist/services/environment-probe.js +106 -0
  518. package/dist/services/environment-probe.js.map +1 -0
  519. package/dist/services/environment-run-orchestrator.d.ts +124 -0
  520. package/dist/services/environment-run-orchestrator.d.ts.map +1 -0
  521. package/dist/services/environment-run-orchestrator.js +392 -0
  522. package/dist/services/environment-run-orchestrator.js.map +1 -0
  523. package/dist/services/environment-runtime.d.ts +90 -0
  524. package/dist/services/environment-runtime.d.ts.map +1 -0
  525. package/dist/services/environment-runtime.js +968 -0
  526. package/dist/services/environment-runtime.js.map +1 -0
  527. package/dist/services/environments.d.ts +36 -0
  528. package/dist/services/environments.d.ts.map +1 -0
  529. package/dist/services/environments.js +260 -0
  530. package/dist/services/environments.js.map +1 -0
  531. package/dist/services/execution-workspace-policy.d.ts +42 -0
  532. package/dist/services/execution-workspace-policy.d.ts.map +1 -0
  533. package/dist/services/execution-workspace-policy.js +262 -0
  534. package/dist/services/execution-workspace-policy.js.map +1 -0
  535. package/dist/services/execution-workspaces.d.ts +30 -0
  536. package/dist/services/execution-workspaces.d.ts.map +1 -0
  537. package/dist/services/execution-workspaces.js +645 -0
  538. package/dist/services/execution-workspaces.js.map +1 -0
  539. package/dist/services/finance.d.ts +93 -0
  540. package/dist/services/finance.d.ts.map +1 -0
  541. package/dist/services/finance.js +120 -0
  542. package/dist/services/finance.js.map +1 -0
  543. package/dist/services/github-fetch.d.ts +4 -0
  544. package/dist/services/github-fetch.d.ts.map +1 -0
  545. package/dist/services/github-fetch.js +23 -0
  546. package/dist/services/github-fetch.js.map +1 -0
  547. package/dist/services/goals.d.ts +433 -0
  548. package/dist/services/goals.d.ts.map +1 -0
  549. package/dist/services/goals.js +54 -0
  550. package/dist/services/goals.js.map +1 -0
  551. package/dist/services/heartbeat-circuit-breaker.d.ts +89 -0
  552. package/dist/services/heartbeat-circuit-breaker.d.ts.map +1 -0
  553. package/dist/services/heartbeat-circuit-breaker.js +156 -0
  554. package/dist/services/heartbeat-circuit-breaker.js.map +1 -0
  555. package/dist/services/heartbeat-circuit-breaker.test.d.ts +2 -0
  556. package/dist/services/heartbeat-circuit-breaker.test.d.ts.map +1 -0
  557. package/dist/services/heartbeat-circuit-breaker.test.js +97 -0
  558. package/dist/services/heartbeat-circuit-breaker.test.js.map +1 -0
  559. package/dist/services/heartbeat-run-summary.d.ts +7 -0
  560. package/dist/services/heartbeat-run-summary.d.ts.map +1 -0
  561. package/dist/services/heartbeat-run-summary.js +84 -0
  562. package/dist/services/heartbeat-run-summary.js.map +1 -0
  563. package/dist/services/heartbeat-stop-metadata.d.ts +28 -0
  564. package/dist/services/heartbeat-stop-metadata.d.ts.map +1 -0
  565. package/dist/services/heartbeat-stop-metadata.js +86 -0
  566. package/dist/services/heartbeat-stop-metadata.js.map +1 -0
  567. package/dist/services/heartbeat-stop-metadata.test.d.ts +2 -0
  568. package/dist/services/heartbeat-stop-metadata.test.d.ts.map +1 -0
  569. package/dist/services/heartbeat-stop-metadata.test.js +93 -0
  570. package/dist/services/heartbeat-stop-metadata.test.js.map +1 -0
  571. package/dist/services/heartbeat.d.ts +1578 -0
  572. package/dist/services/heartbeat.d.ts.map +1 -0
  573. package/dist/services/heartbeat.js +8274 -0
  574. package/dist/services/heartbeat.js.map +1 -0
  575. package/dist/services/hire-hook.d.ts +14 -0
  576. package/dist/services/hire-hook.d.ts.map +1 -0
  577. package/dist/services/hire-hook.js +85 -0
  578. package/dist/services/hire-hook.js.map +1 -0
  579. package/dist/services/inbox-dismissals.d.ts +22 -0
  580. package/dist/services/inbox-dismissals.d.ts.map +1 -0
  581. package/dist/services/inbox-dismissals.js +33 -0
  582. package/dist/services/inbox-dismissals.js.map +1 -0
  583. package/dist/services/index.d.ts +50 -0
  584. package/dist/services/index.d.ts.map +1 -0
  585. package/dist/services/index.js +49 -0
  586. package/dist/services/index.js.map +1 -0
  587. package/dist/services/instance-settings.d.ts +12 -0
  588. package/dist/services/instance-settings.d.ts.map +1 -0
  589. package/dist/services/instance-settings.js +142 -0
  590. package/dist/services/instance-settings.js.map +1 -0
  591. package/dist/services/invite-grants.d.ts +15 -0
  592. package/dist/services/invite-grants.d.ts.map +1 -0
  593. package/dist/services/invite-grants.js +50 -0
  594. package/dist/services/invite-grants.js.map +1 -0
  595. package/dist/services/issue-approvals.d.ts +56 -0
  596. package/dist/services/issue-approvals.d.ts.map +1 -0
  597. package/dist/services/issue-approvals.js +153 -0
  598. package/dist/services/issue-approvals.js.map +1 -0
  599. package/dist/services/issue-assignment-wakeup.d.ts +29 -0
  600. package/dist/services/issue-assignment-wakeup.d.ts.map +1 -0
  601. package/dist/services/issue-assignment-wakeup.js +22 -0
  602. package/dist/services/issue-assignment-wakeup.js.map +1 -0
  603. package/dist/services/issue-continuation-summary.d.ts +71 -0
  604. package/dist/services/issue-continuation-summary.d.ts.map +1 -0
  605. package/dist/services/issue-continuation-summary.js +222 -0
  606. package/dist/services/issue-continuation-summary.js.map +1 -0
  607. package/dist/services/issue-execution-policy.d.ts +93 -0
  608. package/dist/services/issue-execution-policy.d.ts.map +1 -0
  609. package/dist/services/issue-execution-policy.js +838 -0
  610. package/dist/services/issue-execution-policy.js.map +1 -0
  611. package/dist/services/issue-goal-fallback.d.ts +18 -0
  612. package/dist/services/issue-goal-fallback.d.ts.map +1 -0
  613. package/dist/services/issue-goal-fallback.js +33 -0
  614. package/dist/services/issue-goal-fallback.js.map +1 -0
  615. package/dist/services/issue-liveness.d.ts +3 -0
  616. package/dist/services/issue-liveness.d.ts.map +1 -0
  617. package/dist/services/issue-liveness.js +2 -0
  618. package/dist/services/issue-liveness.js.map +1 -0
  619. package/dist/services/issue-recovery-actions.d.ts +40 -0
  620. package/dist/services/issue-recovery-actions.d.ts.map +1 -0
  621. package/dist/services/issue-recovery-actions.js +204 -0
  622. package/dist/services/issue-recovery-actions.js.map +1 -0
  623. package/dist/services/issue-references.d.ts +22 -0
  624. package/dist/services/issue-references.d.ts.map +1 -0
  625. package/dist/services/issue-references.js +341 -0
  626. package/dist/services/issue-references.js.map +1 -0
  627. package/dist/services/issue-thread-interactions.d.ts +81 -0
  628. package/dist/services/issue-thread-interactions.d.ts.map +1 -0
  629. package/dist/services/issue-thread-interactions.js +1017 -0
  630. package/dist/services/issue-thread-interactions.js.map +1 -0
  631. package/dist/services/issue-thread-interactions.test.d.ts +2 -0
  632. package/dist/services/issue-thread-interactions.test.d.ts.map +1 -0
  633. package/dist/services/issue-thread-interactions.test.js +195 -0
  634. package/dist/services/issue-thread-interactions.test.js.map +1 -0
  635. package/dist/services/issue-tree-control.d.ts +89 -0
  636. package/dist/services/issue-tree-control.d.ts.map +1 -0
  637. package/dist/services/issue-tree-control.js +933 -0
  638. package/dist/services/issue-tree-control.js.map +1 -0
  639. package/dist/services/issues.d.ts +898 -0
  640. package/dist/services/issues.d.ts.map +1 -0
  641. package/dist/services/issues.js +4705 -0
  642. package/dist/services/issues.js.map +1 -0
  643. package/dist/services/json-schema-secret-refs.d.ts +5 -0
  644. package/dist/services/json-schema-secret-refs.d.ts.map +1 -0
  645. package/dist/services/json-schema-secret-refs.js +67 -0
  646. package/dist/services/json-schema-secret-refs.js.map +1 -0
  647. package/dist/services/live-events.d.ts +17 -0
  648. package/dist/services/live-events.d.ts.map +1 -0
  649. package/dist/services/live-events.js +33 -0
  650. package/dist/services/live-events.js.map +1 -0
  651. package/dist/services/local-service-supervisor.d.ts +56 -0
  652. package/dist/services/local-service-supervisor.d.ts.map +1 -0
  653. package/dist/services/local-service-supervisor.js +284 -0
  654. package/dist/services/local-service-supervisor.js.map +1 -0
  655. package/dist/services/operator-auth.d.ts +271 -0
  656. package/dist/services/operator-auth.d.ts.map +1 -0
  657. package/dist/services/operator-auth.js +361 -0
  658. package/dist/services/operator-auth.js.map +1 -0
  659. package/dist/services/plugin-capability-validator.d.ts +108 -0
  660. package/dist/services/plugin-capability-validator.d.ts.map +1 -0
  661. package/dist/services/plugin-capability-validator.js +314 -0
  662. package/dist/services/plugin-capability-validator.js.map +1 -0
  663. package/dist/services/plugin-config-validator.d.ts +26 -0
  664. package/dist/services/plugin-config-validator.d.ts.map +1 -0
  665. package/dist/services/plugin-config-validator.js +41 -0
  666. package/dist/services/plugin-config-validator.js.map +1 -0
  667. package/dist/services/plugin-database.d.ts +49 -0
  668. package/dist/services/plugin-database.d.ts.map +1 -0
  669. package/dist/services/plugin-database.js +475 -0
  670. package/dist/services/plugin-database.js.map +1 -0
  671. package/dist/services/plugin-dev-watcher.d.ts +30 -0
  672. package/dist/services/plugin-dev-watcher.d.ts.map +1 -0
  673. package/dist/services/plugin-dev-watcher.js +246 -0
  674. package/dist/services/plugin-dev-watcher.js.map +1 -0
  675. package/dist/services/plugin-environment-driver.d.ts +126 -0
  676. package/dist/services/plugin-environment-driver.d.ts.map +1 -0
  677. package/dist/services/plugin-environment-driver.js +226 -0
  678. package/dist/services/plugin-environment-driver.js.map +1 -0
  679. package/dist/services/plugin-event-bus.d.ts +149 -0
  680. package/dist/services/plugin-event-bus.d.ts.map +1 -0
  681. package/dist/services/plugin-event-bus.js +258 -0
  682. package/dist/services/plugin-event-bus.js.map +1 -0
  683. package/dist/services/plugin-host-service-cleanup.d.ts +14 -0
  684. package/dist/services/plugin-host-service-cleanup.d.ts.map +1 -0
  685. package/dist/services/plugin-host-service-cleanup.js +37 -0
  686. package/dist/services/plugin-host-service-cleanup.js.map +1 -0
  687. package/dist/services/plugin-host-services.d.ts +17 -0
  688. package/dist/services/plugin-host-services.d.ts.map +1 -0
  689. package/dist/services/plugin-host-services.js +2460 -0
  690. package/dist/services/plugin-host-services.js.map +1 -0
  691. package/dist/services/plugin-job-coordinator.d.ts +81 -0
  692. package/dist/services/plugin-job-coordinator.d.ts.map +1 -0
  693. package/dist/services/plugin-job-coordinator.js +172 -0
  694. package/dist/services/plugin-job-coordinator.js.map +1 -0
  695. package/dist/services/plugin-job-scheduler.d.ts +163 -0
  696. package/dist/services/plugin-job-scheduler.d.ts.map +1 -0
  697. package/dist/services/plugin-job-scheduler.js +454 -0
  698. package/dist/services/plugin-job-scheduler.js.map +1 -0
  699. package/dist/services/plugin-job-store.d.ts +208 -0
  700. package/dist/services/plugin-job-store.d.ts.map +1 -0
  701. package/dist/services/plugin-job-store.js +350 -0
  702. package/dist/services/plugin-job-store.js.map +1 -0
  703. package/dist/services/plugin-lifecycle.d.ts +203 -0
  704. package/dist/services/plugin-lifecycle.d.ts.map +1 -0
  705. package/dist/services/plugin-lifecycle.js +501 -0
  706. package/dist/services/plugin-lifecycle.js.map +1 -0
  707. package/dist/services/plugin-loader.d.ts +453 -0
  708. package/dist/services/plugin-loader.d.ts.map +1 -0
  709. package/dist/services/plugin-loader.js +1295 -0
  710. package/dist/services/plugin-loader.js.map +1 -0
  711. package/dist/services/plugin-local-folders.d.ts +49 -0
  712. package/dist/services/plugin-local-folders.d.ts.map +1 -0
  713. package/dist/services/plugin-local-folders.js +510 -0
  714. package/dist/services/plugin-local-folders.js.map +1 -0
  715. package/dist/services/plugin-log-retention.d.ts +20 -0
  716. package/dist/services/plugin-log-retention.d.ts.map +1 -0
  717. package/dist/services/plugin-log-retention.js +63 -0
  718. package/dist/services/plugin-log-retention.js.map +1 -0
  719. package/dist/services/plugin-managed-agents.d.ts +15 -0
  720. package/dist/services/plugin-managed-agents.d.ts.map +1 -0
  721. package/dist/services/plugin-managed-agents.js +457 -0
  722. package/dist/services/plugin-managed-agents.js.map +1 -0
  723. package/dist/services/plugin-managed-routines.d.ts +42 -0
  724. package/dist/services/plugin-managed-routines.d.ts.map +1 -0
  725. package/dist/services/plugin-managed-routines.js +416 -0
  726. package/dist/services/plugin-managed-routines.js.map +1 -0
  727. package/dist/services/plugin-managed-skills.d.ts +14 -0
  728. package/dist/services/plugin-managed-skills.d.ts.map +1 -0
  729. package/dist/services/plugin-managed-skills.js +264 -0
  730. package/dist/services/plugin-managed-skills.js.map +1 -0
  731. package/dist/services/plugin-manifest-validator.d.ts +79 -0
  732. package/dist/services/plugin-manifest-validator.d.ts.map +1 -0
  733. package/dist/services/plugin-manifest-validator.js +84 -0
  734. package/dist/services/plugin-manifest-validator.js.map +1 -0
  735. package/dist/services/plugin-registry.d.ts +2550 -0
  736. package/dist/services/plugin-registry.d.ts.map +1 -0
  737. package/dist/services/plugin-registry.js +581 -0
  738. package/dist/services/plugin-registry.js.map +1 -0
  739. package/dist/services/plugin-runtime-sandbox.d.ts +40 -0
  740. package/dist/services/plugin-runtime-sandbox.d.ts.map +1 -0
  741. package/dist/services/plugin-runtime-sandbox.js +154 -0
  742. package/dist/services/plugin-runtime-sandbox.js.map +1 -0
  743. package/dist/services/plugin-secrets-handler.d.ts +83 -0
  744. package/dist/services/plugin-secrets-handler.d.ts.map +1 -0
  745. package/dist/services/plugin-secrets-handler.js +168 -0
  746. package/dist/services/plugin-secrets-handler.js.map +1 -0
  747. package/dist/services/plugin-state-store.d.ts +92 -0
  748. package/dist/services/plugin-state-store.d.ts.map +1 -0
  749. package/dist/services/plugin-state-store.js +190 -0
  750. package/dist/services/plugin-state-store.js.map +1 -0
  751. package/dist/services/plugin-stream-bus.d.ts +29 -0
  752. package/dist/services/plugin-stream-bus.d.ts.map +1 -0
  753. package/dist/services/plugin-stream-bus.js +48 -0
  754. package/dist/services/plugin-stream-bus.js.map +1 -0
  755. package/dist/services/plugin-tool-dispatcher.d.ts +181 -0
  756. package/dist/services/plugin-tool-dispatcher.d.ts.map +1 -0
  757. package/dist/services/plugin-tool-dispatcher.js +224 -0
  758. package/dist/services/plugin-tool-dispatcher.js.map +1 -0
  759. package/dist/services/plugin-tool-registry.d.ts +192 -0
  760. package/dist/services/plugin-tool-registry.d.ts.map +1 -0
  761. package/dist/services/plugin-tool-registry.js +224 -0
  762. package/dist/services/plugin-tool-registry.js.map +1 -0
  763. package/dist/services/plugin-worker-manager.d.ts +262 -0
  764. package/dist/services/plugin-worker-manager.d.ts.map +1 -0
  765. package/dist/services/plugin-worker-manager.js +942 -0
  766. package/dist/services/plugin-worker-manager.js.map +1 -0
  767. package/dist/services/portable-path.d.ts +2 -0
  768. package/dist/services/portable-path.d.ts.map +1 -0
  769. package/dist/services/portable-path.js +15 -0
  770. package/dist/services/portable-path.js.map +1 -0
  771. package/dist/services/principal-access-compatibility.d.ts +26 -0
  772. package/dist/services/principal-access-compatibility.d.ts.map +1 -0
  773. package/dist/services/principal-access-compatibility.js +94 -0
  774. package/dist/services/principal-access-compatibility.js.map +1 -0
  775. package/dist/services/productivity-review.d.ts +83 -0
  776. package/dist/services/productivity-review.d.ts.map +1 -0
  777. package/dist/services/productivity-review.js +652 -0
  778. package/dist/services/productivity-review.js.map +1 -0
  779. package/dist/services/project-workspace-runtime-config.d.ts +4 -0
  780. package/dist/services/project-workspace-runtime-config.d.ts.map +1 -0
  781. package/dist/services/project-workspace-runtime-config.js +54 -0
  782. package/dist/services/project-workspace-runtime-config.js.map +1 -0
  783. package/dist/services/projects.d.ts +99 -0
  784. package/dist/services/projects.d.ts.map +1 -0
  785. package/dist/services/projects.js +879 -0
  786. package/dist/services/projects.js.map +1 -0
  787. package/dist/services/quota-windows.d.ts +9 -0
  788. package/dist/services/quota-windows.d.ts.map +1 -0
  789. package/dist/services/quota-windows.js +56 -0
  790. package/dist/services/quota-windows.js.map +1 -0
  791. package/dist/services/recovery/index.d.ts +10 -0
  792. package/dist/services/recovery/index.d.ts.map +1 -0
  793. package/dist/services/recovery/index.js +6 -0
  794. package/dist/services/recovery/index.js.map +1 -0
  795. package/dist/services/recovery/issue-graph-liveness.d.ts +85 -0
  796. package/dist/services/recovery/issue-graph-liveness.d.ts.map +1 -0
  797. package/dist/services/recovery/issue-graph-liveness.js +356 -0
  798. package/dist/services/recovery/issue-graph-liveness.js.map +1 -0
  799. package/dist/services/recovery/model-profile-hint.d.ts +21 -0
  800. package/dist/services/recovery/model-profile-hint.d.ts.map +1 -0
  801. package/dist/services/recovery/model-profile-hint.js +36 -0
  802. package/dist/services/recovery/model-profile-hint.js.map +1 -0
  803. package/dist/services/recovery/model-profile-hint.test.d.ts +2 -0
  804. package/dist/services/recovery/model-profile-hint.test.d.ts.map +1 -0
  805. package/dist/services/recovery/model-profile-hint.test.js +38 -0
  806. package/dist/services/recovery/model-profile-hint.test.js.map +1 -0
  807. package/dist/services/recovery/origins.d.ts +36 -0
  808. package/dist/services/recovery/origins.d.ts.map +1 -0
  809. package/dist/services/recovery/origins.js +45 -0
  810. package/dist/services/recovery/origins.js.map +1 -0
  811. package/dist/services/recovery/pause-hold-guard.d.ts +6 -0
  812. package/dist/services/recovery/pause-hold-guard.d.ts.map +1 -0
  813. package/dist/services/recovery/pause-hold-guard.js +6 -0
  814. package/dist/services/recovery/pause-hold-guard.js.map +1 -0
  815. package/dist/services/recovery/run-liveness-continuations.d.ts +50 -0
  816. package/dist/services/recovery/run-liveness-continuations.d.ts.map +1 -0
  817. package/dist/services/recovery/run-liveness-continuations.js +117 -0
  818. package/dist/services/recovery/run-liveness-continuations.js.map +1 -0
  819. package/dist/services/recovery/service.d.ts +258 -0
  820. package/dist/services/recovery/service.d.ts.map +1 -0
  821. package/dist/services/recovery/service.js +2892 -0
  822. package/dist/services/recovery/service.js.map +1 -0
  823. package/dist/services/recovery/successful-run-handoff.d.ts +89 -0
  824. package/dist/services/recovery/successful-run-handoff.d.ts.map +1 -0
  825. package/dist/services/recovery/successful-run-handoff.js +304 -0
  826. package/dist/services/recovery/successful-run-handoff.js.map +1 -0
  827. package/dist/services/recovery/successful-run-handoff.test.d.ts +2 -0
  828. package/dist/services/recovery/successful-run-handoff.test.d.ts.map +1 -0
  829. package/dist/services/recovery/successful-run-handoff.test.js +276 -0
  830. package/dist/services/recovery/successful-run-handoff.test.js.map +1 -0
  831. package/dist/services/resource-memberships.d.ts +55 -0
  832. package/dist/services/resource-memberships.d.ts.map +1 -0
  833. package/dist/services/resource-memberships.js +213 -0
  834. package/dist/services/resource-memberships.js.map +1 -0
  835. package/dist/services/routines.d.ts +170 -0
  836. package/dist/services/routines.d.ts.map +1 -0
  837. package/dist/services/routines.js +2015 -0
  838. package/dist/services/routines.js.map +1 -0
  839. package/dist/services/run-continuations.d.ts +3 -0
  840. package/dist/services/run-continuations.d.ts.map +1 -0
  841. package/dist/services/run-continuations.js +2 -0
  842. package/dist/services/run-continuations.js.map +1 -0
  843. package/dist/services/run-liveness.d.ts +46 -0
  844. package/dist/services/run-liveness.d.ts.map +1 -0
  845. package/dist/services/run-liveness.js +275 -0
  846. package/dist/services/run-liveness.js.map +1 -0
  847. package/dist/services/run-log-store.d.ts +34 -0
  848. package/dist/services/run-log-store.d.ts.map +1 -0
  849. package/dist/services/run-log-store.js +111 -0
  850. package/dist/services/run-log-store.js.map +1 -0
  851. package/dist/services/sandbox-provider-runtime.d.ts +132 -0
  852. package/dist/services/sandbox-provider-runtime.d.ts.map +1 -0
  853. package/dist/services/sandbox-provider-runtime.js +216 -0
  854. package/dist/services/sandbox-provider-runtime.js.map +1 -0
  855. package/dist/services/secrets.d.ts +1991 -0
  856. package/dist/services/secrets.d.ts.map +1 -0
  857. package/dist/services/secrets.js +1781 -0
  858. package/dist/services/secrets.js.map +1 -0
  859. package/dist/services/session-workspace-cwd.d.ts +2 -0
  860. package/dist/services/session-workspace-cwd.d.ts.map +1 -0
  861. package/dist/services/session-workspace-cwd.js +24 -0
  862. package/dist/services/session-workspace-cwd.js.map +1 -0
  863. package/dist/services/session-workspace-cwd.test.d.ts +2 -0
  864. package/dist/services/session-workspace-cwd.test.d.ts.map +1 -0
  865. package/dist/services/session-workspace-cwd.test.js +25 -0
  866. package/dist/services/session-workspace-cwd.test.js.map +1 -0
  867. package/dist/services/sidebar-badges.d.ts +14 -0
  868. package/dist/services/sidebar-badges.d.ts.map +1 -0
  869. package/dist/services/sidebar-badges.js +48 -0
  870. package/dist/services/sidebar-badges.js.map +1 -0
  871. package/dist/services/sidebar-preferences.d.ts +9 -0
  872. package/dist/services/sidebar-preferences.d.ts.map +1 -0
  873. package/dist/services/sidebar-preferences.js +82 -0
  874. package/dist/services/sidebar-preferences.js.map +1 -0
  875. package/dist/services/skills-catalog.d.ts +14 -0
  876. package/dist/services/skills-catalog.d.ts.map +1 -0
  877. package/dist/services/skills-catalog.js +171 -0
  878. package/dist/services/skills-catalog.js.map +1 -0
  879. package/dist/services/squad-export-readme.d.ts +17 -0
  880. package/dist/services/squad-export-readme.d.ts.map +1 -0
  881. package/dist/services/squad-export-readme.js +148 -0
  882. package/dist/services/squad-export-readme.js.map +1 -0
  883. package/dist/services/squad-member-roles.d.ts +9 -0
  884. package/dist/services/squad-member-roles.d.ts.map +1 -0
  885. package/dist/services/squad-member-roles.js +48 -0
  886. package/dist/services/squad-member-roles.js.map +1 -0
  887. package/dist/services/squad-portability.d.ts +24 -0
  888. package/dist/services/squad-portability.d.ts.map +1 -0
  889. package/dist/services/squad-portability.js +4093 -0
  890. package/dist/services/squad-portability.js.map +1 -0
  891. package/dist/services/squad-search-rate-limit.d.ts +22 -0
  892. package/dist/services/squad-search-rate-limit.d.ts.map +1 -0
  893. package/dist/services/squad-search-rate-limit.js +38 -0
  894. package/dist/services/squad-search-rate-limit.js.map +1 -0
  895. package/dist/services/squad-search.d.ts +8 -0
  896. package/dist/services/squad-search.d.ts.map +1 -0
  897. package/dist/services/squad-search.js +626 -0
  898. package/dist/services/squad-search.js.map +1 -0
  899. package/dist/services/squad-skills.d.ts +107 -0
  900. package/dist/services/squad-skills.d.ts.map +1 -0
  901. package/dist/services/squad-skills.js +3044 -0
  902. package/dist/services/squad-skills.js.map +1 -0
  903. package/dist/services/squads.d.ts +154 -0
  904. package/dist/services/squads.d.ts.map +1 -0
  905. package/dist/services/squads.js +278 -0
  906. package/dist/services/squads.js.map +1 -0
  907. package/dist/services/wake-cycle-guard.d.ts +44 -0
  908. package/dist/services/wake-cycle-guard.d.ts.map +1 -0
  909. package/dist/services/wake-cycle-guard.js +79 -0
  910. package/dist/services/wake-cycle-guard.js.map +1 -0
  911. package/dist/services/wake-cycle-guard.test.d.ts +2 -0
  912. package/dist/services/wake-cycle-guard.test.d.ts.map +1 -0
  913. package/dist/services/wake-cycle-guard.test.js +67 -0
  914. package/dist/services/wake-cycle-guard.test.js.map +1 -0
  915. package/dist/services/work-products.d.ts +14 -0
  916. package/dist/services/work-products.d.ts.map +1 -0
  917. package/dist/services/work-products.js +100 -0
  918. package/dist/services/work-products.js.map +1 -0
  919. package/dist/services/workspace-operation-log-store.d.ts +33 -0
  920. package/dist/services/workspace-operation-log-store.d.ts.map +1 -0
  921. package/dist/services/workspace-operation-log-store.js +110 -0
  922. package/dist/services/workspace-operation-log-store.js.map +1 -0
  923. package/dist/services/workspace-operations.d.ts +44 -0
  924. package/dist/services/workspace-operations.d.ts.map +1 -0
  925. package/dist/services/workspace-operations.js +211 -0
  926. package/dist/services/workspace-operations.js.map +1 -0
  927. package/dist/services/workspace-realization.d.ts +33 -0
  928. package/dist/services/workspace-realization.d.ts.map +1 -0
  929. package/dist/services/workspace-realization.js +221 -0
  930. package/dist/services/workspace-realization.js.map +1 -0
  931. package/dist/services/workspace-runtime-read-model.d.ts +92 -0
  932. package/dist/services/workspace-runtime-read-model.d.ts.map +1 -0
  933. package/dist/services/workspace-runtime-read-model.js +67 -0
  934. package/dist/services/workspace-runtime-read-model.js.map +1 -0
  935. package/dist/services/workspace-runtime.d.ts +252 -0
  936. package/dist/services/workspace-runtime.d.ts.map +1 -0
  937. package/dist/services/workspace-runtime.js +2519 -0
  938. package/dist/services/workspace-runtime.js.map +1 -0
  939. package/dist/startup-banner.d.ts +32 -0
  940. package/dist/startup-banner.d.ts.map +1 -0
  941. package/dist/startup-banner.js +118 -0
  942. package/dist/startup-banner.js.map +1 -0
  943. package/dist/static-index-html.d.ts +2 -0
  944. package/dist/static-index-html.d.ts.map +1 -0
  945. package/dist/static-index-html.js +7 -0
  946. package/dist/static-index-html.js.map +1 -0
  947. package/dist/storage/index.d.ts +6 -0
  948. package/dist/storage/index.d.ts.map +1 -0
  949. package/dist/storage/index.js +29 -0
  950. package/dist/storage/index.js.map +1 -0
  951. package/dist/storage/local-disk-provider.d.ts +3 -0
  952. package/dist/storage/local-disk-provider.d.ts.map +1 -0
  953. package/dist/storage/local-disk-provider.js +85 -0
  954. package/dist/storage/local-disk-provider.js.map +1 -0
  955. package/dist/storage/provider-registry.d.ts +4 -0
  956. package/dist/storage/provider-registry.d.ts.map +1 -0
  957. package/dist/storage/provider-registry.js +15 -0
  958. package/dist/storage/provider-registry.js.map +1 -0
  959. package/dist/storage/s3-provider.d.ts +11 -0
  960. package/dist/storage/s3-provider.d.ts.map +1 -0
  961. package/dist/storage/s3-provider.js +124 -0
  962. package/dist/storage/s3-provider.js.map +1 -0
  963. package/dist/storage/service.d.ts +3 -0
  964. package/dist/storage/service.d.ts.map +1 -0
  965. package/dist/storage/service.js +120 -0
  966. package/dist/storage/service.js.map +1 -0
  967. package/dist/storage/types.d.ts +59 -0
  968. package/dist/storage/types.d.ts.map +1 -0
  969. package/dist/storage/types.js +2 -0
  970. package/dist/storage/types.js.map +1 -0
  971. package/dist/ui-branding.d.ts +13 -0
  972. package/dist/ui-branding.d.ts.map +1 -0
  973. package/dist/ui-branding.js +187 -0
  974. package/dist/ui-branding.js.map +1 -0
  975. package/dist/version.d.ts +2 -0
  976. package/dist/version.d.ts.map +1 -0
  977. package/dist/version.js +5 -0
  978. package/dist/version.js.map +1 -0
  979. package/dist/vite-html-renderer.d.ts +18 -0
  980. package/dist/vite-html-renderer.d.ts.map +1 -0
  981. package/dist/vite-html-renderer.js +61 -0
  982. package/dist/vite-html-renderer.js.map +1 -0
  983. package/dist/worktree-config.d.ts +19 -0
  984. package/dist/worktree-config.d.ts.map +1 -0
  985. package/dist/worktree-config.js +373 -0
  986. package/dist/worktree-config.js.map +1 -0
  987. package/package.json +92 -0
  988. package/skills/diagnose-why-work-stopped/SKILL.md +161 -0
  989. package/skills/para-memory-files/SKILL.md +104 -0
  990. package/skills/para-memory-files/references/schemas.md +35 -0
  991. package/skills/slaw/SKILL.md +371 -0
  992. package/skills/slaw/references/api-reference.md +879 -0
  993. package/skills/slaw/references/artifacts.md +44 -0
  994. package/skills/slaw/references/issue-workspaces.md +80 -0
  995. package/skills/slaw/references/routines.md +187 -0
  996. package/skills/slaw/references/squad-skills.md +258 -0
  997. package/skills/slaw/references/workflows.md +113 -0
  998. package/skills/slaw/scripts/slaw-upload-artifact.sh +371 -0
  999. package/skills/slaw-converting-plans-to-tasks/SKILL.md +42 -0
  1000. package/skills/slaw-create-agent/SKILL.md +163 -0
  1001. package/skills/slaw-create-agent/references/agent-instruction-templates.md +123 -0
  1002. package/skills/slaw-create-agent/references/agents/coder.md +64 -0
  1003. package/skills/slaw-create-agent/references/agents/qa.md +88 -0
  1004. package/skills/slaw-create-agent/references/agents/securityengineer.md +135 -0
  1005. package/skills/slaw-create-agent/references/agents/uxdesigner.md +115 -0
  1006. package/skills/slaw-create-agent/references/api-reference.md +110 -0
  1007. package/skills/slaw-create-agent/references/baseline-role-guide.md +168 -0
  1008. package/skills/slaw-create-agent/references/draft-review-checklist.md +95 -0
  1009. package/skills/slaw-create-plugin/SKILL.md +154 -0
  1010. package/skills/slaw-dev/SKILL.md +267 -0
  1011. package/skills/terminal-bench-loop/SKILL.md +236 -0
  1012. package/ui-dist/android-chrome-192x192.png +0 -0
  1013. package/ui-dist/android-chrome-512x512.png +0 -0
  1014. package/ui-dist/apple-touch-icon.png +0 -0
  1015. package/ui-dist/assets/apl-B4CMkyY2.js +1 -0
  1016. package/ui-dist/assets/arc-xbLjL0VN.js +1 -0
  1017. package/ui-dist/assets/architectureDiagram-3BPJPVTR-KcFd4B-U.js +36 -0
  1018. package/ui-dist/assets/asciiarmor-Df11BRmG.js +1 -0
  1019. package/ui-dist/assets/asn1-EdZsLKOL.js +1 -0
  1020. package/ui-dist/assets/asterisk-B-8jnY81.js +1 -0
  1021. package/ui-dist/assets/blockDiagram-GPEHLZMM-CSD4otEL.js +132 -0
  1022. package/ui-dist/assets/brainfuck-C4LP7Hcl.js +1 -0
  1023. package/ui-dist/assets/c4Diagram-AAUBKEIU-Cre_NEHp.js +10 -0
  1024. package/ui-dist/assets/channel-BFN8obi8.js +1 -0
  1025. package/ui-dist/assets/chunk-2J33WTMH-CssLBsbh.js +1 -0
  1026. package/ui-dist/assets/chunk-4BX2VUAB-DjiavNFv.js +1 -0
  1027. package/ui-dist/assets/chunk-55IACEB6-C_F0yeYq.js +1 -0
  1028. package/ui-dist/assets/chunk-727SXJPM-B1FAOW4a.js +206 -0
  1029. package/ui-dist/assets/chunk-AQP2D5EJ-Do1241W-.js +231 -0
  1030. package/ui-dist/assets/chunk-FMBD7UC4-BQRrOMZD.js +15 -0
  1031. package/ui-dist/assets/chunk-ND2GUHAM-BPSt3kZ1.js +1 -0
  1032. package/ui-dist/assets/chunk-QZHKN3VN-BSpmhWDD.js +1 -0
  1033. package/ui-dist/assets/classDiagram-4FO5ZUOK-1Ay0zFCU.js +1 -0
  1034. package/ui-dist/assets/classDiagram-v2-Q7XG4LA2-1Ay0zFCU.js +1 -0
  1035. package/ui-dist/assets/clike-B9uivgTg.js +1 -0
  1036. package/ui-dist/assets/clojure-BMjYHr_A.js +1 -0
  1037. package/ui-dist/assets/cmake-BQqOBYOt.js +1 -0
  1038. package/ui-dist/assets/cobol-CWcv1MsR.js +1 -0
  1039. package/ui-dist/assets/coffeescript-S37ZYGWr.js +1 -0
  1040. package/ui-dist/assets/commonlisp-DBKNyK5s.js +1 -0
  1041. package/ui-dist/assets/cose-bilkent-S5V4N54A-CK2f2Te4.js +1 -0
  1042. package/ui-dist/assets/crystal-SjHAIU92.js +1 -0
  1043. package/ui-dist/assets/css-BnMrqG3P.js +1 -0
  1044. package/ui-dist/assets/cypher-C_CwsFkJ.js +1 -0
  1045. package/ui-dist/assets/cytoscape.esm-D8joxN9f.js +321 -0
  1046. package/ui-dist/assets/d-pRatUO7H.js +1 -0
  1047. package/ui-dist/assets/dagre-BM42HDAG-DaOXTN9-.js +4 -0
  1048. package/ui-dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  1049. package/ui-dist/assets/diagram-2AECGRRQ-D0ScQUGy.js +43 -0
  1050. package/ui-dist/assets/diagram-5GNKFQAL-7mH4Cncd.js +10 -0
  1051. package/ui-dist/assets/diagram-KO2AKTUF-aA9kuK-7.js +3 -0
  1052. package/ui-dist/assets/diagram-LMA3HP47-C9UXfmdK.js +24 -0
  1053. package/ui-dist/assets/diagram-OG6HWLK6-Ba3U-x1r.js +24 -0
  1054. package/ui-dist/assets/diff-DbItnlRl.js +1 -0
  1055. package/ui-dist/assets/dockerfile-BKs6k2Af.js +1 -0
  1056. package/ui-dist/assets/dtd-DF_7sFjM.js +1 -0
  1057. package/ui-dist/assets/dylan-DwRh75JA.js +1 -0
  1058. package/ui-dist/assets/ebnf-CDyGwa7X.js +1 -0
  1059. package/ui-dist/assets/ecl-Cabwm37j.js +1 -0
  1060. package/ui-dist/assets/eiffel-CnydiIhH.js +1 -0
  1061. package/ui-dist/assets/elm-vLlmbW-K.js +1 -0
  1062. package/ui-dist/assets/erDiagram-TEJ5UH35-CmskPKH1.js +85 -0
  1063. package/ui-dist/assets/erlang-BNw1qcRV.js +1 -0
  1064. package/ui-dist/assets/factor-kuTfRLto.js +1 -0
  1065. package/ui-dist/assets/fcl-Kvtd6kyn.js +1 -0
  1066. package/ui-dist/assets/flowDiagram-I6XJVG4X-B0iEPqGd.js +162 -0
  1067. package/ui-dist/assets/forth-Ffai-XNe.js +1 -0
  1068. package/ui-dist/assets/fortran-DYz_wnZ1.js +1 -0
  1069. package/ui-dist/assets/ganttDiagram-6RSMTGT7-DtpxlgWQ.js +292 -0
  1070. package/ui-dist/assets/gas-Bneqetm1.js +1 -0
  1071. package/ui-dist/assets/gherkin-heZmZLOM.js +1 -0
  1072. package/ui-dist/assets/gitGraphDiagram-PVQCEYII-VefBjqya.js +106 -0
  1073. package/ui-dist/assets/graph-CAnANduQ.js +1 -0
  1074. package/ui-dist/assets/groovy-D9Dt4D0W.js +1 -0
  1075. package/ui-dist/assets/haskell-Cw1EW3IL.js +1 -0
  1076. package/ui-dist/assets/haxe-H-WmDvRZ.js +1 -0
  1077. package/ui-dist/assets/http-DBlCnlav.js +1 -0
  1078. package/ui-dist/assets/idl-BEugSyMb.js +1 -0
  1079. package/ui-dist/assets/index-B9KxOFt-.js +1 -0
  1080. package/ui-dist/assets/index-BMPCuc-W.js +1 -0
  1081. package/ui-dist/assets/index-Bbfs2D7R.js +1 -0
  1082. package/ui-dist/assets/index-BrgHE5Lg.js +1 -0
  1083. package/ui-dist/assets/index-C5q-Cwlp.js +7 -0
  1084. package/ui-dist/assets/index-C6LpKpr3.js +1 -0
  1085. package/ui-dist/assets/index-CIzt5DFV.js +1 -0
  1086. package/ui-dist/assets/index-CRwAuYPj.js +1 -0
  1087. package/ui-dist/assets/index-CTEnIXsJ.js +1 -0
  1088. package/ui-dist/assets/index-CXGemv2V.js +1 -0
  1089. package/ui-dist/assets/index-ClDiS51u.js +1 -0
  1090. package/ui-dist/assets/index-CvKYfvpz.js +1 -0
  1091. package/ui-dist/assets/index-D2IqxlXD.js +1 -0
  1092. package/ui-dist/assets/index-D97fJMFR.js +522 -0
  1093. package/ui-dist/assets/index-DDHdUa2f.js +1 -0
  1094. package/ui-dist/assets/index-DMZ0QXqi.js +1 -0
  1095. package/ui-dist/assets/index-DMi4KpxO.js +6 -0
  1096. package/ui-dist/assets/index-DZB48Gve.js +1 -0
  1097. package/ui-dist/assets/index-Drr9zRdK.css +1 -0
  1098. package/ui-dist/assets/index-DtGqpE43.js +1 -0
  1099. package/ui-dist/assets/index-Du18kURt.js +2 -0
  1100. package/ui-dist/assets/index-KaLXuTqA.js +1 -0
  1101. package/ui-dist/assets/index-j5NgiILm.js +13 -0
  1102. package/ui-dist/assets/index-u0SfLZ3g.js +3 -0
  1103. package/ui-dist/assets/infoDiagram-5YYISTIA-D2OGH-dO.js +2 -0
  1104. package/ui-dist/assets/init-Gi6I4Gst.js +1 -0
  1105. package/ui-dist/assets/ishikawaDiagram-YF4QCWOH-CnMf3BJj.js +70 -0
  1106. package/ui-dist/assets/javascript-iXu5QeM3.js +1 -0
  1107. package/ui-dist/assets/journeyDiagram-JHISSGLW-BaXdD53T.js +139 -0
  1108. package/ui-dist/assets/julia-DuME0IfC.js +1 -0
  1109. package/ui-dist/assets/kanban-definition-UN3LZRKU-Brt7LjHm.js +89 -0
  1110. package/ui-dist/assets/katex-yT8l5JNH.js +257 -0
  1111. package/ui-dist/assets/layout-DGIYPm2g.js +1 -0
  1112. package/ui-dist/assets/linear-536T6Mkh.js +1 -0
  1113. package/ui-dist/assets/livescript-BwQOo05w.js +1 -0
  1114. package/ui-dist/assets/lua-VAEuO923.js +1 -0
  1115. package/ui-dist/assets/mathematica-DTrFuWx2.js +1 -0
  1116. package/ui-dist/assets/mbox-CNhZ1qSd.js +1 -0
  1117. package/ui-dist/assets/mermaid.core-CURTLVBm.js +303 -0
  1118. package/ui-dist/assets/mindmap-definition-RKZ34NQL-S2tDCU-U.js +96 -0
  1119. package/ui-dist/assets/mirc-CjQqDB4T.js +1 -0
  1120. package/ui-dist/assets/mllike-CXdrOF99.js +1 -0
  1121. package/ui-dist/assets/modelica-Dc1JOy9r.js +1 -0
  1122. package/ui-dist/assets/mscgen-BA5vi2Kp.js +1 -0
  1123. package/ui-dist/assets/mumps-BT43cFF4.js +1 -0
  1124. package/ui-dist/assets/nginx-DdIZxoE0.js +1 -0
  1125. package/ui-dist/assets/nsis-LdVXkNf5.js +1 -0
  1126. package/ui-dist/assets/ntriples-BfvgReVJ.js +1 -0
  1127. package/ui-dist/assets/octave-Ck1zUtKM.js +1 -0
  1128. package/ui-dist/assets/ordinal-Cboi1Yqb.js +1 -0
  1129. package/ui-dist/assets/oz-BzwKVEFT.js +1 -0
  1130. package/ui-dist/assets/pascal--L3eBynH.js +1 -0
  1131. package/ui-dist/assets/perl-CdXCOZ3F.js +1 -0
  1132. package/ui-dist/assets/pieDiagram-4H26LBE5-DD_Ih32z.js +30 -0
  1133. package/ui-dist/assets/pig-CevX1Tat.js +1 -0
  1134. package/ui-dist/assets/powershell-CFHJl5sT.js +1 -0
  1135. package/ui-dist/assets/properties-C78fOPTZ.js +1 -0
  1136. package/ui-dist/assets/protobuf-ChK-085T.js +1 -0
  1137. package/ui-dist/assets/pug-DeIclll2.js +1 -0
  1138. package/ui-dist/assets/puppet-DMA9R1ak.js +1 -0
  1139. package/ui-dist/assets/python-BuPzkPfP.js +1 -0
  1140. package/ui-dist/assets/q-pXgVlZs6.js +1 -0
  1141. package/ui-dist/assets/quadrantDiagram-W4KKPZXB-DA5BPBIK.js +7 -0
  1142. package/ui-dist/assets/r-B6wPVr8A.js +1 -0
  1143. package/ui-dist/assets/requirementDiagram-4Y6WPE33-Em8SPCro.js +84 -0
  1144. package/ui-dist/assets/rpm-CTu-6PCP.js +1 -0
  1145. package/ui-dist/assets/ruby-B2Rjki9n.js +1 -0
  1146. package/ui-dist/assets/sankeyDiagram-5OEKKPKP-BJVC4haY.js +40 -0
  1147. package/ui-dist/assets/sas-B4kiWyti.js +1 -0
  1148. package/ui-dist/assets/scheme-C41bIUwD.js +1 -0
  1149. package/ui-dist/assets/sequenceDiagram-3UESZ5HK-Cskntadf.js +162 -0
  1150. package/ui-dist/assets/shell-CjFT_Tl9.js +1 -0
  1151. package/ui-dist/assets/sieve-C3Gn_uJK.js +1 -0
  1152. package/ui-dist/assets/simple-mode-GW_nhZxv.js +1 -0
  1153. package/ui-dist/assets/smalltalk-CnHTOXQT.js +1 -0
  1154. package/ui-dist/assets/solr-DehyRSwq.js +1 -0
  1155. package/ui-dist/assets/sparql-DkYu6x3z.js +1 -0
  1156. package/ui-dist/assets/spreadsheet-BCZA_wO0.js +1 -0
  1157. package/ui-dist/assets/sql-D0XecflT.js +1 -0
  1158. package/ui-dist/assets/stateDiagram-AJRCARHV-CxlfdaOi.js +1 -0
  1159. package/ui-dist/assets/stateDiagram-v2-BHNVJYJU-eTgftUjW.js +1 -0
  1160. package/ui-dist/assets/stex-C3f8Ysf7.js +1 -0
  1161. package/ui-dist/assets/stylus-B533Al4x.js +1 -0
  1162. package/ui-dist/assets/swift-BzpIVaGY.js +1 -0
  1163. package/ui-dist/assets/tcl-DVfN8rqt.js +1 -0
  1164. package/ui-dist/assets/textile-CnDTJFAw.js +1 -0
  1165. package/ui-dist/assets/tiddlywiki-DO-Gjzrf.js +1 -0
  1166. package/ui-dist/assets/tiki-DGYXhP31.js +1 -0
  1167. package/ui-dist/assets/timeline-definition-PNZ67QCA-LOdaWSSa.js +120 -0
  1168. package/ui-dist/assets/toml-Bm5Em-hy.js +1 -0
  1169. package/ui-dist/assets/troff-wAsdV37c.js +1 -0
  1170. package/ui-dist/assets/ttcn-CfJYG6tj.js +1 -0
  1171. package/ui-dist/assets/ttcn-cfg-B9xdYoR4.js +1 -0
  1172. package/ui-dist/assets/turtle-B1tBg_DP.js +1 -0
  1173. package/ui-dist/assets/vb-CmGdzxic.js +1 -0
  1174. package/ui-dist/assets/vbscript-BuJXcnF6.js +1 -0
  1175. package/ui-dist/assets/velocity-D8B20fx6.js +1 -0
  1176. package/ui-dist/assets/vennDiagram-CIIHVFJN-CJ4ji6B3.js +34 -0
  1177. package/ui-dist/assets/verilog-C6RDOZhf.js +1 -0
  1178. package/ui-dist/assets/vhdl-lSbBsy5d.js +1 -0
  1179. package/ui-dist/assets/wardley-L42UT6IY-CxnVdUVH.js +153 -0
  1180. package/ui-dist/assets/wardleyDiagram-YWT4CUSO-CgGDttpl.js +78 -0
  1181. package/ui-dist/assets/webidl-ZXfAyPTL.js +1 -0
  1182. package/ui-dist/assets/xquery-DzFWVndE.js +1 -0
  1183. package/ui-dist/assets/xychartDiagram-2RQKCTM6-zuQa7bqx.js +7 -0
  1184. package/ui-dist/assets/yacas-BJ4BC0dw.js +1 -0
  1185. package/ui-dist/assets/z80-Hz9HOZM7.js +1 -0
  1186. package/ui-dist/brands/opencode-logo-dark-square.svg +18 -0
  1187. package/ui-dist/brands/opencode-logo-light-square.svg +18 -0
  1188. package/ui-dist/favicon-16x16.png +0 -0
  1189. package/ui-dist/favicon-32x32.png +0 -0
  1190. package/ui-dist/favicon.ico +0 -0
  1191. package/ui-dist/favicon.svg +8 -0
  1192. package/ui-dist/index.html +46 -0
  1193. package/ui-dist/site.webmanifest +30 -0
  1194. package/ui-dist/sw.js +42 -0
  1195. package/ui-dist/worktree-favicon-16x16.png +0 -0
  1196. package/ui-dist/worktree-favicon-32x32.png +0 -0
  1197. package/ui-dist/worktree-favicon.ico +0 -0
  1198. package/ui-dist/worktree-favicon.svg +9 -0
@@ -0,0 +1,2892 @@
1
+ import { and, asc, desc, eq, gt, gte, inArray, isNull, notInArray, sql } from "drizzle-orm";
2
+ import { DEFAULT_ISSUE_GRAPH_LIVENESS_AUTO_RECOVERY_LOOKBACK_HOURS, MAX_ISSUE_GRAPH_LIVENESS_AUTO_RECOVERY_LOOKBACK_HOURS, MIN_ISSUE_GRAPH_LIVENESS_AUTO_RECOVERY_LOOKBACK_HOURS, } from "@slaw-ai/shared";
3
+ import { agents, agentWakeupRequests, approvals, activityLog, squads, heartbeatRunEvents, heartbeatRunWatchdogDecisions, heartbeatRuns, issueComments, issueApprovals, issueRecoveryActions, issueRelations, issueThreadInteractions, issues, } from "@slaw-ai/db";
4
+ import { parseObject, asBoolean, asNumber } from "../../adapters/utils.js";
5
+ import { runningProcesses } from "../../adapters/index.js";
6
+ import { forbidden, notFound } from "../../errors.js";
7
+ import { logger } from "../../middleware/logger.js";
8
+ import { isPidAlive, isProcessGroupAlive, terminateLocalService } from "../local-service-supervisor.js";
9
+ import { redactCurrentUserText } from "../../log-redaction.js";
10
+ import { redactSensitiveText } from "../../redaction.js";
11
+ import { logActivity } from "../activity-log.js";
12
+ import { budgetService } from "../budgets.js";
13
+ import { instanceSettingsService } from "../instance-settings.js";
14
+ import { issueRecoveryActionService } from "../issue-recovery-actions.js";
15
+ import { issueTreeControlService } from "../issue-tree-control.js";
16
+ import { issueService } from "../issues.js";
17
+ import { getRunLogStore } from "../run-log-store.js";
18
+ import { DEFAULT_MAX_SUCCESSFUL_RUN_HANDOFF_ATTEMPTS, FINISH_SUCCESSFUL_RUN_HANDOFF_REASON, SUCCESSFUL_RUN_MISSING_STATE_REASON, buildSuccessfulRunHandoffExhaustedNotice, noticeMetadataReferencesRecoveryAction, } from "./successful-run-handoff.js";
19
+ import { RECOVERY_ORIGIN_KINDS, buildIssueGraphLivenessLeafKey, isStrandedIssueRecoveryOriginKind, parseIssueGraphLivenessIncidentKey, } from "./origins.js";
20
+ import { classifyIssueGraphLiveness, } from "./issue-graph-liveness.js";
21
+ import { recoveryAssigneeAdapterOverrides, withRecoveryModelProfileHint, } from "./model-profile-hint.js";
22
+ import { isAutomaticRecoverySuppressedByPauseHold } from "./pause-hold-guard.js";
23
+ const EXECUTION_PATH_HEARTBEAT_RUN_STATUSES = ["queued", "running", "scheduled_retry"];
24
+ const UNSUCCESSFUL_HEARTBEAT_RUN_TERMINAL_STATUSES = ["failed", "cancelled", "timed_out"];
25
+ export const ACTIVE_RUN_OUTPUT_SUSPICION_THRESHOLD_MS = 60 * 60 * 1000;
26
+ export const ACTIVE_RUN_OUTPUT_CRITICAL_THRESHOLD_MS = 4 * 60 * 60 * 1000;
27
+ export const ACTIVE_RUN_OUTPUT_CONTINUE_REARM_MS = 30 * 60 * 1000;
28
+ const ACTIVE_RUN_OUTPUT_EVIDENCE_TAIL_BYTES = 8 * 1024;
29
+ const STRANDED_ISSUE_RECOVERY_ORIGIN_KIND = RECOVERY_ORIGIN_KINDS.strandedIssueRecovery;
30
+ const STALE_ACTIVE_RUN_EVALUATION_ORIGIN_KIND = RECOVERY_ORIGIN_KINDS.staleActiveRunEvaluation;
31
+ const DEFERRED_WAKE_CONTEXT_KEY = "_slawWakeContext";
32
+ const SESSIONED_LOCAL_ADAPTERS = new Set([
33
+ "claude_local",
34
+ "codex_local",
35
+ "cursor",
36
+ "gemini_local",
37
+ "hermes_local",
38
+ "opencode_local",
39
+ "pi_local",
40
+ ]);
41
+ function readNonEmptyString(value) {
42
+ return typeof value === "string" && value.trim().length > 0 ? value : null;
43
+ }
44
+ function summarizeRunFailureForIssueComment(run) {
45
+ if (!run)
46
+ return null;
47
+ if (readNonEmptyString(run.error) || readNonEmptyString(run.errorCode)) {
48
+ return " Latest retry failure details were withheld from the issue thread; inspect the linked run for evidence.";
49
+ }
50
+ return null;
51
+ }
52
+ function didAutomaticRecoveryFail(latestRun, expectedRetryReason) {
53
+ if (!latestRun)
54
+ return false;
55
+ const latestContext = parseObject(latestRun.contextSnapshot);
56
+ const latestRetryReason = readNonEmptyString(latestContext.retryReason);
57
+ return latestRetryReason === expectedRetryReason &&
58
+ UNSUCCESSFUL_HEARTBEAT_RUN_TERMINAL_STATUSES.includes(latestRun.status);
59
+ }
60
+ const TRANSIENT_INFRA_CONTINUATION_ERROR_CODES = new Set([
61
+ "adapter_failed",
62
+ "codex_transient_upstream",
63
+ "claude_transient_upstream",
64
+ "timeout",
65
+ ]);
66
+ const NON_RETRYABLE_CONTINUATION_ERROR_CODES = new Set([
67
+ "agent_not_invokable",
68
+ "agent_not_found",
69
+ "budget_blocked",
70
+ "budget_exhausted",
71
+ "issue_paused",
72
+ "issue_dependencies_blocked",
73
+ ]);
74
+ const CONTINUATION_RECOVERY_TRANSIENT_MAX_ATTEMPTS = 3;
75
+ const CONTINUATION_RECOVERY_DEFAULT_MAX_ATTEMPTS = 1;
76
+ const CONTINUATION_RECOVERY_TRANSIENT_BASE_BACKOFF_MS = 60_000;
77
+ function classifyContinuationFailure(latestRun) {
78
+ const errorCode = readNonEmptyString(latestRun?.errorCode);
79
+ if (errorCode && NON_RETRYABLE_CONTINUATION_ERROR_CODES.has(errorCode)) {
80
+ return { kind: "non_retryable", maxAttempts: 0, baseBackoffMs: 0, errorCode };
81
+ }
82
+ if (errorCode && TRANSIENT_INFRA_CONTINUATION_ERROR_CODES.has(errorCode)) {
83
+ return {
84
+ kind: "transient_infra",
85
+ maxAttempts: CONTINUATION_RECOVERY_TRANSIENT_MAX_ATTEMPTS,
86
+ baseBackoffMs: CONTINUATION_RECOVERY_TRANSIENT_BASE_BACKOFF_MS,
87
+ errorCode,
88
+ };
89
+ }
90
+ return {
91
+ kind: "default",
92
+ maxAttempts: CONTINUATION_RECOVERY_DEFAULT_MAX_ATTEMPTS,
93
+ baseBackoffMs: 0,
94
+ errorCode,
95
+ };
96
+ }
97
+ function successfulRunHandoffRecoveryEvidence(latestRun) {
98
+ if (!latestRun)
99
+ return null;
100
+ const context = parseObject(latestRun.contextSnapshot);
101
+ const wakeReason = readNonEmptyString(context.wakeReason);
102
+ const handoffReason = readNonEmptyString(context.handoffReason);
103
+ const isSuccessfulRunHandoff = wakeReason === FINISH_SUCCESSFUL_RUN_HANDOFF_REASON ||
104
+ handoffReason === SUCCESSFUL_RUN_MISSING_STATE_REASON ||
105
+ asBoolean(context.handoffRequired, false) === true;
106
+ if (!isSuccessfulRunHandoff)
107
+ return null;
108
+ const handoffAttempt = asNumber(context.handoffAttempt, 1);
109
+ const maxHandoffAttempts = asNumber(context.maxHandoffAttempts, DEFAULT_MAX_SUCCESSFUL_RUN_HANDOFF_ATTEMPTS);
110
+ return {
111
+ sourceRunId: readNonEmptyString(context.sourceRunId) ?? readNonEmptyString(context.resumeFromRunId),
112
+ correctiveRunId: latestRun.id,
113
+ missingDisposition: readNonEmptyString(context.missingDisposition) ?? "clear_next_step",
114
+ handoffAttempt,
115
+ maxHandoffAttempts,
116
+ };
117
+ }
118
+ function isExhaustedSuccessfulRunHandoff(latestRun) {
119
+ const evidence = successfulRunHandoffRecoveryEvidence(latestRun);
120
+ if (!evidence)
121
+ return null;
122
+ if (evidence.handoffAttempt < evidence.maxHandoffAttempts)
123
+ return { ...evidence, exhausted: false };
124
+ return { ...evidence, exhausted: true };
125
+ }
126
+ function issueIdFromRunContext(contextSnapshot) {
127
+ const context = parseObject(contextSnapshot);
128
+ return readNonEmptyString(context.issueId) ?? readNonEmptyString(context.taskId);
129
+ }
130
+ function issueIdFromWakePayload(payload) {
131
+ const parsed = parseObject(payload);
132
+ const nestedContext = parseObject(parsed[DEFERRED_WAKE_CONTEXT_KEY]);
133
+ return readNonEmptyString(parsed.issueId) ??
134
+ readNonEmptyString(nestedContext.issueId) ??
135
+ readNonEmptyString(nestedContext.taskId);
136
+ }
137
+ function issueUiLink(issue, prefix) {
138
+ const label = issue.identifier ?? issue.id;
139
+ return `[${label}](/${prefix}/issues/${label})`;
140
+ }
141
+ function runUiLink(run, prefix) {
142
+ return `[${run.id}](/${prefix}/agents/${run.agentId}/runs/${run.id})`;
143
+ }
144
+ function agentUiLink(agent, prefix) {
145
+ if (!agent)
146
+ return "unknown";
147
+ return `[${agent.name ?? agent.id}](/${prefix}/agents/${agent.id})`;
148
+ }
149
+ function formatDuration(ms) {
150
+ if (ms === null)
151
+ return "unknown";
152
+ const minutes = Math.floor(ms / 60_000);
153
+ if (minutes < 60)
154
+ return `${minutes}m`;
155
+ const hours = Math.floor(minutes / 60);
156
+ const remainingMinutes = minutes % 60;
157
+ return remainingMinutes > 0 ? `${hours}h ${remainingMinutes}m` : `${hours}h`;
158
+ }
159
+ function formatIssueLinksForComment(relations) {
160
+ const identifiers = [
161
+ ...new Set(relations
162
+ .map((relation) => relation.identifier)
163
+ .filter((identifier) => Boolean(identifier))),
164
+ ];
165
+ if (identifiers.length === 0)
166
+ return "another open issue";
167
+ return identifiers
168
+ .slice(0, 5)
169
+ .map((identifier) => {
170
+ const prefix = identifier.split("-")[0] || "PAP";
171
+ return `[${identifier}](/${prefix}/issues/${identifier})`;
172
+ })
173
+ .join(", ");
174
+ }
175
+ function unwrapDatabaseConflictError(error) {
176
+ if (!error || typeof error !== "object")
177
+ return null;
178
+ const candidate = error;
179
+ if (typeof candidate.code === "string" ||
180
+ typeof candidate.constraint === "string" ||
181
+ typeof candidate.constraint_name === "string") {
182
+ return candidate;
183
+ }
184
+ const cause = candidate.cause;
185
+ if (!cause || typeof cause !== "object")
186
+ return candidate;
187
+ return cause;
188
+ }
189
+ function isAgentInvokable(agent) {
190
+ return Boolean(agent && !["paused", "terminated", "pending_approval"].includes(agent.status));
191
+ }
192
+ function isStrandedIssueRecoveryIssue(issue) {
193
+ return isStrandedIssueRecoveryOriginKind(issue.originKind);
194
+ }
195
+ function isUnsuccessfulTerminalIssueRun(latestRun) {
196
+ return Boolean(latestRun &&
197
+ UNSUCCESSFUL_HEARTBEAT_RUN_TERMINAL_STATUSES.includes(latestRun.status));
198
+ }
199
+ function isSuccessfulInProgressContinuationRun(latestRun) {
200
+ return latestRun?.status === "succeeded";
201
+ }
202
+ function isProductiveContinuationRun(latestRun) {
203
+ return latestRun?.status === "succeeded" &&
204
+ (latestRun.livenessState === "advanced" ||
205
+ latestRun.livenessState === "completed" ||
206
+ latestRun.livenessState === "blocked" ||
207
+ latestRun.livenessState === "needs_followup");
208
+ }
209
+ function isRepeatedProductiveContinuationRecovery(latestRun) {
210
+ const latestContext = parseObject(latestRun.contextSnapshot);
211
+ return readNonEmptyString(latestContext.retryReason) === "issue_continuation_needed" &&
212
+ readNonEmptyString(latestContext.source) === "issue.productive_terminal_continuation_recovery" &&
213
+ isProductiveContinuationRun(latestRun);
214
+ }
215
+ function parseLivenessIncidentKey(incidentKey) {
216
+ if (!incidentKey)
217
+ return null;
218
+ return parseIssueGraphLivenessIncidentKey(incidentKey);
219
+ }
220
+ function livenessRecoveryLeafIssueId(finding) {
221
+ return finding.recoveryIssueId;
222
+ }
223
+ function livenessRecoveryLeafFingerprint(finding) {
224
+ return buildIssueGraphLivenessLeafKey({
225
+ squadId: finding.squadId,
226
+ state: finding.state,
227
+ leafIssueId: livenessRecoveryLeafIssueId(finding),
228
+ });
229
+ }
230
+ function livenessRecoveryLeafKey(squadId, state, leafIssueId) {
231
+ return buildIssueGraphLivenessLeafKey({ squadId, state, leafIssueId });
232
+ }
233
+ function isUniqueLivenessRecoveryConflict(error) {
234
+ if (!error || typeof error !== "object")
235
+ return false;
236
+ const maybe = error;
237
+ return maybe.code === "23505" &&
238
+ (maybe.constraint === "issues_active_liveness_recovery_incident_uq" ||
239
+ maybe.constraint === "issues_active_liveness_recovery_leaf_uq" ||
240
+ typeof maybe.message === "string" &&
241
+ (maybe.message.includes("issues_active_liveness_recovery_incident_uq") ||
242
+ maybe.message.includes("issues_active_liveness_recovery_leaf_uq")));
243
+ }
244
+ function formatDependencyPath(finding) {
245
+ return finding.dependencyPath
246
+ .map((entry) => entry.identifier ?? entry.issueId)
247
+ .join(" -> ");
248
+ }
249
+ function buildLivenessEscalationDescription(finding) {
250
+ const source = finding.dependencyPath[0];
251
+ const recovery = finding.dependencyPath.find((entry) => entry.issueId === finding.recoveryIssueId);
252
+ const selectedOwner = finding.recommendedOwnerAgentId ?? "none";
253
+ return [
254
+ "Slaw detected a harness-level issue graph liveness incident.",
255
+ "",
256
+ "## Source",
257
+ "",
258
+ `- Source issue: ${source?.identifier ?? source?.issueId ?? finding.issueId}`,
259
+ `- Recovery target issue: ${recovery?.identifier ?? recovery?.issueId ?? finding.recoveryIssueId}`,
260
+ `- Incident key: \`${finding.incidentKey}\``,
261
+ `- Detected invariant: \`${finding.state}\``,
262
+ `- Dependency path: ${formatDependencyPath(finding)}`,
263
+ `- Reason: ${finding.reason}`,
264
+ "",
265
+ "## Ownership",
266
+ "",
267
+ `- Selected owner agent: \`${selectedOwner}\``,
268
+ `- Candidate owner agents: ${finding.recommendedOwnerCandidateAgentIds.length > 0 ? finding.recommendedOwnerCandidateAgentIds.map((id) => `\`${id}\``).join(", ") : "none"}`,
269
+ "",
270
+ "## Next Action",
271
+ "",
272
+ finding.recommendedAction,
273
+ "",
274
+ "Resolve the blocked chain, then mark this escalation issue done so the original issue can resume when all blockers are cleared.",
275
+ ].join("\n");
276
+ }
277
+ function buildLivenessOriginalIssueComment(finding, escalation) {
278
+ return [
279
+ "Slaw detected a harness-level liveness incident in this issue's dependency graph.",
280
+ "",
281
+ `- Escalation issue: ${escalation.identifier ?? escalation.id}`,
282
+ `- Incident key: \`${finding.incidentKey}\``,
283
+ `- Finding: \`${finding.state}\``,
284
+ `- Dependency path: ${formatDependencyPath(finding)}`,
285
+ `- Reason: ${finding.reason}`,
286
+ `- Manager action requested: ${finding.recommendedAction}`,
287
+ "",
288
+ "This issue now keeps its existing blockers and is also blocked by the escalation issue so dependency wakeups remain explicit.",
289
+ ].join("\n");
290
+ }
291
+ export function recoveryService(db, deps) {
292
+ const issuesSvc = issueService(db);
293
+ const recoveryActionsSvc = issueRecoveryActionService(db);
294
+ const treeControlSvc = issueTreeControlService(db);
295
+ const budgets = budgetService(db);
296
+ const instanceSettings = instanceSettingsService(db);
297
+ const runLogStore = getRunLogStore();
298
+ const getCurrentUserRedactionOptions = async () => ({
299
+ enabled: (await instanceSettings.getGeneral()).censorUsernameInLogs,
300
+ });
301
+ async function getAgent(agentId) {
302
+ return db.select().from(agents).where(eq(agents.id, agentId)).then((rows) => rows[0] ?? null);
303
+ }
304
+ async function getLatestIssueRun(squadId, issueId) {
305
+ return db
306
+ .select({
307
+ id: heartbeatRuns.id,
308
+ agentId: heartbeatRuns.agentId,
309
+ status: heartbeatRuns.status,
310
+ error: heartbeatRuns.error,
311
+ errorCode: heartbeatRuns.errorCode,
312
+ contextSnapshot: heartbeatRuns.contextSnapshot,
313
+ livenessState: heartbeatRuns.livenessState,
314
+ })
315
+ .from(heartbeatRuns)
316
+ .where(and(eq(heartbeatRuns.squadId, squadId), sql `${heartbeatRuns.contextSnapshot} ->> 'issueId' = ${issueId}`))
317
+ .orderBy(desc(heartbeatRuns.createdAt), desc(heartbeatRuns.id))
318
+ .limit(1)
319
+ .then((rows) => rows[0] ?? null);
320
+ }
321
+ async function summarizeRecentContinuationRetries(squadId, issueId, errorCodeToMatch) {
322
+ const rows = await db
323
+ .select({
324
+ id: heartbeatRuns.id,
325
+ status: heartbeatRuns.status,
326
+ errorCode: heartbeatRuns.errorCode,
327
+ contextSnapshot: heartbeatRuns.contextSnapshot,
328
+ finishedAt: heartbeatRuns.finishedAt,
329
+ })
330
+ .from(heartbeatRuns)
331
+ .where(and(eq(heartbeatRuns.squadId, squadId), sql `${heartbeatRuns.contextSnapshot} ->> 'issueId' = ${issueId}`))
332
+ .orderBy(desc(heartbeatRuns.createdAt), desc(heartbeatRuns.id))
333
+ .limit(10);
334
+ let consecutive = 0;
335
+ let latestFinishedAt = null;
336
+ for (const row of rows) {
337
+ const ctx = parseObject(row.contextSnapshot);
338
+ const retryReason = readNonEmptyString(ctx.retryReason);
339
+ if (retryReason !== "issue_continuation_needed")
340
+ break;
341
+ if (!UNSUCCESSFUL_HEARTBEAT_RUN_TERMINAL_STATUSES.includes(row.status)) {
342
+ break;
343
+ }
344
+ const rowErrorCode = readNonEmptyString(row.errorCode);
345
+ if (errorCodeToMatch !== rowErrorCode) {
346
+ break;
347
+ }
348
+ consecutive += 1;
349
+ if (latestFinishedAt === null)
350
+ latestFinishedAt = row.finishedAt ?? null;
351
+ }
352
+ return { consecutive, latestFinishedAt };
353
+ }
354
+ async function hasActiveExecutionPath(squadId, issueId) {
355
+ const [run, deferredWake] = await Promise.all([
356
+ db
357
+ .select({ id: heartbeatRuns.id })
358
+ .from(heartbeatRuns)
359
+ .where(and(eq(heartbeatRuns.squadId, squadId), inArray(heartbeatRuns.status, [...EXECUTION_PATH_HEARTBEAT_RUN_STATUSES]), sql `${heartbeatRuns.contextSnapshot} ->> 'issueId' = ${issueId}`))
360
+ .limit(1)
361
+ .then((rows) => rows[0] ?? null),
362
+ db
363
+ .select({ id: agentWakeupRequests.id })
364
+ .from(agentWakeupRequests)
365
+ .where(and(eq(agentWakeupRequests.squadId, squadId), eq(agentWakeupRequests.status, "deferred_issue_execution"), sql `${agentWakeupRequests.payload} ->> 'issueId' = ${issueId}`))
366
+ .limit(1)
367
+ .then((rows) => rows[0] ?? null),
368
+ ]);
369
+ return Boolean(run || deferredWake);
370
+ }
371
+ async function hasQueuedIssueWake(squadId, issueId) {
372
+ return db
373
+ .select({ id: agentWakeupRequests.id })
374
+ .from(agentWakeupRequests)
375
+ .where(and(eq(agentWakeupRequests.squadId, squadId), eq(agentWakeupRequests.status, "queued"), sql `${agentWakeupRequests.payload} ->> 'issueId' = ${issueId}`))
376
+ .limit(1)
377
+ .then((rows) => Boolean(rows[0]));
378
+ }
379
+ async function enqueueStrandedIssueRecovery(input) {
380
+ const queued = await deps.enqueueWakeup(input.agentId, {
381
+ source: "automation",
382
+ triggerDetail: "system",
383
+ reason: input.reason,
384
+ payload: withRecoveryModelProfileHint({
385
+ issueId: input.issueId,
386
+ ...(input.retryOfRunId ? { retryOfRunId: input.retryOfRunId } : {}),
387
+ }, "normal_model"),
388
+ requestedByActorType: "system",
389
+ requestedByActorId: null,
390
+ contextSnapshot: withRecoveryModelProfileHint({
391
+ issueId: input.issueId,
392
+ taskId: input.issueId,
393
+ wakeReason: input.reason,
394
+ retryReason: input.retryReason,
395
+ source: input.source,
396
+ ...(input.retryOfRunId ? { retryOfRunId: input.retryOfRunId } : {}),
397
+ }, "normal_model"),
398
+ });
399
+ if (queued && input.retryOfRunId) {
400
+ return db
401
+ .update(heartbeatRuns)
402
+ .set({
403
+ retryOfRunId: input.retryOfRunId,
404
+ updatedAt: new Date(),
405
+ })
406
+ .where(eq(heartbeatRuns.id, queued.id))
407
+ .returning()
408
+ .then((rows) => rows[0] ?? queued);
409
+ }
410
+ return queued;
411
+ }
412
+ async function enqueueInitialAssignedTodoDispatch(issue, agentId) {
413
+ return deps.enqueueWakeup(agentId, {
414
+ source: "assignment",
415
+ triggerDetail: "system",
416
+ reason: "issue_assigned",
417
+ payload: withRecoveryModelProfileHint({
418
+ issueId: issue.id,
419
+ mutation: "assigned_todo_liveness_dispatch",
420
+ }, "normal_model"),
421
+ requestedByActorType: "system",
422
+ requestedByActorId: null,
423
+ contextSnapshot: withRecoveryModelProfileHint({
424
+ issueId: issue.id,
425
+ taskId: issue.id,
426
+ wakeReason: "issue_assigned",
427
+ source: "issue.assigned_todo_liveness_dispatch",
428
+ }, "normal_model"),
429
+ });
430
+ }
431
+ async function isInvocationBudgetBlocked(issue, agentId) {
432
+ const budgetBlock = await budgets.getInvocationBlock(issue.squadId, agentId, {
433
+ issueId: issue.id,
434
+ projectId: issue.projectId,
435
+ });
436
+ return Boolean(budgetBlock);
437
+ }
438
+ async function reconcileUnassignedBlockingIssues() {
439
+ const candidates = await db
440
+ .select({
441
+ id: issues.id,
442
+ squadId: issues.squadId,
443
+ identifier: issues.identifier,
444
+ status: issues.status,
445
+ createdByAgentId: issues.createdByAgentId,
446
+ })
447
+ .from(issueRelations)
448
+ .innerJoin(issues, eq(issueRelations.issueId, issues.id))
449
+ .where(and(eq(issueRelations.type, "blocks"), inArray(issues.status, ["todo", "blocked"]), isNull(issues.assigneeAgentId), isNull(issues.assigneeUserId), sql `${issues.createdByAgentId} is not null`, sql `exists (
450
+ select 1
451
+ from issues blocked_issue
452
+ where blocked_issue.id = ${issueRelations.relatedIssueId}
453
+ and blocked_issue.squad_id = ${issues.squadId}
454
+ and blocked_issue.status not in ('done', 'cancelled')
455
+ )`));
456
+ let assigned = 0;
457
+ let skipped = 0;
458
+ const issueIds = [];
459
+ const seen = new Set();
460
+ for (const candidate of candidates) {
461
+ if (seen.has(candidate.id))
462
+ continue;
463
+ seen.add(candidate.id);
464
+ const creatorAgentId = candidate.createdByAgentId;
465
+ if (!creatorAgentId) {
466
+ skipped += 1;
467
+ continue;
468
+ }
469
+ const creatorAgent = await getAgent(creatorAgentId);
470
+ if (!creatorAgent || creatorAgent.squadId !== candidate.squadId || !isAgentInvokable(creatorAgent)) {
471
+ skipped += 1;
472
+ continue;
473
+ }
474
+ const relations = await issuesSvc.getRelationSummaries(candidate.id);
475
+ const blockingLinks = formatIssueLinksForComment(relations.blocks);
476
+ const updated = await issuesSvc.update(candidate.id, {
477
+ assigneeAgentId: creatorAgent.id,
478
+ assigneeUserId: null,
479
+ });
480
+ if (!updated) {
481
+ skipped += 1;
482
+ continue;
483
+ }
484
+ await issuesSvc.addComment(candidate.id, [
485
+ "## Assigned Orphan Blocker",
486
+ "",
487
+ `Slaw found this issue is blocking ${blockingLinks} but had no assignee, so no heartbeat could pick it up.`,
488
+ "",
489
+ "- Assigned it back to the agent that created the blocker.",
490
+ "- Next action: resolve this blocker or reassign it to the right owner.",
491
+ ].join("\n"), {});
492
+ await logActivity(db, {
493
+ squadId: candidate.squadId,
494
+ actorType: "system",
495
+ actorId: "system",
496
+ agentId: null,
497
+ runId: null,
498
+ action: "issue.updated",
499
+ entityType: "issue",
500
+ entityId: candidate.id,
501
+ details: {
502
+ identifier: candidate.identifier,
503
+ assigneeAgentId: creatorAgent.id,
504
+ source: "recovery.reconcile_unassigned_blocking_issue",
505
+ },
506
+ });
507
+ const queued = await deps.enqueueWakeup(creatorAgent.id, {
508
+ source: "automation",
509
+ triggerDetail: "system",
510
+ reason: "issue_assigned",
511
+ payload: withRecoveryModelProfileHint({
512
+ issueId: candidate.id,
513
+ mutation: "unassigned_blocker_recovery",
514
+ }, "normal_model"),
515
+ requestedByActorType: "system",
516
+ requestedByActorId: null,
517
+ contextSnapshot: withRecoveryModelProfileHint({
518
+ issueId: candidate.id,
519
+ taskId: candidate.id,
520
+ wakeReason: "issue_assigned",
521
+ source: "issue.unassigned_blocker_recovery",
522
+ }, "normal_model"),
523
+ });
524
+ if (queued) {
525
+ assigned += 1;
526
+ issueIds.push(candidate.id);
527
+ }
528
+ else {
529
+ skipped += 1;
530
+ }
531
+ }
532
+ return { assigned, skipped, issueIds };
533
+ }
534
+ async function getSquadIssuePrefix(squadId) {
535
+ return db
536
+ .select({ issuePrefix: squads.issuePrefix })
537
+ .from(squads)
538
+ .where(eq(squads.id, squadId))
539
+ .then((rows) => rows[0]?.issuePrefix ?? "PAP");
540
+ }
541
+ function staleActiveRunOriginFingerprint(squadId, runId) {
542
+ return `stale_active_run:${squadId}:${runId}`;
543
+ }
544
+ function isTerminalIssueStatus(status) {
545
+ return status === "done" || status === "cancelled";
546
+ }
547
+ function isRecoveryOriginIssue(issue) {
548
+ return Object.values(RECOVERY_ORIGIN_KINDS).includes(issue.originKind);
549
+ }
550
+ function silenceStartedAtForRun(run) {
551
+ return run.lastOutputAt ?? run.processStartedAt ?? run.startedAt ?? run.createdAt ?? null;
552
+ }
553
+ function silenceAgeMsForRun(run, now = new Date()) {
554
+ const startedAt = silenceStartedAtForRun(run);
555
+ return startedAt ? Math.max(0, now.getTime() - startedAt.getTime()) : null;
556
+ }
557
+ async function latestActiveOutputQuietUntilDecision(squadId, runId, now = new Date()) {
558
+ const [row] = await db
559
+ .select()
560
+ .from(heartbeatRunWatchdogDecisions)
561
+ .where(and(eq(heartbeatRunWatchdogDecisions.squadId, squadId), eq(heartbeatRunWatchdogDecisions.runId, runId), inArray(heartbeatRunWatchdogDecisions.decision, ["snooze", "continue"]), gt(heartbeatRunWatchdogDecisions.snoozedUntil, now)))
562
+ .orderBy(desc(heartbeatRunWatchdogDecisions.createdAt))
563
+ .limit(1);
564
+ return row ?? null;
565
+ }
566
+ async function findOpenStaleRunEvaluation(squadId, runId) {
567
+ const [row] = await db
568
+ .select({
569
+ id: issues.id,
570
+ identifier: issues.identifier,
571
+ status: issues.status,
572
+ priority: issues.priority,
573
+ assigneeAgentId: issues.assigneeAgentId,
574
+ updatedAt: issues.updatedAt,
575
+ })
576
+ .from(issues)
577
+ .where(and(eq(issues.squadId, squadId), eq(issues.originKind, STALE_ACTIVE_RUN_EVALUATION_ORIGIN_KIND), eq(issues.originId, runId), isNull(issues.hiddenAt), notInArray(issues.status, ["done", "cancelled"])))
578
+ .limit(1);
579
+ return row ?? null;
580
+ }
581
+ async function buildRunOutputSilence(run, now = new Date()) {
582
+ const [quietUntilDecision, evaluation] = await Promise.all([
583
+ latestActiveOutputQuietUntilDecision(run.squadId, run.id, now),
584
+ findOpenStaleRunEvaluation(run.squadId, run.id),
585
+ ]);
586
+ const silenceStartedAt = silenceStartedAtForRun(run);
587
+ const silenceAgeMs = run.status === "running" ? silenceAgeMsForRun(run, now) : null;
588
+ const level = run.status !== "running"
589
+ ? "not_applicable"
590
+ : quietUntilDecision
591
+ ? "snoozed"
592
+ : (silenceAgeMs ?? 0) >= ACTIVE_RUN_OUTPUT_CRITICAL_THRESHOLD_MS
593
+ ? "critical"
594
+ : (silenceAgeMs ?? 0) >= ACTIVE_RUN_OUTPUT_SUSPICION_THRESHOLD_MS
595
+ ? "suspicious"
596
+ : "ok";
597
+ return {
598
+ lastOutputAt: run.lastOutputAt ?? null,
599
+ lastOutputSeq: run.lastOutputSeq ?? 0,
600
+ lastOutputStream: (run.lastOutputStream === "stdout" || run.lastOutputStream === "stderr")
601
+ ? run.lastOutputStream
602
+ : null,
603
+ silenceStartedAt,
604
+ silenceAgeMs,
605
+ level,
606
+ suspicionThresholdMs: ACTIVE_RUN_OUTPUT_SUSPICION_THRESHOLD_MS,
607
+ criticalThresholdMs: ACTIVE_RUN_OUTPUT_CRITICAL_THRESHOLD_MS,
608
+ snoozedUntil: quietUntilDecision?.snoozedUntil ?? null,
609
+ evaluationIssueId: evaluation?.id ?? null,
610
+ evaluationIssueIdentifier: evaluation?.identifier ?? null,
611
+ evaluationIssueAssigneeAgentId: evaluation?.assigneeAgentId ?? null,
612
+ };
613
+ }
614
+ function redactWatchdogEvidenceText(value, currentUserRedactionOptions) {
615
+ return redactSensitiveText(redactCurrentUserText(value, currentUserRedactionOptions));
616
+ }
617
+ function truncateEvidenceText(value, maxChars = 4000) {
618
+ if (value.length <= maxChars)
619
+ return value;
620
+ return `${value.slice(value.length - maxChars)}\n[truncated earlier evidence]`;
621
+ }
622
+ async function readRunLogTailForEvidence(run) {
623
+ if (!run.logStore || !run.logRef || !run.logBytes)
624
+ return "";
625
+ try {
626
+ const offset = Math.max(0, run.logBytes - ACTIVE_RUN_OUTPUT_EVIDENCE_TAIL_BYTES);
627
+ const result = await runLogStore.read({ store: run.logStore, logRef: run.logRef }, { offset, limitBytes: ACTIVE_RUN_OUTPUT_EVIDENCE_TAIL_BYTES });
628
+ return result.content;
629
+ }
630
+ catch (err) {
631
+ logger.warn({ err, runId: run.id }, "failed to read stale-run watchdog evidence tail");
632
+ return "";
633
+ }
634
+ }
635
+ async function resolveStaleRunSourceIssue(run) {
636
+ const issueId = issueIdFromRunContext(run.contextSnapshot);
637
+ if (!issueId)
638
+ return null;
639
+ const [issue] = await db
640
+ .select()
641
+ .from(issues)
642
+ .where(and(eq(issues.squadId, run.squadId), eq(issues.id, issueId), isNull(issues.hiddenAt)))
643
+ .limit(1);
644
+ return issue ?? null;
645
+ }
646
+ async function latestSameRunSourceTerminalEvidence(input) {
647
+ if (!isTerminalIssueStatus(input.sourceIssue.status))
648
+ return null;
649
+ const after = input.evidenceAfter ?? input.run.startedAt ?? input.run.createdAt ?? null;
650
+ const activityPredicates = [
651
+ eq(activityLog.squadId, input.run.squadId),
652
+ eq(activityLog.runId, input.run.id),
653
+ eq(activityLog.action, "issue.updated"),
654
+ eq(activityLog.entityType, "issue"),
655
+ eq(activityLog.entityId, input.sourceIssue.id),
656
+ sql `${activityLog.details} ->> 'status' = ${input.sourceIssue.status}`,
657
+ ];
658
+ if (after) {
659
+ activityPredicates.push(gte(activityLog.createdAt, after));
660
+ }
661
+ const activity = await db
662
+ .select({
663
+ id: activityLog.id,
664
+ createdAt: activityLog.createdAt,
665
+ action: activityLog.action,
666
+ })
667
+ .from(activityLog)
668
+ .where(and(...activityPredicates))
669
+ .orderBy(desc(activityLog.createdAt))
670
+ .limit(1)
671
+ .then((rows) => rows[0] ?? null);
672
+ if (activity) {
673
+ return {
674
+ kind: "activity",
675
+ id: activity.id,
676
+ createdAt: activity.createdAt,
677
+ action: activity.action,
678
+ };
679
+ }
680
+ return null;
681
+ }
682
+ async function nextRunEventSeq(runId) {
683
+ const [row] = await db
684
+ .select({ maxSeq: sql `max(${heartbeatRunEvents.seq})` })
685
+ .from(heartbeatRunEvents)
686
+ .where(eq(heartbeatRunEvents.runId, runId));
687
+ return Number(row?.maxSeq ?? 0) + 1;
688
+ }
689
+ async function appendRecoveryRunEvent(run, event) {
690
+ await db.insert(heartbeatRunEvents).values({
691
+ squadId: run.squadId,
692
+ runId: run.id,
693
+ agentId: run.agentId,
694
+ seq: await nextRunEventSeq(run.id),
695
+ eventType: "lifecycle",
696
+ stream: "system",
697
+ level: event.level,
698
+ message: event.message,
699
+ payload: event.payload ?? null,
700
+ });
701
+ }
702
+ async function cleanupSourceResolvedRunProcess(input) {
703
+ if (!SESSIONED_LOCAL_ADAPTERS.has(input.runningAgent.adapterType)) {
704
+ return {
705
+ attempted: false,
706
+ outcome: "skipped_non_local_adapter",
707
+ adapterType: input.runningAgent.adapterType,
708
+ };
709
+ }
710
+ const running = runningProcesses.get(input.run.id);
711
+ const pid = running?.child.pid ?? input.run.processPid ?? null;
712
+ const processGroupId = running?.processGroupId ?? input.run.processGroupId ?? null;
713
+ if (typeof pid !== "number" && typeof processGroupId !== "number") {
714
+ return {
715
+ attempted: false,
716
+ outcome: "no_process_metadata",
717
+ adapterType: input.runningAgent.adapterType,
718
+ };
719
+ }
720
+ const wasAlive = (typeof pid === "number" && isPidAlive(pid)) ||
721
+ (typeof processGroupId === "number" && isProcessGroupAlive(processGroupId));
722
+ if (!wasAlive) {
723
+ runningProcesses.delete(input.run.id);
724
+ return {
725
+ attempted: false,
726
+ outcome: "not_running",
727
+ adapterType: input.runningAgent.adapterType,
728
+ pid,
729
+ processGroupId,
730
+ };
731
+ }
732
+ try {
733
+ await terminateLocalService({
734
+ pid: typeof pid === "number" && Number.isInteger(pid) && pid > 0
735
+ ? pid
736
+ : (processGroupId ?? 0),
737
+ processGroupId: typeof processGroupId === "number" && Number.isInteger(processGroupId) && processGroupId > 0
738
+ ? processGroupId
739
+ : null,
740
+ }, running ? { forceAfterMs: Math.max(1, running.graceSec) * 1000 } : undefined);
741
+ runningProcesses.delete(input.run.id);
742
+ const stillAlive = (typeof pid === "number" && isPidAlive(pid)) ||
743
+ (typeof processGroupId === "number" && isProcessGroupAlive(processGroupId));
744
+ return {
745
+ attempted: true,
746
+ outcome: stillAlive ? "termination_sent_still_running" : "terminated",
747
+ adapterType: input.runningAgent.adapterType,
748
+ pid,
749
+ processGroupId,
750
+ };
751
+ }
752
+ catch (error) {
753
+ return {
754
+ attempted: true,
755
+ outcome: "failed",
756
+ adapterType: input.runningAgent.adapterType,
757
+ pid,
758
+ processGroupId,
759
+ error: error instanceof Error ? error.message : String(error),
760
+ };
761
+ }
762
+ }
763
+ async function finalizeAgentAfterSourceResolvedRun(run, status) {
764
+ const [runningCountRow] = await db
765
+ .select({ count: sql `count(*)::int` })
766
+ .from(heartbeatRuns)
767
+ .where(and(eq(heartbeatRuns.agentId, run.agentId), eq(heartbeatRuns.status, "running")));
768
+ const runningCount = Number(runningCountRow?.count ?? 0);
769
+ const nextStatus = runningCount > 0 ? "running" : status === "succeeded" || status === "cancelled" ? "idle" : "error";
770
+ await db
771
+ .update(agents)
772
+ .set({
773
+ status: nextStatus,
774
+ lastHeartbeatAt: new Date(),
775
+ updatedAt: new Date(),
776
+ })
777
+ .where(and(eq(agents.id, run.agentId), notInArray(agents.status, ["paused", "terminated"])));
778
+ }
779
+ async function foldSourceResolvedStaleRun(input) {
780
+ if (!input.evidence)
781
+ return { kind: "skipped" };
782
+ const cleanup = await cleanupSourceResolvedRunProcess({ run: input.run, runningAgent: input.runningAgent });
783
+ const finalRunStatus = input.sourceIssue.status === "cancelled" ? "cancelled" : "succeeded";
784
+ const resultJson = {
785
+ ...parseObject(input.run.resultJson),
786
+ sourceResolvedWatchdogFold: {
787
+ sourceIssueId: input.sourceIssue.id,
788
+ sourceIssueIdentifier: input.sourceIssue.identifier,
789
+ sourceIssueStatus: input.sourceIssue.status,
790
+ sameRunEvidenceKind: input.evidence.kind,
791
+ sameRunEvidenceId: input.evidence.id,
792
+ sameRunEvidenceAt: input.evidence.createdAt.toISOString(),
793
+ silenceStartedAt: input.silenceStartedAt?.toISOString() ?? null,
794
+ silenceAgeMs: input.silenceAgeMs,
795
+ evaluationIssueId: input.existingEvaluation?.id ?? null,
796
+ evaluationIssueIdentifier: input.existingEvaluation?.identifier ?? null,
797
+ cleanup,
798
+ },
799
+ };
800
+ const finalizedRun = await db.transaction(async (tx) => {
801
+ const [updatedRun] = await tx
802
+ .update(heartbeatRuns)
803
+ .set({
804
+ status: finalRunStatus,
805
+ finishedAt: input.now,
806
+ error: null,
807
+ errorCode: null,
808
+ resultJson,
809
+ updatedAt: input.now,
810
+ })
811
+ .where(and(eq(heartbeatRuns.id, input.run.id), eq(heartbeatRuns.squadId, input.run.squadId), eq(heartbeatRuns.status, "running")))
812
+ .returning();
813
+ if (!updatedRun)
814
+ return null;
815
+ if (input.run.wakeupRequestId) {
816
+ await tx
817
+ .update(agentWakeupRequests)
818
+ .set({
819
+ status: finalRunStatus === "succeeded" ? "completed" : "cancelled",
820
+ finishedAt: input.now,
821
+ error: null,
822
+ updatedAt: input.now,
823
+ })
824
+ .where(and(eq(agentWakeupRequests.id, input.run.wakeupRequestId), eq(agentWakeupRequests.squadId, input.run.squadId)));
825
+ }
826
+ await tx
827
+ .update(issues)
828
+ .set({
829
+ executionRunId: null,
830
+ executionAgentNameKey: null,
831
+ executionLockedAt: null,
832
+ updatedAt: input.now,
833
+ })
834
+ .where(and(eq(issues.id, input.sourceIssue.id), eq(issues.squadId, input.run.squadId), eq(issues.executionRunId, input.run.id)));
835
+ return updatedRun;
836
+ });
837
+ if (!finalizedRun)
838
+ return { kind: "skipped" };
839
+ if (input.existingEvaluation && !isTerminalIssueStatus(input.existingEvaluation.status)) {
840
+ await issuesSvc.update(input.existingEvaluation.id, { status: "done" });
841
+ await issuesSvc.addComment(input.existingEvaluation.id, [
842
+ "Source-resolved watchdog fold.",
843
+ "",
844
+ `- Source issue: ${input.sourceIssue.identifier ?? input.sourceIssue.id}`,
845
+ `- Run: \`${input.run.id}\``,
846
+ `- Same-run evidence: \`${input.evidence.kind}:${input.evidence.id}\` at ${input.evidence.createdAt.toISOString()}`,
847
+ "- Outcome: false positive; the source issue already reached a terminal disposition from this run.",
848
+ ].join("\n"), { runId: input.run.id });
849
+ }
850
+ const activeRecoveryAction = await recoveryActionsSvc.getActiveForIssue(input.run.squadId, input.sourceIssue.id);
851
+ if (activeRecoveryAction?.kind === "active_run_watchdog") {
852
+ await recoveryActionsSvc.resolveActiveForIssue({
853
+ squadId: input.run.squadId,
854
+ sourceIssueId: input.sourceIssue.id,
855
+ actionId: activeRecoveryAction.id,
856
+ status: "resolved",
857
+ outcome: "false_positive",
858
+ resolutionNote: "Source issue reached a terminal disposition through durable same-run activity; watchdog folded as source-resolved.",
859
+ });
860
+ }
861
+ const [decision] = await db
862
+ .insert(heartbeatRunWatchdogDecisions)
863
+ .values({
864
+ squadId: input.run.squadId,
865
+ runId: input.run.id,
866
+ evaluationIssueId: input.existingEvaluation?.id ?? null,
867
+ decision: "dismissed_false_positive",
868
+ reason: "Source issue already reached a terminal disposition through durable same-run activity.",
869
+ createdByRunId: input.run.id,
870
+ })
871
+ .returning();
872
+ await appendRecoveryRunEvent(finalizedRun, {
873
+ level: cleanup.outcome === "failed" ? "warn" : "info",
874
+ message: "Source-resolved watchdog fold finalized stale active run",
875
+ payload: resultJson.sourceResolvedWatchdogFold,
876
+ });
877
+ await logActivity(db, {
878
+ squadId: input.run.squadId,
879
+ actorType: "system",
880
+ actorId: "system",
881
+ agentId: input.run.agentId,
882
+ runId: input.run.id,
883
+ action: "heartbeat.output_stale_source_resolved",
884
+ entityType: "heartbeat_run",
885
+ entityId: input.run.id,
886
+ details: {
887
+ source: "recovery.scan_silent_active_runs",
888
+ sourceIssueId: input.sourceIssue.id,
889
+ sourceIssueIdentifier: input.sourceIssue.identifier,
890
+ sourceIssueStatus: input.sourceIssue.status,
891
+ evaluationIssueId: input.existingEvaluation?.id ?? null,
892
+ watchdogDecisionId: decision.id,
893
+ sameRunEvidenceKind: input.evidence.kind,
894
+ sameRunEvidenceId: input.evidence.id,
895
+ sameRunEvidenceAt: input.evidence.createdAt.toISOString(),
896
+ cleanup,
897
+ },
898
+ });
899
+ await finalizeAgentAfterSourceResolvedRun(finalizedRun, finalRunStatus);
900
+ return { kind: "folded", evaluationIssueId: input.existingEvaluation?.id ?? null };
901
+ }
902
+ async function resolveStaleRunOwnerAgentId(input) {
903
+ const candidateIds = [];
904
+ if (input.sourceIssue?.assigneeAgentId) {
905
+ const sourceAssignee = await getAgent(input.sourceIssue.assigneeAgentId);
906
+ if (sourceAssignee?.reportsTo)
907
+ candidateIds.push(sourceAssignee.reportsTo);
908
+ }
909
+ if (input.runningAgent.reportsTo)
910
+ candidateIds.push(input.runningAgent.reportsTo);
911
+ const roleCandidates = await db
912
+ .select()
913
+ .from(agents)
914
+ .where(and(eq(agents.squadId, input.run.squadId), inArray(agents.role, ["engineering_lead", "squad_lead"])))
915
+ .orderBy(sql `case when ${agents.role} = 'engineering_lead' then 0 else 1 end`, asc(agents.createdAt));
916
+ candidateIds.push(...roleCandidates.map((agent) => agent.id));
917
+ const seen = new Set();
918
+ for (const agentId of candidateIds) {
919
+ if (seen.has(agentId))
920
+ continue;
921
+ seen.add(agentId);
922
+ const candidate = await getAgent(agentId);
923
+ if (!candidate || candidate.squadId !== input.run.squadId)
924
+ continue;
925
+ const budgetBlock = await budgets.getInvocationBlock(input.run.squadId, candidate.id, {
926
+ issueId: input.sourceIssue?.id ?? null,
927
+ projectId: input.sourceIssue?.projectId ?? null,
928
+ });
929
+ if (isAgentInvokable(candidate) && !budgetBlock)
930
+ return candidate.id;
931
+ }
932
+ return null;
933
+ }
934
+ async function collectStaleRunEvidence(input) {
935
+ const [tail, recentEvents, childIssues, blockers] = await Promise.all([
936
+ readRunLogTailForEvidence(input.run),
937
+ db
938
+ .select({
939
+ eventType: heartbeatRunEvents.eventType,
940
+ level: heartbeatRunEvents.level,
941
+ message: heartbeatRunEvents.message,
942
+ createdAt: heartbeatRunEvents.createdAt,
943
+ })
944
+ .from(heartbeatRunEvents)
945
+ .where(and(eq(heartbeatRunEvents.squadId, input.run.squadId), eq(heartbeatRunEvents.runId, input.run.id)))
946
+ .orderBy(desc(heartbeatRunEvents.id))
947
+ .limit(8),
948
+ input.sourceIssue
949
+ ? db
950
+ .select({ id: issues.id, identifier: issues.identifier, title: issues.title, status: issues.status })
951
+ .from(issues)
952
+ .where(and(eq(issues.squadId, input.run.squadId), eq(issues.parentId, input.sourceIssue.id), isNull(issues.hiddenAt)))
953
+ .orderBy(desc(issues.updatedAt))
954
+ .limit(8)
955
+ : Promise.resolve([]),
956
+ input.sourceIssue
957
+ ? db
958
+ .select({ id: issues.id, identifier: issues.identifier, title: issues.title, status: issues.status })
959
+ .from(issueRelations)
960
+ .innerJoin(issues, eq(issueRelations.issueId, issues.id))
961
+ .where(and(eq(issueRelations.squadId, input.run.squadId), eq(issueRelations.relatedIssueId, input.sourceIssue.id), eq(issueRelations.type, "blocks")))
962
+ .limit(8)
963
+ : Promise.resolve([]),
964
+ ]);
965
+ const currentUserRedactionOptions = await getCurrentUserRedactionOptions();
966
+ const safeTail = truncateEvidenceText(redactWatchdogEvidenceText(tail, currentUserRedactionOptions));
967
+ const silenceAgeMs = silenceAgeMsForRun(input.run, input.now);
968
+ return {
969
+ safeTail,
970
+ silenceAgeMs,
971
+ recentEvents: recentEvents.reverse().map((event) => ({
972
+ eventType: event.eventType,
973
+ level: event.level,
974
+ createdAt: event.createdAt.toISOString(),
975
+ message: event.message ? truncateEvidenceText(redactWatchdogEvidenceText(event.message, currentUserRedactionOptions), 300) : null,
976
+ })),
977
+ childIssues,
978
+ blockers,
979
+ };
980
+ }
981
+ function buildStaleRunEvaluationDescription(input) {
982
+ const sourceIssue = input.sourceIssue
983
+ ? issueUiLink({ identifier: input.sourceIssue.identifier, id: input.sourceIssue.id }, input.prefix)
984
+ : "none";
985
+ const recentEvents = input.evidence.recentEvents.length > 0
986
+ ? input.evidence.recentEvents.map((event) => `- ${event.createdAt} \`${event.eventType}\`${event.level ? ` ${event.level}` : ""}: ${event.message ?? "(no message)"}`).join("\n")
987
+ : "- none";
988
+ const childIssues = input.evidence.childIssues.length > 0
989
+ ? input.evidence.childIssues.map((issue) => `- ${issueUiLink({ identifier: issue.identifier, id: issue.id }, input.prefix)} \`${issue.status}\`: ${issue.title}`).join("\n")
990
+ : "- none detected";
991
+ const blockers = input.evidence.blockers.length > 0
992
+ ? input.evidence.blockers.map((issue) => `- ${issueUiLink({ identifier: issue.identifier, id: issue.id }, input.prefix)} \`${issue.status}\`: ${issue.title}`).join("\n")
993
+ : "- none detected";
994
+ return [
995
+ `Slaw detected ${input.level} output silence on an active heartbeat run.`,
996
+ "",
997
+ "## Run",
998
+ "",
999
+ `- Run: ${runUiLink(input.run, input.prefix)}`,
1000
+ `- Agent: ${input.runningAgent.name} (${input.runningAgent.adapterType})`,
1001
+ `- Invocation: ${input.run.invocationSource}${input.run.triggerDetail ? ` / ${input.run.triggerDetail}` : ""}`,
1002
+ `- Source issue: ${sourceIssue}`,
1003
+ `- Started at: ${input.run.startedAt?.toISOString() ?? "unknown"}`,
1004
+ `- Process started at: ${input.run.processStartedAt?.toISOString() ?? "unknown"}`,
1005
+ `- Last output at: ${input.run.lastOutputAt?.toISOString() ?? "none recorded"}`,
1006
+ `- Last output sequence: ${input.run.lastOutputSeq ?? 0}`,
1007
+ `- Silent for: ${formatDuration(input.evidence.silenceAgeMs)}`,
1008
+ `- Thresholds: suspicious after ${formatDuration(ACTIVE_RUN_OUTPUT_SUSPICION_THRESHOLD_MS)}, critical after ${formatDuration(ACTIVE_RUN_OUTPUT_CRITICAL_THRESHOLD_MS)}`,
1009
+ `- Process metadata: pid \`${input.run.processPid ?? "unknown"}\`, process group \`${input.run.processGroupId ?? "unknown"}\`, in-memory handle \`${runningProcesses.has(input.run.id) ? "yes" : "no"}\``,
1010
+ "",
1011
+ "## Last Output Excerpt",
1012
+ "",
1013
+ input.evidence.safeTail ? `\`\`\`text\n${input.evidence.safeTail}\n\`\`\`` : "_No run-log tail was available._",
1014
+ "",
1015
+ "## Recent Run Events",
1016
+ "",
1017
+ recentEvents,
1018
+ "",
1019
+ "## Related Work",
1020
+ "",
1021
+ "Active child issues:",
1022
+ childIssues,
1023
+ "",
1024
+ "Current source blockers:",
1025
+ blockers,
1026
+ "",
1027
+ "## Decision Checklist",
1028
+ "",
1029
+ "- Continue or snooze if the run is intentionally quiet.",
1030
+ "- Ask the run owner for context if work may be delegated outside the transcript.",
1031
+ "- Preserve artifacts, branch state, and useful output before cancellation.",
1032
+ "- Cancel or recover through the explicit run recovery controls when authorized.",
1033
+ "- Close this issue as a false positive only after recording the reason.",
1034
+ ].join("\n");
1035
+ }
1036
+ function isUniqueStaleRunEvaluationConflict(error) {
1037
+ const maybe = unwrapDatabaseConflictError(error);
1038
+ if (!maybe)
1039
+ return false;
1040
+ return maybe.code === "23505" &&
1041
+ (maybe.constraint === "issues_active_stale_run_evaluation_uq" ||
1042
+ maybe.constraint_name === "issues_active_stale_run_evaluation_uq" ||
1043
+ typeof maybe.message === "string" && maybe.message.includes("issues_active_stale_run_evaluation_uq"));
1044
+ }
1045
+ function isUniqueStrandedIssueRecoveryConflict(error) {
1046
+ const maybe = unwrapDatabaseConflictError(error);
1047
+ if (!maybe)
1048
+ return false;
1049
+ return maybe.code === "23505" &&
1050
+ (maybe.constraint === "issues_active_stranded_issue_recovery_uq" ||
1051
+ maybe.constraint_name === "issues_active_stranded_issue_recovery_uq" ||
1052
+ typeof maybe.message === "string" && maybe.message.includes("issues_active_stranded_issue_recovery_uq"));
1053
+ }
1054
+ async function ensureSourceIssueBlockedByStaleEvaluation(input) {
1055
+ if (!input.sourceIssue || ["done", "cancelled"].includes(input.sourceIssue.status))
1056
+ return false;
1057
+ const blockerIds = await existingBlockerIssueIds(input.sourceIssue.squadId, input.sourceIssue.id);
1058
+ if (blockerIds.includes(input.evaluationIssue.id))
1059
+ return false;
1060
+ const nextBlockerIds = [...blockerIds, input.evaluationIssue.id];
1061
+ await issuesSvc.update(input.sourceIssue.id, {
1062
+ ...(input.sourceIssue.status === "blocked" ? {} : { status: "blocked" }),
1063
+ blockedByIssueIds: nextBlockerIds,
1064
+ });
1065
+ await issuesSvc.addComment(input.sourceIssue.id, [
1066
+ "Slaw detected critical output silence on this issue's active run.",
1067
+ "",
1068
+ `- Evaluation issue: ${input.evaluationIssue.identifier ?? input.evaluationIssue.id}`,
1069
+ `- Run: \`${input.run.id}\``,
1070
+ "",
1071
+ "This blocks the source issue on the explicit review task without cancelling the active process.",
1072
+ ].join("\n"), { runId: input.run.id });
1073
+ await logActivity(db, {
1074
+ squadId: input.sourceIssue.squadId,
1075
+ actorType: "system",
1076
+ actorId: "system",
1077
+ agentId: null,
1078
+ runId: input.run.id,
1079
+ action: "heartbeat.output_stale_escalated",
1080
+ entityType: "issue",
1081
+ entityId: input.sourceIssue.id,
1082
+ details: {
1083
+ source: "recovery.scan_silent_active_runs",
1084
+ evaluationIssueId: input.evaluationIssue.id,
1085
+ blockerIssueIds: nextBlockerIds,
1086
+ },
1087
+ });
1088
+ return true;
1089
+ }
1090
+ async function createOrUpdateStaleRunEvaluation(input) {
1091
+ const runningAgent = await getAgent(input.run.agentId);
1092
+ if (!runningAgent || runningAgent.squadId !== input.run.squadId)
1093
+ return { kind: "skipped" };
1094
+ const sourceIssue = await resolveStaleRunSourceIssue(input.run);
1095
+ const existing = await findOpenStaleRunEvaluation(input.run.squadId, input.run.id);
1096
+ if (sourceIssue && isRecoveryOriginIssue(sourceIssue)) {
1097
+ await logActivity(db, {
1098
+ squadId: input.run.squadId,
1099
+ actorType: "system",
1100
+ actorId: "system",
1101
+ agentId: input.run.agentId,
1102
+ runId: input.run.id,
1103
+ action: "heartbeat.output_stale_recovery_recursion_refused",
1104
+ entityType: "heartbeat_run",
1105
+ entityId: input.run.id,
1106
+ details: {
1107
+ source: "recovery.scan_silent_active_runs",
1108
+ sourceIssueId: sourceIssue.id,
1109
+ sourceIssueIdentifier: sourceIssue.identifier,
1110
+ sourceIssueOriginKind: sourceIssue.originKind,
1111
+ existingEvaluationIssueId: existing?.id ?? null,
1112
+ },
1113
+ });
1114
+ return { kind: "skipped" };
1115
+ }
1116
+ const silenceStartedAt = silenceStartedAtForRun(input.run);
1117
+ if (sourceIssue && isTerminalIssueStatus(sourceIssue.status)) {
1118
+ const terminalEvidence = await latestSameRunSourceTerminalEvidence({
1119
+ run: input.run,
1120
+ sourceIssue,
1121
+ evidenceAfter: silenceStartedAt,
1122
+ });
1123
+ if (terminalEvidence) {
1124
+ return foldSourceResolvedStaleRun({
1125
+ run: input.run,
1126
+ runningAgent,
1127
+ sourceIssue,
1128
+ evidence: terminalEvidence,
1129
+ existingEvaluation: existing,
1130
+ silenceStartedAt,
1131
+ silenceAgeMs: silenceAgeMsForRun(input.run, input.now),
1132
+ now: input.now,
1133
+ });
1134
+ }
1135
+ }
1136
+ const prefix = await getSquadIssuePrefix(input.run.squadId);
1137
+ const evidence = await collectStaleRunEvidence({
1138
+ run: input.run,
1139
+ runningAgent,
1140
+ sourceIssue,
1141
+ prefix,
1142
+ now: input.now,
1143
+ });
1144
+ const level = (evidence.silenceAgeMs ?? 0) >= ACTIVE_RUN_OUTPUT_CRITICAL_THRESHOLD_MS ? "critical" : "suspicious";
1145
+ if (existing) {
1146
+ if (level === "critical" && existing.priority !== "high") {
1147
+ await issuesSvc.update(existing.id, {
1148
+ priority: "high",
1149
+ });
1150
+ await issuesSvc.addComment(existing.id, [
1151
+ "Critical output silence threshold crossed.",
1152
+ "",
1153
+ `- Run: \`${input.run.id}\``,
1154
+ `- Silent for: ${formatDuration(evidence.silenceAgeMs)}`,
1155
+ `- Last output at: ${input.run.lastOutputAt?.toISOString() ?? "none recorded"}`,
1156
+ ].join("\n"), { runId: input.run.id });
1157
+ await ensureSourceIssueBlockedByStaleEvaluation({
1158
+ sourceIssue,
1159
+ evaluationIssue: existing,
1160
+ run: input.run,
1161
+ });
1162
+ return { kind: "escalated", evaluationIssueId: existing.id };
1163
+ }
1164
+ if (level === "critical") {
1165
+ await ensureSourceIssueBlockedByStaleEvaluation({
1166
+ sourceIssue,
1167
+ evaluationIssue: existing,
1168
+ run: input.run,
1169
+ });
1170
+ }
1171
+ return { kind: "existing", evaluationIssueId: existing.id };
1172
+ }
1173
+ const ownerAgentId = await resolveStaleRunOwnerAgentId({ run: input.run, runningAgent, sourceIssue });
1174
+ const description = buildStaleRunEvaluationDescription({
1175
+ run: input.run,
1176
+ runningAgent,
1177
+ sourceIssue,
1178
+ prefix,
1179
+ evidence,
1180
+ level,
1181
+ now: input.now,
1182
+ });
1183
+ let evaluation;
1184
+ try {
1185
+ evaluation = await issuesSvc.create(input.run.squadId, {
1186
+ title: `Review silent active run for ${runningAgent.name}`,
1187
+ description,
1188
+ status: "todo",
1189
+ priority: level === "critical" ? "high" : "medium",
1190
+ parentId: sourceIssue && !["done", "cancelled"].includes(sourceIssue.status) ? sourceIssue.id : null,
1191
+ projectId: sourceIssue?.projectId ?? null,
1192
+ goalId: sourceIssue?.goalId ?? null,
1193
+ billingCode: sourceIssue?.billingCode ?? null,
1194
+ assigneeAgentId: ownerAgentId,
1195
+ assigneeAdapterOverrides: recoveryAssigneeAdapterOverrides("status_only"),
1196
+ originKind: STALE_ACTIVE_RUN_EVALUATION_ORIGIN_KIND,
1197
+ originId: input.run.id,
1198
+ originRunId: input.run.id,
1199
+ originFingerprint: staleActiveRunOriginFingerprint(input.run.squadId, input.run.id),
1200
+ });
1201
+ }
1202
+ catch (error) {
1203
+ if (!isUniqueStaleRunEvaluationConflict(error))
1204
+ throw error;
1205
+ const raced = await findOpenStaleRunEvaluation(input.run.squadId, input.run.id);
1206
+ if (!raced)
1207
+ throw error;
1208
+ return { kind: "existing", evaluationIssueId: raced.id };
1209
+ }
1210
+ await logActivity(db, {
1211
+ squadId: input.run.squadId,
1212
+ actorType: "system",
1213
+ actorId: "system",
1214
+ agentId: ownerAgentId,
1215
+ runId: input.run.id,
1216
+ action: "heartbeat.output_stale_detected",
1217
+ entityType: "issue",
1218
+ entityId: evaluation.id,
1219
+ details: {
1220
+ source: "recovery.scan_silent_active_runs",
1221
+ level,
1222
+ sourceIssueId: sourceIssue?.id ?? null,
1223
+ silenceAgeMs: evidence.silenceAgeMs,
1224
+ lastOutputAt: input.run.lastOutputAt?.toISOString() ?? null,
1225
+ },
1226
+ });
1227
+ if (level === "critical") {
1228
+ await ensureSourceIssueBlockedByStaleEvaluation({
1229
+ sourceIssue,
1230
+ evaluationIssue: evaluation,
1231
+ run: input.run,
1232
+ });
1233
+ }
1234
+ if (ownerAgentId) {
1235
+ await deps.enqueueWakeup(ownerAgentId, {
1236
+ source: "assignment",
1237
+ triggerDetail: "system",
1238
+ reason: "issue_assigned",
1239
+ payload: withRecoveryModelProfileHint({
1240
+ issueId: evaluation.id,
1241
+ staleRunId: input.run.id,
1242
+ sourceIssueId: sourceIssue?.id ?? null,
1243
+ }, "status_only"),
1244
+ requestedByActorType: "system",
1245
+ requestedByActorId: null,
1246
+ contextSnapshot: withRecoveryModelProfileHint({
1247
+ issueId: evaluation.id,
1248
+ taskId: evaluation.id,
1249
+ wakeReason: "issue_assigned",
1250
+ source: STALE_ACTIVE_RUN_EVALUATION_ORIGIN_KIND,
1251
+ staleRunId: input.run.id,
1252
+ sourceIssueId: sourceIssue?.id ?? null,
1253
+ }, "status_only"),
1254
+ });
1255
+ }
1256
+ return { kind: "created", evaluationIssueId: evaluation.id };
1257
+ }
1258
+ async function scanSilentActiveRuns(opts) {
1259
+ const now = opts?.now ?? new Date();
1260
+ const suspicionBefore = new Date(now.getTime() - ACTIVE_RUN_OUTPUT_SUSPICION_THRESHOLD_MS);
1261
+ const candidates = await db
1262
+ .select()
1263
+ .from(heartbeatRuns)
1264
+ .where(and(opts?.squadId ? eq(heartbeatRuns.squadId, opts.squadId) : undefined, eq(heartbeatRuns.status, "running"), sql `coalesce(${heartbeatRuns.lastOutputAt}, ${heartbeatRuns.processStartedAt}, ${heartbeatRuns.startedAt}, ${heartbeatRuns.createdAt}) <= ${suspicionBefore.toISOString()}::timestamptz`))
1265
+ .orderBy(asc(heartbeatRuns.createdAt))
1266
+ .limit(100);
1267
+ const result = {
1268
+ scanned: candidates.length,
1269
+ created: 0,
1270
+ existing: 0,
1271
+ escalated: 0,
1272
+ folded: 0,
1273
+ snoozed: 0,
1274
+ skipped: 0,
1275
+ evaluationIssueIds: [],
1276
+ };
1277
+ for (const run of candidates) {
1278
+ if (await latestActiveOutputQuietUntilDecision(run.squadId, run.id, now)) {
1279
+ result.snoozed += 1;
1280
+ continue;
1281
+ }
1282
+ const outcome = await createOrUpdateStaleRunEvaluation({ run, now });
1283
+ if (outcome.kind === "created")
1284
+ result.created += 1;
1285
+ else if (outcome.kind === "existing")
1286
+ result.existing += 1;
1287
+ else if (outcome.kind === "escalated")
1288
+ result.escalated += 1;
1289
+ else if (outcome.kind === "folded")
1290
+ result.folded += 1;
1291
+ else
1292
+ result.skipped += 1;
1293
+ if ("evaluationIssueId" in outcome && outcome.evaluationIssueId) {
1294
+ result.evaluationIssueIds.push(outcome.evaluationIssueId);
1295
+ }
1296
+ }
1297
+ return result;
1298
+ }
1299
+ async function recordWatchdogDecision(input) {
1300
+ const [run] = await db
1301
+ .select()
1302
+ .from(heartbeatRuns)
1303
+ .where(eq(heartbeatRuns.id, input.runId))
1304
+ .limit(1);
1305
+ if (!run)
1306
+ throw notFound("Heartbeat run not found");
1307
+ let evaluationIssue = null;
1308
+ if (input.evaluationIssueId) {
1309
+ evaluationIssue = await db
1310
+ .select({
1311
+ id: issues.id,
1312
+ assigneeAgentId: issues.assigneeAgentId,
1313
+ squadId: issues.squadId,
1314
+ originKind: issues.originKind,
1315
+ originId: issues.originId,
1316
+ hiddenAt: issues.hiddenAt,
1317
+ status: issues.status,
1318
+ })
1319
+ .from(issues)
1320
+ .where(and(eq(issues.id, input.evaluationIssueId), eq(issues.squadId, run.squadId)))
1321
+ .then((rows) => rows[0] ?? null);
1322
+ if (!evaluationIssue)
1323
+ throw notFound("Evaluation issue not found");
1324
+ }
1325
+ const operatorActor = input.actor.type === "operator";
1326
+ const assignedRecoveryOwner = input.actor.type === "agent" &&
1327
+ Boolean(input.actor.agentId) &&
1328
+ evaluationIssue !== null &&
1329
+ evaluationIssue.originKind === STALE_ACTIVE_RUN_EVALUATION_ORIGIN_KIND &&
1330
+ evaluationIssue.originId === run.id &&
1331
+ evaluationIssue.hiddenAt === null &&
1332
+ !["done", "cancelled"].includes(evaluationIssue.status) &&
1333
+ evaluationIssue?.assigneeAgentId === input.actor.agentId;
1334
+ if (!operatorActor && !assignedRecoveryOwner) {
1335
+ throw forbidden("Only the operator or the assigned recovery owner can record watchdog decisions");
1336
+ }
1337
+ if (evaluationIssue && (evaluationIssue.originKind !== STALE_ACTIVE_RUN_EVALUATION_ORIGIN_KIND ||
1338
+ evaluationIssue.originId !== run.id)) {
1339
+ throw forbidden("Watchdog decision evaluation issue is not bound to the target run");
1340
+ }
1341
+ if (input.actor.type === "agent" && !evaluationIssue) {
1342
+ throw forbidden("Agent watchdog decisions require the target evaluation issue");
1343
+ }
1344
+ const createdByRunId = input.actor.type === "agent"
1345
+ ? input.actor.runId ?? input.createdByRunId ?? null
1346
+ : input.actor.type === "operator"
1347
+ ? input.actor.runId ?? input.createdByRunId ?? null
1348
+ : null;
1349
+ if (createdByRunId) {
1350
+ const [creatorRun] = await db
1351
+ .select({ id: heartbeatRuns.id, squadId: heartbeatRuns.squadId, agentId: heartbeatRuns.agentId })
1352
+ .from(heartbeatRuns)
1353
+ .where(eq(heartbeatRuns.id, createdByRunId))
1354
+ .limit(1);
1355
+ const sameSquad = creatorRun?.squadId === run.squadId;
1356
+ const sameAgent = input.actor.type !== "agent" || creatorRun?.agentId === input.actor.agentId;
1357
+ if (!creatorRun || !sameSquad || !sameAgent) {
1358
+ throw forbidden("createdByRunId is not valid for this watchdog decision actor");
1359
+ }
1360
+ }
1361
+ const decisionNow = input.now ?? new Date();
1362
+ const effectiveSnoozedUntil = input.decision === "snooze"
1363
+ ? input.snoozedUntil ?? null
1364
+ : input.decision === "continue"
1365
+ ? input.snoozedUntil && input.snoozedUntil > decisionNow
1366
+ ? input.snoozedUntil
1367
+ : new Date(decisionNow.getTime() + ACTIVE_RUN_OUTPUT_CONTINUE_REARM_MS)
1368
+ : null;
1369
+ const [row] = await db
1370
+ .insert(heartbeatRunWatchdogDecisions)
1371
+ .values({
1372
+ squadId: run.squadId,
1373
+ runId: run.id,
1374
+ evaluationIssueId: input.evaluationIssueId ?? null,
1375
+ decision: input.decision,
1376
+ snoozedUntil: effectiveSnoozedUntil,
1377
+ reason: input.reason ?? null,
1378
+ createdByAgentId: input.actor.type === "agent" ? input.actor.agentId ?? null : null,
1379
+ createdByUserId: input.actor.type === "operator" ? input.actor.userId ?? null : null,
1380
+ createdByRunId,
1381
+ })
1382
+ .returning();
1383
+ await logActivity(db, {
1384
+ squadId: run.squadId,
1385
+ actorType: input.actor.type === "agent" ? "agent" : "user",
1386
+ actorId: input.actor.type === "agent"
1387
+ ? input.actor.agentId ?? "agent"
1388
+ : input.actor.type === "operator"
1389
+ ? input.actor.userId ?? "operator"
1390
+ : "unknown",
1391
+ agentId: input.actor.type === "agent" ? input.actor.agentId ?? null : null,
1392
+ runId: run.id,
1393
+ action: input.decision === "snooze" ? "heartbeat.watchdog_snoozed" : "heartbeat.watchdog_decision_recorded",
1394
+ entityType: "heartbeat_run",
1395
+ entityId: run.id,
1396
+ details: {
1397
+ source: "recovery.record_watchdog_decision",
1398
+ decision: input.decision,
1399
+ evaluationIssueId: input.evaluationIssueId ?? null,
1400
+ snoozedUntil: effectiveSnoozedUntil?.toISOString() ?? null,
1401
+ reason: input.reason ?? null,
1402
+ },
1403
+ });
1404
+ return row;
1405
+ }
1406
+ async function findOpenStrandedIssueRecoveryIssue(squadId, sourceIssueId) {
1407
+ return db
1408
+ .select()
1409
+ .from(issues)
1410
+ .where(and(eq(issues.squadId, squadId), eq(issues.originKind, STRANDED_ISSUE_RECOVERY_ORIGIN_KIND), eq(issues.originId, sourceIssueId), isNull(issues.hiddenAt), notInArray(issues.status, ["done", "cancelled"])))
1411
+ .orderBy(desc(issues.createdAt))
1412
+ .limit(1)
1413
+ .then((rows) => rows[0] ?? null);
1414
+ }
1415
+ function isStrandedIssueRecoveryIssue(issue) {
1416
+ return issue.originKind === STRANDED_ISSUE_RECOVERY_ORIGIN_KIND;
1417
+ }
1418
+ async function buildNestedStrandedRecoveryLine(issue, prefix) {
1419
+ const sourceIssueId = readNonEmptyString(issue.originId);
1420
+ const sourceIssue = sourceIssueId
1421
+ ? await db
1422
+ .select({ id: issues.id, identifier: issues.identifier })
1423
+ .from(issues)
1424
+ .where(and(eq(issues.squadId, issue.squadId), eq(issues.id, sourceIssueId)))
1425
+ .then((rows) => rows[0] ?? null)
1426
+ : null;
1427
+ const sourceLine = sourceIssue
1428
+ ? `- Original source issue: ${issueUiLink(sourceIssue, prefix)}`
1429
+ : sourceIssueId
1430
+ ? `- Original source issue: \`${sourceIssueId}\``
1431
+ : "- Original source issue: unknown";
1432
+ return [
1433
+ "",
1434
+ "- Nested recovery: suppressed because this issue is already a `stranded_issue_recovery` issue.",
1435
+ sourceLine,
1436
+ "- Next action: the assigned recovery owner or operator should fix the runtime/adapter problem, resolve or reassign the original source issue, then mark this recovery issue done or cancelled.",
1437
+ ].join("\n");
1438
+ }
1439
+ async function resolveStrandedIssueRecoveryOwnerAgentId(issue) {
1440
+ const candidateIds = [];
1441
+ if (issue.assigneeAgentId) {
1442
+ const assignee = await getAgent(issue.assigneeAgentId);
1443
+ if (assignee?.reportsTo)
1444
+ candidateIds.push(assignee.reportsTo);
1445
+ }
1446
+ if (issue.createdByAgentId) {
1447
+ const creator = await getAgent(issue.createdByAgentId);
1448
+ if (creator?.reportsTo)
1449
+ candidateIds.push(creator.reportsTo);
1450
+ candidateIds.push(issue.createdByAgentId);
1451
+ }
1452
+ const roleCandidates = await db
1453
+ .select()
1454
+ .from(agents)
1455
+ .where(and(eq(agents.squadId, issue.squadId), inArray(agents.role, ["engineering_lead", "squad_lead"])))
1456
+ .orderBy(sql `case when ${agents.role} = 'engineering_lead' then 0 else 1 end`, asc(agents.createdAt));
1457
+ candidateIds.push(...roleCandidates.map((agent) => agent.id));
1458
+ if (issue.assigneeAgentId)
1459
+ candidateIds.push(issue.assigneeAgentId);
1460
+ const seen = new Set();
1461
+ for (const agentId of candidateIds) {
1462
+ if (seen.has(agentId))
1463
+ continue;
1464
+ seen.add(agentId);
1465
+ const candidate = await getAgent(agentId);
1466
+ if (!candidate || candidate.squadId !== issue.squadId)
1467
+ continue;
1468
+ const budgetBlock = await budgets.getInvocationBlock(issue.squadId, candidate.id, {
1469
+ issueId: issue.id,
1470
+ projectId: issue.projectId,
1471
+ });
1472
+ if (isAgentInvokable(candidate) && !budgetBlock)
1473
+ return candidate.id;
1474
+ }
1475
+ return null;
1476
+ }
1477
+ function buildStrandedIssueRecoveryDescription(input) {
1478
+ const sourceIssue = issueUiLink({ identifier: input.issue.identifier, id: input.issue.id }, input.prefix);
1479
+ const runLink = input.latestRun
1480
+ ? `[\`${input.latestRun.id}\`](/${input.prefix}/agents/${input.latestRun.agentId}/runs/${input.latestRun.id})`
1481
+ : "none";
1482
+ if (input.recoveryCause === SUCCESSFUL_RUN_MISSING_STATE_REASON) {
1483
+ const sourceRunId = input.successfulRunHandoffEvidence?.sourceRunId;
1484
+ const sourceRunLink = sourceRunId && input.latestRun
1485
+ ? `[\`${sourceRunId}\`](/${input.prefix}/agents/${input.latestRun.agentId}/runs/${sourceRunId})`
1486
+ : "unknown";
1487
+ const missingDisposition = input.successfulRunHandoffEvidence?.missingDisposition ?? "clear_next_step";
1488
+ return [
1489
+ "Slaw exhausted the bounded corrective handoff for a successful run that still has no valid issue disposition.",
1490
+ "",
1491
+ "This is not a runtime/adapter crash report. The source run succeeded; the remaining problem is the missing `done`, `in_review`, `blocked`, delegated follow-up, or explicit continuation path.",
1492
+ "",
1493
+ "## Safe Evidence",
1494
+ "",
1495
+ `- Source issue: ${sourceIssue}`,
1496
+ `- Source run: ${sourceRunLink}`,
1497
+ `- Corrective handoff run: ${runLink}`,
1498
+ `- Source assignee: ${agentUiLink(input.sourceAssignee ?? null, input.prefix)}`,
1499
+ `- Latest issue status: \`${input.issue.status}\``,
1500
+ `- Latest handoff run status: \`${input.latestRun?.status ?? "unknown"}\``,
1501
+ `- Normalized cause: \`${SUCCESSFUL_RUN_MISSING_STATE_REASON}\``,
1502
+ `- Missing disposition: \`${missingDisposition}\``,
1503
+ "- Suggested manager action: choose and record a valid issue disposition without copying transcript content.",
1504
+ "",
1505
+ "## Required Action",
1506
+ "",
1507
+ "- Inspect the source issue and run metadata, not raw transcript excerpts.",
1508
+ "- Choose a valid issue disposition: `done`/`cancelled`, `in_review` with an owner, `blocked` with first-class blockers, delegated follow-up work, or an explicit continuation path.",
1509
+ "- When the source issue has a clear owner and disposition, mark this recovery issue done.",
1510
+ ].join("\n");
1511
+ }
1512
+ const retryReason = readNonEmptyString(parseObject(input.latestRun?.contextSnapshot)?.retryReason) ?? "unknown";
1513
+ const failureSummary = summarizeRunFailureForIssueComment(input.latestRun);
1514
+ return [
1515
+ "Slaw exhausted automatic recovery for an assigned issue and created this explicit recovery task.",
1516
+ "",
1517
+ "## Source",
1518
+ "",
1519
+ `- Source issue: ${sourceIssue}`,
1520
+ `- Previous source status: \`${input.previousStatus}\``,
1521
+ `- Latest retry run: ${runLink}`,
1522
+ `- Latest retry status: \`${input.latestRun?.status ?? "unknown"}\``,
1523
+ `- Detected invariant: \`stranded_assigned_issue\``,
1524
+ `- Retry reason: \`${retryReason}\``,
1525
+ failureSummary ? `- Failure: ${failureSummary.trim()}` : "- Failure: none recorded",
1526
+ "",
1527
+ "## Ownership",
1528
+ "",
1529
+ "- Selected owner: the first invokable manager/creator/executive candidate with budget available.",
1530
+ "",
1531
+ "## Required Action",
1532
+ "",
1533
+ "- Inspect the latest run and source issue state.",
1534
+ "- Fix the runtime/adapter problem, reassign the source issue, or convert the source issue into a clear manual-review state.",
1535
+ "- When the source issue has a live execution path or has been intentionally resolved, mark this recovery issue done.",
1536
+ ].join("\n");
1537
+ }
1538
+ async function ensureStrandedIssueRecoveryIssue(input) {
1539
+ if (isStrandedIssueRecoveryIssue(input.issue))
1540
+ return null;
1541
+ const existing = await findOpenStrandedIssueRecoveryIssue(input.issue.squadId, input.issue.id);
1542
+ if (existing)
1543
+ return existing;
1544
+ const ownerAgentId = await resolveStrandedIssueRecoveryOwnerAgentId(input.issue);
1545
+ if (!ownerAgentId)
1546
+ return null;
1547
+ const prefix = await getSquadIssuePrefix(input.issue.squadId);
1548
+ const sourceAssignee = input.issue.assigneeAgentId ? await getAgent(input.issue.assigneeAgentId) : null;
1549
+ const recoveryCause = input.recoveryCause ?? "stranded_assigned_issue";
1550
+ let recovery;
1551
+ try {
1552
+ recovery = await issuesSvc.create(input.issue.squadId, {
1553
+ title: recoveryCause === SUCCESSFUL_RUN_MISSING_STATE_REASON
1554
+ ? `Recover missing next step ${input.issue.identifier ?? input.issue.title}`
1555
+ : `Recover stalled issue ${input.issue.identifier ?? input.issue.title}`,
1556
+ description: buildStrandedIssueRecoveryDescription({
1557
+ issue: input.issue,
1558
+ latestRun: input.latestRun,
1559
+ previousStatus: input.previousStatus,
1560
+ prefix,
1561
+ recoveryCause,
1562
+ successfulRunHandoffEvidence: input.successfulRunHandoffEvidence,
1563
+ sourceAssignee,
1564
+ }),
1565
+ status: "todo",
1566
+ priority: input.issue.priority,
1567
+ parentId: input.issue.id,
1568
+ projectId: input.issue.projectId,
1569
+ goalId: input.issue.goalId,
1570
+ assigneeAgentId: ownerAgentId,
1571
+ assigneeAdapterOverrides: recoveryAssigneeAdapterOverrides("status_only"),
1572
+ originKind: STRANDED_ISSUE_RECOVERY_ORIGIN_KIND,
1573
+ originId: input.issue.id,
1574
+ originRunId: input.latestRun?.id ?? null,
1575
+ originFingerprint: [
1576
+ STRANDED_ISSUE_RECOVERY_ORIGIN_KIND,
1577
+ input.issue.squadId,
1578
+ input.issue.id,
1579
+ recoveryCause,
1580
+ input.latestRun?.id ?? "no-run",
1581
+ ].join(":"),
1582
+ billingCode: input.issue.billingCode,
1583
+ inheritExecutionWorkspaceFromIssueId: input.issue.id,
1584
+ });
1585
+ }
1586
+ catch (error) {
1587
+ if (!isUniqueStrandedIssueRecoveryConflict(error))
1588
+ throw error;
1589
+ const raced = await findOpenStrandedIssueRecoveryIssue(input.issue.squadId, input.issue.id);
1590
+ if (!raced)
1591
+ throw error;
1592
+ return raced;
1593
+ }
1594
+ await deps.enqueueWakeup(ownerAgentId, {
1595
+ source: "assignment",
1596
+ triggerDetail: "system",
1597
+ reason: "issue_assigned",
1598
+ payload: withRecoveryModelProfileHint({
1599
+ issueId: recovery.id,
1600
+ sourceIssueId: input.issue.id,
1601
+ strandedRunId: input.latestRun?.id ?? null,
1602
+ recoveryCause,
1603
+ }, "status_only"),
1604
+ requestedByActorType: "system",
1605
+ requestedByActorId: null,
1606
+ contextSnapshot: withRecoveryModelProfileHint({
1607
+ issueId: recovery.id,
1608
+ taskId: recovery.id,
1609
+ wakeReason: "issue_assigned",
1610
+ source: STRANDED_ISSUE_RECOVERY_ORIGIN_KIND,
1611
+ sourceIssueId: input.issue.id,
1612
+ strandedRunId: input.latestRun?.id ?? null,
1613
+ recoveryCause,
1614
+ }, "status_only"),
1615
+ });
1616
+ return recovery;
1617
+ }
1618
+ function strandedRecoveryActionKind(cause) {
1619
+ return cause === SUCCESSFUL_RUN_MISSING_STATE_REASON
1620
+ ? "missing_disposition"
1621
+ : "stranded_assigned_issue";
1622
+ }
1623
+ function strandedRecoveryActionFingerprint(input) {
1624
+ return [
1625
+ "source_scoped_recovery",
1626
+ input.issue.squadId,
1627
+ input.issue.id,
1628
+ input.recoveryCause,
1629
+ ].join(":");
1630
+ }
1631
+ function buildStrandedRecoveryActionEvidence(input) {
1632
+ const context = parseObject(input.latestRun?.contextSnapshot);
1633
+ return {
1634
+ sourceIssueId: input.issue.id,
1635
+ sourceIdentifier: input.issue.identifier,
1636
+ previousStatus: input.previousStatus,
1637
+ latestIssueStatus: input.issue.status,
1638
+ latestRunId: input.latestRun?.id ?? null,
1639
+ latestRunStatus: input.latestRun?.status ?? null,
1640
+ latestRunErrorCode: input.latestRun?.errorCode ?? null,
1641
+ retryReason: readNonEmptyString(context.retryReason) ?? null,
1642
+ recoveryCause: input.recoveryCause,
1643
+ sourceRunId: input.successfulRunHandoffEvidence?.sourceRunId ?? null,
1644
+ correctiveRunId: input.successfulRunHandoffEvidence?.correctiveRunId ?? null,
1645
+ missingDisposition: input.successfulRunHandoffEvidence?.missingDisposition ?? null,
1646
+ handoffAttempt: input.successfulRunHandoffEvidence?.handoffAttempt ?? null,
1647
+ maxHandoffAttempts: input.successfulRunHandoffEvidence?.maxHandoffAttempts ?? null,
1648
+ };
1649
+ }
1650
+ async function ensureSourceScopedStrandedRecoveryAction(input) {
1651
+ const recoveryCause = input.recoveryCause ?? "stranded_assigned_issue";
1652
+ const ownerAgentId = await resolveStrandedIssueRecoveryOwnerAgentId(input.issue);
1653
+ const now = new Date();
1654
+ const action = await recoveryActionsSvc.upsertSourceScoped({
1655
+ squadId: input.issue.squadId,
1656
+ sourceIssueId: input.issue.id,
1657
+ kind: strandedRecoveryActionKind(recoveryCause),
1658
+ ownerType: ownerAgentId ? "agent" : "operator",
1659
+ ownerAgentId,
1660
+ previousOwnerAgentId: input.issue.assigneeAgentId,
1661
+ returnOwnerAgentId: input.issue.assigneeAgentId,
1662
+ cause: recoveryCause,
1663
+ fingerprint: strandedRecoveryActionFingerprint({
1664
+ issue: input.issue,
1665
+ recoveryCause,
1666
+ }),
1667
+ evidence: buildStrandedRecoveryActionEvidence({
1668
+ issue: input.issue,
1669
+ latestRun: input.latestRun,
1670
+ previousStatus: input.previousStatus,
1671
+ recoveryCause,
1672
+ successfulRunHandoffEvidence: input.successfulRunHandoffEvidence,
1673
+ }),
1674
+ nextAction: recoveryCause === SUCCESSFUL_RUN_MISSING_STATE_REASON
1675
+ ? "Choose and record a valid issue disposition without copying transcript content."
1676
+ : "Restore a live execution path, fix the runtime/adapter failure, or record an intentional manual resolution.",
1677
+ wakePolicy: ownerAgentId
1678
+ ? {
1679
+ type: "wake_owner",
1680
+ reason: "source_scoped_recovery_action",
1681
+ ownerAgentId,
1682
+ }
1683
+ : {
1684
+ type: "operator_escalation",
1685
+ reason: "no_invokable_recovery_owner",
1686
+ },
1687
+ monitorPolicy: null,
1688
+ maxAttempts: null,
1689
+ lastAttemptAt: now,
1690
+ });
1691
+ return action;
1692
+ }
1693
+ async function enqueueSourceScopedStrandedRecoveryWake(input) {
1694
+ if (!input.action.ownerAgentId)
1695
+ return;
1696
+ await deps.enqueueWakeup(input.action.ownerAgentId, {
1697
+ source: "assignment",
1698
+ triggerDetail: "system",
1699
+ reason: "source_scoped_recovery_action",
1700
+ idempotencyKey: `source_scoped_recovery_action:${input.action.id}:${input.action.attemptCount}`,
1701
+ payload: withRecoveryModelProfileHint({
1702
+ issueId: input.issue.id,
1703
+ sourceIssueId: input.issue.id,
1704
+ recoveryActionId: input.action.id,
1705
+ strandedRunId: input.latestRun?.id ?? null,
1706
+ recoveryCause: input.recoveryCause,
1707
+ }, "status_only"),
1708
+ requestedByActorType: "system",
1709
+ requestedByActorId: null,
1710
+ contextSnapshot: withRecoveryModelProfileHint({
1711
+ issueId: input.issue.id,
1712
+ taskId: input.issue.id,
1713
+ wakeReason: "source_scoped_recovery_action",
1714
+ skipIssueComment: true,
1715
+ source: "issue_recovery_action",
1716
+ recoveryActionId: input.action.id,
1717
+ sourceIssueId: input.issue.id,
1718
+ strandedRunId: input.latestRun?.id ?? null,
1719
+ recoveryCause: input.recoveryCause,
1720
+ }, "status_only"),
1721
+ });
1722
+ }
1723
+ function buildRecoveryIssueInPlaceEscalationComment(input) {
1724
+ const runLink = input.latestRun
1725
+ ? runUiLink({ id: input.latestRun.id, agentId: input.latestRun.agentId }, input.prefix)
1726
+ : "none";
1727
+ const retryReason = readNonEmptyString(parseObject(input.latestRun?.contextSnapshot)?.retryReason) ?? "none";
1728
+ const failureSummary = summarizeRunFailureForIssueComment(input.latestRun);
1729
+ return [
1730
+ "Slaw stopped automatic stranded-work recovery for this recovery issue.",
1731
+ "",
1732
+ `- Recovery issue: ${issueUiLink({ identifier: input.issue.identifier, id: input.issue.id }, input.prefix)}`,
1733
+ `- Previous status: \`${input.previousStatus}\``,
1734
+ `- Latest run: ${runLink}`,
1735
+ `- Latest run status: \`${input.latestRun?.status ?? "unknown"}\``,
1736
+ `- Retry reason: \`${retryReason}\``,
1737
+ failureSummary ? `- Failure: ${failureSummary.trim()}` : "- Failure: none recorded",
1738
+ "- Guard: recovery issues do not create nested `stranded_issue_recovery` issues.",
1739
+ "",
1740
+ "Next action: the current recovery owner should inspect the failed run evidence, restore a live execution path or record the manual resolution, then move this recovery issue out of `blocked`.",
1741
+ ].join("\n");
1742
+ }
1743
+ async function escalateStrandedRecoveryIssueInPlace(input) {
1744
+ const updated = await issuesSvc.update(input.issue.id, { status: "blocked" });
1745
+ if (!updated)
1746
+ return null;
1747
+ const prefix = await getSquadIssuePrefix(input.issue.squadId);
1748
+ await issuesSvc.addComment(input.issue.id, buildRecoveryIssueInPlaceEscalationComment({
1749
+ issue: input.issue,
1750
+ previousStatus: input.previousStatus,
1751
+ latestRun: input.latestRun,
1752
+ prefix,
1753
+ }), {});
1754
+ await logActivity(db, {
1755
+ squadId: input.issue.squadId,
1756
+ actorType: "system",
1757
+ actorId: "system",
1758
+ agentId: null,
1759
+ runId: null,
1760
+ action: "issue.updated",
1761
+ entityType: "issue",
1762
+ entityId: input.issue.id,
1763
+ details: {
1764
+ identifier: input.issue.identifier,
1765
+ status: "blocked",
1766
+ previousStatus: input.previousStatus,
1767
+ source: "recovery.reconcile_stranded_recovery_issue",
1768
+ latestRunId: input.latestRun?.id ?? null,
1769
+ latestRunStatus: input.latestRun?.status ?? null,
1770
+ latestRunErrorCode: input.latestRun?.errorCode ?? null,
1771
+ originKind: input.issue.originKind,
1772
+ originId: input.issue.originId,
1773
+ },
1774
+ });
1775
+ return updated;
1776
+ }
1777
+ async function existingBlockerIssueIds(squadId, issueId) {
1778
+ return db
1779
+ .select({ blockerIssueId: issueRelations.issueId })
1780
+ .from(issueRelations)
1781
+ .where(and(eq(issueRelations.squadId, squadId), eq(issueRelations.relatedIssueId, issueId), eq(issueRelations.type, "blocks")))
1782
+ .then((rows) => rows.map((row) => row.blockerIssueId));
1783
+ }
1784
+ async function existingUnresolvedBlockerIssueIds(squadId, issueId) {
1785
+ return db
1786
+ .select({ blockerIssueId: issueRelations.issueId })
1787
+ .from(issueRelations)
1788
+ .innerJoin(issues, and(eq(issues.squadId, issueRelations.squadId), eq(issues.id, issueRelations.issueId)))
1789
+ .where(and(eq(issueRelations.squadId, squadId), eq(issueRelations.relatedIssueId, issueId), eq(issueRelations.type, "blocks"), notInArray(issues.status, ["done", "cancelled"])))
1790
+ .then((rows) => rows.map((row) => row.blockerIssueId));
1791
+ }
1792
+ async function escalateStrandedAssignedIssue(input) {
1793
+ if (isStrandedIssueRecoveryIssue(input.issue)) {
1794
+ return escalateStrandedRecoveryIssueInPlace({
1795
+ issue: input.issue,
1796
+ previousStatus: input.previousStatus,
1797
+ latestRun: input.latestRun,
1798
+ });
1799
+ }
1800
+ const recoveryCause = input.recoveryCause ?? "stranded_assigned_issue";
1801
+ const recoveryAction = await ensureSourceScopedStrandedRecoveryAction({
1802
+ issue: input.issue,
1803
+ previousStatus: input.previousStatus,
1804
+ latestRun: input.latestRun,
1805
+ recoveryCause,
1806
+ successfulRunHandoffEvidence: input.successfulRunHandoffEvidence,
1807
+ });
1808
+ const blockerIds = await existingUnresolvedBlockerIssueIds(input.issue.squadId, input.issue.id);
1809
+ const updated = await issuesSvc.update(input.issue.id, {
1810
+ status: "blocked",
1811
+ blockedByIssueIds: blockerIds,
1812
+ assigneeAgentId: recoveryAction.ownerAgentId ?? input.issue.assigneeAgentId,
1813
+ });
1814
+ if (!updated)
1815
+ return null;
1816
+ const prefix = await getSquadIssuePrefix(input.issue.squadId);
1817
+ const recoveryOwner = recoveryAction.ownerAgentId ? await getAgent(recoveryAction.ownerAgentId) : null;
1818
+ const sourceAssignee = input.issue.assigneeAgentId ? await getAgent(input.issue.assigneeAgentId) : null;
1819
+ let notice = null;
1820
+ if (input.recoveryCause === SUCCESSFUL_RUN_MISSING_STATE_REASON && input.successfulRunHandoffEvidence) {
1821
+ notice = buildSuccessfulRunHandoffExhaustedNotice({
1822
+ issue: input.issue,
1823
+ sourceRun: input.successfulRunHandoffEvidence.sourceRunId
1824
+ ? { id: input.successfulRunHandoffEvidence.sourceRunId, status: "succeeded" }
1825
+ : null,
1826
+ correctiveRun: input.latestRun ? { id: input.latestRun.id, status: input.latestRun.status } : null,
1827
+ sourceAssignee,
1828
+ recoveryIssue: null,
1829
+ recoveryActionId: recoveryAction.id,
1830
+ recoveryOwner,
1831
+ latestIssueStatus: input.issue.status,
1832
+ latestHandoffRunStatus: input.latestRun?.status ?? "unknown",
1833
+ missingDisposition: input.successfulRunHandoffEvidence.missingDisposition,
1834
+ });
1835
+ }
1836
+ const recoveryLine = recoveryAction.ownerAgentId
1837
+ ? [
1838
+ "",
1839
+ `- Recovery action: \`${recoveryAction.id}\``,
1840
+ `- Recovery owner: ${agentUiLink(recoveryOwner, prefix)}`,
1841
+ "- Next action: the recovery owner should either restore a live execution path or record the manual resolution on the source issue.",
1842
+ ].join("\n")
1843
+ : [
1844
+ "",
1845
+ `- Recovery action: \`${recoveryAction.id}\``,
1846
+ "- Recovery owner: operator escalation, because Slaw could not find an invokable manager, creator, or executive owner with budget available.",
1847
+ "- Next action: a operator should assign an invokable recovery owner, fix the agent/runtime state, or record an intentional manual resolution.",
1848
+ ].join("\n");
1849
+ if (recoveryAction.attemptCount === 1) {
1850
+ const escalationCommentMarker = `Recovery action: \`${recoveryAction.id}\``;
1851
+ const hasEscalationComment = await db
1852
+ .select({ id: issueComments.id, body: issueComments.body, metadata: issueComments.metadata })
1853
+ .from(issueComments)
1854
+ .where(and(eq(issueComments.issueId, input.issue.id), eq(issueComments.authorType, "system")))
1855
+ .orderBy(desc(issueComments.createdAt))
1856
+ .limit(50)
1857
+ .then((rows) => rows.some((row) => (row.body ?? "").includes(escalationCommentMarker) ||
1858
+ noticeMetadataReferencesRecoveryAction(row.metadata, recoveryAction.id)));
1859
+ if (!hasEscalationComment) {
1860
+ if (notice) {
1861
+ await issuesSvc.addComment(input.issue.id, notice.body, {}, {
1862
+ authorType: "system",
1863
+ presentation: notice.presentation,
1864
+ metadata: notice.metadata,
1865
+ });
1866
+ }
1867
+ else {
1868
+ await issuesSvc.addComment(input.issue.id, `${input.comment ?? ""}${recoveryLine}`, {}, {
1869
+ authorType: "system",
1870
+ });
1871
+ }
1872
+ }
1873
+ }
1874
+ await logActivity(db, {
1875
+ squadId: input.issue.squadId,
1876
+ actorType: "system",
1877
+ actorId: "system",
1878
+ agentId: null,
1879
+ runId: null,
1880
+ action: input.recoveryCause === SUCCESSFUL_RUN_MISSING_STATE_REASON
1881
+ ? "issue.successful_run_handoff_escalated"
1882
+ : "issue.updated",
1883
+ entityType: "issue",
1884
+ entityId: input.issue.id,
1885
+ details: {
1886
+ identifier: input.issue.identifier,
1887
+ status: "blocked",
1888
+ previousStatus: input.previousStatus,
1889
+ source: input.recoveryCause === SUCCESSFUL_RUN_MISSING_STATE_REASON
1890
+ ? "recovery.reconcile_successful_run_handoff_missing_state"
1891
+ : "recovery.reconcile_stranded_assigned_issue",
1892
+ recoveryCause: input.recoveryCause ?? "stranded_assigned_issue",
1893
+ latestRunId: input.latestRun?.id ?? null,
1894
+ latestRunStatus: input.latestRun?.status ?? null,
1895
+ latestRunErrorCode: input.latestRun?.errorCode ?? null,
1896
+ recoveryActionId: recoveryAction.id,
1897
+ recoveryOwnerAgentId: recoveryAction.ownerAgentId,
1898
+ previousOwnerAgentId: recoveryAction.previousOwnerAgentId,
1899
+ returnOwnerAgentId: recoveryAction.returnOwnerAgentId,
1900
+ blockerIssueIds: blockerIds,
1901
+ },
1902
+ });
1903
+ await enqueueSourceScopedStrandedRecoveryWake({
1904
+ action: recoveryAction,
1905
+ issue: input.issue,
1906
+ latestRun: input.latestRun,
1907
+ recoveryCause,
1908
+ });
1909
+ if (recoveryAction.ownerAgentId && recoveryAction.ownerAgentId === input.issue.assigneeAgentId) {
1910
+ const [currentIssue] = await db
1911
+ .select({
1912
+ status: issues.status,
1913
+ assigneeAgentId: issues.assigneeAgentId,
1914
+ })
1915
+ .from(issues)
1916
+ .where(eq(issues.id, input.issue.id))
1917
+ .limit(1);
1918
+ if (currentIssue &&
1919
+ (currentIssue.status !== "blocked" ||
1920
+ currentIssue.assigneeAgentId !== recoveryAction.ownerAgentId)) {
1921
+ const reblocked = await issuesSvc.update(input.issue.id, {
1922
+ status: "blocked",
1923
+ blockedByIssueIds: blockerIds,
1924
+ assigneeAgentId: recoveryAction.ownerAgentId,
1925
+ });
1926
+ if (reblocked)
1927
+ return reblocked;
1928
+ }
1929
+ }
1930
+ return updated;
1931
+ }
1932
+ async function reconcileStrandedAssignedIssues() {
1933
+ const candidates = await db
1934
+ .select()
1935
+ .from(issues)
1936
+ .where(and(isNull(issues.assigneeUserId), inArray(issues.status, ["todo", "in_progress"]), sql `${issues.assigneeAgentId} is not null`));
1937
+ const result = {
1938
+ assignmentDispatched: 0,
1939
+ dispatchRequeued: 0,
1940
+ continuationRequeued: 0,
1941
+ productiveContinuationObserved: 0,
1942
+ successfulContinuationObserved: 0,
1943
+ orphanBlockersAssigned: 0,
1944
+ successfulRunHandoffEscalated: 0,
1945
+ escalated: 0,
1946
+ skipped: 0,
1947
+ issueIds: [],
1948
+ };
1949
+ for (const issue of candidates) {
1950
+ const agentId = issue.assigneeAgentId;
1951
+ if (!agentId) {
1952
+ result.skipped += 1;
1953
+ continue;
1954
+ }
1955
+ const agent = await getAgent(agentId);
1956
+ if (!agent || agent.squadId !== issue.squadId || !isAgentInvokable(agent)) {
1957
+ result.skipped += 1;
1958
+ continue;
1959
+ }
1960
+ if (await hasActiveExecutionPath(issue.squadId, issue.id)) {
1961
+ result.skipped += 1;
1962
+ continue;
1963
+ }
1964
+ if (await isAutomaticRecoverySuppressedByPauseHold(db, issue.squadId, issue.id, treeControlSvc)) {
1965
+ result.skipped += 1;
1966
+ continue;
1967
+ }
1968
+ const latestRun = await getLatestIssueRun(issue.squadId, issue.id);
1969
+ if (isStrandedIssueRecoveryIssue(issue) && isUnsuccessfulTerminalIssueRun(latestRun)) {
1970
+ const updated = await escalateStrandedRecoveryIssueInPlace({
1971
+ issue,
1972
+ previousStatus: issue.status,
1973
+ latestRun,
1974
+ });
1975
+ if (updated) {
1976
+ result.escalated += 1;
1977
+ result.issueIds.push(issue.id);
1978
+ }
1979
+ else {
1980
+ result.skipped += 1;
1981
+ }
1982
+ continue;
1983
+ }
1984
+ if (issue.status === "todo") {
1985
+ if (!latestRun) {
1986
+ if (await hasQueuedIssueWake(issue.squadId, issue.id)) {
1987
+ result.skipped += 1;
1988
+ continue;
1989
+ }
1990
+ if (await isInvocationBudgetBlocked(issue, agentId)) {
1991
+ result.skipped += 1;
1992
+ continue;
1993
+ }
1994
+ const queued = await enqueueInitialAssignedTodoDispatch(issue, agentId);
1995
+ if (queued) {
1996
+ result.assignmentDispatched += 1;
1997
+ result.issueIds.push(issue.id);
1998
+ }
1999
+ else {
2000
+ result.skipped += 1;
2001
+ }
2002
+ continue;
2003
+ }
2004
+ if (latestRun.status === "succeeded") {
2005
+ result.skipped += 1;
2006
+ continue;
2007
+ }
2008
+ if (didAutomaticRecoveryFail(latestRun, "assignment_recovery")) {
2009
+ const failureSummary = summarizeRunFailureForIssueComment(latestRun);
2010
+ const updated = await escalateStrandedAssignedIssue({
2011
+ issue,
2012
+ previousStatus: "todo",
2013
+ latestRun,
2014
+ comment: "Slaw automatically retried dispatch for this assigned `todo` issue after a lost wake/run, " +
2015
+ `but it still has no live execution path.${failureSummary ?? ""} ` +
2016
+ "Moving it to `blocked` so it is visible for intervention.",
2017
+ });
2018
+ if (updated) {
2019
+ result.escalated += 1;
2020
+ result.issueIds.push(issue.id);
2021
+ }
2022
+ else {
2023
+ result.skipped += 1;
2024
+ }
2025
+ continue;
2026
+ }
2027
+ if (await isInvocationBudgetBlocked(issue, agentId)) {
2028
+ result.skipped += 1;
2029
+ continue;
2030
+ }
2031
+ const queued = await enqueueStrandedIssueRecovery({
2032
+ issueId: issue.id,
2033
+ agentId,
2034
+ reason: "issue_assignment_recovery",
2035
+ retryReason: "assignment_recovery",
2036
+ source: "issue.assignment_recovery",
2037
+ retryOfRunId: latestRun.id,
2038
+ });
2039
+ if (queued) {
2040
+ result.dispatchRequeued += 1;
2041
+ result.issueIds.push(issue.id);
2042
+ }
2043
+ else {
2044
+ result.skipped += 1;
2045
+ }
2046
+ continue;
2047
+ }
2048
+ if (!latestRun && !issue.checkoutRunId && !issue.executionRunId) {
2049
+ result.skipped += 1;
2050
+ continue;
2051
+ }
2052
+ const handoffEvidence = isExhaustedSuccessfulRunHandoff(latestRun);
2053
+ if (handoffEvidence) {
2054
+ if (!handoffEvidence.exhausted) {
2055
+ result.skipped += 1;
2056
+ continue;
2057
+ }
2058
+ const updated = await escalateStrandedAssignedIssue({
2059
+ issue,
2060
+ previousStatus: "in_progress",
2061
+ latestRun,
2062
+ recoveryCause: SUCCESSFUL_RUN_MISSING_STATE_REASON,
2063
+ successfulRunHandoffEvidence: handoffEvidence,
2064
+ });
2065
+ if (updated) {
2066
+ result.successfulRunHandoffEscalated += 1;
2067
+ result.issueIds.push(issue.id);
2068
+ }
2069
+ else {
2070
+ result.skipped += 1;
2071
+ }
2072
+ continue;
2073
+ }
2074
+ if (isSuccessfulInProgressContinuationRun(latestRun)) {
2075
+ const successfulRun = latestRun;
2076
+ if (!isProductiveContinuationRun(successfulRun)) {
2077
+ result.successfulContinuationObserved += 1;
2078
+ result.skipped += 1;
2079
+ continue;
2080
+ }
2081
+ if (isRepeatedProductiveContinuationRecovery(successfulRun)) {
2082
+ const updated = await escalateStrandedAssignedIssue({
2083
+ issue,
2084
+ previousStatus: "in_progress",
2085
+ latestRun: successfulRun,
2086
+ comment: "Slaw automatically retried continuation for this assigned `in_progress` issue and the retry " +
2087
+ "made progress, but it still has no live execution path. Moving it to `blocked` so it is visible for intervention.",
2088
+ });
2089
+ if (updated) {
2090
+ result.escalated += 1;
2091
+ result.issueIds.push(issue.id);
2092
+ }
2093
+ else {
2094
+ result.skipped += 1;
2095
+ }
2096
+ continue;
2097
+ }
2098
+ if (await isInvocationBudgetBlocked(issue, agentId)) {
2099
+ result.skipped += 1;
2100
+ continue;
2101
+ }
2102
+ const queued = await enqueueStrandedIssueRecovery({
2103
+ issueId: issue.id,
2104
+ agentId,
2105
+ reason: "issue_continuation_needed",
2106
+ retryReason: "issue_continuation_needed",
2107
+ source: "issue.productive_terminal_continuation_recovery",
2108
+ retryOfRunId: successfulRun.id,
2109
+ });
2110
+ if (queued) {
2111
+ result.continuationRequeued += 1;
2112
+ result.issueIds.push(issue.id);
2113
+ }
2114
+ else {
2115
+ result.skipped += 1;
2116
+ }
2117
+ continue;
2118
+ }
2119
+ if (isUnsuccessfulTerminalIssueRun(latestRun)) {
2120
+ const classification = classifyContinuationFailure(latestRun);
2121
+ if (classification.kind === "non_retryable") {
2122
+ const failureSummary = summarizeRunFailureForIssueComment(latestRun);
2123
+ const updated = await escalateStrandedAssignedIssue({
2124
+ issue,
2125
+ previousStatus: "in_progress",
2126
+ latestRun,
2127
+ comment: "Slaw detected a non-retryable failure on this issue's continuation run " +
2128
+ `(\`${classification.errorCode}\`). Skipping automatic retries and moving it to \`blocked\` ` +
2129
+ `so it is visible for intervention.${failureSummary ?? ""}`,
2130
+ });
2131
+ if (updated) {
2132
+ result.escalated += 1;
2133
+ result.issueIds.push(issue.id);
2134
+ }
2135
+ else {
2136
+ result.skipped += 1;
2137
+ }
2138
+ continue;
2139
+ }
2140
+ if (didAutomaticRecoveryFail(latestRun, "issue_continuation_needed")) {
2141
+ const { consecutive, latestFinishedAt } = await summarizeRecentContinuationRetries(issue.squadId, issue.id, classification.errorCode);
2142
+ if (consecutive >= classification.maxAttempts) {
2143
+ const failureSummary = summarizeRunFailureForIssueComment(latestRun);
2144
+ const attemptCopy = consecutive <= 1 ? "" : ` (${consecutive}× attempts)`;
2145
+ const causeCopy = classification.errorCode
2146
+ ? ` Latest cause: \`${classification.errorCode}\`.`
2147
+ : "";
2148
+ const updated = await escalateStrandedAssignedIssue({
2149
+ issue,
2150
+ previousStatus: "in_progress",
2151
+ latestRun,
2152
+ comment: "Slaw automatically retried continuation for this assigned `in_progress` issue after its live " +
2153
+ `execution disappeared, but it still has no live execution path${attemptCopy}.${causeCopy}${failureSummary ?? ""} ` +
2154
+ "Moving it to `blocked` so it is visible for intervention.",
2155
+ });
2156
+ if (updated) {
2157
+ result.escalated += 1;
2158
+ result.issueIds.push(issue.id);
2159
+ }
2160
+ else {
2161
+ result.skipped += 1;
2162
+ }
2163
+ continue;
2164
+ }
2165
+ if (classification.baseBackoffMs > 0 && latestFinishedAt) {
2166
+ const elapsed = Date.now() - latestFinishedAt.getTime();
2167
+ const requiredDelay = classification.baseBackoffMs *
2168
+ Math.pow(2, Math.max(0, consecutive - 1));
2169
+ if (elapsed < requiredDelay) {
2170
+ result.skipped += 1;
2171
+ continue;
2172
+ }
2173
+ }
2174
+ }
2175
+ }
2176
+ if (await isInvocationBudgetBlocked(issue, agentId)) {
2177
+ result.skipped += 1;
2178
+ continue;
2179
+ }
2180
+ const queued = await enqueueStrandedIssueRecovery({
2181
+ issueId: issue.id,
2182
+ agentId,
2183
+ reason: "issue_continuation_needed",
2184
+ retryReason: "issue_continuation_needed",
2185
+ source: "issue.continuation_recovery",
2186
+ retryOfRunId: latestRun?.id ?? issue.checkoutRunId ?? null,
2187
+ });
2188
+ if (queued) {
2189
+ result.continuationRequeued += 1;
2190
+ result.issueIds.push(issue.id);
2191
+ }
2192
+ else {
2193
+ result.skipped += 1;
2194
+ }
2195
+ }
2196
+ const orphanBlockerRecovery = await reconcileUnassignedBlockingIssues();
2197
+ result.orphanBlockersAssigned = orphanBlockerRecovery.assigned;
2198
+ result.skipped += orphanBlockerRecovery.skipped;
2199
+ result.issueIds.push(...orphanBlockerRecovery.issueIds);
2200
+ return result;
2201
+ }
2202
+ async function collectIssueGraphLivenessFindings() {
2203
+ const issueRowsPromise = Promise.resolve(db
2204
+ .select({
2205
+ id: issues.id,
2206
+ squadId: issues.squadId,
2207
+ identifier: issues.identifier,
2208
+ title: issues.title,
2209
+ status: issues.status,
2210
+ projectId: issues.projectId,
2211
+ goalId: issues.goalId,
2212
+ parentId: issues.parentId,
2213
+ assigneeAgentId: issues.assigneeAgentId,
2214
+ assigneeUserId: issues.assigneeUserId,
2215
+ createdByAgentId: issues.createdByAgentId,
2216
+ createdByUserId: issues.createdByUserId,
2217
+ executionPolicy: issues.executionPolicy,
2218
+ executionState: issues.executionState,
2219
+ monitorNextCheckAt: issues.monitorNextCheckAt,
2220
+ monitorAttemptCount: issues.monitorAttemptCount,
2221
+ })
2222
+ .from(issues)
2223
+ .where(and(isNull(issues.hiddenAt), notInArray(issues.originKind, [RECOVERY_ORIGIN_KINDS.issueGraphLivenessEscalation]))));
2224
+ const [issueRows, relationRows, agentRows, activeRunRows, activeIssueRunRows, wakeRows, interactionRows, approvalRows, recoveryIssueRows, recoveryActionRows,] = await Promise.all([
2225
+ issueRowsPromise,
2226
+ db
2227
+ .select({
2228
+ squadId: issueRelations.squadId,
2229
+ blockerIssueId: issueRelations.issueId,
2230
+ blockedIssueId: issueRelations.relatedIssueId,
2231
+ })
2232
+ .from(issueRelations)
2233
+ .where(eq(issueRelations.type, "blocks")),
2234
+ db
2235
+ .select({
2236
+ id: agents.id,
2237
+ squadId: agents.squadId,
2238
+ name: agents.name,
2239
+ role: agents.role,
2240
+ title: agents.title,
2241
+ status: agents.status,
2242
+ reportsTo: agents.reportsTo,
2243
+ })
2244
+ .from(agents),
2245
+ db
2246
+ .select({
2247
+ squadId: heartbeatRuns.squadId,
2248
+ agentId: heartbeatRuns.agentId,
2249
+ status: heartbeatRuns.status,
2250
+ contextSnapshot: heartbeatRuns.contextSnapshot,
2251
+ })
2252
+ .from(heartbeatRuns)
2253
+ .where(inArray(heartbeatRuns.status, [...EXECUTION_PATH_HEARTBEAT_RUN_STATUSES])),
2254
+ db
2255
+ .select({
2256
+ squadId: issues.squadId,
2257
+ agentId: heartbeatRuns.agentId,
2258
+ status: heartbeatRuns.status,
2259
+ issueId: issues.id,
2260
+ })
2261
+ .from(issues)
2262
+ .innerJoin(heartbeatRuns, eq(issues.executionRunId, heartbeatRuns.id))
2263
+ .where(and(isNull(issues.hiddenAt), notInArray(issues.originKind, [RECOVERY_ORIGIN_KINDS.issueGraphLivenessEscalation]), inArray(heartbeatRuns.status, [...EXECUTION_PATH_HEARTBEAT_RUN_STATUSES]))),
2264
+ db
2265
+ .select({
2266
+ squadId: agentWakeupRequests.squadId,
2267
+ agentId: agentWakeupRequests.agentId,
2268
+ status: agentWakeupRequests.status,
2269
+ payload: agentWakeupRequests.payload,
2270
+ })
2271
+ .from(agentWakeupRequests)
2272
+ .where(inArray(agentWakeupRequests.status, ["queued", "deferred_issue_execution"])),
2273
+ db
2274
+ .select({
2275
+ squadId: issueThreadInteractions.squadId,
2276
+ issueId: issueThreadInteractions.issueId,
2277
+ status: issueThreadInteractions.status,
2278
+ })
2279
+ .from(issueThreadInteractions)
2280
+ .where(eq(issueThreadInteractions.status, "pending")),
2281
+ db
2282
+ .select({
2283
+ squadId: issueApprovals.squadId,
2284
+ issueId: issueApprovals.issueId,
2285
+ status: approvals.status,
2286
+ })
2287
+ .from(issueApprovals)
2288
+ .innerJoin(approvals, eq(issueApprovals.approvalId, approvals.id))
2289
+ .where(inArray(approvals.status, ["pending", "revision_requested"])),
2290
+ db
2291
+ .select({
2292
+ squadId: issues.squadId,
2293
+ id: issues.id,
2294
+ status: issues.status,
2295
+ originKind: issues.originKind,
2296
+ originId: issues.originId,
2297
+ })
2298
+ .from(issues)
2299
+ .where(and(isNull(issues.hiddenAt), inArray(issues.originKind, [
2300
+ STRANDED_ISSUE_RECOVERY_ORIGIN_KIND,
2301
+ RECOVERY_ORIGIN_KINDS.issueGraphLivenessEscalation,
2302
+ ]), notInArray(issues.status, ["done", "cancelled"]))),
2303
+ issueRowsPromise.then((rows) => {
2304
+ const issueIdsUnderAnalysis = rows.map((row) => row.id);
2305
+ return issueIdsUnderAnalysis.length === 0
2306
+ ? []
2307
+ : db
2308
+ .select({
2309
+ squadId: issueRecoveryActions.squadId,
2310
+ issueId: issueRecoveryActions.sourceIssueId,
2311
+ status: issueRecoveryActions.status,
2312
+ })
2313
+ .from(issueRecoveryActions)
2314
+ .where(and(inArray(issueRecoveryActions.status, ["active", "escalated"]), inArray(issueRecoveryActions.sourceIssueId, issueIdsUnderAnalysis)));
2315
+ }),
2316
+ ]);
2317
+ const openRecoveryIssues = recoveryIssueRows.flatMap((row) => {
2318
+ if (row.originKind === RECOVERY_ORIGIN_KINDS.issueGraphLivenessEscalation) {
2319
+ const parsed = parseIssueGraphLivenessIncidentKey(row.originId);
2320
+ if (!parsed || parsed.squadId !== row.squadId)
2321
+ return [];
2322
+ return [
2323
+ {
2324
+ squadId: row.squadId,
2325
+ issueId: parsed.issueId,
2326
+ status: row.status,
2327
+ },
2328
+ {
2329
+ squadId: row.squadId,
2330
+ issueId: parsed.leafIssueId,
2331
+ status: row.status,
2332
+ },
2333
+ ];
2334
+ }
2335
+ const issueId = readNonEmptyString(row.originId);
2336
+ if (!issueId)
2337
+ return [];
2338
+ return [{
2339
+ squadId: row.squadId,
2340
+ issueId,
2341
+ status: row.status,
2342
+ }];
2343
+ });
2344
+ return classifyIssueGraphLiveness({
2345
+ issues: issueRows,
2346
+ relations: relationRows,
2347
+ agents: agentRows,
2348
+ activeRuns: activeRunRows.map((row) => ({
2349
+ squadId: row.squadId,
2350
+ agentId: row.agentId,
2351
+ status: row.status,
2352
+ issueId: issueIdFromRunContext(row.contextSnapshot),
2353
+ })).concat(activeIssueRunRows.map((row) => ({
2354
+ squadId: row.squadId,
2355
+ agentId: row.agentId,
2356
+ status: row.status,
2357
+ issueId: row.issueId,
2358
+ }))),
2359
+ queuedWakeRequests: wakeRows.map((row) => ({
2360
+ squadId: row.squadId,
2361
+ agentId: row.agentId,
2362
+ status: row.status,
2363
+ issueId: issueIdFromWakePayload(row.payload),
2364
+ })),
2365
+ pendingInteractions: interactionRows,
2366
+ pendingApprovals: approvalRows,
2367
+ openRecoveryIssues: openRecoveryIssues.concat(recoveryActionRows),
2368
+ now: new Date(),
2369
+ });
2370
+ }
2371
+ async function findOpenLivenessEscalation(squadId, incidentKey) {
2372
+ return db
2373
+ .select()
2374
+ .from(issues)
2375
+ .where(and(eq(issues.squadId, squadId), eq(issues.originKind, RECOVERY_ORIGIN_KINDS.issueGraphLivenessEscalation), eq(issues.originId, incidentKey), isNull(issues.hiddenAt), notInArray(issues.status, ["done", "cancelled"])))
2376
+ .limit(1)
2377
+ .then((rows) => rows[0] ?? null);
2378
+ }
2379
+ async function findOpenLivenessRecoveryIssueForLeaf(finding) {
2380
+ const byFingerprint = await db
2381
+ .select()
2382
+ .from(issues)
2383
+ .where(and(eq(issues.squadId, finding.squadId), eq(issues.originKind, RECOVERY_ORIGIN_KINDS.issueGraphLivenessEscalation), eq(issues.originFingerprint, livenessRecoveryLeafFingerprint(finding)), isNull(issues.hiddenAt), notInArray(issues.status, ["done", "cancelled"])))
2384
+ .limit(1)
2385
+ .then((rows) => rows[0] ?? null);
2386
+ if (byFingerprint)
2387
+ return byFingerprint;
2388
+ const leafIssueId = livenessRecoveryLeafIssueId(finding);
2389
+ const openRecoveries = await db
2390
+ .select()
2391
+ .from(issues)
2392
+ .where(and(eq(issues.squadId, finding.squadId), eq(issues.originKind, RECOVERY_ORIGIN_KINDS.issueGraphLivenessEscalation), isNull(issues.hiddenAt), notInArray(issues.status, ["done", "cancelled"])));
2393
+ return openRecoveries.find((row) => {
2394
+ const parsed = parseLivenessIncidentKey(row.originId);
2395
+ return parsed?.state === finding.state && parsed.leafIssueId === leafIssueId;
2396
+ }) ?? null;
2397
+ }
2398
+ async function removeRecoveryBlockerFromSource(recovery) {
2399
+ const parsed = parseLivenessIncidentKey(recovery.originId);
2400
+ if (!parsed)
2401
+ return false;
2402
+ const sourceIssue = await db
2403
+ .select()
2404
+ .from(issues)
2405
+ .where(and(eq(issues.squadId, recovery.squadId), eq(issues.id, parsed.issueId)))
2406
+ .then((rows) => rows[0] ?? null);
2407
+ if (!sourceIssue)
2408
+ return false;
2409
+ const blockerIds = await existingBlockerIssueIds(sourceIssue.squadId, sourceIssue.id);
2410
+ if (!blockerIds.includes(recovery.id))
2411
+ return false;
2412
+ await issuesSvc.update(sourceIssue.id, {
2413
+ blockedByIssueIds: blockerIds.filter((blockerId) => blockerId !== recovery.id),
2414
+ });
2415
+ return true;
2416
+ }
2417
+ async function hasActiveRunForIssueId(squadId, issueId) {
2418
+ const [contextRun, issueRun] = await Promise.all([
2419
+ db
2420
+ .select({ id: heartbeatRuns.id })
2421
+ .from(heartbeatRuns)
2422
+ .where(and(eq(heartbeatRuns.squadId, squadId), inArray(heartbeatRuns.status, [...EXECUTION_PATH_HEARTBEAT_RUN_STATUSES]), sql `(${heartbeatRuns.contextSnapshot}->>'issueId' = ${issueId}
2423
+ OR ${heartbeatRuns.contextSnapshot}->>'taskId' = ${issueId})`))
2424
+ .limit(1)
2425
+ .then((rows) => rows[0] ?? null),
2426
+ db
2427
+ .select({ id: heartbeatRuns.id })
2428
+ .from(issues)
2429
+ .innerJoin(heartbeatRuns, eq(issues.executionRunId, heartbeatRuns.id))
2430
+ .where(and(eq(issues.squadId, squadId), eq(issues.id, issueId), inArray(heartbeatRuns.status, [...EXECUTION_PATH_HEARTBEAT_RUN_STATUSES])))
2431
+ .limit(1)
2432
+ .then((rows) => rows[0] ?? null),
2433
+ ]);
2434
+ return Boolean(contextRun || issueRun);
2435
+ }
2436
+ async function retireObsoleteLivenessRecoveryIssues(findings) {
2437
+ const currentIncidentKeys = new Set(findings.map((finding) => finding.incidentKey));
2438
+ const currentLeafKeys = new Set(findings.map((finding) => livenessRecoveryLeafKey(finding.squadId, finding.state, livenessRecoveryLeafIssueId(finding))));
2439
+ const openRecoveries = await db
2440
+ .select()
2441
+ .from(issues)
2442
+ .where(and(eq(issues.originKind, RECOVERY_ORIGIN_KINDS.issueGraphLivenessEscalation), isNull(issues.hiddenAt), notInArray(issues.status, ["done", "cancelled"])));
2443
+ const result = {
2444
+ retired: 0,
2445
+ activeSkipped: 0,
2446
+ blockerRelationsRemoved: 0,
2447
+ retiredIssueIds: [],
2448
+ };
2449
+ for (const recovery of openRecoveries) {
2450
+ if (recovery.originId && currentIncidentKeys.has(recovery.originId))
2451
+ continue;
2452
+ const parsed = parseLivenessIncidentKey(recovery.originId);
2453
+ if (!parsed)
2454
+ continue;
2455
+ if (currentLeafKeys.has(livenessRecoveryLeafKey(parsed.squadId, parsed.state, parsed.leafIssueId))) {
2456
+ continue;
2457
+ }
2458
+ const sourceIssue = await db
2459
+ .select({
2460
+ id: issues.id,
2461
+ status: issues.status,
2462
+ })
2463
+ .from(issues)
2464
+ .where(and(eq(issues.squadId, parsed.squadId), eq(issues.id, parsed.issueId)))
2465
+ .then((rows) => rows[0] ?? null);
2466
+ if (sourceIssue && !["done", "cancelled"].includes(sourceIssue.status)) {
2467
+ const blockerIds = await existingBlockerIssueIds(parsed.squadId, sourceIssue.id);
2468
+ if (blockerIds.includes(recovery.id)) {
2469
+ result.activeSkipped += 1;
2470
+ continue;
2471
+ }
2472
+ }
2473
+ if (await removeRecoveryBlockerFromSource(recovery)) {
2474
+ result.blockerRelationsRemoved += 1;
2475
+ }
2476
+ if (await hasActiveRunForIssueId(recovery.squadId, recovery.id)) {
2477
+ result.activeSkipped += 1;
2478
+ continue;
2479
+ }
2480
+ await issuesSvc.update(recovery.id, { status: "cancelled" });
2481
+ result.retired += 1;
2482
+ result.retiredIssueIds.push(recovery.id);
2483
+ }
2484
+ return result;
2485
+ }
2486
+ async function retireDoneLivenessRecoveryBlockers() {
2487
+ const closedRecoveries = await db
2488
+ .select()
2489
+ .from(issues)
2490
+ .where(and(eq(issues.originKind, RECOVERY_ORIGIN_KINDS.issueGraphLivenessEscalation), isNull(issues.hiddenAt), inArray(issues.status, ["done", "cancelled"])));
2491
+ let blockerRelationsRemoved = 0;
2492
+ for (const recovery of closedRecoveries) {
2493
+ if (await removeRecoveryBlockerFromSource(recovery)) {
2494
+ blockerRelationsRemoved += 1;
2495
+ }
2496
+ }
2497
+ return { blockerRelationsRemoved };
2498
+ }
2499
+ function normalizeIssueGraphLivenessAutoRecoveryLookbackHours(raw) {
2500
+ const numeric = Math.floor(asNumber(raw, DEFAULT_ISSUE_GRAPH_LIVENESS_AUTO_RECOVERY_LOOKBACK_HOURS));
2501
+ return Math.min(MAX_ISSUE_GRAPH_LIVENESS_AUTO_RECOVERY_LOOKBACK_HOURS, Math.max(MIN_ISSUE_GRAPH_LIVENESS_AUTO_RECOVERY_LOOKBACK_HOURS, numeric));
2502
+ }
2503
+ function livenessDependencyIssueKey(squadId, issueId) {
2504
+ return `${squadId}:${issueId}`;
2505
+ }
2506
+ async function loadLivenessDependencyUpdatedAtByIssue(findings) {
2507
+ const issueIds = [
2508
+ ...new Set(findings.flatMap((finding) => finding.dependencyPath.map((entry) => entry.issueId))),
2509
+ ];
2510
+ if (issueIds.length === 0)
2511
+ return new Map();
2512
+ const rows = await db
2513
+ .select({ id: issues.id, squadId: issues.squadId, updatedAt: issues.updatedAt })
2514
+ .from(issues)
2515
+ .where(inArray(issues.id, issueIds));
2516
+ return new Map(rows.map((row) => [
2517
+ livenessDependencyIssueKey(row.squadId, row.id),
2518
+ row.updatedAt,
2519
+ ]));
2520
+ }
2521
+ function latestDependencyUpdatedAtForLivenessFinding(finding, updatedAtByIssueKey) {
2522
+ const dependencyIssueIds = [...new Set(finding.dependencyPath.map((entry) => entry.issueId))];
2523
+ if (dependencyIssueIds.length === 0)
2524
+ return null;
2525
+ const timestamps = dependencyIssueIds.map((issueId) => updatedAtByIssueKey.get(livenessDependencyIssueKey(finding.squadId, issueId)) ?? null);
2526
+ if (timestamps.some((timestamp) => !timestamp))
2527
+ return null;
2528
+ const [firstTimestamp, ...remainingTimestamps] = timestamps;
2529
+ return remainingTimestamps.reduce((latest, updatedAt) => updatedAt > latest ? updatedAt : latest, firstTimestamp);
2530
+ }
2531
+ function isLivenessFindingInsideAutoRecoveryLookback(finding, cutoff, updatedAtByIssueKey) {
2532
+ const latestUpdatedAt = latestDependencyUpdatedAtForLivenessFinding(finding, updatedAtByIssueKey);
2533
+ return Boolean(latestUpdatedAt && latestUpdatedAt >= cutoff);
2534
+ }
2535
+ async function buildIssueGraphLivenessAutoRecoveryPreview(opts) {
2536
+ const now = opts?.now ?? new Date();
2537
+ const lookbackHours = normalizeIssueGraphLivenessAutoRecoveryLookbackHours(opts?.lookbackHours);
2538
+ const cutoff = new Date(now.getTime() - lookbackHours * 60 * 60 * 1000);
2539
+ const findings = await collectIssueGraphLivenessFindings();
2540
+ const updatedAtByIssueKey = await loadLivenessDependencyUpdatedAtByIssue(findings);
2541
+ const issueIds = [...new Set(findings.map((finding) => finding.recoveryIssueId))];
2542
+ const recoveryRows = issueIds.length > 0
2543
+ ? await db
2544
+ .select({ id: issues.id, identifier: issues.identifier, title: issues.title })
2545
+ .from(issues)
2546
+ .where(inArray(issues.id, issueIds))
2547
+ : [];
2548
+ const recoveryById = new Map(recoveryRows.map((row) => [row.id, row]));
2549
+ const items = [];
2550
+ let skippedOutsideLookback = 0;
2551
+ for (const finding of findings) {
2552
+ const latestDependencyUpdatedAt = latestDependencyUpdatedAtForLivenessFinding(finding, updatedAtByIssueKey);
2553
+ if (!latestDependencyUpdatedAt || latestDependencyUpdatedAt < cutoff) {
2554
+ skippedOutsideLookback += 1;
2555
+ continue;
2556
+ }
2557
+ const recoveryIssue = recoveryById.get(finding.recoveryIssueId);
2558
+ items.push({
2559
+ issueId: finding.issueId,
2560
+ identifier: finding.identifier,
2561
+ title: finding.dependencyPath[0]?.title ?? finding.identifier ?? finding.issueId,
2562
+ state: finding.state,
2563
+ severity: finding.severity,
2564
+ reason: finding.reason,
2565
+ recoveryIssueId: finding.recoveryIssueId,
2566
+ recoveryIdentifier: recoveryIssue?.identifier ?? null,
2567
+ recoveryTitle: recoveryIssue?.title ?? null,
2568
+ recommendedOwnerAgentId: finding.recommendedOwnerAgentId,
2569
+ incidentKey: finding.incidentKey,
2570
+ latestDependencyUpdatedAt: latestDependencyUpdatedAt.toISOString(),
2571
+ dependencyPath: finding.dependencyPath,
2572
+ });
2573
+ }
2574
+ return {
2575
+ lookbackHours,
2576
+ cutoff: cutoff.toISOString(),
2577
+ generatedAt: now.toISOString(),
2578
+ findings: findings.length,
2579
+ recoverableFindings: items.length,
2580
+ skippedOutsideLookback,
2581
+ items,
2582
+ };
2583
+ }
2584
+ async function resolveEscalationOwnerAgentId(finding, issue) {
2585
+ const detailedCandidates = finding.recommendedOwnerCandidates.length > 0
2586
+ ? finding.recommendedOwnerCandidates
2587
+ : finding.recommendedOwnerCandidateAgentIds.map((agentId) => ({
2588
+ agentId,
2589
+ reason: "ordered_invokable_fallback",
2590
+ sourceIssueId: finding.recoveryIssueId,
2591
+ }));
2592
+ const seenCandidates = new Set();
2593
+ const candidates = detailedCandidates.filter((candidate) => {
2594
+ if (seenCandidates.has(candidate.agentId))
2595
+ return false;
2596
+ seenCandidates.add(candidate.agentId);
2597
+ return true;
2598
+ });
2599
+ const budgetBlockedCandidateAgentIds = [];
2600
+ for (const candidate of candidates) {
2601
+ const budgetBlock = await budgets.getInvocationBlock(issue.squadId, candidate.agentId, {
2602
+ issueId: issue.id,
2603
+ projectId: issue.projectId,
2604
+ });
2605
+ if (!budgetBlock) {
2606
+ return {
2607
+ agentId: candidate.agentId,
2608
+ reason: candidate.reason,
2609
+ sourceIssueId: candidate.sourceIssueId,
2610
+ candidateAgentIds: candidates.map((entry) => entry.agentId),
2611
+ candidateReasons: candidates.map((entry) => ({
2612
+ agentId: entry.agentId,
2613
+ reason: entry.reason,
2614
+ sourceIssueId: entry.sourceIssueId,
2615
+ })),
2616
+ budgetBlockedCandidateAgentIds,
2617
+ };
2618
+ }
2619
+ budgetBlockedCandidateAgentIds.push(candidate.agentId);
2620
+ }
2621
+ return null;
2622
+ }
2623
+ function shouldReuseRecoveryExecutionWorkspace(input) {
2624
+ if (input.finding.recoveryIssueId === input.finding.issueId)
2625
+ return false;
2626
+ return input.recoveryIssue.assigneeAgentId === input.ownerAgentId;
2627
+ }
2628
+ async function ensureIssueBlockedByEscalation(input) {
2629
+ const blockerIds = await existingBlockerIssueIds(input.issue.squadId, input.issue.id);
2630
+ const nextBlockerIds = [...new Set([...blockerIds, input.escalationIssueId])];
2631
+ const isAlreadyBlockedByEscalation = blockerIds.includes(input.escalationIssueId);
2632
+ const isAlreadyBlocked = input.issue.status === "blocked";
2633
+ if (isAlreadyBlockedByEscalation && isAlreadyBlocked) {
2634
+ return input.issue;
2635
+ }
2636
+ const update = {
2637
+ blockedByIssueIds: nextBlockerIds,
2638
+ };
2639
+ if (!isAlreadyBlocked) {
2640
+ update.status = "blocked";
2641
+ }
2642
+ const updated = await issuesSvc.update(input.issue.id, update);
2643
+ if (!updated)
2644
+ return null;
2645
+ await logActivity(db, {
2646
+ squadId: input.issue.squadId,
2647
+ actorType: "system",
2648
+ actorId: "system",
2649
+ agentId: null,
2650
+ runId: input.runId ?? null,
2651
+ action: "issue.blockers.updated",
2652
+ entityType: "issue",
2653
+ entityId: input.issue.id,
2654
+ details: {
2655
+ source: "recovery.reconcile_issue_graph_liveness",
2656
+ incidentKey: input.finding.incidentKey,
2657
+ findingState: input.finding.state,
2658
+ blockerIssueIds: nextBlockerIds,
2659
+ escalationIssueId: input.escalationIssueId,
2660
+ status: update.status ?? input.issue.status,
2661
+ previousStatus: input.issue.status,
2662
+ },
2663
+ });
2664
+ return updated;
2665
+ }
2666
+ async function createIssueGraphLivenessEscalation(input) {
2667
+ const issue = await db
2668
+ .select()
2669
+ .from(issues)
2670
+ .where(eq(issues.id, input.finding.issueId))
2671
+ .then((rows) => rows[0] ?? null);
2672
+ if (!issue || issue.squadId !== input.finding.squadId)
2673
+ return { kind: "skipped" };
2674
+ if (await isAutomaticRecoverySuppressedByPauseHold(db, issue.squadId, issue.id, treeControlSvc)) {
2675
+ return { kind: "skipped" };
2676
+ }
2677
+ const recoveryIssue = await db
2678
+ .select()
2679
+ .from(issues)
2680
+ .where(and(eq(issues.id, input.finding.recoveryIssueId), eq(issues.squadId, issue.squadId)))
2681
+ .then((rows) => rows[0] ?? null);
2682
+ if (!recoveryIssue)
2683
+ return { kind: "skipped" };
2684
+ const existing = await findOpenLivenessEscalation(issue.squadId, input.finding.incidentKey) ??
2685
+ await findOpenLivenessRecoveryIssueForLeaf(input.finding);
2686
+ if (existing) {
2687
+ await ensureIssueBlockedByEscalation({
2688
+ issue,
2689
+ escalationIssueId: existing.id,
2690
+ finding: input.finding,
2691
+ runId: input.runId ?? null,
2692
+ });
2693
+ return { kind: "existing", escalationIssueId: existing.id };
2694
+ }
2695
+ const ownerSelection = await resolveEscalationOwnerAgentId(input.finding, recoveryIssue);
2696
+ if (!ownerSelection)
2697
+ return { kind: "skipped" };
2698
+ const reuseRecoveryExecutionWorkspace = shouldReuseRecoveryExecutionWorkspace({
2699
+ finding: input.finding,
2700
+ recoveryIssue,
2701
+ ownerAgentId: ownerSelection.agentId,
2702
+ });
2703
+ let escalation;
2704
+ try {
2705
+ escalation = await issuesSvc.create(issue.squadId, {
2706
+ title: `Unblock liveness incident for ${issue.identifier ?? issue.id}`,
2707
+ description: buildLivenessEscalationDescription(input.finding),
2708
+ status: "todo",
2709
+ priority: "high",
2710
+ parentId: recoveryIssue.id,
2711
+ projectId: recoveryIssue.projectId,
2712
+ goalId: recoveryIssue.goalId,
2713
+ assigneeAgentId: ownerSelection.agentId,
2714
+ assigneeAdapterOverrides: recoveryAssigneeAdapterOverrides("status_only"),
2715
+ originKind: RECOVERY_ORIGIN_KINDS.issueGraphLivenessEscalation,
2716
+ originId: input.finding.incidentKey,
2717
+ originFingerprint: livenessRecoveryLeafFingerprint(input.finding),
2718
+ billingCode: recoveryIssue.billingCode,
2719
+ ...(reuseRecoveryExecutionWorkspace
2720
+ ? { inheritExecutionWorkspaceFromIssueId: recoveryIssue.id }
2721
+ : {
2722
+ executionWorkspaceId: null,
2723
+ executionWorkspacePreference: null,
2724
+ executionWorkspaceSettings: null,
2725
+ }),
2726
+ });
2727
+ }
2728
+ catch (error) {
2729
+ if (!isUniqueLivenessRecoveryConflict(error))
2730
+ throw error;
2731
+ const raced = await findOpenLivenessEscalation(issue.squadId, input.finding.incidentKey) ??
2732
+ await findOpenLivenessRecoveryIssueForLeaf(input.finding);
2733
+ if (!raced)
2734
+ throw error;
2735
+ await ensureIssueBlockedByEscalation({
2736
+ issue,
2737
+ escalationIssueId: raced.id,
2738
+ finding: input.finding,
2739
+ runId: input.runId ?? null,
2740
+ });
2741
+ return { kind: "existing", escalationIssueId: raced.id };
2742
+ }
2743
+ await ensureIssueBlockedByEscalation({
2744
+ issue,
2745
+ escalationIssueId: escalation.id,
2746
+ finding: input.finding,
2747
+ runId: input.runId ?? null,
2748
+ });
2749
+ await issuesSvc.addComment(issue.id, buildLivenessOriginalIssueComment(input.finding, escalation), { runId: input.runId ?? null });
2750
+ await logActivity(db, {
2751
+ squadId: issue.squadId,
2752
+ actorType: "system",
2753
+ actorId: "system",
2754
+ agentId: ownerSelection.agentId,
2755
+ runId: input.runId ?? null,
2756
+ action: "issue.harness_liveness_escalation_created",
2757
+ entityType: "issue",
2758
+ entityId: escalation.id,
2759
+ details: {
2760
+ source: "recovery.reconcile_issue_graph_liveness",
2761
+ incidentKey: input.finding.incidentKey,
2762
+ findingState: input.finding.state,
2763
+ sourceIssueId: issue.id,
2764
+ sourceIdentifier: issue.identifier,
2765
+ recoveryIssueId: recoveryIssue.id,
2766
+ recoveryIdentifier: recoveryIssue.identifier,
2767
+ escalationIssueId: escalation.id,
2768
+ escalationIdentifier: escalation.identifier,
2769
+ dependencyPath: input.finding.dependencyPath,
2770
+ ownerSelection: {
2771
+ selectedAgentId: ownerSelection.agentId,
2772
+ selectedReason: ownerSelection.reason,
2773
+ selectedSourceIssueId: ownerSelection.sourceIssueId,
2774
+ candidateAgentIds: ownerSelection.candidateAgentIds,
2775
+ candidateReasons: ownerSelection.candidateReasons,
2776
+ budgetBlockedCandidateAgentIds: ownerSelection.budgetBlockedCandidateAgentIds,
2777
+ },
2778
+ workspaceSelection: {
2779
+ reuseRecoveryExecutionWorkspace,
2780
+ inheritedExecutionWorkspaceFromIssueId: reuseRecoveryExecutionWorkspace ? recoveryIssue.id : null,
2781
+ projectWorkspaceSourceIssueId: recoveryIssue.id,
2782
+ },
2783
+ },
2784
+ });
2785
+ const wake = await deps.enqueueWakeup(ownerSelection.agentId, {
2786
+ source: "assignment",
2787
+ triggerDetail: "system",
2788
+ reason: "issue_assigned",
2789
+ payload: withRecoveryModelProfileHint({
2790
+ issueId: escalation.id,
2791
+ sourceIssueId: issue.id,
2792
+ recoveryIssueId: recoveryIssue.id,
2793
+ incidentKey: input.finding.incidentKey,
2794
+ }, "status_only"),
2795
+ requestedByActorType: "system",
2796
+ requestedByActorId: null,
2797
+ contextSnapshot: withRecoveryModelProfileHint({
2798
+ issueId: escalation.id,
2799
+ taskId: escalation.id,
2800
+ wakeReason: "issue_assigned",
2801
+ source: RECOVERY_ORIGIN_KINDS.issueGraphLivenessEscalation,
2802
+ sourceIssueId: issue.id,
2803
+ recoveryIssueId: recoveryIssue.id,
2804
+ incidentKey: input.finding.incidentKey,
2805
+ }, "status_only"),
2806
+ });
2807
+ logger.warn({
2808
+ incidentKey: input.finding.incidentKey,
2809
+ findingState: input.finding.state,
2810
+ sourceIssueId: issue.id,
2811
+ recoveryIssueId: recoveryIssue.id,
2812
+ escalationIssueId: escalation.id,
2813
+ ownerAgentId: ownerSelection.agentId,
2814
+ ownerSelectionReason: ownerSelection.reason,
2815
+ wakeupRunId: wake?.id ?? null,
2816
+ }, "created issue graph liveness escalation");
2817
+ return { kind: "created", escalationIssueId: escalation.id };
2818
+ }
2819
+ async function reconcileIssueGraphLiveness(opts) {
2820
+ const findings = await collectIssueGraphLivenessFindings();
2821
+ const experimentalSettings = await instanceSettings.getExperimental();
2822
+ const autoRecoveryEnabled = asBoolean(experimentalSettings.enableIssueGraphLivenessAutoRecovery, true) || opts?.force === true;
2823
+ const lookbackHours = normalizeIssueGraphLivenessAutoRecoveryLookbackHours(opts?.lookbackHours ?? experimentalSettings.issueGraphLivenessAutoRecoveryLookbackHours);
2824
+ const now = new Date();
2825
+ const cutoff = new Date(now.getTime() - lookbackHours * 60 * 60 * 1000);
2826
+ const obsoleteRecoveryCleanup = await retireObsoleteLivenessRecoveryIssues(findings);
2827
+ const doneRecoveryBlockerCleanup = await retireDoneLivenessRecoveryBlockers();
2828
+ const updatedAtByIssueKey = await loadLivenessDependencyUpdatedAtByIssue(findings);
2829
+ const result = {
2830
+ findings: findings.length,
2831
+ autoRecoveryEnabled,
2832
+ lookbackHours,
2833
+ cutoff: cutoff.toISOString(),
2834
+ escalationsCreated: 0,
2835
+ existingEscalations: 0,
2836
+ skipped: 0,
2837
+ skippedAutoRecoveryDisabled: 0,
2838
+ skippedOutsideLookback: 0,
2839
+ obsoleteRecoveriesRetired: obsoleteRecoveryCleanup.retired,
2840
+ obsoleteRecoveriesActiveSkipped: obsoleteRecoveryCleanup.activeSkipped,
2841
+ obsoleteRecoveryBlockerRelationsRemoved: obsoleteRecoveryCleanup.blockerRelationsRemoved,
2842
+ doneRecoveryBlockerRelationsRemoved: doneRecoveryBlockerCleanup.blockerRelationsRemoved,
2843
+ issueIds: [],
2844
+ escalationIssueIds: [],
2845
+ retiredRecoveryIssueIds: obsoleteRecoveryCleanup.retiredIssueIds,
2846
+ };
2847
+ if (!autoRecoveryEnabled) {
2848
+ result.skippedAutoRecoveryDisabled = findings.length;
2849
+ return result;
2850
+ }
2851
+ for (const finding of findings) {
2852
+ if (!isLivenessFindingInsideAutoRecoveryLookback(finding, cutoff, updatedAtByIssueKey)) {
2853
+ result.skippedOutsideLookback += 1;
2854
+ result.skipped += 1;
2855
+ continue;
2856
+ }
2857
+ const escalation = await createIssueGraphLivenessEscalation({
2858
+ finding,
2859
+ runId: opts?.runId ?? null,
2860
+ });
2861
+ if (escalation.kind === "created") {
2862
+ result.escalationsCreated += 1;
2863
+ result.issueIds.push(finding.issueId);
2864
+ result.escalationIssueIds.push(escalation.escalationIssueId);
2865
+ }
2866
+ else if (escalation.kind === "existing") {
2867
+ result.existingEscalations += 1;
2868
+ result.issueIds.push(finding.issueId);
2869
+ result.escalationIssueIds.push(escalation.escalationIssueId);
2870
+ }
2871
+ else {
2872
+ result.skipped += 1;
2873
+ }
2874
+ }
2875
+ return result;
2876
+ }
2877
+ function readRecoveryTimerIntervalMs(raw, fallback) {
2878
+ return Math.max(1, Math.floor(asNumber(raw, fallback)));
2879
+ }
2880
+ return {
2881
+ buildRunOutputSilence,
2882
+ escalateStrandedRecoveryIssueInPlace,
2883
+ escalateStrandedAssignedIssue,
2884
+ recordWatchdogDecision,
2885
+ scanSilentActiveRuns,
2886
+ reconcileStrandedAssignedIssues,
2887
+ buildIssueGraphLivenessAutoRecoveryPreview,
2888
+ reconcileIssueGraphLiveness,
2889
+ readRecoveryTimerIntervalMs,
2890
+ };
2891
+ }
2892
+ //# sourceMappingURL=service.js.map