@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.
- package/README.md +37 -0
- package/dist/acpx-engine/cli.d.ts +2 -0
- package/dist/acpx-engine/cli.d.ts.map +1 -0
- package/dist/acpx-engine/cli.js +128 -0
- package/dist/acpx-engine/cli.js.map +1 -0
- package/dist/acpx-engine/composed-run-characterization.test.d.ts +2 -0
- package/dist/acpx-engine/composed-run-characterization.test.d.ts.map +1 -0
- package/dist/acpx-engine/composed-run-characterization.test.js +739 -0
- package/dist/acpx-engine/composed-run-characterization.test.js.map +1 -0
- package/dist/acpx-engine/constants.d.ts +20 -0
- package/dist/acpx-engine/constants.d.ts.map +1 -0
- package/dist/acpx-engine/constants.js +37 -0
- package/dist/acpx-engine/constants.js.map +1 -0
- package/dist/acpx-engine/execute-identity.test.d.ts +2 -0
- package/dist/acpx-engine/execute-identity.test.d.ts.map +1 -0
- package/dist/acpx-engine/execute-identity.test.js +218 -0
- package/dist/acpx-engine/execute-identity.test.js.map +1 -0
- package/dist/acpx-engine/execute.d.ts +325 -0
- package/dist/acpx-engine/execute.d.ts.map +1 -0
- package/dist/acpx-engine/execute.js +4448 -0
- package/dist/acpx-engine/execute.js.map +1 -0
- package/dist/acpx-engine/execute.test.d.ts +2 -0
- package/dist/acpx-engine/execute.test.d.ts.map +1 -0
- package/dist/acpx-engine/execute.test.js +6694 -0
- package/dist/acpx-engine/execute.test.js.map +1 -0
- package/dist/acpx-engine/index.d.ts +6 -0
- package/dist/acpx-engine/index.d.ts.map +1 -0
- package/dist/acpx-engine/index.js +6 -0
- package/dist/acpx-engine/index.js.map +1 -0
- package/dist/acpx-engine/local-process-control.d.ts +5 -0
- package/dist/acpx-engine/local-process-control.d.ts.map +1 -0
- package/dist/acpx-engine/local-process-control.js +39 -0
- package/dist/acpx-engine/local-process-control.js.map +1 -0
- package/dist/acpx-engine/local-process-control.test.d.ts +2 -0
- package/dist/acpx-engine/local-process-control.test.d.ts.map +1 -0
- package/dist/acpx-engine/local-process-control.test.js +38 -0
- package/dist/acpx-engine/local-process-control.test.js.map +1 -0
- package/dist/acpx-engine/operator-stop.test.d.ts +2 -0
- package/dist/acpx-engine/operator-stop.test.d.ts.map +1 -0
- package/dist/acpx-engine/operator-stop.test.js +107 -0
- package/dist/acpx-engine/operator-stop.test.js.map +1 -0
- package/dist/acpx-engine/remote-spawn-smoke.test.d.ts +2 -0
- package/dist/acpx-engine/remote-spawn-smoke.test.d.ts.map +1 -0
- package/dist/acpx-engine/remote-spawn-smoke.test.js +151 -0
- package/dist/acpx-engine/remote-spawn-smoke.test.js.map +1 -0
- package/dist/acpx-engine/run-contracts.d.ts +413 -0
- package/dist/acpx-engine/run-contracts.d.ts.map +1 -0
- package/dist/acpx-engine/run-contracts.js +20 -0
- package/dist/acpx-engine/run-contracts.js.map +1 -0
- package/dist/acpx-engine/run-contracts.test.d.ts +2 -0
- package/dist/acpx-engine/run-contracts.test.d.ts.map +1 -0
- package/dist/acpx-engine/run-contracts.test.js +57 -0
- package/dist/acpx-engine/run-contracts.test.js.map +1 -0
- package/dist/acpx-engine/run-coordinator.d.ts +57 -0
- package/dist/acpx-engine/run-coordinator.d.ts.map +1 -0
- package/dist/acpx-engine/run-coordinator.js +63 -0
- package/dist/acpx-engine/run-coordinator.js.map +1 -0
- package/dist/acpx-engine/run-coordinator.test.d.ts +2 -0
- package/dist/acpx-engine/run-coordinator.test.d.ts.map +1 -0
- package/dist/acpx-engine/run-coordinator.test.js +160 -0
- package/dist/acpx-engine/run-coordinator.test.js.map +1 -0
- package/dist/acpx-engine/run-fault-matrix.test.d.ts +2 -0
- package/dist/acpx-engine/run-fault-matrix.test.d.ts.map +1 -0
- package/dist/acpx-engine/run-fault-matrix.test.js +829 -0
- package/dist/acpx-engine/run-fault-matrix.test.js.map +1 -0
- package/dist/acpx-engine/run-resource-ledger.d.ts +30 -0
- package/dist/acpx-engine/run-resource-ledger.d.ts.map +1 -0
- package/dist/acpx-engine/run-resource-ledger.js +148 -0
- package/dist/acpx-engine/run-resource-ledger.js.map +1 -0
- package/dist/acpx-engine/run-resource-ledger.test.d.ts +2 -0
- package/dist/acpx-engine/run-resource-ledger.test.d.ts.map +1 -0
- package/dist/acpx-engine/run-resource-ledger.test.js +154 -0
- package/dist/acpx-engine/run-resource-ledger.test.js.map +1 -0
- package/dist/acpx-engine/run-site-host.d.ts +73 -0
- package/dist/acpx-engine/run-site-host.d.ts.map +1 -0
- package/dist/acpx-engine/run-site-host.js +65 -0
- package/dist/acpx-engine/run-site-host.js.map +1 -0
- package/dist/acpx-engine/run-site-host.test.d.ts +2 -0
- package/dist/acpx-engine/run-site-host.test.d.ts.map +1 -0
- package/dist/acpx-engine/run-site-host.test.js +98 -0
- package/dist/acpx-engine/run-site-host.test.js.map +1 -0
- package/dist/acpx-engine/run-site-sandbox.d.ts +142 -0
- package/dist/acpx-engine/run-site-sandbox.d.ts.map +1 -0
- package/dist/acpx-engine/run-site-sandbox.js +278 -0
- package/dist/acpx-engine/run-site-sandbox.js.map +1 -0
- package/dist/acpx-engine/run-site-sandbox.test.d.ts +2 -0
- package/dist/acpx-engine/run-site-sandbox.test.d.ts.map +1 -0
- package/dist/acpx-engine/run-site-sandbox.test.js +275 -0
- package/dist/acpx-engine/run-site-sandbox.test.js.map +1 -0
- package/dist/acpx-engine/session-codec.d.ts +3 -0
- package/dist/acpx-engine/session-codec.d.ts.map +1 -0
- package/dist/acpx-engine/session-codec.js +49 -0
- package/dist/acpx-engine/session-codec.js.map +1 -0
- package/dist/acpx-engine/session-reuse-store.d.ts +62 -0
- package/dist/acpx-engine/session-reuse-store.d.ts.map +1 -0
- package/dist/acpx-engine/session-reuse-store.js +134 -0
- package/dist/acpx-engine/session-reuse-store.js.map +1 -0
- package/dist/acpx-engine/session-reuse-store.test.d.ts +2 -0
- package/dist/acpx-engine/session-reuse-store.test.d.ts.map +1 -0
- package/dist/acpx-engine/session-reuse-store.test.js +134 -0
- package/dist/acpx-engine/session-reuse-store.test.js.map +1 -0
- package/dist/acpx-engine/settlement-characterization.test.d.ts +2 -0
- package/dist/acpx-engine/settlement-characterization.test.d.ts.map +1 -0
- package/dist/acpx-engine/settlement-characterization.test.js +880 -0
- package/dist/acpx-engine/settlement-characterization.test.js.map +1 -0
- package/dist/acpx-engine/settlement-sequence.d.ts +77 -0
- package/dist/acpx-engine/settlement-sequence.d.ts.map +1 -0
- package/dist/acpx-engine/settlement-sequence.js +119 -0
- package/dist/acpx-engine/settlement-sequence.js.map +1 -0
- package/dist/acpx-engine/settlement-sequence.test.d.ts +2 -0
- package/dist/acpx-engine/settlement-sequence.test.d.ts.map +1 -0
- package/dist/acpx-engine/settlement-sequence.test.js +272 -0
- package/dist/acpx-engine/settlement-sequence.test.js.map +1 -0
- package/dist/acpx-engine/spawn-smoke.test.d.ts +2 -0
- package/dist/acpx-engine/spawn-smoke.test.d.ts.map +1 -0
- package/dist/acpx-engine/spawn-smoke.test.js +184 -0
- package/dist/acpx-engine/spawn-smoke.test.js.map +1 -0
- package/dist/acpx-engine/startup-cancellation.d.ts +8 -0
- package/dist/acpx-engine/startup-cancellation.d.ts.map +1 -0
- package/dist/acpx-engine/startup-cancellation.js +92 -0
- package/dist/acpx-engine/startup-cancellation.js.map +1 -0
- package/dist/acpx-engine/startup-cancellation.test.d.ts +2 -0
- package/dist/acpx-engine/startup-cancellation.test.d.ts.map +1 -0
- package/dist/acpx-engine/startup-cancellation.test.js +115 -0
- package/dist/acpx-engine/startup-cancellation.test.js.map +1 -0
- package/dist/acpx-engine/startup-characterization.test.d.ts +2 -0
- package/dist/acpx-engine/startup-characterization.test.d.ts.map +1 -0
- package/dist/acpx-engine/startup-characterization.test.js +742 -0
- package/dist/acpx-engine/startup-characterization.test.js.map +1 -0
- package/dist/acpx-engine/startup-timing.d.ts +419 -0
- package/dist/acpx-engine/startup-timing.d.ts.map +1 -0
- package/dist/acpx-engine/startup-timing.js +616 -0
- package/dist/acpx-engine/startup-timing.js.map +1 -0
- package/dist/acpx-engine/startup-timing.test.d.ts +2 -0
- package/dist/acpx-engine/startup-timing.test.d.ts.map +1 -0
- package/dist/acpx-engine/startup-timing.test.js +619 -0
- package/dist/acpx-engine/startup-timing.test.js.map +1 -0
- package/dist/acpx-engine/turn-characterization.test.d.ts +2 -0
- package/dist/acpx-engine/turn-characterization.test.d.ts.map +1 -0
- package/dist/acpx-engine/turn-characterization.test.js +566 -0
- package/dist/acpx-engine/turn-characterization.test.js.map +1 -0
- package/dist/acpx-engine/turn-sequence.d.ts +54 -0
- package/dist/acpx-engine/turn-sequence.d.ts.map +1 -0
- package/dist/acpx-engine/turn-sequence.js +75 -0
- package/dist/acpx-engine/turn-sequence.js.map +1 -0
- package/dist/acpx-engine/turn-sequence.test.d.ts +2 -0
- package/dist/acpx-engine/turn-sequence.test.d.ts.map +1 -0
- package/dist/acpx-engine/turn-sequence.test.js +177 -0
- package/dist/acpx-engine/turn-sequence.test.js.map +1 -0
- package/dist/acpx-engine/ui.d.ts +3 -0
- package/dist/acpx-engine/ui.d.ts.map +1 -0
- package/dist/acpx-engine/ui.js +162 -0
- package/dist/acpx-engine/ui.js.map +1 -0
- package/dist/billing.d.ts +2 -0
- package/dist/billing.d.ts.map +1 -0
- package/dist/billing.js +16 -0
- package/dist/billing.js.map +1 -0
- package/dist/billing.test.d.ts +2 -0
- package/dist/billing.test.d.ts.map +1 -0
- package/dist/billing.test.js +14 -0
- package/dist/billing.test.js.map +1 -0
- package/dist/bridge-transport-contract.d.ts +43 -0
- package/dist/bridge-transport-contract.d.ts.map +1 -0
- package/dist/bridge-transport-contract.js +33 -0
- package/dist/bridge-transport-contract.js.map +1 -0
- package/dist/chat-file-delivery.d.ts +10 -0
- package/dist/chat-file-delivery.d.ts.map +1 -0
- package/dist/chat-file-delivery.js +38 -0
- package/dist/chat-file-delivery.js.map +1 -0
- package/dist/chat-file-delivery.test.d.ts +2 -0
- package/dist/chat-file-delivery.test.d.ts.map +1 -0
- package/dist/chat-file-delivery.test.js +92 -0
- package/dist/chat-file-delivery.test.js.map +1 -0
- package/dist/command-managed-runtime.d.ts +162 -0
- package/dist/command-managed-runtime.d.ts.map +1 -0
- package/dist/command-managed-runtime.js +456 -0
- package/dist/command-managed-runtime.js.map +1 -0
- package/dist/command-managed-runtime.test.d.ts +2 -0
- package/dist/command-managed-runtime.test.d.ts.map +1 -0
- package/dist/command-managed-runtime.test.js +908 -0
- package/dist/command-managed-runtime.test.js.map +1 -0
- package/dist/command-redaction.d.ts +15 -0
- package/dist/command-redaction.d.ts.map +1 -0
- package/dist/command-redaction.js +97 -0
- package/dist/command-redaction.js.map +1 -0
- package/dist/command-redaction.test.d.ts +2 -0
- package/dist/command-redaction.test.d.ts.map +1 -0
- package/dist/command-redaction.test.js +80 -0
- package/dist/command-redaction.test.js.map +1 -0
- package/dist/duplex-frame-codec.d.ts +124 -0
- package/dist/duplex-frame-codec.d.ts.map +1 -0
- package/dist/duplex-frame-codec.js +134 -0
- package/dist/duplex-frame-codec.js.map +1 -0
- package/dist/duplex-frame-codec.test.d.ts +2 -0
- package/dist/duplex-frame-codec.test.d.ts.map +1 -0
- package/dist/duplex-frame-codec.test.js +172 -0
- package/dist/duplex-frame-codec.test.js.map +1 -0
- package/dist/duplex-observability.d.ts +220 -0
- package/dist/duplex-observability.d.ts.map +1 -0
- package/dist/duplex-observability.js +248 -0
- package/dist/duplex-observability.js.map +1 -0
- package/dist/duplex-observability.test.d.ts +2 -0
- package/dist/duplex-observability.test.d.ts.map +1 -0
- package/dist/duplex-observability.test.js +55 -0
- package/dist/duplex-observability.test.js.map +1 -0
- package/dist/env-bindings.d.ts +17 -0
- package/dist/env-bindings.d.ts.map +1 -0
- package/dist/env-bindings.js +98 -0
- package/dist/env-bindings.js.map +1 -0
- package/dist/env-bindings.test.d.ts +2 -0
- package/dist/env-bindings.test.d.ts.map +1 -0
- package/dist/env-bindings.test.js +66 -0
- package/dist/env-bindings.test.js.map +1 -0
- package/dist/exclude-patterns.d.ts +4 -0
- package/dist/exclude-patterns.d.ts.map +1 -0
- package/dist/exclude-patterns.js +26 -0
- package/dist/exclude-patterns.js.map +1 -0
- package/dist/execution-target-sandbox.test.d.ts +2 -0
- package/dist/execution-target-sandbox.test.d.ts.map +1 -0
- package/dist/execution-target-sandbox.test.js +6522 -0
- package/dist/execution-target-sandbox.test.js.map +1 -0
- package/dist/execution-target-stdin-race.test.d.ts +2 -0
- package/dist/execution-target-stdin-race.test.d.ts.map +1 -0
- package/dist/execution-target-stdin-race.test.js +1842 -0
- package/dist/execution-target-stdin-race.test.js.map +1 -0
- package/dist/execution-target.d.ts +537 -0
- package/dist/execution-target.d.ts.map +1 -0
- package/dist/execution-target.js +3877 -0
- package/dist/execution-target.js.map +1 -0
- package/dist/execution-target.test.d.ts +2 -0
- package/dist/execution-target.test.d.ts.map +1 -0
- package/dist/execution-target.test.js +346 -0
- package/dist/execution-target.test.js.map +1 -0
- package/dist/git-workspace-sync.d.ts +193 -0
- package/dist/git-workspace-sync.d.ts.map +1 -0
- package/dist/git-workspace-sync.js +786 -0
- package/dist/git-workspace-sync.js.map +1 -0
- package/dist/git-workspace-sync.test.d.ts +2 -0
- package/dist/git-workspace-sync.test.d.ts.map +1 -0
- package/dist/git-workspace-sync.test.js +830 -0
- package/dist/git-workspace-sync.test.js.map +1 -0
- package/dist/github-launcher-environment.test.d.ts +2 -0
- package/dist/github-launcher-environment.test.d.ts.map +1 -0
- package/dist/github-launcher-environment.test.js +285 -0
- package/dist/github-launcher-environment.test.js.map +1 -0
- package/dist/github-launcher.d.ts +8 -0
- package/dist/github-launcher.d.ts.map +1 -0
- package/dist/github-launcher.js +127 -0
- package/dist/github-launcher.js.map +1 -0
- package/dist/github-launcher.test.d.ts +2 -0
- package/dist/github-launcher.test.d.ts.map +1 -0
- package/dist/github-launcher.test.js +166 -0
- package/dist/github-launcher.test.js.map +1 -0
- package/dist/http2-bridge-server.d.ts +465 -0
- package/dist/http2-bridge-server.d.ts.map +1 -0
- package/dist/http2-bridge-server.js +1087 -0
- package/dist/http2-bridge-server.js.map +1 -0
- package/dist/http2-bridge-server.test.d.ts +2 -0
- package/dist/http2-bridge-server.test.d.ts.map +1 -0
- package/dist/http2-bridge-server.test.js +2129 -0
- package/dist/http2-bridge-server.test.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/local-process-sandbox.d.ts +41 -0
- package/dist/local-process-sandbox.d.ts.map +1 -0
- package/dist/local-process-sandbox.js +432 -0
- package/dist/local-process-sandbox.js.map +1 -0
- package/dist/local-process-sandbox.test.d.ts +2 -0
- package/dist/local-process-sandbox.test.d.ts.map +1 -0
- package/dist/local-process-sandbox.test.js +399 -0
- package/dist/local-process-sandbox.test.js.map +1 -0
- package/dist/log-redaction.d.ts +9 -0
- package/dist/log-redaction.d.ts.map +1 -0
- package/dist/log-redaction.js +88 -0
- package/dist/log-redaction.js.map +1 -0
- package/dist/login-capability.d.ts +88 -0
- package/dist/login-capability.d.ts.map +1 -0
- package/dist/login-capability.js +78 -0
- package/dist/login-capability.js.map +1 -0
- package/dist/login-capability.test.d.ts +2 -0
- package/dist/login-capability.test.d.ts.map +1 -0
- package/dist/login-capability.test.js +78 -0
- package/dist/login-capability.test.js.map +1 -0
- package/dist/login-pty-transport.d.ts +80 -0
- package/dist/login-pty-transport.d.ts.map +1 -0
- package/dist/login-pty-transport.js +75 -0
- package/dist/login-pty-transport.js.map +1 -0
- package/dist/login-pty-transport.test.d.ts +2 -0
- package/dist/login-pty-transport.test.d.ts.map +1 -0
- package/dist/login-pty-transport.test.js +185 -0
- package/dist/login-pty-transport.test.js.map +1 -0
- package/dist/login-runner-lifecycle.d.ts +70 -0
- package/dist/login-runner-lifecycle.d.ts.map +1 -0
- package/dist/login-runner-lifecycle.js +52 -0
- package/dist/login-runner-lifecycle.js.map +1 -0
- package/dist/login-runner-lifecycle.test.d.ts +2 -0
- package/dist/login-runner-lifecycle.test.d.ts.map +1 -0
- package/dist/login-runner-lifecycle.test.js +122 -0
- package/dist/login-runner-lifecycle.test.js.map +1 -0
- package/dist/mcp-isolation.integration.test.d.ts +2 -0
- package/dist/mcp-isolation.integration.test.d.ts.map +1 -0
- package/dist/mcp-isolation.integration.test.js +202 -0
- package/dist/mcp-isolation.integration.test.js.map +1 -0
- package/dist/paperclip-runner-permissions.d.ts +110 -0
- package/dist/paperclip-runner-permissions.d.ts.map +1 -0
- package/dist/paperclip-runner-permissions.js +150 -0
- package/dist/paperclip-runner-permissions.js.map +1 -0
- package/dist/paperclip-runner-permissions.test.d.ts +2 -0
- package/dist/paperclip-runner-permissions.test.d.ts.map +1 -0
- package/dist/paperclip-runner-permissions.test.js +47 -0
- package/dist/paperclip-runner-permissions.test.js.map +1 -0
- package/dist/published-exports.test.d.ts +2 -0
- package/dist/published-exports.test.d.ts.map +1 -0
- package/dist/published-exports.test.js +17 -0
- package/dist/published-exports.test.js.map +1 -0
- package/dist/remote-execution-env.d.ts +2 -0
- package/dist/remote-execution-env.d.ts.map +1 -0
- package/dist/remote-execution-env.js +46 -0
- package/dist/remote-execution-env.js.map +1 -0
- package/dist/remote-managed-runtime.d.ts +45 -0
- package/dist/remote-managed-runtime.d.ts.map +1 -0
- package/dist/remote-managed-runtime.js +183 -0
- package/dist/remote-managed-runtime.js.map +1 -0
- package/dist/remote-managed-runtime.test.d.ts +2 -0
- package/dist/remote-managed-runtime.test.d.ts.map +1 -0
- package/dist/remote-managed-runtime.test.js +294 -0
- package/dist/remote-managed-runtime.test.js.map +1 -0
- package/dist/runner-connectivity.d.ts +61 -0
- package/dist/runner-connectivity.d.ts.map +1 -0
- package/dist/runner-connectivity.js +81 -0
- package/dist/runner-connectivity.js.map +1 -0
- package/dist/runner-connectivity.test.d.ts +2 -0
- package/dist/runner-connectivity.test.d.ts.map +1 -0
- package/dist/runner-connectivity.test.js +183 -0
- package/dist/runner-connectivity.test.js.map +1 -0
- package/dist/runtime-progress.d.ts +48 -0
- package/dist/runtime-progress.d.ts.map +1 -0
- package/dist/runtime-progress.js +99 -0
- package/dist/runtime-progress.js.map +1 -0
- package/dist/runtime-progress.test.d.ts +2 -0
- package/dist/runtime-progress.test.d.ts.map +1 -0
- package/dist/runtime-progress.test.js +240 -0
- package/dist/runtime-progress.test.js.map +1 -0
- package/dist/sandbox-callback-bridge-body.d.ts +12 -0
- package/dist/sandbox-callback-bridge-body.d.ts.map +1 -0
- package/dist/sandbox-callback-bridge-body.js +39 -0
- package/dist/sandbox-callback-bridge-body.js.map +1 -0
- package/dist/sandbox-callback-bridge-body.test.d.ts +2 -0
- package/dist/sandbox-callback-bridge-body.test.d.ts.map +1 -0
- package/dist/sandbox-callback-bridge-body.test.js +31 -0
- package/dist/sandbox-callback-bridge-body.test.js.map +1 -0
- package/dist/sandbox-callback-bridge.d.ts +268 -0
- package/dist/sandbox-callback-bridge.d.ts.map +1 -0
- package/dist/sandbox-callback-bridge.js +2357 -0
- package/dist/sandbox-callback-bridge.js.map +1 -0
- package/dist/sandbox-callback-bridge.test.d.ts +2 -0
- package/dist/sandbox-callback-bridge.test.d.ts.map +1 -0
- package/dist/sandbox-callback-bridge.test.js +3253 -0
- package/dist/sandbox-callback-bridge.test.js.map +1 -0
- package/dist/sandbox-file-sync.test.d.ts +2 -0
- package/dist/sandbox-file-sync.test.d.ts.map +1 -0
- package/dist/sandbox-file-sync.test.js +419 -0
- package/dist/sandbox-file-sync.test.js.map +1 -0
- package/dist/sandbox-install-command.d.ts +2 -0
- package/dist/sandbox-install-command.d.ts.map +1 -0
- package/dist/sandbox-install-command.js +47 -0
- package/dist/sandbox-install-command.js.map +1 -0
- package/dist/sandbox-install-command.test.d.ts +2 -0
- package/dist/sandbox-install-command.test.d.ts.map +1 -0
- package/dist/sandbox-install-command.test.js +22 -0
- package/dist/sandbox-install-command.test.js.map +1 -0
- package/dist/sandbox-managed-runtime.d.ts +417 -0
- package/dist/sandbox-managed-runtime.d.ts.map +1 -0
- package/dist/sandbox-managed-runtime.js +1337 -0
- package/dist/sandbox-managed-runtime.js.map +1 -0
- package/dist/sandbox-managed-runtime.test.d.ts +2 -0
- package/dist/sandbox-managed-runtime.test.d.ts.map +1 -0
- package/dist/sandbox-managed-runtime.test.js +3746 -0
- package/dist/sandbox-managed-runtime.test.js.map +1 -0
- package/dist/sandbox-run-log-stream.d.ts +44 -0
- package/dist/sandbox-run-log-stream.d.ts.map +1 -0
- package/dist/sandbox-run-log-stream.js +203 -0
- package/dist/sandbox-run-log-stream.js.map +1 -0
- package/dist/sandbox-shell.d.ts +3 -0
- package/dist/sandbox-shell.d.ts.map +1 -0
- package/dist/sandbox-shell.js +7 -0
- package/dist/sandbox-shell.js.map +1 -0
- package/dist/server-utils-env.test.d.ts +2 -0
- package/dist/server-utils-env.test.d.ts.map +1 -0
- package/dist/server-utils-env.test.js +15 -0
- package/dist/server-utils-env.test.js.map +1 -0
- package/dist/server-utils.d.ts +617 -0
- package/dist/server-utils.d.ts.map +1 -0
- package/dist/server-utils.js +3258 -0
- package/dist/server-utils.js.map +1 -0
- package/dist/server-utils.test.d.ts +2 -0
- package/dist/server-utils.test.d.ts.map +1 -0
- package/dist/server-utils.test.js +3047 -0
- package/dist/server-utils.test.js.map +1 -0
- package/dist/session-compaction.d.ts +25 -0
- package/dist/session-compaction.d.ts.map +1 -0
- package/dist/session-compaction.js +165 -0
- package/dist/session-compaction.js.map +1 -0
- package/dist/skill-library-manifest.d.ts +6 -0
- package/dist/skill-library-manifest.d.ts.map +1 -0
- package/dist/skill-library-manifest.js +53 -0
- package/dist/skill-library-manifest.js.map +1 -0
- package/dist/skill-library-manifest.test.d.ts +2 -0
- package/dist/skill-library-manifest.test.d.ts.map +1 -0
- package/dist/skill-library-manifest.test.js +84 -0
- package/dist/skill-library-manifest.test.js.map +1 -0
- package/dist/skills-staging-follow-symlinks.test.d.ts +2 -0
- package/dist/skills-staging-follow-symlinks.test.d.ts.map +1 -0
- package/dist/skills-staging-follow-symlinks.test.js +126 -0
- package/dist/skills-staging-follow-symlinks.test.js.map +1 -0
- package/dist/ssh-fixture.test.d.ts +2 -0
- package/dist/ssh-fixture.test.d.ts.map +1 -0
- package/dist/ssh-fixture.test.js +832 -0
- package/dist/ssh-fixture.test.js.map +1 -0
- package/dist/ssh.d.ts +124 -0
- package/dist/ssh.d.ts.map +1 -0
- package/dist/ssh.js +1667 -0
- package/dist/ssh.js.map +1 -0
- package/dist/sync-operation-schedule.d.ts +17 -0
- package/dist/sync-operation-schedule.d.ts.map +1 -0
- package/dist/sync-operation-schedule.js +66 -0
- package/dist/sync-operation-schedule.js.map +1 -0
- package/dist/sync-operation-schedule.test.d.ts +2 -0
- package/dist/sync-operation-schedule.test.d.ts.map +1 -0
- package/dist/sync-operation-schedule.test.js +130 -0
- package/dist/sync-operation-schedule.test.js.map +1 -0
- package/dist/test-support/mcp-isolation-harness.d.ts +23 -0
- package/dist/test-support/mcp-isolation-harness.d.ts.map +1 -0
- package/dist/test-support/mcp-isolation-harness.js +67 -0
- package/dist/test-support/mcp-isolation-harness.js.map +1 -0
- package/dist/types.d.ts +780 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/workspace-restore-merge.d.ts +77 -0
- package/dist/workspace-restore-merge.d.ts.map +1 -0
- package/dist/workspace-restore-merge.js +396 -0
- package/dist/workspace-restore-merge.js.map +1 -0
- package/dist/workspace-restore-merge.test.d.ts +2 -0
- package/dist/workspace-restore-merge.test.d.ts.map +1 -0
- package/dist/workspace-restore-merge.test.js +438 -0
- package/dist/workspace-restore-merge.test.js.map +1 -0
- package/dist/workspace-restore-teardown.d.ts +29 -0
- package/dist/workspace-restore-teardown.d.ts.map +1 -0
- package/dist/workspace-restore-teardown.js +34 -0
- package/dist/workspace-restore-teardown.js.map +1 -0
- package/dist/workspace-restore-teardown.test.d.ts +2 -0
- package/dist/workspace-restore-teardown.test.d.ts.map +1 -0
- package/dist/workspace-restore-teardown.test.js +71 -0
- package/dist/workspace-restore-teardown.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/LICENSE +191 -0
- package/node_modules/@agentclientprotocol/sdk/README.md +70 -0
- package/node_modules/@agentclientprotocol/sdk/dist/acp.d.ts +1803 -0
- package/node_modules/@agentclientprotocol/sdk/dist/acp.js +1869 -0
- package/node_modules/@agentclientprotocol/sdk/dist/acp.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/acp.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/acp.test.js +5036 -0
- package/node_modules/@agentclientprotocol/sdk/dist/acp.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/connection.d.ts +89 -0
- package/node_modules/@agentclientprotocol/sdk/dist/connection.js +490 -0
- package/node_modules/@agentclientprotocol/sdk/dist/connection.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/connection.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/connection.test.js +507 -0
- package/node_modules/@agentclientprotocol/sdk/dist/connection.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/cookie-store.d.ts +26 -0
- package/node_modules/@agentclientprotocol/sdk/dist/cookie-store.js +96 -0
- package/node_modules/@agentclientprotocol/sdk/dist/cookie-store.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/cookie-store.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/cookie-store.test.js +77 -0
- package/node_modules/@agentclientprotocol/sdk/dist/cookie-store.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/agent.d.ts +2 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/agent.js +232 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/agent.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/client.d.ts +2 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/client.js +132 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/client.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/dual-version-agent.d.ts +9 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/dual-version-agent.js +221 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/dual-version-agent.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/http-client.d.ts +2 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/http-client.js +86 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/http-client.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/http-server.d.ts +2 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/http-server.js +112 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/http-server.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/ws-client.d.ts +2 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/ws-client.js +88 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/ws-client.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/http-stream.d.ts +33 -0
- package/node_modules/@agentclientprotocol/sdk/dist/http-stream.js +435 -0
- package/node_modules/@agentclientprotocol/sdk/dist/http-stream.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/http-stream.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/http-stream.test.js +1380 -0
- package/node_modules/@agentclientprotocol/sdk/dist/http-stream.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.d.ts +603 -0
- package/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.js +1076 -0
- package/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.test.js +917 -0
- package/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/line-buffer.d.ts +22 -0
- package/node_modules/@agentclientprotocol/sdk/dist/line-buffer.js +64 -0
- package/node_modules/@agentclientprotocol/sdk/dist/line-buffer.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/line-buffer.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/line-buffer.test.js +45 -0
- package/node_modules/@agentclientprotocol/sdk/dist/line-buffer.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/node-adapter.d.ts +17 -0
- package/node_modules/@agentclientprotocol/sdk/dist/node-adapter.js +426 -0
- package/node_modules/@agentclientprotocol/sdk/dist/node-adapter.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/node-adapter.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/node-adapter.test.js +499 -0
- package/node_modules/@agentclientprotocol/sdk/dist/node-adapter.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/protocol-router.d.ts +37 -0
- package/node_modules/@agentclientprotocol/sdk/dist/protocol-router.js +474 -0
- package/node_modules/@agentclientprotocol/sdk/dist/protocol-router.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/protocol-router.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/protocol-router.test.js +535 -0
- package/node_modules/@agentclientprotocol/sdk/dist/protocol-router.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/protocol.d.ts +11 -0
- package/node_modules/@agentclientprotocol/sdk/dist/protocol.js +69 -0
- package/node_modules/@agentclientprotocol/sdk/dist/protocol.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/protocol.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/protocol.test.js +82 -0
- package/node_modules/@agentclientprotocol/sdk/dist/protocol.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema/guards.gen.d.ts +183 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema/guards.gen.js +207 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema/guards.gen.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema/index.d.ts +51 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema/index.js +52 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema/index.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema/types.gen.d.ts +6219 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema/types.gen.js +3 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema/types.gen.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema/zod.gen.d.ts +15215 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema/zod.gen.js +3084 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema/zod.gen.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema-deserialize.d.ts +30 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema-deserialize.js +98 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema-deserialize.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema-deserialize.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema-deserialize.test.js +93 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema-deserialize.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server-permission.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server-permission.test.js +261 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server-permission.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server-session-sse.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server-session-sse.test.js +536 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server-session-sse.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server-sse.d.ts +2 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server-sse.js +88 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server-sse.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server-sse.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server-sse.test.js +53 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server-sse.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server-websocket-upgrade.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server-websocket-upgrade.test.js +992 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server-websocket-upgrade.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server.d.ts +81 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server.js +386 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server.test.js +1116 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/sse.d.ts +4 -0
- package/node_modules/@agentclientprotocol/sdk/dist/sse.js +91 -0
- package/node_modules/@agentclientprotocol/sdk/dist/sse.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/sse.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/sse.test.js +126 -0
- package/node_modules/@agentclientprotocol/sdk/dist/sse.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/stream.d.ts +35 -0
- package/node_modules/@agentclientprotocol/sdk/dist/stream.js +115 -0
- package/node_modules/@agentclientprotocol/sdk/dist/stream.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/stream.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/stream.test.js +195 -0
- package/node_modules/@agentclientprotocol/sdk/dist/stream.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/test-support/streams.d.ts +8 -0
- package/node_modules/@agentclientprotocol/sdk/dist/test-support/streams.js +51 -0
- package/node_modules/@agentclientprotocol/sdk/dist/test-support/streams.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/test-support/test-agent.d.ts +24 -0
- package/node_modules/@agentclientprotocol/sdk/dist/test-support/test-agent.js +163 -0
- package/node_modules/@agentclientprotocol/sdk/dist/test-support/test-agent.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/test-support/test-http-server.d.ts +9 -0
- package/node_modules/@agentclientprotocol/sdk/dist/test-support/test-http-server.js +74 -0
- package/node_modules/@agentclientprotocol/sdk/dist/test-support/test-http-server.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/acp.d.ts +957 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/acp.js +1484 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/acp.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/acp.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/acp.test.js +1287 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/acp.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/guards.gen.d.ts +946 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/guards.gen.js +1046 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/guards.gen.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/index.d.ts +42 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/index.js +43 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/index.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/types.gen.d.ts +6208 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/types.gen.js +3 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/types.gen.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/zod.gen.d.ts +33267 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/zod.gen.js +3199 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/zod.gen.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/ws-server.d.ts +14 -0
- package/node_modules/@agentclientprotocol/sdk/dist/ws-server.js +357 -0
- package/node_modules/@agentclientprotocol/sdk/dist/ws-server.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/ws-stream.d.ts +54 -0
- package/node_modules/@agentclientprotocol/sdk/dist/ws-stream.js +253 -0
- package/node_modules/@agentclientprotocol/sdk/dist/ws-stream.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/ws-stream.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/ws-stream.test.js +836 -0
- package/node_modules/@agentclientprotocol/sdk/dist/ws-stream.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/ws-utils.d.ts +13 -0
- package/node_modules/@agentclientprotocol/sdk/dist/ws-utils.js +78 -0
- package/node_modules/@agentclientprotocol/sdk/dist/ws-utils.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/ws-utils.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/ws-utils.test.js +42 -0
- package/node_modules/@agentclientprotocol/sdk/dist/ws-utils.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/package.json +104 -0
- package/node_modules/@agentclientprotocol/sdk/schema/schema.json +8967 -0
- package/node_modules/@agentclientprotocol/sdk/schema/v2/schema.unstable.json +10242 -0
- package/node_modules/@clack/core/LICENSE +9 -0
- package/node_modules/@clack/core/README.md +22 -0
- package/node_modules/@clack/core/dist/index.d.mts +503 -0
- package/node_modules/@clack/core/dist/index.mjs +1003 -0
- package/node_modules/@clack/core/package.json +63 -0
- package/node_modules/@clack/prompts/LICENSE +9 -0
- package/node_modules/@clack/prompts/README.md +396 -0
- package/node_modules/@clack/prompts/dist/index.d.mts +949 -0
- package/node_modules/@clack/prompts/dist/index.mjs +1423 -0
- package/node_modules/@clack/prompts/package.json +67 -0
- package/node_modules/@esbuild/linux-x64/README.md +3 -0
- package/node_modules/@esbuild/linux-x64/bin/esbuild +0 -0
- package/node_modules/@esbuild/linux-x64/package.json +20 -0
- package/node_modules/acpx/LICENSE +21 -0
- package/node_modules/acpx/README.md +424 -0
- package/node_modules/acpx/dist/cli-CxsS1XQp.js +197 -0
- package/node_modules/acpx/dist/cli-CxsS1XQp.js.map +1 -0
- package/node_modules/acpx/dist/cli.d.ts +14 -0
- package/node_modules/acpx/dist/cli.d.ts.map +1 -0
- package/node_modules/acpx/dist/cli.js +2683 -0
- package/node_modules/acpx/dist/cli.js.map +1 -0
- package/node_modules/acpx/dist/client-DwNyFauq.d.ts +192 -0
- package/node_modules/acpx/dist/client-DwNyFauq.d.ts.map +1 -0
- package/node_modules/acpx/dist/flags-Bjq_XQ4-.js +260 -0
- package/node_modules/acpx/dist/flags-Bjq_XQ4-.js.map +1 -0
- package/node_modules/acpx/dist/flows-Db_LZKu9.js +1920 -0
- package/node_modules/acpx/dist/flows-Db_LZKu9.js.map +1 -0
- package/node_modules/acpx/dist/flows.d.ts +318 -0
- package/node_modules/acpx/dist/flows.d.ts.map +1 -0
- package/node_modules/acpx/dist/flows.js +2 -0
- package/node_modules/acpx/dist/live-checkpoint-ClPCSdrW.js +6210 -0
- package/node_modules/acpx/dist/live-checkpoint-ClPCSdrW.js.map +1 -0
- package/node_modules/acpx/dist/output-CjdF5rHk.js +4593 -0
- package/node_modules/acpx/dist/output-CjdF5rHk.js.map +1 -0
- package/node_modules/acpx/dist/runtime.d.ts +472 -0
- package/node_modules/acpx/dist/runtime.d.ts.map +1 -0
- package/node_modules/acpx/dist/runtime.js +1711 -0
- package/node_modules/acpx/dist/runtime.js.map +1 -0
- package/node_modules/acpx/dist/session-options-jkYbBxGE.d.ts +278 -0
- package/node_modules/acpx/dist/session-options-jkYbBxGE.d.ts.map +1 -0
- package/node_modules/acpx/package.json +129 -0
- package/node_modules/acpx/skills/acpx/SKILL.md +617 -0
- package/node_modules/b4a/LICENSE +201 -0
- package/node_modules/b4a/README.md +153 -0
- package/node_modules/b4a/browser.js +546 -0
- package/node_modules/b4a/index.d.ts +144 -0
- package/node_modules/b4a/index.js +193 -0
- package/node_modules/b4a/lib/ascii.js +31 -0
- package/node_modules/b4a/lib/base64.js +64 -0
- package/node_modules/b4a/lib/hex.js +51 -0
- package/node_modules/b4a/lib/latin1.js +31 -0
- package/node_modules/b4a/lib/utf16le.js +40 -0
- package/node_modules/b4a/lib/utf8.js +144 -0
- package/node_modules/b4a/package.json +54 -0
- package/node_modules/b4a/react-native.js +5 -0
- package/node_modules/b4a/runtime.d.ts +14 -0
- package/node_modules/bare-events/LICENSE +201 -0
- package/node_modules/bare-events/README.md +25 -0
- package/node_modules/bare-events/global.d.ts +15 -0
- package/node_modules/bare-events/global.js +5 -0
- package/node_modules/bare-events/index.d.ts +89 -0
- package/node_modules/bare-events/index.js +397 -0
- package/node_modules/bare-events/lib/errors.js +23 -0
- package/node_modules/bare-events/package.json +63 -0
- package/node_modules/bare-events/web.d.ts +79 -0
- package/node_modules/bare-events/web.js +417 -0
- package/node_modules/bare-fs/CMakeLists.txt +13 -0
- package/node_modules/bare-fs/LICENSE +201 -0
- package/node_modules/bare-fs/README.md +33 -0
- package/node_modules/bare-fs/binding.c +2913 -0
- package/node_modules/bare-fs/binding.js +1 -0
- package/node_modules/bare-fs/index.d.ts +1502 -0
- package/node_modules/bare-fs/index.js +2855 -0
- package/node_modules/bare-fs/lib/constants.d.ts +50 -0
- package/node_modules/bare-fs/lib/constants.js +49 -0
- package/node_modules/bare-fs/lib/errors.d.ts +13 -0
- package/node_modules/bare-fs/lib/errors.js +55 -0
- package/node_modules/bare-fs/package.json +74 -0
- package/node_modules/bare-fs/prebuilds/android-arm/bare-fs.bare +0 -0
- package/node_modules/bare-fs/prebuilds/android-arm64/bare-fs.bare +0 -0
- package/node_modules/bare-fs/prebuilds/android-ia32/bare-fs.bare +0 -0
- package/node_modules/bare-fs/prebuilds/android-x64/bare-fs.bare +0 -0
- package/node_modules/bare-fs/prebuilds/darwin-arm64/bare-fs.bare +0 -0
- package/node_modules/bare-fs/prebuilds/darwin-x64/bare-fs.bare +0 -0
- package/node_modules/bare-fs/prebuilds/ios-arm64/bare-fs.bare +0 -0
- package/node_modules/bare-fs/prebuilds/ios-arm64-simulator/bare-fs.bare +0 -0
- package/node_modules/bare-fs/prebuilds/ios-x64-simulator/bare-fs.bare +0 -0
- package/node_modules/bare-fs/prebuilds/linux-arm64/bare-fs.bare +0 -0
- package/node_modules/bare-fs/prebuilds/linux-x64/bare-fs.bare +0 -0
- package/node_modules/bare-fs/prebuilds/win32-arm64/bare-fs.bare +0 -0
- package/node_modules/bare-fs/prebuilds/win32-x64/bare-fs.bare +0 -0
- package/node_modules/bare-fs/promises.d.ts +480 -0
- package/node_modules/bare-fs/promises.js +119 -0
- package/node_modules/bare-path/CMakeLists.txt +13 -0
- package/node_modules/bare-path/LICENSE +201 -0
- package/node_modules/bare-path/NOTICE +28 -0
- package/node_modules/bare-path/README.md +19 -0
- package/node_modules/bare-path/binding.c +106 -0
- package/node_modules/bare-path/binding.js +1 -0
- package/node_modules/bare-path/index.d.ts +27 -0
- package/node_modules/bare-path/index.js +9 -0
- package/node_modules/bare-path/lib/constants.js +11 -0
- package/node_modules/bare-path/lib/posix.js +263 -0
- package/node_modules/bare-path/lib/shared.js +81 -0
- package/node_modules/bare-path/lib/win32.js +564 -0
- package/node_modules/bare-path/package.json +47 -0
- package/node_modules/bare-path/prebuilds/android-arm/bare-path.bare +0 -0
- package/node_modules/bare-path/prebuilds/android-arm64/bare-path.bare +0 -0
- package/node_modules/bare-path/prebuilds/android-ia32/bare-path.bare +0 -0
- package/node_modules/bare-path/prebuilds/android-x64/bare-path.bare +0 -0
- package/node_modules/bare-path/prebuilds/darwin-arm64/bare-path.bare +0 -0
- package/node_modules/bare-path/prebuilds/darwin-x64/bare-path.bare +0 -0
- package/node_modules/bare-path/prebuilds/ios-arm64/bare-path.bare +0 -0
- package/node_modules/bare-path/prebuilds/ios-arm64-simulator/bare-path.bare +0 -0
- package/node_modules/bare-path/prebuilds/ios-x64-simulator/bare-path.bare +0 -0
- package/node_modules/bare-path/prebuilds/linux-arm64/bare-path.bare +0 -0
- package/node_modules/bare-path/prebuilds/linux-x64/bare-path.bare +0 -0
- package/node_modules/bare-path/prebuilds/win32-arm64/bare-path.bare +0 -0
- package/node_modules/bare-path/prebuilds/win32-x64/bare-path.bare +0 -0
- package/node_modules/bare-stream/LICENSE +201 -0
- package/node_modules/bare-stream/README.md +354 -0
- package/node_modules/bare-stream/global.d.ts +31 -0
- package/node_modules/bare-stream/global.js +15 -0
- package/node_modules/bare-stream/index.d.ts +235 -0
- package/node_modules/bare-stream/index.js +493 -0
- package/node_modules/bare-stream/package.json +75 -0
- package/node_modules/bare-stream/promises.js +3 -0
- package/node_modules/bare-stream/web.d.ts +152 -0
- package/node_modules/bare-stream/web.js +607 -0
- package/node_modules/bare-url/CMakeLists.txt +36 -0
- package/node_modules/bare-url/LICENSE +201 -0
- package/node_modules/bare-url/README.md +33 -0
- package/node_modules/bare-url/binding.c +422 -0
- package/node_modules/bare-url/binding.js +1 -0
- package/node_modules/bare-url/global.d.ts +24 -0
- package/node_modules/bare-url/global.js +2 -0
- package/node_modules/bare-url/index.d.ts +96 -0
- package/node_modules/bare-url/index.js +676 -0
- package/node_modules/bare-url/lib/errors.d.ts +10 -0
- package/node_modules/bare-url/lib/errors.js +33 -0
- package/node_modules/bare-url/lib/url-search-params.d.ts +66 -0
- package/node_modules/bare-url/lib/url-search-params.js +276 -0
- package/node_modules/bare-url/package.json +54 -0
- package/node_modules/bare-url/prebuilds/android-arm/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/android-arm64/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/android-ia32/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/android-x64/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/darwin-arm64/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/darwin-x64/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/ios-arm64/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/ios-arm64-simulator/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/ios-x64-simulator/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/linux-arm64/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/linux-x64/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/win32-arm64/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/win32-x64/bare-url.bare +0 -0
- package/node_modules/commander/LICENSE +22 -0
- package/node_modules/commander/Readme.md +1172 -0
- package/node_modules/commander/index.js +21 -0
- package/node_modules/commander/lib/argument.js +147 -0
- package/node_modules/commander/lib/command.js +2790 -0
- package/node_modules/commander/lib/error.js +36 -0
- package/node_modules/commander/lib/help.js +731 -0
- package/node_modules/commander/lib/option.js +377 -0
- package/node_modules/commander/lib/suggestSimilar.js +99 -0
- package/node_modules/commander/package-support.json +19 -0
- package/node_modules/commander/package.json +64 -0
- package/node_modules/commander/typings/index.d.ts +1113 -0
- package/node_modules/esbuild/LICENSE.md +21 -0
- package/node_modules/esbuild/README.md +3 -0
- package/node_modules/esbuild/bin/esbuild +223 -0
- package/node_modules/esbuild/install.js +300 -0
- package/node_modules/esbuild/lib/main.d.ts +720 -0
- package/node_modules/esbuild/lib/main.js +2536 -0
- package/node_modules/esbuild/package.json +74 -0
- package/node_modules/events-universal/LICENSE +201 -0
- package/node_modules/events-universal/README.md +17 -0
- package/node_modules/events-universal/bare.js +1 -0
- package/node_modules/events-universal/default.js +1 -0
- package/node_modules/events-universal/index.js +1 -0
- package/node_modules/events-universal/package.json +39 -0
- package/node_modules/events-universal/react-native.js +1 -0
- package/node_modules/fast-fifo/LICENSE +21 -0
- package/node_modules/fast-fifo/README.md +78 -0
- package/node_modules/fast-fifo/fixed-size.js +39 -0
- package/node_modules/fast-fifo/index.js +48 -0
- package/node_modules/fast-fifo/package.json +28 -0
- package/node_modules/fast-string-truncated-width/dist/index.d.ts +4 -0
- package/node_modules/fast-string-truncated-width/dist/index.js +111 -0
- package/node_modules/fast-string-truncated-width/dist/types.d.ts +19 -0
- package/node_modules/fast-string-truncated-width/dist/types.js +2 -0
- package/node_modules/fast-string-truncated-width/dist/utils.d.ts +4 -0
- package/node_modules/fast-string-truncated-width/dist/utils.js +20 -0
- package/node_modules/fast-string-truncated-width/license +21 -0
- package/node_modules/fast-string-truncated-width/package.json +35 -0
- package/node_modules/fast-string-truncated-width/readme.md +59 -0
- package/node_modules/fast-string-width/dist/index.d.ts +4 -0
- package/node_modules/fast-string-width/dist/index.js +14 -0
- package/node_modules/fast-string-width/license +21 -0
- package/node_modules/fast-string-width/package.json +34 -0
- package/node_modules/fast-string-width/readme.md +42 -0
- package/node_modules/fast-wrap-ansi/LICENSE +23 -0
- package/node_modules/fast-wrap-ansi/README.md +26 -0
- package/node_modules/fast-wrap-ansi/lib/main.d.ts +6 -0
- package/node_modules/fast-wrap-ansi/lib/main.js +218 -0
- package/node_modules/fast-wrap-ansi/package.json +51 -0
- package/node_modules/sisteransi/license +21 -0
- package/node_modules/sisteransi/package.json +34 -0
- package/node_modules/sisteransi/readme.md +113 -0
- package/node_modules/sisteransi/src/index.js +58 -0
- package/node_modules/sisteransi/src/sisteransi.d.ts +35 -0
- package/node_modules/skillflag/LICENSE +21 -0
- package/node_modules/skillflag/README.md +44 -0
- package/node_modules/skillflag/dist/bin/skill-install.d.ts +3 -0
- package/node_modules/skillflag/dist/bin/skill-install.d.ts.map +1 -0
- package/node_modules/skillflag/dist/bin/skill-install.js +6 -0
- package/node_modules/skillflag/dist/bin/skill-install.js.map +1 -0
- package/node_modules/skillflag/dist/bin/skillflag.d.ts +3 -0
- package/node_modules/skillflag/dist/bin/skillflag.d.ts.map +1 -0
- package/node_modules/skillflag/dist/bin/skillflag.js +25 -0
- package/node_modules/skillflag/dist/bin/skillflag.js.map +1 -0
- package/node_modules/skillflag/dist/core/digest.d.ts +2 -0
- package/node_modules/skillflag/dist/core/digest.d.ts.map +1 -0
- package/node_modules/skillflag/dist/core/digest.js +15 -0
- package/node_modules/skillflag/dist/core/digest.js.map +1 -0
- package/node_modules/skillflag/dist/core/errors.d.ts +6 -0
- package/node_modules/skillflag/dist/core/errors.d.ts.map +1 -0
- package/node_modules/skillflag/dist/core/errors.js +13 -0
- package/node_modules/skillflag/dist/core/errors.js.map +1 -0
- package/node_modules/skillflag/dist/core/export.d.ts +2 -0
- package/node_modules/skillflag/dist/core/export.d.ts.map +1 -0
- package/node_modules/skillflag/dist/core/export.js +28 -0
- package/node_modules/skillflag/dist/core/export.js.map +1 -0
- package/node_modules/skillflag/dist/core/list.d.ts +21 -0
- package/node_modules/skillflag/dist/core/list.d.ts.map +1 -0
- package/node_modules/skillflag/dist/core/list.js +71 -0
- package/node_modules/skillflag/dist/core/list.js.map +1 -0
- package/node_modules/skillflag/dist/core/paths.d.ts +15 -0
- package/node_modules/skillflag/dist/core/paths.d.ts.map +1 -0
- package/node_modules/skillflag/dist/core/paths.js +149 -0
- package/node_modules/skillflag/dist/core/paths.js.map +1 -0
- package/node_modules/skillflag/dist/core/show.d.ts +2 -0
- package/node_modules/skillflag/dist/core/show.d.ts.map +1 -0
- package/node_modules/skillflag/dist/core/show.js +8 -0
- package/node_modules/skillflag/dist/core/show.js.map +1 -0
- package/node_modules/skillflag/dist/core/skillflag.d.ts +3 -0
- package/node_modules/skillflag/dist/core/skillflag.d.ts.map +1 -0
- package/node_modules/skillflag/dist/core/skillflag.js +2 -0
- package/node_modules/skillflag/dist/core/skillflag.js.map +1 -0
- package/node_modules/skillflag/dist/core/tar.d.ts +16 -0
- package/node_modules/skillflag/dist/core/tar.d.ts.map +1 -0
- package/node_modules/skillflag/dist/core/tar.js +140 -0
- package/node_modules/skillflag/dist/core/tar.js.map +1 -0
- package/node_modules/skillflag/dist/index.d.ts +5 -0
- package/node_modules/skillflag/dist/index.d.ts.map +1 -0
- package/node_modules/skillflag/dist/index.js +3 -0
- package/node_modules/skillflag/dist/index.js.map +1 -0
- package/node_modules/skillflag/dist/install/cli.d.ts +57 -0
- package/node_modules/skillflag/dist/install/cli.d.ts.map +1 -0
- package/node_modules/skillflag/dist/install/cli.js +653 -0
- package/node_modules/skillflag/dist/install/cli.js.map +1 -0
- package/node_modules/skillflag/dist/install/copy.d.ts +2 -0
- package/node_modules/skillflag/dist/install/copy.d.ts.map +1 -0
- package/node_modules/skillflag/dist/install/copy.js +19 -0
- package/node_modules/skillflag/dist/install/copy.js.map +1 -0
- package/node_modules/skillflag/dist/install/errors.d.ts +6 -0
- package/node_modules/skillflag/dist/install/errors.d.ts.map +1 -0
- package/node_modules/skillflag/dist/install/errors.js +13 -0
- package/node_modules/skillflag/dist/install/errors.js.map +1 -0
- package/node_modules/skillflag/dist/install/extract.d.ts +3 -0
- package/node_modules/skillflag/dist/install/extract.d.ts.map +1 -0
- package/node_modules/skillflag/dist/install/extract.js +76 -0
- package/node_modules/skillflag/dist/install/extract.js.map +1 -0
- package/node_modules/skillflag/dist/install/install.d.ts +21 -0
- package/node_modules/skillflag/dist/install/install.d.ts.map +1 -0
- package/node_modules/skillflag/dist/install/install.js +35 -0
- package/node_modules/skillflag/dist/install/install.js.map +1 -0
- package/node_modules/skillflag/dist/install/resolve.d.ts +12 -0
- package/node_modules/skillflag/dist/install/resolve.d.ts.map +1 -0
- package/node_modules/skillflag/dist/install/resolve.js +119 -0
- package/node_modules/skillflag/dist/install/resolve.js.map +1 -0
- package/node_modules/skillflag/dist/install/validate.d.ts +7 -0
- package/node_modules/skillflag/dist/install/validate.d.ts.map +1 -0
- package/node_modules/skillflag/dist/install/validate.js +28 -0
- package/node_modules/skillflag/dist/install/validate.js.map +1 -0
- package/node_modules/skillflag/dist/shared/frontmatter.d.ts +2 -0
- package/node_modules/skillflag/dist/shared/frontmatter.d.ts.map +1 -0
- package/node_modules/skillflag/dist/shared/frontmatter.js +28 -0
- package/node_modules/skillflag/dist/shared/frontmatter.js.map +1 -0
- package/node_modules/skillflag/dist/skillflag.d.ts +29 -0
- package/node_modules/skillflag/dist/skillflag.d.ts.map +1 -0
- package/node_modules/skillflag/dist/skillflag.js +230 -0
- package/node_modules/skillflag/dist/skillflag.js.map +1 -0
- package/node_modules/skillflag/dist/utils/collections.d.ts +3 -0
- package/node_modules/skillflag/dist/utils/collections.d.ts.map +1 -0
- package/node_modules/skillflag/dist/utils/collections.js +18 -0
- package/node_modules/skillflag/dist/utils/collections.js.map +1 -0
- package/node_modules/skillflag/package.json +78 -0
- package/node_modules/skillflag/skills/skillflag/SKILL.md +144 -0
- package/node_modules/streamx/LICENSE +21 -0
- package/node_modules/streamx/README.md +550 -0
- package/node_modules/streamx/index.d.ts +198 -0
- package/node_modules/streamx/index.js +1314 -0
- package/node_modules/streamx/lib/errors.d.ts +13 -0
- package/node_modules/streamx/lib/errors.js +47 -0
- package/node_modules/streamx/package.json +51 -0
- package/node_modules/streamx/runtime.d.ts +55 -0
- package/node_modules/tar-stream/LICENSE +21 -0
- package/node_modules/tar-stream/README.md +183 -0
- package/node_modules/tar-stream/constants.js +14 -0
- package/node_modules/tar-stream/extract.js +418 -0
- package/node_modules/tar-stream/headers.js +322 -0
- package/node_modules/tar-stream/index.d.ts +60 -0
- package/node_modules/tar-stream/index.js +2 -0
- package/node_modules/tar-stream/pack.js +287 -0
- package/node_modules/tar-stream/package.json +50 -0
- package/node_modules/teex/LICENSE +21 -0
- package/node_modules/teex/README.md +31 -0
- package/node_modules/teex/example.js +16 -0
- package/node_modules/teex/index.js +52 -0
- package/node_modules/teex/package.json +26 -0
- package/node_modules/teex/test.js +68 -0
- package/node_modules/text-decoder/LICENSE +201 -0
- package/node_modules/text-decoder/README.md +36 -0
- package/node_modules/text-decoder/index.js +64 -0
- package/node_modules/text-decoder/lib/pass-through-decoder.js +19 -0
- package/node_modules/text-decoder/lib/utf8-decoder.js +185 -0
- package/node_modules/text-decoder/package.json +39 -0
- package/node_modules/tsx/LICENSE +21 -0
- package/node_modules/tsx/README.md +32 -0
- package/node_modules/tsx/dist/cjs/api/index.cjs +2 -0
- package/node_modules/tsx/dist/cjs/api/index.d.cts +35 -0
- package/node_modules/tsx/dist/cjs/api/index.d.mts +35 -0
- package/node_modules/tsx/dist/cjs/api/index.mjs +2 -0
- package/node_modules/tsx/dist/cjs/index.cjs +1 -0
- package/node_modules/tsx/dist/cli.mjs +55 -0
- package/node_modules/tsx/dist/client-XItNFmsq.cjs +1 -0
- package/node_modules/tsx/dist/esm/api/index.cjs +2 -0
- package/node_modules/tsx/dist/esm/api/index.d.cts +37 -0
- package/node_modules/tsx/dist/esm/api/index.d.mts +37 -0
- package/node_modules/tsx/dist/esm/api/index.mjs +2 -0
- package/node_modules/tsx/dist/esm/index.mjs +2 -0
- package/node_modules/tsx/dist/index-Bqjv9TxC.mjs +1 -0
- package/node_modules/tsx/dist/index-DE3OBZuV.mjs +14 -0
- package/node_modules/tsx/dist/lexer-CGfpMDSb.cjs +3 -0
- package/node_modules/tsx/dist/lexer-CanA6ArK.mjs +3 -0
- package/node_modules/tsx/dist/loader.mjs +2 -0
- package/node_modules/tsx/dist/package-Dj0mHsMt.mjs +1 -0
- package/node_modules/tsx/dist/patch-repl.cjs +1 -0
- package/node_modules/tsx/dist/preflight.cjs +1 -0
- package/node_modules/tsx/dist/register-B1c7OH6V.cjs +23 -0
- package/node_modules/tsx/dist/register-nyXW-TH3.mjs +2 -0
- package/node_modules/tsx/dist/repl.mjs +4 -0
- package/node_modules/tsx/dist/require-CBjy4Foe.mjs +10 -0
- package/node_modules/tsx/dist/require-CVaYn9z3.cjs +1 -0
- package/node_modules/tsx/dist/suppress-warnings.cjs +1 -0
- package/node_modules/tsx/dist/temporary-directory-Du7LpLp9.mjs +1 -0
- package/node_modules/tsx/package.json +70 -0
- package/node_modules/zod/LICENSE +21 -0
- package/node_modules/zod/README.md +218 -0
- package/node_modules/zod/compile.cjs +96 -0
- package/node_modules/zod/compile.d.cts +1 -0
- package/node_modules/zod/compile.d.ts +1 -0
- package/node_modules/zod/compile.js +52 -0
- package/node_modules/zod/index.cjs +52 -0
- package/node_modules/zod/index.d.cts +3 -0
- package/node_modules/zod/index.d.ts +3 -0
- package/node_modules/zod/index.js +7 -0
- package/node_modules/zod/locales/index.cjs +36 -0
- package/node_modules/zod/locales/index.d.cts +1 -0
- package/node_modules/zod/locales/index.d.ts +1 -0
- package/node_modules/zod/locales/index.js +1 -0
- package/node_modules/zod/locales/package.json +7 -0
- package/node_modules/zod/mini/index.cjs +51 -0
- package/node_modules/zod/mini/index.d.cts +3 -0
- package/node_modules/zod/mini/index.d.ts +3 -0
- package/node_modules/zod/mini/index.js +3 -0
- package/node_modules/zod/mini/package.json +7 -0
- package/node_modules/zod/package.json +147 -0
- package/node_modules/zod/src/compile.ts +56 -0
- package/node_modules/zod/src/index.ts +7 -0
- package/node_modules/zod/src/locales/index.ts +1 -0
- package/node_modules/zod/src/mini/index.ts +3 -0
- package/node_modules/zod/src/v3/ZodError.ts +340 -0
- package/node_modules/zod/src/v3/benchmarks/datetime.ts +58 -0
- package/node_modules/zod/src/v3/benchmarks/discriminatedUnion.ts +80 -0
- package/node_modules/zod/src/v3/benchmarks/index.ts +59 -0
- package/node_modules/zod/src/v3/benchmarks/ipv4.ts +57 -0
- package/node_modules/zod/src/v3/benchmarks/object.ts +69 -0
- package/node_modules/zod/src/v3/benchmarks/primitives.ts +162 -0
- package/node_modules/zod/src/v3/benchmarks/realworld.ts +63 -0
- package/node_modules/zod/src/v3/benchmarks/string.ts +55 -0
- package/node_modules/zod/src/v3/benchmarks/union.ts +80 -0
- package/node_modules/zod/src/v3/errors.ts +13 -0
- package/node_modules/zod/src/v3/external.ts +6 -0
- package/node_modules/zod/src/v3/helpers/enumUtil.ts +17 -0
- package/node_modules/zod/src/v3/helpers/errorUtil.ts +8 -0
- package/node_modules/zod/src/v3/helpers/parseUtil.ts +176 -0
- package/node_modules/zod/src/v3/helpers/partialUtil.ts +34 -0
- package/node_modules/zod/src/v3/helpers/typeAliases.ts +2 -0
- package/node_modules/zod/src/v3/helpers/util.ts +224 -0
- package/node_modules/zod/src/v3/index.ts +4 -0
- package/node_modules/zod/src/v3/locales/en.ts +124 -0
- package/node_modules/zod/src/v3/standard-schema.ts +113 -0
- package/node_modules/zod/src/v3/tests/Mocker.ts +54 -0
- package/node_modules/zod/src/v3/tests/all-errors.test.ts +157 -0
- package/node_modules/zod/src/v3/tests/anyunknown.test.ts +28 -0
- package/node_modules/zod/src/v3/tests/array.test.ts +71 -0
- package/node_modules/zod/src/v3/tests/async-parsing.test.ts +388 -0
- package/node_modules/zod/src/v3/tests/async-refinements.test.ts +46 -0
- package/node_modules/zod/src/v3/tests/base.test.ts +29 -0
- package/node_modules/zod/src/v3/tests/bigint.test.ts +55 -0
- package/node_modules/zod/src/v3/tests/branded.test.ts +53 -0
- package/node_modules/zod/src/v3/tests/catch.test.ts +220 -0
- package/node_modules/zod/src/v3/tests/coerce.test.ts +133 -0
- package/node_modules/zod/src/v3/tests/complex.test.ts +70 -0
- package/node_modules/zod/src/v3/tests/custom.test.ts +31 -0
- package/node_modules/zod/src/v3/tests/date.test.ts +32 -0
- package/node_modules/zod/src/v3/tests/deepmasking.test.ts +186 -0
- package/node_modules/zod/src/v3/tests/default.test.ts +112 -0
- package/node_modules/zod/src/v3/tests/description.test.ts +33 -0
- package/node_modules/zod/src/v3/tests/discriminated-unions.test.ts +315 -0
- package/node_modules/zod/src/v3/tests/enum.test.ts +80 -0
- package/node_modules/zod/src/v3/tests/error.test.ts +586 -0
- package/node_modules/zod/src/v3/tests/firstparty.test.ts +87 -0
- package/node_modules/zod/src/v3/tests/firstpartyschematypes.test.ts +21 -0
- package/node_modules/zod/src/v3/tests/function.test.ts +261 -0
- package/node_modules/zod/src/v3/tests/generics.test.ts +48 -0
- package/node_modules/zod/src/v3/tests/instanceof.test.ts +37 -0
- package/node_modules/zod/src/v3/tests/intersection.test.ts +128 -0
- package/node_modules/zod/src/v3/tests/language-server.source.ts +76 -0
- package/node_modules/zod/src/v3/tests/language-server.test.ts +206 -0
- package/node_modules/zod/src/v3/tests/literal.test.ts +36 -0
- package/node_modules/zod/src/v3/tests/map.test.ts +110 -0
- package/node_modules/zod/src/v3/tests/masking.test.ts +4 -0
- package/node_modules/zod/src/v3/tests/mocker.test.ts +19 -0
- package/node_modules/zod/src/v3/tests/nan.test.ts +24 -0
- package/node_modules/zod/src/v3/tests/nativeEnum.test.ts +87 -0
- package/node_modules/zod/src/v3/tests/nullable.test.ts +42 -0
- package/node_modules/zod/src/v3/tests/number.test.ts +176 -0
- package/node_modules/zod/src/v3/tests/object-augmentation.test.ts +29 -0
- package/node_modules/zod/src/v3/tests/object-in-es5-env.test.ts +29 -0
- package/node_modules/zod/src/v3/tests/object.test.ts +431 -0
- package/node_modules/zod/src/v3/tests/optional.test.ts +42 -0
- package/node_modules/zod/src/v3/tests/parseUtil.test.ts +23 -0
- package/node_modules/zod/src/v3/tests/parser.test.ts +41 -0
- package/node_modules/zod/src/v3/tests/partials.test.ts +243 -0
- package/node_modules/zod/src/v3/tests/pickomit.test.ts +111 -0
- package/node_modules/zod/src/v3/tests/pipeline.test.ts +29 -0
- package/node_modules/zod/src/v3/tests/preprocess.test.ts +186 -0
- package/node_modules/zod/src/v3/tests/primitive.test.ts +440 -0
- package/node_modules/zod/src/v3/tests/promise.test.ts +90 -0
- package/node_modules/zod/src/v3/tests/readonly.test.ts +114 -0
- package/node_modules/zod/src/v3/tests/record.test.ts +171 -0
- package/node_modules/zod/src/v3/tests/recursive.test.ts +197 -0
- package/node_modules/zod/src/v3/tests/refine.test.ts +313 -0
- package/node_modules/zod/src/v3/tests/safeparse.test.ts +27 -0
- package/node_modules/zod/src/v3/tests/set.test.ts +142 -0
- package/node_modules/zod/src/v3/tests/standard-schema.test.ts +83 -0
- package/node_modules/zod/src/v3/tests/string.test.ts +922 -0
- package/node_modules/zod/src/v3/tests/transformer.test.ts +233 -0
- package/node_modules/zod/src/v3/tests/tuple.test.ts +90 -0
- package/node_modules/zod/src/v3/tests/unions.test.ts +57 -0
- package/node_modules/zod/src/v3/tests/validations.test.ts +133 -0
- package/node_modules/zod/src/v3/tests/void.test.ts +15 -0
- package/node_modules/zod/src/v3/types.ts +5138 -0
- package/node_modules/zod/src/v4/classic/checks.ts +33 -0
- package/node_modules/zod/src/v4/classic/coerce.ts +27 -0
- package/node_modules/zod/src/v4/classic/compat.ts +78 -0
- package/node_modules/zod/src/v4/classic/deep-partial.ts +76 -0
- package/node_modules/zod/src/v4/classic/errors.ts +97 -0
- package/node_modules/zod/src/v4/classic/external.ts +57 -0
- package/node_modules/zod/src/v4/classic/from-json-schema.ts +944 -0
- package/node_modules/zod/src/v4/classic/in-out.ts +45 -0
- package/node_modules/zod/src/v4/classic/index.ts +5 -0
- package/node_modules/zod/src/v4/classic/iso.ts +20 -0
- package/node_modules/zod/src/v4/classic/parse.ts +88 -0
- package/node_modules/zod/src/v4/classic/schemas.ts +2937 -0
- package/node_modules/zod/src/v4/classic/tests/anyunknown.test.ts +26 -0
- package/node_modules/zod/src/v4/classic/tests/apply.test.ts +86 -0
- package/node_modules/zod/src/v4/classic/tests/array.test.ts +264 -0
- package/node_modules/zod/src/v4/classic/tests/assignability.test.ts +402 -0
- package/node_modules/zod/src/v4/classic/tests/async-parsing.test.ts +381 -0
- package/node_modules/zod/src/v4/classic/tests/async-refinements.test.ts +68 -0
- package/node_modules/zod/src/v4/classic/tests/base.test.ts +7 -0
- package/node_modules/zod/src/v4/classic/tests/bigint.test.ts +89 -0
- package/node_modules/zod/src/v4/classic/tests/brand.test.ts +106 -0
- package/node_modules/zod/src/v4/classic/tests/catch.test.ts +604 -0
- package/node_modules/zod/src/v4/classic/tests/coalesce.test.ts +20 -0
- package/node_modules/zod/src/v4/classic/tests/codec-examples.test.ts +572 -0
- package/node_modules/zod/src/v4/classic/tests/codec.test.ts +704 -0
- package/node_modules/zod/src/v4/classic/tests/coerce.test.ts +160 -0
- package/node_modules/zod/src/v4/classic/tests/continuability.test.ts +374 -0
- package/node_modules/zod/src/v4/classic/tests/credit-card.test.ts +76 -0
- package/node_modules/zod/src/v4/classic/tests/custom.test.ts +40 -0
- package/node_modules/zod/src/v4/classic/tests/cyclic-data.test.ts +985 -0
- package/node_modules/zod/src/v4/classic/tests/date.test.ts +92 -0
- package/node_modules/zod/src/v4/classic/tests/datetime.test.ts +316 -0
- package/node_modules/zod/src/v4/classic/tests/deep-partial.test.ts +380 -0
- package/node_modules/zod/src/v4/classic/tests/default-locale.test.ts +32 -0
- package/node_modules/zod/src/v4/classic/tests/default.test.ts +455 -0
- package/node_modules/zod/src/v4/classic/tests/describe-meta-checks.test.ts +27 -0
- package/node_modules/zod/src/v4/classic/tests/description.test.ts +32 -0
- package/node_modules/zod/src/v4/classic/tests/detached-methods.test.ts +221 -0
- package/node_modules/zod/src/v4/classic/tests/discriminated-unions.test.ts +942 -0
- package/node_modules/zod/src/v4/classic/tests/enum.test.ts +305 -0
- package/node_modules/zod/src/v4/classic/tests/error-utils.test.ts +917 -0
- package/node_modules/zod/src/v4/classic/tests/error.test.ts +1108 -0
- package/node_modules/zod/src/v4/classic/tests/file.test.ts +96 -0
- package/node_modules/zod/src/v4/classic/tests/firstparty.test.ts +179 -0
- package/node_modules/zod/src/v4/classic/tests/fix-json-issue.test.ts +26 -0
- package/node_modules/zod/src/v4/classic/tests/from-json-schema.test.ts +1467 -0
- package/node_modules/zod/src/v4/classic/tests/function.test.ts +391 -0
- package/node_modules/zod/src/v4/classic/tests/generics.test.ts +72 -0
- package/node_modules/zod/src/v4/classic/tests/global-config.test.ts +32 -0
- package/node_modules/zod/src/v4/classic/tests/hash.test.ts +67 -0
- package/node_modules/zod/src/v4/classic/tests/iban.test.ts +81 -0
- package/node_modules/zod/src/v4/classic/tests/in-out.test.ts +159 -0
- package/node_modules/zod/src/v4/classic/tests/index.test.ts +958 -0
- package/node_modules/zod/src/v4/classic/tests/instance-footprint.test.ts +319 -0
- package/node_modules/zod/src/v4/classic/tests/instanceof.test.ts +127 -0
- package/node_modules/zod/src/v4/classic/tests/intersection.test.ts +334 -0
- package/node_modules/zod/src/v4/classic/tests/issue-schema.test.ts +215 -0
- package/node_modules/zod/src/v4/classic/tests/jitless-allows-eval.test.ts +39 -0
- package/node_modules/zod/src/v4/classic/tests/json.test.ts +109 -0
- package/node_modules/zod/src/v4/classic/tests/lazy.test.ts +262 -0
- package/node_modules/zod/src/v4/classic/tests/literal.test.ts +128 -0
- package/node_modules/zod/src/v4/classic/tests/locales_ka.test.ts +29 -0
- package/node_modules/zod/src/v4/classic/tests/locales_ro.test.ts +24 -0
- package/node_modules/zod/src/v4/classic/tests/map.test.ts +342 -0
- package/node_modules/zod/src/v4/classic/tests/nan.test.ts +21 -0
- package/node_modules/zod/src/v4/classic/tests/nested-refine.test.ts +168 -0
- package/node_modules/zod/src/v4/classic/tests/nonoptional.test.ts +101 -0
- package/node_modules/zod/src/v4/classic/tests/nullable.test.ts +22 -0
- package/node_modules/zod/src/v4/classic/tests/number.test.ts +401 -0
- package/node_modules/zod/src/v4/classic/tests/object.test.ts +1006 -0
- package/node_modules/zod/src/v4/classic/tests/optin-ladder.test.ts +235 -0
- package/node_modules/zod/src/v4/classic/tests/optional.test.ts +366 -0
- package/node_modules/zod/src/v4/classic/tests/partial.test.ts +497 -0
- package/node_modules/zod/src/v4/classic/tests/pickomit.test.ts +211 -0
- package/node_modules/zod/src/v4/classic/tests/pipe.test.ts +101 -0
- package/node_modules/zod/src/v4/classic/tests/prefault.test.ts +144 -0
- package/node_modules/zod/src/v4/classic/tests/preprocess-types.test.ts +44 -0
- package/node_modules/zod/src/v4/classic/tests/preprocess.test.ts +351 -0
- package/node_modules/zod/src/v4/classic/tests/primitive.test.ts +175 -0
- package/node_modules/zod/src/v4/classic/tests/promise.test.ts +81 -0
- package/node_modules/zod/src/v4/classic/tests/properties.test.ts +141 -0
- package/node_modules/zod/src/v4/classic/tests/prototypes.test.ts +23 -0
- package/node_modules/zod/src/v4/classic/tests/readonly.test.ts +184 -0
- package/node_modules/zod/src/v4/classic/tests/record.test.ts +885 -0
- package/node_modules/zod/src/v4/classic/tests/recursive-types.test.ts +707 -0
- package/node_modules/zod/src/v4/classic/tests/redos.test.ts +68 -0
- package/node_modules/zod/src/v4/classic/tests/refine.test.ts +693 -0
- package/node_modules/zod/src/v4/classic/tests/registries.test.ts +243 -0
- package/node_modules/zod/src/v4/classic/tests/set.test.ts +190 -0
- package/node_modules/zod/src/v4/classic/tests/standard-schema.test.ts +153 -0
- package/node_modules/zod/src/v4/classic/tests/string-formats.test.ts +125 -0
- package/node_modules/zod/src/v4/classic/tests/string.test.ts +1440 -0
- package/node_modules/zod/src/v4/classic/tests/stringbool.test.ts +124 -0
- package/node_modules/zod/src/v4/classic/tests/template-literal.test.ts +812 -0
- package/node_modules/zod/src/v4/classic/tests/to-json-schema-methods.test.ts +438 -0
- package/node_modules/zod/src/v4/classic/tests/to-json-schema.test.ts +4733 -0
- package/node_modules/zod/src/v4/classic/tests/transform.test.ts +378 -0
- package/node_modules/zod/src/v4/classic/tests/tuple.test.ts +511 -0
- package/node_modules/zod/src/v4/classic/tests/union.test.ts +310 -0
- package/node_modules/zod/src/v4/classic/tests/url.test.ts +13 -0
- package/node_modules/zod/src/v4/classic/tests/validate.test.ts +645 -0
- package/node_modules/zod/src/v4/classic/tests/validations.test.ts +283 -0
- package/node_modules/zod/src/v4/classic/tests/void.test.ts +12 -0
- package/node_modules/zod/src/v4/core/api.ts +1875 -0
- package/node_modules/zod/src/v4/core/checks.ts +1266 -0
- package/node_modules/zod/src/v4/core/compile.ts +2313 -0
- package/node_modules/zod/src/v4/core/core.ts +230 -0
- package/node_modules/zod/src/v4/core/doc.ts +52 -0
- package/node_modules/zod/src/v4/core/errors.ts +543 -0
- package/node_modules/zod/src/v4/core/index.ts +19 -0
- package/node_modules/zod/src/v4/core/json-schema-generator.ts +131 -0
- package/node_modules/zod/src/v4/core/json-schema-processors.ts +934 -0
- package/node_modules/zod/src/v4/core/json-schema.ts +148 -0
- package/node_modules/zod/src/v4/core/memoizer.ts +331 -0
- package/node_modules/zod/src/v4/core/parse.ts +309 -0
- package/node_modules/zod/src/v4/core/regexes.ts +217 -0
- package/node_modules/zod/src/v4/core/registries.ts +105 -0
- package/node_modules/zod/src/v4/core/schemas.ts +5192 -0
- package/node_modules/zod/src/v4/core/standard-schema.ts +159 -0
- package/node_modules/zod/src/v4/core/tests/cached.test.ts +54 -0
- package/node_modules/zod/src/v4/core/tests/compile-differential.test.ts +1004 -0
- package/node_modules/zod/src/v4/core/tests/compile-generative.test.ts +445 -0
- package/node_modules/zod/src/v4/core/tests/compile-global.test.ts +89 -0
- package/node_modules/zod/src/v4/core/tests/compile-off-by-default.test.ts +26 -0
- package/node_modules/zod/src/v4/core/tests/compile-stats.test.ts +34 -0
- package/node_modules/zod/src/v4/core/tests/compile.test.ts +2036 -0
- package/node_modules/zod/src/v4/core/tests/email-regex.test.ts +140 -0
- package/node_modules/zod/src/v4/core/tests/extend.test.ts +59 -0
- package/node_modules/zod/src/v4/core/tests/index.test.ts +46 -0
- package/node_modules/zod/src/v4/core/tests/locales/be.test.ts +124 -0
- package/node_modules/zod/src/v4/core/tests/locales/el.test.ts +214 -0
- package/node_modules/zod/src/v4/core/tests/locales/en.test.ts +22 -0
- package/node_modules/zod/src/v4/core/tests/locales/es.test.ts +180 -0
- package/node_modules/zod/src/v4/core/tests/locales/fr.test.ts +72 -0
- package/node_modules/zod/src/v4/core/tests/locales/he.test.ts +379 -0
- package/node_modules/zod/src/v4/core/tests/locales/hr.test.ts +163 -0
- package/node_modules/zod/src/v4/core/tests/locales/nl.test.ts +46 -0
- package/node_modules/zod/src/v4/core/tests/locales/parity.test.ts +42 -0
- package/node_modules/zod/src/v4/core/tests/locales/ru.test.ts +128 -0
- package/node_modules/zod/src/v4/core/tests/locales/tg.test.ts +85 -0
- package/node_modules/zod/src/v4/core/tests/locales/tk.test.ts +69 -0
- package/node_modules/zod/src/v4/core/tests/locales/tr.test.ts +69 -0
- package/node_modules/zod/src/v4/core/tests/locales/uz.test.ts +105 -0
- package/node_modules/zod/src/v4/core/tests/polyfill-collision.test.ts +46 -0
- package/node_modules/zod/src/v4/core/tests/record-constructor.test.ts +125 -0
- package/node_modules/zod/src/v4/core/tests/recursive-tuples.test.ts +45 -0
- package/node_modules/zod/src/v4/core/tests/treeshake.test.ts +51 -0
- package/node_modules/zod/src/v4/core/tests/url-no-canparse.test.ts +133 -0
- package/node_modules/zod/src/v4/core/to-json-schema.ts +857 -0
- package/node_modules/zod/src/v4/core/util.ts +1280 -0
- package/node_modules/zod/src/v4/core/versions.ts +5 -0
- package/node_modules/zod/src/v4/core/visit.ts +193 -0
- package/node_modules/zod/src/v4/core/zsf.ts +323 -0
- package/node_modules/zod/src/v4/index.ts +4 -0
- package/node_modules/zod/src/v4/locales/ar.ts +120 -0
- package/node_modules/zod/src/v4/locales/az.ts +116 -0
- package/node_modules/zod/src/v4/locales/be.ts +188 -0
- package/node_modules/zod/src/v4/locales/bg.ts +133 -0
- package/node_modules/zod/src/v4/locales/bn.ts +123 -0
- package/node_modules/zod/src/v4/locales/ca.ts +121 -0
- package/node_modules/zod/src/v4/locales/ckb.ts +140 -0
- package/node_modules/zod/src/v4/locales/cs.ts +123 -0
- package/node_modules/zod/src/v4/locales/da.ts +128 -0
- package/node_modules/zod/src/v4/locales/de.ts +121 -0
- package/node_modules/zod/src/v4/locales/el.ts +124 -0
- package/node_modules/zod/src/v4/locales/en.ts +136 -0
- package/node_modules/zod/src/v4/locales/eo.ts +123 -0
- package/node_modules/zod/src/v4/locales/es.ts +145 -0
- package/node_modules/zod/src/v4/locales/fa.ts +131 -0
- package/node_modules/zod/src/v4/locales/fi.ts +126 -0
- package/node_modules/zod/src/v4/locales/fr-CA.ts +121 -0
- package/node_modules/zod/src/v4/locales/fr.ts +139 -0
- package/node_modules/zod/src/v4/locales/gu.ts +123 -0
- package/node_modules/zod/src/v4/locales/he.ts +253 -0
- package/node_modules/zod/src/v4/locales/hi.ts +126 -0
- package/node_modules/zod/src/v4/locales/hr.ts +135 -0
- package/node_modules/zod/src/v4/locales/hu.ts +122 -0
- package/node_modules/zod/src/v4/locales/hy.ts +175 -0
- package/node_modules/zod/src/v4/locales/id.ts +120 -0
- package/node_modules/zod/src/v4/locales/index.ts +63 -0
- package/node_modules/zod/src/v4/locales/is.ts +124 -0
- package/node_modules/zod/src/v4/locales/it.ts +121 -0
- package/node_modules/zod/src/v4/locales/ja.ts +119 -0
- package/node_modules/zod/src/v4/locales/ka.ts +128 -0
- package/node_modules/zod/src/v4/locales/kh.ts +7 -0
- package/node_modules/zod/src/v4/locales/km.ts +124 -0
- package/node_modules/zod/src/v4/locales/kn.ts +126 -0
- package/node_modules/zod/src/v4/locales/ko.ts +126 -0
- package/node_modules/zod/src/v4/locales/lt.ts +243 -0
- package/node_modules/zod/src/v4/locales/mk.ts +123 -0
- package/node_modules/zod/src/v4/locales/ms.ts +120 -0
- package/node_modules/zod/src/v4/locales/ne.ts +128 -0
- package/node_modules/zod/src/v4/locales/nl.ts +126 -0
- package/node_modules/zod/src/v4/locales/nn.ts +121 -0
- package/node_modules/zod/src/v4/locales/no.ts +121 -0
- package/node_modules/zod/src/v4/locales/ota.ts +122 -0
- package/node_modules/zod/src/v4/locales/pl.ts +123 -0
- package/node_modules/zod/src/v4/locales/ps.ts +131 -0
- package/node_modules/zod/src/v4/locales/pt-BR.ts +161 -0
- package/node_modules/zod/src/v4/locales/pt.ts +159 -0
- package/node_modules/zod/src/v4/locales/ro.ts +132 -0
- package/node_modules/zod/src/v4/locales/ru.ts +188 -0
- package/node_modules/zod/src/v4/locales/sk.ts +123 -0
- package/node_modules/zod/src/v4/locales/sl.ts +123 -0
- package/node_modules/zod/src/v4/locales/sv.ts +124 -0
- package/node_modules/zod/src/v4/locales/ta.ts +123 -0
- package/node_modules/zod/src/v4/locales/tg.ts +135 -0
- package/node_modules/zod/src/v4/locales/th.ts +124 -0
- package/node_modules/zod/src/v4/locales/tk.ts +113 -0
- package/node_modules/zod/src/v4/locales/tr.ts +116 -0
- package/node_modules/zod/src/v4/locales/ua.ts +7 -0
- package/node_modules/zod/src/v4/locales/uk.ts +122 -0
- package/node_modules/zod/src/v4/locales/ur.ts +124 -0
- package/node_modules/zod/src/v4/locales/uz.ts +120 -0
- package/node_modules/zod/src/v4/locales/vi.ts +122 -0
- package/node_modules/zod/src/v4/locales/yo.ts +129 -0
- package/node_modules/zod/src/v4/locales/zh-CN.ts +121 -0
- package/node_modules/zod/src/v4/locales/zh-TW.ts +120 -0
- package/node_modules/zod/src/v4/mini/checks.ts +33 -0
- package/node_modules/zod/src/v4/mini/coerce.ts +27 -0
- package/node_modules/zod/src/v4/mini/deep-partial.ts +73 -0
- package/node_modules/zod/src/v4/mini/external.ts +52 -0
- package/node_modules/zod/src/v4/mini/in-out.ts +45 -0
- package/node_modules/zod/src/v4/mini/index.ts +3 -0
- package/node_modules/zod/src/v4/mini/iso.ts +66 -0
- package/node_modules/zod/src/v4/mini/parse.ts +16 -0
- package/node_modules/zod/src/v4/mini/schemas.ts +2051 -0
- package/node_modules/zod/src/v4/mini/tests/apply.test.ts +57 -0
- package/node_modules/zod/src/v4/mini/tests/assignability.test.ts +190 -0
- package/node_modules/zod/src/v4/mini/tests/brand.test.ts +94 -0
- package/node_modules/zod/src/v4/mini/tests/checks.test.ts +158 -0
- package/node_modules/zod/src/v4/mini/tests/codec.test.ts +582 -0
- package/node_modules/zod/src/v4/mini/tests/computed.test.ts +30 -0
- package/node_modules/zod/src/v4/mini/tests/cyclic-data.test.ts +84 -0
- package/node_modules/zod/src/v4/mini/tests/deep-partial.test.ts +54 -0
- package/node_modules/zod/src/v4/mini/tests/error.test.ts +41 -0
- package/node_modules/zod/src/v4/mini/tests/functions.test.ts +14 -0
- package/node_modules/zod/src/v4/mini/tests/index.test.ts +1146 -0
- package/node_modules/zod/src/v4/mini/tests/number.test.ts +109 -0
- package/node_modules/zod/src/v4/mini/tests/object.test.ts +292 -0
- package/node_modules/zod/src/v4/mini/tests/prototypes.test.ts +43 -0
- package/node_modules/zod/src/v4/mini/tests/recursive-types.test.ts +341 -0
- package/node_modules/zod/src/v4/mini/tests/standard-schema.test.ts +50 -0
- package/node_modules/zod/src/v4/mini/tests/string.test.ts +396 -0
- package/node_modules/zod/src/v4-mini/index.ts +3 -0
- package/node_modules/zod/v3/ZodError.cjs +167 -0
- package/node_modules/zod/v3/ZodError.d.cts +164 -0
- package/node_modules/zod/v3/ZodError.d.ts +164 -0
- package/node_modules/zod/v3/ZodError.js +143 -0
- package/node_modules/zod/v3/errors.cjs +36 -0
- package/node_modules/zod/v3/errors.d.cts +5 -0
- package/node_modules/zod/v3/errors.d.ts +5 -0
- package/node_modules/zod/v3/errors.js +9 -0
- package/node_modules/zod/v3/external.cjs +41 -0
- package/node_modules/zod/v3/external.d.cts +6 -0
- package/node_modules/zod/v3/external.d.ts +6 -0
- package/node_modules/zod/v3/external.js +6 -0
- package/node_modules/zod/v3/helpers/enumUtil.cjs +21 -0
- package/node_modules/zod/v3/helpers/enumUtil.d.cts +8 -0
- package/node_modules/zod/v3/helpers/enumUtil.d.ts +8 -0
- package/node_modules/zod/v3/helpers/enumUtil.js +1 -0
- package/node_modules/zod/v3/helpers/errorUtil.cjs +28 -0
- package/node_modules/zod/v3/helpers/errorUtil.d.cts +9 -0
- package/node_modules/zod/v3/helpers/errorUtil.d.ts +9 -0
- package/node_modules/zod/v3/helpers/errorUtil.js +6 -0
- package/node_modules/zod/v3/helpers/parseUtil.cjs +143 -0
- package/node_modules/zod/v3/helpers/parseUtil.d.cts +78 -0
- package/node_modules/zod/v3/helpers/parseUtil.d.ts +78 -0
- package/node_modules/zod/v3/helpers/parseUtil.js +109 -0
- package/node_modules/zod/v3/helpers/partialUtil.cjs +21 -0
- package/node_modules/zod/v3/helpers/partialUtil.d.cts +8 -0
- package/node_modules/zod/v3/helpers/partialUtil.d.ts +8 -0
- package/node_modules/zod/v3/helpers/partialUtil.js +1 -0
- package/node_modules/zod/v3/helpers/typeAliases.cjs +21 -0
- package/node_modules/zod/v3/helpers/typeAliases.d.cts +2 -0
- package/node_modules/zod/v3/helpers/typeAliases.d.ts +2 -0
- package/node_modules/zod/v3/helpers/typeAliases.js +1 -0
- package/node_modules/zod/v3/helpers/util.cjs +156 -0
- package/node_modules/zod/v3/helpers/util.d.cts +85 -0
- package/node_modules/zod/v3/helpers/util.d.ts +85 -0
- package/node_modules/zod/v3/helpers/util.js +133 -0
- package/node_modules/zod/v3/index.cjs +52 -0
- package/node_modules/zod/v3/index.d.cts +4 -0
- package/node_modules/zod/v3/index.d.ts +4 -0
- package/node_modules/zod/v3/index.js +4 -0
- package/node_modules/zod/v3/locales/en.cjs +112 -0
- package/node_modules/zod/v3/locales/en.d.cts +3 -0
- package/node_modules/zod/v3/locales/en.d.ts +3 -0
- package/node_modules/zod/v3/locales/en.js +109 -0
- package/node_modules/zod/v3/package.json +7 -0
- package/node_modules/zod/v3/standard-schema.cjs +21 -0
- package/node_modules/zod/v3/standard-schema.d.cts +102 -0
- package/node_modules/zod/v3/standard-schema.d.ts +102 -0
- package/node_modules/zod/v3/standard-schema.js +1 -0
- package/node_modules/zod/v3/types.cjs +3798 -0
- package/node_modules/zod/v3/types.d.cts +1034 -0
- package/node_modules/zod/v3/types.d.ts +1034 -0
- package/node_modules/zod/v3/types.js +3697 -0
- package/node_modules/zod/v4/classic/checks.cjs +53 -0
- package/node_modules/zod/v4/classic/checks.d.cts +1 -0
- package/node_modules/zod/v4/classic/checks.d.ts +1 -0
- package/node_modules/zod/v4/classic/checks.js +1 -0
- package/node_modules/zod/v4/classic/coerce.cjs +66 -0
- package/node_modules/zod/v4/classic/coerce.d.cts +17 -0
- package/node_modules/zod/v4/classic/coerce.d.ts +17 -0
- package/node_modules/zod/v4/classic/coerce.js +17 -0
- package/node_modules/zod/v4/classic/compat.cjs +80 -0
- package/node_modules/zod/v4/classic/compat.d.cts +48 -0
- package/node_modules/zod/v4/classic/compat.d.ts +48 -0
- package/node_modules/zod/v4/classic/compat.js +31 -0
- package/node_modules/zod/v4/classic/deep-partial.cjs +58 -0
- package/node_modules/zod/v4/classic/deep-partial.d.cts +14 -0
- package/node_modules/zod/v4/classic/deep-partial.d.ts +14 -0
- package/node_modules/zod/v4/classic/deep-partial.js +13 -0
- package/node_modules/zod/v4/classic/errors.cjs +102 -0
- package/node_modules/zod/v4/classic/errors.d.cts +29 -0
- package/node_modules/zod/v4/classic/errors.d.ts +29 -0
- package/node_modules/zod/v4/classic/errors.js +57 -0
- package/node_modules/zod/v4/classic/external.cjs +97 -0
- package/node_modules/zod/v4/classic/external.d.cts +19 -0
- package/node_modules/zod/v4/classic/external.d.ts +19 -0
- package/node_modules/zod/v4/classic/external.js +17 -0
- package/node_modules/zod/v4/classic/from-json-schema.cjs +926 -0
- package/node_modules/zod/v4/classic/from-json-schema.d.cts +12 -0
- package/node_modules/zod/v4/classic/from-json-schema.d.ts +12 -0
- package/node_modules/zod/v4/classic/from-json-schema.js +881 -0
- package/node_modules/zod/v4/classic/in-out.cjs +82 -0
- package/node_modules/zod/v4/classic/in-out.d.cts +8 -0
- package/node_modules/zod/v4/classic/in-out.d.ts +8 -0
- package/node_modules/zod/v4/classic/in-out.js +36 -0
- package/node_modules/zod/v4/classic/index.cjs +52 -0
- package/node_modules/zod/v4/classic/index.d.cts +3 -0
- package/node_modules/zod/v4/classic/index.d.ts +3 -0
- package/node_modules/zod/v4/classic/index.js +4 -0
- package/node_modules/zod/v4/classic/iso.cjs +68 -0
- package/node_modules/zod/v4/classic/iso.d.cts +7 -0
- package/node_modules/zod/v4/classic/iso.d.ts +7 -0
- package/node_modules/zod/v4/classic/iso.js +15 -0
- package/node_modules/zod/v4/classic/package.json +7 -0
- package/node_modules/zod/v4/classic/parse.cjs +63 -0
- package/node_modules/zod/v4/classic/parse.d.cts +44 -0
- package/node_modules/zod/v4/classic/parse.d.ts +44 -0
- package/node_modules/zod/v4/classic/parse.js +16 -0
- package/node_modules/zod/v4/classic/schemas.cjs +1676 -0
- package/node_modules/zod/v4/classic/schemas.d.cts +816 -0
- package/node_modules/zod/v4/classic/schemas.d.ts +816 -0
- package/node_modules/zod/v4/classic/schemas.js +1535 -0
- package/node_modules/zod/v4/core/api.cjs +1269 -0
- package/node_modules/zod/v4/core/api.d.cts +335 -0
- package/node_modules/zod/v4/core/api.d.ts +335 -0
- package/node_modules/zod/v4/core/api.js +1107 -0
- package/node_modules/zod/v4/core/checks.cjs +561 -0
- package/node_modules/zod/v4/core/checks.d.cts +294 -0
- package/node_modules/zod/v4/core/checks.d.ts +294 -0
- package/node_modules/zod/v4/core/checks.js +516 -0
- package/node_modules/zod/v4/core/compile.cjs +1908 -0
- package/node_modules/zod/v4/core/compile.d.cts +65 -0
- package/node_modules/zod/v4/core/compile.d.ts +65 -0
- package/node_modules/zod/v4/core/compile.js +1858 -0
- package/node_modules/zod/v4/core/core.cjs +167 -0
- package/node_modules/zod/v4/core/core.d.cts +82 -0
- package/node_modules/zod/v4/core/core.d.ts +82 -0
- package/node_modules/zod/v4/core/core.js +141 -0
- package/node_modules/zod/v4/core/doc.cjs +61 -0
- package/node_modules/zod/v4/core/doc.d.cts +16 -0
- package/node_modules/zod/v4/core/doc.d.ts +16 -0
- package/node_modules/zod/v4/core/doc.js +38 -0
- package/node_modules/zod/v4/core/errors.cjs +317 -0
- package/node_modules/zod/v4/core/errors.d.cts +225 -0
- package/node_modules/zod/v4/core/errors.d.ts +225 -0
- package/node_modules/zod/v4/core/errors.js +267 -0
- package/node_modules/zod/v4/core/index.cjs +70 -0
- package/node_modules/zod/v4/core/index.d.cts +19 -0
- package/node_modules/zod/v4/core/index.d.ts +19 -0
- package/node_modules/zod/v4/core/index.js +19 -0
- package/node_modules/zod/v4/core/json-schema-generator.cjs +122 -0
- package/node_modules/zod/v4/core/json-schema-generator.d.cts +65 -0
- package/node_modules/zod/v4/core/json-schema-generator.d.ts +65 -0
- package/node_modules/zod/v4/core/json-schema-generator.js +99 -0
- package/node_modules/zod/v4/core/json-schema-processors.cjs +929 -0
- package/node_modules/zod/v4/core/json-schema-processors.d.cts +65 -0
- package/node_modules/zod/v4/core/json-schema-processors.d.ts +65 -0
- package/node_modules/zod/v4/core/json-schema-processors.js +843 -0
- package/node_modules/zod/v4/core/json-schema.cjs +21 -0
- package/node_modules/zod/v4/core/json-schema.d.cts +89 -0
- package/node_modules/zod/v4/core/json-schema.d.ts +89 -0
- package/node_modules/zod/v4/core/json-schema.js +1 -0
- package/node_modules/zod/v4/core/memoizer.cjs +360 -0
- package/node_modules/zod/v4/core/memoizer.d.cts +15 -0
- package/node_modules/zod/v4/core/memoizer.d.ts +15 -0
- package/node_modules/zod/v4/core/memoizer.js +311 -0
- package/node_modules/zod/v4/core/package.json +7 -0
- package/node_modules/zod/v4/core/parse.cjs +233 -0
- package/node_modules/zod/v4/core/parse.d.cts +52 -0
- package/node_modules/zod/v4/core/parse.d.ts +52 -0
- package/node_modules/zod/v4/core/parse.js +175 -0
- package/node_modules/zod/v4/core/regexes.cjs +209 -0
- package/node_modules/zod/v4/core/regexes.d.cts +90 -0
- package/node_modules/zod/v4/core/regexes.d.ts +90 -0
- package/node_modules/zod/v4/core/regexes.js +156 -0
- package/node_modules/zod/v4/core/registries.cjs +75 -0
- package/node_modules/zod/v4/core/registries.d.cts +35 -0
- package/node_modules/zod/v4/core/registries.d.ts +35 -0
- package/node_modules/zod/v4/core/registries.js +51 -0
- package/node_modules/zod/v4/core/schemas.cjs +2669 -0
- package/node_modules/zod/v4/core/schemas.d.cts +1273 -0
- package/node_modules/zod/v4/core/schemas.d.ts +1273 -0
- package/node_modules/zod/v4/core/schemas.js +2606 -0
- package/node_modules/zod/v4/core/standard-schema.cjs +21 -0
- package/node_modules/zod/v4/core/standard-schema.d.cts +126 -0
- package/node_modules/zod/v4/core/standard-schema.d.ts +126 -0
- package/node_modules/zod/v4/core/standard-schema.js +1 -0
- package/node_modules/zod/v4/core/to-json-schema.cjs +681 -0
- package/node_modules/zod/v4/core/to-json-schema.d.cts +154 -0
- package/node_modules/zod/v4/core/to-json-schema.d.ts +154 -0
- package/node_modules/zod/v4/core/to-json-schema.js +651 -0
- package/node_modules/zod/v4/core/util.cjs +949 -0
- package/node_modules/zod/v4/core/util.d.cts +278 -0
- package/node_modules/zod/v4/core/util.d.ts +278 -0
- package/node_modules/zod/v4/core/util.js +859 -0
- package/node_modules/zod/v4/core/versions.cjs +27 -0
- package/node_modules/zod/v4/core/versions.d.cts +5 -0
- package/node_modules/zod/v4/core/versions.d.ts +5 -0
- package/node_modules/zod/v4/core/versions.js +5 -0
- package/node_modules/zod/v4/core/visit.cjs +214 -0
- package/node_modules/zod/v4/core/visit.d.cts +30 -0
- package/node_modules/zod/v4/core/visit.d.ts +30 -0
- package/node_modules/zod/v4/core/visit.js +169 -0
- package/node_modules/zod/v4/index.cjs +51 -0
- package/node_modules/zod/v4/index.d.cts +3 -0
- package/node_modules/zod/v4/index.d.ts +3 -0
- package/node_modules/zod/v4/index.js +4 -0
- package/node_modules/zod/v4/locales/ar.cjs +138 -0
- package/node_modules/zod/v4/locales/ar.d.cts +5 -0
- package/node_modules/zod/v4/locales/ar.d.ts +4 -0
- package/node_modules/zod/v4/locales/ar.js +111 -0
- package/node_modules/zod/v4/locales/az.cjs +137 -0
- package/node_modules/zod/v4/locales/az.d.cts +5 -0
- package/node_modules/zod/v4/locales/az.d.ts +4 -0
- package/node_modules/zod/v4/locales/az.js +110 -0
- package/node_modules/zod/v4/locales/be.cjs +195 -0
- package/node_modules/zod/v4/locales/be.d.cts +5 -0
- package/node_modules/zod/v4/locales/be.d.ts +4 -0
- package/node_modules/zod/v4/locales/be.js +168 -0
- package/node_modules/zod/v4/locales/bg.cjs +152 -0
- package/node_modules/zod/v4/locales/bg.d.cts +5 -0
- package/node_modules/zod/v4/locales/bg.d.ts +4 -0
- package/node_modules/zod/v4/locales/bg.js +125 -0
- package/node_modules/zod/v4/locales/bn.cjs +140 -0
- package/node_modules/zod/v4/locales/bn.d.cts +5 -0
- package/node_modules/zod/v4/locales/bn.d.ts +4 -0
- package/node_modules/zod/v4/locales/bn.js +113 -0
- package/node_modules/zod/v4/locales/ca.cjs +139 -0
- package/node_modules/zod/v4/locales/ca.d.cts +5 -0
- package/node_modules/zod/v4/locales/ca.d.ts +4 -0
- package/node_modules/zod/v4/locales/ca.js +112 -0
- package/node_modules/zod/v4/locales/ckb.cjs +160 -0
- package/node_modules/zod/v4/locales/ckb.d.cts +5 -0
- package/node_modules/zod/v4/locales/ckb.d.ts +4 -0
- package/node_modules/zod/v4/locales/ckb.js +133 -0
- package/node_modules/zod/v4/locales/cs.cjs +143 -0
- package/node_modules/zod/v4/locales/cs.d.cts +5 -0
- package/node_modules/zod/v4/locales/cs.d.ts +4 -0
- package/node_modules/zod/v4/locales/cs.js +116 -0
- package/node_modules/zod/v4/locales/da.cjs +147 -0
- package/node_modules/zod/v4/locales/da.d.cts +5 -0
- package/node_modules/zod/v4/locales/da.d.ts +4 -0
- package/node_modules/zod/v4/locales/da.js +120 -0
- package/node_modules/zod/v4/locales/de.cjs +140 -0
- package/node_modules/zod/v4/locales/de.d.cts +5 -0
- package/node_modules/zod/v4/locales/de.d.ts +4 -0
- package/node_modules/zod/v4/locales/de.js +113 -0
- package/node_modules/zod/v4/locales/el.cjs +139 -0
- package/node_modules/zod/v4/locales/el.d.cts +5 -0
- package/node_modules/zod/v4/locales/el.d.ts +4 -0
- package/node_modules/zod/v4/locales/el.js +112 -0
- package/node_modules/zod/v4/locales/en.cjs +152 -0
- package/node_modules/zod/v4/locales/en.d.cts +5 -0
- package/node_modules/zod/v4/locales/en.d.ts +4 -0
- package/node_modules/zod/v4/locales/en.js +125 -0
- package/node_modules/zod/v4/locales/eo.cjs +141 -0
- package/node_modules/zod/v4/locales/eo.d.cts +5 -0
- package/node_modules/zod/v4/locales/eo.d.ts +4 -0
- package/node_modules/zod/v4/locales/eo.js +114 -0
- package/node_modules/zod/v4/locales/es.cjs +163 -0
- package/node_modules/zod/v4/locales/es.d.cts +5 -0
- package/node_modules/zod/v4/locales/es.d.ts +4 -0
- package/node_modules/zod/v4/locales/es.js +136 -0
- package/node_modules/zod/v4/locales/fa.cjs +146 -0
- package/node_modules/zod/v4/locales/fa.d.cts +5 -0
- package/node_modules/zod/v4/locales/fa.d.ts +4 -0
- package/node_modules/zod/v4/locales/fa.js +119 -0
- package/node_modules/zod/v4/locales/fi.cjs +144 -0
- package/node_modules/zod/v4/locales/fi.d.cts +5 -0
- package/node_modules/zod/v4/locales/fi.d.ts +4 -0
- package/node_modules/zod/v4/locales/fi.js +117 -0
- package/node_modules/zod/v4/locales/fr-CA.cjs +139 -0
- package/node_modules/zod/v4/locales/fr-CA.d.cts +5 -0
- package/node_modules/zod/v4/locales/fr-CA.d.ts +4 -0
- package/node_modules/zod/v4/locales/fr-CA.js +112 -0
- package/node_modules/zod/v4/locales/fr.cjs +156 -0
- package/node_modules/zod/v4/locales/fr.d.cts +5 -0
- package/node_modules/zod/v4/locales/fr.d.ts +4 -0
- package/node_modules/zod/v4/locales/fr.js +129 -0
- package/node_modules/zod/v4/locales/gu.cjs +140 -0
- package/node_modules/zod/v4/locales/gu.d.cts +5 -0
- package/node_modules/zod/v4/locales/gu.d.ts +4 -0
- package/node_modules/zod/v4/locales/gu.js +113 -0
- package/node_modules/zod/v4/locales/he.cjs +248 -0
- package/node_modules/zod/v4/locales/he.d.cts +5 -0
- package/node_modules/zod/v4/locales/he.d.ts +4 -0
- package/node_modules/zod/v4/locales/he.js +221 -0
- package/node_modules/zod/v4/locales/hi.cjs +138 -0
- package/node_modules/zod/v4/locales/hi.d.cts +5 -0
- package/node_modules/zod/v4/locales/hi.d.ts +4 -0
- package/node_modules/zod/v4/locales/hi.js +111 -0
- package/node_modules/zod/v4/locales/hr.cjs +153 -0
- package/node_modules/zod/v4/locales/hr.d.cts +5 -0
- package/node_modules/zod/v4/locales/hr.d.ts +4 -0
- package/node_modules/zod/v4/locales/hr.js +126 -0
- package/node_modules/zod/v4/locales/hu.cjs +140 -0
- package/node_modules/zod/v4/locales/hu.d.cts +5 -0
- package/node_modules/zod/v4/locales/hu.d.ts +4 -0
- package/node_modules/zod/v4/locales/hu.js +113 -0
- package/node_modules/zod/v4/locales/hy.cjs +185 -0
- package/node_modules/zod/v4/locales/hy.d.cts +5 -0
- package/node_modules/zod/v4/locales/hy.d.ts +4 -0
- package/node_modules/zod/v4/locales/hy.js +158 -0
- package/node_modules/zod/v4/locales/id.cjs +138 -0
- package/node_modules/zod/v4/locales/id.d.cts +5 -0
- package/node_modules/zod/v4/locales/id.d.ts +4 -0
- package/node_modules/zod/v4/locales/id.js +111 -0
- package/node_modules/zod/v4/locales/index.cjs +152 -0
- package/node_modules/zod/v4/locales/index.d.cts +63 -0
- package/node_modules/zod/v4/locales/index.d.ts +63 -0
- package/node_modules/zod/v4/locales/index.js +63 -0
- package/node_modules/zod/v4/locales/is.cjs +141 -0
- package/node_modules/zod/v4/locales/is.d.cts +5 -0
- package/node_modules/zod/v4/locales/is.d.ts +4 -0
- package/node_modules/zod/v4/locales/is.js +114 -0
- package/node_modules/zod/v4/locales/it.cjs +140 -0
- package/node_modules/zod/v4/locales/it.d.cts +5 -0
- package/node_modules/zod/v4/locales/it.d.ts +4 -0
- package/node_modules/zod/v4/locales/it.js +113 -0
- package/node_modules/zod/v4/locales/ja.cjs +139 -0
- package/node_modules/zod/v4/locales/ja.d.cts +5 -0
- package/node_modules/zod/v4/locales/ja.d.ts +4 -0
- package/node_modules/zod/v4/locales/ja.js +112 -0
- package/node_modules/zod/v4/locales/ka.cjs +144 -0
- package/node_modules/zod/v4/locales/ka.d.cts +5 -0
- package/node_modules/zod/v4/locales/ka.d.ts +4 -0
- package/node_modules/zod/v4/locales/ka.js +117 -0
- package/node_modules/zod/v4/locales/kh.cjs +12 -0
- package/node_modules/zod/v4/locales/kh.d.cts +5 -0
- package/node_modules/zod/v4/locales/kh.d.ts +5 -0
- package/node_modules/zod/v4/locales/kh.js +5 -0
- package/node_modules/zod/v4/locales/km.cjs +142 -0
- package/node_modules/zod/v4/locales/km.d.cts +5 -0
- package/node_modules/zod/v4/locales/km.d.ts +4 -0
- package/node_modules/zod/v4/locales/km.js +115 -0
- package/node_modules/zod/v4/locales/kn.cjs +143 -0
- package/node_modules/zod/v4/locales/kn.d.cts +5 -0
- package/node_modules/zod/v4/locales/kn.d.ts +4 -0
- package/node_modules/zod/v4/locales/kn.js +116 -0
- package/node_modules/zod/v4/locales/ko.cjs +143 -0
- package/node_modules/zod/v4/locales/ko.d.cts +5 -0
- package/node_modules/zod/v4/locales/ko.d.ts +4 -0
- package/node_modules/zod/v4/locales/ko.js +116 -0
- package/node_modules/zod/v4/locales/lt.cjs +234 -0
- package/node_modules/zod/v4/locales/lt.d.cts +5 -0
- package/node_modules/zod/v4/locales/lt.d.ts +4 -0
- package/node_modules/zod/v4/locales/lt.js +207 -0
- package/node_modules/zod/v4/locales/mk.cjs +141 -0
- package/node_modules/zod/v4/locales/mk.d.cts +5 -0
- package/node_modules/zod/v4/locales/mk.d.ts +4 -0
- package/node_modules/zod/v4/locales/mk.js +114 -0
- package/node_modules/zod/v4/locales/ms.cjs +139 -0
- package/node_modules/zod/v4/locales/ms.d.cts +5 -0
- package/node_modules/zod/v4/locales/ms.d.ts +4 -0
- package/node_modules/zod/v4/locales/ms.js +112 -0
- package/node_modules/zod/v4/locales/ne.cjs +138 -0
- package/node_modules/zod/v4/locales/ne.d.cts +5 -0
- package/node_modules/zod/v4/locales/ne.d.ts +4 -0
- package/node_modules/zod/v4/locales/ne.js +111 -0
- package/node_modules/zod/v4/locales/nl.cjs +142 -0
- package/node_modules/zod/v4/locales/nl.d.cts +5 -0
- package/node_modules/zod/v4/locales/nl.d.ts +4 -0
- package/node_modules/zod/v4/locales/nl.js +115 -0
- package/node_modules/zod/v4/locales/nn.cjs +140 -0
- package/node_modules/zod/v4/locales/nn.d.cts +5 -0
- package/node_modules/zod/v4/locales/nn.d.ts +4 -0
- package/node_modules/zod/v4/locales/nn.js +113 -0
- package/node_modules/zod/v4/locales/no.cjs +140 -0
- package/node_modules/zod/v4/locales/no.d.cts +5 -0
- package/node_modules/zod/v4/locales/no.d.ts +4 -0
- package/node_modules/zod/v4/locales/no.js +113 -0
- package/node_modules/zod/v4/locales/ota.cjs +141 -0
- package/node_modules/zod/v4/locales/ota.d.cts +5 -0
- package/node_modules/zod/v4/locales/ota.d.ts +4 -0
- package/node_modules/zod/v4/locales/ota.js +114 -0
- package/node_modules/zod/v4/locales/package.json +7 -0
- package/node_modules/zod/v4/locales/pl.cjs +141 -0
- package/node_modules/zod/v4/locales/pl.d.cts +5 -0
- package/node_modules/zod/v4/locales/pl.d.ts +4 -0
- package/node_modules/zod/v4/locales/pl.js +114 -0
- package/node_modules/zod/v4/locales/ps.cjs +146 -0
- package/node_modules/zod/v4/locales/ps.d.cts +5 -0
- package/node_modules/zod/v4/locales/ps.d.ts +4 -0
- package/node_modules/zod/v4/locales/ps.js +119 -0
- package/node_modules/zod/v4/locales/pt-BR.cjs +170 -0
- package/node_modules/zod/v4/locales/pt-BR.d.cts +5 -0
- package/node_modules/zod/v4/locales/pt-BR.d.ts +4 -0
- package/node_modules/zod/v4/locales/pt-BR.js +143 -0
- package/node_modules/zod/v4/locales/pt.cjs +169 -0
- package/node_modules/zod/v4/locales/pt.d.cts +5 -0
- package/node_modules/zod/v4/locales/pt.d.ts +4 -0
- package/node_modules/zod/v4/locales/pt.js +142 -0
- package/node_modules/zod/v4/locales/ro.cjs +149 -0
- package/node_modules/zod/v4/locales/ro.d.cts +5 -0
- package/node_modules/zod/v4/locales/ro.d.ts +4 -0
- package/node_modules/zod/v4/locales/ro.js +122 -0
- package/node_modules/zod/v4/locales/ru.cjs +195 -0
- package/node_modules/zod/v4/locales/ru.d.cts +5 -0
- package/node_modules/zod/v4/locales/ru.d.ts +4 -0
- package/node_modules/zod/v4/locales/ru.js +168 -0
- package/node_modules/zod/v4/locales/sk.cjs +143 -0
- package/node_modules/zod/v4/locales/sk.d.cts +5 -0
- package/node_modules/zod/v4/locales/sk.d.ts +4 -0
- package/node_modules/zod/v4/locales/sk.js +116 -0
- package/node_modules/zod/v4/locales/sl.cjs +141 -0
- package/node_modules/zod/v4/locales/sl.d.cts +5 -0
- package/node_modules/zod/v4/locales/sl.d.ts +4 -0
- package/node_modules/zod/v4/locales/sl.js +114 -0
- package/node_modules/zod/v4/locales/sv.cjs +142 -0
- package/node_modules/zod/v4/locales/sv.d.cts +5 -0
- package/node_modules/zod/v4/locales/sv.d.ts +4 -0
- package/node_modules/zod/v4/locales/sv.js +115 -0
- package/node_modules/zod/v4/locales/ta.cjs +142 -0
- package/node_modules/zod/v4/locales/ta.d.cts +5 -0
- package/node_modules/zod/v4/locales/ta.d.ts +4 -0
- package/node_modules/zod/v4/locales/ta.js +115 -0
- package/node_modules/zod/v4/locales/tg.cjs +144 -0
- package/node_modules/zod/v4/locales/tg.d.cts +5 -0
- package/node_modules/zod/v4/locales/tg.d.ts +4 -0
- package/node_modules/zod/v4/locales/tg.js +117 -0
- package/node_modules/zod/v4/locales/th.cjs +142 -0
- package/node_modules/zod/v4/locales/th.d.cts +5 -0
- package/node_modules/zod/v4/locales/th.d.ts +4 -0
- package/node_modules/zod/v4/locales/th.js +115 -0
- package/node_modules/zod/v4/locales/tk.cjs +134 -0
- package/node_modules/zod/v4/locales/tk.d.cts +5 -0
- package/node_modules/zod/v4/locales/tk.d.ts +4 -0
- package/node_modules/zod/v4/locales/tk.js +107 -0
- package/node_modules/zod/v4/locales/tr.cjs +137 -0
- package/node_modules/zod/v4/locales/tr.d.cts +5 -0
- package/node_modules/zod/v4/locales/tr.d.ts +4 -0
- package/node_modules/zod/v4/locales/tr.js +110 -0
- package/node_modules/zod/v4/locales/ua.cjs +12 -0
- package/node_modules/zod/v4/locales/ua.d.cts +5 -0
- package/node_modules/zod/v4/locales/ua.d.ts +5 -0
- package/node_modules/zod/v4/locales/ua.js +5 -0
- package/node_modules/zod/v4/locales/uk.cjs +140 -0
- package/node_modules/zod/v4/locales/uk.d.cts +5 -0
- package/node_modules/zod/v4/locales/uk.d.ts +4 -0
- package/node_modules/zod/v4/locales/uk.js +113 -0
- package/node_modules/zod/v4/locales/ur.cjs +142 -0
- package/node_modules/zod/v4/locales/ur.d.cts +5 -0
- package/node_modules/zod/v4/locales/ur.d.ts +4 -0
- package/node_modules/zod/v4/locales/ur.js +115 -0
- package/node_modules/zod/v4/locales/uz.cjs +140 -0
- package/node_modules/zod/v4/locales/uz.d.cts +5 -0
- package/node_modules/zod/v4/locales/uz.d.ts +4 -0
- package/node_modules/zod/v4/locales/uz.js +113 -0
- package/node_modules/zod/v4/locales/vi.cjs +140 -0
- package/node_modules/zod/v4/locales/vi.d.cts +5 -0
- package/node_modules/zod/v4/locales/vi.d.ts +4 -0
- package/node_modules/zod/v4/locales/vi.js +113 -0
- package/node_modules/zod/v4/locales/yo.cjs +139 -0
- package/node_modules/zod/v4/locales/yo.d.cts +5 -0
- package/node_modules/zod/v4/locales/yo.d.ts +4 -0
- package/node_modules/zod/v4/locales/yo.js +112 -0
- package/node_modules/zod/v4/locales/zh-CN.cjs +141 -0
- package/node_modules/zod/v4/locales/zh-CN.d.cts +5 -0
- package/node_modules/zod/v4/locales/zh-CN.d.ts +4 -0
- package/node_modules/zod/v4/locales/zh-CN.js +114 -0
- package/node_modules/zod/v4/locales/zh-TW.cjs +139 -0
- package/node_modules/zod/v4/locales/zh-TW.d.cts +5 -0
- package/node_modules/zod/v4/locales/zh-TW.d.ts +4 -0
- package/node_modules/zod/v4/locales/zh-TW.js +112 -0
- package/node_modules/zod/v4/mini/checks.cjs +54 -0
- package/node_modules/zod/v4/mini/checks.d.cts +1 -0
- package/node_modules/zod/v4/mini/checks.d.ts +1 -0
- package/node_modules/zod/v4/mini/checks.js +1 -0
- package/node_modules/zod/v4/mini/coerce.cjs +71 -0
- package/node_modules/zod/v4/mini/coerce.d.cts +7 -0
- package/node_modules/zod/v4/mini/coerce.d.ts +7 -0
- package/node_modules/zod/v4/mini/coerce.js +22 -0
- package/node_modules/zod/v4/mini/deep-partial.cjs +58 -0
- package/node_modules/zod/v4/mini/deep-partial.d.cts +14 -0
- package/node_modules/zod/v4/mini/deep-partial.d.ts +14 -0
- package/node_modules/zod/v4/mini/deep-partial.js +13 -0
- package/node_modules/zod/v4/mini/external.cjs +95 -0
- package/node_modules/zod/v4/mini/external.d.cts +16 -0
- package/node_modules/zod/v4/mini/external.d.ts +16 -0
- package/node_modules/zod/v4/mini/external.js +16 -0
- package/node_modules/zod/v4/mini/in-out.cjs +82 -0
- package/node_modules/zod/v4/mini/in-out.d.cts +8 -0
- package/node_modules/zod/v4/mini/in-out.d.ts +8 -0
- package/node_modules/zod/v4/mini/in-out.js +36 -0
- package/node_modules/zod/v4/mini/index.cjs +51 -0
- package/node_modules/zod/v4/mini/index.d.cts +3 -0
- package/node_modules/zod/v4/mini/index.d.ts +3 -0
- package/node_modules/zod/v4/mini/index.js +3 -0
- package/node_modules/zod/v4/mini/iso.cjs +83 -0
- package/node_modules/zod/v4/mini/iso.d.cts +22 -0
- package/node_modules/zod/v4/mini/iso.d.ts +22 -0
- package/node_modules/zod/v4/mini/iso.js +34 -0
- package/node_modules/zod/v4/mini/package.json +7 -0
- package/node_modules/zod/v4/mini/parse.cjs +37 -0
- package/node_modules/zod/v4/mini/parse.d.cts +1 -0
- package/node_modules/zod/v4/mini/parse.d.ts +1 -0
- package/node_modules/zod/v4/mini/parse.js +1 -0
- package/node_modules/zod/v4/mini/schemas.cjs +1147 -0
- package/node_modules/zod/v4/mini/schemas.d.cts +480 -0
- package/node_modules/zod/v4/mini/schemas.d.ts +480 -0
- package/node_modules/zod/v4/mini/schemas.js +1002 -0
- package/node_modules/zod/v4/package.json +7 -0
- package/node_modules/zod/v4-mini/index.cjs +51 -0
- package/node_modules/zod/v4-mini/index.d.cts +3 -0
- package/node_modules/zod/v4-mini/index.d.ts +3 -0
- package/node_modules/zod/v4-mini/index.js +3 -0
- package/node_modules/zod/v4-mini/package.json +7 -0
- package/package.json +50 -0
|
@@ -0,0 +1,4448 @@
|
|
|
1
|
+
import { cancellableSandboxStartup } from "./startup-cancellation.js";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import fsSync from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { execFile } from "node:child_process";
|
|
7
|
+
import { promisify } from "node:util";
|
|
8
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { adapterExecutionTargetSessionIdentity, describeAdapterExecutionTarget, adapterExecutionTargetDuplexObservabilityRecorder, adapterExecutionTargetEnablesSandboxDuplexBridge, formatAdapterExecutionTimeoutErrorMessage, formatAdapterExecutionTimeoutStartLogLine, prepareAdapterExecutionTargetRuntime, readAdapterExecutionTarget, resolveAdapterExecutionTargetTimeout, resolveReferencedSourceIgnore, runAdapterExecutionTargetShellCommand, startAdapterExecutionTargetPaperclipBridge, startAdapterExecutionTargetProcessSessionBridge, } from "@tickernelz/paperclip-pro-adapter-utils/execution-target";
|
|
11
|
+
import { captureLocalProcess, capturedProcessExited, killCapturedLocalProcess } from "./local-process-control.js";
|
|
12
|
+
import { DUPLEX_CHANNEL_LOST_ERROR_CODE } from "../bridge-transport-contract.js";
|
|
13
|
+
import { classifyWorkspaceRestoreFailure, describeWorkspaceRestoreFailure, } from "../workspace-restore-merge.js";
|
|
14
|
+
import { DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE, DEFAULT_PAPERCLIP_CONVERSATION_PROMPT_TEMPLATE, applyPaperclipWorkspaceEnv, asNumber, asString, buildInvocationEnvForLogs, buildPaperclipEnv, ensureAbsoluteDirectory, ensurePathInEnv, ensurePaperclipSkillSymlink, isForbiddenConfigEnvKey, isPaperclipExternalChatTurn, isPaperclipRuntimeEnvKey, joinPromptSections, materializePaperclipSkillCopy, parseObject, isPaperclipSkillSourceMissing, readPaperclipRuntimeSkillEntries, readPaperclipIssueWorkModeFromContext, renderPaperclipWakePrompt, renderTemplate, resolvePaperclipInstanceRootForAdapter, selectPaperclipTaskMarkdown, resolveLegacyPaperclipDesiredSkillNames, removeMaintainerOnlySkillSymlinks, rewriteWorkspaceCwdEnvVarsForExecution, shapePaperclipWorkspaceEnvForExecution, } from "@tickernelz/paperclip-pro-adapter-utils/server-utils";
|
|
15
|
+
import { shellQuote } from "@tickernelz/paperclip-pro-adapter-utils/ssh";
|
|
16
|
+
import { createAcpRuntime, createAgentRegistry, createRuntimeStore, isAcpRuntimeError, } from "acpx/runtime";
|
|
17
|
+
import { ACPX_DUPLEX_LOSS_CANCEL_DEADLINE_MS, ACPX_HANDSHAKE_TIMEOUT_MS, ACPX_HANDSHAKE_TRANSPORT_POLL_MS, DEFAULT_ACP_ENGINE_AGENT, DEFAULT_ACP_ENGINE_MODE, DEFAULT_ACP_ENGINE_NON_INTERACTIVE_PERMISSIONS, DEFAULT_ACP_ENGINE_PERMISSION_MODE, DEFAULT_ACP_ENGINE_TIMEOUT_SEC, DEFAULT_ACP_ENGINE_WARM_HANDLE_IDLE_MS, } from "./constants.js";
|
|
18
|
+
import { createRunResourceLedger } from "./run-resource-ledger.js";
|
|
19
|
+
import { settleAcpRun } from "./settlement-sequence.js";
|
|
20
|
+
import { runAttempt, } from "./run-coordinator.js";
|
|
21
|
+
import { runTurn as runTurnSequence, } from "./turn-sequence.js";
|
|
22
|
+
import { createHostRunSite, } from "./run-site-host.js";
|
|
23
|
+
import { createSandboxRunSite } from "./run-site-sandbox.js";
|
|
24
|
+
import { createRuntimeSpanRunner, emitRunPhaseTiming, emitSkippedStartupStep, getActiveStepContext, measureStartupStep, NOOP_STARTUP_SPAN, NOOP_STARTUP_TRACE_CONTEXT, runWithRuntimeParent, setSandboxRootSpanAttributes, } from "./startup-timing.js";
|
|
25
|
+
const defaultModuleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
const PAPERCLIP_MANAGED_CODEX_SKILLS_MANIFEST = ".paperclip-managed-skills.json";
|
|
27
|
+
const BENIGN_NES_CLOSE_STDERR = /method: ['"]nes\/close['"].*-32601/;
|
|
28
|
+
function routeChildStderr(state, chunk) {
|
|
29
|
+
if (state.logPath) {
|
|
30
|
+
fsSync.mkdirSync(path.dirname(state.logPath), { recursive: true });
|
|
31
|
+
fsSync.appendFileSync(state.logPath, chunk);
|
|
32
|
+
}
|
|
33
|
+
const combined = state.pendingLiveLine + chunk;
|
|
34
|
+
const lastNewline = combined.lastIndexOf("\n");
|
|
35
|
+
if (lastNewline < 0) {
|
|
36
|
+
state.pendingLiveLine = combined;
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const complete = combined.slice(0, lastNewline + 1);
|
|
40
|
+
state.pendingLiveLine = combined.slice(lastNewline + 1);
|
|
41
|
+
const filtered = complete
|
|
42
|
+
.split(/(?<=\n)/)
|
|
43
|
+
.filter((line) => !BENIGN_NES_CLOSE_STDERR.test(line))
|
|
44
|
+
.join("");
|
|
45
|
+
if (filtered)
|
|
46
|
+
process.stderr.write(filtered);
|
|
47
|
+
}
|
|
48
|
+
function flushChildStderr(state) {
|
|
49
|
+
if (state.pendingLiveLine && !BENIGN_NES_CLOSE_STDERR.test(state.pendingLiveLine)) {
|
|
50
|
+
process.stderr.write(state.pendingLiveLine);
|
|
51
|
+
}
|
|
52
|
+
state.pendingLiveLine = "";
|
|
53
|
+
}
|
|
54
|
+
const defaultWarmHandles = new Map();
|
|
55
|
+
const defaultStagedRuntimes = new Map();
|
|
56
|
+
const defaultStagingLocks = new Map();
|
|
57
|
+
function resolveEngineSettings(options) {
|
|
58
|
+
const moduleDir = path.resolve(options.moduleDir ?? defaultModuleDir);
|
|
59
|
+
return {
|
|
60
|
+
adapterType: options.adapterType?.trim() || "acp_engine",
|
|
61
|
+
moduleDir,
|
|
62
|
+
packageRootDir: path.resolve(options.packageRootDir ?? path.resolve(moduleDir, "../..")),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function stableJson(value) {
|
|
66
|
+
if (Array.isArray(value))
|
|
67
|
+
return `[${value.map(stableJson).join(",")}]`;
|
|
68
|
+
if (value && typeof value === "object") {
|
|
69
|
+
return `{${Object.entries(value)
|
|
70
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
71
|
+
.map(([key, entry]) => `${JSON.stringify(key)}:${stableJson(entry)}`)
|
|
72
|
+
.join(",")}}`;
|
|
73
|
+
}
|
|
74
|
+
return JSON.stringify(value);
|
|
75
|
+
}
|
|
76
|
+
function shortHash(value) {
|
|
77
|
+
return createHash("sha256").update(stableJson(value)).digest("hex").slice(0, 16);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Hash the session fingerprint. The builder accepts only a
|
|
81
|
+
* `SessionFingerprintIdentity`, so no company, agent, or task identifier can
|
|
82
|
+
* enter the hash. Those identifiers scope the outer session key only (see
|
|
83
|
+
* `SessionKeyIdentity`).
|
|
84
|
+
*/
|
|
85
|
+
export function buildSessionFingerprint(identity) {
|
|
86
|
+
return shortHash(identity);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Build the session key from the fingerprint and the outer-key identity. The key
|
|
90
|
+
* form is `paperclip:companyId:agentId:taskKey:fingerprint`.
|
|
91
|
+
*/
|
|
92
|
+
export function buildSessionKey(identity, fingerprint) {
|
|
93
|
+
return `paperclip:${identity.companyId}:${identity.agentId}:${identity.taskKey}:${fingerprint}`;
|
|
94
|
+
}
|
|
95
|
+
// ACPX runs inside the long-lived Paperclip server process. A local child needs
|
|
96
|
+
// a small amount of host context (PATH, locale, certificate/proxy settings, and
|
|
97
|
+
// provider authentication), but it must not inherit the server's complete
|
|
98
|
+
// environment. A runner-backed remote sandbox inherits no ambient host context
|
|
99
|
+
// at all. In particular, native-runner bootstrap and MCP credentials are host
|
|
100
|
+
// authority, not provider credentials.
|
|
101
|
+
const ACPX_INHERITED_HOST_ENV_KEYS = new Set([
|
|
102
|
+
"PATH",
|
|
103
|
+
"PATHEXT",
|
|
104
|
+
"SYSTEMROOT",
|
|
105
|
+
"WINDIR",
|
|
106
|
+
"COMSPEC",
|
|
107
|
+
"HOME",
|
|
108
|
+
"USERPROFILE",
|
|
109
|
+
"HOMEDRIVE",
|
|
110
|
+
"HOMEPATH",
|
|
111
|
+
"USER",
|
|
112
|
+
"USERNAME",
|
|
113
|
+
"LOGNAME",
|
|
114
|
+
"SHELL",
|
|
115
|
+
"LANG",
|
|
116
|
+
"LANGUAGE",
|
|
117
|
+
"TZ",
|
|
118
|
+
"TMPDIR",
|
|
119
|
+
"TEMP",
|
|
120
|
+
"TMP",
|
|
121
|
+
"XDG_CONFIG_HOME",
|
|
122
|
+
"XDG_CACHE_HOME",
|
|
123
|
+
"XDG_DATA_HOME",
|
|
124
|
+
"SSL_CERT_FILE",
|
|
125
|
+
"SSL_CERT_DIR",
|
|
126
|
+
"NODE_EXTRA_CA_CERTS",
|
|
127
|
+
"HTTP_PROXY",
|
|
128
|
+
"HTTPS_PROXY",
|
|
129
|
+
"NO_PROXY",
|
|
130
|
+
"ALL_PROXY",
|
|
131
|
+
]);
|
|
132
|
+
const ACPX_INHERITED_PROVIDER_ENV_KEYS = {
|
|
133
|
+
codex: new Set([
|
|
134
|
+
"OPENAI_API_KEY",
|
|
135
|
+
"CODEX_API_KEY",
|
|
136
|
+
]),
|
|
137
|
+
claude: new Set([
|
|
138
|
+
"ANTHROPIC_API_KEY",
|
|
139
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
140
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
141
|
+
"ANTHROPIC_BASE_URL",
|
|
142
|
+
"ANTHROPIC_MODEL",
|
|
143
|
+
"ANTHROPIC_SMALL_FAST_MODEL",
|
|
144
|
+
"CLAUDE_CONFIG_DIR",
|
|
145
|
+
"CLAUDE_CODE_USE_BEDROCK",
|
|
146
|
+
"ANTHROPIC_BEDROCK_BASE_URL",
|
|
147
|
+
"AWS_BEARER_TOKEN_BEDROCK",
|
|
148
|
+
"AWS_ACCESS_KEY_ID",
|
|
149
|
+
"AWS_SECRET_ACCESS_KEY",
|
|
150
|
+
"AWS_SESSION_TOKEN",
|
|
151
|
+
"AWS_REGION",
|
|
152
|
+
"AWS_DEFAULT_REGION",
|
|
153
|
+
"AWS_PROFILE",
|
|
154
|
+
"AWS_CONFIG_FILE",
|
|
155
|
+
"AWS_SHARED_CREDENTIALS_FILE",
|
|
156
|
+
]),
|
|
157
|
+
pi: new Set(["OPENROUTER_API_KEY"]),
|
|
158
|
+
gemini: new Set([
|
|
159
|
+
"GEMINI_API_KEY",
|
|
160
|
+
"GOOGLE_API_KEY",
|
|
161
|
+
"GOOGLE_APPLICATION_CREDENTIALS",
|
|
162
|
+
"GOOGLE_GENAI_USE_GCA",
|
|
163
|
+
]),
|
|
164
|
+
kimi: new Set([
|
|
165
|
+
"KIMI_API_KEY",
|
|
166
|
+
"MOONSHOT_API_KEY",
|
|
167
|
+
"KIMI_MODEL_NAME",
|
|
168
|
+
"KIMI_MODEL_API_KEY",
|
|
169
|
+
"KIMI_MODEL_BASE_URL",
|
|
170
|
+
"KIMI_MODEL_PROVIDER_TYPE",
|
|
171
|
+
"KIMI_CODE_HOME",
|
|
172
|
+
]),
|
|
173
|
+
grok: new Set(["XAI_API_KEY"]),
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* Project the server environment onto the closed set a host ACPX provider may
|
|
177
|
+
* inherit. A runner-backed remote sandbox passes `false` and projects nothing.
|
|
178
|
+
* Explicit adapter/runtime env is merged later and is intentionally not
|
|
179
|
+
* restricted by this host projection.
|
|
180
|
+
*/
|
|
181
|
+
export function projectAcpxInheritedHostEnvironment(inheritedEnv, acpxAgent, inheritHostEnvironment) {
|
|
182
|
+
// A runner-backed remote sandbox crosses a serialization boundary. Ambient
|
|
183
|
+
// server state is never part of that contract: provider auth/config must be
|
|
184
|
+
// supplied through adapter config, resolved runtime env, or a contribution.
|
|
185
|
+
if (!inheritHostEnvironment)
|
|
186
|
+
return {};
|
|
187
|
+
const providerKeys = ACPX_INHERITED_PROVIDER_ENV_KEYS[acpxAgent];
|
|
188
|
+
const projected = {};
|
|
189
|
+
for (const [key, value] of Object.entries(inheritedEnv)) {
|
|
190
|
+
if (typeof value !== "string")
|
|
191
|
+
continue;
|
|
192
|
+
const normalizedKey = key.toUpperCase();
|
|
193
|
+
const allowed = ACPX_INHERITED_HOST_ENV_KEYS.has(normalizedKey) ||
|
|
194
|
+
/^LC_[A-Z0-9_]{1,32}$/.test(normalizedKey) ||
|
|
195
|
+
providerKeys?.has(normalizedKey) === true;
|
|
196
|
+
if (allowed)
|
|
197
|
+
projected[key] = value;
|
|
198
|
+
}
|
|
199
|
+
return projected;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Build the single branded launch environment for a run. This is the sole
|
|
203
|
+
* constructor of `LaunchEnvironment`. It applies each contribution into the base
|
|
204
|
+
* env in registration order, then resolves and freezes the launch env.
|
|
205
|
+
*
|
|
206
|
+
* A contribution carries its credential scope. A run-scoped contribution (the
|
|
207
|
+
* run API key, a bridge token) and a session-scoped contribution (the Codex auth
|
|
208
|
+
* copy-back material) both merge here. Neither scope can escape into a reuse
|
|
209
|
+
* payload, because the branded contribution type and the branded environment
|
|
210
|
+
* type forbid it.
|
|
211
|
+
*/
|
|
212
|
+
export function finalizeLaunchEnvironment(baseEnv, contributions, options) {
|
|
213
|
+
for (const contribution of contributions) {
|
|
214
|
+
Object.assign(baseEnv, contribution.env);
|
|
215
|
+
}
|
|
216
|
+
const env = Object.freeze(resolveRuntimeEnv(baseEnv, options.acpxAgent, options));
|
|
217
|
+
return { env };
|
|
218
|
+
}
|
|
219
|
+
// Directory names the staging path never ships for a referenced project (heavy
|
|
220
|
+
// build/cache output and git history), applied regardless of the project's
|
|
221
|
+
// ignore resolution. The content signature skips them so it reflects only the
|
|
222
|
+
// staged tree and never reads their bytes. Keep this set equal to the fixed
|
|
223
|
+
// excludes the sandbox and SSH runtimes always apply. A project's OWN resolved
|
|
224
|
+
// Git-ignored paths (see `resolveReferencedSourceIgnore`) are matched
|
|
225
|
+
// separately, by relative path, inside `referencedSourceContentSignature` — that
|
|
226
|
+
// is the real invariant now: the signature and both staging lanes must consume
|
|
227
|
+
// the SAME one resolution per project, not just this fixed name list.
|
|
228
|
+
const REFERENCED_SOURCE_SIGNATURE_SKIP_DIRS = new Set([
|
|
229
|
+
"node_modules",
|
|
230
|
+
"vendor",
|
|
231
|
+
"dist",
|
|
232
|
+
"build",
|
|
233
|
+
"out",
|
|
234
|
+
"coverage",
|
|
235
|
+
".next",
|
|
236
|
+
".turbo",
|
|
237
|
+
".cache",
|
|
238
|
+
".git",
|
|
239
|
+
]);
|
|
240
|
+
/**
|
|
241
|
+
* Content signature of a referenced-project host tree for the session fingerprint.
|
|
242
|
+
*
|
|
243
|
+
* The staged-runtime cache reuses an already-staged referenced-project tree on a
|
|
244
|
+
* compatible resume and does not re-sync it. Referenced-project metadata (id, host
|
|
245
|
+
* path, workspace id, repo url, pinned ref) can stay identical while the files at
|
|
246
|
+
* that host path change: a branch moved to a new commit, a re-checkout in place, or
|
|
247
|
+
* a dirty worktree. So the metadata identity alone lets a resume serve a stale tree.
|
|
248
|
+
* This signature folds the tree's own content state into the identity.
|
|
249
|
+
*
|
|
250
|
+
* The walk reads each file's relative path and bytes and folds them into the hash.
|
|
251
|
+
* It reads bytes, not only file stats. A stat-only signature (size and modification
|
|
252
|
+
* time) collides when an edit keeps the byte length and the modification time — a
|
|
253
|
+
* re-checkout that restores the same size and timestamp. The byte hash busts on any
|
|
254
|
+
* content change, so the fingerprint busts and the next launch stages the current
|
|
255
|
+
* tree. The walk skips the heavy build, cache, and git directories the staging path
|
|
256
|
+
* never ships, plus the project's own resolved Git-ignored paths, and records a
|
|
257
|
+
* symlink by its target text without following it. On a read error the function
|
|
258
|
+
* returns a stable marker, so the fingerprint does not churn while staging
|
|
259
|
+
* surfaces the real error. The walk runs only when the run carries referenced
|
|
260
|
+
* projects (the multi-project sync path).
|
|
261
|
+
*
|
|
262
|
+
* `ignoreResolution` is the ONE resolution `resolveReferencedSourceIgnore`
|
|
263
|
+
* computed for this project — the same one the sandbox lane and the SSH lane
|
|
264
|
+
* consume. A `failed` resolution skips the walk entirely and returns a stable
|
|
265
|
+
* marker instead, because a failed project is not staged and its bytes are not
|
|
266
|
+
* read anywhere.
|
|
267
|
+
*/
|
|
268
|
+
export async function referencedSourceContentSignature(localPath, ignoreResolution) {
|
|
269
|
+
if (ignoreResolution.kind === "failed") {
|
|
270
|
+
return `unreadable:${ignoreResolution.reason}`;
|
|
271
|
+
}
|
|
272
|
+
const isIgnoredByGitResolution = (relativePath) => ignoreResolution.kind === "git" &&
|
|
273
|
+
ignoreResolution.ignoredPaths.some((entry) => relativePath === entry || relativePath.startsWith(`${entry}/`));
|
|
274
|
+
const hash = createHash("sha256");
|
|
275
|
+
const walk = async (relative) => {
|
|
276
|
+
const current = relative ? path.join(localPath, relative) : localPath;
|
|
277
|
+
const dirents = await fs.readdir(current, { withFileTypes: true });
|
|
278
|
+
dirents.sort((left, right) => (left.name < right.name ? -1 : left.name > right.name ? 1 : 0));
|
|
279
|
+
for (const dirent of dirents) {
|
|
280
|
+
const next = relative ? path.posix.join(relative, dirent.name) : dirent.name;
|
|
281
|
+
if (isIgnoredByGitResolution(next)) {
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (dirent.isDirectory()) {
|
|
285
|
+
if (REFERENCED_SOURCE_SIGNATURE_SKIP_DIRS.has(dirent.name)) {
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
await walk(next);
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
const absolute = path.join(localPath, next);
|
|
292
|
+
const stats = await fs.lstat(absolute);
|
|
293
|
+
if (stats.isSymbolicLink()) {
|
|
294
|
+
const target = await fs.readlink(absolute);
|
|
295
|
+
hash.update(`symlink:${next}:${target}\n`);
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
if (!stats.isFile()) {
|
|
299
|
+
hash.update(`other:${next}:${stats.mode}\n`);
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
hash.update(`file:${next}:${stats.size}\n`);
|
|
303
|
+
hash.update(await fs.readFile(absolute));
|
|
304
|
+
hash.update("\n");
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
try {
|
|
308
|
+
await walk("");
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
return `unreadable:${String(error)}`;
|
|
312
|
+
}
|
|
313
|
+
return hash.digest("hex").slice(0, 16);
|
|
314
|
+
}
|
|
315
|
+
function defaultPaperclipInstanceDir() {
|
|
316
|
+
const home = process.env.PAPERCLIP_HOME?.trim() || path.join(os.homedir(), ".paperclip-pro");
|
|
317
|
+
const instanceId = process.env.PAPERCLIP_INSTANCE_ID?.trim() || "default";
|
|
318
|
+
return resolvePaperclipInstanceRootForAdapter({
|
|
319
|
+
homeDir: home,
|
|
320
|
+
instanceId,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
function defaultStateDir(companyId, agentId) {
|
|
324
|
+
return path.join(defaultPaperclipInstanceDir(), "companies", companyId, "acp-engine", "agents", agentId);
|
|
325
|
+
}
|
|
326
|
+
function resolveManagedCodexHomeDir(companyId) {
|
|
327
|
+
return path.join(defaultPaperclipInstanceDir(), "companies", companyId, "codex-home");
|
|
328
|
+
}
|
|
329
|
+
// Mirrors `resolveManagedGrokHomeDir` in
|
|
330
|
+
// `packages/adapters/grok-local/src/server/grok-home.ts` — this package
|
|
331
|
+
// cannot import that adapter package (it would invert the dependency
|
|
332
|
+
// direction), so the path scheme is duplicated here, the same way
|
|
333
|
+
// `resolveManagedCodexHomeDir` above duplicates the Codex adapter's own
|
|
334
|
+
// helper.
|
|
335
|
+
function resolveManagedGrokHomeDir(companyId) {
|
|
336
|
+
return path.join(defaultPaperclipInstanceDir(), "companies", companyId, "grok-home");
|
|
337
|
+
}
|
|
338
|
+
// Walk up from startDir looking for `node_modules/.bin/<binName>`. This matches
|
|
339
|
+
// npm/pnpm binary hoisting in packaged installs while preserving monorepo dev.
|
|
340
|
+
export async function findAncestorBin(startDir, binName) {
|
|
341
|
+
let current = path.resolve(startDir);
|
|
342
|
+
while (true) {
|
|
343
|
+
const binDir = path.join(current, "node_modules", ".bin");
|
|
344
|
+
const candidates = process.platform === "win32"
|
|
345
|
+
? [path.join(binDir, `${binName}.cmd`), path.join(binDir, binName)]
|
|
346
|
+
: [path.join(binDir, binName)];
|
|
347
|
+
for (const candidate of candidates) {
|
|
348
|
+
if (await pathExists(candidate))
|
|
349
|
+
return candidate;
|
|
350
|
+
}
|
|
351
|
+
const parent = path.dirname(current);
|
|
352
|
+
if (parent === current)
|
|
353
|
+
return null;
|
|
354
|
+
current = parent;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
async function resolveBuiltInAgentCommand(input) {
|
|
358
|
+
const { agent, packageRootDir, executionTargetIsRemote } = input;
|
|
359
|
+
if (agent === "gemini") {
|
|
360
|
+
return { command: "gemini --acp", shellCommand: "gemini --acp" };
|
|
361
|
+
}
|
|
362
|
+
if (agent === "kimi") {
|
|
363
|
+
// Kimi Code exposes its ACP server via the `kimi acp` subcommand (stdio),
|
|
364
|
+
// rather than a flag (gemini) or a dedicated bin (claude/codex).
|
|
365
|
+
return { command: "kimi acp", shellCommand: "kimi acp" };
|
|
366
|
+
}
|
|
367
|
+
const binName = agent === "claude" ? "claude-agent-acp" : agent === "codex" ? "codex-acp" : null;
|
|
368
|
+
if (!binName)
|
|
369
|
+
return null;
|
|
370
|
+
if (executionTargetIsRemote) {
|
|
371
|
+
return { command: binName, shellCommand: binName };
|
|
372
|
+
}
|
|
373
|
+
const resolved = (await findAncestorBin(packageRootDir, binName)) ?? binName;
|
|
374
|
+
return { command: resolved, shellCommand: shellQuote(resolved) };
|
|
375
|
+
}
|
|
376
|
+
const execFileAsync = promisify(execFile);
|
|
377
|
+
// Gemini CLI renamed --experimental-acp to --acp in 0.33.0. acpx normally
|
|
378
|
+
// rewrites the flag itself, but the agent wrapper script hides the gemini
|
|
379
|
+
// command from acpx's detection, so the engine must downgrade it here.
|
|
380
|
+
const GEMINI_NATIVE_ACP_FLAG_MIN_VERSION = [0, 33, 0];
|
|
381
|
+
const GEMINI_VERSION_PROBE_TIMEOUT_MS = 2000;
|
|
382
|
+
export function parseGeminiVersionParts(output) {
|
|
383
|
+
const match = output?.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
384
|
+
if (!match)
|
|
385
|
+
return null;
|
|
386
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
387
|
+
}
|
|
388
|
+
export function geminiVersionSupportsNativeAcpFlag(parts) {
|
|
389
|
+
if (!parts)
|
|
390
|
+
return true;
|
|
391
|
+
for (let index = 0; index < GEMINI_NATIVE_ACP_FLAG_MIN_VERSION.length; index += 1) {
|
|
392
|
+
const diff = (parts[index] ?? 0) - GEMINI_NATIVE_ACP_FLAG_MIN_VERSION[index];
|
|
393
|
+
if (diff !== 0)
|
|
394
|
+
return diff > 0;
|
|
395
|
+
}
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
export function rewriteGeminiAcpFlagForVersion(commandShell, versionParts) {
|
|
399
|
+
if (geminiVersionSupportsNativeAcpFlag(versionParts))
|
|
400
|
+
return commandShell;
|
|
401
|
+
return commandShell
|
|
402
|
+
.trim()
|
|
403
|
+
.split(/\s+/)
|
|
404
|
+
.map((token) => (token === "--acp" ? "--experimental-acp" : token))
|
|
405
|
+
.join(" ");
|
|
406
|
+
}
|
|
407
|
+
function geminiAcpCommandTokens(commandShell) {
|
|
408
|
+
const tokens = commandShell.trim().split(/\s+/);
|
|
409
|
+
const bin = tokens[0];
|
|
410
|
+
if (!bin || bin.startsWith("'") || bin.startsWith('"'))
|
|
411
|
+
return null;
|
|
412
|
+
if (path.basename(bin) !== "gemini")
|
|
413
|
+
return null;
|
|
414
|
+
if (!tokens.includes("--acp"))
|
|
415
|
+
return null;
|
|
416
|
+
return tokens;
|
|
417
|
+
}
|
|
418
|
+
async function normalizeGeminiAcpCommandShell(commandShell, env) {
|
|
419
|
+
const tokens = geminiAcpCommandTokens(commandShell);
|
|
420
|
+
if (!tokens)
|
|
421
|
+
return commandShell;
|
|
422
|
+
let versionParts = null;
|
|
423
|
+
try {
|
|
424
|
+
const { stdout } = await execFileAsync(tokens[0], ["--version"], {
|
|
425
|
+
timeout: GEMINI_VERSION_PROBE_TIMEOUT_MS,
|
|
426
|
+
encoding: "utf8",
|
|
427
|
+
env,
|
|
428
|
+
});
|
|
429
|
+
versionParts = parseGeminiVersionParts(stdout);
|
|
430
|
+
}
|
|
431
|
+
catch {
|
|
432
|
+
return commandShell;
|
|
433
|
+
}
|
|
434
|
+
return rewriteGeminiAcpFlagForVersion(commandShell, versionParts);
|
|
435
|
+
}
|
|
436
|
+
function normalizeAgent(config) {
|
|
437
|
+
const agent = asString(config.agent, DEFAULT_ACP_ENGINE_AGENT).trim();
|
|
438
|
+
return agent || DEFAULT_ACP_ENGINE_AGENT;
|
|
439
|
+
}
|
|
440
|
+
async function pathExists(candidate) {
|
|
441
|
+
return fs.access(candidate).then(() => true).catch(() => false);
|
|
442
|
+
}
|
|
443
|
+
async function ensureParentDir(target) {
|
|
444
|
+
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
445
|
+
}
|
|
446
|
+
async function writeFileAtomically(input) {
|
|
447
|
+
await ensureParentDir(input.target);
|
|
448
|
+
const tempPath = `${input.target}.tmp-${process.pid}-${randomUUID()}`;
|
|
449
|
+
const handle = await fs.open(tempPath, "wx", input.mode);
|
|
450
|
+
try {
|
|
451
|
+
await handle.writeFile(input.contents, "utf8");
|
|
452
|
+
await handle.close();
|
|
453
|
+
await fs.rename(tempPath, input.target);
|
|
454
|
+
await fs.chmod(input.target, input.mode).catch(() => { });
|
|
455
|
+
}
|
|
456
|
+
catch (err) {
|
|
457
|
+
await handle.close().catch(() => { });
|
|
458
|
+
await fs.rm(tempPath, { force: true }).catch(() => { });
|
|
459
|
+
throw err;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
async function ensureSymlink(target, source) {
|
|
463
|
+
const resolvedSource = path.resolve(source);
|
|
464
|
+
const existing = await fs.lstat(target).catch(() => null);
|
|
465
|
+
if (!existing) {
|
|
466
|
+
await ensureParentDir(target);
|
|
467
|
+
await symlinkOrCopyFile(resolvedSource, target);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
if (!existing.isSymbolicLink()) {
|
|
471
|
+
await fs.rm(target, { recursive: true, force: true });
|
|
472
|
+
await symlinkOrCopyFile(resolvedSource, target);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
const linkedPath = await fs.readlink(target).catch(() => null);
|
|
476
|
+
if (!linkedPath)
|
|
477
|
+
return;
|
|
478
|
+
const resolvedLinkedPath = path.resolve(path.dirname(target), linkedPath);
|
|
479
|
+
if (resolvedLinkedPath === resolvedSource)
|
|
480
|
+
return;
|
|
481
|
+
await fs.unlink(target);
|
|
482
|
+
await symlinkOrCopyFile(resolvedSource, target);
|
|
483
|
+
}
|
|
484
|
+
async function symlinkOrCopyFile(source, target) {
|
|
485
|
+
try {
|
|
486
|
+
await fs.symlink(source, target);
|
|
487
|
+
}
|
|
488
|
+
catch (err) {
|
|
489
|
+
if (!isErrnoException(err, "EPERM"))
|
|
490
|
+
throw err;
|
|
491
|
+
await fs.copyFile(source, target);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
function isErrnoException(err, code) {
|
|
495
|
+
return err instanceof Error && "code" in err && err.code === code;
|
|
496
|
+
}
|
|
497
|
+
async function ensureCopiedFile(target, source) {
|
|
498
|
+
if (await pathExists(target))
|
|
499
|
+
return;
|
|
500
|
+
await ensureParentDir(target);
|
|
501
|
+
await fs.copyFile(source, target);
|
|
502
|
+
}
|
|
503
|
+
async function prepareManagedCodexHome(input) {
|
|
504
|
+
const { sourceHome, targetHome, onLog } = input;
|
|
505
|
+
if (path.resolve(sourceHome) === path.resolve(targetHome))
|
|
506
|
+
return targetHome;
|
|
507
|
+
await fs.mkdir(targetHome, { recursive: true });
|
|
508
|
+
const authJson = path.join(sourceHome, "auth.json");
|
|
509
|
+
if (await pathExists(authJson))
|
|
510
|
+
await ensureSymlink(path.join(targetHome, "auth.json"), authJson);
|
|
511
|
+
for (const name of ["config.json", "config.toml", "instructions.md"]) {
|
|
512
|
+
const source = path.join(sourceHome, name);
|
|
513
|
+
if (await pathExists(source))
|
|
514
|
+
await ensureCopiedFile(path.join(targetHome, name), source);
|
|
515
|
+
}
|
|
516
|
+
await onLog("stdout", `[paperclip] Using Paperclip-managed ACPX Codex home "${targetHome}" (seeded from "${sourceHome}").\n`);
|
|
517
|
+
return targetHome;
|
|
518
|
+
}
|
|
519
|
+
async function hashPathContents(candidate, hash, relativePath, seenDirectories) {
|
|
520
|
+
const stat = await fs.lstat(candidate);
|
|
521
|
+
if (stat.isSymbolicLink()) {
|
|
522
|
+
hash.update(`symlink-skipped:${relativePath}\n`);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
if (stat.isDirectory()) {
|
|
526
|
+
const realDir = await fs.realpath(candidate).catch(() => candidate);
|
|
527
|
+
hash.update(`dir:${relativePath}\n`);
|
|
528
|
+
if (seenDirectories.has(realDir)) {
|
|
529
|
+
hash.update("loop\n");
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
seenDirectories.add(realDir);
|
|
533
|
+
const entries = await fs.readdir(candidate, { withFileTypes: true });
|
|
534
|
+
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
535
|
+
for (const entry of entries) {
|
|
536
|
+
const childRelativePath = relativePath.length > 0 ? `${relativePath}/${entry.name}` : entry.name;
|
|
537
|
+
await hashPathContents(path.join(candidate, entry.name), hash, childRelativePath, seenDirectories);
|
|
538
|
+
}
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
if (stat.isFile()) {
|
|
542
|
+
hash.update(`file:${relativePath}\n`);
|
|
543
|
+
hash.update(await fs.readFile(candidate));
|
|
544
|
+
hash.update("\n");
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
hash.update(`other:${relativePath}:${stat.mode}\n`);
|
|
548
|
+
}
|
|
549
|
+
async function buildSkillSetKey(input) {
|
|
550
|
+
const hash = createHash("sha256");
|
|
551
|
+
hash.update(`paperclip-acpx-${input.label}-skills:v1\n`);
|
|
552
|
+
const sorted = [...input.skills].sort((left, right) => left.runtimeName.localeCompare(right.runtimeName));
|
|
553
|
+
for (const entry of sorted) {
|
|
554
|
+
hash.update(`skill:${entry.key}:${entry.runtimeName}\n`);
|
|
555
|
+
await hashPathContents(entry.source, hash, entry.runtimeName, new Set());
|
|
556
|
+
}
|
|
557
|
+
return hash.digest("hex");
|
|
558
|
+
}
|
|
559
|
+
async function resolveSelectedRuntimeSkills(config, moduleDir) {
|
|
560
|
+
const allSkills = await readPaperclipRuntimeSkillEntries(config, moduleDir);
|
|
561
|
+
const desiredSkillNames = resolveLegacyPaperclipDesiredSkillNames(config, allSkills);
|
|
562
|
+
const desiredSet = new Set(desiredSkillNames);
|
|
563
|
+
return {
|
|
564
|
+
allSkills,
|
|
565
|
+
// Missing-source entries never mount: buildSkillSetKey hashes each
|
|
566
|
+
// selected entry's path contents, and a nonexistent source would abort
|
|
567
|
+
// runtime construction over one broken skill.
|
|
568
|
+
selectedSkills: allSkills.filter((entry) => desiredSet.has(entry.key) && !isPaperclipSkillSourceMissing(entry)),
|
|
569
|
+
desiredSkillNames,
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
async function prepareClaudeSkillRuntime(input) {
|
|
573
|
+
const { allSkills, selectedSkills, desiredSkillNames } = await resolveSelectedRuntimeSkills(input.config, input.moduleDir);
|
|
574
|
+
const skillSetKey = await buildSkillSetKey({ skills: selectedSkills, label: "claude" });
|
|
575
|
+
const bundleRoot = path.join(input.stateDir, "runtime-skills", "claude", skillSetKey);
|
|
576
|
+
const skillsHome = path.join(bundleRoot, ".claude", "skills");
|
|
577
|
+
await fs.mkdir(skillsHome, { recursive: true });
|
|
578
|
+
// A failed materialization, or a materialized copy with no usable
|
|
579
|
+
// `SKILL.md`, must drop the skill from every advertised list below.
|
|
580
|
+
// Otherwise the prompt and the session identity still name a skill whose
|
|
581
|
+
// `SKILL.md` is not in `skillsHome` — either the whole copy failed, or the
|
|
582
|
+
// copy skipped a symlinked `SKILL.md` — and the agent's read of that file
|
|
583
|
+
// fails with a missing-file error, the same symptom this bundle exists to
|
|
584
|
+
// fix.
|
|
585
|
+
const materializedNames = [];
|
|
586
|
+
for (const entry of selectedSkills) {
|
|
587
|
+
const target = path.join(skillsHome, entry.runtimeName);
|
|
588
|
+
try {
|
|
589
|
+
const result = await materializePaperclipSkillCopy(entry.source, target);
|
|
590
|
+
const skillMdStat = await fs.stat(path.join(target, "SKILL.md")).catch(() => null);
|
|
591
|
+
if (!skillMdStat?.isFile()) {
|
|
592
|
+
await fs.rm(target, { recursive: true, force: true });
|
|
593
|
+
await input.onLog("stderr", `[paperclip] Skipped ACPX Claude skill "${entry.key}": the staged copy at ${target} has no usable SKILL.md.\n`);
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
materializedNames.push(entry.runtimeName);
|
|
597
|
+
if (result.skippedSymlinks.length > 0) {
|
|
598
|
+
await input.onLog("stdout", `[paperclip] Materialized ACPX Claude skill "${entry.runtimeName}" into ${skillsHome} and skipped ${result.skippedSymlinks.length} symlink(s).\n`);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
catch (err) {
|
|
602
|
+
await input.onLog("stderr", `[paperclip] Failed to materialize ACPX Claude skill "${entry.key}" into ${skillsHome}: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
const selectedNames = materializedNames.sort();
|
|
606
|
+
const promptInstructions = selectedNames.length > 0
|
|
607
|
+
? [
|
|
608
|
+
"Paperclip has materialized selected runtime skills for this ACPX Claude session.",
|
|
609
|
+
`Skill root: ${skillsHome}`,
|
|
610
|
+
`Selected skills: ${selectedNames.join(", ")}`,
|
|
611
|
+
"When a task calls for one of these skills, read its SKILL.md from that root and follow it.",
|
|
612
|
+
].join("\n")
|
|
613
|
+
: "";
|
|
614
|
+
return {
|
|
615
|
+
identity: {
|
|
616
|
+
mode: "claude",
|
|
617
|
+
skillSetKey,
|
|
618
|
+
desiredSkillNames,
|
|
619
|
+
selectedSkills: selectedNames,
|
|
620
|
+
skillRoot: selectedNames.length > 0 ? skillsHome : null,
|
|
621
|
+
},
|
|
622
|
+
promptInstructions,
|
|
623
|
+
commandNotes: selectedNames.length > 0
|
|
624
|
+
? [`Materialized ${selectedNames.length} Paperclip skill(s) for ACPX Claude at ${skillsHome}.`]
|
|
625
|
+
: [],
|
|
626
|
+
bundleDir: selectedNames.length > 0 ? skillsHome : null,
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
async function readManagedCodexSkillsManifest(skillsHome) {
|
|
630
|
+
const manifestPath = path.join(skillsHome, PAPERCLIP_MANAGED_CODEX_SKILLS_MANIFEST);
|
|
631
|
+
try {
|
|
632
|
+
const raw = JSON.parse(await fs.readFile(manifestPath, "utf8"));
|
|
633
|
+
const parsed = parseObject(raw);
|
|
634
|
+
const skills = Array.isArray(parsed.managedSkillNames)
|
|
635
|
+
? parsed.managedSkillNames.filter((value) => typeof value === "string" && value.trim().length > 0)
|
|
636
|
+
: [];
|
|
637
|
+
return new Set(skills);
|
|
638
|
+
}
|
|
639
|
+
catch {
|
|
640
|
+
return new Set();
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
async function writeManagedCodexSkillsManifest(skillsHome, skillNames) {
|
|
644
|
+
const managedSkillNames = Array.from(new Set(skillNames)).sort();
|
|
645
|
+
await fs.writeFile(path.join(skillsHome, PAPERCLIP_MANAGED_CODEX_SKILLS_MANIFEST), `${JSON.stringify({ version: 1, managedSkillNames }, null, 2)}\n`, "utf8");
|
|
646
|
+
}
|
|
647
|
+
async function removeSkillTarget(target) {
|
|
648
|
+
const existing = await fs.lstat(target).catch(() => null);
|
|
649
|
+
if (!existing)
|
|
650
|
+
return false;
|
|
651
|
+
await fs.rm(target, { recursive: true, force: true });
|
|
652
|
+
return true;
|
|
653
|
+
}
|
|
654
|
+
async function reconcileManagedCodexSkills(input) {
|
|
655
|
+
const desired = new Set(input.selectedSkills.map((entry) => entry.runtimeName));
|
|
656
|
+
const managed = await readManagedCodexSkillsManifest(input.skillsHome);
|
|
657
|
+
const availableByRuntimeName = new Map(input.allSkills.map((entry) => [entry.runtimeName, entry]));
|
|
658
|
+
for (const name of managed) {
|
|
659
|
+
if (desired.has(name))
|
|
660
|
+
continue;
|
|
661
|
+
if (await removeSkillTarget(path.join(input.skillsHome, name))) {
|
|
662
|
+
await input.onLog("stdout", `[paperclip] Revoked ACPX Codex skill "${name}" from ${input.skillsHome}\n`);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
for (const entry of input.allSkills) {
|
|
666
|
+
if (desired.has(entry.runtimeName) || managed.has(entry.runtimeName))
|
|
667
|
+
continue;
|
|
668
|
+
const target = path.join(input.skillsHome, entry.runtimeName);
|
|
669
|
+
const existing = await fs.lstat(target).catch(() => null);
|
|
670
|
+
if (!existing?.isSymbolicLink())
|
|
671
|
+
continue;
|
|
672
|
+
const linkedPath = await fs.readlink(target).catch(() => null);
|
|
673
|
+
if (!linkedPath)
|
|
674
|
+
continue;
|
|
675
|
+
const resolvedLinkedPath = path.resolve(path.dirname(target), linkedPath);
|
|
676
|
+
if (resolvedLinkedPath !== path.resolve(entry.source))
|
|
677
|
+
continue;
|
|
678
|
+
if (await removeSkillTarget(target)) {
|
|
679
|
+
await input.onLog("stdout", `[paperclip] Revoked legacy ACPX Codex skill "${entry.runtimeName}" from ${input.skillsHome}\n`);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
for (const name of managed) {
|
|
683
|
+
if (desired.has(name) || availableByRuntimeName.has(name))
|
|
684
|
+
continue;
|
|
685
|
+
if (await removeSkillTarget(path.join(input.skillsHome, name))) {
|
|
686
|
+
await input.onLog("stdout", `[paperclip] Revoked unavailable ACPX Codex skill "${name}" from ${input.skillsHome}\n`);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
async function prepareCodexSkillRuntime(input) {
|
|
691
|
+
const now = input.now ?? (() => Date.now());
|
|
692
|
+
const envConfig = parseObject(input.config.env);
|
|
693
|
+
const configuredCodexHome = typeof envConfig.CODEX_HOME === "string" && envConfig.CODEX_HOME.trim().length > 0
|
|
694
|
+
? path.resolve(envConfig.CODEX_HOME.trim())
|
|
695
|
+
: null;
|
|
696
|
+
const sourceCodexHome = typeof process.env.CODEX_HOME === "string" && process.env.CODEX_HOME.trim().length > 0
|
|
697
|
+
? path.resolve(process.env.CODEX_HOME.trim())
|
|
698
|
+
: path.join(os.homedir(), ".codex");
|
|
699
|
+
const managedCodexHome = resolveManagedCodexHomeDir(input.companyId);
|
|
700
|
+
const effectiveCodexHome = configuredCodexHome ??
|
|
701
|
+
await prepareManagedCodexHome({
|
|
702
|
+
companyId: input.companyId,
|
|
703
|
+
sourceHome: sourceCodexHome,
|
|
704
|
+
targetHome: managedCodexHome,
|
|
705
|
+
onLog: input.onLog,
|
|
706
|
+
});
|
|
707
|
+
const { allSkills, selectedSkills, desiredSkillNames } = await resolveSelectedRuntimeSkills(input.config, input.moduleDir);
|
|
708
|
+
const skillSetKey = await buildSkillSetKey({ skills: selectedSkills, label: "codex" });
|
|
709
|
+
const skillsHome = path.join(effectiveCodexHome, "skills");
|
|
710
|
+
await fs.mkdir(skillsHome, { recursive: true });
|
|
711
|
+
// Step 3 — skills.reconcile: nested inside the codex-home seed (step 2), so it
|
|
712
|
+
// emits its own boundary event and span at this call-site. It must NOT add its
|
|
713
|
+
// wall time to the root work sum. The enclosing step 2 wall already covers this
|
|
714
|
+
// interval, so a second `onWallMs` call would count the same milliseconds
|
|
715
|
+
// twice. Drop `onWallMs` for the nested step; keep every other attribution
|
|
716
|
+
// field.
|
|
717
|
+
const nestedStepMetrics = {
|
|
718
|
+
...(input.stepMetrics ?? {}),
|
|
719
|
+
onWallMs: undefined,
|
|
720
|
+
};
|
|
721
|
+
await measureStartupStep({ onEvent: input.onEvent }, now, "skills.reconcile", () => reconcileManagedCodexSkills({
|
|
722
|
+
skillsHome,
|
|
723
|
+
allSkills,
|
|
724
|
+
selectedSkills,
|
|
725
|
+
onLog: input.onLog,
|
|
726
|
+
}), nestedStepMetrics);
|
|
727
|
+
for (const entry of selectedSkills) {
|
|
728
|
+
const target = path.join(skillsHome, entry.runtimeName);
|
|
729
|
+
try {
|
|
730
|
+
const result = await materializePaperclipSkillCopy(entry.source, target);
|
|
731
|
+
if (result.skippedSymlinks.length > 0) {
|
|
732
|
+
await input.onLog("stdout", `[paperclip] Materialized ACPX Codex skill "${entry.runtimeName}" into ${skillsHome} and skipped ${result.skippedSymlinks.length} symlink(s).\n`);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
catch (err) {
|
|
736
|
+
await input.onLog("stderr", `[paperclip] Failed to inject ACPX Codex skill "${entry.key}" into ${skillsHome}: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
await writeManagedCodexSkillsManifest(skillsHome, selectedSkills.map((entry) => entry.runtimeName));
|
|
740
|
+
input.env.CODEX_HOME = effectiveCodexHome;
|
|
741
|
+
return {
|
|
742
|
+
identity: {
|
|
743
|
+
mode: "codex",
|
|
744
|
+
skillSetKey,
|
|
745
|
+
desiredSkillNames,
|
|
746
|
+
selectedSkills: selectedSkills.map((entry) => entry.runtimeName).sort(),
|
|
747
|
+
codexHome: effectiveCodexHome,
|
|
748
|
+
skillsHome,
|
|
749
|
+
},
|
|
750
|
+
commandNotes: [`Prepared ACPX Codex skill home at ${skillsHome}.`],
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
function resolveGeminiSkillsHome(config) {
|
|
754
|
+
const envConfig = parseObject(config.env);
|
|
755
|
+
const configuredHome = typeof envConfig.HOME === "string" && envConfig.HOME.trim().length > 0
|
|
756
|
+
? path.resolve(envConfig.HOME.trim())
|
|
757
|
+
: os.homedir();
|
|
758
|
+
return path.join(configuredHome, ".gemini", "skills");
|
|
759
|
+
}
|
|
760
|
+
async function prepareGeminiSkillRuntime(input) {
|
|
761
|
+
const { selectedSkills, desiredSkillNames } = await resolveSelectedRuntimeSkills(input.config, input.moduleDir);
|
|
762
|
+
const skillSetKey = await buildSkillSetKey({ skills: selectedSkills, label: "gemini" });
|
|
763
|
+
const skillsHome = resolveGeminiSkillsHome(input.config);
|
|
764
|
+
await fs.mkdir(skillsHome, { recursive: true });
|
|
765
|
+
const allowedSkillNames = selectedSkills.map((entry) => entry.runtimeName);
|
|
766
|
+
const removedSkills = await removeMaintainerOnlySkillSymlinks(skillsHome, allowedSkillNames);
|
|
767
|
+
for (const skillName of removedSkills) {
|
|
768
|
+
await input.onLog("stdout", `[paperclip] Removed maintainer-only ACPX Gemini skill "${skillName}" from ${skillsHome}\n`);
|
|
769
|
+
}
|
|
770
|
+
for (const entry of selectedSkills) {
|
|
771
|
+
const target = path.join(skillsHome, entry.runtimeName);
|
|
772
|
+
try {
|
|
773
|
+
const result = await ensurePaperclipSkillSymlink(entry.source, target);
|
|
774
|
+
if (result === "created" || result === "repaired") {
|
|
775
|
+
await input.onLog("stdout", `[paperclip] ${result === "repaired" ? "Repaired" : "Linked"} ACPX Gemini skill "${entry.runtimeName}" into ${skillsHome}\n`);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
catch (err) {
|
|
779
|
+
if (isErrnoException(err, "EPERM")) {
|
|
780
|
+
const result = await materializePaperclipSkillCopy(entry.source, target);
|
|
781
|
+
await input.onLog("stdout", `[paperclip] Copied ACPX Gemini skill "${entry.runtimeName}" into ${skillsHome} because symlinks are unavailable.${result.skippedSymlinks.length > 0 ? ` Skipped ${result.skippedSymlinks.length} nested symlink(s).` : ""}\n`);
|
|
782
|
+
continue;
|
|
783
|
+
}
|
|
784
|
+
await input.onLog("stderr", `[paperclip] Failed to link ACPX Gemini skill "${entry.key}" into ${skillsHome}: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
return {
|
|
788
|
+
identity: {
|
|
789
|
+
mode: "gemini",
|
|
790
|
+
skillSetKey,
|
|
791
|
+
desiredSkillNames,
|
|
792
|
+
selectedSkills: selectedSkills.map((entry) => entry.runtimeName).sort(),
|
|
793
|
+
skillsHome,
|
|
794
|
+
},
|
|
795
|
+
commandNotes: selectedSkills.length > 0
|
|
796
|
+
? [`Prepared ${selectedSkills.length} ACPX Gemini skill(s) at ${skillsHome}.`]
|
|
797
|
+
: [],
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
function normalizeMode(config) {
|
|
801
|
+
return asString(config.mode, DEFAULT_ACP_ENGINE_MODE) === "oneshot" ? "oneshot" : "persistent";
|
|
802
|
+
}
|
|
803
|
+
function normalizePermissionMode(config) {
|
|
804
|
+
const value = asString(config.permissionMode, DEFAULT_ACP_ENGINE_PERMISSION_MODE).trim();
|
|
805
|
+
if (value === "approve-reads" || value === "deny-all")
|
|
806
|
+
return value;
|
|
807
|
+
if (value === "default")
|
|
808
|
+
return DEFAULT_ACP_ENGINE_PERMISSION_MODE;
|
|
809
|
+
return "approve-all";
|
|
810
|
+
}
|
|
811
|
+
function normalizeNonInteractivePermissions(config) {
|
|
812
|
+
return asString(config.nonInteractivePermissions, DEFAULT_ACP_ENGINE_NON_INTERACTIVE_PERMISSIONS) === "fail"
|
|
813
|
+
? "fail"
|
|
814
|
+
: "deny";
|
|
815
|
+
}
|
|
816
|
+
function normalizeRequestedThinkingEffort(config) {
|
|
817
|
+
return (asString(config.modelReasoningEffort, "") ||
|
|
818
|
+
asString(config.reasoningEffort, "") ||
|
|
819
|
+
asString(config.thinkingEffort, "") ||
|
|
820
|
+
asString(config.effort, "")).trim();
|
|
821
|
+
}
|
|
822
|
+
function buildCodexStartupConfig(input) {
|
|
823
|
+
const hasRuntimeConfig = Boolean(input.requestedModel || input.requestedThinkingEffort || input.fastMode);
|
|
824
|
+
if (!hasRuntimeConfig)
|
|
825
|
+
return { value: null, invalidExistingConfig: false };
|
|
826
|
+
let existing = {};
|
|
827
|
+
let invalidExistingConfig = false;
|
|
828
|
+
if (input.existingConfig) {
|
|
829
|
+
try {
|
|
830
|
+
existing = parseObject(JSON.parse(input.existingConfig));
|
|
831
|
+
}
|
|
832
|
+
catch {
|
|
833
|
+
invalidExistingConfig = true;
|
|
834
|
+
existing = {};
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
return {
|
|
838
|
+
value: JSON.stringify({
|
|
839
|
+
...existing,
|
|
840
|
+
...(input.requestedModel ? { model: input.requestedModel } : {}),
|
|
841
|
+
...(input.requestedThinkingEffort
|
|
842
|
+
? { model_reasoning_effort: input.requestedThinkingEffort }
|
|
843
|
+
: {}),
|
|
844
|
+
...(input.fastMode
|
|
845
|
+
? {
|
|
846
|
+
service_tier: "fast",
|
|
847
|
+
features: {
|
|
848
|
+
...parseObject(existing.features),
|
|
849
|
+
fast_mode: true,
|
|
850
|
+
},
|
|
851
|
+
}
|
|
852
|
+
: {}),
|
|
853
|
+
}),
|
|
854
|
+
invalidExistingConfig,
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
function isCompatibleSession(params, runtime) {
|
|
858
|
+
if (asString(params.configFingerprint, "") !== runtime.fingerprint)
|
|
859
|
+
return false;
|
|
860
|
+
if (asString(params.sessionKey, "") !== runtime.sessionKey)
|
|
861
|
+
return false;
|
|
862
|
+
if (asString(params.agent, "") !== runtime.acpxAgent)
|
|
863
|
+
return false;
|
|
864
|
+
if (asString(params.mode, "") !== runtime.mode)
|
|
865
|
+
return false;
|
|
866
|
+
const savedCwd = asString(params.cwd, "");
|
|
867
|
+
if (!savedCwd || path.resolve(savedCwd) !== path.resolve(runtime.cwd))
|
|
868
|
+
return false;
|
|
869
|
+
const savedRemote = parseObject(params.remoteExecution);
|
|
870
|
+
return stableJson(savedRemote) === stableJson(runtime.remoteExecutionIdentity ?? {});
|
|
871
|
+
}
|
|
872
|
+
function buildSessionParams(input) {
|
|
873
|
+
const { prepared, handle } = input;
|
|
874
|
+
return {
|
|
875
|
+
sessionKey: prepared.sessionKey,
|
|
876
|
+
runtimeSessionName: handle.runtimeSessionName,
|
|
877
|
+
acpxRecordId: handle.acpxRecordId,
|
|
878
|
+
acpSessionId: handle.backendSessionId,
|
|
879
|
+
agentSessionId: handle.agentSessionId,
|
|
880
|
+
agent: prepared.acpxAgent,
|
|
881
|
+
cwd: prepared.cwd,
|
|
882
|
+
mode: prepared.mode,
|
|
883
|
+
stateDir: prepared.stateDir,
|
|
884
|
+
configFingerprint: prepared.fingerprint,
|
|
885
|
+
...(prepared.requestedModel ? { model: prepared.requestedModel } : {}),
|
|
886
|
+
...(prepared.requestedThinkingEffort ? { thinkingEffort: prepared.requestedThinkingEffort } : {}),
|
|
887
|
+
...(prepared.fastMode ? { fastMode: true } : {}),
|
|
888
|
+
skills: prepared.skillsIdentity,
|
|
889
|
+
mcpServers: prepared.mcpIdentity,
|
|
890
|
+
...(prepared.workspaceId ? { workspaceId: prepared.workspaceId } : {}),
|
|
891
|
+
...(prepared.workspaceRepoUrl ? { repoUrl: prepared.workspaceRepoUrl } : {}),
|
|
892
|
+
...(prepared.workspaceRepoRef ? { repoRef: prepared.workspaceRepoRef } : {}),
|
|
893
|
+
...(prepared.remoteExecutionIdentity ? { remoteExecution: prepared.remoteExecutionIdentity } : {}),
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
function uniqueSorted(values) {
|
|
897
|
+
return [...new Set(values.filter((value) => typeof value === "string" && value.length > 0))].sort();
|
|
898
|
+
}
|
|
899
|
+
// The Claude Code SDK that `claude-agent-acp` runs uses
|
|
900
|
+
// `settingSources: ["user", "project", "local"]`. By writing a per-worktree
|
|
901
|
+
// `.claude/settings.local.json` we override the user's potentially-restrictive
|
|
902
|
+
// `~/.claude/settings.json` (e.g. `defaultMode: "dontAsk"`, which silently
|
|
903
|
+
// denies every non-allowlisted tool and never reaches `canUseTool`), and we
|
|
904
|
+
// widen the SDK's Read sandbox to include the Paperclip state dirs the agent
|
|
905
|
+
// needs to talk to its own control plane.
|
|
906
|
+
async function writePaperclipClaudeSettings(input) {
|
|
907
|
+
const filePath = path.join(input.cwd, ".claude", "settings.local.json");
|
|
908
|
+
const instanceRoot = defaultPaperclipInstanceDir();
|
|
909
|
+
const companyRoot = path.join(instanceRoot, "companies", input.companyId);
|
|
910
|
+
const paperclipAdditionalDirectories = uniqueSorted([
|
|
911
|
+
input.stateDir,
|
|
912
|
+
input.agentHome,
|
|
913
|
+
companyRoot,
|
|
914
|
+
]);
|
|
915
|
+
const paperclipAllow = uniqueSorted([
|
|
916
|
+
"Bash(curl:*)",
|
|
917
|
+
"Bash(env:*)",
|
|
918
|
+
"Bash(env)",
|
|
919
|
+
`Bash(${input.cwd}/scripts/paperclip-issue-update.sh:*)`,
|
|
920
|
+
`Bash(${input.cwd}/scripts/paperclip:*)`,
|
|
921
|
+
]);
|
|
922
|
+
let existing = {};
|
|
923
|
+
const existingRaw = await fs.readFile(filePath, "utf8").catch(() => null);
|
|
924
|
+
if (existingRaw) {
|
|
925
|
+
try {
|
|
926
|
+
const parsed = JSON.parse(existingRaw);
|
|
927
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
|
|
928
|
+
existing = parsed;
|
|
929
|
+
}
|
|
930
|
+
catch {
|
|
931
|
+
// Malformed settings file — leave it alone in `existing` and our merge will replace it with a valid one.
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
const existingPerms = existing.permissions && typeof existing.permissions === "object" && !Array.isArray(existing.permissions)
|
|
935
|
+
? existing.permissions
|
|
936
|
+
: {};
|
|
937
|
+
const existingAllow = Array.isArray(existingPerms.allow)
|
|
938
|
+
? existingPerms.allow.filter((value) => typeof value === "string")
|
|
939
|
+
: [];
|
|
940
|
+
const existingAdditionalDirectories = Array.isArray(existingPerms.additionalDirectories)
|
|
941
|
+
? existingPerms.additionalDirectories.filter((value) => typeof value === "string")
|
|
942
|
+
: [];
|
|
943
|
+
const mergedAllow = uniqueSorted([...existingAllow, ...paperclipAllow]);
|
|
944
|
+
const mergedAdditionalDirectories = uniqueSorted([
|
|
945
|
+
...existingAdditionalDirectories,
|
|
946
|
+
...paperclipAdditionalDirectories,
|
|
947
|
+
]);
|
|
948
|
+
const existingDefaultMode = typeof existingPerms.defaultMode === "string" ? existingPerms.defaultMode : "";
|
|
949
|
+
const defaultMode = existingDefaultMode && existingDefaultMode !== "dontAsk" ? existingDefaultMode : "default";
|
|
950
|
+
const overrodeDontAsk = existingDefaultMode === "dontAsk";
|
|
951
|
+
const nextPermissions = {
|
|
952
|
+
...existingPerms,
|
|
953
|
+
allow: mergedAllow,
|
|
954
|
+
additionalDirectories: mergedAdditionalDirectories,
|
|
955
|
+
defaultMode,
|
|
956
|
+
};
|
|
957
|
+
const next = { ...existing, permissions: nextPermissions };
|
|
958
|
+
await writeFileAtomically({
|
|
959
|
+
target: filePath,
|
|
960
|
+
contents: `${JSON.stringify(next, null, 2)}\n`,
|
|
961
|
+
mode: 0o600,
|
|
962
|
+
});
|
|
963
|
+
return {
|
|
964
|
+
filePath,
|
|
965
|
+
allow: mergedAllow,
|
|
966
|
+
additionalDirectories: mergedAdditionalDirectories,
|
|
967
|
+
defaultMode,
|
|
968
|
+
overrodeDontAsk,
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
// Cross the CLI's staging seam for a runner-backed remote sandbox: ship the
|
|
972
|
+
// workspace (and, in PR 2, the per-adapter managed-home `assets`) into the
|
|
973
|
+
// sandbox and obtain the in-sandbox `workspaceRemoteDir` plus the non-null
|
|
974
|
+
// `runtimeRootDir`/`assetDirs` the bridges and the home remap consume. This is
|
|
975
|
+
// the shared-engine mirror of the CLI lanes (codex/claude/gemini
|
|
976
|
+
// `*-local/execute.ts`). PR 1 shipped the workspace + cwd only; PR 2 threads
|
|
977
|
+
// the home `assets` (built by the per-adapter `prepareRemoteManagedHome` seam,
|
|
978
|
+
// carrying the codex `provision`/`restore` auth seams) through `assets` here so
|
|
979
|
+
// `assetDirs.<key>` resolves to the seeded in-sandbox home. The returned
|
|
980
|
+
// `restoreWorkspace` fires the per-asset `restore` (codex copy-back) at
|
|
981
|
+
// teardown.
|
|
982
|
+
async function stageAcpRemoteRuntime(input) {
|
|
983
|
+
await input.onLog("stdout", `[paperclip] Syncing workspace to ${describeAdapterExecutionTarget(input.target)}.\n`);
|
|
984
|
+
return await prepareAdapterExecutionTargetRuntime({
|
|
985
|
+
runId: input.runId,
|
|
986
|
+
target: input.target,
|
|
987
|
+
adapterKey: input.adapterKey,
|
|
988
|
+
timeoutSec: input.timeoutSec,
|
|
989
|
+
workspaceLocalDir: input.workspaceLocalDir,
|
|
990
|
+
...(input.workspaceRemoteDir ? { workspaceRemoteDir: input.workspaceRemoteDir } : {}),
|
|
991
|
+
...(input.assets && input.assets.length > 0 ? { assets: input.assets } : {}),
|
|
992
|
+
...(input.additionalSources && input.additionalSources.length > 0
|
|
993
|
+
? { additionalSources: input.additionalSources }
|
|
994
|
+
: {}),
|
|
995
|
+
onProgress: (line) => input.onLog("stdout", line),
|
|
996
|
+
onRuntimeProgress: input.onRuntimeProgress,
|
|
997
|
+
runtimeSpan: input.runtimeSpan,
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
// Dispose a freshly staged in-sandbox runtime after a managed-home seam threw. The
|
|
1001
|
+
// seam shipped the workspace/home into the sandbox through `stage()` but threw
|
|
1002
|
+
// before it could return its `disposeStaged`, so the engine removes the managed
|
|
1003
|
+
// runtime root here — otherwise the abandoned in-sandbox managed home (with its
|
|
1004
|
+
// staged credentials) leaks in a persistent sandbox. Fail-open: a cleanup miss on
|
|
1005
|
+
// an already-failing run is logged and never re-thrown, so it cannot mask the seam
|
|
1006
|
+
// error.
|
|
1007
|
+
async function disposeFreshStagedRuntime(input) {
|
|
1008
|
+
const runtimeRootDir = input.stagedRuntime.runtimeRootDir;
|
|
1009
|
+
if (!runtimeRootDir)
|
|
1010
|
+
return;
|
|
1011
|
+
try {
|
|
1012
|
+
await runAdapterExecutionTargetShellCommand(input.runId, input.target, `rm -rf ${shellQuote(runtimeRootDir)}`, {
|
|
1013
|
+
cwd: input.cwd,
|
|
1014
|
+
env: {},
|
|
1015
|
+
timeoutSec: Math.max(input.timeoutSec, 15),
|
|
1016
|
+
graceSec: 20,
|
|
1017
|
+
onLog: input.onLog,
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
catch (err) {
|
|
1021
|
+
await input.onLog("stderr", `[paperclip] Failed to dispose the fresh staged runtime after a managed-home seam error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
async function buildRuntime(input) {
|
|
1025
|
+
const { runId, agent, config, context, authToken } = input.ctx;
|
|
1026
|
+
// Injectable monotonic clock for per-step startup timing. Hoisted above the
|
|
1027
|
+
// first instrumented boundary (step 1 `workspace.resolve`, below) so every
|
|
1028
|
+
// `measureStartupStep` call in this function shares one deterministic clock.
|
|
1029
|
+
const nowMs = input.deps.now ?? (() => Date.now());
|
|
1030
|
+
const workspaceContext = parseObject(context.paperclipWorkspace);
|
|
1031
|
+
const secretsContext = parseObject(context.paperclipSecrets);
|
|
1032
|
+
const secretManifest = Array.isArray(secretsContext.manifest) ? secretsContext.manifest : [];
|
|
1033
|
+
const workspaceCwd = asString(workspaceContext.cwd, "");
|
|
1034
|
+
const workspaceSource = asString(workspaceContext.source, "");
|
|
1035
|
+
const workspaceStrategy = asString(workspaceContext.strategy, "");
|
|
1036
|
+
const workspaceId = asString(workspaceContext.workspaceId, "");
|
|
1037
|
+
const workspaceRepoUrl = asString(workspaceContext.repoUrl, "");
|
|
1038
|
+
const workspaceRepoRef = asString(workspaceContext.repoRef, "");
|
|
1039
|
+
const workspaceBranch = asString(workspaceContext.branchName, "");
|
|
1040
|
+
const workspaceWorktreePath = asString(workspaceContext.worktreePath, "");
|
|
1041
|
+
const agentHome = asString(workspaceContext.agentHome, "");
|
|
1042
|
+
const configuredCwd = asString(config.cwd, "");
|
|
1043
|
+
const useConfiguredInsteadOfAgentHome = workspaceSource === "agent_home" && configuredCwd.length > 0;
|
|
1044
|
+
const effectiveWorkspaceCwd = useConfiguredInsteadOfAgentHome ? "" : workspaceCwd;
|
|
1045
|
+
const cwd = effectiveWorkspaceCwd || configuredCwd || process.cwd();
|
|
1046
|
+
// Referenced (additional) projects to stage into the sandbox alongside the
|
|
1047
|
+
// anchor workspace, read from the workspace realization record. The list is
|
|
1048
|
+
// empty unless run prep resolved referenced projects — gated upstream by the
|
|
1049
|
+
// multi-project workspace-sync kill-switch — so the anchor-only path is
|
|
1050
|
+
// unchanged.
|
|
1051
|
+
const realizationContext = parseObject(workspaceContext.realization);
|
|
1052
|
+
const additionalSourceRecords = (Array.isArray(realizationContext.additional) ? realizationContext.additional : []).map((entry) => parseObject(entry));
|
|
1053
|
+
const additionalSourceCandidates = additionalSourceRecords
|
|
1054
|
+
.map((entry) => ({
|
|
1055
|
+
localPath: asString(entry.path, ""),
|
|
1056
|
+
projectId: asString(entry.projectId, ""),
|
|
1057
|
+
projectWorkspaceId: asString(entry.projectWorkspaceId, ""),
|
|
1058
|
+
repoUrl: asString(entry.repoUrl, ""),
|
|
1059
|
+
repoRef: asString(entry.repoRef, ""),
|
|
1060
|
+
}))
|
|
1061
|
+
.filter((entry) => entry.localPath.length > 0 && entry.projectId.length > 0);
|
|
1062
|
+
// Resolve each referenced project's Git-ignored paths ONCE, here, before any
|
|
1063
|
+
// staging site runs. The sandbox lane, the SSH lane, and the content signature
|
|
1064
|
+
// below all consume this SAME resolution per project, so they can never apply
|
|
1065
|
+
// a different exclusion set to the same project. See
|
|
1066
|
+
// `resolveReferencedSourceIgnore` for the fail-closed rules.
|
|
1067
|
+
const additionalSourcesWithIgnore = await Promise.all(additionalSourceCandidates.map(async (entry) => ({
|
|
1068
|
+
...entry,
|
|
1069
|
+
ignoreResolution: await resolveReferencedSourceIgnore(entry.localPath),
|
|
1070
|
+
})));
|
|
1071
|
+
const additionalSources = additionalSourcesWithIgnore.map((entry) => ({
|
|
1072
|
+
localPath: entry.localPath,
|
|
1073
|
+
projectId: entry.projectId,
|
|
1074
|
+
ignoreResolution: entry.ignoreResolution,
|
|
1075
|
+
}));
|
|
1076
|
+
// Stable identity of the referenced-project set for the session fingerprint.
|
|
1077
|
+
// The staged-runtime cache reuses already-staged referenced-project trees on a
|
|
1078
|
+
// compatible resume, so the fingerprint must change when the set OR a project's
|
|
1079
|
+
// pinned checkout changes. Without this, a resume reuses a stale staged tree.
|
|
1080
|
+
// Fold in each project's id, host path, workspace id, and pinned ref; sort by
|
|
1081
|
+
// projectId so the identity depends on the set, not the record order.
|
|
1082
|
+
const additionalSourcesIdentityBase = additionalSourcesWithIgnore
|
|
1083
|
+
.map((entry) => ({
|
|
1084
|
+
projectId: entry.projectId,
|
|
1085
|
+
localPath: entry.localPath,
|
|
1086
|
+
projectWorkspaceId: entry.projectWorkspaceId,
|
|
1087
|
+
repoUrl: entry.repoUrl,
|
|
1088
|
+
repoRef: entry.repoRef,
|
|
1089
|
+
ignoreResolution: entry.ignoreResolution,
|
|
1090
|
+
}))
|
|
1091
|
+
.sort((a, b) => (a.projectId < b.projectId ? -1 : a.projectId > b.projectId ? 1 : 0));
|
|
1092
|
+
// Metadata alone does not change on a content-only checkout change (same host
|
|
1093
|
+
// path and pinned ref, new file bytes). Fold in each tree's content signature so
|
|
1094
|
+
// a file add, remove, or edit busts the fingerprint and the resume re-stages.
|
|
1095
|
+
// The signature reads the same `ignoreResolution` the staging sites above use,
|
|
1096
|
+
// so it never disagrees with what was actually shipped.
|
|
1097
|
+
const additionalSourcesIdentity = await Promise.all(additionalSourcesIdentityBase.map(async ({ ignoreResolution, ...entry }) => ({
|
|
1098
|
+
...entry,
|
|
1099
|
+
contentSignature: await referencedSourceContentSignature(entry.localPath, ignoreResolution),
|
|
1100
|
+
})));
|
|
1101
|
+
// Referenced-project workspace hints exposed to the agent through PAPERCLIP_WORKSPACES_JSON. The
|
|
1102
|
+
// list joins the anchor project's alternative workspaces with the referenced (mentioned) projects.
|
|
1103
|
+
// On the confined sandbox lane the run repoints each referenced hint at its staged directory after
|
|
1104
|
+
// staging below. Empty unless run prep resolved referenced projects or alternative workspaces.
|
|
1105
|
+
const workspaceHints = Array.isArray(context.paperclipWorkspaces)
|
|
1106
|
+
? context.paperclipWorkspaces.filter((value) => typeof value === "object" && value !== null)
|
|
1107
|
+
: [];
|
|
1108
|
+
const executionTarget = readAdapterExecutionTarget({
|
|
1109
|
+
executionTarget: input.ctx.executionTarget,
|
|
1110
|
+
legacyRemoteExecution: input.ctx.executionTransport?.remoteExecution,
|
|
1111
|
+
});
|
|
1112
|
+
const remoteExecutionIdentity = adapterExecutionTargetSessionIdentity(executionTarget);
|
|
1113
|
+
const effectiveExecutionCwd = remoteExecutionIdentity && typeof remoteExecutionIdentity.remoteCwd === "string"
|
|
1114
|
+
? remoteExecutionIdentity.remoteCwd
|
|
1115
|
+
: cwd;
|
|
1116
|
+
const executionTargetIsRemote = remoteExecutionIdentity !== null;
|
|
1117
|
+
// Merge the injected tracer + root parent-context into every step option set,
|
|
1118
|
+
// so each boundary span parents to the root span. With no injected trace
|
|
1119
|
+
// context both fields are no-ops and the span path stays inert.
|
|
1120
|
+
const stepMetrics = {
|
|
1121
|
+
...input.spanParent,
|
|
1122
|
+
};
|
|
1123
|
+
// The two bridge-start steps intentionally overlap. A shared `batch` tag marks
|
|
1124
|
+
// the two spans as one parallel batch, and `criticalPath: false` keeps their
|
|
1125
|
+
// inner exec spans off the critical path (their wall time overlaps).
|
|
1126
|
+
const concurrentBridgeStepMetrics = {
|
|
1127
|
+
...input.spanParent,
|
|
1128
|
+
batch: STARTUP_BRIDGE_BATCH,
|
|
1129
|
+
criticalPath: false,
|
|
1130
|
+
};
|
|
1131
|
+
const shapedWorkspaceEnv = shapePaperclipWorkspaceEnvForExecution({
|
|
1132
|
+
workspaceCwd: effectiveWorkspaceCwd,
|
|
1133
|
+
workspaceWorktreePath,
|
|
1134
|
+
executionTargetIsRemote,
|
|
1135
|
+
executionCwd: effectiveExecutionCwd,
|
|
1136
|
+
});
|
|
1137
|
+
// Step 1 — workspace.resolve: the workspace resolution/fallback chain closes
|
|
1138
|
+
// here on the awaited directory materialization.
|
|
1139
|
+
await measureStartupStep(input.ctx, nowMs, "workspace.resolve", () => ensureAbsoluteDirectory(cwd, { createIfMissing: true }), stepMetrics);
|
|
1140
|
+
const acpxAgent = normalizeAgent(config);
|
|
1141
|
+
// Run summaries always fail closed to the final output segment so internal
|
|
1142
|
+
// thought text and intermediate narration cannot become issue comments.
|
|
1143
|
+
const coalescePlaceholderToolUpdates = config.coalescePlaceholderToolUpdates === true;
|
|
1144
|
+
const mode = normalizeMode(config);
|
|
1145
|
+
const permissionMode = normalizePermissionMode(config);
|
|
1146
|
+
const nonInteractivePermissions = normalizeNonInteractivePermissions(config);
|
|
1147
|
+
const requestedModel = asString(config.model, "").trim();
|
|
1148
|
+
const requestedThinkingEffort = normalizeRequestedThinkingEffort(config);
|
|
1149
|
+
const fastMode = acpxAgent === "codex" && config.fastMode === true;
|
|
1150
|
+
const runtimeMcpServers = input.ctx.runtimeMcp?.getServers() ?? [];
|
|
1151
|
+
const mcpIdentity = runtimeMcpServers.map(({ name, url, connectionId }) => ({
|
|
1152
|
+
name,
|
|
1153
|
+
url,
|
|
1154
|
+
connectionId,
|
|
1155
|
+
}));
|
|
1156
|
+
const mcpServers = runtimeMcpServers.map((server) => ({
|
|
1157
|
+
type: "http",
|
|
1158
|
+
name: server.name,
|
|
1159
|
+
url: server.url,
|
|
1160
|
+
headers: [{ name: "Authorization", value: `Bearer ${server.token}` }],
|
|
1161
|
+
}));
|
|
1162
|
+
// Resolve the wall-clock timeout through the shared execution-target
|
|
1163
|
+
// resolver so sandbox-backed runs pick up the 4h backstop default while
|
|
1164
|
+
// local/SSH runs keep the historical "0 = no adapter timeout" behavior.
|
|
1165
|
+
const timeoutResolution = resolveAdapterExecutionTargetTimeout(executionTarget, asNumber(config.timeoutSec, DEFAULT_ACP_ENGINE_TIMEOUT_SEC));
|
|
1166
|
+
const timeoutSec = timeoutResolution.timeoutSec;
|
|
1167
|
+
const stateDir = path.resolve(asString(config.stateDir, "") || defaultStateDir(agent.companyId, agent.id));
|
|
1168
|
+
await fs.mkdir(stateDir, { recursive: true });
|
|
1169
|
+
const envConfig = parseObject(config.env);
|
|
1170
|
+
const env = { ...buildPaperclipEnv(agent), PAPERCLIP_RUN_ID: runId };
|
|
1171
|
+
const wakeTaskId = (typeof context.taskId === "string" && context.taskId.trim()) ||
|
|
1172
|
+
(typeof context.issueId === "string" && context.issueId.trim()) ||
|
|
1173
|
+
"";
|
|
1174
|
+
const wakeReason = typeof context.wakeReason === "string" ? context.wakeReason.trim() : "";
|
|
1175
|
+
const wakeCommentId = (typeof context.wakeCommentId === "string" && context.wakeCommentId.trim()) ||
|
|
1176
|
+
(typeof context.commentId === "string" && context.commentId.trim()) ||
|
|
1177
|
+
"";
|
|
1178
|
+
const approvalId = typeof context.approvalId === "string" ? context.approvalId.trim() : "";
|
|
1179
|
+
const approvalStatus = typeof context.approvalStatus === "string" ? context.approvalStatus.trim() : "";
|
|
1180
|
+
const linkedIssueIds = Array.isArray(context.issueIds)
|
|
1181
|
+
? context.issueIds.filter((value) => typeof value === "string" && value.trim().length > 0)
|
|
1182
|
+
: [];
|
|
1183
|
+
const issueWorkMode = readPaperclipIssueWorkModeFromContext(context);
|
|
1184
|
+
if (wakeTaskId)
|
|
1185
|
+
env.PAPERCLIP_TASK_ID = wakeTaskId;
|
|
1186
|
+
if (issueWorkMode)
|
|
1187
|
+
env.PAPERCLIP_ISSUE_WORK_MODE = issueWorkMode;
|
|
1188
|
+
if (wakeReason)
|
|
1189
|
+
env.PAPERCLIP_WAKE_REASON = wakeReason;
|
|
1190
|
+
if (wakeCommentId)
|
|
1191
|
+
env.PAPERCLIP_WAKE_COMMENT_ID = wakeCommentId;
|
|
1192
|
+
if (approvalId)
|
|
1193
|
+
env.PAPERCLIP_APPROVAL_ID = approvalId;
|
|
1194
|
+
if (approvalStatus)
|
|
1195
|
+
env.PAPERCLIP_APPROVAL_STATUS = approvalStatus;
|
|
1196
|
+
if (linkedIssueIds.length > 0)
|
|
1197
|
+
env.PAPERCLIP_LINKED_ISSUE_IDS = linkedIssueIds.join(",");
|
|
1198
|
+
applyPaperclipWorkspaceEnv(env, {
|
|
1199
|
+
workspaceCwd: shapedWorkspaceEnv.workspaceCwd,
|
|
1200
|
+
workspaceSource,
|
|
1201
|
+
workspaceStrategy,
|
|
1202
|
+
workspaceId,
|
|
1203
|
+
workspaceRepoUrl,
|
|
1204
|
+
workspaceRepoRef,
|
|
1205
|
+
workspaceBranch,
|
|
1206
|
+
workspaceWorktreePath: shapedWorkspaceEnv.workspaceWorktreePath,
|
|
1207
|
+
agentHome,
|
|
1208
|
+
});
|
|
1209
|
+
const shapedEnvConfig = rewriteWorkspaceCwdEnvVarsForExecution({
|
|
1210
|
+
env: envConfig,
|
|
1211
|
+
workspaceCwd: effectiveWorkspaceCwd,
|
|
1212
|
+
executionCwd: shapedWorkspaceEnv.workspaceCwd,
|
|
1213
|
+
executionTargetIsRemote,
|
|
1214
|
+
});
|
|
1215
|
+
// Resolved adapter env (plain + server-resolved secret_ref values) that we
|
|
1216
|
+
// forward to the spawned agent process. Captured so a stable hash of it can be
|
|
1217
|
+
// folded into the session fingerprint below — a change here must invalidate a
|
|
1218
|
+
// warm/resumable session so the next launch picks up the latest env. Only
|
|
1219
|
+
// user/adapter-configured env flows through this loop; per-wake PAPERCLIP_*
|
|
1220
|
+
// runtime vars (PAPERCLIP_RUN_ID, wake/approval ids, ...) were assigned to
|
|
1221
|
+
// `env` above and are never present in shapedEnvConfig, so they inherently
|
|
1222
|
+
// stay out of the hash and don't reset the session every heartbeat.
|
|
1223
|
+
const resolvedAdapterEnv = {};
|
|
1224
|
+
const scratch = parseObject(context.paperclipScratch);
|
|
1225
|
+
const scratchKeys = scratch.type === "heartbeat_run" && typeof scratch.dir === "string"
|
|
1226
|
+
? new Set(["PAPERCLIP_RUN_SCRATCH_DIR", "PAPERCLIP_TASK_SCRATCH_DIR", "PAPERCLIP_SCRATCH_DIR", "PAPERCLIP_TMPDIR",
|
|
1227
|
+
...(Array.isArray(scratch.tempKeysApplied) ? scratch.tempKeysApplied.filter((key) => typeof key === "string" && ["TMPDIR", "TEMP", "TMP"].includes(key)) : [])])
|
|
1228
|
+
: new Set();
|
|
1229
|
+
for (const [key, value] of Object.entries(shapedEnvConfig)) {
|
|
1230
|
+
if (typeof value !== "string")
|
|
1231
|
+
continue;
|
|
1232
|
+
// Runtime PAPERCLIP_* always wins over config: skip a PAPERCLIP_* key that
|
|
1233
|
+
// Paperclip has already assigned this run. PAPERCLIP_API_KEY is never
|
|
1234
|
+
// accepted from config — the harness-minted run token is the only source.
|
|
1235
|
+
// A PAPERCLIP_* key Paperclip did NOT set is stable per-run config, so it
|
|
1236
|
+
// applies and feeds the fingerprint hash below.
|
|
1237
|
+
if (isForbiddenConfigEnvKey(key))
|
|
1238
|
+
continue;
|
|
1239
|
+
if (isPaperclipRuntimeEnvKey(key) && key in env)
|
|
1240
|
+
continue;
|
|
1241
|
+
env[key] = value;
|
|
1242
|
+
// The server rotates run-owned scratch paths on every wake. Still forward
|
|
1243
|
+
// them, but only hash actual adapter settings. User-supplied temp overrides
|
|
1244
|
+
// are absent from tempKeysApplied and keep their compatibility protection.
|
|
1245
|
+
if (!scratchKeys.has(key) || value !== scratch.dir)
|
|
1246
|
+
resolvedAdapterEnv[key] = value;
|
|
1247
|
+
}
|
|
1248
|
+
if (authToken)
|
|
1249
|
+
env.PAPERCLIP_API_KEY = authToken;
|
|
1250
|
+
// For the claude agent, set model via ANTHROPIC_MODEL at startup rather than
|
|
1251
|
+
// via session/set_config_option — the ACP server's set_config_option handler
|
|
1252
|
+
// validates the value against its internal available-models list and rejects
|
|
1253
|
+
// bare model IDs (e.g. "claude-opus-4-7") that don't exactly match a model
|
|
1254
|
+
// entry in some versions. ANTHROPIC_MODEL is read during initialization, so
|
|
1255
|
+
// it reliably sets the model before any turns are run.
|
|
1256
|
+
if (requestedModel && acpxAgent === "claude" && !env.ANTHROPIC_MODEL) {
|
|
1257
|
+
env.ANTHROPIC_MODEL = requestedModel;
|
|
1258
|
+
}
|
|
1259
|
+
if (acpxAgent === "codex") {
|
|
1260
|
+
const codexStartupConfig = buildCodexStartupConfig({
|
|
1261
|
+
existingConfig: env.CODEX_CONFIG,
|
|
1262
|
+
requestedModel,
|
|
1263
|
+
requestedThinkingEffort,
|
|
1264
|
+
fastMode,
|
|
1265
|
+
});
|
|
1266
|
+
if (codexStartupConfig.invalidExistingConfig) {
|
|
1267
|
+
await input.ctx.onLog("stderr", "[paperclip] Ignoring invalid user CODEX_CONFIG while applying runtime Codex settings; expected a JSON object.\n");
|
|
1268
|
+
}
|
|
1269
|
+
if (codexStartupConfig.value)
|
|
1270
|
+
env.CODEX_CONFIG = codexStartupConfig.value;
|
|
1271
|
+
}
|
|
1272
|
+
let skillPromptInstructions = "";
|
|
1273
|
+
let skillsIdentity = { mode: "unsupported" };
|
|
1274
|
+
const skillCommandNotes = [];
|
|
1275
|
+
// The host directory a remote run stages as the `skills` asset. The engine
|
|
1276
|
+
// uses it to rewrite `skillPromptInstructions` and `skillsIdentity` onto
|
|
1277
|
+
// the in-sandbox copy, once `stagedRuntime` is known (see the rewrite
|
|
1278
|
+
// below, after `placeWorkspace` returns). This field is `null` for every
|
|
1279
|
+
// non-Claude agent, and for a Claude run with no skill selected.
|
|
1280
|
+
let claudeSkillsBundleDir = null;
|
|
1281
|
+
let paperclipClaudeSettings = null;
|
|
1282
|
+
if (acpxAgent === "claude") {
|
|
1283
|
+
const preparedSkills = await prepareClaudeSkillRuntime({
|
|
1284
|
+
stateDir,
|
|
1285
|
+
config,
|
|
1286
|
+
moduleDir: input.engine.moduleDir,
|
|
1287
|
+
onLog: input.ctx.onLog,
|
|
1288
|
+
});
|
|
1289
|
+
skillPromptInstructions = preparedSkills.promptInstructions;
|
|
1290
|
+
skillsIdentity = preparedSkills.identity;
|
|
1291
|
+
skillCommandNotes.push(...preparedSkills.commandNotes);
|
|
1292
|
+
claudeSkillsBundleDir = preparedSkills.bundleDir;
|
|
1293
|
+
paperclipClaudeSettings = await writePaperclipClaudeSettings({
|
|
1294
|
+
cwd,
|
|
1295
|
+
stateDir,
|
|
1296
|
+
agentHome,
|
|
1297
|
+
companyId: agent.companyId,
|
|
1298
|
+
});
|
|
1299
|
+
skillCommandNotes.push(`Wrote Paperclip-managed Claude settings to ${paperclipClaudeSettings.filePath} (defaultMode=${paperclipClaudeSettings.defaultMode}${paperclipClaudeSettings.overrodeDontAsk ? "; overrode user dontAsk" : ""}, +${paperclipClaudeSettings.additionalDirectories.length} read root(s), +${paperclipClaudeSettings.allow.length} allow rule(s)).`);
|
|
1300
|
+
}
|
|
1301
|
+
else if (acpxAgent === "codex") {
|
|
1302
|
+
// Step 2 — codex-home.seed: the codex managed-home + skills preparation.
|
|
1303
|
+
// The nested skills.reconcile boundary (step 3) is timed inside via the
|
|
1304
|
+
// threaded onEvent/now seam.
|
|
1305
|
+
const preparedSkills = await measureStartupStep(input.ctx, nowMs, "codex-home.seed", () => prepareCodexSkillRuntime({
|
|
1306
|
+
companyId: agent.companyId,
|
|
1307
|
+
config,
|
|
1308
|
+
env,
|
|
1309
|
+
moduleDir: input.engine.moduleDir,
|
|
1310
|
+
onLog: input.ctx.onLog,
|
|
1311
|
+
onEvent: input.ctx.onEvent,
|
|
1312
|
+
now: nowMs,
|
|
1313
|
+
stepMetrics,
|
|
1314
|
+
}), stepMetrics);
|
|
1315
|
+
skillsIdentity = preparedSkills.identity;
|
|
1316
|
+
skillCommandNotes.push(...preparedSkills.commandNotes);
|
|
1317
|
+
}
|
|
1318
|
+
else if (acpxAgent === "gemini") {
|
|
1319
|
+
const preparedSkills = await prepareGeminiSkillRuntime({
|
|
1320
|
+
config,
|
|
1321
|
+
moduleDir: input.engine.moduleDir,
|
|
1322
|
+
onLog: input.ctx.onLog,
|
|
1323
|
+
});
|
|
1324
|
+
skillsIdentity = preparedSkills.identity;
|
|
1325
|
+
skillCommandNotes.push(...preparedSkills.commandNotes);
|
|
1326
|
+
}
|
|
1327
|
+
else {
|
|
1328
|
+
// A minimal, separate Grok seam: only the company-scoped `GROK_HOME`
|
|
1329
|
+
// binding, so a Grok run authenticates from the credential a completed
|
|
1330
|
+
// device login wrote. This never touches `prepareCodexSkillRuntime` above
|
|
1331
|
+
// — that function stays Codex-only — and every other custom ACPX agent
|
|
1332
|
+
// (for example `kimi`) falls through this branch unaffected.
|
|
1333
|
+
if (acpxAgent === "grok" && !config.managedAiConnection) {
|
|
1334
|
+
env.GROK_HOME = resolveManagedGrokHomeDir(agent.companyId);
|
|
1335
|
+
}
|
|
1336
|
+
const desired = resolveLegacyPaperclipDesiredSkillNames(config, await readPaperclipRuntimeSkillEntries(config, input.engine.moduleDir));
|
|
1337
|
+
skillsIdentity = { mode: "custom_unsupported", desiredSkillNames: desired };
|
|
1338
|
+
if (desired.length > 0) {
|
|
1339
|
+
skillCommandNotes.push("Selected Paperclip skills are tracked only; ACPX custom commands do not expose a runtime skill contract yet.");
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
const configuredCommand = asString(config.agentCommand, "").trim();
|
|
1343
|
+
const builtInCommand = await resolveBuiltInAgentCommand({
|
|
1344
|
+
agent: acpxAgent,
|
|
1345
|
+
packageRootDir: input.engine.packageRootDir,
|
|
1346
|
+
executionTargetIsRemote,
|
|
1347
|
+
});
|
|
1348
|
+
let agentCommand = configuredCommand || builtInCommand?.command || null;
|
|
1349
|
+
let agentCommandShell = configuredCommand || builtInCommand?.shellCommand || "";
|
|
1350
|
+
// A runner-backed remote sandbox is the only lane that crosses the staging
|
|
1351
|
+
// and serialized-launch-env seam. Runner-less ACP→CLI fallback, SSH, and
|
|
1352
|
+
// local runs keep their historical host-provider compatibility behavior.
|
|
1353
|
+
const useRemoteProcessSession = executionTarget?.kind === "remote" &&
|
|
1354
|
+
executionTarget.transport === "sandbox" &&
|
|
1355
|
+
Boolean(executionTarget.runner) &&
|
|
1356
|
+
Boolean(agentCommandShell);
|
|
1357
|
+
if (acpxAgent === "gemini" && agentCommandShell) {
|
|
1358
|
+
const normalized = await normalizeGeminiAcpCommandShell(agentCommandShell, resolveRuntimeEnv(env, acpxAgent, {
|
|
1359
|
+
inheritHostEnvironment: !useRemoteProcessSession,
|
|
1360
|
+
}));
|
|
1361
|
+
if (normalized !== agentCommandShell) {
|
|
1362
|
+
agentCommandShell = normalized;
|
|
1363
|
+
agentCommand = normalized;
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
const childStderrDir = path.join(stateDir, "run-stderr");
|
|
1367
|
+
const childStderrLogPath = agentCommand ? path.join(childStderrDir, `${runId}.log`) : null;
|
|
1368
|
+
// Stream the agent output through the persistent session log stream instead of
|
|
1369
|
+
// the host output-file poll. The decision comes from the effective capability
|
|
1370
|
+
// snapshot alone: the provider must declare and verify incremental session
|
|
1371
|
+
// output. `incrementalSessionOutput` is an opt-in capability, so a generic
|
|
1372
|
+
// one-shot provider that keeps persistent process sessions and runs independent
|
|
1373
|
+
// control commands, yet never emits incremental session output, keeps the poll
|
|
1374
|
+
// path. The snapshot resolves this key false when it is absent, undeclared, or
|
|
1375
|
+
// capability resolution failed, so this fails closed to the poll path.
|
|
1376
|
+
const streamAgentSessionOutput = executionTarget?.kind === "remote" &&
|
|
1377
|
+
executionTarget.transport === "sandbox" &&
|
|
1378
|
+
executionTarget.effectiveCapabilities?.incrementalSessionOutput === true;
|
|
1379
|
+
// The ACP `session/new` cwd and every cwd-keyed session-state site
|
|
1380
|
+
// (fingerprint, compat, persist, ensureSession, error) bind to THIS single
|
|
1381
|
+
// value so a warm/resumable session created with the in-sandbox cwd is reused
|
|
1382
|
+
// — not invalidated — on the next run. Remote runner-backed → the in-sandbox
|
|
1383
|
+
// workspace dir; local and the runner-less fallback → the HOST cwd,
|
|
1384
|
+
// byte-identical to today.
|
|
1385
|
+
//
|
|
1386
|
+
// PR 3: the staging transport derives the in-sandbox workspace dir
|
|
1387
|
+
// deterministically from the target's `remoteCwd` (it is exactly `remoteCwd`
|
|
1388
|
+
// for the sandbox transport), so we resolve `sessionCwd` — and therefore the
|
|
1389
|
+
// session fingerprint / cache key — BEFORE staging. That lets a compatible
|
|
1390
|
+
// resume decide to reuse an already-staged runtime instead of re-shipping the
|
|
1391
|
+
// workspace / re-seeding the managed home. The stage call below pins its
|
|
1392
|
+
// `workspaceRemoteDir` to this same value, so the staged cwd can never
|
|
1393
|
+
// diverge from the cwd that fed the fingerprint.
|
|
1394
|
+
const sessionCwd = useRemoteProcessSession && executionTarget?.kind === "remote"
|
|
1395
|
+
? executionTarget.remoteCwd
|
|
1396
|
+
: cwd;
|
|
1397
|
+
// The 17 fields the session fingerprint hashes. Company, agent, and task
|
|
1398
|
+
// identifiers are NOT here; they scope the outer session key only (see
|
|
1399
|
+
// `keyIdentity`). The fingerprint builder accepts only this identity.
|
|
1400
|
+
const fingerprintIdentity = {
|
|
1401
|
+
acpxAgent,
|
|
1402
|
+
agentCommand: agentCommand ?? acpxAgent,
|
|
1403
|
+
cwd: path.resolve(sessionCwd),
|
|
1404
|
+
mode,
|
|
1405
|
+
permissionMode,
|
|
1406
|
+
nonInteractivePermissions,
|
|
1407
|
+
requestedModel,
|
|
1408
|
+
requestedThinkingEffort,
|
|
1409
|
+
fastMode,
|
|
1410
|
+
remoteExecutionIdentity,
|
|
1411
|
+
// Referenced-project set + pinned-checkout identity. A change here (a project
|
|
1412
|
+
// added, removed, or re-pinned) invalidates a warm/resumable session so the
|
|
1413
|
+
// next launch stages the current referenced-project trees instead of reusing
|
|
1414
|
+
// a stale staged tree.
|
|
1415
|
+
additionalSourcesIdentity: additionalSourcesIdentity,
|
|
1416
|
+
skillsIdentity,
|
|
1417
|
+
skillPromptInstructions,
|
|
1418
|
+
paperclipClaudeSettings: paperclipClaudeSettings
|
|
1419
|
+
? {
|
|
1420
|
+
allow: paperclipClaudeSettings.allow,
|
|
1421
|
+
additionalDirectories: paperclipClaudeSettings.additionalDirectories,
|
|
1422
|
+
defaultMode: paperclipClaudeSettings.defaultMode,
|
|
1423
|
+
}
|
|
1424
|
+
: null,
|
|
1425
|
+
mcpServers: mcpIdentity,
|
|
1426
|
+
secretManifestHash: shortHash(secretManifest),
|
|
1427
|
+
// Fold the resolved adapter env (all applied user-configured values —
|
|
1428
|
+
// plain, secret_ref, and stable PAPERCLIP_* config such as an explicit
|
|
1429
|
+
// PAPERCLIP_API_KEY) into the fingerprint so a change to any forwarded value
|
|
1430
|
+
// invalidates a warm handle / resumable session and forces a fresh launch
|
|
1431
|
+
// that sources the latest env. secretManifestHash alone misses plain-value
|
|
1432
|
+
// edits and same-version secret rotations. Per-wake runtime vars never enter
|
|
1433
|
+
// resolvedAdapterEnv, so they don't churn the fingerprint every heartbeat.
|
|
1434
|
+
adapterEnvHash: shortHash(resolvedAdapterEnv),
|
|
1435
|
+
};
|
|
1436
|
+
const fingerprint = buildSessionFingerprint(fingerprintIdentity);
|
|
1437
|
+
const taskKey = asString(input.ctx.runtime.taskKey, "") || wakeTaskId || workspaceId || "default";
|
|
1438
|
+
// Company, agent, and task identity for the outer session key. These parts
|
|
1439
|
+
// stay out of the fingerprint hash.
|
|
1440
|
+
const keyIdentity = {
|
|
1441
|
+
companyId: agent.companyId,
|
|
1442
|
+
agentId: agent.id,
|
|
1443
|
+
taskKey,
|
|
1444
|
+
};
|
|
1445
|
+
const sessionKey = buildSessionKey(keyIdentity, fingerprint);
|
|
1446
|
+
// Ship the workspace into the sandbox and capture `{ workspaceRemoteDir,
|
|
1447
|
+
// runtimeRootDir, assetDirs, restoreWorkspace }`. Done once here, before the
|
|
1448
|
+
// bridges, so both bridges receive the real (non-null) `runtimeRootDir`.
|
|
1449
|
+
//
|
|
1450
|
+
// PR 2: on the remote lane, delegate staging to the per-adapter
|
|
1451
|
+
// `prepareRemoteManagedHome` seam when the adapter supplies one. The seam
|
|
1452
|
+
// ships the adapter's managed home as an `assets` entry (through the `stage`
|
|
1453
|
+
// callback = `stageAcpRemoteRuntime`), repoints the home env var (`env`) onto
|
|
1454
|
+
// the in-sandbox `assetDirs.*` path, and returns a `teardown` (per-run codex
|
|
1455
|
+
// auth copy-back via `restoreWorkspace()`) plus a `disposeStaged` (one-time
|
|
1456
|
+
// staged-temp cleanup). Without a seam (custom agents / shared-engine tests)
|
|
1457
|
+
// the engine stages the workspace with no home asset — identical to PR-1.
|
|
1458
|
+
//
|
|
1459
|
+
// PR 3 (stage once per session): a COMPATIBLE resume whose fingerprint matches
|
|
1460
|
+
// this exact `sessionKey` reuses the already-staged in-sandbox runtime — no
|
|
1461
|
+
// workspace re-ship, no home re-seed — while an incompatible fingerprint (a
|
|
1462
|
+
// different key) misses the cache and stages fresh. The `sessionKey`
|
|
1463
|
+
// (`companyId:agentId:taskKey:fingerprint`) is the single scoping key, so one
|
|
1464
|
+
// session can never read another session's staged credentials. The cache is
|
|
1465
|
+
// populated by the executor only after a clean turn and dropped on
|
|
1466
|
+
// failure/cancel/timeout, so it always holds a known-good staged runtime.
|
|
1467
|
+
//
|
|
1468
|
+
// Two guards close the concurrency / cross-session windows Greptile flagged:
|
|
1469
|
+
// * Compatibility gate: reuse only when the supplied session params actually
|
|
1470
|
+
// resume THIS staged session (the same `isCompatibleSession` predicate the
|
|
1471
|
+
// warm-handle path uses). A fresh invocation with missing/cleared
|
|
1472
|
+
// `sessionParams` starts a new ACP session via `session/new`, so it must
|
|
1473
|
+
// NOT inherit the prior session's staged home/credentials — it stages
|
|
1474
|
+
// fresh even when company/agent/task/fingerprint (and hence sessionKey)
|
|
1475
|
+
// collide.
|
|
1476
|
+
// * Per-key staging lock: the stage-or-reuse decision runs under a
|
|
1477
|
+
// `sessionKey` mutex so two overlapping runs of the same session can never
|
|
1478
|
+
// ship into the same remote workspace at once (the loser waits, then
|
|
1479
|
+
// re-checks the cache before deciding).
|
|
1480
|
+
const stagedRuntimes = input.deps.stagedRuntimes ?? defaultStagedRuntimes;
|
|
1481
|
+
const stagingLocks = input.deps.stagingLocks ?? defaultStagingLocks;
|
|
1482
|
+
const previousParams = parseObject(input.ctx.runtime.sessionParams);
|
|
1483
|
+
const isCompatibleResume = isCompatibleSession(previousParams, {
|
|
1484
|
+
fingerprint,
|
|
1485
|
+
sessionKey,
|
|
1486
|
+
cwd: sessionCwd,
|
|
1487
|
+
mode,
|
|
1488
|
+
acpxAgent,
|
|
1489
|
+
remoteExecutionIdentity,
|
|
1490
|
+
});
|
|
1491
|
+
let stagedRuntime = null;
|
|
1492
|
+
let remoteManagedHomeTeardown = null;
|
|
1493
|
+
let remoteStagingDispose = null;
|
|
1494
|
+
let remoteStagingEnvDelta = null;
|
|
1495
|
+
let sessionStagingLeaseRelease = null;
|
|
1496
|
+
// The sandbox run site owns the sandbox lane's staging, the per-session
|
|
1497
|
+
// staging lease, both host-side bridges, the launch-environment contribution,
|
|
1498
|
+
// and sync-back. `buildRuntime` injects the leaf primitives (the workspace
|
|
1499
|
+
// stage call, the managed-home seam, the two bridge starts, the step timers,
|
|
1500
|
+
// and the launch-env finalizer) and reads the site's results back onto the
|
|
1501
|
+
// prepared-runtime fields, so the existing settlement path stays unchanged.
|
|
1502
|
+
let sandboxSite = null;
|
|
1503
|
+
if (useRemoteProcessSession && executionTarget?.kind === "remote") {
|
|
1504
|
+
const remoteTarget = executionTarget;
|
|
1505
|
+
sandboxSite = createSandboxRunSite({
|
|
1506
|
+
ledger: input.ledger,
|
|
1507
|
+
stagedRuntimes,
|
|
1508
|
+
stagingLocks,
|
|
1509
|
+
now: nowMs,
|
|
1510
|
+
idleMs: input.stagedIdleMs,
|
|
1511
|
+
sessionCwd,
|
|
1512
|
+
spawnCwd: cwd,
|
|
1513
|
+
target: remoteTarget,
|
|
1514
|
+
env,
|
|
1515
|
+
isCompatibleResume,
|
|
1516
|
+
stage: (assets) => stageAcpRemoteRuntime({
|
|
1517
|
+
runId,
|
|
1518
|
+
target: remoteTarget,
|
|
1519
|
+
adapterKey: input.engine.adapterType,
|
|
1520
|
+
workspaceLocalDir: cwd,
|
|
1521
|
+
workspaceRemoteDir: sessionCwd,
|
|
1522
|
+
timeoutSec,
|
|
1523
|
+
assets,
|
|
1524
|
+
additionalSources,
|
|
1525
|
+
onLog: input.ctx.onLog,
|
|
1526
|
+
onRuntimeProgress: input.ctx.onRuntimeProgress,
|
|
1527
|
+
runtimeSpan: input.stageRuntimeSpan,
|
|
1528
|
+
}),
|
|
1529
|
+
seedManagedHome: input.deps.prepareRemoteManagedHome
|
|
1530
|
+
? async (stage) => {
|
|
1531
|
+
const seeded = await input.deps.prepareRemoteManagedHome({
|
|
1532
|
+
acpxAgent,
|
|
1533
|
+
companyId: agent.companyId,
|
|
1534
|
+
runId,
|
|
1535
|
+
config,
|
|
1536
|
+
executionTarget: remoteTarget,
|
|
1537
|
+
workspaceLocalDir: cwd,
|
|
1538
|
+
timeoutSec,
|
|
1539
|
+
env,
|
|
1540
|
+
onLog: input.ctx.onLog,
|
|
1541
|
+
onRuntimeProgress: input.ctx.onRuntimeProgress,
|
|
1542
|
+
skillsBundleDir: claudeSkillsBundleDir,
|
|
1543
|
+
stage,
|
|
1544
|
+
});
|
|
1545
|
+
return {
|
|
1546
|
+
stagedRuntime: seeded.stagedRuntime,
|
|
1547
|
+
teardown: seeded.teardown ?? null,
|
|
1548
|
+
dispose: seeded.disposeStaged ?? null,
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1551
|
+
: undefined,
|
|
1552
|
+
disposeFreshStagedRuntime: (freshStagedRuntime) => disposeFreshStagedRuntime({
|
|
1553
|
+
runId,
|
|
1554
|
+
target: remoteTarget,
|
|
1555
|
+
stagedRuntime: freshStagedRuntime,
|
|
1556
|
+
cwd: sessionCwd,
|
|
1557
|
+
timeoutSec,
|
|
1558
|
+
onLog: input.ctx.onLog,
|
|
1559
|
+
}),
|
|
1560
|
+
measureStageStep: (run) => measureStartupStep(input.ctx, nowMs, "stage.sync", run, stepMetrics),
|
|
1561
|
+
publishStagedProjectHints: (stagedProjectDirs) => {
|
|
1562
|
+
const shapedHints = shapePaperclipWorkspaceEnvForExecution({
|
|
1563
|
+
workspaceCwd: effectiveWorkspaceCwd,
|
|
1564
|
+
workspaceWorktreePath,
|
|
1565
|
+
workspaceHints,
|
|
1566
|
+
executionTargetIsRemote,
|
|
1567
|
+
executionCwd: effectiveExecutionCwd,
|
|
1568
|
+
stagedProjectDirs,
|
|
1569
|
+
}).workspaceHints;
|
|
1570
|
+
if (shapedHints.length > 0) {
|
|
1571
|
+
env.PAPERCLIP_WORKSPACES_JSON = JSON.stringify(shapedHints);
|
|
1572
|
+
}
|
|
1573
|
+
},
|
|
1574
|
+
onReuseLog: () => input.ctx.onLog("stdout", "[paperclip] Reusing the staged in-sandbox runtime for this resumed session (no workspace re-ship / managed-home re-seed).\n"),
|
|
1575
|
+
startPaperclipBridge: (runtimeRootDir) => startAdapterExecutionTargetPaperclipBridge({
|
|
1576
|
+
runId,
|
|
1577
|
+
target: { ...remoteTarget, streamRunLogs: false },
|
|
1578
|
+
runtimeRootDir,
|
|
1579
|
+
adapterKey: input.engine.adapterType,
|
|
1580
|
+
timeoutSec,
|
|
1581
|
+
hostApiToken: env.PAPERCLIP_API_KEY,
|
|
1582
|
+
enableSandboxDuplexBridge: adapterExecutionTargetEnablesSandboxDuplexBridge(remoteTarget),
|
|
1583
|
+
duplexObservabilityRecorder: adapterExecutionTargetDuplexObservabilityRecorder(remoteTarget),
|
|
1584
|
+
onLog: input.ctx.onLog,
|
|
1585
|
+
getRuntimeParentContext: input.getRuntimeParentContext,
|
|
1586
|
+
runtimeSpan: input.runtimeSpan,
|
|
1587
|
+
}),
|
|
1588
|
+
startProcessSessionBridge: ({ runtimeRootDir, launchEnv }) => startAdapterExecutionTargetProcessSessionBridge({
|
|
1589
|
+
runId,
|
|
1590
|
+
target: remoteTarget,
|
|
1591
|
+
runtimeRootDir,
|
|
1592
|
+
adapterKey: input.engine.adapterType,
|
|
1593
|
+
command: "sh",
|
|
1594
|
+
args: ["-lc", `exec ${agentCommandShell}`],
|
|
1595
|
+
cwd: sessionCwd,
|
|
1596
|
+
env: launchEnv,
|
|
1597
|
+
timeoutSec,
|
|
1598
|
+
onLog: input.ctx.onLog,
|
|
1599
|
+
getRuntimeParentContext: input.getRuntimeParentContext,
|
|
1600
|
+
runtimeSpan: input.runtimeSpan,
|
|
1601
|
+
streamOutputViaSession: streamAgentSessionOutput,
|
|
1602
|
+
}),
|
|
1603
|
+
measureBridgeStep: (step, run) => measureStartupStep(input.ctx, nowMs, step, run, concurrentBridgeStepMetrics),
|
|
1604
|
+
finalizeLaunchEnv: (contributions) => finalizeLaunchEnvironment(env, contributions, {
|
|
1605
|
+
acpxAgent,
|
|
1606
|
+
inheritHostEnvironment: !useRemoteProcessSession,
|
|
1607
|
+
}).env,
|
|
1608
|
+
onPaperclipBridgeLog: () => input.ctx.onLog("stdout", "[paperclip] Sandbox ACP API callback bridge enabled for this run.\n"),
|
|
1609
|
+
stopBridges: async ({ controlBridge, agentBridge }) => {
|
|
1610
|
+
await Promise.allSettled([agentBridge?.stop(), controlBridge?.stop()]);
|
|
1611
|
+
if (remoteManagedHomeTeardown) {
|
|
1612
|
+
await remoteManagedHomeTeardown().catch(() => { });
|
|
1613
|
+
}
|
|
1614
|
+
},
|
|
1615
|
+
});
|
|
1616
|
+
// Place the workspace (stage fresh or reuse the already-staged runtime) under
|
|
1617
|
+
// the per-session staging lease, then read the staged result back onto the
|
|
1618
|
+
// prepared-runtime fields the settlement path consumes.
|
|
1619
|
+
const placeWorkspaceStart = nowMs();
|
|
1620
|
+
await sandboxSite.placeWorkspace({ sessionKey });
|
|
1621
|
+
await emitRunPhaseTiming(input.ctx, "place_workspace", nowMs() - placeWorkspaceStart, "ok");
|
|
1622
|
+
const placedStaged = sandboxSite.staged;
|
|
1623
|
+
stagedRuntime = placedStaged?.stagedRuntime ?? null;
|
|
1624
|
+
remoteManagedHomeTeardown = placedStaged?.teardown ?? null;
|
|
1625
|
+
remoteStagingDispose = placedStaged?.dispose ?? null;
|
|
1626
|
+
remoteStagingEnvDelta = placedStaged?.envDelta ?? null;
|
|
1627
|
+
sessionStagingLeaseRelease = sandboxSite.stagingLeaseRelease;
|
|
1628
|
+
}
|
|
1629
|
+
// Once the skill bundle is staged, rewrite the prompt and the identity onto
|
|
1630
|
+
// the in-sandbox copy. This code runs here, after `placeWorkspace` resolves
|
|
1631
|
+
// `stagedRuntime`. It runs on every invocation, both a fresh stage and a
|
|
1632
|
+
// compatible resume. It never runs inside the `prepareRemoteManagedHome`
|
|
1633
|
+
// seam, because a compatible resume never calls that seam again.
|
|
1634
|
+
// `skillPromptInstructions` and `skillsIdentity` already fed `fingerprint`
|
|
1635
|
+
// above, with the host-independent identity. So this rewrite never reaches
|
|
1636
|
+
// the fingerprint: it only replaces the host bundle path with the
|
|
1637
|
+
// in-sandbox path, in the local copies used for the returned prompt,
|
|
1638
|
+
// identity, and command notes.
|
|
1639
|
+
if (acpxAgent === "claude" && stagedRuntime && claudeSkillsBundleDir) {
|
|
1640
|
+
const inSandboxSkillsRoot = stagedRuntime.assetDirs.skills ??
|
|
1641
|
+
path.posix.join(stagedRuntime.runtimeRootDir ??
|
|
1642
|
+
path.posix.join(stagedRuntime.workspaceRemoteDir ?? cwd, ".paperclip-runtime", acpxAgent), "skills");
|
|
1643
|
+
const rebaseToSandbox = (value) => value.split(claudeSkillsBundleDir).join(inSandboxSkillsRoot);
|
|
1644
|
+
skillPromptInstructions = rebaseToSandbox(skillPromptInstructions);
|
|
1645
|
+
skillsIdentity = {
|
|
1646
|
+
...skillsIdentity,
|
|
1647
|
+
skillRoot: typeof skillsIdentity.skillRoot === "string"
|
|
1648
|
+
? rebaseToSandbox(skillsIdentity.skillRoot)
|
|
1649
|
+
: skillsIdentity.skillRoot,
|
|
1650
|
+
};
|
|
1651
|
+
for (let i = 0; i < skillCommandNotes.length; i += 1) {
|
|
1652
|
+
skillCommandNotes[i] = rebaseToSandbox(skillCommandNotes[i]);
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
// Both bridge starts run under one try so a failure at EITHER — including the
|
|
1656
|
+
// paperclip callback bridge — fires the same abandon-path cleanup. The
|
|
1657
|
+
// paperclip bridge starts after the workspace + managed home were already
|
|
1658
|
+
// staged and the per-session staging lease is already held, so leaving it
|
|
1659
|
+
// outside the catch would strand the lease (and the staged temp) on a
|
|
1660
|
+
// start failure and deadlock the next run of this session.
|
|
1661
|
+
let paperclipBridge = null;
|
|
1662
|
+
let processSessionBridge = null;
|
|
1663
|
+
let runtimeEnv = {};
|
|
1664
|
+
const startTransportStart = nowMs();
|
|
1665
|
+
try {
|
|
1666
|
+
if (useRemoteProcessSession && sandboxSite) {
|
|
1667
|
+
// The sandbox run site brings up both host-side bridges concurrently, keeps
|
|
1668
|
+
// the one paperclip-env → process-session-launch dependency at a single
|
|
1669
|
+
// sequencing point, settles both starts, and returns the started handles
|
|
1670
|
+
// plus the finalized launch env. On a partial failure it stops nothing and
|
|
1671
|
+
// rethrows; the catch below stops whichever bridge the site started.
|
|
1672
|
+
const transport = await sandboxSite.startTransport({ sessionKey });
|
|
1673
|
+
paperclipBridge = transport.controlBridge;
|
|
1674
|
+
processSessionBridge = transport.agentBridge;
|
|
1675
|
+
runtimeEnv = transport.launchEnv;
|
|
1676
|
+
await emitRunPhaseTiming(input.ctx, "start_transport", nowMs() - startTransportStart, "ok");
|
|
1677
|
+
}
|
|
1678
|
+
else {
|
|
1679
|
+
// Local / runner-less lanes never start a bridge, so they add no
|
|
1680
|
+
// contribution. `finalizeLaunchEnvironment` still produces the one branded
|
|
1681
|
+
// launch env the prepared runtime and the log builder read.
|
|
1682
|
+
runtimeEnv = finalizeLaunchEnvironment(env, [], {
|
|
1683
|
+
acpxAgent,
|
|
1684
|
+
inheritHostEnvironment: !useRemoteProcessSession,
|
|
1685
|
+
}).env;
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
catch (err) {
|
|
1689
|
+
// On a partial concurrent bring-up failure, ONE bridge may have started while
|
|
1690
|
+
// the other threw; `Promise.allSettled` stops whichever started so no live
|
|
1691
|
+
// bridge leaks (mirrors the settlement `stopTransport` step). The site sets its
|
|
1692
|
+
// started bridges before it rethrows, so read them from the site here (the
|
|
1693
|
+
// local handles stay null when `startTransport` throws before it returns).
|
|
1694
|
+
const startedControl = sandboxSite?.controlBridge ?? paperclipBridge;
|
|
1695
|
+
const startedAgent = sandboxSite?.agentBridge ?? processSessionBridge;
|
|
1696
|
+
await Promise.allSettled([startedControl?.stop(), startedAgent?.stop()]);
|
|
1697
|
+
// The staged home / copy-back teardown must run even if a bridge fails to
|
|
1698
|
+
// start after the workspace + managed home were already staged into the
|
|
1699
|
+
// sandbox, so a refreshed credential is copied back on this error path too.
|
|
1700
|
+
// This run never reaches the executor, so also fire the one-time staged-temp
|
|
1701
|
+
// dispose here (it no longer rides the per-run copy-back) — the run is being
|
|
1702
|
+
// abandoned, so its staged temp must be released — and release the per-session
|
|
1703
|
+
// staging lease so the abandoned run does not strand the next same-session run
|
|
1704
|
+
// (the run root `finally`, which normally releases it, is never reached here).
|
|
1705
|
+
//
|
|
1706
|
+
// Route the dispose through the same ownership-guarded removal
|
|
1707
|
+
// `discardStagedRuntime` uses. When this run BORROWED the cached staged runtime
|
|
1708
|
+
// (a compatible resume), the dispose releases the shared host staged-temp, so
|
|
1709
|
+
// the map entry must go too — otherwise a later resume reuses an entry whose
|
|
1710
|
+
// temp is already gone. The identity guard removes the entry only when the map
|
|
1711
|
+
// still holds this exact staged runtime, so a fresh stage (no cache entry) and
|
|
1712
|
+
// a concurrent run's entry are both left untouched.
|
|
1713
|
+
await remoteManagedHomeTeardown?.().catch(() => { });
|
|
1714
|
+
await releaseStagedRuntimeEntry({
|
|
1715
|
+
handles: stagedRuntimes,
|
|
1716
|
+
sessionKey,
|
|
1717
|
+
stagedRuntime,
|
|
1718
|
+
dispose: remoteStagingDispose,
|
|
1719
|
+
});
|
|
1720
|
+
sessionStagingLeaseRelease?.();
|
|
1721
|
+
await emitRunPhaseTiming(input.ctx, "start_transport", nowMs() - startTransportStart, "failed");
|
|
1722
|
+
throw err;
|
|
1723
|
+
}
|
|
1724
|
+
// The relay runs on the host with the sanitized remote launch environment.
|
|
1725
|
+
// Its /usr/bin/env node shebang cannot rely on that environment's PATH.
|
|
1726
|
+
const overrideCommand = processSessionBridge?.agentCommand
|
|
1727
|
+
? [process.execPath, processSessionBridge.agentCommand]
|
|
1728
|
+
.map((part) => JSON.stringify(part.replaceAll("\\", "/"))).join(" ")
|
|
1729
|
+
: agentCommand;
|
|
1730
|
+
const overrides = overrideCommand ? { [acpxAgent]: overrideCommand } : undefined;
|
|
1731
|
+
const agentRegistry = createAgentRegistry({ overrides });
|
|
1732
|
+
const loggedEnv = buildInvocationEnvForLogs(env, {
|
|
1733
|
+
runtimeEnv,
|
|
1734
|
+
includeRuntimeKeys: ["HOME"],
|
|
1735
|
+
resolvedCommand: agentCommand ?? acpxAgent,
|
|
1736
|
+
});
|
|
1737
|
+
return {
|
|
1738
|
+
acpxAgent,
|
|
1739
|
+
coalescePlaceholderToolUpdates,
|
|
1740
|
+
mode,
|
|
1741
|
+
// Remote runner-backed → the in-sandbox workspace dir; local / runner-less
|
|
1742
|
+
// → the HOST cwd (`sessionCwd` resolves both). Every cwd-keyed session site
|
|
1743
|
+
// reads `prepared.cwd`, so binding it once here keeps them consistent.
|
|
1744
|
+
cwd: sessionCwd,
|
|
1745
|
+
// Only the remote process-session lane needs the host proxy's `spawn()`
|
|
1746
|
+
// `chdir` redirected off the in-sandbox `sessionCwd` and onto the host
|
|
1747
|
+
// `cwd` (which is where the workspace was staged FROM, so it is host-valid).
|
|
1748
|
+
// Every other lane leaves it `undefined` → acpx falls back to `cwd`.
|
|
1749
|
+
hostSpawnCwd: useRemoteProcessSession ? cwd : undefined,
|
|
1750
|
+
workspaceId,
|
|
1751
|
+
workspaceRepoUrl,
|
|
1752
|
+
workspaceRepoRef,
|
|
1753
|
+
env: runtimeEnv,
|
|
1754
|
+
loggedEnv,
|
|
1755
|
+
stateDir,
|
|
1756
|
+
permissionMode,
|
|
1757
|
+
nonInteractivePermissions,
|
|
1758
|
+
requestedModel,
|
|
1759
|
+
requestedThinkingEffort,
|
|
1760
|
+
fastMode,
|
|
1761
|
+
timeoutSec,
|
|
1762
|
+
timeoutResolution,
|
|
1763
|
+
sessionKey,
|
|
1764
|
+
fingerprint,
|
|
1765
|
+
agentCommand,
|
|
1766
|
+
agentRegistry,
|
|
1767
|
+
processSessionBridge,
|
|
1768
|
+
paperclipBridge,
|
|
1769
|
+
stagedRuntime,
|
|
1770
|
+
remoteManagedHomeTeardown,
|
|
1771
|
+
remoteStagingDispose,
|
|
1772
|
+
remoteStagingEnvDelta,
|
|
1773
|
+
sessionStagingLeaseRelease,
|
|
1774
|
+
remoteExecutionIdentity,
|
|
1775
|
+
skillPromptInstructions,
|
|
1776
|
+
skillsIdentity: {
|
|
1777
|
+
...skillsIdentity,
|
|
1778
|
+
commandNotes: skillCommandNotes,
|
|
1779
|
+
},
|
|
1780
|
+
childStderrLogPath,
|
|
1781
|
+
paperclipClaudeSettings,
|
|
1782
|
+
mcpServers,
|
|
1783
|
+
mcpIdentity,
|
|
1784
|
+
stepMetrics,
|
|
1785
|
+
};
|
|
1786
|
+
}
|
|
1787
|
+
function sessionConfigOptions(prepared) {
|
|
1788
|
+
const options = [];
|
|
1789
|
+
// Claude and Codex runtime config is pre-set via startup env vars; skip
|
|
1790
|
+
// set_config_option to avoid ACP-server picker validation rejecting valid
|
|
1791
|
+
// backend model IDs that are not advertised by the local ACP server.
|
|
1792
|
+
if (prepared.requestedModel &&
|
|
1793
|
+
prepared.acpxAgent !== "claude" &&
|
|
1794
|
+
prepared.acpxAgent !== "codex") {
|
|
1795
|
+
options.push({ key: "model", value: prepared.requestedModel });
|
|
1796
|
+
}
|
|
1797
|
+
if (prepared.requestedThinkingEffort && prepared.acpxAgent !== "codex") {
|
|
1798
|
+
options.push({
|
|
1799
|
+
key: "effort",
|
|
1800
|
+
value: prepared.requestedThinkingEffort,
|
|
1801
|
+
});
|
|
1802
|
+
}
|
|
1803
|
+
if (prepared.fastMode && prepared.acpxAgent !== "codex") {
|
|
1804
|
+
options.push({ key: "service_tier", value: "fast" }, { key: "features.fast_mode", value: "true" });
|
|
1805
|
+
}
|
|
1806
|
+
return options;
|
|
1807
|
+
}
|
|
1808
|
+
async function applySessionConfigOptions(input) {
|
|
1809
|
+
const options = sessionConfigOptions(input.prepared);
|
|
1810
|
+
if (options.length === 0)
|
|
1811
|
+
return;
|
|
1812
|
+
if (!input.runtime.setConfigOption) {
|
|
1813
|
+
const message = "ACPX runtime does not expose session config controls; upgrade ACPX or remove configured model, effort, and fast mode overrides.";
|
|
1814
|
+
await input.onLog("stderr", `[paperclip] ${message}\n`);
|
|
1815
|
+
throw new Error(message);
|
|
1816
|
+
}
|
|
1817
|
+
for (const option of options) {
|
|
1818
|
+
await input.runtime.setConfigOption({
|
|
1819
|
+
handle: input.handle,
|
|
1820
|
+
key: option.key,
|
|
1821
|
+
value: option.value,
|
|
1822
|
+
});
|
|
1823
|
+
await input.onLog("stdout", `[paperclip] Applied ACPX ${input.prepared.acpxAgent} config ${option.key}=${option.value}\n`);
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
/**
|
|
1827
|
+
* Build the process-session launch env: the target-specific host projection
|
|
1828
|
+
* overlaid with the run's explicit `env` (so adapter config, runtime variables,
|
|
1829
|
+
* and bridge contributions win), narrowed to string values. Host-side launches
|
|
1830
|
+
* get the closed projection plus a default `PATH` when the host did not provide
|
|
1831
|
+
* one. Runner-backed remote sandbox launches get no ambient host state or
|
|
1832
|
+
* synthesized host `PATH`; omitting `PATH` preserves the sandbox-native value.
|
|
1833
|
+
*/
|
|
1834
|
+
function resolveRuntimeEnv(env, acpxAgent, options) {
|
|
1835
|
+
const inheritedEnv = options.inheritedEnv ?? process.env;
|
|
1836
|
+
const projectedHostEnv = projectAcpxInheritedHostEnvironment(inheritedEnv, acpxAgent, options.inheritHostEnvironment);
|
|
1837
|
+
const inheritedLaunchEnv = options.inheritHostEnvironment
|
|
1838
|
+
? ensurePathInEnv(projectedHostEnv)
|
|
1839
|
+
: projectedHostEnv;
|
|
1840
|
+
const mergedEnv = mergeRuntimeEnvironment(inheritedLaunchEnv, env, (options.platform ?? process.platform) === "win32");
|
|
1841
|
+
const finalEnv = Object.fromEntries(Object.entries(mergedEnv).filter((entry) => typeof entry[1] === "string"));
|
|
1842
|
+
// codex-acp supports both key names, but ACP clients must select its
|
|
1843
|
+
// api-key authentication method during session creation. Without this
|
|
1844
|
+
// request, the server advertises authentication and rejects session/new even
|
|
1845
|
+
// though the credential is present in the launched process environment. Check
|
|
1846
|
+
// the final merged environment, not just the explicit run config, so a host
|
|
1847
|
+
// key the local launch inherits still selects this default.
|
|
1848
|
+
if (acpxAgent === "codex" &&
|
|
1849
|
+
(finalEnv.OPENAI_API_KEY || finalEnv.CODEX_API_KEY) &&
|
|
1850
|
+
!finalEnv.DEFAULT_AUTH_REQUEST) {
|
|
1851
|
+
finalEnv.DEFAULT_AUTH_REQUEST = JSON.stringify({ methodId: "api-key" });
|
|
1852
|
+
}
|
|
1853
|
+
return finalEnv;
|
|
1854
|
+
}
|
|
1855
|
+
function mergeRuntimeEnvironment(inheritedEnv, explicitEnv, caseInsensitiveKeys) {
|
|
1856
|
+
if (!caseInsensitiveKeys) {
|
|
1857
|
+
return { ...inheritedEnv, ...explicitEnv };
|
|
1858
|
+
}
|
|
1859
|
+
const merged = {};
|
|
1860
|
+
const keyByCanonicalName = new Map();
|
|
1861
|
+
const apply = (source) => {
|
|
1862
|
+
for (const [key, value] of Object.entries(source)) {
|
|
1863
|
+
if (typeof value !== "string")
|
|
1864
|
+
continue;
|
|
1865
|
+
const canonicalName = key.toUpperCase();
|
|
1866
|
+
const previousKey = keyByCanonicalName.get(canonicalName);
|
|
1867
|
+
if (previousKey !== undefined && previousKey !== key) {
|
|
1868
|
+
delete merged[previousKey];
|
|
1869
|
+
}
|
|
1870
|
+
merged[key] = value;
|
|
1871
|
+
keyByCanonicalName.set(canonicalName, key);
|
|
1872
|
+
}
|
|
1873
|
+
};
|
|
1874
|
+
apply(inheritedEnv);
|
|
1875
|
+
apply(explicitEnv);
|
|
1876
|
+
return merged;
|
|
1877
|
+
}
|
|
1878
|
+
// Stop both host-side bridges in one `allSettled`. This is the settlement
|
|
1879
|
+
// `stopTransport` effect. The bridge tokens are run-scoped, so they die with the
|
|
1880
|
+
// bridges here (Amendment B).
|
|
1881
|
+
async function stopRunTransport(prepared) {
|
|
1882
|
+
await Promise.allSettled([
|
|
1883
|
+
prepared.processSessionBridge?.stop(),
|
|
1884
|
+
prepared.paperclipBridge?.stop(),
|
|
1885
|
+
]);
|
|
1886
|
+
}
|
|
1887
|
+
// The site sync-back. This is the settlement `syncBack` effect. It runs AFTER the
|
|
1888
|
+
// bridges stop (mirrors the CLI finally: stop bridge → restore workspace). It
|
|
1889
|
+
// fires the codex auth copy-back via `restoreWorkspace()` and removes staged temp
|
|
1890
|
+
// dirs. The seam logs and swallows its own failures — an unclean-teardown
|
|
1891
|
+
// copy-back miss is the accepted, loud `refresh_token_reused` residual on the
|
|
1892
|
+
// next host Codex use, never silent HOST-credential corruption — so a teardown
|
|
1893
|
+
// fault never masks or fails the run result here. It still returns the restore
|
|
1894
|
+
// outcome, so the caller can record a failure on the run record; the run's exit
|
|
1895
|
+
// code and status stay exactly what the turn produced.
|
|
1896
|
+
// The per-session staging lease does NOT release here. The settlement releases
|
|
1897
|
+
// it last, in its own `finally`, so a same-session second run cannot re-stage
|
|
1898
|
+
// until this run fully settles and the caller observes the result.
|
|
1899
|
+
async function syncBackManagedHome(prepared) {
|
|
1900
|
+
if (!prepared.remoteManagedHomeTeardown) {
|
|
1901
|
+
return { ok: true };
|
|
1902
|
+
}
|
|
1903
|
+
// The teardown closure already catches and logs its own error (fail-soft);
|
|
1904
|
+
// this `.catch` is defense in depth for the case where it rejects anyway, so
|
|
1905
|
+
// a teardown fault can never propagate out of settlement.
|
|
1906
|
+
return await prepared
|
|
1907
|
+
.remoteManagedHomeTeardown()
|
|
1908
|
+
.catch(() => ({ ok: false, code: "restore_failed" }));
|
|
1909
|
+
}
|
|
1910
|
+
// ACP startup handshake guard and late-completion fence.
|
|
1911
|
+
//
|
|
1912
|
+
// `runtime.ensureSession()` comes from the external `acpx/runtime` package. It
|
|
1913
|
+
// takes no `AbortSignal`, so the engine cannot cancel it; it can only walk
|
|
1914
|
+
// away from the promise. `guardEnsureSession` races the call against a fixed
|
|
1915
|
+
// deadline and a poll of the duplex control-channel disposition, and rejects
|
|
1916
|
+
// with one of these two host-generated, typed errors the moment either
|
|
1917
|
+
// condition trips. The error type — not its message — is what
|
|
1918
|
+
// `classifyError` reads, so a startup timeout can never collapse into the
|
|
1919
|
+
// session-identity failure code.
|
|
1920
|
+
//
|
|
1921
|
+
// A guard rejection does not stop the external call. `HandshakeAbandonmentFence`
|
|
1922
|
+
// gives that abandoned promise one owner: a resolution that arrives after the
|
|
1923
|
+
// engine walked away can never become the live session handle, the promise
|
|
1924
|
+
// stays observed so it can never raise an unhandled rejection, and the real
|
|
1925
|
+
// handle is closed exactly once, whichever side — the settlement step or the
|
|
1926
|
+
// fence itself — ends up seeing it first.
|
|
1927
|
+
class AcpxHandshakeTimeoutError extends Error {
|
|
1928
|
+
acpxHandshakeGuardKind = "timeout";
|
|
1929
|
+
constructor() {
|
|
1930
|
+
super("The ACP startup handshake did not finish before the startup deadline.");
|
|
1931
|
+
this.name = "AcpxHandshakeTimeoutError";
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
class AcpxHandshakeTransportLostError extends Error {
|
|
1935
|
+
acpxHandshakeGuardKind = "transport_lost";
|
|
1936
|
+
constructor() {
|
|
1937
|
+
super("The sandbox duplex control channel was lost during the ACP startup handshake.");
|
|
1938
|
+
this.name = "AcpxHandshakeTransportLostError";
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
function isAcpxHandshakeTimeoutError(err) {
|
|
1942
|
+
return err instanceof AcpxHandshakeTimeoutError;
|
|
1943
|
+
}
|
|
1944
|
+
function isAcpxHandshakeTransportLostError(err) {
|
|
1945
|
+
return err instanceof AcpxHandshakeTransportLostError;
|
|
1946
|
+
}
|
|
1947
|
+
/**
|
|
1948
|
+
* Gives one abandoned `ensureSession` promise one owner. Construct one fence
|
|
1949
|
+
* per run and reuse it across both `ensureSession` call sites (the first
|
|
1950
|
+
* attempt and the fresh-session retry): only one of those two calls can ever
|
|
1951
|
+
* be the one a guard rejection abandons, because the retry only runs after
|
|
1952
|
+
* the first call already settled on its own.
|
|
1953
|
+
*
|
|
1954
|
+
* `seal()` is the idempotent boundary: only its first call can flip the
|
|
1955
|
+
* state and hand back a handle that arrived before it ran. A later call is a
|
|
1956
|
+
* no-op, so `endSession` can call it unconditionally as its first operation
|
|
1957
|
+
* on every settlement path, not only a handshake-guard one.
|
|
1958
|
+
*/
|
|
1959
|
+
class HandshakeAbandonmentFence {
|
|
1960
|
+
sealedFlag = false;
|
|
1961
|
+
lateHandle = null;
|
|
1962
|
+
get isSealed() {
|
|
1963
|
+
return this.sealedFlag;
|
|
1964
|
+
}
|
|
1965
|
+
/** Record a late resolution that arrived before `seal()` ran. */
|
|
1966
|
+
storeLateHandle(handle) {
|
|
1967
|
+
if (this.sealedFlag)
|
|
1968
|
+
return;
|
|
1969
|
+
this.lateHandle = handle;
|
|
1970
|
+
}
|
|
1971
|
+
/** Idempotent. Only the first call can return a handle that beat it here. */
|
|
1972
|
+
seal() {
|
|
1973
|
+
if (this.sealedFlag)
|
|
1974
|
+
return null;
|
|
1975
|
+
this.sealedFlag = true;
|
|
1976
|
+
const handle = this.lateHandle;
|
|
1977
|
+
this.lateHandle = null;
|
|
1978
|
+
return handle;
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
/**
|
|
1982
|
+
* Race one `ensureSession()` call against the startup guard. Whichever
|
|
1983
|
+
* settles first wins; `outcome` records which one, so the promise's own
|
|
1984
|
+
* `.then` (attached synchronously, right here, before this function returns)
|
|
1985
|
+
* can tell a genuinely late settlement apart from the one the race already
|
|
1986
|
+
* delivered through its normal return value.
|
|
1987
|
+
*
|
|
1988
|
+
* The deadline and the transport-loss poll both start now, at the call site,
|
|
1989
|
+
* not earlier. The bridge disposition this polls is a latch: once a loss
|
|
1990
|
+
* orders, every later read still reports it, so a loss that happened before
|
|
1991
|
+
* this call started polling is still caught on the first tick.
|
|
1992
|
+
*/
|
|
1993
|
+
function guardEnsureSession(params) {
|
|
1994
|
+
const sessionPromise = params.call();
|
|
1995
|
+
let outcome = "pending";
|
|
1996
|
+
// Attached the moment the promise exists. A guard-won race still leaves
|
|
1997
|
+
// this promise observed for as long as it takes to settle, so it can never
|
|
1998
|
+
// raise an unhandled rejection.
|
|
1999
|
+
sessionPromise.then((handle) => {
|
|
2000
|
+
if (outcome !== "guard")
|
|
2001
|
+
return;
|
|
2002
|
+
if (params.fence.isSealed) {
|
|
2003
|
+
params.onLateHandleAfterSeal(handle);
|
|
2004
|
+
}
|
|
2005
|
+
else {
|
|
2006
|
+
params.fence.storeLateHandle(handle);
|
|
2007
|
+
}
|
|
2008
|
+
}, () => {
|
|
2009
|
+
if (outcome !== "guard")
|
|
2010
|
+
return;
|
|
2011
|
+
params.onLateRejection();
|
|
2012
|
+
});
|
|
2013
|
+
return new Promise((resolve, reject) => {
|
|
2014
|
+
let settled = false;
|
|
2015
|
+
const timer = setTimeout(() => {
|
|
2016
|
+
if (settled)
|
|
2017
|
+
return;
|
|
2018
|
+
settled = true;
|
|
2019
|
+
outcome = "guard";
|
|
2020
|
+
clearInterval(poll);
|
|
2021
|
+
reject(new AcpxHandshakeTimeoutError());
|
|
2022
|
+
}, ACPX_HANDSHAKE_TIMEOUT_MS);
|
|
2023
|
+
const poll = setInterval(() => {
|
|
2024
|
+
if (settled)
|
|
2025
|
+
return;
|
|
2026
|
+
if (!params.isTransportLost())
|
|
2027
|
+
return;
|
|
2028
|
+
settled = true;
|
|
2029
|
+
outcome = "guard";
|
|
2030
|
+
clearTimeout(timer);
|
|
2031
|
+
clearInterval(poll);
|
|
2032
|
+
reject(new AcpxHandshakeTransportLostError());
|
|
2033
|
+
}, ACPX_HANDSHAKE_TRANSPORT_POLL_MS);
|
|
2034
|
+
sessionPromise.then((handle) => {
|
|
2035
|
+
if (settled)
|
|
2036
|
+
return;
|
|
2037
|
+
settled = true;
|
|
2038
|
+
outcome = "session";
|
|
2039
|
+
clearTimeout(timer);
|
|
2040
|
+
clearInterval(poll);
|
|
2041
|
+
resolve(handle);
|
|
2042
|
+
}, (err) => {
|
|
2043
|
+
if (settled)
|
|
2044
|
+
return;
|
|
2045
|
+
settled = true;
|
|
2046
|
+
outcome = "session";
|
|
2047
|
+
clearTimeout(timer);
|
|
2048
|
+
clearInterval(poll);
|
|
2049
|
+
reject(err);
|
|
2050
|
+
});
|
|
2051
|
+
});
|
|
2052
|
+
}
|
|
2053
|
+
function renderPaperclipEnvNote(env) {
|
|
2054
|
+
const paperclipKeys = Object.keys(env)
|
|
2055
|
+
.filter((key) => key.startsWith("PAPERCLIP_"))
|
|
2056
|
+
.sort();
|
|
2057
|
+
if (paperclipKeys.length === 0)
|
|
2058
|
+
return "";
|
|
2059
|
+
return [
|
|
2060
|
+
"Paperclip runtime note:",
|
|
2061
|
+
`The following PAPERCLIP_* environment variables are available in this run: ${paperclipKeys.join(", ")}`,
|
|
2062
|
+
"Do not assume these variables are missing without checking your shell environment.",
|
|
2063
|
+
].join("\n");
|
|
2064
|
+
}
|
|
2065
|
+
function renderApiAccessNote(env) {
|
|
2066
|
+
if (!env.PAPERCLIP_API_URL || !env.PAPERCLIP_API_KEY)
|
|
2067
|
+
return "";
|
|
2068
|
+
const lines = [
|
|
2069
|
+
"Paperclip API access note:",
|
|
2070
|
+
"Use terminal commands with curl to make Paperclip API requests.",
|
|
2071
|
+
"Normalize the base URL before adding API paths:",
|
|
2072
|
+
` PAPERCLIP_API_BASE="\${PAPERCLIP_API_URL%/}"; PAPERCLIP_API_BASE="\${PAPERCLIP_API_BASE%/api}"`,
|
|
2073
|
+
"GET example:",
|
|
2074
|
+
` curl -s -H "Authorization: Bearer $PAPERCLIP_API_KEY" "$PAPERCLIP_API_BASE/api/agents/me"`,
|
|
2075
|
+
];
|
|
2076
|
+
if (env.PAPERCLIP_TASK_ID) {
|
|
2077
|
+
lines.push("Scoped issue comment example:", ` curl -s -X POST -H "Authorization: Bearer $PAPERCLIP_API_KEY" -H "Content-Type: application/json" -H "X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID" -d '{"body":"Status update from agent."}' "$PAPERCLIP_API_BASE/api/issues/$PAPERCLIP_TASK_ID/comments"`);
|
|
2078
|
+
}
|
|
2079
|
+
else {
|
|
2080
|
+
lines.push("Use a real issue id from the current context before making issue write requests.");
|
|
2081
|
+
}
|
|
2082
|
+
return lines.join("\n");
|
|
2083
|
+
}
|
|
2084
|
+
async function buildPrompt(ctx, resumedSession, env) {
|
|
2085
|
+
const { agent, runId, config, context, onLog } = ctx;
|
|
2086
|
+
const configuredPromptTemplate = asString(config.promptTemplate, "");
|
|
2087
|
+
const hasCustomPromptTemplate = configuredPromptTemplate.trim().length > 0;
|
|
2088
|
+
const promptTemplate = hasCustomPromptTemplate
|
|
2089
|
+
? configuredPromptTemplate
|
|
2090
|
+
: context.conversationMode === true
|
|
2091
|
+
? DEFAULT_PAPERCLIP_CONVERSATION_PROMPT_TEMPLATE
|
|
2092
|
+
: DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE;
|
|
2093
|
+
const instructionsFilePath = asString(config.instructionsFilePath, "").trim();
|
|
2094
|
+
const instructionsDir = instructionsFilePath ? `${path.dirname(instructionsFilePath)}/` : "";
|
|
2095
|
+
let instructionsPrefix = "";
|
|
2096
|
+
const commandNotes = [];
|
|
2097
|
+
if (instructionsFilePath) {
|
|
2098
|
+
try {
|
|
2099
|
+
const instructionsContents = await fs.readFile(instructionsFilePath, "utf8");
|
|
2100
|
+
instructionsPrefix =
|
|
2101
|
+
`${instructionsContents}\n\n` +
|
|
2102
|
+
`The above agent instructions were loaded from ${instructionsFilePath}. ` +
|
|
2103
|
+
`Resolve any relative file references from ${instructionsDir}.\n\n`;
|
|
2104
|
+
commandNotes.push(`Loaded agent instructions from ${instructionsFilePath}`, `Prepended instructions + path directive to the ACPX prompt (relative references from ${instructionsDir}).`);
|
|
2105
|
+
}
|
|
2106
|
+
catch (err) {
|
|
2107
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
2108
|
+
await onLog("stderr", `[paperclip] Warning: could not read agent instructions file "${instructionsFilePath}": ${reason}\n`);
|
|
2109
|
+
commandNotes.push(`Configured instructionsFilePath ${instructionsFilePath}, but file could not be read.`);
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
const bootstrapPromptTemplate = asString(config.bootstrapPromptTemplate, "");
|
|
2113
|
+
const templateData = {
|
|
2114
|
+
agentId: agent.id,
|
|
2115
|
+
companyId: agent.companyId,
|
|
2116
|
+
runId,
|
|
2117
|
+
company: { id: agent.companyId },
|
|
2118
|
+
agent,
|
|
2119
|
+
run: { id: runId, source: "on_demand" },
|
|
2120
|
+
context,
|
|
2121
|
+
};
|
|
2122
|
+
const renderedBootstrapPrompt = !resumedSession && bootstrapPromptTemplate.trim().length > 0
|
|
2123
|
+
? renderTemplate(bootstrapPromptTemplate, templateData).trim()
|
|
2124
|
+
: "";
|
|
2125
|
+
const taskContextNote = selectPaperclipTaskMarkdown(context, { resumedSession });
|
|
2126
|
+
const externalChatTurn = isPaperclipExternalChatTurn(context.paperclipWake);
|
|
2127
|
+
const wakePrompt = renderPaperclipWakePrompt(context.paperclipWake, {
|
|
2128
|
+
resumedSession,
|
|
2129
|
+
conversationMode: context.conversationMode === true,
|
|
2130
|
+
// The task-context markdown is the authoritative brief on this lane; keep
|
|
2131
|
+
// the wake prompt's description copy out so the prompt carries it once.
|
|
2132
|
+
suppressIssueDescription: taskContextNote.length > 0,
|
|
2133
|
+
});
|
|
2134
|
+
const shouldUseResumeDeltaPrompt = resumedSession && wakePrompt.length > 0;
|
|
2135
|
+
const promptInstructionsPrefix = shouldUseResumeDeltaPrompt ? "" : instructionsPrefix;
|
|
2136
|
+
const renderedPrompt = shouldUseResumeDeltaPrompt || (externalChatTurn && !hasCustomPromptTemplate)
|
|
2137
|
+
? ""
|
|
2138
|
+
: renderTemplate(promptTemplate, templateData);
|
|
2139
|
+
const sessionHandoffNote = asString(context.paperclipSessionHandoffMarkdown, "").trim();
|
|
2140
|
+
const paperclipEnvNote = externalChatTurn ? "" : renderPaperclipEnvNote(env);
|
|
2141
|
+
const apiAccessNote = externalChatTurn ? "" : renderApiAccessNote(env);
|
|
2142
|
+
const prompt = joinPromptSections([
|
|
2143
|
+
promptInstructionsPrefix,
|
|
2144
|
+
renderedBootstrapPrompt,
|
|
2145
|
+
wakePrompt,
|
|
2146
|
+
sessionHandoffNote,
|
|
2147
|
+
taskContextNote,
|
|
2148
|
+
paperclipEnvNote,
|
|
2149
|
+
apiAccessNote,
|
|
2150
|
+
renderedPrompt,
|
|
2151
|
+
]);
|
|
2152
|
+
return {
|
|
2153
|
+
prompt,
|
|
2154
|
+
commandNotes,
|
|
2155
|
+
promptMetrics: {
|
|
2156
|
+
promptChars: prompt.length,
|
|
2157
|
+
instructionsChars: promptInstructionsPrefix.length,
|
|
2158
|
+
bootstrapPromptChars: renderedBootstrapPrompt.length,
|
|
2159
|
+
wakePromptChars: wakePrompt.length,
|
|
2160
|
+
sessionHandoffChars: sessionHandoffNote.length,
|
|
2161
|
+
taskContextChars: taskContextNote.length,
|
|
2162
|
+
runtimeNoteChars: paperclipEnvNote.length + apiAccessNote.length,
|
|
2163
|
+
heartbeatPromptChars: renderedPrompt.length,
|
|
2164
|
+
},
|
|
2165
|
+
};
|
|
2166
|
+
}
|
|
2167
|
+
async function emitAcpxLog(ctx, payload) {
|
|
2168
|
+
await ctx.onLog("stdout", `${JSON.stringify(payload)}\n`);
|
|
2169
|
+
}
|
|
2170
|
+
/**
|
|
2171
|
+
* Build the short run summary that Paperclip may auto-post as an issue comment
|
|
2172
|
+
* when the agent leaves no comment of its own.
|
|
2173
|
+
*
|
|
2174
|
+
* Prefer the last non-empty *output* segment after a tool call. Intermediate
|
|
2175
|
+
* "let me check…" narration between tools must not become a 50k-char dump.
|
|
2176
|
+
* Thought-stream text is never included (callers must not push it into segments).
|
|
2177
|
+
*/
|
|
2178
|
+
export function buildAcpxRunSummary(input) {
|
|
2179
|
+
for (let i = input.outputSegments.length - 1; i >= 0; i -= 1) {
|
|
2180
|
+
const text = (input.outputSegments[i] ?? "").trim();
|
|
2181
|
+
if (text)
|
|
2182
|
+
return text;
|
|
2183
|
+
}
|
|
2184
|
+
const fallback = (input.fallback ?? "").trim();
|
|
2185
|
+
return fallback;
|
|
2186
|
+
}
|
|
2187
|
+
// acpx substitutes a literal "tool call" title when an ACP tool_call_update
|
|
2188
|
+
// omits one, which would persist a generic name over the real one ("Terminal",
|
|
2189
|
+
// "Read", …) in the stored run log. Remember each call's real title so update
|
|
2190
|
+
// lines keep the name durably. Some ACP backends also stream partial tool
|
|
2191
|
+
// arguments as one in-progress update per token under this placeholder;
|
|
2192
|
+
// adapters that opt into coalescePlaceholderToolUpdates in their acpx config
|
|
2193
|
+
// have those updates coalesced until a real title is available.
|
|
2194
|
+
const GENERIC_ACP_TOOL_TITLE = "tool call";
|
|
2195
|
+
async function emitRuntimeEvent(ctx, event, toolTitles, coalescePlaceholderToolUpdates) {
|
|
2196
|
+
if (event.type === "text_delta") {
|
|
2197
|
+
await emitAcpxLog(ctx, {
|
|
2198
|
+
type: "acpx.text_delta",
|
|
2199
|
+
text: event.text,
|
|
2200
|
+
channel: event.stream === "thought" ? "thought" : "output",
|
|
2201
|
+
tag: event.tag,
|
|
2202
|
+
});
|
|
2203
|
+
return;
|
|
2204
|
+
}
|
|
2205
|
+
if (event.type === "tool_call") {
|
|
2206
|
+
// Coalesce token-by-token argument streaming for adapters that opt in via
|
|
2207
|
+
// their acpx config: skip in-progress updates that still carry only the
|
|
2208
|
+
// unresolved placeholder title. Backends that stream tool arguments
|
|
2209
|
+
// otherwise emit tens of thousands of these per run, flooding the
|
|
2210
|
+
// transcript and pinning the live activity indicator to a generic
|
|
2211
|
+
// "tool call" instead of the real tool. The initial pending event, the
|
|
2212
|
+
// resolved-title in-progress update, and the terminal
|
|
2213
|
+
// completed/failed/cancelled update all still flow through. Adapters that
|
|
2214
|
+
// do not opt in never have an event dropped.
|
|
2215
|
+
if (coalescePlaceholderToolUpdates &&
|
|
2216
|
+
event.status === "in_progress" &&
|
|
2217
|
+
(event.title ?? "").trim() === GENERIC_ACP_TOOL_TITLE) {
|
|
2218
|
+
return;
|
|
2219
|
+
}
|
|
2220
|
+
const eventRecord = event;
|
|
2221
|
+
const toolInput = eventRecord.input;
|
|
2222
|
+
let name = event.title ?? "acp_tool";
|
|
2223
|
+
const toolCallId = typeof event.toolCallId === "string" ? event.toolCallId : "";
|
|
2224
|
+
if (toolTitles && toolCallId) {
|
|
2225
|
+
if (event.title && event.title !== GENERIC_ACP_TOOL_TITLE) {
|
|
2226
|
+
// First real title is the call's identity; later retitles (ACP swaps
|
|
2227
|
+
// in the invocation, e.g. "Terminal" → "ls -la") keep their own line
|
|
2228
|
+
// but don't become the remembered name.
|
|
2229
|
+
if (!toolTitles.has(toolCallId))
|
|
2230
|
+
toolTitles.set(toolCallId, event.title);
|
|
2231
|
+
}
|
|
2232
|
+
else {
|
|
2233
|
+
name = toolTitles.get(toolCallId) ?? name;
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
await emitAcpxLog(ctx, {
|
|
2237
|
+
type: "acpx.tool_call",
|
|
2238
|
+
name,
|
|
2239
|
+
toolCallId: event.toolCallId,
|
|
2240
|
+
status: event.status,
|
|
2241
|
+
text: event.text,
|
|
2242
|
+
tag: event.tag,
|
|
2243
|
+
...(toolInput !== undefined ? { input: toolInput } : {}),
|
|
2244
|
+
});
|
|
2245
|
+
return;
|
|
2246
|
+
}
|
|
2247
|
+
if (event.type === "status") {
|
|
2248
|
+
await emitAcpxLog(ctx, {
|
|
2249
|
+
type: "acpx.status",
|
|
2250
|
+
text: event.text,
|
|
2251
|
+
tag: event.tag,
|
|
2252
|
+
used: event.used,
|
|
2253
|
+
size: event.size,
|
|
2254
|
+
...(event.cost ? { cost: event.cost } : {}),
|
|
2255
|
+
...(event.breakdown ? { breakdown: event.breakdown } : {}),
|
|
2256
|
+
});
|
|
2257
|
+
return;
|
|
2258
|
+
}
|
|
2259
|
+
if (event.type === "done") {
|
|
2260
|
+
await emitAcpxLog(ctx, {
|
|
2261
|
+
type: "acpx.result",
|
|
2262
|
+
summary: event.stopReason ?? "completed",
|
|
2263
|
+
stopReason: event.stopReason,
|
|
2264
|
+
});
|
|
2265
|
+
return;
|
|
2266
|
+
}
|
|
2267
|
+
if (event.type === "error") {
|
|
2268
|
+
await emitAcpxLog(ctx, {
|
|
2269
|
+
type: "acpx.error",
|
|
2270
|
+
message: event.message,
|
|
2271
|
+
code: event.code,
|
|
2272
|
+
retryable: event.retryable,
|
|
2273
|
+
});
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
function resultErrorMessage(result) {
|
|
2277
|
+
if (result.status !== "failed")
|
|
2278
|
+
return null;
|
|
2279
|
+
return result.error.message;
|
|
2280
|
+
}
|
|
2281
|
+
function usageBreakdownsEqual(left, right) {
|
|
2282
|
+
return (asNumber(left.inputTokens, 0) === asNumber(right.inputTokens, 0) &&
|
|
2283
|
+
asNumber(left.outputTokens, 0) === asNumber(right.outputTokens, 0) &&
|
|
2284
|
+
asNumber(left.cachedReadTokens, 0) === asNumber(right.cachedReadTokens, 0) &&
|
|
2285
|
+
asNumber(left.cachedWriteTokens, 0) === asNumber(right.cachedWriteTokens, 0) &&
|
|
2286
|
+
asNumber(left.thoughtTokens, 0) === asNumber(right.thoughtTokens, 0) &&
|
|
2287
|
+
asNumber(left.totalTokens, 0) === asNumber(right.totalTokens, 0));
|
|
2288
|
+
}
|
|
2289
|
+
function usdCostAmount(cost) {
|
|
2290
|
+
if (!cost || typeof cost.amount !== "number" || !Number.isFinite(cost.amount))
|
|
2291
|
+
return null;
|
|
2292
|
+
if (cost.currency && cost.currency.trim().toUpperCase() !== "USD")
|
|
2293
|
+
return null;
|
|
2294
|
+
return cost.amount;
|
|
2295
|
+
}
|
|
2296
|
+
async function readRuntimeStatus(runtime, handle) {
|
|
2297
|
+
if (!runtime.getStatus)
|
|
2298
|
+
return null;
|
|
2299
|
+
try {
|
|
2300
|
+
return (await runtime.getStatus({ handle })) ?? null;
|
|
2301
|
+
}
|
|
2302
|
+
catch {
|
|
2303
|
+
return null;
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
/**
|
|
2307
|
+
* Fold the ACP runtime's post-turn usage into the adapter execution result
|
|
2308
|
+
* shape. The runtime persists the latest turn's token breakdown (adapters like
|
|
2309
|
+
* claude-agent-acp report per-turn accumulated usage in the prompt response),
|
|
2310
|
+
* so tokens are per-run. Cost is reported by agents as a cumulative session
|
|
2311
|
+
* amount, so the per-run cost is the delta against the pre-turn snapshot; a
|
|
2312
|
+
* decrease means the agent process restarted and its counter reset, in which
|
|
2313
|
+
* case the post-turn amount alone covers this run.
|
|
2314
|
+
*/
|
|
2315
|
+
export function summarizeAcpxTurnUsage(input) {
|
|
2316
|
+
// The persisted breakdown is overwritten per turn, so an unchanged value
|
|
2317
|
+
// is stale for this turn. Prefer an in-turn event breakdown when available;
|
|
2318
|
+
// otherwise suppress the stale value so it cannot be double-counted.
|
|
2319
|
+
const preBreakdown = input.preStatus?.usage?.cumulative ?? null;
|
|
2320
|
+
const postBreakdown = input.postStatus?.usage?.cumulative ?? null;
|
|
2321
|
+
const postBreakdownIsStale = preBreakdown != null &&
|
|
2322
|
+
postBreakdown != null &&
|
|
2323
|
+
usageBreakdownsEqual(preBreakdown, postBreakdown);
|
|
2324
|
+
const breakdown = postBreakdownIsStale
|
|
2325
|
+
? input.eventBreakdown
|
|
2326
|
+
: postBreakdown ?? input.eventBreakdown ?? null;
|
|
2327
|
+
const inputTokens = Math.max(0, Math.floor(asNumber(breakdown?.inputTokens, 0)));
|
|
2328
|
+
const outputTokens = Math.max(0, Math.floor(asNumber(breakdown?.outputTokens, 0)));
|
|
2329
|
+
const cachedReadTokens = Math.max(0, Math.floor(asNumber(breakdown?.cachedReadTokens, 0)));
|
|
2330
|
+
const cachedWriteTokens = Math.max(0, Math.floor(asNumber(breakdown?.cachedWriteTokens, 0)));
|
|
2331
|
+
const hasTokens = inputTokens > 0 || outputTokens > 0 || cachedReadTokens > 0 || cachedWriteTokens > 0;
|
|
2332
|
+
// Cache-write tokens are prompt tokens the provider billed to create cache
|
|
2333
|
+
// entries; UsageSummary has no dedicated field, so count them as input.
|
|
2334
|
+
const usage = hasTokens
|
|
2335
|
+
? {
|
|
2336
|
+
inputTokens: inputTokens + cachedWriteTokens,
|
|
2337
|
+
outputTokens,
|
|
2338
|
+
cachedInputTokens: cachedReadTokens,
|
|
2339
|
+
}
|
|
2340
|
+
: null;
|
|
2341
|
+
const usageDetail = breakdown
|
|
2342
|
+
? Object.fromEntries(Object.entries({
|
|
2343
|
+
inputTokens: breakdown.inputTokens,
|
|
2344
|
+
outputTokens: breakdown.outputTokens,
|
|
2345
|
+
cachedReadTokens: breakdown.cachedReadTokens,
|
|
2346
|
+
cachedWriteTokens: breakdown.cachedWriteTokens,
|
|
2347
|
+
thoughtTokens: breakdown.thoughtTokens,
|
|
2348
|
+
totalTokens: breakdown.totalTokens,
|
|
2349
|
+
}).filter((entry) => typeof entry[1] === "number"))
|
|
2350
|
+
: null;
|
|
2351
|
+
const previousCostUsd = usdCostAmount(input.preStatus?.usage?.cost);
|
|
2352
|
+
const postCostUsd = usdCostAmount(input.postStatus?.usage?.cost);
|
|
2353
|
+
const postCostIsStale = input.eventCostUsd != null &&
|
|
2354
|
+
previousCostUsd != null &&
|
|
2355
|
+
postCostUsd != null &&
|
|
2356
|
+
postCostUsd === previousCostUsd;
|
|
2357
|
+
const cumulativeCostUsd = postCostIsStale ? input.eventCostUsd : postCostUsd ?? input.eventCostUsd;
|
|
2358
|
+
let costUsd = null;
|
|
2359
|
+
if (cumulativeCostUsd != null) {
|
|
2360
|
+
costUsd =
|
|
2361
|
+
previousCostUsd != null && cumulativeCostUsd >= previousCostUsd
|
|
2362
|
+
? cumulativeCostUsd - previousCostUsd
|
|
2363
|
+
: cumulativeCostUsd;
|
|
2364
|
+
}
|
|
2365
|
+
return { usage, usageDetail, costUsd, cumulativeCostUsd };
|
|
2366
|
+
}
|
|
2367
|
+
function describeErrorDiagnostics(err) {
|
|
2368
|
+
const errorName = err instanceof Error ? err.name || err.constructor.name : typeof err;
|
|
2369
|
+
const maybeCode = err && typeof err === "object" && typeof err.code === "string"
|
|
2370
|
+
? err.code
|
|
2371
|
+
: null;
|
|
2372
|
+
const acpCode = isAcpRuntimeError(err) || (maybeCode?.startsWith("ACP_") ?? false) ? maybeCode : null;
|
|
2373
|
+
const cause = err && typeof err === "object" && err.cause !== undefined
|
|
2374
|
+
? err.cause
|
|
2375
|
+
: undefined;
|
|
2376
|
+
const causeMessage = cause instanceof Error
|
|
2377
|
+
? cause.message
|
|
2378
|
+
: typeof cause === "string"
|
|
2379
|
+
? cause
|
|
2380
|
+
: null;
|
|
2381
|
+
const retryable = err && typeof err === "object" && typeof err.retryable === "boolean"
|
|
2382
|
+
? err.retryable
|
|
2383
|
+
: null;
|
|
2384
|
+
const stack = err instanceof Error && typeof err.stack === "string" ? err.stack : "";
|
|
2385
|
+
const stackPreview = stack ? stack.split("\n").slice(0, 6).join("\n") : null;
|
|
2386
|
+
return { errorName, acpCode, causeMessage, retryable, stackPreview };
|
|
2387
|
+
}
|
|
2388
|
+
function classifyError(err, phase) {
|
|
2389
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2390
|
+
const diagnostics = describeErrorDiagnostics(err);
|
|
2391
|
+
const { acpCode, errorName, causeMessage, retryable, stackPreview } = diagnostics;
|
|
2392
|
+
const baseMeta = {
|
|
2393
|
+
errorName,
|
|
2394
|
+
...(acpCode ? { acpCode } : {}),
|
|
2395
|
+
...(causeMessage ? { causeMessage } : {}),
|
|
2396
|
+
...(retryable !== null ? { retryable } : {}),
|
|
2397
|
+
...(stackPreview ? { stackPreview } : {}),
|
|
2398
|
+
...(phase ? { phase } : {}),
|
|
2399
|
+
};
|
|
2400
|
+
// A host-generated handshake-guard error is classified by its type, never
|
|
2401
|
+
// by message text, and runs before the message-driven heuristics below. A
|
|
2402
|
+
// startup timeout or a duplex control-channel loss must report its own
|
|
2403
|
+
// closed code, not the generic session-identity failure `phase ===
|
|
2404
|
+
// "ensure_session"` would otherwise produce a few lines down.
|
|
2405
|
+
if (isAcpxHandshakeTimeoutError(err)) {
|
|
2406
|
+
return {
|
|
2407
|
+
errorCode: "acpx_handshake_timeout",
|
|
2408
|
+
errorMeta: { category: "runtime", ...baseMeta },
|
|
2409
|
+
};
|
|
2410
|
+
}
|
|
2411
|
+
if (isAcpxHandshakeTransportLostError(err)) {
|
|
2412
|
+
return {
|
|
2413
|
+
errorCode: "acpx_handshake_transport_lost",
|
|
2414
|
+
errorMeta: { category: "runtime", ...baseMeta },
|
|
2415
|
+
};
|
|
2416
|
+
}
|
|
2417
|
+
const lower = message.toLowerCase();
|
|
2418
|
+
const authLike = lower.includes("auth") || lower.includes("login") || lower.includes("credential");
|
|
2419
|
+
if (authLike) {
|
|
2420
|
+
return {
|
|
2421
|
+
errorCode: "acpx_auth_required",
|
|
2422
|
+
errorMeta: { category: "auth", ...baseMeta },
|
|
2423
|
+
};
|
|
2424
|
+
}
|
|
2425
|
+
const phaseCode = (() => {
|
|
2426
|
+
if (acpCode === "ACP_SESSION_INIT_FAILED")
|
|
2427
|
+
return "acpx_session_init_failed";
|
|
2428
|
+
if (acpCode === "ACP_TURN_FAILED")
|
|
2429
|
+
return "acpx_turn_failed";
|
|
2430
|
+
if (acpCode === "ACP_BACKEND_MISSING")
|
|
2431
|
+
return "acpx_backend_missing";
|
|
2432
|
+
if (acpCode === "ACP_BACKEND_UNAVAILABLE")
|
|
2433
|
+
return "acpx_backend_unavailable";
|
|
2434
|
+
if (phase === "ensure_session")
|
|
2435
|
+
return "acpx_session_init_failed";
|
|
2436
|
+
if (phase === "configure_session")
|
|
2437
|
+
return "acpx_session_config_failed";
|
|
2438
|
+
if (phase === "turn")
|
|
2439
|
+
return "acpx_turn_failed";
|
|
2440
|
+
return null;
|
|
2441
|
+
})();
|
|
2442
|
+
if (phaseCode) {
|
|
2443
|
+
return {
|
|
2444
|
+
errorCode: phaseCode,
|
|
2445
|
+
errorMeta: { category: acpCode ? "protocol" : "runtime", ...baseMeta },
|
|
2446
|
+
};
|
|
2447
|
+
}
|
|
2448
|
+
if (acpCode) {
|
|
2449
|
+
return {
|
|
2450
|
+
errorCode: "acpx_protocol_error",
|
|
2451
|
+
errorMeta: { category: "protocol", ...baseMeta },
|
|
2452
|
+
};
|
|
2453
|
+
}
|
|
2454
|
+
return {
|
|
2455
|
+
errorCode: "acpx_runtime_error",
|
|
2456
|
+
errorMeta: { category: "runtime", ...baseMeta },
|
|
2457
|
+
};
|
|
2458
|
+
}
|
|
2459
|
+
async function readChildStderrTail(input) {
|
|
2460
|
+
if (!input.logPath)
|
|
2461
|
+
return null;
|
|
2462
|
+
const maxBytes = input.maxBytes ?? 4096;
|
|
2463
|
+
let handle = null;
|
|
2464
|
+
try {
|
|
2465
|
+
const stat = await fs.stat(input.logPath);
|
|
2466
|
+
if (stat.size === 0)
|
|
2467
|
+
return null;
|
|
2468
|
+
handle = await fs.open(input.logPath, "r");
|
|
2469
|
+
const readBytes = Math.min(stat.size, maxBytes);
|
|
2470
|
+
const buffer = Buffer.alloc(readBytes);
|
|
2471
|
+
await handle.read(buffer, 0, readBytes, Math.max(0, stat.size - readBytes));
|
|
2472
|
+
const tail = buffer.toString("utf8").trim();
|
|
2473
|
+
return tail.length > 0 ? tail : null;
|
|
2474
|
+
}
|
|
2475
|
+
catch {
|
|
2476
|
+
return null;
|
|
2477
|
+
}
|
|
2478
|
+
finally {
|
|
2479
|
+
if (handle)
|
|
2480
|
+
await handle.close().catch(() => { });
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
async function emitAcpxFailure(input) {
|
|
2484
|
+
const { ctx, prepared, err, phase, messageOverride, suppressChildStderrTail } = input;
|
|
2485
|
+
const rawMessage = err instanceof Error ? err.message : String(err);
|
|
2486
|
+
const message = messageOverride ?? rawMessage;
|
|
2487
|
+
const classified = classifyError(err, phase);
|
|
2488
|
+
const childStderrTail = suppressChildStderrTail
|
|
2489
|
+
? null
|
|
2490
|
+
: await readChildStderrTail({ logPath: prepared.childStderrLogPath });
|
|
2491
|
+
if (childStderrTail) {
|
|
2492
|
+
await ctx.onLog("stderr", `[paperclip] ACPX child stderr tail (${phase}):\n${childStderrTail}\n`);
|
|
2493
|
+
}
|
|
2494
|
+
await emitAcpxLog(ctx, {
|
|
2495
|
+
type: "acpx.error",
|
|
2496
|
+
message,
|
|
2497
|
+
phase,
|
|
2498
|
+
...classified.errorMeta,
|
|
2499
|
+
...(childStderrTail ? { childStderrTail } : {}),
|
|
2500
|
+
});
|
|
2501
|
+
return { classified, message, childStderrTail };
|
|
2502
|
+
}
|
|
2503
|
+
function isResumeFailure(err) {
|
|
2504
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2505
|
+
return /resume|load|not found|no session|unknown session|conversation/i.test(message);
|
|
2506
|
+
}
|
|
2507
|
+
// Drop staged-runtime entries the session has not touched within the warm-idle
|
|
2508
|
+
// window, so the cache does not accumulate abandoned sessions (e.g. every time
|
|
2509
|
+
// a config change shifts the fingerprint to a new key). The per-run copy-back
|
|
2510
|
+
// already ran on the entry's last run's settlement `syncBack`; eviction fires
|
|
2511
|
+
// the entry's one-time `dispose` (host staged-temp cleanup) — the only place
|
|
2512
|
+
// the staged temp is removed now that it no longer rides the per-run teardown.
|
|
2513
|
+
// A later run of the same session simply re-stages fresh (re-shipping into the
|
|
2514
|
+
// still-persistent sandbox, which the inbound monotonic auth-merge keeps safe).
|
|
2515
|
+
async function cleanupIdleStagedRuntimes(input) {
|
|
2516
|
+
if (input.idleMs <= 0)
|
|
2517
|
+
return;
|
|
2518
|
+
const stale = [];
|
|
2519
|
+
for (const entry of input.handles.entries()) {
|
|
2520
|
+
if (input.now() - entry[1].lastUsedAt >= input.idleMs)
|
|
2521
|
+
stale.push(entry);
|
|
2522
|
+
}
|
|
2523
|
+
for (const [key, entry] of stale) {
|
|
2524
|
+
const lease = await withSessionStagingLease(input.locks, key, async () => {
|
|
2525
|
+
const current = input.handles.get(key);
|
|
2526
|
+
if (current !== entry)
|
|
2527
|
+
return;
|
|
2528
|
+
if (input.now() - current.lastUsedAt < input.idleMs)
|
|
2529
|
+
return;
|
|
2530
|
+
input.handles.delete(key);
|
|
2531
|
+
if (entry.dispose)
|
|
2532
|
+
await entry.dispose().catch(() => { });
|
|
2533
|
+
});
|
|
2534
|
+
lease.release();
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
// Persist a remote runner-backed session's staged runtime for reuse on the next
|
|
2538
|
+
// compatible resume. Called ONLY after a clean turn, so the cache never offers a
|
|
2539
|
+
// half-staged or failed session for reuse. Non-remote lanes carry a null
|
|
2540
|
+
// stagedRuntime / null envDelta and are skipped.
|
|
2541
|
+
function saveStagedRuntimeAfterCleanTurn(input) {
|
|
2542
|
+
const { prepared } = input;
|
|
2543
|
+
if (!prepared.stagedRuntime || prepared.remoteStagingEnvDelta === null)
|
|
2544
|
+
return;
|
|
2545
|
+
input.handles.set(prepared.sessionKey, {
|
|
2546
|
+
stagedRuntime: prepared.stagedRuntime,
|
|
2547
|
+
envDelta: prepared.remoteStagingEnvDelta,
|
|
2548
|
+
teardown: prepared.remoteManagedHomeTeardown,
|
|
2549
|
+
dispose: prepared.remoteStagingDispose,
|
|
2550
|
+
lastUsedAt: input.now,
|
|
2551
|
+
});
|
|
2552
|
+
}
|
|
2553
|
+
// Drop the staged-runtime entry a finished run owns and release its host-side
|
|
2554
|
+
// staged resources. Two guards make this safe under overlapping runs of the same
|
|
2555
|
+
// session key (PR 3 fix — "Concurrent Runs Corrupt Cache Ownership"):
|
|
2556
|
+
// 1. Ownership guard: only delete the map entry when it is still the exact
|
|
2557
|
+
// staged runtime THIS run installed/reused (object identity). A concurrent
|
|
2558
|
+
// run that installed a different clean entry keeps it — a failed run can no
|
|
2559
|
+
// longer evict another run's good cache entry.
|
|
2560
|
+
// 2. `dispose` is fired for THIS run's own staged resources regardless, so a
|
|
2561
|
+
// failed/cancelled run always frees its own staged temp. `dispose` is
|
|
2562
|
+
// idempotent, so a shared closure re-fired across a reuse chain is safe.
|
|
2563
|
+
async function discardStagedRuntime(input) {
|
|
2564
|
+
const { handles, prepared } = input;
|
|
2565
|
+
await releaseStagedRuntimeEntry({
|
|
2566
|
+
handles,
|
|
2567
|
+
sessionKey: prepared.sessionKey,
|
|
2568
|
+
stagedRuntime: prepared.stagedRuntime,
|
|
2569
|
+
dispose: prepared.remoteStagingDispose,
|
|
2570
|
+
});
|
|
2571
|
+
}
|
|
2572
|
+
// Ownership-guarded removal + dispose for a staged-runtime cache entry a run owns.
|
|
2573
|
+
// Shared by `discardStagedRuntime` (clean-run drop) and the partial-bring-up
|
|
2574
|
+
// rollback so both release a borrowed entry the same way:
|
|
2575
|
+
// 1. Delete the map entry ONLY when it still holds the exact staged runtime this
|
|
2576
|
+
// run installed or reused (object identity). A concurrent run that replaced
|
|
2577
|
+
// the entry with its own keeps it.
|
|
2578
|
+
// 2. Fire this run's own one-time host staged-temp dispose regardless. The
|
|
2579
|
+
// dispose closure is idempotent, so a shared closure re-fired across a reuse
|
|
2580
|
+
// chain is safe.
|
|
2581
|
+
async function releaseStagedRuntimeEntry(input) {
|
|
2582
|
+
const { handles, sessionKey, stagedRuntime, dispose } = input;
|
|
2583
|
+
const existing = handles.get(sessionKey);
|
|
2584
|
+
if (existing && stagedRuntime && existing.stagedRuntime === stagedRuntime) {
|
|
2585
|
+
handles.delete(sessionKey);
|
|
2586
|
+
}
|
|
2587
|
+
if (dispose)
|
|
2588
|
+
await dispose().catch(() => { });
|
|
2589
|
+
}
|
|
2590
|
+
// Per-`sessionKey` async lease: chains each caller after the previous one so
|
|
2591
|
+
// the stage-or-reuse decision for a session runs serially, then keeps the
|
|
2592
|
+
// lease held until the active turn finishes and bridge cleanup runs. That means
|
|
2593
|
+
// overlapping runs of the same session can never stage fresh into the same
|
|
2594
|
+
// remote workspace while a prior turn is still using it: the loser waits, then
|
|
2595
|
+
// re-checks the cache before deciding to reuse or re-stage.
|
|
2596
|
+
async function withSessionStagingLease(locks, key, fn) {
|
|
2597
|
+
const prev = locks.get(key) ?? Promise.resolve();
|
|
2598
|
+
let releaseGate;
|
|
2599
|
+
const gate = new Promise((resolve) => {
|
|
2600
|
+
releaseGate = resolve;
|
|
2601
|
+
});
|
|
2602
|
+
// The next waiter's `prev` is this promise; it settles only once we release
|
|
2603
|
+
// the gate below, so callers run one at a time.
|
|
2604
|
+
const mine = prev.then(() => gate);
|
|
2605
|
+
locks.set(key, mine);
|
|
2606
|
+
await prev.catch(() => { });
|
|
2607
|
+
let released = false;
|
|
2608
|
+
const release = () => {
|
|
2609
|
+
if (released)
|
|
2610
|
+
return;
|
|
2611
|
+
released = true;
|
|
2612
|
+
releaseGate();
|
|
2613
|
+
// GC the lock if no later caller has chained after us.
|
|
2614
|
+
if (locks.get(key) === mine)
|
|
2615
|
+
locks.delete(key);
|
|
2616
|
+
};
|
|
2617
|
+
try {
|
|
2618
|
+
return { value: await fn(), release };
|
|
2619
|
+
}
|
|
2620
|
+
catch (error) {
|
|
2621
|
+
if (!released)
|
|
2622
|
+
release();
|
|
2623
|
+
throw error;
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
function clearWarmHandleTimer(entry) {
|
|
2627
|
+
if (!entry.cleanupTimer)
|
|
2628
|
+
return;
|
|
2629
|
+
clearTimeout(entry.cleanupTimer);
|
|
2630
|
+
entry.cleanupTimer = undefined;
|
|
2631
|
+
}
|
|
2632
|
+
async function closeWarmHandle(input) {
|
|
2633
|
+
if (input.handles.get(input.key) === input.entry) {
|
|
2634
|
+
input.handles.delete(input.key);
|
|
2635
|
+
}
|
|
2636
|
+
clearWarmHandleTimer(input.entry);
|
|
2637
|
+
await input.entry.runtime.close({
|
|
2638
|
+
handle: input.entry.handle,
|
|
2639
|
+
reason: input.reason,
|
|
2640
|
+
discardPersistentState: input.discardPersistentState ?? false,
|
|
2641
|
+
}).catch(() => { });
|
|
2642
|
+
flushChildStderr(input.entry.childStderrState);
|
|
2643
|
+
}
|
|
2644
|
+
function warmHandleMatches(entry, runtime, handle) {
|
|
2645
|
+
return entry !== undefined && entry.runtime === runtime && entry.handle === handle;
|
|
2646
|
+
}
|
|
2647
|
+
/** The stable name of the one root span for a sandbox bring-up. It is a fixed
|
|
2648
|
+
* low-cardinality constant, never derived from run/user data. */
|
|
2649
|
+
const STARTUP_ROOT_SPAN_NAME = "sandbox.startup";
|
|
2650
|
+
/** The shared batch tag for the two parallel bridge steps. It is a fixed
|
|
2651
|
+
* low-cardinality literal, so it marks the two spans as one batch without
|
|
2652
|
+
* carrying run or user data. */
|
|
2653
|
+
const STARTUP_BRIDGE_BATCH = "bridge";
|
|
2654
|
+
/**
|
|
2655
|
+
* Open the one root span for a sandbox bring-up and return its parent-context
|
|
2656
|
+
* token plus a guarded `end`. The span parents to the run root span through
|
|
2657
|
+
* `runParentContext`, so `sandbox.startup` becomes a child of `task.run`. The
|
|
2658
|
+
* span then parents every startup boundary span in turn: the engine forwards
|
|
2659
|
+
* `parentContext` to each `measureStartupStep` call. The `end` closure runs at
|
|
2660
|
+
* most once (bring-up complete OR a bring-up failure) and swallows every tracer
|
|
2661
|
+
* error, so observability never changes startup control flow. With no injected
|
|
2662
|
+
* trace context, the tracer is a no-op and the span is a no-op.
|
|
2663
|
+
*/
|
|
2664
|
+
function openStartupRootSpan(tracing, nowMs,
|
|
2665
|
+
// The run root span parent context. `sandbox.startup` opens as a child of it,
|
|
2666
|
+
// so the whole bring-up parents to `task.run`. It is an opaque token here.
|
|
2667
|
+
runParentContext,
|
|
2668
|
+
// Return the final root-span numbers and context at end time. The work sum
|
|
2669
|
+
// and the cold-start flag are known only after the bring-up runs, so the
|
|
2670
|
+
// caller reads them lazily here.
|
|
2671
|
+
finalize) {
|
|
2672
|
+
let span;
|
|
2673
|
+
try {
|
|
2674
|
+
span = tracing.tracer.startSpan(STARTUP_ROOT_SPAN_NAME, undefined, runParentContext);
|
|
2675
|
+
}
|
|
2676
|
+
catch {
|
|
2677
|
+
span = NOOP_STARTUP_SPAN;
|
|
2678
|
+
}
|
|
2679
|
+
let parentContext;
|
|
2680
|
+
try {
|
|
2681
|
+
parentContext = tracing.contextWithSpan(span);
|
|
2682
|
+
}
|
|
2683
|
+
catch {
|
|
2684
|
+
parentContext = undefined;
|
|
2685
|
+
}
|
|
2686
|
+
const startedAtMs = nowMs();
|
|
2687
|
+
let ended = false;
|
|
2688
|
+
return {
|
|
2689
|
+
parentContext,
|
|
2690
|
+
end: (failed) => {
|
|
2691
|
+
if (ended)
|
|
2692
|
+
return;
|
|
2693
|
+
ended = true;
|
|
2694
|
+
try {
|
|
2695
|
+
// The root span records its own wall time, the step-work sum, and the
|
|
2696
|
+
// bounded context. `setSandboxRootSpanAttributes` sets only the closed
|
|
2697
|
+
// allowlist, so no raw id or image reference rides the span.
|
|
2698
|
+
const { workMs, context } = finalize();
|
|
2699
|
+
setSandboxRootSpanAttributes(span, { wallMs: nowMs() - startedAtMs, workMs }, context);
|
|
2700
|
+
// `2` is `SpanStatusCode.ERROR`. `adapter-utils` stays OTel-free, so it
|
|
2701
|
+
// uses the numeric value that a real injected span reads as the error
|
|
2702
|
+
// status.
|
|
2703
|
+
if (failed)
|
|
2704
|
+
span.setStatus({ code: 2 });
|
|
2705
|
+
span.end();
|
|
2706
|
+
}
|
|
2707
|
+
catch {
|
|
2708
|
+
// Observability must not change startup control flow.
|
|
2709
|
+
}
|
|
2710
|
+
},
|
|
2711
|
+
};
|
|
2712
|
+
}
|
|
2713
|
+
/** The stable name of the one root span for a whole run. It is a fixed
|
|
2714
|
+
* low-cardinality constant, never derived from run or user data. */
|
|
2715
|
+
const RUN_ROOT_SPAN_NAME = "task.run";
|
|
2716
|
+
/** The stable name of the one span for the agent turn. It is a fixed
|
|
2717
|
+
* low-cardinality constant, never derived from run or user data. The turn span
|
|
2718
|
+
* is a child of the run root span. */
|
|
2719
|
+
const TURN_SPAN_NAME = "agent.turn";
|
|
2720
|
+
/** The attribute prefix for the run root span. It groups the run-level span
|
|
2721
|
+
* attributes under one namespace, the same shape as the sandbox startup
|
|
2722
|
+
* prefix. */
|
|
2723
|
+
const RUN_ROOT_SPAN_ATTR_PREFIX = "paperclip.task.run.";
|
|
2724
|
+
/** The attribute prefix for the agent turn span. It groups the turn-level span
|
|
2725
|
+
* attributes under one namespace, the same shape as the run root prefix. */
|
|
2726
|
+
const TURN_SPAN_ATTR_PREFIX = "paperclip.agent.turn.";
|
|
2727
|
+
/** Map a run id to a non-reversible 12-hex hash for a span attribute. The raw
|
|
2728
|
+
* run id never rides a span; only this hash does. This mirrors the id-hash rule
|
|
2729
|
+
* that `clampSpanLabel` uses for the startup ids. */
|
|
2730
|
+
function hashRunId(runId) {
|
|
2731
|
+
return createHash("sha256").update(runId).digest("hex").slice(0, 12);
|
|
2732
|
+
}
|
|
2733
|
+
/**
|
|
2734
|
+
* Open the one root span for a whole run and return its parent-context token
|
|
2735
|
+
* plus a guarded `end`. The run root span is the trace root: the sandbox
|
|
2736
|
+
* bring-up span (`sandbox.startup`) parents to it, so the engine forwards
|
|
2737
|
+
* `parentContext` into `openStartupRootSpan`. The `end` closure runs at most
|
|
2738
|
+
* once and swallows every tracer error, so observability never changes run
|
|
2739
|
+
* control flow. With no injected trace context the tracer is a no-op and the
|
|
2740
|
+
* span is a no-op.
|
|
2741
|
+
*
|
|
2742
|
+
* The span carries only a bounded, non-reversible run-id hash and its own wall
|
|
2743
|
+
* time. It never carries the prompt, the command, or any user text, so no raw
|
|
2744
|
+
* run text rides the span. This follows the same allowlist rule as
|
|
2745
|
+
* `openStartupRootSpan`.
|
|
2746
|
+
*/
|
|
2747
|
+
function openRunRootSpan(tracing, nowMs, runId) {
|
|
2748
|
+
let span;
|
|
2749
|
+
try {
|
|
2750
|
+
span = tracing.tracer.startSpan(RUN_ROOT_SPAN_NAME);
|
|
2751
|
+
}
|
|
2752
|
+
catch {
|
|
2753
|
+
span = NOOP_STARTUP_SPAN;
|
|
2754
|
+
}
|
|
2755
|
+
let parentContext;
|
|
2756
|
+
try {
|
|
2757
|
+
parentContext = tracing.contextWithSpan(span);
|
|
2758
|
+
}
|
|
2759
|
+
catch {
|
|
2760
|
+
parentContext = undefined;
|
|
2761
|
+
}
|
|
2762
|
+
const startedAtMs = nowMs();
|
|
2763
|
+
let ended = false;
|
|
2764
|
+
return {
|
|
2765
|
+
parentContext,
|
|
2766
|
+
end: (failed) => {
|
|
2767
|
+
if (ended)
|
|
2768
|
+
return;
|
|
2769
|
+
ended = true;
|
|
2770
|
+
try {
|
|
2771
|
+
// The run id rides only as a non-reversible short hash, never as the raw
|
|
2772
|
+
// id. The wall time is a plain duration. No raw run text rides the span.
|
|
2773
|
+
span.setAttribute(`${RUN_ROOT_SPAN_ATTR_PREFIX}run_id`, hashRunId(runId));
|
|
2774
|
+
span.setAttribute(`${RUN_ROOT_SPAN_ATTR_PREFIX}wall_ms`, nowMs() - startedAtMs);
|
|
2775
|
+
// `2` is `SpanStatusCode.ERROR`. `adapter-utils` stays OTel-free, so it
|
|
2776
|
+
// uses the numeric value that a real injected span reads as the error
|
|
2777
|
+
// status.
|
|
2778
|
+
if (failed)
|
|
2779
|
+
span.setStatus({ code: 2 });
|
|
2780
|
+
span.end();
|
|
2781
|
+
}
|
|
2782
|
+
catch {
|
|
2783
|
+
// Observability must not change run control flow.
|
|
2784
|
+
}
|
|
2785
|
+
},
|
|
2786
|
+
};
|
|
2787
|
+
}
|
|
2788
|
+
/**
|
|
2789
|
+
* Open the one span for the agent turn and return its parent-context token plus
|
|
2790
|
+
* a guarded `end`. The span parents to the run root span through
|
|
2791
|
+
* `runParentContext`, so `agent.turn` becomes a child of `task.run`. The
|
|
2792
|
+
* executor holds the returned `parentContext` for later exec parenting. The
|
|
2793
|
+
* `end` closure runs at most once and swallows every tracer error, so
|
|
2794
|
+
* observability never changes turn control flow. With no injected trace context
|
|
2795
|
+
* the tracer is a no-op and the span is a no-op.
|
|
2796
|
+
*
|
|
2797
|
+
* The span carries only its own wall time. It never carries the prompt, the
|
|
2798
|
+
* command, or any user text, so no raw run text rides the span. This follows the
|
|
2799
|
+
* same allowlist rule as `openStartupRootSpan`.
|
|
2800
|
+
*/
|
|
2801
|
+
function openTurnSpan(tracing, nowMs,
|
|
2802
|
+
// The run root span parent context. `agent.turn` opens as a child of it, so
|
|
2803
|
+
// the turn parents to `task.run`. It is an opaque token here.
|
|
2804
|
+
runParentContext) {
|
|
2805
|
+
let span;
|
|
2806
|
+
try {
|
|
2807
|
+
span = tracing.tracer.startSpan(TURN_SPAN_NAME, undefined, runParentContext);
|
|
2808
|
+
}
|
|
2809
|
+
catch {
|
|
2810
|
+
span = NOOP_STARTUP_SPAN;
|
|
2811
|
+
}
|
|
2812
|
+
let parentContext;
|
|
2813
|
+
try {
|
|
2814
|
+
parentContext = tracing.contextWithSpan(span);
|
|
2815
|
+
}
|
|
2816
|
+
catch {
|
|
2817
|
+
parentContext = undefined;
|
|
2818
|
+
}
|
|
2819
|
+
const startedAtMs = nowMs();
|
|
2820
|
+
let ended = false;
|
|
2821
|
+
return {
|
|
2822
|
+
parentContext,
|
|
2823
|
+
end: (failed) => {
|
|
2824
|
+
if (ended)
|
|
2825
|
+
return;
|
|
2826
|
+
ended = true;
|
|
2827
|
+
try {
|
|
2828
|
+
// The wall time is a plain duration. No raw run text rides the span.
|
|
2829
|
+
span.setAttribute(`${TURN_SPAN_ATTR_PREFIX}wall_ms`, nowMs() - startedAtMs);
|
|
2830
|
+
// `2` is `SpanStatusCode.ERROR`. `adapter-utils` stays OTel-free, so it
|
|
2831
|
+
// uses the numeric value that a real injected span reads as the error
|
|
2832
|
+
// status.
|
|
2833
|
+
if (failed)
|
|
2834
|
+
span.setStatus({ code: 2 });
|
|
2835
|
+
span.end();
|
|
2836
|
+
}
|
|
2837
|
+
catch {
|
|
2838
|
+
// Observability must not change turn control flow.
|
|
2839
|
+
}
|
|
2840
|
+
},
|
|
2841
|
+
};
|
|
2842
|
+
}
|
|
2843
|
+
export function createAcpxEngineExecutor(deps = {}) {
|
|
2844
|
+
const createRuntime = deps.createRuntime ?? createAcpRuntime;
|
|
2845
|
+
const now = deps.now ?? (() => Date.now());
|
|
2846
|
+
const duplexLossCancelDeadlineMs = deps.duplexLossCancelDeadlineMs ?? ACPX_DUPLEX_LOSS_CANCEL_DEADLINE_MS;
|
|
2847
|
+
const warmHandles = deps.warmHandles ?? defaultWarmHandles;
|
|
2848
|
+
const stagedRuntimes = deps.stagedRuntimes ?? defaultStagedRuntimes;
|
|
2849
|
+
const stagingLocks = deps.stagingLocks ?? defaultStagingLocks;
|
|
2850
|
+
const engine = resolveEngineSettings(deps);
|
|
2851
|
+
return async function executeAcpxEngine(ctx) {
|
|
2852
|
+
let billingIdentity = null;
|
|
2853
|
+
try {
|
|
2854
|
+
billingIdentity = (await deps.resolveBillingIdentity?.(ctx)) ?? null;
|
|
2855
|
+
}
|
|
2856
|
+
catch {
|
|
2857
|
+
billingIdentity = null;
|
|
2858
|
+
}
|
|
2859
|
+
const billingFields = {
|
|
2860
|
+
provider: billingIdentity?.provider ?? "acpx",
|
|
2861
|
+
...(billingIdentity?.biller ? { biller: billingIdentity.biller } : {}),
|
|
2862
|
+
billingType: billingIdentity?.billingType ?? "unknown",
|
|
2863
|
+
};
|
|
2864
|
+
const warmIdleMs = asNumber(ctx.config.warmHandleIdleMs, DEFAULT_ACP_ENGINE_WARM_HANDLE_IDLE_MS);
|
|
2865
|
+
// The host run site owns the warm-handle store on this run. It operates over
|
|
2866
|
+
// the engine's persistent `warmHandles` map, so a warm runtime stays live for
|
|
2867
|
+
// the next compatible resume. The store arms the per-entry idle timer on a
|
|
2868
|
+
// warm save, sweeps idle entries at run start, and closes a released entry
|
|
2869
|
+
// through `closeWarmEntry`. The run reads `warmHandleIdleMs` per run, so the
|
|
2870
|
+
// site takes this run's bound.
|
|
2871
|
+
const hostSite = createHostRunSite({
|
|
2872
|
+
warmHandles,
|
|
2873
|
+
now,
|
|
2874
|
+
idleMs: warmIdleMs,
|
|
2875
|
+
closeWarmEntry: async (entry) => {
|
|
2876
|
+
await entry.runtime
|
|
2877
|
+
.close({ handle: entry.handle, reason: "paperclip idle cleanup", discardPersistentState: false })
|
|
2878
|
+
.catch(() => { });
|
|
2879
|
+
flushChildStderr(entry.childStderrState);
|
|
2880
|
+
},
|
|
2881
|
+
});
|
|
2882
|
+
const hostStore = hostSite.reuse();
|
|
2883
|
+
// The `task.run` and `sandbox.startup` spans must not cover a local or SSH
|
|
2884
|
+
// run: those runs have no sandbox, so they stay out of sandbox telemetry.
|
|
2885
|
+
// Open a real root span only when the target is a remote sandbox and the
|
|
2886
|
+
// server injected a trace context; every other target forces the no-op
|
|
2887
|
+
// trace context, so the whole span path stays inert.
|
|
2888
|
+
const startupExecutionTarget = readAdapterExecutionTarget({
|
|
2889
|
+
executionTarget: ctx.executionTarget,
|
|
2890
|
+
legacyRemoteExecution: ctx.executionTransport?.remoteExecution,
|
|
2891
|
+
});
|
|
2892
|
+
const sandboxTarget = startupExecutionTarget?.kind === "remote" && startupExecutionTarget.transport === "sandbox"
|
|
2893
|
+
? startupExecutionTarget
|
|
2894
|
+
: null;
|
|
2895
|
+
const targetsRemoteSandbox = sandboxTarget !== null;
|
|
2896
|
+
const tracing = targetsRemoteSandbox && ctx.startupTraceContext
|
|
2897
|
+
? ctx.startupTraceContext
|
|
2898
|
+
: NOOP_STARTUP_TRACE_CONTEXT;
|
|
2899
|
+
// Open the one run root span at the engine first line, before any bring-up
|
|
2900
|
+
// work. It is the trace root for the whole run: the sandbox bring-up and the
|
|
2901
|
+
// agent turn parent to it. The engine forwards its parent context into the
|
|
2902
|
+
// startup span. `runRootSpan.end` runs exactly once, in the `finally` below,
|
|
2903
|
+
// on every return and on a throw.
|
|
2904
|
+
const runRootSpan = openRunRootSpan(tracing, now, ctx.runId);
|
|
2905
|
+
// Hold the current-run parent-context token for the whole run. It starts as
|
|
2906
|
+
// the `task.run` token, switches to the `agent.turn` token during the turn,
|
|
2907
|
+
// and switches back to the `task.run` token after the turn. It is never
|
|
2908
|
+
// `undefined` while the run is live. The holder is a run-scoped local, so two
|
|
2909
|
+
// concurrent runs in one host process keep separate tokens. A detached exec
|
|
2910
|
+
// reads it through `getRuntimeParentContext` to parent to the live span.
|
|
2911
|
+
let currentRunParentContext = runRootSpan.parentContext;
|
|
2912
|
+
const getRuntimeParentContext = () => currentRunParentContext;
|
|
2913
|
+
// Wrap each unit of bridge run-time work (one outbound ACP message, one poll
|
|
2914
|
+
// tick, one callback request) in its own named span, parented to the live run
|
|
2915
|
+
// span. The runner reads the run parent per call through
|
|
2916
|
+
// `getRuntimeParentContext`, so a wrapper span always parents to the current
|
|
2917
|
+
// run span. On a no-op trace context the runner opens no real span.
|
|
2918
|
+
const runRuntimeSpan = createRuntimeSpanRunner(tracing, getRuntimeParentContext);
|
|
2919
|
+
// Wrap the host workspace tarball build in one `pack` span. This runner
|
|
2920
|
+
// parents each span to the ACTIVE startup step (not the run span), so the
|
|
2921
|
+
// `pack` span nests under the `stage.sync` step that runs the staging seam.
|
|
2922
|
+
// The staging seam runs inside `stage.sync`'s measured step, so
|
|
2923
|
+
// `getActiveStepContext()` returns that step's child context at pack time.
|
|
2924
|
+
// On a no-op trace context the runner opens no real span.
|
|
2925
|
+
const runStageSpan = createRuntimeSpanRunner(tracing, () => getActiveStepContext()?.parentContext);
|
|
2926
|
+
// `runFailed` marks the run root span status at end time. It stays `true`
|
|
2927
|
+
// until the run reaches a clean completed turn, so every failure and every
|
|
2928
|
+
// early exit closes the span with error status.
|
|
2929
|
+
let runFailed = true;
|
|
2930
|
+
// The run's per-session staging lease release. `buildRuntime` returns it on
|
|
2931
|
+
// `prepared` after it acquires the lease; the startup step captures it here so
|
|
2932
|
+
// the run root `finally` can release it as the final settlement act. It stays
|
|
2933
|
+
// null on the host lane (no staging) and on a build failure (where
|
|
2934
|
+
// `buildRuntime` already released its own partial lease).
|
|
2935
|
+
let releaseStagingLease = null;
|
|
2936
|
+
let stopTimer;
|
|
2937
|
+
let removeStopListener;
|
|
2938
|
+
// Unregisters the sandbox duplex bridge's loss listener (below, in
|
|
2939
|
+
// `stepTurnStart`). Set only on a sandbox target whose bridge exposes
|
|
2940
|
+
// `onLoss`; stays undefined everywhere else, so the cleanup call is a
|
|
2941
|
+
// no-op there.
|
|
2942
|
+
let removeLossListener;
|
|
2943
|
+
// Bounds the wait after a latched terminal duplex loss so a silent agent
|
|
2944
|
+
// cannot hold the run open on the cooperative `turn.cancel()` request
|
|
2945
|
+
// alone. `stepTurnStart` arms `lossDeadlineTimer` the moment a loss
|
|
2946
|
+
// latches; it stays undefined everywhere else, so the cleanup call below
|
|
2947
|
+
// is a no-op there. `stepEventRelay` races the turn against
|
|
2948
|
+
// `lossDeadline` and, once it fires, ends the event drain and hands
|
|
2949
|
+
// `turnFinalize` a host-built terminal instead of the agent's.
|
|
2950
|
+
let lossDeadlineTimer;
|
|
2951
|
+
let lossDeadlineTripped = false;
|
|
2952
|
+
let resolveLossDeadline;
|
|
2953
|
+
const lossDeadline = new Promise((resolve) => {
|
|
2954
|
+
resolveLossDeadline = resolve;
|
|
2955
|
+
});
|
|
2956
|
+
let forcedStop = false;
|
|
2957
|
+
let runtimeStopConfirmed = false;
|
|
2958
|
+
let safeInterruptedSession = false;
|
|
2959
|
+
let preserveInterruptedSession = false;
|
|
2960
|
+
const interruptionTools = new Map();
|
|
2961
|
+
let incompleteToolInventory = false;
|
|
2962
|
+
try {
|
|
2963
|
+
await ctx.onCancellationReady?.();
|
|
2964
|
+
if (ctx.signal?.aborted)
|
|
2965
|
+
return {
|
|
2966
|
+
exitCode: null, signal: null, timedOut: false,
|
|
2967
|
+
errorCode: "cancelled", errorMessage: "Stopped before provider startup",
|
|
2968
|
+
executionRecovery: { kind: "bootstrap", providerWorkStarted: false },
|
|
2969
|
+
resultJson: { executionCancellation: { state: "acknowledged", acknowledgedAt: new Date().toISOString(), forced: false } },
|
|
2970
|
+
};
|
|
2971
|
+
// Evict idle staged runtimes BEFORE building the runtime, since buildRuntime
|
|
2972
|
+
// consults the staged cache to decide whether a compatible resume may reuse
|
|
2973
|
+
// an already-staged runtime — an expired entry must not be reused.
|
|
2974
|
+
await cleanupIdleStagedRuntimes({
|
|
2975
|
+
handles: stagedRuntimes,
|
|
2976
|
+
locks: stagingLocks,
|
|
2977
|
+
now,
|
|
2978
|
+
idleMs: warmIdleMs,
|
|
2979
|
+
});
|
|
2980
|
+
// The sum of the step wall times. The root span records it as `root.work_ms`
|
|
2981
|
+
// and the difference from its own wall time as `root.diff_ms` (the overlap
|
|
2982
|
+
// the parallel steps saved). Every step reports its wall time through
|
|
2983
|
+
// `onWallMs`; a skipped step adds zero.
|
|
2984
|
+
let stepWallSumMs = 0;
|
|
2985
|
+
// Whether this bring-up is a cold start (no warm handle). Set once the warm-
|
|
2986
|
+
// handle lookup runs below; it stays undefined on an early build failure, so
|
|
2987
|
+
// the root span omits the attribute (fail open).
|
|
2988
|
+
let coldStart;
|
|
2989
|
+
const rootSpan = openStartupRootSpan(tracing, now, runRootSpan.parentContext, () => ({
|
|
2990
|
+
workMs: stepWallSumMs,
|
|
2991
|
+
context: {
|
|
2992
|
+
coldStart,
|
|
2993
|
+
// The provider key and the lease id are the only low-cardinality
|
|
2994
|
+
// context values this provider-agnostic layer holds. The region, the
|
|
2995
|
+
// image id, and the sandbox id are not threaded here, so the root span
|
|
2996
|
+
// omits them (fail open). The lease id rides only as a hash.
|
|
2997
|
+
provider: sandboxTarget?.providerKey ?? undefined,
|
|
2998
|
+
leaseId: sandboxTarget?.leaseId ?? undefined,
|
|
2999
|
+
},
|
|
3000
|
+
}));
|
|
3001
|
+
const spanParent = {
|
|
3002
|
+
tracer: tracing.tracer,
|
|
3003
|
+
parentContext: rootSpan.parentContext,
|
|
3004
|
+
// Each step uses this to publish its own child context, so an inner exec
|
|
3005
|
+
// span parents to the step span, not to the root.
|
|
3006
|
+
contextWithSpan: (span) => tracing.contextWithSpan(span),
|
|
3007
|
+
// Accumulate each step wall time into the root work sum.
|
|
3008
|
+
onWallMs: (wallMs) => {
|
|
3009
|
+
stepWallSumMs += wallMs;
|
|
3010
|
+
},
|
|
3011
|
+
};
|
|
3012
|
+
// `startupFailed` drives the sandbox.startup span end status. It stays true
|
|
3013
|
+
// until the bring-up establishes a session handle. The one `finally` below
|
|
3014
|
+
// ends the span exactly once, on every return and on every throw.
|
|
3015
|
+
let startupFailed = true;
|
|
3016
|
+
// `buildRuntimeSettled` marks that buildRuntime returned. A failure before it
|
|
3017
|
+
// settled has nothing to settle here; a failure after it settled routes
|
|
3018
|
+
// through the create_runtime handler below.
|
|
3019
|
+
let buildRuntimeSettled = false;
|
|
3020
|
+
// The bring-up assigns these. The turn and teardown paths read them after
|
|
3021
|
+
// the sandbox.startup span closes, so they are declared here.
|
|
3022
|
+
let prepared;
|
|
3023
|
+
let runtime;
|
|
3024
|
+
let sessionHandle;
|
|
3025
|
+
let childStderrState;
|
|
3026
|
+
let processIdentitySink;
|
|
3027
|
+
let resumedSession = false;
|
|
3028
|
+
let clearSession = false;
|
|
3029
|
+
let referencedProjectStagingFailuresField = {};
|
|
3030
|
+
// Set only when the settlement sync-back reports a failed workspace
|
|
3031
|
+
// restore. `reproduceResult` merges this into the returned result's
|
|
3032
|
+
// `resultJson`, so a clean run adds no new key. The run's exit code and
|
|
3033
|
+
// status stay exactly what the turn produced — this is a signal, not an
|
|
3034
|
+
// outcome change.
|
|
3035
|
+
let workspaceRestoreFailureField = {};
|
|
3036
|
+
// The one settlement step name whose error can be the same workspace-
|
|
3037
|
+
// restore failure the adapter teardown closure already classifies (a
|
|
3038
|
+
// caught error from `syncBackManagedHome`). The closure already
|
|
3039
|
+
// sanitizes its own `onLog` line; this is a second, independent layer,
|
|
3040
|
+
// so a defect in that closure (or a future call site that forgets to
|
|
3041
|
+
// sanitize) still cannot put a raw `Error.message` — and the host path
|
|
3042
|
+
// or process id it can carry — on the run log.
|
|
3043
|
+
const SYNC_BACK_SETTLEMENT_STEP = "settlement-sync_back";
|
|
3044
|
+
const recordTeardownError = async (step, teardownErr) => {
|
|
3045
|
+
const reason = step === SYNC_BACK_SETTLEMENT_STEP
|
|
3046
|
+
? describeWorkspaceRestoreFailure(classifyWorkspaceRestoreFailure(teardownErr))
|
|
3047
|
+
: teardownErr instanceof Error
|
|
3048
|
+
? teardownErr.message
|
|
3049
|
+
: String(teardownErr);
|
|
3050
|
+
await ctx
|
|
3051
|
+
.onLog("stderr", `[paperclip] ACPX teardown step "${step}" failed: ${reason}\n`)
|
|
3052
|
+
.catch(() => { });
|
|
3053
|
+
};
|
|
3054
|
+
// Emit one per-phase timing run-log event. It is not an OpenTelemetry
|
|
3055
|
+
// export and it is not a Telemetry event: it carries the phase name
|
|
3056
|
+
// (from the closed allowlist), the wall time, and the outcome, and
|
|
3057
|
+
// never a command, a path, an environment value, or an identifier. A
|
|
3058
|
+
// failure to emit this event never fails the run.
|
|
3059
|
+
const emitPhase = (phase, startMs, outcome) => emitRunPhaseTiming(ctx, phase, now() - startMs, outcome);
|
|
3060
|
+
// Time a settlement step and emit its phase timing on every path. A step
|
|
3061
|
+
// error still emits `failed` before it re-throws to the Phase 3 error policy.
|
|
3062
|
+
const timedPhase = async (phase, run) => {
|
|
3063
|
+
const start = now();
|
|
3064
|
+
try {
|
|
3065
|
+
await run();
|
|
3066
|
+
}
|
|
3067
|
+
catch (error) {
|
|
3068
|
+
await emitPhase(phase, start, "failed");
|
|
3069
|
+
throw error;
|
|
3070
|
+
}
|
|
3071
|
+
await emitPhase(phase, start, "ok");
|
|
3072
|
+
};
|
|
3073
|
+
// The turn the run started. It is hoisted to the run scope so the settlement
|
|
3074
|
+
// `endSession` step can cancel a running turn before it closes the runtime
|
|
3075
|
+
// (the cancel-before-close order). The turn wrapper assigns it in `turnStart`.
|
|
3076
|
+
let activeTurn = null;
|
|
3077
|
+
let terminalFailureClassification = null;
|
|
3078
|
+
// How the settlement `endSession` step must release the runtime for the path
|
|
3079
|
+
// this run took. Each exit path that acquired the runtime records it before it
|
|
3080
|
+
// returns; a build or create-runtime failure never registers the runtime, so
|
|
3081
|
+
// the step no-ops on the empty slot and this stays null.
|
|
3082
|
+
let runtimeSettlement = null;
|
|
3083
|
+
// True only when a handshake-guard rejection (a startup timeout or a
|
|
3084
|
+
// duplex transport loss) abandoned an `ensureSession` call. It tells the
|
|
3085
|
+
// settlement `endSession` step to defer the close to `handshakeFence`
|
|
3086
|
+
// when no late handle has arrived yet, instead of closing the synthetic
|
|
3087
|
+
// placeholder itself — closing both would close the same session twice.
|
|
3088
|
+
let handshakeAbandoned = false;
|
|
3089
|
+
// The one owner of an abandoned `ensureSession` promise for this run. See
|
|
3090
|
+
// `HandshakeAbandonmentFence` above for the state machine.
|
|
3091
|
+
const handshakeFence = new HandshakeAbandonmentFence();
|
|
3092
|
+
// A synthetic close handle from the prepared identity, for a close that has no
|
|
3093
|
+
// established session handle (a cold `ensureSession` throw or a missing
|
|
3094
|
+
// handle). `runtime.close` reads the session key and the runtime session name.
|
|
3095
|
+
const syntheticCloseHandle = () => ({
|
|
3096
|
+
sessionKey: prepared.sessionKey,
|
|
3097
|
+
backend: prepared.acpxAgent,
|
|
3098
|
+
runtimeSessionName: prepared.sessionKey,
|
|
3099
|
+
});
|
|
3100
|
+
// The run coordinator owns the one ledger for the attempt. The engine
|
|
3101
|
+
// creates it here, passes it into `buildRuntime` (the sandbox site registers
|
|
3102
|
+
// what it acquires into it), and hands it to `runAttempt` as the plan
|
|
3103
|
+
// ledger. The turn and settlement code still runs inline in the step
|
|
3104
|
+
// wrappers below; Phases 21-22 move it into its own modules.
|
|
3105
|
+
const runResourceLedger = createRunResourceLedger();
|
|
3106
|
+
// The step wrappers build the external result inline (today's behavior) and
|
|
3107
|
+
// record it here; the coordinator reproduces it after settlement. An empty
|
|
3108
|
+
// consumed set satisfies the `settle` result shape without claiming the run
|
|
3109
|
+
// ledger, because the inline teardown reads `prepared.*`, not the ledger, in
|
|
3110
|
+
// this phase.
|
|
3111
|
+
let capturedResult = null;
|
|
3112
|
+
const emptyConsumed = createRunResourceLedger().takeForSettlement();
|
|
3113
|
+
// Build the `settle` startup result for a pre-turn failure. The step wrapper
|
|
3114
|
+
// already recorded the external result and ran the inline teardown; this
|
|
3115
|
+
// carries only the routing kind and the cause for the coordinator.
|
|
3116
|
+
const settleFor = (phase, error) => ({
|
|
3117
|
+
kind: "settle",
|
|
3118
|
+
cause: {
|
|
3119
|
+
kind: "pre_turn_failed",
|
|
3120
|
+
phase,
|
|
3121
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
3122
|
+
},
|
|
3123
|
+
resources: emptyConsumed,
|
|
3124
|
+
});
|
|
3125
|
+
// The startup step: bring up the runtime, establish the session, and apply
|
|
3126
|
+
// the session config. It returns `ready` on success, `settle` on a pre-turn
|
|
3127
|
+
// failure after the ledger acquired resources, and throws on a build failure
|
|
3128
|
+
// or a partial-bridge failure (the coordinator owns that rollback).
|
|
3129
|
+
const startup = async () => {
|
|
3130
|
+
try {
|
|
3131
|
+
// Publish the `sandbox.startup` context to the runtime-parent store for
|
|
3132
|
+
// the whole bring-up. A startup-body exec that runs outside a measured
|
|
3133
|
+
// step reads this token and parents its span to `sandbox.startup`, not to
|
|
3134
|
+
// a detached root. A measured step nests its own `activeStepContextStorage`
|
|
3135
|
+
// run inside this wrap and overrides the store, so an in-step exec still
|
|
3136
|
+
// parents to its step span. On a local or SSH target
|
|
3137
|
+
// `spanParent.parentContext` is a no-op token, so the wrap is inert.
|
|
3138
|
+
const startupCancellation = cancellableSandboxStartup(ctx);
|
|
3139
|
+
try {
|
|
3140
|
+
prepared = await runWithRuntimeParent(spanParent.parentContext, () => buildRuntime({
|
|
3141
|
+
ctx: startupCancellation.context,
|
|
3142
|
+
engine,
|
|
3143
|
+
deps,
|
|
3144
|
+
ledger: runResourceLedger,
|
|
3145
|
+
stagedIdleMs: warmIdleMs,
|
|
3146
|
+
spanParent,
|
|
3147
|
+
getRuntimeParentContext,
|
|
3148
|
+
runtimeSpan: runRuntimeSpan,
|
|
3149
|
+
stageRuntimeSpan: runStageSpan,
|
|
3150
|
+
}));
|
|
3151
|
+
buildRuntimeSettled = true;
|
|
3152
|
+
// Capture acquired resources before the cancellation boundary so the
|
|
3153
|
+
// normal settlement path also releases a just-completed build.
|
|
3154
|
+
releaseStagingLease = prepared.sessionStagingLeaseRelease;
|
|
3155
|
+
}
|
|
3156
|
+
finally {
|
|
3157
|
+
await startupCancellation.finish();
|
|
3158
|
+
}
|
|
3159
|
+
// Per-project staging outcomes for the referenced (mentioned) projects, surfaced back to the
|
|
3160
|
+
// server on the run result. A referenced project that failed to stage into the sandbox is a
|
|
3161
|
+
// first-class, counted failure in the requested-vs-synced observability, not only a warning. The
|
|
3162
|
+
// list is empty on a local target, on a transport that does not stage referenced projects, or
|
|
3163
|
+
// when every staged referenced project succeeded, so the spread adds the field only when there
|
|
3164
|
+
// is a failure to report.
|
|
3165
|
+
const referencedProjectStagingFailures = (prepared.stagedRuntime?.additionalSourceFailures ?? []).map((failure) => ({ projectId: failure.projectId, error: failure.error }));
|
|
3166
|
+
referencedProjectStagingFailuresField =
|
|
3167
|
+
referencedProjectStagingFailures.length > 0 ? { referencedProjectStagingFailures } : {};
|
|
3168
|
+
// Write one run-log line per failure, so a reader of the run log alone sees
|
|
3169
|
+
// each dropped referenced project and its reason. The run continues without
|
|
3170
|
+
// the failed project (per-project failure isolation). Goes to stderr: the
|
|
3171
|
+
// acpx stdout log stream carries machine-parseable JSON event payloads.
|
|
3172
|
+
for (const failure of referencedProjectStagingFailures) {
|
|
3173
|
+
await ctx.onLog("stderr", `[paperclip] Referenced project ${failure.projectId} failed to stage; the run continues without it: ${failure.error}\n`);
|
|
3174
|
+
}
|
|
3175
|
+
// State the effective wall-clock timeout and its source up front so a
|
|
3176
|
+
// later timeout is diagnosable from the run log alone. Goes to stderr:
|
|
3177
|
+
// the acpx stdout log stream carries JSON acpx.* event payloads and must
|
|
3178
|
+
// stay machine-parseable line by line.
|
|
3179
|
+
await ctx.onLog("stderr", `[paperclip] ${formatAdapterExecutionTimeoutStartLogLine(prepared.timeoutResolution)}\n`);
|
|
3180
|
+
await hostStore.evictIdle(now());
|
|
3181
|
+
const previousParams = parseObject(ctx.runtime.sessionParams);
|
|
3182
|
+
const canResume = isCompatibleSession(previousParams, prepared);
|
|
3183
|
+
const resumeSessionId = canResume ? asString(previousParams.acpSessionId, "") || undefined : undefined;
|
|
3184
|
+
// Borrow the warm entry without removing it, so an overlapping run of the
|
|
3185
|
+
// same session still sees it. The borrow clears the entry's idle timer, so
|
|
3186
|
+
// the reused runtime cannot expire under its own timer while this run uses
|
|
3187
|
+
// it (today's `clearWarmHandleTimer(cached)` after the reuse decision).
|
|
3188
|
+
const cached = canResume ? hostStore.borrow(prepared.sessionKey) : undefined;
|
|
3189
|
+
childStderrState = cached?.childStderrState ?? { logPath: null, pendingLiveLine: "" };
|
|
3190
|
+
processIdentitySink = cached?.processIdentitySink ?? {
|
|
3191
|
+
current: ctx.onSpawn,
|
|
3192
|
+
latest: null,
|
|
3193
|
+
};
|
|
3194
|
+
// ACPX runtimes can stay warm across heartbeat runs. Keep the callback
|
|
3195
|
+
// target mutable so a later agent respawn records identity on the current
|
|
3196
|
+
// heartbeat instead of the run that originally created the runtime.
|
|
3197
|
+
processIdentitySink.current = ctx.onSpawn;
|
|
3198
|
+
flushChildStderr(childStderrState);
|
|
3199
|
+
childStderrState.logPath = prepared.childStderrLogPath;
|
|
3200
|
+
const persistedRuntimeStore = createRuntimeStore({ stateDir: prepared.stateDir });
|
|
3201
|
+
const runtimeStore = {
|
|
3202
|
+
async load(id) {
|
|
3203
|
+
const record = await persistedRuntimeStore.load(id);
|
|
3204
|
+
if (!record)
|
|
3205
|
+
return undefined;
|
|
3206
|
+
// ACPX resumes from the stored session options rather than the
|
|
3207
|
+
// options passed to ensureSession. Keep conversation state, but
|
|
3208
|
+
// launch the provider with this run's credentials and scratch paths.
|
|
3209
|
+
return {
|
|
3210
|
+
...record,
|
|
3211
|
+
acpx: {
|
|
3212
|
+
...record.acpx,
|
|
3213
|
+
session_options: {
|
|
3214
|
+
...record.acpx?.session_options,
|
|
3215
|
+
env: { ...prepared.env },
|
|
3216
|
+
},
|
|
3217
|
+
},
|
|
3218
|
+
};
|
|
3219
|
+
},
|
|
3220
|
+
save: (record) => persistedRuntimeStore.save(record),
|
|
3221
|
+
};
|
|
3222
|
+
const runtimeOptions = {
|
|
3223
|
+
cwd: prepared.cwd,
|
|
3224
|
+
// Host-only spawn cwd for the relay proxy on the remote process-session
|
|
3225
|
+
// lane; `undefined` elsewhere so acpx falls back to `cwd` (byte-identical).
|
|
3226
|
+
// The advertised `session/new` cwd (`prepared.cwd` = `remoteCwd`) and the
|
|
3227
|
+
// fingerprint / compat key are unaffected — this redirects ONLY the host
|
|
3228
|
+
// `spawn()` `chdir`, not the in-sandbox data path.
|
|
3229
|
+
spawnCwd: prepared.hostSpawnCwd,
|
|
3230
|
+
sessionStore: runtimeStore,
|
|
3231
|
+
agentRegistry: prepared.agentRegistry,
|
|
3232
|
+
permissionMode: prepared.permissionMode,
|
|
3233
|
+
nonInteractivePermissions: prepared.nonInteractivePermissions,
|
|
3234
|
+
mcpServers: prepared.mcpServers,
|
|
3235
|
+
timeoutMs: prepared.timeoutSec > 0 ? prepared.timeoutSec * 1000 : undefined,
|
|
3236
|
+
// Scope ACPX runtime verbose logs to the claude agent only. Codex
|
|
3237
|
+
// and custom agents already emit their own per-tool output and don't
|
|
3238
|
+
// benefit from doubling the log volume.
|
|
3239
|
+
verbose: prepared.acpxAgent === "claude",
|
|
3240
|
+
// The engine passes a complete, sanitized launch environment. ACPX
|
|
3241
|
+
// must not merge the Paperclip server's ambient environment back in
|
|
3242
|
+
// when it spawns the provider child.
|
|
3243
|
+
inheritProcessEnv: false,
|
|
3244
|
+
onAgentStderr: prepared.childStderrLogPath
|
|
3245
|
+
? (chunk) => routeChildStderr(childStderrState, chunk)
|
|
3246
|
+
: undefined,
|
|
3247
|
+
onAgentSpawn: async (meta) => {
|
|
3248
|
+
processIdentitySink.latest = meta;
|
|
3249
|
+
processIdentitySink.localProcess = prepared.processSessionBridge ? undefined : captureLocalProcess(meta.pid);
|
|
3250
|
+
await processIdentitySink.current?.({
|
|
3251
|
+
pid: meta.pid,
|
|
3252
|
+
processGroupId: null,
|
|
3253
|
+
startedAt: meta.startedAt,
|
|
3254
|
+
});
|
|
3255
|
+
},
|
|
3256
|
+
getRuntimeParentContext,
|
|
3257
|
+
};
|
|
3258
|
+
// Open Q2: split the ~7s `acp.handshake` into the two in-repo-observable
|
|
3259
|
+
// sub-phases — the ACP runtime construction (`createRuntime`) vs the session
|
|
3260
|
+
// establishment envelope (`ensureSession`). The patched spawn lifecycle
|
|
3261
|
+
// hook records process identity, but the finer spawn/`initialize`/
|
|
3262
|
+
// `session/new` timing split still lives inside external `acpx`.
|
|
3263
|
+
// `createRuntime` runs once and only on a cold start; a warm-handle hit
|
|
3264
|
+
// reuses `cached.runtime`, so `createRuntimeMs` stays undefined and the
|
|
3265
|
+
// split reports nothing for it.
|
|
3266
|
+
let createRuntimeMs;
|
|
3267
|
+
// A warm handle reuses the running ACP runtime; a miss constructs one. The
|
|
3268
|
+
// root span records this as `cold_start`.
|
|
3269
|
+
coldStart = !cached?.runtime;
|
|
3270
|
+
if (cached?.runtime) {
|
|
3271
|
+
runtime = cached.runtime;
|
|
3272
|
+
}
|
|
3273
|
+
else {
|
|
3274
|
+
const createRuntimeStart = now();
|
|
3275
|
+
runtime = createRuntime(runtimeOptions);
|
|
3276
|
+
createRuntimeMs = now() - createRuntimeStart;
|
|
3277
|
+
// The create_runtime phase runs only on a cold start.
|
|
3278
|
+
await emitRunPhaseTiming(ctx, "create_runtime", createRuntimeMs, "ok");
|
|
3279
|
+
}
|
|
3280
|
+
// Register the runtime composite in the ledger now that it exists. The
|
|
3281
|
+
// settlement `endSession` step closes it on every path the reuse decision
|
|
3282
|
+
// does not transfer. Registration here (before `ensureSession`) closes the
|
|
3283
|
+
// cold-handshake leak: a cold `ensureSession` throw before a handle exists
|
|
3284
|
+
// still leaves the runtime in the ledger for `endSession` to close. A
|
|
3285
|
+
// create-runtime failure throws before this line, so the runtime never
|
|
3286
|
+
// enters the ledger and the settlement no-ops on the empty slot. The
|
|
3287
|
+
// session handle fills in below; the payload carries the best-known handle
|
|
3288
|
+
// for the composite (`runtime.close` is the one release boundary).
|
|
3289
|
+
runResourceLedger.register({
|
|
3290
|
+
id: "acp_runtime",
|
|
3291
|
+
payload: {
|
|
3292
|
+
runtime,
|
|
3293
|
+
sessionHandle: cached?.handle ?? syntheticCloseHandle(),
|
|
3294
|
+
childProcessPid: processIdentitySink.latest?.pid ?? null,
|
|
3295
|
+
},
|
|
3296
|
+
scope: "per_run",
|
|
3297
|
+
});
|
|
3298
|
+
if (!canResume && asString(previousParams.runtimeSessionName, "")) {
|
|
3299
|
+
await ctx.onLog("stdout", `[paperclip] ACPX session "${asString(previousParams.runtimeSessionName, "")}" does not match the current agent/cwd/mode/runtime identity; starting fresh in "${prepared.cwd}".\n`);
|
|
3300
|
+
}
|
|
3301
|
+
let handle = cached?.handle ?? null;
|
|
3302
|
+
// The ensure_session phase covers the handshake (session establishment or
|
|
3303
|
+
// resume) up to the established handle.
|
|
3304
|
+
const ensureSessionPhaseStart = now();
|
|
3305
|
+
resumedSession = Boolean(handle ?? resumeSessionId);
|
|
3306
|
+
const isHandshakeTransportLost = () => prepared.paperclipBridge?.readRunDisposition?.().failed ?? false;
|
|
3307
|
+
// The fence's own close, for a real handle that resolves after
|
|
3308
|
+
// `endSession` already sealed. `endSession` closed the synthetic
|
|
3309
|
+
// placeholder by then (or skipped closing because the channel was
|
|
3310
|
+
// already known lost), so this is the one close for this real handle,
|
|
3311
|
+
// not a second one.
|
|
3312
|
+
// A rejected close comes from inside the sandbox (a forged late handle
|
|
3313
|
+
// can make `runtime.close` reject with chosen bytes). It crosses the
|
|
3314
|
+
// sandbox-to-host trust boundary, so it must never reach the run log,
|
|
3315
|
+
// the result, or a classification: log the fixed closed code only.
|
|
3316
|
+
// The catch stays attached so the promise stays observed and no
|
|
3317
|
+
// unhandled rejection can occur.
|
|
3318
|
+
const closeLateHandshakeHandle = (lateHandle) => {
|
|
3319
|
+
if (isHandshakeTransportLost())
|
|
3320
|
+
return;
|
|
3321
|
+
void runtime
|
|
3322
|
+
.close({
|
|
3323
|
+
handle: lateHandle,
|
|
3324
|
+
reason: "paperclip late handshake cleanup",
|
|
3325
|
+
discardPersistentState: false,
|
|
3326
|
+
})
|
|
3327
|
+
.catch(() => ctx
|
|
3328
|
+
.onLog("stderr", "[paperclip] ACPX handshake late close failed: acpx_handshake_late_close_failed\n")
|
|
3329
|
+
.catch(() => { }));
|
|
3330
|
+
};
|
|
3331
|
+
// The late rejection comes from inside the sandbox. It crosses the
|
|
3332
|
+
// sandbox-to-host trust boundary, so it must never reach the run log,
|
|
3333
|
+
// the result, or a classification: log the fixed closed code only.
|
|
3334
|
+
const recordLateHandshakeRejection = () => {
|
|
3335
|
+
void ctx
|
|
3336
|
+
.onLog("stderr", "[paperclip] ACPX handshake late rejection: acpx_handshake_late_rejection\n")
|
|
3337
|
+
.catch(() => { });
|
|
3338
|
+
};
|
|
3339
|
+
try {
|
|
3340
|
+
if (!handle) {
|
|
3341
|
+
try {
|
|
3342
|
+
// Step 7 — acp.handshake: ACP session establishment (session/new or
|
|
3343
|
+
// resume). A throwing handshake still reports its duration before the
|
|
3344
|
+
// resume-retry path below runs. The createRuntime/ensureSession
|
|
3345
|
+
// sub-split rides the step span as fixed, closed keys (Open Q2).
|
|
3346
|
+
let ensureSessionMs;
|
|
3347
|
+
handle = await measureStartupStep(ctx, now, "acp.handshake", async () => {
|
|
3348
|
+
const ensureSessionStart = now();
|
|
3349
|
+
const established = await guardEnsureSession({
|
|
3350
|
+
call: () => runtime.ensureSession({
|
|
3351
|
+
sessionKey: prepared.sessionKey,
|
|
3352
|
+
agent: prepared.acpxAgent,
|
|
3353
|
+
mode: prepared.mode,
|
|
3354
|
+
cwd: prepared.cwd,
|
|
3355
|
+
resumeSessionId,
|
|
3356
|
+
sessionOptions: { env: prepared.env },
|
|
3357
|
+
}),
|
|
3358
|
+
fence: handshakeFence,
|
|
3359
|
+
isTransportLost: isHandshakeTransportLost,
|
|
3360
|
+
onLateHandleAfterSeal: closeLateHandshakeHandle,
|
|
3361
|
+
onLateRejection: recordLateHandshakeRejection,
|
|
3362
|
+
});
|
|
3363
|
+
ensureSessionMs = now() - ensureSessionStart;
|
|
3364
|
+
return established;
|
|
3365
|
+
}, {
|
|
3366
|
+
...prepared.stepMetrics,
|
|
3367
|
+
// The two sub-times ride the span as fixed, closed keys.
|
|
3368
|
+
spanWallTimes: () => ({
|
|
3369
|
+
createRuntime: createRuntimeMs,
|
|
3370
|
+
ensureSession: ensureSessionMs,
|
|
3371
|
+
}),
|
|
3372
|
+
});
|
|
3373
|
+
}
|
|
3374
|
+
catch (err) {
|
|
3375
|
+
if (!resumeSessionId || !isResumeFailure(err))
|
|
3376
|
+
throw err;
|
|
3377
|
+
clearSession = true;
|
|
3378
|
+
resumedSession = false;
|
|
3379
|
+
await ctx.onLog("stdout", `[paperclip] ACPX resume session "${resumeSessionId}" is unavailable; retrying with a fresh session.\n`);
|
|
3380
|
+
// Fresh-session retry: the runtime was already constructed on the
|
|
3381
|
+
// first attempt (never re-created), so this event reports only its
|
|
3382
|
+
// own `ensureSessionMs` — no `createRuntimeMs`.
|
|
3383
|
+
let retryEnsureSessionMs;
|
|
3384
|
+
handle = await measureStartupStep(ctx, now, "acp.handshake", async () => {
|
|
3385
|
+
const ensureSessionStart = now();
|
|
3386
|
+
const established = await guardEnsureSession({
|
|
3387
|
+
call: () => runtime.ensureSession({
|
|
3388
|
+
sessionKey: prepared.sessionKey,
|
|
3389
|
+
agent: prepared.acpxAgent,
|
|
3390
|
+
mode: prepared.mode,
|
|
3391
|
+
cwd: prepared.cwd,
|
|
3392
|
+
sessionOptions: { env: prepared.env },
|
|
3393
|
+
}),
|
|
3394
|
+
fence: handshakeFence,
|
|
3395
|
+
isTransportLost: isHandshakeTransportLost,
|
|
3396
|
+
onLateHandleAfterSeal: closeLateHandshakeHandle,
|
|
3397
|
+
onLateRejection: recordLateHandshakeRejection,
|
|
3398
|
+
});
|
|
3399
|
+
retryEnsureSessionMs = now() - ensureSessionStart;
|
|
3400
|
+
return established;
|
|
3401
|
+
}, {
|
|
3402
|
+
...prepared.stepMetrics,
|
|
3403
|
+
// The retry reuses the runtime from the first attempt, so it reports
|
|
3404
|
+
// only its own ensure-session sub-time on the span.
|
|
3405
|
+
spanWallTimes: () => ({ ensureSession: retryEnsureSessionMs }),
|
|
3406
|
+
});
|
|
3407
|
+
}
|
|
3408
|
+
}
|
|
3409
|
+
else {
|
|
3410
|
+
// Warm-handle hit: a compatible cached handle reuses the running ACP
|
|
3411
|
+
// agent, so the `acp.handshake` step does no work. Emit a step span and
|
|
3412
|
+
// event with `outcome = skipped` and a zero wall time, so the trace and
|
|
3413
|
+
// the run log show the skip as a distinct outcome, never a misleading
|
|
3414
|
+
// zero-work `ok` step.
|
|
3415
|
+
await emitSkippedStartupStep(ctx, "acp.handshake", {
|
|
3416
|
+
tracer: prepared.stepMetrics.tracer,
|
|
3417
|
+
parentContext: prepared.stepMetrics.parentContext,
|
|
3418
|
+
});
|
|
3419
|
+
}
|
|
3420
|
+
if (resumeSessionId && handle?.backendSessionId !== resumeSessionId) {
|
|
3421
|
+
resumedSession = false;
|
|
3422
|
+
clearSession = true;
|
|
3423
|
+
}
|
|
3424
|
+
// A compatible warm handle reuses the already-running ACP agent and does
|
|
3425
|
+
// not emit another spawn event. Persist its known identity on this run
|
|
3426
|
+
// before the next prompt starts so every running heartbeat is adoptable.
|
|
3427
|
+
if (handle && cached && processIdentitySink.latest && ctx.onSpawn) {
|
|
3428
|
+
await ctx.onSpawn({
|
|
3429
|
+
pid: processIdentitySink.latest.pid,
|
|
3430
|
+
processGroupId: null,
|
|
3431
|
+
startedAt: processIdentitySink.latest.startedAt,
|
|
3432
|
+
});
|
|
3433
|
+
}
|
|
3434
|
+
}
|
|
3435
|
+
catch (err) {
|
|
3436
|
+
// Record how the settlement closes the runtime on a pre-turn handshake
|
|
3437
|
+
// failure: a direct close that drops a matching warm entry, the same as
|
|
3438
|
+
// the configuration-failure path. A warm-hit reuse already cleared the
|
|
3439
|
+
// idle timer before the handshake ran, so the failure must close and
|
|
3440
|
+
// remove the entry, never re-arm it. A cold `ensureSession` throw has no
|
|
3441
|
+
// established handle, so the settlement closes the synthetic handle — the
|
|
3442
|
+
// ledger now holds the runtime, so `endSession` closes it and the former
|
|
3443
|
+
// cold-handshake leak is gone. The settlement stops the bridges, releases
|
|
3444
|
+
// the staging lease, and flushes the child stderr on every exit path.
|
|
3445
|
+
//
|
|
3446
|
+
// A handshake-guard rejection is the one exception: `handle` stays
|
|
3447
|
+
// null, but the abandoned `ensureSession` call can still resolve
|
|
3448
|
+
// into a real handle later. `handshakeAbandoned` tells `endSession`
|
|
3449
|
+
// to defer that placeholder close to `handshakeFence` when no late
|
|
3450
|
+
// handle has arrived yet, instead of closing it here and possibly
|
|
3451
|
+
// again later.
|
|
3452
|
+
const guardTripped = isAcpxHandshakeTimeoutError(err) || isAcpxHandshakeTransportLostError(err);
|
|
3453
|
+
handshakeAbandoned = guardTripped;
|
|
3454
|
+
runtimeSettlement = {
|
|
3455
|
+
mode: "direct",
|
|
3456
|
+
handle: handle ?? syntheticCloseHandle(),
|
|
3457
|
+
reason: "paperclip handshake cleanup",
|
|
3458
|
+
discardPersistentState: false,
|
|
3459
|
+
dropWarmEntry: true,
|
|
3460
|
+
recordCloseError: true,
|
|
3461
|
+
cancelTurnReason: null,
|
|
3462
|
+
skipRemoteClose: false,
|
|
3463
|
+
};
|
|
3464
|
+
await emitPhase("ensure_session", ensureSessionPhaseStart, "failed");
|
|
3465
|
+
const { classified, message } = await emitAcpxFailure({
|
|
3466
|
+
ctx,
|
|
3467
|
+
prepared,
|
|
3468
|
+
err,
|
|
3469
|
+
phase: "ensure_session",
|
|
3470
|
+
// The guard's own message is already generic and host-authored, so
|
|
3471
|
+
// this replaces the underlying error text with the same closed
|
|
3472
|
+
// message `classifyError` classified — the ensure_session phase
|
|
3473
|
+
// never gets a chance to summarize it differently.
|
|
3474
|
+
...(guardTripped && err instanceof Error ? { messageOverride: err.message } : {}),
|
|
3475
|
+
// A compromised child can hold ensureSession open until the guard
|
|
3476
|
+
// fires, then write chosen bytes to its own stderr. Suppress the
|
|
3477
|
+
// child stderr tail on this route only, so those sandbox-provided
|
|
3478
|
+
// bytes never reach the run log or the acpx.error payload.
|
|
3479
|
+
...(guardTripped ? { suppressChildStderrTail: true } : {}),
|
|
3480
|
+
});
|
|
3481
|
+
capturedResult = {
|
|
3482
|
+
exitCode: 1,
|
|
3483
|
+
signal: null,
|
|
3484
|
+
timedOut: false,
|
|
3485
|
+
errorMessage: message,
|
|
3486
|
+
...classified,
|
|
3487
|
+
...billingFields,
|
|
3488
|
+
...referencedProjectStagingFailuresField,
|
|
3489
|
+
model: prepared.requestedModel || null,
|
|
3490
|
+
clearSession,
|
|
3491
|
+
resultJson: { phase: "ensure_session" },
|
|
3492
|
+
summary: message,
|
|
3493
|
+
};
|
|
3494
|
+
return settleFor("handshake", err);
|
|
3495
|
+
}
|
|
3496
|
+
if (!handle) {
|
|
3497
|
+
// ensureSession returned no session handle. The ledger holds the runtime
|
|
3498
|
+
// the run constructed, so record a direct close of the synthetic handle
|
|
3499
|
+
// for the settlement `endSession` step (the child process cannot leak).
|
|
3500
|
+
runtimeSettlement = {
|
|
3501
|
+
mode: "direct",
|
|
3502
|
+
handle: syntheticCloseHandle(),
|
|
3503
|
+
reason: "paperclip missing-handle cleanup",
|
|
3504
|
+
discardPersistentState: false,
|
|
3505
|
+
dropWarmEntry: false,
|
|
3506
|
+
recordCloseError: true,
|
|
3507
|
+
cancelTurnReason: null,
|
|
3508
|
+
skipRemoteClose: false,
|
|
3509
|
+
};
|
|
3510
|
+
await emitPhase("ensure_session", ensureSessionPhaseStart, "failed");
|
|
3511
|
+
capturedResult = {
|
|
3512
|
+
exitCode: 1,
|
|
3513
|
+
signal: null,
|
|
3514
|
+
timedOut: false,
|
|
3515
|
+
errorMessage: "ACPX did not return a runtime session handle.",
|
|
3516
|
+
errorCode: "acpx_runtime_error",
|
|
3517
|
+
...billingFields,
|
|
3518
|
+
...referencedProjectStagingFailuresField,
|
|
3519
|
+
model: prepared.requestedModel || null,
|
|
3520
|
+
resultJson: { phase: "ensure_session" },
|
|
3521
|
+
summary: "ACPX did not return a runtime session handle.",
|
|
3522
|
+
};
|
|
3523
|
+
return settleFor("session_handle_missing", new Error("ACPX did not return a runtime session handle."));
|
|
3524
|
+
}
|
|
3525
|
+
sessionHandle = handle;
|
|
3526
|
+
startupFailed = false;
|
|
3527
|
+
await emitPhase("ensure_session", ensureSessionPhaseStart, "ok");
|
|
3528
|
+
}
|
|
3529
|
+
catch (err) {
|
|
3530
|
+
if (!buildRuntimeSettled) {
|
|
3531
|
+
// buildRuntime failed before it staged or bridged anything, so there is
|
|
3532
|
+
// nothing to settle here. The finally below ends the sandbox.startup
|
|
3533
|
+
// span; let the failure propagate.
|
|
3534
|
+
throw err;
|
|
3535
|
+
}
|
|
3536
|
+
// The post-build runtime-creation window failed after buildRuntime returned
|
|
3537
|
+
// live bridges and a held staging lease. `createRuntime` throws before the
|
|
3538
|
+
// ledger registers the runtime, so the settlement `endSession` step no-ops
|
|
3539
|
+
// on the empty slot (create_runtime never closes a runtime). The settlement
|
|
3540
|
+
// still stops the bridges, discards the staged runtime, releases the staging
|
|
3541
|
+
// lease, and flushes the child stderr.
|
|
3542
|
+
const { classified, message } = await emitAcpxFailure({
|
|
3543
|
+
ctx,
|
|
3544
|
+
prepared,
|
|
3545
|
+
err,
|
|
3546
|
+
phase: "create_runtime",
|
|
3547
|
+
});
|
|
3548
|
+
capturedResult = {
|
|
3549
|
+
exitCode: 1,
|
|
3550
|
+
signal: null,
|
|
3551
|
+
timedOut: false,
|
|
3552
|
+
errorMessage: message,
|
|
3553
|
+
...classified,
|
|
3554
|
+
...billingFields,
|
|
3555
|
+
...referencedProjectStagingFailuresField,
|
|
3556
|
+
model: prepared.requestedModel || null,
|
|
3557
|
+
clearSession,
|
|
3558
|
+
resultJson: { phase: "create_runtime" },
|
|
3559
|
+
summary: message,
|
|
3560
|
+
};
|
|
3561
|
+
return settleFor("runtime_create", err);
|
|
3562
|
+
}
|
|
3563
|
+
finally {
|
|
3564
|
+
// End the sandbox.startup span exactly once, on every return and on every
|
|
3565
|
+
// throw. It covers buildRuntime through acp.handshake and no further; the
|
|
3566
|
+
// agent turn runs after and is out of the span's scope.
|
|
3567
|
+
rootSpan.end(startupFailed);
|
|
3568
|
+
}
|
|
3569
|
+
const configureSessionStart = now();
|
|
3570
|
+
try {
|
|
3571
|
+
await applySessionConfigOptions({
|
|
3572
|
+
runtime,
|
|
3573
|
+
handle: sessionHandle,
|
|
3574
|
+
prepared,
|
|
3575
|
+
onLog: ctx.onLog,
|
|
3576
|
+
});
|
|
3577
|
+
await emitPhase("configure_session", configureSessionStart, "ok");
|
|
3578
|
+
}
|
|
3579
|
+
catch (err) {
|
|
3580
|
+
// Record a direct close that drops the matching warm entry for the
|
|
3581
|
+
// settlement `endSession` step. The settlement stops the bridges, discards
|
|
3582
|
+
// the staged runtime, releases the staging lease, and flushes the child
|
|
3583
|
+
// stderr on this exit path too.
|
|
3584
|
+
runtimeSettlement = {
|
|
3585
|
+
mode: "direct",
|
|
3586
|
+
handle: sessionHandle,
|
|
3587
|
+
reason: "paperclip config cleanup",
|
|
3588
|
+
discardPersistentState: false,
|
|
3589
|
+
dropWarmEntry: true,
|
|
3590
|
+
recordCloseError: true,
|
|
3591
|
+
cancelTurnReason: null,
|
|
3592
|
+
skipRemoteClose: false,
|
|
3593
|
+
};
|
|
3594
|
+
await emitPhase("configure_session", configureSessionStart, "failed");
|
|
3595
|
+
const { classified, message } = await emitAcpxFailure({
|
|
3596
|
+
ctx,
|
|
3597
|
+
prepared,
|
|
3598
|
+
err,
|
|
3599
|
+
phase: "configure_session",
|
|
3600
|
+
});
|
|
3601
|
+
capturedResult = {
|
|
3602
|
+
exitCode: 1,
|
|
3603
|
+
signal: null,
|
|
3604
|
+
timedOut: false,
|
|
3605
|
+
errorMessage: message,
|
|
3606
|
+
...classified,
|
|
3607
|
+
...billingFields,
|
|
3608
|
+
...referencedProjectStagingFailuresField,
|
|
3609
|
+
model: prepared.requestedModel || null,
|
|
3610
|
+
clearSession,
|
|
3611
|
+
resultJson: {
|
|
3612
|
+
phase: "configure_session",
|
|
3613
|
+
agent: prepared.acpxAgent,
|
|
3614
|
+
requestedModel: prepared.requestedModel || null,
|
|
3615
|
+
requestedThinkingEffort: prepared.requestedThinkingEffort || null,
|
|
3616
|
+
fastMode: prepared.fastMode,
|
|
3617
|
+
},
|
|
3618
|
+
summary: message,
|
|
3619
|
+
};
|
|
3620
|
+
return settleFor("session_configuration", err);
|
|
3621
|
+
}
|
|
3622
|
+
// Startup succeeded: seal the ledger (promotes the startup_rollback entries
|
|
3623
|
+
// that survived to `per_run`) and hand the ready resources to the turn. The
|
|
3624
|
+
// turn wrapper reads the run state through the shared closure locals in this
|
|
3625
|
+
// phase, so the sealed view and the context are the contract carriers only.
|
|
3626
|
+
return {
|
|
3627
|
+
kind: "ready",
|
|
3628
|
+
resources: runResourceLedger.seal([]),
|
|
3629
|
+
context: { sessionKey: prepared.sessionKey },
|
|
3630
|
+
};
|
|
3631
|
+
};
|
|
3632
|
+
// The turn step: run the turn sequence and settle the runtime inline
|
|
3633
|
+
// (today's behavior). The sequence owns the wall-clock timer and the abort
|
|
3634
|
+
// controller and never rejects; it returns a `TurnCompletion`. The step
|
|
3635
|
+
// bodies below record the external result for the coordinator to reproduce.
|
|
3636
|
+
const runTurn = async (_ready) => {
|
|
3637
|
+
// Summary accumulation collects output text only (never thought stream),
|
|
3638
|
+
// segmented on tool starts so multi-step narration is not glued into one
|
|
3639
|
+
// automatic comment dump.
|
|
3640
|
+
const outputSegments = [];
|
|
3641
|
+
let currentOutputChunk = [];
|
|
3642
|
+
const flushOutputSegment = () => {
|
|
3643
|
+
if (currentOutputChunk.length === 0)
|
|
3644
|
+
return;
|
|
3645
|
+
outputSegments.push(currentOutputChunk.join(""));
|
|
3646
|
+
currentOutputChunk = [];
|
|
3647
|
+
};
|
|
3648
|
+
let eventBreakdown = null;
|
|
3649
|
+
let eventCostUsd = null;
|
|
3650
|
+
// The turn-local state the sequence steps share. `promptBuild` sets the
|
|
3651
|
+
// prompt, `preTurnUsage` sets the pre-turn status, `turnStart` sets the
|
|
3652
|
+
// active turn, and `turnFinalize` reads all three. `activeTurn` is the run-
|
|
3653
|
+
// scoped hoisted local, so the settlement `endSession` step can cancel it.
|
|
3654
|
+
let runPrompt = "";
|
|
3655
|
+
let preTurnStatus = null;
|
|
3656
|
+
// Phase-timing markers for the prepare_turn and turn phases. The prepare
|
|
3657
|
+
// phase covers the prompt build and the pre-turn usage snapshot; the turn
|
|
3658
|
+
// phase covers the started turn and the event relay.
|
|
3659
|
+
let preparePhaseStart = now();
|
|
3660
|
+
let turnPhaseStart = now();
|
|
3661
|
+
// Open the agent turn span as a child of the run root span. It wraps the
|
|
3662
|
+
// whole turn: the executor holds `turnSpan.parentContext` for later exec
|
|
3663
|
+
// parenting, and the `finally` below ends the span once on every path. The
|
|
3664
|
+
// span is declared before the `try` so the `finally` can reach it.
|
|
3665
|
+
const turnSpan = openTurnSpan(tracing, now, runRootSpan.parentContext);
|
|
3666
|
+
// Switch the current-run holder to the `agent.turn` token for the turn, so
|
|
3667
|
+
// a detached exec during the turn parents to `agent.turn`. The turn
|
|
3668
|
+
// `finally` resets the holder to the `task.run` token.
|
|
3669
|
+
currentRunParentContext = turnSpan.parentContext;
|
|
3670
|
+
const stepPromptBuild = async (_signal) => {
|
|
3671
|
+
// Build the prompt and emit the run metadata inside the turn failure
|
|
3672
|
+
// boundary. A failure here returns an error result with phase
|
|
3673
|
+
// `prepare_turn`.
|
|
3674
|
+
preparePhaseStart = now();
|
|
3675
|
+
const { prompt, promptMetrics, commandNotes } = await buildPrompt(ctx, resumedSession, prepared.env);
|
|
3676
|
+
runPrompt = joinPromptSections([prepared.skillPromptInstructions, prompt]);
|
|
3677
|
+
await emitAcpxLog(ctx, {
|
|
3678
|
+
type: "acpx.session",
|
|
3679
|
+
agent: prepared.acpxAgent,
|
|
3680
|
+
sessionId: sessionHandle.backendSessionId,
|
|
3681
|
+
acpSessionId: sessionHandle.backendSessionId,
|
|
3682
|
+
agentSessionId: sessionHandle.agentSessionId,
|
|
3683
|
+
runtimeSessionName: sessionHandle.runtimeSessionName,
|
|
3684
|
+
mode: prepared.mode,
|
|
3685
|
+
permissionMode: prepared.permissionMode,
|
|
3686
|
+
model: prepared.requestedModel || null,
|
|
3687
|
+
thinkingEffort: prepared.requestedThinkingEffort || null,
|
|
3688
|
+
fastMode: prepared.fastMode,
|
|
3689
|
+
});
|
|
3690
|
+
if (ctx.onMeta) {
|
|
3691
|
+
await ctx.onMeta({
|
|
3692
|
+
adapterType: engine.adapterType,
|
|
3693
|
+
command: prepared.agentCommand ?? prepared.acpxAgent,
|
|
3694
|
+
cwd: prepared.cwd,
|
|
3695
|
+
commandNotes: [
|
|
3696
|
+
`ACPX runtime embedded in Paperclip with ${prepared.mode} session mode.`,
|
|
3697
|
+
`Effective ACPX permission mode: ${prepared.permissionMode}.`,
|
|
3698
|
+
...(prepared.requestedModel
|
|
3699
|
+
? [
|
|
3700
|
+
prepared.acpxAgent === "claude"
|
|
3701
|
+
? `Requested ACPX model: ${prepared.requestedModel} (set via ANTHROPIC_MODEL env at startup).`
|
|
3702
|
+
: prepared.acpxAgent === "codex"
|
|
3703
|
+
? `Requested ACPX model: ${prepared.requestedModel} (set via CODEX_CONFIG at startup).`
|
|
3704
|
+
: `Requested ACPX model: ${prepared.requestedModel}.`,
|
|
3705
|
+
]
|
|
3706
|
+
: []),
|
|
3707
|
+
...(prepared.requestedThinkingEffort ? [`Requested ACPX thinking effort: ${prepared.requestedThinkingEffort}.`] : []),
|
|
3708
|
+
...(prepared.fastMode ? ["Requested ACPX Codex fast mode."] : []),
|
|
3709
|
+
...(Array.isArray(prepared.skillsIdentity.commandNotes)
|
|
3710
|
+
? prepared.skillsIdentity.commandNotes.filter((note) => typeof note === "string")
|
|
3711
|
+
: []),
|
|
3712
|
+
...commandNotes,
|
|
3713
|
+
],
|
|
3714
|
+
env: prepared.loggedEnv,
|
|
3715
|
+
prompt: runPrompt,
|
|
3716
|
+
promptMetrics,
|
|
3717
|
+
context: ctx.context,
|
|
3718
|
+
});
|
|
3719
|
+
}
|
|
3720
|
+
};
|
|
3721
|
+
const stepPreTurnUsage = async () => {
|
|
3722
|
+
// Snapshot pre-turn usage so cumulative agent-reported cost can be
|
|
3723
|
+
// attributed to this run alone.
|
|
3724
|
+
preTurnStatus = await readRuntimeStatus(runtime, sessionHandle);
|
|
3725
|
+
// The prepare phase (prompt build + usage snapshot) finished; the turn
|
|
3726
|
+
// phase starts next.
|
|
3727
|
+
await emitPhase("prepare_turn", preparePhaseStart, "ok");
|
|
3728
|
+
turnPhaseStart = now();
|
|
3729
|
+
};
|
|
3730
|
+
const stepTurnStart = (signal, startTimeoutMs) => {
|
|
3731
|
+
ctx.signal?.throwIfAborted();
|
|
3732
|
+
const turn = runtime.startTurn({
|
|
3733
|
+
handle: sessionHandle,
|
|
3734
|
+
text: runPrompt,
|
|
3735
|
+
mode: "prompt",
|
|
3736
|
+
requestId: ctx.runId,
|
|
3737
|
+
timeoutMs: startTimeoutMs,
|
|
3738
|
+
signal,
|
|
3739
|
+
// The callback belongs to this turn, including when a runtime is reused.
|
|
3740
|
+
// Raw provider text must never enter the result or the run log.
|
|
3741
|
+
...(deps.classifyTerminalSessionFailure
|
|
3742
|
+
? {
|
|
3743
|
+
onTerminalSessionFailure: (failure) => {
|
|
3744
|
+
terminalFailureClassification = deps.classifyTerminalSessionFailure(failure, new Date(now()));
|
|
3745
|
+
},
|
|
3746
|
+
}
|
|
3747
|
+
: {}),
|
|
3748
|
+
});
|
|
3749
|
+
activeTurn = turn;
|
|
3750
|
+
// A latched sandbox duplex-channel loss otherwise has no way to reach
|
|
3751
|
+
// this turn: the bridge only exposes a pull read, and the engine
|
|
3752
|
+
// pulls it at the terminal-finalization boundary, which runs only
|
|
3753
|
+
// after the turn already returned a terminal result. A channel that
|
|
3754
|
+
// dies mid-turn then leaves the turn with no terminal result to
|
|
3755
|
+
// return, so it waits for the wall-clock adapter execution timeout
|
|
3756
|
+
// instead of failing fast. Cancel the turn the moment a terminal loss
|
|
3757
|
+
// latches — whether it latches from here on, or already latched
|
|
3758
|
+
// before this turn started — so the turn returns a terminal result
|
|
3759
|
+
// right away. `turnFinalize` reads the same latch and builds the
|
|
3760
|
+
// failure from the typed loss reason alone.
|
|
3761
|
+
const bridge = prepared.paperclipBridge;
|
|
3762
|
+
if (bridge?.onLoss) {
|
|
3763
|
+
const cancelForLoss = (reason) => {
|
|
3764
|
+
void turn.cancel({ reason: `paperclip sandbox duplex channel lost (${reason})` }).catch(() => { });
|
|
3765
|
+
// `cancel()` only asks the agent to end the turn; it does not end
|
|
3766
|
+
// the turn by itself. Start the fail-fast deadline the moment the
|
|
3767
|
+
// loss latches, so the run does not wait past this bound for an
|
|
3768
|
+
// agent that stopped answering.
|
|
3769
|
+
if (!lossDeadlineTimer && !lossDeadlineTripped) {
|
|
3770
|
+
lossDeadlineTimer = setTimeout(() => {
|
|
3771
|
+
lossDeadlineTripped = true;
|
|
3772
|
+
resolveLossDeadline?.();
|
|
3773
|
+
}, duplexLossCancelDeadlineMs);
|
|
3774
|
+
}
|
|
3775
|
+
};
|
|
3776
|
+
removeLossListener = bridge.onLoss(cancelForLoss);
|
|
3777
|
+
const alreadyLatched = bridge.readRunDisposition?.();
|
|
3778
|
+
if (alreadyLatched?.failed)
|
|
3779
|
+
cancelForLoss(alreadyLatched.lossReason ?? "other");
|
|
3780
|
+
}
|
|
3781
|
+
// ACP can resolve the turn before its provider exits. Keep the Stop
|
|
3782
|
+
// deadline armed through settlement, including provider cleanup.
|
|
3783
|
+
const armStopDeadline = () => {
|
|
3784
|
+
stopTimer = setTimeout(() => {
|
|
3785
|
+
forcedStop = true;
|
|
3786
|
+
const providerPid = processIdentitySink.latest?.pid;
|
|
3787
|
+
if (providerPid && !prepared.processSessionBridge) {
|
|
3788
|
+
// Escalate the owned local process directly. A second ACP close
|
|
3789
|
+
// can wait behind the same hung cleanup (or open a new client).
|
|
3790
|
+
killCapturedLocalProcess(processIdentitySink.localProcess);
|
|
3791
|
+
}
|
|
3792
|
+
else {
|
|
3793
|
+
void runtime.close({ handle: sessionHandle, reason: "operator stop deadline", discardPersistentState: true })
|
|
3794
|
+
.catch(() => { });
|
|
3795
|
+
}
|
|
3796
|
+
}, Math.max(1, asNumber(ctx.config.graceSec, 15)) * 1000);
|
|
3797
|
+
};
|
|
3798
|
+
ctx.signal?.addEventListener("abort", armStopDeadline, { once: true });
|
|
3799
|
+
removeStopListener = () => ctx.signal?.removeEventListener("abort", armStopDeadline);
|
|
3800
|
+
if (ctx.signal?.aborted)
|
|
3801
|
+
armStopDeadline();
|
|
3802
|
+
return {
|
|
3803
|
+
cancel: async (reason) => {
|
|
3804
|
+
await turn.cancel({ reason });
|
|
3805
|
+
},
|
|
3806
|
+
};
|
|
3807
|
+
};
|
|
3808
|
+
// The host-built terminal `stepEventRelay` hands to `turnFinalize` once
|
|
3809
|
+
// the fail-fast deadline fires with no agent-supplied terminal. Its
|
|
3810
|
+
// `status` mirrors the shape a real cooperative cancel already
|
|
3811
|
+
// produces, so `turnFinalize` needs no change: it reads the latched
|
|
3812
|
+
// loss disposition, not this `stopReason`, to build the reported
|
|
3813
|
+
// failure and its message.
|
|
3814
|
+
const LOSS_DEADLINE_TERMINAL = {
|
|
3815
|
+
status: "cancelled",
|
|
3816
|
+
stopReason: "paperclip_duplex_loss_deadline",
|
|
3817
|
+
};
|
|
3818
|
+
const stepEventRelay = async () => {
|
|
3819
|
+
const turn = activeTurn;
|
|
3820
|
+
const toolTitles = new Map();
|
|
3821
|
+
const drainEvents = (async () => {
|
|
3822
|
+
for await (const event of turn.events) {
|
|
3823
|
+
// ACPX currently flattens client-side filesystem/terminal receipts
|
|
3824
|
+
// into status text. They cannot establish complete action outcomes.
|
|
3825
|
+
if (event.type === "status" && /^(fs|terminal)\//.test(event.text))
|
|
3826
|
+
incompleteToolInventory = true;
|
|
3827
|
+
if (event.type === "tool_call") {
|
|
3828
|
+
if (!event.toolCallId)
|
|
3829
|
+
incompleteToolInventory = true;
|
|
3830
|
+
else {
|
|
3831
|
+
const previous = interruptionTools.get(event.toolCallId);
|
|
3832
|
+
interruptionTools.set(event.toolCallId, {
|
|
3833
|
+
kind: event.kind ?? previous?.kind,
|
|
3834
|
+
status: event.status ?? previous?.status,
|
|
3835
|
+
});
|
|
3836
|
+
}
|
|
3837
|
+
}
|
|
3838
|
+
if (event.type === "text_delta" && event.stream !== "thought") {
|
|
3839
|
+
currentOutputChunk.push(event.text);
|
|
3840
|
+
}
|
|
3841
|
+
else if (event.type === "tool_call" && event.tag !== "tool_call_update") {
|
|
3842
|
+
// ACP makes tool-call status optional. The normalized event tag is
|
|
3843
|
+
// the reliable boundary between an initial call and its updates,
|
|
3844
|
+
// so a statusless initial call must still end the preceding output
|
|
3845
|
+
// segment while updates must not create extra boundaries.
|
|
3846
|
+
flushOutputSegment();
|
|
3847
|
+
}
|
|
3848
|
+
if (event.type === "status" && event.tag === "usage_update") {
|
|
3849
|
+
eventBreakdown = event.breakdown ?? eventBreakdown;
|
|
3850
|
+
eventCostUsd = usdCostAmount(event.cost) ?? eventCostUsd;
|
|
3851
|
+
}
|
|
3852
|
+
await emitRuntimeEvent(ctx, event, toolTitles, prepared.coalescePlaceholderToolUpdates);
|
|
3853
|
+
}
|
|
3854
|
+
})();
|
|
3855
|
+
// A latched loss already asked the agent to cancel (above, in
|
|
3856
|
+
// `cancelForLoss`); that request settles neither `turn.events` nor
|
|
3857
|
+
// `turn.result` by itself. Race the event drain against the fail-fast
|
|
3858
|
+
// deadline so a silent agent cannot hold this wait open.
|
|
3859
|
+
const eventsEnded = await Promise.race([
|
|
3860
|
+
drainEvents.then(() => true),
|
|
3861
|
+
lossDeadline.then(() => false),
|
|
3862
|
+
]);
|
|
3863
|
+
if (!eventsEnded) {
|
|
3864
|
+
// The deadline won: stop waiting on the agent. `closeStream` ends
|
|
3865
|
+
// the event drain locally, with no agent cooperation required. Await
|
|
3866
|
+
// both the close call and the drain it unblocks before this step
|
|
3867
|
+
// returns, so no late runtime event can still mutate shared state
|
|
3868
|
+
// (output segments, tool inventory) after finalization reads it.
|
|
3869
|
+
await turn.closeStream({ reason: "paperclip duplex loss cancel deadline" }).catch(() => { });
|
|
3870
|
+
await drainEvents.catch(() => { });
|
|
3871
|
+
flushOutputSegment();
|
|
3872
|
+
return LOSS_DEADLINE_TERMINAL;
|
|
3873
|
+
}
|
|
3874
|
+
flushOutputSegment();
|
|
3875
|
+
// `turn.result` settles only when the agent's provider process
|
|
3876
|
+
// returns or rejects; a latched loss that armed the deadline after
|
|
3877
|
+
// the event drain already ended must still bound this wait.
|
|
3878
|
+
return await Promise.race([turn.result, lossDeadline.then(() => LOSS_DEADLINE_TERMINAL)]);
|
|
3879
|
+
};
|
|
3880
|
+
const stepTurnFinalize = async (input) => {
|
|
3881
|
+
if (input.kind === "terminal") {
|
|
3882
|
+
const terminal = input.terminal;
|
|
3883
|
+
const timedOut = input.timedOut;
|
|
3884
|
+
// Read the sandbox duplex control-channel disposition at the ACP
|
|
3885
|
+
// terminal-finalization boundary, before the bridge teardown, on every
|
|
3886
|
+
// terminal outcome. A control channel that died before this point
|
|
3887
|
+
// latches a failure with a typed loss reason; a healthy channel or a
|
|
3888
|
+
// normal-teardown loss reports a success. The read and the mark of the
|
|
3889
|
+
// host-observed orderly completion happen atomically in one broker
|
|
3890
|
+
// step, with no `await` between them, so a teardown loss cannot slip
|
|
3891
|
+
// in between. This stops a later teardown `channel_exit` from latching
|
|
3892
|
+
// a false loss. The mark no-ops once a loss already latched, so a real
|
|
3893
|
+
// mid-turn loss still fails the run — including a loss that arrived
|
|
3894
|
+
// through the in-flight-turn cancel this seam issues, which surfaces
|
|
3895
|
+
// here as a `cancelled` (not `completed`) terminal, not just through a
|
|
3896
|
+
// nominally completed terminal. The file bridge path never sets this
|
|
3897
|
+
// method, so the optional call no-ops there.
|
|
3898
|
+
let duplexLossReason = null;
|
|
3899
|
+
const disposition = prepared.paperclipBridge?.settleRunDisposition?.() ?? null;
|
|
3900
|
+
if (disposition?.failed) {
|
|
3901
|
+
duplexLossReason = disposition.lossReason ?? "other";
|
|
3902
|
+
}
|
|
3903
|
+
// A terminal that reports "completed" but whose duplex control channel
|
|
3904
|
+
// died before the completion is not a success. The seam fails it closed.
|
|
3905
|
+
const channelLost = duplexLossReason !== null;
|
|
3906
|
+
// Build the boundary failure message only from the closed loss-reason
|
|
3907
|
+
// enum, so no raw provider text rides the message.
|
|
3908
|
+
const channelLostMessage = duplexLossReason
|
|
3909
|
+
? `The sandbox duplex control channel was lost (${duplexLossReason}) before the run completed.`
|
|
3910
|
+
: null;
|
|
3911
|
+
// A completed, non-timed-out turn whose channel stayed live is the one
|
|
3912
|
+
// success path. Every other outcome — a failed, cancelled, or timed-out
|
|
3913
|
+
// terminal, or a completed terminal with a lost channel — is a failure.
|
|
3914
|
+
const turnSucceeded = terminal.status === "completed" && !timedOut && !channelLost;
|
|
3915
|
+
// Read usage before the settlement can discard runtime state.
|
|
3916
|
+
const postTurnStatus = await readRuntimeStatus(runtime, sessionHandle);
|
|
3917
|
+
const turnUsage = summarizeAcpxTurnUsage({
|
|
3918
|
+
preStatus: preTurnStatus,
|
|
3919
|
+
postStatus: postTurnStatus,
|
|
3920
|
+
eventBreakdown,
|
|
3921
|
+
eventCostUsd,
|
|
3922
|
+
});
|
|
3923
|
+
const failedTurn = terminal.status === "failed" || terminal.status === "cancelled" || timedOut;
|
|
3924
|
+
// ACPX can defer session/load until runTurn. Forget an unavailable
|
|
3925
|
+
// session so the next bounded turn receives the full task conversation.
|
|
3926
|
+
const sessionUnavailable = terminal.status === "failed" &&
|
|
3927
|
+
terminal.error.detailCode === "SESSION_RESUME_REQUIRED";
|
|
3928
|
+
if (sessionUnavailable)
|
|
3929
|
+
clearSession = true;
|
|
3930
|
+
// Saving a conversation is independent from certifying tool outcomes.
|
|
3931
|
+
// Its next turn receives history, not a replay of pending tool calls.
|
|
3932
|
+
preserveInterruptedSession = ctx.signal?.aborted === true && !forcedStop && !timedOut && !channelLost
|
|
3933
|
+
&& (terminal.status === "cancelled" || terminal.status === "completed")
|
|
3934
|
+
&& prepared.mode === "persistent" && !prepared.processSessionBridge
|
|
3935
|
+
&& Boolean(sessionHandle.backendSessionId);
|
|
3936
|
+
safeInterruptedSession = preserveInterruptedSession
|
|
3937
|
+
&& !incompleteToolInventory
|
|
3938
|
+
&& [...interruptionTools.values()].every((tool) => tool.kind === "read" && tool.status === "completed");
|
|
3939
|
+
// Record how the settlement `endSession` step closes the runtime for this
|
|
3940
|
+
// outcome. A clean persistent host turn is save-eligible, but the
|
|
3941
|
+
// Amendment B credential gate fails on the host lane (the run API key is
|
|
3942
|
+
// never revoked), so the reuse decision discards and `endSession` closes it
|
|
3943
|
+
// (close-and-relaunch). The sandbox lane always closes the runtime; its
|
|
3944
|
+
// staged files are the reuse. A matching warm entry closes through the warm
|
|
3945
|
+
// store (which also flushes its stderr); otherwise the runtime closes
|
|
3946
|
+
// directly. The settlement then stops the bridges, saves or discards the
|
|
3947
|
+
// staged runtime, releases the staging lease, and flushes the child stderr.
|
|
3948
|
+
runtimeSettlement = {
|
|
3949
|
+
mode: "warm_or_close",
|
|
3950
|
+
handle: sessionHandle,
|
|
3951
|
+
reason: timedOut
|
|
3952
|
+
? "paperclip timeout cleanup"
|
|
3953
|
+
: channelLost
|
|
3954
|
+
? "paperclip duplex channel lost cleanup"
|
|
3955
|
+
: failedTurn
|
|
3956
|
+
? `paperclip turn ${terminal.status}`
|
|
3957
|
+
: "paperclip completed turn cleanup",
|
|
3958
|
+
discardPersistentState: sessionUnavailable || (terminal.status === "cancelled" && !preserveInterruptedSession) || timedOut || channelLost,
|
|
3959
|
+
dropWarmEntry: false,
|
|
3960
|
+
recordCloseError: false,
|
|
3961
|
+
cancelTurnReason: null,
|
|
3962
|
+
skipRemoteClose: channelLost,
|
|
3963
|
+
};
|
|
3964
|
+
const errorMessage = timedOut
|
|
3965
|
+
? formatAdapterExecutionTimeoutErrorMessage(prepared.timeoutResolution)
|
|
3966
|
+
: channelLost
|
|
3967
|
+
? channelLostMessage
|
|
3968
|
+
: resultErrorMessage(terminal);
|
|
3969
|
+
const terminalStopReason = terminal.status === "failed" ? terminal.error.message : terminal.stopReason;
|
|
3970
|
+
const classifiedFailure = !timedOut && !channelLost && terminal.status === "failed"
|
|
3971
|
+
? terminalFailureClassification
|
|
3972
|
+
: null;
|
|
3973
|
+
await emitAcpxLog(ctx, {
|
|
3974
|
+
type: turnSucceeded ? "acpx.result" : "acpx.error",
|
|
3975
|
+
summary: channelLost ? "duplex_channel_lost" : terminal.status,
|
|
3976
|
+
stopReason: terminalStopReason,
|
|
3977
|
+
message: errorMessage,
|
|
3978
|
+
});
|
|
3979
|
+
// The one clean-completion path clears the run failure flag; every other
|
|
3980
|
+
// path keeps it set, so the run root span closes with error status. A
|
|
3981
|
+
// completed terminal with a lost duplex channel keeps the flag set.
|
|
3982
|
+
runFailed = turnSucceeded ? false : true;
|
|
3983
|
+
capturedResult = {
|
|
3984
|
+
exitCode: turnSucceeded ? 0 : 1,
|
|
3985
|
+
signal: timedOut ? "SIGTERM" : null,
|
|
3986
|
+
timedOut,
|
|
3987
|
+
errorMessage,
|
|
3988
|
+
errorCode: timedOut
|
|
3989
|
+
? "acpx_timeout"
|
|
3990
|
+
: channelLost
|
|
3991
|
+
? DUPLEX_CHANNEL_LOST_ERROR_CODE
|
|
3992
|
+
: terminal.status === "failed"
|
|
3993
|
+
? classifiedFailure?.errorCode ?? "acpx_turn_failed"
|
|
3994
|
+
: null,
|
|
3995
|
+
...(classifiedFailure?.errorFamily ? { errorFamily: classifiedFailure.errorFamily } : {}),
|
|
3996
|
+
...(classifiedFailure?.retryNotBefore ? { retryNotBefore: classifiedFailure.retryNotBefore } : {}),
|
|
3997
|
+
sessionId: sessionHandle.backendSessionId ?? sessionHandle.runtimeSessionName,
|
|
3998
|
+
sessionParams: buildSessionParams({ prepared, handle: sessionHandle }),
|
|
3999
|
+
sessionDisplayId: sessionHandle.agentSessionId ?? sessionHandle.backendSessionId ?? sessionHandle.runtimeSessionName,
|
|
4000
|
+
...billingFields,
|
|
4001
|
+
...referencedProjectStagingFailuresField,
|
|
4002
|
+
model: prepared.requestedModel || null,
|
|
4003
|
+
...(turnUsage.usage ? { usage: turnUsage.usage, usageBasis: "per_run" } : {}),
|
|
4004
|
+
costUsd: turnUsage.costUsd,
|
|
4005
|
+
resultJson: {
|
|
4006
|
+
status: channelLost ? "failed" : terminal.status,
|
|
4007
|
+
...(classifiedFailure?.errorFamily ? { errorFamily: classifiedFailure.errorFamily } : {}),
|
|
4008
|
+
...(classifiedFailure?.retryNotBefore
|
|
4009
|
+
? {
|
|
4010
|
+
retryNotBefore: classifiedFailure.retryNotBefore,
|
|
4011
|
+
...(classifiedFailure.errorFamily === "provider_quota"
|
|
4012
|
+
? { providerQuotaRetryNotBefore: classifiedFailure.retryNotBefore }
|
|
4013
|
+
: {}),
|
|
4014
|
+
}
|
|
4015
|
+
: {}),
|
|
4016
|
+
stopReason: terminalStopReason,
|
|
4017
|
+
permissionMode: prepared.permissionMode,
|
|
4018
|
+
mode: prepared.mode,
|
|
4019
|
+
requestedModel: prepared.requestedModel || null,
|
|
4020
|
+
requestedThinkingEffort: prepared.requestedThinkingEffort || null,
|
|
4021
|
+
fastMode: prepared.fastMode,
|
|
4022
|
+
...(turnUsage.usageDetail ? { usage: turnUsage.usageDetail } : {}),
|
|
4023
|
+
...(turnUsage.cumulativeCostUsd != null
|
|
4024
|
+
? { cumulativeCostUsd: turnUsage.cumulativeCostUsd }
|
|
4025
|
+
: {}),
|
|
4026
|
+
},
|
|
4027
|
+
summary: buildAcpxRunSummary({
|
|
4028
|
+
outputSegments,
|
|
4029
|
+
fallback: terminalStopReason || terminal.status,
|
|
4030
|
+
}),
|
|
4031
|
+
clearSession,
|
|
4032
|
+
};
|
|
4033
|
+
// The turn phase finished. A completed, non-timed-out turn with a live
|
|
4034
|
+
// duplex channel is `ok`; every other terminal outcome is `failed`.
|
|
4035
|
+
await emitPhase("turn", turnPhaseStart, turnSucceeded ? "ok" : "failed");
|
|
4036
|
+
// Return the typed turn completion so the coordinator settles for the right
|
|
4037
|
+
// cause. The completion carries no live resources; the settlement claims the
|
|
4038
|
+
// ledger and owns the release. A clean completed turn carries no cause, so
|
|
4039
|
+
// the reuse decision can permit a save; a timed-out, failed, or cancelled
|
|
4040
|
+
// turn carries its cause, which forbids the save.
|
|
4041
|
+
if (timedOut) {
|
|
4042
|
+
return {
|
|
4043
|
+
kind: "timed_out",
|
|
4044
|
+
cause: { kind: "turn_timed_out", timeoutSec: prepared.timeoutSec },
|
|
4045
|
+
resources: emptyConsumed,
|
|
4046
|
+
};
|
|
4047
|
+
}
|
|
4048
|
+
if (terminal.status === "cancelled") {
|
|
4049
|
+
return {
|
|
4050
|
+
kind: "cancelled",
|
|
4051
|
+
cause: { kind: "turn_cancelled", reason: terminal.stopReason ?? "cancelled" },
|
|
4052
|
+
resources: emptyConsumed,
|
|
4053
|
+
};
|
|
4054
|
+
}
|
|
4055
|
+
// A duplex control-channel loss outranks a provider-reported failure or
|
|
4056
|
+
// completion: the loss reason explains why the provider terminal reads
|
|
4057
|
+
// the way it does, not the other way round. This also covers a
|
|
4058
|
+
// "completed" terminal whose channel died mid-turn. The message carries
|
|
4059
|
+
// only the typed loss reason, so no raw provider text rides the cause,
|
|
4060
|
+
// even when the provider terminal itself reports `failed`.
|
|
4061
|
+
if (channelLost) {
|
|
4062
|
+
return {
|
|
4063
|
+
kind: "failed",
|
|
4064
|
+
cause: {
|
|
4065
|
+
kind: "turn_failed",
|
|
4066
|
+
error: new Error(channelLostMessage ?? "The sandbox duplex control channel was lost."),
|
|
4067
|
+
},
|
|
4068
|
+
resources: emptyConsumed,
|
|
4069
|
+
};
|
|
4070
|
+
}
|
|
4071
|
+
if (terminal.status === "failed") {
|
|
4072
|
+
return {
|
|
4073
|
+
kind: "failed",
|
|
4074
|
+
cause: {
|
|
4075
|
+
kind: "turn_failed",
|
|
4076
|
+
error: terminal.error instanceof Error ? terminal.error : new Error(String(terminal.error)),
|
|
4077
|
+
},
|
|
4078
|
+
resources: emptyConsumed,
|
|
4079
|
+
};
|
|
4080
|
+
}
|
|
4081
|
+
return { kind: "finalized" };
|
|
4082
|
+
}
|
|
4083
|
+
const err = input.error;
|
|
4084
|
+
const timedOut = input.timedOut;
|
|
4085
|
+
// The failure phase comes from the sequence: a failure before the turn
|
|
4086
|
+
// started is `prepare_turn`; a failure after it is `turn`. The teardown is
|
|
4087
|
+
// the same; only the reported phase differs.
|
|
4088
|
+
const phase = input.phase;
|
|
4089
|
+
// Emit the failed phase timing for the phase the sequence reported.
|
|
4090
|
+
if (phase === "prepare_turn") {
|
|
4091
|
+
await emitPhase("prepare_turn", preparePhaseStart, "failed");
|
|
4092
|
+
}
|
|
4093
|
+
else {
|
|
4094
|
+
await emitPhase("turn", turnPhaseStart, "failed");
|
|
4095
|
+
}
|
|
4096
|
+
const messageOverride = timedOut
|
|
4097
|
+
? formatAdapterExecutionTimeoutErrorMessage(prepared.timeoutResolution)
|
|
4098
|
+
: undefined;
|
|
4099
|
+
const preEmitMessage = messageOverride ?? (err instanceof Error ? err.message : String(err));
|
|
4100
|
+
// Record a direct close for the settlement `endSession` step: cancel the
|
|
4101
|
+
// running turn first (cancel-before-close), then close the runtime and drop
|
|
4102
|
+
// a matching warm entry. The settlement discards the staged runtime, stops
|
|
4103
|
+
// the bridges, releases the staging lease, and flushes the child stderr.
|
|
4104
|
+
runtimeSettlement = {
|
|
4105
|
+
mode: "direct",
|
|
4106
|
+
handle: sessionHandle,
|
|
4107
|
+
reason: timedOut ? "paperclip timeout cleanup" : "paperclip error cleanup",
|
|
4108
|
+
discardPersistentState: timedOut,
|
|
4109
|
+
dropWarmEntry: true,
|
|
4110
|
+
recordCloseError: true,
|
|
4111
|
+
cancelTurnReason: preEmitMessage,
|
|
4112
|
+
skipRemoteClose: false,
|
|
4113
|
+
};
|
|
4114
|
+
// Emit the failure best-effort. `turnFinalize` must not reject, so a
|
|
4115
|
+
// failing emission never propagates: the settlement owns the teardown, and
|
|
4116
|
+
// it runs only after this returns a completion. On an emission failure the
|
|
4117
|
+
// run records a degraded result from the pre-emit message.
|
|
4118
|
+
let emitted = null;
|
|
4119
|
+
try {
|
|
4120
|
+
emitted = await emitAcpxFailure({ ctx, prepared, err, phase, messageOverride });
|
|
4121
|
+
}
|
|
4122
|
+
catch {
|
|
4123
|
+
emitted = null;
|
|
4124
|
+
}
|
|
4125
|
+
const message = emitted?.message ?? preEmitMessage;
|
|
4126
|
+
capturedResult = {
|
|
4127
|
+
exitCode: 1,
|
|
4128
|
+
signal: timedOut ? "SIGTERM" : null,
|
|
4129
|
+
timedOut,
|
|
4130
|
+
errorMessage: message,
|
|
4131
|
+
errorCode: timedOut ? "acpx_timeout" : (emitted?.classified.errorCode ?? null),
|
|
4132
|
+
errorMeta: emitted?.classified.errorMeta,
|
|
4133
|
+
...billingFields,
|
|
4134
|
+
...referencedProjectStagingFailuresField,
|
|
4135
|
+
model: prepared.requestedModel || null,
|
|
4136
|
+
clearSession: clearSession || timedOut,
|
|
4137
|
+
resultJson: { phase },
|
|
4138
|
+
summary: message,
|
|
4139
|
+
};
|
|
4140
|
+
// Return a typed failed completion so the coordinator settles for a cause
|
|
4141
|
+
// that forbids the save. The reported phase lives on the recorded result.
|
|
4142
|
+
return {
|
|
4143
|
+
kind: "failed",
|
|
4144
|
+
cause: { kind: "turn_failed", error: err instanceof Error ? err : new Error(String(err)) },
|
|
4145
|
+
resources: emptyConsumed,
|
|
4146
|
+
};
|
|
4147
|
+
};
|
|
4148
|
+
try {
|
|
4149
|
+
return await runTurnSequence({
|
|
4150
|
+
signal: ctx.signal,
|
|
4151
|
+
timeoutMs: prepared.timeoutSec > 0 ? prepared.timeoutSec * 1000 : undefined,
|
|
4152
|
+
timeoutMessage: formatAdapterExecutionTimeoutErrorMessage(prepared.timeoutResolution),
|
|
4153
|
+
promptBuild: stepPromptBuild,
|
|
4154
|
+
preTurnUsage: stepPreTurnUsage,
|
|
4155
|
+
turnStart: stepTurnStart,
|
|
4156
|
+
eventRelay: stepEventRelay,
|
|
4157
|
+
turnFinalize: stepTurnFinalize,
|
|
4158
|
+
});
|
|
4159
|
+
}
|
|
4160
|
+
finally {
|
|
4161
|
+
// End the agent turn span exactly once, on every return and on a throw.
|
|
4162
|
+
// `runFailed` is `false` only on a completed, non-timed-out turn, so the
|
|
4163
|
+
// span status is correct for success, error, and timeout.
|
|
4164
|
+
turnSpan.end(runFailed);
|
|
4165
|
+
// Reset the current-run holder to the `task.run` token after the turn.
|
|
4166
|
+
// The run stays live here, so the holder is never `undefined`. A detached
|
|
4167
|
+
// exec after the turn parents to `task.run`.
|
|
4168
|
+
currentRunParentContext = runRootSpan.parentContext;
|
|
4169
|
+
}
|
|
4170
|
+
};
|
|
4171
|
+
// A live run-scoped credential marker. The host-lane reuse candidate carries
|
|
4172
|
+
// it, so the Amendment B credential gate blocks the host warm save: the
|
|
4173
|
+
// run-minted API key is never revoked, so it stays valid and forces a
|
|
4174
|
+
// close-and-relaunch. It is a non-secret marker; the settlement reads only its
|
|
4175
|
+
// presence, and no report or reuse payload carries it.
|
|
4176
|
+
const LIVE_RUN_SCOPED_API_KEY = "paperclip-run-scoped-api-key";
|
|
4177
|
+
// Record the final per-resource disposition report where a test can observe
|
|
4178
|
+
// it. The engine never reads it back.
|
|
4179
|
+
const recordDispositionReport = (report) => {
|
|
4180
|
+
deps.onSettlementDisposition?.(report);
|
|
4181
|
+
};
|
|
4182
|
+
// The settlement sequence is the one live cleanup owner for every settled path
|
|
4183
|
+
// (Phase 22). It claims the ledger once, makes the pure reuse decision, then
|
|
4184
|
+
// runs the ordered steps: `endSession`, `settleReuse`, `stopTransport`,
|
|
4185
|
+
// `syncBack`, and `releaseStagingLease` (in a finally). Each step reads the run
|
|
4186
|
+
// state through the shared closure locals and no-ops on an empty ledger slot.
|
|
4187
|
+
// The Phase 3 error policy governs every step. The coordinator alone owns the
|
|
4188
|
+
// startup rollback.
|
|
4189
|
+
const settlementSteps = {
|
|
4190
|
+
// Derive the reuse candidate. Only a clean turn (no cause) can save. The
|
|
4191
|
+
// host lane would save the live runtime, but the Amendment B credential gate
|
|
4192
|
+
// fails there: the run-minted API key is never revoked, so a live run-scoped
|
|
4193
|
+
// credential stays valid and the decision discards (close-and-relaunch). The
|
|
4194
|
+
// sandbox lane saves its staged files, which carry no credential.
|
|
4195
|
+
reuseCandidate: (slots, cause) => {
|
|
4196
|
+
const clean = cause === null;
|
|
4197
|
+
if (prepared.processSessionBridge) {
|
|
4198
|
+
// Sandbox lane: the staged files are the reuse. Save only after a clean
|
|
4199
|
+
// turn and only when the run holds a staged runtime.
|
|
4200
|
+
if (!clean || !slots.has("staged_runtime"))
|
|
4201
|
+
return null;
|
|
4202
|
+
return { kind: "sandbox", causePermitsSave: true, liveRunScopedCredentials: [] };
|
|
4203
|
+
}
|
|
4204
|
+
// Host lane: save a clean persistent warm-eligible turn, but carry the
|
|
4205
|
+
// live run-scoped credential so the gate blocks the transfer.
|
|
4206
|
+
if (!slots.has("acp_runtime"))
|
|
4207
|
+
return null;
|
|
4208
|
+
const permits = clean && prepared.mode === "persistent" && warmIdleMs > 0;
|
|
4209
|
+
if (!permits)
|
|
4210
|
+
return null;
|
|
4211
|
+
return { kind: "host", causePermitsSave: true, liveRunScopedCredentials: [LIVE_RUN_SCOPED_API_KEY] };
|
|
4212
|
+
},
|
|
4213
|
+
// Close every runtime the decision did not transfer, and drop the warm entry
|
|
4214
|
+
// on close.
|
|
4215
|
+
endSession: (slots, decision) => timedPhase("end_session", async () => {
|
|
4216
|
+
// The fence is the one idempotent owner of the abandoned promise's
|
|
4217
|
+
// eventual handle. Seal it first, before every other decision
|
|
4218
|
+
// below, including the empty-slot and save early returns: a late
|
|
4219
|
+
// handle that already arrived must never race this step.
|
|
4220
|
+
const lateHandle = handshakeFence.seal();
|
|
4221
|
+
if (!slots.has("acp_runtime"))
|
|
4222
|
+
return;
|
|
4223
|
+
if (decision.kind === "save" && decision.savedId === "acp_runtime")
|
|
4224
|
+
return;
|
|
4225
|
+
const baseSettlement = runtimeSettlement ?? {
|
|
4226
|
+
mode: "direct",
|
|
4227
|
+
handle: syntheticCloseHandle(),
|
|
4228
|
+
reason: "paperclip cleanup",
|
|
4229
|
+
discardPersistentState: false,
|
|
4230
|
+
dropWarmEntry: false,
|
|
4231
|
+
recordCloseError: true,
|
|
4232
|
+
cancelTurnReason: null,
|
|
4233
|
+
skipRemoteClose: false,
|
|
4234
|
+
};
|
|
4235
|
+
// A late handle that arrived before this seal replaces the synthetic
|
|
4236
|
+
// placeholder, so the one close call below uses the real identity
|
|
4237
|
+
// `ensureSession` eventually returned.
|
|
4238
|
+
const settlement = lateHandle
|
|
4239
|
+
? { ...baseSettlement, handle: lateHandle }
|
|
4240
|
+
: baseSettlement;
|
|
4241
|
+
// Cancel a running turn before the close (the turn-error path).
|
|
4242
|
+
if (settlement.cancelTurnReason && activeTurn) {
|
|
4243
|
+
await activeTurn.cancel({ reason: settlement.cancelTurnReason }).catch(() => { });
|
|
4244
|
+
}
|
|
4245
|
+
const existing = warmHandles.get(prepared.sessionKey);
|
|
4246
|
+
// Re-read the duplex control-channel disposition here, at the
|
|
4247
|
+
// boundary that places the remote call. The settlement snapshot
|
|
4248
|
+
// above can predate a channel loss the bridge latches during the
|
|
4249
|
+
// awaited finalization work between the snapshot and this point, so
|
|
4250
|
+
// a stale `false` on the snapshot must not force a call onto a
|
|
4251
|
+
// channel that is dead by now. The read is non-mutating and only
|
|
4252
|
+
// adds a later-observed loss; it never clears the snapshot's `true`.
|
|
4253
|
+
const remoteChannelLost = settlement.skipRemoteClose || (prepared.paperclipBridge?.readRunDisposition?.().failed ?? false);
|
|
4254
|
+
// The control channel is already known lost, so no remote call can
|
|
4255
|
+
// reach the backend. Release the local bookkeeping only and place no
|
|
4256
|
+
// `runtime.close(...)` call — that call has no deadline of its own
|
|
4257
|
+
// and would block on the dead channel.
|
|
4258
|
+
if (remoteChannelLost) {
|
|
4259
|
+
if (warmHandleMatches(existing, runtime, settlement.handle) && existing) {
|
|
4260
|
+
clearWarmHandleTimer(existing);
|
|
4261
|
+
warmHandles.delete(prepared.sessionKey);
|
|
4262
|
+
flushChildStderr(existing.childStderrState);
|
|
4263
|
+
}
|
|
4264
|
+
return;
|
|
4265
|
+
}
|
|
4266
|
+
// The handshake guard abandoned this `ensureSession` call and no late
|
|
4267
|
+
// handle has arrived yet. Do not close the synthetic placeholder now:
|
|
4268
|
+
// the real handle can still resolve later, and closing it too would
|
|
4269
|
+
// close the same session twice. `handshakeFence`'s own late-arrival
|
|
4270
|
+
// hook (armed at the `ensureSession` call site) closes it once,
|
|
4271
|
+
// whenever it shows up. A guarded call only ever runs on a cold
|
|
4272
|
+
// start, so there is no matching warm entry here to drop.
|
|
4273
|
+
if (handshakeAbandoned && !lateHandle)
|
|
4274
|
+
return;
|
|
4275
|
+
if (settlement.mode === "warm_or_close" &&
|
|
4276
|
+
warmHandleMatches(existing, runtime, settlement.handle) &&
|
|
4277
|
+
existing) {
|
|
4278
|
+
// A matching warm entry closes through the warm store, which also
|
|
4279
|
+
// clears its idle timer and flushes its child stderr.
|
|
4280
|
+
await closeWarmHandle({
|
|
4281
|
+
handles: warmHandles,
|
|
4282
|
+
key: prepared.sessionKey,
|
|
4283
|
+
entry: existing,
|
|
4284
|
+
reason: settlement.reason,
|
|
4285
|
+
discardPersistentState: settlement.discardPersistentState,
|
|
4286
|
+
});
|
|
4287
|
+
return;
|
|
4288
|
+
}
|
|
4289
|
+
const onCloseError = settlement.recordCloseError || ctx.signal?.aborted
|
|
4290
|
+
? (closeErr) => recordTeardownError("runtime-close", closeErr)
|
|
4291
|
+
: () => { };
|
|
4292
|
+
await runtime
|
|
4293
|
+
.close({
|
|
4294
|
+
handle: settlement.handle,
|
|
4295
|
+
reason: settlement.reason,
|
|
4296
|
+
discardPersistentState: settlement.discardPersistentState,
|
|
4297
|
+
})
|
|
4298
|
+
.then(() => { runtimeStopConfirmed = true; })
|
|
4299
|
+
.catch(onCloseError);
|
|
4300
|
+
if (settlement.dropWarmEntry && warmHandleMatches(existing, runtime, settlement.handle) && existing) {
|
|
4301
|
+
clearWarmHandleTimer(existing);
|
|
4302
|
+
warmHandles.delete(prepared.sessionKey);
|
|
4303
|
+
}
|
|
4304
|
+
}),
|
|
4305
|
+
// Perform the reuse decision. A save transfers the staged files to the site
|
|
4306
|
+
// store (which arms the idle policy); every other case discards the staged
|
|
4307
|
+
// runtime. The host warm save is closed in `endSession`.
|
|
4308
|
+
settleReuse: (slots, decision) => timedPhase("settle_reuse", async () => {
|
|
4309
|
+
if (!slots.has("staged_runtime"))
|
|
4310
|
+
return;
|
|
4311
|
+
if (decision.kind === "save" && decision.savedId === "staged_runtime") {
|
|
4312
|
+
saveStagedRuntimeAfterCleanTurn({ handles: stagedRuntimes, prepared, now: now() });
|
|
4313
|
+
return;
|
|
4314
|
+
}
|
|
4315
|
+
await discardStagedRuntime({ handles: stagedRuntimes, prepared });
|
|
4316
|
+
}),
|
|
4317
|
+
// Stop both bridges in one allSettled.
|
|
4318
|
+
stopTransport: () => timedPhase("stop_transport", async () => {
|
|
4319
|
+
await stopRunTransport(prepared);
|
|
4320
|
+
}),
|
|
4321
|
+
// The site sync-back (the managed-home copy-back). The run-parented span
|
|
4322
|
+
// runner wraps the restore in a `sandbox.syncBack` span. The runner also
|
|
4323
|
+
// publishes the run parent into the runtime-parent store while the restore
|
|
4324
|
+
// runs, so the host mints a `traceparent` for the provider spans, and the
|
|
4325
|
+
// per-task restore spans parent to `sandbox.syncBack`.
|
|
4326
|
+
syncBack: () => timedPhase("sync_back", async () => {
|
|
4327
|
+
await runRuntimeSpan("sandbox.syncBack", async () => {
|
|
4328
|
+
const restoreOutcome = await syncBackManagedHome(prepared);
|
|
4329
|
+
if (!restoreOutcome.ok) {
|
|
4330
|
+
workspaceRestoreFailureField = { workspaceRestoreFailure: restoreOutcome.code };
|
|
4331
|
+
}
|
|
4332
|
+
});
|
|
4333
|
+
}),
|
|
4334
|
+
// The staging lease releases as the run's final act, AFTER the coordinator
|
|
4335
|
+
// reproduces the result, in the run root `finally` below. This step stays a
|
|
4336
|
+
// no-op in the live engine: a same-session second run must stay blocked on
|
|
4337
|
+
// the lease until this run fully returns, not merely until the settlement
|
|
4338
|
+
// sync-back finishes, so the release cannot move earlier into settlement.
|
|
4339
|
+
releaseStagingLease: () => { },
|
|
4340
|
+
recordError: async (step, error) => {
|
|
4341
|
+
await recordTeardownError(`settlement-${step}`, error);
|
|
4342
|
+
},
|
|
4343
|
+
};
|
|
4344
|
+
// Drive the attempt through the coordinator routing table. The startup step
|
|
4345
|
+
// returns `ready` or `settle` (or throws on a build or partial-bridge
|
|
4346
|
+
// failure); the coordinator runs the turn on the ready path, settles, then
|
|
4347
|
+
// reproduces the recorded result.
|
|
4348
|
+
const plan = {
|
|
4349
|
+
ledger: runResourceLedger,
|
|
4350
|
+
startup,
|
|
4351
|
+
runTurn,
|
|
4352
|
+
// The coordinator owns the startup rollback: `buildRuntime` runs its own
|
|
4353
|
+
// partial-bring-up rollback and throws, so no ledger resource is left to
|
|
4354
|
+
// release here.
|
|
4355
|
+
rollbackStartup: () => { },
|
|
4356
|
+
recordDisposition: recordDispositionReport,
|
|
4357
|
+
// The settlement sequence is the one live cleanup owner. It claims the
|
|
4358
|
+
// ledger, releases every settled resource, and returns the final
|
|
4359
|
+
// disposition report. The child stderr flushes after the sync-back on every
|
|
4360
|
+
// settled exit path (it stays null before the run reads the warm entry).
|
|
4361
|
+
settle: async (reason) => {
|
|
4362
|
+
const cause = reason.kind === "pre_turn"
|
|
4363
|
+
? reason.cause
|
|
4364
|
+
: reason.completion.kind === "finalized"
|
|
4365
|
+
? null
|
|
4366
|
+
: reason.completion.cause;
|
|
4367
|
+
const report = await settleAcpRun(runResourceLedger, cause, settlementSteps);
|
|
4368
|
+
recordDispositionReport(report);
|
|
4369
|
+
if (childStderrState)
|
|
4370
|
+
flushChildStderr(childStderrState);
|
|
4371
|
+
},
|
|
4372
|
+
reproduceResult: async () => {
|
|
4373
|
+
if (!capturedResult) {
|
|
4374
|
+
throw new Error("run coordinator reproduced a result before the run recorded one");
|
|
4375
|
+
}
|
|
4376
|
+
clearTimeout(stopTimer);
|
|
4377
|
+
let providerExited = false;
|
|
4378
|
+
const providerPid = processIdentitySink?.latest?.pid;
|
|
4379
|
+
if (ctx.signal?.aborted && providerPid && !prepared.processSessionBridge) {
|
|
4380
|
+
for (let attempt = 0; attempt < 40; attempt += 1) {
|
|
4381
|
+
providerExited = capturedProcessExited(processIdentitySink.localProcess);
|
|
4382
|
+
if (providerExited || !forcedStop)
|
|
4383
|
+
break;
|
|
4384
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
4385
|
+
}
|
|
4386
|
+
}
|
|
4387
|
+
// A forced process exit can reject ACP close after the OS has already
|
|
4388
|
+
// confirmed termination. Acknowledge Stop, but never grant replay.
|
|
4389
|
+
if (ctx.signal?.aborted && providerExited && (runtimeStopConfirmed || forcedStop)) {
|
|
4390
|
+
capturedResult = {
|
|
4391
|
+
...capturedResult,
|
|
4392
|
+
...(safeInterruptedSession && !forcedStop && !("workspaceRestoreFailure" in workspaceRestoreFailureField)
|
|
4393
|
+
? { executionRecovery: { kind: "interrupted", providerStopped: true, sessionPreserved: true, actionOutcomes: "settled" },
|
|
4394
|
+
sessionParams: { ...capturedResult.sessionParams, interruptedCheckpoint: true } }
|
|
4395
|
+
: {}),
|
|
4396
|
+
resultJson: { ...capturedResult.resultJson, executionCancellation: {
|
|
4397
|
+
state: "acknowledged", acknowledgedAt: new Date().toISOString(), forced: forcedStop,
|
|
4398
|
+
} },
|
|
4399
|
+
};
|
|
4400
|
+
}
|
|
4401
|
+
// The sync-back settlement step runs before this reproduces the result
|
|
4402
|
+
// (settlement precedes reproduction), so a failed workspace restore is
|
|
4403
|
+
// already recorded by the time we get here. Merge it into `resultJson`
|
|
4404
|
+
// only on a failure — a clean restore adds no new key.
|
|
4405
|
+
if (!("workspaceRestoreFailure" in workspaceRestoreFailureField)) {
|
|
4406
|
+
return capturedResult;
|
|
4407
|
+
}
|
|
4408
|
+
return {
|
|
4409
|
+
...capturedResult,
|
|
4410
|
+
resultJson: {
|
|
4411
|
+
...(capturedResult.resultJson ?? {}),
|
|
4412
|
+
...workspaceRestoreFailureField,
|
|
4413
|
+
},
|
|
4414
|
+
};
|
|
4415
|
+
},
|
|
4416
|
+
};
|
|
4417
|
+
return await runAttempt(plan);
|
|
4418
|
+
}
|
|
4419
|
+
finally {
|
|
4420
|
+
clearTimeout(stopTimer);
|
|
4421
|
+
removeStopListener?.();
|
|
4422
|
+
removeLossListener?.();
|
|
4423
|
+
clearTimeout(lossDeadlineTimer);
|
|
4424
|
+
// End the run root span exactly once, on every return and on a throw.
|
|
4425
|
+
runRootSpan.end(runFailed);
|
|
4426
|
+
// Release the per-session staging lease as the run's final act, AFTER the
|
|
4427
|
+
// coordinator settled every other resource and reproduced the result. It runs
|
|
4428
|
+
// last, in this `finally`, so a same-session second run stays blocked on the
|
|
4429
|
+
// lease until this run fully returns (not merely until the settlement
|
|
4430
|
+
// sync-back finishes) and an earlier teardown fault never strands the lease.
|
|
4431
|
+
// It stays null on a build failure (where `buildRuntime` released its own
|
|
4432
|
+
// partial lease) and on the host lane (no staging). The settlement stopped the
|
|
4433
|
+
// bridges and ran the sync-back before this point, so the ordering is
|
|
4434
|
+
// bridge-stop → sync-back → lease release.
|
|
4435
|
+
const leaseRelease = releaseStagingLease;
|
|
4436
|
+
if (leaseRelease) {
|
|
4437
|
+
const leaseStart = now();
|
|
4438
|
+
leaseRelease();
|
|
4439
|
+
// Fire-and-forget the phase timing: the release is the run's final act, so
|
|
4440
|
+
// the run must not await telemetry here. `emitRunPhaseTiming` swallows a
|
|
4441
|
+
// sink failure, so this never rejects.
|
|
4442
|
+
void emitRunPhaseTiming(ctx, "release_staging_lease", now() - leaseStart, "ok");
|
|
4443
|
+
}
|
|
4444
|
+
}
|
|
4445
|
+
};
|
|
4446
|
+
}
|
|
4447
|
+
export const execute = createAcpxEngineExecutor();
|
|
4448
|
+
//# sourceMappingURL=execute.js.map
|