@paperclipai/server 2026.824.0 → 2026.825.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 (519) hide show
  1. package/dist/adapters/builtin-adapter-types.d.ts.map +1 -1
  2. package/dist/adapters/builtin-adapter-types.js +1 -0
  3. package/dist/adapters/builtin-adapter-types.js.map +1 -1
  4. package/dist/adapters/plugin-loader.d.ts +1 -0
  5. package/dist/adapters/plugin-loader.d.ts.map +1 -1
  6. package/dist/adapters/plugin-loader.js +12 -1
  7. package/dist/adapters/plugin-loader.js.map +1 -1
  8. package/dist/adapters/plugin-loader.test.d.ts +2 -0
  9. package/dist/adapters/plugin-loader.test.d.ts.map +1 -0
  10. package/dist/adapters/plugin-loader.test.js +37 -0
  11. package/dist/adapters/plugin-loader.test.js.map +1 -0
  12. package/dist/adapters/registry.d.ts.map +1 -1
  13. package/dist/adapters/registry.js +68 -5
  14. package/dist/adapters/registry.js.map +1 -1
  15. package/dist/adapters/registry.test.d.ts +2 -0
  16. package/dist/adapters/registry.test.d.ts.map +1 -0
  17. package/dist/adapters/registry.test.js +35 -0
  18. package/dist/adapters/registry.test.js.map +1 -0
  19. package/dist/app.d.ts.map +1 -1
  20. package/dist/app.js +5 -7
  21. package/dist/app.js.map +1 -1
  22. package/dist/auth/better-auth.d.ts +10 -0
  23. package/dist/auth/better-auth.d.ts.map +1 -1
  24. package/dist/auth/better-auth.js +47 -0
  25. package/dist/auth/better-auth.js.map +1 -1
  26. package/dist/auth/workspace-login-handoff-exchange.d.ts +75 -0
  27. package/dist/auth/workspace-login-handoff-exchange.d.ts.map +1 -0
  28. package/dist/auth/workspace-login-handoff-exchange.js +73 -0
  29. package/dist/auth/workspace-login-handoff-exchange.js.map +1 -0
  30. package/dist/auth/workspace-login-handoff-plugin.d.ts +40 -0
  31. package/dist/auth/workspace-login-handoff-plugin.d.ts.map +1 -0
  32. package/dist/auth/workspace-login-handoff-plugin.js +140 -0
  33. package/dist/auth/workspace-login-handoff-plugin.js.map +1 -0
  34. package/dist/auth/workspace-login-handoff.d.ts +189 -0
  35. package/dist/auth/workspace-login-handoff.d.ts.map +1 -0
  36. package/dist/auth/workspace-login-handoff.js +319 -0
  37. package/dist/auth/workspace-login-handoff.js.map +1 -0
  38. package/dist/build-info.json +3 -0
  39. package/dist/config.d.ts +1 -0
  40. package/dist/config.d.ts.map +1 -1
  41. package/dist/config.js +14 -0
  42. package/dist/config.js.map +1 -1
  43. package/dist/dev-runner-worktree.d.ts.map +1 -1
  44. package/dist/dev-runner-worktree.js +5 -0
  45. package/dist/dev-runner-worktree.js.map +1 -1
  46. package/dist/duplex-aggregate-ceiling-env.d.ts +21 -0
  47. package/dist/duplex-aggregate-ceiling-env.d.ts.map +1 -0
  48. package/dist/duplex-aggregate-ceiling-env.js +23 -0
  49. package/dist/duplex-aggregate-ceiling-env.js.map +1 -0
  50. package/dist/embedded-postgres-supervisor.d.ts +27 -0
  51. package/dist/embedded-postgres-supervisor.d.ts.map +1 -0
  52. package/dist/embedded-postgres-supervisor.js +72 -0
  53. package/dist/embedded-postgres-supervisor.js.map +1 -0
  54. package/dist/errors.d.ts +2 -0
  55. package/dist/errors.d.ts.map +1 -1
  56. package/dist/errors.js +6 -0
  57. package/dist/errors.js.map +1 -1
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +115 -7
  60. package/dist/index.js.map +1 -1
  61. package/dist/instrumentation.d.ts +33 -0
  62. package/dist/instrumentation.d.ts.map +1 -1
  63. package/dist/instrumentation.js +68 -1
  64. package/dist/instrumentation.js.map +1 -1
  65. package/dist/middleware/auth.d.ts.map +1 -1
  66. package/dist/middleware/auth.js +35 -10
  67. package/dist/middleware/auth.js.map +1 -1
  68. package/dist/middleware/error-handler.d.ts.map +1 -1
  69. package/dist/middleware/error-handler.js +8 -1
  70. package/dist/middleware/error-handler.js.map +1 -1
  71. package/dist/middleware/logger.d.ts.map +1 -1
  72. package/dist/middleware/logger.js +5 -2
  73. package/dist/middleware/logger.js.map +1 -1
  74. package/dist/middleware/redact-sensitive.d.ts.map +1 -1
  75. package/dist/middleware/redact-sensitive.js +4 -0
  76. package/dist/middleware/redact-sensitive.js.map +1 -1
  77. package/dist/middleware/validate.d.ts +3 -1
  78. package/dist/middleware/validate.d.ts.map +1 -1
  79. package/dist/middleware/validate.js +37 -0
  80. package/dist/middleware/validate.js.map +1 -1
  81. package/dist/routes/access.d.ts.map +1 -1
  82. package/dist/routes/access.js +20 -0
  83. package/dist/routes/access.js.map +1 -1
  84. package/dist/routes/activity.js +4 -4
  85. package/dist/routes/activity.js.map +1 -1
  86. package/dist/routes/adapter-login-route-spine.d.ts +81 -0
  87. package/dist/routes/adapter-login-route-spine.d.ts.map +1 -0
  88. package/dist/routes/adapter-login-route-spine.js +40 -0
  89. package/dist/routes/adapter-login-route-spine.js.map +1 -0
  90. package/dist/routes/adapters.d.ts +33 -0
  91. package/dist/routes/adapters.d.ts.map +1 -1
  92. package/dist/routes/adapters.js +35 -1
  93. package/dist/routes/adapters.js.map +1 -1
  94. package/dist/routes/adapters.test.d.ts +2 -0
  95. package/dist/routes/adapters.test.d.ts.map +1 -0
  96. package/dist/routes/adapters.test.js +58 -0
  97. package/dist/routes/adapters.test.js.map +1 -0
  98. package/dist/routes/agents.d.ts.map +1 -1
  99. package/dist/routes/agents.js +281 -91
  100. package/dist/routes/agents.js.map +1 -1
  101. package/dist/routes/cases.d.ts +11 -22
  102. package/dist/routes/cases.d.ts.map +1 -1
  103. package/dist/routes/cases.js +13 -13
  104. package/dist/routes/companies.js +3 -3
  105. package/dist/routes/decision-training.js +6 -6
  106. package/dist/routes/decisions.js +4 -4
  107. package/dist/routes/decisions.js.map +1 -1
  108. package/dist/routes/environments.d.ts.map +1 -1
  109. package/dist/routes/environments.js +79 -6
  110. package/dist/routes/environments.js.map +1 -1
  111. package/dist/routes/execution-workspaces.d.ts.map +1 -1
  112. package/dist/routes/execution-workspaces.js +384 -16
  113. package/dist/routes/execution-workspaces.js.map +1 -1
  114. package/dist/routes/file-resources.js +2 -2
  115. package/dist/routes/health.d.ts.map +1 -1
  116. package/dist/routes/health.js +58 -4
  117. package/dist/routes/health.js.map +1 -1
  118. package/dist/routes/instance-settings.d.ts.map +1 -1
  119. package/dist/routes/instance-settings.js +40 -0
  120. package/dist/routes/instance-settings.js.map +1 -1
  121. package/dist/routes/issues.d.ts +10 -0
  122. package/dist/routes/issues.d.ts.map +1 -1
  123. package/dist/routes/issues.js +495 -65
  124. package/dist/routes/issues.js.map +1 -1
  125. package/dist/routes/openapi.d.ts.map +1 -1
  126. package/dist/routes/openapi.js +241 -122
  127. package/dist/routes/openapi.js.map +1 -1
  128. package/dist/routes/pipelines.js +19 -19
  129. package/dist/routes/plugins.d.ts.map +1 -1
  130. package/dist/routes/plugins.js +22 -0
  131. package/dist/routes/plugins.js.map +1 -1
  132. package/dist/routes/projects.d.ts.map +1 -1
  133. package/dist/routes/projects.js +2 -0
  134. package/dist/routes/projects.js.map +1 -1
  135. package/dist/routes/routines.js +2 -2
  136. package/dist/routes/routines.js.map +1 -1
  137. package/dist/routes/secrets.d.ts +1 -1
  138. package/dist/routes/secrets.d.ts.map +1 -1
  139. package/dist/routes/secrets.js +39 -50
  140. package/dist/routes/secrets.js.map +1 -1
  141. package/dist/routes/setup-token-route.test.js +50 -3
  142. package/dist/routes/setup-token-route.test.js.map +1 -1
  143. package/dist/services/adapter-login-lease.d.ts +50 -0
  144. package/dist/services/adapter-login-lease.d.ts.map +1 -0
  145. package/dist/services/adapter-login-lease.js +26 -0
  146. package/dist/services/adapter-login-lease.js.map +1 -0
  147. package/dist/services/adapter-login-lease.test.d.ts +2 -0
  148. package/dist/services/adapter-login-lease.test.d.ts.map +1 -0
  149. package/dist/services/adapter-login-lease.test.js +68 -0
  150. package/dist/services/adapter-login-lease.test.js.map +1 -0
  151. package/dist/services/agent-action-audit.js +1 -1
  152. package/dist/services/agent-secret-bindings.d.ts +15 -0
  153. package/dist/services/agent-secret-bindings.d.ts.map +1 -1
  154. package/dist/services/agent-secret-bindings.js +2 -2
  155. package/dist/services/agent-secret-bindings.js.map +1 -1
  156. package/dist/services/agents.d.ts +13 -13
  157. package/dist/services/agents.d.ts.map +1 -1
  158. package/dist/services/agents.js +46 -5
  159. package/dist/services/agents.js.map +1 -1
  160. package/dist/services/artifact-review-documents.d.ts +34 -0
  161. package/dist/services/artifact-review-documents.d.ts.map +1 -0
  162. package/dist/services/artifact-review-documents.js +196 -0
  163. package/dist/services/artifact-review-documents.js.map +1 -0
  164. package/dist/services/assets.d.ts +1 -1
  165. package/dist/services/codex-device-login-credential-read.d.ts +58 -0
  166. package/dist/services/codex-device-login-credential-read.d.ts.map +1 -0
  167. package/dist/services/codex-device-login-credential-read.js +118 -0
  168. package/dist/services/codex-device-login-credential-read.js.map +1 -0
  169. package/dist/services/codex-device-login-reaper.d.ts.map +1 -1
  170. package/dist/services/codex-device-login-reaper.js +8 -5
  171. package/dist/services/codex-device-login-reaper.js.map +1 -1
  172. package/dist/services/codex-device-login-service.d.ts +127 -36
  173. package/dist/services/codex-device-login-service.d.ts.map +1 -1
  174. package/dist/services/codex-device-login-service.js +204 -84
  175. package/dist/services/codex-device-login-service.js.map +1 -1
  176. package/dist/services/company-portability.d.ts +3 -1
  177. package/dist/services/company-portability.d.ts.map +1 -1
  178. package/dist/services/company-portability.js +76 -20
  179. package/dist/services/company-portability.js.map +1 -1
  180. package/dist/services/company-skill-policy.d.ts +1 -1
  181. package/dist/services/costs.d.ts +1 -1
  182. package/dist/services/decisions.d.ts +6 -6
  183. package/dist/services/duplex-observability-recorder.d.ts +62 -0
  184. package/dist/services/duplex-observability-recorder.d.ts.map +1 -0
  185. package/dist/services/duplex-observability-recorder.js +54 -0
  186. package/dist/services/duplex-observability-recorder.js.map +1 -0
  187. package/dist/services/environment-config.js +5 -5
  188. package/dist/services/environment-config.js.map +1 -1
  189. package/dist/services/environment-custom-image-runtime.d.ts +116 -0
  190. package/dist/services/environment-custom-image-runtime.d.ts.map +1 -1
  191. package/dist/services/environment-custom-image-runtime.js +255 -1
  192. package/dist/services/environment-custom-image-runtime.js.map +1 -1
  193. package/dist/services/environment-custom-images.d.ts +39 -0
  194. package/dist/services/environment-custom-images.d.ts.map +1 -1
  195. package/dist/services/environment-custom-images.js +219 -17
  196. package/dist/services/environment-custom-images.js.map +1 -1
  197. package/dist/services/environment-driver-traits.d.ts +109 -0
  198. package/dist/services/environment-driver-traits.d.ts.map +1 -0
  199. package/dist/services/environment-driver-traits.js +102 -0
  200. package/dist/services/environment-driver-traits.js.map +1 -0
  201. package/dist/services/environment-execution-target.d.ts +4 -0
  202. package/dist/services/environment-execution-target.d.ts.map +1 -1
  203. package/dist/services/environment-execution-target.js +63 -7
  204. package/dist/services/environment-execution-target.js.map +1 -1
  205. package/dist/services/environment-run-orchestrator.d.ts +27 -1
  206. package/dist/services/environment-run-orchestrator.d.ts.map +1 -1
  207. package/dist/services/environment-run-orchestrator.js +24 -4
  208. package/dist/services/environment-run-orchestrator.js.map +1 -1
  209. package/dist/services/environment-runtime.d.ts +118 -20
  210. package/dist/services/environment-runtime.d.ts.map +1 -1
  211. package/dist/services/environment-runtime.js +436 -91
  212. package/dist/services/environment-runtime.js.map +1 -1
  213. package/dist/services/environments.d.ts.map +1 -1
  214. package/dist/services/environments.js +20 -2
  215. package/dist/services/environments.js.map +1 -1
  216. package/dist/services/execution-workspace-branch-ownership.d.ts +4 -0
  217. package/dist/services/execution-workspace-branch-ownership.d.ts.map +1 -0
  218. package/dist/services/execution-workspace-branch-ownership.js +9 -0
  219. package/dist/services/execution-workspace-branch-ownership.js.map +1 -0
  220. package/dist/services/execution-workspace-policy.d.ts.map +1 -1
  221. package/dist/services/execution-workspace-policy.js +3 -0
  222. package/dist/services/execution-workspace-policy.js.map +1 -1
  223. package/dist/services/execution-workspaces.d.ts +2 -0
  224. package/dist/services/execution-workspaces.d.ts.map +1 -1
  225. package/dist/services/execution-workspaces.js +90 -2
  226. package/dist/services/execution-workspaces.js.map +1 -1
  227. package/dist/services/external-objects.d.ts +19 -19
  228. package/dist/services/finance.d.ts +1 -1
  229. package/dist/services/goals.d.ts +2 -2
  230. package/dist/services/heartbeat-policy.d.ts +3 -0
  231. package/dist/services/heartbeat-policy.d.ts.map +1 -0
  232. package/dist/services/heartbeat-policy.js +10 -0
  233. package/dist/services/heartbeat-policy.js.map +1 -0
  234. package/dist/services/heartbeat-run-runtime-status.test.js +1 -1
  235. package/dist/services/heartbeat-run-runtime-status.test.js.map +1 -1
  236. package/dist/services/heartbeat.d.ts +23 -1
  237. package/dist/services/heartbeat.d.ts.map +1 -1
  238. package/dist/services/heartbeat.js +192 -11
  239. package/dist/services/heartbeat.js.map +1 -1
  240. package/dist/services/index.d.ts +1 -0
  241. package/dist/services/index.d.ts.map +1 -1
  242. package/dist/services/index.js +1 -0
  243. package/dist/services/index.js.map +1 -1
  244. package/dist/services/instance-settings.d.ts.map +1 -1
  245. package/dist/services/instance-settings.js +2 -0
  246. package/dist/services/instance-settings.js.map +1 -1
  247. package/dist/services/issue-dependency-wakeups.d.ts +57 -9
  248. package/dist/services/issue-dependency-wakeups.d.ts.map +1 -1
  249. package/dist/services/issue-dependency-wakeups.js +146 -14
  250. package/dist/services/issue-dependency-wakeups.js.map +1 -1
  251. package/dist/services/issue-dependency-wakeups.test.d.ts +2 -0
  252. package/dist/services/issue-dependency-wakeups.test.d.ts.map +1 -0
  253. package/dist/services/issue-dependency-wakeups.test.js +183 -0
  254. package/dist/services/issue-dependency-wakeups.test.js.map +1 -0
  255. package/dist/services/issue-recovery-actions.d.ts +6 -1
  256. package/dist/services/issue-recovery-actions.d.ts.map +1 -1
  257. package/dist/services/issue-recovery-actions.js +123 -44
  258. package/dist/services/issue-recovery-actions.js.map +1 -1
  259. package/dist/services/issue-thread-interactions.d.ts +8 -0
  260. package/dist/services/issue-thread-interactions.d.ts.map +1 -1
  261. package/dist/services/issue-thread-interactions.js +220 -10
  262. package/dist/services/issue-thread-interactions.js.map +1 -1
  263. package/dist/services/issues.d.ts +8 -7
  264. package/dist/services/issues.d.ts.map +1 -1
  265. package/dist/services/issues.js +84 -12
  266. package/dist/services/issues.js.map +1 -1
  267. package/dist/services/local-service-supervisor.d.ts +61 -1
  268. package/dist/services/local-service-supervisor.d.ts.map +1 -1
  269. package/dist/services/local-service-supervisor.js +221 -25
  270. package/dist/services/local-service-supervisor.js.map +1 -1
  271. package/dist/services/login-command.d.ts +46 -0
  272. package/dist/services/login-command.d.ts.map +1 -0
  273. package/dist/services/login-command.js +87 -0
  274. package/dist/services/login-command.js.map +1 -0
  275. package/dist/services/login-command.test.d.ts +2 -0
  276. package/dist/services/login-command.test.d.ts.map +1 -0
  277. package/dist/services/login-command.test.js +71 -0
  278. package/dist/services/login-command.test.js.map +1 -0
  279. package/dist/services/managed-workspace-identity.d.ts +140 -0
  280. package/dist/services/managed-workspace-identity.d.ts.map +1 -0
  281. package/dist/services/managed-workspace-identity.js +277 -0
  282. package/dist/services/managed-workspace-identity.js.map +1 -0
  283. package/dist/services/plugin-environment-driver.d.ts +15 -1
  284. package/dist/services/plugin-environment-driver.d.ts.map +1 -1
  285. package/dist/services/plugin-environment-driver.js +23 -1
  286. package/dist/services/plugin-environment-driver.js.map +1 -1
  287. package/dist/services/plugin-host-services.d.ts +3 -2
  288. package/dist/services/plugin-host-services.d.ts.map +1 -1
  289. package/dist/services/plugin-host-services.js +11 -2
  290. package/dist/services/plugin-host-services.js.map +1 -1
  291. package/dist/services/plugin-loader.d.ts.map +1 -1
  292. package/dist/services/plugin-loader.js +39 -8
  293. package/dist/services/plugin-loader.js.map +1 -1
  294. package/dist/services/plugin-managed-agents.d.ts.map +1 -1
  295. package/dist/services/plugin-managed-agents.js +50 -5
  296. package/dist/services/plugin-managed-agents.js.map +1 -1
  297. package/dist/services/plugin-manifest-validator.d.ts.map +1 -1
  298. package/dist/services/plugin-manifest-validator.js +5 -2
  299. package/dist/services/plugin-manifest-validator.js.map +1 -1
  300. package/dist/services/plugin-registry.d.ts +5 -5
  301. package/dist/services/plugin-worker-manager.d.ts +158 -11
  302. package/dist/services/plugin-worker-manager.d.ts.map +1 -1
  303. package/dist/services/plugin-worker-manager.js +1139 -79
  304. package/dist/services/plugin-worker-manager.js.map +1 -1
  305. package/dist/services/projects.d.ts +1 -1
  306. package/dist/services/recovery/disposition-repair.d.ts +24 -0
  307. package/dist/services/recovery/disposition-repair.d.ts.map +1 -0
  308. package/dist/services/recovery/disposition-repair.js +125 -0
  309. package/dist/services/recovery/disposition-repair.js.map +1 -0
  310. package/dist/services/recovery/service.d.ts +6 -6
  311. package/dist/services/recovery/service.d.ts.map +1 -1
  312. package/dist/services/recovery/service.js +856 -384
  313. package/dist/services/recovery/service.js.map +1 -1
  314. package/dist/services/recovery/stranded-notice.js +4 -4
  315. package/dist/services/recovery/stranded-notice.js.map +1 -1
  316. package/dist/services/recovery/stranded-notice.test.js +1 -1
  317. package/dist/services/recovery/stranded-notice.test.js.map +1 -1
  318. package/dist/services/recovery/successful-run-handoff.d.ts +1 -1
  319. package/dist/services/recovery/successful-run-handoff.d.ts.map +1 -1
  320. package/dist/services/recovery/successful-run-handoff.js +6 -4
  321. package/dist/services/recovery/successful-run-handoff.js.map +1 -1
  322. package/dist/services/recovery/successful-run-handoff.test.js +1 -1
  323. package/dist/services/recovery/successful-run-handoff.test.js.map +1 -1
  324. package/dist/services/recovery-observability.d.ts +1 -1
  325. package/dist/services/recovery-observability.d.ts.map +1 -1
  326. package/dist/services/recovery-observability.js +4 -1
  327. package/dist/services/recovery-observability.js.map +1 -1
  328. package/dist/services/routines.d.ts.map +1 -1
  329. package/dist/services/routines.js +86 -1
  330. package/dist/services/routines.js.map +1 -1
  331. package/dist/services/runtime-exposure/loopback-listener.js +1 -1
  332. package/dist/services/runtime-exposure/loopback-listener.js.map +1 -1
  333. package/dist/services/runtime-exposure/loopback-listener.test.js +1 -1
  334. package/dist/services/runtime-exposure/loopback-listener.test.js.map +1 -1
  335. package/dist/services/scripts/codex-auth-read.cjs +93 -0
  336. package/dist/services/secret-proposal-authorization.d.ts +15 -0
  337. package/dist/services/secret-proposal-authorization.d.ts.map +1 -0
  338. package/dist/services/secret-proposal-authorization.js +29 -0
  339. package/dist/services/secret-proposal-authorization.js.map +1 -0
  340. package/dist/services/secret-proposal-notifications.d.ts +18 -0
  341. package/dist/services/secret-proposal-notifications.d.ts.map +1 -0
  342. package/dist/services/secret-proposal-notifications.js +37 -0
  343. package/dist/services/secret-proposal-notifications.js.map +1 -0
  344. package/dist/services/secret-proposals.d.ts +20 -10
  345. package/dist/services/secret-proposals.d.ts.map +1 -1
  346. package/dist/services/secret-proposals.js +205 -14
  347. package/dist/services/secret-proposals.js.map +1 -1
  348. package/dist/services/secrets.d.ts +3 -3
  349. package/dist/services/settings-visibility.d.ts +17 -0
  350. package/dist/services/settings-visibility.d.ts.map +1 -0
  351. package/dist/services/settings-visibility.js +29 -0
  352. package/dist/services/settings-visibility.js.map +1 -0
  353. package/dist/services/setup-token-reaper.d.ts +36 -0
  354. package/dist/services/setup-token-reaper.d.ts.map +1 -0
  355. package/dist/services/setup-token-reaper.js +38 -0
  356. package/dist/services/setup-token-reaper.js.map +1 -0
  357. package/dist/services/setup-token-reaper.test.d.ts +2 -0
  358. package/dist/services/setup-token-reaper.test.d.ts.map +1 -0
  359. package/dist/services/setup-token-reaper.test.js +123 -0
  360. package/dist/services/setup-token-reaper.test.js.map +1 -0
  361. package/dist/services/setup-token-session.d.ts +57 -45
  362. package/dist/services/setup-token-session.d.ts.map +1 -1
  363. package/dist/services/setup-token-session.js +118 -110
  364. package/dist/services/setup-token-session.js.map +1 -1
  365. package/dist/services/setup-token-session.test.js +88 -47
  366. package/dist/services/setup-token-session.test.js.map +1 -1
  367. package/dist/services/setup-token-transport-binding.d.ts +20 -12
  368. package/dist/services/setup-token-transport-binding.d.ts.map +1 -1
  369. package/dist/services/setup-token-transport-binding.js +59 -32
  370. package/dist/services/setup-token-transport-binding.js.map +1 -1
  371. package/dist/services/setup-token-transport-binding.test.js +50 -17
  372. package/dist/services/setup-token-transport-binding.test.js.map +1 -1
  373. package/dist/services/smoke-lab.d.ts +1 -1
  374. package/dist/services/stalled-review-decisions.d.ts +1 -1
  375. package/dist/services/status-cards.d.ts +168 -168
  376. package/dist/services/tool-access-policy.d.ts +2 -2
  377. package/dist/services/tool-gateway.d.ts +1 -1
  378. package/dist/services/work-products.d.ts +19 -0
  379. package/dist/services/work-products.d.ts.map +1 -1
  380. package/dist/services/work-products.js +50 -3
  381. package/dist/services/work-products.js.map +1 -1
  382. package/dist/services/workspace-git-operation-scheduler.test.js +10 -3
  383. package/dist/services/workspace-git-operation-scheduler.test.js.map +1 -1
  384. package/dist/services/workspace-instance-cleanup.d.ts +9 -0
  385. package/dist/services/workspace-instance-cleanup.d.ts.map +1 -1
  386. package/dist/services/workspace-instance-cleanup.js +29 -3
  387. package/dist/services/workspace-instance-cleanup.js.map +1 -1
  388. package/dist/services/workspace-login-handoff-issuer.d.ts +95 -0
  389. package/dist/services/workspace-login-handoff-issuer.d.ts.map +1 -0
  390. package/dist/services/workspace-login-handoff-issuer.js +147 -0
  391. package/dist/services/workspace-login-handoff-issuer.js.map +1 -0
  392. package/dist/services/workspace-operations.d.ts +4 -1
  393. package/dist/services/workspace-operations.d.ts.map +1 -1
  394. package/dist/services/workspace-operations.js +29 -15
  395. package/dist/services/workspace-operations.js.map +1 -1
  396. package/dist/services/workspace-readiness.d.ts +56 -0
  397. package/dist/services/workspace-readiness.d.ts.map +1 -0
  398. package/dist/services/workspace-readiness.js +250 -0
  399. package/dist/services/workspace-readiness.js.map +1 -0
  400. package/dist/services/workspace-realization.d.ts.map +1 -1
  401. package/dist/services/workspace-realization.js +5 -33
  402. package/dist/services/workspace-realization.js.map +1 -1
  403. package/dist/services/workspace-runtime-exposure.test.js +321 -12
  404. package/dist/services/workspace-runtime-exposure.test.js.map +1 -1
  405. package/dist/services/workspace-runtime-leases.d.ts.map +1 -1
  406. package/dist/services/workspace-runtime-leases.js +1 -1
  407. package/dist/services/workspace-runtime-leases.js.map +1 -1
  408. package/dist/services/workspace-runtime-read-model.d.ts +8 -8
  409. package/dist/services/workspace-runtime-read-model.test.js +20 -0
  410. package/dist/services/workspace-runtime-read-model.test.js.map +1 -1
  411. package/dist/services/workspace-runtime-ready-comment.test.js +1 -0
  412. package/dist/services/workspace-runtime-ready-comment.test.js.map +1 -1
  413. package/dist/services/workspace-runtime.d.ts +77 -4
  414. package/dist/services/workspace-runtime.d.ts.map +1 -1
  415. package/dist/services/workspace-runtime.js +1020 -126
  416. package/dist/services/workspace-runtime.js.map +1 -1
  417. package/dist/worktree-config.d.ts.map +1 -1
  418. package/dist/worktree-config.js +3 -76
  419. package/dist/worktree-config.js.map +1 -1
  420. package/dist/worktree-seed-manifest.d.ts +8 -0
  421. package/dist/worktree-seed-manifest.d.ts.map +1 -0
  422. package/dist/worktree-seed-manifest.js +47 -0
  423. package/dist/worktree-seed-manifest.js.map +1 -0
  424. package/package.json +34 -30
  425. package/skills/paperclip/SKILL.md +2 -0
  426. package/skills/paperclip/references/api-reference.md +44 -2
  427. package/ui-dist/assets/CompanyExport-DnEsO-St.js +4 -0
  428. package/ui-dist/assets/{abnfDiagram-N423BO3Z-BvgoV-u5.js → abnfDiagram-N423BO3Z-2ff8TsQ0.js} +1 -1
  429. package/ui-dist/assets/{arc-BInnUBUU.js → arc-B2AXsH34.js} +1 -1
  430. package/ui-dist/assets/architectureDiagram-T3A2C74G-BDxtKMxk.js +36 -0
  431. package/ui-dist/assets/{blockDiagram-VBNYF7ZC-D9kzAkLN.js → blockDiagram-VBNYF7ZC-BB8T5Mj5.js} +1 -1
  432. package/ui-dist/assets/{c4Diagram-5PPSVZJV-BwzsgV_0.js → c4Diagram-5PPSVZJV-lMo8qk-x.js} +1 -1
  433. package/ui-dist/assets/channel-DCTVZpyq.js +1 -0
  434. package/ui-dist/assets/{chunk-2GRJ4B5K-D2EQ9vrl.js → chunk-2GRJ4B5K-CsS7EB3G.js} +1 -1
  435. package/ui-dist/assets/{chunk-2Q5K7J3B-DOA_8fSd.js → chunk-2Q5K7J3B-DFyQPjoR.js} +1 -1
  436. package/ui-dist/assets/{chunk-5RXB4S5H-CT5ukRiT.js → chunk-5RXB4S5H-No2SHOQR.js} +1 -1
  437. package/ui-dist/assets/{chunk-5VM5RSS4-D2gOlkyB.js → chunk-5VM5RSS4-DdxQO7V9.js} +1 -1
  438. package/ui-dist/assets/{chunk-6Q2QTUOP-DUb6aG8c.js → chunk-6Q2QTUOP-gbHjh-aK.js} +1 -1
  439. package/ui-dist/assets/{chunk-GF5L2VYU-BPabG3Cx.js → chunk-GF5L2VYU-h-BBl6NY.js} +1 -1
  440. package/ui-dist/assets/{chunk-JWPE2WC7-scNfhlZz.js → chunk-JWPE2WC7-Q3pJdTz5.js} +1 -1
  441. package/ui-dist/assets/{chunk-KBJHAD2P-BcRAzRpq.js → chunk-KBJHAD2P-Ba8LGLSx.js} +1 -1
  442. package/ui-dist/assets/{chunk-RYQCIY6F-BiZ0-EGh.js → chunk-RYQCIY6F-9VcvykBo.js} +1 -1
  443. package/ui-dist/assets/{chunk-XXDRQBXY-lJULxFIE.js → chunk-XXDRQBXY-NVbjDH4W.js} +1 -1
  444. package/ui-dist/assets/classDiagram-JCYQIIEL-CzJ88u6h.js +1 -0
  445. package/ui-dist/assets/classDiagram-v2-OCEON4UE-CzJ88u6h.js +1 -0
  446. package/ui-dist/assets/cose-bilkent-JH36ORCC-Cdz4wtUh.js +1 -0
  447. package/ui-dist/assets/{cynefin-VYW2F7L2-B34jJY1x.js → cynefin-VYW2F7L2-MpzJkW5s.js} +1 -1
  448. package/ui-dist/assets/{cynefinDiagram-MW4NZA55-DQ8QK8JK.js → cynefinDiagram-MW4NZA55-_Nx8av7i.js} +1 -1
  449. package/ui-dist/assets/{dagre-VZM6K2ZE-B7lSKgxH.js → dagre-VZM6K2ZE-DcZTQ4Oh.js} +1 -1
  450. package/ui-dist/assets/{diagram-7IWD3JNH-B3hLAFGE.js → diagram-7IWD3JNH-UyLi2mee.js} +1 -1
  451. package/ui-dist/assets/{diagram-B4RE2ZJO-CH9Ro9e0.js → diagram-B4RE2ZJO-B6p0Ivqg.js} +1 -1
  452. package/ui-dist/assets/{diagram-LBJQPF4R-DdPVMii7.js → diagram-LBJQPF4R-C8u6pa97.js} +1 -1
  453. package/ui-dist/assets/{diagram-Q27KOJAE-f5VXswjc.js → diagram-Q27KOJAE-DbwyagW0.js} +1 -1
  454. package/ui-dist/assets/{diagram-UB23O5K3-DGqxixsR.js → diagram-UB23O5K3-Cn5BMEH7.js} +1 -1
  455. package/ui-dist/assets/{ebnfDiagram-BXEA7PRR-C-1klg_w.js → ebnfDiagram-BXEA7PRR-vE0IZma9.js} +1 -1
  456. package/ui-dist/assets/{erDiagram-JOGREHBK-Dt4qK4gv.js → erDiagram-JOGREHBK-3OZVIlVG.js} +1 -1
  457. package/ui-dist/assets/{flowDiagram-UKHOOZJN-9ZlrTuET.js → flowDiagram-UKHOOZJN-C94FiPyr.js} +1 -1
  458. package/ui-dist/assets/ganttDiagram-PKOTCBZU-DMVaPmF-.js +292 -0
  459. package/ui-dist/assets/{gitGraphDiagram-DS77QQ5N-HH2fSgad.js → gitGraphDiagram-DS77QQ5N-yeFtJ689.js} +1 -1
  460. package/ui-dist/assets/{index-Cm6iEYPt.js → index-B4ZF3vY8.js} +288 -224
  461. package/ui-dist/assets/{index-CHXG4GPL.js → index-Bi_vqkcQ.js} +1 -1
  462. package/ui-dist/assets/{index-Bc9g6q63.js → index-BjDGQg9-.js} +1 -1
  463. package/ui-dist/assets/{index-FHzrJ7iQ.js → index-BnpyUMTb.js} +1 -1
  464. package/ui-dist/assets/{index-C1mpWCcn.js → index-BoqQTVMA.js} +1 -1
  465. package/ui-dist/assets/index-Bq-Mr-4x.js +1 -0
  466. package/ui-dist/assets/{index-DceuCrr8.js → index-Brb2U7N4.js} +1 -1
  467. package/ui-dist/assets/{index-DQIrrn2L.js → index-BsBybVwH.js} +1 -1
  468. package/ui-dist/assets/{index-CQhaM6qd.js → index-C2iv4Wgu.js} +1 -1
  469. package/ui-dist/assets/{index-DV2FOgyD.js → index-CKxIu7uO.js} +1 -1
  470. package/ui-dist/assets/{index-DU4U1fuv.js → index-Cb4VGIwp.js} +1 -1
  471. package/ui-dist/assets/{index-B61ORkIb.js → index-ChpszwcO.js} +1 -1
  472. package/ui-dist/assets/{index-DlVBBF8o.js → index-CoQFdT8N.js} +1 -1
  473. package/ui-dist/assets/index-D6P8_f0W.css +1 -0
  474. package/ui-dist/assets/index-DKdmghUG.js +1 -0
  475. package/ui-dist/assets/{index-CVTL3aAL.js → index-DaBci7Bx.js} +1 -1
  476. package/ui-dist/assets/{index-diFs0CNC.js → index-DjYaNiXZ.js} +1 -1
  477. package/ui-dist/assets/{index-Bf7tyGxE.js → index-DxTBJl4h.js} +1 -1
  478. package/ui-dist/assets/{index-DsdVneqe.js → index-Dz3E8nn0.js} +1 -1
  479. package/ui-dist/assets/{index-DvFUXI-Q.js → index-J7dz_hap.js} +1 -1
  480. package/ui-dist/assets/{index-CLu3Tfpv.js → index-JMp2Gpnz.js} +1 -1
  481. package/ui-dist/assets/{index-CcRTJ93J.js → index-Kp6wavQt.js} +1 -1
  482. package/ui-dist/assets/{index-BvFx-4KW.js → index-LBhYZ0L4.js} +1 -1
  483. package/ui-dist/assets/{index-BbAhzCq8.js → index-c3C0P6l8.js} +4 -4
  484. package/ui-dist/assets/{index-BY5ITKmG.js → index-pBEPheTW.js} +1 -1
  485. package/ui-dist/assets/{infoDiagram-6WML65LV-2jai2fw-.js → infoDiagram-6WML65LV-BiL7jKLA.js} +1 -1
  486. package/ui-dist/assets/{ishikawaDiagram-WSZJBQD7-CUmGhmEb.js → ishikawaDiagram-WSZJBQD7-DYcQy41D.js} +1 -1
  487. package/ui-dist/assets/{journeyDiagram-NVQOT4AX-CGQuPDO1.js → journeyDiagram-NVQOT4AX-Bxva6JgA.js} +1 -1
  488. package/ui-dist/assets/{kanban-definition-27J2QSJJ-zgEPcbHl.js → kanban-definition-27J2QSJJ-B2HpN34Q.js} +1 -1
  489. package/ui-dist/assets/{linear-BVf5VtLS.js → linear-Dq8qSESw.js} +1 -1
  490. package/ui-dist/assets/{mermaid.core-D2TFkGfI.js → mermaid.core-Ikay8Cpl.js} +35 -35
  491. package/ui-dist/assets/{mindmap-definition-FAOFIHXS-xoAQ0Lb_.js → mindmap-definition-FAOFIHXS-C-SIdYSY.js} +1 -1
  492. package/ui-dist/assets/{pegDiagram-VL7TDLO6-CBR1PWLr.js → pegDiagram-VL7TDLO6-C7rIJ_13.js} +1 -1
  493. package/ui-dist/assets/{pieDiagram-7S7Q4E2Y-BMPnzK_e.js → pieDiagram-7S7Q4E2Y-3gx0a0ve.js} +1 -1
  494. package/ui-dist/assets/{quadrantDiagram-CIZ2JOQS-CPtVUPkR.js → quadrantDiagram-CIZ2JOQS-BiwM9QGv.js} +1 -1
  495. package/ui-dist/assets/{railroadDiagram-AXF67PYL-DC0lsZV1.js → railroadDiagram-AXF67PYL-rFS8XcFA.js} +1 -1
  496. package/ui-dist/assets/{requirementDiagram-LRYGKXZP-BF6k05SB.js → requirementDiagram-LRYGKXZP-CEAUDUTQ.js} +1 -1
  497. package/ui-dist/assets/{sankeyDiagram-W5VNT64P-CRN0ibiP.js → sankeyDiagram-W5VNT64P-B7anpjlM.js} +1 -1
  498. package/ui-dist/assets/{sequenceDiagram-SI44F4Z6-CQtTiPWm.js → sequenceDiagram-SI44F4Z6-CJEdJvm_.js} +1 -1
  499. package/ui-dist/assets/{sizeCapture-X5ZJPWSS-BdjuGZd1.js → sizeCapture-X5ZJPWSS-CU1Vm6c1.js} +1 -1
  500. package/ui-dist/assets/{stateDiagram-OKZ733FA-B99onxyh.js → stateDiagram-OKZ733FA-wxVV9WIi.js} +1 -1
  501. package/ui-dist/assets/stateDiagram-v2-UEYNNEHI-BIFLqlqR.js +1 -0
  502. package/ui-dist/assets/{swimlanes-SLNWSIFB-BIByPbrL.js → swimlanes-SLNWSIFB-Cd-C1Z_k.js} +2 -2
  503. package/ui-dist/assets/swimlanesDiagram-ULZ7WXOC-CoBROkkd.js +8 -0
  504. package/ui-dist/assets/{timeline-definition-Z64GVDOM-I1NgOcn9.js → timeline-definition-Z64GVDOM-SWngk4aR.js} +1 -1
  505. package/ui-dist/assets/{vennDiagram-T6HMQDX7-D6t2Njcl.js → vennDiagram-T6HMQDX7-DFdPUr36.js} +1 -1
  506. package/ui-dist/assets/{wardleyDiagram-T6FBY63Y-BiYKm9gh.js → wardleyDiagram-T6FBY63Y-C6IlrZ7t.js} +1 -1
  507. package/ui-dist/assets/{xychartDiagram-ELKLHX3M-BhwXKWIa.js → xychartDiagram-ELKLHX3M-BNm6hHGl.js} +1 -1
  508. package/ui-dist/index.html +2 -2
  509. package/ui-dist/assets/architectureDiagram-T3A2C74G-Dg4MziOm.js +0 -36
  510. package/ui-dist/assets/channel-qoSYbCn0.js +0 -1
  511. package/ui-dist/assets/classDiagram-JCYQIIEL-B24u1REs.js +0 -1
  512. package/ui-dist/assets/classDiagram-v2-OCEON4UE-B24u1REs.js +0 -1
  513. package/ui-dist/assets/cose-bilkent-JH36ORCC-Do8pe8JB.js +0 -1
  514. package/ui-dist/assets/ganttDiagram-PKOTCBZU-CE3lyvFq.js +0 -292
  515. package/ui-dist/assets/index-BHiUiToL.css +0 -1
  516. package/ui-dist/assets/index-hkanj4t6.js +0 -1
  517. package/ui-dist/assets/index-yRJnh4vx.js +0 -1
  518. package/ui-dist/assets/stateDiagram-v2-UEYNNEHI-bzFlYBZ1.js +0 -1
  519. package/ui-dist/assets/swimlanesDiagram-ULZ7WXOC-DgdkBnuK.js +0 -8
