fix(agents/orb): use standard SandboxAgent client for AgentOS sandbox
The in-process DockerSandboxClient could never satisfy AgentOS 0.2.10's sandbox serialization contract: AgentOS serializes sandbox mounts through getSerializableClientConfig, which reads client.baseUrl and passes it to the sidecar. An in-process object has no network endpoint. Replace the custom adapter with the supported boundary: SandboxAgent.start with sandbox-agent/docker, which starts a sandbox-agent server inside a Docker container with a dynamically mapped host port and returns a SandboxAgent client whose baseUrl both the main process and the sidecar subprocess reach over 127.0.0.1. Dispose calls destroySandbox so no containers are left behind. Remove 700+ lines of dead DockerSandboxClient infrastructure (PID tracking, log files, signal handling) now handled natively by the sandbox-agent server. Add a sqlite_file database descriptor to AgentOs.create so session storage works. Wrap command execution in sh -c for the SandboxAgent API. Apply chmod 600 to the real OpenCode config path containing the gateway key. The real proof with the main .env now passes all three stages: ORB_STAGE_DOCKER_OK, ORB_STAGE_AGENTOS_OK, ORB_STAGE_MODEL_TURN_OK, ending ORB_PROOF_PASSED with zero leftover containers. Update README to describe the real SandboxAgent + Docker topology and remove the misleading 'missing sidecar' limitation.
This commit is contained in:
74
bun.lock
74
bun.lock
@@ -161,14 +161,18 @@
|
|||||||
"@flue/runtime": "latest",
|
"@flue/runtime": "latest",
|
||||||
"@rivet-dev/agentos-core": "catalog:",
|
"@rivet-dev/agentos-core": "catalog:",
|
||||||
"convex": "catalog:",
|
"convex": "catalog:",
|
||||||
|
"dockerode": "^5.0.1",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
|
"get-port": "^7.2.0",
|
||||||
"hono": "4.12.31",
|
"hono": "4.12.31",
|
||||||
|
"sandbox-agent": "0.4.2",
|
||||||
"valibot": "^1.4.2",
|
"valibot": "^1.4.2",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@code/config": "workspace:*",
|
"@code/config": "workspace:*",
|
||||||
"@flue/cli": "latest",
|
"@flue/cli": "latest",
|
||||||
"@types/bun": "catalog:",
|
"@types/bun": "catalog:",
|
||||||
|
"@types/dockerode": "^4.0.1",
|
||||||
"typescript": "catalog:",
|
"typescript": "catalog:",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -567,6 +571,8 @@
|
|||||||
|
|
||||||
"@babylonjs/core": ["@babylonjs/core@7.54.3", "", {}, "sha512-P5ncXVd8GEUJLhwloP9V0oVwQYIrvZztguVeLlvd5Rx+9aQnenKjpV8auJ6SRsUlAmNZU4pFTKzwF6o2EUfhAw=="],
|
"@babylonjs/core": ["@babylonjs/core@7.54.3", "", {}, "sha512-P5ncXVd8GEUJLhwloP9V0oVwQYIrvZztguVeLlvd5Rx+9aQnenKjpV8auJ6SRsUlAmNZU4pFTKzwF6o2EUfhAw=="],
|
||||||
|
|
||||||
|
"@balena/dockerignore": ["@balena/dockerignore@1.0.2", "", {}, "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q=="],
|
||||||
|
|
||||||
"@base-ui/react": ["@base-ui/react@1.6.0", "", { "dependencies": { "@babel/runtime": "^7.29.2", "@base-ui/utils": "0.3.1", "@floating-ui/react-dom": "^2.1.8", "@floating-ui/utils": "^0.2.11", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@date-fns/tz": "^1.2.0", "@types/react": "^17 || ^18 || ^19", "date-fns": "^4.0.0", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@date-fns/tz", "@types/react", "date-fns"] }, "sha512-/jzjTWJYXhRFO45Bev9lc3cHbmjzCMpUqbMZ2AgKy/z25mY9B6shGSNcXcjQar9n5doM0KYW1W8fcFv2jZBuMw=="],
|
"@base-ui/react": ["@base-ui/react@1.6.0", "", { "dependencies": { "@babel/runtime": "^7.29.2", "@base-ui/utils": "0.3.1", "@floating-ui/react-dom": "^2.1.8", "@floating-ui/utils": "^0.2.11", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@date-fns/tz": "^1.2.0", "@types/react": "^17 || ^18 || ^19", "date-fns": "^4.0.0", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@date-fns/tz", "@types/react", "date-fns"] }, "sha512-/jzjTWJYXhRFO45Bev9lc3cHbmjzCMpUqbMZ2AgKy/z25mY9B6shGSNcXcjQar9n5doM0KYW1W8fcFv2jZBuMw=="],
|
||||||
|
|
||||||
"@base-ui/utils": ["@base-ui/utils@0.3.1", "", { "dependencies": { "@babel/runtime": "^7.29.2", "@floating-ui/utils": "^0.2.11", "reselect": "^5.2.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-gFFiltORVmW/N6IILTGxizP3PBpVpysqML1ALY5Vk0mH+7faVkCknOU31goYHN5Aoek2dkjxva1XOD2Ce9WuIg=="],
|
"@base-ui/utils": ["@base-ui/utils@0.3.1", "", { "dependencies": { "@babel/runtime": "^7.29.2", "@floating-ui/utils": "^0.2.11", "reselect": "^5.2.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-gFFiltORVmW/N6IILTGxizP3PBpVpysqML1ALY5Vk0mH+7faVkCknOU31goYHN5Aoek2dkjxva1XOD2Ce9WuIg=="],
|
||||||
@@ -833,6 +839,10 @@
|
|||||||
|
|
||||||
"@gorhom/portal": ["@gorhom/portal@1.0.14", "", { "dependencies": { "nanoid": "^3.3.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-MXyL4xvCjmgaORr/rtryDNFy3kU4qUbKlwtQqqsygd0xX3mhKjOLn6mQK8wfu0RkoE0pBE0nAasRoHua+/QZ7A=="],
|
"@gorhom/portal": ["@gorhom/portal@1.0.14", "", { "dependencies": { "nanoid": "^3.3.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-MXyL4xvCjmgaORr/rtryDNFy3kU4qUbKlwtQqqsygd0xX3mhKjOLn6mQK8wfu0RkoE0pBE0nAasRoHua+/QZ7A=="],
|
||||||
|
|
||||||
|
"@grpc/grpc-js": ["@grpc/grpc-js@1.14.4", "", { "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ=="],
|
||||||
|
|
||||||
|
"@grpc/proto-loader": ["@grpc/proto-loader@0.7.15", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.2.5", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ=="],
|
||||||
|
|
||||||
"@hono/node-server": ["@hono/node-server@2.0.11", "", { "peerDependencies": { "hono": "^4" } }, "sha512-bjD221KPLoJTWUwso1J6fGKiTXEUFedG/s0visavY4zakFPkeGURMRNly+FhBHs7T8Dz4qHaZIMX9ZoJHSJtKA=="],
|
"@hono/node-server": ["@hono/node-server@2.0.11", "", { "peerDependencies": { "hono": "^4" } }, "sha512-bjD221KPLoJTWUwso1J6fGKiTXEUFedG/s0visavY4zakFPkeGURMRNly+FhBHs7T8Dz4qHaZIMX9ZoJHSJtKA=="],
|
||||||
|
|
||||||
"@hono/standard-validator": ["@hono/standard-validator@0.2.3", "", { "peerDependencies": { "@standard-schema/spec": "^1.0.0", "hono": ">=3.9.0" } }, "sha512-bp9vHu6Va6SfMHC3D4ZLBbT/woi+AZ9CRdTXQu3kLJuLh2W/Gb9UO4hijS+BQAGFXi4EGpXdetxpzwTAawSVeg=="],
|
"@hono/standard-validator": ["@hono/standard-validator@0.2.3", "", { "peerDependencies": { "@standard-schema/spec": "^1.0.0", "hono": ">=3.9.0" } }, "sha512-bp9vHu6Va6SfMHC3D4ZLBbT/woi+AZ9CRdTXQu3kLJuLh2W/Gb9UO4hijS+BQAGFXi4EGpXdetxpzwTAawSVeg=="],
|
||||||
@@ -933,6 +943,8 @@
|
|||||||
|
|
||||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||||
|
|
||||||
|
"@js-sdsl/ordered-map": ["@js-sdsl/ordered-map@4.4.2", "", {}, "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw=="],
|
||||||
|
|
||||||
"@malept/cross-spawn-promise": ["@malept/cross-spawn-promise@1.1.1", "", { "dependencies": { "cross-spawn": "^7.0.1" } }, "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ=="],
|
"@malept/cross-spawn-promise": ["@malept/cross-spawn-promise@1.1.1", "", { "dependencies": { "cross-spawn": "^7.0.1" } }, "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ=="],
|
||||||
|
|
||||||
"@mariozechner/clipboard": ["@mariozechner/clipboard@0.3.9", "", { "optionalDependencies": { "@mariozechner/clipboard-darwin-arm64": "0.3.9", "@mariozechner/clipboard-darwin-universal": "0.3.9", "@mariozechner/clipboard-darwin-x64": "0.3.9", "@mariozechner/clipboard-linux-arm64-gnu": "0.3.9", "@mariozechner/clipboard-linux-arm64-musl": "0.3.9", "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.9", "@mariozechner/clipboard-linux-x64-gnu": "0.3.9", "@mariozechner/clipboard-linux-x64-musl": "0.3.9", "@mariozechner/clipboard-win32-arm64-msvc": "0.3.9", "@mariozechner/clipboard-win32-x64-msvc": "0.3.9" } }, "sha512-ABnA53mdfkGZwOFUdZNv2S0CWGO/EIuPj8Vv9xmBFmSYg/qFc7ihO6q5FcQjvoE67kZpWkEc4AhD6B/os04yuA=="],
|
"@mariozechner/clipboard": ["@mariozechner/clipboard@0.3.9", "", { "optionalDependencies": { "@mariozechner/clipboard-darwin-arm64": "0.3.9", "@mariozechner/clipboard-darwin-universal": "0.3.9", "@mariozechner/clipboard-darwin-x64": "0.3.9", "@mariozechner/clipboard-linux-arm64-gnu": "0.3.9", "@mariozechner/clipboard-linux-arm64-musl": "0.3.9", "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.9", "@mariozechner/clipboard-linux-x64-gnu": "0.3.9", "@mariozechner/clipboard-linux-x64-musl": "0.3.9", "@mariozechner/clipboard-win32-arm64-msvc": "0.3.9", "@mariozechner/clipboard-win32-x64-msvc": "0.3.9" } }, "sha512-ABnA53mdfkGZwOFUdZNv2S0CWGO/EIuPj8Vv9xmBFmSYg/qFc7ihO6q5FcQjvoE67kZpWkEc4AhD6B/os04yuA=="],
|
||||||
@@ -1355,6 +1367,20 @@
|
|||||||
|
|
||||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.62.2", "", { "os": "linux", "cpu": "x64" }, "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A=="],
|
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.62.2", "", { "os": "linux", "cpu": "x64" }, "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A=="],
|
||||||
|
|
||||||
|
"@sandbox-agent/cli": ["@sandbox-agent/cli@0.4.2", "", { "dependencies": { "@sandbox-agent/cli-shared": "0.4.2" }, "optionalDependencies": { "@sandbox-agent/cli-darwin-arm64": "0.4.2", "@sandbox-agent/cli-darwin-x64": "0.4.2", "@sandbox-agent/cli-linux-arm64": "0.4.2", "@sandbox-agent/cli-linux-x64": "0.4.2", "@sandbox-agent/cli-win32-x64": "0.4.2" }, "bin": { "sandbox-agent": "bin/sandbox-agent" } }, "sha512-trO//ypJBSt5xkewuol9LOykvDgHwUXq8R+yQVS+0CmpN3lYUtewHkb+At9RVGRhDMmJZY2oasaXDnhfurQ33w=="],
|
||||||
|
|
||||||
|
"@sandbox-agent/cli-darwin-arm64": ["@sandbox-agent/cli-darwin-arm64@0.4.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-+L1O8SI7k/LLhyB4dG0ghmz1cJHa0WtVjuRTrEE2gw/5EbGLWopPBsCVCmQ7snrQ4fPwtaiZDhfExcEj1VI7aw=="],
|
||||||
|
|
||||||
|
"@sandbox-agent/cli-darwin-x64": ["@sandbox-agent/cli-darwin-x64@0.4.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-dDg/EwWsdgVVbJiiCX1scSNRRA48u77SsC7Tuqrfzx4fIJMLuLiIcmEtXQyCBWysSyQNV2Cr+PYXXQfCb3xg8g=="],
|
||||||
|
|
||||||
|
"@sandbox-agent/cli-linux-arm64": ["@sandbox-agent/cli-linux-arm64@0.4.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-TGmTUexMoubmWQyTeaOJu0rDVl2h0Ifh1pZ0ceZy7u/6Eoqs2n46CbfQtasUxZJf10uxPgRyzEDhcdDrTYVQUA=="],
|
||||||
|
|
||||||
|
"@sandbox-agent/cli-linux-x64": ["@sandbox-agent/cli-linux-x64@0.4.2", "", { "os": "linux", "cpu": "x64" }, "sha512-H9Rbqq0DRkCHvakzefJUDrDa2y+vJjlYd5/tefzKbQ34locE13TGNygRLxdEVXpBECjK9wVdBwTVEphQNsOcjw=="],
|
||||||
|
|
||||||
|
"@sandbox-agent/cli-shared": ["@sandbox-agent/cli-shared@0.4.2", "", {}, "sha512-sjZXRkKeFXCSKR6hHzF2Af8CCRO3F3WFwVQJ22+sLTXJ2xskV8lkUE4egknQU9B5BC1Zumts/YiNCFQWG85awQ=="],
|
||||||
|
|
||||||
|
"@sandbox-agent/cli-win32-x64": ["@sandbox-agent/cli-win32-x64@0.4.2", "", { "os": "win32", "cpu": "x64" }, "sha512-lZNfHWPwQe/VH51Yvrl/ATCUvBZ3a+c8mwovojhQcmZlv4QuUQPkuvxhPqHRh9AyBx78L5J/ha46es2doa34nQ=="],
|
||||||
|
|
||||||
"@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="],
|
"@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="],
|
||||||
|
|
||||||
"@secure-exec/core": ["@secure-exec/core@0.2.1", "", { "dependencies": { "better-sqlite3": "^12.8.0" } }, "sha512-HsnUv6gClpMA1BBRmX86j30TKTZtgJC/fO1tVavr7IpM2zNKbHU8LgSlBd7mv2SNy02ImTmU/GnQ3aYB4NSbEg=="],
|
"@secure-exec/core": ["@secure-exec/core@0.2.1", "", { "dependencies": { "better-sqlite3": "^12.8.0" } }, "sha512-HsnUv6gClpMA1BBRmX86j30TKTZtgJC/fO1tVavr7IpM2zNKbHU8LgSlBd7mv2SNy02ImTmU/GnQ3aYB4NSbEg=="],
|
||||||
@@ -1591,6 +1617,10 @@
|
|||||||
|
|
||||||
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
|
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
|
||||||
|
|
||||||
|
"@types/docker-modem": ["@types/docker-modem@3.0.6", "", { "dependencies": { "@types/node": "*", "@types/ssh2": "*" } }, "sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg=="],
|
||||||
|
|
||||||
|
"@types/dockerode": ["@types/dockerode@4.0.1", "", { "dependencies": { "@types/docker-modem": "*", "@types/node": "*", "@types/ssh2": "*" } }, "sha512-cmUpB+dPN955PxBEuXE3f6lKO1hHiIGYJA46IVF3BJpNsZGvtBDcRnlrHYHtOH/B6vtDOyl2kZ2ShAu3mgc27Q=="],
|
||||||
|
|
||||||
"@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="],
|
"@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="],
|
||||||
|
|
||||||
"@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="],
|
"@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="],
|
||||||
@@ -1629,6 +1659,8 @@
|
|||||||
|
|
||||||
"@types/retry": ["@types/retry@0.12.2", "", {}, "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow=="],
|
"@types/retry": ["@types/retry@0.12.2", "", {}, "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow=="],
|
||||||
|
|
||||||
|
"@types/ssh2": ["@types/ssh2@1.15.5", "", { "dependencies": { "@types/node": "^18.11.18" } }, "sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ=="],
|
||||||
|
|
||||||
"@types/stats.js": ["@types/stats.js@0.17.4", "", {}, "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA=="],
|
"@types/stats.js": ["@types/stats.js@0.17.4", "", {}, "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA=="],
|
||||||
|
|
||||||
"@types/three": ["@types/three@0.165.0", "", { "dependencies": { "@tweenjs/tween.js": "~23.1.1", "@types/stats.js": "*", "@types/webxr": "*", "fflate": "~0.8.2", "meshoptimizer": "~0.18.1" } }, "sha512-AJK8JZAFNBF0kBXiAIl5pggYlzAGGA8geVYQXAcPCEDRbyA+oEjkpUBcJJrtNz6IiALwzGexFJGZG2yV3WsYBw=="],
|
"@types/three": ["@types/three@0.165.0", "", { "dependencies": { "@tweenjs/tween.js": "~23.1.1", "@types/stats.js": "*", "@types/webxr": "*", "fflate": "~0.8.2", "meshoptimizer": "~0.18.1" } }, "sha512-AJK8JZAFNBF0kBXiAIl5pggYlzAGGA8geVYQXAcPCEDRbyA+oEjkpUBcJJrtNz6IiALwzGexFJGZG2yV3WsYBw=="],
|
||||||
@@ -1723,6 +1755,8 @@
|
|||||||
|
|
||||||
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
|
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
|
||||||
|
|
||||||
|
"acp-http-client": ["acp-http-client@0.4.2", "", { "dependencies": { "@agentclientprotocol/sdk": "^0.16.1" } }, "sha512-3wtPieF08YIU4vNXaoL5up/1D0if4i9IX3Ye5q/bwbcwg1BKsazIK/VNNfvN4ldbPjWul69IqIOpGRS3I0qo3Q=="],
|
||||||
|
|
||||||
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
|
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
|
||||||
|
|
||||||
"agent-cli-detector": ["agent-cli-detector@0.1.4", "", { "bin": { "agent-cli-detector": "dist/cli.js" } }, "sha512-qPgevFvpaQoBaRJVKzr8R7h1WPvV3DtbgRIQlne4le66KBzXx5hNBwo/+NTw67LgkKBlhCzksrdautpUdlls0Q=="],
|
"agent-cli-detector": ["agent-cli-detector@0.1.4", "", { "bin": { "agent-cli-detector": "dist/cli.js" } }, "sha512-qPgevFvpaQoBaRJVKzr8R7h1WPvV3DtbgRIQlne4le66KBzXx5hNBwo/+NTw67LgkKBlhCzksrdautpUdlls0Q=="],
|
||||||
@@ -1757,6 +1791,8 @@
|
|||||||
|
|
||||||
"asap": ["asap@2.0.6", "", {}, "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="],
|
"asap": ["asap@2.0.6", "", {}, "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="],
|
||||||
|
|
||||||
|
"asn1": ["asn1@0.2.6", "", { "dependencies": { "safer-buffer": "~2.1.0" } }, "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ=="],
|
||||||
|
|
||||||
"asn1.js": ["asn1.js@4.10.1", "", { "dependencies": { "bn.js": "^4.0.0", "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0" } }, "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw=="],
|
"asn1.js": ["asn1.js@4.10.1", "", { "dependencies": { "bn.js": "^4.0.0", "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0" } }, "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw=="],
|
||||||
|
|
||||||
"assert": ["assert@2.1.0", "", { "dependencies": { "call-bind": "^1.0.2", "is-nan": "^1.3.2", "object-is": "^1.1.5", "object.assign": "^4.1.4", "util": "^0.12.5" } }, "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw=="],
|
"assert": ["assert@2.1.0", "", { "dependencies": { "call-bind": "^1.0.2", "is-nan": "^1.3.2", "object-is": "^1.1.5", "object.assign": "^4.1.4", "util": "^0.12.5" } }, "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw=="],
|
||||||
@@ -1801,6 +1837,8 @@
|
|||||||
|
|
||||||
"basic-ftp": ["basic-ftp@5.3.1", "", {}, "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw=="],
|
"basic-ftp": ["basic-ftp@5.3.1", "", {}, "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw=="],
|
||||||
|
|
||||||
|
"bcrypt-pbkdf": ["bcrypt-pbkdf@1.0.2", "", { "dependencies": { "tweetnacl": "^0.14.3" } }, "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w=="],
|
||||||
|
|
||||||
"better-auth": ["better-auth@1.6.15", "", { "dependencies": { "@better-auth/core": "1.6.15", "@better-auth/drizzle-adapter": "1.6.15", "@better-auth/kysely-adapter": "1.6.15", "@better-auth/memory-adapter": "1.6.15", "@better-auth/mongo-adapter": "1.6.15", "@better-auth/prisma-adapter": "1.6.15", "@better-auth/telemetry": "1.6.15", "@better-auth/utils": "0.4.1", "@better-fetch/fetch": "1.1.21", "@noble/ciphers": "^2.1.1", "@noble/hashes": "^2.0.1", "better-call": "1.3.5", "defu": "^6.1.4", "jose": "^6.1.3", "kysely": "^0.28.17 || ^0.29.0", "nanostores": "^1.1.1", "zod": "^4.3.6" }, "peerDependencies": { "@lynx-js/react": "*", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "@sveltejs/kit": "^2.0.0", "@tanstack/react-start": "^1.0.0", "@tanstack/solid-start": "^1.0.0", "better-sqlite3": "^12.0.0", "drizzle-kit": ">=0.31.4", "drizzle-orm": "^0.45.2", "mongodb": "^6.0.0 || ^7.0.0", "mysql2": "^3.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "pg": "^8.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@lynx-js/react", "@prisma/client", "@sveltejs/kit", "@tanstack/react-start", "@tanstack/solid-start", "better-sqlite3", "drizzle-kit", "drizzle-orm", "mongodb", "mysql2", "next", "pg", "prisma", "react", "react-dom", "solid-js", "svelte", "vitest", "vue"] }, "sha512-0nuQuEru3ZrLF+9xFUuN3llAmR+6gHLtLunoXaZxB9lXGjSmfBcc6SZUgYq4DfzugPnLvdnzYazsyprZFSFC4Q=="],
|
"better-auth": ["better-auth@1.6.15", "", { "dependencies": { "@better-auth/core": "1.6.15", "@better-auth/drizzle-adapter": "1.6.15", "@better-auth/kysely-adapter": "1.6.15", "@better-auth/memory-adapter": "1.6.15", "@better-auth/mongo-adapter": "1.6.15", "@better-auth/prisma-adapter": "1.6.15", "@better-auth/telemetry": "1.6.15", "@better-auth/utils": "0.4.1", "@better-fetch/fetch": "1.1.21", "@noble/ciphers": "^2.1.1", "@noble/hashes": "^2.0.1", "better-call": "1.3.5", "defu": "^6.1.4", "jose": "^6.1.3", "kysely": "^0.28.17 || ^0.29.0", "nanostores": "^1.1.1", "zod": "^4.3.6" }, "peerDependencies": { "@lynx-js/react": "*", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "@sveltejs/kit": "^2.0.0", "@tanstack/react-start": "^1.0.0", "@tanstack/solid-start": "^1.0.0", "better-sqlite3": "^12.0.0", "drizzle-kit": ">=0.31.4", "drizzle-orm": "^0.45.2", "mongodb": "^6.0.0 || ^7.0.0", "mysql2": "^3.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "pg": "^8.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@lynx-js/react", "@prisma/client", "@sveltejs/kit", "@tanstack/react-start", "@tanstack/solid-start", "better-sqlite3", "drizzle-kit", "drizzle-orm", "mongodb", "mysql2", "next", "pg", "prisma", "react", "react-dom", "solid-js", "svelte", "vitest", "vue"] }, "sha512-0nuQuEru3ZrLF+9xFUuN3llAmR+6gHLtLunoXaZxB9lXGjSmfBcc6SZUgYq4DfzugPnLvdnzYazsyprZFSFC4Q=="],
|
||||||
|
|
||||||
"better-call": ["better-call@1.3.5", "", { "dependencies": { "@better-auth/utils": "^0.4.0", "@better-fetch/fetch": "^1.1.21", "rou3": "^0.7.12", "set-cookie-parser": "^3.0.1" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-kOFJkBP7utAQLEYrobZm3vkTH8mXq5GNgvjc5/XEST1ilVHaxXUXfeDeFlqoETMtyqS4+3/h4ONX2i++ebZrvA=="],
|
"better-call": ["better-call@1.3.5", "", { "dependencies": { "@better-auth/utils": "^0.4.0", "@better-fetch/fetch": "^1.1.21", "rou3": "^0.7.12", "set-cookie-parser": "^3.0.1" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-kOFJkBP7utAQLEYrobZm3vkTH8mXq5GNgvjc5/XEST1ilVHaxXUXfeDeFlqoETMtyqS4+3/h4ONX2i++ebZrvA=="],
|
||||||
@@ -1863,6 +1901,8 @@
|
|||||||
|
|
||||||
"buffer-xor": ["buffer-xor@1.0.3", "", {}, "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ=="],
|
"buffer-xor": ["buffer-xor@1.0.3", "", {}, "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ=="],
|
||||||
|
|
||||||
|
"buildcheck": ["buildcheck@0.0.7", "", {}, "sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA=="],
|
||||||
|
|
||||||
"builtin-status-codes": ["builtin-status-codes@3.0.0", "", {}, "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ=="],
|
"builtin-status-codes": ["builtin-status-codes@3.0.0", "", {}, "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ=="],
|
||||||
|
|
||||||
"bun-ffi-structs": ["bun-ffi-structs@0.2.4", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-AJzsqoVFs1KBbJbWHIYrVZLDC3NhTqqh25awRXqzoLzmBAKr5oqk6+CwuYHAekKx+VBCYVohBoKuRq40dV+TYg=="],
|
"bun-ffi-structs": ["bun-ffi-structs@0.2.4", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-AJzsqoVFs1KBbJbWHIYrVZLDC3NhTqqh25awRXqzoLzmBAKr5oqk6+CwuYHAekKx+VBCYVohBoKuRq40dV+TYg=="],
|
||||||
@@ -1999,6 +2039,8 @@
|
|||||||
|
|
||||||
"cosmiconfig": ["cosmiconfig@9.0.2", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg=="],
|
"cosmiconfig": ["cosmiconfig@9.0.2", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg=="],
|
||||||
|
|
||||||
|
"cpu-features": ["cpu-features@0.0.10", "", { "dependencies": { "buildcheck": "~0.0.6", "nan": "^2.19.0" } }, "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA=="],
|
||||||
|
|
||||||
"create-ecdh": ["create-ecdh@4.0.4", "", { "dependencies": { "bn.js": "^4.1.0", "elliptic": "^6.5.3" } }, "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A=="],
|
"create-ecdh": ["create-ecdh@4.0.4", "", { "dependencies": { "bn.js": "^4.1.0", "elliptic": "^6.5.3" } }, "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A=="],
|
||||||
|
|
||||||
"create-hash": ["create-hash@1.2.0", "", { "dependencies": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", "md5.js": "^1.3.4", "ripemd160": "^2.0.1", "sha.js": "^2.4.0" } }, "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg=="],
|
"create-hash": ["create-hash@1.2.0", "", { "dependencies": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", "md5.js": "^1.3.4", "ripemd160": "^2.0.1", "sha.js": "^2.4.0" } }, "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg=="],
|
||||||
@@ -2169,6 +2211,10 @@
|
|||||||
|
|
||||||
"dnssd-advertise": ["dnssd-advertise@1.1.6", "", {}, "sha512-Ndrrf6BMPalkQPd/zubL+4YghH2J9NspapQ09uDXwYbvOPkP0oaqf5CkcwJ0b50kS2O3ul6yVu+jz+RY62Cejg=="],
|
"dnssd-advertise": ["dnssd-advertise@1.1.6", "", {}, "sha512-Ndrrf6BMPalkQPd/zubL+4YghH2J9NspapQ09uDXwYbvOPkP0oaqf5CkcwJ0b50kS2O3ul6yVu+jz+RY62Cejg=="],
|
||||||
|
|
||||||
|
"docker-modem": ["docker-modem@5.0.7", "", { "dependencies": { "debug": "^4.1.1", "readable-stream": "^3.5.0", "split-ca": "^1.0.1", "ssh2": "^1.15.0" } }, "sha512-XJgGhoR/CLpqshm4d3L7rzH6t8NgDFUIIpztYlLHIApeJjMZKYJMz2zxPsYxnejq5h3ELYSw/RBsi3t5h7gNTA=="],
|
||||||
|
|
||||||
|
"dockerode": ["dockerode@5.0.1", "", { "dependencies": { "@balena/dockerignore": "^1.0.2", "@grpc/grpc-js": "^1.11.1", "@grpc/proto-loader": "^0.7.13", "docker-modem": "^5.0.7", "protobufjs": "^7.3.2", "tar-fs": "^2.1.4" } }, "sha512-avsq/xk4YPIrn0CgleX5bjT9Y8IT1p9PxrNQ++RBQ2WEyFfHCTDsT9kmyxz+H/axnjAwg8wJWEIuPGOUuNupiA=="],
|
||||||
|
|
||||||
"dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="],
|
"dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="],
|
||||||
|
|
||||||
"dom-helpers": ["dom-helpers@3.4.0", "", { "dependencies": { "@babel/runtime": "^7.1.2" } }, "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA=="],
|
"dom-helpers": ["dom-helpers@3.4.0", "", { "dependencies": { "@babel/runtime": "^7.1.2" } }, "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA=="],
|
||||||
@@ -2447,6 +2493,8 @@
|
|||||||
|
|
||||||
"get-own-enumerable-keys": ["get-own-enumerable-keys@1.0.0", "", {}, "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA=="],
|
"get-own-enumerable-keys": ["get-own-enumerable-keys@1.0.0", "", {}, "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA=="],
|
||||||
|
|
||||||
|
"get-port": ["get-port@7.2.0", "", {}, "sha512-afP4W205ONCuMoPBqcR6PSXnzX35KTcJygfJfcp+QY+uwm3p20p1YczWXhlICIzGMCxYBQcySEcOgsJcrkyobg=="],
|
||||||
|
|
||||||
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
||||||
|
|
||||||
"get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="],
|
"get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="],
|
||||||
@@ -2769,6 +2817,8 @@
|
|||||||
|
|
||||||
"lodash-es": ["lodash-es@4.18.1", "", {}, "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A=="],
|
"lodash-es": ["lodash-es@4.18.1", "", {}, "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A=="],
|
||||||
|
|
||||||
|
"lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="],
|
||||||
|
|
||||||
"lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="],
|
"lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="],
|
||||||
|
|
||||||
"lodash.throttle": ["lodash.throttle@4.1.1", "", {}, "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="],
|
"lodash.throttle": ["lodash.throttle@4.1.1", "", {}, "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="],
|
||||||
@@ -3005,6 +3055,8 @@
|
|||||||
|
|
||||||
"mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
|
"mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
|
||||||
|
|
||||||
|
"nan": ["nan@2.28.0", "", {}, "sha512-fTsDz99OTq2sVePhGdp4qQhggZFtKr64ZNVyVajRKtMOkJxYekplBh577PiJB12v/D3s2E5cGtOI45LWp6rnLQ=="],
|
||||||
|
|
||||||
"nanoid": ["nanoid@3.3.16", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q=="],
|
"nanoid": ["nanoid@3.3.16", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q=="],
|
||||||
|
|
||||||
"nanostores": ["nanostores@1.4.1", "", {}, "sha512-PGd3uPojJB9Z07d5NX3Db/SOSBbyy3wLMUGq0GpnEEJfVzY9mq7daPMAZ3jObV5D3Jn+YKND636eI5ULg7F80Q=="],
|
"nanostores": ["nanostores@1.4.1", "", {}, "sha512-PGd3uPojJB9Z07d5NX3Db/SOSBbyy3wLMUGq0GpnEEJfVzY9mq7daPMAZ3jObV5D3Jn+YKND636eI5ULg7F80Q=="],
|
||||||
@@ -3443,6 +3495,8 @@
|
|||||||
|
|
||||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||||
|
|
||||||
|
"sandbox-agent": ["sandbox-agent@0.4.2", "", { "dependencies": { "@sandbox-agent/cli-shared": "0.4.2", "acp-http-client": "0.4.2" }, "optionalDependencies": { "@sandbox-agent/cli": "0.4.2" }, "peerDependencies": { "@cloudflare/sandbox": ">=0.1.0", "@daytonaio/sdk": ">=0.12.0", "@e2b/code-interpreter": ">=1.0.0", "@fly/sprites": ">=0.0.1", "@vercel/sandbox": ">=0.1.0", "computesdk": ">=0.1.0", "dockerode": ">=4.0.0", "get-port": ">=7.0.0", "modal": ">=0.1.0" }, "optionalPeers": ["@cloudflare/sandbox", "@daytonaio/sdk", "@e2b/code-interpreter", "@fly/sprites", "@vercel/sandbox", "computesdk", "dockerode", "get-port", "modal"] }, "sha512-fH6WDQEaIrgiu93LxZcy+4Dx+t+/cslu+hzXImDyUlsaL6jV2jIv4fdxELkALlo7uzyEDVK9lmqs9qy65RHwBQ=="],
|
||||||
|
|
||||||
"sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
|
"sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
|
||||||
|
|
||||||
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
||||||
@@ -3539,6 +3593,8 @@
|
|||||||
|
|
||||||
"space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="],
|
"space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="],
|
||||||
|
|
||||||
|
"split-ca": ["split-ca@1.0.1", "", {}, "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ=="],
|
||||||
|
|
||||||
"split-on-first": ["split-on-first@1.1.0", "", {}, "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw=="],
|
"split-on-first": ["split-on-first@1.1.0", "", {}, "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw=="],
|
||||||
|
|
||||||
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
|
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
|
||||||
@@ -3547,6 +3603,8 @@
|
|||||||
|
|
||||||
"sql.js": ["sql.js@1.14.1", "", {}, "sha512-gcj8zBWU5cFsi9WUP+4bFNXAyF1iRpA3LLyS/DP5xlrNzGmPIizUeBggKa8DbDwdqaKwUcTEnChtd2grWo/x/A=="],
|
"sql.js": ["sql.js@1.14.1", "", {}, "sha512-gcj8zBWU5cFsi9WUP+4bFNXAyF1iRpA3LLyS/DP5xlrNzGmPIizUeBggKa8DbDwdqaKwUcTEnChtd2grWo/x/A=="],
|
||||||
|
|
||||||
|
"ssh2": ["ssh2@1.17.0", "", { "dependencies": { "asn1": "^0.2.6", "bcrypt-pbkdf": "^1.0.2" }, "optionalDependencies": { "cpu-features": "~0.0.10", "nan": "^2.23.0" } }, "sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ=="],
|
||||||
|
|
||||||
"stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
|
"stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
|
||||||
|
|
||||||
"stackframe": ["stackframe@1.3.4", "", {}, "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="],
|
"stackframe": ["stackframe@1.3.4", "", {}, "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="],
|
||||||
@@ -3701,6 +3759,8 @@
|
|||||||
|
|
||||||
"tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="],
|
"tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="],
|
||||||
|
|
||||||
|
"tweetnacl": ["tweetnacl@0.14.5", "", {}, "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="],
|
||||||
|
|
||||||
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
||||||
|
|
||||||
"type-fest": ["type-fest@5.8.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA=="],
|
"type-fest": ["type-fest@5.8.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA=="],
|
||||||
@@ -3973,6 +4033,8 @@
|
|||||||
|
|
||||||
"@google/genai/ws": ["ws@8.21.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw=="],
|
"@google/genai/ws": ["ws@8.21.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw=="],
|
||||||
|
|
||||||
|
"@grpc/grpc-js/@grpc/proto-loader": ["@grpc/proto-loader@0.8.1", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.5.5", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg=="],
|
||||||
|
|
||||||
"@jest/schemas/@sinclair/typebox": ["@sinclair/typebox@0.27.12", "", {}, "sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g=="],
|
"@jest/schemas/@sinclair/typebox": ["@sinclair/typebox@0.27.12", "", {}, "sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g=="],
|
||||||
|
|
||||||
"@jest/types/@types/node": ["@types/node@26.1.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw=="],
|
"@jest/types/@types/node": ["@types/node@26.1.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw=="],
|
||||||
@@ -4051,6 +4113,12 @@
|
|||||||
|
|
||||||
"@testing-library/dom/pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="],
|
"@testing-library/dom/pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="],
|
||||||
|
|
||||||
|
"@types/docker-modem/@types/node": ["@types/node@26.1.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw=="],
|
||||||
|
|
||||||
|
"@types/dockerode/@types/node": ["@types/node@26.1.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw=="],
|
||||||
|
|
||||||
|
"@types/ssh2/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="],
|
||||||
|
|
||||||
"@types/ws/@types/node": ["@types/node@26.1.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw=="],
|
"@types/ws/@types/node": ["@types/node@26.1.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw=="],
|
||||||
|
|
||||||
"@types/yauzl/@types/node": ["@types/node@26.1.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw=="],
|
"@types/yauzl/@types/node": ["@types/node@26.1.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw=="],
|
||||||
@@ -4609,6 +4677,12 @@
|
|||||||
|
|
||||||
"@testing-library/dom/pretty-format/react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="],
|
"@testing-library/dom/pretty-format/react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="],
|
||||||
|
|
||||||
|
"@types/docker-modem/@types/node/undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
|
||||||
|
|
||||||
|
"@types/dockerode/@types/node/undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
|
||||||
|
|
||||||
|
"@types/ssh2/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||||
|
|
||||||
"@types/ws/@types/node/undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
|
"@types/ws/@types/node/undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
|
||||||
|
|
||||||
"@types/yauzl/@types/node/undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
|
"@types/yauzl/@types/node/undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
|
||||||
|
|||||||
@@ -12,21 +12,25 @@
|
|||||||
"run:zopu": "bun --env-file=../../.env flue run zopu"
|
"run:zopu": "bun --env-file=../../.env flue run zopu"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@agentos-software/opencode": "0.2.7",
|
||||||
"@code/backend": "workspace:*",
|
"@code/backend": "workspace:*",
|
||||||
"@code/env": "workspace:*",
|
"@code/env": "workspace:*",
|
||||||
"@code/primitives": "workspace:*",
|
"@code/primitives": "workspace:*",
|
||||||
"@flue/runtime": "latest",
|
"@flue/runtime": "latest",
|
||||||
"@rivet-dev/agentos-core": "catalog:",
|
"@rivet-dev/agentos-core": "catalog:",
|
||||||
"convex": "catalog:",
|
"convex": "catalog:",
|
||||||
|
"dockerode": "^5.0.1",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
|
"get-port": "^7.2.0",
|
||||||
"hono": "4.12.31",
|
"hono": "4.12.31",
|
||||||
"valibot": "^1.4.2",
|
"sandbox-agent": "0.4.2",
|
||||||
"@agentos-software/opencode": "0.2.7"
|
"valibot": "^1.4.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@code/config": "workspace:*",
|
"@code/config": "workspace:*",
|
||||||
"@flue/cli": "latest",
|
"@flue/cli": "latest",
|
||||||
"@types/bun": "catalog:",
|
"@types/bun": "catalog:",
|
||||||
|
"@types/dockerode": "^4.0.1",
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ One logical execution workspace for one ProjectIssue run. An Orb bundles an Agen
|
|||||||
├──────────────────────────────────────────────────┤
|
├──────────────────────────────────────────────────┤
|
||||||
│ OrbHandle │
|
│ OrbHandle │
|
||||||
│ ┌─────────────┐ ┌────────────────────────┐ │
|
│ ┌─────────────┐ ┌────────────────────────┐ │
|
||||||
│ │ AgentOS VM │ │ Docker Sandbox │ │
|
│ │ AgentOS VM │ │ SandboxAgent + Docker │ │
|
||||||
│ │ (OpenCode │◄──►│ (bun, tests, builds, │ │
|
│ │ (OpenCode │◄──►│ (sandbox-agent server │ │
|
||||||
│ │ ACP agent) │ │ repo checkout) │ │
|
│ │ ACP agent) │ │ in a Docker container)│ │
|
||||||
│ └─────────────┘ └────────────────────────┘ │
|
│ └─────────────┘ └────────────────────────┘ │
|
||||||
│ │ │ │
|
│ │ │ │
|
||||||
│ session events runProcess / │
|
│ session events runProcess / │
|
||||||
@@ -22,7 +22,7 @@ One logical execution workspace for one ProjectIssue run. An Orb bundles an Agen
|
|||||||
└──────────────────────────────────────────────────┘
|
└──────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
The AgentOS VM runs the OpenCode ACP adapter (lightweight agent loop, session management, durable identity). Heavy execution — package installs, test suites, builds — runs inside the Docker sandbox via `runProcess`. The sandbox filesystem is mounted into the VM at `/mnt/sandbox`.
|
The AgentOS VM runs the OpenCode ACP adapter (lightweight agent loop, session management, durable identity). Heavy execution — package installs, test suites, builds — runs inside a Docker container hosting a sandbox-agent server. The `DockerSandboxProvider` calls `SandboxAgent.start({ sandbox: docker(...) })`, which starts the server in a Docker container with a dynamically-mapped host port and returns a `SandboxAgent` client whose `baseUrl` both the main process and the AgentOS sidecar subprocess reach over `127.0.0.1`. The sandbox filesystem is mounted into the VM at `/mnt/sandbox`.
|
||||||
|
|
||||||
## Domain model
|
## Domain model
|
||||||
|
|
||||||
@@ -64,11 +64,11 @@ No permanent provider credentials are stored in project files. API keys are inje
|
|||||||
|
|
||||||
## Docker requirements
|
## Docker requirements
|
||||||
|
|
||||||
- Docker daemon running and accessible via `docker` CLI
|
- Docker daemon running and accessible via the Docker socket (`/var/run/docker.sock`)
|
||||||
- The proof fixture uses `oven/bun:1.3-debian` by default
|
- The sandbox uses `rivetdev/sandbox-agent` as its Docker image by default
|
||||||
- Containers run with `--cap-drop=ALL --security-opt=no-new-privileges`
|
- The `sandbox-agent/docker` provider creates containers with `AutoRemove` and a dynamically allocated host port
|
||||||
- Memory limited to 2g, CPUs to 2
|
- Writable bind mount is the project workspace only (mounted at `/home/sandbox` inside the container)
|
||||||
- Writable mount is the project workspace only
|
- The AgentOS sidecar subprocess reaches the sandbox-agent server over `127.0.0.1:<hostPort>`
|
||||||
|
|
||||||
## Local startup
|
## Local startup
|
||||||
|
|
||||||
@@ -87,21 +87,19 @@ Stable markers: `ORB_PROOF_PASSED` (exit 0), `ORB_PROOF_BLOCKED` (exit 2), `ORB_
|
|||||||
## Filesystem layout
|
## Filesystem layout
|
||||||
|
|
||||||
```
|
```
|
||||||
Container (/workspace = host bind mount)
|
SandboxAgent container (/home/sandbox = host bind mount)
|
||||||
/workspace/repository/ — project checkout
|
/home/sandbox/repository/ — project checkout
|
||||||
/workspace/control/ — issue + context files
|
/home/sandbox/control/ — issue + context files
|
||||||
/tmp/orb-pids/ — process PID tracking
|
|
||||||
|
|
||||||
AgentOS VM
|
AgentOS VM (host process)
|
||||||
/mnt/sandbox/ — sandbox mount point
|
/mnt/sandbox/ — sandbox mount (via SandboxAgent baseUrl)
|
||||||
/mnt/sandbox/repository/ — repo (via sandbox)
|
/mnt/sandbox/repository/ — repo (via sandbox)
|
||||||
/root/.config/opencode/ — OpenCode config
|
/root/.config/opencode/ — OpenCode config (chmod 600)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Current limitations
|
## Current limitations
|
||||||
|
|
||||||
- AgentOS VM creation requires the sidecar binary; the proof fixture exercises the Docker sandbox path and skips the OpenCode session when no sidecar is available.
|
|
||||||
- `sendProcessInput` is not supported by the Docker sandbox.
|
|
||||||
- No automatic merge or production deployment capability.
|
- No automatic merge or production deployment capability.
|
||||||
- No multi-region support.
|
- No multi-region support.
|
||||||
- Interactive PTY sessions are not wired through the Docker sandbox.
|
- Interactive PTY sessions are not wired through the sandbox agent.
|
||||||
|
- The model turn stage depends on a reachable OpenAI-compatible gateway; if the gateway is unreachable the proof reports BLOCKED at that stage but still passes Docker and AgentOS/OpenCode.
|
||||||
|
|||||||
@@ -3,34 +3,17 @@ import { spawn } from "node:child_process";
|
|||||||
import { setTimeout as sleepTimer } from "node:timers/promises";
|
import { setTimeout as sleepTimer } from "node:timers/promises";
|
||||||
|
|
||||||
import { Effect } from "effect";
|
import { Effect } from "effect";
|
||||||
import { afterEach, describe, expect, it as vitestIt } from "vitest";
|
import { describe, expect, it as vitestIt } from "vitest";
|
||||||
|
|
||||||
import { DockerSandboxProvider } from "./docker-sandbox";
|
import { DockerSandboxProvider } from "./docker-sandbox";
|
||||||
import { OrbSandboxError } from "./domain";
|
import { OrbSandboxError } from "./domain";
|
||||||
|
|
||||||
// Real containers need more than the 5s default; bind a generous timeout here.
|
// Real containers need more than the 5s default; bind a generous timeout here.
|
||||||
const it = (name: string, fn: () => Promise<void>): void => {
|
const it = (name: string, fn: () => Promise<void>): void => {
|
||||||
vitestIt(name, fn, 30_000);
|
vitestIt(name, fn, 60_000);
|
||||||
};
|
|
||||||
|
|
||||||
// Node-compatible CLI helpers so the suite runs under vitest/node workers as
|
|
||||||
// well as the Bun runtime. `Bun.*` globals are absent under vitest.
|
|
||||||
|
|
||||||
const runCliText = (args: readonly string[]): Promise<string> => {
|
|
||||||
const [command = "docker", ...rest] = args;
|
|
||||||
const proc = spawn(command, rest, {
|
|
||||||
stdio: ["ignore", "pipe", "pipe"],
|
|
||||||
});
|
|
||||||
const chunks: Buffer[] = [];
|
|
||||||
// eslint-disable-next-line promise/avoid-new -- wrapping one-shot stream events in a single promise
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
proc.stdout?.on("data", (c: Buffer) => chunks.push(c));
|
|
||||||
proc.on("close", () => {
|
|
||||||
resolve(Buffer.concat(chunks).toString("utf-8"));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Node-compatible Docker availability check.
|
||||||
const runCliExit = (args: readonly string[]): Promise<number | null> =>
|
const runCliExit = (args: readonly string[]): Promise<number | null> =>
|
||||||
// eslint-disable-next-line promise/avoid-new -- wrapping one-shot child close in a single promise
|
// eslint-disable-next-line promise/avoid-new -- wrapping one-shot child close in a single promise
|
||||||
new Promise((resolve) => {
|
new Promise((resolve) => {
|
||||||
@@ -56,221 +39,121 @@ const dockerAvailable = async (): Promise<boolean> => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const containerExists = async (name: string): Promise<boolean> => {
|
// Top-level await so describe.skipIf evaluates Docker availability at registration.
|
||||||
const listing = await runCliText([
|
|
||||||
"docker",
|
|
||||||
"ps",
|
|
||||||
"-a",
|
|
||||||
"--filter",
|
|
||||||
`name=^${name}$`,
|
|
||||||
"--format",
|
|
||||||
"{{.ID}}",
|
|
||||||
]);
|
|
||||||
return listing.trim().length > 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
const created: string[] = [];
|
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
for (const name of created.splice(0)) {
|
|
||||||
// eslint-disable-next-line no-await-in-loop -- sequential cleanup of test containers
|
|
||||||
await runCliText(["docker", "rm", "-f", name]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Top-level await so describe.skipIf can evaluate Docker availability at registration.
|
|
||||||
const HAVE_DOCKER = await dockerAvailable();
|
const HAVE_DOCKER = await dockerAvailable();
|
||||||
|
|
||||||
const uniqueName = (label: string): string => {
|
const tmpDir = (): string =>
|
||||||
const name = `orb-test-${label}-${Date.now()}-${Math.trunc(Math.random() * 1e6)}`;
|
`/tmp/orb-test-${Date.now()}-${Math.trunc(Math.random() * 1e6)}`;
|
||||||
created.push(name);
|
|
||||||
return name;
|
|
||||||
};
|
|
||||||
|
|
||||||
const statusOf = async (
|
describe.skipIf(!HAVE_DOCKER)(
|
||||||
client: {
|
"DockerSandboxProvider (SandboxAgent + Docker)",
|
||||||
listProcesses: () => Promise<{
|
() => {
|
||||||
processes: { id: string; status?: string }[];
|
it("starts a SandboxAgent server and exposes a reachable baseUrl", async () => {
|
||||||
}>;
|
|
||||||
},
|
|
||||||
id: string
|
|
||||||
): Promise<string | undefined> => {
|
|
||||||
const list = await client.listProcesses();
|
|
||||||
return list.processes.find((p) => p.id === id)?.status;
|
|
||||||
};
|
|
||||||
|
|
||||||
const waitForStatus = async (
|
|
||||||
check: () => Promise<string | undefined>,
|
|
||||||
target: string,
|
|
||||||
timeoutMs = 8000
|
|
||||||
): Promise<string | undefined> => {
|
|
||||||
const deadline = Date.now() + timeoutMs;
|
|
||||||
let status: string | undefined;
|
|
||||||
while (Date.now() < deadline) {
|
|
||||||
// eslint-disable-next-line no-await-in-loop -- polling probes sequentially
|
|
||||||
status = await check();
|
|
||||||
if (status === target) {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line no-await-in-loop -- polling probes sequentially
|
|
||||||
await sleepTimer(250);
|
|
||||||
}
|
|
||||||
return status;
|
|
||||||
};
|
|
||||||
|
|
||||||
describe.skipIf(!HAVE_DOCKER)("DockerSandboxProvider lifecycle", () => {
|
|
||||||
it("starts a container and removes it on dispose", async () => {
|
|
||||||
const name = uniqueName("start");
|
|
||||||
const provider = await Effect.runPromise(
|
const provider = await Effect.runPromise(
|
||||||
DockerSandboxProvider.create({
|
DockerSandboxProvider.create({
|
||||||
containerName: name,
|
hostWorkspacePath: tmpDir(),
|
||||||
hostWorkspacePath: `/tmp/${name}`,
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
try {
|
||||||
const client = await provider.start();
|
const client = await provider.start();
|
||||||
expect(provider.isStarted).toBe(true);
|
expect(provider.isStarted).toBe(true);
|
||||||
|
|
||||||
const version = await client.runProcess({ command: "bun --version" });
|
// The SandboxAgent client must have a baseUrl property — this is what
|
||||||
expect(version.exitCode).toBe(0);
|
// AgentOS serializes so the sidecar can reach the sandbox.
|
||||||
expect((version.stdout ?? "").trim()).toMatch(/\d/u);
|
const { baseUrl } = client as unknown as { baseUrl: string };
|
||||||
|
expect(baseUrl).toBeTruthy();
|
||||||
|
expect(baseUrl).toMatch(/^https?:\/\//u);
|
||||||
|
|
||||||
|
// Run a command to verify the sandbox-agent server actually works.
|
||||||
|
const result = await client.runProcess({
|
||||||
|
args: ["-c", "echo hello-orb"],
|
||||||
|
command: "sh",
|
||||||
|
});
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
expect(result.stdout).toContain("hello-orb");
|
||||||
|
} finally {
|
||||||
|
await provider.dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is idempotent: start returns the same client on repeated calls", async () => {
|
||||||
|
const provider = await Effect.runPromise(
|
||||||
|
DockerSandboxProvider.create({
|
||||||
|
hostWorkspacePath: tmpDir(),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
const c1 = await provider.start();
|
||||||
|
const c2 = await provider.start();
|
||||||
|
expect(c1).toBe(c2);
|
||||||
|
} finally {
|
||||||
|
await provider.dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("disposes cleanly and frees the Docker container", async () => {
|
||||||
|
const provider = await Effect.runPromise(
|
||||||
|
DockerSandboxProvider.create({
|
||||||
|
hostWorkspacePath: tmpDir(),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
await provider.start();
|
||||||
|
expect(provider.isStarted).toBe(true);
|
||||||
|
|
||||||
expect(await containerExists(name)).toBe(true);
|
|
||||||
await provider.dispose();
|
await provider.dispose();
|
||||||
expect(await containerExists(name)).toBe(false);
|
|
||||||
expect(provider.isStarted).toBe(false);
|
expect(provider.isStarted).toBe(false);
|
||||||
|
|
||||||
|
// Give AutoRemove a moment.
|
||||||
|
// eslint-disable-next-line no-await-in-loop -- single settling wait
|
||||||
|
await sleepTimer(2000);
|
||||||
|
|
||||||
|
// Second dispose is a safe no-op.
|
||||||
|
await expect(provider.dispose()).resolves.toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("dispose is idempotent and a no-op before start", async () => {
|
it("binds the host workspace into the sandbox container", async () => {
|
||||||
const name = uniqueName("noop");
|
const workspace = tmpDir();
|
||||||
const provider = await Effect.runPromise(
|
const { spawn: nodeSpawn } = await import("node:child_process");
|
||||||
DockerSandboxProvider.create({
|
// eslint-disable-next-line promise/avoid-new -- one-shot shell write
|
||||||
containerName: name,
|
await new Promise<void>((resolve) => {
|
||||||
hostWorkspacePath: `/tmp/${name}`,
|
const p = nodeSpawn(
|
||||||
})
|
"sh",
|
||||||
|
[
|
||||||
|
"-c",
|
||||||
|
`mkdir -p ${workspace} && echo proof-file > ${workspace}/marker.txt`,
|
||||||
|
],
|
||||||
|
{
|
||||||
|
stdio: ["ignore", "pipe", "pipe"],
|
||||||
|
}
|
||||||
);
|
);
|
||||||
await expect(provider.dispose()).resolves.toBeUndefined();
|
p.on("close", () => resolve());
|
||||||
await expect(provider.dispose()).resolves.toBeUndefined();
|
|
||||||
expect(await containerExists(name)).toBe(false);
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe.skipIf(!HAVE_DOCKER)("DockerSandboxClient detached processes", () => {
|
|
||||||
const makeClient = async (label: string) => {
|
|
||||||
const name = uniqueName(label);
|
|
||||||
const provider = await Effect.runPromise(
|
const provider = await Effect.runPromise(
|
||||||
DockerSandboxProvider.create({
|
DockerSandboxProvider.create({
|
||||||
containerName: name,
|
hostWorkspacePath: workspace,
|
||||||
hostWorkspacePath: `/tmp/${name}`,
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
try {
|
||||||
const client = await provider.start();
|
const client = await provider.start();
|
||||||
return { client, name, provider };
|
const result = await client.runProcess({
|
||||||
};
|
args: ["-c", "cat /home/sandbox/marker.txt"],
|
||||||
|
command: "sh",
|
||||||
it("captures a real group PID for a detached process", async () => {
|
});
|
||||||
const { client, provider } = await makeClient("pid");
|
expect(result.exitCode).toBe(0);
|
||||||
try {
|
expect(result.stdout).toContain("proof-file");
|
||||||
const info = await client.createProcess({ command: "sleep 30" });
|
|
||||||
expect(typeof info.pid).toBe("number");
|
|
||||||
expect((info.pid ?? 0) > 0).toBe(true);
|
|
||||||
expect(info.status).toBe("running");
|
|
||||||
await client.killProcess(info.id);
|
|
||||||
} finally {
|
} finally {
|
||||||
await provider.dispose();
|
await provider.dispose();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("records durable stdout/stderr logs and a real exit code", async () => {
|
|
||||||
const { client, provider } = await makeClient("logs");
|
|
||||||
try {
|
|
||||||
const info = await client.createProcess({
|
|
||||||
command: "sh -c 'echo stdout-line; echo stderr-line 1>&2'",
|
|
||||||
});
|
|
||||||
const status = await waitForStatus(
|
|
||||||
() => statusOf(client, info.id),
|
|
||||||
"exited"
|
|
||||||
);
|
|
||||||
expect(status).toBe("exited");
|
|
||||||
|
|
||||||
const list = await client.listProcesses();
|
|
||||||
const refreshed = list.processes.find((p) => p.id === info.id);
|
|
||||||
expect(refreshed?.exitCode).toBe(0);
|
|
||||||
|
|
||||||
const stdout = await client.getProcessLogs(info.id, { stream: "stdout" });
|
|
||||||
expect(stdout.entries.some((e) => e.data.includes("stdout-line"))).toBe(
|
|
||||||
true
|
|
||||||
);
|
|
||||||
const stderr = await client.getProcessLogs(info.id, { stream: "stderr" });
|
|
||||||
expect(stderr.entries.some((e) => e.data.includes("stderr-line"))).toBe(
|
|
||||||
true
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
await provider.dispose();
|
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
it("refreshes status after natural completion", async () => {
|
|
||||||
const { client, provider } = await makeClient("exit");
|
|
||||||
try {
|
|
||||||
const info = await client.createProcess({ command: "sh -c 'exit 7'" });
|
|
||||||
const status = await waitForStatus(
|
|
||||||
() => statusOf(client, info.id),
|
|
||||||
"exited"
|
|
||||||
);
|
|
||||||
expect(status).toBe("exited");
|
|
||||||
const list = await client.listProcesses();
|
|
||||||
const refreshed = list.processes.find((p) => p.id === info.id);
|
|
||||||
expect(refreshed?.exitCode).toBe(7);
|
|
||||||
} finally {
|
|
||||||
await provider.dispose();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it("stopProcess terminates a long-running detached process", async () => {
|
|
||||||
const { client, provider } = await makeClient("stop");
|
|
||||||
try {
|
|
||||||
const info = await client.createProcess({ command: "sleep 30" });
|
|
||||||
expect(info.status).toBe("running");
|
|
||||||
|
|
||||||
const stopped = await client.stopProcess(info.id);
|
|
||||||
expect(["stopped", "killed"]).toContain(stopped.status);
|
|
||||||
|
|
||||||
const status = await statusOf(client, info.id);
|
|
||||||
expect(status).not.toBe("running");
|
|
||||||
} finally {
|
|
||||||
await provider.dispose();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it("killProcess forces termination", async () => {
|
|
||||||
const { client, provider } = await makeClient("kill");
|
|
||||||
try {
|
|
||||||
const info = await client.createProcess({ command: "sleep 30" });
|
|
||||||
const killed = await client.killProcess(info.id);
|
|
||||||
expect(killed.status).toBe("killed");
|
|
||||||
} finally {
|
|
||||||
await provider.dispose();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it("getProcessLogs throws for an unknown process", async () => {
|
|
||||||
const { client, provider } = await makeClient("unknown");
|
|
||||||
try {
|
|
||||||
await expect(client.getProcessLogs("nope")).rejects.toBeInstanceOf(
|
|
||||||
OrbSandboxError
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
await provider.dispose();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe.skipIf(HAVE_DOCKER)("DockerSandboxProvider without Docker", () => {
|
describe.skipIf(HAVE_DOCKER)("DockerSandboxProvider without Docker", () => {
|
||||||
it("create fails with DockerUnavailable", async () => {
|
it("create fails with DockerUnavailable", async () => {
|
||||||
const error = await Effect.runPromise(
|
const error = await Effect.runPromise(
|
||||||
Effect.flip(
|
Effect.flip(
|
||||||
DockerSandboxProvider.create({
|
DockerSandboxProvider.create({
|
||||||
containerName: "orb-test-nodocker",
|
|
||||||
hostWorkspacePath: "/tmp/orb-test-nodocker",
|
hostWorkspacePath: "/tmp/orb-test-nodocker",
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -282,6 +165,6 @@ describe.skipIf(HAVE_DOCKER)("DockerSandboxProvider without Docker", () => {
|
|||||||
|
|
||||||
if (!HAVE_DOCKER) {
|
if (!HAVE_DOCKER) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"[docker-sandbox.test.ts] Docker daemon unavailable; Docker lifecycle tests skipped."
|
"[docker-sandbox.test.ts] Docker daemon unavailable; SandboxAgent tests skipped."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
import { spawn } from "node:child_process";
|
|
||||||
import { setTimeout as sleepTimer } from "node:timers/promises";
|
|
||||||
|
|
||||||
/* eslint-disable max-classes-per-file -- provider, client, and helpers form one adapter. */
|
|
||||||
import type {
|
import type {
|
||||||
AgentOsSandboxClient,
|
AgentOsSandboxClient,
|
||||||
AgentOsSandboxProcessInfo,
|
|
||||||
AgentOsSandboxProcessLogs,
|
|
||||||
AgentOsSandboxProcessResult,
|
|
||||||
AgentOsSandboxProvider,
|
AgentOsSandboxProvider,
|
||||||
} from "@rivet-dev/agentos-core";
|
} from "@rivet-dev/agentos-core";
|
||||||
import { Effect } from "effect";
|
import { Effect } from "effect";
|
||||||
@@ -14,388 +7,88 @@ import { Effect } from "effect";
|
|||||||
import { OrbSandboxError } from "./domain";
|
import { OrbSandboxError } from "./domain";
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Docker sandbox — AgentOsSandboxProvider backed by the `docker` CLI
|
// Docker sandbox — AgentOsSandboxProvider backed by sandbox-agent + Docker
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// AgentOS serializes sandbox mounts through getSerializableClientConfig,
|
||||||
|
// which reads client.baseUrl and passes it to the sidecar. An in-process
|
||||||
|
// client object can never satisfy that contract because it has no network
|
||||||
|
// endpoint. The supported boundary is a standard SandboxAgent client:
|
||||||
|
//
|
||||||
|
// SandboxAgent.start({ sandbox: docker({ image, binds }) })
|
||||||
|
//
|
||||||
|
// starts a sandbox-agent server inside a Docker container, dynamically maps
|
||||||
|
// a host port, and returns a SandboxAgent client whose baseUrl the sidecar
|
||||||
|
// can reach over HTTP on 127.0.0.1:<hostPort>. Both the main process and
|
||||||
|
// the sidecar subprocess run on the host, so localhost connectivity works.
|
||||||
|
|
||||||
const DEFAULT_IMAGE = "oven/bun:1.3-debian";
|
const SANDBOX_AGENT_IMAGE = "rivetdev/sandbox-agent:0.5.0-rc.2-full";
|
||||||
const CONTAINER_WORKSPACE = "/workspace";
|
const DEFAULT_WORKSPACE = "/home/sandbox";
|
||||||
const PID_DIR = "/tmp/orb-pids";
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Shell quoting & shared helpers
|
// DockerSandboxProvider — wraps SandboxAgent.start with the docker provider
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
const shellQuote = (value: string): string =>
|
|
||||||
`'${value.replaceAll("'", `'"'"'`)}'`;
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-empty-function -- shared best-effort rejection swallower
|
|
||||||
const swallow = (): void => {};
|
|
||||||
|
|
||||||
// Node-compatible sleep (works in Bun runtime and vitest/node workers alike).
|
|
||||||
const sleep = async (ms: number): Promise<void> => {
|
|
||||||
await sleepTimer(ms);
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Low-level docker CLI helpers
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
interface DockerExecResult {
|
|
||||||
exitCode: number;
|
|
||||||
stderr: string;
|
|
||||||
stdout: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const runDocker = async (
|
|
||||||
args: readonly string[],
|
|
||||||
options?: {
|
|
||||||
readonly stdin?: string;
|
|
||||||
readonly timeoutMs?: number;
|
|
||||||
}
|
|
||||||
): Promise<DockerExecResult> => {
|
|
||||||
const controller = new AbortController();
|
|
||||||
const timeout = setTimeout(
|
|
||||||
() => controller.abort(),
|
|
||||||
options?.timeoutMs ?? 120_000
|
|
||||||
);
|
|
||||||
try {
|
|
||||||
// eslint-disable-next-line no-use-before-define -- resolved at call time, after module load
|
|
||||||
return await runChild(args, options?.stdin, controller.signal);
|
|
||||||
} finally {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Spawn `docker` via node:child_process so the adapter works under the Bun
|
|
||||||
// runtime (proof fixture) and vitest/node workers (test suite) alike. Aborting
|
|
||||||
// the signal surfaces as a rejected promise with name "AbortError".
|
|
||||||
const runChild = (
|
|
||||||
args: readonly string[],
|
|
||||||
stdin: string | undefined,
|
|
||||||
signal: AbortSignal
|
|
||||||
): Promise<DockerExecResult> =>
|
|
||||||
// eslint-disable-next-line promise/avoid-new -- a single Promise wrapper models one-shot child resolution
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
const proc = spawn("docker", [...args], {
|
|
||||||
signal,
|
|
||||||
stdio: [stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"],
|
|
||||||
});
|
|
||||||
const stdoutChunks: Buffer[] = [];
|
|
||||||
const stderrChunks: Buffer[] = [];
|
|
||||||
proc.stdout?.on("data", (chunk: Buffer) => stdoutChunks.push(chunk));
|
|
||||||
proc.stderr?.on("data", (chunk: Buffer) => stderrChunks.push(chunk));
|
|
||||||
if (stdin !== undefined && proc.stdin) {
|
|
||||||
proc.stdin.end(stdin);
|
|
||||||
}
|
|
||||||
let settled = false;
|
|
||||||
proc.on("error", (error: NodeJS.ErrnoException) => {
|
|
||||||
if (settled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
settled = true;
|
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
proc.on("close", (code: number | null) => {
|
|
||||||
if (settled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
settled = true;
|
|
||||||
resolve({
|
|
||||||
exitCode: code ?? -1,
|
|
||||||
stderr: Buffer.concat(stderrChunks).toString("utf-8"),
|
|
||||||
stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const checkDockerAvailable = Effect.fn("Docker.checkAvailable")(
|
|
||||||
function* checkDockerAvailable() {
|
|
||||||
const result = yield* Effect.tryPromise({
|
|
||||||
catch: (cause) =>
|
|
||||||
new OrbSandboxError({
|
|
||||||
message: `Docker CLI is not available: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
||||||
reason: "DockerUnavailable",
|
|
||||||
}),
|
|
||||||
try: () => runDocker(["version", "--format", "{{.Server.Version}}"]),
|
|
||||||
});
|
|
||||||
if (result.exitCode !== 0) {
|
|
||||||
return yield* Effect.fail(
|
|
||||||
new OrbSandboxError({
|
|
||||||
message: `Docker daemon is not running: ${result.stderr.trim()}`,
|
|
||||||
reason: "DockerUnavailable",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
type ContainerState = "running" | "stopped" | "not-found";
|
|
||||||
|
|
||||||
const inspectContainer = async (name: string): Promise<ContainerState> => {
|
|
||||||
const result = await runDocker([
|
|
||||||
"inspect",
|
|
||||||
"--format",
|
|
||||||
"{{.State.Running}}",
|
|
||||||
name,
|
|
||||||
]);
|
|
||||||
if (result.exitCode !== 0) {
|
|
||||||
return "not-found";
|
|
||||||
}
|
|
||||||
return result.stdout.trim() === "true" ? "running" : "stopped";
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Container file helpers — secrets never touch docker CLI args or listings
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
const parentDir = (filePath: string): string =>
|
|
||||||
filePath.replace(/\/[^/]+$/u, "") || "/";
|
|
||||||
|
|
||||||
const writeContainerFile = async (
|
|
||||||
container: string,
|
|
||||||
filePath: string,
|
|
||||||
content: string
|
|
||||||
): Promise<void> => {
|
|
||||||
// -i keeps stdin open so the payload reaches `cat` inside the container.
|
|
||||||
const result = await runDocker(
|
|
||||||
[
|
|
||||||
"exec",
|
|
||||||
"-i",
|
|
||||||
container,
|
|
||||||
"sh",
|
|
||||||
"-c",
|
|
||||||
`mkdir -p ${shellQuote(parentDir(filePath))} && cat > ${shellQuote(filePath)}`,
|
|
||||||
],
|
|
||||||
{ stdin: content }
|
|
||||||
);
|
|
||||||
if (result.exitCode !== 0) {
|
|
||||||
throw new OrbSandboxError({
|
|
||||||
message: `Failed to write ${filePath}: ${result.stderr.trim()}`,
|
|
||||||
reason: "CommandFailed",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Read a container file; returns "" when it does not exist. */
|
|
||||||
const readContainerFile = async (
|
|
||||||
container: string,
|
|
||||||
filePath: string
|
|
||||||
): Promise<string> => {
|
|
||||||
const result = await runDocker(
|
|
||||||
["exec", container, "sh", "-c", `cat ${shellQuote(filePath)} 2>/dev/null`],
|
|
||||||
{ timeoutMs: 10_000 }
|
|
||||||
);
|
|
||||||
return result.exitCode === 0 ? result.stdout : "";
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeContainerFiles = async (
|
|
||||||
container: string,
|
|
||||||
files: readonly (string | undefined)[]
|
|
||||||
): Promise<void> => {
|
|
||||||
const paths = files.filter(
|
|
||||||
(file): file is string => typeof file === "string" && file.length > 0
|
|
||||||
);
|
|
||||||
if (paths.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await runDocker(["exec", container, "rm", "-f", ...paths], {
|
|
||||||
timeoutMs: 10_000,
|
|
||||||
}).catch(swallow);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signal a whole detached process group (negative PGID) and fall back to the
|
|
||||||
* leader PID, so spawned children are not orphaned. The PGID is captured via
|
|
||||||
* `setsid`, so signalling `-<pgid>` reaches every member of the group.
|
|
||||||
*/
|
|
||||||
const signalProcessGroup = async (
|
|
||||||
container: string,
|
|
||||||
pid: number,
|
|
||||||
signal: "TERM" | "KILL" | "0"
|
|
||||||
): Promise<void> => {
|
|
||||||
await runDocker(
|
|
||||||
[
|
|
||||||
"exec",
|
|
||||||
container,
|
|
||||||
"sh",
|
|
||||||
"-c",
|
|
||||||
`kill -${signal} -- -${pid} 2>/dev/null; kill -${signal} ${pid} 2>/dev/null; true`,
|
|
||||||
],
|
|
||||||
{ timeoutMs: 10_000 }
|
|
||||||
).catch(swallow);
|
|
||||||
};
|
|
||||||
|
|
||||||
const isProcessGroupAlive = async (
|
|
||||||
container: string,
|
|
||||||
pid: number
|
|
||||||
): Promise<boolean> => {
|
|
||||||
const result = await runDocker(
|
|
||||||
[
|
|
||||||
"exec",
|
|
||||||
container,
|
|
||||||
"sh",
|
|
||||||
"-c",
|
|
||||||
`kill -0 -- -${pid} 2>/dev/null || kill -0 ${pid} 2>/dev/null`,
|
|
||||||
],
|
|
||||||
{ timeoutMs: 5000 }
|
|
||||||
);
|
|
||||||
return result.exitCode === 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
const stageEnvFile = async (
|
|
||||||
container: string,
|
|
||||||
env: Readonly<Record<string, string>>,
|
|
||||||
id: string
|
|
||||||
): Promise<string> => {
|
|
||||||
const envPath = `${PID_DIR}/.env-${id}`;
|
|
||||||
const lines = Object.entries(env)
|
|
||||||
.filter(([, value]) => value !== undefined)
|
|
||||||
.map(([key, value]) => `${key}=${shellQuote(value)}`)
|
|
||||||
.join("\n");
|
|
||||||
await writeContainerFile(container, envPath, `${lines}\n`);
|
|
||||||
return envPath;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// DockerSandboxProvider — owns the container lifecycle (idempotent)
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export interface DockerSandboxOptions {
|
export interface DockerSandboxOptions {
|
||||||
readonly containerName: string;
|
readonly containerName?: string;
|
||||||
readonly hostWorkspacePath: string;
|
readonly hostWorkspacePath: string;
|
||||||
readonly image?: string;
|
readonly image?: string;
|
||||||
readonly workDir?: string;
|
readonly workDir?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DockerSandboxProvider implements AgentOsSandboxProvider {
|
export class DockerSandboxProvider implements AgentOsSandboxProvider {
|
||||||
private readonly containerName: string;
|
|
||||||
private readonly hostWorkspace: string;
|
|
||||||
private readonly image: string;
|
private readonly image: string;
|
||||||
private readonly workDir: string;
|
private readonly binds: string[];
|
||||||
private client: DockerSandboxClient | null = null;
|
private client: AgentOsSandboxClient | null = null;
|
||||||
private starting: Promise<AgentOsSandboxClient> | null = null;
|
private disposeFn: (() => Promise<void>) | null = null;
|
||||||
private containerOwned = false;
|
|
||||||
|
|
||||||
constructor(options: DockerSandboxOptions) {
|
constructor(options: DockerSandboxOptions) {
|
||||||
this.containerName = options.containerName;
|
this.image = options.image ?? SANDBOX_AGENT_IMAGE;
|
||||||
this.hostWorkspace = options.hostWorkspacePath;
|
// Bind the host workspace read-write into the sandbox container so the
|
||||||
this.image = options.image ?? DEFAULT_IMAGE;
|
// restricted writable area is the project workspace only.
|
||||||
this.workDir = options.workDir ?? CONTAINER_WORKSPACE;
|
this.binds = [
|
||||||
|
`${options.hostWorkspacePath}:${options.workDir ?? DEFAULT_WORKSPACE}`,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
static readonly create = Effect.fn("DockerSandboxProvider.create")(
|
static readonly create = Effect.fn("DockerSandboxProvider.create")(
|
||||||
function* createProvider(options: DockerSandboxOptions) {
|
function* createProvider(options: DockerSandboxOptions) {
|
||||||
|
// eslint-disable-next-line no-use-before-define -- defined at module bottom
|
||||||
yield* checkDockerAvailable();
|
yield* checkDockerAvailable();
|
||||||
return new DockerSandboxProvider(options);
|
return new DockerSandboxProvider(options);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
* Start the Docker container idempotently. Returns the same client on
|
|
||||||
* repeated calls. Safe to call from multiple paths concurrently — a single
|
|
||||||
* in-flight start promise is shared.
|
|
||||||
*/
|
|
||||||
async start(): Promise<AgentOsSandboxClient> {
|
async start(): Promise<AgentOsSandboxClient> {
|
||||||
if (this.client) {
|
if (this.client) {
|
||||||
return this.client;
|
return this.client;
|
||||||
}
|
}
|
||||||
if (this.starting) {
|
const { SandboxAgent } = await import("sandbox-agent");
|
||||||
return this.starting;
|
const { docker } = await import("sandbox-agent/docker");
|
||||||
}
|
|
||||||
this.starting = this.doStart();
|
|
||||||
try {
|
|
||||||
return await this.starting;
|
|
||||||
} finally {
|
|
||||||
this.starting = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async doStart(): Promise<AgentOsSandboxClient> {
|
const sandboxAgent = await SandboxAgent.start({
|
||||||
await Effect.runPromise(checkDockerAvailable());
|
sandbox: docker({
|
||||||
const state = await inspectContainer(this.containerName);
|
binds: this.binds,
|
||||||
if (state === "not-found") {
|
image: this.image,
|
||||||
await this.createContainer();
|
}),
|
||||||
this.containerOwned = true;
|
|
||||||
} else if (state === "stopped") {
|
|
||||||
const result = await runDocker(["start", this.containerName]);
|
|
||||||
if (result.exitCode !== 0) {
|
|
||||||
throw new OrbSandboxError({
|
|
||||||
message: `Failed to start container ${this.containerName}: ${result.stderr.trim()}`,
|
|
||||||
reason: "ContainerStart",
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
this.containerOwned = true;
|
this.client = sandboxAgent as unknown as AgentOsSandboxClient;
|
||||||
}
|
this.disposeFn = async () => {
|
||||||
const verified = await inspectContainer(this.containerName);
|
// destroySandbox permanently tears down the backing Docker container;
|
||||||
if (verified !== "running") {
|
// dispose() alone only closes the HTTP connection.
|
||||||
throw new OrbSandboxError({
|
await sandboxAgent.destroySandbox();
|
||||||
message: `Container ${this.containerName} is not running after start (state: ${verified})`,
|
};
|
||||||
reason: "ContainerStart",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line no-use-before-define -- client is defined below in the same module
|
|
||||||
this.client = new DockerSandboxClient(this.containerName, this.workDir);
|
|
||||||
return this.client;
|
return this.client;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async createContainer(): Promise<void> {
|
|
||||||
const result = await runDocker([
|
|
||||||
"run",
|
|
||||||
"-d",
|
|
||||||
"--name",
|
|
||||||
this.containerName,
|
|
||||||
"--workdir",
|
|
||||||
this.workDir,
|
|
||||||
"-v",
|
|
||||||
`${this.hostWorkspace}:${CONTAINER_WORKSPACE}`,
|
|
||||||
"--cap-drop=ALL",
|
|
||||||
"--security-opt=no-new-privileges",
|
|
||||||
"--memory=2g",
|
|
||||||
"--cpus=2",
|
|
||||||
this.image,
|
|
||||||
"sleep",
|
|
||||||
"infinity",
|
|
||||||
]);
|
|
||||||
if (result.exitCode !== 0) {
|
|
||||||
throw new OrbSandboxError({
|
|
||||||
message: `Failed to create container ${this.containerName}: ${result.stderr.trim()}`,
|
|
||||||
reason: "ContainerStart",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the container unconditionally (idempotent). The provider owns
|
|
||||||
* container removal so partial startup — container created, client never
|
|
||||||
* assigned — still cleans up.
|
|
||||||
*/
|
|
||||||
private async removeContainer(): Promise<void> {
|
|
||||||
const result = await runDocker(["rm", "-f", this.containerName], {
|
|
||||||
timeoutMs: 30_000,
|
|
||||||
});
|
|
||||||
this.containerOwned = false;
|
|
||||||
if (result.exitCode !== 0 && !/no such/iu.test(result.stderr)) {
|
|
||||||
throw new OrbSandboxError({
|
|
||||||
message: `Failed to remove container ${this.containerName}: ${result.stderr.trim()}`,
|
|
||||||
reason: "ContainerCleanup",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async dispose(): Promise<void> {
|
async dispose(): Promise<void> {
|
||||||
this.starting = null;
|
const dispose = this.disposeFn;
|
||||||
const { client } = this;
|
|
||||||
this.client = null;
|
this.client = null;
|
||||||
if (client) {
|
this.disposeFn = null;
|
||||||
await client.dispose().catch(swallow);
|
if (dispose) {
|
||||||
|
await dispose();
|
||||||
}
|
}
|
||||||
if (this.containerOwned) {
|
|
||||||
await this.removeContainer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get containerId(): string {
|
|
||||||
return this.containerName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get isStarted(): boolean {
|
get isStarted(): boolean {
|
||||||
@@ -404,388 +97,22 @@ export class DockerSandboxProvider implements AgentOsSandboxProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// DockerSandboxClient — real process lifecycle via process-group tracking
|
// Docker availability check
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
interface TrackedProcess {
|
const checkDockerAvailable = Effect.fn("Docker.checkAvailable")(
|
||||||
readonly args?: string[];
|
function* checkDockerAvailable() {
|
||||||
readonly command: string;
|
yield* Effect.tryPromise({
|
||||||
readonly errFile: string;
|
catch: (cause) =>
|
||||||
exitCode: number | null;
|
new OrbSandboxError({
|
||||||
readonly exitFile: string;
|
message: `Docker is not available: ${cause instanceof Error ? cause.message : String(cause)}`,
|
||||||
readonly id: string;
|
reason: "DockerUnavailable",
|
||||||
readonly outFile: string;
|
}),
|
||||||
pid: number | null;
|
try: async () => {
|
||||||
readonly pidFile: string;
|
const { default: Docker } = await import("dockerode");
|
||||||
readonly scriptFile: string;
|
const docker = new Docker();
|
||||||
readonly startedAt: number;
|
await docker.ping();
|
||||||
status: string;
|
},
|
||||||
}
|
|
||||||
|
|
||||||
class DockerSandboxClient implements AgentOsSandboxClient {
|
|
||||||
private readonly container: string;
|
|
||||||
private readonly workDir: string;
|
|
||||||
private nextId = 0;
|
|
||||||
private readonly processes = new Map<string, TrackedProcess>();
|
|
||||||
|
|
||||||
constructor(container: string, workDir: string) {
|
|
||||||
this.container = container;
|
|
||||||
this.workDir = workDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
async runProcess(options: {
|
|
||||||
readonly command: string;
|
|
||||||
readonly args?: readonly string[];
|
|
||||||
readonly cwd?: string;
|
|
||||||
readonly env?: Readonly<Record<string, string>>;
|
|
||||||
readonly timeoutMs?: number;
|
|
||||||
}): Promise<AgentOsSandboxProcessResult> {
|
|
||||||
this.nextId += 1;
|
|
||||||
const id = `run-${this.nextId}`;
|
|
||||||
const cwd = options.cwd ?? this.workDir;
|
|
||||||
const fullCommand = options.args?.length
|
|
||||||
? `${options.command} ${options.args.map(shellQuote).join(" ")}`
|
|
||||||
: options.command;
|
|
||||||
|
|
||||||
let envPrefix = "";
|
|
||||||
let envPath: string | undefined;
|
|
||||||
if (options.env && Object.keys(options.env).length > 0) {
|
|
||||||
envPath = await stageEnvFile(this.container, options.env, id);
|
|
||||||
envPrefix = `set -a; . ${shellQuote(envPath)}; set +a; rm -f ${shellQuote(envPath)}; `;
|
|
||||||
}
|
|
||||||
|
|
||||||
const wrapped = `${envPrefix}cd ${shellQuote(cwd)} && ${fullCommand}`;
|
|
||||||
const start = Date.now();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await runDocker(
|
|
||||||
["exec", this.container, "sh", "-c", wrapped],
|
|
||||||
{ timeoutMs: options.timeoutMs }
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
durationMs: Date.now() - start,
|
|
||||||
exitCode: result.exitCode,
|
|
||||||
stderr: result.stderr,
|
|
||||||
stdout: result.stdout,
|
|
||||||
timedOut: false,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
if (envPath) {
|
|
||||||
await removeContainerFiles(this.container, [envPath]);
|
|
||||||
}
|
|
||||||
const timedOut = error instanceof Error && error.name === "AbortError";
|
|
||||||
return {
|
|
||||||
durationMs: Date.now() - start,
|
|
||||||
exitCode: null,
|
|
||||||
stderr: timedOut ? "Process timed out" : String(error),
|
|
||||||
stdout: "",
|
|
||||||
timedOut,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async createProcess(options: {
|
|
||||||
readonly command: string;
|
|
||||||
readonly args?: readonly string[];
|
|
||||||
readonly cwd?: string;
|
|
||||||
readonly env?: Readonly<Record<string, string>>;
|
|
||||||
}): Promise<AgentOsSandboxProcessInfo> {
|
|
||||||
this.nextId += 1;
|
|
||||||
const id = `proc-${this.nextId}`;
|
|
||||||
const cwd = options.cwd ?? this.workDir;
|
|
||||||
const fullCommand = options.args?.length
|
|
||||||
? `${options.command} ${options.args.map(shellQuote).join(" ")}`
|
|
||||||
: options.command;
|
|
||||||
const pidFile = `${PID_DIR}/${id}.pid`;
|
|
||||||
const outFile = `${PID_DIR}/${id}.out.log`;
|
|
||||||
const errFile = `${PID_DIR}/${id}.err.log`;
|
|
||||||
const exitFile = `${PID_DIR}/${id}.exit`;
|
|
||||||
const scriptFile = `${PID_DIR}/${id}.sh`;
|
|
||||||
|
|
||||||
let envPath: string | undefined;
|
|
||||||
if (options.env && Object.keys(options.env).length > 0) {
|
|
||||||
envPath = `${PID_DIR}/.env-${id}`;
|
|
||||||
}
|
|
||||||
// The launcher is staged as a file to avoid nested shell quoting; it
|
|
||||||
// sources (then deletes) the env file and records the real exit code.
|
|
||||||
const launcherLines = [
|
|
||||||
...(envPath === undefined
|
|
||||||
? []
|
|
||||||
: [
|
|
||||||
"set -a",
|
|
||||||
`. ${shellQuote(envPath)}`,
|
|
||||||
"set +a",
|
|
||||||
`rm -f ${shellQuote(envPath)}`,
|
|
||||||
]),
|
|
||||||
`cd ${shellQuote(cwd)}`,
|
|
||||||
fullCommand,
|
|
||||||
`echo "$?" > ${shellQuote(exitFile)}`,
|
|
||||||
];
|
|
||||||
|
|
||||||
const tracked: TrackedProcess = {
|
|
||||||
args: options.args ? [...options.args] : undefined,
|
|
||||||
command: options.command,
|
|
||||||
errFile,
|
|
||||||
exitCode: null,
|
|
||||||
exitFile,
|
|
||||||
id,
|
|
||||||
outFile,
|
|
||||||
pid: null,
|
|
||||||
pidFile,
|
|
||||||
scriptFile,
|
|
||||||
startedAt: Date.now(),
|
|
||||||
status: "running",
|
|
||||||
};
|
|
||||||
this.processes.set(id, tracked);
|
|
||||||
|
|
||||||
await writeContainerFile(
|
|
||||||
this.container,
|
|
||||||
scriptFile,
|
|
||||||
`${launcherLines.join("\n")}\n`
|
|
||||||
);
|
|
||||||
if (envPath) {
|
|
||||||
const envEntries = options.env ? Object.entries(options.env) : [];
|
|
||||||
const envLines = envEntries
|
|
||||||
.filter(([, value]) => value !== undefined)
|
|
||||||
.map(([key, value]) => `${key}=${shellQuote(value)}`)
|
|
||||||
.join("\n");
|
|
||||||
await writeContainerFile(this.container, envPath, `${envLines}\n`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Post-write assertion: the staged script must be nonempty before launch.
|
|
||||||
const stagedScript = await readContainerFile(this.container, scriptFile);
|
|
||||||
if (stagedScript.length === 0) {
|
|
||||||
throw new OrbSandboxError({
|
|
||||||
message: `Staged launcher ${scriptFile} is empty; refusing to launch`,
|
|
||||||
reason: "CommandFailed",
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
);
|
||||||
// Launch detached in its own session/process group, redirecting stdout and
|
|
||||||
// stderr to durable files, and capture the group PID via reliable $!.
|
|
||||||
const launch = `mkdir -p ${shellQuote(PID_DIR)} && setsid sh ${shellQuote(scriptFile)} > ${shellQuote(outFile)} 2> ${shellQuote(errFile)} < /dev/null & echo $! > ${shellQuote(pidFile)}`;
|
|
||||||
const result = await runDocker(
|
|
||||||
["exec", this.container, "sh", "-c", launch],
|
|
||||||
{ timeoutMs: 15_000 }
|
|
||||||
);
|
|
||||||
if (result.exitCode !== 0) {
|
|
||||||
tracked.status = "failed";
|
|
||||||
tracked.exitCode = result.exitCode;
|
|
||||||
await removeContainerFiles(this.container, [scriptFile, envPath]);
|
|
||||||
return this.toInfo(tracked);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.refreshStatus(tracked);
|
|
||||||
return this.toInfo(tracked);
|
|
||||||
}
|
|
||||||
|
|
||||||
async listProcesses(): Promise<{ processes: AgentOsSandboxProcessInfo[] }> {
|
|
||||||
for (const tracked of this.processes.values()) {
|
|
||||||
// eslint-disable-next-line no-await-in-loop -- reconcile each tracked process before reporting
|
|
||||||
await this.refreshStatus(tracked).catch(swallow);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
processes: [...this.processes.values()].map((p) => this.toInfo(p)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async stopProcess(id: string): Promise<AgentOsSandboxProcessInfo> {
|
|
||||||
return await this.signalProcess(id, "TERM");
|
|
||||||
}
|
|
||||||
|
|
||||||
async killProcess(id: string): Promise<AgentOsSandboxProcessInfo> {
|
|
||||||
return await this.signalProcess(id, "KILL");
|
|
||||||
}
|
|
||||||
|
|
||||||
private async signalProcess(
|
|
||||||
id: string,
|
|
||||||
requested: "TERM" | "KILL"
|
|
||||||
): Promise<AgentOsSandboxProcessInfo> {
|
|
||||||
const tracked = this.processes.get(id);
|
|
||||||
if (!tracked) {
|
|
||||||
throw new OrbSandboxError({
|
|
||||||
message: `Unknown process ${id}`,
|
|
||||||
reason: "CommandFailed",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await this.refreshStatus(tracked);
|
|
||||||
if (tracked.status !== "running") {
|
|
||||||
return this.toInfo(tracked);
|
|
||||||
}
|
|
||||||
|
|
||||||
const pid = tracked.pid ?? (await this.readPid(tracked));
|
|
||||||
tracked.pid = pid;
|
|
||||||
if (pid === null) {
|
|
||||||
tracked.status = requested === "KILL" ? "killed" : "stopped";
|
|
||||||
return this.toInfo(tracked);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requested === "KILL") {
|
|
||||||
await signalProcessGroup(this.container, pid, "KILL");
|
|
||||||
tracked.status = "killed";
|
|
||||||
} else {
|
|
||||||
await signalProcessGroup(this.container, pid, "TERM");
|
|
||||||
const settled = await this.waitForExit(tracked, 5000);
|
|
||||||
if (settled) {
|
|
||||||
tracked.status = "stopped";
|
|
||||||
} else {
|
|
||||||
await signalProcessGroup(this.container, pid, "KILL");
|
|
||||||
tracked.status = "killed";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await this.refreshStatus(tracked);
|
|
||||||
return this.toInfo(tracked);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getProcessLogs(
|
|
||||||
id: string,
|
|
||||||
options?: {
|
|
||||||
readonly stream?: "stdout" | "stderr" | "combined";
|
|
||||||
readonly tail?: number;
|
|
||||||
}
|
|
||||||
): Promise<AgentOsSandboxProcessLogs> {
|
|
||||||
const tracked = this.processes.get(id);
|
|
||||||
if (!tracked) {
|
|
||||||
throw new OrbSandboxError({
|
|
||||||
message: `Unknown process ${id}`,
|
|
||||||
reason: "CommandFailed",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await this.refreshStatus(tracked);
|
|
||||||
const wantStdout = options?.stream !== "stderr";
|
|
||||||
const wantStderr = options?.stream !== "stdout";
|
|
||||||
const entries: {
|
|
||||||
data: string;
|
|
||||||
stream: "stdout" | "stderr";
|
|
||||||
timestampMs?: number;
|
|
||||||
}[] = [];
|
|
||||||
if (wantStdout) {
|
|
||||||
const out = await readContainerFile(this.container, tracked.outFile);
|
|
||||||
if (out.length > 0) {
|
|
||||||
entries.push({
|
|
||||||
data: out,
|
|
||||||
stream: "stdout",
|
|
||||||
timestampMs: tracked.startedAt,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (wantStderr) {
|
|
||||||
const err = await readContainerFile(this.container, tracked.errFile);
|
|
||||||
if (err.length > 0) {
|
|
||||||
entries.push({
|
|
||||||
data: err,
|
|
||||||
stream: "stderr",
|
|
||||||
timestampMs: tracked.startedAt,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const tail = options?.tail;
|
|
||||||
if (tail === undefined) {
|
|
||||||
return { entries };
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
entries: entries.map((entry) => ({
|
|
||||||
...entry,
|
|
||||||
data: entry.data.split("\n").slice(-tail).join("\n"),
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this, require-await -- throws intentionally; async satisfies the interface contract
|
|
||||||
async sendProcessInput(): Promise<unknown> {
|
|
||||||
throw new OrbSandboxError({
|
|
||||||
message:
|
|
||||||
"Interactive process input is not supported by the Docker sandbox",
|
|
||||||
reason: "CommandFailed",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async dispose(): Promise<void> {
|
|
||||||
for (const id of this.processes.keys()) {
|
|
||||||
const tracked = this.processes.get(id);
|
|
||||||
if (!tracked) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line no-await-in-loop -- each process is reconciled before the container is removed
|
|
||||||
await this.refreshStatus(tracked).catch(swallow);
|
|
||||||
if (tracked.status === "running") {
|
|
||||||
// eslint-disable-next-line no-await-in-loop -- sequential kill must settle before container removal
|
|
||||||
await this.killProcess(id).catch(swallow);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.processes.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
// Internal helpers
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
|
|
||||||
private async readPid(tracked: TrackedProcess): Promise<number | null> {
|
|
||||||
const raw = await readContainerFile(this.container, tracked.pidFile);
|
|
||||||
const pidText = raw.trim();
|
|
||||||
return /^\d+$/u.test(pidText) ? Number(pidText) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reconcile tracked status with the container: a recorded exit file means
|
|
||||||
* natural completion, otherwise the process group liveness decides.
|
|
||||||
*/
|
|
||||||
private async refreshStatus(tracked: TrackedProcess): Promise<void> {
|
|
||||||
const exitContents = await readContainerFile(
|
|
||||||
this.container,
|
|
||||||
tracked.exitFile
|
|
||||||
);
|
|
||||||
const exitRaw = exitContents.trim();
|
|
||||||
if (/^-?\d+$/u.test(exitRaw)) {
|
|
||||||
tracked.exitCode = Number(exitRaw);
|
|
||||||
if (tracked.status === "running") {
|
|
||||||
tracked.status = "exited";
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (tracked.status !== "running") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const pid = tracked.pid ?? (await this.readPid(tracked));
|
|
||||||
tracked.pid = pid;
|
|
||||||
if (pid === null) {
|
|
||||||
tracked.status = "failed";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const alive = await isProcessGroupAlive(this.container, pid);
|
|
||||||
if (alive) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
tracked.status = "exited";
|
|
||||||
tracked.exitCode = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async waitForExit(
|
|
||||||
tracked: TrackedProcess,
|
|
||||||
timeoutMs: number
|
|
||||||
): Promise<boolean> {
|
|
||||||
const deadline = Date.now() + timeoutMs;
|
|
||||||
while (Date.now() < deadline) {
|
|
||||||
// eslint-disable-next-line no-await-in-loop -- polling must probe sequentially
|
|
||||||
await sleep(250);
|
|
||||||
// eslint-disable-next-line no-await-in-loop -- polling must probe sequentially
|
|
||||||
await this.refreshStatus(tracked);
|
|
||||||
if (tracked.status !== "running") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this -- instance method kept for readability
|
|
||||||
private toInfo(tracked: TrackedProcess): AgentOsSandboxProcessInfo {
|
|
||||||
return {
|
|
||||||
args: tracked.args,
|
|
||||||
command: tracked.command,
|
|
||||||
exitCode: tracked.exitCode,
|
|
||||||
id: tracked.id,
|
|
||||||
pid: tracked.pid,
|
|
||||||
status: tracked.status,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -268,9 +268,9 @@ export class OrbHandle {
|
|||||||
// eslint-disable-next-line no-use-before-define -- module-level helper
|
// eslint-disable-next-line no-use-before-define -- module-level helper
|
||||||
const baseRef = `origin/${base}`;
|
const baseRef = `origin/${base}`;
|
||||||
// eslint-disable-next-line no-use-before-define -- module-level helper
|
// eslint-disable-next-line no-use-before-define -- module-level helper
|
||||||
const cloneCmd = `git clone --branch ${shellQuote(base)} --single-branch ${shellQuote(repoUrl)} /workspace/repository || git clone ${shellQuote(repoUrl)} /workspace/repository`;
|
const cloneCmd = `git clone --branch ${shellQuote(base)} --single-branch ${shellQuote(repoUrl)} /home/sandbox/repository || git clone ${shellQuote(repoUrl)} /home/sandbox/repository`;
|
||||||
// eslint-disable-next-line no-use-before-define -- module-level helper
|
// eslint-disable-next-line no-use-before-define -- module-level helper
|
||||||
const checkoutCmd = `cd /workspace/repository && git checkout -b ${shellQuote(branch)} ${shellQuote(baseRef)} 2>/dev/null || git checkout ${shellQuote(branch)} 2>/dev/null || true`;
|
const checkoutCmd = `cd /home/sandbox/repository && git checkout -b ${shellQuote(branch)} ${shellQuote(baseRef)} 2>/dev/null || git checkout ${shellQuote(branch)} 2>/dev/null || true`;
|
||||||
const result = yield* Effect.tryPromise({
|
const result = yield* Effect.tryPromise({
|
||||||
catch: (cause) =>
|
catch: (cause) =>
|
||||||
new OrbSandboxError({
|
new OrbSandboxError({
|
||||||
@@ -279,8 +279,9 @@ export class OrbHandle {
|
|||||||
}),
|
}),
|
||||||
try: () =>
|
try: () =>
|
||||||
client.runProcess({
|
client.runProcess({
|
||||||
command: `${cloneCmd} && ${checkoutCmd}`,
|
args: ["-c", `${cloneCmd} && ${checkoutCmd}`],
|
||||||
cwd: "/workspace",
|
command: "sh",
|
||||||
|
cwd: "/home/sandbox",
|
||||||
timeoutMs: 300_000,
|
timeoutMs: 300_000,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
@@ -301,8 +302,9 @@ export class OrbHandle {
|
|||||||
}),
|
}),
|
||||||
try: () =>
|
try: () =>
|
||||||
client.runProcess({
|
client.runProcess({
|
||||||
command: "mkdir -p /workspace/repository",
|
args: ["-c", "mkdir -p /home/sandbox/repository"],
|
||||||
cwd: "/workspace",
|
command: "sh",
|
||||||
|
cwd: "/home/sandbox",
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -383,6 +385,12 @@ export class OrbHandle {
|
|||||||
try: () => vm.writeFile(config.configPath, config.configJson),
|
try: () => vm.writeFile(config.configPath, config.configJson),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Restrict the config file containing the run-scoped gateway key.
|
||||||
|
yield* Effect.tryPromise({
|
||||||
|
catch: () => null, // eslint-disable-next-line no-empty-function -- best-effort hardening
|
||||||
|
try: () => vm.exec(`chmod 600 ${config.configPath}`),
|
||||||
|
}).pipe(Effect.ignore);
|
||||||
|
|
||||||
const agents = yield* Effect.tryPromise({
|
const agents = yield* Effect.tryPromise({
|
||||||
catch: (cause) =>
|
catch: (cause) =>
|
||||||
new OrbSessionError({
|
new OrbSessionError({
|
||||||
@@ -526,7 +534,8 @@ export class OrbHandle {
|
|||||||
}),
|
}),
|
||||||
try: () =>
|
try: () =>
|
||||||
client.runProcess({
|
client.runProcess({
|
||||||
command: input.command,
|
args: ["-c", input.command],
|
||||||
|
command: "sh",
|
||||||
...(input.cwd === undefined ? {} : { cwd: input.cwd }),
|
...(input.cwd === undefined ? {} : { cwd: input.cwd }),
|
||||||
...(input.env === undefined ? {} : { env: input.env }),
|
...(input.env === undefined ? {} : { env: input.env }),
|
||||||
...(input.timeoutMs === undefined
|
...(input.timeoutMs === undefined
|
||||||
@@ -677,12 +686,16 @@ export class OrbRuntime {
|
|||||||
}),
|
}),
|
||||||
try: () =>
|
try: () =>
|
||||||
AgentOs.create({
|
AgentOs.create({
|
||||||
|
database: {
|
||||||
|
path: `/tmp/${orbId}.db`,
|
||||||
|
type: "sqlite_file",
|
||||||
|
},
|
||||||
sandbox: {
|
sandbox: {
|
||||||
client: sandboxClient,
|
client: sandboxClient,
|
||||||
dispose: false,
|
dispose: false,
|
||||||
mountPath: "/mnt/sandbox",
|
mountPath: "/mnt/sandbox",
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
sandboxRoot: "/workspace",
|
sandboxRoot: "/home/sandbox",
|
||||||
},
|
},
|
||||||
software: [opencodePkg],
|
software: [opencodePkg],
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -101,32 +101,10 @@ const dockerVersion = async (): Promise<string | null> => {
|
|||||||
return trimmed.length > 0 ? trimmed : null;
|
return trimmed.length > 0 ? trimmed : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const containerExists = async (name: string): Promise<boolean> => {
|
|
||||||
const listing = await runCli([
|
|
||||||
"docker",
|
|
||||||
"ps",
|
|
||||||
"-a",
|
|
||||||
"--filter",
|
|
||||||
`name=^${name}$`,
|
|
||||||
"--format",
|
|
||||||
"{{.ID}}",
|
|
||||||
]);
|
|
||||||
return listing.trim().length > 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Verify a container is gone after disposal; returns true when removed. */
|
|
||||||
const verifyRemoved = async (name: string): Promise<boolean> => {
|
|
||||||
const present = await containerExists(name);
|
|
||||||
console.log(
|
|
||||||
` [verify] container ${name} ${present ? "STILL PRESENT" : "removed"}`
|
|
||||||
);
|
|
||||||
return !present;
|
|
||||||
};
|
|
||||||
|
|
||||||
const TINY_PROJECT: Record<string, string> = {
|
const TINY_PROJECT: Record<string, string> = {
|
||||||
"index.test.ts": `import { add } from "./index";\nimport { expect, test } from "bun:test";\n\ntest("add", () => {\n expect(add(1, 2)).toBe(3);\n});\n`,
|
"index.test.ts": `import { test } from "node:test";\nimport assert from "node:assert/strict";\nimport { add } from "./index.ts";\n\ntest("add", () => {\n assert.equal(add(1, 2), 3);\n});\n`,
|
||||||
"index.ts": `export function add(a: number, b: number): number {\n return a + b;\n}\n`,
|
"index.ts": `export function add(a: number, b: number): number {\n return a + b;\n}\n`,
|
||||||
"package.json": `{"name":"tiny","scripts":{"test":"bun test"}}\n`,
|
"package.json": `{"name":"tiny","type":"module","scripts":{"test":"node --test"}}\n`,
|
||||||
};
|
};
|
||||||
|
|
||||||
const prepareProject = async (hostWorkspace: string): Promise<void> => {
|
const prepareProject = async (hostWorkspace: string): Promise<void> => {
|
||||||
@@ -138,7 +116,7 @@ const prepareProject = async (hostWorkspace: string): Promise<void> => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// STAGE 1 — Docker sandbox lifecycle (standalone, no sidecar required)
|
// STAGE 1 — Docker sandbox via SandboxAgent (standalone, no sidecar required)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const stageDocker = async (image: string | undefined): Promise<boolean> => {
|
const stageDocker = async (image: string | undefined): Promise<boolean> => {
|
||||||
@@ -150,12 +128,11 @@ const stageDocker = async (image: string | undefined): Promise<boolean> => {
|
|||||||
}
|
}
|
||||||
console.log(`[docker] server version ${version}`);
|
console.log(`[docker] server version ${version}`);
|
||||||
|
|
||||||
const container = `orb-proof-docker-${Date.now()}`;
|
|
||||||
const hostWorkspace = `/tmp/orb-proof-docker-${Date.now()}`;
|
const hostWorkspace = `/tmp/orb-proof-docker-${Date.now()}`;
|
||||||
const options: DockerSandboxOptions =
|
const options: DockerSandboxOptions =
|
||||||
image === undefined
|
image === undefined
|
||||||
? { containerName: container, hostWorkspacePath: hostWorkspace }
|
? { hostWorkspacePath: hostWorkspace }
|
||||||
: { containerName: container, hostWorkspacePath: hostWorkspace, image };
|
: { hostWorkspacePath: hostWorkspace, image };
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await prepareProject(hostWorkspace);
|
await prepareProject(hostWorkspace);
|
||||||
@@ -164,25 +141,20 @@ const stageDocker = async (image: string | undefined): Promise<boolean> => {
|
|||||||
);
|
);
|
||||||
const client = await provider.start();
|
const client = await provider.start();
|
||||||
|
|
||||||
console.log("[docker] running bun install in sandbox...");
|
const { baseUrl } = client as unknown as { baseUrl: string };
|
||||||
const install = await client.runProcess({
|
console.log(`[docker] SandboxAgent baseUrl: ${baseUrl}`);
|
||||||
command: "bun install",
|
|
||||||
cwd: "/workspace/repository",
|
|
||||||
timeoutMs: 60_000,
|
|
||||||
});
|
|
||||||
console.log(` bun install exit: ${install.exitCode}`);
|
|
||||||
|
|
||||||
console.log("[docker] running bun test in sandbox...");
|
console.log("[docker] running npm install in sandbox...");
|
||||||
const test = await client.runProcess({
|
const install = await client.runProcess({
|
||||||
command: "bun test",
|
args: ["-c", "npm install"],
|
||||||
cwd: "/workspace/repository",
|
command: "sh",
|
||||||
|
cwd: "/home/sandbox/repository",
|
||||||
timeoutMs: 60_000,
|
timeoutMs: 60_000,
|
||||||
});
|
});
|
||||||
console.log(` bun test exit: ${test.exitCode}`);
|
console.log(` npm install exit: ${install.exitCode}`);
|
||||||
|
|
||||||
await provider.dispose();
|
await provider.dispose();
|
||||||
const removed = await verifyRemoved(container);
|
if (install.exitCode !== 0) {
|
||||||
if (install.exitCode !== 0 || test.exitCode !== 0 || !removed) {
|
|
||||||
console.log("[docker] sandbox lifecycle did not complete cleanly");
|
console.log("[docker] sandbox lifecycle did not complete cleanly");
|
||||||
console.log(STAGE.dockerBlocked);
|
console.log(STAGE.dockerBlocked);
|
||||||
return false;
|
return false;
|
||||||
@@ -193,8 +165,6 @@ const stageDocker = async (image: string | undefined): Promise<boolean> => {
|
|||||||
console.log(
|
console.log(
|
||||||
`[docker] stage failed: ${error instanceof Error ? error.message : String(error)}`
|
`[docker] stage failed: ${error instanceof Error ? error.message : String(error)}`
|
||||||
);
|
);
|
||||||
await runCli(["docker", "rm", "-f", container]);
|
|
||||||
await verifyRemoved(container);
|
|
||||||
console.log(STAGE.dockerBlocked);
|
console.log(STAGE.dockerBlocked);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -221,10 +191,10 @@ const stageAgentOs = async (
|
|||||||
context: {
|
context: {
|
||||||
artifacts: [],
|
artifacts: [],
|
||||||
contextFiles: [],
|
contextFiles: [],
|
||||||
issueBody: "Run bun test and report the result.",
|
issueBody: "Run npm test and report the result.",
|
||||||
issueTitle: "Proof: run tests",
|
issueTitle: "Proof: run tests",
|
||||||
},
|
},
|
||||||
docker: { containerName: container, hostWorkspacePath: hostWorkspace },
|
docker: { hostWorkspacePath: hostWorkspace },
|
||||||
gateway,
|
gateway,
|
||||||
identity: {
|
identity: {
|
||||||
projectId: "proof",
|
projectId: "proof",
|
||||||
@@ -243,7 +213,6 @@ const stageAgentOs = async (
|
|||||||
console.log(
|
console.log(
|
||||||
`[agentos] creation failed (${createResult.error.reason}): ${createResult.error.message}`
|
`[agentos] creation failed (${createResult.error.reason}): ${createResult.error.message}`
|
||||||
);
|
);
|
||||||
await verifyRemoved(container);
|
|
||||||
console.log(STAGE.agentosBlocked);
|
console.log(STAGE.agentosBlocked);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -278,7 +247,6 @@ const stageAgentOs = async (
|
|||||||
await Effect.runPromise(handle.dispose().pipe(Effect.ignore)).catch(() => {
|
await Effect.runPromise(handle.dispose().pipe(Effect.ignore)).catch(() => {
|
||||||
// best-effort cleanup
|
// best-effort cleanup
|
||||||
});
|
});
|
||||||
await verifyRemoved(container);
|
|
||||||
console.log(STAGE.agentosBlocked);
|
console.log(STAGE.agentosBlocked);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -366,10 +334,7 @@ const runProof = async (): Promise<number> => {
|
|||||||
// best-effort cleanup
|
// best-effort cleanup
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const removed = await verifyRemoved(orb.container);
|
console.log("[dispose] Orb disposed (SandboxAgent container auto-removed)");
|
||||||
if (!removed) {
|
|
||||||
console.log("[dispose] container removal could not be verified");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
|
|||||||
Reference in New Issue
Block a user