/** * Lifecycle layer — Protocol v3's session-boundary surface. * * The deterministic boot envelope {@link SessionInitCommand} replaces the v2 * `configure` + `provision_secrets` + initial `request_access_token` round * trips with one upfront message delivering identity, resolved config, * pre-minted credentials, secrets, capabilities, shared state, active flows, * resume hints, and logging config. * * Spec: `_devlog/specs/2026-05-10-deterministic-session-bootstrap.md`. * * @see {@link ProtocolVersion} * @see {@link Capability} * * @category Lifecycle * @since 3.0.0 */ import type { Capability } from "./capabilities.js"; import type { LogStoreConfig } from "./logging.js"; import type { ResourceDeclaration } from "./protocol.js"; import type { AiResourceReference, SharedStateConfig, SubagentDefinition } from "./runtime.js"; import type { ProtocolVersion } from "./version.js"; /** * Emitted by the runner at session boot to announce its protocol version. * The platform's debug panel surfaces this alongside its own * {@link PROTOCOL_VERSION} so operators can spot version drift. * * @category Lifecycle * @since 2.0.0 * @docLink packages/types/protocol-v3#lifecycle-protocol-info-event */ export type ProtocolInfoEvent = { type: "protocol_info"; /** Version the runner speaks. */ version: ProtocolVersion; /** Executable runtime features; absence means none are supported. */ runtimeFeatures?: string[]; }; /** * Emitted by the runner when {@link SessionInitCommand.protocolVersion.major} * does not match the runner's own {@link PROTOCOL_VERSION}. The runner shuts * down after emitting; the platform is expected to restart the session * against a compatible image. * * @category Lifecycle * @since 2.0.0 * @docLink packages/types/protocol-v3#lifecycle-incompatible-protocol-event */ export type IncompatibleProtocolEvent = { type: "incompatible_protocol"; /** Version the runtime speaks. */ agentVersion: ProtocolVersion; /** Version the platform announced via `SessionInitCommand.protocolVersion`. */ platformVersion: ProtocolVersion; }; /** * Discriminated result of a credential mint dispatch. Carried in * {@link SessionInitCommand.credentials.mounts} / `.connectors` (pre-mint at * boot) and returned as the typed value of the `host.refresh_credential` * capability invocation (refresh on 401). * * Success branch carries the bearer token plus its provenance (`mintedAt`, * `expiresAt`). Failure branch carries a stable `code` so the runner can * choose the right surfacing strategy: * * - `not-configured`: no credential set up for the target (e.g. no * provider link bound to the project). Mount/connector stays inactive; * the runner emits a notice via `host.notify_user`. * - `revoked`: credential was revoked at the provider; user must * reconnect. The runner surfaces a deep link via `host.notify_user`. * - `provider-error`: upstream provider returned a non-success response; * `message` carries the upstream error text. * - `backend-error`: internal mediator error; safe to retry. * * Spec: `_devlog/specs/2026-05-10-deterministic-session-bootstrap.md`. * * @category Lifecycle * @since 3.0.0 * @docLink packages/types/protocol-v3#credential-mint */ export type CredentialMint = { ok: true; /** Bearer token. Treated as opaque by the runner. */ token: string; /** ISO-8601 expiry, or `null` for indefinite tokens (static PATs). */ expiresAt: string | null; /** ISO-8601 mint timestamp (for audit). */ mintedAt: string; /** * AI-provider config (seat) the platform actually minted for. Differs * from the one the runner asked for when the platform re-resolved — the * pinned config was parked or unusable and a peer was picked instead. * The runner treats a difference as an instruction to repoint the live * session at that config. * * Optional and additive: a platform that does not send it produces * exactly today's behaviour (no switch is ever detected). * * @since 3.9.0 */ configId?: string; } | { ok: false; code: "not-configured" | "revoked" | "provider-error" | "backend-error"; /** Human-readable detail. Never carries the secret itself. */ message: string; }; /** * Which rejection drove a `retry-401` `host.refresh_credential` — the runner * saying *why* it is asking, where `reason: 'retry-401'` alone cannot. * * - `auth` — the provider returned an **auth-shaped** rejection (HTTP 401 / * `authentication_error`). The credential is the suspect; refresh it. * - `usage-limit` — the turn was blocked by an upstream usage / rate limit. * The credential is fine; the **seat** is exhausted, so re-resolving to a * peer seat is the repair, not rotating this one's token. * * **`auth` is not a claim that capacity is fine.** The bridge's * `error-classifier` documents that Anthropic mislabels quota exhaustion on * Claude Max OAuth subscriptions as `authentication_error`, so an `auth` value * says only "the rejection looked like auth" and must never be read as evidence * *against* a limit. Only `usage-limit` is a positive assertion. * * The field carrying this is **optional** and is omitted entirely whenever the * runner cannot classify (the connector and MCP refresh paths classify nothing), * so a consumer that ignores it behaves exactly as it did before 3.10.0. * Consumers MUST treat an **unrecognised** value as if the field were absent — * that is what lets a third rejection be added later without another breaking * change. * * Spec: skaile-ai/platform#3562. * * @category Lifecycle * @since 3.10.0 */ export type CredentialRejection = "auth" | "usage-limit"; /** * Resolved agent-config profile the platform substituted for this session — * the same values it bakes into the generated skaile.yaml `agent-config.default` * (driver already precedence-resolved, model already alias-translated). * * @category Lifecycle * @since 3.6.0 */ export type ResolvedAgentConfig = { driver?: string; provider?: string; /** Explicit managed OpenAI billing mode; never inferred from available credentials. */ authMode?: "subscription" | "api-key"; model?: string; /** AI cloud transport (`default | bedrock | vertex | azure | gateway`). */ cloud?: string; /** Non-secret cloud transport settings (camelCase wire shape). */ cloudConfig?: { region?: string; projectId?: string; resource?: string; baseUrl?: string; }; thinking?: "adaptive" | "enabled" | "disabled"; effort?: "low" | "medium" | "high" | "max" | (string & {}); }; /** * The resolved `skaile.yaml` the platform substituted for this session. * * The runner does NOT re-resolve — it consumes the entries verbatim. Mount * and connector credentials ride separately in * {@link SessionInitCommand.credentials} so the runner never has to call back * into the platform during mount setup. * * @category Lifecycle * @since 3.0.0 * @docLink packages/types/protocol-v3#resolved-skaile-config */ export type ResolvedSkaileConfig = { /** Mount declarations from `skaile.yaml`. */ mounts?: Array; /** Connector declarations from `skaile.yaml`. */ connectors?: ResourceDeclaration[]; /** AI resource (skill / flow / prompt) references from `skaile.yaml`. */ aiResources?: AiResourceReference[]; /** Subagent definitions from `skaile.yaml`. */ subagents?: Record; /** * Resolved agent config (driver/model/cloud). Consumed as a disk-first * fallback: a skaile.yaml `agent-config` profile on disk wins outright. * @since 3.6.0 */ agentConfig?: ResolvedAgentConfig; /** * Platform `AIProviderConfig.id` for the AI credential provisioned into * this session. The runner uses this when invoking * `host.refresh_credential` for `ai-credentials`. */ aiProviderConfigId?: string; }; /** * Sent by the platform immediately after the WebSocket connects. Replaces the * v2 `configure` + `provision_secrets` + initial `request_access_token` * round trips with one deterministic envelope. * * The runner gates mount/connector setup on processing this message. No * other commands are accepted until the runner has emitted * {@link SessionInitAckEvent}. The runner enforces identity on every * successive `session_init` (e.g. after a backend restart and gateway * reconnect): mismatch on `identity.containerId` triggers * {@link SessionInitMismatchEvent} + shutdown. * * Spec: `_devlog/specs/2026-05-10-deterministic-session-bootstrap.md`. * * @category Lifecycle * @since 3.0.0 * @docLink packages/types/protocol-v3#session-init-command */ export type SessionInitCommand = { type: "session_init"; /** Protocol version the platform speaks. Major-equality enforced by the runner. */ protocolVersion: ProtocolVersion; /** Must be supported before the runner consumes any credentials. */ requiredRuntimeFeatures?: string[]; /** Identity context. Available to every capability handler via `HandlerContext`. */ identity: { containerId: string; sessionId: string; projectId: string; /** Project owner; used by capability handlers that resolve UserDelegation credentials. */ ownerUserId: string; /** * Optional list of users with read access. Populated by the platform's * sharing service for multi-user sessions; absent for single-owner * projects. */ members?: { userId: string; role: "Owner" | "Editor" | "Viewer"; }[]; }; /** * Resolved skaile.yaml as the platform substituted it. The runner does * NOT re-resolve. */ resolvedConfig: ResolvedSkaileConfig; /** * Pre-minted credentials, keyed by mount id and connector id. Each entry * is either a successful mint (token + expiry) or a structured failure * the runner can surface to the user as a "this mount is unavailable" * notice. * * For mounts/connectors whose `auth: backend` was rendered, the runner * reads from this map instead of calling back to the platform. Refresh on * 401 still goes through the `host.refresh_credential` capability. */ credentials: { mounts: Record; connectors: Record; mcp?: Record; /** Dedicated managed AI delivery, separate from mount credentials. */ ai?: CodexCredentialDelivery; }; /** * Long-lived secrets pushed at boot. Same shape and isolation rules as * the legacy v2 `provision_secrets` command. */ secrets?: Record; /** * Initial capability set (both directions). Replaces * `ConfigureCommandV2.capabilities`. Includes: * - host capabilities the runner can invoke (audience: `['runtime']`). * - client-side capabilities the platform exposes to the LLM * (audience: `['llm', 'user']` etc.). * * Runtime-audience entries are re-pushed on every `session_init` (e.g. * on every wake / reconnect) — the runner does NOT persist them across * hibernation. */ capabilities?: Capability[]; /** Initial shared state stores. Replaces `AgentReconfigureOptions.sharedState`. */ sharedState?: SharedStateConfig[]; /** * Resume cascade hints. Replaces `AgentReconfigureOptions.resumeSessionId` * and `expectedCapabilitySignature`. * * When `sessionId` is set, drivers that support native resume (claude-sdk * today) recreate the driver with this id. The runner validates * `expectedCapabilitySignature` against its post-init registry signature; * mismatch causes the hint to be dropped and {@link ResumeFailedEvent} * to fire. */ resume?: { sessionId?: string; expectedCapabilitySignature?: string; }; /** * Logging configuration (mode, level, retention). Used to bootstrap the * runner's LogStore at boot. The pre-init ring buffer drains into the * LogStore once it is constructed. */ logging?: LogStoreConfig; }; /** * Emitted by the runner once {@link SessionInitCommand} has been processed. * Until this event fires the runner is in init-only mode — no prompts, no * capability invocations are dispatched. Also re-sent unsolicited, with * `replayed: true`, as the first event of every later connect while the * runner stays initialized. * * @category Lifecycle * @since 3.0.0 * @docLink packages/types/protocol-v3#session-init-ack-event */ export type SessionInitAckEvent = { type: "session_init_ack"; /** * Capability signature the runner has after processing the init. * Computed from the registry's contents via * `computeCapabilitySignature` (in `@skaile/workspaces/runner`). The platform * uses this to verify that tier-1 native resume (when requested) found * a compatible toolset. */ capabilitySignature: string; /** * Outcome of the pre-first-turn filesystem transcript probe, stamped only * when the init requested a tier-1 native resume ({@link SessionInitCommand} * `resume.sessionId`). `transcript_found` means the SDK JSONL exists on disk * and the resume can proceed; `jsonl_lost` means it is gone, so the platform * should synchronously downgrade to a DB-backed restoration. Absent means no * resume was requested (or an older runner). Only transcript EXISTENCE is * confirmed here — the actual SDK resume still runs on the first turn. * * @since 3.1.0 */ resumeProbe?: "transcript_found" | "jsonl_lost"; /** * Set on an unsolicited re-emission of the last ack on a gateway (re)connect. * Carries no new init outcome: `capabilitySignature` is recomputed from the * live registry at replay time and `resumeProbe` is never replayed. Absent on * the ack that answers a `session_init`. * * @since 3.22.0 */ replayed?: true; }; /** * Emitted by the runner when a successive {@link SessionInitCommand} arrives * with an `identity.containerId` that does not match the runner's current * containerId. Defence against accidental cross-session reuse and routing * bugs. The runner shuts down after emitting. * * @category Lifecycle * @since 3.0.0 * @docLink packages/types/protocol-v3#session-init-mismatch-event */ export type SessionInitMismatchEvent = { type: "session_init_mismatch"; expectedContainerId: string; receivedContainerId: string; }; /** Short-lived managed OpenAI credentials. Refresh material never crosses this boundary. */ export type CodexCredentialDelivery = { kind: "codex-subscription"; configId: string; credentialVersion: number; accessToken: string; chatgptAccountId: string; chatgptPlanType?: string; expiresAt: string; mintedAt: string; } | { kind: "openai-api-key"; configId: string; credentialVersion: number; apiKey: string; mintedAt: string; }; /** Runtime-only host.refresh_credential input; connection identity supplies authorization. */ export type CodexSubscriptionRefreshInput = { kind: "codex-subscription"; configId: string; credentialVersion: number; reason: "initial" | "refresh" | "retry-401"; }; /** Subscription refresh can never return an API key or select another config. */ export type CodexSubscriptionRefreshResult = { ok: true; credential: Extract; } | { ok: false; code: "not-configured" | "revoked" | "provider-error" | "backend-error"; message: string; }; //# sourceMappingURL=lifecycle.d.ts.map