@@ -1,10 +1,11 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
- import { and, eq, inArray } from "drizzle-orm";
3
- import { companySecrets, companySecretVersions, environmentLeases } from "@paperclipai/db";
2
+ import { and, eq, inArray, sql } from "drizzle-orm";
3
+ import { companySecrets, companySecretVersions, environmentLeases, heartbeatRuns } from "@paperclipai/db";
4
4
  import { resolveDeclaredSandboxCapabilities } from "@paperclipai/shared";
5
5
  import { ensureSshWorkspaceReady } from "@paperclipai/adapter-utils/ssh";
6
6
  import { getActiveStepContext, runWithRuntimeParent, } from "@paperclipai/adapter-utils/acpx-engine/startup-timing";
7
7
  import { environmentService } from "./environments.js";
8
+ import { instanceSettingsService } from "./instance-settings.js";
8
9
  import { collectEnvironmentSecretRefs, parseEnvironmentDriverConfig, resolveEnvironmentDriverConfigForRuntime, resolveSandboxCleanupConfigSecrets, stripSandboxProviderEnvelope, } from "./environment-config.js";
9
10
  import { createEffectiveRunConfigFingerprints, } from "./effective-run-config-fingerprints.js";
10
11
  import { acquireSandboxProviderLease, destroySandboxProviderLease, findReusableSandboxProviderLeaseId, getSandboxProvider as getBuiltinSandboxProvider, isBuiltinSandboxProvider, releaseSandboxProviderLease, sandboxConfigFromLeaseMetadata, sandboxConfigFromLeaseMetadataLoose, } from "./sandbox-provider-runtime.js";
