@paperclipai/server 2026.817.0 → 2026.818.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (548) hide show
  1. package/dist/adapters/hermes-gateway-doc.d.ts +1 -1
  2. package/dist/adapters/hermes-gateway-doc.d.ts.map +1 -1
  3. package/dist/adapters/hermes-gateway-doc.js +1 -1
  4. package/dist/adapters/plugin-loader.d.ts.map +1 -1
  5. package/dist/adapters/plugin-loader.js +3 -2
  6. package/dist/adapters/plugin-loader.js.map +1 -1
  7. package/dist/agent-auth-jwt.d.ts.map +1 -1
  8. package/dist/agent-auth-jwt.js +8 -2
  9. package/dist/agent-auth-jwt.js.map +1 -1
  10. package/dist/app.d.ts +5 -1
  11. package/dist/app.d.ts.map +1 -1
  12. package/dist/app.js +202 -15
  13. package/dist/app.js.map +1 -1
  14. package/dist/built-ins/agents/summarizer/AGENTS.md +2 -2
  15. package/dist/built-ins/agents/summarizer/routines/refresh-stale-summaries.md +1 -0
  16. package/dist/db-errors.d.ts +9 -0
  17. package/dist/db-errors.d.ts.map +1 -1
  18. package/dist/db-errors.js +19 -0
  19. package/dist/db-errors.js.map +1 -1
  20. package/dist/errors.d.ts +1 -1
  21. package/dist/errors.d.ts.map +1 -1
  22. package/dist/errors.js +2 -2
  23. package/dist/errors.js.map +1 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +162 -38
  26. package/dist/index.js.map +1 -1
  27. package/dist/lib/request-promise-memo.d.ts +6 -0
  28. package/dist/lib/request-promise-memo.d.ts.map +1 -0
  29. package/dist/lib/request-promise-memo.js +25 -0
  30. package/dist/lib/request-promise-memo.js.map +1 -0
  31. package/dist/lib/request-promise-memo.test.d.ts +2 -0
  32. package/dist/lib/request-promise-memo.test.d.ts.map +1 -0
  33. package/dist/lib/request-promise-memo.test.js +74 -0
  34. package/dist/lib/request-promise-memo.test.js.map +1 -0
  35. package/dist/middleware/auth.d.ts +13 -1
  36. package/dist/middleware/auth.d.ts.map +1 -1
  37. package/dist/middleware/auth.js +92 -41
  38. package/dist/middleware/auth.js.map +1 -1
  39. package/dist/middleware/cloud-tenant-actor.test.js +26 -1
  40. package/dist/middleware/cloud-tenant-actor.test.js.map +1 -1
  41. package/dist/middleware/error-handler.d.ts.map +1 -1
  42. package/dist/middleware/error-handler.js +11 -2
  43. package/dist/middleware/error-handler.js.map +1 -1
  44. package/dist/middleware/logger.d.ts.map +1 -1
  45. package/dist/middleware/logger.js +7 -33
  46. package/dist/middleware/logger.js.map +1 -1
  47. package/dist/middleware/private-hostname-guard.d.ts.map +1 -1
  48. package/dist/middleware/private-hostname-guard.js +11 -6
  49. package/dist/middleware/private-hostname-guard.js.map +1 -1
  50. package/dist/middleware/private-json-etag.d.ts +3 -0
  51. package/dist/middleware/private-json-etag.d.ts.map +1 -0
  52. package/dist/middleware/private-json-etag.js +37 -0
  53. package/dist/middleware/private-json-etag.js.map +1 -0
  54. package/dist/middleware/redact-sensitive.d.ts.map +1 -1
  55. package/dist/middleware/redact-sensitive.js +11 -0
  56. package/dist/middleware/redact-sensitive.js.map +1 -1
  57. package/dist/realtime/live-events-ws.d.ts +12 -0
  58. package/dist/realtime/live-events-ws.d.ts.map +1 -1
  59. package/dist/realtime/live-events-ws.js +20 -0
  60. package/dist/realtime/live-events-ws.js.map +1 -1
  61. package/dist/redaction.js +1 -1
  62. package/dist/redaction.js.map +1 -1
  63. package/dist/routes/access.d.ts +2 -0
  64. package/dist/routes/access.d.ts.map +1 -1
  65. package/dist/routes/access.js +21 -10
  66. package/dist/routes/access.js.map +1 -1
  67. package/dist/routes/agents.d.ts +42 -0
  68. package/dist/routes/agents.d.ts.map +1 -1
  69. package/dist/routes/agents.js +875 -8
  70. package/dist/routes/agents.js.map +1 -1
  71. package/dist/routes/companies.d.ts +5 -1
  72. package/dist/routes/companies.d.ts.map +1 -1
  73. package/dist/routes/companies.js +467 -30
  74. package/dist/routes/companies.js.map +1 -1
  75. package/dist/routes/environments.d.ts +12 -3
  76. package/dist/routes/environments.d.ts.map +1 -1
  77. package/dist/routes/environments.js +139 -23
  78. package/dist/routes/environments.js.map +1 -1
  79. package/dist/routes/execution-workspaces.d.ts.map +1 -1
  80. package/dist/routes/execution-workspaces.js +240 -70
  81. package/dist/routes/execution-workspaces.js.map +1 -1
  82. package/dist/routes/file-resources.d.ts +14 -1
  83. package/dist/routes/file-resources.d.ts.map +1 -1
  84. package/dist/routes/file-resources.js +182 -3
  85. package/dist/routes/file-resources.js.map +1 -1
  86. package/dist/routes/index.d.ts +2 -1
  87. package/dist/routes/index.d.ts.map +1 -1
  88. package/dist/routes/index.js +2 -1
  89. package/dist/routes/index.js.map +1 -1
  90. package/dist/routes/instance-settings.d.ts.map +1 -1
  91. package/dist/routes/instance-settings.js +20 -1
  92. package/dist/routes/instance-settings.js.map +1 -1
  93. package/dist/routes/issues.d.ts.map +1 -1
  94. package/dist/routes/issues.js +750 -235
  95. package/dist/routes/issues.js.map +1 -1
  96. package/dist/routes/onboarding-seed.d.ts +27 -0
  97. package/dist/routes/onboarding-seed.d.ts.map +1 -0
  98. package/dist/routes/onboarding-seed.js +56 -0
  99. package/dist/routes/onboarding-seed.js.map +1 -0
  100. package/dist/routes/openapi.d.ts.map +1 -1
  101. package/dist/routes/openapi.js +316 -2
  102. package/dist/routes/openapi.js.map +1 -1
  103. package/dist/routes/secrets.d.ts.map +1 -1
  104. package/dist/routes/secrets.js +11 -1
  105. package/dist/routes/secrets.js.map +1 -1
  106. package/dist/routes/setup-token-route.test.d.ts +2 -0
  107. package/dist/routes/setup-token-route.test.d.ts.map +1 -0
  108. package/dist/routes/setup-token-route.test.js +896 -0
  109. package/dist/routes/setup-token-route.test.js.map +1 -0
  110. package/dist/routes/tool-access.d.ts.map +1 -1
  111. package/dist/routes/tool-access.js +87 -52
  112. package/dist/routes/tool-access.js.map +1 -1
  113. package/dist/routes/workspace-runtime-service-authz.d.ts +11 -1
  114. package/dist/routes/workspace-runtime-service-authz.d.ts.map +1 -1
  115. package/dist/routes/workspace-runtime-service-authz.js +18 -16
  116. package/dist/routes/workspace-runtime-service-authz.js.map +1 -1
  117. package/dist/services/access.d.ts.map +1 -1
  118. package/dist/services/activity.d.ts +1 -1
  119. package/dist/services/agents.d.ts +21 -0
  120. package/dist/services/agents.d.ts.map +1 -1
  121. package/dist/services/agents.js +121 -1
  122. package/dist/services/agents.js.map +1 -1
  123. package/dist/services/attention-resolver-audience.test.d.ts +2 -0
  124. package/dist/services/attention-resolver-audience.test.d.ts.map +1 -0
  125. package/dist/services/attention-resolver-audience.test.js +81 -0
  126. package/dist/services/attention-resolver-audience.test.js.map +1 -0
  127. package/dist/services/attention.d.ts +20 -1
  128. package/dist/services/attention.d.ts.map +1 -1
  129. package/dist/services/attention.js +45 -1
  130. package/dist/services/attention.js.map +1 -1
  131. package/dist/services/authorization.d.ts +2 -2
  132. package/dist/services/authorization.d.ts.map +1 -1
  133. package/dist/services/authorization.js +89 -46
  134. package/dist/services/authorization.js.map +1 -1
  135. package/dist/services/board-auth.d.ts.map +1 -1
  136. package/dist/services/board-auth.js +40 -1
  137. package/dist/services/board-auth.js.map +1 -1
  138. package/dist/services/board-auth.test.d.ts +2 -0
  139. package/dist/services/board-auth.test.d.ts.map +1 -0
  140. package/dist/services/board-auth.test.js +34 -0
  141. package/dist/services/board-auth.test.js.map +1 -0
  142. package/dist/services/built-in-agents.d.ts.map +1 -1
  143. package/dist/services/built-in-agents.js +20 -4
  144. package/dist/services/built-in-agents.js.map +1 -1
  145. package/dist/services/bundled-plugins.d.ts +11 -1
  146. package/dist/services/bundled-plugins.d.ts.map +1 -1
  147. package/dist/services/bundled-plugins.js +46 -1
  148. package/dist/services/bundled-plugins.js.map +1 -1
  149. package/dist/services/codex-device-login-reaper.d.ts +61 -0
  150. package/dist/services/codex-device-login-reaper.d.ts.map +1 -0
  151. package/dist/services/codex-device-login-reaper.js +219 -0
  152. package/dist/services/codex-device-login-reaper.js.map +1 -0
  153. package/dist/services/codex-device-login-service.d.ts +355 -0
  154. package/dist/services/codex-device-login-service.d.ts.map +1 -0
  155. package/dist/services/codex-device-login-service.js +775 -0
  156. package/dist/services/codex-device-login-service.js.map +1 -0
  157. package/dist/services/companies.d.ts.map +1 -1
  158. package/dist/services/companies.js +7 -0
  159. package/dist/services/companies.js.map +1 -1
  160. package/dist/services/company-export-readme.js +1 -1
  161. package/dist/services/company-export-readme.js.map +1 -1
  162. package/dist/services/company-import-transfers.d.ts +55 -0
  163. package/dist/services/company-import-transfers.d.ts.map +1 -0
  164. package/dist/services/company-import-transfers.js +151 -0
  165. package/dist/services/company-import-transfers.js.map +1 -0
  166. package/dist/services/company-portability.d.ts.map +1 -1
  167. package/dist/services/company-portability.js +128 -3
  168. package/dist/services/company-portability.js.map +1 -1
  169. package/dist/services/company-transfer-runs.d.ts +136 -0
  170. package/dist/services/company-transfer-runs.d.ts.map +1 -0
  171. package/dist/services/company-transfer-runs.js +243 -0
  172. package/dist/services/company-transfer-runs.js.map +1 -0
  173. package/dist/services/cross-issue-influence-limit.d.ts +7 -1
  174. package/dist/services/cross-issue-influence-limit.d.ts.map +1 -1
  175. package/dist/services/cross-issue-influence-limit.js.map +1 -1
  176. package/dist/services/environment-config.d.ts +15 -0
  177. package/dist/services/environment-config.d.ts.map +1 -1
  178. package/dist/services/environment-config.js +56 -10
  179. package/dist/services/environment-config.js.map +1 -1
  180. package/dist/services/environment-execution-target.d.ts.map +1 -1
  181. package/dist/services/environment-execution-target.js +61 -11
  182. package/dist/services/environment-execution-target.js.map +1 -1
  183. package/dist/services/environment-run-orchestrator.d.ts +16 -1
  184. package/dist/services/environment-run-orchestrator.d.ts.map +1 -1
  185. package/dist/services/environment-run-orchestrator.js +1 -1
  186. package/dist/services/environment-run-orchestrator.js.map +1 -1
  187. package/dist/services/environment-runtime.d.ts +178 -2
  188. package/dist/services/environment-runtime.d.ts.map +1 -1
  189. package/dist/services/environment-runtime.js +1035 -96
  190. package/dist/services/environment-runtime.js.map +1 -1
  191. package/dist/services/environments.d.ts +75 -0
  192. package/dist/services/environments.d.ts.map +1 -1
  193. package/dist/services/environments.js +199 -8
  194. package/dist/services/environments.js.map +1 -1
  195. package/dist/services/execution-allowlist.d.ts +16 -4
  196. package/dist/services/execution-allowlist.d.ts.map +1 -1
  197. package/dist/services/execution-allowlist.js +35 -16
  198. package/dist/services/execution-allowlist.js.map +1 -1
  199. package/dist/services/execution-allowlist.test.js +33 -0
  200. package/dist/services/execution-allowlist.test.js.map +1 -1
  201. package/dist/services/execution-workspace-policy.d.ts +14 -1
  202. package/dist/services/execution-workspace-policy.d.ts.map +1 -1
  203. package/dist/services/execution-workspace-policy.js +29 -12
  204. package/dist/services/execution-workspace-policy.js.map +1 -1
  205. package/dist/services/execution-workspaces.d.ts +89 -0
  206. package/dist/services/execution-workspaces.d.ts.map +1 -1
  207. package/dist/services/execution-workspaces.js +997 -125
  208. package/dist/services/execution-workspaces.js.map +1 -1
  209. package/dist/services/heartbeat-run-summary.d.ts +1 -0
  210. package/dist/services/heartbeat-run-summary.d.ts.map +1 -1
  211. package/dist/services/heartbeat-run-summary.js +19 -3
  212. package/dist/services/heartbeat-run-summary.js.map +1 -1
  213. package/dist/services/heartbeat.d.ts +18 -2
  214. package/dist/services/heartbeat.d.ts.map +1 -1
  215. package/dist/services/heartbeat.js +369 -16
  216. package/dist/services/heartbeat.js.map +1 -1
  217. package/dist/services/import-write-types.d.ts +12 -0
  218. package/dist/services/import-write-types.d.ts.map +1 -1
  219. package/dist/services/index.d.ts +2 -0
  220. package/dist/services/index.d.ts.map +1 -1
  221. package/dist/services/index.js +2 -0
  222. package/dist/services/index.js.map +1 -1
  223. package/dist/services/instance-settings.d.ts +10 -1
  224. package/dist/services/instance-settings.d.ts.map +1 -1
  225. package/dist/services/instance-settings.js +16 -9
  226. package/dist/services/instance-settings.js.map +1 -1
  227. package/dist/services/issue-review-policy.d.ts +4 -0
  228. package/dist/services/issue-review-policy.d.ts.map +1 -1
  229. package/dist/services/issue-review-policy.js +18 -7
  230. package/dist/services/issue-review-policy.js.map +1 -1
  231. package/dist/services/issue-thread-interaction-resolution.d.ts +92 -0
  232. package/dist/services/issue-thread-interaction-resolution.d.ts.map +1 -0
  233. package/dist/services/issue-thread-interaction-resolution.js +198 -0
  234. package/dist/services/issue-thread-interaction-resolution.js.map +1 -0
  235. package/dist/services/issue-thread-interaction-resolution.test.d.ts +2 -0
  236. package/dist/services/issue-thread-interaction-resolution.test.d.ts.map +1 -0
  237. package/dist/services/issue-thread-interaction-resolution.test.js +152 -0
  238. package/dist/services/issue-thread-interaction-resolution.test.js.map +1 -0
  239. package/dist/services/issue-thread-interactions.d.ts +24 -4
  240. package/dist/services/issue-thread-interactions.d.ts.map +1 -1
  241. package/dist/services/issue-thread-interactions.js +360 -145
  242. package/dist/services/issue-thread-interactions.js.map +1 -1
  243. package/dist/services/issue-thread-interactions.test.js +18 -11
  244. package/dist/services/issue-thread-interactions.test.js.map +1 -1
  245. package/dist/services/issues.d.ts +3 -0
  246. package/dist/services/issues.d.ts.map +1 -1
  247. package/dist/services/issues.js +157 -33
  248. package/dist/services/issues.js.map +1 -1
  249. package/dist/services/local-service-supervisor.d.ts +2 -0
  250. package/dist/services/local-service-supervisor.d.ts.map +1 -1
  251. package/dist/services/local-service-supervisor.js +53 -3
  252. package/dist/services/local-service-supervisor.js.map +1 -1
  253. package/dist/services/managed-environments.d.ts +4 -1
  254. package/dist/services/managed-environments.d.ts.map +1 -1
  255. package/dist/services/managed-environments.js +86 -2
  256. package/dist/services/managed-environments.js.map +1 -1
  257. package/dist/services/managed-environments.test.js +164 -0
  258. package/dist/services/managed-environments.test.js.map +1 -1
  259. package/dist/services/onboarding-greeting.d.ts +7 -0
  260. package/dist/services/onboarding-greeting.d.ts.map +1 -0
  261. package/dist/services/onboarding-greeting.js +27 -0
  262. package/dist/services/onboarding-greeting.js.map +1 -0
  263. package/dist/services/onboarding-greeting.test.d.ts +2 -0
  264. package/dist/services/onboarding-greeting.test.d.ts.map +1 -0
  265. package/dist/services/onboarding-greeting.test.js +32 -0
  266. package/dist/services/onboarding-greeting.test.js.map +1 -0
  267. package/dist/services/onboarding-seed.d.ts +57 -0
  268. package/dist/services/onboarding-seed.d.ts.map +1 -0
  269. package/dist/services/onboarding-seed.js +344 -0
  270. package/dist/services/onboarding-seed.js.map +1 -0
  271. package/dist/services/plan-review-context.d.ts +9 -1
  272. package/dist/services/plan-review-context.d.ts.map +1 -1
  273. package/dist/services/plan-review-context.js +192 -0
  274. package/dist/services/plan-review-context.js.map +1 -1
  275. package/dist/services/plugin-environment-driver.d.ts +46 -0
  276. package/dist/services/plugin-environment-driver.d.ts.map +1 -1
  277. package/dist/services/plugin-environment-driver.js +50 -0
  278. package/dist/services/plugin-environment-driver.js.map +1 -1
  279. package/dist/services/plugin-host-services.d.ts +1 -0
  280. package/dist/services/plugin-host-services.d.ts.map +1 -1
  281. package/dist/services/plugin-host-services.js +51 -28
  282. package/dist/services/plugin-host-services.js.map +1 -1
  283. package/dist/services/plugin-loader.d.ts +10 -1
  284. package/dist/services/plugin-loader.d.ts.map +1 -1
  285. package/dist/services/plugin-loader.js +43 -2
  286. package/dist/services/plugin-loader.js.map +1 -1
  287. package/dist/services/plugin-worker-manager.d.ts +61 -0
  288. package/dist/services/plugin-worker-manager.d.ts.map +1 -1
  289. package/dist/services/plugin-worker-manager.js +248 -1
  290. package/dist/services/plugin-worker-manager.js.map +1 -1
  291. package/dist/services/productivity-review.d.ts.map +1 -1
  292. package/dist/services/productivity-review.js +5 -0
  293. package/dist/services/productivity-review.js.map +1 -1
  294. package/dist/services/recovery/service.d.ts.map +1 -1
  295. package/dist/services/recovery/service.js +5 -1
  296. package/dist/services/recovery/service.js.map +1 -1
  297. package/dist/services/recovery/successful-run-handoff.d.ts +16 -1
  298. package/dist/services/recovery/successful-run-handoff.d.ts.map +1 -1
  299. package/dist/services/recovery/successful-run-handoff.js +32 -0
  300. package/dist/services/recovery/successful-run-handoff.js.map +1 -1
  301. package/dist/services/recovery/successful-run-handoff.test.js +51 -1
  302. package/dist/services/recovery/successful-run-handoff.test.js.map +1 -1
  303. package/dist/services/routines.d.ts.map +1 -1
  304. package/dist/services/routines.js +48 -4
  305. package/dist/services/routines.js.map +1 -1
  306. package/dist/services/runtime-exposure/broker-client.d.ts +88 -0
  307. package/dist/services/runtime-exposure/broker-client.d.ts.map +1 -0
  308. package/dist/services/runtime-exposure/broker-client.js +217 -0
  309. package/dist/services/runtime-exposure/broker-client.js.map +1 -0
  310. package/dist/services/runtime-exposure/broker-client.test.d.ts +2 -0
  311. package/dist/services/runtime-exposure/broker-client.test.d.ts.map +1 -0
  312. package/dist/services/runtime-exposure/broker-client.test.js +185 -0
  313. package/dist/services/runtime-exposure/broker-client.test.js.map +1 -0
  314. package/dist/services/runtime-exposure/exposure-manager.d.ts +135 -0
  315. package/dist/services/runtime-exposure/exposure-manager.d.ts.map +1 -0
  316. package/dist/services/runtime-exposure/exposure-manager.js +247 -0
  317. package/dist/services/runtime-exposure/exposure-manager.js.map +1 -0
  318. package/dist/services/runtime-exposure/exposure-manager.test.d.ts +2 -0
  319. package/dist/services/runtime-exposure/exposure-manager.test.d.ts.map +1 -0
  320. package/dist/services/runtime-exposure/exposure-manager.test.js +384 -0
  321. package/dist/services/runtime-exposure/exposure-manager.test.js.map +1 -0
  322. package/dist/services/runtime-exposure/loopback-listener.d.ts +33 -0
  323. package/dist/services/runtime-exposure/loopback-listener.d.ts.map +1 -0
  324. package/dist/services/runtime-exposure/loopback-listener.js +146 -0
  325. package/dist/services/runtime-exposure/loopback-listener.js.map +1 -0
  326. package/dist/services/runtime-exposure/loopback-listener.test.d.ts +2 -0
  327. package/dist/services/runtime-exposure/loopback-listener.test.d.ts.map +1 -0
  328. package/dist/services/runtime-exposure/loopback-listener.test.js +116 -0
  329. package/dist/services/runtime-exposure/loopback-listener.test.js.map +1 -0
  330. package/dist/services/runtime-exposure/port-pair.d.ts +47 -0
  331. package/dist/services/runtime-exposure/port-pair.d.ts.map +1 -0
  332. package/dist/services/runtime-exposure/port-pair.js +57 -0
  333. package/dist/services/runtime-exposure/port-pair.js.map +1 -0
  334. package/dist/services/runtime-exposure/port-pair.test.d.ts +2 -0
  335. package/dist/services/runtime-exposure/port-pair.test.d.ts.map +1 -0
  336. package/dist/services/runtime-exposure/port-pair.test.js +92 -0
  337. package/dist/services/runtime-exposure/port-pair.test.js.map +1 -0
  338. package/dist/services/runtime-exposure/port-reservation.d.ts +211 -0
  339. package/dist/services/runtime-exposure/port-reservation.d.ts.map +1 -0
  340. package/dist/services/runtime-exposure/port-reservation.js +380 -0
  341. package/dist/services/runtime-exposure/port-reservation.js.map +1 -0
  342. package/dist/services/runtime-exposure/port-reservation.test.d.ts +2 -0
  343. package/dist/services/runtime-exposure/port-reservation.test.d.ts.map +1 -0
  344. package/dist/services/runtime-exposure/port-reservation.test.js +456 -0
  345. package/dist/services/runtime-exposure/port-reservation.test.js.map +1 -0
  346. package/dist/services/runtime-exposure/tailscale-hostname.d.ts +11 -0
  347. package/dist/services/runtime-exposure/tailscale-hostname.d.ts.map +1 -0
  348. package/dist/services/runtime-exposure/tailscale-hostname.js +36 -0
  349. package/dist/services/runtime-exposure/tailscale-hostname.js.map +1 -0
  350. package/dist/services/runtime-exposure/tailscale-hostname.test.d.ts +2 -0
  351. package/dist/services/runtime-exposure/tailscale-hostname.test.d.ts.map +1 -0
  352. package/dist/services/runtime-exposure/tailscale-hostname.test.js +14 -0
  353. package/dist/services/runtime-exposure/tailscale-hostname.test.js.map +1 -0
  354. package/dist/services/sandbox-orphan-cleanup-spool.d.ts +64 -0
  355. package/dist/services/sandbox-orphan-cleanup-spool.d.ts.map +1 -0
  356. package/dist/services/sandbox-orphan-cleanup-spool.js +134 -0
  357. package/dist/services/sandbox-orphan-cleanup-spool.js.map +1 -0
  358. package/dist/services/sandbox-provider-runtime.d.ts +15 -0
  359. package/dist/services/sandbox-provider-runtime.d.ts.map +1 -1
  360. package/dist/services/sandbox-provider-runtime.js +1 -0
  361. package/dist/services/sandbox-provider-runtime.js.map +1 -1
  362. package/dist/services/secrets.d.ts +103 -0
  363. package/dist/services/secrets.d.ts.map +1 -1
  364. package/dist/services/secrets.js +432 -10
  365. package/dist/services/secrets.js.map +1 -1
  366. package/dist/services/setup-token-session.d.ts +630 -0
  367. package/dist/services/setup-token-session.d.ts.map +1 -0
  368. package/dist/services/setup-token-session.js +1005 -0
  369. package/dist/services/setup-token-session.js.map +1 -0
  370. package/dist/services/setup-token-session.test.d.ts +2 -0
  371. package/dist/services/setup-token-session.test.d.ts.map +1 -0
  372. package/dist/services/setup-token-session.test.js +1143 -0
  373. package/dist/services/setup-token-session.test.js.map +1 -0
  374. package/dist/services/setup-token-transport-binding.d.ts +202 -0
  375. package/dist/services/setup-token-transport-binding.d.ts.map +1 -0
  376. package/dist/services/setup-token-transport-binding.js +386 -0
  377. package/dist/services/setup-token-transport-binding.js.map +1 -0
  378. package/dist/services/setup-token-transport-binding.test.d.ts +2 -0
  379. package/dist/services/setup-token-transport-binding.test.d.ts.map +1 -0
  380. package/dist/services/setup-token-transport-binding.test.js +761 -0
  381. package/dist/services/setup-token-transport-binding.test.js.map +1 -0
  382. package/dist/services/summary-slots.d.ts.map +1 -1
  383. package/dist/services/summary-slots.js +40 -6
  384. package/dist/services/summary-slots.js.map +1 -1
  385. package/dist/services/task-watchdogs.js +2 -2
  386. package/dist/services/task-watchdogs.js.map +1 -1
  387. package/dist/services/tool-access-policy.d.ts +1 -1
  388. package/dist/services/tool-access.d.ts.map +1 -1
  389. package/dist/services/tool-access.js +29 -11
  390. package/dist/services/tool-access.js.map +1 -1
  391. package/dist/services/tool-gateway.d.ts +1 -1
  392. package/dist/services/tool-gateway.d.ts.map +1 -1
  393. package/dist/services/tool-gateway.js +56 -5
  394. package/dist/services/tool-gateway.js.map +1 -1
  395. package/dist/services/workspace-file-resources.d.ts +10 -1
  396. package/dist/services/workspace-file-resources.d.ts.map +1 -1
  397. package/dist/services/workspace-file-resources.js +258 -6
  398. package/dist/services/workspace-file-resources.js.map +1 -1
  399. package/dist/services/workspace-git-operation-scheduler.d.ts +135 -0
  400. package/dist/services/workspace-git-operation-scheduler.d.ts.map +1 -0
  401. package/dist/services/workspace-git-operation-scheduler.js +655 -0
  402. package/dist/services/workspace-git-operation-scheduler.js.map +1 -0
  403. package/dist/services/workspace-git-operation-scheduler.test.d.ts +2 -0
  404. package/dist/services/workspace-git-operation-scheduler.test.d.ts.map +1 -0
  405. package/dist/services/workspace-git-operation-scheduler.test.js +367 -0
  406. package/dist/services/workspace-git-operation-scheduler.test.js.map +1 -0
  407. package/dist/services/workspace-operations.d.ts +60 -0
  408. package/dist/services/workspace-operations.d.ts.map +1 -1
  409. package/dist/services/workspace-operations.js +384 -19
  410. package/dist/services/workspace-operations.js.map +1 -1
  411. package/dist/services/workspace-runtime-exposure-backfill.test.d.ts +2 -0
  412. package/dist/services/workspace-runtime-exposure-backfill.test.d.ts.map +1 -0
  413. package/dist/services/workspace-runtime-exposure-backfill.test.js +149 -0
  414. package/dist/services/workspace-runtime-exposure-backfill.test.js.map +1 -0
  415. package/dist/services/workspace-runtime-exposure.test.d.ts +2 -0
  416. package/dist/services/workspace-runtime-exposure.test.d.ts.map +1 -0
  417. package/dist/services/workspace-runtime-exposure.test.js +517 -0
  418. package/dist/services/workspace-runtime-exposure.test.js.map +1 -0
  419. package/dist/services/workspace-runtime-leases.d.ts +90 -0
  420. package/dist/services/workspace-runtime-leases.d.ts.map +1 -0
  421. package/dist/services/workspace-runtime-leases.js +252 -0
  422. package/dist/services/workspace-runtime-leases.js.map +1 -0
  423. package/dist/services/workspace-runtime-read-model.d.ts +12 -0
  424. package/dist/services/workspace-runtime-read-model.d.ts.map +1 -1
  425. package/dist/services/workspace-runtime-read-model.test.js +3 -0
  426. package/dist/services/workspace-runtime-read-model.test.js.map +1 -1
  427. package/dist/services/workspace-runtime-ready-comment.test.js +1 -0
  428. package/dist/services/workspace-runtime-ready-comment.test.js.map +1 -1
  429. package/dist/services/workspace-runtime.d.ts +160 -2
  430. package/dist/services/workspace-runtime.d.ts.map +1 -1
  431. package/dist/services/workspace-runtime.js +1693 -141
  432. package/dist/services/workspace-runtime.js.map +1 -1
  433. package/dist/shutdown.d.ts +24 -0
  434. package/dist/shutdown.d.ts.map +1 -1
  435. package/dist/shutdown.js +37 -0
  436. package/dist/shutdown.js.map +1 -1
  437. package/dist/shutdown.test.js +113 -1
  438. package/dist/shutdown.test.js.map +1 -1
  439. package/dist/startup-banner.js +1 -1
  440. package/dist/startup-banner.js.map +1 -1
  441. package/dist/url-utils.d.ts +26 -0
  442. package/dist/url-utils.d.ts.map +1 -0
  443. package/dist/url-utils.js +59 -0
  444. package/dist/url-utils.js.map +1 -0
  445. package/dist/url-utils.test.d.ts +2 -0
  446. package/dist/url-utils.test.d.ts.map +1 -0
  447. package/dist/url-utils.test.js +55 -0
  448. package/dist/url-utils.test.js.map +1 -0
  449. package/dist/worktree-config.d.ts.map +1 -1
  450. package/dist/worktree-config.js +3 -17
  451. package/dist/worktree-config.js.map +1 -1
  452. package/package.json +20 -20
  453. package/skills/paperclip/SKILL.md +16 -11
  454. package/skills/paperclip/references/api-reference.md +8 -5
  455. package/skills/paperclip-board/SKILL.md +1 -1
  456. package/ui-dist/assets/{abnfDiagram-VRR7QNED-wvSnvD6-.js → abnfDiagram-N423BO3Z-BvgoV-u5.js} +1 -1
  457. package/ui-dist/assets/{arc-Bx0V1JsN.js → arc-BInnUBUU.js} +1 -1
  458. package/ui-dist/assets/architectureDiagram-T3A2C74G-Dg4MziOm.js +36 -0
  459. package/ui-dist/assets/{blockDiagram-677ZJIJ3-BROcLaUk.js → blockDiagram-VBNYF7ZC-D9kzAkLN.js} +1 -1
  460. package/ui-dist/assets/{c4Diagram-LMCZKHZV-D-V3kgju.js → c4Diagram-5PPSVZJV-BwzsgV_0.js} +1 -1
  461. package/ui-dist/assets/channel-qoSYbCn0.js +1 -0
  462. package/ui-dist/assets/{chunk-32BRIVSS-qillH3H6.js → chunk-2GRJ4B5K-D2EQ9vrl.js} +1 -1
  463. package/ui-dist/assets/{chunk-2Q5K7J3B-D19cVnii.js → chunk-2Q5K7J3B-DOA_8fSd.js} +1 -1
  464. package/ui-dist/assets/{chunk-EX3LRPZG-CLUswrZg.js → chunk-5RXB4S5H-CT5ukRiT.js} +1 -1
  465. package/ui-dist/assets/{chunk-5VM5RSS4-Cwlp02Lx.js → chunk-5VM5RSS4-D2gOlkyB.js} +1 -1
  466. package/ui-dist/assets/{chunk-MOJQB5TN-yKciyq6E.js → chunk-6Q2QTUOP-DUb6aG8c.js} +1 -1
  467. package/ui-dist/assets/{chunk-V7JOEXUC-DjJ75g0_.js → chunk-GF5L2VYU-BPabG3Cx.js} +1 -1
  468. package/ui-dist/assets/{chunk-JWPE2WC7-BjepyzEb.js → chunk-JWPE2WC7-scNfhlZz.js} +1 -1
  469. package/ui-dist/assets/{chunk-VR4S4FIN-CiEmy8-R.js → chunk-KBJHAD2P-BcRAzRpq.js} +1 -1
  470. package/ui-dist/assets/{chunk-RYQCIY6F-ed7mQM9j.js → chunk-RYQCIY6F-BiZ0-EGh.js} +1 -1
  471. package/ui-dist/assets/{chunk-XXDRQBXY-BiI83zoO.js → chunk-XXDRQBXY-lJULxFIE.js} +1 -1
  472. package/ui-dist/assets/classDiagram-JCYQIIEL-B24u1REs.js +1 -0
  473. package/ui-dist/assets/classDiagram-v2-OCEON4UE-B24u1REs.js +1 -0
  474. package/ui-dist/assets/{cose-bilkent-JH36ORCC-DIdQf5ta.js → cose-bilkent-JH36ORCC-Do8pe8JB.js} +1 -1
  475. package/ui-dist/assets/{cynefin-VYW2F7L2-B2m9xxh8.js → cynefin-VYW2F7L2-B34jJY1x.js} +1 -1
  476. package/ui-dist/assets/{cynefinDiagram-TSTJHNR4-CGVdB0kX.js → cynefinDiagram-MW4NZA55-DQ8QK8JK.js} +1 -1
  477. package/ui-dist/assets/cytoscape.esm-DShqj__B.js +321 -0
  478. package/ui-dist/assets/{dagre-VKFMJZFB-CskKiZwJ.js → dagre-VZM6K2ZE-B7lSKgxH.js} +1 -1
  479. package/ui-dist/assets/{diagram-OA4YK3LP-D2cn5eIN.js → diagram-7IWD3JNH-B3hLAFGE.js} +1 -1
  480. package/ui-dist/assets/{diagram-FQU43EPY-oa5Zu0XG.js → diagram-B4RE2ZJO-CH9Ro9e0.js} +1 -1
  481. package/ui-dist/assets/{diagram-NH7WQ7WH-fymti5Js.js → diagram-LBJQPF4R-DdPVMii7.js} +1 -1
  482. package/ui-dist/assets/{diagram-G47NLZAW-BWF2ZCsw.js → diagram-Q27KOJAE-f5VXswjc.js} +1 -1
  483. package/ui-dist/assets/diagram-UB23O5K3-DGqxixsR.js +41 -0
  484. package/ui-dist/assets/{ebnfDiagram-CCIWWBDH-CJ1d3D5q.js → ebnfDiagram-BXEA7PRR-C-1klg_w.js} +1 -1
  485. package/ui-dist/assets/{erDiagram-Q63AITRT-BJCPa7kK.js → erDiagram-JOGREHBK-Dt4qK4gv.js} +1 -1
  486. package/ui-dist/assets/{flowDiagram-23GEKE2U-BWjWuz6r.js → flowDiagram-UKHOOZJN-9ZlrTuET.js} +5 -5
  487. package/ui-dist/assets/{ganttDiagram-NO4QXBWP-Dv1KKjoM.js → ganttDiagram-PKOTCBZU-CE3lyvFq.js} +1 -1
  488. package/ui-dist/assets/{gitGraphDiagram-IHSO6WYX-9X7vgL0A.js → gitGraphDiagram-DS77QQ5N-HH2fSgad.js} +1 -1
  489. package/ui-dist/assets/{index-CpaK48ok.js → index-B61ORkIb.js} +1 -1
  490. package/ui-dist/assets/index-BHiUiToL.css +1 -0
  491. package/ui-dist/assets/{index-D2YCrA-D.js → index-BY5ITKmG.js} +1 -1
  492. package/ui-dist/assets/{index-9JMDmXc5.js → index-BbAhzCq8.js} +1 -1
  493. package/ui-dist/assets/{index-C5qHatpp.js → index-Bc9g6q63.js} +1 -1
  494. package/ui-dist/assets/{index-DuI6e5mP.js → index-Bf7tyGxE.js} +1 -1
  495. package/ui-dist/assets/{index-BahRsj0s.js → index-BvFx-4KW.js} +1 -1
  496. package/ui-dist/assets/{index-DKlsEDV7.js → index-C1mpWCcn.js} +1 -1
  497. package/ui-dist/assets/{index-DIXaXZrW.js → index-CHXG4GPL.js} +1 -1
  498. package/ui-dist/assets/{index-DcfO6NoY.js → index-CLu3Tfpv.js} +1 -1
  499. package/ui-dist/assets/{index-Cfz6P0Qq.js → index-CQhaM6qd.js} +1 -1
  500. package/ui-dist/assets/{index-D9d3sl6K.js → index-CVTL3aAL.js} +1 -1
  501. package/ui-dist/assets/{index-BexA3NAl.js → index-CcRTJ93J.js} +1 -1
  502. package/ui-dist/assets/{index-CnBrDwGT.js → index-Cm6iEYPt.js} +247 -223
  503. package/ui-dist/assets/{index-ByGcu-MB.js → index-DQIrrn2L.js} +1 -1
  504. package/ui-dist/assets/{index-DKEzHO7c.js → index-DU4U1fuv.js} +1 -1
  505. package/ui-dist/assets/{index-pqJO8FsV.js → index-DV2FOgyD.js} +1 -1
  506. package/ui-dist/assets/{index-tDuieU3u.js → index-DceuCrr8.js} +1 -1
  507. package/ui-dist/assets/{index-CjN2lm_w.js → index-DlVBBF8o.js} +1 -1
  508. package/ui-dist/assets/{index-0yJtUsFh.js → index-DsdVneqe.js} +1 -1
  509. package/ui-dist/assets/{index-C2a4_IKU.js → index-DvFUXI-Q.js} +1 -1
  510. package/ui-dist/assets/{index-BTMiDMYv.js → index-FHzrJ7iQ.js} +1 -1
  511. package/ui-dist/assets/{index-cUQXJtLK.js → index-diFs0CNC.js} +1 -1
  512. package/ui-dist/assets/{index-C96LHV1A.js → index-hkanj4t6.js} +1 -1
  513. package/ui-dist/assets/{index-rDI1Q4r9.js → index-yRJnh4vx.js} +1 -1
  514. package/ui-dist/assets/{infoDiagram-FWYZ7A6U-CHIhutSb.js → infoDiagram-6WML65LV-2jai2fw-.js} +1 -1
  515. package/ui-dist/assets/{ishikawaDiagram-FXEZZL3T-ChaD7-5_.js → ishikawaDiagram-WSZJBQD7-CUmGhmEb.js} +1 -1
  516. package/ui-dist/assets/{journeyDiagram-5HDEW3XC-M5T2sMgn.js → journeyDiagram-NVQOT4AX-CGQuPDO1.js} +1 -1
  517. package/ui-dist/assets/{kanban-definition-HUTT4EX6-B8H6R_Wc.js → kanban-definition-27J2QSJJ-zgEPcbHl.js} +1 -1
  518. package/ui-dist/assets/{linear-cpPKlVCq.js → linear-BVf5VtLS.js} +1 -1
  519. package/ui-dist/assets/mermaid.core-D2TFkGfI.js +308 -0
  520. package/ui-dist/assets/{mindmap-definition-LN4V7U3C-Bf6POzvm.js → mindmap-definition-FAOFIHXS-xoAQ0Lb_.js} +1 -1
  521. package/ui-dist/assets/{pegDiagram-2B236MQR-BJbGktAV.js → pegDiagram-VL7TDLO6-CBR1PWLr.js} +1 -1
  522. package/ui-dist/assets/{pieDiagram-ENE6RG2P-BmSN9Bio.js → pieDiagram-7S7Q4E2Y-BMPnzK_e.js} +1 -1
  523. package/ui-dist/assets/{quadrantDiagram-ABIIQ3AL-JUpqQCx_.js → quadrantDiagram-CIZ2JOQS-CPtVUPkR.js} +1 -1
  524. package/ui-dist/assets/{railroadDiagram-RFXS5EU6-B8k8T4-2.js → railroadDiagram-AXF67PYL-DC0lsZV1.js} +1 -1
  525. package/ui-dist/assets/{requirementDiagram-TGXJPOKE-b32heq7Q.js → requirementDiagram-LRYGKXZP-BF6k05SB.js} +1 -1
  526. package/ui-dist/assets/{sankeyDiagram-HTMAVEWB-DC3s-xgf.js → sankeyDiagram-W5VNT64P-CRN0ibiP.js} +1 -1
  527. package/ui-dist/assets/{sequenceDiagram-DBY2YBRQ-BsCFyv_E.js → sequenceDiagram-SI44F4Z6-CQtTiPWm.js} +1 -1
  528. package/ui-dist/assets/{sizeCapture-X5ZJPWSS-S7xeCud3.js → sizeCapture-X5ZJPWSS-BdjuGZd1.js} +1 -1
  529. package/ui-dist/assets/{stateDiagram-2N3HPSRC-58O3ofoS.js → stateDiagram-OKZ733FA-B99onxyh.js} +1 -1
  530. package/ui-dist/assets/stateDiagram-v2-UEYNNEHI-bzFlYBZ1.js +1 -0
  531. package/ui-dist/assets/{swimlanes-5IMT3BWC-DFCVG0iF.js → swimlanes-SLNWSIFB-BIByPbrL.js} +2 -2
  532. package/ui-dist/assets/swimlanesDiagram-ULZ7WXOC-DgdkBnuK.js +8 -0
  533. package/ui-dist/assets/{timeline-definition-FHXFAJF6-C0c0SkQu.js → timeline-definition-Z64GVDOM-I1NgOcn9.js} +1 -1
  534. package/ui-dist/assets/{vennDiagram-L72KCM5P-B1cEx8GQ.js → vennDiagram-T6HMQDX7-D6t2Njcl.js} +1 -1
  535. package/ui-dist/assets/{wardleyDiagram-EHGQE667-BQhDkOx9.js → wardleyDiagram-T6FBY63Y-BiYKm9gh.js} +1 -1
  536. package/ui-dist/assets/{xychartDiagram-FW5EYKEG-YSQp1N0i.js → xychartDiagram-ELKLHX3M-BhwXKWIa.js} +4 -4
  537. package/ui-dist/index.html +3 -3
  538. package/ui-dist/sw.js +8 -3
  539. package/ui-dist/assets/architectureDiagram-ZJ3FMSHR-BcJt8Ia1.js +0 -36
  540. package/ui-dist/assets/channel-ZRpjyNiZ.js +0 -1
  541. package/ui-dist/assets/classDiagram-OUVF2IWQ-CCzzzCgk.js +0 -1
  542. package/ui-dist/assets/classDiagram-v2-EOCWNBFH-CCzzzCgk.js +0 -1
  543. package/ui-dist/assets/cytoscape.esm-D8joxN9f.js +0 -321
  544. package/ui-dist/assets/diagram-WEI45ONY-M3WOC2tK.js +0 -41
  545. package/ui-dist/assets/index-BqfqfoQX.css +0 -1
  546. package/ui-dist/assets/mermaid.core-MCazc_od.js +0 -308
  547. package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-_VY5dpDG.js +0 -1
  548. package/ui-dist/assets/swimlanesDiagram-G3AALYLV-BJhUjfns.js +0 -8
