@luxorai/agentbox-provider 0.0.0-stage → 0.2.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/backend.ts","../src/sdk.ts","../src/env-loader.ts","../src/retry.ts","../src/prepared-state.ts","../src/build-attach.ts","../src/prepare.ts","../src/credentials.ts","../src/secrets.ts","../src/provider-module.ts"],"sourcesContent":["/**\n * The Tenki sandbox provider. A thin `CloudBackend` over the\n * `@tenkicloud/sandbox` SDK, composed via the provider SDK's\n * `createCloudProvider` for everything provider-agnostic (workspace seeding,\n * ctl launch, state, relay polling).\n *\n * Three capabilities are overridden on top of the cloud scaffold:\n * - `prepare` — bake the AgentBox base snapshot in the Tenki workspace\n * (`agentbox prepare --provider tenki`).\n * - `buildAttach` — the host's own `ssh` client, pointed at the session\n * through a `ProxyCommand` that bridges stdio to\n * `session.ssh()`. Requires OpenSSH on the host.\n * - `checkpoint` — store the Tenki snapshot id in the manifest so restore\n * boots from it (`createSnapshotAndWait` returns an id-addressed snapshot).\n *\n * `launchDockerd: true` — in-box Docker works on Tenki: verified on a live\n * VM with cgroup2 and native overlay2 (not the vfs fallback), brought up by\n * the shared `agentbox-dockerd-start` launcher. Containers need no nested\n * virtualization, so the absence of /dev/kvm doesn't matter. The daemon is\n * started by the same idempotent bootstrap kick used on create and resume.\n */\n\nimport {\n createCloudProvider,\n currentCloudBaseFingerprint,\n listCloudCheckpoints,\n readCliStamp,\n removeCloudCheckpointDir,\n resolveCloudCheckpoint,\n SDK_API_VERSION,\n writeCloudCheckpointManifest,\n type BoxRecord,\n type CreateBoxRequest,\n type CreatedBox,\n type Provider,\n type ProviderCheckpoint,\n type ProviderModule,\n} from '@madarco/agentbox-provider-sdk';\nimport { DEFAULT_MAX_DURATION_MS, tenkiBackend } from './backend.js';\nimport { getTenkiClient } from './sdk.js';\nimport { readAgentboxEnv } from './env-loader.js';\nimport { withTenkiRetry } from './retry.js';\nimport { prepareTenkiProvider } from './prepare.js';\nimport { buildTenkiAttach } from './build-attach.js';\nimport { currentTenkiBaseFingerprintLive } from './prepared-state.js';\nimport { ensureTenkiCredentials, setTenkiCredentials } from './credentials.js';\nimport { doctorChecks, readCredStatusSummary } from './provider-module.js';\n\n/** The AgentBox provider name. `agentbox create --provider tenki` matches on this exactly. */\nexport const PROVIDER_NAME = 'tenki';\n/** Luxor AI alias: `--provider luxor` runs the same provider, backend and state as `tenki`. */\nexport const LUXOR_PROVIDER_NAME = 'luxor';\n\nconst BACKEND_NAME = PROVIDER_NAME;\n\nconst cloudProvider = createCloudProvider(tenkiBackend, {\n defaultResources: { cpu: 2, memory: 4, disk: 8 },\n launchDockerd: true,\n});\n\n/** Session lifetime override, in ms. Falsy/invalid values fall through to the default. */\nfunction envTimeoutMs(): number | undefined {\n const raw = readAgentboxEnv('TIMEOUT_MS');\n if (!raw) return undefined;\n const n = Number(raw);\n return Number.isFinite(n) && n > 0 ? n : undefined;\n}\n\n/**\n * Resolve the session lifetime and thread it through `providerOptions` before\n * delegating to the scaffold.\n *\n * AgentBox derives `providerOptions.timeoutMs` from a per-provider config key\n * (`box.<provider>TimeoutMs`) that only exists for the built-in providers, so a\n * plugin's create request always arrives without one. Injecting it here — rather\n * than defaulting inside the backend — matters because the scaffold records the\n * value it was GIVEN as `cloud.sessionTimeoutMs`, and the host keepalive loop\n * reads that record to decide when to call `renewTimeout`. Defaulting only in\n * the backend would boot a 45-minute VM while the record claimed no known\n * lifetime, leaving the keepalive to guess.\n */\nfunction createTenkiBox(req: CreateBoxRequest): Promise<CreatedBox> {\n const requested = req.providerOptions?.['timeoutMs'];\n const timeoutMs =\n typeof requested === 'number' && requested > 0\n ? requested\n : (envTimeoutMs() ?? DEFAULT_MAX_DURATION_MS);\n return cloudProvider.create({\n ...req,\n providerOptions: { ...req.providerOptions, timeoutMs },\n });\n}\n\n/**\n * Capture a reusable, id-addressed Tenki snapshot from a running session.\n * `createSnapshotAndWait` blocks until the snapshot reaches READY and returns\n * its opaque id, usable later with `createAndWait({ snapshotId })`.\n */\nasync function createTenkiSnapshot(sessionId: string, name: string): Promise<string> {\n return withTenkiRetry(\n { method: 'createSnapshot', retryOnAmbiguous: false, attemptTimeoutMs: 900_000, backoffMs: [] },\n async () => {\n const snap = await getTenkiClient().createSnapshotAndWait(sessionId, { name });\n return snap.id;\n },\n );\n}\n\n/** Delete a snapshot by id. Idempotent — a missing snapshot is success. */\nasync function deleteTenkiSnapshot(snapshotId: string): Promise<void> {\n await withTenkiRetry({ method: 'deleteSnapshot', retryOnAmbiguous: true }, async () => {\n try {\n await getTenkiClient().deleteSnapshot(snapshotId);\n } catch (err) {\n const name = err instanceof Error ? err.name : '';\n const msg = err instanceof Error ? err.message : String(err);\n if (name === 'SnapshotNotFoundError' || /not.?found|404/i.test(msg)) return; // idempotent\n throw err;\n }\n });\n}\n\n/**\n * Build a safe, unique Tenki snapshot label. Snapshots are id-addressed so the\n * name is only a human label, but we stamp a per-create timestamp so the UI\n * shows distinct entries rather than reusing one name.\n */\nfunction snapshotLabel(boxName: string, checkpointName: string): string {\n const sanitize = (s: string): string =>\n s\n .toLowerCase()\n .replace(/[^a-z0-9.-]+/g, '-')\n .replace(/^-+|-+$/g, '');\n const ts = new Date().toISOString().replace(/[:.]/g, '-').replace('T', '_').replace('Z', '');\n return `agentbox-${sanitize(boxName)}-${sanitize(checkpointName)}-${sanitize(ts)}`;\n}\n\n/**\n * Tenki-specific checkpoint capability. We capture the SDK-returned opaque\n * snapshot id and store THAT in the manifest's `snapshotName` field — the cloud\n * create flow passes `manifest.snapshotName` straight to `provision({ snapshot })`,\n * and the Tenki backend boots from it as `createAndWait({ snapshotId })`.\n */\nconst tenkiCheckpoint: 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(`tenki box ${box.name} has no sandboxId — record is malformed`);\n }\n const label = snapshotLabel(box.name, name);\n const snapshotId = await createTenkiSnapshot(box.cloud.sandboxId, label);\n const info = await writeCloudCheckpointManifest(box.projectRoot, BACKEND_NAME, name, {\n snapshotName: snapshotId,\n sourceBoxId: box.id,\n sourceBoxName: box.name,\n baseProvider: BACKEND_NAME,\n baseFingerprint: currentCloudBaseFingerprint(BACKEND_NAME),\n cliVersion: readCliStamp().cliVersion,\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 // Delete the remote snapshot FIRST and only drop the local manifest once it\n // is gone. A missing snapshot is already swallowed as success by\n // deleteTenkiSnapshot (idempotent), and withTenkiRetry has exhausted its\n // transient retries by the time this throws — so a throw is a real,\n // persistent failure. Preserve the manifest and surface it rather than\n // orphaning a billable, quota-limited snapshot the user can no longer\n // reference to retry the delete.\n await deleteTenkiSnapshot(entry.manifest.snapshotName);\n await removeCloudCheckpointDir(projectRoot, BACKEND_NAME, ref);\n },\n};\n\nexport const tenkiProvider: Provider = {\n ...cloudProvider,\n create: createTenkiBox,\n prepare: prepareTenkiProvider,\n buildAttach: buildTenkiAttach,\n checkpoint: tenkiCheckpoint,\n baseFingerprint: () => currentTenkiBaseFingerprintLive(),\n};\n\n/** Uniform surface the CLI provider loader resolves this package through. */\nexport const providerModule: ProviderModule = {\n provider: tenkiProvider,\n backend: tenkiBackend,\n ensureCredentials: ensureTenkiCredentials,\n readCredStatus: readCredStatusSummary,\n setCredentials: (fields) => Promise.resolve(setTenkiCredentials(fields)),\n currentBaseFingerprintLive: () => currentTenkiBaseFingerprintLive(),\n doctorChecks,\n};\n\nexport const luxorProviderModule: ProviderModule = {\n ...providerModule,\n provider: { ...tenkiProvider, name: LUXOR_PROVIDER_NAME },\n};\n\n/** Both names a plugin install registers; AgentBox matches `--provider` against `provider.name`. */\nexport const providerModules: ProviderModule[] = [providerModule, luxorProviderModule];\n\nexport { tenkiBackend };\nexport { ensureTenkiEnvLoaded, reloadTenkiEnv } from './env-loader.js';\nexport {\n ensureTenkiCredentials,\n setTenkiCredentials,\n readTenkiCredStatus,\n secretsPath,\n maskKey,\n type EnsureTenkiCredentialsOptions,\n type TenkiCredStatus,\n} from './credentials.js';\nexport {\n prepareTenki,\n prepareTenkiProvider,\n buildInstallPlan,\n shadowCodexHome,\n stagedAssetPath,\n PREPARE_ASSETS,\n type PrepareAsset,\n type PrepareStep,\n type PrepareTenkiOptions,\n type PrepareTenkiResult,\n} from './prepare.js';\nexport {\n currentTenkiBaseFingerprintLive,\n ensureTenkiBaseImage,\n preparedStatePath,\n readPreparedState,\n writePreparedState,\n updatePreparedState,\n type PreparedTenkiState,\n type PreparedTenkiBase,\n} from './prepared-state.js';\nexport { buildTenkiAttach } from './build-attach.js';\n// Surfaced so a consumer (and the package-validation script) can assert which\n// provider-contract major this build targets without reaching into the SDK.\nexport { SDK_API_VERSION };\n","/**\n * Tenki `CloudBackend` — maps the provider-neutral cloud primitives onto the\n * `@tenkicloud/sandbox` SDK (VMs + pause/resume snapshots).\n * Composed into a full `Provider` by the provider SDK's `createCloudProvider`.\n *\n * Platform shape this backend is built around:\n * - Boxes boot from a snapshot id: the base snapshot baked by `agentbox\n * prepare --provider tenki`, or a checkpoint snapshot (`req.snapshot`),\n * which wins. `backend.provision` gates on `ensureTenkiBaseImage()` (mirrors\n * the e2b/hetzner/vercel pattern: `prepare` sidesteps the gate so a cold\n * install can bootstrap).\n * - The SDK's high-level `Session` carries both the control-plane handle and\n * a per-session data plane (`run`, `readFile`/`writeFile`/`fs.*`). We\n * resolve a fresh `Session` per op via `client.get(sessionId)` — the CLI is\n * a short-lived process per command, matching e2b's `Sandbox.connect`.\n * - `CloudHandle.sandboxId` IS the Tenki session id.\n * - Comms are ConnectRPC + a websocket data plane (no SSH for exec/files);\n * interactive attach uses `session.ssh()`, wired by `buildTenkiAttach`.\n * - Preview URLs come from `session.exposePort(port)` — public HTTPS, no\n * header token (same shape as e2b/vercel).\n * - `session.pause()` / `session.resume()` give free pause/resume;\n * `createSnapshotAndWait` is the reusable, id-addressed checkpoint\n * primitive (the provider overrides `checkpoint` in index.ts to store the\n * snapshot id, matching vercel/e2b).\n */\n\nimport { createReadStream, createWriteStream, rmSync } from 'node:fs';\nimport { basename, posix } from 'node:path';\nimport { Readable } from 'node:stream';\nimport { pipeline } from 'node:stream/promises';\nimport type { ReadableStream as WebReadableStream } from 'node:stream/web';\nimport type {\n CloudBackend,\n CloudExecOptions,\n CloudExecResult,\n CloudFileEntry,\n CloudHandle,\n CloudPreviewUrl,\n CloudProvisionRequest,\n CloudSandboxSummary,\n CloudState,\n} from '@madarco/agentbox-provider-sdk';\nimport type { ExposedPort, Session, SessionState } from './sdk.js';\nimport { getTenkiClient } from './sdk.js';\nimport { withTenkiRetry } from './retry.js';\nimport { ensureTenkiBaseImage, readPreparedState } from './prepared-state.js';\nimport { sshKeyDir } from './build-attach.js';\nimport { readAgentboxEnv } from './env-loader.js';\n\n/** In-box port the cloud WebProxy binds + that we expose as the box's \"web\" port.\n * 8080 matches the non-privileged convention vercel/e2b use for VMs (the\n * in-box ctl is told the same value via AGENTBOX_WEB_PROXY_PORT). */\nconst TENKI_WEB_PORT = 8080;\n\n/**\n * Per-box session timeout the SDK enforces at create (`maxDurationMs`). The\n * host keepalive loop pushes it forward while the agent is active via\n * `renewTimeout` (additive `session.extend`). 45 min default mirrors e2b/vercel.\n */\nexport const DEFAULT_MAX_DURATION_MS = 45 * 60_000;\n\n/** Wait budget for create/resume (`createAndWait` boots + waits for the data plane). */\nconst PROVISION_TIMEOUT_MS = 300_000;\n\nconst decoder = new TextDecoder();\n\n/** Map the SDK's SessionState onto our 4-value CloudState. */\nexport function mapState(s: SessionState | undefined): CloudState {\n switch (s) {\n case 'RUNNING':\n case 'CREATING':\n case 'RESUMING':\n return 'running';\n case 'PAUSED':\n case 'PAUSING':\n // USER_SHUTDOWN is a stopped-but-not-terminated VM (0.4.0+). The API groups\n // it with PAUSED as an existing, non-active state, so treat it as paused so\n // an op that needs the box live resumes it rather than agentbox concluding\n // the box is gone and recreating it.\n case 'USER_SHUTDOWN':\n return 'paused';\n default:\n // TERMINATING / TERMINATED / UNSPECIFIED / undefined\n return 'missing';\n }\n}\n\n/** True when the error means \"session doesn't exist / is gone\". */\nfunction isGone(err: unknown): boolean {\n const name = err instanceof Error ? err.name : '';\n return (\n name === 'SessionNotFoundError' ||\n name === 'SessionTerminatedError' ||\n name === 'SessionExpiredError'\n );\n}\n\n/** Strip control chars defensively so a name with embedded newlines can't break log parsing. */\nexport function safeName(name: string): string {\n return name.replace(/[\\u0000-\\u001f]/g, '').slice(0, 200);\n}\n\n/**\n * Stable, DNS-safe preview slug for (session, port) so re-exposing the same\n * port is idempotent and the URL doesn't churn across calls.\n */\nexport function previewSlug(sandboxId: string, port: number): string {\n const id =\n sandboxId\n .replace(/[^a-z0-9]/gi, '')\n .slice(-12)\n .toLowerCase() || 'box';\n return `ab-${id}-${String(port)}`;\n}\n\n/**\n * Minimum lifetime handed to a re-minted expiring share, so a refresh can't\n * return a URL that is already about to die.\n */\nconst REFRESH_MIN_TTL_MS = 60_000;\n\n// --- preview share kinds ---------------------------------------------------\n//\n// The two kinds this backend mints are mutually exclusive per share, because the\n// API rejects `slug` + `ttlMs` together (`expires_at is not supported when slug\n// is set`). So a share either carries our stable slug and never expires, or it\n// carries a server-assigned URL and an `expiresAt`. `expiresAt` is the\n// discriminator.\n//\n// Selecting by KIND rather than by port alone is the whole point: a port can\n// hold either kind, and reusing whatever was found first meant\n// `signedPreviewUrl` could hand back the permanent public URL (never expiring,\n// despite the caller asking for an expiry) and `previewUrl` could hand back one\n// that dies minutes later.\n\n/** The non-expiring, stable-slug share for `port`, if one is exposed. */\nexport function findPermanentShare(\n shares: readonly ExposedPort[],\n port: number,\n): ExposedPort | undefined {\n return shares.find((p) => p.port === port && p.expiresAt === undefined);\n}\n\n/**\n * An expiring share for `port` with at least `minRemainingMs` of life left. A\n * shorter-lived one is rejected rather than reused, so a signed URL is never\n * quietly weaker than the caller asked for.\n */\nexport function findExpiringShare(\n shares: readonly ExposedPort[],\n port: number,\n minRemainingMs: number,\n nowMs: number,\n): ExposedPort | undefined {\n return shares.find(\n (p) =>\n p.port === port &&\n p.expiresAt !== undefined &&\n p.expiresAt.getTime() - nowMs >= minRemainingMs,\n );\n}\n\nasync function resolveSession(handle: CloudHandle): Promise<Session> {\n return getTenkiClient().get(handle.sandboxId);\n}\n\n/**\n * The owner scope a session is created under.\n *\n * A workspace auth token infers its own scope, so this is normally empty and\n * `create` carries no owner fields at all. `AGENTBOX_LUXORAI_WORKSPACE_ID` or\n * `AGENTBOX_TENKI_WORKSPACE_ID` (the\n * name `prepare` already honors) pins it explicitly, which is what trusted\n * service credentials — valid for more than one workspace — need.\n *\n * Historically this also resolved a `project_id`, which `create` once required\n * and which cost a `whoAmI` round-trip to discover. Tenki removed that\n * requirement (the proto field is deprecated and `CreateOptions` no longer\n * accepts it), so the lookup is gone with it.\n */\nfunction resolveOwnerScope(): { workspaceId?: string } {\n const envWorkspace = readAgentboxEnv('WORKSPACE_ID');\n return envWorkspace ? { workspaceId: envWorkspace } : {};\n}\n\n/**\n * Resume a paused session before an op that needs the box live (exec, file\n * transfer). Matches e2b's connect-auto-resumes semantics: a caller asking to\n * run a command on the box wants it awake. No-op when already running.\n */\nasync function ensureLive(session: Session): Promise<void> {\n if (\n session.state === 'PAUSED' ||\n session.state === 'PAUSING' ||\n session.state === 'USER_SHUTDOWN'\n ) {\n await session.resume();\n await session.waitReady(PROVISION_TIMEOUT_MS);\n }\n}\n\n// --- workdir bridge for file transfer -------------------------------------\n//\n// Tenki's guest-agent file RPC (`writeFileStream`/`readFileStream`) is jailed\n// to the session's configured workdir — an absolute path outside it (the cloud\n// scaffold stages workspace/credential tarballs under `/tmp`) is rejected with\n// `path outside workdir`. `exec` is NOT jailed, so we bridge: write/read through\n// the RPC at a workdir-relative staging path (which it accepts), then move the\n// bytes to/from the caller's absolute target with a shell `mv`/`cp`. Paths that\n// already live under the workdir skip the bridge and stream directly.\n\n/** Single-quote a string for safe interpolation into a `bash -c` script. */\nfunction shq(s: string): string {\n return `'${s.replace(/'/g, `'\\\\''`)}'`;\n}\n\nlet stageSeq = 0;\n/** A collision-free, workdir-relative staging filename (dotfile, so it stays out of the way). */\nfunction stageName(kind: string): string {\n stageSeq += 1;\n return `.agentbox-${kind}-${String(process.pid)}-${String(stageSeq)}.tmp`;\n}\n\n// The guest workdir is fixed for a session's life; resolve it once per session\n// (a cheap `pwd`, whose cwd defaults to the workdir) and cache it.\nconst workdirCache = new Map<string, string>();\nasync function resolveGuestWorkdir(session: Session): Promise<string> {\n const cached = workdirCache.get(session.id);\n if (cached !== undefined) return cached;\n const r = await session.run(['bash', '-c', 'pwd']);\n const wd = decoder.decode(r.stdout).trim() || '/';\n workdirCache.set(session.id, wd);\n return wd;\n}\n\n/**\n * True when a file RPC target is inside the guest workdir (so it can stream\n * directly instead of via the exec bridge). Relative paths always resolve under\n * the workdir; absolute paths must equal it or sit beneath it (a `/` boundary\n * check so `/workspace` doesn't match `/workspace-evil`).\n */\nexport function isUnderWorkdir(path: string, workdir: string): boolean {\n if (!posix.isAbsolute(path)) return true;\n const p = posix.normalize(path);\n const d = posix.normalize(workdir);\n return p === d || p.startsWith(d.endsWith('/') ? d : `${d}/`);\n}\n\n/** Move a workdir-relative staged file to an absolute target via exec (unjailed). */\nasync function moveStagedTo(session: Session, relStage: string, remotePath: string): Promise<void> {\n const script = [\n 'set -eu',\n \"if command -v sudo >/dev/null 2>&1; then SUDO='sudo -n'; else SUDO=''; fi\",\n `src=\"$(pwd)/${relStage}\"`,\n `$SUDO mkdir -p ${shq(posix.dirname(remotePath))}`,\n `$SUDO mv -f \"$src\" ${shq(remotePath)}`,\n ].join('\\n');\n const r = await session.run(['bash', '-c', script]);\n if (r.exitCode !== 0) {\n try {\n await session.run(['bash', '-c', `rm -f ${shq(relStage)}`]);\n } catch {\n // best-effort cleanup of the orphaned stage\n }\n throw new Error(\n `tenki uploadFile: staging move to ${remotePath} failed (exit ${String(r.exitCode)}): ${decoder.decode(r.stderr).trim()}`,\n );\n }\n}\n\n/** Copy an absolute source into a workdir-relative staged file via exec (unjailed). */\nasync function copyIntoStage(\n session: Session,\n remotePath: string,\n relStage: string,\n): Promise<void> {\n const script = [\n 'set -eu',\n \"if command -v sudo >/dev/null 2>&1; then SUDO='sudo -n'; else SUDO=''; fi\",\n `dst=\"$(pwd)/${relStage}\"`,\n `$SUDO cp -f ${shq(remotePath)} \"$dst\"`,\n // The RPC reads as the guest user; ensure it owns the staged copy.\n `$SUDO chown \"$(id -u):$(id -g)\" \"$dst\" 2>/dev/null || true`,\n ].join('\\n');\n const r = await session.run(['bash', '-c', script]);\n if (r.exitCode !== 0) {\n throw new Error(\n `tenki downloadFile: staging copy from ${remotePath} failed (exit ${String(r.exitCode)}): ${decoder.decode(r.stderr).trim()}`,\n );\n }\n}\n\n/**\n * Parse a `cpu-memory` or `cpu-memory-disk` GB size spec (e.g. `4-8` or\n * `4-8-20`) into create-time resources. Returns undefined on malformed input\n * (2 or 3 positive-integer slots). `memory`/`disk` are GB. Tenki applies these\n * at create (`createAndWait`), so `--size` / `box.size` actually changes the\n * box's resources (unlike e2b, whose size is baked at prepare time).\n */\nexport function parseTenkiSize(\n spec: string | undefined,\n): { cpu: number; memoryGb: number; diskGb?: number } | undefined {\n if (!spec) return undefined;\n const parts = spec.trim().split('-');\n if (parts.length !== 2 && parts.length !== 3) return undefined;\n const nums = parts.map((p) => Number(p));\n if (nums.some((n) => !Number.isInteger(n) || n <= 0)) return undefined;\n return { cpu: nums[0]!, memoryGb: nums[1]!, diskGb: nums[2] };\n}\n\nexport const tenkiBackend: CloudBackend = {\n name: 'tenki',\n\n webProxyPort: TENKI_WEB_PORT,\n\n async provision(req: CloudProvisionRequest): Promise<CloudHandle> {\n const log = req.onLog ?? (() => {});\n // Both paths boot from a snapshot id: a checkpoint (req.snapshot) wins,\n // otherwise the base snapshot baked by `prepare`. The gate throws an\n // actionable \"run `agentbox prepare`\" error when no base is recorded\n // (prepare itself sidesteps the gate by never calling provision).\n if (req.snapshot === undefined) ensureTenkiBaseImage();\n const snapshotId = req.snapshot ?? readPreparedState().base?.snapshotId;\n if (!snapshotId) {\n throw new Error(\n 'tenki provision: no base snapshot available — `agentbox prepare --provider tenki` must run first',\n );\n }\n\n // `--size` / `box.size` (a `cpu-memory[-disk]` GB spec, e.g. `4-8`)\n // overrides the create-time resources when valid; Tenki applies them at\n // create, so the box actually gets the requested size. Invalid specs fall\n // back to the resolved defaults with a log.\n let cpuCores = req.resources?.cpu ?? 2;\n let memoryMb = (req.resources?.memory ?? 4) * 1024;\n let diskSizeGb = req.resources?.disk ?? 8;\n if (req.size && req.size.trim().length > 0) {\n const parsed = parseTenkiSize(req.size);\n if (parsed) {\n cpuCores = parsed.cpu;\n memoryMb = parsed.memoryGb * 1024;\n if (parsed.diskGb !== undefined) diskSizeGb = parsed.diskGb;\n } else {\n (req.onLog ?? (() => {}))(\n `tenki: ignoring invalid size '${req.size}' (expected 'cpu-memory' or 'cpu-memory-disk' GB, e.g. '4-8')`,\n );\n }\n }\n const maxDurationMs = req.timeoutMs ?? DEFAULT_MAX_DURATION_MS;\n const client = getTenkiClient();\n\n // Empty for a workspace token (it infers its own scope); set only when\n // AGENTBOX_TENKI_WORKSPACE_ID pins it.\n const { workspaceId } = resolveOwnerScope();\n\n // Split create from wait so a slow/stuck boot can't orphan a billable VM.\n // `createAndWait` throws WITHOUT surfacing the session id on a readiness\n // timeout, so a box that was provisioned but never became ready would leak\n // (its id lost). Instead: `create({ waitReady: false })` returns the id as\n // soon as the session record exists, then we wait separately — and if the\n // wait times out or fails, we still hold the id and terminate the box.\n //\n // No-retry on the create RPC: it is billable and non-idempotent, and the\n // SDK exposes no idempotency key, so a retry could double-provision.\n const session = await withTenkiRetry(\n { method: 'provision', retryOnAmbiguous: false, attemptTimeoutMs: 120_000, backoffMs: [] },\n () =>\n client.create({\n name: safeName(req.name),\n snapshotId,\n ...(workspaceId ? { workspaceId } : {}),\n env: req.env,\n cpuCores,\n memoryMb,\n diskSizeGb,\n // Inbound is required for preview URLs (exposePort); outbound for the\n // agents' network. Tenki has no domain-allowlist primitive, so\n // req.networkPolicy 'deny-all' maps to allowOutbound:false, else allow.\n allowInbound: true,\n allowOutbound: req.networkPolicy !== 'deny-all',\n maxDurationMs,\n // Marker so list()/prune can filter to agentbox-managed sessions.\n metadata: { agentbox: 'true', 'agentbox.name': safeName(req.name) },\n tags: ['agentbox'],\n // Return as soon as the session exists; we own the wait below so a\n // readiness timeout can't lose the id.\n waitReady: false,\n }),\n );\n try {\n await session.waitReady(PROVISION_TIMEOUT_MS);\n } catch (err) {\n // The VM exists but never came ready (timeout / terminal boot failure).\n // Terminate it so it doesn't linger billable, then surface the error.\n await session.close().catch(() => undefined);\n throw err;\n }\n log(`tenki: created session ${session.id} (${req.snapshot ? 'snapshot' : 'image'})`);\n return { sandboxId: session.id };\n },\n\n async get(sandboxId: string): Promise<CloudHandle | null> {\n return withTenkiRetry({ method: 'get', retryOnAmbiguous: true }, async () => {\n try {\n await getTenkiClient().get(sandboxId);\n return { sandboxId };\n } catch (err) {\n if (isGone(err)) return null;\n throw err;\n }\n });\n },\n\n async list(): Promise<CloudSandboxSummary[]> {\n return withTenkiRetry({ method: 'list', retryOnAmbiguous: true }, async () => {\n const sessions = await getTenkiClient().list();\n const out: CloudSandboxSummary[] = [];\n for (const s of sessions) {\n if (s.metadata?.['agentbox'] !== 'true') continue;\n const summary: CloudSandboxSummary = { sandboxId: s.id, state: mapState(s.state) };\n const friendly = s.metadata?.['agentbox.name'] ?? s.name;\n if (friendly) summary.name = friendly;\n out.push(summary);\n }\n return out;\n });\n },\n\n async start(h: CloudHandle): Promise<void> {\n await withTenkiRetry(\n { method: 'start', retryOnAmbiguous: true, attemptTimeoutMs: PROVISION_TIMEOUT_MS },\n async () => {\n const session = await resolveSession(h);\n await ensureLive(session);\n },\n );\n },\n\n // No separate \"stop VM, keep record\" in the high-level SDK — pause IS the\n // cold-storage state (it captures a pause snapshot). stop ≡ pause.\n async stop(h: CloudHandle): Promise<void> {\n await this.pause(h);\n },\n\n async pause(h: CloudHandle): Promise<void> {\n await withTenkiRetry(\n { method: 'pause', retryOnAmbiguous: true, attemptTimeoutMs: 120_000 },\n async () => {\n const session = await resolveSession(h);\n // Already cold (paused or shut down) — nothing to pause.\n if (\n session.state === 'PAUSED' ||\n session.state === 'PAUSING' ||\n session.state === 'USER_SHUTDOWN'\n )\n return;\n await session.pause();\n },\n );\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 withTenkiRetry(\n { method: 'destroy', retryOnAmbiguous: true, attemptTimeoutMs: 120_000 },\n async () => {\n try {\n const session = await resolveSession(h);\n await session.close();\n } catch (err) {\n if (isGone(err)) return; // idempotent\n throw err;\n }\n },\n );\n // The attach keypair outlives the session it authenticates, so without this\n // every box ever created leaves a private key behind on the host. Losing the\n // key is harmless once the sandbox is gone, so failures here are ignored.\n try {\n rmSync(sshKeyDir(h.sandboxId), { recursive: true, force: true });\n } catch {\n // best effort\n }\n },\n\n async state(h: CloudHandle): Promise<CloudState> {\n return withTenkiRetry({ method: 'state', retryOnAmbiguous: true }, async () => {\n try {\n const session = await resolveSession(h);\n return mapState(session.state);\n } catch (err) {\n if (isGone(err)) return 'missing';\n throw err;\n }\n });\n },\n\n // The host keepalive loop calls this while the agent is active. `extend` is\n // ADDITIVE (extends by N ms), and the SDK doesn't expose the remaining time\n // readably, so we extend by `target - current` (the host owns the deadline\n // bookkeeping, same as vercel). The host only calls when target > current.\n async renewTimeout(\n h: CloudHandle,\n targetDeadlineEpochMs: number,\n currentDeadlineEpochMs: number,\n ): Promise<void> {\n const deltaMs = Math.max(0, targetDeadlineEpochMs - currentDeadlineEpochMs);\n if (deltaMs === 0) return;\n await withTenkiRetry({ method: 'renewTimeout', retryOnAmbiguous: true }, async () => {\n const session = await resolveSession(h);\n await session.extend(deltaMs);\n });\n },\n\n async exec(h: CloudHandle, cmd: string, opts?: CloudExecOptions): Promise<CloudExecResult> {\n const timeoutMs = opts?.attemptTimeoutMs ?? 300_000;\n return withTenkiRetry(\n {\n method: 'exec',\n retryOnAmbiguous: opts?.noRetry ? false : true,\n attemptTimeoutMs: timeoutMs,\n backoffMs: opts?.noRetry ? [] : undefined,\n },\n async (signal) => {\n const session = await resolveSession(h);\n await ensureLive(session);\n // The scaffold hands us a ready-to-run shell command string (either a\n // shell-quoted argv or a `bash -c '<script>'`). Run it through bash so\n // pipefail/heredocs/etc. behave uniformly. Tenki's `run` has no\n // per-exec user field — only `privileged`; map user==='root' to it and\n // otherwise rely on the base image's default user.\n signal.throwIfAborted();\n const r = await session.run(['bash', '-c', cmd], {\n timeoutMs,\n signal,\n ...(opts?.cwd !== undefined ? { cwd: opts.cwd } : {}),\n ...(opts?.env !== undefined ? { env: opts.env } : {}),\n ...(opts?.user === 'root' ? { privileged: true } : {}),\n });\n return {\n exitCode: r.timedOut ? 124 : r.exitCode,\n stdout: decoder.decode(r.stdout),\n stderr:\n decoder.decode(r.stderr) +\n (r.timedOut ? `\\ntenki exec: command timed out after ${String(timeoutMs)}ms` : ''),\n };\n },\n );\n },\n\n async uploadFile(h: CloudHandle, localPath: string, remotePath: string): Promise<void> {\n await withTenkiRetry(\n { method: 'uploadFile', retryOnAmbiguous: true, attemptTimeoutMs: 300_000 },\n async () => {\n const session = await resolveSession(h);\n await ensureLive(session);\n // Stream the file so a large workspace tarball (tens of MB) never has to\n // sit fully in memory. The guest-agent file RPC is jailed to the workdir;\n // targets outside it stage under the workdir then move into place (see\n // the workdir-bridge helpers). writeFileStream and run share the data\n // plane uid, so no post-write chown is needed.\n const workdir = await resolveGuestWorkdir(session);\n const web = () =>\n Readable.toWeb(createReadStream(localPath)) as unknown as ReadableStream<Uint8Array>;\n if (isUnderWorkdir(remotePath, workdir)) {\n await session.writeFileStream(remotePath, web(), { mode: 0o644 });\n return;\n }\n const stage = stageName('upload');\n await session.writeFileStream(stage, web(), { mode: 0o644 });\n await moveStagedTo(session, stage, remotePath);\n },\n );\n },\n\n async downloadFile(h: CloudHandle, remotePath: string, localPath: string): Promise<void> {\n await withTenkiRetry(\n { method: 'downloadFile', retryOnAmbiguous: true, attemptTimeoutMs: 300_000 },\n async () => {\n const session = await resolveSession(h);\n await ensureLive(session);\n const workdir = await resolveGuestWorkdir(session);\n const sink = (web: WebReadableStream<Uint8Array>) =>\n pipeline(Readable.fromWeb(web), createWriteStream(localPath));\n if (isUnderWorkdir(remotePath, workdir)) {\n const web = (await session.readFileStream(\n remotePath,\n )) as unknown as WebReadableStream<Uint8Array>;\n await sink(web);\n return;\n }\n // Outside the workdir: copy into a staged file via exec, read that back\n // through the RPC, then drop the stage.\n const stage = stageName('download');\n await copyIntoStage(session, remotePath, stage);\n try {\n const web = (await session.readFileStream(\n stage,\n )) as unknown as WebReadableStream<Uint8Array>;\n await sink(web);\n } finally {\n await session.remove(stage).catch(() => undefined);\n }\n },\n );\n },\n\n async listFiles(h: CloudHandle, remoteDir: string): Promise<CloudFileEntry[]> {\n return withTenkiRetry({ method: 'listFiles', retryOnAmbiguous: true }, async () => {\n const session = await resolveSession(h);\n await ensureLive(session);\n const entries = await session.list(remoteDir);\n return entries.map((e) => ({ name: basename(e.path), isDir: e.isDir }));\n });\n },\n\n async previewUrl(h: CloudHandle, port: number): Promise<CloudPreviewUrl> {\n return withTenkiRetry({ method: 'previewUrl', retryOnAmbiguous: true }, async () => {\n const session = await resolveSession(h);\n // Reuse only a PERMANENT share (idempotent across the box's lifetime).\n // Handing back an expiring one would give the caller — and the scaffold's\n // cache — a URL that dies on its own.\n const shares = await session.listExposedPorts();\n const existing = findPermanentShare(shares, port);\n if (existing) return { url: existing.previewUrl };\n // The port is held by the other kind. Clear it first: unexpose-then-expose\n // is the sequence `refreshPreviewUrl` already relies on, whereas exposing\n // a port that is already exposed is not a behaviour we can count on.\n if (shares.some((p) => p.port === port)) {\n await session.unexposePort(port).catch(() => undefined);\n }\n const exposed = await session.exposePort(port, { slug: previewSlug(h.sandboxId, port) });\n return { url: exposed.previewUrl };\n });\n },\n\n // Tenki preview URLs are public + browser-usable; expiry maps to ttlMs.\n // NOTE: the API rejects `slug` + `ttlMs` together (`expires_at is not\n // supported when slug is set`), so an expiring share can't carry our stable\n // slug — we mint a server-assigned URL instead. Re-exposing therefore churns\n // the URL, which is inherent to an expiring (signed) share.\n async signedPreviewUrl(\n h: CloudHandle,\n port: number,\n expiresInSeconds: number,\n ): Promise<CloudPreviewUrl> {\n return withTenkiRetry({ method: 'signedPreviewUrl', retryOnAmbiguous: true }, async () => {\n const session = await resolveSession(h);\n const ttlMs = Math.max(1, expiresInSeconds) * 1000;\n const shares = await session.listExposedPorts();\n // Reuse only an EXPIRING share that still covers the requested window.\n // Reusing any share for the port meant a request for a 60-second URL could\n // be answered with the permanent public one, which never expires — the\n // caller believes the exposure lapses and it does not.\n const existing = findExpiringShare(shares, port, ttlMs, Date.now());\n if (existing) return { url: existing.previewUrl };\n if (shares.some((p) => p.port === port)) {\n await session.unexposePort(port).catch(() => undefined);\n }\n const exposed = await session.exposePort(port, { ttlMs });\n return { url: exposed.previewUrl };\n });\n },\n\n // Re-mint a share when the cached URL stops responding (host poller).\n async refreshPreviewUrl(h: CloudHandle, port: number): Promise<CloudPreviewUrl> {\n return withTenkiRetry({ method: 'refreshPreviewUrl', retryOnAmbiguous: true }, async () => {\n const session = await resolveSession(h);\n // Re-mint the SAME KIND that was there. Unconditionally re-exposing with\n // the stable slug silently promoted an expiring share into a permanent\n // public one: a refresh must never widen exposure beyond what the share it\n // replaces already had.\n const prior = (await session.listExposedPorts()).find((p) => p.port === port);\n const priorRemainingMs =\n prior?.expiresAt === undefined ? undefined : prior.expiresAt.getTime() - Date.now();\n try {\n await session.unexposePort(port);\n } catch {\n // best-effort: the share may already be gone\n }\n if (priorRemainingMs !== undefined) {\n // Floor the replacement's lifetime: the prior share may have been at the\n // very end of its window (or already past it) when the refresh fired.\n const exposed = await session.exposePort(port, {\n ttlMs: Math.max(REFRESH_MIN_TTL_MS, priorRemainingMs),\n });\n return { url: exposed.previewUrl };\n }\n const exposed = await session.exposePort(port, { slug: previewSlug(h.sandboxId, port) });\n return { url: exposed.previewUrl };\n });\n },\n\n /**\n * Probe whether a snapshot id is still bootable. Returns false on any lookup\n * failure (treated by the cloud-provider as \"gone\" so it falls back to a\n * from-base boot rather than erroring the user).\n */\n async snapshotExists(snapshotName: string): Promise<boolean> {\n return withTenkiRetry({ method: 'snapshotExists', retryOnAmbiguous: true }, async () => {\n try {\n const snap = await getTenkiClient().getSnapshot(snapshotName);\n return snap.state === 'READY';\n } catch {\n return false;\n }\n });\n },\n};\n","/**\n * Thin wrapper around the `@tenkicloud/sandbox` SDK. Resolves the auth token\n * once and re-exports the SDK surface the rest of the package uses from a\n * single place (so tests can mock `./sdk.js` instead of the package).\n *\n * Tenki authenticates with a single workspace auth token (`tk_…`). The SDK\n * reads `TENKI_AUTH_TOKEN` from `process.env`, but we still expose\n * `resolveAuthToken()` so callers can fail loud with an actionable error\n * before the SDK throws a generic \"missing auth token\" deep inside an op, and\n * `getTenkiClient()` so the optional `TENKI_BASE_URL` / `TENKI_GATEWAY_ADDRESS`\n * overrides (self-hosted / staging control planes) are wired consistently.\n */\n\nimport { TenkiSandbox } from '@tenkicloud/sandbox';\nimport type { ClientOptions } from '@tenkicloud/sandbox';\nimport { UserFacingError } from '@madarco/agentbox-provider-sdk';\nimport { ensureTenkiEnvLoaded, readTenkiAuthToken, readTenkiEnv } from './env-loader.js';\n\nexport { TenkiSandbox };\n// The template API is deliberately unused: `prepare` bakes its base from a\n// builder sandbox instead (see the header of ./prepare.ts for why). The\n// `Template*` types below are re-exported for completeness of this seam; no\n// `TemplateSpec` value export, so nothing suggests a builder that isn't wired up.\nexport type {\n Session,\n ClientOptions,\n CreateOptions,\n ExposedPort,\n FileInfo,\n Identity,\n RegistryPublishResult,\n ResolvedRegistryRef,\n Snapshot,\n SnapshotState,\n SessionState,\n Template,\n TemplateBuild,\n TemplateBuildEvent,\n TemplateImage,\n} from '@tenkicloud/sandbox';\nexport {\n SandboxError,\n MissingAuthTokenError,\n SessionNotFoundError,\n SessionTerminatedError,\n SessionExpiredError,\n SnapshotNotFoundError,\n SnapshotFailedError,\n UnauthorizedError,\n PermissionDeniedError,\n QuotaExceededError,\n CapacityUnavailableError,\n RateLimitedError,\n InvalidStateError,\n FileNotFoundError,\n} from '@tenkicloud/sandbox';\n\n/**\n * Return the configured Tenki auth token. Throws an actionable error when\n * nothing is configured. Idempotent — env-loader caches itself after the\n * first call.\n */\nexport function resolveAuthToken(): string {\n ensureTenkiEnvLoaded();\n const t = readTenkiAuthToken();\n if (!t) {\n // UserFacingError (not a plain Error): AgentBox prints these as a clean\n // message, where anything else also dumps a stack trace at the user.\n throw new UserFacingError(\n 'Tenki credentials not configured.\\n' +\n 'Set TENKI_AUTH_TOKEN (your auth token from https://tenki.cloud) in the environment ' +\n 'or in ~/.agentbox/secrets.env. Running `agentbox create --provider tenki` on a ' +\n 'terminal also prompts for it.',\n );\n }\n return t;\n}\n\n/** True when an auth token is configured. Used by the credential gate. */\nexport function hasUsableCredentials(): boolean {\n ensureTenkiEnvLoaded();\n return Boolean(readTenkiAuthToken());\n}\n\n/**\n * Construct a `TenkiSandbox` client wired to the configured credentials and\n * any control-plane overrides. Each call returns a fresh client — the CLI is a\n * short-lived process per command, so we don't bother caching one.\n */\nexport function getTenkiClient(): TenkiSandbox {\n const opts: ClientOptions = { authToken: resolveAuthToken() };\n const baseUrl = readTenkiEnv('BASE_URL');\n if (baseUrl) opts.baseUrl = baseUrl;\n const gatewayAddress = readTenkiEnv('GATEWAY_ADDRESS');\n if (gatewayAddress) opts.gatewayAddress = gatewayAddress;\n return new TenkiSandbox(opts);\n}\n","/**\n * Tenki env auto-loader. The `@tenkicloud/sandbox` SDK reads `TENKI_AUTH_TOKEN`\n * from `process.env` (and we honor optional `TENKI_BASE_URL` /\n * `TENKI_GATEWAY_ADDRESS` overrides for non-default / self-hosted control\n * planes). We seed those from `~/.agentbox/secrets.env` — the store AgentBox's\n * built-in cloud providers use — so the SDK Just Works after a one-time setup.\n *\n * Lookup order (first wins; process.env is never overwritten):\n * 1. `process.env` (already set in the shell).\n * 2. `~/.agentbox/secrets.env` — written by the credential prompt.\n *\n * `TENKI_API_TOKEN` is accepted as an alias for `TENKI_AUTH_TOKEN`. Every key\n * also has a `LUXORAI_*` equivalent that wins over all `TENKI_*` keys; read them\n * through `readTenkiEnv` rather than `process.env`.\n *\n * Project-level `.env` / `.env.local` are intentionally NOT consulted: those\n * files belong to the app code being developed. Put host credentials in\n * `~/.agentbox/secrets.env` (or the shell env).\n *\n * Only TENKI- and LUXORAI-prefixed keys are imported; the rest of the file is left alone.\n * Idempotent and side-effect-free after the first call.\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { resolve } from 'node:path';\n\nconst ENV_SUFFIXES = ['AUTH_TOKEN', 'API_TOKEN', 'BASE_URL', 'GATEWAY_ADDRESS'] as const;\nconst TENKI_KEYS = [\n ...ENV_SUFFIXES.map((s) => `LUXORAI_${s}`),\n ...ENV_SUFFIXES.map((s) => `TENKI_${s}`),\n];\n\n/** Every `LUXORAI_<suffix>` is tried (in order) before any legacy `TENKI_<suffix>`. */\nexport function readTenkiEnv(...suffixes: string[]): string | undefined {\n for (const prefix of ['LUXORAI_', 'TENKI_']) {\n for (const suffix of suffixes) {\n const value = process.env[`${prefix}${suffix}`];\n if (value) return value;\n }\n }\n return undefined;\n}\n\n/** `AGENTBOX_LUXORAI_<suffix>`, falling back to the legacy `AGENTBOX_TENKI_<suffix>`. */\nexport function readAgentboxEnv(suffix: string): string | undefined {\n return (\n process.env[`AGENTBOX_LUXORAI_${suffix}`]?.trim() ||\n process.env[`AGENTBOX_TENKI_${suffix}`]?.trim() ||\n undefined\n );\n}\n\n/** The configured auth token: `LUXORAI_AUTH_TOKEN` > `LUXORAI_API_TOKEN` > `TENKI_AUTH_TOKEN` > `TENKI_API_TOKEN`. */\nexport function readTenkiAuthToken(): string | undefined {\n return readTenkiEnv('AUTH_TOKEN', 'API_TOKEN');\n}\n\nlet loaded = false;\n\nexport function ensureTenkiEnvLoaded(): void {\n if (loaded) return;\n loaded = true;\n importTenkiFromFile(resolve(homedir(), '.agentbox', 'secrets.env'), TENKI_KEYS);\n // `TENKI_AUTH_TOKEN` is the name the Tenki SDK reads and the one we write.\n // `TENKI_API_TOKEN` is accepted as an alias because it's the more common\n // spelling to reach for; normalize it once here so nothing downstream has to\n // know about two names.\n if (!process.env.TENKI_AUTH_TOKEN && process.env.TENKI_API_TOKEN) {\n process.env.TENKI_AUTH_TOKEN = process.env.TENKI_API_TOKEN;\n }\n}\n\n/**\n * Force a re-read of `~/.agentbox/secrets.env`. Used by the interactive\n * credential flow after it persists the token, so the same process can pick it\n * up without a restart.\n */\nexport function reloadTenkiEnv(): void {\n loaded = false;\n ensureTenkiEnvLoaded();\n}\n\nfunction importTenkiFromFile(path: string, keys: readonly string[]): void {\n if (!existsSync(path)) return;\n let body: string;\n try {\n body = readFileSync(path, 'utf8');\n } catch {\n return;\n }\n const parsed = parseEnvFile(body);\n for (const key of keys) {\n if (process.env[key] !== undefined) continue;\n const value = parsed[key];\n if (typeof value === 'string') {\n process.env[key] = value;\n }\n }\n}\n\n/**\n * Minimal `.env` parser: handles `KEY=value`, `KEY=\"value\"`, `KEY='value'`,\n * `export KEY=value`, blank lines, and `#` comments. No variable interpolation\n * — predictable over feature-complete (matches the daytona / vercel / e2b\n * loaders).\n */\nexport function parseEnvFile(body: string): Record<string, string> {\n const out: Record<string, string> = {};\n for (const rawLine of body.split(/\\r?\\n/)) {\n const line = rawLine.trim();\n if (line.length === 0 || line.startsWith('#')) continue;\n const stripped = line.startsWith('export ') ? line.slice('export '.length) : line;\n const eq = stripped.indexOf('=');\n if (eq <= 0) continue;\n const key = stripped.slice(0, eq).trim();\n let value = stripped.slice(eq + 1).trim();\n if (\n value.length >= 2 &&\n ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\")))\n ) {\n value = value.slice(1, -1);\n }\n out[key] = value;\n }\n return out;\n}\n","/**\n * Bounded retry wrapper for Tenki SDK calls — mirrors `withE2bRetry` /\n * `withVercelRetry` in shape and intent. The Tenki API rate-limits and can\n * report transient capacity / transport errors during incidents; without\n * bounded retries those propagate as wedges in the calling lifecycle code.\n *\n * Non-idempotent ops (`provision`/`createAndWait`) pass `retryOnAmbiguous:\n * false` so a timeout after the request reached the origin doesn't create a\n * duplicate billable session.\n *\n * Classification is by the SDK's typed error names (`RateLimitedError`,\n * `CapacityUnavailableError`, …) plus ConnectRPC status codes (the SDK is\n * ConnectRPC-based) and raw Node socket error codes.\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, transient transport). Set\n * false for non-idempotent operations where a retry could create a duplicate\n * 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(`tenki ${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/** ConnectRPC status code string (e.g. \"unavailable\") dug out of the error. */\nfunction connectCodeOf(err: unknown): string | undefined {\n if (!err || typeof err !== 'object') return undefined;\n const code = (err as { code?: unknown }).code;\n return typeof code === 'string' ? code : undefined;\n}\n\n/** Numeric 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'] as const) {\n const v = (err as Record<string, unknown>)[key];\n if (typeof v === 'number') return v;\n }\n return undefined;\n}\n\n/** ConnectRPC codes that are safe-to-retry transient server conditions. */\nconst RETRIABLE_CONNECT_CODES = new Set([\n 'unavailable',\n 'resource_exhausted',\n 'deadline_exceeded',\n 'aborted',\n]);\n\nexport function isRetriable(err: unknown, allowAmbiguous: boolean): boolean {\n if (err instanceof AttemptTimeoutError) return allowAmbiguous;\n\n // Match the SDK's typed errors by name to avoid an import cycle\n // (sdk.ts → retry → sdk). RateLimited / CapacityUnavailable are explicitly\n // transient; auth / not-found / quota / invalid-state are terminal.\n const name = err instanceof Error ? err.name : undefined;\n if (name === 'RateLimitedError' || name === 'CapacityUnavailableError') return true;\n if (\n name === 'UnauthorizedError' ||\n name === 'PermissionDeniedError' ||\n name === 'MissingAuthTokenError' ||\n name === 'SessionNotFoundError' ||\n name === 'SnapshotNotFoundError' ||\n name === 'QuotaExceededError' ||\n name === 'InvalidStateError' ||\n name === 'FileNotFoundError'\n ) {\n return false;\n }\n\n // Only short-circuit on a KNOWN retriable Connect code. A non-retriable\n // Connect code (e.g. `permission_denied`) and raw Node socket codes (e.g.\n // `ECONNRESET`) both fall through to the status / socket checks below — so a\n // socket error isn't misread as a terminal Connect code and dropped.\n const connectCode = connectCodeOf(err);\n if (connectCode !== undefined && RETRIABLE_CONNECT_CODES.has(connectCode)) {\n // `aborted` / `deadline_exceeded` are ambiguous (the server may have\n // applied the call); `unavailable` / `resource_exhausted` are pre-apply.\n return connectCode === 'unavailable' || connectCode === 'resource_exhausted'\n ? true\n : allowAmbiguous;\n }\n\n const status = statusCodeOf(err);\n if (status !== undefined) {\n if (status === 429) return true;\n if (status >= 500 && status <= 599) return allowAmbiguous;\n return false;\n }\n\n // Raw fetch / undici / ws 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 === 'EPIPE' ||\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 withTenkiRetry<T>(\n opts: WithRetryOptions,\n fn: (signal: AbortSignal) => 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 `tenki ${opts.method}: attempt ${String(attempt)} failed (${errorSummary(err)}); retrying in ${String(delay)}ms`,\n );\n await sleep(delay);\n }\n }\n throw new Error(`withTenkiRetry: exhausted attempts for ${opts.method}`);\n}\n\nfunction defaultRetryLog(line: string): void {\n process.stderr.write(`\\n[tenki-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>(\n fn: (signal: AbortSignal) => Promise<T>,\n ms: number,\n method: string,\n): Promise<T> {\n const controller = new AbortController();\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n const timeout = new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => {\n const err = new AttemptTimeoutError(method, ms);\n controller.abort(err);\n reject(err);\n }, ms);\n });\n return await Promise.race([fn(controller.signal), timeout]);\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n }\n}\n\nfunction errorSummary(err: unknown): string {\n if (err instanceof Error) {\n const code = connectCodeOf(err) ?? statusCodeOf(err);\n return code !== undefined\n ? `${err.name}(${String(code)}): ${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 tenki` has resolved.\n * Lives at `~/.agentbox/tenki-prepared.json` so the auto-prepare gate\n * (`ensureTenkiBaseImage()`) and `backend.provision` can resolve the base\n * image every box boots from.\n *\n * Tenki boots a session from a snapshot id.\n * The \"base\" recorded here is the ID of the snapshot carrying\n * the AgentBox runtime (agentbox-ctl, the agents, tmux), captured by\n * `prepare` from a throwaway builder sandbox. Per-box `create` then boots from\n * it in seconds.\n *\n * Schema versioned so future shape changes can migrate. Only `schema: 2` is\n * accepted; an older file is refused by `readPreparedState` and simply re-baked.\n */\n\nimport {\n preparedStatePathFor,\n readPreparedStateRaw,\n UserFacingError,\n writePreparedStateRaw,\n} from '@madarco/agentbox-provider-sdk';\n\n// Bumped to 2 when the base moved from a registry image ref to a snapshot id;\n// readPreparedState refuses an older schema, so a stale file is simply re-baked.\nconst SCHEMA = 2 as const;\n\nexport interface PreparedTenkiBase {\n /** Tenki snapshot id the AgentBox runtime is baked into. `create({ snapshotId })` boots from this. */\n snapshotId: string;\n /** Human-readable snapshot label (informational; ids are what we boot from). */\n snapshotName?: string;\n /** CLI version that produced this base (informational). */\n cliVersion?: string;\n /** Git short SHA of the CLI build (informational). */\n cliCommit?: string;\n /** ISO timestamp of prepare completion. */\n createdAt: string;\n}\n\nexport interface PreparedTenkiState {\n schema: typeof SCHEMA;\n /** The shared base image ref. Absent until first `agentbox prepare`. */\n base?: PreparedTenkiBase;\n}\n\nexport function preparedStatePath(): string {\n return preparedStatePathFor('tenki');\n}\n\nexport function readPreparedState(): PreparedTenkiState {\n const raw = readPreparedStateRaw('tenki');\n if (raw === null || typeof raw !== 'object') return { schema: SCHEMA };\n const parsed = raw as Partial<PreparedTenkiState>;\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: PreparedTenkiState): void {\n writePreparedStateRaw('tenki', state);\n}\n\n/** Update one field of the state without forcing callers to read/merge/write. */\nexport function updatePreparedState(mutate: (s: PreparedTenkiState) => void): void {\n const s = readPreparedState();\n mutate(s);\n writePreparedState(s);\n}\n\n/**\n * CURRENT build-context fingerprint for the tenki base. The base is an opaque\n * Tenki snapshot id, so there is no reproducible host-side fingerprint to diff\n * against. Returning `undefined` makes the cross-provider freshness nudge\n * degrade to \"can't tell, don't nag\" — the same contract the other providers use\n * when assets can't be resolved.\n */\nexport async function currentTenkiBaseFingerprintLive(): Promise<string | undefined> {\n return undefined;\n}\n\n/**\n * First-use gate. If no base image is recorded, throw an actionable error\n * pointing at `agentbox prepare --provider tenki`. Called by\n * `backend.provision` (so `create` / `claude` trip it but `prepare` itself\n * does not — same shape as the hetzner / vercel / e2b gates).\n */\nexport function ensureTenkiBaseImage(): void {\n const state = readPreparedState();\n if (state.base !== undefined) return;\n throw new UserFacingError(\n 'no Tenki base image found.\\n' +\n 'Run `agentbox prepare --provider tenki` first — it bakes a snapshot carrying the ' +\n 'agentbox runtime (agentbox-ctl, claude/codex/opencode, tmux) in your Tenki ' +\n 'workspace so per-box `create` boots ready in seconds.',\n );\n}\n","/**\n * `buildTenkiAttach` — the Tenki provider's override of `Provider.buildAttach`.\n *\n * ## Why this shells out to real OpenSSH\n *\n * `session.ssh()` is NOT a shell channel: `SSHConnection` is a thin wrapper over\n * a WebSocket carrying the **raw SSH wire protocol**, exposing only\n * `read()/write()/close()`. Writing shell commands into it just gets you the\n * gateway's version banner (`SSH-2.0-Tenki-edge-gateway`) — reaching a shell\n * means performing the SSH handshake: key exchange, publickey auth, channel\n * open, PTY request. The data plane is no help either: `session.run` has no\n * tty/pty option, so there is no interactive path through it.\n *\n * So we let the host's `ssh` do the protocol and use our helper purely as the\n * transport:\n *\n * ssh -o ProxyCommand='node attach-helper.cjs --proxy --session-id <id>' \\\n * -i <key> -o CertificateFile=<key>-cert.pub -t <user>@<id> <inner cmd>\n *\n * Auth is an ephemeral ed25519 key plus a short-lived OpenSSH certificate from\n * `issueSandboxSSHCert` — which the SDK documents for exactly this\n * (`<key>-cert.pub` / `-o CertificateFile=`).\n *\n * Beyond correctness this gets window-size propagation for free: OpenSSH\n * forwards SIGWINCH, so tmux tracks terminal resizes. The previous\n * bridge-the-bytes-ourselves design could not, since `SSHConnection` has no\n * resize op.\n *\n * Detached pre-start keeps its own path (see `--detached` in the helper): it only\n * needs to CREATE the tmux session, which is a plain non-interactive exec.\n */\n\nimport { execFile } from 'node:child_process';\nimport { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { promisify } from 'node:util';\nimport {\n hostTermForCloud,\n renderInnerCommand,\n UserFacingError,\n type AttachKind,\n type AttachSpec,\n type BoxRecord,\n type BuildAttachOptions,\n} from '@madarco/agentbox-provider-sdk';\nimport { getTenkiClient, resolveAuthToken } from './sdk.js';\nimport { readAgentboxEnv, readTenkiEnv } from './env-loader.js';\n\nconst execFileAsync = promisify(execFile);\n\nconst SELF = dirname(fileURLToPath(import.meta.url));\n\n/**\n * Login user inside the box. Tenki's `sandbox` base runs as `tenki`, and the\n * certificate's principals are issued for the session — override only if a\n * custom base image uses a different account.\n */\nconst SSH_USER = readAgentboxEnv('SSH_USER') || 'tenki';\n\n/** Certificate lifetime. Short by design: a new one is minted per attach. */\nconst CERT_TTL_MS = 60 * 60_000;\n\n/**\n * Resolve the absolute path to `attach-helper.cjs`. Both entries are built into\n * this package's own `dist/`, so the helper is always a sibling of the module\n * calling it — a plugin ships its own runtime assets rather than having them\n * staged into the CLI's `runtime/` tree. The `..` candidate covers running from\n * `src/` under a TS loader during development.\n */\nexport function resolveAttachHelperPath(): string {\n const candidates = [\n resolve(SELF, 'attach-helper.cjs'),\n resolve(SELF, '..', 'dist', 'attach-helper.cjs'),\n ];\n for (const p of candidates) {\n if (existsSync(p)) return p;\n }\n return candidates[0]!;\n}\n\n/** Per-box key material. Mirrors hetzner's layout; the private key never leaves the host. */\nexport function sshKeyDir(sandboxId: string): string {\n return resolve(homedir(), '.agentbox', 'boxes', sandboxId, 'ssh');\n}\n\n/**\n * Ensure an ed25519 keypair exists for this box, returning its path and public\n * key. The keypair is reused across attaches (cheap, and its only authority is a\n * certificate that expires); the certificate is re-minted every time.\n */\nasync function ensureKeypair(sandboxId: string): Promise<{ keyPath: string; publicKey: string }> {\n const dir = sshKeyDir(sandboxId);\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n const keyPath = join(dir, 'id_ed25519');\n if (!existsSync(keyPath)) {\n try {\n await execFileAsync('ssh-keygen', [\n '-t',\n 'ed25519',\n '-N',\n '',\n '-C',\n `agentbox-tenki-${sandboxId}`,\n '-q',\n '-f',\n keyPath,\n ]);\n } catch (err) {\n throw new UserFacingError(\n 'tenki attach: could not generate an SSH key — `ssh-keygen` is required ' +\n '(it ships with OpenSSH, alongside the `ssh` client this attach also needs).\\n' +\n (err instanceof Error ? err.message : String(err)),\n );\n }\n chmodSync(keyPath, 0o600);\n }\n return { keyPath, publicKey: readFileSync(`${keyPath}.pub`, 'utf8').trim() };\n}\n\n/** Mint a fresh certificate for the keypair and write it where ssh will find it. */\nasync function issueCert(sandboxId: string, keyPath: string, publicKey: string): Promise<string> {\n const cert = await getTenkiClient().issueSandboxSSHCert(sandboxId, publicKey, {\n ttlMs: CERT_TTL_MS,\n });\n const certPath = `${keyPath}-cert.pub`;\n writeFileSync(certPath, cert.sshCert.trim() + '\\n', { mode: 0o600 });\n return certPath;\n}\n\n/**\n * Quote a path for the ProxyCommand string. `ssh` hands that value to `/bin/sh`,\n * so a path with spaces would otherwise split into separate words.\n */\nfunction shq(s: string): string {\n return `'${s.replace(/'/g, `'\\\\''`)}'`;\n}\n\nexport async function buildTenkiAttach(\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(`tenki box ${box.name} has no sandboxId — record is malformed`);\n }\n\n const helper = resolveAttachHelperPath();\n if (!existsSync(helper)) {\n throw new Error(\n `tenki attach helper not found at ${helper} — the installed ` +\n '@tenkicloud/agentbox-provider is missing dist/attach-helper.cjs; reinstall the package.',\n );\n }\n\n const authToken = resolveAuthToken();\n const inner = renderInnerCommand(kind, opts);\n const hostTerm = hostTermForCloud();\n\n const env: Record<string, string> = {\n // The ProxyCommand child opens the gateway WebSocket, so it needs the token.\n TENKI_AUTH_TOKEN: authToken,\n AGENTBOX_HOST_TERM: hostTerm,\n // `ssh -t` propagates the local TERM to the remote PTY.\n TERM: hostTerm,\n };\n // The helper reads the TENKI_* names; LUXORAI_* values are resolved here.\n const baseUrl = readTenkiEnv('BASE_URL');\n if (baseUrl) env.TENKI_BASE_URL = baseUrl;\n const gatewayAddress = readTenkiEnv('GATEWAY_ADDRESS');\n if (gatewayAddress) env.TENKI_GATEWAY_ADDRESS = gatewayAddress;\n\n // Detached pre-start: only CREATE the tmux session (renderInnerCommand with\n // `detached:true` emits no trailing `exec tmux attach`). That is a plain\n // non-interactive exec, so it skips SSH entirely — and must, since opening an\n // interactive session here would idle forever and hang the host's\n // `runDetached` await.\n if (opts?.detached) {\n return {\n argv: [process.execPath, helper, '--detached', '--session-id', sandboxId],\n env: { ...env, AGENTBOX_TENKI_INNER_CMD: inner },\n };\n }\n\n const { keyPath, publicKey } = await ensureKeypair(sandboxId);\n const certPath = await issueCert(sandboxId, keyPath, publicKey);\n\n const proxyCommand = `${shq(process.execPath)} ${shq(helper)} --proxy --session-id ${shq(sandboxId)}`;\n\n const argv = [\n 'ssh',\n '-o',\n `ProxyCommand=${proxyCommand}`,\n // Only ever offer the key we just minted a cert for; without this ssh may\n // walk the user's agent/default identities first and exhaust auth attempts.\n '-o',\n 'IdentitiesOnly=yes',\n '-i',\n keyPath,\n '-o',\n `CertificateFile=${certPath}`,\n // The gateway terminates the connection, and the \"host\" is a session id\n // rather than a stable endpoint — so there is no host key worth pinning, and\n // recording one would just churn the user's known_hosts.\n '-o',\n 'StrictHostKeyChecking=no',\n '-o',\n 'UserKnownHostsFile=/dev/null',\n // Suppresses the \"Permanently added … to the list of known hosts\" banner\n // that would otherwise print over the agent's first screen.\n '-o',\n 'LogLevel=ERROR',\n // Force a PTY: the remote command is `tmux attach`, which needs one.\n '-t',\n `${SSH_USER}@${sandboxId}`,\n inner,\n ];\n\n return { argv, env };\n}\n","/**\n * `agentbox prepare --provider tenki` — bake the AgentBox base image, then pin it\n * as a snapshot so per-box `create` boots ready in seconds.\n *\n * ## Shape: builder sandbox, not the template API\n *\n * Boot a throwaway session from Tenki's plain `sandbox` base, push the runtime in\n * over the data plane, run the install with `session.run`, snapshot it, and\n * record the snapshot id. This is the same shape the built-in Vercel and E2B\n * providers use, and it sidesteps every constraint the template API imposes:\n *\n * - **Real file upload.** A template build context is Git-only, so a template\n * cannot receive host files at all — it would have to reinstall the runtime\n * from npm and hope the published version matches the CLI. Over the data\n * plane we ship the *running CLI's own* `ctl.cjs` and shims via the provider\n * SDK's `resolveSharedRuntimeAsset`, which is the intended version-locking\n * mechanism.\n * - **Bash.** Template steps run under `sh -lc` (dash), where `pipefail` is a\n * fatal bashism. `session.run(['bash', '-c', …])` — what the backend already\n * uses for every exec — gives us bash 5.2.\n * - **Per-step error attribution.** Each install step is its own exec with a\n * name, so a failure says which step broke instead of failing somewhere\n * inside one opaque script.\n * - **No template-API restrictions**: no required `setup_script`, no\n * single-default-run-step projection, no Git-context rule.\n *\n * Boxes then boot with `create({ snapshotId })` — the same path the\n * checkpoint-restore flow already exercises.\n */\n\nimport {\n lstatSync,\n mkdirSync,\n mkdtempSync,\n readdirSync,\n rmSync,\n statSync,\n symlinkSync,\n} from 'node:fs';\nimport { homedir, tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport {\n readCliStamp,\n resolveSharedRuntimeAsset,\n stageClaudeStaticForUpload,\n stageCodexStaticForUpload,\n stageOpencodeStaticForUpload,\n UserFacingError,\n type CloudHandle,\n type Provider,\n type StageResult,\n} from '@madarco/agentbox-provider-sdk';\nimport { tenkiBackend } from './backend.js';\nimport { ensureTenkiCredentials } from './credentials.js';\nimport { getTenkiClient, resolveAuthToken } from './sdk.js';\nimport { readAgentboxEnv } from './env-loader.js';\nimport { preparedStatePath, readPreparedState, writePreparedState } from './prepared-state.js';\n\n/** Resources the builder session runs with. Disk is the constraint — npm installs are chunky. */\nconst BUILDER_CPU = 2;\nconst BUILDER_MEMORY_MB = 4096;\nconst BUILDER_DISK_GB = 20;\n/** Builder lifetime. Generous: apt plus three npm installs on a cold VM. */\nconst BUILDER_MAX_DURATION_MS = 45 * 60_000;\nconst BUILDER_READY_TIMEOUT_MS = 240_000;\n\n/**\n * The unprivileged user Tenki's `sandbox` base runs as — its data plane, `run`\n * and `ssh()` all land here. We keep it rather than renaming it to AgentBox's\n * usual `vscode`: renaming the account the platform's own guest agent runs under\n * is a good way to break the session. `/home/vscode` is symlinked to it instead.\n */\nconst BOX_USER = 'tenki';\nconst BOX_HOME = `/home/${BOX_USER}`;\n\n/** Directory layout every AgentBox box is expected to have. */\nconst BOX_DIRS = [\n '/workspace',\n '/run/agentbox',\n '/var/log/agentbox',\n '/var/lib/agentbox',\n '/etc/agentbox',\n '/etc/claude-code',\n '/usr/local/share/agentbox',\n];\n\n/** Paths chowned to the box user once everything is installed. */\nconst OWNED_PATHS = [\n BOX_HOME,\n '/workspace',\n '/run/agentbox',\n '/var/log/agentbox',\n '/var/lib/agentbox',\n];\n\n/**\n * A provider-neutral runtime asset, taken from the *running CLI* and pushed in\n * over the data plane.\n */\nexport interface PrepareAsset {\n /** Basename in the CLI's `runtime/_shared/`. */\n shared: string;\n /** Absolute destination inside the box. */\n dest: string;\n /** Octal mode applied on install. */\n mode: string;\n}\n\n/**\n * `shared` names must exist in the SDK's `SHARED_RUNTIME_ASSETS`; never vendor\n * copies of these — resolving them from the CLI is what keeps the baked\n * `agentbox-ctl` version-locked to the host driving the box.\n *\n * The `gh` / `git` / `ntn` / `linear` entries are deliberately *shims*: they land\n * in `/usr/local/bin`, which precedes `/usr/bin` on PATH, so an agent running\n * `git push` reaches the host relay (which holds the credentials) rather than the\n * real binary. That indirection is why a box needs no credentials of its own.\n */\nexport const PREPARE_ASSETS: readonly PrepareAsset[] = [\n { shared: 'ctl.cjs', dest: '/usr/local/bin/agentbox-ctl', mode: '0755' },\n { shared: 'agentbox-vnc-start', dest: '/usr/local/bin/agentbox-vnc-start', mode: '0755' },\n { shared: 'agentbox-dockerd-start', dest: '/usr/local/bin/agentbox-dockerd-start', mode: '0755' },\n {\n shared: 'agentbox-portless-trust',\n dest: '/usr/local/bin/agentbox-portless-trust',\n mode: '0755',\n },\n {\n shared: 'agentbox-checkpoint-cleanup',\n dest: '/usr/local/bin/agentbox-checkpoint-cleanup',\n mode: '0755',\n },\n { shared: 'agentbox-open', dest: '/usr/local/bin/agentbox-open', mode: '0755' },\n { shared: 'gh-shim', dest: '/usr/local/bin/gh', mode: '0755' },\n { shared: 'git-shim', dest: '/usr/local/bin/git', mode: '0755' },\n { shared: 'ntn-shim', dest: '/usr/local/bin/ntn', mode: '0755' },\n { shared: 'linear-shim', dest: '/usr/local/bin/linear', mode: '0755' },\n {\n shared: 'claude-managed-settings.json',\n dest: '/etc/claude-code/managed-settings.json',\n mode: '0644',\n },\n {\n shared: 'agentbox-codex-hooks.json',\n dest: '/usr/local/share/agentbox/codex-hooks.json',\n mode: '0644',\n },\n {\n shared: 'agentbox-setup-skill.md',\n dest: '/usr/local/share/agentbox/setup-guide.md',\n mode: '0644',\n },\n {\n shared: 'opencode-agentbox-plugin.js',\n dest: '/usr/local/share/agentbox/opencode-agentbox-plugin.js',\n mode: '0644',\n },\n];\n\n/** Staging path an asset is uploaded to before being installed into place. */\nexport function stagedAssetPath(shared: string): string {\n return `/tmp/agentbox-stage-${shared}`;\n}\n\n/** Coding agents installed globally from npm. */\nconst AGENT_PACKAGES = ['@anthropic-ai/claude-code', '@openai/codex', 'opencode-ai'];\n\n/**\n * Login-shell environment. `box.env` is written per-box at create time, so this\n * only carries what is constant for the image.\n */\nconst PROFILE_SH = `# Auto-loaded by login shells; box.env is written at create time.\nif [ -r /etc/agentbox/box.env ]; then\n set -a\n . /etc/agentbox/box.env\n set +a\nfi\ncase \":$PATH:\" in\n *:${BOX_HOME}/.local/bin:*) : ;;\n *) PATH=${BOX_HOME}/.local/bin:$PATH ;;\nesac\nexport PATH\nexport COLORTERM=\\${COLORTERM:-truecolor}\nexport DISABLE_AUTOUPDATER=\\${DISABLE_AUTOUPDATER:-1}\nexport BROWSER=\\${BROWSER:-/usr/local/bin/agentbox-open}\n`;\n\n/** One install step, run as its own exec so a failure names the step. */\nexport interface PrepareStep {\n name: string;\n command: string;\n /** Run as root (`privileged`). Unset means the box user. */\n root?: boolean;\n /** Per-step attempt timeout; the backend default (5 min) is too short for npm. */\n timeoutMs?: number;\n}\n\n/**\n * The install plan, as discrete steps. Pure, so the whole recipe is assertable\n * offline — a mistake here otherwise only surfaces minutes into a live bake.\n *\n * `bash -c` is guaranteed (the backend's exec always uses it), so `pipefail` and\n * heredocs are safe here, unlike in a template build step.\n */\nexport function buildInstallPlan(): PrepareStep[] {\n const quoted = (paths: readonly string[]): string => paths.map((p) => `\"${p}\"`).join(' ');\n\n return [\n {\n name: 'apt-packages',\n root: true,\n timeoutMs: 600_000,\n // tmux: the one hard dependency the base image lacks, and every\n // interactive attach runs inside a tmux session.\n //\n // docker.io: in-box Docker. Verified working on a Tenki VM — cgroup2,\n // native overlay2 (not the slow vfs fallback), and the shared\n // `agentbox-dockerd-start` launcher brings the daemon up. Containers do\n // not need nested virtualization, so the absence of /dev/kvm is irrelevant.\n command: [\n 'set -euo pipefail',\n 'export DEBIAN_FRONTEND=noninteractive',\n 'apt-get update -qq',\n 'apt-get install -y --no-install-recommends tmux docker.io',\n // The agent runs unprivileged. Login shells (attach, tmux) reach the\n // daemon through this group; Tenki's guest agent starts its exec\n // processes without supplementary groups, so those rely instead on the\n // socket mode the dockerd launcher sets. Both paths are covered.\n `usermod -aG docker ${BOX_USER}`,\n ].join('\\n'),\n },\n {\n name: 'box-dirs',\n root: true,\n command: `set -euo pipefail\\nmkdir -p ${quoted(BOX_DIRS)}\\nchmod 755 /workspace`,\n },\n {\n name: 'vscode-user',\n root: true,\n // AgentBox's credential seed (as inlined in the published provider SDK)\n // extracts with an in-shell `sudo -u vscode`, so the box needs a real\n // `vscode` account or the seed dies with \"sudo: unknown user vscode\" and\n // every box starts logged out. We add it as an ALIAS of the box user\n // (same uid/gid, same home, `-o` to permit the duplicate uid) rather than\n // a separate account: files then land owned by the user the agents\n // actually run as, and Tenki's own guest agent keeps its identity.\n //\n // The CLI has since switched to `backend.exec(…, { user })` with no\n // in-shell sudo; this step stays harmless once the SDK catches up.\n command: [\n 'set -euo pipefail',\n 'if ! id vscode >/dev/null 2>&1; then',\n ` useradd -o -u \"$(id -u ${BOX_USER})\" -g \"$(id -g ${BOX_USER})\" -d ${BOX_HOME} -s /bin/bash vscode`,\n 'fi',\n 'id vscode >/dev/null',\n ].join('\\n'),\n },\n {\n name: 'vscode-home-link',\n root: true,\n // agentbox-ctl hardcodes /home/vscode/... credential paths and the profile\n // shim puts /home/vscode/.local/bin on PATH. A symlink satisfies both\n // without renaming the platform's own user.\n command: `set -euo pipefail\\nln -sfn ${BOX_HOME} /home/vscode`,\n },\n {\n name: 'runtime-assets',\n root: true,\n command: [\n 'set -euo pipefail',\n ...PREPARE_ASSETS.map(\n ({ shared, dest, mode }) =>\n `install -D -m ${mode} \"${stagedAssetPath(shared)}\" \"${dest}\"`,\n ),\n // `xdg-open` is what agents and browsers actually invoke; route it to the\n // relay-backed opener so a link opens on the *host*, not in the box.\n 'ln -sf /usr/local/bin/agentbox-open /usr/local/bin/xdg-open',\n `rm -f ${quoted(PREPARE_ASSETS.map((a) => stagedAssetPath(a.shared)))}`,\n ].join('\\n'),\n },\n {\n name: 'agent-dirs',\n // Runs as the box user so its own home comes out correctly owned.\n //\n // The credential PIVOT is the point of this step. AgentBox's cloud scaffold\n // seeds per-agent credentials at CREATE time — for a backend with no volume\n // primitive (us, like vercel/e2b/hetzner) it uploads them straight into\n // `~/.agentbox-creds/<agent>/`. Those symlinks are what make the agents\n // actually read them; without the layout baked here, the seed lands in a\n // directory nothing consults and every box starts logged out.\n command: [\n 'set -euo pipefail',\n `mkdir -p ${BOX_HOME}/.claude/skills/agentbox-setup ${BOX_HOME}/.codex \\\n ${BOX_HOME}/.local/share/opencode ${BOX_HOME}/.agentbox-creds/claude \\\n ${BOX_HOME}/.agentbox-creds/codex ${BOX_HOME}/.agentbox-creds/opencode`,\n `ln -sf ${BOX_HOME}/.agentbox-creds/claude/.credentials.json ${BOX_HOME}/.claude/.credentials.json`,\n `ln -sf ${BOX_HOME}/.agentbox-creds/codex/auth.json ${BOX_HOME}/.codex/auth.json`,\n `ln -sf ${BOX_HOME}/.agentbox-creds/opencode/auth.json ${BOX_HOME}/.local/share/opencode/auth.json`,\n `ln -sf ${BOX_HOME}/.claude/_claude.json ${BOX_HOME}/.claude.json`,\n // The in-box first-run wizard the setup prompt refers to.\n `cp /usr/local/share/agentbox/setup-guide.md ${BOX_HOME}/.claude/skills/agentbox-setup/SKILL.md`,\n ].join('\\n'),\n },\n {\n name: 'coding-agents',\n // npm's global prefix is writable by the box user in Tenki's base image, so\n // this needs no elevation — and installing as the box user keeps the\n // resulting file ownership right.\n timeoutMs: 1_800_000,\n command: `set -euo pipefail\\nnpm install -g ${AGENT_PACKAGES.join(' ')}`,\n },\n {\n name: 'login-shell-env',\n root: true,\n command: [\n 'set -euo pipefail',\n \"cat > /etc/profile.d/agentbox.sh <<'AGENTBOX_PROFILE'\",\n PROFILE_SH.trimEnd(),\n 'AGENTBOX_PROFILE',\n 'chmod 0644 /etc/profile.d/agentbox.sh',\n ].join('\\n'),\n },\n {\n name: 'ownership',\n root: true,\n // Last: the steps above wrote as root, and the box runs unprivileged.\n command: `set -euo pipefail\\nchown -R ${BOX_USER}:${BOX_USER} ${quoted(OWNED_PATHS)}`,\n },\n {\n name: 'verify',\n // Fail the bake here rather than pinning a snapshot that cannot supervise a\n // box. `agentbox-ctl` is the one that really matters.\n command: [\n 'set -euo pipefail',\n 'command -v tmux >/dev/null',\n 'command -v agentbox-ctl >/dev/null',\n // In-box docker: the binaries plus the box user's group membership, so a\n // login shell in the box reaches the daemon without sudo.\n 'command -v docker >/dev/null',\n 'command -v dockerd >/dev/null',\n `id -nG ${BOX_USER} | tr ' ' '\\\\n' | grep -qx docker`,\n 'test -x /usr/local/bin/agentbox-ctl',\n 'test -f /etc/claude-code/managed-settings.json',\n 'for a in claude codex opencode; do command -v \"$a\" >/dev/null || { echo \"agent $a missing\" >&2; exit 1; }; done',\n // The credential pivot must be symlinks pointing into ~/.agentbox-creds,\n // or the create-time credential seed lands where nothing reads it. A\n // dangling link is expected and fine here: the target file only appears\n // when a box is created.\n `for l in \"${BOX_HOME}/.claude/.credentials.json\" \"${BOX_HOME}/.codex/auth.json\" \\\n \"${BOX_HOME}/.local/share/opencode/auth.json\" \"${BOX_HOME}/.claude.json\"; do \\\n test -L \"$l\" || { echo \"credential pivot missing: $l\" >&2; exit 1; }; done`,\n `test -d \"${BOX_HOME}/.agentbox-creds/claude\"`,\n // The credential seed runs `sudo -u vscode`; without this the seed fails\n // silently-ish at create and boxes start unauthenticated.\n 'id vscode >/dev/null',\n `test \"$(id -u vscode)\" = \"$(id -u ${BOX_USER})\"`,\n `test -f \"${BOX_HOME}/.claude/skills/agentbox-setup/SKILL.md\"`,\n ].join('\\n'),\n },\n ];\n}\n\nexport interface PrepareTenkiOptions {\n name?: string;\n hostWorkspace?: string;\n /** Rebake even when a base snapshot is already recorded. */\n force?: boolean;\n /** Tenki base-image id the builder boots from (default: the workspace default). */\n baseImage?: string;\n /** Tenki workspace to build in (defaults to the token's own scope). */\n workspaceId?: string;\n cpuCount?: number;\n memoryMB?: number;\n diskSizeGB?: number;\n onLog?: (line: string) => void;\n}\n\nexport interface PrepareTenkiResult {\n /** The Tenki snapshot id recorded as the base. */\n snapshotName?: string;\n}\n\n/**\n * Build a throwaway HOME containing a *shadow* of `~/.codex`: real directories,\n * but every regular file replaced by a symlink to the original.\n *\n * Why: `stageCodexStaticForUpload` runs `rsync -aL` over `~/.codex`, and `-a`\n * implies `-D` (recreate devices and specials). Codex leaves a live Unix socket\n * at `~/.codex/ipc/ipc.sock` whenever it has run, which rsync cannot recreate —\n * it exits 23 (\"partial transfer due to error\") and the whole staging throws. The\n * upstream exclude list doesn't filter specials, so the built-in vercel and e2b\n * providers fail their entire `prepare` on such a host.\n *\n * The shadow skips anything that isn't a directory or regular file, so the socket\n * simply isn't there to trip over. Symlinks (rather than copies) mean no user data\n * is duplicated on disk — `rsync -L` reads through them to the originals. We also\n * omit `auth.json`: the helper excludes it from the tarball anyway, and this way\n * the token never lands in a temp directory at all.\n *\n * Returns null when there is no `~/.codex` to shadow — the caller then stages\n * normally and the helper reports \"nothing to stage\".\n */\nexport function shadowCodexHome(hostHome: string): { home: string; cleanup: () => void } | null {\n const source = join(hostHome, '.codex');\n try {\n if (!lstatSync(source).isDirectory()) return null;\n } catch {\n return null;\n }\n\n const root = mkdtempSync(join(tmpdir(), 'agentbox-tenki-codex-shadow-'));\n const mirror = (from: string, to: string, depth: number): void => {\n // Depth cap purely as a loop guard: a symlinked directory cycle would\n // otherwise recurse forever.\n if (depth > 12) return;\n mkdirSync(to, { recursive: true });\n for (const entry of readdirSync(from, { withFileTypes: true })) {\n // Never let the credential file into the temp tree.\n if (entry.name === 'auth.json') continue;\n const src = join(from, entry.name);\n const dest = join(to, entry.name);\n let stat;\n try {\n // statSync FOLLOWS symlinks, which is what we want: a symlinked config\n // directory gets recursed into (so a socket nested under it is still\n // dropped), a symlinked file gets shadowed, and a dangling link throws\n // here and is skipped.\n stat = statSync(src);\n } catch {\n continue;\n }\n if (stat.isDirectory()) {\n mirror(src, dest, depth + 1);\n } else if (stat.isFile()) {\n try {\n symlinkSync(src, dest);\n } catch {\n // A name collision or unsupported target is not worth failing over.\n }\n }\n // Sockets, FIFOs and devices are deliberately dropped — see above.\n }\n };\n\n try {\n mirror(source, join(root, '.codex'), 0);\n } catch {\n rmSync(root, { recursive: true, force: true });\n return null;\n }\n return { home: root, cleanup: () => rmSync(root, { recursive: true, force: true }) };\n}\n\n/**\n * Bake the host's STATIC agent config into the snapshot — settings, MCP servers,\n * plugin registries, history, and `/workspace` pre-trust — so a box starts with\n * the user's own setup instead of a blank one. This is what the built-in vercel\n * and e2b providers do at bake time, via the same three SDK helpers.\n *\n * Note what these deliberately do NOT carry: `stageClaudeStaticForUpload`\n * excludes `.credentials.json` and the opencode variant excludes `auth.json`.\n * Credentials are seeded per-box at CREATE time by the cloud scaffold (into the\n * `~/.agentbox-creds/<agent>/` layout the `agent-dirs` step bakes), which keeps\n * auth tokens out of a snapshot that may be shared or long-lived.\n *\n * Best-effort by design: a user with no host agent config, or one whose tarball\n * fails to extract, should still get a working base image.\n */\nasync function bakeAgentStaticConfig(\n handle: CloudHandle,\n hostWorkspace: string | undefined,\n progress: (s: string) => void,\n): Promise<void> {\n progress('staging host agent static config');\n const stagings: { kind: string; tar: StageResult; dest: string }[] = [];\n\n /**\n * Stage one agent, tolerating failure. These helpers shell out to `rsync` over\n * the user's real home, so a single unreadable file makes rsync exit non-zero\n * (23 = partial transfer) and throw. One awkward dotfile must not take the\n * whole base image down with it.\n */\n const stageOne = async (\n kind: string,\n stage: () => Promise<StageResult>,\n dest: string,\n ): Promise<void> => {\n let tar: StageResult;\n try {\n tar = await stage();\n } catch (err) {\n progress(\n `WARNING: could not stage ${kind} config (${err instanceof Error ? err.message.split('\\n')[0] : String(err)}) — continuing without it`,\n );\n return;\n }\n for (const w of tar.warnings) progress(w);\n if (tar.tarballPath) stagings.push({ kind, tar, dest });\n else await tar.cleanup();\n };\n\n try {\n await stageOne(\n 'claude',\n () => stageClaudeStaticForUpload(hostWorkspace ? { hostWorkspace } : {}),\n `${BOX_HOME}/.claude`,\n );\n // Codex goes through a sanitized shadow HOME; see shadowCodexHome.\n const codexShadow = shadowCodexHome(homedir());\n try {\n await stageOne(\n 'codex',\n () =>\n codexShadow\n ? stageCodexStaticForUpload({ hostHome: codexShadow.home })\n : stageCodexStaticForUpload(),\n `${BOX_HOME}/.codex`,\n );\n } finally {\n codexShadow?.cleanup();\n }\n await stageOne(\n 'opencode',\n () => stageOpencodeStaticForUpload(),\n `${BOX_HOME}/.local/share/opencode`,\n );\n\n if (stagings.length === 0) {\n progress('no host agent config to bake (boxes will start unconfigured)');\n return;\n }\n\n for (const st of stagings) {\n const remote = `/tmp/agentbox-${st.kind}-static.tar.gz`;\n progress(`baking ${st.kind} static config`);\n try {\n await tenkiBackend.uploadFile(handle, st.tar.tarballPath as string, remote);\n // Extract as the box user (no `root`) so files end up owned by it.\n // --no-same-owner/-permissions because the tarball carries host uids.\n const extract = [\n 'set -euo pipefail',\n `mkdir -p \"${st.dest}\"`,\n `tar -xzf \"${remote}\" -C \"${st.dest}\" --no-same-permissions --no-same-owner -m`,\n `rm -f \"${remote}\"`,\n ].join('\\n');\n const r = await tenkiBackend.exec(handle, extract, { attemptTimeoutMs: 300_000 });\n if (r.exitCode !== 0) {\n progress(\n `WARNING: ${st.kind} static config did not extract (exit ${String(r.exitCode)}) — continuing`,\n );\n }\n } catch (err) {\n // Same reasoning as staging: a usable base image without one agent's\n // settings beats no base image at all.\n progress(\n `WARNING: ${st.kind} static config failed to bake (${err instanceof Error ? err.message.split('\\n')[0] : String(err)}) — continuing`,\n );\n }\n }\n } finally {\n for (const st of stagings) await st.tar.cleanup();\n }\n}\n\n/**\n * True when the snapshot id still resolves in the workspace the current token\n * points at. A lookup failure of any kind counts as \"gone\": re-baking is safe\n * and self-correcting, whereas trusting a dead pin breaks the next `create`.\n */\nasync function snapshotExists(snapshotId: string): Promise<boolean> {\n try {\n const snap = await getTenkiClient().getSnapshot(snapshotId);\n return snap.state === undefined || snap.state === 'READY';\n } catch {\n return false;\n }\n}\n\n/** Timestamped label for the base snapshot. Ids are what we boot from; this is for humans. */\nfunction snapshotLabel(): string {\n const stamp = new Date().toISOString().replace(/[:.]/g, '-').replace('T', '_').replace('Z', '');\n return `agentbox-base-${stamp}`.toLowerCase();\n}\n\nexport async function prepareTenki(opts: PrepareTenkiOptions = {}): Promise<PrepareTenkiResult> {\n await ensureTenkiCredentials();\n resolveAuthToken(); // fail loud before any RPC if creds are missing\n const client = getTenkiClient();\n const log = opts.onLog ?? ((): void => {});\n const progress = (s: string): void => log(`prepare-tenki: ${s}`);\n\n const existing = readPreparedState();\n if (!opts.force && existing.base) {\n // Confirm the recorded snapshot still EXISTS before skipping. The pin lives\n // on the host but snapshots live in a Tenki workspace, so it goes stale two\n // ways: the snapshot is deleted, or the token now points at a different\n // workspace. Skipping blindly would defer the failure to `create`, which\n // surfaces it as an opaque platform error instead of \"re-bake your base\".\n if (await snapshotExists(existing.base.snapshotId)) {\n progress(\n `base snapshot ${existing.base.snapshotId} already prepared; skipping (pass --force to rebake)`,\n );\n return { snapshotName: existing.base.snapshotId };\n }\n progress(\n `recorded base snapshot ${existing.base.snapshotId} no longer exists in this workspace; re-baking`,\n );\n }\n\n // Resolve every host asset BEFORE booting a VM: `resolveSharedRuntimeAsset`\n // throws when the CLI has not stamped its runtime dir, and paying for a builder\n // only to fail on the first upload would be waste.\n const assets = PREPARE_ASSETS.map((asset) => ({\n ...asset,\n localPath: resolveSharedRuntimeAsset(asset.shared),\n }));\n\n const baseImage = opts.baseImage ?? readAgentboxEnv('BASE_IMAGE');\n const workspaceId = opts.workspaceId ?? readAgentboxEnv('WORKSPACE_ID');\n\n progress('booting builder sandbox');\n const builder = await client.create({\n name: 'agentbox-prepare',\n ...(baseImage ? { image: baseImage } : {}),\n ...(workspaceId ? { workspaceId } : {}),\n cpuCores: opts.cpuCount ?? BUILDER_CPU,\n memoryMb: opts.memoryMB ?? BUILDER_MEMORY_MB,\n diskSizeGb: opts.diskSizeGB ?? BUILDER_DISK_GB,\n maxDurationMs: BUILDER_MAX_DURATION_MS,\n allowOutbound: true,\n // The builder serves no traffic; it only needs egress for apt and npm.\n allowInbound: false,\n waitReady: true,\n waitTimeoutMs: BUILDER_READY_TIMEOUT_MS,\n // Marker so `list`/prune can tell an in-flight bake from a real box.\n metadata: { agentbox: 'true', 'agentbox.role': 'prepare' },\n });\n const handle: CloudHandle = { sandboxId: builder.id };\n progress(`builder ${builder.id} ready`);\n\n try {\n progress(`uploading ${String(assets.length)} runtime assets`);\n for (const asset of assets) {\n await tenkiBackend.uploadFile(handle, asset.localPath, stagedAssetPath(asset.shared));\n }\n\n const plan = buildInstallPlan();\n for (const [index, step] of plan.entries()) {\n progress(`[${String(index + 1)}/${String(plan.length)}] ${step.name}`);\n const result = await tenkiBackend.exec(handle, step.command, {\n ...(step.root ? { user: 'root' as const } : {}),\n ...(step.timeoutMs ? { attemptTimeoutMs: step.timeoutMs } : {}),\n });\n if (result.exitCode !== 0) {\n // Surface the step name and the tail of stderr: the whole point of\n // discrete steps is knowing which one broke.\n const detail = (result.stderr || result.stdout || '')\n .trim()\n .split('\\n')\n .slice(-12)\n .join('\\n');\n throw new UserFacingError(\n `tenki prepare: step '${step.name}' failed (exit ${String(result.exitCode)})` +\n (detail ? `:\\n${detail}` : ''),\n );\n }\n }\n\n await bakeAgentStaticConfig(handle, opts.hostWorkspace, progress);\n\n progress('snapshotting the builder (this takes a minute)');\n const label = snapshotLabel();\n const snapshot = await client.createSnapshotAndWait(builder.id, { name: label });\n\n const cliStamp = readCliStamp();\n writePreparedState({\n schema: 2,\n base: {\n snapshotId: snapshot.id,\n snapshotName: label,\n cliVersion: cliStamp.cliVersion,\n cliCommit: cliStamp.cliCommit,\n createdAt: new Date().toISOString(),\n },\n });\n progress(`wrote ${preparedStatePath()}`);\n progress(`prepare complete — base snapshot ${snapshot.id}`);\n return { snapshotName: snapshot.id };\n } finally {\n // Always tear the builder down: it is billable and serves no purpose once\n // snapshotted. A cleanup failure must not mask a build error, but it does\n // need to be visible, or a leaked VM runs until its deadline.\n try {\n const session = await client.get(builder.id);\n await session.close();\n progress(`builder ${builder.id} destroyed`);\n } catch (err) {\n progress(\n `WARNING: could not destroy builder ${builder.id} — terminate it manually: ` +\n (err instanceof Error ? err.message : String(err)),\n );\n }\n }\n}\n\n/** Provider-level binding used by the CLI's `prepare` command. */\nexport const prepareTenkiProvider: NonNullable<Provider['prepare']> = (req) =>\n prepareTenki({\n name: req.name,\n hostWorkspace: req.hostWorkspace ?? process.cwd(),\n force: req.force,\n // `--size` sizes the BOX at create time (the backend applies cpu/memory/disk\n // per session), so it deliberately does not size the builder here.\n onLog: req.onLog,\n });\n","/**\n * Interactive Tenki credential setup. Single mode — paste a workspace auth\n * token (`tk_…`) from the Tenki dashboard. Persists to\n * `~/.agentbox/secrets.env` (the canonical store, matching daytona / hetzner /\n * vercel / e2b).\n *\n * Non-interactive callers (no TTY): silent no-op, so scripted/CI runs surface\n * the SDK's own \"not configured\" error instead of hanging on a prompt.\n */\n\nimport { homedir } from 'node:os';\nimport { resolve } from 'node:path';\nimport { hostOpenCommand } from '@madarco/agentbox-provider-sdk';\nimport { cancel, confirm, intro, isCancel, log, note, outro, password } from '@clack/prompts';\nimport { ensureTenkiEnvLoaded, readTenkiAuthToken, reloadTenkiEnv } from './env-loader.js';\nimport { hasUsableCredentials } from './sdk.js';\nimport { writeManagedSecrets, type CredSetResult } from './secrets.js';\n\n// Ctrl+C at a prompt resolves with the cancel symbol; turn that into a real\n// quit so the command never silently continues as if the user answered \"No\".\nfunction exitOnCancel<T>(v: T | symbol): T {\n if (isCancel(v)) {\n cancel('Cancelled.');\n process.exit(130);\n }\n return v as T;\n}\n\nconst DASHBOARD_URL = 'https://tenki.cloud';\nconst DOCS_URL = 'https://tenki.cloud/docs';\n\n/**\n * Keys we manage in `~/.agentbox/secrets.env`. On reconfigure we strip prior\n * values for these before appending so the file never accumulates duplicates.\n * The LUXORAI_* tokens are cleared too, since they would shadow the new value.\n */\nconst MANAGED_KEYS = ['TENKI_AUTH_TOKEN', 'LUXORAI_AUTH_TOKEN', 'LUXORAI_API_TOKEN'] as const;\n\nexport interface EnsureTenkiCredentialsOptions {\n /** Re-prompt even when valid credentials are already present. */\n force?: boolean;\n}\n\nexport async function ensureTenkiCredentials(\n opts: EnsureTenkiCredentialsOptions = {},\n): Promise<void> {\n ensureTenkiEnvLoaded();\n\n if (!opts.force && hasUsableCredentials()) return;\n if (!process.stdin.isTTY) return;\n\n intro('Tenki setup');\n note(\n `AgentBox needs a Tenki workspace auth token to provision sandboxes.\\n` +\n `Create one in your Tenki dashboard (${DASHBOARD_URL}; see ${DOCS_URL}), then paste it below.\\n` +\n `The token is stored in \\`~/.agentbox/secrets.env\\` (mode 0600) — no .env.local harvesting.`,\n 'Credentials required',\n );\n\n const openIt = exitOnCancel(\n await confirm({\n message: `Open ${DASHBOARD_URL} to create a token?`,\n initialValue: true,\n }),\n );\n if (openIt) openDashboard();\n\n const token = exitOnCancel(\n await password({\n message: 'Paste your Tenki auth token (tk_…)',\n validate: (v) => (v && v.trim().length > 0 ? undefined : 'Cannot be empty'),\n }),\n );\n\n persistCredentials({ authToken: token.trim() });\n reloadTenkiEnv();\n log.success(`Tenki credentials saved to ${secretsPath()}`);\n outro('Setup complete.');\n}\n\nfunction persistCredentials(creds: { authToken: string }): void {\n // Atomic writer (temp-file + rename, mode 0600) so a token is never half\n // written or briefly world-readable. See ./secrets.ts.\n writeManagedSecrets(MANAGED_KEYS, { TENKI_AUTH_TOKEN: creds.authToken });\n}\n\n/**\n * Non-interactive credential write — the headless path an API driver uses,\n * bypassing the TTY-gated `ensureTenkiCredentials` prompts. Extracts the `token`\n * field, persists it, and returns a normalized result without echoing the secret.\n */\nexport function setTenkiCredentials(fields: Record<string, string>): CredSetResult {\n const token = (fields.token ?? '').trim();\n if (!token) {\n return { ok: false, error: 'token is required', status: { configured: false } };\n }\n persistCredentials({ authToken: token });\n const cred = readTenkiCredStatus();\n return { ok: true, status: { configured: cred.auth !== 'none', label: cred.auth } };\n}\n\nfunction openDashboard(): void {\n import('node:child_process')\n .then(({ spawnSync }) => {\n const r = spawnSync(hostOpenCommand(), [DASHBOARD_URL], { stdio: 'ignore' });\n if (r.status !== 0) {\n log.warn(`Could not auto-open the browser — visit ${DASHBOARD_URL} manually.`);\n }\n })\n .catch(() => {\n log.warn(`Could not auto-open the browser — visit ${DASHBOARD_URL} manually.`);\n });\n}\n\nexport function secretsPath(): string {\n return resolve(homedir(), '.agentbox', 'secrets.env');\n}\n\nexport interface TenkiCredStatus {\n auth: 'token' | 'none';\n token?: string;\n source: 'env' | 'secrets.env' | 'none';\n}\n\nexport function readTenkiCredStatus(): TenkiCredStatus {\n const shellHad = readTenkiAuthToken() !== undefined;\n ensureTenkiEnvLoaded();\n const token = readTenkiAuthToken();\n if (!token) return { auth: 'none', source: 'none' };\n return { auth: 'token', token, source: shellHad ? 'env' : 'secrets.env' };\n}\n\nexport function maskKey(value: string): string {\n if (value.length <= 8) return '*'.repeat(value.length);\n return `${value.slice(0, 4)}…${'*'.repeat(8)}${value.slice(-4)}`;\n}\n","/**\n * Atomic writer for the managed keys this provider owns in\n * `~/.agentbox/secrets.env` — the same store AgentBox's built-in cloud providers\n * use, so a Tenki token sits alongside the rest and `agentbox doctor` finds it.\n *\n * The provider SDK does not re-export AgentBox's internal secrets writer, so\n * this is a local implementation of the same contract: strip any prior values\n * for the keys we manage, append the new ones, and write through a temp file +\n * rename at mode 0600 so a concurrent reader never sees a half-written file and\n * the token is never briefly world-readable.\n */\n\nimport { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, resolve } from 'node:path';\nimport type { CredStatusSummary } from '@madarco/agentbox-provider-sdk';\n\n/**\n * Outcome of a non-interactive credential write. `status` reflects the store\n * *after* the attempt, so a caller can report `configured` without a second read.\n * Mirrors the shape AgentBox's `providerModule.setCredentials` returns.\n */\nexport interface CredSetResult {\n ok: boolean;\n /** One-line failure reason when `ok` is false (e.g. a rejected token). */\n error?: string;\n status: CredStatusSummary;\n}\n\n/** Canonical path of the AgentBox-managed secrets file. */\nexport function secretsEnvPath(): string {\n return resolve(homedir(), '.agentbox', 'secrets.env');\n}\n\n/**\n * Replace `managedKeys` in the secrets file with `record`, leaving every other\n * line untouched. Also updates `process.env` in place so the current process\n * sees the new values without a reload.\n */\nexport function writeManagedSecrets(\n managedKeys: readonly string[],\n record: Record<string, string>,\n): void {\n for (const k of managedKeys) delete process.env[k];\n for (const [k, v] of Object.entries(record)) process.env[k] = v;\n\n const path = secretsEnvPath();\n mkdirSync(dirname(path), { recursive: true });\n\n let existing = '';\n if (existsSync(path)) {\n try {\n existing = readFileSync(path, 'utf8');\n } catch {\n // An unreadable file is treated as empty rather than fatal: failing here\n // would leave the user unable to save credentials at all.\n existing = '';\n }\n }\n\n const kept = existing\n .split(/\\r?\\n/)\n .filter((line) => {\n const stripped = line.startsWith('export ') ? line.slice('export '.length) : line;\n const eq = stripped.indexOf('=');\n if (eq <= 0) return true;\n return !managedKeys.includes(stripped.slice(0, eq).trim());\n })\n .join('\\n')\n .replace(/\\s+$/u, '');\n\n const lines = Object.entries(record).map(([k, v]) => `${k}=${v}`);\n const body = (kept ? `${kept}\\n` : '') + lines.join('\\n') + '\\n';\n\n const tmp = `${path}.tmp`;\n writeFileSync(tmp, body, { mode: 0o600 });\n try {\n chmodSync(tmp, 0o600);\n } catch {\n // Best-effort: the writeFileSync mode already covers most filesystems.\n }\n renameSync(tmp, path);\n try {\n chmodSync(path, 0o600);\n } catch {\n // Already attempted on the temp file.\n }\n}\n","/**\n * Doctor probes + normalized credential status, assembled into `providerModule`\n * in `index.ts`. AgentBox dispatches to these generically (see the provider\n * SDK's `ProviderModule`), so `agentbox doctor` renders a `tenki:` group with the\n * same shape as the built-in providers.\n *\n * These checks are also the provider's ONLY credential-status surface. A plugin\n * cannot register a top-level command, so there is no `agentbox tenki login\n * --status` to run — the masked token and its source are reported here instead.\n */\n\nimport {\n errSummary,\n type CheckResult,\n type CredStatusSummary,\n} from '@madarco/agentbox-provider-sdk';\nimport { maskKey, readTenkiCredStatus, secretsPath } from './credentials.js';\nimport { readPreparedState } from './prepared-state.js';\n\nexport function readCredStatusSummary(): CredStatusSummary {\n const cred = readTenkiCredStatus();\n return { configured: cred.auth !== 'none', label: cred.auth };\n}\n\nexport async function doctorChecks(): Promise<CheckResult[]> {\n try {\n const cred = readTenkiCredStatus();\n const credRes: CheckResult =\n cred.auth === 'none'\n ? {\n label: 'credentials',\n status: 'warn',\n detail: 'not configured',\n // No `tenki login` command exists for a plugin provider: point at\n // the two places the token is actually read from. An interactive\n // `create` also prompts for it.\n hint: `set TENKI_AUTH_TOKEN in the environment or in ${secretsPath()}`,\n }\n : {\n label: 'credentials',\n status: 'ok',\n detail: `${cred.token ? maskKey(cred.token) : 'token'} (${cred.source})`,\n };\n\n const prepared = readPreparedState();\n const baseRes: CheckResult = prepared.base?.snapshotId\n ? {\n label: 'base image',\n status: 'ok',\n // Snapshot ids are long opaque UUIDs; show a short prefix plus the CLI\n // version that baked it, which is what actually tells you if it's stale.\n detail: `${prepared.base.snapshotId.slice(0, 18)}… (${prepared.base.cliVersion ?? '—'})`,\n }\n : {\n label: 'base image',\n status: 'warn',\n detail: 'not prepared',\n hint: '`agentbox prepare --provider tenki`',\n };\n return [credRes, baseRes];\n } catch (err) {\n return [{ label: 'credentials', status: 'warn', detail: errSummary(err) }];\n }\n}\n"],"mappings":"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","names":["readCliStamp","log","UserFacingError","existsSync","readFileSync","homedir","resolve","UserFacingError","resolve","existsSync","homedir","UserFacingError","readFileSync","shq","log","exposed","mkdirSync","rmSync","homedir","join","UserFacingError","homedir","resolve","chmodSync","existsSync","mkdirSync","readFileSync","writeFileSync","homedir","dirname","resolve","resolve","homedir","join","mkdirSync","rmSync","homedir","log","UserFacingError","snapshotLabel","readCliStamp"]}
package/package.json CHANGED
@@ -1,6 +1,80 @@
1
1
  {
2
2
  "name": "@luxorai/agentbox-provider",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "Tenki provider plugin for AgentBox — run coding agents in Tenki VMs",
5
+ "license": "MIT",
6
+ "author": "Luxor Labs",
7
+ "type": "module",
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "keywords": [
22
+ "agentbox",
23
+ "agentbox-provider",
24
+ "provider",
25
+ "plugin",
26
+ "tenki",
27
+ "sandbox",
28
+ "vm",
29
+ "coding-agent"
30
+ ],
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/LuxorLabs/tenki-agentbox-provider.git"
34
+ },
35
+ "homepage": "https://github.com/LuxorLabs/tenki-agentbox-provider#readme",
36
+ "bugs": {
37
+ "url": "https://github.com/LuxorLabs/tenki-agentbox-provider/issues"
38
+ },
39
+ "engines": {
40
+ "node": ">=20"
41
+ },
42
+ "agentbox": {
43
+ "providerApiVersion": 2
44
+ },
45
+ "publishConfig": {
46
+ "access": "public"
47
+ },
48
+ "scripts": {
49
+ "build": "npm run clean && tsup",
50
+ "dev": "tsup --watch",
51
+ "lint": "eslint src test scripts",
52
+ "format": "prettier --write .",
53
+ "format:check": "prettier --check .",
54
+ "typecheck": "tsc --noEmit",
55
+ "test": "vitest run",
56
+ "test:watch": "vitest",
57
+ "validate:package": "node scripts/validate-package.mjs",
58
+ "prepublishOnly": "npm run build",
59
+ "clean": "rm -rf dist",
60
+ "check:changelog": "node scripts/check-changelog.mjs"
61
+ },
62
+ "dependencies": {
63
+ "@clack/prompts": "^0.9.0",
64
+ "@madarco/agentbox-provider-sdk": "^2",
65
+ "@tenkicloud/sandbox": "^1.4.0"
66
+ },
67
+ "devDependencies": {
68
+ "@types/node": "^22.10.1",
69
+ "eslint": "^10.8.0",
70
+ "eslint-config-prettier": "^10.1.8",
71
+ "prettier": "^3.4.2",
72
+ "tsup": "^8.3.5",
73
+ "typescript": "^5.7.2",
74
+ "typescript-eslint": "^8.65.0",
75
+ "vitest": "^4.1.10"
76
+ },
77
+ "overrides": {
78
+ "esbuild": "^0.28.1"
79
+ }
80
+ }