@@ -19,6 +20,10 @@ import { logger } from "../middleware/logger.js";
19
20
  // credential in its name, code, message, cause, or stack. So the log records
20
21
  // this constant and the plain provider identifiers only.
21
22
  const SANDBOX_ORPHAN_CLEANUP_WRITE_ERROR_KIND = "sandbox_orphan_cleanup_write_failed";
23
+ // The fixed non-secret refusal a duplex channel open returns when the lease does
24
+ // not grant the opt-in `duplexCommandStream` capability. The service resolves the
25
+ // exact lease capability snapshot and throws this before it reaches any driver.
26
+ const DUPLEX_CHANNEL_CAPABILITY_DENIED = "Sandbox lease does not grant the duplex command stream capability.";
22
27
  // ---------------------------------------------------------------------------
23
28
  // Sandbox capability contract — one normalizer for both branches
24
29
  // ---------------------------------------------------------------------------
@@ -29,6 +34,8 @@ export const SANDBOX_CAPABILITY_KEYS = [
29
34
  "persistentProcessSessions",
30
35
  "independentControlCommands",
31
36
  "incrementalSessionOutput",
37
+ "concurrentSyncOperations",
38
+ "duplexCommandStream",
32
39
  ];
33
40
  /**
34
41
  * Opt-in capability keys. Most capabilities are a worker property: an absent
@@ -41,6 +48,8 @@ export const SANDBOX_CAPABILITY_KEYS = [
41
48
  */