@@ -1,13 +1,15 @@
1
1
  import { isDeepStrictEqual } from "node:util";
2
2
  import { and, asc, desc, eq, inArray, isNotNull, isNull, ne } from "drizzle-orm";
3
3
  import { agents, companies, documents, heartbeatRuns, issueComments, issueDocuments, issueThreadInteractions, issues, toolActionRequests, } from "@paperclipai/db";
4
- import { trackInteractionResolved } from "@paperclipai/shared/telemetry";
5
- import { acceptIssueThreadInteractionSchema, askUserQuestionsPayloadSchema, askUserQuestionsResultSchema, cancelIssueThreadInteractionSchema, createIssueThreadInteractionSchema, rejectIssueThreadInteractionSchema, requestCheckboxConfirmationPayloadSchema, requestCheckboxConfirmationResultSchema, requestConfirmationPayloadSchema, requestConfirmationResultSchema, requestItemVerdictsPayloadSchema, requestItemVerdictsResultSchema, suggestTasksPayloadSchema, suggestTasksResultSchema, submitIssueThreadInteractionVerdictsSchema, withdrawIssueThreadInteractionSchema, } from "@paperclipai/shared";
4
+ import { trackInteractionCreated, trackInteractionResolved } from "@paperclipai/shared/telemetry";
5
+ import { acceptIssueThreadInteractionSchema, askUserQuestionsPayloadSchema, askUserQuestionsResultSchema, cancelIssueThreadInteractionSchema, createIssueThreadInteractionSchema, legacyIssueThreadInteractionResolverPolicyAlias, normalizeIssueThreadInteractionResolverPolicy, rejectIssueThreadInteractionSchema, requestCheckboxConfirmationPayloadSchema, requestCheckboxConfirmationResultSchema, requestConfirmationPayloadSchema, requestConfirmationResultSchema, requestItemVerdictsPayloadSchema, requestItemVerdictsResultSchema, suggestTasksPayloadSchema, suggestTasksResultSchema, submitIssueThreadInteractionVerdictsSchema, withdrawIssueThreadInteractionSchema, } from "@paperclipai/shared";
6
6
  import { conflict, forbidden, notFound, unprocessable } from "../errors.js";
