@mycadet/app 0.13.0-dev.202611011818.51165c0 → 0.13.0-dev.202611012320.cea2f8a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/package/package.json +1 -1
- package/dist/package/resolution-contract-Coknve4C.js.map +1 -1
- package/dist/standalone/assets/injected-config-DJGGNYkD.js.map +1 -1
- package/dist/standalone/assets/local-mode-host-lL-KCJsc.js.map +1 -1
- package/dist/standalone/index.html +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolution-contract-Coknve4C.js","names":[],"sources":["../../../../packages/service-contracts/src/ingress.ts","../../../../packages/environments/src/seeds.ts","../../__vite-browser-external","../../../../packages/environments/src/install-layout.ts","../../../../packages/local-mode/src/lockfile-contract.ts","../../src/assistant/resolution-contract.ts"],"sourcesContent":["/**\n * Map a Cadet Cloud host onto its environment's tunnel host, following the\n * deployment naming convention (`cloud.mycadet.ai` → `tunnel.mycadet.ai`,\n * `{env}-cloud.mycadet.ai` → `{env}-tunnel.mycadet.ai`). Returns null for\n * hosts outside that convention (localhost, custom domains). Shared by the\n * gateway speech relay and the web live-voice client so the two ends of the\n * cloud-speech transport can't drift.\n */\nexport function tunnelHostForCloudHost(host: string): string | null {\n if (host === \"cloud.mycadet.ai\") {\n return \"tunnel.mycadet.ai\";\n }\n const match = /^([a-z0-9-]+)-cloud\\.mycadet\\.ai$/.exec(host);\n return match ? `${match[1]}-tunnel.mycadet.ai` : null;\n}\n\nexport function normalizePublicBaseUrl(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const normalized = value.trim().replace(/\\/+$/, \"\");\n return normalized.length > 0 ? normalized : undefined;\n}\n\n/**\n * Application close code the gateway's tunnel bridge sends to proxied\n * WebSockets when the tunnel itself is lost (tunnel disconnect, gateway\n * shutdown). A dedicated code because the natural 1001 (going away) cannot\n * be sent through the JS `close()` API (the bridge would remap it to a\n * misleading 4001), and Bun's WebSocket client drops close reasons, so the\n * code is the only signal that survives the relay to the daemon.\n */\nexport const GATEWAY_TUNNEL_LOST_WS_CLOSE_CODE = 4801;\n\nexport function normalizeHttpPublicBaseUrl(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const trimmed = value.trim();\n if (trimmed.length === 0) return undefined;\n if (/[?#]/.test(trimmed)) return undefined;\n\n try {\n const url = new URL(trimmed);\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n return undefined;\n }\n if (!url.hostname) return undefined;\n url.pathname = url.pathname.replace(/\\/+$/, \"\") || \"/\";\n return url.toString();\n } catch {\n return undefined;\n }\n}\n\n/**\n * The same validation as `normalizeHttpPublicBaseUrl`, minus the root path it\n * always emits (`https://x` becomes `https://x/`). Callers that append a path\n * or persist the result want this shape, so they neither join through a double\n * slash nor re-strip what the normalizer just added.\n */\nexport function normalizeHttpPublicBaseUrlWithoutTrailingSlash(\n value: unknown,\n): string | undefined {\n return normalizePublicBaseUrl(normalizeHttpPublicBaseUrl(value));\n}\n\n/**\n * A trimmed string, or undefined when the value is not a non-blank string.\n *\n * Exported because the identity check in the daemon's tunnel probe compares an\n * id recorded here against one an edge serves, and the two sides have to trim\n * alike: a padded served id must read as a match, not as a different\n * assistant.\n */\nexport function trimmedNonEmptyString(value: unknown): string | undefined {\n if (typeof value !== \"string\") {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\n// Records `cadet tunnel` leaves in the workspace config's `ingress` section.\n// The CLI writes them and the daemon reads them, and the two are separate\n// build units that cannot import each other, so the key names, the provider\n// allowlist, and the validation all live here rather than in a copy on each\n// side.\n\n/** Key under `ingress` holding the tunnel that most recently ran. */\nexport const INGRESS_LAST_TUNNEL_KEY = \"lastTunnel\";\n\n/**\n * Key under `ingress` holding a tunnel published for device pairing alone.\n *\n * A tailnet-only tunnel started while webhook integrations are configured\n * cannot carry their callbacks, so it leaves `publicBaseUrl` (the callback\n * base) as it is and records itself here. Readers that answer the pairing card\n * prefer this address over `publicBaseUrl`; teardown drops the key.\n */\nexport const INGRESS_PAIRING_TUNNEL_KEY = \"pairingTunnel\";\n\n/** Key under `ingress` holding the assistant id that tunnel fronted. */\nexport const INGRESS_ASSISTANT_ID_KEY = \"assistantId\";\n\n/**\n * The tunnel providers the CLI offers, in the order they are presented —\n * the managed `cadet` tunnel first (listed, not yet implemented), then the\n * local ones. `cadet tunnel`'s accepted `--provider` values and the\n * persisted-record validation below both derive from it.\n */\nexport const TUNNEL_PROVIDERS = [\n \"cadet\",\n \"ngrok\",\n \"cloudflare\",\n \"tailscale\",\n] as const;\n\nexport type TunnelProviderName = (typeof TUNNEL_PROVIDERS)[number];\n\n/** A recorded tunnel, kept after teardown so UIs can name the command to restart. */\nexport interface TunnelRecord {\n provider: TunnelProviderName;\n publicBaseUrl: string;\n}\n\nfunction isTunnelProviderName(value: unknown): value is TunnelProviderName {\n return (\n typeof value === \"string\" &&\n (TUNNEL_PROVIDERS as readonly string[]).includes(value)\n );\n}\n\n/**\n * Parse an `ingress.lastTunnel` or `ingress.pairingTunnel` value, or null when\n * it is absent or malformed.\n *\n * A hand-edited config must not hand callers an unusable address or a provider\n * name no command accepts: the provider is checked against the allowlist\n * because readers render it into a restart command, and the URL faces the same\n * absolute HTTP(S) constraint as every other public base URL. The URL comes\n * back in the shape the validator produced, so readers never re-normalize it.\n */\nexport function parseTunnelRecord(value: unknown): TunnelRecord | null {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return null;\n }\n const { provider, publicBaseUrl } = value as Record<string, unknown>;\n if (!isTunnelProviderName(provider)) {\n return null;\n }\n const normalized =\n normalizeHttpPublicBaseUrlWithoutTrailingSlash(publicBaseUrl);\n if (normalized === undefined) {\n return null;\n }\n return { provider, publicBaseUrl: normalized };\n}\n\n/**\n * The tunnel a reader should offer to restart, given an `ingress` section, or\n * null when it records none.\n *\n * A pairing-only record wins: it is written by a tunnel that deliberately left\n * `publicBaseUrl` (and the `lastTunnel` beside it) pointing at an older public\n * provider, so naming that older one would send the user to rebuild an ingress\n * they are not using instead of the tunnel actually in front of them.\n */\nexport function selectRestartTunnelRecord(\n ingress: unknown,\n): TunnelRecord | null {\n if (typeof ingress !== \"object\" || ingress === null) {\n return null;\n }\n const section = ingress as Record<string, unknown>;\n return (\n parseTunnelRecord(section[INGRESS_PAIRING_TUNNEL_KEY]) ??\n parseTunnelRecord(section[INGRESS_LAST_TUNNEL_KEY])\n );\n}\n\n/** Parse an `ingress.assistantId` value, or null when it is absent or blank. */\nexport function parseRecordedAssistantId(value: unknown): string | null {\n return trimmedNonEmptyString(value) ?? null;\n}\n","// Loaded standalone by Capacitor's single-file TS config loader — keep\n// imports type-only and keep this file free of the import-meta meta-property:\n// the loader transpiles this file to CJS, where that syntax is illegal and\n// makes Node misroute the result as ESM.\nimport type { EnvironmentDefinition, PortMap } from \"./types.js\";\n\n/**\n * Non-prod port blocks. Each environment gets a 1000-port window in the\n * 17000–21000 band. Within a block, services are spaced 100 apart so up to\n * 100 assistants can coexist without the scan (`findAvailablePort`) running\n * one service's range into the next. Band chosen to sit below Linux's\n * default ephemeral start (32768) and macOS's (49152), and away from the\n * 3000/5000/8000/9000 dev-tool swamp. Production keeps its legacy,\n * non-contiguous port set (7821/7830/6333/8090/8080/8765): cross-env\n * collision is the only problem this change targets, prod is unaffected\n * because only one env's assistants compete on a given machine, and\n * churning it would leave existing hatches on 7821 while new ones\n * allocated elsewhere.\n */\nfunction portBlock(base: number): PortMap {\n return {\n daemon: base,\n gateway: base + 100,\n qdrant: base + 200,\n ces: base + 300,\n outboundProxy: base + 400,\n tcp: base + 500,\n };\n}\n\n/** Web origin for the current environment (CADET_ENVIRONMENT or VITE_ENVIRONMENT; unknown falls back to dev). */\nexport function cloudWebUrl(): string {\n // globalThis.process reads the same env under bun/node while staying\n // crash-free in browser bundles (no bare `process` reference) — and free\n // of import-meta syntax, which the Capacitor config loader can't transpile.\n const env = (\n globalThis as unknown as {\n process?: { env?: Record<string, string | undefined> };\n }\n ).process?.env;\n const envName = env?.CADET_ENVIRONMENT ?? env?.VITE_ENVIRONMENT;\n const seed = (envName && SEEDS[envName]) || SEEDS.dev;\n return seed.webUrl;\n}\n\n/** The cloud assistant hub — booting at the bare host bounces non-prod shells. */\nexport function cloudAssistantHubUrl(): string {\n return `${cloudWebUrl()}/assistant`;\n}\n\n/**\n * Built-in environment definitions and the source of truth for the\n * set of known environment names.\n *\n * Custom environments via a user config file are a future phase — see the\n * \"Coexisting environments\" design doc. Until then, a call site that needs a\n * new environment must add it here and rebuild.\n */\nexport const SEEDS: Record<string, EnvironmentDefinition> = {\n production: {\n name: \"production\",\n cloudUrl: \"https://cloud.mycadet.ai\",\n webUrl: \"https://mycadet.ai\",\n },\n staging: {\n name: \"staging\",\n cloudUrl: \"https://staging-cloud.mycadet.ai\",\n webUrl: \"https://staging.mycadet.ai\",\n portsOverride: portBlock(17000),\n },\n test: {\n name: \"test\",\n // Non-functional URL — used only by unit tests for URL resolution, never\n // hit in production.\n cloudUrl: \"https://test-cloud.mycadet.ai\",\n webUrl: \"https://dev.mycadet.ai\",\n portsOverride: portBlock(19000),\n },\n dev: {\n name: \"dev\",\n cloudUrl: \"https://dev-cloud.mycadet.ai\",\n webUrl: \"https://dev.mycadet.ai\",\n portsOverride: portBlock(18000),\n },\n local: {\n name: \"local\",\n cloudUrl: \"http://localhost:8000\",\n webUrl: \"http://localhost:3000\",\n // assistantCloudUrl: \"http://host.docker.internal:8000\",\n // ^ uncomment this once dockerized hatch path is live.\n // The assistant runs in a different network namespace than the host.\n portsOverride: portBlock(20000),\n },\n};\n","module.exports = {}","/**\n * Reading the layout a Cadet runtime is installed in.\n *\n * The daemon is started either from a developer's repo checkout or from an\n * installed npm package (`<install>/node_modules/@mycadet/assistant/src/\n * index.ts`, which is what the desktop app runs). The CLI needs the layout to\n * decide whether a launch is a dev run and what to put on the daemon's PATH;\n * the daemon needs it to decide what to install as the `assistant` command.\n * Both ask the same two questions, so they ask them here.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { join, sep } from \"node:path\";\n\nconst NODE_MODULES_SEGMENT = `${sep}node_modules${sep}`;\n\n/**\n * Whether `path` sits in a developer's repo checkout rather than inside an\n * installed package.\n */\nexport function isRepoCheckoutPath(path: string): boolean {\n return !path.includes(NODE_MODULES_SEGMENT);\n}\n\n/**\n * The `assistant` command shipped with the install that contains `path`, or\n * null when there is none (a repo checkout, or an install whose dependency\n * graph omits the package declaring the bin).\n *\n * Searches innermost `node_modules` outward, since a nested install shadows a\n * hoisted one.\n */\nexport function findAssistantCommand(path: string): string | null {\n let index = path.lastIndexOf(NODE_MODULES_SEGMENT);\n\n while (index !== -1) {\n const nodeModulesDir = path.slice(\n 0,\n index + NODE_MODULES_SEGMENT.length - 1,\n );\n const command = join(nodeModulesDir, \".bin\", \"assistant\");\n if (existsSync(command)) {\n return command;\n }\n index = path.lastIndexOf(NODE_MODULES_SEGMENT, index - 1);\n }\n\n return null;\n}\n","/**\n * The lockfile wire contract — one Zod schema that is the single source of truth\n * for an assistant entry's shape, its validation, the `cloud` taxonomy, the\n * normalization, and the redaction policy.\n *\n * Kept in its own module, exported from the package as `./contract`, so\n * consumers import the contract without pulling in the host I/O graph\n * (`node:fs`, the CLI resolver). The CLI, the host I/O layer, the Electron and\n * dev-server bridges, and the renderer all depend on this one definition. It\n * references no node built-ins — the renderer bundles it directly — and `zod`\n * is its only third-party import.\n *\n * The lockfile is written by the Cadet CLI and read across the macOS↔CLI\n * boundary, which do not release in lockstep, so the parser is permissive at\n * the file level: it validates each entry whole through the schema (a\n * malformed field drops that entry, never the file), accepts — but drops from\n * the validated value — entry fields this build has never heard of, and\n * defaults `cloud` when it is absent or unknown. The schema is strict over\n * the fields it models: every field a reader needs is IN the schema, so a\n * CLI read-modify-write round-trip preserves it by construction. The\n * local-mode write path still merges raw JSON (unknown root fields survive\n * on disk; see `lockfile.ts`); the validated value carries only the modeled\n * shape.\n */\nimport { TUNNEL_PROVIDERS } from \"@mycadet/service-contracts/ingress\";\nimport { SEEDS } from \"@mycadet/environments\";\nimport { z } from \"zod\";\n\nimport { resolveEnvironmentName } from \"./environment\";\nimport { readLockfile } from \"./lockfile\";\n\n/**\n * Known deployment topologies of a Cadet:\n * - `local` — on-machine bare-metal processes\n * - `docker` — docker-managed container\n * - `apple-container` — macOS-app-managed container\n * - `cloud` — managed by Cadet Cloud\n * - `custom` — remote VM accessible via SSH\n */\nexport const KNOWN_REMOTES = [\n \"local\",\n \"docker\",\n \"apple-container\",\n \"cloud\",\n \"custom\",\n \"baseten\",\n] as const;\n\n/**\n * The topology values as one zod enum, shared by the lockfile schema (which\n * wraps it in a `catch`) and the clients' injected-config schema, so the two\n * can never drift.\n */\nexport const RemoteSchema = z.enum(KNOWN_REMOTES);\nexport type Remote = z.infer<typeof RemoteSchema>;\n\n/**\n * Keys that must never cross the host→renderer boundary. Stripped from raw\n * lockfile JSON before it leaves a host (see `stripSensitiveFields`) and absent\n * from the renderer-facing schema below; the CLI's extended type is the only\n * place they are modeled.\n */\nexport const SENSITIVE_KEYS = [\n \"bearerToken\",\n \"guardianBootstrapSecret\",\n] as const;\n\n/**\n * The runtime config a shell injects (`window.__CADET_CONFIG__`). This schema\n * is the single shape: hosts stamp it, served bundles parse the window\n * through it instead of re-declaring per-file object types.\n *\n * The URL fields are distinct origins in the topology, not synonyms:\n * `cloudUrl` is the cloud API base and `hubUrl` the cloud hub SPA the\n * pairing/settings pages link back to. The cloud login base is\n * deliberately absent: the loopback handoff reads it from the build's\n * environment seed (see startLoopbackAuth), keeping this config to the\n * origins a served bundle can't infer.\n */\nexport const InjectedConfigSchema = z.object({\n /** Nothing injected (SSR) or an unparseable config means the local cadet. */\n remote: RemoteSchema.catch(\"local\"),\n /** Cloud API base (auth endpoints + assistant API). */\n cloudUrl: z.string().optional(),\n /** Device id stamped by the Electron host (device management). */\n deviceId: z.string().optional(),\n /** Version of the shell bundle serving this page (stamped by the host). */\n version: z.string().optional(),\n /** Serving assistant's id, stamped by every host that fronts one assistant. */\n assistantId: z.string().optional(),\n /** Serving assistant's display name, stamped by every host that fronts one assistant. */\n assistantName: z.string().optional(),\n /** Cloud web SPA base for this build's environment (remote-web ingress configs only). */\n hubUrl: z.string().optional(),\n});\n\nexport type InjectedConfig = z.infer<typeof InjectedConfigSchema>;\n\n/**\n * Strip every `SENSITIVE_KEYS` field from a parsed lockfile, in place: from\n * each assistant entry and from that entry's `resources`. The one redaction\n * every host runs before lockfile data crosses to the renderer — pure, no\n * node built-ins, so it lives beside the keys it enforces and bundles into\n * the renderer through the `./contract` subpath.\n */\nexport function stripSensitiveFields(data: Record<string, unknown>): void {\n const assistants = data.assistants;\n if (!Array.isArray(assistants)) return;\n for (const assistant of assistants) {\n if (assistant && typeof assistant === \"object\") {\n const entry = assistant as Record<string, unknown>;\n for (const field of SENSITIVE_KEYS) {\n delete entry[field];\n }\n const resources = entry.resources;\n if (resources && typeof resources === \"object\") {\n for (const field of SENSITIVE_KEYS) {\n delete (resources as Record<string, unknown>)[field];\n }\n }\n }\n }\n}\n\n/**\n * Whether an entry's `runtimeUrl` is one a serving host can forward to: an\n * absolute http(s) URL. Shared by the renderer's paired-gateway URL derivation\n * and the host-side paired allowlist reader so the two can't drift.\n */\nexport function isUsableBaseUrl(url: unknown): url is string {\n if (typeof url !== \"string\" || url === \"\") {\n return false;\n }\n try {\n const parsed = new URL(url);\n return parsed.protocol === \"http:\" || parsed.protocol === \"https:\";\n } catch {\n return false;\n }\n}\n\n/**\n * The local build's control-plane base, read from the lockfile alone: the\n * auto-tunnel's recorded `publicUrl` when one is up, else the app server\n * edge's loopback port. A loopback base is reachable from the iOS simulator\n * only — a device's `127.0.0.1` is the phone itself — so a device build\n * wants a tunnel up before it stamps. `undefined` when neither record\n * carries a usable address.\n *\n * The assistants play no role in this resolution — the capacitor app shell\n * only deals with the app server — and the CLI's workspace config is never\n * touched: the lockfile is the single home for edge state.\n */\nexport function resolveLocalServerUrl(): string | undefined {\n const lockfile = readLockfile();\n const tunnelUrl = lockfile.tunnel?.publicUrl;\n if (isUsableBaseUrl(tunnelUrl)) {\n return tunnelUrl;\n }\n const port = lockfile.app?.port;\n if (typeof port === \"number\") {\n return `http://127.0.0.1:${port}`;\n }\n return undefined;\n}\n\n/**\n * The id-keyed, same-origin gateway proxy base for any assistant entry: the\n * serving host (Electron `app://` handler, Vite dev middleware, CLI web\n * server) resolves the id through the lockfile to a loopback port (locally\n * hatched) or a recorded remote `runtimeUrl` (imported pairing), so the\n * renderer never encodes either.\n */\n/**\n * First segments whose scoped `/v1/assistants/{id}/` prefix the gateway does\n * not serve: the contact family lives on flat `/v1/...` control-plane routes\n * (the shape the cloud's runtime-proxy strip delivers). Every\n * forwarder to the gateway — the scoped host proxy and the request\n * interceptor's remote-ingress rewrite alike — flattens them to match.\n */\nexport const FLATTENED_FIRST_SEGMENTS = new Set<string>([\n \"contacts\",\n \"contact-channels\",\n]);\n\n/**\n * Clouds whose gateway listens on a loopback port of this machine: the\n * on-machine daemon (\"local\") and local Docker instances (\"docker\").\n */\nexport const LOOPBACK_GATEWAY_CLOUDS: readonly Remote[] = [\"local\", \"docker\"];\n\nexport function isLoopbackGatewayCloud(cloud: Remote): boolean {\n return LOOPBACK_GATEWAY_CLOUDS.includes(cloud);\n}\n\n/**\n * The gateway port a freshly hatched assistant's gateway listens on, with\n * the `GATEWAY_PORT` env override the CLI tooling honors.\n */\nexport const GATEWAY_PORT = process.env.GATEWAY_PORT\n ? Number(process.env.GATEWAY_PORT)\n : 7830;\n\n/**\n * The cloud URL stack: the lockfile's platform base url (what the active\n * assistant was last hatched/waked against), else the `CADET_CLOUD_URL`\n * override, else the resolved environment's cloud url.\n */\nfunction getCloudUrl(): string {\n const lockfileUrl = readLockfile().cloudBaseUrl;\n return (\n (typeof lockfileUrl === \"string\" && lockfileUrl.trim()) ||\n process.env.CADET_CLOUD_URL?.trim() ||\n SEEDS[resolveEnvironmentName()]?.cloudUrl ||\n SEEDS.production!.cloudUrl\n );\n}\n\n/**\n * The URL to dial an entry's gateway on, derived from the entry's canonical\n * fields — the one seam every consumer addresses a gateway through:\n *\n * - `custom` remotes: their recorded host and port.\n * - cloud-managed: the cloud URL stack.\n * - loopback topologies (`local`, `docker`, `apple-container`): the\n * recorded loopback gateway port, else the default gateway port.\n */\nexport function getGatewayUrl(entry: LockfileAssistant): string {\n // Baseten: the gateway rides the sandbox API server's `/port/<PORT>/` route.\n if (entry.cloud === \"baseten\") {\n return `${entry.sandboxUrl.replace(/\\/+$/, \"\")}/port/${entry.port}`;\n }\n if (entry.cloud === \"custom\") {\n // Custom remotes speak TLS. Default ports elided the way a URL prints\n // them. The schema requires the address and port.\n const elided = entry.port === 443;\n return `https://${entry.ipAddress}${elided ? \"\" : `:${entry.port}`}`;\n }\n if (isCloudEntry(entry)) {\n return getCloudUrl().replace(/\\/+$/, \"\");\n }\n return `http://127.0.0.1:${entry.port}`;\n}\n\n/** Docker image metadata for the service group. Enables rollback to known-good refs. */\nexport const ContainerInfoSchema = z.object({\n assistantImage: z.string(),\n gatewayImage: z.string(),\n cesImage: z.string(),\n});\nexport type ContainerInfo = z.infer<typeof ContainerInfoSchema>;\n\n/**\n * Host-independent resource policy for one assistant's machine allocation —\n * what the allocation *should be*, as opposed to the record fields (ports,\n * instanceDir, containerInfo) that describe what *is*. The section rides\n * every topology's entry: the stored shape is the same everywhere, while how\n * a policy materializes differs per remote (see {@link MACHINE_APPLICABILITY}).\n *\n * The schema is strict — unknown keys are rejected — because the write path\n * (future `cadet machine set`, daemon PATCH, cloud API) is the loud validator:\n * a typo'd knob must fail at the boundary, never silently degrade to the\n * runtime default. Parse time is deliberately softer: the field is embedded\n * with `.catch(undefined)`, so a malformed or unrecognized section from a\n * hand-edit or a different build quarantines to the defaults while the\n * entry itself survives, and the drift is visible to readers instead of\n * costing the assistant its identity fields.\n */\nexport const MachineMemoryPolicySchema = z\n .strictObject({\n /**\n * Soft limit in bytes: where the runtime should start reclaiming early\n * and smoothly (cgroup v2 `memory.high` on docker remotes). Absent =\n * unconstrained.\n */\n high: z.number().int().positive().optional(),\n /** Hard limit in bytes: the backstop the runtime may not exceed\n * (cgroup v2 `memory.max` on docker remotes). Absent = unconstrained. */\n max: z.number().int().positive().optional(),\n })\n .superRefine((memory, context) => {\n if (\n memory.high !== undefined &&\n memory.max !== undefined &&\n memory.high > memory.max\n ) {\n context.addIssue({\n code: \"custom\",\n path: [\"high\"],\n message: `memory.high (${memory.high}) must not exceed memory.max (${memory.max})`,\n });\n }\n });\nexport type MachineMemoryPolicy = z.infer<typeof MachineMemoryPolicySchema>;\n\n/** The `machine` section: one key per resource knob, added as they migrate here. */\nexport const MachinePolicySchema = z.strictObject({\n memory: MachineMemoryPolicySchema.optional(),\n});\nexport type MachinePolicy = z.infer<typeof MachinePolicySchema>;\n\n/**\n * How a stored `machine` policy materializes per remote topology. The store\n * is host-independent — this table is the one place that says what a policy\n * *means* on each remote, so the apply layer and readers derive semantics\n * from it instead of re-deciding per call site. Remotes gain real\n * applicability as their apply paths land (cloud/gVisor, apple-container).\n */\nexport type MachineApplicability = \"cgroup\" | \"process-hint\" | \"unsupported\";\n\nexport const MACHINE_APPLICABILITY = {\n local: \"process-hint\",\n docker: \"cgroup\",\n \"apple-container\": \"unsupported\",\n cloud: \"unsupported\",\n custom: \"unsupported\",\n baseten: \"unsupported\",\n} as const satisfies Record<Remote, MachineApplicability>;\n\n/**\n * The fields every topology actually uses: the identity every reader looks\n * entries up by, the display name, and the hatch/onboarding/conversation\n * bookkeeping that is topology-agnostic.\n */\nconst BaseAssistantSchema = z.object({\n assistantId: z.string(),\n /** Platform assistant UUID for this assistant's registration. */\n cloudAssistantId: z.string().optional(),\n /** Platform base URL this assistant was registered against. */\n cloudBaseUrl: z.string().optional(),\n /** Platform organization the entry belongs to. */\n cloudOrganizationId: z.string().optional(),\n name: z.string().optional(),\n hatchedAt: z.string().optional(),\n /**\n * When this assistant finished first-run onboarding. Absent for entries that\n * predate the record, which fall back to hatch age (see the web client's\n * `onboarded-assistant.ts`).\n */\n onboardedAt: z.string().optional(),\n /** PID of the file watcher process for instances hatched with --watch. */\n watcherPid: z.number().optional(),\n /**\n * Host-independent resource policy for this assistant's machine allocation\n * (see {@link MachinePolicySchema} for the strict-at-write, fallback-at-parse\n * contract, and {@link MACHINE_APPLICABILITY} for how it materializes per\n * remote). Absent = the runtime's own defaults apply.\n */\n machine: MachinePolicySchema.optional().catch(undefined),\n});\n\n/**\n * The fields the self-hosted, on-machine topologies (local, docker,\n * apple-container) share: host-recorded ports and instance dir.\n */\nconst LocalRuntimeAssistantFields = {\n /** The gateway port a freshly hatched assistant's gateway listens on. */\n port: z.number(),\n /** The runtime daemon's port, recorded at hatch. */\n daemonPort: z.number().optional(),\n /** The instance's state root on this machine. Local instances keep their\n * data here; docker instances record their host-side volume root. */\n instanceDir: z.string().optional(),\n /** The content-editor server's port, recorded at hatch. */\n cesPort: z.number().optional(),\n /** The runtime build version this instance runs. */\n runtimeVersion: z.string().optional(),\n /** Where the runtime build is installed on this machine. */\n runtimeInstallDir: z.string().optional(),\n};\n\n/** A plain on-machine assistant: host-managed ports and instance dir. */\nconst LocalAssistantSchema = BaseAssistantSchema.extend({\n ...LocalRuntimeAssistantFields,\n cloud: z.literal(\"local\"),\n});\n\n/** A docker-hosted assistant: container metadata for rollback/upgrade. */\nconst DockerAssistantSchema = BaseAssistantSchema.extend({\n ...LocalRuntimeAssistantFields,\n cloud: z.literal(\"docker\"),\n /** Docker image metadata for rollback. Only present for docker topology entries. */\n containerInfo: ContainerInfoSchema.optional(),\n});\n\n/** An apple-container assistant, driven through its management socket. */\nconst AppleContainerAssistantSchema = BaseAssistantSchema.extend({\n ...LocalRuntimeAssistantFields,\n cloud: z.literal(\"apple-container\"),\n /** Management socket path for apple-container assistants; written by the macOS host. */\n mgmtSocket: z.string(),\n});\n\n/** A platform-hosted assistant, registered against the cloud API. */\nconst CloudAssistantSchema = BaseAssistantSchema.extend({\n cloud: z.literal(\"cloud\"),\n});\n\n/** A self-hosted remote assistant, reached at its own address. */\nconst CustomAssistantSchema = BaseAssistantSchema.extend({\n cloud: z.literal(\"custom\"),\n /** Host address of the custom remote's gateway. */\n ipAddress: z.string(),\n /** Gateway port on the custom remote. */\n port: z.number(),\n});\n\n/**\n * A Baseten sandbox entry: the sandbox server URL and the sandbox-internal\n * gateway port are pinned at creation, so every reader can address\n * `{sandboxUrl}/port/{port}` without a fallback.\n */\nconst BasetenAssistantSchema = BaseAssistantSchema.extend({\n cloud: z.literal(\"baseten\"),\n /** The Baseten sandbox server URL; the gateway rides `{sandboxUrl}/port/{port}`. */\n sandboxUrl: z.string(),\n /** The sandbox-internal gateway port, piped in as `GATEWAY_PORT` at creation. */\n port: z.number(),\n});\n\n/**\n * Route absent and unknown topology values to the local default before the\n * union discriminates, keeping the on-disk contract permissive about clouds\n * this build has never heard of.\n */\n/**\n * The one preprocess the entry union parses through. Routes absent and\n * unknown topology values to the local default before the union\n * discriminates, and backfills the legacy `resources.gatewayPort` onto\n * `port` — but only when the flat `port` field would fail the number\n * schema (absent or not a number): a valid port always wins. Zod has no\n * field-level sibling fallback, so the object-level preprocess is the\n * supported surface for this.\n */\nfunction normalizeEntry(entry: unknown): unknown {\n if (typeof entry !== \"object\" || entry === null) return entry;\n let rec = entry as Record<string, unknown>;\n const cloud = rec.cloud;\n if (!(typeof cloud === \"string\" && RemoteSchema.safeParse(cloud).success)) {\n rec = { ...rec, cloud: \"local\" };\n }\n if (rec.port === undefined) {\n // The flat port failed the required schema (absent on a legacy or bare\n // entry): fall back to the legacy resources.gatewayPort, else the\n // gateway default. A mistyped port never falls back — a bad modeled\n // field still drops the entry.\n const resources = rec.resources;\n const gatewayPort =\n resources && typeof resources === \"object\"\n ? (resources as Record<string, unknown>).gatewayPort\n : undefined;\n rec = {\n ...rec,\n port: typeof gatewayPort === \"number\" ? gatewayPort : GATEWAY_PORT,\n };\n }\n return rec;\n}\n\nexport const LockfileAssistantSchema = z.preprocess(\n normalizeEntry,\n z.discriminatedUnion(\"cloud\", [\n LocalAssistantSchema,\n DockerAssistantSchema,\n AppleContainerAssistantSchema,\n CloudAssistantSchema,\n CustomAssistantSchema,\n BasetenAssistantSchema,\n ]),\n);\n\n/**\n * A validated, normalized assistant entry — a discriminated union on\n * `cloud`, so each topology carries exactly the fields it uses: a baseten\n * entry's `sandboxUrl`/`port` are always defined, a docker entry's\n * `containerInfo` reads without touching unrelated topologies, and absent or\n * unknown on-disk clouds route to the local branch.\n */\nexport type LockfileAssistant = z.infer<typeof LockfileAssistantSchema>;\n\nexport type LocalAssistant = z.infer<typeof LocalAssistantSchema>;\nexport type DockerAssistant = z.infer<typeof DockerAssistantSchema>;\nexport type AppleContainerAssistant = z.infer<\n typeof AppleContainerAssistantSchema\n>;\nexport type CloudAssistant = z.infer<typeof CloudAssistantSchema>;\nexport type CustomAssistant = z.infer<typeof CustomAssistantSchema>;\nexport type BasetenAssistant = z.infer<typeof BasetenAssistantSchema>;\n\n/** Whether the entry is a platform-hosted assistant. */\nexport function isCloudEntry(\n entry: LockfileAssistant,\n): entry is CloudAssistant {\n return entry.cloud === \"cloud\";\n}\n\n/**\n * The stored `machine` policy for an entry, exactly as validated — no default\n * resolution here. Falling back to runtime defaults is the reader's decision\n * (the apply layer pairs this with the spec defaults it owns), so callers that\n * need to distinguish an unset section from a set one can.\n */\nexport function getMachinePolicy(\n entry: LockfileAssistant,\n): MachinePolicy | undefined {\n return entry.machine;\n}\n\n/** Whether the entry is a docker-hosted assistant. */\nexport function isDockerEntry(\n entry: LockfileAssistant,\n): entry is DockerAssistant {\n return entry.cloud === \"docker\";\n}\n\n/** Whether the entry is an apple-container assistant. */\nexport function isAppleContainerEntry(\n entry: LockfileAssistant,\n): entry is AppleContainerAssistant {\n return entry.cloud === \"apple-container\";\n}\n\n/** Whether the entry is a plain on-machine assistant. */\nexport function isLocalEntry(\n entry: LockfileAssistant,\n): entry is LocalAssistant {\n return entry.cloud === \"local\";\n}\n\n/**\n * A CLI-managed OS process recorded at the lockfile root under `tunnel`:\n * the auto-tunnel agent fronting the edge. `cadet ps` renders it alongside\n * the per-assistant process trees, and the stop paths verify the pid before\n * signaling it.\n */\nexport const TunnelProcessRecordSchema = z.object({\n pid: z.number(),\n /** Which tunnel provider owns the process. */\n provider: z.enum(TUNNEL_PROVIDERS),\n startedAt: z.string(),\n /** The public URL the tunnel agent serves. */\n publicUrl: z.string().optional(),\n});\nexport type TunnelProcessRecord = z.infer<typeof TunnelProcessRecordSchema>;\n\n/**\n * A CLI-managed OS process recorded at the lockfile root under `app`: the\n * nginx ingress edge (web artifacts, `/v1` proxying, the outer shell).\n * Shares nothing with the tunnel record beyond the pid bookkeeping — a\n * tunnel serves a URL, the edge serves a port.\n */\nexport const AppProcessRecordSchema = z.object({\n /** The edge process's bookkeeping, present while the edge runs. */\n pid: z.number().optional(),\n startedAt: z.string().optional(),\n /** Listening port for the nginx edge. */\n port: z.number(),\n /**\n * The commit the edge's source-built client bundles were last built from\n * — the auto-build staleness stamp. Rides the running edge's record: a\n * build only stamps when an edge record exists to carry it.\n */\n builtSha: z.string().optional(),\n});\nexport type AppProcessRecord = z.infer<typeof AppProcessRecordSchema>;\n\n/**\n * Renderer-facing result of a lockfile write, returned across the host bridge.\n * Carries no HTTP-style status — hosts map that away because the renderer\n * surfaces failures by message alone.\n */\nexport type LockfileWriteResult =\n | { ok: true; lockfile: Lockfile }\n | { ok: false; error: string };\n\nexport const LockfileSchema = z.looseObject({\n assistants: z\n .array(LockfileAssistantSchema.nullable().catch(null))\n .catch([])\n .transform((entries) =>\n entries.filter((entry): entry is LockfileAssistant => entry !== null),\n ),\n activeAssistant: z.union([z.string(), z.null()]).catch(null),\n tunnel: TunnelProcessRecordSchema.optional().catch(undefined),\n app: AppProcessRecordSchema.optional().catch(undefined),\n});\n\n/**\n * A validated, normalized lockfile — this schema's own output. Loose at the\n * root so the CLI's read-modify-write round-trip preserves the root keys it\n * owns (`cloudBaseUrl`) and anything a newer writer added; readers key on the\n * modeled fields.\n */\nexport type Lockfile = z.infer<typeof LockfileSchema>;\n\n/**\n * Coerce parsed JSON into a validated {@link Lockfile}. Total and non-throwing:\n * the whole file parses through {@link LockfileSchema} — an entry that fails\n * validation is dropped whole (one malformed entry can't discard the file), a\n * missing or non-array `assistants` becomes `[]`, a non-string\n * `activeAssistant` becomes null, and a non-object root becomes an empty\n * lockfile.\n */\nexport function parseLockfile(raw: unknown): Lockfile {\n const parsed = LockfileSchema.safeParse(raw);\n return parsed.success ? parsed.data : LockfileSchema.parse({});\n}\n","import { z } from \"zod\";\n\nimport { RemoteSchema } from \"@mycadet/local-mode/contract\";\n\nfunction publicUrl(value: string): boolean {\n if (/[\\\\\\u0000-\\u0020]/.test(value) || value.startsWith(\"//\")) {\n return false;\n }\n if (\n (!value.startsWith(\"/\") &&\n !/^https?:\\/\\//i.test(value) &&\n value.includes(\":\")) ||\n /(?:^|\\/)(?:\\.|%2e){1,2}(?:\\/|$)/i.test(value)\n ) {\n return false;\n }\n try {\n const url = new URL(value, \"https://app.example\");\n if (url.username || url.password || url.search || url.hash) {\n return false;\n }\n return (\n value.startsWith(\"/\") ||\n url.protocol === \"https:\" ||\n (url.protocol === \"http:\" &&\n [\"localhost\", \"127.0.0.1\", \"[::1]\"].includes(url.hostname))\n );\n } catch {\n return false;\n }\n}\n\nexport const PublicUrlSchema = z\n .string()\n .min(1)\n .refine(\n publicUrl,\n \"Expected a public host URL without credentials or query parameters.\",\n );\nexport const ResolvedAssistantSchema = z.object({\n id: z.string().min(1),\n version: z.string().min(1),\n url: PublicUrlSchema.refine((value) => {\n if (!publicUrl(value)) {\n return false;\n }\n return new URL(value, \"https://app.example\").pathname\n .replace(/\\/$/, \"\")\n .endsWith(\"/assistant\");\n }, \"UI entry must end at /assistant/.\"),\n});\n\nexport const AssistantDetailSchema = ResolvedAssistantSchema.extend({\n status: z.literal(\"active\"),\n});\n\nexport const AssistantChoiceSchema = z.object({\n id: z.string().min(1),\n name: z\n .string()\n .nullish()\n .transform((name) => name?.trim() || \"New Assistant\"),\n /** The cloud's synced avatar thumbnail; null means the cloud holds none. */\n avatar_url: z.string().nullish(),\n /** When the cloud hatched the assistant (ISO 8601); null when unknown. */\n created: z.string().nullish(),\n /**\n * The assistant's own hosting kind, stamped per row by the serving control\n * plane — the cloud lists its assistants \"cloud\", a self-hosted edge\n * stamps its lockfile cloud. Unknown or absent values (servers predating\n * the stamp, a hostile row) fall back to undefined; the chooser then uses\n * the build's own remote.\n */\n remote: RemoteSchema.nullish().catch(() => undefined),\n});\n\nexport const AssistantResolutionSchema = z.discriminatedUnion(\"status\", [\n z.object({ status: z.literal(\"ready\"), assistant: ResolvedAssistantSchema }),\n z.object({ status: z.literal(\"notReady\") }),\n z.object({ status: z.literal(\"unavailable\") }),\n z.object({\n status: z.literal(\"selection\"),\n assistants: z.array(AssistantChoiceSchema),\n }),\n z.object({ status: z.literal(\"empty\") }),\n z.object({ status: z.literal(\"unauthenticated\") }),\n]);\n/**\n * What the list endpoint alone can answer: the choices on the account, or no\n * session to read them with. The chooser renders off this contract only —\n * the single-assistant resolution arms (ready/notReady/unavailable/empty)\n * belong to the shell's resolution flow.\n */\nexport const AssistantListSchema = z.discriminatedUnion(\"status\", [\n z.object({\n status: z.literal(\"selection\"),\n assistants: z.array(AssistantChoiceSchema),\n }),\n z.object({ status: z.literal(\"unauthenticated\") }),\n]);\nexport type ResolvedAssistant = z.infer<typeof ResolvedAssistantSchema>;\nexport type AssistantResolution = z.infer<typeof AssistantResolutionSchema>;\nexport type AssistantList = z.infer<typeof AssistantListSchema>;\nexport const AssistantNavigationSchema = z.object({\n source: z.literal(\"mycadet-web-nav\"),\n nav: z.enum([\"push\", \"replace\", \"pop\"]),\n path: z.string(),\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"resolution-contract-Coknve4C.js","names":[],"sources":["../../../../packages/service-contracts/src/ingress.ts","../../../../packages/environments/src/seeds.ts","../../__vite-browser-external","../../../../packages/environments/src/install-layout.ts","../../../../packages/local-mode/src/lockfile-contract.ts","../../src/assistant/resolution-contract.ts"],"sourcesContent":["/**\n * Map a Cadet Cloud host onto its environment's tunnel host, following the\n * deployment naming convention (`cloud.mycadet.ai` → `tunnel.mycadet.ai`,\n * `{env}-cloud.mycadet.ai` → `{env}-tunnel.mycadet.ai`). Returns null for\n * hosts outside that convention (localhost, custom domains). Shared by the\n * gateway speech relay and the web live-voice client so the two ends of the\n * cloud-speech transport can't drift.\n */\nexport function tunnelHostForCloudHost(host: string): string | null {\n if (host === \"cloud.mycadet.ai\") {\n return \"tunnel.mycadet.ai\";\n }\n const match = /^([a-z0-9-]+)-cloud\\.mycadet\\.ai$/.exec(host);\n return match ? `${match[1]}-tunnel.mycadet.ai` : null;\n}\n\nexport function normalizePublicBaseUrl(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const normalized = value.trim().replace(/\\/+$/, \"\");\n return normalized.length > 0 ? normalized : undefined;\n}\n\n/**\n * Application close code the gateway's tunnel bridge sends to proxied\n * WebSockets when the tunnel itself is lost (tunnel disconnect, gateway\n * shutdown). A dedicated code because the natural 1001 (going away) cannot\n * be sent through the JS `close()` API (the bridge would remap it to a\n * misleading 4001), and Bun's WebSocket client drops close reasons, so the\n * code is the only signal that survives the relay to the daemon.\n */\nexport const GATEWAY_TUNNEL_LOST_WS_CLOSE_CODE = 4801;\n\nexport function normalizeHttpPublicBaseUrl(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const trimmed = value.trim();\n if (trimmed.length === 0) return undefined;\n if (/[?#]/.test(trimmed)) return undefined;\n\n try {\n const url = new URL(trimmed);\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n return undefined;\n }\n if (!url.hostname) return undefined;\n url.pathname = url.pathname.replace(/\\/+$/, \"\") || \"/\";\n return url.toString();\n } catch {\n return undefined;\n }\n}\n\n/**\n * The same validation as `normalizeHttpPublicBaseUrl`, minus the root path it\n * always emits (`https://x` becomes `https://x/`). Callers that append a path\n * or persist the result want this shape, so they neither join through a double\n * slash nor re-strip what the normalizer just added.\n */\nexport function normalizeHttpPublicBaseUrlWithoutTrailingSlash(\n value: unknown,\n): string | undefined {\n return normalizePublicBaseUrl(normalizeHttpPublicBaseUrl(value));\n}\n\n/**\n * A trimmed string, or undefined when the value is not a non-blank string.\n *\n * Exported because the identity check in the daemon's tunnel probe compares an\n * id recorded here against one an edge serves, and the two sides have to trim\n * alike: a padded served id must read as a match, not as a different\n * assistant.\n */\nexport function trimmedNonEmptyString(value: unknown): string | undefined {\n if (typeof value !== \"string\") {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\n// Records `cadet tunnel` leaves in the workspace config's `ingress` section.\n// The CLI writes them and the daemon reads them, and the two are separate\n// build units that cannot import each other, so the key names, the provider\n// allowlist, and the validation all live here rather than in a copy on each\n// side.\n\n/** Key under `ingress` holding the tunnel that most recently ran. */\nexport const INGRESS_LAST_TUNNEL_KEY = \"lastTunnel\";\n\n/**\n * Key under `ingress` holding a tunnel published for device pairing alone.\n *\n * A tailnet-only tunnel started while webhook integrations are configured\n * cannot carry their callbacks, so it leaves `publicBaseUrl` (the callback\n * base) as it is and records itself here. Readers that answer the pairing card\n * prefer this address over `publicBaseUrl`; teardown drops the key.\n */\nexport const INGRESS_PAIRING_TUNNEL_KEY = \"pairingTunnel\";\n\n/** Key under `ingress` holding the assistant id that tunnel fronted. */\nexport const INGRESS_ASSISTANT_ID_KEY = \"assistantId\";\n\n/**\n * The tunnel providers the CLI offers, in the order they are presented —\n * the managed `cadet` tunnel first (listed, not yet implemented), then the\n * local ones. `cadet tunnel`'s accepted `--provider` values and the\n * persisted-record validation below both derive from it.\n */\nexport const TUNNEL_PROVIDERS = [\n \"cadet\",\n \"ngrok\",\n \"cloudflare\",\n \"tailscale\",\n] as const;\n\nexport type TunnelProviderName = (typeof TUNNEL_PROVIDERS)[number];\n\n/** A recorded tunnel, kept after teardown so UIs can name the command to restart. */\nexport interface TunnelRecord {\n provider: TunnelProviderName;\n publicBaseUrl: string;\n}\n\nfunction isTunnelProviderName(value: unknown): value is TunnelProviderName {\n return (\n typeof value === \"string\" &&\n (TUNNEL_PROVIDERS as readonly string[]).includes(value)\n );\n}\n\n/**\n * Parse an `ingress.lastTunnel` or `ingress.pairingTunnel` value, or null when\n * it is absent or malformed.\n *\n * A hand-edited config must not hand callers an unusable address or a provider\n * name no command accepts: the provider is checked against the allowlist\n * because readers render it into a restart command, and the URL faces the same\n * absolute HTTP(S) constraint as every other public base URL. The URL comes\n * back in the shape the validator produced, so readers never re-normalize it.\n */\nexport function parseTunnelRecord(value: unknown): TunnelRecord | null {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return null;\n }\n const { provider, publicBaseUrl } = value as Record<string, unknown>;\n if (!isTunnelProviderName(provider)) {\n return null;\n }\n const normalized =\n normalizeHttpPublicBaseUrlWithoutTrailingSlash(publicBaseUrl);\n if (normalized === undefined) {\n return null;\n }\n return { provider, publicBaseUrl: normalized };\n}\n\n/**\n * The tunnel a reader should offer to restart, given an `ingress` section, or\n * null when it records none.\n *\n * A pairing-only record wins: it is written by a tunnel that deliberately left\n * `publicBaseUrl` (and the `lastTunnel` beside it) pointing at an older public\n * provider, so naming that older one would send the user to rebuild an ingress\n * they are not using instead of the tunnel actually in front of them.\n */\nexport function selectRestartTunnelRecord(\n ingress: unknown,\n): TunnelRecord | null {\n if (typeof ingress !== \"object\" || ingress === null) {\n return null;\n }\n const section = ingress as Record<string, unknown>;\n return (\n parseTunnelRecord(section[INGRESS_PAIRING_TUNNEL_KEY]) ??\n parseTunnelRecord(section[INGRESS_LAST_TUNNEL_KEY])\n );\n}\n\n/** Parse an `ingress.assistantId` value, or null when it is absent or blank. */\nexport function parseRecordedAssistantId(value: unknown): string | null {\n return trimmedNonEmptyString(value) ?? null;\n}\n","// Loaded standalone by Capacitor's single-file TS config loader — keep\n// imports type-only and keep this file free of the import-meta meta-property:\n// the loader transpiles this file to CJS, where that syntax is illegal and\n// makes Node misroute the result as ESM.\nimport type { EnvironmentDefinition, PortMap } from \"./types.js\";\n\n/**\n * Non-prod port blocks. Each environment gets a 1000-port window in the\n * 17000–21000 band. Within a block, services are spaced 100 apart so up to\n * 100 assistants can coexist without the scan (`findAvailablePort`) running\n * one service's range into the next. Band chosen to sit below Linux's\n * default ephemeral start (32768) and macOS's (49152), and away from the\n * 3000/5000/8000/9000 dev-tool swamp. Production keeps its legacy,\n * non-contiguous port set (7821/7830/6333/8090/8080/8765): cross-env\n * collision is the only problem this change targets, prod is unaffected\n * because only one env's assistants compete on a given machine, and\n * churning it would leave existing hatches on 7821 while new ones\n * allocated elsewhere.\n */\nfunction portBlock(base: number): PortMap {\n return {\n daemon: base,\n gateway: base + 100,\n qdrant: base + 200,\n ces: base + 300,\n outboundProxy: base + 400,\n tcp: base + 500,\n };\n}\n\n/** Web origin for the current environment (CADET_ENVIRONMENT or VITE_ENVIRONMENT; unknown falls back to dev). */\nexport function cloudWebUrl(): string {\n // globalThis.process reads the same env under bun/node while staying\n // crash-free in browser bundles (no bare `process` reference) — and free\n // of import-meta syntax, which the Capacitor config loader can't transpile.\n const env = (\n globalThis as unknown as {\n process?: { env?: Record<string, string | undefined> };\n }\n ).process?.env;\n const envName = env?.CADET_ENVIRONMENT ?? env?.VITE_ENVIRONMENT;\n const seed = (envName && SEEDS[envName]) || SEEDS.dev;\n return seed.webUrl;\n}\n\n/** The cloud assistant hub — booting at the bare host bounces non-prod shells. */\nexport function cloudAssistantHubUrl(): string {\n return `${cloudWebUrl()}/assistant`;\n}\n\n/**\n * Built-in environment definitions and the source of truth for the\n * set of known environment names.\n *\n * Custom environments via a user config file are a future phase — see the\n * \"Coexisting environments\" design doc. Until then, a call site that needs a\n * new environment must add it here and rebuild.\n */\nexport const SEEDS: Record<string, EnvironmentDefinition> = {\n production: {\n name: \"production\",\n cloudUrl: \"https://cloud.mycadet.ai\",\n webUrl: \"https://mycadet.ai\",\n },\n staging: {\n name: \"staging\",\n cloudUrl: \"https://staging-cloud.mycadet.ai\",\n webUrl: \"https://staging.mycadet.ai\",\n portsOverride: portBlock(17000),\n },\n test: {\n name: \"test\",\n // Non-functional URL — used only by unit tests for URL resolution, never\n // hit in production.\n cloudUrl: \"https://test-cloud.mycadet.ai\",\n webUrl: \"https://dev.mycadet.ai\",\n portsOverride: portBlock(19000),\n },\n dev: {\n name: \"dev\",\n cloudUrl: \"https://dev-cloud.mycadet.ai\",\n webUrl: \"https://dev.mycadet.ai\",\n portsOverride: portBlock(18000),\n },\n local: {\n name: \"local\",\n cloudUrl: \"http://localhost:8000\",\n webUrl: \"http://localhost:3000\",\n // assistantCloudUrl: \"http://host.docker.internal:8000\",\n // ^ uncomment this once dockerized hatch path is live.\n // The assistant runs in a different network namespace than the host.\n portsOverride: portBlock(20000),\n },\n};\n","module.exports = {}","/**\n * Reading the layout a Cadet runtime is installed in.\n *\n * The daemon is started either from a developer's repo checkout or from an\n * installed npm package (`<install>/node_modules/@mycadet/assistant/src/\n * index.ts`, which is what the desktop app runs). The CLI needs the layout to\n * decide whether a launch is a dev run and what to put on the daemon's PATH;\n * the daemon needs it to decide what to install as the `assistant` command.\n * Both ask the same two questions, so they ask them here.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { join, sep } from \"node:path\";\n\nconst NODE_MODULES_SEGMENT = `${sep}node_modules${sep}`;\n\n/**\n * Whether `path` sits in a developer's repo checkout rather than inside an\n * installed package.\n */\nexport function isRepoCheckoutPath(path: string): boolean {\n return !path.includes(NODE_MODULES_SEGMENT);\n}\n\n/**\n * The `assistant` command shipped with the install that contains `path`, or\n * null when there is none (a repo checkout, or an install whose dependency\n * graph omits the package declaring the bin).\n *\n * Searches innermost `node_modules` outward, since a nested install shadows a\n * hoisted one.\n */\nexport function findAssistantCommand(path: string): string | null {\n let index = path.lastIndexOf(NODE_MODULES_SEGMENT);\n\n while (index !== -1) {\n const nodeModulesDir = path.slice(\n 0,\n index + NODE_MODULES_SEGMENT.length - 1,\n );\n const command = join(nodeModulesDir, \".bin\", \"assistant\");\n if (existsSync(command)) {\n return command;\n }\n index = path.lastIndexOf(NODE_MODULES_SEGMENT, index - 1);\n }\n\n return null;\n}\n","/**\n * The lockfile wire contract — one Zod schema that is the single source of truth\n * for an assistant entry's shape, its validation, the `cloud` taxonomy, the\n * normalization, and the redaction policy.\n *\n * Kept in its own module, exported from the package as `./contract`, so\n * consumers import the contract without pulling in the host I/O graph\n * (`node:fs`, the CLI resolver). The CLI, the host I/O layer, the Electron and\n * dev-server bridges, and the renderer all depend on this one definition. It\n * references no node built-ins — the renderer bundles it directly — and `zod`\n * is its only third-party import.\n *\n * The lockfile is written by the Cadet CLI and read across the macOS↔CLI\n * boundary, which do not release in lockstep, so the parser is permissive at\n * the file level: it validates each entry whole through the schema (a\n * malformed field drops that entry, never the file), accepts — but drops from\n * the validated value — entry fields this build has never heard of, and\n * defaults `cloud` when it is absent or unknown. The schema is strict over\n * the fields it models: every field a reader needs is IN the schema, so a\n * CLI read-modify-write round-trip preserves it by construction. The\n * local-mode write path still merges raw JSON (unknown root fields survive\n * on disk; see `lockfile.ts`); the validated value carries only the modeled\n * shape.\n */\nimport { TUNNEL_PROVIDERS } from \"@mycadet/service-contracts/ingress\";\nimport { SEEDS } from \"@mycadet/environments\";\nimport { z } from \"zod\";\n\nimport { resolveEnvironmentName } from \"./environment\";\nimport { readLockfile } from \"./lockfile\";\n\n/**\n * Known deployment topologies of a Cadet:\n * - `local` — on-machine bare-metal processes\n * - `docker` — docker-managed container\n * - `apple-container` — macOS-app-managed container\n * - `cloud` — managed by Cadet Cloud\n * - `custom` — remote VM accessible via SSH\n */\nexport const KNOWN_REMOTES = [\n \"local\",\n \"docker\",\n \"apple-container\",\n \"cloud\",\n \"custom\",\n \"baseten\",\n] as const;\n\n/**\n * The topology values as one zod enum, shared by the lockfile schema (which\n * wraps it in a `catch`) and the clients' injected-config schema, so the two\n * can never drift.\n */\nexport const RemoteSchema = z.enum(KNOWN_REMOTES);\nexport type Remote = z.infer<typeof RemoteSchema>;\n\n/**\n * Keys that must never cross the host→renderer boundary. Stripped from raw\n * lockfile JSON before it leaves a host (see `stripSensitiveFields`) and absent\n * from the renderer-facing schema below; the CLI's extended type is the only\n * place they are modeled.\n */\nexport const SENSITIVE_KEYS = [\n \"bearerToken\",\n \"guardianBootstrapSecret\",\n] as const;\n\n/**\n * The runtime config a shell injects (`window.__CADET_CONFIG__`). This schema\n * is the single shape: hosts stamp it, served bundles parse the window\n * through it instead of re-declaring per-file object types.\n *\n * The URL fields are distinct origins in the topology, not synonyms:\n * `cloudUrl` is the cloud API base and `hubUrl` the cloud hub SPA the\n * pairing/settings pages link back to. The cloud login base is\n * deliberately absent: the loopback handoff reads it from the build's\n * environment seed (see startLoopbackAuth), keeping this config to the\n * origins a served bundle can't infer.\n */\nexport const InjectedConfigSchema = z.object({\n /** Nothing injected (SSR) or an unparseable config means the local cadet. */\n remote: RemoteSchema.catch(\"local\"),\n /** Cloud API base (auth endpoints + assistant API). */\n cloudUrl: z.string().optional(),\n /** Device id stamped by the Electron host (device management). */\n deviceId: z.string().optional(),\n /** Version of the shell bundle serving this page (stamped by the host). */\n version: z.string().optional(),\n /** Serving assistant's id, stamped by every host that fronts one assistant. */\n assistantId: z.string().optional(),\n /** Serving assistant's display name, stamped by every host that fronts one assistant. */\n assistantName: z.string().optional(),\n /** Cloud web SPA base for this build's environment (remote-web ingress configs only). */\n hubUrl: z.string().optional(),\n});\n\nexport type InjectedConfig = z.infer<typeof InjectedConfigSchema>;\n\n/**\n * Strip every `SENSITIVE_KEYS` field from a parsed lockfile, in place: from\n * each assistant entry and from that entry's `resources`. The one redaction\n * every host runs before lockfile data crosses to the renderer — pure, no\n * node built-ins, so it lives beside the keys it enforces and bundles into\n * the renderer through the `./contract` subpath.\n */\nexport function stripSensitiveFields(data: Record<string, unknown>): void {\n const assistants = data.assistants;\n if (!Array.isArray(assistants)) return;\n for (const assistant of assistants) {\n if (assistant && typeof assistant === \"object\") {\n const entry = assistant as Record<string, unknown>;\n for (const field of SENSITIVE_KEYS) {\n delete entry[field];\n }\n const resources = entry.resources;\n if (resources && typeof resources === \"object\") {\n for (const field of SENSITIVE_KEYS) {\n delete (resources as Record<string, unknown>)[field];\n }\n }\n }\n }\n}\n\n/**\n * Whether an entry's `runtimeUrl` is one a serving host can forward to: an\n * absolute http(s) URL. Shared by the renderer's paired-gateway URL derivation\n * and the host-side paired allowlist reader so the two can't drift.\n */\nexport function isUsableBaseUrl(url: unknown): url is string {\n if (typeof url !== \"string\" || url === \"\") {\n return false;\n }\n try {\n const parsed = new URL(url);\n return parsed.protocol === \"http:\" || parsed.protocol === \"https:\";\n } catch {\n return false;\n }\n}\n\n/**\n * The local build's control-plane base, read from the lockfile alone: the\n * auto-tunnel's recorded `publicUrl` when one is up, else the app server\n * edge's loopback port. A loopback base is reachable from the iOS simulator\n * only — a device's `127.0.0.1` is the phone itself — so a device build\n * wants a tunnel up before it stamps. `undefined` when neither record\n * carries a usable address.\n *\n * The assistants play no role in this resolution — the capacitor app shell\n * only deals with the app server — and the CLI's workspace config is never\n * touched: the lockfile is the single home for edge state.\n */\nexport function resolveLocalServerUrl(): string | undefined {\n const lockfile = readLockfile();\n const tunnelUrl = lockfile.tunnel?.publicUrl;\n if (isUsableBaseUrl(tunnelUrl)) {\n return tunnelUrl;\n }\n const port = lockfile.app?.port;\n if (typeof port === \"number\") {\n return `http://127.0.0.1:${port}`;\n }\n return undefined;\n}\n\n/**\n * The id-keyed, same-origin gateway proxy base for any assistant entry: the\n * serving host (Electron `app://` handler, Vite dev middleware, CLI web\n * server) resolves the id through the lockfile to a loopback port (locally\n * hatched) or a recorded remote `runtimeUrl` (imported pairing), so the\n * renderer never encodes either.\n */\n/**\n * First segments whose scoped `/v1/assistants/{id}/` prefix the gateway does\n * not serve: the contact family lives on flat `/v1/...` control-plane routes\n * (the shape the cloud's runtime-proxy strip delivers). Every\n * forwarder to the gateway — the scoped host proxy and the request\n * interceptor's remote-ingress rewrite alike — flattens them to match.\n */\nexport const FLATTENED_FIRST_SEGMENTS = new Set<string>([\n \"contacts\",\n \"contact-channels\",\n]);\n\n/**\n * Clouds whose gateway listens on a loopback port of this machine: the\n * on-machine daemon (\"local\") and local Docker instances (\"docker\").\n */\nexport const LOOPBACK_GATEWAY_CLOUDS: readonly Remote[] = [\"local\", \"docker\"];\n\nexport function isLoopbackGatewayCloud(cloud: Remote): boolean {\n return LOOPBACK_GATEWAY_CLOUDS.includes(cloud);\n}\n\n/**\n * The gateway port a freshly hatched assistant's gateway listens on, with\n * the `GATEWAY_PORT` env override the CLI tooling honors.\n */\nexport const GATEWAY_PORT = process.env.GATEWAY_PORT\n ? Number(process.env.GATEWAY_PORT)\n : 7830;\n\n/**\n * The cloud URL stack: the lockfile's platform base url (what the active\n * assistant was last hatched/waked against), else the `CADET_CLOUD_URL`\n * override, else the resolved environment's cloud url.\n */\nfunction getCloudUrl(): string {\n const lockfileUrl = readLockfile().cloudBaseUrl;\n return (\n (typeof lockfileUrl === \"string\" && lockfileUrl.trim()) ||\n process.env.CADET_CLOUD_URL?.trim() ||\n SEEDS[resolveEnvironmentName()]?.cloudUrl ||\n SEEDS.production!.cloudUrl\n );\n}\n\n/**\n * The URL to dial an entry's gateway on, derived from the entry's canonical\n * fields — the one seam every consumer addresses a gateway through:\n *\n * - `custom` remotes: their recorded host and port.\n * - cloud-managed: the cloud URL stack.\n * - loopback topologies (`local`, `docker`, `apple-container`): the\n * recorded loopback gateway port, else the default gateway port.\n */\nexport function getGatewayUrl(entry: LockfileAssistant): string {\n // Baseten: the gateway rides the sandbox API server's `/port/<PORT>/` route.\n if (entry.cloud === \"baseten\") {\n return `${entry.sandboxUrl.replace(/\\/+$/, \"\")}/port/${entry.port}`;\n }\n if (entry.cloud === \"custom\") {\n // Custom remotes speak TLS. Default ports elided the way a URL prints\n // them. The schema requires the address and port.\n const elided = entry.port === 443;\n return `https://${entry.ipAddress}${elided ? \"\" : `:${entry.port}`}`;\n }\n if (isCloudEntry(entry)) {\n return getCloudUrl().replace(/\\/+$/, \"\");\n }\n return `http://127.0.0.1:${entry.port}`;\n}\n\n/** Docker image metadata for the service group. Enables rollback to known-good refs. */\nexport const ContainerInfoSchema = z.object({\n assistantImage: z.string(),\n gatewayImage: z.string(),\n cesImage: z.string(),\n});\nexport type ContainerInfo = z.infer<typeof ContainerInfoSchema>;\n\n/**\n * Host-independent resource policy for one assistant's machine allocation —\n * what the allocation *should be*, as opposed to the record fields (ports,\n * instanceDir, containerInfo) that describe what *is*. The section rides\n * every topology's entry: the stored shape is the same everywhere, while how\n * a policy materializes differs per remote (see {@link MACHINE_APPLICABILITY}).\n *\n * The schema is strict — unknown keys are rejected — because the write path\n * (future `cadet machine set`, daemon PATCH, cloud API) is the loud validator:\n * a typo'd knob must fail at the boundary, never silently degrade to the\n * runtime default. Parse time is deliberately softer: the field is embedded\n * with `.catch(undefined)`, so a malformed or unrecognized section from a\n * hand-edit or a different build quarantines to the defaults while the\n * entry itself survives, and the drift is visible to readers instead of\n * costing the assistant its identity fields.\n */\nexport const MachineMemoryPolicySchema = z\n .strictObject({\n /**\n * Soft limit in bytes: where the runtime should start reclaiming early\n * and smoothly (cgroup v2 `memory.high` on docker remotes). Absent =\n * unconstrained.\n */\n high: z.number().int().positive().optional(),\n /** Hard limit in bytes: the backstop the runtime may not exceed\n * (cgroup v2 `memory.max` on docker remotes). Absent = unconstrained. */\n max: z.number().int().positive().optional(),\n })\n .superRefine((memory, context) => {\n if (\n memory.high !== undefined &&\n memory.max !== undefined &&\n memory.high > memory.max\n ) {\n context.addIssue({\n code: \"custom\",\n path: [\"high\"],\n message: `memory.high (${memory.high}) must not exceed memory.max (${memory.max})`,\n });\n }\n });\nexport type MachineMemoryPolicy = z.infer<typeof MachineMemoryPolicySchema>;\n\n/** The `machine` section: one key per resource knob, added as they migrate here. */\nexport const MachinePolicySchema = z.strictObject({\n memory: MachineMemoryPolicySchema.optional(),\n});\nexport type MachinePolicy = z.infer<typeof MachinePolicySchema>;\n\n/**\n * How a stored `machine` policy materializes per remote topology. The store\n * is host-independent — this table is the one place that says what a policy\n * *means* on each remote, so the apply layer and readers derive semantics\n * from it instead of re-deciding per call site. Remotes gain real\n * applicability as their apply paths land (cloud/gVisor, apple-container).\n */\nexport type MachineApplicability = \"cgroup\" | \"process-hint\" | \"unsupported\";\n\nexport const MACHINE_APPLICABILITY = {\n local: \"process-hint\",\n docker: \"cgroup\",\n \"apple-container\": \"unsupported\",\n cloud: \"unsupported\",\n custom: \"unsupported\",\n baseten: \"unsupported\",\n} as const satisfies Record<Remote, MachineApplicability>;\n\n/**\n * The fields every topology actually uses: the identity every reader looks\n * entries up by, the display name, and the hatch/onboarding/conversation\n * bookkeeping that is topology-agnostic.\n */\nconst BaseAssistantSchema = z.object({\n assistantId: z.string(),\n /** Platform assistant UUID for this assistant's registration. */\n cloudAssistantId: z.string().optional(),\n /** Platform base URL this assistant was registered against. */\n cloudBaseUrl: z.string().optional(),\n /** Platform organization the entry belongs to. */\n cloudOrganizationId: z.string().optional(),\n name: z.string().optional(),\n hatchedAt: z.string().optional(),\n /**\n * When this assistant finished first-run onboarding. Absent for entries that\n * predate the record, which fall back to hatch age (see the web client's\n * `onboarded-assistant.ts`).\n */\n onboardedAt: z.string().optional(),\n /** PID of the file watcher process for instances hatched with --watch. */\n watcherPid: z.number().optional(),\n /**\n * Host-independent resource policy for this assistant's machine allocation\n * (see {@link MachinePolicySchema} for the strict-at-write, fallback-at-parse\n * contract, and {@link MACHINE_APPLICABILITY} for how it materializes per\n * remote). Absent = the runtime's own defaults apply.\n */\n machine: MachinePolicySchema.optional().catch(undefined),\n});\n\n/**\n * The fields the self-hosted, on-machine topologies (local, docker,\n * apple-container) share: host-recorded ports and instance dir.\n */\nconst LocalRuntimeAssistantFields = {\n /** The gateway port a freshly hatched assistant's gateway listens on. */\n port: z.number(),\n /** The runtime daemon's port, recorded at hatch. */\n daemonPort: z.number().optional(),\n /** The instance's state root on this machine. Local instances keep their\n * data here; docker instances record their host-side volume root. */\n instanceDir: z.string().optional(),\n /** The content-editor server's port, recorded at hatch. */\n cesPort: z.number().optional(),\n /** The runtime build version this instance runs. */\n runtimeVersion: z.string().optional(),\n /** Where the runtime build is installed on this machine. */\n runtimeInstallDir: z.string().optional(),\n};\n\n/** A plain on-machine assistant: host-managed ports and instance dir. */\nconst LocalAssistantSchema = BaseAssistantSchema.extend({\n ...LocalRuntimeAssistantFields,\n cloud: z.literal(\"local\"),\n});\n\n/** A docker-hosted assistant: container metadata for rollback/upgrade. */\nconst DockerAssistantSchema = BaseAssistantSchema.extend({\n ...LocalRuntimeAssistantFields,\n cloud: z.literal(\"docker\"),\n /** Docker image metadata for rollback. Only present for docker topology entries. */\n containerInfo: ContainerInfoSchema.optional(),\n});\n\n/** An apple-container assistant, driven through its management socket. */\nconst AppleContainerAssistantSchema = BaseAssistantSchema.extend({\n ...LocalRuntimeAssistantFields,\n cloud: z.literal(\"apple-container\"),\n /** Management socket path for apple-container assistants; written by the macOS host. */\n mgmtSocket: z.string(),\n});\n\n/** A platform-hosted assistant, registered against the cloud API. */\nconst CloudAssistantSchema = BaseAssistantSchema.extend({\n cloud: z.literal(\"cloud\"),\n});\n\n/** A self-hosted remote assistant, reached at its own address. */\nconst CustomAssistantSchema = BaseAssistantSchema.extend({\n cloud: z.literal(\"custom\"),\n /** Host address of the custom remote's gateway. */\n ipAddress: z.string(),\n /** Gateway port on the custom remote. */\n port: z.number(),\n});\n\n/**\n * A Baseten sandbox entry: the sandbox server URL and the sandbox-internal\n * gateway port are pinned at creation, so every reader can address\n * `{sandboxUrl}/port/{port}` without a fallback.\n */\nconst BasetenAssistantSchema = BaseAssistantSchema.extend({\n cloud: z.literal(\"baseten\"),\n /** The Baseten sandbox server URL; the gateway rides `{sandboxUrl}/port/{port}`. */\n sandboxUrl: z.string(),\n /** The sandbox-internal gateway port, piped in as `GATEWAY_PORT` at creation. */\n port: z.number(),\n});\n\n/**\n * Route absent and unknown topology values to the local default before the\n * union discriminates, keeping the on-disk contract permissive about clouds\n * this build has never heard of.\n */\n/**\n * The one preprocess the entry union parses through. Routes absent and\n * unknown topology values to the local default before the union\n * discriminates, and backfills the legacy `resources.gatewayPort` onto\n * `port` — but only when the flat `port` field would fail the number\n * schema (absent or not a number): a valid port always wins. Zod has no\n * field-level sibling fallback, so the object-level preprocess is the\n * supported surface for this.\n */\nfunction normalizeEntry(entry: unknown): unknown {\n if (typeof entry !== \"object\" || entry === null) return entry;\n let rec = entry as Record<string, unknown>;\n const cloud = rec.cloud;\n if (!(typeof cloud === \"string\" && RemoteSchema.safeParse(cloud).success)) {\n rec = { ...rec, cloud: \"local\" };\n }\n if (rec.port === undefined) {\n // The flat port failed the required schema (absent on a legacy or bare\n // entry): fall back to the legacy resources.gatewayPort, else the\n // gateway default. A mistyped port never falls back — a bad modeled\n // field still drops the entry.\n const resources = rec.resources;\n const gatewayPort =\n resources && typeof resources === \"object\"\n ? (resources as Record<string, unknown>).gatewayPort\n : undefined;\n rec = {\n ...rec,\n port: typeof gatewayPort === \"number\" ? gatewayPort : GATEWAY_PORT,\n };\n }\n return rec;\n}\n\nexport const LockfileAssistantSchema = z.preprocess(\n normalizeEntry,\n z.discriminatedUnion(\"cloud\", [\n LocalAssistantSchema,\n DockerAssistantSchema,\n AppleContainerAssistantSchema,\n CloudAssistantSchema,\n CustomAssistantSchema,\n BasetenAssistantSchema,\n ]),\n);\n\n/**\n * A validated, normalized assistant entry — a discriminated union on\n * `cloud`, so each topology carries exactly the fields it uses: a baseten\n * entry's `sandboxUrl`/`port` are always defined, a docker entry's\n * `containerInfo` reads without touching unrelated topologies, and absent or\n * unknown on-disk clouds route to the local branch.\n */\nexport type LockfileAssistant = z.infer<typeof LockfileAssistantSchema>;\n\nexport type LocalAssistant = z.infer<typeof LocalAssistantSchema>;\nexport type DockerAssistant = z.infer<typeof DockerAssistantSchema>;\nexport type AppleContainerAssistant = z.infer<\n typeof AppleContainerAssistantSchema\n>;\nexport type CloudAssistant = z.infer<typeof CloudAssistantSchema>;\nexport type CustomAssistant = z.infer<typeof CustomAssistantSchema>;\nexport type BasetenAssistant = z.infer<typeof BasetenAssistantSchema>;\n\n/** Whether the entry is a platform-hosted assistant. */\nexport function isCloudEntry(\n entry: LockfileAssistant,\n): entry is CloudAssistant {\n return entry.cloud === \"cloud\";\n}\n\n/** Whether the entry is a docker-hosted assistant. */\nexport function isDockerEntry(\n entry: LockfileAssistant,\n): entry is DockerAssistant {\n return entry.cloud === \"docker\";\n}\n\n/** Whether the entry is an apple-container assistant. */\nexport function isAppleContainerEntry(\n entry: LockfileAssistant,\n): entry is AppleContainerAssistant {\n return entry.cloud === \"apple-container\";\n}\n\n/** Whether the entry is a plain on-machine assistant. */\nexport function isLocalEntry(\n entry: LockfileAssistant,\n): entry is LocalAssistant {\n return entry.cloud === \"local\";\n}\n\n/**\n * A CLI-managed OS process recorded at the lockfile root under `tunnel`:\n * the auto-tunnel agent fronting the edge. `cadet ps` renders it alongside\n * the per-assistant process trees, and the stop paths verify the pid before\n * signaling it.\n */\nexport const TunnelProcessRecordSchema = z.object({\n pid: z.number(),\n /** Which tunnel provider owns the process. */\n provider: z.enum(TUNNEL_PROVIDERS),\n startedAt: z.string(),\n /** The public URL the tunnel agent serves. */\n publicUrl: z.string().optional(),\n});\nexport type TunnelProcessRecord = z.infer<typeof TunnelProcessRecordSchema>;\n\n/**\n * A CLI-managed OS process recorded at the lockfile root under `app`: the\n * nginx ingress edge (web artifacts, `/v1` proxying, the outer shell).\n * Shares nothing with the tunnel record beyond the pid bookkeeping — a\n * tunnel serves a URL, the edge serves a port.\n */\nexport const AppProcessRecordSchema = z.object({\n /** The edge process's bookkeeping, present while the edge runs. */\n pid: z.number().optional(),\n startedAt: z.string().optional(),\n /** Listening port for the nginx edge. */\n port: z.number(),\n /**\n * The commit the edge's source-built client bundles were last built from\n * — the auto-build staleness stamp. Rides the running edge's record: a\n * build only stamps when an edge record exists to carry it.\n */\n builtSha: z.string().optional(),\n});\nexport type AppProcessRecord = z.infer<typeof AppProcessRecordSchema>;\n\n/**\n * Renderer-facing result of a lockfile write, returned across the host bridge.\n * Carries no HTTP-style status — hosts map that away because the renderer\n * surfaces failures by message alone.\n */\nexport type LockfileWriteResult =\n | { ok: true; lockfile: Lockfile }\n | { ok: false; error: string };\n\nexport const LockfileSchema = z.looseObject({\n assistants: z\n .array(LockfileAssistantSchema.nullable().catch(null))\n .catch([])\n .transform((entries) =>\n entries.filter((entry): entry is LockfileAssistant => entry !== null),\n ),\n activeAssistant: z.union([z.string(), z.null()]).catch(null),\n tunnel: TunnelProcessRecordSchema.optional().catch(undefined),\n app: AppProcessRecordSchema.optional().catch(undefined),\n});\n\n/**\n * A validated, normalized lockfile — this schema's own output. Loose at the\n * root so the CLI's read-modify-write round-trip preserves the root keys it\n * owns (`cloudBaseUrl`) and anything a newer writer added; readers key on the\n * modeled fields.\n */\nexport type Lockfile = z.infer<typeof LockfileSchema>;\n\n/**\n * Coerce parsed JSON into a validated {@link Lockfile}. Total and non-throwing:\n * the whole file parses through {@link LockfileSchema} — an entry that fails\n * validation is dropped whole (one malformed entry can't discard the file), a\n * missing or non-array `assistants` becomes `[]`, a non-string\n * `activeAssistant` becomes null, and a non-object root becomes an empty\n * lockfile.\n */\nexport function parseLockfile(raw: unknown): Lockfile {\n const parsed = LockfileSchema.safeParse(raw);\n return parsed.success ? parsed.data : LockfileSchema.parse({});\n}\n","import { z } from \"zod\";\n\nimport { RemoteSchema } from \"@mycadet/local-mode/contract\";\n\nfunction publicUrl(value: string): boolean {\n if (/[\\\\\\u0000-\\u0020]/.test(value) || value.startsWith(\"//\")) {\n return false;\n }\n if (\n (!value.startsWith(\"/\") &&\n !/^https?:\\/\\//i.test(value) &&\n value.includes(\":\")) ||\n /(?:^|\\/)(?:\\.|%2e){1,2}(?:\\/|$)/i.test(value)\n ) {\n return false;\n }\n try {\n const url = new URL(value, \"https://app.example\");\n if (url.username || url.password || url.search || url.hash) {\n return false;\n }\n return (\n value.startsWith(\"/\") ||\n url.protocol === \"https:\" ||\n (url.protocol === \"http:\" &&\n [\"localhost\", \"127.0.0.1\", \"[::1]\"].includes(url.hostname))\n );\n } catch {\n return false;\n }\n}\n\nexport const PublicUrlSchema = z\n .string()\n .min(1)\n .refine(\n publicUrl,\n \"Expected a public host URL without credentials or query parameters.\",\n );\nexport const ResolvedAssistantSchema = z.object({\n id: z.string().min(1),\n version: z.string().min(1),\n url: PublicUrlSchema.refine((value) => {\n if (!publicUrl(value)) {\n return false;\n }\n return new URL(value, \"https://app.example\").pathname\n .replace(/\\/$/, \"\")\n .endsWith(\"/assistant\");\n }, \"UI entry must end at /assistant/.\"),\n});\n\nexport const AssistantDetailSchema = ResolvedAssistantSchema.extend({\n status: z.literal(\"active\"),\n});\n\nexport const AssistantChoiceSchema = z.object({\n id: z.string().min(1),\n name: z\n .string()\n .nullish()\n .transform((name) => name?.trim() || \"New Assistant\"),\n /** The cloud's synced avatar thumbnail; null means the cloud holds none. */\n avatar_url: z.string().nullish(),\n /** When the cloud hatched the assistant (ISO 8601); null when unknown. */\n created: z.string().nullish(),\n /**\n * The assistant's own hosting kind, stamped per row by the serving control\n * plane — the cloud lists its assistants \"cloud\", a self-hosted edge\n * stamps its lockfile cloud. Unknown or absent values (servers predating\n * the stamp, a hostile row) fall back to undefined; the chooser then uses\n * the build's own remote.\n */\n remote: RemoteSchema.nullish().catch(() => undefined),\n});\n\nexport const AssistantResolutionSchema = z.discriminatedUnion(\"status\", [\n z.object({ status: z.literal(\"ready\"), assistant: ResolvedAssistantSchema }),\n z.object({ status: z.literal(\"notReady\") }),\n z.object({ status: z.literal(\"unavailable\") }),\n z.object({\n status: z.literal(\"selection\"),\n assistants: z.array(AssistantChoiceSchema),\n }),\n z.object({ status: z.literal(\"empty\") }),\n z.object({ status: z.literal(\"unauthenticated\") }),\n]);\n/**\n * What the list endpoint alone can answer: the choices on the account, or no\n * session to read them with. The chooser renders off this contract only —\n * the single-assistant resolution arms (ready/notReady/unavailable/empty)\n * belong to the shell's resolution flow.\n */\nexport const AssistantListSchema = z.discriminatedUnion(\"status\", [\n z.object({\n status: z.literal(\"selection\"),\n assistants: z.array(AssistantChoiceSchema),\n }),\n z.object({ status: z.literal(\"unauthenticated\") }),\n]);\nexport type ResolvedAssistant = z.infer<typeof ResolvedAssistantSchema>;\nexport type AssistantResolution = z.infer<typeof AssistantResolutionSchema>;\nexport type AssistantList = z.infer<typeof AssistantListSchema>;\nexport const AssistantNavigationSchema = z.object({\n source: z.literal(\"mycadet-web-nav\"),\n nav: z.enum([\"push\", \"replace\", \"pop\"]),\n path: z.string(),\n});\n"],"mappings":"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"}
|