42
49
  const SANDBOX_CAPABILITY_OPT_IN_KEYS = new Set([
43
50
  "incrementalSessionOutput",
51
+ "concurrentSyncOperations",
52
+ "duplexCommandStream",
44
53
  ]);
45
54
  /**
46
55
  * Verified prerequisite mapping: the worker methods each capability requires.
@@ -70,6 +79,17 @@ const SANDBOX_CAPABILITY_OPT_IN_KEYS = new Set([
70
79
  * provider tails the session log through it. The verified verb is necessary
71
80
  * but not sufficient: this key is opt-in, so the declaration is the real gate
72
81
  * (see {@link SANDBOX_CAPABILITY_OPT_IN_KEYS}).
82
+ * - `concurrentSyncOperations` requires BOTH sync verbs, because parallel
83
+ * bidirectional transfer runs an inbound and an outbound transfer at the same
84
+ * time. The two verbs are separate required groups, so a provider that
85
+ * verifies only one direction cannot get the capability. The verbs are
86
+ * necessary but not sufficient: this key is opt-in, so the declaration is the
87
+ * real gate (see {@link SANDBOX_CAPABILITY_OPT_IN_KEYS}).
88
+ * - `duplexCommandStream` requires `duplexChannelOpen`, the worker verb that
89
+ * opens the persistent duplex channel. The verified verb is necessary but not
90
+ * sufficient: this key is opt-in, so the declaration is the real gate. A
91
+ * provider that does not implement the duplex open verb resolves `false` and
92
+ * keeps the file bridge.
73
93
  */