7
7
  import { getTelemetryClient } from "../telemetry.js";
8
8
  import { logActivity } from "./activity-log.js";
9
9
  import { evaluateAgentInvokabilityFromDb } from "./agent-invokability.js";
10
+ import { assertIssueReviewVerdictActorAllowed, isIssueReviewVerdictInteraction, } from "./issue-review-policy.js";
10
11
  import { issueService, runWorkspaceIsFinalized } from "./issues.js";
12
+ import { assertIssueThreadInteractionResolverAudience, canonicalizeStoredResolverPolicy, issueThreadInteractionResolutionError, } from "./issue-thread-interaction-resolution.js";
11
13
  import { createPullRequestMergeStateResolver, extractGitHubPullRequestReferences, setBoundedPullRequestCacheEntry, } from "./github-pull-request-merge.js";
12
14
  export { extractGitHubPullRequestReferences } from "./github-pull-request-merge.js";
13
15
  const GITHUB_PULL_REQUEST_URL_PATTERN = /https:\/\/(?:www\.)?github\.com\/([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)\/pull\/([1-9][0-9]*)/gi;
@@ -106,54 +108,91 @@ export function getMergeConfirmationPullRequestReferences(row) {
106
108
  return extractGitHubPullRequestReferences(trustedTextValues);
107
109
  }
108
110
  const ISSUE_THREAD_INTERACTION_IDEMPOTENCY_CONSTRAINT = "issue_thread_interactions_company_issue_idempotency_uq";
109
- const DEFAULT_RESOLVER_POLICY_BY_KIND = {
110
- suggest_tasks: "board_only",
111
- ask_user_questions: "board_or_agents",
112
- request_confirmation: "board_only",
113
- request_checkbox_confirmation: "board_only",
114
- request_item_verdicts: "board_only",
111
+ export const DEFAULT_RESOLVER_POLICY_BY_KIND = {
112
+ suggest_tasks: "anyone",
113
+ ask_user_questions: "anyone",
114
+ request_confirmation: "anyone",
115
+ request_checkbox_confirmation: "anyone",
116
+ request_item_verdicts: "anyone",
117
+ };
118
+ const RESOLVER_POLICY_RESTRICTION_RANK = {
119
+ anyone: 0,
120
+ not_creator: 1,
121
+ human_only: 2,
115
122
  };
116
123
  export function resolveInteractionPolicy(args) {
117
124
  const kindGovernance = args.governance[args.kind];
118
- const requestedResolverPolicy = args.requested
125
+ const requestedPolicyInput = args.requested
119
126
  ?? kindGovernance?.defaultPolicy
120
127
  ?? DEFAULT_RESOLVER_POLICY_BY_KIND[args.kind];
121
- const effectiveResolverPolicy = args.hasToolAction || kindGovernance?.cap === "board_only"
122
- ? "board_only"
123
- : requestedResolverPolicy;
124
- return { requestedResolverPolicy, effectiveResolverPolicy };
125
- }
126
- function assertAgentResolutionAllowed(current, actor) {
127
- if (!actor.agentId)
128
- return;
129
- if (!actor.runId)
130
- throw forbidden("Agent run id required to resolve an issue-thread interaction");
131
- if (current.kind === "request_confirmation"
132
- && current.payload
133
- && typeof current.payload === "object"
134
- && "toolAction" in current.payload
135
- && current.payload.toolAction !== undefined) {
136
- throw forbidden("Tool-action confirmations are always board-only");
137
- }
138
- if (actor.reviewVerdictAuthorized && isRequestConfirmationLikeKind(current.kind)) {
139
- assertAgentInteractionActorAllowed(current, actor);
140
- return;
128
+ const requestedResolverPolicy = normalizeIssueThreadInteractionResolverPolicy(requestedPolicyInput);
129
+ const resolverPolicyProvenance = args.requested === undefined ? "inherited" : "explicit";
130
+ let effectiveResolverPolicy = requestedResolverPolicy;
131
+ let effectiveResolverPolicySource = "requested";
132
+ if (args.hasToolAction) {
133
+ effectiveResolverPolicy = "human_only";
134
+ effectiveResolverPolicySource = "governed_action";
141
135
  }
142
- if (current.effectiveResolverPolicy !== "board_or_agents") {
143
- throw forbidden("This issue-thread interaction is board-only");
136
+ else if (kindGovernance?.cap) {
137
+ const cap = normalizeIssueThreadInteractionResolverPolicy(kindGovernance.cap);
138
+ if (RESOLVER_POLICY_RESTRICTION_RANK[cap] > RESOLVER_POLICY_RESTRICTION_RANK[effectiveResolverPolicy]) {
139
+ effectiveResolverPolicy = cap;
140
+ effectiveResolverPolicySource = "company_cap";
141
+ }
144
142
  }
145
- assertAgentInteractionActorAllowed(current, actor);
143
+ return {
144
+ requestedResolverPolicy,
145
+ effectiveResolverPolicy,
146
+ resolverPolicyProvenance,
147
+ effectiveResolverPolicySource,
148
+ };
146
149
  }
147
- function assertAgentInteractionActorAllowed(current, actor) {
148
- if (current.addresseeAgentId && current.addresseeAgentId !== actor.agentId) {
149
- throw forbidden("Only the addressed agent or a board user may resolve this issue-thread interaction");
150
+ function resolverActor(actor) {
151
+ if (actor.systemId)
152
+ return { type: "system", systemId: actor.systemId };
153
+ if (actor.agentId) {
154
+ return { type: "agent", agentId: actor.agentId, runId: actor.runId };
150
155
  }
151
- if (current.createdByAgentId === actor.agentId) {
152
- throw forbidden("Agents cannot resolve interactions they created");
156
+ if (actor.userId)
157
+ return { type: "user", userId: actor.userId };
158
+ // Missing principals must fail closed. Internal maintenance paths that are
159
+ // intentionally system-owned provide an explicit systemId.
160
+ return { type: "agent", agentId: null, runId: null };
161
+ }
162
+ function assertInteractionResolutionAllowed(current, actor) {
163
+ return assertIssueThreadInteractionResolverAudience({
164
+ actor: resolverActor(actor),
165
+ interaction: current,
166
+ additionalRestriction: actor.resolverPolicyRestriction,
167
+ governedAction: current.kind === "request_confirmation"
168
+ && current.payload !== null
169
+ && typeof current.payload === "object"
170
+ && "toolAction" in current.payload
171
+ && current.payload.toolAction !== undefined,
172
+ });
173
+ }
174
+ async function assertRequestConfirmationResolutionAllowedUnderLock(tx, issue, interaction, actor) {
175
+ if (isTerminalIssueStatus(issue.status)) {
176
+ throw conflict("Interaction is no longer actionable because the issue is closed");
153
177
  }
154
- if (current.sourceRunId && current.sourceRunId === actor.runId) {
155
- throw forbidden("Agents cannot resolve interactions created by the same run");
178
+ const isReviewVerdict = issue.status === "in_review"
179
+ && isRequestConfirmationLikeKind(interaction.kind)
180
+ && await isIssueReviewVerdictInteraction(tx, { issue, interaction });
181
+ assertInteractionResolutionAllowed(interaction, actor);
182
+ if (!isReviewVerdict)
183
+ return;
184
+ const verdictActor = actor.agentId
185
+ ? { type: "agent", id: actor.agentId }
186
+ : actor.userId
187
+ ? { type: "user", id: actor.userId }
188
+ : null;
189
+ if (!verdictActor) {
190
+ throw forbidden("A review verdict requires an authenticated agent or user");
156
191
  }
192
+ await assertIssueReviewVerdictActorAllowed(tx, {
193
+ issue,
194
+ actor: verdictActor,
195
+ });
157
196
  }
158
197
  const REQUEST_CONFIRMATION_INTERACTION_KINDS = [
159
198
  "request_confirmation",
@@ -218,15 +257,30 @@ function parseStoredInteractionResult(schema, raw, row) {
218
257
  return null;
219
258
  }
220
259
  function hydrateInteraction(row) {
260
+ const storedRequestedResolverPolicy = row.requestedResolverPolicy;
261
+ const storedEffectiveResolverPolicy = row.effectiveResolverPolicy;
262
+ const resolverPolicyProvenance = row.resolverPolicyProvenance
263
+ ?? (storedRequestedResolverPolicy === "board_only" || storedRequestedResolverPolicy === "board_or_agents"
264
+ ? "legacy_inherited_restriction"
265
+ : "inherited");
266
+ const canonicalizeStoredPolicy = (policy) => canonicalizeStoredResolverPolicy(policy, resolverPolicyProvenance);
267
+ const requestedResolverPolicy = canonicalizeStoredPolicy(storedRequestedResolverPolicy);
268
+ const effectiveResolverPolicy = canonicalizeStoredPolicy(storedEffectiveResolverPolicy);
221
269
  const base = {
222
270
  ...row,
223
271
  idempotencyKey: row.idempotencyKey ?? null,
224
272
  addresseeAgentId: row.addresseeAgentId ?? null,
225
273
  status: row.status,
226
274
  continuationPolicy: row.continuationPolicy,
227
- resolverPolicy: row.requestedResolverPolicy,
228
- requestedResolverPolicy: row.requestedResolverPolicy,
229
- effectiveResolverPolicy: row.effectiveResolverPolicy,
275
+ resolverPolicy: requestedResolverPolicy,
276
+ requestedResolverPolicy,
277
+ effectiveResolverPolicy,
278
+ resolverPolicyProvenance,
279
+ effectiveResolverPolicySource: row.effectiveResolverPolicySource ?? "requested",
280
+ legacyResolverPolicyAliases: {
281
+ requested: legacyIssueThreadInteractionResolverPolicyAlias(requestedResolverPolicy),
282
+ effective: legacyIssueThreadInteractionResolverPolicyAlias(effectiveResolverPolicy),
283
+ },
230
284
  };
231
285
  switch (row.kind) {
232
286
  case "suggest_tasks":
@@ -277,6 +331,29 @@ async function touchIssue(db, issueId) {
277
331
  function isTerminalIssueStatus(status) {
278
332
  return status === "done" || status === "cancelled";
279
333
  }
334
+ function interactionNotFoundError() {
335
+ return notFound("Interaction not found", { code: "interaction_not_found" });
336
+ }
337
+ function interactionIssueClosedError() {
338
+ return issueThreadInteractionResolutionError(409, "interaction_issue_closed", "Interaction is no longer actionable because the issue is closed");
339
+ }
340
+ function interactionAlreadyResolvedError() {
341
+ return issueThreadInteractionResolutionError(409, "interaction_already_resolved", "Interaction has already been resolved");
342
+ }
343
+ function interactionTerminalError(row) {
344
+ const result = row.result && typeof row.result === "object" && !Array.isArray(row.result)
345
+ ? row.result
346
+ : null;
347
+ if (result?.outcome === "stale_target") {
348
+ return issueThreadInteractionResolutionError(409, "interaction_stale_target", "Interaction target is stale");
349
+ }
350
+ if (result?.outcome === "superseded_by_comment" || result?.outcome === "superseded_by_newer_request") {
351
+ return issueThreadInteractionResolutionError(409, "interaction_superseded", "Interaction has been superseded");
352
+ }
353
+ if (result?.outcome === "issue_closed")
354
+ return interactionIssueClosedError();
355
+ return issueThreadInteractionResolutionError(409, "interaction_already_resolved", "Interaction has already been resolved");
356
+ }
280
357
  function shouldReturnAcceptedConfirmationToCreatorAgent(args) {
281
358
  if (!isRequestConfirmationLikeKind(args.current.kind))
282
359
  return false;
@@ -383,6 +460,20 @@ function buildSupersededByNewerRequestResult(replacementInteractionId) {
383
460
  supersededByInteractionId: replacementInteractionId,
384
461
  };
385
462
  }
463
+ // An agent that posts a fresh ask_user_questions while its own earlier ones on
464
+ // the same issue are still pending has replaced them — the newer card carries
465
+ // the real ask, so the stale siblings auto-expire (PAP-437). Mirrors the
466
+ // `superseded_by_comment` shape (ask_user_questions results key expiry off
467
+ // `expirationReason`, not `outcome`) so the UI can hide them cleanly.
468
+ function buildSupersededByNewerInteractionResult(replacementInteractionId) {
469
+ return {
470
+ version: 1,
471
+ answers: [],
472
+ expirationReason: "superseded_by_newer_interaction",
473
+ supersededByInteractionId: replacementInteractionId,
474
+ summaryMarkdown: null,
475
+ };
476
+ }
386
477
  function buildAdministrativeOutcomeResult(row, outcome, reason = null) {
387
478
  if (row.kind === "ask_user_questions") {
388
479
  return { version: 1, outcome, reason, answers: [], summaryMarkdown: null };
@@ -554,12 +645,24 @@ async function emitInteractionResolvedTelemetry(db, interaction, args) {
554
645
  ...buildInteractionResolvedCounts(interaction, {
555
646
  createdTaskCount: args?.createdTaskCount,
556
647
  }),
648
+ legacyInheritedRestriction: interaction.resolverPolicyProvenance === "legacy_inherited_restriction",
557
649
  });
558
650
  }
559
651
  catch (error) {
560
652
  console.error("[paperclip] Failed to emit interaction.resolved telemetry", error);
561
653
  }
562
654
  }
655
+ function emitInteractionCreatedTelemetry(args) {
656
+ const telemetryClient = getTelemetryClient();
657
+ if (!telemetryClient)
658
+ return;
659
+ try {
660
+ trackInteractionCreated(telemetryClient, args);
661
+ }
662
+ catch (error) {
663
+ console.error("[paperclip] Failed to emit interaction.created telemetry", error);
664
+ }
665
+ }
563
666
  async function emitResolvedInteractionsTelemetry(db, interactions) {
564
667
  if (interactions.length === 0 || !getTelemetryClient())
565
668
  return;
@@ -606,7 +709,7 @@ function buildTaskCreationOrder(tasks) {
606
709
  }
607
710
  return ordered;
608
711
  }
609
- function resolveSelectedSuggestedTasks(args) {
712
+ export function resolveSelectedSuggestedTasks(args) {
610
713
  const taskByClientKey = new Map(args.interaction.payload.tasks.map((task) => [task.clientKey, task]));
611
714
  const selectedClientKeys = args.selectedClientKeys ?? args.interaction.payload.tasks.map((task) => task.clientKey);
612
715
  const selectedClientKeySet = new Set();
@@ -660,9 +763,6 @@ function resolveSelectedCheckboxConfirmationOptions(args) {
660
763
  .map((option) => option.id);
661
764
  }
662
765
  function resolveRequestItemVerdictSubmissions(args) {
663
- if (!args.actor.userId) {
664
- throw unprocessable("request_item_verdicts submissions require a user actor");
665
- }
666
766
  const existingItems = args.interaction.result?.items ?? [];
667
767
  const existingById = new Map(existingItems.map((item) => [item.id, item]));
668
768
  const payloadItemIds = new Set(args.interaction.payload.items.map((item) => item.id));
@@ -691,7 +791,9 @@ function resolveRequestItemVerdictSubmissions(args) {
691
791
  id: submitted.id,
692
792
  verdict: submitted.verdict,
693
793
  ...(reason ? { reason } : {}),
694
- resolvedByUserId: args.actor.userId,
794
+ ...(args.actor.userId ? { resolvedByUserId: args.actor.userId } : {}),
795
+ ...(args.actor.agentId ? { resolvedByAgentId: args.actor.agentId } : {}),
796
+ ...(args.actor.runId ? { resolvedByRunId: args.actor.runId } : {}),
695
797
  resolvedAt: args.now,
696
798
  });
697
799
  newlyResolvedItemIds.push(submitted.id);
@@ -859,7 +961,7 @@ async function expireStaleRequestConfirmationTarget(db, args) {
859
961
  .where(and(eq(issueThreadInteractions.id, args.row.id), eq(issueThreadInteractions.status, "pending")))
860
962
  .returning();
861
963
  if (!updated) {
862
- throw conflict("Interaction has already been resolved");
964
+ throw issueThreadInteractionResolutionError(409, "interaction_already_resolved", "Interaction has already been resolved");
863
965
  }
864
966
  await touchIssue(db, args.row.issueId);
865
967
  const expired = hydrateInteraction(updated);
@@ -906,7 +1008,7 @@ export function issueThreadInteractionService(db, opts = {}) {
906
1008
  .where(eq(issueThreadInteractions.id, interactionId))
907
1009
  .then((rows) => rows[0] ?? null);
908
1010
  if (!current || current.companyId !== issue.companyId || current.issueId !== issue.id) {
909
- throw notFound("Interaction not found");
1011
+ throw interactionNotFoundError();
910
1012
  }
911
1013
  return hydrateInteraction(current);
912
1014
  }
@@ -938,32 +1040,75 @@ export function issueThreadInteractionService(db, opts = {}) {
938
1040
  .where(eq(issueThreadInteractions.id, args.interactionId))
939
1041
  .then((rows) => rows[0] ?? null);
940
1042
  if (!current)
941
- throw notFound("Interaction not found");
1043
+ throw interactionNotFoundError();
942
1044
  if (current.companyId !== args.issue.companyId || current.issueId !== args.issue.id) {
943
- throw notFound("Interaction not found");
1045
+ throw interactionNotFoundError();
1046
+ }
1047
+ if (args.issue.status && isTerminalIssueStatus(args.issue.status)) {
1048
+ throw interactionIssueClosedError();
944
1049
  }
945
1050
  if (current.status !== "pending") {
946
- throw conflict("Interaction has already been resolved");
1051
+ throw interactionTerminalError(current);
947
1052
  }
948
1053
  return current;
949
1054
  }
1055
+ function assertIssueOpenForInteractionResolution(issue) {
1056
+ if (issue.status && isTerminalIssueStatus(issue.status)) {
1057
+ throw interactionIssueClosedError();
1058
+ }
1059
+ }
950
1060
  async function acceptRequestConfirmation(args) {
951
1061
  const expired = await expireStaleRequestConfirmationTarget(db, {
952
1062
  row: args.current,
953
1063
  actor: args.actor,
954
1064
  });
955
- if (expired) {
956
- return { interaction: expired, continuationIssue: null };
957
- }
958
- const interaction = hydrateInteraction(args.current);
959
- const selectedOptionIds = interaction.kind === "request_checkbox_confirmation"
960
- ? resolveSelectedCheckboxConfirmationOptions({
961
- interaction,
962
- selectedOptionIds: args.input.selectedOptionIds,
963
- })
964
- : undefined;
1065
+ if (expired)
1066
+ throw interactionTerminalError({ status: expired.status, result: expired.result });
965
1067
  const now = new Date();
966
1068
  const result = await db.transaction(async (tx) => {
1069
+ // Lock the issue before claiming the interaction. Policy mutations and
1070
+ // review transitions use the same issue-row lock, so the authoritative
1071
+ // review policy and requester are stable through the verdict write.
1072
+ const issueContext = await tx
1073
+ .select({
1074
+ id: issues.id,
1075
+ companyId: issues.companyId,
1076
+ status: issues.status,
1077
+ assigneeAgentId: issues.assigneeAgentId,
1078
+ assigneeUserId: issues.assigneeUserId,
1079
+ reviewPolicy: issues.reviewPolicy,
1080
+ createdByAgentId: issues.createdByAgentId,
1081
+ createdByUserId: issues.createdByUserId,
1082
+ })
1083
+ .from(issues)
1084
+ .where(eq(issues.id, args.issue.id))
1085
+ .for("update")
1086
+ .then((rows) => rows[0] ?? null);
1087
+ if (!issueContext || issueContext.companyId !== args.issue.companyId) {
1088
+ throw notFound("Issue not found");
1089
+ }
1090
+ const lockedCurrent = await tx
1091
+ .select()
1092
+ .from(issueThreadInteractions)
1093
+ .where(eq(issueThreadInteractions.id, args.current.id))
1094
+ .for("update")
1095
+ .then((rows) => rows[0] ?? null);
1096
+ if (!lockedCurrent
1097
+ || lockedCurrent.companyId !== args.issue.companyId
1098
+ || lockedCurrent.issueId !== args.issue.id) {
1099
+ throw notFound("Interaction not found");
1100
+ }
1101
+ if (lockedCurrent.status !== "pending") {
1102
+ throw issueThreadInteractionResolutionError(409, "interaction_already_resolved", "Interaction has already been resolved");
1103
+ }
1104
+ await assertRequestConfirmationResolutionAllowedUnderLock(tx, issueContext, lockedCurrent, args.actor);
1105
+ const interaction = hydrateInteraction(lockedCurrent);
1106
+ const selectedOptionIds = interaction.kind === "request_checkbox_confirmation"
1107
+ ? resolveSelectedCheckboxConfirmationOptions({
1108
+ interaction,
1109
+ selectedOptionIds: args.input.selectedOptionIds,
1110
+ })
1111
+ : undefined;
967
1112
  const [updated] = await tx
968
1113
  .update(issueThreadInteractions)
969
1114
  .set({
@@ -979,35 +1124,21 @@ export function issueThreadInteractionService(db, opts = {}) {
979
1124
  resolvedAt: now,
980
1125
  updatedAt: now,
981
1126
  })
982
- .where(and(eq(issueThreadInteractions.id, args.current.id), eq(issueThreadInteractions.status, "pending")))
1127
+ .where(and(eq(issueThreadInteractions.id, lockedCurrent.id), eq(issueThreadInteractions.status, "pending")))
983
1128
  .returning();
984
1129
  if (!updated) {
985
- throw conflict("Interaction has already been resolved");
986
- }
987
- const issueContext = await tx
988
- .select({
989
- id: issues.id,
990
- companyId: issues.companyId,
991
- status: issues.status,
992
- assigneeAgentId: issues.assigneeAgentId,
993
- assigneeUserId: issues.assigneeUserId,
994
- })
995
- .from(issues)
996
- .where(eq(issues.id, args.issue.id))
997
- .then((rows) => rows[0] ?? null);
998
- if (!issueContext || issueContext.companyId !== args.issue.companyId) {
999
- throw notFound("Issue not found");
1130
+ throw issueThreadInteractionResolutionError(409, "interaction_already_resolved", "Interaction has already been resolved");
1000
1131
  }
1001
1132
  let continuationIssue = null;
1002
1133
  if (shouldReturnAcceptedConfirmationToCreatorAgent({
1003
1134
  issue: issueContext,
1004
- current: args.current,
1135
+ current: lockedCurrent,
1005
1136
  actor: args.actor,
1006
1137
  })) {
1007
1138
  const returnStatus = issueContext.status === "blocked" ? "blocked" : "todo";
1008
1139
  const returnedIssue = await issueService(db).update(args.issue.id, {
1009
1140
  status: returnStatus,
1010
- assigneeAgentId: args.current.createdByAgentId,
1141
+ assigneeAgentId: lockedCurrent.createdByAgentId,
1011
1142
  assigneeUserId: null,
1012
1143
  actorAgentId: args.actor.agentId ?? null,
1013
1144
  actorUserId: args.actor.userId ?? null,
@@ -1035,12 +1166,12 @@ export function issueThreadInteractionService(db, opts = {}) {
1035
1166
  entityType: "issue",
1036
1167
  entityId: args.issue.id,
1037
1168
  details: {
1038
- interactionId: args.current.id,
1039
- interactionKind: args.current.kind,
1169
+ interactionId: lockedCurrent.id,
1170
+ interactionKind: lockedCurrent.kind,
1040
1171
  interactionStatus: "accepted",
1041
1172
  resolutionActorKind: "system",
1042
- requestedResolverPolicy: args.current.requestedResolverPolicy,
1043
- effectiveResolverPolicy: args.current.effectiveResolverPolicy,
1173
+ requestedResolverPolicy: lockedCurrent.requestedResolverPolicy,
1174
+ effectiveResolverPolicy: lockedCurrent.effectiveResolverPolicy,
1044
1175
  ...(args.actor.resolutionDetails ?? {}),
1045
1176
  },
1046
1177
  });
@@ -1058,36 +1189,71 @@ export function issueThreadInteractionService(db, opts = {}) {
1058
1189
  row: args.current,
1059
1190
  actor: args.actor,
1060
1191
  });
1061
- if (expired) {
1062
- return expired;
1063
- }
1192
+ if (expired)
1193
+ throw interactionTerminalError({ status: expired.status, result: expired.result });
1064
1194
  const interaction = hydrateInteraction(args.current);
1065
1195
  const reason = args.input.reason?.trim() ?? "";
1066
1196
  if (interaction.payload.rejectRequiresReason === true && reason.length === 0) {
1067
1197
  throw unprocessable("A decline reason is required for this confirmation");
1068
1198
  }
1069
1199
  const now = new Date();
1070
- const [updated] = await db
1071
- .update(issueThreadInteractions)
1072
- .set({
1073
- status: "rejected",
1074
- result: {
1075
- version: 1,
1076
- outcome: "rejected",
1077
- reason: reason || null,
1078
- },
1079
- resolvedByAgentId: args.actor.agentId ?? null,
1080
- resolvedByRunId: args.actor.runId ?? null,
1081
- resolvedByUserId: args.actor.userId ?? null,
1082
- resolvedAt: now,
1083
- updatedAt: now,
1084
- })
1085
- .where(and(eq(issueThreadInteractions.id, args.current.id), eq(issueThreadInteractions.status, "pending")))
1086
- .returning();
1087
- if (!updated) {
1088
- throw conflict("Interaction has already been resolved");
1089
- }
1090
- await touchIssue(db, args.issue.id);
1200
+ const updated = await db.transaction(async (tx) => {
1201
+ const issueContext = await tx
1202
+ .select({
1203
+ id: issues.id,
1204
+ companyId: issues.companyId,
1205
+ status: issues.status,
1206
+ assigneeAgentId: issues.assigneeAgentId,
1207
+ assigneeUserId: issues.assigneeUserId,
1208
+ reviewPolicy: issues.reviewPolicy,
1209
+ createdByAgentId: issues.createdByAgentId,
1210
+ createdByUserId: issues.createdByUserId,
1211
+ })
1212
+ .from(issues)
1213
+ .where(eq(issues.id, args.issue.id))
1214
+ .for("update")
1215
+ .then((rows) => rows[0] ?? null);
1216
+ if (!issueContext || issueContext.companyId !== args.issue.companyId) {
1217
+ throw notFound("Issue not found");
1218
+ }
1219
+ const lockedCurrent = await tx
1220
+ .select()
1221
+ .from(issueThreadInteractions)
1222
+ .where(eq(issueThreadInteractions.id, args.current.id))
1223
+ .for("update")
1224
+ .then((rows) => rows[0] ?? null);
1225
+ if (!lockedCurrent
1226
+ || lockedCurrent.companyId !== args.issue.companyId
1227
+ || lockedCurrent.issueId !== args.issue.id) {
1228
+ throw notFound("Interaction not found");
1229
+ }
1230
+ if (lockedCurrent.status !== "pending") {
1231
+ throw issueThreadInteractionResolutionError(409, "interaction_already_resolved", "Interaction has already been resolved");
1232
+ }
1233
+ await assertRequestConfirmationResolutionAllowedUnderLock(tx, issueContext, lockedCurrent, args.actor);
1234
+ const [resolved] = await tx
1235
+ .update(issueThreadInteractions)
1236
+ .set({
1237
+ status: "rejected",
1238
+ result: {
1239
+ version: 1,
1240
+ outcome: "rejected",
1241
+ reason: reason || null,
1242
+ },
1243
+ resolvedByAgentId: args.actor.agentId ?? null,
1244
+ resolvedByRunId: args.actor.runId ?? null,
1245
+ resolvedByUserId: args.actor.userId ?? null,
1246
+ resolvedAt: now,
1247
+ updatedAt: now,
1248
+ })
1249
+ .where(and(eq(issueThreadInteractions.id, lockedCurrent.id), eq(issueThreadInteractions.status, "pending")))
1250
+ .returning();
1251
+ if (!resolved) {
1252
+ throw issueThreadInteractionResolutionError(409, "interaction_already_resolved", "Interaction has already been resolved");
1253
+ }
1254
+ await touchIssue(tx, args.issue.id);
1255
+ return resolved;
1256
+ });
1091
1257
  const rejected = hydrateInteraction(updated);
1092
1258
  await emitInteractionResolvedTelemetry(db, rejected);
1093
1259
  return rejected;
@@ -1212,12 +1378,26 @@ export function issueThreadInteractionService(db, opts = {}) {
1212
1378
  return { checked: rows.length, candidates: eligible.length, accepted, woken };
1213
1379
  },
1214
1380
  listForIssue: async (issueId) => {
1215
- const rows = await db
1216
- .select()
1217
- .from(issueThreadInteractions)
1218
- .where(eq(issueThreadInteractions.issueId, issueId))
1219
- .orderBy(asc(issueThreadInteractions.createdAt), asc(issueThreadInteractions.id));
1220
- return rows.map((row) => hydrateInteraction(row));
1381
+ const [rows, issueStatus] = await Promise.all([
1382
+ db
1383
+ .select()
1384
+ .from(issueThreadInteractions)
1385
+ .where(eq(issueThreadInteractions.issueId, issueId))
1386
+ .orderBy(asc(issueThreadInteractions.createdAt), asc(issueThreadInteractions.id)),
1387
+ db
1388
+ .select({ status: issues.status })
1389
+ .from(issues)
1390
+ .where(eq(issues.id, issueId))
1391
+ .then((issueRows) => issueRows[0]?.status ?? null),
1392
+ ]);
1393
+ return rows.map((row) => hydrateInteraction(issueStatus && isTerminalIssueStatus(issueStatus) && row.status === "pending"
1394
+ ? {
1395
+ ...row,
1396
+ status: "expired",
1397
+ result: buildAdministrativeOutcomeResult(row, "issue_closed"),
1398
+ resolvedAt: row.updatedAt,
1399
+ }
1400
+ : row));
1221
1401
  },
1222
1402
  getById: async (interactionId) => {
1223
1403
  const row = await db
@@ -1320,6 +1500,7 @@ export function issueThreadInteractionService(db, opts = {}) {
1320
1500
  },
1321
1501
  create: async (issue, input, actor) => {
1322
1502
  const data = normalizeCreateInteractionInput(createIssueThreadInteractionSchema.parse(input));
1503
+ const usedDeprecatedResolverPolicyAlias = data.resolverPolicy === "board_or_agents" || data.resolverPolicy === "board_only";
1323
1504
  const governance = await db
1324
1505
  .select({ interactionResolverGovernance: companies.interactionResolverGovernance })
1325
1506
  .from(companies)
@@ -1444,6 +1625,8 @@ export function issueThreadInteractionService(db, opts = {}) {
1444
1625
  continuationPolicy: data.continuationPolicy,
1445
1626
  requestedResolverPolicy: policy.requestedResolverPolicy,
1446
1627
  effectiveResolverPolicy: policy.effectiveResolverPolicy,
1628
+ resolverPolicyProvenance: policy.resolverPolicyProvenance,
1629
+ effectiveResolverPolicySource: policy.effectiveResolverPolicySource,
1447
1630
  idempotencyKey: data.idempotencyKey ?? null,
1448
1631
  sourceCommentId: data.sourceCommentId ?? null,
1449
1632
  sourceRunId: data.sourceRunId ?? null,
@@ -1455,15 +1638,25 @@ export function issueThreadInteractionService(db, opts = {}) {
1455
1638
  payload: data.payload,
1456
1639
  })
1457
1640
  .returning();
1458
- if (data.kind !== "request_confirmation" || !actor.agentId) {
1641
+ // An agent replacing its own still-pending card supersedes the older
1642
+ // one so the thread never accumulates stale sibling cards. This covers
1643
+ // request_confirmation drafts and ask_user_questions (PAP-437: probe
1644
+ // question cards that agents never withdrew). Each kind keeps its own
1645
+ // result shape. Scoped strictly to the same agent + issue + kind, so
1646
+ // other agents' or other kinds' pending cards are untouched.
1647
+ const canSupersedeSiblingCards = data.kind === "request_confirmation" || data.kind === "ask_user_questions";
1648
+ if (!actor.agentId || !canSupersedeSiblingCards) {
1459
1649
  return { row, supersededRows: [] };
1460
1650
  }
1461
1651
  const now = new Date();
1652
+ const supersededResult = data.kind === "ask_user_questions"
1653
+ ? buildSupersededByNewerInteractionResult(row.id)
1654
+ : buildSupersededByNewerRequestResult(row.id);
1462
1655
  const supersededRows = await tx
1463
1656
  .update(issueThreadInteractions)
1464
1657
  .set({
1465
1658
  status: "expired",
1466
- result: buildSupersededByNewerRequestResult(row.id),
1659
+ result: supersededResult,
1467
1660
  resolvedByAgentId: actor.agentId,
1468
1661
  resolvedByUserId: actor.userId ?? null,
1469
1662
  resolvedAt: now,
@@ -1506,12 +1699,17 @@ export function issueThreadInteractionService(db, opts = {}) {
1506
1699
  if (superseded.length > 0) {
1507
1700
  await emitResolvedInteractionsTelemetry(db, superseded.map(hydrateInteraction));
1508
1701
  }
1509
- return hydrateInteraction(created);
1702
+ const interaction = hydrateInteraction(created);
1703
+ emitInteractionCreatedTelemetry({
1704
+ interactionKind: interaction.kind,
1705
+ usedDeprecatedResolverPolicyAlias,
1706
+ });
1707
+ return interaction;
1510
1708
  },
1511
1709
  acceptInteraction: async (issue, interactionId, input, actor) => {
1512
1710
  const data = acceptIssueThreadInteractionSchema.parse(input);
1513
1711
  const current = await getPendingInteractionForResolution({ issue, interactionId });
1514
- assertAgentResolutionAllowed(current, actor);
1712
+ assertInteractionResolutionAllowed(current, actor);
1515
1713
  switch (current.kind) {
1516
1714
  case "suggest_tasks":
1517
1715
  // Accepting suggest_tasks only creates follow-up issues; it does not
@@ -1551,22 +1749,26 @@ export function issueThreadInteractionService(db, opts = {}) {
1551
1749
  }
1552
1750
  },
1553
1751
  acceptSuggestedTasks: async (issue, interactionId, input, actor) => {
1752
+ assertIssueOpenForInteractionResolution(issue);
1554
1753
  const current = await db
1555
1754
  .select()
1556
1755
  .from(issueThreadInteractions)
1557
1756
  .where(eq(issueThreadInteractions.id, interactionId))
1558
1757
  .then((rows) => rows[0] ?? null);
1559
1758
  if (!current)
1560
- throw notFound("Interaction not found");
1759
+ throw interactionNotFoundError();
1561
1760
  if (current.companyId !== issue.companyId || current.issueId !== issue.id) {
1562
- throw notFound("Interaction not found");
1761
+ throw interactionNotFoundError();
1563
1762
  }
1564
- assertAgentResolutionAllowed(current, actor);
1763
+ assertInteractionResolutionAllowed(current, actor);
1565
1764
  if (current.kind !== "suggest_tasks") {
1566
1765
  throw unprocessable("Only suggest_tasks interactions can be accepted");
1567
1766
  }
1568
1767
  if (current.status !== "pending") {
1569
- throw conflict("Interaction has already been resolved");
1768
+ throw interactionTerminalError(current);
1769
+ }
1770
+ if (actor.agentId && actor.suggestedTaskEffectsAuthorized !== true) {
1771
+ throw issueThreadInteractionResolutionError(403, "interaction_governed_action_denied", "Suggested-task creation requires independent task-creation authorization");
1570
1772
  }
1571
1773
  const interaction = hydrateInteraction(current);
1572
1774
  const { selectedTasks, skippedClientKeys } = resolveSelectedSuggestedTasks({
@@ -1612,7 +1814,7 @@ export function issueThreadInteractionService(db, opts = {}) {
1612
1814
  .where(and(eq(issueThreadInteractions.id, interactionId), eq(issueThreadInteractions.status, "pending")))
1613
1815
  .returning();
1614
1816
  if (!claimed) {
1615
- throw conflict("Interaction has already been resolved");
1817
+ throw interactionAlreadyResolvedError();
1616
1818
  }
1617
1819
  for (const task of orderedTasks) {
1618
1820
  const parentIssueId = task.parentClientKey
@@ -1686,7 +1888,7 @@ export function issueThreadInteractionService(db, opts = {}) {
1686
1888
  rejectInteraction: async (issue, interactionId, input, actor) => {
1687
1889
  const data = rejectIssueThreadInteractionSchema.parse(input);
1688
1890
  const current = await getPendingInteractionForResolution({ issue, interactionId });
1689
- assertAgentResolutionAllowed(current, actor);
1891
+ assertInteractionResolutionAllowed(current, actor);
1690
1892
  switch (current.kind) {
1691
1893
  case "suggest_tasks":
1692
1894
  return issueThreadInteractionService(db).rejectSuggestedTasks(issue, interactionId, data, actor, current);
@@ -1703,6 +1905,7 @@ export function issueThreadInteractionService(db, opts = {}) {
1703
1905
  }
1704
1906
  },
1705
1907
  submitItemVerdicts: async (issue, interactionId, input, actor) => {
1908
+ assertIssueOpenForInteractionResolution(issue);
1706
1909
  const data = submitIssueThreadInteractionVerdictsSchema.parse(input);
1707
1910
  const submission = await db.transaction(async (tx) => {
1708
1911
  const current = await tx
@@ -1712,13 +1915,14 @@ export function issueThreadInteractionService(db, opts = {}) {
1712
1915
  .for("update")
1713
1916
  .then((rows) => rows[0] ?? null);
1714
1917
  if (!current)
1715
- throw notFound("Interaction not found");
1918
+ throw interactionNotFoundError();
1716
1919
  if (current.companyId !== issue.companyId || current.issueId !== issue.id) {
1717
- throw notFound("Interaction not found");
1920
+ throw interactionNotFoundError();
1718
1921
  }
1719
1922
  if (current.kind !== "request_item_verdicts") {
1720
1923
  throw unprocessable("Only request_item_verdicts interactions can receive item verdicts");
1721
1924
  }
1925
+ assertInteractionResolutionAllowed(current, actor);
1722
1926
  const interaction = hydrateInteraction(current);
1723
1927
  if (current.status !== "pending") {
1724
1928
  if (current.status === "answered") {
@@ -1729,19 +1933,24 @@ export function issueThreadInteractionService(db, opts = {}) {
1729
1933
  throw unprocessable(`Unknown item verdict id: ${submitted.id}`);
1730
1934
  }
1731
1935
  if (!resolvedIds.has(submitted.id)) {
1732
- throw conflict("Interaction has already been resolved");
1936
+ throw interactionTerminalError(current);
1733
1937
  }
1734
1938
  }
1735
1939
  return { interaction, newlyResolvedItemIds: [], resolved: false };
1736
1940
  }
1737
- throw conflict("Interaction has already been resolved");
1941
+ throw interactionTerminalError(current);
1738
1942
  }
1739
1943
  const expired = await expireStaleRequestConfirmationTarget(tx, {
1740
1944
  row: current,
1741
1945
  actor,
1742
1946
  });
1743
1947
  if (expired) {
1744
- return { interaction: expired, newlyResolvedItemIds: [], resolved: false };
1948
+ return {
1949
+ interaction: expired,
1950
+ newlyResolvedItemIds: [],
1951
+ resolved: false,
1952
+ terminalError: interactionTerminalError({ status: expired.status, result: expired.result }),
1953
+ };
1745
1954
  }
1746
1955
  const now = new Date();
1747
1956
  const { items, complete, newlyResolvedItemIds } = resolveRequestItemVerdictSubmissions({
@@ -1773,7 +1982,7 @@ export function issueThreadInteractionService(db, opts = {}) {
1773
1982
  .where(and(eq(issueThreadInteractions.id, interactionId), eq(issueThreadInteractions.status, "pending")))
1774
1983
  .returning();
1775
1984
  if (!updated) {
1776
- throw conflict("Interaction has already been resolved");
1985
+ throw interactionAlreadyResolvedError();
1777
1986
  }
1778
1987
  await touchIssue(tx, issue.id);
1779
1988
  return {
@@ -1782,20 +1991,23 @@ export function issueThreadInteractionService(db, opts = {}) {
1782
1991
  resolved: complete,
1783
1992
  };
1784
1993
  });
1994
+ if ("terminalError" in submission)
1995
+ throw submission.terminalError;
1785
1996
  if (submission.resolved) {
1786
1997
  await emitInteractionResolvedTelemetry(db, submission.interaction);
1787
1998
  }
1788
1999
  return submission;
1789
2000
  },
1790
2001
  rejectSuggestedTasks: async (issue, interactionId, input, actor, current) => {
2002
+ assertIssueOpenForInteractionResolution(issue);
1791
2003
  if (current.companyId !== issue.companyId || current.issueId !== issue.id) {
1792
- throw notFound("Interaction not found");
2004
+ throw interactionNotFoundError();
1793
2005
  }
1794
2006
  if (current.kind !== "suggest_tasks") {
1795
2007
  throw unprocessable("Only suggest_tasks interactions can be rejected");
1796
2008
  }
1797
2009
  if (current.status !== "pending") {
1798
- throw conflict("Interaction has already been resolved");
2010
+ throw interactionTerminalError(current);
1799
2011
  }
1800
2012
  const [updated] = await db
1801
2013
  .update(issueThreadInteractions)
@@ -1814,7 +2026,7 @@ export function issueThreadInteractionService(db, opts = {}) {
1814
2026
  .where(and(eq(issueThreadInteractions.id, interactionId), eq(issueThreadInteractions.status, "pending")))
1815
2027
  .returning();
1816
2028
  if (!updated) {
1817
- throw conflict("Interaction has already been resolved");
2029
+ throw interactionAlreadyResolvedError();
1818
2030
  }
1819
2031
  await touchIssue(db, issue.id);
1820
2032
  const rejected = hydrateInteraction(updated);
@@ -2099,6 +2311,7 @@ export function issueThreadInteractionService(db, opts = {}) {
2099
2311
  return expired;
2100
2312
  },
2101
2313
  withdrawInteraction: async (issue, interactionId, input, actor) => {
2314
+ assertIssueOpenForInteractionResolution(issue);
2102
2315
  const data = withdrawIssueThreadInteractionSchema.parse(input);
2103
2316
  const current = await db
2104
2317
  .select()
@@ -2106,10 +2319,10 @@ export function issueThreadInteractionService(db, opts = {}) {
2106
2319
  .where(eq(issueThreadInteractions.id, interactionId))
2107
2320
  .then((rows) => rows[0] ?? null);
2108
2321
  if (!current || current.companyId !== issue.companyId || current.issueId !== issue.id) {
2109
- throw notFound("Interaction not found");
2322
+ throw interactionNotFoundError();
2110
2323
  }
2111
2324
  if (current.status !== "pending")
2112
- throw conflict("Interaction has already been resolved");
2325
+ throw interactionTerminalError(current);
2113
2326
  const reason = data.reason?.trim() || null;
2114
2327
  const now = new Date();
2115
2328
  // One transaction, linked tool action first: revoking pending/approved
@@ -2150,7 +2363,7 @@ export function issueThreadInteractionService(db, opts = {}) {
2150
2363
  .where(and(eq(issueThreadInteractions.id, interactionId), eq(issueThreadInteractions.status, "pending")))
2151
2364
  .returning();
2152
2365
  if (!row)
2153
- throw conflict("Interaction has already been resolved");
2366
+ throw interactionAlreadyResolvedError();
2154
2367
  return row;
2155
2368
  });
2156
2369
  await touchIssue(db, issue.id);
@@ -2159,22 +2372,23 @@ export function issueThreadInteractionService(db, opts = {}) {
2159
2372
  return withdrawn;
2160
2373
  },
2161
2374
  answerQuestions: async (issue, interactionId, input, actor) => {
2375
+ assertIssueOpenForInteractionResolution(issue);
2162
2376
  const current = await db
2163
2377
  .select()
2164
2378
  .from(issueThreadInteractions)
2165
2379
  .where(eq(issueThreadInteractions.id, interactionId))
2166
2380
  .then((rows) => rows[0] ?? null);
2167
2381
  if (!current)
2168
- throw notFound("Interaction not found");
2382
+ throw interactionNotFoundError();
2169
2383
  if (current.companyId !== issue.companyId || current.issueId !== issue.id) {
2170
- throw notFound("Interaction not found");
2384
+ throw interactionNotFoundError();
2171
2385
  }
2172
- assertAgentResolutionAllowed(current, actor);
2386
+ assertInteractionResolutionAllowed(current, actor);
2173
2387
  if (current.kind !== "ask_user_questions") {
2174
2388
  throw unprocessable("Only ask_user_questions interactions can be answered");
2175
2389
  }
2176
2390
  if (current.status !== "pending") {
2177
- throw conflict("Interaction has already been resolved");
2391
+ throw interactionTerminalError(current);
2178
2392
  }
2179
2393
  const interaction = hydrateInteraction(current);
2180
2394
  const normalizedAnswers = normalizeQuestionAnswers({
@@ -2199,7 +2413,7 @@ export function issueThreadInteractionService(db, opts = {}) {
2199
2413
  .where(and(eq(issueThreadInteractions.id, interactionId), eq(issueThreadInteractions.status, "pending")))
2200
2414
  .returning();
2201
2415
  if (!updated) {
2202
- throw conflict("Interaction has already been resolved");
2416
+ throw interactionAlreadyResolvedError();
2203
2417
  }
2204
2418
  await touchIssue(db, issue.id);
2205
2419
  const answered = hydrateInteraction(updated);
@@ -2207,6 +2421,7 @@ export function issueThreadInteractionService(db, opts = {}) {
2207
2421
  return answered;
2208
2422
  },
2209
2423
  cancelQuestions: async (issue, interactionId, input, actor) => {
2424
+ assertIssueOpenForInteractionResolution(issue);
2210
2425
  const data = cancelIssueThreadInteractionSchema.parse(input);
2211
2426
  const current = await db
2212
2427
  .select()
@@ -2214,15 +2429,15 @@ export function issueThreadInteractionService(db, opts = {}) {
2214
2429
  .where(eq(issueThreadInteractions.id, interactionId))
2215
2430
  .then((rows) => rows[0] ?? null);
2216
2431
  if (!current)
2217
- throw notFound("Interaction not found");
2432
+ throw interactionNotFoundError();
2218
2433
  if (current.companyId !== issue.companyId || current.issueId !== issue.id) {
2219
- throw notFound("Interaction not found");
2434
+ throw interactionNotFoundError();
2220
2435
  }
2221
2436
  if (current.kind !== "ask_user_questions") {
2222
2437
  throw unprocessable("Only ask_user_questions interactions can be cancelled");
2223
2438
  }
2224
2439
  if (current.status !== "pending") {
2225
- throw conflict("Interaction has already been resolved");
2440
+ throw interactionTerminalError(current);
2226
2441
  }
2227
2442
  const reason = data.reason?.trim() || null;
2228
2443
  const [updated] = await db
@@ -2245,7 +2460,7 @@ export function issueThreadInteractionService(db, opts = {}) {
2245
2460
  .where(and(eq(issueThreadInteractions.id, interactionId), eq(issueThreadInteractions.status, "pending")))
2246
2461
  .returning();
2247
2462
  if (!updated) {
2248
- throw conflict("Interaction has already been resolved");
2463
+ throw interactionAlreadyResolvedError();
2249
2464
  }
2250
2465
  await touchIssue(db, issue.id);
2251
2466
  const cancelled = hydrateInteraction(updated);