Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .cursor/environment.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"name": "opencode-plugin-compat",
"install": "#!/usr/bin/env bash\n# Idempotent Cloud Agent bootstrap. .cursor/environment.json install is this\n# file, so a build of main does not need this path to exist.\n# Installs Bun and the workspace tools needed for isolated install/build/test.\n# There is no OpenCode dual install and no live plugin path in this repository.\n\ninstall_main() {\n set -euo pipefail\n export PYTHONDONTWRITEBYTECODE=1\n export DEBIAN_FRONTEND=noninteractive\n\n if [[ -n \"${BASH_SOURCE[0]:-}\" && -f \"${BASH_SOURCE[0]}\" && \"$(basename \"$(dirname \"${BASH_SOURCE[0]}\")\")\" == \"scripts\" ]]; then\n ROOT=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")/..\" && pwd)\"\n else\n ROOT=\"$(pwd)\"\n fi\n cd \"$ROOT\"\n\n if [[ ! -x \"$HOME/.bun/bin/bun\" ]]; then\n curl -fsSL https://bun.sh/install | bash\n fi\n sudo ln -sf \"$HOME/.bun/bin/bun\" /usr/local/bin/bun\n # Workspace build/typecheck scripts call bunx. Bun selects that entry point\n # from argv[0], and scripts/isolated-check.sh mounts it by this name.\n sudo ln -sf \"$HOME/.bun/bin/bun\" /usr/local/bin/bunx\n\n if ! command -v zsh >/dev/null 2>&1; then\n sudo apt-get update\n sudo apt-get install -y zsh\n fi\n if ! command -v python3 >/dev/null 2>&1; then\n sudo apt-get update\n sudo apt-get install -y python3\n fi\n if ! command -v openssl >/dev/null 2>&1; then\n sudo apt-get update\n sudo apt-get install -y openssl\n fi\n\n /usr/local/bin/bun install --frozen-lockfile\n /usr/local/bin/bun run build\n # Workspace packages emit under packages/*/dist.\n if ! ls \"$ROOT\"/packages/*/dist/*.js >/dev/null 2>&1; then\n echo \"Missing packages/*/dist after build.\" >&2\n exit 1\n fi\n\n echo \"Bun $(/usr/local/bin/bun --version)\"\n}\n\nif [[ -z \"${BASH_SOURCE[0]:-}\" || \"${BASH_SOURCE[0]}\" == \"$0\" ]]; then\n install_main \"$@\"\nfi\n"
"install": "#!/usr/bin/env bash\n# Idempotent Cloud Agent bootstrap. .cursor/environment.json install is this\n# file, so a build of main does not need this path to exist.\n# Installs Bun and the workspace tools needed for isolated install/build/test.\n# There is no OpenCode dual install and no live plugin path in this repository.\n\ninstall_main() {\n set -euo pipefail\n export PYTHONDONTWRITEBYTECODE=1\n export DEBIAN_FRONTEND=noninteractive\n\n if [[ -n \"${BASH_SOURCE[0]:-}\" && -f \"${BASH_SOURCE[0]}\" && \"$(basename \"$(dirname \"${BASH_SOURCE[0]}\")\")\" == \"scripts\" ]]; then\n ROOT=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")/..\" && pwd)\"\n else\n ROOT=\"$(pwd)\"\n fi\n cd \"$ROOT\"\n\n if [[ ! -x \"$HOME/.bun/bin/bun\" ]]; then\n curl -fsSL https://bun.sh/install | bash\n fi\n # Non-interactive bash skips the installer's ~/.bashrc PATH edit, and the\n # isolate-run PATH is /opt/tools:/usr/bin:/bin:/usr/sbin. /usr/local/bin is\n # not on that PATH, and a symlink from there into $HOME is not mounted.\n # Copy the binary into /usr/bin, which both search, and point bunx at it.\n # Workspace build/typecheck scripts call bunx; Bun selects that entry from argv[0].\n sudo install -m 0755 \"$HOME/.bun/bin/bun\" /usr/bin/bun\n sudo ln -sfn bun /usr/bin/bunx\n sudo ln -sfn /usr/bin/bun /usr/local/bin/bun\n sudo ln -sfn /usr/bin/bun /usr/local/bin/bunx\n\n if ! command -v zsh >/dev/null 2>&1; then\n sudo apt-get update\n sudo apt-get install -y zsh\n fi\n if ! command -v python3 >/dev/null 2>&1; then\n sudo apt-get update\n sudo apt-get install -y python3\n fi\n if ! command -v openssl >/dev/null 2>&1; then\n sudo apt-get update\n sudo apt-get install -y openssl\n fi\n\n /usr/local/bin/bun install --frozen-lockfile\n /usr/local/bin/bun run build\n # Workspace packages emit under packages/*/dist.\n if ! ls \"$ROOT\"/packages/*/dist/*.js >/dev/null 2>&1; then\n echo \"Missing packages/*/dist after build.\" >&2\n exit 1\n fi\n\n echo \"Bun $(/usr/local/bin/bun --version)\"\n}\n\nif [[ -z \"${BASH_SOURCE[0]:-}\" || \"${BASH_SOURCE[0]}\" == \"$0\" ]]; then\n install_main \"$@\"\nfi\n"
}
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ The monorepo ships **one shared train version** across all eleven packages, so u