74
94
  const SANDBOX_CAPABILITY_PREREQUISITE_METHODS = {
75
95
  // Reusable leases require ALL reuse verbs. Each verb is its own required
@@ -83,6 +103,8 @@ const SANDBOX_CAPABILITY_PREREQUISITE_METHODS = {
83
103
  persistentProcessSessions: [["environmentExecute"]],
84
104
  independentControlCommands: [["environmentExecute"]],
85
105
  incrementalSessionOutput: [["environmentExecute"]],
106
+ concurrentSyncOperations: [["environmentSyncIn"], ["environmentSyncOut"]],
107
+ duplexCommandStream: [["duplexChannelOpen"]],
86
108
  };
87
109
  function capabilityIsVerified(key, verifiedMethods) {
88
110
  return SANDBOX_CAPABILITY_PREREQUISITE_METHODS[key].every((group) => group.some((verb) => verifiedMethods.has(verb)));
@@ -108,28 +130,49 @@ export function builtinSandboxProviderVerifiedMethods(provider) {
108
130
  }
109
131
  return methods;
110
132
  }
133
+ // `EnvironmentDriverCapabilitySupport` and `ENVIRONMENT_DRIVER_CAPABILITY_SUPPORT`
134
+ // now live in the dependency-leaf module `environment-driver-traits.ts`, next to
135
+ // the other static per-driver traits. Re-export both names here so an existing
136
+ // import of this module keeps resolving.
137
+ export { ENVIRONMENT_DRIVER_CAPABILITY_SUPPORT, } from "./environment-driver-traits.js";
138
+ import { ENVIRONMENT_DRIVER_CAPABILITY_SUPPORT } from "./environment-driver-traits.js";
111
139
  /**
112
- * The one normalizer for the sandbox capability contract. It resolves the
113
- * effective capability as verified ∩ declared ∩ narrowing.
140
+ * The one general capability classifier. It resolves each of the eight effective
141
+ * capabilities as static support ∩ verified ∩ declared ∩ narrowing, and returns
142
+ * the read-only eight-field snapshot. Every driver reads the same classifier, so
143
+ * the runtime no longer branches on the driver name.
114
144
  *
145
+ * - `supportedCapabilities` is the driver's static support definition (see
146
+ * {@link ENVIRONMENT_DRIVER_CAPABILITY_SUPPORT}). A capability not in the set
147
+ * resolves `false` regardless of the other inputs. An absent set applies no
148
+ * static gate, so the classifier behaves as the pure declaration ∩ verified ∩
149
+ * narrowing normalizer.
115
150
  * - `verifiedMethods` is the runtime's verified worker verb list (the plug-in
116
151
  * worker's `supportedMethods`, or a built-in provider mapped through
117
152
  * {@link builtinSandboxProviderVerifiedMethods}). A missing or empty list
118
153
  * verifies nothing, so every capability resolves `false` (fail closed).
119
154
  * - `declared` is the provider's declaration. An absent flag defers to the
120
- * verified discovery baseline; it never grants a capability. A present flag
121
- * can only remove a verified capability, never add one.
122
- * - `narrowing` is the per-target restriction from the config or lease. An
123
- * absent key applies no restriction; a `false` value removes the capability.
155
+ * verified discovery baseline for a worker-property capability; it never grants
156
+ * an opt-in capability. A present flag can only remove a capability.
157
+ * - `narrowing` is the per-target restriction from the config or lease. An absent
158
+ * key applies no restriction; a `false` value removes the capability.
124
159
  *
125
- * A declaration never grants a capability the runtime did not verify, so a
126
- * declared capability whose worker lacks a prerequisite verb resolves `false`.
160
+ * The two default rules stay intact. An absent declaration defers to the
161
+ * verified baseline for a worker-property capability. The three opt-in
162
+ * capabilities deny by default (see {@link SANDBOX_CAPABILITY_OPT_IN_KEYS}). A
163
+ * declaration never grants a capability the runtime did not verify.
127
164
  */
128
- export function resolveEffectiveSandboxCapabilities(input) {
165
+ export function classifyEnvironmentCapabilities(input) {
129
166
  const verifiedMethods = new Set(input.verifiedMethods ?? []);
130
167
  const declared = input.declared ?? {};
131
168
  const narrowing = input.narrowing ?? {};
169
+ const supportedCapabilities = input.supportedCapabilities ?? null;
132
170
  const resolve = (key) => {
171
+ // The static support definition is the first gate. A capability the driver
172
+ // family cannot support resolves false, whatever the other inputs say. An
173
+ // absent set applies no static gate.
174
+ if (supportedCapabilities && !supportedCapabilities.has(key))
175
+ return false;
133
176
  const verified = capabilityIsVerified(key, verifiedMethods);
134
177
  // An absent declaration defers to the verified baseline for a worker-property
135
178
  // capability (true = no extra restriction), but denies an opt-in capability
@@ -147,6 +190,8 @@ export function resolveEffectiveSandboxCapabilities(input) {
147
190
  persistentProcessSessions: resolve("persistentProcessSessions"),
148
191
  independentControlCommands: resolve("independentControlCommands"),
149
192
  incrementalSessionOutput: resolve("incrementalSessionOutput"),
193
+ concurrentSyncOperations: resolve("concurrentSyncOperations"),
194
+ duplexCommandStream: resolve("duplexCommandStream"),
150
195
  };
151
196
  }
152
197
  /**
@@ -160,9 +205,9 @@ export function resolveEffectiveSandboxCapabilities(input) {
160
205
  * which falls back for a `job` backend or a `nativeFileSyncUnsupported` lease).
161
206
  * - `configResolutionFailed` marks that the runtime could not resolve the
162
207
  * provider config. A provider whose config cannot be resolved is untrusted, so
163
- * the runtime fails closed and narrows `persistentProcessSessions` and
164
- * `incrementalSessionOutput` to false. An empty config alone does not fail
165
- * closed; only a resolution error does.
208
+ * the runtime fails closed and narrows `persistentProcessSessions`,
209
+ * `incrementalSessionOutput`, and `duplexCommandStream` to false. An empty
210
+ * config alone does not fail closed; only a resolution error does.
166
211
  */
167
212
  export function buildSandboxCapabilityNarrowing(input) {
168
213
  const narrowing = {};
@@ -174,11 +219,15 @@ export function buildSandboxCapabilityNarrowing(input) {
174
219
  }
175
220
  if (input.configResolutionFailed === true) {
176
221
  // The runtime could not resolve the provider config, so it fails closed and
177
- // denies persistent process sessions and incremental session output. The
178
- // session-output streaming gate reads `incrementalSessionOutput`, so it must
179
- // narrow with the persistent-session gate to keep the fail-closed behavior.
222
+ // denies persistent process sessions, incremental session output, and the
223
+ // duplex command stream. The session-output streaming gate reads
224
+ // `incrementalSessionOutput`, so it must narrow with the persistent-session
225
+ // gate to keep the fail-closed behavior. The duplex command stream opens a
226
+ // host-owned bidirectional channel, so an untrusted provider must not keep
227
+ // it either.
180
228
  narrowing.persistentProcessSessions = false;
181
229
  narrowing.incrementalSessionOutput = false;
230
+ narrowing.duplexCommandStream = false;
182
231
  }
183
232
  return narrowing;
184
233
  }
@@ -188,6 +237,14 @@ export function buildEnvironmentLeaseContext(input) {
188
237
  executionWorkspaceMode: input.persistedExecutionWorkspace?.mode ?? null,
189
238
  };
190
239
  }
240
+ /**
241
+ * Map the experimental instance setting `enableSandboxDuplexBridge` into the
242
+ * per-run duplex bridge input. The setting is the kill switch. It defaults off,
243
+ * so an absent or false setting keeps the file bridge.
244
+ */
245
+ export function resolveSandboxDuplexBridgeInput(experimental) {
246
+ return { enableDuplexBridge: experimental.enableSandboxDuplexBridge === true };
247
+ }
191
248
  function stripSecretRefValuesFromPluginLeaseMetadata(input) {
192
249
  const sanitized = structuredClone(input.metadata ?? {});
193
250
  for (const path of collectSecretRefPaths(input.schema)) {
@@ -537,6 +594,15 @@ function createLocalEnvironmentDriver(db) {
537
594
  },
538
595
  };
539
596
  },
597
+ async resolveCapabilities() {
598
+ // The local driver runs commands on the host file system with no
599
+ // provider capability model, so its static support definition names
600
+ // none of the eight capabilities. The classifier resolves every field
601
+ // `false` regardless of the other inputs, so this method needs none.
602
+ return classifyEnvironmentCapabilities({
603
+ supportedCapabilities: ENVIRONMENT_DRIVER_CAPABILITY_SUPPORT.local.supportedCapabilities,
604
+ });
605
+ },
540
606
  };
541
607
  }
