/** * The spawned-agent env (P3) - the single chokepoint for what a child process sees. * * Connectors build the child's env as `{ ...launchEnv(...), , }` * and runtimes pass only that env. The default is a fixed OS allow-list, the machine-wide * {@link OPERATOR_ENV_KEEP} knobs, connector-declared provider keys, and `mcpKeys` — never the * manager's ambient environment. This is both a capability and a session-identity boundary: * sockets, temporary credentials, and host markers such as `CLAUDE_CODE_CHILD_SESSION` must not * become properties of every seat merely because an ancestor ran `cotal up` inside a host. * An operator deliberately expands the boundary only through `spawn.env`. * * Scope this is HONEST about (P6). This does not close filesystem secret access: HOME / XDG / * platform config dirs are forwarded, so a child with a shell reads ~/.aws, ~/.ssh, ~/.config and * ~/.cotal straight off disk. An allow-list stops env-ONLY secrets - an `aws-vault exec` or * `op run` shell, CI-injected values - and nothing that has a file behind it. Nor does it close * model-key exfil: a key-based agent holds its provider key in its own process in order to do * inference. Cotal's own connection material is not in the environment at all: {@link materialEnv} * moved the credential, the broker address and the control token behind a 0600 file. */ import { type McpServerSpec } from "@cotal-ai/core"; /** Model-provider API keys a key-based connector may forward to its child. Other connectors extend * this list locally when their supported provider surface is broader. */ export declare const MODEL_PROVIDER_KEYS: readonly ["OPENCODE_API_KEY", "ANTHROPIC_API_KEY", "OPENAI_API_KEY", "OPENROUTER_API_KEY", "NOUS_API_KEY"]; /** The `COTAL_*` an operator sets MACHINE-WIDE, which a child legitimately needs. The qualifying * property is not "harmless" but "no connector assigns it per spawn": a name no launch path writes * cannot carry one agent's grant into another, which is why this list stays correct as connectors * are added and a deny-list of per-session names would not. `COTAL_HOME` is the load-bearing entry * - it redirects the mesh registry, and a child that shells out to `cotal` must resolve the one * its parent did. `COTAL_CODEX_BIN` and its siblings are operator binary overrides, and sit here * precisely BECAUSE the neighbouring per-launch `COTAL_CODEX_HOME`/`_CONFIG`/`_TUI`/`_PROMPT` do * not. Host-session markers (`CLAUDE_CODE_CHILD_SESSION`, `CLAUDECODE`, `CLAUDE_CODE_ENTRYPOINT`, * and the analogous names other hosts use to mark a nested session) are never on this list: a * seat's transcript/resume behaviour is a property of the seat, never of how many layers up * someone once ran `cotal up` inside an agent. Connector-declared auth vars such as * `CLAUDE_CODE_OAUTH_TOKEN` are not operator knobs and live on the connector's providerKeys. */ export declare const OPERATOR_ENV_KEEP: readonly ["COTAL_HOME", "COTAL_FEEDBACK_KEY", "COTAL_FEEDBACK_EMAIL", "COTAL_FEEDBACK_URL", "COTAL_DEFAULT_AGENT", "COTAL_DEFAULT_PERSONA", "COTAL_SKIP_CONNECTOR_SEED", "COTAL_SKIP_ASSIST", "COTAL_DETACH_KEY", "COTAL_COMPLETE_DEBUG", "COTAL_DEBUG", "COTAL_SERVE_HEADLESS", "COTAL_EVENTS_DEFAULT", "COTAL_MEMBERSHIP_INTERVAL_MS", "COTAL_DELIVERY_BROKER_GONE_MS", "COTAL_IDP_TIMEOUT_MS", "COTAL_CODEX_BIN", "COTAL_OPENCODE_BIN", "COTAL_ORCA_BIN"]; /** Build the base env a spawned agent runs with. * * The child receives the OS allow-list, {@link OPERATOR_ENV_KEEP}, connector-declared provider * keys, `mcpKeys` (the `${VAR}` secrets a shared MCP server references), and names deliberately * added by `spawn.env`. There is no inherit mode: omitting `envAllow` adds no extras, never the * manager's ambient environment. Host-session markers therefore cannot leak unless a persona or * operator names them on `spawn.env`. * * Allow-list matching is CASE-INSENSITIVE and each value is copied under the OS's OWN key casing: * Windows spells these `Path`/`ComSpec`/`windir`, so a canonical-only copy would either miss them * (a plain read of `process.env.SystemRoot` differs from `process.env.systemroot`) or, worse, emit * BOTH `Path` and `PATH` - a case-duplicate Windows process creation chokes on. Keying off the * source env's actual casing (one entry per lowercased name) forwards each var exactly once. */ export declare function launchEnv(opts?: { providerKeys?: readonly string[]; mcpKeys?: readonly string[]; envAllow?: readonly string[]; }): Record; /** The agent's resolved access policy as `COTAL_*` env, when present. Forwarded by each connector * so the spawned session's runtime read/post set matches the creds the manager minted from the * same policy. Without it a manifest-spawned agent — whose materialized persona carries no access * frontmatter — has no channel set to read, so it joins nothing even though its creds authorize * channels. Empty/absent lists are omitted: the connector then defers to the persona file or the * join link, preserving the persona-spawn path unchanged. */ export declare function aclEnv(opts: { subscribe?: string[]; allowSubscribe?: string[]; allowPublish?: string[]; capabilities?: string[]; }): Record; /** Validate a connector's opaque {@link LaunchOpts.launchOptions} bag and return its entries for the * connector to render into its host form (CLI flags / config / env). This is a RAW passthrough: the * connector forwards every option as-is. The trust boundary is the spawn capability itself — WHO may * spawn (the caller's authenticated identity), not WHICH flags a spawn carries. An operator running * `cotal spawn --opt` on their own host can already run the agent binary directly with any flag, so * capping their flags is theater; a mesh peer's `cotal_spawn` is bounded by whether it holds the * spawn capability at all. So no allow-list, no deny-list — the only check is a key-SHAPE guard for * process integrity (see {@link LAUNCH_OPTION_KEY}, {@link UNSAFE_LAUNCH_OPTION_KEYS}): a key must * name one flag / config field, never a prototype-polluting name or an `=`-embedding token that * would corrupt the config object or garble a rendered flag. Core never sees this; each connector * calls it for the surface IT renders. */ export declare function connectorLaunchOptions(connector: string, launchOptions: Record | undefined): [string, unknown][]; /** * The launch's CONNECTION MATERIAL as a private file, and one env entry naming it. * * This replaces `userAuthEnv` and the per-connector `COTAL_CREDS` / `COTAL_SERVERS` / * `COTAL_CONTROL_TOKEN` assignments it used to sit beside. Those put the broker address, the * credential and a control-plane bearer into the seat's process environment, which every descendant * of the seat inherits: the build it runs, the linter, the third-party CLI, the test suite that * reads its broker from the environment. Nothing in that chain asked for any of it, and there is no * moment where a human sees a credential being handed over, so there is no natural moment to object. * * Now they ride a 0600 file (see `writeLaunchMaterial`) and only its PATH is exported. The identity * that is not secret - space, name, role, id, lifecycle uid, the ACLs, the control SOCKET path - * stays in the environment where the launcher's contract has always put it, because a descendant * learning the seat's name is not the failure. * * Refuses a creds+userAuth combination here (one launch, one identity plane - U10), which is where * `userAuthEnv` refused it. */ export declare function materialEnv(opts: { creds?: string; servers?: string; token?: string; controlToken?: string; userAuth?: { owner: string; actor: string; sentinelCredsPath: string; bearerCmd: string[]; }; }): Record; /** The per-agent EVENT channel and its classifier, RE-EXPORTED FROM CORE. * * They were defined here, and they moved. The convention is one every connector publishes to and * every reader has to recognise, so it is a protocol shape rather than an adapter's choice, and it * now lives beside the frame's identity in `packages/core/src/event-channel.ts`. The comment that * used to sit here argued the opposite in those words: that an agent event stream is a connector * feature and a classifier for the convention belongs beside its constructor. The second half was * right and is why they moved TOGETHER; the first half was wrong, and the evidence is that the two * surfaces which most need to classify, the console's mesh view and the dashboard, cannot reach * this package at all. * * `isEventChannel` is no longer a prefix test. It derives the principal and refuses a name that * does not resolve to one, which is what retires the known limit this file used to document. The * reasoning for that direction is on the core function. * * Re-exported rather than relocated silently, so every existing importer of `../src/launch.js` * keeps working and the move is not a breaking change to this package's surface. */ export { EVENT_CHANNEL_PREFIX, eventChannel, eventChannelPrincipal, isEventChannel, } from "@cotal-ai/core"; /** The event channel for a LIVE session, derived from the endpoint's own principal — what the * broker will actually enforce against, never `config.name` and never the launch env. * * REFUSES AN EPHEMERAL ACTOR LOUDLY, and that refusal is the whole point. An endpoint with neither * a declared `card.id` nor creds SELF-MINTS a random actor per process ({@link CotalEndpoint} dev * branch), so its channel would differ on every restart and could never match a grant minted in * advance. The tempting repair — fall back to the display name for that one mode — would reinstate * the fused-channel defect on the single path that has no credential to grade it against, which is * where it would live forever. So the mode fails closed: events are unavailable without a stable * identity, and the operator is told which. * * Structurally typed rather than importing `CotalEndpoint`, so the three connectors' publish paths * and a test can drive the SAME refusal. */ export declare function eventChannelForSession(ep: { principal: { owner: string; actor: string; }; actorIsEphemeral: boolean; }): string; /** * Resolve a DISPLAY NAME to its event channel, against the presence records a reader already holds. * * **THIS EXISTS BECAUSE THE RE-KEY MADE THE CHANNEL UNGUESSABLE, AND THAT COST IS REAL.** While the * channel was `events.`, a viewer holding a roster row could construct it by string * arithmetic. It now carries the principal — in the dev default that is `events.local.<56-char * nkey>` — which nothing about a display name predicts. The isolation defect the re-key fixed was * worth that; leaving every reader to invent its own lookup would not be, because each one would * invent a different answer to the ambiguity below and most would invent the wrong one. * * **AMBIGUITY IS REFUSED, NOT RESOLVED, AND IT IS THE WHOLE POINT OF THE FUNCTION.** Display names * are not unique and never were: `assertValidName` permits two agents to carry the same one, and * this mesh runs duplicate lane names routinely. A resolver that returned the FIRST match would * reinstate the exact defect the re-key removed — two distinct principals fused onto one answer — * except now at the READ end, where it is worse: a viewer would silently display one agent's stream * under another agent's name, and nothing on the wire would look wrong. So a name matching two * DIFFERENT principals throws and names both. * * **Rows that agree on the principal are ONE agent, not an ambiguity.** A roster carries stale * presence within its TTL, so the same agent legitimately appears more than once; refusing that * would make the function useless exactly when a reader most needs it. The test is on the resolved * principal, never on the row count. * * **It resolves from `owner`/`actor` when present and falls back to parsing `id`** — both are the * same principal by construction (`card.id` is `principalKey(owner, actor).key`), and `id` is the * field every peer is guaranteed to carry. It does NOT guess: a row whose principal cannot be * determined from either is reported as such rather than skipped, because silently skipping the one * row that mattered turns a wrong answer into a confident wrong answer. * * @throws naming the failure, never returning a sentinel — a reader that got `undefined` would show * an empty pane, and an empty pane is indistinguishable from a correctly-empty one. */ export declare function eventChannelForName(name: string, peers: readonly { name: string; id?: string; owner?: string; actor?: string; }[]): string; /** The environment-variable NAMES a set of shared MCP server specs reference via `${VAR}` / * `${VAR:-default}` (in command/args/env/url/headers). The single source of which operator vars * a shared server needs: forwarded BY NAME through {@link launchEnv} (`mcpKeys`), never * `...process.env`, so secret keys keep living in the operator's env (and the `.mcp.json`-style * config stays a `${VAR}` reference, not a plaintext secret). */ export declare function mcpServerEnvKeys(servers: Record): string[]; //# sourceMappingURL=launch.d.ts.map