### Fixed

- Cloud Agent setup copies `bun` and `bunx` onto `/usr/bin`, so non-interactive shells and the isolated-check sandbox can run them without `~/.bashrc`.
- The isolated-check sandbox names the program it could not start.
- `ocp-dev` DSH local wiring generates Host Typert declarations before host `tsc`, so package `./remote` exports resolve and leftover empty package dirs no longer block a full reshim.
- `ocp-dev` DSH file-stage and profile sync include root `cordis-entry.js`, so Cordis can import `@opencode-compat/dsh-bridge` after a local reshim.

Expand Down
13 changes: 9 additions & 4 deletions scripts/cloud-agent-install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,15 @@ install_main() {
if [[ ! -x "$HOME/.bun/bin/bun" ]]; then
curl -fsSL https://bun.sh/install | bash
fi
sudo ln -sf "$HOME/.bun/bin/bun" /usr/local/bin/bun
# Workspace build/typecheck scripts call bunx. Bun selects that entry point
# from argv[0], and scripts/isolated-check.sh mounts it by this name.
sudo ln -sf "$HOME/.bun/bin/bun" /usr/local/bin/bunx
# Non-interactive bash skips the installer's ~/.bashrc PATH edit, and the
# isolate-run PATH is /opt/tools:/usr/bin:/bin:/usr/sbin. /usr/local/bin is
# not on that PATH, and a symlink from there into $HOME is not mounted.
# Copy the binary into /usr/bin, which both search, and point bunx at it.
# Workspace build/typecheck scripts call bunx; Bun selects that entry from argv[0].
sudo install -m 0755 "$HOME/.bun/bin/bun" /usr/bin/bun
sudo ln -sfn bun /usr/bin/bunx
sudo ln -sfn /usr/bin/bun /usr/local/bin/bun
sudo ln -sfn /usr/bin/bun /usr/local/bin/bunx

if ! command -v zsh >/dev/null 2>&1; then
sudo apt-get update
Expand Down
9 changes: 8 additions & 1 deletion scripts/isolate-run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,14 @@ if [[ -f /home/reviewer/env ]]; then
fi
set +a
cd /src
exec python3 -c 'import json, os; args = json.load(open("/argv.json")); os.execvp(args[0], args)'
exec python3 -c 'import json, os, sys
args = json.load(open("/argv.json"))
try:
os.execvp(args[0], args)
except OSError as error:
sys.stderr.write("cannot run %s: %s\n" % (args[0], error.strerror))
raise SystemExit(127)
'
EOF
chmod 0755 "$root/init" "$root/enter-command"
cat >"$root/drop-caps.py" <<'EOF'
Expand Down
46 changes: 40 additions & 6 deletions test/review-scripts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ describe("cloud agent install", () => {
}
expect(environment.install).toBe(script)
expect(environment.install).toContain("https://bun.sh/install")
expect(environment.install).toContain("/usr/bin/bunx")
expect(environment.install).toContain("/usr/local/bin/bunx")
expect(environment.install).toContain("bun install --frozen-lockfile")
expect(environment.install).toContain("bun run build")
Expand Down Expand Up @@ -205,16 +206,19 @@ describe("cloud agent install", () => {
const bin = path.join(directory, "bin")
const bunBin = path.join(directory, ".bun", "bin")
const localBin = path.join(directory, "local", "bin")
const usrBin = path.join(directory, "usr", "bin")
const workspace = path.join(directory, "workspace")
fs.mkdirSync(bin)
fs.mkdirSync(bunBin, { recursive: true })
fs.mkdirSync(localBin, { recursive: true })
fs.mkdirSync(usrBin, { recursive: true })
fs.mkdirSync(path.join(workspace, "scripts"), { recursive: true })
// Redirect the absolute Cloud Agent bun path into the private home so the
// test never writes under the real /usr/local/bin.
// Redirect the absolute Cloud Agent bun paths into the private home so the
// test never writes under the real /usr/bin or /usr/local/bin.
const script = fs
.readFileSync(path.join(root, "scripts/cloud-agent-install.sh"), "utf8")
.replaceAll("/usr/local/bin/bun", `${localBin}/bun`)
.replaceAll("/usr/bin/bun", `${usrBin}/bun`)
fs.writeFileSync(path.join(workspace, "scripts/cloud-agent-install.sh"), script)
fs.writeFileSync(
path.join(bunBin, "bun"),
Expand Down Expand Up @@ -245,6 +249,10 @@ if [ "$1" = "ln" ]; then
shift
exec ln "$@"
fi
if [ "$1" = "install" ]; then
shift
exec install "$@"
fi
exit 0
`,
)
Expand All @@ -265,10 +273,19 @@ exit 0
expect(fs.readFileSync(path.join(directory, "bun.log"), "utf8")).toContain("install --frozen-lockfile")
expect(fs.readFileSync(path.join(directory, "bun.log"), "utf8")).toContain("run build")
expect(fs.existsSync(path.join(workspace, "packages/profile/dist/index.js"))).toBe(true)
expect(fs.readFileSync(path.join(directory, "sudo.log"), "utf8")).toContain("ln -sf")
expect(fs.existsSync(path.join(localBin, "bun"))).toBe(true)
// Workspace build/typecheck scripts call bunx; the isolated check mounts it by that name.
expect(fs.realpathSync(path.join(localBin, "bunx"))).toBe(fs.realpathSync(path.join(bunBin, "bun")))
expect(fs.readFileSync(path.join(directory, "sudo.log"), "utf8")).toContain("install -m 0755")
expect(fs.readFileSync(path.join(directory, "sudo.log"), "utf8")).toContain("ln -sfn")
expect(fs.lstatSync(path.join(usrBin, "bun")).isSymbolicLink()).toBe(false)
expect(fs.readlinkSync(path.join(usrBin, "bunx"))).toBe("bun")
expect(fs.realpathSync(path.join(usrBin, "bunx"))).toBe(fs.realpathSync(path.join(usrBin, "bun")))
expect(fs.realpathSync(path.join(localBin, "bun"))).toBe(fs.realpathSync(path.join(usrBin, "bun")))
expect(fs.realpathSync(path.join(localBin, "bunx"))).toBe(fs.realpathSync(path.join(usrBin, "bun")))
const bash = fs.existsSync("/bin/bash") ? "/bin/bash" : "/usr/bin/bash"
const resolved = run(bash, ["--noprofile", "--norc", "-c", "command -v bun; command -v bunx"], {
env: { PATH: usrBin },
})
expect(resolved.status, resolved.stderr).toBe(0)
expect(resolved.stdout).toBe(`${usrBin}/bun\n${usrBin}/bunx\n`)
} finally {
fs.rmSync(directory, { recursive: true, force: true })
}
Expand All @@ -279,14 +296,17 @@ exit 0
const bin = path.join(directory, "bin")
const bunBin = path.join(directory, ".bun", "bin")
const localBin = path.join(directory, "local", "bin")
const usrBin = path.join(directory, "usr", "bin")
const workspace = path.join(directory, "workspace")
fs.mkdirSync(bin)
fs.mkdirSync(bunBin, { recursive: true })
fs.mkdirSync(localBin, { recursive: true })
fs.mkdirSync(usrBin, { recursive: true })
fs.mkdirSync(path.join(workspace, "scripts"), { recursive: true })
const script = fs
.readFileSync(path.join(root, "scripts/cloud-agent-install.sh"), "utf8")
.replaceAll("/usr/local/bin/bun", `${localBin}/bun`)
.replaceAll("/usr/bin/bun", `${usrBin}/bun`)
fs.writeFileSync(path.join(workspace, "scripts/cloud-agent-install.sh"), script)
fs.writeFileSync(
path.join(bunBin, "bun"),
Expand All @@ -300,6 +320,7 @@ exit 0
path.join(bin, "sudo"),
`#!/bin/sh
if [ "$1" = "ln" ]; then shift; exec ln "$@"; fi
if [ "$1" = "install" ]; then shift; exec install "$@"; fi
exit 0
`,
)
Expand Down Expand Up @@ -1097,6 +1118,19 @@ print("SAW=" + str(found))
fs.rmSync(base, { recursive: true, force: true })
})

it("names the program when the sandbox cannot exec it", () => {
const src = fs.mkdtempSync(path.join(os.tmpdir(), "review-missing-program-"))
try {
const result = run("bash", ["scripts/isolate-run.sh", "--src", src, "--", "not-a-real-program-a977"])
if (result.status === 2 && /ISOLATION=unavailable/.test(result.stderr)) return
expect(result.status, `${result.stdout}\n${result.stderr}`).toBe(127)
expect(result.stderr).toContain("cannot run not-a-real-program-a977:")
expect(result.stderr).not.toContain("Traceback")
} finally {
fs.rmSync(src, { recursive: true, force: true })
}
})

it("runs a command when no tools or environment names are passed", () => {
const src = fs.mkdtempSync(path.join(os.tmpdir(), "review-empty-arrays-"))
try {
Expand Down
Loading