542
608
  function createSshEnvironmentDriver(db) {
@@ -593,6 +659,45 @@ function createSshEnvironmentDriver(db) {
593
659
  },
594
660
  };
595
661
  },
662
+ async resolveCapabilities() {
663
+ // The SSH driver runs commands on a remote host through the SSH
664
+ // transport with no provider capability model, so it supports none of
665
+ // the eight capabilities either.
666
+ return classifyEnvironmentCapabilities({
667
+ supportedCapabilities: ENVIRONMENT_DRIVER_CAPABILITY_SUPPORT.ssh.supportedCapabilities,
668
+ });
669
+ },
670
+ };
671
+ }
672
+ /**
673
+ * Adapt the worker manager's duplex host session to the cross-layer channel. The
674
+ * two shapes differ in the exit and stop members: the host session resolves the
675
+ * exit one time from `wait()`, so the channel bridges it to a one-time `onExit`
676
+ * listener; `kill()` maps to `stop()`. The `write`, `onData`, and `close`
677
+ * members map one to one.
678
+ */
679
+ function adaptDuplexChannelHostSession(session) {
680
+ return {
681
+ write(data) {
682
+ session.write(data);
683
+ },
684
+ onData(listener) {
685
+ session.onData(listener);
686
+ },
687
+ onExit(listener) {
688
+ // `wait()` resolves one time with the exit and never rejects, so a single
689
+ // `then` bridges it to the one-time exit listener. The exit carries
690
+ // `transportClosed`, so the broker tells a real exit from a transport close.
691
+ void session.wait().then((exit) => {
692
+ listener(exit);
693
+ });
694
+ },
695
+ stop() {
696
+ session.kill();
697
+ },
698
+ close() {
699
+ return session.close();
700
+ },
596
701
  };
597
702
  }
