@madarco/agentbox 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +89 -0
- package/README.md +161 -0
- package/dist/{_cloud-attach-ZXBCNWJX.js → _cloud-attach-O6NYTLES.js} +3 -3
- package/dist/{chunk-BXQMIEHC.js → chunk-2GPORKYF.js} +254 -162
- package/dist/chunk-2GPORKYF.js.map +1 -0
- package/dist/{chunk-NCJP5MTN.js → chunk-7UIAO7PC.js} +213 -51
- package/dist/chunk-7UIAO7PC.js.map +1 -0
- package/dist/{chunk-GU5LW4B5.js → chunk-R4O5WPHW.js} +374 -62
- package/dist/chunk-R4O5WPHW.js.map +1 -0
- package/dist/{dist-GDHP34ZK.js → dist-5FQGYRW5.js} +15 -3
- package/dist/dist-5FQGYRW5.js.map +1 -0
- package/dist/{dist-32EZBYG4.js → dist-BQNX7RQE.js} +12 -2
- package/dist/{dist-XML54CNB.js → dist-PZW3GWWU.js} +30 -5
- package/dist/dist-PZW3GWWU.js.map +1 -0
- package/dist/{dist-CX5CGVEB.js → dist-TMHSUVTP.js} +3 -3
- package/dist/index.js +1773 -526
- package/dist/index.js.map +1 -1
- package/package.json +9 -7
- package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +9 -8
- package/runtime/docker/packages/ctl/dist/bin.cjs +32 -3
- package/runtime/hetzner/agentbox-setup-skill.md +9 -8
- package/runtime/hetzner/ctl.cjs +32 -3
- package/runtime/relay/bin.cjs +32 -3
- package/runtime/vercel/agentbox-setup-skill.md +9 -8
- package/runtime/vercel/ctl.cjs +32 -3
- package/runtime/vercel/custom-system-CLAUDE.md +1 -4
- package/runtime/vercel/scripts/provision.sh +40 -0
- package/share/agentbox-setup/SKILL.md +9 -8
- package/dist/chunk-BXQMIEHC.js.map +0 -1
- package/dist/chunk-GU5LW4B5.js.map +0 -1
- package/dist/chunk-NCJP5MTN.js.map +0 -1
- package/dist/dist-GDHP34ZK.js.map +0 -1
- package/dist/dist-XML54CNB.js.map +0 -1
- /package/dist/{_cloud-attach-ZXBCNWJX.js.map → _cloud-attach-O6NYTLES.js.map} +0 -0
- /package/dist/{dist-32EZBYG4.js.map → dist-BQNX7RQE.js.map} +0 -0
- /package/dist/{dist-CX5CGVEB.js.map → dist-TMHSUVTP.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../packages/sandbox-hetzner/src/index.ts","../../../packages/sandbox-hetzner/src/backend.ts","../../../packages/sandbox-hetzner/src/cloud-init.ts","../../../packages/sandbox-hetzner/src/poll.ts","../../../packages/sandbox-hetzner/src/prepared-state.ts","../../../packages/sandbox-hetzner/src/prepare.ts","../../../packages/sandbox-hetzner/src/runtime-assets.ts","../../../packages/sandbox-hetzner/src/ssh-key.ts","../../../packages/sandbox-hetzner/src/ssh-cli.ts","../../../packages/sandbox-hetzner/src/ssh-tunnel.ts"],"sourcesContent":["/**\n * The Hetzner Cloud VPS sandbox provider. A thin `CloudBackend` over OpenSSH\n * + the Hetzner Cloud REST API, composed via `@agentbox/sandbox-cloud`'s\n * `createCloudProvider` for the provider-agnostic scaffolding (workspace\n * seeding, ctl launch, state, relay polling, VNC, checkpoints).\n *\n * **Phase 2 status:** the SDK shim (auth + REST client + retry + firewall\n * + egress IP) is wired; `hetznerBackend` is a stub whose per-method bodies\n * throw `notImplemented` until Phase 4 plumbs in SSH ControlMaster +\n * provisioning. The provider is registered so `--provider hetzner` resolves\n * cleanly and the build is honest about what's missing.\n */\n\nimport type { Provider } from '@agentbox/core';\nimport { createCloudProvider } from '@agentbox/sandbox-cloud';\nimport { hetznerBackend, HETZNER_DEFAULT_BOX_IMAGE_REF } from './backend.js';\nimport { prepareHetznerProvider } from './prepare.js';\n\nconst cloudProvider = createCloudProvider(hetznerBackend, {\n defaultResources: { cpu: 2, memory: 4, disk: 40 },\n});\n\nexport const hetznerProvider: Provider = {\n ...cloudProvider,\n prepare: prepareHetznerProvider,\n};\n\nexport { hetznerBackend, HETZNER_DEFAULT_BOX_IMAGE_REF };\nexport { ensureHetznerEnvLoaded } from './env-loader.js';\nexport {\n ensureHetznerCredentials,\n readHetznerCredStatus,\n secretsPath,\n maskKey,\n type EnsureHetznerCredentialsOptions,\n type HetznerCredStatus,\n} from './credentials.js';\nexport {\n ensureHetznerBaseSnapshot,\n prepareHetzner,\n prepareHetznerProvider,\n type PrepareHetznerOptions,\n type PrepareHetznerResult,\n} from './prepare.js';\nexport { generateBoxCloudInit, generatePrepareCloudInit, type BoxCloudInitOptions, type PrepareCloudInitOptions } from './cloud-init.js';\nexport {\n RUNTIME_ASSETS,\n candidatesFor,\n resolveRuntimeAssets,\n type ResolvedAsset,\n type RuntimeAsset,\n} from './runtime-assets.js';\nexport { mintPrepareKey, mintSshKey, type MintedSshKey } from './ssh-key.js';\nexport {\n scpDownload,\n scpUpload,\n sshExec,\n sshOptArgs,\n waitForSsh,\n type SshExecOptions,\n type SshExecResult,\n type SshTargetArgs,\n} from './ssh-cli.js';\nexport { pollUntil, type PollOptions } from './poll.js';\nexport {\n preparedStatePath,\n readPreparedState,\n writePreparedState,\n updatePreparedState,\n type PreparedBaseSnapshot,\n type PreparedHetznerState,\n} from './prepared-state.js';\nexport {\n makeHetznerClient,\n HetznerApiError,\n DEFAULT_HCLOUD_ENDPOINT,\n type CreateFirewallRequest,\n type CreateServerRequest,\n type HetznerAction,\n type HetznerClient,\n type HetznerFirewall,\n type HetznerFirewallRule,\n type HetznerImage,\n type HetznerServer,\n type HetznerServerStatus,\n type HetznerSshKey,\n} from './client.js';\nexport { detectEgressIp, type DetectEgressIpOptions } from './egress-ip.js';\nexport {\n createPerBoxFirewall,\n deletePerBoxFirewall,\n normalizeSourceCidr,\n sshOnlyInboundRule,\n syncFirewallSource,\n type CreateFirewallOptions,\n} from './firewall.js';\nexport { withHetznerRetry, isAttemptTimeout, isRetriable } from './retry.js';\n","/**\n * Hetzner `CloudBackend` — maps the provider-neutral cloud primitives onto\n * OpenSSH + the Hetzner Cloud REST API.\n *\n * Design rationale + the safety/tunneling model live in the plan at\n * `~/.claude/plans/how-to-safely-create-parallel-pebble.md` and the live\n * status doc at `docs/hertzner_backlog.md`. The short version:\n *\n * - 1:1 VPS-per-box. Each box gets a per-box ed25519 keypair (private key\n * never leaves the host) and a per-box Hetzner Cloud Firewall locked to\n * the host's egress IP.\n * - All comms (exec, file I/O, port forwards for bridge / web / VNC) flow\n * over one persistent `ssh` ControlMaster owned by `SshTunnelManager`.\n * - `previewUrl(port)` mints an `ssh -L 127.0.0.1:<localPort>:127.0.0.1:<remote>`\n * on demand. The cloud-provider scaffolding (`createCloudProvider`) then\n * decorates those URLs with Portless aliases for symmetric\n * `<box-name>.localhost` semantics — handled provider-side rather than\n * here so the backend stays focused on plumbing.\n * - Checkpoints map to Hetzner `create_image` snapshots; default no-pause,\n * opt-in pause via `createSnapshot({pause: true})`.\n */\n\nimport { existsSync } from 'node:fs';\nimport { rm, rename, mkdir } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { execa } from 'execa';\nimport { resolve as resolvePath } from 'node:path';\nimport type {\n CloudBackend,\n CloudExecResult,\n CloudFileEntry,\n CloudHandle,\n CloudPreviewUrl,\n CloudProvisionRequest,\n CloudSandboxSummary,\n CloudState,\n} from '@agentbox/core';\nimport {\n stageClaudeCredentialsForUpload,\n stageCodexCredentialsForUpload,\n stageOpencodeCredentialsForUpload,\n} from '@agentbox/sandbox-cloud';\nimport { generateBoxCloudInit } from './cloud-init.js';\nimport {\n HetznerApiError,\n makeHetznerClient,\n type HetznerClient,\n type HetznerImage,\n type HetznerServer,\n type HetznerServerStatus,\n} from './client.js';\nimport { detectEgressIp } from './egress-ip.js';\nimport {\n createPerBoxFirewall,\n deletePerBoxFirewall,\n normalizeSourceCidr,\n} from './firewall.js';\nimport { pollUntil } from './poll.js';\nimport { readPreparedState } from './prepared-state.js';\nimport { ensureHetznerBaseSnapshot } from './prepare.js';\nimport { mintSshKey } from './ssh-key.js';\nimport { waitForSsh, sshOptArgs, type SshTargetArgs } from './ssh-cli.js';\nimport { SshTunnelManager, defaultBoxSshDir } from './ssh-tunnel.js';\nimport { withHetznerRetry } from './retry.js';\n\nexport const HETZNER_DEFAULT_BOX_IMAGE_REF = 'agentbox-base';\n\n/**\n * The cloud-provider scaffolding defaults `req.image` to `'agentbox/box:dev'`\n * (the docker provider's local image tag) when nothing else is specified.\n * That value is meaningless on Hetzner — we recognize it (alongside our own\n * sentinel + plain undefined) as \"use the hetzner base snapshot.\"\n */\nconst SCAFFOLDING_FALLBACK_IMAGE = 'agentbox/box:dev';\nconst VPS_USER = 'vscode';\nconst PROVISION_SSH_DEADLINE_MS = 5 * 60_000;\nconst ACTION_DEADLINE_MS = 5 * 60_000;\nconst SNAPSHOT_DEADLINE_MS = 20 * 60_000;\n// `cx22` was deprecated by Hetzner in early 2026; `cx23` is the drop-in\n// replacement with the same 2 vCPU / 4 GB / 40 GB shape on x86.\nconst HETZNER_DEFAULT_SERVER_TYPE = 'cx23';\nconst HETZNER_DEFAULT_LOCATION = 'nbg1';\n\n/** Module-level tunnel manager — one ControlMaster per box for this process. */\nconst tunnels = new SshTunnelManager();\n\n/**\n * Map Hetzner's per-server status onto the four-value `CloudState` everyone\n * else consumes. Transitional ones ('starting', 'rebuilding', …) are reported\n * as 'running' so callers don't ping-pong; 'off' maps to 'paused' because\n * Hetzner's stop = power off (vs Daytona's archive).\n */\nfunction mapState(s: HetznerServerStatus | string | undefined): CloudState {\n switch (s) {\n case 'running':\n return 'running';\n case 'starting':\n case 'initializing':\n case 'stopping':\n case 'migrating':\n case 'rebuilding':\n return 'running';\n case 'off':\n return 'paused';\n case 'deleting':\n case 'unknown':\n default:\n return 'missing';\n }\n}\n\nfunction client(): HetznerClient {\n return makeHetznerClient();\n}\n\nasync function getServerStrict(id: number): Promise<HetznerServer> {\n const s = await client().getServer(id);\n if (!s) {\n throw new Error(`hetzner: server ${String(id)} not found (already destroyed?)`);\n }\n return s;\n}\n\n/** Lookup an image by description (\"snapshot name\" in user-facing terms). */\nasync function findImageByDescription(c: HetznerClient, description: string): Promise<HetznerImage | null> {\n const all = await c.listImages({ type: 'snapshot' });\n return all.find((i) => i.description === description) ?? null;\n}\n\n/**\n * Resolve a `CloudProvisionRequest.image|snapshot` into a Hetzner image id.\n * Precedence: `req.snapshot` → `req.image`:\n * - the sentinel `agentbox-base` → load id from `hetzner-prepared.json`.\n * - a numeric string → use as-is.\n * - any other string → treated as a snapshot description (checkpoint name)\n * and looked up via the API.\n */\nasync function resolveImageId(c: HetznerClient, req: CloudProvisionRequest): Promise<number | string> {\n const ref = req.snapshot ?? req.image;\n if (!ref || ref === HETZNER_DEFAULT_BOX_IMAGE_REF || ref === SCAFFOLDING_FALLBACK_IMAGE) {\n await ensureHetznerBaseSnapshot();\n const state = readPreparedState();\n if (!state.base) {\n throw new Error(\n 'no Hetzner base snapshot found — run `agentbox prepare --provider hetzner` to bake one.',\n );\n }\n return state.base.imageId;\n }\n if (/^\\d+$/.test(ref)) {\n return Number.parseInt(ref, 10);\n }\n // Try snapshot-by-description first (checkpoints use that), then fall through\n // to Hetzner stock images (the user passed e.g. `ubuntu-24.04`).\n const snap = await findImageByDescription(c, ref);\n if (snap) return snap.id;\n return ref;\n}\n\n/**\n * Capture per-box state we need across method calls. Lives on disk under\n * `~/.agentbox/hetzner/boxes/<sandboxId>/` and is the source of truth for\n * the SSH identity + firewall id + cached IP.\n */\ninterface PerBoxState {\n dir: string;\n identity: string;\n knownHosts: string;\n firewallId?: number;\n firewallSource?: string;\n vpsIp?: string;\n}\n\nfunction perBoxDir(sandboxId: string): string {\n return resolvePath(defaultBoxSshDir(sandboxId), '..');\n}\n\nasync function ensurePerBoxState(sandboxId: string): Promise<PerBoxState> {\n const dir = perBoxDir(sandboxId);\n const sshDir = join(dir, 'ssh');\n await mkdir(sshDir, { recursive: true, mode: 0o700 });\n return {\n dir,\n identity: join(sshDir, 'id_ed25519'),\n knownHosts: join(sshDir, 'known_hosts'),\n };\n}\n\nfunction bashScript(s: string): string {\n // Always run remote commands under bash -lc so /etc/profile.d/agentbox.sh\n // (and the PATH prepend / DISPLAY / AGENT_BROWSER_* it sets) get sourced.\n return `bash -lc ${shellQuote(s)}`;\n}\n\nfunction shellQuote(s: string): string {\n return `'${s.replace(/'/g, `'\\\\''`)}'`;\n}\n\nfunction buildSshTarget(state: PerBoxState, vpsIp: string, controlPath?: string): SshTargetArgs {\n return {\n host: vpsIp,\n user: VPS_USER,\n identity: state.identity,\n knownHosts: state.knownHosts,\n controlPath,\n };\n}\n\nasync function ensureTunnel(sandboxId: string, state: PerBoxState, vpsIp: string): Promise<void> {\n if (tunnels.has(sandboxId)) return;\n await tunnels.open({\n boxId: sandboxId,\n vpsHost: vpsIp,\n identity: state.identity,\n });\n}\n\n/**\n * Open the ControlMaster (if not already up) and return an SshTargetArgs\n * whose `controlPath` is set to the live master so exec/scp reuse it.\n */\nasync function ensureLiveTarget(sandboxId: string): Promise<{\n target: SshTargetArgs;\n state: PerBoxState;\n vpsIp: string;\n}> {\n const id = Number.parseInt(sandboxId, 10);\n if (!Number.isFinite(id)) {\n throw new Error(`hetzner: invalid sandboxId ${sandboxId}`);\n }\n const server = await getServerStrict(id);\n const vpsIp = server.public_net.ipv4?.ip;\n if (!vpsIp) {\n throw new Error(`hetzner: server ${String(id)} has no IPv4 address`);\n }\n const state = await ensurePerBoxState(sandboxId);\n if (!existsSync(state.identity)) {\n throw new Error(\n `hetzner: per-box SSH key missing for sandbox ${sandboxId} (expected at ${state.identity}). ` +\n `If this box was created by a different host, you'll need to re-create it on this host.`,\n );\n }\n await ensureTunnel(sandboxId, state, vpsIp);\n const controlPath = tunnels.controlPath(sandboxId);\n return { target: buildSshTarget(state, vpsIp, controlPath), state, vpsIp };\n}\n\nexport const hetznerBackend: CloudBackend = {\n name: 'hetzner',\n\n async provision(req: CloudProvisionRequest): Promise<CloudHandle> {\n const c = client();\n const onLog = req.onLog ?? (() => {});\n const progress = (s: string) => onLog(`hetzner: ${s}`);\n\n // 1. Gate on the base snapshot existing (lifts the Phase-2 placeholder).\n await ensureHetznerBaseSnapshot();\n const imageRef = await resolveImageId(c, req);\n\n // 2. Detect egress IP + normalize firewall source.\n const egressOverride =\n req.env?.AGENTBOX_HETZNER_FIREWALL_SOURCE ?? process.env.AGENTBOX_HETZNER_FIREWALL_SOURCE;\n const source = egressOverride\n ? normalizeSourceCidr(egressOverride)\n : `${await detectEgressIp({ onLog })}/32`;\n progress(`firewall source: ${source}`);\n\n // 3. Mint per-box SSH key into a temp dir keyed by a fresh uuid; we\n // rename it to `~/.agentbox/hetzner/boxes/<sandboxId>/ssh/` once the\n // server id is known.\n const stamp = Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 8);\n const tempDir = resolvePath(\n process.env.HOME ?? process.cwd(),\n '.agentbox',\n 'hetzner',\n `pending-${stamp}`,\n 'ssh',\n );\n const key = await mintSshKey(tempDir, `agentbox-box-${req.name}-${stamp}`);\n\n let firewallId: number | null = null;\n let serverId: number | null = null;\n try {\n // 4. Firewall.\n const firewall = await createPerBoxFirewall(c, {\n name: `agentbox-${req.name}-${stamp}`,\n sourceCidr: source,\n labels: {\n 'agentbox.box': req.name,\n 'agentbox.role': 'box',\n },\n });\n firewallId = firewall.id;\n\n // 5. Cloud-init for the box: vscode user, pubkey, /etc/hosts alias,\n // optional box.env passthrough from req.env.\n const boxEnv: Record<string, string> = {};\n for (const [k, v] of Object.entries(req.env ?? {})) {\n if (k.startsWith('AGENTBOX_')) boxEnv[k] = v;\n }\n const cloudInit = generateBoxCloudInit({\n sshPubkey: key.publicKey,\n boxName: req.name,\n boxEnv: Object.keys(boxEnv).length > 0 ? boxEnv : undefined,\n });\n\n // 6. Create the server.\n progress(`creating VPS '${req.name}' from image ${String(imageRef)} (cx22 / nbg1 unless overridden)`);\n const created = await withHetznerRetry(\n { method: 'createServer', retryOnAmbiguous: false, attemptTimeoutMs: 120_000 },\n () =>\n c.createServer({\n name: `agentbox-${req.name}-${stamp}`,\n server_type: HETZNER_DEFAULT_SERVER_TYPE,\n image: imageRef,\n location: HETZNER_DEFAULT_LOCATION,\n user_data: cloudInit,\n firewalls: [{ firewall: firewall.id }],\n labels: {\n 'agentbox.managed': 'true',\n 'agentbox.role': 'box',\n 'agentbox.box': req.name,\n 'agentbox.firewall': String(firewall.id),\n },\n start_after_create: true,\n }),\n );\n serverId = created.server.id;\n const vpsIp = created.server.public_net.ipv4?.ip;\n if (!vpsIp) {\n throw new Error(`hetzner: server ${String(serverId)} came up without an IPv4 address`);\n }\n progress(`server ${String(serverId)} provisioned at ${vpsIp}; waiting for ssh`);\n\n // 7. Move the freshly-minted key from its temp location into the\n // sandboxId-keyed final dir, then rm the temp parent. We move file-\n // by-file rather than renaming the whole dir because `ensurePerBoxState`\n // had to mkdir the final dir before we knew the sandbox id (so the\n // tunnel manager + state-restoration paths can find it later).\n const sandboxId = String(serverId);\n const state = await ensurePerBoxState(sandboxId);\n await rename(key.privatePath, state.identity);\n await rename(key.publicPath, `${state.identity}.pub`);\n // Drop the now-empty temp dir + its `pending-XXX` parent.\n await rm(key.dir, { recursive: true, force: true });\n const pendingParent = resolvePath(key.dir, '..');\n await rm(pendingParent, { recursive: true, force: true });\n\n // 8. Wait for sshd to accept the new key.\n const up = await waitForSsh(buildSshTarget(state, vpsIp), PROVISION_SSH_DEADLINE_MS);\n if (!up) {\n throw new Error(`hetzner: ssh on ${vpsIp} did not come up within ${String(PROVISION_SSH_DEADLINE_MS / 1000)}s`);\n }\n\n // 9. Open ControlMaster.\n await ensureTunnel(sandboxId, state, vpsIp);\n progress('ssh up; ControlMaster open');\n\n // 10. Push renewable agent credentials (.credentials.json for claude,\n // auth.json for codex/opencode) into /home/vscode/.agentbox-creds/<agent>/\n // via scp. Hetzner has no shared-volume primitive, so we can't reuse the\n // Daytona path (`seedAgentVolumesIfFresh` returns empty mounts for\n // backends without `ensureVolume`). The symlinks baked into the snapshot\n // by install-box.sh route ~/.claude/.credentials.json etc. through to\n // this dir, so the agents find their tokens at the expected paths.\n // Best-effort: a failure here means the in-box agent falls back to\n // interactive login (the current `claude --provider hetzner` failure\n // mode), so we log + continue rather than aborting create.\n const liveTarget = buildSshTarget(state, vpsIp, tunnels.controlPath(sandboxId));\n try {\n await pushHetznerAgentCredentials(liveTarget, onLog);\n } catch (credErr) {\n onLog(\n `hetzner: WARN — agent credential push failed (${credErr instanceof Error ? credErr.message : String(credErr)}); ` +\n `in-box claude/codex/opencode will prompt for interactive login`,\n );\n }\n\n return { sandboxId };\n } catch (err) {\n // Cleanup on failure: server + firewall + temp ssh dir.\n if (serverId !== null) {\n progress(`cleanup — deleting server ${String(serverId)} after provision failure`);\n try {\n await c.deleteServer(serverId);\n } catch (cleanupErr) {\n onLog(\n `hetzner: WARN — failed to delete server ${String(serverId)}; check the Hetzner dashboard manually. ${\n cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr)\n }`,\n );\n }\n }\n if (firewallId !== null) {\n try {\n await deletePerBoxFirewall(c, firewallId);\n } catch {\n // best-effort\n }\n }\n // Clean the temp ssh dir (or its renamed location if we got that far).\n try {\n if (existsSync(key.dir)) await rm(key.dir, { recursive: true, force: true });\n if (serverId !== null) {\n const finalDir = perBoxDir(String(serverId));\n if (existsSync(finalDir)) await rm(finalDir, { recursive: true, force: true });\n }\n } catch {\n // best-effort\n }\n throw err;\n }\n },\n\n async get(sandboxId: string): Promise<CloudHandle | null> {\n const id = Number.parseInt(sandboxId, 10);\n if (!Number.isFinite(id)) return null;\n const server = await client().getServer(id);\n return server ? { sandboxId } : null;\n },\n\n async list(): Promise<CloudSandboxSummary[]> {\n const servers = await client().listServers({ label_selector: 'agentbox.managed=true' });\n return servers.map((s) => ({\n sandboxId: String(s.id),\n name: s.labels['agentbox.box'] ?? s.name,\n createdAt: s.created,\n state: mapState(s.status),\n }));\n },\n\n async start(h: CloudHandle): Promise<void> {\n const id = Number.parseInt(h.sandboxId, 10);\n await client().powerOn(id);\n // Wait for ssh to come back so callers can immediately exec.\n await pollUntil(\n `server ${h.sandboxId} running`,\n async () => {\n const s = await client().getServer(id);\n return s?.status === 'running' ? s : null;\n },\n { deadlineMs: ACTION_DEADLINE_MS, intervalMs: 2_000, maxIntervalMs: 8_000 },\n );\n },\n\n async stop(h: CloudHandle): Promise<void> {\n const id = Number.parseInt(h.sandboxId, 10);\n // Try graceful shutdown first; fall back to power-off after a short wait.\n try {\n await client().shutdown(id);\n await pollUntil(\n `server ${h.sandboxId} off`,\n async () => {\n const s = await client().getServer(id);\n return s?.status === 'off' ? s : null;\n },\n { deadlineMs: 60_000, intervalMs: 2_000, maxIntervalMs: 8_000 },\n );\n } catch {\n await client().powerOff(id);\n }\n await tunnels.close(h.sandboxId);\n },\n\n async pause(h: CloudHandle): Promise<void> {\n // Hetzner has no archive primitive. Pause ≡ stop.\n await this.stop(h);\n },\n\n async resume(h: CloudHandle): Promise<void> {\n await this.start(h);\n },\n\n async destroy(h: CloudHandle): Promise<void> {\n const id = Number.parseInt(h.sandboxId, 10);\n await tunnels.close(h.sandboxId);\n // Discover the per-box firewall via labels so we don't need to\n // round-trip through the server's `firewalls[]` (which is absent from\n // our typed slice anyway).\n const c = client();\n let firewallId: number | undefined;\n try {\n const server = await c.getServer(id);\n firewallId = server\n ? Number.parseInt(server.labels['agentbox.firewall'] ?? '', 10)\n : undefined;\n } catch {\n // ignore — we'll still try to delete the server\n }\n try {\n await c.deleteServer(id);\n } catch (err) {\n if (!(err instanceof HetznerApiError && (err.statusCode === 404 || err.code === 'not_found'))) {\n throw err;\n }\n }\n if (firewallId && Number.isFinite(firewallId)) {\n await deletePerBoxFirewall(c, firewallId);\n }\n // Clean the per-box ssh dir.\n const dir = perBoxDir(h.sandboxId);\n try {\n await rm(dir, { recursive: true, force: true });\n } catch {\n // best-effort\n }\n },\n\n async state(h: CloudHandle): Promise<CloudState> {\n const id = Number.parseInt(h.sandboxId, 10);\n const s = await client().getServer(id);\n return s ? mapState(s.status) : 'missing';\n },\n\n async exec(h, cmd, opts): Promise<CloudExecResult> {\n const { target } = await ensureLiveTarget(h.sandboxId);\n const argv = [\n ...sshOptArgs(target),\n `${target.user}@${target.host}`,\n bashScript(opts?.cwd ? `cd ${shellQuote(opts.cwd)} && ${cmd}` : cmd),\n ];\n const res = await execa('ssh', argv, {\n reject: false,\n timeout: opts?.attemptTimeoutMs ?? 120_000,\n env: { ...process.env, ...(opts?.env ?? {}) },\n });\n return {\n exitCode: typeof res.exitCode === 'number' ? res.exitCode : 1,\n stdout: typeof res.stdout === 'string' ? res.stdout : '',\n stderr: typeof res.stderr === 'string' ? res.stderr : '',\n };\n },\n\n async uploadFile(h, localPath, remotePath): Promise<void> {\n const { target } = await ensureLiveTarget(h.sandboxId);\n const argv = [\n ...sshOptArgs(target),\n localPath,\n `${target.user}@${target.host}:${remotePath}`,\n ];\n const res = await execa('scp', argv, { reject: false, timeout: 300_000 });\n if (res.exitCode !== 0) {\n throw new Error(`hetzner: scp upload failed (exit ${String(res.exitCode)}): ${res.stderr || ''}`);\n }\n },\n\n async downloadFile(h, remotePath, localPath): Promise<void> {\n const { target } = await ensureLiveTarget(h.sandboxId);\n const argv = [\n ...sshOptArgs(target),\n `${target.user}@${target.host}:${remotePath}`,\n localPath,\n ];\n const res = await execa('scp', argv, { reject: false, timeout: 300_000 });\n if (res.exitCode !== 0) {\n throw new Error(`hetzner: scp download failed (exit ${String(res.exitCode)}): ${res.stderr || ''}`);\n }\n },\n\n async listFiles(h, remoteDir): Promise<CloudFileEntry[]> {\n const res = await this.exec(\n h,\n // -L for nicer dir-detection on symlinks; `--printf` is non-portable so\n // use a small awk wrap that prints `<name>\\t<d|f>` per entry.\n `find ${shellQuote(remoteDir)} -mindepth 1 -maxdepth 1 -printf '%f\\\\t%y\\\\n'`,\n );\n if (res.exitCode !== 0) return [];\n return res.stdout\n .split(/\\r?\\n/)\n .filter((line) => line.length > 0)\n .map((line) => {\n const [name, kind] = line.split('\\t');\n return { name: name ?? line, isDir: kind === 'd' };\n });\n },\n\n async previewUrl(h, port): Promise<CloudPreviewUrl> {\n const { state, vpsIp } = await ensureLiveTarget(h.sandboxId);\n void state;\n void vpsIp;\n const localPort = await tunnels.forward(h.sandboxId, port);\n // Plain loopback URL — no preview-token here (SSH local forward is\n // already auth-gated by the tunnel itself). The cloud-provider layer\n // adds the Portless alias for symmetric `<box-name>.localhost` URLs.\n return { url: `http://127.0.0.1:${String(localPort)}` };\n },\n\n async signedPreviewUrl(h, port, _ttl): Promise<CloudPreviewUrl> {\n // SSH tunnels have no signed-URL primitive — they're already loopback-\n // only and the cloud-provider layer's bridge-token enforces auth. The\n // signed form is functionally equivalent to the unsigned one here.\n void _ttl;\n return this.previewUrl(h, port);\n },\n\n async refreshPreviewUrl(h, port): Promise<CloudPreviewUrl> {\n // Tear down the (likely dead) ControlMaster + every cached `-L` forward\n // for this box and re-open from scratch. Called by the host\n // CloudBoxPoller when ECONNREFUSED on the local port shows that the\n // master died (host sleep/wake, transient network blip). Without this\n // the poller would back off forever against a stale localPort.\n const { state, vpsIp } = await ensureLiveTarget(h.sandboxId);\n void state;\n void vpsIp;\n await tunnels.refresh({\n boxId: h.sandboxId,\n vpsHost: vpsIp,\n identity: state.identity,\n });\n const localPort = await tunnels.forward(h.sandboxId, port);\n return { url: `http://127.0.0.1:${String(localPort)}` };\n },\n\n async startInBoxPortless(h, opts): Promise<void> {\n // Bring up a `portless` proxy *inside the VPS* mirroring the host's\n // mode so `<boxName>.localhost:<P>` resolves to the same content on\n // both sides. The `portless` CLI is baked into the base snapshot by\n // install-box.sh:316. Idempotent — `portless proxy start` exits 0 if a\n // proxy is already running on the port. Best-effort: a failure here\n // just means the in-box symmetric URL won't resolve; the host URL\n // still works.\n //\n // Run as root (vscode has NOPASSWD sudo): portless's :443/:80 TLS\n // proxy self-elevates to root anyway, and its state lands in\n // /root/.portless. A subsequent `portless alias` from vscode would\n // write to /home/vscode/.portless and the proxy wouldn't see it —\n // the two state dirs are disjoint. Using sudo for both keeps them\n // pointed at the same `/root/.portless`.\n const tlsFlag = opts.tls ? '' : '--no-tls';\n const startCmd = `sudo portless proxy start ${tlsFlag} -p ${String(opts.proxyPort)}`.replace(/\\s+/g, ' ');\n const aliasCmd = `sudo portless alias ${shellQuote(opts.boxName)} ${String(opts.webPort)}`;\n await this.exec(h, `${startCmd}; ${aliasCmd}`);\n },\n\n async attachArgv(h): Promise<string[]> {\n const { target } = await ensureLiveTarget(h.sandboxId);\n // Reuse the ControlMaster via `-S <sock>` — no new auth handshake, no\n // SSH-token mint pressure (unlike Daytona). Callers append `-t '<cmd>'`\n // or similar in the `buildAttach` helper.\n return [\n 'ssh',\n ...sshOptArgs(target),\n `${target.user}@${target.host}`,\n ];\n },\n\n async createSnapshot(h, name): Promise<void> {\n const id = Number.parseInt(h.sandboxId, 10);\n const c = client();\n const { image } = await withHetznerRetry(\n { method: 'createImage', retryOnAmbiguous: false, attemptTimeoutMs: 120_000 },\n () =>\n c.createImage(id, {\n type: 'snapshot',\n description: name,\n labels: { 'agentbox.role': 'ckpt', 'agentbox.box': h.sandboxId },\n }),\n );\n await pollUntil(\n `image ${String(image.id)} availability`,\n async () => {\n const img = await c.getImage(image.id);\n return img?.status === 'available' ? img : null;\n },\n { deadlineMs: SNAPSHOT_DEADLINE_MS, intervalMs: 3_000, maxIntervalMs: 10_000 },\n );\n },\n\n async deleteSnapshot(name): Promise<void> {\n const c = client();\n const img = await findImageByDescription(c, name);\n if (!img) return;\n try {\n await c.deleteImage(img.id);\n } catch (err) {\n if (err instanceof HetznerApiError && (err.statusCode === 404 || err.code === 'not_found')) return;\n throw err;\n }\n },\n};\n\n/** Exposed for the CLI's `firewall sync` / `show` subcommands. */\nexport { tunnels as _hetznerTunnels };\n\n/**\n * Push the host's renewable agent credentials (.credentials.json for claude,\n * auth.json for codex / opencode) into /home/vscode/.agentbox-creds/<agent>/\n * over the live ControlMaster. The base-snapshot symlinks route the agents'\n * expected paths through to this dir, so the in-box agents find their tokens\n * without prompting for login.\n *\n * Each stage helper returns `tarballPath: null` when there's nothing to push\n * (e.g. the user never logged in to that agent on the host) — we skip silently\n * in that case. Warnings (codex Keychain landmine, etc.) flow through `log`.\n */\nasync function pushHetznerAgentCredentials(\n target: SshTargetArgs,\n log: (line: string) => void,\n): Promise<void> {\n const specs: Array<{\n kind: 'claude' | 'codex' | 'opencode';\n stage: () => Promise<import('@agentbox/sandbox-cloud').StageResult>;\n dest: string;\n }> = [\n { kind: 'claude', stage: stageClaudeCredentialsForUpload, dest: '/home/vscode/.agentbox-creds/claude' },\n { kind: 'codex', stage: stageCodexCredentialsForUpload, dest: '/home/vscode/.agentbox-creds/codex' },\n { kind: 'opencode', stage: stageOpencodeCredentialsForUpload, dest: '/home/vscode/.agentbox-creds/opencode' },\n ];\n\n for (const spec of specs) {\n const staged = await spec.stage();\n for (const w of staged.warnings) log(`hetzner: [${spec.kind}-creds] ${w}`);\n try {\n if (!staged.tarballPath) {\n log(`hetzner: ${spec.kind}: no host credentials to push (skipping)`);\n continue;\n }\n const remote = `/tmp/agentbox-${spec.kind}-creds.tar.gz`;\n const argv = [\n ...sshOptArgs(target),\n staged.tarballPath,\n `${target.user}@${target.host}:${remote}`,\n ];\n const scpRes = await execa('scp', argv, { reject: false, timeout: 120_000 });\n if (scpRes.exitCode !== 0) {\n throw new Error(\n `scp ${spec.kind} credentials failed (exit ${String(scpRes.exitCode)}): ${scpRes.stderr ?? ''}`,\n );\n }\n // Extract as vscode into the dest dir (which already exists as a\n // chown'd dir from install-box.sh's credential-pivot step).\n const extract = await execa(\n 'ssh',\n [\n ...sshOptArgs(target),\n `${target.user}@${target.host}`,\n `sudo -u vscode mkdir -p ${spec.dest} && sudo -u vscode tar -xzf ${remote} -C ${spec.dest} --no-same-permissions --no-same-owner -m && rm -f ${remote}`,\n ],\n { reject: false, timeout: 30_000 },\n );\n if (extract.exitCode !== 0) {\n throw new Error(\n `${spec.kind} credential extract failed (exit ${String(extract.exitCode)}): ${extract.stderr ?? ''}`,\n );\n }\n log(`hetzner: ${spec.kind}: credentials pushed`);\n } finally {\n await staged.cleanup();\n }\n }\n}\n","/**\n * Cloud-init `#cloud-config` user-data generators.\n *\n * Two flavors:\n * - **prepare** (`generatePrepareCloudInit`): for the temporary VPS that\n * `prepareHetzner()` boots to bake the base snapshot. Just injects a\n * `root` SSH key — the install script does everything else over ssh.\n * - **box** (`generateBoxCloudInit`): for per-box VPSes provisioned from\n * the base snapshot. Injects the per-box ssh pubkey for `vscode`,\n * optionally writes `/etc/hosts` aliases and `box.env`.\n *\n * The cloud-init format is documented at https://cloudinit.readthedocs.io.\n * We emit it as a small hand-rolled YAML doc — using a full yaml lib would\n * be overkill for the handful of fields we touch (and pulls in the same\n * `yaml` dep `sandbox-cloud` already has, but we keep this package's dep\n * surface minimal).\n */\n\nexport interface PrepareCloudInitOptions {\n /** ed25519/rsa public key string (one line, OpenSSH format). */\n sshPubkey: string;\n}\n\n/**\n * Cloud-init for the temporary prepare VPS. We log in as `root` here (it's a\n * throwaway VPS that lives ~10–15 min) and run `install-box.sh` over ssh.\n * The install script then creates the `vscode` user, installs everything,\n * and writes the sshd hardening drop-in that disables root login — which\n * doesn't take effect until we reload sshd at the end of the script (the\n * orchestrator finishes its scp/ssh dance before that point).\n */\nexport function generatePrepareCloudInit(opts: PrepareCloudInitOptions): string {\n const pubkey = opts.sshPubkey.trim();\n return [\n '#cloud-config',\n '# AgentBox temporary prepare VPS — used by `agentbox prepare --provider hetzner`',\n '# to bake the base snapshot. SSH key is single-use and discarded on VPS destroy.',\n 'disable_root: false',\n 'ssh_pwauth: false',\n // Hetzner's Ubuntu 24.04 stock image enforces a first-login password\n // change for root. With key-based auth that path can't run (no TTY),\n // so sshd refuses with \"Password change required but no TTY available.\"\n // Telling cloud-init to NOT expire passwords + clearing root's expiry\n // via `passwd -d` removes the gate. Belt-and-braces: the chpasswd block\n // covers cloud-init's own users-and-groups run; the runcmd covers the\n // case where the image's pre-baked expiry survives cloud-init.\n 'chpasswd:',\n ' expire: false',\n 'users:',\n ' - name: root',\n ' lock_passwd: false',\n ' ssh_authorized_keys:',\n ` - ${yamlScalar(pubkey)}`,\n 'runcmd:',\n ' - [ passwd, -d, root ]',\n ' - [ chage, -E, \"-1\", -I, \"-1\", -M, \"99999\", root ]',\n ' - [ bash, -lc, \"echo agentbox-prepare-ready\" ]',\n '',\n ].join('\\n');\n}\n\nexport interface BoxCloudInitOptions {\n /** ed25519/rsa public key string (one line, OpenSSH format). */\n sshPubkey: string;\n /**\n * Box name. Used to write a `<boxName>.localhost → 127.0.0.1` /etc/hosts\n * entry so non-browser in-box clients (curl, fetch in Node) can hit the\n * symmetric Portless URL the host browser sees.\n */\n boxName: string;\n /**\n * Lines for `/etc/agentbox/box.env` — set as `KEY=VALUE` pairs. Cloud-init\n * `write_files` writes them as-is; no shell escaping is applied because\n * the file is sourced via `set -a; . /etc/agentbox/box.env; set +a`.\n */\n boxEnv?: Record<string, string>;\n}\n\n/**\n * Cloud-init for per-box VPSes provisioned from the base snapshot. The\n * base snapshot already has the `vscode` user, sshd hardening, agentbox-ctl,\n * etc. — this just injects the per-box key and any per-box config.\n */\nexport function generateBoxCloudInit(opts: BoxCloudInitOptions): string {\n const pubkey = opts.sshPubkey.trim();\n const lines: string[] = [\n '#cloud-config',\n `# AgentBox per-box VPS — box '${opts.boxName}'`,\n 'disable_root: true',\n 'ssh_pwauth: false',\n // Same first-login expiry guard as the prepare cloud-init — keeps\n // Hetzner's Ubuntu hardening from blocking our key-based vscode login.\n 'chpasswd:',\n ' expire: false',\n 'users:',\n ' - name: vscode',\n ' lock_passwd: false',\n ' sudo: ALL=(ALL) NOPASSWD:ALL',\n ' ssh_authorized_keys:',\n ` - ${yamlScalar(pubkey)}`,\n ];\n\n const writeFiles: string[] = [\n ' path: /etc/hosts',\n ' append: true',\n ` content: \"127.0.0.1 ${opts.boxName}.localhost\\\\n\"`,\n ];\n lines.push('write_files:');\n lines.push(' - ' + writeFiles[0]);\n for (let i = 1; i < writeFiles.length; i++) {\n lines.push(' ' + writeFiles[i]);\n }\n\n if (opts.boxEnv && Object.keys(opts.boxEnv).length > 0) {\n const envContent = Object.entries(opts.boxEnv)\n .map(([k, v]) => `${k}=${v}`)\n .join('\\\\n') + '\\\\n';\n lines.push(' - path: /etc/agentbox/box.env');\n lines.push(' permissions: \"0644\"');\n lines.push(` content: \"${envContent}\"`);\n }\n\n lines.push('');\n return lines.join('\\n');\n}\n\n/**\n * Quote a string as a YAML scalar. ssh pubkeys may contain spaces and `+`\n * characters; the safe move is to wrap in double quotes and escape `\"` /\n * `\\` if they appear (they don't for ed25519/rsa). Returns the value\n * already-quoted (so the caller embeds it after the `- ` prefix).\n */\nfunction yamlScalar(value: string): string {\n // No `\"` or `\\` in a valid OpenSSH pubkey, so a bare double-quote wrap is\n // safe; we still guard so a future caller doesn't surprise us.\n if (/[\"\\\\]/.test(value)) {\n return JSON.stringify(value);\n }\n return `\"${value}\"`;\n}\n","/**\n * Polling helpers — wait for a Hetzner Cloud resource to reach a desired\n * state. Reused across the prepare flow + Phase 4 backend lifecycle.\n *\n * Bounded by `deadlineMs`; intervals grow modestly (1s → 2s → 4s, capped)\n * so a fast-completing action is observed quickly without hammering the\n * API for slow ones.\n */\n\nexport interface PollOptions {\n deadlineMs?: number;\n /** Starting interval; doubles per attempt up to `maxIntervalMs`. */\n intervalMs?: number;\n maxIntervalMs?: number;\n /** Optional logger for each poll attempt — useful to stream progress. */\n onPoll?: (line: string) => void;\n}\n\n/**\n * Poll `check()` until it returns a non-null value or `deadlineMs` elapses.\n * Throws on timeout with the supplied `label` in the message.\n */\nexport async function pollUntil<T>(\n label: string,\n check: () => Promise<T | null | undefined>,\n opts: PollOptions = {},\n): Promise<T> {\n const deadline = Date.now() + (opts.deadlineMs ?? 5 * 60_000);\n const max = opts.maxIntervalMs ?? 10_000;\n let interval = opts.intervalMs ?? 1_000;\n let attempt = 0;\n while (true) {\n attempt += 1;\n const out = await check();\n if (out !== null && out !== undefined) return out;\n if (Date.now() >= deadline) {\n throw new Error(`hetzner: timed out waiting for ${label} after ${String(attempt)} attempts`);\n }\n opts.onPoll?.(`${label}: not ready yet (attempt ${String(attempt)}); polling again in ${String(interval)}ms`);\n await new Promise((r) => setTimeout(r, interval));\n interval = Math.min(interval * 2, max);\n }\n}\n","/**\n * Persisted record of what `agentbox prepare --provider hetzner` has built.\n * Lives at `~/.agentbox/hetzner-prepared.json` so the auto-prepare gate\n * (`ensureHetznerBaseSnapshot()`) and runtime image resolution can see it.\n *\n * Only the shared `base` snapshot is recorded here — built once per Hetzner\n * project / API token: Ubuntu + deps + agentbox-ctl + agents + agent-browser,\n * baked from `install-box.sh`.\n *\n * The per-project snapshot tier is NOT a separate registry: it's the existing\n * `agentbox checkpoint create --set-default` + `box.defaultCheckpointHetzner`\n * flow (see `docs/cloud-create-flow.md` §\"base vs project snapshot\"), and\n * auto-capture at the end of setup is driven by the `/agentbox-setup` skill\n * (`agentbox-ctl checkpoint --set-default`), cross-provider. So there's no\n * `projects[<hash>]` map here.\n *\n * Schema versioned so future shape changes can migrate.\n */\n\nimport { preparedStatePathFor, readPreparedStateRaw, writePreparedStateRaw } from '@agentbox/sandbox-core';\n\n/**\n * Schema history:\n * 1 — `base.imageId`, `base.description`, `base.createdAt`,\n * `base.installScriptSha256?`\n * 2 — `base.installScriptSha256` → `base.contextSha256` (now covers every\n * asset we scp'd in, not just the install script); `base.cliVersion`\n * and `base.cliCommit?` added so we can warn when an old snapshot\n * predates the running CLI.\n *\n * Read-time migration is lossy in one direction: a schema-1 file is lifted\n * to schema 2 by *renaming* `installScriptSha256` to `contextSha256`. The\n * hash doesn't change but the meaning narrows (install script only → full\n * asset list), so the next `agentbox prepare --provider hetzner` run will\n * recompute and overwrite. A legacy `projects` key (an early, never-wired\n * per-project tier) is simply ignored — removing the field doesn't break\n * reads, so no schema bump is needed.\n */\nconst SCHEMA = 2 as const;\n\nexport interface PreparedBaseSnapshot {\n /** Hetzner image id (numeric — opaque, but stable across `getImage` calls). */\n imageId: number;\n /** User-facing description (matches the firewall-dashboard rows). */\n description: string;\n /** ISO timestamp of bake-completion. */\n createdAt: string;\n /**\n * Deterministic SHA-256 of the build context (every file scp'd into the\n * prepare VPS). Rebuild when it changes.\n */\n contextSha256?: string;\n /** CLI version that produced this snapshot (informational). */\n cliVersion?: string;\n /** Git short SHA of the CLI build (informational). */\n cliCommit?: string;\n}\n\nexport interface PreparedHetznerState {\n schema: typeof SCHEMA;\n /** The shared base snapshot. Absent until first `agentbox prepare`. */\n base?: PreparedBaseSnapshot;\n}\n\ninterface LegacyV1Base {\n imageId: number;\n description: string;\n createdAt: string;\n installScriptSha256?: string;\n}\n\ninterface LegacyV1State {\n schema: 1;\n base?: LegacyV1Base;\n}\n\nexport function preparedStatePath(): string {\n return preparedStatePathFor('hetzner');\n}\n\nexport function readPreparedState(): PreparedHetznerState {\n const raw = readPreparedStateRaw('hetzner');\n if (raw === null || typeof raw !== 'object') return { schema: SCHEMA };\n const parsed = raw as Partial<PreparedHetznerState> | LegacyV1State;\n if ((parsed as { schema?: unknown }).schema === 1) {\n const v1 = parsed as LegacyV1State;\n return migrateFromV1(v1);\n }\n if (parsed.schema !== SCHEMA) {\n // Unknown schema: don't crash, just refuse to read — the file will be\n // overwritten on the next successful prepare.\n return { schema: SCHEMA };\n }\n return {\n schema: SCHEMA,\n base: parsed.base,\n };\n}\n\nfunction migrateFromV1(v1: LegacyV1State): PreparedHetznerState {\n // The v1 `installScriptSha256` covered only `install-box.sh`, not the full\n // asset list a v2 `contextSha256` represents. Lifting it forward as a\n // placeholder fingerprint means the next prepare run will mismatch and\n // rebuild — exactly what we want, since the broader hash semantics also\n // changed.\n const base: PreparedBaseSnapshot | undefined = v1.base\n ? {\n imageId: v1.base.imageId,\n description: v1.base.description,\n createdAt: v1.base.createdAt,\n contextSha256: v1.base.installScriptSha256,\n }\n : undefined;\n return {\n schema: SCHEMA,\n base,\n };\n}\n\nexport function writePreparedState(state: PreparedHetznerState): void {\n writePreparedStateRaw('hetzner', state);\n}\n\n/**\n * Convenience helper: update one field of the state without forcing callers\n * to read/merge/write themselves.\n */\nexport function updatePreparedState(mutate: (s: PreparedHetznerState) => void): void {\n const s = readPreparedState();\n mutate(s);\n writePreparedState(s);\n}\n","/**\n * `agentbox prepare --provider hetzner` — bake the per-org Hetzner base\n * snapshot. Mirrors the user request: \"for `agentbox prepare` (and first\n * time hetzner is used) start a VM and set it up and snapshot as base image\n * (there's no way to start a VPS from an existing dockerfile).\"\n *\n * Flow:\n * 1. Mint an ephemeral SSH keypair under\n * ~/.agentbox/hetzner/prepare-<ts>/.\n * 2. Detect the host's egress IP and create a firewall locked to it,\n * named `agentbox-prepare-<ts>`.\n * 3. Create a temp VPS (Ubuntu 24.04, `cx22` default) with cloud-init\n * injecting the pubkey for `root`.\n * 4. Poll until cloud-init + sshd come up.\n * 5. scp the runtime assets (install script + agentbox-ctl + helpers +\n * baked config files) into /tmp.\n * 6. Run `bash /tmp/agentbox-install.sh` over ssh; stream stdout to the\n * prepare log via the `onLog` callback.\n * 7. `create_image` snapshot of the VPS; poll until `available`.\n * 8. Delete the VPS + firewall.\n * 9. Persist the snapshot id into `~/.agentbox/hetzner-prepared.json`.\n *\n * Failure-mode discipline: each major step is wrapped in try/catch so the\n * temp VPS + firewall are *always* cleaned up on failure (the user must\n * never end up with a forgotten €4/mo VPS due to a prepare error).\n *\n * The user requested noisy logging — every BEGIN/END marker from the\n * install script is forwarded verbatim into the prepare log, plus our own\n * step boundaries from `progress()`.\n */\n\nimport { join } from 'node:path';\nimport type { Provider } from '@agentbox/core';\nimport { computeContextSha256, readCliStamp } from '@agentbox/sandbox-core';\nimport {\n stageClaudeStaticForUpload,\n stageCodexStaticForUpload,\n stageOpencodeStaticForUpload,\n type StageResult,\n} from '@agentbox/sandbox-cloud';\nimport { ensureHetznerCredentials } from './credentials.js';\nimport { detectEgressIp } from './egress-ip.js';\nimport {\n createPerBoxFirewall,\n deletePerBoxFirewall,\n normalizeSourceCidr,\n} from './firewall.js';\nimport { makeHetznerClient } from './client.js';\nimport { generatePrepareCloudInit } from './cloud-init.js';\nimport {\n preparedStatePath,\n readPreparedState,\n writePreparedState,\n} from './prepared-state.js';\nimport { pollUntil } from './poll.js';\nimport {\n findStagedCliRuntimeRoot,\n resolveRuntimeAssets,\n type ResolvedAsset,\n} from './runtime-assets.js';\nimport { mintPrepareKey } from './ssh-key.js';\nimport {\n scpUpload,\n sshExec,\n waitForSsh,\n type SshTargetArgs,\n} from './ssh-cli.js';\n\nexport interface PrepareHetznerOptions {\n name?: string;\n hostWorkspace?: string;\n /** Force re-bake even when `~/.agentbox/hetzner-prepared.json` has a usable base. */\n force?: boolean;\n /** Hetzner location (defaults to `nbg1`). */\n location?: string;\n /** Server type (defaults to `cx22` — 2 vCPU / 4 GB / 40 GB / ~€4/mo). */\n serverType?: string;\n /**\n * Override the firewall source CIDR. Defaults to auto-detected egress IP\n * via `detectEgressIp()` (with `/32` appended). Pass `'0.0.0.0/0'` for\n * the explicit-open opt-in. Passing a bare IP appends `/32` automatically.\n */\n firewallSource?: string;\n /** CLI runtime tree (set by the CLI to its dist neighbor). */\n cliRuntimeRoot?: string;\n /** Repo root for the dev fallback (defaults to `process.cwd()` walk). */\n repoRoot?: string;\n onLog?: (line: string) => void;\n}\n\nexport interface PrepareHetznerResult {\n snapshotName?: string;\n /** Hetzner image id (numeric) — also recorded in hetzner-prepared.json. */\n imageId?: number;\n}\n\n// `cx22` was deprecated by Hetzner in early 2026; `cx23` is the drop-in\n// replacement with the same 2 vCPU / 4 GB / 40 GB shape on x86. Users can\n// still override via `prepareHetzner({serverType: ...})`.\nconst TEMP_SERVER_TYPE_DEFAULT = 'cx23';\nconst TEMP_SERVER_LOCATION_DEFAULT = 'nbg1';\nconst PREPARE_SSH_DEADLINE_MS = 5 * 60_000;\nconst INSTALL_SCRIPT_TIMEOUT_MS = 30 * 60_000;\nconst SNAPSHOT_DEADLINE_MS = 20 * 60_000;\n\n/**\n * Bake the per-org Hetzner base snapshot. Resolves only after the image is\n * `available` and the temp VPS + firewall are gone. Persists `{base.imageId,\n * base.description, base.createdAt, base.installScriptSha256}` into\n * `~/.agentbox/hetzner-prepared.json`.\n */\nexport async function prepareHetzner(\n opts: PrepareHetznerOptions = {},\n): Promise<PrepareHetznerResult> {\n await ensureHetznerCredentials();\n const client = makeHetznerClient();\n const log = opts.onLog ?? (() => {});\n const progress = (step: string) => log(`prepare-hetzner: ${step}`);\n\n // Skip-fast: if a base snapshot is already recorded *and* its image is\n // still on Hetzner *and* the build-context fingerprint hasn't changed *and*\n // --force was not passed, return the existing record.\n const existingState = readPreparedState();\n // Prefer an explicit override; otherwise auto-detect the published-CLI\n // staged runtime tree by inspecting where this module was loaded from.\n const assets = resolveRuntimeAssets({\n cliRuntimeRoot: opts.cliRuntimeRoot ?? findStagedCliRuntimeRoot(),\n repoRoot: opts.repoRoot,\n });\n // Fingerprint = hash of every asset we scp into the prepare VPS. Keyed on\n // logical name (stable across staged-vs-monorepo layouts) so two CLIs with\n // the same staged tree produce the same hash.\n const contextSha = await computeContextSha256(\n assets.map((a) => ({ rel: a.name, abs: a.localPath })),\n );\n\n if (!opts.force && existingState.base) {\n const remote = await client\n .getImage(existingState.base.imageId)\n .catch(() => null);\n if (remote && existingState.base.contextSha256 === contextSha) {\n progress(\n `base snapshot ${String(existingState.base.imageId)} already exists (fingerprint ${contextSha.slice(0, 12)} matches); skipping rebuild (pass --force to override)`,\n );\n return {\n snapshotName: existingState.base.description,\n imageId: existingState.base.imageId,\n };\n }\n if (!remote) {\n progress(`recorded base snapshot ${String(existingState.base.imageId)} is gone on Hetzner; rebuilding`);\n } else {\n progress(\n `build context changed (was ${existingState.base.contextSha256?.slice(0, 12) ?? '<none>'}, now ${contextSha.slice(0, 12)}); rebuilding base snapshot`,\n );\n }\n }\n\n // 1. Mint ephemeral key + detect egress IP in parallel.\n progress('minting ephemeral ssh key');\n const key = await mintPrepareKey();\n let firewallId: number | null = null;\n let serverId: number | null = null;\n try {\n progress('detecting host egress IP');\n const source = opts.firewallSource\n ? normalizeSourceCidr(opts.firewallSource)\n : `${await detectEgressIp({ onLog: log })}/32`;\n\n // 2. Create per-prepare firewall.\n const stamp = Date.now().toString(36);\n const firewallName = `agentbox-prepare-${stamp}`;\n progress(`creating firewall ${firewallName} (source ${source})`);\n const firewall = await createPerBoxFirewall(client, {\n name: firewallName,\n sourceCidr: source,\n labels: { 'agentbox.role': 'prepare' },\n });\n firewallId = firewall.id;\n\n // 3. Create temp VPS.\n const serverName = `agentbox-prepare-${stamp}`;\n const cloudInit = generatePrepareCloudInit({ sshPubkey: key.publicKey });\n progress(`creating temp VPS ${serverName} (${opts.serverType ?? TEMP_SERVER_TYPE_DEFAULT} / ${opts.location ?? TEMP_SERVER_LOCATION_DEFAULT})`);\n const created = await client.createServer({\n name: serverName,\n server_type: opts.serverType ?? TEMP_SERVER_TYPE_DEFAULT,\n image: 'ubuntu-24.04',\n location: opts.location ?? TEMP_SERVER_LOCATION_DEFAULT,\n user_data: cloudInit,\n firewalls: [{ firewall: firewall.id }],\n labels: { 'agentbox.managed': 'true', 'agentbox.role': 'prepare' },\n start_after_create: true,\n });\n serverId = created.server.id;\n const ip = created.server.public_net.ipv4?.ip;\n if (!ip) {\n throw new Error('hetzner: temp VPS came up without an IPv4 address');\n }\n\n // 4. Wait for sshd.\n progress(`waiting for ssh on ${ip} (deadline ${String(PREPARE_SSH_DEADLINE_MS / 1000)}s)`);\n const sshTarget: SshTargetArgs = {\n host: ip,\n user: 'root',\n identity: key.privatePath,\n knownHosts: join(key.dir, 'known_hosts'),\n };\n const up = await waitForSsh(sshTarget, PREPARE_SSH_DEADLINE_MS);\n if (!up) {\n throw new Error(`hetzner: ssh on ${ip} did not come up within ${String(PREPARE_SSH_DEADLINE_MS / 1000)}s`);\n }\n progress('ssh up — scp\\'ing runtime assets');\n\n // 5. scp every asset into /tmp/ **sequentially**. Parallel uploads\n // through 10 fresh ssh connections trip sshd's MaxStartups (10:30:100\n // default) on a freshly-booted VPS — surviving connections look fine\n // but some randomly write 0 bytes to the destination. The sequential\n // form is plenty fast (each file is small, total ~1MB).\n for (const asset of assets) {\n const remote = `/tmp/${asset.remoteBasename}`;\n log(`prepare-hetzner: scp ${asset.name} -> ${remote}`);\n await scpUpload(sshTarget, asset.localPath, remote);\n if (asset.remoteMode !== undefined) {\n const modeOctal = asset.remoteMode.toString(8);\n await sshExec(sshTarget, `chmod ${modeOctal} ${remote}`);\n }\n }\n\n // 6. Run the install script. We trace via `bash -x` and tee the full\n // output to /var/log/agentbox/install.log on the VPS so the trace\n // survives into the snapshot — handy when diagnosing a step that ran\n // (or didn't) deep inside the install. Stream stdout/stderr through\n // `onLog` so `prepare.log` shows the BEGIN/END markers in real time.\n // `set -o pipefail` so the pipe's exit code is bash's, not tee's.\n progress('running install-box.sh on temp VPS (this takes ~5-15 min)');\n const installRes = await sshExec(\n sshTarget,\n `sudo mkdir -p /var/log/agentbox && set -o pipefail && bash -x /tmp/agentbox-install.sh 2>&1 | sudo tee /var/log/agentbox/install.log`,\n {\n timeoutMs: INSTALL_SCRIPT_TIMEOUT_MS,\n onLine: (line) => log(`[install] ${line}`),\n },\n );\n if (installRes.exitCode !== 0) {\n throw new Error(\n `install-box.sh failed on temp VPS (exit ${String(installRes.exitCode)})\\n` +\n `Last stderr: ${installRes.stderr.slice(-500) || '(empty)'}\\n` +\n `The full trace was preserved at /var/log/agentbox/install.log inside any box made from the resulting snapshot.`,\n );\n }\n progress('install script complete');\n\n // 6b. Stage host agent static config (~/.claude plugins/skills/settings/\n // _claude.json, ~/.codex config + prompts, ~/.local/share/opencode), scp\n // each tarball, extract into /home/vscode/ as the `vscode` user. Mirrors\n // the Daytona bake step (`Image.addLocalFile` + `Image.runCommands`),\n // adapted for our ssh+scp model. Without this, the in-box claude/codex/\n // opencode boot with no plugins, no skills, no settings, and prompt the\n // user to log in fresh on every box.\n progress('staging host agent static config');\n const stagings: Array<{ kind: 'claude' | 'codex' | 'opencode'; tar: StageResult; dest: string }> = [];\n try {\n const claudeTar = await stageClaudeStaticForUpload({ hostWorkspace: opts.hostWorkspace });\n for (const w of claudeTar.warnings) log(`prepare-hetzner: ${w}`);\n if (claudeTar.tarballPath) stagings.push({ kind: 'claude', tar: claudeTar, dest: '/home/vscode/.claude' });\n else await claudeTar.cleanup();\n\n const codexTar = await stageCodexStaticForUpload();\n for (const w of codexTar.warnings) log(`prepare-hetzner: ${w}`);\n if (codexTar.tarballPath) stagings.push({ kind: 'codex', tar: codexTar, dest: '/home/vscode/.codex' });\n else await codexTar.cleanup();\n\n const opencodeTar = await stageOpencodeStaticForUpload();\n for (const w of opencodeTar.warnings) log(`prepare-hetzner: ${w}`);\n if (opencodeTar.tarballPath) stagings.push({ kind: 'opencode', tar: opencodeTar, dest: '/home/vscode/.local/share/opencode' });\n else await opencodeTar.cleanup();\n\n for (const s of stagings) {\n const remote = `/tmp/agentbox-${s.kind}-static.tar.gz`;\n log(`prepare-hetzner: scp ${s.kind} static (${s.tar.tarballPath}) -> ${remote}`);\n await scpUpload(sshTarget, s.tar.tarballPath as string, remote);\n // Extract as vscode so the files land owned by uid 1000. The dir\n // already exists (created by the install script's credential-pivot\n // step) — extract into it, don't replace it.\n const extractCmd =\n `sudo -u vscode mkdir -p ${s.dest} && ` +\n `sudo -u vscode tar -xzf ${remote} -C ${s.dest} --no-same-permissions --no-same-owner -m && ` +\n `rm -f ${remote}`;\n const r = await sshExec(sshTarget, extractCmd, { onLine: (line) => log(`[stage:${s.kind}] ${line}`) });\n if (r.exitCode !== 0) {\n throw new Error(\n `prepare-hetzner: ${s.kind} static extract failed (exit ${String(r.exitCode)}): ${r.stderr.slice(-300)}`,\n );\n }\n progress(`baked ${s.kind} static config into snapshot`);\n }\n } finally {\n for (const s of stagings) await s.tar.cleanup();\n }\n\n // 7. Snapshot.\n const description = opts.name ?? `agentbox-base-${stamp}`;\n progress(`creating snapshot '${description}' from VPS ${String(serverId)}`);\n const snap = await client.createImage(serverId, {\n type: 'snapshot',\n description,\n labels: { 'agentbox.role': 'base', 'agentbox.schema': '1' },\n });\n progress(`snapshot create requested (image id ${String(snap.image.id)}); polling until available`);\n const ready = await pollUntil(\n `image ${String(snap.image.id)} availability`,\n async () => {\n const img = await client.getImage(snap.image.id);\n if (!img) return null;\n if (img.status === 'available') return img;\n return null;\n },\n { deadlineMs: SNAPSHOT_DEADLINE_MS, intervalMs: 3_000, maxIntervalMs: 10_000, onPoll: (l) => log(`prepare-hetzner: ${l}`) },\n );\n\n // 8. Persist before tearing down — if the cleanup fails we still know\n // about the new snapshot.\n progress('persisting hetzner-prepared.json');\n const state = readPreparedState();\n const cliStamp = readCliStamp();\n state.base = {\n imageId: ready.id,\n description: ready.description,\n createdAt: new Date().toISOString(),\n contextSha256: contextSha,\n cliVersion: cliStamp.cliVersion,\n cliCommit: cliStamp.cliCommit,\n };\n writePreparedState(state);\n log(`prepare-hetzner: wrote ${preparedStatePath()}`);\n\n // 9. Cleanup: delete server first (cleanly detaches from firewall),\n // then the firewall.\n progress(`deleting temp VPS ${String(serverId)}`);\n await client.deleteServer(serverId);\n serverId = null;\n progress(`deleting per-prepare firewall ${String(firewallId)}`);\n await deletePerBoxFirewall(client, firewallId);\n firewallId = null;\n\n progress(`prepare complete — base snapshot ${String(ready.id)} (${ready.description})`);\n return { snapshotName: ready.description, imageId: ready.id };\n } catch (err) {\n // Failure cleanup — best-effort. Always try to delete the VPS first\n // (it costs €4/mo if left running). Surface the original error in any\n // case.\n if (serverId !== null) {\n log(`prepare-hetzner: cleanup — deleting temp VPS ${String(serverId)} after failure`);\n try {\n await client.deleteServer(serverId);\n } catch (cleanupErr) {\n log(\n `prepare-hetzner: WARN — failed to delete temp VPS ${String(serverId)}; check the Hetzner dashboard manually. ${\n cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr)\n }`,\n );\n }\n }\n if (firewallId !== null) {\n log(`prepare-hetzner: cleanup — deleting per-prepare firewall ${String(firewallId)} after failure`);\n try {\n await deletePerBoxFirewall(client, firewallId);\n } catch (cleanupErr) {\n log(\n `prepare-hetzner: WARN — failed to delete firewall ${String(firewallId)}; check the Hetzner dashboard manually. ${\n cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr)\n }`,\n );\n }\n }\n throw err;\n } finally {\n await key.cleanup();\n }\n}\n\n/**\n * Provider-level binding used by the CLI's `prepare` command. Matches the\n * shape of `daytonaProvider.prepare`.\n */\nexport const prepareHetznerProvider: NonNullable<Provider['prepare']> = (req) =>\n prepareHetzner({\n name: req.name,\n hostWorkspace: req.hostWorkspace ?? process.cwd(),\n force: req.force,\n onLog: req.onLog,\n });\n\n/**\n * First-use gate. If no base snapshot is recorded in\n * `~/.agentbox/hetzner-prepared.json`, throws an actionable error pointing\n * at `agentbox prepare --provider hetzner`.\n *\n * This is called by `backend.provision()` (lazily, from Phase 4 onward) so\n * `agentbox prepare --provider hetzner` itself can run without tripping\n * the gate.\n *\n * Phase 4 will widen this to also re-check the image is still on Hetzner\n * (404 → retrigger prepare prompt). For now it just gates on the local\n * record so the build is honest about the failure mode.\n */\nexport async function ensureHetznerBaseSnapshot(): Promise<void> {\n const state = readPreparedState();\n if (state.base !== undefined) return;\n throw new Error(\n 'no Hetzner base snapshot found.\\n' +\n 'Run `agentbox prepare --provider hetzner` first (Hetzner cannot build images from a Dockerfile,\\n' +\n 'so the base snapshot is a one-time prerequisite for cloud boxes on this backend).',\n );\n}\n\nexport type { ResolvedAsset };\n","/**\n * Resolver for the on-disk files we need to ship into a fresh VPS during\n * `prepareHetzner()` — same shape as the docker provider's runtime/docker\n * staging, but lighter (no Dockerfile build, just a flat tarball of files\n * to scp into /tmp).\n *\n * Lookup order for each file:\n * 1. The CLI's staged runtime tree: `<cliRoot>/runtime/hetzner/...`\n * (populated by `apps/cli/scripts/stage-runtime.mjs`).\n * 2. The monorepo source tree (dev fallback): the file's canonical\n * package-relative path under `packages/`.\n *\n * Failure mode: any missing file throws a clear error naming the lookup\n * paths so a partial dev rebuild is obvious to debug.\n */\n\nimport { existsSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst SELF = dirname(fileURLToPath(import.meta.url));\n\n/**\n * Locate the staged `runtime/hetzner/` tree. Two candidates:\n *\n * 1. Bundled CLI: the hetzner module is inlined into apps/cli's dist/, so\n * `dirname(import.meta.url)` is `<cliRoot>/dist`; the staged runtime\n * sits at `<cliRoot>/runtime/hetzner`.\n * 2. Workspace dev: this module's dist is at\n * `packages/sandbox-hetzner/dist/`. There's no staged runtime there;\n * callers fall through to the monorepo source paths in\n * `candidatesFor()`. Returns undefined in that case.\n *\n * The CLI doesn't have to pass `cliRuntimeRoot` explicitly — this helper\n * picks it up by inspecting where the module was loaded from.\n */\nexport function findStagedCliRuntimeRoot(): string | undefined {\n const candidates = [\n resolve(SELF, '..', 'runtime'), // <cliRoot>/dist/.. → <cliRoot> then /runtime\n resolve(SELF, '..', '..', 'runtime'), // chunk-NNNN.js at <cliRoot>/dist/<sub>/.. → <cliRoot>/runtime\n ];\n for (const c of candidates) {\n if (existsSync(resolve(c, 'hetzner', 'scripts', 'install-box.sh'))) return c;\n }\n return undefined;\n}\n\n/**\n * Each runtime asset has a stable, well-known destination basename in\n * `/tmp` on the prepare VPS and is resolved from one of N candidate\n * source paths on the host.\n */\nexport interface RuntimeAsset {\n /** Logical name (used in error messages + log lines). */\n name: string;\n /** Basename on the prepare VPS (under /tmp/). */\n remoteBasename: string;\n /** Optional file mode at scp-time. */\n remoteMode?: number;\n}\n\nexport const RUNTIME_ASSETS: readonly RuntimeAsset[] = [\n { name: 'install-box.sh', remoteBasename: 'agentbox-install.sh', remoteMode: 0o755 },\n { name: 'agentbox-ctl', remoteBasename: 'agentbox-ctl', remoteMode: 0o755 },\n { name: 'agentbox-vnc-start', remoteBasename: 'agentbox-vnc-start', remoteMode: 0o755 },\n { name: 'agentbox-dockerd-start', remoteBasename: 'agentbox-dockerd-start', remoteMode: 0o755 },\n { name: 'agentbox-checkpoint-cleanup', remoteBasename: 'agentbox-checkpoint-cleanup', remoteMode: 0o755 },\n { name: 'agentbox-open', remoteBasename: 'agentbox-open', remoteMode: 0o755 },\n { name: 'gh-shim', remoteBasename: 'agentbox-gh-shim', remoteMode: 0o755 },\n { name: 'git-shim', remoteBasename: 'agentbox-git-shim', remoteMode: 0o755 },\n { name: 'custom-system-CLAUDE.md', remoteBasename: 'agentbox-custom-CLAUDE.md', remoteMode: 0o644 },\n { name: 'claude-managed-settings.json', remoteBasename: 'agentbox-managed-settings.json', remoteMode: 0o644 },\n { name: 'agentbox-codex-hooks.json', remoteBasename: 'agentbox-codex-hooks.json', remoteMode: 0o644 },\n { name: 'agentbox-setup-skill.md', remoteBasename: 'agentbox-setup-skill.md', remoteMode: 0o644 },\n] as const;\n\nexport interface ResolvedAsset extends RuntimeAsset {\n localPath: string;\n}\n\n/**\n * Build the candidate search paths for a given asset. Tries CLI runtime\n * first, then the monorepo source tree. The first one that exists wins.\n *\n * `cliRuntimeRoot` is provided by the caller because we don't know how the\n * embedding CLI lays out its dist (the @madarco/agentbox CLI puts dist + a\n * sibling runtime/ next to it; tests pass a tmp dir). Use the helper\n * `findCliRuntimeRoot()` below from the calling context that has the right\n * anchor (typically `import.meta.url` of an apps/cli module).\n */\nexport function candidatesFor(\n name: string,\n opts: { cliRuntimeRoot?: string; repoRoot?: string } = {},\n): string[] {\n const cliRoot = opts.cliRuntimeRoot;\n const monorepo = opts.repoRoot ?? guessRepoRoot();\n\n // Map logical → relative paths (relative to either anchor).\n const monorepoRelative: Record<string, string[]> = {\n 'install-box.sh': ['packages/sandbox-hetzner/scripts/install-box.sh'],\n 'agentbox-ctl': ['packages/ctl/dist/bin.cjs'],\n 'agentbox-vnc-start': ['packages/sandbox-docker/scripts/agentbox-vnc-start'],\n 'agentbox-dockerd-start': ['packages/sandbox-docker/scripts/agentbox-dockerd-start'],\n 'agentbox-checkpoint-cleanup': ['packages/sandbox-docker/scripts/agentbox-checkpoint-cleanup'],\n 'agentbox-open': ['packages/sandbox-docker/scripts/agentbox-open'],\n 'gh-shim': ['packages/sandbox-docker/scripts/gh-shim'],\n 'git-shim': ['packages/sandbox-docker/scripts/git-shim'],\n 'custom-system-CLAUDE.md': ['packages/sandbox-hetzner/scripts/custom-system-CLAUDE.md'],\n 'claude-managed-settings.json': ['packages/sandbox-docker/scripts/claude-managed-settings.json'],\n 'agentbox-codex-hooks.json': ['packages/sandbox-docker/scripts/agentbox-codex-hooks.json'],\n 'agentbox-setup-skill.md': ['apps/cli/share/agentbox-setup/SKILL.md'],\n };\n\n // CLI-runtime-tree relative paths (mirrors the staging layout).\n const cliRelative: Record<string, string[]> = {\n 'install-box.sh': ['hetzner/scripts/install-box.sh'],\n 'agentbox-ctl': ['hetzner/ctl.cjs'],\n 'agentbox-vnc-start': ['hetzner/agentbox-vnc-start', 'docker/packages/sandbox-docker/scripts/agentbox-vnc-start'],\n 'agentbox-dockerd-start': ['hetzner/agentbox-dockerd-start', 'docker/packages/sandbox-docker/scripts/agentbox-dockerd-start'],\n 'agentbox-checkpoint-cleanup': ['hetzner/agentbox-checkpoint-cleanup', 'docker/packages/sandbox-docker/scripts/agentbox-checkpoint-cleanup'],\n 'agentbox-open': ['hetzner/agentbox-open', 'docker/packages/sandbox-docker/scripts/agentbox-open'],\n 'gh-shim': ['hetzner/gh-shim', 'docker/packages/sandbox-docker/scripts/gh-shim'],\n 'git-shim': ['hetzner/git-shim', 'docker/packages/sandbox-docker/scripts/git-shim'],\n 'custom-system-CLAUDE.md': ['hetzner/custom-system-CLAUDE.md'],\n 'claude-managed-settings.json': ['hetzner/claude-managed-settings.json', 'docker/packages/sandbox-docker/scripts/claude-managed-settings.json'],\n 'agentbox-codex-hooks.json': ['hetzner/agentbox-codex-hooks.json', 'docker/packages/sandbox-docker/scripts/agentbox-codex-hooks.json'],\n 'agentbox-setup-skill.md': ['hetzner/agentbox-setup-skill.md', 'docker/apps/cli/share/agentbox-setup/SKILL.md'],\n };\n\n const out: string[] = [];\n if (cliRoot) {\n for (const rel of cliRelative[name] ?? []) out.push(resolve(cliRoot, rel));\n }\n for (const rel of monorepoRelative[name] ?? []) out.push(resolve(monorepo, rel));\n return out;\n}\n\n/**\n * Resolve every runtime asset to its absolute on-host path. Throws an\n * actionable error if any asset can't be found (lists every path tried).\n */\nexport function resolveRuntimeAssets(opts: {\n cliRuntimeRoot?: string;\n repoRoot?: string;\n} = {}): ResolvedAsset[] {\n const out: ResolvedAsset[] = [];\n const missing: Array<{ name: string; tried: string[] }> = [];\n for (const asset of RUNTIME_ASSETS) {\n const cands = candidatesFor(asset.name, opts);\n const hit = cands.find((p) => existsSync(p));\n if (!hit) {\n missing.push({ name: asset.name, tried: cands });\n continue;\n }\n out.push({ ...asset, localPath: hit });\n }\n if (missing.length > 0) {\n const lines = missing.flatMap((m) => [` - ${m.name}: tried`, ...m.tried.map((p) => ` ${p}`)]);\n throw new Error(\n `hetzner: could not resolve runtime assets — these files are needed to install on the prepare VPS:\\n` +\n lines.join('\\n') +\n `\\n\\nIf you are running from the monorepo, ensure \\`pnpm -w build\\` has run so packages/ctl/dist/bin.cjs exists. ` +\n `If you are running from a published CLI bundle, the runtime/hetzner tree should be staged automatically.`,\n );\n }\n return out;\n}\n\n/** Best-effort: walk up from this file looking for `pnpm-workspace.yaml`. */\nfunction guessRepoRoot(): string {\n let cur = SELF;\n for (let i = 0; i < 8; i++) {\n if (existsSync(resolve(cur, 'pnpm-workspace.yaml'))) return cur;\n const parent = dirname(cur);\n if (parent === cur) break;\n cur = parent;\n }\n return SELF; // fall through to itself; resolution will fail with a clear error\n}\n","/**\n * Per-box (and per-prepare-run) SSH key minting.\n *\n * AgentBox mints a fresh ed25519 keypair per box at provision time. The\n * private key never leaves the host; the public key is shipped to the VPS\n * via cloud-init `users:` (NOT the Hetzner SSH-keys-import API, which\n * would make the same pubkey available to attach to other VPSes the user\n * provisions — see the plan's §\"Key & key-lifecycle hygiene\").\n *\n * Storage layout (per the plan):\n * ~/.agentbox/boxes/<box-id>/ssh/\n * id_ed25519 (private, 0600)\n * id_ed25519.pub (public, 0644)\n * known_hosts (per-box, populated post-first-connect)\n * control.sock (ssh ControlMaster socket — created at runtime)\n *\n * For the temp prepare VPS we use a parallel path:\n * ~/.agentbox/hetzner/prepare-<timestamp>/\n * deleted after the snapshot completes.\n */\n\nimport { mkdir, readFile } from 'node:fs/promises';\nimport { dirname, join, resolve } from 'node:path';\nimport { execa } from 'execa';\n\nexport interface MintedSshKey {\n /** Directory holding the key files. */\n dir: string;\n /** Absolute path to the private key. */\n privatePath: string;\n /** Absolute path to the public key. */\n publicPath: string;\n /** Public key contents (one OpenSSH-format line). */\n publicKey: string;\n}\n\n/**\n * Mint a fresh ed25519 keypair into `targetDir/id_ed25519` (+ `.pub`). The\n * directory is created if missing. Throws if the private key already exists\n * — callers handle reuse explicitly (we don't silently overwrite).\n *\n * `comment` is embedded in the public key (the `agentbox/<box-id>` tag) so\n * the key is identifiable in `~/.ssh/authorized_keys` on a forensic look.\n */\nexport async function mintSshKey(targetDir: string, comment: string): Promise<MintedSshKey> {\n const dir = resolve(targetDir);\n const priv = join(dir, 'id_ed25519');\n const pub = `${priv}.pub`;\n await mkdir(dir, { recursive: true, mode: 0o700 });\n\n // `ssh-keygen -N ''` for no passphrase; `-q` to suppress the random art.\n // Caller is responsible for ensuring the dir is fresh — if `priv` already\n // exists, ssh-keygen would prompt to overwrite (and we don't pipe stdin so\n // it would hang). `mintPrepareKey` creates a fresh dir per call; the\n // per-box minter in backend.ts uses a fresh stamp directory too.\n await execa(\n 'ssh-keygen',\n ['-t', 'ed25519', '-N', '', '-C', comment, '-f', priv, '-q'],\n { stdio: 'pipe' },\n );\n\n const publicKey = (await readFile(pub, 'utf8')).trim();\n return { dir, privatePath: priv, publicPath: pub, publicKey };\n}\n\n/**\n * Mint a temporary keypair for the prepare orchestrator. Returns the same\n * shape as `mintSshKey` plus a `cleanup()` that rm -rf's the directory.\n * The caller is expected to call `cleanup()` in a `finally` block.\n */\nexport async function mintPrepareKey(): Promise<MintedSshKey & { cleanup: () => Promise<void> }> {\n const root = resolve(homedirOrCwd(), '.agentbox', 'hetzner', `prepare-${Date.now().toString(36)}`);\n const key = await mintSshKey(root, `agentbox-prepare-${new Date().toISOString().replace(/[:.]/g, '-')}`);\n return {\n ...key,\n cleanup: async () => {\n try {\n const { rm } = await import('node:fs/promises');\n await rm(dirname(key.privatePath), { recursive: true, force: true });\n } catch {\n // best-effort\n }\n },\n };\n}\n\nfunction homedirOrCwd(): string {\n try {\n // Lazy require so this module is import-safe even if `os` is shimmed\n // away in some weird bundle environment.\n return process.env.HOME ?? process.cwd();\n } catch {\n return process.cwd();\n }\n}\n","/**\n * Thin wrappers around the system `ssh` / `scp` binaries used during\n * `prepareHetzner()` (and Phase 4's SshTunnelManager).\n *\n * These helpers compose flags that suppress the usual interactive prompts\n * (`StrictHostKeyChecking=accept-new`, `UserKnownHostsFile=<per-box file>`)\n * and bind to the per-key/per-box paths.\n *\n * We shell out to the system OpenSSH rather than a JS SSH library because:\n * - The provider matrix already depends on system `ssh` for `agentbox\n * shell` against Docker boxes (`docker exec` doesn't go through ssh,\n * but `agentbox open`'s sshfs flow does).\n * - OpenSSH's ControlMaster + dynamic port forwarding is exactly the\n * primitive Phase 4 needs, and is hard to replicate in pure JS.\n * - No native-dep crutch and no surprise binary sizes.\n */\n\nimport { execa, type ResultPromise } from 'execa';\n\nexport interface SshTargetArgs {\n /** VPS IP (or DNS name) — passed to ssh as user@host. */\n host: string;\n /** Remote user. Hetzner stock images come up with `root`; baked snapshots use `vscode`. */\n user: string;\n /** Absolute path to the per-box/per-prepare private key. */\n identity: string;\n /** Absolute path to the per-box known_hosts file. */\n knownHosts: string;\n /** Optional ControlMaster socket (set during Phase 4 SshTunnelManager). */\n controlPath?: string;\n /** Extra `-o key=value` settings (e.g. ConnectTimeout for prepare polling). */\n options?: Record<string, string>;\n}\n\n/** Compose the `-o … -i … -o UserKnownHostsFile=…` flags for ssh/scp. */\nexport function sshOptArgs(target: SshTargetArgs): string[] {\n const out: string[] = [\n '-i', target.identity,\n '-o', 'StrictHostKeyChecking=accept-new',\n '-o', `UserKnownHostsFile=${target.knownHosts}`,\n '-o', 'GlobalKnownHostsFile=/dev/null',\n '-o', 'BatchMode=yes',\n '-o', 'LogLevel=ERROR',\n ];\n if (target.controlPath) {\n out.push('-o', `ControlPath=${target.controlPath}`);\n }\n for (const [k, v] of Object.entries(target.options ?? {})) {\n out.push('-o', `${k}=${v}`);\n }\n return out;\n}\n\nexport interface SshExecOptions {\n /** Stream stdout/stderr line-by-line into this callback. */\n onLine?: (line: string) => void;\n /** Pipe extra env into the remote shell. */\n env?: Record<string, string>;\n /** Per-command wall-clock cap (ms). */\n timeoutMs?: number;\n}\n\nexport interface SshExecResult {\n exitCode: number;\n stdout: string;\n stderr: string;\n}\n\n/**\n * Run a one-shot command on the target VPS over ssh. Returns the exit code\n * + captured stdout/stderr; non-zero exits do NOT throw — callers decide\n * what to do with them.\n */\nexport async function sshExec(\n target: SshTargetArgs,\n remoteCmd: string,\n opts: SshExecOptions = {},\n): Promise<SshExecResult> {\n const argv = [\n ...sshOptArgs(target),\n `${target.user}@${target.host}`,\n remoteCmd,\n ];\n const child = execa('ssh', argv, {\n reject: false,\n timeout: opts.timeoutMs,\n env: { ...process.env, ...opts.env },\n stdio: opts.onLine ? ['ignore', 'pipe', 'pipe'] : ['ignore', 'pipe', 'pipe'],\n }) as ResultPromise;\n\n if (opts.onLine) {\n const handle = (chunk: Buffer | string) => {\n const text = typeof chunk === 'string' ? chunk : chunk.toString('utf8');\n for (const line of text.split(/\\r?\\n/)) {\n if (line.length > 0) opts.onLine?.(line);\n }\n };\n child.stdout?.on('data', handle);\n child.stderr?.on('data', handle);\n }\n\n const res = await child;\n return {\n exitCode: typeof res.exitCode === 'number' ? res.exitCode : 1,\n stdout: typeof res.stdout === 'string' ? res.stdout : '',\n stderr: typeof res.stderr === 'string' ? res.stderr : '',\n };\n}\n\n/** Copy a local file to the target VPS via `scp`. Throws on non-zero exit. */\nexport async function scpUpload(\n target: SshTargetArgs,\n localPath: string,\n remotePath: string,\n opts: SshExecOptions = {},\n): Promise<void> {\n const argv = [\n ...sshOptArgs(target),\n localPath,\n `${target.user}@${target.host}:${remotePath}`,\n ];\n const res = await execa('scp', argv, {\n reject: false,\n timeout: opts.timeoutMs,\n });\n if (res.exitCode !== 0) {\n throw new Error(\n `scp upload failed (exit ${String(res.exitCode)}): ${localPath} → ${remotePath}\\n${res.stderr ?? ''}`,\n );\n }\n}\n\n/** Copy a remote file to the host via `scp`. Throws on non-zero exit. */\nexport async function scpDownload(\n target: SshTargetArgs,\n remotePath: string,\n localPath: string,\n opts: SshExecOptions = {},\n): Promise<void> {\n const argv = [\n ...sshOptArgs(target),\n `${target.user}@${target.host}:${remotePath}`,\n localPath,\n ];\n const res = await execa('scp', argv, {\n reject: false,\n timeout: opts.timeoutMs,\n });\n if (res.exitCode !== 0) {\n throw new Error(\n `scp download failed (exit ${String(res.exitCode)}): ${remotePath} → ${localPath}\\n${res.stderr ?? ''}`,\n );\n }\n}\n\n/**\n * Poll the target until ssh succeeds (or `deadlineMs` elapses). Used by the\n * prepare orchestrator after `createServer` to wait for cloud-init to bring\n * sshd up. Returns true on success, false on timeout — callers throw with\n * appropriate context.\n */\nexport async function waitForSsh(\n target: SshTargetArgs,\n deadlineMs: number,\n intervalMs = 5_000,\n): Promise<boolean> {\n const stop = Date.now() + deadlineMs;\n while (Date.now() < stop) {\n const res = await sshExec(\n { ...target, options: { ...target.options, ConnectTimeout: '5' } },\n 'true',\n { timeoutMs: 10_000 },\n );\n if (res.exitCode === 0) return true;\n await new Promise((r) => setTimeout(r, intervalMs));\n }\n return false;\n}\n","/**\n * `SshTunnelManager` — one persistent `ssh` ControlMaster per box, plus\n * dynamic `-L` port forwards minted on demand.\n *\n * This is the load-bearing piece that makes the Hetzner provider's preview-URL\n * + exec/file I/O paths work without paying the SSH handshake on every call.\n * Design rationale lives in `~/.claude/plans/how-to-safely-create-parallel-pebble.md`\n * §\"SSH tunnel design (host side)\" and is recapped here.\n *\n * Layout:\n *\n * ~/.agentbox/boxes/<box-id>/ssh/\n * id_ed25519 per-box private key (0600)\n * id_ed25519.pub per-box public key (0644)\n * known_hosts per-box host-key pinning\n * control.sock ssh ControlMaster socket (created at runtime, removed by `-O exit`)\n *\n * Lifecycle:\n *\n * - open() → spawn `ssh -fNT -M -S control.sock -i id_ed25519 vscode@vps` once per box.\n * - forward() → `ssh -O forward -L 127.0.0.1:<localPort>:127.0.0.1:<remotePort> -S control.sock dummy`.\n * Picks a free local port from the ephemeral range.\n * Idempotent per (boxId, remotePort) — returns the cached\n * localPort on a repeated call.\n * - unforward() → `ssh -O cancel -L 127.0.0.1:<localPort>:127.0.0.1:<remotePort> -S control.sock dummy`.\n * - close() → `ssh -O exit -S control.sock dummy`; removes the socket.\n *\n * Thread-safety: a single AgentBox process owns one manager per CLI\n * invocation. Concurrent `forward()` calls for the same (boxId, remotePort)\n * race-resolve to the same localPort because the cache is checked first and\n * `ssh -O forward` is idempotent on the SSH side.\n */\n\nimport { existsSync } from 'node:fs';\nimport { mkdir, rm } from 'node:fs/promises';\nimport { createServer } from 'node:net';\nimport { homedir } from 'node:os';\nimport { dirname, join, resolve } from 'node:path';\nimport { execa } from 'execa';\n\nconst HOST = '127.0.0.1';\nconst EPHEMERAL_MIN = 49152;\nconst EPHEMERAL_MAX = 65535;\n\nexport interface SshTunnelOpenOptions {\n boxId: string;\n /** VPS IP/host. */\n vpsHost: string;\n /** Remote user — `vscode` for boxes baked from `install-box.sh`. */\n vpsUser?: string;\n /** Absolute path to the private key. */\n identity: string;\n /** Override `~/.agentbox/boxes/<box-id>/ssh/` (tests inject this). */\n boxSshDir?: string;\n /** Override the connect-timeout (default 10s — fast-fail on a dropped firewall rule). */\n connectTimeoutSeconds?: number;\n}\n\nexport interface PortForward {\n localPort: number;\n remotePort: number;\n}\n\ninterface BoxTunnel {\n controlPath: string;\n vpsHost: string;\n vpsUser: string;\n identity: string;\n boxSshDir: string;\n // remotePort → localPort. Used so repeated `forward()` calls for the same\n // remote port return the same local port without re-asking sshd.\n forwards: Map<number, number>;\n}\n\nexport class SshTunnelManager {\n private boxes = new Map<string, BoxTunnel>();\n\n /**\n * Open the ControlMaster for `boxId`. Idempotent: if a master is already\n * up for this box (socket exists + responsive), no-op. Otherwise spawn a\n * fresh `ssh -fNT -M` and wait for the socket to appear.\n */\n async open(opts: SshTunnelOpenOptions): Promise<void> {\n const boxSshDir = opts.boxSshDir ?? defaultBoxSshDir(opts.boxId);\n await mkdir(boxSshDir, { recursive: true, mode: 0o700 });\n const controlPath = join(boxSshDir, 'control.sock');\n const knownHosts = join(boxSshDir, 'known_hosts');\n const user = opts.vpsUser ?? 'vscode';\n const tunnel: BoxTunnel = {\n controlPath,\n vpsHost: opts.vpsHost,\n vpsUser: user,\n identity: opts.identity,\n boxSshDir,\n forwards: new Map(),\n };\n\n // Reuse an existing live master if the socket already responds.\n if (existsSync(controlPath) && (await this.isAlive(controlPath))) {\n this.boxes.set(opts.boxId, tunnel);\n return;\n }\n // Stale socket from a prior crashed process — remove before re-opening.\n if (existsSync(controlPath)) {\n await rm(controlPath, { force: true });\n }\n\n const connectTimeout = opts.connectTimeoutSeconds ?? 10;\n const argv = [\n '-fNT',\n '-M',\n '-S', controlPath,\n '-i', opts.identity,\n '-o', 'StrictHostKeyChecking=accept-new',\n '-o', `UserKnownHostsFile=${knownHosts}`,\n '-o', 'GlobalKnownHostsFile=/dev/null',\n '-o', 'BatchMode=yes',\n '-o', 'LogLevel=ERROR',\n '-o', 'ExitOnForwardFailure=yes',\n '-o', 'ServerAliveInterval=30',\n '-o', 'ServerAliveCountMax=3',\n '-o', `ConnectTimeout=${String(connectTimeout)}`,\n `${user}@${opts.vpsHost}`,\n ];\n const res = await execa('ssh', argv, { reject: false });\n if (res.exitCode !== 0 || !existsSync(controlPath)) {\n throw new Error(\n `ssh ControlMaster failed for ${opts.boxId} (exit ${String(res.exitCode)}): ${res.stderr || res.stdout || '(no output)'}`,\n );\n }\n this.boxes.set(opts.boxId, tunnel);\n }\n\n /**\n * Mint (or fetch the cached) `127.0.0.1:<localPort> → vps:127.0.0.1:<remotePort>`\n * forward. Returns the local port. Idempotent per (boxId, remotePort).\n *\n * The cached entry is only returned when the underlying ControlMaster is\n * still alive — without that check we'd happily hand back a localPort that\n * stopped listening when the master died (e.g. transient network blip,\n * host sleep/wake). When the master is dead we drop ALL cached forwards\n * for this box (they all share one tunnel) and re-mint from scratch.\n */\n async forward(boxId: string, remotePort: number): Promise<number> {\n const tunnel = this.getTunnelOrThrow(boxId);\n const cached = tunnel.forwards.get(remotePort);\n if (cached !== undefined && (await this.isAlive(tunnel.controlPath))) {\n return cached;\n }\n if (cached !== undefined) {\n // Master died — every cached local port stopped listening. Drop them\n // all; callers that still hold a stale `localPort` will get a fresh\n // one on their next forward() call.\n tunnel.forwards.clear();\n }\n const localPort = await pickFreePort();\n const argv = [\n '-O', 'forward',\n '-L', `${HOST}:${String(localPort)}:${HOST}:${String(remotePort)}`,\n '-S', tunnel.controlPath,\n 'dummy', // the target host is ignored when -O is used, but argv needs one\n ];\n const res = await execa('ssh', argv, { reject: false });\n if (res.exitCode !== 0) {\n throw new Error(\n `ssh -O forward failed for ${boxId} (exit ${String(res.exitCode)}): ${res.stderr || res.stdout || '(no output)'}`,\n );\n }\n tunnel.forwards.set(remotePort, localPort);\n return localPort;\n }\n\n /**\n * Tear down a dead ControlMaster + every cached forward for this box,\n * then re-open from scratch. Idempotent — if the master is already alive\n * the master open() is a no-op, but the cached forwards still get\n * cleared so the next forward() call re-mints them. Returns when the\n * master is open and the box's forwards map is empty (ready for fresh\n * forward() calls).\n *\n * Use case: the cloud-poller observes ECONNREFUSED on the local port and\n * asks the backend to refresh the preview URL — that path calls into\n * here so the master + forward both come back fresh.\n */\n async refresh(opts: SshTunnelOpenOptions): Promise<void> {\n const existing = this.boxes.get(opts.boxId);\n if (existing) {\n const alive = await this.isAlive(existing.controlPath);\n if (!alive && existsSync(existing.controlPath)) {\n // Stale socket from a dead master — best-effort cleanup, then reopen.\n try {\n await execa(\n 'ssh',\n ['-O', 'exit', '-S', existing.controlPath, 'dummy'],\n { reject: false },\n );\n } catch {\n // ignore — `-O exit` on a dead master can fail\n }\n await rm(existing.controlPath, { force: true });\n }\n // Either way drop the cached forwards: even if the master happened to\n // be alive, the caller asked us to refresh because *something*\n // upstream (the local port) wasn't responding.\n existing.forwards.clear();\n }\n await this.open(opts);\n }\n\n /** Tear down a single forward. Idempotent — unknown ports are no-ops. */\n async unforward(boxId: string, remotePort: number): Promise<void> {\n const tunnel = this.getTunnelOrThrow(boxId);\n const localPort = tunnel.forwards.get(remotePort);\n if (localPort === undefined) return;\n const argv = [\n '-O', 'cancel',\n '-L', `${HOST}:${String(localPort)}:${HOST}:${String(remotePort)}`,\n '-S', tunnel.controlPath,\n 'dummy',\n ];\n await execa('ssh', argv, { reject: false });\n tunnel.forwards.delete(remotePort);\n }\n\n /**\n * Close the ControlMaster (and all its forwards). Idempotent — if no\n * master is recorded, no-op. Removes the socket file.\n */\n async close(boxId: string): Promise<void> {\n const tunnel = this.boxes.get(boxId);\n if (!tunnel) return;\n if (existsSync(tunnel.controlPath)) {\n await execa('ssh', ['-O', 'exit', '-S', tunnel.controlPath, 'dummy'], { reject: false });\n await rm(tunnel.controlPath, { force: true });\n }\n this.boxes.delete(boxId);\n }\n\n /** Tear down every open box. */\n async closeAll(): Promise<void> {\n const ids = Array.from(this.boxes.keys());\n await Promise.all(ids.map((id) => this.close(id)));\n }\n\n /** Path to the ControlMaster socket for `boxId`, if open. */\n controlPath(boxId: string): string | undefined {\n return this.boxes.get(boxId)?.controlPath;\n }\n\n /** True if a ControlMaster is registered for `boxId` (regardless of liveness). */\n has(boxId: string): boolean {\n return this.boxes.has(boxId);\n }\n\n /** Per-box ssh dir (tests use this to verify the layout). */\n boxSshDir(boxId: string): string | undefined {\n return this.boxes.get(boxId)?.boxSshDir;\n }\n\n /** Re-open the manager record for an existing-on-disk control socket. */\n registerExisting(boxId: string, opts: SshTunnelOpenOptions): void {\n const boxSshDir = opts.boxSshDir ?? defaultBoxSshDir(opts.boxId);\n this.boxes.set(boxId, {\n controlPath: join(boxSshDir, 'control.sock'),\n vpsHost: opts.vpsHost,\n vpsUser: opts.vpsUser ?? 'vscode',\n identity: opts.identity,\n boxSshDir,\n forwards: new Map(),\n });\n }\n\n private async isAlive(controlPath: string): Promise<boolean> {\n const res = await execa('ssh', ['-O', 'check', '-S', controlPath, 'dummy'], { reject: false });\n return res.exitCode === 0;\n }\n\n private getTunnelOrThrow(boxId: string): BoxTunnel {\n const t = this.boxes.get(boxId);\n if (!t) throw new Error(`no SSH ControlMaster registered for box ${boxId}; call open() first`);\n return t;\n }\n}\n\n/** Default per-box ssh dir: `~/.agentbox/boxes/<box-id>/ssh/`. */\nexport function defaultBoxSshDir(boxId: string): string {\n return resolve(homedir(), '.agentbox', 'boxes', boxId, 'ssh');\n}\n\n/**\n * Ask the kernel for a free port in the ephemeral range. We bind a fresh\n * server on `127.0.0.1:0`, capture the port the kernel assigned, and close\n * — there's a tiny race where the port could be claimed before `ssh -O\n * forward` lands, but in practice ssh's idempotent socket re-creation\n * doesn't conflict with the captive process.\n */\nasync function pickFreePort(): Promise<number> {\n return new Promise((resolveOk, reject) => {\n const srv = createServer();\n srv.unref();\n srv.on('error', reject);\n srv.listen(0, HOST, () => {\n const addr = srv.address();\n if (!addr || typeof addr === 'string') {\n srv.close();\n reject(new Error('could not get a free local port'));\n return;\n }\n const port = addr.port;\n srv.close(() => {\n if (port < EPHEMERAL_MIN || port > EPHEMERAL_MAX) {\n // Some systems give back a low port — that's fine, just allow it.\n resolveOk(port);\n } else {\n resolveOk(port);\n }\n });\n });\n });\n}\n\nexport const _internalForTests = {\n pickFreePort,\n defaultBoxSshDir,\n EPHEMERAL_MIN,\n EPHEMERAL_MAX,\n // Tests use the export below; this object documents the surface.\n exports: {} as Record<string, never>,\n};\n// Avoid unused-vars in case the test wants to add helpers later — see backend.ts pattern.\nvoid dirname;\n"],"mappings":"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","names":["existsSync","rm","mkdir","join","execa","join","dirname","resolve","execa","existsSync","mkdir","join","rm","client","SNAPSHOT_DEADLINE_MS"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../packages/sandbox-vercel/src/index.ts","../../../packages/sandbox-vercel/src/backend.ts","../../../packages/sandbox-vercel/src/retry.ts","../../../packages/sandbox-vercel/src/prepared-state.ts","../../../packages/sandbox-vercel/src/prepare.ts","../../../packages/sandbox-vercel/src/runtime-assets.ts","../../../packages/sandbox-vercel/src/build-attach.ts"],"sourcesContent":["/**\n * The Vercel Sandbox provider. A thin `CloudBackend` over `@vercel/sandbox`,\n * composed via `@agentbox/sandbox-cloud`'s `createCloudProvider` for everything\n * provider-agnostic (workspace seeding, ctl/VNC launch, state, relay polling).\n *\n * Three capabilities are overridden on top of the cloud scaffold:\n * - `prepare` — bake the base snapshot (Vercel can't build from a Dockerfile).\n * - `buildAttach` — SDK-streaming tmux bridge (Vercel has no SSH).\n * - `checkpoint` — store the Vercel snapshot *id* in the manifest so restore\n * boots from it (Vercel snapshots are id-addressed, not name-addressed).\n *\n * `launchDockerd: false` because Vercel Sandbox can't run nested containers.\n */\n\nimport type { BoxRecord, Provider, ProviderCheckpoint } from '@agentbox/core';\nimport {\n createCloudProvider,\n listCloudCheckpoints,\n removeCloudCheckpointDir,\n resolveCloudCheckpoint,\n writeCloudCheckpointManifest,\n} from '@agentbox/sandbox-cloud';\nimport {\n vercelBackend,\n snapshotVercelSandbox,\n deleteVercelSnapshot,\n DEFAULT_BOX_IMAGE_REF,\n} from './backend.js';\nimport { prepareVercelProvider } from './prepare.js';\nimport { buildVercelAttach } from './build-attach.js';\n\nconst BACKEND_NAME = 'vercel';\n\nconst cloudProvider = createCloudProvider(vercelBackend, {\n // Vercel couples RAM to vCPU at 2048 MB/vCPU; disk is a fixed 32 GB NVMe.\n defaultResources: { cpu: 2, memory: 4, disk: 32 },\n launchDockerd: false,\n});\n\n/**\n * Vercel-specific checkpoint capability. Unlike the scaffold's default (which\n * stores a caller-chosen snapshot *name*), we capture the opaque Vercel\n * snapshot id and store THAT in the manifest's `snapshotName` field — the cloud\n * create flow passes `manifest.snapshotName` straight to\n * `provision({ snapshot })`, and the Vercel backend boots from it as a snapshot\n * id. (The scaffold's `cloudSnapshotName` project-scoping isn't needed — Vercel\n * snapshot ids are already globally unique.)\n */\nconst vercelCheckpoint: ProviderCheckpoint = {\n async create(box: BoxRecord, name: string) {\n if (!box.projectRoot) {\n throw new Error(\n 'cloud checkpoint requires the box to have a project root (run `agentbox checkpoint` from inside the project)',\n );\n }\n if (!box.cloud?.sandboxId) {\n throw new Error(`vercel box ${box.name} has no sandboxId — record is malformed`);\n }\n // NOTE: snapshotting stops the source sandbox; persistent mode resumes it\n // on the next call. Surfaced to the user in `agentbox checkpoint` docs.\n const snapshotId = await snapshotVercelSandbox(box.cloud.sandboxId);\n const info = await writeCloudCheckpointManifest(box.projectRoot, BACKEND_NAME, name, {\n snapshotName: snapshotId,\n sourceBoxId: box.id,\n sourceBoxName: box.name,\n });\n return { ref: info.name };\n },\n async list(projectRoot: string) {\n const entries = await listCloudCheckpoints(projectRoot, BACKEND_NAME);\n return entries.map((e) => ({ ref: e.name, createdAt: e.manifest.createdAt }));\n },\n async remove(projectRoot: string, ref: string) {\n const entry = await resolveCloudCheckpoint(projectRoot, BACKEND_NAME, ref);\n if (!entry) return;\n try {\n await deleteVercelSnapshot(entry.manifest.snapshotName);\n } catch {\n // best-effort: drop the local manifest even if the remote delete failed\n // (network/perms/already-gone) so the user isn't left with a dead pointer.\n }\n await removeCloudCheckpointDir(projectRoot, BACKEND_NAME, ref);\n },\n};\n\nexport const vercelProvider: Provider = {\n ...cloudProvider,\n prepare: prepareVercelProvider,\n buildAttach: buildVercelAttach,\n checkpoint: vercelCheckpoint,\n};\n\nexport { vercelBackend, DEFAULT_BOX_IMAGE_REF };\nexport { ensureVercelEnvLoaded, reloadVercelEnv } from './env-loader.js';\nexport { ensureVercelCredentials } from './credentials.js';\nexport type { EnsureVercelCredentialsOptions } from './credentials.js';\nexport {\n readVercelCredStatus,\n secretsPath,\n maskKey,\n type VercelCredStatus,\n} from './credentials.js';\nexport {\n prepareVercel,\n prepareVercelProvider,\n type PrepareVercelOptions,\n type PrepareVercelResult,\n} from './prepare.js';\nexport {\n ensureVercelBaseSnapshot,\n preparedStatePath,\n readPreparedState,\n writePreparedState,\n updatePreparedState,\n type PreparedVercelState,\n type PreparedVercelBase,\n} from './prepared-state.js';\nexport {\n RUNTIME_ASSETS,\n candidatesFor,\n resolveRuntimeAssets,\n findStagedCliRuntimeRoot,\n type RuntimeAsset,\n type ResolvedAsset,\n} from './runtime-assets.js';\nexport { buildVercelAttach } from './build-attach.js';\n","/**\n * Vercel `CloudBackend` — maps the provider-neutral cloud primitives onto\n * `@vercel/sandbox` v2 (Firecracker microVMs + snapshots). Composed into a full\n * `Provider` by `@agentbox/sandbox-cloud`'s `createCloudProvider`.\n *\n * Platform shape this backend is built around (see docs/cloud-providers.md):\n * - No custom image — sandboxes boot from a Vercel snapshot baked once by\n * `agentbox prepare --provider vercel`. `provision` always needs a snapshot\n * id (the prepared base, or a cloud-checkpoint snapshot).\n * - No SSH — `attachArgv` is intentionally omitted; the provider overrides\n * `buildAttach` with a Vercel-SDK-streaming helper instead.\n * - No nested containers — dockerd is disabled at the provider level.\n * - Persistent sandboxes auto-snapshot on stop and auto-resume on the next\n * `Sandbox.get({ resume: true })`, which is how pause/resume map cleanly.\n * - The sandbox's native user is `vercel-sandbox`; agentbox standardizes on\n * `vscode` (uid 1000), created by provision.sh. So `exec` drops privileges\n * to `vscode` (root → `sudo -u vscode`) unless the caller asks for root,\n * and `uploadFile` chowns to uid 1000 after the SDK writes as\n * `vercel-sandbox`.\n * - Max 4 exposed ports: we use 80 (WebProxy), 6080 (noVNC), 8788 (relay/ctl\n * bridge). One slot is left free for a future per-service expose.\n */\n\nimport { readFile } from 'node:fs/promises';\nimport type {\n CloudBackend,\n CloudExecOptions,\n CloudExecResult,\n CloudFileEntry,\n CloudHandle,\n CloudPreviewUrl,\n CloudProvisionRequest,\n CloudSandboxSummary,\n CloudState,\n} from '@agentbox/core';\nimport type { NetworkPolicy } from '@vercel/sandbox';\nimport {\n ensureFreshCredentials,\n resolveCredentials,\n Sandbox,\n Snapshot,\n type SandboxType,\n} from './sdk.js';\nimport {\n stageClaudeCredentialsForUpload,\n stageCodexCredentialsForUpload,\n stageOpencodeCredentialsForUpload,\n type StageResult,\n} from '@agentbox/sandbox-cloud';\nimport { withVercelRetry } from './retry.js';\nimport { readPreparedState } from './prepared-state.js';\n\n/** Sentinel image ref the cloud-provider hands us when no --image was passed. */\nexport const DEFAULT_BOX_IMAGE_REF = 'agentbox/box:dev';\n\n/** Box user agentbox standardizes on. provision.sh creates it (uid auto-assigned —\n * the Vercel default user may already hold 1000, and there are no bind mounts so\n * the exact uid is irrelevant). chown targets it by name, not number. */\nconst BOX_USER = 'vscode';\nconst BOX_OWNER = 'vscode:vscode';\n\n/**\n * Base ports exposed at create. Vercel REJECTS privileged ports (<1024) with a\n * 400, so we cannot expose the scaffold's WebProxy port 80 — the two base ports\n * are 6080 (noVNC) and 8788 (the relay/ctl bridge the host poller reaches via\n * `sandbox.domain(8788)`). The in-box WebProxy still binds 80, but it isn't\n * externally reachable on Vercel, so `agentbox url` for a web app on :80 stays a\n * documented limitation. The remaining slots (up to VERCEL_MAX_PORTS) are filled\n * at create from `agentbox.yaml` `expose:` ports so per-service preview URLs\n * actually route (see buildExposedPorts).\n */\nexport const VERCEL_EXPOSED_PORTS = [6080, 8788] as const;\n\n/** Vercel's hard per-sandbox exposed-port cap. */\nexport const VERCEL_MAX_PORTS = 4;\n\n/**\n * Merge requested `expose:` service ports into the base set: drop privileged\n * (<1024 — Vercel 400s) and out-of-range ports + dupes, preserve order, and cap\n * at Vercel's 4-port limit. A preview URL only routes to a port declared here at\n * create time, so this is what makes `services.*.expose` reachable on Vercel.\n */\nexport function buildExposedPorts(extra: readonly number[] | undefined): number[] {\n const ports = [...VERCEL_EXPOSED_PORTS] as number[];\n const seen = new Set<number>(ports);\n for (const p of extra ?? []) {\n if (ports.length >= VERCEL_MAX_PORTS) break;\n if (Number.isInteger(p) && p >= 1024 && p < 65_536 && !seen.has(p)) {\n ports.push(p);\n seen.add(p);\n }\n }\n return ports;\n}\n\n/**\n * Parse the `box.vercelNetworkPolicy` config string into a Vercel\n * `NetworkPolicy`. `''`/unset → undefined (SDK default = allow-all). The\n * literals `allow-all` / `deny-all` pass through; anything else is treated as a\n * comma-separated domain allowlist `{ allow: [...] }` (everything else denied).\n */\nexport function parseNetworkPolicy(raw: string | undefined): NetworkPolicy | undefined {\n const v = (raw ?? '').trim();\n if (v === '') return undefined;\n if (v === 'allow-all' || v === 'deny-all') return v;\n const allow = v\n .split(',')\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n return allow.length > 0 ? { allow } : undefined;\n}\n\n/**\n * Default per-session timeout. 45 min is the Hobby ceiling, so it's safe across\n * all plans; persistent mode makes a hit transparent (the VM auto-snapshots and\n * auto-resumes on the next SDK call). Pro/Enterprise users who want a longer\n * single session can rely on `extendTimeout` / future config.\n */\nconst DEFAULT_TIMEOUT_MS = 45 * 60_000;\n\n/** Keep exactly one auto-snapshot per box, never expiring, so a paused box can\n * always resume and storage stays flat. `destroy` purges it explicitly. */\nconst KEEP_LAST_SNAPSHOTS = { count: 1, expiration: 0, deleteEvicted: true } as const;\n\nfunction creds(): Partial<{ token: string; teamId: string; projectId: string }> {\n return resolveCredentials();\n}\n\n/** Single-quote a string for safe embedding inside a `bash -lc '<…>'`. */\nfunction shq(s: string): string {\n return \"'\" + s.replace(/'/g, \"'\\\\''\") + \"'\";\n}\n\nasync function getSandbox(id: string): Promise<SandboxType> {\n // resume:false — plain handle resolution; lifecycle methods opt into resume.\n return Sandbox.get({ name: id, resume: false, ...creds() });\n}\n\nasync function maybeGetSandbox(id: string): Promise<SandboxType | null> {\n try {\n return await getSandbox(id);\n } catch {\n return null;\n }\n}\n\n/**\n * Map Vercel's session status onto our 4-value `CloudState`. Transitional\n * states report as 'running' so callers don't ping-pong; 'stopped' maps to\n * 'paused' because a persistent sandbox keeps an auto-snapshot and resumes on\n * the next call (our pause semantics). 'aborted'/'failed' → 'missing'.\n */\nfunction mapState(s: string | undefined): CloudState {\n switch (s) {\n case 'running':\n return 'running';\n case 'pending':\n case 'stopping':\n case 'snapshotting':\n return 'running';\n case 'stopped':\n return 'paused';\n case 'aborted':\n case 'failed':\n default:\n return 'missing';\n }\n}\n\n/**\n * Build a `runCommand` invocation that runs `cmd` (already a shell string) as\n * the box user (`vscode`) by default, or as root when requested. Always starts\n * the SDK command as root (`sudo: true`) so the inner `sudo -u vscode` is\n * reliably passwordless, then drops privileges. cwd + env are applied inside\n * the dropped shell so they land in the right user/home context.\n */\nfunction buildRunCommand(\n cmd: string,\n opts?: CloudExecOptions,\n): { cmd: string; args: string[]; sudo: boolean } {\n const prelude: string[] = [];\n if (opts?.cwd) prelude.push(`cd ${shq(opts.cwd)}`);\n for (const [k, v] of Object.entries(opts?.env ?? {})) {\n // The value is shell-quoted, but the key is interpolated bare into a\n // `bash -lc` string that runs as root — reject anything that isn't a POSIX\n // env-var name so a key like `x;rm -rf /` can't inject a command.\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(k)) {\n throw new Error(`vercel exec: invalid env var name ${JSON.stringify(k)}`);\n }\n prelude.push(`export ${k}=${shq(v)}`);\n }\n const inner = [...prelude, cmd].join('\\n');\n const user = opts?.user ?? BOX_USER;\n if (user === 'root') {\n return { cmd: 'bash', args: ['-lc', inner], sudo: true };\n }\n return {\n cmd: 'bash',\n args: ['-lc', `sudo -u ${user} -H bash -lc ${shq(inner)}`],\n sudo: true,\n };\n}\n\n/**\n * Push the host's renewable agent credentials (`.credentials.json` for claude,\n * `auth.json` for codex/opencode) into `/home/vscode/.agentbox-creds/<agent>/`\n * per-box at create time. Vercel has no shared-volume primitive, so the cloud\n * credential-volume path (`seedAgentVolumesIfFresh`) is a no-op here — this is\n * the equivalent of Hetzner's scp push (`pushHetznerAgentCredentials`), over the\n * Vercel SDK instead. The credential-pivot symlinks baked into the snapshot by\n * provision.sh route `~/.claude/.credentials.json` etc. through to this dir, so\n * the in-box agents find their tokens at the expected paths. Pushes on every\n * create (tokens are renewable). Best-effort: a failure means the in-box agent\n * falls back to interactive login, so the caller logs + continues.\n */\nasync function pushVercelAgentCredentials(\n sb: SandboxType,\n log: (line: string) => void,\n): Promise<void> {\n const specs: Array<{\n kind: 'claude' | 'codex' | 'opencode';\n stage: () => Promise<StageResult>;\n dest: string;\n }> = [\n { kind: 'claude', stage: stageClaudeCredentialsForUpload, dest: '/home/vscode/.agentbox-creds/claude' },\n { kind: 'codex', stage: stageCodexCredentialsForUpload, dest: '/home/vscode/.agentbox-creds/codex' },\n { kind: 'opencode', stage: stageOpencodeCredentialsForUpload, dest: '/home/vscode/.agentbox-creds/opencode' },\n ];\n for (const spec of specs) {\n const staged = await spec.stage();\n for (const w of staged.warnings) log(`vercel: [${spec.kind}-creds] ${w}`);\n try {\n if (!staged.tarballPath) {\n log(`vercel: ${spec.kind}: no host credentials to push (skipping)`);\n continue;\n }\n const remote = `/tmp/agentbox-${spec.kind}-creds.tar.gz`;\n await sb.writeFiles([{ path: remote, content: await readFile(staged.tarballPath) }]);\n // Extract as vscode into the dest dir (created + chown'd by provision.sh's\n // credential-pivot step). Plain mkdir/tar/rm — no $()/loops — so the exec\n // wrapping is irrelevant.\n const extract =\n `sudo -u vscode mkdir -p ${spec.dest} && ` +\n `sudo -u vscode tar -xzf ${remote} -C ${spec.dest} --no-same-permissions --no-same-owner -m && ` +\n `rm -f ${remote}`;\n const r = await sb.runCommand({ cmd: 'bash', args: ['-lc', extract], sudo: true });\n if (r.exitCode !== 0) {\n log(`vercel: WARN — ${spec.kind} credential extract failed (exit ${String(r.exitCode)})`);\n } else {\n log(`vercel: ${spec.kind}: credentials pushed`);\n }\n } finally {\n await staged.cleanup();\n }\n }\n}\n\nexport const vercelBackend: CloudBackend = {\n name: 'vercel',\n\n async provision(req: CloudProvisionRequest): Promise<CloudHandle> {\n await ensureFreshCredentials();\n // Resolve the snapshot to boot from: an explicit cloud-checkpoint snapshot\n // (req.snapshot) wins, else the prepared base. Vercel can't build from a\n // Dockerfile, so there is no image fallback — fail loud with the fix.\n const snapshotId = req.snapshot ?? readPreparedState().base?.snapshotId;\n if (!snapshotId) {\n throw new Error(\n 'no Vercel base snapshot found.\\n' +\n 'Run `agentbox prepare --provider vercel` first — Vercel cannot build images ' +\n 'from a Dockerfile, so the base snapshot is a one-time prerequisite.',\n );\n }\n const networkPolicy = parseNetworkPolicy(req.networkPolicy);\n const log = req.onLog ?? (() => {});\n // No-retry: Sandbox.create is billable and non-idempotent — a timeout after\n // the request reached the origin could leave a duplicate sandbox we can't\n // reference for cleanup.\n const handle = await withVercelRetry(\n { method: 'provision', retryOnAmbiguous: false, attemptTimeoutMs: 900_000, backoffMs: [] },\n async () => {\n const sb = await Sandbox.create({\n name: req.name,\n source: { type: 'snapshot', snapshotId },\n resources: { vcpus: req.resources?.cpu ?? 2 },\n ports: buildExposedPorts(req.exposePorts),\n timeout: req.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n env: req.env,\n tags: { agentbox: 'true', 'agentbox.name': req.name },\n persistent: true,\n keepLastSnapshots: { ...KEEP_LAST_SNAPSHOTS },\n ...(networkPolicy ? { networkPolicy } : {}),\n ...creds(),\n });\n return { sandboxId: sb.name };\n },\n );\n // Push renewable agent credentials per-box (outside the billable, no-retry\n // create block so a push failure never affects create semantics).\n try {\n const sb = await getSandbox(handle.sandboxId);\n await pushVercelAgentCredentials(sb, log);\n } catch (err) {\n log(\n `vercel: WARN — agent credential push failed (${err instanceof Error ? err.message : String(err)}); ` +\n 'in-box claude/codex/opencode will prompt for interactive login',\n );\n }\n return handle;\n },\n\n async get(sandboxId: string): Promise<CloudHandle | null> {\n await ensureFreshCredentials();\n return withVercelRetry({ method: 'get', retryOnAmbiguous: true }, async () => {\n const sb = await maybeGetSandbox(sandboxId);\n return sb ? { sandboxId: sb.name } : null;\n });\n },\n\n async list(): Promise<CloudSandboxSummary[]> {\n await ensureFreshCredentials();\n return withVercelRetry({ method: 'list', retryOnAmbiguous: true }, async () => {\n const page = await Sandbox.list({ ...creds() });\n const items = await page.toArray();\n return items\n .filter((sb) => sb.tags?.['agentbox'] === 'true')\n .map((sb): CloudSandboxSummary => {\n const summary: CloudSandboxSummary = { sandboxId: sb.name };\n const friendly = sb.tags?.['agentbox.name'] ?? sb.name;\n if (friendly) summary.name = friendly;\n if (typeof sb.createdAt === 'number') {\n summary.createdAt = new Date(sb.createdAt).toISOString();\n }\n summary.state = mapState(sb.status);\n return summary;\n });\n });\n },\n\n async start(h: CloudHandle): Promise<void> {\n await ensureFreshCredentials();\n await withVercelRetry(\n { method: 'start', retryOnAmbiguous: true, attemptTimeoutMs: 120_000 },\n async () => {\n // resume:true auto-resumes a persistent sandbox from its current snapshot.\n await Sandbox.get({ name: h.sandboxId, resume: true, ...creds() });\n },\n );\n },\n\n async stop(h: CloudHandle): Promise<void> {\n await ensureFreshCredentials();\n await withVercelRetry(\n { method: 'stop', retryOnAmbiguous: true, attemptTimeoutMs: 120_000 },\n async () => {\n const sb = await getSandbox(h.sandboxId);\n // For a persistent sandbox this captures an auto-snapshot and shuts the\n // VM down — resume happens lazily on the next Sandbox.get.\n await sb.stop();\n },\n );\n },\n\n // pause == stop on Vercel (the auto-snapshot IS the cold-storage state).\n async pause(h: CloudHandle): Promise<void> {\n await this.stop(h);\n },\n\n async resume(h: CloudHandle): Promise<void> {\n await this.start(h);\n },\n\n async destroy(h: CloudHandle): Promise<void> {\n await ensureFreshCredentials();\n await withVercelRetry(\n { method: 'destroy', retryOnAmbiguous: true, attemptTimeoutMs: 120_000 },\n async () => {\n const sb = await maybeGetSandbox(h.sandboxId);\n if (!sb) return; // already gone — destroy is idempotent\n // Purge only a snapshot THIS box created (its own stop-time auto-\n // snapshot), never the shared base/source it booted from. A fresh box\n // has currentSnapshotId === sourceSnapshotId === the prepared base, and\n // deleting that would nuke the base snapshot every other box depends on.\n const snapId = sb.currentSnapshotId;\n const source = sb.sourceSnapshotId;\n const base = readPreparedState().base?.snapshotId;\n const ownSnapshot =\n snapId !== undefined && snapId !== source && snapId !== base;\n await sb.delete();\n if (ownSnapshot) {\n try {\n const snap = await Snapshot.get({ snapshotId: snapId, ...creds() });\n await snap.delete();\n } catch {\n // best-effort: a snapshot already gone is fine; the user can clean\n // stragglers from the Vercel dashboard.\n }\n }\n },\n );\n },\n\n async state(h: CloudHandle): Promise<CloudState> {\n await ensureFreshCredentials();\n return withVercelRetry({ method: 'state', retryOnAmbiguous: true }, async () => {\n const sb = await maybeGetSandbox(h.sandboxId);\n if (!sb) return 'missing';\n return mapState(sb.status);\n });\n },\n\n async exec(h: CloudHandle, cmd: string, opts?: CloudExecOptions): Promise<CloudExecResult> {\n await ensureFreshCredentials();\n return withVercelRetry(\n {\n method: 'exec',\n retryOnAmbiguous: opts?.noRetry ? false : true,\n attemptTimeoutMs: opts?.attemptTimeoutMs ?? 120_000,\n backoffMs: opts?.noRetry ? [] : undefined,\n },\n async () => {\n const sb = await getSandbox(h.sandboxId);\n const r = await sb.runCommand(buildRunCommand(cmd, opts));\n const [stdout, stderr] = await Promise.all([r.stdout(), r.stderr()]);\n return { exitCode: r.exitCode, stdout, stderr };\n },\n );\n },\n\n async uploadFile(h: CloudHandle, localPath: string, remotePath: string): Promise<void> {\n await ensureFreshCredentials();\n await withVercelRetry(\n { method: 'uploadFile', retryOnAmbiguous: true, attemptTimeoutMs: 300_000 },\n async () => {\n const content = await readFile(localPath);\n const sb = await getSandbox(h.sandboxId);\n await sb.writeFiles([{ path: remotePath, content }]);\n // writeFiles writes as `vercel-sandbox`; chown to the box user so the\n // scaffold's vscode-context reads/extractions succeed. Best-effort —\n // a chown failure on a world-readable /tmp staging file is harmless.\n try {\n await sb.runCommand({ cmd: 'chown', args: [BOX_OWNER, remotePath], sudo: true });\n } catch {\n // ignore — file is at least present and readable\n }\n },\n );\n },\n\n async downloadFile(h: CloudHandle, remotePath: string, localPath: string): Promise<void> {\n await ensureFreshCredentials();\n await withVercelRetry(\n { method: 'downloadFile', retryOnAmbiguous: true, attemptTimeoutMs: 300_000 },\n async () => {\n const sb = await getSandbox(h.sandboxId);\n const written = await sb.downloadFile(\n { path: remotePath },\n { path: localPath },\n { mkdirRecursive: true },\n );\n if (written === null) {\n throw new Error(`vercel downloadFile: source not found: ${remotePath}`);\n }\n },\n );\n },\n\n async listFiles(h: CloudHandle, remoteDir: string): Promise<CloudFileEntry[]> {\n await ensureFreshCredentials();\n return withVercelRetry({ method: 'listFiles', retryOnAmbiguous: true }, async () => {\n const sb = await getSandbox(h.sandboxId);\n const entries = await sb.fs.readdir(remoteDir, { withFileTypes: true });\n return entries.map((e) => ({ name: e.name, isDir: e.isDirectory() }));\n });\n },\n\n async previewUrl(h: CloudHandle, port: number): Promise<CloudPreviewUrl> {\n await ensureFreshCredentials();\n return withVercelRetry({ method: 'previewUrl', retryOnAmbiguous: true }, async () => {\n const sb = await getSandbox(h.sandboxId);\n // sb.domain(port) is a public HTTPS URL (no header token needed).\n return { url: sb.domain(port), token: undefined };\n });\n },\n\n // Fewer params than the interface's (h, port, expiresInSeconds) is fine —\n // Vercel sandbox domains are already public + browser-usable, so the signed\n // URL is just the standard one (the TTL is governed by the sandbox session\n // lifetime, not a per-URL signature, so the expiry arg is irrelevant here).\n async signedPreviewUrl(h: CloudHandle, port: number): Promise<CloudPreviewUrl> {\n return this.previewUrl(h, port);\n },\n\n // NOTE: no `createSnapshot`/`deleteSnapshot` here. Vercel snapshots are\n // addressed by an opaque id (not a caller-chosen name), which doesn't fit the\n // CloudBackend `createSnapshot(handle, name): void` contract — the provider\n // needs the id back to store it in the checkpoint manifest. The Vercel\n // provider therefore overrides the whole `checkpoint` capability in index.ts\n // using `snapshotVercelSandbox` / `deleteVercelSnapshot` below.\n};\n\n/**\n * Snapshot a running sandbox and return the resulting Vercel snapshot id.\n * `sb.snapshot()` stops the source sandbox as part of capture; persistent mode\n * resumes it on the next SDK call, so the box comes back automatically.\n */\nexport async function snapshotVercelSandbox(sandboxId: string): Promise<string> {\n await ensureFreshCredentials();\n return withVercelRetry(\n { method: 'createSnapshot', retryOnAmbiguous: false, attemptTimeoutMs: 900_000, backoffMs: [] },\n async () => {\n const sb = await getSandbox(sandboxId);\n const snap = await sb.snapshot({ expiration: 0 });\n return snap.snapshotId;\n },\n );\n}\n\n/** Delete a Vercel snapshot by id. Idempotent — a missing snapshot is success. */\nexport async function deleteVercelSnapshot(snapshotId: string): Promise<void> {\n await ensureFreshCredentials();\n await withVercelRetry({ method: 'deleteSnapshot', retryOnAmbiguous: true }, async () => {\n try {\n const snap = await Snapshot.get({ snapshotId, ...creds() });\n await snap.delete();\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (/not.?found|404/i.test(msg)) return; // idempotent\n throw err;\n }\n });\n}\n","/**\n * Bounded retry wrapper for Vercel Sandbox SDK calls — mirrors\n * `withDaytonaRetry` / `withHetznerRetry` in shape and intent. The Vercel\n * control plane rate-limits (429) and can return transient 5xx during\n * incidents; without bounded retries those propagate as wedges in the calling\n * lifecycle code.\n *\n * Non-idempotent ops (`provision`/`Sandbox.create`, `createSnapshot`) pass\n * `retryOnAmbiguous: false` so a timeout after the request reached the origin\n * doesn't create a duplicate billable sandbox/snapshot.\n */\n\nexport interface WithRetryOptions {\n method: string;\n /** Per-attempt timeout (ms). Default 30_000. */\n attemptTimeoutMs?: number;\n /** Backoff before attempts 2, 3, … (ms). Default [1000, 2000, 4000]. */\n backoffMs?: readonly number[];\n /**\n * Retry on errors where we can't be sure the server applied the request\n * (connection failures, per-attempt timeouts, 5xx). Set false for\n * non-idempotent operations where a retry could create a duplicate resource.\n */\n retryOnAmbiguous: boolean;\n /** Override the default stderr retry sink (used by tests). */\n onRetry?: (line: string) => void;\n}\n\nconst DEFAULT_BACKOFF: readonly number[] = [1000, 2000, 4000];\nconst DEFAULT_ATTEMPT_TIMEOUT_MS = 30_000;\n\nclass AttemptTimeoutError extends Error {\n constructor(method: string, ms: number) {\n super(`vercel ${method}: per-attempt timeout after ${String(ms)}ms`);\n this.name = 'AttemptTimeoutError';\n }\n}\n\nexport function isAttemptTimeout(err: unknown): err is AttemptTimeoutError {\n return err instanceof AttemptTimeoutError;\n}\n\n/** HTTP status code dug out of whatever error shape the SDK throws. */\nfunction statusCodeOf(err: unknown): number | undefined {\n if (!err || typeof err !== 'object') return undefined;\n for (const key of ['statusCode', 'status', 'code'] as const) {\n const v = (err as Record<string, unknown>)[key];\n if (typeof v === 'number') return v;\n }\n const resp = (err as { response?: { status?: unknown } }).response;\n if (resp && typeof resp.status === 'number') return resp.status;\n return undefined;\n}\n\nexport function isRetriable(err: unknown, allowAmbiguous: boolean): boolean {\n if (err instanceof AttemptTimeoutError) return allowAmbiguous;\n\n const status = statusCodeOf(err);\n if (status !== undefined) {\n if (status === 429) return true; // rate limited — the server told us to wait\n if (status >= 500 && status <= 599) return allowAmbiguous;\n return false; // 4xx (auth, validation, not_found) — permanent\n }\n\n // Raw fetch / undici errors. Node wraps low-level errors in `{ cause }`.\n if (err && typeof err === 'object') {\n const candidates: unknown[] = [err, (err as { cause?: unknown }).cause];\n for (const c of candidates) {\n if (!c || typeof c !== 'object') continue;\n const code = (c as { code?: unknown }).code;\n if (\n code === 'ECONNRESET' ||\n code === 'ETIMEDOUT' ||\n code === 'ECONNABORTED' ||\n code === 'EAI_AGAIN' ||\n code === 'ECONNREFUSED' ||\n code === 'ENOTFOUND' ||\n code === 'UND_ERR_SOCKET' ||\n code === 'UND_ERR_CONNECT_TIMEOUT'\n ) {\n return allowAmbiguous;\n }\n }\n }\n return false;\n}\n\nexport async function withVercelRetry<T>(\n opts: WithRetryOptions,\n fn: () => Promise<T>,\n): Promise<T> {\n const backoff = opts.backoffMs ?? DEFAULT_BACKOFF;\n const maxAttempts = backoff.length + 1;\n const timeoutMs = opts.attemptTimeoutMs ?? DEFAULT_ATTEMPT_TIMEOUT_MS;\n const log = opts.onRetry ?? defaultRetryLog;\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n return await raceTimeout(fn(), timeoutMs, opts.method);\n } catch (err) {\n const last = attempt === maxAttempts;\n if (last || !isRetriable(err, opts.retryOnAmbiguous)) throw err;\n const delay = backoff[attempt - 1] ?? backoff[backoff.length - 1] ?? 4000;\n log(\n `vercel ${opts.method}: attempt ${String(attempt)} failed (${errorSummary(err)}); retrying in ${String(delay)}ms`,\n );\n await sleep(delay);\n }\n }\n throw new Error(`withVercelRetry: exhausted attempts for ${opts.method}`);\n}\n\nfunction defaultRetryLog(line: string): void {\n process.stderr.write(`\\n[vercel-retry] ${line}\\n`);\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\nasync function raceTimeout<T>(p: Promise<T>, ms: number, method: string): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n p,\n new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => reject(new AttemptTimeoutError(method, ms)), ms);\n }),\n ]);\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n }\n}\n\nfunction errorSummary(err: unknown): string {\n if (err instanceof Error) {\n const status = statusCodeOf(err);\n return status !== undefined\n ? `${err.name}(${String(status)}): ${truncate(err.message)}`\n : `${err.name}: ${truncate(err.message)}`;\n }\n return truncate(String(err));\n}\n\nfunction truncate(s: string, max = 160): string {\n return s.length > max ? `${s.slice(0, max)}…` : s;\n}\n","/**\n * Persisted record of what `agentbox prepare --provider vercel` has built.\n * Lives at `~/.agentbox/vercel-prepared.json` so the auto-prepare gate\n * (`ensureVercelBaseSnapshot()`) and `backend.provision` can resolve the base\n * snapshot to boot every box from.\n *\n * Single tier for now — the shared base snapshot (AL2023 + deps + agentbox-ctl\n * + agents). A per-project snapshot tier (matching the hetzner/daytona shape)\n * is a future optimization tracked in docs/vercel-backlog.md.\n *\n * Schema versioned so future shape changes can migrate; only `schema: 1` is\n * accepted today.\n */\n\nimport { readPreparedStateRaw, writePreparedStateRaw, preparedStatePathFor } from '@agentbox/sandbox-core';\n\nconst SCHEMA = 1 as const;\n\nexport interface PreparedVercelBase {\n /** Vercel snapshot id (opaque). The thing `Sandbox.create({ source }) ` boots from. */\n snapshotId: string;\n /** Deterministic SHA-256 of the prepare build context (provision.sh + assets). */\n contextSha256?: string;\n /** CLI version that produced this snapshot (informational). */\n cliVersion?: string;\n /** Git short SHA of the CLI build (informational). */\n cliCommit?: string;\n /** ISO timestamp of bake completion. */\n createdAt: string;\n}\n\nexport interface PreparedVercelState {\n schema: typeof SCHEMA;\n /** The shared base snapshot. Absent until first `agentbox prepare`. */\n base?: PreparedVercelBase;\n}\n\nexport function preparedStatePath(): string {\n return preparedStatePathFor('vercel');\n}\n\nexport function readPreparedState(): PreparedVercelState {\n const raw = readPreparedStateRaw('vercel');\n if (raw === null || typeof raw !== 'object') return { schema: SCHEMA };\n const parsed = raw as Partial<PreparedVercelState>;\n if (parsed.schema !== SCHEMA) {\n // Unknown/missing schema: refuse to read — the next prepare overwrites it.\n return { schema: SCHEMA };\n }\n return { schema: SCHEMA, base: parsed.base };\n}\n\nexport function writePreparedState(state: PreparedVercelState): void {\n writePreparedStateRaw('vercel', state);\n}\n\n/** Update one field of the state without forcing callers to read/merge/write. */\nexport function updatePreparedState(mutate: (s: PreparedVercelState) => void): void {\n const s = readPreparedState();\n mutate(s);\n writePreparedState(s);\n}\n\n/**\n * First-use gate. If no base snapshot is recorded, throw an actionable error\n * pointing at `agentbox prepare --provider vercel`. Called by `backend.provision`\n * (indirectly via the snapshot resolution) and usable by the CLI.\n */\nexport function ensureVercelBaseSnapshot(): void {\n const state = readPreparedState();\n if (state.base !== undefined) return;\n throw new Error(\n 'no Vercel base snapshot found.\\n' +\n 'Run `agentbox prepare --provider vercel` first — Vercel cannot build images ' +\n 'from a Dockerfile, so the base snapshot is a one-time prerequisite for cloud boxes.',\n );\n}\n","/**\n * `agentbox prepare --provider vercel` — bake the per-team Vercel base\n * snapshot. Vercel can't build an image from a Dockerfile, so (like hetzner)\n * we boot a fresh sandbox, run an installer, and snapshot the result. That\n * snapshot id is what every per-box `create` boots from.\n *\n * Flow:\n * 1. Resolve runtime assets + fingerprint the build context. Skip the bake\n * when an up-to-date base snapshot already exists (unless --force).\n * 2. `Sandbox.create({ runtime: 'node24', persistent: false })` — fresh AL2023.\n * 3. `writeFiles` the assets (ctl bundle, helpers, baked configs, provision.sh).\n * 4. Run provision.sh as root, streaming output to the prepare log.\n * 5. Stage host agent static config (claude/codex/opencode) into the snapshot.\n * 6. `sandbox.snapshot({ expiration: 0 })` → the never-expiring base snapshot.\n * 7. Persist the snapshot id into ~/.agentbox/vercel-prepared.json.\n * 8. Delete the builder sandbox.\n *\n * Step 8 is safe: a Vercel snapshot is an independent, id-addressed resource\n * that survives its source sandbox's deletion (verified live — snapshot stays\n * `status: 'created'` and boots a fresh sandbox after the builder is deleted).\n * We delete it best-effort *after* the snapshot id is persisted, so a delete\n * failure only leaves a lingering sandbox for Vercel's reaper, never a broken\n * bake.\n */\n\nimport { readFile } from 'node:fs/promises';\nimport { Writable } from 'node:stream';\nimport type { Provider } from '@agentbox/core';\nimport { computeContextSha256, readCliStamp } from '@agentbox/sandbox-core';\nimport {\n stageClaudeStaticForUpload,\n stageCodexStaticForUpload,\n stageOpencodeStaticForUpload,\n type StageResult,\n} from '@agentbox/sandbox-cloud';\nimport { ensureVercelCredentials } from './credentials.js';\nimport {\n ensureFreshCredentials,\n resolveCredentials,\n Sandbox,\n Snapshot,\n type SandboxType,\n} from './sdk.js';\nimport {\n preparedStatePath,\n readPreparedState,\n writePreparedState,\n} from './prepared-state.js';\nimport {\n findStagedCliRuntimeRoot,\n resolveRuntimeAssets,\n type ResolvedAsset,\n} from './runtime-assets.js';\n\nexport interface PrepareVercelOptions {\n name?: string;\n hostWorkspace?: string;\n /** Force re-bake even when an up-to-date base snapshot is recorded. */\n force?: boolean;\n /** vCPUs for the builder sandbox (default 4 for a fast bake). */\n vcpus?: number;\n /** CLI runtime tree (set by the CLI to its dist neighbor). */\n cliRuntimeRoot?: string;\n /** Repo root for the dev fallback (defaults to a cwd-walk). */\n repoRoot?: string;\n onLog?: (line: string) => void;\n}\n\nexport interface PrepareVercelResult {\n snapshotName?: string;\n}\n\nconst BUILDER_TIMEOUT_MS = 25 * 60_000;\nconst SHELL = '/bin/bash';\n\nexport async function prepareVercel(\n opts: PrepareVercelOptions = {},\n): Promise<PrepareVercelResult> {\n await ensureVercelCredentials();\n await ensureFreshCredentials();\n const creds = resolveCredentials();\n const log = opts.onLog ?? (() => {});\n const progress = (s: string) => log(`prepare-vercel: ${s}`);\n\n const assets = resolveRuntimeAssets({\n cliRuntimeRoot: opts.cliRuntimeRoot ?? findStagedCliRuntimeRoot(),\n repoRoot: opts.repoRoot,\n });\n const contextSha = await computeContextSha256(\n assets.map((a) => ({ rel: a.name, abs: a.localPath })),\n );\n\n // Skip-fast: existing base snapshot still on Vercel + matching fingerprint.\n const existing = readPreparedState();\n if (!opts.force && existing.base) {\n const stillThere = await snapshotExists(existing.base.snapshotId, creds);\n if (stillThere && existing.base.contextSha256 === contextSha) {\n progress(\n `base snapshot ${existing.base.snapshotId} already exists (fingerprint ${contextSha.slice(0, 12)} matches); skipping (pass --force to rebuild)`,\n );\n return { snapshotName: existing.base.snapshotId };\n }\n if (!stillThere) {\n progress(`recorded base snapshot ${existing.base.snapshotId} is gone on Vercel; rebuilding`);\n } else {\n progress(\n `build context changed (was ${existing.base.contextSha256?.slice(0, 12) ?? '<none>'}, now ${contextSha.slice(0, 12)}); rebuilding`,\n );\n }\n }\n\n progress(`creating builder sandbox (node24, ${String(opts.vcpus ?? 4)} vcpus)`);\n const sb = await Sandbox.create({\n runtime: 'node24',\n resources: { vcpus: opts.vcpus ?? 4 },\n timeout: BUILDER_TIMEOUT_MS,\n tags: { agentbox: 'true', 'agentbox.role': 'prepare' },\n persistent: false,\n ...creds,\n });\n progress(`builder sandbox ${sb.name} up`);\n\n // 3. Upload assets.\n progress(`uploading ${String(assets.length)} runtime asset(s)`);\n await sb.writeFiles(\n await Promise.all(\n assets.map(async (a: ResolvedAsset) => ({\n path: a.remotePath,\n content: await readFile(a.localPath),\n mode: a.remoteMode,\n })),\n ),\n );\n\n // 4. Run provision.sh as root, streaming output.\n progress('running provision.sh (this takes a few minutes)');\n const install = await sb.runCommand({\n cmd: SHELL,\n args: ['-lc', 'bash /tmp/agentbox-provision.sh 2>&1'],\n sudo: true,\n stdout: lineSink((l) => log(`[provision] ${l}`)),\n stderr: lineSink((l) => log(`[provision] ${l}`)),\n });\n if (install.exitCode !== 0) {\n throw new Error(`provision.sh failed on the builder sandbox (exit ${String(install.exitCode)})`);\n }\n progress('provision.sh complete');\n\n // 5. Stage host agent static config into the snapshot (best-effort).\n await stageAgentConfig(sb, opts.hostWorkspace, log);\n\n // 6. Snapshot (never expires). NOTE: this stops the builder sandbox.\n progress('creating base snapshot (expiration: never)');\n const snap = await sb.snapshot({ expiration: 0 });\n progress(`snapshot created: ${snap.snapshotId}`);\n\n // 7. Persist.\n const cliStamp = readCliStamp();\n writePreparedState({\n schema: 1,\n base: {\n snapshotId: snap.snapshotId,\n contextSha256: contextSha,\n cliVersion: cliStamp.cliVersion,\n cliCommit: cliStamp.cliCommit,\n createdAt: new Date().toISOString(),\n },\n });\n progress(`wrote ${preparedStatePath()}`);\n\n // 8. Delete the builder. The snapshot is an independent resource that\n // survives this (verified live), and its id is already persisted above, so\n // this is best-effort: a failure just leaves the sandbox for Vercel's reaper.\n progress('deleting builder sandbox');\n try {\n await sb.delete();\n progress('builder sandbox deleted');\n } catch (err) {\n progress(\n `builder delete failed (left for Vercel reaper): ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n progress(`prepare complete — base snapshot ${snap.snapshotId}`);\n return { snapshotName: snap.snapshotId };\n}\n\nasync function snapshotExists(\n snapshotId: string,\n creds: Partial<{ token: string; teamId: string; projectId: string }>,\n): Promise<boolean> {\n try {\n const snap = await Snapshot.get({ snapshotId, ...creds });\n // `Snapshot.get` resolves even for a deleted/failed snapshot (status field),\n // so a bare \"didn't throw\" wrongly skip-passes a tombstone. Only a 'created'\n // snapshot is bootable — anything else means rebuild.\n return snap.status === 'created';\n } catch {\n return false;\n }\n}\n\nasync function stageAgentConfig(\n sb: SandboxType,\n hostWorkspace: string | undefined,\n log: (line: string) => void,\n): Promise<void> {\n const progress = (s: string) => log(`prepare-vercel: ${s}`);\n progress('staging host agent static config');\n const stagings: Array<{ kind: 'claude' | 'codex' | 'opencode'; tar: StageResult; dest: string }> = [];\n try {\n const claudeTar = await stageClaudeStaticForUpload({ hostWorkspace });\n for (const w of claudeTar.warnings) progress(w);\n if (claudeTar.tarballPath) stagings.push({ kind: 'claude', tar: claudeTar, dest: '/home/vscode/.claude' });\n else await claudeTar.cleanup();\n\n const codexTar = await stageCodexStaticForUpload();\n for (const w of codexTar.warnings) progress(w);\n if (codexTar.tarballPath) stagings.push({ kind: 'codex', tar: codexTar, dest: '/home/vscode/.codex' });\n else await codexTar.cleanup();\n\n const opencodeTar = await stageOpencodeStaticForUpload();\n for (const w of opencodeTar.warnings) progress(w);\n if (opencodeTar.tarballPath) stagings.push({ kind: 'opencode', tar: opencodeTar, dest: '/home/vscode/.local/share/opencode' });\n else await opencodeTar.cleanup();\n\n for (const s of stagings) {\n const remote = `/tmp/agentbox-${s.kind}-static.tar.gz`;\n progress(`uploading ${s.kind} static config`);\n await sb.writeFiles([{ path: remote, content: await readFile(s.tar.tarballPath as string) }]);\n // Extract as vscode so files land owned by the box user. The dest dir\n // already exists (provision.sh's credential-pivot step) — extract into it.\n const extract =\n `sudo -u vscode mkdir -p ${s.dest} && ` +\n `sudo -u vscode tar -xzf ${remote} -C ${s.dest} --no-same-permissions --no-same-owner -m && ` +\n `rm -f ${remote}`;\n const r = await sb.runCommand({ cmd: SHELL, args: ['-lc', extract], sudo: true });\n if (r.exitCode !== 0) {\n progress(`WARN: ${s.kind} static extract failed (exit ${String(r.exitCode)}) — continuing`);\n } else {\n progress(`baked ${s.kind} static config into snapshot`);\n }\n }\n } finally {\n for (const s of stagings) await s.tar.cleanup();\n }\n}\n\n/**\n * Adapt a line-callback to the `Writable` the SDK's `runCommand` streams into.\n * Buffers partial lines so each `onLine` gets a complete line.\n */\nfunction lineSink(onLine: (line: string) => void): Writable {\n let buf = '';\n return new Writable({\n write(chunk: Buffer, _enc: BufferEncoding, cb: () => void) {\n buf += chunk.toString('utf8');\n let nl: number;\n while ((nl = buf.indexOf('\\n')) !== -1) {\n onLine(buf.slice(0, nl));\n buf = buf.slice(nl + 1);\n }\n cb();\n },\n final(cb: () => void) {\n if (buf.length > 0) onLine(buf);\n cb();\n },\n });\n}\n\n/** Provider-level binding used by the CLI's `prepare` command. */\nexport const prepareVercelProvider: NonNullable<Provider['prepare']> = (req) =>\n prepareVercel({\n name: req.name,\n hostWorkspace: req.hostWorkspace ?? process.cwd(),\n force: req.force,\n onLog: req.onLog,\n });\n","/**\n * Resolver for the on-disk files shipped into a fresh Vercel sandbox during\n * `prepareVercel()`. Same idea as the hetzner resolver: a flat list of files to\n * upload via `sandbox.writeFiles`, each resolved from either the staged CLI\n * runtime tree or the monorepo source tree.\n *\n * Lookup order per file:\n * 1. The CLI's staged runtime tree: `<cliRoot>/runtime/vercel/...`.\n * 2. The monorepo source tree (dev fallback) under `packages/`.\n *\n * Any missing file throws a clear error naming the paths tried. Note: no\n * dockerd helper — Vercel can't run nested containers.\n */\n\nimport { existsSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst SELF = dirname(fileURLToPath(import.meta.url));\n\nexport function findStagedCliRuntimeRoot(): string | undefined {\n const candidates = [\n resolve(SELF, '..', 'runtime'),\n resolve(SELF, '..', '..', 'runtime'),\n ];\n for (const c of candidates) {\n if (existsSync(resolve(c, 'vercel', 'scripts', 'provision.sh'))) return c;\n }\n return undefined;\n}\n\nexport interface RuntimeAsset {\n /** Logical name (used in error messages + log lines). */\n name: string;\n /** Absolute path on the box (writeFiles target). */\n remotePath: string;\n /** File mode to apply after upload. */\n remoteMode: number;\n}\n\n/**\n * Where each asset lands inside the sandbox. provision.sh reads them from these\n * fixed paths. The agent/runtime helpers go straight to /usr/local/bin; baked\n * config files to /tmp for provision.sh to `install` into place.\n */\nexport const RUNTIME_ASSETS: readonly RuntimeAsset[] = [\n { name: 'provision.sh', remotePath: '/tmp/agentbox-provision.sh', remoteMode: 0o755 },\n { name: 'agentbox-ctl', remotePath: '/tmp/agentbox-ctl', remoteMode: 0o755 },\n { name: 'agentbox-vnc-start', remotePath: '/tmp/agentbox-vnc-start', remoteMode: 0o755 },\n { name: 'agentbox-checkpoint-cleanup', remotePath: '/tmp/agentbox-checkpoint-cleanup', remoteMode: 0o755 },\n { name: 'agentbox-open', remotePath: '/tmp/agentbox-open', remoteMode: 0o755 },\n { name: 'gh-shim', remotePath: '/tmp/agentbox-gh-shim', remoteMode: 0o755 },\n { name: 'git-shim', remotePath: '/tmp/agentbox-git-shim', remoteMode: 0o755 },\n { name: 'custom-system-CLAUDE.md', remotePath: '/tmp/agentbox-custom-CLAUDE.md', remoteMode: 0o644 },\n { name: 'claude-managed-settings.json', remotePath: '/tmp/agentbox-managed-settings.json', remoteMode: 0o644 },\n { name: 'agentbox-codex-hooks.json', remotePath: '/tmp/agentbox-codex-hooks.json', remoteMode: 0o644 },\n { name: 'agentbox-setup-skill.md', remotePath: '/tmp/agentbox-setup-skill.md', remoteMode: 0o644 },\n] as const;\n\nexport interface ResolvedAsset extends RuntimeAsset {\n localPath: string;\n}\n\nexport function candidatesFor(\n name: string,\n opts: { cliRuntimeRoot?: string; repoRoot?: string } = {},\n): string[] {\n const cliRoot = opts.cliRuntimeRoot;\n const monorepo = opts.repoRoot ?? guessRepoRoot();\n\n const monorepoRelative: Record<string, string[]> = {\n 'provision.sh': ['packages/sandbox-vercel/scripts/provision.sh'],\n 'agentbox-ctl': ['packages/ctl/dist/bin.cjs'],\n 'agentbox-vnc-start': ['packages/sandbox-docker/scripts/agentbox-vnc-start'],\n 'agentbox-checkpoint-cleanup': ['packages/sandbox-docker/scripts/agentbox-checkpoint-cleanup'],\n 'agentbox-open': ['packages/sandbox-docker/scripts/agentbox-open'],\n 'gh-shim': ['packages/sandbox-docker/scripts/gh-shim'],\n 'git-shim': ['packages/sandbox-docker/scripts/git-shim'],\n 'custom-system-CLAUDE.md': ['packages/sandbox-vercel/scripts/custom-system-CLAUDE.md'],\n 'claude-managed-settings.json': ['packages/sandbox-docker/scripts/claude-managed-settings.json'],\n 'agentbox-codex-hooks.json': ['packages/sandbox-docker/scripts/agentbox-codex-hooks.json'],\n 'agentbox-setup-skill.md': ['apps/cli/share/agentbox-setup/SKILL.md'],\n };\n\n const cliRelative: Record<string, string[]> = {\n 'provision.sh': ['vercel/scripts/provision.sh'],\n 'agentbox-ctl': ['vercel/ctl.cjs'],\n 'agentbox-vnc-start': ['vercel/agentbox-vnc-start', 'docker/packages/sandbox-docker/scripts/agentbox-vnc-start'],\n 'agentbox-checkpoint-cleanup': ['vercel/agentbox-checkpoint-cleanup', 'docker/packages/sandbox-docker/scripts/agentbox-checkpoint-cleanup'],\n 'agentbox-open': ['vercel/agentbox-open', 'docker/packages/sandbox-docker/scripts/agentbox-open'],\n 'gh-shim': ['vercel/gh-shim', 'docker/packages/sandbox-docker/scripts/gh-shim'],\n 'git-shim': ['vercel/git-shim', 'docker/packages/sandbox-docker/scripts/git-shim'],\n 'custom-system-CLAUDE.md': ['vercel/custom-system-CLAUDE.md'],\n 'claude-managed-settings.json': ['vercel/claude-managed-settings.json', 'docker/packages/sandbox-docker/scripts/claude-managed-settings.json'],\n 'agentbox-codex-hooks.json': ['vercel/agentbox-codex-hooks.json', 'docker/packages/sandbox-docker/scripts/agentbox-codex-hooks.json'],\n 'agentbox-setup-skill.md': ['vercel/agentbox-setup-skill.md', 'docker/apps/cli/share/agentbox-setup/SKILL.md'],\n };\n\n const out: string[] = [];\n if (cliRoot) {\n for (const rel of cliRelative[name] ?? []) out.push(resolve(cliRoot, rel));\n }\n for (const rel of monorepoRelative[name] ?? []) out.push(resolve(monorepo, rel));\n return out;\n}\n\nexport function resolveRuntimeAssets(\n opts: { cliRuntimeRoot?: string; repoRoot?: string } = {},\n): ResolvedAsset[] {\n const out: ResolvedAsset[] = [];\n const missing: Array<{ name: string; tried: string[] }> = [];\n for (const asset of RUNTIME_ASSETS) {\n const cands = candidatesFor(asset.name, opts);\n const hit = cands.find((p) => existsSync(p));\n if (!hit) {\n missing.push({ name: asset.name, tried: cands });\n continue;\n }\n out.push({ ...asset, localPath: hit });\n }\n if (missing.length > 0) {\n const lines = missing.flatMap((m) => [` - ${m.name}: tried`, ...m.tried.map((p) => ` ${p}`)]);\n throw new Error(\n `vercel: could not resolve runtime assets needed to bake the base snapshot:\\n` +\n lines.join('\\n') +\n `\\n\\nIf running from the monorepo, ensure \\`pnpm -w build\\` has run so packages/ctl/dist/bin.cjs exists.`,\n );\n }\n return out;\n}\n\nfunction guessRepoRoot(): string {\n let cur = SELF;\n for (let i = 0; i < 8; i++) {\n if (existsSync(resolve(cur, 'pnpm-workspace.yaml'))) return cur;\n const parent = dirname(cur);\n if (parent === cur) break;\n cur = parent;\n }\n return SELF;\n}\n","/**\n * `buildVercelAttach` — the Vercel provider's override of `Provider.buildAttach`.\n *\n * Vercel has no SSH, so the cloud scaffold's `ssh … -t '<cmd>'` argv is unusable.\n * Instead we drive the official Vercel Sandbox CLI (`sbx`/`sandbox`), which has a\n * real interactive PTY (`sbx exec -i`) and streams non-interactive output live —\n * giving a proper terminal with none of the old send-keys/capture-pane polling.\n *\n * Argv shape (validated against sbx 3.0.1):\n * sbx exec --sudo [-i] --project <p> --scope <team> <name>\n * -- sudo -u vscode -H bash -lc '<inner>'\n *\n * Notes:\n * - The sandbox's default exec user is `vercel-sandbox`; we pass `--sudo` (runs\n * as root) and then `sudo -u vscode -H` so tmux/agents run as the box user in\n * /workspace. Passing `sudo -u vscode …` directly as sbx's argv (not wrapped\n * in an outer `bash -lc`) avoids a double-`bash -lc` re-parse.\n * - `-i` only for interactive shell/agent attaches; detached pre-start and logs\n * run non-interactively (live stdout stream).\n * - The access token is passed via the child env (`VERCEL_AUTH_TOKEN`), never in\n * argv, so it can't leak through `ps`. project/scope are not secret → flags.\n * - `<inner>` is the shared cloud `renderInnerCommand` (same tmux ensure +\n * footer-aware config + `exec tmux attach` used by hetzner/daytona).\n */\n\nimport {\n type AttachKind,\n type AttachSpec,\n type BoxRecord,\n type BuildAttachOptions,\n} from '@agentbox/core';\nimport { renderInnerCommand } from '@agentbox/sandbox-cloud';\nimport { detectSbx } from './sbx-cli.js';\nimport { ensureFreshCredentials, resolveCredentials } from './sdk.js';\n\nexport async function buildVercelAttach(\n box: BoxRecord,\n kind: AttachKind,\n opts?: BuildAttachOptions,\n): Promise<AttachSpec> {\n const sandboxId = box.cloud?.sandboxId;\n if (!sandboxId) {\n throw new Error(`vercel box ${box.name} has no sandboxId — record is malformed`);\n }\n\n const det = await detectSbx();\n if (!det.installed || !det.bin) {\n throw new Error(\n 'Vercel interactive attach needs the Vercel `sandbox` CLI — run ' +\n '`agentbox vercel login` (it installs it) or `npm install -g sandbox`.',\n );\n }\n\n await ensureFreshCredentials();\n const { token, teamId, projectId } = resolveCredentials();\n\n // Interactive (real PTY) only for live shell/agent attaches. Detached\n // pre-start and logs stream non-interactively.\n const interactive = (kind === 'shell' || kind === 'agent') && !opts?.detached;\n\n // `sbx exec` (unlike `ssh -t`) forwards neither TERM nor the locale, so the\n // box session lands in TERM=unknown + an ASCII (POSIX) locale — tmux then\n // collapses Claude Code's Unicode glyphs (logo, spinner, box-drawing) to `_`.\n // Force a UTF-8 locale + a 256color TERM (matching the host PTY wrapper) so\n // the tmux server + the agent it spawns render correctly.\n const envPrelude = 'export LANG=C.UTF-8 LC_ALL=C.UTF-8 TERM=xterm-256color; ';\n const inner = envPrelude + renderInnerCommand(kind, opts);\n\n const argv = [\n det.bin,\n 'exec',\n '--sudo',\n ...(interactive ? ['-i'] : []),\n '--project',\n projectId,\n '--scope',\n teamId,\n sandboxId,\n '--',\n 'sudo',\n '-u',\n 'vscode',\n '-H',\n 'bash',\n '-lc',\n inner,\n ];\n\n return { argv, env: { VERCEL_AUTH_TOKEN: token } };\n}\n"],"mappings":"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","names":["readFile","creds","readFile"]}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|