@madarco/agentbox 0.7.0 → 0.9.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.
Files changed (77) hide show
  1. package/dist/_cloud-attach-ZXBCNWJX.js +13 -0
  2. package/dist/{chunk-NW5NYTQM.js → chunk-BXQMIEHC.js} +459 -110
  3. package/dist/chunk-BXQMIEHC.js.map +1 -0
  4. package/dist/{chunk-UK72UQ5U.js → chunk-G3H2L3O2.js} +55 -4
  5. package/dist/chunk-G3H2L3O2.js.map +1 -0
  6. package/dist/{chunk-7KOEFGN2.js → chunk-GU5LW4B5.js} +385 -31
  7. package/dist/chunk-GU5LW4B5.js.map +1 -0
  8. package/dist/chunk-KL36BRN4.js +455 -0
  9. package/dist/chunk-KL36BRN4.js.map +1 -0
  10. package/dist/{chunk-V5KZGB5V.js → chunk-LEV3KICD.js} +18 -2
  11. package/dist/chunk-LEV3KICD.js.map +1 -0
  12. package/dist/chunk-MTVI44DW.js +662 -0
  13. package/dist/chunk-MTVI44DW.js.map +1 -0
  14. package/dist/{chunk-NAVL4R34.js → chunk-NCJP5MTN.js} +1281 -556
  15. package/dist/chunk-NCJP5MTN.js.map +1 -0
  16. package/dist/{cloud-poller-ZIWSADJB-JXFRJUEM.js → cloud-poller-SUNA6ZQC-2RG5WPRN.js} +2 -2
  17. package/dist/{dist-ETCFRVPA.js → dist-32EZBYG4.js} +50 -20
  18. package/dist/{dist-R67WMLCF.js → dist-CX5CGVEB.js} +120 -10
  19. package/dist/dist-CX5CGVEB.js.map +1 -0
  20. package/dist/{dist-QZGJIBT5.js → dist-GDHP34ZK.js} +141 -75
  21. package/dist/dist-GDHP34ZK.js.map +1 -0
  22. package/dist/dist-XML54CNB.js +849 -0
  23. package/dist/dist-XML54CNB.js.map +1 -0
  24. package/dist/index.js +3881 -867
  25. package/dist/index.js.map +1 -1
  26. package/dist/prepared-state-CL4CWXQA-H5THETIM.js +18 -0
  27. package/dist/prepared-state-CL4CWXQA-H5THETIM.js.map +1 -0
  28. package/package.json +7 -5
  29. package/runtime/daytona/custom-system-CLAUDE.md +39 -0
  30. package/runtime/docker/Dockerfile.box +22 -0
  31. package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +1 -1
  32. package/runtime/docker/packages/ctl/dist/bin.cjs +1214 -98
  33. package/runtime/docker/packages/sandbox-docker/scripts/agentbox-codex-hooks.json +66 -35
  34. package/runtime/docker/packages/sandbox-docker/scripts/agentbox-vnc-start +15 -1
  35. package/runtime/docker/packages/sandbox-docker/scripts/claude-managed-settings.json +62 -1
  36. package/runtime/docker/packages/sandbox-docker/scripts/custom-system-CLAUDE.md +15 -4
  37. package/runtime/docker/packages/sandbox-docker/scripts/gh-shim +263 -0
  38. package/runtime/docker/packages/sandbox-docker/scripts/git-shim +131 -0
  39. package/runtime/docker/packages/sandbox-docker/scripts/opencode-agentbox-plugin.js +76 -0
  40. package/runtime/hetzner/agentbox-codex-hooks.json +66 -35
  41. package/runtime/hetzner/agentbox-setup-skill.md +1 -1
  42. package/runtime/hetzner/agentbox-vnc-start +15 -1
  43. package/runtime/hetzner/claude-managed-settings.json +62 -1
  44. package/runtime/hetzner/ctl.cjs +1214 -98
  45. package/runtime/hetzner/custom-system-CLAUDE.md +26 -14
  46. package/runtime/hetzner/gh-shim +263 -0
  47. package/runtime/hetzner/git-shim +131 -0
  48. package/runtime/hetzner/opencode-agentbox-plugin.js +76 -0
  49. package/runtime/hetzner/scripts/install-box.sh +11 -2
  50. package/runtime/relay/bin.cjs +1146 -63
  51. package/runtime/vercel/agentbox-checkpoint-cleanup +52 -0
  52. package/runtime/vercel/agentbox-codex-hooks.json +68 -0
  53. package/runtime/vercel/agentbox-open +28 -0
  54. package/runtime/vercel/agentbox-setup-skill.md +196 -0
  55. package/runtime/vercel/agentbox-vnc-start +91 -0
  56. package/runtime/vercel/claude-managed-settings.json +115 -0
  57. package/runtime/vercel/ctl.cjs +23466 -0
  58. package/runtime/vercel/custom-system-CLAUDE.md +50 -0
  59. package/runtime/vercel/gh-shim +263 -0
  60. package/runtime/vercel/git-shim +131 -0
  61. package/runtime/vercel/scripts/provision.sh +274 -0
  62. package/share/agentbox-setup/SKILL.md +1 -1
  63. package/share/host-skills/agentbox/SKILL.md +29 -0
  64. package/share/host-skills/agentbox-info/SKILL.md +211 -0
  65. package/share/host-skills/codex/agentbox.md +35 -0
  66. package/share/host-skills/opencode/agentbox.md +26 -0
  67. package/dist/_cloud-attach-DMVH6GWO.js +0 -12
  68. package/dist/chunk-7KOEFGN2.js.map +0 -1
  69. package/dist/chunk-NAVL4R34.js.map +0 -1
  70. package/dist/chunk-NW5NYTQM.js.map +0 -1
  71. package/dist/chunk-UK72UQ5U.js.map +0 -1
  72. package/dist/chunk-V5KZGB5V.js.map +0 -1
  73. package/dist/dist-QZGJIBT5.js.map +0 -1
  74. package/dist/dist-R67WMLCF.js.map +0 -1
  75. /package/dist/{_cloud-attach-DMVH6GWO.js.map → _cloud-attach-ZXBCNWJX.js.map} +0 -0
  76. /package/dist/{cloud-poller-ZIWSADJB-JXFRJUEM.js.map → cloud-poller-SUNA6ZQC-2RG5WPRN.js.map} +0 -0
  77. /package/dist/{dist-ETCFRVPA.js.map → dist-32EZBYG4.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../packages/sandbox-docker/src/create.ts","../../../packages/sandbox-docker/src/claude.ts","../../../packages/sandbox-docker/src/claude-hooks-filter.ts","../../../packages/sandbox-docker/src/claude-pull.ts","../../../packages/sandbox-docker/src/docker.ts","../../../packages/sandbox-docker/src/host-export.ts","../../../packages/sandbox-docker/src/claude-credentials.ts","../../../packages/sandbox-docker/src/state.ts","../../../packages/sandbox-docker/src/codex.ts","../../../packages/sandbox-docker/src/opencode.ts","../../../packages/sandbox-docker/src/dockerd.ts","../../../packages/sandbox-docker/src/vnc.ts","../../../packages/sandbox-docker/src/web.ts","../../../packages/sandbox-docker/src/git-worktree.ts","../../../packages/sandbox-docker/src/in-box-git.ts","../../../packages/sandbox-docker/src/portless.ts","../../../packages/sandbox-docker/src/image.ts","../../../packages/sandbox-docker/src/snapshot.ts","../../../packages/sandbox-docker/src/checkpoint.ts","../../../packages/sandbox-docker/src/ctl.ts","../../../packages/sandbox-docker/src/box-env.ts","../../../packages/sandbox-docker/src/home-ownership.ts","../../../packages/sandbox-docker/src/relay.ts","../../../packages/sandbox-docker/src/vscode.ts","../../../packages/sandbox-docker/src/lifecycle.ts","../../../packages/sandbox-docker/src/shell-session.ts","../../../packages/sandbox-docker/src/endpoints.ts","../../../packages/sandbox-docker/src/stats.ts","../../../packages/sandbox-docker/src/docker-provider.ts","../../../packages/sandbox-docker/src/host-stage.ts","../../../packages/sandbox-docker/src/browser.ts","../../../packages/ctl/src/types.ts","../../../packages/ctl/src/client.ts","../../../packages/ctl/src/render.ts","../../../packages/ctl/src/config.ts","../../../packages/config/src/types.ts","../../../packages/config/src/parse.ts","../../../packages/config/src/paths.ts","../../../packages/config/src/load.ts","../../../packages/config/src/checkpoint.ts","../../../packages/config/src/write.ts","../../../packages/sandbox-core/src/state.ts","../../../packages/sandbox-core/src/git-detect.ts","../../../packages/relay/src/types.ts","../../../packages/relay/src/host-action-queue.ts","../../../packages/relay/src/registry.ts","../../../packages/relay/src/prompts.ts","../../../packages/relay/src/notices.ts","../../../packages/relay/src/status-store.ts","../../../packages/relay/src/server.ts","../../../packages/relay/src/host-actions.ts","../../../packages/relay/src/autopause.ts","../../../packages/core/src/agent.ts","../../../packages/core/src/box-record.ts","../../../packages/core/src/errors.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { mkdir, stat } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { basename, join, resolve } from 'node:path';\nimport { execa } from 'execa';\nimport { ConfigError, loadConfig } from '@agentbox/ctl';\nimport {\n buildClaudeMounts,\n ensureClaudeVolume,\n resolveClaudeVolume,\n seedSetupSkillIntoVolume,\n} from './claude.js';\nimport { syncClaudeCredentials } from './claude-credentials.js';\nimport {\n buildCodexMounts,\n ensureCodexVolume,\n resolveCodexVolume,\n seedCodexHooks,\n type CodexMountResult,\n} from './codex.js';\nimport {\n buildOpencodeMounts,\n ensureOpencodeVolume,\n resolveOpencodeVolume,\n type OpencodeMountResult,\n} from './opencode.js';\nimport {\n type BoxLimitSpec,\n containerExists,\n dockerInfo,\n dockerStorageDriver,\n ensureVolume,\n publishedHostPort,\n runBox,\n} from './docker.js';\nimport { dockerVolumeName, launchDockerdDaemon } from './dockerd.js';\nimport { generateVncPassword, launchVncDaemon, VNC_CONTAINER_PORT } from './vnc.js';\nimport { WEB_CONTAINER_PORT } from './web.js';\nimport { detectGitRepos, pickFreshBranch } from './git-worktree.js';\nimport {\n bindWorktrees,\n chownGitBindParents,\n collectRepoCarryOver,\n gitWorktreePathFor,\n seedWorkspace,\n seedWorkspaceFromDir,\n type RepoCarryOver,\n} from './in-box-git.js';\nimport {\n CONTAINER_EXPORT_MERGED,\n DEFAULT_ENV_PATTERNS,\n boxRunDirFor,\n copyHostEnvFilesToBox,\n copyHostFilesToBox,\n detectEngine,\n} from './host-export.js';\nimport {\n detectPortless,\n portlessAlias,\n portlessBrowserEnv,\n portlessGetUrl,\n portlessStartHint,\n resolvePortlessHostStateDir,\n} from './portless.js';\nimport { DEFAULT_BOX_IMAGE, ensureImage } from './image.js';\nimport {\n allocateProjectIndex,\n readState,\n recordBox,\n type BoxRecord,\n type GitWorktreeRecord,\n} from './state.js';\nimport { createSnapshot, snapshotPathFor } from './snapshot.js';\nimport { resolveCheckpoint } from './checkpoint.js';\nimport { launchCtlDaemon } from './ctl.js';\nimport { writeBoxEnvFile } from './box-env.js';\nimport { ensureHomeOwnedByVscode } from './home-ownership.js';\nimport {\n ensureRelay,\n generateRelayToken,\n registerBoxWithRelay,\n rehydrateRelayRegistry,\n} from './relay.js';\nimport {\n buildIdeMounts,\n cursorServerVolumeName,\n ensureIdeVolumes,\n repairIdeOwnership,\n vscodeServerVolumeName,\n} from './vscode.js';\n\nexport interface CreateBoxOptions {\n workspacePath: string;\n name?: string;\n /**\n * Take a `cp -c` APFS clone of the host workspace into\n * `~/.agentbox/snapshots/<id>/` before seeding `/workspace`. Stabilizes the\n * source of the tar pipe in the non-git case (and the untracked-file\n * pipe in the git case) against host edits during create. Effectively a\n * no-op when a git worktree is detected — the worktree's tracked content\n * comes from `.git`, not from a workspace copy.\n */\n useSnapshot: boolean;\n /**\n * Start the box from a project checkpoint (the `--snapshot <ref>` path).\n * Resolved against `projectRoot` (or `workspacePath` when unset). The\n * checkpoint is a local Docker *image* tag now; the box is created with\n * `docker run <ckpt-image>` and inherits a populated `/workspace`. No\n * `seedWorkspace` runs in this path.\n */\n checkpointRef?: string;\n image?: string;\n onLog?: (line: string) => void;\n /**\n * Claude Code config volume. When omitted, defaults to `{ isolate: false }` —\n * every box mounts the shared `agentbox-claude-config` volume at\n * /home/vscode/.claude so auth / skills / plugins persist across boxes.\n */\n claudeConfig?: { isolate: boolean };\n /** Extra env vars forwarded to the container (merged on top of claude env forwarding). */\n claudeEnv?: Record<string, string>;\n /**\n * Codex CLI config volume. When provided (i.e. `agentbox codex`), the box\n * always mounts a synced `agentbox-codex-config` volume at /home/vscode/.codex.\n * When omitted, `createBox` still mounts it *if the host has a `~/.codex`*\n * (so a plain `agentbox create` for a Codex user gets a working box) — see\n * the codex block below. `isolate: true` opts into a per-box volume.\n */\n codexConfig?: { isolate: boolean };\n /**\n * OpenCode CLI config volume. When provided (i.e. `agentbox opencode`), the\n * box always mounts a synced `agentbox-opencode-config` volume. When omitted,\n * `createBox` still mounts it *if the host uses OpenCode* (`~/.config/opencode`\n * or `~/.local/share/opencode` exists). `isolate: true` opts into a per-box\n * volume. See the opencode block below.\n */\n opencodeConfig?: { isolate: boolean };\n /**\n * When true, run `npm install -g @playwright/cli@latest` inside the box after\n * `/workspace` is seeded. agent-browser is always installed in the image;\n * this flag adds the Playwright CLI on top for boxes that need it.\n */\n withPlaywright?: boolean;\n /**\n * When true, copy the host's env/config files (DEFAULT_ENV_PATTERNS basename\n * globs — `.env*`, `secrets.toml`, `agentbox.yaml`, ...) into the box's\n * /workspace after seeding, bypassing gitignore. The reverse of `pull env`.\n * One-shot at create time; the files persist in the container's writable\n * layer across pause/stop/start.\n */\n withEnv?: boolean;\n /**\n * Explicit relative-path file list to copy from `workspacePath` into the\n * box's /workspace after seeding (no glob expansion, no scan — the list is\n * pre-vetted, e.g. picked by the wizard's multiselect). Independent of\n * `withEnv`: if both are set, both run (idempotent on overlapping files).\n * One-shot at create time; persists across pause/stop/start.\n */\n envFilesToImport?: string[];\n /**\n * VNC stack (Xvnc on :1 + websockify serving noVNC on container :6080).\n * Defaults to enabled. The CLI exposes `--no-vnc` for opt-out. Disabling\n * skips port mapping + password generation + the in-container supervisor\n * launch; the apt-installed binaries stay in the image but are unused.\n */\n vnc?: { enabled: boolean };\n /**\n * Docker-in-Docker. Always-on (the in-box dockerd is part of the box\n * surface). When `sharedCache` is true the per-box `agentbox-docker-<id>`\n * volume is replaced with the shared `agentbox-docker-cache` volume — image\n * layers persist across boxes (and `destroy`/`prune` won't remove it).\n */\n docker?: { sharedCache: boolean };\n /**\n * When true, register a Portless route (`portless alias <box-name> <webPort>`)\n * so the box web app is reachable at `https://<box-name>.localhost`. Only\n * acts on non-OrbStack engines (OrbStack already has `.orb.local`) and only\n * when Portless is installed on the host — best-effort, never fails create.\n */\n portless?: boolean;\n /**\n * Override for the host Portless state directory shared into the box (the\n * `portless.stateDir` config key). When unset, `createBox` resolves Portless's\n * own default. Only consulted when `portless` is true.\n */\n portlessStateDir?: string;\n /**\n * Absolute host path of the cwd's project at create time. When provided,\n * `createBox` stamps `projectRoot` + an allocated `projectIndex` on the\n * BoxRecord so the CLI can auto-pick / resolve by index. The CLI computes\n * this via `findProjectRoot(workspacePath)` from `@agentbox/config`; this\n * package stays free of the config dep. Omit for unowned boxes created\n * directly via the programmatic API.\n */\n projectRoot?: string;\n /**\n * Container resource ceilings (engine-agnostic: bytes / fractional cpus /\n * pid count / raw disk size string). Absent fields = unlimited. `disk` is\n * best-effort: dropped (with a warning via `onLog`) when the engine's\n * storage driver can't enforce `--storage-opt size=` (overlay2 / macOS).\n */\n limits?: BoxLimitSpec;\n}\n\nexport interface CreatedBox {\n record: BoxRecord;\n imageBuilt: boolean;\n}\n\n/**\n * Compact the engine-applied limits into the BoxRecord shape: only fields that\n * actually constrain the box (>0 / non-empty). Returns undefined when nothing\n * was applied so legacy/unlimited boxes stay free of the field.\n */\nfunction persistableLimits(\n lim: BoxLimitSpec | undefined,\n): BoxRecord['resourceLimits'] | undefined {\n if (!lim) return undefined;\n const out: NonNullable<BoxRecord['resourceLimits']> = {};\n if (lim.memoryBytes && lim.memoryBytes > 0) out.memoryBytes = Math.floor(lim.memoryBytes);\n if (lim.cpus && lim.cpus > 0) out.cpus = lim.cpus;\n if (lim.pidsLimit && lim.pidsLimit > 0) out.pidsLimit = Math.floor(lim.pidsLimit);\n if (lim.disk) out.disk = lim.disk;\n return Object.keys(out).length > 0 ? out : undefined;\n}\n\nfunction generateBoxId(): string {\n return randomBytes(4).toString('hex');\n}\n\nexport function sanitizeBasename(workspacePath: string): string {\n const raw = basename(resolve(workspacePath));\n return raw\n .toLowerCase()\n .replace(/[^a-z0-9._-]+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^[-._]+|[-._]+$/g, '')\n .slice(0, 30)\n .replace(/[-._]+$/, '');\n}\n\nexport function defaultBoxName(workspacePath: string, id: string): string {\n const base = sanitizeBasename(workspacePath);\n return base.length > 0 ? `${base}-${id}` : id;\n}\n\nasync function pathExists(p: string): Promise<boolean> {\n try {\n await stat(p);\n return true;\n } catch {\n return false;\n }\n}\n\n// ~/.claude and ~/.codex are intentionally NOT in this list: each lives in a\n// named volume (`agentbox-claude-config` / `agentbox-codex-config`, see\n// resolveClaudeVolume / resolveCodexVolume) so auth persists inside the\n// container without leaking host state. Only the remaining identity files are\n// bind-mounted from the host.\nasync function buildIdentityMounts(): Promise<string[]> {\n const home = homedir();\n const candidates: Array<{ src: string; dst: string; readOnly: boolean }> = [\n { src: join(home, '.gitconfig'), dst: '/home/vscode/.gitconfig', readOnly: true },\n ];\n const out: string[] = [];\n for (const c of candidates) {\n if (await pathExists(c.src)) {\n out.push(`${c.src}:${c.dst}${c.readOnly ? ':ro' : ''}`);\n }\n }\n return out;\n}\n\nexport async function createBox(opts: CreateBoxOptions): Promise<CreatedBox> {\n const log = opts.onLog ?? (() => {});\n const workspace = resolve(opts.workspacePath);\n if (!(await pathExists(workspace))) {\n throw new Error(`workspace does not exist: ${workspace}`);\n }\n\n // Pre-flight agentbox.yaml validation on the host so the user sees the real\n // ConfigError instead of an opaque \"socket did not appear\" timeout from the\n // detached daemon exec later. The daemon re-validates inside the box anyway\n // — defence in depth, and necessary because the file lives in the\n // container's writable layer and can be edited after create.\n const cfgPath = join(workspace, 'agentbox.yaml');\n if (await pathExists(cfgPath)) {\n try {\n const cfg = await loadConfig(cfgPath);\n log(`agentbox.yaml validated (${String(cfg.services.length)} service(s))`);\n } catch (err) {\n if (err instanceof ConfigError) {\n throw new Error(`agentbox.yaml validation failed:\\n ${err.message}`);\n }\n throw err;\n }\n }\n\n await dockerInfo();\n log('docker daemon reachable');\n\n // Checkpoint resolution happens *before* image ensure because a checkpoint\n // image replaces the base image as the docker-run base. resolveCheckpoint\n // returns null on miss; we error with the ref so the user can fix it.\n let checkpointImage: string | undefined;\n let checkpointSource: BoxRecord['checkpointSource'];\n let restoredWorktrees: GitWorktreeRecord[] | undefined;\n if (opts.checkpointRef) {\n const projectRootForCkpt = opts.projectRoot ?? workspace;\n const head = await resolveCheckpoint(projectRootForCkpt, opts.checkpointRef);\n if (!head) {\n throw new Error(`checkpoint not found: ${opts.checkpointRef}`);\n }\n checkpointImage = head.manifest.image;\n // Chain: head first then its parents (base-most last). For a flattened\n // checkpoint this collapses to a single-entry chain.\n const chain = [head.name, ...head.manifest.parents];\n checkpointSource = { ref: opts.checkpointRef, type: head.manifest.type, chain };\n // The source's per-worktree paths persisted on the manifest so we can\n // re-establish the /workspace bind mount(s) after `docker run` (docker\n // commit doesn't capture bind-mount content, so the image's /workspace\n // is empty until we re-bind).\n restoredWorktrees = head.manifest.worktrees;\n log(\n `starting from checkpoint ${opts.checkpointRef} (${head.manifest.type}, ${String(chain.length)} layer(s), image ${head.manifest.image})`,\n );\n }\n\n const imageRef = checkpointImage ?? opts.image ?? DEFAULT_BOX_IMAGE;\n // ensureImage only acts on the base image; checkpoint images are local-only\n // and must already exist (they were created by `agentbox checkpoint`).\n const ensureRef = checkpointImage ? (opts.image ?? DEFAULT_BOX_IMAGE) : imageRef;\n const { built } = await ensureImage(ensureRef, {\n onProgress: (line) => log(`[image] ${line}`),\n });\n log(built ? `built image ${ensureRef}` : `using cached image ${imageRef}`);\n\n // Bring up the host relay before the box so the box can post events\n // immediately on boot. Best-effort — a relay outage shouldn't block create.\n // Always re-push known box tokens after ensure: the relay's registry is\n // in-memory, so a daemon restart or `docker restart agentbox-relay` between\n // CLI invocations leaves it empty. Repushing is idempotent and cheap.\n let relayUp = false;\n try {\n await ensureRelay({ onLog: log });\n const existing = await readState();\n await rehydrateRelayRegistry(existing.boxes);\n relayUp = true;\n } catch (err) {\n log(`relay unavailable: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n const id = generateBoxId();\n const name = opts.name ?? defaultBoxName(workspace, id);\n const containerName = `agentbox-${name}`;\n const createdAt = new Date().toISOString();\n if (await containerExists(containerName)) {\n throw new Error(`container ${containerName} already exists; remove it first`);\n }\n\n // Per-project monotonic index. Allocated *here* so it can flow into the\n // box / snapshot dir segments (`<id>-<n>-<mnemonic>`) and the\n // `AGENTBOX_PROJECT_INDEX` container env var. Pre-feature legacy boxes\n // never pass `projectRoot`; those records keep `projectIndex` undefined and\n // the dir segments fall back to `<id>-<mnemonic>`.\n let projectIndex: number | undefined;\n if (opts.projectRoot) {\n projectIndex = allocateProjectIndex(await readState(), opts.projectRoot);\n }\n\n // Repo detection + host-side carry-over capture. Branches are picked here\n // (against the host main repos' refs) so they're recorded on the BoxRecord\n // regardless of whether the in-container `git worktree add` succeeds later.\n // When restoring from a checkpoint, the source's per-worktree records are\n // restored from the manifest *here* (not after `docker run`) so the\n // `.git/` bind-mounts in `extraVolumes` know which host main repos to\n // wire up — without those binds the in-container `/workspace/.git` would\n // resolve to a path that doesn't exist in the new container.\n const repoCarryOvers: RepoCarryOver[] = [];\n const gitWorktreeRecords: GitWorktreeRecord[] = [];\n if (checkpointImage && restoredWorktrees && restoredWorktrees.length > 0) {\n gitWorktreeRecords.push(...restoredWorktrees);\n }\n if (!checkpointImage) {\n const repos = await detectGitRepos(workspace);\n if (repos.length > 0) {\n log(\n `detected ${String(repos.length)} git repo(s): ` +\n repos.map((r) => `${r.kind}${r.relPathFromWorkspace ? '@' + r.relPathFromWorkspace : ''}`).join(', '),\n );\n }\n for (const r of repos) {\n const branchBase =\n r.kind === 'root'\n ? `agentbox/${name}`\n : `agentbox/${name}--${r.relPathFromWorkspace.replace(/[^A-Za-z0-9._-]+/g, '_')}`;\n const branch = await pickFreshBranch(r.hostMainRepo, branchBase);\n const containerPath =\n r.kind === 'root' ? '/workspace' : `/workspace/${r.relPathFromWorkspace}`;\n const gitWorktreePath = gitWorktreePathFor(branch);\n const carry = await collectRepoCarryOver(r, branch, containerPath, gitWorktreePath);\n repoCarryOvers.push(carry);\n gitWorktreeRecords.push({\n kind: r.kind,\n hostMainRepo: r.hostMainRepo,\n containerPath,\n gitWorktreePath,\n branch,\n relPathFromWorkspace: r.relPathFromWorkspace,\n });\n }\n }\n\n // --host-snapshot: APFS clone the workspace into a per-box scratch dir.\n // Only the no-git, no-checkpoint path actually consumes the clone (as the\n // source of the tar pipe in seedWorkspaceFromDir). For the git path the\n // worktree content comes from `.git`'s object DB (bind-mounted) and the\n // untracked-file tar pipe reads from the live host main repo — neither\n // touches `snapshotDir`, so we skip it. For checkpoint restore there's no\n // seedWorkspace at all. Kept on the BoxRecord so destroyBox can clean it up.\n let snapshotDir: string | null = null;\n const snapshotIsUseful = !checkpointImage && repoCarryOvers.length === 0;\n if (opts.useSnapshot && snapshotIsUseful) {\n snapshotDir = snapshotPathFor({ id, name, projectIndex });\n log(`cloning workspace to ${snapshotDir} (APFS clone where available)`);\n const snap = await createSnapshot({ source: workspace, destination: snapshotDir });\n log(`pruned ${snap.prunedPaths.length} platform-dependent dirs from snapshot`);\n } else if (opts.useSnapshot && !checkpointImage) {\n log('skipping --host-snapshot: git worktree path reads content from .git, not from a workspace clone');\n }\n\n await ensureIdeVolumes(id);\n const dockerCacheShared = opts.docker?.sharedCache === true;\n const dockerVolume = dockerVolumeName(id, dockerCacheShared);\n await ensureVolume(dockerVolume);\n log(`prepared volumes ${vscodeServerVolumeName(id)}, ${cursorServerVolumeName(id)}, ${dockerVolume}`);\n const ide = buildIdeMounts(id);\n\n // Claude Code config volume. Shared by default so users sign in once across\n // every box; --isolate-claude-config opts into a per-box volume. Either way,\n // the host's ~/.claude is the authoritative source: we rsync host -> volume\n // on every create so updates on the host (new login, new skills, new MCP)\n // flow into the next box. Sync is additive — box-only state (session logs,\n // etc.) is preserved.\n const claudeSpec = resolveClaudeVolume({\n isolate: opts.claudeConfig?.isolate ?? false,\n boxId: id,\n });\n const claudeEnsured = await ensureClaudeVolume(claudeSpec, {\n syncFromHost: true,\n image: ensureRef,\n hostWorkspace: workspace,\n });\n if (claudeEnsured.synced) {\n log(`synced ${claudeSpec.volume} from ~/.claude`);\n if ((claudeEnsured.filteredHookCount ?? 0) > 0) {\n log(\n `filtered ${String(claudeEnsured.filteredHookCount)} host-path hook(s) (paths under ~/)`,\n );\n }\n if (claudeEnsured.installMethodFixed) {\n log('set installMethod=native in synced .claude.json (matches box native install)');\n }\n if (claudeEnsured.aliasedProjectKey) {\n log(`aliased project state for ${workspace} -> /workspace in synced .claude.json`);\n }\n if (claudeEnsured.workspaceTrusted) {\n log('pre-trusted /workspace in synced .claude.json (skips the trust dialog)');\n }\n } else if (claudeEnsured.created) {\n log(`created empty volume ${claudeSpec.volume} (no host ~/.claude to sync)`);\n } else {\n log(`reusing volume ${claudeSpec.volume} (no host ~/.claude to sync)`);\n }\n // Box-only: seed /agentbox-setup into the volume from the image. Never\n // touches the host's ~/.claude. Re-copied every run so an image upgrade\n // propagates to a long-lived shared volume.\n const seeded = await seedSetupSkillIntoVolume(claudeSpec.volume, ensureRef);\n if (seeded.seeded) log(`refreshed /agentbox-setup skill into ${claudeSpec.volume}`);\n // Mirror the in-box OAuth credentials with the host backup: extract a\n // box-written `.credentials.json` out to ~/.agentbox, or seed a fresh\n // volume from a previous box's login. Best-effort.\n const credSync = await syncClaudeCredentials(claudeSpec, {\n image: ensureRef,\n isolate: opts.claudeConfig?.isolate ?? false,\n });\n if (credSync.direction === 'extracted') {\n log('extracted box claude credentials to host backup');\n } else if (credSync.direction === 'seeded') {\n log(`seeded claude credentials into ${claudeSpec.volume} from host backup`);\n }\n const claudeMounts = buildClaudeMounts(claudeSpec, process.env);\n\n // Codex config volume. Mounted when the caller explicitly wants codex\n // (`agentbox codex` passes `codexConfig`) OR the host already uses codex\n // (`~/.codex` exists) — so a plain `agentbox create` for a Codex user still\n // gets a working box. Same host-authoritative additive sync as the claude\n // volume; `--isolate-codex-config` opts into a per-box volume.\n const wantCodex =\n opts.codexConfig !== undefined || (await pathExists(join(homedir(), '.codex')));\n let codexMounts: CodexMountResult | undefined;\n let codexConfigVolume: string | undefined;\n if (wantCodex) {\n const codexSpec = resolveCodexVolume({\n isolate: opts.codexConfig?.isolate ?? false,\n boxId: id,\n });\n const codexEnsured = await ensureCodexVolume(codexSpec, {\n syncFromHost: true,\n image: ensureRef,\n });\n if (codexEnsured.synced) log(`synced ${codexSpec.volume} from ~/.codex`);\n else if (codexEnsured.created) log(`created empty volume ${codexSpec.volume} (no host ~/.codex)`);\n else log(`reusing volume ${codexSpec.volume}`);\n // Box-only: seed the Codex activity hooks (~/.codex/hooks.json). Re-seeded\n // each create so an image upgrade propagates; never touches the host.\n const codexHooks = await seedCodexHooks(codexSpec.volume, ensureRef);\n if (codexHooks.seeded) log(`seeded Codex activity hooks into ${codexSpec.volume}`);\n codexMounts = buildCodexMounts(codexSpec, process.env);\n codexConfigVolume = codexSpec.volume;\n }\n\n // OpenCode config volume. Mounted when the caller wants opencode\n // (`agentbox opencode` passes `opencodeConfig`) OR the host already uses\n // OpenCode (`~/.config/opencode` or `~/.local/share/opencode` exists). One\n // volume holds both OpenCode dirs (data at the root, config in a `config/`\n // subdir via OPENCODE_CONFIG_DIR — see opencode.ts).\n const wantOpencode =\n opts.opencodeConfig !== undefined ||\n (await pathExists(join(homedir(), '.config', 'opencode'))) ||\n (await pathExists(join(homedir(), '.local', 'share', 'opencode')));\n let opencodeMounts: OpencodeMountResult | undefined;\n let opencodeConfigVolume: string | undefined;\n if (wantOpencode) {\n const opencodeSpec = resolveOpencodeVolume({\n isolate: opts.opencodeConfig?.isolate ?? false,\n boxId: id,\n });\n const opencodeEnsured = await ensureOpencodeVolume(opencodeSpec, {\n syncFromHost: true,\n image: ensureRef,\n });\n if (opencodeEnsured.synced) log(`synced ${opencodeSpec.volume} from ~/.config + ~/.local/share opencode`);\n else if (opencodeEnsured.created) log(`created empty volume ${opencodeSpec.volume} (no host opencode)`);\n else log(`reusing volume ${opencodeSpec.volume}`);\n opencodeMounts = buildOpencodeMounts(opencodeSpec, process.env);\n opencodeConfigVolume = opencodeSpec.volume;\n }\n\n const boxDir = boxRunDirFor({ id, name, projectIndex });\n const socketDir = join(boxDir, 'run');\n const socketPath = join(socketDir, 'ctl.sock');\n // Per-box host dir that `agentbox open` refreshes the merged /workspace\n // into. Bound in at create time so `docker exec rsync` can write straight\n // to the host filesystem — no container restart needed.\n const mergedExportDir = join(boxDir, 'workspace');\n await mkdir(socketDir, { recursive: true });\n await mkdir(mergedExportDir, { recursive: true });\n\n const extraVolumes = await buildIdentityMounts();\n extraVolumes.push(...claudeMounts.extraVolumes);\n if (codexMounts) extraVolumes.push(...codexMounts.extraVolumes);\n if (opencodeMounts) extraVolumes.push(...opencodeMounts.extraVolumes);\n extraVolumes.push(...ide.extraVolumes);\n extraVolumes.push(`${socketDir}:/run/agentbox`);\n extraVolumes.push(`${mergedExportDir}:${CONTAINER_EXPORT_MERGED}`);\n // In-box dockerd's data root. Per-box (`agentbox-docker-<id>`, wiped on\n // destroy) by default; shared (`agentbox-docker-cache`, preserved) when\n // `box.dockerCacheShared` is set.\n extraVolumes.push(`${dockerVolume}:/var/lib/docker`);\n // Bind-mount each main repo's `.git/` at its identical absolute host path,\n // RW. The in-container `git worktree add` writes to <main>/.git/worktrees/\n // and the agent's commits write to refs/objects; both have to hit the same\n // path on host and inside the container so `git push` from the host main\n // repo sees the new commits without further sync.\n for (const w of gitWorktreeRecords) {\n extraVolumes.push(`${w.hostMainRepo}/.git:${w.hostMainRepo}/.git`);\n }\n\n // Portless: when enabled (and not OrbStack), (1) make the in-box browser\n // route the box's `<name>.localhost` URL out to the host proxy\n // (`portlessBrowserEnv`), and (2) bind-mount the host's Portless state dir so\n // the in-box `portless` CLI shares the host's route registry (discovery).\n // Best-effort; a missing host dir is created so the bind has a source.\n // PORTLESS_STATE_DIR pins both sides to the same path.\n const portlessEnv: Record<string, string> = {};\n if (opts.portless === true && (await detectEngine()) !== 'orbstack') {\n Object.assign(portlessEnv, portlessBrowserEnv(name, { mapTarget: 'host.docker.internal' }));\n try {\n const hostStateDir = await resolvePortlessHostStateDir(opts.portlessStateDir);\n await mkdir(hostStateDir, { recursive: true });\n const boxStateDir = '/home/vscode/.portless';\n extraVolumes.push(`${hostStateDir}:${boxStateDir}`);\n portlessEnv['PORTLESS_STATE_DIR'] = boxStateDir;\n } catch (err) {\n log(\n `portless: state-dir share skipped (${err instanceof Error ? err.message : String(err)})`,\n );\n }\n }\n\n for (const v of extraVolumes) log(`mounting agent dir: ${v}`);\n\n // Per-box bearer token for the host relay. Register *before* runBox so the\n // box's supervisor can post on boot. Skip if the relay isn't reachable —\n // the box still works, it just won't deliver events to the host.\n const relayToken = generateRelayToken();\n if (relayUp) {\n try {\n await registerBoxWithRelay({\n boxId: id,\n token: relayToken,\n name,\n containerName,\n createdAt,\n projectIndex,\n worktrees: gitWorktreeRecords,\n });\n log(`registered box token with relay`);\n } catch (err) {\n log(`relay register failed: ${err instanceof Error ? err.message : String(err)}`);\n relayUp = false;\n }\n }\n const relayEnv: Record<string, string> = relayUp\n ? {\n // host.docker.internal resolves to the host (where the relay node\n // process is running). The matching `--add-host` is set in runBox.\n AGENTBOX_RELAY_URL: `http://host.docker.internal:8787`,\n AGENTBOX_RELAY_TOKEN: relayToken,\n }\n : {};\n\n // VNC stack defaults on; the CLI surfaces `--no-vnc` for opt-out. Generate\n // the password and the port mapping up front so they're baked into the\n // container's env + `-p` flags before `docker run` — both must be set at\n // create time (env survives stop/start; port mappings are immutable).\n const vncEnabled = opts.vnc?.enabled !== false;\n const vncPassword = vncEnabled ? generateVncPassword() : undefined;\n const vncEnv: Record<string, string> = vncEnabled && vncPassword\n ? { AGENTBOX_VNC_PASSWORD: vncPassword }\n : {};\n const vncPortMappings = vncEnabled\n ? [{ hostPort: 0, containerPort: VNC_CONTAINER_PORT, hostIp: '127.0.0.1' }]\n : [];\n\n // Reserve the web port unconditionally: `docker run -p` is immutable, but the\n // `expose:`-flagged service is usually only known after the in-box wizard\n // writes agentbox.yaml. The supervisor forwards :80 to it later; here we just\n // guarantee a published host port exists for whenever that happens.\n const webPortMappings = [\n { hostPort: 0, containerPort: WEB_CONTAINER_PORT, hostIp: '127.0.0.1' },\n ];\n\n // Identity vars that make the box self-aware. `projectIndex` was allocated\n // earlier (right after `id`/`name`) so dir-segment helpers could see it; we\n // just read the binding here.\n const agentboxEnv: Record<string, string> = {\n AGENTBOX: '1',\n AGENTBOX_BOX_NAME: name,\n AGENTBOX_HOST_WORKSPACE: workspace,\n ...(opts.projectRoot ? { AGENTBOX_PROJECT_ROOT: opts.projectRoot } : {}),\n ...(projectIndex !== undefined\n ? { AGENTBOX_PROJECT_INDEX: String(projectIndex) }\n : {}),\n };\n const boxEnvForFile: Record<string, string> = {\n AGENTBOX_BOX_ID: id,\n ...agentboxEnv,\n ...portlessEnv,\n };\n\n // `--storage-opt size=` is only enforced by devicemapper/btrfs/zfs.\n const appliedLimits: BoxLimitSpec | undefined = opts.limits;\n let effectiveLimits = appliedLimits;\n if (appliedLimits?.disk) {\n const driver = await dockerStorageDriver();\n if (!/^(devicemapper|btrfs|zfs|windowsfilter)$/.test(driver)) {\n log(\n `warning: --disk/box.disk is a no-op on this engine (storage-driver=${driver || 'unknown'}); ignoring`,\n );\n effectiveLimits = { ...appliedLimits, disk: null };\n }\n }\n\n await runBox({\n name: containerName,\n image: imageRef,\n extraVolumes,\n limits: effectiveLimits,\n portMappings: [...vncPortMappings, ...webPortMappings],\n env: {\n AGENTBOX_BOX_ID: id,\n ...agentboxEnv,\n ...claudeMounts.env,\n ...(codexMounts?.env ?? {}),\n ...(opencodeMounts?.env ?? {}),\n ...relayEnv,\n ...vncEnv,\n ...portlessEnv,\n ...(opts.claudeEnv ?? {}),\n },\n });\n log(`container ${containerName} started`);\n\n // Flip the in-container parent dir of each bind-mounted `.git` to\n // vscode-owned. Docker auto-creates the intermediates (e.g. the project root\n // path that contains `.git`) as root:root 755 in the writable layer; without\n // this chown the agent can't write siblings of `.git` (`.turbo/`, `.next/`,\n // build caches) at the project root. Non-recursive — the bind-mounted `.git`\n // itself stays untouched (recursive chown would propagate to the host).\n if (gitWorktreeRecords.length > 0) {\n await chownGitBindParents({\n container: containerName,\n hostMainRepos: gitWorktreeRecords.map((w) => w.hostMainRepo),\n onLog: log,\n });\n }\n\n // /etc/agentbox/box.env: sourced by /etc/profile.d/agentbox.sh in login\n // shells (the docker-run env doesn't reach `agentbox shell <box>` cleanly\n // without it). Best-effort — env vars on the container are the primary\n // path; this file is for shells launched via tools that strip env.\n const boxEnv = await writeBoxEnvFile(containerName, boxEnvForFile);\n if (boxEnv.ok) log('wrote /etc/agentbox/box.env');\n else log(`writing /etc/agentbox/box.env failed: ${boxEnv.reason}`);\n\n // Re-own /home/vscode to vscode. Root-run exec steps (checkpoint cleanup,\n // dockerd setup) and boxes restored from a checkpoint can leave home-dir\n // files root-owned; the shell + agent run as vscode and would silently\n // fail to write them (e.g. dropped `.bash_history`). Best-effort.\n await ensureHomeOwnedByVscode(containerName);\n\n // Seed /workspace.\n // - Checkpoint restore: the image already has the source box's per-box\n // worktree dir populated; we only need to re-establish the bind mount\n // onto /workspace (docker commit doesn't capture bind-mount content).\n // - Git path: create in-container worktrees + bind + replay stash + untracked.\n // - No-git path: tar-pipe host workspace (or its APFS clone) into\n // /workspace (no bind — files live directly in the image's writable\n // layer at /workspace).\n if (!checkpointImage) {\n if (repoCarryOvers.length > 0) {\n try {\n await seedWorkspace({ container: containerName, repos: repoCarryOvers, onLog: log });\n log('seeded /workspace from in-container git worktree(s)');\n } catch (err) {\n log(\n `seedWorkspace failed; leaving ${containerName} running so you can inspect it`,\n );\n throw err;\n }\n } else {\n const source = snapshotDir ?? workspace;\n await seedWorkspaceFromDir({ container: containerName, hostSource: source, onLog: log });\n }\n } else if (restoredWorktrees && restoredWorktrees.length > 0) {\n // gitWorktreeRecords was populated above (pre-`docker run`) so the .git\n // bind-mounts in extraVolumes are wired. The /workspace bind itself\n // can't be set up until the container is running, so we apply it here.\n await bindWorktrees(\n containerName,\n restoredWorktrees.map((w) => ({\n kind: w.kind,\n containerPath: w.containerPath,\n gitWorktreePath: w.gitWorktreePath,\n })),\n log,\n );\n log('re-bound /workspace from checkpoint image');\n } else {\n log('using /workspace from checkpoint image (no worktrees recorded; no rebind)');\n }\n\n await repairIdeOwnership(containerName);\n log('.vscode-server + .cursor-server ownership verified');\n\n const ctl = await launchCtlDaemon(containerName, socketPath);\n if (ctl.up) log('agentbox-ctl daemon up');\n else log(`agentbox-ctl daemon did not become reachable: ${ctl.reason}`);\n\n // dockerd: always-on, mirrors launchVncDaemon. Best-effort — a slow start\n // shouldn't fail box creation; `agentbox start` will relaunch on restart\n // (the daemon dies with the container). The storage driver is selected at\n // runtime by agentbox-dockerd-start (overlay2, with a fuse-overlayfs\n // fallback) — see /var/log/agentbox/dockerd.log inside the box.\n const dockerd = await launchDockerdDaemon(containerName);\n if (dockerd.up) {\n log(`dockerd up (data root=${dockerVolume})`);\n } else {\n log(`dockerd did not become ready: ${dockerd.reason}`);\n }\n\n if (opts.withPlaywright) {\n log('installing @playwright/cli@latest (--with-playwright)');\n const result = await execa(\n 'docker',\n [\n 'exec',\n '--user',\n 'root',\n containerName,\n 'bash',\n '-lc',\n 'npm install -g @playwright/cli@latest 2>&1',\n ],\n { reject: false },\n );\n for (const line of (result.stdout ?? '').split('\\n')) {\n if (line.trim().length > 0) log(`[playwright] ${line}`);\n }\n if (result.exitCode !== 0) {\n throw new Error(\n `failed to install @playwright/cli (exit ${String(result.exitCode)}): ${(result.stderr ?? '').toString().slice(0, 400)}`,\n );\n }\n log('@playwright/cli installed');\n }\n\n if (opts.withEnv) {\n log('copying host env/config files into /workspace (--with-env)');\n const { copied } = await copyHostEnvFilesToBox({\n container: containerName,\n workspaceDir: workspace,\n patterns: DEFAULT_ENV_PATTERNS,\n onLog: log,\n });\n log(copied > 0 ? `copied ${String(copied)} env/config file(s)` : 'no env/config files found');\n }\n\n if (opts.envFilesToImport && opts.envFilesToImport.length > 0) {\n log(`copying ${String(opts.envFilesToImport.length)} selected env/config file(s) into /workspace`);\n const { copied } = await copyHostFilesToBox({\n container: containerName,\n workspaceDir: workspace,\n files: opts.envFilesToImport,\n onLog: log,\n });\n if (copied !== opts.envFilesToImport.length) {\n log(`copied ${String(copied)}/${String(opts.envFilesToImport.length)} selected env/config file(s)`);\n }\n }\n\n // VNC daemon (Xvnc + websockify). Best-effort, like launchCtlDaemon. The\n // host port mapping was wired into runBox above (hostPort=0 → random); we\n // resolve the assigned port here for storage. If the daemon fails to come\n // up we still record vncEnabled so `agentbox start` will retry the launch.\n let vncHostPort: number | null = null;\n if (vncEnabled) {\n const vnc = await launchVncDaemon(containerName);\n if (vnc.up) log('vnc stack up (Xvnc + websockify + noVNC)');\n else log(`vnc stack did not become reachable: ${vnc.reason}`);\n vncHostPort = await publishedHostPort(containerName, VNC_CONTAINER_PORT);\n if (vncHostPort) log(`vnc web on host 127.0.0.1:${String(vncHostPort)}`);\n }\n\n const webHostPort = await publishedHostPort(containerName, WEB_CONTAINER_PORT);\n if (webHostPort) {\n log(\n `web port reserved on host 127.0.0.1:${String(webHostPort)} ` +\n `(forwards to the web service once agentbox.yaml sets a service expose:)`,\n );\n }\n\n // Portless: register `https://<box-name>.localhost -> 127.0.0.1:<webHostPort>`.\n // Best-effort — Portless is user-installed and never required; any failure\n // here just leaves the box on its loopback URL. Skipped on OrbStack (which\n // already has <container>.orb.local).\n let portlessAliasName: string | undefined;\n let portlessUrl: string | undefined;\n if (opts.portless === true && webHostPort) {\n try {\n const engine = await detectEngine();\n if (engine === 'orbstack') {\n log('portless: skipped (OrbStack already provides <container>.orb.local)');\n } else {\n const portless = await detectPortless();\n if (!portless.installed) {\n log('portless not installed — run `npm install -g portless` for a <name>.localhost URL');\n } else if (await portlessAlias(name, webHostPort)) {\n portlessAliasName = name;\n // Resolve the real URL from the proxy: scheme + port depend on how\n // the proxy was started (http://…:1355 no-TLS, or https://… on :443).\n portlessUrl = await portlessGetUrl(name);\n log(`portless alias ${portlessUrl} -> 127.0.0.1:${String(webHostPort)}`);\n if (!portless.proxyRunning) {\n log(`portless proxy not running — start it with \\`${portlessStartHint()}\\``);\n }\n } else {\n log('portless alias failed (best-effort) — box still reachable on the loopback URL');\n }\n }\n } catch (err) {\n log(`portless: ${err instanceof Error ? err.message : String(err)} (best-effort, ignored)`);\n }\n }\n\n const record: BoxRecord = {\n id,\n name,\n container: containerName,\n image: imageRef,\n workspacePath: workspace,\n snapshotDir,\n socketPath,\n claudeConfigVolume: claudeSpec.volume,\n codexConfigVolume,\n opencodeConfigVolume,\n vscodeServerVolume: vscodeServerVolumeName(id),\n cursorServerVolume: cursorServerVolumeName(id),\n relayToken: relayUp ? relayToken : undefined,\n gitWorktrees: gitWorktreeRecords.length > 0 ? gitWorktreeRecords : undefined,\n withPlaywright: opts.withPlaywright ? true : undefined,\n withEnv: opts.withEnv ? true : undefined,\n vncEnabled: vncEnabled ? true : undefined,\n vncContainerPort: vncEnabled ? VNC_CONTAINER_PORT : undefined,\n vncHostPort: vncHostPort ?? undefined,\n vncPassword: vncPassword,\n webContainerPort: WEB_CONTAINER_PORT,\n webHostPort: webHostPort ?? undefined,\n portlessAlias: portlessAliasName,\n portlessUrl,\n dockerVolume,\n dockerCacheShared: dockerCacheShared || undefined,\n projectRoot: opts.projectRoot,\n projectIndex,\n checkpointImage,\n checkpointSource,\n resourceLimits: persistableLimits(effectiveLimits),\n createdAt,\n };\n await recordBox(record);\n\n return { record, imageBuilt: built };\n}\n","import { spawnSync } from 'node:child_process';\nimport { mkdir, mkdtemp, readdir, readFile, rm, stat, writeFile } from 'node:fs/promises';\nimport { homedir, tmpdir } from 'node:os';\nimport { join, relative } from 'node:path';\nimport { setTimeout as delay } from 'node:timers/promises';\nimport { execa } from 'execa';\nimport {\n addProjectAlias,\n filterHostHooks,\n setInstallMethodNative,\n trustWorkspace,\n} from './claude-hooks-filter.js';\nimport {\n mergeInstalledPlugins,\n mergeKnownMarketplaces,\n pickNewItems,\n referencedPluginVersionKeys,\n SKILL_EXCLUDE_PREFIXES,\n} from './claude-pull.js';\nimport { ensureVolume, volumeExists } from './docker.js';\nimport { detectEngine, orbstackVolumePath } from './host-export.js';\n\nexport const SHARED_CLAUDE_VOLUME = 'agentbox-claude-config';\nexport const DEFAULT_CLAUDE_SESSION = 'claude';\nconst CONTAINER_CLAUDE_DIR = '/home/vscode/.claude';\nexport const CONTAINER_USER = 'vscode';\n/** Workspace is always mounted here inside the box, regardless of host path. */\nconst CONTAINER_WORKSPACE = '/workspace';\n/**\n * Image-baked copy of the agentbox-setup skill (Dockerfile.box COPYs\n * `apps/cli/share/agentbox-setup/SKILL.md` here). We seed it into the\n * claude-config volume so `/agentbox-setup` is available *inside boxes only* —\n * it is intentionally never written to the host's ~/.claude.\n */\nconst IN_BOX_SETUP_GUIDE_PATH = '/usr/local/share/agentbox/setup-guide.md';\n/** Destination skill file inside the claude-config volume (mounted at /dst). */\nconst SETUP_SKILL_DST = '/dst/skills/agentbox-setup/SKILL.md';\n\nexport interface ClaudeConfigSpec {\n /** Resolved Docker volume name mounted at /home/vscode/.claude. */\n volume: string;\n}\n\nexport function resolveClaudeVolume(opts: { isolate: boolean; boxId: string }): ClaudeConfigSpec {\n if (opts.isolate) {\n return { volume: `${SHARED_CLAUDE_VOLUME}-${opts.boxId}` };\n }\n return { volume: SHARED_CLAUDE_VOLUME };\n}\n\nexport interface EnsureClaudeVolumeOptions {\n /**\n * When true and the host's ~/.claude exists, rsync host -> volume on every call.\n * Sync is additive: files present on host overwrite same-named files in the\n * volume; box-only files (e.g. `projects/<hash>/*.jsonl` session history written\n * inside earlier boxes) are preserved.\n */\n syncFromHost: boolean;\n /** Image used by the throwaway sync helper container; we use the box image to avoid extra pulls. */\n image: string;\n /**\n * Host-absolute path of the workspace being bound to /workspace inside the\n * box. When provided, the synced `_claude.json` gets `projects[<hostWorkspace>]`\n * duplicated to `projects['/workspace']` so project-scoped MCP servers,\n * trust state, and history match what the host has for this project.\n */\n hostWorkspace?: string;\n}\n\nexport interface EnsureClaudeVolumeResult {\n /** True only the very first time the volume is created (on this host). */\n created: boolean;\n /** True when the rsync helper actually ran (syncFromHost was true AND host ~/.claude existed). */\n synced: boolean;\n /**\n * Number of hook entries dropped during sync because their `command` pointed\n * at a host path (under `$HOME/`) that wouldn't exist inside the container.\n * 0 when nothing was filtered or no sync ran.\n */\n filteredHookCount?: number;\n /**\n * True when the synced `_claude.json` had its install-method fields\n * (installMethod / autoUpdates / autoUpdatesProtectedForNative) coerced\n * to match the box's native install. False when they already matched.\n */\n installMethodFixed?: boolean;\n /**\n * True when `projects[<hostWorkspace>]` was duplicated to\n * `projects['/workspace']` in the synced `_claude.json` so the in-box claude\n * sees the host's project-scoped state (mcpServers, history, …).\n */\n aliasedProjectKey?: boolean;\n /**\n * True when `projects['/workspace'].hasTrustDialogAccepted` was set to `true`\n * in the synced `_claude.json` (it wasn't already). Pre-trusting the box's\n * workspace skips the trust dialog and avoids the Claude Code untrusted-\n * workspace `400 role 'system'` bug.\n */\n workspaceTrusted?: boolean;\n}\n\nasync function pathExists(p: string): Promise<boolean> {\n try {\n await stat(p);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * True when the claude-config volume already holds a `_claude.json` at its\n * root. Claude Code treats `~/.claude.json` as its own mutable runtime/auth\n * state (`oauthAccount`, `userID`, onboarding flags, ...). Once the volume has\n * one — written by an earlier box session or the throwaway `claude auth login`\n * container — overwriting it with the host's copy clobbers that state, and the\n * box's first API request then fails with `400 \"system\" role is not supported`.\n * Callers use this to keep `_claude.json` write-once.\n */\nasync function volumeHasClaudeJson(volume: string, image: string): Promise<boolean> {\n const res = await execa(\n 'docker',\n ['run', '--rm', '-v', `${volume}:/dst`, image, 'sh', '-c', 'test -e /dst/_claude.json'],\n { reject: false },\n );\n return res.exitCode === 0;\n}\n\n/**\n * Walk `root` and return rsync-style relative paths of every symlink whose\n * target doesn't resolve. We pass these to rsync as `--exclude` patterns so\n * the broken-symlink set (e.g. claude's `debug/latest` once an older debug\n * file is reaped) doesn't abort the whole sync under `--copy-unsafe-links`.\n *\n * Crosses into subdirs; doesn't follow symlinks (the whole point is to test\n * them rather than traverse them).\n */\nasync function findBrokenSymlinks(root: string): Promise<string[]> {\n const broken: string[] = [];\n async function walk(dir: string): Promise<void> {\n let entries;\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const ent of entries) {\n const full = join(dir, ent.name);\n if (ent.isSymbolicLink()) {\n try {\n await stat(full);\n } catch {\n broken.push(relative(root, full));\n }\n } else if (ent.isDirectory()) {\n await walk(full);\n }\n }\n }\n await walk(root);\n return broken;\n}\n\n/**\n * Ensure the named volume exists, then (when {@link EnsureClaudeVolumeOptions.syncFromHost}\n * is true and the host has a `~/.claude` directory) rsync host -> volume via a throwaway\n * helper container. The host is treated as the authoritative source for config:\n * settings, auth token, skills, plugins, and MCP entries on the host overwrite the\n * same-named files in the volume on every call. Files that only exist in the volume\n * (in-box session history under `projects/`, statsig cache, etc.) are preserved —\n * rsync runs without `--delete`.\n *\n * Caveat: if another box is currently running with the same shared volume mounted,\n * the rsync can change config files under it mid-session. We accept this as part of\n * \"host is authoritative\" — per-box state under `projects/` is untouched, so the\n * effect is limited to overlapping config files (rare to be edited live).\n *\n * Returns `created: true` only on the very first run for this volume; `synced: true`\n * whenever the rsync actually executed.\n */\nexport async function ensureClaudeVolume(\n spec: ClaudeConfigSpec,\n opts: EnsureClaudeVolumeOptions,\n): Promise<EnsureClaudeVolumeResult> {\n const existed = await volumeExists(spec.volume);\n await ensureVolume(spec.volume);\n const created = !existed;\n\n if (!opts.syncFromHost) return { created, synced: false };\n\n const hostClaude = join(homedir(), '.claude');\n if (!(await pathExists(hostClaude))) return { created, synced: false };\n\n // rsync (not cp -a) so repeat syncs skip unchanged files. rsync is installed in\n // the box image (Dockerfile.box). Trailing slash on /src-claude/ means\n // \"contents of src\", matching the original cp -a /src/. /dst/ semantics.\n // We run as root (--user 0) because the volume's existing content may be a\n // mix of UIDs (host's macOS UID for files copied from ~/.claude, plus\n // vscode's UID 1000 for anything claude wrote inside a box); only root can\n // rewrite arbitrary ownership. The post-chown brings everything back to\n // UID 1000 so the in-box vscode user can read/write.\n //\n // We also pull in ~/.claude.json (the *file* at home root that Claude Code\n // uses for global state: hasCompletedOnboarding, anonymousId, oauthAccount,\n // plugin caches). It's not inside ~/.claude, so we bind-mount it separately\n // (when present) and copy it into the volume as _claude.json. A symlink\n // baked into the image (/home/vscode/.claude.json -> .../_claude.json)\n // makes it reachable from the path claude expects.\n const hostClaudeJson = join(homedir(), '.claude.json');\n const hasJson = await pathExists(hostClaudeJson);\n // `_claude.json` is write-once in the volume. The first writer wins — the\n // throwaway `claude auth login` container (seeded just before it via this\n // same function), or an earlier box session. Re-copying the host's\n // ~/.claude.json on every create/start would clobber Claude's own\n // `oauthAccount` and break the box's first request (see volumeHasClaudeJson).\n const seedClaudeJson = !(await volumeHasClaudeJson(spec.volume, opts.image));\n const hostHome = homedir();\n // Claude Code's user-skills convention: ~/.claude/skills/<name> is a\n // RELATIVE symlink to ../../.agents/skills/<name>. From /src-claude/skills/\n // inside the helper that resolves to /.agents/skills/<name>. Bind-mount the\n // host's ~/.agents at /.agents so --copy-unsafe-links can dereference each\n // symlink into a real directory in /dst. Without this, rsync errors with\n // \"symlink has no referent\" and the whole sync aborts.\n const hostAgents = join(homedir(), '.agents');\n const hasAgents = await pathExists(hostAgents);\n const args: string[] = [\n 'run',\n '--rm',\n '--user',\n '0',\n // HOST_HOME used inside the shell script to rewrite host-absolute\n // installPath values in plugins/installed_plugins.json.\n '-e',\n `HOST_HOME=${hostHome}`,\n '-v',\n `${spec.volume}:/dst`,\n '-v',\n `${hostClaude}:/src-claude:ro`,\n ];\n if (hasJson && seedClaudeJson) args.push('-v', `${hostClaudeJson}:/src-claude-json:ro`);\n if (hasAgents) args.push('-v', `${hostAgents}:/.agents:ro`);\n\n // Pre-filter host-path hooks. Hook commands whose path is under the user's\n // host home (e.g. `/Users/marco/.config/iterm2/cc-status`) won't exist\n // inside the Linux container, and Claude logs a noisy\n // `SessionStart:startup hook error /bin/sh: …: not found` every time. We\n // build a small tempdir with filtered copies of `settings.json` /\n // `.claude.json`, mount it as `/src-filter`, and let the helper container\n // overlay it on top of what rsync brought in. The host files are never\n // touched.\n const filterDir = await mkdtemp(join(tmpdir(), 'agentbox-claude-filter-'));\n let filteredHookCount = 0;\n let installMethodFixed = false;\n let aliasedProjectKey = false;\n let workspaceTrusted = false;\n try {\n const settingsResult = await maybeFilterTo(\n join(hostClaude, 'settings.json'),\n join(filterDir, 'settings.json'),\n hostHome,\n );\n filteredHookCount += settingsResult.removedHooks;\n if (!seedClaudeJson) {\n // The volume already has a `_claude.json`; write-once leaves it intact\n // (see seedClaudeJson). No host overlay is generated for it — the\n // settings.json filtering above still applies.\n } else if (hasJson) {\n const jsonResult = await maybeFilterTo(\n hostClaudeJson,\n join(filterDir, '_claude.json'),\n hostHome,\n {\n setInstallMethodNative: true,\n aliasProject: opts.hostWorkspace\n ? { from: opts.hostWorkspace, to: CONTAINER_WORKSPACE }\n : undefined,\n trustWorkspacePath: CONTAINER_WORKSPACE,\n },\n );\n filteredHookCount += jsonResult.removedHooks;\n installMethodFixed = jsonResult.installMethodFixed;\n aliasedProjectKey = jsonResult.aliasedProjectKey;\n workspaceTrusted = jsonResult.workspaceTrusted;\n } else {\n // Host has no ~/.claude.json. Write a minimal _claude.json directly to\n // the filter dir so the in-box claude still gets installMethod=native\n // (skips the integrity warning) and a pre-trusted /workspace (skips the\n // trust dialog — and avoids the Claude Code bug where an untrusted\n // workspace yields `400 role 'system' is not supported on this model`).\n await writeFile(\n join(filterDir, '_claude.json'),\n JSON.stringify(\n {\n installMethod: 'native',\n autoUpdates: false,\n autoUpdatesProtectedForNative: true,\n projects: { [CONTAINER_WORKSPACE]: { hasTrustDialogAccepted: true } },\n },\n null,\n 2,\n ),\n );\n installMethodFixed = true;\n workspaceTrusted = true;\n }\n if (filteredHookCount > 0 || installMethodFixed || aliasedProjectKey || workspaceTrusted) {\n args.push('-v', `${filterDir}:/src-filter:ro`);\n }\n // Pre-scan for broken symlinks. With --copy-unsafe-links rsync errors out\n // and exits 23 when any unsafe symlink's referent is missing — e.g.\n // `~/.claude/debug/latest` regularly points to a debug file that's been\n // reaped. We can't predict every such case, so we walk once and tell\n // rsync to skip exactly those entries.\n const brokenSymlinks = await findBrokenSymlinks(hostClaude);\n const rsyncExcludes = ['--exclude=node_modules'];\n for (const rel of brokenSymlinks) rsyncExcludes.push(`--exclude=/${rel}`);\n const rsyncFlags = `-a --copy-unsafe-links ${rsyncExcludes.join(' ')}`;\n args.push(\n opts.image,\n 'sh',\n '-c',\n // Each step in its own brace group so a missing optional file (no\n // .claude.json on host, no filtered overlays) doesn't short-circuit the\n // final chown.\n //\n // --copy-unsafe-links: dereference symlinks pointing OUTSIDE\n // /src-claude (e.g. ~/.claude/skills/* -> ../../.agents/skills/*),\n // so user skills materialize as real directories inside the volume\n // without needing to also bind-mount ~/.agents.\n // --exclude=node_modules: skip every node_modules directory anywhere\n // in the tree. Plugin caches (plugins/cache/<m>/<p>/<v>/node_modules)\n // ship host-platform-specific binaries (darwin-arm64 fsevents,\n // esbuild, rollup, sharp) that are useless on linux/amd64. The\n // plugin source still lands; node_modules is rebuilt lazily inside\n // the box on first claude session (see rebuildPluginNativeDeps).\n //\n // The top-level plugin registry JSONs (installed_plugins.json,\n // known_marketplaces.json) carry host-absolute `installPath` /\n // `installLocation` values; without rewriting, claude resolves them\n // to `/Users/<you>/...` (or, when claude detects the missing path,\n // falls back to a slug derived from `source.repo` like\n // `microsoft-playwright-cli` — neither exists in the box, and the\n // marketplace fails to load, which masquerades as \"plugin not\n // found in marketplace\"). One sweep over every JSON directly under\n // /dst/plugins/ catches both files (and any future registry).\n // One-shot migration for volumes that were populated before\n // --exclude=node_modules existed. Without it, the volume keeps\n // host-darwin node_modules forever (rsync without --delete won't\n // remove them). The `.agentbox-cleaned-nm-v1` sentinel makes the wipe\n // a no-op after the first run; rebuildPluginNativeDeps repopulates\n // linux/amd64 node_modules on the next `agentbox claude`.\n '{ [ ! -f /dst/.agentbox-cleaned-nm-v1 ] && ' +\n 'find /dst -name node_modules -type d -prune -exec rm -rf {} + && ' +\n 'touch /dst/.agentbox-cleaned-nm-v1; true; }' +\n ` && rsync ${rsyncFlags} /src-claude/ /dst/` +\n ' && { [ -f /src-claude-json ] && cp -a /src-claude-json /dst/_claude.json; true; }' +\n ' && { [ -f /src-filter/settings.json ] && cp -a /src-filter/settings.json /dst/settings.json; true; }' +\n ' && { [ -f /src-filter/_claude.json ] && cp -a /src-filter/_claude.json /dst/_claude.json; true; }' +\n ' && { [ -d /dst/plugins ] && [ -n \"$HOST_HOME\" ] && ' +\n 'find /dst/plugins -maxdepth 1 -type f -name \"*.json\" ' +\n '-exec sed -i \"s|$HOST_HOME/.claude/plugins/|/home/vscode/.claude/plugins/|g\" {} +; true; }' +\n ' && chown -R 1000:1000 /dst',\n );\n await execa('docker', args);\n } finally {\n await rm(filterDir, { recursive: true, force: true });\n }\n\n return {\n created,\n synced: true,\n filteredHookCount,\n installMethodFixed,\n aliasedProjectKey,\n workspaceTrusted,\n };\n}\n\n/**\n * Seed the `agentbox-setup` skill into the claude-config volume from the\n * image-baked copy ({@link IN_BOX_SETUP_GUIDE_PATH}). This is the box-only\n * install path: the skill is intentionally never written to the host's\n * ~/.claude (so `agentbox claude` doesn't pollute the user's machine).\n *\n * Independent of `ensureClaudeVolume`'s host rsync — it runs even when the\n * host has no ~/.claude or `syncFromHost` was false. The skill is\n * agentbox-owned and image-versioned (not user-customizable, excluded from\n * the host<->box sync), so we re-copy it unconditionally: a stale copy in a\n * long-lived shared volume must not pin an old skill after an image upgrade.\n *\n * Best-effort: a failure here must not fail box creation.\n */\nexport async function seedSetupSkillIntoVolume(\n volume: string,\n image: string,\n): Promise<{ seeded: boolean }> {\n try {\n const { stdout } = await execa('docker', [\n 'run',\n '--rm',\n '--user',\n '0',\n '-v',\n `${volume}:/dst`,\n image,\n 'sh',\n '-c',\n // Always overwrite from the image so an image upgrade propagates. Prints\n // SEEDED on success; the whole thing is `|| true` so a missing image\n // asset is a clean no-op, never a non-zero exit.\n `{ [ -f ${IN_BOX_SETUP_GUIDE_PATH} ] && ` +\n `rm -rf /dst/skills/agentbox-setup && ` +\n `mkdir -p /dst/skills/agentbox-setup && ` +\n `cp -a ${IN_BOX_SETUP_GUIDE_PATH} ${SETUP_SKILL_DST} && ` +\n `chown -R 1000:1000 /dst/skills/agentbox-setup && echo SEEDED; } || true`,\n ]);\n return { seeded: stdout.includes('SEEDED') };\n } catch {\n return { seeded: false };\n }\n}\n\n/**\n * Read a JSON file, run it through {@link filterHostHooks}, (when opted in)\n * {@link setInstallMethodNative}, {@link addProjectAlias}, and\n * {@link trustWorkspace}, and write the result to `dest` ONLY when at least\n * one change was made. Tolerant of missing or garbage JSON — silently returns\n * zero changes in those cases (sync proceeds with the raw rsync'd file).\n */\nasync function maybeFilterTo(\n src: string,\n dest: string,\n hostHome: string,\n opts: {\n setInstallMethodNative?: boolean;\n aliasProject?: { from: string; to: string };\n trustWorkspacePath?: string;\n } = {},\n): Promise<{\n removedHooks: number;\n installMethodFixed: boolean;\n aliasedProjectKey: boolean;\n workspaceTrusted: boolean;\n}> {\n const zero = {\n removedHooks: 0,\n installMethodFixed: false,\n aliasedProjectKey: false,\n workspaceTrusted: false,\n };\n let parsed: unknown;\n try {\n parsed = JSON.parse(await readFile(src, 'utf8'));\n } catch {\n return zero;\n }\n const filtered = filterHostHooks(parsed, hostHome);\n let working: unknown = filtered.data;\n let installFixed = false;\n if (opts.setInstallMethodNative) {\n const r = setInstallMethodNative(working);\n working = r.data;\n installFixed = r.applied;\n }\n let aliased = false;\n if (opts.aliasProject) {\n const r = addProjectAlias(working, opts.aliasProject.from, opts.aliasProject.to);\n working = r.data;\n aliased = r.aliased;\n }\n let trusted = false;\n if (opts.trustWorkspacePath) {\n const r = trustWorkspace(working, opts.trustWorkspacePath);\n working = r.data;\n trusted = r.trusted;\n }\n if (filtered.removedCommands.length === 0 && !installFixed && !aliased && !trusted) {\n return zero;\n }\n await writeFile(dest, JSON.stringify(working, null, 2));\n return {\n removedHooks: filtered.removedCommands.length,\n installMethodFixed: installFixed,\n aliasedProjectKey: aliased,\n workspaceTrusted: trusted,\n };\n}\n\nexport interface ClaudeMountResult {\n /** Docker -v spec strings to append to runBox(extraVolumes). */\n extraVolumes: string[];\n /** Env vars to forward into the container; only includes keys that were set + non-empty on the host. */\n env: Record<string, string>;\n volumeName: string;\n}\n\n// Forwarded from the host's `process.env` into the box at `docker run -e` time\n// (and re-forwarded by `startClaudeSession` at `docker exec -e` time, so a\n// later `agentbox claude start <existing-box>` picks up the host's current\n// session env even when the container was created from a different shell).\n//\n// CLAUDE_EFFORT / ANTHROPIC_MODEL: Claude Code stores the user's model\n// selection (Opus/Sonnet/Haiku via /model or --effort) only in the parent\n// claude's process env — not in `~/.claude.json` or `~/.claude/settings.json`.\n// When the user invokes `agentbox claude` from inside their host claude\n// session, that env IS present in the calling shell; forwarding it is the\n// only way the in-box claude inherits the same model default.\nexport const CLAUDE_FORWARDED_ENV_KEYS = [\n 'ANTHROPIC_API_KEY',\n 'CLAUDE_CODE_OAUTH_TOKEN',\n 'CLAUDE_EFFORT',\n 'ANTHROPIC_MODEL',\n] as const;\n// Internal alias kept so existing usages in this file stay terse.\nconst FORWARDED_ENV_KEYS = CLAUDE_FORWARDED_ENV_KEYS;\n\nexport function buildClaudeMounts(\n spec: ClaudeConfigSpec,\n hostEnv: NodeJS.ProcessEnv,\n): ClaudeMountResult {\n const env: Record<string, string> = {};\n for (const k of FORWARDED_ENV_KEYS) {\n const v = hostEnv[k];\n if (typeof v === 'string' && v.length > 0) env[k] = v;\n }\n return {\n extraVolumes: [`${spec.volume}:${CONTAINER_CLAUDE_DIR}`],\n env,\n volumeName: spec.volume,\n };\n}\n\nexport interface RebuildPluginNativeDepsResult {\n /** Plugin cache directories whose node_modules was (re)installed during this call. */\n rebuilt: string[];\n /** Plugin cache directories where install failed; non-fatal, claude often still loads. */\n failed: Array<{ dir: string; stderr: string }>;\n /**\n * Stale plugin-version cache dirs (`<m>/<p>/<v>`, not referenced by\n * `installed_plugins.json`) whose `node_modules` was pruned during this call.\n */\n pruned: string[];\n /** Total bytes freed by {@link RebuildPluginNativeDepsResult.pruned}. */\n prunedBytes: number;\n /**\n * True when the in-box exec was skipped entirely because a host-side scan\n * proved every package.json-bearing plugin already carries its install\n * marker. Only possible when the volume is host-visible (OrbStack).\n */\n skipped: boolean;\n}\n\n/** Per-plugin sentinel written inside the cache dir after a successful install. */\nconst PLUGIN_INSTALLED_MARKER = '.agentbox-installed';\n\n/**\n * Per-plugin sentinel written (mtime = failure time) when an install fails. A\n * plugin with a *recent* fail marker is skipped instead of retried on every\n * launch; once the marker ages past {@link PLUGIN_INSTALL_BACKOFF_MS} it's\n * retried. Cleared on a later success.\n */\nconst PLUGIN_FAILED_MARKER = '.agentbox-install-failed';\n\n/** How long a failed plugin install is skipped before it's retried. */\nconst PLUGIN_INSTALL_BACKOFF_MS = 6 * 60 * 60 * 1000;\n\n/** Backoff window in whole minutes, for the in-box `find -mmin` recency test. */\nconst PLUGIN_INSTALL_BACKOFF_MIN = Math.round(PLUGIN_INSTALL_BACKOFF_MS / 60000);\n\n/**\n * Persistent npm cache, kept inside the claude-config volume so a given\n * package@version is fetched from the registry once *globally* and reused by\n * every later box and plugin version. Shared across boxes with the default\n * shared volume; per-box only under `--isolate-claude-config`. Not named\n * `node_modules`, so the one-time node_modules cleanup migration leaves it\n * alone; the host->volume rsync is additive (won't delete it) and `pull claude`\n * only pulls skills/plugins/agents/commands (won't drag it to the host).\n */\nconst NPM_CACHE_DIR = '/home/vscode/.claude/.agentbox-npm-cache';\n\nasync function isFile(p: string): Promise<boolean> {\n try {\n return (await stat(p)).isFile();\n } catch {\n return false;\n }\n}\n\n/** True when `p` exists and its mtime is within the install-backoff window. */\nasync function isRecentFailMarker(p: string): Promise<boolean> {\n try {\n const st = await stat(p);\n return Date.now() - st.mtimeMs < PLUGIN_INSTALL_BACKOFF_MS;\n } catch {\n return false;\n }\n}\n\nasync function isDir(p: string): Promise<boolean> {\n try {\n return (await stat(p)).isDirectory();\n } catch {\n return false;\n }\n}\n\n/**\n * Read `installed_plugins.json` next to a plugin cache dir and reduce it to the\n * set of `<m>/<p>/<v>` version keys it actively references. Missing/unparseable\n * file -> empty set (\"can't determine\"), which callers treat as \"apply no\n * reference-based filtering\".\n */\nasync function readReferencedPluginKeys(installedPluginsJsonPath: string): Promise<Set<string>> {\n try {\n const raw = await readFile(installedPluginsJsonPath, 'utf8');\n return referencedPluginVersionKeys(JSON.parse(raw) as unknown);\n } catch {\n return new Set<string>();\n }\n}\n\n/**\n * Pure host-side scan of a plugin `cache/<m>/<p>/<v>/` tree. Returns true iff\n * at least one version dir has a `package.json`, no install marker, and no\n * *recent* failure marker — i.e. the in-box rebuild would actually do npm\n * work. A missing/empty cache root means nothing to do (false). Mirrors the\n * in-box script's accept/skip rules (`packages/sandbox-docker/src/claude.ts`\n * rebuild script) so the host pre-check and the container never disagree.\n *\n * When the sibling `installed_plugins.json` yields a non-empty referenced set,\n * unreferenced version dirs are ignored here exactly as the in-box loop skips\n * installing them (prevention) — a stale dir is never \"rebuild work\".\n */\nexport async function scanPluginCacheForRebuild(cacheRoot: string): Promise<boolean> {\n const referenced = await readReferencedPluginKeys(\n join(cacheRoot, '..', 'installed_plugins.json'),\n );\n let marketplaces;\n try {\n marketplaces = await readdir(cacheRoot, { withFileTypes: true });\n } catch {\n return false;\n }\n for (const m of marketplaces) {\n if (!m.isDirectory()) continue;\n const mPath = join(cacheRoot, m.name);\n let plugins;\n try {\n plugins = await readdir(mPath, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const p of plugins) {\n if (!p.isDirectory()) continue;\n const pPath = join(mPath, p.name);\n let versions;\n try {\n versions = await readdir(pPath, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const v of versions) {\n if (!v.isDirectory()) continue;\n if (referenced.size > 0 && !referenced.has(`${m.name}/${p.name}/${v.name}`)) continue;\n const vPath = join(pPath, v.name);\n if (!(await isFile(join(vPath, 'package.json')))) continue;\n if (await isFile(join(vPath, PLUGIN_INSTALLED_MARKER))) continue;\n if (await isRecentFailMarker(join(vPath, PLUGIN_FAILED_MARKER))) continue;\n return true;\n }\n }\n }\n return false;\n}\n\n/**\n * Host-visible `plugins/cache` dir for a claude-config volume, or null when the\n * engine doesn't expose volume contents to the host (Docker Desktop / other).\n * Returns the cache path even if it doesn't exist yet — {@link\n * scanPluginCacheForRebuild} treats a missing cache as \"nothing to do\" — but\n * only once the volume itself is materialized on the host.\n */\nasync function resolveClaudeCacheLiveOnHost(volume: string): Promise<string | null> {\n if ((await detectEngine()) !== 'orbstack') return null;\n if (!(await isDir(orbstackVolumePath(volume)))) return null;\n return orbstackVolumePath(volume, 'plugins', 'cache');\n}\n\n/**\n * Walk `/home/vscode/.claude/plugins/cache/<m>/<p>/<v>/` inside the box and run\n * `npm install` (or `npm ci` when a lockfile is present) for any plugin that\n * ships a `package.json` but hasn't been installed yet. Marker-gated (not\n * node_modules — plugins with empty dep lists install cleanly without ever\n * creating a node_modules dir, so a dir check would loop forever).\n *\n * This exists because the host→volume rsync excludes `node_modules` (host\n * darwin-arm64 native binaries like fsevents.node / @esbuild/darwin-arm64\n * are useless on the linux/amd64 box). The first claude session in a fresh\n * box pays the install cost; subsequent attaches don't.\n *\n * Three things keep this fast: installs run **in parallel** (bounded), npm\n * shares a **persistent cache in the claude-config volume** ({@link\n * NPM_CACHE_DIR}) with `--prefer-offline` so a package@version is fetched once\n * globally, and a failed plugin records {@link PLUGIN_FAILED_MARKER} so it's\n * skipped (not retried) until {@link PLUGIN_INSTALL_BACKOFF_MS} elapses.\n *\n * Failures on individual plugins are reported but don't throw — most\n * plugins still load with a partial dependency graph, and we prefer\n * launching claude over blocking on a third-party plugin's install hiccup.\n *\n * When this pass runs at all (i.e. a plugin difference was detected, so a\n * rebuild is warranted) it also **prunes** stale plugin-version dirs: when\n * Claude updates a plugin it leaves the old `cache/<m>/<p>/<v>/` dir on disk,\n * and its ~hundreds-of-MB `node_modules` would otherwise live forever in the\n * shared claude-config volume. Any version dir not referenced by\n * `installed_plugins.json` has its `node_modules` (and our markers) removed,\n * and the install loop never (re)installs into an unreferenced dir.\n */\nasync function readBoxReferencedPluginKeys(container: string): Promise<Set<string>> {\n const res = await execa(\n 'docker',\n [\n 'exec',\n '--user',\n CONTAINER_USER,\n container,\n 'cat',\n `${CONTAINER_CLAUDE_DIR}/plugins/installed_plugins.json`,\n ],\n { reject: false },\n );\n if (res.exitCode !== 0 || !res.stdout) return new Set<string>();\n try {\n return referencedPluginVersionKeys(JSON.parse(res.stdout) as unknown);\n } catch {\n return new Set<string>();\n }\n}\n\nexport async function rebuildPluginNativeDeps(\n container: string,\n opts: {\n onProgress?: (line: string) => void;\n /**\n * The claude-config volume backing this box. When given and host-visible\n * (OrbStack), a pure-fs pre-scan skips the `docker exec` entirely if every\n * package.json plugin already has its install marker — the common case for\n * every box after the first global install.\n */\n volume?: string;\n } = {},\n): Promise<RebuildPluginNativeDepsResult> {\n if (opts.volume) {\n const cacheRoot = await resolveClaudeCacheLiveOnHost(opts.volume);\n if (cacheRoot && !(await scanPluginCacheForRebuild(cacheRoot))) {\n return { rebuilt: [], failed: [], pruned: [], prunedBytes: 0, skipped: true };\n }\n }\n // Reference set from the box's installed_plugins.json: version dirs Claude no\n // longer points at are stale. An empty set (file missing / unparseable)\n // disables both prevention and the prune pass — the script then behaves\n // exactly as it did before this feature.\n const referenced = await readBoxReferencedPluginKeys(container);\n const refSetup =\n referenced.size > 0\n ? `cat <<'AGENTBOX_REF_EOF' > \"$WORK/referenced\"\\n${[...referenced].sort().join('\\n')}\\nAGENTBOX_REF_EOF\\n`\n : '';\n // The host parser below expects the REBUILD_START / REBUILD_OK /\n // REBUILD_FAIL..REBUILD_FAIL_END protocol (plus PRUNE_OK lines); parallel\n // jobs write per-dir result+stderr files and we replay them after `wait`.\n const script = `set -u\nPLUGINS_DIR=/home/vscode/.claude/plugins/cache\nMARKER=${PLUGIN_INSTALLED_MARKER}\nFAILMARKER=${PLUGIN_FAILED_MARKER}\nNPM_CACHE=${NPM_CACHE_DIR}\nBACKOFF_MIN=${PLUGIN_INSTALL_BACKOFF_MIN}\nMAX=4\n[ -d \"$PLUGINS_DIR\" ] || exit 0\nmkdir -p \"$NPM_CACHE\"\nWORK=\\$(mktemp -d)\n${refSetup}relkey() { printf '%s' \"\\${1#$PLUGINS_DIR/}\" | tr '/' '_'; }\n# True when refs are unknown (no file) or $1 (<m>/<p>/<v>) is referenced.\nis_referenced() {\n [ -s \"$WORK/referenced\" ] || return 0\n grep -Fxq \"$1\" \"$WORK/referenced\"\n}\n# Run one plugin's install. $1 is frozen by value at call time, so it's safe\n# to read from the backgrounded subshell; the rest are set-once constants.\ndo_one() {\n d=\\$1\n key=\\$(relkey \"$d\")\n if (cd \"$d\" && \\\\\n if [ -f package-lock.json ]; then \\\\\n npm ci --no-audit --no-fund --silent --prefer-offline --cache \"$NPM_CACHE\"; \\\\\n else \\\\\n npm install --no-audit --no-fund --silent --no-package-lock --prefer-offline --cache \"$NPM_CACHE\"; \\\\\n fi) >\"$WORK/$key.out\" 2>\"$WORK/$key.err\"; then\n touch \"$d/$MARKER\"\n rm -f \"$d/$FAILMARKER\"\n printf 'OK\\\\n' > \"$WORK/$key.res\"\n else\n : > \"$d/$FAILMARKER\"\n printf 'FAIL\\\\n' > \"$WORK/$key.res\"\n fi\n}\n# Prune pass: every unreferenced (stale) version dir loses its node_modules and\n# our markers. Only runs when installed_plugins.json gave us a reference set.\nif [ -s \"$WORK/referenced\" ]; then\n for dir in \"$PLUGINS_DIR\"/*/*/*/; do\n [ -d \"$dir\" ] || continue\n rel=\\${dir%/}; rel=\\${rel#$PLUGINS_DIR/}\n grep -Fxq \"$rel\" \"$WORK/referenced\" && continue\n if [ -d \"$dir/node_modules\" ]; then\n bytes=\\$(du -sb \"$dir/node_modules\" 2>/dev/null | cut -f1)\n [ -n \"$bytes\" ] || bytes=0\n rm -rf \"$dir/node_modules\" \"$dir/$MARKER\" \"$dir/$FAILMARKER\"\n echo \"PRUNE_OK $rel $bytes\"\n else\n rm -f \"$dir/$MARKER\" \"$dir/$FAILMARKER\"\n fi\n done\nfi\nn=0\nfor dir in \"$PLUGINS_DIR\"/*/*/*/; do\n [ -d \"$dir\" ] || continue\n [ -f \"$dir/package.json\" ] || continue\n rel=\\${dir%/}; rel=\\${rel#$PLUGINS_DIR/}\n is_referenced \"$rel\" || continue\n [ -f \"$dir/$MARKER\" ] && continue\n [ -n \"\\$(find \"$dir\" -maxdepth 1 -name \"$FAILMARKER\" -mmin -\\$BACKOFF_MIN 2>/dev/null)\" ] && continue\n echo \"REBUILD_START \\${dir#$PLUGINS_DIR/}\"\n n=\\$((n+1))\n printf '%s\\\\n' \"$dir\" >> \"$WORK/dirs\"\ndone\nif [ \"$n\" -eq 0 ]; then rm -rf \"$WORK\"; exit 0; fi\nrunning=0\nwhile IFS= read -r dir; do\n do_one \"$dir\" &\n running=\\$((running+1))\n if [ \"$running\" -ge \"$MAX\" ]; then wait; running=0; fi\ndone < \"$WORK/dirs\"\nwait\nwhile IFS= read -r dir; do\n key=\\$(relkey \"$dir\")\n rel=\\${dir#$PLUGINS_DIR/}\n [ -f \"$WORK/$key.res\" ] || continue\n read -r st < \"$WORK/$key.res\"\n if [ \"$st\" = OK ]; then\n echo \"REBUILD_OK $rel\"\n else\n echo \"REBUILD_FAIL $rel\"\n sed 's/^/ /' \"$WORK/$key.err\"\n echo \"REBUILD_FAIL_END\"\n fi\ndone < \"$WORK/dirs\"\nrm -rf \"$WORK\"\n`;\n const result = await execa(\n 'docker',\n ['exec', '--user', CONTAINER_USER, container, 'sh', '-c', script],\n { reject: false },\n );\n const rebuilt: string[] = [];\n const failed: Array<{ dir: string; stderr: string }> = [];\n const pruned: string[] = [];\n let prunedBytes = 0;\n const lines = (result.stdout ?? '').split('\\n');\n let collectingFail: { dir: string; stderr: string[] } | null = null;\n for (const line of lines) {\n if (collectingFail) {\n if (line === 'REBUILD_FAIL_END') {\n failed.push({ dir: collectingFail.dir, stderr: collectingFail.stderr.join('\\n') });\n collectingFail = null;\n } else {\n collectingFail.stderr.push(line);\n }\n continue;\n }\n if (line.startsWith('REBUILD_START ')) {\n opts.onProgress?.(`rebuilding ${line.slice('REBUILD_START '.length)}`);\n } else if (line.startsWith('REBUILD_OK ')) {\n rebuilt.push(line.slice('REBUILD_OK '.length));\n } else if (line.startsWith('REBUILD_FAIL ')) {\n collectingFail = { dir: line.slice('REBUILD_FAIL '.length), stderr: [] };\n } else if (line.startsWith('PRUNE_OK ')) {\n // `PRUNE_OK <m>/<p>/<v> <bytes>` — bytes is the last space-delimited token.\n const rest = line.slice('PRUNE_OK '.length);\n const sp = rest.lastIndexOf(' ');\n if (sp > 0) {\n const dir = rest.slice(0, sp);\n const bytes = Number(rest.slice(sp + 1));\n pruned.push(dir);\n if (Number.isFinite(bytes)) prunedBytes += bytes;\n opts.onProgress?.(`pruning stale plugin cache ${dir}`);\n }\n }\n }\n return { rebuilt, failed, pruned, prunedBytes, skipped: false };\n}\n\nexport class ClaudeSessionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ClaudeSessionError';\n }\n}\n\nexport interface StartClaudeSessionOptions {\n container: string;\n claudeArgs: string[];\n sessionName?: string;\n /** Previously fed into the in-tmux status bar; now unused (the outer UI\n * shows the name). Kept for back-compat — callers may still pass it. */\n boxName?: string;\n}\n\n/**\n * Single-quote a token for /bin/sh. Conservative: anything outside the safe alphabet\n * gets wrapped. We don't try to detect \"obviously safe\" inputs; quoting is cheap.\n */\nfunction shQuote(arg: string): string {\n if (arg.length === 0) return `''`;\n if (/^[A-Za-z0-9_\\-./=:@%+,]+$/.test(arg)) return arg;\n return `'${arg.replace(/'/g, `'\\\\''`)}'`;\n}\n\n/**\n * Start a detached tmux session running Claude Code inside the container. The session\n * survives client disconnects; reattach via {@link attachClaudeSession}.\n *\n * We forward the host's TERM (default xterm-256color) so the in-container tmux\n * picks the right terminal-overrides at session creation time — without this,\n * docker exec defaults TERM to `xterm` and tmux can't declare 24-bit color.\n *\n * We also re-forward {@link FORWARDED_ENV_KEYS} from the host's process env.\n * Values set at container-create time (via runBox -e) are still inherited\n * for free, but the user might be invoking `agentbox claude start <box>`\n * from a different shell session — e.g. inside their host claude (which sets\n * CLAUDE_EFFORT) for a box created earlier from a plain terminal. Re-passing\n * at exec time lets the in-box claude pick up the host's *current* selection.\n */\nexport async function startClaudeSession(opts: StartClaudeSessionOptions): Promise<void> {\n const sessionName = opts.sessionName ?? DEFAULT_CLAUDE_SESSION;\n const cmd = ['claude', ...opts.claudeArgs].map(shQuote).join(' ');\n const term = process.env['TERM'] ?? 'xterm-256color';\n const envFlags: string[] = ['-e', `TERM=${term}`];\n for (const k of FORWARDED_ENV_KEYS) {\n const v = process.env[k];\n if (typeof v === 'string' && v.length > 0) envFlags.push('-e', `${k}=${v}`);\n }\n const result = await execa(\n 'docker',\n [\n 'exec',\n ...envFlags,\n '--user',\n CONTAINER_USER,\n opts.container,\n 'tmux',\n 'new-session',\n '-d',\n '-s',\n sessionName,\n cmd,\n ...buildTmuxSessionArgs(sessionName),\n ],\n { reject: false },\n );\n if (result.exitCode === 0) return;\n const stderr = (result.stderr ?? '').toString();\n if (result.exitCode === 127 || /command not found|tmux: not found/i.test(stderr)) {\n throw new ClaudeSessionError(\n `tmux is missing from the box image. Rebuild with: docker rmi agentbox/box:dev && retry.`,\n );\n }\n if (/claude.*not found|exec: \"claude\"/i.test(stderr)) {\n throw new ClaudeSessionError(\n `claude is missing from the box image. Rebuild with: docker rmi agentbox/box:dev && retry.`,\n );\n }\n if (/duplicate session/i.test(stderr)) {\n throw new ClaudeSessionError(\n `a tmux session \"${sessionName}\" already exists in ${opts.container}; use \\`agentbox claude attach\\` to reattach.`,\n );\n }\n throw new ClaudeSessionError(\n `failed to start claude session in ${opts.container}: ${stderr.trim() || `exit ${String(result.exitCode)}`}`,\n );\n}\n\n/**\n * Replace the current process with `docker exec -it tmux attach`. Ctrl+a d returns\n * the user to their host shell with exit 0. We forward TERM so tmux declares\n * the outer terminal's true-color and hyperlink capabilities; without it\n * docker exec sets TERM=xterm and Claude renders without RGB.\n */\n/**\n * The `docker` argv that attaches an interactive terminal to a box's Claude\n * tmux session. Shared by {@link attachClaudeSession} (which `spawnSync`s it\n * directly) and the dashboard command (which hands it to `tmux respawn-pane`).\n */\nexport function buildClaudeAttachArgv(container: string, sessionName?: string): string[] {\n const name = sessionName ?? DEFAULT_CLAUDE_SESSION;\n const term = process.env['TERM'] ?? 'xterm-256color';\n return [\n 'exec',\n '-it',\n '-e',\n `TERM=${term}`,\n '--user',\n CONTAINER_USER,\n container,\n 'tmux',\n 'attach',\n '-t',\n name,\n ];\n}\n\n/**\n * Like {@link buildClaudeAttachArgv}, but for the dashboard's right pane.\n * Agent-agnostic — `sessionName` selects which agent's tmux session to attach\n * (`claude` / `codex` / `opencode`). The dashboard already draws its own bottom\n * status bar, so a second client must not show the inner tmux status bar. We\n * attach via a *grouped* sibling session (`<name>-dash`, `tmux new-session -t\n * <name>`): grouped sessions share the same windows/panes (identical live\n * screen + scrollback) but keep independent session options, so `status off`\n * here does not affect a direct `agentbox <agent> attach` to `<name>`. The bare\n * `;` elements are tmux's command separator — node-pty spawns docker without a\n * shell, so they reach tmux verbatim. `new-session -A -d` is a no-op if the\n * grouped session already exists; `attach` runs after `status off` so the\n * footer is gone on first paint.\n */\nexport function buildDashboardAttachArgv(\n container: string,\n sessionName?: string,\n): string[] {\n const name = sessionName ?? DEFAULT_CLAUDE_SESSION;\n const dash = `${name}-dash`;\n const term = process.env['TERM'] ?? 'xterm-256color';\n return [\n 'exec',\n '-it',\n '-e',\n `TERM=${term}`,\n '--user',\n CONTAINER_USER,\n container,\n 'tmux',\n 'new-session',\n '-A',\n '-d',\n '-s',\n dash,\n '-t',\n name,\n ';',\n 'set',\n '-t',\n dash,\n 'status',\n 'off',\n ';',\n 'attach',\n '-t',\n dash,\n ];\n}\n\n/**\n * Poll a box's tmux pane until it has rendered non-blank content, or until\n * `timeoutMs` elapses. The dashboard's right-pane terminal emulator can latch\n * blank if it attaches while a heavy agent TUI (notably OpenCode's Bun-based\n * UI) is still initializing — a fresh attach gets tmux's full screen replay,\n * but a mid-init attach can miss it. Waiting for first content means the\n * dashboard attaches in the working (post-draw) condition. Best-effort:\n * returns on timeout regardless so a never-drawing agent never hangs the UI.\n */\nexport async function waitForTmuxPaneContent(\n container: string,\n sessionName: string,\n timeoutMs = 20_000,\n): Promise<void> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n const res = await execa(\n 'docker',\n ['exec', '--user', CONTAINER_USER, container, 'tmux', 'capture-pane', '-p', '-t', sessionName],\n { reject: false },\n );\n if (res.exitCode === 0 && (res.stdout ?? '').trim().length > 0) return;\n await delay(400);\n }\n}\n\n/**\n * tmux command-list (separator-prefixed) that remaps the prefix and turns\n * the inner tmux status bar off. The outer host UI (the wrapped-pty footer\n * for `agentbox claude` / `agentbox shell`, the dashboard's own status row\n * for the right pane) already shows the box name + the detach hint, so the\n * inner bar is double-footer — strip it. Shared by {@link startClaudeSession}\n * and `startShellSession` (both tmux-backed sessions get the same chords).\n *\n * `Ctrl+a` is the **primary** prefix (matches the dashboard's quit chord), and\n * tmux's default `Ctrl+b` is kept as a **secondary** prefix (`prefix2 C-b`) so\n * existing tmux muscle memory + integrations that send `Ctrl+b <key>` keep\n * working. Either prefix triggers the same key table — so `Ctrl+a d` *and*\n * `Ctrl+b d` both detach. `Ctrl+a Ctrl+a` sends a literal `Ctrl+a` through to\n * Claude (`send-prefix`); `Ctrl+b Ctrl+b` does the same for `Ctrl+b` via\n * `send-prefix -2`. `prefix`/`bind-key` are tmux server-global (no `-t`);\n * that's fine because each box's tmux server hosts only the claude session.\n * Applied here (not in the image) so existing boxes pick it up on the next\n * fresh session with no rebuild.\n *\n * Appended after `tmux new-session …` in {@link startClaudeSession}; the bare\n * `;` elements are tmux's command separator (execa array args, no host shell,\n * so they reach tmux verbatim). `status off` is a session option scoped with\n * `-t <session>` — the dashboard's grouped `<name>-dash` session has its own\n * option scope and runs its own `status off` in {@link buildDashboardAttachArgv}.\n */\nexport function buildTmuxSessionArgs(sessionName: string): string[] {\n const s = sessionName;\n return [\n // Server-global (no -t): primary prefix Ctrl+a (dashboard parity), keep\n // tmux's default Ctrl+b as a secondary prefix so users with existing\n // muscle memory / integrations aren't broken. `d` is the same key under\n // both prefixes (single key table) -> Ctrl+a d AND Ctrl+b d both detach\n // (and `d` is already tmux's built-in detach key — bound explicitly so\n // the contract is visible). `send-prefix` / `send-prefix -2` let a\n // double-tap of either prefix reach Claude as that literal key.\n ';', 'set', '-g', 'prefix', 'C-a',\n ';', 'set', '-g', 'prefix2', 'C-b',\n ';', 'bind-key', 'C-a', 'send-prefix',\n ';', 'bind-key', 'C-b', 'send-prefix', '-2',\n ';', 'bind-key', 'd', 'detach-client',\n // Modified-key reporting: without `extended-keys on`, tmux strips the\n // modifier from Shift+Enter / Ctrl+Enter / etc. so Claude Code can't\n // distinguish them from a plain Enter — pressing Shift+Enter submits the\n // prompt instead of inserting a newline. `csi-u` is the format Claude\n // Code recognises after `/terminal-setup`. Server-global so it survives\n // grouped sibling sessions (e.g. the dashboard's `<name>-dash`).\n ';', 'set', '-g', 'extended-keys', 'on',\n ';', 'set', '-as', 'terminal-features', ',*:extkeys',\n // Hide the inner tmux status bar — the wrapped-pty footer (for\n // `agentbox claude` / `agentbox shell`) and the dashboard's own status\n // row already show the box name + detach hint; without `status off`\n // they double up.\n ';', 'set', '-t', s, 'status', 'off',\n ];\n}\n\n/**\n * The `docker` argv for an interactive login shell in a box — the same shape\n * `agentbox shell` uses (vscode user, image WORKDIR `/workspace`, `bash -l`).\n * Handed to node-pty by the dashboard's \"open a shell\" action.\n */\nexport function buildShellArgv(container: string): string[] {\n const term = process.env['TERM'] ?? 'xterm-256color';\n return ['exec', '-it', '-e', `TERM=${term}`, '--user', CONTAINER_USER, container, 'bash', '-l'];\n}\n\n/**\n * The `docker run` argv for an interactive `claude auth login` in a throwaway\n * container. Mounts the claude-config volume at `~/.claude` so the written\n * credentials persist; runs before any box exists. `extraArgs` are appended\n * verbatim (e.g. `['--claudeai']`, `['--sso']`).\n *\n * `DISPLAY` is blanked: the box image bakes `DISPLAY=:1` (a VNC X server) and\n * `claude auth login` would otherwise try to open a browser on that invisible\n * display. An empty `DISPLAY` forces claude's terminal URL/paste-code flow.\n */\nexport function buildClaudeLoginRunArgv(opts: {\n volume: string;\n image: string;\n extraArgs: string[];\n}): string[] {\n const term = process.env['TERM'] ?? 'xterm-256color';\n return [\n 'run',\n '-it',\n '--rm',\n '-e',\n `TERM=${term}`,\n '-e',\n 'DISPLAY=',\n '-v',\n `${opts.volume}:${CONTAINER_CLAUDE_DIR}`,\n '--user',\n CONTAINER_USER,\n opts.image,\n 'claude',\n 'auth',\n 'login',\n ...opts.extraArgs,\n ];\n}\n\n/**\n * Run an interactive docker argv (from {@link buildClaudeLoginRunArgv}) with\n * the user's terminal attached. Returns the exit code; a null status (killed /\n * failed to spawn) is reported as 1.\n */\nexport function runInteractiveClaudeLogin(dockerArgv: string[]): { exitCode: number } {\n const child = spawnSync('docker', dockerArgv, { stdio: 'inherit' });\n return { exitCode: child.status ?? 1 };\n}\n\nexport interface WarmUpClaudeResult {\n /** True once a headless `claude -p` request actually succeeded. */\n warmed: boolean;\n /** How many attempts were made (1 = warm on the first try). */\n attempts: number;\n}\n\n/**\n * After a fresh `claude auth login`, the *first* Claude Code inference request\n * on the newly minted Claude.ai subscription token is rejected by the API with\n * `400 role 'system' is not supported on this model` — the account/token needs\n * one inference round-trip to be provisioned. A later process then works\n * (confirmed empirically: the first in-box session 400s, every later\n * box/session on the same credentials succeeds).\n *\n * Absorb that sacrificial request here: run a headless `claude -p` in a\n * throwaway container against the shared volume the login just wrote to,\n * retrying until one request actually succeeds — so the user's real box\n * session is never the first request. `--dangerously-skip-permissions` keeps\n * the headless run from stalling on a trust/permission prompt (this is a\n * throwaway sandbox container; nothing it does is persisted beyond the volume).\n *\n * Best-effort and time-boxed: if it never succeeds we return `warmed: false`\n * and the caller proceeds anyway — the box then behaves exactly as it did\n * before this warm-up existed.\n */\nexport async function warmUpClaudeCredentials(\n volume: string,\n image: string,\n opts: { onProgress?: (line: string) => void } = {},\n): Promise<WarmUpClaudeResult> {\n const MAX_ATTEMPTS = 6;\n const SLEEP_MS = 5000;\n for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {\n opts.onProgress?.(`checking credentials... ${attempt}/${MAX_ATTEMPTS}`);\n const res = await execa(\n 'docker',\n [\n 'run',\n '--rm',\n '-v',\n `${volume}:${CONTAINER_CLAUDE_DIR}`,\n '--user',\n CONTAINER_USER,\n '-e',\n 'DISABLE_AUTOUPDATER=1',\n image,\n 'claude',\n '--dangerously-skip-permissions',\n '-p',\n 'ok',\n ],\n { reject: false, timeout: 60_000 },\n );\n // `claude -p` can exit 0 while printing an API error as the turn's text,\n // so success needs a clean exit AND no error signature in the output.\n const out = `${res.stdout ?? ''}\\n${res.stderr ?? ''}`;\n const apiError = /API Error|is not supported on this model|\"type\":\\s*\"error\"/i.test(out);\n if (res.exitCode === 0 && !apiError) return { warmed: true, attempts: attempt };\n if (attempt < MAX_ATTEMPTS) await delay(SLEEP_MS);\n }\n return { warmed: false, attempts: MAX_ATTEMPTS };\n}\n\nexport function formatDetachNotice(\n ref: string,\n command: 'claude' | 'shell' | 'codex' | 'opencode' = 'claude',\n suffix = '',\n): string {\n return `Session detached. Reattach with: agentbox ${command} attach ${ref}${suffix}`;\n}\n\nexport function attachClaudeSession(\n container: string,\n sessionName?: string,\n reattachRef?: string,\n): never {\n const child = spawnSync('docker', buildClaudeAttachArgv(container, sessionName), {\n stdio: 'inherit',\n });\n const code = child.status ?? 0;\n if (reattachRef && code === 0) {\n // Overwrite tmux's own `[detached (from session …)]` line (printed just\n // above the cursor on a clean detach). Best-effort cosmetics: if the\n // terminal ignores the cursor moves, our line still prints below it.\n process.stdout.write('\\x1b[1A\\x1b[2K\\r' + formatDetachNotice(reattachRef) + '\\n');\n }\n process.exit(code);\n}\n\nexport interface ClaudeSessionInfo {\n running: boolean;\n sessionName: string;\n /** ISO-8601 timestamp from tmux's `#{session_created}` format string, or null when not running. */\n startedAt: string | null;\n}\n\n/**\n * Best-effort: returns `{ running: false, …, startedAt: null }` for any non-zero exit\n * from `tmux has-session` (which includes \"no server running\" and \"no such session\").\n */\nexport async function claudeSessionInfo(\n container: string,\n sessionName?: string,\n): Promise<ClaudeSessionInfo> {\n const name = sessionName ?? DEFAULT_CLAUDE_SESSION;\n const has = await execa(\n 'docker',\n ['exec', '--user', CONTAINER_USER, container, 'tmux', 'has-session', '-t', name],\n { reject: false },\n );\n if (has.exitCode !== 0) {\n return { running: false, sessionName: name, startedAt: null };\n }\n const ts = await execa(\n 'docker',\n [\n 'exec',\n '--user',\n CONTAINER_USER,\n container,\n 'tmux',\n 'display-message',\n '-p',\n '-t',\n name,\n '#{session_created}',\n ],\n { reject: false },\n );\n let startedAt: string | null = null;\n if (ts.exitCode === 0) {\n const secs = Number.parseInt((ts.stdout ?? '').trim(), 10);\n if (Number.isFinite(secs) && secs > 0) startedAt = new Date(secs * 1000).toISOString();\n }\n return { running: true, sessionName: name, startedAt };\n}\n\nexport interface PullClaudeResult {\n /**\n * Box-installed extensions not present on the host. `category` is one of\n * skills/agents/commands (then `name` is the dir name) or `plugins` (then\n * `name` is the `<marketplace>/<plugin>` cache key).\n */\n newItems: Array<{ category: string; name: string }>;\n /** Registry JSONs that gained box-only entries (e.g. `known_marketplaces.json`). */\n mergedRegistries: string[];\n}\n\nexport interface PullClaudeOptions {\n /** Image for the throwaway helper container; use the box's image to avoid extra pulls. */\n image: string;\n /** When true, compute the delta but write nothing. */\n dryRun?: boolean;\n}\n\nconst PULL_DIR_CATEGORIES = ['skills', 'agents', 'commands'] as const;\n\n/**\n * Immediate child item names of `dir`, or [] if it doesn't exist. Symlinks\n * count: the host's `~/.claude/skills/<name>` is a symlink into `~/.agents`\n * (Claude Code's user-skills convention), so `isDirectory()` alone would miss\n * them and every host skill would look \"new\".\n */\nasync function listChildDirs(dir: string): Promise<string[]> {\n try {\n const entries = await readdir(dir, { withFileTypes: true });\n return entries.filter((e) => e.isDirectory() || e.isSymbolicLink()).map((e) => e.name);\n } catch {\n return [];\n }\n}\n\nasync function readJsonFile(path: string): Promise<unknown> {\n try {\n return JSON.parse(await readFile(path, 'utf8'));\n } catch {\n return undefined;\n }\n}\n\n/**\n * Reverse of {@link ensureClaudeVolume}: pull box-installed Claude extensions\n * (skills/agents/commands dirs + plugins) from the claude-config volume back to\n * the host's `~/.claude`. Additive only — an item already present on the host\n * is never overwritten. The box need not be running; we read the *volume* via a\n * throwaway helper container (the exact mirror of the forward sync), so this\n * also works while the box is stopped.\n *\n * Plugin registry JSONs (`installed_plugins.json`, `known_marketplaces.json`)\n * are merged host-side: only box-only keys are added, with the forward sync's\n * `/home/vscode/.claude/plugins/` rewrite reversed back to the host path.\n */\nexport async function pullClaudeExtras(\n spec: ClaudeConfigSpec,\n opts: PullClaudeOptions,\n): Promise<PullClaudeResult> {\n const hostHome = homedir();\n const hostClaude = join(hostHome, '.claude');\n\n // Inventory pass: enumerate the volume's contents via a read-only helper\n // container. `--user 0` so root can read files claude wrote as uid 1000.\n // base64 -w0 keeps each registry JSON on one parseable line.\n const inventoryScript = [\n 'for cat in skills agents commands; do',\n ' [ -d \"/src/$cat\" ] || continue;',\n ' for d in \"/src/$cat\"/*/; do',\n ' [ -d \"$d\" ] || continue;',\n ' printf \"DIR %s %s\\\\n\" \"$cat\" \"$(basename \"$d\")\";',\n ' done;',\n 'done;',\n 'if [ -d /src/plugins/cache ]; then',\n ' for m in /src/plugins/cache/*/; do',\n ' [ -d \"$m\" ] || continue;',\n ' for p in \"$m\"*/; do',\n ' [ -d \"$p\" ] || continue;',\n ' printf \"PLUGIN %s/%s\\\\n\" \"$(basename \"$m\")\" \"$(basename \"$p\")\";',\n ' done;',\n ' done;',\n 'fi;',\n 'for f in installed_plugins known_marketplaces; do',\n ' [ -f \"/src/plugins/$f.json\" ] || continue;',\n ' printf \"JSON %s \" \"$f\";',\n ' base64 -w0 \"/src/plugins/$f.json\";',\n ' printf \"\\\\n\";',\n 'done',\n ].join(' ');\n\n const inv = await execa(\n 'docker',\n ['run', '--rm', '--user', '0', '-v', `${spec.volume}:/src:ro`, opts.image, 'sh', '-c', inventoryScript],\n { reject: false },\n );\n if (inv.exitCode !== 0) {\n throw new ClaudeSessionError(\n `failed to read claude-config volume ${spec.volume}: ${(inv.stderr ?? '').toString().trim() || `exit ${String(inv.exitCode)}`}`,\n );\n }\n\n const boxDirs: Record<string, string[]> = { skills: [], agents: [], commands: [] };\n const boxPlugins: string[] = [];\n const boxJson: Record<string, unknown> = {};\n for (const line of (inv.stdout ?? '').split('\\n')) {\n if (line.startsWith('DIR ')) {\n const rest = line.slice(4);\n const sp = rest.indexOf(' ');\n if (sp === -1) continue;\n const cat = rest.slice(0, sp);\n const name = rest.slice(sp + 1);\n if (cat in boxDirs) boxDirs[cat]!.push(name);\n } else if (line.startsWith('PLUGIN ')) {\n boxPlugins.push(line.slice(7));\n } else if (line.startsWith('JSON ')) {\n const rest = line.slice(5);\n const sp = rest.indexOf(' ');\n if (sp === -1) continue;\n const which = rest.slice(0, sp);\n try {\n boxJson[which] = JSON.parse(Buffer.from(rest.slice(sp + 1), 'base64').toString('utf8'));\n } catch {\n // Leave undefined; the merge helpers tolerate it.\n }\n }\n }\n\n // Compute deltas host-side (the host ~/.claude is directly accessible —\n // only the volume needed a container).\n const newItems: PullClaudeResult['newItems'] = [];\n const applyPaths: Array<{ src: string; dest: string }> = [];\n for (const cat of PULL_DIR_CATEGORIES) {\n const hostNames = await listChildDirs(join(hostClaude, cat));\n const excludes = cat === 'skills' ? SKILL_EXCLUDE_PREFIXES : [];\n for (const name of pickNewItems(boxDirs[cat] ?? [], hostNames, excludes)) {\n newItems.push({ category: cat, name });\n applyPaths.push({ src: `/src/${cat}/${name}`, dest: `/dst/${cat}/${name}` });\n }\n }\n const hostPluginKeys: string[] = [];\n for (const m of await listChildDirs(join(hostClaude, 'plugins', 'cache'))) {\n for (const p of await listChildDirs(join(hostClaude, 'plugins', 'cache', m))) {\n hostPluginKeys.push(`${m}/${p}`);\n }\n }\n for (const key of pickNewItems(boxPlugins, hostPluginKeys)) {\n newItems.push({ category: 'plugins', name: key });\n applyPaths.push({ src: `/src/plugins/cache/${key}`, dest: `/dst/plugins/cache/${key}` });\n }\n\n // Additive merge of the two plugin registries (reverses the forward path\n // rewrite). Computed regardless so the preview can report it.\n const hostInstalled = await readJsonFile(join(hostClaude, 'plugins', 'installed_plugins.json'));\n const hostMarkets = await readJsonFile(join(hostClaude, 'plugins', 'known_marketplaces.json'));\n const mergedInstalled = mergeInstalledPlugins(hostInstalled, boxJson['installed_plugins'], {\n hostHome,\n });\n const mergedMarkets = mergeKnownMarketplaces(hostMarkets, boxJson['known_marketplaces'], {\n hostHome,\n });\n const mergedRegistries: string[] = [];\n if (mergedInstalled.changed) mergedRegistries.push('installed_plugins.json');\n if (mergedMarkets.changed) mergedRegistries.push('known_marketplaces.json');\n\n if (opts.dryRun || (newItems.length === 0 && mergedRegistries.length === 0)) {\n return { newItems, mergedRegistries };\n }\n\n // Apply pass: rsync each new item dir from the volume into the host\n // ~/.claude bind mount. --ignore-existing is belt-and-suspenders (the\n // host-side delta is the real guard); --exclude=node_modules because the\n // box carries linux/amd64 binaries useless on the darwin host (claude/host\n // rebuilds lazily, same rationale as the forward sync's exclude).\n if (applyPaths.length > 0) {\n const cmds = applyPaths.map(({ src, dest }) => {\n const parent = dest.slice(0, dest.lastIndexOf('/'));\n return `mkdir -p '${parent}' && rsync -a --ignore-existing --exclude=node_modules '${src}/' '${dest}/'`;\n });\n const apply = await execa(\n 'docker',\n [\n 'run',\n '--rm',\n '--user',\n '0',\n '-v',\n `${spec.volume}:/src:ro`,\n '-v',\n `${hostClaude}:/dst`,\n opts.image,\n 'sh',\n '-c',\n cmds.join(' && '),\n ],\n { reject: false },\n );\n if (apply.exitCode !== 0) {\n throw new ClaudeSessionError(\n `failed to copy extensions from ${spec.volume}: ${(apply.stderr ?? '').toString().trim() || `exit ${String(apply.exitCode)}`}`,\n );\n }\n }\n\n // Registry JSONs are written host-side (host path is directly writable;\n // no container needed) — only when the merge actually added keys.\n if (mergedMarkets.changed || mergedInstalled.changed) {\n await mkdir(join(hostClaude, 'plugins'), { recursive: true });\n if (mergedMarkets.changed) {\n await writeFile(\n join(hostClaude, 'plugins', 'known_marketplaces.json'),\n `${JSON.stringify(mergedMarkets.data, null, 2)}\\n`,\n );\n }\n if (mergedInstalled.changed) {\n await writeFile(\n join(hostClaude, 'plugins', 'installed_plugins.json'),\n `${JSON.stringify(mergedInstalled.data, null, 2)}\\n`,\n );\n }\n }\n\n return { newItems, mergedRegistries };\n}\n","export interface HookFilterResult<T = unknown> {\n data: T;\n removedCommands: string[];\n}\n\n/**\n * Predicate for the filter: does `command` look like it points at a host path\n * outside the project, i.e. under the user's host home directory?\n *\n * Uses `.includes(hostHome + '/')` so we also catch shell-quoted forms like\n * `bash -c '/Users/marco/.config/iterm2/cc-status'`. The trailing slash gates\n * against false matches on similar-prefix home dirs (e.g. `/Users/marco`\n * shouldn't match `/Users/marco-other/...`).\n *\n * Returns false when `hostHome` is empty so we degrade safely if no home is\n * resolvable for some reason.\n */\nexport function isHostPathHookCommand(command: string, hostHome: string): boolean {\n if (typeof command !== 'string' || command.length === 0) return false;\n if (hostHome.length === 0) return false;\n return command.includes(hostHome + '/');\n}\n\ninterface HookLeaf {\n type?: string;\n command?: string;\n [k: string]: unknown;\n}\n\ninterface HookMatcherEntry {\n hooks?: unknown;\n [k: string]: unknown;\n}\n\n/**\n * Walk Claude Code's documented `hooks.<Trigger>[].hooks[]` structure and drop\n * any leaf `{ type: 'command', command: '<host-path>' }` whose `command` matches\n * {@link isHostPathHookCommand}. Empty `hooks: []` arrays and their matcher\n * wrappers are left intact — they don't break Claude and avoiding recursive\n * cleanup keeps the filter predictable.\n *\n * Returns a deep clone; input is not mutated. Tolerant of unexpected shapes\n * (string, number, null at any level): unrecognized branches pass through\n * unchanged, removed count stays accurate.\n */\nexport function filterHostHooks<T = unknown>(data: T, hostHome: string): HookFilterResult<T> {\n // structuredClone is in node >= 17; the repo targets node20, so this is safe.\n const clone = structuredClone(data) as unknown;\n const removedCommands: string[] = [];\n\n if (clone === null || typeof clone !== 'object' || Array.isArray(clone)) {\n return { data: clone as T, removedCommands };\n }\n\n const top = clone as { hooks?: unknown };\n const hooksRoot = top.hooks;\n if (hooksRoot === null || typeof hooksRoot !== 'object' || Array.isArray(hooksRoot)) {\n return { data: clone as T, removedCommands };\n }\n\n for (const triggerName of Object.keys(hooksRoot as Record<string, unknown>)) {\n const triggerValue = (hooksRoot as Record<string, unknown>)[triggerName];\n if (!Array.isArray(triggerValue)) continue;\n for (const entry of triggerValue) {\n if (entry === null || typeof entry !== 'object') continue;\n const matcher = entry as HookMatcherEntry;\n const inner = matcher.hooks;\n if (!Array.isArray(inner)) continue;\n // In-place filter on the cloned array.\n for (let i = inner.length - 1; i >= 0; i--) {\n const leaf = inner[i] as HookLeaf | null;\n if (leaf === null || typeof leaf !== 'object') continue;\n if (\n leaf.type === 'command' &&\n typeof leaf.command === 'string' &&\n isHostPathHookCommand(leaf.command, hostHome)\n ) {\n removedCommands.push(leaf.command);\n inner.splice(i, 1);\n }\n }\n }\n }\n\n return { data: clone as T, removedCommands };\n}\n\nexport interface SetInstallMethodNativeResult<T = unknown> {\n data: T;\n applied: boolean;\n}\n\nexport interface AddProjectAliasResult<T = unknown> {\n data: T;\n aliased: boolean;\n}\n\nexport interface TrustWorkspaceResult<T = unknown> {\n data: T;\n trusted: boolean;\n}\n\n/**\n * Force `projects[workspacePath].hasTrustDialogAccepted = true` in a parsed\n * `~/.claude.json`, creating the `projects` map and the project entry if\n * absent.\n *\n * The box is a sandbox: the agent is created explicitly to work in\n * `/workspace`, and the box is isolated from the host — so the folder-trust\n * dialog is pointless there. More importantly, Claude Code, when it opens an\n * *untrusted* folder, sends a malformed first API request that Anthropic\n * rejects with `400 role 'system' is not supported on this model`. Pre-trusting\n * the box's workspace skips the dialog *and* dodges that bug.\n *\n * Returns a deep-cloned, modified copy plus a flag for whether anything\n * changed (`false` when it was already trusted). Input is not mutated; no-op\n * for non-object data or an empty path.\n */\nexport function trustWorkspace<T = unknown>(\n data: T,\n workspacePath: string,\n): TrustWorkspaceResult<T> {\n const clone = structuredClone(data) as unknown;\n if (clone === null || typeof clone !== 'object' || Array.isArray(clone)) {\n return { data: clone as T, trusted: false };\n }\n if (workspacePath.length === 0) return { data: clone as T, trusted: false };\n const obj = clone as { projects?: unknown };\n if (obj.projects === null || typeof obj.projects !== 'object' || Array.isArray(obj.projects)) {\n obj.projects = {};\n }\n const projects = obj.projects as Record<string, unknown>;\n const existing = projects[workspacePath];\n const entry =\n existing !== null && typeof existing === 'object' && !Array.isArray(existing)\n ? (existing as Record<string, unknown>)\n : {};\n if (entry.hasTrustDialogAccepted === true) {\n projects[workspacePath] = entry;\n return { data: clone as T, trusted: false };\n }\n entry.hasTrustDialogAccepted = true;\n projects[workspacePath] = entry;\n return { data: clone as T, trusted: true };\n}\n\n/**\n * Claude Code keys project-scoped state (history, mcpServers, enabledPlugins,\n * trust prompts) under `projects[<absolute-workspace-path>]` in\n * `~/.claude.json`. On the host the key is something like\n * `/Users/marco/Projects/foo`; inside the box the workspace is always\n * `/workspace`. Without rewriting, the box never sees the host's project-\n * scoped settings.\n *\n * Copy (don't move) the host-keyed entry to `toPath` if present. Existing\n * `projects[toPath]` is preserved by merging the host entry on top — host\n * is authoritative for keys it sets; box-only keys (e.g. session ids\n * accumulated inside earlier boxes) stay intact.\n *\n * No-op (returns `aliased: false`) when:\n * - data isn't an object, or `projects` isn't an object\n * - fromPath equals toPath\n * - projects[fromPath] doesn't exist or isn't an object\n *\n * Returns a deep-cloned, modified copy; input is not mutated.\n */\nexport function addProjectAlias<T = unknown>(\n data: T,\n fromPath: string,\n toPath: string,\n): AddProjectAliasResult<T> {\n const clone = structuredClone(data) as unknown;\n if (clone === null || typeof clone !== 'object' || Array.isArray(clone)) {\n return { data: clone as T, aliased: false };\n }\n if (fromPath === toPath || fromPath.length === 0 || toPath.length === 0) {\n return { data: clone as T, aliased: false };\n }\n const obj = clone as { projects?: unknown };\n const projects = obj.projects;\n if (projects === null || typeof projects !== 'object' || Array.isArray(projects)) {\n return { data: clone as T, aliased: false };\n }\n const projectsMap = projects as Record<string, unknown>;\n const src = projectsMap[fromPath];\n if (src === null || typeof src !== 'object' || Array.isArray(src)) {\n return { data: clone as T, aliased: false };\n }\n const existing = projectsMap[toPath];\n if (existing !== null && typeof existing === 'object' && !Array.isArray(existing)) {\n projectsMap[toPath] = { ...(existing as Record<string, unknown>), ...(src as Record<string, unknown>) };\n } else {\n projectsMap[toPath] = structuredClone(src);\n }\n return { data: clone as T, aliased: true };\n}\n\n/**\n * Force the install-method fields in a parsed `~/.claude.json` to match the\n * box's native install. Sets exactly what `claude install` writes:\n * installMethod: \"native\"\n * autoUpdates: false\n * autoUpdatesProtectedForNative: true\n *\n * Without this, the in-box claude reports\n * `Running native installation but config install method is 'not set'` in\n * /status — the host's value (often `npm-global` on Mac, or absent) doesn't\n * match the box's `~/.local/bin/claude` install location, and merely\n * clearing the field leaves it unset rather than fixing it.\n *\n * Returns a deep-cloned, fixed copy plus a flag indicating whether any of\n * the three fields actually changed. Input is not mutated.\n */\nexport function setInstallMethodNative<T = unknown>(data: T): SetInstallMethodNativeResult<T> {\n const clone = structuredClone(data) as unknown;\n if (clone === null || typeof clone !== 'object' || Array.isArray(clone)) {\n return { data: clone as T, applied: false };\n }\n const obj = clone as Record<string, unknown>;\n const changed =\n obj.installMethod !== 'native' ||\n obj.autoUpdates !== false ||\n obj.autoUpdatesProtectedForNative !== true;\n obj.installMethod = 'native';\n obj.autoUpdates = false;\n obj.autoUpdatesProtectedForNative = true;\n return { data: clone as T, applied: changed };\n}\n","/**\n * Pure, docker-free helpers for `agentbox download claude` (box -> host pull of\n * Claude extensions). Kept separate from `claude.ts` so the delta + JSON-merge\n * logic is unit-testable without spawning containers — mirrors how\n * `claude-hooks-filter.ts` factors the forward-sync transforms.\n *\n * The forward sync (`ensureClaudeVolume` in claude.ts) is host-authoritative\n * and rewrites `$HOST_HOME/.claude/plugins/` -> `/home/vscode/.claude/plugins/`\n * in the plugin registry JSONs. This module is the reverse: additive (host\n * wins, only missing items are added) and rewrites the container path back to\n * the host path.\n */\n\n/** Categories under ~/.claude we pull box-side additions for. */\nexport const PULL_CATEGORIES = ['skills', 'plugins', 'agents', 'commands'] as const;\nexport type PullCategory = (typeof PULL_CATEGORIES)[number];\n\n/**\n * Skills whose directory name starts with one of these prefixes are agentbox's\n * own (currently just `agentbox-setup`, seeded box-only into the claude-config\n * volume by `seedSetupSkillIntoVolume` in claude.ts — never on the host).\n * Pulling them back would re-introduce them onto the host, which is exactly\n * what the box-only design avoids, so we never treat them as user-authored\n * additions.\n */\nexport const SKILL_EXCLUDE_PREFIXES = ['agentbox-'] as const;\n\n/** Container path prefix the forward sync rewrites host plugin paths to. */\nexport const CONTAINER_PLUGINS_PREFIX = '/home/vscode/.claude/plugins/';\n\n/**\n * Set-difference of `boxNames` against `hostNames`, dropping any name that\n * starts with one of `excludePrefixes`. Result is sorted for stable output.\n * Used for skills/agents/commands (top-level dir names) and plugin cache\n * (`<marketplace>/<plugin>` keys).\n */\nexport function pickNewItems(\n boxNames: string[],\n hostNames: string[],\n excludePrefixes: readonly string[] = [],\n): string[] {\n const host = new Set(hostNames);\n const seen = new Set<string>();\n const out: string[] = [];\n for (const name of boxNames) {\n if (name.length === 0 || host.has(name) || seen.has(name)) continue;\n if (excludePrefixes.some((p) => name.startsWith(p))) continue;\n seen.add(name);\n out.push(name);\n }\n return out.sort();\n}\n\n/**\n * Rewrite the forward sync's container plugin prefix back to the host's, in\n * every string anywhere in `value`. Generic over the JSON shape so it covers\n * both `installLocation` (known_marketplaces.json) and `installPath`\n * (installed_plugins.json) plus any future path-bearing field.\n */\nfunction rewritePluginPaths<T>(value: T, hostPluginsPrefix: string): T {\n if (typeof value === 'string') {\n return value.split(CONTAINER_PLUGINS_PREFIX).join(hostPluginsPrefix) as unknown as T;\n }\n if (Array.isArray(value)) {\n return value.map((v) => rewritePluginPaths(v, hostPluginsPrefix)) as unknown as T;\n }\n if (value !== null && typeof value === 'object') {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value)) {\n out[k] = rewritePluginPaths(v, hostPluginsPrefix);\n }\n return out as T;\n }\n return value;\n}\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return v !== null && typeof v === 'object' && !Array.isArray(v);\n}\n\nexport interface MergeResult {\n /** Merged JSON to write back to the host (the host object plus added keys). */\n data: unknown;\n /** True when at least one key was added (caller should write on true only). */\n changed: boolean;\n /** Keys added to the host map, for the preview/summary. */\n addedKeys: string[];\n}\n\n/**\n * Additive object-key merge. For every key in `boxMap` absent from `hostMap`,\n * copy the box's value in (with plugin paths rewritten to the host prefix).\n * Existing host keys are never touched. Tolerant of missing / non-object\n * inputs: returns the host value unchanged with `changed: false` (matches\n * `maybeFilterTo`'s defensiveness in claude.ts).\n *\n * `selectMap` projects the registry object to the map being merged\n * (identity for known_marketplaces.json, `.plugins` for installed_plugins.json)\n * and `withMap` writes the merged map back into a clone of the host registry.\n */\nfunction additiveMerge(\n hostRoot: unknown,\n boxRoot: unknown,\n hostPluginsPrefix: string,\n selectMap: (root: unknown) => unknown,\n withMap: (hostRoot: unknown, mergedMap: Record<string, unknown>) => unknown,\n): MergeResult {\n const hostMap = selectMap(hostRoot);\n const boxMap = selectMap(boxRoot);\n if (!isPlainObject(boxMap)) {\n return { data: hostRoot, changed: false, addedKeys: [] };\n }\n const base: Record<string, unknown> = isPlainObject(hostMap) ? { ...hostMap } : {};\n const addedKeys: string[] = [];\n for (const [key, value] of Object.entries(boxMap)) {\n if (Object.prototype.hasOwnProperty.call(base, key)) continue;\n base[key] = rewritePluginPaths(value, hostPluginsPrefix);\n addedKeys.push(key);\n }\n if (addedKeys.length === 0) {\n return { data: hostRoot, changed: false, addedKeys: [] };\n }\n return { data: withMap(hostRoot, base), changed: true, addedKeys: addedKeys.sort() };\n}\n\n/**\n * known_marketplaces.json is a flat object keyed by marketplace name\n * (`{ \"<name>\": { source, installLocation, lastUpdated } }`). Add box-only\n * marketplaces; rewrite their `installLocation` back to the host path.\n */\nexport function mergeKnownMarketplaces(\n hostJson: unknown,\n boxJson: unknown,\n opts: { hostHome: string },\n): MergeResult {\n const prefix = `${opts.hostHome}/.claude/plugins/`;\n return additiveMerge(\n isPlainObject(hostJson) ? hostJson : {},\n boxJson,\n prefix,\n (root) => root,\n (_host, merged) => merged,\n );\n}\n\n/**\n * installed_plugins.json is `{ version, plugins: { \"<name>@<mkt>\": [...] } }`.\n * Add box-only entries under `.plugins`; rewrite each entry's `installPath`\n * back to the host path. The top-level `version` and any other host keys are\n * preserved as-is.\n */\nexport function mergeInstalledPlugins(\n hostJson: unknown,\n boxJson: unknown,\n opts: { hostHome: string },\n): MergeResult {\n const prefix = `${opts.hostHome}/.claude/plugins/`;\n const hostRoot = isPlainObject(hostJson) ? hostJson : { plugins: {} };\n return additiveMerge(\n hostRoot,\n boxJson,\n prefix,\n (root) => (isPlainObject(root) ? (root as Record<string, unknown>)['plugins'] : undefined),\n (host, merged) => ({ ...(host as Record<string, unknown>), plugins: merged }),\n );\n}\n\n/**\n * Collect the set of `<marketplace>/<plugin>/<version>` cache keys that\n * `installed_plugins.json` actively references — every entry's `installPath`\n * reduced to its last three path segments. The plugin cache is a fixed\n * three-level tree (`cache/<m>/<p>/<v>/`), so the last three segments uniquely\n * identify the version dir regardless of whether the path is host-rooted\n * (`/Users/...`) or container-rooted (`/home/vscode/...`).\n *\n * Used to tell stale plugin-version dirs (an old version Claude left behind\n * after an update) apart from live ones, so a rebuild pass can prune the stale\n * ones' `node_modules` and never reinstall them. Returns an empty set for\n * missing / non-object input or entries without a usable `installPath` — the\n * caller treats \"empty\" as \"can't determine, do nothing\".\n */\nexport function referencedPluginVersionKeys(installedPluginsJson: unknown): Set<string> {\n const keys = new Set<string>();\n if (!isPlainObject(installedPluginsJson)) return keys;\n const plugins = installedPluginsJson['plugins'];\n if (!isPlainObject(plugins)) return keys;\n for (const entries of Object.values(plugins)) {\n if (!Array.isArray(entries)) continue;\n for (const entry of entries) {\n if (!isPlainObject(entry)) continue;\n const installPath = entry['installPath'];\n if (typeof installPath !== 'string') continue;\n const segments = installPath.split('/').filter((s) => s.length > 0);\n if (segments.length < 3) continue;\n keys.add(segments.slice(-3).join('/'));\n }\n }\n return keys;\n}\n","import { execa, type Result } from 'execa';\n\nexport interface DockerExecResult {\n exitCode: number;\n stdout: string;\n stderr: string;\n}\n\nexport type ContainerRuntimeState = 'running' | 'paused' | 'stopped' | 'missing';\n\nexport async function dockerInfo(): Promise<void> {\n const result: Result = await execa('docker', ['info'], { reject: false });\n if (result.exitCode !== 0) {\n throw new Error(\n `docker info failed (exit ${String(result.exitCode)}). Is the Docker daemon running?\\n${String(result.stderr)}`,\n );\n }\n}\n\n/**\n * Engine-agnostic resource ceilings. Memory in bytes, cpus fractional, disk a\n * raw engine-native size string. `null`/absent = unlimited.\n */\nexport interface BoxLimitSpec {\n memoryBytes?: number | null;\n cpus?: number | null;\n pidsLimit?: number | null;\n disk?: string | null;\n}\n\nexport interface RunBoxSpec {\n name: string;\n image: string;\n extraVolumes?: string[];\n env?: Record<string, string>;\n limits?: BoxLimitSpec;\n /**\n * docker `-p` mappings to forward host ports into the container. `hostPort: 0`\n * lets Docker pick a free ephemeral port; resolve it back with\n * {@link publishedHostPort}. `hostIp` defaults to all interfaces — pin it to\n * `127.0.0.1` for loopback-only exposure.\n */\n portMappings?: Array<{ hostPort: number; containerPort: number; hostIp?: string }>;\n}\n\nexport async function runBox(spec: RunBoxSpec): Promise<string> {\n const args: string[] = [\n 'run',\n '-d',\n '--name',\n spec.name,\n '--hostname',\n spec.name,\n '--cap-add=SYS_ADMIN',\n // dockerd inside the box (always-on, see launchDockerdDaemon) needs\n // NET_ADMIN to set up its bridge + iptables NAT for inner containers.\n // seccomp:unconfined is required because the default profile blocks\n // syscalls (notably keyctl, clone3 in some kernels) that nested containers\n // need. Both are scoped to the outer box's namespaces — inner containers\n // can't escape it. We still avoid --privileged for cloud portability.\n '--cap-add=NET_ADMIN',\n // /dev/fuse + SYS_ADMIN + apparmor:unconfined used to be required for the\n // outer /workspace FUSE overlay. That overlay is gone, but they're still\n // load-bearing for the *inner* dockerd's storage driver, which\n // agentbox-dockerd-start selects at runtime: SYS_ADMIN lets it `mount -t\n // overlay` for the preferred kernel-native overlay2 driver, and /dev/fuse\n // + SYS_ADMIN + apparmor:unconfined are needed for the fuse-overlayfs\n // fallback (used where overlay2's runtime probe fails).\n '--device=/dev/fuse',\n '--security-opt=apparmor:unconfined',\n '--security-opt=seccomp=unconfined',\n // cgroup v2 + DinD: with --cgroupns=host (the OrbStack default) the\n // outer container sees the host's read-only cgroup hierarchy at\n // /sys/fs/cgroup, so the inner dockerd can't `mkdir /sys/fs/cgroup/docker`\n // for its own slice and inner `docker run` fails with \"read-only file\n // system\". Private gives the box its own writable cgroup namespace; runc\n // creates the docker slice there and inner containers nest under it.\n '--cgroupns=private',\n // Make the host reachable from inside the container at the well-known DNS\n // name host.docker.internal. Docker Desktop / OrbStack ship this alias by\n // default; on Linux native Docker it requires this explicit flag (no-op\n // on the macOS engines). Boxes use it to reach the host relay process.\n '--add-host=host.docker.internal:host-gateway',\n ];\n const lim = spec.limits;\n if (lim) {\n if (lim.memoryBytes && lim.memoryBytes > 0) {\n args.push('--memory', String(Math.floor(lim.memoryBytes)));\n }\n if (lim.cpus && lim.cpus > 0) {\n args.push('--cpus', String(lim.cpus));\n }\n if (lim.pidsLimit && lim.pidsLimit > 0) {\n args.push('--pids-limit', String(Math.floor(lim.pidsLimit)));\n }\n // Best-effort: a no-op on overlay2 / the macOS engines. createBox() drops\n // this on those drivers (and warns) so `docker run` doesn't hard-error.\n if (lim.disk) {\n args.push('--storage-opt', `size=${lim.disk}`);\n }\n }\n for (const v of spec.extraVolumes ?? []) {\n args.push('-v', v);\n }\n for (const pm of spec.portMappings ?? []) {\n const host = pm.hostIp ? `${pm.hostIp}:${String(pm.hostPort)}` : String(pm.hostPort);\n args.push('-p', `${host}:${String(pm.containerPort)}`);\n }\n for (const [k, val] of Object.entries(spec.env ?? {})) {\n args.push('-e', `${k}=${val}`);\n }\n args.push(spec.image, 'sleep', 'infinity');\n\n const { stdout } = await execa('docker', args);\n return stdout.trim();\n}\n\n/**\n * The engine's storage driver (`overlay2`, `fuse-overlayfs`, `btrfs`, …).\n * `--storage-opt size=` is only enforced by devicemapper/btrfs/zfs/windowsfilter\n * — a no-op everywhere the macOS engines run. Empty string on probe failure.\n */\nexport async function dockerStorageDriver(): Promise<string> {\n const result = await execa('docker', ['info', '--format', '{{.Driver}}'], { reject: false });\n if (result.exitCode !== 0) return '';\n return (result.stdout ?? '').trim();\n}\n\nexport async function execInBox(\n container: string,\n cmd: string[],\n opts: { user?: string; detach?: boolean; timeoutMs?: number } = {},\n): Promise<DockerExecResult> {\n const args: string[] = ['exec'];\n if (opts.detach) args.push('-d');\n if (opts.user) args.push('--user', opts.user);\n args.push(container, ...cmd);\n const result = await execa('docker', args, {\n reject: false,\n ...(opts.timeoutMs ? { timeout: opts.timeoutMs } : {}),\n });\n return {\n exitCode: result.exitCode ?? -1,\n stdout: result.stdout ?? '',\n stderr: result.stderr ?? '',\n };\n}\n\nexport async function removeContainer(container: string): Promise<void> {\n await execa('docker', ['rm', '-f', container], { reject: false });\n}\n\nexport async function removeVolume(name: string): Promise<void> {\n await execa('docker', ['volume', 'rm', name], { reject: false });\n}\n\n/**\n * Best-effort `docker image rm`. Returns true when the image was actually\n * removed, false when it was already absent (or removal failed). `-f` is\n * passed by default so a stale tagged image with no live containers goes away\n * even if other tags point at the same layers.\n */\nexport async function removeImage(\n ref: string,\n opts: { force?: boolean } = {},\n): Promise<boolean> {\n const args = ['image', 'rm'];\n if (opts.force !== false) args.push('-f');\n args.push(ref);\n const result = await execa('docker', args, { reject: false });\n return result.exitCode === 0;\n}\n\nexport async function containerExists(name: string): Promise<boolean> {\n const result = await execa('docker', ['container', 'inspect', '--format', '{{.Id}}', name], {\n reject: false,\n });\n return result.exitCode === 0;\n}\n\nexport async function volumeExists(name: string): Promise<boolean> {\n const result = await execa('docker', ['volume', 'inspect', name], { reject: false });\n return result.exitCode === 0;\n}\n\nexport async function ensureVolume(name: string): Promise<void> {\n if (await volumeExists(name)) return;\n await execa('docker', ['volume', 'create', name]);\n}\n\nexport async function networkExists(name: string): Promise<boolean> {\n const result = await execa('docker', ['network', 'inspect', name], { reject: false });\n return result.exitCode === 0;\n}\n\nexport async function ensureNetwork(name: string): Promise<void> {\n if (await networkExists(name)) return;\n await execa('docker', ['network', 'create', name]);\n}\n\nexport async function removeNetwork(name: string): Promise<void> {\n await execa('docker', ['network', 'rm', name], { reject: false });\n}\n\nexport async function containerIsRunning(name: string): Promise<boolean> {\n return (await inspectContainerStatus(name)) === 'running';\n}\n\nexport async function pauseContainer(name: string): Promise<void> {\n await execa('docker', ['pause', name]);\n}\n\nexport async function unpauseContainer(name: string): Promise<void> {\n await execa('docker', ['unpause', name]);\n}\n\nexport async function stopContainer(name: string): Promise<void> {\n await execa('docker', ['stop', name]);\n}\n\nexport async function startContainer(name: string): Promise<void> {\n await execa('docker', ['start', name]);\n}\n\nexport async function inspectContainerStatus(name: string): Promise<ContainerRuntimeState> {\n const result = await execa('docker', ['inspect', '--format', '{{.State.Status}}', name], {\n reject: false,\n });\n if (result.exitCode !== 0) return 'missing';\n const status = (result.stdout ?? '').trim();\n switch (status) {\n case 'running':\n return 'running';\n case 'paused':\n return 'paused';\n case 'created':\n case 'exited':\n case 'dead':\n case 'restarting':\n case 'removing':\n return 'stopped';\n default:\n return 'missing';\n }\n}\n\nexport async function inspectContainer(name: string): Promise<unknown | null> {\n const result = await execa('docker', ['inspect', name], { reject: false });\n if (result.exitCode !== 0) return null;\n try {\n const parsed = JSON.parse(result.stdout ?? 'null') as unknown[];\n return Array.isArray(parsed) ? (parsed[0] ?? null) : null;\n } catch {\n return null;\n }\n}\n\nexport async function inspectVolumeMountpoint(name: string): Promise<string | null> {\n const result = await execa('docker', ['volume', 'inspect', '--format', '{{.Mountpoint}}', name], {\n reject: false,\n });\n if (result.exitCode !== 0) return null;\n return (result.stdout ?? '').trim() || null;\n}\n\nconst AGENTBOX_PREFIX = 'agentbox-';\n\nexport async function listAgentboxContainers(): Promise<string[]> {\n const result = await execa(\n 'docker',\n ['ps', '-a', '--filter', `name=^${AGENTBOX_PREFIX}`, '--format', '{{.Names}}'],\n { reject: false },\n );\n if (result.exitCode !== 0) return [];\n return (result.stdout ?? '')\n .split('\\n')\n .map((s) => s.trim())\n .filter((s) => s.startsWith(AGENTBOX_PREFIX));\n}\n\n/**\n * Resolve the host port Docker assigned to a `-p hostPort:containerPort` mapping\n * that used `hostPort=0`. Returns null when the port isn't published or the\n * container is gone. `docker port <name> 6080/tcp` prints e.g.\n * `127.0.0.1:54321` (one line per binding); we take the first.\n */\nexport async function publishedHostPort(\n container: string,\n containerPort: number,\n): Promise<number | null> {\n const result = await execa('docker', ['port', container, `${String(containerPort)}/tcp`], {\n reject: false,\n });\n if (result.exitCode !== 0) return null;\n const first = (result.stdout ?? '').split('\\n')[0]?.trim();\n if (!first) return null;\n const m = /:(\\d+)$/.exec(first);\n return m ? Number(m[1]) : null;\n}\n\nexport async function listAgentboxVolumes(): Promise<string[]> {\n const result = await execa(\n 'docker',\n ['volume', 'ls', '--filter', `name=^${AGENTBOX_PREFIX}`, '--format', '{{.Name}}'],\n { reject: false },\n );\n if (result.exitCode !== 0) return [];\n return (result.stdout ?? '')\n .split('\\n')\n .map((s) => s.trim())\n .filter((s) => s.startsWith(AGENTBOX_PREFIX));\n}\n","import { mkdir, readFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { execa } from 'execa';\nimport { sanitizeMnemonic } from '@agentbox/config';\nimport type { BoxStatus } from '@agentbox/ctl';\nimport { execInBox } from './docker.js';\nimport type { BoxRecord } from './state.js';\n\nexport type DockerEngine = 'orbstack' | 'docker-desktop' | 'other';\n\n/** In-container path bind-mounted to the per-box host export dir by createBox. */\nexport const CONTAINER_EXPORT_MERGED = '/host-export';\n\nexport interface HostPaths {\n /** Per-box runtime dir on host, e.g. ~/.agentbox/boxes/<id>. */\n boxDir: string;\n /** Snapshot target for the merged /workspace view (rsync'd in by `refreshExport`). */\n mergedExport: string;\n}\n\nlet cachedEngine: DockerEngine | null = null;\n\n/**\n * Inspect the docker daemon to decide which host-side conventions apply.\n * `docker info --format '{{.OperatingSystem}}'` returns strings like\n * \"OrbStack\" or \"Docker Desktop\" — we only care about those two on macOS.\n */\nexport async function detectEngine(): Promise<DockerEngine> {\n if (cachedEngine !== null) return cachedEngine;\n const result = await execa('docker', ['info', '--format', '{{.OperatingSystem}}'], {\n reject: false,\n });\n const os = (result.stdout ?? '').trim().toLowerCase();\n if (os.includes('orbstack')) cachedEngine = 'orbstack';\n else if (os.includes('docker desktop')) cachedEngine = 'docker-desktop';\n else cachedEngine = 'other';\n return cachedEngine;\n}\n\n/**\n * Pin the engine to a specific value, bypassing the `docker info` probe. Two\n * callers today:\n * 1. The CLI bootstrap (apps/cli) when the user has set `engine.kind` in\n * ~/.agentbox/config.yaml — the override applies for the rest of the\n * process so every `detectEngine()` returns the user's choice.\n * 2. Tests, via `__setEngineForTesting` (kept as an alias for back-compat).\n */\nexport function setEngineOverride(engine: DockerEngine | null): void {\n cachedEngine = engine;\n}\n\n/** @deprecated alias for `setEngineOverride`; kept so existing tests don't churn. */\nexport function __setEngineForTesting(engine: DockerEngine | null): void {\n cachedEngine = engine;\n}\n\n/**\n * The active Docker context name — `docker context show` (which honors the\n * `DOCKER_CONTEXT` / `DOCKER_HOST` overrides and `~/.docker/config.json`'s\n * `currentContext`). Embedded in the VS Code attached-container URI so the\n * Dev Containers extension talks to the same daemon agentbox used — without\n * it, switching engines (OrbStack ⇄ Docker Desktop) makes the extension probe\n * the wrong daemon. Best-effort: returns undefined if the probe fails.\n */\nexport async function getDockerContext(): Promise<string | undefined> {\n const result = await execa('docker', ['context', 'show'], { reject: false });\n if (result.exitCode !== 0) return undefined;\n const ctx = (result.stdout ?? '').trim();\n return ctx.length > 0 ? ctx : undefined;\n}\n\nexport const BOXES_ROOT = join(homedir(), '.agentbox', 'boxes');\n\n/** Box-identity subset every dir helper accepts. Structurally compatible with\n * `BoxRecord`, but only the fields the segment needs. `projectIndex` is the\n * 1-based per-project number (`agentbox list`'s `N` column); when present, it\n * appears between the id and the mnemonic so dir listings sort cleanly within\n * a project and the segment matches `agentbox <cmd> <n>` intuitively. Legacy\n * (pre-feature) boxes lack it and keep the original `<id>-<mnemonic>` shape.\n */\nexport interface BoxDirRef {\n id: string;\n name: string;\n projectIndex?: number;\n}\n\n/** On-disk dir segment for a box: `<id>-<n>-<mnemonic>` (or `<id>-<mnemonic>` legacy). */\nexport function boxDirSegment(box: BoxDirRef): string {\n const mnemonic = sanitizeMnemonic(box.name);\n const n = box.projectIndex;\n if (typeof n === 'number' && Number.isFinite(n) && n > 0) {\n return `${box.id}-${String(n)}-${mnemonic}`;\n }\n return `${box.id}-${mnemonic}`;\n}\n\nexport function boxRunDirFor(box: BoxDirRef): string {\n return join(BOXES_ROOT, boxDirSegment(box));\n}\n\n/**\n * Per-box durable status file. The host relay writes it (atomic tmp+rename)\n * when the in-box daemon pushes a `box-status` snapshot; it persists here on\n * the host fs even while the box is paused/stopped. Path must stay in sync\n * with `boxStatusPathFor` in @agentbox/relay's status-store.\n */\nexport function boxStatusPathFor(box: BoxDirRef): string {\n return join(boxRunDirFor(box), 'status.json');\n}\n\n/**\n * Read the persisted box status, or null when there is none (box predates the\n * feature, relay never received a push, corrupt JSON, or a future-incompatible\n * schema). Never throws — callers fall back to live/“unknown”.\n */\nexport async function readBoxStatus(box: BoxDirRef): Promise<BoxStatus | null> {\n try {\n const raw = await readFile(boxStatusPathFor(box), 'utf8');\n const parsed = JSON.parse(raw) as BoxStatus;\n if (parsed.schema !== 1) return null;\n return parsed;\n } catch {\n return null;\n }\n}\n\n/**\n * Host path to a subpath inside an OrbStack-managed named volume.\n *\n * OrbStack exposes named volumes at `~/OrbStack/docker/volumes/<name>/` —\n * NO `_data` wrapper. `docker volume inspect` reports the in-VM mountpoint\n * instead, which isn't reachable from macOS. Returns the path regardless of\n * whether it exists; callers stat it themselves. Used by claude's plugin-cache\n * pre-scan to find host-visible package.jsons on OrbStack.\n */\nexport function orbstackVolumePath(volume: string, ...sub: string[]): string {\n return join(homedir(), 'OrbStack', 'docker', 'volumes', volume, ...sub);\n}\n\nexport async function getHostPaths(\n record: Pick<BoxRecord, 'id' | 'name' | 'projectIndex'>,\n): Promise<HostPaths> {\n const boxDir = boxRunDirFor(record);\n return {\n boxDir,\n mergedExport: join(boxDir, 'workspace'),\n };\n}\n\nexport interface RefreshOptions {\n /** When true, include /workspace/node_modules in the merged export. Off by default. */\n includeNodeModules?: boolean;\n}\n\nexport interface RefreshResult {\n /** Host path that now reflects the box's current state. */\n hostPath: string;\n /** True when an rsync copy actually ran (always true today; kept for callers). */\n copied: boolean;\n /** True when the box predates the /host-export bind and we used the tar-pipe fallback. */\n usedFallback: boolean;\n}\n\nasync function hasContainerPath(container: string, path: string): Promise<boolean> {\n const probe = await execInBox(container, ['test', '-d', path], { user: 'root' });\n return probe.exitCode === 0;\n}\n\n/**\n * Refresh the per-box merged host export (~/.agentbox/boxes/<id>/workspace) so\n * Finder sees the box's current `/workspace`. /workspace lives in the\n * container's writable layer and is invisible to macOS directly, so we always\n * have to copy it out. Prefers rsync via the /host-export bind-mount; falls\n * back to a `tar | tar` pipe through `docker exec` for boxes that predate the\n * bind.\n */\nexport async function refreshExport(\n record: Pick<BoxRecord, 'id' | 'name' | 'projectIndex' | 'container'>,\n opts: RefreshOptions = {},\n): Promise<RefreshResult> {\n const paths = await getHostPaths(record);\n const excludeNodeModules = !opts.includeNodeModules;\n await mkdir(paths.mergedExport, { recursive: true });\n\n const bindAvailable = await hasContainerPath(record.container, CONTAINER_EXPORT_MERGED);\n if (bindAvailable) {\n const args = ['rsync', '-a', '--delete'];\n if (excludeNodeModules) args.push('--exclude=node_modules');\n args.push('/workspace/', `${CONTAINER_EXPORT_MERGED}/`);\n const r = await execInBox(record.container, args, { user: 'root' });\n if (r.exitCode !== 0) {\n throw new ExportError(`rsync into ${CONTAINER_EXPORT_MERGED} failed`, r.stdout, r.stderr);\n }\n return { hostPath: paths.mergedExport, copied: true, usedFallback: false };\n }\n\n // Fallback for pre-existing boxes: stream a tar through docker exec into the\n // host target. Slower and skips the in-place delete that rsync gives us, but\n // it works without recreating the container.\n const excludes = excludeNodeModules ? ['--exclude=node_modules'] : [];\n const result = await execa(\n 'docker',\n ['exec', '--user', 'root', record.container, 'tar', '-cf', '-', ...excludes, '-C', '/workspace', '.'],\n { reject: false, encoding: 'buffer' },\n );\n if (result.exitCode !== 0) {\n throw new ExportError(\n `tar from /workspace failed`,\n '',\n typeof result.stderr === 'string' ? result.stderr : (result.stderr as Buffer).toString('utf8'),\n );\n }\n const extract = await execa('tar', ['-xf', '-', '-C', paths.mergedExport], {\n input: result.stdout as Buffer,\n reject: false,\n });\n if (extract.exitCode !== 0) {\n throw new ExportError('tar extract on host failed', extract.stdout, extract.stderr);\n }\n return { hostPath: paths.mergedExport, copied: true, usedFallback: true };\n}\n\n/**\n * Default env/config file basename globs for `pull env` / `pull --with-env`.\n * These are almost always gitignored, so a normal gitignore-aware `pull`\n * skips them; this list opts them back in explicitly. `agentbox.yaml` is\n * included so a file generated in-box by `/agentbox-setup` lands on the host\n * even before it's committed.\n */\nexport const DEFAULT_ENV_PATTERNS = [\n '.env',\n '.env.*',\n '.envrc',\n '.dev.vars',\n 'secrets.toml',\n 'local.settings.json',\n 'appsettings.*.json',\n 'agentbox.yaml',\n];\n\n/** Directories the env-file `find` prunes — heavy or never-relevant. */\nconst ENV_PRUNE_DIRS = [\n 'node_modules',\n '.git',\n '.venv',\n 'venv',\n '__pycache__',\n 'dist',\n '.next',\n 'build',\n];\n\n/**\n * Build the in-box `find` argv that enumerates env/config files by basename\n * glob, pruning `ENV_PRUNE_DIRS`. `-printf '%P\\0'` emits NUL-delimited paths\n * already relative to /workspace (so they feed rsync --files-from --from0\n * directly, exactly like `git ls-files -z`).\n */\nfunction buildEnvFindArgs(patterns: string[]): string[] {\n const nameGroup = (names: string[]): string[] => {\n const out: string[] = [];\n names.forEach((n, i) => {\n if (i > 0) out.push('-o');\n out.push('-name', n);\n });\n return out;\n };\n return [\n 'find',\n '/workspace',\n '(',\n '-type',\n 'd',\n '(',\n ...nameGroup(ENV_PRUNE_DIRS),\n ')',\n '-prune',\n ')',\n '-o',\n '(',\n '-type',\n 'f',\n '(',\n ...nameGroup(patterns),\n ')',\n '-printf',\n '%P\\\\0',\n ')',\n ];\n}\n\n/**\n * Host-side mirror of `buildEnvFindArgs` for the reverse direction (host →\n * box). Rooted at `.` (run with cwd = the host workspace) and uses `-print0`\n * instead of `-printf '%P\\0'` because macOS's BSD `find` has no `-printf`;\n * `./relpath` entries feed `tar -C <workspace> --null -T -` directly, exactly\n * like the untracked-file pipe in git-worktree.ts.\n */\nexport function buildHostEnvFindArgs(patterns: string[]): string[] {\n const nameGroup = (names: string[]): string[] => {\n const out: string[] = [];\n names.forEach((n, i) => {\n if (i > 0) out.push('-o');\n out.push('-name', n);\n });\n return out;\n };\n return [\n 'find',\n '.',\n '(',\n '-type',\n 'd',\n '(',\n ...nameGroup(ENV_PRUNE_DIRS),\n ')',\n '-prune',\n ')',\n '-o',\n '(',\n '-type',\n 'f',\n '(',\n ...nameGroup(patterns),\n ')',\n '-print0',\n ')',\n ];\n}\n\nexport interface CopyHostEnvOptions {\n /** Target container name (must be running with the overlay mounted). */\n container: string;\n /** Absolute host workspace dir — the same dir that maps to /workspace. */\n workspaceDir: string;\n /** Basename globs to copy (normally DEFAULT_ENV_PATTERNS). */\n patterns: string[];\n onLog?: (line: string) => void;\n}\n\n/**\n * Copy the host's env/config files (selected by `patterns`, gitignore ignored)\n * into the running box's `/workspace`. The reverse of `pullToHost`'s env\n * segment. Writes land in the overlay's writable upper layer (the container is\n * up + mounted at call time), so they survive pause/stop/start.\n *\n * Best-effort: a tar/exec failure or an empty match set logs and returns the\n * count rather than throwing — a missing secret shouldn't abort an otherwise\n * healthy box. Files extract as uid 1000 so they're owned by `vscode` like the\n * rest of /workspace.\n */\nexport async function copyHostEnvFilesToBox(\n opts: CopyHostEnvOptions,\n): Promise<{ copied: number }> {\n const log = opts.onLog ?? (() => {});\n\n // Default (utf8) encoding: `find` output is NUL-delimited path text, and\n // `encoding:'buffer'` would hand back a Uint8Array whose .toString() is\n // comma-joined byte codes, not the paths.\n const found = await execa('find', buildHostEnvFindArgs(opts.patterns).slice(1), {\n cwd: opts.workspaceDir,\n reject: false,\n });\n if (found.exitCode !== 0) {\n log(`warning: env-file scan failed: ${String(found.stderr).slice(0, 300)}`);\n return { copied: 0 };\n }\n const list = String(found.stdout)\n .split('\\0')\n .filter((p) => p.length > 0);\n if (list.length === 0) return { copied: 0 };\n\n // Same fork-and-stream as the untracked-file carry-over in git-worktree.ts.\n const packed = await execa('tar', ['-C', opts.workspaceDir, '--null', '-T', '-', '-cf', '-'], {\n input: list.join('\\0'),\n encoding: 'buffer',\n reject: false,\n });\n if (packed.exitCode !== 0) {\n log(`warning: env-file tar pack failed: ${String(packed.stderr).slice(0, 300)}`);\n return { copied: 0 };\n }\n const extract = await execa(\n 'docker',\n ['exec', '-i', '--user', '1000:1000', opts.container, 'tar', '-xf', '-', '-C', '/workspace'],\n { input: packed.stdout as Buffer, reject: false },\n );\n if (extract.exitCode !== 0) {\n log(`warning: env-file copy into box failed: ${String(extract.stderr).slice(0, 300)}`);\n return { copied: 0 };\n }\n return { copied: list.length };\n}\n\n/**\n * Run `buildHostEnvFindArgs` against `workspaceDir` and return the matched\n * paths as a relative-path string array. Pure host-side helper: no docker, no\n * mutation. Empty array on a scan failure (best-effort, matching\n * `copyHostEnvFilesToBox`). Used by the setup wizard to preview a multiselect\n * of importable env files.\n */\nexport async function scanHostEnvFiles(\n workspaceDir: string,\n patterns: string[],\n): Promise<string[]> {\n if (patterns.length === 0) return [];\n const found = await execa('find', buildHostEnvFindArgs(patterns).slice(1), {\n cwd: workspaceDir,\n reject: false,\n });\n if (found.exitCode !== 0) return [];\n return String(found.stdout)\n .split('\\0')\n .map((p) => p.replace(/^\\.\\//, ''))\n .filter((p) => p.length > 0);\n}\n\nexport interface CopyHostFilesOptions {\n /** Target container name (must be running). */\n container: string;\n /** Absolute host workspace dir — the same dir that maps to /workspace. */\n workspaceDir: string;\n /** Relative paths (to workspaceDir) to copy. NUL-safe; no glob expansion. */\n files: string[];\n onLog?: (line: string) => void;\n}\n\n/**\n * Sibling to `copyHostEnvFilesToBox` that skips the `find` scan and trusts a\n * pre-vetted file list (e.g. the user's multiselect picks from the wizard).\n * Same tar-pipe body: tar reads the NUL-delimited list on stdin and pipes into\n * `docker exec tar -x`. Best-effort error handling — a tar/exec failure logs\n * and returns the count rather than throwing.\n */\nexport async function copyHostFilesToBox(\n opts: CopyHostFilesOptions,\n): Promise<{ copied: number }> {\n const log = opts.onLog ?? (() => {});\n // Normalise — drop any leading \"./\" so the in-container extract lands at the\n // right path, and drop empties so a stray trailing NUL doesn't become `tar: ''`.\n const list = opts.files.map((p) => p.replace(/^\\.\\//, '')).filter((p) => p.length > 0);\n if (list.length === 0) return { copied: 0 };\n\n const packed = await execa('tar', ['-C', opts.workspaceDir, '--null', '-T', '-', '-cf', '-'], {\n input: list.join('\\0'),\n encoding: 'buffer',\n reject: false,\n });\n if (packed.exitCode !== 0) {\n log(`warning: env-file tar pack failed: ${String(packed.stderr).slice(0, 300)}`);\n return { copied: 0 };\n }\n const extract = await execa(\n 'docker',\n ['exec', '-i', '--user', '1000:1000', opts.container, 'tar', '-xf', '-', '-C', '/workspace'],\n { input: packed.stdout as Buffer, reject: false },\n );\n if (extract.exitCode !== 0) {\n log(`warning: env-file copy into box failed: ${String(extract.stderr).slice(0, 300)}`);\n return { copied: 0 };\n }\n return { copied: list.length };\n}\n\nexport interface PullOptions {\n /** Default true. When false, skip git ls-files and use the static exclude-list. */\n respectGitignore?: boolean;\n /** Default false. When true, don't filter node_modules even in fallback mode. */\n includeNodeModules?: boolean;\n /** Default false. Skip the initial refreshExport — pull whatever's already in the scratch dir. */\n noRefresh?: boolean;\n /** Default false. Run rsync with --dry-run; return the change list without writing. */\n dryRun?: boolean;\n /**\n * Extra env/config files to pull, selected by these basename globs via an\n * in-box `find` (heavy dirs pruned). Composes WITH gitignore selection: the\n * rsync file list is the union of the git-tracked set (unless\n * respectGitignore is false) and these matches. Empty/undefined = no env\n * segment.\n */\n envPatterns?: string[];\n}\n\nexport interface PullResult {\n /** Absolute host workspace path the pull targeted (record.workspacePath). */\n hostPath: string;\n /** Per-file rsync change list (itemized `-i` lines, transfers/deletes only). */\n changes: string[];\n /** True when an actual write happened. False on dry-run. */\n applied: boolean;\n /** True when gitignore-mode was used (vs. the fallback exclude-list). */\n usedGitignore: boolean;\n}\n\n/**\n * Keep only itemized lines that represent an actual file transfer or delete.\n * rsync `-i` emits a leading 11-char code: char 0 is the update type\n * (`>`/`<`/`c`/`*` = transfer/change/delete; `.` = attr-only, skipped) and\n * char 1 is the entry type (`f` file, `d` dir, ...). Directory lines (`d`)\n * are pruned: rsync creates parent dirs as a side effect of transferring\n * files, so counting them would overstate \"files changed\".\n */\nfunction parseItemizedChanges(stdout: string): string[] {\n return stdout\n .split('\\n')\n .map((l) => l.trimEnd())\n .filter((l) => l.length > 0)\n .filter((l) => {\n const code = l[0];\n const kind = l[1];\n return (code === '>' || code === '<' || code === 'c' || code === '*') && kind !== 'd';\n });\n}\n\n/**\n * Reverse of `refreshExport`: bring the box's merged `/workspace` view back\n * into the user's actual host working directory (`record.workspacePath`).\n *\n * Two-stage: (1) `refreshExport` materializes `/workspace` in the per-box\n * scratch dir (`~/.agentbox/boxes/<id>/workspace`) — `/workspace` lives in\n * the container's writable layer, invisible to macOS directly; (2) a\n * host-side rsync copies scratch → `workspacePath`.\n *\n * Filtering: by default we ask git *inside the box* which files it would\n * track (`git ls-files --cached --others --exclude-standard`) so node_modules\n * / build dirs / gitignored secrets never leak back. Non-git workspaces (or\n * `respectGitignore: false`) fall back to a static `--exclude` list.\n *\n * Never passes `--delete`: files that exist on the host but not in the box\n * are preserved. Removals are the user's call.\n */\nexport async function pullToHost(\n record: Pick<BoxRecord, 'id' | 'name' | 'projectIndex' | 'container' | 'workspacePath'>,\n opts: PullOptions = {},\n): Promise<PullResult> {\n const paths = await getHostPaths(record);\n\n let scratchDir: string;\n if (opts.noRefresh) {\n scratchDir = paths.mergedExport;\n await mkdir(scratchDir, { recursive: true });\n } else {\n const refreshed = await refreshExport(record, {\n includeNodeModules: opts.includeNodeModules,\n });\n scratchDir = refreshed.hostPath;\n }\n\n // The rsync file list is the union of up to two independent NUL-delimited\n // segments: git-tracked (gitignore-aware) and env-pattern (gitignore\n // bypassed). If neither is produced we fall through to the static\n // exclude-list (non-git workspace, no env patterns).\n const segments: string[] = [];\n let usedGitignore = false;\n if (opts.respectGitignore !== false) {\n const isGit = await execInBox(\n record.container,\n ['git', '-C', '/workspace', 'rev-parse', '--is-inside-work-tree'],\n { user: 'root' },\n );\n if (isGit.exitCode === 0 && isGit.stdout.trim() === 'true') {\n const ls = await execInBox(\n record.container,\n ['git', '-C', '/workspace', 'ls-files', '-z', '--cached', '--others', '--exclude-standard'],\n { user: 'root' },\n );\n if (ls.exitCode !== 0) {\n throw new ExportError('git ls-files in box failed', ls.stdout, ls.stderr);\n }\n // git -z is NUL-delimited; rsync --from0 wants the same.\n const tracked = ls.stdout.replace(/\\0$/, '');\n if (tracked.length > 0) segments.push(tracked);\n usedGitignore = true;\n }\n }\n if (opts.envPatterns && opts.envPatterns.length > 0) {\n const found = await execInBox(\n record.container,\n buildEnvFindArgs(opts.envPatterns),\n { user: 'root' },\n );\n if (found.exitCode !== 0) {\n throw new ExportError('find env files in box failed', found.stdout, found.stderr);\n }\n const envFiles = found.stdout.replace(/\\0$/, '');\n if (envFiles.length > 0) segments.push(envFiles);\n }\n const fileList =\n segments.length > 0\n ? Array.from(new Set(segments.join('\\0').split('\\0'))).join('\\0')\n : null;\n\n // --checksum, not the default size+mtime quick-check: the box runs on a\n // fresh git worktree so every file's mtime differs from the user's working\n // tree even when the content is byte-identical. Without -c, rsync would\n // \"update\" the entire tree. -c compares content hashes so only genuinely\n // changed files are written.\n const baseArgs = ['-a', '--checksum'];\n if (fileList === null) {\n baseArgs.push('--exclude=.git');\n if (!opts.includeNodeModules) baseArgs.push('--exclude=node_modules');\n } else {\n baseArgs.push('--files-from=-', '--from0');\n }\n const src = `${scratchDir}/`;\n const dst = `${record.workspacePath}/`;\n\n const dry = await execa('rsync', [...baseArgs, '--dry-run', '-i', src, dst], {\n reject: false,\n input: fileList !== null ? fileList : undefined,\n });\n if (dry.exitCode !== 0) {\n throw new ExportError('rsync dry-run failed', dry.stdout, dry.stderr);\n }\n const changes = parseItemizedChanges(dry.stdout);\n\n if (opts.dryRun) {\n return { hostPath: record.workspacePath, changes, applied: false, usedGitignore };\n }\n\n const real = await execa('rsync', [...baseArgs, src, dst], {\n reject: false,\n input: fileList !== null ? fileList : undefined,\n });\n if (real.exitCode !== 0) {\n throw new ExportError(`rsync into ${record.workspacePath} failed`, real.stdout, real.stderr);\n }\n return { hostPath: record.workspacePath, changes, applied: true, usedGitignore };\n}\n\nexport interface OpenOptions extends RefreshOptions {\n /** When true, skip rsync and just open whatever's already on disk. */\n noRefresh?: boolean;\n /** When true, refresh as usual but don't launch macOS `open` on the resulting path. */\n noOpen?: boolean;\n}\n\nexport interface OpenResult {\n hostPath: string;\n copied: boolean;\n usedFallback: boolean;\n engine: DockerEngine;\n}\n\n/**\n * Refresh the merged export (unless suppressed) and launch the macOS `open`\n * command on it. Returns the host path that was opened.\n *\n * Set `noOpen: true` to refresh and return the path without launching\n * Finder — used by `agentbox open --path` so scripted callers get a fresh\n * path in one call.\n */\nexport async function openInFinder(\n record: Pick<BoxRecord, 'id' | 'name' | 'projectIndex' | 'container'>,\n opts: OpenOptions,\n): Promise<OpenResult> {\n const engine = await detectEngine();\n let hostPath: string;\n let copied = false;\n let usedFallback = false;\n\n if (opts.noRefresh) {\n const paths = await getHostPaths(record);\n hostPath = paths.mergedExport;\n await mkdir(hostPath, { recursive: true });\n } else {\n const refreshed = await refreshExport(record, opts);\n hostPath = refreshed.hostPath;\n copied = refreshed.copied;\n usedFallback = refreshed.usedFallback;\n }\n\n if (!opts.noOpen) {\n const opened = await execa('open', [hostPath], { reject: false });\n if (opened.exitCode !== 0) {\n throw new ExportError(`open ${hostPath} failed`, opened.stdout, opened.stderr);\n }\n }\n\n return { hostPath, copied, usedFallback, engine };\n}\n\nexport class ExportError extends Error {\n constructor(\n message: string,\n public readonly stdout: string,\n public readonly stderr: string,\n ) {\n super(`${message}${stderr ? `: ${stderr.trim()}` : ''}`);\n this.name = 'ExportError';\n }\n}\n","import { chmod, mkdir, readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { execa } from 'execa';\nimport type { ClaudeConfigSpec } from './claude.js';\nimport { STATE_DIR } from './state.js';\n\n/**\n * Host-side backup of the in-box Claude Code OAuth credentials.\n *\n * Claude's interactive login (inside a box) writes the only Linux auth file:\n * `~/.claude/.credentials.json`, holding a `claudeAiOauth` block with a real\n * `refreshToken` — that refresh token is what makes claude report \"Claude Max\"\n * instead of \"Claude API\". This host backup mirrors that blob so a fresh\n * `--isolate-claude-config` box, or a recreated shared volume, can be seeded\n * with the user's login instead of forcing a re-login.\n *\n * Security: the file holds a live refresh token. It is written mode 0600 under\n * `~/.agentbox/` (already the 0600-convention dir for `auth.json`). Plaintext\n * on disk matches the existing model — the claude-config volume already stores\n * the same blob in the clear; macOS Keychain is the *host* claude's store and\n * is unreachable from a Linux container.\n */\nexport const CREDENTIALS_BACKUP_FILE = join(STATE_DIR, 'claude-credentials.json');\n\nexport type CredentialSyncDirection = 'extracted' | 'seeded' | 'noop';\n\nexport interface SyncClaudeCredentialsResult {\n /**\n * `extracted` — the volume's live credentials were copied out to the host\n * backup. `seeded` — the host backup was copied into an empty volume.\n * `noop` — nothing to do (or the best-effort helper failed silently).\n */\n direction: CredentialSyncDirection;\n /**\n * Whether the volume holds a real OAuth blob *after* the sync (true also\n * right after a `seeded`). When false, the in-box claude will fall back to\n * the setup-token / API key, or prompt for an interactive login.\n */\n volumeHasCredentials: boolean;\n}\n\n/**\n * True iff the host backup file holds a real OAuth blob (a non-empty\n * `claudeAiOauth.refreshToken`). Used to decide whether to offer an\n * interactive sign-in before creating a box. Tolerant of a missing or\n * garbage file — returns false.\n */\nexport async function hostBackupHasCredentials(\n path: string = CREDENTIALS_BACKUP_FILE,\n): Promise<boolean> {\n try {\n const parsed = JSON.parse(await readFile(path, 'utf8')) as {\n claudeAiOauth?: { refreshToken?: unknown };\n };\n const rt = parsed?.claudeAiOauth?.refreshToken;\n return typeof rt === 'string' && rt.length > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Parse the `EXTRACTED=<yes|no> SEEDED=<yes|no> VOLREAL=<yes|no>` line the\n * helper container prints. Pure — unit-tested independently of docker.\n */\nexport function parseSyncResult(stdout: string): SyncClaudeCredentialsResult {\n const volumeHasCredentials = /\\bVOLREAL=yes\\b/.test(stdout);\n if (/\\bEXTRACTED=yes\\b/.test(stdout)) return { direction: 'extracted', volumeHasCredentials };\n if (/\\bSEEDED=yes\\b/.test(stdout)) return { direction: 'seeded', volumeHasCredentials };\n return { direction: 'noop', volumeHasCredentials };\n}\n\n// Bidirectional, best-effort. `jq` ships in the box image. A credentials blob\n// only counts as \"real\" when `claudeAiOauth.refreshToken` is a non-empty\n// string — a setup-token blob has an accessToken but no refreshToken, and\n// seeding/extracting it would just reproduce the \"Claude API\" label. The\n// final `echo` always runs so the caller can read the outcome; a failed `cp`\n// leaves the flag at `no` and the whole thing still exits 0.\nconst SYNC_SCRIPT = `\nEXTRACTED=no\nSEEDED=no\nVOL=/dst/.credentials.json\nHOST=/host-state/claude-credentials.json\nif [ -f \"$VOL\" ] && jq -e '(.claudeAiOauth.refreshToken // \"\") | length > 0' \"$VOL\" >/dev/null 2>&1; then VOL_REAL=yes; else VOL_REAL=no; fi\nif [ -f \"$HOST\" ] && jq -e '(.claudeAiOauth.refreshToken // \"\") | length > 0' \"$HOST\" >/dev/null 2>&1; then HOST_REAL=yes; else HOST_REAL=no; fi\nif [ \"$VOL_REAL\" = yes ] && [ \"$ISOLATE\" != yes ]; then\n cp -a \"$VOL\" \"$HOST\" && chmod 600 \"$HOST\" && EXTRACTED=yes\nelif [ \"$VOL_REAL\" = no ] && [ \"$HOST_REAL\" = yes ]; then\n cp -a \"$HOST\" \"$VOL\" && chown 1000:1000 \"$VOL\" && chmod 600 \"$VOL\" && SEEDED=yes && VOL_REAL=yes\nfi\necho \"EXTRACTED=$EXTRACTED SEEDED=$SEEDED VOLREAL=$VOL_REAL\"\n`;\n\n/**\n * Bidirectionally sync the box's `.credentials.json` with the host backup:\n *\n * - volume has a real OAuth blob and the box is **not** isolate → copy it OUT\n * to {@link CREDENTIALS_BACKUP_FILE} (the volume is authoritative — it is\n * the live copy the in-box claude refreshes).\n * - volume has no `.credentials.json` and the host backup is real → copy it\n * IN (seeds a fresh isolate box, or a recreated shared volume).\n * - otherwise → noop.\n *\n * Isolate boxes are read-seed only — never extract — so N isolate boxes can't\n * race on the single host backup.\n *\n * Best-effort: any failure resolves to `{ direction: 'noop' }` and never\n * throws into a box operation.\n */\nexport async function syncClaudeCredentials(\n spec: ClaudeConfigSpec,\n opts: { image: string; isolate: boolean },\n): Promise<SyncClaudeCredentialsResult> {\n try {\n await mkdir(STATE_DIR, { recursive: true });\n const { stdout } = await execa('docker', [\n 'run',\n '--rm',\n '--user',\n '0',\n '-v',\n `${spec.volume}:/dst`,\n '-v',\n `${STATE_DIR}:/host-state`,\n '-e',\n `ISOLATE=${opts.isolate ? 'yes' : 'no'}`,\n opts.image,\n 'sh',\n '-c',\n SYNC_SCRIPT,\n ]);\n const result = parseSyncResult(stdout);\n // The helper runs as root; re-assert 0600 from the host side so the backup\n // is owner-only regardless of how the bind mount maps ownership.\n if (result.direction === 'extracted') {\n await chmod(CREDENTIALS_BACKUP_FILE, 0o600).catch(() => {});\n }\n return result;\n } catch {\n return { direction: 'noop', volumeHasCredentials: false };\n }\n}\n","// The box state store is provider-neutral and lives in @agentbox/sandbox-core\n// so cloud providers share it. The record TYPES live in @agentbox/core. Both\n// are re-exported here so existing `@agentbox/sandbox-docker` consumers keep\n// importing them from the same place.\nexport {\n STATE_DIR,\n STATE_FILE,\n allocateProjectIndex,\n autoPickProjectBox,\n findBox,\n readState,\n recordBox,\n removeBoxRecord,\n resolveBoxRef,\n writeState,\n} from '@agentbox/sandbox-core';\nexport type { BoxRecord, FindBoxResult, GitWorktreeRecord, StateFile } from '@agentbox/core';\n","import { spawnSync } from 'node:child_process';\nimport { stat } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { execa } from 'execa';\nimport { buildTmuxSessionArgs, CONTAINER_USER } from './claude.js';\nimport { ensureVolume, volumeExists } from './docker.js';\n\n/**\n * Codex support mirrors the Claude support in `claude.ts`, trimmed for what\n * Codex actually has: a synced `~/.codex` config volume, a detachable tmux\n * session, and `codex login`. Codex has no plugin system, so there is no\n * plugin-native-deps rebuild and no setup-skill seeding here.\n */\nexport const SHARED_CODEX_VOLUME = 'agentbox-codex-config';\nexport const DEFAULT_CODEX_SESSION = 'codex';\n/** Workspace inside the box, same as for claude. */\nconst CONTAINER_CODEX_DIR = '/home/vscode/.codex';\n/**\n * Image-baked copy of the AgentBox Codex activity hooks (Dockerfile.box COPYs\n * `scripts/agentbox-codex-hooks.json` here). {@link seedCodexHooks} copies it\n * into the codex-config volume as `~/.codex/hooks.json`.\n */\nconst IN_BOX_CODEX_HOOKS_PATH = '/usr/local/share/agentbox/codex-hooks.json';\n\nexport interface CodexConfigSpec {\n /** Resolved Docker volume name mounted at /home/vscode/.codex. */\n volume: string;\n}\n\nexport function resolveCodexVolume(opts: { isolate: boolean; boxId: string }): CodexConfigSpec {\n if (opts.isolate) {\n return { volume: `${SHARED_CODEX_VOLUME}-${opts.boxId}` };\n }\n return { volume: SHARED_CODEX_VOLUME };\n}\n\nasync function pathExists(p: string): Promise<boolean> {\n try {\n await stat(p);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Single-quote a token for /bin/sh. Conservative: anything outside the safe\n * alphabet gets wrapped. Mirrors the helper in claude.ts (not exported there).\n */\nfunction shQuote(arg: string): string {\n if (arg.length === 0) return `''`;\n if (/^[A-Za-z0-9_\\-./=:@%+,]+$/.test(arg)) return arg;\n return `'${arg.replace(/'/g, `'\\\\''`)}'`;\n}\n\nexport interface EnsureCodexVolumeOptions {\n /**\n * When true and the host's ~/.codex exists, rsync host -> volume on every\n * call. Additive (no `--delete`): host files win on overlap, box-only files\n * (e.g. an `auth.json` written by an in-box `codex login`) are preserved.\n */\n syncFromHost: boolean;\n /** Image used by the throwaway sync helper container (the box image). */\n image: string;\n}\n\nexport interface EnsureCodexVolumeResult {\n /** True only the very first time the volume is created (on this host). */\n created: boolean;\n /** True when the rsync helper ran (syncFromHost was true AND host ~/.codex existed). */\n synced: boolean;\n}\n\n/**\n * Ensure the codex-config volume exists, then (when {@link\n * EnsureCodexVolumeOptions.syncFromHost} is true and the host has a `~/.codex`)\n * rsync host -> volume via a throwaway helper container. The host is treated as\n * authoritative — same model as {@link import('./claude.js').ensureClaudeVolume}.\n *\n * Rollout transcripts (`sessions/`), logs (`log/`) and shell history\n * (`history.jsonl`) are excluded: large, box-irrelevant, and not something the\n * in-box codex needs seeded.\n *\n * When there is nothing to sync the volume root is still `chown`ed to uid 1000\n * so a throwaway `codex login` container (running as `vscode`) can write\n * `auth.json` into a freshly created, otherwise root-owned volume.\n */\nexport async function ensureCodexVolume(\n spec: CodexConfigSpec,\n opts: EnsureCodexVolumeOptions,\n): Promise<EnsureCodexVolumeResult> {\n const existed = await volumeExists(spec.volume);\n await ensureVolume(spec.volume);\n const created = !existed;\n\n const hostCodex = join(homedir(), '.codex');\n const willSync = opts.syncFromHost && (await pathExists(hostCodex));\n if (willSync) {\n await execa('docker', [\n 'run',\n '--rm',\n '--user',\n '0',\n '-v',\n `${spec.volume}:/dst`,\n '-v',\n `${hostCodex}:/src:ro`,\n opts.image,\n 'sh',\n '-c',\n // --exclude=hooks.json: the AgentBox activity hooks file is box-owned\n // (seeded by seedCodexHooks); never let the host copy clobber it.\n 'rsync -a --exclude=sessions --exclude=log --exclude=history.jsonl --exclude=hooks.json' +\n ' /src/ /dst/ && chown -R 1000:1000 /dst',\n ]);\n return { created, synced: true };\n }\n\n // No host ~/.codex to sync — still make the (possibly freshly created,\n // root-owned) volume root writable by the in-box `vscode` user.\n await execa(\n 'docker',\n [\n 'run',\n '--rm',\n '--user',\n '0',\n '-v',\n `${spec.volume}:/dst`,\n opts.image,\n 'sh',\n '-c',\n 'chown 1000:1000 /dst',\n ],\n { reject: false },\n );\n return { created, synced: false };\n}\n\n/**\n * Seed the AgentBox Codex activity hooks into the codex-config volume from the\n * image-baked copy ({@link IN_BOX_CODEX_HOOKS_PATH}) as `~/.codex/hooks.json`.\n * Codex auto-discovers that file; its hooks accumulate with any the user\n * defined, so this never disables the user's own hooks.\n *\n * Re-seeded on every create/start (image-versioned) so an image upgrade\n * propagates. Best-effort — a failure must not fail box creation.\n */\nexport async function seedCodexHooks(\n volume: string,\n image: string,\n): Promise<{ seeded: boolean }> {\n try {\n const { stdout } = await execa('docker', [\n 'run',\n '--rm',\n '--user',\n '0',\n '-v',\n `${volume}:/dst`,\n image,\n 'sh',\n '-c',\n `{ [ -f ${IN_BOX_CODEX_HOOKS_PATH} ] && cp -a ${IN_BOX_CODEX_HOOKS_PATH} /dst/hooks.json && ` +\n `chown 1000:1000 /dst/hooks.json && echo SEEDED; } || true`,\n ]);\n return { seeded: stdout.includes('SEEDED') };\n } catch {\n return { seeded: false };\n }\n}\n\nexport interface CodexMountResult {\n /** Docker -v spec strings to append to runBox(extraVolumes). */\n extraVolumes: string[];\n /** Env vars to forward into the container; only keys set + non-empty on the host. */\n env: Record<string, string>;\n volumeName: string;\n}\n\n// Forwarded from the host's `process.env` into the box at `docker run -e` time\n// (and re-forwarded by `startCodexSession` at `docker exec -e` time). Codex\n// stores the selected model in `~/.codex/config.toml`, not the environment, so\n// the API key is the only thing worth forwarding.\nexport const CODEX_FORWARDED_ENV_KEYS = ['OPENAI_API_KEY'] as const;\n\nexport function buildCodexMounts(\n spec: CodexConfigSpec,\n hostEnv: NodeJS.ProcessEnv,\n): CodexMountResult {\n const env: Record<string, string> = {};\n for (const k of CODEX_FORWARDED_ENV_KEYS) {\n const v = hostEnv[k];\n if (typeof v === 'string' && v.length > 0) env[k] = v;\n }\n return {\n extraVolumes: [`${spec.volume}:${CONTAINER_CODEX_DIR}`],\n env,\n volumeName: spec.volume,\n };\n}\n\nexport class CodexSessionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'CodexSessionError';\n }\n}\n\nexport interface EnsureCodexInstalledResult {\n /**\n * True when codex had to be installed just now — i.e. it was absent from the\n * box image. Happens for boxes created from a checkpoint image captured\n * before Codex was baked into the base image, or from an older base image.\n */\n installed: boolean;\n}\n\n/**\n * Make sure the `codex` binary is on PATH inside the box. Codex is baked into\n * the current base image, but a box created from a **checkpoint** image (a\n * frozen snapshot — see docs/create-and-checkpoints.md) captured before Codex\n * support, or from an older base image, won't have it. In that case we\n * `npm install -g @openai/codex` into the box's writable layer (persists across\n * stop/start, wiped on destroy), mirroring how `--with-playwright` installs at\n * create time.\n *\n * Fast no-op (one `command -v`) when codex is already present — the case for\n * every box built from the current base image. Throws {@link CodexSessionError}\n * when codex is absent *and* the install fails.\n */\nexport async function ensureCodexInstalled(\n container: string,\n opts: { onProgress?: (line: string) => void } = {},\n): Promise<EnsureCodexInstalledResult> {\n const probe = await execa(\n 'docker',\n ['exec', '--user', CONTAINER_USER, container, 'sh', '-c', 'command -v codex'],\n { reject: false },\n );\n if (probe.exitCode === 0) return { installed: false };\n\n opts.onProgress?.('installing codex (absent from this box image)');\n const install = await execa(\n 'docker',\n ['exec', '--user', 'root', container, 'bash', '-lc', 'npm install -g @openai/codex 2>&1'],\n { reject: false },\n );\n if (install.exitCode !== 0) {\n throw new CodexSessionError(\n `codex is not in this box's image and \\`npm install -g @openai/codex\\` failed ` +\n `(exit ${String(install.exitCode)}). This box was likely created from a ` +\n `checkpoint captured before Codex support — recapture the project checkpoint ` +\n `from a fresh box. Install output:\\n${(install.stdout ?? '').toString().slice(-600)}`,\n );\n }\n return { installed: true };\n}\n\nexport interface StartCodexSessionOptions {\n container: string;\n codexArgs: string[];\n sessionName?: string;\n}\n\n/**\n * Start a detached tmux session running the Codex CLI inside the container.\n * Survives client disconnects; reattach via {@link buildCodexAttachArgv}. The\n * shared {@link buildTmuxSessionArgs} remaps the prefix (Ctrl+a / Ctrl+b) and\n * hides the inner status bar, exactly as for the claude session.\n */\nexport async function startCodexSession(opts: StartCodexSessionOptions): Promise<void> {\n const sessionName = opts.sessionName ?? DEFAULT_CODEX_SESSION;\n const cmd = ['codex', ...opts.codexArgs].map(shQuote).join(' ');\n const term = process.env['TERM'] ?? 'xterm-256color';\n const envFlags: string[] = ['-e', `TERM=${term}`];\n for (const k of CODEX_FORWARDED_ENV_KEYS) {\n const v = process.env[k];\n if (typeof v === 'string' && v.length > 0) envFlags.push('-e', `${k}=${v}`);\n }\n const result = await execa(\n 'docker',\n [\n 'exec',\n ...envFlags,\n '--user',\n CONTAINER_USER,\n opts.container,\n 'tmux',\n 'new-session',\n '-d',\n '-s',\n sessionName,\n cmd,\n ...buildTmuxSessionArgs(sessionName),\n ],\n { reject: false },\n );\n if (result.exitCode === 0) return;\n const stderr = (result.stderr ?? '').toString();\n if (result.exitCode === 127 || /command not found|tmux: not found/i.test(stderr)) {\n throw new CodexSessionError(\n `tmux is missing from the box image. Rebuild with: docker rmi agentbox/box:dev && retry.`,\n );\n }\n if (/codex.*not found|exec: \"codex\"/i.test(stderr)) {\n throw new CodexSessionError(\n `codex is missing from the box image. Rebuild with: docker rmi agentbox/box:dev && retry.`,\n );\n }\n if (/duplicate session/i.test(stderr)) {\n throw new CodexSessionError(\n `a tmux session \"${sessionName}\" already exists in ${opts.container}; use \\`agentbox codex attach\\` to reattach.`,\n );\n }\n throw new CodexSessionError(\n `failed to start codex session in ${opts.container}: ${stderr.trim() || `exit ${String(result.exitCode)}`}`,\n );\n}\n\n/**\n * The `docker` argv that attaches an interactive terminal to a box's Codex\n * tmux session. Mirrors {@link import('./claude.js').buildClaudeAttachArgv}.\n */\nexport function buildCodexAttachArgv(container: string, sessionName?: string): string[] {\n const name = sessionName ?? DEFAULT_CODEX_SESSION;\n const term = process.env['TERM'] ?? 'xterm-256color';\n return [\n 'exec',\n '-it',\n '-e',\n `TERM=${term}`,\n '--user',\n CONTAINER_USER,\n container,\n 'tmux',\n 'attach',\n '-t',\n name,\n ];\n}\n\n/**\n * The `docker run` argv for an interactive `codex login` in a throwaway\n * container. Mounts the codex-config volume at `~/.codex` so the written\n * credentials persist. Defaults to `--device-auth` — the headless device-code\n * flow (prints a URL + one-time code, no localhost callback to publish) — which\n * is the only OAuth flow that works cleanly from inside a container. Explicit\n * `extraArgs` (e.g. `['--api-key']`) override the default verbatim.\n *\n * `DISPLAY` is blanked for the same reason as the claude login: the image bakes\n * `DISPLAY=:1` (a VNC X server) and codex must not try to open a browser there.\n */\nexport function buildCodexLoginRunArgv(opts: {\n volume: string;\n image: string;\n extraArgs: string[];\n}): string[] {\n const term = process.env['TERM'] ?? 'xterm-256color';\n const loginArgs = opts.extraArgs.length > 0 ? opts.extraArgs : ['--device-auth'];\n return [\n 'run',\n '-it',\n '--rm',\n '-e',\n `TERM=${term}`,\n '-e',\n 'DISPLAY=',\n '-v',\n `${opts.volume}:${CONTAINER_CODEX_DIR}`,\n '--user',\n CONTAINER_USER,\n opts.image,\n 'codex',\n 'login',\n ...loginArgs,\n ];\n}\n\n/**\n * Run an interactive docker argv (from {@link buildCodexLoginRunArgv}) with the\n * user's terminal attached. Returns the exit code; a null status is reported\n * as 1.\n */\nexport function runInteractiveCodexLogin(dockerArgv: string[]): { exitCode: number } {\n const child = spawnSync('docker', dockerArgv, { stdio: 'inherit' });\n return { exitCode: child.status ?? 1 };\n}\n\n/**\n * True when the codex-config volume already holds an `auth.json`. Used to skip\n * the first-run sign-in offer when an earlier box / `agentbox codex login`\n * already authenticated.\n */\nexport async function volumeHasCodexAuth(volume: string, image: string): Promise<boolean> {\n const res = await execa(\n 'docker',\n ['run', '--rm', '-v', `${volume}:/dst`, image, 'sh', '-c', 'test -e /dst/auth.json'],\n { reject: false },\n );\n return res.exitCode === 0;\n}\n\nexport interface CodexSessionInfo {\n running: boolean;\n sessionName: string;\n /** ISO-8601 timestamp from tmux's `#{session_created}`, or null when not running. */\n startedAt: string | null;\n}\n\n/**\n * Best-effort: returns `{ running: false, …, startedAt: null }` for any\n * non-zero exit from `tmux has-session`. Mirrors `claudeSessionInfo`.\n */\nexport async function codexSessionInfo(\n container: string,\n sessionName?: string,\n): Promise<CodexSessionInfo> {\n const name = sessionName ?? DEFAULT_CODEX_SESSION;\n const has = await execa(\n 'docker',\n ['exec', '--user', CONTAINER_USER, container, 'tmux', 'has-session', '-t', name],\n { reject: false },\n );\n if (has.exitCode !== 0) {\n return { running: false, sessionName: name, startedAt: null };\n }\n const ts = await execa(\n 'docker',\n [\n 'exec',\n '--user',\n CONTAINER_USER,\n container,\n 'tmux',\n 'display-message',\n '-p',\n '-t',\n name,\n '#{session_created}',\n ],\n { reject: false },\n );\n let startedAt: string | null = null;\n if (ts.exitCode === 0) {\n const secs = Number.parseInt((ts.stdout ?? '').trim(), 10);\n if (Number.isFinite(secs) && secs > 0) startedAt = new Date(secs * 1000).toISOString();\n }\n return { running: true, sessionName: name, startedAt };\n}\n\nexport interface PullCodexResult {\n /** Volume items copied to the host (or, in dry-run, that would be copied). */\n newItems: string[];\n}\n\nexport interface PullCodexOptions {\n /** Image for the throwaway helper container; use the box's image. */\n image: string;\n /** When true, compute the delta but write nothing. */\n dryRun?: boolean;\n}\n\n/** Top-level codex-config items `download codex` considers. */\nconst CODEX_PULL_ITEMS = ['config.toml', 'auth.json', 'prompts'] as const;\n\n/**\n * Reverse of {@link ensureCodexVolume}: pull box-side codex config/auth from\n * the codex-config volume back to the host's `~/.codex`. Additive only — an\n * item already present on the host is never overwritten. The box need not be\n * running (we read the *volume* via a throwaway helper container).\n */\nexport async function pullCodexConfig(\n spec: CodexConfigSpec,\n opts: PullCodexOptions,\n): Promise<PullCodexResult> {\n const hostCodex = join(homedir(), '.codex');\n\n const inv = await execa(\n 'docker',\n [\n 'run',\n '--rm',\n '--user',\n '0',\n '-v',\n `${spec.volume}:/src:ro`,\n opts.image,\n 'sh',\n '-c',\n `for f in ${CODEX_PULL_ITEMS.join(' ')}; do [ -e \"/src/$f\" ] && echo \"$f\"; done; true`,\n ],\n { reject: false },\n );\n if (inv.exitCode !== 0) {\n throw new CodexSessionError(\n `failed to read codex-config volume ${spec.volume}: ${(inv.stderr ?? '').toString().trim() || `exit ${String(inv.exitCode)}`}`,\n );\n }\n\n const present = new Set(\n (inv.stdout ?? '')\n .split('\\n')\n .map((s) => s.trim())\n .filter(Boolean),\n );\n const newItems: string[] = [];\n for (const item of CODEX_PULL_ITEMS) {\n if (!present.has(item)) continue;\n if (await pathExists(join(hostCodex, item))) continue; // additive — never overwrite\n newItems.push(item);\n }\n\n if (opts.dryRun || newItems.length === 0) return { newItems };\n\n // Copy each new item from the volume into the host ~/.codex. `--user 0` so\n // root can read uid-1000 files; chown the result back to the host user so\n // the host's own `codex` can read/write a freshly created ~/.codex.\n const uid = process.getuid?.() ?? 0;\n const gid = process.getgid?.() ?? 0;\n const cmds = newItems.map((it) => `cp -a '/src/${it}' '/dst/${it}'`);\n const apply = await execa(\n 'docker',\n [\n 'run',\n '--rm',\n '--user',\n '0',\n '-v',\n `${spec.volume}:/src:ro`,\n '-v',\n `${hostCodex}:/dst`,\n opts.image,\n 'sh',\n '-c',\n `mkdir -p /dst && ${cmds.join(' && ')} && chown -R ${String(uid)}:${String(gid)} /dst`,\n ],\n { reject: false },\n );\n if (apply.exitCode !== 0) {\n throw new CodexSessionError(\n `failed to copy codex config from ${spec.volume}: ${(apply.stderr ?? '').toString().trim() || `exit ${String(apply.exitCode)}`}`,\n );\n }\n return { newItems };\n}\n","import { spawnSync } from 'node:child_process';\nimport { stat } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { execa } from 'execa';\nimport { buildTmuxSessionArgs, CONTAINER_USER } from './claude.js';\nimport { ensureVolume, volumeExists } from './docker.js';\n\n/**\n * OpenCode support mirrors the Codex support in `codex.ts`. The one structural\n * difference: OpenCode splits its state across two XDG directories —\n * `~/.config/opencode` (config) and `~/.local/share/opencode` (data + the\n * `auth.json` provider credentials). A single volume holds both: it is mounted\n * at the data dir, and the config dir is relocated into a `config/` subdir of\n * that same volume via the `OPENCODE_CONFIG_DIR` env var (OpenCode-specific, so\n * safe to set box-global — unlike `XDG_DATA_HOME`).\n */\nexport const SHARED_OPENCODE_VOLUME = 'agentbox-opencode-config';\nexport const DEFAULT_OPENCODE_SESSION = 'opencode';\n/** Volume mount point inside the box — OpenCode's native data dir. */\nconst CONTAINER_OPENCODE_DIR = '/home/vscode/.local/share/opencode';\n/** Relocated config dir (a subdir of the volume); the value of `OPENCODE_CONFIG_DIR`. */\nconst CONTAINER_OPENCODE_CONFIG_DIR = '/home/vscode/.local/share/opencode/config';\n\nexport interface OpencodeConfigSpec {\n /** Resolved Docker volume name mounted at the OpenCode data dir. */\n volume: string;\n}\n\nexport function resolveOpencodeVolume(opts: {\n isolate: boolean;\n boxId: string;\n}): OpencodeConfigSpec {\n if (opts.isolate) {\n return { volume: `${SHARED_OPENCODE_VOLUME}-${opts.boxId}` };\n }\n return { volume: SHARED_OPENCODE_VOLUME };\n}\n\nasync function pathExists(p: string): Promise<boolean> {\n try {\n await stat(p);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Single-quote a token for /bin/sh. Conservative: anything outside the safe\n * alphabet gets wrapped. Mirrors the helper in codex.ts.\n */\nfunction shQuote(arg: string): string {\n if (arg.length === 0) return `''`;\n if (/^[A-Za-z0-9_\\-./=:@%+,]+$/.test(arg)) return arg;\n return `'${arg.replace(/'/g, `'\\\\''`)}'`;\n}\n\nexport interface EnsureOpencodeVolumeOptions {\n /**\n * When true and the host has OpenCode dirs, rsync host -> volume on every\n * call. Additive (no `--delete`): host files win on overlap, box-only files\n * (e.g. an `auth.json` written by an in-box `opencode auth login`) are kept.\n */\n syncFromHost: boolean;\n /** Image used by the throwaway sync helper container (the box image). */\n image: string;\n}\n\nexport interface EnsureOpencodeVolumeResult {\n /** True only the very first time the volume is created (on this host). */\n created: boolean;\n /** True when the rsync helper ran (syncFromHost was true AND a host dir existed). */\n synced: boolean;\n}\n\n/**\n * Ensure the opencode-config volume exists, then (when {@link\n * EnsureOpencodeVolumeOptions.syncFromHost} is true and the host has OpenCode\n * state) rsync host -> volume via a throwaway helper container. The host is\n * authoritative — same model as the claude/codex volumes.\n *\n * Two host sources land in the one volume: `~/.local/share/opencode` -> volume\n * root (the data dir, holds `auth.json`), and `~/.config/opencode` -> volume\n * `config/` (the relocated config dir). The data sync excludes the SQLite\n * session storage / logs (`storage`, `log`, `project`, `cache`, `bin`) — large,\n * box-irrelevant, and host binaries don't run on linux.\n *\n * When there is nothing to sync the volume root is still `chown`ed to uid 1000\n * so a throwaway `opencode auth login` container can write into it.\n */\nexport async function ensureOpencodeVolume(\n spec: OpencodeConfigSpec,\n opts: EnsureOpencodeVolumeOptions,\n): Promise<EnsureOpencodeVolumeResult> {\n const existed = await volumeExists(spec.volume);\n await ensureVolume(spec.volume);\n const created = !existed;\n\n const hostData = join(homedir(), '.local', 'share', 'opencode');\n const hostConfig = join(homedir(), '.config', 'opencode');\n const hasData = await pathExists(hostData);\n const hasConfig = await pathExists(hostConfig);\n const willSync = opts.syncFromHost && (hasData || hasConfig);\n\n if (willSync) {\n const args = ['run', '--rm', '--user', '0', '-v', `${spec.volume}:/dst`];\n if (hasData) args.push('-v', `${hostData}:/src-data:ro`);\n if (hasConfig) args.push('-v', `${hostConfig}:/src-config:ro`);\n const steps: string[] = [];\n if (hasData) {\n // Exclude the SQLite session store (`opencode.db*`), logs, cloned repos\n // and host binaries — large / box-irrelevant. `auth.json` (when present)\n // and small json carry over.\n steps.push(\n 'rsync -a --exclude=storage --exclude=log --exclude=project --exclude=cache' +\n ' --exclude=bin --exclude=repos --exclude=config' +\n ' --exclude=opencode.db --exclude=opencode.db-shm --exclude=opencode.db-wal' +\n ' /src-data/ /dst/',\n );\n }\n if (hasConfig) {\n steps.push('mkdir -p /dst/config && rsync -a /src-config/ /dst/config/');\n }\n steps.push('chown -R 1000:1000 /dst');\n args.push(opts.image, 'sh', '-c', steps.join(' && '));\n await execa('docker', args);\n return { created, synced: true };\n }\n\n // No host OpenCode state to sync — still make the (possibly freshly created,\n // root-owned) volume root writable by the in-box `vscode` user.\n await execa(\n 'docker',\n [\n 'run',\n '--rm',\n '--user',\n '0',\n '-v',\n `${spec.volume}:/dst`,\n opts.image,\n 'sh',\n '-c',\n 'chown 1000:1000 /dst',\n ],\n { reject: false },\n );\n return { created, synced: false };\n}\n\nexport interface OpencodeMountResult {\n /** Docker -v spec strings to append to runBox(extraVolumes). */\n extraVolumes: string[];\n /**\n * Env vars for the container: the fixed `OPENCODE_CONFIG_DIR` (relocates the\n * config dir into the volume) plus any forwarded provider keys set on the host.\n */\n env: Record<string, string>;\n volumeName: string;\n}\n\n// Provider API keys forwarded from the host's `process.env` into the box.\n// OpenCode's primary auth is the synced `auth.json`; these are a fallback /\n// supplement (OpenCode \"loads keys from environment variables at startup\").\n// Both Google key names are forwarded: `opencode auth list` recognizes\n// `GEMINI_API_KEY`, but the underlying provider SDK reads\n// `GOOGLE_GENERATIVE_AI_API_KEY` at request time.\nexport const OPENCODE_FORWARDED_ENV_KEYS = [\n 'ANTHROPIC_API_KEY',\n 'OPENAI_API_KEY',\n 'OPENROUTER_API_KEY',\n 'GEMINI_API_KEY',\n 'GOOGLE_GENERATIVE_AI_API_KEY',\n 'GOOGLE_API_KEY',\n 'GROQ_API_KEY',\n] as const;\n\nexport function buildOpencodeMounts(\n spec: OpencodeConfigSpec,\n hostEnv: NodeJS.ProcessEnv,\n): OpencodeMountResult {\n // OPENCODE_CONFIG_DIR is a fixed box-internal path (relocates the config dir\n // into the volume). It is OpenCode-specific, so setting it box-global is\n // safe — unlike XDG_DATA_HOME, which would move every app's data dir.\n const env: Record<string, string> = { OPENCODE_CONFIG_DIR: CONTAINER_OPENCODE_CONFIG_DIR };\n for (const k of OPENCODE_FORWARDED_ENV_KEYS) {\n const v = hostEnv[k];\n if (typeof v === 'string' && v.length > 0) env[k] = v;\n }\n return {\n extraVolumes: [`${spec.volume}:${CONTAINER_OPENCODE_DIR}`],\n env,\n volumeName: spec.volume,\n };\n}\n\nexport class OpencodeSessionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'OpencodeSessionError';\n }\n}\n\nexport interface EnsureOpencodeInstalledResult {\n /**\n * True when opencode had to be installed just now — i.e. it was absent from\n * the box image. Happens for boxes created from a checkpoint image captured\n * before OpenCode support, or from an older base image.\n */\n installed: boolean;\n}\n\n/**\n * Make sure the `opencode` binary is on PATH inside the box. OpenCode is baked\n * into the current base image, but a box created from a checkpoint image (a\n * frozen snapshot) captured before OpenCode support — or from an older base\n * image — won't have it. In that case we `npm install -g opencode-ai` into the\n * box's writable layer (persists across stop/start, wiped on destroy), mirroring\n * how `--with-playwright` installs at create time.\n *\n * Fast no-op (one `command -v`) when opencode is already present. Throws\n * {@link OpencodeSessionError} when opencode is absent *and* the install fails.\n */\nexport async function ensureOpencodeInstalled(\n container: string,\n opts: { onProgress?: (line: string) => void } = {},\n): Promise<EnsureOpencodeInstalledResult> {\n const probe = await execa(\n 'docker',\n ['exec', '--user', CONTAINER_USER, container, 'sh', '-c', 'command -v opencode'],\n { reject: false },\n );\n if (probe.exitCode === 0) return { installed: false };\n\n opts.onProgress?.('installing opencode (absent from this box image)');\n const install = await execa(\n 'docker',\n ['exec', '--user', 'root', container, 'bash', '-lc', 'npm install -g opencode-ai 2>&1'],\n { reject: false },\n );\n if (install.exitCode !== 0) {\n throw new OpencodeSessionError(\n `opencode is not in this box's image and \\`npm install -g opencode-ai\\` failed ` +\n `(exit ${String(install.exitCode)}). This box was likely created from a ` +\n `checkpoint captured before OpenCode support — recapture the project checkpoint ` +\n `from a fresh box. Install output:\\n${(install.stdout ?? '').toString().slice(-600)}`,\n );\n }\n return { installed: true };\n}\n\nexport interface StartOpencodeSessionOptions {\n container: string;\n opencodeArgs: string[];\n sessionName?: string;\n}\n\n/**\n * Start a detached tmux session running the OpenCode TUI inside the container.\n * Survives client disconnects; reattach via {@link buildOpencodeAttachArgv}. The\n * shared {@link buildTmuxSessionArgs} remaps the prefix and hides the inner\n * status bar, exactly as for the claude/codex sessions.\n *\n * `OPENCODE_CONFIG_DIR` is already in the container env (set at `docker run -e`\n * by {@link buildOpencodeMounts}), so `docker exec` inherits it — only the\n * host-forwarded provider keys are re-passed here to pick up the host shell's\n * current values.\n */\nexport async function startOpencodeSession(opts: StartOpencodeSessionOptions): Promise<void> {\n const sessionName = opts.sessionName ?? DEFAULT_OPENCODE_SESSION;\n const cmd = ['opencode', ...opts.opencodeArgs].map(shQuote).join(' ');\n const term = process.env['TERM'] ?? 'xterm-256color';\n const envFlags: string[] = ['-e', `TERM=${term}`];\n for (const k of OPENCODE_FORWARDED_ENV_KEYS) {\n const v = process.env[k];\n if (typeof v === 'string' && v.length > 0) envFlags.push('-e', `${k}=${v}`);\n }\n const result = await execa(\n 'docker',\n [\n 'exec',\n ...envFlags,\n '--user',\n CONTAINER_USER,\n opts.container,\n 'tmux',\n 'new-session',\n '-d',\n '-s',\n sessionName,\n cmd,\n ...buildTmuxSessionArgs(sessionName),\n ],\n { reject: false },\n );\n if (result.exitCode === 0) return;\n const stderr = (result.stderr ?? '').toString();\n if (result.exitCode === 127 || /command not found|tmux: not found/i.test(stderr)) {\n throw new OpencodeSessionError(\n `tmux is missing from the box image. Rebuild with: docker rmi agentbox/box:dev && retry.`,\n );\n }\n if (/opencode.*not found|exec: \"opencode\"/i.test(stderr)) {\n throw new OpencodeSessionError(\n `opencode is missing from the box image. Rebuild with: docker rmi agentbox/box:dev && retry.`,\n );\n }\n if (/duplicate session/i.test(stderr)) {\n throw new OpencodeSessionError(\n `a tmux session \"${sessionName}\" already exists in ${opts.container}; use \\`agentbox opencode attach\\` to reattach.`,\n );\n }\n throw new OpencodeSessionError(\n `failed to start opencode session in ${opts.container}: ${stderr.trim() || `exit ${String(result.exitCode)}`}`,\n );\n}\n\n/**\n * The `docker` argv that attaches an interactive terminal to a box's OpenCode\n * tmux session. Mirrors {@link import('./codex.js').buildCodexAttachArgv}.\n */\nexport function buildOpencodeAttachArgv(container: string, sessionName?: string): string[] {\n const name = sessionName ?? DEFAULT_OPENCODE_SESSION;\n const term = process.env['TERM'] ?? 'xterm-256color';\n return [\n 'exec',\n '-it',\n '-e',\n `TERM=${term}`,\n '--user',\n CONTAINER_USER,\n container,\n 'tmux',\n 'attach',\n '-t',\n name,\n ];\n}\n\n/**\n * The `docker run` argv for an interactive `opencode auth login` in a throwaway\n * container. Mounts the opencode-config volume so the written `auth.json`\n * persists, and sets `OPENCODE_CONFIG_DIR` so config writes land in the volume\n * too. `opencode auth login` is interactive (provider picker); `extraArgs` are\n * appended verbatim (e.g. `['--provider', 'anthropic']`).\n *\n * `DISPLAY` is blanked for the same reason as the claude/codex login: the image\n * bakes `DISPLAY=:1` (a VNC X server) and opencode must not try to open a\n * browser there — forcing the terminal URL/paste-code flow.\n */\nexport function buildOpencodeLoginRunArgv(opts: {\n volume: string;\n image: string;\n extraArgs: string[];\n}): string[] {\n const term = process.env['TERM'] ?? 'xterm-256color';\n return [\n 'run',\n '-it',\n '--rm',\n '-e',\n `TERM=${term}`,\n '-e',\n 'DISPLAY=',\n '-e',\n `OPENCODE_CONFIG_DIR=${CONTAINER_OPENCODE_CONFIG_DIR}`,\n '-v',\n `${opts.volume}:${CONTAINER_OPENCODE_DIR}`,\n '--user',\n CONTAINER_USER,\n opts.image,\n 'opencode',\n 'auth',\n 'login',\n ...opts.extraArgs,\n ];\n}\n\n/**\n * Run an interactive docker argv (from {@link buildOpencodeLoginRunArgv}) with\n * the user's terminal attached. Returns the exit code; a null status is\n * reported as 1.\n */\nexport function runInteractiveOpencodeLogin(dockerArgv: string[]): { exitCode: number } {\n const child = spawnSync('docker', dockerArgv, { stdio: 'inherit' });\n return { exitCode: child.status ?? 1 };\n}\n\n/**\n * True when the opencode-config volume already holds an `auth.json` (at the\n * data-dir root). Used to skip the first-run sign-in offer when an earlier box\n * / `agentbox opencode login` already authenticated.\n */\nexport async function volumeHasOpencodeAuth(volume: string, image: string): Promise<boolean> {\n const res = await execa(\n 'docker',\n ['run', '--rm', '-v', `${volume}:/dst`, image, 'sh', '-c', 'test -e /dst/auth.json'],\n { reject: false },\n );\n return res.exitCode === 0;\n}\n\nexport interface OpencodeSessionInfo {\n running: boolean;\n sessionName: string;\n /** ISO-8601 timestamp from tmux's `#{session_created}`, or null when not running. */\n startedAt: string | null;\n}\n\n/**\n * Best-effort: returns `{ running: false, …, startedAt: null }` for any\n * non-zero exit from `tmux has-session`. Mirrors `codexSessionInfo`.\n */\nexport async function opencodeSessionInfo(\n container: string,\n sessionName?: string,\n): Promise<OpencodeSessionInfo> {\n const name = sessionName ?? DEFAULT_OPENCODE_SESSION;\n const has = await execa(\n 'docker',\n ['exec', '--user', CONTAINER_USER, container, 'tmux', 'has-session', '-t', name],\n { reject: false },\n );\n if (has.exitCode !== 0) {\n return { running: false, sessionName: name, startedAt: null };\n }\n const ts = await execa(\n 'docker',\n [\n 'exec',\n '--user',\n CONTAINER_USER,\n container,\n 'tmux',\n 'display-message',\n '-p',\n '-t',\n name,\n '#{session_created}',\n ],\n { reject: false },\n );\n let startedAt: string | null = null;\n if (ts.exitCode === 0) {\n const secs = Number.parseInt((ts.stdout ?? '').trim(), 10);\n if (Number.isFinite(secs) && secs > 0) startedAt = new Date(secs * 1000).toISOString();\n }\n return { running: true, sessionName: name, startedAt };\n}\n\nexport interface PullOpencodeResult {\n /** Volume items copied to the host (or, in dry-run, that would be copied). */\n newItems: string[];\n}\n\nexport interface PullOpencodeOptions {\n /** Image for the throwaway helper container; use the box's image. */\n image: string;\n /** When true, compute the delta but write nothing. */\n dryRun?: boolean;\n}\n\n/** Data-dir items (volume root -> host ~/.local/share/opencode). */\nconst OPENCODE_PULL_DATA_ITEMS = ['auth.json'] as const;\n/**\n * Config-dir items (volume `config/` -> host ~/.config/opencode). Covers both\n * the `.json` and `.jsonc` global config and OpenCode's user-extension subdirs.\n */\nconst OPENCODE_PULL_CONFIG_ITEMS = [\n 'opencode.json',\n 'opencode.jsonc',\n 'agents',\n 'commands',\n 'modes',\n 'plugins',\n 'skills',\n 'tools',\n 'themes',\n] as const;\n\n/**\n * Reverse of {@link ensureOpencodeVolume}: pull box-side OpenCode config/auth\n * from the volume back to the host. Additive only — an item already present on\n * the host is never overwritten. The box need not be running (we read the\n * *volume* via a throwaway helper container). `auth.json` lands in the host's\n * `~/.local/share/opencode`; config items in `~/.config/opencode`.\n */\nexport async function pullOpencodeConfig(\n spec: OpencodeConfigSpec,\n opts: PullOpencodeOptions,\n): Promise<PullOpencodeResult> {\n const hostData = join(homedir(), '.local', 'share', 'opencode');\n const hostConfig = join(homedir(), '.config', 'opencode');\n\n // Inventory: data items at the volume root, config items under `config/`.\n const inv = await execa(\n 'docker',\n [\n 'run',\n '--rm',\n '--user',\n '0',\n '-v',\n `${spec.volume}:/src:ro`,\n opts.image,\n 'sh',\n '-c',\n `for f in ${OPENCODE_PULL_DATA_ITEMS.join(' ')}; do [ -e \"/src/$f\" ] && echo \"data $f\"; done;` +\n ` for f in ${OPENCODE_PULL_CONFIG_ITEMS.join(' ')}; do [ -e \"/src/config/$f\" ] && echo \"config $f\"; done;` +\n ' true',\n ],\n { reject: false },\n );\n if (inv.exitCode !== 0) {\n throw new OpencodeSessionError(\n `failed to read opencode-config volume ${spec.volume}: ${(inv.stderr ?? '').toString().trim() || `exit ${String(inv.exitCode)}`}`,\n );\n }\n\n // Volume items not already on the host (additive — never overwrite).\n const newItems: Array<{ label: string; src: string; hostDst: 'data' | 'config'; name: string }> =\n [];\n for (const line of (inv.stdout ?? '').split('\\n')) {\n const [group, name] = line.trim().split(/\\s+/, 2);\n if (!name || (group !== 'data' && group !== 'config')) continue;\n const hostBase = group === 'data' ? hostData : hostConfig;\n if (await pathExists(join(hostBase, name))) continue;\n newItems.push({\n label: group === 'data' ? name : `config/${name}`,\n src: group === 'data' ? `/src/${name}` : `/src/config/${name}`,\n hostDst: group,\n name,\n });\n }\n\n if (opts.dryRun || newItems.length === 0) {\n return { newItems: newItems.map((i) => i.label) };\n }\n\n // Copy each new item from the volume into the matching host dir. `--user 0`\n // so root can read uid-1000 files; chown the result back to the host user.\n const uid = process.getuid?.() ?? 0;\n const gid = process.getgid?.() ?? 0;\n const cmds = newItems.map(\n (i) => `cp -a '${i.src}' '${i.hostDst === 'data' ? '/dst-data' : '/dst-config'}/${i.name}'`,\n );\n const apply = await execa(\n 'docker',\n [\n 'run',\n '--rm',\n '--user',\n '0',\n '-v',\n `${spec.volume}:/src:ro`,\n '-v',\n `${hostData}:/dst-data`,\n '-v',\n `${hostConfig}:/dst-config`,\n opts.image,\n 'sh',\n '-c',\n `mkdir -p /dst-data /dst-config && ${cmds.join(' && ')}` +\n ` && chown -R ${String(uid)}:${String(gid)} /dst-data /dst-config`,\n ],\n { reject: false },\n );\n if (apply.exitCode !== 0) {\n throw new OpencodeSessionError(\n `failed to copy opencode config from ${spec.volume}: ${(apply.stderr ?? '').toString().trim() || `exit ${String(apply.exitCode)}`}`,\n );\n }\n return { newItems: newItems.map((i) => i.label) };\n}\n","import { execInBox } from './docker.js';\n\nexport interface DockerdLaunchResult {\n up: boolean;\n reason?: string;\n}\n\n/**\n * Shared image-cache volume across boxes. When a box is created with\n * `dockerCacheShared=true`, its in-box dockerd's data root is this volume\n * instead of the per-box `agentbox-docker-<id>` volume. Mutually exclusive at\n * runtime: only one box can hold the lock on `/var/lib/docker` at a time —\n * dockerd's own boltdb lock will refuse a second start. This is a power-user\n * feature for users who run boxes serially and want pulled layers to persist\n * across recreations; documented as such on the CLI flag.\n */\nexport const SHARED_DOCKER_CACHE_VOLUME = 'agentbox-docker-cache';\n\nexport function dockerVolumeName(boxId: string, shared: boolean): string {\n return shared ? SHARED_DOCKER_CACHE_VOLUME : `agentbox-docker-${boxId}`;\n}\n\n/**\n * Spawn the in-container dockerd via `/usr/local/bin/agentbox-dockerd-start`\n * detached, then poll for `/var/run/docker.sock` to become accept()-able.\n * Best-effort, mirroring {@link launchVncDaemon} — failure is logged but\n * doesn't fail box creation. Default timeout 30s: first start has to\n * initialize iptables + the storage graphdriver, which is slower than the\n * VNC stack.\n */\nexport async function launchDockerdDaemon(\n container: string,\n timeoutMs = 30_000,\n): Promise<DockerdLaunchResult> {\n const result = await execInBox(container, ['/usr/local/bin/agentbox-dockerd-start'], {\n user: 'root',\n detach: true,\n });\n if (result.exitCode !== 0) {\n return { up: false, reason: `docker exec failed: ${result.stderr || result.stdout}` };\n }\n\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n const probe = await execInBox(\n container,\n [\n 'bash',\n '-lc',\n '[ -S /var/run/docker.sock ] && docker -H unix:///var/run/docker.sock info >/dev/null 2>&1',\n ],\n { user: 'root' },\n );\n if (probe.exitCode === 0) return { up: true };\n await new Promise((r) => setTimeout(r, 200));\n }\n return { up: false, reason: `dockerd did not become ready within ${String(timeoutMs)}ms` };\n}\n","import { randomBytes } from 'node:crypto';\nimport { execInBox } from './docker.js';\n\nexport interface VncLaunchResult {\n up: boolean;\n reason?: string;\n}\n\n/**\n * Spawn the in-container VNC supervisor (`/usr/local/bin/agentbox-vnc-start`)\n * detached, then poll the container's TCP 6080 to confirm websockify is up.\n * Best-effort, mirroring {@link launchCtlDaemon} — failure is logged but\n * doesn't fail box creation. The password reaches the script through the\n * container's AGENTBOX_VNC_PASSWORD env, set at `docker run` time, so we don't\n * need `-e` on the exec (and the re-launch path on `agentbox start` works\n * without it too).\n */\nexport async function launchVncDaemon(\n container: string,\n timeoutMs = 5000,\n): Promise<VncLaunchResult> {\n const result = await execInBox(container, ['/usr/local/bin/agentbox-vnc-start'], {\n user: 'vscode',\n detach: true,\n });\n if (result.exitCode !== 0) {\n return { up: false, reason: `docker exec failed: ${result.stderr || result.stdout}` };\n }\n\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n const probe = await execInBox(\n container,\n ['bash', '-lc', '(echo > /dev/tcp/127.0.0.1/6080) 2>/dev/null'],\n { user: 'vscode' },\n );\n if (probe.exitCode === 0) return { up: true };\n await new Promise((r) => setTimeout(r, 150));\n }\n return { up: false, reason: `websockify did not bind 6080 within ${String(timeoutMs)}ms` };\n}\n\nconst VNC_PASSWORD_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';\n\n/**\n * 8-char password from a 62-symbol alphabet. The 8-char cap is a real RFB\n * protocol limit — VncAuth truncates at compare time, so longer passwords give\n * no security gain. 62^8 ≈ 47 bits; adequate for the loopback-bound surface\n * we expose (host port pinned to 127.0.0.1 + OrbStack's name-based routing,\n * neither of which is reachable from off-host without explicit tunnelling).\n */\nexport function generateVncPassword(): string {\n const bytes = randomBytes(8);\n let out = '';\n for (let i = 0; i < 8; i++) {\n out += VNC_PASSWORD_ALPHABET[bytes[i]! % VNC_PASSWORD_ALPHABET.length];\n }\n return out;\n}\n\n/**\n * Container port the VNC web client (noVNC) binds inside the box. Fixed today;\n * stored on BoxRecord for future-proofing if we ever support multiple displays.\n */\nexport const VNC_CONTAINER_PORT = 6080;\n\nexport interface VncUrls {\n /** OrbStack name-based URL, e.g. http://agentbox-foo.orb.local:6080/... Present only on OrbStack hosts. */\n orbUrl?: string;\n /** Loopback URL via the auto-allocated host port, e.g. http://127.0.0.1:54321/... Present whenever vncHostPort is known. */\n loopbackUrl?: string;\n}\n\n/**\n * Build the noVNC URLs for a box, given the box record + (host engine).\n * `engine === 'orbstack'` triggers the `<container>.orb.local:6080` route;\n * either engine produces the loopback URL when the host port is resolved.\n * Returns an empty object when VNC isn't enabled or the password isn't known.\n */\nexport function buildVncUrls(\n record: {\n container?: string;\n vncEnabled?: boolean;\n vncHostPort?: number;\n vncContainerPort?: number;\n vncPassword?: string;\n },\n engine: 'orbstack' | 'docker-desktop' | 'other',\n): VncUrls {\n if (!record.vncEnabled || !record.vncPassword) return {};\n const containerPort = record.vncContainerPort ?? VNC_CONTAINER_PORT;\n const qs = `autoconnect=1&password=${encodeURIComponent(record.vncPassword)}`;\n const urls: VncUrls = {};\n if (engine === 'orbstack' && record.container) {\n urls.orbUrl = `http://${record.container}.orb.local:${String(containerPort)}/vnc.html?${qs}`;\n }\n if (record.vncHostPort) {\n urls.loopbackUrl = `http://127.0.0.1:${String(record.vncHostPort)}/vnc.html?${qs}`;\n }\n return urls;\n}\n","/**\n * The single container port AgentBox reserves + publishes for a box's web\n * service. The `-p 127.0.0.1:0:80` mapping is created unconditionally at\n * `create` (immutable after `docker run`); the in-box supervisor forwards :80\n * to the `expose:`-flagged service's port once `agentbox.yaml` is set. Mirrors\n * `VNC_CONTAINER_PORT`. Must equal `RESERVED_WEB_PORT` in @agentbox/ctl.\n */\nexport const WEB_CONTAINER_PORT = 80;\n","// Host-side git-repo detection is provider-neutral (cloud boxes also seed from\n// git) and lives in @agentbox/sandbox-core. Re-exported here so existing\n// `@agentbox/sandbox-docker` consumers are unchanged.\nexport {\n detectGitRepos,\n GitWorktreeError,\n pickFreshBranch,\n type DetectedGitRepo,\n} from '@agentbox/sandbox-core';\n","import { execa } from 'execa';\nimport { execInBox } from './docker.js';\nimport type { DetectedGitRepo } from './git-worktree.js';\nimport { GitWorktreeError } from './git-worktree.js';\n\n/**\n * Root for per-box git-worktree directories inside the container. Each box\n * registers its worktree at a unique subpath here so the host main repo's\n * worktree registry can list multiple concurrent boxes without path\n * collision; `/workspace` is then a symlink to the per-box dir. Lives under\n * the vscode user's home so it's writable without sudo. Exported for the\n * fs-safe path helper.\n */\nexport const WORKTREE_ROOT = '/home/vscode/.agentbox-worktrees';\n\n/** Sanitize a branch name into an FS-safe path segment. */\nexport function fsSafeBranch(branch: string): string {\n return branch.replace(/[^A-Za-z0-9._-]+/g, '_');\n}\n\n/**\n * Per-box per-repo path at which `git worktree add` registers the worktree\n * inside the container. The agent's working tree path stays `/workspace`\n * (root) / `/workspace/<sub>` (nested) via symlinks created after the add.\n * Unique because the branch name carries the box name, so the host main\n * repo never sees a path collision when multiple boxes from the same\n * project run concurrently.\n */\nexport function gitWorktreePathFor(branch: string): string {\n return `${WORKTREE_ROOT}/${fsSafeBranch(branch)}`;\n}\n\n/**\n * Per-repo carry-over captured on the host before the container starts. The\n * host runs the `git stash create` + `ls-files --others` here against the\n * user's main checkout, then `seedWorkspace` replays both inside the box.\n *\n * `stashSha` and the untracked tarball are stored in the shared `.git/`\n * object database (stash) and as a buffer (untracked) so the container can\n * apply them after `git worktree add` runs against the bind-mounted `.git`.\n */\nexport interface RepoCarryOver {\n repo: DetectedGitRepo;\n /**\n * Agent-visible container path of the worktree (`/workspace` for root,\n * `/workspace/<sub>` for nested). After seedWorkspace runs this is a\n * symlink to `gitWorktreePath`.\n */\n containerPath: string;\n /**\n * Real container path where git registered the worktree\n * (`/home/vscode/.agentbox-worktrees/<fsSafeBranch>`). Per-box unique, so\n * concurrent boxes from the same project don't collide in the host main\n * repo's worktree registry.\n */\n gitWorktreePath: string;\n /** Branch name to pass to `git worktree add -b`. */\n branch: string;\n /** Stash-commit SHA (`git stash create`); null when the host main was clean. */\n stashSha: string | null;\n /**\n * NUL-separated list of repo-relative untracked paths. We tar these up\n * host-side and pipe them in inside seedWorkspace. Empty when no untracked\n * files.\n */\n untrackedNul: string;\n /** Host dir to tar from (== repo.hostMainRepo, kept here so seedWorkspace doesn't need to know about the repo shape). */\n hostSource: string;\n}\n\n/**\n * Collect host-side state for each detected repo so it can be replayed\n * inside the container by `seedWorkspace`. Pure of any docker calls — every\n * shell-out is host git.\n *\n * Branch picking is left to the caller (so it can be allocated before\n * `docker run` and recorded on the BoxRecord regardless of how the rest of\n * create proceeds).\n */\nexport async function collectRepoCarryOver(\n repo: DetectedGitRepo,\n branch: string,\n containerPath: string,\n gitWorktreePath: string,\n): Promise<RepoCarryOver> {\n // `stash create` writes a stash commit without touching the working tree or\n // stash list; empty output = clean. The commit lands in the host's `.git/`\n // object DB, which is bind-mounted into the container — so the in-box\n // worktree can `stash apply <sha>` against it.\n const stash = await execa('git', ['-C', repo.hostMainRepo, 'stash', 'create'], { reject: false });\n const stashSha = stash.exitCode === 0 ? stash.stdout.trim() || null : null;\n\n const untracked = await execa(\n 'git',\n ['-C', repo.hostMainRepo, 'ls-files', '--others', '--exclude-standard', '-z'],\n { reject: false },\n );\n const untrackedNul = untracked.exitCode === 0 ? untracked.stdout : '';\n\n return {\n repo,\n containerPath,\n gitWorktreePath,\n branch,\n stashSha,\n untrackedNul,\n hostSource: repo.hostMainRepo,\n };\n}\n\nexport interface SeedWorkspaceOptions {\n container: string;\n /** Repos with collected carry-over, in DAG order: root first, nested after. */\n repos: RepoCarryOver[];\n onLog?: (line: string) => void;\n}\n\n/**\n * docker exec helper that throws GitWorktreeError on non-zero exit. `cwd`\n * defaults to `/` so callers that intentionally rebind `/workspace` (the\n * image's WORKDIR) don't get hit by \"chdir failed: no such file or directory\"\n * on the next exec.\n */\nasync function dexec(\n container: string,\n argv: string[],\n user: 'vscode' | 'root' = 'vscode',\n cwd: string = '/',\n): Promise<void> {\n const r = await execa(\n 'docker',\n ['exec', '-w', cwd, '--user', user, container, ...argv],\n { reject: false },\n );\n if (r.exitCode !== 0) {\n throw new GitWorktreeError(`${argv.join(' ')} failed: ${r.stderr || r.stdout}`);\n }\n}\n\n/**\n * Minimum shape `bindWorktrees` needs — keeps the helper independent of\n * the `RepoCarryOver` / `GitWorktreeRecord` types so both `seedWorkspace`\n * (after creation) and `startBox` (after `docker start`) can call it.\n */\nexport interface WorktreeBindSpec {\n kind: 'root' | 'nested';\n containerPath: string;\n gitWorktreePath: string;\n}\n\n/**\n * Apply the `/workspace` (and `/workspace/<sub>`) bind mounts that expose\n * each per-box git worktree at its canonical agent path.\n *\n * Idempotent for `startBox` re-runs: if the target is already a mountpoint\n * we unmount it first. Root bind first so the nested mount points (created\n * by the root worktree's `worktree add`) exist before we cover them.\n *\n * The mount runs as `root` because `mount(2)` requires `CAP_SYS_ADMIN` —\n * which we already grant the outer container for the in-box dockerd. The\n * bind itself respects file ownership on the source side (vscode-owned),\n * so subsequent in-container operations under `/workspace` work as vscode.\n */\n/**\n * Make the in-container parent directory of each bind-mounted `.git` owned by\n * `vscode`. Docker auto-creates the intermediate path for a bind mount\n * (e.g. `/Users/marco/Projects/Foo/` for a `.git` at `/Users/marco/Projects/Foo/.git`)\n * in the container's writable layer as `root:root 755`. The bind-mounted\n * `.git` itself keeps its host UIDs, but agents (turborepo, build caches,\n * etc.) often want to write *siblings* of `.git` at the project root —\n * `.turbo/`, `.next/`, scratch files — which fails as `vscode` if the parent\n * is root-owned. This flips just that parent dir's UID.\n *\n * NOT recursive on purpose: `chown -R` would descend into `.git` (the\n * bind-mount inode) and propagate ownership changes back to the host,\n * defeating the \"no host perms touched\" property.\n *\n * Best-effort: failures are logged, not thrown — the box still functions,\n * only sibling writes at the project root are affected.\n */\nexport async function chownGitBindParents(args: {\n container: string;\n hostMainRepos: string[];\n onLog?: (line: string) => void;\n}): Promise<void> {\n const log = args.onLog ?? (() => {});\n // Dedupe — nested-repo carry-overs can repeat hostMainRepo.\n const repos = Array.from(new Set(args.hostMainRepos));\n for (const repo of repos) {\n const result = await execInBox(args.container, ['chown', 'vscode:vscode', repo], {\n user: 'root',\n });\n if (result.exitCode === 0) {\n log(`chowned ${repo} to vscode:vscode (parent of bind-mounted .git)`);\n } else {\n const msg = (result.stderr || result.stdout || `exit ${result.exitCode}`).trim();\n log(`chown ${repo} failed (best-effort, ignoring): ${msg}`);\n }\n }\n}\n\nexport async function bindWorktrees(\n container: string,\n binds: WorktreeBindSpec[],\n onLog?: (line: string) => void,\n): Promise<void> {\n const log = onLog ?? (() => {});\n // Root first; nested mountpoints live inside the root worktree's tree so\n // the root bind has to be in place before we cover sub-paths.\n const ordered = [...binds].sort((a, b) =>\n a.kind === 'root' && b.kind !== 'root' ? -1 : a.kind !== 'root' && b.kind === 'root' ? 1 : 0,\n );\n for (const b of ordered) {\n // Best-effort unmount of any leftover bind at the target (idempotent for\n // startBox: container stop drops mounts, but a partial create might\n // leave one in place).\n await execa(\n 'docker',\n ['exec', '-w', '/', '--user', 'root', container, 'sh', '-c', `mountpoint -q ${b.containerPath} && umount ${b.containerPath} || true`],\n { reject: false },\n );\n // For nested: parent must exist. The root bind exposes the root\n // worktree's tracked tree, which typically contains <sub>/, but if the\n // root .gitignores it or the nested repo is in a fresh path, mkdir is\n // needed.\n if (b.kind === 'nested') {\n await dexec(container, ['mkdir', '-p', ctParent(b.containerPath)], 'root');\n await dexec(container, ['mkdir', '-p', b.containerPath], 'root');\n }\n await dexec(container, ['mount', '--bind', b.gitWorktreePath, b.containerPath], 'root');\n log(`bind-mounted ${b.containerPath} <- ${b.gitWorktreePath}`);\n }\n}\n\n/**\n * Materialize each per-box git worktree *inside* the container against the\n * bind-mounted `.git/`, then replay the host's uncommitted state (stash +\n * untracked) into it. Runs as `vscode` (the in-container user) so files in\n * /workspace are owned by uid 1000.\n *\n * Layout: every worktree is registered at a per-box unique path under\n * `WORKTREE_ROOT` (`/home/vscode/.agentbox-worktrees/<fsSafeBranch>`), then\n * a `mount --bind` exposes it at `/workspace` (and `/workspace/<sub>` for\n * nested repos). The uniqueness is load-bearing — the host main repo's\n * worktree registry is keyed by absolute path, so multiple concurrent\n * boxes from the same project must register at *different* paths. They\n * share the object DB (the bind-mounted `.git/`) but have independent\n * HEAD/index in their own `.git/worktrees/<subdir>`.\n *\n * The bind mount (not a symlink) is intentional: getcwd() / realpath() /\n * git rev-parse --show-toplevel / Node's process.cwd() all return\n * `/workspace`. A symlink would leak the per-box physical path everywhere\n * tools canonicalize. Cost: the mount is per-container-namespace and\n * doesn't survive `docker stop`, so `startBox` re-binds via\n * {@link bindWorktrees}. Host's `git worktree list` will show each box's\n * registered path as `/home/vscode/.agentbox-worktrees/...` (container-only\n * — host marks it `prunable`, which is harmless).\n */\nexport async function seedWorkspace(opts: SeedWorkspaceOptions): Promise<void> {\n const log = opts.onLog ?? (() => {});\n\n // Ensure the per-box worktree root exists. Idempotent — multiple boxes\n // can be created in parallel against the same image.\n await dexec(opts.container, ['mkdir', '-p', WORKTREE_ROOT]);\n\n // Phase 1: register each worktree at its per-box unique path.\n for (const r of opts.repos) {\n const main = r.repo.hostMainRepo;\n const wt = r.gitWorktreePath;\n const add = await execa(\n 'docker',\n [\n 'exec',\n '--user',\n 'vscode',\n opts.container,\n 'git',\n '-C',\n main,\n 'worktree',\n 'add',\n '-b',\n r.branch,\n wt,\n 'HEAD',\n ],\n { reject: false },\n );\n if (add.exitCode !== 0) {\n throw new GitWorktreeError(\n `git worktree add ${wt} (branch ${r.branch}) failed: ${add.stderr || add.stdout}`,\n );\n }\n log(`worktree ${wt} on branch ${r.branch} (host main ${main})`);\n\n // Boxes don't carry the user's signing keys, so commit.gpgsign=true on\n // the host would make every in-box `git commit` fail. Enable per-worktree\n // config on the main repo, then disable signing on just this worktree.\n await execa(\n 'docker',\n [\n 'exec',\n '--user',\n 'vscode',\n opts.container,\n 'git',\n '-C',\n main,\n 'config',\n 'extensions.worktreeConfig',\n 'true',\n ],\n { reject: false },\n );\n await execa(\n 'docker',\n [\n 'exec',\n '--user',\n 'vscode',\n opts.container,\n 'git',\n '-C',\n wt,\n 'config',\n '--worktree',\n 'commit.gpgsign',\n 'false',\n ],\n { reject: false },\n );\n }\n\n // Phase 2: bind each worktree onto its agent-visible /workspace path.\n await bindWorktrees(\n opts.container,\n opts.repos.map((r) => ({\n kind: r.repo.kind,\n containerPath: r.containerPath,\n gitWorktreePath: r.gitWorktreePath,\n })),\n log,\n );\n\n // Phase 3: replay host carry-over into each worktree (via the\n // /workspace[*] symlinks, so the agent sees the changes at the canonical\n // paths it expects).\n for (const r of opts.repos) {\n const ct = r.containerPath;\n if (r.stashSha) {\n const withIndex = await execa(\n 'docker',\n [\n 'exec',\n '--user',\n 'vscode',\n opts.container,\n 'git',\n '-C',\n ct,\n 'stash',\n 'apply',\n '--index',\n r.stashSha,\n ],\n { reject: false },\n );\n if (withIndex.exitCode !== 0) {\n const noIndex = await execa(\n 'docker',\n [\n 'exec',\n '--user',\n 'vscode',\n opts.container,\n 'git',\n '-C',\n ct,\n 'stash',\n 'apply',\n r.stashSha,\n ],\n { reject: false },\n );\n if (noIndex.exitCode !== 0) {\n log(\n `warning: stash apply failed in ${ct} (${withIndex.stderr || withIndex.stdout || 'no message'})`,\n );\n } else {\n log(`applied tracked changes (without index — staged state lost) in ${ct}`);\n }\n } else {\n log(`applied tracked changes from host main into ${ct}`);\n }\n }\n if (r.untrackedNul.length > 0) {\n const tarOut = await execa('tar', ['-C', r.hostSource, '--null', '-T', '-', '-cf', '-'], {\n input: r.untrackedNul.replace(/\\0$/, ''),\n encoding: 'buffer',\n reject: false,\n });\n if (tarOut.exitCode !== 0) {\n log(`warning: tar of untracked files for ${r.repo.hostMainRepo} failed: ${tarOut.stderr}`);\n continue;\n }\n const tarIn = await execa(\n 'docker',\n ['exec', '-i', '--user', 'vscode', opts.container, 'tar', '-C', ct, '-xf', '-'],\n { input: tarOut.stdout as Buffer, reject: false },\n );\n if (tarIn.exitCode !== 0) {\n log(`warning: untracked-file copy into ${ct} failed: ${tarIn.stderr}`);\n } else {\n const count = r.untrackedNul.split('\\0').filter((s) => s.length > 0).length;\n log(`copied ${String(count)} untracked file(s) into ${ct}`);\n }\n }\n }\n}\n\n/**\n * Tar-pipe a host source dir into the container's /workspace. Used for the\n * no-git case (no detected repos), and for the `--host-snapshot` flow where\n * the source is the APFS clone instead of the live workspace.\n *\n * Runs as uid:gid 1000:1000 so extracted files are owned by `vscode` (the\n * in-container user) — same convention as `copyHostEnvFilesToBox`.\n */\nexport async function seedWorkspaceFromDir(opts: {\n container: string;\n hostSource: string;\n onLog?: (line: string) => void;\n}): Promise<void> {\n const log = opts.onLog ?? (() => {});\n const tarOut = await execa('tar', ['-C', opts.hostSource, '-cf', '-', '.'], {\n encoding: 'buffer',\n reject: false,\n });\n if (tarOut.exitCode !== 0) {\n throw new GitWorktreeError(`tar of ${opts.hostSource} failed: ${tarOut.stderr}`);\n }\n const tarIn = await execa(\n 'docker',\n ['exec', '-i', '--user', '1000:1000', opts.container, 'tar', '-C', '/workspace', '-xf', '-'],\n { input: tarOut.stdout as Buffer, reject: false },\n );\n if (tarIn.exitCode !== 0) {\n throw new GitWorktreeError(`tar extract into /workspace failed: ${tarIn.stderr}`);\n }\n log(`seeded /workspace from ${opts.hostSource}`);\n}\n\n/**\n * Remove an in-container worktree from the host's main repo's worktree\n * registry. Called from `destroyBox` per registered worktree. The registered\n * path (`gitWorktreePath`) was a container-only path (under `WORKTREE_ROOT`),\n * so `git worktree remove` will see it as missing and we go straight to\n * `worktree prune` to drop the registry entry. Best-effort throughout.\n */\nexport async function removeInBoxWorktree(args: {\n hostMainRepo: string;\n gitWorktreePath: string;\n}): Promise<void> {\n const remove = await execa(\n 'git',\n ['-C', args.hostMainRepo, 'worktree', 'remove', '--force', args.gitWorktreePath],\n { reject: false },\n );\n if (remove.exitCode === 0) return;\n await execa('git', ['-C', args.hostMainRepo, 'worktree', 'prune'], { reject: false });\n}\n\nfunction ctParent(p: string): string {\n const i = p.lastIndexOf('/');\n return i <= 0 ? '/' : p.slice(0, i);\n}\n","import { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { execa } from 'execa';\n\n/**\n * Portless (https://portless.sh) — a host reverse-proxy that maps\n * `portless alias <name> <port>` to a stable https://<name>.localhost URL.\n *\n * AgentBox uses it to give a box a friendly web URL on the host. Two\n * providers consume these helpers today:\n * - sandbox-docker: aliases the docker port-published web port\n * (`<name>.localhost -> 127.0.0.1:<webHostPort>`). In-box browser\n * remaps `<name>.localhost` to `host.docker.internal` because the box\n * is in a separate net namespace.\n * - sandbox-hetzner: aliases the SSH-forwarded loopback port for the\n * remote VPS's WebProxy. In-box browser remaps to `127.0.0.1` because\n * the box (= VPS) has no separate net namespace.\n *\n * The file lives in sandbox-docker for historical reasons but is shared via\n * a re-export from `@agentbox/sandbox-cloud` (Phase 1 of the hetzner work).\n * The dep direction is `sandbox-cloud → sandbox-docker`, so this is the\n * canonical home; we can't move it the other way without cycle-fixing\n * package.json wiring.\n *\n * Portless is user-installed; AgentBox never bundles, installs, or starts\n * it — every function here is best-effort and must never throw: a Portless\n * failure degrades to the loopback URL.\n */\n\n/**\n * The Portless CLI surface AgentBox depends on. Pinned here so a rename in a\n * future Portless release is a one-line fix. Verified against portless 0.13.0.\n */\nconst PORTLESS_BIN = 'portless';\nconst SUB_VERSION = ['--version'];\nconst SUB_ALIAS = 'alias';\nconst SUB_ALIAS_REMOVE = '--remove';\nconst SUB_GET = 'get';\n\n/**\n * Port AgentBox starts the Portless proxy on when it sets one up itself.\n * A port >= 1024 needs no root (Portless's own documented no-sudo port);\n * combined with `--no-tls` the whole setup runs without a single prompt.\n */\nexport const PORTLESS_PROXY_PORT = 1355;\n\nexport interface PortlessState {\n /** `portless` resolved on PATH and answered `--version`. */\n installed: boolean;\n /** Portless version string, when installed. */\n version?: string;\n /**\n * A live proxy daemon was found. Note `portless alias` writes the route\n * regardless — the proxy only has to be up for the URL to actually resolve.\n * `false` also covers \"could not tell\".\n */\n proxyRunning: boolean;\n}\n\nlet cached: PortlessState | null = null;\n\n/**\n * Probe the host for Portless. Cached per-process like `detectEngine` — the\n * install state cannot change mid-command and the proxy state is only used\n * for a soft hint.\n */\nexport async function detectPortless(): Promise<PortlessState> {\n if (cached !== null) return cached;\n try {\n const ver = await execa(PORTLESS_BIN, SUB_VERSION, { reject: false });\n if (ver.exitCode !== 0) {\n cached = { installed: false, proxyRunning: false };\n return cached;\n }\n cached = {\n installed: true,\n version: (ver.stdout ?? '').trim() || undefined,\n proxyRunning: await isProxyRunning(),\n };\n } catch {\n cached = { installed: false, proxyRunning: false };\n }\n return cached;\n}\n\n/**\n * Drop the per-process probe cache so the next `detectPortless()` re-probes.\n * Called after an install / proxy-start changes the host state, and by tests.\n */\nexport function resetPortlessCache(): void {\n cached = null;\n}\n\n/**\n * Register (or re-point) a static route so the proxy serves\n * https://<name>.localhost -> 127.0.0.1:<port>. Returns whether Portless\n * accepted it. The route is written even when the proxy is down.\n */\nexport async function portlessAlias(name: string, port: number): Promise<boolean> {\n try {\n const r = await execa(PORTLESS_BIN, [SUB_ALIAS, name, String(port)], { reject: false });\n return r.exitCode === 0;\n } catch {\n return false;\n }\n}\n\n/** Remove a static route registered by `portlessAlias`. */\nexport async function portlessUnalias(name: string): Promise<boolean> {\n try {\n const r = await execa(PORTLESS_BIN, [SUB_ALIAS, SUB_ALIAS_REMOVE, name], { reject: false });\n return r.exitCode === 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Resolve the user-facing URL for a registered route. Falls back to the\n * deterministic `https://<name>.localhost` when `portless get` is unavailable\n * (proxy down, route not yet registered, Portless missing).\n */\nexport async function portlessGetUrl(name: string): Promise<string> {\n const fallback = `https://${name}.localhost`;\n try {\n const r = await execa(PORTLESS_BIN, [SUB_GET, name], { reject: false });\n const out = (r.stdout ?? '').trim();\n if (r.exitCode === 0 && /^https?:\\/\\//.test(out)) return out;\n } catch {\n // fall through\n }\n return fallback;\n}\n\n/** Command the user should run to install Portless. */\nexport function portlessInstallHint(): string {\n return 'npm install -g portless';\n}\n\n/** Command the user should run to bring the Portless proxy up. */\nexport function portlessStartHint(): string {\n return 'portless proxy start';\n}\n\nexport interface PortlessBrowserEnvOptions {\n /**\n * Where Chromium should resolve `<box-name>.localhost` to when running\n * inside the box. Docker boxes pass `host.docker.internal` (the host\n * gateway baked into every container's `/etc/hosts`). Hetzner boxes —\n * where the box *is* the VPS and WebProxy listens on the VPS's loopback —\n * pass `127.0.0.1`.\n */\n mapTarget: string;\n}\n\n/**\n * Box env that makes the in-box browser (agent-browser → Chromium) load the\n * box's Portless `<name>.localhost` URL via the *host* Portless proxy — so the\n * web app is reachable on the exact URL the host browser uses.\n *\n * Chromium hard-codes `*.localhost` → loopback and ignores `/etc/hosts`, so\n * `--host-resolver-rules` (passed through agent-browser's `AGENT_BROWSER_ARGS`)\n * remaps the box's hostname to `opts.mapTarget` — the address that, from\n * inside the box, actually reaches the host Portless proxy.\n * `IGNORE_HTTPS_ERRORS` covers a TLS host proxy whose self-signed CA the box\n * doesn't trust.\n */\nexport function portlessBrowserEnv(\n boxName: string,\n opts: PortlessBrowserEnvOptions,\n): Record<string, string> {\n return {\n AGENT_BROWSER_ARGS: `--host-resolver-rules=MAP ${boxName}.localhost ${opts.mapTarget}`,\n AGENT_BROWSER_IGNORE_HTTPS_ERRORS: '1',\n };\n}\n\n/** Install the Portless CLI globally (`npm install -g portless`). Never throws. */\nexport async function installPortless(): Promise<boolean> {\n try {\n const r = await execa('npm', ['install', '-g', 'portless'], { reject: false });\n return r.exitCode === 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Start a Portless proxy with no TLS on the no-root port (`PORTLESS_PROXY_PORT`)\n * — `portless proxy start --no-tls -p <port>`. No sudo, no CA-trust prompt.\n * Idempotent: Portless reports \"already running\" (exit 0) if one is already up.\n * Never throws.\n */\nexport async function startPortlessProxy(): Promise<boolean> {\n try {\n const r = await execa(\n PORTLESS_BIN,\n ['proxy', 'start', '--no-tls', '-p', String(PORTLESS_PROXY_PORT)],\n { reject: false },\n );\n return r.exitCode === 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Candidate Portless state directories. `$PORTLESS_STATE_DIR` wins outright;\n * otherwise Portless picks `/tmp/portless` (proxy port < 1024, e.g. the sudo\n * :443 proxy) or `~/.portless` (>= 1024) — we check both since we don't know\n * the port.\n */\nfunction portlessStateDirCandidates(): string[] {\n const env = process.env['PORTLESS_STATE_DIR'];\n if (env && env.trim().length > 0) return [env.trim()];\n return ['/tmp/portless', join(homedir(), '.portless')];\n}\n\n/**\n * Whether `pid` names a live process. `process.kill(pid, 0)` succeeds for a\n * process we own; it throws `EPERM` for one owned by another user (the proxy\n * runs as root when bound to :443 via sudo) — that still means it is alive.\n * Only `ESRCH` (\"no such process\") is a dead pid.\n */\nfunction pidAlive(pid: number): boolean {\n if (!Number.isFinite(pid) || pid <= 0) return false;\n try {\n process.kill(pid, 0); // signal 0 = existence/permission check only\n return true;\n } catch (err) {\n return (err as NodeJS.ErrnoException).code === 'EPERM';\n }\n}\n\n/** Read `<dir>/proxy.pid`, returning the pid or null when absent/garbage. */\nasync function readProxyPid(dir: string): Promise<number | null> {\n try {\n const raw = await readFile(join(dir, 'proxy.pid'), 'utf8');\n const pid = Number.parseInt(raw.trim(), 10);\n return Number.isFinite(pid) && pid > 0 ? pid : null;\n } catch {\n return null;\n }\n}\n\n/**\n * The Portless state directory whose `proxy.pid` names a live process — i.e.\n * where the *running* proxy keeps its route registry. `null` when no proxy is\n * up. This is the authoritative way to pick between `/tmp/portless` and\n * `~/.portless` (they can both exist; only one has the live proxy).\n */\nasync function findLivePortlessStateDir(): Promise<string | null> {\n for (const dir of portlessStateDirCandidates()) {\n const pid = await readProxyPid(dir);\n if (pid !== null && pidAlive(pid)) return dir;\n }\n return null;\n}\n\n/**\n * Resolve the host Portless state directory to bind-mount into a box (so the\n * in-box `portless` CLI shares the host's route registry). Precedence:\n * 1. an explicit override — the `portless.stateDir` config key;\n * 2. `$PORTLESS_STATE_DIR`;\n * 3. the directory of the *running* proxy (authoritative);\n * 4. whichever of `~/.portless` / `/tmp/portless` already exists;\n * 5. `~/.portless` as the final fallback.\n * Does not create the directory.\n */\nexport async function resolvePortlessHostStateDir(override?: string): Promise<string> {\n if (override && override.trim().length > 0) return override.trim();\n const env = process.env['PORTLESS_STATE_DIR'];\n if (env && env.trim().length > 0) return env.trim();\n const live = await findLivePortlessStateDir();\n if (live) return live;\n const home = join(homedir(), '.portless');\n if (existsSync(home)) return home;\n if (existsSync('/tmp/portless')) return '/tmp/portless';\n return home;\n}\n\n/**\n * Best-effort: is a Portless proxy currently running on the host. A daemonized\n * proxy writes a live `proxy.pid`, but a `--foreground` proxy (and some daemon\n * modes) does not — so we also scan the process table. Either signal counts.\n */\nasync function isProxyRunning(): Promise<boolean> {\n if ((await findLivePortlessStateDir()) !== null) return true;\n try {\n const r = await execa('pgrep', ['-f', 'portless proxy'], { reject: false });\n return r.exitCode === 0 && (r.stdout ?? '').trim().length > 0;\n } catch {\n return false;\n }\n}\n","import { execa } from 'execa';\nimport { existsSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, resolve } from 'node:path';\n\nexport const DEFAULT_BOX_IMAGE = 'agentbox/box:dev';\n\nconst here = dirname(fileURLToPath(import.meta.url));\n\n// The Dockerfile's COPY lines reference monorepo-relative paths\n// (packages/ctl/dist/bin.cjs, apps/cli/share/..., packages/sandbox-docker/scripts/*),\n// so the build context must be a dir containing that tree.\n//\n// Resolution order:\n// 0. AGENTBOX_DOCKER_CONTEXT env override (dir holding Dockerfile.box).\n// 1. Staged context shipped with the bundled `agent-box` package: this\n// module is bundled into the CLI at <root>/dist, the stage step mirrors\n// the COPY tree at <root>/runtime/docker (sibling of dist/, uniform in\n// dev and when installed).\n// 2. Legacy monorepo: Dockerfile.box at the sandbox-docker package root,\n// build context = monorepo root.\nfunction resolveDockerBuild(): { dockerfile: string; context: string } {\n const override = process.env.AGENTBOX_DOCKER_CONTEXT;\n if (override && existsSync(resolve(override, 'Dockerfile.box'))) {\n return { dockerfile: resolve(override, 'Dockerfile.box'), context: override };\n }\n const staged = resolve(here, '..', 'runtime', 'docker');\n if (existsSync(resolve(staged, 'Dockerfile.box'))) {\n return { dockerfile: resolve(staged, 'Dockerfile.box'), context: staged };\n }\n // Legacy: src/ (or the unbundled package dist/) is one level under the\n // package root; the monorepo root is two more up.\n const packageRoot = resolve(here, '..');\n return {\n dockerfile: resolve(packageRoot, 'Dockerfile.box'),\n context: resolve(packageRoot, '..', '..'),\n };\n}\n\nconst { dockerfile: DOCKERFILE_PATH_RESOLVED, context: BUILD_CONTEXT_DIR_RESOLVED } =\n resolveDockerBuild();\nexport const DOCKERFILE_PATH = DOCKERFILE_PATH_RESOLVED;\nexport const BUILD_CONTEXT_DIR = BUILD_CONTEXT_DIR_RESOLVED;\n\nexport async function imageExists(ref: string): Promise<boolean> {\n const result = await execa('docker', ['image', 'inspect', ref], { reject: false });\n return result.exitCode === 0;\n}\n\nexport interface ImageInfo {\n /** Image ref (e.g. `agentbox/box:dev`). */\n ref: string;\n /** True when the engine has the image locally. */\n exists: boolean;\n /** Image size in bytes, when known. */\n sizeBytes?: number;\n /** ISO-8601 creation time, when known. */\n createdAt?: string;\n}\n\n/**\n * Read-only inspect of a Docker image. Used by `agentbox prepare` (no-args\n * status mode) to surface base-image state. Never throws — returns\n * `{ exists: false }` on any error so the status command works even when\n * the docker daemon is unreachable.\n */\nexport async function imageInfo(ref: string = DEFAULT_BOX_IMAGE): Promise<ImageInfo> {\n const result = await execa(\n 'docker',\n ['image', 'inspect', '--format', '{{.Size}}|{{.Created}}', ref],\n { reject: false },\n );\n if (result.exitCode !== 0) return { ref, exists: false };\n const [sizeStr, createdAt] = result.stdout.trim().split('|');\n const sizeBytes = sizeStr ? Number.parseInt(sizeStr, 10) : NaN;\n return {\n ref,\n exists: true,\n sizeBytes: Number.isFinite(sizeBytes) ? sizeBytes : undefined,\n createdAt: createdAt && createdAt.length > 0 ? createdAt : undefined,\n };\n}\n\nexport interface BuildImageOptions {\n ref?: string;\n dockerfile?: string;\n contextDir?: string;\n onProgress?: (line: string) => void;\n}\n\nexport async function buildImage(opts: BuildImageOptions = {}): Promise<string> {\n const ref = opts.ref ?? DEFAULT_BOX_IMAGE;\n const dockerfile = opts.dockerfile ?? DOCKERFILE_PATH;\n const contextDir = opts.contextDir ?? BUILD_CONTEXT_DIR;\n\n const subprocess = execa('docker', ['build', '-t', ref, '-f', dockerfile, contextDir], {\n stderr: 'pipe',\n stdout: 'pipe',\n });\n\n if (opts.onProgress) {\n const forward = (chunk: Buffer | string): void => {\n const text = typeof chunk === 'string' ? chunk : chunk.toString('utf8');\n for (const line of text.split(/\\r?\\n/)) {\n if (line.length > 0) opts.onProgress?.(line);\n }\n };\n subprocess.stdout?.on('data', forward);\n subprocess.stderr?.on('data', forward);\n }\n\n await subprocess;\n return ref;\n}\n\nexport interface EnsureImageOptions {\n onProgress?: (line: string) => void;\n /** Dockerfile path. Defaults to `Dockerfile.box` next to this package. */\n dockerfile?: string;\n /** Build context directory. Defaults to the monorepo root. */\n contextDir?: string;\n}\n\nexport async function ensureImage(\n ref: string = DEFAULT_BOX_IMAGE,\n opts: EnsureImageOptions = {},\n): Promise<{ ref: string; built: boolean }> {\n if (await imageExists(ref)) {\n return { ref, built: false };\n }\n await buildImage({\n ref,\n dockerfile: opts.dockerfile,\n contextDir: opts.contextDir,\n onProgress: opts.onProgress,\n });\n return { ref, built: true };\n}\n\n","import { execa } from 'execa';\nimport { mkdir, readdir, rm, stat } from 'node:fs/promises';\nimport { homedir, platform } from 'node:os';\nimport { join, resolve } from 'node:path';\nimport { sanitizeMnemonic } from '@agentbox/config';\n\n/**\n * Directories whose contents are either platform-specific (built native modules,\n * compiled outputs) or large enough to be wasteful to include in a frozen\n * workspace snapshot. Pruned from the snapshot tree *after* the APFS clone\n * — removing CoW-cloned entries is essentially free.\n */\nexport const EXCLUDE_DIRS: ReadonlySet<string> = new Set([\n 'node_modules',\n '.next',\n '.nuxt',\n '.turbo',\n '.svelte-kit',\n 'dist',\n 'build',\n 'out',\n 'target',\n '.venv',\n '__pycache__',\n '.cache',\n '.parcel-cache',\n]);\n\nexport const SNAPSHOTS_ROOT = join(homedir(), '.agentbox', 'snapshots');\n\n/**\n * `<id>-<n>-<mnemonic>` when `projectIndex` is set (post-feature boxes — all\n * new boxes), else `<id>-<mnemonic>` (legacy pre-feature fallback). Mirrors\n * `boxDirSegment` in `host-export.ts` — kept structurally compatible so the\n * snapshot dir can be looked up alongside its box dir.\n */\nexport function snapshotPathFor(box: { id: string; name: string; projectIndex?: number }): string {\n const mnemonic = sanitizeMnemonic(box.name);\n const n = box.projectIndex;\n const segment =\n typeof n === 'number' && Number.isFinite(n) && n > 0\n ? `${box.id}-${String(n)}-${mnemonic}`\n : `${box.id}-${mnemonic}`;\n return join(SNAPSHOTS_ROOT, segment);\n}\n\n/**\n * Walk a directory tree and return absolute paths of every directory whose\n * basename matches `EXCLUDE_DIRS`. Does not descend into a matched directory.\n * Pure (modulo `fs.readdir`) — easy to unit-test against a fixture tree.\n */\nexport async function findExcludedDirs(\n root: string,\n excluded: ReadonlySet<string> = EXCLUDE_DIRS,\n): Promise<string[]> {\n const matches: string[] = [];\n const walk = async (dir: string): Promise<void> => {\n let entries;\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const abs = join(dir, entry.name);\n if (excluded.has(entry.name)) {\n matches.push(abs);\n continue; // do not descend\n }\n await walk(abs);\n }\n };\n await walk(root);\n return matches;\n}\n\nexport interface CreateSnapshotOptions {\n source: string;\n destination: string;\n excluded?: ReadonlySet<string>;\n}\n\nexport interface CreateSnapshotResult {\n destination: string;\n prunedPaths: string[];\n}\n\n/**\n * Create a frozen workspace snapshot. On macOS (APFS) this is an instant CoW\n * clone via `cp -cR`; on other platforms it falls back to plain `cp -R`\n * (slow, but functional — the production fallback will be `rsync --exclude`).\n *\n * After the copy, prune all `EXCLUDE_DIRS` directories so the snapshot is free\n * of platform-specific artifacts before it becomes the overlay's lower layer.\n */\nexport async function createSnapshot(opts: CreateSnapshotOptions): Promise<CreateSnapshotResult> {\n const source = resolve(opts.source);\n const destination = resolve(opts.destination);\n const excluded = opts.excluded ?? EXCLUDE_DIRS;\n\n await mkdir(SNAPSHOTS_ROOT, { recursive: true });\n\n // `cp -c` only exists on macOS and is the APFS clone flag.\n const cpArgs = platform() === 'darwin' ? ['-cR'] : ['-R'];\n await execa('cp', [...cpArgs, `${source}/`, destination]);\n\n const toPrune = await findExcludedDirs(destination, excluded);\n await Promise.all(toPrune.map((p) => rm(p, { recursive: true, force: true })));\n\n return { destination, prunedPaths: toPrune };\n}\n\n/** Guard used by tests + by `create.ts` when we don't want to clobber a path. */\nexport async function snapshotExists(path: string): Promise<boolean> {\n try {\n const s = await stat(path);\n return s.isDirectory();\n } catch {\n return false;\n }\n}\n","import { mkdir, mkdtemp, readFile, readdir, rm, writeFile } from 'node:fs/promises';\nimport { homedir, tmpdir } from 'node:os';\nimport { basename, join } from 'node:path';\nimport { execa } from 'execa';\nimport {\n hashProjectPath,\n projectDirSegment,\n sanitizeMnemonic,\n setConfigValue,\n} from '@agentbox/config';\nimport { execInBox, removeImage } from './docker.js';\nimport { DEFAULT_BOX_IMAGE } from './image.js';\nimport type { BoxRecord, GitWorktreeRecord } from './state.js';\n\nexport const CHECKPOINTS_ROOT = join(homedir(), '.agentbox', 'checkpoints');\n\n/**\n * All per-project checkpoint *image* tags share this prefix. `prune --all`\n * allowlists by it (parallel to the old volume prefix) and `agentbox status`\n * /`inspect` recognizes a box's image as a checkpoint from this prefix.\n */\nexport const CHECKPOINT_IMAGE_PREFIX = 'agentbox-ckpt-';\n\nexport type CheckpointType = 'layered' | 'flattened';\n\n/**\n * Deterministic image tag for a project checkpoint. The repository segment is\n * keyed on the project root (same hash the per-project config dir uses) and\n * carries a mnemonic suffix so `docker images` reads self-describing. The\n * mnemonic is joined with `_` (not `-`) so the leading 16 hex chars remain\n * unambiguous and the existing `agentbox-ckpt-*` prune glob still matches.\n * Pure — unit-tested directly.\n */\nexport function checkpointImageTag(projectRoot: string, name: string): string {\n const mnemonic = sanitizeMnemonic(basename(projectRoot));\n return `${CHECKPOINT_IMAGE_PREFIX}${hashProjectPath(projectRoot)}_${mnemonic}:${name}`;\n}\n\nexport interface CheckpointManifest {\n schema: 2;\n name: string;\n type: CheckpointType;\n /** Local Docker image tag this checkpoint resolves to (`agentbox-ckpt-<hash>:<name>`). */\n image: string;\n /**\n * For a `layered` checkpoint, the older checkpoint refs this commit stacks\n * on — i.e. the chain the *source* box was built from (upper-most first,\n * base-most last). `[]` for a `flattened` checkpoint (self-contained,\n * exported+rebuilt as a single layer) or a layered checkpoint taken from a\n * box that itself started from bare host code.\n */\n parents: string[];\n base: 'worktree' | 'workspace';\n sourceBoxId: string;\n sourceBoxName: string;\n /**\n * Source box's per-worktree paths so a restored box can re-bind\n * `/workspace` (and `/workspace/<sub>`) without rerunning `seedWorkspace`.\n * `docker commit` does NOT capture bind-mounted content, so the captured\n * image's `/workspace` is empty — the actual worktree files live under\n * `gitWorktreePath` (preserved in the image's writable layer). On restore,\n * `bindWorktrees` reads these to wire the binds back. Absent only for\n * no-git boxes (their `seedWorkspaceFromDir` puts files directly in\n * `/workspace`, which the bind logic doesn't touch).\n */\n worktrees?: GitWorktreeRecord[];\n createdAt: string;\n}\n\nexport interface CheckpointInfo {\n name: string;\n /** Host dir holding `manifest.json` (`~/.agentbox/checkpoints/<hash>/<name>`). */\n dir: string;\n manifest: CheckpointManifest;\n}\n\nexport function projectCheckpointsDir(projectRoot: string): string {\n return join(CHECKPOINTS_ROOT, projectDirSegment(projectRoot));\n}\n\nfunction checkpointDir(projectRoot: string, name: string): string {\n return join(projectCheckpointsDir(projectRoot), name);\n}\n\nasync function readManifest(dir: string): Promise<CheckpointManifest | null> {\n try {\n const raw = await readFile(join(dir, 'manifest.json'), 'utf8');\n const m = JSON.parse(raw) as CheckpointManifest;\n if (m.schema !== 2) return null;\n return m;\n } catch {\n return null;\n }\n}\n\nexport async function listCheckpoints(projectRoot: string): Promise<CheckpointInfo[]> {\n const root = projectCheckpointsDir(projectRoot);\n let entries: string[];\n try {\n entries = (await readdir(root, { withFileTypes: true }))\n .filter((e) => e.isDirectory())\n .map((e) => e.name);\n } catch {\n return [];\n }\n const out: CheckpointInfo[] = [];\n for (const name of entries) {\n const dir = join(root, name);\n const manifest = await readManifest(dir);\n if (manifest) out.push({ name, dir, manifest });\n }\n out.sort((a, b) => a.manifest.createdAt.localeCompare(b.manifest.createdAt));\n return out;\n}\n\nexport async function resolveCheckpoint(\n projectRoot: string,\n ref: string,\n): Promise<CheckpointInfo | null> {\n const dir = checkpointDir(projectRoot, ref);\n const manifest = await readManifest(dir);\n if (!manifest) return null;\n return { name: ref, dir, manifest };\n}\n\n/**\n * Walk every per-project checkpoint manifest under CHECKPOINTS_ROOT and\n * return the union of their `image` tags. Used by `pruneBoxes({ all: true })`\n * to keep an image alive as long as any manifest on disk points at it —\n * destroy leaves the checkpoint behind by design, and the user expects to\n * still be able to start a new box from it long after the source box is gone.\n *\n * Best-effort, matching listSnapshotDirs / listBoxDirs in lifecycle.ts:\n * missing root, unreadable / non-schema-2 manifests, and non-directory\n * entries at any level are all skipped silently.\n */\nexport async function listAllCheckpointImages(): Promise<string[]> {\n let projectDirs: string[];\n try {\n projectDirs = (await readdir(CHECKPOINTS_ROOT, { withFileTypes: true }))\n .filter((e) => e.isDirectory())\n .map((e) => e.name);\n } catch {\n return [];\n }\n const out = new Set<string>();\n for (const proj of projectDirs) {\n const projPath = join(CHECKPOINTS_ROOT, proj);\n let names: string[];\n try {\n names = (await readdir(projPath, { withFileTypes: true }))\n .filter((e) => e.isDirectory())\n .map((e) => e.name);\n } catch {\n continue;\n }\n for (const name of names) {\n const manifest = await readManifest(join(projPath, name));\n if (manifest) out.add(manifest.image);\n }\n }\n return Array.from(out);\n}\n\nexport async function removeCheckpoint(projectRoot: string, ref: string): Promise<boolean> {\n const dir = checkpointDir(projectRoot, ref);\n const manifest = await readManifest(dir);\n if (!manifest) return false;\n await rm(dir, { recursive: true, force: true });\n // Image is the durable artifact; best-effort because nothing else references\n // it once the manifest is gone. `-f` so a stale tag without containers is\n // dropped even if Docker considers it \"in use\" via dangling layers.\n await removeImage(manifest.image, { force: true });\n return true;\n}\n\n/**\n * Next `<boxName>-<n>` given the names already present. Monotonic per\n * box-name; gaps from deleted checkpoints are skipped (max+1, never\n * recycled). Pure — unit-tested directly.\n */\nexport function computeNextCheckpointName(existingNames: string[], boxName: string): string {\n const re = new RegExp(`^${boxName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}-(\\\\d+)$`);\n let max = 0;\n for (const n of existingNames) {\n const m = re.exec(n);\n if (m) max = Math.max(max, Number(m[1]));\n }\n return `${boxName}-${String(max + 1)}`;\n}\n\nasync function nextCheckpointName(projectRoot: string, boxName: string): Promise<string> {\n const existing = await listCheckpoints(projectRoot);\n return computeNextCheckpointName(\n existing.map((c) => c.name),\n boxName,\n );\n}\n\nfunction chainDepth(box: BoxRecord): number {\n return box.checkpointSource?.chain.length ?? 0;\n}\n\nexport interface CreateCheckpointOptions {\n box: BoxRecord;\n projectRoot: string;\n name?: string;\n /** Force a flattened (export+`FROM scratch` rebuild) capture. */\n merged?: boolean;\n setDefault?: boolean;\n /**\n * If an existing checkpoint has the same name, delete it (manifest + image)\n * before capturing. Without this, name collisions throw `CheckpointError`.\n * Useful for idempotent re-runs from the in-box agent (`agentbox-ctl\n * checkpoint --replace`), e.g. when the previous invocation's stdout was\n * lost mid-flight and the agent can't tell whether it succeeded.\n */\n replace?: boolean;\n /** checkpoint.maxLayers — auto-flatten when the source chain is at/over this. */\n maxLayers: number;\n onLog?: (line: string) => void;\n}\n\n/**\n * Run the pre-commit cleanup in the box. Script is image-baked at\n * /usr/local/bin/agentbox-checkpoint-cleanup. Best-effort: a non-zero exit\n * from the cleanup logs but does not abort the capture (the script itself\n * is also `set +e` for the same reason).\n */\nasync function runCleanup(container: string, log: (line: string) => void): Promise<void> {\n const r = await execInBox(container, ['/usr/local/bin/agentbox-checkpoint-cleanup'], {\n user: 'root',\n });\n if (r.exitCode !== 0) {\n log(`warning: checkpoint cleanup exited ${String(r.exitCode)}: ${r.stderr.slice(0, 200)}`);\n }\n}\n\n/**\n * Read the base image's Config block so the flatten step can replay\n * Env/Cmd/Entrypoint/WorkingDir/User/ExposedPorts on the `FROM scratch`\n * rebuild. (`docker export` discards every Config field.)\n */\nasync function inspectImageConfig(imageRef: string): Promise<DockerImageConfig> {\n const r = await execa('docker', ['image', 'inspect', imageRef], { reject: false });\n if (r.exitCode !== 0) {\n throw new CheckpointError(`docker image inspect ${imageRef} failed`, r.stdout, r.stderr);\n }\n const parsed = JSON.parse(r.stdout) as Array<{ Config: DockerImageConfig }>;\n if (!Array.isArray(parsed) || parsed.length === 0 || !parsed[0]?.Config) {\n throw new CheckpointError(`unexpected docker image inspect shape for ${imageRef}`, r.stdout, '');\n }\n return parsed[0].Config;\n}\n\ninterface DockerImageConfig {\n Env?: string[];\n Cmd?: string[];\n Entrypoint?: string[] | null;\n WorkingDir?: string;\n User?: string;\n ExposedPorts?: Record<string, unknown>;\n}\n\n/**\n * Per-instance env keys that `docker commit` picks up from the running\n * container's Config and that must NOT be baked into a project-shared\n * checkpoint image. `runBox -e` sets these per launch; persisting them\n * would leak another box's relay token / VNC password / box-id into every\n * future box restored from this checkpoint.\n */\nconst RUNTIME_ENV_BLOCKLIST = new Set([\n 'AGENTBOX',\n 'AGENTBOX_BOX_ID',\n 'AGENTBOX_BOX_NAME',\n 'AGENTBOX_HOST_WORKSPACE',\n 'AGENTBOX_PROJECT_ROOT',\n 'AGENTBOX_PROJECT_INDEX',\n 'AGENTBOX_RELAY_URL',\n 'AGENTBOX_RELAY_TOKEN',\n 'AGENTBOX_VNC_PASSWORD',\n 'CLAUDE_EFFORT',\n 'CLAUDE_CODE_OAUTH_TOKEN',\n 'ANTHROPIC_API_KEY',\n 'ANTHROPIC_MODEL',\n]);\n\n/**\n * Render the Dockerfile lines that replay an image's Config on top of a\n * `FROM scratch\\nADD rootfs.tar /` base. Each Env entry becomes its own ENV\n * line (the Dockerfile `ENV KEY=value` form is the only one that safely\n * handles `=` in values). Per-instance env keys (RUNTIME_ENV_BLOCKLIST) are\n * stripped — those come from `docker run -e` at the source box's launch and\n * must not leak into a project-shared checkpoint image.\n * Cmd/Entrypoint are emitted in their JSON exec form.\n */\nfunction renderConfigDirectives(cfg: DockerImageConfig): string[] {\n const lines: string[] = [];\n for (const kv of cfg.Env ?? []) {\n const eq = kv.indexOf('=');\n if (eq <= 0) continue;\n const k = kv.slice(0, eq);\n if (RUNTIME_ENV_BLOCKLIST.has(k)) continue;\n const v = kv.slice(eq + 1);\n lines.push(`ENV ${k}=${dockerfileQuote(v)}`);\n }\n if (cfg.WorkingDir) lines.push(`WORKDIR ${cfg.WorkingDir}`);\n if (cfg.User) lines.push(`USER ${cfg.User}`);\n for (const p of Object.keys(cfg.ExposedPorts ?? {})) lines.push(`EXPOSE ${p.replace('/tcp', '')}`);\n if (cfg.Entrypoint && cfg.Entrypoint.length > 0) {\n lines.push(`ENTRYPOINT ${JSON.stringify(cfg.Entrypoint)}`);\n }\n if (cfg.Cmd && cfg.Cmd.length > 0) {\n lines.push(`CMD ${JSON.stringify(cfg.Cmd)}`);\n }\n return lines;\n}\n\n/** Dockerfile-safe quoting for ENV values (handles spaces and quotes). */\nfunction dockerfileQuote(v: string): string {\n if (/^[A-Za-z0-9._/:+@,=-]+$/.test(v)) return v;\n return `\"${v.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"')}\"`;\n}\n\n/**\n * Capture a box's accumulated state as a project checkpoint.\n *\n * - `layered`: `docker exec /usr/local/bin/agentbox-checkpoint-cleanup` then\n * `docker commit <container> <tag>`. Captures everything the container has\n * written *and* the inherited image layers; the new image's parent is the\n * container's current image (i.e. the previous checkpoint or the base).\n * - `flattened`: same prelude, then `docker export | docker build` against\n * a tiny `FROM scratch` Dockerfile that ADDs the rootfs tar and replays\n * the base image's Env/Cmd/Entrypoint/WorkingDir/User/ExposedPorts. The\n * resulting image is a single layer.\n *\n * Flattened is chosen when `--merged` is passed or the source box's chain is\n * already `>= maxLayers` deep (caps image-layer growth and reset lineage).\n */\nexport async function createCheckpoint(opts: CreateCheckpointOptions): Promise<CheckpointInfo> {\n const log = opts.onLog ?? (() => {});\n const { box } = opts;\n\n const type: CheckpointType =\n opts.merged === true || chainDepth(box) >= opts.maxLayers ? 'flattened' : 'layered';\n const name = opts.name ?? (await nextCheckpointName(opts.projectRoot, box.name));\n const dir = checkpointDir(opts.projectRoot, name);\n const existing = await readManifest(dir);\n if (existing) {\n if (opts.replace) {\n log(`replacing existing checkpoint ${name} (created ${existing.createdAt})`);\n await removeCheckpoint(opts.projectRoot, name);\n } else {\n // Surface the existing checkpoint's createdAt so a caller whose previous\n // stdout was lost (e.g. the in-box agent's harness wiping output) can\n // immediately tell whether their prior attempt succeeded. Same `rm`\n // hint, plus a `--replace` shortcut for idempotent re-runs.\n throw new CheckpointError(\n `checkpoint ${name} already exists (created ${existing.createdAt}; rm it or pass --replace to recapture)`,\n '',\n '',\n );\n }\n }\n const tag = checkpointImageTag(opts.projectRoot, name);\n await mkdir(dir, { recursive: true });\n\n log(`running pre-commit cleanup in ${box.container}`);\n await runCleanup(box.container, log);\n\n if (type === 'layered') {\n log(`docker commit ${box.container} -> ${tag} (layered)`);\n const r = await execa('docker', ['commit', box.container, tag], { reject: false });\n if (r.exitCode !== 0) {\n throw new CheckpointError(`docker commit failed for ${box.container}`, r.stdout, r.stderr);\n }\n } else {\n log(`docker commit ${box.container} -> <intermediate> (flattened path)`);\n // Two-step: commit first (so we can `docker export` from the resulting\n // image without disturbing the live container), then export+build.\n const intermediate = `${tag}-intermediate`;\n const commit = await execa('docker', ['commit', box.container, intermediate], {\n reject: false,\n });\n if (commit.exitCode !== 0) {\n throw new CheckpointError(`docker commit (intermediate) failed`, commit.stdout, commit.stderr);\n }\n try {\n await flattenImage(intermediate, tag, log);\n } finally {\n // The intermediate is layered (commits don't squash); replaced by the\n // flattened tag. -f because dangling tags would otherwise pin the\n // layered intermediate forever.\n await removeImage(intermediate, { force: true });\n }\n }\n\n const base: 'worktree' | 'workspace' = (box.gitWorktrees ?? []).some((w) => w.kind === 'root')\n ? 'worktree'\n : 'workspace';\n const manifest: CheckpointManifest = {\n schema: 2,\n name,\n type,\n image: tag,\n // Layered carries lineage forward; flattened is self-contained.\n parents: type === 'layered' ? (box.checkpointSource?.chain ?? []) : [],\n base,\n sourceBoxId: box.id,\n sourceBoxName: box.name,\n worktrees: box.gitWorktrees,\n createdAt: new Date().toISOString(),\n };\n await writeFile(join(dir, 'manifest.json'), JSON.stringify(manifest, null, 2) + '\\n', 'utf8');\n\n if (opts.setDefault) {\n // Pin the docker-specific default so a `agentbox create --provider daytona`\n // in the same project doesn't trip over a docker image ref it can't load.\n await setConfigValue('project', 'box.defaultCheckpointDocker', name, opts.projectRoot);\n log(`set project default checkpoint (box.defaultCheckpointDocker) -> ${name}`);\n }\n\n return { name, dir, manifest };\n}\n\n/**\n * Flatten a layered image: export its rootfs as a tar, wrap it in a tiny\n * `FROM scratch` Dockerfile that replays the source image's Config block,\n * and build the result. Leaves the source image alone (caller removes it).\n */\nasync function flattenImage(\n sourceTag: string,\n destTag: string,\n log: (line: string) => void,\n): Promise<void> {\n // `docker export` needs a *container*, so create one without running it.\n const tmpName = `agentbox-flatten-${Date.now().toString(36)}`;\n const create = await execa(\n 'docker',\n ['create', '--name', tmpName, sourceTag, 'sleep', '0'],\n { reject: false },\n );\n if (create.exitCode !== 0) {\n throw new CheckpointError(`docker create for flatten failed`, create.stdout, create.stderr);\n }\n const scratch = await mkdtemp(join(tmpdir(), 'agentbox-flatten-'));\n try {\n const rootfsPath = join(scratch, 'rootfs.tar');\n log(`exporting rootfs of ${sourceTag} to ${rootfsPath}`);\n const exp = await execa('docker', ['export', '-o', rootfsPath, tmpName], { reject: false });\n if (exp.exitCode !== 0) {\n throw new CheckpointError(`docker export failed`, exp.stdout, exp.stderr);\n }\n\n // The Config block to replay is the *source* image's — that's what the\n // running container saw, and `docker commit` carries it through\n // unmodified. `docker export` drops it, which is the only reason we need\n // to inspect+replay here.\n const cfg = await inspectImageConfig(sourceTag);\n const lines = [\n 'FROM scratch',\n // ADD untars during build (Docker's documented behavior for local tars).\n 'ADD rootfs.tar /',\n ...renderConfigDirectives(cfg),\n ];\n await writeFile(join(scratch, 'Dockerfile'), lines.join('\\n') + '\\n', 'utf8');\n\n log(`building flattened ${destTag} from rootfs.tar (FROM scratch)`);\n const build = await execa(\n 'docker',\n ['build', '-t', destTag, '-f', join(scratch, 'Dockerfile'), scratch],\n { reject: false },\n );\n if (build.exitCode !== 0) {\n throw new CheckpointError(`flatten docker build failed`, build.stdout, build.stderr);\n }\n } finally {\n await execa('docker', ['rm', '-f', tmpName], { reject: false });\n await rm(scratch, { recursive: true, force: true });\n }\n}\n\nexport class CheckpointError extends Error {\n constructor(\n message: string,\n public readonly stdout: string,\n public readonly stderr: string,\n ) {\n super(`${message}${stderr ? `: ${stderr.trim()}` : ''}`);\n this.name = 'CheckpointError';\n }\n}\n\n/** Kept for the type-only import in `image.ts`'s self-update path. */\nexport const _DEFAULT_BASE_IMAGE_REF = DEFAULT_BOX_IMAGE;\n","import { stat } from 'node:fs/promises';\nimport { execInBox } from './docker.js';\n\nexport interface CtlLaunchResult {\n up: boolean;\n reason?: string;\n}\n\n/**\n * In-box path the daemon's stdout/stderr are redirected to. `docker exec -d`\n * discards stdio, so without this redirect a crash on startup leaves no trace\n * — the unix socket file lingers (Node doesn't auto-unlink on exit) and any\n * later `agentbox-ctl <op>` connect gets ECONNREFUSED with no log to explain\n * why. The file lives in the container's writable layer; it survives\n * stop/start and is wiped on destroy.\n */\nconst CTL_DAEMON_LOG = '/var/log/agentbox/ctl-daemon.log';\n\n/**\n * Spawn `agentbox-ctl daemon` detached inside the container and wait briefly\n * for the unix socket to appear on the host-mounted path. Best-effort —\n * failure is logged but doesn't fail box creation, since a missing or empty\n * agentbox.yaml is a perfectly valid state.\n */\nexport async function launchCtlDaemon(\n container: string,\n hostSocketPath: string,\n timeoutMs = 3000,\n): Promise<CtlLaunchResult> {\n // Wrap in `sh -c` so the daemon's stdio lands in a log file we can read\n // after a crash. The log dir is pre-created in the image (Dockerfile.box\n // mkdir+chown vscode); `mkdir -p` is a cheap belt-and-braces. `exec` lets\n // the shell replace itself with the daemon for a clean process tree.\n const wrapped = `mkdir -p ${CTL_DAEMON_LOG.replace(/\\/[^/]*$/, '')} && exec agentbox-ctl daemon >>${CTL_DAEMON_LOG} 2>&1`;\n const result = await execInBox(container, ['sh', '-c', wrapped], {\n user: 'vscode',\n detach: true,\n });\n if (result.exitCode !== 0) {\n return { up: false, reason: `docker exec failed: ${result.stderr || result.stdout}` };\n }\n\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (await pathExists(hostSocketPath)) return { up: true };\n await new Promise((r) => setTimeout(r, 100));\n }\n return {\n up: false,\n reason: `socket ${hostSocketPath} did not appear within ${String(timeoutMs)}ms`,\n };\n}\n\nasync function pathExists(p: string): Promise<boolean> {\n try {\n await stat(p);\n return true;\n } catch {\n return false;\n }\n}\n","import { execa } from 'execa';\n\n/**\n * Writes /etc/agentbox/box.env inside the container as a POSIX-sourceable\n * key='value' file. Paired with /etc/profile.d/agentbox.sh (baked in the\n * image), which `set -a; . /etc/agentbox/box.env; set +a`s it on login.\n *\n * Best-effort: failure is logged by the caller; an unwritable file just\n * means interactive shells lose the AGENTBOX_* vars (the env vars baked\n * into docker run still survive).\n */\nexport async function writeBoxEnvFile(\n container: string,\n env: Record<string, string>,\n): Promise<{ ok: true } | { ok: false; reason: string }> {\n const body = formatBoxEnvBody(env);\n const result = await execa(\n 'docker',\n ['exec', '--user', 'root', '-i', container, 'sh', '-c', 'umask 022 && cat > /etc/agentbox/box.env'],\n { input: body, reject: false },\n );\n if (result.exitCode !== 0) {\n return {\n ok: false,\n reason: `docker exec failed (exit ${String(result.exitCode)}): ${(result.stderr ?? '').toString().slice(0, 400)}`,\n };\n }\n return { ok: true };\n}\n\n// Single-quote each value and escape embedded single quotes as '\\''. Avoids\n// double-quoted form because `. ` would expand $foo / `cmd` at source time.\nexport function formatBoxEnvBody(env: Record<string, string>): string {\n const lines: string[] = [];\n for (const [k, v] of Object.entries(env)) {\n lines.push(`${k}=${shellSingleQuote(v)}`);\n }\n return lines.join('\\n') + '\\n';\n}\n\nfunction shellSingleQuote(s: string): string {\n return `'${s.replace(/'/g, `'\\\\''`)}'`;\n}\n","import { execa } from 'execa';\n\n/**\n * Re-own any root-owned file under /home/vscode to the uid-1000 `vscode`\n * user. Root-run `docker exec` steps (checkpoint cleanup, dockerd setup) and\n * any `sudo` the in-box agent runs can leave home-dir files owned by root;\n * since the box's shell and agent both run as `vscode`, those files become\n * silently unwritable (the original symptom: a root-owned `.bash_history`\n * dropping all shell history). Boxes are throwaway dev sandboxes, so healing\n * the whole home dir in one sweep beats per-file whack-a-mole.\n *\n * `--from=root` is load-bearing: it limits the chown to files actually owned\n * by root and skips the (vast) vscode-owned majority. A plain `chown -R`\n * issues a chown syscall per file, which on the box's overlay rootfs forces\n * a copy-up of every image-layer file into the writable layer — ~10s and\n * needless disk bloat. `--from` keeps it a fast (~1s) targeted heal.\n *\n * Best-effort, idempotent: runs at every create + start. A few files are\n * legitimately unfixable (a read-only `.gitconfig` bind-mount, git pack\n * files) — chown's per-file errors are harmless and ignored.\n */\nexport async function ensureHomeOwnedByVscode(container: string): Promise<void> {\n await execa(\n 'docker',\n [\n 'exec',\n '--user',\n 'root',\n container,\n 'chown',\n '-R',\n '--from=root',\n 'vscode:vscode',\n '/home/vscode',\n ],\n { reject: false },\n );\n}\n","import { spawn } from 'node:child_process';\nimport { randomBytes } from 'node:crypto';\nimport { existsSync, openSync } from 'node:fs';\nimport { mkdir, readFile, unlink, writeFile } from 'node:fs/promises';\nimport { request as httpRequest } from 'node:http';\nimport { homedir } from 'node:os';\nimport { dirname, join, resolve } from 'node:path';\nimport { setTimeout as delay } from 'node:timers/promises';\nimport { fileURLToPath } from 'node:url';\nimport {\n DEFAULT_RELAY_PORT,\n RELAY_CONTAINER_NAME,\n RELAY_IMAGE_REF,\n RELAY_NETWORK_NAME,\n type BoxWorktree,\n} from '@agentbox/relay';\nimport { containerExists, removeContainer } from './docker.js';\nimport type { GitWorktreeRecord } from './state.js';\n\nconst STATE_DIR = join(homedir(), '.agentbox');\nconst PID_FILE = join(STATE_DIR, 'relay.pid');\nconst LOG_FILE = join(STATE_DIR, 'relay.log');\n\nexport interface RelayEndpoint {\n /** URL boxes use to reach the relay from inside the container. */\n url: string;\n /** URL host-side processes (the CLI itself) use. */\n hostUrl: string;\n port: number;\n}\n\nconst PORT = DEFAULT_RELAY_PORT;\nconst ENDPOINT: RelayEndpoint = {\n // host.docker.internal is the Docker Desktop / OrbStack-supplied alias for\n // the host's loopback as seen from inside a container. The corresponding\n // `--add-host=host.docker.internal:host-gateway` flag in runBox makes the\n // resolution work on Linux native Docker too.\n url: `http://host.docker.internal:${String(PORT)}`,\n hostUrl: `http://127.0.0.1:${String(PORT)}`,\n port: PORT,\n};\n\nexport interface EnsureRelayOptions {\n onLog?: (line: string) => void;\n}\n\n/**\n * Idempotently bring up the host relay. Spawns the bundled `agentbox-relay`\n * bin as a detached node process bound to 0.0.0.0:8787 (so boxes can reach\n * it via host.docker.internal, and the CLI via 127.0.0.1). Best-effort:\n * failures throw and the caller treats it as \"relay not reachable\".\n *\n * If a legacy relay container from a previous version of agentbox is still\n * around, it's removed first so its bound DNS name doesn't shadow the new\n * host process for any old boxes that happen to still be running.\n */\nexport async function ensureRelay(opts: EnsureRelayOptions = {}): Promise<RelayEndpoint> {\n const log = opts.onLog ?? (() => {});\n await mkdir(STATE_DIR, { recursive: true });\n\n // Migration: kill the old in-docker relay if it's around. The host process\n // wants the same port; the container did NOT publish to host:8787, so there\n // is no actual port collision. We still remove it to avoid confusion (it'd\n // show up in `docker ps -a` forever otherwise).\n if (await containerExists(RELAY_CONTAINER_NAME)) {\n await removeContainer(RELAY_CONTAINER_NAME);\n log(`removed legacy relay container ${RELAY_CONTAINER_NAME}`);\n }\n\n if (await pingHealthz(500)) {\n return ENDPOINT;\n }\n\n const existingPid = await readPidFile();\n if (existingPid !== null && (await processAlive(existingPid))) {\n // Pid exists but healthz isn't responding yet — give it a beat to finish\n // startup. If it stays unresponsive, leave it alone (someone might be\n // debugging it) and let downstream POSTs fail as best-effort.\n for (let i = 0; i < 10; i++) {\n if (await pingHealthz(300)) return ENDPOINT;\n await delay(200);\n }\n log(`relay pid ${String(existingPid)} alive but /healthz unresponsive — proceeding anyway`);\n return ENDPOINT;\n }\n if (existingPid !== null) {\n await unlink(PID_FILE).catch(() => {});\n }\n\n const relayBin = resolveRelayBin();\n const logFd = openSync(LOG_FILE, 'a');\n // The relay shells out to this CLI entry for the checkpoint.create RPC\n // (it only knows the box id; the CLI resolves the rest). Resolve best-effort\n // — if not found the relay's handler reports a clear error.\n const cliEntry = resolveCliEntry();\n const child = spawn(\n process.execPath,\n [relayBin, 'serve', '--port', String(PORT), '--host', '0.0.0.0'],\n {\n detached: true,\n stdio: ['ignore', logFd, logFd],\n env: {\n ...process.env,\n ...(cliEntry ? { AGENTBOX_CLI_ENTRY: cliEntry } : {}),\n },\n },\n );\n child.unref();\n if (typeof child.pid === 'number') {\n await writeFile(PID_FILE, String(child.pid), 'utf8');\n log(`spawned relay host process (pid ${String(child.pid)}, port ${String(PORT)})`);\n }\n\n for (let i = 0; i < 25; i++) {\n if (await pingHealthz(300)) {\n log(`relay reachable on ${ENDPOINT.hostUrl}`);\n return ENDPOINT;\n }\n await delay(200);\n }\n throw new Error(\n `relay did not become reachable on ${ENDPOINT.hostUrl} within 5s; see ${LOG_FILE}`,\n );\n}\n\n/**\n * Locate the `agentbox-relay` bin spawned as a child process. Layouts:\n * 0. env override: `AGENTBOX_RELAY_BIN`\n * 1. bundled CLI (dev + published `agent-box`): this module is bundled into\n * the CLI at `<root>/dist/index.js`, the stage step puts the bin at\n * `<root>/runtime/relay/bin.cjs` (sibling of dist/ in both layouts)\n * 2. legacy workspace: `<repo>/packages/sandbox-docker/dist` ↔ `<repo>/packages/relay/dist/bin.cjs`\n * 3. legacy externalized install: `<...>/node_modules/@agentbox/relay/dist/bin.cjs`\n */\nfunction resolveRelayBin(): string {\n const override = process.env.AGENTBOX_RELAY_BIN;\n if (override && existsSync(override)) return override;\n const here = dirname(fileURLToPath(import.meta.url));\n const candidates = [\n resolve(here, '..', 'runtime', 'relay', 'bin.cjs'),\n resolve(here, '..', '..', 'relay', 'dist', 'bin.cjs'),\n resolve(here, '..', '..', '..', '@agentbox', 'relay', 'dist', 'bin.cjs'),\n resolve(here, '..', '..', 'node_modules', '@agentbox', 'relay', 'dist', 'bin.cjs'),\n ];\n for (const c of candidates) {\n if (existsSync(c)) return c;\n }\n throw new Error(\n `could not locate @agentbox/relay bin; tried:\\n ${candidates.join('\\n ')}`,\n );\n}\n\n/**\n * Locate the agentbox CLI entry the relay spawns for `checkpoint.create`.\n * Mirrors {@link resolveRelayBin}'s two layouts:\n * 1. workspace dev: `<repo>/packages/sandbox-docker/dist` ↔ `<repo>/apps/cli/dist/index.js`\n * 2. installed: `<...>/agentbox/node_modules/@agentbox/sandbox-docker/dist` ↔ `<...>/agentbox/dist/index.js`\n * Best-effort: returns null when not found (relay reports a clear error).\n */\nfunction resolveCliEntry(): string | null {\n const override = process.env.AGENTBOX_CLI_ENTRY;\n if (override && existsSync(override)) return override;\n const here = dirname(fileURLToPath(import.meta.url));\n const candidates = [\n // Bundled CLI (dev + published): this module IS bundled into the CLI\n // entry, so the entry is index.js next to this file.\n resolve(here, 'index.js'),\n resolve(here, '..', '..', '..', 'apps', 'cli', 'dist', 'index.js'),\n resolve(here, '..', '..', '..', '..', 'dist', 'index.js'),\n ];\n for (const c of candidates) {\n if (existsSync(c)) return c;\n }\n return null;\n}\n\nexport interface StopRelayResult {\n /** True when a live relay process was signalled (and the pidfile cleared). */\n stopped: boolean;\n /** The pid that was found in the pidfile, if any. */\n pid: number | null;\n}\n\n/**\n * Stop the host relay process and clear its pidfile. SIGTERM first, then\n * SIGKILL if it's still alive after a short grace period. Idempotent: a\n * missing/stale pidfile is not an error (returns `{ stopped: false }`).\n *\n * Used by `agentbox update` to reload the relay; the next box command brings\n * it back via {@link ensureRelay} (running the freshly-installed bin).\n */\nexport async function stopRelay(): Promise<StopRelayResult> {\n const pid = await readPidFile();\n if (pid === null) {\n return { stopped: false, pid: null };\n }\n if (!(await processAlive(pid))) {\n await unlink(PID_FILE).catch(() => {});\n return { stopped: false, pid };\n }\n try {\n process.kill(pid, 'SIGTERM');\n } catch {\n // already gone between the liveness check and the signal\n }\n for (let i = 0; i < 20; i++) {\n if (!(await processAlive(pid))) break;\n await delay(100);\n }\n if (await processAlive(pid)) {\n try {\n process.kill(pid, 'SIGKILL');\n } catch {\n // best-effort\n }\n }\n await unlink(PID_FILE).catch(() => {});\n return { stopped: true, pid };\n}\n\nexport interface RelayStatus {\n /** True when /healthz responded with a 2xx. */\n running: boolean;\n /** Pidfile contents (null when missing/unparseable). */\n pid: number | null;\n /** Signal-0 probe on `pid` (false when `pid` is null). */\n pidAlive: boolean;\n /** Configured port (same value as endpoint.port). */\n port: number;\n /** URLs boxes / host-side callers use to reach the relay. */\n endpoint: RelayEndpoint;\n /** Parsed /healthz body; null when the relay isn't responding. */\n health: { boxes: number; events: number } | null;\n /** Absolute path to the pidfile. */\n pidFile: string;\n /** Absolute path to the process log. */\n logFile: string;\n}\n\n/**\n * Read-only snapshot of the host relay's liveness. Combines the two probes the\n * lifecycle code uses internally: pidfile + signal-0 + a short /healthz GET.\n * Three terminal states callers care about:\n * - running: true — healthz ok\n * - running: false, pidAlive: true — zombie (process up, healthz silent)\n * - running: false, pidAlive: false — truly down\n */\nexport async function getRelayStatus(): Promise<RelayStatus> {\n const pid = await readPidFile();\n const pidAlive = pid !== null && (await processAlive(pid));\n const health = await fetchHealthz(300);\n return {\n running: health !== null,\n pid,\n pidAlive,\n port: PORT,\n endpoint: ENDPOINT,\n health: health === null ? null : { boxes: health.boxes, events: health.events },\n pidFile: PID_FILE,\n logFile: LOG_FILE,\n };\n}\n\nfunction pingHealthz(timeoutMs: number): Promise<boolean> {\n return new Promise<boolean>((resolveP) => {\n const req = httpRequest(\n { host: '127.0.0.1', port: PORT, method: 'GET', path: '/healthz', timeout: timeoutMs },\n (res) => {\n res.resume();\n const status = res.statusCode ?? 0;\n resolveP(status >= 200 && status < 300);\n },\n );\n req.on('error', () => resolveP(false));\n req.on('timeout', () => {\n req.destroy();\n resolveP(false);\n });\n req.end();\n });\n}\n\ninterface HealthzBody {\n ok: boolean;\n boxes: number;\n events: number;\n}\n\nfunction fetchHealthz(timeoutMs: number): Promise<HealthzBody | null> {\n return new Promise<HealthzBody | null>((resolveP) => {\n const req = httpRequest(\n { host: '127.0.0.1', port: PORT, method: 'GET', path: '/healthz', timeout: timeoutMs },\n (res) => {\n const status = res.statusCode ?? 0;\n if (status < 200 || status >= 300) {\n res.resume();\n resolveP(null);\n return;\n }\n const chunks: Buffer[] = [];\n res.on('data', (c: Buffer) => chunks.push(c));\n res.on('end', () => {\n try {\n const parsed = JSON.parse(Buffer.concat(chunks).toString('utf8')) as Partial<HealthzBody>;\n if (\n typeof parsed.ok === 'boolean' &&\n typeof parsed.boxes === 'number' &&\n typeof parsed.events === 'number'\n ) {\n resolveP({ ok: parsed.ok, boxes: parsed.boxes, events: parsed.events });\n } else {\n resolveP(null);\n }\n } catch {\n resolveP(null);\n }\n });\n res.on('error', () => resolveP(null));\n },\n );\n req.on('error', () => resolveP(null));\n req.on('timeout', () => {\n req.destroy();\n resolveP(null);\n });\n req.end();\n });\n}\n\nasync function readPidFile(): Promise<number | null> {\n try {\n const text = await readFile(PID_FILE, 'utf8');\n const pid = Number.parseInt(text.trim(), 10);\n return Number.isFinite(pid) && pid > 0 ? pid : null;\n } catch {\n return null;\n }\n}\n\nasync function processAlive(pid: number): Promise<boolean> {\n try {\n // Signal 0 is the existence probe: throws if no such process.\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function generateRelayToken(): string {\n return randomBytes(32).toString('hex');\n}\n\nexport interface RegisterBoxArgs {\n boxId: string;\n token: string;\n name: string;\n /**\n * Sandbox backend. Defaults to 'docker'. 'cloud' tells the host relay to\n * spawn a `CloudBoxPoller` for this box (which requires `previewUrl` +\n * `bridgeToken` to be set).\n */\n kind?: 'docker' | 'cloud';\n /**\n * For cloud boxes: which cloud backend to drive (e.g. 'daytona'). The\n * relay's executor lazy-imports `@agentbox/sandbox-{backend}` to do\n * host-only RPCs like git push.\n */\n backend?: string;\n /** Docker container name; lets the relay `docker pause` the box for auto-pause. */\n containerName?: string;\n /** ISO-8601 box-creation time (BoxRecord.createdAt); auto-pause tie-break. */\n createdAt?: string;\n /**\n * 1-based per-project box index. Forwarded so the relay's status-store\n * builds the same `<id>-<n>-<mnemonic>` dir segment the host's\n * `boxRunDirFor` uses. Absent for legacy boxes.\n */\n projectIndex?: number;\n /**\n * Subset of BoxRecord.gitWorktrees the relay needs to dispatch git RPCs.\n * Empty/omitted for boxes without git repos.\n */\n worktrees?: GitWorktreeRecord[];\n /** Required for `kind === 'cloud'`: preview URL of the in-sandbox relay's `/bridge/*`. */\n previewUrl?: string;\n /** Provider-proxy token for `previewUrl` (Daytona `x-daytona-preview-token`). */\n previewToken?: string;\n /** Required for `kind === 'cloud'`: bearer for the in-sandbox relay's `/bridge/*`. */\n bridgeToken?: string;\n}\n\nexport async function registerBoxWithRelay(args: RegisterBoxArgs): Promise<void> {\n const worktrees: BoxWorktree[] = (args.worktrees ?? []).map((w) => ({\n containerPath: w.containerPath,\n hostMainRepo: w.hostMainRepo,\n branch: w.branch,\n }));\n await adminPost('/admin/register-box', {\n boxId: args.boxId,\n token: args.token,\n name: args.name,\n kind: args.kind ?? 'docker',\n backend: args.backend,\n containerName: args.containerName,\n createdAt: args.createdAt,\n projectIndex: args.projectIndex,\n worktrees,\n previewUrl: args.previewUrl,\n previewToken: args.previewToken,\n bridgeToken: args.bridgeToken,\n });\n}\n\nexport async function forgetBoxFromRelay(boxId: string): Promise<void> {\n try {\n await adminPost('/admin/forget-box', { boxId });\n } catch {\n // best-effort\n }\n}\n\n/**\n * Best-effort: register an informational notice for a box so attached\n * `agentbox claude` footers / the dashboard show it (e.g. a spinner while a\n * checkpoint freezes the box). Returns the notice id, or null when the relay\n * is unreachable / too old to know the route — the caller treats a null id\n * as \"nothing to clear later\". Never throws: a missing notice must not fail\n * the operation it was decorating.\n */\nexport async function setRelayNotice(\n boxId: string,\n kind: string,\n message: string,\n ttlMs?: number,\n): Promise<string | null> {\n try {\n const body = await adminPostForJson('/admin/notices/set', {\n boxId,\n kind,\n message,\n ...(typeof ttlMs === 'number' ? { ttlMs } : {}),\n });\n const id = (body as { id?: unknown } | null)?.id;\n return typeof id === 'string' && id.length > 0 ? id : null;\n } catch {\n return null;\n }\n}\n\n/** Best-effort: clear a notice previously set via {@link setRelayNotice}. */\nexport async function clearRelayNotice(boxId: string, id: string): Promise<void> {\n try {\n await adminPost('/admin/notices/clear', { boxId, id });\n } catch {\n // best-effort\n }\n}\n\nasync function adminPost(path: string, body: unknown): Promise<void> {\n const json = JSON.stringify(body);\n await new Promise<void>((resolveP, rejectP) => {\n const req = httpRequest(\n {\n host: '127.0.0.1',\n port: PORT,\n method: 'POST',\n path,\n headers: {\n 'Content-Type': 'application/json',\n 'Content-Length': Buffer.byteLength(json).toString(),\n },\n timeout: 3000,\n },\n (res) => {\n const chunks: Buffer[] = [];\n res.on('data', (c: Buffer) => chunks.push(c));\n res.on('end', () => {\n const status = res.statusCode ?? 0;\n if (status >= 200 && status < 300) {\n resolveP();\n } else {\n const text = Buffer.concat(chunks).toString('utf8');\n rejectP(new Error(`relay ${path} → ${String(status)}: ${text}`));\n }\n });\n },\n );\n req.on('error', rejectP);\n req.on('timeout', () => {\n req.destroy();\n rejectP(new Error(`relay ${path} timeout`));\n });\n req.write(json);\n req.end();\n });\n}\n\n/** Like {@link adminPost} but resolves with the parsed JSON response body. */\nasync function adminPostForJson(path: string, body: unknown): Promise<unknown> {\n const json = JSON.stringify(body);\n return new Promise<unknown>((resolveP, rejectP) => {\n const req = httpRequest(\n {\n host: '127.0.0.1',\n port: PORT,\n method: 'POST',\n path,\n headers: {\n 'Content-Type': 'application/json',\n 'Content-Length': Buffer.byteLength(json).toString(),\n },\n timeout: 3000,\n },\n (res) => {\n const chunks: Buffer[] = [];\n res.on('data', (c: Buffer) => chunks.push(c));\n res.on('end', () => {\n const status = res.statusCode ?? 0;\n if (status < 200 || status >= 300) {\n rejectP(new Error(`relay ${path} → ${String(status)}`));\n return;\n }\n const text = Buffer.concat(chunks).toString('utf8');\n try {\n resolveP(text.length > 0 ? JSON.parse(text) : {});\n } catch (err) {\n rejectP(err instanceof Error ? err : new Error(String(err)));\n }\n });\n res.on('error', rejectP);\n },\n );\n req.on('error', rejectP);\n req.on('timeout', () => {\n req.destroy();\n rejectP(new Error(`relay ${path} timeout`));\n });\n req.write(json);\n req.end();\n });\n}\n\nexport interface BoxWithToken {\n id: string;\n name: string;\n /** Sandbox backend the box runs on. Defaults to 'docker' when absent. */\n provider?: 'docker' | 'cloud' | string;\n container?: string;\n createdAt?: string;\n relayToken?: string;\n projectIndex?: number;\n gitWorktrees?: GitWorktreeRecord[];\n /** Cloud-only: which backend (e.g. 'daytona') drives this box. */\n cloudBackend?: string;\n /** Cloud-only: in-sandbox /bridge URL + tokens (from BoxRecord.cloud). */\n relayPreviewUrl?: string;\n relayPreviewToken?: string;\n bridgeToken?: string;\n}\n\n/**\n * Re-push every known (id, token) to the relay's in-memory registry. Called\n * after `ensureRelay()` so a fresh / restarted relay learns about boxes that\n * were created in a previous CLI invocation — and, for cloud boxes,\n * restarts the host-side `CloudBoxPoller`.\n */\nexport async function rehydrateRelayRegistry(boxes: BoxWithToken[]): Promise<void> {\n for (const b of boxes) {\n if (!b.relayToken) continue;\n const kind = b.provider === 'docker' || b.provider === undefined ? 'docker' : 'cloud';\n try {\n await registerBoxWithRelay({\n boxId: b.id,\n token: b.relayToken,\n name: b.name,\n kind,\n backend: kind === 'cloud' ? b.cloudBackend : undefined,\n containerName: b.container,\n createdAt: b.createdAt,\n projectIndex: b.projectIndex,\n worktrees: b.gitWorktrees,\n previewUrl: b.relayPreviewUrl,\n previewToken: b.relayPreviewToken,\n bridgeToken: b.bridgeToken,\n });\n } catch {\n // best-effort\n }\n }\n}\n\nexport { RELAY_CONTAINER_NAME, RELAY_NETWORK_NAME, RELAY_IMAGE_REF, DEFAULT_RELAY_PORT };\n","import { ensureVolume, execInBox, type DockerExecResult } from './docker.js';\n\nexport type IdeFlavor = 'vscode' | 'cursor';\n\ninterface IdeProfile {\n /** Container path the IDE's server installs into. */\n serverDir: string;\n /** Container path of the extensions subdir under serverDir. */\n extensionsDir: string;\n /** Per-box volume name = perBoxVolumePrefix + boxId. */\n perBoxVolumePrefix: string;\n /** Shared extensions volume name (never auto-removed). */\n sharedExtensionsVolume: string;\n /** Host CLI binary that opens this IDE (`code` / `cursor`). */\n cli: string;\n /** Human-readable label used in CLI output. */\n displayName: string;\n /** macOS protocol scheme for the `open` fallback (no trailing colon). */\n protocolScheme: string;\n}\n\nconst PROFILES: Record<IdeFlavor, IdeProfile> = {\n vscode: {\n serverDir: '/home/vscode/.vscode-server',\n extensionsDir: '/home/vscode/.vscode-server/extensions',\n perBoxVolumePrefix: 'agentbox-vscode-server-',\n sharedExtensionsVolume: 'agentbox-vscode-extensions',\n cli: 'code',\n displayName: 'VS Code',\n protocolScheme: 'vscode',\n },\n cursor: {\n serverDir: '/home/vscode/.cursor-server',\n extensionsDir: '/home/vscode/.cursor-server/extensions',\n perBoxVolumePrefix: 'agentbox-cursor-server-',\n sharedExtensionsVolume: 'agentbox-cursor-extensions',\n cli: 'cursor',\n displayName: 'Cursor',\n protocolScheme: 'cursor',\n },\n};\n\nexport const IDE_FLAVORS: readonly IdeFlavor[] = ['vscode', 'cursor'];\n\nexport function ideProfile(flavor: IdeFlavor): IdeProfile {\n return PROFILES[flavor];\n}\n\n/**\n * Shared across all boxes. Holds downloaded VS Code extensions so the second\n * box onward doesn't re-download them. Never auto-removed by destroy/prune\n * (parallel to SHARED_CLAUDE_VOLUME).\n */\nexport const SHARED_VSCODE_EXTENSIONS_VOLUME = PROFILES.vscode.sharedExtensionsVolume;\n\n/** Same idea for Cursor's downloaded extensions. */\nexport const SHARED_CURSOR_EXTENSIONS_VOLUME = PROFILES.cursor.sharedExtensionsVolume;\n\n/** Per-box VS Code server volume name. Holds server binary + TS cache + workspace state. */\nexport function vscodeServerVolumeName(boxId: string): string {\n return ideServerVolumeName('vscode', boxId);\n}\n\n/** Per-box Cursor server volume name. */\nexport function cursorServerVolumeName(boxId: string): string {\n return ideServerVolumeName('cursor', boxId);\n}\n\nexport function ideServerVolumeName(flavor: IdeFlavor, boxId: string): string {\n return `${PROFILES[flavor].perBoxVolumePrefix}${boxId}`;\n}\n\nexport interface IdeMounts {\n /** Volume names to ensure() before runBox. */\n volumes: string[];\n /** `-v` arg values to pass to runBox. */\n extraVolumes: string[];\n}\n\n/**\n * Build the volume mounts for one IDE flavor: per-box `.vscode-server` (or\n * `.cursor-server`) mounts first, then the shared extensions volume layered\n * over its `extensions` subdir.\n */\nexport function buildFlavorMounts(flavor: IdeFlavor, boxId: string): IdeMounts {\n const profile = PROFILES[flavor];\n const perBox = ideServerVolumeName(flavor, boxId);\n return {\n volumes: [perBox, profile.sharedExtensionsVolume],\n extraVolumes: [\n `${perBox}:${profile.serverDir}`,\n `${profile.sharedExtensionsVolume}:${profile.extensionsDir}`,\n ],\n };\n}\n\n/** VS Code subset — kept for callers that only want the VS Code mounts. */\nexport function buildVscodeMounts(boxId: string): IdeMounts {\n return buildFlavorMounts('vscode', boxId);\n}\n\n/**\n * All IDE flavors' mounts unioned together. This is what createBox uses so\n * any existing box can be opened with either IDE without recreating.\n */\nexport function buildIdeMounts(boxId: string): IdeMounts {\n const merged: IdeMounts = { volumes: [], extraVolumes: [] };\n for (const f of IDE_FLAVORS) {\n const m = buildFlavorMounts(f, boxId);\n merged.volumes.push(...m.volumes);\n merged.extraVolumes.push(...m.extraVolumes);\n }\n return merged;\n}\n\n/** Ensure VS Code's volumes exist. */\nexport async function ensureVscodeVolumes(boxId: string): Promise<void> {\n for (const v of buildFlavorMounts('vscode', boxId).volumes) await ensureVolume(v);\n}\n\n/** Ensure every IDE flavor's volumes exist. */\nexport async function ensureIdeVolumes(boxId: string): Promise<void> {\n for (const v of buildIdeMounts(boxId).volumes) await ensureVolume(v);\n}\n\n/**\n * Belt-and-suspenders chown of the server trees after the named volumes are\n * mounted. The Dockerfile pre-creates these dirs so first-mount inherits\n * vscode:vscode ownership, but a shared extensions volume might already exist\n * from a box created against an older image where the dirs weren't seeded —\n * in that case the volume is root-owned and the Dev Containers extension\n * fails with \"mkdir: cannot create directory '<server>/bin': Permission\n * denied\". One docker exec fixes it idempotently for both flavors.\n */\nexport async function repairVscodeServerOwnership(container: string): Promise<void> {\n await execInBox(container, ['chown', '-R', 'vscode:vscode', PROFILES.vscode.serverDir], {\n user: 'root',\n });\n}\n\nexport async function repairIdeOwnership(container: string): Promise<void> {\n for (const flavor of IDE_FLAVORS) {\n await execInBox(container, ['chown', '-R', 'vscode:vscode', PROFILES[flavor].serverDir], {\n user: 'root',\n });\n }\n}\n\nexport interface AttachedContainerUriOptions {\n /** Active Docker context (e.g. \"desktop-linux\" / \"orbstack\"). Embedded in\n * the URI's `settings.context` so the Dev Containers extension queries the\n * same daemon agentbox created the container in. Omitted → the extension\n * falls back to its own default context. */\n dockerContext?: string;\n /** Folder opened inside the container (default `/workspace`). */\n workspacePath?: string;\n}\n\n/**\n * Resource URI for an attached container, consumed by `code --folder-uri` /\n * `cursor --folder-uri` (Cursor is a VS Code fork — same URI scheme).\n *\n * The modern Dev Containers extension expects the `attached-container+<hex>`\n * authority to decode to a JSON payload, not a bare name:\n *\n * attached-container+hex({\"containerName\":\"/<name>\",\"settings\":{\"context\":\"<ctx>\"}})\n *\n * `containerName` keeps the leading slash docker's own `.Name` field carries.\n * `settings.context` pins the Docker context — without it, after switching\n * engines (OrbStack ⇄ Docker Desktop) the extension probes the wrong daemon\n * and reports the container as non-existent (\"…because it no longer exists\").\n *\n * Note: the `vscode://vscode-remote/...` protocol-handler form looks similar\n * but goes through macOS `open`, which percent-encodes the `+` authority\n * separator into `%2B` and the extension then fails to resolve it. Use\n * `<cli> --folder-uri <this>` to bypass that.\n */\nexport function attachedContainerUri(\n containerName: string,\n opts: AttachedContainerUriOptions = {},\n): string {\n const workspacePath = opts.workspacePath ?? '/workspace';\n const payload: { containerName: string; settings?: { context: string } } = {\n containerName: containerName.startsWith('/') ? containerName : `/${containerName}`,\n };\n if (opts.dockerContext) payload.settings = { context: opts.dockerContext };\n const hex = Buffer.from(JSON.stringify(payload), 'utf8').toString('hex');\n return `vscode-remote://attached-container+${hex}${workspacePath}`;\n}\n\n/**\n * agentbox-managed `.vscode/tasks.json` lives in the overlay's upper layer so\n * it doesn't pollute the host's working tree. The sentinel comment lets us\n * detect our own file and regenerate it on every `agentbox code` invocation\n * without overwriting a user-authored one. The file lives at `.vscode/` —\n * Cursor reads the same path (VS Code fork), so no per-IDE variant needed.\n */\nconst SENTINEL =\n '// agentbox-managed: regenerated on `agentbox code`; remove this header to take ownership';\n\nexport type ServiceTailHint = { name: string };\n\nexport interface EnsureTasksFileResult {\n status: 'wrote' | 'skipped-user-owned' | 'skipped-no-services';\n}\n\n/**\n * Write (or skip) `/workspace/.vscode/tasks.json` inside the container. Each\n * service in `services` gets a background task that tails its log so the IDE\n * shows a dedicated terminal panel on attach.\n *\n * - File absent → write.\n * - File present with our sentinel → overwrite.\n * - File present without sentinel → skip (user owns it). Caller can force\n * by setting `regen: true`.\n */\nexport async function ensureAgentboxTasksFile(\n container: string,\n services: ServiceTailHint[],\n opts: { regen?: boolean } = {},\n): Promise<EnsureTasksFileResult> {\n if (services.length === 0) return { status: 'skipped-no-services' };\n\n const existing = await execInBox(container, ['cat', '/workspace/.vscode/tasks.json'], {\n user: 'vscode',\n });\n if (existing.exitCode === 0 && !opts.regen && !existing.stdout.includes(SENTINEL)) {\n return { status: 'skipped-user-owned' };\n }\n\n const tasks = services.map((s) => ({\n label: `agentbox: ${s.name}`,\n type: 'shell',\n command: `tail -F /var/log/agentbox/${s.name}.log`,\n isBackground: true,\n presentation: { panel: 'dedicated', reveal: 'always', echo: false },\n runOptions: { runOn: 'folderOpen' },\n problemMatcher: [] as unknown[],\n }));\n const body =\n `${SENTINEL}\\n` +\n JSON.stringify(\n {\n version: '2.0.0',\n tasks,\n },\n null,\n 2,\n ) +\n '\\n';\n\n await execInBox(container, ['mkdir', '-p', '/workspace/.vscode'], { user: 'vscode' });\n const write = await writeFileInBox(container, '/workspace/.vscode/tasks.json', body);\n if (write.exitCode !== 0) {\n throw new Error(`failed to write tasks.json in ${container}: ${write.stderr || write.stdout}`);\n }\n return { status: 'wrote' };\n}\n\nasync function writeFileInBox(\n container: string,\n path: string,\n content: string,\n): Promise<DockerExecResult> {\n const { execa } = await import('execa');\n const result = await execa(\n 'docker',\n ['exec', '-i', '--user', 'vscode', container, 'sh', '-c', `cat > ${shellQuote(path)}`],\n { input: content, reject: false },\n );\n return {\n exitCode: result.exitCode ?? -1,\n stdout: result.stdout ?? '',\n stderr: result.stderr ?? '',\n };\n}\n\nfunction shellQuote(s: string): string {\n return `'${s.replace(/'/g, `'\\\\''`)}'`;\n}\n\n// Backward-compat alias for the previous mount type name.\nexport type VscodeMounts = IdeMounts;\n","import { execa } from 'execa';\nimport { readdir, rm, stat } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { BoxState } from '@agentbox/core';\nimport { AmbiguousBoxError, BoxNotFoundError } from '@agentbox/core';\nimport type { AgentActivityState, BoxStatus, ClaudeActivityState } from '@agentbox/ctl';\nimport { claudeSessionInfo, SHARED_CLAUDE_VOLUME, type ClaudeSessionInfo } from './claude.js';\nimport { codexSessionInfo, SHARED_CODEX_VOLUME, type CodexSessionInfo } from './codex.js';\nimport {\n opencodeSessionInfo,\n SHARED_OPENCODE_VOLUME,\n type OpencodeSessionInfo,\n} from './opencode.js';\nimport { listShellSessions, type ShellSessionSummary } from './shell-session.js';\nimport { bindWorktrees, removeInBoxWorktree } from './in-box-git.js';\nimport {\n cursorServerVolumeName,\n SHARED_CURSOR_EXTENSIONS_VOLUME,\n SHARED_VSCODE_EXTENSIONS_VOLUME,\n vscodeServerVolumeName,\n} from './vscode.js';\nimport {\n BOXES_ROOT,\n boxRunDirFor,\n detectEngine,\n getHostPaths,\n openInFinder,\n readBoxStatus,\n type HostPaths,\n type OpenOptions,\n type OpenResult,\n} from './host-export.js';\nimport {\n inspectContainer,\n inspectContainerStatus,\n listAgentboxContainers,\n listAgentboxVolumes,\n pauseContainer,\n publishedHostPort,\n removeContainer,\n removeImage,\n removeNetwork,\n removeVolume,\n startContainer,\n stopContainer,\n unpauseContainer,\n} from './docker.js';\nimport { CHECKPOINT_IMAGE_PREFIX, listAllCheckpointImages } from './checkpoint.js';\nimport { launchCtlDaemon } from './ctl.js';\nimport { ensureHomeOwnedByVscode } from './home-ownership.js';\nimport { launchDockerdDaemon, SHARED_DOCKER_CACHE_VOLUME } from './dockerd.js';\nimport { launchVncDaemon, VNC_CONTAINER_PORT } from './vnc.js';\nimport { WEB_CONTAINER_PORT } from './web.js';\nimport { detectPortless, portlessAlias, portlessGetUrl, portlessUnalias } from './portless.js';\nimport { getBoxEndpoints, type BoxEndpoints } from './endpoints.js';\nimport {\n ensureRelay,\n forgetBoxFromRelay,\n registerBoxWithRelay,\n RELAY_CONTAINER_NAME,\n RELAY_IMAGE_REF,\n RELAY_NETWORK_NAME,\n} from './relay.js';\nimport { SNAPSHOTS_ROOT } from './snapshot.js';\nimport {\n findBox,\n readState,\n recordBox,\n removeBoxRecord,\n type BoxRecord,\n type FindBoxResult,\n} from './state.js';\n\nexport interface ListedBox extends BoxRecord {\n state: BoxState;\n endpoints: BoxEndpoints;\n /** From the persisted status file; undefined for pre-feature/never-pushed boxes. */\n claudeActivity?: ClaudeActivityState;\n /** Sanitized in-box terminal title Claude set; undefined when none. */\n claudeSessionTitle?: string;\n /** Codex activity from the persisted status file (Codex hooks); undefined when none. */\n codexActivity?: AgentActivityState;\n /** Sanitized in-box terminal title the Codex/OpenCode TUI set; undefined when none. */\n codexSessionTitle?: string;\n opencodeSessionTitle?: string;\n /** Live shell tmux sessions; `[]` for non-running boxes (can't `docker exec`). */\n shellSessions: ShellSessionSummary[];\n /** Live probe of the Codex tmux session; null when the box isn't running. */\n codexSession: CodexSessionInfo | null;\n /** Live probe of the OpenCode tmux session; null when the box isn't running. */\n opencodeSession: OpencodeSessionInfo | null;\n}\n\nexport async function listBoxes(): Promise<ListedBox[]> {\n const { boxes } = await readState();\n const engine = await detectEngine();\n return Promise.all(\n boxes.map(async (b): Promise<ListedBox> => {\n // Cloud boxes don't have a host Docker container — skip every Docker\n // probe (inspect / exec / shell-session). Their state is optimistically\n // 'running' (a real probe would round-trip the cloud SDK on every list;\n // tracked for Phase 6); endpoints come from the cloud.previewUrls map\n // populated at create/start; agent activity rides the persisted status\n // snapshot the host poller mirrors from the in-sandbox relay.\n if (b.provider && b.provider !== 'docker') {\n const persisted = await readBoxStatus(b);\n const webPort = b.cloud?.webPort ?? 0;\n // Prefer the stable Portless URL when one was registered (Hetzner\n // gets it by default; Daytona naturally skips because its URL isn't\n // loopback). Falls back to the ephemeral `127.0.0.1:<random>`\n // preview URL when Portless isn't in play.\n const portlessWebUrl =\n b.portlessAlias !== undefined\n ? (b.portlessUrl ?? `https://${b.portlessAlias}.localhost`)\n : undefined;\n const cachedWebUrl = webPort > 0 ? b.cloud?.previewUrls?.[webPort] : undefined;\n const webUrl = portlessWebUrl ?? cachedWebUrl;\n const endpoints: BoxEndpoints = {\n domain: webUrl ? safeHost(webUrl) : '',\n domainIsOrb: false,\n endpoints: webUrl\n ? [\n {\n kind: 'web',\n name: 'web',\n containerPort: webPort,\n url: webUrl,\n reachable: true,\n },\n ]\n : [],\n };\n return {\n ...b,\n state: 'running',\n endpoints,\n claudeActivity: persisted?.claude.state,\n claudeSessionTitle: persisted?.claude.sessionTitle,\n codexActivity: persisted?.codex?.state,\n codexSessionTitle: persisted?.codex?.sessionTitle,\n opencodeSessionTitle: persisted?.opencode?.sessionTitle,\n shellSessions: [],\n codexSession: null,\n opencodeSession: null,\n };\n }\n const state = await inspectContainerStatus(b.container);\n const persisted = await readBoxStatus(b);\n const endpoints = await getBoxEndpoints(b, engine, persisted);\n // Shell sessions + the codex session probe are live tmux state — only a\n // running container is reachable via `docker exec`; paused/stopped report\n // none. (Claude activity rides the persisted status snapshot instead, so\n // it needs no live probe.)\n const shellSessions =\n state === 'running' ? await listShellSessions(b.container) : [];\n const codexSession =\n state === 'running' ? await codexSessionInfo(b.container) : null;\n const opencodeSession =\n state === 'running' ? await opencodeSessionInfo(b.container) : null;\n return {\n ...b,\n state,\n endpoints,\n claudeActivity: persisted?.claude.state,\n claudeSessionTitle: persisted?.claude.sessionTitle,\n codexActivity: persisted?.codex?.state,\n codexSessionTitle: persisted?.codex?.sessionTitle,\n opencodeSessionTitle: persisted?.opencode?.sessionTitle,\n shellSessions,\n codexSession,\n opencodeSession,\n };\n }),\n );\n}\n\n/** Extract host from a URL string; empty when unparseable. */\nfunction safeHost(url: string): string {\n try {\n return new URL(url).host;\n } catch {\n return '';\n }\n}\n\n// The box-resolution error classes are provider-neutral; they live in\n// @agentbox/core. Imported for local `throw`s below and re-exported so existing\n// `@agentbox/sandbox-docker` consumers keep importing them from here.\nexport { AmbiguousBoxError, BoxNotFoundError };\n\nasync function pathExists(p: string): Promise<boolean> {\n try {\n await stat(p);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function resolveBox(idOrName: string): Promise<BoxRecord> {\n const state = await readState();\n const result: FindBoxResult = findBox(idOrName, state);\n switch (result.kind) {\n case 'ok':\n return result.box;\n case 'none':\n throw new BoxNotFoundError(idOrName);\n case 'ambiguous':\n throw new AmbiguousBoxError(idOrName, result.matches);\n }\n}\n\nexport async function pauseBox(idOrName: string): Promise<BoxRecord> {\n const box = await resolveBox(idOrName);\n await pauseContainer(box.container);\n return box;\n}\n\nexport async function unpauseBox(idOrName: string): Promise<BoxRecord> {\n const box = await resolveBox(idOrName);\n await unpauseContainer(box.container);\n return box;\n}\n\nexport async function stopBox(idOrName: string): Promise<BoxRecord> {\n const box = await resolveBox(idOrName);\n await stopContainer(box.container);\n return box;\n}\n\nexport interface StartedBox {\n record: BoxRecord;\n}\n\n/**\n * Re-start a stopped box.\n *\n * /workspace is just the container's writable filesystem now, so there's no\n * overlay to remount — `docker start` brings everything back. The in-box\n * supervisor, dockerd, and Xvnc all die with the container, so we relaunch\n * them via the same exec-d helpers `create` used. Ephemeral host ports for\n * VNC + web get re-allocated by Docker on `start`, so we re-resolve and\n * persist those too.\n */\nexport async function startBox(idOrName: string): Promise<StartedBox> {\n const box = await resolveBox(idOrName);\n // .git bind-mounts are baked into the container at create time; if a host\n // main repo's .git/ has been deleted out from under us, restart fails with\n // an opaque mount error. Surface it loudly.\n for (const w of box.gitWorktrees ?? []) {\n if (!(await pathExists(join(w.hostMainRepo, '.git')))) {\n throw new Error(\n `main repo for box worktree missing: ${join(w.hostMainRepo, '.git')} (recreate the box)`,\n );\n }\n }\n await startContainer(box.container);\n\n // /workspace bind mounts don't survive `docker stop` (the mount namespace\n // is recreated on start). Re-bind each registered worktree before any\n // daemon comes up — the supervisor and dockerd may resolve paths under\n // /workspace and would see the image's empty dir without this.\n if ((box.gitWorktrees ?? []).length > 0) {\n await bindWorktrees(\n box.container,\n (box.gitWorktrees ?? []).map((w) => ({\n kind: w.kind,\n containerPath: w.containerPath,\n gitWorktreePath: w.gitWorktreePath,\n })),\n );\n }\n\n // Re-own /home/vscode to vscode in case root-run steps left files behind\n // (see ensureHomeOwnedByVscode). Best-effort; safe to repeat.\n await ensureHomeOwnedByVscode(box.container);\n\n if (box.socketPath) {\n // The daemon died with the container; relaunch it. Best-effort, same as\n // create.ts — a missing config or other startup issue shouldn't block\n // resumption of the box itself.\n await launchCtlDaemon(box.container, box.socketPath);\n }\n if (box.dockerVolume) {\n await launchDockerdDaemon(box.container);\n }\n if (box.vncEnabled) {\n // Xvnc + websockify both die with the container. The password is already\n // in the container env (set at `docker run` time and preserved across\n // start/stop), so we don't need to forward it here.\n await launchVncDaemon(box.container);\n // Docker re-allocates an ephemeral host port for `-p 0:6080` on every\n // `start`. Re-resolve and persist; the orb.local URL is name-based and\n // unaffected. Best-effort — a failed resolve just leaves the record as-is.\n const freshHostPort = await publishedHostPort(box.container, VNC_CONTAINER_PORT);\n if (freshHostPort && freshHostPort !== box.vncHostPort) {\n box.vncHostPort = freshHostPort;\n await recordBox(box);\n }\n }\n // Same ephemeral-reallocation story for the reserved web port. Gated on\n // webContainerPort so pre-feature boxes (no `-p 0:80` mapping) are skipped.\n if (box.webContainerPort !== undefined) {\n const freshWebPort = await publishedHostPort(\n box.container,\n box.webContainerPort ?? WEB_CONTAINER_PORT,\n );\n if (freshWebPort && freshWebPort !== box.webHostPort) {\n box.webHostPort = freshWebPort;\n await recordBox(box);\n }\n // Docker reallocated the host port, so the Portless route now points at a\n // stale port — re-register it. Best-effort and silent (startBox has no\n // onLog); if the proxy/Portless is gone the box still works on loopback.\n if (box.portlessAlias && box.webHostPort) {\n try {\n const portless = await detectPortless();\n if (portless.installed) {\n await portlessAlias(box.portlessAlias, box.webHostPort);\n // The proxy's scheme/port can change between sessions — re-resolve.\n const url = await portlessGetUrl(box.portlessAlias);\n if (url !== box.portlessUrl) {\n box.portlessUrl = url;\n await recordBox(box);\n }\n }\n } catch {\n /* best-effort */\n }\n }\n }\n // Relay's in-memory registry may have been lost if the relay restarted\n // between create and now (or this is the first start after a host reboot).\n // Re-ensure + re-register so outbound push from the box keeps working.\n if (box.relayToken) {\n try {\n await ensureRelay();\n await registerBoxWithRelay({\n boxId: box.id,\n token: box.relayToken,\n name: box.name,\n containerName: box.container,\n createdAt: box.createdAt,\n projectIndex: box.projectIndex,\n worktrees: box.gitWorktrees,\n });\n } catch {\n // best-effort\n }\n }\n return { record: box };\n}\n\nexport interface OpenedBox extends OpenResult {\n record: BoxRecord;\n}\n\nexport async function openBoxInFinder(idOrName: string, opts: OpenOptions): Promise<OpenedBox> {\n const box = await resolveBox(idOrName);\n const result = await openInFinder(box, opts);\n return { ...result, record: box };\n}\n\nexport async function getBoxHostPaths(\n idOrName: string,\n): Promise<{ record: BoxRecord; paths: HostPaths }> {\n const box = await resolveBox(idOrName);\n const paths = await getHostPaths(box);\n return { record: box, paths };\n}\n\nexport interface InspectedBox {\n record: BoxRecord;\n state: BoxState;\n snapshotSizeBytes: number | null;\n dockerInspect: unknown;\n /** Null when the container isn't running; otherwise best-effort probe of the tmux 'claude' session. */\n claudeSession: ClaudeSessionInfo | null;\n /** Null when the container isn't running; otherwise best-effort probe of the tmux 'codex' session. */\n codexSession: CodexSessionInfo | null;\n /** Null when the container isn't running; otherwise best-effort probe of the tmux 'opencode' session. */\n opencodeSession: OpencodeSessionInfo | null;\n /** Live shell tmux sessions; `[]` when the container isn't running. */\n shellSessions: ShellSessionSummary[];\n /** Persisted status snapshot (services/tasks/ports/claude); null when none. */\n persistedStatus: BoxStatus | null;\n /** Host paths for `agentbox open`. */\n hostPaths: HostPaths;\n /** Box network surface: domain + VNC + service ports. */\n endpoints: BoxEndpoints;\n}\n\nasync function dirSizeBytes(path: string): Promise<number | null> {\n try {\n const result = await execa('du', ['-sk', path], { reject: false });\n if (result.exitCode !== 0) return null;\n const sizeKb = Number.parseInt((result.stdout ?? '').split(/\\s+/)[0] ?? '', 10);\n if (Number.isNaN(sizeKb)) return null;\n return sizeKb * 1024;\n } catch {\n return null;\n }\n}\n\nexport async function inspectBox(idOrName: string): Promise<InspectedBox> {\n const record = await resolveBox(idOrName);\n const state = await inspectContainerStatus(record.container);\n const snapshotSizeBytes = record.snapshotDir ? await dirSizeBytes(record.snapshotDir) : null;\n const dockerJson = await inspectContainer(record.container);\n\n let claudeSession: ClaudeSessionInfo | null = null;\n let codexSession: CodexSessionInfo | null = null;\n let opencodeSession: OpencodeSessionInfo | null = null;\n let shellSessions: ShellSessionSummary[] = [];\n if (state === 'running') {\n try {\n claudeSession = await claudeSessionInfo(record.container);\n } catch {\n claudeSession = null;\n }\n try {\n codexSession = await codexSessionInfo(record.container);\n } catch {\n codexSession = null;\n }\n try {\n opencodeSession = await opencodeSessionInfo(record.container);\n } catch {\n opencodeSession = null;\n }\n shellSessions = await listShellSessions(record.container);\n }\n\n const hostPaths = await getHostPaths(record);\n const engine = await detectEngine();\n const persistedStatus = await readBoxStatus(record);\n const endpoints = await getBoxEndpoints(record, engine, persistedStatus);\n\n return {\n record,\n state,\n snapshotSizeBytes,\n dockerInspect: dockerJson,\n claudeSession,\n codexSession,\n opencodeSession,\n shellSessions,\n persistedStatus,\n hostPaths,\n endpoints,\n };\n}\n\nexport interface DestroyOptions {\n keepSnapshot?: boolean;\n}\n\nexport interface DestroyResult {\n record: BoxRecord;\n removedContainer: boolean;\n removedVolumes: string[];\n removedSnapshot: string | null;\n}\n\nexport async function destroyBox(\n idOrName: string,\n opts: DestroyOptions = {},\n): Promise<DestroyResult> {\n const box = await resolveBox(idOrName);\n\n // Each step is best-effort. We collect what actually went away so the CLI\n // can show a truthful summary even if e.g. the container was gone already.\n if (box.relayToken) {\n try {\n await forgetBoxFromRelay(box.id);\n } catch {\n // best-effort — relay may be down or already wiped the entry\n }\n }\n // Remove the Portless route so it doesn't dangle in the user's proxy config.\n if (box.portlessAlias) {\n try {\n await portlessUnalias(box.portlessAlias);\n } catch {\n // best-effort — Portless may be uninstalled or the route already gone\n }\n }\n // Deregister each in-container worktree from the host main repo. Skip\n // when this box was checkpoint-restored: its `gitWorktrees` were inherited\n // from the source box via the checkpoint manifest, and the same\n // `gitWorktreePath` may still be in use by the source (or by sibling\n // restores). Removing the registration here would break those. The\n // registration is cosmetically `prunable` on the host anyway (the path is\n // container-only) and can be reaped with `git worktree prune` when the\n // user is sure no box references it.\n const ownsWorktrees = !box.checkpointImage;\n if (ownsWorktrees) {\n for (const w of box.gitWorktrees ?? []) {\n try {\n await removeInBoxWorktree({\n hostMainRepo: w.hostMainRepo,\n gitWorktreePath: w.gitWorktreePath,\n });\n } catch {\n // best-effort\n }\n }\n }\n const beforeContainer = await inspectContainerStatus(box.container);\n await removeContainer(box.container);\n const afterContainer = await inspectContainerStatus(box.container);\n const removedContainer = beforeContainer !== 'missing' && afterContainer === 'missing';\n\n const removedVolumes: string[] = [];\n // Per-box claude config volumes are box-private — safe to remove. The shared\n // SHARED_CLAUDE_VOLUME holds user identity (auth, skills, plugins) across\n // every box, so never auto-remove it; users delete it manually if they want.\n if (box.claudeConfigVolume && box.claudeConfigVolume !== SHARED_CLAUDE_VOLUME) {\n await removeVolume(box.claudeConfigVolume);\n removedVolumes.push(box.claudeConfigVolume);\n }\n // Same reasoning for the codex-config volume: per-box variants are private\n // and removed here; the shared SHARED_CODEX_VOLUME holds the user's Codex\n // auth across boxes and is never auto-removed.\n if (box.codexConfigVolume && box.codexConfigVolume !== SHARED_CODEX_VOLUME) {\n await removeVolume(box.codexConfigVolume);\n removedVolumes.push(box.codexConfigVolume);\n }\n // Same for the opencode-config volume.\n if (box.opencodeConfigVolume && box.opencodeConfigVolume !== SHARED_OPENCODE_VOLUME) {\n await removeVolume(box.opencodeConfigVolume);\n removedVolumes.push(box.opencodeConfigVolume);\n }\n // Per-box `.vscode-server` and `.cursor-server` volumes. The shared\n // SHARED_*_EXTENSIONS_VOLUMEs are never auto-removed (parallel reasoning to\n // the shared claude volume).\n const perBoxIdeVolumes = [\n box.vscodeServerVolume ?? vscodeServerVolumeName(box.id),\n box.cursorServerVolume ?? cursorServerVolumeName(box.id),\n ];\n for (const v of perBoxIdeVolumes) {\n await removeVolume(v);\n removedVolumes.push(v);\n }\n // Per-box dockerd data root. Skip when this box used the shared cache —\n // wiping it would also remove image layers other boxes (or future ones)\n // depend on. The shared volume is allowlisted in `pruneBoxes --all` too.\n if (box.dockerVolume && !box.dockerCacheShared) {\n await removeVolume(box.dockerVolume);\n removedVolumes.push(box.dockerVolume);\n }\n\n let removedSnapshot: string | null = null;\n if (box.snapshotDir && !opts.keepSnapshot) {\n try {\n await rm(box.snapshotDir, { recursive: true, force: true });\n removedSnapshot = box.snapshotDir;\n } catch {\n removedSnapshot = null;\n }\n }\n\n // The per-box runtime dir holds the ctl socket plus the workspace export\n // dir used by `agentbox open`. Wipe the whole thing so destroy leaves no\n // residue under ~/.agentbox/boxes/.\n try {\n await rm(boxRunDirFor(box), { recursive: true, force: true });\n } catch {\n // best-effort\n }\n\n await removeBoxRecord(box.id);\n\n return { record: box, removedContainer, removedVolumes, removedSnapshot };\n}\n\nexport interface PruneOptions {\n dryRun?: boolean;\n all?: boolean;\n}\n\nexport interface PruneResult {\n removedRecords: string[];\n removedContainers: string[];\n removedVolumes: string[];\n removedSnapshotDirs: string[];\n removedBoxDirs: string[];\n removedCheckpointImages: string[];\n dryRun: boolean;\n}\n\nasync function listSnapshotDirs(): Promise<string[]> {\n try {\n const entries = await readdir(SNAPSHOTS_ROOT, { withFileTypes: true });\n return entries.filter((e) => e.isDirectory()).map((e) => join(SNAPSHOTS_ROOT, e.name));\n } catch {\n return [];\n }\n}\n\nasync function listBoxDirs(): Promise<string[]> {\n try {\n const entries = await readdir(BOXES_ROOT, { withFileTypes: true });\n return entries.filter((e) => e.isDirectory()).map((e) => join(BOXES_ROOT, e.name));\n } catch {\n return [];\n }\n}\n\n/**\n * Local Docker image *tags* that look like checkpoint images\n * (`agentbox-ckpt-<projectHash>:<name>`). Used by `prune --all` to find\n * candidates for reaping. An image is reapable only when **both** of these\n * are true: no surviving box's `checkpointImage` points at it, **and** no\n * on-disk manifest under `~/.agentbox/checkpoints/<projectHash>/<name>/`\n * names it as its `image` (see `listAllCheckpointImages`) — otherwise a\n * `destroy` + `prune --all` would silently break checkpoints the user still\n * intends to start new boxes from. Best-effort: returns empty on docker\n * errors.\n */\nasync function listCheckpointImageTags(): Promise<string[]> {\n const r = await execa(\n 'docker',\n ['image', 'ls', '--format', '{{.Repository}}:{{.Tag}}', `${CHECKPOINT_IMAGE_PREFIX}*`],\n { reject: false },\n );\n if (r.exitCode !== 0) return [];\n return (r.stdout ?? '')\n .split('\\n')\n .map((s) => s.trim())\n .filter((s) => s.startsWith(CHECKPOINT_IMAGE_PREFIX));\n}\n\nexport async function pruneBoxes(opts: PruneOptions = {}): Promise<PruneResult> {\n const dryRun = opts.dryRun ?? false;\n const all = opts.all ?? false;\n\n const { boxes } = await readState();\n\n // Step 1: missing-state records.\n const stateChecks = await Promise.all(\n boxes.map(async (b) => ({ box: b, status: await inspectContainerStatus(b.container) })),\n );\n const missingRecords = stateChecks.filter((c) => c.status === 'missing').map((c) => c.box);\n\n // Step 2 (only with --all): orphan docker containers / volumes / snapshot\n // dirs / per-box dirs / unreferenced checkpoint images.\n let orphanContainers: string[] = [];\n let orphanVolumes: string[] = [];\n let orphanSnapshots: string[] = [];\n let orphanBoxDirs: string[] = [];\n let orphanCheckpointImages: string[] = [];\n\n if (all) {\n const liveContainers = await listAgentboxContainers();\n const liveVolumes = await listAgentboxVolumes();\n const liveSnapshotDirs = await listSnapshotDirs();\n const liveBoxDirs = await listBoxDirs();\n const liveCheckpointImages = await listCheckpointImageTags();\n // Manifests on disk are the durable source of truth for \"this checkpoint\n // exists\" — `destroyBox` leaves them alone on purpose, so an image whose\n // source box was destroyed is still pinned as long as its manifest is\n // there.\n const manifestPinnedImages = await listAllCheckpointImages();\n // The state we'd have AFTER step 1 runs: missing-state records gone.\n const survivingBoxes = boxes.filter((b) => !missingRecords.some((m) => m.id === b.id));\n const expectedContainers = new Set<string>([\n ...survivingBoxes.map((b) => b.container),\n // The relay no longer runs as a container; leftovers are collected\n // below.\n ]);\n const expectedVolumes = new Set<string>([\n ...survivingBoxes\n .map((b) => b.claudeConfigVolume)\n .filter((v): v is string => typeof v === 'string'),\n ...survivingBoxes\n .map((b) => b.codexConfigVolume)\n .filter((v): v is string => typeof v === 'string'),\n ...survivingBoxes\n .map((b) => b.opencodeConfigVolume)\n .filter((v): v is string => typeof v === 'string'),\n ...survivingBoxes\n .map((b) => b.vscodeServerVolume)\n .filter((v): v is string => typeof v === 'string'),\n ...survivingBoxes\n .map((b) => b.cursorServerVolume)\n .filter((v): v is string => typeof v === 'string'),\n ...survivingBoxes\n .map((b) => b.dockerVolume)\n .filter((v): v is string => typeof v === 'string'),\n // The shared claude-config volume holds user identity across every box;\n // never reap it via prune even if no surviving box currently references it.\n SHARED_CLAUDE_VOLUME,\n // The shared codex-config volume — same reasoning (holds Codex auth).\n SHARED_CODEX_VOLUME,\n // The shared opencode-config volume — same reasoning (holds OpenCode auth).\n SHARED_OPENCODE_VOLUME,\n // Shared across boxes: downloaded IDE extensions. Same reasoning.\n SHARED_VSCODE_EXTENSIONS_VOLUME,\n SHARED_CURSOR_EXTENSIONS_VOLUME,\n // Shared in-box docker image cache — opt-in via `box.dockerCacheShared`,\n // never auto-removed (image layers may be reused by future boxes).\n SHARED_DOCKER_CACHE_VOLUME,\n ]);\n const expectedSnapshots = new Set(\n survivingBoxes\n .filter((b): b is BoxRecord & { snapshotDir: string } =>\n typeof b.snapshotDir === 'string',\n )\n .map((b) => b.snapshotDir),\n );\n const expectedBoxDirs = new Set(survivingBoxes.map((b) => boxRunDirFor(b)));\n // Checkpoint images: keep any tag that either a surviving box's\n // `checkpointImage` points at, or that any on-disk manifest still claims\n // as its `image`. The manifest case is the one that matters most after\n // destroy: the source box is gone but the user still wants to seed new\n // boxes from the checkpoint. The surviving-box case stays as a fallback\n // for the edge where someone `rm -rf`'d a manifest dir while a box\n // restored from it is still running.\n const expectedCheckpointImages = new Set<string>([\n ...survivingBoxes\n .map((b) => b.checkpointImage)\n .filter((v): v is string => typeof v === 'string'),\n ...manifestPinnedImages,\n ]);\n orphanContainers = liveContainers.filter((c) => !expectedContainers.has(c));\n orphanVolumes = liveVolumes.filter((v) => !expectedVolumes.has(v));\n orphanSnapshots = liveSnapshotDirs.filter((d) => !expectedSnapshots.has(d));\n orphanBoxDirs = liveBoxDirs.filter((d) => !expectedBoxDirs.has(d));\n orphanCheckpointImages = liveCheckpointImages.filter(\n (t) => !expectedCheckpointImages.has(t),\n );\n }\n\n if (dryRun) {\n return {\n removedRecords: missingRecords.map((b) => b.id),\n removedContainers: orphanContainers,\n removedVolumes: orphanVolumes,\n removedSnapshotDirs: orphanSnapshots,\n removedBoxDirs: orphanBoxDirs,\n removedCheckpointImages: orphanCheckpointImages,\n dryRun: true,\n };\n }\n\n for (const b of missingRecords) await removeBoxRecord(b.id);\n for (const c of orphanContainers) await removeContainer(c);\n for (const v of orphanVolumes) await removeVolume(v);\n for (const d of orphanSnapshots) {\n try {\n await rm(d, { recursive: true, force: true });\n } catch {\n // best-effort\n }\n }\n for (const d of orphanBoxDirs) {\n try {\n await rm(d, { recursive: true, force: true });\n } catch {\n // best-effort\n }\n }\n for (const img of orphanCheckpointImages) {\n await removeImage(img, { force: true });\n }\n\n // Migration sweep: the relay used to be a docker container on a dedicated\n // network with its own image. None of those exist after this version of\n // agentbox; drop any leftovers from previous installs. Idempotent and\n // best-effort — these calls succeed silently if the objects are already\n // gone.\n if (all) {\n try {\n await removeContainer(RELAY_CONTAINER_NAME);\n } catch {\n // best-effort\n }\n try {\n await execa('docker', ['image', 'rm', RELAY_IMAGE_REF], { reject: false });\n } catch {\n // best-effort\n }\n try {\n await removeNetwork(RELAY_NETWORK_NAME);\n } catch {\n // best-effort\n }\n }\n\n return {\n removedRecords: missingRecords.map((b) => b.id),\n removedContainers: orphanContainers,\n removedVolumes: orphanVolumes,\n removedSnapshotDirs: orphanSnapshots,\n removedBoxDirs: orphanBoxDirs,\n removedCheckpointImages: orphanCheckpointImages,\n dryRun: false,\n };\n}\n\n// Help vitest / unit tests get to the snapshot-root constant without pulling\n// the whole snapshot module surface.\nexport { SNAPSHOTS_ROOT };\n\n// Re-export the file existence helper for inspect output; useful guard for\n// callers that want to know if a snapshot dir was ever created.\nexport async function snapshotPresent(path: string | null): Promise<boolean> {\n if (!path) return false;\n try {\n const s = await stat(path);\n return s.isDirectory();\n } catch {\n return false;\n }\n}\n","import { execa } from 'execa';\nimport { buildTmuxSessionArgs, CONTAINER_USER } from './claude.js';\n\n/** Default tmux session name for `agentbox shell` (the box's first shell). */\nexport const DEFAULT_SHELL_SESSION = 'shell';\n\n/** Reserved prefix for non-default shell sessions: `shell-2`, `shell-build`, … */\nexport const SHELL_SESSION_PREFIX = `${DEFAULT_SHELL_SESSION}-`;\n\nexport interface StartShellSessionOptions {\n container: string;\n sessionName?: string;\n /** In-container user (default vscode). The tmux server is per-user, so every\n * helper here must agree on it. */\n user?: string;\n /** Pass `-l` to bash so the login profile loads (default true). */\n login?: boolean;\n}\n\nexport interface ShellSessionInfo {\n running: boolean;\n sessionName: string;\n}\n\nexport interface ShellSessionSummary {\n /** User-facing label: `shell` for the default, else the suffix (`2`, `build`). */\n label: string;\n /** Underlying tmux session name. */\n sessionName: string;\n /** Whether at least one client is attached. */\n attached: boolean;\n /** ISO-8601 creation time, or null when tmux didn't report it. */\n createdAt: string | null;\n}\n\n/**\n * Map a user-facing shell label to its tmux session name. The default shell\n * (`undefined` / empty / `shell`) is the bare `shell`; everything else is\n * `shell-<label>`, so every shell session shares the `shell` prefix.\n */\nexport function shellSessionName(label?: string): string {\n const l = (label ?? '').trim();\n if (l === '' || l === DEFAULT_SHELL_SESSION) return DEFAULT_SHELL_SESSION;\n return `${SHELL_SESSION_PREFIX}${l}`;\n}\n\n/** Inverse of {@link shellSessionName}: the user-facing label for a session. */\nexport function shellLabel(sessionName: string): string {\n return sessionName === DEFAULT_SHELL_SESSION\n ? DEFAULT_SHELL_SESSION\n : sessionName.slice(SHELL_SESSION_PREFIX.length);\n}\n\n/**\n * True iff a tmux session name belongs to a shell (vs the `claude` agent\n * session or a dashboard `*-dash` grouped sibling). Pure string rule — no\n * config dependency, never collides with the agent session.\n */\nexport function isShellSessionName(name: string): boolean {\n if (name === DEFAULT_SHELL_SESSION) return true;\n return name.startsWith(SHELL_SESSION_PREFIX) && !name.endsWith('-dash');\n}\n\n/**\n * Pick the lowest-free shell session name given the ones that already exist:\n * `shell`, then `shell-2`, `shell-3`, … (never recycles a name that is in use).\n */\nexport function allocateShellSessionName(existing: readonly string[]): string {\n const taken = new Set(existing);\n if (!taken.has(DEFAULT_SHELL_SESSION)) return DEFAULT_SHELL_SESSION;\n for (let n = 2; ; n++) {\n const candidate = `${SHELL_SESSION_PREFIX}${String(n)}`;\n if (!taken.has(candidate)) return candidate;\n }\n}\n\n/**\n * Parse the tab-separated output of `tmux list-sessions -F\n * '#{session_name}\\t#{session_created}\\t#{session_attached}'`, keeping only\n * shell sessions. Default `shell` sorts first, the rest by creation time.\n */\nexport function parseShellSessionList(stdout: string): ShellSessionSummary[] {\n const out: ShellSessionSummary[] = [];\n for (const line of stdout.split('\\n')) {\n if (line.trim() === '') continue;\n const [name, created, attached] = line.split('\\t');\n if (name === undefined || !isShellSessionName(name)) continue;\n let createdAt: string | null = null;\n const secs = Number.parseInt((created ?? '').trim(), 10);\n if (Number.isFinite(secs) && secs > 0) createdAt = new Date(secs * 1000).toISOString();\n out.push({\n label: shellLabel(name),\n sessionName: name,\n attached: Number.parseInt((attached ?? '0').trim(), 10) > 0,\n createdAt,\n });\n }\n out.sort((a, b) => {\n if (a.sessionName === DEFAULT_SHELL_SESSION) return -1;\n if (b.sessionName === DEFAULT_SHELL_SESSION) return 1;\n return (a.createdAt ?? '').localeCompare(b.createdAt ?? '');\n });\n return out;\n}\n\n/**\n * Enumerate a box's shell tmux sessions. Best-effort: a missing tmux server,\n * a stopped/paused container, or any error surfaces as `[]`.\n */\nexport async function listShellSessions(\n container: string,\n user?: string,\n): Promise<ShellSessionSummary[]> {\n const res = await execa(\n 'docker',\n [\n 'exec',\n '--user',\n user ?? CONTAINER_USER,\n container,\n 'tmux',\n 'list-sessions',\n '-F',\n // Literal tabs reach tmux verbatim (execa array args, no host shell).\n '#{session_name}\\t#{session_created}\\t#{session_attached}',\n ],\n { reject: false },\n );\n if (res.exitCode !== 0) return [];\n return parseShellSessionList(res.stdout ?? '');\n}\n\n/**\n * Start a detached tmux session running `bash` inside the container — the shell\n * counterpart of `startClaudeSession`. The session survives client disconnects;\n * reattach with {@link buildShellSessionAttachArgv}.\n *\n * We forward the host's TERM (default xterm-256color) so the in-container tmux\n * picks the right terminal-overrides at session creation time — without this,\n * docker exec defaults TERM to `xterm` and tmux can't declare 24-bit color.\n */\nexport async function startShellSession(opts: StartShellSessionOptions): Promise<void> {\n const sessionName = opts.sessionName ?? DEFAULT_SHELL_SESSION;\n const user = opts.user ?? CONTAINER_USER;\n const login = opts.login !== false;\n const term = process.env['TERM'] ?? 'xterm-256color';\n // tmux runs a single-arg shell-command via `/bin/sh -c`; `bash -l` / `bash`\n // need no quoting.\n const cmd = login ? 'bash -l' : 'bash';\n const result = await execa(\n 'docker',\n [\n 'exec',\n '-e',\n `TERM=${term}`,\n '--user',\n user,\n opts.container,\n 'tmux',\n 'new-session',\n '-d',\n '-s',\n sessionName,\n cmd,\n ...buildTmuxSessionArgs(sessionName),\n ],\n { reject: false },\n );\n if (result.exitCode === 0) return;\n const stderr = (result.stderr ?? '').toString();\n if (result.exitCode === 127 || /command not found|tmux: not found/i.test(stderr)) {\n throw new Error(\n `tmux is missing from the box image. Rebuild with: docker rmi agentbox/box:dev && retry.`,\n );\n }\n throw new Error(\n `failed to start shell session in ${opts.container}: ${\n stderr.trim() || `exit ${String(result.exitCode)}`\n }`,\n );\n}\n\n/**\n * The `docker` argv that attaches an interactive terminal to a box's shell\n * tmux session. `user` must match the user the session was started as (the\n * tmux server is per-user). Handed to the node-pty wrapper by `agentbox shell`.\n */\nexport function buildShellSessionAttachArgv(\n container: string,\n sessionName?: string,\n user?: string,\n): string[] {\n const name = sessionName ?? DEFAULT_SHELL_SESSION;\n const term = process.env['TERM'] ?? 'xterm-256color';\n return [\n 'exec',\n '-it',\n '-e',\n `TERM=${term}`,\n '--user',\n user ?? CONTAINER_USER,\n container,\n 'tmux',\n 'attach',\n '-t',\n name,\n ];\n}\n\n/**\n * Best-effort: returns `{ running: false }` for any non-zero exit from\n * `tmux has-session` (covers \"no server running\" and \"no such session\").\n */\nexport async function shellSessionInfo(\n container: string,\n sessionName?: string,\n user?: string,\n): Promise<ShellSessionInfo> {\n const name = sessionName ?? DEFAULT_SHELL_SESSION;\n const has = await execa(\n 'docker',\n ['exec', '--user', user ?? CONTAINER_USER, container, 'tmux', 'has-session', '-t', name],\n { reject: false },\n );\n return { running: has.exitCode === 0, sessionName: name };\n}\n\n/**\n * Kill a box's shell tmux session (`tmux kill-session`). Returns true on a\n * clean kill, false when the session was already gone / no tmux server.\n */\nexport async function killShellSession(\n container: string,\n sessionName: string,\n user?: string,\n): Promise<boolean> {\n const res = await execa(\n 'docker',\n [\n 'exec',\n '--user',\n user ?? CONTAINER_USER,\n container,\n 'tmux',\n 'kill-session',\n '-t',\n sessionName,\n ],\n { reject: false },\n );\n return res.exitCode === 0;\n}\n","import { join } from 'node:path';\nimport { loadConfig } from '@agentbox/ctl';\nimport type { BoxStatus } from '@agentbox/ctl';\nimport type { BoxEndpoint, BoxEndpoints, BoxRecord } from '@agentbox/core';\nimport type { DockerEngine } from './host-export.js';\nimport { buildVncUrls, VNC_CONTAINER_PORT } from './vnc.js';\nimport { WEB_CONTAINER_PORT } from './web.js';\n\n// BoxEndpoint / BoxEndpoints are the provider-neutral network-surface shape;\n// they live in @agentbox/core. Re-exported so existing consumers are unchanged.\nexport type { BoxEndpoint, BoxEndpoints };\n\n/**\n * Build the box's user-facing network surface. Pure host-side: no docker exec,\n * no network — safe to call from `agentbox list` in a tight loop.\n *\n * Service ports come from the persisted status snapshot when available\n * (`~/.agentbox/boxes/<id>/status.json`, pushed by the in-box supervisor via\n * the relay). That snapshot resolves `ready_when.port` *inside the box*, so it\n * works even when `agentbox.yaml` lives only in the box and was never pulled to\n * the host. Falls back to parsing the host's `agentbox.yaml` for pre-relay\n * boxes (or ones that never pushed a snapshot).\n *\n * Missing config + no snapshot is non-fatal: the VNC entry (if any) is still\n * returned. Engine drives reachability — OrbStack auto-routes\n * `<container>.orb.local:<port>` for any in-box port; other engines see only\n * what we explicitly publish via `docker run -p`, which today is just VNC.\n */\nexport async function getBoxEndpoints(\n record: BoxRecord,\n engine: DockerEngine,\n persisted?: BoxStatus | null,\n): Promise<BoxEndpoints> {\n const domainIsOrb = engine === 'orbstack';\n const domain = domainIsOrb ? `${record.container}.orb.local` : '127.0.0.1';\n\n const endpoints: BoxEndpoint[] = [];\n\n if (record.vncEnabled && record.vncPassword) {\n const vncUrls = buildVncUrls(record, engine);\n const url = vncUrls.orbUrl ?? vncUrls.loopbackUrl;\n endpoints.push({\n kind: 'vnc',\n name: 'vnc',\n containerPort: VNC_CONTAINER_PORT,\n url,\n reachable: Boolean(url),\n });\n }\n\n // The single `expose:`-flagged service, from the snapshot first (works when\n // agentbox.yaml lives only in the box), else the host yaml.\n let webServiceName: string | null = null;\n const persistedWeb = persisted?.services.find((s) => s.expose);\n if (persistedWeb) {\n webServiceName = persistedWeb.name;\n }\n\n const pushService = (name: string, port: number): void => {\n // The web service is surfaced as the dedicated `web` endpoint below;\n // don't also list it as a generic service.\n if (name === webServiceName) return;\n endpoints.push({\n kind: 'service',\n name,\n containerPort: port,\n // Only OrbStack auto-routes arbitrary in-box ports; on other engines we\n // don't publish service ports, so the URL isn't host-reachable.\n ...(domainIsOrb\n ? { url: `http://${domain}:${String(port)}`, reachable: true }\n : { reachable: false }),\n });\n };\n\n const persistedServices = persisted?.services.filter(\n (s): s is typeof s & { port: number } => typeof s.port === 'number',\n );\n if (persistedServices && persistedServices.length > 0) {\n for (const svc of persistedServices) pushService(svc.name, svc.port);\n } else {\n try {\n const cfg = await loadConfig(join(record.workspacePath, 'agentbox.yaml'));\n if (!webServiceName) {\n webServiceName = cfg.services.find((s) => s.expose)?.name ?? null;\n }\n for (const svc of cfg.services) {\n if (svc.readyWhen?.kind !== 'port') continue;\n pushService(svc.name, svc.readyWhen.port);\n }\n } catch {\n // No persisted snapshot and no host agentbox.yaml — skip service\n // endpoints. The VNC entry, if any, is unaffected.\n }\n }\n\n // Web endpoint: only for boxes that reserved container :80 at create. The\n // URL is the published loopback host port — uniform across engines, NOT\n // gated on OrbStack (requirement: don't rely on orb auto-DNS). No url until\n // both a service declares `expose:` and the host port is resolved; until\n // then it renders as \"reserved\".\n if (record.webContainerPort !== undefined) {\n const hasTarget = webServiceName !== null && record.webHostPort !== undefined;\n // A registered Portless alias gives the box a stable <name>.localhost URL\n // on non-OrbStack engines. `portlessUrl` is the real URL resolved from the\n // proxy at create/start (scheme + port vary); fall back to the https form\n // for records written before that field existed. No `portless` shell-out\n // here — this runs in a tight loop for `agentbox list`.\n const usePortless = record.portlessAlias !== undefined && engine !== 'orbstack';\n const webUrl = usePortless\n ? (record.portlessUrl ?? `https://${record.portlessAlias}.localhost`)\n : `http://127.0.0.1:${String(record.webHostPort)}`;\n endpoints.push({\n kind: 'web',\n name: webServiceName ?? 'web',\n containerPort: record.webContainerPort ?? WEB_CONTAINER_PORT,\n ...(hasTarget ? { url: webUrl, reachable: true } : { reachable: false }),\n });\n }\n\n return { domain, domainIsOrb, endpoints };\n}\n","import { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { execa } from 'execa';\nimport type { BoxResourceLimits, BoxResourceStats } from '@agentbox/core';\nimport { CHECKPOINT_IMAGE_PREFIX, checkpointImageTag } from './checkpoint.js';\nimport {\n inspectContainer,\n inspectContainerStatus,\n inspectVolumeMountpoint,\n} from './docker.js';\nimport { detectEngine } from './host-export.js';\nimport type { BoxRecord } from './state.js';\n\n/**\n * Parse one of Docker's human-formatted size tokens (`512MiB`, `1.2kB`,\n * `3.4GB`, `0B`, `--`). Returns null when unparseable. Docker mixes binary\n * (`KiB/MiB/GiB`) and decimal (`kB/MB/GB`) suffixes depending on the column, so\n * we handle both.\n */\nexport function parseDockerSize(raw: string): number | null {\n const s = raw.trim();\n if (!s || s === '--' || s === 'N/A') return null;\n const m = /^([\\d.]+)\\s*([A-Za-z]*)$/.exec(s);\n if (!m) return null;\n const n = Number(m[1]);\n if (!Number.isFinite(n)) return null;\n const unit = (m[2] ?? '').toLowerCase();\n const mult: Record<string, number> = {\n '': 1,\n b: 1,\n kb: 1e3,\n mb: 1e6,\n gb: 1e9,\n tb: 1e12,\n kib: 1024,\n mib: 1024 ** 2,\n gib: 1024 ** 3,\n tib: 1024 ** 4,\n };\n const factor = mult[unit];\n return factor === undefined ? null : n * factor;\n}\n\nfunction parsePercent(raw: string | undefined): number | null {\n if (!raw) return null;\n const n = Number(raw.replace('%', '').trim());\n return Number.isFinite(n) ? n : null;\n}\n\n/** Split Docker's \"<a> / <b>\" pair columns (MemUsage, NetIO, BlockIO). */\nfunction splitPair(raw: string | undefined): [string, string] | null {\n if (!raw) return null;\n const parts = raw.split('/');\n if (parts.length !== 2) return null;\n return [parts[0]!.trim(), parts[1]!.trim()];\n}\n\nasync function duBytes(path: string): Promise<number | null> {\n const result = await execa('du', ['-sk', path], { reject: false });\n if (result.exitCode !== 0) return null;\n const kb = Number.parseInt((result.stdout ?? '').split(/\\s+/)[0] ?? '', 10);\n return Number.isNaN(kb) ? null : kb * 1024;\n}\n\n/**\n * Best-effort on-host byte size of a Docker named volume. Fastest path first:\n * 1. OrbStack exposes volumes live at ~/OrbStack/docker/volumes/<name>/.\n * 2. `docker system df -v` (cheap-walked once; may report \"N/A\").\n * 3. The reported mountpoint, only when host-readable (Linux native Docker).\n * Returns null when no path is reachable from the host (the macOS VM case\n * where `system df` also has no number).\n */\nexport async function volumeSizeBytes(name: string): Promise<number | null> {\n if (!name) return null;\n const engine = await detectEngine();\n if (engine === 'orbstack') {\n const live = join(homedir(), 'OrbStack', 'docker', 'volumes', name);\n const sz = await duBytes(live);\n if (sz !== null) return sz;\n }\n const df = await execa(\n 'docker',\n ['system', 'df', '-v', '--format', '{{json .Volumes}}'],\n { reject: false },\n );\n if (df.exitCode === 0) {\n try {\n const vols = JSON.parse(df.stdout || '[]') as Array<{ Name?: string; Size?: string }>;\n const hit = vols.find((v) => v.Name === name);\n const parsed = hit?.Size ? parseDockerSize(hit.Size) : null;\n if (parsed !== null) return parsed;\n } catch {\n // fall through to mountpoint\n }\n }\n const mp = await inspectVolumeMountpoint(name);\n if (mp && !mp.startsWith('/var/lib/docker')) {\n return duBytes(mp);\n }\n return null;\n}\n\n/**\n * On-host byte size of a Docker image (sum of its own layer sizes — what\n * `docker images` reports). Null on docker errors.\n */\nasync function imageBytes(tag: string): Promise<number | null> {\n const r = await execa('docker', ['image', 'inspect', tag, '--format', '{{.Size}}'], {\n reject: false,\n });\n if (r.exitCode !== 0) return null;\n const n = Number.parseInt((r.stdout ?? '').trim(), 10);\n return Number.isFinite(n) ? n : null;\n}\n\n/**\n * Size of a project's most-recent checkpoint image (the head of the lineage,\n * resolved via the `checkpointImageTag` helper from a checkpoint *name*). The\n * caller passes the name because we don't enumerate manifests from this\n * module — that lives in checkpoint.ts. Null when the image isn't present.\n */\nexport async function projectCheckpointImageBytes(\n projectRoot: string,\n name: string,\n): Promise<number | null> {\n return imageBytes(checkpointImageTag(projectRoot, name));\n}\n\n/**\n * Total on-host bytes of every checkpoint image (the durable, cross-box\n * warm-state assets). Walks every image tag under `CHECKPOINT_IMAGE_PREFIX`.\n * Null when none exist.\n */\nexport async function allCheckpointImagesBytes(): Promise<number | null> {\n const r = await execa(\n 'docker',\n [\n 'image',\n 'ls',\n '--format',\n '{{.Repository}}:{{.Tag}}\\t{{.Size}}',\n `${CHECKPOINT_IMAGE_PREFIX}*`,\n ],\n { reject: false },\n );\n if (r.exitCode !== 0) return null;\n const lines = (r.stdout ?? '')\n .split('\\n')\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n if (lines.length === 0) return null;\n let total = 0;\n let any = false;\n for (const line of lines) {\n const [, size] = line.split('\\t');\n const n = size ? parseDockerSize(size) : null;\n if (n !== null) {\n total += n;\n any = true;\n }\n }\n return any ? total : null;\n}\n\n/** On-host byte size of the whole ~/.agentbox state/runtime directory. */\nexport async function agentboxHomeBytes(): Promise<number | null> {\n return duBytes(join(homedir(), '.agentbox'));\n}\n\nfunction limitsFromRecord(record: BoxRecord): BoxResourceLimits {\n const r = record.resourceLimits;\n return {\n memoryBytes: r?.memoryBytes && r.memoryBytes > 0 ? r.memoryBytes : null,\n cpus: r?.cpus && r.cpus > 0 ? r.cpus : null,\n pidsLimit: r?.pidsLimit && r.pidsLimit > 0 ? r.pidsLimit : null,\n disk: r?.disk || null,\n };\n}\n\n/**\n * Cross-check persisted limits against the live container's HostConfig so an\n * externally `docker update`d box still reports the truth. The persisted\n * record stays the fallback when the container is gone.\n */\nfunction reconcileLimits(persisted: BoxResourceLimits, dockerJson: unknown): BoxResourceLimits {\n const hc = (dockerJson as { HostConfig?: Record<string, unknown> } | null)?.HostConfig;\n if (!hc) return persisted;\n const mem = typeof hc.Memory === 'number' && hc.Memory > 0 ? hc.Memory : null;\n const nano = typeof hc.NanoCpus === 'number' && hc.NanoCpus > 0 ? hc.NanoCpus : null;\n const pids = typeof hc.PidsLimit === 'number' && hc.PidsLimit > 0 ? hc.PidsLimit : null;\n return {\n memoryBytes: mem ?? persisted.memoryBytes,\n cpus: nano ? nano / 1e9 : persisted.cpus,\n pidsLimit: pids ?? persisted.pidsLimit,\n disk: persisted.disk,\n };\n}\n\ninterface DockerStatsLine {\n CPUPerc?: string;\n MemUsage?: string;\n MemPerc?: string;\n PIDs?: string;\n NetIO?: string;\n BlockIO?: string;\n}\n\n/**\n * Container writable-layer size from `docker ps --size`. With the overlay\n * gone, `/workspace` lives here (not in a named volume), so this is the\n * box's primary writable-surface number.\n */\nasync function containerWritableBytes(container: string): Promise<number | null> {\n const r = await execa(\n 'docker',\n ['ps', '-a', '--filter', `name=^${container}$`, '--format', '{{.Size}}', '--size'],\n { reject: false },\n );\n if (r.exitCode !== 0) return null;\n // `--size` produces `<rw> (virtual <total>)`; we want the first number.\n const first = (r.stdout ?? '').split('\\n')[0]?.trim();\n if (!first) return null;\n const m = /^([^()]+?)(?:\\s*\\(.*\\))?$/.exec(first);\n const sz = m ? m[1]!.trim() : first;\n return parseDockerSize(sz);\n}\n\n/**\n * Provider-agnostic resource snapshot for a box. CPU/mem/pids/IO come from\n * `docker stats --no-stream` (point-in-time sample; only when the container is\n * running). Disk is the container's writable layer (where `/workspace` lives\n * now that the overlay is gone) plus the in-box dockerd's data-root volume;\n * the per-box host snapshot dir and the checkpoint image lineage are\n * reported on their own fields.\n */\nexport async function boxResourceStats(record: BoxRecord): Promise<BoxResourceStats> {\n const warnings: string[] = [];\n const dockerJson = await inspectContainer(record.container);\n const limits = reconcileLimits(limitsFromRecord(record), dockerJson);\n\n const [diskContainer, diskDocker, snapshotDiskBytes, checkpointImageBytesValue] =\n await Promise.all([\n containerWritableBytes(record.container),\n record.dockerVolume ? volumeSizeBytes(record.dockerVolume) : Promise.resolve(null),\n record.snapshotDir ? duBytes(record.snapshotDir) : Promise.resolve(null),\n record.checkpointImage ? imageBytes(record.checkpointImage) : Promise.resolve(null),\n ]);\n const diskUsedBytes =\n diskContainer === null && diskDocker === null\n ? null\n : (diskContainer ?? 0) + (diskDocker ?? 0);\n if (diskUsedBytes === null) {\n warnings.push('disk usage unavailable on this engine');\n }\n\n const base: BoxResourceStats = {\n source: 'docker',\n live: false,\n cpuPercent: null,\n memUsedBytes: null,\n memLimitBytes: limits.memoryBytes,\n memPercent: null,\n pids: null,\n diskUsedBytes,\n snapshotDiskBytes,\n checkpointVolumeBytes: checkpointImageBytesValue,\n netRxBytes: null,\n netTxBytes: null,\n blockReadBytes: null,\n blockWriteBytes: null,\n limits,\n warnings,\n };\n\n if ((await inspectContainerStatus(record.container)) !== 'running') {\n return base;\n }\n\n const proc = await execa(\n 'docker',\n ['stats', '--no-stream', '--format', '{{json .}}', record.container],\n { reject: false },\n );\n if (proc.exitCode !== 0 || !proc.stdout.trim()) {\n return base;\n }\n let line: DockerStatsLine;\n try {\n line = JSON.parse(proc.stdout.trim().split('\\n')[0]!) as DockerStatsLine;\n } catch {\n return base;\n }\n\n const memPair = splitPair(line.MemUsage);\n const memUsedBytes = memPair ? parseDockerSize(memPair[0]) : null;\n const memEngineTotal = memPair ? parseDockerSize(memPair[1]) : null;\n const netPair = splitPair(line.NetIO);\n const blkPair = splitPair(line.BlockIO);\n\n return {\n ...base,\n live: true,\n cpuPercent: parsePercent(line.CPUPerc),\n memUsedBytes,\n // The applied limit when set; otherwise docker stats' own denominator\n // (the engine/host total).\n memLimitBytes: limits.memoryBytes ?? memEngineTotal,\n memPercent: parsePercent(line.MemPerc),\n pids: line.PIDs ? Number.parseInt(line.PIDs, 10) || null : null,\n netRxBytes: netPair ? parseDockerSize(netPair[0]) : null,\n netTxBytes: netPair ? parseDockerSize(netPair[1]) : null,\n blockReadBytes: blkPair ? parseDockerSize(blkPair[0]) : null,\n blockWriteBytes: blkPair ? parseDockerSize(blkPair[1]) : null,\n };\n}\n","/**\n * `DockerProvider` — the local-Docker implementation of the provider-neutral\n * `Provider` interface. A thin adapter: every method delegates to the existing\n * `createBox` / lifecycle / docker / stats functions. The CLI resolves this via\n * the provider registry for any box whose `provider` is `'docker'`.\n */\n\nimport type {\n BoxRecord,\n BoxResourceStats,\n BoxRuntimeState,\n CreateBoxRequest,\n CreatedBox,\n ExecOptions,\n ExecResult,\n InspectedBox,\n PrepareOptions,\n PrepareResult,\n Provider,\n} from '@agentbox/core';\nimport { createBox, type CreateBoxOptions } from './create.js';\nimport { destroyBox, inspectBox, pauseBox, startBox, stopBox, unpauseBox } from './lifecycle.js';\nimport { execInBox, inspectContainerStatus } from './docker.js';\nimport { boxResourceStats } from './stats.js';\nimport { detectEngine } from './host-export.js';\nimport { portlessGetUrl } from './portless.js';\nimport { DEFAULT_BOX_IMAGE, buildImage, imageExists } from './image.js';\n\n/**\n * Docker-specific knobs the CLI passes through `CreateBoxRequest.providerOptions`.\n * Kept out of the provider-neutral `CreateBoxRequest` so cloud backends don't\n * carry Docker concepts.\n */\nexport interface DockerCreateOptions {\n useSnapshot?: boolean;\n sharedCache?: boolean;\n portless?: boolean;\n portlessStateDir?: string;\n claudeConfig?: { isolate: boolean };\n codexConfig?: { isolate: boolean };\n opencodeConfig?: { isolate: boolean };\n claudeEnv?: Record<string, string>;\n}\n\nexport const dockerProvider: Provider = {\n name: 'docker',\n\n async create(req: CreateBoxRequest): Promise<CreatedBox> {\n const po = (req.providerOptions ?? {}) as DockerCreateOptions;\n const opts: CreateBoxOptions = {\n workspacePath: req.workspacePath,\n name: req.name,\n useSnapshot: po.useSnapshot ?? false,\n checkpointRef: req.checkpointRef,\n image: req.image,\n onLog: req.onLog,\n claudeConfig: po.claudeConfig,\n claudeEnv: po.claudeEnv,\n codexConfig: po.codexConfig,\n opencodeConfig: po.opencodeConfig,\n withPlaywright: req.withPlaywright,\n withEnv: req.withEnv,\n envFilesToImport: req.envFilesToImport,\n vnc: req.vnc,\n docker: po.sharedCache !== undefined ? { sharedCache: po.sharedCache } : undefined,\n portless: po.portless,\n portlessStateDir: po.portlessStateDir,\n projectRoot: req.projectRoot,\n limits: req.limits ?? undefined,\n };\n const result = await createBox(opts);\n return {\n record: { ...result.record, provider: 'docker' },\n imageBuilt: result.imageBuilt,\n };\n },\n\n async start(box: BoxRecord): Promise<BoxRecord> {\n const { record } = await startBox(box.id);\n return { ...record, provider: 'docker' };\n },\n\n async pause(box: BoxRecord): Promise<void> {\n await pauseBox(box.id);\n },\n\n async resume(box: BoxRecord): Promise<void> {\n await unpauseBox(box.id);\n },\n\n async stop(box: BoxRecord): Promise<void> {\n await stopBox(box.id);\n },\n\n async destroy(box: BoxRecord): Promise<void> {\n await destroyBox(box.id);\n },\n\n async inspect(box: BoxRecord): Promise<InspectedBox> {\n const insp = await inspectBox(box.id);\n return {\n record: insp.record,\n // The Docker `BoxState` adds a 'destroyed' value the provider-neutral\n // `BoxRuntimeState` folds into 'missing'.\n state: insp.state === 'destroyed' ? 'missing' : insp.state,\n endpoints: insp.endpoints,\n raw: insp.dockerInspect,\n };\n },\n\n async probeState(box: BoxRecord): Promise<BoxRuntimeState> {\n return inspectContainerStatus(box.container);\n },\n\n async stats(box: BoxRecord): Promise<BoxResourceStats> {\n return boxResourceStats(box);\n },\n\n async exec(box: BoxRecord, argv: string[], opts?: ExecOptions): Promise<ExecResult> {\n const r = await execInBox(box.container, argv, opts?.user ? { user: opts.user } : {});\n return { exitCode: r.exitCode, stdout: r.stdout, stderr: r.stderr };\n },\n\n async resolveUrl(\n box: BoxRecord,\n opts?: { loopback?: boolean; kind?: 'web' | 'vnc'; ttl?: number },\n ): Promise<string> {\n if (box.webContainerPort === undefined) {\n throw new Error(\n `box ${box.name} predates the reserved web port; recreate it to use \\`agentbox url\\``,\n );\n }\n const engine = await detectEngine();\n if (engine === 'orbstack' && !opts?.loopback) {\n // OrbStack auto-routes <container>.orb.local to container :80.\n return `http://${box.container}.orb.local`;\n }\n if (box.portlessAlias && !opts?.loopback) {\n return box.portlessUrl ?? (await portlessGetUrl(box.portlessAlias));\n }\n if (box.webHostPort === undefined) {\n throw new Error(\n `web port not resolved for box ${box.name}; is the container running?`,\n );\n }\n return `http://127.0.0.1:${String(box.webHostPort)}`;\n },\n\n async prepare(opts: PrepareOptions): Promise<PrepareResult> {\n // Docker uses the rsync-into-named-volume flow at create time, so the\n // base image stays generic — no agent-config layering. `prepare` here is\n // just an explicit handle on the build step `agentbox create` does\n // lazily on first use. Idempotent: skip if the image is already built\n // unless force.\n const ref = DEFAULT_BOX_IMAGE;\n if (!opts.force && (await imageExists(ref))) {\n opts.onLog?.(`docker image ${ref} already built — skipping (use --force to rebuild)`);\n return {};\n }\n opts.onLog?.(`building docker image ${ref}…`);\n await buildImage({ ref, onProgress: opts.onLog });\n opts.onLog?.(`docker image ${ref} built`);\n return {};\n },\n};\n","/**\n * Stage the host's agent-config trees (`~/.claude`, `~/.codex`,\n * `~/.config/opencode` + `~/.local/share/opencode`) into filtered tarballs any\n * provider can ship into a remote sandbox.\n *\n * Per agent we produce **two** tarballs:\n *\n * - **static**: plugins, skills, settings, marketplaces, prompts, config —\n * stuff that's stable across re-auths. The cloud path bakes this into the\n * published Daytona snapshot once (`agentbox daytona publish-snapshot`),\n * so it ships into the sandbox FS at snapshot capture time, never the\n * S3-backed FUSE volume.\n *\n * - **credentials**: the renewable OAuth/auth files only (a handful of KB).\n * The cloud path uploads these into a per-org `agentbox-credentials`\n * volume on every create (cheap) and `agentbox daytona resync` refreshes\n * them after a re-auth — without touching the snapshot.\n *\n * Each `stage*ForUpload(opts)` returns a `StageResult`:\n * - `tarballPath`: absolute path to a `.tar.gz`, or `null` when the host has\n * nothing relevant to stage (no `~/.claude` etc., or no credentials file).\n * - `cleanup()`: removes the staging dir + the tarball; ALWAYS call after\n * the upload completes, even on error.\n * - `warnings`: non-fatal user-facing messages (the codex Keychain landmine\n * surfaces here).\n *\n * Requires `rsync` and `tar` on the host. macOS + every common Linux distro\n * ship both.\n */\n\nimport { copyFile, mkdtemp, readdir, readFile, rm, stat, writeFile } from 'node:fs/promises';\nimport { homedir, tmpdir } from 'node:os';\nimport { basename, join, relative } from 'node:path';\nimport { execa } from 'execa';\nimport {\n addProjectAlias,\n filterHostHooks,\n setInstallMethodNative,\n trustWorkspace,\n} from './claude-hooks-filter.js';\nimport { CREDENTIALS_BACKUP_FILE } from './claude-credentials.js';\n\nexport interface StageResult {\n /** Absolute path to the .tar.gz, or null when there was nothing to stage. */\n tarballPath: string | null;\n /** Remove the staging dir + tarball. Idempotent. */\n cleanup(): Promise<void>;\n /** Non-fatal messages (e.g. codex Keychain landmine). */\n warnings: string[];\n}\n\n/** Workspace path inside every cloud sandbox — matches the Docker model. */\nconst CLOUD_WORKSPACE = '/workspace';\n\nasync function pathExists(p: string): Promise<boolean> {\n try {\n await stat(p);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function findBrokenSymlinks(root: string): Promise<string[]> {\n const broken: string[] = [];\n async function walk(dir: string): Promise<void> {\n let entries;\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return;\n }\n for (const ent of entries) {\n const full = join(dir, ent.name);\n if (ent.isSymbolicLink()) {\n try {\n await stat(full);\n } catch {\n broken.push(relative(root, full));\n }\n } else if (ent.isDirectory()) {\n await walk(full);\n }\n }\n }\n await walk(root);\n return broken;\n}\n\nasync function mkStageDir(prefix: string): Promise<string> {\n return mkdtemp(join(tmpdir(), `agentbox-${prefix}-stage-`));\n}\n\nfunction emptyResult(warnings: string[] = []): StageResult {\n return { tarballPath: null, cleanup: async () => {}, warnings };\n}\n\nasync function tarballFromDir(stageDir: string, agent: string): Promise<string> {\n const tarballPath = join(tmpdir(), `agentbox-${agent}-${basename(stageDir)}.tar.gz`);\n // COPYFILE_DISABLE=1: macOS's bsdtar (the system `tar`) walks extended attrs\n // and emits AppleDouble `._<name>` sidecars for any file with xattrs, which\n // then pollute the volume inside the cloud sandbox (claude reads ~/.claude\n // top-level and chokes on those bogus entries). The env knob makes Apple's\n // copyfile() helpers a no-op, so tar produces a clean POSIX archive.\n await execa('tar', ['-czf', tarballPath, '-C', stageDir, '.'], {\n env: { ...process.env, COPYFILE_DISABLE: '1' },\n });\n return tarballPath;\n}\n\nfunction makeCleanup(paths: string[]): () => Promise<void> {\n return async () => {\n for (const p of paths) {\n await rm(p, { recursive: true, force: true });\n }\n };\n}\n\n/**\n * Stage one file into a tarball whose only entry is that file at the tarball\n * root. Used for the credentials-only variants.\n */\nasync function stageSingleFileTarball(\n agent: string,\n sourcePath: string,\n tarballEntryName: string,\n): Promise<StageResult> {\n const stageDir = await mkStageDir(agent);\n let tarballPath: string | null = null;\n try {\n await copyFile(sourcePath, join(stageDir, tarballEntryName));\n tarballPath = await tarballFromDir(stageDir, agent);\n return {\n tarballPath,\n cleanup: makeCleanup([stageDir, tarballPath]),\n warnings: [],\n };\n } catch (err) {\n await rm(stageDir, { recursive: true, force: true });\n if (tarballPath) await rm(tarballPath, { force: true });\n throw err;\n }\n}\n\n// ---------- claude ----------\n\nexport interface StageClaudeOptions {\n /** Defaults to `homedir()`. Override for tests. */\n hostHome?: string;\n /**\n * The host-absolute workspace path being mounted as `/workspace` in the\n * box. When set, host-keyed `projects[<hostWorkspace>]` in `_claude.json`\n * gets duplicated to `projects['/workspace']` so MCP servers, history,\n * and trust state line up with the host's view of this project.\n */\n hostWorkspace?: string;\n}\n\nconst CLAUDE_RUNTIME_EXCLUDES = [\n 'projects',\n 'sessions',\n 'history.jsonl',\n 'file-history',\n 'shell-snapshots',\n 'backups',\n 'session-env',\n 'paste-cache',\n 'cache',\n 'telemetry',\n 'tasks',\n 'downloads',\n 'chrome',\n 'ide',\n 'debug',\n 'mcp-needs-auth-cache.json',\n 'stats-cache.json',\n];\n\n/**\n * Filtered tarball of `~/.claude/` (+ `~/.claude.json` as `_claude.json` at\n * tarball root) **excluding** `.credentials.json`. Extracts into\n * `/home/vscode/.claude/` on the sandbox FS at snapshot-bake time.\n *\n * Mirrors `ensureClaudeVolume`'s rsync excludes (drops `node_modules`),\n * filters host-path hooks out of `settings.json` / `_claude.json`, coerces\n * install-method to native, aliases the host workspace path to `/workspace`,\n * and pre-trusts `/workspace`. Plugin `installed_plugins.json` and\n * `known_marketplaces.json` get their host-home `installPath` values rewritten\n * to the box's `/home/vscode/.claude/`.\n */\nexport async function stageClaudeStaticForUpload(\n opts: StageClaudeOptions = {},\n): Promise<StageResult> {\n const hostHome = opts.hostHome ?? homedir();\n const hostClaude = join(hostHome, '.claude');\n if (!(await pathExists(hostClaude))) return emptyResult();\n\n const stageDir = await mkStageDir('claude-static');\n let tarballPath: string | null = null;\n try {\n // rsync host ~/.claude → stage. --copy-unsafe-links dereferences user\n // skill symlinks; --exclude=node_modules drops host-platform binaries\n // (fsevents.node, esbuild, ...). Broken symlinks would abort the whole\n // sync under --copy-unsafe-links, so pre-scan and exclude them.\n //\n // Drop runtime/history state so the snapshot bake doesn't capture\n // per-machine session data the in-box claude will regenerate anyway.\n const broken = await findBrokenSymlinks(hostClaude);\n const excludes = [\n '--exclude=node_modules',\n '--exclude=.credentials.json',\n ...CLAUDE_RUNTIME_EXCLUDES.map((p) => `--exclude=${p}`),\n ...broken.map((r) => `--exclude=/${r}`),\n ];\n await execa('rsync', [\n '-a',\n '--copy-unsafe-links',\n ...excludes,\n `${hostClaude}/`,\n `${stageDir}/`,\n ]);\n\n // settings.json: filter host-path hooks; rewrite in place when changed.\n const settingsPath = join(stageDir, 'settings.json');\n if (await pathExists(settingsPath)) {\n try {\n const parsed = JSON.parse(await readFile(settingsPath, 'utf8'));\n const filtered = filterHostHooks(parsed, hostHome);\n if (filtered.removedCommands.length > 0) {\n await writeFile(settingsPath, JSON.stringify(filtered.data, null, 2));\n }\n } catch {\n // Leave the rsynced copy if parse failed.\n }\n }\n\n // _claude.json — sourced from $HOME/.claude.json (which lives outside\n // ~/.claude). Apply the same filter chain `ensureClaudeVolume` uses:\n // host-path hooks, install-method=native, host->/workspace project alias,\n // and /workspace trust pre-accept. The Dockerfile.box bakes a symlink\n // `~/.claude.json -> ~/.claude/_claude.json` so the in-box claude reads\n // through to this file at runtime.\n const hostClaudeJson = join(hostHome, '.claude.json');\n let working: unknown;\n if (await pathExists(hostClaudeJson)) {\n try {\n working = JSON.parse(await readFile(hostClaudeJson, 'utf8'));\n } catch {\n working = null;\n }\n }\n if (working === undefined || working === null) {\n working = {\n installMethod: 'native',\n autoUpdates: false,\n autoUpdatesProtectedForNative: true,\n projects: { [CLOUD_WORKSPACE]: { hasTrustDialogAccepted: true } },\n };\n } else {\n working = filterHostHooks(working, hostHome).data;\n working = setInstallMethodNative(working).data;\n if (opts.hostWorkspace) {\n working = addProjectAlias(working, opts.hostWorkspace, CLOUD_WORKSPACE).data;\n }\n working = trustWorkspace(working, CLOUD_WORKSPACE).data;\n }\n await writeFile(join(stageDir, '_claude.json'), JSON.stringify(working, null, 2));\n\n // plugins/*.json: rewrite host-home installPath/installLocation values to\n // the box's /home/vscode/.claude/plugins/ tree. Without this, claude\n // resolves plugin paths to /Users/<you>/... inside the box and the\n // marketplace fails to load.\n const pluginsDir = join(stageDir, 'plugins');\n if (await pathExists(pluginsDir)) {\n try {\n const entries = await readdir(pluginsDir, { withFileTypes: true });\n for (const ent of entries) {\n if (!ent.isFile() || !ent.name.endsWith('.json')) continue;\n const file = join(pluginsDir, ent.name);\n const raw = await readFile(file, 'utf8');\n const replaced = raw\n .split(`${hostHome}/.claude/plugins/`)\n .join('/home/vscode/.claude/plugins/');\n if (replaced !== raw) await writeFile(file, replaced);\n }\n } catch {\n // Best-effort: a broken plugins/ dir mustn't sink the whole seed.\n }\n }\n\n tarballPath = await tarballFromDir(stageDir, 'claude-static');\n return {\n tarballPath,\n cleanup: makeCleanup([stageDir, tarballPath]),\n warnings: [],\n };\n } catch (err) {\n await rm(stageDir, { recursive: true, force: true });\n if (tarballPath) await rm(tarballPath, { force: true });\n throw err;\n }\n}\n\n/**\n * Tarball with **only** `.credentials.json` (sourced from\n * `~/.agentbox/claude-credentials.json`, the portable backup the Docker\n * provider's `syncClaudeCredentials` mirrors from the macOS Keychain). The\n * cloud path extracts this into `/home/vscode/.agentbox-creds/claude/` on the\n * shared `agentbox-credentials` volume; a baked symlink in the snapshot at\n * `~/.claude/.credentials.json` resolves through to it at runtime.\n *\n * Returns an empty result when no backup exists (the in-box claude falls back\n * to interactive sign-in).\n */\nexport async function stageClaudeCredentialsForUpload(): Promise<StageResult> {\n if (!(await pathExists(CREDENTIALS_BACKUP_FILE))) return emptyResult();\n return stageSingleFileTarball('claude-creds', CREDENTIALS_BACKUP_FILE, '.credentials.json');\n}\n\n// ---------- codex ----------\n\nexport interface StageCodexOptions {\n hostHome?: string;\n}\n\nconst CODEX_RSYNC_EXCLUDES = [\n '--exclude=sessions',\n '--exclude=log',\n '--exclude=history.jsonl',\n '--exclude=hooks.json',\n '--exclude=logs_2.sqlite',\n '--exclude=logs_2.sqlite-shm',\n '--exclude=logs_2.sqlite-wal',\n '--exclude=state_5.sqlite',\n '--exclude=state_5.sqlite-shm',\n '--exclude=state_5.sqlite-wal',\n '--exclude=sqlite',\n '--exclude=cache',\n '--exclude=vendor_imports',\n '--exclude=tmp',\n // .tmp holds codex plugin sync state — ~100 MB of marketplace cache. Not\n // the same as `tmp/`; both can exist side by side on a long-running host.\n '--exclude=.tmp',\n '--exclude=.codex-global-state.json',\n '--exclude=.codex-global-state.json.bak',\n '--exclude=.personality_migration',\n '--exclude=shell_snapshots',\n '--exclude=session_index.jsonl',\n '--exclude=models_cache.json',\n '--exclude=installation_id',\n '--exclude=version.json',\n];\n\nconst CODEX_KEYCHAIN_WARNING =\n 'codex: ~/.codex/auth.json missing. On macOS the codex CLI defaults to ' +\n 'storing the OAuth token in the system Keychain, which isn\\'t reachable ' +\n 'from a remote sandbox. To share creds with cloud boxes either:\\n' +\n ' - add `cli_auth_credentials_store = \"file\"` to ~/.codex/config.toml ' +\n 'then re-run `codex login`, or\\n' +\n ' - set OPENAI_API_KEY in your environment, or\\n' +\n ' - run `codex login --with-api-key` for a file-backed login.\\n' +\n 'Skipping codex seed; in-box codex will prompt for sign-in.';\n\n/**\n * Filtered tarball of `~/.codex/` **excluding** `auth.json`. Extracts into\n * `/home/vscode/.codex/` on the sandbox FS at snapshot-bake time.\n *\n * `-L` dereferences EVERY symlink (codex sprouts links into `~/.nvm` for the\n * `applypatch` argv0 trick and into `~/.agents/skills/*`); produces a\n * symlink-free archive suitable for the FUSE-backed Daytona volume and the\n * sandbox FS alike. Broken symlinks would abort rsync under `-L`, so pre-scan\n * and skip them.\n */\nexport async function stageCodexStaticForUpload(\n opts: StageCodexOptions = {},\n): Promise<StageResult> {\n const hostHome = opts.hostHome ?? homedir();\n const hostCodex = join(hostHome, '.codex');\n if (!(await pathExists(hostCodex))) return emptyResult();\n\n const stageDir = await mkStageDir('codex-static');\n let tarballPath: string | null = null;\n try {\n const codexBroken = await findBrokenSymlinks(hostCodex);\n await execa('rsync', [\n '-a',\n '-L',\n ...codexBroken.map((r) => `--exclude=/${r}`),\n '--exclude=auth.json',\n ...CODEX_RSYNC_EXCLUDES,\n `${hostCodex}/`,\n `${stageDir}/`,\n ]);\n tarballPath = await tarballFromDir(stageDir, 'codex-static');\n return {\n tarballPath,\n cleanup: makeCleanup([stageDir, tarballPath]),\n warnings: [],\n };\n } catch (err) {\n await rm(stageDir, { recursive: true, force: true });\n if (tarballPath) await rm(tarballPath, { force: true });\n throw err;\n }\n}\n\n/**\n * Tarball with **only** `auth.json` (sourced from `~/.codex/auth.json`).\n * Surfaces the macOS Keychain landmine as a warning when the file is missing\n * — see `CODEX_KEYCHAIN_WARNING`.\n */\nexport async function stageCodexCredentialsForUpload(\n opts: StageCodexOptions = {},\n): Promise<StageResult> {\n const hostHome = opts.hostHome ?? homedir();\n const hostAuth = join(hostHome, '.codex', 'auth.json');\n if (!(await pathExists(hostAuth))) return emptyResult([CODEX_KEYCHAIN_WARNING]);\n return stageSingleFileTarball('codex-creds', hostAuth, 'auth.json');\n}\n\n// ---------- opencode ----------\n\nexport interface StageOpencodeOptions {\n hostHome?: string;\n}\n\nconst OPENCODE_DATA_EXCLUDES = [\n '--exclude=storage',\n '--exclude=log',\n '--exclude=project',\n '--exclude=cache',\n '--exclude=bin',\n '--exclude=repos',\n '--exclude=snapshot',\n '--exclude=config',\n '--exclude=opencode.db',\n '--exclude=opencode.db-shm',\n '--exclude=opencode.db-wal',\n];\n\n/**\n * Filtered tarball of opencode static config. Layout extracts into\n * `/home/vscode/.local/share/opencode/`:\n *\n * ./<data files> ← from ~/.local/share/opencode/ (minus auth.json)\n * ./config/<config files> ← from ~/.config/opencode/\n *\n * `auth.json` is **excluded** — it ships separately via the credentials\n * variant.\n */\nexport async function stageOpencodeStaticForUpload(\n opts: StageOpencodeOptions = {},\n): Promise<StageResult> {\n const hostHome = opts.hostHome ?? homedir();\n const hostData = join(hostHome, '.local', 'share', 'opencode');\n const hostConfig = join(hostHome, '.config', 'opencode');\n const hasData = await pathExists(hostData);\n const hasConfig = await pathExists(hostConfig);\n if (!hasData && !hasConfig) return emptyResult();\n\n const stageDir = await mkStageDir('opencode-static');\n let tarballPath: string | null = null;\n try {\n // `-L` dereferences every symlink — Daytona's FUSE volumes reject symlink\n // creation, and the sandbox FS doesn't care either way. Broken symlinks\n // would abort rsync under `-L`, so pre-scan and skip them.\n if (hasData) {\n const dataBroken = await findBrokenSymlinks(hostData);\n await execa('rsync', [\n '-a',\n '-L',\n ...dataBroken.map((r) => `--exclude=/${r}`),\n '--exclude=auth.json',\n ...OPENCODE_DATA_EXCLUDES,\n `${hostData}/`,\n `${stageDir}/`,\n ]);\n }\n if (hasConfig) {\n const configStage = join(stageDir, 'config');\n const cfgBroken = await findBrokenSymlinks(hostConfig);\n await execa('rsync', [\n '-a',\n '-L',\n ...cfgBroken.map((r) => `--exclude=/${r}`),\n `${hostConfig}/`,\n `${configStage}/`,\n ]);\n }\n tarballPath = await tarballFromDir(stageDir, 'opencode-static');\n return {\n tarballPath,\n cleanup: makeCleanup([stageDir, tarballPath]),\n warnings: [],\n };\n } catch (err) {\n await rm(stageDir, { recursive: true, force: true });\n if (tarballPath) await rm(tarballPath, { force: true });\n throw err;\n }\n}\n\n/**\n * Tarball with **only** `auth.json` (sourced from\n * `~/.local/share/opencode/auth.json`). Returns an empty result when the host\n * has no opencode auth file.\n */\nexport async function stageOpencodeCredentialsForUpload(\n opts: StageOpencodeOptions = {},\n): Promise<StageResult> {\n const hostHome = opts.hostHome ?? homedir();\n const hostAuth = join(hostHome, '.local', 'share', 'opencode', 'auth.json');\n if (!(await pathExists(hostAuth))) return emptyResult();\n return stageSingleFileTarball('opencode-creds', hostAuth, 'auth.json');\n}\n","import { execInBox } from './docker.js';\n\nexport interface BoxBrowserResult {\n up: boolean;\n /** True when a session was already active, so we left the agent's browser untouched. */\n alreadyRunning?: boolean;\n reason?: string;\n}\n\n/**\n * Decide whether `agent-browser session list` reports a live session. Pure so\n * it can be unit-tested without docker. `agent-browser` exits 0 and prints\n * \"No active sessions\" when nothing is running; any other exit-0 output lists\n * one or more sessions (a persistent Chromium is up).\n */\nexport function browserSessionActive(stdout: string, exitCode: number): boolean {\n return exitCode === 0 && !/no active sessions/i.test(stdout);\n}\n\n/**\n * Ensure the box's in-box browser (agent-browser's persistent default-session\n * Chromium) is running, so the VNC view shows a browser instead of a blank X\n * screen. Idempotent: if a session is already active we do nothing — the agent\n * may be mid-task and we must not navigate its page away. Otherwise we open\n * `targetUrl` (default `about:blank`) **headed** (agent-browser defaults to\n * headless — a headless Chromium renders nothing on the VNC display, defeating\n * the whole point), so the agent's subsequent `agent-browser open <url>` reuses\n * the same visible window (it just navigates it). `agentbox screen` passes the\n * box's own web-service URL (`http://localhost:<expose.port>`) so the app is\n * shown *inside* the VNC desktop rather than popped open on the host.\n *\n * Best-effort, mirroring {@link import('./vnc.js').launchVncDaemon} — the\n * caller warns on failure but never aborts (the noVNC client still connects).\n * `DISPLAY=:1` + `AGENT_BROWSER_EXECUTABLE_PATH` are image-baked env so the\n * exec inherits them; runs as `vscode` like the other in-box launches.\n *\n * When `targetUrl` is the box's Portless `<name>.localhost` URL, the in-box\n * Chromium routes it back out to the host Portless proxy — the box's\n * `AGENT_BROWSER_ARGS` env (set at create, see `portlessBrowserEnv`) carries\n * the `--host-resolver-rules` that makes that work.\n */\nexport async function ensureBoxBrowser(\n container: string,\n timeoutMs = 8000,\n targetUrl = 'about:blank',\n): Promise<BoxBrowserResult> {\n const list = await execInBox(container, ['agent-browser', 'session', 'list'], {\n user: 'vscode',\n timeoutMs,\n });\n if (browserSessionActive(list.stdout, list.exitCode)) {\n return { up: true, alreadyRunning: true };\n }\n\n const open = await execInBox(container, ['agent-browser', 'open', '--headed', targetUrl], {\n user: 'vscode',\n timeoutMs,\n });\n if (open.exitCode === 0) return { up: true };\n return {\n up: false,\n reason: `agent-browser open failed: ${open.stderr || open.stdout || `exit ${String(open.exitCode)}`}`,\n };\n}\n","export type ServiceState =\n | 'pending'\n | 'waiting'\n | 'starting'\n | 'running'\n | 'ready'\n | 'unhealthy'\n | 'crashed'\n | 'backoff'\n | 'stopped';\n\nexport type TaskState = 'pending' | 'waiting' | 'running' | 'done' | 'failed' | 'skipped';\n\nexport interface ServiceStatus {\n name: string;\n state: ServiceState;\n pid: number | null;\n restarts: number;\n lastExitCode: number | null;\n startedAt: string | null;\n readyAt: string | null;\n nextRetryAt: string | null;\n blockedOn: string[];\n command: string;\n}\n\nexport interface StatusReply {\n services: ServiceStatus[];\n tasks: TaskStatus[];\n ports: BoxStatusPort[];\n}\n\nexport interface WaitReadyArgs {\n timeoutMs?: number;\n units?: string[];\n}\n\nexport type WaitReadyReply =\n | { ready: true }\n | { ready: false; timedOut: string[]; failed: string[] };\n\nexport interface TaskStatus {\n name: string;\n state: TaskState;\n pid: number | null;\n lastExitCode: number | null;\n startedAt: string | null;\n finishedAt: string | null;\n command: string;\n}\n\nexport interface LogEvent {\n service: string;\n ts: string;\n stream: 'stdout' | 'stderr';\n line: string;\n}\n\nexport interface ReloadResult {\n added: string[];\n removed: string[];\n changed: string[];\n}\n\n/**\n * Coarse activity state of the in-box Claude Code session, fed by Claude Code\n * hooks via `agentbox-ctl claude-state <state>`. `unknown` is the initial value\n * before any hook has fired (or for boxes whose image predates the hooks).\n */\nexport type ClaudeActivityState = 'working' | 'idle' | 'waiting' | 'unknown';\n\nexport const CLAUDE_ACTIVITY_STATES: readonly ClaudeActivityState[] = [\n 'working',\n 'idle',\n 'waiting',\n 'unknown',\n];\n\n/**\n * Same coarse activity union, reused for any agent. Codex feeds it via\n * `agentbox-ctl codex-state <state>` (Codex lifecycle hooks); the value space\n * is identical to {@link ClaudeActivityState}.\n */\nexport type AgentActivityState = ClaudeActivityState;\n\nexport interface BoxStatusServiceEntry {\n name: string;\n state: ServiceState;\n /** Configured `ready_when` port for this service, else null. */\n port: number | null;\n /**\n * The service's `expose:` mapping (container `as` → in-box `port`) when it is\n * the designated web service, else absent. Additive field — snapshots written\n * before this existed simply lack it (schema stays 1; treat absent as none).\n */\n expose?: { port: number; as: number };\n}\n\nexport interface BoxStatusTaskEntry {\n name: string;\n state: TaskState;\n}\n\nexport interface BoxStatusPort {\n port: number;\n /** Name of the service whose `ready_when` port matches, else null (ad-hoc). */\n service: string | null;\n}\n\nexport interface BoxStatusClaude {\n state: ClaudeActivityState;\n /** ISO-8601 time the last claude-state hook fired, or null if none yet. */\n updatedAt: string | null;\n /** Whether the claude tmux session was present at snapshot time. */\n sessionRunning: boolean;\n /**\n * Human-readable title Claude Code set on its terminal (the in-box tmux\n * pane title), sanitized. Additive field — snapshots written before this\n * existed simply lack it (schema stays 1; treat absent as no title).\n */\n sessionTitle?: string;\n}\n\n/**\n * Codex session status — parallel to {@link BoxStatusClaude}. `state` is fed by\n * Codex lifecycle hooks via `agentbox-ctl codex-state <state>`.\n */\nexport interface BoxStatusCodex {\n state: AgentActivityState;\n /** ISO-8601 time the last codex-state hook fired, or null if none yet. */\n updatedAt: string | null;\n /** Whether the codex tmux session was present at snapshot time. */\n sessionRunning: boolean;\n /** Sanitized in-box tmux pane title, when the Codex TUI set one. */\n sessionTitle?: string;\n}\n\n/**\n * OpenCode session status. OpenCode has no activity-hook integration yet, so\n * only the tmux session presence + title are reported (no `state`).\n */\nexport interface BoxStatusOpencode {\n /** Whether the opencode tmux session was present at snapshot time. */\n sessionRunning: boolean;\n /** Sanitized in-box tmux pane title, when the OpenCode TUI set one. */\n sessionTitle?: string;\n}\n\n/**\n * Durable snapshot of a box's runtime status. The in-box daemon builds it and\n * pushes it to the host relay, which persists it to\n * `~/.agentbox/boxes/<id>/status.json` so `agentbox status` / `list` /\n * `inspect` can show it even when the box is paused or stopped.\n */\nexport interface BoxStatus {\n /** Schema version; bump on incompatible changes so old readers can reject. */\n schema: 1;\n boxId: string;\n /** ISO-8601 time the daemon built this snapshot. */\n timestamp: string;\n services: BoxStatusServiceEntry[];\n tasks: BoxStatusTaskEntry[];\n /** Live-discovered listening TCP ports inside the box. */\n ports: BoxStatusPort[];\n claude: BoxStatusClaude;\n /**\n * Codex / OpenCode session status. Additive + optional — present only when\n * that agent's tmux session is running (or, for codex, a hook has fired);\n * a claude-only box's snapshot simply omits them (schema stays 1).\n */\n codex?: BoxStatusCodex;\n opencode?: BoxStatusOpencode;\n}\n\nexport const BOX_STATUS_SCHEMA = 1 as const;\n\n/** Relay event type carrying a `BoxStatus` payload. */\nexport const BOX_STATUS_EVENT = 'box-status';\n\nexport type CtlRequest =\n | { op: 'status' }\n | { op: 'task-status' }\n | { op: 'wait-ready'; timeoutMs?: number; units?: string[] }\n | { op: 'run-task'; name: string; force?: boolean }\n | { op: 'logs'; service: string; tail?: number; follow?: boolean }\n | { op: 'restart'; service: string }\n | { op: 'stop'; service: string }\n | { op: 'start'; service: string }\n | { op: 'reload' }\n | { op: 'ping' }\n | { op: 'claude-session'; sessionName?: string }\n | { op: 'claude-state'; state: ClaudeActivityState }\n | { op: 'codex-state'; state: AgentActivityState };\n\nexport type CtlResponse = { ok: true; data: unknown } | { ok: false; error: string };\n\n/**\n * Status of the in-container tmux session running Claude Code. The daemon\n * doesn't own this session lifecycle — it probes via `tmux has-session` and\n * `tmux display-message`. Missing tmux server / missing session both surface\n * as `running: false`.\n */\nexport interface ClaudeSessionStatus {\n running: boolean;\n sessionName: string;\n /** ISO-8601 timestamp from tmux's `#{session_created}`, or null when not running. */\n startedAt: string | null;\n /**\n * Sanitized tmux `#{pane_title}` (the title Claude Code set on its\n * terminal), or null when not running / no meaningful title.\n */\n title: string | null;\n}\n\nexport const DEFAULT_SOCKET_PATH = '/run/agentbox/ctl.sock';\nexport const DEFAULT_CONFIG_PATH = '/workspace/agentbox.yaml';\nexport const DEFAULT_LOG_DIR = '/var/log/agentbox';\nexport const DEFAULT_CLAUDE_SESSION_NAME = 'claude';\nexport const DEFAULT_CODEX_SESSION_NAME = 'codex';\nexport const DEFAULT_OPENCODE_SESSION_NAME = 'opencode';\n","import { spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { createConnection, type Socket } from 'node:net';\nimport type {\n ClaudeActivityState,\n ClaudeSessionStatus,\n CtlRequest,\n CtlResponse,\n LogEvent,\n ReloadResult,\n ServiceStatus,\n StatusReply,\n TaskStatus,\n WaitReadyArgs,\n WaitReadyReply,\n} from './types.js';\n\nexport interface ConnectOptions {\n socketPath: string;\n /** Default 3000 ms. */\n timeoutMs?: number;\n}\n\ninterface NodeErrno extends Error {\n code?: string;\n}\n\n/**\n * Best-effort daemon respawn when the unix socket is dead. `docker exec -d`\n * leaves no log when the daemon crashes on startup and Node doesn't unlink\n * unix sockets on exit, so an orphaned file is the symptom we see most often.\n * Spawning the bin detached and polling for a fresh listener recovers without\n * needing host involvement — mirrors `ensureRelay()` on the host side.\n *\n * Gated on `AGENTBOX=1` (set on every box at `docker run`) so unit tests on\n * the host — which start an ephemeral server with no `agentbox-ctl` bin\n * anywhere on PATH — don't accidentally spawn anything.\n */\nasync function tryReviveDaemon(socketPath: string): Promise<boolean> {\n if (process.env.AGENTBOX !== '1') return false;\n try {\n const child = spawn('agentbox-ctl', ['daemon'], {\n detached: true,\n stdio: 'ignore',\n });\n child.unref();\n } catch {\n return false;\n }\n // The daemon `unlink`s any stale socket file before `listen()`, so once the\n // file reappears it's bound to a live listener.\n for (let i = 0; i < 50; i++) {\n await new Promise((r) => setTimeout(r, 100));\n if (existsSync(socketPath)) return true;\n }\n return false;\n}\n\nasync function connectOnce(opts: ConnectOptions): Promise<Socket> {\n const sock = createConnection(opts.socketPath);\n await new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => {\n sock.destroy();\n reject(new Error(`connect ${opts.socketPath} timed out`));\n }, opts.timeoutMs ?? 3000);\n sock.once('connect', () => {\n clearTimeout(timer);\n resolve();\n });\n sock.once('error', (err) => {\n clearTimeout(timer);\n reject(err);\n });\n });\n return sock;\n}\n\nasync function connect(opts: ConnectOptions): Promise<Socket> {\n try {\n return await connectOnce(opts);\n } catch (err) {\n // Only ECONNREFUSED (stale socket file, no listener) and ENOENT (no file\n // at all) suggest a missing daemon. Anything else (EACCES, ETIMEDOUT, …)\n // is a real failure we shouldn't try to paper over.\n const code = (err as NodeErrno).code;\n if (code !== 'ECONNREFUSED' && code !== 'ENOENT') throw err;\n const revived = await tryReviveDaemon(opts.socketPath);\n if (!revived) throw err;\n return await connectOnce(opts);\n }\n}\n\nasync function sendOneShot<T>(opts: ConnectOptions, req: CtlRequest): Promise<T> {\n const sock = await connect(opts);\n sock.write(`${JSON.stringify(req)}\\n`);\n let buf = '';\n sock.setEncoding('utf8');\n for await (const chunk of sock) {\n buf += chunk as string;\n const idx = buf.indexOf('\\n');\n if (idx !== -1) {\n const line = buf.slice(0, idx);\n sock.end();\n return parseResponse<T>(line);\n }\n }\n // Connection closed before we got a full line.\n if (buf.length > 0) return parseResponse<T>(buf);\n throw new Error('connection closed with no response');\n}\n\nfunction parseResponse<T>(line: string): T {\n const parsed = JSON.parse(line) as CtlResponse;\n if (parsed.ok) return parsed.data as T;\n throw new Error(parsed.error);\n}\n\nexport async function ping(opts: ConnectOptions): Promise<'pong'> {\n return sendOneShot<'pong'>(opts, { op: 'ping' });\n}\n\nexport async function status(opts: ConnectOptions): Promise<StatusReply> {\n return sendOneShot<StatusReply>(opts, { op: 'status' });\n}\n\nexport async function taskStatus(opts: ConnectOptions): Promise<TaskStatus[]> {\n return sendOneShot<TaskStatus[]>(opts, { op: 'task-status' });\n}\n\nexport async function waitReady(\n opts: ConnectOptions,\n args: WaitReadyArgs = {},\n): Promise<WaitReadyReply> {\n return sendOneShot<WaitReadyReply>(opts, {\n op: 'wait-ready',\n timeoutMs: args.timeoutMs,\n units: args.units,\n });\n}\n\nexport async function runTask(\n opts: ConnectOptions,\n name: string,\n force?: boolean,\n): Promise<TaskStatus> {\n return sendOneShot<TaskStatus>(opts, { op: 'run-task', name, force });\n}\n\nexport async function restart(opts: ConnectOptions, service: string): Promise<ServiceStatus> {\n return sendOneShot<ServiceStatus>(opts, { op: 'restart', service });\n}\n\nexport async function stop(opts: ConnectOptions, service: string): Promise<ServiceStatus> {\n return sendOneShot<ServiceStatus>(opts, { op: 'stop', service });\n}\n\nexport async function start(opts: ConnectOptions, service: string): Promise<ServiceStatus> {\n return sendOneShot<ServiceStatus>(opts, { op: 'start', service });\n}\n\nexport async function reload(opts: ConnectOptions): Promise<ReloadResult> {\n return sendOneShot<ReloadResult>(opts, { op: 'reload' });\n}\n\nexport async function claudeSession(\n opts: ConnectOptions & { sessionName?: string },\n): Promise<ClaudeSessionStatus> {\n return sendOneShot<ClaudeSessionStatus>(opts, {\n op: 'claude-session',\n sessionName: opts.sessionName,\n });\n}\n\nexport async function claudeState(\n opts: ConnectOptions,\n state: ClaudeActivityState,\n): Promise<'ok'> {\n return sendOneShot<'ok'>(opts, { op: 'claude-state', state });\n}\n\nexport async function codexState(\n opts: ConnectOptions,\n state: ClaudeActivityState,\n): Promise<'ok'> {\n return sendOneShot<'ok'>(opts, { op: 'codex-state', state });\n}\n\nexport interface LogsResult {\n initial: LogEvent[];\n /**\n * When `follow: true` was passed, this async iterator yields further events\n * until the caller breaks out (which closes the socket).\n */\n follow?: AsyncIterableIterator<LogEvent>;\n}\n\nexport async function logs(\n opts: ConnectOptions,\n args: { service: string; tail?: number; follow?: boolean },\n): Promise<LogsResult> {\n const sock = await connect(opts);\n sock.write(`${JSON.stringify({ op: 'logs', ...args })}\\n`);\n\n const lines = createLineIterator(sock);\n const first = await lines.next();\n if (first.done) {\n sock.end();\n throw new Error('connection closed with no response');\n }\n const parsed = JSON.parse(first.value) as CtlResponse;\n if (!parsed.ok) {\n sock.end();\n throw new Error(parsed.error);\n }\n const data = parsed.data as { events: LogEvent[]; follow: boolean };\n if (!data.follow) {\n sock.end();\n return { initial: data.events };\n }\n\n const followGen = (async function* () {\n try {\n for await (const line of lines) {\n const p = JSON.parse(line) as CtlResponse;\n if (p.ok && p.data && typeof p.data === 'object' && 'event' in p.data) {\n yield (p.data as { event: LogEvent }).event;\n }\n }\n } finally {\n sock.end();\n }\n })();\n\n return { initial: data.events, follow: followGen };\n}\n\nasync function* createLineIterator(sock: Socket): AsyncIterableIterator<string> {\n let buf = '';\n sock.setEncoding('utf8');\n for await (const chunk of sock) {\n buf += chunk as string;\n let idx = buf.indexOf('\\n');\n while (idx !== -1) {\n yield buf.slice(0, idx);\n buf = buf.slice(idx + 1);\n idx = buf.indexOf('\\n');\n }\n }\n if (buf.length > 0) yield buf;\n}\n","import type { BoxStatusPort, ServiceStatus, TaskStatus } from './types.js';\n\nexport function renderStatusTable(rows: ServiceStatus[]): string {\n if (rows.length === 0) return '(no services configured)';\n const headers = ['NAME', 'STATE', 'PID', 'RESTARTS', 'LAST EXIT', 'BLOCKED ON', 'COMMAND'];\n const data: string[][] = rows.map((r) => [\n r.name,\n r.state,\n r.pid === null ? '-' : String(r.pid),\n String(r.restarts),\n r.lastExitCode === null ? '-' : String(r.lastExitCode),\n r.blockedOn.length === 0 ? '-' : r.blockedOn.join(','),\n truncate(r.command, 40),\n ]);\n return renderTable(headers, data);\n}\n\nexport function renderTaskTable(rows: TaskStatus[]): string {\n if (rows.length === 0) return '(no tasks configured)';\n const headers = ['NAME', 'STATE', 'EXIT', 'STARTED', 'FINISHED', 'COMMAND'];\n const data: string[][] = rows.map((r) => [\n r.name,\n r.state,\n r.lastExitCode === null ? '-' : String(r.lastExitCode),\n r.startedAt ?? '-',\n r.finishedAt ?? '-',\n truncate(r.command, 40),\n ]);\n return renderTable(headers, data);\n}\n\nexport function renderPortsTable(rows: BoxStatusPort[]): string {\n if (rows.length === 0) return '(no ports listening)';\n const named = rows.filter((r) => r.service);\n const other = rows\n .filter((r) => !r.service)\n .map((r) => r.port)\n .sort((a, b) => a - b);\n const lines: string[] = [];\n if (named.length > 0) {\n lines.push(\n renderTable(\n ['PORT', 'SERVICE'],\n named.map((r) => [`:${String(r.port)}`, r.service ?? '-']),\n ),\n );\n }\n // Collapse the unattributed ports (VNC, dev-tool worker IPC, multi-port\n // services like inngest) into one line so they don't drown the real services.\n if (other.length > 0) {\n lines.push(`other (${other.length}): ${other.join(', ')}`);\n }\n return lines.join('\\n');\n}\n\nfunction renderTable(headers: string[], data: string[][]): string {\n const widths = headers.map((h, i) =>\n Math.max(h.length, ...data.map((row) => (row[i] ?? '').length)),\n );\n const fmt = (row: string[]): string =>\n row.map((cell, i) => cell.padEnd(widths[i] ?? cell.length)).join(' ');\n return [fmt(headers), ...data.map(fmt)].join('\\n');\n}\n\nfunction truncate(s: string, n: number): string {\n if (s.length <= n) return s;\n return s.slice(0, n - 1) + '…';\n}\n","import { readFile } from 'node:fs/promises';\nimport { parse as parseYaml } from 'yaml';\n\nexport type RestartPolicy = 'always' | 'on-failure' | 'never';\nexport type ProbeOnTimeout = 'kill' | 'mark_unhealthy';\n\nexport interface BackoffSpec {\n initialMs: number;\n maxMs: number;\n factor: number;\n}\n\nexport interface PortProbe {\n kind: 'port';\n port: number;\n host: string;\n intervalMs: number;\n initialDelayMs: number;\n timeoutMs: number;\n onTimeout: ProbeOnTimeout;\n}\n\nexport interface LogMatchProbe {\n kind: 'log_match';\n pattern: RegExp;\n timeoutMs: number;\n onTimeout: ProbeOnTimeout;\n}\n\nexport interface HttpProbe {\n kind: 'http';\n url: string;\n expectStatus?: number;\n intervalMs: number;\n initialDelayMs: number;\n timeoutMs: number;\n onTimeout: ProbeOnTimeout;\n}\n\nexport type ReadyProbe = PortProbe | LogMatchProbe | HttpProbe;\n\nexport interface ExposeSpec {\n /** The port this service listens on inside the box. */\n port: number;\n /** Container port forwarded to it. Only 80 is reserved/published today. */\n as: number;\n}\n\nexport interface TaskSpec {\n name: string;\n command: string | string[];\n cwd?: string;\n env?: Record<string, string>;\n needs: string[];\n}\n\nexport interface ServiceSpec {\n name: string;\n command: string | string[];\n cwd?: string;\n env?: Record<string, string>;\n autostart: boolean;\n restart: RestartPolicy;\n backoff: BackoffSpec;\n needs: string[];\n readyWhen?: ReadyProbe;\n /** When set, container port `expose.as` forwards to `127.0.0.1:expose.port`. */\n expose?: ExposeSpec;\n}\n\nexport interface CtlConfig {\n services: ServiceSpec[];\n tasks: TaskSpec[];\n}\n\nexport const DEFAULT_BACKOFF: BackoffSpec = {\n initialMs: 500,\n maxMs: 30_000,\n factor: 2,\n};\n\nexport const DEFAULT_PROBE_INTERVAL_MS = 500;\nexport const DEFAULT_PROBE_INITIAL_DELAY_MS = 0;\nexport const DEFAULT_PROBE_TIMEOUT_MS = 60_000;\nexport const DEFAULT_PROBE_HOST = '127.0.0.1';\nexport const DEFAULT_PROBE_ON_TIMEOUT: ProbeOnTimeout = 'kill';\n\nexport class ConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ConfigError';\n }\n}\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\nfunction parseEnv(raw: unknown, where: string): Record<string, string> | undefined {\n if (raw === undefined || raw === null) return undefined;\n if (!isPlainObject(raw)) {\n throw new ConfigError(`${where}.env must be a mapping of string → string`);\n }\n const out: Record<string, string> = {};\n for (const [k, v] of Object.entries(raw)) {\n if (typeof v !== 'string' && typeof v !== 'number' && typeof v !== 'boolean') {\n throw new ConfigError(`${where}.env.${k} must be a scalar`);\n }\n out[k] = String(v);\n }\n return out;\n}\n\nfunction parseCommand(raw: unknown, where: string): string | string[] {\n if (typeof raw === 'string') {\n if (raw.trim().length === 0) {\n throw new ConfigError(`${where}.command must not be empty`);\n }\n return raw;\n }\n if (Array.isArray(raw)) {\n if (raw.length === 0) {\n throw new ConfigError(`${where}.command array must not be empty`);\n }\n const argv: string[] = [];\n for (const [i, item] of raw.entries()) {\n if (typeof item !== 'string') {\n throw new ConfigError(`${where}.command[${String(i)}] must be a string`);\n }\n argv.push(item);\n }\n return argv;\n }\n throw new ConfigError(`${where}.command must be a string or array of strings`);\n}\n\nfunction parseRestart(raw: unknown, where: string): RestartPolicy {\n if (raw === undefined) return 'on-failure';\n if (raw === 'always' || raw === 'on-failure' || raw === 'never') return raw;\n throw new ConfigError(`${where}.restart must be one of: always, on-failure, never`);\n}\n\nconst BACKOFF_KEYS = new Set(['initial_ms', 'max_ms', 'factor']);\n\nfunction parseBackoff(raw: unknown, where: string): BackoffSpec {\n if (raw === undefined) return { ...DEFAULT_BACKOFF };\n if (!isPlainObject(raw)) {\n throw new ConfigError(`${where}.backoff must be a mapping`);\n }\n rejectUnknownKeys(raw, BACKOFF_KEYS, `${where}.backoff`);\n const initialMs = parseNonNegativeInt(\n raw.initial_ms,\n `${where}.backoff.initial_ms`,\n DEFAULT_BACKOFF.initialMs,\n );\n const maxMs = parseNonNegativeInt(raw.max_ms, `${where}.backoff.max_ms`, DEFAULT_BACKOFF.maxMs);\n const factor = parseFactor(raw.factor, `${where}.backoff.factor`, DEFAULT_BACKOFF.factor);\n if (maxMs < initialMs) {\n throw new ConfigError(`${where}.backoff.max_ms must be >= initial_ms`);\n }\n return { initialMs, maxMs, factor };\n}\n\nfunction rejectUnknownKeys(\n obj: Record<string, unknown>,\n allowed: Set<string>,\n where: string,\n): void {\n for (const key of Object.keys(obj)) {\n if (!allowed.has(key)) {\n throw new ConfigError(`${where} has unknown key \"${key}\"`);\n }\n }\n}\n\nfunction parseNonNegativeInt(raw: unknown, where: string, fallback: number): number {\n if (raw === undefined) return fallback;\n if (typeof raw !== 'number' || !Number.isFinite(raw) || raw < 0) {\n throw new ConfigError(`${where} must be a non-negative number`);\n }\n return Math.floor(raw);\n}\n\nfunction parsePositiveInt(raw: unknown, where: string, fallback: number): number {\n if (raw === undefined) return fallback;\n if (typeof raw !== 'number' || !Number.isFinite(raw) || raw < 1) {\n throw new ConfigError(`${where} must be a positive integer`);\n }\n return Math.floor(raw);\n}\n\nfunction parseFactor(raw: unknown, where: string, fallback: number): number {\n if (raw === undefined) return fallback;\n if (typeof raw !== 'number' || !Number.isFinite(raw) || raw < 1) {\n throw new ConfigError(`${where} must be a number >= 1`);\n }\n return raw;\n}\n\nfunction parseOnTimeout(raw: unknown, where: string): ProbeOnTimeout {\n if (raw === undefined) return DEFAULT_PROBE_ON_TIMEOUT;\n if (raw === 'kill' || raw === 'mark_unhealthy') return raw;\n throw new ConfigError(`${where} must be one of: kill, mark_unhealthy`);\n}\n\nfunction parseNeeds(raw: unknown, where: string): string[] {\n if (raw === undefined || raw === null) return [];\n if (!Array.isArray(raw)) {\n throw new ConfigError(`${where} must be an array of unit names`);\n }\n const seen = new Set<string>();\n const out: string[] = [];\n for (const [i, item] of raw.entries()) {\n if (typeof item !== 'string') {\n throw new ConfigError(`${where}[${String(i)}] must be a string`);\n }\n if (!/^[A-Za-z0-9_-]+$/.test(item)) {\n throw new ConfigError(`${where}[${String(i)}] \"${item}\" must match [A-Za-z0-9_-]+`);\n }\n if (seen.has(item)) continue;\n seen.add(item);\n out.push(item);\n }\n return out;\n}\n\nconst PROBE_KEYS = new Set([\n 'port',\n 'host',\n 'log_match',\n 'http',\n 'expect_status',\n 'interval_ms',\n 'initial_delay_ms',\n 'timeout_ms',\n 'on_timeout',\n]);\n\nfunction parseReadyWhen(raw: unknown, where: string): ReadyProbe | undefined {\n if (raw === undefined || raw === null) return undefined;\n if (!isPlainObject(raw)) {\n throw new ConfigError(`${where}.ready_when must be a mapping`);\n }\n rejectUnknownKeys(raw, PROBE_KEYS, `${where}.ready_when`);\n\n const kinds: Array<'port' | 'log_match' | 'http'> = [];\n if (raw.port !== undefined) kinds.push('port');\n if (raw.log_match !== undefined) kinds.push('log_match');\n if (raw.http !== undefined) kinds.push('http');\n if (kinds.length === 0) {\n throw new ConfigError(\n `${where}.ready_when must declare exactly one of: port, log_match, http`,\n );\n }\n if (kinds.length > 1) {\n throw new ConfigError(\n `${where}.ready_when may declare only one of: port, log_match, http (got ${kinds.join(', ')})`,\n );\n }\n\n const timeoutMs = parsePositiveInt(\n raw.timeout_ms,\n `${where}.ready_when.timeout_ms`,\n DEFAULT_PROBE_TIMEOUT_MS,\n );\n const onTimeout = parseOnTimeout(raw.on_timeout, `${where}.ready_when.on_timeout`);\n\n const kind = kinds[0]!;\n if (kind === 'log_match') {\n if (raw.host !== undefined || raw.expect_status !== undefined || raw.interval_ms !== undefined || raw.initial_delay_ms !== undefined) {\n throw new ConfigError(\n `${where}.ready_when.log_match cannot be combined with host/expect_status/interval_ms/initial_delay_ms`,\n );\n }\n const pat = assertString(raw.log_match, `${where}.ready_when.log_match`);\n let pattern: RegExp;\n try {\n pattern = new RegExp(pat);\n } catch (err) {\n throw new ConfigError(\n `${where}.ready_when.log_match is not a valid regex: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n return { kind: 'log_match', pattern, timeoutMs, onTimeout };\n }\n\n const intervalMs = parsePositiveInt(\n raw.interval_ms,\n `${where}.ready_when.interval_ms`,\n DEFAULT_PROBE_INTERVAL_MS,\n );\n const initialDelayMs = parseNonNegativeInt(\n raw.initial_delay_ms,\n `${where}.ready_when.initial_delay_ms`,\n DEFAULT_PROBE_INITIAL_DELAY_MS,\n );\n\n if (kind === 'port') {\n if (raw.expect_status !== undefined) {\n throw new ConfigError(`${where}.ready_when.expect_status only applies to http probes`);\n }\n const port = parsePositiveInt(raw.port, `${where}.ready_when.port`, 0);\n if (port < 1 || port > 65535) {\n throw new ConfigError(`${where}.ready_when.port must be between 1 and 65535`);\n }\n const host =\n raw.host === undefined\n ? DEFAULT_PROBE_HOST\n : assertString(raw.host, `${where}.ready_when.host`);\n return { kind: 'port', port, host, intervalMs, initialDelayMs, timeoutMs, onTimeout };\n }\n\n if (raw.host !== undefined) {\n throw new ConfigError(`${where}.ready_when.host only applies to port probes`);\n }\n const url = assertString(raw.http, `${where}.ready_when.http`);\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new ConfigError(`${where}.ready_when.http must be a valid URL`);\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new ConfigError(`${where}.ready_when.http must use http(s):// (got ${parsed.protocol})`);\n }\n let expectStatus: number | undefined;\n if (raw.expect_status !== undefined) {\n expectStatus = parsePositiveInt(raw.expect_status, `${where}.ready_when.expect_status`, 0);\n if (expectStatus < 100 || expectStatus > 599) {\n throw new ConfigError(`${where}.ready_when.expect_status must be between 100 and 599`);\n }\n }\n return { kind: 'http', url, expectStatus, intervalMs, initialDelayMs, timeoutMs, onTimeout };\n}\n\n/**\n * The only container port AgentBox reserves + publishes for a web service today\n * (see `WEB_CONTAINER_PORT` host-side in @agentbox/sandbox-docker). A service's\n * `expose.as` must equal this — any other value would parse fine but be\n * unreachable from the host, so we reject it loudly.\n */\nexport const RESERVED_WEB_PORT = 80;\n\nconst SERVICE_KEYS = new Set([\n 'command',\n 'cwd',\n 'env',\n 'autostart',\n 'restart',\n 'backoff',\n 'needs',\n 'ready_when',\n 'expose',\n 'ide',\n]);\n\nconst EXPOSE_KEYS = new Set(['port', 'as']);\n\nfunction parseExpose(raw: unknown, where: string): ExposeSpec | undefined {\n if (raw === undefined || raw === null) return undefined;\n if (!isPlainObject(raw)) {\n throw new ConfigError(`${where}.expose must be a mapping`);\n }\n rejectUnknownKeys(raw, EXPOSE_KEYS, `${where}.expose`);\n if (raw.port === undefined) {\n throw new ConfigError(`${where}.expose.port is required`);\n }\n const port = parsePortNumber(raw.port, `${where}.expose.port`);\n const as = raw.as === undefined ? RESERVED_WEB_PORT : parsePortNumber(raw.as, `${where}.expose.as`);\n if (as !== RESERVED_WEB_PORT) {\n throw new ConfigError(\n `${where}.expose.as must be ${String(RESERVED_WEB_PORT)} (the only container port AgentBox publishes)`,\n );\n }\n return { port, as };\n}\n\nfunction parsePortNumber(raw: unknown, where: string): number {\n if (typeof raw !== 'number' || !Number.isInteger(raw) || raw < 1 || raw > 65535) {\n throw new ConfigError(`${where} must be an integer between 1 and 65535`);\n }\n return raw;\n}\n\nfunction parseService(name: string, raw: unknown): ServiceSpec {\n const where = `services.${name}`;\n if (!isPlainObject(raw)) {\n throw new ConfigError(`${where} must be a mapping`);\n }\n rejectUnknownKeys(raw, SERVICE_KEYS, where);\n const command = parseCommand(raw.command, where);\n const cwd = raw.cwd === undefined ? undefined : assertString(raw.cwd, `${where}.cwd`);\n const env = parseEnv(raw.env, where);\n const autostart =\n raw.autostart === undefined ? true : assertBool(raw.autostart, `${where}.autostart`);\n const restart = parseRestart(raw.restart, where);\n const backoff = parseBackoff(raw.backoff, where);\n const needs = parseNeeds(raw.needs, `${where}.needs`);\n const readyWhen = parseReadyWhen(raw.ready_when, where);\n const expose = parseExpose(raw.expose, where);\n return { name, command, cwd, env, autostart, restart, backoff, needs, readyWhen, expose };\n}\n\nconst TASK_KEYS = new Set(['command', 'cwd', 'env', 'needs']);\n\nfunction parseTask(name: string, raw: unknown): TaskSpec {\n const where = `tasks.${name}`;\n if (!isPlainObject(raw)) {\n throw new ConfigError(`${where} must be a mapping`);\n }\n rejectUnknownKeys(raw, TASK_KEYS, where);\n const command = parseCommand(raw.command, where);\n const cwd = raw.cwd === undefined ? undefined : assertString(raw.cwd, `${where}.cwd`);\n const env = parseEnv(raw.env, where);\n const needs = parseNeeds(raw.needs, `${where}.needs`);\n return { name, command, cwd, env, needs };\n}\n\nfunction assertString(raw: unknown, where: string): string {\n if (typeof raw !== 'string') throw new ConfigError(`${where} must be a string`);\n return raw;\n}\n\nfunction assertBool(raw: unknown, where: string): boolean {\n if (typeof raw !== 'boolean') throw new ConfigError(`${where} must be a boolean`);\n return raw;\n}\n\n// `defaults` is the host-side config layer (read by @agentbox/config) — the\n// supervisor doesn't touch it, but we accept it here so `ctl validate` doesn't\n// flag it as unknown. Strict typo-detection still applies (top-level keys\n// outside this set are rejected).\nconst TOP_LEVEL_KEYS = new Set(['services', 'tasks', 'ide', 'defaults']);\n\nfunction validateUnitGraph(tasks: TaskSpec[], services: ServiceSpec[]): void {\n const names = new Set<string>();\n for (const t of tasks) {\n if (names.has(t.name)) {\n throw new ConfigError(`unit name \"${t.name}\" declared more than once (task vs service collision)`);\n }\n names.add(t.name);\n }\n for (const s of services) {\n if (names.has(s.name)) {\n throw new ConfigError(`unit name \"${s.name}\" declared more than once (task vs service collision)`);\n }\n names.add(s.name);\n }\n\n const deps = new Map<string, string[]>();\n for (const t of tasks) deps.set(t.name, t.needs);\n for (const s of services) deps.set(s.name, s.needs);\n\n for (const [unit, list] of deps) {\n for (const dep of list) {\n if (!names.has(dep)) {\n throw new ConfigError(`unit \"${unit}\" needs unknown unit \"${dep}\"`);\n }\n if (dep === unit) {\n throw new ConfigError(`unit \"${unit}\" cannot depend on itself`);\n }\n }\n }\n\n // DFS with three-color marking; record the cycle path in the error.\n const WHITE = 0, GRAY = 1, BLACK = 2;\n const color = new Map<string, number>();\n for (const name of deps.keys()) color.set(name, WHITE);\n const stack: string[] = [];\n\n function visit(name: string): void {\n color.set(name, GRAY);\n stack.push(name);\n for (const dep of deps.get(name)!) {\n const c = color.get(dep) ?? WHITE;\n if (c === GRAY) {\n const startIdx = stack.indexOf(dep);\n const cycle = stack.slice(startIdx).concat(dep).join(' → ');\n throw new ConfigError(`cyclic dependency: ${cycle}`);\n }\n if (c === WHITE) visit(dep);\n }\n stack.pop();\n color.set(name, BLACK);\n }\n\n for (const name of deps.keys()) {\n if (color.get(name) === WHITE) visit(name);\n }\n}\n\nexport function parseConfig(text: string): CtlConfig {\n let doc: unknown;\n try {\n doc = parseYaml(text);\n } catch (err) {\n throw new ConfigError(`yaml parse error: ${err instanceof Error ? err.message : String(err)}`);\n }\n if (doc === null || doc === undefined) return { services: [], tasks: [] };\n if (!isPlainObject(doc)) {\n throw new ConfigError('top-level config must be a mapping');\n }\n rejectUnknownKeys(doc, TOP_LEVEL_KEYS, '(root)');\n\n const services: ServiceSpec[] = [];\n const servicesRaw = doc.services;\n if (servicesRaw !== undefined && servicesRaw !== null) {\n if (!isPlainObject(servicesRaw)) {\n throw new ConfigError('services must be a mapping of name → service');\n }\n for (const [name, raw] of Object.entries(servicesRaw)) {\n if (!/^[A-Za-z0-9_-]+$/.test(name)) {\n throw new ConfigError(`service name \"${name}\" must match [A-Za-z0-9_-]+`);\n }\n services.push(parseService(name, raw));\n }\n }\n\n const tasks: TaskSpec[] = [];\n const tasksRaw = doc.tasks;\n if (tasksRaw !== undefined && tasksRaw !== null) {\n if (!isPlainObject(tasksRaw)) {\n throw new ConfigError('tasks must be a mapping of name → task');\n }\n for (const [name, raw] of Object.entries(tasksRaw)) {\n if (!/^[A-Za-z0-9_-]+$/.test(name)) {\n throw new ConfigError(`task name \"${name}\" must match [A-Za-z0-9_-]+`);\n }\n tasks.push(parseTask(name, raw));\n }\n }\n\n // ide: parsed only enough to confirm it's an object; contents are host-side\n // and the supervisor doesn't touch them. Schema is permissive here too.\n if (doc.ide !== undefined && doc.ide !== null && !isPlainObject(doc.ide)) {\n throw new ConfigError('ide must be a mapping');\n }\n\n // defaults: host-side layered-config block. We only require it to be a\n // mapping here; @agentbox/config validates the leaves strictly when the\n // host loads the file. Letting ctl deep-validate would force a circular\n // dependency on the host-only @agentbox/config package — see CLAUDE.md.\n if (doc.defaults !== undefined && doc.defaults !== null && !isPlainObject(doc.defaults)) {\n throw new ConfigError('defaults must be a mapping');\n }\n\n validateUnitGraph(tasks, services);\n\n const exposed = services.filter((s) => s.expose);\n if (exposed.length > 1) {\n throw new ConfigError(\n `at most one service may set expose: (got: ${exposed.map((s) => s.name).join(', ')})`,\n );\n }\n\n return { services, tasks };\n}\n\nexport async function loadConfig(path: string): Promise<CtlConfig> {\n let text: string;\n try {\n text = await readFile(path, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return { services: [], tasks: [] };\n }\n throw err;\n }\n return parseConfig(text);\n}\n\nexport function describeCommand(cmd: string | string[]): string {\n return Array.isArray(cmd) ? cmd.join(' ') : cmd;\n}\n","/**\n * Layered user config. The same shape is accepted at three layers:\n * - global ~/.agentbox/config.yaml\n * - project ~/.agentbox/projects/<hash>/config.yaml\n * - workspace defaults: block in ./agentbox.yaml\n *\n * Plus a CLI-flag layer at runtime. Precedence (highest wins):\n * cli > workspace > project > global > built-in defaults.\n */\n\nexport type IdeFlavor = 'vscode' | 'cursor' | 'auto';\nexport type EngineKind = 'orbstack' | 'docker-desktop' | 'other' | 'auto';\nexport type BrowserKind = 'agent-browser' | 'playwright' | 'both';\n/** Sandbox backend new boxes are created on. */\nexport type ProviderKind = 'docker' | 'daytona' | 'hetzner';\n/** Where `agentbox claude|codex|opencode` opens the attached session when the host\n * shell is running inside tmux or iTerm2. `same` keeps today's inline behavior. */\nexport type AttachOpenIn = 'split' | 'window' | 'tab' | 'same';\n\nexport interface UserConfig {\n box?: {\n provider?: ProviderKind;\n hostSnapshot?: boolean;\n defaultCheckpoint?: string;\n /** Per-provider override of `defaultCheckpoint`. Resolved before falling back to the global. */\n defaultCheckpointDocker?: string;\n defaultCheckpointDaytona?: string;\n defaultCheckpointHetzner?: string;\n withPlaywright?: boolean;\n withEnv?: boolean;\n vnc?: boolean;\n isolateClaudeConfig?: boolean;\n isolateCodexConfig?: boolean;\n isolateOpencodeConfig?: boolean;\n image?: string;\n dockerCacheShared?: boolean;\n memory?: number;\n cpus?: number;\n pidsLimit?: number;\n disk?: string;\n };\n checkpoint?: {\n maxLayers?: number;\n };\n claude?: {\n sessionName?: string;\n };\n codex?: {\n sessionName?: string;\n };\n opencode?: {\n sessionName?: string;\n };\n attach?: {\n openIn?: AttachOpenIn;\n };\n code?: {\n ide?: IdeFlavor;\n wait?: boolean;\n timeoutMs?: number;\n autoTerminals?: boolean;\n };\n shell?: {\n user?: string;\n login?: boolean;\n tmux?: boolean;\n };\n engine?: {\n kind?: EngineKind;\n };\n browser?: {\n default?: BrowserKind;\n };\n relay?: {\n port?: number;\n };\n vnc?: {\n containerPort?: number;\n };\n portless?: {\n enabled?: boolean;\n stateDir?: string;\n };\n autopause?: {\n enabled?: boolean;\n maxRunningBoxes?: number;\n idleMinutes?: number;\n };\n maintenance?: {\n pruneProjectConfigs?: boolean;\n pruneProjectConfigsEvery?: number;\n };\n}\n\n/**\n * Required-everywhere variant returned as the merged effective config. Each\n * leaf is filled from BUILT_IN_DEFAULTS when no layer set it.\n *\n * `box.hostSnapshot` and `portless.enabled` are intentionally\n * `boolean | undefined` (unprompted): the default is \"ask the user\", expressed\n * as undefined.\n */\nexport interface EffectiveConfig {\n box: {\n provider: ProviderKind;\n hostSnapshot: boolean | undefined;\n defaultCheckpoint: string;\n defaultCheckpointDocker: string;\n defaultCheckpointDaytona: string;\n defaultCheckpointHetzner: string;\n withPlaywright: boolean;\n withEnv: boolean;\n vnc: boolean;\n isolateClaudeConfig: boolean;\n isolateCodexConfig: boolean;\n isolateOpencodeConfig: boolean;\n image: string;\n dockerCacheShared: boolean;\n memory: number;\n cpus: number;\n pidsLimit: number;\n disk: string;\n };\n checkpoint: {\n maxLayers: number;\n };\n claude: {\n sessionName: string;\n };\n codex: {\n sessionName: string;\n };\n opencode: {\n sessionName: string;\n };\n attach: {\n openIn: AttachOpenIn;\n };\n code: {\n ide: IdeFlavor;\n wait: boolean;\n timeoutMs: number;\n autoTerminals: boolean;\n };\n shell: {\n user: string;\n login: boolean;\n tmux: boolean;\n };\n engine: {\n kind: EngineKind;\n };\n browser: {\n default: BrowserKind;\n };\n relay: {\n port: number;\n };\n vnc: {\n containerPort: number;\n };\n portless: {\n enabled: boolean | undefined;\n stateDir: string;\n };\n autopause: {\n enabled: boolean;\n maxRunningBoxes: number;\n idleMinutes: number;\n };\n maintenance: {\n pruneProjectConfigs: boolean;\n pruneProjectConfigsEvery: number;\n };\n}\n\nexport type ConfigSource = 'cli' | 'workspace' | 'project' | 'global' | 'default';\n\nexport interface ConfigLayer {\n source: ConfigSource;\n /** File path the layer was loaded from. Absent for `cli` and `default`. */\n path?: string;\n values: Partial<UserConfig>;\n}\n\nexport interface LoadedConfig {\n effective: EffectiveConfig;\n layers: {\n cli: { values: Partial<UserConfig> };\n workspace: { path: string | null; values: Partial<UserConfig> };\n project: { path: string; values: Partial<UserConfig> };\n global: { path: string; values: Partial<UserConfig> };\n defaults: EffectiveConfig;\n };\n /** Per-leaf source map: 'box.hostSnapshot' -> 'workspace'. Powers `config get --all`. */\n sources: Record<string, ConfigSource>;\n /** Resolved project root used for the project layer (cwd if no agentbox.yaml found). */\n projectRoot: string;\n projectHash: string;\n /** True if we walked up to an agentbox.yaml; false if we fell back to cwd. */\n hasAgentboxYaml: boolean;\n}\n\nexport const BUILT_IN_DEFAULTS: EffectiveConfig = {\n box: {\n provider: 'docker',\n hostSnapshot: undefined,\n defaultCheckpoint: '',\n defaultCheckpointDocker: '',\n defaultCheckpointDaytona: '',\n defaultCheckpointHetzner: '',\n withPlaywright: false,\n withEnv: false,\n vnc: true,\n isolateClaudeConfig: false,\n isolateCodexConfig: false,\n isolateOpencodeConfig: false,\n image: 'agentbox/box:dev',\n dockerCacheShared: false,\n memory: 0,\n cpus: 0,\n pidsLimit: 0,\n disk: '',\n },\n checkpoint: {\n maxLayers: 3,\n },\n claude: {\n sessionName: 'claude',\n },\n codex: {\n sessionName: 'codex',\n },\n opencode: {\n sessionName: 'opencode',\n },\n attach: {\n openIn: 'split',\n },\n code: {\n ide: 'auto',\n wait: true,\n timeoutMs: 120_000,\n autoTerminals: true,\n },\n shell: {\n user: 'vscode',\n login: true,\n tmux: true,\n },\n engine: {\n kind: 'auto',\n },\n browser: {\n default: 'agent-browser',\n },\n relay: {\n port: 8787,\n },\n vnc: {\n containerPort: 6080,\n },\n portless: {\n enabled: undefined,\n stateDir: '',\n },\n autopause: {\n enabled: true,\n maxRunningBoxes: 5,\n idleMinutes: 5,\n },\n maintenance: {\n pruneProjectConfigs: true,\n pruneProjectConfigsEvery: 50,\n },\n};\n\nexport type KeyType = 'bool' | 'string' | 'int' | 'enum';\n\nexport interface KeyDescriptor {\n /** Dot-path, e.g. \"box.snapshot\". */\n key: string;\n type: KeyType;\n enumValues?: readonly string[];\n description: string;\n /** True for keys most users shouldn't touch (image, ports). Hidden from `list` by default. */\n advanced?: boolean;\n}\n\n/**\n * Single source of truth for which keys are addressable from the CLI. The\n * parser, `set`/`unset`, and `list` all walk this. Adding a key here is the\n * one place a new field has to be registered (plus the type interface above\n * and the JSON schema).\n */\nexport const KEY_REGISTRY: readonly KeyDescriptor[] = [\n {\n key: 'box.provider',\n type: 'enum',\n enumValues: ['docker', 'daytona', 'hetzner'] as const,\n description:\n 'Sandbox backend new boxes are created on: local Docker containers, Daytona Cloud sandboxes, or Hetzner Cloud VPSes.',\n },\n {\n key: 'box.hostSnapshot',\n type: 'bool',\n description:\n 'Use a frozen APFS clone of the host workspace as the overlay lower (default: prompt). Was box.snapshot.',\n },\n {\n key: 'box.defaultCheckpoint',\n type: 'string',\n description:\n 'Checkpoint ref new boxes in this project start from when --snapshot is not given (set via `agentbox checkpoint set-default`). Used as fallback when no per-provider override is set.',\n },\n {\n key: 'box.defaultCheckpointDocker',\n type: 'string',\n description:\n 'Per-provider override of `box.defaultCheckpoint` for docker. Wins over the global when set; set via `agentbox checkpoint set-default --provider docker`.',\n advanced: true,\n },\n {\n key: 'box.defaultCheckpointDaytona',\n type: 'string',\n description:\n 'Per-provider override of `box.defaultCheckpoint` for daytona. Wins over the global when set; set via `agentbox checkpoint set-default --provider daytona`.',\n advanced: true,\n },\n {\n key: 'box.defaultCheckpointHetzner',\n type: 'string',\n description:\n 'Per-provider override of `box.defaultCheckpoint` for hetzner. Wins over the global when set; set via `agentbox checkpoint set-default --provider hetzner`.',\n advanced: true,\n },\n {\n key: 'checkpoint.maxLayers',\n type: 'int',\n description:\n 'Max stacked checkpoint layers before a new checkpoint is materialized merged (flattened) instead of layered.',\n advanced: true,\n },\n {\n key: 'box.withPlaywright',\n type: 'bool',\n description: 'Install @playwright/cli@latest in the box at create time.',\n },\n {\n key: 'box.withEnv',\n type: 'bool',\n description:\n 'Copy host env/config files (.env*, secrets.toml, agentbox.yaml, ...) into /workspace at box create time (gitignore-bypassing).',\n },\n {\n key: 'box.vnc',\n type: 'bool',\n description: 'Run the per-box Xvnc + noVNC stack.',\n },\n {\n key: 'box.isolateClaudeConfig',\n type: 'bool',\n description: 'Use a per-box ~/.claude volume instead of the shared one.',\n },\n {\n key: 'box.isolateCodexConfig',\n type: 'bool',\n description: 'Use a per-box ~/.codex volume instead of the shared one.',\n },\n {\n key: 'box.isolateOpencodeConfig',\n type: 'bool',\n description: 'Use a per-box OpenCode config/data volume instead of the shared one.',\n },\n {\n key: 'box.image',\n type: 'string',\n description: 'Box image ref (advanced).',\n advanced: true,\n },\n {\n key: 'box.dockerCacheShared',\n type: 'bool',\n description:\n \"Share the in-box docker image cache across boxes via the 'agentbox-docker-cache' volume (preserved on destroy/prune; only one box can run at a time when set).\",\n },\n {\n key: 'box.memory',\n type: 'int',\n description:\n 'Hard memory ceiling in MiB for new boxes (0 = unlimited). Use --memory on create/claude for byte/k/m/g strings.',\n },\n {\n key: 'box.cpus',\n type: 'int',\n description:\n 'CPU count cap for new boxes (0 = unlimited). Whole cores via config; use --cpus for fractional (e.g. 1.5).',\n },\n {\n key: 'box.pidsLimit',\n type: 'int',\n description: 'Max process count (PIDs cgroup) for new boxes (0 = unlimited).',\n },\n {\n key: 'box.disk',\n type: 'string',\n description:\n \"Best-effort writable-layer size for new boxes, e.g. '10G'. No-op on overlay2 / the macOS engines.\",\n advanced: true,\n },\n {\n key: 'claude.sessionName',\n type: 'string',\n description: 'tmux session name for `agentbox claude`.',\n },\n {\n key: 'codex.sessionName',\n type: 'string',\n description: 'tmux session name for `agentbox codex`.',\n },\n {\n key: 'opencode.sessionName',\n type: 'string',\n description: 'tmux session name for `agentbox opencode`.',\n },\n {\n key: 'attach.openIn',\n type: 'enum',\n enumValues: ['split', 'window', 'tab', 'same'] as const,\n description:\n 'Where `agentbox claude|codex|opencode` opens the attached session when run from tmux or iTerm2: `split` (tmux split-window / iTerm2 vertical split, default), `window` (tmux new-window / new iTerm2 window), `tab` (tmux new-window / new iTerm2 tab), or `same` (attach inline in the current terminal). Outside tmux/iTerm2 every value behaves like `same`.',\n },\n {\n key: 'code.ide',\n type: 'enum',\n enumValues: ['vscode', 'cursor', 'auto'] as const,\n description: 'Which IDE `agentbox code` launches; \"auto\" prefers code, falls back to cursor.',\n },\n {\n key: 'code.wait',\n type: 'bool',\n description: 'Block on agentbox-ctl wait-ready before opening the IDE.',\n },\n {\n key: 'code.timeoutMs',\n type: 'int',\n description: 'wait-ready timeout in milliseconds.',\n },\n {\n key: 'code.autoTerminals',\n type: 'bool',\n description: 'Generate /workspace/.vscode/tasks.json so the IDE auto-opens log panels.',\n },\n {\n key: 'shell.user',\n type: 'string',\n description: 'Default in-container user for `agentbox shell`.',\n },\n {\n key: 'shell.login',\n type: 'bool',\n description: 'Pass `-l` to bash so the login profile loads.',\n },\n {\n key: 'shell.tmux',\n type: 'bool',\n description: 'Run `agentbox shell` inside a detachable tmux session (Ctrl+a d to detach).',\n },\n {\n key: 'engine.kind',\n type: 'enum',\n enumValues: ['orbstack', 'docker-desktop', 'other', 'auto'] as const,\n description: 'Override the docker-engine auto-detection (used for OrbStack-only optimisations).',\n },\n {\n key: 'browser.default',\n type: 'enum',\n enumValues: ['agent-browser', 'playwright', 'both'] as const,\n description: 'Default browser stack inside the box. \"playwright\" or \"both\" implies box.withPlaywright.',\n },\n {\n key: 'relay.port',\n type: 'int',\n description: 'Host relay TCP port (advanced).',\n advanced: true,\n },\n {\n key: 'vnc.containerPort',\n type: 'int',\n description: 'Container-side noVNC port (advanced).',\n advanced: true,\n },\n {\n key: 'portless.enabled',\n type: 'bool',\n description:\n 'Map each box web app to a https://<box-name>.localhost URL via the Portless proxy (Docker Desktop only; OrbStack already has .orb.local). Default: prompt.',\n },\n {\n key: 'portless.stateDir',\n type: 'string',\n description:\n 'Host Portless state directory to share into boxes (advanced; default: Portless’s own location).',\n advanced: true,\n },\n {\n key: 'autopause.enabled',\n type: 'bool',\n description:\n 'Let the host relay periodically pause idle boxes when more than autopause.maxRunningBoxes are running.',\n },\n {\n key: 'autopause.maxRunningBoxes',\n type: 'int',\n description:\n 'Target maximum number of simultaneously-running boxes before idle ones get auto-paused.',\n },\n {\n key: 'autopause.idleMinutes',\n type: 'int',\n description:\n 'Minutes a box must be continuously idle (claude state) before it is eligible for auto-pause.',\n },\n {\n key: 'maintenance.pruneProjectConfigs',\n type: 'bool',\n description:\n 'Periodically delete ~/.agentbox/projects/<hash>/ dirs whose source workspace folder no longer exists.',\n },\n {\n key: 'maintenance.pruneProjectConfigsEvery',\n type: 'int',\n description: 'Run the orphan project-config sweep every N successful `agentbox create`.',\n },\n];\n\nconst REGISTRY_BY_KEY = new Map<string, KeyDescriptor>(KEY_REGISTRY.map((d) => [d.key, d]));\n\nexport function lookupKey(key: string): KeyDescriptor | undefined {\n return REGISTRY_BY_KEY.get(key);\n}\n\nexport class UserConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'UserConfigError';\n }\n}\n\nexport type ConfigScope = 'global' | 'project';\n","import { parse as parseYaml } from 'yaml';\nimport {\n KEY_REGISTRY,\n type KeyDescriptor,\n type UserConfig,\n UserConfigError,\n} from './types.js';\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\n/** Keys removed in a rename. Surfaced with a migration hint instead of a bare \"unknown key\". */\nconst RENAMED_KEYS: ReadonlyMap<string, string> = new Map([['box.snapshot', 'box.hostSnapshot']]);\n\ninterface BranchSpec {\n name: string;\n /** Map of leaf-key (dot-suffix) → descriptor whose `key` starts with this branch. */\n leaves: Map<string, KeyDescriptor>;\n}\n\nconst BRANCHES: Map<string, BranchSpec> = (() => {\n const out = new Map<string, BranchSpec>();\n for (const desc of KEY_REGISTRY) {\n const idx = desc.key.indexOf('.');\n if (idx < 0) {\n throw new Error(`KEY_REGISTRY entry ${desc.key} must use dot-path form (branch.leaf)`);\n }\n const branch = desc.key.slice(0, idx);\n const leaf = desc.key.slice(idx + 1);\n let entry = out.get(branch);\n if (!entry) {\n entry = { name: branch, leaves: new Map() };\n out.set(branch, entry);\n }\n entry.leaves.set(leaf, desc);\n }\n return out;\n})();\n\n/**\n * Coerce a typed YAML scalar (already typed by the YAML parser) into the\n * descriptor's expected type. Strings from the CLI go through `coerceFromString`\n * instead — that path accepts e.g. \"true\" or \"120000\".\n */\nfunction coerceTypedValue(raw: unknown, desc: KeyDescriptor, where: string): unknown {\n if (raw === null) {\n throw new UserConfigError(`${where} must not be null (use \\`agentbox config unset\\` to clear)`);\n }\n switch (desc.type) {\n case 'bool':\n if (typeof raw !== 'boolean') {\n throw new UserConfigError(`${where} must be a boolean (got ${typeof raw})`);\n }\n return raw;\n case 'string':\n if (typeof raw !== 'string') {\n throw new UserConfigError(`${where} must be a string (got ${typeof raw})`);\n }\n if (raw.length === 0) {\n throw new UserConfigError(`${where} must not be empty`);\n }\n return raw;\n case 'int':\n if (typeof raw !== 'number' || !Number.isInteger(raw)) {\n throw new UserConfigError(`${where} must be an integer (got ${String(raw)})`);\n }\n return raw;\n case 'enum':\n if (typeof raw !== 'string' || !desc.enumValues!.includes(raw)) {\n throw new UserConfigError(\n `${where} must be one of: ${desc.enumValues!.join(', ')} (got ${String(raw)})`,\n );\n }\n return raw;\n }\n}\n\n/**\n * Parse a UserConfig document text (YAML). Strict: unknown branches and\n * unknown leaves throw UserConfigError so typos surface early.\n *\n * `where` is the human-readable origin for error messages, e.g. the file path.\n * Empty / whitespace-only input parses to `{}`.\n */\nexport function parseUserConfig(text: string, where: string): Partial<UserConfig> {\n let doc: unknown;\n try {\n doc = parseYaml(text);\n } catch (err) {\n throw new UserConfigError(\n `${where}: yaml parse error: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n if (doc === null || doc === undefined) return {};\n if (!isPlainObject(doc)) {\n throw new UserConfigError(`${where}: top-level must be a mapping`);\n }\n return parseUserConfigObject(doc, where);\n}\n\n/**\n * Same validation as `parseUserConfig` but starting from an already-decoded\n * object. Used when the caller has the YAML parsed (e.g. agentbox.yaml's\n * `defaults:` block).\n */\nexport function parseUserConfigObject(doc: unknown, where: string): Partial<UserConfig> {\n if (doc === null || doc === undefined) return {};\n if (!isPlainObject(doc)) {\n throw new UserConfigError(`${where}: must be a mapping`);\n }\n\n const out: Partial<UserConfig> = {};\n for (const [branchName, branchRaw] of Object.entries(doc)) {\n const branchSpec = BRANCHES.get(branchName);\n if (!branchSpec) {\n throw new UserConfigError(\n `${where}: unknown config section \"${branchName}\" (known: ${[...BRANCHES.keys()].join(', ')})`,\n );\n }\n if (branchRaw === null || branchRaw === undefined) continue;\n if (!isPlainObject(branchRaw)) {\n throw new UserConfigError(`${where}.${branchName}: must be a mapping`);\n }\n const branchOut: Record<string, unknown> = {};\n for (const [leafName, leafRaw] of Object.entries(branchRaw)) {\n const desc = branchSpec.leaves.get(leafName);\n if (!desc) {\n const renamedTo = RENAMED_KEYS.get(`${branchName}.${leafName}`);\n if (renamedTo) {\n throw new UserConfigError(\n `${where}.${branchName}.${leafName} was renamed to ${renamedTo} — update your config`,\n );\n }\n throw new UserConfigError(\n `${where}.${branchName}: unknown key \"${leafName}\" (known: ${[...branchSpec.leaves.keys()].join(', ')})`,\n );\n }\n if (leafRaw === undefined) continue;\n branchOut[leafName] = coerceTypedValue(leafRaw, desc, `${where}.${desc.key}`);\n }\n if (Object.keys(branchOut).length > 0) {\n // We've validated that each branch matches one of UserConfig's known\n // sub-objects; the indexed write keeps the union type happy.\n (out as Record<string, unknown>)[branchName] = branchOut;\n }\n }\n return out;\n}\n\n/**\n * Coerce a string (e.g. typed at the CLI by `agentbox config set`) into the\n * declared type for `key`. Booleans accept true/false/yes/no/1/0 (case\n * insensitive). Returns the typed value or throws UserConfigError.\n */\nexport function coerceFromString(key: string, raw: string): unknown {\n const desc = lookupKeyOrThrow(key);\n switch (desc.type) {\n case 'bool': {\n const v = raw.trim().toLowerCase();\n if (v === 'true' || v === 'yes' || v === '1' || v === 'on') return true;\n if (v === 'false' || v === 'no' || v === '0' || v === 'off') return false;\n throw new UserConfigError(`${key}: expected a boolean (true/false), got \"${raw}\"`);\n }\n case 'string':\n if (raw.length === 0) throw new UserConfigError(`${key}: must not be empty`);\n return raw;\n case 'int': {\n const n = Number(raw);\n if (!Number.isFinite(n) || !Number.isInteger(n)) {\n throw new UserConfigError(`${key}: expected an integer, got \"${raw}\"`);\n }\n return n;\n }\n case 'enum':\n if (!desc.enumValues!.includes(raw)) {\n throw new UserConfigError(\n `${key}: expected one of ${desc.enumValues!.join(', ')}, got \"${raw}\"`,\n );\n }\n return raw;\n }\n}\n\nfunction lookupKeyOrThrow(key: string): KeyDescriptor {\n const renamedTo = RENAMED_KEYS.get(key);\n if (renamedTo) {\n throw new UserConfigError(`${key} was renamed to ${renamedTo} — use ${renamedTo} instead`);\n }\n const idx = key.indexOf('.');\n if (idx < 0) {\n throw new UserConfigError(`unknown key \"${key}\" (must be in branch.leaf form)`);\n }\n const branch = BRANCHES.get(key.slice(0, idx));\n if (!branch) {\n throw new UserConfigError(\n `unknown config section \"${key.slice(0, idx)}\" (known: ${[...BRANCHES.keys()].join(', ')})`,\n );\n }\n const desc = branch.leaves.get(key.slice(idx + 1));\n if (!desc) {\n throw new UserConfigError(\n `unknown key \"${key}\" (known in ${branch.name}: ${[...branch.leaves.keys()].join(', ')})`,\n );\n }\n return desc;\n}\n","import { createHash } from 'node:crypto';\nimport { realpath, stat } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { basename, dirname, join, resolve } from 'node:path';\nimport type { ConfigScope } from './types.js';\n\nexport const STATE_DIR = join(homedir(), '.agentbox');\nexport const GLOBAL_CONFIG_FILE = join(STATE_DIR, 'config.yaml');\nexport const PROJECTS_DIR = join(STATE_DIR, 'projects');\nexport const WORKSPACE_CONFIG_BASENAME = 'agentbox.yaml';\n\nexport interface ProjectRoot {\n /** Absolute path to the resolved project root (host filesystem). */\n root: string;\n hasAgentboxYaml: boolean;\n}\n\n/**\n * Walk up from `cwd` until we find an `agentbox.yaml`. That dir is the\n * \"project\". If no ancestor has one, we fall back to `cwd` (per spec) so\n * `agentbox config` still does something sane in dirs without a workspace\n * file. The returned path is always absolute and **symlink-canonicalised**\n * via `realpath` — without this, macOS's `/tmp` symlink to `/private/tmp`\n * makes `findProjectRoot('/tmp/x')` (at create time, --workspace) and\n * `findProjectRoot(process.cwd())` (at resolve time, the same dir) return\n * different roots, breaking the per-project box index match.\n */\nexport async function findProjectRoot(cwd: string): Promise<ProjectRoot> {\n const start = await canonicalize(cwd);\n let dir = start;\n // Defensive cap on iterations: filesystem roots end with `dirname(x) === x`.\n for (let i = 0; i < 64; i++) {\n if (await fileExists(join(dir, WORKSPACE_CONFIG_BASENAME))) {\n return { root: dir, hasAgentboxYaml: true };\n }\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return { root: start, hasAgentboxYaml: false };\n}\n\nasync function canonicalize(p: string): Promise<string> {\n const abs = resolve(p);\n // realpath only works for paths that exist. cwd and create-time\n // workspaces always do; fall back to the resolved (non-canonicalised)\n // path for anything else (e.g. config get from a deleted dir).\n try {\n return await realpath(abs);\n } catch {\n return abs;\n }\n}\n\nasync function fileExists(p: string): Promise<boolean> {\n try {\n const st = await stat(p);\n return st.isFile();\n } catch {\n return false;\n }\n}\n\n/**\n * SHA-1 (first 16 hex chars) of the *normalised* absolute path. We strip a\n * single trailing slash so `/foo/` and `/foo` hash identically. Case is\n * preserved — macOS APFS is case-preserving and so is the user's intent.\n */\nexport function hashProjectPath(absPath: string): string {\n const normalised = absPath.length > 1 && absPath.endsWith('/')\n ? absPath.slice(0, -1)\n : absPath;\n return createHash('sha1').update(normalised).digest('hex').slice(0, 16);\n}\n\n/**\n * Make `raw` safe to embed as the mnemonic half of an on-disk dir segment or a\n * Docker tag repo. Lowercased so docker tag repos stay valid; `-` collapses to\n * `_` so the single `-` between hash and mnemonic remains the only one (which\n * is what `listProjectsConfigured` parses on). Bounded length, never empty.\n */\nexport function sanitizeMnemonic(raw: string): string {\n return (\n raw\n .toLowerCase()\n .replace(/-/g, '_')\n .replace(/[^a-z0-9_]+/g, '_')\n .replace(/_+/g, '_')\n .replace(/^_+|_+$/g, '')\n .slice(0, 32) || 'unnamed'\n );\n}\n\n/**\n * On-disk dir segment for a project under `~/.agentbox/projects/` and\n * `~/.agentbox/checkpoints/`: `<hash>-<mnemonic>`. The hash stays the\n * canonical key; the trailing mnemonic is decorative — readers parse the hash\n * as the leading 16 hex chars and ignore the suffix.\n */\nexport function projectDirSegment(absPath: string): string {\n return `${hashProjectPath(absPath)}-${sanitizeMnemonic(basename(absPath))}`;\n}\n\nexport function projectConfigDir(absPath: string): string {\n return join(PROJECTS_DIR, projectDirSegment(absPath));\n}\n\nexport function projectConfigFile(absPath: string): string {\n return join(projectConfigDir(absPath), 'config.yaml');\n}\n\nexport function projectMetaFile(absPath: string): string {\n return join(projectConfigDir(absPath), 'meta.json');\n}\n\nexport function workspaceConfigFile(workspacePath: string): string {\n return join(workspacePath, WORKSPACE_CONFIG_BASENAME);\n}\n\n/**\n * Resolve a file path for a given scope. For `global`, no cwd is needed;\n * for `project`, `cwd` selects which project hash. Workspace path uses the\n * resolved project root (the dir holding `agentbox.yaml`, or cwd as fallback).\n */\nexport async function configPathFor(\n scope: ConfigScope | 'workspace',\n cwd: string,\n): Promise<string> {\n if (scope === 'global') return GLOBAL_CONFIG_FILE;\n const root = await findProjectRoot(cwd);\n if (scope === 'project') return projectConfigFile(root.root);\n return workspaceConfigFile(root.root);\n}\n","import { readFile } from 'node:fs/promises';\nimport { parse as parseYaml } from 'yaml';\nimport {\n findProjectRoot,\n GLOBAL_CONFIG_FILE,\n hashProjectPath,\n projectConfigFile,\n workspaceConfigFile,\n} from './paths.js';\nimport { parseUserConfig, parseUserConfigObject } from './parse.js';\nimport {\n BUILT_IN_DEFAULTS,\n type ConfigSource,\n type EffectiveConfig,\n KEY_REGISTRY,\n type LoadedConfig,\n type UserConfig,\n} from './types.js';\n\n/**\n * ENOENT-tolerant read of a UserConfig file. Anything else propagates.\n */\nasync function loadOptionalUserConfig(path: string): Promise<Partial<UserConfig>> {\n let text: string;\n try {\n text = await readFile(path, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return {};\n throw err;\n }\n return parseUserConfig(text, path);\n}\n\n/**\n * Read the `defaults:` block from `<workspacePath>/agentbox.yaml`. Returns\n * `{}` if the file is missing or has no `defaults:` key. Throws on YAML\n * parse errors or invalid `defaults:` content (so typos surface as soon as\n * the user runs anything in the workspace).\n *\n * The rest of `agentbox.yaml` is owned by `@agentbox/ctl` — we don't validate\n * it here and we don't depend on that package.\n */\nexport async function loadProjectAgentboxDefaults(\n workspacePath: string,\n): Promise<Partial<UserConfig>> {\n const path = workspaceConfigFile(workspacePath);\n let text: string;\n try {\n text = await readFile(path, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return {};\n throw err;\n }\n let doc: unknown;\n try {\n doc = parseYaml(text);\n } catch {\n // The ctl parser will surface its own error on the next box action; we\n // only throw if defaults: is present and broken (validated below).\n return {};\n }\n if (doc === null || doc === undefined || typeof doc !== 'object' || Array.isArray(doc)) {\n return {};\n }\n const defaults = (doc as Record<string, unknown>)['defaults'];\n if (defaults === undefined || defaults === null) return {};\n return parseUserConfigObject(defaults, `${path} defaults`);\n}\n\nexport interface LoadEffectiveConfigOptions {\n /** Highest-precedence layer; values supplied by command-line flags. */\n cliOverrides?: Partial<UserConfig>;\n}\n\n/**\n * Load and merge all four config sources for the cwd. Per-leaf precedence\n * (highest wins): cli > workspace > project > global > built-in defaults.\n *\n * The returned `sources` map has one entry per leaf in the registry — useful\n * for `agentbox config get --all` to show provenance.\n */\nexport async function loadEffectiveConfig(\n cwd: string,\n opts: LoadEffectiveConfigOptions = {},\n): Promise<LoadedConfig> {\n const projectRoot = await findProjectRoot(cwd);\n const projectPath = projectConfigFile(projectRoot.root);\n const workspacePath = projectRoot.hasAgentboxYaml ? workspaceConfigFile(projectRoot.root) : null;\n\n const [globalValues, projectValues, workspaceValues] = await Promise.all([\n loadOptionalUserConfig(GLOBAL_CONFIG_FILE),\n loadOptionalUserConfig(projectPath),\n workspacePath ? loadProjectAgentboxDefaults(projectRoot.root) : Promise.resolve({}),\n ]);\n\n const cliValues = opts.cliOverrides ?? {};\n\n const { effective, sources } = mergeLayers({\n cli: cliValues,\n workspace: workspaceValues,\n project: projectValues,\n global: globalValues,\n });\n\n return {\n effective,\n layers: {\n cli: { values: cliValues },\n workspace: { path: workspacePath, values: workspaceValues },\n project: { path: projectPath, values: projectValues },\n global: { path: GLOBAL_CONFIG_FILE, values: globalValues },\n defaults: BUILT_IN_DEFAULTS,\n },\n sources,\n projectRoot: projectRoot.root,\n projectHash: hashProjectPath(projectRoot.root),\n hasAgentboxYaml: projectRoot.hasAgentboxYaml,\n };\n}\n\ninterface MergeInput {\n cli: Partial<UserConfig>;\n workspace: Partial<UserConfig>;\n project: Partial<UserConfig>;\n global: Partial<UserConfig>;\n}\n\n/**\n * Walk every key in KEY_REGISTRY, pick the highest-precedence layer that has\n * a leaf value, and record the source. Lower-precedence layers never\n * overwrite a higher-precedence definition; absent leaves don't shadow.\n */\nfunction mergeLayers(input: MergeInput): {\n effective: EffectiveConfig;\n sources: Record<string, ConfigSource>;\n} {\n // Deep-clone the defaults; we'll overwrite leaves in place.\n const effective: EffectiveConfig = JSON.parse(JSON.stringify(BUILT_IN_DEFAULTS)) as EffectiveConfig;\n const sources: Record<string, ConfigSource> = {};\n\n const layerOrder: Array<{ source: ConfigSource; values: Partial<UserConfig> }> = [\n { source: 'cli', values: input.cli },\n { source: 'workspace', values: input.workspace },\n { source: 'project', values: input.project },\n { source: 'global', values: input.global },\n ];\n\n for (const desc of KEY_REGISTRY) {\n const idx = desc.key.indexOf('.');\n const branch = desc.key.slice(0, idx);\n const leaf = desc.key.slice(idx + 1);\n\n let chosen: { source: ConfigSource; value: unknown } | null = null;\n for (const layer of layerOrder) {\n const v = readLeaf(layer.values, branch, leaf);\n if (v !== undefined) {\n chosen = { source: layer.source, value: v };\n break;\n }\n }\n\n if (chosen) {\n writeLeaf(effective, branch, leaf, chosen.value);\n sources[desc.key] = chosen.source;\n } else {\n sources[desc.key] = 'default';\n }\n }\n\n return { effective, sources };\n}\n\nfunction readLeaf(\n obj: Partial<UserConfig>,\n branch: string,\n leaf: string,\n): unknown {\n const b = (obj as Record<string, unknown>)[branch];\n if (b === undefined || b === null || typeof b !== 'object') return undefined;\n return (b as Record<string, unknown>)[leaf];\n}\n\nfunction writeLeaf(\n obj: EffectiveConfig,\n branch: string,\n leaf: string,\n value: unknown,\n): void {\n const b = (obj as unknown as Record<string, Record<string, unknown>>)[branch];\n if (!b) return; // BUILT_IN_DEFAULTS guarantees the branch exists\n b[leaf] = value;\n}\n","/**\n * Resolve the effective default checkpoint ref for a given provider.\n *\n * Precedence (highest wins):\n * 1. `box.defaultCheckpoint<Provider>` — per-provider override\n * (`defaultCheckpointDocker` / `defaultCheckpointDaytona` /\n * `defaultCheckpointHetzner`).\n * 2. `box.defaultCheckpoint` — global fallback (back-compat shape: every\n * pre-cloud config has this; no flag was needed).\n * 3. '' — no default.\n *\n * Returning the empty string (instead of undefined) matches the\n * `EffectiveConfig.box.defaultCheckpoint` shape so call sites that already\n * test `.length > 0` keep working unchanged.\n */\nimport type { EffectiveConfig, ProviderKind } from './types.js';\n\nexport function resolveDefaultCheckpoint(\n cfg: EffectiveConfig,\n provider: ProviderKind | string,\n): string {\n // Treat unknown provider names like 'docker' for back-compat — a stray\n // value in config or argv shouldn't crash before the validation layer.\n const perProvider =\n provider === 'daytona'\n ? cfg.box.defaultCheckpointDaytona\n : provider === 'hetzner'\n ? cfg.box.defaultCheckpointHetzner\n : cfg.box.defaultCheckpointDocker;\n if (perProvider && perProvider.length > 0) return perProvider;\n return cfg.box.defaultCheckpoint;\n}\n\n/**\n * Config key (one of the flat KEY_REGISTRY entries) used by `agentbox\n * checkpoint set-default [--provider X]` to write the right field. Passing\n * an unknown provider falls back to the global key (legacy callers).\n */\nexport function defaultCheckpointConfigKey(\n provider: ProviderKind | string | undefined,\n):\n | 'box.defaultCheckpoint'\n | 'box.defaultCheckpointDocker'\n | 'box.defaultCheckpointDaytona'\n | 'box.defaultCheckpointHetzner' {\n if (provider === 'docker') return 'box.defaultCheckpointDocker';\n if (provider === 'daytona') return 'box.defaultCheckpointDaytona';\n if (provider === 'hetzner') return 'box.defaultCheckpointHetzner';\n return 'box.defaultCheckpoint';\n}\n","import { mkdir, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, join } from 'node:path';\nimport { stringify as stringifyYaml } from 'yaml';\nimport {\n configPathFor,\n findProjectRoot,\n hashProjectPath,\n PROJECTS_DIR,\n projectConfigFile,\n projectMetaFile,\n} from './paths.js';\nimport { coerceFromString, parseUserConfig } from './parse.js';\nimport { type ConfigScope, lookupKey, type UserConfig, UserConfigError } from './types.js';\nimport { readdir } from 'node:fs/promises';\n\ninterface WriteResult {\n path: string;\n /** The value we coerced and stored, after string→typed conversion. */\n coerced: unknown;\n}\n\ninterface SetOptions {\n /**\n * When true (the CLI `set` path), accept a string and coerce. When false\n * (programmatic), accept any typed value and write it through after a\n * round-trip parse for validation.\n */\n raw?: boolean;\n}\n\n/**\n * Write a single key into the chosen scope's config file. Creates parent\n * dirs and (for project scope) the meta.json sidecar. Atomic via tmp-rename.\n */\nexport async function setConfigValue(\n scope: ConfigScope,\n key: string,\n value: unknown,\n cwd: string,\n opts: SetOptions = {},\n): Promise<WriteResult> {\n if (!lookupKey(key)) {\n throw new UserConfigError(`unknown key \"${key}\"`);\n }\n\n const coerced = opts.raw && typeof value === 'string'\n ? coerceFromString(key, value)\n : value;\n\n const path = await configPathFor(scope, cwd);\n const current = await readExistingDoc(path);\n setLeaf(current, key, coerced);\n // Re-parse to validate the merged document; any change that produces an\n // invalid file (shouldn't be possible here, but defence-in-depth) throws.\n parseUserConfig(stringifyYaml(current), path);\n await atomicWriteYaml(path, current);\n\n if (scope === 'project') {\n const root = (await findProjectRoot(cwd)).root;\n await touchProjectMeta(root);\n }\n\n return { path, coerced };\n}\n\n/**\n * Remove a key from the chosen scope's config file. Empty parent objects are\n * pruned so the file stays tidy. ENOENT is treated as success.\n */\nexport async function unsetConfigValue(\n scope: ConfigScope,\n key: string,\n cwd: string,\n): Promise<{ path: string; existed: boolean }> {\n if (!lookupKey(key)) {\n throw new UserConfigError(`unknown key \"${key}\"`);\n }\n const path = await configPathFor(scope, cwd);\n const current = await readExistingDoc(path);\n const existed = unsetLeaf(current, key);\n if (!existed) return { path, existed: false };\n await atomicWriteYaml(path, current);\n if (scope === 'project') {\n const root = (await findProjectRoot(cwd)).root;\n await touchProjectMeta(root);\n }\n return { path, existed: true };\n}\n\ninterface ProjectEntry {\n /** SHA-1 (first 16 hex chars) of `originalPath` — the canonical key. */\n hash: string;\n /**\n * On-disk dir name under `PROJECTS_DIR`. Equal to the hash for legacy\n * pre-rename dirs; `<hash>-<mnemonic>` for new dirs. Used directly when we\n * need to `rm` the dir so the call works regardless of which shape happens\n * to be on disk.\n */\n dirName: string;\n originalPath: string;\n createdAt: string | null;\n lastSeenAt: string | null;\n configPath: string;\n hasConfigFile: boolean;\n}\n\n/**\n * Enumerate per-project config dirs. The meta.json's recorded\n * `originalPath` is what we report — the hash on disk is opaque.\n */\nexport async function listProjectsConfigured(): Promise<ProjectEntry[]> {\n let entries: string[];\n try {\n entries = await readdir(PROJECTS_DIR);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return [];\n throw err;\n }\n const out: ProjectEntry[] = [];\n for (const dirName of entries) {\n // Dir shape is `<sha1-16>` (legacy) or `<sha1-16>-<mnemonic>` (current);\n // either way the leading 16 hex chars are the canonical key.\n const m = /^([0-9a-f]{16})(?:-.+)?$/.exec(dirName);\n if (!m) continue;\n const hash = m[1]!;\n const meta = await readMeta(dirName);\n if (!meta) continue;\n const cfgPath = projectConfigFile(meta.originalPath);\n const hasConfig = await fileExists(cfgPath);\n out.push({\n hash,\n dirName,\n originalPath: meta.originalPath,\n createdAt: meta.createdAt,\n lastSeenAt: meta.lastSeenAt,\n configPath: cfgPath,\n hasConfigFile: hasConfig,\n });\n }\n out.sort((a, b) => a.originalPath.localeCompare(b.originalPath));\n return out;\n}\n\nexport interface PruneOrphanProjectConfigsOptions {\n dryRun?: boolean;\n /** Absolute project roots of live boxes — kept even if the folder is gone. */\n protectedPaths?: string[];\n}\n\nexport interface PruneOrphanProjectConfigsResult {\n removed: { hash: string; originalPath: string }[];\n dryRun: boolean;\n}\n\n/**\n * Delete `~/.agentbox/projects/<hash>/` dirs whose recorded `originalPath`\n * workspace folder no longer exists on disk. Conservative by construction:\n * only an ENOENT on `originalPath` counts as orphaned (a transient/permission\n * error is never treated as \"deleted\"), and any path in `protectedPaths` (the\n * project roots of still-live boxes) is left alone. Best-effort and\n * idempotent — a failed `rm` is swallowed.\n */\nexport async function pruneOrphanProjectConfigs(\n opts: PruneOrphanProjectConfigsOptions = {},\n): Promise<PruneOrphanProjectConfigsResult> {\n const dryRun = opts.dryRun ?? false;\n const keep = new Set(opts.protectedPaths ?? []);\n const removed: { hash: string; originalPath: string }[] = [];\n for (const entry of await listProjectsConfigured()) {\n if (!isAbsolute(entry.originalPath) || keep.has(entry.originalPath)) continue;\n let missing = false;\n try {\n await stat(entry.originalPath);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') missing = true;\n }\n if (!missing) continue;\n removed.push({ hash: entry.hash, originalPath: entry.originalPath });\n if (!dryRun) {\n try {\n // Remove by the on-disk dir name (originalPath is gone, so recomputing\n // the segment from it would be pointless; entry.dirName preserves\n // whichever shape — legacy `<hash>` or new `<hash>-<mnemonic>` —\n // happens to be on disk).\n await rm(join(PROJECTS_DIR, entry.dirName), { recursive: true, force: true });\n } catch {\n /* best-effort */\n }\n }\n }\n return { removed, dryRun };\n}\n\n/**\n * Sidecar counter for the periodic create-time sweep. Lives inside\n * `PROJECTS_DIR` but `listProjectsConfigured` only reads 16-hex-named dirs, so\n * a dotfile here is invisible to it.\n */\nconst PROJECT_GC_COUNTER_FILE = join(PROJECTS_DIR, '.gc.json');\n\n/** Read `{creates}` (0 if missing/corrupt), increment, atomic-write, return new value. */\nexport async function bumpProjectGcCounter(): Promise<number> {\n let prior = 0;\n try {\n const parsed = JSON.parse(await readFile(PROJECT_GC_COUNTER_FILE, 'utf8')) as {\n creates?: unknown;\n };\n if (typeof parsed.creates === 'number' && Number.isFinite(parsed.creates)) {\n prior = parsed.creates;\n }\n } catch {\n /* missing or corrupt -> start from 0 */\n }\n const next = prior + 1;\n await mkdir(PROJECTS_DIR, { recursive: true });\n const tmp = `${PROJECT_GC_COUNTER_FILE}.tmp-${process.pid.toString()}-${Date.now().toString(36)}`;\n await writeFile(tmp, JSON.stringify({ creates: next }) + '\\n', { encoding: 'utf8', mode: 0o644 });\n await rename(tmp, PROJECT_GC_COUNTER_FILE);\n return next;\n}\n\nasync function readMeta(\n dirName: string,\n): Promise<{ originalPath: string; createdAt: string | null; lastSeenAt: string | null } | null> {\n const metaPath = `${PROJECTS_DIR}/${dirName}/meta.json`;\n try {\n const text = await readFile(metaPath, 'utf8');\n const parsed = JSON.parse(text) as Record<string, unknown>;\n if (typeof parsed['originalPath'] !== 'string') return null;\n return {\n originalPath: parsed['originalPath'],\n createdAt: typeof parsed['createdAt'] === 'string' ? parsed['createdAt'] : null,\n lastSeenAt: typeof parsed['lastSeenAt'] === 'string' ? parsed['lastSeenAt'] : null,\n };\n } catch {\n return null;\n }\n}\n\nasync function fileExists(p: string): Promise<boolean> {\n try {\n const st = await stat(p);\n return st.isFile();\n } catch {\n return false;\n }\n}\n\nasync function readExistingDoc(path: string): Promise<Partial<UserConfig>> {\n let text: string;\n try {\n text = await readFile(path, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return {};\n throw err;\n }\n return parseUserConfig(text, path);\n}\n\nfunction setLeaf(doc: Partial<UserConfig>, key: string, value: unknown): void {\n const idx = key.indexOf('.');\n const branch = key.slice(0, idx);\n const leaf = key.slice(idx + 1);\n const root = doc as unknown as Record<string, Record<string, unknown>>;\n if (!root[branch] || typeof root[branch] !== 'object') {\n root[branch] = {};\n }\n root[branch][leaf] = value;\n}\n\nfunction unsetLeaf(doc: Partial<UserConfig>, key: string): boolean {\n const idx = key.indexOf('.');\n const branch = key.slice(0, idx);\n const leaf = key.slice(idx + 1);\n const root = doc as unknown as Record<string, Record<string, unknown>>;\n const b = root[branch];\n if (!b || typeof b !== 'object' || !(leaf in b)) return false;\n delete b[leaf];\n if (Object.keys(b).length === 0) {\n delete root[branch];\n }\n return true;\n}\n\nasync function atomicWriteYaml(path: string, doc: Partial<UserConfig>): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n // YAML serialises empty objects as `{}` which is correct but ugly; if the\n // doc is empty we still want a usable placeholder file.\n const text = Object.keys(doc).length === 0\n ? '# managed by agentbox config — empty\\n'\n : stringifyYaml(doc);\n const tmp = `${path}.tmp-${process.pid.toString()}-${Date.now().toString(36)}`;\n await writeFile(tmp, text, { encoding: 'utf8', mode: 0o644 });\n await rename(tmp, path);\n}\n\nasync function touchProjectMeta(absPath: string): Promise<void> {\n const dir = dirname(projectMetaFile(absPath));\n await mkdir(dir, { recursive: true });\n const metaPath = projectMetaFile(absPath);\n let prior: { originalPath?: string; createdAt?: string } = {};\n try {\n prior = JSON.parse(await readFile(metaPath, 'utf8')) as typeof prior;\n } catch {\n /* fresh file */\n }\n const now = new Date().toISOString();\n const next = {\n originalPath: absPath,\n hash: hashProjectPath(absPath),\n createdAt: prior.createdAt ?? now,\n lastSeenAt: now,\n };\n const tmp = `${metaPath}.tmp-${process.pid.toString()}-${Date.now().toString(36)}`;\n await writeFile(tmp, JSON.stringify(next, null, 2) + '\\n', { encoding: 'utf8', mode: 0o644 });\n await rename(tmp, metaPath);\n}\n\n// Re-export for ergonomics; same path resolution as the loader uses.\nexport { configPathFor };\n","import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\nimport type { BoxRecord, DockerBoxFields, FindBoxResult, StateFile } from '@agentbox/core';\n\nexport const STATE_DIR = join(homedir(), '.agentbox');\nexport const STATE_FILE = join(STATE_DIR, 'state.json');\n\nconst EMPTY: StateFile = { version: 1, boxes: [] };\n\nexport async function readState(path: string = STATE_FILE): Promise<StateFile> {\n try {\n const raw = await readFile(path, 'utf8');\n const parsed = JSON.parse(raw) as StateFile;\n if (parsed.version !== 1 || !Array.isArray(parsed.boxes)) {\n throw new Error(`unrecognized state file shape at ${path}`);\n }\n // Migrate-on-read: records written before the multi-provider split carry no\n // `provider` field — they are all Docker boxes. Default it so every\n // consumer (provider registry, `findBox`) sees a discriminated record.\n // Also backfill `box.docker` from the flat fields for Docker records so\n // forward-looking readers (7.1) see the nested shape without waiting\n // for the box to be re-recorded.\n for (const b of parsed.boxes) {\n b.provider ??= 'docker';\n if ((b.provider ?? 'docker') === 'docker' && !b.docker) {\n b.docker = projectDockerFields(b);\n }\n }\n return parsed;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return { ...EMPTY };\n }\n throw err;\n }\n}\n\nexport async function writeState(state: StateFile, path: string = STATE_FILE): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, JSON.stringify(state, null, 2) + '\\n', 'utf8');\n}\n\nexport async function recordBox(box: BoxRecord, path: string = STATE_FILE): Promise<void> {\n // Forward-looking shape: every Docker write also mirrors the flat\n // docker-specific fields into `box.docker` so readers can move to the\n // nested form opportunistically (7.1). Cloud records skip the mirror —\n // the discriminator is `box.provider !== 'docker'`.\n const toWrite: BoxRecord =\n (box.provider ?? 'docker') === 'docker' && !box.docker\n ? { ...box, docker: projectDockerFields(box) }\n : box;\n const state = await readState(path);\n const next: StateFile = {\n version: 1,\n boxes: [...state.boxes.filter((b) => b.id !== toWrite.id), toWrite],\n };\n await writeState(next, path);\n}\n\n/**\n * Build a `DockerBoxFields` payload from the flat Docker-specific fields\n * still living on `BoxRecord` for back-compat. Pure function, no\n * filesystem; safe for both `readState` migration and `recordBox` mirror.\n *\n * Once every reader uses `box.docker?.<field>` (the rest of 7.1), the\n * flat fields can be dropped and this projection becomes the canonical\n * shape. Until then, every write produces both shapes from the same\n * source so they can't drift.\n */\nfunction projectDockerFields(box: BoxRecord): DockerBoxFields {\n return {\n container: box.container,\n image: box.image,\n snapshotDir: box.snapshotDir ?? null,\n socketPath: box.socketPath,\n claudeConfigVolume: box.claudeConfigVolume,\n codexConfigVolume: box.codexConfigVolume,\n opencodeConfigVolume: box.opencodeConfigVolume,\n vscodeServerVolume: box.vscodeServerVolume,\n cursorServerVolume: box.cursorServerVolume,\n vncHostPort: box.vncHostPort,\n webHostPort: box.webHostPort,\n portlessAlias: box.portlessAlias,\n portlessUrl: box.portlessUrl,\n dockerVolume: box.dockerVolume,\n dockerCacheShared: box.dockerCacheShared,\n checkpointImage: box.checkpointImage,\n };\n}\n\nexport async function removeBoxRecord(id: string, path: string = STATE_FILE): Promise<boolean> {\n const state = await readState(path);\n const before = state.boxes.length;\n const next: StateFile = {\n version: 1,\n boxes: state.boxes.filter((b) => b.id !== id),\n };\n if (next.boxes.length === before) return false;\n await writeState(next, path);\n return true;\n}\n\n/**\n * Resolve a user-supplied identifier against the state file. Matching\n * precedence mirrors `docker`'s container reference resolution:\n *\n * 1. exact id\n * 2. unique id prefix\n * 3. exact name\n * 4. exact container name\n *\n * Returns `'ambiguous'` if step 2 finds more than one match (steps 1, 3, 4\n * are exact-match so they cannot be ambiguous on their own).\n */\nexport function findBox(idOrName: string, state: StateFile): FindBoxResult {\n const q = idOrName.trim();\n if (q.length === 0) return { kind: 'none' };\n\n const exactId = state.boxes.find((b) => b.id === q);\n if (exactId) return { kind: 'ok', box: exactId };\n\n const prefixMatches = state.boxes.filter((b) => b.id.startsWith(q));\n if (prefixMatches.length === 1) return { kind: 'ok', box: prefixMatches[0]! };\n if (prefixMatches.length > 1) return { kind: 'ambiguous', matches: prefixMatches };\n\n const byName = state.boxes.find((b) => b.name === q);\n if (byName) return { kind: 'ok', box: byName };\n\n // For docker records `container` is the docker container name; for cloud\n // records it's `cloud:<sandboxId>` (post 7.2 — no more synthetic\n // agentbox-cloud-* prefix). Either form is a valid byContainer lookup\n // key for `findBox`.\n const byContainer = state.boxes.find((b) => b.container === q);\n if (byContainer) return { kind: 'ok', box: byContainer };\n\n return { kind: 'none' };\n}\n\n/**\n * Next monotonic 1-based index for the given project. Reads only `state.boxes`\n * — caller is responsible for persisting the assignment. Boxes without\n * `projectRoot` are ignored (legacy records); boxes in *other* projects are\n * also ignored. Indices are never recycled, so a destroyed #2 leaves a gap.\n */\nexport function allocateProjectIndex(state: StateFile, projectRoot: string): number {\n let max = 0;\n for (const b of state.boxes) {\n if (b.projectRoot !== projectRoot) continue;\n if (typeof b.projectIndex === 'number' && b.projectIndex > max) {\n max = b.projectIndex;\n }\n }\n return max + 1;\n}\n\n/**\n * Auto-pick when a command's `[box]` argument is omitted. Returns the unique\n * box for `projectRoot`, an `ambiguous` carrying all candidates so the CLI can\n * print a chooser, or `none`.\n */\nexport function autoPickProjectBox(state: StateFile, projectRoot: string): FindBoxResult {\n const matches = state.boxes.filter((b) => b.projectRoot === projectRoot);\n if (matches.length === 0) return { kind: 'none' };\n if (matches.length === 1) return { kind: 'ok', box: matches[0]! };\n return { kind: 'ambiguous', matches };\n}\n\n/**\n * Top-level resolver every CLI command goes through. Combines numeric-index\n * lookup with the legacy `findBox` matcher:\n *\n * - `ref === undefined` and `projectRoot` known → autoPickProjectBox.\n * - `ref` is a pure positive integer and `projectRoot` known → resolve as\n * project index. **Never** falls through to `findBox` on miss, so\n * `agentbox open 3` is reserved for the index and won't accidentally\n * match a hex id like `3abc…`.\n * - Otherwise → `findBox` (id → prefix → name → container).\n */\nexport function resolveBoxRef(\n ref: string | undefined,\n state: StateFile,\n projectRoot: string | undefined,\n): FindBoxResult {\n if (ref === undefined) {\n if (projectRoot === undefined) return { kind: 'none' };\n return autoPickProjectBox(state, projectRoot);\n }\n const trimmed = ref.trim();\n if (projectRoot !== undefined && /^[1-9][0-9]*$/.test(trimmed)) {\n const idx = Number.parseInt(trimmed, 10);\n const hit = state.boxes.find(\n (b) => b.projectRoot === projectRoot && b.projectIndex === idx,\n );\n return hit ? { kind: 'ok', box: hit } : { kind: 'none' };\n }\n return findBox(trimmed, state);\n}\n","import { execa } from 'execa';\nimport { readdir, stat } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nexport interface DetectedGitRepo {\n kind: 'root' | 'nested';\n /** Absolute host path of the repo working tree (== `<workspace>` for root). */\n hostMainRepo: string;\n /** Path relative to the workspace where the repo lives. Empty string for root. */\n relPathFromWorkspace: string;\n}\n\n/**\n * Look for `.git` directories at the workspace root and at every 1st-level\n * subdirectory. Worktree-form `.git` files (regular file containing\n * `gitdir: …`) are intentionally skipped — turning an existing worktree into\n * another worktree gets weird, and the user case for it is rare.\n *\n * Pure host-side detection: it only tells callers where the repos are. Docker\n * boxes create the worktree inside the container against the bind-mounted\n * `.git/`; cloud boxes clone from a bundle. Either way this is the host probe.\n */\nexport async function detectGitRepos(workspace: string): Promise<DetectedGitRepo[]> {\n const out: DetectedGitRepo[] = [];\n if (await isGitDir(join(workspace, '.git'))) {\n out.push({ kind: 'root', hostMainRepo: workspace, relPathFromWorkspace: '' });\n }\n let entries: Array<{ name: string; isDirectory: () => boolean }>;\n try {\n entries = await readdir(workspace, { withFileTypes: true });\n } catch {\n return out;\n }\n for (const e of entries) {\n if (!e.isDirectory() || e.name.startsWith('.')) continue;\n const sub = join(workspace, e.name);\n if (await isGitDir(join(sub, '.git'))) {\n out.push({ kind: 'nested', hostMainRepo: sub, relPathFromWorkspace: e.name });\n }\n }\n return out;\n}\n\nasync function isGitDir(path: string): Promise<boolean> {\n try {\n const s = await stat(path);\n return s.isDirectory();\n } catch {\n return false;\n }\n}\n\n/**\n * Pick `<base>`, `<base>-2`, `<base>-3`, … until git reports no such branch\n * exists. Avoids collision when the user reruns `agentbox create -n same-name`\n * after destroying — the destroyed box's branch still lives in the host repo.\n */\nexport async function pickFreshBranch(hostMainRepo: string, base: string): Promise<string> {\n let candidate = base;\n let suffix = 2;\n while (await branchExists(hostMainRepo, candidate)) {\n candidate = `${base}-${String(suffix++)}`;\n if (suffix > 100) throw new GitWorktreeError(`could not find a free branch name near ${base}`);\n }\n return candidate;\n}\n\nasync function branchExists(hostMainRepo: string, name: string): Promise<boolean> {\n const result = await execa(\n 'git',\n ['-C', hostMainRepo, 'show-ref', '--verify', '--quiet', `refs/heads/${name}`],\n { reject: false },\n );\n return result.exitCode === 0;\n}\n\nexport class GitWorktreeError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'GitWorktreeError';\n }\n}\n","export const DEFAULT_RELAY_PORT = 8787;\nexport const RELAY_CONTAINER_NAME = 'agentbox-relay';\nexport const RELAY_NETWORK_NAME = 'agentbox-net';\nexport const RELAY_IMAGE_REF = 'agentbox/relay:dev';\nexport const RELAY_EVENT_RING_SIZE = 1000;\n\nexport type BoxKind = 'docker' | 'cloud';\n\nexport interface BoxRegistration {\n boxId: string;\n token: string;\n name: string;\n /** ISO-8601 time the relay received this registration. */\n registeredAt: string;\n /**\n * Which sandbox backend the box runs on. Drives whether the host relay\n * spawns a `CloudBoxPoller` for it. Absent on legacy registrations →\n * treated as 'docker'.\n */\n kind?: BoxKind;\n /**\n * For `kind === 'cloud'`: which cloud backend the executor must resolve\n * (e.g. 'daytona'). The host action executor lazy-imports\n * `@agentbox/sandbox-{backend}` to drive the in-sandbox channel.\n */\n backend?: string;\n /** Docker container name; the relay needs it to `docker pause` an idle box. */\n containerName?: string;\n /** ISO-8601 box-creation time (BoxRecord.createdAt); used as a tie-break in auto-pause ordering. */\n createdAt?: string;\n /**\n * 1-based per-project box index (`agentbox list`'s `N` column). When set,\n * the relay writes status.json under `~/.agentbox/boxes/<id>-<n>-<mnemonic>/`\n * to match the host's `boxDirSegment` helper. Absent for legacy\n * (pre-feature) boxes; absent registrations fall back to `<id>-<mnemonic>`.\n */\n projectIndex?: number;\n /**\n * Container-path → host-worktree-dir mapping the host uses to resolve\n * git.pull/git.push RPCs. Empty when the box has no git repos.\n */\n worktrees?: BoxWorktree[];\n /**\n * Preview URL of the in-sandbox relay's `/bridge/*` surface. Set by cloud\n * registrations; the host's `CloudBoxPoller` long-polls it for status +\n * queued host-only RPCs.\n */\n previewUrl?: string;\n /**\n * Provider-proxy token for `previewUrl` (e.g. Daytona's\n * `x-daytona-preview-token`). The host poller attaches it as a header so\n * Daytona's preview proxy lets the request reach the in-sandbox relay.\n */\n previewToken?: string;\n /**\n * Bearer secret authenticating the host poller to `/bridge/*` on the\n * in-sandbox relay. Distinct from `token` (which the in-box agent sees)\n * so a compromised agent can't impersonate the host.\n */\n bridgeToken?: string;\n}\n\nexport interface BoxWorktree {\n /** Path inside the container (e.g. /workspace, /workspace/app). */\n containerPath: string;\n /**\n * Absolute host path of the main repo whose `.git/` is shared with the\n * container. `git push/fetch` RPCs run with `git -C <hostMainRepo>` — the\n * worktree's working tree lives inside the container's writable layer, but\n * refs/objects are in this shared `.git/`, so push from the main repo dir\n * sees the in-container commits.\n */\n hostMainRepo: string;\n /** Branch the in-container worktree was created on (`agentbox/<box-name>`). */\n branch: string;\n}\n\nexport interface RelayEvent {\n /** Monotonic per-relay-process id, useful for `since=` polling. */\n id: number;\n /** Box id that posted the event. */\n boxId: string;\n /** Free-form event type, e.g. 'service-state', 'task-state', 'notify'. */\n type: string;\n /** ISO-8601 timestamp the relay assigned on receipt. */\n receivedAt: string;\n /** ISO-8601 client-supplied timestamp, if any. */\n ts?: string;\n /** Arbitrary JSON payload. */\n payload?: unknown;\n}\n\nexport interface PostEventBody {\n type: string;\n ts?: string;\n payload?: unknown;\n}\n\nexport interface PostRpcBody {\n method: string;\n params?: unknown;\n}\n\nexport interface RegisterBoxBody {\n boxId: string;\n token: string;\n name: string;\n /** Sandbox backend kind. Defaults to 'docker' when absent (legacy). */\n kind?: BoxKind;\n /** For cloud boxes: which backend (e.g. 'daytona'). Drives executor lazy-import. */\n backend?: string;\n containerName?: string;\n createdAt?: string;\n /**\n * 1-based per-project box index. Optional — additive; older boxes and\n * legacy (pre-feature) records register without it and the status path\n * falls back to `<id>-<mnemonic>`.\n */\n projectIndex?: number;\n worktrees?: BoxWorktree[];\n /** Required when `kind === 'cloud'`: in-sandbox relay's /bridge URL. */\n previewUrl?: string;\n /** When the cloud provider's proxy needs a token (Daytona) — attached as a header. */\n previewToken?: string;\n /** Required when `kind === 'cloud'`: bearer for /bridge/* auth. */\n bridgeToken?: string;\n}\n\n/**\n * A host-only RPC the in-sandbox relay (box mode) parked while waiting for\n * the host poller to drain, execute on the host, and post back a result.\n * Equivalent of an in-flight `/rpc` call queued through the bridge.\n */\nexport interface HostAction {\n /** Server-generated uuid; the host poller echoes it back in the result. */\n id: string;\n /** Box that initiated the in-sandbox `/rpc`. */\n boxId: string;\n /** Original `/rpc` method (e.g. 'git.push', 'cp.toHost'). */\n method: string;\n /** Original `/rpc` params payload, opaque to the queue. */\n params: unknown;\n /** ISO-8601 enqueue time. */\n createdAt: string;\n}\n\nexport interface HostActionResult {\n exitCode: number;\n stdout: string;\n stderr: string;\n}\n\n/** Response shape for `GET /bridge/poll`. */\nexport interface BridgePollResponse {\n /** Newly-queued host actions the poller hasn't drained yet. */\n actions: HostAction[];\n /** Events appended since `?since=<id>`. */\n events: RelayEvent[];\n /** Latest box-status snapshot, when one has been pushed. */\n status: unknown | null;\n /** Highest event id seen — the poller's next `?since=`. */\n cursor: number;\n}\n\n/** Body of `POST /bridge/action-result`. */\nexport interface BridgeActionResultBody {\n id: string;\n exitCode: number;\n stdout: string;\n stderr: string;\n}\n\nexport interface GitRpcParams {\n /** Container path identifying which worktree to run against. Defaults to /workspace. */\n path?: string;\n /** Remote name; defaults to 'origin'. */\n remote?: string;\n /** Extra argv tail appended after the standard args (e.g. ['--set-upstream', 'origin', 'branch']). */\n args?: string[];\n}\n\nexport interface GitRpcResult {\n exitCode: number;\n stdout: string;\n stderr: string;\n}\n\nexport interface CheckpointRpcParams {\n /** Checkpoint name; defaults host-side to `<box-name>-<next>`. */\n name?: string;\n /** Flatten lower+upper into one tree instead of a layered delta. */\n merged?: boolean;\n /** Mark the new checkpoint as the project default. */\n setDefault?: boolean;\n /**\n * If a checkpoint with the same name exists, rm it (manifest + image)\n * before capturing. Makes the call safe to retry — useful when the\n * agent's harness lost the previous invocation's stdout and can't tell\n * whether it succeeded.\n */\n replace?: boolean;\n}\n\n/**\n * First-cut prompt UX is a y/N confirmation in the host wrapper's footer.\n * `select` / `text` are reserved for a follow-up that grows the footer to\n * two rows; keeping the kind in the wire from day one means the host\n * wrapper can ignore unknown kinds gracefully when an older box hits a\n * newer relay (and vice-versa).\n */\nexport type PromptKind = 'confirm';\n\nexport interface PromptContext {\n /** Short label, e.g. \"git push\" or \"cp toHost: /workspace/x -> ~/dl/x\". */\n command?: string;\n /** Container path of the calling cwd, when known. */\n cwd?: string;\n /** Full argv; wrapper truncates for the footer. */\n argv?: string[];\n}\n\n/**\n * The shape pushed over SSE on `event: prompt-ask`. Also the shape the\n * relay-internal `askPrompt()` helper produces. Not a /rpc method — the\n * relay generates these itself when it is about to take a host-side\n * action; the in-box ctl never asks for prompts directly.\n */\nexport interface PromptAskEvent {\n /** Relay-generated UUIDv4 (the wrapper echoes it back in the answer). */\n id: string;\n kind: PromptKind;\n /** Primary question; wrapper truncates to footer width. */\n message: string;\n /** Optional second-line context; wrapper may show or skip. */\n detail?: string;\n /** Default when the user just hits Enter; default 'n' so y/N is the safe shape. */\n defaultAnswer?: 'y' | 'n';\n context?: PromptContext;\n}\n\n/** Body of `POST /admin/prompts/answer`. */\nexport interface PromptAnswerBody {\n id: string;\n answer: 'y' | 'n';\n /** Set when the user dismissed the prompt (Esc / Ctrl-c); treated as 'n'. */\n cancelled?: boolean;\n}\n\n/**\n * Notice kinds. `checkpoint` is the first — a box is transiently frozen\n * while a checkpoint is captured (`docker commit` pauses the container).\n * Kept open-ended like {@link PromptKind} so an older wrapper degrades\n * gracefully (renders the message) when a newer relay sends a new kind.\n */\nexport type NoticeKind = 'checkpoint';\n\n/**\n * The shape pushed over SSE on `event: notice-set`. Unlike a prompt, a\n * notice is purely informational — there is no answer and the box's RPC\n * is not blocked on it. The host wrapper renders it as an animated footer\n * line so the user knows the box is busy, not stuck. Cleared by a\n * `notice-clear` event carrying `{ id }`.\n */\nexport interface BoxNoticeEvent {\n /** Relay-generated UUIDv4. */\n id: string;\n kind: NoticeKind;\n /** Warning text; the wrapper truncates to footer width. */\n message: string;\n}\n\n/** Body of `POST /admin/notices/set`; the response is `{ id }`. */\nexport interface SetNoticeBody {\n boxId: string;\n kind: NoticeKind;\n message: string;\n /** Auto-expiry backstop in ms; defaults relay-side. */\n ttlMs?: number;\n}\n\n/** Body of `POST /admin/notices/clear`. */\nexport interface ClearNoticeBody {\n boxId: string;\n id: string;\n}\n\nexport interface CpRpcParams {\n /** Container-side path. */\n boxPath: string;\n /** Host-side path (dst for toHost, src for fromHost). */\n hostPath: string;\n /** Defaults true; relay always uses `docker exec tar` (recursive). */\n recursive?: boolean;\n}\n\nexport interface BrowserOpenRpcParams {\n /** Absolute http(s) URL to open in the host's default browser. */\n url: string;\n}\n\nexport type DownloadKind = 'workspace' | 'env' | 'config' | 'claude';\n\nexport interface DownloadRpcParams {\n kind: DownloadKind;\n /**\n * Host destination override. Reserved — the v1 relay ignores it and uses\n * the host CLI's defaults (`box.workspacePath`, or `~/.claude`). Kept in\n * the wire so a later upgrade can land without bumping the type.\n */\n hostPath?: string;\n /** Reserved for per-kind flags (e.g. workspace: includeNodeModules). */\n options?: Record<string, unknown>;\n}\n","/**\n * `HostActionQueue` — the box-mode relay's parking lot for host-only RPCs.\n * When the in-sandbox `/rpc` route receives `git.push` / `cp.*` / etc., it\n * cannot execute them locally (no host SSH keys, no host filesystem), so it\n * `enqueue`s and `await`s. The host's `CloudBoxPoller` drains via\n * `/bridge/poll`, executes on the host, and `resolve`s here via\n * `/bridge/action-result` — that resolves the awaited Promise and the\n * in-sandbox `/rpc` finally answers the in-box agent.\n *\n * Lifetime: per in-sandbox relay process; in-memory only. Actions outlive\n * individual polls (the queue is the source of truth, not the wire).\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type { HostAction, HostActionResult } from './types.js';\n\ninterface Pending {\n action: HostAction;\n resolve: (r: HostActionResult) => void;\n /** True once a `/bridge/poll` has handed this action out. */\n delivered: boolean;\n}\n\n/**\n * Default max age for a parked action before it expires (15 minutes).\n * Picked to be longer than any reasonable interactive prompt + a host\n * restart window, but short enough that a forgotten queue doesn't replay\n * stale `git.push` attempts when the host relay rehydrates. Override per\n * queue instance for tests.\n */\nexport const DEFAULT_HOST_ACTION_MAX_AGE_MS = 15 * 60 * 1000;\n\nexport interface HostActionQueueOptions {\n /** Override the per-action expiry. Default {@link DEFAULT_HOST_ACTION_MAX_AGE_MS}. */\n maxAgeMs?: number;\n /** Clock injector (tests). */\n now?: () => number;\n}\n\nexport class HostActionQueue {\n private readonly map = new Map<string, Pending>();\n private readonly maxAgeMs: number;\n private readonly now: () => number;\n\n constructor(opts: HostActionQueueOptions = {}) {\n this.maxAgeMs = opts.maxAgeMs ?? DEFAULT_HOST_ACTION_MAX_AGE_MS;\n this.now = opts.now ?? ((): number => Date.now());\n }\n\n /**\n * Park a host-only RPC and return a Promise that resolves when the host\n * poller posts the result back. Caller should `await` and forward the\n * result to the in-box client; there's intentionally no client-side\n * timeout (matches the existing in-box `postRpc`'s \"block while a prompt\n * is open\" semantics).\n */\n enqueue(boxId: string, method: string, params: unknown): Promise<HostActionResult> {\n const id = randomUUID();\n const action: HostAction = {\n id,\n boxId,\n method,\n params,\n createdAt: new Date(this.now()).toISOString(),\n };\n return new Promise<HostActionResult>((resolve) => {\n this.map.set(id, { action, resolve, delivered: false });\n });\n }\n\n /**\n * Return every action the host hasn't been handed yet, marking them\n * delivered. The poller is expected to execute them and POST back to\n * `/bridge/action-result`. Re-delivery on poller retry isn't needed —\n * the queue holds the Promise until `resolve` is called.\n *\n * Actions older than `maxAgeMs` expire here: their `resolve` is called\n * with an `exitCode: 124, stderr: 'expired'` result so the in-box RPC\n * unblocks, and they don't appear in the drained list. Keeps a host\n * relay restart from replaying a long-forgotten `git.push`.\n */\n drain(): HostAction[] {\n const now = this.now();\n const out: HostAction[] = [];\n for (const [id, p] of this.map) {\n const createdAt = Date.parse(p.action.createdAt);\n if (Number.isFinite(createdAt) && now - createdAt > this.maxAgeMs) {\n this.map.delete(id);\n p.resolve({\n exitCode: 124,\n stdout: '',\n stderr: `host action '${p.action.method}' expired before the host could execute it\\n`,\n });\n continue;\n }\n if (!p.delivered) {\n out.push(p.action);\n p.delivered = true;\n }\n }\n return out;\n }\n\n /**\n * Settle a parked action with the host's result. Idempotent: a duplicate\n * resolve on the same id is a no-op. Returns whether the id matched.\n */\n resolve(id: string, result: HostActionResult): boolean {\n const p = this.map.get(id);\n if (!p) return false;\n this.map.delete(id);\n p.resolve(result);\n return true;\n }\n\n size(): number {\n return this.map.size;\n }\n\n /** Test/diagnostic: get the action with this id (returns undefined when settled). */\n peek(id: string): HostAction | undefined {\n return this.map.get(id)?.action;\n }\n}\n","import type { BoxRegistration, RelayEvent } from './types.js';\nimport { RELAY_EVENT_RING_SIZE } from './types.js';\n\nexport class BoxRegistry {\n private readonly map = new Map<string, BoxRegistration>();\n\n register(reg: BoxRegistration): void {\n this.map.set(reg.boxId, reg);\n }\n\n forget(boxId: string): boolean {\n return this.map.delete(boxId);\n }\n\n /** Returns the registration whose token matches, or null. */\n authenticate(token: string): BoxRegistration | null {\n if (token.length === 0) return null;\n for (const reg of this.map.values()) {\n if (reg.token === token) return reg;\n }\n return null;\n }\n\n get(boxId: string): BoxRegistration | undefined {\n return this.map.get(boxId);\n }\n\n list(): BoxRegistration[] {\n return [...this.map.values()];\n }\n\n size(): number {\n return this.map.size;\n }\n}\n\nexport class EventBuffer {\n private readonly buf: RelayEvent[] = [];\n private nextId = 1;\n constructor(private readonly capacity: number = RELAY_EVENT_RING_SIZE) {}\n\n append(input: Omit<RelayEvent, 'id' | 'receivedAt'>): RelayEvent {\n const ev: RelayEvent = {\n id: this.nextId++,\n receivedAt: new Date().toISOString(),\n ...input,\n };\n this.buf.push(ev);\n if (this.buf.length > this.capacity) this.buf.shift();\n return ev;\n }\n\n /** Returns events with id > since. If `box` is given, filters to that box. */\n since(since: number, box?: string): RelayEvent[] {\n const out: RelayEvent[] = [];\n for (const ev of this.buf) {\n if (ev.id <= since) continue;\n if (box && ev.boxId !== box) continue;\n out.push(ev);\n }\n return out;\n }\n\n all(): RelayEvent[] {\n return this.buf.slice();\n }\n\n size(): number {\n return this.buf.length;\n }\n}\n","import { randomUUID } from 'node:crypto';\nimport type { ServerResponse } from 'node:http';\nimport type { PromptAnswerBody, PromptAskEvent } from './types.js';\n\n/**\n * Resolution shape passed back through `askPrompt`'s Promise. Mirrors the\n * shape the wrapper POSTs to `/admin/prompts/answer` minus the id (the\n * caller already knows it).\n */\nexport interface PromptResolution {\n answer: 'y' | 'n';\n cancelled?: boolean;\n}\n\ninterface PendingPromptEntry {\n ev: PromptAskEvent;\n boxId: string;\n resolve: (r: PromptResolution) => void;\n createdAt: string;\n}\n\n/**\n * In-memory pending prompts map. The relay's host-action handlers (git push,\n * cp.*, download.*) put a pending entry here and await the Promise; the\n * wrapper's POST to `/admin/prompts/answer` resolves it. Entries live for\n * however long the user takes — per the design we block indefinitely until\n * a wrapper attaches and answers.\n */\nexport class PendingPrompts {\n private readonly entries = new Map<string, PendingPromptEntry>();\n\n add(boxId: string, ev: PromptAskEvent): Promise<PromptResolution> {\n return new Promise<PromptResolution>((resolve) => {\n this.entries.set(ev.id, {\n ev,\n boxId,\n resolve,\n createdAt: new Date().toISOString(),\n });\n });\n }\n\n /**\n * Idempotent: returns true if a pending entry was found + resolved, false\n * otherwise. The /admin/prompts/answer handler uses the bool to decide\n * 204 vs 404 — the wrapper treats both as \"we're done.\"\n */\n resolve(id: string, answer: 'y' | 'n', cancelled?: boolean): boolean {\n const entry = this.entries.get(id);\n if (!entry) return false;\n this.entries.delete(id);\n entry.resolve({ answer, cancelled });\n return true;\n }\n\n /**\n * Snapshot of all pending prompts for a given box; used to flush the\n * backlog to a newly-attached SSE subscriber.\n */\n forBox(boxId: string): PromptAskEvent[] {\n const out: PromptAskEvent[] = [];\n for (const entry of this.entries.values()) {\n if (entry.boxId === boxId) out.push(entry.ev);\n }\n return out;\n }\n\n /** boxId that owns a pending prompt id, or null when unknown. */\n boxFor(id: string): string | null {\n const entry = this.entries.get(id);\n return entry ? entry.boxId : null;\n }\n\n size(): number {\n return this.entries.size;\n }\n}\n\n/**\n * Tracks the set of host-side wrappers (SSE clients) currently subscribed\n * per box. `broadcast` writes to every subscriber so the user can answer\n * from whichever attached window they happen to be in.\n */\nexport class PromptSubscribers {\n private readonly byBox = new Map<string, Set<ServerResponse>>();\n\n add(boxId: string, res: ServerResponse): void {\n let set = this.byBox.get(boxId);\n if (!set) {\n set = new Set();\n this.byBox.set(boxId, set);\n }\n set.add(res);\n }\n\n remove(boxId: string, res: ServerResponse): void {\n const set = this.byBox.get(boxId);\n if (!set) return;\n set.delete(res);\n if (set.size === 0) this.byBox.delete(boxId);\n }\n\n forBox(boxId: string): ServerResponse[] {\n const set = this.byBox.get(boxId);\n return set ? Array.from(set) : [];\n }\n\n /**\n * Fire-and-forget broadcast. SSE writes that fail (closed socket) are\n * swallowed — the `res.on('close')` handler in the server route already\n * deregisters the dead subscriber.\n */\n broadcast(boxId: string, event: string, data: unknown): void {\n const set = this.byBox.get(boxId);\n if (!set) return;\n const payload = `event: ${event}\\ndata: ${JSON.stringify(data)}\\n\\n`;\n for (const res of set) {\n try {\n res.write(payload);\n } catch {\n /* dead socket; close handler will deregister */\n }\n }\n }\n}\n\n/**\n * Internal API used by handleGitRpc / handleCpRpc / handleDownloadRpc and the\n * `browser.open` host-mirror offer. Generates a UUID, adds a pending entry,\n * broadcasts the SSE event, and awaits the answer. Respects\n * `process.env.AGENTBOX_PROMPT === 'off'` — auto-accepts without broadcasting\n * (useful for headless scripts and tests).\n *\n * `opts.ttlMs` makes the prompt auto-expire: if no answer arrives in time it\n * resolves to its `defaultAnswer` (cancelled) and a `prompt-resolved` event is\n * broadcast so attached wrappers clear it. Used for optional, non-blocking\n * prompts that must not linger when nobody answers; omit it for the\n * block-until-answered prompts that gate a paused in-box RPC.\n */\nexport async function askPrompt(\n prompts: PendingPrompts,\n subscribers: PromptSubscribers,\n boxId: string,\n params: Omit<PromptAskEvent, 'id'>,\n opts?: { ttlMs?: number },\n): Promise<PromptResolution> {\n if (process.env.AGENTBOX_PROMPT === 'off') {\n return { answer: 'y' };\n }\n const ev: PromptAskEvent = { id: randomUUID(), ...params };\n const promise = prompts.add(boxId, ev);\n subscribers.broadcast(boxId, 'prompt-ask', ev);\n if (opts?.ttlMs !== undefined && opts.ttlMs > 0) {\n const timer = setTimeout(() => {\n if (prompts.resolve(ev.id, params.defaultAnswer ?? 'n', true)) {\n subscribers.broadcast(boxId, 'prompt-resolved', { id: ev.id });\n }\n }, opts.ttlMs);\n if (typeof timer.unref === 'function') timer.unref();\n void promise.then(() => {\n clearTimeout(timer);\n });\n }\n return promise;\n}\n\n/** Helper for the answer body — used by the relay server to validate. */\nexport function isPromptAnswerBody(v: unknown): v is PromptAnswerBody {\n if (!v || typeof v !== 'object') return false;\n const o = v as Record<string, unknown>;\n if (typeof o.id !== 'string' || o.id.length === 0) return false;\n if (o.answer !== 'y' && o.answer !== 'n') return false;\n if (o.cancelled !== undefined && typeof o.cancelled !== 'boolean') return false;\n return true;\n}\n","import { randomUUID } from 'node:crypto';\nimport type { PromptSubscribers } from './prompts.js';\nimport type { BoxNoticeEvent, NoticeKind } from './types.js';\n\n/**\n * Default lifespan of a notice when its owner never clears it explicitly.\n * Longer than the relay's checkpoint RPC timeout (600s) so a notice still\n * self-expires even if the host CLI is SIGKILLed before its `finally` runs.\n */\nconst DEFAULT_NOTICE_TTL_MS = 660_000;\n\ninterface NoticeEntry {\n ev: BoxNoticeEvent;\n boxId: string;\n timer: ReturnType<typeof setTimeout>;\n}\n\n/**\n * In-memory per-box informational notices, broadcast over the same SSE\n * channel as confirmation prompts. Unlike {@link import('./prompts.js').PendingPrompts}\n * these are fire-and-forget: there is no awaited promise and no answer. A\n * notice marks a box as transiently busy (a checkpoint freezes it via\n * `docker commit`); the host wrapper renders a spinner so the user can\n * tell the box from stuck.\n *\n * Deliberately NOT gated by `AGENTBOX_PROMPT=off` (which `askPrompt`\n * honours): a notice is informational, not a consent gate, so suppressing\n * it would only hide useful feedback.\n */\nexport class BoxNotices {\n /** keyed by notice id. */\n private readonly entries = new Map<string, NoticeEntry>();\n\n constructor(private readonly subscribers: PromptSubscribers) {}\n\n /**\n * Register a notice for `boxId` and broadcast `notice-set`. At most one\n * notice per (box, kind) is kept — a fresh `set` for the same kind\n * replaces the previous one (and cancels its TTL timer so a stale timer\n * can't later fire a `notice-clear` racing the replacement). Returns the\n * generated notice id.\n */\n set(boxId: string, kind: NoticeKind, message: string, ttlMs?: number): string {\n for (const [id, entry] of this.entries) {\n if (entry.boxId === boxId && entry.ev.kind === kind) {\n clearTimeout(entry.timer);\n this.entries.delete(id);\n }\n }\n const ev: BoxNoticeEvent = { id: randomUUID(), kind, message };\n const ttl = typeof ttlMs === 'number' && ttlMs > 0 ? ttlMs : DEFAULT_NOTICE_TTL_MS;\n const timer = setTimeout(() => {\n // Safety net: a notice whose owner died without clearing it self-expires.\n if (this.entries.delete(ev.id)) {\n this.subscribers.broadcast(boxId, 'notice-clear', { id: ev.id });\n }\n }, ttl);\n if (typeof timer.unref === 'function') timer.unref();\n this.entries.set(ev.id, { ev, boxId, timer });\n this.subscribers.broadcast(boxId, 'notice-set', ev);\n return ev.id;\n }\n\n /**\n * Clear a notice by id. Idempotent: returns false when no such notice\n * exists (already cleared / expired). Broadcasts `notice-clear` on a hit.\n */\n clear(id: string): boolean {\n const entry = this.entries.get(id);\n if (!entry) return false;\n clearTimeout(entry.timer);\n this.entries.delete(id);\n this.subscribers.broadcast(entry.boxId, 'notice-clear', { id });\n return true;\n }\n\n /** Snapshot of active notices for a box; replayed to a new SSE subscriber. */\n forBox(boxId: string): BoxNoticeEvent[] {\n const out: BoxNoticeEvent[] = [];\n for (const entry of this.entries.values()) {\n if (entry.boxId === boxId) out.push(entry.ev);\n }\n return out;\n }\n\n size(): number {\n return this.entries.size;\n }\n}\n","import { mkdir, rename, rm, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\n/**\n * A box status snapshot as received from the in-box daemon. The relay treats\n * it structurally (it has no dep on `@agentbox/ctl`); the rich type lives in\n * `@agentbox/ctl` (`BoxStatus`) and is what the host CLI parses back.\n */\nexport type BoxStatusSnapshot = Record<string, unknown>;\n\n/**\n * Mirrors `sanitizeMnemonic` in @agentbox/config — duplicated here so the relay\n * stays dep-free. Two source-of-truth files; the schema-drift-style guarantee\n * is that boxes only land in `<id>-<mnemonic>/` if both impls agree.\n */\nfunction sanitizeMnemonic(raw: string): string {\n return (\n raw\n .toLowerCase()\n .replace(/-/g, '_')\n .replace(/[^a-z0-9_]+/g, '_')\n .replace(/_+/g, '_')\n .replace(/^_+|_+$/g, '')\n .slice(0, 32) || 'unnamed'\n );\n}\n\n/**\n * Mirrors `boxRunDirFor` / `boxDirSegment` in @agentbox/sandbox-docker — kept\n * in sync by hand. When `projectIndex` (`agentbox list`'s `N`) is set the\n * segment is `<id>-<n>-<mnemonic>` so directories sort cleanly within a\n * project; legacy (pre-feature) boxes register without it and fall back to\n * the original `<id>-<mnemonic>` shape.\n */\nfunction boxRunDirFor(boxId: string, name: string, projectIndex?: number): string {\n const mnemonic = sanitizeMnemonic(name);\n const segment =\n typeof projectIndex === 'number' && Number.isFinite(projectIndex) && projectIndex > 0\n ? `${boxId}-${String(projectIndex)}-${mnemonic}`\n : `${boxId}-${mnemonic}`;\n return join(homedir(), '.agentbox', 'boxes', segment);\n}\n\nfunction boxStatusPathFor(boxId: string, name: string, projectIndex?: number): string {\n return join(boxRunDirFor(boxId, name, projectIndex), 'status.json');\n}\n\n/**\n * Structural guard: a valid box-status payload is an object with `schema === 1`\n * and a non-empty `boxId` string. The relay persists it verbatim; the host\n * reader does the strict typing.\n */\nexport function isValidBoxStatus(payload: unknown): payload is BoxStatusSnapshot {\n if (typeof payload !== 'object' || payload === null) return false;\n const o = payload as Record<string, unknown>;\n return o.schema === 1 && typeof o.boxId === 'string' && o.boxId.length > 0;\n}\n\n/**\n * In-memory latest-status map plus a durable per-box file at\n * `~/.agentbox/boxes/<id>/status.json`. The relay is a single process so it is\n * the single writer; the atomic tmp+rename means the host CLI never reads a\n * torn file. The on-disk copy is what makes status survive box pause/stop,\n * relay restart, and host reboot.\n */\nexport class BoxStatusStore {\n private readonly map = new Map<string, BoxStatusSnapshot>();\n\n get(boxId: string): BoxStatusSnapshot | undefined {\n return this.map.get(boxId);\n }\n\n /**\n * Update the in-memory entry and best-effort persist it to disk. `name` is\n * the box's user-facing name (from the registry); `projectIndex` is the\n * 1-based per-project `N`. Together they form the on-disk dir\n * `~/.agentbox/boxes/<id>-<n>-<mnemonic>/status.json` (or\n * `<id>-<mnemonic>/` if N is absent — legacy boxes).\n */\n async set(\n boxId: string,\n name: string,\n projectIndex: number | undefined,\n status: BoxStatusSnapshot,\n ): Promise<void> {\n this.map.set(boxId, status);\n const target = boxStatusPathFor(boxId, name, projectIndex);\n const tmp = `${target}.${String(process.pid)}.tmp`;\n try {\n await mkdir(boxRunDirFor(boxId, name, projectIndex), { recursive: true });\n await writeFile(tmp, JSON.stringify(status), 'utf8');\n await rename(tmp, target);\n } catch {\n await rm(tmp, { force: true }).catch(() => {});\n }\n }\n\n /** Drop the in-memory entry (the on-disk file is wiped with the box dir). */\n delete(boxId: string): void {\n this.map.delete(boxId);\n }\n}\n","import { spawn } from 'node:child_process';\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';\nimport { executeCloudAction } from './host-actions.js';\nimport { HostActionQueue } from './host-action-queue.js';\nimport { BoxNotices } from './notices.js';\nimport { askPrompt, isPromptAnswerBody, PendingPrompts, PromptSubscribers } from './prompts.js';\nimport { BoxRegistry, EventBuffer } from './registry.js';\nimport { BoxStatusStore, isValidBoxStatus } from './status-store.js';\nimport type {\n BoxRegistration,\n BoxWorktree,\n BridgeActionResultBody,\n BridgePollResponse,\n BrowserOpenRpcParams,\n CheckpointRpcParams,\n ClearNoticeBody,\n CpRpcParams,\n DownloadKind,\n DownloadRpcParams,\n GitRpcParams,\n GitRpcResult,\n PostEventBody,\n PostRpcBody,\n PromptAnswerBody,\n RegisterBoxBody,\n RelayEvent,\n SetNoticeBody,\n} from './types.js';\n\nexport type RelayMode = 'host' | 'box';\n\nexport interface RelayServerOptions {\n port: number;\n /** Bind address; defaults to '0.0.0.0' so containers can reach the relay across the local docker network OR the Daytona preview proxy can hit the in-sandbox box-mode relay. */\n host?: string;\n logger?: (line: string) => void;\n /**\n * 'host' (default): host relay process; executes host-only RPCs locally\n * via `spawn` and serves `/admin/*` to the CLI / wrapper.\n * 'box': in-sandbox relay; host-only RPCs enqueue on a `HostActionQueue`\n * for the host poller to drain via `/bridge/*`.\n */\n mode?: RelayMode;\n /**\n * Required when `mode === 'box'`: bearer for the box-only `/bridge/*`\n * routes. Distinct from per-box `BoxRegistration.token` so a compromised\n * in-box agent cannot impersonate the host poller.\n */\n bridgeToken?: string;\n}\n\nexport interface RelayServerHandle {\n server: Server;\n registry: BoxRegistry;\n events: EventBuffer;\n statusStore: BoxStatusStore;\n prompts: PendingPrompts;\n subscribers: PromptSubscribers;\n notices: BoxNotices;\n /** Present only in `mode === 'box'`: the parking lot for host-only RPCs. */\n hostActions?: HostActionQueue;\n url: string;\n close: () => Promise<void>;\n}\n\n/** Event type whose payload is a durable BoxStatus snapshot (persisted, not ringed). */\nconst BOX_STATUS_EVENT = 'box-status';\n\nconst MAX_BODY_BYTES = 1024 * 1024; // 1 MiB hard cap; relay is for control-plane traffic, not payloads.\nconst GIT_RPC_TIMEOUT_MS = 120_000; // git push/pull can be slow on big repos.\nconst CHECKPOINT_RPC_TIMEOUT_MS = 600_000; // capturing node_modules/build trees can be slow.\nconst DOWNLOAD_RPC_TIMEOUT_MS = 600_000; // claude/workspace pulls over rsync can take minutes.\nconst CP_RPC_TIMEOUT_MS = 300_000; // single-file/dir cp; tar pipe through docker exec.\nconst BROWSER_OPEN_RPC_TIMEOUT_MS = 15_000; // `open` hands off to the browser and returns at once.\nconst BROWSER_OPEN_PROMPT_TTL_MS = 25_000; // the \"open on host too?\" offer auto-dismisses if ignored.\nconst SSE_HEARTBEAT_MS = 15_000; // every 15s; wrapper reconnects if it sees no traffic for ~30s.\n\nfunction send(\n res: ServerResponse,\n status: number,\n body: unknown,\n contentType: string = 'application/json',\n): void {\n const text = body == null ? '' : typeof body === 'string' ? body : JSON.stringify(body);\n res.statusCode = status;\n if (text.length > 0) {\n res.setHeader('Content-Type', contentType);\n res.setHeader('Content-Length', Buffer.byteLength(text).toString());\n res.end(text);\n } else {\n res.end();\n }\n}\n\nasync function readJsonBody<T>(req: IncomingMessage): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n let total = 0;\n const chunks: Buffer[] = [];\n req.on('data', (chunk: Buffer) => {\n total += chunk.length;\n if (total > MAX_BODY_BYTES) {\n reject(new Error('request body too large'));\n req.destroy();\n return;\n }\n chunks.push(chunk);\n });\n req.on('end', () => {\n const text = Buffer.concat(chunks).toString('utf8');\n if (text.length === 0) {\n resolve({} as T);\n return;\n }\n try {\n resolve(JSON.parse(text) as T);\n } catch (err) {\n reject(err instanceof Error ? err : new Error(String(err)));\n }\n });\n req.on('error', reject);\n });\n}\n\nfunction bearerToken(req: IncomingMessage): string {\n const raw = req.headers.authorization;\n if (typeof raw !== 'string') return '';\n const m = /^Bearer\\s+(.+)$/i.exec(raw.trim());\n return m ? m[1]!.trim() : '';\n}\n\nfunction isLoopbackAddress(addr: string | undefined): boolean {\n if (!addr) return false;\n return (\n addr === '127.0.0.1' ||\n addr === '::1' ||\n addr === '::ffff:127.0.0.1' ||\n addr.startsWith('127.')\n );\n}\n\n/**\n * Build the relay HTTP server. Routes:\n * POST /events — bearer auth (box token), appends to ring buffer.\n * POST /rpc — bearer auth; dispatches git.push/fetch, cp.*, download.*, checkpoint.create on the host.\n * POST /admin/register-box — loopback only.\n * POST /admin/forget-box — loopback only.\n * GET /admin/box-status — loopback only; query `box`; latest snapshot.\n * GET /admin/events — loopback only; query `box`, `since`.\n * GET /admin/registry — loopback only; list registered boxes (token redacted).\n * GET /admin/prompts/stream — loopback only; SSE; pushes prompt-ask/prompt-resolved/notice-set/notice-clear/ping events.\n * POST /admin/prompts/answer — loopback only; resolves a pending prompt by id.\n * POST /admin/notices/set — loopback only; sets an informational box notice (returns {id}).\n * POST /admin/notices/clear — loopback only; clears a box notice by id.\n * GET /healthz — liveness probe (no auth).\n */\nexport function createRelayServer(opts: RelayServerOptions): RelayServerHandle {\n const log = opts.logger ?? (() => {});\n const registry = new BoxRegistry();\n const events = new EventBuffer();\n const statusStore = new BoxStatusStore();\n const prompts = new PendingPrompts();\n const subscribers = new PromptSubscribers();\n const notices = new BoxNotices(subscribers);\n const host = opts.host ?? '0.0.0.0';\n const mode: RelayMode = opts.mode ?? 'host';\n // Box mode parks host-only RPCs until the host poller answers; host mode\n // executes them inline (the historical behavior).\n const hostActions = mode === 'box' ? new HostActionQueue() : null;\n if (mode === 'box' && (!opts.bridgeToken || opts.bridgeToken.length === 0)) {\n throw new Error(\"relay mode='box' requires a non-empty bridgeToken\");\n }\n const bridgeToken = opts.bridgeToken ?? '';\n\n // Host-mode pollers for cloud-tagged boxes; started on /admin/register-box,\n // stopped on /admin/forget-box. Lazy import to keep host-mode startup free\n // of cloud-poller deps until actually needed.\n type CloudPollersModule = typeof import('./cloud-poller.js');\n let pollers: InstanceType<CloudPollersModule['CloudBoxPollers']> | null = null;\n async function getPollers(): Promise<InstanceType<CloudPollersModule['CloudBoxPollers']>> {\n if (!pollers) {\n const mod: CloudPollersModule = await import('./cloud-poller.js');\n pollers = new mod.CloudBoxPollers();\n }\n return pollers;\n }\n\n const server = createServer((req, res) => {\n handle(req, res).catch((err: unknown) => {\n const msg = err instanceof Error ? err.message : String(err);\n log(`relay: handler error: ${msg}`);\n if (!res.headersSent) send(res, 500, { error: 'internal error' });\n else res.end();\n });\n });\n\n async function handle(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'relay'}`);\n const route = `${req.method ?? 'GET'} ${url.pathname}`;\n\n if (route === 'GET /healthz') {\n send(res, 200, { ok: true, boxes: registry.size(), events: events.size() });\n return;\n }\n\n // Bridge routes are the host poller's view into an in-sandbox box-mode\n // relay. They are bearer-authed with the per-relay bridgeToken and\n // exist only when mode === 'box'. No loopback check: the Daytona\n // preview proxy reaches them from non-loopback IPs.\n if (url.pathname.startsWith('/bridge/')) {\n if (mode !== 'box' || !hostActions) {\n send(res, 404, { error: 'bridge routes available only in mode=box' });\n return;\n }\n if (bearerToken(req) !== bridgeToken) {\n send(res, 401, { error: 'invalid bridge token' });\n return;\n }\n if (route === 'GET /bridge/healthz') {\n send(res, 200, { ok: true, queued: hostActions.size(), events: events.size() });\n return;\n }\n if (route === 'GET /bridge/poll') {\n const since = Number.parseInt(url.searchParams.get('since') ?? '0', 10) || 0;\n const newEvents = events.since(since);\n const lastId = newEvents.length > 0 ? newEvents[newEvents.length - 1]!.id : since;\n const actions = hostActions.drain();\n // A box-mode relay only ever has one registered box (itself). The\n // status snapshot — if any has been pushed — belongs to that box.\n const only = registry.list()[0];\n const status = only ? statusStore.get(only.boxId) ?? null : null;\n const reply: BridgePollResponse = {\n actions,\n events: newEvents,\n status,\n cursor: lastId,\n };\n send(res, 200, reply);\n return;\n }\n if (route === 'POST /bridge/action-result') {\n const body = await readJsonBody<BridgeActionResultBody>(req);\n if (\n !body ||\n typeof body.id !== 'string' ||\n body.id.length === 0 ||\n typeof body.exitCode !== 'number'\n ) {\n send(res, 400, { error: 'expected {id, exitCode, stdout, stderr}' });\n return;\n }\n const ok = hostActions.resolve(body.id, {\n exitCode: body.exitCode,\n stdout: typeof body.stdout === 'string' ? body.stdout : '',\n stderr: typeof body.stderr === 'string' ? body.stderr : '',\n });\n if (!ok) {\n send(res, 404, { error: 'no parked action with that id' });\n return;\n }\n send(res, 204, null);\n return;\n }\n send(res, 404, { error: 'not found', route });\n return;\n }\n\n // Admin endpoints are reachable from loopback only. The relay binds to\n // 0.0.0.0 so containers can reach /events and /rpc via host.docker.internal,\n // but admin operations (register-box, forget-box, list events, etc.) are\n // for the host CLI and must not be exposed to boxes.\n if (url.pathname.startsWith('/admin/')) {\n if (!isLoopbackAddress(req.socket.remoteAddress)) {\n send(res, 403, { error: 'admin endpoints are loopback-only' });\n return;\n }\n }\n\n if (route === 'POST /events') {\n const reg = authBox(req, res, registry);\n if (!reg) return;\n const body = await readJsonBody<PostEventBody>(req);\n if (!body || typeof body.type !== 'string' || body.type.length === 0) {\n send(res, 400, { error: 'missing \"type\" string' });\n return;\n }\n // box-status is durable state, not an event: persist the latest snapshot\n // per box and skip the ring buffer (a 15s heartbeat per box would\n // otherwise evict the useful git/service events from the 1000-cap ring).\n if (body.type === BOX_STATUS_EVENT) {\n if (!isValidBoxStatus(body.payload)) {\n send(res, 400, { error: 'invalid box-status payload' });\n return;\n }\n await statusStore.set(reg.boxId, reg.name, reg.projectIndex, body.payload);\n log(`box-status box=${reg.boxId}`);\n send(res, 202, { ok: true });\n return;\n }\n const ev = events.append({\n boxId: reg.boxId,\n type: body.type,\n ts: typeof body.ts === 'string' ? body.ts : undefined,\n payload: body.payload,\n });\n log(`event ${String(ev.id)} box=${reg.boxId} type=${body.type}`);\n send(res, 202, { id: ev.id });\n return;\n }\n\n if (route === 'POST /rpc') {\n const reg = authBox(req, res, registry);\n if (!reg) return;\n const body = await readJsonBody<PostRpcBody>(req);\n if (!body || typeof body.method !== 'string' || body.method.length === 0) {\n send(res, 400, { error: 'missing \"method\" string' });\n return;\n }\n log(`rpc box=${reg.boxId} method=${body.method}`);\n // Box-mode: every host-only RPC (anything except the in-sandbox-local\n // `browser.open` notification) is parked on the HostActionQueue. The\n // host's CloudBoxPoller drains via `/bridge/poll`, executes on the\n // host (with the existing `askPrompt` gate for `git.push`), and POSTs\n // the result back to `/bridge/action-result`, which resolves the\n // awaited Promise here and unblocks the in-box `/rpc` caller.\n if (mode === 'box' && hostActions && body.method !== 'browser.open') {\n const queued = await hostActions.enqueue(reg.boxId, body.method, body.params);\n const status = queued.exitCode === 0 ? 200 : 500;\n send(res, status, queued);\n return;\n }\n if (body.method === 'git.push' || body.method === 'git.fetch') {\n // Only `push` mutates the user's remote; fetch is read-only and noisy.\n if (body.method === 'git.push') {\n const params = body.params as GitRpcParams | undefined;\n const verdict = await askPrompt(prompts, subscribers, reg.boxId, {\n kind: 'confirm',\n message: `Allow git push from box ${reg.name}?`,\n detail: `${params?.remote ?? 'origin'} ${(params?.args ?? []).join(' ')}`.trim(),\n defaultAnswer: 'n',\n context: {\n command: 'git push',\n cwd: params?.path,\n argv: params?.args,\n },\n });\n if (verdict.answer !== 'y') {\n send(res, 500, { exitCode: 10, stdout: '', stderr: 'denied by user\\n' });\n return;\n }\n }\n const result = await handleGitRpc(reg, body.method, body.params as GitRpcParams | undefined);\n const status = result.exitCode === 0 ? 200 : 500;\n send(res, status, result);\n return;\n }\n if (body.method === 'cp.toHost' || body.method === 'cp.fromHost') {\n const params = body.params as CpRpcParams | undefined;\n if (!params || typeof params.boxPath !== 'string' || typeof params.hostPath !== 'string') {\n send(res, 400, { error: 'cp.* requires {boxPath, hostPath} strings' });\n return;\n }\n const direction = body.method === 'cp.toHost' ? 'box -> host' : 'host -> box';\n const verdict = await askPrompt(prompts, subscribers, reg.boxId, {\n kind: 'confirm',\n message: `Allow cp (${direction}) on ${reg.name}?`,\n detail:\n body.method === 'cp.toHost'\n ? `${params.boxPath} -> ${params.hostPath}`\n : `${params.hostPath} -> ${params.boxPath}`,\n defaultAnswer: 'n',\n context: {\n command: body.method,\n argv: [params.boxPath, params.hostPath],\n },\n });\n if (verdict.answer !== 'y') {\n send(res, 500, { exitCode: 10, stdout: '', stderr: 'denied by user\\n' });\n return;\n }\n const result = await handleCpRpc(reg, body.method, params);\n const status = result.exitCode === 0 ? 200 : 500;\n send(res, status, result);\n return;\n }\n if (\n body.method === 'download.workspace' ||\n body.method === 'download.env' ||\n body.method === 'download.config' ||\n body.method === 'download.claude'\n ) {\n const params = body.params as DownloadRpcParams | undefined;\n const kind = (body.method.split('.')[1] ?? 'workspace') as DownloadKind;\n const verdict = await askPrompt(prompts, subscribers, reg.boxId, {\n kind: 'confirm',\n message: `Allow download (${kind}) from ${reg.name}?`,\n detail: params?.hostPath ?? '(default host location)',\n defaultAnswer: 'n',\n context: {\n command: body.method,\n argv: params?.hostPath ? [params.hostPath] : [],\n },\n });\n if (verdict.answer !== 'y') {\n send(res, 500, { exitCode: 10, stdout: '', stderr: 'denied by user\\n' });\n return;\n }\n const result = await handleDownloadRpc(reg, kind);\n const status = result.exitCode === 0 ? 200 : 500;\n send(res, status, result);\n return;\n }\n if (body.method === 'checkpoint.create') {\n const result = await handleCheckpointRpc(\n reg,\n body.params as CheckpointRpcParams | undefined,\n );\n const status = result.exitCode === 0 ? 200 : 500;\n send(res, status, result);\n return;\n }\n if (body.method === 'browser.open') {\n const params = body.params as BrowserOpenRpcParams | undefined;\n const url = typeof params?.url === 'string' ? params.url.trim() : '';\n if (!isOpenableUrl(url)) {\n // The scheme guard keeps a box from handing the host's `open` a\n // file path or app instead of a URL.\n send(res, 400, {\n exitCode: 64,\n stdout: '',\n stderr: 'browser.open: only http/https URLs are allowed\\n',\n });\n return;\n }\n // The box already opened the link in its own browser; this RPC is\n // just a notification. Record the event and answer at once — never\n // block the box on the host user's decision.\n events.append({ boxId: reg.boxId, type: 'browser-open', payload: { url } });\n send(res, 200, { exitCode: 0, stdout: '', stderr: '' });\n // Offer to mirror the link to the host browser: a non-blocking,\n // auto-expiring confirm prompt in the footer/dashboard. Skipped under\n // AGENTBOX_PROMPT=off so a headless box can't spray the host with\n // browser tabs via askPrompt's auto-'y'.\n if (process.env.AGENTBOX_PROMPT !== 'off') {\n if (mode === 'box' && hostActions) {\n // Cloud: the in-sandbox relay has no SSE subscribers (the host\n // wrapper attaches to the host relay, not the in-sandbox one).\n // Queue a `browser.open.mirror` host action — the host poller\n // drains it, executes the prompt + open against host\n // subscribers, and resolves the parked entry. We don't await;\n // the host's verdict isn't reported back to the in-box agent\n // and `HostActionQueue.maxAgeMs` GCs the entry if it lingers.\n void hostActions.enqueue(reg.boxId, 'browser.open.mirror', { url });\n } else {\n void askPrompt(\n prompts,\n subscribers,\n reg.boxId,\n {\n kind: 'confirm',\n message: `Open link from box ${reg.name} on the host?`,\n detail: url,\n defaultAnswer: 'n',\n context: { command: 'browser.open', argv: [url] },\n },\n { ttlMs: BROWSER_OPEN_PROMPT_TTL_MS },\n )\n .then((verdict) => {\n if (verdict.answer === 'y' && !verdict.cancelled) {\n void runHostCommand(['open', url], BROWSER_OPEN_RPC_TIMEOUT_MS);\n }\n })\n .catch(() => {\n /* best-effort */\n });\n }\n }\n return;\n }\n events.append({\n boxId: reg.boxId,\n type: 'rpc-unknown',\n payload: { method: body.method },\n });\n send(res, 501, { error: 'rpc method not implemented', method: body.method });\n return;\n }\n\n if (route === 'POST /admin/register-box') {\n const body = await readJsonBody<RegisterBoxBody>(req);\n if (\n !body ||\n typeof body.boxId !== 'string' ||\n typeof body.token !== 'string' ||\n typeof body.name !== 'string' ||\n body.boxId.length === 0 ||\n body.token.length === 0\n ) {\n send(res, 400, { error: 'expected {boxId, token, name}' });\n return;\n }\n const worktrees = sanitizeWorktrees(body.worktrees);\n // Only accept a finite positive integer; everything else (including the\n // common `undefined` from legacy boxes) drops to `undefined` and the\n // status-store falls back to the `<id>-<mnemonic>` segment shape.\n const projectIndex =\n typeof body.projectIndex === 'number' &&\n Number.isFinite(body.projectIndex) &&\n body.projectIndex > 0\n ? Math.trunc(body.projectIndex)\n : undefined;\n const kind = body.kind === 'cloud' ? 'cloud' : 'docker';\n const reg: BoxRegistration = {\n boxId: body.boxId,\n token: body.token,\n name: body.name,\n kind,\n backend:\n typeof body.backend === 'string' && body.backend.length > 0\n ? body.backend\n : undefined,\n registeredAt: new Date().toISOString(),\n containerName:\n typeof body.containerName === 'string' && body.containerName.length > 0\n ? body.containerName\n : undefined,\n createdAt:\n typeof body.createdAt === 'string' && body.createdAt.length > 0\n ? body.createdAt\n : undefined,\n projectIndex,\n worktrees,\n previewUrl:\n typeof body.previewUrl === 'string' && body.previewUrl.length > 0\n ? body.previewUrl\n : undefined,\n previewToken:\n typeof body.previewToken === 'string' && body.previewToken.length > 0\n ? body.previewToken\n : undefined,\n bridgeToken:\n typeof body.bridgeToken === 'string' && body.bridgeToken.length > 0\n ? body.bridgeToken\n : undefined,\n };\n registry.register(reg);\n log(\n `registered ${kind} box ${reg.boxId} (${reg.name})` +\n (worktrees && worktrees.length > 0 ? ` with ${String(worktrees.length)} worktree(s)` : ''),\n );\n // Cloud boxes get a host-side poller so the host relay can mirror their\n // status into its BoxStatusStore (and, once the executor is wired,\n // drain queued host-only RPCs and post results back).\n if (kind === 'cloud' && reg.previewUrl && reg.bridgeToken) {\n try {\n const set = await getPollers();\n set.start(reg.boxId, {\n boxId: reg.boxId,\n previewUrl: reg.previewUrl,\n bridgeToken: reg.bridgeToken,\n previewToken: reg.previewToken,\n onEvents: (evs) => {\n for (const ev of evs) {\n events.append({ boxId: reg.boxId, type: ev.type, payload: ev.payload, ts: ev.ts });\n }\n },\n onStatus: (status) => {\n if (isValidBoxStatus(status)) {\n void statusStore.set(reg.boxId, reg.name, reg.projectIndex, status);\n }\n },\n // Drained host-only RPCs (git.push, …) run on the host via the\n // executor and the result is POSTed back to /bridge/action-result.\n // No backend name → no executor; the poller's default respond\n // already returns a \"no executor\" error so the box unblocks.\n onAction: reg.backend\n ? async (action, respond) => {\n try {\n const result = await executeCloudAction(action, {\n backendName: reg.backend!,\n boxId: reg.boxId,\n boxName: reg.name,\n prompts,\n subscribers,\n log,\n });\n await respond(result);\n } catch (err) {\n await respond({\n exitCode: 1,\n stdout: '',\n stderr: `host executor failed: ${err instanceof Error ? err.message : String(err)}\\n`,\n });\n }\n }\n : undefined,\n logger: log,\n });\n } catch (err) {\n log(\n `failed to start cloud poller for ${reg.boxId}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n send(res, 204, null);\n return;\n }\n\n if (route === 'POST /admin/forget-box') {\n const body = await readJsonBody<{ boxId?: string }>(req);\n if (!body || typeof body.boxId !== 'string' || body.boxId.length === 0) {\n send(res, 400, { error: 'expected {boxId}' });\n return;\n }\n const existed = registry.forget(body.boxId);\n statusStore.delete(body.boxId);\n if (pollers) await pollers.stop(body.boxId);\n log(`forgot box ${body.boxId} (existed=${String(existed)})`);\n send(res, 204, null);\n return;\n }\n\n if (route === 'GET /admin/box-status') {\n const box = url.searchParams.get('box') ?? '';\n const status = box ? statusStore.get(box) : undefined;\n if (!status) {\n send(res, 404, { error: 'no status for box', box });\n return;\n }\n send(res, 200, status);\n return;\n }\n\n if (route === 'GET /admin/events') {\n const since = Number.parseInt(url.searchParams.get('since') ?? '0', 10) || 0;\n const box = url.searchParams.get('box') ?? undefined;\n const list = events.since(since, box ?? undefined);\n send(res, 200, { events: list });\n return;\n }\n\n if (route === 'GET /admin/registry') {\n // Redact tokens; callers on the admin path don't need them and we don't\n // want them showing up in logs if someone curls this.\n const redacted = registry.list().map((r) => ({\n boxId: r.boxId,\n name: r.name,\n registeredAt: r.registeredAt,\n containerName: r.containerName,\n createdAt: r.createdAt,\n projectIndex: r.projectIndex,\n worktrees: r.worktrees ?? [],\n }));\n send(res, 200, { boxes: redacted });\n return;\n }\n\n if (route === 'GET /admin/prompts/stream') {\n // Per-box SSE channel. The wrapper (apps/cli/src/wrapped-pty) subscribes\n // and stays connected; we push prompt-ask events on broadcast and a\n // periodic ping so the wrapper can detect a dead socket without traffic.\n // `boxId=` is required so a host with multiple boxes only sees its own\n // box's prompts (the wrapper attaches per-box anyway).\n const boxId = url.searchParams.get('boxId') ?? '';\n if (boxId.length === 0) {\n send(res, 400, { error: 'missing boxId query param' });\n return;\n }\n res.statusCode = 200;\n res.setHeader('Content-Type', 'text/event-stream');\n res.setHeader('Cache-Control', 'no-cache');\n res.setHeader('Connection', 'keep-alive');\n // Helps with proxies (e.g. nginx) that would otherwise buffer the\n // chunked response. The relay binds to loopback so this is belt-and-\n // suspenders, but the cost is one extra header.\n res.setHeader('X-Accel-Buffering', 'no');\n if (typeof res.flushHeaders === 'function') res.flushHeaders();\n res.write(': connected\\n\\n');\n subscribers.add(boxId, res);\n // Flush any prompts that arrived while no wrapper was attached — per\n // the design we block indefinitely on the in-box RPC, so a backlog can\n // build up between detach and reattach.\n for (const ev of prompts.forBox(boxId)) {\n res.write(`event: prompt-ask\\ndata: ${JSON.stringify(ev)}\\n\\n`);\n }\n // Then any active notices, so a wrapper attaching mid-checkpoint still\n // sees the in-progress warning (prompts first — they outrank notices).\n for (const ev of notices.forBox(boxId)) {\n res.write(`event: notice-set\\ndata: ${JSON.stringify(ev)}\\n\\n`);\n }\n const heartbeat = setInterval(() => {\n try {\n res.write(`event: ping\\ndata: {\"ts\":\"${new Date().toISOString()}\"}\\n\\n`);\n } catch {\n /* dead socket; close handler below removes */\n }\n }, SSE_HEARTBEAT_MS);\n if (typeof heartbeat.unref === 'function') heartbeat.unref();\n res.on('close', () => {\n clearInterval(heartbeat);\n subscribers.remove(boxId, res);\n });\n return;\n }\n\n if (route === 'POST /admin/prompts/answer') {\n const body = await readJsonBody<PromptAnswerBody>(req);\n if (!isPromptAnswerBody(body)) {\n send(res, 400, { error: 'expected {id, answer:\"y\"|\"n\", cancelled?}' });\n return;\n }\n // Find which box this id belongs to before resolving, so we can target\n // the prompt-resolved broadcast (other wrappers on the same box clear\n // their stale footer).\n const targetBox = prompts.boxFor(body.id);\n const hit = prompts.resolve(body.id, body.answer, body.cancelled);\n if (!hit) {\n // Already answered (idempotent) or never existed.\n send(res, 404, { error: 'no pending prompt with that id' });\n return;\n }\n if (targetBox) {\n subscribers.broadcast(targetBox, 'prompt-resolved', { id: body.id });\n }\n send(res, 204, null);\n return;\n }\n\n if (route === 'POST /admin/notices/set') {\n const body = await readJsonBody<SetNoticeBody>(req);\n if (\n !body ||\n typeof body.boxId !== 'string' ||\n body.boxId.length === 0 ||\n typeof body.kind !== 'string' ||\n body.kind.length === 0 ||\n typeof body.message !== 'string' ||\n body.message.length === 0\n ) {\n send(res, 400, { error: 'expected {boxId, kind, message}' });\n return;\n }\n const ttlMs =\n typeof body.ttlMs === 'number' && Number.isFinite(body.ttlMs) && body.ttlMs > 0\n ? body.ttlMs\n : undefined;\n const id = notices.set(body.boxId, body.kind, body.message, ttlMs);\n log(`notice-set box=${body.boxId} kind=${body.kind} id=${id}`);\n send(res, 200, { id });\n return;\n }\n\n if (route === 'POST /admin/notices/clear') {\n const body = await readJsonBody<ClearNoticeBody>(req);\n if (!body || typeof body.id !== 'string' || body.id.length === 0) {\n send(res, 400, { error: 'expected {boxId, id}' });\n return;\n }\n notices.clear(body.id);\n log(`notice-clear id=${body.id}`);\n send(res, 204, null);\n return;\n }\n\n send(res, 404, { error: 'not found', route });\n }\n\n function authBox(\n req: IncomingMessage,\n res: ServerResponse,\n reg: BoxRegistry,\n ): BoxRegistration | null {\n const token = bearerToken(req);\n if (token.length === 0) {\n send(res, 401, { error: 'missing bearer token' });\n return null;\n }\n const match = reg.authenticate(token);\n if (!match) {\n send(res, 401, { error: 'unknown box token' });\n return null;\n }\n return match;\n }\n\n return {\n server,\n registry,\n events,\n statusStore,\n prompts,\n subscribers,\n notices,\n hostActions: hostActions ?? undefined,\n url: `http://${host}:${String(opts.port)}`,\n close: async () => {\n if (pollers) await pollers.stopAll();\n await new Promise<void>((resolve, reject) => {\n server.close((err) => {\n if (err) reject(err);\n else resolve();\n });\n });\n },\n };\n}\n\n\nfunction sanitizeWorktrees(input: BoxWorktree[] | undefined): BoxWorktree[] | undefined {\n if (!Array.isArray(input)) return undefined;\n const out: BoxWorktree[] = [];\n for (const w of input) {\n if (\n w &&\n typeof w.containerPath === 'string' &&\n typeof w.hostMainRepo === 'string' &&\n typeof w.branch === 'string'\n ) {\n out.push({\n containerPath: w.containerPath,\n hostMainRepo: w.hostMainRepo,\n branch: w.branch,\n });\n }\n }\n return out;\n}\n\n/**\n * Resolve `params.path` (a path inside the container) to the registered\n * worktree whose hostMainRepo + branch the relay should run git in.\n * `/workspace` maps to the root repo; `/workspace/<sub>` maps to the nested\n * repo when one is registered (longest prefix wins).\n */\nfunction resolveWorktree(reg: BoxRegistration, containerPath: string): BoxWorktree | null {\n const trees = reg.worktrees ?? [];\n if (trees.length === 0) return null;\n const exact = trees.find((w) => w.containerPath === containerPath);\n if (exact) return exact;\n const prefixMatches = trees\n .filter((w) => containerPath === w.containerPath || containerPath.startsWith(w.containerPath + '/'))\n .sort((a, b) => b.containerPath.length - a.containerPath.length);\n return prefixMatches[0] ?? trees.find((w) => w.containerPath === '/workspace') ?? null;\n}\n\n/**\n * git.push / git.fetch: run `git -C <hostMainRepo> <op> <remote> <branch>\n * [args]` on the host with the user's creds. The in-container worktree's\n * working tree isn't on the host, so we operate on the shared `.git/` from\n * the main repo dir — refs already point at the in-container commits\n * (committed there against the bind-mounted .git).\n *\n * git.pull is intentionally NOT handled here: a pull merges into the\n * working tree, which lives inside the container. The in-box\n * `agentbox-ctl git pull` calls git.fetch via RPC, then runs a local merge.\n */\nasync function handleGitRpc(\n reg: BoxRegistration,\n method: 'git.push' | 'git.fetch',\n params: GitRpcParams | undefined,\n): Promise<GitRpcResult> {\n const containerPath = params?.path ?? '/workspace';\n const worktree = resolveWorktree(reg, containerPath);\n if (!worktree) {\n return {\n exitCode: 64,\n stdout: '',\n stderr: `no worktree registered for box ${reg.boxId} matching ${containerPath}`,\n };\n }\n const op = method === 'git.push' ? 'push' : 'fetch';\n const remote = params?.remote ?? 'origin';\n const argv = ['git', '-C', worktree.hostMainRepo, op, remote, worktree.branch];\n if (Array.isArray(params?.args)) {\n for (const a of params.args) {\n if (typeof a === 'string') argv.push(a);\n }\n }\n return runHostCommand(argv);\n}\n\n/**\n * cp.toHost / cp.fromHost: copy a file/dir between box and host. Shells\n * out to the installed agentbox CLI's `cp` subcommand — that command\n * already knows how to handle the docker exec tar pipe + chown + auto-\n * unpause; duplicating that here would drift. `AGENTBOX_CLI_ENTRY` is set\n * by `ensureRelay` when it spawns this process.\n *\n * Caller (the /rpc route) already gated this with askPrompt and rejected\n * non-'y' answers; we never reach this code without consent.\n */\nasync function handleCpRpc(\n reg: BoxRegistration,\n method: 'cp.toHost' | 'cp.fromHost',\n params: CpRpcParams,\n): Promise<GitRpcResult> {\n const entry = process.env.AGENTBOX_CLI_ENTRY;\n if (!entry) {\n return {\n exitCode: 64,\n stdout: '',\n stderr: 'relay: AGENTBOX_CLI_ENTRY not set; cannot run cp host-side',\n };\n }\n // `agentbox cp` is positional: <src> [dst]. Direction is encoded by which\n // arg carries the `<boxName>:` prefix.\n const boxRef = `${reg.name}:${params.boxPath}`;\n const argv =\n method === 'cp.toHost'\n ? [process.execPath, entry, 'cp', boxRef, params.hostPath]\n : [process.execPath, entry, 'cp', params.hostPath, boxRef];\n return runHostCommand(argv, CP_RPC_TIMEOUT_MS);\n}\n\n/**\n * download.{workspace,env,config,claude}: ask the installed agentbox CLI\n * to pull box contents to the host. Same decoupling rationale as cp — the\n * CLI owns rsync exclude lists, gitignore handling, claude registry\n * merging. The relay passes `-y` so the host CLI doesn't try to prompt\n * (we already did, via the host wrapper, before reaching this handler).\n */\nasync function handleDownloadRpc(\n reg: BoxRegistration,\n kind: DownloadKind,\n): Promise<GitRpcResult> {\n // params.hostPath is reserved in the wire shape; the v1 relay ignores it\n // and lets the host CLI use its defaults (box.workspacePath or ~/.claude).\n const entry = process.env.AGENTBOX_CLI_ENTRY;\n if (!entry) {\n return {\n exitCode: 64,\n stdout: '',\n stderr: 'relay: AGENTBOX_CLI_ENTRY not set; cannot run download host-side',\n };\n }\n const argv = [process.execPath, entry, 'download'];\n // `workspace` is the default download (no subcommand); the other three\n // are subcommands of `download`.\n if (kind !== 'workspace') argv.push(kind);\n argv.push(reg.name, '-y');\n return runHostCommand(argv, DOWNLOAD_RPC_TIMEOUT_MS);\n}\n\n/**\n * Capture a checkpoint host-side by shelling out to the installed agentbox\n * CLI (same decoupling philosophy as `handleGitRpc` spawning `git`). The\n * relay only knows the box id; the CLI resolves the BoxRecord (project root,\n * checkpoint config, snapshot storage) from it. `AGENTBOX_CLI_ENTRY` is set\n * by `ensureRelay` when it spawns this process.\n */\nasync function handleCheckpointRpc(\n reg: BoxRegistration,\n params: CheckpointRpcParams | undefined,\n): Promise<GitRpcResult> {\n const entry = process.env.AGENTBOX_CLI_ENTRY;\n if (!entry) {\n return {\n exitCode: 64,\n stdout: '',\n stderr: 'relay: AGENTBOX_CLI_ENTRY not set; cannot run checkpoint host-side',\n };\n }\n const argv = [process.execPath, entry, 'checkpoint', 'create', reg.boxId];\n if (params?.name) argv.push('--name', params.name);\n if (params?.merged === true) argv.push('--merged');\n if (params?.setDefault === true) argv.push('--set-default');\n if (params?.replace === true) argv.push('--replace');\n return runHostCommand(argv, CHECKPOINT_RPC_TIMEOUT_MS);\n}\n\n/**\n * Guard for the `browser.open` RPC: only absolute http/https URLs may be\n * handed to the host's `open`. Rejecting every other scheme (`file:`,\n * `javascript:`, bare paths) keeps an in-box agent from opening host files\n * or apps under the guise of \"opening a link\".\n */\nexport function isOpenableUrl(value: string): boolean {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return false;\n }\n return url.protocol === 'http:' || url.protocol === 'https:';\n}\n\nfunction runHostCommand(\n argv: string[],\n timeoutMs: number = GIT_RPC_TIMEOUT_MS,\n): Promise<GitRpcResult> {\n return new Promise<GitRpcResult>((resolve) => {\n const [cmd, ...rest] = argv;\n if (!cmd) {\n resolve({ exitCode: 64, stdout: '', stderr: 'empty command' });\n return;\n }\n const child = spawn(cmd, rest, {\n env: process.env,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n let stdout = '';\n let stderr = '';\n let settled = false;\n const finish = (exitCode: number): void => {\n if (settled) return;\n settled = true;\n resolve({ exitCode, stdout, stderr });\n };\n const timer = setTimeout(() => {\n child.kill('SIGTERM');\n stderr += `\\nrelay: command timed out after ${String(timeoutMs)}ms\\n`;\n finish(124);\n }, timeoutMs);\n child.stdout?.on('data', (chunk: Buffer) => {\n stdout += chunk.toString('utf8');\n });\n child.stderr?.on('data', (chunk: Buffer) => {\n stderr += chunk.toString('utf8');\n });\n child.on('error', (err) => {\n clearTimeout(timer);\n stderr += String(err.message ?? err);\n finish(127);\n });\n child.on('close', (code) => {\n clearTimeout(timer);\n finish(code ?? -1);\n });\n });\n}\n\nexport async function startRelayServer(opts: RelayServerOptions): Promise<RelayServerHandle> {\n const handle = createRelayServer(opts);\n await new Promise<void>((resolve, reject) => {\n handle.server.once('error', reject);\n handle.server.listen(opts.port, opts.host ?? '0.0.0.0', () => {\n handle.server.removeListener('error', reject);\n resolve();\n });\n });\n return handle;\n}\n\nexport type { BoxRegistration, RelayEvent };\n","/**\n * Host-side executor for actions parked by the in-sandbox `box-mode` relay\n * and drained by the `CloudBoxPoller`. This is where the host actually does\n * the work an in-box `agentbox-ctl git push` needs done — with host SSH\n * creds, in the host repo, without ever sending secrets into the box.\n *\n * v0 implements `git.push` and `git.fetch` via the git-bundle pull-back\n * pattern: the in-sandbox `git bundle create` is fetched into a host tmp\n * file, the host repo `git fetch`es the per-box branch from it (always a\n * fast-forward — the per-box branch only ever moves forward), then runs\n * the real `git push origin` / `git fetch origin`. `cp.*`, `download.*`,\n * `checkpoint.create`, `browser.open` are stubbed with a clear \"not yet\n * supported for cloud boxes\" error so the in-box command unblocks instead\n * of hanging. Filling each in is localized to this file.\n */\n\nimport { execa } from 'execa';\nimport { mkdtemp, rm } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport type { CloudBackend, CloudHandle } from '@agentbox/core';\nimport { findBox, readState } from '@agentbox/sandbox-core';\nimport { askPrompt, type PendingPrompts, type PromptSubscribers } from './prompts.js';\nimport type {\n CheckpointRpcParams,\n CpRpcParams,\n DownloadKind,\n DownloadRpcParams,\n GitRpcParams,\n HostAction,\n HostActionResult,\n} from './types.js';\n\nexport interface CloudActionExecutorDeps {\n /** From BoxRegistration.backend (e.g. 'daytona'). */\n backendName: string;\n /** From BoxRegistration.boxId. Used to look up the BoxRecord in ~/.agentbox/state.json. */\n boxId: string;\n /** Friendly box name — used in confirm-prompt messages. Falls back to boxId. */\n boxName?: string;\n /** Host relay's pending-prompts queue, for gating destructive ops like git push. */\n prompts?: PendingPrompts;\n /** Host wrapper SSE subscribers — the prompt UX feeds through them. */\n subscribers?: PromptSubscribers;\n /** Best-effort logger. */\n log?: (line: string) => void;\n}\n\n/**\n * Lazy backend resolver. Hardcoded for the providers we ship; future cloud\n * backends slot in here. Dynamic import keeps the relay process from loading\n * cloud SDKs (heavy CJS trees) until a cloud box is actually registered.\n *\n * ## Runtime contract (the relay→sandbox-* dependency story)\n *\n * The import path is a computed string on purpose — a literal would make\n * esbuild eager-resolve the package at bundle time, and the relay tsup\n * configs intentionally don't depend on the per-backend packages (to avoid\n * a `sandbox-daytona → sandbox-cloud → sandbox-docker → relay` cycle in\n * package.json deps).\n *\n * **At runtime the parent process is responsible for making\n * `@agentbox/sandbox-daytona` (and `@agentbox/sandbox-cloud` for the cp /\n * download / browser-open executors) resolvable from `node_modules` next\n * to the relay bin.** The `@madarco/agentbox` CLI satisfies this by\n * carrying both packages in its build, bundled via `tsup` with\n * `noExternal: [/^@agentbox\\//]`. The published `agent-box` npm package\n * ships them inlined; the workspace setup hits the same path via\n * pnpm-symlinked `node_modules`.\n *\n * If you embed `@agentbox/relay` standalone in a different host (no\n * agentbox CLI around), you MUST install `@agentbox/sandbox-daytona`\n * yourself for cloud executors to work — otherwise this throws a clear\n * `MODULE_NOT_FOUND` error and the in-box `/rpc` will see exit 1 with the\n * \"no host executor\" message above.\n */\nexport async function resolveCloudBackend(name: string): Promise<CloudBackend> {\n if (name === 'daytona') {\n const pkg = '@agentbox/sandbox-' + 'daytona';\n try {\n const mod = (await import(pkg)) as { daytonaBackend: CloudBackend };\n return mod.daytonaBackend;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (/cannot find module|MODULE_NOT_FOUND/i.test(msg)) {\n throw new Error(\n `relay: cannot load '${pkg}' at runtime — install it alongside @agentbox/relay (the @madarco/agentbox CLI normally provides this dependency). Original: ${msg}`,\n );\n }\n throw err;\n }\n }\n if (name === 'hetzner') {\n const pkg = '@agentbox/sandbox-' + 'hetzner';\n try {\n const mod = (await import(pkg)) as { hetznerBackend: CloudBackend };\n return mod.hetznerBackend;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (/cannot find module|MODULE_NOT_FOUND/i.test(msg)) {\n throw new Error(\n `relay: cannot load '${pkg}' at runtime — install it alongside @agentbox/relay (the @madarco/agentbox CLI normally provides this dependency). Original: ${msg}`,\n );\n }\n throw err;\n }\n }\n throw new Error(`no host executor for cloud backend '${name}'`);\n}\n\nexport async function executeCloudAction(\n action: HostAction,\n deps: CloudActionExecutorDeps,\n): Promise<HostActionResult> {\n const log = deps.log ?? (() => {});\n log(`executing ${action.method} for box ${deps.boxId}`);\n\n if (action.method === 'git.push' || action.method === 'git.fetch') {\n return runGitRpc(action, deps);\n }\n if (action.method === 'cp.toHost' || action.method === 'cp.fromHost') {\n return runCpRpc(action, deps);\n }\n if (\n action.method === 'download.workspace' ||\n action.method === 'download.env' ||\n action.method === 'download.config' ||\n action.method === 'download.claude'\n ) {\n return runDownloadRpc(action, deps);\n }\n if (action.method === 'checkpoint.create') {\n return runCheckpointRpc(action, deps);\n }\n if (action.method === 'browser.open.mirror') {\n return runBrowserOpenMirror(action, deps);\n }\n return {\n exitCode: 1,\n stdout: '',\n stderr: `host executor for '${action.method}' is not yet supported for cloud boxes\\n`,\n };\n}\n\n/**\n * Mirror an in-box `browser.open` notification on the host. The action runs\n * detached from the box's `/rpc` (the in-box handler responded 200 long\n * before queuing this), so blocking here doesn't tie up an agent — we can\n * happily wait for the host user's verdict with a TTL fallback.\n *\n * On `y` we spawn `open <url>` on the host. Any other verdict (deny / TTL\n * timeout / no subscribers) silently drops the link. Always resolves\n * exit 0 because the box doesn't observe the result.\n */\nasync function runBrowserOpenMirror(\n action: HostAction,\n deps: CloudActionExecutorDeps,\n): Promise<HostActionResult> {\n const params = (action.params ?? {}) as { url?: string };\n const url = typeof params.url === 'string' ? params.url.trim() : '';\n if (!url || !/^https?:\\/\\//i.test(url)) {\n return { exitCode: 0, stdout: '', stderr: '' };\n }\n if (!deps.prompts || !deps.subscribers) return { exitCode: 0, stdout: '', stderr: '' };\n if (process.env['AGENTBOX_PROMPT'] === 'off') {\n return { exitCode: 0, stdout: '', stderr: '' };\n }\n // 90s TTL matches the docker browser.open behavior closely enough that an\n // attached user has plenty of time to answer without leaving a stale\n // prompt indefinitely.\n const TTL_MS = 90_000;\n try {\n const verdict = await askPrompt(\n deps.prompts,\n deps.subscribers,\n deps.boxId,\n {\n kind: 'confirm',\n message: `Open link from cloud box ${deps.boxName ?? deps.boxId} on the host?`,\n detail: url,\n defaultAnswer: 'n',\n context: { command: 'browser.open', argv: [url] },\n },\n { ttlMs: TTL_MS },\n );\n if (verdict.answer === 'y' && !verdict.cancelled) {\n // macOS `open` is the only supported launcher today (Daytona client is\n // mac/Linux; on Linux the same call no-ops or errors — either way the\n // box doesn't observe). Spawn detached so the relay loop isn't blocked.\n const { spawn } = await import('node:child_process');\n const child = spawn('open', [url], { stdio: 'ignore', detached: true });\n child.unref();\n }\n } catch (err) {\n deps.log?.(`browser.open.mirror failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n return { exitCode: 0, stdout: '', stderr: '' };\n}\n\ninterface BoxLookup {\n workspacePath: string;\n cloudSandboxId: string;\n}\n\nasync function lookupCloudBox(boxId: string): Promise<BoxLookup> {\n const state = await readState();\n const hit = findBox(boxId, state);\n if (hit.kind !== 'ok') {\n throw new Error(`box ${boxId} not in ~/.agentbox/state.json`);\n }\n const sid = hit.box.cloud?.sandboxId;\n if (!sid) {\n throw new Error(`box ${boxId} has no cloud.sandboxId — record is malformed`);\n }\n return { workspacePath: hit.box.workspacePath, cloudSandboxId: sid };\n}\n\n/**\n * Cloud cp helpers live in `@agentbox/sandbox-cloud` — same dynamic-import\n * trick as `resolveCloudBackend` keeps the relay bundle from eagerly pulling\n * the cloud package (and its sandbox-docker transitive). Imports the helpers\n * once and caches; only loaded the first time a cloud box queues `cp.*`.\n *\n * Same runtime contract as `resolveCloudBackend`: the parent process is\n * responsible for making `@agentbox/sandbox-cloud` resolvable next to the\n * relay bin. The @madarco/agentbox CLI bundles it; standalone embedders\n * must install it themselves. See the longer note on `resolveCloudBackend`.\n */\ninterface CloudCpModule {\n uploadToCloudBox(\n backend: CloudBackend,\n handle: CloudHandle,\n hostSrc: string,\n boxDst: string,\n ): Promise<{ finalPath: string }>;\n downloadFromCloudBox(\n backend: CloudBackend,\n handle: CloudHandle,\n boxSrc: string,\n hostDst: string,\n ): Promise<{ finalPath: string }>;\n pullCloudDirContents(\n backend: CloudBackend,\n handle: CloudHandle,\n boxSrc: string,\n hostDst: string,\n ): Promise<{ finalPath: string }>;\n}\n\nlet cloudCpModule: CloudCpModule | undefined;\nasync function loadCloudCp(): Promise<CloudCpModule> {\n if (cloudCpModule) return cloudCpModule;\n // Computed string defeats esbuild's static resolution — see resolveCloudBackend.\n const pkg = '@agentbox/sandbox-' + 'cloud';\n try {\n const mod = (await import(pkg)) as CloudCpModule;\n cloudCpModule = mod;\n return mod;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (/cannot find module|MODULE_NOT_FOUND/i.test(msg)) {\n throw new Error(\n `relay: cannot load '${pkg}' at runtime — install it alongside @agentbox/relay (the @madarco/agentbox CLI normally provides this dependency). Original: ${msg}`,\n );\n }\n throw err;\n }\n}\n\nasync function runCpRpc(\n action: HostAction,\n deps: CloudActionExecutorDeps,\n): Promise<HostActionResult> {\n const params = (action.params ?? {}) as Partial<CpRpcParams>;\n if (typeof params.boxPath !== 'string' || typeof params.hostPath !== 'string') {\n return {\n exitCode: 64,\n stdout: '',\n stderr: 'cp.* requires {boxPath, hostPath} strings\\n',\n };\n }\n const direction = action.method === 'cp.toHost' ? 'box -> host' : 'host -> box';\n // Same askPrompt UX as docker's /rpc handler — keeps the in-box agent from\n // pulling host files / scattering box files without explicit consent.\n if (deps.prompts && deps.subscribers) {\n const verdict = await askPrompt(deps.prompts, deps.subscribers, deps.boxId, {\n kind: 'confirm',\n message: `Allow cp (${direction}) on ${deps.boxName ?? deps.boxId}?`,\n detail:\n action.method === 'cp.toHost'\n ? `${params.boxPath} -> ${params.hostPath}`\n : `${params.hostPath} -> ${params.boxPath}`,\n defaultAnswer: 'n',\n context: {\n command: action.method,\n argv: [params.boxPath, params.hostPath],\n },\n });\n if (verdict.answer !== 'y') {\n return { exitCode: 10, stdout: '', stderr: 'denied by user\\n' };\n }\n }\n const lookup = await lookupCloudBox(deps.boxId);\n const backend = await resolveCloudBackend(deps.backendName);\n const handle: CloudHandle = { sandboxId: lookup.cloudSandboxId };\n const cp = await loadCloudCp();\n try {\n const result =\n action.method === 'cp.toHost'\n ? await cp.downloadFromCloudBox(backend, handle, params.boxPath, params.hostPath)\n : await cp.uploadToCloudBox(backend, handle, params.hostPath, params.boxPath);\n return { exitCode: 0, stdout: `${result.finalPath}\\n`, stderr: '' };\n } catch (err) {\n return {\n exitCode: 1,\n stdout: '',\n stderr: `cp failed: ${err instanceof Error ? err.message : String(err)}\\n`,\n };\n }\n}\n\n/**\n * Capture a checkpoint by shelling out to the installed `agentbox` CLI\n * (same decoupling as the docker handler — the CLI owns checkpoint name\n * allocation, the `--set-default` config write, snapshot store layout, and\n * the cloud-snapshot creation via `provider.checkpoint.create`). The CLI's\n * `checkpoint create` is already provider-aware, so this path works for\n * both backends; we just hand it the box id.\n */\nasync function runCheckpointRpc(\n action: HostAction,\n deps: CloudActionExecutorDeps,\n): Promise<HostActionResult> {\n const params = (action.params ?? {}) as Partial<CheckpointRpcParams>;\n const entry = process.env['AGENTBOX_CLI_ENTRY'];\n if (!entry) {\n return {\n exitCode: 64,\n stdout: '',\n stderr: 'relay: AGENTBOX_CLI_ENTRY not set; cannot run checkpoint host-side\\n',\n };\n }\n const argv = [process.execPath, entry, 'checkpoint', 'create', deps.boxId];\n if (params.name) argv.push('--name', params.name);\n // --merged is docker-image-layer specific (flatten). For cloud snapshots\n // it's a no-op; pass it through anyway so the CLI's docker branch sees it\n // and the cloud branch ignores it cleanly.\n if (params.merged === true) argv.push('--merged');\n if (params.setDefault === true) argv.push('--set-default');\n if (params.replace === true) argv.push('--replace');\n const result = await execa(argv[0]!, argv.slice(1), { reject: false });\n return {\n exitCode: result.exitCode ?? 1,\n stdout: result.stdout ?? '',\n stderr: result.stderr ?? '',\n };\n}\n\nasync function runDownloadRpc(\n action: HostAction,\n deps: CloudActionExecutorDeps,\n): Promise<HostActionResult> {\n const params = (action.params ?? {}) as Partial<DownloadRpcParams>;\n const kind = (action.method.split('.')[1] ?? 'workspace') as DownloadKind;\n // Only `workspace` lands cleanly on cloud today — env/config/claude live in\n // per-agent volumes and aren't routed yet (Phase 6 follow-up; see backlog\n // 2.2). Surface a clear error instead of pretending to succeed.\n if (kind !== 'workspace') {\n return {\n exitCode: 1,\n stdout: '',\n stderr: `download.${kind} is not yet supported for cloud boxes (only download.workspace is)\\n`,\n };\n }\n if (deps.prompts && deps.subscribers) {\n const verdict = await askPrompt(deps.prompts, deps.subscribers, deps.boxId, {\n kind: 'confirm',\n message: `Allow download (${kind}) from ${deps.boxName ?? deps.boxId}?`,\n detail: params.hostPath ?? '(default host location)',\n defaultAnswer: 'n',\n context: {\n command: action.method,\n argv: params.hostPath ? [params.hostPath] : [],\n },\n });\n if (verdict.answer !== 'y') {\n return { exitCode: 10, stdout: '', stderr: 'denied by user\\n' };\n }\n }\n const lookup = await lookupCloudBox(deps.boxId);\n const backend = await resolveCloudBackend(deps.backendName);\n const handle: CloudHandle = { sandboxId: lookup.cloudSandboxId };\n const cp = await loadCloudCp();\n // params.hostPath is reserved in the wire shape; v1 lands /workspace under\n // box.workspacePath (the host project root), matching docker's default.\n const hostDst = typeof params.hostPath === 'string' && params.hostPath.length > 0\n ? params.hostPath\n : lookup.workspacePath;\n try {\n const result = await cp.pullCloudDirContents(backend, handle, '/workspace', hostDst);\n return { exitCode: 0, stdout: `${result.finalPath}\\n`, stderr: '' };\n } catch (err) {\n return {\n exitCode: 1,\n stdout: '',\n stderr: `download failed: ${err instanceof Error ? err.message : String(err)}\\n`,\n };\n }\n}\n\n/**\n * Git RPC executor for cloud boxes. The push direction:\n *\n * 1. In the sandbox, write a bundle of the per-box branch.\n * 2. Download the bundle to the host.\n * 3. Fetch the bundle ref into the host repo (always a fast-forward).\n * 4. Run the real git push from the host repo.\n *\n * Fetch direction is the mirror: host fetches origin, bundles, uploads,\n * sandbox fetches from the bundle, in-box `agentbox-ctl git pull` then\n * does its local merge as today.\n */\nasync function runGitRpc(action: HostAction, deps: CloudActionExecutorDeps): Promise<HostActionResult> {\n const params = (action.params ?? {}) as GitRpcParams;\n const lookup = await lookupCloudBox(deps.boxId);\n const backend = await resolveCloudBackend(deps.backendName);\n const handle = { sandboxId: lookup.cloudSandboxId };\n\n // The in-box ctl sends `params.path = process.cwd()`. When the user runs\n // `agentbox-ctl git push` from anywhere outside /workspace (e.g. $HOME),\n // that path won't be a git repo and `git rev-parse` would fail. Fall back\n // to /workspace whenever the supplied path can't resolve a git dir — the\n // per-box branch only ever lives at /workspace anyway.\n let containerPath = params.path ?? '/workspace';\n if (containerPath !== '/workspace') {\n const probe = await backend.exec(\n handle,\n `git -C ${shellQuote(containerPath)} rev-parse --git-dir`,\n );\n if (probe.exitCode !== 0) containerPath = '/workspace';\n }\n\n // 1. Resolve the box's current branch (the per-box `agentbox/<name>`).\n const branchProbe = await backend.exec(\n handle,\n `git -C ${shellQuote(containerPath)} rev-parse --abbrev-ref HEAD`,\n );\n const branch = (branchProbe.stdout ?? '').trim();\n if (branchProbe.exitCode !== 0 || branch.length === 0 || branch === 'HEAD') {\n return {\n exitCode: branchProbe.exitCode || 1,\n stdout: '',\n stderr: `failed to resolve branch in sandbox ${containerPath}: ${branchProbe.stderr || branch}`,\n };\n }\n\n // Gate `git.push` (and only `git.push`) behind the same host-side confirm\n // prompt the Docker provider already uses. The wrapper's SSE subscriber on\n // /admin/prompts/stream surfaces it as a footer y/N; `askPrompt` returns\n // auto-`y` when AGENTBOX_PROMPT=off (matches Docker behavior).\n if (action.method === 'git.push' && deps.prompts && deps.subscribers) {\n // Cloud-specific fallback: when no SSE subscriber is attached the prompt\n // would block indefinitely (the user has nothing to answer in). Choose\n // up-front whether to auto-deny (default) or auto-approve based on env\n // — same env knob shape as `AGENTBOX_PROMPT`. The decision is bounded\n // by `AGENTBOX_GIT_PUSH_NO_SUB`: 'deny' (default), 'allow', or 'prompt'\n // (block anyway, legacy behavior).\n const hasSubscriber = deps.subscribers.forBox(deps.boxId).length > 0;\n if (!hasSubscriber && process.env['AGENTBOX_PROMPT'] !== 'off') {\n const noSubMode = (process.env['AGENTBOX_GIT_PUSH_NO_SUB'] ?? 'deny').toLowerCase();\n if (noSubMode === 'deny') {\n return {\n exitCode: 10,\n stdout: '',\n stderr:\n 'denied automatically — no attached wrapper to confirm. Attach `agentbox claude` (or similar) and retry, or set AGENTBOX_GIT_PUSH_NO_SUB=allow.\\n',\n };\n }\n if (noSubMode === 'allow') {\n deps.log?.('git.push auto-approved (no subscribers, AGENTBOX_GIT_PUSH_NO_SUB=allow)');\n // Fall through to the actual push.\n } else {\n // 'prompt' or anything else: legacy blocking behavior with a TTL so\n // a never-attaching user doesn't wedge the executor forever.\n const verdict = await askPrompt(\n deps.prompts,\n deps.subscribers,\n deps.boxId,\n {\n kind: 'confirm',\n message: `Allow git push from cloud box ${deps.boxName ?? deps.boxId}?`,\n detail: `${params.remote ?? 'origin'} ${branch} ${(params.args ?? []).join(' ')}`.trim(),\n defaultAnswer: 'n',\n context: { command: 'git push', cwd: containerPath, argv: params.args },\n },\n { ttlMs: 5 * 60 * 1000 },\n );\n if (verdict.answer !== 'y') {\n return { exitCode: 10, stdout: '', stderr: 'denied by user\\n' };\n }\n }\n } else {\n const verdict = await askPrompt(deps.prompts, deps.subscribers, deps.boxId, {\n kind: 'confirm',\n message: `Allow git push from cloud box ${deps.boxName ?? deps.boxId}?`,\n detail: `${params.remote ?? 'origin'} ${branch} ${(params.args ?? []).join(' ')}`.trim(),\n defaultAnswer: 'n',\n context: { command: 'git push', cwd: containerPath, argv: params.args },\n });\n if (verdict.answer !== 'y') {\n return { exitCode: 10, stdout: '', stderr: 'denied by user\\n' };\n }\n }\n }\n\n const stage = await mkdtemp(join(tmpdir(), 'agentbox-git-rpc-'));\n const hostBundle = join(stage, 'op.bundle');\n const remoteBundle = '/tmp/agentbox-rpc.bundle';\n try {\n if (action.method === 'git.push') {\n // 2a. Bundle the per-box branch inside the sandbox.\n const make = await backend.exec(\n handle,\n `git -C ${shellQuote(containerPath)} bundle create ${shellQuote(remoteBundle)} ${shellQuote(branch)}`,\n );\n if (make.exitCode !== 0) {\n return { exitCode: make.exitCode, stdout: '', stderr: `bundle create failed: ${make.stderr || make.stdout}` };\n }\n // 2b. Download to host tmp.\n await backend.downloadFile(handle, remoteBundle, hostBundle);\n // 3. Fast-forward the host repo's per-box branch ref to the sandbox tip.\n const fetch = await execa(\n 'git',\n ['-C', lookup.workspacePath, 'fetch', hostBundle, `${branch}:${branch}`],\n { reject: false },\n );\n if (fetch.exitCode !== 0) {\n return {\n exitCode: fetch.exitCode ?? 1,\n stdout: fetch.stdout ?? '',\n stderr: `host git fetch from bundle failed: ${fetch.stderr ?? ''}`,\n };\n }\n // 4. Real push. Args are user-controlled (`agentbox-ctl git push --\n // <args>`); pass them through to git on the host. Remote defaults to\n // 'origin'; the bundle's branch is the explicit refspec.\n const remote = params.remote ?? 'origin';\n const argv = ['-C', lookup.workspacePath, 'push', remote, branch];\n if (Array.isArray(params.args)) {\n for (const a of params.args) if (typeof a === 'string') argv.push(a);\n }\n const push = await execa('git', argv, { reject: false });\n return {\n exitCode: push.exitCode ?? 1,\n stdout: push.stdout ?? '',\n stderr: push.stderr ?? '',\n };\n }\n // git.fetch: host fetches origin, bundles, uploads, sandbox fetches.\n const remote = params.remote ?? 'origin';\n const hostFetch = await execa('git', ['-C', lookup.workspacePath, 'fetch', remote], { reject: false });\n if (hostFetch.exitCode !== 0) {\n return {\n exitCode: hostFetch.exitCode ?? 1,\n stdout: hostFetch.stdout ?? '',\n stderr: `host git fetch failed: ${hostFetch.stderr ?? ''}`,\n };\n }\n // Bundle origin's remote-tracking refs so the sandbox sees the updates.\n const bundle = await execa(\n 'git',\n ['-C', lookup.workspacePath, 'bundle', 'create', hostBundle, `--all`],\n { reject: false },\n );\n if (bundle.exitCode !== 0) {\n return {\n exitCode: bundle.exitCode ?? 1,\n stdout: '',\n stderr: `host git bundle create failed: ${bundle.stderr ?? ''}`,\n };\n }\n await backend.uploadFile(handle, hostBundle, remoteBundle);\n const sandboxFetch = await backend.exec(\n handle,\n `git -C ${shellQuote(containerPath)} fetch ${shellQuote(remoteBundle)} '+refs/heads/*:refs/remotes/origin/*' --tags`,\n );\n return {\n exitCode: sandboxFetch.exitCode,\n stdout: sandboxFetch.stdout,\n stderr: sandboxFetch.stderr,\n };\n } finally {\n await rm(stage, { recursive: true, force: true });\n await backend\n .exec(handle, `rm -f ${shellQuote(remoteBundle)}`)\n .catch(() => {\n /* best-effort */\n });\n }\n}\n\n/** Local helper — sandbox-cloud's `quoteShellArg` would be a cross-package import. */\nfunction shellQuote(arg: string): string {\n if (arg.length === 0) return \"''\";\n if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(arg)) return arg;\n return \"'\" + arg.replace(/'/g, \"'\\\\''\") + \"'\";\n}\n","import { spawn } from 'node:child_process';\nimport { readFile } from 'node:fs/promises';\nimport {\n BUILT_IN_DEFAULTS,\n GLOBAL_CONFIG_FILE,\n parseUserConfig,\n type UserConfig,\n} from '@agentbox/config';\nimport type { BoxRegistry, EventBuffer } from './registry.js';\nimport type { BoxStatusStore } from './status-store.js';\n\nexport interface AutopauseConfig {\n enabled: boolean;\n maxRunningBoxes: number;\n idleMinutes: number;\n}\n\nexport type ContainerState = 'running' | 'paused' | 'stopped' | 'missing';\nexport type ClaudeState = 'working' | 'idle' | 'waiting' | 'unknown';\n\n/** One box's runtime facts the pure selector reasons about. No I/O, no clock. */\nexport interface BoxScanEntry {\n boxId: string;\n containerName: string;\n /** docker inspect status === 'running'. */\n running: boolean;\n /** Latest reported claude activity, or null when no snapshot exists. */\n claudeState: ClaudeState | null;\n /** ms the box has been idle (now - claude.updatedAt) when idle; else null. */\n idleMs: number | null;\n /** Box creation time as epoch ms; 0 when unknown/unparseable. */\n createdAt: number;\n}\n\n/**\n * Pure selection: given each running box's idle facts and the config, return\n * the boxIds to pause, in pause order. Pauses only enough to bring the running\n * count back to `maxRunningBoxes`, picking provably-idle boxes longest-idle\n * first (tie-break: oldest box, then boxId for determinism).\n */\nexport function selectBoxesToPause(entries: BoxScanEntry[], cfg: AutopauseConfig): string[] {\n if (!cfg.enabled) return [];\n const runningCount = entries.reduce((n, e) => (e.running ? n + 1 : n), 0);\n const excess = runningCount - cfg.maxRunningBoxes;\n if (excess <= 0) return [];\n\n const idleThresholdMs = cfg.idleMinutes * 60_000;\n const candidates = entries.filter(\n (e) => e.running && e.claudeState === 'idle' && e.idleMs != null && e.idleMs >= idleThresholdMs,\n );\n candidates.sort(\n (a, b) =>\n (b.idleMs as number) - (a.idleMs as number) ||\n a.createdAt - b.createdAt ||\n (a.boxId < b.boxId ? -1 : a.boxId > b.boxId ? 1 : 0),\n );\n return candidates.slice(0, excess).map((e) => e.boxId);\n}\n\n/**\n * Global+built-in autopause config. The relay is host-wide (not project\n * scoped), so it deliberately ignores the project/workspace layers that\n * `loadEffectiveConfig` would apply. Re-read every tick so\n * `agentbox config set --global autopause.*` takes effect without a relay\n * restart. A missing or malformed global file falls back to built-in defaults.\n */\nexport async function loadAutopauseConfig(): Promise<AutopauseConfig> {\n const d = BUILT_IN_DEFAULTS.autopause;\n let global: Partial<UserConfig> = {};\n try {\n global = parseUserConfig(await readFile(GLOBAL_CONFIG_FILE, 'utf8'), GLOBAL_CONFIG_FILE);\n } catch {\n // ENOENT (no global config yet) or a parse error -> built-in defaults.\n }\n const a = global.autopause ?? {};\n return {\n enabled: a.enabled ?? d.enabled,\n maxRunningBoxes: a.maxRunningBoxes ?? d.maxRunningBoxes,\n idleMinutes: a.idleMinutes ?? d.idleMinutes,\n };\n}\n\nexport interface AutopauseLoopDeps {\n registry: BoxRegistry;\n statusStore: BoxStatusStore;\n events: EventBuffer;\n log: (line: string) => void;\n /** Injectable for tests; defaults to the global-config loader. */\n loadConfig?: () => Promise<AutopauseConfig>;\n /** Injectable for tests; defaults to `docker inspect`. */\n inspectStatus?: (containerName: string) => Promise<ContainerState>;\n /** Injectable for tests; defaults to `docker pause`. */\n pause?: (containerName: string) => Promise<void>;\n intervalMs?: number;\n}\n\nexport interface AutopauseLoopHandle {\n /** Stop scheduling and await any in-flight tick. */\n stop: () => Promise<void>;\n}\n\nconst DEFAULT_INTERVAL_MS = 60_000;\n\nexport function startAutopauseLoop(deps: AutopauseLoopDeps): AutopauseLoopHandle {\n const loadConfig = deps.loadConfig ?? loadAutopauseConfig;\n const inspectStatus = deps.inspectStatus ?? inspectContainerState;\n const pause = deps.pause ?? pauseContainer;\n const intervalMs = deps.intervalMs ?? DEFAULT_INTERVAL_MS;\n const { registry, statusStore, events, log } = deps;\n\n let ticking = false;\n let stopped = false;\n let inFlight: Promise<void> = Promise.resolve();\n\n async function tick(): Promise<void> {\n // A slow tick (many `docker inspect`s) must not overlap the next interval.\n if (ticking) return;\n ticking = true;\n try {\n const cfg = await loadConfig();\n if (!cfg.enabled) return;\n\n const entries: BoxScanEntry[] = [];\n for (const reg of registry.list()) {\n if (!reg.containerName) continue; // pre-feature box; can't pause it\n const state = await inspectStatus(reg.containerName);\n const claude = readClaude(statusStore.get(reg.boxId));\n const idleMs =\n claude.state === 'idle' && claude.updatedAt ? msSince(claude.updatedAt) : null;\n entries.push({\n boxId: reg.boxId,\n containerName: reg.containerName,\n running: state === 'running',\n claudeState: claude.state,\n idleMs,\n createdAt: reg.createdAt ? toEpoch(reg.createdAt) : 0,\n });\n }\n\n const toPause = selectBoxesToPause(entries, cfg);\n if (toPause.length === 0) return;\n\n const byId = new Map(entries.map((e) => [e.boxId, e]));\n const runningBefore = entries.reduce((n, e) => (e.running ? n + 1 : n), 0);\n for (const boxId of toPause) {\n const e = byId.get(boxId);\n if (!e) continue;\n try {\n await pause(e.containerName);\n const mins = e.idleMs != null ? Math.round(e.idleMs / 60_000) : null;\n events.append({\n boxId,\n type: 'autopause',\n payload: {\n containerName: e.containerName,\n action: 'paused',\n idleMs: e.idleMs,\n runningBefore,\n max: cfg.maxRunningBoxes,\n },\n });\n log(\n `autopause: paused box ${boxId} (${e.containerName})` +\n (mins != null ? ` after ~${String(mins)}m idle` : '') +\n `; running ${String(runningBefore)} -> target ${String(cfg.maxRunningBoxes)}`,\n );\n } catch (err) {\n // docker failure is non-fatal: log, record, keep going.\n const msg = err instanceof Error ? err.message : String(err);\n log(`autopause: docker pause ${e.containerName} failed: ${msg}`);\n events.append({\n boxId,\n type: 'autopause',\n payload: { containerName: e.containerName, action: 'pause-failed', error: msg },\n });\n }\n }\n } catch (err) {\n // The loop must never crash the relay or stop scheduling.\n const msg = err instanceof Error ? err.message : String(err);\n log(`autopause: tick error: ${msg}`);\n } finally {\n ticking = false;\n }\n }\n\n const timer = setInterval(() => {\n if (stopped) return;\n inFlight = tick();\n }, intervalMs);\n // The HTTP server keeps the process alive; the timer shouldn't on its own.\n timer.unref();\n\n return {\n stop: async () => {\n stopped = true;\n clearInterval(timer);\n await inFlight.catch(() => {});\n },\n };\n}\n\ninterface ClaudeSnap {\n state: ClaudeState | null;\n updatedAt: string | null;\n}\n\nfunction readClaude(snap: Record<string, unknown> | undefined): ClaudeSnap {\n const c = snap && typeof snap === 'object' ? (snap as { claude?: unknown }).claude : undefined;\n if (!c || typeof c !== 'object') return { state: null, updatedAt: null };\n const o = c as Record<string, unknown>;\n const state =\n o.state === 'working' || o.state === 'idle' || o.state === 'waiting' || o.state === 'unknown'\n ? o.state\n : null;\n return { state, updatedAt: typeof o.updatedAt === 'string' ? o.updatedAt : null };\n}\n\nfunction msSince(iso: string): number | null {\n const t = Date.parse(iso);\n return Number.isNaN(t) ? null : Date.now() - t;\n}\n\nfunction toEpoch(iso: string): number {\n const t = Date.parse(iso);\n return Number.isNaN(t) ? 0 : t;\n}\n\nconst INSPECT_TIMEOUT_MS = 15_000;\nconst PAUSE_TIMEOUT_MS = 30_000;\n\ninterface DockerResult {\n exitCode: number;\n stdout: string;\n stderr: string;\n}\n\nfunction runDocker(args: string[], timeoutMs: number): Promise<DockerResult> {\n return new Promise<DockerResult>((resolve) => {\n const child = spawn('docker', args, { stdio: ['ignore', 'pipe', 'pipe'] });\n let stdout = '';\n let stderr = '';\n let settled = false;\n const finish = (exitCode: number): void => {\n if (settled) return;\n settled = true;\n resolve({ exitCode, stdout, stderr });\n };\n const timer = setTimeout(() => {\n child.kill('SIGTERM');\n stderr += `\\nrelay: docker ${args.join(' ')} timed out after ${String(timeoutMs)}ms\\n`;\n finish(124);\n }, timeoutMs);\n child.stdout?.on('data', (c: Buffer) => {\n stdout += c.toString('utf8');\n });\n child.stderr?.on('data', (c: Buffer) => {\n stderr += c.toString('utf8');\n });\n child.on('error', (err) => {\n clearTimeout(timer);\n stderr += String(err.message ?? err);\n finish(127);\n });\n child.on('close', (code) => {\n clearTimeout(timer);\n finish(code ?? -1);\n });\n });\n}\n\n/** Mirrors `inspectContainerStatus` in @agentbox/sandbox-docker (no dep on it — cycle). */\nasync function inspectContainerState(name: string): Promise<ContainerState> {\n const r = await runDocker(['inspect', '--format', '{{.State.Status}}', name], INSPECT_TIMEOUT_MS);\n if (r.exitCode !== 0) return 'missing';\n switch (r.stdout.trim()) {\n case 'running':\n return 'running';\n case 'paused':\n return 'paused';\n case 'created':\n case 'exited':\n case 'dead':\n case 'restarting':\n case 'removing':\n return 'stopped';\n default:\n return 'missing';\n }\n}\n\nasync function pauseContainer(name: string): Promise<void> {\n const r = await runDocker(['pause', name], PAUSE_TIMEOUT_MS);\n if (r.exitCode !== 0) {\n throw new Error(r.stderr.trim() || `docker pause ${name} exited ${String(r.exitCode)}`);\n }\n}\n","import type { AgentKind } from './types.js';\n\nexport interface AgentLauncher {\n readonly kind: AgentKind;\n buildArgs(initialMessage: string, userArgs: string[]): string[];\n}\n\nconst claudeCodeLauncher: AgentLauncher = {\n kind: 'claude-code',\n // claude treats its first positional argument as the seed user turn in\n // interactive mode (`claude \"<message>\"`), so we slot the initial message\n // ahead of any user-passed flags.\n buildArgs(initialMessage, userArgs) {\n if (!initialMessage) return [...userArgs];\n return [initialMessage, ...userArgs];\n },\n};\n\n// codex accepts a leading positional as the seed prompt — `codex \"<message>\"`\n// drops the user into the TUI with that turn pre-submitted. Same shape as\n// claude, so the launcher is structurally identical.\nconst codexLauncher: AgentLauncher = {\n kind: 'codex',\n buildArgs(initialMessage, userArgs) {\n if (!initialMessage) return [...userArgs];\n return [initialMessage, ...userArgs];\n },\n};\n\n// opencode also accepts a leading positional as the seed prompt — `opencode\n// \"<message>\"` enters the TUI with that turn pre-submitted. Mirrors claude/codex.\nconst opencodeLauncher: AgentLauncher = {\n kind: 'opencode',\n buildArgs(initialMessage, userArgs) {\n if (!initialMessage) return [...userArgs];\n return [initialMessage, ...userArgs];\n },\n};\n\nexport function resolveAgentLauncher(kind: AgentKind): AgentLauncher {\n if (kind === 'claude-code') return claudeCodeLauncher;\n if (kind === 'codex') return codexLauncher;\n if (kind === 'opencode') return opencodeLauncher;\n throw new Error(`unknown agent kind: ${String(kind)}`);\n}\n","/**\n * The per-box state record persisted to `~/.agentbox/state.json`. Shared by\n * every provider: a box may be a local Docker container or a remote cloud\n * sandbox. The `provider` discriminator says which; provider-specific fields\n * live flat (Docker, for historical reasons) or under `cloud` (cloud backends).\n */\n\n/** Sandbox backend a box runs on. Open-ended so future providers need no core change. */\nexport type ProviderName = 'docker' | 'daytona' | 'hetzner' | (string & {});\n\n/**\n * Docker-backend-specific fields nested under `BoxRecord.docker` once 7.1\n * lands fully. Today every Docker box also keeps the flat copies of these\n * fields on `BoxRecord` directly for back-compat (state.json migration is\n * the rest of 7.1) — write sites populate both shapes; read sites still\n * use the flat fields. New code should target this interface so the\n * eventual flat-field removal is a search-and-replace, not a redesign.\n */\nexport interface DockerBoxFields {\n /** Docker container name (`agentbox-<id|name>`). */\n container: string;\n /** Base image / checkpoint image tag the container was started from. */\n image: string;\n /** Per-box scratch dir with the `cp -c` APFS clone (when --host-snapshot is on). */\n snapshotDir?: string | null;\n /** Host-side path to the agentbox-ctl unix socket bind-mounted into the container. */\n socketPath?: string;\n /** Docker volume mounted at /home/vscode/.claude inside the box. */\n claudeConfigVolume?: string;\n /** Docker volume mounted at /home/vscode/.codex inside the box. */\n codexConfigVolume?: string;\n /** Docker volume mounted at /home/vscode/.local/share/opencode. */\n opencodeConfigVolume?: string;\n /** Per-box volume holding `.vscode-server`. */\n vscodeServerVolume?: string;\n /** Per-box volume holding `.cursor-server`. */\n cursorServerVolume?: string;\n /** Host port mapped to the noVNC web server. */\n vncHostPort?: number;\n /** Host port mapped to container :80. */\n webHostPort?: number;\n /** Portless route name registered for this box's web port. */\n portlessAlias?: string;\n /** Full user-facing URL the Portless proxy serves for this box. */\n portlessUrl?: string;\n /** Volume mounted at /var/lib/docker for the in-box dockerd. */\n dockerVolume?: string;\n /** True when this box's `dockerVolume` is the shared cache. */\n dockerCacheShared?: boolean;\n /** Checkpoint image tag this box was started from. */\n checkpointImage?: string;\n}\n\n/**\n * Cloud-backend-specific fields for a box. Populated only when\n * `BoxRecord.provider` is a cloud provider; `undefined` for Docker boxes.\n */\nexport interface CloudBoxFields {\n /** Cloud backend name, e.g. 'daytona'. Mirrors `BoxRecord.provider`. */\n backend: string;\n /** Provider-native sandbox id (the handle the backend SDK resolves). */\n sandboxId: string;\n /** Resolved base image / snapshot ref the sandbox was provisioned from. */\n image?: string;\n /**\n * In-box port the web service is exposed on (the supervisor's WebProxy\n * listen port). Cloud boxes bind a non-privileged port; Docker boxes use 80.\n */\n webPort?: number;\n /**\n * Token-authed preview URLs keyed by in-box port. Resolved at create and\n * refreshed on every start (preview tokens can rotate across stop/start).\n */\n previewUrls?: Record<number, string>;\n /**\n * Preview URL of the in-sandbox relay's `/bridge/*` surface — the channel the\n * host CloudBoxPoller drains. Refreshed on start alongside `previewUrls`.\n */\n relayPreviewUrl?: string;\n /**\n * Daytona-style preview-proxy token for `relayPreviewUrl` (sent as\n * `x-daytona-preview-token` header). Persisted so a relay restart can\n * rehydrate the poller without re-resolving from the SDK.\n */\n relayPreviewToken?: string;\n /**\n * Bearer secret authenticating the host poller to the in-sandbox relay's\n * `/bridge/*` routes. Distinct from `BoxRecord.relayToken` (the per-box token\n * the in-box agent sees) so a compromised agent cannot impersonate the host.\n */\n bridgeToken?: string;\n /**\n * User-facing checkpoint name this box was provisioned from (e.g. `setup`),\n * when `agentbox create --checkpoint <name>` resolved to a cloud snapshot.\n * Surfaces in `agentbox status --inspect` so the user can tell which\n * checkpoint a box is currently running.\n */\n snapshotRef?: string;\n}\n\nexport interface GitWorktreeRecord {\n kind: 'root' | 'nested';\n /** Host path to the main repo whose `.git/` is the source of the worktree. */\n hostMainRepo: string;\n /**\n * Agent-visible container path of the worktree (`/workspace` for root,\n * `/workspace/<sub>` for nested).\n */\n containerPath: string;\n /**\n * Per-box unique path where git registered the worktree. Docker boxes\n * register against the bind-mounted host `.git/`; cloud boxes clone into the\n * sandbox so this is a sandbox-local path. `destroyBox` uses it to deregister\n * a Docker worktree on the host.\n */\n gitWorktreePath: string;\n /** Branch the worktree was created on, e.g. `agentbox/<box-name>`. */\n branch: string;\n /** Workspace-relative path the repo was found at (empty string for root). */\n relPathFromWorkspace: string;\n}\n\nexport interface BoxRecord {\n id: string;\n name: string;\n /**\n * Sandbox backend the box runs on. Absent on records written before the\n * multi-provider split — `readState` migrates those to `'docker'` on read.\n */\n provider?: ProviderName;\n /**\n * Unique runtime identifier. For docker records: the container name\n * (`agentbox-<id|name>`) — what `docker exec` resolves. For cloud\n * records: the backend sandbox id, prefixed with `cloud:` so a grep\n * for \"agentbox-cloud-*\" finds nothing (post 7.2) — the cloud\n * sandbox is not a docker container and shouldn't look like one.\n * Docker-internal code should only ever reach this via\n * `requireDockerProvider(box, ...)` first.\n */\n container: string;\n /**\n * The image the box was started from. Docker: base image or checkpoint\n * image tag. Cloud: the resolved sandbox image / snapshot ref (mirrors\n * `box.cloud.image`).\n */\n image: string;\n workspacePath: string;\n /**\n * Optional per-box scratch dir holding a `cp -c` APFS clone of the host\n * workspace, made at create time when `--host-snapshot` is on. Docker only.\n */\n snapshotDir?: string | null;\n /**\n * Host-side path to the agentbox-ctl unix socket bind-mounted into the\n * container at /run/agentbox/ctl.sock. Docker only.\n */\n socketPath?: string;\n /** Docker volume mounted at /home/vscode/.claude inside the box. Docker only. */\n claudeConfigVolume?: string;\n /** Docker volume mounted at /home/vscode/.codex inside the box. Docker only. */\n codexConfigVolume?: string;\n /** Docker volume mounted at /home/vscode/.local/share/opencode. Docker only. */\n opencodeConfigVolume?: string;\n /** Per-box volume holding `.vscode-server`. Docker only. */\n vscodeServerVolume?: string;\n /** Per-box volume holding `.cursor-server`. Docker only. */\n cursorServerVolume?: string;\n /**\n * Bearer token the in-box supervisor uses to authenticate with the relay.\n * Generated at create time and forwarded as AGENTBOX_RELAY_TOKEN.\n */\n relayToken?: string;\n /** Per-box git worktrees. Empty/absent when the host workspace is not a git checkout. */\n gitWorktrees?: GitWorktreeRecord[];\n /** True when the box was created with --with-playwright. */\n withPlaywright?: boolean;\n /** True when the box was created with --with-env. */\n withEnv?: boolean;\n /** VNC stack (Xvnc + websockify + noVNC) is enabled for this box. */\n vncEnabled?: boolean;\n /** Container-side noVNC web port. */\n vncContainerPort?: number;\n /** Host port mapped to the noVNC web server (Docker), or preview port (cloud). */\n vncHostPort?: number;\n /** Per-box password baked into Xvnc's PasswordFile and the auto-connect URL. */\n vncPassword?: string;\n /** Container port reserved for the web service `expose:` forward. */\n webContainerPort?: number;\n /** Host port mapped to container :80 (Docker). */\n webHostPort?: number;\n /** Portless route name registered for this box's web port. Docker only. */\n portlessAlias?: string;\n /** Full user-facing URL the Portless proxy serves for this box. Docker only. */\n portlessUrl?: string;\n /** Volume mounted at /var/lib/docker for the in-box dockerd. Docker only. */\n dockerVolume?: string;\n /** True when this box's `dockerVolume` is the shared cache. Docker only. */\n dockerCacheShared?: boolean;\n /** Absolute host path of the project this box belongs to. */\n projectRoot?: string;\n /** Monotonic 1-based index within `projectRoot`. Never recycled. */\n projectIndex?: number;\n /** The checkpoint image tag this box was started from. Docker only. */\n checkpointImage?: string;\n /** Lineage of the checkpoint this box was started from. */\n checkpointSource?: {\n ref: string;\n type: 'layered' | 'flattened';\n /** Checkpoint refs composing the chain, base-most last. */\n chain: string[];\n };\n /** Resource ceilings actually applied at create. */\n resourceLimits?: {\n memoryBytes?: number;\n cpus?: number;\n pidsLimit?: number;\n disk?: string;\n };\n /**\n * Docker-backend-specific fields. Populated alongside the flat fields\n * during the 7.1 transition; once readers migrate, the flat fields go\n * away. New code SHOULD prefer `box.docker?.<field>` and fall back to\n * the flat field via the `dockerField()` helper in `@agentbox/core`.\n */\n docker?: DockerBoxFields;\n /** Cloud-backend-specific fields. Present only for cloud providers. */\n cloud?: CloudBoxFields;\n createdAt: string; // ISO-8601\n}\n\n/**\n * Read a Docker-specific field with fallback to the legacy flat slot.\n * Prefer this over `box.field` directly while the 7.1 migration is in\n * flight — once the flat fields are removed, this collapses to\n * `box.docker?.[key]` and the migration is mechanical.\n */\nexport function dockerField<K extends keyof DockerBoxFields>(\n box: BoxRecord,\n key: K,\n): DockerBoxFields[K] | undefined {\n if (box.docker && box.docker[key] !== undefined) return box.docker[key];\n // Fall back to the flat copy. Container / image are required at the\n // BoxRecord top level so they always exist as flat fields today.\n return (box as unknown as Record<string, unknown>)[key as string] as\n | DockerBoxFields[K]\n | undefined;\n}\n\nexport interface StateFile {\n version: 1;\n boxes: BoxRecord[];\n}\n\nexport type FindBoxResult =\n | { kind: 'ok'; box: BoxRecord }\n | { kind: 'none' }\n | { kind: 'ambiguous'; matches: BoxRecord[] };\n","/**\n * Provider-neutral box-resolution errors. Thrown by the box-ref resolver and\n * caught by the CLI's lifecycle error handler. They carry the query (and, for\n * the ambiguous case, the candidate records) so the CLI can render a useful\n * hint.\n */\n\nimport type { BoxRecord } from './box-record.js';\n\nexport class BoxNotFoundError extends Error {\n constructor(public readonly query: string) {\n super(`no agentbox matches \"${query}\"`);\n this.name = 'BoxNotFoundError';\n }\n}\n\nexport class AmbiguousBoxError extends Error {\n constructor(\n public readonly query: string,\n public readonly matches: BoxRecord[],\n ) {\n const ids = matches.map((m) => m.id).join(', ');\n super(`\"${query}\" matches multiple boxes: ${ids}`);\n this.name = 'AmbiguousBoxError';\n }\n}\n"],"mappings":"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EACR;EACA,QAAQ;IACN,MAAM;EACR;EACA,SAAS;IACP,SAAS;EACX;EACA,OAAO;IACL,MAAM;EACR;EACA,KAAK;IACH,eAAe;EACjB;EACA,UAAU;IACR,SAAS;IACT,UAAU;EACZ;EACA,WAAW;IACT,SAAS;IACT,iBAAiB;IACjB,aAAa;EACf;EACA,aAAa;IACX,qBAAqB;IACrB,0BAA0B;EAC5B;AACF;AAoBO,IAAM,eAAyC;EACpD;IACE,KAAK;IACL,MAAM;IACN,YAAY,CAAC,UAAU,WAAW,SAAS;IAC3C,aACE;EACJ;EACA;IACE,KAAK;IACL,MAAM;IACN,aACE;EACJ;EACA;IACE,KAAK;IACL,MAAM;IACN,aACE;EACJ;EACA;IACE,KAAK;IACL,MAAM;IACN,aACE;IACF,UAAU;EACZ;EACA;IACE,KAAK;IACL,MAAM;IACN,aACE;IACF,UAAU;EACZ;EACA;IACE,KAAK;IACL,MAAM;IACN,aACE;IACF,UAAU;EACZ;EACA;IACE,KAAK;IACL,MAAM;IACN,aACE;IACF,UAAU;EACZ;EACA;IACE,KAAK;IACL,MAAM;IACN,aAAa;EACf;EACA;IACE,KAAK;IACL,MAAM;IACN,aACE;EACJ;EACA;IACE,KAAK;IACL,MAAM;IACN,aAAa;EACf;EACA;IACE,KAAK;IACL,MAAM;IACN,aAAa;EACf;EACA;IACE,KAAK;IACL,MAAM;IACN,aAAa;EACf;EACA;IACE,KAAK;IACL,MAAM;IACN,aAAa;EACf;EACA;IACE,KAAK;IACL,MAAM;IACN,aAAa;IACb,UAAU;EACZ;EACA;IACE,KAAK;IACL,MAAM;IACN,aACE;EACJ;EACA;IACE,KAAK;IACL,MAAM;IACN,aACE;EACJ;EACA;IACE,KAAK;IACL,MAAM;IACN,aACE;EACJ;EACA;IACE,KAAK;IACL,MAAM;IACN,aAAa;EACf;EACA;IACE,KAAK;IACL,MAAM;IACN,aACE;IACF,UAAU;EACZ;EACA;IACE,KAAK;IACL,MAAM;IACN,aAAa;EACf;EACA;IACE,KAAK;IACL,MAAM;IACN,aAAa;EACf;EACA;IACE,KAAK;IACL,MAAM;IACN,aAAa;EACf;EACA;IACE,KAAK;IACL,MAAM;IACN,YAAY,CAAC,SAAS,UAAU,OAAO,MAAM;IAC7C,aACE;EACJ;EACA;IACE,KAAK;IACL,MAAM;IACN,YAAY,CAAC,UAAU,UAAU,MAAM;IACvC,aAAa;EACf;EACA;IACE,KAAK;IACL,MAAM;IACN,aAAa;EACf;EACA;IACE,KAAK;IACL,MAAM;IACN,aAAa;EACf;EACA;IACE,KAAK;IACL,MAAM;IACN,aAAa;EACf;EACA;IACE,KAAK;IACL,MAAM;IACN,aAAa;EACf;EACA;IACE,KAAK;IACL,MAAM;IACN,aAAa;EACf;EACA;IACE,KAAK;IACL,MAAM;IACN,aAAa;EACf;EACA;IACE,KAAK;IACL,MAAM;IACN,YAAY,CAAC,YAAY,kBAAkB,SAAS,MAAM;IAC1D,aAAa;EACf;EACA;IACE,KAAK;IACL,MAAM;IACN,YAAY,CAAC,iBAAiB,cAAc,MAAM;IAClD,aAAa;EACf;EACA;IACE,KAAK;IACL,MAAM;IACN,aAAa;IACb,UAAU;EACZ;EACA;IACE,KAAK;IACL,MAAM;IACN,aAAa;IACb,UAAU;EACZ;EACA;IACE,KAAK;IACL,MAAM;IACN,aACE;EACJ;EACA;IACE,KAAK;IACL,MAAM;IACN,aACE;IACF,UAAU;EACZ;EACA;IACE,KAAK;IACL,MAAM;IACN,aACE;EACJ;EACA;IACE,KAAK;IACL,MAAM;IACN,aACE;EACJ;EACA;IACE,KAAK;IACL,MAAM;IACN,aACE;EACJ;EACA;IACE,KAAK;IACL,MAAM;IACN,aACE;EACJ;EACA;IACE,KAAK;IACL,MAAM;IACN,aAAa;EACf;AACF;AAEA,IAAM,kBAAkB,IAAI,IAA2B,aAAa,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;AAEnF,SAAS,UAAU,KAAwC;AAChE,SAAO,gBAAgB,IAAI,GAAG;AAChC;AAEO,IAAM,kBAAN,cAA8B,MAAM;EACzC,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;EACd;AACF;AC3hBA,SAASC,eAAc,GAA0C;AAC/D,SAAO,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,MAAM,QAAQ,CAAC;AAChE;AAGA,IAAM,eAA4C,oBAAI,IAAI,CAAC,CAAC,gBAAgB,kBAAkB,CAAC,CAAC;AAQhG,IAAM,YAAqC,MAAM;AAC/C,QAAM,MAAM,oBAAI,IAAwB;AACxC,aAAW,QAAQ,cAAc;AAC/B,UAAM,MAAM,KAAK,IAAI,QAAQ,GAAG;AAChC,QAAI,MAAM,GAAG;AACX,YAAM,IAAI,MAAM,sBAAsB,KAAK,GAAG,uCAAuC;IACvF;AACA,UAAM,SAAS,KAAK,IAAI,MAAM,GAAG,GAAG;AACpC,UAAM,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AACnC,QAAI,QAAQ,IAAI,IAAI,MAAM;AAC1B,QAAI,CAAC,OAAO;AACV,cAAQ,EAAE,MAAM,QAAQ,QAAQ,oBAAI,IAAI,EAAE;AAC1C,UAAI,IAAI,QAAQ,KAAK;IACvB;AACA,UAAM,OAAO,IAAI,MAAM,IAAI;EAC7B;AACA,SAAO;AACT,GAAG;AAOH,SAAS,iBAAiB,KAAc,MAAqB,OAAwB;AACnF,MAAI,QAAQ,MAAM;AAChB,UAAM,IAAI,gBAAgB,GAAG,KAAK,4DAA4D;EAChG;AACA,UAAQ,KAAK,MAAM;IACjB,KAAK;AACH,UAAI,OAAO,QAAQ,WAAW;AAC5B,cAAM,IAAI,gBAAgB,GAAG,KAAK,2BAA2B,OAAO,GAAG,GAAG;MAC5E;AACA,aAAO;IACT,KAAK;AACH,UAAI,OAAO,QAAQ,UAAU;AAC3B,cAAM,IAAI,gBAAgB,GAAG,KAAK,0BAA0B,OAAO,GAAG,GAAG;MAC3E;AACA,UAAI,IAAI,WAAW,GAAG;AACpB,cAAM,IAAI,gBAAgB,GAAG,KAAK,oBAAoB;MACxD;AACA,aAAO;IACT,KAAK;AACH,UAAI,OAAO,QAAQ,YAAY,CAAC,OAAO,UAAU,GAAG,GAAG;AACrD,cAAM,IAAI,gBAAgB,GAAG,KAAK,4BAA4B,OAAO,GAAG,CAAC,GAAG;MAC9E;AACA,aAAO;IACT,KAAK;AACH,UAAI,OAAO,QAAQ,YAAY,CAAC,KAAK,WAAY,SAAS,GAAG,GAAG;AAC9D,cAAM,IAAI;UACR,GAAG,KAAK,oBAAoB,KAAK,WAAY,KAAK,IAAI,CAAC,SAAS,OAAO,GAAG,CAAC;QAC7E;MACF;AACA,aAAO;EACX;AACF;AASO,SAAS,gBAAgB,MAAc,OAAoC;AAChF,MAAI;AACJ,MAAI;AACF,UAAML,WAAU,IAAI;EACtB,SAAS,KAAK;AACZ,UAAM,IAAI;MACR,GAAG,KAAK,uBAAuB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;IACjF;EACF;AACA,MAAI,QAAQ,QAAQ,QAAQ,OAAW,QAAO,CAAC;AAC/C,MAAI,CAACK,eAAc,GAAG,GAAG;AACvB,UAAM,IAAI,gBAAgB,GAAG,KAAK,+BAA+B;EACnE;AACA,SAAO,sBAAsB,KAAK,KAAK;AACzC;AAOO,SAAS,sBAAsB,KAAc,OAAoC;AACtF,MAAI,QAAQ,QAAQ,QAAQ,OAAW,QAAO,CAAC;AAC/C,MAAI,CAACA,eAAc,GAAG,GAAG;AACvB,UAAM,IAAI,gBAAgB,GAAG,KAAK,qBAAqB;EACzD;AAEA,QAAM,MAA2B,CAAC;AAClC,aAAW,CAAC,YAAY,SAAS,KAAK,OAAO,QAAQ,GAAG,GAAG;AACzD,UAAM,aAAa,SAAS,IAAI,UAAU;AAC1C,QAAI,CAAC,YAAY;AACf,YAAM,IAAI;QACR,GAAG,KAAK,6BAA6B,UAAU,aAAa,CAAC,GAAG,SAAS,KAAK,CAAC,EAAE,KAAK,IAAI,CAAC;MAC7F;IACF;AACA,QAAI,cAAc,QAAQ,cAAc,OAAW;AACnD,QAAI,CAACA,eAAc,SAAS,GAAG;AAC7B,YAAM,IAAI,gBAAgB,GAAG,KAAK,IAAI,UAAU,qBAAqB;IACvE;AACA,UAAM,YAAqC,CAAC;AAC5C,eAAW,CAAC,UAAU,OAAO,KAAK,OAAO,QAAQ,SAAS,GAAG;AAC3D,YAAM,OAAO,WAAW,OAAO,IAAI,QAAQ;AAC3C,UAAI,CAAC,MAAM;AACT,cAAM,YAAY,aAAa,IAAI,GAAG,UAAU,IAAI,QAAQ,EAAE;AAC9D,YAAI,WAAW;AACb,gBAAM,IAAI;YACR,GAAG,KAAK,IAAI,UAAU,IAAI,QAAQ,mBAAmB,SAAS;UAChE;QACF;AACA,cAAM,IAAI;UACR,GAAG,KAAK,IAAI,UAAU,kBAAkB,QAAQ,aAAa,CAAC,GAAG,WAAW,OAAO,KAAK,CAAC,EAAE,KAAK,IAAI,CAAC;QACvG;MACF;AACA,UAAI,YAAY,OAAW;AAC3B,gBAAU,QAAQ,IAAI,iBAAiB,SAAS,MAAM,GAAG,KAAK,IAAI,KAAK,GAAG,EAAE;IAC9E;AACA,QAAI,OAAO,KAAK,SAAS,EAAE,SAAS,GAAG;AAGpC,UAAgC,UAAU,IAAI;IACjD;EACF;AACA,SAAO;AACT;AAOO,SAAS,iBAAiB,KAAa,KAAsB;AAClE,QAAM,OAAO,iBAAiB,GAAG;AACjC,UAAQ,KAAK,MAAM;IACjB,KAAK,QAAQ;AACX,YAAM,IAAI,IAAI,KAAK,EAAE,YAAY;AACjC,UAAI,MAAM,UAAU,MAAM,SAAS,MAAM,OAAO,MAAM,KAAM,QAAO;AACnE,UAAI,MAAM,WAAW,MAAM,QAAQ,MAAM,OAAO,MAAM,MAAO,QAAO;AACpE,YAAM,IAAI,gBAAgB,GAAG,GAAG,2CAA2C,GAAG,GAAG;IACnF;IACA,KAAK;AACH,UAAI,IAAI,WAAW,EAAG,OAAM,IAAI,gBAAgB,GAAG,GAAG,qBAAqB;AAC3E,aAAO;IACT,KAAK,OAAO;AACV,YAAM,IAAI,OAAO,GAAG;AACpB,UAAI,CAAC,OAAO,SAAS,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,GAAG;AAC/C,cAAM,IAAI,gBAAgB,GAAG,GAAG,+BAA+B,GAAG,GAAG;MACvE;AACA,aAAO;IACT;IACA,KAAK;AACH,UAAI,CAAC,KAAK,WAAY,SAAS,GAAG,GAAG;AACnC,cAAM,IAAI;UACR,GAAG,GAAG,qBAAqB,KAAK,WAAY,KAAK,IAAI,CAAC,UAAU,GAAG;QACrE;MACF;AACA,aAAO;EACX;AACF;AAEA,SAAS,iBAAiB,KAA4B;AACpD,QAAM,YAAY,aAAa,IAAI,GAAG;AACtC,MAAI,WAAW;AACb,UAAM,IAAI,gBAAgB,GAAG,GAAG,mBAAmB,SAAS,eAAU,SAAS,UAAU;EAC3F;AACA,QAAM,MAAM,IAAI,QAAQ,GAAG;AAC3B,MAAI,MAAM,GAAG;AACX,UAAM,IAAI,gBAAgB,gBAAgB,GAAG,iCAAiC;EAChF;AACA,QAAM,SAAS,SAAS,IAAI,IAAI,MAAM,GAAG,GAAG,CAAC;AAC7C,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI;MACR,2BAA2B,IAAI,MAAM,GAAG,GAAG,CAAC,aAAa,CAAC,GAAG,SAAS,KAAK,CAAC,EAAE,KAAK,IAAI,CAAC;IAC1F;EACF;AACA,QAAM,OAAO,OAAO,OAAO,IAAI,IAAI,MAAM,MAAM,CAAC,CAAC;AACjD,MAAI,CAAC,MAAM;AACT,UAAM,IAAI;MACR,gBAAgB,GAAG,eAAe,OAAO,IAAI,KAAK,CAAC,GAAG,OAAO,OAAO,KAAK,CAAC,EAAE,KAAK,IAAI,CAAC;IACxF;EACF;AACA,SAAO;AACT;ACxMO,IAAM,YAAY,KAAK,QAAQ,GAAG,WAAW;AAC7C,IAAM,qBAAqB,KAAK,WAAW,aAAa;AACxD,IAAM,eAAe,KAAK,WAAW,UAAU;AAC/C,IAAM,4BAA4B;AAkBzC,eAAsB,gBAAgB,KAAmC;AACvE,QAAM,QAAQ,MAAM,aAAa,GAAG;AACpC,MAAI,MAAM;AAEV,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,QAAI,MAAM,WAAW,KAAK,KAAK,yBAAyB,CAAC,GAAG;AAC1D,aAAO,EAAE,MAAM,KAAK,iBAAiB,KAAK;IAC5C;AACA,UAAM,SAAS,QAAQ,GAAG;AAC1B,QAAI,WAAW,IAAK;AACpB,UAAM;EACR;AACA,SAAO,EAAE,MAAM,OAAO,iBAAiB,MAAM;AAC/C;AAEA,eAAe,aAAa,GAA4B;AACtD,QAAM,MAAM,QAAQ,CAAC;AAIrB,MAAI;AACF,WAAO,MAAM,SAAS,GAAG;EAC3B,QAAQ;AACN,WAAO;EACT;AACF;AAEA,eAAe,WAAW,GAA6B;AACrD,MAAI;AACF,UAAM,KAAK,MAAM,KAAK,CAAC;AACvB,WAAO,GAAG,OAAO;EACnB,QAAQ;AACN,WAAO;EACT;AACF;AAOO,SAAS,gBAAgB,SAAyB;AACvD,QAAM,aAAa,QAAQ,SAAS,KAAK,QAAQ,SAAS,GAAG,IACzD,QAAQ,MAAM,GAAG,EAAE,IACnB;AACJ,SAAO,WAAW,MAAM,EAAE,OAAO,UAAU,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AACxE;AAQO,SAAS,iBAAiB,KAAqB;AACpD,SACE,IACG,YAAY,EACZ,QAAQ,MAAM,GAAG,EACjB,QAAQ,gBAAgB,GAAG,EAC3B,QAAQ,OAAO,GAAG,EAClB,QAAQ,YAAY,EAAE,EACtB,MAAM,GAAG,EAAE,KAAK;AAEvB;AAQO,SAAS,kBAAkB,SAAyB;AACzD,SAAO,GAAG,gBAAgB,OAAO,CAAC,IAAI,iBAAiB,SAAS,OAAO,CAAC,CAAC;AAC3E;AAEO,SAAS,iBAAiB,SAAyB;AACxD,SAAO,KAAK,cAAc,kBAAkB,OAAO,CAAC;AACtD;AAEO,SAAS,kBAAkB,SAAyB;AACzD,SAAO,KAAK,iBAAiB,OAAO,GAAG,aAAa;AACtD;AAEO,SAAS,gBAAgB,SAAyB;AACvD,SAAO,KAAK,iBAAiB,OAAO,GAAG,WAAW;AACpD;AAEO,SAAS,oBAAoB,eAA+B;AACjE,SAAO,KAAK,eAAe,yBAAyB;AACtD;AAOA,eAAsB,cACpB,OACA,KACiB;AACjB,MAAI,UAAU,SAAU,QAAO;AAC/B,QAAM,OAAO,MAAM,gBAAgB,GAAG;AACtC,MAAI,UAAU,UAAW,QAAO,kBAAkB,KAAK,IAAI;AAC3D,SAAO,oBAAoB,KAAK,IAAI;AACtC;AC9GA,eAAe,uBAAuB,MAA4C;AAChF,MAAI;AACJ,MAAI;AACF,WAAO,MAAMJ,UAAS,MAAM,MAAM;EACpC,SAAS,KAAK;AACZ,QAAK,IAA8B,SAAS,SAAU,QAAO,CAAC;AAC9D,UAAM;EACR;AACA,SAAO,gBAAgB,MAAM,IAAI;AACnC;AAWA,eAAsB,4BACpB,eAC8B;AAC9B,QAAM,OAAO,oBAAoB,aAAa;AAC9C,MAAI;AACJ,MAAI;AACF,WAAO,MAAMA,UAAS,MAAM,MAAM;EACpC,SAAS,KAAK;AACZ,QAAK,IAA8B,SAAS,SAAU,QAAO,CAAC;AAC9D,UAAM;EACR;AACA,MAAI;AACJ,MAAI;AACF,UAAMD,YAAU,IAAI;EACtB,QAAQ;AAGN,WAAO,CAAC;EACV;AACA,MAAI,QAAQ,QAAQ,QAAQ,UAAa,OAAO,QAAQ,YAAY,MAAM,QAAQ,GAAG,GAAG;AACtF,WAAO,CAAC;EACV;AACA,QAAM,WAAY,IAAgC,UAAU;AAC5D,MAAI,aAAa,UAAa,aAAa,KAAM,QAAO,CAAC;AACzD,SAAO,sBAAsB,UAAU,GAAG,IAAI,WAAW;AAC3D;AAcA,eAAsB,oBACpB,KACA,OAAmC,CAAC,GACb;AACvB,QAAM,cAAc,MAAM,gBAAgB,GAAG;AAC7C,QAAM,cAAc,kBAAkB,YAAY,IAAI;AACtD,QAAM,gBAAgB,YAAY,kBAAkB,oBAAoB,YAAY,IAAI,IAAI;AAE5F,QAAM,CAAC,cAAc,eAAe,eAAe,IAAI,MAAM,QAAQ,IAAI;IACvE,uBAAuB,kBAAkB;IACzC,uBAAuB,WAAW;IAClC,gBAAgB,4BAA4B,YAAY,IAAI,IAAI,QAAQ,QAAQ,CAAC,CAAC;EACpF,CAAC;AAED,QAAM,YAAY,KAAK,gBAAgB,CAAC;AAExC,QAAM,EAAE,WAAW,QAAQ,IAAI,YAAY;IACzC,KAAK;IACL,WAAW;IACX,SAAS;IACT,QAAQ;EACV,CAAC;AAED,SAAO;IACL;IACA,QAAQ;MACN,KAAK,EAAE,QAAQ,UAAU;MACzB,WAAW,EAAE,MAAM,eAAe,QAAQ,gBAAgB;MAC1D,SAAS,EAAE,MAAM,aAAa,QAAQ,cAAc;MACpD,QAAQ,EAAE,MAAM,oBAAoB,QAAQ,aAAa;MACzD,UAAU;IACZ;IACA;IACA,aAAa,YAAY;IACzB,aAAa,gBAAgB,YAAY,IAAI;IAC7C,iBAAiB,YAAY;EAC/B;AACF;AAcA,SAAS,YAAY,OAGnB;AAEA,QAAM,YAA6B,KAAK,MAAM,KAAK,UAAU,iBAAiB,CAAC;AAC/E,QAAM,UAAwC,CAAC;AAE/C,QAAM,aAA2E;IAC/E,EAAE,QAAQ,OAAO,QAAQ,MAAM,IAAI;IACnC,EAAE,QAAQ,aAAa,QAAQ,MAAM,UAAU;IAC/C,EAAE,QAAQ,WAAW,QAAQ,MAAM,QAAQ;IAC3C,EAAE,QAAQ,UAAU,QAAQ,MAAM,OAAO;EAC3C;AAEA,aAAW,QAAQ,cAAc;AAC/B,UAAM,MAAM,KAAK,IAAI,QAAQ,GAAG;AAChC,UAAM,SAAS,KAAK,IAAI,MAAM,GAAG,GAAG;AACpC,UAAM,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAEnC,QAAI,SAA0D;AAC9D,eAAW,SAAS,YAAY;AAC9B,YAAM,IAAI,SAAS,MAAM,QAAQ,QAAQ,IAAI;AAC7C,UAAI,MAAM,QAAW;AACnB,iBAAS,EAAE,QAAQ,MAAM,QAAQ,OAAO,EAAE;AAC1C;MACF;IACF;AAEA,QAAI,QAAQ;AACV,gBAAU,WAAW,QAAQ,MAAM,OAAO,KAAK;AAC/C,cAAQ,KAAK,GAAG,IAAI,OAAO;IAC7B,OAAO;AACL,cAAQ,KAAK,GAAG,IAAI;IACtB;EACF;AAEA,SAAO,EAAE,WAAW,QAAQ;AAC9B;AAEA,SAAS,SACP,KACA,QACA,MACS;AACT,QAAM,IAAK,IAAgC,MAAM;AACjD,MAAI,MAAM,UAAa,MAAM,QAAQ,OAAO,MAAM,SAAU,QAAO;AACnE,SAAQ,EAA8B,IAAI;AAC5C;AAEA,SAAS,UACP,KACA,QACA,MACA,OACM;AACN,QAAM,IAAK,IAA2D,MAAM;AAC5E,MAAI,CAAC,EAAG;AACR,IAAE,IAAI,IAAI;AACZ;AC9KO,SAAS,yBACd,KACA,UACQ;AAGR,QAAM,cACJ,aAAa,YACT,IAAI,IAAI,2BACR,aAAa,YACX,IAAI,IAAI,2BACR,IAAI,IAAI;AAChB,MAAI,eAAe,YAAY,SAAS,EAAG,QAAO;AAClD,SAAO,IAAI,IAAI;AACjB;AAOO,SAAS,2BACd,UAKiC;AACjC,MAAI,aAAa,SAAU,QAAO;AAClC,MAAI,aAAa,UAAW,QAAO;AACnC,MAAI,aAAa,UAAW,QAAO;AACnC,SAAO;AACT;ACfA,eAAsB,eACpB,OACA,KACA,OACA,KACA,OAAmB,CAAC,GACE;AACtB,MAAI,CAAC,UAAU,GAAG,GAAG;AACnB,UAAM,IAAI,gBAAgB,gBAAgB,GAAG,GAAG;EAClD;AAEA,QAAM,UAAU,KAAK,OAAO,OAAO,UAAU,WACzC,iBAAiB,KAAK,KAAK,IAC3B;AAEJ,QAAM,OAAO,MAAM,cAAc,OAAO,GAAG;AAC3C,QAAM,UAAU,MAAM,gBAAgB,IAAI;AAC1C,UAAQ,SAAS,KAAK,OAAO;AAG7B,kBAAgB,cAAc,OAAO,GAAG,IAAI;AAC5C,QAAM,gBAAgB,MAAM,OAAO;AAEnC,MAAI,UAAU,WAAW;AACvB,UAAM,QAAQ,MAAM,gBAAgB,GAAG,GAAG;AAC1C,UAAM,iBAAiB,IAAI;EAC7B;AAEA,SAAO,EAAE,MAAM,QAAQ;AACzB;AAMA,eAAsB,iBACpB,OACA,KACA,KAC6C;AAC7C,MAAI,CAAC,UAAU,GAAG,GAAG;AACnB,UAAM,IAAI,gBAAgB,gBAAgB,GAAG,GAAG;EAClD;AACA,QAAM,OAAO,MAAM,cAAc,OAAO,GAAG;AAC3C,QAAM,UAAU,MAAM,gBAAgB,IAAI;AAC1C,QAAM,UAAU,UAAU,SAAS,GAAG;AACtC,MAAI,CAAC,QAAS,QAAO,EAAE,MAAM,SAAS,MAAM;AAC5C,QAAM,gBAAgB,MAAM,OAAO;AACnC,MAAI,UAAU,WAAW;AACvB,UAAM,QAAQ,MAAM,gBAAgB,GAAG,GAAG;AAC1C,UAAM,iBAAiB,IAAI;EAC7B;AACA,SAAO,EAAE,MAAM,SAAS,KAAK;AAC/B;AAuBA,eAAsB,yBAAkD;AACtE,MAAI;AACJ,MAAI;AACF,cAAU,MAAM,QAAQ,YAAY;EACtC,SAAS,KAAK;AACZ,QAAK,IAA8B,SAAS,SAAU,QAAO,CAAC;AAC9D,UAAM;EACR;AACA,QAAM,MAAsB,CAAC;AAC7B,aAAW,WAAW,SAAS;AAG7B,UAAM,IAAI,2BAA2B,KAAK,OAAO;AACjD,QAAI,CAAC,EAAG;AACR,UAAM,OAAO,EAAE,CAAC;AAChB,UAAM,OAAO,MAAM,SAAS,OAAO;AACnC,QAAI,CAAC,KAAM;AACX,UAAM,UAAU,kBAAkB,KAAK,YAAY;AACnD,UAAM,YAAY,MAAMM,YAAW,OAAO;AAC1C,QAAI,KAAK;MACP;MACA;MACA,cAAc,KAAK;MACnB,WAAW,KAAK;MAChB,YAAY,KAAK;MACjB,YAAY;MACZ,eAAe;IACjB,CAAC;EACH;AACA,MAAI,KAAK,CAAC,GAAG,MAAM,EAAE,aAAa,cAAc,EAAE,YAAY,CAAC;AAC/D,SAAO;AACT;AAqBA,eAAsB,0BACpB,OAAyC,CAAC,GACA;AAC1C,QAAM,SAAS,KAAK,UAAU;AAC9B,QAAM,OAAO,IAAI,IAAI,KAAK,kBAAkB,CAAC,CAAC;AAC9C,QAAM,UAAoD,CAAC;AAC3D,aAAW,SAAS,MAAM,uBAAuB,GAAG;AAClD,QAAI,CAAC,WAAW,MAAM,YAAY,KAAK,KAAK,IAAI,MAAM,YAAY,EAAG;AACrE,QAAI,UAAU;AACd,QAAI;AACF,YAAMJ,MAAK,MAAM,YAAY;IAC/B,SAAS,KAAK;AACZ,UAAK,IAA8B,SAAS,SAAU,WAAU;IAClE;AACA,QAAI,CAAC,QAAS;AACd,YAAQ,KAAK,EAAE,MAAM,MAAM,MAAM,cAAc,MAAM,aAAa,CAAC;AACnE,QAAI,CAAC,QAAQ;AACX,UAAI;AAKF,cAAM,GAAGE,MAAK,cAAc,MAAM,OAAO,GAAG,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;MAC9E,QAAQ;MAER;IACF;EACF;AACA,SAAO,EAAE,SAAS,OAAO;AAC3B;AAOA,IAAM,0BAA0BA,MAAK,cAAc,UAAU;AAG7D,eAAsB,uBAAwC;AAC5D,MAAI,QAAQ;AACZ,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,MAAMH,WAAS,yBAAyB,MAAM,CAAC;AAGzE,QAAI,OAAO,OAAO,YAAY,YAAY,OAAO,SAAS,OAAO,OAAO,GAAG;AACzE,cAAQ,OAAO;IACjB;EACF,QAAQ;EAER;AACA,QAAM,OAAO,QAAQ;AACrB,QAAM,MAAM,cAAc,EAAE,WAAW,KAAK,CAAC;AAC7C,QAAM,MAAM,GAAG,uBAAuB,QAAQ,QAAQ,IAAI,SAAS,CAAC,IAAI,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC;AAC/F,QAAM,UAAU,KAAK,KAAK,UAAU,EAAE,SAAS,KAAK,CAAC,IAAI,MAAM,EAAE,UAAU,QAAQ,MAAM,IAAM,CAAC;AAChG,QAAM,OAAO,KAAK,uBAAuB;AACzC,SAAO;AACT;AAEA,eAAe,SACb,SAC+F;AAC/F,QAAM,WAAW,GAAG,YAAY,IAAI,OAAO;AAC3C,MAAI;AACF,UAAM,OAAO,MAAMA,WAAS,UAAU,MAAM;AAC5C,UAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,QAAI,OAAO,OAAO,cAAc,MAAM,SAAU,QAAO;AACvD,WAAO;MACL,cAAc,OAAO,cAAc;MACnC,WAAW,OAAO,OAAO,WAAW,MAAM,WAAW,OAAO,WAAW,IAAI;MAC3E,YAAY,OAAO,OAAO,YAAY,MAAM,WAAW,OAAO,YAAY,IAAI;IAChF;EACF,QAAQ;AACN,WAAO;EACT;AACF;AAEA,eAAeK,YAAW,GAA6B;AACrD,MAAI;AACF,UAAM,KAAK,MAAMJ,MAAK,CAAC;AACvB,WAAO,GAAG,OAAO;EACnB,QAAQ;AACN,WAAO;EACT;AACF;AAEA,eAAe,gBAAgB,MAA4C;AACzE,MAAI;AACJ,MAAI;AACF,WAAO,MAAMD,WAAS,MAAM,MAAM;EACpC,SAAS,KAAK;AACZ,QAAK,IAA8B,SAAS,SAAU,QAAO,CAAC;AAC9D,UAAM;EACR;AACA,SAAO,gBAAgB,MAAM,IAAI;AACnC;AAEA,SAAS,QAAQ,KAA0B,KAAa,OAAsB;AAC5E,QAAM,MAAM,IAAI,QAAQ,GAAG;AAC3B,QAAM,SAAS,IAAI,MAAM,GAAG,GAAG;AAC/B,QAAM,OAAO,IAAI,MAAM,MAAM,CAAC;AAC9B,QAAM,OAAO;AACb,MAAI,CAAC,KAAK,MAAM,KAAK,OAAO,KAAK,MAAM,MAAM,UAAU;AACrD,SAAK,MAAM,IAAI,CAAC;EAClB;AACA,OAAK,MAAM,EAAE,IAAI,IAAI;AACvB;AAEA,SAAS,UAAU,KAA0B,KAAsB;AACjE,QAAM,MAAM,IAAI,QAAQ,GAAG;AAC3B,QAAM,SAAS,IAAI,MAAM,GAAG,GAAG;AAC/B,QAAM,OAAO,IAAI,MAAM,MAAM,CAAC;AAC9B,QAAM,OAAO;AACb,QAAM,IAAI,KAAK,MAAM;AACrB,MAAI,CAAC,KAAK,OAAO,MAAM,YAAY,EAAE,QAAQ,GAAI,QAAO;AACxD,SAAO,EAAE,IAAI;AACb,MAAI,OAAO,KAAK,CAAC,EAAE,WAAW,GAAG;AAC/B,WAAO,KAAK,MAAM;EACpB;AACA,SAAO;AACT;AAEA,eAAe,gBAAgB,MAAc,KAAyC;AACpF,QAAM,MAAME,SAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAG9C,QAAM,OAAO,OAAO,KAAK,GAAG,EAAE,WAAW,IACrC,gDACA,cAAc,GAAG;AACrB,QAAM,MAAM,GAAG,IAAI,QAAQ,QAAQ,IAAI,SAAS,CAAC,IAAI,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC;AAC5E,QAAM,UAAU,KAAK,MAAM,EAAE,UAAU,QAAQ,MAAM,IAAM,CAAC;AAC5D,QAAM,OAAO,KAAK,IAAI;AACxB;AAEA,eAAe,iBAAiB,SAAgC;AAC9D,QAAM,MAAMA,SAAQ,gBAAgB,OAAO,CAAC;AAC5C,QAAM,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;AACpC,QAAM,WAAW,gBAAgB,OAAO;AACxC,MAAI,QAAuD,CAAC;AAC5D,MAAI;AACF,YAAQ,KAAK,MAAM,MAAMF,WAAS,UAAU,MAAM,CAAC;EACrD,QAAQ;EAER;AACA,QAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,QAAM,OAAO;IACX,cAAc;IACd,MAAM,gBAAgB,OAAO;IAC7B,WAAW,MAAM,aAAa;IAC9B,YAAY;EACd;AACA,QAAM,MAAM,GAAG,QAAQ,QAAQ,QAAQ,IAAI,SAAS,CAAC,IAAI,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC;AAChF,QAAM,UAAU,KAAK,KAAK,UAAU,MAAM,MAAM,CAAC,IAAI,MAAM,EAAE,UAAU,QAAQ,MAAM,IAAM,CAAC;AAC5F,QAAM,OAAO,KAAK,QAAQ;AAC5B;;;AlC5TA,SAAS,OAAO,SAAAM,SAAO,YAAAC,kBAAgB;AACvC,SAAS,QAAAC,cAAY;AACrB,SAAS,SAAAC,eAAa;;;AmCFtB,SAAS,SAAAC,QAAO,YAAAC,WAAU,aAAAC,kBAAiB;AAC3C,SAAS,WAAAC,gBAAe;AACxB,SAAS,WAAAC,UAAS,QAAAC,aAAY;ACF9B,SAAS,aAAa;AACtB,SAAS,WAAAC,UAAS,QAAAC,aAAY;AAC9B,SAAS,QAAAF,cAAY;ADGd,IAAMG,aAAYH,MAAKF,SAAQ,GAAG,WAAW;AAC7C,IAAM,aAAaE,MAAKG,YAAW,YAAY;AAEtD,IAAM,QAAmB,EAAE,SAAS,GAAG,OAAO,CAAC,EAAE;AAEjD,eAAsB,UAAU,OAAe,YAAgC;AAC7E,MAAI;AACF,UAAM,MAAM,MAAMP,UAAS,MAAM,MAAM;AACvC,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAI,OAAO,YAAY,KAAK,CAAC,MAAM,QAAQ,OAAO,KAAK,GAAG;AACxD,YAAM,IAAI,MAAM,oCAAoC,IAAI,EAAE;IAC5D;AAOA,eAAW,KAAK,OAAO,OAAO;AAC5B,QAAE,aAAa;AACf,WAAK,EAAE,YAAY,cAAc,YAAY,CAAC,EAAE,QAAQ;AACtD,UAAE,SAAS,oBAAoB,CAAC;MAClC;IACF;AACA,WAAO;EACT,SAAS,KAAK;AACZ,QAAK,IAA8B,SAAS,UAAU;AACpD,aAAO,EAAE,GAAG,MAAM;IACpB;AACA,UAAM;EACR;AACF;AAEA,eAAsB,WAAW,OAAkB,OAAe,YAA2B;AAC3F,QAAMD,OAAMI,SAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC9C,QAAMF,WAAU,MAAM,KAAK,UAAU,OAAO,MAAM,CAAC,IAAI,MAAM,MAAM;AACrE;AAEA,eAAsB,UAAU,KAAgB,OAAe,YAA2B;AAKxF,QAAM,WACH,IAAI,YAAY,cAAc,YAAY,CAAC,IAAI,SAC5C,EAAE,GAAG,KAAK,QAAQ,oBAAoB,GAAG,EAAE,IAC3C;AACN,QAAM,QAAQ,MAAM,UAAU,IAAI;AAClC,QAAM,OAAkB;IACtB,SAAS;IACT,OAAO,CAAC,GAAG,MAAM,MAAM,OAAO,CAAC,MAAM,EAAE,OAAO,QAAQ,EAAE,GAAG,OAAO;EACpE;AACA,QAAM,WAAW,MAAM,IAAI;AAC7B;AAYA,SAAS,oBAAoB,KAAiC;AAC5D,SAAO;IACL,WAAW,IAAI;IACf,OAAO,IAAI;IACX,aAAa,IAAI,eAAe;IAChC,YAAY,IAAI;IAChB,oBAAoB,IAAI;IACxB,mBAAmB,IAAI;IACvB,sBAAsB,IAAI;IAC1B,oBAAoB,IAAI;IACxB,oBAAoB,IAAI;IACxB,aAAa,IAAI;IACjB,aAAa,IAAI;IACjB,eAAe,IAAI;IACnB,aAAa,IAAI;IACjB,cAAc,IAAI;IAClB,mBAAmB,IAAI;IACvB,iBAAiB,IAAI;EACvB;AACF;AAEA,eAAsB,gBAAgB,IAAY,OAAe,YAA8B;AAC7F,QAAM,QAAQ,MAAM,UAAU,IAAI;AAClC,QAAM,SAAS,MAAM,MAAM;AAC3B,QAAM,OAAkB;IACtB,SAAS;IACT,OAAO,MAAM,MAAM,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE;EAC9C;AACA,MAAI,KAAK,MAAM,WAAW,OAAQ,QAAO;AACzC,QAAM,WAAW,MAAM,IAAI;AAC3B,SAAO;AACT;AAcO,SAAS,QAAQ,UAAkB,OAAiC;AACzE,QAAM,IAAI,SAAS,KAAK;AACxB,MAAI,EAAE,WAAW,EAAG,QAAO,EAAE,MAAM,OAAO;AAE1C,QAAM,UAAU,MAAM,MAAM,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC;AAClD,MAAI,QAAS,QAAO,EAAE,MAAM,MAAM,KAAK,QAAQ;AAE/C,QAAM,gBAAgB,MAAM,MAAM,OAAO,CAAC,MAAM,EAAE,GAAG,WAAW,CAAC,CAAC;AAClE,MAAI,cAAc,WAAW,EAAG,QAAO,EAAE,MAAM,MAAM,KAAK,cAAc,CAAC,EAAG;AAC5E,MAAI,cAAc,SAAS,EAAG,QAAO,EAAE,MAAM,aAAa,SAAS,cAAc;AAEjF,QAAM,SAAS,MAAM,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC;AACnD,MAAI,OAAQ,QAAO,EAAE,MAAM,MAAM,KAAK,OAAO;AAM7C,QAAM,cAAc,MAAM,MAAM,KAAK,CAAC,MAAM,EAAE,cAAc,CAAC;AAC7D,MAAI,YAAa,QAAO,EAAE,MAAM,MAAM,KAAK,YAAY;AAEvD,SAAO,EAAE,MAAM,OAAO;AACxB;AAQO,SAAS,qBAAqB,OAAkB,aAA6B;AAClF,MAAI,MAAM;AACV,aAAW,KAAK,MAAM,OAAO;AAC3B,QAAI,EAAE,gBAAgB,YAAa;AACnC,QAAI,OAAO,EAAE,iBAAiB,YAAY,EAAE,eAAe,KAAK;AAC9D,YAAM,EAAE;IACV;EACF;AACA,SAAO,MAAM;AACf;AAOO,SAAS,mBAAmB,OAAkB,aAAoC;AACvF,QAAM,UAAU,MAAM,MAAM,OAAO,CAAC,MAAM,EAAE,gBAAgB,WAAW;AACvE,MAAI,QAAQ,WAAW,EAAG,QAAO,EAAE,MAAM,OAAO;AAChD,MAAI,QAAQ,WAAW,EAAG,QAAO,EAAE,MAAM,MAAM,KAAK,QAAQ,CAAC,EAAG;AAChE,SAAO,EAAE,MAAM,aAAa,QAAQ;AACtC;AAaO,SAAS,cACd,KACA,OACA,aACe;AACf,MAAI,QAAQ,QAAW;AACrB,QAAI,gBAAgB,OAAW,QAAO,EAAE,MAAM,OAAO;AACrD,WAAO,mBAAmB,OAAO,WAAW;EAC9C;AACA,QAAM,UAAU,IAAI,KAAK;AACzB,MAAI,gBAAgB,UAAa,gBAAgB,KAAK,OAAO,GAAG;AAC9D,UAAM,MAAM,OAAO,SAAS,SAAS,EAAE;AACvC,UAAM,MAAM,MAAM,MAAM;MACtB,CAAC,MAAM,EAAE,gBAAgB,eAAe,EAAE,iBAAiB;IAC7D;AACA,WAAO,MAAM,EAAE,MAAM,MAAM,KAAK,IAAI,IAAI,EAAE,MAAM,OAAO;EACzD;AACA,SAAO,QAAQ,SAAS,KAAK;AAC/B;AC/KA,eAAsB,eAAe,WAA+C;AAClF,QAAM,MAAyB,CAAC;AAChC,MAAI,MAAM,SAASG,OAAK,WAAW,MAAM,CAAC,GAAG;AAC3C,QAAI,KAAK,EAAE,MAAM,QAAQ,cAAc,WAAW,sBAAsB,GAAG,CAAC;EAC9E;AACA,MAAI;AACJ,MAAI;AACF,cAAU,MAAMC,SAAQ,WAAW,EAAE,eAAe,KAAK,CAAC;EAC5D,QAAQ;AACN,WAAO;EACT;AACA,aAAW,KAAK,SAAS;AACvB,QAAI,CAAC,EAAE,YAAY,KAAK,EAAE,KAAK,WAAW,GAAG,EAAG;AAChD,UAAM,MAAMD,OAAK,WAAW,EAAE,IAAI;AAClC,QAAI,MAAM,SAASA,OAAK,KAAK,MAAM,CAAC,GAAG;AACrC,UAAI,KAAK,EAAE,MAAM,UAAU,cAAc,KAAK,sBAAsB,EAAE,KAAK,CAAC;IAC9E;EACF;AACA,SAAO;AACT;AAEA,eAAe,SAAS,MAAgC;AACtD,MAAI;AACF,UAAM,IAAI,MAAME,MAAK,IAAI;AACzB,WAAO,EAAE,YAAY;EACvB,QAAQ;AACN,WAAO;EACT;AACF;AAOA,eAAsB,gBAAgB,cAAsB,MAA+B;AACzF,MAAI,YAAY;AAChB,MAAI,SAAS;AACb,SAAO,MAAM,aAAa,cAAc,SAAS,GAAG;AAClD,gBAAY,GAAG,IAAI,IAAI,OAAO,QAAQ,CAAC;AACvC,QAAI,SAAS,IAAK,OAAM,IAAI,iBAAiB,0CAA0C,IAAI,EAAE;EAC/F;AACA,SAAO;AACT;AAEA,eAAe,aAAa,cAAsB,MAAgC;AAChF,QAAM,SAAS,MAAM;IACnB;IACA,CAAC,MAAM,cAAc,YAAY,YAAY,WAAW,cAAc,IAAI,EAAE;IAC5E,EAAE,QAAQ,MAAM;EAClB;AACA,SAAO,OAAO,aAAa;AAC7B;AAEO,IAAM,mBAAN,cAA+B,MAAM;EAC1C,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;EACd;AACF;;;AlCjFA,SAAS,aAAAE,kBAAiB;AAC1B,SAAS,QAAAC,cAAY;AACrB,SAAS,WAAAC,iBAAe;AACxB,SAAS,QAAAC,cAAY;AACrB,SAAS,SAAAC,cAAa;ACJtB,SAAS,aAAAJ,kBAAiB;AAC1B,SAAS,QAAAC,cAAY;AACrB,SAAS,WAAAC,gBAAe;AACxB,SAAS,QAAAC,aAAY;AACrB,SAAS,SAAAC,cAAa;AEJtB,SAAS,mBAAmB;AGA5B,SAAS,SAAAC,cAAa;ACAtB,SAAS,kBAAkB;AAC3B,SAAS,YAAAC,kBAAgB;AACzB,SAAS,WAAAC,gBAAe;AACxB,SAAS,QAAAC,aAAY;AACrB,SAAS,SAAAH,cAAa;ACJtB,SAAS,SAAAA,cAAa;AACtB,SAAS,cAAAI,mBAAkB;AAC3B,SAAS,qBAAqB;AAC9B,SAAS,WAAAC,UAAS,WAAAC,gBAAe;ACHjC,SAAS,SAAAN,eAAa;AACtB,SAAS,SAAAO,QAAO,WAAAC,WAAS,MAAAC,MAAI,QAAAC,cAAY;AACzC,SAAS,WAAAR,UAAS,gBAAgB;AAClC,SAAS,QAAAC,OAAM,WAAAG,iBAAe;ACH9B,SAAS,SAAAK,QAAO,WAAAC,UAAS,YAAAC,WAAU,WAAAC,WAAS,MAAAC,KAAI,aAAAC,mBAAiB;AACjE,SAAS,WAAAC,UAAS,UAAAC,eAAc;AAChC,SAAS,YAAAC,WAAU,QAAAC,aAAY;AAC/B,SAAS,SAAAC,eAAa;ACHtB,SAAS,QAAAC,aAAY;ACArB,SAAS,SAAAC,eAAa;ACAtB,SAAS,SAAAA,eAAa;ACAtB,SAAS,aAAa;AACtB,SAAS,eAAAC,oBAAmB;AAC5B,SAAS,cAAAC,aAAY,gBAAgB;AACrC,SAAS,SAAAC,QAAO,YAAAC,WAAU,QAAQ,aAAAC,mBAAiB;AACnD,SAAS,WAAW,mBAAmB;AACvC,SAAS,WAAAC,gBAAe;AACxB,SAAS,WAAAC,WAAS,QAAAC,OAAM,WAAAC,gBAAe;AACvC,SAAS,cAAcC,cAAa;AACpC,SAAS,iBAAAC,sBAAqB;;;A4BQ9B,SAAS,SAAAC,cAAa;APhBf,IAAM,qBAAqB;AAC3B,IAAM,uBAAuB;AAC7B,IAAM,qBAAqB;AAC3B,IAAM,kBAAkB;AC2BxB,IAAM,iCAAiC,KAAK,KAAK;AKsCxD,IAAM,iBAAiB,OAAO;;;AzBpE9B,SAAS,SAAAC,eAAa;AACtB,SAAS,WAAAC,UAAS,MAAAC,KAAI,QAAAC,aAAY;AAClC,SAAS,QAAAC,cAAY;;;A4BKrB,IAAM,qBAAoC;EACxC,MAAM;;;;EAIN,UAAU,gBAAgB,UAAU;AAClC,QAAI,CAAC,eAAgB,QAAO,CAAC,GAAG,QAAQ;AACxC,WAAO,CAAC,gBAAgB,GAAG,QAAQ;EACrC;AACF;AAKA,IAAM,gBAA+B;EACnC,MAAM;EACN,UAAU,gBAAgB,UAAU;AAClC,QAAI,CAAC,eAAgB,QAAO,CAAC,GAAG,QAAQ;AACxC,WAAO,CAAC,gBAAgB,GAAG,QAAQ;EACrC;AACF;AAIA,IAAM,mBAAkC;EACtC,MAAM;EACN,UAAU,gBAAgB,UAAU;AAClC,QAAI,CAAC,eAAgB,QAAO,CAAC,GAAG,QAAQ;AACxC,WAAO,CAAC,gBAAgB,GAAG,QAAQ;EACrC;AACF;AAEO,SAAS,qBAAqB,MAAgC;AACnE,MAAI,SAAS,cAAe,QAAO;AACnC,MAAI,SAAS,QAAS,QAAO;AAC7B,MAAI,SAAS,WAAY,QAAO;AAChC,QAAM,IAAI,MAAM,uBAAuB,OAAO,IAAI,CAAC,EAAE;AACvD;AEnCO,IAAM,mBAAN,cAA+B,MAAM;EAC1C,YAA4B,OAAe;AACzC,UAAM,wBAAwB,KAAK,GAAG;AADZ,SAAA,QAAA;AAE1B,SAAK,OAAO;EACd;EAH4B;AAI9B;AAEO,IAAM,oBAAN,cAAgC,MAAM;EAC3C,YACkB,OACA,SAChB;AACA,UAAM,MAAM,QAAQ,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,KAAK,IAAI;AAC9C,UAAM,IAAI,KAAK,6BAA6B,GAAG,EAAE;AAJjC,SAAA,QAAA;AACA,SAAA,UAAA;AAIhB,SAAK,OAAO;EACd;EANkB;EACA;AAMpB;;;A7BzBA,SAAS,SAAAC,eAAa;ACAtB,SAAS,QAAAC,cAAY;ACArB,SAAS,WAAAC,iBAAe;AACxB,SAAS,QAAAC,cAAY;AACrB,SAAS,SAAAC,eAAa;AE4BtB,SAAS,UAAU,WAAAC,UAAS,WAAAC,UAAS,YAAAC,WAAU,MAAAC,KAAI,QAAAC,OAAM,aAAAC,kBAAiB;AAC1E,SAAS,WAAAR,WAAS,UAAAS,eAAc;AAChC,SAAS,YAAAC,YAAU,QAAAT,QAAM,YAAAU,iBAAgB;AACzC,SAAS,SAAAT,eAAa;A3BhBf,SAAS,sBAAsB,SAAiB,UAA2B;AAChF,MAAI,OAAO,YAAY,YAAY,QAAQ,WAAW,EAAG,QAAO;AAChE,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,SAAO,QAAQ,SAAS,WAAW,GAAG;AACxC;AAwBO,SAAS,gBAA6B,MAAS,UAAuC;AAE3F,QAAM,QAAQ,gBAAgB,IAAI;AAClC,QAAM,kBAA4B,CAAC;AAEnC,MAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG;AACvE,WAAO,EAAE,MAAM,OAAY,gBAAgB;EAC7C;AAEA,QAAM,MAAM;AACZ,QAAM,YAAY,IAAI;AACtB,MAAI,cAAc,QAAQ,OAAO,cAAc,YAAY,MAAM,QAAQ,SAAS,GAAG;AACnF,WAAO,EAAE,MAAM,OAAY,gBAAgB;EAC7C;AAEA,aAAW,eAAe,OAAO,KAAK,SAAoC,GAAG;AAC3E,UAAM,eAAgB,UAAsC,WAAW;AACvE,QAAI,CAAC,MAAM,QAAQ,YAAY,EAAG;AAClC,eAAW,SAAS,cAAc;AAChC,UAAI,UAAU,QAAQ,OAAO,UAAU,SAAU;AACjD,YAAM,UAAU;AAChB,YAAM,QAAQ,QAAQ;AACtB,UAAI,CAAC,MAAM,QAAQ,KAAK,EAAG;AAE3B,eAAS,IAAI,MAAM,SAAS,GAAG,KAAK,GAAG,KAAK;AAC1C,cAAM,OAAO,MAAM,CAAC;AACpB,YAAI,SAAS,QAAQ,OAAO,SAAS,SAAU;AAC/C,YACE,KAAK,SAAS,aACd,OAAO,KAAK,YAAY,YACxB,sBAAsB,KAAK,SAAS,QAAQ,GAC5C;AACA,0BAAgB,KAAK,KAAK,OAAO;AACjC,gBAAM,OAAO,GAAG,CAAC;QACnB;MACF;IACF;EACF;AAEA,SAAO,EAAE,MAAM,OAAY,gBAAgB;AAC7C;AAiCO,SAAS,eACd,MACA,eACyB;AACzB,QAAM,QAAQ,gBAAgB,IAAI;AAClC,MAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG;AACvE,WAAO,EAAE,MAAM,OAAY,SAAS,MAAM;EAC5C;AACA,MAAI,cAAc,WAAW,EAAG,QAAO,EAAE,MAAM,OAAY,SAAS,MAAM;AAC1E,QAAM,MAAM;AACZ,MAAI,IAAI,aAAa,QAAQ,OAAO,IAAI,aAAa,YAAY,MAAM,QAAQ,IAAI,QAAQ,GAAG;AAC5F,QAAI,WAAW,CAAC;EAClB;AACA,QAAM,WAAW,IAAI;AACrB,QAAM,WAAW,SAAS,aAAa;AACvC,QAAM,QACJ,aAAa,QAAQ,OAAO,aAAa,YAAY,CAAC,MAAM,QAAQ,QAAQ,IACvE,WACD,CAAC;AACP,MAAI,MAAM,2BAA2B,MAAM;AACzC,aAAS,aAAa,IAAI;AAC1B,WAAO,EAAE,MAAM,OAAY,SAAS,MAAM;EAC5C;AACA,QAAM,yBAAyB;AAC/B,WAAS,aAAa,IAAI;AAC1B,SAAO,EAAE,MAAM,OAAY,SAAS,KAAK;AAC3C;AAsBO,SAAS,gBACd,MACA,UACA,QAC0B;AAC1B,QAAM,QAAQ,gBAAgB,IAAI;AAClC,MAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG;AACvE,WAAO,EAAE,MAAM,OAAY,SAAS,MAAM;EAC5C;AACA,MAAI,aAAa,UAAU,SAAS,WAAW,KAAK,OAAO,WAAW,GAAG;AACvE,WAAO,EAAE,MAAM,OAAY,SAAS,MAAM;EAC5C;AACA,QAAM,MAAM;AACZ,QAAM,WAAW,IAAI;AACrB,MAAI,aAAa,QAAQ,OAAO,aAAa,YAAY,MAAM,QAAQ,QAAQ,GAAG;AAChF,WAAO,EAAE,MAAM,OAAY,SAAS,MAAM;EAC5C;AACA,QAAM,cAAc;AACpB,QAAM,MAAM,YAAY,QAAQ;AAChC,MAAI,QAAQ,QAAQ,OAAO,QAAQ,YAAY,MAAM,QAAQ,GAAG,GAAG;AACjE,WAAO,EAAE,MAAM,OAAY,SAAS,MAAM;EAC5C;AACA,QAAM,WAAW,YAAY,MAAM;AACnC,MAAI,aAAa,QAAQ,OAAO,aAAa,YAAY,CAAC,MAAM,QAAQ,QAAQ,GAAG;AACjF,gBAAY,MAAM,IAAI,EAAE,GAAI,UAAsC,GAAI,IAAgC;EACxG,OAAO;AACL,gBAAY,MAAM,IAAI,gBAAgB,GAAG;EAC3C;AACA,SAAO,EAAE,MAAM,OAAY,SAAS,KAAK;AAC3C;AAkBO,SAAS,uBAAoC,MAA0C;AAC5F,QAAM,QAAQ,gBAAgB,IAAI;AAClC,MAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG;AACvE,WAAO,EAAE,MAAM,OAAY,SAAS,MAAM;EAC5C;AACA,QAAM,MAAM;AACZ,QAAM,UACJ,IAAI,kBAAkB,YACtB,IAAI,gBAAgB,SACpB,IAAI,kCAAkC;AACxC,MAAI,gBAAgB;AACpB,MAAI,cAAc;AAClB,MAAI,gCAAgC;AACpC,SAAO,EAAE,MAAM,OAAY,SAAS,QAAQ;AAC9C;AC1MO,IAAM,yBAAyB,CAAC,WAAW;AAG3C,IAAM,2BAA2B;AAQjC,SAAS,aACd,UACA,WACA,kBAAqC,CAAC,GAC5B;AACV,QAAM,OAAO,IAAI,IAAI,SAAS;AAC9B,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,MAAgB,CAAC;AACvB,aAAW,QAAQ,UAAU;AAC3B,QAAI,KAAK,WAAW,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,IAAI,IAAI,EAAG;AAC3D,QAAI,gBAAgB,KAAK,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,EAAG;AACrD,SAAK,IAAI,IAAI;AACb,QAAI,KAAK,IAAI;EACf;AACA,SAAO,IAAI,KAAK;AAClB;AAQA,SAAS,mBAAsB,OAAU,mBAA8B;AACrE,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,MAAM,MAAM,wBAAwB,EAAE,KAAK,iBAAiB;EACrE;AACA,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,MAAM,IAAI,CAAC,MAAM,mBAAmB,GAAG,iBAAiB,CAAC;EAClE;AACA,MAAI,UAAU,QAAQ,OAAO,UAAU,UAAU;AAC/C,UAAM,MAA+B,CAAC;AACtC,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,KAAK,GAAG;AAC1C,UAAI,CAAC,IAAI,mBAAmB,GAAG,iBAAiB;IAClD;AACA,WAAO;EACT;AACA,SAAO;AACT;AAEA,SAASU,eAAc,GAA0C;AAC/D,SAAO,MAAM,QAAQ,OAAO,MAAM,YAAY,CAAC,MAAM,QAAQ,CAAC;AAChE;AAsBA,SAAS,cACP,UACA,SACA,mBACA,WACA,SACa;AACb,QAAM,UAAU,UAAU,QAAQ;AAClC,QAAM,SAAS,UAAU,OAAO;AAChC,MAAI,CAACA,eAAc,MAAM,GAAG;AAC1B,WAAO,EAAE,MAAM,UAAU,SAAS,OAAO,WAAW,CAAC,EAAE;EACzD;AACA,QAAM,OAAgCA,eAAc,OAAO,IAAI,EAAE,GAAG,QAAQ,IAAI,CAAC;AACjF,QAAM,YAAsB,CAAC;AAC7B,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,QAAI,OAAO,UAAU,eAAe,KAAK,MAAM,GAAG,EAAG;AACrD,SAAK,GAAG,IAAI,mBAAmB,OAAO,iBAAiB;AACvD,cAAU,KAAK,GAAG;EACpB;AACA,MAAI,UAAU,WAAW,GAAG;AAC1B,WAAO,EAAE,MAAM,UAAU,SAAS,OAAO,WAAW,CAAC,EAAE;EACzD;AACA,SAAO,EAAE,MAAM,QAAQ,UAAU,IAAI,GAAG,SAAS,MAAM,WAAW,UAAU,KAAK,EAAE;AACrF;AAOO,SAAS,uBACd,UACA,SACA,MACa;AACb,QAAM,SAAS,GAAG,KAAK,QAAQ;AAC/B,SAAO;IACLA,eAAc,QAAQ,IAAI,WAAW,CAAC;IACtC;IACA;IACA,CAAC,SAAS;IACV,CAAC,OAAO,WAAW;EACrB;AACF;AAQO,SAAS,sBACd,UACA,SACA,MACa;AACb,QAAM,SAAS,GAAG,KAAK,QAAQ;AAC/B,QAAM,WAAWA,eAAc,QAAQ,IAAI,WAAW,EAAE,SAAS,CAAC,EAAE;AACpE,SAAO;IACL;IACA;IACA;IACA,CAAC,SAAUA,eAAc,IAAI,IAAK,KAAiC,SAAS,IAAI;IAChF,CAAC,MAAM,YAAY,EAAE,GAAI,MAAkC,SAAS,OAAO;EAC7E;AACF;AAgBO,SAAS,4BAA4B,sBAA4C;AACtF,QAAM,OAAO,oBAAI,IAAY;AAC7B,MAAI,CAACA,eAAc,oBAAoB,EAAG,QAAO;AACjD,QAAM,UAAU,qBAAqB,SAAS;AAC9C,MAAI,CAACA,eAAc,OAAO,EAAG,QAAO;AACpC,aAAW,WAAW,OAAO,OAAO,OAAO,GAAG;AAC5C,QAAI,CAAC,MAAM,QAAQ,OAAO,EAAG;AAC7B,eAAW,SAAS,SAAS;AAC3B,UAAI,CAACA,eAAc,KAAK,EAAG;AAC3B,YAAM,cAAc,MAAM,aAAa;AACvC,UAAI,OAAO,gBAAgB,SAAU;AACrC,YAAM,WAAW,YAAY,MAAM,GAAG,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAClE,UAAI,SAAS,SAAS,EAAG;AACzB,WAAK,IAAI,SAAS,MAAM,EAAE,EAAE,KAAK,GAAG,CAAC;IACvC;EACF;AACA,SAAO;AACT;AC5LA,eAAsB,aAA4B;AAChD,QAAM,SAAiB,MAAMV,OAAM,UAAU,CAAC,MAAM,GAAG,EAAE,QAAQ,MAAM,CAAC;AACxE,MAAI,OAAO,aAAa,GAAG;AACzB,UAAM,IAAI;MACR,4BAA4B,OAAO,OAAO,QAAQ,CAAC;EAAqC,OAAO,OAAO,MAAM,CAAC;IAC/G;EACF;AACF;AA4BA,eAAsB,OAAO,MAAmC;AAC9D,QAAM,OAAiB;IACrB;IACA;IACA;IACA,KAAK;IACL;IACA,KAAK;IACL;;;;;;;IAOA;;;;;;;;IAQA;IACA;IACA;;;;;;;IAOA;;;;;IAKA;EACF;AACA,QAAM,MAAM,KAAK;AACjB,MAAI,KAAK;AACP,QAAI,IAAI,eAAe,IAAI,cAAc,GAAG;AAC1C,WAAK,KAAK,YAAY,OAAO,KAAK,MAAM,IAAI,WAAW,CAAC,CAAC;IAC3D;AACA,QAAI,IAAI,QAAQ,IAAI,OAAO,GAAG;AAC5B,WAAK,KAAK,UAAU,OAAO,IAAI,IAAI,CAAC;IACtC;AACA,QAAI,IAAI,aAAa,IAAI,YAAY,GAAG;AACtC,WAAK,KAAK,gBAAgB,OAAO,KAAK,MAAM,IAAI,SAAS,CAAC,CAAC;IAC7D;AAGA,QAAI,IAAI,MAAM;AACZ,WAAK,KAAK,iBAAiB,QAAQ,IAAI,IAAI,EAAE;IAC/C;EACF;AACA,aAAW,KAAK,KAAK,gBAAgB,CAAC,GAAG;AACvC,SAAK,KAAK,MAAM,CAAC;EACnB;AACA,aAAW,MAAM,KAAK,gBAAgB,CAAC,GAAG;AACxC,UAAM,OAAO,GAAG,SAAS,GAAG,GAAG,MAAM,IAAI,OAAO,GAAG,QAAQ,CAAC,KAAK,OAAO,GAAG,QAAQ;AACnF,SAAK,KAAK,MAAM,GAAG,IAAI,IAAI,OAAO,GAAG,aAAa,CAAC,EAAE;EACvD;AACA,aAAW,CAAC,GAAG,GAAG,KAAK,OAAO,QAAQ,KAAK,OAAO,CAAC,CAAC,GAAG;AACrD,SAAK,KAAK,MAAM,GAAG,CAAC,IAAI,GAAG,EAAE;EAC/B;AACA,OAAK,KAAK,KAAK,OAAO,SAAS,UAAU;AAEzC,QAAM,EAAE,OAAO,IAAI,MAAMA,OAAM,UAAU,IAAI;AAC7C,SAAO,OAAO,KAAK;AACrB;AAOA,eAAsB,sBAAuC;AAC3D,QAAM,SAAS,MAAMA,OAAM,UAAU,CAAC,QAAQ,YAAY,aAAa,GAAG,EAAE,QAAQ,MAAM,CAAC;AAC3F,MAAI,OAAO,aAAa,EAAG,QAAO;AAClC,UAAQ,OAAO,UAAU,IAAI,KAAK;AACpC;AAEA,eAAsB,UACpB,WACA,KACA,OAAgE,CAAC,GACtC;AAC3B,QAAM,OAAiB,CAAC,MAAM;AAC9B,MAAI,KAAK,OAAQ,MAAK,KAAK,IAAI;AAC/B,MAAI,KAAK,KAAM,MAAK,KAAK,UAAU,KAAK,IAAI;AAC5C,OAAK,KAAK,WAAW,GAAG,GAAG;AAC3B,QAAM,SAAS,MAAMA,OAAM,UAAU,MAAM;IACzC,QAAQ;IACR,GAAI,KAAK,YAAY,EAAE,SAAS,KAAK,UAAU,IAAI,CAAC;EACtD,CAAC;AACD,SAAO;IACL,UAAU,OAAO,YAAY;IAC7B,QAAQ,OAAO,UAAU;IACzB,QAAQ,OAAO,UAAU;EAC3B;AACF;AAEA,eAAsB,gBAAgB,WAAkC;AACtE,QAAMA,OAAM,UAAU,CAAC,MAAM,MAAM,SAAS,GAAG,EAAE,QAAQ,MAAM,CAAC;AAClE;AAEA,eAAsB,aAAa,MAA6B;AAC9D,QAAMA,OAAM,UAAU,CAAC,UAAU,MAAM,IAAI,GAAG,EAAE,QAAQ,MAAM,CAAC;AACjE;AAQA,eAAsB,YACpB,KACA,OAA4B,CAAC,GACX;AAClB,QAAM,OAAO,CAAC,SAAS,IAAI;AAC3B,MAAI,KAAK,UAAU,MAAO,MAAK,KAAK,IAAI;AACxC,OAAK,KAAK,GAAG;AACb,QAAM,SAAS,MAAMA,OAAM,UAAU,MAAM,EAAE,QAAQ,MAAM,CAAC;AAC5D,SAAO,OAAO,aAAa;AAC7B;AAEA,eAAsB,gBAAgB,MAAgC;AACpE,QAAM,SAAS,MAAMA,OAAM,UAAU,CAAC,aAAa,WAAW,YAAY,WAAW,IAAI,GAAG;IAC1F,QAAQ;EACV,CAAC;AACD,SAAO,OAAO,aAAa;AAC7B;AAEA,eAAsB,aAAa,MAAgC;AACjE,QAAM,SAAS,MAAMA,OAAM,UAAU,CAAC,UAAU,WAAW,IAAI,GAAG,EAAE,QAAQ,MAAM,CAAC;AACnF,SAAO,OAAO,aAAa;AAC7B;AAEA,eAAsB,aAAa,MAA6B;AAC9D,MAAI,MAAM,aAAa,IAAI,EAAG;AAC9B,QAAMA,OAAM,UAAU,CAAC,UAAU,UAAU,IAAI,CAAC;AAClD;AAYA,eAAsB,cAAc,MAA6B;AAC/D,QAAMA,OAAM,UAAU,CAAC,WAAW,MAAM,IAAI,GAAG,EAAE,QAAQ,MAAM,CAAC;AAClE;AAMA,eAAsB,eAAe,MAA6B;AAChE,QAAMA,OAAM,UAAU,CAAC,SAAS,IAAI,CAAC;AACvC;AAEA,eAAsB,iBAAiB,MAA6B;AAClE,QAAMA,OAAM,UAAU,CAAC,WAAW,IAAI,CAAC;AACzC;AAEA,eAAsB,cAAc,MAA6B;AAC/D,QAAMA,OAAM,UAAU,CAAC,QAAQ,IAAI,CAAC;AACtC;AAEA,eAAsB,eAAe,MAA6B;AAChE,QAAMA,OAAM,UAAU,CAAC,SAAS,IAAI,CAAC;AACvC;AAEA,eAAsB,uBAAuB,MAA8C;AACzF,QAAM,SAAS,MAAMA,OAAM,UAAU,CAAC,WAAW,YAAY,qBAAqB,IAAI,GAAG;IACvF,QAAQ;EACV,CAAC;AACD,MAAI,OAAO,aAAa,EAAG,QAAO;AAClC,QAAM,UAAU,OAAO,UAAU,IAAI,KAAK;AAC1C,UAAQ,QAAQ;IACd,KAAK;AACH,aAAO;IACT,KAAK;AACH,aAAO;IACT,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;IACL,KAAK;AACH,aAAO;IACT;AACE,aAAO;EACX;AACF;AAEA,eAAsB,iBAAiB,MAAuC;AAC5E,QAAM,SAAS,MAAMA,OAAM,UAAU,CAAC,WAAW,IAAI,GAAG,EAAE,QAAQ,MAAM,CAAC;AACzE,MAAI,OAAO,aAAa,EAAG,QAAO;AAClC,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,OAAO,UAAU,MAAM;AACjD,WAAO,MAAM,QAAQ,MAAM,IAAK,OAAO,CAAC,KAAK,OAAQ;EACvD,QAAQ;AACN,WAAO;EACT;AACF;AAEA,eAAsB,wBAAwB,MAAsC;AAClF,QAAM,SAAS,MAAMA,OAAM,UAAU,CAAC,UAAU,WAAW,YAAY,mBAAmB,IAAI,GAAG;IAC/F,QAAQ;EACV,CAAC;AACD,MAAI,OAAO,aAAa,EAAG,QAAO;AAClC,UAAQ,OAAO,UAAU,IAAI,KAAK,KAAK;AACzC;AAEA,IAAM,kBAAkB;AAExB,eAAsB,yBAA4C;AAChE,QAAM,SAAS,MAAMA;IACnB;IACA,CAAC,MAAM,MAAM,YAAY,SAAS,eAAe,IAAI,YAAY,YAAY;IAC7E,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,OAAO,aAAa,EAAG,QAAO,CAAC;AACnC,UAAQ,OAAO,UAAU,IACtB,MAAM,IAAI,EACV,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,CAAC,MAAM,EAAE,WAAW,eAAe,CAAC;AAChD;AAQA,eAAsB,kBACpB,WACA,eACwB;AACxB,QAAM,SAAS,MAAMA,OAAM,UAAU,CAAC,QAAQ,WAAW,GAAG,OAAO,aAAa,CAAC,MAAM,GAAG;IACxF,QAAQ;EACV,CAAC;AACD,MAAI,OAAO,aAAa,EAAG,QAAO;AAClC,QAAM,SAAS,OAAO,UAAU,IAAI,MAAM,IAAI,EAAE,CAAC,GAAG,KAAK;AACzD,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,IAAI,UAAU,KAAK,KAAK;AAC9B,SAAO,IAAI,OAAO,EAAE,CAAC,CAAC,IAAI;AAC5B;AAEA,eAAsB,sBAAyC;AAC7D,QAAM,SAAS,MAAMA;IACnB;IACA,CAAC,UAAU,MAAM,YAAY,SAAS,eAAe,IAAI,YAAY,WAAW;IAChF,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,OAAO,aAAa,EAAG,QAAO,CAAC;AACnC,UAAQ,OAAO,UAAU,IACtB,MAAM,IAAI,EACV,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,CAAC,MAAM,EAAE,WAAW,eAAe,CAAC;AAChD;AC3SO,IAAM,0BAA0B;AASvC,IAAI,eAAoC;AAOxC,eAAsB,eAAsC;AAC1D,MAAI,iBAAiB,KAAM,QAAO;AAClC,QAAM,SAAS,MAAMA,QAAM,UAAU,CAAC,QAAQ,YAAY,sBAAsB,GAAG;IACjF,QAAQ;EACV,CAAC;AACD,QAAM,MAAM,OAAO,UAAU,IAAI,KAAK,EAAE,YAAY;AACpD,MAAI,GAAG,SAAS,UAAU,EAAG,gBAAe;WACnC,GAAG,SAAS,gBAAgB,EAAG,gBAAe;MAClD,gBAAe;AACpB,SAAO;AACT;AAUO,SAAS,kBAAkB,QAAmC;AACnE,iBAAe;AACjB;AAeA,eAAsB,mBAAgD;AACpE,QAAM,SAAS,MAAMA,QAAM,UAAU,CAAC,WAAW,MAAM,GAAG,EAAE,QAAQ,MAAM,CAAC;AAC3E,MAAI,OAAO,aAAa,EAAG,QAAO;AAClC,QAAM,OAAO,OAAO,UAAU,IAAI,KAAK;AACvC,SAAO,IAAI,SAAS,IAAI,MAAM;AAChC;AAEO,IAAM,aAAaD,MAAKD,SAAQ,GAAG,aAAa,OAAO;AAgBvD,SAAS,cAAc,KAAwB;AACpD,QAAM,WAAW,iBAAiB,IAAI,IAAI;AAC1C,QAAM,IAAI,IAAI;AACd,MAAI,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC,KAAK,IAAI,GAAG;AACxD,WAAO,GAAG,IAAI,EAAE,IAAI,OAAO,CAAC,CAAC,IAAI,QAAQ;EAC3C;AACA,SAAO,GAAG,IAAI,EAAE,IAAI,QAAQ;AAC9B;AAEO,SAAS,aAAa,KAAwB;AACnD,SAAOC,MAAK,YAAY,cAAc,GAAG,CAAC;AAC5C;AAQO,SAAS,iBAAiB,KAAwB;AACvD,SAAOA,MAAK,aAAa,GAAG,GAAG,aAAa;AAC9C;AAOA,eAAsB,cAAc,KAA2C;AAC7E,MAAI;AACF,UAAM,MAAM,MAAMI,UAAS,iBAAiB,GAAG,GAAG,MAAM;AACxD,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAI,OAAO,WAAW,EAAG,QAAO;AAChC,WAAO;EACT,QAAQ;AACN,WAAO;EACT;AACF;AAWO,SAAS,mBAAmB,WAAmB,KAAuB;AAC3E,SAAOJ,MAAKD,SAAQ,GAAG,YAAY,UAAU,WAAW,QAAQ,GAAG,GAAG;AACxE;AAEA,eAAsB,aACpB,QACoB;AACpB,QAAM,SAAS,aAAa,MAAM;AAClC,SAAO;IACL;IACA,cAAcC,MAAK,QAAQ,WAAW;EACxC;AACF;AAgBA,eAAe,iBAAiB,WAAmB,MAAgC;AACjF,QAAM,QAAQ,MAAM,UAAU,WAAW,CAAC,QAAQ,MAAM,IAAI,GAAG,EAAE,MAAM,OAAO,CAAC;AAC/E,SAAO,MAAM,aAAa;AAC5B;AAUA,eAAsB,cACpB,QACA,OAAuB,CAAC,GACA;AACxB,QAAM,QAAQ,MAAM,aAAa,MAAM;AACvC,QAAM,qBAAqB,CAAC,KAAK;AACjC,QAAMY,OAAM,MAAM,cAAc,EAAE,WAAW,KAAK,CAAC;AAEnD,QAAM,gBAAgB,MAAM,iBAAiB,OAAO,WAAW,uBAAuB;AACtF,MAAI,eAAe;AACjB,UAAM,OAAO,CAAC,SAAS,MAAM,UAAU;AACvC,QAAI,mBAAoB,MAAK,KAAK,wBAAwB;AAC1D,SAAK,KAAK,eAAe,GAAG,uBAAuB,GAAG;AACtD,UAAM,IAAI,MAAM,UAAU,OAAO,WAAW,MAAM,EAAE,MAAM,OAAO,CAAC;AAClE,QAAI,EAAE,aAAa,GAAG;AACpB,YAAM,IAAI,YAAY,cAAc,uBAAuB,WAAW,EAAE,QAAQ,EAAE,MAAM;IAC1F;AACA,WAAO,EAAE,UAAU,MAAM,cAAc,QAAQ,MAAM,cAAc,MAAM;EAC3E;AAKA,QAAM,WAAW,qBAAqB,CAAC,wBAAwB,IAAI,CAAC;AACpE,QAAM,SAAS,MAAMX;IACnB;IACA,CAAC,QAAQ,UAAU,QAAQ,OAAO,WAAW,OAAO,OAAO,KAAK,GAAG,UAAU,MAAM,cAAc,GAAG;IACpG,EAAE,QAAQ,OAAO,UAAU,SAAS;EACtC;AACA,MAAI,OAAO,aAAa,GAAG;AACzB,UAAM,IAAI;MACR;MACA;MACA,OAAO,OAAO,WAAW,WAAW,OAAO,SAAU,OAAO,OAAkB,SAAS,MAAM;IAC/F;EACF;AACA,QAAM,UAAU,MAAMA,QAAM,OAAO,CAAC,OAAO,KAAK,MAAM,MAAM,YAAY,GAAG;IACzE,OAAO,OAAO;IACd,QAAQ;EACV,CAAC;AACD,MAAI,QAAQ,aAAa,GAAG;AAC1B,UAAM,IAAI,YAAY,8BAA8B,QAAQ,QAAQ,QAAQ,MAAM;EACpF;AACA,SAAO,EAAE,UAAU,MAAM,cAAc,QAAQ,MAAM,cAAc,KAAK;AAC1E;AASO,IAAM,uBAAuB;EAClC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AACF;AAGA,IAAM,iBAAiB;EACrB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AACF;AAQA,SAAS,iBAAiB,UAA8B;AACtD,QAAM,YAAY,CAAC,UAA8B;AAC/C,UAAM,MAAgB,CAAC;AACvB,UAAM,QAAQ,CAAC,GAAG,MAAM;AACtB,UAAI,IAAI,EAAG,KAAI,KAAK,IAAI;AACxB,UAAI,KAAK,SAAS,CAAC;IACrB,CAAC;AACD,WAAO;EACT;AACA,SAAO;IACL;IACA;IACA;IACA;IACA;IACA;IACA,GAAG,UAAU,cAAc;IAC3B;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA,GAAG,UAAU,QAAQ;IACrB;IACA;IACA;IACA;EACF;AACF;AASO,SAAS,qBAAqB,UAA8B;AACjE,QAAM,YAAY,CAAC,UAA8B;AAC/C,UAAM,MAAgB,CAAC;AACvB,UAAM,QAAQ,CAAC,GAAG,MAAM;AACtB,UAAI,IAAI,EAAG,KAAI,KAAK,IAAI;AACxB,UAAI,KAAK,SAAS,CAAC;IACrB,CAAC;AACD,WAAO;EACT;AACA,SAAO;IACL;IACA;IACA;IACA;IACA;IACA;IACA,GAAG,UAAU,cAAc;IAC3B;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA,GAAG,UAAU,QAAQ;IACrB;IACA;IACA;EACF;AACF;AAuBA,eAAsB,sBACpB,MAC6B;AAC7B,QAAM,MAAM,KAAK,UAAU,MAAM;EAAC;AAKlC,QAAM,QAAQ,MAAMA,QAAM,QAAQ,qBAAqB,KAAK,QAAQ,EAAE,MAAM,CAAC,GAAG;IAC9E,KAAK,KAAK;IACV,QAAQ;EACV,CAAC;AACD,MAAI,MAAM,aAAa,GAAG;AACxB,QAAI,kCAAkC,OAAO,MAAM,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,EAAE;AAC1E,WAAO,EAAE,QAAQ,EAAE;EACrB;AACA,QAAM,OAAO,OAAO,MAAM,MAAM,EAC7B,MAAM,IAAI,EACV,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAC7B,MAAI,KAAK,WAAW,EAAG,QAAO,EAAE,QAAQ,EAAE;AAG1C,QAAM,SAAS,MAAMA,QAAM,OAAO,CAAC,MAAM,KAAK,cAAc,UAAU,MAAM,KAAK,OAAO,GAAG,GAAG;IAC5F,OAAO,KAAK,KAAK,IAAI;IACrB,UAAU;IACV,QAAQ;EACV,CAAC;AACD,MAAI,OAAO,aAAa,GAAG;AACzB,QAAI,sCAAsC,OAAO,OAAO,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,EAAE;AAC/E,WAAO,EAAE,QAAQ,EAAE;EACrB;AACA,QAAM,UAAU,MAAMA;IACpB;IACA,CAAC,QAAQ,MAAM,UAAU,aAAa,KAAK,WAAW,OAAO,OAAO,KAAK,MAAM,YAAY;IAC3F,EAAE,OAAO,OAAO,QAAkB,QAAQ,MAAM;EAClD;AACA,MAAI,QAAQ,aAAa,GAAG;AAC1B,QAAI,2CAA2C,OAAO,QAAQ,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,EAAE;AACrF,WAAO,EAAE,QAAQ,EAAE;EACrB;AACA,SAAO,EAAE,QAAQ,KAAK,OAAO;AAC/B;AASA,eAAsB,iBACpB,cACA,UACmB;AACnB,MAAI,SAAS,WAAW,EAAG,QAAO,CAAC;AACnC,QAAM,QAAQ,MAAMA,QAAM,QAAQ,qBAAqB,QAAQ,EAAE,MAAM,CAAC,GAAG;IACzE,KAAK;IACL,QAAQ;EACV,CAAC;AACD,MAAI,MAAM,aAAa,EAAG,QAAO,CAAC;AAClC,SAAO,OAAO,MAAM,MAAM,EACvB,MAAM,IAAI,EACV,IAAI,CAAC,MAAM,EAAE,QAAQ,SAAS,EAAE,CAAC,EACjC,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAC/B;AAmBA,eAAsB,mBACpB,MAC6B;AAC7B,QAAM,MAAM,KAAK,UAAU,MAAM;EAAC;AAGlC,QAAM,OAAO,KAAK,MAAM,IAAI,CAAC,MAAM,EAAE,QAAQ,SAAS,EAAE,CAAC,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AACrF,MAAI,KAAK,WAAW,EAAG,QAAO,EAAE,QAAQ,EAAE;AAE1C,QAAM,SAAS,MAAMA,QAAM,OAAO,CAAC,MAAM,KAAK,cAAc,UAAU,MAAM,KAAK,OAAO,GAAG,GAAG;IAC5F,OAAO,KAAK,KAAK,IAAI;IACrB,UAAU;IACV,QAAQ;EACV,CAAC;AACD,MAAI,OAAO,aAAa,GAAG;AACzB,QAAI,sCAAsC,OAAO,OAAO,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,EAAE;AAC/E,WAAO,EAAE,QAAQ,EAAE;EACrB;AACA,QAAM,UAAU,MAAMA;IACpB;IACA,CAAC,QAAQ,MAAM,UAAU,aAAa,KAAK,WAAW,OAAO,OAAO,KAAK,MAAM,YAAY;IAC3F,EAAE,OAAO,OAAO,QAAkB,QAAQ,MAAM;EAClD;AACA,MAAI,QAAQ,aAAa,GAAG;AAC1B,QAAI,2CAA2C,OAAO,QAAQ,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,EAAE;AACrF,WAAO,EAAE,QAAQ,EAAE;EACrB;AACA,SAAO,EAAE,QAAQ,KAAK,OAAO;AAC/B;AAwCA,SAAS,qBAAqB,QAA0B;AACtD,SAAO,OACJ,MAAM,IAAI,EACV,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,EACtB,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC,EAC1B,OAAO,CAAC,MAAM;AACb,UAAM,OAAO,EAAE,CAAC;AAChB,UAAM,OAAO,EAAE,CAAC;AAChB,YAAQ,SAAS,OAAO,SAAS,OAAO,SAAS,OAAO,SAAS,QAAQ,SAAS;EACpF,CAAC;AACL;AAmBA,eAAsB,WACpB,QACA,OAAoB,CAAC,GACA;AACrB,QAAM,QAAQ,MAAM,aAAa,MAAM;AAEvC,MAAI;AACJ,MAAI,KAAK,WAAW;AAClB,iBAAa,MAAM;AACnB,UAAMW,OAAM,YAAY,EAAE,WAAW,KAAK,CAAC;EAC7C,OAAO;AACL,UAAM,YAAY,MAAM,cAAc,QAAQ;MAC5C,oBAAoB,KAAK;IAC3B,CAAC;AACD,iBAAa,UAAU;EACzB;AAMA,QAAM,WAAqB,CAAC;AAC5B,MAAI,gBAAgB;AACpB,MAAI,KAAK,qBAAqB,OAAO;AACnC,UAAM,QAAQ,MAAM;MAClB,OAAO;MACP,CAAC,OAAO,MAAM,cAAc,aAAa,uBAAuB;MAChE,EAAE,MAAM,OAAO;IACjB;AACA,QAAI,MAAM,aAAa,KAAK,MAAM,OAAO,KAAK,MAAM,QAAQ;AAC1D,YAAM,KAAK,MAAM;QACf,OAAO;QACP,CAAC,OAAO,MAAM,cAAc,YAAY,MAAM,YAAY,YAAY,oBAAoB;QAC1F,EAAE,MAAM,OAAO;MACjB;AACA,UAAI,GAAG,aAAa,GAAG;AACrB,cAAM,IAAI,YAAY,8BAA8B,GAAG,QAAQ,GAAG,MAAM;MAC1E;AAEA,YAAM,UAAU,GAAG,OAAO,QAAQ,OAAO,EAAE;AAC3C,UAAI,QAAQ,SAAS,EAAG,UAAS,KAAK,OAAO;AAC7C,sBAAgB;IAClB;EACF;AACA,MAAI,KAAK,eAAe,KAAK,YAAY,SAAS,GAAG;AACnD,UAAM,QAAQ,MAAM;MAClB,OAAO;MACP,iBAAiB,KAAK,WAAW;MACjC,EAAE,MAAM,OAAO;IACjB;AACA,QAAI,MAAM,aAAa,GAAG;AACxB,YAAM,IAAI,YAAY,gCAAgC,MAAM,QAAQ,MAAM,MAAM;IAClF;AACA,UAAM,WAAW,MAAM,OAAO,QAAQ,OAAO,EAAE;AAC/C,QAAI,SAAS,SAAS,EAAG,UAAS,KAAK,QAAQ;EACjD;AACA,QAAM,WACJ,SAAS,SAAS,IACd,MAAM,KAAK,IAAI,IAAI,SAAS,KAAK,IAAI,EAAE,MAAM,IAAI,CAAC,CAAC,EAAE,KAAK,IAAI,IAC9D;AAON,QAAM,WAAW,CAAC,MAAM,YAAY;AACpC,MAAI,aAAa,MAAM;AACrB,aAAS,KAAK,gBAAgB;AAC9B,QAAI,CAAC,KAAK,mBAAoB,UAAS,KAAK,wBAAwB;EACtE,OAAO;AACL,aAAS,KAAK,kBAAkB,SAAS;EAC3C;AACA,QAAM,MAAM,GAAG,UAAU;AACzB,QAAM,MAAM,GAAG,OAAO,aAAa;AAEnC,QAAM,MAAM,MAAMX,QAAM,SAAS,CAAC,GAAG,UAAU,aAAa,MAAM,KAAK,GAAG,GAAG;IAC3E,QAAQ;IACR,OAAO,aAAa,OAAO,WAAW;EACxC,CAAC;AACD,MAAI,IAAI,aAAa,GAAG;AACtB,UAAM,IAAI,YAAY,wBAAwB,IAAI,QAAQ,IAAI,MAAM;EACtE;AACA,QAAM,UAAU,qBAAqB,IAAI,MAAM;AAE/C,MAAI,KAAK,QAAQ;AACf,WAAO,EAAE,UAAU,OAAO,eAAe,SAAS,SAAS,OAAO,cAAc;EAClF;AAEA,QAAM,OAAO,MAAMA,QAAM,SAAS,CAAC,GAAG,UAAU,KAAK,GAAG,GAAG;IACzD,QAAQ;IACR,OAAO,aAAa,OAAO,WAAW;EACxC,CAAC;AACD,MAAI,KAAK,aAAa,GAAG;AACvB,UAAM,IAAI,YAAY,cAAc,OAAO,aAAa,WAAW,KAAK,QAAQ,KAAK,MAAM;EAC7F;AACA,SAAO,EAAE,UAAU,OAAO,eAAe,SAAS,SAAS,MAAM,cAAc;AACjF;AAwBA,eAAsB,aACpB,QACA,MACqB;AACrB,QAAM,SAAS,MAAM,aAAa;AAClC,MAAI;AACJ,MAAI,SAAS;AACb,MAAI,eAAe;AAEnB,MAAI,KAAK,WAAW;AAClB,UAAM,QAAQ,MAAM,aAAa,MAAM;AACvC,eAAW,MAAM;AACjB,UAAMW,OAAM,UAAU,EAAE,WAAW,KAAK,CAAC;EAC3C,OAAO;AACL,UAAM,YAAY,MAAM,cAAc,QAAQ,IAAI;AAClD,eAAW,UAAU;AACrB,aAAS,UAAU;AACnB,mBAAe,UAAU;EAC3B;AAEA,MAAI,CAAC,KAAK,QAAQ;AAChB,UAAM,SAAS,MAAMX,QAAM,QAAQ,CAAC,QAAQ,GAAG,EAAE,QAAQ,MAAM,CAAC;AAChE,QAAI,OAAO,aAAa,GAAG;AACzB,YAAM,IAAI,YAAY,QAAQ,QAAQ,WAAW,OAAO,QAAQ,OAAO,MAAM;IAC/E;EACF;AAEA,SAAO,EAAE,UAAU,QAAQ,cAAc,OAAO;AAClD;AAEO,IAAM,cAAN,cAA0B,MAAM;EACrC,YACE,SACgB,QACA,QAChB;AACA,UAAM,GAAG,OAAO,GAAG,SAAS,KAAK,OAAO,KAAK,CAAC,KAAK,EAAE,EAAE;AAHvC,SAAA,SAAA;AACA,SAAA,SAAA;AAGhB,SAAK,OAAO;EACd;EALkB;EACA;AAKpB;AJ9pBO,IAAM,uBAAuB;AAC7B,IAAM,yBAAyB;AACtC,IAAM,uBAAuB;AACtB,IAAM,iBAAiB;AAE9B,IAAM,sBAAsB;AAO5B,IAAM,0BAA0B;AAEhC,IAAM,kBAAkB;AAOjB,SAAS,oBAAoB,MAA6D;AAC/F,MAAI,KAAK,SAAS;AAChB,WAAO,EAAE,QAAQ,GAAG,oBAAoB,IAAI,KAAK,KAAK,GAAG;EAC3D;AACA,SAAO,EAAE,QAAQ,qBAAqB;AACxC;AAqDA,eAAe,WAAW,GAA6B;AACrD,MAAI;AACF,UAAMK,MAAK,CAAC;AACZ,WAAO;EACT,QAAQ;AACN,WAAO;EACT;AACF;AAWA,eAAe,oBAAoB,QAAgB,OAAiC;AAClF,QAAM,MAAM,MAAML;IAChB;IACA,CAAC,OAAO,QAAQ,MAAM,GAAG,MAAM,SAAS,OAAO,MAAM,MAAM,2BAA2B;IACtF,EAAE,QAAQ,MAAM;EAClB;AACA,SAAO,IAAI,aAAa;AAC1B;AAWA,eAAe,mBAAmB,MAAiC;AACjE,QAAM,SAAmB,CAAC;AAC1B,iBAAe,KAAK,KAA4B;AAC9C,QAAI;AACJ,QAAI;AACF,gBAAU,MAAME,SAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;IACtD,QAAQ;AACN;IACF;AACA,eAAW,OAAO,SAAS;AACzB,YAAM,OAAOH,OAAK,KAAK,IAAI,IAAI;AAC/B,UAAI,IAAI,eAAe,GAAG;AACxB,YAAI;AACF,gBAAMM,MAAK,IAAI;QACjB,QAAQ;AACN,iBAAO,KAAK,SAAS,MAAM,IAAI,CAAC;QAClC;MACF,WAAW,IAAI,YAAY,GAAG;AAC5B,cAAM,KAAK,IAAI;MACjB;IACF;EACF;AACA,QAAM,KAAK,IAAI;AACf,SAAO;AACT;AAmBA,eAAsB,mBACpB,MACA,MACmC;AACnC,QAAM,UAAU,MAAM,aAAa,KAAK,MAAM;AAC9C,QAAM,aAAa,KAAK,MAAM;AAC9B,QAAM,UAAU,CAAC;AAEjB,MAAI,CAAC,KAAK,aAAc,QAAO,EAAE,SAAS,QAAQ,MAAM;AAExD,QAAM,aAAaN,OAAKD,UAAQ,GAAG,SAAS;AAC5C,MAAI,CAAE,MAAM,WAAW,UAAU,EAAI,QAAO,EAAE,SAAS,QAAQ,MAAM;AAiBrE,QAAM,iBAAiBC,OAAKD,UAAQ,GAAG,cAAc;AACrD,QAAM,UAAU,MAAM,WAAW,cAAc;AAM/C,QAAM,iBAAiB,CAAE,MAAM,oBAAoB,KAAK,QAAQ,KAAK,KAAK;AAC1E,QAAM,WAAWA,UAAQ;AAOzB,QAAM,aAAaC,OAAKD,UAAQ,GAAG,SAAS;AAC5C,QAAM,YAAY,MAAM,WAAW,UAAU;AAC7C,QAAM,OAAiB;IACrB;IACA;IACA;IACA;;;IAGA;IACA,aAAa,QAAQ;IACrB;IACA,GAAG,KAAK,MAAM;IACd;IACA,GAAG,UAAU;EACf;AACA,MAAI,WAAW,eAAgB,MAAK,KAAK,MAAM,GAAG,cAAc,sBAAsB;AACtF,MAAI,UAAW,MAAK,KAAK,MAAM,GAAG,UAAU,cAAc;AAU1D,QAAM,YAAY,MAAM,QAAQC,OAAK,OAAO,GAAG,yBAAyB,CAAC;AACzE,MAAI,oBAAoB;AACxB,MAAI,qBAAqB;AACzB,MAAI,oBAAoB;AACxB,MAAI,mBAAmB;AACvB,MAAI;AACF,UAAM,iBAAiB,MAAM;MAC3BA,OAAK,YAAY,eAAe;MAChCA,OAAK,WAAW,eAAe;MAC/B;IACF;AACA,yBAAqB,eAAe;AACpC,QAAI,CAAC,gBAAgB;IAIrB,WAAW,SAAS;AAClB,YAAM,aAAa,MAAM;QACvB;QACAA,OAAK,WAAW,cAAc;QAC9B;QACA;UACE,wBAAwB;UACxB,cAAc,KAAK,gBACf,EAAE,MAAM,KAAK,eAAe,IAAI,oBAAoB,IACpD;UACJ,oBAAoB;QACtB;MACF;AACA,2BAAqB,WAAW;AAChC,2BAAqB,WAAW;AAChC,0BAAoB,WAAW;AAC/B,yBAAmB,WAAW;IAChC,OAAO;AAML,YAAMO;QACJP,OAAK,WAAW,cAAc;QAC9B,KAAK;UACH;YACE,eAAe;YACf,aAAa;YACb,+BAA+B;YAC/B,UAAU,EAAE,CAAC,mBAAmB,GAAG,EAAE,wBAAwB,KAAK,EAAE;UACtE;UACA;UACA;QACF;MACF;AACA,2BAAqB;AACrB,yBAAmB;IACrB;AACA,QAAI,oBAAoB,KAAK,sBAAsB,qBAAqB,kBAAkB;AACxF,WAAK,KAAK,MAAM,GAAG,SAAS,iBAAiB;IAC/C;AAMA,UAAM,iBAAiB,MAAM,mBAAmB,UAAU;AAC1D,UAAM,gBAAgB,CAAC,wBAAwB;AAC/C,eAAW,OAAO,eAAgB,eAAc,KAAK,cAAc,GAAG,EAAE;AACxE,UAAM,aAAa,0BAA0B,cAAc,KAAK,GAAG,CAAC;AACpE,SAAK;MACH,KAAK;MACL;MACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MA+BA,oKAGe,UAAU;IAQ3B;AACA,UAAMC,OAAM,UAAU,IAAI;EAC5B,UAAA;AACE,UAAMI,IAAG,WAAW,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;EACtD;AAEA,SAAO;IACL;IACA,QAAQ;IACR;IACA;IACA;IACA;EACF;AACF;AAgBA,eAAsB,yBACpB,QACA,OAC8B;AAC9B,MAAI;AACF,UAAM,EAAE,OAAO,IAAI,MAAMJ,OAAM,UAAU;MACvC;MACA;MACA;MACA;MACA;MACA,GAAG,MAAM;MACT;MACA;MACA;;;;MAIA,UAAU,uBAAuB,2FAGtB,uBAAuB,IAAI,eAAe;IAEvD,CAAC;AACD,WAAO,EAAE,QAAQ,OAAO,SAAS,QAAQ,EAAE;EAC7C,QAAQ;AACN,WAAO,EAAE,QAAQ,MAAM;EACzB;AACF;AASA,eAAe,cACb,KACA,MACA,UACA,OAII,CAAC,GAMJ;AACD,QAAM,OAAO;IACX,cAAc;IACd,oBAAoB;IACpB,mBAAmB;IACnB,kBAAkB;EACpB;AACA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,MAAMG,WAAS,KAAK,MAAM,CAAC;EACjD,QAAQ;AACN,WAAO;EACT;AACA,QAAM,WAAW,gBAAgB,QAAQ,QAAQ;AACjD,MAAI,UAAmB,SAAS;AAChC,MAAI,eAAe;AACnB,MAAI,KAAK,wBAAwB;AAC/B,UAAM,IAAI,uBAAuB,OAAO;AACxC,cAAU,EAAE;AACZ,mBAAe,EAAE;EACnB;AACA,MAAI,UAAU;AACd,MAAI,KAAK,cAAc;AACrB,UAAM,IAAI,gBAAgB,SAAS,KAAK,aAAa,MAAM,KAAK,aAAa,EAAE;AAC/E,cAAU,EAAE;AACZ,cAAU,EAAE;EACd;AACA,MAAI,UAAU;AACd,MAAI,KAAK,oBAAoB;AAC3B,UAAM,IAAI,eAAe,SAAS,KAAK,kBAAkB;AACzD,cAAU,EAAE;AACZ,cAAU,EAAE;EACd;AACA,MAAI,SAAS,gBAAgB,WAAW,KAAK,CAAC,gBAAgB,CAAC,WAAW,CAAC,SAAS;AAClF,WAAO;EACT;AACA,QAAMG,WAAU,MAAM,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC;AACtD,SAAO;IACL,cAAc,SAAS,gBAAgB;IACvC,oBAAoB;IACpB,mBAAmB;IACnB,kBAAkB;EACpB;AACF;AAqBO,IAAM,4BAA4B;EACvC;EACA;EACA;EACA;AACF;AAEA,IAAM,qBAAqB;AAEpB,SAAS,kBACd,MACA,SACmB;AACnB,QAAM,MAA8B,CAAC;AACrC,aAAW,KAAK,oBAAoB;AAClC,UAAM,IAAI,QAAQ,CAAC;AACnB,QAAI,OAAO,MAAM,YAAY,EAAE,SAAS,EAAG,KAAI,CAAC,IAAI;EACtD;AACA,SAAO;IACL,cAAc,CAAC,GAAG,KAAK,MAAM,IAAI,oBAAoB,EAAE;IACvD;IACA,YAAY,KAAK;EACnB;AACF;AAuBA,IAAM,0BAA0B;AAQhC,IAAM,uBAAuB;AAG7B,IAAM,4BAA4B,IAAI,KAAK,KAAK;AAGhD,IAAM,6BAA6B,KAAK,MAAM,4BAA4B,GAAK;AAW/E,IAAM,gBAAgB;AAEtB,eAAe,OAAO,GAA6B;AACjD,MAAI;AACF,YAAQ,MAAMD,MAAK,CAAC,GAAG,OAAO;EAChC,QAAQ;AACN,WAAO;EACT;AACF;AAGA,eAAe,mBAAmB,GAA6B;AAC7D,MAAI;AACF,UAAM,KAAK,MAAMA,MAAK,CAAC;AACvB,WAAO,KAAK,IAAI,IAAI,GAAG,UAAU;EACnC,QAAQ;AACN,WAAO;EACT;AACF;AAEA,eAAe,MAAM,GAA6B;AAChD,MAAI;AACF,YAAQ,MAAMA,MAAK,CAAC,GAAG,YAAY;EACrC,QAAQ;AACN,WAAO;EACT;AACF;AAQA,eAAe,yBAAyB,0BAAwD;AAC9F,MAAI;AACF,UAAM,MAAM,MAAMF,WAAS,0BAA0B,MAAM;AAC3D,WAAO,4BAA4B,KAAK,MAAM,GAAG,CAAY;EAC/D,QAAQ;AACN,WAAO,oBAAI,IAAY;EACzB;AACF;AAcA,eAAsB,0BAA0B,WAAqC;AACnF,QAAM,aAAa,MAAM;IACvBJ,OAAK,WAAW,MAAM,wBAAwB;EAChD;AACA,MAAI;AACJ,MAAI;AACF,mBAAe,MAAMG,SAAQ,WAAW,EAAE,eAAe,KAAK,CAAC;EACjE,QAAQ;AACN,WAAO;EACT;AACA,aAAW,KAAK,cAAc;AAC5B,QAAI,CAAC,EAAE,YAAY,EAAG;AACtB,UAAM,QAAQH,OAAK,WAAW,EAAE,IAAI;AACpC,QAAI;AACJ,QAAI;AACF,gBAAU,MAAMG,SAAQ,OAAO,EAAE,eAAe,KAAK,CAAC;IACxD,QAAQ;AACN;IACF;AACA,eAAW,KAAK,SAAS;AACvB,UAAI,CAAC,EAAE,YAAY,EAAG;AACtB,YAAM,QAAQH,OAAK,OAAO,EAAE,IAAI;AAChC,UAAI;AACJ,UAAI;AACF,mBAAW,MAAMG,SAAQ,OAAO,EAAE,eAAe,KAAK,CAAC;MACzD,QAAQ;AACN;MACF;AACA,iBAAW,KAAK,UAAU;AACxB,YAAI,CAAC,EAAE,YAAY,EAAG;AACtB,YAAI,WAAW,OAAO,KAAK,CAAC,WAAW,IAAI,GAAG,EAAE,IAAI,IAAI,EAAE,IAAI,IAAI,EAAE,IAAI,EAAE,EAAG;AAC7E,cAAM,QAAQH,OAAK,OAAO,EAAE,IAAI;AAChC,YAAI,CAAE,MAAM,OAAOA,OAAK,OAAO,cAAc,CAAC,EAAI;AAClD,YAAI,MAAM,OAAOA,OAAK,OAAO,uBAAuB,CAAC,EAAG;AACxD,YAAI,MAAM,mBAAmBA,OAAK,OAAO,oBAAoB,CAAC,EAAG;AACjE,eAAO;MACT;IACF;EACF;AACA,SAAO;AACT;AASA,eAAe,6BAA6B,QAAwC;AAClF,MAAK,MAAM,aAAa,MAAO,WAAY,QAAO;AAClD,MAAI,CAAE,MAAM,MAAM,mBAAmB,MAAM,CAAC,EAAI,QAAO;AACvD,SAAO,mBAAmB,QAAQ,WAAW,OAAO;AACtD;AAgCA,eAAe,4BAA4B,WAAyC;AAClF,QAAM,MAAM,MAAMC;IAChB;IACA;MACE;MACA;MACA;MACA;MACA;MACA,GAAG,oBAAoB;IACzB;IACA,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,IAAI,aAAa,KAAK,CAAC,IAAI,OAAQ,QAAO,oBAAI,IAAY;AAC9D,MAAI;AACF,WAAO,4BAA4B,KAAK,MAAM,IAAI,MAAM,CAAY;EACtE,QAAQ;AACN,WAAO,oBAAI,IAAY;EACzB;AACF;AAEA,eAAsB,wBACpB,WACA,OASI,CAAC,GACmC;AACxC,MAAI,KAAK,QAAQ;AACf,UAAM,YAAY,MAAM,6BAA6B,KAAK,MAAM;AAChE,QAAI,aAAa,CAAE,MAAM,0BAA0B,SAAS,GAAI;AAC9D,aAAO,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,GAAG,QAAQ,CAAC,GAAG,aAAa,GAAG,SAAS,KAAK;IAC9E;EACF;AAKA,QAAM,aAAa,MAAM,4BAA4B,SAAS;AAC9D,QAAM,WACJ,WAAW,OAAO,IACd;EAAkD,CAAC,GAAG,UAAU,EAAE,KAAK,EAAE,KAAK,IAAI,CAAC;;IACnF;AAIN,QAAM,SAAS;;SAER,uBAAuB;aACnB,oBAAoB;YACrB,aAAa;cACX,0BAA0B;;;;;EAKtC,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6ER,QAAM,SAAS,MAAMA;IACnB;IACA,CAAC,QAAQ,UAAU,gBAAgB,WAAW,MAAM,MAAM,MAAM;IAChE,EAAE,QAAQ,MAAM;EAClB;AACA,QAAM,UAAoB,CAAC;AAC3B,QAAM,SAAiD,CAAC;AACxD,QAAM,SAAmB,CAAC;AAC1B,MAAI,cAAc;AAClB,QAAM,SAAS,OAAO,UAAU,IAAI,MAAM,IAAI;AAC9C,MAAI,iBAA2D;AAC/D,aAAW,QAAQ,OAAO;AACxB,QAAI,gBAAgB;AAClB,UAAI,SAAS,oBAAoB;AAC/B,eAAO,KAAK,EAAE,KAAK,eAAe,KAAK,QAAQ,eAAe,OAAO,KAAK,IAAI,EAAE,CAAC;AACjF,yBAAiB;MACnB,OAAO;AACL,uBAAe,OAAO,KAAK,IAAI;MACjC;AACA;IACF;AACA,QAAI,KAAK,WAAW,gBAAgB,GAAG;AACrC,WAAK,aAAa,cAAc,KAAK,MAAM,iBAAiB,MAAM,CAAC,EAAE;IACvE,WAAW,KAAK,WAAW,aAAa,GAAG;AACzC,cAAQ,KAAK,KAAK,MAAM,cAAc,MAAM,CAAC;IAC/C,WAAW,KAAK,WAAW,eAAe,GAAG;AAC3C,uBAAiB,EAAE,KAAK,KAAK,MAAM,gBAAgB,MAAM,GAAG,QAAQ,CAAC,EAAE;IACzE,WAAW,KAAK,WAAW,WAAW,GAAG;AAEvC,YAAM,OAAO,KAAK,MAAM,YAAY,MAAM;AAC1C,YAAM,KAAK,KAAK,YAAY,GAAG;AAC/B,UAAI,KAAK,GAAG;AACV,cAAM,MAAM,KAAK,MAAM,GAAG,EAAE;AAC5B,cAAM,QAAQ,OAAO,KAAK,MAAM,KAAK,CAAC,CAAC;AACvC,eAAO,KAAK,GAAG;AACf,YAAI,OAAO,SAAS,KAAK,EAAG,gBAAe;AAC3C,aAAK,aAAa,8BAA8B,GAAG,EAAE;MACvD;IACF;EACF;AACA,SAAO,EAAE,SAAS,QAAQ,QAAQ,aAAa,SAAS,MAAM;AAChE;AAEO,IAAM,qBAAN,cAAiC,MAAM;EAC5C,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;EACd;AACF;AAeA,SAAS,QAAQ,KAAqB;AACpC,MAAI,IAAI,WAAW,EAAG,QAAO;AAC7B,MAAI,4BAA4B,KAAK,GAAG,EAAG,QAAO;AAClD,SAAO,IAAI,IAAI,QAAQ,MAAM,OAAO,CAAC;AACvC;AAiBA,eAAsB,mBAAmB,MAAgD;AACvF,QAAM,cAAc,KAAK,eAAe;AACxC,QAAM,MAAM,CAAC,UAAU,GAAG,KAAK,UAAU,EAAE,IAAI,OAAO,EAAE,KAAK,GAAG;AAChE,QAAM,OAAO,QAAQ,IAAI,MAAM,KAAK;AACpC,QAAM,WAAqB,CAAC,MAAM,QAAQ,IAAI,EAAE;AAChD,aAAW,KAAK,oBAAoB;AAClC,UAAM,IAAI,QAAQ,IAAI,CAAC;AACvB,QAAI,OAAO,MAAM,YAAY,EAAE,SAAS,EAAG,UAAS,KAAK,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE;EAC5E;AACA,QAAM,SAAS,MAAMA;IACnB;IACA;MACE;MACA,GAAG;MACH;MACA;MACA,KAAK;MACL;MACA;MACA;MACA;MACA;MACA;MACA,GAAG,qBAAqB,WAAW;IACrC;IACA,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,OAAO,aAAa,EAAG;AAC3B,QAAM,UAAU,OAAO,UAAU,IAAI,SAAS;AAC9C,MAAI,OAAO,aAAa,OAAO,qCAAqC,KAAK,MAAM,GAAG;AAChF,UAAM,IAAI;MACR;IACF;EACF;AACA,MAAI,oCAAoC,KAAK,MAAM,GAAG;AACpD,UAAM,IAAI;MACR;IACF;EACF;AACA,MAAI,qBAAqB,KAAK,MAAM,GAAG;AACrC,UAAM,IAAI;MACR,mBAAmB,WAAW,uBAAuB,KAAK,SAAS;IACrE;EACF;AACA,QAAM,IAAI;IACR,qCAAqC,KAAK,SAAS,KAAK,OAAO,KAAK,KAAK,QAAQ,OAAO,OAAO,QAAQ,CAAC,EAAE;EAC5G;AACF;AAaO,SAAS,sBAAsB,WAAmB,aAAgC;AACvF,QAAM,OAAO,eAAe;AAC5B,QAAM,OAAO,QAAQ,IAAI,MAAM,KAAK;AACpC,SAAO;IACL;IACA;IACA;IACA,QAAQ,IAAI;IACZ;IACA;IACA;IACA;IACA;IACA;IACA;EACF;AACF;AAgBO,SAAS,yBACd,WACA,aACU;AACV,QAAM,OAAO,eAAe;AAC5B,QAAM,OAAO,GAAG,IAAI;AACpB,QAAM,OAAO,QAAQ,IAAI,MAAM,KAAK;AACpC,SAAO;IACL;IACA;IACA;IACA,QAAQ,IAAI;IACZ;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;EACF;AACF;AAWA,eAAsB,uBACpB,WACA,aACA,YAAY,KACG;AACf,QAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,SAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,UAAM,MAAM,MAAMA;MAChB;MACA,CAAC,QAAQ,UAAU,gBAAgB,WAAW,QAAQ,gBAAgB,MAAM,MAAM,WAAW;MAC7F,EAAE,QAAQ,MAAM;IAClB;AACA,QAAI,IAAI,aAAa,MAAM,IAAI,UAAU,IAAI,KAAK,EAAE,SAAS,EAAG;AAChE,UAAM,MAAM,GAAG;EACjB;AACF;AA2BO,SAAS,qBAAqB,aAA+B;AAClE,QAAM,IAAI;AACV,SAAO;;;;;;;;IAQL;IAAK;IAAO;IAAM;IAAU;IAC5B;IAAK;IAAO;IAAM;IAAW;IAC7B;IAAK;IAAY;IAAO;IACxB;IAAK;IAAY;IAAO;IAAe;IACvC;IAAK;IAAY;IAAK;;;;;;;IAOtB;IAAK;IAAO;IAAM;IAAiB;IACnC;IAAK;IAAO;IAAO;IAAqB;;;;;IAKxC;IAAK;IAAO;IAAM;IAAG;IAAU;EACjC;AACF;AAOO,SAAS,eAAe,WAA6B;AAC1D,QAAM,OAAO,QAAQ,IAAI,MAAM,KAAK;AACpC,SAAO,CAAC,QAAQ,OAAO,MAAM,QAAQ,IAAI,IAAI,UAAU,gBAAgB,WAAW,QAAQ,IAAI;AAChG;AAYO,SAAS,wBAAwB,MAI3B;AACX,QAAM,OAAO,QAAQ,IAAI,MAAM,KAAK;AACpC,SAAO;IACL;IACA;IACA;IACA;IACA,QAAQ,IAAI;IACZ;IACA;IACA;IACA,GAAG,KAAK,MAAM,IAAI,oBAAoB;IACtC;IACA;IACA,KAAK;IACL;IACA;IACA;IACA,GAAG,KAAK;EACV;AACF;AAOO,SAAS,0BAA0B,YAA4C;AACpF,QAAM,QAAQ,UAAU,UAAU,YAAY,EAAE,OAAO,UAAU,CAAC;AAClE,SAAO,EAAE,UAAU,MAAM,UAAU,EAAE;AACvC;AA4BA,eAAsB,wBACpB,QACA,OACA,OAAgD,CAAC,GACpB;AAC7B,QAAM,eAAe;AACrB,QAAM,WAAW;AACjB,WAAS,UAAU,GAAG,WAAW,cAAc,WAAW;AACxD,SAAK,aAAa,2BAA2B,OAAO,IAAI,YAAY,EAAE;AACtE,UAAM,MAAM,MAAMA;MAChB;MACA;QACE;QACA;QACA;QACA,GAAG,MAAM,IAAI,oBAAoB;QACjC;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;MACF;MACA,EAAE,QAAQ,OAAO,SAAS,IAAO;IACnC;AAGA,UAAM,MAAM,GAAG,IAAI,UAAU,EAAE;EAAK,IAAI,UAAU,EAAE;AACpD,UAAM,WAAW,8DAA8D,KAAK,GAAG;AACvF,QAAI,IAAI,aAAa,KAAK,CAAC,SAAU,QAAO,EAAE,QAAQ,MAAM,UAAU,QAAQ;AAC9E,QAAI,UAAU,aAAc,OAAM,MAAM,QAAQ;EAClD;AACA,SAAO,EAAE,QAAQ,OAAO,UAAU,aAAa;AACjD;AAEO,SAAS,mBACd,KACA,UAAqD,UACrD,SAAS,IACD;AACR,SAAO,6CAA6C,OAAO,WAAW,GAAG,GAAG,MAAM;AACpF;AAEO,SAAS,oBACd,WACA,aACA,aACO;AACP,QAAM,QAAQ,UAAU,UAAU,sBAAsB,WAAW,WAAW,GAAG;IAC/E,OAAO;EACT,CAAC;AACD,QAAM,OAAO,MAAM,UAAU;AAC7B,MAAI,eAAe,SAAS,GAAG;AAI7B,YAAQ,OAAO,MAAM,qBAAqB,mBAAmB,WAAW,IAAI,IAAI;EAClF;AACA,UAAQ,KAAK,IAAI;AACnB;AAaA,eAAsB,kBACpB,WACA,aAC4B;AAC5B,QAAM,OAAO,eAAe;AAC5B,QAAM,MAAM,MAAMA;IAChB;IACA,CAAC,QAAQ,UAAU,gBAAgB,WAAW,QAAQ,eAAe,MAAM,IAAI;IAC/E,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,IAAI,aAAa,GAAG;AACtB,WAAO,EAAE,SAAS,OAAO,aAAa,MAAM,WAAW,KAAK;EAC9D;AACA,QAAM,KAAK,MAAMA;IACf;IACA;MACE;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;IACF;IACA,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,YAA2B;AAC/B,MAAI,GAAG,aAAa,GAAG;AACrB,UAAM,OAAO,OAAO,UAAU,GAAG,UAAU,IAAI,KAAK,GAAG,EAAE;AACzD,QAAI,OAAO,SAAS,IAAI,KAAK,OAAO,EAAG,aAAY,IAAI,KAAK,OAAO,GAAI,EAAE,YAAY;EACvF;AACA,SAAO,EAAE,SAAS,MAAM,aAAa,MAAM,UAAU;AACvD;AAoBA,IAAM,sBAAsB,CAAC,UAAU,UAAU,UAAU;AAQ3D,eAAe,cAAc,KAAgC;AAC3D,MAAI;AACF,UAAM,UAAU,MAAME,SAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;AAC1D,WAAO,QAAQ,OAAO,CAAC,MAAM,EAAE,YAAY,KAAK,EAAE,eAAe,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI;EACvF,QAAQ;AACN,WAAO,CAAC;EACV;AACF;AAEA,eAAe,aAAa,MAAgC;AAC1D,MAAI;AACF,WAAO,KAAK,MAAM,MAAMC,WAAS,MAAM,MAAM,CAAC;EAChD,QAAQ;AACN,WAAO;EACT;AACF;AAcA,eAAsB,iBACpB,MACA,MAC2B;AAC3B,QAAM,WAAWL,UAAQ;AACzB,QAAM,aAAaC,OAAK,UAAU,SAAS;AAK3C,QAAM,kBAAkB;IACtB;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;EACF,EAAE,KAAK,GAAG;AAEV,QAAM,MAAM,MAAMC;IAChB;IACA,CAAC,OAAO,QAAQ,UAAU,KAAK,MAAM,GAAG,KAAK,MAAM,YAAY,KAAK,OAAO,MAAM,MAAM,eAAe;IACtG,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,IAAI,aAAa,GAAG;AACtB,UAAM,IAAI;MACR,uCAAuC,KAAK,MAAM,MAAM,IAAI,UAAU,IAAI,SAAS,EAAE,KAAK,KAAK,QAAQ,OAAO,IAAI,QAAQ,CAAC,EAAE;IAC/H;EACF;AAEA,QAAM,UAAoC,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,GAAG,UAAU,CAAC,EAAE;AACjF,QAAM,aAAuB,CAAC;AAC9B,QAAM,UAAmC,CAAC;AAC1C,aAAW,SAAS,IAAI,UAAU,IAAI,MAAM,IAAI,GAAG;AACjD,QAAI,KAAK,WAAW,MAAM,GAAG;AAC3B,YAAM,OAAO,KAAK,MAAM,CAAC;AACzB,YAAM,KAAK,KAAK,QAAQ,GAAG;AAC3B,UAAI,OAAO,GAAI;AACf,YAAM,MAAM,KAAK,MAAM,GAAG,EAAE;AAC5B,YAAM,OAAO,KAAK,MAAM,KAAK,CAAC;AAC9B,UAAI,OAAO,QAAS,SAAQ,GAAG,EAAG,KAAK,IAAI;IAC7C,WAAW,KAAK,WAAW,SAAS,GAAG;AACrC,iBAAW,KAAK,KAAK,MAAM,CAAC,CAAC;IAC/B,WAAW,KAAK,WAAW,OAAO,GAAG;AACnC,YAAM,OAAO,KAAK,MAAM,CAAC;AACzB,YAAM,KAAK,KAAK,QAAQ,GAAG;AAC3B,UAAI,OAAO,GAAI;AACf,YAAM,QAAQ,KAAK,MAAM,GAAG,EAAE;AAC9B,UAAI;AACF,gBAAQ,KAAK,IAAI,KAAK,MAAM,OAAO,KAAK,KAAK,MAAM,KAAK,CAAC,GAAG,QAAQ,EAAE,SAAS,MAAM,CAAC;MACxF,QAAQ;MAER;IACF;EACF;AAIA,QAAM,WAAyC,CAAC;AAChD,QAAM,aAAmD,CAAC;AAC1D,aAAW,OAAO,qBAAqB;AACrC,UAAM,YAAY,MAAM,cAAcD,OAAK,YAAY,GAAG,CAAC;AAC3D,UAAM,WAAW,QAAQ,WAAW,yBAAyB,CAAC;AAC9D,eAAW,QAAQ,aAAa,QAAQ,GAAG,KAAK,CAAC,GAAG,WAAW,QAAQ,GAAG;AACxE,eAAS,KAAK,EAAE,UAAU,KAAK,KAAK,CAAC;AACrC,iBAAW,KAAK,EAAE,KAAK,QAAQ,GAAG,IAAI,IAAI,IAAI,MAAM,QAAQ,GAAG,IAAI,IAAI,GAAG,CAAC;IAC7E;EACF;AACA,QAAM,iBAA2B,CAAC;AAClC,aAAW,KAAK,MAAM,cAAcA,OAAK,YAAY,WAAW,OAAO,CAAC,GAAG;AACzE,eAAW,KAAK,MAAM,cAAcA,OAAK,YAAY,WAAW,SAAS,CAAC,CAAC,GAAG;AAC5E,qBAAe,KAAK,GAAG,CAAC,IAAI,CAAC,EAAE;IACjC;EACF;AACA,aAAW,OAAO,aAAa,YAAY,cAAc,GAAG;AAC1D,aAAS,KAAK,EAAE,UAAU,WAAW,MAAM,IAAI,CAAC;AAChD,eAAW,KAAK,EAAE,KAAK,sBAAsB,GAAG,IAAI,MAAM,sBAAsB,GAAG,GAAG,CAAC;EACzF;AAIA,QAAM,gBAAgB,MAAM,aAAaA,OAAK,YAAY,WAAW,wBAAwB,CAAC;AAC9F,QAAM,cAAc,MAAM,aAAaA,OAAK,YAAY,WAAW,yBAAyB,CAAC;AAC7F,QAAM,kBAAkB,sBAAsB,eAAe,QAAQ,mBAAmB,GAAG;IACzF;EACF,CAAC;AACD,QAAM,gBAAgB,uBAAuB,aAAa,QAAQ,oBAAoB,GAAG;IACvF;EACF,CAAC;AACD,QAAM,mBAA6B,CAAC;AACpC,MAAI,gBAAgB,QAAS,kBAAiB,KAAK,wBAAwB;AAC3E,MAAI,cAAc,QAAS,kBAAiB,KAAK,yBAAyB;AAE1E,MAAI,KAAK,UAAW,SAAS,WAAW,KAAK,iBAAiB,WAAW,GAAI;AAC3E,WAAO,EAAE,UAAU,iBAAiB;EACtC;AAOA,MAAI,WAAW,SAAS,GAAG;AACzB,UAAM,OAAO,WAAW,IAAI,CAAC,EAAE,KAAK,KAAK,MAAM;AAC7C,YAAM,SAAS,KAAK,MAAM,GAAG,KAAK,YAAY,GAAG,CAAC;AAClD,aAAO,aAAa,MAAM,2DAA2D,GAAG,OAAO,IAAI;IACrG,CAAC;AACD,UAAM,QAAQ,MAAMC;MAClB;MACA;QACE;QACA;QACA;QACA;QACA;QACA,GAAG,KAAK,MAAM;QACd;QACA,GAAG,UAAU;QACb,KAAK;QACL;QACA;QACA,KAAK,KAAK,MAAM;MAClB;MACA,EAAE,QAAQ,MAAM;IAClB;AACA,QAAI,MAAM,aAAa,GAAG;AACxB,YAAM,IAAI;QACR,kCAAkC,KAAK,MAAM,MAAM,MAAM,UAAU,IAAI,SAAS,EAAE,KAAK,KAAK,QAAQ,OAAO,MAAM,QAAQ,CAAC,EAAE;MAC9H;IACF;EACF;AAIA,MAAI,cAAc,WAAW,gBAAgB,SAAS;AACpD,UAAMW,QAAMZ,OAAK,YAAY,SAAS,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5D,QAAI,cAAc,SAAS;AACzB,YAAMO;QACJP,OAAK,YAAY,WAAW,yBAAyB;QACrD,GAAG,KAAK,UAAU,cAAc,MAAM,MAAM,CAAC,CAAC;;MAChD;IACF;AACA,QAAI,gBAAgB,SAAS;AAC3B,YAAMO;QACJP,OAAK,YAAY,WAAW,wBAAwB;QACpD,GAAG,KAAK,UAAU,gBAAgB,MAAM,MAAM,CAAC,CAAC;;MAClD;IACF;EACF;AAEA,SAAO,EAAE,UAAU,iBAAiB;AACtC;AKzgDO,IAAM,0BAA0BA,OAAKa,YAAW,yBAAyB;AAyBhF,eAAsB,yBACpB,OAAe,yBACG;AAClB,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,MAAMT,WAAS,MAAM,MAAM,CAAC;AAGtD,UAAM,KAAK,QAAQ,eAAe;AAClC,WAAO,OAAO,OAAO,YAAY,GAAG,SAAS;EAC/C,QAAQ;AACN,WAAO;EACT;AACF;AAMO,SAAS,gBAAgB,QAA6C;AAC3E,QAAM,uBAAuB,kBAAkB,KAAK,MAAM;AAC1D,MAAI,oBAAoB,KAAK,MAAM,EAAG,QAAO,EAAE,WAAW,aAAa,qBAAqB;AAC5F,MAAI,iBAAiB,KAAK,MAAM,EAAG,QAAO,EAAE,WAAW,UAAU,qBAAqB;AACtF,SAAO,EAAE,WAAW,QAAQ,qBAAqB;AACnD;AAQA,IAAM,cAAc;;;;;;;;;;;;;;AA+BpB,eAAsB,sBACpB,MACA,MACsC;AACtC,MAAI;AACF,UAAMQ,QAAMC,YAAW,EAAE,WAAW,KAAK,CAAC;AAC1C,UAAM,EAAE,OAAO,IAAI,MAAMZ,QAAM,UAAU;MACvC;MACA;MACA;MACA;MACA;MACA,GAAG,KAAK,MAAM;MACd;MACA,GAAGY,UAAS;MACZ;MACA,WAAW,KAAK,UAAU,QAAQ,IAAI;MACtC,KAAK;MACL;MACA;MACA;IACF,CAAC;AACD,UAAM,SAAS,gBAAgB,MAAM;AAGrC,QAAI,OAAO,cAAc,aAAa;AACpC,YAAM,MAAM,yBAAyB,GAAK,EAAE,MAAM,MAAM;MAAC,CAAC;IAC5D;AACA,WAAO;EACT,QAAQ;AACN,WAAO,EAAE,WAAW,QAAQ,sBAAsB,MAAM;EAC1D;AACF;AE/HO,IAAM,sBAAsB;AAC5B,IAAM,wBAAwB;AAErC,IAAM,sBAAsB;AAM5B,IAAM,0BAA0B;AAOzB,SAAS,mBAAmB,MAA4D;AAC7F,MAAI,KAAK,SAAS;AAChB,WAAO,EAAE,QAAQ,GAAG,mBAAmB,IAAI,KAAK,KAAK,GAAG;EAC1D;AACA,SAAO,EAAE,QAAQ,oBAAoB;AACvC;AAEA,eAAeC,YAAW,GAA6B;AACrD,MAAI;AACF,UAAMR,OAAK,CAAC;AACZ,WAAO;EACT,QAAQ;AACN,WAAO;EACT;AACF;AAMA,SAASS,SAAQ,KAAqB;AACpC,MAAI,IAAI,WAAW,EAAG,QAAO;AAC7B,MAAI,4BAA4B,KAAK,GAAG,EAAG,QAAO;AAClD,SAAO,IAAI,IAAI,QAAQ,MAAM,OAAO,CAAC;AACvC;AAkCA,eAAsB,kBACpB,MACA,MACkC;AAClC,QAAM,UAAU,MAAM,aAAa,KAAK,MAAM;AAC9C,QAAM,aAAa,KAAK,MAAM;AAC9B,QAAM,UAAU,CAAC;AAEjB,QAAM,YAAYf,OAAKD,UAAQ,GAAG,QAAQ;AAC1C,QAAM,WAAW,KAAK,gBAAiB,MAAMe,YAAW,SAAS;AACjE,MAAI,UAAU;AACZ,UAAMb,OAAM,UAAU;MACpB;MACA;MACA;MACA;MACA;MACA,GAAG,KAAK,MAAM;MACd;MACA,GAAG,SAAS;MACZ,KAAK;MACL;MACA;;;MAGA;IAEF,CAAC;AACD,WAAO,EAAE,SAAS,QAAQ,KAAK;EACjC;AAIA,QAAMA;IACJ;IACA;MACE;MACA;MACA;MACA;MACA;MACA,GAAG,KAAK,MAAM;MACd,KAAK;MACL;MACA;MACA;IACF;IACA,EAAE,QAAQ,MAAM;EAClB;AACA,SAAO,EAAE,SAAS,QAAQ,MAAM;AAClC;AAWA,eAAsB,eACpB,QACA,OAC8B;AAC9B,MAAI;AACF,UAAM,EAAE,OAAO,IAAI,MAAMA,OAAM,UAAU;MACvC;MACA;MACA;MACA;MACA;MACA,GAAG,MAAM;MACT;MACA;MACA;MACA,UAAU,uBAAuB,eAAe,uBAAuB;IAEzE,CAAC;AACD,WAAO,EAAE,QAAQ,OAAO,SAAS,QAAQ,EAAE;EAC7C,QAAQ;AACN,WAAO,EAAE,QAAQ,MAAM;EACzB;AACF;AAcO,IAAM,2BAA2B,CAAC,gBAAgB;AAElD,SAAS,iBACd,MACA,SACkB;AAClB,QAAM,MAA8B,CAAC;AACrC,aAAW,KAAK,0BAA0B;AACxC,UAAM,IAAI,QAAQ,CAAC;AACnB,QAAI,OAAO,MAAM,YAAY,EAAE,SAAS,EAAG,KAAI,CAAC,IAAI;EACtD;AACA,SAAO;IACL,cAAc,CAAC,GAAG,KAAK,MAAM,IAAI,mBAAmB,EAAE;IACtD;IACA,YAAY,KAAK;EACnB;AACF;AAEO,IAAM,oBAAN,cAAgC,MAAM;EAC3C,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;EACd;AACF;AAwBA,eAAsB,qBACpB,WACA,OAAgD,CAAC,GACZ;AACrC,QAAM,QAAQ,MAAMA;IAClB;IACA,CAAC,QAAQ,UAAU,gBAAgB,WAAW,MAAM,MAAM,kBAAkB;IAC5E,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,MAAM,aAAa,EAAG,QAAO,EAAE,WAAW,MAAM;AAEpD,OAAK,aAAa,+CAA+C;AACjE,QAAM,UAAU,MAAMA;IACpB;IACA,CAAC,QAAQ,UAAU,QAAQ,WAAW,QAAQ,OAAO,mCAAmC;IACxF,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,QAAQ,aAAa,GAAG;AAC1B,UAAM,IAAI;MACR,sFACW,OAAO,QAAQ,QAAQ,CAAC;GAEM,QAAQ,UAAU,IAAI,SAAS,EAAE,MAAM,IAAI,CAAC;IACvF;EACF;AACA,SAAO,EAAE,WAAW,KAAK;AAC3B;AAcA,eAAsB,kBAAkB,MAA+C;AACrF,QAAM,cAAc,KAAK,eAAe;AACxC,QAAM,MAAM,CAAC,SAAS,GAAG,KAAK,SAAS,EAAE,IAAIc,QAAO,EAAE,KAAK,GAAG;AAC9D,QAAM,OAAO,QAAQ,IAAI,MAAM,KAAK;AACpC,QAAM,WAAqB,CAAC,MAAM,QAAQ,IAAI,EAAE;AAChD,aAAW,KAAK,0BAA0B;AACxC,UAAM,IAAI,QAAQ,IAAI,CAAC;AACvB,QAAI,OAAO,MAAM,YAAY,EAAE,SAAS,EAAG,UAAS,KAAK,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE;EAC5E;AACA,QAAM,SAAS,MAAMd;IACnB;IACA;MACE;MACA,GAAG;MACH;MACA;MACA,KAAK;MACL;MACA;MACA;MACA;MACA;MACA;MACA,GAAG,qBAAqB,WAAW;IACrC;IACA,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,OAAO,aAAa,EAAG;AAC3B,QAAM,UAAU,OAAO,UAAU,IAAI,SAAS;AAC9C,MAAI,OAAO,aAAa,OAAO,qCAAqC,KAAK,MAAM,GAAG;AAChF,UAAM,IAAI;MACR;IACF;EACF;AACA,MAAI,kCAAkC,KAAK,MAAM,GAAG;AAClD,UAAM,IAAI;MACR;IACF;EACF;AACA,MAAI,qBAAqB,KAAK,MAAM,GAAG;AACrC,UAAM,IAAI;MACR,mBAAmB,WAAW,uBAAuB,KAAK,SAAS;IACrE;EACF;AACA,QAAM,IAAI;IACR,oCAAoC,KAAK,SAAS,KAAK,OAAO,KAAK,KAAK,QAAQ,OAAO,OAAO,QAAQ,CAAC,EAAE;EAC3G;AACF;AAMO,SAAS,qBAAqB,WAAmB,aAAgC;AACtF,QAAM,OAAO,eAAe;AAC5B,QAAM,OAAO,QAAQ,IAAI,MAAM,KAAK;AACpC,SAAO;IACL;IACA;IACA;IACA,QAAQ,IAAI;IACZ;IACA;IACA;IACA;IACA;IACA;IACA;EACF;AACF;AAaO,SAAS,uBAAuB,MAI1B;AACX,QAAM,OAAO,QAAQ,IAAI,MAAM,KAAK;AACpC,QAAM,YAAY,KAAK,UAAU,SAAS,IAAI,KAAK,YAAY,CAAC,eAAe;AAC/E,SAAO;IACL;IACA;IACA;IACA;IACA,QAAQ,IAAI;IACZ;IACA;IACA;IACA,GAAG,KAAK,MAAM,IAAI,mBAAmB;IACrC;IACA;IACA,KAAK;IACL;IACA;IACA,GAAG;EACL;AACF;AAOO,SAAS,yBAAyB,YAA4C;AACnF,QAAM,QAAQe,WAAU,UAAU,YAAY,EAAE,OAAO,UAAU,CAAC;AAClE,SAAO,EAAE,UAAU,MAAM,UAAU,EAAE;AACvC;AAOA,eAAsB,mBAAmB,QAAgB,OAAiC;AACxF,QAAM,MAAM,MAAMf;IAChB;IACA,CAAC,OAAO,QAAQ,MAAM,GAAG,MAAM,SAAS,OAAO,MAAM,MAAM,wBAAwB;IACnF,EAAE,QAAQ,MAAM;EAClB;AACA,SAAO,IAAI,aAAa;AAC1B;AAaA,eAAsB,iBACpB,WACA,aAC2B;AAC3B,QAAM,OAAO,eAAe;AAC5B,QAAM,MAAM,MAAMA;IAChB;IACA,CAAC,QAAQ,UAAU,gBAAgB,WAAW,QAAQ,eAAe,MAAM,IAAI;IAC/E,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,IAAI,aAAa,GAAG;AACtB,WAAO,EAAE,SAAS,OAAO,aAAa,MAAM,WAAW,KAAK;EAC9D;AACA,QAAM,KAAK,MAAMA;IACf;IACA;MACE;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;IACF;IACA,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,YAA2B;AAC/B,MAAI,GAAG,aAAa,GAAG;AACrB,UAAM,OAAO,OAAO,UAAU,GAAG,UAAU,IAAI,KAAK,GAAG,EAAE;AACzD,QAAI,OAAO,SAAS,IAAI,KAAK,OAAO,EAAG,aAAY,IAAI,KAAK,OAAO,GAAI,EAAE,YAAY;EACvF;AACA,SAAO,EAAE,SAAS,MAAM,aAAa,MAAM,UAAU;AACvD;AAeA,IAAM,mBAAmB,CAAC,eAAe,aAAa,SAAS;AAQ/D,eAAsB,gBACpB,MACA,MAC0B;AAC1B,QAAM,YAAYD,OAAKD,UAAQ,GAAG,QAAQ;AAE1C,QAAM,MAAM,MAAME;IAChB;IACA;MACE;MACA;MACA;MACA;MACA;MACA,GAAG,KAAK,MAAM;MACd,KAAK;MACL;MACA;MACA,YAAY,iBAAiB,KAAK,GAAG,CAAC;IACxC;IACA,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,IAAI,aAAa,GAAG;AACtB,UAAM,IAAI;MACR,sCAAsC,KAAK,MAAM,MAAM,IAAI,UAAU,IAAI,SAAS,EAAE,KAAK,KAAK,QAAQ,OAAO,IAAI,QAAQ,CAAC,EAAE;IAC9H;EACF;AAEA,QAAM,UAAU,IAAI;KACjB,IAAI,UAAU,IACZ,MAAM,IAAI,EACV,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,OAAO;EACnB;AACA,QAAM,WAAqB,CAAC;AAC5B,aAAW,QAAQ,kBAAkB;AACnC,QAAI,CAAC,QAAQ,IAAI,IAAI,EAAG;AACxB,QAAI,MAAMa,YAAWd,OAAK,WAAW,IAAI,CAAC,EAAG;AAC7C,aAAS,KAAK,IAAI;EACpB;AAEA,MAAI,KAAK,UAAU,SAAS,WAAW,EAAG,QAAO,EAAE,SAAS;AAK5D,QAAM,MAAM,QAAQ,SAAS,KAAK;AAClC,QAAM,MAAM,QAAQ,SAAS,KAAK;AAClC,QAAM,OAAO,SAAS,IAAI,CAAC,OAAO,eAAe,EAAE,WAAW,EAAE,GAAG;AACnE,QAAM,QAAQ,MAAMC;IAClB;IACA;MACE;MACA;MACA;MACA;MACA;MACA,GAAG,KAAK,MAAM;MACd;MACA,GAAG,SAAS;MACZ,KAAK;MACL;MACA;MACA,oBAAoB,KAAK,KAAK,MAAM,CAAC,gBAAgB,OAAO,GAAG,CAAC,IAAI,OAAO,GAAG,CAAC;IACjF;IACA,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,MAAM,aAAa,GAAG;AACxB,UAAM,IAAI;MACR,oCAAoC,KAAK,MAAM,MAAM,MAAM,UAAU,IAAI,SAAS,EAAE,KAAK,KAAK,QAAQ,OAAO,MAAM,QAAQ,CAAC,EAAE;IAChI;EACF;AACA,SAAO,EAAE,SAAS;AACpB;ACjhBO,IAAM,yBAAyB;AAC/B,IAAM,2BAA2B;AAExC,IAAM,yBAAyB;AAE/B,IAAM,gCAAgC;AAO/B,SAAS,sBAAsB,MAGf;AACrB,MAAI,KAAK,SAAS;AAChB,WAAO,EAAE,QAAQ,GAAG,sBAAsB,IAAI,KAAK,KAAK,GAAG;EAC7D;AACA,SAAO,EAAE,QAAQ,uBAAuB;AAC1C;AAEA,eAAea,YAAW,GAA6B;AACrD,MAAI;AACF,UAAMR,OAAK,CAAC;AACZ,WAAO;EACT,QAAQ;AACN,WAAO;EACT;AACF;AAMA,SAASS,SAAQ,KAAqB;AACpC,MAAI,IAAI,WAAW,EAAG,QAAO;AAC7B,MAAI,4BAA4B,KAAK,GAAG,EAAG,QAAO;AAClD,SAAO,IAAI,IAAI,QAAQ,MAAM,OAAO,CAAC;AACvC;AAmCA,eAAsB,qBACpB,MACA,MACqC;AACrC,QAAM,UAAU,MAAM,aAAa,KAAK,MAAM;AAC9C,QAAM,aAAa,KAAK,MAAM;AAC9B,QAAM,UAAU,CAAC;AAEjB,QAAM,WAAWf,MAAKD,SAAQ,GAAG,UAAU,SAAS,UAAU;AAC9D,QAAM,aAAaC,MAAKD,SAAQ,GAAG,WAAW,UAAU;AACxD,QAAM,UAAU,MAAMe,YAAW,QAAQ;AACzC,QAAM,YAAY,MAAMA,YAAW,UAAU;AAC7C,QAAM,WAAW,KAAK,iBAAiB,WAAW;AAElD,MAAI,UAAU;AACZ,UAAM,OAAO,CAAC,OAAO,QAAQ,UAAU,KAAK,MAAM,GAAG,KAAK,MAAM,OAAO;AACvE,QAAI,QAAS,MAAK,KAAK,MAAM,GAAG,QAAQ,eAAe;AACvD,QAAI,UAAW,MAAK,KAAK,MAAM,GAAG,UAAU,iBAAiB;AAC7D,UAAM,QAAkB,CAAC;AACzB,QAAI,SAAS;AAIX,YAAM;QACJ;MAIF;IACF;AACA,QAAI,WAAW;AACb,YAAM,KAAK,4DAA4D;IACzE;AACA,UAAM,KAAK,yBAAyB;AACpC,SAAK,KAAK,KAAK,OAAO,MAAM,MAAM,MAAM,KAAK,MAAM,CAAC;AACpD,UAAMb,OAAM,UAAU,IAAI;AAC1B,WAAO,EAAE,SAAS,QAAQ,KAAK;EACjC;AAIA,QAAMA;IACJ;IACA;MACE;MACA;MACA;MACA;MACA;MACA,GAAG,KAAK,MAAM;MACd,KAAK;MACL;MACA;MACA;IACF;IACA,EAAE,QAAQ,MAAM;EAClB;AACA,SAAO,EAAE,SAAS,QAAQ,MAAM;AAClC;AAmBO,IAAM,8BAA8B;EACzC;EACA;EACA;EACA;EACA;EACA;EACA;AACF;AAEO,SAAS,oBACd,MACA,SACqB;AAIrB,QAAM,MAA8B,EAAE,qBAAqB,8BAA8B;AACzF,aAAW,KAAK,6BAA6B;AAC3C,UAAM,IAAI,QAAQ,CAAC;AACnB,QAAI,OAAO,MAAM,YAAY,EAAE,SAAS,EAAG,KAAI,CAAC,IAAI;EACtD;AACA,SAAO;IACL,cAAc,CAAC,GAAG,KAAK,MAAM,IAAI,sBAAsB,EAAE;IACzD;IACA,YAAY,KAAK;EACnB;AACF;AAEO,IAAM,uBAAN,cAAmC,MAAM;EAC9C,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;EACd;AACF;AAsBA,eAAsB,wBACpB,WACA,OAAgD,CAAC,GACT;AACxC,QAAM,QAAQ,MAAMA;IAClB;IACA,CAAC,QAAQ,UAAU,gBAAgB,WAAW,MAAM,MAAM,qBAAqB;IAC/E,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,MAAM,aAAa,EAAG,QAAO,EAAE,WAAW,MAAM;AAEpD,OAAK,aAAa,kDAAkD;AACpE,QAAM,UAAU,MAAMA;IACpB;IACA,CAAC,QAAQ,UAAU,QAAQ,WAAW,QAAQ,OAAO,iCAAiC;IACtF,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,QAAQ,aAAa,GAAG;AAC1B,UAAM,IAAI;MACR,uFACW,OAAO,QAAQ,QAAQ,CAAC;GAEM,QAAQ,UAAU,IAAI,SAAS,EAAE,MAAM,IAAI,CAAC;IACvF;EACF;AACA,SAAO,EAAE,WAAW,KAAK;AAC3B;AAmBA,eAAsB,qBAAqB,MAAkD;AAC3F,QAAM,cAAc,KAAK,eAAe;AACxC,QAAM,MAAM,CAAC,YAAY,GAAG,KAAK,YAAY,EAAE,IAAIc,QAAO,EAAE,KAAK,GAAG;AACpE,QAAM,OAAO,QAAQ,IAAI,MAAM,KAAK;AACpC,QAAM,WAAqB,CAAC,MAAM,QAAQ,IAAI,EAAE;AAChD,aAAW,KAAK,6BAA6B;AAC3C,UAAM,IAAI,QAAQ,IAAI,CAAC;AACvB,QAAI,OAAO,MAAM,YAAY,EAAE,SAAS,EAAG,UAAS,KAAK,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE;EAC5E;AACA,QAAM,SAAS,MAAMd;IACnB;IACA;MACE;MACA,GAAG;MACH;MACA;MACA,KAAK;MACL;MACA;MACA;MACA;MACA;MACA;MACA,GAAG,qBAAqB,WAAW;IACrC;IACA,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,OAAO,aAAa,EAAG;AAC3B,QAAM,UAAU,OAAO,UAAU,IAAI,SAAS;AAC9C,MAAI,OAAO,aAAa,OAAO,qCAAqC,KAAK,MAAM,GAAG;AAChF,UAAM,IAAI;MACR;IACF;EACF;AACA,MAAI,wCAAwC,KAAK,MAAM,GAAG;AACxD,UAAM,IAAI;MACR;IACF;EACF;AACA,MAAI,qBAAqB,KAAK,MAAM,GAAG;AACrC,UAAM,IAAI;MACR,mBAAmB,WAAW,uBAAuB,KAAK,SAAS;IACrE;EACF;AACA,QAAM,IAAI;IACR,uCAAuC,KAAK,SAAS,KAAK,OAAO,KAAK,KAAK,QAAQ,OAAO,OAAO,QAAQ,CAAC,EAAE;EAC9G;AACF;AAMO,SAAS,wBAAwB,WAAmB,aAAgC;AACzF,QAAM,OAAO,eAAe;AAC5B,QAAM,OAAO,QAAQ,IAAI,MAAM,KAAK;AACpC,SAAO;IACL;IACA;IACA;IACA,QAAQ,IAAI;IACZ;IACA;IACA;IACA;IACA;IACA;IACA;EACF;AACF;AAaO,SAAS,0BAA0B,MAI7B;AACX,QAAM,OAAO,QAAQ,IAAI,MAAM,KAAK;AACpC,SAAO;IACL;IACA;IACA;IACA;IACA,QAAQ,IAAI;IACZ;IACA;IACA;IACA,uBAAuB,6BAA6B;IACpD;IACA,GAAG,KAAK,MAAM,IAAI,sBAAsB;IACxC;IACA;IACA,KAAK;IACL;IACA;IACA;IACA,GAAG,KAAK;EACV;AACF;AAOO,SAAS,4BAA4B,YAA4C;AACtF,QAAM,QAAQe,WAAU,UAAU,YAAY,EAAE,OAAO,UAAU,CAAC;AAClE,SAAO,EAAE,UAAU,MAAM,UAAU,EAAE;AACvC;AAOA,eAAsB,sBAAsB,QAAgB,OAAiC;AAC3F,QAAM,MAAM,MAAMf;IAChB;IACA,CAAC,OAAO,QAAQ,MAAM,GAAG,MAAM,SAAS,OAAO,MAAM,MAAM,wBAAwB;IACnF,EAAE,QAAQ,MAAM;EAClB;AACA,SAAO,IAAI,aAAa;AAC1B;AAaA,eAAsB,oBACpB,WACA,aAC8B;AAC9B,QAAM,OAAO,eAAe;AAC5B,QAAM,MAAM,MAAMA;IAChB;IACA,CAAC,QAAQ,UAAU,gBAAgB,WAAW,QAAQ,eAAe,MAAM,IAAI;IAC/E,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,IAAI,aAAa,GAAG;AACtB,WAAO,EAAE,SAAS,OAAO,aAAa,MAAM,WAAW,KAAK;EAC9D;AACA,QAAM,KAAK,MAAMA;IACf;IACA;MACE;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;IACF;IACA,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,YAA2B;AAC/B,MAAI,GAAG,aAAa,GAAG;AACrB,UAAM,OAAO,OAAO,UAAU,GAAG,UAAU,IAAI,KAAK,GAAG,EAAE;AACzD,QAAI,OAAO,SAAS,IAAI,KAAK,OAAO,EAAG,aAAY,IAAI,KAAK,OAAO,GAAI,EAAE,YAAY;EACvF;AACA,SAAO,EAAE,SAAS,MAAM,aAAa,MAAM,UAAU;AACvD;AAeA,IAAM,2BAA2B,CAAC,WAAW;AAK7C,IAAM,6BAA6B;EACjC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AACF;AASA,eAAsB,mBACpB,MACA,MAC6B;AAC7B,QAAM,WAAWD,MAAKD,SAAQ,GAAG,UAAU,SAAS,UAAU;AAC9D,QAAM,aAAaC,MAAKD,SAAQ,GAAG,WAAW,UAAU;AAGxD,QAAM,MAAM,MAAME;IAChB;IACA;MACE;MACA;MACA;MACA;MACA;MACA,GAAG,KAAK,MAAM;MACd,KAAK;MACL;MACA;MACA,YAAY,yBAAyB,KAAK,GAAG,CAAC,2DAC/B,2BAA2B,KAAK,GAAG,CAAC;IAErD;IACA,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,IAAI,aAAa,GAAG;AACtB,UAAM,IAAI;MACR,yCAAyC,KAAK,MAAM,MAAM,IAAI,UAAU,IAAI,SAAS,EAAE,KAAK,KAAK,QAAQ,OAAO,IAAI,QAAQ,CAAC,EAAE;IACjI;EACF;AAGA,QAAM,WACJ,CAAC;AACH,aAAW,SAAS,IAAI,UAAU,IAAI,MAAM,IAAI,GAAG;AACjD,UAAM,CAAC,OAAO,IAAI,IAAI,KAAK,KAAK,EAAE,MAAM,OAAO,CAAC;AAChD,QAAI,CAAC,QAAS,UAAU,UAAU,UAAU,SAAW;AACvD,UAAM,WAAW,UAAU,SAAS,WAAW;AAC/C,QAAI,MAAMa,YAAWd,MAAK,UAAU,IAAI,CAAC,EAAG;AAC5C,aAAS,KAAK;MACZ,OAAO,UAAU,SAAS,OAAO,UAAU,IAAI;MAC/C,KAAK,UAAU,SAAS,QAAQ,IAAI,KAAK,eAAe,IAAI;MAC5D,SAAS;MACT;IACF,CAAC;EACH;AAEA,MAAI,KAAK,UAAU,SAAS,WAAW,GAAG;AACxC,WAAO,EAAE,UAAU,SAAS,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE;EAClD;AAIA,QAAM,MAAM,QAAQ,SAAS,KAAK;AAClC,QAAM,MAAM,QAAQ,SAAS,KAAK;AAClC,QAAM,OAAO,SAAS;IACpB,CAAC,MAAM,UAAU,EAAE,GAAG,MAAM,EAAE,YAAY,SAAS,cAAc,aAAa,IAAI,EAAE,IAAI;EAC1F;AACA,QAAM,QAAQ,MAAMC;IAClB;IACA;MACE;MACA;MACA;MACA;MACA;MACA,GAAG,KAAK,MAAM;MACd;MACA,GAAG,QAAQ;MACX;MACA,GAAG,UAAU;MACb,KAAK;MACL;MACA;MACA,qCAAqC,KAAK,KAAK,MAAM,CAAC,gBACpC,OAAO,GAAG,CAAC,IAAI,OAAO,GAAG,CAAC;IAC9C;IACA,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,MAAM,aAAa,GAAG;AACxB,UAAM,IAAI;MACR,uCAAuC,KAAK,MAAM,MAAM,MAAM,UAAU,IAAI,SAAS,EAAE,KAAK,KAAK,QAAQ,OAAO,MAAM,QAAQ,CAAC,EAAE;IACnI;EACF;AACA,SAAO,EAAE,UAAU,SAAS,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE;AAClD;AC9iBO,IAAM,6BAA6B;AAEnC,SAAS,iBAAiB,OAAe,QAAyB;AACvE,SAAO,SAAS,6BAA6B,mBAAmB,KAAK;AACvE;AAUA,eAAsB,oBACpB,WACA,YAAY,KACkB;AAC9B,QAAM,SAAS,MAAM,UAAU,WAAW,CAAC,uCAAuC,GAAG;IACnF,MAAM;IACN,QAAQ;EACV,CAAC;AACD,MAAI,OAAO,aAAa,GAAG;AACzB,WAAO,EAAE,IAAI,OAAO,QAAQ,uBAAuB,OAAO,UAAU,OAAO,MAAM,GAAG;EACtF;AAEA,QAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,SAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,UAAM,QAAQ,MAAM;MAClB;MACA;QACE;QACA;QACA;MACF;MACA,EAAE,MAAM,OAAO;IACjB;AACA,QAAI,MAAM,aAAa,EAAG,QAAO,EAAE,IAAI,KAAK;AAC5C,UAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;EAC7C;AACA,SAAO,EAAE,IAAI,OAAO,QAAQ,uCAAuC,OAAO,SAAS,CAAC,KAAK;AAC3F;ACxCA,eAAsB,gBACpB,WACA,YAAY,KACc;AAC1B,QAAM,SAAS,MAAM,UAAU,WAAW,CAAC,mCAAmC,GAAG;IAC/E,MAAM;IACN,QAAQ;EACV,CAAC;AACD,MAAI,OAAO,aAAa,GAAG;AACzB,WAAO,EAAE,IAAI,OAAO,QAAQ,uBAAuB,OAAO,UAAU,OAAO,MAAM,GAAG;EACtF;AAEA,QAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,SAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,UAAM,QAAQ,MAAM;MAClB;MACA,CAAC,QAAQ,OAAO,8CAA8C;MAC9D,EAAE,MAAM,SAAS;IACnB;AACA,QAAI,MAAM,aAAa,EAAG,QAAO,EAAE,IAAI,KAAK;AAC5C,UAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;EAC7C;AACA,SAAO,EAAE,IAAI,OAAO,QAAQ,uCAAuC,OAAO,SAAS,CAAC,KAAK;AAC3F;AAEA,IAAM,wBAAwB;AASvB,SAAS,sBAA8B;AAC5C,QAAM,QAAQ,YAAY,CAAC;AAC3B,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,WAAO,sBAAsB,MAAM,CAAC,IAAK,sBAAsB,MAAM;EACvE;AACA,SAAO;AACT;AAMO,IAAM,qBAAqB;AAe3B,SAAS,aACd,QAOA,QACS;AACT,MAAI,CAAC,OAAO,cAAc,CAAC,OAAO,YAAa,QAAO,CAAC;AACvD,QAAM,gBAAgB,OAAO,oBAAoB;AACjD,QAAM,KAAK,0BAA0B,mBAAmB,OAAO,WAAW,CAAC;AAC3E,QAAM,OAAgB,CAAC;AACvB,MAAI,WAAW,cAAc,OAAO,WAAW;AAC7C,SAAK,SAAS,UAAU,OAAO,SAAS,cAAc,OAAO,aAAa,CAAC,aAAa,EAAE;EAC5F;AACA,MAAI,OAAO,aAAa;AACtB,SAAK,cAAc,oBAAoB,OAAO,OAAO,WAAW,CAAC,aAAa,EAAE;EAClF;AACA,SAAO;AACT;AC7FO,IAAM,qBAAqB;AEM3B,IAAM,gBAAgB;AAGtB,SAAS,aAAa,QAAwB;AACnD,SAAO,OAAO,QAAQ,qBAAqB,GAAG;AAChD;AAUO,SAAS,mBAAmB,QAAwB;AACzD,SAAO,GAAG,aAAa,IAAI,aAAa,MAAM,CAAC;AACjD;AAiDA,eAAsB,qBACpB,MACA,QACA,eACA,iBACwB;AAKxB,QAAM,QAAQ,MAAMA,OAAM,OAAO,CAAC,MAAM,KAAK,cAAc,SAAS,QAAQ,GAAG,EAAE,QAAQ,MAAM,CAAC;AAChG,QAAM,WAAW,MAAM,aAAa,IAAI,MAAM,OAAO,KAAK,KAAK,OAAO;AAEtE,QAAM,YAAY,MAAMA;IACtB;IACA,CAAC,MAAM,KAAK,cAAc,YAAY,YAAY,sBAAsB,IAAI;IAC5E,EAAE,QAAQ,MAAM;EAClB;AACA,QAAM,eAAe,UAAU,aAAa,IAAI,UAAU,SAAS;AAEnE,SAAO;IACL;IACA;IACA;IACA;IACA;IACA;IACA,YAAY,KAAK;EACnB;AACF;AAeA,eAAe,MACb,WACA,MACA,OAA0B,UAC1B,MAAc,KACC;AACf,QAAM,IAAI,MAAMA;IACd;IACA,CAAC,QAAQ,MAAM,KAAK,UAAU,MAAM,WAAW,GAAG,IAAI;IACtD,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,EAAE,aAAa,GAAG;AACpB,UAAM,IAAI,iBAAiB,GAAG,KAAK,KAAK,GAAG,CAAC,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE;EAChF;AACF;AA2CA,eAAsB,oBAAoB,MAIxB;AAChB,QAAM,MAAM,KAAK,UAAU,MAAM;EAAC;AAElC,QAAM,QAAQ,MAAM,KAAK,IAAI,IAAI,KAAK,aAAa,CAAC;AACpD,aAAW,QAAQ,OAAO;AACxB,UAAM,SAAS,MAAM,UAAU,KAAK,WAAW,CAAC,SAAS,iBAAiB,IAAI,GAAG;MAC/E,MAAM;IACR,CAAC;AACD,QAAI,OAAO,aAAa,GAAG;AACzB,UAAI,WAAW,IAAI,iDAAiD;IACtE,OAAO;AACL,YAAM,OAAO,OAAO,UAAU,OAAO,UAAU,QAAQ,OAAO,QAAQ,IAAI,KAAK;AAC/E,UAAI,SAAS,IAAI,oCAAoC,GAAG,EAAE;IAC5D;EACF;AACF;AAEA,eAAsB,cACpB,WACA,OACA,OACe;AACf,QAAM,MAAM,UAAU,MAAM;EAAC;AAG7B,QAAM,UAAU,CAAC,GAAG,KAAK,EAAE;IAAK,CAAC,GAAG,MAClC,EAAE,SAAS,UAAU,EAAE,SAAS,SAAS,KAAK,EAAE,SAAS,UAAU,EAAE,SAAS,SAAS,IAAI;EAC7F;AACA,aAAW,KAAK,SAAS;AAIvB,UAAMA;MACJ;MACA,CAAC,QAAQ,MAAM,KAAK,UAAU,QAAQ,WAAW,MAAM,MAAM,iBAAiB,EAAE,aAAa,cAAc,EAAE,aAAa,UAAU;MACpI,EAAE,QAAQ,MAAM;IAClB;AAKA,QAAI,EAAE,SAAS,UAAU;AACvB,YAAM,MAAM,WAAW,CAAC,SAAS,MAAM,SAAS,EAAE,aAAa,CAAC,GAAG,MAAM;AACzE,YAAM,MAAM,WAAW,CAAC,SAAS,MAAM,EAAE,aAAa,GAAG,MAAM;IACjE;AACA,UAAM,MAAM,WAAW,CAAC,SAAS,UAAU,EAAE,iBAAiB,EAAE,aAAa,GAAG,MAAM;AACtF,QAAI,gBAAgB,EAAE,aAAa,OAAO,EAAE,eAAe,EAAE;EAC/D;AACF;AA0BA,eAAsB,cAAc,MAA2C;AAC7E,QAAM,MAAM,KAAK,UAAU,MAAM;EAAC;AAIlC,QAAM,MAAM,KAAK,WAAW,CAAC,SAAS,MAAM,aAAa,CAAC;AAG1D,aAAW,KAAK,KAAK,OAAO;AAC1B,UAAM,OAAO,EAAE,KAAK;AACpB,UAAM,KAAK,EAAE;AACb,UAAM,MAAM,MAAMA;MAChB;MACA;QACE;QACA;QACA;QACA,KAAK;QACL;QACA;QACA;QACA;QACA;QACA;QACA,EAAE;QACF;QACA;MACF;MACA,EAAE,QAAQ,MAAM;IAClB;AACA,QAAI,IAAI,aAAa,GAAG;AACtB,YAAM,IAAI;QACR,oBAAoB,EAAE,YAAY,EAAE,MAAM,aAAa,IAAI,UAAU,IAAI,MAAM;MACjF;IACF;AACA,QAAI,YAAY,EAAE,cAAc,EAAE,MAAM,eAAe,IAAI,GAAG;AAK9D,UAAMA;MACJ;MACA;QACE;QACA;QACA;QACA,KAAK;QACL;QACA;QACA;QACA;QACA;QACA;MACF;MACA,EAAE,QAAQ,MAAM;IAClB;AACA,UAAMA;MACJ;MACA;QACE;QACA;QACA;QACA,KAAK;QACL;QACA;QACA;QACA;QACA;QACA;QACA;MACF;MACA,EAAE,QAAQ,MAAM;IAClB;EACF;AAGA,QAAM;IACJ,KAAK;IACL,KAAK,MAAM,IAAI,CAAC,OAAO;MACrB,MAAM,EAAE,KAAK;MACb,eAAe,EAAE;MACjB,iBAAiB,EAAE;IACrB,EAAE;IACF;EACF;AAKA,aAAW,KAAK,KAAK,OAAO;AAC1B,UAAM,KAAK,EAAE;AACb,QAAI,EAAE,UAAU;AACd,YAAM,YAAY,MAAMA;QACtB;QACA;UACE;UACA;UACA;UACA,KAAK;UACL;UACA;UACA;UACA;UACA;UACA;UACA,EAAE;QACJ;QACA,EAAE,QAAQ,MAAM;MAClB;AACA,UAAI,UAAU,aAAa,GAAG;AAC5B,cAAM,UAAU,MAAMA;UACpB;UACA;YACE;YACA;YACA;YACA,KAAK;YACL;YACA;YACA;YACA;YACA;YACA,EAAE;UACJ;UACA,EAAE,QAAQ,MAAM;QAClB;AACA,YAAI,QAAQ,aAAa,GAAG;AAC1B;YACE,kCAAkC,EAAE,KAAK,UAAU,UAAU,UAAU,UAAU,YAAY;UAC/F;QACF,OAAO;AACL,cAAI,uEAAkE,EAAE,EAAE;QAC5E;MACF,OAAO;AACL,YAAI,+CAA+C,EAAE,EAAE;MACzD;IACF;AACA,QAAI,EAAE,aAAa,SAAS,GAAG;AAC7B,YAAM,SAAS,MAAMA,OAAM,OAAO,CAAC,MAAM,EAAE,YAAY,UAAU,MAAM,KAAK,OAAO,GAAG,GAAG;QACvF,OAAO,EAAE,aAAa,QAAQ,OAAO,EAAE;QACvC,UAAU;QACV,QAAQ;MACV,CAAC;AACD,UAAI,OAAO,aAAa,GAAG;AACzB,YAAI,uCAAuC,EAAE,KAAK,YAAY,YAAY,OAAO,MAAM,EAAE;AACzF;MACF;AACA,YAAM,QAAQ,MAAMA;QAClB;QACA,CAAC,QAAQ,MAAM,UAAU,UAAU,KAAK,WAAW,OAAO,MAAM,IAAI,OAAO,GAAG;QAC9E,EAAE,OAAO,OAAO,QAAkB,QAAQ,MAAM;MAClD;AACA,UAAI,MAAM,aAAa,GAAG;AACxB,YAAI,qCAAqC,EAAE,YAAY,MAAM,MAAM,EAAE;MACvE,OAAO;AACL,cAAM,QAAQ,EAAE,aAAa,MAAM,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE;AACrE,YAAI,UAAU,OAAO,KAAK,CAAC,2BAA2B,EAAE,EAAE;MAC5D;IACF;EACF;AACF;AAUA,eAAsB,qBAAqB,MAIzB;AAChB,QAAM,MAAM,KAAK,UAAU,MAAM;EAAC;AAClC,QAAM,SAAS,MAAMA,OAAM,OAAO,CAAC,MAAM,KAAK,YAAY,OAAO,KAAK,GAAG,GAAG;IAC1E,UAAU;IACV,QAAQ;EACV,CAAC;AACD,MAAI,OAAO,aAAa,GAAG;AACzB,UAAM,IAAI,iBAAiB,UAAU,KAAK,UAAU,YAAY,OAAO,MAAM,EAAE;EACjF;AACA,QAAM,QAAQ,MAAMA;IAClB;IACA,CAAC,QAAQ,MAAM,UAAU,aAAa,KAAK,WAAW,OAAO,MAAM,cAAc,OAAO,GAAG;IAC3F,EAAE,OAAO,OAAO,QAAkB,QAAQ,MAAM;EAClD;AACA,MAAI,MAAM,aAAa,GAAG;AACxB,UAAM,IAAI,iBAAiB,uCAAuC,MAAM,MAAM,EAAE;EAClF;AACA,MAAI,0BAA0B,KAAK,UAAU,EAAE;AACjD;AASA,eAAsB,oBAAoB,MAGxB;AAChB,QAAM,SAAS,MAAMA;IACnB;IACA,CAAC,MAAM,KAAK,cAAc,YAAY,UAAU,WAAW,KAAK,eAAe;IAC/E,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,OAAO,aAAa,EAAG;AAC3B,QAAMA,OAAM,OAAO,CAAC,MAAM,KAAK,cAAc,YAAY,OAAO,GAAG,EAAE,QAAQ,MAAM,CAAC;AACtF;AAEA,SAAS,SAAS,GAAmB;AACnC,QAAM,IAAI,EAAE,YAAY,GAAG;AAC3B,SAAO,KAAK,IAAI,MAAM,EAAE,MAAM,GAAG,CAAC;AACpC;ACxbA,IAAM,eAAe;AACrB,IAAM,cAAc,CAAC,WAAW;AAChC,IAAM,YAAY;AAClB,IAAM,mBAAmB;AACzB,IAAM,UAAU;AAOT,IAAM,sBAAsB;AAenC,IAAI,SAA+B;AAOnC,eAAsB,iBAAyC;AAC7D,MAAI,WAAW,KAAM,QAAO;AAC5B,MAAI;AACF,UAAM,MAAM,MAAMA,OAAM,cAAc,aAAa,EAAE,QAAQ,MAAM,CAAC;AACpE,QAAI,IAAI,aAAa,GAAG;AACtB,eAAS,EAAE,WAAW,OAAO,cAAc,MAAM;AACjD,aAAO;IACT;AACA,aAAS;MACP,WAAW;MACX,UAAU,IAAI,UAAU,IAAI,KAAK,KAAK;MACtC,cAAc,MAAM,eAAe;IACrC;EACF,QAAQ;AACN,aAAS,EAAE,WAAW,OAAO,cAAc,MAAM;EACnD;AACA,SAAO;AACT;AAMO,SAAS,qBAA2B;AACzC,WAAS;AACX;AAOA,eAAsB,cAAc,MAAc,MAAgC;AAChF,MAAI;AACF,UAAM,IAAI,MAAMA,OAAM,cAAc,CAAC,WAAW,MAAM,OAAO,IAAI,CAAC,GAAG,EAAE,QAAQ,MAAM,CAAC;AACtF,WAAO,EAAE,aAAa;EACxB,QAAQ;AACN,WAAO;EACT;AACF;AAGA,eAAsB,gBAAgB,MAAgC;AACpE,MAAI;AACF,UAAM,IAAI,MAAMA,OAAM,cAAc,CAAC,WAAW,kBAAkB,IAAI,GAAG,EAAE,QAAQ,MAAM,CAAC;AAC1F,WAAO,EAAE,aAAa;EACxB,QAAQ;AACN,WAAO;EACT;AACF;AAOA,eAAsB,eAAe,MAA+B;AAClE,QAAM,WAAW,WAAW,IAAI;AAChC,MAAI;AACF,UAAM,IAAI,MAAMA,OAAM,cAAc,CAAC,SAAS,IAAI,GAAG,EAAE,QAAQ,MAAM,CAAC;AACtE,UAAM,OAAO,EAAE,UAAU,IAAI,KAAK;AAClC,QAAI,EAAE,aAAa,KAAK,eAAe,KAAK,GAAG,EAAG,QAAO;EAC3D,QAAQ;EAER;AACA,SAAO;AACT;AAGO,SAAS,sBAA8B;AAC5C,SAAO;AACT;AAGO,SAAS,oBAA4B;AAC1C,SAAO;AACT;AAyBO,SAAS,mBACd,SACA,MACwB;AACxB,SAAO;IACL,oBAAoB,6BAA6B,OAAO,cAAc,KAAK,SAAS;IACpF,mCAAmC;EACrC;AACF;AAGA,eAAsB,kBAAoC;AACxD,MAAI;AACF,UAAM,IAAI,MAAMA,OAAM,OAAO,CAAC,WAAW,MAAM,UAAU,GAAG,EAAE,QAAQ,MAAM,CAAC;AAC7E,WAAO,EAAE,aAAa;EACxB,QAAQ;AACN,WAAO;EACT;AACF;AAQA,eAAsB,qBAAuC;AAC3D,MAAI;AACF,UAAM,IAAI,MAAMA;MACd;MACA,CAAC,SAAS,SAAS,YAAY,MAAM,OAAO,mBAAmB,CAAC;MAChE,EAAE,QAAQ,MAAM;IAClB;AACA,WAAO,EAAE,aAAa;EACxB,QAAQ;AACN,WAAO;EACT;AACF;AAQA,SAAS,6BAAuC;AAC9C,QAAM,MAAM,QAAQ,IAAI,oBAAoB;AAC5C,MAAI,OAAO,IAAI,KAAK,EAAE,SAAS,EAAG,QAAO,CAAC,IAAI,KAAK,CAAC;AACpD,SAAO,CAAC,iBAAiBD,MAAKD,SAAQ,GAAG,WAAW,CAAC;AACvD;AAQA,SAAS,SAAS,KAAsB;AACtC,MAAI,CAAC,OAAO,SAAS,GAAG,KAAK,OAAO,EAAG,QAAO;AAC9C,MAAI;AACF,YAAQ,KAAK,KAAK,CAAC;AACnB,WAAO;EACT,SAAS,KAAK;AACZ,WAAQ,IAA8B,SAAS;EACjD;AACF;AAGA,eAAe,aAAa,KAAqC;AAC/D,MAAI;AACF,UAAM,MAAM,MAAMK,WAASJ,MAAK,KAAK,WAAW,GAAG,MAAM;AACzD,UAAM,MAAM,OAAO,SAAS,IAAI,KAAK,GAAG,EAAE;AAC1C,WAAO,OAAO,SAAS,GAAG,KAAK,MAAM,IAAI,MAAM;EACjD,QAAQ;AACN,WAAO;EACT;AACF;AAQA,eAAe,2BAAmD;AAChE,aAAW,OAAO,2BAA2B,GAAG;AAC9C,UAAM,MAAM,MAAM,aAAa,GAAG;AAClC,QAAI,QAAQ,QAAQ,SAAS,GAAG,EAAG,QAAO;EAC5C;AACA,SAAO;AACT;AAYA,eAAsB,4BAA4B,UAAoC;AACpF,MAAI,YAAY,SAAS,KAAK,EAAE,SAAS,EAAG,QAAO,SAAS,KAAK;AACjE,QAAM,MAAM,QAAQ,IAAI,oBAAoB;AAC5C,MAAI,OAAO,IAAI,KAAK,EAAE,SAAS,EAAG,QAAO,IAAI,KAAK;AAClD,QAAM,OAAO,MAAM,yBAAyB;AAC5C,MAAI,KAAM,QAAO;AACjB,QAAM,OAAOA,MAAKD,SAAQ,GAAG,WAAW;AACxC,MAAI,WAAW,IAAI,EAAG,QAAO;AAC7B,MAAI,WAAW,eAAe,EAAG,QAAO;AACxC,SAAO;AACT;AAOA,eAAe,iBAAmC;AAChD,MAAK,MAAM,yBAAyB,MAAO,KAAM,QAAO;AACxD,MAAI;AACF,UAAM,IAAI,MAAME,OAAM,SAAS,CAAC,MAAM,gBAAgB,GAAG,EAAE,QAAQ,MAAM,CAAC;AAC1E,WAAO,EAAE,aAAa,MAAM,EAAE,UAAU,IAAI,KAAK,EAAE,SAAS;EAC9D,QAAQ;AACN,WAAO;EACT;AACF;ACnSO,IAAM,oBAAoB;AAEjC,IAAM,OAAOgB,SAAQ,cAAc,YAAY,GAAG,CAAC;AAcnD,SAAS,qBAA8D;AACrE,QAAM,WAAW,QAAQ,IAAI;AAC7B,MAAI,YAAYC,YAAWC,SAAQ,UAAU,gBAAgB,CAAC,GAAG;AAC/D,WAAO,EAAE,YAAYA,SAAQ,UAAU,gBAAgB,GAAG,SAAS,SAAS;EAC9E;AACA,QAAM,SAASA,SAAQ,MAAM,MAAM,WAAW,QAAQ;AACtD,MAAID,YAAWC,SAAQ,QAAQ,gBAAgB,CAAC,GAAG;AACjD,WAAO,EAAE,YAAYA,SAAQ,QAAQ,gBAAgB,GAAG,SAAS,OAAO;EAC1E;AAGA,QAAM,cAAcA,SAAQ,MAAM,IAAI;AACtC,SAAO;IACL,YAAYA,SAAQ,aAAa,gBAAgB;IACjD,SAASA,SAAQ,aAAa,MAAM,IAAI;EAC1C;AACF;AAEA,IAAM,EAAE,YAAY,0BAA0B,SAAS,2BAA2B,IAChF,mBAAmB;AACd,IAAM,kBAAkB;AACxB,IAAM,oBAAoB;AAEjC,eAAsB,YAAY,KAA+B;AAC/D,QAAM,SAAS,MAAMlB,OAAM,UAAU,CAAC,SAAS,WAAW,GAAG,GAAG,EAAE,QAAQ,MAAM,CAAC;AACjF,SAAO,OAAO,aAAa;AAC7B;AAmBA,eAAsB,UAAU,MAAc,mBAAuC;AACnF,QAAM,SAAS,MAAMA;IACnB;IACA,CAAC,SAAS,WAAW,YAAY,0BAA0B,GAAG;IAC9D,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,OAAO,aAAa,EAAG,QAAO,EAAE,KAAK,QAAQ,MAAM;AACvD,QAAM,CAAC,SAAS,SAAS,IAAI,OAAO,OAAO,KAAK,EAAE,MAAM,GAAG;AAC3D,QAAM,YAAY,UAAU,OAAO,SAAS,SAAS,EAAE,IAAI;AAC3D,SAAO;IACL;IACA,QAAQ;IACR,WAAW,OAAO,SAAS,SAAS,IAAI,YAAY;IACpD,WAAW,aAAa,UAAU,SAAS,IAAI,YAAY;EAC7D;AACF;AASA,eAAsB,WAAW,OAA0B,CAAC,GAAoB;AAC9E,QAAM,MAAM,KAAK,OAAO;AACxB,QAAM,aAAa,KAAK,cAAc;AACtC,QAAM,aAAa,KAAK,cAAc;AAEtC,QAAM,aAAaA,OAAM,UAAU,CAAC,SAAS,MAAM,KAAK,MAAM,YAAY,UAAU,GAAG;IACrF,QAAQ;IACR,QAAQ;EACV,CAAC;AAED,MAAI,KAAK,YAAY;AACnB,UAAM,UAAU,CAAC,UAAiC;AAChD,YAAM,OAAO,OAAO,UAAU,WAAW,QAAQ,MAAM,SAAS,MAAM;AACtE,iBAAW,QAAQ,KAAK,MAAM,OAAO,GAAG;AACtC,YAAI,KAAK,SAAS,EAAG,MAAK,aAAa,IAAI;MAC7C;IACF;AACA,eAAW,QAAQ,GAAG,QAAQ,OAAO;AACrC,eAAW,QAAQ,GAAG,QAAQ,OAAO;EACvC;AAEA,QAAM;AACN,SAAO;AACT;AAUA,eAAsB,YACpB,MAAc,mBACd,OAA2B,CAAC,GACc;AAC1C,MAAI,MAAM,YAAY,GAAG,GAAG;AAC1B,WAAO,EAAE,KAAK,OAAO,MAAM;EAC7B;AACA,QAAM,WAAW;IACf;IACA,YAAY,KAAK;IACjB,YAAY,KAAK;IACjB,YAAY,KAAK;EACnB,CAAC;AACD,SAAO,EAAE,KAAK,OAAO,KAAK;AAC5B;AC7HO,IAAM,eAAoC,oBAAI,IAAI;EACvD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AACF,CAAC;AAEM,IAAM,iBAAiBD,MAAKD,SAAQ,GAAG,aAAa,WAAW;AAQ/D,SAAS,gBAAgB,KAAkE;AAChG,QAAM,WAAWqB,iBAAiB,IAAI,IAAI;AAC1C,QAAM,IAAI,IAAI;AACd,QAAM,UACJ,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC,KAAK,IAAI,IAC/C,GAAG,IAAI,EAAE,IAAI,OAAO,CAAC,CAAC,IAAI,QAAQ,KAClC,GAAG,IAAI,EAAE,IAAI,QAAQ;AAC3B,SAAOpB,MAAK,gBAAgB,OAAO;AACrC;AAOA,eAAsB,iBACpB,MACA,WAAgC,cACb;AACnB,QAAM,UAAoB,CAAC;AAC3B,QAAM,OAAO,OAAO,QAA+B;AACjD,QAAI;AACJ,QAAI;AACF,gBAAU,MAAMG,UAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;IACtD,QAAQ;AACN;IACF;AACA,eAAW,SAAS,SAAS;AAC3B,UAAI,CAAC,MAAM,YAAY,EAAG;AAC1B,YAAM,MAAMH,MAAK,KAAK,MAAM,IAAI;AAChC,UAAI,SAAS,IAAI,MAAM,IAAI,GAAG;AAC5B,gBAAQ,KAAK,GAAG;AAChB;MACF;AACA,YAAM,KAAK,GAAG;IAChB;EACF;AACA,QAAM,KAAK,IAAI;AACf,SAAO;AACT;AAqBA,eAAsB,eAAe,MAA4D;AAC/F,QAAM,SAASmB,UAAQ,KAAK,MAAM;AAClC,QAAM,cAAcA,UAAQ,KAAK,WAAW;AAC5C,QAAM,WAAW,KAAK,YAAY;AAElC,QAAMP,OAAM,gBAAgB,EAAE,WAAW,KAAK,CAAC;AAG/C,QAAM,SAAS,SAAS,MAAM,WAAW,CAAC,KAAK,IAAI,CAAC,IAAI;AACxD,QAAMX,QAAM,MAAM,CAAC,GAAG,QAAQ,GAAG,MAAM,KAAK,WAAW,CAAC;AAExD,QAAM,UAAU,MAAM,iBAAiB,aAAa,QAAQ;AAC5D,QAAM,QAAQ,IAAI,QAAQ,IAAI,CAAC,MAAMI,KAAG,GAAG,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC,CAAC,CAAC;AAE7E,SAAO,EAAE,aAAa,aAAa,QAAQ;AAC7C;ACjGO,IAAM,mBAAmBL,MAAKD,SAAQ,GAAG,aAAa,aAAa;AAOnE,IAAM,0BAA0B;AAYhC,SAAS,mBAAmB,aAAqB,MAAsB;AAC5E,QAAM,WAAWqB,iBAAiBX,UAAS,WAAW,CAAC;AACvD,SAAO,GAAG,uBAAuB,GAAG,gBAAgB,WAAW,CAAC,IAAI,QAAQ,IAAI,IAAI;AACtF;AAwCO,SAAS,sBAAsB,aAA6B;AACjE,SAAOT,MAAK,kBAAkB,kBAAkB,WAAW,CAAC;AAC9D;AAEA,SAAS,cAAc,aAAqB,MAAsB;AAChE,SAAOA,MAAK,sBAAsB,WAAW,GAAG,IAAI;AACtD;AAEA,eAAe,aAAa,KAAiD;AAC3E,MAAI;AACF,UAAM,MAAM,MAAMI,UAASJ,MAAK,KAAK,eAAe,GAAG,MAAM;AAC7D,UAAM,IAAI,KAAK,MAAM,GAAG;AACxB,QAAI,EAAE,WAAW,EAAG,QAAO;AAC3B,WAAO;EACT,QAAQ;AACN,WAAO;EACT;AACF;AAEA,eAAsB,gBAAgB,aAAgD;AACpF,QAAM,OAAO,sBAAsB,WAAW;AAC9C,MAAI;AACJ,MAAI;AACF,eAAW,MAAMG,UAAQ,MAAM,EAAE,eAAe,KAAK,CAAC,GACnD,OAAO,CAAC,MAAM,EAAE,YAAY,CAAC,EAC7B,IAAI,CAAC,MAAM,EAAE,IAAI;EACtB,QAAQ;AACN,WAAO,CAAC;EACV;AACA,QAAM,MAAwB,CAAC;AAC/B,aAAW,QAAQ,SAAS;AAC1B,UAAM,MAAMH,MAAK,MAAM,IAAI;AAC3B,UAAM,WAAW,MAAM,aAAa,GAAG;AACvC,QAAI,SAAU,KAAI,KAAK,EAAE,MAAM,KAAK,SAAS,CAAC;EAChD;AACA,MAAI,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,UAAU,cAAc,EAAE,SAAS,SAAS,CAAC;AAC3E,SAAO;AACT;AAEA,eAAsB,kBACpB,aACA,KACgC;AAChC,QAAM,MAAM,cAAc,aAAa,GAAG;AAC1C,QAAM,WAAW,MAAM,aAAa,GAAG;AACvC,MAAI,CAAC,SAAU,QAAO;AACtB,SAAO,EAAE,MAAM,KAAK,KAAK,SAAS;AACpC;AAaA,eAAsB,0BAA6C;AACjE,MAAI;AACJ,MAAI;AACF,mBAAe,MAAMG,UAAQ,kBAAkB,EAAE,eAAe,KAAK,CAAC,GACnE,OAAO,CAAC,MAAM,EAAE,YAAY,CAAC,EAC7B,IAAI,CAAC,MAAM,EAAE,IAAI;EACtB,QAAQ;AACN,WAAO,CAAC;EACV;AACA,QAAM,MAAM,oBAAI,IAAY;AAC5B,aAAW,QAAQ,aAAa;AAC9B,UAAM,WAAWH,MAAK,kBAAkB,IAAI;AAC5C,QAAI;AACJ,QAAI;AACF,eAAS,MAAMG,UAAQ,UAAU,EAAE,eAAe,KAAK,CAAC,GACrD,OAAO,CAAC,MAAM,EAAE,YAAY,CAAC,EAC7B,IAAI,CAAC,MAAM,EAAE,IAAI;IACtB,QAAQ;AACN;IACF;AACA,eAAW,QAAQ,OAAO;AACxB,YAAM,WAAW,MAAM,aAAaH,MAAK,UAAU,IAAI,CAAC;AACxD,UAAI,SAAU,KAAI,IAAI,SAAS,KAAK;IACtC;EACF;AACA,SAAO,MAAM,KAAK,GAAG;AACvB;AAEA,eAAsB,iBAAiB,aAAqB,KAA+B;AACzF,QAAM,MAAM,cAAc,aAAa,GAAG;AAC1C,QAAM,WAAW,MAAM,aAAa,GAAG;AACvC,MAAI,CAAC,SAAU,QAAO;AACtB,QAAMK,IAAG,KAAK,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAI9C,QAAM,YAAY,SAAS,OAAO,EAAE,OAAO,KAAK,CAAC;AACjD,SAAO;AACT;AAOO,SAAS,0BAA0B,eAAyB,SAAyB;AAC1F,QAAM,KAAK,IAAI,OAAO,IAAI,QAAQ,QAAQ,uBAAuB,MAAM,CAAC,UAAU;AAClF,MAAI,MAAM;AACV,aAAW,KAAK,eAAe;AAC7B,UAAM,IAAI,GAAG,KAAK,CAAC;AACnB,QAAI,EAAG,OAAM,KAAK,IAAI,KAAK,OAAO,EAAE,CAAC,CAAC,CAAC;EACzC;AACA,SAAO,GAAG,OAAO,IAAI,OAAO,MAAM,CAAC,CAAC;AACtC;AAEA,eAAe,mBAAmB,aAAqB,SAAkC;AACvF,QAAM,WAAW,MAAM,gBAAgB,WAAW;AAClD,SAAO;IACL,SAAS,IAAI,CAAC,MAAM,EAAE,IAAI;IAC1B;EACF;AACF;AAEA,SAAS,WAAW,KAAwB;AAC1C,SAAO,IAAI,kBAAkB,MAAM,UAAU;AAC/C;AA4BA,eAAe,WAAW,WAAmB,KAA4C;AACvF,QAAM,IAAI,MAAM,UAAU,WAAW,CAAC,4CAA4C,GAAG;IACnF,MAAM;EACR,CAAC;AACD,MAAI,EAAE,aAAa,GAAG;AACpB,QAAI,sCAAsC,OAAO,EAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,MAAM,GAAG,GAAG,CAAC,EAAE;EAC3F;AACF;AAOA,eAAe,mBAAmB,UAA8C;AAC9E,QAAM,IAAI,MAAMJ,QAAM,UAAU,CAAC,SAAS,WAAW,QAAQ,GAAG,EAAE,QAAQ,MAAM,CAAC;AACjF,MAAI,EAAE,aAAa,GAAG;AACpB,UAAM,IAAI,gBAAgB,wBAAwB,QAAQ,WAAW,EAAE,QAAQ,EAAE,MAAM;EACzF;AACA,QAAM,SAAS,KAAK,MAAM,EAAE,MAAM;AAClC,MAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,OAAO,WAAW,KAAK,CAAC,OAAO,CAAC,GAAG,QAAQ;AACvE,UAAM,IAAI,gBAAgB,6CAA6C,QAAQ,IAAI,EAAE,QAAQ,EAAE;EACjG;AACA,SAAO,OAAO,CAAC,EAAE;AACnB;AAkBA,IAAM,wBAAwB,oBAAI,IAAI;EACpC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AACF,CAAC;AAWD,SAAS,uBAAuB,KAAkC;AAChE,QAAM,QAAkB,CAAC;AACzB,aAAW,MAAM,IAAI,OAAO,CAAC,GAAG;AAC9B,UAAM,KAAK,GAAG,QAAQ,GAAG;AACzB,QAAI,MAAM,EAAG;AACb,UAAM,IAAI,GAAG,MAAM,GAAG,EAAE;AACxB,QAAI,sBAAsB,IAAI,CAAC,EAAG;AAClC,UAAM,IAAI,GAAG,MAAM,KAAK,CAAC;AACzB,UAAM,KAAK,OAAO,CAAC,IAAI,gBAAgB,CAAC,CAAC,EAAE;EAC7C;AACA,MAAI,IAAI,WAAY,OAAM,KAAK,WAAW,IAAI,UAAU,EAAE;AAC1D,MAAI,IAAI,KAAM,OAAM,KAAK,QAAQ,IAAI,IAAI,EAAE;AAC3C,aAAW,KAAK,OAAO,KAAK,IAAI,gBAAgB,CAAC,CAAC,EAAG,OAAM,KAAK,UAAU,EAAE,QAAQ,QAAQ,EAAE,CAAC,EAAE;AACjG,MAAI,IAAI,cAAc,IAAI,WAAW,SAAS,GAAG;AAC/C,UAAM,KAAK,cAAc,KAAK,UAAU,IAAI,UAAU,CAAC,EAAE;EAC3D;AACA,MAAI,IAAI,OAAO,IAAI,IAAI,SAAS,GAAG;AACjC,UAAM,KAAK,OAAO,KAAK,UAAU,IAAI,GAAG,CAAC,EAAE;EAC7C;AACA,SAAO;AACT;AAGA,SAAS,gBAAgB,GAAmB;AAC1C,MAAI,0BAA0B,KAAK,CAAC,EAAG,QAAO;AAC9C,SAAO,IAAI,EAAE,QAAQ,OAAO,MAAM,EAAE,QAAQ,MAAM,KAAK,CAAC;AAC1D;AAiBA,eAAsB,iBAAiB,MAAwD;AAC7F,QAAM,MAAM,KAAK,UAAU,MAAM;EAAC;AAClC,QAAM,EAAE,IAAI,IAAI;AAEhB,QAAM,OACJ,KAAK,WAAW,QAAQ,WAAW,GAAG,KAAK,KAAK,YAAY,cAAc;AAC5E,QAAM,OAAO,KAAK,QAAS,MAAM,mBAAmB,KAAK,aAAa,IAAI,IAAI;AAC9E,QAAM,MAAM,cAAc,KAAK,aAAa,IAAI;AAChD,QAAM,WAAW,MAAM,aAAa,GAAG;AACvC,MAAI,UAAU;AACZ,QAAI,KAAK,SAAS;AAChB,UAAI,iCAAiC,IAAI,aAAa,SAAS,SAAS,GAAG;AAC3E,YAAM,iBAAiB,KAAK,aAAa,IAAI;IAC/C,OAAO;AAKL,YAAM,IAAI;QACR,cAAc,IAAI,4BAA4B,SAAS,SAAS;QAChE;QACA;MACF;IACF;EACF;AACA,QAAM,MAAM,mBAAmB,KAAK,aAAa,IAAI;AACrD,QAAMW,OAAM,KAAK,EAAE,WAAW,KAAK,CAAC;AAEpC,MAAI,iCAAiC,IAAI,SAAS,EAAE;AACpD,QAAM,WAAW,IAAI,WAAW,GAAG;AAEnC,MAAI,SAAS,WAAW;AACtB,QAAI,iBAAiB,IAAI,SAAS,OAAO,GAAG,YAAY;AACxD,UAAM,IAAI,MAAMX,QAAM,UAAU,CAAC,UAAU,IAAI,WAAW,GAAG,GAAG,EAAE,QAAQ,MAAM,CAAC;AACjF,QAAI,EAAE,aAAa,GAAG;AACpB,YAAM,IAAI,gBAAgB,4BAA4B,IAAI,SAAS,IAAI,EAAE,QAAQ,EAAE,MAAM;IAC3F;EACF,OAAO;AACL,QAAI,iBAAiB,IAAI,SAAS,qCAAqC;AAGvE,UAAM,eAAe,GAAG,GAAG;AAC3B,UAAM,SAAS,MAAMA,QAAM,UAAU,CAAC,UAAU,IAAI,WAAW,YAAY,GAAG;MAC5E,QAAQ;IACV,CAAC;AACD,QAAI,OAAO,aAAa,GAAG;AACzB,YAAM,IAAI,gBAAgB,uCAAuC,OAAO,QAAQ,OAAO,MAAM;IAC/F;AACA,QAAI;AACF,YAAM,aAAa,cAAc,KAAK,GAAG;IAC3C,UAAA;AAIE,YAAM,YAAY,cAAc,EAAE,OAAO,KAAK,CAAC;IACjD;EACF;AAEA,QAAM,QAAkC,IAAI,gBAAgB,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM,IACzF,aACA;AACJ,QAAM,WAA+B;IACnC,QAAQ;IACR;IACA;IACA,OAAO;;IAEP,SAAS,SAAS,YAAa,IAAI,kBAAkB,SAAS,CAAC,IAAK,CAAC;IACrE;IACA,aAAa,IAAI;IACjB,eAAe,IAAI;IACnB,WAAW,IAAI;IACf,YAAW,oBAAI,KAAK,GAAE,YAAY;EACpC;AACA,QAAMM,YAAUP,MAAK,KAAK,eAAe,GAAG,KAAK,UAAU,UAAU,MAAM,CAAC,IAAI,MAAM,MAAM;AAE5F,MAAI,KAAK,YAAY;AAGnB,UAAM,eAAe,WAAW,+BAA+B,MAAM,KAAK,WAAW;AACrF,QAAI,mEAAmE,IAAI,EAAE;EAC/E;AAEA,SAAO,EAAE,MAAM,KAAK,SAAS;AAC/B;AAOA,eAAe,aACb,WACA,SACA,KACe;AAEf,QAAM,UAAU,oBAAoB,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC;AAC3D,QAAM,SAAS,MAAMC;IACnB;IACA,CAAC,UAAU,UAAU,SAAS,WAAW,SAAS,GAAG;IACrD,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,OAAO,aAAa,GAAG;AACzB,UAAM,IAAI,gBAAgB,oCAAoC,OAAO,QAAQ,OAAO,MAAM;EAC5F;AACA,QAAM,UAAU,MAAMC,SAAQF,MAAKQ,QAAO,GAAG,mBAAmB,CAAC;AACjE,MAAI;AACF,UAAM,aAAaR,MAAK,SAAS,YAAY;AAC7C,QAAI,uBAAuB,SAAS,OAAO,UAAU,EAAE;AACvD,UAAM,MAAM,MAAMC,QAAM,UAAU,CAAC,UAAU,MAAM,YAAY,OAAO,GAAG,EAAE,QAAQ,MAAM,CAAC;AAC1F,QAAI,IAAI,aAAa,GAAG;AACtB,YAAM,IAAI,gBAAgB,wBAAwB,IAAI,QAAQ,IAAI,MAAM;IAC1E;AAMA,UAAM,MAAM,MAAM,mBAAmB,SAAS;AAC9C,UAAM,QAAQ;MACZ;;MAEA;MACA,GAAG,uBAAuB,GAAG;IAC/B;AACA,UAAMM,YAAUP,MAAK,SAAS,YAAY,GAAG,MAAM,KAAK,IAAI,IAAI,MAAM,MAAM;AAE5E,QAAI,sBAAsB,OAAO,iCAAiC;AAClE,UAAM,QAAQ,MAAMC;MAClB;MACA,CAAC,SAAS,MAAM,SAAS,MAAMD,MAAK,SAAS,YAAY,GAAG,OAAO;MACnE,EAAE,QAAQ,MAAM;IAClB;AACA,QAAI,MAAM,aAAa,GAAG;AACxB,YAAM,IAAI,gBAAgB,+BAA+B,MAAM,QAAQ,MAAM,MAAM;IACrF;EACF,UAAA;AACE,UAAMC,QAAM,UAAU,CAAC,MAAM,MAAM,OAAO,GAAG,EAAE,QAAQ,MAAM,CAAC;AAC9D,UAAMI,IAAG,SAAS,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;EACpD;AACF;AAEO,IAAM,kBAAN,cAA8B,MAAM;EACzC,YACE,SACgB,QACA,QAChB;AACA,UAAM,GAAG,OAAO,GAAG,SAAS,KAAK,OAAO,KAAK,CAAC,KAAK,EAAE,EAAE;AAHvC,SAAA,SAAA;AACA,SAAA,SAAA;AAGhB,SAAK,OAAO;EACd;EALkB;EACA;AAKpB;AC3dA,IAAM,iBAAiB;AAQvB,eAAsB,gBACpB,WACA,gBACA,YAAY,KACc;AAK1B,QAAM,UAAU,YAAY,eAAe,QAAQ,YAAY,EAAE,CAAC,kCAAkC,cAAc;AAClH,QAAM,SAAS,MAAM,UAAU,WAAW,CAAC,MAAM,MAAM,OAAO,GAAG;IAC/D,MAAM;IACN,QAAQ;EACV,CAAC;AACD,MAAI,OAAO,aAAa,GAAG;AACzB,WAAO,EAAE,IAAI,OAAO,QAAQ,uBAAuB,OAAO,UAAU,OAAO,MAAM,GAAG;EACtF;AAEA,QAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,SAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,QAAI,MAAMS,YAAW,cAAc,EAAG,QAAO,EAAE,IAAI,KAAK;AACxD,UAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;EAC7C;AACA,SAAO;IACL,IAAI;IACJ,QAAQ,UAAU,cAAc,0BAA0B,OAAO,SAAS,CAAC;EAC7E;AACF;AAEA,eAAeA,YAAW,GAA6B;AACrD,MAAI;AACF,UAAMR,MAAK,CAAC;AACZ,WAAO;EACT,QAAQ;AACN,WAAO;EACT;AACF;ACjDA,eAAsB,gBACpB,WACA,KACuD;AACvD,QAAM,OAAO,iBAAiB,GAAG;AACjC,QAAM,SAAS,MAAML;IACnB;IACA,CAAC,QAAQ,UAAU,QAAQ,MAAM,WAAW,MAAM,MAAM,0CAA0C;IAClG,EAAE,OAAO,MAAM,QAAQ,MAAM;EAC/B;AACA,MAAI,OAAO,aAAa,GAAG;AACzB,WAAO;MACL,IAAI;MACJ,QAAQ,4BAA4B,OAAO,OAAO,QAAQ,CAAC,OAAO,OAAO,UAAU,IAAI,SAAS,EAAE,MAAM,GAAG,GAAG,CAAC;IACjH;EACF;AACA,SAAO,EAAE,IAAI,KAAK;AACpB;AAIO,SAAS,iBAAiB,KAAqC;AACpE,QAAM,QAAkB,CAAC;AACzB,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,GAAG,GAAG;AACxC,UAAM,KAAK,GAAG,CAAC,IAAI,iBAAiB,CAAC,CAAC,EAAE;EAC1C;AACA,SAAO,MAAM,KAAK,IAAI,IAAI;AAC5B;AAEA,SAAS,iBAAiB,GAAmB;AAC3C,SAAO,IAAI,EAAE,QAAQ,MAAM,OAAO,CAAC;AACrC;ACrBA,eAAsB,wBAAwB,WAAkC;AAC9E,QAAMA;IACJ;IACA;MACE;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA;IACF;IACA,EAAE,QAAQ,MAAM;EAClB;AACF;AClBA,IAAMY,cAAYb,MAAKD,SAAQ,GAAG,WAAW;AAC7C,IAAM,WAAWC,MAAKa,aAAW,WAAW;AAC5C,IAAM,WAAWb,MAAKa,aAAW,WAAW;AAU5C,IAAM,OAAO;AACb,IAAM,WAA0B;;;;;EAK9B,KAAK,+BAA+B,OAAO,IAAI,CAAC;EAChD,SAAS,oBAAoB,OAAO,IAAI,CAAC;EACzC,MAAM;AACR;AAgBA,eAAsB,YAAY,OAA2B,CAAC,GAA2B;AACvF,QAAM,MAAM,KAAK,UAAU,MAAM;EAAC;AAClC,QAAMD,OAAMC,aAAW,EAAE,WAAW,KAAK,CAAC;AAM1C,MAAI,MAAM,gBAAgB,oBAAoB,GAAG;AAC/C,UAAM,gBAAgB,oBAAoB;AAC1C,QAAI,kCAAkC,oBAAoB,EAAE;EAC9D;AAEA,MAAI,MAAM,YAAY,GAAG,GAAG;AAC1B,WAAO;EACT;AAEA,QAAM,cAAc,MAAM,YAAY;AACtC,MAAI,gBAAgB,QAAS,MAAM,aAAa,WAAW,GAAI;AAI7D,aAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,UAAI,MAAM,YAAY,GAAG,EAAG,QAAO;AACnC,YAAMQ,OAAM,GAAG;IACjB;AACA,QAAI,aAAa,OAAO,WAAW,CAAC,2DAAsD;AAC1F,WAAO;EACT;AACA,MAAI,gBAAgB,MAAM;AACxB,UAAM,OAAO,QAAQ,EAAE,MAAM,MAAM;IAAC,CAAC;EACvC;AAEA,QAAM,WAAW,gBAAgB;AACjC,QAAM,QAAQ,SAAS,UAAU,GAAG;AAIpC,QAAM,WAAW,gBAAgB;AACjC,QAAM,QAAQ;IACZ,QAAQ;IACR,CAAC,UAAU,SAAS,UAAU,OAAO,IAAI,GAAG,UAAU,SAAS;IAC/D;MACE,UAAU;MACV,OAAO,CAAC,UAAU,OAAO,KAAK;MAC9B,KAAK;QACH,GAAG,QAAQ;QACX,GAAI,WAAW,EAAE,oBAAoB,SAAS,IAAI,CAAC;MACrD;IACF;EACF;AACA,QAAM,MAAM;AACZ,MAAI,OAAO,MAAM,QAAQ,UAAU;AACjC,UAAMd,YAAU,UAAU,OAAO,MAAM,GAAG,GAAG,MAAM;AACnD,QAAI,mCAAmC,OAAO,MAAM,GAAG,CAAC,UAAU,OAAO,IAAI,CAAC,GAAG;EACnF;AAEA,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,QAAI,MAAM,YAAY,GAAG,GAAG;AAC1B,UAAI,sBAAsB,SAAS,OAAO,EAAE;AAC5C,aAAO;IACT;AACA,UAAMc,OAAM,GAAG;EACjB;AACA,QAAM,IAAI;IACR,qCAAqC,SAAS,OAAO,mBAAmB,QAAQ;EAClF;AACF;AAWA,SAAS,kBAA0B;AACjC,QAAM,WAAW,QAAQ,IAAI;AAC7B,MAAI,YAAYH,YAAW,QAAQ,EAAG,QAAO;AAC7C,QAAMI,QAAOL,UAAQM,eAAc,YAAY,GAAG,CAAC;AACnD,QAAM,aAAa;IACjBJ,SAAQG,OAAM,MAAM,WAAW,SAAS,SAAS;IACjDH,SAAQG,OAAM,MAAM,MAAM,SAAS,QAAQ,SAAS;IACpDH,SAAQG,OAAM,MAAM,MAAM,MAAM,aAAa,SAAS,QAAQ,SAAS;IACvEH,SAAQG,OAAM,MAAM,MAAM,gBAAgB,aAAa,SAAS,QAAQ,SAAS;EACnF;AACA,aAAW,KAAK,YAAY;AAC1B,QAAIJ,YAAW,CAAC,EAAG,QAAO;EAC5B;AACA,QAAM,IAAI;IACR;IAAmD,WAAW,KAAK,MAAM,CAAC;EAC5E;AACF;AASA,SAAS,kBAAiC;AACxC,QAAM,WAAW,QAAQ,IAAI;AAC7B,MAAI,YAAYA,YAAW,QAAQ,EAAG,QAAO;AAC7C,QAAMI,QAAOL,UAAQM,eAAc,YAAY,GAAG,CAAC;AACnD,QAAM,aAAa;;;IAGjBJ,SAAQG,OAAM,UAAU;IACxBH,SAAQG,OAAM,MAAM,MAAM,MAAM,QAAQ,OAAO,QAAQ,UAAU;IACjEH,SAAQG,OAAM,MAAM,MAAM,MAAM,MAAM,QAAQ,UAAU;EAC1D;AACA,aAAW,KAAK,YAAY;AAC1B,QAAIJ,YAAW,CAAC,EAAG,QAAO;EAC5B;AACA,SAAO;AACT;AAiBA,eAAsB,YAAsC;AAC1D,QAAM,MAAM,MAAM,YAAY;AAC9B,MAAI,QAAQ,MAAM;AAChB,WAAO,EAAE,SAAS,OAAO,KAAK,KAAK;EACrC;AACA,MAAI,CAAE,MAAM,aAAa,GAAG,GAAI;AAC9B,UAAM,OAAO,QAAQ,EAAE,MAAM,MAAM;IAAC,CAAC;AACrC,WAAO,EAAE,SAAS,OAAO,IAAI;EAC/B;AACA,MAAI;AACF,YAAQ,KAAK,KAAK,SAAS;EAC7B,QAAQ;EAER;AACA,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,QAAI,CAAE,MAAM,aAAa,GAAG,EAAI;AAChC,UAAMG,OAAM,GAAG;EACjB;AACA,MAAI,MAAM,aAAa,GAAG,GAAG;AAC3B,QAAI;AACF,cAAQ,KAAK,KAAK,SAAS;IAC7B,QAAQ;IAER;EACF;AACA,QAAM,OAAO,QAAQ,EAAE,MAAM,MAAM;EAAC,CAAC;AACrC,SAAO,EAAE,SAAS,MAAM,IAAI;AAC9B;AA6BA,eAAsB,iBAAuC;AAC3D,QAAM,MAAM,MAAM,YAAY;AAC9B,QAAMG,YAAW,QAAQ,QAAS,MAAM,aAAa,GAAG;AACxD,QAAM,SAAS,MAAM,aAAa,GAAG;AACrC,SAAO;IACL,SAAS,WAAW;IACpB;IACA,UAAAA;IACA,MAAM;IACN,UAAU;IACV,QAAQ,WAAW,OAAO,OAAO,EAAE,OAAO,OAAO,OAAO,QAAQ,OAAO,OAAO;IAC9E,SAAS;IACT,SAAS;EACX;AACF;AAEA,SAAS,YAAY,WAAqC;AACxD,SAAO,IAAI,QAAiB,CAAC,aAAa;AACxC,UAAM,MAAM;MACV,EAAE,MAAM,aAAa,MAAM,MAAM,QAAQ,OAAO,MAAM,YAAY,SAAS,UAAU;MACrF,CAAC,QAAQ;AACP,YAAI,OAAO;AACX,cAAM,SAAS,IAAI,cAAc;AACjC,iBAAS,UAAU,OAAO,SAAS,GAAG;MACxC;IACF;AACA,QAAI,GAAG,SAAS,MAAM,SAAS,KAAK,CAAC;AACrC,QAAI,GAAG,WAAW,MAAM;AACtB,UAAI,QAAQ;AACZ,eAAS,KAAK;IAChB,CAAC;AACD,QAAI,IAAI;EACV,CAAC;AACH;AAQA,SAAS,aAAa,WAAgD;AACpE,SAAO,IAAI,QAA4B,CAAC,aAAa;AACnD,UAAM,MAAM;MACV,EAAE,MAAM,aAAa,MAAM,MAAM,QAAQ,OAAO,MAAM,YAAY,SAAS,UAAU;MACrF,CAAC,QAAQ;AACP,cAAM,SAAS,IAAI,cAAc;AACjC,YAAI,SAAS,OAAO,UAAU,KAAK;AACjC,cAAI,OAAO;AACX,mBAAS,IAAI;AACb;QACF;AACA,cAAM,SAAmB,CAAC;AAC1B,YAAI,GAAG,QAAQ,CAAC,MAAc,OAAO,KAAK,CAAC,CAAC;AAC5C,YAAI,GAAG,OAAO,MAAM;AAClB,cAAI;AACF,kBAAM,SAAS,KAAK,MAAM,OAAO,OAAO,MAAM,EAAE,SAAS,MAAM,CAAC;AAChE,gBACE,OAAO,OAAO,OAAO,aACrB,OAAO,OAAO,UAAU,YACxB,OAAO,OAAO,WAAW,UACzB;AACA,uBAAS,EAAE,IAAI,OAAO,IAAI,OAAO,OAAO,OAAO,QAAQ,OAAO,OAAO,CAAC;YACxE,OAAO;AACL,uBAAS,IAAI;YACf;UACF,QAAQ;AACN,qBAAS,IAAI;UACf;QACF,CAAC;AACD,YAAI,GAAG,SAAS,MAAM,SAAS,IAAI,CAAC;MACtC;IACF;AACA,QAAI,GAAG,SAAS,MAAM,SAAS,IAAI,CAAC;AACpC,QAAI,GAAG,WAAW,MAAM;AACtB,UAAI,QAAQ;AACZ,eAAS,IAAI;IACf,CAAC;AACD,QAAI,IAAI;EACV,CAAC;AACH;AAEA,eAAe,cAAsC;AACnD,MAAI;AACF,UAAM,OAAO,MAAMpB,UAAS,UAAU,MAAM;AAC5C,UAAM,MAAM,OAAO,SAAS,KAAK,KAAK,GAAG,EAAE;AAC3C,WAAO,OAAO,SAAS,GAAG,KAAK,MAAM,IAAI,MAAM;EACjD,QAAQ;AACN,WAAO;EACT;AACF;AAEA,eAAe,aAAa,KAA+B;AACzD,MAAI;AAEF,YAAQ,KAAK,KAAK,CAAC;AACnB,WAAO;EACT,QAAQ;AACN,WAAO;EACT;AACF;AAEO,SAAS,qBAA6B;AAC3C,SAAOqB,aAAY,EAAE,EAAE,SAAS,KAAK;AACvC;AAyCA,eAAsB,qBAAqB,MAAsC;AAC/E,QAAM,aAA4B,KAAK,aAAa,CAAC,GAAG,IAAI,CAAC,OAAO;IAClE,eAAe,EAAE;IACjB,cAAc,EAAE;IAChB,QAAQ,EAAE;EACZ,EAAE;AACF,QAAM,UAAU,uBAAuB;IACrC,OAAO,KAAK;IACZ,OAAO,KAAK;IACZ,MAAM,KAAK;IACX,MAAM,KAAK,QAAQ;IACnB,SAAS,KAAK;IACd,eAAe,KAAK;IACpB,WAAW,KAAK;IAChB,cAAc,KAAK;IACnB;IACA,YAAY,KAAK;IACjB,cAAc,KAAK;IACnB,aAAa,KAAK;EACpB,CAAC;AACH;AAEA,eAAsB,mBAAmB,OAA8B;AACrE,MAAI;AACF,UAAM,UAAU,qBAAqB,EAAE,MAAM,CAAC;EAChD,QAAQ;EAER;AACF;AAUA,eAAsB,eACpB,OACA,MACA,SACA,OACwB;AACxB,MAAI;AACF,UAAM,OAAO,MAAM,iBAAiB,sBAAsB;MACxD;MACA;MACA;MACA,GAAI,OAAO,UAAU,WAAW,EAAE,MAAM,IAAI,CAAC;IAC/C,CAAC;AACD,UAAM,KAAM,MAAkC;AAC9C,WAAO,OAAO,OAAO,YAAY,GAAG,SAAS,IAAI,KAAK;EACxD,QAAQ;AACN,WAAO;EACT;AACF;AAGA,eAAsB,iBAAiB,OAAe,IAA2B;AAC/E,MAAI;AACF,UAAM,UAAU,wBAAwB,EAAE,OAAO,GAAG,CAAC;EACvD,QAAQ;EAER;AACF;AAEA,eAAe,UAAU,MAAc,MAA8B;AACnE,QAAM,OAAO,KAAK,UAAU,IAAI;AAChC,QAAM,IAAI,QAAc,CAAC,UAAU,YAAY;AAC7C,UAAM,MAAM;MACV;QACE,MAAM;QACN,MAAM;QACN,QAAQ;QACR;QACA,SAAS;UACP,gBAAgB;UAChB,kBAAkB,OAAO,WAAW,IAAI,EAAE,SAAS;QACrD;QACA,SAAS;MACX;MACA,CAAC,QAAQ;AACP,cAAM,SAAmB,CAAC;AAC1B,YAAI,GAAG,QAAQ,CAAC,MAAc,OAAO,KAAK,CAAC,CAAC;AAC5C,YAAI,GAAG,OAAO,MAAM;AAClB,gBAAM,SAAS,IAAI,cAAc;AACjC,cAAI,UAAU,OAAO,SAAS,KAAK;AACjC,qBAAS;UACX,OAAO;AACL,kBAAM,OAAO,OAAO,OAAO,MAAM,EAAE,SAAS,MAAM;AAClD,oBAAQ,IAAI,MAAM,SAAS,IAAI,WAAM,OAAO,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;UACjE;QACF,CAAC;MACH;IACF;AACA,QAAI,GAAG,SAAS,OAAO;AACvB,QAAI,GAAG,WAAW,MAAM;AACtB,UAAI,QAAQ;AACZ,cAAQ,IAAI,MAAM,SAAS,IAAI,UAAU,CAAC;IAC5C,CAAC;AACD,QAAI,MAAM,IAAI;AACd,QAAI,IAAI;EACV,CAAC;AACH;AAGA,eAAe,iBAAiB,MAAc,MAAiC;AAC7E,QAAM,OAAO,KAAK,UAAU,IAAI;AAChC,SAAO,IAAI,QAAiB,CAAC,UAAU,YAAY;AACjD,UAAM,MAAM;MACV;QACE,MAAM;QACN,MAAM;QACN,QAAQ;QACR;QACA,SAAS;UACP,gBAAgB;UAChB,kBAAkB,OAAO,WAAW,IAAI,EAAE,SAAS;QACrD;QACA,SAAS;MACX;MACA,CAAC,QAAQ;AACP,cAAM,SAAmB,CAAC;AAC1B,YAAI,GAAG,QAAQ,CAAC,MAAc,OAAO,KAAK,CAAC,CAAC;AAC5C,YAAI,GAAG,OAAO,MAAM;AAClB,gBAAM,SAAS,IAAI,cAAc;AACjC,cAAI,SAAS,OAAO,UAAU,KAAK;AACjC,oBAAQ,IAAI,MAAM,SAAS,IAAI,WAAM,OAAO,MAAM,CAAC,EAAE,CAAC;AACtD;UACF;AACA,gBAAM,OAAO,OAAO,OAAO,MAAM,EAAE,SAAS,MAAM;AAClD,cAAI;AACF,qBAAS,KAAK,SAAS,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC;UAClD,SAAS,KAAK;AACZ,oBAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;UAC7D;QACF,CAAC;AACD,YAAI,GAAG,SAAS,OAAO;MACzB;IACF;AACA,QAAI,GAAG,SAAS,OAAO;AACvB,QAAI,GAAG,WAAW,MAAM;AACtB,UAAI,QAAQ;AACZ,cAAQ,IAAI,MAAM,SAAS,IAAI,UAAU,CAAC;IAC5C,CAAC;AACD,QAAI,MAAM,IAAI;AACd,QAAI,IAAI;EACV,CAAC;AACH;AA0BA,eAAsB,uBAAuB,OAAsC;AACjF,aAAW,KAAK,OAAO;AACrB,QAAI,CAAC,EAAE,WAAY;AACnB,UAAM,OAAO,EAAE,aAAa,YAAY,EAAE,aAAa,SAAY,WAAW;AAC9E,QAAI;AACF,YAAM,qBAAqB;QACzB,OAAO,EAAE;QACT,OAAO,EAAE;QACT,MAAM,EAAE;QACR;QACA,SAAS,SAAS,UAAU,EAAE,eAAe;QAC7C,eAAe,EAAE;QACjB,WAAW,EAAE;QACb,cAAc,EAAE;QAChB,WAAW,EAAE;QACb,YAAY,EAAE;QACd,cAAc,EAAE;QAChB,aAAa,EAAE;MACjB,CAAC;IACH,QAAQ;IAER;EACF;AACF;ACzjBA,IAAM,WAA0C;EAC9C,QAAQ;IACN,WAAW;IACX,eAAe;IACf,oBAAoB;IACpB,wBAAwB;IACxB,KAAK;IACL,aAAa;IACb,gBAAgB;EAClB;EACA,QAAQ;IACN,WAAW;IACX,eAAe;IACf,oBAAoB;IACpB,wBAAwB;IACxB,KAAK;IACL,aAAa;IACb,gBAAgB;EAClB;AACF;AAEO,IAAM,cAAoC,CAAC,UAAU,QAAQ;AAE7D,SAAS,WAAW,QAA+B;AACxD,SAAO,SAAS,MAAM;AACxB;AAOO,IAAM,kCAAkC,SAAS,OAAO;AAGxD,IAAM,kCAAkC,SAAS,OAAO;AAGxD,SAAS,uBAAuB,OAAuB;AAC5D,SAAO,oBAAoB,UAAU,KAAK;AAC5C;AAGO,SAAS,uBAAuB,OAAuB;AAC5D,SAAO,oBAAoB,UAAU,KAAK;AAC5C;AAEO,SAAS,oBAAoB,QAAmB,OAAuB;AAC5E,SAAO,GAAG,SAAS,MAAM,EAAE,kBAAkB,GAAG,KAAK;AACvD;AAcO,SAAS,kBAAkB,QAAmB,OAA0B;AAC7E,QAAM,UAAU,SAAS,MAAM;AAC/B,QAAM,SAAS,oBAAoB,QAAQ,KAAK;AAChD,SAAO;IACL,SAAS,CAAC,QAAQ,QAAQ,sBAAsB;IAChD,cAAc;MACZ,GAAG,MAAM,IAAI,QAAQ,SAAS;MAC9B,GAAG,QAAQ,sBAAsB,IAAI,QAAQ,aAAa;IAC5D;EACF;AACF;AAGO,SAAS,kBAAkB,OAA0B;AAC1D,SAAO,kBAAkB,UAAU,KAAK;AAC1C;AAMO,SAAS,eAAe,OAA0B;AACvD,QAAM,SAAoB,EAAE,SAAS,CAAC,GAAG,cAAc,CAAC,EAAE;AAC1D,aAAW,KAAK,aAAa;AAC3B,UAAM,IAAI,kBAAkB,GAAG,KAAK;AACpC,WAAO,QAAQ,KAAK,GAAG,EAAE,OAAO;AAChC,WAAO,aAAa,KAAK,GAAG,EAAE,YAAY;EAC5C;AACA,SAAO;AACT;AAGA,eAAsB,oBAAoB,OAA8B;AACtE,aAAW,KAAK,kBAAkB,UAAU,KAAK,EAAE,QAAS,OAAM,aAAa,CAAC;AAClF;AAGA,eAAsB,iBAAiB,OAA8B;AACnE,aAAW,KAAK,eAAe,KAAK,EAAE,QAAS,OAAM,aAAa,CAAC;AACrE;AAWA,eAAsB,4BAA4B,WAAkC;AAClF,QAAM,UAAU,WAAW,CAAC,SAAS,MAAM,iBAAiB,SAAS,OAAO,SAAS,GAAG;IACtF,MAAM;EACR,CAAC;AACH;AAEA,eAAsB,mBAAmB,WAAkC;AACzE,aAAW,UAAU,aAAa;AAChC,UAAM,UAAU,WAAW,CAAC,SAAS,MAAM,iBAAiB,SAAS,MAAM,EAAE,SAAS,GAAG;MACvF,MAAM;IACR,CAAC;EACH;AACF;AA+BO,SAAS,qBACd,eACA,OAAoC,CAAC,GAC7B;AACR,QAAM,gBAAgB,KAAK,iBAAiB;AAC5C,QAAM,UAAqE;IACzE,eAAe,cAAc,WAAW,GAAG,IAAI,gBAAgB,IAAI,aAAa;EAClF;AACA,MAAI,KAAK,cAAe,SAAQ,WAAW,EAAE,SAAS,KAAK,cAAc;AACzE,QAAM,MAAM,OAAO,KAAK,KAAK,UAAU,OAAO,GAAG,MAAM,EAAE,SAAS,KAAK;AACvE,SAAO,sCAAsC,GAAG,GAAG,aAAa;AAClE;AASA,IAAM,WACJ;AAkBF,eAAsB,wBACpB,WACA,UACA,OAA4B,CAAC,GACG;AAChC,MAAI,SAAS,WAAW,EAAG,QAAO,EAAE,QAAQ,sBAAsB;AAElE,QAAM,WAAW,MAAM,UAAU,WAAW,CAAC,OAAO,+BAA+B,GAAG;IACpF,MAAM;EACR,CAAC;AACD,MAAI,SAAS,aAAa,KAAK,CAAC,KAAK,SAAS,CAAC,SAAS,OAAO,SAAS,QAAQ,GAAG;AACjF,WAAO,EAAE,QAAQ,qBAAqB;EACxC;AAEA,QAAM,QAAQ,SAAS,IAAI,CAAC,OAAO;IACjC,OAAO,aAAa,EAAE,IAAI;IAC1B,MAAM;IACN,SAAS,6BAA6B,EAAE,IAAI;IAC5C,cAAc;IACd,cAAc,EAAE,OAAO,aAAa,QAAQ,UAAU,MAAM,MAAM;IAClE,YAAY,EAAE,OAAO,aAAa;IAClC,gBAAgB,CAAC;EACnB,EAAE;AACF,QAAM,OACJ,GAAG,QAAQ;IACX,KAAK;IACH;MACE,SAAS;MACT;IACF;IACA;IACA;EACF,IACA;AAEF,QAAM,UAAU,WAAW,CAAC,SAAS,MAAM,oBAAoB,GAAG,EAAE,MAAM,SAAS,CAAC;AACpF,QAAM,QAAQ,MAAM,eAAe,WAAW,iCAAiC,IAAI;AACnF,MAAI,MAAM,aAAa,GAAG;AACxB,UAAM,IAAI,MAAM,iCAAiC,SAAS,KAAK,MAAM,UAAU,MAAM,MAAM,EAAE;EAC/F;AACA,SAAO,EAAE,QAAQ,QAAQ;AAC3B;AAEA,eAAe,eACb,WACA,MACA,SAC2B;AAC3B,QAAM,EAAE,OAAAxB,QAAM,IAAI,MAAM,OAAO,OAAO;AACtC,QAAM,SAAS,MAAMA;IACnB;IACA,CAAC,QAAQ,MAAM,UAAU,UAAU,WAAW,MAAM,MAAM,SAAS,WAAW,IAAI,CAAC,EAAE;IACrF,EAAE,OAAO,SAAS,QAAQ,MAAM;EAClC;AACA,SAAO;IACL,UAAU,OAAO,YAAY;IAC7B,QAAQ,OAAO,UAAU;IACzB,QAAQ,OAAO,UAAU;EAC3B;AACF;AAEA,SAAS,WAAW,GAAmB;AACrC,SAAO,IAAI,EAAE,QAAQ,MAAM,OAAO,CAAC;AACrC;AvBjEA,SAAS,kBACP,KACyC;AACzC,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,MAAgD,CAAC;AACvD,MAAI,IAAI,eAAe,IAAI,cAAc,EAAG,KAAI,cAAc,KAAK,MAAM,IAAI,WAAW;AACxF,MAAI,IAAI,QAAQ,IAAI,OAAO,EAAG,KAAI,OAAO,IAAI;AAC7C,MAAI,IAAI,aAAa,IAAI,YAAY,EAAG,KAAI,YAAY,KAAK,MAAM,IAAI,SAAS;AAChF,MAAI,IAAI,KAAM,KAAI,OAAO,IAAI;AAC7B,SAAO,OAAO,KAAK,GAAG,EAAE,SAAS,IAAI,MAAM;AAC7C;AAEA,SAAS,gBAAwB;AAC/B,SAAOwB,aAAY,CAAC,EAAE,SAAS,KAAK;AACtC;AAEO,SAAS,iBAAiB,eAA+B;AAC9D,QAAM,MAAMhB,UAASU,SAAQ,aAAa,CAAC;AAC3C,SAAO,IACJ,YAAY,EACZ,QAAQ,kBAAkB,GAAG,EAC7B,QAAQ,OAAO,GAAG,EAClB,QAAQ,oBAAoB,EAAE,EAC9B,MAAM,GAAG,EAAE,EACX,QAAQ,WAAW,EAAE;AAC1B;AAEO,SAAS,eAAe,eAAuB,IAAoB;AACxE,QAAM,OAAO,iBAAiB,aAAa;AAC3C,SAAO,KAAK,SAAS,IAAI,GAAG,IAAI,IAAI,EAAE,KAAK;AAC7C;AAEA,eAAeL,YAAW,GAA6B;AACrD,MAAI;AACF,UAAMR,MAAK,CAAC;AACZ,WAAO;EACT,QAAQ;AACN,WAAO;EACT;AACF;AAOA,eAAe,sBAAyC;AACtD,QAAM,OAAOP,SAAQ;AACrB,QAAM,aAAqE;IACzE,EAAE,KAAKC,OAAK,MAAM,YAAY,GAAG,KAAK,2BAA2B,UAAU,KAAK;EAClF;AACA,QAAM,MAAgB,CAAC;AACvB,aAAW,KAAK,YAAY;AAC1B,QAAI,MAAMc,YAAW,EAAE,GAAG,GAAG;AAC3B,UAAI,KAAK,GAAG,EAAE,GAAG,IAAI,EAAE,GAAG,GAAG,EAAE,WAAW,QAAQ,EAAE,EAAE;IACxD;EACF;AACA,SAAO;AACT;AAEA,eAAsB,UAAU,MAA6C;AAC3E,QAAM,MAAM,KAAK,UAAU,MAAM;EAAC;AAClC,QAAM,YAAYK,SAAQ,KAAK,aAAa;AAC5C,MAAI,CAAE,MAAML,YAAW,SAAS,GAAI;AAClC,UAAM,IAAI,MAAM,6BAA6B,SAAS,EAAE;EAC1D;AAOA,QAAM,UAAUd,OAAK,WAAW,eAAe;AAC/C,MAAI,MAAMc,YAAW,OAAO,GAAG;AAC7B,QAAI;AACF,YAAM,MAAM,MAAM,WAAW,OAAO;AACpC,UAAI,4BAA4B,OAAO,IAAI,SAAS,MAAM,CAAC,cAAc;IAC3E,SAAS,KAAK;AACZ,UAAI,eAAe,aAAa;AAC9B,cAAM,IAAI,MAAM;IAAuC,IAAI,OAAO,EAAE;MACtE;AACA,YAAM;IACR;EACF;AAEA,QAAM,WAAW;AACjB,MAAI,yBAAyB;AAK7B,MAAI;AACJ,MAAI;AACJ,MAAI;AACJ,MAAI,KAAK,eAAe;AACtB,UAAM,qBAAqB,KAAK,eAAe;AAC/C,UAAM,OAAO,MAAM,kBAAkB,oBAAoB,KAAK,aAAa;AAC3E,QAAI,CAAC,MAAM;AACT,YAAM,IAAI,MAAM,yBAAyB,KAAK,aAAa,EAAE;IAC/D;AACA,sBAAkB,KAAK,SAAS;AAGhC,UAAM,QAAQ,CAAC,KAAK,MAAM,GAAG,KAAK,SAAS,OAAO;AAClD,uBAAmB,EAAE,KAAK,KAAK,eAAe,MAAM,KAAK,SAAS,MAAM,MAAM;AAK9E,wBAAoB,KAAK,SAAS;AAClC;MACE,4BAA4B,KAAK,aAAa,KAAK,KAAK,SAAS,IAAI,KAAK,OAAO,MAAM,MAAM,CAAC,oBAAoB,KAAK,SAAS,KAAK;IACvI;EACF;AAEA,QAAM,WAAW,mBAAmB,KAAK,SAAS;AAGlD,QAAM,YAAY,kBAAmB,KAAK,SAAS,oBAAqB;AACxE,QAAM,EAAE,MAAM,IAAI,MAAM,YAAY,WAAW;IAC7C,YAAY,CAAC,SAAS,IAAI,WAAW,IAAI,EAAE;EAC7C,CAAC;AACD,MAAI,QAAQ,eAAe,SAAS,KAAK,sBAAsB,QAAQ,EAAE;AAOzE,MAAI,UAAU;AACd,MAAI;AACF,UAAM,YAAY,EAAE,OAAO,IAAI,CAAC;AAChC,UAAM,WAAW,MAAM,UAAU;AACjC,UAAM,uBAAuB,SAAS,KAAK;AAC3C,cAAU;EACZ,SAAS,KAAK;AACZ,QAAI,sBAAsB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;EAC9E;AAEA,QAAM,KAAK,cAAc;AACzB,QAAM,OAAO,KAAK,QAAQ,eAAe,WAAW,EAAE;AACtD,QAAM,gBAAgB,YAAY,IAAI;AACtC,QAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AACzC,MAAI,MAAM,gBAAgB,aAAa,GAAG;AACxC,UAAM,IAAI,MAAM,aAAa,aAAa,kCAAkC;EAC9E;AAOA,MAAI;AACJ,MAAI,KAAK,aAAa;AACpB,mBAAe,qBAAqB,MAAM,UAAU,GAAG,KAAK,WAAW;EACzE;AAUA,QAAM,iBAAkC,CAAC;AACzC,QAAM,qBAA0C,CAAC;AACjD,MAAI,mBAAmB,qBAAqB,kBAAkB,SAAS,GAAG;AACxE,uBAAmB,KAAK,GAAG,iBAAiB;EAC9C;AACA,MAAI,CAAC,iBAAiB;AACpB,UAAM,QAAQ,MAAM,eAAe,SAAS;AAC5C,QAAI,MAAM,SAAS,GAAG;AACpB;QACE,YAAY,OAAO,MAAM,MAAM,CAAC,mBAC9B,MAAM,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,GAAG,EAAE,uBAAuB,MAAM,EAAE,uBAAuB,EAAE,EAAE,EAAE,KAAK,IAAI;MACxG;IACF;AACA,eAAW,KAAK,OAAO;AACrB,YAAM,aACJ,EAAE,SAAS,SACP,YAAY,IAAI,KAChB,YAAY,IAAI,KAAK,EAAE,qBAAqB,QAAQ,qBAAqB,GAAG,CAAC;AACnF,YAAM,SAAS,MAAM,gBAAgB,EAAE,cAAc,UAAU;AAC/D,YAAM,gBACJ,EAAE,SAAS,SAAS,eAAe,cAAc,EAAE,oBAAoB;AACzE,YAAM,kBAAkB,mBAAmB,MAAM;AACjD,YAAM,QAAQ,MAAM,qBAAqB,GAAG,QAAQ,eAAe,eAAe;AAClF,qBAAe,KAAK,KAAK;AACzB,yBAAmB,KAAK;QACtB,MAAM,EAAE;QACR,cAAc,EAAE;QAChB;QACA;QACA;QACA,sBAAsB,EAAE;MAC1B,CAAC;IACH;EACF;AASA,MAAI,cAA6B;AACjC,QAAM,mBAAmB,CAAC,mBAAmB,eAAe,WAAW;AACvE,MAAI,KAAK,eAAe,kBAAkB;AACxC,kBAAc,gBAAgB,EAAE,IAAI,MAAM,aAAa,CAAC;AACxD,QAAI,wBAAwB,WAAW,+BAA+B;AACtE,UAAM,OAAO,MAAM,eAAe,EAAE,QAAQ,WAAW,aAAa,YAAY,CAAC;AACjF,QAAI,UAAU,KAAK,YAAY,MAAM,wCAAwC;EAC/E,WAAW,KAAK,eAAe,CAAC,iBAAiB;AAC/C,QAAI,iGAAiG;EACvG;AAEA,QAAM,iBAAiB,EAAE;AACzB,QAAM,oBAAoB,KAAK,QAAQ,gBAAgB;AACvD,QAAM,eAAe,iBAAiB,IAAI,iBAAiB;AAC3D,QAAM,aAAa,YAAY;AAC/B,MAAI,oBAAoB,uBAAuB,EAAE,CAAC,KAAK,uBAAuB,EAAE,CAAC,KAAK,YAAY,EAAE;AACpG,QAAM,MAAM,eAAe,EAAE;AAQ7B,QAAM,aAAa,oBAAoB;IACrC,SAAS,KAAK,cAAc,WAAW;IACvC,OAAO;EACT,CAAC;AACD,QAAM,gBAAgB,MAAM,mBAAmB,YAAY;IACzD,cAAc;IACd,OAAO;IACP,eAAe;EACjB,CAAC;AACD,MAAI,cAAc,QAAQ;AACxB,QAAI,UAAU,WAAW,MAAM,iBAAiB;AAChD,SAAK,cAAc,qBAAqB,KAAK,GAAG;AAC9C;QACE,YAAY,OAAO,cAAc,iBAAiB,CAAC;MACrD;IACF;AACA,QAAI,cAAc,oBAAoB;AACpC,UAAI,8EAA8E;IACpF;AACA,QAAI,cAAc,mBAAmB;AACnC,UAAI,6BAA6B,SAAS,uCAAuC;IACnF;AACA,QAAI,cAAc,kBAAkB;AAClC,UAAI,wEAAwE;IAC9E;EACF,WAAW,cAAc,SAAS;AAChC,QAAI,wBAAwB,WAAW,MAAM,8BAA8B;EAC7E,OAAO;AACL,QAAI,kBAAkB,WAAW,MAAM,8BAA8B;EACvE;AAIA,QAAM,SAAS,MAAM,yBAAyB,WAAW,QAAQ,SAAS;AAC1E,MAAI,OAAO,OAAQ,KAAI,wCAAwC,WAAW,MAAM,EAAE;AAIlF,QAAM,WAAW,MAAM,sBAAsB,YAAY;IACvD,OAAO;IACP,SAAS,KAAK,cAAc,WAAW;EACzC,CAAC;AACD,MAAI,SAAS,cAAc,aAAa;AACtC,QAAI,iDAAiD;EACvD,WAAW,SAAS,cAAc,UAAU;AAC1C,QAAI,kCAAkC,WAAW,MAAM,mBAAmB;EAC5E;AACA,QAAM,eAAe,kBAAkB,YAAY,QAAQ,GAAG;AAO9D,QAAM,YACJ,KAAK,gBAAgB,UAAc,MAAMA,YAAWd,OAAKD,SAAQ,GAAG,QAAQ,CAAC;AAC/E,MAAI;AACJ,MAAI;AACJ,MAAI,WAAW;AACb,UAAM,YAAY,mBAAmB;MACnC,SAAS,KAAK,aAAa,WAAW;MACtC,OAAO;IACT,CAAC;AACD,UAAM,eAAe,MAAM,kBAAkB,WAAW;MACtD,cAAc;MACd,OAAO;IACT,CAAC;AACD,QAAI,aAAa,OAAQ,KAAI,UAAU,UAAU,MAAM,gBAAgB;aAC9D,aAAa,QAAS,KAAI,wBAAwB,UAAU,MAAM,qBAAqB;QAC3F,KAAI,kBAAkB,UAAU,MAAM,EAAE;AAG7C,UAAM,aAAa,MAAM,eAAe,UAAU,QAAQ,SAAS;AACnE,QAAI,WAAW,OAAQ,KAAI,oCAAoC,UAAU,MAAM,EAAE;AACjF,kBAAc,iBAAiB,WAAW,QAAQ,GAAG;AACrD,wBAAoB,UAAU;EAChC;AAOA,QAAM,eACJ,KAAK,mBAAmB,UACvB,MAAMe,YAAWd,OAAKD,SAAQ,GAAG,WAAW,UAAU,CAAC,KACvD,MAAMe,YAAWd,OAAKD,SAAQ,GAAG,UAAU,SAAS,UAAU,CAAC;AAClE,MAAI;AACJ,MAAI;AACJ,MAAI,cAAc;AAChB,UAAM,eAAe,sBAAsB;MACzC,SAAS,KAAK,gBAAgB,WAAW;MACzC,OAAO;IACT,CAAC;AACD,UAAM,kBAAkB,MAAM,qBAAqB,cAAc;MAC/D,cAAc;MACd,OAAO;IACT,CAAC;AACD,QAAI,gBAAgB,OAAQ,KAAI,UAAU,aAAa,MAAM,2CAA2C;aAC/F,gBAAgB,QAAS,KAAI,wBAAwB,aAAa,MAAM,qBAAqB;QACjG,KAAI,kBAAkB,aAAa,MAAM,EAAE;AAChD,qBAAiB,oBAAoB,cAAc,QAAQ,GAAG;AAC9D,2BAAuB,aAAa;EACtC;AAEA,QAAM,SAAS,aAAa,EAAE,IAAI,MAAM,aAAa,CAAC;AACtD,QAAM,YAAYC,OAAK,QAAQ,KAAK;AACpC,QAAM,aAAaA,OAAK,WAAW,UAAU;AAI7C,QAAM,kBAAkBA,OAAK,QAAQ,WAAW;AAChD,QAAMY,OAAM,WAAW,EAAE,WAAW,KAAK,CAAC;AAC1C,QAAMA,OAAM,iBAAiB,EAAE,WAAW,KAAK,CAAC;AAEhD,QAAM,eAAe,MAAM,oBAAoB;AAC/C,eAAa,KAAK,GAAG,aAAa,YAAY;AAC9C,MAAI,YAAa,cAAa,KAAK,GAAG,YAAY,YAAY;AAC9D,MAAI,eAAgB,cAAa,KAAK,GAAG,eAAe,YAAY;AACpE,eAAa,KAAK,GAAG,IAAI,YAAY;AACrC,eAAa,KAAK,GAAG,SAAS,gBAAgB;AAC9C,eAAa,KAAK,GAAG,eAAe,IAAI,uBAAuB,EAAE;AAIjE,eAAa,KAAK,GAAG,YAAY,kBAAkB;AAMnD,aAAW,KAAK,oBAAoB;AAClC,iBAAa,KAAK,GAAG,EAAE,YAAY,SAAS,EAAE,YAAY,OAAO;EACnE;AAQA,QAAM,cAAsC,CAAC;AAC7C,MAAI,KAAK,aAAa,QAAS,MAAM,aAAa,MAAO,YAAY;AACnE,WAAO,OAAO,aAAa,mBAAmB,MAAM,EAAE,WAAW,uBAAuB,CAAC,CAAC;AAC1F,QAAI;AACF,YAAM,eAAe,MAAM,4BAA4B,KAAK,gBAAgB;AAC5E,YAAMA,OAAM,cAAc,EAAE,WAAW,KAAK,CAAC;AAC7C,YAAM,cAAc;AACpB,mBAAa,KAAK,GAAG,YAAY,IAAI,WAAW,EAAE;AAClD,kBAAY,oBAAoB,IAAI;IACtC,SAAS,KAAK;AACZ;QACE,sCAAsC,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;MACxF;IACF;EACF;AAEA,aAAW,KAAK,aAAc,KAAI,uBAAuB,CAAC,EAAE;AAK5D,QAAM,aAAa,mBAAmB;AACtC,MAAI,SAAS;AACX,QAAI;AACF,YAAM,qBAAqB;QACzB,OAAO;QACP,OAAO;QACP;QACA;QACA;QACA;QACA,WAAW;MACb,CAAC;AACD,UAAI,iCAAiC;IACvC,SAAS,KAAK;AACZ,UAAI,0BAA0B,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAChF,gBAAU;IACZ;EACF;AACA,QAAM,WAAmC,UACrC;;;IAGE,oBAAoB;IACpB,sBAAsB;EACxB,IACA,CAAC;AAML,QAAM,aAAa,KAAK,KAAK,YAAY;AACzC,QAAM,cAAc,aAAa,oBAAoB,IAAI;AACzD,QAAM,SAAiC,cAAc,cACjD,EAAE,uBAAuB,YAAY,IACrC,CAAC;AACL,QAAM,kBAAkB,aACpB,CAAC,EAAE,UAAU,GAAG,eAAe,oBAAoB,QAAQ,YAAY,CAAC,IACxE,CAAC;AAML,QAAM,kBAAkB;IACtB,EAAE,UAAU,GAAG,eAAe,oBAAoB,QAAQ,YAAY;EACxE;AAKA,QAAM,cAAsC;IAC1C,UAAU;IACV,mBAAmB;IACnB,yBAAyB;IACzB,GAAI,KAAK,cAAc,EAAE,uBAAuB,KAAK,YAAY,IAAI,CAAC;IACtE,GAAI,iBAAiB,SACjB,EAAE,wBAAwB,OAAO,YAAY,EAAE,IAC/C,CAAC;EACP;AACA,QAAM,gBAAwC;IAC5C,iBAAiB;IACjB,GAAG;IACH,GAAG;EACL;AAGA,QAAM,gBAA0C,KAAK;AACrD,MAAI,kBAAkB;AACtB,MAAI,eAAe,MAAM;AACvB,UAAM,SAAS,MAAM,oBAAoB;AACzC,QAAI,CAAC,2CAA2C,KAAK,MAAM,GAAG;AAC5D;QACE,sEAAsE,UAAU,SAAS;MAC3F;AACA,wBAAkB,EAAE,GAAG,eAAe,MAAM,KAAK;IACnD;EACF;AAEA,QAAM,OAAO;IACX,MAAM;IACN,OAAO;IACP;IACA,QAAQ;IACR,cAAc,CAAC,GAAG,iBAAiB,GAAG,eAAe;IACrD,KAAK;MACH,iBAAiB;MACjB,GAAG;MACH,GAAG,aAAa;MAChB,GAAI,aAAa,OAAO,CAAC;MACzB,GAAI,gBAAgB,OAAO,CAAC;MAC5B,GAAG;MACH,GAAG;MACH,GAAG;MACH,GAAI,KAAK,aAAa,CAAC;IACzB;EACF,CAAC;AACD,MAAI,aAAa,aAAa,UAAU;AAQxC,MAAI,mBAAmB,SAAS,GAAG;AACjC,UAAM,oBAAoB;MACxB,WAAW;MACX,eAAe,mBAAmB,IAAI,CAAC,MAAM,EAAE,YAAY;MAC3D,OAAO;IACT,CAAC;EACH;AAMA,QAAM,SAAS,MAAM,gBAAgB,eAAe,aAAa;AACjE,MAAI,OAAO,GAAI,KAAI,6BAA6B;MAC3C,KAAI,yCAAyC,OAAO,MAAM,EAAE;AAMjE,QAAM,wBAAwB,aAAa;AAU3C,MAAI,CAAC,iBAAiB;AACpB,QAAI,eAAe,SAAS,GAAG;AAC7B,UAAI;AACF,cAAM,cAAc,EAAE,WAAW,eAAe,OAAO,gBAAgB,OAAO,IAAI,CAAC;AACnF,YAAI,qDAAqD;MAC3D,SAAS,KAAK;AACZ;UACE,iCAAiC,aAAa;QAChD;AACA,cAAM;MACR;IACF,OAAO;AACL,YAAM,SAAS,eAAe;AAC9B,YAAM,qBAAqB,EAAE,WAAW,eAAe,YAAY,QAAQ,OAAO,IAAI,CAAC;IACzF;EACF,WAAW,qBAAqB,kBAAkB,SAAS,GAAG;AAI5D,UAAM;MACJ;MACA,kBAAkB,IAAI,CAAC,OAAO;QAC5B,MAAM,EAAE;QACR,eAAe,EAAE;QACjB,iBAAiB,EAAE;MACrB,EAAE;MACF;IACF;AACA,QAAI,2CAA2C;EACjD,OAAO;AACL,QAAI,2EAA2E;EACjF;AAEA,QAAM,mBAAmB,aAAa;AACtC,MAAI,oDAAoD;AAExD,QAAM,MAAM,MAAM,gBAAgB,eAAe,UAAU;AAC3D,MAAI,IAAI,GAAI,KAAI,wBAAwB;MACnC,KAAI,iDAAiD,IAAI,MAAM,EAAE;AAOtE,QAAM,UAAU,MAAM,oBAAoB,aAAa;AACvD,MAAI,QAAQ,IAAI;AACd,QAAI,yBAAyB,YAAY,GAAG;EAC9C,OAAO;AACL,QAAI,iCAAiC,QAAQ,MAAM,EAAE;EACvD;AAEA,MAAI,KAAK,gBAAgB;AACvB,QAAI,uDAAuD;AAC3D,UAAM,SAAS,MAAMX;MACnB;MACA;QACE;QACA;QACA;QACA;QACA;QACA;QACA;MACF;MACA,EAAE,QAAQ,MAAM;IAClB;AACA,eAAW,SAAS,OAAO,UAAU,IAAI,MAAM,IAAI,GAAG;AACpD,UAAI,KAAK,KAAK,EAAE,SAAS,EAAG,KAAI,gBAAgB,IAAI,EAAE;IACxD;AACA,QAAI,OAAO,aAAa,GAAG;AACzB,YAAM,IAAI;QACR,2CAA2C,OAAO,OAAO,QAAQ,CAAC,OAAO,OAAO,UAAU,IAAI,SAAS,EAAE,MAAM,GAAG,GAAG,CAAC;MACxH;IACF;AACA,QAAI,2BAA2B;EACjC;AAEA,MAAI,KAAK,SAAS;AAChB,QAAI,4DAA4D;AAChE,UAAM,EAAE,OAAO,IAAI,MAAM,sBAAsB;MAC7C,WAAW;MACX,cAAc;MACd,UAAU;MACV,OAAO;IACT,CAAC;AACD,QAAI,SAAS,IAAI,UAAU,OAAO,MAAM,CAAC,wBAAwB,2BAA2B;EAC9F;AAEA,MAAI,KAAK,oBAAoB,KAAK,iBAAiB,SAAS,GAAG;AAC7D,QAAI,WAAW,OAAO,KAAK,iBAAiB,MAAM,CAAC,8CAA8C;AACjG,UAAM,EAAE,OAAO,IAAI,MAAM,mBAAmB;MAC1C,WAAW;MACX,cAAc;MACd,OAAO,KAAK;MACZ,OAAO;IACT,CAAC;AACD,QAAI,WAAW,KAAK,iBAAiB,QAAQ;AAC3C,UAAI,UAAU,OAAO,MAAM,CAAC,IAAI,OAAO,KAAK,iBAAiB,MAAM,CAAC,8BAA8B;IACpG;EACF;AAMA,MAAI,cAA6B;AACjC,MAAI,YAAY;AACd,UAAM,MAAM,MAAM,gBAAgB,aAAa;AAC/C,QAAI,IAAI,GAAI,KAAI,0CAA0C;QACrD,KAAI,uCAAuC,IAAI,MAAM,EAAE;AAC5D,kBAAc,MAAM,kBAAkB,eAAe,kBAAkB;AACvE,QAAI,YAAa,KAAI,6BAA6B,OAAO,WAAW,CAAC,EAAE;EACzE;AAEA,QAAM,cAAc,MAAM,kBAAkB,eAAe,kBAAkB;AAC7E,MAAI,aAAa;AACf;MACE,uCAAuC,OAAO,WAAW,CAAC;IAE5D;EACF;AAMA,MAAI;AACJ,MAAI;AACJ,MAAI,KAAK,aAAa,QAAQ,aAAa;AACzC,QAAI;AACF,YAAM,SAAS,MAAM,aAAa;AAClC,UAAI,WAAW,YAAY;AACzB,YAAI,qEAAqE;MAC3E,OAAO;AACL,cAAM,WAAW,MAAM,eAAe;AACtC,YAAI,CAAC,SAAS,WAAW;AACvB,cAAI,wFAAmF;QACzF,WAAW,MAAM,cAAc,MAAM,WAAW,GAAG;AACjD,8BAAoB;AAGpB,wBAAc,MAAM,eAAe,IAAI;AACvC,cAAI,kBAAkB,WAAW,iBAAiB,OAAO,WAAW,CAAC,EAAE;AACvE,cAAI,CAAC,SAAS,cAAc;AAC1B,gBAAI,qDAAgD,kBAAkB,CAAC,IAAI;UAC7E;QACF,OAAO;AACL,cAAI,oFAA+E;QACrF;MACF;IACF,SAAS,KAAK;AACZ,UAAI,aAAa,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,yBAAyB;IAC5F;EACF;AAEA,QAAM,SAAoB;IACxB;IACA;IACA,WAAW;IACX,OAAO;IACP,eAAe;IACf;IACA;IACA,oBAAoB,WAAW;IAC/B;IACA;IACA,oBAAoB,uBAAuB,EAAE;IAC7C,oBAAoB,uBAAuB,EAAE;IAC7C,YAAY,UAAU,aAAa;IACnC,cAAc,mBAAmB,SAAS,IAAI,qBAAqB;IACnE,gBAAgB,KAAK,iBAAiB,OAAO;IAC7C,SAAS,KAAK,UAAU,OAAO;IAC/B,YAAY,aAAa,OAAO;IAChC,kBAAkB,aAAa,qBAAqB;IACpD,aAAa,eAAe;IAC5B;IACA,kBAAkB;IAClB,aAAa,eAAe;IAC5B,eAAe;IACf;IACA;IACA,mBAAmB,qBAAqB;IACxC,aAAa,KAAK;IAClB;IACA;IACA;IACA,gBAAgB,kBAAkB,eAAe;IACjD;EACF;AACA,QAAM,UAAU,MAAM;AAEtB,SAAO,EAAE,QAAQ,YAAY,MAAM;AACrC;AyBn6BO,IAAM,wBAAwB;AAG9B,IAAM,uBAAuB,GAAG,qBAAqB;AAiCrD,SAAS,iBAAiB,OAAwB;AACvD,QAAM,KAAK,SAAS,IAAI,KAAK;AAC7B,MAAI,MAAM,MAAM,MAAM,sBAAuB,QAAO;AACpD,SAAO,GAAG,oBAAoB,GAAG,CAAC;AACpC;AAGO,SAAS,WAAW,aAA6B;AACtD,SAAO,gBAAgB,wBACnB,wBACA,YAAY,MAAM,qBAAqB,MAAM;AACnD;AAOO,SAAS,mBAAmB,MAAuB;AACxD,MAAI,SAAS,sBAAuB,QAAO;AAC3C,SAAO,KAAK,WAAW,oBAAoB,KAAK,CAAC,KAAK,SAAS,OAAO;AACxE;AAMO,SAAS,yBAAyB,UAAqC;AAC5E,QAAM,QAAQ,IAAI,IAAI,QAAQ;AAC9B,MAAI,CAAC,MAAM,IAAI,qBAAqB,EAAG,QAAO;AAC9C,WAAS,IAAI,KAAK,KAAK;AACrB,UAAM,YAAY,GAAG,oBAAoB,GAAG,OAAO,CAAC,CAAC;AACrD,QAAI,CAAC,MAAM,IAAI,SAAS,EAAG,QAAO;EACpC;AACF;AAOO,SAAS,sBAAsB,QAAuC;AAC3E,QAAM,MAA6B,CAAC;AACpC,aAAW,QAAQ,OAAO,MAAM,IAAI,GAAG;AACrC,QAAI,KAAK,KAAK,MAAM,GAAI;AACxB,UAAM,CAAC,MAAM,SAAS,QAAQ,IAAI,KAAK,MAAM,GAAI;AACjD,QAAI,SAAS,UAAa,CAAC,mBAAmB,IAAI,EAAG;AACrD,QAAI,YAA2B;AAC/B,UAAM,OAAO,OAAO,UAAU,WAAW,IAAI,KAAK,GAAG,EAAE;AACvD,QAAI,OAAO,SAAS,IAAI,KAAK,OAAO,EAAG,aAAY,IAAI,KAAK,OAAO,GAAI,EAAE,YAAY;AACrF,QAAI,KAAK;MACP,OAAO,WAAW,IAAI;MACtB,aAAa;MACb,UAAU,OAAO,UAAU,YAAY,KAAK,KAAK,GAAG,EAAE,IAAI;MAC1D;IACF,CAAC;EACH;AACA,MAAI,KAAK,CAAC,GAAG,MAAM;AACjB,QAAI,EAAE,gBAAgB,sBAAuB,QAAO;AACpD,QAAI,EAAE,gBAAgB,sBAAuB,QAAO;AACpD,YAAQ,EAAE,aAAa,IAAI,cAAc,EAAE,aAAa,EAAE;EAC5D,CAAC;AACD,SAAO;AACT;AAMA,eAAsB,kBACpB,WACA,MACgC;AAChC,QAAM,MAAM,MAAMA;IAChB;IACA;MACE;MACA;MACA,QAAQ;MACR;MACA;MACA;MACA;;MAEA;IACF;IACA,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,IAAI,aAAa,EAAG,QAAO,CAAC;AAChC,SAAO,sBAAsB,IAAI,UAAU,EAAE;AAC/C;AAWA,eAAsB,kBAAkB,MAA+C;AACrF,QAAM,cAAc,KAAK,eAAe;AACxC,QAAM,OAAO,KAAK,QAAQ;AAC1B,QAAM,QAAQ,KAAK,UAAU;AAC7B,QAAM,OAAO,QAAQ,IAAI,MAAM,KAAK;AAGpC,QAAM,MAAM,QAAQ,YAAY;AAChC,QAAM,SAAS,MAAMA;IACnB;IACA;MACE;MACA;MACA,QAAQ,IAAI;MACZ;MACA;MACA,KAAK;MACL;MACA;MACA;MACA;MACA;MACA;MACA,GAAG,qBAAqB,WAAW;IACrC;IACA,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,OAAO,aAAa,EAAG;AAC3B,QAAM,UAAU,OAAO,UAAU,IAAI,SAAS;AAC9C,MAAI,OAAO,aAAa,OAAO,qCAAqC,KAAK,MAAM,GAAG;AAChF,UAAM,IAAI;MACR;IACF;EACF;AACA,QAAM,IAAI;IACR,oCAAoC,KAAK,SAAS,KAChD,OAAO,KAAK,KAAK,QAAQ,OAAO,OAAO,QAAQ,CAAC,EAClD;EACF;AACF;AAOO,SAAS,4BACd,WACA,aACA,MACU;AACV,QAAM,OAAO,eAAe;AAC5B,QAAM,OAAO,QAAQ,IAAI,MAAM,KAAK;AACpC,SAAO;IACL;IACA;IACA;IACA,QAAQ,IAAI;IACZ;IACA,QAAQ;IACR;IACA;IACA;IACA;IACA;EACF;AACF;AAMA,eAAsB,iBACpB,WACA,aACA,MAC2B;AAC3B,QAAM,OAAO,eAAe;AAC5B,QAAM,MAAM,MAAMA;IAChB;IACA,CAAC,QAAQ,UAAU,QAAQ,gBAAgB,WAAW,QAAQ,eAAe,MAAM,IAAI;IACvF,EAAE,QAAQ,MAAM;EAClB;AACA,SAAO,EAAE,SAAS,IAAI,aAAa,GAAG,aAAa,KAAK;AAC1D;AAMA,eAAsB,iBACpB,WACA,aACA,MACkB;AAClB,QAAM,MAAM,MAAMA;IAChB;IACA;MACE;MACA;MACA,QAAQ;MACR;MACA;MACA;MACA;MACA;IACF;IACA,EAAE,QAAQ,MAAM;EAClB;AACA,SAAO,IAAI,aAAa;AAC1B;AC/NA,eAAsB,gBACpB,QACA,QACA,WACuB;AACvB,QAAM,cAAc,WAAW;AAC/B,QAAM,SAAS,cAAc,GAAG,OAAO,SAAS,eAAe;AAE/D,QAAM,YAA2B,CAAC;AAElC,MAAI,OAAO,cAAc,OAAO,aAAa;AAC3C,UAAM,UAAU,aAAa,QAAQ,MAAM;AAC3C,UAAM,MAAM,QAAQ,UAAU,QAAQ;AACtC,cAAU,KAAK;MACb,MAAM;MACN,MAAM;MACN,eAAe;MACf;MACA,WAAW,QAAQ,GAAG;IACxB,CAAC;EACH;AAIA,MAAI,iBAAgC;AACpC,QAAM,eAAe,WAAW,SAAS,KAAK,CAAC,MAAM,EAAE,MAAM;AAC7D,MAAI,cAAc;AAChB,qBAAiB,aAAa;EAChC;AAEA,QAAM,cAAc,CAAC,MAAc,SAAuB;AAGxD,QAAI,SAAS,eAAgB;AAC7B,cAAU,KAAK;MACb,MAAM;MACN;MACA,eAAe;;;MAGf,GAAI,cACA,EAAE,KAAK,UAAU,MAAM,IAAI,OAAO,IAAI,CAAC,IAAI,WAAW,KAAK,IAC3D,EAAE,WAAW,MAAM;IACzB,CAAC;EACH;AAEA,QAAM,oBAAoB,WAAW,SAAS;IAC5C,CAAC,MAAwC,OAAO,EAAE,SAAS;EAC7D;AACA,MAAI,qBAAqB,kBAAkB,SAAS,GAAG;AACrD,eAAW,OAAO,kBAAmB,aAAY,IAAI,MAAM,IAAI,IAAI;EACrE,OAAO;AACL,QAAI;AACF,YAAM,MAAM,MAAMyB,WAAW1B,OAAK,OAAO,eAAe,eAAe,CAAC;AACxE,UAAI,CAAC,gBAAgB;AACnB,yBAAiB,IAAI,SAAS,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ;MAC/D;AACA,iBAAW,OAAO,IAAI,UAAU;AAC9B,YAAI,IAAI,WAAW,SAAS,OAAQ;AACpC,oBAAY,IAAI,MAAM,IAAI,UAAU,IAAI;MAC1C;IACF,QAAQ;IAGR;EACF;AAOA,MAAI,OAAO,qBAAqB,QAAW;AACzC,UAAM,YAAY,mBAAmB,QAAQ,OAAO,gBAAgB;AAMpE,UAAM,cAAc,OAAO,kBAAkB,UAAa,WAAW;AACrE,UAAM,SAAS,cACV,OAAO,eAAe,WAAW,OAAO,aAAa,eACtD,oBAAoB,OAAO,OAAO,WAAW,CAAC;AAClD,cAAU,KAAK;MACb,MAAM;MACN,MAAM,kBAAkB;MACxB,eAAe,OAAO,oBAAoB;MAC1C,GAAI,YAAY,EAAE,KAAK,QAAQ,WAAW,KAAK,IAAI,EAAE,WAAW,MAAM;IACxE,CAAC;EACH;AAEA,SAAO,EAAE,QAAQ,aAAa,UAAU;AAC1C;AF3BA,eAAsB,YAAkC;AACtD,QAAM,EAAE,MAAM,IAAI,MAAM,UAAU;AAClC,QAAM,SAAS,MAAM,aAAa;AAClC,SAAO,QAAQ;IACb,MAAM,IAAI,OAAO,MAA0B;AAOzC,UAAI,EAAE,YAAY,EAAE,aAAa,UAAU;AACzC,cAAM2B,aAAY,MAAM,cAAc,CAAC;AACvC,cAAM,UAAU,EAAE,OAAO,WAAW;AAKpC,cAAM,iBACJ,EAAE,kBAAkB,SACf,EAAE,eAAe,WAAW,EAAE,aAAa,eAC5C;AACN,cAAM,eAAe,UAAU,IAAI,EAAE,OAAO,cAAc,OAAO,IAAI;AACrE,cAAM,SAAS,kBAAkB;AACjC,cAAMC,aAA0B;UAC9B,QAAQ,SAAS,SAAS,MAAM,IAAI;UACpC,aAAa;UACb,WAAW,SACP;YACE;cACE,MAAM;cACN,MAAM;cACN,eAAe;cACf,KAAK;cACL,WAAW;YACb;UACF,IACA,CAAC;QACP;AACA,eAAO;UACL,GAAG;UACH,OAAO;UACP,WAAAA;UACA,gBAAgBD,YAAW,OAAO;UAClC,oBAAoBA,YAAW,OAAO;UACtC,eAAeA,YAAW,OAAO;UACjC,mBAAmBA,YAAW,OAAO;UACrC,sBAAsBA,YAAW,UAAU;UAC3C,eAAe,CAAC;UAChB,cAAc;UACd,iBAAiB;QACnB;MACF;AACA,YAAM,QAAQ,MAAM,uBAAuB,EAAE,SAAS;AACtD,YAAM,YAAY,MAAM,cAAc,CAAC;AACvC,YAAM,YAAY,MAAM,gBAAgB,GAAG,QAAQ,SAAS;AAK5D,YAAM,gBACJ,UAAU,YAAY,MAAM,kBAAkB,EAAE,SAAS,IAAI,CAAC;AAChE,YAAM,eACJ,UAAU,YAAY,MAAM,iBAAiB,EAAE,SAAS,IAAI;AAC9D,YAAM,kBACJ,UAAU,YAAY,MAAM,oBAAoB,EAAE,SAAS,IAAI;AACjE,aAAO;QACL,GAAG;QACH;QACA;QACA,gBAAgB,WAAW,OAAO;QAClC,oBAAoB,WAAW,OAAO;QACtC,eAAe,WAAW,OAAO;QACjC,mBAAmB,WAAW,OAAO;QACrC,sBAAsB,WAAW,UAAU;QAC3C;QACA;QACA;MACF;IACF,CAAC;EACH;AACF;AAGA,SAAS,SAAS,KAAqB;AACrC,MAAI;AACF,WAAO,IAAI,IAAI,GAAG,EAAE;EACtB,QAAQ;AACN,WAAO;EACT;AACF;AAOA,eAAeb,YAAW,GAA6B;AACrD,MAAI;AACF,UAAMR,MAAK,CAAC;AACZ,WAAO;EACT,QAAQ;AACN,WAAO;EACT;AACF;AAEA,eAAe,WAAW,UAAsC;AAC9D,QAAM,QAAQ,MAAM,UAAU;AAC9B,QAAM,SAAwB,QAAQ,UAAU,KAAK;AACrD,UAAQ,OAAO,MAAM;IACnB,KAAK;AACH,aAAO,OAAO;IAChB,KAAK;AACH,YAAM,IAAI,iBAAiB,QAAQ;IACrC,KAAK;AACH,YAAM,IAAI,kBAAkB,UAAU,OAAO,OAAO;EACxD;AACF;AAEA,eAAsB,SAAS,UAAsC;AACnE,QAAM,MAAM,MAAM,WAAW,QAAQ;AACrC,QAAM,eAAe,IAAI,SAAS;AAClC,SAAO;AACT;AAEA,eAAsB,WAAW,UAAsC;AACrE,QAAM,MAAM,MAAM,WAAW,QAAQ;AACrC,QAAM,iBAAiB,IAAI,SAAS;AACpC,SAAO;AACT;AAEA,eAAsB,QAAQ,UAAsC;AAClE,QAAM,MAAM,MAAM,WAAW,QAAQ;AACrC,QAAM,cAAc,IAAI,SAAS;AACjC,SAAO;AACT;AAgBA,eAAsB,SAAS,UAAuC;AACpE,QAAM,MAAM,MAAM,WAAW,QAAQ;AAIrC,aAAW,KAAK,IAAI,gBAAgB,CAAC,GAAG;AACtC,QAAI,CAAE,MAAMQ,YAAWd,OAAK,EAAE,cAAc,MAAM,CAAC,GAAI;AACrD,YAAM,IAAI;QACR,uCAAuCA,OAAK,EAAE,cAAc,MAAM,CAAC;MACrE;IACF;EACF;AACA,QAAM,eAAe,IAAI,SAAS;AAMlC,OAAK,IAAI,gBAAgB,CAAC,GAAG,SAAS,GAAG;AACvC,UAAM;MACJ,IAAI;OACH,IAAI,gBAAgB,CAAC,GAAG,IAAI,CAAC,OAAO;QACnC,MAAM,EAAE;QACR,eAAe,EAAE;QACjB,iBAAiB,EAAE;MACrB,EAAE;IACJ;EACF;AAIA,QAAM,wBAAwB,IAAI,SAAS;AAE3C,MAAI,IAAI,YAAY;AAIlB,UAAM,gBAAgB,IAAI,WAAW,IAAI,UAAU;EACrD;AACA,MAAI,IAAI,cAAc;AACpB,UAAM,oBAAoB,IAAI,SAAS;EACzC;AACA,MAAI,IAAI,YAAY;AAIlB,UAAM,gBAAgB,IAAI,SAAS;AAInC,UAAM,gBAAgB,MAAM,kBAAkB,IAAI,WAAW,kBAAkB;AAC/E,QAAI,iBAAiB,kBAAkB,IAAI,aAAa;AACtD,UAAI,cAAc;AAClB,YAAM,UAAU,GAAG;IACrB;EACF;AAGA,MAAI,IAAI,qBAAqB,QAAW;AACtC,UAAM,eAAe,MAAM;MACzB,IAAI;MACJ,IAAI,oBAAoB;IAC1B;AACA,QAAI,gBAAgB,iBAAiB,IAAI,aAAa;AACpD,UAAI,cAAc;AAClB,YAAM,UAAU,GAAG;IACrB;AAIA,QAAI,IAAI,iBAAiB,IAAI,aAAa;AACxC,UAAI;AACF,cAAM,WAAW,MAAM,eAAe;AACtC,YAAI,SAAS,WAAW;AACtB,gBAAM,cAAc,IAAI,eAAe,IAAI,WAAW;AAEtD,gBAAM,MAAM,MAAM,eAAe,IAAI,aAAa;AAClD,cAAI,QAAQ,IAAI,aAAa;AAC3B,gBAAI,cAAc;AAClB,kBAAM,UAAU,GAAG;UACrB;QACF;MACF,QAAQ;MAER;IACF;EACF;AAIA,MAAI,IAAI,YAAY;AAClB,QAAI;AACF,YAAM,YAAY;AAClB,YAAM,qBAAqB;QACzB,OAAO,IAAI;QACX,OAAO,IAAI;QACX,MAAM,IAAI;QACV,eAAe,IAAI;QACnB,WAAW,IAAI;QACf,cAAc,IAAI;QAClB,WAAW,IAAI;MACjB,CAAC;IACH,QAAQ;IAER;EACF;AACA,SAAO,EAAE,QAAQ,IAAI;AACvB;AAMA,eAAsB,gBAAgB,UAAkB,MAAuC;AAC7F,QAAM,MAAM,MAAM,WAAW,QAAQ;AACrC,QAAM,SAAS,MAAM,aAAa,KAAK,IAAI;AAC3C,SAAO,EAAE,GAAG,QAAQ,QAAQ,IAAI;AAClC;AAEA,eAAsB,gBACpB,UACkD;AAClD,QAAM,MAAM,MAAM,WAAW,QAAQ;AACrC,QAAM,QAAQ,MAAM,aAAa,GAAG;AACpC,SAAO,EAAE,QAAQ,KAAK,MAAM;AAC9B;AAuBA,eAAe,aAAa,MAAsC;AAChE,MAAI;AACF,UAAM,SAAS,MAAMC,QAAM,MAAM,CAAC,OAAO,IAAI,GAAG,EAAE,QAAQ,MAAM,CAAC;AACjE,QAAI,OAAO,aAAa,EAAG,QAAO;AAClC,UAAM,SAAS,OAAO,UAAU,OAAO,UAAU,IAAI,MAAM,KAAK,EAAE,CAAC,KAAK,IAAI,EAAE;AAC9E,QAAI,OAAO,MAAM,MAAM,EAAG,QAAO;AACjC,WAAO,SAAS;EAClB,QAAQ;AACN,WAAO;EACT;AACF;AAEA,eAAsB,WAAW,UAAyC;AACxE,QAAM,SAAS,MAAM,WAAW,QAAQ;AACxC,QAAM,QAAQ,MAAM,uBAAuB,OAAO,SAAS;AAC3D,QAAM,oBAAoB,OAAO,cAAc,MAAM,aAAa,OAAO,WAAW,IAAI;AACxF,QAAM,aAAa,MAAM,iBAAiB,OAAO,SAAS;AAE1D,MAAI,gBAA0C;AAC9C,MAAI,eAAwC;AAC5C,MAAI,kBAA8C;AAClD,MAAI,gBAAuC,CAAC;AAC5C,MAAI,UAAU,WAAW;AACvB,QAAI;AACF,sBAAgB,MAAM,kBAAkB,OAAO,SAAS;IAC1D,QAAQ;AACN,sBAAgB;IAClB;AACA,QAAI;AACF,qBAAe,MAAM,iBAAiB,OAAO,SAAS;IACxD,QAAQ;AACN,qBAAe;IACjB;AACA,QAAI;AACF,wBAAkB,MAAM,oBAAoB,OAAO,SAAS;IAC9D,QAAQ;AACN,wBAAkB;IACpB;AACA,oBAAgB,MAAM,kBAAkB,OAAO,SAAS;EAC1D;AAEA,QAAM,YAAY,MAAM,aAAa,MAAM;AAC3C,QAAM,SAAS,MAAM,aAAa;AAClC,QAAM,kBAAkB,MAAM,cAAc,MAAM;AAClD,QAAM,YAAY,MAAM,gBAAgB,QAAQ,QAAQ,eAAe;AAEvE,SAAO;IACL;IACA;IACA;IACA,eAAe;IACf;IACA;IACA;IACA;IACA;IACA;IACA;EACF;AACF;AAaA,eAAsB,WACpB,UACA,OAAuB,CAAC,GACA;AACxB,QAAM,MAAM,MAAM,WAAW,QAAQ;AAIrC,MAAI,IAAI,YAAY;AAClB,QAAI;AACF,YAAM,mBAAmB,IAAI,EAAE;IACjC,QAAQ;IAER;EACF;AAEA,MAAI,IAAI,eAAe;AACrB,QAAI;AACF,YAAM,gBAAgB,IAAI,aAAa;IACzC,QAAQ;IAER;EACF;AASA,QAAM,gBAAgB,CAAC,IAAI;AAC3B,MAAI,eAAe;AACjB,eAAW,KAAK,IAAI,gBAAgB,CAAC,GAAG;AACtC,UAAI;AACF,cAAM,oBAAoB;UACxB,cAAc,EAAE;UAChB,iBAAiB,EAAE;QACrB,CAAC;MACH,QAAQ;MAER;IACF;EACF;AACA,QAAM,kBAAkB,MAAM,uBAAuB,IAAI,SAAS;AAClE,QAAM,gBAAgB,IAAI,SAAS;AACnC,QAAM,iBAAiB,MAAM,uBAAuB,IAAI,SAAS;AACjE,QAAM,mBAAmB,oBAAoB,aAAa,mBAAmB;AAE7E,QAAM,iBAA2B,CAAC;AAIlC,MAAI,IAAI,sBAAsB,IAAI,uBAAuB,sBAAsB;AAC7E,UAAM,aAAa,IAAI,kBAAkB;AACzC,mBAAe,KAAK,IAAI,kBAAkB;EAC5C;AAIA,MAAI,IAAI,qBAAqB,IAAI,sBAAsB,qBAAqB;AAC1E,UAAM,aAAa,IAAI,iBAAiB;AACxC,mBAAe,KAAK,IAAI,iBAAiB;EAC3C;AAEA,MAAI,IAAI,wBAAwB,IAAI,yBAAyB,wBAAwB;AACnF,UAAM,aAAa,IAAI,oBAAoB;AAC3C,mBAAe,KAAK,IAAI,oBAAoB;EAC9C;AAIA,QAAM,mBAAmB;IACvB,IAAI,sBAAsB,uBAAuB,IAAI,EAAE;IACvD,IAAI,sBAAsB,uBAAuB,IAAI,EAAE;EACzD;AACA,aAAW,KAAK,kBAAkB;AAChC,UAAM,aAAa,CAAC;AACpB,mBAAe,KAAK,CAAC;EACvB;AAIA,MAAI,IAAI,gBAAgB,CAAC,IAAI,mBAAmB;AAC9C,UAAM,aAAa,IAAI,YAAY;AACnC,mBAAe,KAAK,IAAI,YAAY;EACtC;AAEA,MAAI,kBAAiC;AACrC,MAAI,IAAI,eAAe,CAAC,KAAK,cAAc;AACzC,QAAI;AACF,YAAMI,IAAG,IAAI,aAAa,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAC1D,wBAAkB,IAAI;IACxB,QAAQ;AACN,wBAAkB;IACpB;EACF;AAKA,MAAI;AACF,UAAMA,IAAG,aAAa,GAAG,GAAG,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;EAC9D,QAAQ;EAER;AAEA,QAAM,gBAAgB,IAAI,EAAE;AAE5B,SAAO,EAAE,QAAQ,KAAK,kBAAkB,gBAAgB,gBAAgB;AAC1E;AAiBA,eAAe,mBAAsC;AACnD,MAAI;AACF,UAAM,UAAU,MAAMF,SAAQ,gBAAgB,EAAE,eAAe,KAAK,CAAC;AACrE,WAAO,QAAQ,OAAO,CAAC,MAAM,EAAE,YAAY,CAAC,EAAE,IAAI,CAAC,MAAMH,OAAK,gBAAgB,EAAE,IAAI,CAAC;EACvF,QAAQ;AACN,WAAO,CAAC;EACV;AACF;AAEA,eAAe,cAAiC;AAC9C,MAAI;AACF,UAAM,UAAU,MAAMG,SAAQ,YAAY,EAAE,eAAe,KAAK,CAAC;AACjE,WAAO,QAAQ,OAAO,CAAC,MAAM,EAAE,YAAY,CAAC,EAAE,IAAI,CAAC,MAAMH,OAAK,YAAY,EAAE,IAAI,CAAC;EACnF,QAAQ;AACN,WAAO,CAAC;EACV;AACF;AAaA,eAAe,0BAA6C;AAC1D,QAAM,IAAI,MAAMC;IACd;IACA,CAAC,SAAS,MAAM,YAAY,4BAA4B,GAAG,uBAAuB,GAAG;IACrF,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,EAAE,aAAa,EAAG,QAAO,CAAC;AAC9B,UAAQ,EAAE,UAAU,IACjB,MAAM,IAAI,EACV,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,CAAC,MAAM,EAAE,WAAW,uBAAuB,CAAC;AACxD;AAEA,eAAsB,WAAW,OAAqB,CAAC,GAAyB;AAC9E,QAAM,SAAS,KAAK,UAAU;AAC9B,QAAM,MAAM,KAAK,OAAO;AAExB,QAAM,EAAE,MAAM,IAAI,MAAM,UAAU;AAGlC,QAAM,cAAc,MAAM,QAAQ;IAChC,MAAM,IAAI,OAAO,OAAO,EAAE,KAAK,GAAG,QAAQ,MAAM,uBAAuB,EAAE,SAAS,EAAE,EAAE;EACxF;AACA,QAAM,iBAAiB,YAAY,OAAO,CAAC,MAAM,EAAE,WAAW,SAAS,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG;AAIzF,MAAI,mBAA6B,CAAC;AAClC,MAAI,gBAA0B,CAAC;AAC/B,MAAI,kBAA4B,CAAC;AACjC,MAAI,gBAA0B,CAAC;AAC/B,MAAI,yBAAmC,CAAC;AAExC,MAAI,KAAK;AACP,UAAM,iBAAiB,MAAM,uBAAuB;AACpD,UAAM,cAAc,MAAM,oBAAoB;AAC9C,UAAM,mBAAmB,MAAM,iBAAiB;AAChD,UAAM,cAAc,MAAM,YAAY;AACtC,UAAM,uBAAuB,MAAM,wBAAwB;AAK3D,UAAM,uBAAuB,MAAM,wBAAwB;AAE3D,UAAM,iBAAiB,MAAM,OAAO,CAAC,MAAM,CAAC,eAAe,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,CAAC;AACrF,UAAM,qBAAqB,oBAAI,IAAY;MACzC,GAAG,eAAe,IAAI,CAAC,MAAM,EAAE,SAAS;;;IAG1C,CAAC;AACD,UAAM,kBAAkB,oBAAI,IAAY;MACtC,GAAG,eACA,IAAI,CAAC,MAAM,EAAE,kBAAkB,EAC/B,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ;MACnD,GAAG,eACA,IAAI,CAAC,MAAM,EAAE,iBAAiB,EAC9B,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ;MACnD,GAAG,eACA,IAAI,CAAC,MAAM,EAAE,oBAAoB,EACjC,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ;MACnD,GAAG,eACA,IAAI,CAAC,MAAM,EAAE,kBAAkB,EAC/B,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ;MACnD,GAAG,eACA,IAAI,CAAC,MAAM,EAAE,kBAAkB,EAC/B,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ;MACnD,GAAG,eACA,IAAI,CAAC,MAAM,EAAE,YAAY,EACzB,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ;;;MAGnD;;MAEA;;MAEA;;MAEA;MACA;;;MAGA;IACF,CAAC;AACD,UAAM,oBAAoB,IAAI;MAC5B,eACG;QAAO,CAAC,MACP,OAAO,EAAE,gBAAgB;MAC3B,EACC,IAAI,CAAC,MAAM,EAAE,WAAW;IAC7B;AACA,UAAM,kBAAkB,IAAI,IAAI,eAAe,IAAI,CAAC,MAAM,aAAa,CAAC,CAAC,CAAC;AAQ1E,UAAM,2BAA2B,oBAAI,IAAY;MAC/C,GAAG,eACA,IAAI,CAAC,MAAM,EAAE,eAAe,EAC5B,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ;MACnD,GAAG;IACL,CAAC;AACD,uBAAmB,eAAe,OAAO,CAAC,MAAM,CAAC,mBAAmB,IAAI,CAAC,CAAC;AAC1E,oBAAgB,YAAY,OAAO,CAAC,MAAM,CAAC,gBAAgB,IAAI,CAAC,CAAC;AACjE,sBAAkB,iBAAiB,OAAO,CAAC,MAAM,CAAC,kBAAkB,IAAI,CAAC,CAAC;AAC1E,oBAAgB,YAAY,OAAO,CAAC,MAAM,CAAC,gBAAgB,IAAI,CAAC,CAAC;AACjE,6BAAyB,qBAAqB;MAC5C,CAAC,MAAM,CAAC,yBAAyB,IAAI,CAAC;IACxC;EACF;AAEA,MAAI,QAAQ;AACV,WAAO;MACL,gBAAgB,eAAe,IAAI,CAAC,MAAM,EAAE,EAAE;MAC9C,mBAAmB;MACnB,gBAAgB;MAChB,qBAAqB;MACrB,gBAAgB;MAChB,yBAAyB;MACzB,QAAQ;IACV;EACF;AAEA,aAAW,KAAK,eAAgB,OAAM,gBAAgB,EAAE,EAAE;AAC1D,aAAW,KAAK,iBAAkB,OAAM,gBAAgB,CAAC;AACzD,aAAW,KAAK,cAAe,OAAM,aAAa,CAAC;AACnD,aAAW,KAAK,iBAAiB;AAC/B,QAAI;AACF,YAAMI,IAAG,GAAG,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;IAC9C,QAAQ;IAER;EACF;AACA,aAAW,KAAK,eAAe;AAC7B,QAAI;AACF,YAAMA,IAAG,GAAG,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;IAC9C,QAAQ;IAER;EACF;AACA,aAAW,OAAO,wBAAwB;AACxC,UAAM,YAAY,KAAK,EAAE,OAAO,KAAK,CAAC;EACxC;AAOA,MAAI,KAAK;AACP,QAAI;AACF,YAAM,gBAAgB,oBAAoB;IAC5C,QAAQ;IAER;AACA,QAAI;AACF,YAAMJ,QAAM,UAAU,CAAC,SAAS,MAAM,eAAe,GAAG,EAAE,QAAQ,MAAM,CAAC;IAC3E,QAAQ;IAER;AACA,QAAI;AACF,YAAM,cAAc,kBAAkB;IACxC,QAAQ;IAER;EACF;AAEA,SAAO;IACL,gBAAgB,eAAe,IAAI,CAAC,MAAM,EAAE,EAAE;IAC9C,mBAAmB;IACnB,gBAAgB;IAChB,qBAAqB;IACrB,gBAAgB;IAChB,yBAAyB;IACzB,QAAQ;EACV;AACF;AAQA,eAAsB,gBAAgB,MAAuC;AAC3E,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI;AACF,UAAM,IAAI,MAAMK,MAAK,IAAI;AACzB,WAAO,EAAE,YAAY;EACvB,QAAQ;AACN,WAAO;EACT;AACF;AG5xBO,SAAS,gBAAgB,KAA4B;AAC1D,QAAM,IAAI,IAAI,KAAK;AACnB,MAAI,CAAC,KAAK,MAAM,QAAQ,MAAM,MAAO,QAAO;AAC5C,QAAM,IAAI,2BAA2B,KAAK,CAAC;AAC3C,MAAI,CAAC,EAAG,QAAO;AACf,QAAM,IAAI,OAAO,EAAE,CAAC,CAAC;AACrB,MAAI,CAAC,OAAO,SAAS,CAAC,EAAG,QAAO;AAChC,QAAM,QAAQ,EAAE,CAAC,KAAK,IAAI,YAAY;AACtC,QAAM,OAA+B;IACnC,IAAI;IACJ,GAAG;IACH,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,KAAK;IACL,KAAK,QAAQ;IACb,KAAK,QAAQ;IACb,KAAK,QAAQ;EACf;AACA,QAAM,SAAS,KAAK,IAAI;AACxB,SAAO,WAAW,SAAY,OAAO,IAAI;AAC3C;AAEA,SAAS,aAAa,KAAwC;AAC5D,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,IAAI,OAAO,IAAI,QAAQ,KAAK,EAAE,EAAE,KAAK,CAAC;AAC5C,SAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAClC;AAGA,SAAS,UAAU,KAAkD;AACnE,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,QAAQ,IAAI,MAAM,GAAG;AAC3B,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,SAAO,CAAC,MAAM,CAAC,EAAG,KAAK,GAAG,MAAM,CAAC,EAAG,KAAK,CAAC;AAC5C;AAEA,eAAe,QAAQ,MAAsC;AAC3D,QAAM,SAAS,MAAML,QAAM,MAAM,CAAC,OAAO,IAAI,GAAG,EAAE,QAAQ,MAAM,CAAC;AACjE,MAAI,OAAO,aAAa,EAAG,QAAO;AAClC,QAAM,KAAK,OAAO,UAAU,OAAO,UAAU,IAAI,MAAM,KAAK,EAAE,CAAC,KAAK,IAAI,EAAE;AAC1E,SAAO,OAAO,MAAM,EAAE,IAAI,OAAO,KAAK;AACxC;AAUA,eAAsB,gBAAgB,MAAsC;AAC1E,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,SAAS,MAAM,aAAa;AAClC,MAAI,WAAW,YAAY;AACzB,UAAM,OAAOD,OAAKD,UAAQ,GAAG,YAAY,UAAU,WAAW,IAAI;AAClE,UAAM,KAAK,MAAM,QAAQ,IAAI;AAC7B,QAAI,OAAO,KAAM,QAAO;EAC1B;AACA,QAAM,KAAK,MAAME;IACf;IACA,CAAC,UAAU,MAAM,MAAM,YAAY,mBAAmB;IACtD,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,GAAG,aAAa,GAAG;AACrB,QAAI;AACF,YAAM,OAAO,KAAK,MAAM,GAAG,UAAU,IAAI;AACzC,YAAM,MAAM,KAAK,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AAC5C,YAAM,SAAS,KAAK,OAAO,gBAAgB,IAAI,IAAI,IAAI;AACvD,UAAI,WAAW,KAAM,QAAO;IAC9B,QAAQ;IAER;EACF;AACA,QAAM,KAAK,MAAM,wBAAwB,IAAI;AAC7C,MAAI,MAAM,CAAC,GAAG,WAAW,iBAAiB,GAAG;AAC3C,WAAO,QAAQ,EAAE;EACnB;AACA,SAAO;AACT;AAMA,eAAe,WAAW,KAAqC;AAC7D,QAAM,IAAI,MAAMA,QAAM,UAAU,CAAC,SAAS,WAAW,KAAK,YAAY,WAAW,GAAG;IAClF,QAAQ;EACV,CAAC;AACD,MAAI,EAAE,aAAa,EAAG,QAAO;AAC7B,QAAM,IAAI,OAAO,UAAU,EAAE,UAAU,IAAI,KAAK,GAAG,EAAE;AACrD,SAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAClC;AAQA,eAAsB,4BACpB,aACA,MACwB;AACxB,SAAO,WAAW,mBAAmB,aAAa,IAAI,CAAC;AACzD;AAOA,eAAsB,2BAAmD;AACvE,QAAM,IAAI,MAAMA;IACd;IACA;MACE;MACA;MACA;MACA;MACA,GAAG,uBAAuB;IAC5B;IACA,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,EAAE,aAAa,EAAG,QAAO;AAC7B,QAAM,SAAS,EAAE,UAAU,IACxB,MAAM,IAAI,EACV,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAC7B,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,MAAI,QAAQ;AACZ,MAAI,MAAM;AACV,aAAW,QAAQ,OAAO;AACxB,UAAM,CAAC,EAAE,IAAI,IAAI,KAAK,MAAM,GAAI;AAChC,UAAM,IAAI,OAAO,gBAAgB,IAAI,IAAI;AACzC,QAAI,MAAM,MAAM;AACd,eAAS;AACT,YAAM;IACR;EACF;AACA,SAAO,MAAM,QAAQ;AACvB;AAGA,eAAsB,oBAA4C;AAChE,SAAO,QAAQD,OAAKD,UAAQ,GAAG,WAAW,CAAC;AAC7C;AAEA,SAAS,iBAAiB,QAAsC;AAC9D,QAAM,IAAI,OAAO;AACjB,SAAO;IACL,aAAa,GAAG,eAAe,EAAE,cAAc,IAAI,EAAE,cAAc;IACnE,MAAM,GAAG,QAAQ,EAAE,OAAO,IAAI,EAAE,OAAO;IACvC,WAAW,GAAG,aAAa,EAAE,YAAY,IAAI,EAAE,YAAY;IAC3D,MAAM,GAAG,QAAQ;EACnB;AACF;AAOA,SAAS,gBAAgB,WAA8B,YAAwC;AAC7F,QAAM,KAAM,YAAgE;AAC5E,MAAI,CAAC,GAAI,QAAO;AAChB,QAAM,MAAM,OAAO,GAAG,WAAW,YAAY,GAAG,SAAS,IAAI,GAAG,SAAS;AACzE,QAAM,OAAO,OAAO,GAAG,aAAa,YAAY,GAAG,WAAW,IAAI,GAAG,WAAW;AAChF,QAAM,OAAO,OAAO,GAAG,cAAc,YAAY,GAAG,YAAY,IAAI,GAAG,YAAY;AACnF,SAAO;IACL,aAAa,OAAO,UAAU;IAC9B,MAAM,OAAO,OAAO,MAAM,UAAU;IACpC,WAAW,QAAQ,UAAU;IAC7B,MAAM,UAAU;EAClB;AACF;AAgBA,eAAe,uBAAuB,WAA2C;AAC/E,QAAM,IAAI,MAAME;IACd;IACA,CAAC,MAAM,MAAM,YAAY,SAAS,SAAS,KAAK,YAAY,aAAa,QAAQ;IACjF,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,EAAE,aAAa,EAAG,QAAO;AAE7B,QAAM,SAAS,EAAE,UAAU,IAAI,MAAM,IAAI,EAAE,CAAC,GAAG,KAAK;AACpD,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,IAAI,4BAA4B,KAAK,KAAK;AAChD,QAAM,KAAK,IAAI,EAAE,CAAC,EAAG,KAAK,IAAI;AAC9B,SAAO,gBAAgB,EAAE;AAC3B;AAUA,eAAsB,iBAAiB,QAA8C;AACnF,QAAM,WAAqB,CAAC;AAC5B,QAAM,aAAa,MAAM,iBAAiB,OAAO,SAAS;AAC1D,QAAM,SAAS,gBAAgB,iBAAiB,MAAM,GAAG,UAAU;AAEnE,QAAM,CAAC,eAAe,YAAY,mBAAmB,yBAAyB,IAC5E,MAAM,QAAQ,IAAI;IAChB,uBAAuB,OAAO,SAAS;IACvC,OAAO,eAAe,gBAAgB,OAAO,YAAY,IAAI,QAAQ,QAAQ,IAAI;IACjF,OAAO,cAAc,QAAQ,OAAO,WAAW,IAAI,QAAQ,QAAQ,IAAI;IACvE,OAAO,kBAAkB,WAAW,OAAO,eAAe,IAAI,QAAQ,QAAQ,IAAI;EACpF,CAAC;AACH,QAAM,gBACJ,kBAAkB,QAAQ,eAAe,OACrC,QACC,iBAAiB,MAAM,cAAc;AAC5C,MAAI,kBAAkB,MAAM;AAC1B,aAAS,KAAK,uCAAuC;EACvD;AAEA,QAAM,OAAyB;IAC7B,QAAQ;IACR,MAAM;IACN,YAAY;IACZ,cAAc;IACd,eAAe,OAAO;IACtB,YAAY;IACZ,MAAM;IACN;IACA;IACA,uBAAuB;IACvB,YAAY;IACZ,YAAY;IACZ,gBAAgB;IAChB,iBAAiB;IACjB;IACA;EACF;AAEA,MAAK,MAAM,uBAAuB,OAAO,SAAS,MAAO,WAAW;AAClE,WAAO;EACT;AAEA,QAAM,OAAO,MAAMA;IACjB;IACA,CAAC,SAAS,eAAe,YAAY,cAAc,OAAO,SAAS;IACnE,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,KAAK,aAAa,KAAK,CAAC,KAAK,OAAO,KAAK,GAAG;AAC9C,WAAO;EACT;AACA,MAAI;AACJ,MAAI;AACF,WAAO,KAAK,MAAM,KAAK,OAAO,KAAK,EAAE,MAAM,IAAI,EAAE,CAAC,CAAE;EACtD,QAAQ;AACN,WAAO;EACT;AAEA,QAAM,UAAU,UAAU,KAAK,QAAQ;AACvC,QAAM,eAAe,UAAU,gBAAgB,QAAQ,CAAC,CAAC,IAAI;AAC7D,QAAM,iBAAiB,UAAU,gBAAgB,QAAQ,CAAC,CAAC,IAAI;AAC/D,QAAM,UAAU,UAAU,KAAK,KAAK;AACpC,QAAM,UAAU,UAAU,KAAK,OAAO;AAEtC,SAAO;IACL,GAAG;IACH,MAAM;IACN,YAAY,aAAa,KAAK,OAAO;IACrC;;;IAGA,eAAe,OAAO,eAAe;IACrC,YAAY,aAAa,KAAK,OAAO;IACrC,MAAM,KAAK,OAAO,OAAO,SAAS,KAAK,MAAM,EAAE,KAAK,OAAO;IAC3D,YAAY,UAAU,gBAAgB,QAAQ,CAAC,CAAC,IAAI;IACpD,YAAY,UAAU,gBAAgB,QAAQ,CAAC,CAAC,IAAI;IACpD,gBAAgB,UAAU,gBAAgB,QAAQ,CAAC,CAAC,IAAI;IACxD,iBAAiB,UAAU,gBAAgB,QAAQ,CAAC,CAAC,IAAI;EAC3D;AACF;AC9QO,IAAM,iBAA2B;EACtC,MAAM;EAEN,MAAM,OAAO,KAA4C;AACvD,UAAM,KAAM,IAAI,mBAAmB,CAAC;AACpC,UAAM,OAAyB;MAC7B,eAAe,IAAI;MACnB,MAAM,IAAI;MACV,aAAa,GAAG,eAAe;MAC/B,eAAe,IAAI;MACnB,OAAO,IAAI;MACX,OAAO,IAAI;MACX,cAAc,GAAG;MACjB,WAAW,GAAG;MACd,aAAa,GAAG;MAChB,gBAAgB,GAAG;MACnB,gBAAgB,IAAI;MACpB,SAAS,IAAI;MACb,kBAAkB,IAAI;MACtB,KAAK,IAAI;MACT,QAAQ,GAAG,gBAAgB,SAAY,EAAE,aAAa,GAAG,YAAY,IAAI;MACzE,UAAU,GAAG;MACb,kBAAkB,GAAG;MACrB,aAAa,IAAI;MACjB,QAAQ,IAAI,UAAU;IACxB;AACA,UAAM,SAAS,MAAM,UAAU,IAAI;AACnC,WAAO;MACL,QAAQ,EAAE,GAAG,OAAO,QAAQ,UAAU,SAAS;MAC/C,YAAY,OAAO;IACrB;EACF;EAEA,MAAM,MAAM,KAAoC;AAC9C,UAAM,EAAE,OAAO,IAAI,MAAM,SAAS,IAAI,EAAE;AACxC,WAAO,EAAE,GAAG,QAAQ,UAAU,SAAS;EACzC;EAEA,MAAM,MAAM,KAA+B;AACzC,UAAM,SAAS,IAAI,EAAE;EACvB;EAEA,MAAM,OAAO,KAA+B;AAC1C,UAAM,WAAW,IAAI,EAAE;EACzB;EAEA,MAAM,KAAK,KAA+B;AACxC,UAAM,QAAQ,IAAI,EAAE;EACtB;EAEA,MAAM,QAAQ,KAA+B;AAC3C,UAAM,WAAW,IAAI,EAAE;EACzB;EAEA,MAAM,QAAQ,KAAuC;AACnD,UAAM,OAAO,MAAM,WAAW,IAAI,EAAE;AACpC,WAAO;MACL,QAAQ,KAAK;;;MAGb,OAAO,KAAK,UAAU,cAAc,YAAY,KAAK;MACrD,WAAW,KAAK;MAChB,KAAK,KAAK;IACZ;EACF;EAEA,MAAM,WAAW,KAA0C;AACzD,WAAO,uBAAuB,IAAI,SAAS;EAC7C;EAEA,MAAM,MAAM,KAA2C;AACrD,WAAO,iBAAiB,GAAG;EAC7B;EAEA,MAAM,KAAK,KAAgB,MAAgB,MAAyC;AAClF,UAAM,IAAI,MAAM,UAAU,IAAI,WAAW,MAAM,MAAM,OAAO,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC,CAAC;AACpF,WAAO,EAAE,UAAU,EAAE,UAAU,QAAQ,EAAE,QAAQ,QAAQ,EAAE,OAAO;EACpE;EAEA,MAAM,WACJ,KACA,MACiB;AACjB,QAAI,IAAI,qBAAqB,QAAW;AACtC,YAAM,IAAI;QACR,OAAO,IAAI,IAAI;MACjB;IACF;AACA,UAAM,SAAS,MAAM,aAAa;AAClC,QAAI,WAAW,cAAc,CAAC,MAAM,UAAU;AAE5C,aAAO,UAAU,IAAI,SAAS;IAChC;AACA,QAAI,IAAI,iBAAiB,CAAC,MAAM,UAAU;AACxC,aAAO,IAAI,eAAgB,MAAM,eAAe,IAAI,aAAa;IACnE;AACA,QAAI,IAAI,gBAAgB,QAAW;AACjC,YAAM,IAAI;QACR,iCAAiC,IAAI,IAAI;MAC3C;IACF;AACA,WAAO,oBAAoB,OAAO,IAAI,WAAW,CAAC;EACpD;EAEA,MAAM,QAAQ,MAA8C;AAM1D,UAAM,MAAM;AACZ,QAAI,CAAC,KAAK,SAAU,MAAM,YAAY,GAAG,GAAI;AAC3C,WAAK,QAAQ,gBAAgB,GAAG,yDAAoD;AACpF,aAAO,CAAC;IACV;AACA,SAAK,QAAQ,yBAAyB,GAAG,QAAG;AAC5C,UAAM,WAAW,EAAE,KAAK,YAAY,KAAK,MAAM,CAAC;AAChD,SAAK,QAAQ,gBAAgB,GAAG,QAAQ;AACxC,WAAO,CAAC;EACV;AACF;AChHA,IAAM,kBAAkB;AAExB,eAAea,YAAW,GAA6B;AACrD,MAAI;AACF,UAAMR,MAAK,CAAC;AACZ,WAAO;EACT,QAAQ;AACN,WAAO;EACT;AACF;AAEA,eAAeuB,oBAAmB,MAAiC;AACjE,QAAM,SAAmB,CAAC;AAC1B,iBAAe,KAAK,KAA4B;AAC9C,QAAI;AACJ,QAAI;AACF,gBAAU,MAAM1B,SAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;IACtD,QAAQ;AACN;IACF;AACA,eAAW,OAAO,SAAS;AACzB,YAAM,OAAOH,OAAK,KAAK,IAAI,IAAI;AAC/B,UAAI,IAAI,eAAe,GAAG;AACxB,YAAI;AACF,gBAAMM,MAAK,IAAI;QACjB,QAAQ;AACN,iBAAO,KAAKI,UAAS,MAAM,IAAI,CAAC;QAClC;MACF,WAAW,IAAI,YAAY,GAAG;AAC5B,cAAM,KAAK,IAAI;MACjB;IACF;EACF;AACA,QAAM,KAAK,IAAI;AACf,SAAO;AACT;AAEA,eAAe,WAAW,QAAiC;AACzD,SAAOR,SAAQF,OAAKQ,QAAO,GAAG,YAAY,MAAM,SAAS,CAAC;AAC5D;AAEA,SAAS,YAAY,WAAqB,CAAC,GAAgB;AACzD,SAAO,EAAE,aAAa,MAAM,SAAS,YAAY;EAAC,GAAG,SAAS;AAChE;AAEA,eAAe,eAAe,UAAkB,OAAgC;AAC9E,QAAM,cAAcR,OAAKQ,QAAO,GAAG,YAAY,KAAK,IAAIC,WAAS,QAAQ,CAAC,SAAS;AAMnF,QAAMR,QAAM,OAAO,CAAC,QAAQ,aAAa,MAAM,UAAU,GAAG,GAAG;IAC7D,KAAK,EAAE,GAAG,QAAQ,KAAK,kBAAkB,IAAI;EAC/C,CAAC;AACD,SAAO;AACT;AAEA,SAAS,YAAY,OAAsC;AACzD,SAAO,YAAY;AACjB,eAAW,KAAK,OAAO;AACrB,YAAMI,IAAG,GAAG,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;IAC9C;EACF;AACF;AAMA,eAAe,uBACb,OACA,YACA,kBACsB;AACtB,QAAM,WAAW,MAAM,WAAW,KAAK;AACvC,MAAI,cAA6B;AACjC,MAAI;AACF,UAAM,SAAS,YAAYL,OAAK,UAAU,gBAAgB,CAAC;AAC3D,kBAAc,MAAM,eAAe,UAAU,KAAK;AAClD,WAAO;MACL;MACA,SAAS,YAAY,CAAC,UAAU,WAAW,CAAC;MAC5C,UAAU,CAAC;IACb;EACF,SAAS,KAAK;AACZ,UAAMK,IAAG,UAAU,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AACnD,QAAI,YAAa,OAAMA,IAAG,aAAa,EAAE,OAAO,KAAK,CAAC;AACtD,UAAM;EACR;AACF;AAgBA,IAAM,0BAA0B;EAC9B;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AACF;AAcA,eAAsB,2BACpB,OAA2B,CAAC,GACN;AACtB,QAAM,WAAW,KAAK,YAAYN,UAAQ;AAC1C,QAAM,aAAaC,OAAK,UAAU,SAAS;AAC3C,MAAI,CAAE,MAAMc,YAAW,UAAU,EAAI,QAAO,YAAY;AAExD,QAAM,WAAW,MAAM,WAAW,eAAe;AACjD,MAAI,cAA6B;AACjC,MAAI;AAQF,UAAM,SAAS,MAAMe,oBAAmB,UAAU;AAClD,UAAM,WAAW;MACf;MACA;MACA,GAAG,wBAAwB,IAAI,CAAC,MAAM,aAAa,CAAC,EAAE;MACtD,GAAG,OAAO,IAAI,CAAC,MAAM,cAAc,CAAC,EAAE;IACxC;AACA,UAAM5B,QAAM,SAAS;MACnB;MACA;MACA,GAAG;MACH,GAAG,UAAU;MACb,GAAG,QAAQ;IACb,CAAC;AAGD,UAAM,eAAeD,OAAK,UAAU,eAAe;AACnD,QAAI,MAAMc,YAAW,YAAY,GAAG;AAClC,UAAI;AACF,cAAM,SAAS,KAAK,MAAM,MAAMV,UAAS,cAAc,MAAM,CAAC;AAC9D,cAAM,WAAW,gBAAgB,QAAQ,QAAQ;AACjD,YAAI,SAAS,gBAAgB,SAAS,GAAG;AACvC,gBAAMG,WAAU,cAAc,KAAK,UAAU,SAAS,MAAM,MAAM,CAAC,CAAC;QACtE;MACF,QAAQ;MAER;IACF;AAQA,UAAM,iBAAiBP,OAAK,UAAU,cAAc;AACpD,QAAI;AACJ,QAAI,MAAMc,YAAW,cAAc,GAAG;AACpC,UAAI;AACF,kBAAU,KAAK,MAAM,MAAMV,UAAS,gBAAgB,MAAM,CAAC;MAC7D,QAAQ;AACN,kBAAU;MACZ;IACF;AACA,QAAI,YAAY,UAAa,YAAY,MAAM;AAC7C,gBAAU;QACR,eAAe;QACf,aAAa;QACb,+BAA+B;QAC/B,UAAU,EAAE,CAAC,eAAe,GAAG,EAAE,wBAAwB,KAAK,EAAE;MAClE;IACF,OAAO;AACL,gBAAU,gBAAgB,SAAS,QAAQ,EAAE;AAC7C,gBAAU,uBAAuB,OAAO,EAAE;AAC1C,UAAI,KAAK,eAAe;AACtB,kBAAU,gBAAgB,SAAS,KAAK,eAAe,eAAe,EAAE;MAC1E;AACA,gBAAU,eAAe,SAAS,eAAe,EAAE;IACrD;AACA,UAAMG,WAAUP,OAAK,UAAU,cAAc,GAAG,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC;AAMhF,UAAM,aAAaA,OAAK,UAAU,SAAS;AAC3C,QAAI,MAAMc,YAAW,UAAU,GAAG;AAChC,UAAI;AACF,cAAM,UAAU,MAAMX,SAAQ,YAAY,EAAE,eAAe,KAAK,CAAC;AACjE,mBAAW,OAAO,SAAS;AACzB,cAAI,CAAC,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,SAAS,OAAO,EAAG;AAClD,gBAAM,OAAOH,OAAK,YAAY,IAAI,IAAI;AACtC,gBAAM,MAAM,MAAMI,UAAS,MAAM,MAAM;AACvC,gBAAM,WAAW,IACd,MAAM,GAAG,QAAQ,mBAAmB,EACpC,KAAK,+BAA+B;AACvC,cAAI,aAAa,IAAK,OAAMG,WAAU,MAAM,QAAQ;QACtD;MACF,QAAQ;MAER;IACF;AAEA,kBAAc,MAAM,eAAe,UAAU,eAAe;AAC5D,WAAO;MACL;MACA,SAAS,YAAY,CAAC,UAAU,WAAW,CAAC;MAC5C,UAAU,CAAC;IACb;EACF,SAAS,KAAK;AACZ,UAAMF,IAAG,UAAU,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AACnD,QAAI,YAAa,OAAMA,IAAG,aAAa,EAAE,OAAO,KAAK,CAAC;AACtD,UAAM;EACR;AACF;AAaA,eAAsB,kCAAwD;AAC5E,MAAI,CAAE,MAAMS,YAAW,uBAAuB,EAAI,QAAO,YAAY;AACrE,SAAO,uBAAuB,gBAAgB,yBAAyB,mBAAmB;AAC5F;AAQA,IAAM,uBAAuB;EAC3B;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;EAGA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AACF;AAEA,IAAM,yBACJ;AAmBF,eAAsB,0BACpB,OAA0B,CAAC,GACL;AACtB,QAAM,WAAW,KAAK,YAAYf,UAAQ;AAC1C,QAAM,YAAYC,OAAK,UAAU,QAAQ;AACzC,MAAI,CAAE,MAAMc,YAAW,SAAS,EAAI,QAAO,YAAY;AAEvD,QAAM,WAAW,MAAM,WAAW,cAAc;AAChD,MAAI,cAA6B;AACjC,MAAI;AACF,UAAM,cAAc,MAAMe,oBAAmB,SAAS;AACtD,UAAM5B,QAAM,SAAS;MACnB;MACA;MACA,GAAG,YAAY,IAAI,CAAC,MAAM,cAAc,CAAC,EAAE;MAC3C;MACA,GAAG;MACH,GAAG,SAAS;MACZ,GAAG,QAAQ;IACb,CAAC;AACD,kBAAc,MAAM,eAAe,UAAU,cAAc;AAC3D,WAAO;MACL;MACA,SAAS,YAAY,CAAC,UAAU,WAAW,CAAC;MAC5C,UAAU,CAAC;IACb;EACF,SAAS,KAAK;AACZ,UAAMI,IAAG,UAAU,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AACnD,QAAI,YAAa,OAAMA,IAAG,aAAa,EAAE,OAAO,KAAK,CAAC;AACtD,UAAM;EACR;AACF;AAOA,eAAsB,+BACpB,OAA0B,CAAC,GACL;AACtB,QAAM,WAAW,KAAK,YAAYN,UAAQ;AAC1C,QAAM,WAAWC,OAAK,UAAU,UAAU,WAAW;AACrD,MAAI,CAAE,MAAMc,YAAW,QAAQ,EAAI,QAAO,YAAY,CAAC,sBAAsB,CAAC;AAC9E,SAAO,uBAAuB,eAAe,UAAU,WAAW;AACpE;AAQA,IAAM,yBAAyB;EAC7B;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AACF;AAYA,eAAsB,6BACpB,OAA6B,CAAC,GACR;AACtB,QAAM,WAAW,KAAK,YAAYf,UAAQ;AAC1C,QAAM,WAAWC,OAAK,UAAU,UAAU,SAAS,UAAU;AAC7D,QAAM,aAAaA,OAAK,UAAU,WAAW,UAAU;AACvD,QAAM,UAAU,MAAMc,YAAW,QAAQ;AACzC,QAAM,YAAY,MAAMA,YAAW,UAAU;AAC7C,MAAI,CAAC,WAAW,CAAC,UAAW,QAAO,YAAY;AAE/C,QAAM,WAAW,MAAM,WAAW,iBAAiB;AACnD,MAAI,cAA6B;AACjC,MAAI;AAIF,QAAI,SAAS;AACX,YAAM,aAAa,MAAMe,oBAAmB,QAAQ;AACpD,YAAM5B,QAAM,SAAS;QACnB;QACA;QACA,GAAG,WAAW,IAAI,CAAC,MAAM,cAAc,CAAC,EAAE;QAC1C;QACA,GAAG;QACH,GAAG,QAAQ;QACX,GAAG,QAAQ;MACb,CAAC;IACH;AACA,QAAI,WAAW;AACb,YAAM,cAAcD,OAAK,UAAU,QAAQ;AAC3C,YAAM,YAAY,MAAM6B,oBAAmB,UAAU;AACrD,YAAM5B,QAAM,SAAS;QACnB;QACA;QACA,GAAG,UAAU,IAAI,CAAC,MAAM,cAAc,CAAC,EAAE;QACzC,GAAG,UAAU;QACb,GAAG,WAAW;MAChB,CAAC;IACH;AACA,kBAAc,MAAM,eAAe,UAAU,iBAAiB;AAC9D,WAAO;MACL;MACA,SAAS,YAAY,CAAC,UAAU,WAAW,CAAC;MAC5C,UAAU,CAAC;IACb;EACF,SAAS,KAAK;AACZ,UAAMI,IAAG,UAAU,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AACnD,QAAI,YAAa,OAAMA,IAAG,aAAa,EAAE,OAAO,KAAK,CAAC;AACtD,UAAM;EACR;AACF;AAOA,eAAsB,kCACpB,OAA6B,CAAC,GACR;AACtB,QAAM,WAAW,KAAK,YAAYN,UAAQ;AAC1C,QAAM,WAAWC,OAAK,UAAU,UAAU,SAAS,YAAY,WAAW;AAC1E,MAAI,CAAE,MAAMc,YAAW,QAAQ,EAAI,QAAO,YAAY;AACtD,SAAO,uBAAuB,kBAAkB,UAAU,WAAW;AACvE;ACnfO,SAAS,qBAAqB,QAAgB,UAA2B;AAC9E,SAAO,aAAa,KAAK,CAAC,sBAAsB,KAAK,MAAM;AAC7D;AAwBA,eAAsB,iBACpB,WACA,YAAY,KACZ,YAAY,eACe;AAC3B,QAAM,OAAO,MAAM,UAAU,WAAW,CAAC,iBAAiB,WAAW,MAAM,GAAG;IAC5E,MAAM;IACN;EACF,CAAC;AACD,MAAI,qBAAqB,KAAK,QAAQ,KAAK,QAAQ,GAAG;AACpD,WAAO,EAAE,IAAI,MAAM,gBAAgB,KAAK;EAC1C;AAEA,QAAM,OAAO,MAAM,UAAU,WAAW,CAAC,iBAAiB,QAAQ,YAAY,SAAS,GAAG;IACxF,MAAM;IACN;EACF,CAAC;AACD,MAAI,KAAK,aAAa,EAAG,QAAO,EAAE,IAAI,KAAK;AAC3C,SAAO;IACL,IAAI;IACJ,QAAQ,8BAA8B,KAAK,UAAU,KAAK,UAAU,QAAQ,OAAO,KAAK,QAAQ,CAAC,EAAE;EACrG;AACF;","names":["randomBytes","mkdir","stat","homedir","basename","join","resolve","execa","restart","mkdir","readdir","readFile","rm","stat","writeFile","homedir","join","execa","parseYaml","readFile","stat","dirname","join","isPlainObject","fileExists","mkdir","readFile","join","execa","mkdir","readFile","writeFile","homedir","dirname","join","readdir","stat","STATE_DIR","spawnSync","stat","homedir","join","execa","execa","readFile","homedir","join","existsSync","dirname","resolve","mkdir","readdir","rm","stat","mkdir","mkdtemp","readFile","readdir","rm","writeFile","homedir","tmpdir","basename","join","execa","stat","execa","randomBytes","existsSync","mkdir","readFile","writeFile","homedir","dirname","join","resolve","delay","fileURLToPath","execa","execa","readdir","rm","stat","join","execa","join","homedir","join","execa","mkdtemp","readdir","readFile","rm","stat","writeFile","tmpdir","basename","relative","isPlainObject","mkdir","STATE_DIR","pathExists","shQuote","spawnSync","dirname","existsSync","resolve","sanitizeMnemonic","delay","here","fileURLToPath","pidAlive","randomBytes","loadConfig","persisted","endpoints","findBrokenSymlinks"]}