598
703
  function createSandboxEnvironmentDriver(db, options = {}) {
@@ -1794,80 +1899,36 @@ function createSandboxEnvironmentDriver(db, options = {}) {
1794
1899
  async syncOut(input) {
1795
1900
  return await callPluginEnvironmentSync("environmentSyncOut", input);
1796
1901
  },
1797
- async effectiveSandboxCapabilities(input) {
1798
- const metadata = input.lease.metadata ?? {};
1799
- const providerKey = readString(metadata.provider) ??
1800
- (input.environment.driver === "sandbox"
1801
- ? readString(parseEnvironmentDriverConfig(input.environment).config.provider)
1802
- : null);
1803
- if (!providerKey) {
1804
- return resolveEffectiveSandboxCapabilities({ verifiedMethods: [], declared: null, narrowing: null });
1805
- }
1806
- let declared = null;
1807
- let verifiedMethods = [];
1808
- let configResolutionFailed = false;
1809
- if (metadata.sandboxProviderPlugin) {
1810
- const pluginId = readString(metadata.pluginId);
1811
- // Read the declaration from the exact plugin that acquired the lease,
1812
- // not the first installed plugin with this driver key. A driver key is
1813
- // only unique inside one manifest, so two plugins can share it. The
1814
- // by-key resolver could intersect this lease's verified methods with a
1815
- // different plugin's declaration. This resolver fails closed: it returns
1816
- // null when the pinned plugin id is absent, or when that exact plugin no
1817
- // longer declares this provider key with the `sandbox_provider` kind.
1818
- const resolvedDriver = pluginId
1819
- ? await resolvePluginSandboxProviderDriverById({
1820
- db,
1821
- pluginId,
1822
- driverKey: providerKey,
1823
- })
1824
- : null;
1825
- if (!pluginId || !resolvedDriver) {
1826
- // Exact-plugin identity failure. The lease pins a plugin id, but the
1827
- // id is missing, that plugin is absent, or it no longer declares this
1828
- // provider key. Fail closed: resolve every effective capability to
1829
- // false, no matter what methods a stale or running worker still
1830
- // advertises. Do not read the worker methods here; passing them would
1831
- // let an identity-less lease keep a verified baseline. This differs
1832
- // from a valid plugin whose manifest merely omits
1833
- // `sandboxCapabilities`: that case keeps `declared` null below and
1834
- // defers to verified worker discovery.
1835
- return resolveEffectiveSandboxCapabilities({
1836
- verifiedMethods: [],
1837
- declared: null,
1838
- narrowing: null,
1839
- });
1840
- }
1841
- verifiedMethods = pluginWorkerManager?.getWorker(pluginId)?.supportedMethods ?? [];
1842
- declared = resolveDeclaredSandboxCapabilities(resolvedDriver.driver);
1843
- try {
1844
- // Resolve the provider config to confirm it is readable. A provider
1845
- // whose config cannot be resolved is untrusted, so a resolution error
1846
- // fails closed on persistent process sessions below. The resolved
1847
- // value itself is no longer read for the narrowing decision.
1848
- await resolvePluginSandboxRuntimeConfig({
1849
- environment: input.environment,
1850
- lease: input.lease,
1851
- provider: providerKey,
1852
- });
1853
- }
1854
- catch {
1855
- configResolutionFailed = true;
1856
- }
1902
+ async openDuplexChannel(input) {
1903
+ // Plugin-backed sandbox providers only: open the host-owned duplex route on
1904
+ // the plugin worker. The lease scope mirrors the sandbox execute path — the
1905
+ // provider driver key, the company, the environment, and the provider lease
1906
+ // id — so the route binds to the same worker session the runner streams.
1907
+ if (!input.lease.metadata?.sandboxProviderPlugin || !pluginWorkerManager) {
1908
+ throw new Error("Sandbox driver does not support duplex channels for this lease.");
1857
1909
  }
1858
- else {
1859
- const builtin = getBuiltinSandboxProvider(providerKey);
1860
- verifiedMethods = builtinSandboxProviderVerifiedMethods(builtin);
1861
- declared = resolveDeclaredSandboxCapabilities({
1862
- supportsReusableLeases: builtin?.supportsReusableLeases,
1863
- });
1910
+ const pluginId = readString(input.lease.metadata?.pluginId);
1911
+ const providerKey = readString(input.lease.metadata?.provider);
1912
+ const providerLeaseId = readString(input.lease.providerLeaseId);
1913
+ if (!pluginId || !providerKey || !providerLeaseId) {
1914
+ throw new Error("Sandbox duplex channel needs a plugin id, a provider key, and a provider lease id on the lease.");
1864
1915
  }
1865
- const narrowing = buildSandboxCapabilityNarrowing({
1866
- leasePolicy: input.lease.leasePolicy,
1867
- leaseMetadata: metadata,
1868
- configResolutionFailed,
1869
- });
1870
- return resolveEffectiveSandboxCapabilities({ verifiedMethods, declared, narrowing });
1916
+ const worker = pluginWorkerManager.getWorker(pluginId);
1917
+ if (!worker) {
1918
+ throw new Error(`Plugin worker "${pluginId}" is not running for the duplex channel.`);
1919
+ }
1920
+ const managerInput = {
1921
+ driverKey: providerKey,
1922
+ companyId: input.lease.companyId,
1923
+ environmentId: input.environment.id,
1924
+ providerLeaseId,
1925
+ command: input.command,
1926
+ };
1927
+ const session = await worker.openDuplexChannel(managerInput);
1928
+ return adaptDuplexChannelHostSession(session);
1929
+ },
1930
+ async resolveCapabilities(input) {
1931
+ return await resolveSandboxCapabilitiesForLease(input);
1871
1932
  },
1872
1933
  async destroyRunLease(input) {
1873
1934
  return await destroyReusableSandboxLease({
@@ -1877,6 +1938,102 @@ function createSandboxEnvironmentDriver(db, options = {}) {
1877
1938
  });
1878
1939
  },
1879
1940
  };
1941
+ /**
1942
+ * Resolve the effective capability snapshot for one sandbox lease. This is
1943
+ * the sandbox driver's implementation of the general capability-resolution
1944
+ * method: it gates through {@link ENVIRONMENT_DRIVER_CAPABILITY_SUPPORT}'s
1945
+ * `sandbox` support (the whole set, so this gate adds no restriction beyond
1946
+ * declaration, verification, and narrowing), then reads the per-lease
1947
+ * declaration, the live verified worker methods, and the per-lease
1948
+ * narrowing, exactly as the runtime resolved them before this method
1949
+ * existed.
1950
+ */
1951
+ async function resolveSandboxCapabilitiesForLease(input) {
1952
+ const metadata = input.lease.metadata ?? {};
1953
+ const providerKey = readString(metadata.provider) ??
1954
+ (input.environment.driver === "sandbox"
1955
+ ? readString(parseEnvironmentDriverConfig(input.environment).config.provider)
1956
+ : null);
1957
+ if (!providerKey) {
1958
+ return classifyEnvironmentCapabilities({
1959
+ verifiedMethods: [],
1960
+ declared: null,
1961
+ narrowing: null,
1962
+ supportedCapabilities: ENVIRONMENT_DRIVER_CAPABILITY_SUPPORT.sandbox.supportedCapabilities,
1963
+ });
1964
+ }
1965
+ let declared = null;
1966
+ let verifiedMethods = [];
1967
+ let configResolutionFailed = false;
1968
+ if (metadata.sandboxProviderPlugin) {
1969
+ const pluginId = readString(metadata.pluginId);
1970
+ // Read the declaration from the exact plugin that acquired the lease,
1971
+ // not the first installed plugin with this driver key. A driver key is
1972
+ // only unique inside one manifest, so two plugins can share it. The
1973
+ // by-key resolver could intersect this lease's verified methods with a
1974
+ // different plugin's declaration. This resolver fails closed: it returns
1975
+ // null when the pinned plugin id is absent, or when that exact plugin no
1976
+ // longer declares this provider key with the `sandbox_provider` kind.
1977
+ const resolvedDriver = pluginId
1978
+ ? await resolvePluginSandboxProviderDriverById({
1979
+ db,
1980
+ pluginId,
1981
+ driverKey: providerKey,
1982
+ })
1983
+ : null;
1984
+ if (!pluginId || !resolvedDriver) {
1985
+ // Exact-plugin identity failure. The lease pins a plugin id, but the
1986
+ // id is missing, that plugin is absent, or it no longer declares this
1987
+ // provider key. Fail closed: resolve every effective capability to
1988
+ // false, no matter what methods a stale or running worker still
1989
+ // advertises. Do not read the worker methods here; passing them would
1990
+ // let an identity-less lease keep a verified baseline. This differs
1991
+ // from a valid plugin whose manifest merely omits
1992
+ // `sandboxCapabilities`: that case keeps `declared` null below and
1993
+ // defers to verified worker discovery.
1994
+ return classifyEnvironmentCapabilities({
1995
+ verifiedMethods: [],
1996
+ declared: null,
1997
+ narrowing: null,
1998
+ supportedCapabilities: ENVIRONMENT_DRIVER_CAPABILITY_SUPPORT.sandbox.supportedCapabilities,
1999
+ });
2000
+ }
2001
+ verifiedMethods = pluginWorkerManager?.getWorker(pluginId)?.supportedMethods ?? [];
2002
+ declared = resolveDeclaredSandboxCapabilities(resolvedDriver.driver);
2003
+ try {
2004
+ // Resolve the provider config to confirm it is readable. A provider
2005
+ // whose config cannot be resolved is untrusted, so a resolution error
2006
+ // fails closed on persistent process sessions below. The resolved
2007
+ // value itself is no longer read for the narrowing decision.
2008
+ await resolvePluginSandboxRuntimeConfig({
2009
+ environment: input.environment,
2010
+ lease: input.lease,
2011
+ provider: providerKey,
2012
+ });
2013
+ }
2014
+ catch {
2015
+ configResolutionFailed = true;
2016
+ }
2017
+ }
2018
+ else {
2019
+ const builtin = getBuiltinSandboxProvider(providerKey);
2020
+ verifiedMethods = builtinSandboxProviderVerifiedMethods(builtin);
2021
+ declared = resolveDeclaredSandboxCapabilities({
2022
+ supportsReusableLeases: builtin?.supportsReusableLeases,
2023
+ });
2024
+ }
2025
+ const narrowing = buildSandboxCapabilityNarrowing({
2026
+ leasePolicy: input.lease.leasePolicy,
2027
+ leaseMetadata: metadata,
2028
+ configResolutionFailed,
2029
+ });
2030
+ return classifyEnvironmentCapabilities({
2031
+ verifiedMethods,
2032
+ declared,
2033
+ narrowing,
2034
+ supportedCapabilities: ENVIRONMENT_DRIVER_CAPABILITY_SUPPORT.sandbox.supportedCapabilities,
2035
+ });
2036
+ }
1880
2037
  /**
1881
2038
  * Verify that the live plugin worker still advertises a reusable-lease
1882
2039
  * lifecycle method before the runtime dispatches that RPC. The worker reports
@@ -2326,6 +2483,50 @@ function createPluginEnvironmentDriver(db, workerManager) {
2326
2483
  },
2327
2484
  });
2328
2485
  },
2486
+ async resolveCapabilities(input) {
2487
+ const metadata = input.lease.metadata ?? {};
2488
+ const pluginId = readString(metadata.pluginId);
2489
+ const driverKey = readString(metadata.driverKey);
2490
+ const noCapabilities = () => classifyEnvironmentCapabilities({
2491
+ verifiedMethods: [],
2492
+ declared: null,
2493
+ narrowing: null,
2494
+ supportedCapabilities: ENVIRONMENT_DRIVER_CAPABILITY_SUPPORT.plugin.supportedCapabilities,
2495
+ });
2496
+ if (!pluginId || !driverKey) {
2497
+ // The lease carries no plugin pin, so there is no exact plugin to read
2498
+ // the declaration or the live worker methods from. Fail closed: resolve
2499
+ // every capability false.
2500
+ return noCapabilities();
2501
+ }
2502
+ // Read the declaration from the exact plugin that acquired the lease, not
2503
+ // the plugin the environment's current config names. A plugin uninstall,
2504
+ // a manifest change, or a config edit between the acquire and this call
2505
+ // must not let a stale declaration or a different plugin's worker grant a
2506
+ // capability this lease never verified.
2507
+ const plugin = await pluginRegistry.getById(pluginId);
2508
+ const declaredDriver = plugin && plugin.status === "ready"
2509
+ ? plugin.manifestJson.environmentDrivers?.find((candidate) => candidate.driverKey === driverKey)
2510
+ : undefined;
2511
+ if (!plugin || plugin.status !== "ready" || !declaredDriver) {
2512
+ return noCapabilities();
2513
+ }
2514
+ // Read the live worker method list fresh on every call. A worker restart
2515
+ // can drop or add a method between the acquire and this call, so the
2516
+ // runtime never trusts a cached method list for a capability grant.
2517
+ const verifiedMethods = workerManager.getWorker(plugin.id)?.supportedMethods ?? [];
2518
+ const declared = resolveDeclaredSandboxCapabilities(declaredDriver);
2519
+ const narrowing = buildSandboxCapabilityNarrowing({
2520
+ leasePolicy: input.lease.leasePolicy,
2521
+ leaseMetadata: metadata,
2522
+ });
2523
+ return classifyEnvironmentCapabilities({
2524
+ verifiedMethods,
2525
+ declared,
2526
+ narrowing,
2527
+ supportedCapabilities: ENVIRONMENT_DRIVER_CAPABILITY_SUPPORT.plugin.supportedCapabilities,
2528
+ });
2529
+ },
2329
2530
  };
2330
2531
  }
2331
2532
  export function environmentRuntimeService(db, options = {}) {
@@ -2370,6 +2571,16 @@ export function environmentRuntimeService(db, options = {}) {
2370
2571
  }
2371
2572
  return {
2372
2573
  getDriver,
2574
+ /**
2575
+ * Read the sandbox duplex bridge kill switch for a new run. The host calls it
2576
+ * per run before it selects the callback bridge transport. It reads the
2577
+ * experimental instance setting `enableSandboxDuplexBridge` and maps it into
2578
+ * the resolved bridge input. The default-off setting keeps the file bridge.
2579
+ */
2580
+ async readSandboxDuplexBridgeInput() {
2581
+ const experimental = await instanceSettingsService(db).getExperimental();
2582
+ return resolveSandboxDuplexBridgeInput(experimental);
2583
+ },
2373
2584
  async acquireRunLease(input) {
2374
2585
  if (input.environment.status !== "active") {
2375
2586
  throw new Error(`Environment "${input.environment.name}" is not active.`);
@@ -2530,6 +2741,113 @@ export function environmentRuntimeService(db, options = {}) {
2530
2741
  }
2531
2742
  return destroyed;
2532
2743
  },
2744
+ /**
2745
+ * Destroy every reusable sandbox lease still owned by one environment, so a
2746
+ * consented environment delete can proceed. This must run while the
2747
+ * environment row still exists: the driver resolves provider credentials
2748
+ * from the environment config, and after the delete the normal destroy path
2749
+ * has no context left. A per-lease failure is contained — the driver routes
2750
+ * a failed teardown to `pending_cleanup` for the sweep, and an unexpected
2751
+ * throw leaves the lease in place — so the caller re-checks the blast
2752
+ * radius instead of trusting these counts for the delete decision.
2753
+ */
2754
+ async destroyReusableSandboxLeasesForEnvironment(input) {
2755
+ const environment = await environmentsSvc.getById(input.environmentId);
2756
+ if (!environment)
2757
+ return { destroyed: 0, failed: 0, skippedLiveRun: 0 };
2758
+ const leaseRows = await db
2759
+ .select()
2760
+ .from(environmentLeases)
2761
+ .where(and(eq(environmentLeases.environmentId, input.environmentId), eq(environmentLeases.leasePolicy, "reuse_by_environment"), inArray(environmentLeases.status, ["active", "released", "retained"])));
2762
+ // A lease whose holding run is still in flight keeps its sandbox: the
2763
+ // consented delete must not tear a live run's environment out from under
2764
+ // it. The skipped lease keeps blocking the delete, so the caller's
2765
+ // blast-radius re-check rejects and the operator retries after the run
2766
+ // finishes. A lease pointing at a finished run — or at no run — is a
2767
+ // stale reservation and destroys normally.
2768
+ const holdingRunIds = leaseRows
2769
+ .map((row) => row.heartbeatRunId)
2770
+ .filter((runId) => Boolean(runId));
2771
+ const liveRunIds = new Set();
2772
+ if (holdingRunIds.length > 0) {
2773
+ const liveRuns = await db
2774
+ .select({ id: heartbeatRuns.id })
2775
+ .from(heartbeatRuns)
2776
+ .where(and(inArray(heartbeatRuns.id, holdingRunIds), inArray(heartbeatRuns.status, ["queued", "scheduled_retry", "running"])));
2777
+ for (const run of liveRuns)
2778
+ liveRunIds.add(run.id);
2779
+ }
2780
+ let destroyed = 0;
2781
+ let failed = 0;
2782
+ let skippedLiveRun = 0;
2783
+ const failureReason = input.failureReason ?? "environment_delete_requested";
2784
+ const now = new Date();
2785
+ for (const leaseRow of leaseRows) {
2786
+ if (leaseRow.heartbeatRunId && liveRunIds.has(leaseRow.heartbeatRunId)) {
2787
+ skippedLiveRun += 1;
2788
+ continue;
2789
+ }
2790
+ // Claim the row BEFORE the provider call, mirroring the inline-teardown
2791
+ // invariant used elsewhere in this file: no provider destroy without a
2792
+ // durable `pending_cleanup` reference already on disk. The claim is one
2793
+ // conditional UPDATE, so it is the fence against a racing resume: a
2794
+ // resume that re-activates the lease first makes the status predicate
2795
+ // (or the run-liveness predicate) fail and the claim loses — the live
2796
+ // run keeps its sandbox. A claim that wins parks the lease where the
2797
+ // cleanup sweep owns it, so a crash or thrown destroy after this point
2798
+ // is recovered by the sweep's idempotent teardown, and a double write
2799
+ // failure cannot strand the lease in a reusable status.
2800
+ const claimedRow = await db
2801
+ .update(environmentLeases)
2802
+ .set({
2803
+ status: "pending_cleanup",
2804
+ failureReason,
2805
+ cleanupStatus: "failed",
2806
+ releasedAt: now,
2807
+ lastUsedAt: now,
2808
+ updatedAt: now,
2809
+ })
2810
+ .where(and(eq(environmentLeases.id, leaseRow.id), inArray(environmentLeases.status, ["active", "released", "retained"]), sql `NOT EXISTS (
2811
+ SELECT 1 FROM ${heartbeatRuns}
2812
+ WHERE ${heartbeatRuns.id} = ${environmentLeases.heartbeatRunId}
2813
+ AND ${heartbeatRuns.status} IN ('queued', 'scheduled_retry', 'running')
2814
+ )`))
2815
+ .returning()
2816
+ .then((rows) => rows[0] ?? null);
2817
+ if (!claimedRow) {
2818
+ // Lost to a racing resume or a concurrent terminal transition — the
2819
+ // lease is no longer ours to destroy.
2820
+ skippedLiveRun += 1;
2821
+ continue;
2822
+ }
2823
+ const leaseSnapshot = toEnvironmentLeaseSnapshot(claimedRow);
2824
+ try {
2825
+ const driver = getDriver(getLeaseDriverKey(leaseSnapshot, environment));
2826
+ if (!driver?.destroyRunLease) {
2827
+ // No driver available: the claim already parked the lease for the
2828
+ // sweep, which retries once the driver's plugin is back.
2829
+ failed += 1;
2830
+ continue;
2831
+ }
2832
+ const lease = await driver.destroyRunLease({
2833
+ environment,
2834
+ lease: leaseSnapshot,
2835
+ failureReason,
2836
+ });
2837
+ if (lease && lease.status !== "pending_cleanup")
2838
+ destroyed += 1;
2839
+ else
2840
+ failed += 1;
2841
+ }
2842
+ catch {
2843
+ // The claim above already parked the lease in `pending_cleanup`, so
2844
+ // the sweep owns the retry; its teardown is idempotent, so a destroy
2845
+ // that reached the provider before the throw resolves as success.
2846
+ failed += 1;
2847
+ }
2848
+ }
2849
+ return { destroyed, failed, skippedLiveRun };
2850
+ },
2533
2851
  async resumeRunLease(input) {
2534
2852
  const driver = requireDriverKey(getLeaseDriverKey(input.lease, input.environment));
2535
2853
  if (!driver.resumeRunLease) {
@@ -2562,9 +2880,18 @@ export function environmentRuntimeService(db, options = {}) {
2562
2880
  const driver = getDriver(getLeaseDriverKey(input.lease, input.environment));
2563
2881
  return driver?.supportsSync?.(input) ?? false;
2564
2882
  },
2565
- async effectiveSandboxCapabilities(input) {
2883
+ /**
2884
+ * Resolve the general per-lease capability snapshot through the driver's
2885
+ * {@link EnvironmentRuntimeDriver.resolveCapabilities}. Every registered
2886
+ * driver implements this method, so it returns a full snapshot for any
2887
+ * registered driver. It returns `null` only when the lease's driver is
2888
+ * not registered — never as a stand-in for "every capability denied".
2889
+ */
2890
+ async resolveCapabilities(input) {
2566
2891
  const driver = getDriver(getLeaseDriverKey(input.lease, input.environment));
2567
- return (await driver?.effectiveSandboxCapabilities?.(input)) ?? null;
2892
+ if (!driver)
2893
+ return null;
2894
+ return await driver.resolveCapabilities(input);
2568
2895
  },
2569
2896
  async syncIn(input) {
2570
2897
  const driver = requireDriverKey(getLeaseDriverKey(input.lease, input.environment));
@@ -2580,6 +2907,24 @@ export function environmentRuntimeService(db, options = {}) {
2580
2907
  }
2581
2908
  return await driver.syncOut(input);
2582
2909
  },
2910
+ async openDuplexChannel(input) {
2911
+ const driver = requireDriverKey(getLeaseDriverKey(input.lease, input.environment));
2912
+ if (!driver.openDuplexChannel) {
2913
+ throw new Error(`Environment driver "${driver.driver}" does not support duplex channels.`);
2914
+ }
2915
+ // Centralize the duplex channel authorization here. Resolve the exact lease
2916
+ // capability snapshot through the general resolver and refuse unless the
2917
+ // effective snapshot grants the opt-in `duplexCommandStream` capability.
2918
+ // This gate runs before the driver call, so an unauthorized lease never
2919
+ // reaches the worker. The execution-target member gate stays as defense
2920
+ // in depth. A driver that cannot resolve the snapshot fails closed with
2921
+ // the fixed refusal.
2922
+ const effective = await driver.resolveCapabilities(input);
2923
+ if (effective.duplexCommandStream !== true) {
2924
+ throw new Error(DUPLEX_CHANNEL_CAPABILITY_DENIED);
2925
+ }
2926
+ return await driver.openDuplexChannel(input);
2927
+ },
2583
2928
  };
2584
2929
  }
2585
2930
  //# sourceMappingURL=environment-runtime.js.map