@tickernelz/paperclip-pro-adapter-utils 2026.925.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1826) hide show
  1. package/README.md +37 -0
  2. package/dist/acpx-engine/cli.d.ts +2 -0
  3. package/dist/acpx-engine/cli.d.ts.map +1 -0
  4. package/dist/acpx-engine/cli.js +128 -0
  5. package/dist/acpx-engine/cli.js.map +1 -0
  6. package/dist/acpx-engine/composed-run-characterization.test.d.ts +2 -0
  7. package/dist/acpx-engine/composed-run-characterization.test.d.ts.map +1 -0
  8. package/dist/acpx-engine/composed-run-characterization.test.js +739 -0
  9. package/dist/acpx-engine/composed-run-characterization.test.js.map +1 -0
  10. package/dist/acpx-engine/constants.d.ts +20 -0
  11. package/dist/acpx-engine/constants.d.ts.map +1 -0
  12. package/dist/acpx-engine/constants.js +37 -0
  13. package/dist/acpx-engine/constants.js.map +1 -0
  14. package/dist/acpx-engine/execute-identity.test.d.ts +2 -0
  15. package/dist/acpx-engine/execute-identity.test.d.ts.map +1 -0
  16. package/dist/acpx-engine/execute-identity.test.js +218 -0
  17. package/dist/acpx-engine/execute-identity.test.js.map +1 -0
  18. package/dist/acpx-engine/execute.d.ts +325 -0
  19. package/dist/acpx-engine/execute.d.ts.map +1 -0
  20. package/dist/acpx-engine/execute.js +4448 -0
  21. package/dist/acpx-engine/execute.js.map +1 -0
  22. package/dist/acpx-engine/execute.test.d.ts +2 -0
  23. package/dist/acpx-engine/execute.test.d.ts.map +1 -0
  24. package/dist/acpx-engine/execute.test.js +6694 -0
  25. package/dist/acpx-engine/execute.test.js.map +1 -0
  26. package/dist/acpx-engine/index.d.ts +6 -0
  27. package/dist/acpx-engine/index.d.ts.map +1 -0
  28. package/dist/acpx-engine/index.js +6 -0
  29. package/dist/acpx-engine/index.js.map +1 -0
  30. package/dist/acpx-engine/local-process-control.d.ts +5 -0
  31. package/dist/acpx-engine/local-process-control.d.ts.map +1 -0
  32. package/dist/acpx-engine/local-process-control.js +39 -0
  33. package/dist/acpx-engine/local-process-control.js.map +1 -0
  34. package/dist/acpx-engine/local-process-control.test.d.ts +2 -0
  35. package/dist/acpx-engine/local-process-control.test.d.ts.map +1 -0
  36. package/dist/acpx-engine/local-process-control.test.js +38 -0
  37. package/dist/acpx-engine/local-process-control.test.js.map +1 -0
  38. package/dist/acpx-engine/operator-stop.test.d.ts +2 -0
  39. package/dist/acpx-engine/operator-stop.test.d.ts.map +1 -0
  40. package/dist/acpx-engine/operator-stop.test.js +107 -0
  41. package/dist/acpx-engine/operator-stop.test.js.map +1 -0
  42. package/dist/acpx-engine/remote-spawn-smoke.test.d.ts +2 -0
  43. package/dist/acpx-engine/remote-spawn-smoke.test.d.ts.map +1 -0
  44. package/dist/acpx-engine/remote-spawn-smoke.test.js +151 -0
  45. package/dist/acpx-engine/remote-spawn-smoke.test.js.map +1 -0
  46. package/dist/acpx-engine/run-contracts.d.ts +413 -0
  47. package/dist/acpx-engine/run-contracts.d.ts.map +1 -0
  48. package/dist/acpx-engine/run-contracts.js +20 -0
  49. package/dist/acpx-engine/run-contracts.js.map +1 -0
  50. package/dist/acpx-engine/run-contracts.test.d.ts +2 -0
  51. package/dist/acpx-engine/run-contracts.test.d.ts.map +1 -0
  52. package/dist/acpx-engine/run-contracts.test.js +57 -0
  53. package/dist/acpx-engine/run-contracts.test.js.map +1 -0
  54. package/dist/acpx-engine/run-coordinator.d.ts +57 -0
  55. package/dist/acpx-engine/run-coordinator.d.ts.map +1 -0
  56. package/dist/acpx-engine/run-coordinator.js +63 -0
  57. package/dist/acpx-engine/run-coordinator.js.map +1 -0
  58. package/dist/acpx-engine/run-coordinator.test.d.ts +2 -0
  59. package/dist/acpx-engine/run-coordinator.test.d.ts.map +1 -0
  60. package/dist/acpx-engine/run-coordinator.test.js +160 -0
  61. package/dist/acpx-engine/run-coordinator.test.js.map +1 -0
  62. package/dist/acpx-engine/run-fault-matrix.test.d.ts +2 -0
  63. package/dist/acpx-engine/run-fault-matrix.test.d.ts.map +1 -0
  64. package/dist/acpx-engine/run-fault-matrix.test.js +829 -0
  65. package/dist/acpx-engine/run-fault-matrix.test.js.map +1 -0
  66. package/dist/acpx-engine/run-resource-ledger.d.ts +30 -0
  67. package/dist/acpx-engine/run-resource-ledger.d.ts.map +1 -0
  68. package/dist/acpx-engine/run-resource-ledger.js +148 -0
  69. package/dist/acpx-engine/run-resource-ledger.js.map +1 -0
  70. package/dist/acpx-engine/run-resource-ledger.test.d.ts +2 -0
  71. package/dist/acpx-engine/run-resource-ledger.test.d.ts.map +1 -0
  72. package/dist/acpx-engine/run-resource-ledger.test.js +154 -0
  73. package/dist/acpx-engine/run-resource-ledger.test.js.map +1 -0
  74. package/dist/acpx-engine/run-site-host.d.ts +73 -0
  75. package/dist/acpx-engine/run-site-host.d.ts.map +1 -0
  76. package/dist/acpx-engine/run-site-host.js +65 -0
  77. package/dist/acpx-engine/run-site-host.js.map +1 -0
  78. package/dist/acpx-engine/run-site-host.test.d.ts +2 -0
  79. package/dist/acpx-engine/run-site-host.test.d.ts.map +1 -0
  80. package/dist/acpx-engine/run-site-host.test.js +98 -0
  81. package/dist/acpx-engine/run-site-host.test.js.map +1 -0
  82. package/dist/acpx-engine/run-site-sandbox.d.ts +142 -0
  83. package/dist/acpx-engine/run-site-sandbox.d.ts.map +1 -0
  84. package/dist/acpx-engine/run-site-sandbox.js +278 -0
  85. package/dist/acpx-engine/run-site-sandbox.js.map +1 -0
  86. package/dist/acpx-engine/run-site-sandbox.test.d.ts +2 -0
  87. package/dist/acpx-engine/run-site-sandbox.test.d.ts.map +1 -0
  88. package/dist/acpx-engine/run-site-sandbox.test.js +275 -0
  89. package/dist/acpx-engine/run-site-sandbox.test.js.map +1 -0
  90. package/dist/acpx-engine/session-codec.d.ts +3 -0
  91. package/dist/acpx-engine/session-codec.d.ts.map +1 -0
  92. package/dist/acpx-engine/session-codec.js +49 -0
  93. package/dist/acpx-engine/session-codec.js.map +1 -0
  94. package/dist/acpx-engine/session-reuse-store.d.ts +62 -0
  95. package/dist/acpx-engine/session-reuse-store.d.ts.map +1 -0
  96. package/dist/acpx-engine/session-reuse-store.js +134 -0
  97. package/dist/acpx-engine/session-reuse-store.js.map +1 -0
  98. package/dist/acpx-engine/session-reuse-store.test.d.ts +2 -0
  99. package/dist/acpx-engine/session-reuse-store.test.d.ts.map +1 -0
  100. package/dist/acpx-engine/session-reuse-store.test.js +134 -0
  101. package/dist/acpx-engine/session-reuse-store.test.js.map +1 -0
  102. package/dist/acpx-engine/settlement-characterization.test.d.ts +2 -0
  103. package/dist/acpx-engine/settlement-characterization.test.d.ts.map +1 -0
  104. package/dist/acpx-engine/settlement-characterization.test.js +880 -0
  105. package/dist/acpx-engine/settlement-characterization.test.js.map +1 -0
  106. package/dist/acpx-engine/settlement-sequence.d.ts +77 -0
  107. package/dist/acpx-engine/settlement-sequence.d.ts.map +1 -0
  108. package/dist/acpx-engine/settlement-sequence.js +119 -0
  109. package/dist/acpx-engine/settlement-sequence.js.map +1 -0
  110. package/dist/acpx-engine/settlement-sequence.test.d.ts +2 -0
  111. package/dist/acpx-engine/settlement-sequence.test.d.ts.map +1 -0
  112. package/dist/acpx-engine/settlement-sequence.test.js +272 -0
  113. package/dist/acpx-engine/settlement-sequence.test.js.map +1 -0
  114. package/dist/acpx-engine/spawn-smoke.test.d.ts +2 -0
  115. package/dist/acpx-engine/spawn-smoke.test.d.ts.map +1 -0
  116. package/dist/acpx-engine/spawn-smoke.test.js +184 -0
  117. package/dist/acpx-engine/spawn-smoke.test.js.map +1 -0
  118. package/dist/acpx-engine/startup-cancellation.d.ts +8 -0
  119. package/dist/acpx-engine/startup-cancellation.d.ts.map +1 -0
  120. package/dist/acpx-engine/startup-cancellation.js +92 -0
  121. package/dist/acpx-engine/startup-cancellation.js.map +1 -0
  122. package/dist/acpx-engine/startup-cancellation.test.d.ts +2 -0
  123. package/dist/acpx-engine/startup-cancellation.test.d.ts.map +1 -0
  124. package/dist/acpx-engine/startup-cancellation.test.js +115 -0
  125. package/dist/acpx-engine/startup-cancellation.test.js.map +1 -0
  126. package/dist/acpx-engine/startup-characterization.test.d.ts +2 -0
  127. package/dist/acpx-engine/startup-characterization.test.d.ts.map +1 -0
  128. package/dist/acpx-engine/startup-characterization.test.js +742 -0
  129. package/dist/acpx-engine/startup-characterization.test.js.map +1 -0
  130. package/dist/acpx-engine/startup-timing.d.ts +419 -0
  131. package/dist/acpx-engine/startup-timing.d.ts.map +1 -0
  132. package/dist/acpx-engine/startup-timing.js +616 -0
  133. package/dist/acpx-engine/startup-timing.js.map +1 -0
  134. package/dist/acpx-engine/startup-timing.test.d.ts +2 -0
  135. package/dist/acpx-engine/startup-timing.test.d.ts.map +1 -0
  136. package/dist/acpx-engine/startup-timing.test.js +619 -0
  137. package/dist/acpx-engine/startup-timing.test.js.map +1 -0
  138. package/dist/acpx-engine/turn-characterization.test.d.ts +2 -0
  139. package/dist/acpx-engine/turn-characterization.test.d.ts.map +1 -0
  140. package/dist/acpx-engine/turn-characterization.test.js +566 -0
  141. package/dist/acpx-engine/turn-characterization.test.js.map +1 -0
  142. package/dist/acpx-engine/turn-sequence.d.ts +54 -0
  143. package/dist/acpx-engine/turn-sequence.d.ts.map +1 -0
  144. package/dist/acpx-engine/turn-sequence.js +75 -0
  145. package/dist/acpx-engine/turn-sequence.js.map +1 -0
  146. package/dist/acpx-engine/turn-sequence.test.d.ts +2 -0
  147. package/dist/acpx-engine/turn-sequence.test.d.ts.map +1 -0
  148. package/dist/acpx-engine/turn-sequence.test.js +177 -0
  149. package/dist/acpx-engine/turn-sequence.test.js.map +1 -0
  150. package/dist/acpx-engine/ui.d.ts +3 -0
  151. package/dist/acpx-engine/ui.d.ts.map +1 -0
  152. package/dist/acpx-engine/ui.js +162 -0
  153. package/dist/acpx-engine/ui.js.map +1 -0
  154. package/dist/billing.d.ts +2 -0
  155. package/dist/billing.d.ts.map +1 -0
  156. package/dist/billing.js +16 -0
  157. package/dist/billing.js.map +1 -0
  158. package/dist/billing.test.d.ts +2 -0
  159. package/dist/billing.test.d.ts.map +1 -0
  160. package/dist/billing.test.js +14 -0
  161. package/dist/billing.test.js.map +1 -0
  162. package/dist/bridge-transport-contract.d.ts +43 -0
  163. package/dist/bridge-transport-contract.d.ts.map +1 -0
  164. package/dist/bridge-transport-contract.js +33 -0
  165. package/dist/bridge-transport-contract.js.map +1 -0
  166. package/dist/chat-file-delivery.d.ts +10 -0
  167. package/dist/chat-file-delivery.d.ts.map +1 -0
  168. package/dist/chat-file-delivery.js +38 -0
  169. package/dist/chat-file-delivery.js.map +1 -0
  170. package/dist/chat-file-delivery.test.d.ts +2 -0
  171. package/dist/chat-file-delivery.test.d.ts.map +1 -0
  172. package/dist/chat-file-delivery.test.js +92 -0
  173. package/dist/chat-file-delivery.test.js.map +1 -0
  174. package/dist/command-managed-runtime.d.ts +162 -0
  175. package/dist/command-managed-runtime.d.ts.map +1 -0
  176. package/dist/command-managed-runtime.js +456 -0
  177. package/dist/command-managed-runtime.js.map +1 -0
  178. package/dist/command-managed-runtime.test.d.ts +2 -0
  179. package/dist/command-managed-runtime.test.d.ts.map +1 -0
  180. package/dist/command-managed-runtime.test.js +908 -0
  181. package/dist/command-managed-runtime.test.js.map +1 -0
  182. package/dist/command-redaction.d.ts +15 -0
  183. package/dist/command-redaction.d.ts.map +1 -0
  184. package/dist/command-redaction.js +97 -0
  185. package/dist/command-redaction.js.map +1 -0
  186. package/dist/command-redaction.test.d.ts +2 -0
  187. package/dist/command-redaction.test.d.ts.map +1 -0
  188. package/dist/command-redaction.test.js +80 -0
  189. package/dist/command-redaction.test.js.map +1 -0
  190. package/dist/duplex-frame-codec.d.ts +124 -0
  191. package/dist/duplex-frame-codec.d.ts.map +1 -0
  192. package/dist/duplex-frame-codec.js +134 -0
  193. package/dist/duplex-frame-codec.js.map +1 -0
  194. package/dist/duplex-frame-codec.test.d.ts +2 -0
  195. package/dist/duplex-frame-codec.test.d.ts.map +1 -0
  196. package/dist/duplex-frame-codec.test.js +172 -0
  197. package/dist/duplex-frame-codec.test.js.map +1 -0
  198. package/dist/duplex-observability.d.ts +220 -0
  199. package/dist/duplex-observability.d.ts.map +1 -0
  200. package/dist/duplex-observability.js +248 -0
  201. package/dist/duplex-observability.js.map +1 -0
  202. package/dist/duplex-observability.test.d.ts +2 -0
  203. package/dist/duplex-observability.test.d.ts.map +1 -0
  204. package/dist/duplex-observability.test.js +55 -0
  205. package/dist/duplex-observability.test.js.map +1 -0
  206. package/dist/env-bindings.d.ts +17 -0
  207. package/dist/env-bindings.d.ts.map +1 -0
  208. package/dist/env-bindings.js +98 -0
  209. package/dist/env-bindings.js.map +1 -0
  210. package/dist/env-bindings.test.d.ts +2 -0
  211. package/dist/env-bindings.test.d.ts.map +1 -0
  212. package/dist/env-bindings.test.js +66 -0
  213. package/dist/env-bindings.test.js.map +1 -0
  214. package/dist/exclude-patterns.d.ts +4 -0
  215. package/dist/exclude-patterns.d.ts.map +1 -0
  216. package/dist/exclude-patterns.js +26 -0
  217. package/dist/exclude-patterns.js.map +1 -0
  218. package/dist/execution-target-sandbox.test.d.ts +2 -0
  219. package/dist/execution-target-sandbox.test.d.ts.map +1 -0
  220. package/dist/execution-target-sandbox.test.js +6522 -0
  221. package/dist/execution-target-sandbox.test.js.map +1 -0
  222. package/dist/execution-target-stdin-race.test.d.ts +2 -0
  223. package/dist/execution-target-stdin-race.test.d.ts.map +1 -0
  224. package/dist/execution-target-stdin-race.test.js +1842 -0
  225. package/dist/execution-target-stdin-race.test.js.map +1 -0
  226. package/dist/execution-target.d.ts +537 -0
  227. package/dist/execution-target.d.ts.map +1 -0
  228. package/dist/execution-target.js +3877 -0
  229. package/dist/execution-target.js.map +1 -0
  230. package/dist/execution-target.test.d.ts +2 -0
  231. package/dist/execution-target.test.d.ts.map +1 -0
  232. package/dist/execution-target.test.js +346 -0
  233. package/dist/execution-target.test.js.map +1 -0
  234. package/dist/git-workspace-sync.d.ts +193 -0
  235. package/dist/git-workspace-sync.d.ts.map +1 -0
  236. package/dist/git-workspace-sync.js +786 -0
  237. package/dist/git-workspace-sync.js.map +1 -0
  238. package/dist/git-workspace-sync.test.d.ts +2 -0
  239. package/dist/git-workspace-sync.test.d.ts.map +1 -0
  240. package/dist/git-workspace-sync.test.js +830 -0
  241. package/dist/git-workspace-sync.test.js.map +1 -0
  242. package/dist/github-launcher-environment.test.d.ts +2 -0
  243. package/dist/github-launcher-environment.test.d.ts.map +1 -0
  244. package/dist/github-launcher-environment.test.js +285 -0
  245. package/dist/github-launcher-environment.test.js.map +1 -0
  246. package/dist/github-launcher.d.ts +8 -0
  247. package/dist/github-launcher.d.ts.map +1 -0
  248. package/dist/github-launcher.js +127 -0
  249. package/dist/github-launcher.js.map +1 -0
  250. package/dist/github-launcher.test.d.ts +2 -0
  251. package/dist/github-launcher.test.d.ts.map +1 -0
  252. package/dist/github-launcher.test.js +166 -0
  253. package/dist/github-launcher.test.js.map +1 -0
  254. package/dist/http2-bridge-server.d.ts +465 -0
  255. package/dist/http2-bridge-server.d.ts.map +1 -0
  256. package/dist/http2-bridge-server.js +1087 -0
  257. package/dist/http2-bridge-server.js.map +1 -0
  258. package/dist/http2-bridge-server.test.d.ts +2 -0
  259. package/dist/http2-bridge-server.test.d.ts.map +1 -0
  260. package/dist/http2-bridge-server.test.js +2129 -0
  261. package/dist/http2-bridge-server.test.js.map +1 -0
  262. package/dist/index.d.ts +20 -0
  263. package/dist/index.d.ts.map +1 -0
  264. package/dist/index.js +12 -0
  265. package/dist/index.js.map +1 -0
  266. package/dist/local-process-sandbox.d.ts +41 -0
  267. package/dist/local-process-sandbox.d.ts.map +1 -0
  268. package/dist/local-process-sandbox.js +432 -0
  269. package/dist/local-process-sandbox.js.map +1 -0
  270. package/dist/local-process-sandbox.test.d.ts +2 -0
  271. package/dist/local-process-sandbox.test.d.ts.map +1 -0
  272. package/dist/local-process-sandbox.test.js +399 -0
  273. package/dist/local-process-sandbox.test.js.map +1 -0
  274. package/dist/log-redaction.d.ts +9 -0
  275. package/dist/log-redaction.d.ts.map +1 -0
  276. package/dist/log-redaction.js +88 -0
  277. package/dist/log-redaction.js.map +1 -0
  278. package/dist/login-capability.d.ts +88 -0
  279. package/dist/login-capability.d.ts.map +1 -0
  280. package/dist/login-capability.js +78 -0
  281. package/dist/login-capability.js.map +1 -0
  282. package/dist/login-capability.test.d.ts +2 -0
  283. package/dist/login-capability.test.d.ts.map +1 -0
  284. package/dist/login-capability.test.js +78 -0
  285. package/dist/login-capability.test.js.map +1 -0
  286. package/dist/login-pty-transport.d.ts +80 -0
  287. package/dist/login-pty-transport.d.ts.map +1 -0
  288. package/dist/login-pty-transport.js +75 -0
  289. package/dist/login-pty-transport.js.map +1 -0
  290. package/dist/login-pty-transport.test.d.ts +2 -0
  291. package/dist/login-pty-transport.test.d.ts.map +1 -0
  292. package/dist/login-pty-transport.test.js +185 -0
  293. package/dist/login-pty-transport.test.js.map +1 -0
  294. package/dist/login-runner-lifecycle.d.ts +70 -0
  295. package/dist/login-runner-lifecycle.d.ts.map +1 -0
  296. package/dist/login-runner-lifecycle.js +52 -0
  297. package/dist/login-runner-lifecycle.js.map +1 -0
  298. package/dist/login-runner-lifecycle.test.d.ts +2 -0
  299. package/dist/login-runner-lifecycle.test.d.ts.map +1 -0
  300. package/dist/login-runner-lifecycle.test.js +122 -0
  301. package/dist/login-runner-lifecycle.test.js.map +1 -0
  302. package/dist/mcp-isolation.integration.test.d.ts +2 -0
  303. package/dist/mcp-isolation.integration.test.d.ts.map +1 -0
  304. package/dist/mcp-isolation.integration.test.js +202 -0
  305. package/dist/mcp-isolation.integration.test.js.map +1 -0
  306. package/dist/paperclip-runner-permissions.d.ts +110 -0
  307. package/dist/paperclip-runner-permissions.d.ts.map +1 -0
  308. package/dist/paperclip-runner-permissions.js +150 -0
  309. package/dist/paperclip-runner-permissions.js.map +1 -0
  310. package/dist/paperclip-runner-permissions.test.d.ts +2 -0
  311. package/dist/paperclip-runner-permissions.test.d.ts.map +1 -0
  312. package/dist/paperclip-runner-permissions.test.js +47 -0
  313. package/dist/paperclip-runner-permissions.test.js.map +1 -0
  314. package/dist/published-exports.test.d.ts +2 -0
  315. package/dist/published-exports.test.d.ts.map +1 -0
  316. package/dist/published-exports.test.js +17 -0
  317. package/dist/published-exports.test.js.map +1 -0
  318. package/dist/remote-execution-env.d.ts +2 -0
  319. package/dist/remote-execution-env.d.ts.map +1 -0
  320. package/dist/remote-execution-env.js +46 -0
  321. package/dist/remote-execution-env.js.map +1 -0
  322. package/dist/remote-managed-runtime.d.ts +45 -0
  323. package/dist/remote-managed-runtime.d.ts.map +1 -0
  324. package/dist/remote-managed-runtime.js +183 -0
  325. package/dist/remote-managed-runtime.js.map +1 -0
  326. package/dist/remote-managed-runtime.test.d.ts +2 -0
  327. package/dist/remote-managed-runtime.test.d.ts.map +1 -0
  328. package/dist/remote-managed-runtime.test.js +294 -0
  329. package/dist/remote-managed-runtime.test.js.map +1 -0
  330. package/dist/runner-connectivity.d.ts +61 -0
  331. package/dist/runner-connectivity.d.ts.map +1 -0
  332. package/dist/runner-connectivity.js +81 -0
  333. package/dist/runner-connectivity.js.map +1 -0
  334. package/dist/runner-connectivity.test.d.ts +2 -0
  335. package/dist/runner-connectivity.test.d.ts.map +1 -0
  336. package/dist/runner-connectivity.test.js +183 -0
  337. package/dist/runner-connectivity.test.js.map +1 -0
  338. package/dist/runtime-progress.d.ts +48 -0
  339. package/dist/runtime-progress.d.ts.map +1 -0
  340. package/dist/runtime-progress.js +99 -0
  341. package/dist/runtime-progress.js.map +1 -0
  342. package/dist/runtime-progress.test.d.ts +2 -0
  343. package/dist/runtime-progress.test.d.ts.map +1 -0
  344. package/dist/runtime-progress.test.js +240 -0
  345. package/dist/runtime-progress.test.js.map +1 -0
  346. package/dist/sandbox-callback-bridge-body.d.ts +12 -0
  347. package/dist/sandbox-callback-bridge-body.d.ts.map +1 -0
  348. package/dist/sandbox-callback-bridge-body.js +39 -0
  349. package/dist/sandbox-callback-bridge-body.js.map +1 -0
  350. package/dist/sandbox-callback-bridge-body.test.d.ts +2 -0
  351. package/dist/sandbox-callback-bridge-body.test.d.ts.map +1 -0
  352. package/dist/sandbox-callback-bridge-body.test.js +31 -0
  353. package/dist/sandbox-callback-bridge-body.test.js.map +1 -0
  354. package/dist/sandbox-callback-bridge.d.ts +268 -0
  355. package/dist/sandbox-callback-bridge.d.ts.map +1 -0
  356. package/dist/sandbox-callback-bridge.js +2357 -0
  357. package/dist/sandbox-callback-bridge.js.map +1 -0
  358. package/dist/sandbox-callback-bridge.test.d.ts +2 -0
  359. package/dist/sandbox-callback-bridge.test.d.ts.map +1 -0
  360. package/dist/sandbox-callback-bridge.test.js +3253 -0
  361. package/dist/sandbox-callback-bridge.test.js.map +1 -0
  362. package/dist/sandbox-file-sync.test.d.ts +2 -0
  363. package/dist/sandbox-file-sync.test.d.ts.map +1 -0
  364. package/dist/sandbox-file-sync.test.js +419 -0
  365. package/dist/sandbox-file-sync.test.js.map +1 -0
  366. package/dist/sandbox-install-command.d.ts +2 -0
  367. package/dist/sandbox-install-command.d.ts.map +1 -0
  368. package/dist/sandbox-install-command.js +47 -0
  369. package/dist/sandbox-install-command.js.map +1 -0
  370. package/dist/sandbox-install-command.test.d.ts +2 -0
  371. package/dist/sandbox-install-command.test.d.ts.map +1 -0
  372. package/dist/sandbox-install-command.test.js +22 -0
  373. package/dist/sandbox-install-command.test.js.map +1 -0
  374. package/dist/sandbox-managed-runtime.d.ts +417 -0
  375. package/dist/sandbox-managed-runtime.d.ts.map +1 -0
  376. package/dist/sandbox-managed-runtime.js +1337 -0
  377. package/dist/sandbox-managed-runtime.js.map +1 -0
  378. package/dist/sandbox-managed-runtime.test.d.ts +2 -0
  379. package/dist/sandbox-managed-runtime.test.d.ts.map +1 -0
  380. package/dist/sandbox-managed-runtime.test.js +3746 -0
  381. package/dist/sandbox-managed-runtime.test.js.map +1 -0
  382. package/dist/sandbox-run-log-stream.d.ts +44 -0
  383. package/dist/sandbox-run-log-stream.d.ts.map +1 -0
  384. package/dist/sandbox-run-log-stream.js +203 -0
  385. package/dist/sandbox-run-log-stream.js.map +1 -0
  386. package/dist/sandbox-shell.d.ts +3 -0
  387. package/dist/sandbox-shell.d.ts.map +1 -0
  388. package/dist/sandbox-shell.js +7 -0
  389. package/dist/sandbox-shell.js.map +1 -0
  390. package/dist/server-utils-env.test.d.ts +2 -0
  391. package/dist/server-utils-env.test.d.ts.map +1 -0
  392. package/dist/server-utils-env.test.js +15 -0
  393. package/dist/server-utils-env.test.js.map +1 -0
  394. package/dist/server-utils.d.ts +617 -0
  395. package/dist/server-utils.d.ts.map +1 -0
  396. package/dist/server-utils.js +3258 -0
  397. package/dist/server-utils.js.map +1 -0
  398. package/dist/server-utils.test.d.ts +2 -0
  399. package/dist/server-utils.test.d.ts.map +1 -0
  400. package/dist/server-utils.test.js +3047 -0
  401. package/dist/server-utils.test.js.map +1 -0
  402. package/dist/session-compaction.d.ts +25 -0
  403. package/dist/session-compaction.d.ts.map +1 -0
  404. package/dist/session-compaction.js +165 -0
  405. package/dist/session-compaction.js.map +1 -0
  406. package/dist/skill-library-manifest.d.ts +6 -0
  407. package/dist/skill-library-manifest.d.ts.map +1 -0
  408. package/dist/skill-library-manifest.js +53 -0
  409. package/dist/skill-library-manifest.js.map +1 -0
  410. package/dist/skill-library-manifest.test.d.ts +2 -0
  411. package/dist/skill-library-manifest.test.d.ts.map +1 -0
  412. package/dist/skill-library-manifest.test.js +84 -0
  413. package/dist/skill-library-manifest.test.js.map +1 -0
  414. package/dist/skills-staging-follow-symlinks.test.d.ts +2 -0
  415. package/dist/skills-staging-follow-symlinks.test.d.ts.map +1 -0
  416. package/dist/skills-staging-follow-symlinks.test.js +126 -0
  417. package/dist/skills-staging-follow-symlinks.test.js.map +1 -0
  418. package/dist/ssh-fixture.test.d.ts +2 -0
  419. package/dist/ssh-fixture.test.d.ts.map +1 -0
  420. package/dist/ssh-fixture.test.js +832 -0
  421. package/dist/ssh-fixture.test.js.map +1 -0
  422. package/dist/ssh.d.ts +124 -0
  423. package/dist/ssh.d.ts.map +1 -0
  424. package/dist/ssh.js +1667 -0
  425. package/dist/ssh.js.map +1 -0
  426. package/dist/sync-operation-schedule.d.ts +17 -0
  427. package/dist/sync-operation-schedule.d.ts.map +1 -0
  428. package/dist/sync-operation-schedule.js +66 -0
  429. package/dist/sync-operation-schedule.js.map +1 -0
  430. package/dist/sync-operation-schedule.test.d.ts +2 -0
  431. package/dist/sync-operation-schedule.test.d.ts.map +1 -0
  432. package/dist/sync-operation-schedule.test.js +130 -0
  433. package/dist/sync-operation-schedule.test.js.map +1 -0
  434. package/dist/test-support/mcp-isolation-harness.d.ts +23 -0
  435. package/dist/test-support/mcp-isolation-harness.d.ts.map +1 -0
  436. package/dist/test-support/mcp-isolation-harness.js +67 -0
  437. package/dist/test-support/mcp-isolation-harness.js.map +1 -0
  438. package/dist/types.d.ts +780 -0
  439. package/dist/types.d.ts.map +1 -0
  440. package/dist/types.js +5 -0
  441. package/dist/types.js.map +1 -0
  442. package/dist/workspace-restore-merge.d.ts +77 -0
  443. package/dist/workspace-restore-merge.d.ts.map +1 -0
  444. package/dist/workspace-restore-merge.js +396 -0
  445. package/dist/workspace-restore-merge.js.map +1 -0
  446. package/dist/workspace-restore-merge.test.d.ts +2 -0
  447. package/dist/workspace-restore-merge.test.d.ts.map +1 -0
  448. package/dist/workspace-restore-merge.test.js +438 -0
  449. package/dist/workspace-restore-merge.test.js.map +1 -0
  450. package/dist/workspace-restore-teardown.d.ts +29 -0
  451. package/dist/workspace-restore-teardown.d.ts.map +1 -0
  452. package/dist/workspace-restore-teardown.js +34 -0
  453. package/dist/workspace-restore-teardown.js.map +1 -0
  454. package/dist/workspace-restore-teardown.test.d.ts +2 -0
  455. package/dist/workspace-restore-teardown.test.d.ts.map +1 -0
  456. package/dist/workspace-restore-teardown.test.js +71 -0
  457. package/dist/workspace-restore-teardown.test.js.map +1 -0
  458. package/node_modules/@agentclientprotocol/sdk/LICENSE +191 -0
  459. package/node_modules/@agentclientprotocol/sdk/README.md +70 -0
  460. package/node_modules/@agentclientprotocol/sdk/dist/acp.d.ts +1803 -0
  461. package/node_modules/@agentclientprotocol/sdk/dist/acp.js +1869 -0
  462. package/node_modules/@agentclientprotocol/sdk/dist/acp.js.map +1 -0
  463. package/node_modules/@agentclientprotocol/sdk/dist/acp.test.d.ts +1 -0
  464. package/node_modules/@agentclientprotocol/sdk/dist/acp.test.js +5036 -0
  465. package/node_modules/@agentclientprotocol/sdk/dist/acp.test.js.map +1 -0
  466. package/node_modules/@agentclientprotocol/sdk/dist/connection.d.ts +89 -0
  467. package/node_modules/@agentclientprotocol/sdk/dist/connection.js +490 -0
  468. package/node_modules/@agentclientprotocol/sdk/dist/connection.js.map +1 -0
  469. package/node_modules/@agentclientprotocol/sdk/dist/connection.test.d.ts +1 -0
  470. package/node_modules/@agentclientprotocol/sdk/dist/connection.test.js +507 -0
  471. package/node_modules/@agentclientprotocol/sdk/dist/connection.test.js.map +1 -0
  472. package/node_modules/@agentclientprotocol/sdk/dist/cookie-store.d.ts +26 -0
  473. package/node_modules/@agentclientprotocol/sdk/dist/cookie-store.js +96 -0
  474. package/node_modules/@agentclientprotocol/sdk/dist/cookie-store.js.map +1 -0
  475. package/node_modules/@agentclientprotocol/sdk/dist/cookie-store.test.d.ts +1 -0
  476. package/node_modules/@agentclientprotocol/sdk/dist/cookie-store.test.js +77 -0
  477. package/node_modules/@agentclientprotocol/sdk/dist/cookie-store.test.js.map +1 -0
  478. package/node_modules/@agentclientprotocol/sdk/dist/examples/agent.d.ts +2 -0
  479. package/node_modules/@agentclientprotocol/sdk/dist/examples/agent.js +232 -0
  480. package/node_modules/@agentclientprotocol/sdk/dist/examples/agent.js.map +1 -0
  481. package/node_modules/@agentclientprotocol/sdk/dist/examples/client.d.ts +2 -0
  482. package/node_modules/@agentclientprotocol/sdk/dist/examples/client.js +132 -0
  483. package/node_modules/@agentclientprotocol/sdk/dist/examples/client.js.map +1 -0
  484. package/node_modules/@agentclientprotocol/sdk/dist/examples/dual-version-agent.d.ts +9 -0
  485. package/node_modules/@agentclientprotocol/sdk/dist/examples/dual-version-agent.js +221 -0
  486. package/node_modules/@agentclientprotocol/sdk/dist/examples/dual-version-agent.js.map +1 -0
  487. package/node_modules/@agentclientprotocol/sdk/dist/examples/http-client.d.ts +2 -0
  488. package/node_modules/@agentclientprotocol/sdk/dist/examples/http-client.js +86 -0
  489. package/node_modules/@agentclientprotocol/sdk/dist/examples/http-client.js.map +1 -0
  490. package/node_modules/@agentclientprotocol/sdk/dist/examples/http-server.d.ts +2 -0
  491. package/node_modules/@agentclientprotocol/sdk/dist/examples/http-server.js +112 -0
  492. package/node_modules/@agentclientprotocol/sdk/dist/examples/http-server.js.map +1 -0
  493. package/node_modules/@agentclientprotocol/sdk/dist/examples/ws-client.d.ts +2 -0
  494. package/node_modules/@agentclientprotocol/sdk/dist/examples/ws-client.js +88 -0
  495. package/node_modules/@agentclientprotocol/sdk/dist/examples/ws-client.js.map +1 -0
  496. package/node_modules/@agentclientprotocol/sdk/dist/http-stream.d.ts +33 -0
  497. package/node_modules/@agentclientprotocol/sdk/dist/http-stream.js +435 -0
  498. package/node_modules/@agentclientprotocol/sdk/dist/http-stream.js.map +1 -0
  499. package/node_modules/@agentclientprotocol/sdk/dist/http-stream.test.d.ts +1 -0
  500. package/node_modules/@agentclientprotocol/sdk/dist/http-stream.test.js +1380 -0
  501. package/node_modules/@agentclientprotocol/sdk/dist/http-stream.test.js.map +1 -0
  502. package/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.d.ts +603 -0
  503. package/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.js +1076 -0
  504. package/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.js.map +1 -0
  505. package/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.test.d.ts +1 -0
  506. package/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.test.js +917 -0
  507. package/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.test.js.map +1 -0
  508. package/node_modules/@agentclientprotocol/sdk/dist/line-buffer.d.ts +22 -0
  509. package/node_modules/@agentclientprotocol/sdk/dist/line-buffer.js +64 -0
  510. package/node_modules/@agentclientprotocol/sdk/dist/line-buffer.js.map +1 -0
  511. package/node_modules/@agentclientprotocol/sdk/dist/line-buffer.test.d.ts +1 -0
  512. package/node_modules/@agentclientprotocol/sdk/dist/line-buffer.test.js +45 -0
  513. package/node_modules/@agentclientprotocol/sdk/dist/line-buffer.test.js.map +1 -0
  514. package/node_modules/@agentclientprotocol/sdk/dist/node-adapter.d.ts +17 -0
  515. package/node_modules/@agentclientprotocol/sdk/dist/node-adapter.js +426 -0
  516. package/node_modules/@agentclientprotocol/sdk/dist/node-adapter.js.map +1 -0
  517. package/node_modules/@agentclientprotocol/sdk/dist/node-adapter.test.d.ts +1 -0
  518. package/node_modules/@agentclientprotocol/sdk/dist/node-adapter.test.js +499 -0
  519. package/node_modules/@agentclientprotocol/sdk/dist/node-adapter.test.js.map +1 -0
  520. package/node_modules/@agentclientprotocol/sdk/dist/protocol-router.d.ts +37 -0
  521. package/node_modules/@agentclientprotocol/sdk/dist/protocol-router.js +474 -0
  522. package/node_modules/@agentclientprotocol/sdk/dist/protocol-router.js.map +1 -0
  523. package/node_modules/@agentclientprotocol/sdk/dist/protocol-router.test.d.ts +1 -0
  524. package/node_modules/@agentclientprotocol/sdk/dist/protocol-router.test.js +535 -0
  525. package/node_modules/@agentclientprotocol/sdk/dist/protocol-router.test.js.map +1 -0
  526. package/node_modules/@agentclientprotocol/sdk/dist/protocol.d.ts +11 -0
  527. package/node_modules/@agentclientprotocol/sdk/dist/protocol.js +69 -0
  528. package/node_modules/@agentclientprotocol/sdk/dist/protocol.js.map +1 -0
  529. package/node_modules/@agentclientprotocol/sdk/dist/protocol.test.d.ts +1 -0
  530. package/node_modules/@agentclientprotocol/sdk/dist/protocol.test.js +82 -0
  531. package/node_modules/@agentclientprotocol/sdk/dist/protocol.test.js.map +1 -0
  532. package/node_modules/@agentclientprotocol/sdk/dist/schema/guards.gen.d.ts +183 -0
  533. package/node_modules/@agentclientprotocol/sdk/dist/schema/guards.gen.js +207 -0
  534. package/node_modules/@agentclientprotocol/sdk/dist/schema/guards.gen.js.map +1 -0
  535. package/node_modules/@agentclientprotocol/sdk/dist/schema/index.d.ts +51 -0
  536. package/node_modules/@agentclientprotocol/sdk/dist/schema/index.js +52 -0
  537. package/node_modules/@agentclientprotocol/sdk/dist/schema/index.js.map +1 -0
  538. package/node_modules/@agentclientprotocol/sdk/dist/schema/types.gen.d.ts +6219 -0
  539. package/node_modules/@agentclientprotocol/sdk/dist/schema/types.gen.js +3 -0
  540. package/node_modules/@agentclientprotocol/sdk/dist/schema/types.gen.js.map +1 -0
  541. package/node_modules/@agentclientprotocol/sdk/dist/schema/zod.gen.d.ts +15215 -0
  542. package/node_modules/@agentclientprotocol/sdk/dist/schema/zod.gen.js +3084 -0
  543. package/node_modules/@agentclientprotocol/sdk/dist/schema/zod.gen.js.map +1 -0
  544. package/node_modules/@agentclientprotocol/sdk/dist/schema-deserialize.d.ts +30 -0
  545. package/node_modules/@agentclientprotocol/sdk/dist/schema-deserialize.js +98 -0
  546. package/node_modules/@agentclientprotocol/sdk/dist/schema-deserialize.js.map +1 -0
  547. package/node_modules/@agentclientprotocol/sdk/dist/schema-deserialize.test.d.ts +1 -0
  548. package/node_modules/@agentclientprotocol/sdk/dist/schema-deserialize.test.js +93 -0
  549. package/node_modules/@agentclientprotocol/sdk/dist/schema-deserialize.test.js.map +1 -0
  550. package/node_modules/@agentclientprotocol/sdk/dist/server-permission.test.d.ts +1 -0
  551. package/node_modules/@agentclientprotocol/sdk/dist/server-permission.test.js +261 -0
  552. package/node_modules/@agentclientprotocol/sdk/dist/server-permission.test.js.map +1 -0
  553. package/node_modules/@agentclientprotocol/sdk/dist/server-session-sse.test.d.ts +1 -0
  554. package/node_modules/@agentclientprotocol/sdk/dist/server-session-sse.test.js +536 -0
  555. package/node_modules/@agentclientprotocol/sdk/dist/server-session-sse.test.js.map +1 -0
  556. package/node_modules/@agentclientprotocol/sdk/dist/server-sse.d.ts +2 -0
  557. package/node_modules/@agentclientprotocol/sdk/dist/server-sse.js +88 -0
  558. package/node_modules/@agentclientprotocol/sdk/dist/server-sse.js.map +1 -0
  559. package/node_modules/@agentclientprotocol/sdk/dist/server-sse.test.d.ts +1 -0
  560. package/node_modules/@agentclientprotocol/sdk/dist/server-sse.test.js +53 -0
  561. package/node_modules/@agentclientprotocol/sdk/dist/server-sse.test.js.map +1 -0
  562. package/node_modules/@agentclientprotocol/sdk/dist/server-websocket-upgrade.test.d.ts +1 -0
  563. package/node_modules/@agentclientprotocol/sdk/dist/server-websocket-upgrade.test.js +992 -0
  564. package/node_modules/@agentclientprotocol/sdk/dist/server-websocket-upgrade.test.js.map +1 -0
  565. package/node_modules/@agentclientprotocol/sdk/dist/server.d.ts +81 -0
  566. package/node_modules/@agentclientprotocol/sdk/dist/server.js +386 -0
  567. package/node_modules/@agentclientprotocol/sdk/dist/server.js.map +1 -0
  568. package/node_modules/@agentclientprotocol/sdk/dist/server.test.d.ts +1 -0
  569. package/node_modules/@agentclientprotocol/sdk/dist/server.test.js +1116 -0
  570. package/node_modules/@agentclientprotocol/sdk/dist/server.test.js.map +1 -0
  571. package/node_modules/@agentclientprotocol/sdk/dist/sse.d.ts +4 -0
  572. package/node_modules/@agentclientprotocol/sdk/dist/sse.js +91 -0
  573. package/node_modules/@agentclientprotocol/sdk/dist/sse.js.map +1 -0
  574. package/node_modules/@agentclientprotocol/sdk/dist/sse.test.d.ts +1 -0
  575. package/node_modules/@agentclientprotocol/sdk/dist/sse.test.js +126 -0
  576. package/node_modules/@agentclientprotocol/sdk/dist/sse.test.js.map +1 -0
  577. package/node_modules/@agentclientprotocol/sdk/dist/stream.d.ts +35 -0
  578. package/node_modules/@agentclientprotocol/sdk/dist/stream.js +115 -0
  579. package/node_modules/@agentclientprotocol/sdk/dist/stream.js.map +1 -0
  580. package/node_modules/@agentclientprotocol/sdk/dist/stream.test.d.ts +1 -0
  581. package/node_modules/@agentclientprotocol/sdk/dist/stream.test.js +195 -0
  582. package/node_modules/@agentclientprotocol/sdk/dist/stream.test.js.map +1 -0
  583. package/node_modules/@agentclientprotocol/sdk/dist/test-support/streams.d.ts +8 -0
  584. package/node_modules/@agentclientprotocol/sdk/dist/test-support/streams.js +51 -0
  585. package/node_modules/@agentclientprotocol/sdk/dist/test-support/streams.js.map +1 -0
  586. package/node_modules/@agentclientprotocol/sdk/dist/test-support/test-agent.d.ts +24 -0
  587. package/node_modules/@agentclientprotocol/sdk/dist/test-support/test-agent.js +163 -0
  588. package/node_modules/@agentclientprotocol/sdk/dist/test-support/test-agent.js.map +1 -0
  589. package/node_modules/@agentclientprotocol/sdk/dist/test-support/test-http-server.d.ts +9 -0
  590. package/node_modules/@agentclientprotocol/sdk/dist/test-support/test-http-server.js +74 -0
  591. package/node_modules/@agentclientprotocol/sdk/dist/test-support/test-http-server.js.map +1 -0
  592. package/node_modules/@agentclientprotocol/sdk/dist/v2/acp.d.ts +957 -0
  593. package/node_modules/@agentclientprotocol/sdk/dist/v2/acp.js +1484 -0
  594. package/node_modules/@agentclientprotocol/sdk/dist/v2/acp.js.map +1 -0
  595. package/node_modules/@agentclientprotocol/sdk/dist/v2/acp.test.d.ts +1 -0
  596. package/node_modules/@agentclientprotocol/sdk/dist/v2/acp.test.js +1287 -0
  597. package/node_modules/@agentclientprotocol/sdk/dist/v2/acp.test.js.map +1 -0
  598. package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/guards.gen.d.ts +946 -0
  599. package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/guards.gen.js +1046 -0
  600. package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/guards.gen.js.map +1 -0
  601. package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/index.d.ts +42 -0
  602. package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/index.js +43 -0
  603. package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/index.js.map +1 -0
  604. package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/types.gen.d.ts +6208 -0
  605. package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/types.gen.js +3 -0
  606. package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/types.gen.js.map +1 -0
  607. package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/zod.gen.d.ts +33267 -0
  608. package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/zod.gen.js +3199 -0
  609. package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/zod.gen.js.map +1 -0
  610. package/node_modules/@agentclientprotocol/sdk/dist/ws-server.d.ts +14 -0
  611. package/node_modules/@agentclientprotocol/sdk/dist/ws-server.js +357 -0
  612. package/node_modules/@agentclientprotocol/sdk/dist/ws-server.js.map +1 -0
  613. package/node_modules/@agentclientprotocol/sdk/dist/ws-stream.d.ts +54 -0
  614. package/node_modules/@agentclientprotocol/sdk/dist/ws-stream.js +253 -0
  615. package/node_modules/@agentclientprotocol/sdk/dist/ws-stream.js.map +1 -0
  616. package/node_modules/@agentclientprotocol/sdk/dist/ws-stream.test.d.ts +1 -0
  617. package/node_modules/@agentclientprotocol/sdk/dist/ws-stream.test.js +836 -0
  618. package/node_modules/@agentclientprotocol/sdk/dist/ws-stream.test.js.map +1 -0
  619. package/node_modules/@agentclientprotocol/sdk/dist/ws-utils.d.ts +13 -0
  620. package/node_modules/@agentclientprotocol/sdk/dist/ws-utils.js +78 -0
  621. package/node_modules/@agentclientprotocol/sdk/dist/ws-utils.js.map +1 -0
  622. package/node_modules/@agentclientprotocol/sdk/dist/ws-utils.test.d.ts +1 -0
  623. package/node_modules/@agentclientprotocol/sdk/dist/ws-utils.test.js +42 -0
  624. package/node_modules/@agentclientprotocol/sdk/dist/ws-utils.test.js.map +1 -0
  625. package/node_modules/@agentclientprotocol/sdk/package.json +104 -0
  626. package/node_modules/@agentclientprotocol/sdk/schema/schema.json +8967 -0
  627. package/node_modules/@agentclientprotocol/sdk/schema/v2/schema.unstable.json +10242 -0
  628. package/node_modules/@clack/core/LICENSE +9 -0
  629. package/node_modules/@clack/core/README.md +22 -0
  630. package/node_modules/@clack/core/dist/index.d.mts +503 -0
  631. package/node_modules/@clack/core/dist/index.mjs +1003 -0
  632. package/node_modules/@clack/core/package.json +63 -0
  633. package/node_modules/@clack/prompts/LICENSE +9 -0
  634. package/node_modules/@clack/prompts/README.md +396 -0
  635. package/node_modules/@clack/prompts/dist/index.d.mts +949 -0
  636. package/node_modules/@clack/prompts/dist/index.mjs +1423 -0
  637. package/node_modules/@clack/prompts/package.json +67 -0
  638. package/node_modules/@esbuild/linux-x64/README.md +3 -0
  639. package/node_modules/@esbuild/linux-x64/bin/esbuild +0 -0
  640. package/node_modules/@esbuild/linux-x64/package.json +20 -0
  641. package/node_modules/acpx/LICENSE +21 -0
  642. package/node_modules/acpx/README.md +424 -0
  643. package/node_modules/acpx/dist/cli-CxsS1XQp.js +197 -0
  644. package/node_modules/acpx/dist/cli-CxsS1XQp.js.map +1 -0
  645. package/node_modules/acpx/dist/cli.d.ts +14 -0
  646. package/node_modules/acpx/dist/cli.d.ts.map +1 -0
  647. package/node_modules/acpx/dist/cli.js +2683 -0
  648. package/node_modules/acpx/dist/cli.js.map +1 -0
  649. package/node_modules/acpx/dist/client-DwNyFauq.d.ts +192 -0
  650. package/node_modules/acpx/dist/client-DwNyFauq.d.ts.map +1 -0
  651. package/node_modules/acpx/dist/flags-Bjq_XQ4-.js +260 -0
  652. package/node_modules/acpx/dist/flags-Bjq_XQ4-.js.map +1 -0
  653. package/node_modules/acpx/dist/flows-Db_LZKu9.js +1920 -0
  654. package/node_modules/acpx/dist/flows-Db_LZKu9.js.map +1 -0
  655. package/node_modules/acpx/dist/flows.d.ts +318 -0
  656. package/node_modules/acpx/dist/flows.d.ts.map +1 -0
  657. package/node_modules/acpx/dist/flows.js +2 -0
  658. package/node_modules/acpx/dist/live-checkpoint-ClPCSdrW.js +6210 -0
  659. package/node_modules/acpx/dist/live-checkpoint-ClPCSdrW.js.map +1 -0
  660. package/node_modules/acpx/dist/output-CjdF5rHk.js +4593 -0
  661. package/node_modules/acpx/dist/output-CjdF5rHk.js.map +1 -0
  662. package/node_modules/acpx/dist/runtime.d.ts +472 -0
  663. package/node_modules/acpx/dist/runtime.d.ts.map +1 -0
  664. package/node_modules/acpx/dist/runtime.js +1711 -0
  665. package/node_modules/acpx/dist/runtime.js.map +1 -0
  666. package/node_modules/acpx/dist/session-options-jkYbBxGE.d.ts +278 -0
  667. package/node_modules/acpx/dist/session-options-jkYbBxGE.d.ts.map +1 -0
  668. package/node_modules/acpx/package.json +129 -0
  669. package/node_modules/acpx/skills/acpx/SKILL.md +617 -0
  670. package/node_modules/b4a/LICENSE +201 -0
  671. package/node_modules/b4a/README.md +153 -0
  672. package/node_modules/b4a/browser.js +546 -0
  673. package/node_modules/b4a/index.d.ts +144 -0
  674. package/node_modules/b4a/index.js +193 -0
  675. package/node_modules/b4a/lib/ascii.js +31 -0
  676. package/node_modules/b4a/lib/base64.js +64 -0
  677. package/node_modules/b4a/lib/hex.js +51 -0
  678. package/node_modules/b4a/lib/latin1.js +31 -0
  679. package/node_modules/b4a/lib/utf16le.js +40 -0
  680. package/node_modules/b4a/lib/utf8.js +144 -0
  681. package/node_modules/b4a/package.json +54 -0
  682. package/node_modules/b4a/react-native.js +5 -0
  683. package/node_modules/b4a/runtime.d.ts +14 -0
  684. package/node_modules/bare-events/LICENSE +201 -0
  685. package/node_modules/bare-events/README.md +25 -0
  686. package/node_modules/bare-events/global.d.ts +15 -0
  687. package/node_modules/bare-events/global.js +5 -0
  688. package/node_modules/bare-events/index.d.ts +89 -0
  689. package/node_modules/bare-events/index.js +397 -0
  690. package/node_modules/bare-events/lib/errors.js +23 -0
  691. package/node_modules/bare-events/package.json +63 -0
  692. package/node_modules/bare-events/web.d.ts +79 -0
  693. package/node_modules/bare-events/web.js +417 -0
  694. package/node_modules/bare-fs/CMakeLists.txt +13 -0
  695. package/node_modules/bare-fs/LICENSE +201 -0
  696. package/node_modules/bare-fs/README.md +33 -0
  697. package/node_modules/bare-fs/binding.c +2913 -0
  698. package/node_modules/bare-fs/binding.js +1 -0
  699. package/node_modules/bare-fs/index.d.ts +1502 -0
  700. package/node_modules/bare-fs/index.js +2855 -0
  701. package/node_modules/bare-fs/lib/constants.d.ts +50 -0
  702. package/node_modules/bare-fs/lib/constants.js +49 -0
  703. package/node_modules/bare-fs/lib/errors.d.ts +13 -0
  704. package/node_modules/bare-fs/lib/errors.js +55 -0
  705. package/node_modules/bare-fs/package.json +74 -0
  706. package/node_modules/bare-fs/prebuilds/android-arm/bare-fs.bare +0 -0
  707. package/node_modules/bare-fs/prebuilds/android-arm64/bare-fs.bare +0 -0
  708. package/node_modules/bare-fs/prebuilds/android-ia32/bare-fs.bare +0 -0
  709. package/node_modules/bare-fs/prebuilds/android-x64/bare-fs.bare +0 -0
  710. package/node_modules/bare-fs/prebuilds/darwin-arm64/bare-fs.bare +0 -0
  711. package/node_modules/bare-fs/prebuilds/darwin-x64/bare-fs.bare +0 -0
  712. package/node_modules/bare-fs/prebuilds/ios-arm64/bare-fs.bare +0 -0
  713. package/node_modules/bare-fs/prebuilds/ios-arm64-simulator/bare-fs.bare +0 -0
  714. package/node_modules/bare-fs/prebuilds/ios-x64-simulator/bare-fs.bare +0 -0
  715. package/node_modules/bare-fs/prebuilds/linux-arm64/bare-fs.bare +0 -0
  716. package/node_modules/bare-fs/prebuilds/linux-x64/bare-fs.bare +0 -0
  717. package/node_modules/bare-fs/prebuilds/win32-arm64/bare-fs.bare +0 -0
  718. package/node_modules/bare-fs/prebuilds/win32-x64/bare-fs.bare +0 -0
  719. package/node_modules/bare-fs/promises.d.ts +480 -0
  720. package/node_modules/bare-fs/promises.js +119 -0
  721. package/node_modules/bare-path/CMakeLists.txt +13 -0
  722. package/node_modules/bare-path/LICENSE +201 -0
  723. package/node_modules/bare-path/NOTICE +28 -0
  724. package/node_modules/bare-path/README.md +19 -0
  725. package/node_modules/bare-path/binding.c +106 -0
  726. package/node_modules/bare-path/binding.js +1 -0
  727. package/node_modules/bare-path/index.d.ts +27 -0
  728. package/node_modules/bare-path/index.js +9 -0
  729. package/node_modules/bare-path/lib/constants.js +11 -0
  730. package/node_modules/bare-path/lib/posix.js +263 -0
  731. package/node_modules/bare-path/lib/shared.js +81 -0
  732. package/node_modules/bare-path/lib/win32.js +564 -0
  733. package/node_modules/bare-path/package.json +47 -0
  734. package/node_modules/bare-path/prebuilds/android-arm/bare-path.bare +0 -0
  735. package/node_modules/bare-path/prebuilds/android-arm64/bare-path.bare +0 -0
  736. package/node_modules/bare-path/prebuilds/android-ia32/bare-path.bare +0 -0
  737. package/node_modules/bare-path/prebuilds/android-x64/bare-path.bare +0 -0
  738. package/node_modules/bare-path/prebuilds/darwin-arm64/bare-path.bare +0 -0
  739. package/node_modules/bare-path/prebuilds/darwin-x64/bare-path.bare +0 -0
  740. package/node_modules/bare-path/prebuilds/ios-arm64/bare-path.bare +0 -0
  741. package/node_modules/bare-path/prebuilds/ios-arm64-simulator/bare-path.bare +0 -0
  742. package/node_modules/bare-path/prebuilds/ios-x64-simulator/bare-path.bare +0 -0
  743. package/node_modules/bare-path/prebuilds/linux-arm64/bare-path.bare +0 -0
  744. package/node_modules/bare-path/prebuilds/linux-x64/bare-path.bare +0 -0
  745. package/node_modules/bare-path/prebuilds/win32-arm64/bare-path.bare +0 -0
  746. package/node_modules/bare-path/prebuilds/win32-x64/bare-path.bare +0 -0
  747. package/node_modules/bare-stream/LICENSE +201 -0
  748. package/node_modules/bare-stream/README.md +354 -0
  749. package/node_modules/bare-stream/global.d.ts +31 -0
  750. package/node_modules/bare-stream/global.js +15 -0
  751. package/node_modules/bare-stream/index.d.ts +235 -0
  752. package/node_modules/bare-stream/index.js +493 -0
  753. package/node_modules/bare-stream/package.json +75 -0
  754. package/node_modules/bare-stream/promises.js +3 -0
  755. package/node_modules/bare-stream/web.d.ts +152 -0
  756. package/node_modules/bare-stream/web.js +607 -0
  757. package/node_modules/bare-url/CMakeLists.txt +36 -0
  758. package/node_modules/bare-url/LICENSE +201 -0
  759. package/node_modules/bare-url/README.md +33 -0
  760. package/node_modules/bare-url/binding.c +422 -0
  761. package/node_modules/bare-url/binding.js +1 -0
  762. package/node_modules/bare-url/global.d.ts +24 -0
  763. package/node_modules/bare-url/global.js +2 -0
  764. package/node_modules/bare-url/index.d.ts +96 -0
  765. package/node_modules/bare-url/index.js +676 -0
  766. package/node_modules/bare-url/lib/errors.d.ts +10 -0
  767. package/node_modules/bare-url/lib/errors.js +33 -0
  768. package/node_modules/bare-url/lib/url-search-params.d.ts +66 -0
  769. package/node_modules/bare-url/lib/url-search-params.js +276 -0
  770. package/node_modules/bare-url/package.json +54 -0
  771. package/node_modules/bare-url/prebuilds/android-arm/bare-url.bare +0 -0
  772. package/node_modules/bare-url/prebuilds/android-arm64/bare-url.bare +0 -0
  773. package/node_modules/bare-url/prebuilds/android-ia32/bare-url.bare +0 -0
  774. package/node_modules/bare-url/prebuilds/android-x64/bare-url.bare +0 -0
  775. package/node_modules/bare-url/prebuilds/darwin-arm64/bare-url.bare +0 -0
  776. package/node_modules/bare-url/prebuilds/darwin-x64/bare-url.bare +0 -0
  777. package/node_modules/bare-url/prebuilds/ios-arm64/bare-url.bare +0 -0
  778. package/node_modules/bare-url/prebuilds/ios-arm64-simulator/bare-url.bare +0 -0
  779. package/node_modules/bare-url/prebuilds/ios-x64-simulator/bare-url.bare +0 -0
  780. package/node_modules/bare-url/prebuilds/linux-arm64/bare-url.bare +0 -0
  781. package/node_modules/bare-url/prebuilds/linux-x64/bare-url.bare +0 -0
  782. package/node_modules/bare-url/prebuilds/win32-arm64/bare-url.bare +0 -0
  783. package/node_modules/bare-url/prebuilds/win32-x64/bare-url.bare +0 -0
  784. package/node_modules/commander/LICENSE +22 -0
  785. package/node_modules/commander/Readme.md +1172 -0
  786. package/node_modules/commander/index.js +21 -0
  787. package/node_modules/commander/lib/argument.js +147 -0
  788. package/node_modules/commander/lib/command.js +2790 -0
  789. package/node_modules/commander/lib/error.js +36 -0
  790. package/node_modules/commander/lib/help.js +731 -0
  791. package/node_modules/commander/lib/option.js +377 -0
  792. package/node_modules/commander/lib/suggestSimilar.js +99 -0
  793. package/node_modules/commander/package-support.json +19 -0
  794. package/node_modules/commander/package.json +64 -0
  795. package/node_modules/commander/typings/index.d.ts +1113 -0
  796. package/node_modules/esbuild/LICENSE.md +21 -0
  797. package/node_modules/esbuild/README.md +3 -0
  798. package/node_modules/esbuild/bin/esbuild +223 -0
  799. package/node_modules/esbuild/install.js +300 -0
  800. package/node_modules/esbuild/lib/main.d.ts +720 -0
  801. package/node_modules/esbuild/lib/main.js +2536 -0
  802. package/node_modules/esbuild/package.json +74 -0
  803. package/node_modules/events-universal/LICENSE +201 -0
  804. package/node_modules/events-universal/README.md +17 -0
  805. package/node_modules/events-universal/bare.js +1 -0
  806. package/node_modules/events-universal/default.js +1 -0
  807. package/node_modules/events-universal/index.js +1 -0
  808. package/node_modules/events-universal/package.json +39 -0
  809. package/node_modules/events-universal/react-native.js +1 -0
  810. package/node_modules/fast-fifo/LICENSE +21 -0
  811. package/node_modules/fast-fifo/README.md +78 -0
  812. package/node_modules/fast-fifo/fixed-size.js +39 -0
  813. package/node_modules/fast-fifo/index.js +48 -0
  814. package/node_modules/fast-fifo/package.json +28 -0
  815. package/node_modules/fast-string-truncated-width/dist/index.d.ts +4 -0
  816. package/node_modules/fast-string-truncated-width/dist/index.js +111 -0
  817. package/node_modules/fast-string-truncated-width/dist/types.d.ts +19 -0
  818. package/node_modules/fast-string-truncated-width/dist/types.js +2 -0
  819. package/node_modules/fast-string-truncated-width/dist/utils.d.ts +4 -0
  820. package/node_modules/fast-string-truncated-width/dist/utils.js +20 -0
  821. package/node_modules/fast-string-truncated-width/license +21 -0
  822. package/node_modules/fast-string-truncated-width/package.json +35 -0
  823. package/node_modules/fast-string-truncated-width/readme.md +59 -0
  824. package/node_modules/fast-string-width/dist/index.d.ts +4 -0
  825. package/node_modules/fast-string-width/dist/index.js +14 -0
  826. package/node_modules/fast-string-width/license +21 -0
  827. package/node_modules/fast-string-width/package.json +34 -0
  828. package/node_modules/fast-string-width/readme.md +42 -0
  829. package/node_modules/fast-wrap-ansi/LICENSE +23 -0
  830. package/node_modules/fast-wrap-ansi/README.md +26 -0
  831. package/node_modules/fast-wrap-ansi/lib/main.d.ts +6 -0
  832. package/node_modules/fast-wrap-ansi/lib/main.js +218 -0
  833. package/node_modules/fast-wrap-ansi/package.json +51 -0
  834. package/node_modules/sisteransi/license +21 -0
  835. package/node_modules/sisteransi/package.json +34 -0
  836. package/node_modules/sisteransi/readme.md +113 -0
  837. package/node_modules/sisteransi/src/index.js +58 -0
  838. package/node_modules/sisteransi/src/sisteransi.d.ts +35 -0
  839. package/node_modules/skillflag/LICENSE +21 -0
  840. package/node_modules/skillflag/README.md +44 -0
  841. package/node_modules/skillflag/dist/bin/skill-install.d.ts +3 -0
  842. package/node_modules/skillflag/dist/bin/skill-install.d.ts.map +1 -0
  843. package/node_modules/skillflag/dist/bin/skill-install.js +6 -0
  844. package/node_modules/skillflag/dist/bin/skill-install.js.map +1 -0
  845. package/node_modules/skillflag/dist/bin/skillflag.d.ts +3 -0
  846. package/node_modules/skillflag/dist/bin/skillflag.d.ts.map +1 -0
  847. package/node_modules/skillflag/dist/bin/skillflag.js +25 -0
  848. package/node_modules/skillflag/dist/bin/skillflag.js.map +1 -0
  849. package/node_modules/skillflag/dist/core/digest.d.ts +2 -0
  850. package/node_modules/skillflag/dist/core/digest.d.ts.map +1 -0
  851. package/node_modules/skillflag/dist/core/digest.js +15 -0
  852. package/node_modules/skillflag/dist/core/digest.js.map +1 -0
  853. package/node_modules/skillflag/dist/core/errors.d.ts +6 -0
  854. package/node_modules/skillflag/dist/core/errors.d.ts.map +1 -0
  855. package/node_modules/skillflag/dist/core/errors.js +13 -0
  856. package/node_modules/skillflag/dist/core/errors.js.map +1 -0
  857. package/node_modules/skillflag/dist/core/export.d.ts +2 -0
  858. package/node_modules/skillflag/dist/core/export.d.ts.map +1 -0
  859. package/node_modules/skillflag/dist/core/export.js +28 -0
  860. package/node_modules/skillflag/dist/core/export.js.map +1 -0
  861. package/node_modules/skillflag/dist/core/list.d.ts +21 -0
  862. package/node_modules/skillflag/dist/core/list.d.ts.map +1 -0
  863. package/node_modules/skillflag/dist/core/list.js +71 -0
  864. package/node_modules/skillflag/dist/core/list.js.map +1 -0
  865. package/node_modules/skillflag/dist/core/paths.d.ts +15 -0
  866. package/node_modules/skillflag/dist/core/paths.d.ts.map +1 -0
  867. package/node_modules/skillflag/dist/core/paths.js +149 -0
  868. package/node_modules/skillflag/dist/core/paths.js.map +1 -0
  869. package/node_modules/skillflag/dist/core/show.d.ts +2 -0
  870. package/node_modules/skillflag/dist/core/show.d.ts.map +1 -0
  871. package/node_modules/skillflag/dist/core/show.js +8 -0
  872. package/node_modules/skillflag/dist/core/show.js.map +1 -0
  873. package/node_modules/skillflag/dist/core/skillflag.d.ts +3 -0
  874. package/node_modules/skillflag/dist/core/skillflag.d.ts.map +1 -0
  875. package/node_modules/skillflag/dist/core/skillflag.js +2 -0
  876. package/node_modules/skillflag/dist/core/skillflag.js.map +1 -0
  877. package/node_modules/skillflag/dist/core/tar.d.ts +16 -0
  878. package/node_modules/skillflag/dist/core/tar.d.ts.map +1 -0
  879. package/node_modules/skillflag/dist/core/tar.js +140 -0
  880. package/node_modules/skillflag/dist/core/tar.js.map +1 -0
  881. package/node_modules/skillflag/dist/index.d.ts +5 -0
  882. package/node_modules/skillflag/dist/index.d.ts.map +1 -0
  883. package/node_modules/skillflag/dist/index.js +3 -0
  884. package/node_modules/skillflag/dist/index.js.map +1 -0
  885. package/node_modules/skillflag/dist/install/cli.d.ts +57 -0
  886. package/node_modules/skillflag/dist/install/cli.d.ts.map +1 -0
  887. package/node_modules/skillflag/dist/install/cli.js +653 -0
  888. package/node_modules/skillflag/dist/install/cli.js.map +1 -0
  889. package/node_modules/skillflag/dist/install/copy.d.ts +2 -0
  890. package/node_modules/skillflag/dist/install/copy.d.ts.map +1 -0
  891. package/node_modules/skillflag/dist/install/copy.js +19 -0
  892. package/node_modules/skillflag/dist/install/copy.js.map +1 -0
  893. package/node_modules/skillflag/dist/install/errors.d.ts +6 -0
  894. package/node_modules/skillflag/dist/install/errors.d.ts.map +1 -0
  895. package/node_modules/skillflag/dist/install/errors.js +13 -0
  896. package/node_modules/skillflag/dist/install/errors.js.map +1 -0
  897. package/node_modules/skillflag/dist/install/extract.d.ts +3 -0
  898. package/node_modules/skillflag/dist/install/extract.d.ts.map +1 -0
  899. package/node_modules/skillflag/dist/install/extract.js +76 -0
  900. package/node_modules/skillflag/dist/install/extract.js.map +1 -0
  901. package/node_modules/skillflag/dist/install/install.d.ts +21 -0
  902. package/node_modules/skillflag/dist/install/install.d.ts.map +1 -0
  903. package/node_modules/skillflag/dist/install/install.js +35 -0
  904. package/node_modules/skillflag/dist/install/install.js.map +1 -0
  905. package/node_modules/skillflag/dist/install/resolve.d.ts +12 -0
  906. package/node_modules/skillflag/dist/install/resolve.d.ts.map +1 -0
  907. package/node_modules/skillflag/dist/install/resolve.js +119 -0
  908. package/node_modules/skillflag/dist/install/resolve.js.map +1 -0
  909. package/node_modules/skillflag/dist/install/validate.d.ts +7 -0
  910. package/node_modules/skillflag/dist/install/validate.d.ts.map +1 -0
  911. package/node_modules/skillflag/dist/install/validate.js +28 -0
  912. package/node_modules/skillflag/dist/install/validate.js.map +1 -0
  913. package/node_modules/skillflag/dist/shared/frontmatter.d.ts +2 -0
  914. package/node_modules/skillflag/dist/shared/frontmatter.d.ts.map +1 -0
  915. package/node_modules/skillflag/dist/shared/frontmatter.js +28 -0
  916. package/node_modules/skillflag/dist/shared/frontmatter.js.map +1 -0
  917. package/node_modules/skillflag/dist/skillflag.d.ts +29 -0
  918. package/node_modules/skillflag/dist/skillflag.d.ts.map +1 -0
  919. package/node_modules/skillflag/dist/skillflag.js +230 -0
  920. package/node_modules/skillflag/dist/skillflag.js.map +1 -0
  921. package/node_modules/skillflag/dist/utils/collections.d.ts +3 -0
  922. package/node_modules/skillflag/dist/utils/collections.d.ts.map +1 -0
  923. package/node_modules/skillflag/dist/utils/collections.js +18 -0
  924. package/node_modules/skillflag/dist/utils/collections.js.map +1 -0
  925. package/node_modules/skillflag/package.json +78 -0
  926. package/node_modules/skillflag/skills/skillflag/SKILL.md +144 -0
  927. package/node_modules/streamx/LICENSE +21 -0
  928. package/node_modules/streamx/README.md +550 -0
  929. package/node_modules/streamx/index.d.ts +198 -0
  930. package/node_modules/streamx/index.js +1314 -0
  931. package/node_modules/streamx/lib/errors.d.ts +13 -0
  932. package/node_modules/streamx/lib/errors.js +47 -0
  933. package/node_modules/streamx/package.json +51 -0
  934. package/node_modules/streamx/runtime.d.ts +55 -0
  935. package/node_modules/tar-stream/LICENSE +21 -0
  936. package/node_modules/tar-stream/README.md +183 -0
  937. package/node_modules/tar-stream/constants.js +14 -0
  938. package/node_modules/tar-stream/extract.js +418 -0
  939. package/node_modules/tar-stream/headers.js +322 -0
  940. package/node_modules/tar-stream/index.d.ts +60 -0
  941. package/node_modules/tar-stream/index.js +2 -0
  942. package/node_modules/tar-stream/pack.js +287 -0
  943. package/node_modules/tar-stream/package.json +50 -0
  944. package/node_modules/teex/LICENSE +21 -0
  945. package/node_modules/teex/README.md +31 -0
  946. package/node_modules/teex/example.js +16 -0
  947. package/node_modules/teex/index.js +52 -0
  948. package/node_modules/teex/package.json +26 -0
  949. package/node_modules/teex/test.js +68 -0
  950. package/node_modules/text-decoder/LICENSE +201 -0
  951. package/node_modules/text-decoder/README.md +36 -0
  952. package/node_modules/text-decoder/index.js +64 -0
  953. package/node_modules/text-decoder/lib/pass-through-decoder.js +19 -0
  954. package/node_modules/text-decoder/lib/utf8-decoder.js +185 -0
  955. package/node_modules/text-decoder/package.json +39 -0
  956. package/node_modules/tsx/LICENSE +21 -0
  957. package/node_modules/tsx/README.md +32 -0
  958. package/node_modules/tsx/dist/cjs/api/index.cjs +2 -0
  959. package/node_modules/tsx/dist/cjs/api/index.d.cts +35 -0
  960. package/node_modules/tsx/dist/cjs/api/index.d.mts +35 -0
  961. package/node_modules/tsx/dist/cjs/api/index.mjs +2 -0
  962. package/node_modules/tsx/dist/cjs/index.cjs +1 -0
  963. package/node_modules/tsx/dist/cli.mjs +55 -0
  964. package/node_modules/tsx/dist/client-XItNFmsq.cjs +1 -0
  965. package/node_modules/tsx/dist/esm/api/index.cjs +2 -0
  966. package/node_modules/tsx/dist/esm/api/index.d.cts +37 -0
  967. package/node_modules/tsx/dist/esm/api/index.d.mts +37 -0
  968. package/node_modules/tsx/dist/esm/api/index.mjs +2 -0
  969. package/node_modules/tsx/dist/esm/index.mjs +2 -0
  970. package/node_modules/tsx/dist/index-Bqjv9TxC.mjs +1 -0
  971. package/node_modules/tsx/dist/index-DE3OBZuV.mjs +14 -0
  972. package/node_modules/tsx/dist/lexer-CGfpMDSb.cjs +3 -0
  973. package/node_modules/tsx/dist/lexer-CanA6ArK.mjs +3 -0
  974. package/node_modules/tsx/dist/loader.mjs +2 -0
  975. package/node_modules/tsx/dist/package-Dj0mHsMt.mjs +1 -0
  976. package/node_modules/tsx/dist/patch-repl.cjs +1 -0
  977. package/node_modules/tsx/dist/preflight.cjs +1 -0
  978. package/node_modules/tsx/dist/register-B1c7OH6V.cjs +23 -0
  979. package/node_modules/tsx/dist/register-nyXW-TH3.mjs +2 -0
  980. package/node_modules/tsx/dist/repl.mjs +4 -0
  981. package/node_modules/tsx/dist/require-CBjy4Foe.mjs +10 -0
  982. package/node_modules/tsx/dist/require-CVaYn9z3.cjs +1 -0
  983. package/node_modules/tsx/dist/suppress-warnings.cjs +1 -0
  984. package/node_modules/tsx/dist/temporary-directory-Du7LpLp9.mjs +1 -0
  985. package/node_modules/tsx/package.json +70 -0
  986. package/node_modules/zod/LICENSE +21 -0
  987. package/node_modules/zod/README.md +218 -0
  988. package/node_modules/zod/compile.cjs +96 -0
  989. package/node_modules/zod/compile.d.cts +1 -0
  990. package/node_modules/zod/compile.d.ts +1 -0
  991. package/node_modules/zod/compile.js +52 -0
  992. package/node_modules/zod/index.cjs +52 -0
  993. package/node_modules/zod/index.d.cts +3 -0
  994. package/node_modules/zod/index.d.ts +3 -0
  995. package/node_modules/zod/index.js +7 -0
  996. package/node_modules/zod/locales/index.cjs +36 -0
  997. package/node_modules/zod/locales/index.d.cts +1 -0
  998. package/node_modules/zod/locales/index.d.ts +1 -0
  999. package/node_modules/zod/locales/index.js +1 -0
  1000. package/node_modules/zod/locales/package.json +7 -0
  1001. package/node_modules/zod/mini/index.cjs +51 -0
  1002. package/node_modules/zod/mini/index.d.cts +3 -0
  1003. package/node_modules/zod/mini/index.d.ts +3 -0
  1004. package/node_modules/zod/mini/index.js +3 -0
  1005. package/node_modules/zod/mini/package.json +7 -0
  1006. package/node_modules/zod/package.json +147 -0
  1007. package/node_modules/zod/src/compile.ts +56 -0
  1008. package/node_modules/zod/src/index.ts +7 -0
  1009. package/node_modules/zod/src/locales/index.ts +1 -0
  1010. package/node_modules/zod/src/mini/index.ts +3 -0
  1011. package/node_modules/zod/src/v3/ZodError.ts +340 -0
  1012. package/node_modules/zod/src/v3/benchmarks/datetime.ts +58 -0
  1013. package/node_modules/zod/src/v3/benchmarks/discriminatedUnion.ts +80 -0
  1014. package/node_modules/zod/src/v3/benchmarks/index.ts +59 -0
  1015. package/node_modules/zod/src/v3/benchmarks/ipv4.ts +57 -0
  1016. package/node_modules/zod/src/v3/benchmarks/object.ts +69 -0
  1017. package/node_modules/zod/src/v3/benchmarks/primitives.ts +162 -0
  1018. package/node_modules/zod/src/v3/benchmarks/realworld.ts +63 -0
  1019. package/node_modules/zod/src/v3/benchmarks/string.ts +55 -0
  1020. package/node_modules/zod/src/v3/benchmarks/union.ts +80 -0
  1021. package/node_modules/zod/src/v3/errors.ts +13 -0
  1022. package/node_modules/zod/src/v3/external.ts +6 -0
  1023. package/node_modules/zod/src/v3/helpers/enumUtil.ts +17 -0
  1024. package/node_modules/zod/src/v3/helpers/errorUtil.ts +8 -0
  1025. package/node_modules/zod/src/v3/helpers/parseUtil.ts +176 -0
  1026. package/node_modules/zod/src/v3/helpers/partialUtil.ts +34 -0
  1027. package/node_modules/zod/src/v3/helpers/typeAliases.ts +2 -0
  1028. package/node_modules/zod/src/v3/helpers/util.ts +224 -0
  1029. package/node_modules/zod/src/v3/index.ts +4 -0
  1030. package/node_modules/zod/src/v3/locales/en.ts +124 -0
  1031. package/node_modules/zod/src/v3/standard-schema.ts +113 -0
  1032. package/node_modules/zod/src/v3/tests/Mocker.ts +54 -0
  1033. package/node_modules/zod/src/v3/tests/all-errors.test.ts +157 -0
  1034. package/node_modules/zod/src/v3/tests/anyunknown.test.ts +28 -0
  1035. package/node_modules/zod/src/v3/tests/array.test.ts +71 -0
  1036. package/node_modules/zod/src/v3/tests/async-parsing.test.ts +388 -0
  1037. package/node_modules/zod/src/v3/tests/async-refinements.test.ts +46 -0
  1038. package/node_modules/zod/src/v3/tests/base.test.ts +29 -0
  1039. package/node_modules/zod/src/v3/tests/bigint.test.ts +55 -0
  1040. package/node_modules/zod/src/v3/tests/branded.test.ts +53 -0
  1041. package/node_modules/zod/src/v3/tests/catch.test.ts +220 -0
  1042. package/node_modules/zod/src/v3/tests/coerce.test.ts +133 -0
  1043. package/node_modules/zod/src/v3/tests/complex.test.ts +70 -0
  1044. package/node_modules/zod/src/v3/tests/custom.test.ts +31 -0
  1045. package/node_modules/zod/src/v3/tests/date.test.ts +32 -0
  1046. package/node_modules/zod/src/v3/tests/deepmasking.test.ts +186 -0
  1047. package/node_modules/zod/src/v3/tests/default.test.ts +112 -0
  1048. package/node_modules/zod/src/v3/tests/description.test.ts +33 -0
  1049. package/node_modules/zod/src/v3/tests/discriminated-unions.test.ts +315 -0
  1050. package/node_modules/zod/src/v3/tests/enum.test.ts +80 -0
  1051. package/node_modules/zod/src/v3/tests/error.test.ts +586 -0
  1052. package/node_modules/zod/src/v3/tests/firstparty.test.ts +87 -0
  1053. package/node_modules/zod/src/v3/tests/firstpartyschematypes.test.ts +21 -0
  1054. package/node_modules/zod/src/v3/tests/function.test.ts +261 -0
  1055. package/node_modules/zod/src/v3/tests/generics.test.ts +48 -0
  1056. package/node_modules/zod/src/v3/tests/instanceof.test.ts +37 -0
  1057. package/node_modules/zod/src/v3/tests/intersection.test.ts +128 -0
  1058. package/node_modules/zod/src/v3/tests/language-server.source.ts +76 -0
  1059. package/node_modules/zod/src/v3/tests/language-server.test.ts +206 -0
  1060. package/node_modules/zod/src/v3/tests/literal.test.ts +36 -0
  1061. package/node_modules/zod/src/v3/tests/map.test.ts +110 -0
  1062. package/node_modules/zod/src/v3/tests/masking.test.ts +4 -0
  1063. package/node_modules/zod/src/v3/tests/mocker.test.ts +19 -0
  1064. package/node_modules/zod/src/v3/tests/nan.test.ts +24 -0
  1065. package/node_modules/zod/src/v3/tests/nativeEnum.test.ts +87 -0
  1066. package/node_modules/zod/src/v3/tests/nullable.test.ts +42 -0
  1067. package/node_modules/zod/src/v3/tests/number.test.ts +176 -0
  1068. package/node_modules/zod/src/v3/tests/object-augmentation.test.ts +29 -0
  1069. package/node_modules/zod/src/v3/tests/object-in-es5-env.test.ts +29 -0
  1070. package/node_modules/zod/src/v3/tests/object.test.ts +431 -0
  1071. package/node_modules/zod/src/v3/tests/optional.test.ts +42 -0
  1072. package/node_modules/zod/src/v3/tests/parseUtil.test.ts +23 -0
  1073. package/node_modules/zod/src/v3/tests/parser.test.ts +41 -0
  1074. package/node_modules/zod/src/v3/tests/partials.test.ts +243 -0
  1075. package/node_modules/zod/src/v3/tests/pickomit.test.ts +111 -0
  1076. package/node_modules/zod/src/v3/tests/pipeline.test.ts +29 -0
  1077. package/node_modules/zod/src/v3/tests/preprocess.test.ts +186 -0
  1078. package/node_modules/zod/src/v3/tests/primitive.test.ts +440 -0
  1079. package/node_modules/zod/src/v3/tests/promise.test.ts +90 -0
  1080. package/node_modules/zod/src/v3/tests/readonly.test.ts +114 -0
  1081. package/node_modules/zod/src/v3/tests/record.test.ts +171 -0
  1082. package/node_modules/zod/src/v3/tests/recursive.test.ts +197 -0
  1083. package/node_modules/zod/src/v3/tests/refine.test.ts +313 -0
  1084. package/node_modules/zod/src/v3/tests/safeparse.test.ts +27 -0
  1085. package/node_modules/zod/src/v3/tests/set.test.ts +142 -0
  1086. package/node_modules/zod/src/v3/tests/standard-schema.test.ts +83 -0
  1087. package/node_modules/zod/src/v3/tests/string.test.ts +922 -0
  1088. package/node_modules/zod/src/v3/tests/transformer.test.ts +233 -0
  1089. package/node_modules/zod/src/v3/tests/tuple.test.ts +90 -0
  1090. package/node_modules/zod/src/v3/tests/unions.test.ts +57 -0
  1091. package/node_modules/zod/src/v3/tests/validations.test.ts +133 -0
  1092. package/node_modules/zod/src/v3/tests/void.test.ts +15 -0
  1093. package/node_modules/zod/src/v3/types.ts +5138 -0
  1094. package/node_modules/zod/src/v4/classic/checks.ts +33 -0
  1095. package/node_modules/zod/src/v4/classic/coerce.ts +27 -0
  1096. package/node_modules/zod/src/v4/classic/compat.ts +78 -0
  1097. package/node_modules/zod/src/v4/classic/deep-partial.ts +76 -0
  1098. package/node_modules/zod/src/v4/classic/errors.ts +97 -0
  1099. package/node_modules/zod/src/v4/classic/external.ts +57 -0
  1100. package/node_modules/zod/src/v4/classic/from-json-schema.ts +944 -0
  1101. package/node_modules/zod/src/v4/classic/in-out.ts +45 -0
  1102. package/node_modules/zod/src/v4/classic/index.ts +5 -0
  1103. package/node_modules/zod/src/v4/classic/iso.ts +20 -0
  1104. package/node_modules/zod/src/v4/classic/parse.ts +88 -0
  1105. package/node_modules/zod/src/v4/classic/schemas.ts +2937 -0
  1106. package/node_modules/zod/src/v4/classic/tests/anyunknown.test.ts +26 -0
  1107. package/node_modules/zod/src/v4/classic/tests/apply.test.ts +86 -0
  1108. package/node_modules/zod/src/v4/classic/tests/array.test.ts +264 -0
  1109. package/node_modules/zod/src/v4/classic/tests/assignability.test.ts +402 -0
  1110. package/node_modules/zod/src/v4/classic/tests/async-parsing.test.ts +381 -0
  1111. package/node_modules/zod/src/v4/classic/tests/async-refinements.test.ts +68 -0
  1112. package/node_modules/zod/src/v4/classic/tests/base.test.ts +7 -0
  1113. package/node_modules/zod/src/v4/classic/tests/bigint.test.ts +89 -0
  1114. package/node_modules/zod/src/v4/classic/tests/brand.test.ts +106 -0
  1115. package/node_modules/zod/src/v4/classic/tests/catch.test.ts +604 -0
  1116. package/node_modules/zod/src/v4/classic/tests/coalesce.test.ts +20 -0
  1117. package/node_modules/zod/src/v4/classic/tests/codec-examples.test.ts +572 -0
  1118. package/node_modules/zod/src/v4/classic/tests/codec.test.ts +704 -0
  1119. package/node_modules/zod/src/v4/classic/tests/coerce.test.ts +160 -0
  1120. package/node_modules/zod/src/v4/classic/tests/continuability.test.ts +374 -0
  1121. package/node_modules/zod/src/v4/classic/tests/credit-card.test.ts +76 -0
  1122. package/node_modules/zod/src/v4/classic/tests/custom.test.ts +40 -0
  1123. package/node_modules/zod/src/v4/classic/tests/cyclic-data.test.ts +985 -0
  1124. package/node_modules/zod/src/v4/classic/tests/date.test.ts +92 -0
  1125. package/node_modules/zod/src/v4/classic/tests/datetime.test.ts +316 -0
  1126. package/node_modules/zod/src/v4/classic/tests/deep-partial.test.ts +380 -0
  1127. package/node_modules/zod/src/v4/classic/tests/default-locale.test.ts +32 -0
  1128. package/node_modules/zod/src/v4/classic/tests/default.test.ts +455 -0
  1129. package/node_modules/zod/src/v4/classic/tests/describe-meta-checks.test.ts +27 -0
  1130. package/node_modules/zod/src/v4/classic/tests/description.test.ts +32 -0
  1131. package/node_modules/zod/src/v4/classic/tests/detached-methods.test.ts +221 -0
  1132. package/node_modules/zod/src/v4/classic/tests/discriminated-unions.test.ts +942 -0
  1133. package/node_modules/zod/src/v4/classic/tests/enum.test.ts +305 -0
  1134. package/node_modules/zod/src/v4/classic/tests/error-utils.test.ts +917 -0
  1135. package/node_modules/zod/src/v4/classic/tests/error.test.ts +1108 -0
  1136. package/node_modules/zod/src/v4/classic/tests/file.test.ts +96 -0
  1137. package/node_modules/zod/src/v4/classic/tests/firstparty.test.ts +179 -0
  1138. package/node_modules/zod/src/v4/classic/tests/fix-json-issue.test.ts +26 -0
  1139. package/node_modules/zod/src/v4/classic/tests/from-json-schema.test.ts +1467 -0
  1140. package/node_modules/zod/src/v4/classic/tests/function.test.ts +391 -0
  1141. package/node_modules/zod/src/v4/classic/tests/generics.test.ts +72 -0
  1142. package/node_modules/zod/src/v4/classic/tests/global-config.test.ts +32 -0
  1143. package/node_modules/zod/src/v4/classic/tests/hash.test.ts +67 -0
  1144. package/node_modules/zod/src/v4/classic/tests/iban.test.ts +81 -0
  1145. package/node_modules/zod/src/v4/classic/tests/in-out.test.ts +159 -0
  1146. package/node_modules/zod/src/v4/classic/tests/index.test.ts +958 -0
  1147. package/node_modules/zod/src/v4/classic/tests/instance-footprint.test.ts +319 -0
  1148. package/node_modules/zod/src/v4/classic/tests/instanceof.test.ts +127 -0
  1149. package/node_modules/zod/src/v4/classic/tests/intersection.test.ts +334 -0
  1150. package/node_modules/zod/src/v4/classic/tests/issue-schema.test.ts +215 -0
  1151. package/node_modules/zod/src/v4/classic/tests/jitless-allows-eval.test.ts +39 -0
  1152. package/node_modules/zod/src/v4/classic/tests/json.test.ts +109 -0
  1153. package/node_modules/zod/src/v4/classic/tests/lazy.test.ts +262 -0
  1154. package/node_modules/zod/src/v4/classic/tests/literal.test.ts +128 -0
  1155. package/node_modules/zod/src/v4/classic/tests/locales_ka.test.ts +29 -0
  1156. package/node_modules/zod/src/v4/classic/tests/locales_ro.test.ts +24 -0
  1157. package/node_modules/zod/src/v4/classic/tests/map.test.ts +342 -0
  1158. package/node_modules/zod/src/v4/classic/tests/nan.test.ts +21 -0
  1159. package/node_modules/zod/src/v4/classic/tests/nested-refine.test.ts +168 -0
  1160. package/node_modules/zod/src/v4/classic/tests/nonoptional.test.ts +101 -0
  1161. package/node_modules/zod/src/v4/classic/tests/nullable.test.ts +22 -0
  1162. package/node_modules/zod/src/v4/classic/tests/number.test.ts +401 -0
  1163. package/node_modules/zod/src/v4/classic/tests/object.test.ts +1006 -0
  1164. package/node_modules/zod/src/v4/classic/tests/optin-ladder.test.ts +235 -0
  1165. package/node_modules/zod/src/v4/classic/tests/optional.test.ts +366 -0
  1166. package/node_modules/zod/src/v4/classic/tests/partial.test.ts +497 -0
  1167. package/node_modules/zod/src/v4/classic/tests/pickomit.test.ts +211 -0
  1168. package/node_modules/zod/src/v4/classic/tests/pipe.test.ts +101 -0
  1169. package/node_modules/zod/src/v4/classic/tests/prefault.test.ts +144 -0
  1170. package/node_modules/zod/src/v4/classic/tests/preprocess-types.test.ts +44 -0
  1171. package/node_modules/zod/src/v4/classic/tests/preprocess.test.ts +351 -0
  1172. package/node_modules/zod/src/v4/classic/tests/primitive.test.ts +175 -0
  1173. package/node_modules/zod/src/v4/classic/tests/promise.test.ts +81 -0
  1174. package/node_modules/zod/src/v4/classic/tests/properties.test.ts +141 -0
  1175. package/node_modules/zod/src/v4/classic/tests/prototypes.test.ts +23 -0
  1176. package/node_modules/zod/src/v4/classic/tests/readonly.test.ts +184 -0
  1177. package/node_modules/zod/src/v4/classic/tests/record.test.ts +885 -0
  1178. package/node_modules/zod/src/v4/classic/tests/recursive-types.test.ts +707 -0
  1179. package/node_modules/zod/src/v4/classic/tests/redos.test.ts +68 -0
  1180. package/node_modules/zod/src/v4/classic/tests/refine.test.ts +693 -0
  1181. package/node_modules/zod/src/v4/classic/tests/registries.test.ts +243 -0
  1182. package/node_modules/zod/src/v4/classic/tests/set.test.ts +190 -0
  1183. package/node_modules/zod/src/v4/classic/tests/standard-schema.test.ts +153 -0
  1184. package/node_modules/zod/src/v4/classic/tests/string-formats.test.ts +125 -0
  1185. package/node_modules/zod/src/v4/classic/tests/string.test.ts +1440 -0
  1186. package/node_modules/zod/src/v4/classic/tests/stringbool.test.ts +124 -0
  1187. package/node_modules/zod/src/v4/classic/tests/template-literal.test.ts +812 -0
  1188. package/node_modules/zod/src/v4/classic/tests/to-json-schema-methods.test.ts +438 -0
  1189. package/node_modules/zod/src/v4/classic/tests/to-json-schema.test.ts +4733 -0
  1190. package/node_modules/zod/src/v4/classic/tests/transform.test.ts +378 -0
  1191. package/node_modules/zod/src/v4/classic/tests/tuple.test.ts +511 -0
  1192. package/node_modules/zod/src/v4/classic/tests/union.test.ts +310 -0
  1193. package/node_modules/zod/src/v4/classic/tests/url.test.ts +13 -0
  1194. package/node_modules/zod/src/v4/classic/tests/validate.test.ts +645 -0
  1195. package/node_modules/zod/src/v4/classic/tests/validations.test.ts +283 -0
  1196. package/node_modules/zod/src/v4/classic/tests/void.test.ts +12 -0
  1197. package/node_modules/zod/src/v4/core/api.ts +1875 -0
  1198. package/node_modules/zod/src/v4/core/checks.ts +1266 -0
  1199. package/node_modules/zod/src/v4/core/compile.ts +2313 -0
  1200. package/node_modules/zod/src/v4/core/core.ts +230 -0
  1201. package/node_modules/zod/src/v4/core/doc.ts +52 -0
  1202. package/node_modules/zod/src/v4/core/errors.ts +543 -0
  1203. package/node_modules/zod/src/v4/core/index.ts +19 -0
  1204. package/node_modules/zod/src/v4/core/json-schema-generator.ts +131 -0
  1205. package/node_modules/zod/src/v4/core/json-schema-processors.ts +934 -0
  1206. package/node_modules/zod/src/v4/core/json-schema.ts +148 -0
  1207. package/node_modules/zod/src/v4/core/memoizer.ts +331 -0
  1208. package/node_modules/zod/src/v4/core/parse.ts +309 -0
  1209. package/node_modules/zod/src/v4/core/regexes.ts +217 -0
  1210. package/node_modules/zod/src/v4/core/registries.ts +105 -0
  1211. package/node_modules/zod/src/v4/core/schemas.ts +5192 -0
  1212. package/node_modules/zod/src/v4/core/standard-schema.ts +159 -0
  1213. package/node_modules/zod/src/v4/core/tests/cached.test.ts +54 -0
  1214. package/node_modules/zod/src/v4/core/tests/compile-differential.test.ts +1004 -0
  1215. package/node_modules/zod/src/v4/core/tests/compile-generative.test.ts +445 -0
  1216. package/node_modules/zod/src/v4/core/tests/compile-global.test.ts +89 -0
  1217. package/node_modules/zod/src/v4/core/tests/compile-off-by-default.test.ts +26 -0
  1218. package/node_modules/zod/src/v4/core/tests/compile-stats.test.ts +34 -0
  1219. package/node_modules/zod/src/v4/core/tests/compile.test.ts +2036 -0
  1220. package/node_modules/zod/src/v4/core/tests/email-regex.test.ts +140 -0
  1221. package/node_modules/zod/src/v4/core/tests/extend.test.ts +59 -0
  1222. package/node_modules/zod/src/v4/core/tests/index.test.ts +46 -0
  1223. package/node_modules/zod/src/v4/core/tests/locales/be.test.ts +124 -0
  1224. package/node_modules/zod/src/v4/core/tests/locales/el.test.ts +214 -0
  1225. package/node_modules/zod/src/v4/core/tests/locales/en.test.ts +22 -0
  1226. package/node_modules/zod/src/v4/core/tests/locales/es.test.ts +180 -0
  1227. package/node_modules/zod/src/v4/core/tests/locales/fr.test.ts +72 -0
  1228. package/node_modules/zod/src/v4/core/tests/locales/he.test.ts +379 -0
  1229. package/node_modules/zod/src/v4/core/tests/locales/hr.test.ts +163 -0
  1230. package/node_modules/zod/src/v4/core/tests/locales/nl.test.ts +46 -0
  1231. package/node_modules/zod/src/v4/core/tests/locales/parity.test.ts +42 -0
  1232. package/node_modules/zod/src/v4/core/tests/locales/ru.test.ts +128 -0
  1233. package/node_modules/zod/src/v4/core/tests/locales/tg.test.ts +85 -0
  1234. package/node_modules/zod/src/v4/core/tests/locales/tk.test.ts +69 -0
  1235. package/node_modules/zod/src/v4/core/tests/locales/tr.test.ts +69 -0
  1236. package/node_modules/zod/src/v4/core/tests/locales/uz.test.ts +105 -0
  1237. package/node_modules/zod/src/v4/core/tests/polyfill-collision.test.ts +46 -0
  1238. package/node_modules/zod/src/v4/core/tests/record-constructor.test.ts +125 -0
  1239. package/node_modules/zod/src/v4/core/tests/recursive-tuples.test.ts +45 -0
  1240. package/node_modules/zod/src/v4/core/tests/treeshake.test.ts +51 -0
  1241. package/node_modules/zod/src/v4/core/tests/url-no-canparse.test.ts +133 -0
  1242. package/node_modules/zod/src/v4/core/to-json-schema.ts +857 -0
  1243. package/node_modules/zod/src/v4/core/util.ts +1280 -0
  1244. package/node_modules/zod/src/v4/core/versions.ts +5 -0
  1245. package/node_modules/zod/src/v4/core/visit.ts +193 -0
  1246. package/node_modules/zod/src/v4/core/zsf.ts +323 -0
  1247. package/node_modules/zod/src/v4/index.ts +4 -0
  1248. package/node_modules/zod/src/v4/locales/ar.ts +120 -0
  1249. package/node_modules/zod/src/v4/locales/az.ts +116 -0
  1250. package/node_modules/zod/src/v4/locales/be.ts +188 -0
  1251. package/node_modules/zod/src/v4/locales/bg.ts +133 -0
  1252. package/node_modules/zod/src/v4/locales/bn.ts +123 -0
  1253. package/node_modules/zod/src/v4/locales/ca.ts +121 -0
  1254. package/node_modules/zod/src/v4/locales/ckb.ts +140 -0
  1255. package/node_modules/zod/src/v4/locales/cs.ts +123 -0
  1256. package/node_modules/zod/src/v4/locales/da.ts +128 -0
  1257. package/node_modules/zod/src/v4/locales/de.ts +121 -0
  1258. package/node_modules/zod/src/v4/locales/el.ts +124 -0
  1259. package/node_modules/zod/src/v4/locales/en.ts +136 -0
  1260. package/node_modules/zod/src/v4/locales/eo.ts +123 -0
  1261. package/node_modules/zod/src/v4/locales/es.ts +145 -0
  1262. package/node_modules/zod/src/v4/locales/fa.ts +131 -0
  1263. package/node_modules/zod/src/v4/locales/fi.ts +126 -0
  1264. package/node_modules/zod/src/v4/locales/fr-CA.ts +121 -0
  1265. package/node_modules/zod/src/v4/locales/fr.ts +139 -0
  1266. package/node_modules/zod/src/v4/locales/gu.ts +123 -0
  1267. package/node_modules/zod/src/v4/locales/he.ts +253 -0
  1268. package/node_modules/zod/src/v4/locales/hi.ts +126 -0
  1269. package/node_modules/zod/src/v4/locales/hr.ts +135 -0
  1270. package/node_modules/zod/src/v4/locales/hu.ts +122 -0
  1271. package/node_modules/zod/src/v4/locales/hy.ts +175 -0
  1272. package/node_modules/zod/src/v4/locales/id.ts +120 -0
  1273. package/node_modules/zod/src/v4/locales/index.ts +63 -0
  1274. package/node_modules/zod/src/v4/locales/is.ts +124 -0
  1275. package/node_modules/zod/src/v4/locales/it.ts +121 -0
  1276. package/node_modules/zod/src/v4/locales/ja.ts +119 -0
  1277. package/node_modules/zod/src/v4/locales/ka.ts +128 -0
  1278. package/node_modules/zod/src/v4/locales/kh.ts +7 -0
  1279. package/node_modules/zod/src/v4/locales/km.ts +124 -0
  1280. package/node_modules/zod/src/v4/locales/kn.ts +126 -0
  1281. package/node_modules/zod/src/v4/locales/ko.ts +126 -0
  1282. package/node_modules/zod/src/v4/locales/lt.ts +243 -0
  1283. package/node_modules/zod/src/v4/locales/mk.ts +123 -0
  1284. package/node_modules/zod/src/v4/locales/ms.ts +120 -0
  1285. package/node_modules/zod/src/v4/locales/ne.ts +128 -0
  1286. package/node_modules/zod/src/v4/locales/nl.ts +126 -0
  1287. package/node_modules/zod/src/v4/locales/nn.ts +121 -0
  1288. package/node_modules/zod/src/v4/locales/no.ts +121 -0
  1289. package/node_modules/zod/src/v4/locales/ota.ts +122 -0
  1290. package/node_modules/zod/src/v4/locales/pl.ts +123 -0
  1291. package/node_modules/zod/src/v4/locales/ps.ts +131 -0
  1292. package/node_modules/zod/src/v4/locales/pt-BR.ts +161 -0
  1293. package/node_modules/zod/src/v4/locales/pt.ts +159 -0
  1294. package/node_modules/zod/src/v4/locales/ro.ts +132 -0
  1295. package/node_modules/zod/src/v4/locales/ru.ts +188 -0
  1296. package/node_modules/zod/src/v4/locales/sk.ts +123 -0
  1297. package/node_modules/zod/src/v4/locales/sl.ts +123 -0
  1298. package/node_modules/zod/src/v4/locales/sv.ts +124 -0
  1299. package/node_modules/zod/src/v4/locales/ta.ts +123 -0
  1300. package/node_modules/zod/src/v4/locales/tg.ts +135 -0
  1301. package/node_modules/zod/src/v4/locales/th.ts +124 -0
  1302. package/node_modules/zod/src/v4/locales/tk.ts +113 -0
  1303. package/node_modules/zod/src/v4/locales/tr.ts +116 -0
  1304. package/node_modules/zod/src/v4/locales/ua.ts +7 -0
  1305. package/node_modules/zod/src/v4/locales/uk.ts +122 -0
  1306. package/node_modules/zod/src/v4/locales/ur.ts +124 -0
  1307. package/node_modules/zod/src/v4/locales/uz.ts +120 -0
  1308. package/node_modules/zod/src/v4/locales/vi.ts +122 -0
  1309. package/node_modules/zod/src/v4/locales/yo.ts +129 -0
  1310. package/node_modules/zod/src/v4/locales/zh-CN.ts +121 -0
  1311. package/node_modules/zod/src/v4/locales/zh-TW.ts +120 -0
  1312. package/node_modules/zod/src/v4/mini/checks.ts +33 -0
  1313. package/node_modules/zod/src/v4/mini/coerce.ts +27 -0
  1314. package/node_modules/zod/src/v4/mini/deep-partial.ts +73 -0
  1315. package/node_modules/zod/src/v4/mini/external.ts +52 -0
  1316. package/node_modules/zod/src/v4/mini/in-out.ts +45 -0
  1317. package/node_modules/zod/src/v4/mini/index.ts +3 -0
  1318. package/node_modules/zod/src/v4/mini/iso.ts +66 -0
  1319. package/node_modules/zod/src/v4/mini/parse.ts +16 -0
  1320. package/node_modules/zod/src/v4/mini/schemas.ts +2051 -0
  1321. package/node_modules/zod/src/v4/mini/tests/apply.test.ts +57 -0
  1322. package/node_modules/zod/src/v4/mini/tests/assignability.test.ts +190 -0
  1323. package/node_modules/zod/src/v4/mini/tests/brand.test.ts +94 -0
  1324. package/node_modules/zod/src/v4/mini/tests/checks.test.ts +158 -0
  1325. package/node_modules/zod/src/v4/mini/tests/codec.test.ts +582 -0
  1326. package/node_modules/zod/src/v4/mini/tests/computed.test.ts +30 -0
  1327. package/node_modules/zod/src/v4/mini/tests/cyclic-data.test.ts +84 -0
  1328. package/node_modules/zod/src/v4/mini/tests/deep-partial.test.ts +54 -0
  1329. package/node_modules/zod/src/v4/mini/tests/error.test.ts +41 -0
  1330. package/node_modules/zod/src/v4/mini/tests/functions.test.ts +14 -0
  1331. package/node_modules/zod/src/v4/mini/tests/index.test.ts +1146 -0
  1332. package/node_modules/zod/src/v4/mini/tests/number.test.ts +109 -0
  1333. package/node_modules/zod/src/v4/mini/tests/object.test.ts +292 -0
  1334. package/node_modules/zod/src/v4/mini/tests/prototypes.test.ts +43 -0
  1335. package/node_modules/zod/src/v4/mini/tests/recursive-types.test.ts +341 -0
  1336. package/node_modules/zod/src/v4/mini/tests/standard-schema.test.ts +50 -0
  1337. package/node_modules/zod/src/v4/mini/tests/string.test.ts +396 -0
  1338. package/node_modules/zod/src/v4-mini/index.ts +3 -0
  1339. package/node_modules/zod/v3/ZodError.cjs +167 -0
  1340. package/node_modules/zod/v3/ZodError.d.cts +164 -0
  1341. package/node_modules/zod/v3/ZodError.d.ts +164 -0
  1342. package/node_modules/zod/v3/ZodError.js +143 -0
  1343. package/node_modules/zod/v3/errors.cjs +36 -0
  1344. package/node_modules/zod/v3/errors.d.cts +5 -0
  1345. package/node_modules/zod/v3/errors.d.ts +5 -0
  1346. package/node_modules/zod/v3/errors.js +9 -0
  1347. package/node_modules/zod/v3/external.cjs +41 -0
  1348. package/node_modules/zod/v3/external.d.cts +6 -0
  1349. package/node_modules/zod/v3/external.d.ts +6 -0
  1350. package/node_modules/zod/v3/external.js +6 -0
  1351. package/node_modules/zod/v3/helpers/enumUtil.cjs +21 -0
  1352. package/node_modules/zod/v3/helpers/enumUtil.d.cts +8 -0
  1353. package/node_modules/zod/v3/helpers/enumUtil.d.ts +8 -0
  1354. package/node_modules/zod/v3/helpers/enumUtil.js +1 -0
  1355. package/node_modules/zod/v3/helpers/errorUtil.cjs +28 -0
  1356. package/node_modules/zod/v3/helpers/errorUtil.d.cts +9 -0
  1357. package/node_modules/zod/v3/helpers/errorUtil.d.ts +9 -0
  1358. package/node_modules/zod/v3/helpers/errorUtil.js +6 -0
  1359. package/node_modules/zod/v3/helpers/parseUtil.cjs +143 -0
  1360. package/node_modules/zod/v3/helpers/parseUtil.d.cts +78 -0
  1361. package/node_modules/zod/v3/helpers/parseUtil.d.ts +78 -0
  1362. package/node_modules/zod/v3/helpers/parseUtil.js +109 -0
  1363. package/node_modules/zod/v3/helpers/partialUtil.cjs +21 -0
  1364. package/node_modules/zod/v3/helpers/partialUtil.d.cts +8 -0
  1365. package/node_modules/zod/v3/helpers/partialUtil.d.ts +8 -0
  1366. package/node_modules/zod/v3/helpers/partialUtil.js +1 -0
  1367. package/node_modules/zod/v3/helpers/typeAliases.cjs +21 -0
  1368. package/node_modules/zod/v3/helpers/typeAliases.d.cts +2 -0
  1369. package/node_modules/zod/v3/helpers/typeAliases.d.ts +2 -0
  1370. package/node_modules/zod/v3/helpers/typeAliases.js +1 -0
  1371. package/node_modules/zod/v3/helpers/util.cjs +156 -0
  1372. package/node_modules/zod/v3/helpers/util.d.cts +85 -0
  1373. package/node_modules/zod/v3/helpers/util.d.ts +85 -0
  1374. package/node_modules/zod/v3/helpers/util.js +133 -0
  1375. package/node_modules/zod/v3/index.cjs +52 -0
  1376. package/node_modules/zod/v3/index.d.cts +4 -0
  1377. package/node_modules/zod/v3/index.d.ts +4 -0
  1378. package/node_modules/zod/v3/index.js +4 -0
  1379. package/node_modules/zod/v3/locales/en.cjs +112 -0
  1380. package/node_modules/zod/v3/locales/en.d.cts +3 -0
  1381. package/node_modules/zod/v3/locales/en.d.ts +3 -0
  1382. package/node_modules/zod/v3/locales/en.js +109 -0
  1383. package/node_modules/zod/v3/package.json +7 -0
  1384. package/node_modules/zod/v3/standard-schema.cjs +21 -0
  1385. package/node_modules/zod/v3/standard-schema.d.cts +102 -0
  1386. package/node_modules/zod/v3/standard-schema.d.ts +102 -0
  1387. package/node_modules/zod/v3/standard-schema.js +1 -0
  1388. package/node_modules/zod/v3/types.cjs +3798 -0
  1389. package/node_modules/zod/v3/types.d.cts +1034 -0
  1390. package/node_modules/zod/v3/types.d.ts +1034 -0
  1391. package/node_modules/zod/v3/types.js +3697 -0
  1392. package/node_modules/zod/v4/classic/checks.cjs +53 -0
  1393. package/node_modules/zod/v4/classic/checks.d.cts +1 -0
  1394. package/node_modules/zod/v4/classic/checks.d.ts +1 -0
  1395. package/node_modules/zod/v4/classic/checks.js +1 -0
  1396. package/node_modules/zod/v4/classic/coerce.cjs +66 -0
  1397. package/node_modules/zod/v4/classic/coerce.d.cts +17 -0
  1398. package/node_modules/zod/v4/classic/coerce.d.ts +17 -0
  1399. package/node_modules/zod/v4/classic/coerce.js +17 -0
  1400. package/node_modules/zod/v4/classic/compat.cjs +80 -0
  1401. package/node_modules/zod/v4/classic/compat.d.cts +48 -0
  1402. package/node_modules/zod/v4/classic/compat.d.ts +48 -0
  1403. package/node_modules/zod/v4/classic/compat.js +31 -0
  1404. package/node_modules/zod/v4/classic/deep-partial.cjs +58 -0
  1405. package/node_modules/zod/v4/classic/deep-partial.d.cts +14 -0
  1406. package/node_modules/zod/v4/classic/deep-partial.d.ts +14 -0
  1407. package/node_modules/zod/v4/classic/deep-partial.js +13 -0
  1408. package/node_modules/zod/v4/classic/errors.cjs +102 -0
  1409. package/node_modules/zod/v4/classic/errors.d.cts +29 -0
  1410. package/node_modules/zod/v4/classic/errors.d.ts +29 -0
  1411. package/node_modules/zod/v4/classic/errors.js +57 -0
  1412. package/node_modules/zod/v4/classic/external.cjs +97 -0
  1413. package/node_modules/zod/v4/classic/external.d.cts +19 -0
  1414. package/node_modules/zod/v4/classic/external.d.ts +19 -0
  1415. package/node_modules/zod/v4/classic/external.js +17 -0
  1416. package/node_modules/zod/v4/classic/from-json-schema.cjs +926 -0
  1417. package/node_modules/zod/v4/classic/from-json-schema.d.cts +12 -0
  1418. package/node_modules/zod/v4/classic/from-json-schema.d.ts +12 -0
  1419. package/node_modules/zod/v4/classic/from-json-schema.js +881 -0
  1420. package/node_modules/zod/v4/classic/in-out.cjs +82 -0
  1421. package/node_modules/zod/v4/classic/in-out.d.cts +8 -0
  1422. package/node_modules/zod/v4/classic/in-out.d.ts +8 -0
  1423. package/node_modules/zod/v4/classic/in-out.js +36 -0
  1424. package/node_modules/zod/v4/classic/index.cjs +52 -0
  1425. package/node_modules/zod/v4/classic/index.d.cts +3 -0
  1426. package/node_modules/zod/v4/classic/index.d.ts +3 -0
  1427. package/node_modules/zod/v4/classic/index.js +4 -0
  1428. package/node_modules/zod/v4/classic/iso.cjs +68 -0
  1429. package/node_modules/zod/v4/classic/iso.d.cts +7 -0
  1430. package/node_modules/zod/v4/classic/iso.d.ts +7 -0
  1431. package/node_modules/zod/v4/classic/iso.js +15 -0
  1432. package/node_modules/zod/v4/classic/package.json +7 -0
  1433. package/node_modules/zod/v4/classic/parse.cjs +63 -0
  1434. package/node_modules/zod/v4/classic/parse.d.cts +44 -0
  1435. package/node_modules/zod/v4/classic/parse.d.ts +44 -0
  1436. package/node_modules/zod/v4/classic/parse.js +16 -0
  1437. package/node_modules/zod/v4/classic/schemas.cjs +1676 -0
  1438. package/node_modules/zod/v4/classic/schemas.d.cts +816 -0
  1439. package/node_modules/zod/v4/classic/schemas.d.ts +816 -0
  1440. package/node_modules/zod/v4/classic/schemas.js +1535 -0
  1441. package/node_modules/zod/v4/core/api.cjs +1269 -0
  1442. package/node_modules/zod/v4/core/api.d.cts +335 -0
  1443. package/node_modules/zod/v4/core/api.d.ts +335 -0
  1444. package/node_modules/zod/v4/core/api.js +1107 -0
  1445. package/node_modules/zod/v4/core/checks.cjs +561 -0
  1446. package/node_modules/zod/v4/core/checks.d.cts +294 -0
  1447. package/node_modules/zod/v4/core/checks.d.ts +294 -0
  1448. package/node_modules/zod/v4/core/checks.js +516 -0
  1449. package/node_modules/zod/v4/core/compile.cjs +1908 -0
  1450. package/node_modules/zod/v4/core/compile.d.cts +65 -0
  1451. package/node_modules/zod/v4/core/compile.d.ts +65 -0
  1452. package/node_modules/zod/v4/core/compile.js +1858 -0
  1453. package/node_modules/zod/v4/core/core.cjs +167 -0
  1454. package/node_modules/zod/v4/core/core.d.cts +82 -0
  1455. package/node_modules/zod/v4/core/core.d.ts +82 -0
  1456. package/node_modules/zod/v4/core/core.js +141 -0
  1457. package/node_modules/zod/v4/core/doc.cjs +61 -0
  1458. package/node_modules/zod/v4/core/doc.d.cts +16 -0
  1459. package/node_modules/zod/v4/core/doc.d.ts +16 -0
  1460. package/node_modules/zod/v4/core/doc.js +38 -0
  1461. package/node_modules/zod/v4/core/errors.cjs +317 -0
  1462. package/node_modules/zod/v4/core/errors.d.cts +225 -0
  1463. package/node_modules/zod/v4/core/errors.d.ts +225 -0
  1464. package/node_modules/zod/v4/core/errors.js +267 -0
  1465. package/node_modules/zod/v4/core/index.cjs +70 -0
  1466. package/node_modules/zod/v4/core/index.d.cts +19 -0
  1467. package/node_modules/zod/v4/core/index.d.ts +19 -0
  1468. package/node_modules/zod/v4/core/index.js +19 -0
  1469. package/node_modules/zod/v4/core/json-schema-generator.cjs +122 -0
  1470. package/node_modules/zod/v4/core/json-schema-generator.d.cts +65 -0
  1471. package/node_modules/zod/v4/core/json-schema-generator.d.ts +65 -0
  1472. package/node_modules/zod/v4/core/json-schema-generator.js +99 -0
  1473. package/node_modules/zod/v4/core/json-schema-processors.cjs +929 -0
  1474. package/node_modules/zod/v4/core/json-schema-processors.d.cts +65 -0
  1475. package/node_modules/zod/v4/core/json-schema-processors.d.ts +65 -0
  1476. package/node_modules/zod/v4/core/json-schema-processors.js +843 -0
  1477. package/node_modules/zod/v4/core/json-schema.cjs +21 -0
  1478. package/node_modules/zod/v4/core/json-schema.d.cts +89 -0
  1479. package/node_modules/zod/v4/core/json-schema.d.ts +89 -0
  1480. package/node_modules/zod/v4/core/json-schema.js +1 -0
  1481. package/node_modules/zod/v4/core/memoizer.cjs +360 -0
  1482. package/node_modules/zod/v4/core/memoizer.d.cts +15 -0
  1483. package/node_modules/zod/v4/core/memoizer.d.ts +15 -0
  1484. package/node_modules/zod/v4/core/memoizer.js +311 -0
  1485. package/node_modules/zod/v4/core/package.json +7 -0
  1486. package/node_modules/zod/v4/core/parse.cjs +233 -0
  1487. package/node_modules/zod/v4/core/parse.d.cts +52 -0
  1488. package/node_modules/zod/v4/core/parse.d.ts +52 -0
  1489. package/node_modules/zod/v4/core/parse.js +175 -0
  1490. package/node_modules/zod/v4/core/regexes.cjs +209 -0
  1491. package/node_modules/zod/v4/core/regexes.d.cts +90 -0
  1492. package/node_modules/zod/v4/core/regexes.d.ts +90 -0
  1493. package/node_modules/zod/v4/core/regexes.js +156 -0
  1494. package/node_modules/zod/v4/core/registries.cjs +75 -0
  1495. package/node_modules/zod/v4/core/registries.d.cts +35 -0
  1496. package/node_modules/zod/v4/core/registries.d.ts +35 -0
  1497. package/node_modules/zod/v4/core/registries.js +51 -0
  1498. package/node_modules/zod/v4/core/schemas.cjs +2669 -0
  1499. package/node_modules/zod/v4/core/schemas.d.cts +1273 -0
  1500. package/node_modules/zod/v4/core/schemas.d.ts +1273 -0
  1501. package/node_modules/zod/v4/core/schemas.js +2606 -0
  1502. package/node_modules/zod/v4/core/standard-schema.cjs +21 -0
  1503. package/node_modules/zod/v4/core/standard-schema.d.cts +126 -0
  1504. package/node_modules/zod/v4/core/standard-schema.d.ts +126 -0
  1505. package/node_modules/zod/v4/core/standard-schema.js +1 -0
  1506. package/node_modules/zod/v4/core/to-json-schema.cjs +681 -0
  1507. package/node_modules/zod/v4/core/to-json-schema.d.cts +154 -0
  1508. package/node_modules/zod/v4/core/to-json-schema.d.ts +154 -0
  1509. package/node_modules/zod/v4/core/to-json-schema.js +651 -0
  1510. package/node_modules/zod/v4/core/util.cjs +949 -0
  1511. package/node_modules/zod/v4/core/util.d.cts +278 -0
  1512. package/node_modules/zod/v4/core/util.d.ts +278 -0
  1513. package/node_modules/zod/v4/core/util.js +859 -0
  1514. package/node_modules/zod/v4/core/versions.cjs +27 -0
  1515. package/node_modules/zod/v4/core/versions.d.cts +5 -0
  1516. package/node_modules/zod/v4/core/versions.d.ts +5 -0
  1517. package/node_modules/zod/v4/core/versions.js +5 -0
  1518. package/node_modules/zod/v4/core/visit.cjs +214 -0
  1519. package/node_modules/zod/v4/core/visit.d.cts +30 -0
  1520. package/node_modules/zod/v4/core/visit.d.ts +30 -0
  1521. package/node_modules/zod/v4/core/visit.js +169 -0
  1522. package/node_modules/zod/v4/index.cjs +51 -0
  1523. package/node_modules/zod/v4/index.d.cts +3 -0
  1524. package/node_modules/zod/v4/index.d.ts +3 -0
  1525. package/node_modules/zod/v4/index.js +4 -0
  1526. package/node_modules/zod/v4/locales/ar.cjs +138 -0
  1527. package/node_modules/zod/v4/locales/ar.d.cts +5 -0
  1528. package/node_modules/zod/v4/locales/ar.d.ts +4 -0
  1529. package/node_modules/zod/v4/locales/ar.js +111 -0
  1530. package/node_modules/zod/v4/locales/az.cjs +137 -0
  1531. package/node_modules/zod/v4/locales/az.d.cts +5 -0
  1532. package/node_modules/zod/v4/locales/az.d.ts +4 -0
  1533. package/node_modules/zod/v4/locales/az.js +110 -0
  1534. package/node_modules/zod/v4/locales/be.cjs +195 -0
  1535. package/node_modules/zod/v4/locales/be.d.cts +5 -0
  1536. package/node_modules/zod/v4/locales/be.d.ts +4 -0
  1537. package/node_modules/zod/v4/locales/be.js +168 -0
  1538. package/node_modules/zod/v4/locales/bg.cjs +152 -0
  1539. package/node_modules/zod/v4/locales/bg.d.cts +5 -0
  1540. package/node_modules/zod/v4/locales/bg.d.ts +4 -0
  1541. package/node_modules/zod/v4/locales/bg.js +125 -0
  1542. package/node_modules/zod/v4/locales/bn.cjs +140 -0
  1543. package/node_modules/zod/v4/locales/bn.d.cts +5 -0
  1544. package/node_modules/zod/v4/locales/bn.d.ts +4 -0
  1545. package/node_modules/zod/v4/locales/bn.js +113 -0
  1546. package/node_modules/zod/v4/locales/ca.cjs +139 -0
  1547. package/node_modules/zod/v4/locales/ca.d.cts +5 -0
  1548. package/node_modules/zod/v4/locales/ca.d.ts +4 -0
  1549. package/node_modules/zod/v4/locales/ca.js +112 -0
  1550. package/node_modules/zod/v4/locales/ckb.cjs +160 -0
  1551. package/node_modules/zod/v4/locales/ckb.d.cts +5 -0
  1552. package/node_modules/zod/v4/locales/ckb.d.ts +4 -0
  1553. package/node_modules/zod/v4/locales/ckb.js +133 -0
  1554. package/node_modules/zod/v4/locales/cs.cjs +143 -0
  1555. package/node_modules/zod/v4/locales/cs.d.cts +5 -0
  1556. package/node_modules/zod/v4/locales/cs.d.ts +4 -0
  1557. package/node_modules/zod/v4/locales/cs.js +116 -0
  1558. package/node_modules/zod/v4/locales/da.cjs +147 -0
  1559. package/node_modules/zod/v4/locales/da.d.cts +5 -0
  1560. package/node_modules/zod/v4/locales/da.d.ts +4 -0
  1561. package/node_modules/zod/v4/locales/da.js +120 -0
  1562. package/node_modules/zod/v4/locales/de.cjs +140 -0
  1563. package/node_modules/zod/v4/locales/de.d.cts +5 -0
  1564. package/node_modules/zod/v4/locales/de.d.ts +4 -0
  1565. package/node_modules/zod/v4/locales/de.js +113 -0
  1566. package/node_modules/zod/v4/locales/el.cjs +139 -0
  1567. package/node_modules/zod/v4/locales/el.d.cts +5 -0
  1568. package/node_modules/zod/v4/locales/el.d.ts +4 -0
  1569. package/node_modules/zod/v4/locales/el.js +112 -0
  1570. package/node_modules/zod/v4/locales/en.cjs +152 -0
  1571. package/node_modules/zod/v4/locales/en.d.cts +5 -0
  1572. package/node_modules/zod/v4/locales/en.d.ts +4 -0
  1573. package/node_modules/zod/v4/locales/en.js +125 -0
  1574. package/node_modules/zod/v4/locales/eo.cjs +141 -0
  1575. package/node_modules/zod/v4/locales/eo.d.cts +5 -0
  1576. package/node_modules/zod/v4/locales/eo.d.ts +4 -0
  1577. package/node_modules/zod/v4/locales/eo.js +114 -0
  1578. package/node_modules/zod/v4/locales/es.cjs +163 -0
  1579. package/node_modules/zod/v4/locales/es.d.cts +5 -0
  1580. package/node_modules/zod/v4/locales/es.d.ts +4 -0
  1581. package/node_modules/zod/v4/locales/es.js +136 -0
  1582. package/node_modules/zod/v4/locales/fa.cjs +146 -0
  1583. package/node_modules/zod/v4/locales/fa.d.cts +5 -0
  1584. package/node_modules/zod/v4/locales/fa.d.ts +4 -0
  1585. package/node_modules/zod/v4/locales/fa.js +119 -0
  1586. package/node_modules/zod/v4/locales/fi.cjs +144 -0
  1587. package/node_modules/zod/v4/locales/fi.d.cts +5 -0
  1588. package/node_modules/zod/v4/locales/fi.d.ts +4 -0
  1589. package/node_modules/zod/v4/locales/fi.js +117 -0
  1590. package/node_modules/zod/v4/locales/fr-CA.cjs +139 -0
  1591. package/node_modules/zod/v4/locales/fr-CA.d.cts +5 -0
  1592. package/node_modules/zod/v4/locales/fr-CA.d.ts +4 -0
  1593. package/node_modules/zod/v4/locales/fr-CA.js +112 -0
  1594. package/node_modules/zod/v4/locales/fr.cjs +156 -0
  1595. package/node_modules/zod/v4/locales/fr.d.cts +5 -0
  1596. package/node_modules/zod/v4/locales/fr.d.ts +4 -0
  1597. package/node_modules/zod/v4/locales/fr.js +129 -0
  1598. package/node_modules/zod/v4/locales/gu.cjs +140 -0
  1599. package/node_modules/zod/v4/locales/gu.d.cts +5 -0
  1600. package/node_modules/zod/v4/locales/gu.d.ts +4 -0
  1601. package/node_modules/zod/v4/locales/gu.js +113 -0
  1602. package/node_modules/zod/v4/locales/he.cjs +248 -0
  1603. package/node_modules/zod/v4/locales/he.d.cts +5 -0
  1604. package/node_modules/zod/v4/locales/he.d.ts +4 -0
  1605. package/node_modules/zod/v4/locales/he.js +221 -0
  1606. package/node_modules/zod/v4/locales/hi.cjs +138 -0
  1607. package/node_modules/zod/v4/locales/hi.d.cts +5 -0
  1608. package/node_modules/zod/v4/locales/hi.d.ts +4 -0
  1609. package/node_modules/zod/v4/locales/hi.js +111 -0
  1610. package/node_modules/zod/v4/locales/hr.cjs +153 -0
  1611. package/node_modules/zod/v4/locales/hr.d.cts +5 -0
  1612. package/node_modules/zod/v4/locales/hr.d.ts +4 -0
  1613. package/node_modules/zod/v4/locales/hr.js +126 -0
  1614. package/node_modules/zod/v4/locales/hu.cjs +140 -0
  1615. package/node_modules/zod/v4/locales/hu.d.cts +5 -0
  1616. package/node_modules/zod/v4/locales/hu.d.ts +4 -0
  1617. package/node_modules/zod/v4/locales/hu.js +113 -0
  1618. package/node_modules/zod/v4/locales/hy.cjs +185 -0
  1619. package/node_modules/zod/v4/locales/hy.d.cts +5 -0
  1620. package/node_modules/zod/v4/locales/hy.d.ts +4 -0
  1621. package/node_modules/zod/v4/locales/hy.js +158 -0
  1622. package/node_modules/zod/v4/locales/id.cjs +138 -0
  1623. package/node_modules/zod/v4/locales/id.d.cts +5 -0
  1624. package/node_modules/zod/v4/locales/id.d.ts +4 -0
  1625. package/node_modules/zod/v4/locales/id.js +111 -0
  1626. package/node_modules/zod/v4/locales/index.cjs +152 -0
  1627. package/node_modules/zod/v4/locales/index.d.cts +63 -0
  1628. package/node_modules/zod/v4/locales/index.d.ts +63 -0
  1629. package/node_modules/zod/v4/locales/index.js +63 -0
  1630. package/node_modules/zod/v4/locales/is.cjs +141 -0
  1631. package/node_modules/zod/v4/locales/is.d.cts +5 -0
  1632. package/node_modules/zod/v4/locales/is.d.ts +4 -0
  1633. package/node_modules/zod/v4/locales/is.js +114 -0
  1634. package/node_modules/zod/v4/locales/it.cjs +140 -0
  1635. package/node_modules/zod/v4/locales/it.d.cts +5 -0
  1636. package/node_modules/zod/v4/locales/it.d.ts +4 -0
  1637. package/node_modules/zod/v4/locales/it.js +113 -0
  1638. package/node_modules/zod/v4/locales/ja.cjs +139 -0
  1639. package/node_modules/zod/v4/locales/ja.d.cts +5 -0
  1640. package/node_modules/zod/v4/locales/ja.d.ts +4 -0
  1641. package/node_modules/zod/v4/locales/ja.js +112 -0
  1642. package/node_modules/zod/v4/locales/ka.cjs +144 -0
  1643. package/node_modules/zod/v4/locales/ka.d.cts +5 -0
  1644. package/node_modules/zod/v4/locales/ka.d.ts +4 -0
  1645. package/node_modules/zod/v4/locales/ka.js +117 -0
  1646. package/node_modules/zod/v4/locales/kh.cjs +12 -0
  1647. package/node_modules/zod/v4/locales/kh.d.cts +5 -0
  1648. package/node_modules/zod/v4/locales/kh.d.ts +5 -0
  1649. package/node_modules/zod/v4/locales/kh.js +5 -0
  1650. package/node_modules/zod/v4/locales/km.cjs +142 -0
  1651. package/node_modules/zod/v4/locales/km.d.cts +5 -0
  1652. package/node_modules/zod/v4/locales/km.d.ts +4 -0
  1653. package/node_modules/zod/v4/locales/km.js +115 -0
  1654. package/node_modules/zod/v4/locales/kn.cjs +143 -0
  1655. package/node_modules/zod/v4/locales/kn.d.cts +5 -0
  1656. package/node_modules/zod/v4/locales/kn.d.ts +4 -0
  1657. package/node_modules/zod/v4/locales/kn.js +116 -0
  1658. package/node_modules/zod/v4/locales/ko.cjs +143 -0
  1659. package/node_modules/zod/v4/locales/ko.d.cts +5 -0
  1660. package/node_modules/zod/v4/locales/ko.d.ts +4 -0
  1661. package/node_modules/zod/v4/locales/ko.js +116 -0
  1662. package/node_modules/zod/v4/locales/lt.cjs +234 -0
  1663. package/node_modules/zod/v4/locales/lt.d.cts +5 -0
  1664. package/node_modules/zod/v4/locales/lt.d.ts +4 -0
  1665. package/node_modules/zod/v4/locales/lt.js +207 -0
  1666. package/node_modules/zod/v4/locales/mk.cjs +141 -0
  1667. package/node_modules/zod/v4/locales/mk.d.cts +5 -0
  1668. package/node_modules/zod/v4/locales/mk.d.ts +4 -0
  1669. package/node_modules/zod/v4/locales/mk.js +114 -0
  1670. package/node_modules/zod/v4/locales/ms.cjs +139 -0
  1671. package/node_modules/zod/v4/locales/ms.d.cts +5 -0
  1672. package/node_modules/zod/v4/locales/ms.d.ts +4 -0
  1673. package/node_modules/zod/v4/locales/ms.js +112 -0
  1674. package/node_modules/zod/v4/locales/ne.cjs +138 -0
  1675. package/node_modules/zod/v4/locales/ne.d.cts +5 -0
  1676. package/node_modules/zod/v4/locales/ne.d.ts +4 -0
  1677. package/node_modules/zod/v4/locales/ne.js +111 -0
  1678. package/node_modules/zod/v4/locales/nl.cjs +142 -0
  1679. package/node_modules/zod/v4/locales/nl.d.cts +5 -0
  1680. package/node_modules/zod/v4/locales/nl.d.ts +4 -0
  1681. package/node_modules/zod/v4/locales/nl.js +115 -0
  1682. package/node_modules/zod/v4/locales/nn.cjs +140 -0
  1683. package/node_modules/zod/v4/locales/nn.d.cts +5 -0
  1684. package/node_modules/zod/v4/locales/nn.d.ts +4 -0
  1685. package/node_modules/zod/v4/locales/nn.js +113 -0
  1686. package/node_modules/zod/v4/locales/no.cjs +140 -0
  1687. package/node_modules/zod/v4/locales/no.d.cts +5 -0
  1688. package/node_modules/zod/v4/locales/no.d.ts +4 -0
  1689. package/node_modules/zod/v4/locales/no.js +113 -0
  1690. package/node_modules/zod/v4/locales/ota.cjs +141 -0
  1691. package/node_modules/zod/v4/locales/ota.d.cts +5 -0
  1692. package/node_modules/zod/v4/locales/ota.d.ts +4 -0
  1693. package/node_modules/zod/v4/locales/ota.js +114 -0
  1694. package/node_modules/zod/v4/locales/package.json +7 -0
  1695. package/node_modules/zod/v4/locales/pl.cjs +141 -0
  1696. package/node_modules/zod/v4/locales/pl.d.cts +5 -0
  1697. package/node_modules/zod/v4/locales/pl.d.ts +4 -0
  1698. package/node_modules/zod/v4/locales/pl.js +114 -0
  1699. package/node_modules/zod/v4/locales/ps.cjs +146 -0
  1700. package/node_modules/zod/v4/locales/ps.d.cts +5 -0
  1701. package/node_modules/zod/v4/locales/ps.d.ts +4 -0
  1702. package/node_modules/zod/v4/locales/ps.js +119 -0
  1703. package/node_modules/zod/v4/locales/pt-BR.cjs +170 -0
  1704. package/node_modules/zod/v4/locales/pt-BR.d.cts +5 -0
  1705. package/node_modules/zod/v4/locales/pt-BR.d.ts +4 -0
  1706. package/node_modules/zod/v4/locales/pt-BR.js +143 -0
  1707. package/node_modules/zod/v4/locales/pt.cjs +169 -0
  1708. package/node_modules/zod/v4/locales/pt.d.cts +5 -0
  1709. package/node_modules/zod/v4/locales/pt.d.ts +4 -0
  1710. package/node_modules/zod/v4/locales/pt.js +142 -0
  1711. package/node_modules/zod/v4/locales/ro.cjs +149 -0
  1712. package/node_modules/zod/v4/locales/ro.d.cts +5 -0
  1713. package/node_modules/zod/v4/locales/ro.d.ts +4 -0
  1714. package/node_modules/zod/v4/locales/ro.js +122 -0
  1715. package/node_modules/zod/v4/locales/ru.cjs +195 -0
  1716. package/node_modules/zod/v4/locales/ru.d.cts +5 -0
  1717. package/node_modules/zod/v4/locales/ru.d.ts +4 -0
  1718. package/node_modules/zod/v4/locales/ru.js +168 -0
  1719. package/node_modules/zod/v4/locales/sk.cjs +143 -0
  1720. package/node_modules/zod/v4/locales/sk.d.cts +5 -0
  1721. package/node_modules/zod/v4/locales/sk.d.ts +4 -0
  1722. package/node_modules/zod/v4/locales/sk.js +116 -0
  1723. package/node_modules/zod/v4/locales/sl.cjs +141 -0
  1724. package/node_modules/zod/v4/locales/sl.d.cts +5 -0
  1725. package/node_modules/zod/v4/locales/sl.d.ts +4 -0
  1726. package/node_modules/zod/v4/locales/sl.js +114 -0
  1727. package/node_modules/zod/v4/locales/sv.cjs +142 -0
  1728. package/node_modules/zod/v4/locales/sv.d.cts +5 -0
  1729. package/node_modules/zod/v4/locales/sv.d.ts +4 -0
  1730. package/node_modules/zod/v4/locales/sv.js +115 -0
  1731. package/node_modules/zod/v4/locales/ta.cjs +142 -0
  1732. package/node_modules/zod/v4/locales/ta.d.cts +5 -0
  1733. package/node_modules/zod/v4/locales/ta.d.ts +4 -0
  1734. package/node_modules/zod/v4/locales/ta.js +115 -0
  1735. package/node_modules/zod/v4/locales/tg.cjs +144 -0
  1736. package/node_modules/zod/v4/locales/tg.d.cts +5 -0
  1737. package/node_modules/zod/v4/locales/tg.d.ts +4 -0
  1738. package/node_modules/zod/v4/locales/tg.js +117 -0
  1739. package/node_modules/zod/v4/locales/th.cjs +142 -0
  1740. package/node_modules/zod/v4/locales/th.d.cts +5 -0
  1741. package/node_modules/zod/v4/locales/th.d.ts +4 -0
  1742. package/node_modules/zod/v4/locales/th.js +115 -0
  1743. package/node_modules/zod/v4/locales/tk.cjs +134 -0
  1744. package/node_modules/zod/v4/locales/tk.d.cts +5 -0
  1745. package/node_modules/zod/v4/locales/tk.d.ts +4 -0
  1746. package/node_modules/zod/v4/locales/tk.js +107 -0
  1747. package/node_modules/zod/v4/locales/tr.cjs +137 -0
  1748. package/node_modules/zod/v4/locales/tr.d.cts +5 -0
  1749. package/node_modules/zod/v4/locales/tr.d.ts +4 -0
  1750. package/node_modules/zod/v4/locales/tr.js +110 -0
  1751. package/node_modules/zod/v4/locales/ua.cjs +12 -0
  1752. package/node_modules/zod/v4/locales/ua.d.cts +5 -0
  1753. package/node_modules/zod/v4/locales/ua.d.ts +5 -0
  1754. package/node_modules/zod/v4/locales/ua.js +5 -0
  1755. package/node_modules/zod/v4/locales/uk.cjs +140 -0
  1756. package/node_modules/zod/v4/locales/uk.d.cts +5 -0
  1757. package/node_modules/zod/v4/locales/uk.d.ts +4 -0
  1758. package/node_modules/zod/v4/locales/uk.js +113 -0
  1759. package/node_modules/zod/v4/locales/ur.cjs +142 -0
  1760. package/node_modules/zod/v4/locales/ur.d.cts +5 -0
  1761. package/node_modules/zod/v4/locales/ur.d.ts +4 -0
  1762. package/node_modules/zod/v4/locales/ur.js +115 -0
  1763. package/node_modules/zod/v4/locales/uz.cjs +140 -0
  1764. package/node_modules/zod/v4/locales/uz.d.cts +5 -0
  1765. package/node_modules/zod/v4/locales/uz.d.ts +4 -0
  1766. package/node_modules/zod/v4/locales/uz.js +113 -0
  1767. package/node_modules/zod/v4/locales/vi.cjs +140 -0
  1768. package/node_modules/zod/v4/locales/vi.d.cts +5 -0
  1769. package/node_modules/zod/v4/locales/vi.d.ts +4 -0
  1770. package/node_modules/zod/v4/locales/vi.js +113 -0
  1771. package/node_modules/zod/v4/locales/yo.cjs +139 -0
  1772. package/node_modules/zod/v4/locales/yo.d.cts +5 -0
  1773. package/node_modules/zod/v4/locales/yo.d.ts +4 -0
  1774. package/node_modules/zod/v4/locales/yo.js +112 -0
  1775. package/node_modules/zod/v4/locales/zh-CN.cjs +141 -0
  1776. package/node_modules/zod/v4/locales/zh-CN.d.cts +5 -0
  1777. package/node_modules/zod/v4/locales/zh-CN.d.ts +4 -0
  1778. package/node_modules/zod/v4/locales/zh-CN.js +114 -0
  1779. package/node_modules/zod/v4/locales/zh-TW.cjs +139 -0
  1780. package/node_modules/zod/v4/locales/zh-TW.d.cts +5 -0
  1781. package/node_modules/zod/v4/locales/zh-TW.d.ts +4 -0
  1782. package/node_modules/zod/v4/locales/zh-TW.js +112 -0
  1783. package/node_modules/zod/v4/mini/checks.cjs +54 -0
  1784. package/node_modules/zod/v4/mini/checks.d.cts +1 -0
  1785. package/node_modules/zod/v4/mini/checks.d.ts +1 -0
  1786. package/node_modules/zod/v4/mini/checks.js +1 -0
  1787. package/node_modules/zod/v4/mini/coerce.cjs +71 -0
  1788. package/node_modules/zod/v4/mini/coerce.d.cts +7 -0
  1789. package/node_modules/zod/v4/mini/coerce.d.ts +7 -0
  1790. package/node_modules/zod/v4/mini/coerce.js +22 -0
  1791. package/node_modules/zod/v4/mini/deep-partial.cjs +58 -0
  1792. package/node_modules/zod/v4/mini/deep-partial.d.cts +14 -0
  1793. package/node_modules/zod/v4/mini/deep-partial.d.ts +14 -0
  1794. package/node_modules/zod/v4/mini/deep-partial.js +13 -0
  1795. package/node_modules/zod/v4/mini/external.cjs +95 -0
  1796. package/node_modules/zod/v4/mini/external.d.cts +16 -0
  1797. package/node_modules/zod/v4/mini/external.d.ts +16 -0
  1798. package/node_modules/zod/v4/mini/external.js +16 -0
  1799. package/node_modules/zod/v4/mini/in-out.cjs +82 -0
  1800. package/node_modules/zod/v4/mini/in-out.d.cts +8 -0
  1801. package/node_modules/zod/v4/mini/in-out.d.ts +8 -0
  1802. package/node_modules/zod/v4/mini/in-out.js +36 -0
  1803. package/node_modules/zod/v4/mini/index.cjs +51 -0
  1804. package/node_modules/zod/v4/mini/index.d.cts +3 -0
  1805. package/node_modules/zod/v4/mini/index.d.ts +3 -0
  1806. package/node_modules/zod/v4/mini/index.js +3 -0
  1807. package/node_modules/zod/v4/mini/iso.cjs +83 -0
  1808. package/node_modules/zod/v4/mini/iso.d.cts +22 -0
  1809. package/node_modules/zod/v4/mini/iso.d.ts +22 -0
  1810. package/node_modules/zod/v4/mini/iso.js +34 -0
  1811. package/node_modules/zod/v4/mini/package.json +7 -0
  1812. package/node_modules/zod/v4/mini/parse.cjs +37 -0
  1813. package/node_modules/zod/v4/mini/parse.d.cts +1 -0
  1814. package/node_modules/zod/v4/mini/parse.d.ts +1 -0
  1815. package/node_modules/zod/v4/mini/parse.js +1 -0
  1816. package/node_modules/zod/v4/mini/schemas.cjs +1147 -0
  1817. package/node_modules/zod/v4/mini/schemas.d.cts +480 -0
  1818. package/node_modules/zod/v4/mini/schemas.d.ts +480 -0
  1819. package/node_modules/zod/v4/mini/schemas.js +1002 -0
  1820. package/node_modules/zod/v4/package.json +7 -0
  1821. package/node_modules/zod/v4-mini/index.cjs +51 -0
  1822. package/node_modules/zod/v4-mini/index.d.cts +3 -0
  1823. package/node_modules/zod/v4-mini/index.d.ts +3 -0
  1824. package/node_modules/zod/v4-mini/index.js +3 -0
  1825. package/node_modules/zod/v4-mini/package.json +7 -0
  1826. package/package.json +50 -0
@@ -0,0 +1,3877 @@
1
+ import fs from "node:fs/promises";
2
+ import { execFile } from "node:child_process";
3
+ import { promisify } from "node:util";
4
+ import net from "node:net";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+ import { randomBytes, randomUUID } from "node:crypto";
8
+ import { githubLauncherSource } from "./github-launcher.js";
9
+ import { prepareCommandManagedRuntime, } from "./command-managed-runtime.js";
10
+ import { buildRemoteExecutionSessionIdentity, prepareRemoteManagedRuntime, remoteExecutionSessionMatches, } from "./remote-managed-runtime.js";
11
+ export { resolveReferencedSourceIgnore } from "./sandbox-managed-runtime.js";
12
+ import { createCommandManagedSandboxCallbackBridgeQueueClient, createSandboxCallbackBridgeAsset, createSandboxCallbackBridgeToken, DEFAULT_SANDBOX_CALLBACK_BRIDGE_MAX_BODY_BYTES, HTTP2_SANDBOX_CALLBACK_BRIDGE_ROUTE_ALLOWLIST, SANDBOX_CALLBACK_BRIDGE_HTTP2_MODE, sandboxCallbackBridgeDirectories, startSandboxCallbackBridgeServer, startSandboxCallbackBridgeWorker, syncRemoteTextFileWithHashSkip, syncSandboxCallbackBridgeEntrypoint, } from "./sandbox-callback-bridge.js";
13
+ import { createHttp2BridgeServer, BridgeProcessCapacityError, } from "./http2-bridge-server.js";
14
+ import { createSandboxRunLogTailFactory, } from "./sandbox-run-log-stream.js";
15
+ import { DEFAULT_DUPLEX_BROKER_BUDGETS, DUPLEX_CHANNEL_LOST_ERROR_CODE, isSafeBridgeMethod, } from "./bridge-transport-contract.js";
16
+ import { decodeDuplexLine, DEFAULT_MAX_DUPLEX_FRAME_BYTES } from "./duplex-frame-codec.js";
17
+ import { createDuplexObservability, mapHttp2EventToDuplexLossReason, } from "./duplex-observability.js";
18
+ import { createSshCommandManagedRuntimeRunner, parseSshRemoteExecutionSpec, runSshCommand, shellQuote } from "./ssh.js";
19
+ import { ensureCommandResolvable, resolveCommandForLogs, runChildProcess, } from "./server-utils.js";
20
+ import { sanitizeRemoteExecutionEnv } from "./remote-execution-env.js";
21
+ import { preferredShellForSandbox, shellCommandArgs } from "./sandbox-shell.js";
22
+ import { runWithRuntimeParent, } from "./acpx-engine/startup-timing.js";
23
+ export function postedIssueCommentLogMarker(method, requestPath, status, body) {
24
+ if (method !== "POST" || !/^\/api\/issues\/[^/]+\/comments$/.test(requestPath) || status < 200 || status >= 300) {
25
+ return null;
26
+ }
27
+ const bodyText = typeof body === "string" ? body : body.toString("utf8");
28
+ try {
29
+ const parsed = JSON.parse(bodyText);
30
+ return typeof parsed.id === "string" && parsed.id.length > 0 ? `comment id: ${parsed.id}\n` : null;
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ }
36
+ export { sanitizeRemoteExecutionEnv } from "./remote-execution-env.js";
37
+ // 4-hour wall-clock backstop for sandbox-backed adapter runs. This is a
38
+ // last-resort kill switch, not the primary hang detector: genuinely hung runs
39
+ // are caught much earlier by the adapters' output-inactivity monitors (e.g.
40
+ // codex-local's 7-minute monitor). The value intentionally matches the
41
+ // recovery watchdog's ACTIVE_RUN_OUTPUT_CRITICAL_THRESHOLD_MS (4h) in
42
+ // server/src/services/recovery/service.ts so healthy long runs are never
43
+ // killed by the adapter before the watchdog would even consider them stuck.
44
+ export const DEFAULT_REMOTE_SANDBOX_ADAPTER_TIMEOUT_SEC = 14_400;
45
+ function parseObject(value) {
46
+ return value && typeof value === "object" && !Array.isArray(value)
47
+ ? value
48
+ : {};
49
+ }
50
+ function readString(value) {
51
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
52
+ }
53
+ // Read a serialized effective-capability snapshot back into a full record. A
54
+ // missing or non-boolean field reads as `false`, so a round-tripped target
55
+ // never grants a capability that the snapshot did not carry. Returns null when
56
+ // there is no object to read.
57
+ function parseEffectiveExecutionCapabilities(value) {
58
+ const parsed = parseObject(value);
59
+ if (Object.keys(parsed).length === 0)
60
+ return null;
61
+ return {
62
+ reusableLeases: parsed.reusableLeases === true,
63
+ nativeSyncIn: parsed.nativeSyncIn === true,
64
+ nativeSyncOut: parsed.nativeSyncOut === true,
65
+ persistentProcessSessions: parsed.persistentProcessSessions === true,
66
+ independentControlCommands: parsed.independentControlCommands === true,
67
+ incrementalSessionOutput: parsed.incrementalSessionOutput === true,
68
+ concurrentSyncOperations: parsed.concurrentSyncOperations === true,
69
+ duplexCommandStream: parsed.duplexCommandStream === true,
70
+ runnerWebSocketIngress: parsed.runnerWebSocketIngress === true,
71
+ };
72
+ }
73
+ function readStringMeta(parsed, key) {
74
+ return readString(parsed[key]);
75
+ }
76
+ function resolveHostForUrl(rawHost) {
77
+ const host = rawHost.trim();
78
+ // Preserve the wildcard bind's address family: a server bound to 0.0.0.0
79
+ // accepts IPv4, so target the IPv4 loopback (and [::1] for ::) instead of
80
+ // "localhost", which the resolver may map to the other family.
81
+ if (host === "0.0.0.0")
82
+ return "127.0.0.1";
83
+ if (host === "::")
84
+ return "[::1]";
85
+ if (!host)
86
+ return "localhost";
87
+ if (host.includes(":") && !host.startsWith("[") && !host.endsWith("]"))
88
+ return `[${host}]`;
89
+ return host;
90
+ }
91
+ function resolveDefaultPaperclipApiUrl() {
92
+ const runtimeHost = resolveHostForUrl(process.env.PAPERCLIP_LISTEN_HOST ?? process.env.HOST ?? "localhost");
93
+ // 3100 matches the default Paperclip dev server port when the runtime does not provide one.
94
+ const runtimePort = process.env.PAPERCLIP_LISTEN_PORT ?? process.env.PORT ?? "3100";
95
+ return `http://${runtimeHost}:${runtimePort}`;
96
+ }
97
+ function isBridgeDebugEnabled(env) {
98
+ const value = env.PAPERCLIP_BRIDGE_DEBUG?.trim().toLowerCase();
99
+ return value === "1" || value === "true" || value === "yes";
100
+ }
101
+ function isAdapterExecutionTargetInstance(value) {
102
+ const parsed = parseObject(value);
103
+ if (parsed.kind === "local")
104
+ return true;
105
+ if (parsed.kind !== "remote")
106
+ return false;
107
+ if (parsed.transport === "ssh")
108
+ return parseSshRemoteExecutionSpec(parseObject(parsed.spec)) !== null;
109
+ if (parsed.transport !== "sandbox")
110
+ return false;
111
+ return readStringMeta(parsed, "remoteCwd") !== null;
112
+ }
113
+ export function adapterExecutionTargetToRemoteSpec(target) {
114
+ return target?.kind === "remote" && target.transport === "ssh" ? target.spec : null;
115
+ }
116
+ export function adapterExecutionTargetIsRemote(target) {
117
+ return target?.kind === "remote";
118
+ }
119
+ export function adapterExecutionTargetUsesManagedHome(target) {
120
+ return target?.kind === "remote" && target.transport === "sandbox";
121
+ }
122
+ /**
123
+ * Read the per-run duplex bridge kill switch off a target. Only a sandbox
124
+ * target with `enableSandboxDuplexBridge` set to `true` returns `true`. Every
125
+ * other target and every other value returns `false`, so the caller fails
126
+ * closed to the file bridge.
127
+ */
128
+ export function adapterExecutionTargetEnablesSandboxDuplexBridge(target) {
129
+ return (target?.kind === "remote" &&
130
+ target.transport === "sandbox" &&
131
+ target.enableSandboxDuplexBridge === true);
132
+ }
133
+ /**
134
+ * Read the injected duplex observability recorder off a target. Only a
135
+ * sandbox target with a recorder attached returns it. Every other target
136
+ * returns null, so the bridge falls back to the safe no-op recorder.
137
+ */
138
+ export function adapterExecutionTargetDuplexObservabilityRecorder(target) {
139
+ return target?.kind === "remote" && target.transport === "sandbox"
140
+ ? target.duplexObservabilityRecorder ?? null
141
+ : null;
142
+ }
143
+ export function adapterExecutionTargetRemoteCwd(target, localCwd) {
144
+ return target?.kind === "remote" ? target.remoteCwd : localCwd;
145
+ }
146
+ export function overrideAdapterExecutionTargetRemoteCwd(target, remoteCwd) {
147
+ const nextRemoteCwd = remoteCwd?.trim();
148
+ if (!target || target.kind !== "remote" || !nextRemoteCwd) {
149
+ return target;
150
+ }
151
+ if (target.remoteCwd === nextRemoteCwd) {
152
+ return target;
153
+ }
154
+ if (target.transport === "ssh") {
155
+ return {
156
+ ...target,
157
+ remoteCwd: nextRemoteCwd,
158
+ spec: {
159
+ ...target.spec,
160
+ remoteCwd: nextRemoteCwd,
161
+ },
162
+ };
163
+ }
164
+ return {
165
+ ...target,
166
+ remoteCwd: nextRemoteCwd,
167
+ };
168
+ }
169
+ export function resolveAdapterExecutionTargetCwd(target, configuredCwd, localFallbackCwd) {
170
+ if (typeof configuredCwd === "string" && configuredCwd.trim().length > 0) {
171
+ return configuredCwd;
172
+ }
173
+ return adapterExecutionTargetRemoteCwd(target, localFallbackCwd);
174
+ }
175
+ export function adapterExecutionTargetUsesPaperclipBridge(target) {
176
+ return target?.kind === "remote";
177
+ }
178
+ export function describeAdapterExecutionTarget(target) {
179
+ if (!target || target.kind === "local")
180
+ return "local environment";
181
+ if (target.transport === "ssh") {
182
+ return `SSH environment ${target.spec.username}@${target.spec.host}:${target.spec.port}`;
183
+ }
184
+ return `sandbox environment${target.providerKey ? ` (${target.providerKey})` : ""}`;
185
+ }
186
+ export function resolveAdapterExecutionTargetTimeout(target, configuredTimeoutSec) {
187
+ if (typeof configuredTimeoutSec === "number" && Number.isFinite(configuredTimeoutSec)) {
188
+ // Preserve fractional (sub-second) configured values instead of flooring:
189
+ // adapters historically honored e.g. timeoutSec=0.5, and flooring would
190
+ // silently turn it into "no timeout".
191
+ if (configuredTimeoutSec > 0) {
192
+ return { timeoutSec: configuredTimeoutSec, source: "configured" };
193
+ }
194
+ // A negative timeoutSec is the explicit "no adapter wall-clock timeout"
195
+ // opt-out, honored even on sandbox targets. Zero cannot carry that
196
+ // meaning: the adapter config UI persists the schema default of 0 for
197
+ // untouched fields, so timeoutSec=0 in stored config does not signal
198
+ // operator intent and falls through to target defaults below.
199
+ if (configuredTimeoutSec < 0) {
200
+ return { timeoutSec: 0, source: "configured" };
201
+ }
202
+ }
203
+ // Local and SSH adapters preserve the historical "0 means no adapter
204
+ // timeout" behavior. Sandbox-backed runs execute through provider RPCs
205
+ // that usually apply their own shorter command defaults, so request an
206
+ // explicit longer timeout for full adapter runs when the adapter leaves
207
+ // timeoutSec unset.
208
+ if (target?.kind === "remote" && target.transport === "sandbox") {
209
+ return { timeoutSec: DEFAULT_REMOTE_SANDBOX_ADAPTER_TIMEOUT_SEC, source: "sandbox_default" };
210
+ }
211
+ return { timeoutSec: 0, source: "unlimited" };
212
+ }
213
+ export function resolveAdapterExecutionTargetTimeoutSec(target, configuredTimeoutSec) {
214
+ return resolveAdapterExecutionTargetTimeout(target, configuredTimeoutSec).timeoutSec;
215
+ }
216
+ function describeAdapterExecutionTimeoutSource(source) {
217
+ switch (source) {
218
+ case "configured":
219
+ return "configured via adapterConfig.timeoutSec";
220
+ case "sandbox_default":
221
+ return "sandbox default";
222
+ case "unlimited":
223
+ return "no adapter wall-clock timeout";
224
+ }
225
+ }
226
+ /**
227
+ * Self-describing error message for when the adapter wall-clock execution
228
+ * timeout kills a run. Names the timer that fired and the knob that controls
229
+ * it so run failures never surface as a bare "Timed out".
230
+ */
231
+ export function formatAdapterExecutionTimeoutErrorMessage(resolution) {
232
+ return (`Run exceeded the adapter execution timeout ` +
233
+ `(timeoutSec=${resolution.timeoutSec}, ${describeAdapterExecutionTimeoutSource(resolution.source)}). ` +
234
+ `Set adapterConfig.timeoutSec to raise it.`);
235
+ }
236
+ /**
237
+ * One-line start-of-run statement of the effective wall-clock timeout and its
238
+ * source. Callers prefix with `[paperclip] ` and append a newline.
239
+ */
240
+ export function formatAdapterExecutionTimeoutStartLogLine(resolution) {
241
+ if (resolution.timeoutSec <= 0) {
242
+ if (resolution.source === "configured") {
243
+ return ("Adapter execution timeout: none " +
244
+ "(explicitly disabled via adapterConfig.timeoutSec; set it to a positive value to add one).");
245
+ }
246
+ return ("Adapter execution timeout: none " +
247
+ "(no adapter wall-clock timeout for this target; set adapterConfig.timeoutSec to add one).");
248
+ }
249
+ return (`Adapter execution timeout: timeoutSec=${resolution.timeoutSec} ` +
250
+ `(${describeAdapterExecutionTimeoutSource(resolution.source)}; set adapterConfig.timeoutSec to override).`);
251
+ }
252
+ function requireSandboxRunner(target) {
253
+ if (target.runner)
254
+ return target.runner;
255
+ throw new Error("Sandbox execution target is missing its provider runtime runner. Sandbox commands must execute through the environment runtime.");
256
+ }
257
+ function preferredSandboxShell(target) {
258
+ return preferredShellForSandbox(target.shellCommand);
259
+ }
260
+ // The Secure Shell command runner's own output buffer. This value used to
261
+ // derive from the bridge body limit (`DEFAULT_SANDBOX_CALLBACK_BRIDGE_MAX_BODY_BYTES
262
+ // * 4`), so a bridge limit rise silently grew it too. It now stands on its
263
+ // own local constant, independent of the bridge body limit, so a later
264
+ // bridge limit change never resizes this buffer as a side effect.
265
+ const SSH_COMMAND_MAX_BUFFER_BYTES = 1024 * 1024;
266
+ function adapterExecutionTargetCommandRunner(target) {
267
+ if (target.transport === "ssh") {
268
+ return createSshCommandManagedRuntimeRunner({
269
+ spec: target.spec,
270
+ defaultCwd: target.remoteCwd,
271
+ maxBufferBytes: SSH_COMMAND_MAX_BUFFER_BYTES,
272
+ });
273
+ }
274
+ return requireSandboxRunner(target);
275
+ }
276
+ function adapterExecutionTargetShellCommand(target) {
277
+ return target.transport === "ssh" ? "sh" : preferredSandboxShell(target);
278
+ }
279
+ function adapterExecutionTargetTimeoutMs(target) {
280
+ return target.transport === "sandbox" ? target.timeoutMs : undefined;
281
+ }
282
+ export async function ensureAdapterExecutionTargetCommandResolvable(command, target, cwd, env, options = {}) {
283
+ if (target?.kind === "remote" && target.transport === "sandbox") {
284
+ await ensureSandboxCommandResolvable(command, target, sanitizeRemoteExecutionEnv(Object.fromEntries(Object.entries(env).filter((entry) => typeof entry[1] === "string"))), options.installCommand?.trim() || null, options.timeoutSec);
285
+ return;
286
+ }
287
+ await ensureCommandResolvable(command, cwd, env, {
288
+ remoteExecution: adapterExecutionTargetToRemoteSpec(target),
289
+ });
290
+ }
291
+ async function probeSandboxCommandResolvable(command, target, env) {
292
+ const runner = requireSandboxRunner(target);
293
+ const probeScript = `command -v ${shellQuote(command)}`;
294
+ const result = await runner.execute({
295
+ command: "sh",
296
+ args: ["-c", probeScript],
297
+ cwd: target.remoteCwd,
298
+ env,
299
+ timeoutMs: target.timeoutMs ?? 15_000,
300
+ });
301
+ return {
302
+ resolved: !result.timedOut && (result.exitCode ?? 1) === 0,
303
+ timedOut: result.timedOut,
304
+ stderr: result.stderr.trim(),
305
+ };
306
+ }
307
+ async function ensureSandboxCommandResolvable(command, target, env, installCommand, timeoutSec) {
308
+ // Probe whether the binary is resolvable inside the sandbox. We previously
309
+ // short-circuited this for sandbox targets, which let the caller report a
310
+ // success message even when the CLI was missing from the image. Now we run
311
+ // a real `command -v` through the same runner the hello probe will use, so
312
+ // the first step honestly reflects whether the binary is on PATH. The
313
+ // sandbox provider is responsible for sourcing login profiles (e2b mirrors
314
+ // SSH's buildSshSpawnTarget) so this and the hello probe agree on PATH.
315
+ let probe = await probeSandboxCommandResolvable(command, target, env);
316
+ if (probe.resolved)
317
+ return;
318
+ if (probe.timedOut) {
319
+ throw new Error(`Timed out checking command "${command}" on sandbox target.`);
320
+ }
321
+ // If the caller supplied an install command, attempt the install once via
322
+ // the sandbox runner (which the sandbox provider wraps in a login shell)
323
+ // and re-probe before reporting failure. This lets fresh sandbox leases
324
+ // bring up the CLI before the resolvability gate, mirroring the test path.
325
+ let installFailureDetail = null;
326
+ if (installCommand) {
327
+ const runner = requireSandboxRunner(target);
328
+ const installTimeoutMs = typeof timeoutSec === "number" && Number.isFinite(timeoutSec) && timeoutSec > 0
329
+ ? Math.floor(timeoutSec * 1000)
330
+ : target.timeoutMs ?? 300_000;
331
+ try {
332
+ const installResult = await runner.execute({
333
+ command: "sh",
334
+ args: shellCommandArgs(installCommand),
335
+ cwd: target.remoteCwd,
336
+ env,
337
+ timeoutMs: installTimeoutMs,
338
+ });
339
+ if (installResult.timedOut) {
340
+ installFailureDetail = `install command timed out: ${installCommand}`;
341
+ }
342
+ else if ((installResult.exitCode ?? 0) !== 0) {
343
+ const tail = (text) => text.split(/\r?\n/).filter((line) => line.trim().length > 0).slice(-2).join(" | ").slice(0, 240);
344
+ const reason = tail(installResult.stderr || installResult.stdout) || `exit ${installResult.exitCode ?? "?"}`;
345
+ installFailureDetail = `install command exited ${installResult.exitCode ?? "?"}: ${reason}`;
346
+ }
347
+ }
348
+ catch (err) {
349
+ installFailureDetail = `install command threw: ${err instanceof Error ? err.message : String(err)}`;
350
+ }
351
+ probe = await probeSandboxCommandResolvable(command, target, env);
352
+ if (probe.resolved)
353
+ return;
354
+ if (probe.timedOut) {
355
+ throw new Error(`Timed out checking command "${command}" on sandbox target.`);
356
+ }
357
+ }
358
+ const probeStderr = probe.stderr.length > 0 ? ` probe stderr: ${probe.stderr}` : "";
359
+ const installDetail = installFailureDetail ? `; ${installFailureDetail}` : "";
360
+ throw new Error(`Command "${command}" is not installed or not on PATH in the sandbox environment${installDetail}.${probeStderr}`);
361
+ }
362
+ export async function resolveAdapterExecutionTargetCommandForLogs(command, target, cwd, env) {
363
+ if (target?.kind === "remote" && target.transport === "sandbox") {
364
+ return `sandbox://${target.providerKey ?? "provider"}/${target.leaseId ?? "lease"}/${target.remoteCwd} :: ${command}`;
365
+ }
366
+ return await resolveCommandForLogs(command, cwd, env, {
367
+ remoteExecution: adapterExecutionTargetToRemoteSpec(target),
368
+ });
369
+ }
370
+ // Apply the run-disposition seam to one clean process result. Only a clean
371
+ // completion is success-eligible: a timed-out, signalled, or non-zero-exit
372
+ // result is already a failure, so the seam leaves it unchanged. This is the same
373
+ // success-eligibility rule the ACP lane applies. For a success-eligible result
374
+ // the seam settles the disposition in one atomic broker step: it reads the
375
+ // disposition and marks the host-observed orderly completion together, so a
376
+ // gateway exit after the clean completion cannot latch a false mid-run loss. A
377
+ // duplex control channel that died before the clean completion fails the run
378
+ // closed: the seam sets a non-zero exit code, the typed `duplex_channel_lost`
379
+ // error code, and a stderr note that names only the typed loss reason.
380
+ function applyRunDispositionSeam(result, settleRunDisposition) {
381
+ const successEligible = result.exitCode === 0 && !result.timedOut && result.signal === null;
382
+ if (!successEligible || !settleRunDisposition)
383
+ return result;
384
+ const disposition = settleRunDisposition();
385
+ if (!disposition.failed)
386
+ return result;
387
+ const lossReason = disposition.lossReason ?? "other";
388
+ const note = `[paperclip] The sandbox duplex control channel was lost (${lossReason}) before the run completed.\n`;
389
+ const separator = result.stderr.length > 0 && !result.stderr.endsWith("\n") ? "\n" : "";
390
+ return {
391
+ ...result,
392
+ exitCode: 1,
393
+ errorCode: DUPLEX_CHANNEL_LOST_ERROR_CODE,
394
+ stderr: `${result.stderr}${separator}${note}`,
395
+ };
396
+ }
397
+ export async function runAdapterExecutionTargetProcess(runId, target, command, args, options) {
398
+ if (target?.kind === "remote" && target.transport === "sandbox") {
399
+ const runner = requireSandboxRunner(target);
400
+ const env = sanitizeRemoteExecutionEnv(options.env);
401
+ await options.onRuntimeProgress?.({
402
+ phase: "adapter_startup",
403
+ message: "Starting adapter in environment",
404
+ });
405
+ const runLogTail = options.runLogTail?.create() ?? null;
406
+ let execCommand = command;
407
+ let execArgs = args;
408
+ if (runLogTail) {
409
+ ({ command: execCommand, args: execArgs } = runLogTail.wrapCommand(command, args));
410
+ runLogTail.start(options.onLog);
411
+ }
412
+ try {
413
+ const result = await runner.execute({
414
+ command: execCommand,
415
+ args: execArgs,
416
+ cwd: target.remoteCwd,
417
+ env,
418
+ stdin: options.stdin,
419
+ timeoutMs: options.timeoutSec > 0 ? options.timeoutSec * 1000 : target.timeoutMs ?? undefined,
420
+ // The tail loop already streams incremental chunks; suppress the
421
+ // runner's end-of-run batched onLog to avoid duplicate log bytes.
422
+ onLog: runLogTail ? undefined : options.onLog,
423
+ onSpawn: options.onSpawn
424
+ ? async (meta) => options.onSpawn?.({ ...meta, processGroupId: null })
425
+ : undefined,
426
+ });
427
+ // Settle the duplex run disposition synchronously at the clean-completion
428
+ // boundary, before the run-log tail finishes. The atomic settle marks the
429
+ // host-observed orderly completion in one broker step, so a gateway exit
430
+ // after the clean process completion cannot latch a false mid-run loss. A
431
+ // control channel that died before this clean completion still fails the
432
+ // run closed.
433
+ const settled = applyRunDispositionSeam(result, options.settleRunDisposition);
434
+ if (runLogTail) {
435
+ await runLogTail.finish({ stdout: result.stdout, stderr: result.stderr });
436
+ }
437
+ return settled;
438
+ }
439
+ catch (error) {
440
+ if (runLogTail) {
441
+ await runLogTail.abort();
442
+ }
443
+ throw error;
444
+ }
445
+ }
446
+ const env = target?.kind === "remote" && target.transport === "ssh"
447
+ ? sanitizeRemoteExecutionEnv(options.env)
448
+ : options.env;
449
+ return await runChildProcess(runId, command, args, {
450
+ cwd: options.cwd,
451
+ env,
452
+ stdin: options.stdin,
453
+ timeoutSec: options.timeoutSec,
454
+ graceSec: options.graceSec,
455
+ onLog: options.onLog,
456
+ onSpawn: options.onSpawn,
457
+ terminalResultCleanup: options.terminalResultCleanup,
458
+ localProcessSandbox: target?.kind === "local" || !target ? options.localProcessSandbox : null,
459
+ remoteExecution: adapterExecutionTargetToRemoteSpec(target),
460
+ });
461
+ }
462
+ export async function runAdapterExecutionTargetShellCommand(runId, target, command, options) {
463
+ const onLog = options.onLog ?? (async () => { });
464
+ if (target?.kind === "remote") {
465
+ const startedAt = new Date().toISOString();
466
+ const env = sanitizeRemoteExecutionEnv(options.env);
467
+ if (target.transport === "ssh") {
468
+ try {
469
+ // Pass the raw command — `runSshCommand` owns profile sourcing and
470
+ // the outer shell wrapper. Wrapping again here would nest a second
471
+ // shell after the explicit `env KEY=VAL` overrides, re-sourcing
472
+ // login profiles AFTER the override and silently undoing any
473
+ // identity var (NVM_DIR / PATH / etc.) that a profile re-exports.
474
+ const result = await runSshCommand(target.spec, command, {
475
+ env,
476
+ timeoutMs: (options.timeoutSec ?? 15) * 1000,
477
+ });
478
+ if (result.stdout)
479
+ await onLog("stdout", result.stdout);
480
+ if (result.stderr)
481
+ await onLog("stderr", result.stderr);
482
+ return {
483
+ exitCode: 0,
484
+ signal: null,
485
+ timedOut: false,
486
+ stdout: result.stdout,
487
+ stderr: result.stderr,
488
+ pid: null,
489
+ startedAt,
490
+ };
491
+ }
492
+ catch (error) {
493
+ const timedOutError = error;
494
+ const stdout = timedOutError.stdout ?? "";
495
+ const stderr = timedOutError.stderr ?? "";
496
+ if (typeof timedOutError.code === "number") {
497
+ if (stdout)
498
+ await onLog("stdout", stdout);
499
+ if (stderr)
500
+ await onLog("stderr", stderr);
501
+ return {
502
+ exitCode: timedOutError.code,
503
+ signal: timedOutError.signal ?? null,
504
+ timedOut: false,
505
+ stdout,
506
+ stderr,
507
+ pid: null,
508
+ startedAt,
509
+ };
510
+ }
511
+ if (timedOutError.code !== "ETIMEDOUT") {
512
+ throw error;
513
+ }
514
+ if (stdout)
515
+ await onLog("stdout", stdout);
516
+ if (stderr)
517
+ await onLog("stderr", stderr);
518
+ return {
519
+ exitCode: null,
520
+ signal: timedOutError.signal ?? null,
521
+ timedOut: true,
522
+ stdout,
523
+ stderr,
524
+ pid: null,
525
+ startedAt,
526
+ };
527
+ }
528
+ }
529
+ const shellCommand = preferredSandboxShell(target);
530
+ return await requireSandboxRunner(target).execute({
531
+ command: shellCommand,
532
+ args: shellCommandArgs(command),
533
+ cwd: target.remoteCwd,
534
+ env,
535
+ timeoutMs: (options.timeoutSec ?? 15) * 1000,
536
+ onLog,
537
+ });
538
+ }
539
+ return await runAdapterExecutionTargetProcess(runId, target, "sh", ["-lc", command], {
540
+ cwd: options.cwd,
541
+ env: options.env,
542
+ timeoutSec: options.timeoutSec ?? 15,
543
+ graceSec: options.graceSec ?? 5,
544
+ onLog,
545
+ });
546
+ }
547
+ // Best-effort run of an adapter-supplied install command on a sandbox target
548
+ // before the resolvability + hello probe. Returns null for non-sandbox
549
+ // targets so callers can no-op. Returns a structured check otherwise — never
550
+ // throws — so the rest of the test still runs and reports the post-install
551
+ // state honestly. Caller pushes the check into its result array; the test
552
+ // report shows whether install was attempted and what came back.
553
+ export async function maybeRunSandboxInstallCommand(input) {
554
+ const { target, adapterKey, installCommand } = input;
555
+ if (!target || target.kind !== "remote" || target.transport !== "sandbox") {
556
+ return null;
557
+ }
558
+ const trimmed = installCommand.trim();
559
+ if (trimmed.length === 0)
560
+ return null;
561
+ const code = `${adapterKey}_install_command_run`;
562
+ // Skip install when the binary is already on PATH. Avoids running
563
+ // network-dependent installers (e.g. `curl ... | bash`) on every test
564
+ // probe when the CLI is preinstalled on the lease/template.
565
+ const detectCommand = input.detectCommand?.trim();
566
+ if (detectCommand) {
567
+ try {
568
+ const probe = await runAdapterExecutionTargetShellCommand(input.runId, target, `command -v ${shellQuote(detectCommand)} >/dev/null 2>&1`, {
569
+ cwd: target.remoteCwd,
570
+ env: input.env ?? {},
571
+ timeoutSec: 30,
572
+ graceSec: 5,
573
+ });
574
+ if (!probe.timedOut && probe.exitCode === 0) {
575
+ return {
576
+ code,
577
+ level: "info",
578
+ message: `${detectCommand} already on PATH; skipped install.`,
579
+ };
580
+ }
581
+ }
582
+ catch {
583
+ // Fall through to actually running the install — failure to probe
584
+ // is not a reason to skip the install gate.
585
+ }
586
+ }
587
+ let result;
588
+ try {
589
+ result = await runAdapterExecutionTargetShellCommand(input.runId, target, trimmed, {
590
+ cwd: target.remoteCwd,
591
+ env: input.env ?? {},
592
+ timeoutSec: input.timeoutSec ?? 240,
593
+ graceSec: 10,
594
+ });
595
+ }
596
+ catch (err) {
597
+ return {
598
+ code,
599
+ level: "warn",
600
+ message: "Install command threw before completion.",
601
+ detail: err instanceof Error ? err.message : String(err),
602
+ };
603
+ }
604
+ const tail = (text) => text.split(/\r?\n/).filter((line) => line.trim().length > 0).slice(-3).join(" | ").slice(0, 480);
605
+ if (result.timedOut) {
606
+ return {
607
+ code,
608
+ level: "warn",
609
+ message: `Install command timed out: ${trimmed}`,
610
+ detail: tail(result.stderr || result.stdout),
611
+ };
612
+ }
613
+ if ((result.exitCode ?? 1) === 0) {
614
+ return {
615
+ code,
616
+ level: "info",
617
+ message: `Install command ran: ${trimmed}`,
618
+ ...(tail(result.stdout) ? { detail: tail(result.stdout) } : {}),
619
+ };
620
+ }
621
+ return {
622
+ code,
623
+ level: "warn",
624
+ message: `Install command exited ${result.exitCode}: ${trimmed}`,
625
+ detail: tail(result.stderr || result.stdout),
626
+ };
627
+ }
628
+ export async function readAdapterExecutionTargetHomeDir(runId, target, options) {
629
+ const result = await runAdapterExecutionTargetShellCommand(runId, target, 'printf %s "$HOME"', options);
630
+ const homeDir = result.stdout.trim();
631
+ return homeDir.length > 0 ? homeDir : null;
632
+ }
633
+ export async function ensureAdapterExecutionTargetRuntimeCommandInstalled(input) {
634
+ const installCommand = input.installCommand?.trim();
635
+ if (!installCommand || input.target?.kind !== "remote" || input.target.transport !== "sandbox") {
636
+ return;
637
+ }
638
+ const detectCommand = input.detectCommand?.trim();
639
+ if (detectCommand) {
640
+ const probe = await runAdapterExecutionTargetShellCommand(input.runId, input.target, `command -v ${shellQuote(detectCommand)} >/dev/null 2>&1`, {
641
+ cwd: input.cwd,
642
+ env: input.env,
643
+ timeoutSec: input.timeoutSec,
644
+ graceSec: input.graceSec,
645
+ });
646
+ if (!probe.timedOut && probe.exitCode === 0) {
647
+ return;
648
+ }
649
+ }
650
+ const result = await runAdapterExecutionTargetShellCommand(input.runId, input.target, installCommand, {
651
+ cwd: input.cwd,
652
+ env: input.env,
653
+ timeoutSec: input.timeoutSec,
654
+ graceSec: input.graceSec,
655
+ onLog: input.onLog,
656
+ });
657
+ // A failed or timed-out install is not necessarily fatal: the CLI may already
658
+ // be on PATH from a previous lease's install, the template image, or another
659
+ // path entry. Re-run the detect probe (when one is configured) so a transient
660
+ // install failure does not abort the agent run when the binary is reachable.
661
+ const installFailed = result.timedOut || (result.exitCode ?? 0) !== 0;
662
+ if (!installFailed) {
663
+ return;
664
+ }
665
+ if (detectCommand) {
666
+ const recheck = await runAdapterExecutionTargetShellCommand(input.runId, input.target, `command -v ${shellQuote(detectCommand)} >/dev/null 2>&1`, {
667
+ cwd: input.cwd,
668
+ env: input.env,
669
+ timeoutSec: input.timeoutSec,
670
+ graceSec: input.graceSec,
671
+ });
672
+ if (!recheck.timedOut && recheck.exitCode === 0) {
673
+ if (input.onLog) {
674
+ const reason = result.timedOut ? "timed out" : `exited ${result.exitCode ?? "?"}`;
675
+ await input.onLog("stderr", `[paperclip] Install command ${reason} (${installCommand}) but ${detectCommand} is on PATH; continuing.\n`);
676
+ }
677
+ return;
678
+ }
679
+ }
680
+ if (result.timedOut) {
681
+ throw new Error(`Timed out while installing the adapter runtime command via: ${installCommand}`);
682
+ }
683
+ throw new Error(`Failed to install the adapter runtime command via: ${installCommand}`);
684
+ }
685
+ export async function ensureAdapterExecutionTargetFile(runId, target, filePath, options) {
686
+ await runAdapterExecutionTargetShellCommand(runId, target, `mkdir -p ${shellQuote(path.posix.dirname(filePath))} && : > ${shellQuote(filePath)}`, options);
687
+ }
688
+ /**
689
+ * Ensure a working directory exists (and is a directory) on the execution target.
690
+ *
691
+ * For local targets this delegates to the local `ensureAbsoluteDirectory` helper
692
+ * (Node fs). For remote (SSH/sandbox) targets it shells out and runs
693
+ * `mkdir -p` (when allowed) followed by a `[ -d ]` check so the result reflects
694
+ * the directory state inside the environment, not on the Paperclip host.
695
+ *
696
+ * Throws an Error with a human-readable message on failure.
697
+ */
698
+ export async function ensureAdapterExecutionTargetDirectory(runId, target, cwd, options) {
699
+ const createIfMissing = options.createIfMissing ?? false;
700
+ if (!target || target.kind === "local") {
701
+ const { ensureAbsoluteDirectory } = await import("./server-utils.js");
702
+ await ensureAbsoluteDirectory(cwd, { createIfMissing });
703
+ return;
704
+ }
705
+ // Remote (SSH or sandbox): both expect POSIX absolute paths inside the env.
706
+ if (!cwd.startsWith("/")) {
707
+ throw new Error(`Working directory must be an absolute POSIX path on the remote target: "${cwd}"`);
708
+ }
709
+ const quoted = shellQuote(cwd);
710
+ const script = createIfMissing
711
+ ? `mkdir -p ${quoted} && [ -d ${quoted} ]`
712
+ : `[ -d ${quoted} ]`;
713
+ const result = await runAdapterExecutionTargetShellCommand(runId, target, script, {
714
+ cwd: target.kind === "remote" ? target.remoteCwd : cwd,
715
+ env: options.env,
716
+ timeoutSec: options.timeoutSec ?? 15,
717
+ graceSec: options.graceSec ?? 5,
718
+ onLog: options.onLog,
719
+ });
720
+ if (result.timedOut) {
721
+ throw new Error(`Timed out checking working directory on remote target: "${cwd}"`);
722
+ }
723
+ if ((result.exitCode ?? 1) !== 0) {
724
+ const detail = (result.stderr || result.stdout || "").trim();
725
+ if (createIfMissing) {
726
+ throw new Error(`Could not create working directory "${cwd}" on remote target${detail ? `: ${detail}` : "."}`);
727
+ }
728
+ throw new Error(`Working directory does not exist on remote target: "${cwd}"${detail ? ` (${detail})` : ""}`);
729
+ }
730
+ }
731
+ export function adapterExecutionTargetSessionIdentity(target) {
732
+ if (!target || target.kind === "local")
733
+ return null;
734
+ if (target.transport === "ssh")
735
+ return buildRemoteExecutionSessionIdentity(target.spec);
736
+ return {
737
+ transport: "sandbox",
738
+ providerKey: target.providerKey ?? null,
739
+ environmentId: target.environmentId ?? null,
740
+ leaseId: target.leaseId ?? null,
741
+ remoteCwd: target.remoteCwd,
742
+ };
743
+ }
744
+ export function adapterExecutionTargetSessionMatches(saved, target) {
745
+ if (!target || target.kind === "local") {
746
+ return Object.keys(parseObject(saved)).length === 0;
747
+ }
748
+ if (target.transport === "ssh")
749
+ return remoteExecutionSessionMatches(saved, target.spec);
750
+ const current = adapterExecutionTargetSessionIdentity(target);
751
+ const parsedSaved = parseObject(saved);
752
+ return (readStringMeta(parsedSaved, "transport") === current?.transport &&
753
+ readStringMeta(parsedSaved, "providerKey") === current?.providerKey &&
754
+ readStringMeta(parsedSaved, "environmentId") === current?.environmentId &&
755
+ readStringMeta(parsedSaved, "leaseId") === current?.leaseId &&
756
+ readStringMeta(parsedSaved, "remoteCwd") === current?.remoteCwd);
757
+ }
758
+ export function parseAdapterExecutionTarget(value) {
759
+ const parsed = parseObject(value);
760
+ const kind = readStringMeta(parsed, "kind");
761
+ if (kind === "local") {
762
+ return {
763
+ kind: "local",
764
+ environmentId: readStringMeta(parsed, "environmentId"),
765
+ leaseId: readStringMeta(parsed, "leaseId"),
766
+ };
767
+ }
768
+ if (kind === "remote" && readStringMeta(parsed, "transport") === "ssh") {
769
+ const spec = parseSshRemoteExecutionSpec(parseObject(parsed.spec));
770
+ if (!spec)
771
+ return null;
772
+ return {
773
+ kind: "remote",
774
+ transport: "ssh",
775
+ environmentId: readStringMeta(parsed, "environmentId"),
776
+ leaseId: readStringMeta(parsed, "leaseId"),
777
+ remoteCwd: spec.remoteCwd,
778
+ spec,
779
+ };
780
+ }
781
+ if (kind === "remote" && readStringMeta(parsed, "transport") === "sandbox") {
782
+ const remoteCwd = readStringMeta(parsed, "remoteCwd");
783
+ if (!remoteCwd)
784
+ return null;
785
+ const effectiveCapabilities = parseEffectiveExecutionCapabilities(parsed.effectiveCapabilities);
786
+ return {
787
+ kind: "remote",
788
+ transport: "sandbox",
789
+ providerKey: readStringMeta(parsed, "providerKey"),
790
+ environmentId: readStringMeta(parsed, "environmentId"),
791
+ leaseId: readStringMeta(parsed, "leaseId"),
792
+ remoteCwd,
793
+ timeoutMs: typeof parsed.timeoutMs === "number" ? parsed.timeoutMs : null,
794
+ streamRunLogs: typeof parsed.streamRunLogs === "boolean" ? parsed.streamRunLogs : null,
795
+ // Fail closed: only the literal `true` reads as a grant. An absent field
796
+ // or any other value parses as no grant, so a round-trip never invents one.
797
+ enableSandboxDuplexBridge: parsed.enableSandboxDuplexBridge === true,
798
+ ...(effectiveCapabilities ? { effectiveCapabilities } : {}),
799
+ };
800
+ }
801
+ return null;
802
+ }
803
+ export function adapterExecutionTargetFromRemoteExecution(remoteExecution, metadata = {}) {
804
+ const parsed = parseObject(remoteExecution);
805
+ const ssh = parseSshRemoteExecutionSpec(parsed);
806
+ if (ssh) {
807
+ return {
808
+ kind: "remote",
809
+ transport: "ssh",
810
+ environmentId: metadata.environmentId ?? null,
811
+ leaseId: metadata.leaseId ?? null,
812
+ remoteCwd: ssh.remoteCwd,
813
+ spec: ssh,
814
+ };
815
+ }
816
+ return null;
817
+ }
818
+ export function readAdapterExecutionTarget(input) {
819
+ if (isAdapterExecutionTargetInstance(input.executionTarget)) {
820
+ return input.executionTarget;
821
+ }
822
+ return (parseAdapterExecutionTarget(input.executionTarget) ??
823
+ adapterExecutionTargetFromRemoteExecution(input.legacyRemoteExecution));
824
+ }
825
+ export async function prepareAdapterExecutionTargetRuntime(input) {
826
+ const target = input.target ?? { kind: "local" };
827
+ if (target.kind === "local") {
828
+ return {
829
+ target,
830
+ workspaceRemoteDir: null,
831
+ runtimeRootDir: null,
832
+ assetDirs: {},
833
+ additionalSourceDirs: {},
834
+ additionalSourceFailures: [],
835
+ workspaceSyncSnapshot: null,
836
+ restoreWorkspace: async () => { },
837
+ };
838
+ }
839
+ if (target.transport === "ssh") {
840
+ const prepared = await prepareRemoteManagedRuntime({
841
+ spec: target.spec,
842
+ runId: input.runId,
843
+ adapterKey: input.adapterKey,
844
+ workspaceLocalDir: input.workspaceLocalDir,
845
+ workspaceRemoteDir: input.workspaceRemoteDir,
846
+ syncWorkspace: input.syncWorkspace,
847
+ assets: input.assets,
848
+ additionalSources: input.additionalSources,
849
+ onProgress: input.onProgress,
850
+ });
851
+ return {
852
+ target,
853
+ workspaceRemoteDir: prepared.workspaceRemoteDir,
854
+ runtimeRootDir: prepared.runtimeRootDir,
855
+ assetDirs: prepared.assetDirs,
856
+ additionalSourceDirs: prepared.additionalSourceDirs,
857
+ // The SSH transport does not stage referenced projects (it is out of scope), so it never
858
+ // reports a per-project staging failure.
859
+ additionalSourceFailures: [],
860
+ workspaceSyncSnapshot: null,
861
+ restoreWorkspace: prepared.restoreWorkspace,
862
+ };
863
+ }
864
+ const prepared = await prepareCommandManagedRuntime({
865
+ runner: requireSandboxRunner(target),
866
+ spec: {
867
+ providerKey: target.providerKey,
868
+ shellCommand: target.shellCommand,
869
+ leaseId: target.leaseId,
870
+ remoteCwd: target.remoteCwd,
871
+ timeoutMs: input.timeoutSec && input.timeoutSec > 0
872
+ ? input.timeoutSec * 1000
873
+ : target.timeoutMs,
874
+ },
875
+ adapterKey: input.adapterKey,
876
+ workspaceLocalDir: input.workspaceLocalDir,
877
+ workspaceRemoteDir: input.workspaceRemoteDir,
878
+ syncWorkspace: input.syncWorkspace,
879
+ workspaceInboundMode: input.workspaceInboundMode,
880
+ workspaceDurableSeed: input.workspaceDurableSeed,
881
+ workspaceBaseline: input.workspaceBaseline,
882
+ workspaceGitSnapshot: input.workspaceGitSnapshot,
883
+ workspaceExclude: input.workspaceExclude,
884
+ preserveAbsentOnRestore: input.preserveAbsentOnRestore,
885
+ assets: input.assets,
886
+ additionalSources: input.additionalSources,
887
+ installCommand: input.installCommand,
888
+ detectCommand: input.detectCommand,
889
+ onProgress: input.onProgress,
890
+ onRuntimeProgress: input.onRuntimeProgress,
891
+ runtimeSpan: input.runtimeSpan,
892
+ });
893
+ return {
894
+ target,
895
+ workspaceRemoteDir: prepared.workspaceRemoteDir,
896
+ runtimeRootDir: prepared.runtimeRootDir,
897
+ assetDirs: prepared.assetDirs,
898
+ additionalSourceDirs: prepared.additionalSourceDirs,
899
+ additionalSourceFailures: prepared.additionalSourceFailures,
900
+ workspaceSyncSnapshot: prepared.workspaceSyncSnapshot,
901
+ restoreWorkspace: prepared.restoreWorkspace,
902
+ };
903
+ }
904
+ export function runtimeAssetDir(prepared, key, fallbackRemoteCwd) {
905
+ return prepared.assetDirs[key] ?? path.posix.join(fallbackRemoteCwd, ".paperclip-runtime", key);
906
+ }
907
+ function githubOperationLauncherDirectory(input) {
908
+ // Only controller-generated run IDs may name a removable directory.
909
+ if (!/^[a-zA-Z0-9_-]+$/.test(input.runId))
910
+ throw new Error("Invalid GitHub launcher run ID");
911
+ return input.target?.kind === "remote"
912
+ ? path.posix.join(input.target.remoteCwd, ".paperclip-runtime", "github", input.runId)
913
+ : path.join(os.tmpdir(), "paperclip-github-runtime", input.runId);
914
+ }
915
+ /** Call only after execution settles, before releasing its remote environment lease. */
916
+ export async function cleanupGitHubOperationLaunchers(input) {
917
+ const directory = githubOperationLauncherDirectory(input);
918
+ if (input.target?.kind === "remote") {
919
+ const result = await adapterExecutionTargetCommandRunner(input.target).execute({
920
+ command: "sh", args: ["-c", `rm -rf -- ${shellQuote(directory)}`],
921
+ cwd: input.target.remoteCwd, timeoutMs: 5_000,
922
+ });
923
+ if (result.exitCode !== 0)
924
+ throw new Error("Could not clean managed GitHub launchers");
925
+ }
926
+ else {
927
+ await fs.rm(directory, { recursive: true, force: true });
928
+ }
929
+ }
930
+ async function githubOperationLauncherBasePath(target, env) {
931
+ if (!target)
932
+ return env.PATH || process.env.PATH || "/usr/bin:/bin";
933
+ const configuredPath = sanitizeRemoteExecutionEnv(env).PATH;
934
+ if (configuredPath !== undefined)
935
+ return configuredPath;
936
+ // The provider owns login/profile setup. Query its effective PATH before
937
+ // staging BASH_ENV, rather than substituting the controller's toolchain or
938
+ // a minimal PATH that hides legacy NVM/user-local agent installations.
939
+ const result = await adapterExecutionTargetCommandRunner(target).execute({
940
+ command: "sh",
941
+ args: ["-c", "printf '\\000%s\\000' \"$PATH\""],
942
+ cwd: target.remoteCwd,
943
+ timeoutMs: 15_000,
944
+ });
945
+ // Frame the value so login banners cannot become executable search paths.
946
+ const remotePath = result.stdout.match(/\0([^\0]+)\0/)?.[1];
947
+ if (result.timedOut || result.exitCode !== 0 || !remotePath) {
948
+ throw new Error("Could not resolve remote PATH for managed GitHub launchers");
949
+ }
950
+ return remotePath;
951
+ }
952
+ /** Read only execution-target Git context; never import the controller's credentials into SSH. */
953
+ export async function prepareGitHubExecutionEnvironment(input) {
954
+ const script = String.raw `
955
+ const fs = require('node:fs');
956
+ const path = require('node:path');
957
+ const cp = require('node:child_process');
958
+ const env = {};
959
+ env.PAPERCLIP_RUNNER_NETWORK_ROOTS = JSON.stringify(['/etc/resolv.conf','/etc/hosts','/etc/nsswitch.conf','/etc/ssl/certs','/etc/ssl/cert.pem'].flatMap(p => { try { return [fs.realpathSync(p)]; } catch { return []; } }));
960
+ if (process.argv[1] === 'host') {
961
+ for (const [key, value] of Object.entries(process.env)) {
962
+ if (/^(GH_TOKEN|GITHUB_TOKEN|GH_ENTERPRISE_TOKEN|GITHUB_ENTERPRISE_TOKEN|PAPERCLIP_GIT_TOKEN|GH_CONFIG_DIR|GIT_CONFIG_(GLOBAL|SYSTEM|NOSYSTEM|COUNT|KEY_\d+|VALUE_\d+)|GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)|GIT_ASKPASS|SSH_ASKPASS|SSH_AUTH_SOCK|GIT_SSH_COMMAND|GIT_SSH)$/.test(key)) env[key] = value;
963
+ }
964
+ env.PAPERCLIP_GITHUB_HOST_HOME = process.env.HOME || '';
965
+ env.GH_CONFIG_DIR ||= path.join(process.env.XDG_CONFIG_HOME || path.join(process.env.HOME || '', '.config'), 'gh');
966
+ }
967
+ try {
968
+ const top = cp.execFileSync('git', ['rev-parse', '--show-toplevel'], {encoding:'utf8',stdio:['ignore','pipe','ignore']}).trim();
969
+ if (fs.realpathSync(top) === fs.realpathSync(process.cwd())) {
970
+ env.PAPERCLIP_GIT_METADATA_ROOTS = JSON.stringify(cp.execFileSync('git', ['rev-parse','--path-format=absolute','--git-common-dir','--git-dir'], {encoding:'utf8',stdio:['ignore','pipe','ignore']}).trim().split('\n').map(p => fs.realpathSync(p)));
971
+ }
972
+ } catch {}
973
+ process.stdout.write("\0" + JSON.stringify(env) + "\0");
974
+ `;
975
+ const args = ["-e", script, input.hostCredentials ? "host" : "managed"];
976
+ const remote = input.target?.kind === "remote" ? input.target : null;
977
+ let discovered;
978
+ if (remote) {
979
+ // A legacy SSH host may run a standalone agent binary without Node. Use
980
+ // only the shell and Git, and emit bounded, NUL-framed environment records.
981
+ const probe = String.raw `
982
+ printf '\0PAPERCLIP_GIT_CONTEXT_V1\0'
983
+ if [ "$1" = host ]; then
984
+ for key in GH_TOKEN GITHUB_TOKEN GH_ENTERPRISE_TOKEN GITHUB_ENTERPRISE_TOKEN PAPERCLIP_GIT_TOKEN GH_CONFIG_DIR GIT_CONFIG_GLOBAL GIT_CONFIG_SYSTEM GIT_CONFIG_NOSYSTEM GIT_CONFIG_COUNT GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL GIT_ASKPASS SSH_ASKPASS SSH_AUTH_SOCK GIT_SSH_COMMAND GIT_SSH; do
985
+ eval 'value=${"$"}{'"$key"'-}'
986
+ [ -z "$value" ] || printf '%s\0%s\0' "$key" "$value"
987
+ done
988
+ index=0
989
+ while [ "$index" -lt 32 ]; do
990
+ for prefix in GIT_CONFIG_KEY_ GIT_CONFIG_VALUE_; do
991
+ key="$prefix$index"
992
+ eval 'value=${"$"}{'"$key"'-}'
993
+ [ -z "$value" ] || printf '%s\0%s\0' "$key" "$value"
994
+ done
995
+ index=$((index + 1))
996
+ done
997
+ printf 'PAPERCLIP_GITHUB_HOST_HOME\0%s\0' "$HOME"
998
+ printf 'GH_CONFIG_DIR\0%s\0' "${"$"}{GH_CONFIG_DIR:-${"$"}{XDG_CONFIG_HOME:-$HOME/.config}/gh}"
999
+ fi
1000
+ for file in /etc/resolv.conf /etc/hosts /etc/nsswitch.conf /etc/ssl/certs /etc/ssl/cert.pem; do
1001
+ index=0
1002
+ while [ -L "$file" ] && [ "$index" -lt 40 ]; do
1003
+ target=$(readlink "$file") || break
1004
+ case "$target" in /*) file="$target" ;; *) file="$(dirname "$file")/$target" ;; esac
1005
+ index=$((index + 1))
1006
+ done
1007
+ if [ -e "$file" ]; then
1008
+ parent=$(cd "$(dirname "$file")" && pwd -P) || continue
1009
+ printf 'PAPERCLIP_RUNNER_NETWORK_ROOT\0%s\0' "$parent/$(basename "$file")"
1010
+ fi
1011
+ done
1012
+ cwd=$(pwd -P)
1013
+ top=$(git rev-parse --show-toplevel 2>/dev/null) || top=
1014
+ if [ -n "$top" ] && [ "$(cd "$top" && pwd -P)" = "$cwd" ]; then
1015
+ for kind in --git-common-dir --git-dir; do
1016
+ root=$(git rev-parse --path-format=absolute "$kind" 2>/dev/null) || continue
1017
+ root=$(cd "$root" && pwd -P) || continue
1018
+ printf 'PAPERCLIP_GIT_METADATA_ROOT\0%s\0' "$root"
1019
+ done
1020
+ fi
1021
+ printf '\0PAPERCLIP_GIT_CONTEXT_END\0'
1022
+ `;
1023
+ const result = await adapterExecutionTargetCommandRunner(remote).execute({
1024
+ command: "sh", args: ["-c", probe, "paperclip-git-context", input.hostCredentials ? "host" : "managed"],
1025
+ // The caller's cwd belongs to the controller. Copied sandbox/SSH
1026
+ // workspaces can live at a different path on the execution target.
1027
+ cwd: remote.remoteCwd, timeoutMs: 15_000,
1028
+ });
1029
+ if (result.exitCode !== 0)
1030
+ throw new Error("Could not read execution-target Git context");
1031
+ const payload = result.stdout.split("\0PAPERCLIP_GIT_CONTEXT_V1\0")[1]?.split("\0PAPERCLIP_GIT_CONTEXT_END\0")[0];
1032
+ if (payload === undefined)
1033
+ throw new Error("Could not read execution-target Git context");
1034
+ discovered = {};
1035
+ const records = payload.split("\0");
1036
+ const roots = [];
1037
+ const networkRoots = [];
1038
+ for (let index = 0; index + 1 < records.length; index += 2) {
1039
+ const key = records[index];
1040
+ const value = records[index + 1];
1041
+ if (key === "PAPERCLIP_GIT_METADATA_ROOT")
1042
+ roots.push(value);
1043
+ else if (key === "PAPERCLIP_RUNNER_NETWORK_ROOT")
1044
+ networkRoots.push(value);
1045
+ else
1046
+ discovered[key] = value;
1047
+ }
1048
+ discovered.PAPERCLIP_GIT_METADATA_ROOTS = JSON.stringify([...new Set(roots)]);
1049
+ discovered.PAPERCLIP_RUNNER_NETWORK_ROOTS = JSON.stringify([...new Set(networkRoots)]);
1050
+ }
1051
+ else {
1052
+ const result = await promisify(execFile)(process.execPath, args, { cwd: input.cwd, timeout: 15_000, maxBuffer: 1024 * 1024 });
1053
+ try {
1054
+ discovered = JSON.parse(result.stdout.split("\0")[1] ?? "");
1055
+ }
1056
+ catch {
1057
+ throw new Error("Could not read execution-target Git context");
1058
+ }
1059
+ }
1060
+ // Controller-derived roots and mode must not be replaced by agent bindings.
1061
+ return { ...discovered, ...input.env,
1062
+ ...(input.hostCredentials ? { PAPERCLIP_GITHUB_HOST_HOME: discovered.PAPERCLIP_GITHUB_HOST_HOME } : {}),
1063
+ PAPERCLIP_GIT_METADATA_ROOTS: discovered.PAPERCLIP_GIT_METADATA_ROOTS ?? "[]",
1064
+ PAPERCLIP_RUNNER_NETWORK_ROOTS: discovered.PAPERCLIP_RUNNER_NETWORK_ROOTS ?? "[]",
1065
+ PAPERCLIP_GITHUB_AUTH_MODE: input.hostCredentials ? "host" : "managed",
1066
+ PAPERCLIP_RUNNER_NETWORK_ACCESS: input.networkAccess ? "enabled" : "disabled",
1067
+ };
1068
+ }
1069
+ /** Stage token-free launchers next to the execution, not in shared global Git config. */
1070
+ export async function prepareGitHubOperationLaunchers(input) {
1071
+ const remote = input.target?.kind === "remote" ? input.target : null;
1072
+ const directory = githubOperationLauncherDirectory(input);
1073
+ const configDirectory = path.posix.join(directory, "gh-config");
1074
+ const basePath = await githubOperationLauncherBasePath(remote, input.env);
1075
+ const managedPath = basePath ? `${directory}:${basePath}` : directory;
1076
+ // Login shells may reorder PATH through /etc/profile or path_helper. Restore
1077
+ // the managed launchers after startup without loading a host user's profile.
1078
+ // Empty merge overrides clear host identity before launch, but Git treats
1079
+ // them as an explicit empty author. Remove them once the shell has inherited
1080
+ // its final environment; preserve nonempty per-operation identity values.
1081
+ const clearEmptyGitIdentity = ["GIT_AUTHOR_NAME", "GIT_AUTHOR_EMAIL", "GIT_COMMITTER_NAME", "GIT_COMMITTER_EMAIL"]
1082
+ .map((key) => `if [ -z "\${${key}-}" ]; then unset ${key}; fi\n`)
1083
+ .join("");
1084
+ const profile = `export PATH=${shellQuote(managedPath)}\n${clearEmptyGitIdentity}`;
1085
+ const files = Object.fromEntries([
1086
+ // Remote launchers live beneath the checkout. Pin their own package scope
1087
+ // so an enclosing project's "type": "module" cannot reinterpret require().
1088
+ ["package.json", '{"type":"commonjs"}\n'],
1089
+ ...["git", "gh"].map((name) => [name, githubLauncherSource()]),
1090
+ ...[".zshenv", ".zprofile", ".zshrc", ".bash_profile", ".bashrc", ".profile"].map((name) => [name, profile]),
1091
+ ]);
1092
+ if (remote) {
1093
+ const runner = adapterExecutionTargetCommandRunner(remote);
1094
+ for (const [program, body] of Object.entries(files)) {
1095
+ await syncRemoteTextFileWithHashSkip({
1096
+ runner, remoteCwd: remote.remoteCwd, remoteDir: directory,
1097
+ remotePath: path.posix.join(directory, program), body,
1098
+ label: "GitHub operation launcher", action: "stage GitHub operation launcher",
1099
+ lockDir: path.posix.join(directory, `.${program}.lock`),
1100
+ timeoutMs: 15_000, shellCommand: adapterExecutionTargetShellCommand(remote),
1101
+ });
1102
+ }
1103
+ const permissions = await runner.execute({ command: "sh", args: ["-c", `chmod 700 ${shellQuote(directory)}/git ${shellQuote(directory)}/gh && mkdir -p ${shellQuote(configDirectory)}`], cwd: remote.remoteCwd, timeoutMs: 15_000 });
1104
+ if (permissions.exitCode !== 0)
1105
+ throw new Error("Could not prepare managed GitHub launchers");
1106
+ }
1107
+ else {
1108
+ await fs.mkdir(directory, { recursive: true, mode: 0o700 });
1109
+ await fs.mkdir(configDirectory, { recursive: true, mode: 0o700 });
1110
+ for (const [program, body] of Object.entries(files))
1111
+ await fs.writeFile(path.join(directory, program), body, { mode: 0o700 });
1112
+ }
1113
+ return { ...input.env, PATH: managedPath, ZDOTDIR: directory, BASH_ENV: `${directory}/.bashrc`,
1114
+ GH_CONFIG_DIR: configDirectory, PAPERCLIP_GITHUB_LAUNCHER_DIR: directory };
1115
+ }
1116
+ function buildBridgeResponseHeaders(response) {
1117
+ const out = {};
1118
+ // Keep `x-paperclip-bridge-outcome` in this list. The host marks a
1119
+ // possibly-committed mutation with the `indeterminate` outcome. The in-sandbox
1120
+ // server reads that header to map the 504 to a terminal 409. If the forward
1121
+ // drops the header, the server keeps the retryable 504 and a caller that
1122
+ // retries 5xx can repeat a mutation that already committed.
1123
+ for (const key of ["content-type", "etag", "last-modified", "x-paperclip-bridge-outcome"]) {
1124
+ const value = response.headers.get(key);
1125
+ if (value && value.trim().length > 0)
1126
+ out[key] = value.trim();
1127
+ }
1128
+ return out;
1129
+ }
1130
+ function buildBridgeForwardUrl(baseUrl, request) {
1131
+ const url = new URL(request.path, baseUrl);
1132
+ const query = request.query.trim();
1133
+ url.search = query.startsWith("?") ? query.slice(1) : query;
1134
+ return url;
1135
+ }
1136
+ function bridgeResponseBodyLimitError(maxBodyBytes) {
1137
+ return new Error(`Bridge response body exceeded the configured size limit of ${maxBodyBytes} bytes.`);
1138
+ }
1139
+ /**
1140
+ * Read the forward response body into a `Buffer`, with no text decoding. The
1141
+ * per-request `maxBodyBytes` limit rejects a body larger than the configured
1142
+ * per-request ceiling.
1143
+ *
1144
+ * When the caller passes a `reservation`, this reserves each chunk's bytes
1145
+ * against it immediately after `reader.read()` yields the chunk, and before
1146
+ * `Buffer.from(value)` copies it — the allocation happens inside that
1147
+ * expression, so reserving only before the later `chunks.push` would let the
1148
+ * copy happen first. It also reserves the concatenated buffer's own byte
1149
+ * count before `Buffer.concat` allocates it: the chunk array and the
1150
+ * concatenated buffer are two separate live copies. A denied reservation
1151
+ * cancels the reader and throws {@link BridgeProcessCapacityError}, copying
1152
+ * no further chunk. This function never releases the reservation; the
1153
+ * stream owner that created it does, once the whole forward call settles.
1154
+ *
1155
+ * With no `reservation`, this function enforces only the one request's own
1156
+ * ceiling, exactly as it did before this parameter existed — the queue
1157
+ * transport calls it with no reservation, and its behavior must not change.
1158
+ */
1159
+ async function readBridgeForwardResponseBody(response, maxBodyBytes, reservation) {
1160
+ const rawContentLength = response.headers.get("content-length");
1161
+ if (rawContentLength) {
1162
+ const contentLength = Number.parseInt(rawContentLength, 10);
1163
+ if (Number.isFinite(contentLength) && contentLength > maxBodyBytes) {
1164
+ throw bridgeResponseBodyLimitError(maxBodyBytes);
1165
+ }
1166
+ }
1167
+ if (!response.body) {
1168
+ return Buffer.alloc(0);
1169
+ }
1170
+ const reader = response.body.getReader();
1171
+ const chunks = [];
1172
+ let totalBytes = 0;
1173
+ while (true) {
1174
+ const { done, value } = await reader.read();
1175
+ if (done)
1176
+ break;
1177
+ if (!value)
1178
+ continue;
1179
+ const chunkBytes = value.byteLength;
1180
+ totalBytes += chunkBytes;
1181
+ if (totalBytes > maxBodyBytes) {
1182
+ await reader.cancel().catch(() => undefined);
1183
+ throw bridgeResponseBodyLimitError(maxBodyBytes);
1184
+ }
1185
+ if (reservation && !reservation.reserve(chunkBytes)) {
1186
+ await reader.cancel().catch(() => undefined);
1187
+ throw new BridgeProcessCapacityError();
1188
+ }
1189
+ chunks.push(Buffer.from(value));
1190
+ }
1191
+ if (reservation && !reservation.reserve(totalBytes)) {
1192
+ await reader.cancel().catch(() => undefined);
1193
+ throw new BridgeProcessCapacityError();
1194
+ }
1195
+ return Buffer.concat(chunks, totalBytes);
1196
+ }
1197
+ const PROCESS_SESSION_PROXY_SCRIPT = "paperclip-process-session-proxy.mjs";
1198
+ const PROCESS_SESSION_REMOTE_SCRIPT = "paperclip-process-session-remote.mjs";
1199
+ // The streamed variant writes its output frames to stdout, so it rides a
1200
+ // separate remote path. A sandbox can hold both scripts without the content
1201
+ // hash-skip gate thrashing when a run switches output mode.
1202
+ const PROCESS_SESSION_REMOTE_STREAM_SCRIPT = "paperclip-process-session-remote-stream.mjs";
1203
+ const PROCESS_SESSION_AUTH_TIMEOUT_MS = 5_000;
1204
+ // The bounded budget `stop()` waits for the wrapper's `shutdownAck` event
1205
+ // before it removes `sessionDir` unconditionally. The wrapper writes the
1206
+ // acknowledgement right after it arms its own kill timer, well before its
1207
+ // child actually exits, so this budget only needs to cover message delivery,
1208
+ // not the child's full shutdown.
1209
+ const DEFAULT_PROCESS_SESSION_SHUTDOWN_WAIT_MS = 3_000;
1210
+ function jsonLine(value) {
1211
+ return `${JSON.stringify(value)}\n`;
1212
+ }
1213
+ function splitJsonLines(buffer) {
1214
+ const parts = buffer.split(/\n/);
1215
+ return { lines: parts.slice(0, -1), rest: parts.at(-1) ?? "" };
1216
+ }
1217
+ async function writeProcessSessionProxyScript(dir, port, token) {
1218
+ await fs.mkdir(dir, { recursive: true });
1219
+ const proxyPath = path.join(dir, PROCESS_SESSION_PROXY_SCRIPT);
1220
+ await fs.writeFile(proxyPath, getProcessSessionProxySource({ port, token }), { mode: 0o700 });
1221
+ return proxyPath;
1222
+ }
1223
+ // Content-hash-skip the process-session remote script write, mirroring the
1224
+ // sandbox callback bridge entrypoint sha256 gate. The script is a static
1225
+ // Paperclip-authored `.mjs` that only changes when the build changes, so on a
1226
+ // warm start (same sandbox, script already present) the single sha-gate exec
1227
+ // skips the ~3-exec base64 upload entirely. `syncRemoteTextFileWithHashSkip`
1228
+ // fails loud on a check error rather than silently re-uploading.
1229
+ async function syncProcessSessionRemoteScript(input) {
1230
+ const { uploaded } = await syncRemoteTextFileWithHashSkip({
1231
+ runner: input.runner,
1232
+ remoteCwd: input.remoteCwd,
1233
+ remoteDir: input.remoteScriptDir,
1234
+ remotePath: input.remoteScriptPath,
1235
+ body: getProcessSessionRemoteSource({ outputToStdout: input.outputToStdout === true }),
1236
+ label: "Process session remote script",
1237
+ action: "sync process session remote script",
1238
+ lockDir: path.posix.join(input.remoteScriptDir, ".paperclip-process-session-script.lock"),
1239
+ timeoutMs: input.timeoutMs,
1240
+ shellCommand: input.shellCommand,
1241
+ });
1242
+ return { uploaded };
1243
+ }
1244
+ async function readRemoteJsonFiles(input) {
1245
+ const names = await input.client.listJsonFiles(input.dir);
1246
+ const out = [];
1247
+ for (const name of names) {
1248
+ const filePath = path.posix.join(input.dir, name);
1249
+ const body = await input.client.readTextFile(filePath);
1250
+ await input.client.remove(filePath).catch(() => undefined);
1251
+ out.push({ name, body });
1252
+ }
1253
+ return out;
1254
+ }
1255
+ async function waitForLocalServerListen(server) {
1256
+ await new Promise((resolve, reject) => {
1257
+ server.once("error", reject);
1258
+ server.listen(0, "127.0.0.1", () => {
1259
+ server.off("error", reject);
1260
+ resolve();
1261
+ });
1262
+ });
1263
+ const address = server.address();
1264
+ if (!address || typeof address === "string") {
1265
+ throw new Error("Process session bridge did not expose a TCP port.");
1266
+ }
1267
+ return address.port;
1268
+ }
1269
+ /** Span name that wraps the socket handler's one `writeTextFile` exec — one
1270
+ * outbound ACP message to the agent. */
1271
+ const AGENT_SESSION_SEND_INPUT_SPAN = "sandbox.agentSession.sendInput";
1272
+ /** Span name that wraps one 100 ms poll tick — `list`, then `read`+`remove` per
1273
+ * file found (`1 + 2n` execs). */
1274
+ const AGENT_SESSION_POLL_OUTPUT_SPAN = "sandbox.agentSession.pollOutput";
1275
+ export async function startAdapterExecutionTargetProcessSessionBridge(input) {
1276
+ if (!input.target || input.target.kind !== "remote" || input.target.transport !== "sandbox") {
1277
+ return null;
1278
+ }
1279
+ const target = input.target;
1280
+ const onLog = input.onLog ?? (async () => { });
1281
+ const runner = requireSandboxRunner(target);
1282
+ // Run one unit of run-time work under its named wrapper span when a span
1283
+ // runner is injected. Without a runner, run the work under the current run
1284
+ // parent, so the inner `sandbox.exec` spans parent to the live run span,
1285
+ // exactly like the earlier behavior.
1286
+ const runRuntimeWork = (name, work) => input.runtimeSpan
1287
+ ? input.runtimeSpan(name, work)
1288
+ : runWithRuntimeParent(input.getRuntimeParentContext?.(), work);
1289
+ const shellCommand = preferredSandboxShell(target);
1290
+ const timeoutMs = typeof input.timeoutSec === "number" && Number.isFinite(input.timeoutSec) && input.timeoutSec > 0
1291
+ ? Math.trunc(input.timeoutSec * 1000)
1292
+ : target.timeoutMs ?? undefined;
1293
+ const bridgeRuntimeDir = path.posix.join(input.runtimeRootDir?.trim() || path.posix.join(target.remoteCwd, ".paperclip-runtime", input.adapterKey), "process-sessions");
1294
+ const sessionId = randomUUID();
1295
+ const sessionDir = path.posix.join(bridgeRuntimeDir, sessionId);
1296
+ const stdinDir = path.posix.join(sessionDir, "stdin");
1297
+ const eventsDir = path.posix.join(sessionDir, "events");
1298
+ // The streamed wrapper writes its frames to stdout and rides a separate remote
1299
+ // path, so a warm sandbox can hold both wrapper scripts without the content
1300
+ // hash-skip gate thrashing when a run switches output mode.
1301
+ const streamOutput = input.streamOutputViaSession === true;
1302
+ const remoteScriptPath = path.posix.join(bridgeRuntimeDir, streamOutput ? PROCESS_SESSION_REMOTE_STREAM_SCRIPT : PROCESS_SESSION_REMOTE_SCRIPT);
1303
+ const client = createCommandManagedSandboxCallbackBridgeQueueClient({
1304
+ runner,
1305
+ remoteCwd: target.remoteCwd,
1306
+ timeoutMs,
1307
+ shellCommand,
1308
+ });
1309
+ // The launch exec below re-creates stdinDir and eventsDir with one `mkdir -p`,
1310
+ // and the remote script also creates them on start. No reader touches the two
1311
+ // directories before the launch exec runs, so upfront makeDir execs are redundant.
1312
+ await syncProcessSessionRemoteScript({
1313
+ runner,
1314
+ remoteCwd: target.remoteCwd,
1315
+ remoteScriptDir: bridgeRuntimeDir,
1316
+ remoteScriptPath,
1317
+ timeoutMs,
1318
+ shellCommand,
1319
+ outputToStdout: streamOutput,
1320
+ });
1321
+ // Resolve the launch env AFTER the env-independent setup above, so a caller
1322
+ // can defer it until an upstream dependency (e.g. the paperclip bridge's env)
1323
+ // is ready without blocking the dir/script setup.
1324
+ const launchEnv = typeof input.env === "function" ? await input.env() : input.env;
1325
+ const commandPayload = Buffer.from(JSON.stringify({
1326
+ command: input.command,
1327
+ args: input.args,
1328
+ cwd: input.cwd || target.remoteCwd,
1329
+ // The ACP engine has already projected this launch env from explicit
1330
+ // adapter/runtime inputs and registered contributions. Compare against an
1331
+ // empty inherited baseline so an explicit identity value (notably PATH)
1332
+ // is not reclassified as ambient merely because it equals the host value.
1333
+ env: sanitizeRemoteExecutionEnv(launchEnv, {}),
1334
+ }), "utf8").toString("base64");
1335
+ // Base64 plus JSON escaping can push an otherwise valid child environment
1336
+ // past Linux's per-argument/per-variable exec limit. Keep small launches on
1337
+ // the existing path; upload larger envelopes in bounded chunks instead.
1338
+ const commandEnv = {};
1339
+ if (commandPayload.length <= 64 * 1024) {
1340
+ commandEnv.PAPERCLIP_PROCESS_SESSION_COMMAND_B64 = commandPayload;
1341
+ }
1342
+ else {
1343
+ const payloadPath = path.posix.join(sessionDir, "command.b64");
1344
+ const runPayloadSetup = async (script) => {
1345
+ const result = await runner.execute({
1346
+ command: shellCommand,
1347
+ args: shellCommandArgs(script),
1348
+ cwd: target.remoteCwd,
1349
+ timeoutMs,
1350
+ bypassSession: true,
1351
+ });
1352
+ if (result.timedOut || result.exitCode !== 0) {
1353
+ throw new Error("Failed to stage sandbox process session command payload.");
1354
+ }
1355
+ };
1356
+ try {
1357
+ // The envelope can contain credentials. Its upload intermediates stay
1358
+ // inside a private session directory, and the wrapper deletes it before
1359
+ // spawning the child. Teardown removes it if the wrapper cannot start.
1360
+ await runPayloadSetup(`umask 077 && mkdir -m 700 ${shellQuote(sessionDir)}`);
1361
+ await client.writeTextFile(payloadPath, commandPayload);
1362
+ await runPayloadSetup(`chmod 600 ${shellQuote(payloadPath)}`);
1363
+ }
1364
+ catch (error) {
1365
+ await client.remove(sessionDir).catch(() => undefined);
1366
+ throw error;
1367
+ }
1368
+ }
1369
+ // Legacy poll path: background the wrapper with `nohup` and read its output
1370
+ // event files with the host poll below. The streamed path launches the wrapper
1371
+ // as one foreground session command further down instead, so skip this.
1372
+ if (!streamOutput) {
1373
+ await onLog("stdout", `[paperclip] Starting ACP process session bridge in sandbox (${target.providerKey ?? "provider"}).\n`);
1374
+ const startResult = await runner.execute({
1375
+ command: shellCommand,
1376
+ args: shellCommandArgs([
1377
+ `mkdir -p ${shellQuote(stdinDir)} ${shellQuote(eventsDir)}`,
1378
+ // I3: no numeric process identifier anywhere. Background the
1379
+ // wrapper and let it go; do not capture `$!`.
1380
+ `PAPERCLIP_PROCESS_SESSION_DIR=${shellQuote(sessionDir)} ` +
1381
+ Object.entries(commandEnv).map(([key, value]) => `${key}=${shellQuote(value)} `).join("") +
1382
+ `nohup node ${shellQuote(remoteScriptPath)} >/dev/null 2>&1 < /dev/null &`,
1383
+ ].join("\n")),
1384
+ cwd: target.remoteCwd,
1385
+ env: {
1386
+ PAPERCLIP_SANDBOX_EXEC_CHANNEL: "bridge",
1387
+ },
1388
+ timeoutMs,
1389
+ // The wrapper launch is bridge plumbing. Keep it off the persistent
1390
+ // session so it never queues behind an in-run session command.
1391
+ bypassSession: true,
1392
+ }).catch(async (error) => {
1393
+ await client.remove(sessionDir).catch(() => undefined);
1394
+ throw error;
1395
+ });
1396
+ if (startResult.timedOut || (startResult.exitCode ?? 1) !== 0) {
1397
+ await client.remove(sessionDir).catch(() => undefined);
1398
+ throw new Error(`Failed to start sandbox ACP process session bridge: ${startResult.stderr || startResult.stdout}`);
1399
+ }
1400
+ }
1401
+ let socket = null;
1402
+ let stopping = false;
1403
+ // Resolves when `stop()` tears the bridge down. The streamed `sandbox.agentProcess`
1404
+ // span races its work against this, so the span ends at teardown at the latest
1405
+ // even when the remote process lingers, and never outlives the run root span.
1406
+ let signalStopped = () => { };
1407
+ const stopped = new Promise((resolve) => {
1408
+ signalStopped = resolve;
1409
+ });
1410
+ let stdinSeq = 0;
1411
+ // One promise chain per session that serializes the stdin file writes. Each
1412
+ // write is multi-exec on the command-managed client: prepare, append per 32
1413
+ // KiB, then an atomic rename. The chain makes the rename for file N finish
1414
+ // before the write for file N+1 starts, so the files land in send order.
1415
+ // Without it the writes overlap. A small later chunk can then rename ahead of
1416
+ // a big earlier chunk, so the wrapper reads the stdin bytes out of order and
1417
+ // corrupts a large prompt on the stdin path.
1418
+ let stdinWriteChain = Promise.resolve();
1419
+ let pollTimer = null;
1420
+ const pendingRemoteEvents = [];
1421
+ const token = createSandboxCallbackBridgeToken(18);
1422
+ const proxyDir = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-process-session-proxy-"));
1423
+ // `stop()` waits on this promise, bounded, for the wrapper's `shutdownAck`
1424
+ // event. `deliverRemoteEvent` resolves it below and never forwards the
1425
+ // event further: it is a host-internal control ack, not part of the ACP
1426
+ // output stream. An event under `sessionDir` is untrusted telemetry: an
1427
+ // `exit` or `error` event is never treated as proof of shutdown, because
1428
+ // any process running under the sandbox can write one. Only `shutdownAck`
1429
+ // counts, and `stop()` also gives itself a dedicated reader for it below,
1430
+ // so a late `shutdownAck` still lands even after the long-lived poll has
1431
+ // stopped re-arming.
1432
+ let signalShutdownAcknowledged = () => { };
1433
+ const shutdownAcknowledged = new Promise((resolve) => {
1434
+ signalShutdownAcknowledged = resolve;
1435
+ });
1436
+ const writeRemoteEventToSocket = (event) => {
1437
+ if (!socket)
1438
+ return false;
1439
+ socket.write(jsonLine(event));
1440
+ if (event.type === "exit") {
1441
+ stopping = true;
1442
+ socket.end();
1443
+ }
1444
+ else if (event.type === "error") {
1445
+ stopping = true;
1446
+ socket.destroy();
1447
+ }
1448
+ return true;
1449
+ };
1450
+ const deliverRemoteEvent = (event) => {
1451
+ if (event.type === "shutdownAck") {
1452
+ signalShutdownAcknowledged();
1453
+ return;
1454
+ }
1455
+ if (socket) {
1456
+ writeRemoteEventToSocket(event);
1457
+ return;
1458
+ }
1459
+ pendingRemoteEvents.push(event);
1460
+ if (event.type === "exit" || event.type === "error") {
1461
+ stopping = true;
1462
+ }
1463
+ };
1464
+ const flushPendingRemoteEvents = () => {
1465
+ if (!socket)
1466
+ return;
1467
+ while (pendingRemoteEvents.length > 0 && socket) {
1468
+ const event = pendingRemoteEvents.shift();
1469
+ if (event)
1470
+ writeRemoteEventToSocket(event);
1471
+ }
1472
+ };
1473
+ const liveSockets = new Set();
1474
+ // Register the per-connection socket handlers with no run parent context.
1475
+ // A stdin write from a socket handler is a run-time exec, not startup work.
1476
+ // The connection can open under `task.run` and receive stdin later, during an
1477
+ // `agent.turn`. So the handler must read the current-run parent at send time,
1478
+ // not at connect time. A connect-time read captures the parent that was live
1479
+ // when the socket opened, and every later exec span parents to that stale
1480
+ // parent. The `data` handler below reads the getter per message instead.
1481
+ const server = net.createServer((nextSocket) => {
1482
+ liveSockets.add(nextSocket);
1483
+ nextSocket.setEncoding("utf8");
1484
+ nextSocket.on("error", () => undefined);
1485
+ let connectionBuffer = "";
1486
+ let authenticated = false;
1487
+ // Connections own the session (and receive buffered process output) only
1488
+ // after presenting the bridge token; idle unauthenticated peers are dropped.
1489
+ const authTimer = setTimeout(() => {
1490
+ if (!authenticated)
1491
+ nextSocket.destroy();
1492
+ }, PROCESS_SESSION_AUTH_TIMEOUT_MS);
1493
+ authTimer.unref?.();
1494
+ nextSocket.on("close", () => {
1495
+ clearTimeout(authTimer);
1496
+ liveSockets.delete(nextSocket);
1497
+ });
1498
+ nextSocket.on("data", (chunk) => {
1499
+ connectionBuffer += chunk;
1500
+ const split = splitJsonLines(connectionBuffer);
1501
+ connectionBuffer = split.rest;
1502
+ for (const line of split.lines) {
1503
+ if (!line.trim())
1504
+ continue;
1505
+ let message;
1506
+ try {
1507
+ message = JSON.parse(line);
1508
+ }
1509
+ catch {
1510
+ nextSocket.destroy();
1511
+ return;
1512
+ }
1513
+ if (message.token !== token) {
1514
+ nextSocket.destroy();
1515
+ return;
1516
+ }
1517
+ if (!authenticated) {
1518
+ if (socket) {
1519
+ nextSocket.destroy();
1520
+ return;
1521
+ }
1522
+ authenticated = true;
1523
+ clearTimeout(authTimer);
1524
+ socket = nextSocket;
1525
+ flushPendingRemoteEvents();
1526
+ }
1527
+ // Wrap one outbound ACP message to the agent in a
1528
+ // `sandbox.agentSession.sendInput` span, so its one `writeTextFile` exec
1529
+ // groups under one named span. The span runner reads the current-run
1530
+ // parent at send time: the live parent switches to `agent.turn` during
1531
+ // the turn and back to `task.run` after it. A message that is neither
1532
+ // `stdin` nor `stdinEnd` writes nothing, so it opens no span.
1533
+ const stdinPayload = message.type === "stdin" && typeof message.data === "string"
1534
+ ? { type: "stdin", data: message.data }
1535
+ : message.type === "stdinEnd"
1536
+ ? { type: "stdinEnd" }
1537
+ : null;
1538
+ if (stdinPayload) {
1539
+ stdinSeq += 1;
1540
+ const name = `${String(stdinSeq).padStart(12, "0")}.json`;
1541
+ const filePath = path.posix.join(stdinDir, name);
1542
+ // Chain this write after the previous one, so the atomic rename for
1543
+ // file N finishes before the write for file N+1 starts. Keep the
1544
+ // per-message `sandbox.agentSession.sendInput` span inside the chain.
1545
+ const write = stdinWriteChain.then(() => runRuntimeWork(AGENT_SESSION_SEND_INPUT_SPAN, () => client.writeTextFile(filePath, jsonLine(stdinPayload))));
1546
+ // The next message chains after this write on success or failure, so a
1547
+ // failed write never blocks the chain. This mirrors the wrapper
1548
+ // `writeChain` pattern for its event files.
1549
+ stdinWriteChain = write.then(() => undefined, () => undefined);
1550
+ // Keep the failure behavior: send one error line, then destroy the socket.
1551
+ write.catch((error) => {
1552
+ nextSocket.write(jsonLine({ type: "error", message: error instanceof Error ? error.message : String(error) }));
1553
+ nextSocket.destroy();
1554
+ });
1555
+ }
1556
+ }
1557
+ });
1558
+ });
1559
+ const poll = async () => {
1560
+ if (stopping)
1561
+ return;
1562
+ try {
1563
+ // Read every file this tick fetched before this loop decides whether to
1564
+ // keep polling. A `shutdownAck` can land in the same batch right after
1565
+ // an `exit` event; deliver it too, so this tick never drops an
1566
+ // already-fetched (and already-removed-from-disk) event.
1567
+ const events = await readRemoteJsonFiles({ client, dir: eventsDir });
1568
+ for (const event of events) {
1569
+ const parsed = JSON.parse(event.body);
1570
+ deliverRemoteEvent(parsed);
1571
+ }
1572
+ }
1573
+ catch (error) {
1574
+ const message = error instanceof Error ? error.message : String(error);
1575
+ await onLog("stderr", `[paperclip] ACP process session bridge poll failed: ${message}\n`);
1576
+ deliverRemoteEvent({ type: "error", message });
1577
+ return;
1578
+ }
1579
+ finally {
1580
+ if (!stopping) {
1581
+ schedulePoll();
1582
+ }
1583
+ }
1584
+ };
1585
+ // Schedule the long-lived poll timer. Wrap each 100 ms poll tick in a
1586
+ // `sandbox.agentSession.pollOutput` span, so the tick's `list` plus per-file
1587
+ // `read`/`remove` execs group under one named span. The poll loop reads remote
1588
+ // event files with run-time execs, not startup work, so the wrapper span and
1589
+ // its child execs parent to the live run span, not to the ended bridge step.
1590
+ // The span runner reads the run parent per tick, because the re-arm timer that
1591
+ // the poll body schedules opens a new tick span: the live parent switches to
1592
+ // `agent.turn` during the turn and back to `task.run` after it.
1593
+ const schedulePoll = () => {
1594
+ pollTimer = setTimeout(() => void runRuntimeWork(AGENT_SESSION_POLL_OUTPUT_SPAN, poll), 100);
1595
+ pollTimer.unref?.();
1596
+ };
1597
+ const port = await waitForLocalServerListen(server);
1598
+ const agentCommand = await writeProcessSessionProxyScript(proxyDir, port, token);
1599
+ if (streamOutput) {
1600
+ // Streamed output path. Run the wrapper as one long-lived session command;
1601
+ // its stdout carries newline-delimited JSON frames that reach the host
1602
+ // through the provider session log stream. Deliver each frame exactly once
1603
+ // by its monotonic `seq`, so a frame that arrives both live and in the final
1604
+ // result is not repeated. There is no host output-file poll here.
1605
+ let streamBuffer = "";
1606
+ let lastSeq = 0;
1607
+ let sawTerminal = false;
1608
+ const deliverFrame = (frame) => {
1609
+ if (typeof frame.seq === "number") {
1610
+ if (frame.seq <= lastSeq)
1611
+ return;
1612
+ lastSeq = frame.seq;
1613
+ }
1614
+ if (frame.type === "exit" || frame.type === "error")
1615
+ sawTerminal = true;
1616
+ deliverRemoteEvent(frame);
1617
+ };
1618
+ const parseFrameLine = (line) => {
1619
+ if (!line.trim())
1620
+ return;
1621
+ let frame;
1622
+ try {
1623
+ frame = JSON.parse(line);
1624
+ }
1625
+ catch {
1626
+ return;
1627
+ }
1628
+ deliverFrame(frame);
1629
+ };
1630
+ // Live delivery: buffer partial lines across stream chunks, deliver each
1631
+ // complete frame line as it arrives.
1632
+ const ingestStreamChunk = (text) => {
1633
+ streamBuffer += text;
1634
+ const split = splitJsonLines(streamBuffer);
1635
+ streamBuffer = split.rest;
1636
+ for (const line of split.lines)
1637
+ parseFrameLine(line);
1638
+ };
1639
+ // Terminal delivery (the defined fallback to the poll): the resolved result
1640
+ // carries the full wrapper stdout even when the live stream degraded to the
1641
+ // provider session-log poll. The text is complete and self-contained, so
1642
+ // re-parse it on its own; the `seq` guard drops every frame the live stream
1643
+ // already delivered. Drop any partial live line — its complete form is in the
1644
+ // full text.
1645
+ const ingestFinalText = (text) => {
1646
+ streamBuffer = "";
1647
+ for (const line of text.split(/\n/))
1648
+ parseFrameLine(line);
1649
+ };
1650
+ await onLog("stdout", `[paperclip] Starting streamed ACP process session bridge in sandbox (${target.providerKey ?? "provider"}).\n`);
1651
+ // Fire the long-lived command; do NOT await it here. `useSession` forces the
1652
+ // persistent session so the provider streams the wrapper stdout back through
1653
+ // `onLog`. On resolve, the terminal re-parse fills any frames the live stream
1654
+ // missed; on reject, deliver one error frame so the local proxy fails loud.
1655
+ //
1656
+ // Wrap the launch in a `sandbox.agentProcess` span. `runRuntimeWork` parents
1657
+ // it to the LIVE RUN root (`task.run` at launch time — no turn has started
1658
+ // yet), not to the ephemeral `bridge.process-session` bring-up step, and it
1659
+ // stays open for the whole process lifetime. The inner `sandbox.exec` nests
1660
+ // under it. Without the wrapper the raw exec's span inherits the ~2.28s
1661
+ // bring-up step as its parent and then dangles ~50s past it, overlapping
1662
+ // `agent.turn` — a child outliving its parent. As a run-scoped span it reads
1663
+ // instead as a resource that OVERLAPS the sibling `agent.turn`, which is the
1664
+ // correct shape (the persistent process hosts the turn; it is not a child of
1665
+ // it, and on multi-turn runs one process spans several turns). `runRuntimeWork`
1666
+ // is voided, not awaited, so bring-up never blocks on the long-lived command,
1667
+ // and it defaults to a no-op parent when no span runner is injected.
1668
+ //
1669
+ // The span is bounded to the bridge lifecycle: it ends when the command
1670
+ // settles OR when `stop()` runs, whichever comes first. `stop()` runs during
1671
+ // run teardown, before the caller ends the `task.run` root span, so the span
1672
+ // never outlives the run root even if the remote process lingers past
1673
+ // teardown (`execute` has no cancel, so a lingering process cannot be forced
1674
+ // to resolve). The command promise keeps running after the span ends so its
1675
+ // frame handlers still deliver; they no-op once `stopping` is set.
1676
+ void runRuntimeWork("sandbox.agentProcess", async () => {
1677
+ const commandSettled = (async () => {
1678
+ try {
1679
+ const result = await runner.execute({
1680
+ command: shellCommand,
1681
+ args: shellCommandArgs(`node ${shellQuote(remoteScriptPath)}`),
1682
+ cwd: target.remoteCwd,
1683
+ env: {
1684
+ PAPERCLIP_PROCESS_SESSION_DIR: sessionDir,
1685
+ ...commandEnv,
1686
+ PAPERCLIP_SANDBOX_EXEC_CHANNEL: "bridge",
1687
+ },
1688
+ timeoutMs,
1689
+ useSession: true,
1690
+ onLog: async (stream, chunk) => {
1691
+ if (stream === "stdout")
1692
+ ingestStreamChunk(chunk);
1693
+ },
1694
+ });
1695
+ ingestFinalText(result.stdout);
1696
+ if (!sawTerminal && !stopping) {
1697
+ // A shell can fail before the wrapper emits any frames. Preserve
1698
+ // that launch diagnostic instead of reporting only its exit code.
1699
+ if (result.stderr)
1700
+ await onLog("stderr", result.stderr);
1701
+ deliverRemoteEvent({
1702
+ type: "exit",
1703
+ code: typeof result.exitCode === "number" ? result.exitCode : null,
1704
+ });
1705
+ }
1706
+ }
1707
+ catch (error) {
1708
+ if (!stopping) {
1709
+ deliverRemoteEvent({
1710
+ type: "error",
1711
+ message: error instanceof Error ? error.message : String(error),
1712
+ });
1713
+ }
1714
+ }
1715
+ })();
1716
+ await Promise.race([commandSettled, stopped]);
1717
+ });
1718
+ }
1719
+ else {
1720
+ schedulePoll();
1721
+ }
1722
+ // `stop()` cannot rely on the long-lived poll above to observe a late
1723
+ // `shutdownAck`: that poll stops re-arming as soon as it forwards a
1724
+ // terminal `exit`/`error` event, and `stop()` itself sets `stopping` on
1725
+ // its own first line. A normal completion's `shutdownAck` file, written a
1726
+ // moment after `exit`, can then land on disk after nobody reads the events
1727
+ // directory any more. Give `stop()` its own bounded reader that looks only
1728
+ // for `shutdownAck` and ignores every other event type, so the wait below
1729
+ // shortens on the wrapper's own proof of shutdown -- never on an `exit` or
1730
+ // `error` event, which any process running under `sessionDir` can forge.
1731
+ let stopReadingForShutdownAck = false;
1732
+ const readShutdownAckUntil = (deadlineEpochMs) => {
1733
+ if (stopReadingForShutdownAck)
1734
+ return;
1735
+ void (async () => {
1736
+ try {
1737
+ const events = await readRemoteJsonFiles({ client, dir: eventsDir });
1738
+ if (stopReadingForShutdownAck)
1739
+ return;
1740
+ for (const event of events) {
1741
+ try {
1742
+ const parsed = JSON.parse(event.body);
1743
+ if (parsed.type === "shutdownAck") {
1744
+ signalShutdownAcknowledged();
1745
+ return;
1746
+ }
1747
+ }
1748
+ catch {
1749
+ // Not readable JSON yet. It is not a `shutdownAck`; ignore it.
1750
+ }
1751
+ }
1752
+ }
1753
+ catch {
1754
+ // Best-effort: a read failure here is not proof of anything.
1755
+ }
1756
+ if (!stopReadingForShutdownAck && Date.now() < deadlineEpochMs) {
1757
+ const timer = setTimeout(() => readShutdownAckUntil(deadlineEpochMs), 100);
1758
+ timer.unref?.();
1759
+ }
1760
+ })();
1761
+ };
1762
+ return {
1763
+ agentCommand,
1764
+ stop: async () => {
1765
+ stopping = true;
1766
+ // End the `sandbox.agentProcess` span now, before the caller ends the run
1767
+ // root span, even if the remote command has not resolved yet.
1768
+ signalStopped();
1769
+ if (pollTimer)
1770
+ clearTimeout(pollTimer);
1771
+ for (const liveSocket of liveSockets)
1772
+ liveSocket.destroy();
1773
+ await new Promise((resolve) => server.close(() => resolve())).catch(() => undefined);
1774
+ // Wait for every accepted stdin write before `stdinEnd`. The socket handler
1775
+ // fires each chunk write un-awaited through `stdinWriteChain`, so an earlier
1776
+ // chunk can still be pending here. Chain the `stdinEnd` write onto the same
1777
+ // per-session chain, so its file rename never finishes before an earlier
1778
+ // chunk. `stdinSeq` is stable now, because the sockets are destroyed and the
1779
+ // server is closed, so no new message can increment it.
1780
+ const stdinEndPath = path.posix.join(stdinDir, `${String(stdinSeq + 1).padStart(12, "0")}.json`);
1781
+ const stdinEndWrite = stdinWriteChain.then(() => client.writeTextFile(stdinEndPath, jsonLine({ type: "stdinEnd" })));
1782
+ stdinWriteChain = stdinEndWrite.then(() => undefined, () => undefined);
1783
+ await stdinEndWrite.catch(() => undefined);
1784
+ // The `shutdown` control message tells the wrapper to terminate itself
1785
+ // and its own child (I3: no operating-system signal and no process
1786
+ // identifier cross this boundary — only a file-queue message does).
1787
+ // Chain it onto the same per-session write order as `stdinEnd`, so its
1788
+ // file never lands before the earlier one.
1789
+ const shutdownPath = path.posix.join(stdinDir, `${String(stdinSeq + 2).padStart(12, "0")}.json`);
1790
+ const shutdownWrite = stdinWriteChain.then(() => client.writeTextFile(shutdownPath, jsonLine({ type: "shutdown" })));
1791
+ stdinWriteChain = shutdownWrite.then(() => undefined, () => undefined);
1792
+ await shutdownWrite.catch(() => undefined);
1793
+ // Wait a bounded budget for a hint that the wrapper stopped: only the
1794
+ // `shutdownAck` event counts; an `exit` or `error` event is untrusted
1795
+ // telemetry from inside the sandbox and never shortens this wait or
1796
+ // suppresses the warning below. `shutdownAck` itself is ALSO an
1797
+ // untrusted hint, not proof: any process that shares the sandbox can
1798
+ // write the same event under this session's event directory. It can
1799
+ // only shorten this wait and suppress the warning below; it never
1800
+ // gates, shortens, or replaces the unconditional removal further down.
1801
+ // What actually makes the wrapper's own termination deterministic is
1802
+ // the wrapper-side session-identity latch, not this event.
1803
+ let acknowledgedInTime = false;
1804
+ readShutdownAckUntil(Date.now() + DEFAULT_PROCESS_SESSION_SHUTDOWN_WAIT_MS);
1805
+ await Promise.race([
1806
+ shutdownAcknowledged.then(() => {
1807
+ acknowledgedInTime = true;
1808
+ }),
1809
+ new Promise((resolve) => {
1810
+ const budgetTimer = setTimeout(resolve, DEFAULT_PROCESS_SESSION_SHUTDOWN_WAIT_MS);
1811
+ budgetTimer.unref?.();
1812
+ }),
1813
+ ]);
1814
+ stopReadingForShutdownAck = true;
1815
+ if (!acknowledgedInTime) {
1816
+ await onLog("stderr", `[paperclip] ACP process session wrapper did not acknowledge shutdown within ${DEFAULT_PROCESS_SESSION_SHUTDOWN_WAIT_MS}ms; removing the session directory anyway.\n`).catch(() => undefined);
1817
+ }
1818
+ // Unconditional: this removal runs whether or not the wrapper
1819
+ // acknowledged, and whether or not any event (real or forged) arrived
1820
+ // under `sessionDir`. `stop()` runs during run teardown and must stay
1821
+ // non-fatal, so every step above is best-effort and this step never
1822
+ // throws.
1823
+ await client.remove(sessionDir).catch(() => undefined);
1824
+ await fs.rm(proxyDir, { recursive: true, force: true }).catch(() => undefined);
1825
+ },
1826
+ };
1827
+ }
1828
+ function getProcessSessionProxySource(input) {
1829
+ return `#!/usr/bin/env node
1830
+ import net from "node:net";
1831
+
1832
+ const socket = net.createConnection({ host: "127.0.0.1", port: ${input.port} });
1833
+ const token = ${JSON.stringify(input.token)};
1834
+ let buffer = "";
1835
+ let exiting = false;
1836
+
1837
+ function send(message) {
1838
+ if (exiting) return;
1839
+ socket.write(JSON.stringify({ token, ...message }) + "\\n");
1840
+ }
1841
+
1842
+ function finish(code) {
1843
+ if (exiting) return;
1844
+ exiting = true;
1845
+ process.exitCode = code;
1846
+ // ACP keeps stdin open while waiting for a reply. Release that handle when
1847
+ // the remote process stops, but let pending stdout/stderr writes drain.
1848
+ process.stdin.destroy();
1849
+ socket.end();
1850
+ }
1851
+
1852
+ socket.on("connect", () => send({ type: "hello" }));
1853
+ process.stdin.on("data", (chunk) => send({ type: "stdin", data: Buffer.from(chunk).toString("base64") }));
1854
+ process.stdin.on("end", () => send({ type: "stdinEnd" }));
1855
+ process.stdin.resume();
1856
+
1857
+ socket.setEncoding("utf8");
1858
+ socket.on("data", (chunk) => {
1859
+ buffer += chunk;
1860
+ const parts = buffer.split(/\\n/);
1861
+ buffer = parts.pop() || "";
1862
+ for (const line of parts) {
1863
+ if (!line.trim()) continue;
1864
+ const message = JSON.parse(line);
1865
+ if (message.type === "data") {
1866
+ const out = Buffer.from(message.data || "", "base64");
1867
+ (message.stream === "stderr" ? process.stderr : process.stdout).write(out);
1868
+ } else if (message.type === "error") {
1869
+ process.stderr.write(String(message.message || "Process session bridge failed.") + "\\n");
1870
+ finish(1);
1871
+ } else if (message.type === "exit") {
1872
+ finish(typeof message.code === "number" ? message.code : 1);
1873
+ }
1874
+ }
1875
+ });
1876
+ socket.on("close", () => {
1877
+ if (!exiting) process.exit(1);
1878
+ });
1879
+ `;
1880
+ }
1881
+ export function getProcessSessionRemoteSource(input) {
1882
+ return input?.outputToStdout === true
1883
+ ? getProcessSessionRemoteStreamSource()
1884
+ : getProcessSessionRemoteEventFileSource();
1885
+ }
1886
+ // The shared stdin drain. Both wrappers read newline-delimited stdin messages
1887
+ // from the stdin file queue and write them to the child, then end the child
1888
+ // stdin on `stdinEnd`. A write to a closed child stdin only emits an `error`
1889
+ // event, so the wrapper installs a no-op handler at the call site.
1890
+ const PROCESS_SESSION_STDIN_POLL_TAIL = `child.stdin.on("error", () => {});
1891
+
1892
+ // A stdin file can appear before the host finishes the write. An empty read is
1893
+ // the non-atomic-write window; a partial read makes JSON.parse throw. The
1894
+ // poller must not delete a file before it validates the content. So read and
1895
+ // parse each file first, and delete it only after a successful parse. The files
1896
+ // sort in send order. If an earlier file is not readable yet, stop the cycle and
1897
+ // keep the order: a later file (for example stdinEnd) must not run ahead of it.
1898
+ // Retry the earlier file on a later cycle. After the retry limit, drop the file
1899
+ // and write an error event, so a lost message fails loud, and let later files
1900
+ // run.
1901
+ const stdinMaxParseRetries = (() => {
1902
+ const raw = Number.parseInt(process.env.PAPERCLIP_PROCESS_SESSION_STDIN_MAX_RETRIES || "", 10);
1903
+ return Number.isFinite(raw) && raw > 0 ? raw : 100;
1904
+ })();
1905
+ const stdinParseRetries = new Map();
1906
+ // Track the next expected sequence number. The host writes the stdin files in
1907
+ // send order and pads the number to 12 digits, starting at 1. The files sort in
1908
+ // send order. When the smallest present number is greater than expected, an
1909
+ // earlier file has not appeared yet: a missing file, not an unreadable one. Hold
1910
+ // the send order and wait for it, bounded by the same retry budget as the
1911
+ // unreadable-file path. This turns a reordering into a loud error, never silent
1912
+ // corruption.
1913
+ let stdinExpectedSeq = 1;
1914
+ let stdinGapRetries = 0;
1915
+
1916
+ // The bounded grace period between the SIGTERM and the SIGKILL a terminate()
1917
+ // call sends. A test can override it through the environment, so a stubborn
1918
+ // child does not force a slow test.
1919
+ const terminateGraceMs = (() => {
1920
+ const raw = Number.parseInt(process.env.PAPERCLIP_PROCESS_SESSION_TERMINATE_GRACE_MS || "", 10);
1921
+ return Number.isFinite(raw) && raw > 0 ? raw : 3000;
1922
+ })();
1923
+
1924
+ // I2: terminate() is the only function in this wrapper that calls
1925
+ // child.kill(). No child event handler and no sibling callback calls it.
1926
+ // terminate() is idempotent: a second call, or a first call after the child
1927
+ // already exited on its own, does nothing beyond what already ran.
1928
+ async function terminate() {
1929
+ if (terminated) return;
1930
+ terminated = true;
1931
+ shuttingDown = true;
1932
+ stdinClosed = true;
1933
+ child.stdin.end();
1934
+ // A \`false\` return means the child's process handle is already gone (the
1935
+ // child exited before this call ran). ChildProcess#kill() is handle-scoped:
1936
+ // once Node clears the handle at reap, the method call above sends no
1937
+ // signal and never falls back to a stored process identifier (I3). Treat
1938
+ // \`false\` as a no-op and do not retry through a numeric identifier.
1939
+ const sentTerm = child.kill("SIGTERM");
1940
+ if (sentTerm) {
1941
+ killTimer = setTimeout(() => {
1942
+ // Escalate on the same handle only (I2): the grace period expired, so
1943
+ // send SIGKILL through the same child handle, never a numeric
1944
+ // identifier and never a process-group signal.
1945
+ child.kill("SIGKILL");
1946
+ }, terminateGraceMs);
1947
+ killTimer.unref?.();
1948
+ }
1949
+ // This event is an untrusted latency hint, not proof. Any process that can
1950
+ // reach this session's event directory can write the same event type. It
1951
+ // can only shorten the host's shutdown wait and suppress the host's
1952
+ // timeout warning; it is never evidence that this wrapper's lifecycle
1953
+ // completed, and the host's cleanup never depends on it. The identity
1954
+ // latch below is what makes this wrapper's own termination deterministic.
1955
+ await writeEvent({ type: "shutdownAck" });
1956
+ }
1957
+
1958
+ // A sandbox peer can delete sessionDir and stdinDir, then recreate a
1959
+ // directory at the same pathname. A pathname does not prove identity: any
1960
+ // process that shares the sandbox can write it. So this wrapper captures the
1961
+ // OS-level identity of both paths once at startup, before the first poll
1962
+ // cycle, and checks it on every later cycle.
1963
+ //
1964
+ // The identity is the device number, the inode number, AND the inode's own
1965
+ // creation time. The device/inode pair alone is not enough: a filesystem can
1966
+ // reissue the exact inode number a just-removed directory held to the very
1967
+ // next directory created at the same path, with no attacker action needed
1968
+ // beyond the recreate the finding already describes. The creation time does
1969
+ // not have this gap: it is set fresh on every inode allocation, even when the
1970
+ // allocator reissues an old inode number, so a recreated directory always
1971
+ // carries a different creation time. The creation time alone is not enough
1972
+ // either, on a filesystem or kernel too old to report it, so this wrapper
1973
+ // keeps the device/inode pair as a second signal rather than relying on
1974
+ // either alone. Ordinary use of stdinDir (the host writing and this wrapper
1975
+ // deleting individual stdin files) changes that directory's OWN change time,
1976
+ // but never its creation time, so the creation time is safe to latch on
1977
+ // without producing a false positive on every stdin message.
1978
+ //
1979
+ // A filesystem or kernel that cannot report a real creation time does not
1980
+ // always report a value of zero. Node fails in one of two ways, and both are
1981
+ // grounded, not assumed: on Linux, when the statx() call finds no creation
1982
+ // time support, the kernel leaves the field unset and Node reports 0. On a
1983
+ // platform whose stat() call has no creation-time field at all, Node copies
1984
+ // the change time into the creation time instead. A 0 value fails open (any
1985
+ // recreated directory then matches on birthtimeMs alone), and a change-time
1986
+ // copy fails closed but far too often (it would move on every stdin file
1987
+ // this wrapper deletes). captureSessionIdentity() below proves the value is
1988
+ // usable before it trusts it, and fails closed on both known fallbacks.
1989
+ let sessionDirIdentity = null;
1990
+ let stdinDirIdentity = null;
1991
+ // The latch. Once set, it never clears. This replaces a counter that a
1992
+ // successful read reset to zero: an attacker who recreated the directory
1993
+ // before the counter reached its threshold kept the wrapper polling forever.
1994
+ // A latch has no threshold to race and no reset path.
1995
+ let identityLost = false;
1996
+
1997
+ async function statPathIdentity(candidatePath) {
1998
+ const stats = await fs.lstat(candidatePath);
1999
+ if (stats.isSymbolicLink()) {
2000
+ const error = new Error("Refusing a symbolic link on a process session control path.");
2001
+ error.code = "EPAPERCLIP_SYMLINK";
2002
+ throw error;
2003
+ }
2004
+ if (!stats.isDirectory()) {
2005
+ const error = new Error("A process session control path is not a directory.");
2006
+ error.code = "ENOTDIR";
2007
+ throw error;
2008
+ }
2009
+ return { dev: stats.dev, ino: stats.ino, birthtimeMs: stats.birthtimeMs };
2010
+ }
2011
+
2012
+ function sameIdentity(left, right) {
2013
+ return left.dev === right.dev && left.ino === right.ino && left.birthtimeMs === right.birthtimeMs;
2014
+ }
2015
+
2016
+ async function latchAndTerminate() {
2017
+ if (identityLost) return;
2018
+ identityLost = true;
2019
+ await terminate();
2020
+ }
2021
+
2022
+ let probeSeq = 0;
2023
+
2024
+ // A probe file name that pollStdin() can never read as a stdin message: it
2025
+ // does not end in ".json", so the ".json" filter in pollStdin() skips it if
2026
+ // a poll cycle ever lists the directory during the probe's short window.
2027
+ function nextProbeFileName() {
2028
+ probeSeq += 1;
2029
+ return ".paperclip-birthtime-probe-" + process.pid + "-" + probeSeq;
2030
+ }
2031
+
2032
+ // Proves a directory's reported birthtimeMs is a real creation time, not a
2033
+ // change-time copy. Creating and removing a file inside a directory changes
2034
+ // that directory's OWN change time but never its true creation time, so a
2035
+ // birthtimeMs that moves across the probe is a change-time copy. Returns
2036
+ // null when the value is proven real. Returns a stderr-ready reason string
2037
+ // on any failure (a detected copy, or a probe that cannot run at all, for
2038
+ // example a permission error or a pre-created probe path): either way the
2039
+ // caller must not trust the value.
2040
+ //
2041
+ // The open uses the "wx" flag: exclusive create, fail if the path exists.
2042
+ // A sandbox peer cannot pre-create the probe path as a symbolic link and
2043
+ // have this call follow it, because "wx" fails closed on an existing path
2044
+ // instead of following a link to it.
2045
+ //
2046
+ // Cleanup checks identity, not only ownership of the initial create. This
2047
+ // wrapper reads the probe file's identity, (dev, ino, ctimeMs), off the open
2048
+ // file descriptor itself (fstat), not off the path, so a peer that swaps the
2049
+ // path in the short gap after create cannot poison the identity this
2050
+ // wrapper trusts as its own. Right before removal, this wrapper reads the
2051
+ // path's identity again and removes it only when that identity still
2052
+ // matches. A same-sandbox peer that deletes the probe file and creates its
2053
+ // own entry at the same path in between leaves a different identity behind,
2054
+ // so this wrapper leaves that entry untouched instead of removing it. This
2055
+ // covers a peer's replacement file, a peer's replacement directory, and a
2056
+ // peer's replacement symbolic link alike, because all three change the
2057
+ // identity this wrapper reads back. The identity check includes ctimeMs,
2058
+ // not only (dev, ino): a filesystem can hand this call's freed inode number
2059
+ // straight back out to a peer's very next create at the same path, so
2060
+ // (dev, ino) alone can match a path this call no longer owns; ctimeMs resets
2061
+ // on every create, so a peer's replacement carries a different one even when
2062
+ // the inode number repeats. Node's filesystem API has no call that removes a
2063
+ // path only when its identity still matches an earlier read as one atomic
2064
+ // step, so a gap remains between this wrapper's final identity read and the
2065
+ // removal call itself. A peer that wins this gap can put any entry at the
2066
+ // probe path before the removal call runs. This can include a pre-existing
2067
+ // file the peer renames into place, not only a file the peer creates fresh.
2068
+ // The removal call then removes whatever entry sits at the probe path at
2069
+ // that moment. Two bounds still hold on that removal. The path always
2070
+ // stays under dirPath. If the entry is a symbolic link, the removal call
2071
+ // removes the link itself instead of following it to a different target.
2072
+ // A non-recursive removal call also fails if the entry is a directory.
2073
+ async function birthtimeSurvivesProbe(dirPath) {
2074
+ let before;
2075
+ try {
2076
+ before = (await fs.lstat(dirPath)).birthtimeMs;
2077
+ } catch {
2078
+ return "its reported creation time could not be read";
2079
+ }
2080
+ const probePath = path.posix.join(dirPath, nextProbeFileName());
2081
+ let handle;
2082
+ try {
2083
+ handle = await fs.open(probePath, "wx");
2084
+ } catch {
2085
+ return "its probe file could not be created exclusively (the path may already exist)";
2086
+ }
2087
+ // fstat on the open handle names the exact inode this call just created.
2088
+ // A path-based lstat here instead would be racy against a peer that swaps
2089
+ // the path in the gap between the create above and the stat: fstat has no
2090
+ // such gap, because a file descriptor keeps naming the inode it opened no
2091
+ // matter what a later swap does to the path.
2092
+ let ownedIdentity = null;
2093
+ try {
2094
+ const createdStats = await handle.stat();
2095
+ // ctimeMs guards against inode reuse; see the function comment above.
2096
+ ownedIdentity = { dev: createdStats.dev, ino: createdStats.ino, ctimeMs: createdStats.ctimeMs };
2097
+ } catch {
2098
+ ownedIdentity = null;
2099
+ } finally {
2100
+ await handle.close().catch(() => undefined);
2101
+ }
2102
+ if (!ownedIdentity) {
2103
+ // fstat on this call's own just-opened descriptor failed. This call then
2104
+ // has no verified identity for the probe file it created, so it must not
2105
+ // check or remove that file by path: a peer could already own the entry
2106
+ // at that path, and a path-based removal here could delete the peer's
2107
+ // entry instead of this call's own file. Fail closed right here instead
2108
+ // of falling through to the birthtime comparison below, so a failed
2109
+ // identity read can never let this probe report success.
2110
+ return "its own probe file's identity could not be read from the open file descriptor";
2111
+ }
2112
+ // The one gap the fs API cannot close: this lstat and the removal below
2113
+ // are two separate calls, not one atomic "remove if identity still
2114
+ // matches" step. A peer that wins this narrow gap can put any entry at
2115
+ // the probe path, including a pre-existing file it renames into place,
2116
+ // and the removal call below removes whatever entry is there when it
2117
+ // runs.
2118
+ const currentStats = await fs.lstat(probePath).catch(() => null);
2119
+ const stillOwned =
2120
+ currentStats !== null &&
2121
+ currentStats.dev === ownedIdentity.dev &&
2122
+ currentStats.ino === ownedIdentity.ino &&
2123
+ currentStats.ctimeMs === ownedIdentity.ctimeMs;
2124
+ if (stillOwned) {
2125
+ await fs.rm(probePath, { force: true }).catch(() => undefined);
2126
+ }
2127
+ let after;
2128
+ try {
2129
+ after = (await fs.lstat(dirPath)).birthtimeMs;
2130
+ } catch {
2131
+ return "its reported creation time could not be read";
2132
+ }
2133
+ return before === after ? null : "its reported creation time changed after a probe write";
2134
+ }
2135
+
2136
+ async function refuseUnusableCreationTime(label, dirPath, reason) {
2137
+ process.stderr.write(
2138
+ "Refusing to trust the process session control path " + label + " (" + dirPath + "): " + reason +
2139
+ ". This filesystem or kernel gives no usable creation time. Terminating.\\n",
2140
+ );
2141
+ await latchAndTerminate();
2142
+ }
2143
+
2144
+ // Runs once, before the first poll cycle, and before this wrapper captures
2145
+ // the identities it later checks on every cycle. A failed capture fails
2146
+ // closed: the wrapper has no verified identity to check on later cycles, so
2147
+ // it terminates now instead of polling a control path it never verified.
2148
+ //
2149
+ // This wrapper cannot assume stats.birthtimeMs is a real creation time. Node
2150
+ // can report a change-time copy as a creation time. That value fails closed
2151
+ // far too aggressively (it would move on every stdin file this wrapper
2152
+ // deletes), so this wrapper proves the value is not a copy with a probe before
2153
+ // it trusts it, run once here, before either directory's identity is captured.
2154
+ async function captureSessionIdentity() {
2155
+ try {
2156
+ const sessionProbeFailure = await birthtimeSurvivesProbe(sessionDir);
2157
+ if (sessionProbeFailure) {
2158
+ await refuseUnusableCreationTime("sessionDir", sessionDir, sessionProbeFailure);
2159
+ return;
2160
+ }
2161
+ const stdinProbeFailure = await birthtimeSurvivesProbe(stdinDir);
2162
+ if (stdinProbeFailure) {
2163
+ await refuseUnusableCreationTime("stdinDir", stdinDir, stdinProbeFailure);
2164
+ return;
2165
+ }
2166
+ const session = await statPathIdentity(sessionDir);
2167
+ const stdin = await statPathIdentity(stdinDir);
2168
+ sessionDirIdentity = session;
2169
+ stdinDirIdentity = stdin;
2170
+ } catch (error) {
2171
+ process.stderr.write(
2172
+ "Failed to capture the process session identity: " +
2173
+ (error instanceof Error ? error.message : String(error)) + ". Terminating.\\n",
2174
+ );
2175
+ await latchAndTerminate();
2176
+ }
2177
+ }
2178
+
2179
+ // Runs on every poll cycle, before the wrapper reads stdinDir. Terminate and
2180
+ // latch on any proof the control path is no longer the one this wrapper
2181
+ // captured at startup (a missing path, a path that is no longer a directory,
2182
+ // a symbolic link, or a directory whose identity changed), AND on every
2183
+ // other lstat failure. A permission error is not transient here: a sandbox
2184
+ // peer can deny search permission on the control directory without removing
2185
+ // it, and treating that as transient would leave the wrapper and its child
2186
+ // alive forever. The error code below only picks the stderr message, so an
2187
+ // operator can still tell a removed directory from a permission error; it
2188
+ // never decides whether to latch.
2189
+ //
2190
+ // Contrast readStdinDirNames() right below, whose catch block stays narrow
2191
+ // on purpose: readdir() opens a directory descriptor, so it can fail with a
2192
+ // genuinely transient error under descriptor exhaustion, and latching there
2193
+ // would kill live sessions under load. lstat() opens no descriptor, and this
2194
+ // function already runs before every call to readStdinDirNames(), so a
2195
+ // permission error latches here before readdir() is ever reached.
2196
+ async function verifySessionIdentity() {
2197
+ if (identityLost) return false;
2198
+ try {
2199
+ const session = await statPathIdentity(sessionDir);
2200
+ const stdin = await statPathIdentity(stdinDir);
2201
+ if (!sameIdentity(session, sessionDirIdentity) || !sameIdentity(stdin, stdinDirIdentity)) {
2202
+ await latchAndTerminate();
2203
+ return false;
2204
+ }
2205
+ return true;
2206
+ } catch (error) {
2207
+ const code = error && typeof error === "object" ? error.code : undefined;
2208
+ const reason =
2209
+ code === "ENOENT"
2210
+ ? "the control path no longer exists"
2211
+ : code === "ENOTDIR"
2212
+ ? "the control path is no longer a directory"
2213
+ : code === "EPAPERCLIP_SYMLINK"
2214
+ ? "the control path is now a symbolic link"
2215
+ : "lstat failed" + (code ? " with " + code : "");
2216
+ process.stderr.write("Latching on a lost process session identity: " + reason + ". Terminating.\\n");
2217
+ await latchAndTerminate();
2218
+ return false;
2219
+ }
2220
+ }
2221
+
2222
+ // This catch block stays narrow on purpose: see the comment above
2223
+ // verifySessionIdentity() for why a permission error here is treated as
2224
+ // transient while the same error latches there.
2225
+ async function readStdinDirNames() {
2226
+ if (!(await verifySessionIdentity())) return [];
2227
+ try {
2228
+ return await fs.readdir(stdinDir);
2229
+ } catch (error) {
2230
+ const code = error && typeof error === "object" ? error.code : undefined;
2231
+ if (code === "ENOENT" || code === "ENOTDIR") {
2232
+ await latchAndTerminate();
2233
+ }
2234
+ return [];
2235
+ }
2236
+ }
2237
+
2238
+ async function pollStdin() {
2239
+ while (!shuttingDown) {
2240
+ const entries = (await readStdinDirNames()).filter((name) => name.endsWith(".json")).sort();
2241
+ for (const name of entries) {
2242
+ if (shuttingDown) break;
2243
+ const entrySeq = Number.parseInt(name, 10);
2244
+ // Hold the send order when an earlier file has not appeared. Do not consume
2245
+ // this later file: wait for the missing file on a later cycle, bounded by
2246
+ // the retry budget. After the budget, fail loud and advance past the gap,
2247
+ // so the present file can run.
2248
+ if (Number.isFinite(entrySeq) && entrySeq > stdinExpectedSeq) {
2249
+ stdinGapRetries += 1;
2250
+ if (stdinGapRetries < stdinMaxParseRetries) {
2251
+ break;
2252
+ }
2253
+ await writeEvent({
2254
+ type: "error",
2255
+ message:
2256
+ "Advanced past missing stdin files " + stdinExpectedSeq + " to " + (entrySeq - 1) +
2257
+ " after " + stdinMaxParseRetries + " retries.",
2258
+ });
2259
+ stdinGapRetries = 0;
2260
+ stdinExpectedSeq = entrySeq;
2261
+ }
2262
+ const file = path.posix.join(stdinDir, name);
2263
+ let message;
2264
+ try {
2265
+ // Hardening (I3): open with O_NOFOLLOW where the platform defines it,
2266
+ // so a control-path symbolic link swapped in after the directory
2267
+ // check fails the read instead of following it.
2268
+ const readFlag =
2269
+ typeof fsConstants.O_NOFOLLOW === "number" ? fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW : "r";
2270
+ const raw = await fs.readFile(file, { encoding: "utf8", flag: readFlag });
2271
+ // An empty read means the content is not on disk yet. Treat it the same
2272
+ // as a parse failure: keep the file and retry on a later cycle.
2273
+ if (!raw) throw new Error("stdin file is empty");
2274
+ message = JSON.parse(raw);
2275
+ } catch (error) {
2276
+ const retries = (stdinParseRetries.get(name) || 0) + 1;
2277
+ if (retries >= stdinMaxParseRetries) {
2278
+ // The retry limit is reached. Drop the file and write an error event,
2279
+ // so the lost message fails loud. The file is resolved now, so let the
2280
+ // loop go on to the next entry.
2281
+ stdinParseRetries.delete(name);
2282
+ await fs.rm(file, { force: true }).catch(() => undefined);
2283
+ await writeEvent({
2284
+ type: "error",
2285
+ message:
2286
+ "Dropped unreadable stdin file after " + stdinMaxParseRetries + " retries: " + name + ": " +
2287
+ (error instanceof Error ? error.message : String(error)),
2288
+ });
2289
+ // The file is resolved (dropped). Advance the expected number and reset
2290
+ // the gap budget, then let the loop go on to the next entry.
2291
+ if (Number.isFinite(entrySeq)) stdinExpectedSeq = entrySeq + 1;
2292
+ stdinGapRetries = 0;
2293
+ continue;
2294
+ }
2295
+ // The file is not readable yet and is not past the retry limit. Keep it
2296
+ // and stop this cycle to hold the send order. A later file (for example
2297
+ // stdinEnd) must not run before this earlier file. A later cycle reads
2298
+ // from the start again.
2299
+ stdinParseRetries.set(name, retries);
2300
+ break;
2301
+ }
2302
+ // The parse succeeded, so the content is complete. Delete the file first,
2303
+ // then act on the message. A later cycle never re-reads a handled file.
2304
+ stdinParseRetries.delete(name);
2305
+ await fs.rm(file, { force: true }).catch(() => undefined);
2306
+ // The file is handled. Advance the expected number and reset the gap
2307
+ // budget, so the next expected file starts fresh.
2308
+ if (Number.isFinite(entrySeq)) stdinExpectedSeq = entrySeq + 1;
2309
+ stdinGapRetries = 0;
2310
+ if (message.type === "stdin" && typeof message.data === "string") {
2311
+ if (!stdinClosed) child.stdin.write(Buffer.from(message.data, "base64"));
2312
+ } else if (message.type === "stdinEnd") {
2313
+ stdinClosed = true;
2314
+ child.stdin.end();
2315
+ break;
2316
+ } else if (message.type === "shutdown") {
2317
+ await terminate();
2318
+ break;
2319
+ }
2320
+ }
2321
+ if (!shuttingDown) await new Promise((resolve) => setTimeout(resolve, 50));
2322
+ }
2323
+ }
2324
+
2325
+ await captureSessionIdentity();
2326
+
2327
+ void pollStdin().catch((error) => void writeEvent({ type: "error", message: error instanceof Error ? error.message : String(error) }));
2328
+ `;
2329
+ // Shared by both wrappers. Refuse a symlink and remove the private envelope
2330
+ // before creating the child; no launch payload file belongs to the agent.
2331
+ const PROCESS_SESSION_READ_COMMAND = `
2332
+ let config;
2333
+ try {
2334
+ let commandPayload = process.env.PAPERCLIP_PROCESS_SESSION_COMMAND_B64;
2335
+ if (!commandPayload) {
2336
+ const payloadPath = path.posix.join(sessionDir, "command.b64");
2337
+ const handle = await fs.open(payloadPath, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
2338
+ try {
2339
+ commandPayload = await handle.readFile("utf8");
2340
+ } finally {
2341
+ await handle.close();
2342
+ await fs.rm(payloadPath, { force: true });
2343
+ }
2344
+ }
2345
+ config = JSON.parse(Buffer.from(commandPayload, "base64").toString("utf8"));
2346
+ } catch {
2347
+ // No child exists yet. Emit a terminal event on both transports and attest
2348
+ // shutdown, instead of letting a detached polled wrapper fail silently.
2349
+ // Parse errors can quote credential bytes, so use a fixed diagnostic.
2350
+ await writeEvent({ type: "error", message: "Failed to read sandbox process session command payload." });
2351
+ await writeEvent({ type: "shutdownAck" });
2352
+ await new Promise((resolve) => process.stdout.write("", resolve));
2353
+ process.exit(1);
2354
+ }
2355
+ `;
2356
+ // Streamed variant: the wrapper writes each output frame as one newline-
2357
+ // delimited JSON line to its stdout. The host runs this wrapper as one
2358
+ // long-lived session command and reads the frames from the session log stream,
2359
+ // so there is no host output-file poll. Each frame carries a monotonic `seq`,
2360
+ // so the host delivers every frame exactly once whether it arrives live or in
2361
+ // the final result. The wrapper exits when the child closes, so the session
2362
+ // command settles and the session shell (the subshell wrap around it) survives.
2363
+ function getProcessSessionRemoteStreamSource() {
2364
+ return `import { spawn } from "node:child_process";
2365
+ import { promises as fs, constants as fsConstants } from "node:fs";
2366
+ import path from "node:path";
2367
+
2368
+ const sessionDir = process.env.PAPERCLIP_PROCESS_SESSION_DIR;
2369
+ if (!sessionDir) throw new Error("Missing process session bridge env.");
2370
+
2371
+ const stdinDir = path.posix.join(sessionDir, "stdin");
2372
+ let seq = 0;
2373
+ let stdinClosed = false;
2374
+ let shuttingDown = false;
2375
+ let terminated = false;
2376
+ let killTimer = null;
2377
+
2378
+ await fs.mkdir(stdinDir, { recursive: true });
2379
+
2380
+ // One newline-delimited JSON frame per event. Node keeps process.stdout writes
2381
+ // ordered, and the base64 payload holds no newline, so each frame is one line.
2382
+ function writeEvent(event) {
2383
+ seq += 1;
2384
+ process.stdout.write(JSON.stringify({ seq, ...event }) + "\\n");
2385
+ }
2386
+
2387
+ // Hardening (I3): refuse a symbolic link on a control path before this
2388
+ // wrapper reads or writes through it. A symbolic link here could let another
2389
+ // sandbox process redirect the wrapper's file I/O outside the session tree.
2390
+ async function isSymbolicLink(candidatePath) {
2391
+ try {
2392
+ const stats = await fs.lstat(candidatePath);
2393
+ return stats.isSymbolicLink();
2394
+ } catch {
2395
+ return false;
2396
+ }
2397
+ }
2398
+
2399
+ if ((await isSymbolicLink(sessionDir)) || (await isSymbolicLink(stdinDir))) {
2400
+ await writeEvent({ type: "error", message: "Refusing a symbolic link on a process session control path." });
2401
+ process.exitCode = 1;
2402
+ process.exit(1);
2403
+ }
2404
+
2405
+ ${PROCESS_SESSION_READ_COMMAND}
2406
+
2407
+ // Hardening (I3, not containment): the wrapper's own launch env carries the
2408
+ // session dir and the command payload. Scrub both keys before they reach the
2409
+ // spawned child, so the child never inherits a path to its own control files.
2410
+ const childEnv = { ...process.env, ...(config.env || {}) };
2411
+ delete childEnv.PAPERCLIP_PROCESS_SESSION_DIR;
2412
+ delete childEnv.PAPERCLIP_PROCESS_SESSION_COMMAND_B64;
2413
+
2414
+ // I1: exactly one child process per emitted wrapper. Do not add a second
2415
+ // tracked child handle.
2416
+ const child = spawn(config.command, Array.isArray(config.args) ? config.args : [], {
2417
+ cwd: config.cwd || process.cwd(),
2418
+ env: childEnv,
2419
+ stdio: ["pipe", "pipe", "pipe"],
2420
+ });
2421
+
2422
+ child.stdout.on("data", (chunk) => writeEvent({ type: "data", stream: "stdout", data: Buffer.from(chunk).toString("base64") }));
2423
+ child.stderr.on("data", (chunk) => writeEvent({ type: "data", stream: "stderr", data: Buffer.from(chunk).toString("base64") }));
2424
+ child.on("error", (error) => writeEvent({ type: "error", message: error.message }));
2425
+ // "close" (not "exit") so stdout/stderr fully drain before the exit frame.
2426
+ // Queue the exit frame first, then run terminate(), so the exit frame always
2427
+ // lands even when the child closes on its own, with no stdinEnd and no
2428
+ // shutdown message ever received. writeEvent() only queues an asynchronous
2429
+ // write. terminate()'s own synchronous work (ending the child's stdin and
2430
+ // sending SIGTERM) already runs in this same handler by the time the exit
2431
+ // frame becomes readable on disk. terminate() is idempotent and its
2432
+ // child.kill() call here is always a no-op (I2): the child's process handle
2433
+ // is already gone by the time "close" fires. An error frame carries no such
2434
+ // guarantee: child.on("error", ...) below does not call terminate(), and
2435
+ // neither does the poll loop's own error writes, so those fire while the
2436
+ // wrapper and its child are still fully alive.
2437
+ child.on("close", (code, signal) => {
2438
+ writeEvent({ type: "exit", code, signal });
2439
+ process.exitCode = typeof code === "number" ? code : 1;
2440
+ void terminate();
2441
+ });
2442
+
2443
+ ${PROCESS_SESSION_STDIN_POLL_TAIL}`;
2444
+ }
2445
+ function getProcessSessionRemoteEventFileSource() {
2446
+ return `import { spawn } from "node:child_process";
2447
+ import { promises as fs, constants as fsConstants } from "node:fs";
2448
+ import path from "node:path";
2449
+
2450
+ const sessionDir = process.env.PAPERCLIP_PROCESS_SESSION_DIR;
2451
+ if (!sessionDir) throw new Error("Missing process session bridge env.");
2452
+
2453
+ const stdinDir = path.posix.join(sessionDir, "stdin");
2454
+ const eventsDir = path.posix.join(sessionDir, "events");
2455
+ let seq = 0;
2456
+ let stdinClosed = false;
2457
+ let shuttingDown = false;
2458
+ let terminated = false;
2459
+ let killTimer = null;
2460
+
2461
+ await fs.mkdir(stdinDir, { recursive: true });
2462
+ await fs.mkdir(eventsDir, { recursive: true });
2463
+
2464
+ let writeChain = Promise.resolve();
2465
+
2466
+ function writeEvent(event) {
2467
+ seq += 1;
2468
+ const file = path.posix.join(eventsDir, String(seq).padStart(12, "0") + ".json");
2469
+ const write = writeChain.then(async () => {
2470
+ await fs.writeFile(file + ".tmp", JSON.stringify(event) + "\\n", "utf8");
2471
+ await fs.rename(file + ".tmp", file);
2472
+ });
2473
+ writeChain = write.catch(() => undefined);
2474
+ return write;
2475
+ }
2476
+
2477
+ // Hardening (I3): refuse a symbolic link on a control path before this
2478
+ // wrapper reads or writes through it. A symbolic link here could let another
2479
+ // sandbox process redirect the wrapper's file I/O outside the session tree.
2480
+ async function isSymbolicLink(candidatePath) {
2481
+ try {
2482
+ const stats = await fs.lstat(candidatePath);
2483
+ return stats.isSymbolicLink();
2484
+ } catch {
2485
+ return false;
2486
+ }
2487
+ }
2488
+
2489
+ if ((await isSymbolicLink(sessionDir)) || (await isSymbolicLink(stdinDir))) {
2490
+ await writeEvent({ type: "error", message: "Refusing a symbolic link on a process session control path." });
2491
+ process.exitCode = 1;
2492
+ process.exit(1);
2493
+ }
2494
+
2495
+ ${PROCESS_SESSION_READ_COMMAND}
2496
+
2497
+ // Hardening (I3, not containment): the wrapper's own launch env carries the
2498
+ // session dir and the command payload. Scrub both keys before they reach the
2499
+ // spawned child, so the child never inherits a path to its own control files.
2500
+ const childEnv = { ...process.env, ...(config.env || {}) };
2501
+ delete childEnv.PAPERCLIP_PROCESS_SESSION_DIR;
2502
+ delete childEnv.PAPERCLIP_PROCESS_SESSION_COMMAND_B64;
2503
+
2504
+ // I1: exactly one child process per emitted wrapper. Do not add a second
2505
+ // tracked child handle.
2506
+ const child = spawn(config.command, Array.isArray(config.args) ? config.args : [], {
2507
+ cwd: config.cwd || process.cwd(),
2508
+ env: childEnv,
2509
+ stdio: ["pipe", "pipe", "pipe"],
2510
+ });
2511
+
2512
+ child.stdout.on("data", (chunk) => void writeEvent({ type: "data", stream: "stdout", data: Buffer.from(chunk).toString("base64") }));
2513
+ child.stderr.on("data", (chunk) => void writeEvent({ type: "data", stream: "stderr", data: Buffer.from(chunk).toString("base64") }));
2514
+ child.on("error", (error) => void writeEvent({ type: "error", message: error.message }));
2515
+ // "close" (not "exit") so stdout/stderr fully drain before the exit event;
2516
+ // the write chain then guarantees the exit file lands after every data file.
2517
+ // Queue the exit event first, then run terminate(), so the poll loop ends
2518
+ // even when the child closes on its own, with no stdinEnd and no shutdown
2519
+ // message ever received. writeEvent() only queues an asynchronous write.
2520
+ // terminate()'s own synchronous work (ending the child's stdin and sending
2521
+ // SIGTERM) already runs in this same handler by the time the exit file
2522
+ // becomes readable on disk. terminate() is idempotent and its child.kill()
2523
+ // call here is always a no-op (I2): the child's process handle is already
2524
+ // gone by the time "close" fires. An error event carries no such guarantee:
2525
+ // child.on("error", ...) below does not call terminate(), and neither does
2526
+ // the poll loop's own error writes, so those fire while the wrapper and its
2527
+ // child are still fully alive.
2528
+ child.on("close", (code, signal) => {
2529
+ void writeEvent({ type: "exit", code, signal });
2530
+ process.exitCode = typeof code === "number" ? code : 1;
2531
+ void terminate();
2532
+ });
2533
+
2534
+ ${PROCESS_SESSION_STDIN_POLL_TAIL}`;
2535
+ }
2536
+ /** The default deadline for the duplex readiness handshake, in milliseconds. */
2537
+ const DEFAULT_DUPLEX_READINESS_TIMEOUT_MS = 10_000;
2538
+ /** The default bounded budget to close a partial duplex channel, in milliseconds. */
2539
+ const DEFAULT_DUPLEX_CLEANUP_BUDGET_MS = 2_000;
2540
+ /**
2541
+ * Reserve a loopback port the host assigns to the duplex gateway. The host binds
2542
+ * an ephemeral listener on `127.0.0.1`, reads the port the operating system
2543
+ * chose, then closes the listener. The number is very likely free when the
2544
+ * gateway binds it a moment later. The gateway binds exactly this port or exits
2545
+ * nonzero, so a taken port fails closed to the file bridge and never steers the
2546
+ * endpoint.
2547
+ */
2548
+ async function reserveHostAssignedLoopbackPort() {
2549
+ return new Promise((resolve, reject) => {
2550
+ const probe = net.createServer();
2551
+ probe.once("error", reject);
2552
+ probe.listen(0, "127.0.0.1", () => {
2553
+ const address = probe.address();
2554
+ if (!address || typeof address === "string") {
2555
+ probe.close(() => reject(new Error("Could not reserve a loopback port for the duplex gateway.")));
2556
+ return;
2557
+ }
2558
+ const reserved = address.port;
2559
+ probe.close(() => resolve(reserved));
2560
+ });
2561
+ });
2562
+ }
2563
+ /**
2564
+ * Build the argument vector that launches the duplex gateway in the sandbox. The
2565
+ * host passes the assigned port and the per-open nonce only through the launch
2566
+ * environment, so the argument vector sets them as environment assignments in
2567
+ * front of the node command. No addressing data comes from the channel.
2568
+ *
2569
+ * The script uses `exec env NAME=value ... command`. A POSIX shell accepts an
2570
+ * environment-assignment prefix only on a plain command, never on `exec`. The
2571
+ * form `exec NAME=value command` exits with status 127. The `env` utility carries
2572
+ * the assignments, and `exec` still replaces the shell with the gateway process,
2573
+ * so the gateway keeps the process slot and the assigned environment.
2574
+ */
2575
+ export function buildDuplexGatewayLaunchArgv(input) {
2576
+ const assignments = Object.entries(input.env)
2577
+ .map(([key, value]) => `${key}=${shellQuote(value)}`)
2578
+ .join(" ");
2579
+ const nodeCommand = input.nodeCommand?.trim() || "node";
2580
+ const script = `exec env ${assignments} ${shellQuote(nodeCommand)} ${shellQuote(input.remoteEntrypoint)}`;
2581
+ return [input.shellCommand, ...shellCommandArgs(script)];
2582
+ }
2583
+ /**
2584
+ * Map a readiness failure to the fixed fallback reason. Each readiness failure
2585
+ * maps to exactly one reason from the closed telemetry set, so the fallback
2586
+ * counter and the transport event carry only an approved value.
2587
+ */
2588
+ function duplexReadinessFallbackReason(reason) {
2589
+ switch (reason) {
2590
+ case "protocol_contamination":
2591
+ return "contaminated";
2592
+ case "nonce_mismatch":
2593
+ return "ready_nonce_mismatch";
2594
+ case "timeout":
2595
+ return "ready_timeout";
2596
+ case "channel_exit":
2597
+ return "ready_invalid";
2598
+ }
2599
+ }
2600
+ /**
2601
+ * The fixed marker the worker manager puts in a route-busy rejection. The manager
2602
+ * defines the constant; this module matches the fixed string, because the two
2603
+ * layers ship in separate packages and share no import. The marker names the
2604
+ * process-scoped route ceiling, so it carries no route, query, body, or token.
2605
+ */
2606
+ const DUPLEX_ROUTE_BUSY_ERROR_MARKER = "DUPLEX_CHANNEL_ROUTE_BUSY";
2607
+ /**
2608
+ * Report whether the caught open error is a route-busy rejection. The host maps it
2609
+ * to the `route_busy` fallback stage, so a full process-scoped route ceiling never
2610
+ * folds into a generic open failure.
2611
+ */
2612
+ function isDuplexRouteBusyError(error) {
2613
+ return error instanceof Error && error.message.includes(DUPLEX_ROUTE_BUSY_ERROR_MARKER);
2614
+ }
2615
+ /**
2616
+ * The cap on the pre-READY readiness buffer, in bytes. The gate reads untrusted
2617
+ * bytes before the READY line arrives, so it bounds the buffer. The cap is the
2618
+ * codec frame-size bound plus one line of margin, so a legitimate maximum-size
2619
+ * READY frame still fits. Past the cap with no newline, the stream cannot be a
2620
+ * valid READY frame, so the gate finishes with protocol contamination.
2621
+ */
2622
+ const DUPLEX_READINESS_BUFFER_CAP_BYTES = DEFAULT_MAX_DUPLEX_FRAME_BYTES + 4_096;
2623
+ // ---------------------------------------------------------------------------
2624
+ // http2_v1: the client connection preface scan and the run disposition latch.
2625
+ // ---------------------------------------------------------------------------
2626
+ /**
2627
+ * The HTTP/2 client connection preface: 24 octets, `PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n`
2628
+ * (RFC 9113, Section 3.4). A Node `http2.performServerHandshake` call needs a
2629
+ * `Duplex` whose readable side starts at this exact sequence; one extra
2630
+ * leading byte makes the preface invalid, and the server reports a
2631
+ * `PROTOCOL_ERROR`. The server does not skip a leading byte and does not
2632
+ * search for the sequence, so the host finds the offset itself before it
2633
+ * hands the channel to the server.
2634
+ */
2635
+ const HTTP2_CLIENT_CONNECTION_PREFACE = Buffer.from("505249202a20485454502f322e300d0a0d0a534d0d0a0d0a", "hex");
2636
+ /** The one shared empty buffer. The preface scan starts and resets its two
2637
+ * retained buffers to it. */
2638
+ const HTTP2_PREFACE_EMPTY_BUFFER = Buffer.alloc(0);
2639
+ // The count of bytes the preface scan's substring search examines, in bytes.
2640
+ // A search that always starts from the beginning of the retained buffer
2641
+ // re-examines the whole buffer on every fragment, so this count grows
2642
+ // quadratically in the number of fragments. `findPrefaceFrom` instead
2643
+ // resumes from where the prior search left off, so this count stays linear
2644
+ // in the bytes received. A test reads this count to prove the search work
2645
+ // does not regress to the quadratic shape. Production code never reads this
2646
+ // count.
2647
+ let http2PrefaceScanSearchUnits = 0;
2648
+ // The count of bytes the pre-preface scan buffer copies while it grows its
2649
+ // backing storage. A one-copy-per-fragment append copies the whole retained
2650
+ // buffer on every fragment, so this count grows quadratically in the number
2651
+ // of fragments. The doubling-growth approach copies only on a reallocation,
2652
+ // so this count stays linear in the bytes received. A test reads this count
2653
+ // to prove the growth work does not regress to the quadratic shape.
2654
+ // Production code never reads this count.
2655
+ let http2PrefaceScanBufferGrowthCopyUnits = 0;
2656
+ // The same count as `http2PrefaceScanBufferGrowthCopyUnits`, for the
2657
+ // post-preface replay buffer instead of the pre-preface scan buffer.
2658
+ let http2PrefaceReplayBufferGrowthCopyUnits = 0;
2659
+ /**
2660
+ * A byte buffer that grows its backing storage by doubling its capacity,
2661
+ * instead of copying the whole retained buffer on every appended fragment. A
2662
+ * sender that trickles input in many small fragments would otherwise force
2663
+ * one full copy of the whole retained buffer per fragment: with the buffer
2664
+ * growing toward its cap, that is quadratic work in the number of fragments.
2665
+ * Doubling the backing storage's capacity only when the current capacity
2666
+ * runs out reallocates and copies a logarithmic number of times, so the
2667
+ * total copy work stays linear in the bytes received. `countGrowthCopy`
2668
+ * receives the number of bytes each reallocation copies, so a test can add
2669
+ * these up and prove the growth work stays linear.
2670
+ */
2671
+ function createGrowableByteBuffer(countGrowthCopy) {
2672
+ let used = HTTP2_PREFACE_EMPTY_BUFFER;
2673
+ let storage = HTTP2_PREFACE_EMPTY_BUFFER;
2674
+ return {
2675
+ view: () => used,
2676
+ length: () => used.length,
2677
+ append: (chunk) => {
2678
+ const usedLength = used.length;
2679
+ const neededLength = usedLength + chunk.byteLength;
2680
+ if (neededLength > storage.length) {
2681
+ let nextCapacity = storage.length === 0 ? chunk.byteLength : storage.length * 2;
2682
+ while (nextCapacity < neededLength) {
2683
+ nextCapacity *= 2;
2684
+ }
2685
+ const grown = Buffer.allocUnsafe(nextCapacity);
2686
+ storage.copy(grown, 0, 0, usedLength);
2687
+ countGrowthCopy(usedLength);
2688
+ storage = grown;
2689
+ }
2690
+ storage.set(chunk, usedLength);
2691
+ used = storage.subarray(0, neededLength);
2692
+ },
2693
+ reset: () => {
2694
+ used = HTTP2_PREFACE_EMPTY_BUFFER;
2695
+ storage = HTTP2_PREFACE_EMPTY_BUFFER;
2696
+ },
2697
+ };
2698
+ }
2699
+ /**
2700
+ * Find the client connection preface in `buffer` at or after index `from`
2701
+ * and return its offset, or -1. This counts the real scan distance for a
2702
+ * test: from `from` up to the found preface's end, or to the end of the
2703
+ * buffer when it finds none. The count stays linear in the bytes received
2704
+ * when the caller advances `from` to just short of the buffer's end on every
2705
+ * miss, instead of always searching from the start of the buffer.
2706
+ */
2707
+ function findPrefaceFrom(buffer, from) {
2708
+ const offset = buffer.indexOf(HTTP2_CLIENT_CONNECTION_PREFACE, from);
2709
+ const scannedTo = offset === -1 ? buffer.length : offset + HTTP2_CLIENT_CONNECTION_PREFACE.length;
2710
+ http2PrefaceScanSearchUnits += Math.max(scannedTo - from, 0);
2711
+ return offset;
2712
+ }
2713
+ /**
2714
+ * Wrap the readiness gate's broker channel so its `onData` delivers no byte
2715
+ * until the client connection preface appears, then delivers every byte from
2716
+ * the preface onward.
2717
+ *
2718
+ * The wrapped channel opens its scan window only on the bytes the readiness
2719
+ * gate already retained after it accepted the READY line (constraint: the
2720
+ * scan window opens only after the gate accepts the nonce). Nothing before
2721
+ * that line ever reaches this scan, because the gate itself discards the
2722
+ * whole pre-READY buffer on acceptance. The scan buffers at most
2723
+ * {@link DUPLEX_READINESS_BUFFER_CAP_BYTES}; past that bound with no preface
2724
+ * found, it calls `onMissing` exactly one time and stops buffering, so the
2725
+ * caller can abort the open and fall back to `queue_v1`. The function holds
2726
+ * no prologue byte count: it always scans for the fixed 24-octet sequence,
2727
+ * never a length.
2728
+ *
2729
+ * The bytes that follow the found preface, before the HTTP/2 server binds a
2730
+ * downstream listener, land in `pendingAfterPreface`. This buffer holds
2731
+ * untrusted bytes on the same footing as the scan buffer, so it carries the
2732
+ * same {@link DUPLEX_READINESS_BUFFER_CAP_BYTES} cap. A chunk that would pass
2733
+ * the cap fails closed: the function drops the buffer and stops the channel.
2734
+ * The caller reads {@link replayOverflowed} after the preface settles and, on
2735
+ * `true`, treats the open the same as a missing preface.
2736
+ */
2737
+ function createHttp2PrefaceScanningChannel(channel, options) {
2738
+ // The pre-preface scan buffer. `scanBuf.append` grows its backing storage
2739
+ // by doubling, instead of copying the whole retained buffer on every
2740
+ // fragment — see {@link createGrowableByteBuffer}. `scanSearchFrom` is the
2741
+ // first index the next search must examine: `findPrefaceFrom` advances it
2742
+ // to just short of the buffer's end on every miss, so a fragmented preface
2743
+ // is found without a full rescan of the retained buffer on every fragment.
2744
+ const scanBuf = createGrowableByteBuffer((copiedBytes) => {
2745
+ http2PrefaceScanBufferGrowthCopyUnits += copiedBytes;
2746
+ });
2747
+ let scanSearchFrom = 0;
2748
+ let sawPreface = false;
2749
+ let failed = false;
2750
+ let downstream = null;
2751
+ // Bytes found after the preface before a downstream listener attaches. The
2752
+ // wrapped channel replays them on attach, the same pattern the readiness
2753
+ // gate itself uses for its own post-READY replay buffer. This also grows
2754
+ // by doubling, for the same reason as `scanBuf`: fragmented post-preface
2755
+ // input must not force a full copy of the retained buffer per fragment.
2756
+ const pendingAfterPreface = createGrowableByteBuffer((copiedBytes) => {
2757
+ http2PrefaceReplayBufferGrowthCopyUnits += copiedBytes;
2758
+ });
2759
+ let replayOverflow = false;
2760
+ // Drop the pending buffer and stop the channel. The caller reads
2761
+ // `replayOverflowed()` after the preface settles and falls back the same
2762
+ // way it does for a missing preface.
2763
+ function overflowAndStop() {
2764
+ replayOverflow = true;
2765
+ pendingAfterPreface.reset();
2766
+ channel.stop();
2767
+ }
2768
+ function deliver(chunk) {
2769
+ if (downstream) {
2770
+ downstream(chunk);
2771
+ return;
2772
+ }
2773
+ if (replayOverflow)
2774
+ return;
2775
+ if (pendingAfterPreface.length() + chunk.byteLength > options.capBytes) {
2776
+ overflowAndStop();
2777
+ return;
2778
+ }
2779
+ pendingAfterPreface.append(chunk);
2780
+ }
2781
+ channel.onData((chunk) => {
2782
+ if (failed || replayOverflow)
2783
+ return;
2784
+ if (sawPreface) {
2785
+ deliver(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
2786
+ return;
2787
+ }
2788
+ const rawChunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
2789
+ // Reject the chunk on its prospective length before it grows the scan
2790
+ // buffer, the same way `deliver` bounds `pendingAfterPreface`. A single
2791
+ // oversized chunk — or a chunk that tips an already-large buffer past
2792
+ // the cap — must fail closed here, before `scanBuf.append` performs the
2793
+ // allocation. Checking the cap only after the append still bounds the
2794
+ // retained buffer, but it lets one untrusted chunk force an allocation
2795
+ // as large as the chunk itself, unbounded by `capBytes`.
2796
+ if (scanBuf.length() + rawChunk.byteLength > options.capBytes) {
2797
+ failed = true;
2798
+ scanBuf.reset();
2799
+ scanSearchFrom = 0;
2800
+ options.onMissing();
2801
+ return;
2802
+ }
2803
+ scanBuf.append(rawChunk);
2804
+ const scanBuffer = scanBuf.view();
2805
+ const offset = findPrefaceFrom(scanBuffer, scanSearchFrom);
2806
+ if (offset === -1) {
2807
+ // No match yet. Resume the next search just short of the buffer's
2808
+ // end, keeping back an overlap of one octet less than the preface
2809
+ // length, so a preface split across this fragment and the next one is
2810
+ // still found. Each byte enters that overlap window a bounded number
2811
+ // of times, so the total search work stays linear in the bytes
2812
+ // received, not quadratic in the number of fragments.
2813
+ scanSearchFrom = Math.max(0, scanBuffer.length - (HTTP2_CLIENT_CONNECTION_PREFACE.length - 1));
2814
+ return;
2815
+ }
2816
+ sawPreface = true;
2817
+ options.onFound();
2818
+ const fromPreface = Buffer.from(scanBuffer.subarray(offset));
2819
+ scanBuf.reset();
2820
+ scanSearchFrom = 0;
2821
+ deliver(fromPreface);
2822
+ });
2823
+ return {
2824
+ channel: {
2825
+ write: (data) => channel.write(data),
2826
+ onData: (listener) => {
2827
+ downstream = listener;
2828
+ if (pendingAfterPreface.length() > 0) {
2829
+ // Copy the exact retained bytes instead of handing the listener
2830
+ // the growable buffer's backing view. That backing storage can
2831
+ // run ahead of the bytes in use (the doubling growth in
2832
+ // `pendingAfterPreface.append` over-provisions it), so a raw view
2833
+ // would keep the whole over-provisioned allocation alive for as
2834
+ // long as the listener holds its reference.
2835
+ const replay = Buffer.from(pendingAfterPreface.view());
2836
+ pendingAfterPreface.reset();
2837
+ listener(replay);
2838
+ }
2839
+ },
2840
+ onExit: (listener) => channel.onExit(listener),
2841
+ stop: () => channel.stop(),
2842
+ close: () => channel.close(),
2843
+ },
2844
+ replayOverflowed: () => replayOverflow,
2845
+ /**
2846
+ * Drop the scan buffer, for a caller-side terminal path this function
2847
+ * itself never reaches — the bound readiness timeout elapsing while the
2848
+ * scan is still searching, with no preface found and no cap refusal of
2849
+ * its own. A call after the preface already matched, or after the cap
2850
+ * already failed the scan closed, is a no-op: both paths already reset
2851
+ * the scan buffer themselves.
2852
+ */
2853
+ disposeScanBuffer: () => {
2854
+ if (sawPreface || failed)
2855
+ return;
2856
+ failed = true;
2857
+ scanBuf.reset();
2858
+ scanSearchFrom = 0;
2859
+ },
2860
+ };
2861
+ }
2862
+ /**
2863
+ * Wait for {@link createHttp2PrefaceScanningChannel} to settle: either the
2864
+ * preface appears, or the scan passes the bound with no match, or the bound
2865
+ * readiness timeout elapses first. Reapplying the readiness timeout here
2866
+ * keeps one configured value for both the READY-line wait and this
2867
+ * immediately-following preface wait; the task adds no new timeout setting.
2868
+ * Returns the scanning channel alongside the settled result, so the caller
2869
+ * binds the HTTP/2 server to it only on a `found` result. On a `found`
2870
+ * result, the caller must still read `replayOverflowed()`: the post-preface
2871
+ * buffer can overflow its cap after the preface settles as `found` and
2872
+ * before the caller binds a downstream listener.
2873
+ */
2874
+ function scanForHttp2ClientPreface(channel, options) {
2875
+ let resolveSettled;
2876
+ let settledOnce = false;
2877
+ const settled = new Promise((resolve) => {
2878
+ resolveSettled = resolve;
2879
+ });
2880
+ // Reassigned to the real function once `createHttp2PrefaceScanningChannel`
2881
+ // returns, below. `settle` can only actually run after that point (the
2882
+ // timer fires later, and `onMissing`/`onFound` fire from inside the
2883
+ // channel's own `onData`, which registers after this call), so the
2884
+ // placeholder never runs for real.
2885
+ let disposeScanBuffer = () => { };
2886
+ const settle = (result) => {
2887
+ if (settledOnce)
2888
+ return;
2889
+ settledOnce = true;
2890
+ clearTimeout(timer);
2891
+ // The bound readiness timeout can elapse while the scan still searches,
2892
+ // with no preface found and no cap refusal of its own. That path holds
2893
+ // no other cleanup, so drop the scan buffer here. A `found` result, or a
2894
+ // `missing` result the scan itself already failed closed, is a no-op
2895
+ // inside `disposeScanBuffer`.
2896
+ if (result === "missing")
2897
+ disposeScanBuffer();
2898
+ resolveSettled(result);
2899
+ };
2900
+ const timer = setTimeout(() => settle("missing"), options.timeoutMs);
2901
+ timer.unref?.();
2902
+ const scan = createHttp2PrefaceScanningChannel(channel, {
2903
+ capBytes: options.capBytes,
2904
+ onFound: () => settle("found"),
2905
+ onMissing: () => settle("missing"),
2906
+ });
2907
+ disposeScanBuffer = scan.disposeScanBuffer;
2908
+ return { scanned: scan.channel, settled, replayOverflowed: scan.replayOverflowed };
2909
+ }
2910
+ /**
2911
+ * Test-only surface for {@link scanForHttp2ClientPreface}. A test drives the
2912
+ * post-preface replay cap across every terminal path without the whole
2913
+ * bridge. Production code never reads this export.
2914
+ */
2915
+ export const __http2PrefaceScanTesting = {
2916
+ scanForHttp2ClientPreface: (channel, options) => scanForHttp2ClientPreface(channel, options),
2917
+ readScanSearchUnits: () => http2PrefaceScanSearchUnits,
2918
+ resetScanSearchUnits: () => {
2919
+ http2PrefaceScanSearchUnits = 0;
2920
+ },
2921
+ readScanBufferGrowthCopyUnits: () => http2PrefaceScanBufferGrowthCopyUnits,
2922
+ resetScanBufferGrowthCopyUnits: () => {
2923
+ http2PrefaceScanBufferGrowthCopyUnits = 0;
2924
+ },
2925
+ readReplayBufferGrowthCopyUnits: () => http2PrefaceReplayBufferGrowthCopyUnits,
2926
+ resetReplayBufferGrowthCopyUnits: () => {
2927
+ http2PrefaceReplayBufferGrowthCopyUnits = 0;
2928
+ },
2929
+ };
2930
+ function createHttp2RunDispositionLatch() {
2931
+ let lossOrdered = false;
2932
+ let lossReason = null;
2933
+ let completionOrdered = false;
2934
+ let lossListener = null;
2935
+ const markOrderlyCompletion = () => {
2936
+ if (completionOrdered || lossOrdered)
2937
+ return;
2938
+ completionOrdered = true;
2939
+ };
2940
+ return {
2941
+ get disposition() {
2942
+ return { failed: lossOrdered, lossReason };
2943
+ },
2944
+ recordLoss(reason) {
2945
+ if (lossOrdered || completionOrdered)
2946
+ return false;
2947
+ lossOrdered = true;
2948
+ lossReason = reason;
2949
+ lossListener?.(reason);
2950
+ return true;
2951
+ },
2952
+ markOrderlyCompletion,
2953
+ settleRunDisposition() {
2954
+ markOrderlyCompletion();
2955
+ return { failed: lossOrdered, lossReason };
2956
+ },
2957
+ onLoss(listener) {
2958
+ lossListener = listener;
2959
+ return () => {
2960
+ if (lossListener === listener)
2961
+ lossListener = null;
2962
+ };
2963
+ },
2964
+ };
2965
+ }
2966
+ /**
2967
+ * Create the run-log directory on the sandbox before the tail starts. The file
2968
+ * bridge worker creates this directory on the file path. The duplex path starts
2969
+ * no worker, so the host creates the directory here. The tail then reads a real
2970
+ * directory from its first tick.
2971
+ *
2972
+ * This step is best effort. The broker already serves the duplex transport when
2973
+ * the host reaches it, and the tail wrap command runs its own `mkdir -p` as a
2974
+ * backstop. So a create failure must not tear down a working duplex transport;
2975
+ * the host swallows it and still builds the tail. The log line names no raw
2976
+ * error, so no raw error rides a log line here.
2977
+ */
2978
+ async function ensureSandboxRunLogDirectory(input) {
2979
+ try {
2980
+ await input.runner.execute({
2981
+ command: input.shellCommand,
2982
+ args: shellCommandArgs(`mkdir -p ${shellQuote(input.logsDir)}`),
2983
+ cwd: input.remoteCwd,
2984
+ timeoutMs: input.timeoutMs ?? undefined,
2985
+ });
2986
+ }
2987
+ catch {
2988
+ // Best effort: the tail wrap command creates the directory before it writes.
2989
+ }
2990
+ }
2991
+ /**
2992
+ * The duplex readiness gate. The gate owns the single data listener and the
2993
+ * single exit listener of the channel while the host waits for a valid READY
2994
+ * frame. It resolves the handshake, then hands the channel to the broker.
2995
+ *
2996
+ * The gate authenticates readiness with the nonce and the strict READY schema,
2997
+ * not with the line position. A PTY channel echoes the launch wrapper line before
2998
+ * it sets raw mode, so the first line is often not the READY frame. The gate skips
2999
+ * each pre-READY line that does not decode as a READY frame, then accepts the first
3000
+ * line that decodes as a READY frame with the matching nonce. A line that decodes
3001
+ * as a READY frame with a wrong nonce fails the handshake with a nonce mismatch. An
3002
+ * early exit or a timeout also fails the handshake. The gate never dispatches a
3003
+ * request; the broker does that after readiness passes.
3004
+ *
3005
+ * The skipped bytes stay in the capped buffer, so the O(1) cap and the readiness
3006
+ * timeout still bound the wait. The gate enforces the cap on every pre-READY path
3007
+ * and before it decodes a candidate line, so an over-cap prefix never reaches READY
3008
+ * acceptance. The cap check has priority over READY acceptance on every path.
3009
+ */
3010
+ // The count of the pre-READY newline-scan work, in bytes. Each search adds the
3011
+ // number of bytes it can read. A test reads this count to prove the scan work
3012
+ // stays linear in the bytes received. Production code never reads this count.
3013
+ let duplexReadinessNewlineScanUnits = 0;
3014
+ // The count of bytes `appendReadinessBytes` copies while it grows the pre-READY
3015
+ // buffer's backing storage. A one-copy-per-fragment approach copies the whole
3016
+ // retained buffer on every fragment, so this count grows quadratically in the
3017
+ // number of fragments. The doubling-growth approach copies only on a
3018
+ // reallocation, so this count stays linear in the bytes received. A test reads
3019
+ // this count to prove the growth work does not regress to the quadratic shape.
3020
+ // Production code never reads this count.
3021
+ let duplexReadinessBufferGrowthCopyUnits = 0;
3022
+ /** The newline byte. The readiness buffer is a byte buffer, not a string. */
3023
+ const READINESS_NEWLINE_BYTE = 0x0a;
3024
+ /** The opening-brace byte. The bracketed-paste retry scans for it, not a string. */
3025
+ const READINESS_OPEN_BRACE_BYTE = 0x7b;
3026
+ /** The one shared empty buffer. The gate starts and resets `buffer` to it. */
3027
+ const READINESS_EMPTY_BUFFER = Buffer.alloc(0);
3028
+ /**
3029
+ * Find the first newline in `buffer` at or after index `from` and return its
3030
+ * index, or -1. `Buffer#indexOf` reads the bytes from `from` up to the newline
3031
+ * it finds, or to the end of the buffer when it finds none. This helper counts
3032
+ * that real scan distance for a test. The count stays linear in the bytes
3033
+ * received when the caller advances `from` past each newline it consumes.
3034
+ */
3035
+ function findNewlineFrom(buffer, from) {
3036
+ const newlineIndex = buffer.indexOf(READINESS_NEWLINE_BYTE, from);
3037
+ const scanned = newlineIndex === -1 ? buffer.length - from : newlineIndex - from + 1;
3038
+ duplexReadinessNewlineScanUnits += scanned;
3039
+ return newlineIndex;
3040
+ }
3041
+ /**
3042
+ * Test-only surface for the pre-READY readiness gate. A test reads the scan
3043
+ * count to prove the newline search work stays linear in the bytes received.
3044
+ * Production code does not use this object.
3045
+ */
3046
+ export const __duplexReadinessTesting = {
3047
+ readNewlineScanUnits: () => duplexReadinessNewlineScanUnits,
3048
+ resetNewlineScanUnits: () => {
3049
+ duplexReadinessNewlineScanUnits = 0;
3050
+ },
3051
+ readBufferGrowthCopyUnits: () => duplexReadinessBufferGrowthCopyUnits,
3052
+ resetBufferGrowthCopyUnits: () => {
3053
+ duplexReadinessBufferGrowthCopyUnits = 0;
3054
+ },
3055
+ // Build one readiness gate over a supplied channel, so a test can drive the
3056
+ // readiness-replay cap lifecycle across every terminal path without the whole
3057
+ // bridge. Production code never reads this factory.
3058
+ createReadinessGate: (channel, options) => createDuplexReadinessGate(channel, options),
3059
+ };
3060
+ function createDuplexReadinessGate(channel, options) {
3061
+ let settled = false;
3062
+ let readyOk = false;
3063
+ // The gate sets this when a post-READY pre-bind chunk tips the pending replay
3064
+ // buffer past the cap. On that overflow the gate drops the pending buffer and
3065
+ // stops the channel. The caller reads it through `replayOverflowed` and selects
3066
+ // the file bridge.
3067
+ let replayOverflow = false;
3068
+ // The raw bytes the host reads before the READY frame completes. `buffer` is
3069
+ // append-only and always a zero-copy view over the used prefix of `storage`,
3070
+ // so the O(1) cap check on `buffer.length` stays valid.
3071
+ let buffer = READINESS_EMPTY_BUFFER;
3072
+ // The backing storage for `buffer`. `appendReadinessBytes` grows this by
3073
+ // doubling its capacity, instead of copying the whole retained buffer on
3074
+ // every fragment. See `appendReadinessBytes` for why this bounds the total
3075
+ // copy work.
3076
+ let storage = READINESS_EMPTY_BUFFER;
3077
+ // The start index of the current line in `buffer`. A leading blank line
3078
+ // advances this cursor past its newline without a buffer copy.
3079
+ let lineStart = 0;
3080
+ // The next index to search for a newline. The gate scans from here, so each
3081
+ // byte is read at most one time for the newline search.
3082
+ let scanFrom = 0;
3083
+ /**
3084
+ * Append `chunk` to the pre-READY buffer without copying the bytes already
3085
+ * retained. A sender that trickles the handshake in many small fragments
3086
+ * (a slow socket, a byte-at-a-time PTY echo) would otherwise force one full
3087
+ * copy of the whole retained buffer per fragment: with `buffer` growing
3088
+ * toward the {@link DUPLEX_READINESS_BUFFER_CAP_BYTES} cap, that is
3089
+ * quadratic work in the number of fragments. This instead grows `storage`
3090
+ * by doubling its capacity only when the current capacity runs out, so the
3091
+ * backing store reallocates and copies a logarithmic number of times, and
3092
+ * each append copies only the incoming chunk. The used length still reads
3093
+ * in O(1) through `buffer.length`, so every cap check and slice below stays
3094
+ * unchanged.
3095
+ */
3096
+ function appendReadinessBytes(chunk) {
3097
+ const usedLength = buffer.length;
3098
+ const neededLength = usedLength + chunk.byteLength;
3099
+ if (neededLength > storage.length) {
3100
+ let nextCapacity = storage.length === 0 ? chunk.byteLength : storage.length * 2;
3101
+ while (nextCapacity < neededLength) {
3102
+ nextCapacity *= 2;
3103
+ }
3104
+ const grown = Buffer.allocUnsafe(nextCapacity);
3105
+ storage.copy(grown, 0, 0, usedLength);
3106
+ duplexReadinessBufferGrowthCopyUnits += usedLength;
3107
+ storage = grown;
3108
+ }
3109
+ storage.set(chunk, usedLength);
3110
+ buffer = storage.subarray(0, neededLength);
3111
+ }
3112
+ // The bytes that followed the READY frame, held until the broker binds.
3113
+ let pending = READINESS_EMPTY_BUFFER;
3114
+ // The exit that arrived after READY but before the broker bound, if any.
3115
+ let pendingExit = null;
3116
+ let dataSink = null;
3117
+ let exitSink = null;
3118
+ let resolveReady;
3119
+ const ready = new Promise((resolve) => {
3120
+ resolveReady = resolve;
3121
+ });
3122
+ const timer = setTimeout(() => finish({ ok: false, reason: "timeout" }), options.timeoutMs);
3123
+ timer.unref?.();
3124
+ function finish(result) {
3125
+ if (settled)
3126
+ return;
3127
+ settled = true;
3128
+ clearTimeout(timer);
3129
+ if (result.ok)
3130
+ readyOk = true;
3131
+ resolveReady(result);
3132
+ }
3133
+ channel.onData((chunk) => {
3134
+ if (dataSink) {
3135
+ dataSink(chunk);
3136
+ return;
3137
+ }
3138
+ if (readyOk) {
3139
+ // READY already passed; hold the bytes until the broker binds. Bound this
3140
+ // buffer directly against {@link DUPLEX_READINESS_BUFFER_CAP_BYTES}, the
3141
+ // same way the preface-scan gate's own post-preface replay buffer bounds
3142
+ // itself: a worker that keeps sending bytes after READY, faster than the
3143
+ // broker can bind, cannot grow this buffer past the cap. A chunk that
3144
+ // would pass the cap fails closed: the gate drops the pending buffer,
3145
+ // stops the channel, and sets the overflow flag. The caller reads the
3146
+ // flag and selects the file bridge, because `ready` already resolved
3147
+ // before this synchronous post-READY chunk arrived.
3148
+ if (pending.length + chunk.byteLength > DUPLEX_READINESS_BUFFER_CAP_BYTES) {
3149
+ replayOverflow = true;
3150
+ pending = READINESS_EMPTY_BUFFER;
3151
+ channel.stop();
3152
+ return;
3153
+ }
3154
+ // Copy a first chunk instead of aliasing the caller's `Uint8Array`, so a
3155
+ // channel that reuses its delivered buffer across calls cannot corrupt the
3156
+ // bytes this gate holds for the broker replay.
3157
+ pending = pending.length === 0 ? Buffer.from(chunk) : Buffer.concat([pending, chunk]);
3158
+ return;
3159
+ }
3160
+ if (settled) {
3161
+ // The handshake already failed. The channel is untrusted and can keep
3162
+ // sending bytes until the host closes it. Drop them, so a failed handshake
3163
+ // never grows the buffer after the gate settles.
3164
+ return;
3165
+ }
3166
+ // Append the new bytes and continue the newline search from `scanFrom`, the
3167
+ // first index not yet examined. Each byte is read at most one time for the
3168
+ // search, and `appendReadinessBytes` copies at most the incoming chunk, so
3169
+ // the total work stays linear in the bytes received, not in the number of
3170
+ // fragments they arrive in.
3171
+ appendReadinessBytes(chunk);
3172
+ for (;;) {
3173
+ const newlineIndex = findNewlineFrom(buffer, scanFrom);
3174
+ if (newlineIndex === -1) {
3175
+ // No complete line yet. The whole buffer up to the end is now scanned.
3176
+ scanFrom = buffer.length;
3177
+ // The gate reads untrusted bytes, so bound the pre-READY buffer. Past
3178
+ // the cap with no complete READY line, the stream cannot be a valid
3179
+ // READY frame, so finish with protocol contamination. The retained
3180
+ // skipped lines count against the cap; that is acceptable and fail-closed.
3181
+ //
3182
+ // `buffer` is a byte buffer, so `buffer.length` is the exact byte count,
3183
+ // read in O(1).
3184
+ if (buffer.length > DUPLEX_READINESS_BUFFER_CAP_BYTES) {
3185
+ finish({ ok: false, reason: "protocol_contamination" });
3186
+ }
3187
+ return;
3188
+ }
3189
+ if (newlineIndex === lineStart) {
3190
+ // Skip a blank line, the same as the frame decoder. Advance the line-start
3191
+ // cursor past the newline without a buffer copy, then search the next line
3192
+ // from the position after the newline. Enforce the cap after the advance,
3193
+ // so a blank-line flood past the cap fails closed before READY acceptance.
3194
+ lineStart = newlineIndex + 1;
3195
+ scanFrom = newlineIndex + 1;
3196
+ if (lineStart > DUPLEX_READINESS_BUFFER_CAP_BYTES) {
3197
+ finish({ ok: false, reason: "protocol_contamination" });
3198
+ return;
3199
+ }
3200
+ continue;
3201
+ }
3202
+ // A complete non-blank candidate line spans `[lineStart, newlineIndex)`.
3203
+ // Enforce the cap on the line's end offset before the decode, so an over-cap
3204
+ // prefix never reaches READY acceptance on a completed line.
3205
+ if (newlineIndex + 1 > DUPLEX_READINESS_BUFFER_CAP_BYTES) {
3206
+ finish({ ok: false, reason: "protocol_contamination" });
3207
+ return;
3208
+ }
3209
+ // Slice only the single candidate line for the decode.
3210
+ const line = buffer.slice(lineStart, newlineIndex);
3211
+ let decoded = decodeDuplexLine(line);
3212
+ if (!decoded.ok) {
3213
+ // The whole line did not decode. A terminal can put bytes in front of the
3214
+ // gateway's first frame on the same line, with no newline between them: a
3215
+ // shell with bracketed paste enabled writes its disable sequence and a
3216
+ // bare carriage return (`ESC [ ? 2 0 0 4 l CR`) immediately before the
3217
+ // child's first output. So retry the decode from the first `{` in the
3218
+ // line, which is where a frame can start.
3219
+ //
3220
+ // This does not weaken the handshake. The retry still runs the same
3221
+ // strict decode over the remainder of the line, and readiness still
3222
+ // authenticates on the nonce below, so a prefix cannot forge a frame or
3223
+ // smuggle a second one — it can only be discarded.
3224
+ const braceIndex = line.indexOf(READINESS_OPEN_BRACE_BYTE);
3225
+ if (braceIndex > 0) {
3226
+ decoded = decodeDuplexLine(line.slice(braceIndex));
3227
+ }
3228
+ }
3229
+ if (decoded.ok && decoded.frame.type === "ready") {
3230
+ // A line that decodes as a READY frame authenticates by the nonce. A wrong
3231
+ // nonce fails the handshake; the matching nonce passes it.
3232
+ if (decoded.frame.nonce !== options.nonce) {
3233
+ finish({ ok: false, reason: "nonce_mismatch" });
3234
+ return;
3235
+ }
3236
+ // The bytes that follow the READY line become the replay buffer for the
3237
+ // broker.
3238
+ //
3239
+ // Copy the suffix instead of slicing it off `buffer`. `buffer` is a view
3240
+ // over `storage`, and `storage`'s capacity can run ahead of the bytes in
3241
+ // use (the doubling growth in `appendReadinessBytes` over-provisions it).
3242
+ // A slice would keep that whole over-provisioned allocation alive for as
3243
+ // long as the broker replay holds its reference. The copy is exactly
3244
+ // `suffix.length` bytes, one time, not a per-fragment cost.
3245
+ const suffix = Buffer.from(buffer.subarray(newlineIndex + 1));
3246
+ // Drop the original pre-READY buffer and its backing storage now. The
3247
+ // gate keeps only the retained suffix as `pending`. This clear also
3248
+ // covers the broker handoff and the replay disposal. Both run later and
3249
+ // read no buffer bytes.
3250
+ buffer = READINESS_EMPTY_BUFFER;
3251
+ storage = READINESS_EMPTY_BUFFER;
3252
+ pending = suffix;
3253
+ finish({ ok: true });
3254
+ return;
3255
+ }
3256
+ // The line does not decode as a READY frame. A PTY echo line or any other
3257
+ // pre-READY noise reaches here. Skip it and keep scanning; the nonce and the
3258
+ // strict schema, not the line position, authenticate readiness. Enforce the
3259
+ // cap after the advance, so a noise-line flood past the cap fails closed.
3260
+ lineStart = newlineIndex + 1;
3261
+ scanFrom = newlineIndex + 1;
3262
+ if (lineStart > DUPLEX_READINESS_BUFFER_CAP_BYTES) {
3263
+ finish({ ok: false, reason: "protocol_contamination" });
3264
+ return;
3265
+ }
3266
+ }
3267
+ });
3268
+ channel.onExit((exit) => {
3269
+ if (exitSink) {
3270
+ exitSink(exit);
3271
+ return;
3272
+ }
3273
+ if (readyOk) {
3274
+ // The channel exited after READY but before the broker bound. Hold the
3275
+ // exit so the broker still learns of the loss.
3276
+ pendingExit = exit;
3277
+ return;
3278
+ }
3279
+ finish({ ok: false, reason: "channel_exit" });
3280
+ });
3281
+ const brokerChannel = {
3282
+ write: (data) => channel.write(data),
3283
+ onData: (listener) => {
3284
+ dataSink = listener;
3285
+ if (pending.length > 0) {
3286
+ const replay = pending;
3287
+ pending = READINESS_EMPTY_BUFFER;
3288
+ listener(replay);
3289
+ return;
3290
+ }
3291
+ },
3292
+ onExit: (listener) => {
3293
+ exitSink = listener;
3294
+ if (pendingExit) {
3295
+ const exit = pendingExit;
3296
+ pendingExit = null;
3297
+ listener(exit);
3298
+ }
3299
+ },
3300
+ stop: () => channel.stop(),
3301
+ close: () => channel.close(),
3302
+ };
3303
+ return {
3304
+ ready,
3305
+ brokerChannel,
3306
+ replayOverflowed: () => replayOverflow,
3307
+ disposePendingReplay: () => {
3308
+ pending = READINESS_EMPTY_BUFFER;
3309
+ },
3310
+ retainedReadinessBufferLength: () => buffer.length,
3311
+ };
3312
+ }
3313
+ /**
3314
+ * Close a partial duplex channel inside a bounded budget, then stop the child.
3315
+ * The host runs this on any readiness failure, so a failed duplex attempt leaves
3316
+ * no live provider session before the fallback to the file bridge.
3317
+ */
3318
+ async function closeDuplexChannelWithinBudget(channel, budgetMs) {
3319
+ try {
3320
+ let timer;
3321
+ const budget = new Promise((resolve) => {
3322
+ timer = setTimeout(resolve, budgetMs);
3323
+ timer.unref?.();
3324
+ });
3325
+ await Promise.race([channel.close().catch(() => undefined), budget]);
3326
+ if (timer !== undefined)
3327
+ clearTimeout(timer);
3328
+ }
3329
+ catch {
3330
+ // Best effort: the stop below still removes the child.
3331
+ }
3332
+ finally {
3333
+ try {
3334
+ channel.stop();
3335
+ }
3336
+ catch {
3337
+ // The channel is already gone; nothing more to do.
3338
+ }
3339
+ }
3340
+ }
3341
+ export async function startAdapterExecutionTargetPaperclipBridge(input) {
3342
+ if (!adapterExecutionTargetUsesPaperclipBridge(input.target)) {
3343
+ return null;
3344
+ }
3345
+ if (!input.target || input.target.kind !== "remote") {
3346
+ return null;
3347
+ }
3348
+ const target = input.target;
3349
+ const onLog = input.onLog ?? (async () => { });
3350
+ const hostApiToken = input.hostApiToken?.trim() ?? "";
3351
+ if (hostApiToken.length === 0) {
3352
+ throw new Error("Sandbox bridge mode requires a host-side Paperclip API token.");
3353
+ }
3354
+ // The forward budget for one relayed request. It stays at the broker's default
3355
+ // forward budget (30 s) when the caller sets no option, so current behavior
3356
+ // does not change.
3357
+ const forwardTimeoutMs = input.forwardTimeoutMs ?? DEFAULT_DUPLEX_BROKER_BUDGETS.forwardTimeoutMs;
3358
+ const runtimeRootDir = input.runtimeRootDir?.trim().length
3359
+ ? input.runtimeRootDir.trim()
3360
+ : path.posix.join(target.remoteCwd, ".paperclip-runtime", input.adapterKey);
3361
+ const bridgeRuntimeDir = path.posix.join(runtimeRootDir, "paperclip-bridge");
3362
+ const queueDir = path.posix.join(bridgeRuntimeDir, "queue");
3363
+ const assetRemoteDir = path.posix.join(bridgeRuntimeDir, "server");
3364
+ const bridgeToken = createSandboxCallbackBridgeToken();
3365
+ const configuredAttachmentBytes = Number(process.env.PAPERCLIP_ATTACHMENT_MAX_BYTES);
3366
+ // A larger upload limit needs multipart headroom. A smaller attachment limit
3367
+ // remains enforced by the API and must not shrink unrelated JSON responses.
3368
+ const defaultBodyBytes = Number.isSafeInteger(configuredAttachmentBytes) && configuredAttachmentBytes > 0
3369
+ ? Math.max(DEFAULT_SANDBOX_CALLBACK_BRIDGE_MAX_BODY_BYTES, configuredAttachmentBytes + 64 * 1024)
3370
+ : DEFAULT_SANDBOX_CALLBACK_BRIDGE_MAX_BODY_BYTES;
3371
+ const maxBodyBytes = typeof input.maxBodyBytes === "number" && Number.isFinite(input.maxBodyBytes) && input.maxBodyBytes > 0
3372
+ ? Math.trunc(input.maxBodyBytes)
3373
+ : defaultBodyBytes;
3374
+ // The bridge worker runs inside the same process that serves the Paperclip
3375
+ // API, so forwarded sandbox calls must target the LOCAL listen origin. The
3376
+ // PAPERCLIP_RUNTIME_API_URL / PAPERCLIP_API_URL exports now prefer a
3377
+ // configured public base URL, which is the origin browsers and external
3378
+ // agents use; routing this in-process loopback hop through the network edge
3379
+ // breaks deployments whose public origin sits behind a session-gated proxy
3380
+ // (every forwarded agent API call is rejected at the edge). Server boot
3381
+ // exports PAPERCLIP_LISTEN_HOST / PAPERCLIP_LISTEN_PORT before any run
3382
+ // executes, and resolveDefaultPaperclipApiUrl() maps wildcard listen hosts
3383
+ // to the loopback address of the same family (0.0.0.0 -> 127.0.0.1,
3384
+ // :: -> [::1]), so the fallback is always loopback-reachable.
3385
+ // input.hostApiUrl stays available as an explicit override seam.
3386
+ const hostApiUrl = input.hostApiUrl?.trim() || resolveDefaultPaperclipApiUrl();
3387
+ const shellCommand = adapterExecutionTargetShellCommand(target);
3388
+ const runner = adapterExecutionTargetCommandRunner(target);
3389
+ const bridgeTimeoutMs = typeof input.timeoutSec === "number" && Number.isFinite(input.timeoutSec) && input.timeoutSec > 0
3390
+ ? Math.trunc(input.timeoutSec * 1000)
3391
+ : adapterExecutionTargetTimeoutMs(target);
3392
+ await onLog("stdout", `[paperclip] Starting sandbox callback bridge for ${input.adapterKey} in ${bridgeRuntimeDir}.\n`);
3393
+ const bridgeAsset = await createSandboxCallbackBridgeAsset();
3394
+ // The provider-scoped telemetry facade for the fixed duplex observability
3395
+ // surface. It maps the raw provider key through the allowlist one time, so no
3396
+ // raw plugin key reaches a span, a counter, or the event. The default recorder
3397
+ // is a no-op, so the facade is inert until the host injects a real recorder.
3398
+ const duplexProviderKey = "providerKey" in target ? target.providerKey ?? undefined : undefined;
3399
+ const duplexObservability = createDuplexObservability({
3400
+ recorder: input.duplexObservabilityRecorder ?? undefined,
3401
+ providerKey: duplexProviderKey,
3402
+ // http2_v1 is the one active non-file transport now; every non-file
3403
+ // record this facade produces stamps the `http2` transport value.
3404
+ transport: "http2",
3405
+ });
3406
+ // PAPERCLIP_BRIDGE_DEBUG opts into verbose stdout logs of every bridge proxy
3407
+ // request/response. The query string is logged verbatim, so callers who pass
3408
+ // auth tokens or other sensitive values as query parameters should be aware
3409
+ // those values appear in the host process's stdout when this flag is enabled.
3410
+ // Only intended for active debugging in trusted environments.
3411
+ const bridgeDebugEnabled = isBridgeDebugEnabled(process.env);
3412
+ // One forward of a relayed sandbox request onto the existing Paperclip API
3413
+ // path. The forward applies the real host token and the signed run id, so the
3414
+ // token replacement and the run attribution stay in one place for both the
3415
+ // file bridge and the duplex broker. The sandbox request carries only the
3416
+ // bridge token; the real agent token never leaves the host.
3417
+ const forwardBridgeRequest = async (request, signal, options) => {
3418
+ const method = request.method.trim().toUpperCase() || "GET";
3419
+ // The per-request debug log prints the method, the path, and the query. The
3420
+ // duplex path suppresses it, so no route or query rides a log line there. The
3421
+ // file path keeps the existing behavior.
3422
+ const emitDebugLog = bridgeDebugEnabled && options?.suppressDebugLog !== true;
3423
+ if (emitDebugLog) {
3424
+ await onLog("stdout", `[paperclip] Bridge proxy ${method} ${request.path}${request.query ? `?${request.query}` : ""}\n`);
3425
+ }
3426
+ const headers = new Headers();
3427
+ for (const [key, value] of Object.entries(request.headers)) {
3428
+ if (value.trim().length === 0)
3429
+ continue;
3430
+ headers.set(key, value);
3431
+ }
3432
+ headers.set("authorization", `Bearer ${hostApiToken}`);
3433
+ headers.set("x-paperclip-run-id", input.runId);
3434
+ // Abort the forward when the caller aborts the request (its per-iteration
3435
+ // timeout or watchdog fired, or the broker's forward budget ended), or after
3436
+ // the forward budget here, whichever comes first.
3437
+ const timeoutSignal = AbortSignal.timeout(forwardTimeoutMs);
3438
+ const forwardSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
3439
+ // Build the request-body init. A GET or a HEAD carries no body. The file
3440
+ // bridge passes legacy JSON as a string and binary data as a `Buffer`;
3441
+ // HTTP/2 passes raw `Buffer` bodies. Undici accepts a `Buffer` request body directly (a
3442
+ // `Buffer` is an `ArrayBufferView`), so neither shape needs a conversion.
3443
+ // The cast below only bridges a `BodyInit` typing gap: the DOM library
3444
+ // type this project's ambient `RequestInit` resolves to excludes a
3445
+ // `Buffer`, though Undici accepts one at runtime.
3446
+ const forwardInit = {
3447
+ method,
3448
+ headers,
3449
+ signal: forwardSignal,
3450
+ };
3451
+ if (method !== "GET" && method !== "HEAD" && request.body !== undefined) {
3452
+ forwardInit.body = request.body;
3453
+ }
3454
+ const response = await fetch(buildBridgeForwardUrl(hostApiUrl, request), forwardInit);
3455
+ if (emitDebugLog) {
3456
+ await onLog("stdout", `[paperclip] Bridge proxy response ${response.status} for ${method} ${request.path}${request.query ? `?${request.query}` : ""}\n`);
3457
+ }
3458
+ // The host delivered response headers, so the response-body read starts after
3459
+ // the host processed the request. A later response-body read failure (a body
3460
+ // over the size limit, or a stream read error) needs a classification by
3461
+ // method safety. A safe method (GET, HEAD, OPTIONS, TRACE) never changes host
3462
+ // state, so a retry cannot double-apply a mutation and the failure stays
3463
+ // retryable. A mutating or otherwise unsafe method may have committed on the
3464
+ // host, so a retryable status is unsafe: it makes the caller repeat the
3465
+ // request with a new request id outside the broker deduplication set, and the
3466
+ // host applies the mutation twice. For an unsafe method the code returns a
3467
+ // non-retryable 504 and marks the outcome indeterminate, exactly like an
3468
+ // aborted in-flight forward. The in-sandbox server maps the indeterminate 504
3469
+ // to a non-retryable 409 for both the file bridge and the duplex broker.
3470
+ let responseBody;
3471
+ try {
3472
+ responseBody = await readBridgeForwardResponseBody(response, maxBodyBytes, options?.reservation);
3473
+ }
3474
+ catch (error) {
3475
+ // A denied reservation is retryable capacity pressure for a safe
3476
+ // method, not a body-read fault: rethrow it before the method-safety
3477
+ // classification below runs, so it reaches the HTTP/2 bridge's own
3478
+ // capacity-denial catch (which answers the retryable 503 and settles
3479
+ // the stream) instead of this function turning it into a 502. For an
3480
+ // unsafe (mutating) method, the host has already delivered response
3481
+ // headers by this point, so it may already have committed the
3482
+ // mutation. Rethrowing there too would let the retryable 503 reach a
3483
+ // caller that repeats the request, applying the mutation twice. An
3484
+ // unsafe method's capacity denial falls through to the same
3485
+ // non-retryable indeterminate 504 any other response-body read fault
3486
+ // gets below.
3487
+ if (error instanceof BridgeProcessCapacityError && isSafeBridgeMethod(method))
3488
+ throw error;
3489
+ if (isSafeBridgeMethod(method)) {
3490
+ // The method is safe, so a retry cannot double-apply a mutation. Return a
3491
+ // retryable 502 with no indeterminate marker, so the gateway passes it
3492
+ // through as a retryable status.
3493
+ return {
3494
+ status: 502,
3495
+ headers: { "content-type": "application/json" },
3496
+ body: Buffer.from(JSON.stringify({
3497
+ error: error instanceof Error ? error.message : String(error),
3498
+ }), "utf8"),
3499
+ };
3500
+ }
3501
+ return {
3502
+ status: 504,
3503
+ headers: {
3504
+ "content-type": "application/json",
3505
+ "x-paperclip-bridge-outcome": "indeterminate",
3506
+ },
3507
+ body: Buffer.from(JSON.stringify({
3508
+ error: error instanceof Error ? error.message : String(error),
3509
+ outcome: "indeterminate",
3510
+ retryable: false,
3511
+ }), "utf8"),
3512
+ };
3513
+ }
3514
+ const commentMarker = postedIssueCommentLogMarker(method, request.path, response.status, responseBody);
3515
+ if (commentMarker)
3516
+ await onLog("stdout", commentMarker);
3517
+ return {
3518
+ status: response.status,
3519
+ headers: buildBridgeResponseHeaders(response),
3520
+ body: responseBody,
3521
+ };
3522
+ };
3523
+ // Two strict gates guard the duplex transport. Select duplex only when the
3524
+ // experimental setting is exactly `true`, the resolved capability
3525
+ // `duplexCommandStream` is exactly `true`, and the runner exposes the duplex
3526
+ // channel. Any other value of either gate selects the file bridge below.
3527
+ // HTTP/2 is the preferred transport. `queue_v1` is the soft-deprecated fallback.
3528
+ const duplexRequested = input.enableSandboxDuplexBridge === true;
3529
+ const capabilityGranted = "effectiveCapabilities" in target &&
3530
+ target.effectiveCapabilities?.duplexCommandStream === true;
3531
+ const openDuplexChannel = runner.openDuplexChannel?.bind(runner);
3532
+ // Record the pre-attempt fallback for a file-bridge selection that opens no
3533
+ // channel. `gate_off` marks the kill switch off; `capability_absent` marks the
3534
+ // capability or the runner method absent. A later channel-open failure records
3535
+ // its own fallback through the channel-open attempt below.
3536
+ if (!duplexRequested) {
3537
+ duplexObservability.recordFallback("gate_off");
3538
+ }
3539
+ else if (!capabilityGranted || typeof openDuplexChannel !== "function") {
3540
+ duplexObservability.recordFallback("capability_absent");
3541
+ }
3542
+ if (duplexRequested && capabilityGranted && typeof openDuplexChannel === "function") {
3543
+ // Begin the channel-open attempt. The block reports exactly one terminal:
3544
+ // `ready` on success, or `fallback(reason)` on an open or a readiness failure.
3545
+ const duplexChannelOpen = duplexObservability.startChannelOpen();
3546
+ const readinessTimeoutMs = typeof input.duplexReadinessTimeoutMs === "number" &&
3547
+ Number.isFinite(input.duplexReadinessTimeoutMs) &&
3548
+ input.duplexReadinessTimeoutMs > 0
3549
+ ? Math.trunc(input.duplexReadinessTimeoutMs)
3550
+ : DEFAULT_DUPLEX_READINESS_TIMEOUT_MS;
3551
+ // The host assigns the loopback port before it opens the channel. It passes
3552
+ // the port and one random per-open nonce to the gateway only through the
3553
+ // launch environment. It builds the sandbox-facing origin from its own
3554
+ // stored port. No field of any channel frame contributes to the endpoint.
3555
+ //
3556
+ // Provider-boundary rationale (a security-review condition): the Daytona SDK
3557
+ // surface the plugin uses (`sandbox.process` sessions, PTY, and exec;
3558
+ // `sandbox.fs`) exposes no listener identity that the provider control plane
3559
+ // binds to a launched process. No channel-supplied address can be attested,
3560
+ // so this design uses zero channel-supplied addressing data. The only actor
3561
+ // that can pre-bind the host-assigned loopback port before the agent starts
3562
+ // is the provider itself, which already delivers the agent launch
3563
+ // environment. The gateway bind-or-exit contract fails closed. This is an
3564
+ // accepted provider-untrusted residual, not endpoint authentication: the
3565
+ // nonce is a liveness signal, because a compromised channel can read the
3566
+ // launch environment; the endpoint stays safe because it never derives from
3567
+ // the channel.
3568
+ const assignedPort = await reserveHostAssignedLoopbackPort();
3569
+ const nonce = randomBytes(16).toString("hex");
3570
+ const sandboxOrigin = `http://127.0.0.1:${assignedPort}`;
3571
+ let channel = null;
3572
+ // The open runs two stages under one try: the entrypoint sync, then the
3573
+ // channel open. The stage names the exact open-failure outcome in the catch.
3574
+ let openStage = "entrypoint_sync";
3575
+ try {
3576
+ const assetSync = await syncSandboxCallbackBridgeEntrypoint({
3577
+ runner,
3578
+ remoteCwd: target.remoteCwd,
3579
+ assetRemoteDir,
3580
+ bridgeAsset,
3581
+ timeoutMs: bridgeTimeoutMs,
3582
+ shellCommand,
3583
+ });
3584
+ const gatewayEnv = {
3585
+ PAPERCLIP_API_BRIDGE_MODE: SANDBOX_CALLBACK_BRIDGE_HTTP2_MODE,
3586
+ PAPERCLIP_BRIDGE_TOKEN: bridgeToken,
3587
+ PAPERCLIP_BRIDGE_HOST: "127.0.0.1",
3588
+ PAPERCLIP_BRIDGE_PORT: String(assignedPort),
3589
+ PAPERCLIP_BRIDGE_NONCE: nonce,
3590
+ PAPERCLIP_BRIDGE_MAX_BODY_BYTES: String(maxBodyBytes),
3591
+ };
3592
+ const command = buildDuplexGatewayLaunchArgv({
3593
+ shellCommand,
3594
+ remoteEntrypoint: assetSync.remoteEntrypoint,
3595
+ env: gatewayEnv,
3596
+ });
3597
+ openStage = "channel_open";
3598
+ channel = await openDuplexChannel({ command });
3599
+ }
3600
+ catch (error) {
3601
+ // The channel never opened, so no request could carry the bridge token.
3602
+ // The open call is the last statement of the try, so `channel` is still
3603
+ // null here; there is nothing to close. Fall through to the file bridge
3604
+ // below. Bind and keep the caught error, so the fallback names the exact
3605
+ // open-failure stage: a full process-scoped route ceiling is `route_busy`;
3606
+ // otherwise the stage is the entrypoint sync or the channel open. The log
3607
+ // line names only the fixed stage enum, so no raw provider error rides a log
3608
+ // line on the duplex path.
3609
+ const reason = isDuplexRouteBusyError(error)
3610
+ ? "route_busy"
3611
+ : openStage === "entrypoint_sync"
3612
+ ? "entrypoint_sync_failed"
3613
+ : "channel_open_failed";
3614
+ duplexChannelOpen.fallback(reason);
3615
+ await onLog("stderr", `[paperclip] Could not open the sandbox duplex channel (${reason}). Using the file bridge.\n`);
3616
+ channel = null;
3617
+ }
3618
+ if (channel) {
3619
+ const gate = createDuplexReadinessGate(channel, {
3620
+ nonce,
3621
+ timeoutMs: readinessTimeoutMs,
3622
+ });
3623
+ const readiness = await gate.ready;
3624
+ if (!readiness.ok) {
3625
+ // Fail closed. Close the partial channel inside a bounded budget, then
3626
+ // select the file bridge. The broker never started, so no request that
3627
+ // carries the bridge token reached the channel or any endpoint. The reason
3628
+ // is a fixed enum, so it rides the log line and the fallback telemetry
3629
+ // with no raw value.
3630
+ gate.disposePendingReplay();
3631
+ await closeDuplexChannelWithinBudget(channel, DEFAULT_DUPLEX_CLEANUP_BUDGET_MS);
3632
+ duplexChannelOpen.fallback(duplexReadinessFallbackReason(readiness.reason));
3633
+ await onLog("stderr", `[paperclip] Sandbox duplex readiness failed (${readiness.reason}). Using the file bridge.\n`);
3634
+ }
3635
+ else {
3636
+ // Readiness passed. The gate retained every byte that followed the
3637
+ // accepted READY line. Scan those retained bytes for the HTTP/2
3638
+ // client connection preface — the scan window opens only now, after
3639
+ // the gate accepted the nonce — and start the HTTP/2 session at that
3640
+ // offset, inclusive. A missing preface inside the bounded readiness
3641
+ // buffer aborts the open; the run falls back to `queue_v1` exactly
3642
+ // one time (accepted security fix 6).
3643
+ const openedChannel = channel;
3644
+ const prefaceScan = scanForHttp2ClientPreface(gate.brokerChannel, {
3645
+ capBytes: DUPLEX_READINESS_BUFFER_CAP_BYTES,
3646
+ timeoutMs: readinessTimeoutMs,
3647
+ });
3648
+ const prefaceResult = await prefaceScan.settled;
3649
+ if (prefaceResult === "missing") {
3650
+ // Fail closed, the same shape as a readiness failure: close the
3651
+ // partial channel inside the cleanup budget, then select the file
3652
+ // bridge. No HTTP/2 server ever bound to this channel, so no
3653
+ // request reached it or any endpoint.
3654
+ gate.disposePendingReplay();
3655
+ await closeDuplexChannelWithinBudget(openedChannel, DEFAULT_DUPLEX_CLEANUP_BUDGET_MS);
3656
+ duplexChannelOpen.fallback("preface_missing");
3657
+ await onLog("stderr", "[paperclip] Sandbox HTTP/2 client preface did not appear inside the bounded readiness buffer (preface_missing). Using the file bridge.\n");
3658
+ }
3659
+ else {
3660
+ // The run disposition latch for the http2_v1 path, in the same
3661
+ // shape the retired duplex_v1 broker exposed. A loss ordered before
3662
+ // an orderly completion reports a failure with the typed loss
3663
+ // reason; every other state reports a success.
3664
+ const dispositionLatch = createHttp2RunDispositionLatch();
3665
+ // Set before the first await inside the forward handler, so a loss
3666
+ // that lands mid-request still classifies as `post_dispatch`.
3667
+ let anyStreamDispatched = false;
3668
+ const recordHttp2Loss = (event) => {
3669
+ const reason = mapHttp2EventToDuplexLossReason(event);
3670
+ const disposedNow = dispositionLatch.recordLoss(reason);
3671
+ if (!disposedNow && (reason === "provider_exit" || reason === "transport_closed")) {
3672
+ // A clean channel end that orders after a host-observed orderly
3673
+ // completion is a normal teardown, not a loss: the run already
3674
+ // completed. Emit no loss telemetry and no log line for it, the
3675
+ // same policy the retired duplex_v1 broker applied.
3676
+ return;
3677
+ }
3678
+ const lossClass = anyStreamDispatched ? "post_dispatch" : "pre_dispatch";
3679
+ duplexObservability.recordLoss(lossClass, reason);
3680
+ void onLog("stderr", `[paperclip] Sandbox HTTP/2 channel lost (${reason}). The run fails.\n`);
3681
+ };
3682
+ // The forward handler applies the real host token and the run id
3683
+ // through the existing `forwardBridgeRequest` — the same function
3684
+ // the file bridge uses. The route allowlist, the header allowlist,
3685
+ // and the per-request debug-log suppression already ran inside
3686
+ // `createHttp2BridgeServer`'s own stream handler before this call.
3687
+ // It records the request span with the same latency-and-outcome
3688
+ // shape the retired duplex_v1 broker recorded: `ok` for any
3689
+ // delivered host response (any status), `error` only when the
3690
+ // forward call itself throws.
3691
+ const http2ForwardRequest = async (request) => {
3692
+ anyStreamDispatched = true;
3693
+ const dispatchStartMs = Date.now();
3694
+ try {
3695
+ const result = await forwardBridgeRequest({
3696
+ method: request.method,
3697
+ path: request.pathname,
3698
+ query: request.query,
3699
+ headers: request.headers,
3700
+ body: request.body,
3701
+ }, request.signal, { suppressDebugLog: true, reservation: request.reservation });
3702
+ duplexObservability.recordRequest({ latencyMs: Date.now() - dispatchStartMs, outcome: "ok" });
3703
+ return { status: result.status, headers: result.headers, body: result.body };
3704
+ }
3705
+ catch (error) {
3706
+ duplexObservability.recordRequest({ latencyMs: Date.now() - dispatchStartMs, outcome: "error" });
3707
+ throw error;
3708
+ }
3709
+ };
3710
+ const http2Server = createHttp2BridgeServer({
3711
+ bridgeToken,
3712
+ forwardRequest: http2ForwardRequest,
3713
+ routes: HTTP2_SANDBOX_CALLBACK_BRIDGE_ROUTE_ALLOWLIST,
3714
+ // The same resolved limit the launch environment hands the
3715
+ // sandbox-side gateway (`PAPERCLIP_BRIDGE_MAX_BODY_BYTES`,
3716
+ // below), so the host check and the gateway check enforce one
3717
+ // value instead of the host silently falling back to the
3718
+ // package default.
3719
+ maxBodyBytes,
3720
+ onGoaway: () => recordHttp2Loss("session_goaway"),
3721
+ onSessionError: () => recordHttp2Loss("session_error"),
3722
+ });
3723
+ // Combine the loss hook with the forward-to-Duplex handoff inside
3724
+ // one `onExit` registration: the channel primitive holds exactly
3725
+ // one listener slot, and `bindChannel` below registers the one that
3726
+ // ends the wrapped `Duplex`.
3727
+ //
3728
+ // This object's `stop()` is also the real sandbox-side effect of
3729
+ // the post-bind read backpressure bound `bindChannel` applies
3730
+ // (`wrapDuplexChannelAsNodeDuplex` in `http2-bridge-server.ts`):
3731
+ // this raw provider channel exposes no pause, so once the bounded
3732
+ // read queue there overflows, it calls `stop()` through this exact
3733
+ // chain, down to `prefaceScan.scanned.stop()` and on to the real
3734
+ // channel, instead of letting sandbox-controlled bytes grow host
3735
+ // memory with no bound.
3736
+ const channelForHttp2Server = {
3737
+ write: (data) => prefaceScan.scanned.write(data),
3738
+ onData: (listener) => prefaceScan.scanned.onData(listener),
3739
+ onExit: (listener) => {
3740
+ prefaceScan.scanned.onExit((exit) => {
3741
+ recordHttp2Loss("channel_exit");
3742
+ listener(exit);
3743
+ });
3744
+ },
3745
+ stop: () => prefaceScan.scanned.stop(),
3746
+ close: () => prefaceScan.scanned.close(),
3747
+ };
3748
+ const boundDuplex = http2Server.bindChannel(channelForHttp2Server);
3749
+ // Also catches the `Duplex` this wrapper destroys when the bounded
3750
+ // read backpressure queue overflows post-bind, so that loss still
3751
+ // reaches `recordHttp2Loss` the same way any other write fault does.
3752
+ boundDuplex.on("error", () => recordHttp2Loss("write_error"));
3753
+ duplexChannelOpen.ready();
3754
+ await onLog("stdout", "[paperclip] Sandbox HTTP/2 transport ready; serving the host-assigned origin.\n");
3755
+ // Stream run logs on the http2 path with the same gate and the same
3756
+ // log line as the file path. The http2 path starts no file-bridge
3757
+ // worker, so create the log directory before the tail starts.
3758
+ let duplexRunLogTail = null;
3759
+ if (target.transport === "sandbox" && target.streamRunLogs !== false) {
3760
+ const duplexLogsDir = sandboxCallbackBridgeDirectories(queueDir).logsDir;
3761
+ await ensureSandboxRunLogDirectory({
3762
+ runner,
3763
+ remoteCwd: target.remoteCwd,
3764
+ logsDir: duplexLogsDir,
3765
+ shellCommand,
3766
+ timeoutMs: bridgeTimeoutMs,
3767
+ });
3768
+ duplexRunLogTail = createSandboxRunLogTailFactory({
3769
+ runner,
3770
+ remoteCwd: target.remoteCwd,
3771
+ logsDir: duplexLogsDir,
3772
+ shellCommand,
3773
+ });
3774
+ await onLog("stdout", "[paperclip] Sandbox run log streaming enabled for this run.\n");
3775
+ }
3776
+ return {
3777
+ env: {
3778
+ PAPERCLIP_API_URL: sandboxOrigin,
3779
+ PAPERCLIP_API_KEY: bridgeToken,
3780
+ PAPERCLIP_API_BRIDGE_MODE: SANDBOX_CALLBACK_BRIDGE_HTTP2_MODE,
3781
+ },
3782
+ runLogTail: duplexRunLogTail,
3783
+ readRunDisposition: () => dispositionLatch.disposition,
3784
+ // Atomically read the latch and mark the orderly completion for the
3785
+ // ACP success-eligible terminal, so no await separates the read from
3786
+ // the mark and a teardown loss cannot slip in between.
3787
+ settleRunDisposition: () => dispositionLatch.settleRunDisposition(),
3788
+ markOrderlyCompletion: () => dispositionLatch.markOrderlyCompletion(),
3789
+ onLoss: (listener) => dispositionLatch.onLoss(listener),
3790
+ stop: async () => {
3791
+ // Close the HTTP/2 server's sessions, then the channel, before
3792
+ // lease release, so no live provider session remains when the
3793
+ // caller releases the lease.
3794
+ await http2Server.close();
3795
+ await closeDuplexChannelWithinBudget(openedChannel, DEFAULT_DUPLEX_CLEANUP_BUDGET_MS);
3796
+ await bridgeAsset.cleanup();
3797
+ },
3798
+ };
3799
+ }
3800
+ }
3801
+ }
3802
+ }
3803
+ let server = null;
3804
+ let worker = null;
3805
+ try {
3806
+ const client = createCommandManagedSandboxCallbackBridgeQueueClient({
3807
+ runner,
3808
+ remoteCwd: target.remoteCwd,
3809
+ timeoutMs: bridgeTimeoutMs,
3810
+ shellCommand,
3811
+ });
3812
+ // `startSandboxCallbackBridgeWorker` keeps its awaited queue-directory
3813
+ // setup on the active `bridge.paperclip` step, and runs each request under
3814
+ // the run parent context (see `runWithRuntimeParent` inside that function).
3815
+ // So the startup `mkdir` execs stay parented to the step, and every later
3816
+ // request `sandbox.exec` span parents to the live run span.
3817
+ worker = await startSandboxCallbackBridgeWorker({
3818
+ client,
3819
+ queueDir,
3820
+ maxBodyBytes,
3821
+ getRuntimeParentContext: input.getRuntimeParentContext,
3822
+ runtimeSpan: input.runtimeSpan,
3823
+ // The worker encodes binary bodies only at the queue boundary.
3824
+ handleRequest: (request, options) => forwardBridgeRequest(request, options?.signal, {
3825
+ reservation: options?.reservation,
3826
+ }),
3827
+ });
3828
+ server = await startSandboxCallbackBridgeServer({
3829
+ runner,
3830
+ remoteCwd: target.remoteCwd,
3831
+ assetRemoteDir,
3832
+ queueDir,
3833
+ bridgeToken,
3834
+ bridgeAsset,
3835
+ timeoutMs: bridgeTimeoutMs,
3836
+ maxBodyBytes,
3837
+ shellCommand,
3838
+ });
3839
+ }
3840
+ catch (error) {
3841
+ await Promise.allSettled([
3842
+ server?.stop(),
3843
+ worker?.stop(),
3844
+ bridgeAsset.cleanup(),
3845
+ ]);
3846
+ throw error;
3847
+ }
3848
+ let runLogTail = null;
3849
+ if (target.transport === "sandbox" && target.streamRunLogs !== false) {
3850
+ runLogTail = createSandboxRunLogTailFactory({
3851
+ runner,
3852
+ remoteCwd: target.remoteCwd,
3853
+ logsDir: sandboxCallbackBridgeDirectories(queueDir).logsDir,
3854
+ shellCommand,
3855
+ });
3856
+ await onLog("stdout", "[paperclip] Sandbox run log streaming enabled for this run.\n");
3857
+ }
3858
+ return {
3859
+ env: {
3860
+ PAPERCLIP_API_URL: server.baseUrl,
3861
+ PAPERCLIP_API_KEY: bridgeToken,
3862
+ PAPERCLIP_API_BRIDGE_MODE: "queue_v1",
3863
+ PAPERCLIP_BRIDGE_QUEUE_DIR: queueDir,
3864
+ },
3865
+ runLogTail,
3866
+ stop: async () => {
3867
+ await Promise.allSettled([
3868
+ server?.stop(),
3869
+ ]);
3870
+ await Promise.allSettled([
3871
+ worker?.stop(),
3872
+ bridgeAsset.cleanup(),
3873
+ ]);
3874
+ },
3875
+ };
3876
+ }
3877
+ //# sourceMappingURL=execution-target.js.map