@era-laboratories/era-sdk 0.1.0-next.44 → 0.1.0-next.45

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/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["genPostChains","genGetChains","genGetChainsById","genPutChainsById","genDeleteChainsById","genPostChainsByIdPublishingVersions","genGetChainsByIdPublishingVersions","genPutChainVersionsByVId","genPostChainsByIdPublishingVersionsByVIdSubmitForReview","genPostChainsByIdPublishingVersionsByVIdApproveReview","genPostChainsByIdPublishingVersionsByVIdRejectReview","genPostChainsByIdRollback","genStartRun","genGetRunEvents","genDeliverRunEvent","genGetRun","genCancelRun","genGetExperiencesById","predicateSchema","genPostDeviceKeysRenew","isRef","simulateSpec"],"sources":["../src/modules/outputs.ts","../src/modules/chains.ts","../src/modules/chainFlows.ts","../src/modules/commands.ts","../src/modules/context.ts","../src/modules/conversation.ts","../src/modules/deviceStream.ts","../src/modules/guardrails.ts","../src/modules/simulate.ts","../src/modules/experienceAuthoring.ts","../src/modules/keyStore.ts","../src/modules/triggers.ts"],"sourcesContent":["// outputs module — the Output contract (PRD #1 §5.6, ERA-4403 physical / ERA-4404 surface).\n//\n// Outputs INFORM THE USER that the work is done (Spindle results go to the\n// agent; Output results go to the user). Transport-shaped factories — the\n// era-maker actuator catalog + the iOS action set are both open-ended, so we\n// model by transport, not one factory per modality:\n// - outputs.physical() → a device-stream actuator command (subscribe path)\n// - outputs.surface() → an iOS ActionRegistry action (push path)\n// - outputs.say() → a device-stream text response (ERA-6126, text-first)\n//\n// These are AUTHOR-TIME factories: they validate and produce an `OutputDef`\n// that the orchestrator's `builtin:emit-output` action (ERA-4402, era-ingress)\n// actually fires — with fan-out, bound either declaratively via `on:` or\n// agent-emitted. Surface/voice transports are stateless fire-and-forget;\n// PHYSICAL outputs carry delivery receipts (ERA-6033): the run's events show\n// pending → output_delivered / output_failed / output_undeliverable /\n// output_expired, driven by the device's execution ack (see the deviceStream\n// module's `onExecute` / `ack()`), with server-side ledger-sourced retry.\n//\n// `fields` / `params` values may be literals OR `$.<ref>` tokens (e.g.\n// `$.agent.summary`) resolved at runtime by the orchestrator — author-time\n// validation type-checks literals and passes refs through untouched.\n//\n// Tree-shakeable subpath import: `@era-laboratories/era-sdk/modules/outputs`.\n\nimport * as z from 'zod';\nimport type { Ref } from './chains.js';\n\n// ─── Shared ───────────────────────────────────────────────────────────────────\n\n/** When an output fires, relative to the run's step lifecycle. */\nexport type OutputLifecycle = 'start' | 'success' | 'failure';\n\n/** A field/param value: a literal (JSON) or a `$.<ref>` resolved at runtime. */\nexport type OutputValue = Ref<unknown> | unknown;\n\nconst lifecycleSchema = z.enum(['start', 'success', 'failure']);\n\n/** Author-time ref token shape (`$.a.b` → `{ $ref: '$.a.b' }`). */\nconst refSchema = z.object({ $ref: z.string() });\n\n/**\n * A direct `deviceId` target (ERA-6202): a literal device UUID, or a `$.` ref\n * token resolved at runtime. `z.uuid()` (house style, matches the generated\n * grammar) rejects a non-UUID literal such as a slot NAME at author time — the\n * live-reproduced footgun where `deviceId: 'my-lamp'` silently shipped a\n * never-resolving target.\n */\nconst deviceIdTargetSchema = z.uuid().or(refSchema);\n\n/**\n * The device-targeting half of a `physical`/`say` output — EXACTLY\n * ONE of:\n * - `slot` — a named binding, resolved to a deviceId server-side per user.\n * - `deviceId` — a direct target: a literal device UUID, or a `$.` ref token\n * resolved at runtime by the orchestrator. The canonical reply-to-sender\n * idiom is `deviceId: { $ref: '$.input.trigger.source.deviceId' }` (see\n * {@link triggerRefs.senderDeviceId}).\n */\nexport type OutputTarget =\n\t| { slot: string; deviceId?: never }\n\t| { deviceId: string | Ref<unknown>; slot?: never };\n\n/** slot-XOR-deviceId predicate shared by factories + structural schemas. */\nconst hasExactlyOneTarget = (val: { slot?: unknown; deviceId?: unknown }) =>\n\t(val.slot !== undefined) !== (val.deviceId !== undefined);\n\n/** Enforce the slot-XOR-deviceId contract at factory call time (ERA-6191). */\nfunction assertExactlyOneTarget(\n\tfactory: 'physical' | 'say',\n\topts: { slot?: string; deviceId?: string | Ref<unknown> },\n): void {\n\tif (!hasExactlyOneTarget(opts)) {\n\t\tthrow new Error(\n\t\t\t`outputs.${factory}: exactly one of \\`slot\\` or \\`deviceId\\` is required.`,\n\t\t);\n\t}\n\tif (opts.slot !== undefined && opts.slot.length === 0) {\n\t\tthrow new Error(`outputs.${factory}: \\`slot\\` must be a non-empty string.`);\n\t}\n\tif (opts.deviceId !== undefined) {\n\t\t// literal must be a device UUID; a `$.` ref passes through untouched\n\t\tdeviceIdTargetSchema.parse(opts.deviceId);\n\t}\n}\n\n/** Emit only the provided targeting key (no `slot: undefined` on the wire). */\nconst targetKeys = (opts: {\n\tslot?: string;\n\tdeviceId?: string | Ref<unknown>;\n}) => ({\n\t...(opts.slot !== undefined ? { slot: opts.slot } : {}),\n\t...(opts.deviceId !== undefined ? { deviceId: opts.deviceId } : {}),\n});\n\n/**\n * A strict object schema — rejects unknown keys so a typo'd field/param fails at\n * author time. Implemented via `z.object().catchall(z.never())` (the house lint\n * prefers `z.object` over `z.strictObject`).\n */\nconst strictShape = (shape: z.ZodRawShape) =>\n\tz.object(shape).catchall(z.never());\n\n// ─── physical() — device-stream actuator output (ERA-4403) ───────────────────────\n\n/**\n * A physical (actuator) output def — the object {@link physical} returns, lowered\n * at runtime to a device-stream actuator command. Physical outputs carry delivery\n * receipts (the run's events progress `pending` → `output_delivered` /\n * `output_failed` / `output_undeliverable` / `output_expired`).\n */\nexport interface PhysicalOutput {\n\t/** Transport discriminant — always `'physical'`. */\n\ttransport: 'physical';\n\t/**\n\t * Named binding slot — resolved to a deviceId/canvas server-side per\n\t * user. Exactly one of `slot` | `deviceId`.\n\t */\n\tslot?: string;\n\t/**\n\t * Direct device target: a literal device UUID, or a `$.` ref\n\t * resolved at runtime (canonical reply-to-sender:\n\t * `deviceId: { $ref: '$.input.trigger.source.deviceId' }` — see\n\t * {@link triggerRefs.senderDeviceId}). Exactly one of `slot` | `deviceId`.\n\t */\n\tdeviceId?: string | Ref<unknown>;\n\t/** ID of the target actuator in the era-maker catalogue (e.g. `'status_led'`, `'servo'`). */\n\tactuatorId: string;\n\t/** Actuator field values, keyed by field name; each value is a literal or a `$.<ref>` resolved at runtime. */\n\tfields: Record<string, OutputValue>;\n\t/** Lifecycle binding — when the output fires relative to the run's step lifecycle. Omitted = agent-emitted at runtime. */\n\ton?: OutputLifecycle;\n}\n\n/**\n * Vendored era-maker actuator catalogue — BETA-ANCHOR SUBSET. The full\n * catalogue lives in era-maker (`src/catalogue/*`, not checked out here);\n * catalog-driven validation + delivery receipts are PRD #3. Each field accepts\n * its literal type OR a `$.<ref>`.\n */\nconst ACTUATOR_CATALOGUE: Record<string, z.ZodType> = {\n\tstatus_led: strictShape({\n\t\tcolor: z.string().min(1).or(refSchema).optional(),\n\t\tblink: z.int().min(0).max(10).or(refSchema).optional(),\n\t}),\n\tservo: strictShape({\n\t\t// PRD §5.1: angle is a u8 in 0..180.\n\t\tangle: z.int().min(0).max(180).or(refSchema),\n\t}),\n};\n\n/**\n * Declare a physical (actuator) output. Validates `actuatorId` against the\n * vendored era-maker catalogue and `fields` against that actuator's capability\n * schema (literals type-checked; `$.<ref>` values passed through). Throws on an\n * unknown actuator or an invalid field.\n *\n * Targeting: exactly ONE of `slot` (named binding, resolved\n * server-side) or `deviceId` (direct target — literal UUID or `$.` ref, e.g.\n * `triggerRefs.senderDeviceId` to reply to the triggering device).\n *\n * @param opts - The actuator target and command. `slot` XOR `deviceId` (exactly one), `actuatorId` (must exist in the vendored catalogue), `fields` (validated against that actuator's capability schema), and optional `on` lifecycle binding.\n * @returns A {@link PhysicalOutput} def ready to attach to a chain's `outputs`.\n * @throws {Error} When neither or both of `slot`/`deviceId` are given, when `slot` is empty, or when `actuatorId` is unknown.\n * @throws {z.ZodError} When a literal `deviceId` is not a UUID, or when `fields` contains an unknown or out-of-range value for the actuator (e.g. a `servo` `angle` outside 0–180).\n * @remarks Pure and author-time — no network call. Literal field values are type-checked; `$.<ref>` tokens pass through untouched and resolve at runtime.\n * @example\n * ```ts\n * const led = outputs.physical({\n * slot: 'porch-light',\n * actuatorId: 'status_led',\n * fields: { color: 'green', blink: 3 },\n * on: 'success',\n * });\n *\n * const arm = outputs.physical({\n * deviceId: triggerRefs.senderDeviceId,\n * actuatorId: 'servo',\n * fields: { angle: { $ref: '$.plan.angle' } },\n * });\n * ```\n */\nexport function physical(\n\topts: OutputTarget & {\n\t\tactuatorId: string;\n\t\tfields: Record<string, OutputValue>;\n\t\ton?: OutputLifecycle;\n\t},\n): PhysicalOutput {\n\tassertExactlyOneTarget('physical', opts);\n\tconst schema = ACTUATOR_CATALOGUE[opts.actuatorId];\n\tif (!schema) {\n\t\tthrow new Error(\n\t\t\t`Unknown actuatorId \"${opts.actuatorId}\". Known: ${Object.keys(ACTUATOR_CATALOGUE).join(', ')}.`,\n\t\t);\n\t}\n\tschema.parse(opts.fields); // throws ZodError on unknown/out-of-range fields\n\treturn {\n\t\ttransport: 'physical',\n\t\t...targetKeys(opts),\n\t\tactuatorId: opts.actuatorId,\n\t\tfields: opts.fields,\n\t\t...(opts.on ? { on: opts.on } : {}),\n\t};\n}\n\n// ─── surface() — iOS ActionRegistry output (ERA-4404) ─────────────────────────────\n\n// ERA-6036: push delivery is not currently deployed. era-push-service was\n// decommissioned (ERA-5188) and era-ingress `_emit_surface` no-ops when its\n// push key is unset (returns `{sent:false, reason:'api_key_unset'}`). So a\n// `surface` output authors + validates + publishes fine but is delivered\n// nowhere today. We warn ONCE per process at author time — never throw: the\n// wire shape is stable and the backend accepts it, so nothing needs\n// re-authoring when push delivery ships (ERA-6034). Remove this warning as\n// part of ERA-6034.\nlet surfaceUndeliverableWarned = false;\n\n/**\n * One-shot (per process) advisory that `surface` outputs are authored but not\n * currently deliverable. Shared by the `surface()` factory and the\n * spec-validation paths (`defineChain` / `defineExperience`) so a single\n * warning fires regardless of how the surface output entered a spec. Never\n * throws — validation semantics are unchanged.\n *\n * @internal Cross-module implementation detail; not part of the partner-facing API.\n */\nexport function warnSurfaceUndeliverable(): void {\n\tif (surfaceUndeliverableWarned) return;\n\tsurfaceUndeliverableWarned = true;\n\tconsole.warn(\n\t\t'[era-sdk] Heads up: `surface` outputs are not yet deliverable — push ' +\n\t\t\t'delivery is planned but not implemented yet. Your spec is valid and ' +\n\t\t\t'will start delivering automatically once push support ships, so no ' +\n\t\t\t'changes are needed. `physical` and voice outputs work today.',\n\t);\n}\n\n/**\n * Per-action delivery capability (the typed push-action layer):\n * - `background` — executes silently on a content-available push.\n * - `on-tap` — iOS forbids launching UI without interaction, so the\n * notification IS the inform; the action runs when the user taps.\n * - `interactive` — notification action buttons (follow-on, shared with\n * `confirmBefore`; not yet emitted by the base layer).\n */\nexport type CapabilityTier = 'background' | 'on-tap' | 'interactive';\n\n/**\n * A surface (iOS ActionRegistry / push) output binding.\n *\n * ⚠️ Deliverability: `surface` outputs are **not yet deliverable** — push\n * delivery is planned but not implemented yet. Authoring one is still valid and\n * forward-compatible: your spec will start delivering automatically once push\n * support ships, with no changes needed. `physical` and voice outputs work\n * today.\n */\nexport interface SurfaceOutput {\n\t/** Transport discriminant — always `'surface'`. */\n\ttransport: 'surface';\n\t/** iOS ActionRegistry action name to invoke (e.g. `'push'`, `'setReminder'`), or a `custom.*` app-defined action. */\n\taction: string;\n\t/** Action params, keyed by name; each value is a literal or a `$.<ref>` resolved at runtime. */\n\tparams: Record<string, OutputValue>;\n\t/** Derived from the action's registry entry; informs silent vs tap delivery. */\n\ttier: CapabilityTier;\n\t/** Lifecycle binding — when the output fires relative to the run's step lifecycle. Omitted = agent-emitted at runtime. */\n\ton?: OutputLifecycle;\n}\n\ninterface ActionDef {\n\ttier: CapabilityTier;\n\tparams: z.ZodType;\n}\n\n/**\n * Vendored iOS `ActionRegistry` contract (Era-Hub, 13 handlers). `tier` mirrors\n * each handler's `canRunInBackground`. Param schemas accept literals OR refs.\n */\nconst ACTION_REGISTRY: Record<string, ActionDef> = {\n\t// 'push' is a plain user-facing notification (the banner IS the output) — not\n\t// an ActionRegistry handler; delivered as title/body with no data.action.\n\tpush: {\n\t\ttier: 'on-tap',\n\t\tparams: strictShape({\n\t\t\ttext: z.string().min(1).or(refSchema),\n\t\t\ttitle: z.string().or(refSchema).optional(),\n\t\t}),\n\t},\n\topenURL: {\n\t\ttier: 'on-tap',\n\t\tparams: strictShape({ url: z.url().or(refSchema) }),\n\t},\n\t'music.play': { tier: 'background', params: strictShape({}) },\n\t'music.pause': { tier: 'background', params: strictShape({}) },\n\t'music.skip': { tier: 'background', params: strictShape({}) },\n\t'music.previous': { tier: 'background', params: strictShape({}) },\n\thapticBuzz: {\n\t\ttier: 'background',\n\t\tparams: strictShape({\n\t\t\tstyle: z\n\t\t\t\t.enum(['light', 'medium', 'rigid', 'soft', 'heavy'])\n\t\t\t\t.or(refSchema)\n\t\t\t\t.optional(),\n\t\t\tcount: z.int().min(1).max(5).or(refSchema).optional(),\n\t\t}),\n\t},\n\tsetTimer: {\n\t\ttier: 'on-tap',\n\t\tparams: strictShape({\n\t\t\tduration: z.int().min(1).max(86400).or(refSchema),\n\t\t\tlabel: z.string().or(refSchema).optional(),\n\t\t}),\n\t},\n\tsetReminder: {\n\t\ttier: 'background',\n\t\tparams: strictShape({\n\t\t\ttitle: z.string().min(1).or(refSchema),\n\t\t\tdueDate: z.string().or(refSchema).optional(),\n\t\t\tdueTime: z.string().or(refSchema).optional(),\n\t\t\tlist: z.string().or(refSchema).optional(),\n\t\t\tnotes: z.string().or(refSchema).optional(),\n\t\t}),\n\t},\n\tcreateEvent: {\n\t\ttier: 'background',\n\t\tparams: strictShape({\n\t\t\ttitle: z.string().min(1).or(refSchema),\n\t\t\tstartDate: z.string().or(refSchema).optional(),\n\t\t\tendDate: z.string().or(refSchema).optional(),\n\t\t\tlocation: z.string().or(refSchema).optional(),\n\t\t\tnotes: z.string().or(refSchema).optional(),\n\t\t\tallDay: z.boolean().or(refSchema).optional(),\n\t\t}),\n\t},\n\topenDevice: {\n\t\ttier: 'on-tap',\n\t\tparams: strictShape({ deviceId: z.string().min(1).or(refSchema) }),\n\t},\n\topenSettings: {\n\t\ttier: 'on-tap',\n\t\tparams: strictShape({\n\t\t\tsection: z\n\t\t\t\t.enum(['notifications', 'location', 'services', 'connected-accounts'])\n\t\t\t\t.or(refSchema)\n\t\t\t\t.optional(),\n\t\t}),\n\t},\n\topenProfile: { tier: 'on-tap', params: strictShape({}) },\n};\n\n/** `custom.*` actions are app-defined: tap-gated, arbitrary JSON params. */\nconst customActionDef: ActionDef = {\n\ttier: 'on-tap',\n\tparams: z.record(z.string(), z.json()),\n};\n\nfunction resolveAction(action: string): ActionDef {\n\tif (action.startsWith('custom.')) return customActionDef;\n\tconst def = ACTION_REGISTRY[action];\n\tif (!def) {\n\t\tthrow new Error(\n\t\t\t`Unknown surface action \"${action}\". Known: ${Object.keys(ACTION_REGISTRY).join(', ')}, custom.*.`,\n\t\t);\n\t}\n\treturn def;\n}\n\n/**\n * Declare a surface (push / iOS ActionRegistry) output. Validates `action`\n * against the vendored registry and `params` against that action's schema\n * (literals type-checked; refs passed through), and tags the delivery `tier`.\n * Throws on an unknown action or invalid params.\n *\n * ⚠️ Deliverability: emits a one-shot console warning — `surface` outputs are\n * not yet deliverable (push delivery is planned but not implemented yet). The\n * authored spec is valid and forward-compatible; it will start delivering\n * automatically once push support ships, with no changes needed. `physical` and\n * voice outputs work today.\n *\n * @param opts - `action` (a known registry action or `custom.*`), optional `params` (defaults to `{}`; validated against the action's schema), and optional `on` lifecycle binding.\n * @returns A {@link SurfaceOutput} def with its delivery `tier` tagged from the action's registry entry.\n * @throws {Error} When `action` is not a known registry action and does not start with `custom.`.\n * @throws {z.ZodError} When `params` contains an unknown or invalid value for the action (e.g. a non-URL `url` for `openURL`).\n * @remarks Pure and author-time — no network call, but emits a one-shot (per-process) console warning that surface outputs are not yet deliverable. Never throws for deliverability.\n * @example\n * ```ts\n * const notify = outputs.surface({\n * action: 'push',\n * params: { text: { $ref: '$.draft.output' }, title: 'Draft ready' },\n * on: 'success',\n * });\n * ```\n */\nexport function surface(opts: {\n\taction: string;\n\tparams?: Record<string, OutputValue>;\n\ton?: OutputLifecycle;\n}): SurfaceOutput {\n\twarnSurfaceUndeliverable();\n\tconst def = resolveAction(opts.action);\n\tconst params = opts.params ?? {};\n\tdef.params.parse(params); // throws ZodError on unknown/invalid params\n\treturn {\n\t\ttransport: 'surface',\n\t\taction: opts.action,\n\t\tparams,\n\t\ttier: def.tier,\n\t\t...(opts.on ? { on: opts.on } : {}),\n\t};\n}\n\n// ─── say() — device-stream text response output (ERA-6126) ───────────────────────\n\n/**\n * A say (device-targeted text response) output def — the object {@link say}\n * returns, lowered at runtime to a device-stream `say` command frame. Reaches a\n * device with no LiveKit room; text-first, with optional server-side TTS.\n */\nexport interface SayOutput {\n\t/** Transport discriminant — always `'say'`. */\n\ttransport: 'say';\n\t/**\n\t * Named binding slot — resolved to a deviceId server-side per user.\n\t * Exactly one of `slot` | `deviceId`.\n\t */\n\tslot?: string;\n\t/**\n\t * Direct device target: a literal device UUID, or a `$.` ref\n\t * resolved at runtime (canonical reply-to-sender:\n\t * `deviceId: { $ref: '$.input.trigger.source.deviceId' }` — see\n\t * {@link triggerRefs.senderDeviceId}). Exactly one of `slot` | `deviceId`.\n\t */\n\tdeviceId?: string | Ref<unknown>;\n\t/** Text the device renders or speaks; a literal or a `$.<ref>` (e.g. `$.reply.text`). */\n\ttext: string | Ref<unknown>;\n\t/**\n\t * Opt into server-side TTS: the platform synthesizes the\n\t * resolved text and the device receives a short-TTL signed `audioRef`\n\t * alongside the text. Absent/false = text-only, zero synthesis cost.\n\t */\n\taudio?: boolean;\n\t/** Lifecycle binding — when the output fires relative to the run's step lifecycle. Omitted = agent-emitted at runtime. */\n\ton?: OutputLifecycle;\n}\n\n/**\n * Declare a say (device-targeted text response) output — lowered to a\n * device-stream `say` command frame. Unlike the session-scoped voice\n * transport, this reaches a device with no LiveKit room. Text-first: by\n * default the device renders/displays the text or performs on-device TTS;\n * set `audio: true` to opt into server-side TTS.\n *\n * Targeting: exactly ONE of `slot` (named binding, resolved\n * server-side) or `deviceId` (direct target — literal UUID or `$.` ref, e.g.\n * `triggerRefs.senderDeviceId` to reply to the triggering device).\n *\n * @param opts - `slot` XOR `deviceId` (exactly one), `text` (literal or `$.<ref>`; a literal must be non-empty), optional `audio` (opt into server-side TTS), and optional `on` lifecycle binding.\n * @returns A {@link SayOutput} def ready to attach to a chain's `outputs`.\n * @throws {Error} When neither or both of `slot`/`deviceId` are given, or when `slot` is empty.\n * @throws {z.ZodError} When a literal `deviceId` is not a UUID, or when a literal `text` is an empty string.\n * @remarks Pure and author-time — no network call. `audio: true` opts into server-side TTS (the device receives a short-TTL signed `audioRef` alongside the text); absent/false = text-only at zero synthesis cost.\n * @example\n * ```ts\n * const reply = outputs.say({\n * deviceId: triggerRefs.senderDeviceId,\n * text: { $ref: '$.reply.text' },\n * audio: true,\n * on: 'success',\n * });\n * ```\n */\nexport function say(\n\topts: OutputTarget & {\n\t\ttext: string | Ref<unknown>;\n\t\taudio?: boolean;\n\t\ton?: OutputLifecycle;\n\t},\n): SayOutput {\n\tassertExactlyOneTarget('say', opts);\n\tz.string().min(1).or(refSchema).parse(opts.text); // literal must be non-empty\n\treturn {\n\t\ttransport: 'say',\n\t\t...targetKeys(opts),\n\t\ttext: opts.text,\n\t\t...(opts.audio !== undefined ? { audio: opts.audio } : {}),\n\t\t...(opts.on ? { on: opts.on } : {}),\n\t};\n}\n\n// ─── OutputDef + factory namespace ────────────────────────────────────────────────\n\n/**\n * Any output binding a chain's `outputs` array can hold — the discriminated\n * union of the three transports ({@link PhysicalOutput} | {@link SurfaceOutput}\n * | {@link SayOutput}), discriminated on `transport`. Build values with\n * {@link physical} / {@link surface} / {@link say} rather than by hand.\n */\nexport type OutputDef = PhysicalOutput | SurfaceOutput | SayOutput;\n\n/** The three transport-shaped output factories. */\nexport const outputs = { physical, surface, say };\n\n/** Typed `$.` ref helpers for trigger-derived output targeting. */\nexport const triggerRefs = {\n\t/** The device that fired this run's trigger — the canonical \"reply to sender\" target. Resolves server-side; on runs whose trigger carries no device (e.g. plain HTTP), the output fails closed and siblings still deliver. */\n\tsenderDeviceId: { $ref: '$.input.trigger.source.deviceId' } as Ref<string>,\n} as const;\n\n// ─── Structural schemas (for the chain spec `outputs` field + docs) ───────────────\n\nconst valueRecordSchema = z.record(z.string(), z.json().or(refSchema));\n\n/**\n * Zod schema for a {@link PhysicalOutput} binding — the structural validator\n * behind the chain spec's `outputs` field and the source of the generated docs\n * type table. Enforces the `slot` XOR `deviceId` targeting rule.\n */\nexport const physicalOutputSchema = z\n\t.object({\n\t\ttransport: z.literal('physical').meta({\n\t\t\tdescription: 'Transport discriminant — always \"physical\".',\n\t\t}),\n\t\tslot: z.string().min(1).optional().meta({\n\t\t\tdescription:\n\t\t\t\t'Named binding slot resolved to a deviceId/canvas server-side (§5.7). Exactly one of `slot`/`deviceId` (ERA-6191).',\n\t\t}),\n\t\tdeviceId: deviceIdTargetSchema.optional().meta({\n\t\t\tdescription:\n\t\t\t\t'Direct device target (ERA-6191): a literal device UUID or a `$.` ref (e.g. `$.input.trigger.source.deviceId`) resolved at runtime. Exactly one of `slot`/`deviceId`.',\n\t\t}),\n\t\tactuatorId: z.string().min(1).meta({\n\t\t\tdescription: 'ID of the target actuator in the era-maker catalogue.',\n\t\t}),\n\t\tfields: valueRecordSchema,\n\t\ton: lifecycleSchema.optional(),\n\t})\n\t.refine(hasExactlyOneTarget, {\n\t\terror: 'exactly one of `slot` or `deviceId` is required',\n\t})\n\t.meta({\n\t\tid: 'PhysicalOutput',\n\t\ttitle: 'PhysicalOutput',\n\t\tdescription:\n\t\t\t'A physical output binding — lowered to a device-stream actuator command (slot → deviceId resolved server-side, ERA-4500).',\n\t});\n\n/**\n * Zod schema for a {@link SurfaceOutput} binding — the structural validator\n * behind the chain spec's `outputs` field and the source of the generated docs\n * type table.\n */\nexport const surfaceOutputSchema = z\n\t.object({\n\t\ttransport: z.literal('surface').meta({\n\t\t\tdescription: 'Transport discriminant — always \"surface\".',\n\t\t}),\n\t\taction: z.string().min(1).meta({\n\t\t\tdescription: 'iOS ActionRegistry action name (or custom.*) to invoke.',\n\t\t}),\n\t\tparams: valueRecordSchema,\n\t\ttier: z.enum(['background', 'on-tap', 'interactive']).meta({\n\t\t\tdescription:\n\t\t\t\t'Delivery capability tier derived from the action registry entry (background/on-tap/interactive).',\n\t\t}),\n\t\ton: lifecycleSchema.optional(),\n\t})\n\t.meta({\n\t\tid: 'SurfaceOutput',\n\t\ttitle: 'SurfaceOutput',\n\t\tdescription:\n\t\t\t'A surface output binding — a typed server-side push action delivered to the iOS ActionRegistry, capability-tiered (background/on-tap/interactive). Not yet deliverable: push delivery is planned but not implemented yet; the spec is valid and forward-compatible and will start delivering automatically once push support ships.',\n\t});\n\n/**\n * Zod schema for a {@link SayOutput} binding — the structural validator behind\n * the chain spec's `outputs` field and the source of the generated docs type\n * table. Enforces the `slot` XOR `deviceId` targeting rule.\n */\nexport const sayOutputSchema = z\n\t.object({\n\t\ttransport: z.literal('say').meta({\n\t\t\tdescription: 'Transport discriminant — always \"say\".',\n\t\t}),\n\t\tslot: z.string().min(1).optional().meta({\n\t\t\tdescription:\n\t\t\t\t'Named binding slot resolved to a deviceId server-side per user (ERA-4500). Exactly one of `slot`/`deviceId` (ERA-6191).',\n\t\t}),\n\t\tdeviceId: deviceIdTargetSchema.optional().meta({\n\t\t\tdescription:\n\t\t\t\t'Direct device target (ERA-6191): a literal device UUID or a `$.` ref (e.g. `$.input.trigger.source.deviceId`) resolved at runtime. Exactly one of `slot`/`deviceId`.',\n\t\t}),\n\t\ttext: z.string().min(1).or(refSchema).meta({\n\t\t\tdescription:\n\t\t\t\t'Text the device renders or speaks — a literal or a $.<ref> token.',\n\t\t}),\n\t\taudio: z.boolean().optional().meta({\n\t\t\tdescription:\n\t\t\t\t'Opt into server-side TTS (ERA-6189) — the device receives a short-TTL signed audioRef at delivery. Absent/false = text-only.',\n\t\t}),\n\t\ton: lifecycleSchema.optional(),\n\t})\n\t.refine(hasExactlyOneTarget, {\n\t\terror: 'exactly one of `slot` or `deviceId` is required',\n\t})\n\t.meta({\n\t\tid: 'SayOutput',\n\t\ttitle: 'SayOutput',\n\t\tdescription:\n\t\t\t'A device-targeted text response — lowered to a device-stream say command frame (slot → deviceId resolved server-side, ERA-6126/ERA-6129).',\n\t});\n\n/** An output binding (physical, surface, or say). Built by `outputs.physical/surface/say`. */\nexport const outputDefSchema = z\n\t.discriminatedUnion('transport', [\n\t\tphysicalOutputSchema,\n\t\tsurfaceOutputSchema,\n\t\tsayOutputSchema,\n\t])\n\t.meta({\n\t\tid: 'OutputDef',\n\t\ttitle: 'Output',\n\t\tdescription:\n\t\t\t'An Output that informs the user of completion (PRD §5.6). physical() → device-stream actuator; surface() → iOS ActionRegistry push. Fan-out; fires on a lifecycle binding or agent-emitted.',\n\t});\n","// chains module — declarative orchestration authoring (L5 / ERA-4270) + the\n// server-side run surface.\n//\n// A `ChainSpec` is a plain, JSON-serializable orchestration of steps\n// (agentTurn / tool / branch / fanOut / waitForEvent / handoff / map). The\n// canonical form is an object (object-core, locked decision #2) that round-trips\n// byte-for-byte to/from the backend `chain_versions.content` JSONB column and\n// diffs cleanly. `defineChain()` validates + normalizes it; the fluent `chain()`\n// builder accumulates the same fields and routes through `defineChain()` on\n// `.build()`, so the two forms are deep-equal BY CONSTRUCTION.\n//\n// Authoring (`defineChain` / `chain` / `step` / `$`) is PURE — it never touches\n// the network. Only the `chains(client)` facade does: authoring CRUD/versioning\n// over hub-api (mirroring the experiences publishing lifecycle), and the runtime\n// `run`/`resume`/`send`/`getRun`/`cancel` over the ingress orchestrator (the SSE\n// source). Chains execute entirely server-side — `$.` references are author-time\n// tokens that serialize to `{\"$ref\": \"...\"}` strings the orchestrator resolves.\n//\n// Tree-shakeable subpath import: `@era-laboratories/era-sdk/modules/chains`.\n\nimport * as z from 'zod';\nimport type { CamelizeKeys } from '../core/casing.js';\nimport type { EraClient, PaginateOptions } from '../core/client.js';\nimport { EraChainError } from '../core/errors.js';\nimport type { PageEnvelope, RequestOptions } from '../core/types.js';\nimport {\n\tdeleteChainsById as genDeleteChainsById,\n\tgetChains as genGetChains,\n\tgetChainsById as genGetChainsById,\n\tgetChainsByIdPublishingVersions as genGetChainsByIdPublishingVersions,\n\tpostChains as genPostChains,\n\tpostChainsByIdPublishingVersions as genPostChainsByIdPublishingVersions,\n\tpostChainsByIdPublishingVersionsByVIdApproveReview as genPostChainsByIdPublishingVersionsByVIdApproveReview,\n\tpostChainsByIdPublishingVersionsByVIdRejectReview as genPostChainsByIdPublishingVersionsByVIdRejectReview,\n\tpostChainsByIdPublishingVersionsByVIdSubmitForReview as genPostChainsByIdPublishingVersionsByVIdSubmitForReview,\n\tpostChainsByIdRollback as genPostChainsByIdRollback,\n\tputChainsById as genPutChainsById,\n\tputChainVersionsByVId as genPutChainVersionsByVId,\n} from '../generated/hub-api/sdk.gen.js';\nimport type {\n\tPostChainsByIdPublishingVersionsByVIdApproveReviewResponses as GenApproveReviewResponses,\n\tGetChainsByIdPublishingVersionsByVIdResponses as GenGetChainsByIdPublishingVersionsByVIdResponses,\n\tGetChainsData as GenGetChainsData,\n\tPutChainVersionsByVIdResponses as GenPutChainVersionsByVIdResponses,\n\tPostChainsByIdPublishingVersionsByVIdRejectReviewResponses as GenRejectReviewResponses,\n\tPostChainsByIdPublishingVersionsByVIdSubmitForReviewResponses as GenSubmitForReviewResponses,\n\tAgentRef as HubAgentRef,\n\tAgentTurnStep as HubAgentTurnStep,\n\tBranchStep as HubBranchStep,\n\tChainSpec as HubChainSpec,\n\tChainStep as HubChainStep,\n\tChainTrigger as HubChainTrigger,\n\tChainVar as HubChainVar,\n\tFanOutStep as HubFanOutStep,\n\tHandoffStep as HubHandoffStep,\n\tMapStep as HubMapStep,\n\tPredicate as HubPredicate,\n\tRef as HubRef,\n\tToolStep as HubToolStep,\n\tWaitForEventStep as HubWaitForEventStep,\n} from '../generated/hub-api/types.gen.js';\nimport { zChainSpec } from '../generated/hub-api/zod.gen.js';\nimport {\n\tcancelRun as genCancelRun,\n\tdeliverRunEvent as genDeliverRunEvent,\n\tgetRun as genGetRun,\n\tgetRunEvents as genGetRunEvents,\n\tstartRun as genStartRun,\n} from '../generated/ingress/sdk.gen.js';\nimport type {\n\tChainAwaitingEventEvent as GenChainAwaitingEventEvent,\n\tChainEventDeliveredEvent as GenChainEventDeliveredEvent,\n\tChainHandedOffEvent as GenChainHandedOffEvent,\n\tChainOutputDeliveredEvent as GenChainOutputDeliveredEvent,\n\tChainOutputEmittedEvent as GenChainOutputEmittedEvent,\n\tChainOutputExpiredEvent as GenChainOutputExpiredEvent,\n\tChainOutputFailedEvent as GenChainOutputFailedEvent,\n\tChainOutputUndeliverableEvent as GenChainOutputUndeliverableEvent,\n\tChainRunDoneEvent as GenChainRunDoneEvent,\n\tChainRunErrorEvent as GenChainRunErrorEvent,\n\tChainRunStartedEvent as GenChainRunStartedEvent,\n\tChainRunState as GenChainRunState,\n\tChainSafetyBlockedEvent as GenChainSafetyBlockedEvent,\n\tChainStepCompletedEvent as GenChainStepCompletedEvent,\n\tChainStepFailedEvent as GenChainStepFailedEvent,\n\tChainStepStartedEvent as GenChainStepStartedEvent,\n\tChainStreamEvent as GenChainStreamEvent,\n\tDeliverEventBody as GenDeliverEventBody,\n\tOutputReceipt as GenOutputReceipt,\n} from '../generated/ingress/types.gen.js';\nimport { zChainStreamEvent } from '../generated/ingress/zod.gen.js';\nimport type { ExperienceRunEvent } from './experienceAuthoring.js';\nimport type { RejectReviewInput } from './experienceVersions.js';\nimport { hubPath } from './hubPaths.js';\nimport { ingressPath } from './ingressPaths.js';\nimport {\n\ttype OutputDef,\n\toutputDefSchema,\n\twarnSurfaceUndeliverable,\n} from './outputs.js';\n\n// ─── Authoring types ─────────────────────────────────────────────────────────\n\n/**\n * The interaction style a chain is authored for:\n *\n * - `'text'` — chat/typed interaction.\n * - `'voice'` — spoken interaction.\n * - `'auto'` — no fixed style; leave the choice to the platform.\n *\n * Set at author time on the {@link ChainSpec} / chain envelope; defaults to\n * `'auto'` when omitted. Descriptive metadata today — at run time each agent\n * turn's text/voice behavior follows that agent's experience configuration.\n */\nexport type ChainModality = 'text' | 'voice' | 'auto';\n\n/** Minimal JSON Schema carrier (a runtime schema, not a TS type). */\nexport type JsonSchema = Record<string, unknown>;\n\n/**\n * A reference token. The DATA-level shape comes from the generated hub-api\n * `Ref` (`{ $ref: string }`): at author time `$.input.city` produces `{ $ref:\n * \"$.input.city\" }`, and the stored `ChainSpec` contains only these plain\n * objects (never functions), so `JSON.parse(JSON.stringify(spec))` is identity.\n *\n * The generic `T` is author-facing documentation only — refs resolve loosely at\n * author time (a `$.<step>.output` ref is effectively `Ref<unknown>` unless a\n * typed schema threads `T` through), so `Ref<T>` is structurally just the\n * generated `{ $ref }` and any ref flows into any `Ref<…>` slot.\n * This thin author-side alias exists so the `$` proxy /\n * builder have a TS type to produce; the wire shape is the generated `HubRef`.\n */\nexport type Ref<T = unknown> = HubRef & (T extends never ? unknown : unknown);\n\n// ─── Data grammar (ERA-5830) ───────────────────────────────────────────────────\n//\n// hub-api ships the chain grammar as real OpenAPI components (ERA-5805); the SDK\n// re-exports the GENERATED `ChainSpec`/`ChainStep` (+ variants) verbatim instead\n// of hand-rolling a parallel SSOT. hub-api is the source of truth for the shape;\n// the author-time builder/proxy ergonomics below are re-typed against these.\n\n/** A declared chain input variable, addressable as `$.input.<key>`. */\nexport type ChainVar = HubChainVar;\n/** An agent reference: a published experience, an inline spec, or a named ref. */\nexport type AgentRef = HubAgentRef;\n/** A boolean predicate evaluated server-side against the `$.` context. */\nexport type Predicate = HubPredicate;\n/** Run one agent turn — utterance/prompt overlay, structured output, tool scoping. */\nexport type AgentTurnStep = HubAgentTurnStep;\n/** Invoke a tool (`'spindle-slug:tool_name'` or `'builtin:<action>'`). */\nexport type ToolStep = HubToolStep;\n/** Conditional branch — runs `then` or `else` based on a predicate. */\nexport type BranchStep = HubBranchStep;\n/**\n * Run named branches and collect their keyed results. NOTE: the orchestrator\n * executes branches **sequentially** today — `join: 'all'` (the default) is\n * honored; `join: 'race' | 'allSettled'` fail the step at runtime.\n * Bounded-parallel fan-out with real join semantics is planned.\n */\nexport type FanOutStep = HubFanOutStep;\n/** Suspend the run until a named event arrives (or times out). */\nexport type WaitForEventStep = HubWaitForEventStep;\n/** Hand the run off to a human or another agent, then resume. */\nexport type HandoffStep = HubHandoffStep;\n/** Iterate a collection, running `do` for each element. */\nexport type MapStep = HubMapStep;\n/**\n * What fires a chain: the chain-canonical trigger declaration bound\n * via `.trigger()` / `defineChain({ trigger })`. `kind:'button'` (with an\n * optional `gesture`) or `kind:'voice'`. For button triggers, device→chain\n * routing follows a ladder: an **explicit binding**\n * (`devices.bindInput(deviceId, { inputSlot, gesture })`) beats\n * **implicit resolution** by `kind` + the device's selected experience +\n * `gesture`. The binding's `inputSlot` is matched against the id the device\n * reports in `data.input` — never against this trigger's `slot`, which is\n * documentation of intent, not a matcher (and the bound chain need not\n * declare a trigger at all; input slots also differ from output slots, which\n * resolve to a `deviceId` at emit time). Voice triggers never use\n * bindings: routing matches the utterance against the selected experience's\n * voice commands (spoken phrase → chain) first, then falls back to implicit\n * resolution.\n * A {@link TriggerDef} (what the `triggers.*` builders produce) is structurally\n * identical, so a `triggers.button()` helper value flows straight into\n * `.trigger()`.\n */\nexport type ChainTrigger = HubChainTrigger;\n/** One step in a chain — discriminated on `type` over the seven variants. */\nexport type ChainStep = HubChainStep;\n\n/**\n * Canonical, serializable chain definition. Round-trips to chain_versions.content\n * JSONB. The grammar (name/steps/input/defaultAgent/limits/specVersion) is the\n * generated hub-api `ChainSpec`; the `outputs` slot is narrowed to the SDK-native\n * {@link OutputDef} union so the `outputs.physical/surface` factories,\n * capability `tier`, and author-time validation keep their richer typing (the\n * wire shape is identical — the generated `outputs` is just looser).\n */\nexport type ChainSpec = Omit<HubChainSpec, 'outputs'> & {\n\t/**\n\t * Outputs that inform the user on the run lifecycle (`on:`\n\t * start/success/failure), fanned out by the orchestrator's\n\t * `builtin:emit-output`. Lowered verbatim into `content.outputs`.\n\t * Both `surface` and `physical` are supported — a `physical` output's `slot`\n\t * is resolved to a concrete `deviceId` server-side at emit time,\n\t * against the caller's owner-scoped device bindings.\n\t */\n\toutputs?: OutputDef[];\n};\n\n// ─── The typed `$.` reference proxy ────────────────────────────────────────────\n\n/** A traversable, serializable reference node (`$.a.b.c`). */\nexport type RefNode = Ref<unknown> & { readonly [key: string]: RefNode };\n\n// ── Typed `expect` handoff (ERA-5919) ──\n//\n// `expect` on an agentTurn is enforced at RUN time by the orchestrator\n// (OUTPUT_CONTRACT_VIOLATION, ERA-5815); authoring-side it can ALSO thread the\n// schema's inferred output type into the step's output binding. Pass a Zod\n// schema (bare, or wrapped as `{ schema, name? }`) and downstream\n// `$.steps.<id>.output.<field>` — plus the `$.<id>` / `as`-alias forms —\n// autocompletes, and a path the schema does not declare is a COMPILE error.\n// The wire format is untouched: the Zod schema is lowered to plain JSON Schema\n// at author time (`normalizeExpect`), so the stored spec carries the same\n// `expect: { schema, name? }` JSON the orchestrator already validates.\n// Chains that never pass a Zod `expect` keep the loose default `Ctx` below,\n// so existing authoring code compiles unchanged.\n\n/**\n * What `agentTurn.expect` accepts at author time: the wire `{ schema, name? }`\n * with a plain JSON Schema (output binding stays `Ref<unknown>`), a Zod schema\n * (typed output binding), or the wrapper carrying a Zod schema (typed +\n * named contract). Zod values are lowered to JSON Schema on build — the\n * serialized spec never contains a Zod object.\n */\nexport type ExpectInput =\n\t| z.ZodType\n\t| { schema: z.ZodType | JsonSchema; name?: string };\n\n/**\n * The output type an `expect` value implies for the step's output binding:\n * `z.infer<S>` for a Zod schema (bare or wrapped), `unknown` otherwise.\n */\nexport type InferExpect<E> =\n\tE extends z.ZodType<infer T>\n\t\t? T\n\t\t: E extends { schema: z.ZodType<infer T> }\n\t\t\t? T\n\t\t\t: unknown;\n\n/**\n * A {@link Ref} whose downstream field accesses follow `T`'s structure:\n * `TypedRef<{ n: number }>` allows `.n` (a `TypedRef<number>`) and rejects any\n * other key at compile time. `TypedRef<unknown>` degrades to the loose\n * {@link RefNode}, so untyped bindings keep today's anything-goes traversal.\n */\nexport type TypedRef<T> = unknown extends T\n\t? RefNode\n\t: Ref<T> &\n\t\t\t(NonNullable<T> extends readonly (infer E)[]\n\t\t\t\t? { readonly [index: number]: TypedRef<E> }\n\t\t\t\t: NonNullable<T> extends object\n\t\t\t\t\t? {\n\t\t\t\t\t\t\treadonly [K in keyof NonNullable<T> & string]-?: TypedRef<\n\t\t\t\t\t\t\t\tNonNullable<T>[K]\n\t\t\t\t\t\t\t>;\n\t\t\t\t\t\t}\n\t\t\t\t\t: unknown);\n\n/**\n * A step's binding node (`$.steps.<id>`, `$.<id>`, or the `as` alias): its\n * `output` is a {@link TypedRef} of the step's inferred output type. Untyped\n * steps (`T = unknown`) stay a loose {@link RefNode}.\n */\nexport type StepBinding<T> = unknown extends T\n\t? RefNode\n\t: Ref<T> & { readonly output: TypedRef<T> };\n\n/**\n * Author-time context bag (`$`). Every access produces a {@link RefNode}:\n * `$.input.*`, `$.steps.<id>.output`, `$.<alias>.output`, and run built-ins\n * (`$.now`, `$.today_start`, `$.session.id`, `$.run.id`, `$.run.kernelId`).\n *\n * Typing is loose BY DEFAULT (design note): `$.<step>.output` is\n * `Ref<unknown>` unless a typed schema threads `T` through. That threading is\n * the `Outputs` parameter: once the fluent builder has seen a Zod\n * `expect`, closures receive `Ctx<Outputs>` where each declared binding —\n * `$.steps.<id>` plus the top-level `$.<id>` / `as`-alias forms — is a\n * {@link StepBinding} of its inferred type, and unknown names are compile\n * errors. `defineChain` closures and untyped chains keep the loose shape.\n */\nexport type Ctx<Outputs extends Record<string, unknown> = never> = [\n\tOutputs,\n] extends [never]\n\t? { readonly [key: string]: RefNode }\n\t: {\n\t\t\treadonly steps: {\n\t\t\t\treadonly [K in keyof Outputs & string]: StepBinding<Outputs[K]>;\n\t\t\t};\n\t\t\treadonly input: RefNode;\n\t\t\treadonly now: RefNode;\n\t\t\treadonly today_start: RefNode;\n\t\t\treadonly session: RefNode;\n\t\t\treadonly run: RefNode;\n\t\t} & { readonly [K in keyof Outputs & string]: StepBinding<Outputs[K]> };\n\nconst REF_TO_JSON = 'toJSON';\n\n/** Build a Proxy ref node rooted at `path`; child access extends the path. */\nfunction makeRef(path: string): RefNode {\n\tconst target = { $ref: path };\n\treturn new Proxy(target, {\n\t\tget(_t, prop): unknown {\n\t\t\tif (prop === '$ref') return path;\n\t\t\tif (prop === REF_TO_JSON) return () => ({ $ref: path });\n\t\t\t// Guard against being treated as a thenable / primitive.\n\t\t\tif (typeof prop === 'symbol') return undefined;\n\t\t\treturn makeRef(`${path}.${prop}`);\n\t\t},\n\t\t// Refs are read-only author tokens.\n\t\tset() {\n\t\t\treturn false;\n\t\t},\n\t}) as unknown as RefNode;\n}\n\n/**\n * Root authoring context (`$`). Exported so other authoring modules (guardrails'\n * `neverCallWithout`, the context gate) can build serializable `$.`-ref\n * predicates with the same proxy.\n */\nexport function makeCtx(): Ctx {\n\treturn makeRef('$') as unknown as Ctx;\n}\n\n// ─── Step factories (pure constructors) ────────────────────────────────────────\n\n/**\n * `agentTurn` authoring options — the wire step minus `type`/`id`, with\n * `expect` widened to {@link ExpectInput} so a Zod schema can be passed\n * directly.\n */\nexport type AgentTurnOptions = Omit<AgentTurnStep, 'type' | 'id' | 'expect'> & {\n\texpect?: ExpectInput;\n};\n\n/**\n * A Zod schema, robust across duplicated zod copies: `instanceof` first, then\n * the zod-4 `_zod` internals marker (never present on a plain JSON Schema\n * object or the `{ schema, name? }` wrapper).\n */\nfunction isZodSchema(v: unknown): v is z.ZodType {\n\treturn (\n\t\tv instanceof z.ZodType ||\n\t\t(typeof v === 'object' && v !== null && '_zod' in v)\n\t);\n}\n\n/**\n * Lower an author-time `expect` to the wire `{ schema, name? }` shape\n * (ERA-5919): Zod schemas (bare or wrapped) are converted to plain JSON Schema\n * via `z.toJSONSchema`; an already-wire-shaped value passes through untouched,\n * so the serialized format is exactly what the orchestrator validated before.\n */\nfunction normalizeExpect(e: ExpectInput): NonNullable<AgentTurnStep['expect']> {\n\tif (isZodSchema(e)) return { schema: z.toJSONSchema(e) as JsonSchema };\n\tif (isZodSchema(e.schema)) {\n\t\treturn {\n\t\t\tschema: z.toJSONSchema(e.schema) as JsonSchema,\n\t\t\t...(e.name !== undefined && { name: e.name }),\n\t\t};\n\t}\n\treturn e as NonNullable<AgentTurnStep['expect']>;\n}\n\n/**\n * Pure step constructors — one factory per {@link ChainStep} variant. Each takes\n * a single options object (which must include the step `id`) and stamps the\n * discriminating `type`, returning a plain, JSON-serializable step you drop into\n * a {@link defineChain} `steps` array. No network, no validation beyond typing —\n * the whole spec is validated together by {@link defineChain} / `chain().build()`.\n *\n * @remarks\n * `step.agentTurn` additionally accepts a Zod schema (bare or `{ schema, name? }`)\n * as its `expect` and lowers it to plain JSON Schema at build time for a typed\n * output binding; every other factory forwards its options verbatim.\n *\n * `fanOut` branches and `map` iterations execute **sequentially** server-side\n * today — they are accepted at author time, but `join: 'race' | 'allSettled'`\n * and `map` `concurrency > 1` fail the step at runtime (only `join: 'all'`\n * semantics are provided). A suspending step (`waitForEvent` / `handoff`)\n * nested inside `map`/`fanOut` cannot resume; keep suspends at the top level.\n *\n * @example\n * ```ts\n * import { defineChain, step } from '@era-laboratories/era-sdk';\n *\n * const spec = defineChain({\n * name: 'Research + Draft',\n * steps: ($) => [\n * step.agentTurn({ id: 'research', agent: { experienceId: 'exp_0000000000' }, prompt: $.input.topic }),\n * step.agentTurn({ id: 'draft', agent: { experienceId: 'exp_0000000000' }, prompt: $.research.output }),\n * ],\n * });\n * ```\n */\nexport const step = {\n\tagentTurn: (o: AgentTurnOptions & { id: string }): AgentTurnStep => {\n\t\tconst { expect, ...rest } = o;\n\t\treturn {\n\t\t\ttype: 'agentTurn',\n\t\t\t...rest,\n\t\t\t...(expect !== undefined && { expect: normalizeExpect(expect) }),\n\t\t};\n\t},\n\ttool: (o: Omit<ToolStep, 'type'>): ToolStep => ({ type: 'tool', ...o }),\n\tbranch: (o: Omit<BranchStep, 'type'>): BranchStep => ({\n\t\ttype: 'branch',\n\t\t...o,\n\t}),\n\tfanOut: (o: Omit<FanOutStep, 'type'>): FanOutStep => ({\n\t\ttype: 'fanOut',\n\t\t...o,\n\t}),\n\twaitForEvent: (o: Omit<WaitForEventStep, 'type'>): WaitForEventStep => ({\n\t\ttype: 'waitForEvent',\n\t\t...o,\n\t}),\n\thandoff: (o: Omit<HandoffStep, 'type'>): HandoffStep => ({\n\t\ttype: 'handoff',\n\t\t...o,\n\t}),\n\tmap: (o: Omit<MapStep, 'type'>): MapStep => ({ type: 'map', ...o }),\n};\n\n/**\n * Reference a rostered agent by name — sugar for `{ ref: name }`. The name must\n * be declared in the chain's `agents` roster (`.agents({...})` / the `agents`\n * field); `defineChain` rejects an unrostered ref at author time, mirroring the\n * publish-time `unresolved_agent_reference` block.\n */\nexport function ref(name: string): AgentRef {\n\treturn { ref: name };\n}\n\n/**\n * Reference a published experience directly — `{ experienceId, versionId? }`.\n * Omit `versionId` to let publish pin the experience's current version.\n */\nexport function agent(experienceId: string, versionId?: string): AgentRef {\n\treturn versionId ? { experienceId, versionId } : { experienceId };\n}\n\n/**\n * Recursively walk the (already `zChainSpec`-validated) step tree and enforce\n * step-id uniqueness across the whole chain, including nested branch/fanOut/map/\n * waitForEvent bodies. This is a cross-step REFINEMENT that JSON Schema (and thus\n * `zChainSpec`) cannot express, so it stays an explicit post-parse check.\n */\nfunction collectStepIds(steps: ChainStep[], seen: Set<string>): void {\n\tfor (const s of steps) {\n\t\tif (seen.has(s.id)) {\n\t\t\tthrow new TypeError(`duplicate chain step id \"${s.id}\"`);\n\t\t}\n\t\tseen.add(s.id);\n\t\tif (s.type === 'branch') {\n\t\t\tcollectStepIds(s.then, seen);\n\t\t\tif (s.else) collectStepIds(s.else, seen);\n\t\t} else if (s.type === 'fanOut') {\n\t\t\tfor (const b of s.branches) collectStepIds(b.steps, seen);\n\t\t} else if (s.type === 'map') {\n\t\t\tcollectStepIds(s.do, seen);\n\t\t} else if (s.type === 'waitForEvent') {\n\t\t\tif (s.timeout?.onTimeout) collectStepIds(s.timeout.onTimeout, seen);\n\t\t}\n\t}\n}\n\n/** The `{ ref }` name of an AgentRef (or null for experienceId/spec/`'human'`). */\nfunction refNameOf(a: AgentRef | 'human' | undefined): string | null {\n\tif (a && typeof a === 'object' && 'ref' in a && typeof a.ref === 'string') {\n\t\treturn a.ref;\n\t}\n\treturn null;\n}\n\n/**\n * Collect the names of every `{ ref }` AgentRef used across the step tree\n * (agentTurn.agent + handoff.to) so `defineChain` can verify each resolves\n * against the declared `agents` roster (ERA-5918).\n */\nfunction collectAgentRefNames(steps: ChainStep[], out: Set<string>): void {\n\tfor (const s of steps) {\n\t\tif (s.type === 'agentTurn') {\n\t\t\tconst n = refNameOf(s.agent);\n\t\t\tif (n) out.add(n);\n\t\t} else if (s.type === 'handoff') {\n\t\t\tconst n = refNameOf(s.to);\n\t\t\tif (n) out.add(n);\n\t\t}\n\t\tif (s.type === 'branch') {\n\t\t\tcollectAgentRefNames(s.then, out);\n\t\t\tif (s.else) collectAgentRefNames(s.else, out);\n\t\t} else if (s.type === 'fanOut') {\n\t\t\tfor (const b of s.branches) collectAgentRefNames(b.steps, out);\n\t\t} else if (s.type === 'map') {\n\t\t\tcollectAgentRefNames(s.do, out);\n\t\t} else if (s.type === 'waitForEvent') {\n\t\t\tif (s.timeout?.onTimeout) collectAgentRefNames(s.timeout.onTimeout, out);\n\t\t}\n\t}\n}\n\n/**\n * Validate chain `outputs` (ERA-4453). Each entry is structurally checked\n * against `outputDefSchema`. Both `surface` and `physical` transports are\n * accepted; a `physical` output's `slot` is resolved to a concrete `deviceId`\n * server-side at emit time (ERA-4500).\n */\nfunction validateChainOutputs(outputs: OutputDef[] | undefined): void {\n\tif (outputs === undefined) return;\n\tif (!Array.isArray(outputs)) {\n\t\tthrow new TypeError('defineChain: `outputs` must be an array');\n\t}\n\toutputs.forEach((o, i) => {\n\t\tconst parsed = outputDefSchema.safeParse(o);\n\t\tif (!parsed.success) {\n\t\t\tthrow new TypeError(\n\t\t\t\t`defineChain: outputs[${i}] is not a valid OutputDef — ${parsed.error.message}`,\n\t\t\t);\n\t\t}\n\t});\n\t// ERA-6036: one-shot advisory if the spec authors a `surface` output the\n\t// author may not have built via the factory directly. Never throws.\n\tif (\n\t\toutputs.some((o) => (o as OutputDef | undefined)?.transport === 'surface')\n\t) {\n\t\twarnSurfaceUndeliverable();\n\t}\n}\n\ntype StepsInput = ChainStep[] | ((ctx: Ctx) => ChainStep[]);\n\n/**\n * Validate + normalize a chain definition into a frozen, fully-serialized\n * `ChainSpec`. When `steps` is an authoring closure it is invoked ONCE with the\n * `$` proxy; the result is materialized to a plain `ChainStep[]` (all `$.`\n * tokens collapsed to `{\"$ref\":...}`, no functions) via a JSON round-trip, so\n * the stored spec round-trips byte-for-byte and diffs cleanly.\n *\n * The structural shape is validated against `zChainSpec` (the same grammar the\n * platform validates server-side): a malformed step (wrong/unknown discriminator,\n * missing required field, bad type) is rejected here. The SDK-native `outputs`\n * rule (strict `OutputDef`) runs first so its richer\n * messages win; the one check `zChainSpec` can't express — cross-step id\n * uniqueness — stays as an explicit post-parse refinement ({@link collectStepIds}).\n */\nexport function defineChain(\n\tspec: Omit<ChainSpec, 'specVersion' | 'steps'> & { steps: StepsInput },\n): ChainSpec {\n\tif (typeof spec.name !== 'string' || spec.name.trim().length === 0) {\n\t\tthrow new TypeError('defineChain: `name` must be a non-empty string');\n\t}\n\tconst rawSteps =\n\t\ttypeof spec.steps === 'function' ? spec.steps(makeCtx()) : spec.steps;\n\tif (!Array.isArray(rawSteps)) {\n\t\tthrow new TypeError('defineChain: `steps` must resolve to an array');\n\t}\n\tconst draft: ChainSpec = {\n\t\t...spec,\n\t\tsteps: rawSteps,\n\t\tspecVersion: 1,\n\t};\n\t// Materialize: collapse proxies → {\"$ref\"} plain objects and strip any\n\t// functions, guaranteeing JSON identity for clean version diffs.\n\tconst materialized = JSON.parse(JSON.stringify(draft)) as ChainSpec;\n\tif (materialized.steps.length === 0) {\n\t\tthrow new TypeError('defineChain: a chain needs at least one step');\n\t}\n\t// SDK-native output rule first (strict OutputDef) so\n\t// their richer errors surface before the generic grammar gate.\n\tvalidateChainOutputs(materialized.outputs);\n\t// Validate the data shape against the generated hub-api grammar. Use\n\t// safeParse + return the original `materialized` (not `result.data`) so the\n\t// spec stays byte-identical — `z.object` would otherwise strip keys.\n\t// ERA-6191/ERA-6201: `outputs` now flow through the generated pass. The\n\t// promoted hub-api spec's `zOutputDef` accepts the slot-XOR-deviceId contract\n\t// (deviceId-only outputs, ERA-6191), so the former exclusion is retired. The\n\t// generated grammar is a superset of the SDK-native `outputDefSchema` run\n\t// above (which enforces exactly-one-target), so anything that passed the\n\t// strict check passes here too.\n\tconst result = zChainSpec.safeParse(materialized);\n\tif (!result.success) {\n\t\tconst detail = result.error.issues\n\t\t\t.map((i) => `${i.path.join('.') || '<root>'}: ${i.message}`)\n\t\t\t.join('; ');\n\t\tthrow new TypeError(`defineChain: invalid chain spec — ${detail}`);\n\t}\n\tcollectStepIds(materialized.steps, new Set<string>());\n\t// ERA-5918: every `{ ref }` AgentRef must resolve against the declared\n\t// `agents` roster — rejected here at author time, mirroring the hub's\n\t// publish-time `unresolved_agent_reference` block.\n\tconst refNames = new Set<string>();\n\tcollectAgentRefNames(materialized.steps, refNames);\n\tconst defaultRef = refNameOf(materialized.defaultAgent);\n\tif (defaultRef) refNames.add(defaultRef);\n\tconst roster = materialized.agents ?? {};\n\tconst unresolved = [...refNames].filter((n) => !(n in roster));\n\tif (unresolved.length > 0) {\n\t\tthrow new TypeError(\n\t\t\t`defineChain: unresolved agent ref(s) ${unresolved\n\t\t\t\t.map((n) => `\"${n}\"`)\n\t\t\t\t.join(', ')} — declare them in the \\`agents\\` roster ` +\n\t\t\t\t'(`.agents({...})` or the `agents` field).',\n\t\t);\n\t}\n\treturn Object.freeze(materialized);\n}\n\n// ─── Fluent builder (`chain(name)`) ────────────────────────────────────────────\n\n/** A value, or an `($) => value` author closure resolved at build time. */\ntype OrCtx<T> = T | ((ctx: Ctx) => T);\n\nfunction resolveCtx<T>(v: OrCtx<T>, ctx: Ctx): T {\n\treturn typeof v === 'function' ? (v as (c: Ctx) => T)(ctx) : v;\n}\n\n/**\n * Fluent sub-builder for a nested step list — the `then`/`else` body of a\n * {@link BranchStep} passed to {@link ChainBuilder.branch}. Each method appends\n * one step and returns `this` for chaining; the parent builder collects the\n * accumulated steps. Options may be a literal or an `($) => options` closure\n * that receives the `$` ref proxy for referencing inputs/other steps.\n *\n * @remarks\n * Nested bodies intentionally stay loosely typed — their step ids are not\n * registered in the parent's typed-output map, so closures here always receive\n * the loose {@link Ctx}. `fanOut` and `map` have no sub-builder methods; author\n * those with the {@link step} factories directly.\n */\nexport interface SubChainBuilder {\n\t/** Append an `agentTurn` step with the given `id`; `o` sets prompt/agent/expect/etc. */\n\tagentTurn(id: string, o?: OrCtx<AgentTurnOptions>): this;\n\t/** Append a `tool` step invoking `'spindle-slug:tool_name'` or `'builtin:<action>'`. */\n\ttool(id: string, o: OrCtx<Omit<ToolStep, 'type' | 'id'>>): this;\n\t/** Append a nested `branch` step: run `then` (or `els`) based on the `when` predicate. */\n\tbranch(\n\t\tid: string,\n\t\twhen: (ctx: Ctx) => Predicate,\n\t\tthen: (b: SubChainBuilder) => void,\n\t\tels?: (b: SubChainBuilder) => void,\n\t): this;\n\t/** Append a `handoff` step; defaults to `{ to: 'human' }` when `o` is omitted. */\n\thandoff(id: string, o?: OrCtx<Omit<HandoffStep, 'type' | 'id'>>): this;\n\t/** Append a `waitForEvent` step that suspends the run until `event` arrives (or times out). */\n\twaitForEvent(\n\t\tid: string,\n\t\to: OrCtx<Omit<WaitForEventStep, 'type' | 'id'>>,\n\t): this;\n}\n\nclass StepListBuilder implements SubChainBuilder {\n\tprotected steps: ChainStep[] = [];\n\tconstructor(protected ctx: Ctx) {}\n\n\tagentTurn(id: string, o?: OrCtx<AgentTurnOptions>): this {\n\t\tconst body = o ? resolveCtx(o, this.ctx) : {};\n\t\tthis.steps.push(step.agentTurn({ id, ...body }));\n\t\treturn this;\n\t}\n\ttool(id: string, o: OrCtx<Omit<ToolStep, 'type' | 'id'>>): this {\n\t\tthis.steps.push(step.tool({ id, ...resolveCtx(o, this.ctx) }));\n\t\treturn this;\n\t}\n\thandoff(id: string, o?: OrCtx<Omit<HandoffStep, 'type' | 'id'>>): this {\n\t\tif (o) {\n\t\t\tthis.steps.push(step.handoff({ id, ...resolveCtx(o, this.ctx) }));\n\t\t} else {\n\t\t\tthis.steps.push(step.handoff({ id, to: 'human' }));\n\t\t}\n\t\treturn this;\n\t}\n\twaitForEvent(\n\t\tid: string,\n\t\to: OrCtx<Omit<WaitForEventStep, 'type' | 'id'>>,\n\t): this {\n\t\tthis.steps.push(step.waitForEvent({ id, ...resolveCtx(o, this.ctx) }));\n\t\treturn this;\n\t}\n\tbranch(\n\t\tid: string,\n\t\twhen: (ctx: Ctx) => Predicate,\n\t\tthen: (b: SubChainBuilder) => void,\n\t\tels?: (b: SubChainBuilder) => void,\n\t): this {\n\t\tconst thenB = new StepListBuilder(this.ctx);\n\t\tthen(thenB);\n\t\tconst branchStep: Omit<BranchStep, 'type'> = {\n\t\t\tid,\n\t\t\twhen: when(this.ctx),\n\t\t\t// biome-ignore lint/suspicious/noThenProperty: `then` is the canonical BranchStep field (plain orchestration data, never awaited).\n\t\t\tthen: thenB.collect(),\n\t\t};\n\t\tif (els) {\n\t\t\tconst elseB = new StepListBuilder(this.ctx);\n\t\t\tels(elseB);\n\t\t\tbranchStep.else = elseB.collect();\n\t\t}\n\t\tthis.steps.push(step.branch(branchStep));\n\t\treturn this;\n\t}\n\tcollect(): ChainStep[] {\n\t\treturn this.steps;\n\t}\n}\n\n/**\n * The concrete fluent chain builder behind {@link chain}. Accumulates spec-level\n * fields (name/modality/input/agents/limits/trigger/outputs) plus a step list,\n * and `.build()` routes them through {@link defineChain} — so a chain authored\n * fluently and one authored declaratively are deep-equal by construction.\n *\n * @remarks\n * Authoring is pure: no method here touches the network. `chain(name)` hands\n * back the {@link TypedChainBuilder} view of this same runtime object, which\n * carries the typed-output generics; the class is the single implementation.\n * Reach it via the {@link chain} entrypoint rather than constructing directly.\n */\nexport class ChainBuilder extends StepListBuilder {\n\tprivate spec: Omit<ChainSpec, 'specVersion' | 'steps'>;\n\t/**\n\t * Create a builder for a named chain. Prefer the {@link chain} entrypoint,\n\t * which constructs this for you.\n\t *\n\t * @param name - Chain name shown in run history and referenced by triggers.\n\t */\n\tconstructor(name: string) {\n\t\tsuper(makeCtx());\n\t\tthis.spec = { name };\n\t}\n\t/** Set the run modality — `'text' | 'voice' | 'auto'` (see {@link ChainModality}); default `'auto'`. */\n\tmodality(m: ChainModality): this {\n\t\tthis.spec.modality = m;\n\t\treturn this;\n\t}\n\t/** Set the chain's human-readable description. */\n\tdescription(d: string): this {\n\t\tthis.spec.description = d;\n\t\treturn this;\n\t}\n\t/** Declare the chain's input schema — `$.input.<key>` refs resolve against these {@link ChainVar} entries. */\n\tinput(input: Record<string, ChainVar>): this {\n\t\tthis.spec.input = input;\n\t\treturn this;\n\t}\n\t/** Pin the whole chain to one experience: sets the fallback agent for `agentTurn` steps that omit `agent`, freezing the run's persona to that experience's published version at chain-publish time. */\n\tdefaultAgent(a: AgentRef): this {\n\t\tthis.spec.defaultAgent = a;\n\t\treturn this;\n\t}\n\t/**\n\t * Declare the agent roster that `{ ref }` AgentRefs resolve against.\n\t * Publishing resolves each entry and pins it to a concrete experience version.\n\t */\n\tagents(roster: Record<string, AgentRef>): this {\n\t\tthis.spec.agents = roster;\n\t\treturn this;\n\t}\n\t/** Set run resource ceilings (e.g. `maxSteps` / `maxToolCalls` / `deadlineMs`); the orchestrator fails the run once a ceiling is exceeded. */\n\tlimits(l: ChainSpec['limits']): this {\n\t\tthis.spec.limits = l;\n\t\treturn this;\n\t}\n\t/** Run-lifecycle outputs — both `surface` and `physical` transports (see {@link ChainSpec.outputs}). */\n\toutputs(outputs: OutputDef[]): this {\n\t\tthis.spec.outputs = outputs;\n\t\treturn this;\n\t}\n\t/**\n\t * Declare what fires this chain. Pass a {@link ChainTrigger} or a\n\t * `triggers.*` helper value — they share the wire shape:\n\t *\n\t * ```ts\n\t * import { chain, triggers } from '@era-laboratories/era-sdk';\n\t *\n\t * const c = chain('doorbell')\n\t * .trigger(triggers.button({ slot: 'front-door', gesture: 'press' }))\n\t * .agentTurn('greet', { prompt: 'Someone is at the door.' })\n\t * .build();\n\t * // c.trigger === { kind: 'button', slot: 'front-door', gesture: 'press' }\n\t * ```\n\t *\n\t * The `slot` names this chain's expected input (documentation of intent —\n\t * routing never matches on it). For button triggers, route a device input\n\t * to this chain explicitly with `devices.bindInput(...)`;\n\t * unbound devices fall back to implicit resolution by `kind` + selected\n\t * experience + `gesture` (see {@link ChainTrigger}). Omit the trigger to\n\t * leave the chain untriggered.\n\t */\n\ttrigger(t: ChainTrigger): this {\n\t\tthis.spec.trigger = t;\n\t\treturn this;\n\t}\n\t/** Materialize the identical `ChainSpec` `defineChain` would produce. */\n\tbuild(): ChainSpec {\n\t\treturn defineChain({ ...this.spec, steps: this.collect() });\n\t}\n}\n\n// ── Typed fluent surface (ERA-5919) ──\n//\n// `chain()` hands back this interface instead of the raw {@link ChainBuilder}\n// class. It is the SAME runtime object with the SAME method set — the class\n// stays the single implementation — but each step method threads two pieces of\n// type state through the fluent chain:\n//\n// - `Outputs`: binding name → inferred output type. Every step registers its\n// `id` (and `as` alias when the options are a literal), typed by\n// {@link InferExpect} for agentTurn `expect`, `unknown` otherwise.\n// - `Typed`: flips to `true` on the first `expect` that actually infers a\n// type. Until then closures receive the loose default {@link Ctx} — a chain\n// that never passes a Zod `expect` behaves exactly as before. Once typed,\n// closures receive `Ctx<Outputs>`, where a ref to a field the schema does\n// not declare is a compile error.\n//\n// Nested {@link SubChainBuilder} bodies intentionally stay loose: their step\n// ids register in no map, and their closures keep the default `Ctx`.\n\n/** Builder type-state: binding name → inferred step output type. */\ntype StepOutputs = Record<string, unknown>;\n\n/** The output type a step's options imply (via `expect`), else `unknown`. */\ntype OutputOf<O> = O extends { expect: infer E } ? InferExpect<E> : unknown;\n\n/** Whether {@link OutputOf} inferred a real type (flips the builder typed). */\ntype HasTypedOutput<O> = unknown extends OutputOf<O> ? false : true;\n\n/** Extra `alias → T` entry when the options carry a literal `as` alias. */\ntype AliasBinding<O, T> = O extends { as: infer N }\n\t? N extends string\n\t\t? string extends N\n\t\t\t? Record<never, never>\n\t\t\t: { readonly [K in N]: T }\n\t\t: Record<never, never>\n\t: Record<never, never>;\n\n/** The `$` closures see: loose {@link Ctx} until the chain is typed. */\ntype BuilderCtx<\n\tS extends StepOutputs,\n\tTyped extends boolean,\n> = Typed extends true ? Ctx<S> : Ctx;\n\n/**\n * The typed fluent chain surface returned by {@link chain}. Same\n * methods (and same runtime object) as {@link ChainBuilder}; the generics\n * carry which step outputs are typed — see the section note above. Keep this\n * interface in lockstep with the class.\n */\nexport interface TypedChainBuilder<\n\tS extends StepOutputs = Record<never, never>,\n\tTyped extends boolean = false,\n> {\n\t/** See {@link ChainBuilder.modality}. */\n\tmodality(m: ChainModality): this;\n\t/** See {@link ChainBuilder.description}. */\n\tdescription(d: string): this;\n\t/** See {@link ChainBuilder.input}. */\n\tinput(input: Record<string, ChainVar>): this;\n\t/** See {@link ChainBuilder.defaultAgent}. */\n\tdefaultAgent(a: AgentRef): this;\n\t/** See {@link ChainBuilder.agents}. */\n\tagents(roster: Record<string, AgentRef>): this;\n\t/** See {@link ChainBuilder.limits}. */\n\tlimits(l: ChainSpec['limits']): this;\n\t/** See {@link ChainBuilder.outputs}. */\n\toutputs(outputs: OutputDef[]): this;\n\t/** See {@link ChainBuilder.trigger}. */\n\ttrigger(t: ChainTrigger): this;\n\t/** Materialize the identical `ChainSpec` `defineChain` would produce. */\n\tbuild(): ChainSpec;\n\n\t/**\n\t * Run one agent turn. Pass a Zod schema (bare or `{ schema, name? }`) as\n\t * `expect` and this step's output binding is typed with the schema's\n\t * inferred type downstream.\n\t */\n\tagentTurn<\n\t\tId extends string,\n\t\tconst O extends AgentTurnOptions = Record<never, never>,\n\t>(\n\t\tid: Id,\n\t\to?: O | ((ctx: BuilderCtx<S, Typed>) => O),\n\t): TypedChainBuilder<\n\t\tS & Record<Id, OutputOf<O>> & AliasBinding<O, OutputOf<O>>,\n\t\tTyped extends true ? true : HasTypedOutput<O>\n\t>;\n\t/** Append a `tool` step invoking `'spindle-slug:tool_name'` or `'builtin:<action>'`; its output binding stays untyped. */\n\ttool<Id extends string, const O extends Omit<ToolStep, 'type' | 'id'>>(\n\t\tid: Id,\n\t\to: O | ((ctx: BuilderCtx<S, Typed>) => O),\n\t): TypedChainBuilder<\n\t\tS & Record<Id, unknown> & AliasBinding<O, unknown>,\n\t\tTyped\n\t>;\n\t/** Append a `handoff` step; defaults to `{ to: 'human' }` when `o` is omitted. */\n\thandoff<\n\t\tId extends string,\n\t\tconst O extends Omit<HandoffStep, 'type' | 'id'> = { to: 'human' },\n\t>(\n\t\tid: Id,\n\t\to?: O | ((ctx: BuilderCtx<S, Typed>) => O),\n\t): TypedChainBuilder<\n\t\tS & Record<Id, unknown> & AliasBinding<O, unknown>,\n\t\tTyped\n\t>;\n\t/** Append a `waitForEvent` step that suspends the run until `event` arrives (or times out). */\n\twaitForEvent<\n\t\tId extends string,\n\t\tconst O extends Omit<WaitForEventStep, 'type' | 'id'>,\n\t>(\n\t\tid: Id,\n\t\to: O | ((ctx: BuilderCtx<S, Typed>) => O),\n\t): TypedChainBuilder<\n\t\tS & Record<Id, unknown> & AliasBinding<O, unknown>,\n\t\tTyped\n\t>;\n\t/** Append a `branch` step: run `then` (or `els`) based on the `when` predicate over the `$` context. */\n\tbranch<Id extends string>(\n\t\tid: Id,\n\t\twhen: (ctx: BuilderCtx<S, Typed>) => Predicate,\n\t\tthen: (b: SubChainBuilder) => void,\n\t\tels?: (b: SubChainBuilder) => void,\n\t): TypedChainBuilder<S & Record<Id, unknown>, Typed>;\n}\n\n/**\n * Fluent entrypoint — `chain('name').agentTurn(...).build()`.\n *\n * Returns the {@link TypedChainBuilder} view of a {@link ChainBuilder}: pass a\n * Zod schema as an agentTurn `expect` and downstream `$.steps.<id>.output`\n * refs are typed by the schema; without one, everything stays the\n * loose `Ref<unknown>` shape it always was.\n */\nexport function chain(name: string): TypedChainBuilder {\n\treturn new ChainBuilder(name) as unknown as TypedChainBuilder;\n}\n\n// ─── Facade resource + runtime types ───────────────────────────────────────────\n\n/** A chain row (metadata + lifecycle pointers), mirrors `Experience`. */\nexport interface Chain {\n\t/** Chain id. */\n\tid: string;\n\t/** Chain name. */\n\tname: string;\n\t/** Optional description, or `null`. */\n\tdescription: string | null;\n\t/** Modality the chain runs in. */\n\tmodality: ChainModality;\n\t/** Id of the resource that owns this chain, or `null`. Find ids via `resources.mine()` or `era.scope()`. */\n\tresourceId: string | null;\n\t/** Current draft version id, or `null` if none. */\n\tcurrentDraftVersionId: string | null;\n\t/** Current published version id, or `null` if unpublished. */\n\tcurrentPublishedVersionId: string | null;\n\t/** Whether the chain is active. */\n\tisActive: boolean;\n\t/** ISO-8601 creation timestamp. */\n\tcreatedAt: string;\n\t/** ISO-8601 last-update timestamp. */\n\tupdatedAt: string;\n}\n\n/**\n * A single chain version, mirrors `ExperienceVersion` — the shape returned by\n * `GET /api/chains/{id}/publishing-versions/{vId}` and the lifecycle methods.\n *\n * NOTE: `content` carries the stored wire-format chain spec, NOT the SDK-native\n * {@link ChainSpec} authoring type — the two are wire-identical, but the SDK\n * narrows `outputs` to {@link OutputDef} only on the authoring (request) side.\n */\nexport type ChainVersion =\n\tGenGetChainsByIdPublishingVersionsByVIdResponses[200];\n\n/** Target resource for `create()`. */\nexport interface CreateChainTarget {\n\t/** Id of the resource that will own this chain. Find ids via `resources.mine()` or `era.scope()`. */\n\tresourceId: string;\n}\n\n/** Body for `update()` — only supplied fields change. */\nexport interface UpdateChainInput {\n\t/** New chain name. */\n\tname?: string;\n\t/** New description; `null` clears it. */\n\tdescription?: string | null;\n\t/** New default modality. */\n\tmodality?: ChainModality;\n\t/** Toggle whether the chain is active. */\n\tisActive?: boolean;\n}\n\n/** Pagination + filter options for `list()`. */\nexport interface ListChainsOptions extends PaginateOptions {\n\t/** Restrict to chains owned by this resource id. */\n\tresourceId?: string;\n\t/**\n\t * Filter by active state: `true` returns only active chains,\n\t * `false` only deactivated ones. Omit for all chains.\n\t */\n\tisActive?: boolean;\n}\n\n/** Options for `run()`. */\nexport interface RunChainOptions {\n\t/** Session id the run executes within (required) — groups the run's turns for correlation and billing. */\n\tsession: string;\n\t/** Input variable values keyed by the chain's declared `input` keys. */\n\tinput?: Record<string, unknown>;\n\t/** Pin a version; default = currentPublishedVersionId. */\n\tversionId?: string;\n\t/**\n\t * Output modality hint for the run. Advisory today — each agent turn's\n\t * text/voice behavior follows that agent's experience configuration.\n\t */\n\tmodality?: ChainModality;\n\t/** Abort signal to cancel the run stream. */\n\tsignal?: AbortSignal;\n}\n\n/**\n * Run lifecycle status (`'paused'` is reserved in the contract but never\n * emitted today).\n */\nexport type ChainRunStatus = GenChainRunState['status'];\n\n/**\n * Snapshot delivery-receipt state for one output — the\n * {@link OutputReceipt} `receiptStatus` field.\n *\n * NOTE: `'failed'` is TRANSIENT — a retry is in flight and it may still advance\n * to `'delivered'` / `'undeliverable'` / `'expired'`. Treat this union as\n * non-exhaustive: future transports/states may add members, so avoid relying on\n * an exhaustive switch.\n */\nexport type ReceiptStatus = GenOutputReceipt['receiptStatus'];\n\n/**\n * Latest delivery-receipt state for one emitted **physical** output, folded\n * read-only from the run's receipt trail. Physical outputs only —\n * `say` / `surface` / `voice` carry no receipt today. The folded snapshot keys\n * match the live receipt stream events 1:1.\n *\n * For the *live* trail (each transition as it happens) watch the\n * {@link ChainEvent} stream — `output_emitted` → `output_delivered` /\n * `output_failed` / `output_undeliverable` / `output_expired`, correlated by\n * `receiptToken`. This type is the durable snapshot of that trail for\n * callers that poll {@link ChainsModule.getRun} instead of streaming.\n */\nexport type OutputReceipt = GenOutputReceipt;\n\n/**\n * Durable run-state snapshot (`GET /runs/{runId}`). The server emits every\n * key unconditionally, so **all fields are required** (incl. `outputs`);\n * nullability marks the fields whose *value* can be null.\n *\n * Field notes:\n * - `awaiting` — most recent awaited-event descriptor; `null` only if the run\n * never suspended. NOT cleared on resume — authoritative only while\n * `status` is `'awaiting_event'`.\n * - `awaiting.deadlineAt` — **epoch-milliseconds integer** deadline after\n * which the wait is failed server-side; absent when the wait has no\n * deadline.\n * - `outputs` — per-output delivery-receipt snapshot, latest state per\n * emitted physical output. Empty array for runs with no\n * physical outputs. For the live trail, watch the {@link ChainEvent}\n * receipt events instead.\n * - `status` — includes `'paused'`, reserved but never emitted today.\n * - `cursor` — opaque server-internal resume state; shape not contractual.\n */\nexport type ChainRunState = GenChainRunState;\n\n// facade-exempt(protocol): client-side aggregation computed from the generated ChainStreamEvent stream (cost/step/request-id rollup) — no emitter schema. ERA-6209\n/** Aggregated run-level metadata. */\nexport interface ChainRunMeta {\n\t/** Total cost (cents) across all turns in the run. */\n\ttotalCost: number;\n\t/** Post-charge wallet balance (cents) from the terminal `run_done` event. */\n\tlastBalance?: number;\n\t/** Request ids of every metered call in the run. */\n\trequestIds: string[];\n\t/** Number of steps executed. */\n\tsteps: number;\n}\n\n/**\n * Terminal statuses a run can settle with (`run_done.status`) — the run-state\n * statuses minus the non-terminal ones.\n */\nexport type ChainRunDoneStatus = Exclude<\n\tChainRunStatus,\n\t'running' | 'awaiting_event' | 'paused'\n>;\n\n/**\n * The normalized chain event stream. Each chain-native variant is the\n * camelCase mapping of its wire event (the wire is snake_case; the SDK\n * normalizes keys):\n *\n * - framing: `run_started` / `step_started` / `step_completed` /\n * `step_failed` / `awaiting_event` / `handed_off` / `run_done` / `run_error`\n * - outputs + delivery receipts: `output_emitted`\n * (pending receipt) then `output_delivered` / `output_failed` (retryable) /\n * `output_undeliverable` / `output_expired`, correlated by `receiptToken`\n * - `event_delivered`: an external event callback\n * resumed a run parked on `awaiting_event`\n *\n * The stream also interleaves nested agent events ({@link ExperienceRunEvent},\n * re-tagged with `stepId`) — those reuse the chat SSE event shapes and are\n * composed here, exactly as on the wire.\n *\n * Two narrowings relative to the raw wire shape: `step_started.stepType` is\n * narrowed to the SDK's `ChainStep['type']` union (the wire says `string`), and\n * `run_done.status` is narrowed to {@link ChainRunDoneStatus} (the wire says\n * `string` but emits exactly these values).\n */\nexport type ChainEvent =\n\t| CamelizeKeys<GenChainRunStartedEvent>\n\t| (Omit<CamelizeKeys<GenChainStepStartedEvent>, 'stepType'> &\n\t\t\tRecord<'stepType', ChainStep['type']>)\n\t| (ExperienceRunEvent & Record<'stepId', string>)\n\t| CamelizeKeys<GenChainStepCompletedEvent>\n\t| CamelizeKeys<GenChainStepFailedEvent>\n\t| CamelizeKeys<GenChainSafetyBlockedEvent>\n\t| CamelizeKeys<GenChainAwaitingEventEvent>\n\t| CamelizeKeys<GenChainHandedOffEvent>\n\t| CamelizeKeys<GenChainOutputEmittedEvent>\n\t| CamelizeKeys<GenChainOutputDeliveredEvent>\n\t| CamelizeKeys<GenChainOutputFailedEvent>\n\t| CamelizeKeys<GenChainOutputUndeliverableEvent>\n\t| CamelizeKeys<GenChainOutputExpiredEvent>\n\t| CamelizeKeys<GenChainEventDeliveredEvent>\n\t| (Omit<CamelizeKeys<GenChainRunDoneEvent>, 'status'> &\n\t\t\tRecord<'status', ChainRunDoneStatus>)\n\t| CamelizeKeys<GenChainRunErrorEvent>;\n\n/**\n * Resolved value of {@link ChainRun.result} — the subset of the terminal\n * `run_done` event the run handle settles with.\n */\nexport type ChainRunResult = Pick<\n\tCamelizeKeys<GenChainRunDoneEvent>,\n\t'output'\n> &\n\tRecord<'status', ChainRunDoneStatus>;\n\n// facade-exempt(protocol): client-side run handle — the events/result it carries are derived from the generated ChainStreamEvent components; the handle's own members (promises + meta) have no emitter schema. ERA-6209\n/** `AsyncIterable<ChainEvent>` + run-level control & aggregated `_meta`. */\nexport interface ChainRun extends AsyncIterable<ChainEvent> {\n\t/** Resolves to the run id (== kernelId) once `run_started` is observed. */\n\treadonly chainRunId: Promise<string>;\n\t/** Resolves on `run_done` (or rejects with {@link EraChainError}). */\n\treadonly result: Promise<ChainRunResult>;\n\t/** Aggregated cost across all turns (depends on stream `_meta` landing). */\n\tmeta(): Promise<ChainRunMeta>;\n}\n\n// ─── Module surface ────────────────────────────────────────────────────────────\n\n/**\n * The `chains` facade — declarative multi-step orchestration authoring, its\n * publishing lifecycle, and durable server-side runs. Construct it with\n * {@link chains}(client). Two surfaces:\n *\n * - **Authoring + versioning** (over hub-api): CRUD on the chain envelope plus\n * the draft → review → published state machine, mirroring the experiences\n * publishing lifecycle exactly.\n * - **Runtime** (over the ingress orchestrator): {@link ChainsModule.run} /\n * {@link ChainsModule.resume} open an SSE stream of {@link ChainEvent}s;\n * {@link ChainsModule.send} / {@link ChainsModule.getRun} /\n * {@link ChainsModule.cancel} drive and inspect a durable run out of band.\n *\n * Build the `ChainSpec` these methods consume with the pure {@link defineChain}\n * / {@link chain} authoring helpers.\n *\n * @throws {@link EraAPIError} on non-2xx hub-api / ingress responses (per-method\n * status notes below). Streaming runs surface failures as an\n * {@link EraChainError} on the {@link ChainRun} `result`/iteration instead.\n */\nexport interface ChainsModule {\n\t// ── Authoring (mirrors era.experiences / era.experienceVersions) ──\n\t/**\n\t * Create a chain plus its initial draft version, owned by a resource\n\t * (`POST /api/chains`). The chain starts unpublished — advance a version\n\t * through the review lifecycle to publish it.\n\t *\n\t * @param spec - The chain definition from {@link defineChain} / `chain().build()`.\n\t * @param target - Owning resource; find ids via `era.resources.mine()` / `era.scope()`.\n\t * @param opts - Optional per-request `signal` / `headers`.\n\t * @returns The created {@link Chain} envelope.\n\t * @throws {@link EraAPIError} 400 (invalid spec), 403 (not authorized for the resource).\n\t * @example\n\t * ```ts\n\t * const chain = await era.chains.create(spec, { resourceId: 'res_0000000000' });\n\t * ```\n\t */\n\tcreate(\n\t\tspec: ChainSpec,\n\t\ttarget: CreateChainTarget,\n\t\topts?: RequestOptions,\n\t): Promise<Chain>;\n\t/**\n\t * Cursor-paginated async walk over chains, optionally scoped to one resource\n\t * and/or filtered by active state (`GET /api/chains`). The paginator fetches\n\t * pages lazily as you iterate.\n\t *\n\t * @param opts - Paging ({@link PaginateOptions}) plus `resourceId` / `isActive` filters.\n\t * @returns An `AsyncIterable<Chain>` yielding each chain across pages.\n\t * @example\n\t * ```ts\n\t * for await (const c of era.chains.list({ resourceId: 'res_0000000000', isActive: true })) {\n\t * console.log(c.id, c.name);\n\t * }\n\t * ```\n\t */\n\tlist(opts?: ListChainsOptions): AsyncIterable<Chain>;\n\t/**\n\t * Fetch a single chain envelope by id (`GET /api/chains/{id}`).\n\t *\n\t * @param id - Chain id.\n\t * @returns The {@link Chain}.\n\t * @throws {@link EraAPIError} 404 when no chain with that id is visible to the caller.\n\t */\n\tget(id: string, opts?: RequestOptions): Promise<Chain>;\n\t/**\n\t * Update a chain's editable metadata — name / description / modality /\n\t * `isActive` (`PUT /api/chains/{id}`). Only supplied fields change; this does\n\t * not touch versions or spec content.\n\t *\n\t * @param id - Chain id.\n\t * @param patch - Fields to change (see {@link UpdateChainInput}).\n\t * @returns The updated {@link Chain}.\n\t * @throws {@link EraAPIError} 404 (unknown chain), 400 (invalid patch).\n\t * @remarks Toggling `isActive: false` deactivates the chain; deactivated chains are excluded from `list({ isActive: true })`.\n\t */\n\tupdate(\n\t\tid: string,\n\t\tpatch: UpdateChainInput,\n\t\topts?: RequestOptions,\n\t): Promise<Chain>;\n\t/**\n\t * Delete a chain and all its versions (`DELETE /api/chains/{id}`).\n\t *\n\t * @param id - Chain id.\n\t * @returns Resolves once the delete succeeds.\n\t * @throws {@link EraAPIError} 404 when the chain does not exist.\n\t * @remarks Irreversible — removes the chain envelope and every draft/published version.\n\t */\n\tdelete(id: string, opts?: RequestOptions): Promise<void>;\n\n\t// ── Version lifecycle (mirrors the experience publishing state machine) ──\n\t/**\n\t * Add a new draft version to an existing chain\n\t * (`POST /api/chains/{id}/publishing-versions`). The draft is not live until\n\t * it is submitted, approved, and published.\n\t *\n\t * @param id - Chain id.\n\t * @param spec - The new version's {@link ChainSpec} content.\n\t * @returns The created draft {@link ChainVersion}.\n\t * @throws {@link EraAPIError} 404 (unknown chain), 400 (invalid spec).\n\t */\n\tcreateVersion(\n\t\tid: string,\n\t\tspec: ChainSpec,\n\t\topts?: RequestOptions,\n\t): Promise<ChainVersion>;\n\t/**\n\t * Offset-paginated list of a chain's publishing versions, newest first\n\t * (`GET /api/chains/{id}/publishing-versions`).\n\t *\n\t * @param id - Chain id.\n\t * @param opts - `limit` / `offset` page controls and an optional abort `signal`.\n\t * @returns `{ versions, hasMore }` — the page of {@link ChainVersion}s and whether more remain.\n\t * @throws {@link EraAPIError} 404 when the chain does not exist.\n\t */\n\tlistVersions(\n\t\tid: string,\n\t\topts?: { limit?: number; offset?: number; signal?: AbortSignal },\n\t): Promise<{ versions: ChainVersion[]; hasMore: boolean }>;\n\t/**\n\t * Edit a draft version's spec content\n\t * (`PUT /api/chain-versions/{versionId}`), guarded by an optimistic lock.\n\t *\n\t * @param versionId - Draft version id.\n\t * @param spec - Replacement {@link ChainSpec} content.\n\t * @param ifMatch - The version's current `updatedAt`, sent as `If-Match`.\n\t * @returns The updated {@link ChainVersion}.\n\t * @throws {@link EraAPIError} 409 when `ifMatch` is stale (someone else edited the version) or the version is not an editable draft, 404 (unknown version), 400 (invalid spec).\n\t * @remarks Only draft versions are editable. Pass the `updatedAt` you last read; a mismatch means concurrent modification and must be re-read and retried.\n\t */\n\tupdateVersion(\n\t\tversionId: string,\n\t\tspec: ChainSpec,\n\t\tifMatch: string,\n\t\topts?: RequestOptions,\n\t): Promise<ChainVersion>;\n\t/**\n\t * Move a draft version into review\n\t * (`POST /api/chains/{id}/publishing-versions/{vId}/submit-for-review`).\n\t *\n\t * @param id - Chain id.\n\t * @param versionId - Draft version id to submit.\n\t * @returns The version, now in the review state.\n\t * @throws {@link EraAPIError} 404 (unknown chain/version), 409 (version not in a submittable state).\n\t */\n\tsubmitForReview(\n\t\tid: string,\n\t\tversionId: string,\n\t\topts?: RequestOptions,\n\t): Promise<ChainVersion>;\n\t/**\n\t * Approve a version in review and publish it\n\t * (`POST /api/chains/{id}/publishing-versions/{vId}/approve-review`).\n\t *\n\t * @param id - Chain id.\n\t * @param versionId - Version id under review.\n\t * @returns The now-published {@link ChainVersion}.\n\t * @throws {@link EraAPIError} 404 (unknown chain/version), 409 (version not in review), 400 (unresolved agent/tool references at publish).\n\t * @remarks Supersedes the chain's prior published version — new runs pin to this one. Publishing pins each `{ ref }` agent and `defaultAgent` to a concrete experience version and validates tool steps against the enabled-spindle palette.\n\t */\n\tapproveReview(\n\t\tid: string,\n\t\tversionId: string,\n\t\topts?: RequestOptions,\n\t): Promise<ChainVersion>;\n\t/**\n\t * Reject a version in review, returning it to draft\n\t * (`POST /api/chains/{id}/publishing-versions/{vId}/reject-review`).\n\t *\n\t * @param id - Chain id.\n\t * @param versionId - Version id under review.\n\t * @param input - Optional rejection reason (see {@link RejectReviewInput}).\n\t * @returns The version, back in the draft state.\n\t * @throws {@link EraAPIError} 404 (unknown chain/version), 409 (version not in review).\n\t */\n\trejectReview(\n\t\tid: string,\n\t\tversionId: string,\n\t\tinput?: RejectReviewInput,\n\t\topts?: RequestOptions,\n\t): Promise<ChainVersion>;\n\t/**\n\t * Re-point the chain's published pointer to an earlier version\n\t * (`POST /api/chains/{id}/rollback`). Does not create a new draft — it simply\n\t * moves the published pointer.\n\t *\n\t * @param id - Chain id.\n\t * @param input - `{ toVersionId }` — the previously-published version to restore.\n\t * @returns The updated {@link Chain} with the re-pointed published version.\n\t * @throws {@link EraAPIError} 404 (unknown chain/version), 409 (target version is not in a restorable state), 400 (invalid request).\n\t */\n\trollback(\n\t\tid: string,\n\t\tinput: { toVersionId: string },\n\t\topts?: RequestOptions,\n\t): Promise<Chain>;\n\n\t// ── Runtime (ingress orchestrator; the SSE source) ──\n\t/**\n\t * Start a run and stream its {@link ChainEvent}s (`POST /runs`, SSE). Pass a\n\t * chain `id` to run its published version, or an inline {@link ChainSpec} to\n\t * run without creating/publishing first.\n\t *\n\t * @param idOrSpec - A chain id (runs the published version, or `opts.versionId`) or an inline spec.\n\t * @param opts - Run options — `session` is required; see {@link RunChainOptions}.\n\t * @returns A {@link ChainRun}: an `AsyncIterable<ChainEvent>` with `.chainRunId`, `.result`, and `.meta()`.\n\t * @throws {@link EraChainError} when the run fails mid-stream (surfaced on iteration and on `.result`).\n\t * @throws {@link EraAPIError} when the run cannot start — e.g. 404 (unknown chain), 409 (chain has no published version to run), 422 (invalid spec/body) — surfaced when you begin iterating.\n\t * @example\n\t * ```ts\n\t * const run = era.chains.run('chn_0000000000', {\n\t * session: 'ses_0000000000',\n\t * input: { topic: 'Mars colonization' },\n\t * });\n\t * for await (const ev of run) {\n\t * if (ev.type === 'step_completed') console.log('done:', ev.stepId);\n\t * }\n\t * const { status, output } = await run.result;\n\t * ```\n\t * @remarks `versionId` is only honored when running by id; it is ignored for an inline spec.\n\t */\n\trun(idOrSpec: string | ChainSpec, opts: RunChainOptions): ChainRun;\n\t/**\n\t * Re-attach to a durable run and continue streaming\n\t * (`GET /runs/{runId}/events`, SSE). Replays buffered events after `afterSeq`,\n\t * then tails live — useful after a client disconnect on a long-running chain.\n\t *\n\t * @param runId - The durable run id.\n\t * @param opts - `afterSeq` to replay only events after that sequence number, plus an abort `signal`.\n\t * @returns A {@link ChainRun} over the resumed stream.\n\t * @throws {@link EraAPIError} 404 when the run is unknown or not visible to the caller — surfaced when you begin iterating.\n\t */\n\tresume(\n\t\trunId: string,\n\t\topts?: { afterSeq?: number; signal?: AbortSignal },\n\t): ChainRun;\n\t/**\n\t * Deliver a callback into a run suspended on `waitForEvent` or `handoff`\n\t * (`POST /runs/{runId}/events/{event}`) — e.g. a button press, webhook, or\n\t * human approval. Resumes the run if it was parked on that event name.\n\t *\n\t * @param runId - The durable run id.\n\t * @param event - The event name the run is (or will be) awaiting.\n\t * @param payload - Optional JSON payload delivered with the event.\n\t * @returns Resolves once the event is accepted.\n\t * @throws {@link EraAPIError} 404 when the run is unknown or not visible to the caller, 422 for a malformed request.\n\t * @example\n\t * ```ts\n\t * await era.chains.send('run_0000000000', 'approval_received', { approvedBy: 'usr_0000000000' });\n\t * ```\n\t */\n\tsend(\n\t\trunId: string,\n\t\tevent: string,\n\t\tpayload?: unknown,\n\t\topts?: RequestOptions,\n\t): Promise<void>;\n\t/**\n\t * Fetch a durable run's current state without re-attaching to the stream\n\t * (`GET /runs/{runId}`) — status, per-step results, awaited-event descriptor,\n\t * and folded delivery receipts. The polling counterpart to streaming.\n\t *\n\t * @param runId - The durable run id.\n\t * @returns The {@link ChainRunState} snapshot.\n\t * @throws {@link EraAPIError} 404 when the run is unknown or not visible to the caller, 422 for a malformed request.\n\t */\n\tgetRun(runId: string, opts?: RequestOptions): Promise<ChainRunState>;\n\t/**\n\t * Cancel an in-flight run (`POST /runs/{runId}/cancel`). The run settles with\n\t * `status: 'canceled'`.\n\t *\n\t * @param runId - The durable run id.\n\t * @returns Resolves once the cancel is accepted.\n\t * @throws {@link EraAPIError} 404 when the run is unknown or not visible to the caller, 422 for a malformed request.\n\t */\n\tcancel(runId: string, opts?: RequestOptions): Promise<void>;\n}\n\n// ─── Internal: paths + helpers ──────────────────────────────────────────────────\n\ntype HubRequestInit = RequestOptions & { method?: string; body?: unknown };\n\n/** Forward per-request overrides (signal / headers) into a generated-op call. */\nfunction passThrough(opts?: RequestOptions): {\n\tsignal?: AbortSignal;\n\theaders?: Record<string, string>;\n} {\n\tconst out: { signal?: AbortSignal; headers?: Record<string, string> } = {};\n\tif (opts?.signal !== undefined) out.signal = opts.signal;\n\tif (opts?.headers !== undefined) out.headers = opts.headers;\n\treturn out;\n}\n\ninterface ChainsEnvelope extends PageEnvelope<Chain> {\n\titems: Chain[];\n}\n\ninterface ListVersionsEnvelope {\n\tversions: ChainVersion[];\n\tpagination?: {\n\t\ttotal: number;\n\t\tlimit: number;\n\t\toffset: number;\n\t\thasMore: boolean;\n\t};\n}\n\nfunction buildVersionsQuery(opts?: {\n\tlimit?: number;\n\toffset?: number;\n}): string {\n\tconst params = new URLSearchParams();\n\tif (opts?.limit !== undefined) params.set('limit', String(opts.limit));\n\tif (opts?.offset !== undefined) params.set('offset', String(opts.offset));\n\tconst qs = params.toString();\n\treturn qs ? `?${qs}` : '';\n}\n\n// ─── Chain SSE → ChainEvent parser ──────────────────────────────────────────────\n//\n// The orchestrator emits chain-native events (the generated `ChainStreamEvent`\n// discriminated union, ERA-6209 — framing + output receipts + event_delivered)\n// interleaved with nested agent events (start / chunk / tool_call /\n// tool_result / done / error) that carry a `step_id` and reuse the chat SSE\n// event shapes. The core `parseSSE` only knows the fixed StreamEvent set and\n// would drop the framing events, so chains parse the wire locally: native\n// events are validated with the generated `zChainStreamEvent` schema and\n// key-normalized snake_case → camelCase; agent events are decoded from the\n// raw wire shape, mirroring `core/streaming.ts`.\n\n/** Discriminators of the chain-native `ChainStreamEvent` union (ERA-6209). */\nconst CHAIN_NATIVE_EVENT_TYPES = new Set<string>([\n\t'run_started',\n\t'step_started',\n\t'step_completed',\n\t'step_failed',\n\t'awaiting_event',\n\t'handed_off',\n\t'output_emitted',\n\t'output_delivered',\n\t'output_failed',\n\t'output_undeliverable',\n\t'output_expired',\n\t'event_delivered',\n\t'run_done',\n\t'run_error',\n]);\n\n/** Raw wire shape of the interleaved NESTED AGENT events only — the\n * chain-native events are typed by the generated `ChainStreamEvent`. */\ninterface RawChainWire {\n\ttype?: string;\n\tstep_id?: string;\n\t// nested agent-event fields (subset of the chat SSE wire)\n\tcontent?: string;\n\ttool_name?: string;\n\ttool_call_id?: string;\n\t/** ERA-6208 wire truth: tool results ride under `tool_result` (never `result`). */\n\ttool_result?: string | null;\n\t/**\n\t * 0-based index of a `tool_result_chunk` slice (generated\n\t * `ChatToolResultChunkStreamEvent.chunk_index`; ERA-873, flag-off today).\n\t */\n\tchunk_index?: number;\n\t/** Whether a `tool_result_chunk` is the final slice (`is_final_chunk`). */\n\tis_final_chunk?: boolean;\n\tsession_id?: string;\n\tusage?: {\n\t\tinput_tokens?: number;\n\t\toutput_tokens?: number;\n\t\tcached_input_tokens?: number;\n\t};\n\tinput_tokens?: number;\n\toutput_tokens?: number;\n\tcached_input_tokens?: number;\n\tcost_cents?: number;\n\tbalance_cents?: number;\n\t/** Names of tools invoked during the turn, on the nested `done` event. */\n\ttools_used?: string[] | null;\n\terror?: string;\n\t/** ERA-6208 wire truth: machine codes ride under `error_code` (never `code`). */\n\terror_code?: string | null;\n}\n\nfunction normalizeUsage(raw: RawChainWire) {\n\tconst input = raw.usage?.input_tokens ?? raw.input_tokens;\n\tconst output = raw.usage?.output_tokens ?? raw.output_tokens;\n\tconst cached = raw.usage?.cached_input_tokens ?? raw.cached_input_tokens;\n\tif (input === undefined && output === undefined) return undefined;\n\tconst usage: {\n\t\tinputTokens: number;\n\t\toutputTokens: number;\n\t\tcachedInputTokens?: number;\n\t} = { inputTokens: input ?? 0, outputTokens: output ?? 0 };\n\tif (cached !== undefined) usage.cachedInputTokens = cached;\n\treturn usage;\n}\n\n/**\n * Translate one spec-validated chain-native event into its camelCase\n * {@link ChainEvent} form. Purely a key-casing map — the shapes are the\n * generated `ChainStreamEvent` components (ERA-6209). Optional-nullable wire\n * fields are spread only when non-null so consumers keep the ergonomic\n * \"absent means absent\" contract.\n */\nfunction translateNative(ev: GenChainStreamEvent): ChainEvent {\n\tswitch (ev.type) {\n\t\tcase 'run_started':\n\t\t\treturn { type: 'run_started', runId: ev.run_id, kernelId: ev.kernel_id };\n\t\tcase 'step_started':\n\t\t\treturn {\n\t\t\t\ttype: 'step_started',\n\t\t\t\tstepId: ev.step_id,\n\t\t\t\t// Declared narrowing: spec says `string`; the SDK keys this to its\n\t\t\t\t// step grammar. Unknown future step types still flow through.\n\t\t\t\tstepType: ev.step_type as ChainStep['type'],\n\t\t\t\tfateId: ev.fate_id,\n\t\t\t};\n\t\tcase 'step_completed':\n\t\t\treturn {\n\t\t\t\ttype: 'step_completed',\n\t\t\t\tstepId: ev.step_id,\n\t\t\t\toutput: ev.output,\n\t\t\t\t// ERA-5816 tool-step execution metadata — forwarded since v17.6.0.\n\t\t\t\t...(ev.metadata != null && { metadata: ev.metadata }),\n\t\t\t};\n\t\tcase 'step_failed':\n\t\t\treturn {\n\t\t\t\ttype: 'step_failed',\n\t\t\t\tstepId: ev.step_id,\n\t\t\t\terror: ev.error,\n\t\t\t\t...(ev.error_code != null && { errorCode: ev.error_code }),\n\t\t\t};\n\t\tcase 'safety_blocked':\n\t\t\treturn {\n\t\t\t\ttype: 'safety_blocked',\n\t\t\t\tstepId: ev.step_id,\n\t\t\t\t...(ev.category != null && { category: ev.category }),\n\t\t\t\t...(ev.stage != null && { stage: ev.stage }),\n\t\t\t};\n\t\tcase 'awaiting_event':\n\t\t\treturn {\n\t\t\t\ttype: 'awaiting_event',\n\t\t\t\tstepId: ev.step_id,\n\t\t\t\tevent: ev.event,\n\t\t\t\t...(ev.deadline_ms != null && { deadlineMs: ev.deadline_ms }),\n\t\t\t};\n\t\tcase 'handed_off':\n\t\t\treturn { type: 'handed_off', stepId: ev.step_id, to: ev.to };\n\t\tcase 'output_emitted':\n\t\t\treturn {\n\t\t\t\ttype: 'output_emitted',\n\t\t\t\ttransport: ev.transport,\n\t\t\t\tlifecycle: ev.lifecycle,\n\t\t\t\tstatus: ev.status,\n\t\t\t\t...(ev.target != null && { target: ev.target }),\n\t\t\t\t...(ev.error != null && { error: ev.error }),\n\t\t\t\t...(ev.fields != null && { fields: ev.fields }),\n\t\t\t\t...(ev.receipt_status != null && { receiptStatus: ev.receipt_status }),\n\t\t\t\t...(ev.receipt_token != null && { receiptToken: ev.receipt_token }),\n\t\t\t\t...(ev.idempotency_key != null && {\n\t\t\t\t\tidempotencyKey: ev.idempotency_key,\n\t\t\t\t}),\n\t\t\t\t...(ev.device_id != null && { deviceId: ev.device_id }),\n\t\t\t\t...(ev.actuator_id != null && { actuatorId: ev.actuator_id }),\n\t\t\t\t...(ev.seq != null && { seq: ev.seq }),\n\t\t\t};\n\t\tcase 'output_delivered':\n\t\t\treturn {\n\t\t\t\ttype: 'output_delivered',\n\t\t\t\treceiptToken: ev.receipt_token,\n\t\t\t\t...(ev.idempotency_key != null && {\n\t\t\t\t\tidempotencyKey: ev.idempotency_key,\n\t\t\t\t}),\n\t\t\t\t...(ev.detail != null && { detail: ev.detail }),\n\t\t\t};\n\t\tcase 'output_failed':\n\t\t\treturn {\n\t\t\t\ttype: 'output_failed',\n\t\t\t\treceiptToken: ev.receipt_token,\n\t\t\t\t...(ev.idempotency_key != null && {\n\t\t\t\t\tidempotencyKey: ev.idempotency_key,\n\t\t\t\t}),\n\t\t\t\t...(ev.error != null && { error: ev.error }),\n\t\t\t};\n\t\tcase 'output_undeliverable':\n\t\t\treturn {\n\t\t\t\ttype: 'output_undeliverable',\n\t\t\t\treceiptToken: ev.receipt_token,\n\t\t\t\t...(ev.idempotency_key != null && {\n\t\t\t\t\tidempotencyKey: ev.idempotency_key,\n\t\t\t\t}),\n\t\t\t\t...(ev.error != null && { error: ev.error }),\n\t\t\t};\n\t\tcase 'output_expired':\n\t\t\treturn {\n\t\t\t\ttype: 'output_expired',\n\t\t\t\treceiptToken: ev.receipt_token,\n\t\t\t\t...(ev.idempotency_key != null && {\n\t\t\t\t\tidempotencyKey: ev.idempotency_key,\n\t\t\t\t}),\n\t\t\t};\n\t\tcase 'event_delivered':\n\t\t\t// ERA-6209: an external callback resumed a run parked on awaiting_event.\n\t\t\treturn {\n\t\t\t\ttype: 'event_delivered',\n\t\t\t\tevent: ev.event,\n\t\t\t\t...(ev.payload !== undefined && { payload: ev.payload }),\n\t\t\t};\n\t\tcase 'run_done':\n\t\t\treturn {\n\t\t\t\ttype: 'run_done',\n\t\t\t\trunId: ev.run_id,\n\t\t\t\t// Declared narrowing: spec types `status` as string; the docstring\n\t\t\t\t// enumerates exactly these terminal values.\n\t\t\t\tstatus:\n\t\t\t\t\tev.status === 'failed' || ev.status === 'canceled'\n\t\t\t\t\t\t? ev.status\n\t\t\t\t\t\t: 'succeeded',\n\t\t\t\toutput: ev.output,\n\t\t\t\t...(ev.cost_cents != null && { costCents: ev.cost_cents }),\n\t\t\t\t...(ev.balance_cents != null && { balanceCents: ev.balance_cents }),\n\t\t\t};\n\t\tcase 'run_error':\n\t\t\treturn {\n\t\t\t\ttype: 'run_error',\n\t\t\t\terror: ev.error,\n\t\t\t\t...(ev.error_code != null && { errorCode: ev.error_code }),\n\t\t\t};\n\t}\n}\n\n/** Translate one raw nested-agent wire object into a typed ChainEvent (or null to skip). */\nfunction translateChain(\n\traw: RawChainWire,\n\tacc: Map<string, string>,\n): ChainEvent | null {\n\tconst stepId = raw.step_id ?? '';\n\tswitch (raw.type) {\n\t\t// ── nested agent events (re-tagged with stepId) ──\n\t\tcase 'start':\n\t\t\treturn {\n\t\t\t\ttype: 'start',\n\t\t\t\t...(raw.session_id !== undefined && { sessionId: raw.session_id }),\n\t\t\t\tstepId,\n\t\t\t};\n\t\tcase 'interim':\n\t\t\treturn { type: 'interim', delta: raw.content ?? '', stepId };\n\t\tcase 'chunk': {\n\t\t\tconst delta = raw.content ?? '';\n\t\t\tconst prev = acc.get(stepId);\n\t\t\t// ERA-4887: cap the step-tracking map and per-step content length.\n\t\t\t// Without this, a misbehaving server (or compromised one) could\n\t\t\t// flood the consumer with distinct step IDs or a single\n\t\t\t// runaway-length step, forcing unbounded memory.\n\t\t\tif (prev === undefined && acc.size >= MAX_STEPS_TRACKED) {\n\t\t\t\t// Too many distinct steps tracked — don't add this one. Still\n\t\t\t\t// emit the chunk so the caller sees content; fullContent is\n\t\t\t\t// just `delta` since we have no accumulator state for it.\n\t\t\t\treturn { type: 'chunk', delta, fullContent: delta, stepId };\n\t\t\t}\n\t\t\tif (prev !== undefined && prev.length >= MAX_STEP_CONTENT_BYTES) {\n\t\t\t\t// Per-step content cap already reached — emit chunk but don't\n\t\t\t\t// grow the accumulator further.\n\t\t\t\treturn { type: 'chunk', delta, fullContent: prev, stepId };\n\t\t\t}\n\t\t\tconst candidate = (prev ?? '') + delta;\n\t\t\tconst fullContent =\n\t\t\t\tcandidate.length > MAX_STEP_CONTENT_BYTES\n\t\t\t\t\t? candidate.slice(0, MAX_STEP_CONTENT_BYTES)\n\t\t\t\t\t: candidate;\n\t\t\tacc.set(stepId, fullContent);\n\t\t\treturn { type: 'chunk', delta, fullContent, stepId };\n\t\t}\n\t\tcase 'tool_call':\n\t\t\treturn {\n\t\t\t\ttype: 'tool_call',\n\t\t\t\tname: raw.tool_name ?? '',\n\t\t\t\ttoolCallId: raw.tool_call_id ?? '',\n\t\t\t\tstepId,\n\t\t\t};\n\t\tcase 'tool_result':\n\t\t\treturn {\n\t\t\t\ttype: 'tool_result',\n\t\t\t\ttoolCallId: raw.tool_call_id ?? '',\n\t\t\t\t// ERA-6208: the wire key is `tool_result` (omitted when the result\n\t\t\t\t// is null), not `result`.\n\t\t\t\tresult: raw.tool_result,\n\t\t\t\tstepId,\n\t\t\t};\n\t\tcase 'tool_result_chunk':\n\t\t\t// ERA-873: a partial slice of a large tool result (generated\n\t\t\t// ChatToolResultChunkStreamEvent, feature-flag-off server-side today).\n\t\t\t// Mirrors the core decoder (core/streaming.ts), re-tagged with stepId —\n\t\t\t// without this case the frame would be silently dropped the moment the\n\t\t\t// server flag flips on, despite ChainEvent's type advertising it.\n\t\t\treturn {\n\t\t\t\ttype: 'tool_result_chunk',\n\t\t\t\ttoolCallId: raw.tool_call_id ?? '',\n\t\t\t\tchunkIndex: raw.chunk_index ?? 0,\n\t\t\t\tisFinalChunk: raw.is_final_chunk ?? false,\n\t\t\t\ttoolResult: raw.tool_result ?? '',\n\t\t\t\tstepId,\n\t\t\t};\n\t\tcase 'done': {\n\t\t\tconst usage = normalizeUsage(raw);\n\t\t\tconst fullContent = acc.get(stepId) ?? '';\n\t\t\treturn {\n\t\t\t\ttype: 'done',\n\t\t\t\tfullContent,\n\t\t\t\t// ERA-6208: the nested `done` carries `tools_used` on the wire.\n\t\t\t\ttoolsUsed: raw.tools_used ?? [],\n\t\t\t\t...(usage && { usage }),\n\t\t\t\t...(raw.cost_cents !== undefined && { costCents: raw.cost_cents }),\n\t\t\t\t...(raw.balance_cents !== undefined && {\n\t\t\t\t\tbalanceCents: raw.balance_cents,\n\t\t\t\t}),\n\t\t\t\tstepId,\n\t\t\t};\n\t\t}\n\t\tcase 'error':\n\t\t\treturn {\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage: raw.error ?? 'stream error',\n\t\t\t\t// ERA-6208: the wire key is `error_code`, not `code`.\n\t\t\t\t...(raw.error_code != null && { code: raw.error_code }),\n\t\t\t\tstepId,\n\t\t\t};\n\t\tdefault:\n\t\t\treturn null;\n\t}\n}\n\n/**\n * Hard caps for the chain SSE parser.\n *\n * - `MAX_SSE_LINE_BYTES_CHAIN` (1 MiB): bound the line buffer. Mirrors\n * `core/streaming.ts:MAX_SSE_LINE_BYTES`. A server that ships bytes\n * without a newline triggers an `SSE_LINE_TOO_LONG` `run_error` and\n * the stream closes.\n * - `MAX_STEPS_TRACKED` (1024): cap on distinct step IDs the accumulator\n * tracks. New step IDs past the cap still get their `chunk` events\n * forwarded — they just no longer accumulate `fullContent` across\n * chunks (caller sees `fullContent === delta`).\n * - `MAX_STEP_CONTENT_BYTES` (4 MiB): per-step accumulator cap. A\n * runaway step truncates at this length; subsequent chunks emit but\n * don't grow the accumulator.\n *\n * Exported as `__test` for unit tests that need to drive the cap without\n * fabricating multi-MiB fixtures.\n *\n * @internal Parser implementation detail — not part of the public API surface.\n */\nexport const MAX_SSE_LINE_BYTES_CHAIN = 1_048_576;\n/** @internal Chain SSE parser cap — see {@link MAX_SSE_LINE_BYTES_CHAIN}. */\nexport const MAX_STEPS_TRACKED = 1024;\n/** @internal Chain SSE parser cap — see {@link MAX_SSE_LINE_BYTES_CHAIN}. */\nexport const MAX_STEP_CONTENT_BYTES = 4 * 1_048_576;\n/** @internal — test re-exports */\nexport const __testCaps = {\n\tMAX_SSE_LINE_BYTES_CHAIN,\n\tMAX_STEPS_TRACKED,\n\tMAX_STEP_CONTENT_BYTES,\n};\n\n/** Parse a chain SSE Response body into typed ChainEvents. */\nasync function* parseChainSSE(\n\tbody: ReadableStream<Uint8Array>,\n): AsyncGenerator<ChainEvent, void, void> {\n\tconst reader = body.getReader();\n\tconst decoder = new TextDecoder();\n\tconst acc = new Map<string, string>();\n\tlet buffer = '';\n\n\tfunction lineToEvent(line: string): ChainEvent | null {\n\t\tconst trimmed = line.endsWith('\\r') ? line.slice(0, -1) : line;\n\t\tif (trimmed.length === 0 || trimmed.startsWith(':')) return null;\n\t\tif (!trimmed.startsWith('data:')) return null;\n\t\tlet payload = trimmed.slice(5);\n\t\tif (payload.startsWith(' ')) payload = payload.slice(1);\n\t\tif (payload.length === 0) return null;\n\t\tlet raw: RawChainWire;\n\t\ttry {\n\t\t\traw = JSON.parse(payload) as RawChainWire;\n\t\t} catch {\n\t\t\treturn {\n\t\t\t\ttype: 'run_error',\n\t\t\t\terror: 'parse failed',\n\t\t\t\terrorCode: 'SSE_PARSE_ERROR',\n\t\t\t};\n\t\t}\n\t\tif (!raw.type) return null;\n\t\tif (CHAIN_NATIVE_EVENT_TYPES.has(raw.type)) {\n\t\t\t// ERA-6209: chain-native events are validated against the generated\n\t\t\t// spec schema; a frame that fails validation is dropped (it cannot be\n\t\t\t// safely interpreted), matching the unknown-frame no-op below.\n\t\t\tconst parsed = zChainStreamEvent.safeParse(raw);\n\t\t\treturn parsed.success ? translateNative(parsed.data) : null;\n\t\t}\n\t\treturn translateChain(raw, acc);\n\t}\n\n\ttry {\n\t\twhile (true) {\n\t\t\tlet chunk: ReadableStreamReadResult<Uint8Array>;\n\t\t\ttry {\n\t\t\t\tchunk = await reader.read();\n\t\t\t} catch (err) {\n\t\t\t\tyield {\n\t\t\t\t\ttype: 'run_error',\n\t\t\t\t\terror: err instanceof Error ? err.message : 'stream read failed',\n\t\t\t\t\terrorCode: 'STREAM_DROPPED',\n\t\t\t\t};\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (chunk.done) break;\n\t\t\tbuffer += decoder.decode(chunk.value, { stream: true });\n\t\t\t// ERA-4887: line-buffer cap. A server that ships bytes without a\n\t\t\t// newline would otherwise grow `buffer` unboundedly. Emit a\n\t\t\t// run_error and close.\n\t\t\tif (buffer.length > MAX_SSE_LINE_BYTES_CHAIN) {\n\t\t\t\tyield {\n\t\t\t\t\ttype: 'run_error',\n\t\t\t\t\terror: `SSE line exceeded ${MAX_SSE_LINE_BYTES_CHAIN} bytes without a newline; closing stream`,\n\t\t\t\t\terrorCode: 'SSE_LINE_TOO_LONG',\n\t\t\t\t};\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst parts = buffer.split('\\n');\n\t\t\tbuffer = parts.pop() ?? '';\n\t\t\tfor (const line of parts) {\n\t\t\t\tconst ev = lineToEvent(line);\n\t\t\t\tif (ev) yield ev;\n\t\t\t}\n\t\t}\n\t\tif (buffer.length > 0) {\n\t\t\tconst ev = lineToEvent(buffer);\n\t\t\tif (ev) yield ev;\n\t\t}\n\t} finally {\n\t\ttry {\n\t\t\treader.releaseLock();\n\t\t} catch {\n\t\t\t/* already released */\n\t\t}\n\t}\n}\n\n/**\n * Wrap a chain SSE Response into a controllable {@link ChainRun}. The\n * AbortSignal is threaded into the underlying `client.fetch` by the caller.\n */\nfunction makeChainRun(responsePromise: Promise<Response>): ChainRun {\n\tlet resolveRunId!: (v: string) => void;\n\tlet rejectRunId!: (e: unknown) => void;\n\tconst runIdPromise = new Promise<string>((res, rej) => {\n\t\tresolveRunId = res;\n\t\trejectRunId = rej;\n\t});\n\tlet resolveResult!: (v: ChainRunResult) => void;\n\tlet rejectResult!: (e: unknown) => void;\n\tconst resultPromise = new Promise<ChainRunResult>((res, rej) => {\n\t\tresolveResult = res;\n\t\trejectResult = rej;\n\t});\n\t// Swallow unhandled-rejection noise if a consumer never reads these.\n\trunIdPromise.catch(() => {});\n\tresultPromise.catch(() => {});\n\n\tconst aggregate: ChainRunMeta = {\n\t\ttotalCost: 0,\n\t\trequestIds: [],\n\t\tsteps: 0,\n\t};\n\tlet metaResolved = false;\n\tlet resolveMeta!: (m: ChainRunMeta) => void;\n\tconst metaPromise = new Promise<ChainRunMeta>((res) => {\n\t\tresolveMeta = res;\n\t});\n\n\tasync function* iterate(): AsyncGenerator<ChainEvent, void, void> {\n\t\tlet runId = '';\n\t\tlet settledResult = false;\n\t\ttry {\n\t\t\tconst response = await responsePromise;\n\t\t\t// ERA-5883: the chain-run POST that opened this SSE stream carries the\n\t\t\t// echoed traceparent — attach it to any error from this run.\n\t\t\tconst traceparent = response.headers.get('traceparent') ?? undefined;\n\t\t\tif (!response.body) {\n\t\t\t\tthrow new EraChainError({\n\t\t\t\t\tstatus: response.status,\n\t\t\t\t\tstatusText: response.statusText,\n\t\t\t\t\tmessage: 'chain run response has no body',\n\t\t\t\t\traw: null,\n\t\t\t\t\ttraceparent,\n\t\t\t\t});\n\t\t\t}\n\t\t\tfor await (const ev of parseChainSSE(response.body)) {\n\t\t\t\tif (ev.type === 'run_started') {\n\t\t\t\t\trunId = ev.runId;\n\t\t\t\t\tresolveRunId(ev.runId);\n\t\t\t\t} else if (ev.type === 'step_started') {\n\t\t\t\t\taggregate.steps += 1;\n\t\t\t\t\tif (ev.fateId) aggregate.requestIds.push(ev.fateId);\n\t\t\t\t} else if (ev.type === 'run_done') {\n\t\t\t\t\tsettledResult = true;\n\t\t\t\t\t// ERA-4326 wire truth: `run_done` is the ONLY chain-native\n\t\t\t\t\t// ChainStreamEvent component carrying cost/balance, and its\n\t\t\t\t\t// `cost_cents` is documented as the AGGREGATE charge across the\n\t\t\t\t\t// whole run (not a per-step delta) — so ASSIGN, never `+=`:\n\t\t\t\t\t// parseChainSSE does not terminate on run_done, so a duplicated\n\t\t\t\t\t// terminal frame would double-count an already-aggregated figure.\n\t\t\t\t\t// Last write wins for both fields (balance_cents is the\n\t\t\t\t\t// post-charge snapshot). An absent cost means \"no cost\n\t\t\t\t\t// available\", not \"free\" — leave totalCost at 0 in that case.\n\t\t\t\t\tif (ev.costCents != null) aggregate.totalCost = ev.costCents;\n\t\t\t\t\tif (ev.balanceCents != null) aggregate.lastBalance = ev.balanceCents;\n\t\t\t\t\tresolveResult({ status: ev.status, output: ev.output });\n\t\t\t\t} else if (ev.type === 'run_error') {\n\t\t\t\t\tsettledResult = true;\n\t\t\t\t\trejectResult(\n\t\t\t\t\t\tnew EraChainError({\n\t\t\t\t\t\t\tstatus: 0,\n\t\t\t\t\t\t\tstatusText: 'chain_error',\n\t\t\t\t\t\t\tmessage: ev.error,\n\t\t\t\t\t\t\traw: ev,\n\t\t\t\t\t\t\tchainRunId: runId || undefined,\n\t\t\t\t\t\t\ttraceparent,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tyield ev;\n\t\t\t}\n\t\t} finally {\n\t\t\tif (!runId) rejectRunId(new Error('chain run ended before run_started'));\n\t\t\tif (!settledResult) {\n\t\t\t\trejectResult(new Error('chain run ended before run_done'));\n\t\t\t}\n\t\t\tif (!metaResolved) {\n\t\t\t\tmetaResolved = true;\n\t\t\t\tresolveMeta({ ...aggregate, requestIds: [...aggregate.requestIds] });\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\tchainRunId: runIdPromise,\n\t\tresult: resultPromise,\n\t\tmeta: () => metaPromise,\n\t\t[Symbol.asyncIterator]: iterate,\n\t};\n}\n\n// ─── Facade ────────────────────────────────────────────────────────────────────\n\n/** Construct the `chains` facade bound to an `EraClient`. */\nexport function chains(client: EraClient): ChainsModule {\n\tfunction streamRun(\n\t\tpath: string,\n\t\tbody: unknown,\n\t\tsignal?: AbortSignal,\n\t): ChainRun {\n\t\tconst opts: RequestOptions & { method?: string; body?: unknown } = {\n\t\t\tmethod: 'POST',\n\t\t\tbody,\n\t\t\theaders: { Accept: 'text/event-stream' },\n\t\t};\n\t\tif (signal) opts.signal = signal;\n\t\treturn makeChainRun(client.fetch('ingress', path, opts));\n\t}\n\n\treturn {\n\t\tcreate(\n\t\t\tspec: ChainSpec,\n\t\t\ttarget: CreateChainTarget,\n\t\t\topts?: RequestOptions,\n\t\t): Promise<Chain> {\n\t\t\tconst body = {\n\t\t\t\tresourceId: target.resourceId,\n\t\t\t\tname: spec.name,\n\t\t\t\tdescription: spec.description ?? null,\n\t\t\t\tmodality: spec.modality ?? 'auto',\n\t\t\t\tcontent: spec,\n\t\t\t};\n\t\t\tconst reqOpts: HubRequestInit = { ...opts, method: 'POST', body };\n\t\t\treturn client.hub<Chain>(hubPath(genPostChains), reqOpts);\n\t\t},\n\t\tlist(opts?: ListChainsOptions): AsyncIterable<Chain> {\n\t\t\t// Typed against the generated op's query params (the styling.ts idiom)\n\t\t\t// so a filter that isn't in the spec fails to compile. `limit`/`cursor`\n\t\t\t// are omitted — the paginator owns them.\n\t\t\tconst query: Omit<\n\t\t\t\tNonNullable<GenGetChainsData['query']>,\n\t\t\t\t'limit' | 'cursor'\n\t\t\t> = {};\n\t\t\tif (opts?.resourceId !== undefined) query.resourceId = opts.resourceId;\n\t\t\t// The wire param is a 'true'|'false' string enum (query params are\n\t\t\t// strings); the facade keeps the ergonomic boolean.\n\t\t\tif (opts?.isActive !== undefined)\n\t\t\t\tquery.isActive = opts.isActive ? 'true' : 'false';\n\t\t\tconst base = Object.keys(query).length\n\t\t\t\t? hubPath(genGetChains, { query })\n\t\t\t\t: hubPath(genGetChains);\n\t\t\tconst pag: PaginateOptions = {};\n\t\t\tif (opts?.pageSize !== undefined) pag.pageSize = opts.pageSize;\n\t\t\tif (opts?.maxPages !== undefined) pag.maxPages = opts.maxPages;\n\t\t\tif (opts?.cursor !== undefined) pag.cursor = opts.cursor;\n\t\t\tif (opts?.signal !== undefined) pag.signal = opts.signal;\n\t\t\treturn client.paginateAll<ChainsEnvelope, Chain>(\n\t\t\t\t'hub',\n\t\t\t\tbase,\n\t\t\t\t(env) => env.items,\n\t\t\t\tpag,\n\t\t\t);\n\t\t},\n\t\tget(id: string, opts?: RequestOptions): Promise<Chain> {\n\t\t\treturn client.hub<Chain>(\n\t\t\t\thubPath(genGetChainsById, { path: { id } }),\n\t\t\t\topts,\n\t\t\t);\n\t\t},\n\t\tupdate(\n\t\t\tid: string,\n\t\t\tpatch: UpdateChainInput,\n\t\t\topts?: RequestOptions,\n\t\t): Promise<Chain> {\n\t\t\tconst reqOpts: HubRequestInit = { ...opts, method: 'PUT', body: patch };\n\t\t\treturn client.hub<Chain>(\n\t\t\t\thubPath(genPutChainsById, { path: { id } }),\n\t\t\t\treqOpts,\n\t\t\t);\n\t\t},\n\t\tasync delete(id: string, opts?: RequestOptions): Promise<void> {\n\t\t\tconst reqOpts: HubRequestInit = { ...opts, method: 'DELETE' };\n\t\t\tawait client.hub<unknown>(\n\t\t\t\thubPath(genDeleteChainsById, { path: { id } }),\n\t\t\t\treqOpts,\n\t\t\t);\n\t\t},\n\t\tcreateVersion(\n\t\t\tid: string,\n\t\t\tspec: ChainSpec,\n\t\t\topts?: RequestOptions,\n\t\t): Promise<ChainVersion> {\n\t\t\tconst reqOpts: HubRequestInit = {\n\t\t\t\t...opts,\n\t\t\t\tmethod: 'POST',\n\t\t\t\tbody: { content: spec },\n\t\t\t};\n\t\t\treturn client.hub<ChainVersion>(\n\t\t\t\thubPath(genPostChainsByIdPublishingVersions, { path: { id } }),\n\t\t\t\treqOpts,\n\t\t\t);\n\t\t},\n\t\tasync listVersions(\n\t\t\tid: string,\n\t\t\topts?: { limit?: number; offset?: number; signal?: AbortSignal },\n\t\t): Promise<{ versions: ChainVersion[]; hasMore: boolean }> {\n\t\t\tconst reqOpts: RequestOptions | undefined = opts?.signal\n\t\t\t\t? { signal: opts.signal }\n\t\t\t\t: undefined;\n\t\t\tconst env = await client.hub<ListVersionsEnvelope>(\n\t\t\t\t`${hubPath(genGetChainsByIdPublishingVersions, {\n\t\t\t\t\tpath: { id },\n\t\t\t\t})}${buildVersionsQuery(opts)}`,\n\t\t\t\treqOpts,\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tversions: env.versions,\n\t\t\t\thasMore: env.pagination?.hasMore ?? false,\n\t\t\t};\n\t\t},\n\t\tasync updateVersion(\n\t\t\tversionId: string,\n\t\t\tspec: ChainSpec,\n\t\t\tifMatch: string,\n\t\t\topts?: RequestOptions,\n\t\t): Promise<ChainVersion> {\n\t\t\tconst reqOpts: HubRequestInit = {\n\t\t\t\t...opts,\n\t\t\t\tmethod: 'PUT',\n\t\t\t\tbody: { content: spec },\n\t\t\t\theaders: { ...opts?.headers, 'If-Match': ifMatch },\n\t\t\t};\n\t\t\t// The mutation endpoints wrap the row: `{ success, version, auditLogId }`\n\t\t\t// (see the generated PutChainVersionsByVIdResponses) — unwrap so callers\n\t\t\t// get the same ChainVersion shape reads return.\n\t\t\tconst env = await client.hub<GenPutChainVersionsByVIdResponses[200]>(\n\t\t\t\thubPath(genPutChainVersionsByVId, { path: { vId: versionId } }),\n\t\t\t\treqOpts,\n\t\t\t);\n\t\t\treturn env.version;\n\t\t},\n\t\tasync submitForReview(\n\t\t\tid: string,\n\t\t\tversionId: string,\n\t\t\topts?: RequestOptions,\n\t\t): Promise<ChainVersion> {\n\t\t\tconst reqOpts: HubRequestInit = { ...opts, method: 'POST' };\n\t\t\tconst env = await client.hub<GenSubmitForReviewResponses[200]>(\n\t\t\t\thubPath(genPostChainsByIdPublishingVersionsByVIdSubmitForReview, {\n\t\t\t\t\tpath: { id, vId: versionId },\n\t\t\t\t}),\n\t\t\t\treqOpts,\n\t\t\t);\n\t\t\treturn env.version;\n\t\t},\n\t\tasync approveReview(\n\t\t\tid: string,\n\t\t\tversionId: string,\n\t\t\topts?: RequestOptions,\n\t\t): Promise<ChainVersion> {\n\t\t\tconst reqOpts: HubRequestInit = { ...opts, method: 'POST' };\n\t\t\tconst env = await client.hub<GenApproveReviewResponses[200]>(\n\t\t\t\thubPath(genPostChainsByIdPublishingVersionsByVIdApproveReview, {\n\t\t\t\t\tpath: { id, vId: versionId },\n\t\t\t\t}),\n\t\t\t\treqOpts,\n\t\t\t);\n\t\t\treturn env.version;\n\t\t},\n\t\tasync rejectReview(\n\t\t\tid: string,\n\t\t\tversionId: string,\n\t\t\tinput?: RejectReviewInput,\n\t\t\topts?: RequestOptions,\n\t\t): Promise<ChainVersion> {\n\t\t\tconst reqOpts: HubRequestInit = {\n\t\t\t\t...opts,\n\t\t\t\tmethod: 'POST',\n\t\t\t\tbody: input ?? {},\n\t\t\t};\n\t\t\tconst env = await client.hub<GenRejectReviewResponses[200]>(\n\t\t\t\thubPath(genPostChainsByIdPublishingVersionsByVIdRejectReview, {\n\t\t\t\t\tpath: { id, vId: versionId },\n\t\t\t\t}),\n\t\t\t\treqOpts,\n\t\t\t);\n\t\t\treturn env.version;\n\t\t},\n\t\trollback(\n\t\t\tid: string,\n\t\t\tinput: { toVersionId: string },\n\t\t\topts?: RequestOptions,\n\t\t): Promise<Chain> {\n\t\t\tconst reqOpts: HubRequestInit = { ...opts, method: 'POST', body: input };\n\t\t\treturn client.hub<Chain>(\n\t\t\t\thubPath(genPostChainsByIdRollback, { path: { id } }),\n\t\t\t\treqOpts,\n\t\t\t);\n\t\t},\n\t\trun(idOrSpec: string | ChainSpec, opts: RunChainOptions): ChainRun {\n\t\t\t// ERA-5799 collapsed by-id + inline onto a single POST /runs whose\n\t\t\t// body carries `chainId` XOR `spec`. Build the shared body, then set\n\t\t\t// the run source.\n\t\t\tconst body: Record<string, unknown> = { session: opts.session };\n\t\t\tif (opts.input !== undefined) body.input = opts.input;\n\t\t\tif (opts.modality !== undefined) body.modality = opts.modality;\n\t\t\tif (typeof idOrSpec === 'string') {\n\t\t\t\tbody.chainId = idOrSpec;\n\t\t\t\tif (opts.versionId !== undefined) body.versionId = opts.versionId;\n\t\t\t} else {\n\t\t\t\tbody.spec = idOrSpec;\n\t\t\t}\n\t\t\treturn streamRun(ingressPath(genStartRun), body, opts.signal);\n\t\t},\n\t\tresume(\n\t\t\trunId: string,\n\t\t\topts?: { afterSeq?: number; signal?: AbortSignal },\n\t\t): ChainRun {\n\t\t\tconst qs =\n\t\t\t\topts?.afterSeq !== undefined ? `?afterSeq=${opts.afterSeq}` : '';\n\t\t\tconst reqOpts: RequestOptions & { method?: string } = {\n\t\t\t\tmethod: 'GET',\n\t\t\t\theaders: { Accept: 'text/event-stream' },\n\t\t\t};\n\t\t\tif (opts?.signal) reqOpts.signal = opts.signal;\n\t\t\treturn makeChainRun(\n\t\t\t\tclient.fetch(\n\t\t\t\t\t'ingress',\n\t\t\t\t\t`${ingressPath(genGetRunEvents, {\n\t\t\t\t\t\tpath: { run_id: runId },\n\t\t\t\t\t})}${qs}`,\n\t\t\t\t\treqOpts,\n\t\t\t\t),\n\t\t\t);\n\t\t},\n\t\tasync send(\n\t\t\trunId: string,\n\t\t\tevent: string,\n\t\t\tpayload?: unknown,\n\t\t\topts?: RequestOptions,\n\t\t): Promise<void> {\n\t\t\tawait client.callOp('ingress', genDeliverRunEvent, {\n\t\t\t\tpath: { run_id: runId, event },\n\t\t\t\tbody: (payload ?? {}) as GenDeliverEventBody,\n\t\t\t\t...passThrough(opts),\n\t\t\t});\n\t\t},\n\t\tgetRun(runId: string, opts?: RequestOptions): Promise<ChainRunState> {\n\t\t\treturn client.callOp('ingress', genGetRun, {\n\t\t\t\tpath: { run_id: runId },\n\t\t\t\t...passThrough(opts),\n\t\t\t});\n\t\t},\n\t\tasync cancel(runId: string, opts?: RequestOptions): Promise<void> {\n\t\t\tawait client.callOp('ingress', genCancelRun, {\n\t\t\t\tpath: { run_id: runId },\n\t\t\t\t...passThrough(opts),\n\t\t\t});\n\t\t},\n\t};\n}\n","// Authoring helpers for common *interactive* chain shapes (ERA-6484 follow-up).\n//\n// `defineChain` + `step` + `$` give you the primitives; a real voice flow like\n// \"keep asking the user for items until they say they're done\" is a fixed but\n// fiddly assembly of them: speak → wait → classify → branch → (ack + loop |\n// close). Hand-authoring it invites the ref gotchas we hit live:\n// • a `waitForEvent` reply's text is at `$.<wait>.output.transcript`\n// • an `agentTurn` with `output:'json'` exposes fields at\n// `$.<turn>.output.json.<field>` (NOT `$.<turn>.output.<field>`)\n// • the classifier must be pinned to strict single-line JSON or it echoes\n// the user's utterance back instead of a verdict.\n//\n// `collectLoop` bakes that assembly — and those refs — into one call. It is a\n// pure authoring helper (no client); it returns a validated, materialized\n// `ChainSpec` ready for `chains(client).create(...)` / publish.\n\nimport {\n\ttype ChainSpec,\n\ttype ChainStep,\n\ttype Ctx,\n\tdefineChain,\n\ttype JsonSchema,\n\ttype RefNode,\n\tstep,\n} from './chains.js';\n\n/** Options for {@link collectLoop}. */\nexport interface CollectLoopOptions {\n\t/** Chain name (also its human-facing title). */\n\tname: string;\n\t/**\n\t * The line spoken when the run launches — your opening ask, e.g.\n\t * \"What's the first thing for your shopping list?\". Delivered as the\n\t * first `builtin:say` step so it is heard before the run parks on the\n\t * user's first reply.\n\t */\n\topener: string;\n\t/**\n\t * Max collection rounds before the loop auto-closes even if the user never\n\t * says they're done — a safety cap so the chain can't park forever.\n\t * Default `5`.\n\t */\n\tcap?: number;\n\t/**\n\t * Noun for the thing being collected, woven into the default classifier and\n\t * acknowledgement prompts (e.g. `'grocery item(s)'`, `'guest'`, `'task'`).\n\t * Ignored when you pass your own `classifierPrompt`/`ackPrompt`. Default\n\t * `'item(s)'`.\n\t */\n\titemNoun?: string;\n\t/** Optional description stored on the chain. */\n\tdescription?: string;\n\t/**\n\t * Override the strict-JSON classifier instruction. Must direct the model to\n\t * emit a single-line object matching {@link CollectLoopOptions.itemSchema}\n\t * (`{ done: boolean, item: string }` by default). Only override if you also\n\t * override `itemSchema` or need domain-specific done-detection.\n\t */\n\tclassifierPrompt?: string;\n\t/** Override the per-item acknowledgement instruction. */\n\tackPrompt?: string;\n\t/** Override the closing-confirmation instruction. */\n\tclosePrompt?: string;\n\t/**\n\t * JSON Schema the classifier turn must satisfy. Defaults to\n\t * `{ done: boolean, item: string }` (both required, no extra keys). Override\n\t * together with `classifierPrompt` for a richer per-item shape.\n\t */\n\titemSchema?: JsonSchema;\n}\n\n/** Default classifier output contract: `{ done, item }`. */\nfunction defaultItemSchema(): JsonSchema {\n\treturn {\n\t\ttype: 'object',\n\t\tadditionalProperties: false,\n\t\tproperties: { done: { type: 'boolean' }, item: { type: 'string' } },\n\t\trequired: ['done', 'item'],\n\t};\n}\n\n/** Strict single-line JSON classifier prompt (the shape proven live). */\nfunction defaultClassifierPrompt(itemNoun: string): string {\n\treturn (\n\t\t'You are a strict JSON API. Output ONLY a single-line JSON object, ' +\n\t\t'nothing else — no prose, no code fences. Set done=true if the user ' +\n\t\t`signals they are FINISHED adding ${itemNoun} — this includes any of: ` +\n\t\t'\"that’s it\", \"that’s all\", \"that’s everything\", \"that’s all thanks\", ' +\n\t\t'\"done\", \"I’m done\", \"I’m good\", \"no\", \"nope\", \"nothing else\", \"no ' +\n\t\t'more\", \"all done\", \"finished\", \"stop\", or any clearly negative/closing ' +\n\t\t'reply. In that case output {\"done\":true,\"item\":\"\"}. Otherwise output ' +\n\t\t`{\"done\":false,\"item\":\"<the ${itemNoun} they just named>\"}.`\n\t);\n}\n\n/**\n * Build a voice chain that repeatedly asks the user for one more item until\n * they signal they're done (or a round `cap` is hit), acknowledging each item\n * and confirming at the end.\n *\n * Shape, unrolled to `cap` rounds (per round `i`):\n * ```\n * waitForEvent(reply) ← park for the user's utterance\n * agentTurn(json, classify transcript) ← { done, item }\n * branch(done?)\n * then → agentTurn(close) → builtin:say ← warm confirmation\n * else → agentTurn(ack) → builtin:say ← acknowledge, ask for more\n * → next round (or close, at cap)\n * ```\n * The `opener` is spoken first via a `builtin:say` step, so the user hears the\n * ask before the run parks on their first reply.\n *\n * @param options - Loop configuration — see {@link CollectLoopOptions}. Only `name` and `opener` are required; the classifier, acknowledgement, and close prompts default to voice-friendly text (the classifier and acknowledgement defaults are keyed off `itemNoun`).\n * @returns A validated, materialized {@link ChainSpec} (all `$.` ref tokens already collapsed to `{\"$ref\":…}`) — hand it straight to `chains(client).create(spec)` and the publish lifecycle.\n * @throws {TypeError} If `cap` is not a positive integer.\n *\n * @example\n * const spec = collectLoop({\n * name: 'Shopping list',\n * opener: \"What's the first thing you need?\",\n * itemNoun: 'grocery item(s)',\n * });\n * await chains(client).create(spec);\n */\nexport function collectLoop(options: CollectLoopOptions): ChainSpec {\n\tconst cap = options.cap ?? 5;\n\tif (!Number.isInteger(cap) || cap < 1) {\n\t\tthrow new TypeError('collectLoop: `cap` must be a positive integer');\n\t}\n\tconst itemNoun = options.itemNoun ?? 'item(s)';\n\tconst schema = options.itemSchema ?? defaultItemSchema();\n\tconst classifierPrompt =\n\t\toptions.classifierPrompt ?? defaultClassifierPrompt(itemNoun);\n\tconst ackPrompt =\n\t\toptions.ackPrompt ??\n\t\t`Acknowledge in a few words the ${itemNoun} the user just added, then ` +\n\t\t\t'ask if they’d like to add anything else. One short friendly sentence.';\n\tconst closePrompt =\n\t\toptions.closePrompt ??\n\t\t'The user is finished. Warmly confirm everything is all set in ONE ' +\n\t\t\t'short sentence. Do NOT list the items back.';\n\n\treturn defineChain({\n\t\tname: options.name,\n\t\tdescription:\n\t\t\toptions.description ?? `Interactive voice collection: ${options.name}.`,\n\t\tmodality: 'voice',\n\t\tsteps: ($: Ctx) => {\n\t\t\t// `$[dynamic]` — the ref proxy extends any string key into a RefNode,\n\t\t\t// so `at('wait1').output.transcript` → { $ref: '$.wait1.output.transcript' }.\n\t\t\tconst at = (id: string): RefNode => ($ as Ctx)[id] as RefNode;\n\n\t\t\t// A close = warm confirmation turn + speak it. Distinct `tag` keeps\n\t\t\t// step ids unique across the (mutually exclusive) done- and cap-paths,\n\t\t\t// which defineChain's cross-tree id check requires.\n\t\t\tconst close = (tag: string): ChainStep[] => [\n\t\t\t\tstep.agentTurn({\n\t\t\t\t\tid: `close_${tag}`,\n\t\t\t\t\toutput: 'text',\n\t\t\t\t\tprompt: closePrompt,\n\t\t\t\t}),\n\t\t\t\tstep.tool({\n\t\t\t\t\tid: `sayclose_${tag}`,\n\t\t\t\t\ttool: 'builtin:say',\n\t\t\t\t\tparams: { text: at(`close_${tag}`).output.text },\n\t\t\t\t}),\n\t\t\t];\n\n\t\t\tconst round = (i: number): ChainStep[] => [\n\t\t\t\tstep.waitForEvent({ id: `wait${i}`, event: 'reply' }),\n\t\t\t\tstep.agentTurn({\n\t\t\t\t\tid: `parse${i}`,\n\t\t\t\t\toutput: 'json',\n\t\t\t\t\tsay: at(`wait${i}`).output.transcript,\n\t\t\t\t\texpect: { name: 'item_or_done', schema },\n\t\t\t\t\tprompt: classifierPrompt,\n\t\t\t\t}),\n\t\t\t\tstep.branch({\n\t\t\t\t\tid: `branch${i}`,\n\t\t\t\t\twhen: { truthy: at(`parse${i}`).output.json.done },\n\t\t\t\t\t// biome-ignore lint/suspicious/noThenProperty: `then` is the canonical BranchStep field (plain orchestration data, never awaited).\n\t\t\t\t\tthen: close(`done${i}`),\n\t\t\t\t\telse: [\n\t\t\t\t\t\tstep.agentTurn({\n\t\t\t\t\t\t\tid: `ack${i}`,\n\t\t\t\t\t\t\toutput: 'text',\n\t\t\t\t\t\t\tsay: at(`parse${i}`).output.json.item,\n\t\t\t\t\t\t\tprompt: ackPrompt,\n\t\t\t\t\t\t}),\n\t\t\t\t\t\tstep.tool({\n\t\t\t\t\t\t\tid: `sayack${i}`,\n\t\t\t\t\t\t\ttool: 'builtin:say',\n\t\t\t\t\t\t\tparams: { text: at(`ack${i}`).output.text },\n\t\t\t\t\t\t}),\n\t\t\t\t\t\t...(i === cap ? close('cap') : round(i + 1)),\n\t\t\t\t\t],\n\t\t\t\t}),\n\t\t\t];\n\n\t\t\t// The opener is a plain mid-run utterance (voice outputs aren't in the\n\t\t\t// typed OutputDef union); speak it, then enter the loop.\n\t\t\treturn [\n\t\t\t\tstep.tool({\n\t\t\t\t\tid: 'opener',\n\t\t\t\t\ttool: 'builtin:say',\n\t\t\t\t\tparams: { text: options.opener },\n\t\t\t\t}),\n\t\t\t\t...round(1),\n\t\t\t];\n\t\t},\n\t});\n}\n","// commands module — typed style preferences + voice/button command CRUD.\n//\n// L2.3 (ERA-4265): promotes the loose `stylePreferences` blob to a typed\n// `ExperienceStylePreferences` surface, and exposes CRUD over the curator-\n// authored triggers (`voiceCommands` + `buttonMappings`) the runtime fires.\n//\n// Types mirror era-hub-api/src/schemas/experiences.ts (VoiceCommandSchema,\n// ButtonMappingSchema, ParamMappingSchema, InputSpecSchema,\n// StylePreferencesSchema). `ExperienceStylePreferences` keeps an index\n// signature (`[k: string]: unknown`) so it stays drift-tolerant — the backend\n// schema is a `looseObject` and the SDK must not break on new keys.\n//\n// Persistence: voiceCommands / buttonMappings live in the experience's\n// `stylePreferences`. CRUD here is a read-modify-write through\n// `GET`/`PUT /api/experiences/{id}` that preserves every other style key —\n// the same object + path the `tools` module uses for the `spindles` palette, and\n// verified runtime-honored (era-ingress lib/api_client.py reads\n// `voice_commands`/`button_mappings` from the same stylePreferences).\n//\n// NOTE: `Experience.stylePreferences` on the experiences module stays\n// `Record<string, unknown> | null` (unchanged — additive). Consumers wanting\n// the strict shape cast to / read via `ExperienceStylePreferences` here.\n//\n// Tree-shakeable subpath import: `@era-laboratories/era-sdk/modules/commands`.\n\nimport type { EraClient } from '../core/client.js';\nimport type { RequestOptions } from '../core/types.js';\nimport { getExperiencesById as genGetExperiencesById } from '../generated/hub-api/sdk.gen.js';\nimport { hubPath } from './hubPaths.js';\nimport type { SpindlePalette } from './spindlePalette.js';\n\n// ─── Style-preference sub-shapes ──────────────────────────────────────────────\n\n/** Voice slider values (0–1). */\nexport interface VoiceStyle {\n\t/** Formality slider (0–1). */\n\tformality: number;\n\t/** Warmth slider (0–1). */\n\twarmth: number;\n\t/** Candor slider (0–1). */\n\tcandor: number;\n\t/** Verbosity slider (0–1). */\n\tverbosity: number;\n\t/** Playfulness slider (0–1). */\n\tplayfulness: number;\n\t/** Assertiveness slider (0–1). */\n\tassertiveness: number;\n\t/** Cadence slider (0–1). */\n\tcadence: number;\n}\n\n/** Word-choice and reading-level preferences. */\nexport interface Lexicon {\n\t/** Words/phrases to prefer. */\n\tprefer: string[];\n\t/** Words/phrases to avoid. */\n\tavoid: string[];\n\t/** Whether contractions are allowed. */\n\tcontractions: boolean;\n\t/** Whether domain jargon is allowed. */\n\tjargon_allowed: boolean;\n\t/** Target reading level. */\n\treading_level: 'elementary' | 'general' | 'academic' | 'technical';\n}\n\n/** Rhetorical-device preferences. */\nexport interface Rhetoric {\n\t/** Rate of rhetorical questions (0–1). */\n\tquestion_rate: number;\n\t/** Rate of metaphor use (0–1). */\n\tmetaphor_rate: number;\n\t/** Whether analogies are allowed. */\n\tallow_analogy: boolean;\n}\n\n/** Truthfulness and refusal policy. */\nexport interface Policy {\n\t/** Truthfulness posture. */\n\ttruthfulness: 'strict' | 'standard';\n\t/** Whether the model should express uncertainty. */\n\texpress_uncertainty: boolean;\n\t/** Confidence threshold below which uncertainty is flagged (0–1). */\n\tconfidence_threshold: number;\n\t/** How refusals are phrased. */\n\trefusal_style: 'brief' | 'helpful' | 'policy_citing';\n}\n\n/** Response post-editing preferences. */\nexport interface Editing {\n\t/** Editing aggressiveness. */\n\tmode: 'light' | 'moderate' | 'heavy';\n\t/** Whether editing must preserve the original meaning. */\n\tpreserve_meaning: boolean;\n}\n\n/** Text-to-speech voice configuration. */\nexport interface TtsConfig {\n\t/** TTS provider (e.g. `moss-tts-nano`). */\n\tprovider: string;\n\t/** Voice id used for TTS, or `null` to use the platform default. */\n\tvoice_id: string | null;\n\t/**\n\t * MOSS voice-design/clone preset id, when the voice is a designed/cloned\n\t * preset. Moss cloning models strip this on PUT and fall back to\n\t * `voice_id`, so the two carry the same id; surfaced for round-tripping.\n\t */\n\tpreset_id?: string | null;\n\t/** Display name of the voice, or `null`. */\n\tvoice_name?: string | null;\n\t/** Human-readable voice description, or `null`. */\n\tvoice_description?: string | null;\n\t/** Signed preview-audio URL, or `null`. */\n\tpreview_url?: string | null;\n}\n\n/** Speech-to-text configuration. */\nexport interface SttConfig {\n\t/** STT provider. */\n\tprovider: string;\n\t/** STT model, or `null` to resolve from the experience. */\n\tmodel?: string | null;\n}\n\n/** A single interstitial clip reference. */\nexport interface InterstitialClip {\n\t/** Bank clip id. */\n\tclipId?: string;\n\t/** Spoken phrase rendered for this clip. */\n\tphrase?: string;\n\t/** Signed audio URL for the clip. */\n\tvocalUrl?: string;\n}\n\n/**\n * One earcon binding for a single pipeline state — the tonal-capable keys of\n * `interstitials.byState`. Any of a bank clip, a TTS-rendered phrase, or a\n * pre-rendered audio URL.\n */\nexport interface InterstitialStateBinding {\n\t/** Bank clip id (tonal earcon). */\n\tclipId?: string;\n\t/** Spoken phrase rendered in the experience's voice. */\n\tphrase?: string;\n\t/** Pre-rendered audio URL for the phrase. */\n\tvocalUrl?: string;\n\t/** Whether the binding is active. */\n\tenabled?: boolean;\n}\n\n/**\n * Binding for the conversational filler states (`backchannel`, `delegation`,\n * `resume`). These states are VOCAL-ONLY: they are conversational utterances\n * spoken in the experience's voice, never tonal earcon clips, so `clipId` is\n * omitted from the type. The API rejects a `clipId` on these states\n * (\"conversational states are vocal-only: use phrase or vocalUrl, not\n * clipId\").\n */\nexport interface VocalInterstitialStateBinding {\n\t/** Spoken phrase rendered in the experience's voice. */\n\tphrase?: string;\n\t/** Pre-rendered audio URL for the phrase. */\n\tvocalUrl?: string;\n\t/** Whether the binding is active. */\n\tenabled?: boolean;\n}\n\n/** Interstitial (filler-audio) configuration while a tool runs. */\nexport interface Interstitials {\n\t/** Interstitial playback mode. */\n\tmode: 'off' | 'tonal' | 'vocal' | 'mixed';\n\t/** Delay (ms) before an interstitial starts playing. */\n\tthresholdMs?: number;\n\t/** Default clip id used when no per-tool override matches. */\n\tdefaultClipId?: string;\n\t/** Default spoken phrase used when no per-tool override matches. */\n\tdefaultPhrase?: string;\n\t/** Default audio URL used when no per-tool override matches. */\n\tdefaultVocalUrl?: string;\n\t/** Per-tool clip overrides, keyed by tool name. */\n\tperTool?: Record<string, InterstitialClip>;\n\t/** Per-server clip map; `perTool` is derived from it on save. */\n\tperServer?: Record<string, string>;\n\t/**\n\t * Per-pipeline-state earcon bindings (`listening`, `listeningStopped`,\n\t * `tooling`, `llmWait`) plus the vocal-only conversational states\n\t * (`backchannel`, `delegation`, `resume`). The conversational states take\n\t * `phrase` / `vocalUrl` only — the API rejects `clipId` on them (see\n\t * {@link VocalInterstitialStateBinding}).\n\t */\n\tbyState?: {\n\t\t/** VAD-edge cue: the agent started listening. */\n\t\tlistening?: InterstitialStateBinding;\n\t\t/** VAD-edge cue: the agent stopped listening. */\n\t\tlisteningStopped?: InterstitialStateBinding;\n\t\t/** Cue while a tool call runs (structured successor to `perTool`). */\n\t\ttooling?: InterstitialStateBinding;\n\t\t/** Cue while waiting on LLM inference (successor to `defaultClipId`). */\n\t\tllmWait?: InterstitialStateBinding;\n\t\t/** Vocal-only conversational acknowledgement (\"mm-hm\"). */\n\t\tbackchannel?: VocalInterstitialStateBinding;\n\t\t/** Vocal-only cue while the run is handed off / delegated. */\n\t\tdelegation?: VocalInterstitialStateBinding;\n\t\t/** Vocal-only cue when the conversation resumes after a handoff. */\n\t\tresume?: VocalInterstitialStateBinding;\n\t};\n}\n\n// ─── Triggers (voice + button commands) ───────────────────────────────────────\n\n/** A tool-param source descriptor (`ParamMappingSchema`). */\nexport interface ParamMapping {\n\t/**\n\t * Source key. Non-utterance: `query`, `location`, `timezone`, `now`,\n\t * `today_start`, `today_end`, `tomorrow_start`, `tomorrow_end`,\n\t * `this_week_start`, `this_week_end`. Utterance-only (voice):\n\t * `rest_of_utterance`, `full_transcription`, `extracted_duration`,\n\t * `extracted_date_start`, `extracted_date_end`. App-provided (buttons):\n\t * any `slotKey` from `inputs[]`.\n\t */\n\tsource: string;\n\t/** Source tried when the primary resolves empty. */\n\tfallback?: string | null;\n}\n\n/** A param value — a legacy canonical-key string or a {@link ParamMapping}. */\nexport type ParamValue = string | ParamMapping;\n\n/**\n * A typed input the iOS app collects before firing a button. Discriminated on\n * `type` so the client renders the right native picker; `slotKey` becomes a\n * valid `ParamMapping.source` on the button's `params`.\n */\nexport type InputSpec =\n\t| {\n\t\t\t/** Param key the collected value is bound to. */\n\t\t\tslotKey: string;\n\t\t\t/** Discriminant — renders a native integer picker. */\n\t\t\ttype: 'integer';\n\t\t\t/** Field label shown in the app. */\n\t\t\tlabel: string;\n\t\t\t/** Minimum allowed value. */\n\t\t\tmin?: number;\n\t\t\t/** Maximum allowed value. */\n\t\t\tmax?: number;\n\t\t\t/** Stepper increment. */\n\t\t\tstep?: number;\n\t\t\t/** Default value. */\n\t\t\tdefault?: number;\n\t\t\t/** Unit suffix shown next to the value. */\n\t\t\tunit?: string;\n\t }\n\t| {\n\t\t\t/** Param key the collected value is bound to. */\n\t\t\tslotKey: string;\n\t\t\t/** Discriminant — renders a native decimal picker. */\n\t\t\ttype: 'number';\n\t\t\t/** Field label shown in the app. */\n\t\t\tlabel: string;\n\t\t\t/** Minimum allowed value. */\n\t\t\tmin?: number;\n\t\t\t/** Maximum allowed value. */\n\t\t\tmax?: number;\n\t\t\t/** Stepper increment. */\n\t\t\tstep?: number;\n\t\t\t/** Default value. */\n\t\t\tdefault?: number;\n\t\t\t/** Unit suffix shown next to the value. */\n\t\t\tunit?: string;\n\t }\n\t| {\n\t\t\t/** Param key the collected value is bound to. */\n\t\t\tslotKey: string;\n\t\t\t/** Discriminant — renders a free-text field. */\n\t\t\ttype: 'string';\n\t\t\t/** Field label shown in the app. */\n\t\t\tlabel: string;\n\t\t\t/** Placeholder text for the empty field. */\n\t\t\tplaceholder?: string;\n\t\t\t/** Maximum input length. */\n\t\t\tmaxLength?: number;\n\t }\n\t| {\n\t\t\t/** Param key the collected value is bound to. */\n\t\t\tslotKey: string;\n\t\t\t/** Discriminant — renders a native option picker. */\n\t\t\ttype: 'enum';\n\t\t\t/** Field label shown in the app. */\n\t\t\tlabel: string;\n\t\t\t/** Selectable options (value + display label). */\n\t\t\toptions: Array<{ value: string; label: string }>;\n\t\t\t/** Default selected value. */\n\t\t\tdefault?: string;\n\t }\n\t| {\n\t\t\t/** Param key the collected value is bound to. */\n\t\t\tslotKey: string;\n\t\t\t/** Discriminant — renders a duration picker. */\n\t\t\ttype: 'duration';\n\t\t\t/** Field label shown in the app. */\n\t\t\tlabel: string;\n\t\t\t/** Default value. */\n\t\t\tdefault?: string;\n\t }\n\t| {\n\t\t\t/** Param key the collected value is bound to. */\n\t\t\tslotKey: string;\n\t\t\t/** Discriminant — renders a date picker. */\n\t\t\ttype: 'date';\n\t\t\t/** Field label shown in the app. */\n\t\t\tlabel: string;\n\t\t\t/** Default value. */\n\t\t\tdefault?: 'today' | 'tomorrow';\n\t }\n\t| {\n\t\t\t/** Param key the collected value is bound to. */\n\t\t\tslotKey: string;\n\t\t\t/** Discriminant — renders a toggle. */\n\t\t\ttype: 'boolean';\n\t\t\t/** Field label shown in the app. */\n\t\t\tlabel: string;\n\t\t\t/** Default value. */\n\t\t\tdefault?: boolean;\n\t };\n\n/**\n * A voice command — a trigger phrase set mapped to a tool. The index\n * signature keeps any additional fields the server may add.\n */\nexport interface VoiceCommand {\n\t/** Stable command id. */\n\tid: string;\n\t/** Human-readable command name. */\n\tname?: string;\n\t/** Trigger phrases (substring-matched on the STT transcript). */\n\ttriggers?: string[];\n\t/** `\"spindle-slug:tool_name\"` or `\"builtin:<action>\"`. */\n\ttoolRef?: string;\n\t/** Static query/argument passed to the tool, or `null`. */\n\tquery?: string | null;\n\t/** Human-readable command description. */\n\tdescription?: string;\n\t/** Response-framing instructions for the LLM system prompt. */\n\tpromptContext?: string;\n\t/** Whether the command is active. */\n\tenabled?: boolean;\n\t/** Tool param sources keyed by param name. */\n\tparams?: Record<string, ParamValue>;\n\t/** Drift-tolerant index signature for backend-added keys. */\n\t[key: string]: unknown;\n}\n\n/** A button mapped to trigger a tool — the button-trigger form of a voice command. */\nexport interface ButtonMapping {\n\t/** Stable mapping id. */\n\tid: string;\n\t/** Client-facing slug the iOS app sends on press (stable across versions). */\n\tbuttonId: string;\n\t/** `\"spindle-slug:tool_name\"` or `\"builtin:<action>\"`. */\n\ttoolRef: string;\n\t/** Static query/argument passed to the tool, or `null`. */\n\tquery?: string | null;\n\t/** Human-readable mapping description. */\n\tdescription?: string;\n\t/** Response-framing instructions for the LLM system prompt. */\n\tpromptContext?: string;\n\t/** Tool param sources keyed by param name. */\n\tparams?: Record<string, ParamValue>;\n\t/** Button label shown in the app. */\n\tlabel: string;\n\t/** Icon identifier for the button. */\n\ticon?: string;\n\t/** Sort order among buttons. */\n\tdisplayOrder?: number;\n\t/** Group/section the button belongs to. */\n\tgroup?: string;\n\t/** Visual button style. */\n\tvariant?: 'primary' | 'secondary' | 'destructive' | 'subtle';\n\t/**\n\t * `narrated`: full LLM TTS. `confirm`: short canned TTS. `silent`: tool\n\t * runs, no audio. Defaults to `narrated`.\n\t */\n\tresponseMode?: 'narrated' | 'confirm' | 'silent';\n\t/** Whether the app must confirm before firing the tool. */\n\tconfirmRequired?: boolean;\n\t/** Typed inputs the app collects before firing. */\n\tinputs?: InputSpec[];\n\t/** Whether the mapping is active. */\n\tenabled?: boolean;\n}\n\n// ─── Style preferences ─────────────────────────────────────────────────────────\n\n/**\n * Typed projection of an experience's `stylePreferences`. Open\n * (`[k: string]: unknown`) to mirror the backend `looseObject` — unknown keys\n * pass through untouched, so the SDK never breaks on a new style field.\n */\nexport interface ExperienceStylePreferences {\n\t/** Voice tone sliders. */\n\tvoice?: VoiceStyle;\n\t/** Word-choice and reading-level preferences. */\n\tlexicon?: Lexicon;\n\t/** Rhetorical-device preferences. */\n\trhetoric?: Rhetoric;\n\t/** Truthfulness and refusal policy. */\n\tpolicy?: Policy;\n\t/** Response post-editing preferences. */\n\tediting?: Editing;\n\t/** Text-to-speech voice configuration. */\n\ttts?: TtsConfig;\n\t/** Speech-to-text configuration. */\n\tstt?: SttConfig;\n\t/** Brand/accent color (hex). */\n\tcolor?: string;\n\t/** Server-authored reasoning for the chosen style. */\n\tanalysis_reasoning?: string;\n\t/** Legacy free-form fields, kept for older experiences. */\n\ttone?: string;\n\t/** Legacy free-form personality description. */\n\tpersonality?: string;\n\t/** Legacy response-length preference. */\n\tresponseLength?: 'brief' | 'moderate' | 'detailed';\n\t/** Legacy formality preference. */\n\tformality?: 'casual' | 'neutral' | 'formal';\n\t/**\n\t * Nested, slug-keyed spindle palette — the sole encoding of the tool\n\t * palette (the legacy flat `mcpServers`/`mcpTools` maps are no longer read\n\t * or written by the SDK). Presence enables a spindle; `disabled: true`\n\t * keeps it in-palette but off; `tools` is a sparse per-tool deny.\n\t */\n\tspindles?: SpindlePalette;\n\t/** Interstitial (filler-audio) configuration. */\n\tinterstitials?: Interstitials;\n\t/** Curator-authored voice commands. */\n\tvoiceCommands?: VoiceCommand[];\n\t/** Curator-authored button mappings. */\n\tbuttonMappings?: ButtonMapping[];\n\t/** Drift-tolerant index signature for unknown style keys. */\n\t[key: string]: unknown;\n}\n\n/** The trigger pair read/returned by the commands module. */\nexport interface CommandSet {\n\t/** Voice commands in the experience. */\n\tvoiceCommands: VoiceCommand[];\n\t/** Button mappings in the experience. */\n\tbuttonMappings: ButtonMapping[];\n}\n\n/**\n * Typed CRUD over an experience's curator-authored triggers — the\n * `voiceCommands` and `buttonMappings` arrays stored inside its\n * `stylePreferences`. Reach it by calling {@link commands} with an\n * {@link EraClient}: `commands(era).list(experienceId)`.\n *\n * Every mutation is a read-modify-write against `GET`/`PUT /experiences/{id}`:\n * the module reads the current `stylePreferences`, updates only the targeted\n * array, and writes the whole blob back — so all other style keys (`tts`,\n * `spindles`, `voice`, custom fields) survive untouched.\n *\n * @remarks\n * Because writes go through the full-experience `PUT`, they are last-write-wins:\n * two concurrent mutations against the same experience can clobber each other.\n * Serialize mutations to one experience if that matters.\n */\nexport interface CommandsModule {\n\t/**\n\t * Read an experience's voice + button commands from its `stylePreferences`.\n\t *\n\t * @param experienceId - Id of the experience to read (e.g. `'exp_0000000000'`).\n\t * @param opts - Optional per-request overrides (`signal`, `headers`,\n\t * `idempotencyKey`).\n\t * @returns The experience's {@link CommandSet}; each array is `[]` when the\n\t * experience has no commands of that kind.\n\t * @throws {EraAPIError} On a non-2xx `GET /experiences/{id}` — `404` when the\n\t * experience does not exist (or is another user's private experience, which\n\t * is reported as not-found rather than forbidden), `403` when read access to\n\t * a shared experience is denied, `401` when unauthenticated, `500` on a\n\t * server fault.\n\t * @example\n\t * ```ts\n\t * const { voiceCommands, buttonMappings } = await commands(era).list('exp_0000000000');\n\t * ```\n\t */\n\tlist(experienceId: string, opts?: RequestOptions): Promise<CommandSet>;\n\n\t/**\n\t * Insert or replace a voice command (matched by `id`), preserving all other\n\t * style preferences.\n\t *\n\t * @param experienceId - Id of the experience to mutate (e.g. `'exp_0000000000'`).\n\t * @param command - The voice command to upsert. A command whose `id` already\n\t * exists is replaced in place; a new `id` is appended.\n\t * @param opts - Optional per-request overrides (`signal`, `headers`,\n\t * `idempotencyKey`).\n\t * @returns The full updated `voiceCommands` array after the write.\n\t * @throws {EraAPIError} On a non-2xx read or write — `404` when the experience\n\t * does not exist (or is another user's private experience), `403` when the\n\t * caller lacks read access or write permission on the experience, `400` when\n\t * the resulting `stylePreferences` fails validation, `401` when\n\t * unauthenticated, `500` on a server fault.\n\t * @remarks\n\t * Matching is by `id` only; two commands with the same `id` cannot coexist.\n\t * The runtime substring-matches `triggers` against the STT transcript to fire\n\t * the command's `toolRef`.\n\t * @example\n\t * ```ts\n\t * const updated = await commands(era).upsertVoice('exp_0000000000', {\n\t * id: 'cmd_0000000000',\n\t * triggers: ['check in', 'how am I doing'],\n\t * toolRef: 'memory:get_recent_insights',\n\t * promptContext: 'Use these insights to write a warm check-in.',\n\t * enabled: true,\n\t * });\n\t * ```\n\t */\n\tupsertVoice(\n\t\texperienceId: string,\n\t\tcommand: VoiceCommand,\n\t\topts?: RequestOptions,\n\t): Promise<VoiceCommand[]>;\n\n\t/**\n\t * Insert or replace a button mapping (matched by `id`), preserving all other\n\t * style preferences.\n\t *\n\t * @param experienceId - Id of the experience to mutate (e.g. `'exp_0000000000'`).\n\t * @param mapping - The button mapping to upsert. A mapping whose `id` already\n\t * exists is replaced in place; a new `id` is appended. `buttonId`, `toolRef`,\n\t * and `label` are required.\n\t * @param opts - Optional per-request overrides (`signal`, `headers`,\n\t * `idempotencyKey`).\n\t * @returns The full updated `buttonMappings` array after the write.\n\t * @throws {EraAPIError} On a non-2xx read or write — `404` when the experience\n\t * does not exist (or is another user's private experience), `403` when the\n\t * caller lacks read access or write permission on the experience, `400` when\n\t * the resulting `stylePreferences` fails validation, `401` when\n\t * unauthenticated, `500` on a server fault.\n\t * @remarks\n\t * Matching is by `id` (not `buttonId`). `buttonId` is the stable slug the iOS\n\t * app sends on press; keep it constant across versions so bound buttons keep\n\t * firing.\n\t * @example\n\t * ```ts\n\t * const updated = await commands(era).upsertButton('exp_0000000000', {\n\t * id: 'btn_0000000000',\n\t * buttonId: 'help-corner',\n\t * toolRef: 'docs:search',\n\t * label: 'Help',\n\t * responseMode: 'narrated',\n\t * });\n\t * ```\n\t */\n\tupsertButton(\n\t\texperienceId: string,\n\t\tmapping: ButtonMapping,\n\t\topts?: RequestOptions,\n\t): Promise<ButtonMapping[]>;\n\n\t/**\n\t * Remove a voice command or button mapping by `id`, checking both arrays.\n\t *\n\t * @param experienceId - Id of the experience to mutate (e.g. `'exp_0000000000'`).\n\t * @param commandId - `id` of the command or mapping to remove. Filtered from\n\t * both `voiceCommands` and `buttonMappings`, so a single call clears whichever\n\t * array holds it.\n\t * @param opts - Optional per-request overrides (`signal`, `headers`,\n\t * `idempotencyKey`).\n\t * @returns The resulting {@link CommandSet} with both arrays after removal.\n\t * @throws {EraAPIError} On a non-2xx read or write — `404` when the experience\n\t * does not exist (or is another user's private experience), `403` when the\n\t * caller lacks read access or write permission on the experience, `400` when\n\t * the resulting `stylePreferences` fails validation, `401` when\n\t * unauthenticated, `500` on a server fault.\n\t * @remarks\n\t * A `commandId` that matches nothing is a no-op that still performs the write\n\t * and returns the unchanged arrays — it does not throw.\n\t * @example\n\t * ```ts\n\t * const { voiceCommands, buttonMappings } = await commands(era).remove(\n\t * 'exp_0000000000',\n\t * 'btn_0000000000',\n\t * );\n\t * ```\n\t */\n\tremove(\n\t\texperienceId: string,\n\t\tcommandId: string,\n\t\topts?: RequestOptions,\n\t): Promise<CommandSet>;\n}\n\n// ─── Internal ───────────────────────────────────────────────────────────────\n\ntype HubRequestInit = RequestOptions & {\n\tmethod?: string;\n\tbody?: unknown;\n};\n\ninterface ExperienceStyleSlice {\n\tstylePreferences?: ExperienceStylePreferences | null;\n}\n\n/** Replace the element matching `id`, or append it. Returns a new array. */\nfunction upsertById<T extends { id: string }>(list: T[], item: T): T[] {\n\tconst idx = list.findIndex((x) => x.id === item.id);\n\tif (idx === -1) return [...list, item];\n\tconst next = list.slice();\n\tnext[idx] = item;\n\treturn next;\n}\n\n// ─── Factory ────────────────────────────────────────────────────────────────\n\n/**\n * Construct the `commands` module bound to an {@link EraClient} instance — the\n * typed CRUD surface over an experience's `voiceCommands` and `buttonMappings`.\n * Call it once per operation (`commands(era).list(id)`); it holds no state.\n *\n * @param client - The {@link EraClient} whose auth and base URL back every\n * `GET`/`PUT /experiences/{id}` this module issues.\n * @returns A {@link CommandsModule} bound to `client`.\n * @example\n * ```ts\n * const era = createEraClient({ auth: { mode: 'pkce', ... } });\n * await commands(era).upsertVoice('exp_0000000000', {\n * id: 'cmd_0000000000',\n * triggers: ['book a table'],\n * toolRef: 'reservations:create',\n * params: { time: { source: 'extracted_date_start' } },\n * });\n * ```\n */\nexport function commands(client: EraClient): CommandsModule {\n\tconst path = (id: string): string =>\n\t\thubPath(genGetExperiencesById, { path: { id } });\n\n\tasync function readStyle(\n\t\texperienceId: string,\n\t\topts?: RequestOptions,\n\t): Promise<ExperienceStylePreferences> {\n\t\tconst exp = await client.hub<ExperienceStyleSlice>(\n\t\t\tpath(experienceId),\n\t\t\topts,\n\t\t);\n\t\treturn { ...(exp.stylePreferences ?? {}) };\n\t}\n\n\tasync function writeStyle(\n\t\texperienceId: string,\n\t\tstyle: ExperienceStylePreferences,\n\t\topts?: RequestOptions,\n\t): Promise<void> {\n\t\tconst reqOpts: HubRequestInit = {\n\t\t\t...opts,\n\t\t\tmethod: 'PUT',\n\t\t\tbody: { stylePreferences: style },\n\t\t};\n\t\tawait client.hub<unknown>(path(experienceId), reqOpts);\n\t}\n\n\treturn {\n\t\tasync list(\n\t\t\texperienceId: string,\n\t\t\topts?: RequestOptions,\n\t\t): Promise<CommandSet> {\n\t\t\tconst style = await readStyle(experienceId, opts);\n\t\t\treturn {\n\t\t\t\tvoiceCommands: style.voiceCommands ?? [],\n\t\t\t\tbuttonMappings: style.buttonMappings ?? [],\n\t\t\t};\n\t\t},\n\t\tasync upsertVoice(\n\t\t\texperienceId: string,\n\t\t\tcommand: VoiceCommand,\n\t\t\topts?: RequestOptions,\n\t\t): Promise<VoiceCommand[]> {\n\t\t\tconst style = await readStyle(experienceId, opts);\n\t\t\tconst next = upsertById(style.voiceCommands ?? [], command);\n\t\t\tawait writeStyle(experienceId, { ...style, voiceCommands: next }, opts);\n\t\t\treturn next;\n\t\t},\n\t\tasync upsertButton(\n\t\t\texperienceId: string,\n\t\t\tmapping: ButtonMapping,\n\t\t\topts?: RequestOptions,\n\t\t): Promise<ButtonMapping[]> {\n\t\t\tconst style = await readStyle(experienceId, opts);\n\t\t\tconst next = upsertById(style.buttonMappings ?? [], mapping);\n\t\t\tawait writeStyle(experienceId, { ...style, buttonMappings: next }, opts);\n\t\t\treturn next;\n\t\t},\n\t\tasync remove(\n\t\t\texperienceId: string,\n\t\t\tcommandId: string,\n\t\t\topts?: RequestOptions,\n\t\t): Promise<CommandSet> {\n\t\t\tconst style = await readStyle(experienceId, opts);\n\t\t\tconst voiceCommands = (style.voiceCommands ?? []).filter(\n\t\t\t\t(c) => c.id !== commandId,\n\t\t\t);\n\t\t\tconst buttonMappings = (style.buttonMappings ?? []).filter(\n\t\t\t\t(m) => m.id !== commandId,\n\t\t\t);\n\t\t\tawait writeStyle(\n\t\t\t\texperienceId,\n\t\t\t\t{ ...style, voiceCommands, buttonMappings },\n\t\t\t\topts,\n\t\t\t);\n\t\t\treturn { voiceCommands, buttonMappings };\n\t\t},\n\t};\n}\n","// context module — the Context primitive (PRD #1 §5.3).\n//\n// `ContextSnapshot` is the frozen §5.3 data shape: a hybrid snapshot (the\n// surface attaches location/battery; the cloud enriches) that BOTH gates the\n// trigger and feeds the agent. The TYPE is frozen here because the Trigger\n// contract references it (`TriggerEvent.context`, ERA-4396). The GATE GRAMMAR —\n// `when()` / `when.semantic()` (ERA-4405) — is below; the cloud-side gate\n// evaluation at ingest (publish → evaluate → run/drop) is the era-ingress half.\n//\n// Tree-shakeable subpath import: `@era-laboratories/era-sdk/modules/context`.\n\nimport * as z from 'zod';\nimport type { Ctx, Predicate, RefNode } from './chains.js';\nimport { makeCtx } from './chains.js';\n\n/**\n * The device's network state at snapshot time:\n * - `'wifi'` — connected over Wi-Fi.\n * - `'cell'` — connected over a cellular network.\n * - `'offline'` — no network connectivity.\n */\nexport type Connectivity = 'wifi' | 'cell' | 'offline';\n\n/**\n * Coarse location plus an optional place label, as attached to a\n * {@link ContextSnapshot} by the surface.\n */\nexport interface ContextLocation {\n\t/** Latitude in decimal degrees. */\n\tlat: number;\n\t/** Longitude in decimal degrees. */\n\tlng: number;\n\t/** Place label — known values `home` | `work` | `gym`, or a custom string. */\n\tplace?: string;\n\t/** Location accuracy radius in meters, when the surface reports it. */\n\taccuracy?: number;\n}\n/** A device snapshot — battery and connectivity, as reported by the surface. */\nexport interface ContextDevice {\n\t/** Battery charge level as a percentage (0–100). */\n\tbatteryPct?: number;\n\t/** Current network connectivity of the device. */\n\tconnectivity?: Connectivity;\n}\n/**\n * A prior run of this experience, surfaced so a gate can dedup or run\n * already-fired checks against recent activity.\n */\nexport interface RecentRun {\n\t/** ID of the experience that ran. */\n\texperienceId: string;\n\t/** Terminal status of the run (e.g. `'completed'`, `'failed'`). */\n\tstatus: string;\n\t/** ISO-8601 timestamp of the run. */\n\tat: string;\n}\n/**\n * A pre-fetched, ranked set of memory items. Populated on a\n * {@link ContextSnapshot} only when the gate or goal references it.\n */\nexport interface MemoryDigest {\n\t/** Ranked memory items — each with its text and a relevance score. */\n\titems: { text: string; score: number }[];\n}\n\n/**\n * A hybrid context snapshot. The surface attaches a snapshot\n * (location, battery); the cloud enriches. `memoryDigest` is pre-fetched ONLY\n * when referenced by the gate or goal.\n */\nexport interface ContextSnapshot {\n\t/** ISO-8601 capture time of the snapshot. */\n\toccurredAt: string;\n\t/** Coarse location + place label, when the surface attaches it. */\n\tlocation?: ContextLocation;\n\t/** Device battery + connectivity, when the surface attaches it. */\n\tdevice?: ContextDevice;\n\t/** Recent runs of this experience, for dedup / already-fired checks. */\n\trecentRuns?: RecentRun[];\n\t/** Ranked memory digest — present only when the gate or goal references it. */\n\tmemoryDigest?: MemoryDigest;\n}\n\n/**\n * Zod schema for {@link ContextSnapshot}. Exported so callers can validate a\n * snapshot at their own boundary (e.g. before feeding a mock event into\n * `simulate`) or derive a type from it. The runtime shape mirrors the\n * `ContextSnapshot` interface exactly.\n *\n * @example\n * ```ts\n * const snapshot = contextSnapshotSchema.parse(incoming);\n * ```\n */\nexport const contextSnapshotSchema = z\n\t.object({\n\t\toccurredAt: z.iso\n\t\t\t.datetime({ offset: true })\n\t\t\t.meta({ description: 'ISO-8601 capture time of the snapshot.' }),\n\t\tlocation: z\n\t\t\t.object({\n\t\t\t\tlat: z.number().meta({ description: 'Latitude in decimal degrees.' }),\n\t\t\t\tlng: z.number().meta({ description: 'Longitude in decimal degrees.' }),\n\t\t\t\tplace: z.string().optional().meta({\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t'Place label — known values home | work | gym, or a custom string.',\n\t\t\t\t}),\n\t\t\t\taccuracy: z\n\t\t\t\t\t.number()\n\t\t\t\t\t.optional()\n\t\t\t\t\t.meta({ description: 'Location accuracy radius in meters.' }),\n\t\t\t})\n\t\t\t.optional()\n\t\t\t.meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Coarse location + a place label (home/work/gym or custom).',\n\t\t\t}),\n\t\tdevice: z\n\t\t\t.object({\n\t\t\t\tbatteryPct: z.number().optional().meta({\n\t\t\t\t\tdescription: 'Battery charge level as a percentage (0–100).',\n\t\t\t\t}),\n\t\t\t\tconnectivity: z\n\t\t\t\t\t.enum(['wifi', 'cell', 'offline'])\n\t\t\t\t\t.optional()\n\t\t\t\t\t.meta({ description: 'Current network connectivity of the device.' }),\n\t\t\t})\n\t\t\t.optional()\n\t\t\t.meta({ description: 'Device snapshot — battery + connectivity.' }),\n\t\trecentRuns: z\n\t\t\t.array(\n\t\t\t\tz.object({\n\t\t\t\t\texperienceId: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.meta({ description: 'ID of the experience that ran.' }),\n\t\t\t\t\tstatus: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.meta({ description: 'Terminal status of the run.' }),\n\t\t\t\t\tat: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.meta({ description: 'ISO-8601 timestamp of the run.' }),\n\t\t\t\t}),\n\t\t\t)\n\t\t\t.optional()\n\t\t\t.meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Recent runs of this experience (dedup / already-fired checks).',\n\t\t\t}),\n\t\tmemoryDigest: z\n\t\t\t.object({\n\t\t\t\titems: z\n\t\t\t\t\t.array(\n\t\t\t\t\t\tz.object({\n\t\t\t\t\t\t\ttext: z.string().meta({ description: 'The memory item text.' }),\n\t\t\t\t\t\t\tscore: z\n\t\t\t\t\t\t\t\t.number()\n\t\t\t\t\t\t\t\t.meta({ description: 'Relevance score of the memory item.' }),\n\t\t\t\t\t\t}),\n\t\t\t\t\t)\n\t\t\t\t\t.meta({ description: 'Ranked memory items in the digest.' }),\n\t\t\t})\n\t\t\t.optional()\n\t\t\t.meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Pre-fetched memory digest — populated ONLY when the gate/goal references it.',\n\t\t\t}),\n\t})\n\t.meta({\n\t\tid: 'ContextSnapshot',\n\t\ttitle: 'Context snapshot',\n\t\tdescription:\n\t\t\t'Hybrid context (PRD §5.3) that gates the trigger and feeds the agent.',\n\t});\n\n// ─── Gate grammar (ERA-4405) ──────────────────────────────────────────────────\n\n/**\n * A run-or-drop condition authored against the {@link ContextSnapshot}.\n * `when(...)` is the deterministic default; `when.semantic(...)` is the\n * expensive, model-classified path. Today a gate is consumed by local\n * simulation (`simulate`) — it is not yet enforced server-side at trigger\n * ingest.\n */\nexport type ContextGate =\n\t| { kind: 'deterministic'; predicate: Predicate }\n\t| { kind: 'semantic'; prompt: string };\n\n/**\n * A `Predicate`, a bare `$.` ref (→ truthy), or an `($) => …` closure returning\n * either. Predicates are authored against the {@link ContextSnapshot}\n * (e.g. `$.location.place`).\n */\nexport type GatePredicateInput =\n\t| Predicate\n\t| RefNode\n\t| ((ctx: Ctx) => Predicate | RefNode);\n\nconst isRefNode = (v: unknown): v is RefNode =>\n\ttypeof v === 'object' && v !== null && '$ref' in v;\n\nfunction deterministicGate(input: GatePredicateInput): ContextGate {\n\tconst resolved = typeof input === 'function' ? input(makeCtx()) : input;\n\tconst predicate: Predicate = isRefNode(resolved)\n\t\t? { truthy: resolved }\n\t\t: resolved;\n\t// Materialize ref proxies → plain {\"$ref\"} objects (mirrors defineChain).\n\tconst materialized = JSON.parse(JSON.stringify(predicate)) as Predicate;\n\treturn { kind: 'deterministic', predicate: materialized };\n}\n\n/**\n * The context gate grammar. `when(($) => …)` builds a deterministic,\n * serializable gate over the {@link ContextSnapshot} (reusing the chains\n * `Predicate`/`$` ref system); `when.semantic('…')` builds the expensive\n * model-classified gate. Deterministic is the default.\n *\n * @param input - For the deterministic form: a {@link Predicate}, a bare `$.`\n * ref (treated as a truthy check), or an `($) => …` closure over the context\n * snapshot returning either. Predicate combinators:\n * `eq` / `neq` / `gt` / `lt` / `exists` / `truthy` / `and` / `or` / `not`.\n * @returns A serializable {@link ContextGate} to set as the `gate` field of\n * `defineExperience({ gate })`.\n * @remarks A deterministic gate can be evaluated locally by `simulate` against a\n * mock event's snapshot; a semantic gate cannot, so simulation reports it\n * undecided and assumes the run proceeds. Reach for `when.semantic` only when\n * the condition can't be expressed as a deterministic predicate — it is the\n * expensive path. Note that today the gate is consumed by local simulation\n * only: publishing an Experience does not send the gate to the server, so\n * neither flavor is enforced at trigger ingest yet.\n * @example\n * ```ts\n * // Deterministic: run only when the snapshot says the user is home.\n * const gate = when(($) => ({ eq: [$.location.place, 'home'] }));\n *\n * // Semantic: a natural-language condition classified server-side.\n * const winding = when.semantic('the user appears to be winding down for the evening');\n * ```\n */\nexport const when = Object.assign(deterministicGate, {\n\tsemantic: (prompt: string): ContextGate => ({ kind: 'semantic', prompt }),\n});\n\n/** A materialized predicate — a plain JSON object (refs collapsed to `{$ref}`). */\nconst predicateSchema = z.record(z.string(), z.json());\n\n/**\n * Zod schema for {@link ContextGate}. A discriminated union on `kind`:\n * `'deterministic'` carries a materialized `predicate` object, `'semantic'`\n * carries a non-empty `prompt` string. Exported so callers can validate a gate\n * at their own boundary; the runtime shape mirrors the `ContextGate` type.\n *\n * @example\n * ```ts\n * const gate = contextGateSchema.parse(incoming);\n * ```\n */\nexport const contextGateSchema = z\n\t.discriminatedUnion('kind', [\n\t\tz.object({\n\t\t\tkind: z.literal('deterministic').meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Gate kind discriminant — the deterministic predicate gate.',\n\t\t\t}),\n\t\t\tpredicate: predicateSchema,\n\t\t}),\n\t\tz.object({\n\t\t\tkind: z.literal('semantic').meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Gate kind discriminant — the model-classified semantic gate.',\n\t\t\t}),\n\t\t\tprompt: z.string().min(1).meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Natural-language prompt evaluated by the semantic (model-classified) gate.',\n\t\t\t}),\n\t\t}),\n\t])\n\t.meta({\n\t\tid: 'ContextGate',\n\t\ttitle: 'Context gate',\n\t\tdescription:\n\t\t\t'Gates the trigger at ingest (PRD §5.3). Deterministic when() (default) or the expensive semantic when.semantic().',\n\t});\n","// conversation — high-level browser voice helpers built on `voice`.\n//\n// Two pieces, both browser-only (they use getUserMedia / AudioWorklet / WebRTC):\n//\n// captureMicrophone() — turn the mic into a stream of 16-bit LE PCM chunks\n// suitable for `transcribeLive().sendAudio()`. The\n// AudioWorklet processor is inlined as a Blob URL, so\n// there is NO separate file for the app to serve.\n//\n// startConversation() — wire the whole loop in one call: STT (transcribeLive)\n// → sendText → agent TTS over LiveKit. Sane defaults\n// (publishMicrophone:true so the agent dispatches; a\n// unique deviceId so connections don't DUPLICATE_IDENTITY-\n// kick each other). Returns a push-to-talk handle.\n\nimport type { EraClient } from '../core/client.js';\nimport type { StreamEvent } from '../core/streaming.js';\nimport {\n\ttype LiveTranscribeSession,\n\ttype VoiceAck,\n\ttype VoiceAgentState,\n\ttype VoiceConnectionState,\n\ttype VoiceSession,\n\tvoice,\n} from './voice.js';\n\n// ─── microphone capture ──────────────────────────────────────────────────────\n\n/**\n * The AudioWorklet processor, inlined. Buffers Float32 mic samples and posts\n * 16-bit LE PCM chunks (~128ms at 16kHz) to the main thread. Pause/resume keep\n * the graph warm so the first chunk after a hold has zero startup lag.\n */\nconst PCM_WORKLET_SRC = `\nclass EraPcmProcessor extends AudioWorkletProcessor {\n constructor() {\n super();\n this.size = 2048;\n this.buf = new Float32Array(this.size);\n this.i = 0;\n this.paused = false;\n this.port.onmessage = (e) => {\n if (e.data && e.data.type === 'pause') this.paused = true;\n else if (e.data && e.data.type === 'resume') { this.paused = false; this.buf = new Float32Array(this.size); this.i = 0; }\n };\n }\n process(inputs) {\n if (this.paused) return true;\n const ch = inputs[0] && inputs[0][0];\n if (!ch) return true;\n for (let n = 0; n < ch.length; n++) {\n this.buf[this.i++] = ch[n];\n if (this.i >= this.size) {\n const pcm = new Int16Array(this.size);\n for (let j = 0; j < this.size; j++) {\n const s = Math.max(-1, Math.min(1, this.buf[j]));\n pcm[j] = Math.round(s * 32767);\n }\n this.port.postMessage({ pcm: pcm.buffer }, [pcm.buffer]);\n this.buf = new Float32Array(this.size);\n this.i = 0;\n }\n }\n return true;\n }\n}\nregisterProcessor('era-pcm-processor', EraPcmProcessor);\n`;\n\n/** Options for {@link captureMicrophone}. */\nexport interface MicCaptureOptions {\n\t/** Capture sample rate (Hz). Default 16000 — what server STT expects. */\n\tsampleRate?: number;\n\t/** Called with each 16-bit LE mono PCM chunk (`ArrayBuffer`). */\n\tonAudio: (pcm: ArrayBuffer) => void;\n\t/** Called if mic acquisition / worklet setup fails. */\n\tonError?: (err: Error) => void;\n}\n\n/** Handle returned by {@link captureMicrophone}. */\nexport interface MicCapture {\n\t/** Acquire the mic and start the audio graph (resolves once running). */\n\tstart(): Promise<void>;\n\t/** Stop emitting chunks but keep the graph warm for instant resume. */\n\tpause(): void;\n\t/** Resume emitting chunks after a pause. */\n\tresume(): void;\n\t/** Tear down: stop the mic track, the worklet, and the AudioContext. */\n\tstop(): void;\n}\n\n/**\n * Capture the browser microphone as 16-bit LE mono PCM, ready to pipe straight\n * into `voice(client).transcribeLive().sendAudio()`. The AudioWorklet processor\n * is inlined (Blob URL) so there is nothing for the app to host.\n *\n * @param opts - Capture configuration; see {@link MicCaptureOptions}. `onAudio`\n * is required and receives each 16-bit LE mono PCM chunk; `sampleRate` defaults\n * to 16000 Hz (what server STT expects) and `onError` is optional.\n * @returns A {@link MicCapture} handle with `start` / `pause` / `resume` /\n * `stop`. No audio is captured until `start()` resolves; chunks stop between\n * `pause()` and `resume()` while the audio graph stays warm.\n * @throws {DOMException} Propagated from `start()` (not thrown here) when the\n * browser denies microphone access or the AudioWorklet fails to initialize;\n * `onError` is also invoked (with the failure wrapped in an `Error` when it\n * isn't one already).\n *\n * @example\n * ```ts\n * const stt = await voice(era).transcribeLive({ sessionId, onFinal });\n * const mic = captureMicrophone({ onAudio: (pcm) => stt.sendAudio(pcm) });\n * await mic.start(); // hold-to-talk down\n * // mic.pause() // hold-to-talk up\n * ```\n */\nexport function captureMicrophone(opts: MicCaptureOptions): MicCapture {\n\tlet stream: MediaStream | null = null;\n\tlet ctx: AudioContext | null = null;\n\tlet node: AudioWorkletNode | null = null;\n\tlet workletUrl: string | null = null;\n\tlet recording = false;\n\n\treturn {\n\t\tasync start(): Promise<void> {\n\t\t\ttry {\n\t\t\t\tconst sampleRate = opts.sampleRate ?? 16_000;\n\t\t\t\tstream = await navigator.mediaDevices.getUserMedia({\n\t\t\t\t\taudio: {\n\t\t\t\t\t\tchannelCount: 1,\n\t\t\t\t\t\tsampleRate,\n\t\t\t\t\t\techoCancellation: true,\n\t\t\t\t\t\tnoiseSuppression: true,\n\t\t\t\t\t\t// Soft / far speech under-amplifies and won't transcribe;\n\t\t\t\t\t\t// explicit constraints suppress browser defaults, so request it.\n\t\t\t\t\t\tautoGainControl: true,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\tctx = new AudioContext({ sampleRate });\n\t\t\t\tworkletUrl = URL.createObjectURL(\n\t\t\t\t\tnew Blob([PCM_WORKLET_SRC], { type: 'application/javascript' }),\n\t\t\t\t);\n\t\t\t\tawait ctx.audioWorklet.addModule(workletUrl);\n\t\t\t\tconst source = ctx.createMediaStreamSource(stream);\n\t\t\t\tnode = new AudioWorkletNode(ctx, 'era-pcm-processor');\n\t\t\t\tnode.port.onmessage = (e: MessageEvent) => {\n\t\t\t\t\tif (recording) opts.onAudio(e.data.pcm as ArrayBuffer);\n\t\t\t\t};\n\t\t\t\tsource.connect(node);\n\t\t\t\trecording = true;\n\t\t\t} catch (err) {\n\t\t\t\topts.onError?.(err instanceof Error ? err : new Error(String(err)));\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t},\n\t\tpause(): void {\n\t\t\trecording = false;\n\t\t\tnode?.port.postMessage({ type: 'pause' });\n\t\t},\n\t\tresume(): void {\n\t\t\tnode?.port.postMessage({ type: 'resume' });\n\t\t\tif (ctx?.state === 'suspended') void ctx.resume();\n\t\t\trecording = true;\n\t\t},\n\t\tstop(): void {\n\t\t\trecording = false;\n\t\t\tstream?.getTracks().forEach((t) => {\n\t\t\t\tt.stop();\n\t\t\t});\n\t\t\tnode?.disconnect();\n\t\t\tif (ctx) void ctx.close();\n\t\t\tif (workletUrl) URL.revokeObjectURL(workletUrl);\n\t\t\tstream = null;\n\t\t\tctx = null;\n\t\t\tnode = null;\n\t\t\tworkletUrl = null;\n\t\t},\n\t};\n}\n\n// ─── startConversation ───────────────────────────────────────────────────────\n\n/** Options for {@link startConversation}. */\nexport interface StartConversationOptions {\n\t/** Experience to talk to. Omit to use the api key's default persona. */\n\texperienceId?: string;\n\t/** TTS voice config, e.g. `{ ttsConfig: { model, voice } }`. */\n\tvoiceConfig?: { ttsConfig?: Record<string, unknown> | null };\n\t/**\n\t * Override the client used for the STT WebSocket. Browser WebSockets can't\n\t * traverse a path-rewriting HTTP proxy, so if your main `client` points at\n\t * such a proxy, pass a second client here aimed straight at the API origin\n\t * (with a usable token). Defaults to the main `client`.\n\t */\n\tsttClient?: EraClient;\n\t/** Token for the STT socket `?token=` (defaults to the STT client's token). */\n\tsttToken?: string;\n\t/** STT sample rate. Default 16000. */\n\tsampleRate?: number;\n\t/** STT language (BCP-47). Default `'en-US'`. */\n\tlanguage?: string;\n\t/** Capture + stream the mic automatically. Default true. Set false to drive STT yourself. */\n\tmicrophone?: boolean;\n\t/** Participant identity for the LiveKit room; defaults to a fresh unique id (prevents `DUPLICATE_IDENTITY` when reconnecting). */\n\tdeviceId?: string;\n\t/** Force TURN relay for the LiveKit media. Default false. */\n\tforceTurnRelay?: boolean;\n\t/** Fire AGENT_NOT_PRESENT if no agent joins within this many ms. Default 12000. */\n\tagentJoinTimeoutMs?: number;\n\t/** Your speech, transcribed (`final` flips true on the committed segment). */\n\tonUserTranscript?: (text: string, final: boolean) => void;\n\t/** The agent's streamed reply (same events as `session.onResponse`). */\n\tonReply?: (event: StreamEvent) => void;\n\t/**\n\t * Connection errors. Common `code`s: `DUPLICATE_IDENTITY` (another\n\t * participant is using this id), `AGENT_NOT_PRESENT` (the backend agent\n\t * didn't join), `MIC_ERROR` (microphone access failed), `STT_ERROR`\n\t * (transcription failed), `SEND_TEXT_FAILED` (auto-submitting a\n\t * finalized transcript to the agent threw), and `AGENT_LEFT` (recoverable —\n\t * the agent left mid-session, e.g. after idle eviction).\n\t */\n\tonError?: (err: { message: string; code?: string }) => void;\n\t/** Connection state transitions for the LiveKit session. */\n\tonConnectionState?: (state: VoiceConnectionState) => void;\n\t/** Agent state transitions (listening, thinking, speaking, …). */\n\tonAgentState?: (state: VoiceAgentState) => void;\n}\n\n/** A live conversation handle returned by {@link startConversation}. */\nexport interface Conversation {\n\t/** The underlying LiveKit voice session (sendText / interrupt / meta / …). */\n\treadonly session: VoiceSession;\n\t/** The underlying STT socket. */\n\treadonly stt: LiveTranscribeSession;\n\t/** Push-to-talk down — resume the mic so speech streams to STT. */\n\tstartTalking(): void;\n\t/**\n\t * Push-to-talk up — pause the mic and finalize the STT segment. The final\n\t * transcript is auto-submitted via `sendText`, driving the agent.\n\t */\n\tstopTalking(): void;\n\t/** Send a typed turn (bypasses STT). */\n\tsendText(text: string): Promise<VoiceAck>;\n\t/** Barge-in: stop the agent's current TTS. */\n\tinterrupt(): Promise<VoiceAck>;\n\t/** Tear everything down (mic, STT socket, LiveKit session). */\n\tend(): Promise<void>;\n}\n\nfunction uniqueDeviceId(): string {\n\tconst c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n\tconst id =\n\t\tc?.randomUUID?.() ?? `${Math.random().toString(36).slice(2)}xxxxxxxx`;\n\treturn `web-${id.replace(/-/g, '').slice(0, 12)}`;\n}\n\n/**\n * Start a full voice conversation in one call: server-side STT (`transcribeLive`)\n * feeds `sendText`, and the agent replies with TTS audio over LiveKit (played by\n * the SDK-managed audio element). Returns a push-to-talk {@link Conversation}.\n *\n * Defaults are chosen to avoid the footguns: `publishMicrophone:true` (so the\n * backend dispatches an agent) and a unique `deviceId` (so overlapping\n * connections don't kick each other with DUPLICATE_IDENTITY).\n *\n * @remarks\n * Browser-only — it uses `getUserMedia`, `AudioWorklet`, and WebRTC. When\n * `microphone` is left at its default (`true`) the mic is acquired during setup\n * but held paused until the first `startTalking()`, so audio only streams while\n * push-to-talk is held. Each finalized STT transcript is auto-submitted to the\n * agent via `sendText`; a failure there is reported through `onError` with code\n * `SEND_TEXT_FAILED` rather than rejecting.\n *\n * @param client - The `EraClient` used for the LiveKit session and, unless\n * `opts.sttClient` overrides it, the STT WebSocket.\n * @param opts - Conversation configuration and lifecycle callbacks; see\n * {@link StartConversationOptions}. All fields are optional.\n * @returns A live {@link Conversation} handle with push-to-talk controls\n * (`startTalking` / `stopTalking`), `sendText`, `interrupt`, and `end`, plus\n * the underlying `session` and `stt` objects. Resolves once the LiveKit\n * session is connected, the STT socket is opening (audio sent before it\n * finishes connecting is buffered, so no speech is lost), and (by default)\n * the mic graph is running but paused.\n * @throws When setup fails — the LiveKit session can't connect (e.g. a bad\n * token), the STT pre-flight rejects, or the browser denies microphone\n * access — the returned promise rejects; wrap the call in `try/catch`.\n * Failures after setup (dropped connections, STT socket errors) are reported\n * through `onError` instead of rejecting.\n *\n * @example\n * ```ts\n * const convo = await startConversation(era, {\n * experienceId,\n * onUserTranscript: (t, final) => final && console.log('you:', t),\n * onReply: (ev) => ev.type === 'done' && console.log('agent:', ev.fullContent),\n * });\n * // hold-to-talk: convo.startTalking() … convo.stopTalking()\n * // or typed: convo.sendText('hello')\n * ```\n */\nexport async function startConversation(\n\tclient: EraClient,\n\topts: StartConversationOptions = {},\n): Promise<Conversation> {\n\t// 1. LiveKit session for the agent's TTS + turn submission.\n\tconst session = await voice(client).connect({\n\t\t...(opts.experienceId !== undefined && { experienceId: opts.experienceId }),\n\t\t...(opts.voiceConfig !== undefined && { voiceConfig: opts.voiceConfig }),\n\t\tpublishMicrophone: true,\n\t\tdeviceId: opts.deviceId ?? uniqueDeviceId(),\n\t\t...(opts.forceTurnRelay !== undefined && {\n\t\t\tforceTurnRelay: opts.forceTurnRelay,\n\t\t}),\n\t\t...(opts.agentJoinTimeoutMs !== undefined && {\n\t\t\tagentJoinTimeoutMs: opts.agentJoinTimeoutMs,\n\t\t}),\n\t\t...(opts.onConnectionState && {\n\t\t\tonConnectionState: opts.onConnectionState,\n\t\t}),\n\t\t...(opts.onAgentState && { onAgentState: opts.onAgentState }),\n\t\t...(opts.onError && { onError: opts.onError }),\n\t});\n\tif (opts.onReply) session.onResponse(opts.onReply);\n\n\t// 2. Server STT socket → auto-submit each final transcript as a turn.\n\tconst sttClient = opts.sttClient ?? client;\n\tconst sampleRate = opts.sampleRate ?? 16_000;\n\tconst stt = await voice(sttClient).transcribeLive({\n\t\tsessionId: String(session.sessionId ?? session.roomName ?? 'stt'),\n\t\tsampleRate,\n\t\t...(opts.language !== undefined && { language: opts.language }),\n\t\t...(opts.sttToken !== undefined && { token: opts.sttToken }),\n\t\tonInterim: (text) => opts.onUserTranscript?.(text, false),\n\t\tonFinal: (text) => {\n\t\t\tconst said = text.trim();\n\t\t\tif (!said) return;\n\t\t\topts.onUserTranscript?.(said, true);\n\t\t\tvoid session.sendText(said).catch((e: unknown) =>\n\t\t\t\topts.onError?.({\n\t\t\t\t\tmessage: e instanceof Error ? e.message : String(e),\n\t\t\t\t\tcode: 'SEND_TEXT_FAILED',\n\t\t\t\t}),\n\t\t\t);\n\t\t},\n\t\t...(opts.onError && {\n\t\t\tonError: (m: string) => opts.onError?.({ message: m, code: 'STT_ERROR' }),\n\t\t}),\n\t});\n\n\t// 3. Mic capture (default on) → feed STT. Warm but paused until startTalking().\n\tconst useMic = opts.microphone !== false;\n\tconst mic = useMic\n\t\t? captureMicrophone({\n\t\t\t\tsampleRate,\n\t\t\t\tonAudio: (pcm) => stt.sendAudio(pcm),\n\t\t\t\t...(opts.onError && {\n\t\t\t\t\tonError: (e: Error) =>\n\t\t\t\t\t\topts.onError?.({ message: e.message, code: 'MIC_ERROR' }),\n\t\t\t\t}),\n\t\t\t})\n\t\t: null;\n\tif (mic) {\n\t\tawait mic.start();\n\t\tmic.pause();\n\t}\n\n\treturn {\n\t\tsession,\n\t\tstt,\n\t\tstartTalking(): void {\n\t\t\tmic?.resume();\n\t\t},\n\t\tstopTalking(): void {\n\t\t\tmic?.pause();\n\t\t\tstt.endOfSpeech();\n\t\t},\n\t\tsendText(text: string): Promise<VoiceAck> {\n\t\t\treturn session.sendText(text);\n\t\t},\n\t\tinterrupt(): Promise<VoiceAck> {\n\t\t\treturn session.interrupt();\n\t\t},\n\t\tasync end(): Promise<void> {\n\t\t\tmic?.stop();\n\t\t\tstt.close();\n\t\t\tawait session.disconnect();\n\t\t},\n\t};\n}\n","// deviceStream module — the device WS command receiver (ERA-6031).\n//\n// The receiving half of the cross-device story: a JS-capable device (Pi,\n// desktop, kiosk) connects to the era-ingress device gateway\n// (`WSS /devices/{deviceId}/stream`, prefix-free per ERA-4428) with a\n// device-bound `era_sk_*` key and yields the typed command frames a chain's\n// `builtin:emit-output` publishes — `actuator` (ERA-4402) and `say`\n// (ERA-6126/6129) — while sending inbound `event`/`sensor` frames up the same\n// socket.\n//\n// Reliability contract (matches the gateway's Redis mailbox, ERA-6017/6018):\n// - Delivery is at-least-once. A frame un-acked when the socket drops is\n// redelivered on the NEXT socket. The dedup LRU therefore lives on the\n// DeviceStream (which owns the reconnect loop), NOT the transient socket —\n// it must be non-empty exactly when the post-reconnect duplicate arrives.\n// - Frames carrying an `idempotencyKey` are acked with\n// `{type:\"output.ack\", idempotencyKey}` as soon as they pass dedup, which\n// releases them from the server's `:proc` processing list. Dedup absorbs\n// the redelivery window between receipt and ack.\n// - The gateway pings every ~30s with `{type:\"ping\"}`; the stream answers\n// `{type:\"pong\"}` automatically.\n// - Reconnect is exponential backoff + full jitter, on by default. Close\n// code 1008 (auth rejection) NEVER reconnects — a bad key is surfaced,\n// not retried.\n//\n// Tree-shakeable subpath import:\n// `@era-laboratories/era-sdk/modules/deviceStream`.\n\nimport * as z from 'zod';\nimport type { WithRequired } from '../core/casing.js';\nimport type { EraClient } from '../core/client.js';\nimport type { RequestOptions } from '../core/types.js';\nimport { postDeviceKeysRenew as genPostDeviceKeysRenew } from '../generated/hub-api/sdk.gen.js';\nimport type { RenewDeviceKeyResponse } from '../generated/hub-api/types.gen.js';\nimport type {\n\tActuatorCommand as GenActuatorCommand,\n\tDeviceEventFrame as GenDeviceEventFrame,\n\tDeviceSensorFrame as GenDeviceSensorFrame,\n\tKeyStatus as GenKeyStatus,\n\tSayCommand as GenSayCommand,\n} from '../generated/ingress/types.gen.js';\nimport {\n\tzActuatorCommand,\n\tzDeviceCommand,\n\tzKeyStatus,\n\tzSayCommand,\n} from '../generated/ingress/zod.gen.js';\nimport { hubPath } from './hubPaths.js';\nimport type { DeviceKeyStore } from './keyStore.js';\n\n// ─── Frame contracts (server → device) ───────────────────────────────────────\n//\n// ERA-6206: the device WS frames are normative components in the ingress spec\n// — the SDK types are DERIVED from `src/generated/ingress/types.gen.ts` (the\n// wire truth), not re-declared. As of ingress v19 the generated frames mark the\n// `type` discriminator REQUIRED, so the `WithRequired<…, 'type'>` aliases below\n// are no-ops retained for API stability (the exported names predate v19).\n\n/**\n * An actuator command frame — targets a single physical actuator on the\n * device with a field map (`actuatorId` + catalog-open `fields`).\n * `idempotencyKey` is ALWAYS present (value `null` when the command carries no\n * key); `receiptToken` / `redelivery` / `seq` are omitted when absent;\n * `timestamp` is always present.\n */\nexport type ActuatorCommand = WithRequired<GenActuatorCommand, 'type'>;\n\n/**\n * A say command frame — device-targeted text to render or speak. Unlike\n * {@link ActuatorCommand}, say frames carry no `receiptToken`;\n * `audioRef`/`audioMime` are present only when server-side audio synthesis\n * succeeded — fetch `audioRef` promptly (its signed URL expires on a\n * minutes scale, and a redelivered frame carries a fresh URL) and only from\n * Google Cloud Storage hosts.\n */\nexport type SayCommand = WithRequired<GenSayCommand, 'type'>;\n\n/**\n * The discriminated union of consumer-facing command frames — what you\n * async-iterate off a {@link DeviceStream}. Narrow on `type` (`'actuator'` |\n * `'say'`) to handle each; control frames (ping, key_status, error) never\n * reach this union.\n */\nexport type DeviceCommand = ActuatorCommand | SayCommand;\n\n/**\n * Runtime zod validator for the `actuator` command frame\n * ({@link ActuatorCommand}). Use it to parse or narrow a raw frame outside the\n * stream's own iteration (the stream validates internally; you rarely need\n * this directly).\n */\nexport const actuatorCommandSchema = zActuatorCommand;\n/**\n * Runtime zod validator for the `say` command frame ({@link SayCommand}).\n * Use it to parse or narrow a raw frame outside the stream's own iteration\n * (the stream validates internally; you rarely need this directly).\n */\nexport const sayCommandSchema = zSayCommand;\n/**\n * Runtime zod validator for the full {@link DeviceCommand} union — a\n * discriminated union on `type` (`'actuator'` | `'say'`).\n */\nexport const deviceCommandSchema = zDeviceCommand.meta({\n\tid: 'DeviceCommand',\n\ttitle: 'DeviceCommand',\n\tdescription:\n\t\t'The union of consumer-facing device command frames (actuator | say) yielded by deviceStream — the generated spec schemas; ping/key_status control frames are absorbed internally.',\n});\n\n// Control frames handled internally, never yielded: the liveness ping and the\n// key-rotation advisory (ERA-6037; surfaced via onKeyStatus, not the iterator).\n// key_status is spec-derived (zKeyStatus, ERA-6206).\nconst pingFrameSchema = z.object({ type: z.literal('ping') });\nconst keyStatusFrameSchema = zKeyStatus;\n// Inbound error advisory (era-ingress half of ERA-6202): the gateway rejects a\n// malformed inbound frame with `{type:'error', code, detail, name?}` instead of\n// silently dropping it. Tolerant parse (z.object ignores unknown keys) so a\n// server that adds fields later doesn't break this path. Surfaced via onError,\n// never yielded to the command iterator.\nconst errorFrameSchema = z.object({\n\ttype: z.literal('error'),\n\tcode: z.string(),\n\tdetail: z.string(),\n\tname: z.string().optional(),\n});\n\n// ─── Inbound frames (device → server) ────────────────────────────────────────\n//\n// ERA-6206 split: the SDK's former single `DeviceEventFrame` conflated the\n// gateway's two distinct inbound validators (`_InboundEvent` /\n// `_InboundSensor`, both `extra='forbid'`) — it could not represent a valid\n// sensor frame. The generated spec models them separately; the SDK adopts the\n// split: `DeviceEventFrame` (event: `name`/`data`) + `DeviceSensorFrame`\n// (sensor: `sensorId`/`value`/`unit`), unioned as `DeviceInboundFrame`.\n\n/**\n * An inbound `event` frame the device publishes up its socket — a named\n * occurrence with an optional data payload. Carries `name`/`data` and forbids\n * the sensor fields.\n */\nexport type DeviceEventFrame = GenDeviceEventFrame;\n\n/**\n * An inbound `sensor` frame the device publishes up its socket — a single\n * sensor reading. Carries `sensorId`/`value`/`unit` and forbids `name`/`data`.\n */\nexport type DeviceSensorFrame = GenDeviceSensorFrame;\n\n/** Any inbound frame `DeviceStream.send` accepts (event or sensor). */\nexport type DeviceInboundFrame = DeviceEventFrame | DeviceSensorFrame;\n\n// ─── Options / handle ────────────────────────────────────────────────────────\n\n/**\n * Key-rotation advisory the gateway sends periodically over the socket\n * (`{ exp, renewAfter }`, both epoch seconds): renew once `now >= renewAfter`.\n * Surfaced via {@link DeviceStreamCommonOptions.onKeyStatus}; with\n * `autoRenew: true` the stream acts on it automatically.\n */\nexport type KeyStatus = GenKeyStatus;\n\n/**\n * The successor key minted by an auto-renew — the subset of the key-renewal\n * response the stream surfaces. By the time\n * {@link DeviceStreamCommonOptions.onKeyRenewed} fires, the key has already\n * been durably persisted to the {@link DeviceKeyStore} and activated — this\n * callback is notify-only (logging, fleet telemetry). The predecessor is\n * revoked the moment this key first authenticates.\n */\nexport type RenewedDeviceKey = Pick<\n\tRenewDeviceKeyResponse,\n\t'key' | 'expiresAt' | 'deviceId'\n>;\n\ninterface DeviceStreamCommonOptions {\n\t/**\n\t * Bearer for the socket (`era_sk_*` device-bound key). In Node it rides\n\t * the `Authorization` upgrade header; in browsers — which can't set\n\t * WebSocket headers — it falls back to a `?token=` query param.\n\t * Precedence: `token` → the `keyStore`'s stored key (`pending` slot\n\t * first, then `current`) → the client's access token.\n\t */\n\ttoken?: string;\n\t/** Reconnect with exponential backoff + jitter. Default true. */\n\treconnect?: boolean;\n\t/** Absorb at-least-once duplicates via an idempotencyKey LRU. Default true. */\n\tdedupe?: boolean;\n\t/** Dedup LRU capacity. Default 256. */\n\tdedupeCapacity?: number;\n\t/**\n\t * Bounded FIFO capacity for inbound frames `send()` before the socket is OPEN\n\t * (or while it reconnects). Buffered frames flush in insertion order on the\n\t * next open; an overflow throws. Default 64.\n\t */\n\tsendBufferCapacity?: number;\n\t/** Base reconnect delay in ms (doubles per attempt, full jitter). Default 500. */\n\tbackoffBaseMs?: number;\n\t/** Reconnect delay ceiling in ms. Default 30_000. */\n\tbackoffMaxMs?: number;\n\t/**\n\t * Execute-and-ack handler for delivery receipts. When provided the\n\t * stream runs in HANDLER MODE: every command frame is passed here instead\n\t * of the async iterator, and the execution ack is sent automatically when\n\t * the handler settles:\n\t *\n\t * - resolves → `status:'ok'` → `output_delivered`\n\t * - throws {@link CommandRejectedError} → `status:'rejected'` (terminal,\n\t * the server will NOT retry — use for malformed/unexecutable commands)\n\t * - throws anything else → `status:'error'` (retryable — the server\n\t * re-publishes with backoff)\n\t *\n\t * Without a handler (iterator mode), call {@link DeviceStream.ack} after\n\t * executing each command — otherwise the server times the receipt out and\n\t * re-delivers.\n\t */\n\tonExecute?: (cmd: DeviceCommand) => void | Promise<void>;\n\t/**\n\t * Delivery-receipts completion hook: called after a command has been\n\t * executed (handler mode — after `onExecute` resolves) or yielded to the\n\t * consumer (iterator mode).\n\t */\n\tonDelivered?: (cmd: DeviceCommand) => void;\n\t/** Called with each {@link KeyStatus} key-rotation advisory frame. */\n\tonKeyStatus?: (status: KeyStatus) => void;\n\t/**\n\t * Notify-only: fired after an auto-renewed successor key has been durably\n\t * persisted to the {@link DeviceKeyStore} AND activated. Persistence is the\n\t * store's job, not this callback's. Fired only in autoRenew mode.\n\t */\n\tonKeyRenewed?: (renewed: RenewedDeviceKey) => void;\n\t/** Socket-level errors and auth rejections. */\n\tonError?: (err: { code?: number; reason?: string; message: string }) => void;\n\t/** Abort to close the stream (equivalent to `close()`). */\n\tsignal?: AbortSignal;\n}\n\n/**\n * Options for `connect()`. Discriminated on `autoRenew`: enabling\n * auto-renewal REQUIRES a durable {@link DeviceKeyStore} — the SDK will not\n * activate a successor key it cannot prove was persisted, because a device\n * that restarts holding only its rotated-out predecessor trips the server's\n * reuse detection and is flagged `credential_compromised`.\n *\n * With `autoRenew: true` the stream drives the full two-slot rotation\n * protocol on a `key_status` advisory past `renewAfter`:\n *\n * 1. `POST /hub-api/device-keys/renew` (authed as the current key)\n * 2. `await keyStore.savePending(successor)` + read-back verification —\n * activation is GATED on this; a failing store degrades to advisory\n * (surfaced via `onError`, the live stream is never killed)\n * 3. swap the connection token and proactively cycle the socket so the\n * successor authenticates immediately (inside the predecessor's grace\n * window) instead of waiting for a natural reconnect\n * 4. on the successor's first successful open, `await keyStore.promote()`\n *\n * Boot/connect side: when no explicit `token` is given the store's `pending`\n * slot is presented FIRST, then `current` (crash recovery for a reboot\n * between activation and promotion — presenting a rotated-out `current`\n * first would trip reuse detection). A first-connect 1008 falls back ONCE to\n * the other slot.\n */\nexport type DeviceStreamOptions = DeviceStreamCommonOptions &\n\t(\n\t\t| {\n\t\t\t\t/** Auto-renewal off (default): `key_status` advisories are surfaced via `onKeyStatus` only. */\n\t\t\t\tautoRenew?: false;\n\t\t\t\tkeyStore?: DeviceKeyStore;\n\t\t }\n\t\t| {\n\t\t\t\t/** Enable hands-free key rotation. REQUIRES a durable `keyStore`. */\n\t\t\t\tautoRenew: true;\n\t\t\t\t/** Durable two-slot store the rotation protocol writes through. */\n\t\t\t\tkeyStore: DeviceKeyStore;\n\t\t }\n\t);\n\n/**\n * The live connection state of a {@link DeviceStream}, readable at any time via\n * `stream.state`. Use it to drive device UI/telemetry (e.g. show a \"reconnecting\"\n * indicator) without hooking `onError`.\n *\n * - `connecting` — the first socket attempt is in flight; no frames have flowed yet.\n * - `open` — the socket is live; `send()` writes straight through.\n * - `reconnecting` — the socket dropped and a backoff reconnect is scheduled;\n * `send()` buffers into the FIFO until the next open.\n * - `closed` — terminal: `close()` was called, the `signal` was aborted, a 1008\n * auth rejection ended the stream, or the socket dropped with `reconnect: false`.\n * Iteration has ended and will not resume.\n */\nexport type DeviceStreamState =\n\t| 'connecting'\n\t| 'open'\n\t| 'reconnecting'\n\t| 'closed';\n\n// facade-exempt(protocol): client-side stream handle — wire payloads are derived from the generated device frames; `state` and the `ack` result (the device→server output.ack execution-receipt fields) have no emitter schema in the ingress spec. ERA-6206\n/**\n * A live device command stream: async-iterate the typed frames, `send()`\n * inbound events, `close()` when done. Ping/pong, acking, dedup, and\n * reconnect are handled internally.\n */\nexport interface DeviceStream extends AsyncIterable<DeviceCommand> {\n\t/** The live connection state — see {@link DeviceStreamState}. */\n\treadonly state: DeviceStreamState;\n\t/** Publish an inbound event/sensor frame. Throws if the stream is closed. */\n\tsend(frame: DeviceInboundFrame): void;\n\t/**\n\t * Send the execution ack for a command received in iterator mode. Call\n\t * after executing the command; the receipt lands as `output_delivered` /\n\t * `output_failed` / `output_undeliverable` on the originating chain run.\n\t * No-op for commands without an `idempotencyKey`. Handler mode\n\t * (`onExecute`) acks automatically — don't also call this.\n\t */\n\tack(\n\t\tcmd: DeviceCommand,\n\t\tresult?: { status?: 'ok' | 'error' | 'rejected'; detail?: string },\n\t): void;\n\t/** Close the stream and end iteration. Idempotent. */\n\tclose(): void;\n}\n\n/**\n * Throw from `onExecute` to reject a command as unexecutable: the execution\n * ack carries `status:'rejected'` and the server settles the receipt as\n * terminal `output_undeliverable` WITHOUT retrying — unlike any other thrown\n * error, which is retryable (`status:'error'`).\n */\nexport class CommandRejectedError extends Error {\n\t/**\n\t * @param message - Human-readable reason the command cannot be executed;\n\t * forwarded as the rejection `detail` on the execution ack.\n\t */\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = 'CommandRejectedError';\n\t}\n}\n\n/** The device-stream module surface returned by {@link deviceStream}. */\nexport interface DeviceStreamModule {\n\t/**\n\t * Open the device command socket to `WSS /devices/{deviceId}/stream` on the\n\t * ingress origin and resolve to a live {@link DeviceStream} — the device's\n\t * firmware boot call. Async-iterate the returned handle for the typed\n\t * commands a chain's `builtin:emit-output` publishes, `send()` inbound\n\t * events/sensor readings, and `ack()` each executed command.\n\t *\n\t * @param deviceId - The device the key is bound to; interpolated into the\n\t * upgrade URL (URL-encoded).\n\t * @param opts - Connection options. When omitted, reconnect and dedup are on\n\t * and the bearer is sourced from the client's access token. See\n\t * {@link DeviceStreamOptions}.\n\t * @returns A live {@link DeviceStream} handle once the initial socket attempt\n\t * has been dispatched (the promise resolves before the first open; watch\n\t * `stream.state` / `onError` for connection outcome).\n\t * @throws {TypeError} When the client's `ingressUrl` is a cleartext `ws://`\n\t * (or `http://`) origin outside loopback — a device key must not ride an\n\t * unencrypted socket.\n\t * @throws {Error} When no global `WebSocket` is available (older Node without\n\t * `globalThis.WebSocket` set).\n\t *\n\t * @remarks\n\t * Bearer precedence: `opts.token` → the `keyStore`'s stored key → the\n\t * client's access token. In Node the key rides the `Authorization: Bearer`\n\t * upgrade header (never lands in access logs); browsers fall back to a\n\t * `?token=` query param. A close with code 1008 (auth rejection) is terminal\n\t * — a bad key is surfaced via `onError` and the stream ends rather than\n\t * retrying. With `autoRenew: true`, an advisory past `renewAfter` drives a\n\t * `POST /hub-api/device-keys/renew` (returns the successor key) inside the\n\t * predecessor's grace window.\n\t *\n\t * @example\n\t * ```ts\n\t * const stream = await deviceStream(era).connect('dev_0000000000', {\n\t * keyStore: fileKeyStore('/var/lib/mydevice/era-key.json'),\n\t * autoRenew: true,\n\t * onError: (err) => console.error('[stream]', err.message),\n\t * });\n\t * stream.send({ type: 'sensor', sensorId: 'temperature', value: 21.4, unit: 'celsius' });\n\t * for await (const cmd of stream) {\n\t * if (cmd.type === 'say') await speak(cmd.text);\n\t * else await drive(cmd.actuatorId, cmd.fields);\n\t * stream.ack(cmd);\n\t * }\n\t * ```\n\t */\n\tconnect(deviceId: string, opts?: DeviceStreamOptions): Promise<DeviceStream>;\n}\n\n// ─── Internals ───────────────────────────────────────────────────────────────\n\n/** Insertion-ordered idempotencyKey LRU (Map preserves insertion order). */\nclass KeyLru {\n\tprivate readonly seen = new Map<string, true>();\n\tconstructor(private readonly capacity: number) {}\n\t/** True if `key` was already seen (and refreshes its recency). */\n\tcheck(key: string): boolean {\n\t\tconst dup = this.seen.has(key);\n\t\tif (dup) this.seen.delete(key);\n\t\tthis.seen.set(key, true);\n\t\tif (this.seen.size > this.capacity) {\n\t\t\tconst oldest = this.seen.keys().next().value;\n\t\t\tif (oldest !== undefined) this.seen.delete(oldest);\n\t\t}\n\t\treturn dup;\n\t}\n}\n\nconst WS_CLOSE_AUTH_REJECTED = 1008;\n\nfunction resolveWs(): typeof WebSocket {\n\tconst WS = (globalThis as { WebSocket?: typeof WebSocket }).WebSocket;\n\tif (!WS) {\n\t\tthrow new Error(\n\t\t\t'deviceStream requires a WebSocket implementation. It is available in browsers and Node ≥ 22; in older Node, set globalThis.WebSocket (e.g. from the \"ws\" package).',\n\t\t);\n\t}\n\treturn WS;\n}\n\n// ERA-6270: constructor shape for runtimes whose WebSocket accepts a\n// non-standard `{ headers }` init (Node ≥ 22's undici global, the \"ws\"\n// package). The DOM lib types the second argument as protocols only, so the\n// header path casts through this.\ntype HeaderCapableWebSocket = new (\n\turl: string,\n\tinit?: { headers: Record<string, string> },\n) => WebSocket;\n\n/**\n * True when the runtime's WebSocket can send an `Authorization` upgrade\n * header (ERA-6270). Node's global WebSocket (undici, Node ≥ 22) and the\n * `ws` package both accept a `{ headers }` init object; browser/spec-strict\n * constructors (browsers, Electron renderer, Deno) throw a TypeError on an\n * object second argument, so those runtimes stay on the `?token=` query-param\n * fallback (the only browser-compatible transport, ERA-3620). Bun is excluded\n * conservatively — query-param there is today's behavior, not a regression.\n */\nfunction wsHeaderAuthCapable(): boolean {\n\tconst g = globalThis as {\n\t\tprocess?: { versions?: Record<string, string | undefined> };\n\t\twindow?: unknown;\n\t\tDeno?: unknown;\n\t\tBun?: unknown;\n\t};\n\treturn (\n\t\ttypeof g.process?.versions?.node === 'string' &&\n\t\ttypeof g.window === 'undefined' &&\n\t\ttypeof g.Deno === 'undefined' &&\n\t\ttypeof g.Bun === 'undefined'\n\t);\n}\n\n/** Loopback hosts exempt from the ERA-6271 cleartext-ws:// guard. */\nfunction isLoopbackHost(hostWithPort: string): boolean {\n\tconst h = hostWithPort.toLowerCase();\n\tif (h.startsWith('[')) return h.startsWith('[::1]');\n\tconst name = h.split('/')[0]?.split(':')[0] ?? '';\n\treturn name === 'localhost' || name === '127.0.0.1';\n}\n\n/**\n * Construct the device-stream module bound to an {@link EraClient} — the entry\n * point a JS-capable device (Pi, kiosk, desktop) uses to join the cross-device\n * command loop. Call `deviceStream(era).connect(deviceId, opts)` to open the\n * socket; the module reads `client.config.ingressUrl` for the gateway origin\n * and `client.getAccessToken()` as the fallback bearer.\n *\n * @param client - The Era client whose `ingressUrl` and auth back the socket.\n * @returns A {@link DeviceStreamModule} exposing `connect`.\n *\n * @example\n * ```ts\n * import { createEraClient, deviceStream } from '@era-laboratories/era-sdk';\n * const era = createEraClient({ auth: { mode: 'apiKey', apiKey: process.env.ERA_DEVICE_KEY! } });\n * const stream = await deviceStream(era).connect('dev_0000000000', { token: storedKey });\n * ```\n */\nexport function deviceStream(client: EraClient): DeviceStreamModule {\n\treturn {\n\t\tasync connect(\n\t\t\tdeviceId: string,\n\t\t\topts: DeviceStreamOptions = {},\n\t\t): Promise<DeviceStream> {\n\t\t\tconst WS = resolveWs();\n\t\t\t// Prefix-free device gateway path (ERA-6021/ERA-4428). Auth transport\n\t\t\t// (ERA-6270): Authorization header where the runtime supports it\n\t\t\t// (Node), `?token=` query param only as the browser fallback.\n\t\t\tconst base = client.config.ingressUrl;\n\t\t\tconst wsProtocol = base.startsWith('https') ? 'wss:' : 'ws:';\n\t\t\tconst wsHost = base.replace(/^https?:\\/\\//, '').replace(/\\/$/, '');\n\t\t\t// ERA-6271: refuse cleartext ws:// outside loopback — a device key\n\t\t\t// must not ride an unencrypted socket. An http:// ingressUrl is a\n\t\t\t// misconfiguration, not a transport choice.\n\t\t\tif (wsProtocol === 'ws:' && !isLoopbackHost(wsHost)) {\n\t\t\t\tthrow new TypeError(\n\t\t\t\t\t`deviceStream: refusing cleartext ws:// to \"${wsHost}\" — a device key must not ride an unencrypted socket. Use an https ingressUrl (or localhost for development).`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst headerAuth = wsHeaderAuthCapable();\n\t\t\t// Mutable so auto-renew (ERA-6037/6198) can swap in the successor key;\n\t\t\t// each reconnect rebuilds the URL from the current token. Sourcing:\n\t\t\t// explicit token → keyStore `pending` slot → keyStore `current` slot →\n\t\t\t// client access token.\n\t\t\tconst store = opts.keyStore;\n\t\t\tconst stored = store ? await store.load() : undefined;\n\t\t\t// Present PENDING FIRST (ERA-6202 Fix 3). The activation-crash window\n\t\t\t// leaves the live successor in `pending` and a rotated-out predecessor in\n\t\t\t// `current`; presenting `current` first would ride the revoked predecessor\n\t\t\t// and brick the device (lineage revocation). A rotated-out key only ever\n\t\t\t// occupies `current`, never `pending`, so presenting `pending` first can\n\t\t\t// never trip server reuse detection (hub-api deviceKeyRenewal.ts:216\n\t\t\t// revokes-but-does-not-rotate a replaced successor; introspect.ts trips\n\t\t\t// reuse only on a rotated-out key).\n\t\t\tlet currentToken =\n\t\t\t\topts.token ??\n\t\t\t\tstored?.pending ??\n\t\t\t\tstored?.current ??\n\t\t\t\t(await client.getAccessToken()) ??\n\t\t\t\tundefined;\n\t\t\t// Crash recovery (ERA-6202): on a first-connect 1008, fall back ONCE to\n\t\t\t// the OTHER slot. When `pending` was presented first, the fallback is\n\t\t\t// `current`; falling back TO `current` must NOT promote (that would\n\t\t\t// clobber a good `current` with a stale `pending` — see the onclose 1008\n\t\t\t// handler). Only set when no explicit token was given and both slots exist\n\t\t\t// and differ.\n\t\t\tlet fallbackToken =\n\t\t\t\topts.token === undefined &&\n\t\t\t\tstored?.pending !== undefined &&\n\t\t\t\tstored?.current !== undefined &&\n\t\t\t\tstored.current !== currentToken\n\t\t\t\t\t? stored.current\n\t\t\t\t\t: undefined;\n\t\t\t// Promote only when the token being presented IS the `pending` slot:\n\t\t\t// booting straight onto `pending` (no explicit token, pending exists)\n\t\t\t// means a successful open confirms the successor and should promote it.\n\t\t\tlet promoteOnOpen =\n\t\t\t\topts.token === undefined &&\n\t\t\t\tstored?.pending !== undefined &&\n\t\t\t\tcurrentToken === stored.pending;\n\t\t\t// ERA-6270: in header mode the URL carries NO token — the key rides\n\t\t\t// the Authorization upgrade header instead, so it never lands in\n\t\t\t// access logs, HAR exports, or proxy request lines.\n\t\t\tconst buildUrl = (): string => {\n\t\t\t\tconst params = new URLSearchParams();\n\t\t\t\tif (currentToken && !headerAuth) params.set('token', currentToken);\n\t\t\t\tconst qs = params.size > 0 ? `?${params.toString()}` : '';\n\t\t\t\treturn `${wsProtocol}//${wsHost}/devices/${encodeURIComponent(deviceId)}/stream${qs}`;\n\t\t\t};\n\t\t\t// Both branches read `currentToken` at call time so a reconnect after\n\t\t\t// an auto-renew (ERA-6037/6198) authenticates as the successor key.\n\t\t\tconst openSocket = (): WebSocket =>\n\t\t\t\theaderAuth && currentToken\n\t\t\t\t\t? new (WS as unknown as HeaderCapableWebSocket)(buildUrl(), {\n\t\t\t\t\t\t\theaders: { Authorization: `Bearer ${currentToken}` },\n\t\t\t\t\t\t})\n\t\t\t\t\t: new WS(buildUrl());\n\n\t\t\tconst reconnect = opts.reconnect ?? true;\n\t\t\tconst dedupe = opts.dedupe ?? true;\n\t\t\t// Bounded FIFO for inbound frames send() before the socket is OPEN or\n\t\t\t// while it reconnects (ERA-6202). Serialized (already timestamp-stamped)\n\t\t\t// frames queue here and flush in insertion order on the next open. NOTE:\n\t\t\t// execution acks (sendExecutionAck) intentionally do NOT buffer — the\n\t\t\t// server redelivers un-acked commands and dedup absorbs the replay, so a\n\t\t\t// dropped ack self-heals; user events are not redelivered, so they must.\n\t\t\tconst sendBuffer: string[] = [];\n\t\t\tconst SEND_BUFFER_CAP = opts.sendBufferCapacity ?? 64;\n\t\t\t// The LRU lives HERE — on the stream, across socket reconnects — so\n\t\t\t// a 6018 redelivery after a drop is absorbed (see module header).\n\t\t\tconst lru = new KeyLru(opts.dedupeCapacity ?? 256);\n\t\t\t// ERA-6033: executed-command results, keyed by idempotencyKey. When a\n\t\t\t// redelivery arrives for a command whose Ack B was lost (socket died\n\t\t\t// between execute and ack), the recorded result is re-acked instead of\n\t\t\t// re-executing — closing the at-least-once receipt loop without\n\t\t\t// double actuation.\n\t\t\tconst ackResults = new Map<\n\t\t\t\tstring,\n\t\t\t\t{ status: 'ok' | 'error' | 'rejected'; detail?: string }\n\t\t\t>();\n\t\t\tconst rememberResult = (\n\t\t\t\tidem: string,\n\t\t\t\tresult: { status: 'ok' | 'error' | 'rejected'; detail?: string },\n\t\t\t): void => {\n\t\t\t\tackResults.delete(idem);\n\t\t\t\tackResults.set(idem, result);\n\t\t\t\tif (ackResults.size > (opts.dedupeCapacity ?? 256)) {\n\t\t\t\t\tconst oldest = ackResults.keys().next().value;\n\t\t\t\t\tif (oldest !== undefined) ackResults.delete(oldest);\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tlet state: DeviceStreamState = 'connecting';\n\t\t\tlet socket: WebSocket | null = null;\n\t\t\tlet attempts = 0;\n\t\t\tlet closed = false;\n\t\t\tlet reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n\n\t\t\t// ERA-6037/6198 auto-renew state. `renewing` guards a renew+persist in\n\t\t\t// flight; `renewedPending` is set once a successor is durably persisted\n\t\t\t// + activated and cleared on the next successful socket open — together\n\t\t\t// they cap renewal at one cycle per successor. `renewAttempts` bounds\n\t\t\t// persist-failure retries so a broken store can't burn the server's\n\t\t\t// per-key renewal rate limit (5/24h): an unused successor is safely\n\t\t\t// replaced by the server's crash-recovery path on a later renew.\n\t\t\tconst autoRenew = opts.autoRenew ?? false;\n\t\t\tconst MAX_RENEW_ATTEMPTS = 3;\n\t\t\tlet renewing = false;\n\t\t\tlet renewedPending = false;\n\t\t\tlet renewAttempts = 0;\n\n\t\t\tconst maybeRenew = (status: KeyStatus): void => {\n\t\t\t\tif (!autoRenew || renewing || renewedPending) return;\n\t\t\t\tif (renewAttempts >= MAX_RENEW_ATTEMPTS) return; // degraded to advisory\n\t\t\t\t// exp/renewAfter are epoch seconds; only renew inside the window.\n\t\t\t\tif (Date.now() / 1000 < status.renewAfter) return;\n\t\t\t\trenewing = true;\n\t\t\t\trenewAttempts += 1;\n\t\t\t\tvoid (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst init: RequestOptions & { method?: string } = {\n\t\t\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\t\t\t// Renew authenticates AS the key being renewed — pin the\n\t\t\t\t\t\t\t// current stream token, not the client's default auth.\n\t\t\t\t\t\t\t...(currentToken !== undefined\n\t\t\t\t\t\t\t\t? { headers: { authorization: `Bearer ${currentToken}` } }\n\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst renewed = await client.hub<RenewDeviceKeyResponse>(\n\t\t\t\t\t\t\thubPath(genPostDeviceKeysRenew),\n\t\t\t\t\t\t\tinit,\n\t\t\t\t\t\t);\n\t\t\t\t\t\t// ACTIVATION IS GATED ON DURABLE PERSISTENCE (ERA-6198). The\n\t\t\t\t\t\t// server revokes the predecessor on the successor's first auth,\n\t\t\t\t\t\t// so using a key the store hasn't confirmed risks a reboot into\n\t\t\t\t\t\t// a reuse-trip (`credential_compromised`). Write-ahead, then\n\t\t\t\t\t\t// read back to catch no-op store implementations.\n\t\t\t\t\t\t// biome-ignore lint/style/noNonNullAssertion: autoRenew:true requires keyStore by type; guarded by `autoRenew` above\n\t\t\t\t\t\tconst s = store!;\n\t\t\t\t\t\tawait s.savePending(renewed.key);\n\t\t\t\t\t\tconst persisted = await s.load();\n\t\t\t\t\t\tif (persisted.pending !== renewed.key) {\n\t\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\t'keyStore read-back mismatch — savePending resolved but load() does not return the successor. ' +\n\t\t\t\t\t\t\t\t\t'The store is not persisting; refusing to activate the renewed key.',\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Persisted — safe to activate. Swap the token and proactively\n\t\t\t\t\t\t// cycle the socket so the successor authenticates NOW, inside\n\t\t\t\t\t\t// the predecessor's grace window (a never-reconnecting socket\n\t\t\t\t\t\t// would otherwise ride the old key to hard expiry, ERA-6198\n\t\t\t\t\t\t// Gap 2). The at-least-once + dedup machinery absorbs the\n\t\t\t\t\t\t// redelivery window exactly as on any reconnect.\n\t\t\t\t\t\tcurrentToken = renewed.key;\n\t\t\t\t\t\trenewedPending = true;\n\t\t\t\t\t\t// Explicit true: the fresh successor is the pending slot but was\n\t\t\t\t\t\t// just written and is not yet readable as stored.pending here —\n\t\t\t\t\t\t// promote only after its first successful open (ERA-6202).\n\t\t\t\t\t\tpromoteOnOpen = true;\n\t\t\t\t\t\tfallbackToken = undefined; // fresh successor supersedes any stale slot\n\t\t\t\t\t\topts.onKeyRenewed?.({\n\t\t\t\t\t\t\tkey: renewed.key,\n\t\t\t\t\t\t\texpiresAt: renewed.expiresAt,\n\t\t\t\t\t\t\tdeviceId: renewed.deviceId,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (reconnect && socket && socket.readyState === WS.OPEN) {\n\t\t\t\t\t\t\t// Graceful cycle: the onclose handler sees a non-1008 close\n\t\t\t\t\t\t\t// and schedules the reconnect, which opens with the new key.\n\t\t\t\t\t\t\t// Skipped when reconnect is disabled — the swap then applies\n\t\t\t\t\t\t\t// to whatever connection the caller makes next.\n\t\t\t\t\t\t\tsocket.close(1000, 'era-sdk key rotation');\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t// Degrade to advisory — a failed renew/persist must never kill\n\t\t\t\t\t\t// the stream; the token was NOT swapped, so the live socket and\n\t\t\t\t\t\t// any reconnect keep using the still-valid predecessor.\n\t\t\t\t\t\topts.onError?.({\n\t\t\t\t\t\t\tmessage: `device key auto-renew failed (attempt ${renewAttempts}/${MAX_RENEW_ATTEMPTS}): ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t\t\t\t});\n\t\t\t\t\t} finally {\n\t\t\t\t\t\trenewing = false;\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t};\n\n\t\t\t// Async-iterator handshake: frames queue until a consumer awaits.\n\t\t\tconst queue: DeviceCommand[] = [];\n\t\t\tlet wake: (() => void) | null = null;\n\t\t\tconst notify = (): void => {\n\t\t\t\twake?.();\n\t\t\t\twake = null;\n\t\t\t};\n\n\t\t\t// Ack B (ERA-6033): the execution ack. `status` present distinguishes\n\t\t\t// it from the bare receipt ack on the wire; `receiptToken` is echoed\n\t\t\t// so the server correlates the receipt to its chain-run event.\n\t\t\tconst sendExecutionAck = (\n\t\t\t\tcmd: DeviceCommand,\n\t\t\t\tresult: { status: 'ok' | 'error' | 'rejected'; detail?: string },\n\t\t\t): void => {\n\t\t\t\tconst idem = cmd.idempotencyKey;\n\t\t\t\tif (!idem) return; // nothing to correlate server-side\n\t\t\t\trememberResult(idem, result);\n\t\t\t\t// ERA-6206: only actuator frames carry a receiptToken on the wire —\n\t\t\t\t// the say wire copy strips it (see the generated SayCommand).\n\t\t\t\tconst receiptToken =\n\t\t\t\t\tcmd.type === 'actuator' ? cmd.receiptToken : undefined;\n\t\t\t\tif (socket && socket.readyState === WS.OPEN) {\n\t\t\t\t\tsocket.send(\n\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\ttype: 'output.ack',\n\t\t\t\t\t\t\tidempotencyKey: idem,\n\t\t\t\t\t\t\tstatus: result.status,\n\t\t\t\t\t\t\t...(result.detail !== undefined && { detail: result.detail }),\n\t\t\t\t\t\t\t...(receiptToken != null && { receiptToken }),\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tconst execute = (cmd: DeviceCommand): void => {\n\t\t\t\t// Handler mode: run onExecute, then ack with its outcome. Detached\n\t\t\t\t// on purpose — a slow actuator must not block the read pump.\n\t\t\t\tvoid (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// biome-ignore lint/style/noNonNullAssertion: execute() is only reached in handler mode\n\t\t\t\t\t\tawait opts.onExecute!(cmd);\n\t\t\t\t\t\tsendExecutionAck(cmd, { status: 'ok' });\n\t\t\t\t\t\topts.onDelivered?.(cmd);\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tsendExecutionAck(cmd, {\n\t\t\t\t\t\t\tstatus:\n\t\t\t\t\t\t\t\terr instanceof CommandRejectedError ? 'rejected' : 'error',\n\t\t\t\t\t\t\tdetail: err instanceof Error ? err.message : String(err),\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t};\n\n\t\t\tconst yieldFrame = (cmd: DeviceCommand): void => {\n\t\t\t\tif (opts.onExecute) {\n\t\t\t\t\texecute(cmd);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tqueue.push(cmd);\n\t\t\t\tnotify();\n\t\t\t\topts.onDelivered?.(cmd);\n\t\t\t};\n\n\t\t\tconst handleMessage = (raw: unknown): void => {\n\t\t\t\tlet data: unknown;\n\t\t\t\ttry {\n\t\t\t\t\tdata = JSON.parse(String(raw));\n\t\t\t\t} catch {\n\t\t\t\t\treturn; // ignore non-JSON frames\n\t\t\t\t}\n\t\t\t\tconst ping = pingFrameSchema.safeParse(data);\n\t\t\t\tif (ping.success) {\n\t\t\t\t\tif (socket && socket.readyState === WS.OPEN) {\n\t\t\t\t\t\tsocket.send(JSON.stringify({ type: 'pong' }));\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst keyStatus = keyStatusFrameSchema.safeParse(data);\n\t\t\t\tif (keyStatus.success) {\n\t\t\t\t\tconst status: KeyStatus = keyStatus.data;\n\t\t\t\t\topts.onKeyStatus?.(status);\n\t\t\t\t\t// ERA-6037: opt-in auto-renew past the advisory window.\n\t\t\t\t\tmaybeRenew(status);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// Server error advisory (ERA-6202): surface via onError and stop —\n\t\t\t\t// it is not a command. `code` is a string error code (e.g.\n\t\t\t\t// 'invalid_frame'), so it rides onError.reason (onError.code is the\n\t\t\t\t// numeric WS close code); `detail` is the human-readable message.\n\t\t\t\tconst errorFrame = errorFrameSchema.safeParse(data);\n\t\t\t\tif (errorFrame.success) {\n\t\t\t\t\topts.onError?.({\n\t\t\t\t\t\treason: errorFrame.data.code,\n\t\t\t\t\t\tmessage: errorFrame.data.detail,\n\t\t\t\t\t});\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst cmd = deviceCommandSchema.safeParse(data);\n\t\t\t\tif (!cmd.success) return; // unknown frame kinds are forward-compatible no-ops\n\t\t\t\tconst idem = cmd.data.idempotencyKey;\n\t\t\t\tif (idem) {\n\t\t\t\t\tconst duplicate = lru.check(idem);\n\t\t\t\t\t// Always (re-)ack — releases the server's :proc entry even when\n\t\t\t\t\t// the frame is a duplicate whose original ack was lost. This is\n\t\t\t\t\t// Ack A (bare receipt); execution is acked separately (ERA-6033).\n\t\t\t\t\tif (socket && socket.readyState === WS.OPEN) {\n\t\t\t\t\t\tsocket.send(\n\t\t\t\t\t\t\tJSON.stringify({ type: 'output.ack', idempotencyKey: idem }),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (dedupe && duplicate) {\n\t\t\t\t\t\t// ERA-6033: a redelivery of an already-executed command means\n\t\t\t\t\t\t// the original execution ack may have been lost — re-send the\n\t\t\t\t\t\t// recorded result instead of re-executing.\n\t\t\t\t\t\tconst prior = ackResults.get(idem);\n\t\t\t\t\t\tif (prior) sendExecutionAck(cmd.data, prior);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tyieldFrame(cmd.data);\n\t\t\t};\n\n\t\t\tconst scheduleReconnect = (): void => {\n\t\t\t\tstate = 'reconnecting';\n\t\t\t\tconst backoff = Math.min(\n\t\t\t\t\t(opts.backoffBaseMs ?? 500) * 2 ** attempts,\n\t\t\t\t\topts.backoffMaxMs ?? 30_000,\n\t\t\t\t);\n\t\t\t\tattempts += 1;\n\t\t\t\t// Full jitter: uniform in [0, backoff] — avoids fleet-wide stampedes.\n\t\t\t\treconnectTimer = setTimeout(open, Math.random() * backoff);\n\t\t\t};\n\n\t\t\t// Promote the freshly-confirmed key from `pending` → `current` (ERA-6198)\n\t\t\t// with bounded retry on a transient store failure. promote() is idempotent\n\t\t\t// (keyStore.ts:148 no-ops when `pending` is empty), so a retry after a\n\t\t\t// partial write is safe. Backoff mirrors scheduleReconnect (full jitter).\n\t\t\tconst PROMOTE_MAX_ATTEMPTS = 3;\n\t\t\tconst promoteWithRetry = (attempt = 0): void => {\n\t\t\t\tif (closed || !store) return;\n\t\t\t\tvoid store.promote().catch((err: unknown) => {\n\t\t\t\t\tif (closed) return;\n\t\t\t\t\tif (attempt + 1 < PROMOTE_MAX_ATTEMPTS) {\n\t\t\t\t\t\tconst backoff = Math.min(\n\t\t\t\t\t\t\t(opts.backoffBaseMs ?? 500) * 2 ** attempt,\n\t\t\t\t\t\t\topts.backoffMaxMs ?? 30_000,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tsetTimeout(\n\t\t\t\t\t\t\t() => promoteWithRetry(attempt + 1),\n\t\t\t\t\t\t\tMath.random() * backoff,\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\t// Exhausted — the key stays in `pending`. This is safe: the next\n\t\t\t\t\t// boot presents `pending` FIRST (ERA-6202 Fix 3), so the successor\n\t\t\t\t\t// is used and re-promoted on its next successful open. It is NOT\n\t\t\t\t\t// lost, and no stale `current` is presented ahead of it.\n\t\t\t\t\topts.onError?.({\n\t\t\t\t\t\tmessage: `keyStore.promote failed after ${PROMOTE_MAX_ATTEMPTS} attempts; key remains in pending — the next boot presents pending first and re-promotes it: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t};\n\n\t\t\tconst open = (): void => {\n\t\t\t\tif (closed) return;\n\t\t\t\t// Rebuild from currentToken so a reconnect after an auto-renew\n\t\t\t\t// authenticates as the successor key (header or query per runtime).\n\t\t\t\tconst ws = openSocket();\n\t\t\t\tsocket = ws;\n\t\t\t\tws.onopen = () => {\n\t\t\t\t\tif (socket !== ws) return;\n\t\t\t\t\tstate = 'open';\n\t\t\t\t\tattempts = 0;\n\t\t\t\t\t// Flush any frames send() buffered before this open — in insertion\n\t\t\t\t\t// order (ERA-6202). Reconnects reuse this handler, so gap-sends\n\t\t\t\t\t// during a drop flush here for free.\n\t\t\t\t\tif (sendBuffer.length > 0) {\n\t\t\t\t\t\tconst pending = sendBuffer.splice(0, sendBuffer.length);\n\t\t\t\t\t\tfor (const frame of pending) ws.send(frame);\n\t\t\t\t\t}\n\t\t\t\t\t// This socket now carries whatever token was current at open.\n\t\t\t\t\t// Any minted successor has been activated (or this is a fresh\n\t\t\t\t\t// key), so allow the next advisory to renew again.\n\t\t\t\t\trenewedPending = false;\n\t\t\t\t\trenewAttempts = 0;\n\t\t\t\t\t// The key this socket authenticated with is confirmed live —\n\t\t\t\t\t// promote it to the store's `current` slot (ERA-6198). Fail-open\n\t\t\t\t\t// with bounded retry: if promote never succeeds the key stays in\n\t\t\t\t\t// `pending`, which the next boot presents FIRST (ERA-6202 Fix 3)\n\t\t\t\t\t// and re-promotes — no stale `current` is ever presented ahead of\n\t\t\t\t\t// the confirmed successor.\n\t\t\t\t\tif (promoteOnOpen && store) {\n\t\t\t\t\t\tpromoteOnOpen = false;\n\t\t\t\t\t\tpromoteWithRetry();\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tws.onmessage = (event: MessageEvent) => {\n\t\t\t\t\tif (socket === ws) handleMessage(event.data);\n\t\t\t\t};\n\t\t\t\tws.onerror = () => {\n\t\t\t\t\tif (socket === ws) {\n\t\t\t\t\t\topts.onError?.({ message: 'device stream socket error' });\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tws.onclose = (event: CloseEvent) => {\n\t\t\t\t\tif (socket !== ws) return;\n\t\t\t\t\tsocket = null;\n\t\t\t\t\tif (closed) return;\n\t\t\t\t\tif (event.code === WS_CLOSE_AUTH_REJECTED) {\n\t\t\t\t\t\t// Crash recovery (ERA-6202): the first-presented slot was\n\t\t\t\t\t\t// rejected — try the OTHER slot ONCE. Promote ONLY when the token\n\t\t\t\t\t\t// being presented IS the `pending` slot; when falling back TO\n\t\t\t\t\t\t// `current` (the pending-first path) this is false, so a stale\n\t\t\t\t\t\t// `pending` can never clobber a good `current` via promote().\n\t\t\t\t\t\tif (fallbackToken !== undefined) {\n\t\t\t\t\t\t\tcurrentToken = fallbackToken;\n\t\t\t\t\t\t\tfallbackToken = undefined; // single-shot\n\t\t\t\t\t\t\tpromoteOnOpen = currentToken === stored?.pending;\n\t\t\t\t\t\t\tstate = 'reconnecting';\n\t\t\t\t\t\t\topen();\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Auth rejection is terminal — retrying a bad key is noise the\n\t\t\t\t\t\t// gateway rate-limits anyway. Surface and end the stream.\n\t\t\t\t\t\topts.onError?.({\n\t\t\t\t\t\t\tcode: event.code,\n\t\t\t\t\t\t\treason: event.reason,\n\t\t\t\t\t\t\tmessage: `device stream auth rejected (1008): ${event.reason || 'invalid or mismatched device key'}`,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tclosed = true;\n\t\t\t\t\t\tstate = 'closed';\n\t\t\t\t\t\tnotify();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (reconnect) scheduleReconnect();\n\t\t\t\t\telse {\n\t\t\t\t\t\tclosed = true;\n\t\t\t\t\t\tstate = 'closed';\n\t\t\t\t\t\tnotify();\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t};\n\n\t\t\tconst close = (): void => {\n\t\t\t\tif (closed) return;\n\t\t\t\tclosed = true;\n\t\t\t\tstate = 'closed';\n\t\t\t\tif (reconnectTimer) clearTimeout(reconnectTimer);\n\t\t\t\ttry {\n\t\t\t\t\tsocket?.close(1000, 'client close');\n\t\t\t\t} catch {\n\t\t\t\t\t/* ignore */\n\t\t\t\t}\n\t\t\t\tsocket = null;\n\t\t\t\tnotify();\n\t\t\t};\n\n\t\t\tif (opts.signal) {\n\t\t\t\tif (opts.signal.aborted) close();\n\t\t\t\telse opts.signal.addEventListener('abort', close, { once: true });\n\t\t\t}\n\n\t\t\topen();\n\n\t\t\treturn {\n\t\t\t\tget state(): DeviceStreamState {\n\t\t\t\t\treturn state;\n\t\t\t\t},\n\t\t\t\tsend(frame: DeviceInboundFrame): void {\n\t\t\t\t\tif (closed) {\n\t\t\t\t\t\tthrow new Error('deviceStream.send: stream is closed');\n\t\t\t\t\t}\n\t\t\t\t\t// Auto-stamp the event time when the caller didn't supply one\n\t\t\t\t\t// (ERA-6202) — a caller-provided timestamp is preserved verbatim.\n\t\t\t\t\tconst wire = JSON.stringify({\n\t\t\t\t\t\t...frame,\n\t\t\t\t\t\ttimestamp: frame.timestamp ?? new Date().toISOString(),\n\t\t\t\t\t});\n\t\t\t\t\tif (socket && socket.readyState === WS.OPEN) {\n\t\t\t\t\t\tsocket.send(wire);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\t// Pre-OPEN (or mid-reconnect): buffer in FIFO, flushed on the next\n\t\t\t\t\t// open (ERA-6202). Bounded — overflow throws rather than growing\n\t\t\t\t\t// unbounded while the socket is wedged.\n\t\t\t\t\tif (sendBuffer.length >= SEND_BUFFER_CAP) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t`deviceStream.send: send buffer full (capacity ${SEND_BUFFER_CAP})`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tsendBuffer.push(wire);\n\t\t\t\t},\n\t\t\t\tack(\n\t\t\t\t\tcmd: DeviceCommand,\n\t\t\t\t\tresult?: { status?: 'ok' | 'error' | 'rejected'; detail?: string },\n\t\t\t\t): void {\n\t\t\t\t\tsendExecutionAck(cmd, {\n\t\t\t\t\t\tstatus: result?.status ?? 'ok',\n\t\t\t\t\t\t...(result?.detail !== undefined && { detail: result.detail }),\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tclose,\n\t\t\t\tasync *[Symbol.asyncIterator](): AsyncGenerator<\n\t\t\t\t\tDeviceCommand,\n\t\t\t\t\tvoid,\n\t\t\t\t\tvoid\n\t\t\t\t> {\n\t\t\t\t\twhile (true) {\n\t\t\t\t\t\tconst next = queue.shift();\n\t\t\t\t\t\tif (next !== undefined) {\n\t\t\t\t\t\t\tyield next;\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (closed) return;\n\t\t\t\t\t\tawait new Promise<void>((resolve) => {\n\t\t\t\t\t\t\twake = resolve;\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t};\n}\n","// guardrails module — declarative guardrails (PRD #1 §5.5, ERA-4407).\n//\n// Guardrails are the ONLY source of determinism in the dynamic (no-DAG) agent.\n// This module ships the two NON-pausing guardrails — they ride per-step\n// lifecycle events and do NOT need the chains resume engine:\n// - mustInformOnSuccess() — assert an Output fired on success.\n// - neverCallWithout(action, when) — block a gated action unless `when` holds.\n// `confirmBefore` (the PAUSING guardrail, over the resume engine) is ERA-4409 (S2)\n// and extends the GuardrailDef union.\n//\n// These are AUTHOR-TIME builders producing a serializable `GuardrailDef`.\n// Runtime enforcement is the orchestrator (era-ingress, ERA-4401/4402):\n// mustInformOnSuccess checks at run completion; neverCallWithout is a pre-tool\n// seam that evaluates the predicate.\n//\n// Predicates reuse the chains `Predicate` + `$` ref proxy (`makeCtx`), so they\n// serialize identically and are evaluated by the same orchestrator evaluator.\n//\n// Tree-shakeable subpath import: `@era-laboratories/era-sdk/modules/guardrails`.\n\nimport * as z from 'zod';\nimport type { Ctx, Predicate, RefNode } from './chains.js';\nimport { makeCtx } from './chains.js';\n\n/**\n * A guardrail asserting that an Output fired on success — a run that completes\n * without one is flagged. Build one with {@link mustInformOnSuccess}. Checked by\n * the orchestrator at run completion.\n */\nexport interface MustInformOnSuccess {\n\t/** Discriminant tag identifying this guardrail variant. */\n\tkind: 'mustInformOnSuccess';\n}\n/**\n * A guardrail that blocks a gated action unless a predicate holds — evaluated by\n * the orchestrator immediately before the tool call (a pre-tool seam). Build one\n * with {@link neverCallWithout}.\n */\nexport interface NeverCallWithout {\n\t/** Discriminant tag identifying this guardrail variant. */\n\tkind: 'neverCallWithout';\n\t/** The gated tool, named `slug:toolName`, blocked unless `when` holds. */\n\taction: string;\n\t/** The materialized predicate that must hold for `action` to run. */\n\twhen: Predicate;\n}\n\n/**\n * A declarative guardrail — the `kind`-discriminated union both builders\n * produce. Only non-pausing variants ship today; a pausing `confirmBefore`\n * variant is planned and will extend this union.\n */\nexport type GuardrailDef = MustInformOnSuccess | NeverCallWithout;\n\n/**\n * Build a guardrail asserting that an Output fired on success; a run that\n * completes without one is flagged. An author-time, pure builder — bind the\n * result into `defineExperience({ guardrails })`. The orchestrator enforces it\n * at run completion.\n *\n * @returns A serializable {@link MustInformOnSuccess} guardrail.\n * @example\n * ```ts\n * const spec = defineExperience({\n * // ...\n * guardrails: [mustInformOnSuccess()],\n * });\n * ```\n */\nexport function mustInformOnSuccess(): MustInformOnSuccess {\n\treturn { kind: 'mustInformOnSuccess' };\n}\n\n/**\n * A serializable `Predicate`, a bare `$.` ref (treated as a truthy check), or an\n * `($) => …` closure returning either.\n */\nexport type PredicateInput =\n\t| Predicate\n\t| RefNode\n\t| ((ctx: Ctx) => Predicate | RefNode);\n\nconst isRef = (v: unknown): v is RefNode =>\n\ttypeof v === 'object' && v !== null && '$ref' in v;\n\n/**\n * Build a guardrail that blocks a gated action unless `when` holds — evaluated\n * by the orchestrator immediately before the tool call. An author-time, pure\n * builder; bind the result into `defineExperience({ guardrails })`.\n *\n * @param action - The gated tool to block, named `slug:toolName`\n * (e.g. `'google-calendar:delete_event'`).\n * @param when - The condition that must hold for `action` to run. Accepts a\n * chains `Predicate`, a bare `$.` ref (treated as a truthy check), or an\n * `($) => …` closure returning either. See {@link PredicateInput}.\n * @returns A serializable {@link NeverCallWithout} guardrail.\n * @remarks\n * The predicate is materialized to plain `{ $ref }` JSON (ref proxies collapsed)\n * so authored specs diff stably.\n * @example\n * ```ts\n * // Bare ref → truthy check.\n * neverCallWithout('google-calendar:delete_event', ($) => $.userConfirmed);\n * // Full predicate.\n * neverCallWithout('crm:send_email', ($) => ({\n * and: [{ truthy: $.withinBusinessHours }, { neq: [$.recipient, ''] }],\n * }));\n * ```\n */\nexport function neverCallWithout(\n\taction: string,\n\twhen: PredicateInput,\n): NeverCallWithout {\n\tconst resolved = typeof when === 'function' ? when(makeCtx()) : when;\n\tconst predicate: Predicate = isRef(resolved)\n\t\t? { truthy: resolved }\n\t\t: resolved;\n\t// Materialize ref proxies → plain {\"$ref\"} objects (JSON identity for diffs).\n\tconst materialized = JSON.parse(JSON.stringify(predicate)) as Predicate;\n\treturn { kind: 'neverCallWithout', action, when: materialized };\n}\n\n// ─── Schema (for the experienceSpec `guardrails` field + docs) ────────────────────\n\n/** A materialized predicate — a plain JSON object (refs collapsed to `{$ref}`). */\nconst predicateSchema = z.record(z.string(), z.json());\n\n/**\n * Zod schema for a {@link GuardrailDef} — a `kind`-discriminated union over the\n * shipped guardrails (`mustInformOnSuccess`, `neverCallWithout`). Exported so\n * callers can validate guardrail values at their own boundaries; it is also the\n * schema `defineExperience({ guardrails })` accepts and `simulate` reports back.\n *\n * @example\n * ```ts\n * const parsed = guardrailDefSchema.parse(mustInformOnSuccess());\n * ```\n */\nexport const guardrailDefSchema = z\n\t.discriminatedUnion('kind', [\n\t\tz.object({\n\t\t\tkind: z.literal('mustInformOnSuccess').meta({\n\t\t\t\tdescription: 'Guardrail kind discriminant — mustInformOnSuccess.',\n\t\t\t}),\n\t\t}),\n\t\tz.object({\n\t\t\tkind: z.literal('neverCallWithout').meta({\n\t\t\t\tdescription: 'Guardrail kind discriminant — neverCallWithout.',\n\t\t\t}),\n\t\t\taction: z.string().min(1).meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'The gated action (tool) that is blocked unless `when` holds.',\n\t\t\t}),\n\t\t\twhen: predicateSchema,\n\t\t}),\n\t])\n\t.meta({\n\t\tid: 'GuardrailDef',\n\t\ttitle: 'Guardrail',\n\t\tdescription:\n\t\t\t'A declarative guardrail (PRD §5.5). Non-pausing: mustInformOnSuccess + neverCallWithout. confirmBefore (pausing) is ERA-4409.',\n\t});\n","// simulate module — local Experience simulation (PRD #1 §7 F / US-9, ERA-4397).\n//\n// `simulate(spec, mockTriggerEvent)` is a PURE, no-network dry-run: it evaluates\n// the context gate locally (deterministic predicates; a semantic gate is\n// classified server-side at runtime, so it's reported undecided), renders the\n// trigger framing, and reports the spindle palette, OAuth grants, and declared\n// guardrails the run WOULD exercise. The agent's *dynamic* spindle allocation +\n// emissions are runtime/LLM concerns, so simulate reports the DECLARED surface\n// (the palette), not which the agent will pick.\n//\n// Run INSPECTION of an actual run reuses the chains run-state surface\n// (`experienceAuthoring(client).getRun(runId)` → `chains.getRun` → ChainRunState);\n// no new endpoint.\n//\n// Tree-shakeable subpath import: `@era-laboratories/era-sdk/modules/simulate`.\n\nimport type { Predicate, Ref } from './chains.js';\nimport type { ContextGate } from './context.js';\nimport type {\n\tExperienceSpec,\n\tGrantRef,\n\tSpindleRef,\n} from './experienceAuthoring.js';\nimport type { GuardrailDef } from './guardrails.js';\nimport type { TriggerEvent } from './triggers.js';\n\n/**\n * The outcome of evaluating an Experience's context gate during a local\n * {@link simulate} dry-run. Tells you whether the Experience would run against\n * the mock event, and — for deterministic gates — why.\n *\n * @remarks\n * A gate that cannot be resolved locally (one containing a semantic `classify`\n * predicate) is reported as `decided: false` with `pass: true` — the convention\n * is \"when we can't decide, assume it runs\". Only the platform's classifier can\n * settle a semantic gate at runtime. A deterministic predicate over fields\n * absent from the mock context does NOT go undecided — the missing field\n * resolves to `undefined` and the predicate evaluates decisively (usually to a\n * fail).\n */\nexport interface GateDecision {\n\t/**\n\t * Whether the gate was resolved locally. `false` when the gate needs the\n\t * platform's classifier — a semantic gate, or a deterministic gate containing\n\t * a semantic `classify` predicate (no classifier runs in-process).\n\t */\n\tdecided: boolean;\n\t/**\n\t * Whether the Experience would run. `true` when the gate passed, and also\n\t * `true` whenever `decided` is `false` (undecided gates are assumed to run).\n\t */\n\tpass: boolean;\n\t/**\n\t * Which gate flavor was evaluated:\n\t * - `'none'` — the spec declares no gate; the Experience always runs.\n\t * - `'deterministic'` — a `when(...)` predicate evaluated locally.\n\t * - `'semantic'` — a `when.semantic(...)` gate, resolved server-side only.\n\t */\n\tkind: 'none' | 'deterministic' | 'semantic';\n\t/**\n\t * Human-readable explanation of the decision (e.g. `'gate passed — runs'`,\n\t * `'gate failed — dropped'`, or the semantic prompt awaiting server-side\n\t * classification). Intended for logging and debugging, not for branching on.\n\t */\n\treason: string;\n}\n\n/**\n * The full result of a local {@link simulate} dry-run: the gate decision, the\n * trigger framing the agent would receive, and the *declared* surface the run\n * would exercise. Everything here is computed locally with no network call.\n *\n * @remarks\n * `spindles`, `grants`, and `guardrails` report what the spec *declares*, not\n * what the agent will dynamically allocate at runtime — the agent's actual\n * spindle choices and emissions are runtime/LLM concerns and cannot be\n * predicted offline.\n */\nexport interface SimulationResult {\n\t/** The Experience's name, copied from `spec.name`. */\n\tname: string;\n\t/** How the context gate resolved against the mock event (see {@link GateDecision}). */\n\tgate: GateDecision;\n\t/**\n\t * The trigger the agent would receive: the natural-language `framing` string\n\t * plus the structured `event` that produced it.\n\t */\n\ttrigger: { framing: string; event: TriggerEvent };\n\t/** The spindle palette the agent MAY allocate (dynamic at runtime). */\n\tspindles: SpindleRef[];\n\t/** OAuth grants the credentialed spindles in the palette require. */\n\tgrants: GrantRef[];\n\t/** Declared guardrails that would apply to the run (empty if none declared). */\n\tguardrails: GuardrailDef[];\n}\n\n// ─── local predicate evaluation ─────────────────────────────────────────────────\n\nconst isRef = (v: unknown): v is Ref<unknown> =>\n\ttypeof v === 'object' && v !== null && '$ref' in v;\n\nfunction resolveRef(ref: string, scope: Record<string, unknown>): unknown {\n\tconst path = ref\n\t\t.replace(/^\\$\\.?/, '')\n\t\t.split('.')\n\t\t.filter(Boolean);\n\tlet cur: unknown = scope;\n\tfor (const key of path) {\n\t\tif (cur === null || typeof cur !== 'object') return undefined;\n\t\tcur = (cur as Record<string, unknown>)[key];\n\t}\n\treturn cur;\n}\n\nfunction operand(v: unknown, scope: Record<string, unknown>): unknown {\n\treturn isRef(v) ? resolveRef(v.$ref, scope) : v;\n}\n\nfunction evaluatePredicate(\n\tp: Predicate,\n\tscope: Record<string, unknown>,\n): boolean {\n\tif ('eq' in p) return operand(p.eq[0], scope) === operand(p.eq[1], scope);\n\tif ('neq' in p) return operand(p.neq[0], scope) !== operand(p.neq[1], scope);\n\tif ('gt' in p)\n\t\treturn Number(operand(p.gt[0], scope)) > Number(operand(p.gt[1], scope));\n\tif ('lt' in p)\n\t\treturn Number(operand(p.lt[0], scope)) < Number(operand(p.lt[1], scope));\n\tif ('exists' in p) {\n\t\tconst x = operand(p.exists, scope);\n\t\treturn x !== undefined && x !== null;\n\t}\n\tif ('truthy' in p) return Boolean(operand(p.truthy, scope));\n\tif ('and' in p) return p.and.every((q) => evaluatePredicate(q, scope));\n\tif ('or' in p) return p.or.some((q) => evaluatePredicate(q, scope));\n\tif ('not' in p) return !evaluatePredicate(p.not, scope);\n\t// `classify` — a semantic/tensorzero predicate; only the server can evaluate it.\n\tthrow new Error('undecidable: semantic predicate');\n}\n\nfunction evaluateGate(\n\tgate: ContextGate | undefined,\n\tevent: TriggerEvent,\n): GateDecision {\n\tif (!gate)\n\t\treturn {\n\t\t\tdecided: true,\n\t\t\tpass: true,\n\t\t\tkind: 'none',\n\t\t\treason: 'no gate — always runs',\n\t\t};\n\tif (gate.kind === 'semantic')\n\t\treturn {\n\t\t\tdecided: false,\n\t\t\tpass: true,\n\t\t\tkind: 'semantic',\n\t\t\treason: `semantic gate \"${gate.prompt}\" — classified server-side at runtime`,\n\t\t};\n\t// Deterministic — resolve `$.` refs against the ContextSnapshot (+ trigger).\n\tconst ctx = (event.context ?? {}) as Record<string, unknown>;\n\tconst scope: Record<string, unknown> = {\n\t\t...ctx,\n\t\tcontext: ctx,\n\t\ttrigger: event,\n\t};\n\ttry {\n\t\tconst pass = evaluatePredicate(gate.predicate, scope);\n\t\treturn {\n\t\t\tdecided: true,\n\t\t\tpass,\n\t\t\tkind: 'deterministic',\n\t\t\treason: pass ? 'gate passed — runs' : 'gate failed — dropped',\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tdecided: false,\n\t\t\tpass: true,\n\t\t\tkind: 'deterministic',\n\t\t\treason: 'gate references fields absent from the simulation scope',\n\t\t};\n\t}\n}\n\nfunction frameTrigger(spec: ExperienceSpec, event: TriggerEvent): string {\n\tconst base = `Experience \"${spec.name}\" triggered by a ${event.kind} event on ${event.source.surface}.`;\n\tconst payload = event.payload as\n\t\t| { speech?: { text?: string }; utterance?: { text?: string } }\n\t\t| undefined;\n\tconst text = payload?.utterance?.text ?? payload?.speech?.text;\n\treturn text ? `${base} The user said: \"${text}\".` : base;\n}\n\n/**\n * Locally simulate an Experience against a mock {@link TriggerEvent} — a pure,\n * no-network dry-run. Evaluates the declared context gate in-process, renders\n * the natural-language trigger framing the agent would receive, and reports the\n * *declared* surface the run would exercise (spindle palette, OAuth grants, and\n * guardrails). Use it in tests or a REPL to check gate logic and trigger framing\n * before publishing, without provisioning a real run.\n *\n * @param spec - The Experience specification to simulate (typically the\n * `ExperienceSpec` you built with the authoring DSL).\n * @param event - The mock {@link TriggerEvent} to evaluate against. Populate its\n * `context` with the fields your gate reads — a deterministic gate over a field\n * absent from the mock context evaluates decisively (usually to a fail).\n * @returns A {@link SimulationResult} with the gate decision, trigger framing,\n * and the declared spindle/grant/guardrail surface.\n *\n * @remarks\n * This function never throws for an undecidable gate: a semantic `classify`\n * predicate (the one predicate the local evaluator cannot resolve) is caught\n * and surfaced as `gate.decided: false, gate.pass: true` rather than\n * propagating.\n * It reports the DECLARED spindle palette, not the agent's runtime allocation —\n * dynamic spindle choices and emissions can only be observed on a real run via\n * `experienceAuthoring(era).getRun(runId)`.\n *\n * @example\n * ```ts\n * import { simulate } from '@era-laboratories/era-sdk';\n *\n * const result = simulate(spec, {\n * experienceId: 'exp_0000000000',\n * triggerId: 'front-door',\n * kind: 'button',\n * occurredAt: new Date().toISOString(),\n * idempotencyKey: crypto.randomUUID(),\n * source: { surface: 'maker', deviceId: 'dev_0000000000' },\n * context: { location: { place: 'home' } },\n * payload: { gesture: 'press' },\n * });\n *\n * console.log(result.gate.pass, result.gate.reason);\n * console.log(result.trigger.framing);\n * // 'Experience \"Doorbell greeter\" triggered by a button event on maker.'\n * ```\n */\nexport function simulate(\n\tspec: ExperienceSpec,\n\tevent: TriggerEvent,\n): SimulationResult {\n\treturn {\n\t\tname: spec.name,\n\t\tgate: evaluateGate(spec.gate, event),\n\t\ttrigger: { framing: frameTrigger(spec, event), event },\n\t\tspindles: spec.agent.spindles,\n\t\tgrants: spec.agent.grants,\n\t\tguardrails: spec.guardrails ?? [],\n\t};\n}\n","// experience authoring module — declarative authoring (ERA-4267 / ERA-4410).\n//\n// An `ExperienceSpec` is an opinionated, JSON-serializable projection of an\n// experience: an agentic core (`agent: { goal, spindles, grants }`) plus\n// modality + style + model policy + memory + command bindings. `Agent ⊂\n// Experience` — the nested `agent` is just the agentic core; everything else is\n// presentation/runtime config that hangs off the Experience.\n//\n// `defineExperience()` validates and normalizes the spec; the fluent\n// `experience()` builder accumulates the same fields and routes through\n// `defineExperience()` on `.build()`, so the two forms are deep-equal BY\n// CONSTRUCTION.\n//\n// The pure authoring surface (defineExperience / experience / modality /\n// spindle / command / compile / decompile / diff) does NOT touch the network.\n// Only the `experienceAuthoring(client)` facade does, composing the experiences\n// + experienceVersions modules — no new routes.\n//\n// Compiler mapping (ExperienceSpec → experience payload):\n// agent.goal → stylePrompt\n// agent.internalGoal → internalSystemPrompt\n// agent.spindles[] → stylePreferences.spindles (nested, slug-keyed)\n// agent.grants[] → (authored-but-inert; consumed by the OAuth broker, ERA-4123)\n// modality (voice/mm) → stylePreferences.{tts,stt} (+ mirror to voiceId/voiceProvider)\n// style → stylePreferences.{voice,lexicon,rhetoric,policy,editing,color}\n// commands[] → stylePreferences.{voiceCommands,buttonMappings}\n// model → stylePreferences.model (authored-but-inert; design open-risk #5)\n// memory.retrieval → memoryRetrievalEnabled\n//\n// Known lossy round-trip: `agent.internalGoal` is NOT on the public\n// `Experience` read DTO, so `fromExperience()` cannot recover it (design\n// open-risk #6); `agent.grants` are likewise not persisted to the read DTO.\n// Every other field round-trips content-stably.\n//\n// `publish()` is intentionally NOT in this facade — RBAC v2 publishing is a\n// review state machine (draft → review → published), not a channel write; use\n// `era.experienceVersions` (submitForReview / approveReview) directly.\n//\n// Tree-shakeable subpath import: `@era-laboratories/era-sdk/modules/experienceAuthoring`.\n\nimport * as z from 'zod';\nimport type { CamelizeKeys, PickNonNullable } from '../core/casing.js';\nimport type { EraClient, ResponseMeta, StreamHandle } from '../core/client.js';\nimport { EraStreamError } from '../core/errors.js';\nimport type {\n\tStreamBlockedEvent,\n\tStreamDoneEvent,\n\tStreamErrorEvent,\n\tStreamEvent,\n} from '../core/streaming.js';\nimport type { RequestOptions } from '../core/types.js';\nimport type { ChatDoneStreamEvent as GenChatDoneStreamEvent } from '../generated/ingress/types.gen.js';\nimport type { ChainRunState } from './chains.js';\nimport { chains } from './chains.js';\nimport type {\n\tButtonMapping,\n\tEditing,\n\tExperienceStylePreferences,\n\tLexicon,\n\tPolicy,\n\tRhetoric,\n\tSttConfig,\n\tTtsConfig,\n\tVoiceCommand,\n\tVoiceStyle,\n} from './commands.js';\nimport type { ContextGate } from './context.js';\nimport { contextGateSchema } from './context.js';\nimport type { DeviceCommand, DeviceStream } from './deviceStream.js';\nimport { deviceStream } from './deviceStream.js';\nimport { devices } from './devices.js';\nimport type {\n\tCreateExperienceInput,\n\tExperience,\n\tUpdateExperienceInput,\n} from './experiences.js';\nimport { experiences } from './experiences.js';\nimport type {\n\tUpdateDraftInput,\n\tVersionMutationResult,\n} from './experienceVersions.js';\nimport { experienceVersions } from './experienceVersions.js';\nimport type { GuardrailDef } from './guardrails.js';\nimport { guardrailDefSchema } from './guardrails.js';\nimport type { SimulationResult } from './simulate.js';\nimport { simulate as simulateSpec } from './simulate.js';\nimport {\n\tnestedSpindlesToEntries,\n\ttype SpindlePalette,\n\tspindleRefsToNested,\n} from './spindlePalette.js';\nimport type { TriggerEvent } from './triggers.js';\n\n// ─── Spec types ───────────────────────────────────────────────────────────────\n\n/**\n * Presentation style — the subset of an Experience's style preferences that\n * shapes how the agent sounds and behaves. Every field is optional; unset\n * fields fall back to the platform defaults.\n */\nexport interface ExperienceStyle {\n\t/**\n\t * Voice sliders, each in the range 0–1 (e.g. warmth, formality). Partial —\n\t * any slider you omit falls back to the platform default.\n\t */\n\tvoice?: Partial<VoiceStyle>;\n\t/** Word choice and terminology preferences applied to the agent's replies. */\n\tlexicon?: Lexicon;\n\t/** Rhetorical shaping (tone, persuasion, structure) for the agent's replies. */\n\trhetoric?: Rhetoric;\n\t/** Content policy overlay constraining what the agent may say. */\n\tpolicy?: Policy;\n\t/** Post-processing/editing preferences applied to the agent's output. */\n\tediting?: Editing;\n\t/** Brand/accent color as a hex string (e.g. `'#4d2a1f'`). */\n\tcolor?: string;\n}\n\n/** A text-only Experience — no speech synthesis or transcription. */\nexport interface TextModality {\n\t/** Discriminant identifying the text modality. */\n\tkind: 'text';\n}\n/** A voice Experience — speech in and out, with optional TTS/STT overrides. */\nexport interface VoiceModality {\n\t/** Discriminant identifying the voice modality. */\n\tkind: 'voice';\n\t/** Text-to-speech config (provider + voice). Omit to use the platform default. */\n\ttts?: TtsConfig;\n\t/** Speech-to-text config (provider + model). Omit to use the platform default. */\n\tstt?: SttConfig;\n}\n/** A multimodal Experience — text plus voice, with optional TTS/STT overrides. */\nexport interface MultimodalModality {\n\t/** Discriminant identifying the multimodal modality. */\n\tkind: 'multimodal';\n\t/** Text-to-speech config (provider + voice). Omit to use the platform default. */\n\ttts?: TtsConfig;\n\t/** Speech-to-text config (provider + model). Omit to use the platform default. */\n\tstt?: SttConfig;\n}\n/**\n * How an Experience communicates. Discriminated on `kind` — `'text'`,\n * `'voice'`, or `'multimodal'` — and drives the TTS/STT config and the output\n * modality. Build one with the {@link modality} factory.\n */\nexport type ExperienceModality =\n\t| TextModality\n\t| VoiceModality\n\t| MultimodalModality;\n\n/**\n * A model-routing hint attached to an Experience.\n *\n * @remarks\n * Authored on the spec but **not yet applied at runtime** — the platform\n * ignores it today. Set it to record intent; it does not change which model\n * serves the Experience.\n */\nexport interface ModelPolicy {\n\t/** Named model function to route to. Reserved — not yet applied at runtime. */\n\tfunction?: string;\n\t/** Named model variant to route to. Reserved — not yet applied at runtime. */\n\tvariant?: string;\n}\n\n/**\n * A spindle the agent may use. `ref` is a spindle `slug` (enables all the\n * spindle's tools) or `slug:toolName` (a single tool).\n */\nexport interface SpindleRef {\n\t/**\n\t * Spindle reference: a spindle `slug` (enables all of the spindle's tools) or\n\t * `slug:toolName` (enables a single named tool).\n\t */\n\tref: string;\n\t/** Enablement override. Defaults to enabled when omitted. */\n\tenabled?: boolean;\n}\n\n/**\n * An OAuth grant requirement for a credentialed spindle — the provider and the\n * scopes the agent needs to call that spindle's tools.\n *\n * @remarks\n * Authored on the spec but not sent by `compile()` today, and not persisted on\n * the Experience read model — so grants do not survive a\n * {@link decompileExperience} round-trip.\n */\nexport interface GrantRef {\n\t/** OAuth provider id for the credentialed spindle (e.g. `'google'`). */\n\tprovider: string;\n\t/** OAuth scopes the agent requires (e.g. calendar read). Omit for none. */\n\tscopes?: string[];\n}\n\n/**\n * A command binding that fires a tool when triggered — either a spoken voice\n * command or a hardware button mapping. Discriminated on `kind`. Build one with\n * the {@link command} factory.\n */\nexport type CommandBinding =\n\t| { kind: 'voice'; command: VoiceCommand }\n\t| { kind: 'button'; mapping: ButtonMapping };\n\n/**\n * The agentic core of an Experience — the objective, spindle palette, and OAuth\n * grants. `Agent ⊂ Experience`: this is the nested agent, and everything else on\n * {@link ExperienceSpec} is presentation/runtime config that hangs off it.\n */\nexport interface AgentCore {\n\t/** The agent's objective — becomes the Experience's system prompt. */\n\tgoal?: string;\n\t/**\n\t * Internal/operator prompt overlay layered on top of `goal`. Not recovered by\n\t * {@link decompileExperience} — the Experience read model does not carry it.\n\t */\n\tinternalGoal?: string;\n\t/** The spindle palette the agent may use. Always an array after normalization. */\n\tspindles: SpindleRef[];\n\t/** OAuth grant requirements. Always an array after normalization. */\n\tgrants: GrantRef[];\n}\n\n/**\n * Canonical, JSON-serializable Experience definition. Produced by\n * {@link defineExperience} (and the {@link experience} builder). `agent.spindles`,\n * `agent.grants`, and `commands` are always arrays after normalization.\n *\n * @remarks\n * `compileToExperienceInput` maps the agentic core, modality, style, model,\n * memory, and command bindings onto the Experience payload. The `gate` and\n * `guardrails` fields are accepted on the spec but **not sent by `compile()`**\n * and not stored on an Experience today — declare gates and lifecycle behavior\n * on a chain instead (`chain(...).trigger(...)`). Triggers likewise live on the\n * chain, not the Experience.\n */\nexport interface ExperienceSpec {\n\t/** Unique Experience name within the owning resource. */\n\tname: string;\n\t/** Human-readable description of the Experience. Optional. */\n\tdescription?: string;\n\t/**\n\t * Context gate evaluated when a trigger arrives to decide whether to run or\n\t * drop. Authored here but not consumed by an Experience today — set gates on\n\t * a chain instead. See `when` in the `context` module.\n\t */\n\tgate?: ContextGate;\n\t/** The agentic core: objective + spindle palette + OAuth grants. */\n\tagent: AgentCore;\n\t/** How the Experience communicates: text, voice, or multimodal. */\n\tmodality: ExperienceModality;\n\t/** Presentation style — voice sliders, lexicon/rhetoric/policy/editing, color. */\n\tstyle?: ExperienceStyle;\n\t/** Model-routing hint. Reserved — not yet applied at runtime. */\n\tmodel?: ModelPolicy;\n\t/** Memory configuration. Set `retrieval: true` to enable memory retrieval. */\n\tmemory?: { retrieval?: boolean };\n\t/** Tool-firing command bindings (voice commands + button mappings). */\n\tcommands: CommandBinding[];\n\t/**\n\t * Declarative guardrails. Authored here but not consumed by an Experience\n\t * today — set guardrails on a chain instead. See the `guardrails` module.\n\t */\n\tguardrails?: GuardrailDef[];\n}\n\n/**\n * A single structural difference between two specs, as returned by\n * {@link diffExperiences} — one changed leaf value at a given path.\n */\nexport interface ExperienceDiffEntry {\n\t/** Dot-delimited path to the changed leaf (e.g. `'agent.goal'`). */\n\tpath: string;\n\t/** The value in the first (`a`) spec, or `undefined` if only in `b`. */\n\tbefore: unknown;\n\t/** The value in the second (`b`) spec, or `undefined` if only in `a`. */\n\tafter: unknown;\n}\n\n// ─── Runtime (run / complete) ──────────────────────────────────────────────────\n\n/**\n * Terminal event of a run — the base {@link StreamDoneEvent} enriched with the\n * names of the tools invoked during the turn and the response metadata\n * (`requestId`, and `costCents` / `balanceCents` when the stream provides them).\n *\n * @remarks\n * `toolsUsed` is the list of tool names invoked during the turn: the server's\n * value is used when reported, otherwise the SDK's own aggregation of the\n * turn's `tool_call` events (defaulting to `[]`). `requestId` is read from the\n * response headers. `cost` / `balance` are legacy fields retained for\n * back-compat and are never populated from the stream — read `costCents` /\n * `balanceCents` (integer US cents) for billing.\n */\nexport type ExperienceRunDoneEvent = StreamDoneEvent &\n\tPickNonNullable<CamelizeKeys<GenChatDoneStreamEvent>, 'toolsUsed'> &\n\tPick<ResponseMeta, 'requestId'> &\n\tPartial<Record<'cost' | 'balance', number>>;\n\n/**\n * A normalized event yielded by {@link ExperienceAuthoringModule.run}. The\n * non-terminal events are the SDK's `StreamEvent`s\n * (`start` / `interim` / `chunk` / `tool_call` / `tool_result` /\n * `tool_result_chunk` / `blocked` / `error`); the terminal `done` is widened to\n * {@link ExperienceRunDoneEvent}.\n */\nexport type ExperienceRunEvent =\n\t| Exclude<StreamEvent, { type: 'done' }>\n\t| ExperienceRunDoneEvent;\n\n/** Per-turn input for `run()` / `complete()`. */\nexport interface ExperienceRunInput {\n\t/** The user's prompt for this turn. */\n\tinput: string;\n\t/** Reuse an existing session; omit to let the server create one. */\n\tsessionId?: string;\n\t/**\n\t * Turn modality. `'voice'` routes to `/chat/voice`; `'text'` and\n\t * `'multimodal'` route to `/chat`. Realtime streaming audio is out of scope\n\t * for `run()` — use the `voice` module for that.\n\t */\n\tmodality?: 'text' | 'voice' | 'multimodal';\n\t/** Passthrough JSON attached to the turn for your own logs / telemetry. */\n\tmetadata?: Record<string, unknown>;\n\t/** `AbortSignal` to cancel the in-flight turn. */\n\tsignal?: AbortSignal;\n\t/**\n\t * A stable `Idempotency-Key` for this turn so the server can deduplicate\n\t * retries and never double-charge you. Without it the SDK mints a fresh key\n\t * per call, so your own retry of a failed request would bill twice. Runs\n\t * stream and are not replayed from cache: reusing the same key with the\n\t * same body within 30 days is not re-run — it returns\n\t * `409 IDEMPOTENT_REPLAY` carrying the original cost headers; the same key\n\t * with a different body returns `422 IDEMPOTENCY_KEY_BODY_MISMATCH`. Same\n\t * server-side contract as `chat.send(...)` / `chat.complete(...)`.\n\t */\n\tidempotencyKey?: string;\n}\n\n/**\n * Aggregated reply from the non-streaming {@link ExperienceAuthoringModule.complete}\n * convenience.\n *\n * @remarks\n * Carries the same fields as the terminal {@link ExperienceRunDoneEvent}, minus\n * the event framing (`type` / `fullContent`), plus the accumulated reply `text`.\n *\n * `blocked` is set when the server's moderation layer refused the turn: `text`\n * is then empty and `blocked.safetyCategory` names the triggering category when\n * the server reports one. A block is a policy outcome the caller inspects —\n * `complete()` does NOT throw for it. Absent on normal replies.\n */\nexport type ExperienceReply = Omit<\n\tExperienceRunDoneEvent,\n\t'type' | 'fullContent'\n> &\n\tRecord<'text', string> &\n\tPartial<Record<'blocked', Pick<StreamBlockedEvent, 'safetyCategory'>>>;\n\n// ─── Validation (zod) ─────────────────────────────────────────────────────────\n\nconst ttsSchema = z\n\t.object({\n\t\tprovider: z.string(),\n\t\tvoice_id: z.string().nullable(),\n\t\tvoice_name: z.string().nullable().optional(),\n\t\tvoice_description: z.string().nullable().optional(),\n\t\t// preview_url is a provider voice-catalog audition URL round-tripped from\n\t\t// the Experience read DTO (stylePreferences.tts); `''` is the \"no preview\"\n\t\t// sentinel the compiler emits (`voice.previewUrl ?? ''`).\n\t\tpreview_url: z.url().or(z.literal('')).nullable().optional(),\n\t})\n\t.optional();\nconst sttSchema = z\n\t.object({ provider: z.string(), model: z.string().nullable().optional() })\n\t.optional();\n\nconst modalitySchema = z.discriminatedUnion('kind', [\n\tz.object({ kind: z.literal('text') }),\n\tz.object({ kind: z.literal('voice'), tts: ttsSchema, stt: sttSchema }),\n\tz.object({ kind: z.literal('multimodal'), tts: ttsSchema, stt: sttSchema }),\n]);\n\n// Style is validated structurally but kept permissive on sub-fields (the\n// backend enforces slider ranges); we only require objects where present.\nconst styleSchema = z\n\t.object({\n\t\tvoice: z.record(z.string(), z.number()).optional(),\n\t\tlexicon: z.object({}).loose().optional(),\n\t\trhetoric: z.object({}).loose().optional(),\n\t\tpolicy: z.object({}).loose().optional(),\n\t\tediting: z.object({}).loose().optional(),\n\t\tcolor: z.string().optional(),\n\t})\n\t.loose()\n\t.optional();\n\nconst spindleSchema = z.object({\n\tref: z.string().min(1).meta({\n\t\tdescription:\n\t\t\t'Spindle reference: a spindle `slug` (enables all its tools) or `slug:toolName` (a single tool).',\n\t}),\n\tenabled: z\n\t\t.boolean()\n\t\t.optional()\n\t\t.meta({ description: 'Enablement override. Defaults to enabled.' }),\n});\n\nconst grantSchema = z.object({\n\tprovider: z\n\t\t.string()\n\t\t.min(1)\n\t\t.meta({ description: 'OAuth provider id for a credentialed spindle.' }),\n\tscopes: z.array(z.string()).optional().meta({\n\t\tdescription:\n\t\t\t'Required OAuth scopes; resolved at execution by the per-key token broker (ERA-4123).',\n\t}),\n});\n\nconst commandSchema = z.discriminatedUnion('kind', [\n\tz.object({\n\t\tkind: z.literal('voice'),\n\t\tcommand: z.object({ id: z.string().min(1) }).loose(),\n\t}),\n\tz.object({\n\t\tkind: z.literal('button'),\n\t\tmapping: z.object({ id: z.string().min(1) }).loose(),\n\t}),\n]);\n\nconst experienceSpecSchema = z\n\t.object({\n\t\tname: z.string().min(1).meta({\n\t\t\tdescription: 'Unique Experience name within the owning resource.',\n\t\t}),\n\t\tdescription: z\n\t\t\t.string()\n\t\t\t.optional()\n\t\t\t.meta({ description: 'Human-readable description of the Experience.' }),\n\t\tgate: contextGateSchema.optional().meta({\n\t\t\tdescription:\n\t\t\t\t'Context gate evaluated at trigger ingest (run or drop): deterministic when() or semantic when.semantic().',\n\t\t}),\n\t\tagent: z\n\t\t\t.object({\n\t\t\t\tgoal: z.string().optional().meta({\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t\"The agent's objective — compiles to the Experience systemPrompt (`stylePrompt`).\",\n\t\t\t\t}),\n\t\t\t\tinternalGoal: z.string().optional().meta({\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t'Internal/operator prompt overlay. Lossy round-trip — not on the public read DTO.',\n\t\t\t\t}),\n\t\t\t\tspindles: z.array(spindleSchema).default([]).meta({\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t'The spindle palette the agent may use (slug or slug:tool refs).',\n\t\t\t\t}),\n\t\t\t\tgrants: z.array(grantSchema).default([]).meta({\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t'OAuth grant requirements; authored-but-inert in the SDK, resolved by the token broker (ERA-4123).',\n\t\t\t\t}),\n\t\t\t})\n\t\t\t.meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'The agentic core of the Experience (Agent ⊂ Experience): objective + spindle palette + OAuth grants.',\n\t\t\t}),\n\t\tmodality: modalitySchema.meta({\n\t\t\tdescription:\n\t\t\t\t'How the Experience communicates: text, voice, or multimodal (drives tts/stt config).',\n\t\t}),\n\t\tstyle: styleSchema.meta({\n\t\t\tdescription:\n\t\t\t\t'Presentation style — voice sliders, lexicon/rhetoric/policy/editing, and brand color.',\n\t\t}),\n\t\tmodel: z\n\t\t\t.object({\n\t\t\t\tfunction: z.string().optional(),\n\t\t\t\tvariant: z.string().optional(),\n\t\t\t})\n\t\t\t.optional()\n\t\t\t.meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'TensorZero model-policy hint. Authored-but-inert until ingress wiring.',\n\t\t\t}),\n\t\tmemory: z.object({ retrieval: z.boolean().optional() }).optional().meta({\n\t\t\tdescription: 'Memory configuration (e.g. retrieval enablement).',\n\t\t}),\n\t\tcommands: z.array(commandSchema).default([]).meta({\n\t\t\tdescription:\n\t\t\t\t'Tool-firing command bindings (voice commands + button mappings).',\n\t\t}),\n\t\tguardrails: z.array(guardrailDefSchema).optional().meta({\n\t\t\tdescription:\n\t\t\t\t'Declarative guardrails (non-pausing): mustInformOnSuccess + neverCallWithout.',\n\t\t}),\n\t})\n\t.meta({\n\t\tid: 'ExperienceSpec',\n\t\ttitle: 'Experience',\n\t\tdescription:\n\t\t\t'The canonical Experience authoring object (PRD §5.1): trigger + context gate + agentic core + modality, style, model, memory, command bindings, and guardrails.',\n\t});\n\n/**\n * Validate and normalize an Experience definition into the canonical\n * {@link ExperienceSpec}, filling defaults so downstream code can iterate the\n * arrays safely. Pure — no network. Use it to author a spec you then pass to\n * {@link experienceAuthoring} `.create()` / `.update()`.\n *\n * @param input - The Experience definition to validate.\n * @returns The normalized spec, with `agent.spindles`, `agent.grants`, and\n * `commands` defaulted to `[]` when omitted.\n * @throws {@link https://zod.dev | ZodError} when `input` fails validation\n * (e.g. a missing `name` or an empty spindle `ref`).\n * @example\n * ```typescript\n * const spec = defineExperience({\n * name: 'Doorbell greeter',\n * agent: { goal: 'Greet the visitor and take a message.', spindles: [], grants: [] },\n * modality: modality.voice(),\n * commands: [],\n * });\n * ```\n */\nexport function defineExperience(input: ExperienceSpec): ExperienceSpec {\n\treturn experienceSpecSchema.parse(input) as ExperienceSpec;\n}\n\n// ─── Factories ─────────────────────────────────────────────────────────────────\n\nconst textModality = (): TextModality => ({ kind: 'text' });\nconst voiceModality = (opts?: {\n\ttts?: TtsConfig;\n\tstt?: SttConfig;\n}): VoiceModality => ({\n\tkind: 'voice',\n\t...(opts?.tts ? { tts: opts.tts } : {}),\n\t...(opts?.stt ? { stt: opts.stt } : {}),\n});\nconst multimodalModality = (opts?: {\n\ttts?: TtsConfig;\n\tstt?: SttConfig;\n}): MultimodalModality => ({\n\tkind: 'multimodal',\n\t...(opts?.tts ? { tts: opts.tts } : {}),\n\t...(opts?.stt ? { stt: opts.stt } : {}),\n});\n\n/**\n * Factories for the {@link ExperienceModality} variants, namespaced to avoid\n * colliding with the `voice` module export. Each returns a plain, serializable\n * modality fragment for an {@link ExperienceSpec}.\n *\n * @example\n * ```typescript\n * modality.text(); // { kind: 'text' }\n * modality.voice({ tts, stt }); // { kind: 'voice', ... }\n * modality.multimodal({ tts, stt }); // { kind: 'multimodal', ... }\n * ```\n */\nexport const modality = {\n\t/** Build a text-only modality: `{ kind: 'text' }`. */\n\ttext: textModality,\n\t/** Build a voice modality, optionally overriding TTS/STT config. */\n\tvoice: voiceModality,\n\t/** Build a multimodal modality, optionally overriding TTS/STT config. */\n\tmultimodal: multimodalModality,\n};\n\n/**\n * Build a {@link SpindleRef} for the agent's spindle palette.\n *\n * @param ref - A spindle `slug` (enables all of its tools) or `slug:toolName`\n * (enables a single named tool).\n * @param opts - Optional settings.\n * @param opts.enabled - Enablement override; defaults to enabled when omitted.\n * @returns The spindle reference fragment.\n * @example\n * ```typescript\n * spindle('google-calendar'); // whole spindle\n * spindle('google-calendar:delete_event', { enabled: false }); // one tool, disabled\n * ```\n */\nexport const spindle = (\n\tref: string,\n\topts?: { enabled?: boolean },\n): SpindleRef =>\n\topts?.enabled === undefined ? { ref } : { ref, enabled: opts.enabled };\n\n/**\n * Factories for the {@link CommandBinding} variants — a spoken voice command or\n * a hardware button mapping — for an {@link ExperienceSpec}'s `commands`.\n *\n * @example\n * ```typescript\n * command.voice(voiceCommand); // { kind: 'voice', command }\n * command.button(buttonMapping); // { kind: 'button', mapping }\n * ```\n */\nexport const command = {\n\t/** Wrap a {@link VoiceCommand} as a voice {@link CommandBinding}. */\n\tvoice: (cmd: VoiceCommand): CommandBinding => ({\n\t\tkind: 'voice',\n\t\tcommand: cmd,\n\t}),\n\t/** Wrap a {@link ButtonMapping} as a button {@link CommandBinding}. */\n\tbutton: (mapping: ButtonMapping): CommandBinding => ({\n\t\tkind: 'button',\n\t\tmapping,\n\t}),\n};\n\n// ─── Builder ───────────────────────────────────────────────────────────────────\n\n/**\n * Fluent builder for an {@link ExperienceSpec}. Accumulates the same fields as\n * the object form; `.build()` routes through {@link defineExperience}, so a\n * built spec is deep-equal to the equivalent `defineExperience({...})` object.\n * Start one with {@link experience}. Every setter returns `this` for chaining.\n *\n * @example\n * ```typescript\n * const spec = experience('Doorbell greeter')\n * .goal('Greet the visitor and take a message.')\n * .voice()\n * .addSpindle('google-calendar')\n * .build();\n * ```\n */\nexport class ExperienceBuilder {\n\tprivate readonly draft: ExperienceSpec;\n\n\t/**\n\t * @param name - The Experience name (unique within the owning resource).\n\t * Starts a text modality with empty spindle/grant/command lists.\n\t */\n\tconstructor(name: string) {\n\t\tthis.draft = {\n\t\t\tname,\n\t\t\tagent: { spindles: [], grants: [] },\n\t\t\tmodality: { kind: 'text' },\n\t\t\tcommands: [],\n\t\t};\n\t}\n\n\t/**\n\t * Set the human-readable description.\n\t * @param d - The description text.\n\t */\n\tdescription(d: string): this {\n\t\tthis.draft.description = d;\n\t\treturn this;\n\t}\n\t/**\n\t * Set the agent's objective (the system prompt).\n\t * @param g - The objective text.\n\t */\n\tgoal(g: string): this {\n\t\tthis.draft.agent.goal = g;\n\t\treturn this;\n\t}\n\t/**\n\t * Set the internal/operator prompt overlay.\n\t * @param g - The internal prompt text.\n\t * @remarks Not recovered by {@link decompileExperience} — it is not present\n\t * on the Experience read model.\n\t */\n\tinternalGoal(g: string): this {\n\t\tthis.draft.agent.internalGoal = g;\n\t\treturn this;\n\t}\n\t/**\n\t * Merge presentation style into the draft (shallow-merged over any prior style).\n\t * @param style - The style fields to apply.\n\t */\n\tstyle(style: ExperienceStyle): this {\n\t\tthis.draft.style = { ...this.draft.style, ...style };\n\t\treturn this;\n\t}\n\t/** Set the modality to text-only. */\n\ttext(): this {\n\t\tthis.draft.modality = textModality();\n\t\treturn this;\n\t}\n\t/**\n\t * Set the modality to voice.\n\t * @param opts - Optional TTS/STT overrides; omit to use platform defaults.\n\t */\n\tvoice(opts?: { tts?: TtsConfig; stt?: SttConfig }): this {\n\t\tthis.draft.modality = voiceModality(opts);\n\t\treturn this;\n\t}\n\t/**\n\t * Set the modality to multimodal (text plus voice).\n\t * @param opts - Optional TTS/STT overrides; omit to use platform defaults.\n\t */\n\tmultimodal(opts?: { tts?: TtsConfig; stt?: SttConfig }): this {\n\t\tthis.draft.modality = multimodalModality(opts);\n\t\treturn this;\n\t}\n\t/**\n\t * Set the model-routing hint.\n\t * @param m - The model policy. Reserved — not yet applied at runtime.\n\t */\n\tmodel(m: ModelPolicy): this {\n\t\tthis.draft.model = m;\n\t\treturn this;\n\t}\n\t/**\n\t * Add a spindle to the agent's palette.\n\t * @param ref - A spindle `slug` or `slug:toolName`.\n\t * @param opts - Optional settings.\n\t * @param opts.enabled - Enablement override; defaults to enabled.\n\t */\n\taddSpindle(ref: string, opts?: { enabled?: boolean }): this {\n\t\tthis.draft.agent.spindles.push(spindle(ref, opts));\n\t\treturn this;\n\t}\n\t/**\n\t * Declare an OAuth grant requirement for a credentialed spindle.\n\t * @param provider - OAuth provider id (e.g. `'google'`).\n\t * @param scopes - Required OAuth scopes; omit for none.\n\t */\n\tgrant(provider: string, scopes?: string[]): this {\n\t\tthis.draft.agent.grants.push(\n\t\t\tscopes === undefined ? { provider } : { provider, scopes },\n\t\t);\n\t\treturn this;\n\t}\n\t/**\n\t * Add a voice command binding.\n\t * @param cmd - The voice command to bind.\n\t */\n\tonVoice(cmd: VoiceCommand): this {\n\t\tthis.draft.commands.push(command.voice(cmd));\n\t\treturn this;\n\t}\n\t/**\n\t * Add a button command binding.\n\t * @param mapping - The button mapping to bind.\n\t */\n\tonButton(mapping: ButtonMapping): this {\n\t\tthis.draft.commands.push(command.button(mapping));\n\t\treturn this;\n\t}\n\t/**\n\t * Set the memory configuration.\n\t * @param m - Memory settings; set `retrieval: true` to enable retrieval.\n\t */\n\tmemory(m: { retrieval?: boolean }): this {\n\t\tthis.draft.memory = m;\n\t\treturn this;\n\t}\n\t/**\n\t * Validate and normalize the accumulated draft into a canonical spec.\n\t * @returns The built {@link ExperienceSpec}.\n\t * @throws {@link https://zod.dev | ZodError} when the draft fails validation.\n\t */\n\tbuild(): ExperienceSpec {\n\t\treturn defineExperience(this.draft);\n\t}\n}\n\n/**\n * Start a fluent {@link ExperienceBuilder}. `experience('x')...build()` is\n * deep-equal to the equivalent `defineExperience({...})` object form.\n *\n * @param name - The Experience name (unique within the owning resource).\n * @returns A new builder seeded with the given name.\n */\nexport function experience(name: string): ExperienceBuilder {\n\treturn new ExperienceBuilder(name);\n}\n\n// ─── Compile / decompile ────────────────────────────────────────────────────────\n\nconst STYLE_KEYS = [\n\t'voice',\n\t'lexicon',\n\t'rhetoric',\n\t'policy',\n\t'editing',\n\t'color',\n] as const;\n\n/**\n * Compile an {@link ExperienceSpec} into the Experience create/update payload.\n * Pure — no network. Exposed so callers can inspect exactly what\n * {@link experienceAuthoring} `.create()` / `.update()` would write.\n *\n * @param spec - The spec to compile. Re-validated and defaulted through the\n * canonical schema first, so a spec that skipped {@link defineExperience} is\n * still safe to compile (throws `ZodError` if invalid).\n * @returns The compiled create payload.\n * @throws {@link https://zod.dev | ZodError} when `spec` fails validation.\n * @remarks\n * Field mapping: `agent.goal` → `stylePrompt`, `agent.internalGoal` →\n * `internalSystemPrompt`, `agent.spindles` → the nested\n * `stylePreferences.spindles` palette, modality → `stylePreferences.{tts,stt}`\n * (mirrored to `voiceId` / `voiceProvider`), style →\n * `stylePreferences.{voice,lexicon,rhetoric,policy,editing,color}`, commands →\n * `stylePreferences.{voiceCommands,buttonMappings}`, and `memory.retrieval` →\n * `memoryRetrievalEnabled`. The spec's `gate` and `guardrails` are **not**\n * emitted — an Experience does not store them today.\n * @example\n * ```typescript\n * const payload = compileToExperienceInput(spec);\n * // { name, stylePrompt?, stylePreferences?, voiceId?, ... }\n * ```\n */\nexport function compileToExperienceInput(\n\tspec: ExperienceSpec,\n): CreateExperienceInput {\n\t// ERA-4410 regression fix: validate + fill defaults through the\n\t// canonical Zod schema before any dereference. This is the same\n\t// guarantee `defineExperience` provides (line 413); without it any\n\t// caller reaching this function via a type-safety escape hatch (e.g.\n\t// `as never`, a stale `AgentSpec` import that TS resolves to `any`,\n\t// or a pre-v27 legacy shape) crashed on unguarded reads of\n\t// `spec.agent.spindles`, `spec.commands`, and `spec.style`. The\n\t// schema's `.default([])` on `agent.spindles` / `agent.grants` /\n\t// `commands` guarantees safe iteration below.\n\tspec = experienceSpecSchema.parse(spec) as ExperienceSpec;\n\t// style allows partial voice sliders; the backend stylePreferences is a\n\t// looseObject that fills/validates the rest, so widen here.\n\tconst style = {\n\t\t...(spec.style ?? {}),\n\t} as ExperienceStylePreferences;\n\n\tif (spec.modality.kind !== 'text') {\n\t\tif (spec.modality.tts) style.tts = spec.modality.tts;\n\t\tif (spec.modality.stt) style.stt = spec.modality.stt;\n\t}\n\n\t// ERA-5905: write the canonical nested `spindles` shape only — the legacy\n\t// flat `mcpServers`/`mcpTools` maps are no longer emitted. Built directly\n\t// from the authoring spindle list.\n\tconst nestedSpindles = spindleRefsToNested(spec.agent.spindles);\n\tif (nestedSpindles) style.spindles = nestedSpindles;\n\n\tconst voiceCommands = spec.commands\n\t\t.filter(\n\t\t\t(c): c is { kind: 'voice'; command: VoiceCommand } => c.kind === 'voice',\n\t\t)\n\t\t.map((c) => c.command);\n\tconst buttonMappings = spec.commands\n\t\t.filter(\n\t\t\t(c): c is { kind: 'button'; mapping: ButtonMapping } =>\n\t\t\t\tc.kind === 'button',\n\t\t)\n\t\t.map((c) => c.mapping);\n\tif (voiceCommands.length) style.voiceCommands = voiceCommands;\n\tif (buttonMappings.length) style.buttonMappings = buttonMappings;\n\n\tif (spec.model) style.model = spec.model;\n\n\tconst input: CreateExperienceInput = { name: spec.name };\n\tif (spec.description !== undefined) input.description = spec.description;\n\tif (spec.agent.goal !== undefined) input.stylePrompt = spec.agent.goal;\n\tif (spec.agent.internalGoal !== undefined)\n\t\tinput.internalSystemPrompt = spec.agent.internalGoal;\n\tif (spec.memory?.retrieval !== undefined)\n\t\tinput.memoryRetrievalEnabled = spec.memory.retrieval;\n\tif (spec.modality.kind !== 'text' && spec.modality.tts) {\n\t\tinput.voiceId = spec.modality.tts.voice_id;\n\t\tinput.voiceProvider = spec.modality.tts.provider;\n\t}\n\tif (Object.keys(style).length > 0) input.stylePreferences = style;\n\treturn input;\n}\n\n/**\n * Reverse-compile an {@link Experience} read model into an\n * {@link ExperienceSpec} for round-trip editing. Pure — no network.\n *\n * @param exp - The Experience to reverse-compile (e.g. from\n * `era.experiences.get(id)`).\n * @returns The reconstructed spec, re-validated through {@link defineExperience}.\n * @remarks\n * Lossy for two fields that the Experience read model does not carry:\n * `agent.internalGoal` and `agent.grants` come back empty. The modality `kind`\n * is inferred from the stored TTS/STT config rather than persisted, so a\n * `'multimodal'` spec comes back as `'voice'`, and a voice spec with no\n * TTS/STT overrides comes back as `'text'`. Every other field round-trips\n * content-stably.\n */\nexport function decompileExperience(exp: Experience): ExperienceSpec {\n\tconst sp = (exp.stylePreferences ?? {}) as ExperienceStylePreferences;\n\n\tconst style: ExperienceStyle = {};\n\tfor (const k of STYLE_KEYS) {\n\t\tconst v = sp[k];\n\t\tif (v !== undefined) (style as Record<string, unknown>)[k] = v;\n\t}\n\n\t// ERA-5905: read the canonical nested `spindles` shape only — the legacy\n\t// flat `mcpServers`/`mcpTools` maps are no longer consulted.\n\tconst spindles: SpindleRef[] = [];\n\tif (sp.spindles && typeof sp.spindles === 'object') {\n\t\tfor (const [ref, enabled] of nestedSpindlesToEntries(\n\t\t\tsp.spindles as SpindlePalette,\n\t\t))\n\t\t\tspindles.push({ ref, enabled });\n\t}\n\n\tconst commands: CommandBinding[] = [\n\t\t...(sp.voiceCommands ?? []).map(\n\t\t\t(cmd): CommandBinding => ({ kind: 'voice', command: cmd }),\n\t\t),\n\t\t...(sp.buttonMappings ?? []).map(\n\t\t\t(mapping): CommandBinding => ({ kind: 'button', mapping }),\n\t\t),\n\t];\n\n\tconst mod: ExperienceModality =\n\t\tsp.tts || sp.stt\n\t\t\t? {\n\t\t\t\t\tkind: 'voice',\n\t\t\t\t\t...(sp.tts ? { tts: sp.tts } : {}),\n\t\t\t\t\t...(sp.stt ? { stt: sp.stt } : {}),\n\t\t\t\t}\n\t\t\t: { kind: 'text' };\n\n\tconst agent: AgentCore = { spindles, grants: [] };\n\tif (exp.stylePrompt != null) agent.goal = exp.stylePrompt;\n\n\tconst spec: ExperienceSpec = {\n\t\tname: exp.name,\n\t\tagent,\n\t\tmodality: mod,\n\t\tcommands,\n\t};\n\tif (Object.keys(style).length > 0) spec.style = style;\n\tif (exp.description != null) spec.description = exp.description;\n\tif (exp.memoryRetrievalEnabled !== undefined)\n\t\tspec.memory = { retrieval: exp.memoryRetrievalEnabled };\n\tif (sp.model && typeof sp.model === 'object')\n\t\tspec.model = sp.model as ModelPolicy;\n\n\treturn defineExperience(spec);\n}\n\n/**\n * Structural diff between two specs — the list of changed leaf paths. Pure — no\n * network. Use it to preview what an update would change before writing it.\n *\n * @param a - The baseline spec.\n * @param b - The spec to compare against the baseline.\n * @returns One {@link ExperienceDiffEntry} per changed leaf; empty when the two\n * specs are structurally equal.\n * @example\n * ```typescript\n * const changes = diffExperiences(before, after);\n * // [{ path: 'agent.goal', before: '…', after: '…' }]\n * ```\n */\nexport function diffExperiences(\n\ta: ExperienceSpec,\n\tb: ExperienceSpec,\n): ExperienceDiffEntry[] {\n\tconst out: ExperienceDiffEntry[] = [];\n\tconst walk = (x: unknown, y: unknown, path: string): void => {\n\t\tif (JSON.stringify(x) === JSON.stringify(y)) return;\n\t\tconst xo = x && typeof x === 'object' && !Array.isArray(x);\n\t\tconst yo = y && typeof y === 'object' && !Array.isArray(y);\n\t\tif (xo && yo) {\n\t\t\tconst keys = new Set([\n\t\t\t\t...Object.keys(x as object),\n\t\t\t\t...Object.keys(y as object),\n\t\t\t]);\n\t\t\tfor (const k of keys) {\n\t\t\t\twalk(\n\t\t\t\t\t(x as Record<string, unknown>)[k],\n\t\t\t\t\t(y as Record<string, unknown>)[k],\n\t\t\t\t\tpath ? `${path}.${k}` : k,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tout.push({ path, before: x, after: y });\n\t};\n\twalk(a, b, '');\n\treturn out;\n}\n\n// ─── Facade ─────────────────────────────────────────────────────────────────────\n\n/**\n * Resolve an output `slot` to a concrete deviceId via the caller's slot\n * bindings (ERA-6128 over the ERA-4500 registry). Fails closed, mirroring the\n * orchestrator's emit-time semantics: an unbound slot, or a bound device whose\n * `credentialStatus` is not `ok`, throws rather than connecting.\n */\nasync function resolveOutputDeviceId(\n\tclient: EraClient,\n\tslot: string,\n): Promise<string> {\n\tconst bindings = await devices(client).listSlotBindings();\n\tconst match = bindings.find((b) => b.slot === slot);\n\tif (!match) {\n\t\tconst known = bindings.map((b) => b.slot);\n\t\tthrow new Error(\n\t\t\t`experience.ports.output.subscribe: slot \"${slot}\" is not bound to a device` +\n\t\t\t\t(known.length > 0\n\t\t\t\t\t? ` (bound slots: ${known.join(', ')})`\n\t\t\t\t\t: ' (no slots bound)') +\n\t\t\t\t'. Bind it with devices.bindSlot(deviceId, slot), or pass { deviceId }.',\n\t\t);\n\t}\n\tif (match.credentialStatus !== 'ok') {\n\t\tthrow new Error(\n\t\t\t`experience.ports.output.subscribe: slot \"${slot}\" resolves to device ` +\n\t\t\t\t`${match.deviceId}, whose credentialStatus is \"${match.credentialStatus}\" ` +\n\t\t\t\t'— refusing to connect (fail-closed). Re-provision the device key, then retry.',\n\t\t);\n\t}\n\treturn match.deviceId;\n}\n\n/** Options for {@link ExperienceOutputPort.subscribe}. */\nexport interface OutputSubscribeOptions {\n\t/** Connect to this device directly. Takes precedence over `slot`. */\n\tdeviceId?: string;\n\t/**\n\t * Named output slot to resolve to a device via the caller's slot bindings.\n\t * Ignored when `deviceId` is set. Resolution fails closed: an unbound slot,\n\t * or a device whose credential status is not `ok`, throws (surfaced via\n\t * `onError`).\n\t */\n\tslot?: string;\n\t/**\n\t * Device-bound `era_sk_*` bearer token for the socket. Omit to fall back to\n\t * the SDK's stored device key, then to the client's access token.\n\t */\n\ttoken?: string;\n\t/**\n\t * Called with any error raised while resolving the slot or connecting the\n\t * stream. Without it, such errors reject on the background task.\n\t */\n\tonError?: (err: Error) => void;\n}\n\n/** The output role-port — subscribe to the outputs emitted by an Experience's runs. */\nexport interface ExperienceOutputPort {\n\t/**\n\t * Subscribe to the command frames a run's outputs deliver to a device:\n\t * connects the device stream and invokes `handler` for each typed frame\n\t * (`actuator` / `say`). Returns an unsubscribe function that closes the stream.\n\t *\n\t * @param handler - Called once per delivered {@link DeviceCommand} frame.\n\t * @param opts - Targeting and connection options. Provide exactly one of\n\t * `deviceId` (explicit) or `slot` (resolved via your device bindings).\n\t * @returns An unsubscribe function that closes the stream.\n\t * @throws {@link Error} synchronously when neither `deviceId` nor `slot` is\n\t * given. Slot-resolution failures (unbound slot, or a device whose credential\n\t * status is not `ok`) surface via `opts.onError` when provided.\n\t * @remarks Requires a device-bound `era_sk_*` key (or `opts.token`). Slot\n\t * resolution fails closed — it refuses to connect rather than reaching a\n\t * device with a compromised credential.\n\t * @example\n\t * ```typescript\n\t * const unsubscribe = bound.ports.output.subscribe(\n\t * (command) => console.log(command.type), // 'actuator' | 'say'\n\t * { slot: 'porch-light', onError: console.error },\n\t * );\n\t * ```\n\t */\n\tsubscribe(\n\t\thandler: (command: DeviceCommand) => void,\n\t\topts?: OutputSubscribeOptions,\n\t): () => void;\n}\n\n/**\n * The three role-ports of a bound Experience: `input` (publish a trigger),\n * `run` (drive/observe a turn), and `output` (subscribe to emitted outputs).\n */\nexport interface ExperiencePorts {\n\t/**\n\t * Input port — publish a {@link TriggerEvent} to fire the Experience\n\t * (`POST /trigger`).\n\t * @param event - The trigger to publish. Set `source.sessionId` to route\n\t * the event into that live session.\n\t * @param opts - Optional per-request options.\n\t * @returns Resolves once the trigger is accepted.\n\t */\n\tinput(event: TriggerEvent, opts?: RequestOptions): Promise<void>;\n\t/**\n\t * Run port — drive and observe a turn, streaming {@link ExperienceRunEvent}s.\n\t * @param input - The turn input.\n\t * @returns An async iterable of run events.\n\t * @throws {@link Error} when called before `create()` has set the Experience id.\n\t * @remarks Single-turn today; requires `create()` first to obtain the id.\n\t */\n\trun(input: ExperienceRunInput): AsyncIterable<ExperienceRunEvent>;\n\t/** Output port — subscribe to the outputs emitted by the Experience's runs. */\n\toutput: ExperienceOutputPort;\n}\n\n/**\n * A client-bound Experience handle returned by\n * {@link ExperienceAuthoringModule.bind}. {@link defineExperience} stays\n * pure/serializable; binding adds the client plus the three role-ports.\n */\nexport interface BoundExperience {\n\t/** The authored spec (pure, serializable). */\n\treadonly spec: ExperienceSpec;\n\t/** The created Experience's id — `undefined` until `create()` runs. */\n\treadonly experienceId: string | undefined;\n\t/**\n\t * Create the Experience under an RBAC resource and set `experienceId` so the\n\t * run port works.\n\t * @param target - The owning resource.\n\t * @param target.resourceId - The RBAC resource id to create the Experience under.\n\t * @param opts - Optional per-request options.\n\t * @returns The created {@link Experience}.\n\t * @remarks Promotion to a published version is a separate review flow — use\n\t * `era.experienceVersions` (`submitForReview` / `approveReview`).\n\t */\n\tcreate(\n\t\ttarget: { resourceId: string },\n\t\topts?: RequestOptions,\n\t): Promise<Experience>;\n\t/**\n\t * Locally simulate this Experience against a mock trigger — no network.\n\t * @param event - The trigger event to simulate.\n\t * @returns The {@link SimulationResult}.\n\t */\n\tsimulate(event: TriggerEvent): SimulationResult;\n\t/** The three role-ports: `input` (publish) · `run` (stream) · `output` (subscribe). */\n\treadonly ports: ExperiencePorts;\n}\n\n/**\n * The client-bound experience authoring and run facade, returned by\n * {@link experienceAuthoring}. Compiles pure {@link ExperienceSpec}s into\n * Experience CRUD and draft calls, drives turns over the streaming chat\n * endpoint, and binds specs into runnable {@link BoundExperience} handles.\n * Reach it as `experienceAuthoring(era)` (it is not attached to `era` directly).\n */\nexport interface ExperienceAuthoringModule {\n\t/**\n\t * Compile a spec and create the Experience under an RBAC resource.\n\t * @param spec - The spec to compile and create.\n\t * @param target - The owning resource.\n\t * @param target.resourceId - The RBAC resource id to create the Experience under.\n\t * @param opts - Optional per-request options.\n\t * @returns The created {@link Experience}.\n\t * @throws {EraAPIError} On a non-2xx response (400 invalid body, 403 missing\n\t * `write` on the resource, 404 unknown resource).\n\t * @example\n\t * ```typescript\n\t * const created = await experienceAuthoring(era).create(spec, {\n\t * resourceId: 'res_0000000000',\n\t * });\n\t * ```\n\t */\n\tcreate(\n\t\tspec: ExperienceSpec,\n\t\ttarget: { resourceId: string },\n\t\topts?: RequestOptions,\n\t): Promise<Experience>;\n\n\t/**\n\t * Compile a spec and update an existing Experience.\n\t * @param experienceId - The Experience to update.\n\t * @param spec - The spec whose compiled payload replaces the Experience config.\n\t * @param opts - Optional per-request options.\n\t * @returns The updated {@link Experience}.\n\t * @throws {EraAPIError} On a non-2xx response (400 invalid body, 403 not the\n\t * owner/writer, 404 unknown id).\n\t */\n\tupdate(\n\t\texperienceId: string,\n\t\tspec: ExperienceSpec,\n\t\topts?: RequestOptions,\n\t): Promise<Experience>;\n\n\t/**\n\t * Fetch an Experience and reverse-compile it into an {@link ExperienceSpec}\n\t * for round-trip editing.\n\t * @param experienceId - The Experience to fetch.\n\t * @param opts - Optional per-request options.\n\t * @returns The reconstructed spec (lossy for `agent.internalGoal` and\n\t * `agent.grants`; see {@link decompileExperience}).\n\t * @throws {EraAPIError} On a non-2xx response (403 no read access, 404 not found).\n\t */\n\tfromExperience(\n\t\texperienceId: string,\n\t\topts?: RequestOptions,\n\t): Promise<ExperienceSpec>;\n\n\t/**\n\t * Create a new draft version of an Experience seeded from a spec (creates the\n\t * draft, then writes the compiled content into it).\n\t * @param experienceId - The Experience to draft a new version of.\n\t * @param spec - The spec whose compiled payload seeds the draft.\n\t * @param opts - Optional per-request options.\n\t * @returns The draft's {@link VersionMutationResult}.\n\t * @throws {EraAPIError} On a non-2xx response from either the draft-create or\n\t * the draft-update call: 403 when you lack draft permissions on the\n\t * experience, 404 unknown experience, 409 when a concurrent writer updated\n\t * the draft between the create and the content write.\n\t * @remarks A draft is not live — promote it through the review flow on\n\t * `era.experienceVersions` to publish.\n\t */\n\tdraft(\n\t\texperienceId: string,\n\t\tspec: ExperienceSpec,\n\t\topts?: RequestOptions,\n\t): Promise<VersionMutationResult>;\n\n\t/**\n\t * Structural diff between two specs — the list of changed leaf paths. Pure,\n\t * no network. Alias of {@link diffExperiences}.\n\t * @param a - The baseline spec.\n\t * @param b - The spec to compare against the baseline.\n\t * @returns One {@link ExperienceDiffEntry} per changed leaf.\n\t */\n\tdiff(a: ExperienceSpec, b: ExperienceSpec): ExperienceDiffEntry[];\n\n\t/**\n\t * Inspect the compiled Experience payload for a spec without writing it.\n\t * Pure, no network. Alias of {@link compileToExperienceInput}.\n\t * @param spec - The spec to compile.\n\t * @returns The compiled create payload.\n\t */\n\ttoExperienceInput(spec: ExperienceSpec): CreateExperienceInput;\n\n\t/**\n\t * Run port — drive one turn against an Experience, streaming normalized\n\t * {@link ExperienceRunEvent}s over SSE.\n\t * @param experienceId - The Experience to run.\n\t * @param input - The turn input.\n\t * @returns An async iterable of run events; the terminal `done` carries\n\t * `toolsUsed` plus `requestId` / `costCents` / `balanceCents` when the stream\n\t * provides them.\n\t * @example\n\t * ```typescript\n\t * for await (const ev of experienceAuthoring(era).run('exp_0000000000', {\n\t * input: 'Plan my day',\n\t * })) {\n\t * if (ev.type === 'chunk') process.stdout.write(ev.delta);\n\t * if (ev.type === 'done') console.log('\\ntools used:', ev.toolsUsed);\n\t * }\n\t * ```\n\t */\n\trun(\n\t\texperienceId: string,\n\t\tinput: ExperienceRunInput,\n\t): AsyncIterable<ExperienceRunEvent>;\n\n\t/**\n\t * Non-streaming convenience — drive {@link ExperienceAuthoringModule.run} to\n\t * completion and return the aggregated reply.\n\t *\n\t * @param experienceId - The Experience to run.\n\t * @param input - The turn input.\n\t * @returns The aggregated {@link ExperienceReply}.\n\t * @throws {@link EraStreamError} when the run stream carried a terminal\n\t * `error` event (a server-emitted error, or an SDK-side parse/read\n\t * failure). The aggregation cannot return a meaningful reply for an errored\n\t * stream — a bare `{ text: '' }` is indistinguishable from a genuinely empty\n\t * reply — so it throws instead. The streaming `run()` path keeps yielding\n\t * `error` events verbatim; only this aggregation convenience throws. A\n\t * `blocked` (moderation) outcome alone does NOT throw — it is surfaced\n\t * additively on `reply.blocked` for the caller to inspect. Precedence when\n\t * a stream carries BOTH a block and an error: the error throw wins, and the\n\t * block's category (when a `blocked` event preceded the error) rides on the\n\t * thrown error's `safetyCategory`.\n\t * @example\n\t * ```typescript\n\t * const reply = await experienceAuthoring(era).complete('exp_0000000000', {\n\t * input: 'Someone is at the door.',\n\t * });\n\t * if (reply.blocked) console.warn('refused:', reply.blocked.safetyCategory);\n\t * else console.log(reply.text);\n\t * ```\n\t */\n\tcomplete(\n\t\texperienceId: string,\n\t\tinput: ExperienceRunInput,\n\t): Promise<ExperienceReply>;\n\n\t/**\n\t * Bind a pure {@link ExperienceSpec} to this client → a\n\t * {@link BoundExperience} exposing the three role-ports\n\t * (`.ports = { input, run, output }`).\n\t * @param spec - The spec to bind.\n\t * @returns The client-bound handle.\n\t */\n\tbind(spec: ExperienceSpec): BoundExperience;\n\n\t/**\n\t * Inspect a run's state by run id — reuses the chains run-state surface\n\t * ({@link chains}.getRun): gate decision, spindle calls and results, outputs,\n\t * and guardrail checks from the run-events log.\n\t * @param runId - The run to inspect.\n\t * @param opts - Optional per-request options.\n\t * @returns The {@link ChainRunState}.\n\t * @throws {EraAPIError} On a non-2xx response (404 unknown run).\n\t */\n\tgetRun(runId: string, opts?: RequestOptions): Promise<ChainRunState>;\n}\n\n/**\n * Construct the experience authoring and run facade bound to an `EraClient`.\n *\n * @param client - The Era client to bind the facade to.\n * @returns The {@link ExperienceAuthoringModule} — compile/create/update/draft,\n * run/complete, diff/inspect, and `bind`.\n * @example\n * ```typescript\n * const authoring = experienceAuthoring(era);\n * const created = await authoring.create(spec, { resourceId: 'res_0000000000' });\n * const reply = await authoring.complete(created.id, { input: 'Hello' });\n * ```\n */\nexport function experienceAuthoring(\n\tclient: EraClient,\n): ExperienceAuthoringModule {\n\tconst exp = experiences(client);\n\tconst vers = experienceVersions(client);\n\n\t// Open the underlying ingress stream and wrap it in the run-event decorator.\n\t// `run()` exposes only the events; `complete()` also needs the raw handle so\n\t// it can read `meta()` (requestId) even when the stream aborts without a\n\t// `done` event (parseSSE emits NO synthetic done on SSE_READ_ERROR /\n\t// SSE_LINE_TOO_LONG paths).\n\tconst openRun = (\n\t\texperienceId: string,\n\t\tinput: ExperienceRunInput,\n\t): {\n\t\thandle: StreamHandle;\n\t\tevents: AsyncIterable<ExperienceRunEvent>;\n\t} => {\n\t\tconst isVoice = input.modality === 'voice';\n\t\tconst body: Record<string, unknown> = {\n\t\t\tmessage: input.input,\n\t\t\texperienceId,\n\t\t\t// `/chat` defaults to a non-streaming JSON response\n\t\t\t// (ChatRequest.stream = false server-side); opt into SSE explicitly.\n\t\t\tstream: true,\n\t\t};\n\t\tif (input.sessionId !== undefined) body.sessionId = input.sessionId;\n\t\tif (isVoice) body.voice = true;\n\t\tif (input.metadata !== undefined) body.metadata = input.metadata;\n\t\tconst streamOpts: RequestOptions & { method?: string; body?: unknown } = {\n\t\t\tmethod: 'POST',\n\t\t\tbody,\n\t\t};\n\t\tif (input.signal) streamOpts.signal = input.signal;\n\t\t// ERA-5140: forward the caller-pinned idempotencyKey to the transport\n\t\t// so wrapper retries dedupe against the L1/L2 idempotency stack\n\t\t// (ERA-5088 + ERA-5102 + ERA-5111) instead of minting a fresh UUID\n\t\t// per call. Restores the fix originally added to the pre-rename\n\t\t// `agents.ts` module (commit 26df09b) that was lost in the ERA-4410\n\t\t// rename to `experienceAuthoring.ts` (commit 702a391).\n\t\tif (input.idempotencyKey !== undefined)\n\t\t\tstreamOpts.idempotencyKey = input.idempotencyKey;\n\t\tconst handle = client.stream(\n\t\t\t'ingress',\n\t\t\tisVoice ? '/chat/voice' : '/chat',\n\t\t\tstreamOpts,\n\t\t);\n\t\tconst events = (async function* (): AsyncGenerator<\n\t\t\tExperienceRunEvent,\n\t\t\tvoid,\n\t\t\tvoid\n\t\t> {\n\t\t\tconst toolsUsed: string[] = [];\n\t\t\tfor await (const ev of handle) {\n\t\t\t\tif (ev.type === 'tool_call') {\n\t\t\t\t\ttoolsUsed.push(ev.name);\n\t\t\t\t\tyield ev;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (ev.type === 'done') {\n\t\t\t\t\tlet meta: ResponseMeta = {};\n\t\t\t\t\ttry {\n\t\t\t\t\t\tmeta = await handle.meta();\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t/* meta unavailable — leave cost/balance unset */\n\t\t\t\t\t}\n\t\t\t\t\tconst done: ExperienceRunDoneEvent = {\n\t\t\t\t\t\ttype: 'done',\n\t\t\t\t\t\tfullContent: ev.fullContent,\n\t\t\t\t\t\t// Prefer the wire-reported tools_used (generated\n\t\t\t\t\t\t// ChatDoneStreamEvent) — the server's provenance is\n\t\t\t\t\t\t// authoritative; the client-side tool_call aggregation is\n\t\t\t\t\t\t// the fallback for servers that omit the field.\n\t\t\t\t\t\ttoolsUsed: ev.toolsUsed ?? [...toolsUsed],\n\t\t\t\t\t};\n\t\t\t\t\tif (ev.usage) done.usage = ev.usage;\n\t\t\t\t\tif (meta.requestId !== undefined) done.requestId = meta.requestId;\n\t\t\t\t\t// Cost/balance are integer cents (ERA-4326/ERA-5293). Prefer the\n\t\t\t\t\t// explicit stream `done` event values; fall back to the response\n\t\t\t\t\t// `_meta` (costCents/balanceCents) when the event omits them.\n\t\t\t\t\tif (meta.costCents !== undefined) done.costCents = meta.costCents;\n\t\t\t\t\tif (meta.balanceCents !== undefined)\n\t\t\t\t\t\tdone.balanceCents = meta.balanceCents;\n\t\t\t\t\tif (ev.costCents !== undefined) done.costCents = ev.costCents;\n\t\t\t\t\tif (ev.balanceCents !== undefined)\n\t\t\t\t\t\tdone.balanceCents = ev.balanceCents;\n\t\t\t\t\tyield done;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tyield ev;\n\t\t\t}\n\t\t})();\n\t\treturn { handle, events };\n\t};\n\n\tconst runImpl = (\n\t\texperienceId: string,\n\t\tinput: ExperienceRunInput,\n\t): AsyncIterable<ExperienceRunEvent> => openRun(experienceId, input).events;\n\n\treturn {\n\t\tcreate(\n\t\t\tspec: ExperienceSpec,\n\t\t\ttarget: { resourceId: string },\n\t\t\topts?: RequestOptions,\n\t\t): Promise<Experience> {\n\t\t\treturn exp.create(\n\t\t\t\t{ resourceId: target.resourceId, ...compileToExperienceInput(spec) },\n\t\t\t\topts,\n\t\t\t);\n\t\t},\n\t\tupdate(\n\t\t\texperienceId: string,\n\t\t\tspec: ExperienceSpec,\n\t\t\topts?: RequestOptions,\n\t\t): Promise<Experience> {\n\t\t\t// compile yields CreateExperienceInput (description: string|null);\n\t\t\t// UpdateExperienceInput uses string|undefined. compile never emits null.\n\t\t\treturn exp.update(\n\t\t\t\texperienceId,\n\t\t\t\tcompileToExperienceInput(spec) as unknown as UpdateExperienceInput,\n\t\t\t\topts,\n\t\t\t);\n\t\t},\n\t\tasync fromExperience(\n\t\t\texperienceId: string,\n\t\t\topts?: RequestOptions,\n\t\t): Promise<ExperienceSpec> {\n\t\t\treturn decompileExperience(await exp.get(experienceId, opts));\n\t\t},\n\t\tasync draft(\n\t\t\texperienceId: string,\n\t\t\tspec: ExperienceSpec,\n\t\t\topts?: RequestOptions,\n\t\t): Promise<VersionMutationResult> {\n\t\t\tconst created = await vers.createDraft(experienceId, undefined, opts);\n\t\t\t// compile yields CreateExperienceInput; its fields are a superset of\n\t\t\t// UpdateDraftInput's (compile never sets isActive, and\n\t\t\t// stylePreferences is only ever an object here, never null).\n\t\t\tconst body = compileToExperienceInput(\n\t\t\t\tspec,\n\t\t\t) as unknown as UpdateDraftInput;\n\t\t\treturn vers.updateDraft(\n\t\t\t\tcreated.version.id,\n\t\t\t\tbody,\n\t\t\t\tcreated.version.updatedAt,\n\t\t\t\topts,\n\t\t\t);\n\t\t},\n\t\trun: runImpl,\n\t\tasync complete(\n\t\t\texperienceId: string,\n\t\t\tinput: ExperienceRunInput,\n\t\t): Promise<ExperienceReply> {\n\t\t\tconst { handle, events } = openRun(experienceId, input);\n\t\t\tlet done: ExperienceRunDoneEvent | null = null;\n\t\t\tlet blocked: StreamBlockedEvent | null = null;\n\t\t\tlet streamError: StreamErrorEvent | null = null;\n\t\t\tlet blockedBeforeError: StreamBlockedEvent | null = null;\n\t\t\tlet text = '';\n\t\t\tfor await (const ev of events) {\n\t\t\t\tif (ev.type === 'chunk') text += ev.delta;\n\t\t\t\telse if (ev.type === 'done') done = ev;\n\t\t\t\telse if (ev.type === 'blocked') blocked = ev;\n\t\t\t\t// Capture the FIRST error event — the root cause. For server-emitted\n\t\t\t\t// errors, parseSSE keeps reading (and flushes a synthetic `done`); on\n\t\t\t\t// aborted paths (SSE_READ_ERROR / SSE_LINE_TOO_LONG) the generator\n\t\t\t\t// closes right after the error with NO synthetic done — either way we\n\t\t\t\t// finish draining, then throw below.\n\t\t\t\telse if (ev.type === 'error' && streamError === null) {\n\t\t\t\t\tstreamError = ev;\n\t\t\t\t\t// Snapshot any block already observed — if the stream carried both,\n\t\t\t\t\t// the throw wins and the block's category rides on the error.\n\t\t\t\t\tblockedBeforeError = blocked;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// An errored stream cannot yield a meaningful aggregation — a bare\n\t\t\t// `{ text: '' }` is indistinguishable from a genuinely empty reply — so\n\t\t\t// throw a typed EraStreamError rather than returning a hollow reply.\n\t\t\t// (The streaming `run()` path keeps yielding the `error` event; only\n\t\t\t// this convenience throws.) `requestId` comes from the handle's response\n\t\t\t// headers — NOT from the enriched `done` event, which never arrives on\n\t\t\t// aborted streams (mirrors chat.complete()).\n\t\t\tif (streamError) {\n\t\t\t\tlet meta: ResponseMeta = {};\n\t\t\t\ttry {\n\t\t\t\t\tmeta = await handle.meta();\n\t\t\t\t} catch {\n\t\t\t\t\t/* meta unavailable — omit requestId from the thrown error */\n\t\t\t\t}\n\t\t\t\tthrow new EraStreamError({\n\t\t\t\t\tmessage: streamError.message,\n\t\t\t\t\t...(streamError.code !== undefined && {\n\t\t\t\t\t\terrorCode: streamError.code,\n\t\t\t\t\t}),\n\t\t\t\t\t...(meta.requestId !== undefined && { requestId: meta.requestId }),\n\t\t\t\t\t...(blockedBeforeError?.safetyCategory !== undefined && {\n\t\t\t\t\t\tsafetyCategory: blockedBeforeError.safetyCategory,\n\t\t\t\t\t}),\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst reply: ExperienceReply = {\n\t\t\t\ttext: done?.fullContent ?? text,\n\t\t\t\ttoolsUsed: done?.toolsUsed ?? [],\n\t\t\t};\n\t\t\t// Surface a moderation block additively — the caller inspects\n\t\t\t// `reply.blocked` rather than catching a throw (a block is a policy\n\t\t\t// outcome, not a transport failure; the established complete()\n\t\t\t// contract stays intact).\n\t\t\tif (blocked) {\n\t\t\t\treply.blocked = {\n\t\t\t\t\t...(blocked.safetyCategory !== undefined && {\n\t\t\t\t\t\tsafetyCategory: blocked.safetyCategory,\n\t\t\t\t\t}),\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (done?.usage) reply.usage = done.usage;\n\t\t\tif (done?.cost !== undefined) reply.cost = done.cost;\n\t\t\tif (done?.balance !== undefined) reply.balance = done.balance;\n\t\t\tif (done?.requestId !== undefined) reply.requestId = done.requestId;\n\t\t\tif (done?.costCents !== undefined) reply.costCents = done.costCents;\n\t\t\tif (done?.balanceCents !== undefined)\n\t\t\t\treply.balanceCents = done.balanceCents;\n\t\t\treturn reply;\n\t\t},\n\t\tdiff: diffExperiences,\n\t\ttoExperienceInput: compileToExperienceInput,\n\t\tgetRun(runId: string, opts?: RequestOptions): Promise<ChainRunState> {\n\t\t\treturn chains(client).getRun(runId, opts);\n\t\t},\n\t\tbind(spec: ExperienceSpec): BoundExperience {\n\t\t\tlet experienceId: string | undefined;\n\t\t\tconst ports: ExperiencePorts = {\n\t\t\t\tinput(event: TriggerEvent, opts?: RequestOptions): Promise<void> {\n\t\t\t\t\tconst init: RequestOptions & { method?: string; body?: unknown } = {\n\t\t\t\t\t\t...opts,\n\t\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\t\tbody: event,\n\t\t\t\t\t};\n\t\t\t\t\treturn client.ingress<void>('/trigger', init);\n\t\t\t\t},\n\t\t\t\trun(input: ExperienceRunInput): AsyncIterable<ExperienceRunEvent> {\n\t\t\t\t\tif (experienceId === undefined) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t'experience.ports.run: call create() to obtain an experienceId first.',\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\treturn runImpl(experienceId, input);\n\t\t\t\t},\n\t\t\t\toutput: {\n\t\t\t\t\tsubscribe(\n\t\t\t\t\t\thandler: (command: DeviceCommand) => void,\n\t\t\t\t\t\topts?: OutputSubscribeOptions,\n\t\t\t\t\t): () => void {\n\t\t\t\t\t\t// A target is required — an explicit deviceId, or a slot we\n\t\t\t\t\t\t// resolve via the caller's bindings (ERA-6128). Fail fast and\n\t\t\t\t\t\t// synchronously when neither is given.\n\t\t\t\t\t\tif (!opts?.deviceId && !opts?.slot) {\n\t\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\t'experience.ports.output.subscribe: pass { deviceId }, or { slot } to resolve via your device bindings.',\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlet stream: DeviceStream | null = null;\n\t\t\t\t\t\tlet unsubscribed = false;\n\t\t\t\t\t\tconst run = async (): Promise<void> => {\n\t\t\t\t\t\t\t// deviceId wins; otherwise resolve the slot (fails closed).\n\t\t\t\t\t\t\tconst deviceId =\n\t\t\t\t\t\t\t\topts.deviceId ??\n\t\t\t\t\t\t\t\t(await resolveOutputDeviceId(\n\t\t\t\t\t\t\t\t\tclient,\n\t\t\t\t\t\t\t\t\t// biome-ignore lint/style/noNonNullAssertion: guarded above — slot is set when deviceId is not\n\t\t\t\t\t\t\t\t\topts.slot!,\n\t\t\t\t\t\t\t\t));\n\t\t\t\t\t\t\tif (unsubscribed) return;\n\t\t\t\t\t\t\tconst s = await deviceStream(client).connect(deviceId, {\n\t\t\t\t\t\t\t\t...(opts.token !== undefined ? { token: opts.token } : {}),\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tif (unsubscribed) {\n\t\t\t\t\t\t\t\ts.close();\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tstream = s;\n\t\t\t\t\t\t\tfor await (const command of s) handler(command);\n\t\t\t\t\t\t};\n\t\t\t\t\t\tvoid run().catch((err: unknown) => {\n\t\t\t\t\t\t\tconst e = err instanceof Error ? err : new Error(String(err));\n\t\t\t\t\t\t\tif (opts.onError) opts.onError(e);\n\t\t\t\t\t\t\telse throw e;\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn () => {\n\t\t\t\t\t\t\tunsubscribed = true;\n\t\t\t\t\t\t\tstream?.close();\n\t\t\t\t\t\t};\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t};\n\t\t\treturn {\n\t\t\t\tget spec(): ExperienceSpec {\n\t\t\t\t\treturn spec;\n\t\t\t\t},\n\t\t\t\tget experienceId(): string | undefined {\n\t\t\t\t\treturn experienceId;\n\t\t\t\t},\n\t\t\t\tasync create(\n\t\t\t\t\ttarget: { resourceId: string },\n\t\t\t\t\topts?: RequestOptions,\n\t\t\t\t): Promise<Experience> {\n\t\t\t\t\tconst created = await exp.create(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tresourceId: target.resourceId,\n\t\t\t\t\t\t\t...compileToExperienceInput(spec),\n\t\t\t\t\t\t},\n\t\t\t\t\t\topts,\n\t\t\t\t\t);\n\t\t\t\t\texperienceId = created.id;\n\t\t\t\t\treturn created;\n\t\t\t\t},\n\t\t\t\tsimulate(event: TriggerEvent): SimulationResult {\n\t\t\t\t\treturn simulateSpec(spec, event);\n\t\t\t\t},\n\t\t\t\tports,\n\t\t\t};\n\t\t},\n\t};\n}\n","// keyStore module — durable two-slot device-key storage (ERA-6198).\n//\n// The client half of device-key rotation (ERA-6037). The server revokes a\n// predecessor key the moment its successor FIRST authenticates, and presenting\n// a rotated-out key trips reuse detection: the whole lineage is revoked and the\n// device is flagged `credential_compromised` — bricked until re-provisioned.\n// The only way rotation is restart-safe is write-ahead persistence: the\n// successor must be durably stored BEFORE it is ever used.\n//\n// Two-slot (A/B) protocol, same shape as firmware OTA slots:\n// 1. renew mints K2 → savePending(K2) (durable BEFORE first use)\n// 2. K2 first authenticates → server revokes K1 (inside its grace window)\n// 3. connection confirmed → promote() (pending → current)\n// Boot: try `current`; on auth-reject (1008) fall back to `pending` once.\n//\n// Every crash window is safe: before savePending → K1 still live (an unused\n// successor is replaced server-side on the next renew); after savePending but\n// before activation → K1 connects, K2 idle; after activation but before\n// promote → K1 is rejected and the boot fallback finds K2 in `pending`; after\n// promote → K2 is `current`. No ordering leaves the device holding ONLY a\n// rotated-out key.\n//\n// `deviceStream({ autoRenew: true, keyStore })` drives this protocol and will\n// not activate a successor until `savePending` has resolved AND a read-back\n// confirms the store returns it (catches no-op implementations).\n//\n// Tree-shakeable barrel import: `import { fileKeyStore } from '@era-laboratories/era-sdk'`.\n\n/**\n * Durable two-slot storage for a device-bound key.\n *\n * DURABILITY CONTRACT — read this before implementing for NVS/Keychain/TPM:\n * `savePending` MUST NOT resolve until the key would survive an immediate\n * power loss (fsync or the platform equivalent). If it resolves before the\n * bytes are durable, a crash after the successor's first use leaves the device\n * holding only the revoked predecessor — the next boot trips reuse detection\n * and the device is flagged `credential_compromised`.\n */\nexport interface DeviceKeyStore {\n\t/**\n\t * Read both slots. Either may be absent (a fresh device returns `{}`).\n\t * Called at connect time to pick the key to present, and again as the\n\t * read-back that gates successor activation.\n\t *\n\t * @returns The two slots: `current` (the active key) and `pending` (a\n\t * durably-stored successor awaiting its first confirmed connection). A key\n\t * that is absent from the store is omitted from the result.\n\t * @throws {Error} If the underlying store cannot be read or its contents are\n\t * corrupt — a corrupt store MUST throw rather than read as empty (an empty\n\t * read could route the caller onto a revoked key).\n\t */\n\tload(): Promise<{ current?: string; pending?: string }>;\n\t/**\n\t * Write-ahead the successor key into the `pending` slot. Must NOT resolve\n\t * until the bytes would survive an immediate power loss (fsync or the\n\t * platform equivalent) — this durability is the whole point of the store.\n\t *\n\t * @param key - The successor `era_sk_*` key to persist before it is used.\n\t * @throws {Error} If the key cannot be durably written.\n\t */\n\tsavePending(key: string): Promise<void>;\n\t/**\n\t * Promote `pending` to `current` and clear `pending`, called after the\n\t * successor's first confirmed connection. Idempotent: a no-op when there is\n\t * no pending key.\n\t *\n\t * @throws {Error} If the store cannot be updated durably.\n\t */\n\tpromote(): Promise<void>;\n}\n\n/**\n * On-disk shape for {@link fileKeyStore}.\n *\n * @internal Implementation detail of the file-backed store — not exported.\n */\ninterface FileKeyStoreState {\n\tcurrent?: string;\n\tpending?: string;\n}\n\nfunction parseState(raw: string, path: string): FileKeyStoreState {\n\tlet data: unknown;\n\ttry {\n\t\tdata = JSON.parse(raw);\n\t} catch (err) {\n\t\t// A corrupt store is a loud failure, never a silent empty store — an\n\t\t// \"empty\" read here could route the caller onto a revoked key.\n\t\tthrow new Error(\n\t\t\t`fileKeyStore: ${path} is corrupt (invalid JSON): ${err instanceof Error ? err.message : String(err)}`,\n\t\t);\n\t}\n\tif (typeof data !== 'object' || data === null) {\n\t\tthrow new Error(`fileKeyStore: ${path} is corrupt (expected an object)`);\n\t}\n\tconst obj = data as Record<string, unknown>;\n\tconst out: FileKeyStoreState = {};\n\tif (typeof obj.current === 'string') out.current = obj.current;\n\tif (typeof obj.pending === 'string') out.pending = obj.pending;\n\treturn out;\n}\n\n/**\n * File-backed {@link DeviceKeyStore} — the reference implementation for\n * Node-capable devices (Pi, kiosk, desktop). Two-slot JSON file with atomic\n * replace (write temp + fsync + rename) and a directory fsync so the rename\n * itself survives power loss. Node-only: uses `node:fs/promises` via dynamic\n * import so browser bundles that tree-shake the barrel stay clean.\n *\n * @param path - Absolute path to the JSON file that holds the two slots. The\n * parent directory must already exist. Must be non-empty.\n * @returns A {@link DeviceKeyStore} backed by that file — hand it to\n * `deviceStream(...).connect(deviceId, { autoRenew: true, keyStore })`.\n * @throws {Error} Synchronously if `path` is empty. Its methods reject if the\n * file is unreadable/unwritable, or (on `load`) if the file exists but is\n * corrupt (invalid JSON or wrong shape); a missing file reads as `{}`.\n *\n * @example\n * ```ts\n * const store = fileKeyStore('/var/lib/mydevice/era-key.json');\n * const stream = await deviceStream(era).connect(deviceId, {\n * autoRenew: true,\n * keyStore: store,\n * });\n * ```\n *\n * @remarks\n * `savePending` writes to a `.tmp` sibling, fsyncs it, atomically renames it\n * over `path`, then fsyncs the directory so the rename itself survives power\n * loss (directory fsync is best-effort — silently skipped on platforms that\n * disallow it). `promote` is idempotent (a no-op when nothing is pending).\n * Node-only: `node:fs/promises` is loaded via dynamic import so browser bundles\n * that tree-shake the barrel never pull in `node:` specifiers.\n */\nexport function fileKeyStore(path: string): DeviceKeyStore {\n\tif (!path) throw new Error('fileKeyStore: `path` is required');\n\n\t// Loaded once, lazily — keeps `node:` specifiers out of browser bundles.\n\tconst fsp = () => import('node:fs/promises');\n\tconst dirOf = (p: string): string => {\n\t\tconst i = Math.max(p.lastIndexOf('/'), p.lastIndexOf('\\\\'));\n\t\treturn i > 0 ? p.slice(0, i) : '.';\n\t};\n\n\tasync function read(): Promise<FileKeyStoreState> {\n\t\tconst fs = await fsp();\n\t\tlet raw: string;\n\t\ttry {\n\t\t\traw = await fs.readFile(path, 'utf8');\n\t\t} catch (err) {\n\t\t\tif ((err as NodeJS.ErrnoException).code === 'ENOENT') return {};\n\t\t\tthrow err;\n\t\t}\n\t\treturn parseState(raw, path);\n\t}\n\n\tasync function write(state: FileKeyStoreState): Promise<void> {\n\t\tconst fs = await fsp();\n\t\tconst tmp = `${path}.tmp`;\n\t\tconst handle = await fs.open(tmp, 'w', 0o600);\n\t\ttry {\n\t\t\tawait handle.writeFile(JSON.stringify(state), 'utf8');\n\t\t\t// Durable BEFORE the rename makes it visible.\n\t\t\tawait handle.sync();\n\t\t} finally {\n\t\t\tawait handle.close();\n\t\t}\n\t\tawait fs.rename(tmp, path);\n\t\t// fsync the directory so the rename itself survives power loss. Some\n\t\t// platforms disallow directory fsync — degrade silently (rename is\n\t\t// already atomic; this only narrows the power-loss window further).\n\t\ttry {\n\t\t\tconst dir = await fs.open(dirOf(path), 'r');\n\t\t\ttry {\n\t\t\t\tawait dir.sync();\n\t\t\t} finally {\n\t\t\t\tawait dir.close();\n\t\t\t}\n\t\t} catch {\n\t\t\t/* best-effort */\n\t\t}\n\t}\n\n\treturn {\n\t\tload: read,\n\t\tasync savePending(key: string): Promise<void> {\n\t\t\tif (!key) throw new Error('fileKeyStore.savePending: empty key');\n\t\t\tconst state = await read();\n\t\t\tawait write({ ...state, pending: key });\n\t\t},\n\t\tasync promote(): Promise<void> {\n\t\t\tconst state = await read();\n\t\t\tif (state.pending === undefined) return; // idempotent\n\t\t\tawait write({ current: state.pending });\n\t\t},\n\t};\n}\n","// triggers module — the modality-agnostic Trigger contract (PRD #1 §5.2, ERA-4396).\n// FROZEN for PRD #2–#4 adapters. Two layers:\n// - Runtime contract: the `TriggerEvent` envelope + `Utterance` + per-kind\n// payloads — what the input port publishes. The agent never cares which\n// modality fired it.\n// - Author-time builders: `triggers.*` → a `TriggerDef` bound into a chain\n// via `chain(...).trigger(...)` / `defineChain({ trigger })`.\n//\n// The envelope enumerates all kinds now (frozen). Only `button` + `voice` have\n// author-time builders + typed payloads for the beta; the ambient watchers\n// (schedule / calendar / weather / webhook / geofence) are PRD #2.\n//\n// Tree-shakeable subpath import: `@era-laboratories/era-sdk/modules/triggers`.\n\nimport * as z from 'zod';\nimport type { ContextSnapshot } from './context.js';\nimport { contextSnapshotSchema } from './context.js';\n\n// ─── Runtime contract ───────────────────────────────────────────────────────────\n\n/**\n * The kind of thing that fired a trigger — the discriminant of both the runtime\n * {@link TriggerEvent} envelope and the author-time {@link TriggerDef}.\n *\n * @remarks\n * All kinds are enumerated and frozen, but only `button` and `voice` are wired\n * end-to-end today: they have author-time builders ({@link triggers}) and typed\n * payloads ({@link ButtonPayload} / {@link VoicePayload}). The ambient kinds are\n * reserved — their events carry an opaque JSON payload until their adapters ship.\n *\n * - `button` — a hardware or UI button press.\n * - `voice` — a spoken utterance.\n * - `schedule` — a time/cron trigger (reserved).\n * - `calendar` — a calendar-event trigger (reserved).\n * - `weather` — a weather-condition trigger (reserved).\n * - `webhook` — an inbound HTTP webhook (reserved).\n * - `geofence` — a region enter/exit trigger (reserved).\n */\nexport type TriggerKind =\n\t| 'button'\n\t| 'voice'\n\t| 'schedule'\n\t| 'calendar'\n\t| 'weather'\n\t| 'webhook'\n\t| 'geofence';\n\n/**\n * Which surface published a trigger event.\n *\n * @remarks\n * - `maker` — the maker/authoring surface (console, simulator, tooling).\n * - `ios` — the Era iOS app.\n * - `server` — a server-side or headless caller.\n */\nexport type TriggerSurface = 'maker' | 'ios' | 'server';\n\n/**\n * Which version of an Experience a trigger targets.\n *\n * @remarks\n * - `published` — the live, published version end users see.\n * - `preview` — the in-progress draft version, for testing before publish.\n *\n * Optional on {@link TriggerEvent}.\n */\nexport type VersionChannel = 'published' | 'preview';\n\n/**\n * Identifies the surface that fired a {@link TriggerEvent}: which kind of\n * surface, the device (if any), and the live session it occupies.\n */\nexport interface TriggerSource {\n\t/** Which kind of surface published the event. */\n\tsurface: TriggerSurface;\n\t/** The physical device that fired the trigger, when one is involved. */\n\tdeviceId?: string;\n\t/**\n\t * The live session/room the firing surface occupies. The trigger port routes\n\t * the event into this session, so an in-session trigger supplies it in\n\t * practice.\n\t */\n\tsessionId?: string;\n}\n\n/**\n * Speech payload. `text` is ALWAYS present to the agent — if an adapter\n * supplies only `audioRef`, ingress fills `text` via the existing one-shot STT\n * (no new STT is built).\n */\nexport interface Utterance {\n\t/**\n\t * Transcribed speech, always present to the agent. If an adapter supplies\n\t * only `audioRef`, ingress fills this via one-shot STT before the agent runs.\n\t */\n\ttext: string;\n\t/** Reference to the captured audio clip, when an adapter supplies one. */\n\taudioRef?: string;\n\t/** STT confidence for the transcription, in the range 0–1. */\n\tconfidence?: number;\n\t/** BCP-47 language tag of the utterance (e.g. `'en-US'`). */\n\tlang?: string;\n}\n\n/**\n * The button gesture that fired a trigger.\n *\n * @remarks\n * - `press` — a single short press.\n * - `press-and-hold` — a sustained press (e.g. hold-to-talk).\n * - `double` — two quick presses.\n *\n * The gesture is part of chain resolution: under one Experience, `press` and\n * `double` can route to two different chains, and a chain with no gesture\n * catches whatever no gesture-specific chain claims.\n */\nexport type ButtonGesture = 'press' | 'press-and-hold' | 'double';\n\n/** Payload of a `button` {@link TriggerEvent}: the gesture, plus any speech captured alongside the press. */\nexport interface ButtonPayload {\n\t/** The gesture that fired the trigger. */\n\tgesture: ButtonGesture;\n\t/** Speech captured with the press (e.g. press-and-hold to talk), when the surface provides it. */\n\tspeech?: Utterance;\n}\n\n/** Payload of a `voice` {@link TriggerEvent}: the spoken utterance that fired the trigger. */\nexport interface VoicePayload {\n\t/** The spoken utterance, transcribed to text for the agent. */\n\tutterance: Utterance;\n}\n\n/**\n * The modality-agnostic trigger envelope a firing surface publishes. One shape\n * for every kind, with a discriminated, kind-specific `payload` — so the agent\n * that receives it never has to care which modality fired it.\n *\n * @remarks\n * Validate an untrusted value into this shape with {@link parseTriggerEvent},\n * which checks `payload` against the schema for its `kind`. `button` / `voice`\n * payloads are strictly validated ({@link ButtonPayload} / {@link VoicePayload});\n * the ambient kinds carry an opaque JSON payload for now.\n */\nexport interface TriggerEvent {\n\t/** The Experience this trigger fires; the firing surface knows its own id. */\n\texperienceId: string;\n\t/** Which version channel of the Experience the event targets — `'published'` or `'preview'`. */\n\tversionChannel?: VersionChannel;\n\t/** Identifier of the trigger definition that produced this event. */\n\ttriggerId: string;\n\t/** Discriminant selecting the `payload` shape. */\n\tkind: TriggerKind;\n\t/** When the trigger fired, as an ISO-8601 timestamp with offset. */\n\toccurredAt: string;\n\t/** Stable key used to dedupe under at-least-once delivery — retries reuse the same key. */\n\tidempotencyKey: string;\n\t/** Which surface, device, and session fired the event. */\n\tsource: TriggerSource;\n\t/** Context captured by the surface at fire time; passed through to the run as its trigger context. */\n\tcontext?: ContextSnapshot;\n\t/** Kind-specific data — {@link ButtonPayload} for `button`, {@link VoicePayload} for `voice`, opaque JSON otherwise. */\n\tpayload: unknown;\n}\n\n// ─── Author-time trigger definitions (the `trigger:` field) ───────────────────────\n\n/**\n * A button {@link TriggerDef} — produced by {@link triggers.button} and bound\n * into a chain via `chain(...).trigger(...)`.\n */\nexport interface ButtonTrigger {\n\t/** Discriminant identifying this as a button trigger. */\n\tkind: 'button';\n\t/**\n\t * Named input slot — a declarative name for the input this chain expects.\n\t * It documents intent and is not matched by routing.\n\t *\n\t * @remarks\n\t * To route a specific device input to a chain explicitly, bind it with\n\t * `devices(era).bindInput(deviceId, { inputSlot, gesture })`; the binding is\n\t * keyed on the id the device reports for the input, not on this `slot`.\n\t * Reusing that id here is a helpful naming convention, nothing more. When a\n\t * device input has no explicit binding, the platform falls back to implicit\n\t * resolution by `kind` + the device's selected Experience + `gesture`.\n\t */\n\tslot: string;\n\t/** Which press fires the trigger; omit to catch any gesture no gesture-specific chain claims. */\n\tgesture?: ButtonGesture;\n}\n\n/**\n * A voice {@link TriggerDef} — produced by {@link triggers.voice} and bound into\n * a chain via `chain(...).trigger(...)`.\n */\nexport interface VoiceTrigger {\n\t/** Discriminant identifying this as a voice trigger. */\n\tkind: 'voice';\n\t/**\n\t * Optional named input slot — declarative; it documents the input this chain\n\t * expects and routing never reads it.\n\t *\n\t * @remarks\n\t * Explicit routing uses `devices(era).bindInput`, which is keyed on the\n\t * physical `(device, input, gesture)` and is kind-agnostic — so a voice chain\n\t * bound to a button input runs when that input fires (e.g. press-to-record).\n\t * A spoken utterance that arrives without a bound input is matched against\n\t * the Experience's configured voice commands (spoken phrase → chain) first;\n\t * when no command matches, the platform falls back to implicit resolution by\n\t * `kind` + the device's selected Experience.\n\t */\n\tslot?: string;\n}\n/**\n * The union both {@link triggers} builders produce: what a chain's\n * `.trigger(...)` accepts (assignable to the chain-side `ChainTrigger`).\n *\n * @remarks\n * `button` and `voice` are the buildable kinds. The ambient kinds\n * (schedule / calendar / weather / webhook / geofence) exist in the runtime\n * {@link TriggerEvent} envelope but have no author-time builder yet.\n */\nexport type TriggerDef = ButtonTrigger | VoiceTrigger;\n\n/**\n * Author-time trigger builders. Call one and pass the result to a chain's\n * `.trigger(...)` (or `defineChain({ trigger })`) to declare what fires it.\n *\n * @remarks\n * Pure — these construct a plain {@link TriggerDef} object and make no network\n * call. Reached as the top-level `triggers` export of the SDK.\n *\n * @example\n * import { chain, triggers } from '@era-laboratories/era-sdk';\n *\n * const doorbell = chain('doorbell-greeter')\n * .trigger(triggers.button({ slot: 'front-door', gesture: 'press' }))\n * .agentTurn('greet', { prompt: 'Greet the visitor and take a message.' })\n * .build();\n */\nexport const triggers = {\n\t/**\n\t * Build a button {@link TriggerDef}.\n\t *\n\t * @param opts.slot - Declarative name for the input this chain expects; documentation of intent, not matched by routing (see {@link ButtonTrigger.slot}).\n\t * @param opts.gesture - Which press fires it; omit to catch any gesture no gesture-specific chain claims.\n\t * @returns A {@link ButtonTrigger}.\n\t *\n\t * @example\n\t * triggers.button({ slot: 'front-door', gesture: 'press' });\n\t * // { kind: 'button', slot: 'front-door', gesture: 'press' }\n\t */\n\tbutton: (opts: { slot: string; gesture?: ButtonGesture }): ButtonTrigger => ({\n\t\tkind: 'button',\n\t\tslot: opts.slot,\n\t\t...(opts.gesture ? { gesture: opts.gesture } : {}),\n\t}),\n\t/**\n\t * Build a voice {@link TriggerDef}.\n\t *\n\t * @param opts.slot - Optional declarative input-slot name; routing never reads it (see {@link VoiceTrigger.slot}).\n\t * @returns A {@link VoiceTrigger}.\n\t *\n\t * @example\n\t * triggers.voice(); // { kind: 'voice' }\n\t * triggers.voice({ slot: 'kitchen-puck' }); // { kind: 'voice', slot: 'kitchen-puck' }\n\t */\n\tvoice: (opts?: { slot?: string }): VoiceTrigger => ({\n\t\tkind: 'voice',\n\t\t...(opts?.slot ? { slot: opts.slot } : {}),\n\t}),\n};\n\n// ─── Validation (zod) ─────────────────────────────────────────────────────────────\n\n/**\n * Zod schema for {@link Utterance} — validate a speech payload at your own\n * boundary. Exported so callers can reuse the same contract the runtime uses.\n */\nexport const utteranceSchema = z\n\t.object({\n\t\ttext: z.string().meta({\n\t\t\tdescription:\n\t\t\t\t'Transcribed speech text — always present to the agent (one-shot STT fills it from audioRef).',\n\t\t}),\n\t\taudioRef: z.string().optional().meta({\n\t\t\tdescription:\n\t\t\t\t'Reference to the captured audio clip, when an adapter supplies one.',\n\t\t}),\n\t\tconfidence: z.number().optional().meta({\n\t\t\tdescription: 'STT confidence score for the transcription (0–1).',\n\t\t}),\n\t\tlang: z\n\t\t\t.string()\n\t\t\t.optional()\n\t\t\t.meta({ description: 'BCP-47 language tag of the utterance.' }),\n\t})\n\t.meta({\n\t\tid: 'Utterance',\n\t\tdescription:\n\t\t\t'Speech payload; `text` is always present to the agent (one-shot STT fills it from `audioRef`).',\n\t});\n\nconst buttonPayloadSchema = z.object({\n\tgesture: z.enum(['press', 'press-and-hold', 'double']),\n\tspeech: utteranceSchema.optional(),\n});\nconst voicePayloadSchema = z.object({ utterance: utteranceSchema });\n\nconst triggerEventBaseSchema = z.object({\n\texperienceId: z.string().min(1),\n\tversionChannel: z.enum(['published', 'preview']).optional(),\n\ttriggerId: z.string().min(1),\n\toccurredAt: z.iso.datetime({ offset: true }),\n\tidempotencyKey: z.string().min(1),\n\tsource: z.object({\n\t\tsurface: z.enum(['maker', 'ios', 'server']),\n\t\tdeviceId: z.string().optional(),\n\t\tsessionId: z.string().optional(),\n\t}),\n\tcontext: contextSnapshotSchema.optional(),\n});\n\n/**\n * Zod schema for the runtime {@link TriggerEvent} envelope. Validates the\n * envelope and discriminates `payload` by `kind`: `button` / `voice` payloads\n * are strictly typed, the ambient kinds accept any JSON `payload`. Prefer\n * {@link parseTriggerEvent} for the typed result; use this schema directly when\n * you need to compose or extend it.\n */\nexport const triggerEventSchema = z\n\t.discriminatedUnion('kind', [\n\t\tz.object({\n\t\t\t...triggerEventBaseSchema.shape,\n\t\t\tkind: z.literal('button').meta({\n\t\t\t\tdescription: 'Trigger kind discriminant — a hardware/UI button press.',\n\t\t\t}),\n\t\t\tpayload: buttonPayloadSchema,\n\t\t}),\n\t\tz.object({\n\t\t\t...triggerEventBaseSchema.shape,\n\t\t\tkind: z.literal('voice').meta({\n\t\t\t\tdescription: 'Trigger kind discriminant — a voice utterance.',\n\t\t\t}),\n\t\t\tpayload: voicePayloadSchema,\n\t\t}),\n\t\tz.object({\n\t\t\t...triggerEventBaseSchema.shape,\n\t\t\tkind: z.literal('schedule').meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Trigger kind discriminant — a scheduled (time/cron) trigger.',\n\t\t\t}),\n\t\t\tpayload: z.json().meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Ambient schedule payload — opaque JSON until its adapter lands (PRD #2).',\n\t\t\t}),\n\t\t}),\n\t\tz.object({\n\t\t\t...triggerEventBaseSchema.shape,\n\t\t\tkind: z.literal('calendar').meta({\n\t\t\t\tdescription: 'Trigger kind discriminant — a calendar-event trigger.',\n\t\t\t}),\n\t\t\tpayload: z.json().meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Ambient calendar payload — opaque JSON until its adapter lands (PRD #2).',\n\t\t\t}),\n\t\t}),\n\t\tz.object({\n\t\t\t...triggerEventBaseSchema.shape,\n\t\t\tkind: z.literal('weather').meta({\n\t\t\t\tdescription: 'Trigger kind discriminant — a weather-condition trigger.',\n\t\t\t}),\n\t\t\tpayload: z.json().meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Ambient weather payload — opaque JSON until its adapter lands (PRD #2).',\n\t\t\t}),\n\t\t}),\n\t\tz.object({\n\t\t\t...triggerEventBaseSchema.shape,\n\t\t\tkind: z.literal('webhook').meta({\n\t\t\t\tdescription: 'Trigger kind discriminant — an inbound webhook trigger.',\n\t\t\t}),\n\t\t\tpayload: z.json().meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Ambient webhook payload — opaque JSON until its adapter lands (PRD #2).',\n\t\t\t}),\n\t\t}),\n\t\tz.object({\n\t\t\t...triggerEventBaseSchema.shape,\n\t\t\tkind: z.literal('geofence').meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Trigger kind discriminant — a geofence enter/exit trigger.',\n\t\t\t}),\n\t\t\tpayload: z.json().meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Ambient geofence payload — opaque JSON until its adapter lands (PRD #2).',\n\t\t\t}),\n\t\t}),\n\t])\n\t.meta({\n\t\tid: 'TriggerEvent',\n\t\ttitle: 'Trigger event',\n\t\tdescription:\n\t\t\t'Modality-agnostic trigger envelope (PRD §5.2), frozen for PRD #2–#4 adapters.',\n\t});\n\n/**\n * Parse and validate an unknown value into a runtime {@link TriggerEvent},\n * checking `payload` against the schema for its `kind`. Use it at any boundary\n * where a trigger arrives as untrusted JSON (e.g. an inbound webhook body).\n *\n * @param input - The value to validate — typically parsed JSON.\n * @returns The validated {@link TriggerEvent}. `payload` has been validated for\n * its `kind`, but statically it stays `unknown` — narrow it as in the example.\n * @throws A `ZodError` if `input` does not match the envelope for its `kind`.\n *\n * @example\n * import { parseTriggerEvent } from '@era-laboratories/era-sdk';\n *\n * const event = parseTriggerEvent(await request.json());\n * if (event.kind === 'voice') {\n * const { utterance } = event.payload as VoicePayload;\n * console.log(utterance.text);\n * }\n */\nexport function parseTriggerEvent(input: unknown): TriggerEvent {\n\treturn triggerEventSchema.parse(input) as TriggerEvent;\n}\n\n/**\n * Zod schema for the author-time {@link TriggerDef} (button + voice), discriminated\n * by `kind`. Exported for callers that validate trigger definitions at their own\n * boundary; the ambient kinds have no author-time schema yet.\n */\nexport const triggerDefSchema = z\n\t.discriminatedUnion('kind', [\n\t\tz.object({\n\t\t\tkind: z.literal('button').meta({\n\t\t\t\tdescription: 'Author-time trigger kind — button.',\n\t\t\t}),\n\t\t\tslot: z.string().min(1).meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Named input slot — the declared name of the input this chain expects; documentation of intent, never matched by routing. Explicit routing uses devices.bindInput (ERA-6476), whose inputSlot must equal the id the device reports in data.input — the platform never compares it to this slot. Unbound devices fall back to implicit resolution by kind + selected experience + gesture.',\n\t\t\t}),\n\t\t\tgesture: z.enum(['press', 'press-and-hold', 'double']).optional().meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Button gesture that fires the trigger; defaults to any press.',\n\t\t\t}),\n\t\t}),\n\t\tz.object({\n\t\t\tkind: z.literal('voice').meta({\n\t\t\t\tdescription: 'Author-time trigger kind — voice.',\n\t\t\t}),\n\t\t\tslot: z.string().optional().meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Optional named input slot for the voice surface — declarative; routing never reads it. Explicit routing uses devices.bindInput, keyed on the physical (device, inputSlot, gesture) and kind-agnostic: a device voice clip that names its input (ERA-6511) consults the binding table first. On a binding miss, voice routing matches the utterance against the selected experience voice commands (spoken phrase → chain), then falls back to implicit resolution by kind + selected experience.',\n\t\t\t}),\n\t\t}),\n\t])\n\t.meta({\n\t\tid: 'TriggerDef',\n\t\tdescription:\n\t\t\t'Author-time trigger binding for chain(...).trigger(...). Button + voice for beta; ambient kinds are PRD #2.',\n\t});\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["genPostChains","genGetChains","genGetChainsById","genPutChainsById","genDeleteChainsById","genPostChainsByIdPublishingVersions","genGetChainsByIdPublishingVersions","genPutChainVersionsByVId","genPostChainsByIdPublishingVersionsByVIdSubmitForReview","genPostChainsByIdPublishingVersionsByVIdApproveReview","genPostChainsByIdPublishingVersionsByVIdRejectReview","genPostChainsByIdRollback","genStartRun","genGetRunEvents","genDeliverRunEvent","genGetRun","genCancelRun","genGetExperiencesById","predicateSchema","genPostDeviceKeysRenew","isRef","simulateSpec"],"sources":["../src/modules/outputs.ts","../src/modules/chains.ts","../src/modules/chainFlows.ts","../src/modules/commands.ts","../src/modules/context.ts","../src/modules/conversation.ts","../src/modules/deviceStream.ts","../src/modules/guardrails.ts","../src/modules/simulate.ts","../src/modules/experienceAuthoring.ts","../src/modules/keyStore.ts","../src/modules/triggers.ts"],"sourcesContent":["// outputs module — the Output contract (PRD #1 §5.6, ERA-4403 physical / ERA-4404 surface).\n//\n// Outputs INFORM THE USER that the work is done (Spindle results go to the\n// agent; Output results go to the user). Transport-shaped factories — the\n// era-maker actuator catalog + the iOS action set are both open-ended, so we\n// model by transport, not one factory per modality:\n// - outputs.physical() → a device-stream actuator command (subscribe path)\n// - outputs.surface() → an iOS ActionRegistry action (push path)\n// - outputs.say() → a device-stream text response (ERA-6126, text-first)\n//\n// These are AUTHOR-TIME factories: they validate and produce an `OutputDef`\n// that the orchestrator's `builtin:emit-output` action (ERA-4402, era-ingress)\n// actually fires — with fan-out, bound either declaratively via `on:` or\n// agent-emitted. Surface/voice transports are stateless fire-and-forget;\n// PHYSICAL outputs carry delivery receipts (ERA-6033): the run's events show\n// pending → output_delivered / output_failed / output_undeliverable /\n// output_expired, driven by the device's execution ack (see the deviceStream\n// module's `onExecute` / `ack()`), with server-side ledger-sourced retry.\n//\n// `fields` / `params` values may be literals OR `$.<ref>` tokens (e.g.\n// `$.agent.summary`) resolved at runtime by the orchestrator — author-time\n// validation type-checks literals and passes refs through untouched.\n//\n// Tree-shakeable subpath import: `@era-laboratories/era-sdk/modules/outputs`.\n\nimport * as z from 'zod';\nimport type { Ref } from './chains.js';\n\n// ─── Shared ───────────────────────────────────────────────────────────────────\n\n/** When an output fires, relative to the run's step lifecycle. */\nexport type OutputLifecycle = 'start' | 'success' | 'failure';\n\n/** A field/param value: a literal (JSON) or a `$.<ref>` resolved at runtime. */\nexport type OutputValue = Ref<unknown> | unknown;\n\nconst lifecycleSchema = z.enum(['start', 'success', 'failure']);\n\n/** Author-time ref token shape (`$.a.b` → `{ $ref: '$.a.b' }`). */\nconst refSchema = z.object({ $ref: z.string() });\n\n/**\n * A direct `deviceId` target (ERA-6202): a literal device UUID, or a `$.` ref\n * token resolved at runtime. `z.uuid()` (house style, matches the generated\n * grammar) rejects a non-UUID literal such as a slot NAME at author time — the\n * live-reproduced footgun where `deviceId: 'my-lamp'` silently shipped a\n * never-resolving target.\n */\nconst deviceIdTargetSchema = z.uuid().or(refSchema);\n\n/**\n * The device-targeting half of a `physical`/`say` output — EXACTLY\n * ONE of:\n * - `slot` — a named binding, resolved to a deviceId server-side per user.\n * - `deviceId` — a direct target: a literal device UUID, or a `$.` ref token\n * resolved at runtime by the orchestrator. The canonical reply-to-sender\n * idiom is `deviceId: { $ref: '$.input.trigger.source.deviceId' }` (see\n * {@link triggerRefs.senderDeviceId}).\n */\nexport type OutputTarget =\n\t| { slot: string; deviceId?: never }\n\t| { deviceId: string | Ref<unknown>; slot?: never };\n\n/** slot-XOR-deviceId predicate shared by factories + structural schemas. */\nconst hasExactlyOneTarget = (val: { slot?: unknown; deviceId?: unknown }) =>\n\t(val.slot !== undefined) !== (val.deviceId !== undefined);\n\n/** Enforce the slot-XOR-deviceId contract at factory call time (ERA-6191). */\nfunction assertExactlyOneTarget(\n\tfactory: 'physical' | 'say',\n\topts: { slot?: string; deviceId?: string | Ref<unknown> },\n): void {\n\tif (!hasExactlyOneTarget(opts)) {\n\t\tthrow new Error(\n\t\t\t`outputs.${factory}: exactly one of \\`slot\\` or \\`deviceId\\` is required.`,\n\t\t);\n\t}\n\tif (opts.slot !== undefined && opts.slot.length === 0) {\n\t\tthrow new Error(`outputs.${factory}: \\`slot\\` must be a non-empty string.`);\n\t}\n\tif (opts.deviceId !== undefined) {\n\t\t// literal must be a device UUID; a `$.` ref passes through untouched\n\t\tdeviceIdTargetSchema.parse(opts.deviceId);\n\t}\n}\n\n/** Emit only the provided targeting key (no `slot: undefined` on the wire). */\nconst targetKeys = (opts: {\n\tslot?: string;\n\tdeviceId?: string | Ref<unknown>;\n}) => ({\n\t...(opts.slot !== undefined ? { slot: opts.slot } : {}),\n\t...(opts.deviceId !== undefined ? { deviceId: opts.deviceId } : {}),\n});\n\n/**\n * A strict object schema — rejects unknown keys so a typo'd field/param fails at\n * author time. Implemented via `z.object().catchall(z.never())` (the house lint\n * prefers `z.object` over `z.strictObject`).\n */\nconst strictShape = (shape: z.ZodRawShape) =>\n\tz.object(shape).catchall(z.never());\n\n// ─── physical() — device-stream actuator output (ERA-4403) ───────────────────────\n\n/**\n * A physical (actuator) output def — the object {@link physical} returns, lowered\n * at runtime to a device-stream actuator command. Physical outputs carry delivery\n * receipts (the run's events progress `pending` → `output_delivered` /\n * `output_failed` / `output_undeliverable` / `output_expired`).\n */\nexport interface PhysicalOutput {\n\t/** Transport discriminant — always `'physical'`. */\n\ttransport: 'physical';\n\t/**\n\t * Named binding slot — resolved to a deviceId/canvas server-side per\n\t * user. Exactly one of `slot` | `deviceId`.\n\t */\n\tslot?: string;\n\t/**\n\t * Direct device target: a literal device UUID, or a `$.` ref\n\t * resolved at runtime (canonical reply-to-sender:\n\t * `deviceId: { $ref: '$.input.trigger.source.deviceId' }` — see\n\t * {@link triggerRefs.senderDeviceId}). Exactly one of `slot` | `deviceId`.\n\t */\n\tdeviceId?: string | Ref<unknown>;\n\t/** ID of the target actuator in the era-maker catalogue (e.g. `'status_led'`, `'servo'`). */\n\tactuatorId: string;\n\t/** Actuator field values, keyed by field name; each value is a literal or a `$.<ref>` resolved at runtime. */\n\tfields: Record<string, OutputValue>;\n\t/** Lifecycle binding — when the output fires relative to the run's step lifecycle. Omitted = agent-emitted at runtime. */\n\ton?: OutputLifecycle;\n}\n\n/**\n * Vendored era-maker actuator catalogue — BETA-ANCHOR SUBSET. The full\n * catalogue lives in era-maker (`src/catalogue/*`, not checked out here);\n * catalog-driven validation + delivery receipts are PRD #3. Each field accepts\n * its literal type OR a `$.<ref>`.\n */\nconst ACTUATOR_CATALOGUE: Record<string, z.ZodType> = {\n\tstatus_led: strictShape({\n\t\tcolor: z.string().min(1).or(refSchema).optional(),\n\t\tblink: z.int().min(0).max(10).or(refSchema).optional(),\n\t}),\n\tservo: strictShape({\n\t\t// PRD §5.1: angle is a u8 in 0..180.\n\t\tangle: z.int().min(0).max(180).or(refSchema),\n\t}),\n};\n\n/**\n * Declare a physical (actuator) output. Validates `actuatorId` against the\n * vendored era-maker catalogue and `fields` against that actuator's capability\n * schema (literals type-checked; `$.<ref>` values passed through). Throws on an\n * unknown actuator or an invalid field.\n *\n * Targeting: exactly ONE of `slot` (named binding, resolved\n * server-side) or `deviceId` (direct target — literal UUID or `$.` ref, e.g.\n * `triggerRefs.senderDeviceId` to reply to the triggering device).\n *\n * @param opts - The actuator target and command. `slot` XOR `deviceId` (exactly one), `actuatorId` (must exist in the vendored catalogue), `fields` (validated against that actuator's capability schema), and optional `on` lifecycle binding.\n * @returns A {@link PhysicalOutput} def ready to attach to a chain's `outputs`.\n * @throws {Error} When neither or both of `slot`/`deviceId` are given, when `slot` is empty, or when `actuatorId` is unknown.\n * @throws {z.ZodError} When a literal `deviceId` is not a UUID, or when `fields` contains an unknown or out-of-range value for the actuator (e.g. a `servo` `angle` outside 0–180).\n * @remarks Pure and author-time — no network call. Literal field values are type-checked; `$.<ref>` tokens pass through untouched and resolve at runtime.\n * @example\n * ```ts\n * const led = outputs.physical({\n * slot: 'porch-light',\n * actuatorId: 'status_led',\n * fields: { color: 'green', blink: 3 },\n * on: 'success',\n * });\n *\n * const arm = outputs.physical({\n * deviceId: triggerRefs.senderDeviceId,\n * actuatorId: 'servo',\n * fields: { angle: { $ref: '$.plan.angle' } },\n * });\n * ```\n */\nexport function physical(\n\topts: OutputTarget & {\n\t\tactuatorId: string;\n\t\tfields: Record<string, OutputValue>;\n\t\ton?: OutputLifecycle;\n\t},\n): PhysicalOutput {\n\tassertExactlyOneTarget('physical', opts);\n\tconst schema = ACTUATOR_CATALOGUE[opts.actuatorId];\n\tif (!schema) {\n\t\tthrow new Error(\n\t\t\t`Unknown actuatorId \"${opts.actuatorId}\". Known: ${Object.keys(ACTUATOR_CATALOGUE).join(', ')}.`,\n\t\t);\n\t}\n\tschema.parse(opts.fields); // throws ZodError on unknown/out-of-range fields\n\treturn {\n\t\ttransport: 'physical',\n\t\t...targetKeys(opts),\n\t\tactuatorId: opts.actuatorId,\n\t\tfields: opts.fields,\n\t\t...(opts.on ? { on: opts.on } : {}),\n\t};\n}\n\n// ─── surface() — iOS ActionRegistry output (ERA-4404) ─────────────────────────────\n\n// ERA-6036: push delivery is not currently deployed. era-push-service was\n// decommissioned (ERA-5188) and era-ingress `_emit_surface` no-ops when its\n// push key is unset (returns `{sent:false, reason:'api_key_unset'}`). So a\n// `surface` output authors + validates + publishes fine but is delivered\n// nowhere today. We warn ONCE per process at author time — never throw: the\n// wire shape is stable and the backend accepts it, so nothing needs\n// re-authoring when push delivery ships (ERA-6034). Remove this warning as\n// part of ERA-6034.\nlet surfaceUndeliverableWarned = false;\n\n/**\n * One-shot (per process) advisory that `surface` outputs are authored but not\n * currently deliverable. Shared by the `surface()` factory and the\n * spec-validation paths (`defineChain` / `defineExperience`) so a single\n * warning fires regardless of how the surface output entered a spec. Never\n * throws — validation semantics are unchanged.\n *\n * @internal Cross-module implementation detail; not part of the partner-facing API.\n */\nexport function warnSurfaceUndeliverable(): void {\n\tif (surfaceUndeliverableWarned) return;\n\tsurfaceUndeliverableWarned = true;\n\tconsole.warn(\n\t\t'[era-sdk] Heads up: `surface` outputs are not yet deliverable — push ' +\n\t\t\t'delivery is planned but not implemented yet. Your spec is valid and ' +\n\t\t\t'will start delivering automatically once push support ships, so no ' +\n\t\t\t'changes are needed. `physical` and voice outputs work today.',\n\t);\n}\n\n/**\n * Per-action delivery capability (the typed push-action layer):\n * - `background` — executes silently on a content-available push.\n * - `on-tap` — iOS forbids launching UI without interaction, so the\n * notification IS the inform; the action runs when the user taps.\n * - `interactive` — notification action buttons (follow-on, shared with\n * `confirmBefore`; not yet emitted by the base layer).\n */\nexport type CapabilityTier = 'background' | 'on-tap' | 'interactive';\n\n/**\n * A surface (iOS ActionRegistry / push) output binding.\n *\n * ⚠️ Deliverability: `surface` outputs are **not yet deliverable** — push\n * delivery is planned but not implemented yet. Authoring one is still valid and\n * forward-compatible: your spec will start delivering automatically once push\n * support ships, with no changes needed. `physical` and voice outputs work\n * today.\n */\nexport interface SurfaceOutput {\n\t/** Transport discriminant — always `'surface'`. */\n\ttransport: 'surface';\n\t/** iOS ActionRegistry action name to invoke (e.g. `'push'`, `'setReminder'`), or a `custom.*` app-defined action. */\n\taction: string;\n\t/** Action params, keyed by name; each value is a literal or a `$.<ref>` resolved at runtime. */\n\tparams: Record<string, OutputValue>;\n\t/** Derived from the action's registry entry; informs silent vs tap delivery. */\n\ttier: CapabilityTier;\n\t/** Lifecycle binding — when the output fires relative to the run's step lifecycle. Omitted = agent-emitted at runtime. */\n\ton?: OutputLifecycle;\n}\n\ninterface ActionDef {\n\ttier: CapabilityTier;\n\tparams: z.ZodType;\n}\n\n/**\n * Vendored iOS `ActionRegistry` contract (Era-Hub, 13 handlers). `tier` mirrors\n * each handler's `canRunInBackground`. Param schemas accept literals OR refs.\n */\nconst ACTION_REGISTRY: Record<string, ActionDef> = {\n\t// 'push' is a plain user-facing notification (the banner IS the output) — not\n\t// an ActionRegistry handler; delivered as title/body with no data.action.\n\tpush: {\n\t\ttier: 'on-tap',\n\t\tparams: strictShape({\n\t\t\ttext: z.string().min(1).or(refSchema),\n\t\t\ttitle: z.string().or(refSchema).optional(),\n\t\t}),\n\t},\n\topenURL: {\n\t\ttier: 'on-tap',\n\t\tparams: strictShape({ url: z.url().or(refSchema) }),\n\t},\n\t'music.play': { tier: 'background', params: strictShape({}) },\n\t'music.pause': { tier: 'background', params: strictShape({}) },\n\t'music.skip': { tier: 'background', params: strictShape({}) },\n\t'music.previous': { tier: 'background', params: strictShape({}) },\n\thapticBuzz: {\n\t\ttier: 'background',\n\t\tparams: strictShape({\n\t\t\tstyle: z\n\t\t\t\t.enum(['light', 'medium', 'rigid', 'soft', 'heavy'])\n\t\t\t\t.or(refSchema)\n\t\t\t\t.optional(),\n\t\t\tcount: z.int().min(1).max(5).or(refSchema).optional(),\n\t\t}),\n\t},\n\tsetTimer: {\n\t\ttier: 'on-tap',\n\t\tparams: strictShape({\n\t\t\tduration: z.int().min(1).max(86400).or(refSchema),\n\t\t\tlabel: z.string().or(refSchema).optional(),\n\t\t}),\n\t},\n\tsetReminder: {\n\t\ttier: 'background',\n\t\tparams: strictShape({\n\t\t\ttitle: z.string().min(1).or(refSchema),\n\t\t\tdueDate: z.string().or(refSchema).optional(),\n\t\t\tdueTime: z.string().or(refSchema).optional(),\n\t\t\tlist: z.string().or(refSchema).optional(),\n\t\t\tnotes: z.string().or(refSchema).optional(),\n\t\t}),\n\t},\n\tcreateEvent: {\n\t\ttier: 'background',\n\t\tparams: strictShape({\n\t\t\ttitle: z.string().min(1).or(refSchema),\n\t\t\tstartDate: z.string().or(refSchema).optional(),\n\t\t\tendDate: z.string().or(refSchema).optional(),\n\t\t\tlocation: z.string().or(refSchema).optional(),\n\t\t\tnotes: z.string().or(refSchema).optional(),\n\t\t\tallDay: z.boolean().or(refSchema).optional(),\n\t\t}),\n\t},\n\topenDevice: {\n\t\ttier: 'on-tap',\n\t\tparams: strictShape({ deviceId: z.string().min(1).or(refSchema) }),\n\t},\n\topenSettings: {\n\t\ttier: 'on-tap',\n\t\tparams: strictShape({\n\t\t\tsection: z\n\t\t\t\t.enum(['notifications', 'location', 'services', 'connected-accounts'])\n\t\t\t\t.or(refSchema)\n\t\t\t\t.optional(),\n\t\t}),\n\t},\n\topenProfile: { tier: 'on-tap', params: strictShape({}) },\n};\n\n/** `custom.*` actions are app-defined: tap-gated, arbitrary JSON params. */\nconst customActionDef: ActionDef = {\n\ttier: 'on-tap',\n\tparams: z.record(z.string(), z.json()),\n};\n\nfunction resolveAction(action: string): ActionDef {\n\tif (action.startsWith('custom.')) return customActionDef;\n\tconst def = ACTION_REGISTRY[action];\n\tif (!def) {\n\t\tthrow new Error(\n\t\t\t`Unknown surface action \"${action}\". Known: ${Object.keys(ACTION_REGISTRY).join(', ')}, custom.*.`,\n\t\t);\n\t}\n\treturn def;\n}\n\n/**\n * Declare a surface (push / iOS ActionRegistry) output. Validates `action`\n * against the vendored registry and `params` against that action's schema\n * (literals type-checked; refs passed through), and tags the delivery `tier`.\n * Throws on an unknown action or invalid params.\n *\n * ⚠️ Deliverability: emits a one-shot console warning — `surface` outputs are\n * not yet deliverable (push delivery is planned but not implemented yet). The\n * authored spec is valid and forward-compatible; it will start delivering\n * automatically once push support ships, with no changes needed. `physical` and\n * voice outputs work today.\n *\n * @param opts - `action` (a known registry action or `custom.*`), optional `params` (defaults to `{}`; validated against the action's schema), and optional `on` lifecycle binding.\n * @returns A {@link SurfaceOutput} def with its delivery `tier` tagged from the action's registry entry.\n * @throws {Error} When `action` is not a known registry action and does not start with `custom.`.\n * @throws {z.ZodError} When `params` contains an unknown or invalid value for the action (e.g. a non-URL `url` for `openURL`).\n * @remarks Pure and author-time — no network call, but emits a one-shot (per-process) console warning that surface outputs are not yet deliverable. Never throws for deliverability.\n * @example\n * ```ts\n * const notify = outputs.surface({\n * action: 'push',\n * params: { text: { $ref: '$.draft.output' }, title: 'Draft ready' },\n * on: 'success',\n * });\n * ```\n */\nexport function surface(opts: {\n\taction: string;\n\tparams?: Record<string, OutputValue>;\n\ton?: OutputLifecycle;\n}): SurfaceOutput {\n\twarnSurfaceUndeliverable();\n\tconst def = resolveAction(opts.action);\n\tconst params = opts.params ?? {};\n\tdef.params.parse(params); // throws ZodError on unknown/invalid params\n\treturn {\n\t\ttransport: 'surface',\n\t\taction: opts.action,\n\t\tparams,\n\t\ttier: def.tier,\n\t\t...(opts.on ? { on: opts.on } : {}),\n\t};\n}\n\n// ─── say() — device-stream text response output (ERA-6126) ───────────────────────\n\n/**\n * A say (device-targeted text response) output def — the object {@link say}\n * returns, lowered at runtime to a device-stream `say` command frame. Reaches a\n * device with no LiveKit room; text-first, with optional server-side TTS.\n */\nexport interface SayOutput {\n\t/** Transport discriminant — always `'say'`. */\n\ttransport: 'say';\n\t/**\n\t * Named binding slot — resolved to a deviceId server-side per user.\n\t * Exactly one of `slot` | `deviceId`.\n\t */\n\tslot?: string;\n\t/**\n\t * Direct device target: a literal device UUID, or a `$.` ref\n\t * resolved at runtime (canonical reply-to-sender:\n\t * `deviceId: { $ref: '$.input.trigger.source.deviceId' }` — see\n\t * {@link triggerRefs.senderDeviceId}). Exactly one of `slot` | `deviceId`.\n\t */\n\tdeviceId?: string | Ref<unknown>;\n\t/** Text the device renders or speaks; a literal or a `$.<ref>` (e.g. `$.reply.text`). */\n\ttext: string | Ref<unknown>;\n\t/**\n\t * Opt into server-side TTS: the platform synthesizes the\n\t * resolved text and the device receives a short-TTL signed `audioRef`\n\t * alongside the text. Absent/false = text-only, zero synthesis cost.\n\t */\n\taudio?: boolean;\n\t/** Lifecycle binding — when the output fires relative to the run's step lifecycle. Omitted = agent-emitted at runtime. */\n\ton?: OutputLifecycle;\n}\n\n/**\n * Declare a say (device-targeted text response) output — lowered to a\n * device-stream `say` command frame. Unlike the session-scoped voice\n * transport, this reaches a device with no LiveKit room. Text-first: by\n * default the device renders/displays the text or performs on-device TTS;\n * set `audio: true` to opt into server-side TTS.\n *\n * Targeting: exactly ONE of `slot` (named binding, resolved\n * server-side) or `deviceId` (direct target — literal UUID or `$.` ref, e.g.\n * `triggerRefs.senderDeviceId` to reply to the triggering device).\n *\n * @param opts - `slot` XOR `deviceId` (exactly one), `text` (literal or `$.<ref>`; a literal must be non-empty), optional `audio` (opt into server-side TTS), and optional `on` lifecycle binding.\n * @returns A {@link SayOutput} def ready to attach to a chain's `outputs`.\n * @throws {Error} When neither or both of `slot`/`deviceId` are given, or when `slot` is empty.\n * @throws {z.ZodError} When a literal `deviceId` is not a UUID, or when a literal `text` is an empty string.\n * @remarks Pure and author-time — no network call. `audio: true` opts into server-side TTS (the device receives a short-TTL signed `audioRef` alongside the text); absent/false = text-only at zero synthesis cost.\n * @example\n * ```ts\n * const reply = outputs.say({\n * deviceId: triggerRefs.senderDeviceId,\n * text: { $ref: '$.reply.text' },\n * audio: true,\n * on: 'success',\n * });\n * ```\n */\nexport function say(\n\topts: OutputTarget & {\n\t\ttext: string | Ref<unknown>;\n\t\taudio?: boolean;\n\t\ton?: OutputLifecycle;\n\t},\n): SayOutput {\n\tassertExactlyOneTarget('say', opts);\n\tz.string().min(1).or(refSchema).parse(opts.text); // literal must be non-empty\n\treturn {\n\t\ttransport: 'say',\n\t\t...targetKeys(opts),\n\t\ttext: opts.text,\n\t\t...(opts.audio !== undefined ? { audio: opts.audio } : {}),\n\t\t...(opts.on ? { on: opts.on } : {}),\n\t};\n}\n\n// ─── OutputDef + factory namespace ────────────────────────────────────────────────\n\n/**\n * Any output binding a chain's `outputs` array can hold — the discriminated\n * union of the three transports ({@link PhysicalOutput} | {@link SurfaceOutput}\n * | {@link SayOutput}), discriminated on `transport`. Build values with\n * {@link physical} / {@link surface} / {@link say} rather than by hand.\n */\nexport type OutputDef = PhysicalOutput | SurfaceOutput | SayOutput;\n\n/** The three transport-shaped output factories. */\nexport const outputs = { physical, surface, say };\n\n/** Typed `$.` ref helpers for trigger-derived output targeting. */\nexport const triggerRefs = {\n\t/** The device that fired this run's trigger — the canonical \"reply to sender\" target. Resolves server-side; on runs whose trigger carries no device (e.g. plain HTTP), the output fails closed and siblings still deliver. */\n\tsenderDeviceId: { $ref: '$.input.trigger.source.deviceId' } as Ref<string>,\n} as const;\n\n// ─── Structural schemas (for the chain spec `outputs` field + docs) ───────────────\n\nconst valueRecordSchema = z.record(z.string(), z.json().or(refSchema));\n\n/**\n * Zod schema for a {@link PhysicalOutput} binding — the structural validator\n * behind the chain spec's `outputs` field and the source of the generated docs\n * type table. Enforces the `slot` XOR `deviceId` targeting rule.\n */\nexport const physicalOutputSchema = z\n\t.object({\n\t\ttransport: z.literal('physical').meta({\n\t\t\tdescription: 'Transport discriminant — always \"physical\".',\n\t\t}),\n\t\tslot: z.string().min(1).optional().meta({\n\t\t\tdescription:\n\t\t\t\t'Named binding slot resolved to a deviceId/canvas server-side (§5.7). Exactly one of `slot`/`deviceId` (ERA-6191).',\n\t\t}),\n\t\tdeviceId: deviceIdTargetSchema.optional().meta({\n\t\t\tdescription:\n\t\t\t\t'Direct device target (ERA-6191): a literal device UUID or a `$.` ref (e.g. `$.input.trigger.source.deviceId`) resolved at runtime. Exactly one of `slot`/`deviceId`.',\n\t\t}),\n\t\tactuatorId: z.string().min(1).meta({\n\t\t\tdescription: 'ID of the target actuator in the era-maker catalogue.',\n\t\t}),\n\t\tfields: valueRecordSchema,\n\t\ton: lifecycleSchema.optional(),\n\t})\n\t.refine(hasExactlyOneTarget, {\n\t\terror: 'exactly one of `slot` or `deviceId` is required',\n\t})\n\t.meta({\n\t\tid: 'PhysicalOutput',\n\t\ttitle: 'PhysicalOutput',\n\t\tdescription:\n\t\t\t'A physical output binding — lowered to a device-stream actuator command (slot → deviceId resolved server-side, ERA-4500).',\n\t});\n\n/**\n * Zod schema for a {@link SurfaceOutput} binding — the structural validator\n * behind the chain spec's `outputs` field and the source of the generated docs\n * type table.\n */\nexport const surfaceOutputSchema = z\n\t.object({\n\t\ttransport: z.literal('surface').meta({\n\t\t\tdescription: 'Transport discriminant — always \"surface\".',\n\t\t}),\n\t\taction: z.string().min(1).meta({\n\t\t\tdescription: 'iOS ActionRegistry action name (or custom.*) to invoke.',\n\t\t}),\n\t\tparams: valueRecordSchema,\n\t\ttier: z.enum(['background', 'on-tap', 'interactive']).meta({\n\t\t\tdescription:\n\t\t\t\t'Delivery capability tier derived from the action registry entry (background/on-tap/interactive).',\n\t\t}),\n\t\ton: lifecycleSchema.optional(),\n\t})\n\t.meta({\n\t\tid: 'SurfaceOutput',\n\t\ttitle: 'SurfaceOutput',\n\t\tdescription:\n\t\t\t'A surface output binding — a typed server-side push action delivered to the iOS ActionRegistry, capability-tiered (background/on-tap/interactive). Not yet deliverable: push delivery is planned but not implemented yet; the spec is valid and forward-compatible and will start delivering automatically once push support ships.',\n\t});\n\n/**\n * Zod schema for a {@link SayOutput} binding — the structural validator behind\n * the chain spec's `outputs` field and the source of the generated docs type\n * table. Enforces the `slot` XOR `deviceId` targeting rule.\n */\nexport const sayOutputSchema = z\n\t.object({\n\t\ttransport: z.literal('say').meta({\n\t\t\tdescription: 'Transport discriminant — always \"say\".',\n\t\t}),\n\t\tslot: z.string().min(1).optional().meta({\n\t\t\tdescription:\n\t\t\t\t'Named binding slot resolved to a deviceId server-side per user (ERA-4500). Exactly one of `slot`/`deviceId` (ERA-6191).',\n\t\t}),\n\t\tdeviceId: deviceIdTargetSchema.optional().meta({\n\t\t\tdescription:\n\t\t\t\t'Direct device target (ERA-6191): a literal device UUID or a `$.` ref (e.g. `$.input.trigger.source.deviceId`) resolved at runtime. Exactly one of `slot`/`deviceId`.',\n\t\t}),\n\t\ttext: z.string().min(1).or(refSchema).meta({\n\t\t\tdescription:\n\t\t\t\t'Text the device renders or speaks — a literal or a $.<ref> token.',\n\t\t}),\n\t\taudio: z.boolean().optional().meta({\n\t\t\tdescription:\n\t\t\t\t'Opt into server-side TTS (ERA-6189) — the device receives a short-TTL signed audioRef at delivery. Absent/false = text-only.',\n\t\t}),\n\t\ton: lifecycleSchema.optional(),\n\t})\n\t.refine(hasExactlyOneTarget, {\n\t\terror: 'exactly one of `slot` or `deviceId` is required',\n\t})\n\t.meta({\n\t\tid: 'SayOutput',\n\t\ttitle: 'SayOutput',\n\t\tdescription:\n\t\t\t'A device-targeted text response — lowered to a device-stream say command frame (slot → deviceId resolved server-side, ERA-6126/ERA-6129).',\n\t});\n\n/** An output binding (physical, surface, or say). Built by `outputs.physical/surface/say`. */\nexport const outputDefSchema = z\n\t.discriminatedUnion('transport', [\n\t\tphysicalOutputSchema,\n\t\tsurfaceOutputSchema,\n\t\tsayOutputSchema,\n\t])\n\t.meta({\n\t\tid: 'OutputDef',\n\t\ttitle: 'Output',\n\t\tdescription:\n\t\t\t'An Output that informs the user of completion (PRD §5.6). physical() → device-stream actuator; surface() → iOS ActionRegistry push. Fan-out; fires on a lifecycle binding or agent-emitted.',\n\t});\n","// chains module — declarative orchestration authoring (L5 / ERA-4270) + the\n// server-side run surface.\n//\n// A `ChainSpec` is a plain, JSON-serializable orchestration of steps\n// (agentTurn / tool / branch / fanOut / waitForEvent / handoff / map). The\n// canonical form is an object (object-core, locked decision #2) that round-trips\n// byte-for-byte to/from the backend `chain_versions.content` JSONB column and\n// diffs cleanly. `defineChain()` validates + normalizes it; the fluent `chain()`\n// builder accumulates the same fields and routes through `defineChain()` on\n// `.build()`, so the two forms are deep-equal BY CONSTRUCTION.\n//\n// Authoring (`defineChain` / `chain` / `step` / `$`) is PURE — it never touches\n// the network. Only the `chains(client)` facade does: authoring CRUD/versioning\n// over hub-api (mirroring the experiences publishing lifecycle), and the runtime\n// `run`/`resume`/`send`/`getRun`/`cancel` over the ingress orchestrator (the SSE\n// source). Chains execute entirely server-side — `$.` references are author-time\n// tokens that serialize to `{\"$ref\": \"...\"}` strings the orchestrator resolves.\n//\n// Tree-shakeable subpath import: `@era-laboratories/era-sdk/modules/chains`.\n\nimport * as z from 'zod';\nimport type { CamelizeKeys } from '../core/casing.js';\nimport type { EraClient, PaginateOptions } from '../core/client.js';\nimport { EraChainError } from '../core/errors.js';\nimport type { PageEnvelope, RequestOptions } from '../core/types.js';\nimport {\n\tdeleteChainsById as genDeleteChainsById,\n\tgetChains as genGetChains,\n\tgetChainsById as genGetChainsById,\n\tgetChainsByIdPublishingVersions as genGetChainsByIdPublishingVersions,\n\tpostChains as genPostChains,\n\tpostChainsByIdPublishingVersions as genPostChainsByIdPublishingVersions,\n\tpostChainsByIdPublishingVersionsByVIdApproveReview as genPostChainsByIdPublishingVersionsByVIdApproveReview,\n\tpostChainsByIdPublishingVersionsByVIdRejectReview as genPostChainsByIdPublishingVersionsByVIdRejectReview,\n\tpostChainsByIdPublishingVersionsByVIdSubmitForReview as genPostChainsByIdPublishingVersionsByVIdSubmitForReview,\n\tpostChainsByIdRollback as genPostChainsByIdRollback,\n\tputChainsById as genPutChainsById,\n\tputChainVersionsByVId as genPutChainVersionsByVId,\n} from '../generated/hub-api/sdk.gen.js';\nimport type {\n\tPostChainsByIdPublishingVersionsByVIdApproveReviewResponses as GenApproveReviewResponses,\n\tGetChainsByIdPublishingVersionsByVIdResponses as GenGetChainsByIdPublishingVersionsByVIdResponses,\n\tGetChainsData as GenGetChainsData,\n\tPutChainVersionsByVIdResponses as GenPutChainVersionsByVIdResponses,\n\tPostChainsByIdPublishingVersionsByVIdRejectReviewResponses as GenRejectReviewResponses,\n\tPostChainsByIdPublishingVersionsByVIdSubmitForReviewResponses as GenSubmitForReviewResponses,\n\tAgentRef as HubAgentRef,\n\tAgentTurnStep as HubAgentTurnStep,\n\tBranchStep as HubBranchStep,\n\tChainSpec as HubChainSpec,\n\tChainStep as HubChainStep,\n\tChainTrigger as HubChainTrigger,\n\tChainVar as HubChainVar,\n\tFanOutStep as HubFanOutStep,\n\tHandoffStep as HubHandoffStep,\n\tMapStep as HubMapStep,\n\tPredicate as HubPredicate,\n\tRef as HubRef,\n\tToolStep as HubToolStep,\n\tWaitForEventStep as HubWaitForEventStep,\n} from '../generated/hub-api/types.gen.js';\nimport { zChainSpec } from '../generated/hub-api/zod.gen.js';\nimport {\n\tcancelRun as genCancelRun,\n\tdeliverRunEvent as genDeliverRunEvent,\n\tgetRun as genGetRun,\n\tgetRunEvents as genGetRunEvents,\n\tstartRun as genStartRun,\n} from '../generated/ingress/sdk.gen.js';\nimport type {\n\tChainAwaitingEventEvent as GenChainAwaitingEventEvent,\n\tChainEventDeliveredEvent as GenChainEventDeliveredEvent,\n\tChainHandedOffEvent as GenChainHandedOffEvent,\n\tChainOutputDeliveredEvent as GenChainOutputDeliveredEvent,\n\tChainOutputEmittedEvent as GenChainOutputEmittedEvent,\n\tChainOutputExpiredEvent as GenChainOutputExpiredEvent,\n\tChainOutputFailedEvent as GenChainOutputFailedEvent,\n\tChainOutputUndeliverableEvent as GenChainOutputUndeliverableEvent,\n\tChainRunDoneEvent as GenChainRunDoneEvent,\n\tChainRunErrorEvent as GenChainRunErrorEvent,\n\tChainRunStartedEvent as GenChainRunStartedEvent,\n\tChainRunState as GenChainRunState,\n\tChainSafetyBlockedEvent as GenChainSafetyBlockedEvent,\n\tChainStepCompletedEvent as GenChainStepCompletedEvent,\n\tChainStepFailedEvent as GenChainStepFailedEvent,\n\tChainStepStartedEvent as GenChainStepStartedEvent,\n\tChainStreamEvent as GenChainStreamEvent,\n\tDeliverEventBody as GenDeliverEventBody,\n\tOutputReceipt as GenOutputReceipt,\n} from '../generated/ingress/types.gen.js';\nimport { zChainStreamEvent } from '../generated/ingress/zod.gen.js';\nimport type { ExperienceRunEvent } from './experienceAuthoring.js';\nimport type { RejectReviewInput } from './experienceVersions.js';\nimport { hubPath } from './hubPaths.js';\nimport { ingressPath } from './ingressPaths.js';\nimport {\n\ttype OutputDef,\n\toutputDefSchema,\n\twarnSurfaceUndeliverable,\n} from './outputs.js';\n\n// ─── Authoring types ─────────────────────────────────────────────────────────\n\n/**\n * The interaction style a chain is authored for:\n *\n * - `'text'` — chat/typed interaction.\n * - `'voice'` — spoken interaction.\n * - `'auto'` — no fixed style; leave the choice to the platform.\n *\n * Set at author time on the {@link ChainSpec} / chain envelope; defaults to\n * `'auto'` when omitted. Descriptive metadata today — at run time each agent\n * turn's text/voice behavior follows that agent's experience configuration.\n */\nexport type ChainModality = 'text' | 'voice' | 'auto';\n\n/** Minimal JSON Schema carrier (a runtime schema, not a TS type). */\nexport type JsonSchema = Record<string, unknown>;\n\n/**\n * A reference token. The DATA-level shape comes from the generated hub-api\n * `Ref` (`{ $ref: string }`): at author time `$.input.city` produces `{ $ref:\n * \"$.input.city\" }`, and the stored `ChainSpec` contains only these plain\n * objects (never functions), so `JSON.parse(JSON.stringify(spec))` is identity.\n *\n * The generic `T` is author-facing documentation only — refs resolve loosely at\n * author time (a `$.<step>.output` ref is effectively `Ref<unknown>` unless a\n * typed schema threads `T` through), so `Ref<T>` is structurally just the\n * generated `{ $ref }` and any ref flows into any `Ref<…>` slot.\n * This thin author-side alias exists so the `$` proxy /\n * builder have a TS type to produce; the wire shape is the generated `HubRef`.\n */\nexport type Ref<T = unknown> = HubRef & (T extends never ? unknown : unknown);\n\n// ─── Data grammar (ERA-5830) ───────────────────────────────────────────────────\n//\n// hub-api ships the chain grammar as real OpenAPI components (ERA-5805); the SDK\n// re-exports the GENERATED `ChainSpec`/`ChainStep` (+ variants) verbatim instead\n// of hand-rolling a parallel SSOT. hub-api is the source of truth for the shape;\n// the author-time builder/proxy ergonomics below are re-typed against these.\n\n/** A declared chain input variable, addressable as `$.input.<key>`. */\nexport type ChainVar = HubChainVar;\n/** An agent reference: a published experience, an inline spec, or a named ref. */\nexport type AgentRef = HubAgentRef;\n/** A boolean predicate evaluated server-side against the `$.` context. */\nexport type Predicate = HubPredicate;\n/** Run one agent turn — utterance/prompt overlay, structured output, tool scoping. */\nexport type AgentTurnStep = HubAgentTurnStep;\n/** Invoke a tool (`'spindle-slug:tool_name'` or `'builtin:<action>'`). */\nexport type ToolStep = HubToolStep;\n/** Conditional branch — runs `then` or `else` based on a predicate. */\nexport type BranchStep = HubBranchStep;\n/**\n * Run named branches and collect their keyed results. NOTE: the orchestrator\n * executes branches **sequentially** today — `join: 'all'` (the default) is\n * honored; `join: 'race' | 'allSettled'` fail the step at runtime.\n * Bounded-parallel fan-out with real join semantics is planned.\n */\nexport type FanOutStep = HubFanOutStep;\n/** Suspend the run until a named event arrives (or times out). */\nexport type WaitForEventStep = HubWaitForEventStep;\n/** Hand the run off to a human or another agent, then resume. */\nexport type HandoffStep = HubHandoffStep;\n/** Iterate a collection, running `do` for each element. */\nexport type MapStep = HubMapStep;\n/**\n * What fires a chain: the chain-canonical trigger declaration bound\n * via `.trigger()` / `defineChain({ trigger })`. `kind:'button'` (with an\n * optional `gesture`) or `kind:'voice'`. For button triggers, device→chain\n * routing follows a ladder: an **explicit binding**\n * (`devices.bindInput(deviceId, { inputSlot, gesture })`) beats\n * **implicit resolution** by `kind` + the device's selected experience +\n * `gesture`. The binding's `inputSlot` is matched against the id the device\n * reports in `data.input` — never against this trigger's `slot`, which is\n * documentation of intent, not a matcher (and the bound chain need not\n * declare a trigger at all; input slots also differ from output slots, which\n * resolve to a `deviceId` at emit time). Voice triggers never use\n * bindings: routing matches the utterance against the selected experience's\n * voice commands (spoken phrase → chain) first, then falls back to implicit\n * resolution.\n * A {@link TriggerDef} (what the `triggers.*` builders produce) is structurally\n * identical, so a `triggers.button()` helper value flows straight into\n * `.trigger()`.\n */\nexport type ChainTrigger = HubChainTrigger;\n/** One step in a chain — discriminated on `type` over the seven variants. */\nexport type ChainStep = HubChainStep;\n\n/**\n * Canonical, serializable chain definition. Round-trips to chain_versions.content\n * JSONB. The grammar (name/steps/input/defaultAgent/limits/specVersion) is the\n * generated hub-api `ChainSpec`; the `outputs` slot is narrowed to the SDK-native\n * {@link OutputDef} union so the `outputs.physical/surface` factories,\n * capability `tier`, and author-time validation keep their richer typing (the\n * wire shape is identical — the generated `outputs` is just looser).\n */\nexport type ChainSpec = Omit<HubChainSpec, 'outputs'> & {\n\t/**\n\t * Outputs that inform the user on the run lifecycle (`on:`\n\t * start/success/failure), fanned out by the orchestrator's\n\t * `builtin:emit-output`. Lowered verbatim into `content.outputs`.\n\t * Both `surface` and `physical` are supported — a `physical` output's `slot`\n\t * is resolved to a concrete `deviceId` server-side at emit time,\n\t * against the caller's owner-scoped device bindings.\n\t */\n\toutputs?: OutputDef[];\n};\n\n// ─── The typed `$.` reference proxy ────────────────────────────────────────────\n\n/** A traversable, serializable reference node (`$.a.b.c`). */\nexport type RefNode = Ref<unknown> & { readonly [key: string]: RefNode };\n\n// ── Typed `expect` handoff (ERA-5919) ──\n//\n// `expect` on an agentTurn is enforced at RUN time by the orchestrator\n// (OUTPUT_CONTRACT_VIOLATION, ERA-5815); authoring-side it can ALSO thread the\n// schema's inferred output type into the step's output binding. Pass a Zod\n// schema (bare, or wrapped as `{ schema, name? }`) and downstream\n// `$.steps.<id>.output.<field>` — plus the `$.<id>` / `as`-alias forms —\n// autocompletes, and a path the schema does not declare is a COMPILE error.\n// The wire format is untouched: the Zod schema is lowered to plain JSON Schema\n// at author time (`normalizeExpect`), so the stored spec carries the same\n// `expect: { schema, name? }` JSON the orchestrator already validates.\n// Chains that never pass a Zod `expect` keep the loose default `Ctx` below,\n// so existing authoring code compiles unchanged.\n\n/**\n * What `agentTurn.expect` accepts at author time: the wire `{ schema, name? }`\n * with a plain JSON Schema (output binding stays `Ref<unknown>`), a Zod schema\n * (typed output binding), or the wrapper carrying a Zod schema (typed +\n * named contract). Zod values are lowered to JSON Schema on build — the\n * serialized spec never contains a Zod object.\n */\nexport type ExpectInput =\n\t| z.ZodType\n\t| { schema: z.ZodType | JsonSchema; name?: string };\n\n/**\n * The output type an `expect` value implies for the step's output binding:\n * `z.infer<S>` for a Zod schema (bare or wrapped), `unknown` otherwise.\n */\nexport type InferExpect<E> =\n\tE extends z.ZodType<infer T>\n\t\t? T\n\t\t: E extends { schema: z.ZodType<infer T> }\n\t\t\t? T\n\t\t\t: unknown;\n\n/**\n * A {@link Ref} whose downstream field accesses follow `T`'s structure:\n * `TypedRef<{ n: number }>` allows `.n` (a `TypedRef<number>`) and rejects any\n * other key at compile time. `TypedRef<unknown>` degrades to the loose\n * {@link RefNode}, so untyped bindings keep today's anything-goes traversal.\n */\nexport type TypedRef<T> = unknown extends T\n\t? RefNode\n\t: Ref<T> &\n\t\t\t(NonNullable<T> extends readonly (infer E)[]\n\t\t\t\t? { readonly [index: number]: TypedRef<E> }\n\t\t\t\t: NonNullable<T> extends object\n\t\t\t\t\t? {\n\t\t\t\t\t\t\treadonly [K in keyof NonNullable<T> & string]-?: TypedRef<\n\t\t\t\t\t\t\t\tNonNullable<T>[K]\n\t\t\t\t\t\t\t>;\n\t\t\t\t\t\t}\n\t\t\t\t\t: unknown);\n\n/**\n * A step's binding node (`$.steps.<id>`, `$.<id>`, or the `as` alias): its\n * `output` is a {@link TypedRef} of the step's inferred output type. Untyped\n * steps (`T = unknown`) stay a loose {@link RefNode}.\n */\nexport type StepBinding<T> = unknown extends T\n\t? RefNode\n\t: Ref<T> & { readonly output: TypedRef<T> };\n\n/**\n * Author-time context bag (`$`). Every access produces a {@link RefNode}:\n * `$.input.*`, `$.steps.<id>.output`, `$.<alias>.output`, and run built-ins\n * (`$.now`, `$.today_start`, `$.session.id`, `$.run.id`, `$.run.kernelId`).\n *\n * Typing is loose BY DEFAULT (design note): `$.<step>.output` is\n * `Ref<unknown>` unless a typed schema threads `T` through. That threading is\n * the `Outputs` parameter: once the fluent builder has seen a Zod\n * `expect`, closures receive `Ctx<Outputs>` where each declared binding —\n * `$.steps.<id>` plus the top-level `$.<id>` / `as`-alias forms — is a\n * {@link StepBinding} of its inferred type, and unknown names are compile\n * errors. `defineChain` closures and untyped chains keep the loose shape.\n */\nexport type Ctx<Outputs extends Record<string, unknown> = never> = [\n\tOutputs,\n] extends [never]\n\t? { readonly [key: string]: RefNode }\n\t: {\n\t\t\treadonly steps: {\n\t\t\t\treadonly [K in keyof Outputs & string]: StepBinding<Outputs[K]>;\n\t\t\t};\n\t\t\treadonly input: RefNode;\n\t\t\treadonly now: RefNode;\n\t\t\treadonly today_start: RefNode;\n\t\t\treadonly session: RefNode;\n\t\t\treadonly run: RefNode;\n\t\t} & { readonly [K in keyof Outputs & string]: StepBinding<Outputs[K]> };\n\nconst REF_TO_JSON = 'toJSON';\n\n/** Build a Proxy ref node rooted at `path`; child access extends the path. */\nfunction makeRef(path: string): RefNode {\n\tconst target = { $ref: path };\n\treturn new Proxy(target, {\n\t\tget(_t, prop): unknown {\n\t\t\tif (prop === '$ref') return path;\n\t\t\tif (prop === REF_TO_JSON) return () => ({ $ref: path });\n\t\t\t// Guard against being treated as a thenable / primitive.\n\t\t\tif (typeof prop === 'symbol') return undefined;\n\t\t\treturn makeRef(`${path}.${prop}`);\n\t\t},\n\t\t// Refs are read-only author tokens.\n\t\tset() {\n\t\t\treturn false;\n\t\t},\n\t}) as unknown as RefNode;\n}\n\n/**\n * Root authoring context (`$`). Exported so other authoring modules (guardrails'\n * `neverCallWithout`, the context gate) can build serializable `$.`-ref\n * predicates with the same proxy.\n */\nexport function makeCtx(): Ctx {\n\treturn makeRef('$') as unknown as Ctx;\n}\n\n// ─── Step factories (pure constructors) ────────────────────────────────────────\n\n/**\n * `agentTurn` authoring options — the wire step minus `type`/`id`, with\n * `expect` widened to {@link ExpectInput} so a Zod schema can be passed\n * directly.\n */\nexport type AgentTurnOptions = Omit<AgentTurnStep, 'type' | 'id' | 'expect'> & {\n\texpect?: ExpectInput;\n};\n\n/**\n * A Zod schema, robust across duplicated zod copies: `instanceof` first, then\n * the zod-4 `_zod` internals marker (never present on a plain JSON Schema\n * object or the `{ schema, name? }` wrapper).\n */\nfunction isZodSchema(v: unknown): v is z.ZodType {\n\treturn (\n\t\tv instanceof z.ZodType ||\n\t\t(typeof v === 'object' && v !== null && '_zod' in v)\n\t);\n}\n\n/**\n * Lower an author-time `expect` to the wire `{ schema, name? }` shape\n * (ERA-5919): Zod schemas (bare or wrapped) are converted to plain JSON Schema\n * via `z.toJSONSchema`; an already-wire-shaped value passes through untouched,\n * so the serialized format is exactly what the orchestrator validated before.\n */\nfunction normalizeExpect(e: ExpectInput): NonNullable<AgentTurnStep['expect']> {\n\tif (isZodSchema(e)) return { schema: z.toJSONSchema(e) as JsonSchema };\n\tif (isZodSchema(e.schema)) {\n\t\treturn {\n\t\t\tschema: z.toJSONSchema(e.schema) as JsonSchema,\n\t\t\t...(e.name !== undefined && { name: e.name }),\n\t\t};\n\t}\n\treturn e as NonNullable<AgentTurnStep['expect']>;\n}\n\n/**\n * Pure step constructors — one factory per {@link ChainStep} variant. Each takes\n * a single options object (which must include the step `id`) and stamps the\n * discriminating `type`, returning a plain, JSON-serializable step you drop into\n * a {@link defineChain} `steps` array. No network, no validation beyond typing —\n * the whole spec is validated together by {@link defineChain} / `chain().build()`.\n *\n * @remarks\n * `step.agentTurn` additionally accepts a Zod schema (bare or `{ schema, name? }`)\n * as its `expect` and lowers it to plain JSON Schema at build time for a typed\n * output binding; every other factory forwards its options verbatim.\n *\n * `fanOut` branches and `map` iterations execute **sequentially** server-side\n * today — they are accepted at author time, but `join: 'race' | 'allSettled'`\n * and `map` `concurrency > 1` fail the step at runtime (only `join: 'all'`\n * semantics are provided). A suspending step (`waitForEvent` / `handoff`)\n * nested inside `map`/`fanOut` cannot resume; keep suspends at the top level.\n *\n * @example\n * ```ts\n * import { defineChain, step } from '@era-laboratories/era-sdk';\n *\n * const spec = defineChain({\n * name: 'Research + Draft',\n * steps: ($) => [\n * step.agentTurn({ id: 'research', agent: { experienceId: 'exp_0000000000' }, prompt: $.input.topic }),\n * step.agentTurn({ id: 'draft', agent: { experienceId: 'exp_0000000000' }, prompt: $.research.output }),\n * ],\n * });\n * ```\n */\nexport const step = {\n\tagentTurn: (o: AgentTurnOptions & { id: string }): AgentTurnStep => {\n\t\tconst { expect, ...rest } = o;\n\t\treturn {\n\t\t\ttype: 'agentTurn',\n\t\t\t...rest,\n\t\t\t...(expect !== undefined && { expect: normalizeExpect(expect) }),\n\t\t};\n\t},\n\ttool: (o: Omit<ToolStep, 'type'>): ToolStep => ({ type: 'tool', ...o }),\n\tbranch: (o: Omit<BranchStep, 'type'>): BranchStep => ({\n\t\ttype: 'branch',\n\t\t...o,\n\t}),\n\tfanOut: (o: Omit<FanOutStep, 'type'>): FanOutStep => ({\n\t\ttype: 'fanOut',\n\t\t...o,\n\t}),\n\twaitForEvent: (o: Omit<WaitForEventStep, 'type'>): WaitForEventStep => ({\n\t\ttype: 'waitForEvent',\n\t\t...o,\n\t}),\n\thandoff: (o: Omit<HandoffStep, 'type'>): HandoffStep => ({\n\t\ttype: 'handoff',\n\t\t...o,\n\t}),\n\tmap: (o: Omit<MapStep, 'type'>): MapStep => ({ type: 'map', ...o }),\n};\n\n/**\n * Reference a rostered agent by name — sugar for `{ ref: name }`. The name must\n * be declared in the chain's `agents` roster (`.agents({...})` / the `agents`\n * field); `defineChain` rejects an unrostered ref at author time, mirroring the\n * publish-time `unresolved_agent_reference` block.\n */\nexport function ref(name: string): AgentRef {\n\treturn { ref: name };\n}\n\n/**\n * Reference a published experience directly — `{ experienceId, versionId? }`.\n * Omit `versionId` to let publish pin the experience's current version.\n */\nexport function agent(experienceId: string, versionId?: string): AgentRef {\n\treturn versionId ? { experienceId, versionId } : { experienceId };\n}\n\n/**\n * Recursively walk the (already `zChainSpec`-validated) step tree and enforce\n * step-id uniqueness across the whole chain, including nested branch/fanOut/map/\n * waitForEvent bodies. This is a cross-step REFINEMENT that JSON Schema (and thus\n * `zChainSpec`) cannot express, so it stays an explicit post-parse check.\n */\nfunction collectStepIds(steps: ChainStep[], seen: Set<string>): void {\n\tfor (const s of steps) {\n\t\tif (seen.has(s.id)) {\n\t\t\tthrow new TypeError(`duplicate chain step id \"${s.id}\"`);\n\t\t}\n\t\tseen.add(s.id);\n\t\tif (s.type === 'branch') {\n\t\t\tcollectStepIds(s.then, seen);\n\t\t\tif (s.else) collectStepIds(s.else, seen);\n\t\t} else if (s.type === 'fanOut') {\n\t\t\tfor (const b of s.branches) collectStepIds(b.steps, seen);\n\t\t} else if (s.type === 'map') {\n\t\t\tcollectStepIds(s.do, seen);\n\t\t} else if (s.type === 'waitForEvent') {\n\t\t\tif (s.timeout?.onTimeout) collectStepIds(s.timeout.onTimeout, seen);\n\t\t}\n\t}\n}\n\n/** The `{ ref }` name of an AgentRef (or null for experienceId/spec/`'human'`). */\nfunction refNameOf(a: AgentRef | 'human' | undefined): string | null {\n\tif (a && typeof a === 'object' && 'ref' in a && typeof a.ref === 'string') {\n\t\treturn a.ref;\n\t}\n\treturn null;\n}\n\n/**\n * Collect the names of every `{ ref }` AgentRef used across the step tree\n * (agentTurn.agent + handoff.to) so `defineChain` can verify each resolves\n * against the declared `agents` roster (ERA-5918).\n */\nfunction collectAgentRefNames(steps: ChainStep[], out: Set<string>): void {\n\tfor (const s of steps) {\n\t\tif (s.type === 'agentTurn') {\n\t\t\tconst n = refNameOf(s.agent);\n\t\t\tif (n) out.add(n);\n\t\t} else if (s.type === 'handoff') {\n\t\t\tconst n = refNameOf(s.to);\n\t\t\tif (n) out.add(n);\n\t\t}\n\t\tif (s.type === 'branch') {\n\t\t\tcollectAgentRefNames(s.then, out);\n\t\t\tif (s.else) collectAgentRefNames(s.else, out);\n\t\t} else if (s.type === 'fanOut') {\n\t\t\tfor (const b of s.branches) collectAgentRefNames(b.steps, out);\n\t\t} else if (s.type === 'map') {\n\t\t\tcollectAgentRefNames(s.do, out);\n\t\t} else if (s.type === 'waitForEvent') {\n\t\t\tif (s.timeout?.onTimeout) collectAgentRefNames(s.timeout.onTimeout, out);\n\t\t}\n\t}\n}\n\n/**\n * Validate chain `outputs` (ERA-4453). Each entry is structurally checked\n * against `outputDefSchema`. Both `surface` and `physical` transports are\n * accepted; a `physical` output's `slot` is resolved to a concrete `deviceId`\n * server-side at emit time (ERA-4500).\n */\nfunction validateChainOutputs(outputs: OutputDef[] | undefined): void {\n\tif (outputs === undefined) return;\n\tif (!Array.isArray(outputs)) {\n\t\tthrow new TypeError('defineChain: `outputs` must be an array');\n\t}\n\toutputs.forEach((o, i) => {\n\t\tconst parsed = outputDefSchema.safeParse(o);\n\t\tif (!parsed.success) {\n\t\t\tthrow new TypeError(\n\t\t\t\t`defineChain: outputs[${i}] is not a valid OutputDef — ${parsed.error.message}`,\n\t\t\t);\n\t\t}\n\t});\n\t// ERA-6036: one-shot advisory if the spec authors a `surface` output the\n\t// author may not have built via the factory directly. Never throws.\n\tif (\n\t\toutputs.some((o) => (o as OutputDef | undefined)?.transport === 'surface')\n\t) {\n\t\twarnSurfaceUndeliverable();\n\t}\n}\n\ntype StepsInput = ChainStep[] | ((ctx: Ctx) => ChainStep[]);\n\n/**\n * Validate + normalize a chain definition into a frozen, fully-serialized\n * `ChainSpec`. When `steps` is an authoring closure it is invoked ONCE with the\n * `$` proxy; the result is materialized to a plain `ChainStep[]` (all `$.`\n * tokens collapsed to `{\"$ref\":...}`, no functions) via a JSON round-trip, so\n * the stored spec round-trips byte-for-byte and diffs cleanly.\n *\n * The structural shape is validated against `zChainSpec` (the same grammar the\n * platform validates server-side): a malformed step (wrong/unknown discriminator,\n * missing required field, bad type) is rejected here. The SDK-native `outputs`\n * rule (strict `OutputDef`) runs first so its richer\n * messages win; the one check `zChainSpec` can't express — cross-step id\n * uniqueness — stays as an explicit post-parse refinement ({@link collectStepIds}).\n */\nexport function defineChain(\n\tspec: Omit<ChainSpec, 'specVersion' | 'steps'> & { steps: StepsInput },\n): ChainSpec {\n\tif (typeof spec.name !== 'string' || spec.name.trim().length === 0) {\n\t\tthrow new TypeError('defineChain: `name` must be a non-empty string');\n\t}\n\tconst rawSteps =\n\t\ttypeof spec.steps === 'function' ? spec.steps(makeCtx()) : spec.steps;\n\tif (!Array.isArray(rawSteps)) {\n\t\tthrow new TypeError('defineChain: `steps` must resolve to an array');\n\t}\n\tconst draft: ChainSpec = {\n\t\t...spec,\n\t\tsteps: rawSteps,\n\t\tspecVersion: 1,\n\t};\n\t// Materialize: collapse proxies → {\"$ref\"} plain objects and strip any\n\t// functions, guaranteeing JSON identity for clean version diffs.\n\tconst materialized = JSON.parse(JSON.stringify(draft)) as ChainSpec;\n\tif (materialized.steps.length === 0) {\n\t\tthrow new TypeError('defineChain: a chain needs at least one step');\n\t}\n\t// SDK-native output rule first (strict OutputDef) so\n\t// their richer errors surface before the generic grammar gate.\n\tvalidateChainOutputs(materialized.outputs);\n\t// Validate the data shape against the generated hub-api grammar. Use\n\t// safeParse + return the original `materialized` (not `result.data`) so the\n\t// spec stays byte-identical — `z.object` would otherwise strip keys.\n\t// ERA-6191/ERA-6201: `outputs` now flow through the generated pass. The\n\t// promoted hub-api spec's `zOutputDef` accepts the slot-XOR-deviceId contract\n\t// (deviceId-only outputs, ERA-6191), so the former exclusion is retired. The\n\t// generated grammar is a superset of the SDK-native `outputDefSchema` run\n\t// above (which enforces exactly-one-target), so anything that passed the\n\t// strict check passes here too.\n\tconst result = zChainSpec.safeParse(materialized);\n\tif (!result.success) {\n\t\tconst detail = result.error.issues\n\t\t\t.map((i) => `${i.path.join('.') || '<root>'}: ${i.message}`)\n\t\t\t.join('; ');\n\t\tthrow new TypeError(`defineChain: invalid chain spec — ${detail}`);\n\t}\n\tcollectStepIds(materialized.steps, new Set<string>());\n\t// ERA-5918: every `{ ref }` AgentRef must resolve against the declared\n\t// `agents` roster — rejected here at author time, mirroring the hub's\n\t// publish-time `unresolved_agent_reference` block.\n\tconst refNames = new Set<string>();\n\tcollectAgentRefNames(materialized.steps, refNames);\n\tconst defaultRef = refNameOf(materialized.defaultAgent);\n\tif (defaultRef) refNames.add(defaultRef);\n\tconst roster = materialized.agents ?? {};\n\tconst unresolved = [...refNames].filter((n) => !(n in roster));\n\tif (unresolved.length > 0) {\n\t\tthrow new TypeError(\n\t\t\t`defineChain: unresolved agent ref(s) ${unresolved\n\t\t\t\t.map((n) => `\"${n}\"`)\n\t\t\t\t.join(', ')} — declare them in the \\`agents\\` roster ` +\n\t\t\t\t'(`.agents({...})` or the `agents` field).',\n\t\t);\n\t}\n\treturn Object.freeze(materialized);\n}\n\n// ─── Fluent builder (`chain(name)`) ────────────────────────────────────────────\n\n/** A value, or an `($) => value` author closure resolved at build time. */\ntype OrCtx<T> = T | ((ctx: Ctx) => T);\n\nfunction resolveCtx<T>(v: OrCtx<T>, ctx: Ctx): T {\n\treturn typeof v === 'function' ? (v as (c: Ctx) => T)(ctx) : v;\n}\n\n/**\n * Fluent sub-builder for a nested step list — the `then`/`else` body of a\n * {@link BranchStep} passed to {@link ChainBuilder.branch}. Each method appends\n * one step and returns `this` for chaining; the parent builder collects the\n * accumulated steps. Options may be a literal or an `($) => options` closure\n * that receives the `$` ref proxy for referencing inputs/other steps.\n *\n * @remarks\n * Nested bodies intentionally stay loosely typed — their step ids are not\n * registered in the parent's typed-output map, so closures here always receive\n * the loose {@link Ctx}. `fanOut` and `map` have no sub-builder methods; author\n * those with the {@link step} factories directly.\n */\nexport interface SubChainBuilder {\n\t/** Append an `agentTurn` step with the given `id`; `o` sets prompt/agent/expect/etc. */\n\tagentTurn(id: string, o?: OrCtx<AgentTurnOptions>): this;\n\t/** Append a `tool` step invoking `'spindle-slug:tool_name'` or `'builtin:<action>'`. */\n\ttool(id: string, o: OrCtx<Omit<ToolStep, 'type' | 'id'>>): this;\n\t/** Append a nested `branch` step: run `then` (or `els`) based on the `when` predicate. */\n\tbranch(\n\t\tid: string,\n\t\twhen: (ctx: Ctx) => Predicate,\n\t\tthen: (b: SubChainBuilder) => void,\n\t\tels?: (b: SubChainBuilder) => void,\n\t): this;\n\t/** Append a `handoff` step; defaults to `{ to: 'human' }` when `o` is omitted. */\n\thandoff(id: string, o?: OrCtx<Omit<HandoffStep, 'type' | 'id'>>): this;\n\t/** Append a `waitForEvent` step that suspends the run until `event` arrives (or times out). */\n\twaitForEvent(\n\t\tid: string,\n\t\to: OrCtx<Omit<WaitForEventStep, 'type' | 'id'>>,\n\t): this;\n}\n\nclass StepListBuilder implements SubChainBuilder {\n\tprotected steps: ChainStep[] = [];\n\tconstructor(protected ctx: Ctx) {}\n\n\tagentTurn(id: string, o?: OrCtx<AgentTurnOptions>): this {\n\t\tconst body = o ? resolveCtx(o, this.ctx) : {};\n\t\tthis.steps.push(step.agentTurn({ id, ...body }));\n\t\treturn this;\n\t}\n\ttool(id: string, o: OrCtx<Omit<ToolStep, 'type' | 'id'>>): this {\n\t\tthis.steps.push(step.tool({ id, ...resolveCtx(o, this.ctx) }));\n\t\treturn this;\n\t}\n\thandoff(id: string, o?: OrCtx<Omit<HandoffStep, 'type' | 'id'>>): this {\n\t\tif (o) {\n\t\t\tthis.steps.push(step.handoff({ id, ...resolveCtx(o, this.ctx) }));\n\t\t} else {\n\t\t\tthis.steps.push(step.handoff({ id, to: 'human' }));\n\t\t}\n\t\treturn this;\n\t}\n\twaitForEvent(\n\t\tid: string,\n\t\to: OrCtx<Omit<WaitForEventStep, 'type' | 'id'>>,\n\t): this {\n\t\tthis.steps.push(step.waitForEvent({ id, ...resolveCtx(o, this.ctx) }));\n\t\treturn this;\n\t}\n\tbranch(\n\t\tid: string,\n\t\twhen: (ctx: Ctx) => Predicate,\n\t\tthen: (b: SubChainBuilder) => void,\n\t\tels?: (b: SubChainBuilder) => void,\n\t): this {\n\t\tconst thenB = new StepListBuilder(this.ctx);\n\t\tthen(thenB);\n\t\tconst branchStep: Omit<BranchStep, 'type'> = {\n\t\t\tid,\n\t\t\twhen: when(this.ctx),\n\t\t\t// biome-ignore lint/suspicious/noThenProperty: `then` is the canonical BranchStep field (plain orchestration data, never awaited).\n\t\t\tthen: thenB.collect(),\n\t\t};\n\t\tif (els) {\n\t\t\tconst elseB = new StepListBuilder(this.ctx);\n\t\t\tels(elseB);\n\t\t\tbranchStep.else = elseB.collect();\n\t\t}\n\t\tthis.steps.push(step.branch(branchStep));\n\t\treturn this;\n\t}\n\tcollect(): ChainStep[] {\n\t\treturn this.steps;\n\t}\n}\n\n/**\n * The concrete fluent chain builder behind {@link chain}. Accumulates spec-level\n * fields (name/modality/input/agents/limits/trigger/outputs) plus a step list,\n * and `.build()` routes them through {@link defineChain} — so a chain authored\n * fluently and one authored declaratively are deep-equal by construction.\n *\n * @remarks\n * Authoring is pure: no method here touches the network. `chain(name)` hands\n * back the {@link TypedChainBuilder} view of this same runtime object, which\n * carries the typed-output generics; the class is the single implementation.\n * Reach it via the {@link chain} entrypoint rather than constructing directly.\n */\nexport class ChainBuilder extends StepListBuilder {\n\tprivate spec: Omit<ChainSpec, 'specVersion' | 'steps'>;\n\t/**\n\t * Create a builder for a named chain. Prefer the {@link chain} entrypoint,\n\t * which constructs this for you.\n\t *\n\t * @param name - Chain name shown in run history and referenced by triggers.\n\t */\n\tconstructor(name: string) {\n\t\tsuper(makeCtx());\n\t\tthis.spec = { name };\n\t}\n\t/** Set the run modality — `'text' | 'voice' | 'auto'` (see {@link ChainModality}); default `'auto'`. */\n\tmodality(m: ChainModality): this {\n\t\tthis.spec.modality = m;\n\t\treturn this;\n\t}\n\t/** Set the chain's human-readable description. */\n\tdescription(d: string): this {\n\t\tthis.spec.description = d;\n\t\treturn this;\n\t}\n\t/** Declare the chain's input schema — `$.input.<key>` refs resolve against these {@link ChainVar} entries. */\n\tinput(input: Record<string, ChainVar>): this {\n\t\tthis.spec.input = input;\n\t\treturn this;\n\t}\n\t/** Pin the whole chain to one experience: sets the fallback agent for `agentTurn` steps that omit `agent`, freezing the run's persona to that experience's published version at chain-publish time. */\n\tdefaultAgent(a: AgentRef): this {\n\t\tthis.spec.defaultAgent = a;\n\t\treturn this;\n\t}\n\t/**\n\t * Declare the agent roster that `{ ref }` AgentRefs resolve against.\n\t * Publishing resolves each entry and pins it to a concrete experience version.\n\t */\n\tagents(roster: Record<string, AgentRef>): this {\n\t\tthis.spec.agents = roster;\n\t\treturn this;\n\t}\n\t/** Set run resource ceilings (e.g. `maxSteps` / `maxToolCalls` / `deadlineMs`); the orchestrator fails the run once a ceiling is exceeded. */\n\tlimits(l: ChainSpec['limits']): this {\n\t\tthis.spec.limits = l;\n\t\treturn this;\n\t}\n\t/** Run-lifecycle outputs — both `surface` and `physical` transports (see {@link ChainSpec.outputs}). */\n\toutputs(outputs: OutputDef[]): this {\n\t\tthis.spec.outputs = outputs;\n\t\treturn this;\n\t}\n\t/**\n\t * Declare what fires this chain. Pass a {@link ChainTrigger} or a\n\t * `triggers.*` helper value — they share the wire shape:\n\t *\n\t * ```ts\n\t * import { chain, triggers } from '@era-laboratories/era-sdk';\n\t *\n\t * const c = chain('doorbell')\n\t * .trigger(triggers.button({ slot: 'front-door', gesture: 'press' }))\n\t * .agentTurn('greet', { prompt: 'Someone is at the door.' })\n\t * .build();\n\t * // c.trigger === { kind: 'button', slot: 'front-door', gesture: 'press' }\n\t * ```\n\t *\n\t * The `slot` names this chain's expected input (documentation of intent —\n\t * routing never matches on it). For button triggers, route a device input\n\t * to this chain explicitly with `devices.bindInput(...)`;\n\t * unbound devices fall back to implicit resolution by `kind` + selected\n\t * experience + `gesture` (see {@link ChainTrigger}). Omit the trigger to\n\t * leave the chain untriggered.\n\t */\n\ttrigger(t: ChainTrigger): this {\n\t\tthis.spec.trigger = t;\n\t\treturn this;\n\t}\n\t/** Materialize the identical `ChainSpec` `defineChain` would produce. */\n\tbuild(): ChainSpec {\n\t\treturn defineChain({ ...this.spec, steps: this.collect() });\n\t}\n}\n\n// ── Typed fluent surface (ERA-5919) ──\n//\n// `chain()` hands back this interface instead of the raw {@link ChainBuilder}\n// class. It is the SAME runtime object with the SAME method set — the class\n// stays the single implementation — but each step method threads two pieces of\n// type state through the fluent chain:\n//\n// - `Outputs`: binding name → inferred output type. Every step registers its\n// `id` (and `as` alias when the options are a literal), typed by\n// {@link InferExpect} for agentTurn `expect`, `unknown` otherwise.\n// - `Typed`: flips to `true` on the first `expect` that actually infers a\n// type. Until then closures receive the loose default {@link Ctx} — a chain\n// that never passes a Zod `expect` behaves exactly as before. Once typed,\n// closures receive `Ctx<Outputs>`, where a ref to a field the schema does\n// not declare is a compile error.\n//\n// Nested {@link SubChainBuilder} bodies intentionally stay loose: their step\n// ids register in no map, and their closures keep the default `Ctx`.\n\n/** Builder type-state: binding name → inferred step output type. */\ntype StepOutputs = Record<string, unknown>;\n\n/** The output type a step's options imply (via `expect`), else `unknown`. */\ntype OutputOf<O> = O extends { expect: infer E } ? InferExpect<E> : unknown;\n\n/** Whether {@link OutputOf} inferred a real type (flips the builder typed). */\ntype HasTypedOutput<O> = unknown extends OutputOf<O> ? false : true;\n\n/** Extra `alias → T` entry when the options carry a literal `as` alias. */\ntype AliasBinding<O, T> = O extends { as: infer N }\n\t? N extends string\n\t\t? string extends N\n\t\t\t? Record<never, never>\n\t\t\t: { readonly [K in N]: T }\n\t\t: Record<never, never>\n\t: Record<never, never>;\n\n/** The `$` closures see: loose {@link Ctx} until the chain is typed. */\ntype BuilderCtx<\n\tS extends StepOutputs,\n\tTyped extends boolean,\n> = Typed extends true ? Ctx<S> : Ctx;\n\n/**\n * The typed fluent chain surface returned by {@link chain}. Same\n * methods (and same runtime object) as {@link ChainBuilder}; the generics\n * carry which step outputs are typed — see the section note above. Keep this\n * interface in lockstep with the class.\n */\nexport interface TypedChainBuilder<\n\tS extends StepOutputs = Record<never, never>,\n\tTyped extends boolean = false,\n> {\n\t/** See {@link ChainBuilder.modality}. */\n\tmodality(m: ChainModality): this;\n\t/** See {@link ChainBuilder.description}. */\n\tdescription(d: string): this;\n\t/** See {@link ChainBuilder.input}. */\n\tinput(input: Record<string, ChainVar>): this;\n\t/** See {@link ChainBuilder.defaultAgent}. */\n\tdefaultAgent(a: AgentRef): this;\n\t/** See {@link ChainBuilder.agents}. */\n\tagents(roster: Record<string, AgentRef>): this;\n\t/** See {@link ChainBuilder.limits}. */\n\tlimits(l: ChainSpec['limits']): this;\n\t/** See {@link ChainBuilder.outputs}. */\n\toutputs(outputs: OutputDef[]): this;\n\t/** See {@link ChainBuilder.trigger}. */\n\ttrigger(t: ChainTrigger): this;\n\t/** Materialize the identical `ChainSpec` `defineChain` would produce. */\n\tbuild(): ChainSpec;\n\n\t/**\n\t * Run one agent turn. Pass a Zod schema (bare or `{ schema, name? }`) as\n\t * `expect` and this step's output binding is typed with the schema's\n\t * inferred type downstream.\n\t */\n\tagentTurn<\n\t\tId extends string,\n\t\tconst O extends AgentTurnOptions = Record<never, never>,\n\t>(\n\t\tid: Id,\n\t\to?: O | ((ctx: BuilderCtx<S, Typed>) => O),\n\t): TypedChainBuilder<\n\t\tS & Record<Id, OutputOf<O>> & AliasBinding<O, OutputOf<O>>,\n\t\tTyped extends true ? true : HasTypedOutput<O>\n\t>;\n\t/** Append a `tool` step invoking `'spindle-slug:tool_name'` or `'builtin:<action>'`; its output binding stays untyped. */\n\ttool<Id extends string, const O extends Omit<ToolStep, 'type' | 'id'>>(\n\t\tid: Id,\n\t\to: O | ((ctx: BuilderCtx<S, Typed>) => O),\n\t): TypedChainBuilder<\n\t\tS & Record<Id, unknown> & AliasBinding<O, unknown>,\n\t\tTyped\n\t>;\n\t/** Append a `handoff` step; defaults to `{ to: 'human' }` when `o` is omitted. */\n\thandoff<\n\t\tId extends string,\n\t\tconst O extends Omit<HandoffStep, 'type' | 'id'> = { to: 'human' },\n\t>(\n\t\tid: Id,\n\t\to?: O | ((ctx: BuilderCtx<S, Typed>) => O),\n\t): TypedChainBuilder<\n\t\tS & Record<Id, unknown> & AliasBinding<O, unknown>,\n\t\tTyped\n\t>;\n\t/** Append a `waitForEvent` step that suspends the run until `event` arrives (or times out). */\n\twaitForEvent<\n\t\tId extends string,\n\t\tconst O extends Omit<WaitForEventStep, 'type' | 'id'>,\n\t>(\n\t\tid: Id,\n\t\to: O | ((ctx: BuilderCtx<S, Typed>) => O),\n\t): TypedChainBuilder<\n\t\tS & Record<Id, unknown> & AliasBinding<O, unknown>,\n\t\tTyped\n\t>;\n\t/** Append a `branch` step: run `then` (or `els`) based on the `when` predicate over the `$` context. */\n\tbranch<Id extends string>(\n\t\tid: Id,\n\t\twhen: (ctx: BuilderCtx<S, Typed>) => Predicate,\n\t\tthen: (b: SubChainBuilder) => void,\n\t\tels?: (b: SubChainBuilder) => void,\n\t): TypedChainBuilder<S & Record<Id, unknown>, Typed>;\n}\n\n/**\n * Fluent entrypoint — `chain('name').agentTurn(...).build()`.\n *\n * Returns the {@link TypedChainBuilder} view of a {@link ChainBuilder}: pass a\n * Zod schema as an agentTurn `expect` and downstream `$.steps.<id>.output`\n * refs are typed by the schema; without one, everything stays the\n * loose `Ref<unknown>` shape it always was.\n */\nexport function chain(name: string): TypedChainBuilder {\n\treturn new ChainBuilder(name) as unknown as TypedChainBuilder;\n}\n\n// ─── Facade resource + runtime types ───────────────────────────────────────────\n\n/** A chain row (metadata + lifecycle pointers), mirrors `Experience`. */\nexport interface Chain {\n\t/** Chain id. */\n\tid: string;\n\t/** Chain name. */\n\tname: string;\n\t/** Optional description, or `null`. */\n\tdescription: string | null;\n\t/** Modality the chain runs in. */\n\tmodality: ChainModality;\n\t/** Id of the resource that owns this chain, or `null`. Find ids via `resources.mine()` or `era.scope()`. */\n\tresourceId: string | null;\n\t/** Current draft version id, or `null` if none. */\n\tcurrentDraftVersionId: string | null;\n\t/** Current published version id, or `null` if unpublished. */\n\tcurrentPublishedVersionId: string | null;\n\t/** Whether the chain is active. */\n\tisActive: boolean;\n\t/** ISO-8601 creation timestamp. */\n\tcreatedAt: string;\n\t/** ISO-8601 last-update timestamp. */\n\tupdatedAt: string;\n}\n\n/**\n * A single chain version, mirrors `ExperienceVersion` — the shape returned by\n * `GET /api/chains/{id}/publishing-versions/{vId}` and the lifecycle methods.\n *\n * NOTE: `content` carries the stored wire-format chain spec, NOT the SDK-native\n * {@link ChainSpec} authoring type — the two are wire-identical, but the SDK\n * narrows `outputs` to {@link OutputDef} only on the authoring (request) side.\n */\nexport type ChainVersion =\n\tGenGetChainsByIdPublishingVersionsByVIdResponses[200];\n\n/** Target resource for `create()`. */\nexport interface CreateChainTarget {\n\t/** Id of the resource that will own this chain. Find ids via `resources.mine()` or `era.scope()`. */\n\tresourceId: string;\n}\n\n/** Body for `update()` — only supplied fields change. */\nexport interface UpdateChainInput {\n\t/** New chain name. */\n\tname?: string;\n\t/** New description; `null` clears it. */\n\tdescription?: string | null;\n\t/** New default modality. */\n\tmodality?: ChainModality;\n\t/** Toggle whether the chain is active. */\n\tisActive?: boolean;\n}\n\n/** Pagination + filter options for `list()`. */\nexport interface ListChainsOptions extends PaginateOptions {\n\t/** Restrict to chains owned by this resource id. */\n\tresourceId?: string;\n\t/**\n\t * Filter by active state: `true` returns only active chains,\n\t * `false` only deactivated ones. Omit for all chains.\n\t */\n\tisActive?: boolean;\n}\n\n/** Options for `run()`. */\nexport interface RunChainOptions {\n\t/** Session id the run executes within (required) — groups the run's turns for correlation and billing. */\n\tsession: string;\n\t/** Input variable values keyed by the chain's declared `input` keys. */\n\tinput?: Record<string, unknown>;\n\t/** Pin a version; default = currentPublishedVersionId. */\n\tversionId?: string;\n\t/**\n\t * Output modality hint for the run. Advisory today — each agent turn's\n\t * text/voice behavior follows that agent's experience configuration.\n\t */\n\tmodality?: ChainModality;\n\t/** Abort signal to cancel the run stream. */\n\tsignal?: AbortSignal;\n}\n\n/**\n * Run lifecycle status (`'paused'` is reserved in the contract but never\n * emitted today).\n */\nexport type ChainRunStatus = GenChainRunState['status'];\n\n/**\n * Snapshot delivery-receipt state for one output — the\n * {@link OutputReceipt} `receiptStatus` field.\n *\n * NOTE: `'failed'` is TRANSIENT — a retry is in flight and it may still advance\n * to `'delivered'` / `'undeliverable'` / `'expired'`. Treat this union as\n * non-exhaustive: future transports/states may add members, so avoid relying on\n * an exhaustive switch.\n */\nexport type ReceiptStatus = GenOutputReceipt['receiptStatus'];\n\n/**\n * Latest delivery-receipt state for one emitted **physical** output, folded\n * read-only from the run's receipt trail. Physical outputs only —\n * `say` / `surface` / `voice` carry no receipt today. The folded snapshot keys\n * match the live receipt stream events 1:1.\n *\n * For the *live* trail (each transition as it happens) watch the\n * {@link ChainEvent} stream — `output_emitted` → `output_delivered` /\n * `output_failed` / `output_undeliverable` / `output_expired`, correlated by\n * `receiptToken`. This type is the durable snapshot of that trail for\n * callers that poll {@link ChainsModule.getRun} instead of streaming.\n */\nexport type OutputReceipt = GenOutputReceipt;\n\n/**\n * Durable run-state snapshot (`GET /runs/{runId}`). The server emits every\n * key unconditionally, so **all fields are required** (incl. `outputs`);\n * nullability marks the fields whose *value* can be null.\n *\n * Field notes:\n * - `awaiting` — most recent awaited-event descriptor; `null` only if the run\n * never suspended. NOT cleared on resume — authoritative only while\n * `status` is `'awaiting_event'`.\n * - `awaiting.deadlineAt` — **epoch-milliseconds integer** deadline after\n * which the wait is failed server-side; absent when the wait has no\n * deadline.\n * - `outputs` — per-output delivery-receipt snapshot, latest state per\n * emitted physical output. Empty array for runs with no\n * physical outputs. For the live trail, watch the {@link ChainEvent}\n * receipt events instead.\n * - `status` — includes `'paused'`, reserved but never emitted today.\n * - `cursor` — opaque server-internal resume state; shape not contractual.\n */\nexport type ChainRunState = GenChainRunState;\n\n// facade-exempt(protocol): client-side aggregation computed from the generated ChainStreamEvent stream (cost/step/request-id rollup) — no emitter schema. ERA-6209\n/** Aggregated run-level metadata. */\nexport interface ChainRunMeta {\n\t/** Total cost (cents) across all turns in the run. */\n\ttotalCost: number;\n\t/** Post-charge wallet balance (cents) from the terminal `run_done` event. */\n\tlastBalance?: number;\n\t/** Request ids of every metered call in the run. */\n\trequestIds: string[];\n\t/** Number of steps executed. */\n\tsteps: number;\n}\n\n/**\n * Terminal statuses a run can settle with (`run_done.status`) — the run-state\n * statuses minus the non-terminal ones.\n */\nexport type ChainRunDoneStatus = Exclude<\n\tChainRunStatus,\n\t'running' | 'awaiting_event' | 'paused'\n>;\n\n/**\n * The normalized chain event stream. Each chain-native variant is the\n * camelCase mapping of its wire event (the wire is snake_case; the SDK\n * normalizes keys):\n *\n * - framing: `run_started` / `step_started` / `step_completed` /\n * `step_failed` / `awaiting_event` / `handed_off` / `run_done` / `run_error`\n * - outputs + delivery receipts: `output_emitted`\n * (pending receipt) then `output_delivered` / `output_failed` (retryable) /\n * `output_undeliverable` / `output_expired`, correlated by `receiptToken`\n * - `event_delivered`: an external event callback\n * resumed a run parked on `awaiting_event`\n *\n * The stream also interleaves nested agent events ({@link ExperienceRunEvent},\n * re-tagged with `stepId`) — those reuse the chat SSE event shapes and are\n * composed here, exactly as on the wire.\n *\n * Two narrowings relative to the raw wire shape: `step_started.stepType` is\n * narrowed to the SDK's `ChainStep['type']` union (the wire says `string`), and\n * `run_done.status` is narrowed to {@link ChainRunDoneStatus} (the wire says\n * `string` but emits exactly these values).\n */\nexport type ChainEvent =\n\t| CamelizeKeys<GenChainRunStartedEvent>\n\t| (Omit<CamelizeKeys<GenChainStepStartedEvent>, 'stepType'> &\n\t\t\tRecord<'stepType', ChainStep['type']>)\n\t| (ExperienceRunEvent & Record<'stepId', string>)\n\t| CamelizeKeys<GenChainStepCompletedEvent>\n\t| CamelizeKeys<GenChainStepFailedEvent>\n\t| CamelizeKeys<GenChainSafetyBlockedEvent>\n\t| CamelizeKeys<GenChainAwaitingEventEvent>\n\t| CamelizeKeys<GenChainHandedOffEvent>\n\t| CamelizeKeys<GenChainOutputEmittedEvent>\n\t| CamelizeKeys<GenChainOutputDeliveredEvent>\n\t| CamelizeKeys<GenChainOutputFailedEvent>\n\t| CamelizeKeys<GenChainOutputUndeliverableEvent>\n\t| CamelizeKeys<GenChainOutputExpiredEvent>\n\t| CamelizeKeys<GenChainEventDeliveredEvent>\n\t| (Omit<CamelizeKeys<GenChainRunDoneEvent>, 'status'> &\n\t\t\tRecord<'status', ChainRunDoneStatus>)\n\t| CamelizeKeys<GenChainRunErrorEvent>;\n\n/**\n * Resolved value of {@link ChainRun.result} — the subset of the terminal\n * `run_done` event the run handle settles with.\n */\nexport type ChainRunResult = Pick<\n\tCamelizeKeys<GenChainRunDoneEvent>,\n\t'output'\n> &\n\tRecord<'status', ChainRunDoneStatus>;\n\n// facade-exempt(protocol): client-side run handle — the events/result it carries are derived from the generated ChainStreamEvent components; the handle's own members (promises + meta) have no emitter schema. ERA-6209\n/** `AsyncIterable<ChainEvent>` + run-level control & aggregated `_meta`. */\nexport interface ChainRun extends AsyncIterable<ChainEvent> {\n\t/** Resolves to the run id (== kernelId) once `run_started` is observed. */\n\treadonly chainRunId: Promise<string>;\n\t/** Resolves on `run_done` (or rejects with {@link EraChainError}). */\n\treadonly result: Promise<ChainRunResult>;\n\t/** Aggregated cost across all turns (depends on stream `_meta` landing). */\n\tmeta(): Promise<ChainRunMeta>;\n}\n\n// ─── Module surface ────────────────────────────────────────────────────────────\n\n/**\n * The `chains` facade — declarative multi-step orchestration authoring, its\n * publishing lifecycle, and durable server-side runs. Construct it with\n * {@link chains}(client). Two surfaces:\n *\n * - **Authoring + versioning** (over hub-api): CRUD on the chain envelope plus\n * the draft → review → published state machine, mirroring the experiences\n * publishing lifecycle exactly.\n * - **Runtime** (over the ingress orchestrator): {@link ChainsModule.run} /\n * {@link ChainsModule.resume} open an SSE stream of {@link ChainEvent}s;\n * {@link ChainsModule.send} / {@link ChainsModule.getRun} /\n * {@link ChainsModule.cancel} drive and inspect a durable run out of band.\n *\n * Build the `ChainSpec` these methods consume with the pure {@link defineChain}\n * / {@link chain} authoring helpers.\n *\n * @throws {@link EraAPIError} on non-2xx hub-api / ingress responses (per-method\n * status notes below). Streaming runs surface failures as an\n * {@link EraChainError} on the {@link ChainRun} `result`/iteration instead.\n */\nexport interface ChainsModule {\n\t// ── Authoring (mirrors era.experiences / era.experienceVersions) ──\n\t/**\n\t * Create a chain plus its initial draft version, owned by a resource\n\t * (`POST /api/chains`). The chain starts unpublished — advance a version\n\t * through the review lifecycle to publish it.\n\t *\n\t * @param spec - The chain definition from {@link defineChain} / `chain().build()`.\n\t * @param target - Owning resource; find ids via `era.resources.mine()` / `era.scope()`.\n\t * @param opts - Optional per-request `signal` / `headers`.\n\t * @returns The created {@link Chain} envelope.\n\t * @throws {@link EraAPIError} 400 (invalid spec), 403 (not authorized for the resource).\n\t * @example\n\t * ```ts\n\t * const chain = await era.chains.create(spec, { resourceId: 'res_0000000000' });\n\t * ```\n\t */\n\tcreate(\n\t\tspec: ChainSpec,\n\t\ttarget: CreateChainTarget,\n\t\topts?: RequestOptions,\n\t): Promise<Chain>;\n\t/**\n\t * Cursor-paginated async walk over chains, optionally scoped to one resource\n\t * and/or filtered by active state (`GET /api/chains`). The paginator fetches\n\t * pages lazily as you iterate.\n\t *\n\t * @param opts - Paging ({@link PaginateOptions}) plus `resourceId` / `isActive` filters.\n\t * @returns An `AsyncIterable<Chain>` yielding each chain across pages.\n\t * @example\n\t * ```ts\n\t * for await (const c of era.chains.list({ resourceId: 'res_0000000000', isActive: true })) {\n\t * console.log(c.id, c.name);\n\t * }\n\t * ```\n\t */\n\tlist(opts?: ListChainsOptions): AsyncIterable<Chain>;\n\t/**\n\t * Fetch a single chain envelope by id (`GET /api/chains/{id}`).\n\t *\n\t * @param id - Chain id.\n\t * @returns The {@link Chain}.\n\t * @throws {@link EraAPIError} 404 when no chain with that id is visible to the caller.\n\t */\n\tget(id: string, opts?: RequestOptions): Promise<Chain>;\n\t/**\n\t * Update a chain's editable metadata — name / description / modality /\n\t * `isActive` (`PUT /api/chains/{id}`). Only supplied fields change; this does\n\t * not touch versions or spec content.\n\t *\n\t * @param id - Chain id.\n\t * @param patch - Fields to change (see {@link UpdateChainInput}).\n\t * @returns The updated {@link Chain}.\n\t * @throws {@link EraAPIError} 404 (unknown chain), 400 (invalid patch).\n\t * @remarks Toggling `isActive: false` deactivates the chain; deactivated chains are excluded from `list({ isActive: true })`.\n\t */\n\tupdate(\n\t\tid: string,\n\t\tpatch: UpdateChainInput,\n\t\topts?: RequestOptions,\n\t): Promise<Chain>;\n\t/**\n\t * Delete a chain and all its versions (`DELETE /api/chains/{id}`).\n\t *\n\t * @param id - Chain id.\n\t * @returns Resolves once the delete succeeds.\n\t * @throws {@link EraAPIError} 404 when the chain does not exist.\n\t * @remarks Irreversible — removes the chain envelope and every draft/published version.\n\t */\n\tdelete(id: string, opts?: RequestOptions): Promise<void>;\n\n\t// ── Version lifecycle (mirrors the experience publishing state machine) ──\n\t/**\n\t * Add a new draft version to an existing chain\n\t * (`POST /api/chains/{id}/publishing-versions`). The draft is not live until\n\t * it is submitted, approved, and published.\n\t *\n\t * @param id - Chain id.\n\t * @param spec - The new version's {@link ChainSpec} content.\n\t * @returns The created draft {@link ChainVersion}.\n\t * @throws {@link EraAPIError} 404 (unknown chain), 400 (invalid spec).\n\t */\n\tcreateVersion(\n\t\tid: string,\n\t\tspec: ChainSpec,\n\t\topts?: RequestOptions,\n\t): Promise<ChainVersion>;\n\t/**\n\t * Offset-paginated list of a chain's publishing versions, newest first\n\t * (`GET /api/chains/{id}/publishing-versions`).\n\t *\n\t * @param id - Chain id.\n\t * @param opts - `limit` / `offset` page controls and an optional abort `signal`.\n\t * @returns `{ versions, hasMore }` — the page of {@link ChainVersion}s and whether more remain.\n\t * @throws {@link EraAPIError} 404 when the chain does not exist.\n\t */\n\tlistVersions(\n\t\tid: string,\n\t\topts?: { limit?: number; offset?: number; signal?: AbortSignal },\n\t): Promise<{ versions: ChainVersion[]; hasMore: boolean }>;\n\t/**\n\t * Edit a draft version's spec content\n\t * (`PUT /api/chain-versions/{versionId}`), guarded by an optimistic lock.\n\t *\n\t * @param versionId - Draft version id.\n\t * @param spec - Replacement {@link ChainSpec} content.\n\t * @param ifMatch - The version's current `updatedAt`, sent as `If-Match`.\n\t * @returns The updated {@link ChainVersion}.\n\t * @throws {@link EraAPIError} 409 when `ifMatch` is stale (someone else edited the version) or the version is not an editable draft, 404 (unknown version), 400 (invalid spec).\n\t * @remarks Only draft versions are editable. Pass the `updatedAt` you last read; a mismatch means concurrent modification and must be re-read and retried.\n\t */\n\tupdateVersion(\n\t\tversionId: string,\n\t\tspec: ChainSpec,\n\t\tifMatch: string,\n\t\topts?: RequestOptions,\n\t): Promise<ChainVersion>;\n\t/**\n\t * Move a draft version into review\n\t * (`POST /api/chains/{id}/publishing-versions/{vId}/submit-for-review`).\n\t *\n\t * @param id - Chain id.\n\t * @param versionId - Draft version id to submit.\n\t * @returns The version, now in the review state.\n\t * @throws {@link EraAPIError} 404 (unknown chain/version), 409 (version not in a submittable state).\n\t */\n\tsubmitForReview(\n\t\tid: string,\n\t\tversionId: string,\n\t\topts?: RequestOptions,\n\t): Promise<ChainVersion>;\n\t/**\n\t * Approve a version in review and publish it\n\t * (`POST /api/chains/{id}/publishing-versions/{vId}/approve-review`).\n\t *\n\t * @param id - Chain id.\n\t * @param versionId - Version id under review.\n\t * @returns The now-published {@link ChainVersion}.\n\t * @throws {@link EraAPIError} 404 (unknown chain/version), 409 (version not in review), 400 (unresolved agent/tool references at publish).\n\t * @remarks Supersedes the chain's prior published version — new runs pin to this one. Publishing pins each `{ ref }` agent and `defaultAgent` to a concrete experience version and validates tool steps against the enabled-spindle palette.\n\t */\n\tapproveReview(\n\t\tid: string,\n\t\tversionId: string,\n\t\topts?: RequestOptions,\n\t): Promise<ChainVersion>;\n\t/**\n\t * Reject a version in review, returning it to draft\n\t * (`POST /api/chains/{id}/publishing-versions/{vId}/reject-review`).\n\t *\n\t * @param id - Chain id.\n\t * @param versionId - Version id under review.\n\t * @param input - Optional rejection reason (see {@link RejectReviewInput}).\n\t * @returns The version, back in the draft state.\n\t * @throws {@link EraAPIError} 404 (unknown chain/version), 409 (version not in review).\n\t */\n\trejectReview(\n\t\tid: string,\n\t\tversionId: string,\n\t\tinput?: RejectReviewInput,\n\t\topts?: RequestOptions,\n\t): Promise<ChainVersion>;\n\t/**\n\t * Re-point the chain's published pointer to an earlier version\n\t * (`POST /api/chains/{id}/rollback`). Does not create a new draft — it simply\n\t * moves the published pointer.\n\t *\n\t * @param id - Chain id.\n\t * @param input - `{ toVersionId }` — the previously-published version to restore.\n\t * @returns The updated {@link Chain} with the re-pointed published version.\n\t * @throws {@link EraAPIError} 404 (unknown chain/version), 409 (target version is not in a restorable state), 400 (invalid request).\n\t */\n\trollback(\n\t\tid: string,\n\t\tinput: { toVersionId: string },\n\t\topts?: RequestOptions,\n\t): Promise<Chain>;\n\n\t// ── Runtime (ingress orchestrator; the SSE source) ──\n\t/**\n\t * Start a run and stream its {@link ChainEvent}s (`POST /runs`, SSE). Pass a\n\t * chain `id` to run its published version, or an inline {@link ChainSpec} to\n\t * run without creating/publishing first.\n\t *\n\t * @param idOrSpec - A chain id (runs the published version, or `opts.versionId`) or an inline spec.\n\t * @param opts - Run options — `session` is required; see {@link RunChainOptions}.\n\t * @returns A {@link ChainRun}: an `AsyncIterable<ChainEvent>` with `.chainRunId`, `.result`, and `.meta()`.\n\t * @throws {@link EraChainError} when the run fails mid-stream (surfaced on iteration and on `.result`).\n\t * @throws {@link EraAPIError} when the run cannot start — e.g. 404 (unknown chain), 409 (chain has no published version to run), 422 (invalid spec/body) — surfaced when you begin iterating.\n\t * @example\n\t * ```ts\n\t * const run = era.chains.run('chn_0000000000', {\n\t * session: 'ses_0000000000',\n\t * input: { topic: 'Mars colonization' },\n\t * });\n\t * for await (const ev of run) {\n\t * if (ev.type === 'step_completed') console.log('done:', ev.stepId);\n\t * }\n\t * const { status, output } = await run.result;\n\t * ```\n\t * @remarks `versionId` is only honored when running by id; it is ignored for an inline spec.\n\t */\n\trun(idOrSpec: string | ChainSpec, opts: RunChainOptions): ChainRun;\n\t/**\n\t * Re-attach to a durable run and continue streaming\n\t * (`GET /runs/{runId}/events`, SSE). Replays buffered events after `afterSeq`,\n\t * then tails live — useful after a client disconnect on a long-running chain.\n\t *\n\t * @param runId - The durable run id.\n\t * @param opts - `afterSeq` to replay only events after that sequence number, plus an abort `signal`.\n\t * @returns A {@link ChainRun} over the resumed stream.\n\t * @throws {@link EraAPIError} 404 when the run is unknown or not visible to the caller — surfaced when you begin iterating.\n\t */\n\tresume(\n\t\trunId: string,\n\t\topts?: { afterSeq?: number; signal?: AbortSignal },\n\t): ChainRun;\n\t/**\n\t * Deliver a callback into a run suspended on `waitForEvent` or `handoff`\n\t * (`POST /runs/{runId}/events/{event}`) — e.g. a button press, webhook, or\n\t * human approval. Resumes the run if it was parked on that event name.\n\t *\n\t * @param runId - The durable run id.\n\t * @param event - The event name the run is (or will be) awaiting.\n\t * @param payload - Optional JSON payload delivered with the event.\n\t * @returns Resolves once the event is accepted.\n\t * @throws {@link EraAPIError} 404 when the run is unknown or not visible to the caller, 422 for a malformed request.\n\t * @example\n\t * ```ts\n\t * await era.chains.send('run_0000000000', 'approval_received', { approvedBy: 'usr_0000000000' });\n\t * ```\n\t */\n\tsend(\n\t\trunId: string,\n\t\tevent: string,\n\t\tpayload?: unknown,\n\t\topts?: RequestOptions,\n\t): Promise<void>;\n\t/**\n\t * Fetch a durable run's current state without re-attaching to the stream\n\t * (`GET /runs/{runId}`) — status, per-step results, awaited-event descriptor,\n\t * and folded delivery receipts. The polling counterpart to streaming.\n\t *\n\t * @param runId - The durable run id.\n\t * @returns The {@link ChainRunState} snapshot.\n\t * @throws {@link EraAPIError} 404 when the run is unknown or not visible to the caller, 422 for a malformed request.\n\t */\n\tgetRun(runId: string, opts?: RequestOptions): Promise<ChainRunState>;\n\t/**\n\t * Cancel an in-flight run (`POST /runs/{runId}/cancel`). The run settles with\n\t * `status: 'canceled'`.\n\t *\n\t * @param runId - The durable run id.\n\t * @returns Resolves once the cancel is accepted.\n\t * @throws {@link EraAPIError} 404 when the run is unknown or not visible to the caller, 422 for a malformed request.\n\t */\n\tcancel(runId: string, opts?: RequestOptions): Promise<void>;\n}\n\n// ─── Internal: paths + helpers ──────────────────────────────────────────────────\n\ntype HubRequestInit = RequestOptions & { method?: string; body?: unknown };\n\n/** Forward per-request overrides (signal / headers) into a generated-op call. */\nfunction passThrough(opts?: RequestOptions): {\n\tsignal?: AbortSignal;\n\theaders?: Record<string, string>;\n} {\n\tconst out: { signal?: AbortSignal; headers?: Record<string, string> } = {};\n\tif (opts?.signal !== undefined) out.signal = opts.signal;\n\tif (opts?.headers !== undefined) out.headers = opts.headers;\n\treturn out;\n}\n\ninterface ChainsEnvelope extends PageEnvelope<Chain> {\n\titems: Chain[];\n}\n\ninterface ListVersionsEnvelope {\n\tversions: ChainVersion[];\n\tpagination?: {\n\t\ttotal: number;\n\t\tlimit: number;\n\t\toffset: number;\n\t\thasMore: boolean;\n\t};\n}\n\nfunction buildVersionsQuery(opts?: {\n\tlimit?: number;\n\toffset?: number;\n}): string {\n\tconst params = new URLSearchParams();\n\tif (opts?.limit !== undefined) params.set('limit', String(opts.limit));\n\tif (opts?.offset !== undefined) params.set('offset', String(opts.offset));\n\tconst qs = params.toString();\n\treturn qs ? `?${qs}` : '';\n}\n\n// ─── Chain SSE → ChainEvent parser ──────────────────────────────────────────────\n//\n// The orchestrator emits chain-native events (the generated `ChainStreamEvent`\n// discriminated union, ERA-6209 — framing + output receipts + event_delivered)\n// interleaved with nested agent events (start / chunk / tool_call /\n// tool_result / done / error) that carry a `step_id` and reuse the chat SSE\n// event shapes. The core `parseSSE` only knows the fixed StreamEvent set and\n// would drop the framing events, so chains parse the wire locally: native\n// events are validated with the generated `zChainStreamEvent` schema and\n// key-normalized snake_case → camelCase; agent events are decoded from the\n// raw wire shape, mirroring `core/streaming.ts`.\n\n/** Discriminators of the chain-native `ChainStreamEvent` union (ERA-6209). */\nconst CHAIN_NATIVE_EVENT_TYPES = new Set<string>([\n\t'run_started',\n\t'step_started',\n\t'step_completed',\n\t'step_failed',\n\t'awaiting_event',\n\t'handed_off',\n\t'output_emitted',\n\t'output_delivered',\n\t'output_failed',\n\t'output_undeliverable',\n\t'output_expired',\n\t'event_delivered',\n\t'run_done',\n\t'run_error',\n]);\n\n/** Raw wire shape of the interleaved NESTED AGENT events only — the\n * chain-native events are typed by the generated `ChainStreamEvent`. */\ninterface RawChainWire {\n\ttype?: string;\n\tstep_id?: string;\n\t// nested agent-event fields (subset of the chat SSE wire)\n\tcontent?: string;\n\ttool_name?: string;\n\ttool_call_id?: string;\n\t/** ERA-6208 wire truth: tool results ride under `tool_result` (never `result`). */\n\ttool_result?: string | null;\n\t/**\n\t * 0-based index of a `tool_result_chunk` slice (generated\n\t * `ChatToolResultChunkStreamEvent.chunk_index`; ERA-873, flag-off today).\n\t */\n\tchunk_index?: number;\n\t/** Whether a `tool_result_chunk` is the final slice (`is_final_chunk`). */\n\tis_final_chunk?: boolean;\n\tsession_id?: string;\n\tusage?: {\n\t\tinput_tokens?: number;\n\t\toutput_tokens?: number;\n\t\tcached_input_tokens?: number;\n\t};\n\tinput_tokens?: number;\n\toutput_tokens?: number;\n\tcached_input_tokens?: number;\n\tcost_cents?: number;\n\tbalance_cents?: number;\n\t/** Names of tools invoked during the turn, on the nested `done` event. */\n\ttools_used?: string[] | null;\n\terror?: string;\n\t/** ERA-6208 wire truth: machine codes ride under `error_code` (never `code`). */\n\terror_code?: string | null;\n}\n\nfunction normalizeUsage(raw: RawChainWire) {\n\tconst input = raw.usage?.input_tokens ?? raw.input_tokens;\n\tconst output = raw.usage?.output_tokens ?? raw.output_tokens;\n\tconst cached = raw.usage?.cached_input_tokens ?? raw.cached_input_tokens;\n\tif (input === undefined && output === undefined) return undefined;\n\tconst usage: {\n\t\tinputTokens: number;\n\t\toutputTokens: number;\n\t\tcachedInputTokens?: number;\n\t} = { inputTokens: input ?? 0, outputTokens: output ?? 0 };\n\tif (cached !== undefined) usage.cachedInputTokens = cached;\n\treturn usage;\n}\n\n/**\n * Translate one spec-validated chain-native event into its camelCase\n * {@link ChainEvent} form. Purely a key-casing map — the shapes are the\n * generated `ChainStreamEvent` components (ERA-6209). Optional-nullable wire\n * fields are spread only when non-null so consumers keep the ergonomic\n * \"absent means absent\" contract.\n */\nfunction translateNative(ev: GenChainStreamEvent): ChainEvent {\n\tswitch (ev.type) {\n\t\tcase 'run_started':\n\t\t\treturn { type: 'run_started', runId: ev.run_id, kernelId: ev.kernel_id };\n\t\tcase 'step_started':\n\t\t\treturn {\n\t\t\t\ttype: 'step_started',\n\t\t\t\tstepId: ev.step_id,\n\t\t\t\t// Declared narrowing: spec says `string`; the SDK keys this to its\n\t\t\t\t// step grammar. Unknown future step types still flow through.\n\t\t\t\tstepType: ev.step_type as ChainStep['type'],\n\t\t\t\tfateId: ev.fate_id,\n\t\t\t};\n\t\tcase 'step_completed':\n\t\t\treturn {\n\t\t\t\ttype: 'step_completed',\n\t\t\t\tstepId: ev.step_id,\n\t\t\t\toutput: ev.output,\n\t\t\t\t// ERA-5816 tool-step execution metadata — forwarded since v17.6.0.\n\t\t\t\t...(ev.metadata != null && { metadata: ev.metadata }),\n\t\t\t};\n\t\tcase 'step_failed':\n\t\t\treturn {\n\t\t\t\ttype: 'step_failed',\n\t\t\t\tstepId: ev.step_id,\n\t\t\t\terror: ev.error,\n\t\t\t\t...(ev.error_code != null && { errorCode: ev.error_code }),\n\t\t\t};\n\t\tcase 'safety_blocked':\n\t\t\treturn {\n\t\t\t\ttype: 'safety_blocked',\n\t\t\t\tstepId: ev.step_id,\n\t\t\t\t...(ev.category != null && { category: ev.category }),\n\t\t\t\t...(ev.stage != null && { stage: ev.stage }),\n\t\t\t};\n\t\tcase 'awaiting_event':\n\t\t\treturn {\n\t\t\t\ttype: 'awaiting_event',\n\t\t\t\tstepId: ev.step_id,\n\t\t\t\tevent: ev.event,\n\t\t\t\t...(ev.deadline_ms != null && { deadlineMs: ev.deadline_ms }),\n\t\t\t};\n\t\tcase 'handed_off':\n\t\t\treturn { type: 'handed_off', stepId: ev.step_id, to: ev.to };\n\t\tcase 'output_emitted':\n\t\t\treturn {\n\t\t\t\ttype: 'output_emitted',\n\t\t\t\ttransport: ev.transport,\n\t\t\t\tlifecycle: ev.lifecycle,\n\t\t\t\tstatus: ev.status,\n\t\t\t\t...(ev.target != null && { target: ev.target }),\n\t\t\t\t...(ev.error != null && { error: ev.error }),\n\t\t\t\t...(ev.fields != null && { fields: ev.fields }),\n\t\t\t\t...(ev.receipt_status != null && { receiptStatus: ev.receipt_status }),\n\t\t\t\t...(ev.receipt_token != null && { receiptToken: ev.receipt_token }),\n\t\t\t\t...(ev.idempotency_key != null && {\n\t\t\t\t\tidempotencyKey: ev.idempotency_key,\n\t\t\t\t}),\n\t\t\t\t...(ev.device_id != null && { deviceId: ev.device_id }),\n\t\t\t\t...(ev.actuator_id != null && { actuatorId: ev.actuator_id }),\n\t\t\t\t...(ev.seq != null && { seq: ev.seq }),\n\t\t\t};\n\t\tcase 'output_delivered':\n\t\t\treturn {\n\t\t\t\ttype: 'output_delivered',\n\t\t\t\treceiptToken: ev.receipt_token,\n\t\t\t\t...(ev.idempotency_key != null && {\n\t\t\t\t\tidempotencyKey: ev.idempotency_key,\n\t\t\t\t}),\n\t\t\t\t...(ev.detail != null && { detail: ev.detail }),\n\t\t\t};\n\t\tcase 'output_failed':\n\t\t\treturn {\n\t\t\t\ttype: 'output_failed',\n\t\t\t\treceiptToken: ev.receipt_token,\n\t\t\t\t...(ev.idempotency_key != null && {\n\t\t\t\t\tidempotencyKey: ev.idempotency_key,\n\t\t\t\t}),\n\t\t\t\t...(ev.error != null && { error: ev.error }),\n\t\t\t};\n\t\tcase 'output_undeliverable':\n\t\t\treturn {\n\t\t\t\ttype: 'output_undeliverable',\n\t\t\t\treceiptToken: ev.receipt_token,\n\t\t\t\t...(ev.idempotency_key != null && {\n\t\t\t\t\tidempotencyKey: ev.idempotency_key,\n\t\t\t\t}),\n\t\t\t\t...(ev.error != null && { error: ev.error }),\n\t\t\t};\n\t\tcase 'output_expired':\n\t\t\treturn {\n\t\t\t\ttype: 'output_expired',\n\t\t\t\treceiptToken: ev.receipt_token,\n\t\t\t\t...(ev.idempotency_key != null && {\n\t\t\t\t\tidempotencyKey: ev.idempotency_key,\n\t\t\t\t}),\n\t\t\t};\n\t\tcase 'event_delivered':\n\t\t\t// ERA-6209: an external callback resumed a run parked on awaiting_event.\n\t\t\treturn {\n\t\t\t\ttype: 'event_delivered',\n\t\t\t\tevent: ev.event,\n\t\t\t\t...(ev.payload !== undefined && { payload: ev.payload }),\n\t\t\t};\n\t\tcase 'run_done':\n\t\t\treturn {\n\t\t\t\ttype: 'run_done',\n\t\t\t\trunId: ev.run_id,\n\t\t\t\t// Declared narrowing: spec types `status` as string; the docstring\n\t\t\t\t// enumerates exactly these terminal values.\n\t\t\t\tstatus:\n\t\t\t\t\tev.status === 'failed' || ev.status === 'canceled'\n\t\t\t\t\t\t? ev.status\n\t\t\t\t\t\t: 'succeeded',\n\t\t\t\toutput: ev.output,\n\t\t\t\t...(ev.cost_cents != null && { costCents: ev.cost_cents }),\n\t\t\t\t...(ev.balance_cents != null && { balanceCents: ev.balance_cents }),\n\t\t\t};\n\t\tcase 'run_error':\n\t\t\treturn {\n\t\t\t\ttype: 'run_error',\n\t\t\t\terror: ev.error,\n\t\t\t\t...(ev.error_code != null && { errorCode: ev.error_code }),\n\t\t\t};\n\t}\n}\n\n/** Translate one raw nested-agent wire object into a typed ChainEvent (or null to skip). */\nfunction translateChain(\n\traw: RawChainWire,\n\tacc: Map<string, string>,\n): ChainEvent | null {\n\tconst stepId = raw.step_id ?? '';\n\tswitch (raw.type) {\n\t\t// ── nested agent events (re-tagged with stepId) ──\n\t\tcase 'start':\n\t\t\treturn {\n\t\t\t\ttype: 'start',\n\t\t\t\t...(raw.session_id !== undefined && { sessionId: raw.session_id }),\n\t\t\t\tstepId,\n\t\t\t};\n\t\tcase 'interim':\n\t\t\treturn { type: 'interim', delta: raw.content ?? '', stepId };\n\t\tcase 'chunk': {\n\t\t\tconst delta = raw.content ?? '';\n\t\t\tconst prev = acc.get(stepId);\n\t\t\t// ERA-4887: cap the step-tracking map and per-step content length.\n\t\t\t// Without this, a misbehaving server (or compromised one) could\n\t\t\t// flood the consumer with distinct step IDs or a single\n\t\t\t// runaway-length step, forcing unbounded memory.\n\t\t\tif (prev === undefined && acc.size >= MAX_STEPS_TRACKED) {\n\t\t\t\t// Too many distinct steps tracked — don't add this one. Still\n\t\t\t\t// emit the chunk so the caller sees content; fullContent is\n\t\t\t\t// just `delta` since we have no accumulator state for it.\n\t\t\t\treturn { type: 'chunk', delta, fullContent: delta, stepId };\n\t\t\t}\n\t\t\tif (prev !== undefined && prev.length >= MAX_STEP_CONTENT_BYTES) {\n\t\t\t\t// Per-step content cap already reached — emit chunk but don't\n\t\t\t\t// grow the accumulator further.\n\t\t\t\treturn { type: 'chunk', delta, fullContent: prev, stepId };\n\t\t\t}\n\t\t\tconst candidate = (prev ?? '') + delta;\n\t\t\tconst fullContent =\n\t\t\t\tcandidate.length > MAX_STEP_CONTENT_BYTES\n\t\t\t\t\t? candidate.slice(0, MAX_STEP_CONTENT_BYTES)\n\t\t\t\t\t: candidate;\n\t\t\tacc.set(stepId, fullContent);\n\t\t\treturn { type: 'chunk', delta, fullContent, stepId };\n\t\t}\n\t\tcase 'tool_call':\n\t\t\treturn {\n\t\t\t\ttype: 'tool_call',\n\t\t\t\tname: raw.tool_name ?? '',\n\t\t\t\ttoolCallId: raw.tool_call_id ?? '',\n\t\t\t\tstepId,\n\t\t\t};\n\t\tcase 'tool_result':\n\t\t\treturn {\n\t\t\t\ttype: 'tool_result',\n\t\t\t\ttoolCallId: raw.tool_call_id ?? '',\n\t\t\t\t// ERA-6208: the wire key is `tool_result` (omitted when the result\n\t\t\t\t// is null), not `result`.\n\t\t\t\tresult: raw.tool_result,\n\t\t\t\tstepId,\n\t\t\t};\n\t\tcase 'tool_result_chunk':\n\t\t\t// ERA-873: a partial slice of a large tool result (generated\n\t\t\t// ChatToolResultChunkStreamEvent, feature-flag-off server-side today).\n\t\t\t// Mirrors the core decoder (core/streaming.ts), re-tagged with stepId —\n\t\t\t// without this case the frame would be silently dropped the moment the\n\t\t\t// server flag flips on, despite ChainEvent's type advertising it.\n\t\t\treturn {\n\t\t\t\ttype: 'tool_result_chunk',\n\t\t\t\ttoolCallId: raw.tool_call_id ?? '',\n\t\t\t\tchunkIndex: raw.chunk_index ?? 0,\n\t\t\t\tisFinalChunk: raw.is_final_chunk ?? false,\n\t\t\t\ttoolResult: raw.tool_result ?? '',\n\t\t\t\tstepId,\n\t\t\t};\n\t\tcase 'done': {\n\t\t\tconst usage = normalizeUsage(raw);\n\t\t\tconst fullContent = acc.get(stepId) ?? '';\n\t\t\treturn {\n\t\t\t\ttype: 'done',\n\t\t\t\tfullContent,\n\t\t\t\t// ERA-6208: the nested `done` carries `tools_used` on the wire.\n\t\t\t\ttoolsUsed: raw.tools_used ?? [],\n\t\t\t\t...(usage && { usage }),\n\t\t\t\t...(raw.cost_cents !== undefined && { costCents: raw.cost_cents }),\n\t\t\t\t...(raw.balance_cents !== undefined && {\n\t\t\t\t\tbalanceCents: raw.balance_cents,\n\t\t\t\t}),\n\t\t\t\tstepId,\n\t\t\t};\n\t\t}\n\t\tcase 'error':\n\t\t\treturn {\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage: raw.error ?? 'stream error',\n\t\t\t\t// ERA-6208: the wire key is `error_code`, not `code`.\n\t\t\t\t...(raw.error_code != null && { code: raw.error_code }),\n\t\t\t\tstepId,\n\t\t\t};\n\t\tdefault:\n\t\t\treturn null;\n\t}\n}\n\n/**\n * Hard caps for the chain SSE parser.\n *\n * - `MAX_SSE_LINE_BYTES_CHAIN` (1 MiB): bound the line buffer. Mirrors\n * `core/streaming.ts:MAX_SSE_LINE_BYTES`. A server that ships bytes\n * without a newline triggers an `SSE_LINE_TOO_LONG` `run_error` and\n * the stream closes.\n * - `MAX_STEPS_TRACKED` (1024): cap on distinct step IDs the accumulator\n * tracks. New step IDs past the cap still get their `chunk` events\n * forwarded — they just no longer accumulate `fullContent` across\n * chunks (caller sees `fullContent === delta`).\n * - `MAX_STEP_CONTENT_BYTES` (4 MiB): per-step accumulator cap. A\n * runaway step truncates at this length; subsequent chunks emit but\n * don't grow the accumulator.\n *\n * Exported as `__test` for unit tests that need to drive the cap without\n * fabricating multi-MiB fixtures.\n *\n * @internal Parser implementation detail — not part of the public API surface.\n */\nexport const MAX_SSE_LINE_BYTES_CHAIN = 1_048_576;\n/** @internal Chain SSE parser cap — see {@link MAX_SSE_LINE_BYTES_CHAIN}. */\nexport const MAX_STEPS_TRACKED = 1024;\n/** @internal Chain SSE parser cap — see {@link MAX_SSE_LINE_BYTES_CHAIN}. */\nexport const MAX_STEP_CONTENT_BYTES = 4 * 1_048_576;\n/** @internal — test re-exports */\nexport const __testCaps = {\n\tMAX_SSE_LINE_BYTES_CHAIN,\n\tMAX_STEPS_TRACKED,\n\tMAX_STEP_CONTENT_BYTES,\n};\n\n/** Parse a chain SSE Response body into typed ChainEvents. */\nasync function* parseChainSSE(\n\tbody: ReadableStream<Uint8Array>,\n): AsyncGenerator<ChainEvent, void, void> {\n\tconst reader = body.getReader();\n\tconst decoder = new TextDecoder();\n\tconst acc = new Map<string, string>();\n\tlet buffer = '';\n\n\tfunction lineToEvent(line: string): ChainEvent | null {\n\t\tconst trimmed = line.endsWith('\\r') ? line.slice(0, -1) : line;\n\t\tif (trimmed.length === 0 || trimmed.startsWith(':')) return null;\n\t\tif (!trimmed.startsWith('data:')) return null;\n\t\tlet payload = trimmed.slice(5);\n\t\tif (payload.startsWith(' ')) payload = payload.slice(1);\n\t\tif (payload.length === 0) return null;\n\t\tlet raw: RawChainWire;\n\t\ttry {\n\t\t\traw = JSON.parse(payload) as RawChainWire;\n\t\t} catch {\n\t\t\treturn {\n\t\t\t\ttype: 'run_error',\n\t\t\t\terror: 'parse failed',\n\t\t\t\terrorCode: 'SSE_PARSE_ERROR',\n\t\t\t};\n\t\t}\n\t\tif (!raw.type) return null;\n\t\tif (CHAIN_NATIVE_EVENT_TYPES.has(raw.type)) {\n\t\t\t// ERA-6209: chain-native events are validated against the generated\n\t\t\t// spec schema; a frame that fails validation is dropped (it cannot be\n\t\t\t// safely interpreted), matching the unknown-frame no-op below.\n\t\t\tconst parsed = zChainStreamEvent.safeParse(raw);\n\t\t\treturn parsed.success ? translateNative(parsed.data) : null;\n\t\t}\n\t\treturn translateChain(raw, acc);\n\t}\n\n\ttry {\n\t\twhile (true) {\n\t\t\tlet chunk: ReadableStreamReadResult<Uint8Array>;\n\t\t\ttry {\n\t\t\t\tchunk = await reader.read();\n\t\t\t} catch (err) {\n\t\t\t\tyield {\n\t\t\t\t\ttype: 'run_error',\n\t\t\t\t\terror: err instanceof Error ? err.message : 'stream read failed',\n\t\t\t\t\terrorCode: 'STREAM_DROPPED',\n\t\t\t\t};\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (chunk.done) break;\n\t\t\tbuffer += decoder.decode(chunk.value, { stream: true });\n\t\t\t// ERA-4887: line-buffer cap. A server that ships bytes without a\n\t\t\t// newline would otherwise grow `buffer` unboundedly. Emit a\n\t\t\t// run_error and close.\n\t\t\tif (buffer.length > MAX_SSE_LINE_BYTES_CHAIN) {\n\t\t\t\tyield {\n\t\t\t\t\ttype: 'run_error',\n\t\t\t\t\terror: `SSE line exceeded ${MAX_SSE_LINE_BYTES_CHAIN} bytes without a newline; closing stream`,\n\t\t\t\t\terrorCode: 'SSE_LINE_TOO_LONG',\n\t\t\t\t};\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst parts = buffer.split('\\n');\n\t\t\tbuffer = parts.pop() ?? '';\n\t\t\tfor (const line of parts) {\n\t\t\t\tconst ev = lineToEvent(line);\n\t\t\t\tif (ev) yield ev;\n\t\t\t}\n\t\t}\n\t\tif (buffer.length > 0) {\n\t\t\tconst ev = lineToEvent(buffer);\n\t\t\tif (ev) yield ev;\n\t\t}\n\t} finally {\n\t\ttry {\n\t\t\treader.releaseLock();\n\t\t} catch {\n\t\t\t/* already released */\n\t\t}\n\t}\n}\n\n/**\n * Wrap a chain SSE Response into a controllable {@link ChainRun}. The\n * AbortSignal is threaded into the underlying `client.fetch` by the caller.\n */\nfunction makeChainRun(responsePromise: Promise<Response>): ChainRun {\n\tlet resolveRunId!: (v: string) => void;\n\tlet rejectRunId!: (e: unknown) => void;\n\tconst runIdPromise = new Promise<string>((res, rej) => {\n\t\tresolveRunId = res;\n\t\trejectRunId = rej;\n\t});\n\tlet resolveResult!: (v: ChainRunResult) => void;\n\tlet rejectResult!: (e: unknown) => void;\n\tconst resultPromise = new Promise<ChainRunResult>((res, rej) => {\n\t\tresolveResult = res;\n\t\trejectResult = rej;\n\t});\n\t// Swallow unhandled-rejection noise if a consumer never reads these.\n\trunIdPromise.catch(() => {});\n\tresultPromise.catch(() => {});\n\n\tconst aggregate: ChainRunMeta = {\n\t\ttotalCost: 0,\n\t\trequestIds: [],\n\t\tsteps: 0,\n\t};\n\tlet metaResolved = false;\n\tlet resolveMeta!: (m: ChainRunMeta) => void;\n\tconst metaPromise = new Promise<ChainRunMeta>((res) => {\n\t\tresolveMeta = res;\n\t});\n\n\tasync function* iterate(): AsyncGenerator<ChainEvent, void, void> {\n\t\tlet runId = '';\n\t\tlet settledResult = false;\n\t\ttry {\n\t\t\tconst response = await responsePromise;\n\t\t\t// ERA-5883: the chain-run POST that opened this SSE stream carries the\n\t\t\t// echoed traceparent — attach it to any error from this run.\n\t\t\tconst traceparent = response.headers.get('traceparent') ?? undefined;\n\t\t\tif (!response.body) {\n\t\t\t\tthrow new EraChainError({\n\t\t\t\t\tstatus: response.status,\n\t\t\t\t\tstatusText: response.statusText,\n\t\t\t\t\tmessage: 'chain run response has no body',\n\t\t\t\t\traw: null,\n\t\t\t\t\ttraceparent,\n\t\t\t\t});\n\t\t\t}\n\t\t\tfor await (const ev of parseChainSSE(response.body)) {\n\t\t\t\tif (ev.type === 'run_started') {\n\t\t\t\t\trunId = ev.runId;\n\t\t\t\t\tresolveRunId(ev.runId);\n\t\t\t\t} else if (ev.type === 'step_started') {\n\t\t\t\t\taggregate.steps += 1;\n\t\t\t\t\tif (ev.fateId) aggregate.requestIds.push(ev.fateId);\n\t\t\t\t} else if (ev.type === 'run_done') {\n\t\t\t\t\tsettledResult = true;\n\t\t\t\t\t// ERA-4326 wire truth: `run_done` is the ONLY chain-native\n\t\t\t\t\t// ChainStreamEvent component carrying cost/balance, and its\n\t\t\t\t\t// `cost_cents` is documented as the AGGREGATE charge across the\n\t\t\t\t\t// whole run (not a per-step delta) — so ASSIGN, never `+=`:\n\t\t\t\t\t// parseChainSSE does not terminate on run_done, so a duplicated\n\t\t\t\t\t// terminal frame would double-count an already-aggregated figure.\n\t\t\t\t\t// Last write wins for both fields (balance_cents is the\n\t\t\t\t\t// post-charge snapshot). An absent cost means \"no cost\n\t\t\t\t\t// available\", not \"free\" — leave totalCost at 0 in that case.\n\t\t\t\t\tif (ev.costCents != null) aggregate.totalCost = ev.costCents;\n\t\t\t\t\tif (ev.balanceCents != null) aggregate.lastBalance = ev.balanceCents;\n\t\t\t\t\tresolveResult({ status: ev.status, output: ev.output });\n\t\t\t\t} else if (ev.type === 'run_error') {\n\t\t\t\t\tsettledResult = true;\n\t\t\t\t\trejectResult(\n\t\t\t\t\t\tnew EraChainError({\n\t\t\t\t\t\t\tstatus: 0,\n\t\t\t\t\t\t\tstatusText: 'chain_error',\n\t\t\t\t\t\t\tmessage: ev.error,\n\t\t\t\t\t\t\traw: ev,\n\t\t\t\t\t\t\tchainRunId: runId || undefined,\n\t\t\t\t\t\t\ttraceparent,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tyield ev;\n\t\t\t}\n\t\t} finally {\n\t\t\tif (!runId) rejectRunId(new Error('chain run ended before run_started'));\n\t\t\tif (!settledResult) {\n\t\t\t\trejectResult(new Error('chain run ended before run_done'));\n\t\t\t}\n\t\t\tif (!metaResolved) {\n\t\t\t\tmetaResolved = true;\n\t\t\t\tresolveMeta({ ...aggregate, requestIds: [...aggregate.requestIds] });\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\tchainRunId: runIdPromise,\n\t\tresult: resultPromise,\n\t\tmeta: () => metaPromise,\n\t\t[Symbol.asyncIterator]: iterate,\n\t};\n}\n\n// ─── Facade ────────────────────────────────────────────────────────────────────\n\n/** Construct the `chains` facade bound to an `EraClient`. */\nexport function chains(client: EraClient): ChainsModule {\n\tfunction streamRun(\n\t\tpath: string,\n\t\tbody: unknown,\n\t\tsignal?: AbortSignal,\n\t): ChainRun {\n\t\tconst opts: RequestOptions & { method?: string; body?: unknown } = {\n\t\t\tmethod: 'POST',\n\t\t\tbody,\n\t\t\theaders: { Accept: 'text/event-stream' },\n\t\t};\n\t\tif (signal) opts.signal = signal;\n\t\treturn makeChainRun(client.fetch('ingress', path, opts));\n\t}\n\n\treturn {\n\t\tcreate(\n\t\t\tspec: ChainSpec,\n\t\t\ttarget: CreateChainTarget,\n\t\t\topts?: RequestOptions,\n\t\t): Promise<Chain> {\n\t\t\tconst body = {\n\t\t\t\tresourceId: target.resourceId,\n\t\t\t\tname: spec.name,\n\t\t\t\tdescription: spec.description ?? null,\n\t\t\t\tmodality: spec.modality ?? 'auto',\n\t\t\t\tcontent: spec,\n\t\t\t};\n\t\t\tconst reqOpts: HubRequestInit = { ...opts, method: 'POST', body };\n\t\t\treturn client.hub<Chain>(hubPath(genPostChains), reqOpts);\n\t\t},\n\t\tlist(opts?: ListChainsOptions): AsyncIterable<Chain> {\n\t\t\t// Typed against the generated op's query params (the styling.ts idiom)\n\t\t\t// so a filter that isn't in the spec fails to compile. `limit`/`cursor`\n\t\t\t// are omitted — the paginator owns them.\n\t\t\tconst query: Omit<\n\t\t\t\tNonNullable<GenGetChainsData['query']>,\n\t\t\t\t'limit' | 'cursor'\n\t\t\t> = {};\n\t\t\tif (opts?.resourceId !== undefined) query.resourceId = opts.resourceId;\n\t\t\t// The wire param is a 'true'|'false' string enum (query params are\n\t\t\t// strings); the facade keeps the ergonomic boolean.\n\t\t\tif (opts?.isActive !== undefined)\n\t\t\t\tquery.isActive = opts.isActive ? 'true' : 'false';\n\t\t\tconst base = Object.keys(query).length\n\t\t\t\t? hubPath(genGetChains, { query })\n\t\t\t\t: hubPath(genGetChains);\n\t\t\tconst pag: PaginateOptions = {};\n\t\t\tif (opts?.pageSize !== undefined) pag.pageSize = opts.pageSize;\n\t\t\tif (opts?.maxPages !== undefined) pag.maxPages = opts.maxPages;\n\t\t\tif (opts?.cursor !== undefined) pag.cursor = opts.cursor;\n\t\t\tif (opts?.signal !== undefined) pag.signal = opts.signal;\n\t\t\treturn client.paginateAll<ChainsEnvelope, Chain>(\n\t\t\t\t'hub',\n\t\t\t\tbase,\n\t\t\t\t(env) => env.items,\n\t\t\t\tpag,\n\t\t\t);\n\t\t},\n\t\tget(id: string, opts?: RequestOptions): Promise<Chain> {\n\t\t\treturn client.hub<Chain>(\n\t\t\t\thubPath(genGetChainsById, { path: { id } }),\n\t\t\t\topts,\n\t\t\t);\n\t\t},\n\t\tupdate(\n\t\t\tid: string,\n\t\t\tpatch: UpdateChainInput,\n\t\t\topts?: RequestOptions,\n\t\t): Promise<Chain> {\n\t\t\tconst reqOpts: HubRequestInit = { ...opts, method: 'PUT', body: patch };\n\t\t\treturn client.hub<Chain>(\n\t\t\t\thubPath(genPutChainsById, { path: { id } }),\n\t\t\t\treqOpts,\n\t\t\t);\n\t\t},\n\t\tasync delete(id: string, opts?: RequestOptions): Promise<void> {\n\t\t\tconst reqOpts: HubRequestInit = { ...opts, method: 'DELETE' };\n\t\t\tawait client.hub<unknown>(\n\t\t\t\thubPath(genDeleteChainsById, { path: { id } }),\n\t\t\t\treqOpts,\n\t\t\t);\n\t\t},\n\t\tcreateVersion(\n\t\t\tid: string,\n\t\t\tspec: ChainSpec,\n\t\t\topts?: RequestOptions,\n\t\t): Promise<ChainVersion> {\n\t\t\tconst reqOpts: HubRequestInit = {\n\t\t\t\t...opts,\n\t\t\t\tmethod: 'POST',\n\t\t\t\tbody: { content: spec },\n\t\t\t};\n\t\t\treturn client.hub<ChainVersion>(\n\t\t\t\thubPath(genPostChainsByIdPublishingVersions, { path: { id } }),\n\t\t\t\treqOpts,\n\t\t\t);\n\t\t},\n\t\tasync listVersions(\n\t\t\tid: string,\n\t\t\topts?: { limit?: number; offset?: number; signal?: AbortSignal },\n\t\t): Promise<{ versions: ChainVersion[]; hasMore: boolean }> {\n\t\t\tconst reqOpts: RequestOptions | undefined = opts?.signal\n\t\t\t\t? { signal: opts.signal }\n\t\t\t\t: undefined;\n\t\t\tconst env = await client.hub<ListVersionsEnvelope>(\n\t\t\t\t`${hubPath(genGetChainsByIdPublishingVersions, {\n\t\t\t\t\tpath: { id },\n\t\t\t\t})}${buildVersionsQuery(opts)}`,\n\t\t\t\treqOpts,\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tversions: env.versions,\n\t\t\t\thasMore: env.pagination?.hasMore ?? false,\n\t\t\t};\n\t\t},\n\t\tasync updateVersion(\n\t\t\tversionId: string,\n\t\t\tspec: ChainSpec,\n\t\t\tifMatch: string,\n\t\t\topts?: RequestOptions,\n\t\t): Promise<ChainVersion> {\n\t\t\tconst reqOpts: HubRequestInit = {\n\t\t\t\t...opts,\n\t\t\t\tmethod: 'PUT',\n\t\t\t\tbody: { content: spec },\n\t\t\t\theaders: { ...opts?.headers, 'If-Match': ifMatch },\n\t\t\t};\n\t\t\t// The mutation endpoints wrap the row: `{ success, version, auditLogId }`\n\t\t\t// (see the generated PutChainVersionsByVIdResponses) — unwrap so callers\n\t\t\t// get the same ChainVersion shape reads return.\n\t\t\tconst env = await client.hub<GenPutChainVersionsByVIdResponses[200]>(\n\t\t\t\thubPath(genPutChainVersionsByVId, { path: { vId: versionId } }),\n\t\t\t\treqOpts,\n\t\t\t);\n\t\t\treturn env.version;\n\t\t},\n\t\tasync submitForReview(\n\t\t\tid: string,\n\t\t\tversionId: string,\n\t\t\topts?: RequestOptions,\n\t\t): Promise<ChainVersion> {\n\t\t\tconst reqOpts: HubRequestInit = { ...opts, method: 'POST' };\n\t\t\tconst env = await client.hub<GenSubmitForReviewResponses[200]>(\n\t\t\t\thubPath(genPostChainsByIdPublishingVersionsByVIdSubmitForReview, {\n\t\t\t\t\tpath: { id, vId: versionId },\n\t\t\t\t}),\n\t\t\t\treqOpts,\n\t\t\t);\n\t\t\treturn env.version;\n\t\t},\n\t\tasync approveReview(\n\t\t\tid: string,\n\t\t\tversionId: string,\n\t\t\topts?: RequestOptions,\n\t\t): Promise<ChainVersion> {\n\t\t\tconst reqOpts: HubRequestInit = { ...opts, method: 'POST' };\n\t\t\tconst env = await client.hub<GenApproveReviewResponses[200]>(\n\t\t\t\thubPath(genPostChainsByIdPublishingVersionsByVIdApproveReview, {\n\t\t\t\t\tpath: { id, vId: versionId },\n\t\t\t\t}),\n\t\t\t\treqOpts,\n\t\t\t);\n\t\t\treturn env.version;\n\t\t},\n\t\tasync rejectReview(\n\t\t\tid: string,\n\t\t\tversionId: string,\n\t\t\tinput?: RejectReviewInput,\n\t\t\topts?: RequestOptions,\n\t\t): Promise<ChainVersion> {\n\t\t\tconst reqOpts: HubRequestInit = {\n\t\t\t\t...opts,\n\t\t\t\tmethod: 'POST',\n\t\t\t\tbody: input ?? {},\n\t\t\t};\n\t\t\tconst env = await client.hub<GenRejectReviewResponses[200]>(\n\t\t\t\thubPath(genPostChainsByIdPublishingVersionsByVIdRejectReview, {\n\t\t\t\t\tpath: { id, vId: versionId },\n\t\t\t\t}),\n\t\t\t\treqOpts,\n\t\t\t);\n\t\t\treturn env.version;\n\t\t},\n\t\trollback(\n\t\t\tid: string,\n\t\t\tinput: { toVersionId: string },\n\t\t\topts?: RequestOptions,\n\t\t): Promise<Chain> {\n\t\t\tconst reqOpts: HubRequestInit = { ...opts, method: 'POST', body: input };\n\t\t\treturn client.hub<Chain>(\n\t\t\t\thubPath(genPostChainsByIdRollback, { path: { id } }),\n\t\t\t\treqOpts,\n\t\t\t);\n\t\t},\n\t\trun(idOrSpec: string | ChainSpec, opts: RunChainOptions): ChainRun {\n\t\t\t// ERA-5799 collapsed by-id + inline onto a single POST /runs whose\n\t\t\t// body carries `chainId` XOR `spec`. Build the shared body, then set\n\t\t\t// the run source.\n\t\t\tconst body: Record<string, unknown> = { session: opts.session };\n\t\t\tif (opts.input !== undefined) body.input = opts.input;\n\t\t\tif (opts.modality !== undefined) body.modality = opts.modality;\n\t\t\tif (typeof idOrSpec === 'string') {\n\t\t\t\tbody.chainId = idOrSpec;\n\t\t\t\tif (opts.versionId !== undefined) body.versionId = opts.versionId;\n\t\t\t} else {\n\t\t\t\tbody.spec = idOrSpec;\n\t\t\t}\n\t\t\treturn streamRun(ingressPath(genStartRun), body, opts.signal);\n\t\t},\n\t\tresume(\n\t\t\trunId: string,\n\t\t\topts?: { afterSeq?: number; signal?: AbortSignal },\n\t\t): ChainRun {\n\t\t\tconst qs =\n\t\t\t\topts?.afterSeq !== undefined ? `?afterSeq=${opts.afterSeq}` : '';\n\t\t\tconst reqOpts: RequestOptions & { method?: string } = {\n\t\t\t\tmethod: 'GET',\n\t\t\t\theaders: { Accept: 'text/event-stream' },\n\t\t\t};\n\t\t\tif (opts?.signal) reqOpts.signal = opts.signal;\n\t\t\treturn makeChainRun(\n\t\t\t\tclient.fetch(\n\t\t\t\t\t'ingress',\n\t\t\t\t\t`${ingressPath(genGetRunEvents, {\n\t\t\t\t\t\tpath: { run_id: runId },\n\t\t\t\t\t})}${qs}`,\n\t\t\t\t\treqOpts,\n\t\t\t\t),\n\t\t\t);\n\t\t},\n\t\tasync send(\n\t\t\trunId: string,\n\t\t\tevent: string,\n\t\t\tpayload?: unknown,\n\t\t\topts?: RequestOptions,\n\t\t): Promise<void> {\n\t\t\tawait client.callOp('ingress', genDeliverRunEvent, {\n\t\t\t\tpath: { run_id: runId, event },\n\t\t\t\tbody: (payload ?? {}) as GenDeliverEventBody,\n\t\t\t\t...passThrough(opts),\n\t\t\t});\n\t\t},\n\t\tgetRun(runId: string, opts?: RequestOptions): Promise<ChainRunState> {\n\t\t\treturn client.callOp('ingress', genGetRun, {\n\t\t\t\tpath: { run_id: runId },\n\t\t\t\t...passThrough(opts),\n\t\t\t});\n\t\t},\n\t\tasync cancel(runId: string, opts?: RequestOptions): Promise<void> {\n\t\t\tawait client.callOp('ingress', genCancelRun, {\n\t\t\t\tpath: { run_id: runId },\n\t\t\t\t...passThrough(opts),\n\t\t\t});\n\t\t},\n\t};\n}\n","// Authoring helpers for common *interactive* chain shapes (ERA-6484 follow-up).\n//\n// `defineChain` + `step` + `$` give you the primitives; a real voice flow like\n// \"keep asking the user for items until they say they're done\" is a fixed but\n// fiddly assembly of them: speak → wait → classify → branch → (ack + loop |\n// close). Hand-authoring it invites the ref gotchas we hit live:\n// • a `waitForEvent` reply's text is at `$.<wait>.output.transcript`\n// • an `agentTurn` with `output:'json'` exposes fields at\n// `$.<turn>.output.json.<field>` (NOT `$.<turn>.output.<field>`)\n// • the classifier must be pinned to strict single-line JSON or it echoes\n// the user's utterance back instead of a verdict.\n//\n// `collectLoop` bakes that assembly — and those refs — into one call. It is a\n// pure authoring helper (no client); it returns a validated, materialized\n// `ChainSpec` ready for `chains(client).create(...)` / publish.\n\nimport {\n\ttype ChainSpec,\n\ttype ChainStep,\n\ttype Ctx,\n\tdefineChain,\n\ttype JsonSchema,\n\ttype RefNode,\n\tstep,\n} from './chains.js';\n\n/** Options for {@link collectLoop}. */\nexport interface CollectLoopOptions {\n\t/** Chain name (also its human-facing title). */\n\tname: string;\n\t/**\n\t * The line spoken when the run launches — your opening ask, e.g.\n\t * \"What's the first thing for your shopping list?\". Delivered as the\n\t * first `builtin:say` step so it is heard before the run parks on the\n\t * user's first reply.\n\t */\n\topener: string;\n\t/**\n\t * Max collection rounds before the loop auto-closes even if the user never\n\t * says they're done — a safety cap so the chain can't park forever.\n\t * Default `5`.\n\t */\n\tcap?: number;\n\t/**\n\t * Noun for the thing being collected, woven into the default classifier and\n\t * acknowledgement prompts (e.g. `'grocery item(s)'`, `'guest'`, `'task'`).\n\t * Ignored when you pass your own `classifierPrompt`/`ackPrompt`. Default\n\t * `'item(s)'`.\n\t */\n\titemNoun?: string;\n\t/** Optional description stored on the chain. */\n\tdescription?: string;\n\t/**\n\t * Override the strict-JSON classifier instruction. Must direct the model to\n\t * emit a single-line object matching {@link CollectLoopOptions.itemSchema}\n\t * (`{ done: boolean, item: string }` by default). Only override if you also\n\t * override `itemSchema` or need domain-specific done-detection.\n\t */\n\tclassifierPrompt?: string;\n\t/** Override the per-item acknowledgement instruction. */\n\tackPrompt?: string;\n\t/** Override the closing-confirmation instruction. */\n\tclosePrompt?: string;\n\t/**\n\t * JSON Schema the classifier turn must satisfy. Defaults to\n\t * `{ done: boolean, item: string }` (both required, no extra keys). Override\n\t * together with `classifierPrompt` for a richer per-item shape.\n\t */\n\titemSchema?: JsonSchema;\n}\n\n/** Default classifier output contract: `{ done, item }`. */\nfunction defaultItemSchema(): JsonSchema {\n\treturn {\n\t\ttype: 'object',\n\t\tadditionalProperties: false,\n\t\tproperties: { done: { type: 'boolean' }, item: { type: 'string' } },\n\t\trequired: ['done', 'item'],\n\t};\n}\n\n/** Strict single-line JSON classifier prompt (the shape proven live). */\nfunction defaultClassifierPrompt(itemNoun: string): string {\n\treturn (\n\t\t'You are a strict JSON API. Output ONLY a single-line JSON object, ' +\n\t\t'nothing else — no prose, no code fences. Set done=true if the user ' +\n\t\t`signals they are FINISHED adding ${itemNoun} — this includes any of: ` +\n\t\t'\"that’s it\", \"that’s all\", \"that’s everything\", \"that’s all thanks\", ' +\n\t\t'\"done\", \"I’m done\", \"I’m good\", \"no\", \"nope\", \"nothing else\", \"no ' +\n\t\t'more\", \"all done\", \"finished\", \"stop\", or any clearly negative/closing ' +\n\t\t'reply. In that case output {\"done\":true,\"item\":\"\"}. Otherwise output ' +\n\t\t`{\"done\":false,\"item\":\"<the ${itemNoun} they just named>\"}.`\n\t);\n}\n\n/**\n * Build a voice chain that repeatedly asks the user for one more item until\n * they signal they're done (or a round `cap` is hit), acknowledging each item\n * and confirming at the end.\n *\n * Shape, unrolled to `cap` rounds (per round `i`):\n * ```\n * waitForEvent(reply) ← park for the user's utterance\n * agentTurn(json, classify transcript) ← { done, item }\n * branch(done?)\n * then → agentTurn(close) → builtin:say ← warm confirmation\n * else → agentTurn(ack) → builtin:say ← acknowledge, ask for more\n * → next round (or close, at cap)\n * ```\n * The `opener` is spoken first via a `builtin:say` step, so the user hears the\n * ask before the run parks on their first reply.\n *\n * @param options - Loop configuration — see {@link CollectLoopOptions}. Only `name` and `opener` are required; the classifier, acknowledgement, and close prompts default to voice-friendly text (the classifier and acknowledgement defaults are keyed off `itemNoun`).\n * @returns A validated, materialized {@link ChainSpec} (all `$.` ref tokens already collapsed to `{\"$ref\":…}`) — hand it straight to `chains(client).create(spec)` and the publish lifecycle.\n * @throws {TypeError} If `cap` is not a positive integer.\n *\n * @example\n * const spec = collectLoop({\n * name: 'Shopping list',\n * opener: \"What's the first thing you need?\",\n * itemNoun: 'grocery item(s)',\n * });\n * await chains(client).create(spec);\n */\nexport function collectLoop(options: CollectLoopOptions): ChainSpec {\n\tconst cap = options.cap ?? 5;\n\tif (!Number.isInteger(cap) || cap < 1) {\n\t\tthrow new TypeError('collectLoop: `cap` must be a positive integer');\n\t}\n\tconst itemNoun = options.itemNoun ?? 'item(s)';\n\tconst schema = options.itemSchema ?? defaultItemSchema();\n\tconst classifierPrompt =\n\t\toptions.classifierPrompt ?? defaultClassifierPrompt(itemNoun);\n\tconst ackPrompt =\n\t\toptions.ackPrompt ??\n\t\t`Acknowledge in a few words the ${itemNoun} the user just added, then ` +\n\t\t\t'ask if they’d like to add anything else. One short friendly sentence.';\n\tconst closePrompt =\n\t\toptions.closePrompt ??\n\t\t'The user is finished. Warmly confirm everything is all set in ONE ' +\n\t\t\t'short sentence. Do NOT list the items back.';\n\n\treturn defineChain({\n\t\tname: options.name,\n\t\tdescription:\n\t\t\toptions.description ?? `Interactive voice collection: ${options.name}.`,\n\t\tmodality: 'voice',\n\t\tsteps: ($: Ctx) => {\n\t\t\t// `$[dynamic]` — the ref proxy extends any string key into a RefNode,\n\t\t\t// so `at('wait1').output.transcript` → { $ref: '$.wait1.output.transcript' }.\n\t\t\tconst at = (id: string): RefNode => ($ as Ctx)[id] as RefNode;\n\n\t\t\t// A close = warm confirmation turn + speak it. Distinct `tag` keeps\n\t\t\t// step ids unique across the (mutually exclusive) done- and cap-paths,\n\t\t\t// which defineChain's cross-tree id check requires.\n\t\t\tconst close = (tag: string): ChainStep[] => [\n\t\t\t\tstep.agentTurn({\n\t\t\t\t\tid: `close_${tag}`,\n\t\t\t\t\toutput: 'text',\n\t\t\t\t\tprompt: closePrompt,\n\t\t\t\t}),\n\t\t\t\tstep.tool({\n\t\t\t\t\tid: `sayclose_${tag}`,\n\t\t\t\t\ttool: 'builtin:say',\n\t\t\t\t\tparams: { text: at(`close_${tag}`).output.text },\n\t\t\t\t}),\n\t\t\t];\n\n\t\t\tconst round = (i: number): ChainStep[] => [\n\t\t\t\tstep.waitForEvent({ id: `wait${i}`, event: 'reply' }),\n\t\t\t\tstep.agentTurn({\n\t\t\t\t\tid: `parse${i}`,\n\t\t\t\t\toutput: 'json',\n\t\t\t\t\tsay: at(`wait${i}`).output.transcript,\n\t\t\t\t\texpect: { name: 'item_or_done', schema },\n\t\t\t\t\tprompt: classifierPrompt,\n\t\t\t\t}),\n\t\t\t\tstep.branch({\n\t\t\t\t\tid: `branch${i}`,\n\t\t\t\t\twhen: { truthy: at(`parse${i}`).output.json.done },\n\t\t\t\t\t// biome-ignore lint/suspicious/noThenProperty: `then` is the canonical BranchStep field (plain orchestration data, never awaited).\n\t\t\t\t\tthen: close(`done${i}`),\n\t\t\t\t\telse: [\n\t\t\t\t\t\tstep.agentTurn({\n\t\t\t\t\t\t\tid: `ack${i}`,\n\t\t\t\t\t\t\toutput: 'text',\n\t\t\t\t\t\t\tsay: at(`parse${i}`).output.json.item,\n\t\t\t\t\t\t\tprompt: ackPrompt,\n\t\t\t\t\t\t}),\n\t\t\t\t\t\tstep.tool({\n\t\t\t\t\t\t\tid: `sayack${i}`,\n\t\t\t\t\t\t\ttool: 'builtin:say',\n\t\t\t\t\t\t\tparams: { text: at(`ack${i}`).output.text },\n\t\t\t\t\t\t}),\n\t\t\t\t\t\t...(i === cap ? close('cap') : round(i + 1)),\n\t\t\t\t\t],\n\t\t\t\t}),\n\t\t\t];\n\n\t\t\t// The opener is a plain mid-run utterance (voice outputs aren't in the\n\t\t\t// typed OutputDef union); speak it, then enter the loop.\n\t\t\treturn [\n\t\t\t\tstep.tool({\n\t\t\t\t\tid: 'opener',\n\t\t\t\t\ttool: 'builtin:say',\n\t\t\t\t\tparams: { text: options.opener },\n\t\t\t\t}),\n\t\t\t\t...round(1),\n\t\t\t];\n\t\t},\n\t});\n}\n","// commands module — typed style preferences + voice/button command CRUD.\n//\n// L2.3 (ERA-4265): promotes the loose `stylePreferences` blob to a typed\n// `ExperienceStylePreferences` surface, and exposes CRUD over the curator-\n// authored triggers (`voiceCommands` + `buttonMappings`) the runtime fires.\n//\n// Types mirror era-hub-api/src/schemas/experiences.ts (VoiceCommandSchema,\n// ButtonMappingSchema, ParamMappingSchema, InputSpecSchema,\n// StylePreferencesSchema). `ExperienceStylePreferences` keeps an index\n// signature (`[k: string]: unknown`) so it stays drift-tolerant — the backend\n// schema is a `looseObject` and the SDK must not break on new keys.\n//\n// Persistence: voiceCommands / buttonMappings live in the experience's\n// `stylePreferences`. CRUD here is a read-modify-write through\n// `GET`/`PUT /api/experiences/{id}` that preserves every other style key —\n// the same object + path the `tools` module uses for the `spindles` palette, and\n// verified runtime-honored (era-ingress lib/api_client.py reads\n// `voice_commands`/`button_mappings` from the same stylePreferences).\n//\n// NOTE: `Experience.stylePreferences` on the experiences module stays\n// `Record<string, unknown> | null` (unchanged — additive). Consumers wanting\n// the strict shape cast to / read via `ExperienceStylePreferences` here.\n//\n// Tree-shakeable subpath import: `@era-laboratories/era-sdk/modules/commands`.\n\nimport type { EraClient } from '../core/client.js';\nimport type { RequestOptions } from '../core/types.js';\nimport { getExperiencesById as genGetExperiencesById } from '../generated/hub-api/sdk.gen.js';\nimport { hubPath } from './hubPaths.js';\nimport type { SpindlePalette } from './spindlePalette.js';\n\n// ─── Style-preference sub-shapes ──────────────────────────────────────────────\n\n/** Voice slider values (0–1). */\nexport interface VoiceStyle {\n\t/** Formality slider (0–1). */\n\tformality: number;\n\t/** Warmth slider (0–1). */\n\twarmth: number;\n\t/** Candor slider (0–1). */\n\tcandor: number;\n\t/** Verbosity slider (0–1). */\n\tverbosity: number;\n\t/** Playfulness slider (0–1). */\n\tplayfulness: number;\n\t/** Assertiveness slider (0–1). */\n\tassertiveness: number;\n\t/** Cadence slider (0–1). */\n\tcadence: number;\n}\n\n/** Word-choice and reading-level preferences. */\nexport interface Lexicon {\n\t/** Words/phrases to prefer. */\n\tprefer: string[];\n\t/** Words/phrases to avoid. */\n\tavoid: string[];\n\t/** Whether contractions are allowed. */\n\tcontractions: boolean;\n\t/** Whether domain jargon is allowed. */\n\tjargon_allowed: boolean;\n\t/** Target reading level. */\n\treading_level: 'elementary' | 'general' | 'academic' | 'technical';\n}\n\n/** Rhetorical-device preferences. */\nexport interface Rhetoric {\n\t/** Rate of rhetorical questions (0–1). */\n\tquestion_rate: number;\n\t/** Rate of metaphor use (0–1). */\n\tmetaphor_rate: number;\n\t/** Whether analogies are allowed. */\n\tallow_analogy: boolean;\n}\n\n/** Truthfulness and refusal policy. */\nexport interface Policy {\n\t/** Truthfulness posture. */\n\ttruthfulness: 'strict' | 'standard';\n\t/** Whether the model should express uncertainty. */\n\texpress_uncertainty: boolean;\n\t/** Confidence threshold below which uncertainty is flagged (0–1). */\n\tconfidence_threshold: number;\n\t/** How refusals are phrased. */\n\trefusal_style: 'brief' | 'helpful' | 'policy_citing';\n}\n\n/** Response post-editing preferences. */\nexport interface Editing {\n\t/** Editing aggressiveness. */\n\tmode: 'light' | 'moderate' | 'heavy';\n\t/** Whether editing must preserve the original meaning. */\n\tpreserve_meaning: boolean;\n}\n\n/** Text-to-speech voice configuration. */\nexport interface TtsConfig {\n\t/** TTS provider (e.g. `moss-tts-nano`). */\n\tprovider: string;\n\t/** Voice id used for TTS, or `null` to use the platform default. */\n\tvoice_id: string | null;\n\t/**\n\t * MOSS voice-design/clone preset id, when the voice is a designed/cloned\n\t * preset. Moss cloning models strip this on PUT and fall back to\n\t * `voice_id`, so the two carry the same id; surfaced for round-tripping.\n\t */\n\tpreset_id?: string | null;\n\t/** Display name of the voice, or `null`. */\n\tvoice_name?: string | null;\n\t/** Human-readable voice description, or `null`. */\n\tvoice_description?: string | null;\n\t/** Signed preview-audio URL, or `null`. */\n\tpreview_url?: string | null;\n}\n\n/** Speech-to-text configuration. */\nexport interface SttConfig {\n\t/** STT provider. */\n\tprovider: string;\n\t/** STT model, or `null` to resolve from the experience. */\n\tmodel?: string | null;\n}\n\n/** A single interstitial clip reference. */\nexport interface InterstitialClip {\n\t/** Bank clip id. */\n\tclipId?: string;\n\t/** Spoken phrase rendered for this clip. */\n\tphrase?: string;\n\t/** Signed audio URL for the clip. */\n\tvocalUrl?: string;\n}\n\n/**\n * One earcon binding for a single pipeline state — the tonal-capable keys of\n * `interstitials.byState`. Any of a bank clip, a TTS-rendered phrase, or a\n * pre-rendered audio URL.\n */\nexport interface InterstitialStateBinding {\n\t/** Bank clip id (tonal earcon). */\n\tclipId?: string;\n\t/** Spoken phrase rendered in the experience's voice. */\n\tphrase?: string;\n\t/** Pre-rendered audio URL for the phrase. */\n\tvocalUrl?: string;\n\t/** Whether the binding is active. */\n\tenabled?: boolean;\n}\n\n/**\n * Binding for the conversational filler states (`backchannel`, `delegation`,\n * `resume`). These states are VOCAL-ONLY: they are conversational utterances\n * spoken in the experience's voice, never tonal earcon clips, so `clipId` is\n * omitted from the type. The API rejects a `clipId` on these states\n * (\"conversational states are vocal-only: use phrase or vocalUrl, not\n * clipId\").\n */\nexport interface VocalInterstitialStateBinding {\n\t/** Spoken phrase rendered in the experience's voice. */\n\tphrase?: string;\n\t/** Pre-rendered audio URL for the phrase. */\n\tvocalUrl?: string;\n\t/** Whether the binding is active. */\n\tenabled?: boolean;\n}\n\n/** Interstitial (filler-audio) configuration while a tool runs. */\nexport interface Interstitials {\n\t/** Interstitial playback mode. */\n\tmode: 'off' | 'tonal' | 'vocal' | 'mixed';\n\t/** Delay (ms) before an interstitial starts playing. */\n\tthresholdMs?: number;\n\t/** Default clip id used when no per-tool override matches. */\n\tdefaultClipId?: string;\n\t/** Default spoken phrase used when no per-tool override matches. */\n\tdefaultPhrase?: string;\n\t/** Default audio URL used when no per-tool override matches. */\n\tdefaultVocalUrl?: string;\n\t/** Per-tool clip overrides, keyed by tool name. */\n\tperTool?: Record<string, InterstitialClip>;\n\t/** Per-server clip map; `perTool` is derived from it on save. */\n\tperServer?: Record<string, string>;\n\t/**\n\t * Per-pipeline-state earcon bindings (`listening`, `listeningStopped`,\n\t * `tooling`, `llmWait`) plus the vocal-only conversational states\n\t * (`backchannel`, `delegation`, `resume`). The conversational states take\n\t * `phrase` / `vocalUrl` only — the API rejects `clipId` on them (see\n\t * {@link VocalInterstitialStateBinding}).\n\t */\n\tbyState?: {\n\t\t/** VAD-edge cue: the agent started listening. */\n\t\tlistening?: InterstitialStateBinding;\n\t\t/** VAD-edge cue: the agent stopped listening. */\n\t\tlisteningStopped?: InterstitialStateBinding;\n\t\t/** Cue while a tool call runs (structured successor to `perTool`). */\n\t\ttooling?: InterstitialStateBinding;\n\t\t/** Cue while waiting on LLM inference (successor to `defaultClipId`). */\n\t\tllmWait?: InterstitialStateBinding;\n\t\t/** Vocal-only conversational acknowledgement (\"mm-hm\"). */\n\t\tbackchannel?: VocalInterstitialStateBinding;\n\t\t/** Vocal-only cue while the run is handed off / delegated. */\n\t\tdelegation?: VocalInterstitialStateBinding;\n\t\t/** Vocal-only cue when the conversation resumes after a handoff. */\n\t\tresume?: VocalInterstitialStateBinding;\n\t};\n}\n\n// ─── Triggers (voice + button commands) ───────────────────────────────────────\n\n/** A tool-param source descriptor (`ParamMappingSchema`). */\nexport interface ParamMapping {\n\t/**\n\t * Source key. Non-utterance: `query`, `location`, `timezone`, `now`,\n\t * `today_start`, `today_end`, `tomorrow_start`, `tomorrow_end`,\n\t * `this_week_start`, `this_week_end`. Utterance-only (voice):\n\t * `rest_of_utterance`, `full_transcription`, `extracted_duration`,\n\t * `extracted_date_start`, `extracted_date_end`. App-provided (buttons):\n\t * any `slotKey` from `inputs[]`.\n\t */\n\tsource: string;\n\t/** Source tried when the primary resolves empty. */\n\tfallback?: string | null;\n}\n\n/** A param value — a legacy canonical-key string or a {@link ParamMapping}. */\nexport type ParamValue = string | ParamMapping;\n\n/**\n * A typed input the iOS app collects before firing a button. Discriminated on\n * `type` so the client renders the right native picker; `slotKey` becomes a\n * valid `ParamMapping.source` on the button's `params`.\n */\nexport type InputSpec =\n\t| {\n\t\t\t/** Param key the collected value is bound to. */\n\t\t\tslotKey: string;\n\t\t\t/** Discriminant — renders a native integer picker. */\n\t\t\ttype: 'integer';\n\t\t\t/** Field label shown in the app. */\n\t\t\tlabel: string;\n\t\t\t/** Minimum allowed value. */\n\t\t\tmin?: number;\n\t\t\t/** Maximum allowed value. */\n\t\t\tmax?: number;\n\t\t\t/** Stepper increment. */\n\t\t\tstep?: number;\n\t\t\t/** Default value. */\n\t\t\tdefault?: number;\n\t\t\t/** Unit suffix shown next to the value. */\n\t\t\tunit?: string;\n\t }\n\t| {\n\t\t\t/** Param key the collected value is bound to. */\n\t\t\tslotKey: string;\n\t\t\t/** Discriminant — renders a native decimal picker. */\n\t\t\ttype: 'number';\n\t\t\t/** Field label shown in the app. */\n\t\t\tlabel: string;\n\t\t\t/** Minimum allowed value. */\n\t\t\tmin?: number;\n\t\t\t/** Maximum allowed value. */\n\t\t\tmax?: number;\n\t\t\t/** Stepper increment. */\n\t\t\tstep?: number;\n\t\t\t/** Default value. */\n\t\t\tdefault?: number;\n\t\t\t/** Unit suffix shown next to the value. */\n\t\t\tunit?: string;\n\t }\n\t| {\n\t\t\t/** Param key the collected value is bound to. */\n\t\t\tslotKey: string;\n\t\t\t/** Discriminant — renders a free-text field. */\n\t\t\ttype: 'string';\n\t\t\t/** Field label shown in the app. */\n\t\t\tlabel: string;\n\t\t\t/** Placeholder text for the empty field. */\n\t\t\tplaceholder?: string;\n\t\t\t/** Maximum input length. */\n\t\t\tmaxLength?: number;\n\t }\n\t| {\n\t\t\t/** Param key the collected value is bound to. */\n\t\t\tslotKey: string;\n\t\t\t/** Discriminant — renders a native option picker. */\n\t\t\ttype: 'enum';\n\t\t\t/** Field label shown in the app. */\n\t\t\tlabel: string;\n\t\t\t/** Selectable options (value + display label). */\n\t\t\toptions: Array<{ value: string; label: string }>;\n\t\t\t/** Default selected value. */\n\t\t\tdefault?: string;\n\t }\n\t| {\n\t\t\t/** Param key the collected value is bound to. */\n\t\t\tslotKey: string;\n\t\t\t/** Discriminant — renders a duration picker. */\n\t\t\ttype: 'duration';\n\t\t\t/** Field label shown in the app. */\n\t\t\tlabel: string;\n\t\t\t/** Default value. */\n\t\t\tdefault?: string;\n\t }\n\t| {\n\t\t\t/** Param key the collected value is bound to. */\n\t\t\tslotKey: string;\n\t\t\t/** Discriminant — renders a date picker. */\n\t\t\ttype: 'date';\n\t\t\t/** Field label shown in the app. */\n\t\t\tlabel: string;\n\t\t\t/** Default value. */\n\t\t\tdefault?: 'today' | 'tomorrow';\n\t }\n\t| {\n\t\t\t/** Param key the collected value is bound to. */\n\t\t\tslotKey: string;\n\t\t\t/** Discriminant — renders a toggle. */\n\t\t\ttype: 'boolean';\n\t\t\t/** Field label shown in the app. */\n\t\t\tlabel: string;\n\t\t\t/** Default value. */\n\t\t\tdefault?: boolean;\n\t };\n\n/**\n * A voice command — a trigger phrase set mapped to a tool. The index\n * signature keeps any additional fields the server may add.\n */\nexport interface VoiceCommand {\n\t/** Stable command id. */\n\tid: string;\n\t/** Human-readable command name. */\n\tname?: string;\n\t/** Trigger phrases (substring-matched on the STT transcript). */\n\ttriggers?: string[];\n\t/** `\"spindle-slug:tool_name\"` or `\"builtin:<action>\"`. */\n\ttoolRef?: string;\n\t/** Static query/argument passed to the tool, or `null`. */\n\tquery?: string | null;\n\t/** Human-readable command description. */\n\tdescription?: string;\n\t/** Response-framing instructions for the LLM system prompt. */\n\tpromptContext?: string;\n\t/** Whether the command is active. */\n\tenabled?: boolean;\n\t/** Tool param sources keyed by param name. */\n\tparams?: Record<string, ParamValue>;\n\t/** Drift-tolerant index signature for backend-added keys. */\n\t[key: string]: unknown;\n}\n\n/** A button mapped to trigger a tool — the button-trigger form of a voice command. */\nexport interface ButtonMapping {\n\t/** Stable mapping id. */\n\tid: string;\n\t/** Client-facing slug the iOS app sends on press (stable across versions). */\n\tbuttonId: string;\n\t/** `\"spindle-slug:tool_name\"` or `\"builtin:<action>\"`. */\n\ttoolRef: string;\n\t/** Static query/argument passed to the tool, or `null`. */\n\tquery?: string | null;\n\t/** Human-readable mapping description. */\n\tdescription?: string;\n\t/** Response-framing instructions for the LLM system prompt. */\n\tpromptContext?: string;\n\t/** Tool param sources keyed by param name. */\n\tparams?: Record<string, ParamValue>;\n\t/** Button label shown in the app. */\n\tlabel: string;\n\t/** Icon identifier for the button. */\n\ticon?: string;\n\t/** Sort order among buttons. */\n\tdisplayOrder?: number;\n\t/** Group/section the button belongs to. */\n\tgroup?: string;\n\t/** Visual button style. */\n\tvariant?: 'primary' | 'secondary' | 'destructive' | 'subtle';\n\t/**\n\t * `narrated`: full LLM TTS. `confirm`: short canned TTS. `silent`: tool\n\t * runs, no audio. Defaults to `narrated`.\n\t */\n\tresponseMode?: 'narrated' | 'confirm' | 'silent';\n\t/** Whether the app must confirm before firing the tool. */\n\tconfirmRequired?: boolean;\n\t/** Typed inputs the app collects before firing. */\n\tinputs?: InputSpec[];\n\t/** Whether the mapping is active. */\n\tenabled?: boolean;\n}\n\n// ─── Style preferences ─────────────────────────────────────────────────────────\n\n/**\n * Typed projection of an experience's `stylePreferences`. Open\n * (`[k: string]: unknown`) to mirror the backend `looseObject` — unknown keys\n * pass through untouched, so the SDK never breaks on a new style field.\n */\nexport interface ExperienceStylePreferences {\n\t/** Voice tone sliders. */\n\tvoice?: VoiceStyle;\n\t/** Word-choice and reading-level preferences. */\n\tlexicon?: Lexicon;\n\t/** Rhetorical-device preferences. */\n\trhetoric?: Rhetoric;\n\t/** Truthfulness and refusal policy. */\n\tpolicy?: Policy;\n\t/** Response post-editing preferences. */\n\tediting?: Editing;\n\t/** Text-to-speech voice configuration. */\n\ttts?: TtsConfig;\n\t/** Speech-to-text configuration. */\n\tstt?: SttConfig;\n\t/** Brand/accent color (hex). */\n\tcolor?: string;\n\t/** Server-authored reasoning for the chosen style. */\n\tanalysis_reasoning?: string;\n\t/** Legacy free-form fields, kept for older experiences. */\n\ttone?: string;\n\t/** Legacy free-form personality description. */\n\tpersonality?: string;\n\t/** Legacy response-length preference. */\n\tresponseLength?: 'brief' | 'moderate' | 'detailed';\n\t/** Legacy formality preference. */\n\tformality?: 'casual' | 'neutral' | 'formal';\n\t/**\n\t * Nested, slug-keyed spindle palette — the sole encoding of the tool\n\t * palette (the legacy flat `mcpServers`/`mcpTools` maps are no longer read\n\t * or written by the SDK). Presence enables a spindle; `disabled: true`\n\t * keeps it in-palette but off; `tools` is a sparse per-tool deny.\n\t */\n\tspindles?: SpindlePalette;\n\t/** Interstitial (filler-audio) configuration. */\n\tinterstitials?: Interstitials;\n\t/** Curator-authored voice commands. */\n\tvoiceCommands?: VoiceCommand[];\n\t/** Curator-authored button mappings. */\n\tbuttonMappings?: ButtonMapping[];\n\t/** Drift-tolerant index signature for unknown style keys. */\n\t[key: string]: unknown;\n}\n\n/** The trigger pair read/returned by the commands module. */\nexport interface CommandSet {\n\t/** Voice commands in the experience. */\n\tvoiceCommands: VoiceCommand[];\n\t/** Button mappings in the experience. */\n\tbuttonMappings: ButtonMapping[];\n}\n\n/**\n * Typed CRUD over an experience's curator-authored triggers — the\n * `voiceCommands` and `buttonMappings` arrays stored inside its\n * `stylePreferences`. Reach it by calling {@link commands} with an\n * {@link EraClient}: `commands(era).list(experienceId)`.\n *\n * Every mutation is a read-modify-write against `GET`/`PUT /experiences/{id}`:\n * the module reads the current `stylePreferences`, updates only the targeted\n * array, and writes the whole blob back — so all other style keys (`tts`,\n * `spindles`, `voice`, custom fields) survive untouched.\n *\n * @remarks\n * Because writes go through the full-experience `PUT`, they are last-write-wins:\n * two concurrent mutations against the same experience can clobber each other.\n * Serialize mutations to one experience if that matters.\n */\nexport interface CommandsModule {\n\t/**\n\t * Read an experience's voice + button commands from its `stylePreferences`.\n\t *\n\t * @param experienceId - Id of the experience to read (e.g. `'exp_0000000000'`).\n\t * @param opts - Optional per-request overrides (`signal`, `headers`,\n\t * `idempotencyKey`).\n\t * @returns The experience's {@link CommandSet}; each array is `[]` when the\n\t * experience has no commands of that kind.\n\t * @throws {EraAPIError} On a non-2xx `GET /hub-api/experiences/{id}` — `404` when the\n\t * experience does not exist (or is another user's private experience, which\n\t * is reported as not-found rather than forbidden), `403` when read access to\n\t * a shared experience is denied, `401` when unauthenticated, `500` on a\n\t * server fault.\n\t * @example\n\t * ```ts\n\t * const { voiceCommands, buttonMappings } = await commands(era).list('exp_0000000000');\n\t * ```\n\t */\n\tlist(experienceId: string, opts?: RequestOptions): Promise<CommandSet>;\n\n\t/**\n\t * Insert or replace a voice command (matched by `id`), preserving all other\n\t * style preferences.\n\t *\n\t * @param experienceId - Id of the experience to mutate (e.g. `'exp_0000000000'`).\n\t * @param command - The voice command to upsert. A command whose `id` already\n\t * exists is replaced in place; a new `id` is appended.\n\t * @param opts - Optional per-request overrides (`signal`, `headers`,\n\t * `idempotencyKey`).\n\t * @returns The full updated `voiceCommands` array after the write.\n\t * @throws {EraAPIError} On a non-2xx read or write — `404` when the experience\n\t * does not exist (or is another user's private experience), `403` when the\n\t * caller lacks read access or write permission on the experience, `400` when\n\t * the resulting `stylePreferences` fails validation, `401` when\n\t * unauthenticated, `500` on a server fault.\n\t * @remarks\n\t * Matching is by `id` only; two commands with the same `id` cannot coexist.\n\t * The runtime substring-matches `triggers` against the STT transcript to fire\n\t * the command's `toolRef`.\n\t * @example\n\t * ```ts\n\t * const updated = await commands(era).upsertVoice('exp_0000000000', {\n\t * id: 'cmd_0000000000',\n\t * triggers: ['check in', 'how am I doing'],\n\t * toolRef: 'memory:get_recent_insights',\n\t * promptContext: 'Use these insights to write a warm check-in.',\n\t * enabled: true,\n\t * });\n\t * ```\n\t */\n\tupsertVoice(\n\t\texperienceId: string,\n\t\tcommand: VoiceCommand,\n\t\topts?: RequestOptions,\n\t): Promise<VoiceCommand[]>;\n\n\t/**\n\t * Insert or replace a button mapping (matched by `id`), preserving all other\n\t * style preferences.\n\t *\n\t * @param experienceId - Id of the experience to mutate (e.g. `'exp_0000000000'`).\n\t * @param mapping - The button mapping to upsert. A mapping whose `id` already\n\t * exists is replaced in place; a new `id` is appended. `buttonId`, `toolRef`,\n\t * and `label` are required.\n\t * @param opts - Optional per-request overrides (`signal`, `headers`,\n\t * `idempotencyKey`).\n\t * @returns The full updated `buttonMappings` array after the write.\n\t * @throws {EraAPIError} On a non-2xx read or write — `404` when the experience\n\t * does not exist (or is another user's private experience), `403` when the\n\t * caller lacks read access or write permission on the experience, `400` when\n\t * the resulting `stylePreferences` fails validation, `401` when\n\t * unauthenticated, `500` on a server fault.\n\t * @remarks\n\t * Matching is by `id` (not `buttonId`). `buttonId` is the stable slug the iOS\n\t * app sends on press; keep it constant across versions so bound buttons keep\n\t * firing.\n\t * @example\n\t * ```ts\n\t * const updated = await commands(era).upsertButton('exp_0000000000', {\n\t * id: 'btn_0000000000',\n\t * buttonId: 'help-corner',\n\t * toolRef: 'docs:search',\n\t * label: 'Help',\n\t * responseMode: 'narrated',\n\t * });\n\t * ```\n\t */\n\tupsertButton(\n\t\texperienceId: string,\n\t\tmapping: ButtonMapping,\n\t\topts?: RequestOptions,\n\t): Promise<ButtonMapping[]>;\n\n\t/**\n\t * Remove a voice command or button mapping by `id`, checking both arrays.\n\t *\n\t * @param experienceId - Id of the experience to mutate (e.g. `'exp_0000000000'`).\n\t * @param commandId - `id` of the command or mapping to remove. Filtered from\n\t * both `voiceCommands` and `buttonMappings`, so a single call clears whichever\n\t * array holds it.\n\t * @param opts - Optional per-request overrides (`signal`, `headers`,\n\t * `idempotencyKey`).\n\t * @returns The resulting {@link CommandSet} with both arrays after removal.\n\t * @throws {EraAPIError} On a non-2xx read or write — `404` when the experience\n\t * does not exist (or is another user's private experience), `403` when the\n\t * caller lacks read access or write permission on the experience, `400` when\n\t * the resulting `stylePreferences` fails validation, `401` when\n\t * unauthenticated, `500` on a server fault.\n\t * @remarks\n\t * A `commandId` that matches nothing is a no-op that still performs the write\n\t * and returns the unchanged arrays — it does not throw.\n\t * @example\n\t * ```ts\n\t * const { voiceCommands, buttonMappings } = await commands(era).remove(\n\t * 'exp_0000000000',\n\t * 'btn_0000000000',\n\t * );\n\t * ```\n\t */\n\tremove(\n\t\texperienceId: string,\n\t\tcommandId: string,\n\t\topts?: RequestOptions,\n\t): Promise<CommandSet>;\n}\n\n// ─── Internal ───────────────────────────────────────────────────────────────\n\ntype HubRequestInit = RequestOptions & {\n\tmethod?: string;\n\tbody?: unknown;\n};\n\ninterface ExperienceStyleSlice {\n\tstylePreferences?: ExperienceStylePreferences | null;\n}\n\n/** Replace the element matching `id`, or append it. Returns a new array. */\nfunction upsertById<T extends { id: string }>(list: T[], item: T): T[] {\n\tconst idx = list.findIndex((x) => x.id === item.id);\n\tif (idx === -1) return [...list, item];\n\tconst next = list.slice();\n\tnext[idx] = item;\n\treturn next;\n}\n\n// ─── Factory ────────────────────────────────────────────────────────────────\n\n/**\n * Construct the `commands` module bound to an {@link EraClient} instance — the\n * typed CRUD surface over an experience's `voiceCommands` and `buttonMappings`.\n * Call it once per operation (`commands(era).list(id)`); it holds no state.\n *\n * @param client - The {@link EraClient} whose auth and base URL back every\n * `GET`/`PUT /experiences/{id}` this module issues.\n * @returns A {@link CommandsModule} bound to `client`.\n * @example\n * ```ts\n * const era = createEraClient({ auth: { mode: 'pkce', ... } });\n * await commands(era).upsertVoice('exp_0000000000', {\n * id: 'cmd_0000000000',\n * triggers: ['book a table'],\n * toolRef: 'reservations:create',\n * params: { time: { source: 'extracted_date_start' } },\n * });\n * ```\n */\nexport function commands(client: EraClient): CommandsModule {\n\tconst path = (id: string): string =>\n\t\thubPath(genGetExperiencesById, { path: { id } });\n\n\tasync function readStyle(\n\t\texperienceId: string,\n\t\topts?: RequestOptions,\n\t): Promise<ExperienceStylePreferences> {\n\t\tconst exp = await client.hub<ExperienceStyleSlice>(\n\t\t\tpath(experienceId),\n\t\t\topts,\n\t\t);\n\t\treturn { ...(exp.stylePreferences ?? {}) };\n\t}\n\n\tasync function writeStyle(\n\t\texperienceId: string,\n\t\tstyle: ExperienceStylePreferences,\n\t\topts?: RequestOptions,\n\t): Promise<void> {\n\t\tconst reqOpts: HubRequestInit = {\n\t\t\t...opts,\n\t\t\tmethod: 'PUT',\n\t\t\tbody: { stylePreferences: style },\n\t\t};\n\t\tawait client.hub<unknown>(path(experienceId), reqOpts);\n\t}\n\n\treturn {\n\t\tasync list(\n\t\t\texperienceId: string,\n\t\t\topts?: RequestOptions,\n\t\t): Promise<CommandSet> {\n\t\t\tconst style = await readStyle(experienceId, opts);\n\t\t\treturn {\n\t\t\t\tvoiceCommands: style.voiceCommands ?? [],\n\t\t\t\tbuttonMappings: style.buttonMappings ?? [],\n\t\t\t};\n\t\t},\n\t\tasync upsertVoice(\n\t\t\texperienceId: string,\n\t\t\tcommand: VoiceCommand,\n\t\t\topts?: RequestOptions,\n\t\t): Promise<VoiceCommand[]> {\n\t\t\tconst style = await readStyle(experienceId, opts);\n\t\t\tconst next = upsertById(style.voiceCommands ?? [], command);\n\t\t\tawait writeStyle(experienceId, { ...style, voiceCommands: next }, opts);\n\t\t\treturn next;\n\t\t},\n\t\tasync upsertButton(\n\t\t\texperienceId: string,\n\t\t\tmapping: ButtonMapping,\n\t\t\topts?: RequestOptions,\n\t\t): Promise<ButtonMapping[]> {\n\t\t\tconst style = await readStyle(experienceId, opts);\n\t\t\tconst next = upsertById(style.buttonMappings ?? [], mapping);\n\t\t\tawait writeStyle(experienceId, { ...style, buttonMappings: next }, opts);\n\t\t\treturn next;\n\t\t},\n\t\tasync remove(\n\t\t\texperienceId: string,\n\t\t\tcommandId: string,\n\t\t\topts?: RequestOptions,\n\t\t): Promise<CommandSet> {\n\t\t\tconst style = await readStyle(experienceId, opts);\n\t\t\tconst voiceCommands = (style.voiceCommands ?? []).filter(\n\t\t\t\t(c) => c.id !== commandId,\n\t\t\t);\n\t\t\tconst buttonMappings = (style.buttonMappings ?? []).filter(\n\t\t\t\t(m) => m.id !== commandId,\n\t\t\t);\n\t\t\tawait writeStyle(\n\t\t\t\texperienceId,\n\t\t\t\t{ ...style, voiceCommands, buttonMappings },\n\t\t\t\topts,\n\t\t\t);\n\t\t\treturn { voiceCommands, buttonMappings };\n\t\t},\n\t};\n}\n","// context module — the Context primitive (PRD #1 §5.3).\n//\n// `ContextSnapshot` is the frozen §5.3 data shape: a hybrid snapshot (the\n// surface attaches location/battery; the cloud enriches) that BOTH gates the\n// trigger and feeds the agent. The TYPE is frozen here because the Trigger\n// contract references it (`TriggerEvent.context`, ERA-4396). The GATE GRAMMAR —\n// `when()` / `when.semantic()` (ERA-4405) — is below; the cloud-side gate\n// evaluation at ingest (publish → evaluate → run/drop) is the era-ingress half.\n//\n// Tree-shakeable subpath import: `@era-laboratories/era-sdk/modules/context`.\n\nimport * as z from 'zod';\nimport type { Ctx, Predicate, RefNode } from './chains.js';\nimport { makeCtx } from './chains.js';\n\n/**\n * The device's network state at snapshot time:\n * - `'wifi'` — connected over Wi-Fi.\n * - `'cell'` — connected over a cellular network.\n * - `'offline'` — no network connectivity.\n */\nexport type Connectivity = 'wifi' | 'cell' | 'offline';\n\n/**\n * Coarse location plus an optional place label, as attached to a\n * {@link ContextSnapshot} by the surface.\n */\nexport interface ContextLocation {\n\t/** Latitude in decimal degrees. */\n\tlat: number;\n\t/** Longitude in decimal degrees. */\n\tlng: number;\n\t/** Place label — known values `home` | `work` | `gym`, or a custom string. */\n\tplace?: string;\n\t/** Location accuracy radius in meters, when the surface reports it. */\n\taccuracy?: number;\n}\n/** A device snapshot — battery and connectivity, as reported by the surface. */\nexport interface ContextDevice {\n\t/** Battery charge level as a percentage (0–100). */\n\tbatteryPct?: number;\n\t/** Current network connectivity of the device. */\n\tconnectivity?: Connectivity;\n}\n/**\n * A prior run of this experience, surfaced so a gate can dedup or run\n * already-fired checks against recent activity.\n */\nexport interface RecentRun {\n\t/** ID of the experience that ran. */\n\texperienceId: string;\n\t/** Terminal status of the run (e.g. `'completed'`, `'failed'`). */\n\tstatus: string;\n\t/** ISO-8601 timestamp of the run. */\n\tat: string;\n}\n/**\n * A pre-fetched, ranked set of memory items. Populated on a\n * {@link ContextSnapshot} only when the gate or goal references it.\n */\nexport interface MemoryDigest {\n\t/** Ranked memory items — each with its text and a relevance score. */\n\titems: { text: string; score: number }[];\n}\n\n/**\n * A hybrid context snapshot. The surface attaches a snapshot\n * (location, battery); the cloud enriches. `memoryDigest` is pre-fetched ONLY\n * when referenced by the gate or goal.\n */\nexport interface ContextSnapshot {\n\t/** ISO-8601 capture time of the snapshot. */\n\toccurredAt: string;\n\t/** Coarse location + place label, when the surface attaches it. */\n\tlocation?: ContextLocation;\n\t/** Device battery + connectivity, when the surface attaches it. */\n\tdevice?: ContextDevice;\n\t/** Recent runs of this experience, for dedup / already-fired checks. */\n\trecentRuns?: RecentRun[];\n\t/** Ranked memory digest — present only when the gate or goal references it. */\n\tmemoryDigest?: MemoryDigest;\n}\n\n/**\n * Zod schema for {@link ContextSnapshot}. Exported so callers can validate a\n * snapshot at their own boundary (e.g. before feeding a mock event into\n * `simulate`) or derive a type from it. The runtime shape mirrors the\n * `ContextSnapshot` interface exactly.\n *\n * @example\n * ```ts\n * const snapshot = contextSnapshotSchema.parse(incoming);\n * ```\n */\nexport const contextSnapshotSchema = z\n\t.object({\n\t\toccurredAt: z.iso\n\t\t\t.datetime({ offset: true })\n\t\t\t.meta({ description: 'ISO-8601 capture time of the snapshot.' }),\n\t\tlocation: z\n\t\t\t.object({\n\t\t\t\tlat: z.number().meta({ description: 'Latitude in decimal degrees.' }),\n\t\t\t\tlng: z.number().meta({ description: 'Longitude in decimal degrees.' }),\n\t\t\t\tplace: z.string().optional().meta({\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t'Place label — known values home | work | gym, or a custom string.',\n\t\t\t\t}),\n\t\t\t\taccuracy: z\n\t\t\t\t\t.number()\n\t\t\t\t\t.optional()\n\t\t\t\t\t.meta({ description: 'Location accuracy radius in meters.' }),\n\t\t\t})\n\t\t\t.optional()\n\t\t\t.meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Coarse location + a place label (home/work/gym or custom).',\n\t\t\t}),\n\t\tdevice: z\n\t\t\t.object({\n\t\t\t\tbatteryPct: z.number().optional().meta({\n\t\t\t\t\tdescription: 'Battery charge level as a percentage (0–100).',\n\t\t\t\t}),\n\t\t\t\tconnectivity: z\n\t\t\t\t\t.enum(['wifi', 'cell', 'offline'])\n\t\t\t\t\t.optional()\n\t\t\t\t\t.meta({ description: 'Current network connectivity of the device.' }),\n\t\t\t})\n\t\t\t.optional()\n\t\t\t.meta({ description: 'Device snapshot — battery + connectivity.' }),\n\t\trecentRuns: z\n\t\t\t.array(\n\t\t\t\tz.object({\n\t\t\t\t\texperienceId: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.meta({ description: 'ID of the experience that ran.' }),\n\t\t\t\t\tstatus: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.meta({ description: 'Terminal status of the run.' }),\n\t\t\t\t\tat: z\n\t\t\t\t\t\t.string()\n\t\t\t\t\t\t.meta({ description: 'ISO-8601 timestamp of the run.' }),\n\t\t\t\t}),\n\t\t\t)\n\t\t\t.optional()\n\t\t\t.meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Recent runs of this experience (dedup / already-fired checks).',\n\t\t\t}),\n\t\tmemoryDigest: z\n\t\t\t.object({\n\t\t\t\titems: z\n\t\t\t\t\t.array(\n\t\t\t\t\t\tz.object({\n\t\t\t\t\t\t\ttext: z.string().meta({ description: 'The memory item text.' }),\n\t\t\t\t\t\t\tscore: z\n\t\t\t\t\t\t\t\t.number()\n\t\t\t\t\t\t\t\t.meta({ description: 'Relevance score of the memory item.' }),\n\t\t\t\t\t\t}),\n\t\t\t\t\t)\n\t\t\t\t\t.meta({ description: 'Ranked memory items in the digest.' }),\n\t\t\t})\n\t\t\t.optional()\n\t\t\t.meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Pre-fetched memory digest — populated ONLY when the gate/goal references it.',\n\t\t\t}),\n\t})\n\t.meta({\n\t\tid: 'ContextSnapshot',\n\t\ttitle: 'Context snapshot',\n\t\tdescription:\n\t\t\t'Hybrid context (PRD §5.3) that gates the trigger and feeds the agent.',\n\t});\n\n// ─── Gate grammar (ERA-4405) ──────────────────────────────────────────────────\n\n/**\n * A run-or-drop condition authored against the {@link ContextSnapshot}.\n * `when(...)` is the deterministic default; `when.semantic(...)` is the\n * expensive, model-classified path. Today a gate is consumed by local\n * simulation (`simulate`) — it is not yet enforced server-side at trigger\n * ingest.\n */\nexport type ContextGate =\n\t| { kind: 'deterministic'; predicate: Predicate }\n\t| { kind: 'semantic'; prompt: string };\n\n/**\n * A `Predicate`, a bare `$.` ref (→ truthy), or an `($) => …` closure returning\n * either. Predicates are authored against the {@link ContextSnapshot}\n * (e.g. `$.location.place`).\n */\nexport type GatePredicateInput =\n\t| Predicate\n\t| RefNode\n\t| ((ctx: Ctx) => Predicate | RefNode);\n\nconst isRefNode = (v: unknown): v is RefNode =>\n\ttypeof v === 'object' && v !== null && '$ref' in v;\n\nfunction deterministicGate(input: GatePredicateInput): ContextGate {\n\tconst resolved = typeof input === 'function' ? input(makeCtx()) : input;\n\tconst predicate: Predicate = isRefNode(resolved)\n\t\t? { truthy: resolved }\n\t\t: resolved;\n\t// Materialize ref proxies → plain {\"$ref\"} objects (mirrors defineChain).\n\tconst materialized = JSON.parse(JSON.stringify(predicate)) as Predicate;\n\treturn { kind: 'deterministic', predicate: materialized };\n}\n\n/**\n * The context gate grammar. `when(($) => …)` builds a deterministic,\n * serializable gate over the {@link ContextSnapshot} (reusing the chains\n * `Predicate`/`$` ref system); `when.semantic('…')` builds the expensive\n * model-classified gate. Deterministic is the default.\n *\n * @param input - For the deterministic form: a {@link Predicate}, a bare `$.`\n * ref (treated as a truthy check), or an `($) => …` closure over the context\n * snapshot returning either. Predicate combinators:\n * `eq` / `neq` / `gt` / `lt` / `exists` / `truthy` / `and` / `or` / `not`.\n * @returns A serializable {@link ContextGate} to set as the `gate` field of\n * `defineExperience({ gate })`.\n * @remarks A deterministic gate can be evaluated locally by `simulate` against a\n * mock event's snapshot; a semantic gate cannot, so simulation reports it\n * undecided and assumes the run proceeds. Reach for `when.semantic` only when\n * the condition can't be expressed as a deterministic predicate — it is the\n * expensive path. Note that today the gate is consumed by local simulation\n * only: publishing an Experience does not send the gate to the server, so\n * neither flavor is enforced at trigger ingest yet.\n * @example\n * ```ts\n * // Deterministic: run only when the snapshot says the user is home.\n * const gate = when(($) => ({ eq: [$.location.place, 'home'] }));\n *\n * // Semantic: a natural-language condition classified server-side.\n * const winding = when.semantic('the user appears to be winding down for the evening');\n * ```\n */\nexport const when = Object.assign(deterministicGate, {\n\tsemantic: (prompt: string): ContextGate => ({ kind: 'semantic', prompt }),\n});\n\n/** A materialized predicate — a plain JSON object (refs collapsed to `{$ref}`). */\nconst predicateSchema = z.record(z.string(), z.json());\n\n/**\n * Zod schema for {@link ContextGate}. A discriminated union on `kind`:\n * `'deterministic'` carries a materialized `predicate` object, `'semantic'`\n * carries a non-empty `prompt` string. Exported so callers can validate a gate\n * at their own boundary; the runtime shape mirrors the `ContextGate` type.\n *\n * @example\n * ```ts\n * const gate = contextGateSchema.parse(incoming);\n * ```\n */\nexport const contextGateSchema = z\n\t.discriminatedUnion('kind', [\n\t\tz.object({\n\t\t\tkind: z.literal('deterministic').meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Gate kind discriminant — the deterministic predicate gate.',\n\t\t\t}),\n\t\t\tpredicate: predicateSchema,\n\t\t}),\n\t\tz.object({\n\t\t\tkind: z.literal('semantic').meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Gate kind discriminant — the model-classified semantic gate.',\n\t\t\t}),\n\t\t\tprompt: z.string().min(1).meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Natural-language prompt evaluated by the semantic (model-classified) gate.',\n\t\t\t}),\n\t\t}),\n\t])\n\t.meta({\n\t\tid: 'ContextGate',\n\t\ttitle: 'Context gate',\n\t\tdescription:\n\t\t\t'Gates the trigger at ingest (PRD §5.3). Deterministic when() (default) or the expensive semantic when.semantic().',\n\t});\n","// conversation — high-level browser voice helpers built on `voice`.\n//\n// Two pieces, both browser-only (they use getUserMedia / AudioWorklet / WebRTC):\n//\n// captureMicrophone() — turn the mic into a stream of 16-bit LE PCM chunks\n// suitable for `transcribeLive().sendAudio()`. The\n// AudioWorklet processor is inlined as a Blob URL, so\n// there is NO separate file for the app to serve.\n//\n// startConversation() — wire the whole loop in one call: STT (transcribeLive)\n// → sendText → agent TTS over LiveKit. Sane defaults\n// (publishMicrophone:true so the agent dispatches; a\n// unique deviceId so connections don't DUPLICATE_IDENTITY-\n// kick each other). Returns a push-to-talk handle.\n\nimport type { EraClient } from '../core/client.js';\nimport type { StreamEvent } from '../core/streaming.js';\nimport {\n\ttype LiveTranscribeSession,\n\ttype VoiceAck,\n\ttype VoiceAgentState,\n\ttype VoiceConnectionState,\n\ttype VoiceSession,\n\tvoice,\n} from './voice.js';\n\n// ─── microphone capture ──────────────────────────────────────────────────────\n\n/**\n * The AudioWorklet processor, inlined. Buffers Float32 mic samples and posts\n * 16-bit LE PCM chunks (~128ms at 16kHz) to the main thread. Pause/resume keep\n * the graph warm so the first chunk after a hold has zero startup lag.\n */\nconst PCM_WORKLET_SRC = `\nclass EraPcmProcessor extends AudioWorkletProcessor {\n constructor() {\n super();\n this.size = 2048;\n this.buf = new Float32Array(this.size);\n this.i = 0;\n this.paused = false;\n this.port.onmessage = (e) => {\n if (e.data && e.data.type === 'pause') this.paused = true;\n else if (e.data && e.data.type === 'resume') { this.paused = false; this.buf = new Float32Array(this.size); this.i = 0; }\n };\n }\n process(inputs) {\n if (this.paused) return true;\n const ch = inputs[0] && inputs[0][0];\n if (!ch) return true;\n for (let n = 0; n < ch.length; n++) {\n this.buf[this.i++] = ch[n];\n if (this.i >= this.size) {\n const pcm = new Int16Array(this.size);\n for (let j = 0; j < this.size; j++) {\n const s = Math.max(-1, Math.min(1, this.buf[j]));\n pcm[j] = Math.round(s * 32767);\n }\n this.port.postMessage({ pcm: pcm.buffer }, [pcm.buffer]);\n this.buf = new Float32Array(this.size);\n this.i = 0;\n }\n }\n return true;\n }\n}\nregisterProcessor('era-pcm-processor', EraPcmProcessor);\n`;\n\n/** Options for {@link captureMicrophone}. */\nexport interface MicCaptureOptions {\n\t/** Capture sample rate (Hz). Default 16000 — what server STT expects. */\n\tsampleRate?: number;\n\t/** Called with each 16-bit LE mono PCM chunk (`ArrayBuffer`). */\n\tonAudio: (pcm: ArrayBuffer) => void;\n\t/** Called if mic acquisition / worklet setup fails. */\n\tonError?: (err: Error) => void;\n}\n\n/** Handle returned by {@link captureMicrophone}. */\nexport interface MicCapture {\n\t/** Acquire the mic and start the audio graph (resolves once running). */\n\tstart(): Promise<void>;\n\t/** Stop emitting chunks but keep the graph warm for instant resume. */\n\tpause(): void;\n\t/** Resume emitting chunks after a pause. */\n\tresume(): void;\n\t/** Tear down: stop the mic track, the worklet, and the AudioContext. */\n\tstop(): void;\n}\n\n/**\n * Capture the browser microphone as 16-bit LE mono PCM, ready to pipe straight\n * into `voice(client).transcribeLive().sendAudio()`. The AudioWorklet processor\n * is inlined (Blob URL) so there is nothing for the app to host.\n *\n * @param opts - Capture configuration; see {@link MicCaptureOptions}. `onAudio`\n * is required and receives each 16-bit LE mono PCM chunk; `sampleRate` defaults\n * to 16000 Hz (what server STT expects) and `onError` is optional.\n * @returns A {@link MicCapture} handle with `start` / `pause` / `resume` /\n * `stop`. No audio is captured until `start()` resolves; chunks stop between\n * `pause()` and `resume()` while the audio graph stays warm.\n * @throws {DOMException} Propagated from `start()` (not thrown here) when the\n * browser denies microphone access or the AudioWorklet fails to initialize;\n * `onError` is also invoked (with the failure wrapped in an `Error` when it\n * isn't one already).\n *\n * @example\n * ```ts\n * const stt = await voice(era).transcribeLive({ sessionId, onFinal });\n * const mic = captureMicrophone({ onAudio: (pcm) => stt.sendAudio(pcm) });\n * await mic.start(); // hold-to-talk down\n * // mic.pause() // hold-to-talk up\n * ```\n */\nexport function captureMicrophone(opts: MicCaptureOptions): MicCapture {\n\tlet stream: MediaStream | null = null;\n\tlet ctx: AudioContext | null = null;\n\tlet node: AudioWorkletNode | null = null;\n\tlet workletUrl: string | null = null;\n\tlet recording = false;\n\n\treturn {\n\t\tasync start(): Promise<void> {\n\t\t\ttry {\n\t\t\t\tconst sampleRate = opts.sampleRate ?? 16_000;\n\t\t\t\tstream = await navigator.mediaDevices.getUserMedia({\n\t\t\t\t\taudio: {\n\t\t\t\t\t\tchannelCount: 1,\n\t\t\t\t\t\tsampleRate,\n\t\t\t\t\t\techoCancellation: true,\n\t\t\t\t\t\tnoiseSuppression: true,\n\t\t\t\t\t\t// Soft / far speech under-amplifies and won't transcribe;\n\t\t\t\t\t\t// explicit constraints suppress browser defaults, so request it.\n\t\t\t\t\t\tautoGainControl: true,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\tctx = new AudioContext({ sampleRate });\n\t\t\t\tworkletUrl = URL.createObjectURL(\n\t\t\t\t\tnew Blob([PCM_WORKLET_SRC], { type: 'application/javascript' }),\n\t\t\t\t);\n\t\t\t\tawait ctx.audioWorklet.addModule(workletUrl);\n\t\t\t\tconst source = ctx.createMediaStreamSource(stream);\n\t\t\t\tnode = new AudioWorkletNode(ctx, 'era-pcm-processor');\n\t\t\t\tnode.port.onmessage = (e: MessageEvent) => {\n\t\t\t\t\tif (recording) opts.onAudio(e.data.pcm as ArrayBuffer);\n\t\t\t\t};\n\t\t\t\tsource.connect(node);\n\t\t\t\trecording = true;\n\t\t\t} catch (err) {\n\t\t\t\topts.onError?.(err instanceof Error ? err : new Error(String(err)));\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t},\n\t\tpause(): void {\n\t\t\trecording = false;\n\t\t\tnode?.port.postMessage({ type: 'pause' });\n\t\t},\n\t\tresume(): void {\n\t\t\tnode?.port.postMessage({ type: 'resume' });\n\t\t\tif (ctx?.state === 'suspended') void ctx.resume();\n\t\t\trecording = true;\n\t\t},\n\t\tstop(): void {\n\t\t\trecording = false;\n\t\t\tstream?.getTracks().forEach((t) => {\n\t\t\t\tt.stop();\n\t\t\t});\n\t\t\tnode?.disconnect();\n\t\t\tif (ctx) void ctx.close();\n\t\t\tif (workletUrl) URL.revokeObjectURL(workletUrl);\n\t\t\tstream = null;\n\t\t\tctx = null;\n\t\t\tnode = null;\n\t\t\tworkletUrl = null;\n\t\t},\n\t};\n}\n\n// ─── startConversation ───────────────────────────────────────────────────────\n\n/** Options for {@link startConversation}. */\nexport interface StartConversationOptions {\n\t/** Experience to talk to. Omit to use the api key's default persona. */\n\texperienceId?: string;\n\t/** TTS voice config, e.g. `{ ttsConfig: { model, voice } }`. */\n\tvoiceConfig?: { ttsConfig?: Record<string, unknown> | null };\n\t/**\n\t * Override the client used for the STT WebSocket. Browser WebSockets can't\n\t * traverse a path-rewriting HTTP proxy, so if your main `client` points at\n\t * such a proxy, pass a second client here aimed straight at the API origin\n\t * (with a usable token). Defaults to the main `client`.\n\t */\n\tsttClient?: EraClient;\n\t/** Token for the STT socket `?token=` (defaults to the STT client's token). */\n\tsttToken?: string;\n\t/** STT sample rate. Default 16000. */\n\tsampleRate?: number;\n\t/** STT language (BCP-47). Default `'en-US'`. */\n\tlanguage?: string;\n\t/** Capture + stream the mic automatically. Default true. Set false to drive STT yourself. */\n\tmicrophone?: boolean;\n\t/** Participant identity for the LiveKit room; defaults to a fresh unique id (prevents `DUPLICATE_IDENTITY` when reconnecting). */\n\tdeviceId?: string;\n\t/** Force TURN relay for the LiveKit media. Default false. */\n\tforceTurnRelay?: boolean;\n\t/** Fire AGENT_NOT_PRESENT if no agent joins within this many ms. Default 12000. */\n\tagentJoinTimeoutMs?: number;\n\t/** Your speech, transcribed (`final` flips true on the committed segment). */\n\tonUserTranscript?: (text: string, final: boolean) => void;\n\t/** The agent's streamed reply (same events as `session.onResponse`). */\n\tonReply?: (event: StreamEvent) => void;\n\t/**\n\t * Connection errors. Common `code`s: `DUPLICATE_IDENTITY` (another\n\t * participant is using this id), `AGENT_NOT_PRESENT` (the backend agent\n\t * didn't join), `MIC_ERROR` (microphone access failed), `STT_ERROR`\n\t * (transcription failed), `SEND_TEXT_FAILED` (auto-submitting a\n\t * finalized transcript to the agent threw), and `AGENT_LEFT` (recoverable —\n\t * the agent left mid-session, e.g. after idle eviction).\n\t */\n\tonError?: (err: { message: string; code?: string }) => void;\n\t/** Connection state transitions for the LiveKit session. */\n\tonConnectionState?: (state: VoiceConnectionState) => void;\n\t/** Agent state transitions (listening, thinking, speaking, …). */\n\tonAgentState?: (state: VoiceAgentState) => void;\n}\n\n/** A live conversation handle returned by {@link startConversation}. */\nexport interface Conversation {\n\t/** The underlying LiveKit voice session (sendText / interrupt / meta / …). */\n\treadonly session: VoiceSession;\n\t/** The underlying STT socket. */\n\treadonly stt: LiveTranscribeSession;\n\t/** Push-to-talk down — resume the mic so speech streams to STT. */\n\tstartTalking(): void;\n\t/**\n\t * Push-to-talk up — pause the mic and finalize the STT segment. The final\n\t * transcript is auto-submitted via `sendText`, driving the agent.\n\t */\n\tstopTalking(): void;\n\t/** Send a typed turn (bypasses STT). */\n\tsendText(text: string): Promise<VoiceAck>;\n\t/** Barge-in: stop the agent's current TTS. */\n\tinterrupt(): Promise<VoiceAck>;\n\t/** Tear everything down (mic, STT socket, LiveKit session). */\n\tend(): Promise<void>;\n}\n\nfunction uniqueDeviceId(): string {\n\tconst c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n\tconst id =\n\t\tc?.randomUUID?.() ?? `${Math.random().toString(36).slice(2)}xxxxxxxx`;\n\treturn `web-${id.replace(/-/g, '').slice(0, 12)}`;\n}\n\n/**\n * Start a full voice conversation in one call: server-side STT (`transcribeLive`)\n * feeds `sendText`, and the agent replies with TTS audio over LiveKit (played by\n * the SDK-managed audio element). Returns a push-to-talk {@link Conversation}.\n *\n * Defaults are chosen to avoid the footguns: `publishMicrophone:true` (so the\n * backend dispatches an agent) and a unique `deviceId` (so overlapping\n * connections don't kick each other with DUPLICATE_IDENTITY).\n *\n * @remarks\n * Browser-only — it uses `getUserMedia`, `AudioWorklet`, and WebRTC. When\n * `microphone` is left at its default (`true`) the mic is acquired during setup\n * but held paused until the first `startTalking()`, so audio only streams while\n * push-to-talk is held. Each finalized STT transcript is auto-submitted to the\n * agent via `sendText`; a failure there is reported through `onError` with code\n * `SEND_TEXT_FAILED` rather than rejecting.\n *\n * @param client - The `EraClient` used for the LiveKit session and, unless\n * `opts.sttClient` overrides it, the STT WebSocket.\n * @param opts - Conversation configuration and lifecycle callbacks; see\n * {@link StartConversationOptions}. All fields are optional.\n * @returns A live {@link Conversation} handle with push-to-talk controls\n * (`startTalking` / `stopTalking`), `sendText`, `interrupt`, and `end`, plus\n * the underlying `session` and `stt` objects. Resolves once the LiveKit\n * session is connected, the STT socket is opening (audio sent before it\n * finishes connecting is buffered, so no speech is lost), and (by default)\n * the mic graph is running but paused.\n * @throws When setup fails — the LiveKit session can't connect (e.g. a bad\n * token), the STT pre-flight rejects, or the browser denies microphone\n * access — the returned promise rejects; wrap the call in `try/catch`.\n * Failures after setup (dropped connections, STT socket errors) are reported\n * through `onError` instead of rejecting.\n *\n * @example\n * ```ts\n * const convo = await startConversation(era, {\n * experienceId,\n * onUserTranscript: (t, final) => final && console.log('you:', t),\n * onReply: (ev) => ev.type === 'done' && console.log('agent:', ev.fullContent),\n * });\n * // hold-to-talk: convo.startTalking() … convo.stopTalking()\n * // or typed: convo.sendText('hello')\n * ```\n */\nexport async function startConversation(\n\tclient: EraClient,\n\topts: StartConversationOptions = {},\n): Promise<Conversation> {\n\t// 1. LiveKit session for the agent's TTS + turn submission.\n\tconst session = await voice(client).connect({\n\t\t...(opts.experienceId !== undefined && { experienceId: opts.experienceId }),\n\t\t...(opts.voiceConfig !== undefined && { voiceConfig: opts.voiceConfig }),\n\t\tpublishMicrophone: true,\n\t\tdeviceId: opts.deviceId ?? uniqueDeviceId(),\n\t\t...(opts.forceTurnRelay !== undefined && {\n\t\t\tforceTurnRelay: opts.forceTurnRelay,\n\t\t}),\n\t\t...(opts.agentJoinTimeoutMs !== undefined && {\n\t\t\tagentJoinTimeoutMs: opts.agentJoinTimeoutMs,\n\t\t}),\n\t\t...(opts.onConnectionState && {\n\t\t\tonConnectionState: opts.onConnectionState,\n\t\t}),\n\t\t...(opts.onAgentState && { onAgentState: opts.onAgentState }),\n\t\t...(opts.onError && { onError: opts.onError }),\n\t});\n\tif (opts.onReply) session.onResponse(opts.onReply);\n\n\t// 2. Server STT socket → auto-submit each final transcript as a turn.\n\tconst sttClient = opts.sttClient ?? client;\n\tconst sampleRate = opts.sampleRate ?? 16_000;\n\tconst stt = await voice(sttClient).transcribeLive({\n\t\tsessionId: String(session.sessionId ?? session.roomName ?? 'stt'),\n\t\tsampleRate,\n\t\t...(opts.language !== undefined && { language: opts.language }),\n\t\t...(opts.sttToken !== undefined && { token: opts.sttToken }),\n\t\tonInterim: (text) => opts.onUserTranscript?.(text, false),\n\t\tonFinal: (text) => {\n\t\t\tconst said = text.trim();\n\t\t\tif (!said) return;\n\t\t\topts.onUserTranscript?.(said, true);\n\t\t\tvoid session.sendText(said).catch((e: unknown) =>\n\t\t\t\topts.onError?.({\n\t\t\t\t\tmessage: e instanceof Error ? e.message : String(e),\n\t\t\t\t\tcode: 'SEND_TEXT_FAILED',\n\t\t\t\t}),\n\t\t\t);\n\t\t},\n\t\t...(opts.onError && {\n\t\t\tonError: (m: string) => opts.onError?.({ message: m, code: 'STT_ERROR' }),\n\t\t}),\n\t});\n\n\t// 3. Mic capture (default on) → feed STT. Warm but paused until startTalking().\n\tconst useMic = opts.microphone !== false;\n\tconst mic = useMic\n\t\t? captureMicrophone({\n\t\t\t\tsampleRate,\n\t\t\t\tonAudio: (pcm) => stt.sendAudio(pcm),\n\t\t\t\t...(opts.onError && {\n\t\t\t\t\tonError: (e: Error) =>\n\t\t\t\t\t\topts.onError?.({ message: e.message, code: 'MIC_ERROR' }),\n\t\t\t\t}),\n\t\t\t})\n\t\t: null;\n\tif (mic) {\n\t\tawait mic.start();\n\t\tmic.pause();\n\t}\n\n\treturn {\n\t\tsession,\n\t\tstt,\n\t\tstartTalking(): void {\n\t\t\tmic?.resume();\n\t\t},\n\t\tstopTalking(): void {\n\t\t\tmic?.pause();\n\t\t\tstt.endOfSpeech();\n\t\t},\n\t\tsendText(text: string): Promise<VoiceAck> {\n\t\t\treturn session.sendText(text);\n\t\t},\n\t\tinterrupt(): Promise<VoiceAck> {\n\t\t\treturn session.interrupt();\n\t\t},\n\t\tasync end(): Promise<void> {\n\t\t\tmic?.stop();\n\t\t\tstt.close();\n\t\t\tawait session.disconnect();\n\t\t},\n\t};\n}\n","// deviceStream module — the device WS command receiver (ERA-6031).\n//\n// The receiving half of the cross-device story: a JS-capable device (Pi,\n// desktop, kiosk) connects to the era-ingress device gateway\n// (`WSS /devices/{deviceId}/stream`, prefix-free per ERA-4428) with a\n// device-bound `era_sk_*` key and yields the typed command frames a chain's\n// `builtin:emit-output` publishes — `actuator` (ERA-4402) and `say`\n// (ERA-6126/6129) — while sending inbound `event`/`sensor` frames up the same\n// socket.\n//\n// Reliability contract (matches the gateway's Redis mailbox, ERA-6017/6018):\n// - Delivery is at-least-once. A frame un-acked when the socket drops is\n// redelivered on the NEXT socket. The dedup LRU therefore lives on the\n// DeviceStream (which owns the reconnect loop), NOT the transient socket —\n// it must be non-empty exactly when the post-reconnect duplicate arrives.\n// - Frames carrying an `idempotencyKey` are acked with\n// `{type:\"output.ack\", idempotencyKey}` as soon as they pass dedup, which\n// releases them from the server's `:proc` processing list. Dedup absorbs\n// the redelivery window between receipt and ack.\n// - The gateway pings every ~30s with `{type:\"ping\"}`; the stream answers\n// `{type:\"pong\"}` automatically.\n// - Reconnect is exponential backoff + full jitter, on by default. Close\n// code 1008 (auth rejection) NEVER reconnects — a bad key is surfaced,\n// not retried.\n//\n// Tree-shakeable subpath import:\n// `@era-laboratories/era-sdk/modules/deviceStream`.\n\nimport * as z from 'zod';\nimport type { WithRequired } from '../core/casing.js';\nimport type { EraClient } from '../core/client.js';\nimport type { RequestOptions } from '../core/types.js';\nimport { postDeviceKeysRenew as genPostDeviceKeysRenew } from '../generated/hub-api/sdk.gen.js';\nimport type { RenewDeviceKeyResponse } from '../generated/hub-api/types.gen.js';\nimport type {\n\tActuatorCommand as GenActuatorCommand,\n\tDeviceEventFrame as GenDeviceEventFrame,\n\tDeviceSensorFrame as GenDeviceSensorFrame,\n\tKeyStatus as GenKeyStatus,\n\tSayCommand as GenSayCommand,\n} from '../generated/ingress/types.gen.js';\nimport {\n\tzActuatorCommand,\n\tzDeviceCommand,\n\tzKeyStatus,\n\tzSayCommand,\n} from '../generated/ingress/zod.gen.js';\nimport { hubPath } from './hubPaths.js';\nimport type { DeviceKeyStore } from './keyStore.js';\n\n// ─── Frame contracts (server → device) ───────────────────────────────────────\n//\n// ERA-6206: the device WS frames are normative components in the ingress spec\n// — the SDK types are DERIVED from `src/generated/ingress/types.gen.ts` (the\n// wire truth), not re-declared. As of ingress v19 the generated frames mark the\n// `type` discriminator REQUIRED, so the `WithRequired<…, 'type'>` aliases below\n// are no-ops retained for API stability (the exported names predate v19).\n\n/**\n * An actuator command frame — targets a single physical actuator on the\n * device with a field map (`actuatorId` + catalog-open `fields`).\n * `idempotencyKey` is ALWAYS present (value `null` when the command carries no\n * key); `receiptToken` / `redelivery` / `seq` are omitted when absent;\n * `timestamp` is always present.\n */\nexport type ActuatorCommand = WithRequired<GenActuatorCommand, 'type'>;\n\n/**\n * A say command frame — device-targeted text to render or speak. Unlike\n * {@link ActuatorCommand}, say frames carry no `receiptToken`;\n * `audioRef`/`audioMime` are present only when server-side audio synthesis\n * succeeded — fetch `audioRef` promptly (its signed URL expires on a\n * minutes scale, and a redelivered frame carries a fresh URL) and only from\n * Google Cloud Storage hosts.\n */\nexport type SayCommand = WithRequired<GenSayCommand, 'type'>;\n\n/**\n * The discriminated union of consumer-facing command frames — what you\n * async-iterate off a {@link DeviceStream}. Narrow on `type` (`'actuator'` |\n * `'say'`) to handle each; control frames (ping, key_status, error) never\n * reach this union.\n */\nexport type DeviceCommand = ActuatorCommand | SayCommand;\n\n/**\n * Runtime zod validator for the `actuator` command frame\n * ({@link ActuatorCommand}). Use it to parse or narrow a raw frame outside the\n * stream's own iteration (the stream validates internally; you rarely need\n * this directly).\n */\nexport const actuatorCommandSchema = zActuatorCommand;\n/**\n * Runtime zod validator for the `say` command frame ({@link SayCommand}).\n * Use it to parse or narrow a raw frame outside the stream's own iteration\n * (the stream validates internally; you rarely need this directly).\n */\nexport const sayCommandSchema = zSayCommand;\n/**\n * Runtime zod validator for the full {@link DeviceCommand} union — a\n * discriminated union on `type` (`'actuator'` | `'say'`).\n */\nexport const deviceCommandSchema = zDeviceCommand.meta({\n\tid: 'DeviceCommand',\n\ttitle: 'DeviceCommand',\n\tdescription:\n\t\t'The union of consumer-facing device command frames (actuator | say) yielded by deviceStream — the generated spec schemas; ping/key_status control frames are absorbed internally.',\n});\n\n// Control frames handled internally, never yielded: the liveness ping and the\n// key-rotation advisory (ERA-6037; surfaced via onKeyStatus, not the iterator).\n// key_status is spec-derived (zKeyStatus, ERA-6206).\nconst pingFrameSchema = z.object({ type: z.literal('ping') });\nconst keyStatusFrameSchema = zKeyStatus;\n// Inbound error advisory (era-ingress half of ERA-6202): the gateway rejects a\n// malformed inbound frame with `{type:'error', code, detail, name?}` instead of\n// silently dropping it. Tolerant parse (z.object ignores unknown keys) so a\n// server that adds fields later doesn't break this path. Surfaced via onError,\n// never yielded to the command iterator.\nconst errorFrameSchema = z.object({\n\ttype: z.literal('error'),\n\tcode: z.string(),\n\tdetail: z.string(),\n\tname: z.string().optional(),\n});\n\n// ─── Inbound frames (device → server) ────────────────────────────────────────\n//\n// ERA-6206 split: the SDK's former single `DeviceEventFrame` conflated the\n// gateway's two distinct inbound validators (`_InboundEvent` /\n// `_InboundSensor`, both `extra='forbid'`) — it could not represent a valid\n// sensor frame. The generated spec models them separately; the SDK adopts the\n// split: `DeviceEventFrame` (event: `name`/`data`) + `DeviceSensorFrame`\n// (sensor: `sensorId`/`value`/`unit`), unioned as `DeviceInboundFrame`.\n\n/**\n * An inbound `event` frame the device publishes up its socket — a named\n * occurrence with an optional data payload. Carries `name`/`data` and forbids\n * the sensor fields.\n */\nexport type DeviceEventFrame = GenDeviceEventFrame;\n\n/**\n * An inbound `sensor` frame the device publishes up its socket — a single\n * sensor reading. Carries `sensorId`/`value`/`unit` and forbids `name`/`data`.\n */\nexport type DeviceSensorFrame = GenDeviceSensorFrame;\n\n/** Any inbound frame `DeviceStream.send` accepts (event or sensor). */\nexport type DeviceInboundFrame = DeviceEventFrame | DeviceSensorFrame;\n\n// ─── Options / handle ────────────────────────────────────────────────────────\n\n/**\n * Key-rotation advisory the gateway sends periodically over the socket\n * (`{ exp, renewAfter }`, both epoch seconds): renew once `now >= renewAfter`.\n * Surfaced via {@link DeviceStreamCommonOptions.onKeyStatus}; with\n * `autoRenew: true` the stream acts on it automatically.\n */\nexport type KeyStatus = GenKeyStatus;\n\n/**\n * The successor key minted by an auto-renew — the subset of the key-renewal\n * response the stream surfaces. By the time\n * {@link DeviceStreamCommonOptions.onKeyRenewed} fires, the key has already\n * been durably persisted to the {@link DeviceKeyStore} and activated — this\n * callback is notify-only (logging, fleet telemetry). The predecessor is\n * revoked the moment this key first authenticates.\n */\nexport type RenewedDeviceKey = Pick<\n\tRenewDeviceKeyResponse,\n\t'key' | 'expiresAt' | 'deviceId'\n>;\n\ninterface DeviceStreamCommonOptions {\n\t/**\n\t * Bearer for the socket (`era_sk_*` device-bound key). In Node it rides\n\t * the `Authorization` upgrade header; in browsers — which can't set\n\t * WebSocket headers — it falls back to a `?token=` query param.\n\t * Precedence: `token` → the `keyStore`'s stored key (`pending` slot\n\t * first, then `current`) → the client's access token.\n\t */\n\ttoken?: string;\n\t/** Reconnect with exponential backoff + jitter. Default true. */\n\treconnect?: boolean;\n\t/** Absorb at-least-once duplicates via an idempotencyKey LRU. Default true. */\n\tdedupe?: boolean;\n\t/** Dedup LRU capacity. Default 256. */\n\tdedupeCapacity?: number;\n\t/**\n\t * Bounded FIFO capacity for inbound frames `send()` before the socket is OPEN\n\t * (or while it reconnects). Buffered frames flush in insertion order on the\n\t * next open; an overflow throws. Default 64.\n\t */\n\tsendBufferCapacity?: number;\n\t/** Base reconnect delay in ms (doubles per attempt, full jitter). Default 500. */\n\tbackoffBaseMs?: number;\n\t/** Reconnect delay ceiling in ms. Default 30_000. */\n\tbackoffMaxMs?: number;\n\t/**\n\t * Execute-and-ack handler for delivery receipts. When provided the\n\t * stream runs in HANDLER MODE: every command frame is passed here instead\n\t * of the async iterator, and the execution ack is sent automatically when\n\t * the handler settles:\n\t *\n\t * - resolves → `status:'ok'` → `output_delivered`\n\t * - throws {@link CommandRejectedError} → `status:'rejected'` (terminal,\n\t * the server will NOT retry — use for malformed/unexecutable commands)\n\t * - throws anything else → `status:'error'` (retryable — the server\n\t * re-publishes with backoff)\n\t *\n\t * Without a handler (iterator mode), call {@link DeviceStream.ack} after\n\t * executing each command — otherwise the server times the receipt out and\n\t * re-delivers.\n\t */\n\tonExecute?: (cmd: DeviceCommand) => void | Promise<void>;\n\t/**\n\t * Delivery-receipts completion hook: called after a command has been\n\t * executed (handler mode — after `onExecute` resolves) or yielded to the\n\t * consumer (iterator mode).\n\t */\n\tonDelivered?: (cmd: DeviceCommand) => void;\n\t/** Called with each {@link KeyStatus} key-rotation advisory frame. */\n\tonKeyStatus?: (status: KeyStatus) => void;\n\t/**\n\t * Notify-only: fired after an auto-renewed successor key has been durably\n\t * persisted to the {@link DeviceKeyStore} AND activated. Persistence is the\n\t * store's job, not this callback's. Fired only in autoRenew mode.\n\t */\n\tonKeyRenewed?: (renewed: RenewedDeviceKey) => void;\n\t/** Socket-level errors and auth rejections. */\n\tonError?: (err: { code?: number; reason?: string; message: string }) => void;\n\t/** Abort to close the stream (equivalent to `close()`). */\n\tsignal?: AbortSignal;\n}\n\n/**\n * Options for `connect()`. Discriminated on `autoRenew`: enabling\n * auto-renewal REQUIRES a durable {@link DeviceKeyStore} — the SDK will not\n * activate a successor key it cannot prove was persisted, because a device\n * that restarts holding only its rotated-out predecessor trips the server's\n * reuse detection and is flagged `credential_compromised`.\n *\n * With `autoRenew: true` the stream drives the full two-slot rotation\n * protocol on a `key_status` advisory past `renewAfter`:\n *\n * 1. `POST /hub-api/device-keys/renew` (authed as the current key)\n * 2. `await keyStore.savePending(successor)` + read-back verification —\n * activation is GATED on this; a failing store degrades to advisory\n * (surfaced via `onError`, the live stream is never killed)\n * 3. swap the connection token and proactively cycle the socket so the\n * successor authenticates immediately (inside the predecessor's grace\n * window) instead of waiting for a natural reconnect\n * 4. on the successor's first successful open, `await keyStore.promote()`\n *\n * Boot/connect side: when no explicit `token` is given the store's `pending`\n * slot is presented FIRST, then `current` (crash recovery for a reboot\n * between activation and promotion — presenting a rotated-out `current`\n * first would trip reuse detection). A first-connect 1008 falls back ONCE to\n * the other slot.\n */\nexport type DeviceStreamOptions = DeviceStreamCommonOptions &\n\t(\n\t\t| {\n\t\t\t\t/** Auto-renewal off (default): `key_status` advisories are surfaced via `onKeyStatus` only. */\n\t\t\t\tautoRenew?: false;\n\t\t\t\tkeyStore?: DeviceKeyStore;\n\t\t }\n\t\t| {\n\t\t\t\t/** Enable hands-free key rotation. REQUIRES a durable `keyStore`. */\n\t\t\t\tautoRenew: true;\n\t\t\t\t/** Durable two-slot store the rotation protocol writes through. */\n\t\t\t\tkeyStore: DeviceKeyStore;\n\t\t }\n\t);\n\n/**\n * The live connection state of a {@link DeviceStream}, readable at any time via\n * `stream.state`. Use it to drive device UI/telemetry (e.g. show a \"reconnecting\"\n * indicator) without hooking `onError`.\n *\n * - `connecting` — the first socket attempt is in flight; no frames have flowed yet.\n * - `open` — the socket is live; `send()` writes straight through.\n * - `reconnecting` — the socket dropped and a backoff reconnect is scheduled;\n * `send()` buffers into the FIFO until the next open.\n * - `closed` — terminal: `close()` was called, the `signal` was aborted, a 1008\n * auth rejection ended the stream, or the socket dropped with `reconnect: false`.\n * Iteration has ended and will not resume.\n */\nexport type DeviceStreamState =\n\t| 'connecting'\n\t| 'open'\n\t| 'reconnecting'\n\t| 'closed';\n\n// facade-exempt(protocol): client-side stream handle — wire payloads are derived from the generated device frames; `state` and the `ack` result (the device→server output.ack execution-receipt fields) have no emitter schema in the ingress spec. ERA-6206\n/**\n * A live device command stream: async-iterate the typed frames, `send()`\n * inbound events, `close()` when done. Ping/pong, acking, dedup, and\n * reconnect are handled internally.\n */\nexport interface DeviceStream extends AsyncIterable<DeviceCommand> {\n\t/** The live connection state — see {@link DeviceStreamState}. */\n\treadonly state: DeviceStreamState;\n\t/** Publish an inbound event/sensor frame. Throws if the stream is closed. */\n\tsend(frame: DeviceInboundFrame): void;\n\t/**\n\t * Send the execution ack for a command received in iterator mode. Call\n\t * after executing the command; the receipt lands as `output_delivered` /\n\t * `output_failed` / `output_undeliverable` on the originating chain run.\n\t * No-op for commands without an `idempotencyKey`. Handler mode\n\t * (`onExecute`) acks automatically — don't also call this.\n\t */\n\tack(\n\t\tcmd: DeviceCommand,\n\t\tresult?: { status?: 'ok' | 'error' | 'rejected'; detail?: string },\n\t): void;\n\t/** Close the stream and end iteration. Idempotent. */\n\tclose(): void;\n}\n\n/**\n * Throw from `onExecute` to reject a command as unexecutable: the execution\n * ack carries `status:'rejected'` and the server settles the receipt as\n * terminal `output_undeliverable` WITHOUT retrying — unlike any other thrown\n * error, which is retryable (`status:'error'`).\n */\nexport class CommandRejectedError extends Error {\n\t/**\n\t * @param message - Human-readable reason the command cannot be executed;\n\t * forwarded as the rejection `detail` on the execution ack.\n\t */\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = 'CommandRejectedError';\n\t}\n}\n\n/** The device-stream module surface returned by {@link deviceStream}. */\nexport interface DeviceStreamModule {\n\t/**\n\t * Open the device command socket to `WSS /devices/{deviceId}/stream` on the\n\t * ingress origin and resolve to a live {@link DeviceStream} — the device's\n\t * firmware boot call. Async-iterate the returned handle for the typed\n\t * commands a chain's `builtin:emit-output` publishes, `send()` inbound\n\t * events/sensor readings, and `ack()` each executed command.\n\t *\n\t * @param deviceId - The device the key is bound to; interpolated into the\n\t * upgrade URL (URL-encoded).\n\t * @param opts - Connection options. When omitted, reconnect and dedup are on\n\t * and the bearer is sourced from the client's access token. See\n\t * {@link DeviceStreamOptions}.\n\t * @returns A live {@link DeviceStream} handle once the initial socket attempt\n\t * has been dispatched (the promise resolves before the first open; watch\n\t * `stream.state` / `onError` for connection outcome).\n\t * @throws {TypeError} When the client's `ingressUrl` is a cleartext `ws://`\n\t * (or `http://`) origin outside loopback — a device key must not ride an\n\t * unencrypted socket.\n\t * @throws {Error} When no global `WebSocket` is available (older Node without\n\t * `globalThis.WebSocket` set).\n\t *\n\t * @remarks\n\t * Bearer precedence: `opts.token` → the `keyStore`'s stored key → the\n\t * client's access token. In Node the key rides the `Authorization: Bearer`\n\t * upgrade header (never lands in access logs); browsers fall back to a\n\t * `?token=` query param. A close with code 1008 (auth rejection) is terminal\n\t * — a bad key is surfaced via `onError` and the stream ends rather than\n\t * retrying. With `autoRenew: true`, an advisory past `renewAfter` drives a\n\t * `POST /hub-api/device-keys/renew` (returns the successor key) inside the\n\t * predecessor's grace window.\n\t *\n\t * @example\n\t * ```ts\n\t * const stream = await deviceStream(era).connect('dev_0000000000', {\n\t * keyStore: fileKeyStore('/var/lib/mydevice/era-key.json'),\n\t * autoRenew: true,\n\t * onError: (err) => console.error('[stream]', err.message),\n\t * });\n\t * stream.send({ type: 'sensor', sensorId: 'temperature', value: 21.4, unit: 'celsius' });\n\t * for await (const cmd of stream) {\n\t * if (cmd.type === 'say') await speak(cmd.text);\n\t * else await drive(cmd.actuatorId, cmd.fields);\n\t * stream.ack(cmd);\n\t * }\n\t * ```\n\t */\n\tconnect(deviceId: string, opts?: DeviceStreamOptions): Promise<DeviceStream>;\n}\n\n// ─── Internals ───────────────────────────────────────────────────────────────\n\n/** Insertion-ordered idempotencyKey LRU (Map preserves insertion order). */\nclass KeyLru {\n\tprivate readonly seen = new Map<string, true>();\n\tconstructor(private readonly capacity: number) {}\n\t/** True if `key` was already seen (and refreshes its recency). */\n\tcheck(key: string): boolean {\n\t\tconst dup = this.seen.has(key);\n\t\tif (dup) this.seen.delete(key);\n\t\tthis.seen.set(key, true);\n\t\tif (this.seen.size > this.capacity) {\n\t\t\tconst oldest = this.seen.keys().next().value;\n\t\t\tif (oldest !== undefined) this.seen.delete(oldest);\n\t\t}\n\t\treturn dup;\n\t}\n}\n\nconst WS_CLOSE_AUTH_REJECTED = 1008;\n\nfunction resolveWs(): typeof WebSocket {\n\tconst WS = (globalThis as { WebSocket?: typeof WebSocket }).WebSocket;\n\tif (!WS) {\n\t\tthrow new Error(\n\t\t\t'deviceStream requires a WebSocket implementation. It is available in browsers and Node ≥ 22; in older Node, set globalThis.WebSocket (e.g. from the \"ws\" package).',\n\t\t);\n\t}\n\treturn WS;\n}\n\n// ERA-6270: constructor shape for runtimes whose WebSocket accepts a\n// non-standard `{ headers }` init (Node ≥ 22's undici global, the \"ws\"\n// package). The DOM lib types the second argument as protocols only, so the\n// header path casts through this.\ntype HeaderCapableWebSocket = new (\n\turl: string,\n\tinit?: { headers: Record<string, string> },\n) => WebSocket;\n\n/**\n * True when the runtime's WebSocket can send an `Authorization` upgrade\n * header (ERA-6270). Node's global WebSocket (undici, Node ≥ 22) and the\n * `ws` package both accept a `{ headers }` init object; browser/spec-strict\n * constructors (browsers, Electron renderer, Deno) throw a TypeError on an\n * object second argument, so those runtimes stay on the `?token=` query-param\n * fallback (the only browser-compatible transport, ERA-3620). Bun is excluded\n * conservatively — query-param there is today's behavior, not a regression.\n */\nfunction wsHeaderAuthCapable(): boolean {\n\tconst g = globalThis as {\n\t\tprocess?: { versions?: Record<string, string | undefined> };\n\t\twindow?: unknown;\n\t\tDeno?: unknown;\n\t\tBun?: unknown;\n\t};\n\treturn (\n\t\ttypeof g.process?.versions?.node === 'string' &&\n\t\ttypeof g.window === 'undefined' &&\n\t\ttypeof g.Deno === 'undefined' &&\n\t\ttypeof g.Bun === 'undefined'\n\t);\n}\n\n/** Loopback hosts exempt from the ERA-6271 cleartext-ws:// guard. */\nfunction isLoopbackHost(hostWithPort: string): boolean {\n\tconst h = hostWithPort.toLowerCase();\n\tif (h.startsWith('[')) return h.startsWith('[::1]');\n\tconst name = h.split('/')[0]?.split(':')[0] ?? '';\n\treturn name === 'localhost' || name === '127.0.0.1';\n}\n\n/**\n * Construct the device-stream module bound to an {@link EraClient} — the entry\n * point a JS-capable device (Pi, kiosk, desktop) uses to join the cross-device\n * command loop. Call `deviceStream(era).connect(deviceId, opts)` to open the\n * socket; the module reads `client.config.ingressUrl` for the gateway origin\n * and `client.getAccessToken()` as the fallback bearer.\n *\n * @param client - The Era client whose `ingressUrl` and auth back the socket.\n * @returns A {@link DeviceStreamModule} exposing `connect`.\n *\n * @example\n * ```ts\n * import { createEraClient, deviceStream } from '@era-laboratories/era-sdk';\n * const era = createEraClient({ auth: { mode: 'apiKey', apiKey: process.env.ERA_DEVICE_KEY! } });\n * const stream = await deviceStream(era).connect('dev_0000000000', { token: storedKey });\n * ```\n */\nexport function deviceStream(client: EraClient): DeviceStreamModule {\n\treturn {\n\t\tasync connect(\n\t\t\tdeviceId: string,\n\t\t\topts: DeviceStreamOptions = {},\n\t\t): Promise<DeviceStream> {\n\t\t\tconst WS = resolveWs();\n\t\t\t// Prefix-free device gateway path (ERA-6021/ERA-4428). Auth transport\n\t\t\t// (ERA-6270): Authorization header where the runtime supports it\n\t\t\t// (Node), `?token=` query param only as the browser fallback.\n\t\t\tconst base = client.config.ingressUrl;\n\t\t\tconst wsProtocol = base.startsWith('https') ? 'wss:' : 'ws:';\n\t\t\tconst wsHost = base.replace(/^https?:\\/\\//, '').replace(/\\/$/, '');\n\t\t\t// ERA-6271: refuse cleartext ws:// outside loopback — a device key\n\t\t\t// must not ride an unencrypted socket. An http:// ingressUrl is a\n\t\t\t// misconfiguration, not a transport choice.\n\t\t\tif (wsProtocol === 'ws:' && !isLoopbackHost(wsHost)) {\n\t\t\t\tthrow new TypeError(\n\t\t\t\t\t`deviceStream: refusing cleartext ws:// to \"${wsHost}\" — a device key must not ride an unencrypted socket. Use an https ingressUrl (or localhost for development).`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst headerAuth = wsHeaderAuthCapable();\n\t\t\t// Mutable so auto-renew (ERA-6037/6198) can swap in the successor key;\n\t\t\t// each reconnect rebuilds the URL from the current token. Sourcing:\n\t\t\t// explicit token → keyStore `pending` slot → keyStore `current` slot →\n\t\t\t// client access token.\n\t\t\tconst store = opts.keyStore;\n\t\t\tconst stored = store ? await store.load() : undefined;\n\t\t\t// Present PENDING FIRST (ERA-6202 Fix 3). The activation-crash window\n\t\t\t// leaves the live successor in `pending` and a rotated-out predecessor in\n\t\t\t// `current`; presenting `current` first would ride the revoked predecessor\n\t\t\t// and brick the device (lineage revocation). A rotated-out key only ever\n\t\t\t// occupies `current`, never `pending`, so presenting `pending` first can\n\t\t\t// never trip server reuse detection (hub-api deviceKeyRenewal.ts:216\n\t\t\t// revokes-but-does-not-rotate a replaced successor; introspect.ts trips\n\t\t\t// reuse only on a rotated-out key).\n\t\t\tlet currentToken =\n\t\t\t\topts.token ??\n\t\t\t\tstored?.pending ??\n\t\t\t\tstored?.current ??\n\t\t\t\t(await client.getAccessToken()) ??\n\t\t\t\tundefined;\n\t\t\t// Crash recovery (ERA-6202): on a first-connect 1008, fall back ONCE to\n\t\t\t// the OTHER slot. When `pending` was presented first, the fallback is\n\t\t\t// `current`; falling back TO `current` must NOT promote (that would\n\t\t\t// clobber a good `current` with a stale `pending` — see the onclose 1008\n\t\t\t// handler). Only set when no explicit token was given and both slots exist\n\t\t\t// and differ.\n\t\t\tlet fallbackToken =\n\t\t\t\topts.token === undefined &&\n\t\t\t\tstored?.pending !== undefined &&\n\t\t\t\tstored?.current !== undefined &&\n\t\t\t\tstored.current !== currentToken\n\t\t\t\t\t? stored.current\n\t\t\t\t\t: undefined;\n\t\t\t// Promote only when the token being presented IS the `pending` slot:\n\t\t\t// booting straight onto `pending` (no explicit token, pending exists)\n\t\t\t// means a successful open confirms the successor and should promote it.\n\t\t\tlet promoteOnOpen =\n\t\t\t\topts.token === undefined &&\n\t\t\t\tstored?.pending !== undefined &&\n\t\t\t\tcurrentToken === stored.pending;\n\t\t\t// ERA-6270: in header mode the URL carries NO token — the key rides\n\t\t\t// the Authorization upgrade header instead, so it never lands in\n\t\t\t// access logs, HAR exports, or proxy request lines.\n\t\t\tconst buildUrl = (): string => {\n\t\t\t\tconst params = new URLSearchParams();\n\t\t\t\tif (currentToken && !headerAuth) params.set('token', currentToken);\n\t\t\t\tconst qs = params.size > 0 ? `?${params.toString()}` : '';\n\t\t\t\treturn `${wsProtocol}//${wsHost}/devices/${encodeURIComponent(deviceId)}/stream${qs}`;\n\t\t\t};\n\t\t\t// Both branches read `currentToken` at call time so a reconnect after\n\t\t\t// an auto-renew (ERA-6037/6198) authenticates as the successor key.\n\t\t\tconst openSocket = (): WebSocket =>\n\t\t\t\theaderAuth && currentToken\n\t\t\t\t\t? new (WS as unknown as HeaderCapableWebSocket)(buildUrl(), {\n\t\t\t\t\t\t\theaders: { Authorization: `Bearer ${currentToken}` },\n\t\t\t\t\t\t})\n\t\t\t\t\t: new WS(buildUrl());\n\n\t\t\tconst reconnect = opts.reconnect ?? true;\n\t\t\tconst dedupe = opts.dedupe ?? true;\n\t\t\t// Bounded FIFO for inbound frames send() before the socket is OPEN or\n\t\t\t// while it reconnects (ERA-6202). Serialized (already timestamp-stamped)\n\t\t\t// frames queue here and flush in insertion order on the next open. NOTE:\n\t\t\t// execution acks (sendExecutionAck) intentionally do NOT buffer — the\n\t\t\t// server redelivers un-acked commands and dedup absorbs the replay, so a\n\t\t\t// dropped ack self-heals; user events are not redelivered, so they must.\n\t\t\tconst sendBuffer: string[] = [];\n\t\t\tconst SEND_BUFFER_CAP = opts.sendBufferCapacity ?? 64;\n\t\t\t// The LRU lives HERE — on the stream, across socket reconnects — so\n\t\t\t// a 6018 redelivery after a drop is absorbed (see module header).\n\t\t\tconst lru = new KeyLru(opts.dedupeCapacity ?? 256);\n\t\t\t// ERA-6033: executed-command results, keyed by idempotencyKey. When a\n\t\t\t// redelivery arrives for a command whose Ack B was lost (socket died\n\t\t\t// between execute and ack), the recorded result is re-acked instead of\n\t\t\t// re-executing — closing the at-least-once receipt loop without\n\t\t\t// double actuation.\n\t\t\tconst ackResults = new Map<\n\t\t\t\tstring,\n\t\t\t\t{ status: 'ok' | 'error' | 'rejected'; detail?: string }\n\t\t\t>();\n\t\t\tconst rememberResult = (\n\t\t\t\tidem: string,\n\t\t\t\tresult: { status: 'ok' | 'error' | 'rejected'; detail?: string },\n\t\t\t): void => {\n\t\t\t\tackResults.delete(idem);\n\t\t\t\tackResults.set(idem, result);\n\t\t\t\tif (ackResults.size > (opts.dedupeCapacity ?? 256)) {\n\t\t\t\t\tconst oldest = ackResults.keys().next().value;\n\t\t\t\t\tif (oldest !== undefined) ackResults.delete(oldest);\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tlet state: DeviceStreamState = 'connecting';\n\t\t\tlet socket: WebSocket | null = null;\n\t\t\tlet attempts = 0;\n\t\t\tlet closed = false;\n\t\t\tlet reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n\n\t\t\t// ERA-6037/6198 auto-renew state. `renewing` guards a renew+persist in\n\t\t\t// flight; `renewedPending` is set once a successor is durably persisted\n\t\t\t// + activated and cleared on the next successful socket open — together\n\t\t\t// they cap renewal at one cycle per successor. `renewAttempts` bounds\n\t\t\t// persist-failure retries so a broken store can't burn the server's\n\t\t\t// per-key renewal rate limit (5/24h): an unused successor is safely\n\t\t\t// replaced by the server's crash-recovery path on a later renew.\n\t\t\tconst autoRenew = opts.autoRenew ?? false;\n\t\t\tconst MAX_RENEW_ATTEMPTS = 3;\n\t\t\tlet renewing = false;\n\t\t\tlet renewedPending = false;\n\t\t\tlet renewAttempts = 0;\n\n\t\t\tconst maybeRenew = (status: KeyStatus): void => {\n\t\t\t\tif (!autoRenew || renewing || renewedPending) return;\n\t\t\t\tif (renewAttempts >= MAX_RENEW_ATTEMPTS) return; // degraded to advisory\n\t\t\t\t// exp/renewAfter are epoch seconds; only renew inside the window.\n\t\t\t\tif (Date.now() / 1000 < status.renewAfter) return;\n\t\t\t\trenewing = true;\n\t\t\t\trenewAttempts += 1;\n\t\t\t\tvoid (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst init: RequestOptions & { method?: string } = {\n\t\t\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\t\t\t// Renew authenticates AS the key being renewed — pin the\n\t\t\t\t\t\t\t// current stream token, not the client's default auth.\n\t\t\t\t\t\t\t...(currentToken !== undefined\n\t\t\t\t\t\t\t\t? { headers: { authorization: `Bearer ${currentToken}` } }\n\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst renewed = await client.hub<RenewDeviceKeyResponse>(\n\t\t\t\t\t\t\thubPath(genPostDeviceKeysRenew),\n\t\t\t\t\t\t\tinit,\n\t\t\t\t\t\t);\n\t\t\t\t\t\t// ACTIVATION IS GATED ON DURABLE PERSISTENCE (ERA-6198). The\n\t\t\t\t\t\t// server revokes the predecessor on the successor's first auth,\n\t\t\t\t\t\t// so using a key the store hasn't confirmed risks a reboot into\n\t\t\t\t\t\t// a reuse-trip (`credential_compromised`). Write-ahead, then\n\t\t\t\t\t\t// read back to catch no-op store implementations.\n\t\t\t\t\t\t// biome-ignore lint/style/noNonNullAssertion: autoRenew:true requires keyStore by type; guarded by `autoRenew` above\n\t\t\t\t\t\tconst s = store!;\n\t\t\t\t\t\tawait s.savePending(renewed.key);\n\t\t\t\t\t\tconst persisted = await s.load();\n\t\t\t\t\t\tif (persisted.pending !== renewed.key) {\n\t\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\t'keyStore read-back mismatch — savePending resolved but load() does not return the successor. ' +\n\t\t\t\t\t\t\t\t\t'The store is not persisting; refusing to activate the renewed key.',\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Persisted — safe to activate. Swap the token and proactively\n\t\t\t\t\t\t// cycle the socket so the successor authenticates NOW, inside\n\t\t\t\t\t\t// the predecessor's grace window (a never-reconnecting socket\n\t\t\t\t\t\t// would otherwise ride the old key to hard expiry, ERA-6198\n\t\t\t\t\t\t// Gap 2). The at-least-once + dedup machinery absorbs the\n\t\t\t\t\t\t// redelivery window exactly as on any reconnect.\n\t\t\t\t\t\tcurrentToken = renewed.key;\n\t\t\t\t\t\trenewedPending = true;\n\t\t\t\t\t\t// Explicit true: the fresh successor is the pending slot but was\n\t\t\t\t\t\t// just written and is not yet readable as stored.pending here —\n\t\t\t\t\t\t// promote only after its first successful open (ERA-6202).\n\t\t\t\t\t\tpromoteOnOpen = true;\n\t\t\t\t\t\tfallbackToken = undefined; // fresh successor supersedes any stale slot\n\t\t\t\t\t\topts.onKeyRenewed?.({\n\t\t\t\t\t\t\tkey: renewed.key,\n\t\t\t\t\t\t\texpiresAt: renewed.expiresAt,\n\t\t\t\t\t\t\tdeviceId: renewed.deviceId,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (reconnect && socket && socket.readyState === WS.OPEN) {\n\t\t\t\t\t\t\t// Graceful cycle: the onclose handler sees a non-1008 close\n\t\t\t\t\t\t\t// and schedules the reconnect, which opens with the new key.\n\t\t\t\t\t\t\t// Skipped when reconnect is disabled — the swap then applies\n\t\t\t\t\t\t\t// to whatever connection the caller makes next.\n\t\t\t\t\t\t\tsocket.close(1000, 'era-sdk key rotation');\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t// Degrade to advisory — a failed renew/persist must never kill\n\t\t\t\t\t\t// the stream; the token was NOT swapped, so the live socket and\n\t\t\t\t\t\t// any reconnect keep using the still-valid predecessor.\n\t\t\t\t\t\topts.onError?.({\n\t\t\t\t\t\t\tmessage: `device key auto-renew failed (attempt ${renewAttempts}/${MAX_RENEW_ATTEMPTS}): ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t\t\t\t});\n\t\t\t\t\t} finally {\n\t\t\t\t\t\trenewing = false;\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t};\n\n\t\t\t// Async-iterator handshake: frames queue until a consumer awaits.\n\t\t\tconst queue: DeviceCommand[] = [];\n\t\t\tlet wake: (() => void) | null = null;\n\t\t\tconst notify = (): void => {\n\t\t\t\twake?.();\n\t\t\t\twake = null;\n\t\t\t};\n\n\t\t\t// Ack B (ERA-6033): the execution ack. `status` present distinguishes\n\t\t\t// it from the bare receipt ack on the wire; `receiptToken` is echoed\n\t\t\t// so the server correlates the receipt to its chain-run event.\n\t\t\tconst sendExecutionAck = (\n\t\t\t\tcmd: DeviceCommand,\n\t\t\t\tresult: { status: 'ok' | 'error' | 'rejected'; detail?: string },\n\t\t\t): void => {\n\t\t\t\tconst idem = cmd.idempotencyKey;\n\t\t\t\tif (!idem) return; // nothing to correlate server-side\n\t\t\t\trememberResult(idem, result);\n\t\t\t\t// ERA-6206: only actuator frames carry a receiptToken on the wire —\n\t\t\t\t// the say wire copy strips it (see the generated SayCommand).\n\t\t\t\tconst receiptToken =\n\t\t\t\t\tcmd.type === 'actuator' ? cmd.receiptToken : undefined;\n\t\t\t\tif (socket && socket.readyState === WS.OPEN) {\n\t\t\t\t\tsocket.send(\n\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\ttype: 'output.ack',\n\t\t\t\t\t\t\tidempotencyKey: idem,\n\t\t\t\t\t\t\tstatus: result.status,\n\t\t\t\t\t\t\t...(result.detail !== undefined && { detail: result.detail }),\n\t\t\t\t\t\t\t...(receiptToken != null && { receiptToken }),\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tconst execute = (cmd: DeviceCommand): void => {\n\t\t\t\t// Handler mode: run onExecute, then ack with its outcome. Detached\n\t\t\t\t// on purpose — a slow actuator must not block the read pump.\n\t\t\t\tvoid (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// biome-ignore lint/style/noNonNullAssertion: execute() is only reached in handler mode\n\t\t\t\t\t\tawait opts.onExecute!(cmd);\n\t\t\t\t\t\tsendExecutionAck(cmd, { status: 'ok' });\n\t\t\t\t\t\topts.onDelivered?.(cmd);\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tsendExecutionAck(cmd, {\n\t\t\t\t\t\t\tstatus:\n\t\t\t\t\t\t\t\terr instanceof CommandRejectedError ? 'rejected' : 'error',\n\t\t\t\t\t\t\tdetail: err instanceof Error ? err.message : String(err),\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t};\n\n\t\t\tconst yieldFrame = (cmd: DeviceCommand): void => {\n\t\t\t\tif (opts.onExecute) {\n\t\t\t\t\texecute(cmd);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tqueue.push(cmd);\n\t\t\t\tnotify();\n\t\t\t\topts.onDelivered?.(cmd);\n\t\t\t};\n\n\t\t\tconst handleMessage = (raw: unknown): void => {\n\t\t\t\tlet data: unknown;\n\t\t\t\ttry {\n\t\t\t\t\tdata = JSON.parse(String(raw));\n\t\t\t\t} catch {\n\t\t\t\t\treturn; // ignore non-JSON frames\n\t\t\t\t}\n\t\t\t\tconst ping = pingFrameSchema.safeParse(data);\n\t\t\t\tif (ping.success) {\n\t\t\t\t\tif (socket && socket.readyState === WS.OPEN) {\n\t\t\t\t\t\tsocket.send(JSON.stringify({ type: 'pong' }));\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst keyStatus = keyStatusFrameSchema.safeParse(data);\n\t\t\t\tif (keyStatus.success) {\n\t\t\t\t\tconst status: KeyStatus = keyStatus.data;\n\t\t\t\t\topts.onKeyStatus?.(status);\n\t\t\t\t\t// ERA-6037: opt-in auto-renew past the advisory window.\n\t\t\t\t\tmaybeRenew(status);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// Server error advisory (ERA-6202): surface via onError and stop —\n\t\t\t\t// it is not a command. `code` is a string error code (e.g.\n\t\t\t\t// 'invalid_frame'), so it rides onError.reason (onError.code is the\n\t\t\t\t// numeric WS close code); `detail` is the human-readable message.\n\t\t\t\tconst errorFrame = errorFrameSchema.safeParse(data);\n\t\t\t\tif (errorFrame.success) {\n\t\t\t\t\topts.onError?.({\n\t\t\t\t\t\treason: errorFrame.data.code,\n\t\t\t\t\t\tmessage: errorFrame.data.detail,\n\t\t\t\t\t});\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst cmd = deviceCommandSchema.safeParse(data);\n\t\t\t\tif (!cmd.success) return; // unknown frame kinds are forward-compatible no-ops\n\t\t\t\tconst idem = cmd.data.idempotencyKey;\n\t\t\t\tif (idem) {\n\t\t\t\t\tconst duplicate = lru.check(idem);\n\t\t\t\t\t// Always (re-)ack — releases the server's :proc entry even when\n\t\t\t\t\t// the frame is a duplicate whose original ack was lost. This is\n\t\t\t\t\t// Ack A (bare receipt); execution is acked separately (ERA-6033).\n\t\t\t\t\tif (socket && socket.readyState === WS.OPEN) {\n\t\t\t\t\t\tsocket.send(\n\t\t\t\t\t\t\tJSON.stringify({ type: 'output.ack', idempotencyKey: idem }),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (dedupe && duplicate) {\n\t\t\t\t\t\t// ERA-6033: a redelivery of an already-executed command means\n\t\t\t\t\t\t// the original execution ack may have been lost — re-send the\n\t\t\t\t\t\t// recorded result instead of re-executing.\n\t\t\t\t\t\tconst prior = ackResults.get(idem);\n\t\t\t\t\t\tif (prior) sendExecutionAck(cmd.data, prior);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tyieldFrame(cmd.data);\n\t\t\t};\n\n\t\t\tconst scheduleReconnect = (): void => {\n\t\t\t\tstate = 'reconnecting';\n\t\t\t\tconst backoff = Math.min(\n\t\t\t\t\t(opts.backoffBaseMs ?? 500) * 2 ** attempts,\n\t\t\t\t\topts.backoffMaxMs ?? 30_000,\n\t\t\t\t);\n\t\t\t\tattempts += 1;\n\t\t\t\t// Full jitter: uniform in [0, backoff] — avoids fleet-wide stampedes.\n\t\t\t\treconnectTimer = setTimeout(open, Math.random() * backoff);\n\t\t\t};\n\n\t\t\t// Promote the freshly-confirmed key from `pending` → `current` (ERA-6198)\n\t\t\t// with bounded retry on a transient store failure. promote() is idempotent\n\t\t\t// (keyStore.ts:148 no-ops when `pending` is empty), so a retry after a\n\t\t\t// partial write is safe. Backoff mirrors scheduleReconnect (full jitter).\n\t\t\tconst PROMOTE_MAX_ATTEMPTS = 3;\n\t\t\tconst promoteWithRetry = (attempt = 0): void => {\n\t\t\t\tif (closed || !store) return;\n\t\t\t\tvoid store.promote().catch((err: unknown) => {\n\t\t\t\t\tif (closed) return;\n\t\t\t\t\tif (attempt + 1 < PROMOTE_MAX_ATTEMPTS) {\n\t\t\t\t\t\tconst backoff = Math.min(\n\t\t\t\t\t\t\t(opts.backoffBaseMs ?? 500) * 2 ** attempt,\n\t\t\t\t\t\t\topts.backoffMaxMs ?? 30_000,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tsetTimeout(\n\t\t\t\t\t\t\t() => promoteWithRetry(attempt + 1),\n\t\t\t\t\t\t\tMath.random() * backoff,\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\t// Exhausted — the key stays in `pending`. This is safe: the next\n\t\t\t\t\t// boot presents `pending` FIRST (ERA-6202 Fix 3), so the successor\n\t\t\t\t\t// is used and re-promoted on its next successful open. It is NOT\n\t\t\t\t\t// lost, and no stale `current` is presented ahead of it.\n\t\t\t\t\topts.onError?.({\n\t\t\t\t\t\tmessage: `keyStore.promote failed after ${PROMOTE_MAX_ATTEMPTS} attempts; key remains in pending — the next boot presents pending first and re-promotes it: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t};\n\n\t\t\tconst open = (): void => {\n\t\t\t\tif (closed) return;\n\t\t\t\t// Rebuild from currentToken so a reconnect after an auto-renew\n\t\t\t\t// authenticates as the successor key (header or query per runtime).\n\t\t\t\tconst ws = openSocket();\n\t\t\t\tsocket = ws;\n\t\t\t\tws.onopen = () => {\n\t\t\t\t\tif (socket !== ws) return;\n\t\t\t\t\tstate = 'open';\n\t\t\t\t\tattempts = 0;\n\t\t\t\t\t// Flush any frames send() buffered before this open — in insertion\n\t\t\t\t\t// order (ERA-6202). Reconnects reuse this handler, so gap-sends\n\t\t\t\t\t// during a drop flush here for free.\n\t\t\t\t\tif (sendBuffer.length > 0) {\n\t\t\t\t\t\tconst pending = sendBuffer.splice(0, sendBuffer.length);\n\t\t\t\t\t\tfor (const frame of pending) ws.send(frame);\n\t\t\t\t\t}\n\t\t\t\t\t// This socket now carries whatever token was current at open.\n\t\t\t\t\t// Any minted successor has been activated (or this is a fresh\n\t\t\t\t\t// key), so allow the next advisory to renew again.\n\t\t\t\t\trenewedPending = false;\n\t\t\t\t\trenewAttempts = 0;\n\t\t\t\t\t// The key this socket authenticated with is confirmed live —\n\t\t\t\t\t// promote it to the store's `current` slot (ERA-6198). Fail-open\n\t\t\t\t\t// with bounded retry: if promote never succeeds the key stays in\n\t\t\t\t\t// `pending`, which the next boot presents FIRST (ERA-6202 Fix 3)\n\t\t\t\t\t// and re-promotes — no stale `current` is ever presented ahead of\n\t\t\t\t\t// the confirmed successor.\n\t\t\t\t\tif (promoteOnOpen && store) {\n\t\t\t\t\t\tpromoteOnOpen = false;\n\t\t\t\t\t\tpromoteWithRetry();\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tws.onmessage = (event: MessageEvent) => {\n\t\t\t\t\tif (socket === ws) handleMessage(event.data);\n\t\t\t\t};\n\t\t\t\tws.onerror = () => {\n\t\t\t\t\tif (socket === ws) {\n\t\t\t\t\t\topts.onError?.({ message: 'device stream socket error' });\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tws.onclose = (event: CloseEvent) => {\n\t\t\t\t\tif (socket !== ws) return;\n\t\t\t\t\tsocket = null;\n\t\t\t\t\tif (closed) return;\n\t\t\t\t\tif (event.code === WS_CLOSE_AUTH_REJECTED) {\n\t\t\t\t\t\t// Crash recovery (ERA-6202): the first-presented slot was\n\t\t\t\t\t\t// rejected — try the OTHER slot ONCE. Promote ONLY when the token\n\t\t\t\t\t\t// being presented IS the `pending` slot; when falling back TO\n\t\t\t\t\t\t// `current` (the pending-first path) this is false, so a stale\n\t\t\t\t\t\t// `pending` can never clobber a good `current` via promote().\n\t\t\t\t\t\tif (fallbackToken !== undefined) {\n\t\t\t\t\t\t\tcurrentToken = fallbackToken;\n\t\t\t\t\t\t\tfallbackToken = undefined; // single-shot\n\t\t\t\t\t\t\tpromoteOnOpen = currentToken === stored?.pending;\n\t\t\t\t\t\t\tstate = 'reconnecting';\n\t\t\t\t\t\t\topen();\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Auth rejection is terminal — retrying a bad key is noise the\n\t\t\t\t\t\t// gateway rate-limits anyway. Surface and end the stream.\n\t\t\t\t\t\topts.onError?.({\n\t\t\t\t\t\t\tcode: event.code,\n\t\t\t\t\t\t\treason: event.reason,\n\t\t\t\t\t\t\tmessage: `device stream auth rejected (1008): ${event.reason || 'invalid or mismatched device key'}`,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tclosed = true;\n\t\t\t\t\t\tstate = 'closed';\n\t\t\t\t\t\tnotify();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (reconnect) scheduleReconnect();\n\t\t\t\t\telse {\n\t\t\t\t\t\tclosed = true;\n\t\t\t\t\t\tstate = 'closed';\n\t\t\t\t\t\tnotify();\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t};\n\n\t\t\tconst close = (): void => {\n\t\t\t\tif (closed) return;\n\t\t\t\tclosed = true;\n\t\t\t\tstate = 'closed';\n\t\t\t\tif (reconnectTimer) clearTimeout(reconnectTimer);\n\t\t\t\ttry {\n\t\t\t\t\tsocket?.close(1000, 'client close');\n\t\t\t\t} catch {\n\t\t\t\t\t/* ignore */\n\t\t\t\t}\n\t\t\t\tsocket = null;\n\t\t\t\tnotify();\n\t\t\t};\n\n\t\t\tif (opts.signal) {\n\t\t\t\tif (opts.signal.aborted) close();\n\t\t\t\telse opts.signal.addEventListener('abort', close, { once: true });\n\t\t\t}\n\n\t\t\topen();\n\n\t\t\treturn {\n\t\t\t\tget state(): DeviceStreamState {\n\t\t\t\t\treturn state;\n\t\t\t\t},\n\t\t\t\tsend(frame: DeviceInboundFrame): void {\n\t\t\t\t\tif (closed) {\n\t\t\t\t\t\tthrow new Error('deviceStream.send: stream is closed');\n\t\t\t\t\t}\n\t\t\t\t\t// Auto-stamp the event time when the caller didn't supply one\n\t\t\t\t\t// (ERA-6202) — a caller-provided timestamp is preserved verbatim.\n\t\t\t\t\tconst wire = JSON.stringify({\n\t\t\t\t\t\t...frame,\n\t\t\t\t\t\ttimestamp: frame.timestamp ?? new Date().toISOString(),\n\t\t\t\t\t});\n\t\t\t\t\tif (socket && socket.readyState === WS.OPEN) {\n\t\t\t\t\t\tsocket.send(wire);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\t// Pre-OPEN (or mid-reconnect): buffer in FIFO, flushed on the next\n\t\t\t\t\t// open (ERA-6202). Bounded — overflow throws rather than growing\n\t\t\t\t\t// unbounded while the socket is wedged.\n\t\t\t\t\tif (sendBuffer.length >= SEND_BUFFER_CAP) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t`deviceStream.send: send buffer full (capacity ${SEND_BUFFER_CAP})`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tsendBuffer.push(wire);\n\t\t\t\t},\n\t\t\t\tack(\n\t\t\t\t\tcmd: DeviceCommand,\n\t\t\t\t\tresult?: { status?: 'ok' | 'error' | 'rejected'; detail?: string },\n\t\t\t\t): void {\n\t\t\t\t\tsendExecutionAck(cmd, {\n\t\t\t\t\t\tstatus: result?.status ?? 'ok',\n\t\t\t\t\t\t...(result?.detail !== undefined && { detail: result.detail }),\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tclose,\n\t\t\t\tasync *[Symbol.asyncIterator](): AsyncGenerator<\n\t\t\t\t\tDeviceCommand,\n\t\t\t\t\tvoid,\n\t\t\t\t\tvoid\n\t\t\t\t> {\n\t\t\t\t\twhile (true) {\n\t\t\t\t\t\tconst next = queue.shift();\n\t\t\t\t\t\tif (next !== undefined) {\n\t\t\t\t\t\t\tyield next;\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (closed) return;\n\t\t\t\t\t\tawait new Promise<void>((resolve) => {\n\t\t\t\t\t\t\twake = resolve;\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t};\n}\n","// guardrails module — declarative guardrails (PRD #1 §5.5, ERA-4407).\n//\n// Guardrails are the ONLY source of determinism in the dynamic (no-DAG) agent.\n// This module ships the two NON-pausing guardrails — they ride per-step\n// lifecycle events and do NOT need the chains resume engine:\n// - mustInformOnSuccess() — assert an Output fired on success.\n// - neverCallWithout(action, when) — block a gated action unless `when` holds.\n// `confirmBefore` (the PAUSING guardrail, over the resume engine) is ERA-4409 (S2)\n// and extends the GuardrailDef union.\n//\n// These are AUTHOR-TIME builders producing a serializable `GuardrailDef`.\n// Runtime enforcement is the orchestrator (era-ingress, ERA-4401/4402):\n// mustInformOnSuccess checks at run completion; neverCallWithout is a pre-tool\n// seam that evaluates the predicate.\n//\n// Predicates reuse the chains `Predicate` + `$` ref proxy (`makeCtx`), so they\n// serialize identically and are evaluated by the same orchestrator evaluator.\n//\n// Tree-shakeable subpath import: `@era-laboratories/era-sdk/modules/guardrails`.\n\nimport * as z from 'zod';\nimport type { Ctx, Predicate, RefNode } from './chains.js';\nimport { makeCtx } from './chains.js';\n\n/**\n * A guardrail asserting that an Output fired on success — a run that completes\n * without one is flagged. Build one with {@link mustInformOnSuccess}. Checked by\n * the orchestrator at run completion.\n */\nexport interface MustInformOnSuccess {\n\t/** Discriminant tag identifying this guardrail variant. */\n\tkind: 'mustInformOnSuccess';\n}\n/**\n * A guardrail that blocks a gated action unless a predicate holds — evaluated by\n * the orchestrator immediately before the tool call (a pre-tool seam). Build one\n * with {@link neverCallWithout}.\n */\nexport interface NeverCallWithout {\n\t/** Discriminant tag identifying this guardrail variant. */\n\tkind: 'neverCallWithout';\n\t/** The gated tool, named `slug:toolName`, blocked unless `when` holds. */\n\taction: string;\n\t/** The materialized predicate that must hold for `action` to run. */\n\twhen: Predicate;\n}\n\n/**\n * A declarative guardrail — the `kind`-discriminated union both builders\n * produce. Only non-pausing variants ship today; a pausing `confirmBefore`\n * variant is planned and will extend this union.\n */\nexport type GuardrailDef = MustInformOnSuccess | NeverCallWithout;\n\n/**\n * Build a guardrail asserting that an Output fired on success; a run that\n * completes without one is flagged. An author-time, pure builder — bind the\n * result into `defineExperience({ guardrails })`. The orchestrator enforces it\n * at run completion.\n *\n * @returns A serializable {@link MustInformOnSuccess} guardrail.\n * @example\n * ```ts\n * const spec = defineExperience({\n * // ...\n * guardrails: [mustInformOnSuccess()],\n * });\n * ```\n */\nexport function mustInformOnSuccess(): MustInformOnSuccess {\n\treturn { kind: 'mustInformOnSuccess' };\n}\n\n/**\n * A serializable `Predicate`, a bare `$.` ref (treated as a truthy check), or an\n * `($) => …` closure returning either.\n */\nexport type PredicateInput =\n\t| Predicate\n\t| RefNode\n\t| ((ctx: Ctx) => Predicate | RefNode);\n\nconst isRef = (v: unknown): v is RefNode =>\n\ttypeof v === 'object' && v !== null && '$ref' in v;\n\n/**\n * Build a guardrail that blocks a gated action unless `when` holds — evaluated\n * by the orchestrator immediately before the tool call. An author-time, pure\n * builder; bind the result into `defineExperience({ guardrails })`.\n *\n * @param action - The gated tool to block, named `slug:toolName`\n * (e.g. `'google-calendar:delete_event'`).\n * @param when - The condition that must hold for `action` to run. Accepts a\n * chains `Predicate`, a bare `$.` ref (treated as a truthy check), or an\n * `($) => …` closure returning either. See {@link PredicateInput}.\n * @returns A serializable {@link NeverCallWithout} guardrail.\n * @remarks\n * The predicate is materialized to plain `{ $ref }` JSON (ref proxies collapsed)\n * so authored specs diff stably.\n * @example\n * ```ts\n * // Bare ref → truthy check.\n * neverCallWithout('google-calendar:delete_event', ($) => $.userConfirmed);\n * // Full predicate.\n * neverCallWithout('crm:send_email', ($) => ({\n * and: [{ truthy: $.withinBusinessHours }, { neq: [$.recipient, ''] }],\n * }));\n * ```\n */\nexport function neverCallWithout(\n\taction: string,\n\twhen: PredicateInput,\n): NeverCallWithout {\n\tconst resolved = typeof when === 'function' ? when(makeCtx()) : when;\n\tconst predicate: Predicate = isRef(resolved)\n\t\t? { truthy: resolved }\n\t\t: resolved;\n\t// Materialize ref proxies → plain {\"$ref\"} objects (JSON identity for diffs).\n\tconst materialized = JSON.parse(JSON.stringify(predicate)) as Predicate;\n\treturn { kind: 'neverCallWithout', action, when: materialized };\n}\n\n// ─── Schema (for the experienceSpec `guardrails` field + docs) ────────────────────\n\n/** A materialized predicate — a plain JSON object (refs collapsed to `{$ref}`). */\nconst predicateSchema = z.record(z.string(), z.json());\n\n/**\n * Zod schema for a {@link GuardrailDef} — a `kind`-discriminated union over the\n * shipped guardrails (`mustInformOnSuccess`, `neverCallWithout`). Exported so\n * callers can validate guardrail values at their own boundaries; it is also the\n * schema `defineExperience({ guardrails })` accepts and `simulate` reports back.\n *\n * @example\n * ```ts\n * const parsed = guardrailDefSchema.parse(mustInformOnSuccess());\n * ```\n */\nexport const guardrailDefSchema = z\n\t.discriminatedUnion('kind', [\n\t\tz.object({\n\t\t\tkind: z.literal('mustInformOnSuccess').meta({\n\t\t\t\tdescription: 'Guardrail kind discriminant — mustInformOnSuccess.',\n\t\t\t}),\n\t\t}),\n\t\tz.object({\n\t\t\tkind: z.literal('neverCallWithout').meta({\n\t\t\t\tdescription: 'Guardrail kind discriminant — neverCallWithout.',\n\t\t\t}),\n\t\t\taction: z.string().min(1).meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'The gated action (tool) that is blocked unless `when` holds.',\n\t\t\t}),\n\t\t\twhen: predicateSchema,\n\t\t}),\n\t])\n\t.meta({\n\t\tid: 'GuardrailDef',\n\t\ttitle: 'Guardrail',\n\t\tdescription:\n\t\t\t'A declarative guardrail (PRD §5.5). Non-pausing: mustInformOnSuccess + neverCallWithout. confirmBefore (pausing) is ERA-4409.',\n\t});\n","// simulate module — local Experience simulation (PRD #1 §7 F / US-9, ERA-4397).\n//\n// `simulate(spec, mockTriggerEvent)` is a PURE, no-network dry-run: it evaluates\n// the context gate locally (deterministic predicates; a semantic gate is\n// classified server-side at runtime, so it's reported undecided), renders the\n// trigger framing, and reports the spindle palette, OAuth grants, and declared\n// guardrails the run WOULD exercise. The agent's *dynamic* spindle allocation +\n// emissions are runtime/LLM concerns, so simulate reports the DECLARED surface\n// (the palette), not which the agent will pick.\n//\n// Run INSPECTION of an actual run reuses the chains run-state surface\n// (`experienceAuthoring(client).getRun(runId)` → `chains.getRun` → ChainRunState);\n// no new endpoint.\n//\n// Tree-shakeable subpath import: `@era-laboratories/era-sdk/modules/simulate`.\n\nimport type { Predicate, Ref } from './chains.js';\nimport type { ContextGate } from './context.js';\nimport type {\n\tExperienceSpec,\n\tGrantRef,\n\tSpindleRef,\n} from './experienceAuthoring.js';\nimport type { GuardrailDef } from './guardrails.js';\nimport type { TriggerEvent } from './triggers.js';\n\n/**\n * The outcome of evaluating an Experience's context gate during a local\n * {@link simulate} dry-run. Tells you whether the Experience would run against\n * the mock event, and — for deterministic gates — why.\n *\n * @remarks\n * A gate that cannot be resolved locally (one containing a semantic `classify`\n * predicate) is reported as `decided: false` with `pass: true` — the convention\n * is \"when we can't decide, assume it runs\". Only the platform's classifier can\n * settle a semantic gate at runtime. A deterministic predicate over fields\n * absent from the mock context does NOT go undecided — the missing field\n * resolves to `undefined` and the predicate evaluates decisively (usually to a\n * fail).\n */\nexport interface GateDecision {\n\t/**\n\t * Whether the gate was resolved locally. `false` when the gate needs the\n\t * platform's classifier — a semantic gate, or a deterministic gate containing\n\t * a semantic `classify` predicate (no classifier runs in-process).\n\t */\n\tdecided: boolean;\n\t/**\n\t * Whether the Experience would run. `true` when the gate passed, and also\n\t * `true` whenever `decided` is `false` (undecided gates are assumed to run).\n\t */\n\tpass: boolean;\n\t/**\n\t * Which gate flavor was evaluated:\n\t * - `'none'` — the spec declares no gate; the Experience always runs.\n\t * - `'deterministic'` — a `when(...)` predicate evaluated locally.\n\t * - `'semantic'` — a `when.semantic(...)` gate, resolved server-side only.\n\t */\n\tkind: 'none' | 'deterministic' | 'semantic';\n\t/**\n\t * Human-readable explanation of the decision (e.g. `'gate passed — runs'`,\n\t * `'gate failed — dropped'`, or the semantic prompt awaiting server-side\n\t * classification). Intended for logging and debugging, not for branching on.\n\t */\n\treason: string;\n}\n\n/**\n * The full result of a local {@link simulate} dry-run: the gate decision, the\n * trigger framing the agent would receive, and the *declared* surface the run\n * would exercise. Everything here is computed locally with no network call.\n *\n * @remarks\n * `spindles`, `grants`, and `guardrails` report what the spec *declares*, not\n * what the agent will dynamically allocate at runtime — the agent's actual\n * spindle choices and emissions are runtime/LLM concerns and cannot be\n * predicted offline.\n */\nexport interface SimulationResult {\n\t/** The Experience's name, copied from `spec.name`. */\n\tname: string;\n\t/** How the context gate resolved against the mock event (see {@link GateDecision}). */\n\tgate: GateDecision;\n\t/**\n\t * The trigger the agent would receive: the natural-language `framing` string\n\t * plus the structured `event` that produced it.\n\t */\n\ttrigger: { framing: string; event: TriggerEvent };\n\t/** The spindle palette the agent MAY allocate (dynamic at runtime). */\n\tspindles: SpindleRef[];\n\t/** OAuth grants the credentialed spindles in the palette require. */\n\tgrants: GrantRef[];\n\t/** Declared guardrails that would apply to the run (empty if none declared). */\n\tguardrails: GuardrailDef[];\n}\n\n// ─── local predicate evaluation ─────────────────────────────────────────────────\n\nconst isRef = (v: unknown): v is Ref<unknown> =>\n\ttypeof v === 'object' && v !== null && '$ref' in v;\n\nfunction resolveRef(ref: string, scope: Record<string, unknown>): unknown {\n\tconst path = ref\n\t\t.replace(/^\\$\\.?/, '')\n\t\t.split('.')\n\t\t.filter(Boolean);\n\tlet cur: unknown = scope;\n\tfor (const key of path) {\n\t\tif (cur === null || typeof cur !== 'object') return undefined;\n\t\tcur = (cur as Record<string, unknown>)[key];\n\t}\n\treturn cur;\n}\n\nfunction operand(v: unknown, scope: Record<string, unknown>): unknown {\n\treturn isRef(v) ? resolveRef(v.$ref, scope) : v;\n}\n\nfunction evaluatePredicate(\n\tp: Predicate,\n\tscope: Record<string, unknown>,\n): boolean {\n\tif ('eq' in p) return operand(p.eq[0], scope) === operand(p.eq[1], scope);\n\tif ('neq' in p) return operand(p.neq[0], scope) !== operand(p.neq[1], scope);\n\tif ('gt' in p)\n\t\treturn Number(operand(p.gt[0], scope)) > Number(operand(p.gt[1], scope));\n\tif ('lt' in p)\n\t\treturn Number(operand(p.lt[0], scope)) < Number(operand(p.lt[1], scope));\n\tif ('exists' in p) {\n\t\tconst x = operand(p.exists, scope);\n\t\treturn x !== undefined && x !== null;\n\t}\n\tif ('truthy' in p) return Boolean(operand(p.truthy, scope));\n\tif ('and' in p) return p.and.every((q) => evaluatePredicate(q, scope));\n\tif ('or' in p) return p.or.some((q) => evaluatePredicate(q, scope));\n\tif ('not' in p) return !evaluatePredicate(p.not, scope);\n\t// `classify` — a semantic/tensorzero predicate; only the server can evaluate it.\n\tthrow new Error('undecidable: semantic predicate');\n}\n\nfunction evaluateGate(\n\tgate: ContextGate | undefined,\n\tevent: TriggerEvent,\n): GateDecision {\n\tif (!gate)\n\t\treturn {\n\t\t\tdecided: true,\n\t\t\tpass: true,\n\t\t\tkind: 'none',\n\t\t\treason: 'no gate — always runs',\n\t\t};\n\tif (gate.kind === 'semantic')\n\t\treturn {\n\t\t\tdecided: false,\n\t\t\tpass: true,\n\t\t\tkind: 'semantic',\n\t\t\treason: `semantic gate \"${gate.prompt}\" — classified server-side at runtime`,\n\t\t};\n\t// Deterministic — resolve `$.` refs against the ContextSnapshot (+ trigger).\n\tconst ctx = (event.context ?? {}) as Record<string, unknown>;\n\tconst scope: Record<string, unknown> = {\n\t\t...ctx,\n\t\tcontext: ctx,\n\t\ttrigger: event,\n\t};\n\ttry {\n\t\tconst pass = evaluatePredicate(gate.predicate, scope);\n\t\treturn {\n\t\t\tdecided: true,\n\t\t\tpass,\n\t\t\tkind: 'deterministic',\n\t\t\treason: pass ? 'gate passed — runs' : 'gate failed — dropped',\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tdecided: false,\n\t\t\tpass: true,\n\t\t\tkind: 'deterministic',\n\t\t\treason: 'gate references fields absent from the simulation scope',\n\t\t};\n\t}\n}\n\nfunction frameTrigger(spec: ExperienceSpec, event: TriggerEvent): string {\n\tconst base = `Experience \"${spec.name}\" triggered by a ${event.kind} event on ${event.source.surface}.`;\n\tconst payload = event.payload as\n\t\t| { speech?: { text?: string }; utterance?: { text?: string } }\n\t\t| undefined;\n\tconst text = payload?.utterance?.text ?? payload?.speech?.text;\n\treturn text ? `${base} The user said: \"${text}\".` : base;\n}\n\n/**\n * Locally simulate an Experience against a mock {@link TriggerEvent} — a pure,\n * no-network dry-run. Evaluates the declared context gate in-process, renders\n * the natural-language trigger framing the agent would receive, and reports the\n * *declared* surface the run would exercise (spindle palette, OAuth grants, and\n * guardrails). Use it in tests or a REPL to check gate logic and trigger framing\n * before publishing, without provisioning a real run.\n *\n * @param spec - The Experience specification to simulate (typically the\n * `ExperienceSpec` you built with the authoring DSL).\n * @param event - The mock {@link TriggerEvent} to evaluate against. Populate its\n * `context` with the fields your gate reads — a deterministic gate over a field\n * absent from the mock context evaluates decisively (usually to a fail).\n * @returns A {@link SimulationResult} with the gate decision, trigger framing,\n * and the declared spindle/grant/guardrail surface.\n *\n * @remarks\n * This function never throws for an undecidable gate: a semantic `classify`\n * predicate (the one predicate the local evaluator cannot resolve) is caught\n * and surfaced as `gate.decided: false, gate.pass: true` rather than\n * propagating.\n * It reports the DECLARED spindle palette, not the agent's runtime allocation —\n * dynamic spindle choices and emissions can only be observed on a real run via\n * `experienceAuthoring(era).getRun(runId)`.\n *\n * @example\n * ```ts\n * import { simulate } from '@era-laboratories/era-sdk';\n *\n * const result = simulate(spec, {\n * experienceId: 'exp_0000000000',\n * triggerId: 'front-door',\n * kind: 'button',\n * occurredAt: new Date().toISOString(),\n * idempotencyKey: crypto.randomUUID(),\n * source: { surface: 'maker', deviceId: 'dev_0000000000' },\n * context: { location: { place: 'home' } },\n * payload: { gesture: 'press' },\n * });\n *\n * console.log(result.gate.pass, result.gate.reason);\n * console.log(result.trigger.framing);\n * // 'Experience \"Doorbell greeter\" triggered by a button event on maker.'\n * ```\n */\nexport function simulate(\n\tspec: ExperienceSpec,\n\tevent: TriggerEvent,\n): SimulationResult {\n\treturn {\n\t\tname: spec.name,\n\t\tgate: evaluateGate(spec.gate, event),\n\t\ttrigger: { framing: frameTrigger(spec, event), event },\n\t\tspindles: spec.agent.spindles,\n\t\tgrants: spec.agent.grants,\n\t\tguardrails: spec.guardrails ?? [],\n\t};\n}\n","// experience authoring module — declarative authoring (ERA-4267 / ERA-4410).\n//\n// An `ExperienceSpec` is an opinionated, JSON-serializable projection of an\n// experience: an agentic core (`agent: { goal, spindles, grants }`) plus\n// modality + style + model policy + memory + command bindings. `Agent ⊂\n// Experience` — the nested `agent` is just the agentic core; everything else is\n// presentation/runtime config that hangs off the Experience.\n//\n// `defineExperience()` validates and normalizes the spec; the fluent\n// `experience()` builder accumulates the same fields and routes through\n// `defineExperience()` on `.build()`, so the two forms are deep-equal BY\n// CONSTRUCTION.\n//\n// The pure authoring surface (defineExperience / experience / modality /\n// spindle / command / compile / decompile / diff) does NOT touch the network.\n// Only the `experienceAuthoring(client)` facade does, composing the experiences\n// + experienceVersions modules — no new routes.\n//\n// Compiler mapping (ExperienceSpec → experience payload):\n// agent.goal → stylePrompt\n// agent.internalGoal → internalSystemPrompt\n// agent.spindles[] → stylePreferences.spindles (nested, slug-keyed)\n// agent.grants[] → (authored-but-inert; consumed by the OAuth broker, ERA-4123)\n// modality (voice/mm) → stylePreferences.{tts,stt} (+ mirror to voiceId/voiceProvider)\n// style → stylePreferences.{voice,lexicon,rhetoric,policy,editing,color}\n// commands[] → stylePreferences.{voiceCommands,buttonMappings}\n// model → stylePreferences.model (authored-but-inert; design open-risk #5)\n// memory.retrieval → memoryRetrievalEnabled\n//\n// Known lossy round-trip: `agent.internalGoal` is NOT on the public\n// `Experience` read DTO, so `fromExperience()` cannot recover it (design\n// open-risk #6); `agent.grants` are likewise not persisted to the read DTO.\n// Every other field round-trips content-stably.\n//\n// `publish()` is intentionally NOT in this facade — RBAC v2 publishing is a\n// review state machine (draft → review → published), not a channel write; use\n// `era.experienceVersions` (submitForReview / approveReview) directly.\n//\n// Tree-shakeable subpath import: `@era-laboratories/era-sdk/modules/experienceAuthoring`.\n\nimport * as z from 'zod';\nimport type { CamelizeKeys, PickNonNullable } from '../core/casing.js';\nimport type { EraClient, ResponseMeta, StreamHandle } from '../core/client.js';\nimport { EraStreamError } from '../core/errors.js';\nimport type {\n\tStreamBlockedEvent,\n\tStreamDoneEvent,\n\tStreamErrorEvent,\n\tStreamEvent,\n} from '../core/streaming.js';\nimport type { RequestOptions } from '../core/types.js';\nimport type { ChatDoneStreamEvent as GenChatDoneStreamEvent } from '../generated/ingress/types.gen.js';\nimport type { ChainRunState } from './chains.js';\nimport { chains } from './chains.js';\nimport type {\n\tButtonMapping,\n\tEditing,\n\tExperienceStylePreferences,\n\tLexicon,\n\tPolicy,\n\tRhetoric,\n\tSttConfig,\n\tTtsConfig,\n\tVoiceCommand,\n\tVoiceStyle,\n} from './commands.js';\nimport type { ContextGate } from './context.js';\nimport { contextGateSchema } from './context.js';\nimport type { DeviceCommand, DeviceStream } from './deviceStream.js';\nimport { deviceStream } from './deviceStream.js';\nimport { devices } from './devices.js';\nimport type {\n\tCreateExperienceInput,\n\tExperience,\n\tUpdateExperienceInput,\n} from './experiences.js';\nimport { experiences } from './experiences.js';\nimport type {\n\tUpdateDraftInput,\n\tVersionMutationResult,\n} from './experienceVersions.js';\nimport { experienceVersions } from './experienceVersions.js';\nimport type { GuardrailDef } from './guardrails.js';\nimport { guardrailDefSchema } from './guardrails.js';\nimport type { SimulationResult } from './simulate.js';\nimport { simulate as simulateSpec } from './simulate.js';\nimport {\n\tnestedSpindlesToEntries,\n\ttype SpindlePalette,\n\tspindleRefsToNested,\n} from './spindlePalette.js';\nimport type { TriggerEvent } from './triggers.js';\n\n// ─── Spec types ───────────────────────────────────────────────────────────────\n\n/**\n * Presentation style — the subset of an Experience's style preferences that\n * shapes how the agent sounds and behaves. Every field is optional; unset\n * fields fall back to the platform defaults.\n */\nexport interface ExperienceStyle {\n\t/**\n\t * Voice sliders, each in the range 0–1 (e.g. warmth, formality). Partial —\n\t * any slider you omit falls back to the platform default.\n\t */\n\tvoice?: Partial<VoiceStyle>;\n\t/** Word choice and terminology preferences applied to the agent's replies. */\n\tlexicon?: Lexicon;\n\t/** Rhetorical shaping (tone, persuasion, structure) for the agent's replies. */\n\trhetoric?: Rhetoric;\n\t/** Content policy overlay constraining what the agent may say. */\n\tpolicy?: Policy;\n\t/** Post-processing/editing preferences applied to the agent's output. */\n\tediting?: Editing;\n\t/** Brand/accent color as a hex string (e.g. `'#4d2a1f'`). */\n\tcolor?: string;\n}\n\n/** A text-only Experience — no speech synthesis or transcription. */\nexport interface TextModality {\n\t/** Discriminant identifying the text modality. */\n\tkind: 'text';\n}\n/** A voice Experience — speech in and out, with optional TTS/STT overrides. */\nexport interface VoiceModality {\n\t/** Discriminant identifying the voice modality. */\n\tkind: 'voice';\n\t/** Text-to-speech config (provider + voice). Omit to use the platform default. */\n\ttts?: TtsConfig;\n\t/** Speech-to-text config (provider + model). Omit to use the platform default. */\n\tstt?: SttConfig;\n}\n/** A multimodal Experience — text plus voice, with optional TTS/STT overrides. */\nexport interface MultimodalModality {\n\t/** Discriminant identifying the multimodal modality. */\n\tkind: 'multimodal';\n\t/** Text-to-speech config (provider + voice). Omit to use the platform default. */\n\ttts?: TtsConfig;\n\t/** Speech-to-text config (provider + model). Omit to use the platform default. */\n\tstt?: SttConfig;\n}\n/**\n * How an Experience communicates. Discriminated on `kind` — `'text'`,\n * `'voice'`, or `'multimodal'` — and drives the TTS/STT config and the output\n * modality. Build one with the {@link modality} factory.\n */\nexport type ExperienceModality =\n\t| TextModality\n\t| VoiceModality\n\t| MultimodalModality;\n\n/**\n * A model-routing hint attached to an Experience.\n *\n * @remarks\n * Authored on the spec but **not yet applied at runtime** — the platform\n * ignores it today. Set it to record intent; it does not change which model\n * serves the Experience.\n */\nexport interface ModelPolicy {\n\t/** Named model function to route to. Reserved — not yet applied at runtime. */\n\tfunction?: string;\n\t/** Named model variant to route to. Reserved — not yet applied at runtime. */\n\tvariant?: string;\n}\n\n/**\n * A spindle the agent may use. `ref` is a spindle `slug` (enables all the\n * spindle's tools) or `slug:toolName` (a single tool).\n */\nexport interface SpindleRef {\n\t/**\n\t * Spindle reference: a spindle `slug` (enables all of the spindle's tools) or\n\t * `slug:toolName` (enables a single named tool).\n\t */\n\tref: string;\n\t/** Enablement override. Defaults to enabled when omitted. */\n\tenabled?: boolean;\n}\n\n/**\n * An OAuth grant requirement for a credentialed spindle — the provider and the\n * scopes the agent needs to call that spindle's tools.\n *\n * @remarks\n * Authored on the spec but not sent by `compile()` today, and not persisted on\n * the Experience read model — so grants do not survive a\n * {@link decompileExperience} round-trip.\n */\nexport interface GrantRef {\n\t/** OAuth provider id for the credentialed spindle (e.g. `'google'`). */\n\tprovider: string;\n\t/** OAuth scopes the agent requires (e.g. calendar read). Omit for none. */\n\tscopes?: string[];\n}\n\n/**\n * A command binding that fires a tool when triggered — either a spoken voice\n * command or a hardware button mapping. Discriminated on `kind`. Build one with\n * the {@link command} factory.\n */\nexport type CommandBinding =\n\t| { kind: 'voice'; command: VoiceCommand }\n\t| { kind: 'button'; mapping: ButtonMapping };\n\n/**\n * The agentic core of an Experience — the objective, spindle palette, and OAuth\n * grants. `Agent ⊂ Experience`: this is the nested agent, and everything else on\n * {@link ExperienceSpec} is presentation/runtime config that hangs off it.\n */\nexport interface AgentCore {\n\t/** The agent's objective — becomes the Experience's system prompt. */\n\tgoal?: string;\n\t/**\n\t * Internal/operator prompt overlay layered on top of `goal`. Not recovered by\n\t * {@link decompileExperience} — the Experience read model does not carry it.\n\t */\n\tinternalGoal?: string;\n\t/** The spindle palette the agent may use. Always an array after normalization. */\n\tspindles: SpindleRef[];\n\t/** OAuth grant requirements. Always an array after normalization. */\n\tgrants: GrantRef[];\n}\n\n/**\n * Canonical, JSON-serializable Experience definition. Produced by\n * {@link defineExperience} (and the {@link experience} builder). `agent.spindles`,\n * `agent.grants`, and `commands` are always arrays after normalization.\n *\n * @remarks\n * `compileToExperienceInput` maps the agentic core, modality, style, model,\n * memory, and command bindings onto the Experience payload. The `gate` and\n * `guardrails` fields are accepted on the spec but **not sent by `compile()`**\n * and not stored on an Experience today — declare gates and lifecycle behavior\n * on a chain instead (`chain(...).trigger(...)`). Triggers likewise live on the\n * chain, not the Experience.\n */\nexport interface ExperienceSpec {\n\t/** Unique Experience name within the owning resource. */\n\tname: string;\n\t/** Human-readable description of the Experience. Optional. */\n\tdescription?: string;\n\t/**\n\t * Context gate evaluated when a trigger arrives to decide whether to run or\n\t * drop. Authored here but not consumed by an Experience today — set gates on\n\t * a chain instead. See `when` in the `context` module.\n\t */\n\tgate?: ContextGate;\n\t/** The agentic core: objective + spindle palette + OAuth grants. */\n\tagent: AgentCore;\n\t/** How the Experience communicates: text, voice, or multimodal. */\n\tmodality: ExperienceModality;\n\t/** Presentation style — voice sliders, lexicon/rhetoric/policy/editing, color. */\n\tstyle?: ExperienceStyle;\n\t/** Model-routing hint. Reserved — not yet applied at runtime. */\n\tmodel?: ModelPolicy;\n\t/** Memory configuration. Set `retrieval: true` to enable memory retrieval. */\n\tmemory?: { retrieval?: boolean };\n\t/** Tool-firing command bindings (voice commands + button mappings). */\n\tcommands: CommandBinding[];\n\t/**\n\t * Declarative guardrails. Authored here but not consumed by an Experience\n\t * today — set guardrails on a chain instead. See the `guardrails` module.\n\t */\n\tguardrails?: GuardrailDef[];\n}\n\n/**\n * A single structural difference between two specs, as returned by\n * {@link diffExperiences} — one changed leaf value at a given path.\n */\nexport interface ExperienceDiffEntry {\n\t/** Dot-delimited path to the changed leaf (e.g. `'agent.goal'`). */\n\tpath: string;\n\t/** The value in the first (`a`) spec, or `undefined` if only in `b`. */\n\tbefore: unknown;\n\t/** The value in the second (`b`) spec, or `undefined` if only in `a`. */\n\tafter: unknown;\n}\n\n// ─── Runtime (run / complete) ──────────────────────────────────────────────────\n\n/**\n * Terminal event of a run — the base {@link StreamDoneEvent} enriched with the\n * names of the tools invoked during the turn and the response metadata\n * (`requestId`, and `costCents` / `balanceCents` when the stream provides them).\n *\n * @remarks\n * `toolsUsed` is the list of tool names invoked during the turn: the server's\n * value is used when reported, otherwise the SDK's own aggregation of the\n * turn's `tool_call` events (defaulting to `[]`). `requestId` is read from the\n * response headers. `cost` / `balance` are legacy fields retained for\n * back-compat and are never populated from the stream — read `costCents` /\n * `balanceCents` (integer US cents) for billing.\n */\nexport type ExperienceRunDoneEvent = StreamDoneEvent &\n\tPickNonNullable<CamelizeKeys<GenChatDoneStreamEvent>, 'toolsUsed'> &\n\tPick<ResponseMeta, 'requestId'> &\n\tPartial<Record<'cost' | 'balance', number>>;\n\n/**\n * A normalized event yielded by {@link ExperienceAuthoringModule.run}. The\n * non-terminal events are the SDK's `StreamEvent`s\n * (`start` / `interim` / `chunk` / `tool_call` / `tool_result` /\n * `tool_result_chunk` / `blocked` / `error`); the terminal `done` is widened to\n * {@link ExperienceRunDoneEvent}.\n */\nexport type ExperienceRunEvent =\n\t| Exclude<StreamEvent, { type: 'done' }>\n\t| ExperienceRunDoneEvent;\n\n/** Per-turn input for `run()` / `complete()`. */\nexport interface ExperienceRunInput {\n\t/** The user's prompt for this turn. */\n\tinput: string;\n\t/** Reuse an existing session; omit to let the server create one. */\n\tsessionId?: string;\n\t/**\n\t * Turn modality. `'voice'` routes to `/chat/voice`; `'text'` and\n\t * `'multimodal'` route to `/chat`. Realtime streaming audio is out of scope\n\t * for `run()` — use the `voice` module for that.\n\t */\n\tmodality?: 'text' | 'voice' | 'multimodal';\n\t/** Passthrough JSON attached to the turn for your own logs / telemetry. */\n\tmetadata?: Record<string, unknown>;\n\t/** `AbortSignal` to cancel the in-flight turn. */\n\tsignal?: AbortSignal;\n\t/**\n\t * A stable `Idempotency-Key` for this turn so the server can deduplicate\n\t * retries and never double-charge you. Without it the SDK mints a fresh key\n\t * per call, so your own retry of a failed request would bill twice. Runs\n\t * stream and are not replayed from cache: reusing the same key with the\n\t * same body within 30 days is not re-run — it returns\n\t * `409 IDEMPOTENT_REPLAY` carrying the original cost headers; the same key\n\t * with a different body returns `422 IDEMPOTENCY_KEY_BODY_MISMATCH`. Same\n\t * server-side contract as `chat.send(...)` / `chat.complete(...)`.\n\t */\n\tidempotencyKey?: string;\n}\n\n/**\n * Aggregated reply from the non-streaming {@link ExperienceAuthoringModule.complete}\n * convenience.\n *\n * @remarks\n * Carries the same fields as the terminal {@link ExperienceRunDoneEvent}, minus\n * the event framing (`type` / `fullContent`), plus the accumulated reply `text`.\n *\n * `blocked` is set when the server's moderation layer refused the turn: `text`\n * is then empty and `blocked.safetyCategory` names the triggering category when\n * the server reports one. A block is a policy outcome the caller inspects —\n * `complete()` does NOT throw for it. Absent on normal replies.\n */\nexport type ExperienceReply = Omit<\n\tExperienceRunDoneEvent,\n\t'type' | 'fullContent'\n> &\n\tRecord<'text', string> &\n\tPartial<Record<'blocked', Pick<StreamBlockedEvent, 'safetyCategory'>>>;\n\n// ─── Validation (zod) ─────────────────────────────────────────────────────────\n\nconst ttsSchema = z\n\t.object({\n\t\tprovider: z.string(),\n\t\tvoice_id: z.string().nullable(),\n\t\tvoice_name: z.string().nullable().optional(),\n\t\tvoice_description: z.string().nullable().optional(),\n\t\t// preview_url is a provider voice-catalog audition URL round-tripped from\n\t\t// the Experience read DTO (stylePreferences.tts); `''` is the \"no preview\"\n\t\t// sentinel the compiler emits (`voice.previewUrl ?? ''`).\n\t\tpreview_url: z.url().or(z.literal('')).nullable().optional(),\n\t})\n\t.optional();\nconst sttSchema = z\n\t.object({ provider: z.string(), model: z.string().nullable().optional() })\n\t.optional();\n\nconst modalitySchema = z.discriminatedUnion('kind', [\n\tz.object({ kind: z.literal('text') }),\n\tz.object({ kind: z.literal('voice'), tts: ttsSchema, stt: sttSchema }),\n\tz.object({ kind: z.literal('multimodal'), tts: ttsSchema, stt: sttSchema }),\n]);\n\n// Style is validated structurally but kept permissive on sub-fields (the\n// backend enforces slider ranges); we only require objects where present.\nconst styleSchema = z\n\t.object({\n\t\tvoice: z.record(z.string(), z.number()).optional(),\n\t\tlexicon: z.object({}).loose().optional(),\n\t\trhetoric: z.object({}).loose().optional(),\n\t\tpolicy: z.object({}).loose().optional(),\n\t\tediting: z.object({}).loose().optional(),\n\t\tcolor: z.string().optional(),\n\t})\n\t.loose()\n\t.optional();\n\nconst spindleSchema = z.object({\n\tref: z.string().min(1).meta({\n\t\tdescription:\n\t\t\t'Spindle reference: a spindle `slug` (enables all its tools) or `slug:toolName` (a single tool).',\n\t}),\n\tenabled: z\n\t\t.boolean()\n\t\t.optional()\n\t\t.meta({ description: 'Enablement override. Defaults to enabled.' }),\n});\n\nconst grantSchema = z.object({\n\tprovider: z\n\t\t.string()\n\t\t.min(1)\n\t\t.meta({ description: 'OAuth provider id for a credentialed spindle.' }),\n\tscopes: z.array(z.string()).optional().meta({\n\t\tdescription:\n\t\t\t'Required OAuth scopes; resolved at execution by the per-key token broker (ERA-4123).',\n\t}),\n});\n\nconst commandSchema = z.discriminatedUnion('kind', [\n\tz.object({\n\t\tkind: z.literal('voice'),\n\t\tcommand: z.object({ id: z.string().min(1) }).loose(),\n\t}),\n\tz.object({\n\t\tkind: z.literal('button'),\n\t\tmapping: z.object({ id: z.string().min(1) }).loose(),\n\t}),\n]);\n\nconst experienceSpecSchema = z\n\t.object({\n\t\tname: z.string().min(1).meta({\n\t\t\tdescription: 'Unique Experience name within the owning resource.',\n\t\t}),\n\t\tdescription: z\n\t\t\t.string()\n\t\t\t.optional()\n\t\t\t.meta({ description: 'Human-readable description of the Experience.' }),\n\t\tgate: contextGateSchema.optional().meta({\n\t\t\tdescription:\n\t\t\t\t'Context gate evaluated at trigger ingest (run or drop): deterministic when() or semantic when.semantic().',\n\t\t}),\n\t\tagent: z\n\t\t\t.object({\n\t\t\t\tgoal: z.string().optional().meta({\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t\"The agent's objective — compiles to the Experience systemPrompt (`stylePrompt`).\",\n\t\t\t\t}),\n\t\t\t\tinternalGoal: z.string().optional().meta({\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t'Internal/operator prompt overlay. Lossy round-trip — not on the public read DTO.',\n\t\t\t\t}),\n\t\t\t\tspindles: z.array(spindleSchema).default([]).meta({\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t'The spindle palette the agent may use (slug or slug:tool refs).',\n\t\t\t\t}),\n\t\t\t\tgrants: z.array(grantSchema).default([]).meta({\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t'OAuth grant requirements; authored-but-inert in the SDK, resolved by the token broker (ERA-4123).',\n\t\t\t\t}),\n\t\t\t})\n\t\t\t.meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'The agentic core of the Experience (Agent ⊂ Experience): objective + spindle palette + OAuth grants.',\n\t\t\t}),\n\t\tmodality: modalitySchema.meta({\n\t\t\tdescription:\n\t\t\t\t'How the Experience communicates: text, voice, or multimodal (drives tts/stt config).',\n\t\t}),\n\t\tstyle: styleSchema.meta({\n\t\t\tdescription:\n\t\t\t\t'Presentation style — voice sliders, lexicon/rhetoric/policy/editing, and brand color.',\n\t\t}),\n\t\tmodel: z\n\t\t\t.object({\n\t\t\t\tfunction: z.string().optional(),\n\t\t\t\tvariant: z.string().optional(),\n\t\t\t})\n\t\t\t.optional()\n\t\t\t.meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'TensorZero model-policy hint. Authored-but-inert until ingress wiring.',\n\t\t\t}),\n\t\tmemory: z.object({ retrieval: z.boolean().optional() }).optional().meta({\n\t\t\tdescription: 'Memory configuration (e.g. retrieval enablement).',\n\t\t}),\n\t\tcommands: z.array(commandSchema).default([]).meta({\n\t\t\tdescription:\n\t\t\t\t'Tool-firing command bindings (voice commands + button mappings).',\n\t\t}),\n\t\tguardrails: z.array(guardrailDefSchema).optional().meta({\n\t\t\tdescription:\n\t\t\t\t'Declarative guardrails (non-pausing): mustInformOnSuccess + neverCallWithout.',\n\t\t}),\n\t})\n\t.meta({\n\t\tid: 'ExperienceSpec',\n\t\ttitle: 'Experience',\n\t\tdescription:\n\t\t\t'The canonical Experience authoring object (PRD §5.1): trigger + context gate + agentic core + modality, style, model, memory, command bindings, and guardrails.',\n\t});\n\n/**\n * Validate and normalize an Experience definition into the canonical\n * {@link ExperienceSpec}, filling defaults so downstream code can iterate the\n * arrays safely. Pure — no network. Use it to author a spec you then pass to\n * {@link experienceAuthoring} `.create()` / `.update()`.\n *\n * @param input - The Experience definition to validate.\n * @returns The normalized spec, with `agent.spindles`, `agent.grants`, and\n * `commands` defaulted to `[]` when omitted.\n * @throws {@link https://zod.dev | ZodError} when `input` fails validation\n * (e.g. a missing `name` or an empty spindle `ref`).\n * @example\n * ```typescript\n * const spec = defineExperience({\n * name: 'Doorbell greeter',\n * agent: { goal: 'Greet the visitor and take a message.', spindles: [], grants: [] },\n * modality: modality.voice(),\n * commands: [],\n * });\n * ```\n */\nexport function defineExperience(input: ExperienceSpec): ExperienceSpec {\n\treturn experienceSpecSchema.parse(input) as ExperienceSpec;\n}\n\n// ─── Factories ─────────────────────────────────────────────────────────────────\n\nconst textModality = (): TextModality => ({ kind: 'text' });\nconst voiceModality = (opts?: {\n\ttts?: TtsConfig;\n\tstt?: SttConfig;\n}): VoiceModality => ({\n\tkind: 'voice',\n\t...(opts?.tts ? { tts: opts.tts } : {}),\n\t...(opts?.stt ? { stt: opts.stt } : {}),\n});\nconst multimodalModality = (opts?: {\n\ttts?: TtsConfig;\n\tstt?: SttConfig;\n}): MultimodalModality => ({\n\tkind: 'multimodal',\n\t...(opts?.tts ? { tts: opts.tts } : {}),\n\t...(opts?.stt ? { stt: opts.stt } : {}),\n});\n\n/**\n * Factories for the {@link ExperienceModality} variants, namespaced to avoid\n * colliding with the `voice` module export. Each returns a plain, serializable\n * modality fragment for an {@link ExperienceSpec}.\n *\n * @example\n * ```typescript\n * modality.text(); // { kind: 'text' }\n * modality.voice({ tts, stt }); // { kind: 'voice', ... }\n * modality.multimodal({ tts, stt }); // { kind: 'multimodal', ... }\n * ```\n */\nexport const modality = {\n\t/** Build a text-only modality: `{ kind: 'text' }`. */\n\ttext: textModality,\n\t/** Build a voice modality, optionally overriding TTS/STT config. */\n\tvoice: voiceModality,\n\t/** Build a multimodal modality, optionally overriding TTS/STT config. */\n\tmultimodal: multimodalModality,\n};\n\n/**\n * Build a {@link SpindleRef} for the agent's spindle palette.\n *\n * @param ref - A spindle `slug` (enables all of its tools) or `slug:toolName`\n * (enables a single named tool).\n * @param opts - Optional settings.\n * @param opts.enabled - Enablement override; defaults to enabled when omitted.\n * @returns The spindle reference fragment.\n * @example\n * ```typescript\n * spindle('google-calendar'); // whole spindle\n * spindle('google-calendar:delete_event', { enabled: false }); // one tool, disabled\n * ```\n */\nexport const spindle = (\n\tref: string,\n\topts?: { enabled?: boolean },\n): SpindleRef =>\n\topts?.enabled === undefined ? { ref } : { ref, enabled: opts.enabled };\n\n/**\n * Factories for the {@link CommandBinding} variants — a spoken voice command or\n * a hardware button mapping — for an {@link ExperienceSpec}'s `commands`.\n *\n * @example\n * ```typescript\n * command.voice(voiceCommand); // { kind: 'voice', command }\n * command.button(buttonMapping); // { kind: 'button', mapping }\n * ```\n */\nexport const command = {\n\t/** Wrap a {@link VoiceCommand} as a voice {@link CommandBinding}. */\n\tvoice: (cmd: VoiceCommand): CommandBinding => ({\n\t\tkind: 'voice',\n\t\tcommand: cmd,\n\t}),\n\t/** Wrap a {@link ButtonMapping} as a button {@link CommandBinding}. */\n\tbutton: (mapping: ButtonMapping): CommandBinding => ({\n\t\tkind: 'button',\n\t\tmapping,\n\t}),\n};\n\n// ─── Builder ───────────────────────────────────────────────────────────────────\n\n/**\n * Fluent builder for an {@link ExperienceSpec}. Accumulates the same fields as\n * the object form; `.build()` routes through {@link defineExperience}, so a\n * built spec is deep-equal to the equivalent `defineExperience({...})` object.\n * Start one with {@link experience}. Every setter returns `this` for chaining.\n *\n * @example\n * ```typescript\n * const spec = experience('Doorbell greeter')\n * .goal('Greet the visitor and take a message.')\n * .voice()\n * .addSpindle('google-calendar')\n * .build();\n * ```\n */\nexport class ExperienceBuilder {\n\tprivate readonly draft: ExperienceSpec;\n\n\t/**\n\t * @param name - The Experience name (unique within the owning resource).\n\t * Starts a text modality with empty spindle/grant/command lists.\n\t */\n\tconstructor(name: string) {\n\t\tthis.draft = {\n\t\t\tname,\n\t\t\tagent: { spindles: [], grants: [] },\n\t\t\tmodality: { kind: 'text' },\n\t\t\tcommands: [],\n\t\t};\n\t}\n\n\t/**\n\t * Set the human-readable description.\n\t * @param d - The description text.\n\t */\n\tdescription(d: string): this {\n\t\tthis.draft.description = d;\n\t\treturn this;\n\t}\n\t/**\n\t * Set the agent's objective (the system prompt).\n\t * @param g - The objective text.\n\t */\n\tgoal(g: string): this {\n\t\tthis.draft.agent.goal = g;\n\t\treturn this;\n\t}\n\t/**\n\t * Set the internal/operator prompt overlay.\n\t * @param g - The internal prompt text.\n\t * @remarks Not recovered by {@link decompileExperience} — it is not present\n\t * on the Experience read model.\n\t */\n\tinternalGoal(g: string): this {\n\t\tthis.draft.agent.internalGoal = g;\n\t\treturn this;\n\t}\n\t/**\n\t * Merge presentation style into the draft (shallow-merged over any prior style).\n\t * @param style - The style fields to apply.\n\t */\n\tstyle(style: ExperienceStyle): this {\n\t\tthis.draft.style = { ...this.draft.style, ...style };\n\t\treturn this;\n\t}\n\t/** Set the modality to text-only. */\n\ttext(): this {\n\t\tthis.draft.modality = textModality();\n\t\treturn this;\n\t}\n\t/**\n\t * Set the modality to voice.\n\t * @param opts - Optional TTS/STT overrides; omit to use platform defaults.\n\t */\n\tvoice(opts?: { tts?: TtsConfig; stt?: SttConfig }): this {\n\t\tthis.draft.modality = voiceModality(opts);\n\t\treturn this;\n\t}\n\t/**\n\t * Set the modality to multimodal (text plus voice).\n\t * @param opts - Optional TTS/STT overrides; omit to use platform defaults.\n\t */\n\tmultimodal(opts?: { tts?: TtsConfig; stt?: SttConfig }): this {\n\t\tthis.draft.modality = multimodalModality(opts);\n\t\treturn this;\n\t}\n\t/**\n\t * Set the model-routing hint.\n\t * @param m - The model policy. Reserved — not yet applied at runtime.\n\t */\n\tmodel(m: ModelPolicy): this {\n\t\tthis.draft.model = m;\n\t\treturn this;\n\t}\n\t/**\n\t * Add a spindle to the agent's palette.\n\t * @param ref - A spindle `slug` or `slug:toolName`.\n\t * @param opts - Optional settings.\n\t * @param opts.enabled - Enablement override; defaults to enabled.\n\t */\n\taddSpindle(ref: string, opts?: { enabled?: boolean }): this {\n\t\tthis.draft.agent.spindles.push(spindle(ref, opts));\n\t\treturn this;\n\t}\n\t/**\n\t * Declare an OAuth grant requirement for a credentialed spindle.\n\t * @param provider - OAuth provider id (e.g. `'google'`).\n\t * @param scopes - Required OAuth scopes; omit for none.\n\t */\n\tgrant(provider: string, scopes?: string[]): this {\n\t\tthis.draft.agent.grants.push(\n\t\t\tscopes === undefined ? { provider } : { provider, scopes },\n\t\t);\n\t\treturn this;\n\t}\n\t/**\n\t * Add a voice command binding.\n\t * @param cmd - The voice command to bind.\n\t */\n\tonVoice(cmd: VoiceCommand): this {\n\t\tthis.draft.commands.push(command.voice(cmd));\n\t\treturn this;\n\t}\n\t/**\n\t * Add a button command binding.\n\t * @param mapping - The button mapping to bind.\n\t */\n\tonButton(mapping: ButtonMapping): this {\n\t\tthis.draft.commands.push(command.button(mapping));\n\t\treturn this;\n\t}\n\t/**\n\t * Set the memory configuration.\n\t * @param m - Memory settings; set `retrieval: true` to enable retrieval.\n\t */\n\tmemory(m: { retrieval?: boolean }): this {\n\t\tthis.draft.memory = m;\n\t\treturn this;\n\t}\n\t/**\n\t * Validate and normalize the accumulated draft into a canonical spec.\n\t * @returns The built {@link ExperienceSpec}.\n\t * @throws {@link https://zod.dev | ZodError} when the draft fails validation.\n\t */\n\tbuild(): ExperienceSpec {\n\t\treturn defineExperience(this.draft);\n\t}\n}\n\n/**\n * Start a fluent {@link ExperienceBuilder}. `experience('x')...build()` is\n * deep-equal to the equivalent `defineExperience({...})` object form.\n *\n * @param name - The Experience name (unique within the owning resource).\n * @returns A new builder seeded with the given name.\n */\nexport function experience(name: string): ExperienceBuilder {\n\treturn new ExperienceBuilder(name);\n}\n\n// ─── Compile / decompile ────────────────────────────────────────────────────────\n\nconst STYLE_KEYS = [\n\t'voice',\n\t'lexicon',\n\t'rhetoric',\n\t'policy',\n\t'editing',\n\t'color',\n] as const;\n\n/**\n * Compile an {@link ExperienceSpec} into the Experience create/update payload.\n * Pure — no network. Exposed so callers can inspect exactly what\n * {@link experienceAuthoring} `.create()` / `.update()` would write.\n *\n * @param spec - The spec to compile. Re-validated and defaulted through the\n * canonical schema first, so a spec that skipped {@link defineExperience} is\n * still safe to compile (throws `ZodError` if invalid).\n * @returns The compiled create payload.\n * @throws {@link https://zod.dev | ZodError} when `spec` fails validation.\n * @remarks\n * Field mapping: `agent.goal` → `stylePrompt`, `agent.internalGoal` →\n * `internalSystemPrompt`, `agent.spindles` → the nested\n * `stylePreferences.spindles` palette, modality → `stylePreferences.{tts,stt}`\n * (mirrored to `voiceId` / `voiceProvider`), style →\n * `stylePreferences.{voice,lexicon,rhetoric,policy,editing,color}`, commands →\n * `stylePreferences.{voiceCommands,buttonMappings}`, and `memory.retrieval` →\n * `memoryRetrievalEnabled`. The spec's `gate` and `guardrails` are **not**\n * emitted — an Experience does not store them today.\n * @example\n * ```typescript\n * const payload = compileToExperienceInput(spec);\n * // { name, stylePrompt?, stylePreferences?, voiceId?, ... }\n * ```\n */\nexport function compileToExperienceInput(\n\tspec: ExperienceSpec,\n): CreateExperienceInput {\n\t// ERA-4410 regression fix: validate + fill defaults through the\n\t// canonical Zod schema before any dereference. This is the same\n\t// guarantee `defineExperience` provides (line 413); without it any\n\t// caller reaching this function via a type-safety escape hatch (e.g.\n\t// `as never`, a stale `AgentSpec` import that TS resolves to `any`,\n\t// or a pre-v27 legacy shape) crashed on unguarded reads of\n\t// `spec.agent.spindles`, `spec.commands`, and `spec.style`. The\n\t// schema's `.default([])` on `agent.spindles` / `agent.grants` /\n\t// `commands` guarantees safe iteration below.\n\tspec = experienceSpecSchema.parse(spec) as ExperienceSpec;\n\t// style allows partial voice sliders; the backend stylePreferences is a\n\t// looseObject that fills/validates the rest, so widen here.\n\tconst style = {\n\t\t...(spec.style ?? {}),\n\t} as ExperienceStylePreferences;\n\n\tif (spec.modality.kind !== 'text') {\n\t\tif (spec.modality.tts) style.tts = spec.modality.tts;\n\t\tif (spec.modality.stt) style.stt = spec.modality.stt;\n\t}\n\n\t// ERA-5905: write the canonical nested `spindles` shape only — the legacy\n\t// flat `mcpServers`/`mcpTools` maps are no longer emitted. Built directly\n\t// from the authoring spindle list.\n\tconst nestedSpindles = spindleRefsToNested(spec.agent.spindles);\n\tif (nestedSpindles) style.spindles = nestedSpindles;\n\n\tconst voiceCommands = spec.commands\n\t\t.filter(\n\t\t\t(c): c is { kind: 'voice'; command: VoiceCommand } => c.kind === 'voice',\n\t\t)\n\t\t.map((c) => c.command);\n\tconst buttonMappings = spec.commands\n\t\t.filter(\n\t\t\t(c): c is { kind: 'button'; mapping: ButtonMapping } =>\n\t\t\t\tc.kind === 'button',\n\t\t)\n\t\t.map((c) => c.mapping);\n\tif (voiceCommands.length) style.voiceCommands = voiceCommands;\n\tif (buttonMappings.length) style.buttonMappings = buttonMappings;\n\n\tif (spec.model) style.model = spec.model;\n\n\tconst input: CreateExperienceInput = { name: spec.name };\n\tif (spec.description !== undefined) input.description = spec.description;\n\tif (spec.agent.goal !== undefined) input.stylePrompt = spec.agent.goal;\n\tif (spec.agent.internalGoal !== undefined)\n\t\tinput.internalSystemPrompt = spec.agent.internalGoal;\n\tif (spec.memory?.retrieval !== undefined)\n\t\tinput.memoryRetrievalEnabled = spec.memory.retrieval;\n\tif (spec.modality.kind !== 'text' && spec.modality.tts) {\n\t\tinput.voiceId = spec.modality.tts.voice_id;\n\t\tinput.voiceProvider = spec.modality.tts.provider;\n\t}\n\tif (Object.keys(style).length > 0) input.stylePreferences = style;\n\treturn input;\n}\n\n/**\n * Reverse-compile an {@link Experience} read model into an\n * {@link ExperienceSpec} for round-trip editing. Pure — no network.\n *\n * @param exp - The Experience to reverse-compile (e.g. from\n * `era.experiences.get(id)`).\n * @returns The reconstructed spec, re-validated through {@link defineExperience}.\n * @remarks\n * Lossy for two fields that the Experience read model does not carry:\n * `agent.internalGoal` and `agent.grants` come back empty. The modality `kind`\n * is inferred from the stored TTS/STT config rather than persisted, so a\n * `'multimodal'` spec comes back as `'voice'`, and a voice spec with no\n * TTS/STT overrides comes back as `'text'`. Every other field round-trips\n * content-stably.\n */\nexport function decompileExperience(exp: Experience): ExperienceSpec {\n\tconst sp = (exp.stylePreferences ?? {}) as ExperienceStylePreferences;\n\n\tconst style: ExperienceStyle = {};\n\tfor (const k of STYLE_KEYS) {\n\t\tconst v = sp[k];\n\t\tif (v !== undefined) (style as Record<string, unknown>)[k] = v;\n\t}\n\n\t// ERA-5905: read the canonical nested `spindles` shape only — the legacy\n\t// flat `mcpServers`/`mcpTools` maps are no longer consulted.\n\tconst spindles: SpindleRef[] = [];\n\tif (sp.spindles && typeof sp.spindles === 'object') {\n\t\tfor (const [ref, enabled] of nestedSpindlesToEntries(\n\t\t\tsp.spindles as SpindlePalette,\n\t\t))\n\t\t\tspindles.push({ ref, enabled });\n\t}\n\n\tconst commands: CommandBinding[] = [\n\t\t...(sp.voiceCommands ?? []).map(\n\t\t\t(cmd): CommandBinding => ({ kind: 'voice', command: cmd }),\n\t\t),\n\t\t...(sp.buttonMappings ?? []).map(\n\t\t\t(mapping): CommandBinding => ({ kind: 'button', mapping }),\n\t\t),\n\t];\n\n\tconst mod: ExperienceModality =\n\t\tsp.tts || sp.stt\n\t\t\t? {\n\t\t\t\t\tkind: 'voice',\n\t\t\t\t\t...(sp.tts ? { tts: sp.tts } : {}),\n\t\t\t\t\t...(sp.stt ? { stt: sp.stt } : {}),\n\t\t\t\t}\n\t\t\t: { kind: 'text' };\n\n\tconst agent: AgentCore = { spindles, grants: [] };\n\tif (exp.stylePrompt != null) agent.goal = exp.stylePrompt;\n\n\tconst spec: ExperienceSpec = {\n\t\tname: exp.name,\n\t\tagent,\n\t\tmodality: mod,\n\t\tcommands,\n\t};\n\tif (Object.keys(style).length > 0) spec.style = style;\n\tif (exp.description != null) spec.description = exp.description;\n\tif (exp.memoryRetrievalEnabled !== undefined)\n\t\tspec.memory = { retrieval: exp.memoryRetrievalEnabled };\n\tif (sp.model && typeof sp.model === 'object')\n\t\tspec.model = sp.model as ModelPolicy;\n\n\treturn defineExperience(spec);\n}\n\n/**\n * Structural diff between two specs — the list of changed leaf paths. Pure — no\n * network. Use it to preview what an update would change before writing it.\n *\n * @param a - The baseline spec.\n * @param b - The spec to compare against the baseline.\n * @returns One {@link ExperienceDiffEntry} per changed leaf; empty when the two\n * specs are structurally equal.\n * @example\n * ```typescript\n * const changes = diffExperiences(before, after);\n * // [{ path: 'agent.goal', before: '…', after: '…' }]\n * ```\n */\nexport function diffExperiences(\n\ta: ExperienceSpec,\n\tb: ExperienceSpec,\n): ExperienceDiffEntry[] {\n\tconst out: ExperienceDiffEntry[] = [];\n\tconst walk = (x: unknown, y: unknown, path: string): void => {\n\t\tif (JSON.stringify(x) === JSON.stringify(y)) return;\n\t\tconst xo = x && typeof x === 'object' && !Array.isArray(x);\n\t\tconst yo = y && typeof y === 'object' && !Array.isArray(y);\n\t\tif (xo && yo) {\n\t\t\tconst keys = new Set([\n\t\t\t\t...Object.keys(x as object),\n\t\t\t\t...Object.keys(y as object),\n\t\t\t]);\n\t\t\tfor (const k of keys) {\n\t\t\t\twalk(\n\t\t\t\t\t(x as Record<string, unknown>)[k],\n\t\t\t\t\t(y as Record<string, unknown>)[k],\n\t\t\t\t\tpath ? `${path}.${k}` : k,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tout.push({ path, before: x, after: y });\n\t};\n\twalk(a, b, '');\n\treturn out;\n}\n\n// ─── Facade ─────────────────────────────────────────────────────────────────────\n\n/**\n * Resolve an output `slot` to a concrete deviceId via the caller's slot\n * bindings (ERA-6128 over the ERA-4500 registry). Fails closed, mirroring the\n * orchestrator's emit-time semantics: an unbound slot, or a bound device whose\n * `credentialStatus` is not `ok`, throws rather than connecting.\n */\nasync function resolveOutputDeviceId(\n\tclient: EraClient,\n\tslot: string,\n): Promise<string> {\n\tconst bindings = await devices(client).listSlotBindings();\n\tconst match = bindings.find((b) => b.slot === slot);\n\tif (!match) {\n\t\tconst known = bindings.map((b) => b.slot);\n\t\tthrow new Error(\n\t\t\t`experience.ports.output.subscribe: slot \"${slot}\" is not bound to a device` +\n\t\t\t\t(known.length > 0\n\t\t\t\t\t? ` (bound slots: ${known.join(', ')})`\n\t\t\t\t\t: ' (no slots bound)') +\n\t\t\t\t'. Bind it with devices.bindSlot(deviceId, slot), or pass { deviceId }.',\n\t\t);\n\t}\n\tif (match.credentialStatus !== 'ok') {\n\t\tthrow new Error(\n\t\t\t`experience.ports.output.subscribe: slot \"${slot}\" resolves to device ` +\n\t\t\t\t`${match.deviceId}, whose credentialStatus is \"${match.credentialStatus}\" ` +\n\t\t\t\t'— refusing to connect (fail-closed). Re-provision the device key, then retry.',\n\t\t);\n\t}\n\treturn match.deviceId;\n}\n\n/** Options for {@link ExperienceOutputPort.subscribe}. */\nexport interface OutputSubscribeOptions {\n\t/** Connect to this device directly. Takes precedence over `slot`. */\n\tdeviceId?: string;\n\t/**\n\t * Named output slot to resolve to a device via the caller's slot bindings.\n\t * Ignored when `deviceId` is set. Resolution fails closed: an unbound slot,\n\t * or a device whose credential status is not `ok`, throws (surfaced via\n\t * `onError`).\n\t */\n\tslot?: string;\n\t/**\n\t * Device-bound `era_sk_*` bearer token for the socket. Omit to fall back to\n\t * the SDK's stored device key, then to the client's access token.\n\t */\n\ttoken?: string;\n\t/**\n\t * Called with any error raised while resolving the slot or connecting the\n\t * stream. Without it, such errors reject on the background task.\n\t */\n\tonError?: (err: Error) => void;\n}\n\n/** The output role-port — subscribe to the outputs emitted by an Experience's runs. */\nexport interface ExperienceOutputPort {\n\t/**\n\t * Subscribe to the command frames a run's outputs deliver to a device:\n\t * connects the device stream and invokes `handler` for each typed frame\n\t * (`actuator` / `say`). Returns an unsubscribe function that closes the stream.\n\t *\n\t * @param handler - Called once per delivered {@link DeviceCommand} frame.\n\t * @param opts - Targeting and connection options. Provide exactly one of\n\t * `deviceId` (explicit) or `slot` (resolved via your device bindings).\n\t * @returns An unsubscribe function that closes the stream.\n\t * @throws {@link Error} synchronously when neither `deviceId` nor `slot` is\n\t * given. Slot-resolution failures (unbound slot, or a device whose credential\n\t * status is not `ok`) surface via `opts.onError` when provided.\n\t * @remarks Requires a device-bound `era_sk_*` key (or `opts.token`). Slot\n\t * resolution fails closed — it refuses to connect rather than reaching a\n\t * device with a compromised credential.\n\t * @example\n\t * ```typescript\n\t * const unsubscribe = bound.ports.output.subscribe(\n\t * (command) => console.log(command.type), // 'actuator' | 'say'\n\t * { slot: 'porch-light', onError: console.error },\n\t * );\n\t * ```\n\t */\n\tsubscribe(\n\t\thandler: (command: DeviceCommand) => void,\n\t\topts?: OutputSubscribeOptions,\n\t): () => void;\n}\n\n/**\n * The three role-ports of a bound Experience: `input` (publish a trigger),\n * `run` (drive/observe a turn), and `output` (subscribe to emitted outputs).\n */\nexport interface ExperiencePorts {\n\t/**\n\t * Input port — publish a {@link TriggerEvent} to fire the Experience\n\t * (`POST /trigger`).\n\t * @param event - The trigger to publish. Set `source.sessionId` to route\n\t * the event into that live session.\n\t * @param opts - Optional per-request options.\n\t * @returns Resolves once the trigger is accepted.\n\t */\n\tinput(event: TriggerEvent, opts?: RequestOptions): Promise<void>;\n\t/**\n\t * Run port — drive and observe a turn, streaming {@link ExperienceRunEvent}s.\n\t * @param input - The turn input.\n\t * @returns An async iterable of run events.\n\t * @throws {@link Error} when called before `create()` has set the Experience id.\n\t * @remarks Single-turn today; requires `create()` first to obtain the id.\n\t */\n\trun(input: ExperienceRunInput): AsyncIterable<ExperienceRunEvent>;\n\t/** Output port — subscribe to the outputs emitted by the Experience's runs. */\n\toutput: ExperienceOutputPort;\n}\n\n/**\n * A client-bound Experience handle returned by\n * {@link ExperienceAuthoringModule.bind}. {@link defineExperience} stays\n * pure/serializable; binding adds the client plus the three role-ports.\n */\nexport interface BoundExperience {\n\t/** The authored spec (pure, serializable). */\n\treadonly spec: ExperienceSpec;\n\t/** The created Experience's id — `undefined` until `create()` runs. */\n\treadonly experienceId: string | undefined;\n\t/**\n\t * Create the Experience under an RBAC resource and set `experienceId` so the\n\t * run port works.\n\t * @param target - The owning resource.\n\t * @param target.resourceId - The RBAC resource id to create the Experience under.\n\t * @param opts - Optional per-request options.\n\t * @returns The created {@link Experience}.\n\t * @remarks Promotion to a published version is a separate review flow — use\n\t * `era.experienceVersions` (`submitForReview` / `approveReview`).\n\t */\n\tcreate(\n\t\ttarget: { resourceId: string },\n\t\topts?: RequestOptions,\n\t): Promise<Experience>;\n\t/**\n\t * Locally simulate this Experience against a mock trigger — no network.\n\t * @param event - The trigger event to simulate.\n\t * @returns The {@link SimulationResult}.\n\t */\n\tsimulate(event: TriggerEvent): SimulationResult;\n\t/** The three role-ports: `input` (publish) · `run` (stream) · `output` (subscribe). */\n\treadonly ports: ExperiencePorts;\n}\n\n/**\n * The client-bound experience authoring and run facade, returned by\n * {@link experienceAuthoring}. Compiles pure {@link ExperienceSpec}s into\n * Experience CRUD and draft calls, drives turns over the streaming chat\n * endpoint, and binds specs into runnable {@link BoundExperience} handles.\n * Reach it as `experienceAuthoring(era)` (it is not attached to `era` directly).\n */\nexport interface ExperienceAuthoringModule {\n\t/**\n\t * Compile a spec and create the Experience under an RBAC resource.\n\t * @param spec - The spec to compile and create.\n\t * @param target - The owning resource.\n\t * @param target.resourceId - The RBAC resource id to create the Experience under.\n\t * @param opts - Optional per-request options.\n\t * @returns The created {@link Experience}.\n\t * @throws {EraAPIError} On a non-2xx response (400 invalid body, 403 missing\n\t * `write` on the resource, 404 unknown resource).\n\t * @example\n\t * ```typescript\n\t * const created = await experienceAuthoring(era).create(spec, {\n\t * resourceId: 'res_0000000000',\n\t * });\n\t * ```\n\t */\n\tcreate(\n\t\tspec: ExperienceSpec,\n\t\ttarget: { resourceId: string },\n\t\topts?: RequestOptions,\n\t): Promise<Experience>;\n\n\t/**\n\t * Compile a spec and update an existing Experience.\n\t * @param experienceId - The Experience to update.\n\t * @param spec - The spec whose compiled payload replaces the Experience config.\n\t * @param opts - Optional per-request options.\n\t * @returns The updated {@link Experience}.\n\t * @throws {EraAPIError} On a non-2xx response (400 invalid body, 403 not the\n\t * owner/writer, 404 unknown id).\n\t */\n\tupdate(\n\t\texperienceId: string,\n\t\tspec: ExperienceSpec,\n\t\topts?: RequestOptions,\n\t): Promise<Experience>;\n\n\t/**\n\t * Fetch an Experience and reverse-compile it into an {@link ExperienceSpec}\n\t * for round-trip editing.\n\t * @param experienceId - The Experience to fetch.\n\t * @param opts - Optional per-request options.\n\t * @returns The reconstructed spec (lossy for `agent.internalGoal` and\n\t * `agent.grants`; see {@link decompileExperience}).\n\t * @throws {EraAPIError} On a non-2xx response (403 no read access, 404 not found).\n\t */\n\tfromExperience(\n\t\texperienceId: string,\n\t\topts?: RequestOptions,\n\t): Promise<ExperienceSpec>;\n\n\t/**\n\t * Create a new draft version of an Experience seeded from a spec (creates the\n\t * draft, then writes the compiled content into it).\n\t * @param experienceId - The Experience to draft a new version of.\n\t * @param spec - The spec whose compiled payload seeds the draft.\n\t * @param opts - Optional per-request options.\n\t * @returns The draft's {@link VersionMutationResult}.\n\t * @throws {EraAPIError} On a non-2xx response from either the draft-create or\n\t * the draft-update call: 403 when you lack draft permissions on the\n\t * experience, 404 unknown experience, 409 when a concurrent writer updated\n\t * the draft between the create and the content write.\n\t * @remarks A draft is not live — promote it through the review flow on\n\t * `era.experienceVersions` to publish.\n\t */\n\tdraft(\n\t\texperienceId: string,\n\t\tspec: ExperienceSpec,\n\t\topts?: RequestOptions,\n\t): Promise<VersionMutationResult>;\n\n\t/**\n\t * Structural diff between two specs — the list of changed leaf paths. Pure,\n\t * no network. Alias of {@link diffExperiences}.\n\t * @param a - The baseline spec.\n\t * @param b - The spec to compare against the baseline.\n\t * @returns One {@link ExperienceDiffEntry} per changed leaf.\n\t */\n\tdiff(a: ExperienceSpec, b: ExperienceSpec): ExperienceDiffEntry[];\n\n\t/**\n\t * Inspect the compiled Experience payload for a spec without writing it.\n\t * Pure, no network. Alias of {@link compileToExperienceInput}.\n\t * @param spec - The spec to compile.\n\t * @returns The compiled create payload.\n\t */\n\ttoExperienceInput(spec: ExperienceSpec): CreateExperienceInput;\n\n\t/**\n\t * Run port — drive one turn against an Experience, streaming normalized\n\t * {@link ExperienceRunEvent}s over SSE.\n\t * @param experienceId - The Experience to run.\n\t * @param input - The turn input.\n\t * @returns An async iterable of run events; the terminal `done` carries\n\t * `toolsUsed` plus `requestId` / `costCents` / `balanceCents` when the stream\n\t * provides them.\n\t * @example\n\t * ```typescript\n\t * for await (const ev of experienceAuthoring(era).run('exp_0000000000', {\n\t * input: 'Plan my day',\n\t * })) {\n\t * if (ev.type === 'chunk') process.stdout.write(ev.delta);\n\t * if (ev.type === 'done') console.log('\\ntools used:', ev.toolsUsed);\n\t * }\n\t * ```\n\t */\n\trun(\n\t\texperienceId: string,\n\t\tinput: ExperienceRunInput,\n\t): AsyncIterable<ExperienceRunEvent>;\n\n\t/**\n\t * Non-streaming convenience — drive {@link ExperienceAuthoringModule.run} to\n\t * completion and return the aggregated reply.\n\t *\n\t * @param experienceId - The Experience to run.\n\t * @param input - The turn input.\n\t * @returns The aggregated {@link ExperienceReply}.\n\t * @throws {@link EraStreamError} when the run stream carried a terminal\n\t * `error` event (a server-emitted error, or an SDK-side parse/read\n\t * failure). The aggregation cannot return a meaningful reply for an errored\n\t * stream — a bare `{ text: '' }` is indistinguishable from a genuinely empty\n\t * reply — so it throws instead. The streaming `run()` path keeps yielding\n\t * `error` events verbatim; only this aggregation convenience throws. A\n\t * `blocked` (moderation) outcome alone does NOT throw — it is surfaced\n\t * additively on `reply.blocked` for the caller to inspect. Precedence when\n\t * a stream carries BOTH a block and an error: the error throw wins, and the\n\t * block's category (when a `blocked` event preceded the error) rides on the\n\t * thrown error's `safetyCategory`.\n\t * @example\n\t * ```typescript\n\t * const reply = await experienceAuthoring(era).complete('exp_0000000000', {\n\t * input: 'Someone is at the door.',\n\t * });\n\t * if (reply.blocked) console.warn('refused:', reply.blocked.safetyCategory);\n\t * else console.log(reply.text);\n\t * ```\n\t */\n\tcomplete(\n\t\texperienceId: string,\n\t\tinput: ExperienceRunInput,\n\t): Promise<ExperienceReply>;\n\n\t/**\n\t * Bind a pure {@link ExperienceSpec} to this client → a\n\t * {@link BoundExperience} exposing the three role-ports\n\t * (`.ports = { input, run, output }`).\n\t * @param spec - The spec to bind.\n\t * @returns The client-bound handle.\n\t */\n\tbind(spec: ExperienceSpec): BoundExperience;\n\n\t/**\n\t * Inspect a run's state by run id — reuses the chains run-state surface\n\t * ({@link chains}.getRun): gate decision, spindle calls and results, outputs,\n\t * and guardrail checks from the run-events log.\n\t * @param runId - The run to inspect.\n\t * @param opts - Optional per-request options.\n\t * @returns The {@link ChainRunState}.\n\t * @throws {EraAPIError} On a non-2xx response (404 unknown run).\n\t */\n\tgetRun(runId: string, opts?: RequestOptions): Promise<ChainRunState>;\n}\n\n/**\n * Construct the experience authoring and run facade bound to an `EraClient`.\n *\n * @param client - The Era client to bind the facade to.\n * @returns The {@link ExperienceAuthoringModule} — compile/create/update/draft,\n * run/complete, diff/inspect, and `bind`.\n * @example\n * ```typescript\n * const authoring = experienceAuthoring(era);\n * const created = await authoring.create(spec, { resourceId: 'res_0000000000' });\n * const reply = await authoring.complete(created.id, { input: 'Hello' });\n * ```\n */\nexport function experienceAuthoring(\n\tclient: EraClient,\n): ExperienceAuthoringModule {\n\tconst exp = experiences(client);\n\tconst vers = experienceVersions(client);\n\n\t// Open the underlying ingress stream and wrap it in the run-event decorator.\n\t// `run()` exposes only the events; `complete()` also needs the raw handle so\n\t// it can read `meta()` (requestId) even when the stream aborts without a\n\t// `done` event (parseSSE emits NO synthetic done on SSE_READ_ERROR /\n\t// SSE_LINE_TOO_LONG paths).\n\tconst openRun = (\n\t\texperienceId: string,\n\t\tinput: ExperienceRunInput,\n\t): {\n\t\thandle: StreamHandle;\n\t\tevents: AsyncIterable<ExperienceRunEvent>;\n\t} => {\n\t\tconst isVoice = input.modality === 'voice';\n\t\tconst body: Record<string, unknown> = {\n\t\t\tmessage: input.input,\n\t\t\texperienceId,\n\t\t\t// `/chat` defaults to a non-streaming JSON response\n\t\t\t// (ChatRequest.stream = false server-side); opt into SSE explicitly.\n\t\t\tstream: true,\n\t\t};\n\t\tif (input.sessionId !== undefined) body.sessionId = input.sessionId;\n\t\tif (isVoice) body.voice = true;\n\t\tif (input.metadata !== undefined) body.metadata = input.metadata;\n\t\tconst streamOpts: RequestOptions & { method?: string; body?: unknown } = {\n\t\t\tmethod: 'POST',\n\t\t\tbody,\n\t\t};\n\t\tif (input.signal) streamOpts.signal = input.signal;\n\t\t// ERA-5140: forward the caller-pinned idempotencyKey to the transport\n\t\t// so wrapper retries dedupe against the L1/L2 idempotency stack\n\t\t// (ERA-5088 + ERA-5102 + ERA-5111) instead of minting a fresh UUID\n\t\t// per call. Restores the fix originally added to the pre-rename\n\t\t// `agents.ts` module (commit 26df09b) that was lost in the ERA-4410\n\t\t// rename to `experienceAuthoring.ts` (commit 702a391).\n\t\tif (input.idempotencyKey !== undefined)\n\t\t\tstreamOpts.idempotencyKey = input.idempotencyKey;\n\t\tconst handle = client.stream(\n\t\t\t'ingress',\n\t\t\tisVoice ? '/chat/voice' : '/chat',\n\t\t\tstreamOpts,\n\t\t);\n\t\tconst events = (async function* (): AsyncGenerator<\n\t\t\tExperienceRunEvent,\n\t\t\tvoid,\n\t\t\tvoid\n\t\t> {\n\t\t\tconst toolsUsed: string[] = [];\n\t\t\tfor await (const ev of handle) {\n\t\t\t\tif (ev.type === 'tool_call') {\n\t\t\t\t\ttoolsUsed.push(ev.name);\n\t\t\t\t\tyield ev;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (ev.type === 'done') {\n\t\t\t\t\tlet meta: ResponseMeta = {};\n\t\t\t\t\ttry {\n\t\t\t\t\t\tmeta = await handle.meta();\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t/* meta unavailable — leave cost/balance unset */\n\t\t\t\t\t}\n\t\t\t\t\tconst done: ExperienceRunDoneEvent = {\n\t\t\t\t\t\ttype: 'done',\n\t\t\t\t\t\tfullContent: ev.fullContent,\n\t\t\t\t\t\t// Prefer the wire-reported tools_used (generated\n\t\t\t\t\t\t// ChatDoneStreamEvent) — the server's provenance is\n\t\t\t\t\t\t// authoritative; the client-side tool_call aggregation is\n\t\t\t\t\t\t// the fallback for servers that omit the field.\n\t\t\t\t\t\ttoolsUsed: ev.toolsUsed ?? [...toolsUsed],\n\t\t\t\t\t};\n\t\t\t\t\tif (ev.usage) done.usage = ev.usage;\n\t\t\t\t\tif (meta.requestId !== undefined) done.requestId = meta.requestId;\n\t\t\t\t\t// Cost/balance are integer cents (ERA-4326/ERA-5293). Prefer the\n\t\t\t\t\t// explicit stream `done` event values; fall back to the response\n\t\t\t\t\t// `_meta` (costCents/balanceCents) when the event omits them.\n\t\t\t\t\tif (meta.costCents !== undefined) done.costCents = meta.costCents;\n\t\t\t\t\tif (meta.balanceCents !== undefined)\n\t\t\t\t\t\tdone.balanceCents = meta.balanceCents;\n\t\t\t\t\tif (ev.costCents !== undefined) done.costCents = ev.costCents;\n\t\t\t\t\tif (ev.balanceCents !== undefined)\n\t\t\t\t\t\tdone.balanceCents = ev.balanceCents;\n\t\t\t\t\tyield done;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tyield ev;\n\t\t\t}\n\t\t})();\n\t\treturn { handle, events };\n\t};\n\n\tconst runImpl = (\n\t\texperienceId: string,\n\t\tinput: ExperienceRunInput,\n\t): AsyncIterable<ExperienceRunEvent> => openRun(experienceId, input).events;\n\n\treturn {\n\t\tcreate(\n\t\t\tspec: ExperienceSpec,\n\t\t\ttarget: { resourceId: string },\n\t\t\topts?: RequestOptions,\n\t\t): Promise<Experience> {\n\t\t\treturn exp.create(\n\t\t\t\t{ resourceId: target.resourceId, ...compileToExperienceInput(spec) },\n\t\t\t\topts,\n\t\t\t);\n\t\t},\n\t\tupdate(\n\t\t\texperienceId: string,\n\t\t\tspec: ExperienceSpec,\n\t\t\topts?: RequestOptions,\n\t\t): Promise<Experience> {\n\t\t\t// compile yields CreateExperienceInput (description: string|null);\n\t\t\t// UpdateExperienceInput uses string|undefined. compile never emits null.\n\t\t\treturn exp.update(\n\t\t\t\texperienceId,\n\t\t\t\tcompileToExperienceInput(spec) as unknown as UpdateExperienceInput,\n\t\t\t\topts,\n\t\t\t);\n\t\t},\n\t\tasync fromExperience(\n\t\t\texperienceId: string,\n\t\t\topts?: RequestOptions,\n\t\t): Promise<ExperienceSpec> {\n\t\t\treturn decompileExperience(await exp.get(experienceId, opts));\n\t\t},\n\t\tasync draft(\n\t\t\texperienceId: string,\n\t\t\tspec: ExperienceSpec,\n\t\t\topts?: RequestOptions,\n\t\t): Promise<VersionMutationResult> {\n\t\t\tconst created = await vers.createDraft(experienceId, undefined, opts);\n\t\t\t// compile yields CreateExperienceInput; its fields are a superset of\n\t\t\t// UpdateDraftInput's (compile never sets isActive, and\n\t\t\t// stylePreferences is only ever an object here, never null).\n\t\t\tconst body = compileToExperienceInput(\n\t\t\t\tspec,\n\t\t\t) as unknown as UpdateDraftInput;\n\t\t\treturn vers.updateDraft(\n\t\t\t\tcreated.version.id,\n\t\t\t\tbody,\n\t\t\t\tcreated.version.updatedAt,\n\t\t\t\topts,\n\t\t\t);\n\t\t},\n\t\trun: runImpl,\n\t\tasync complete(\n\t\t\texperienceId: string,\n\t\t\tinput: ExperienceRunInput,\n\t\t): Promise<ExperienceReply> {\n\t\t\tconst { handle, events } = openRun(experienceId, input);\n\t\t\tlet done: ExperienceRunDoneEvent | null = null;\n\t\t\tlet blocked: StreamBlockedEvent | null = null;\n\t\t\tlet streamError: StreamErrorEvent | null = null;\n\t\t\tlet blockedBeforeError: StreamBlockedEvent | null = null;\n\t\t\tlet text = '';\n\t\t\tfor await (const ev of events) {\n\t\t\t\tif (ev.type === 'chunk') text += ev.delta;\n\t\t\t\telse if (ev.type === 'done') done = ev;\n\t\t\t\telse if (ev.type === 'blocked') blocked = ev;\n\t\t\t\t// Capture the FIRST error event — the root cause. For server-emitted\n\t\t\t\t// errors, parseSSE keeps reading (and flushes a synthetic `done`); on\n\t\t\t\t// aborted paths (SSE_READ_ERROR / SSE_LINE_TOO_LONG) the generator\n\t\t\t\t// closes right after the error with NO synthetic done — either way we\n\t\t\t\t// finish draining, then throw below.\n\t\t\t\telse if (ev.type === 'error' && streamError === null) {\n\t\t\t\t\tstreamError = ev;\n\t\t\t\t\t// Snapshot any block already observed — if the stream carried both,\n\t\t\t\t\t// the throw wins and the block's category rides on the error.\n\t\t\t\t\tblockedBeforeError = blocked;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// An errored stream cannot yield a meaningful aggregation — a bare\n\t\t\t// `{ text: '' }` is indistinguishable from a genuinely empty reply — so\n\t\t\t// throw a typed EraStreamError rather than returning a hollow reply.\n\t\t\t// (The streaming `run()` path keeps yielding the `error` event; only\n\t\t\t// this convenience throws.) `requestId` comes from the handle's response\n\t\t\t// headers — NOT from the enriched `done` event, which never arrives on\n\t\t\t// aborted streams (mirrors chat.complete()).\n\t\t\tif (streamError) {\n\t\t\t\tlet meta: ResponseMeta = {};\n\t\t\t\ttry {\n\t\t\t\t\tmeta = await handle.meta();\n\t\t\t\t} catch {\n\t\t\t\t\t/* meta unavailable — omit requestId from the thrown error */\n\t\t\t\t}\n\t\t\t\tthrow new EraStreamError({\n\t\t\t\t\tmessage: streamError.message,\n\t\t\t\t\t...(streamError.code !== undefined && {\n\t\t\t\t\t\terrorCode: streamError.code,\n\t\t\t\t\t}),\n\t\t\t\t\t...(meta.requestId !== undefined && { requestId: meta.requestId }),\n\t\t\t\t\t...(blockedBeforeError?.safetyCategory !== undefined && {\n\t\t\t\t\t\tsafetyCategory: blockedBeforeError.safetyCategory,\n\t\t\t\t\t}),\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst reply: ExperienceReply = {\n\t\t\t\ttext: done?.fullContent ?? text,\n\t\t\t\ttoolsUsed: done?.toolsUsed ?? [],\n\t\t\t};\n\t\t\t// Surface a moderation block additively — the caller inspects\n\t\t\t// `reply.blocked` rather than catching a throw (a block is a policy\n\t\t\t// outcome, not a transport failure; the established complete()\n\t\t\t// contract stays intact).\n\t\t\tif (blocked) {\n\t\t\t\treply.blocked = {\n\t\t\t\t\t...(blocked.safetyCategory !== undefined && {\n\t\t\t\t\t\tsafetyCategory: blocked.safetyCategory,\n\t\t\t\t\t}),\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (done?.usage) reply.usage = done.usage;\n\t\t\tif (done?.cost !== undefined) reply.cost = done.cost;\n\t\t\tif (done?.balance !== undefined) reply.balance = done.balance;\n\t\t\tif (done?.requestId !== undefined) reply.requestId = done.requestId;\n\t\t\tif (done?.costCents !== undefined) reply.costCents = done.costCents;\n\t\t\tif (done?.balanceCents !== undefined)\n\t\t\t\treply.balanceCents = done.balanceCents;\n\t\t\treturn reply;\n\t\t},\n\t\tdiff: diffExperiences,\n\t\ttoExperienceInput: compileToExperienceInput,\n\t\tgetRun(runId: string, opts?: RequestOptions): Promise<ChainRunState> {\n\t\t\treturn chains(client).getRun(runId, opts);\n\t\t},\n\t\tbind(spec: ExperienceSpec): BoundExperience {\n\t\t\tlet experienceId: string | undefined;\n\t\t\tconst ports: ExperiencePorts = {\n\t\t\t\tinput(event: TriggerEvent, opts?: RequestOptions): Promise<void> {\n\t\t\t\t\tconst init: RequestOptions & { method?: string; body?: unknown } = {\n\t\t\t\t\t\t...opts,\n\t\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\t\tbody: event,\n\t\t\t\t\t};\n\t\t\t\t\treturn client.ingress<void>('/trigger', init);\n\t\t\t\t},\n\t\t\t\trun(input: ExperienceRunInput): AsyncIterable<ExperienceRunEvent> {\n\t\t\t\t\tif (experienceId === undefined) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t'experience.ports.run: call create() to obtain an experienceId first.',\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\treturn runImpl(experienceId, input);\n\t\t\t\t},\n\t\t\t\toutput: {\n\t\t\t\t\tsubscribe(\n\t\t\t\t\t\thandler: (command: DeviceCommand) => void,\n\t\t\t\t\t\topts?: OutputSubscribeOptions,\n\t\t\t\t\t): () => void {\n\t\t\t\t\t\t// A target is required — an explicit deviceId, or a slot we\n\t\t\t\t\t\t// resolve via the caller's bindings (ERA-6128). Fail fast and\n\t\t\t\t\t\t// synchronously when neither is given.\n\t\t\t\t\t\tif (!opts?.deviceId && !opts?.slot) {\n\t\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\t'experience.ports.output.subscribe: pass { deviceId }, or { slot } to resolve via your device bindings.',\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlet stream: DeviceStream | null = null;\n\t\t\t\t\t\tlet unsubscribed = false;\n\t\t\t\t\t\tconst run = async (): Promise<void> => {\n\t\t\t\t\t\t\t// deviceId wins; otherwise resolve the slot (fails closed).\n\t\t\t\t\t\t\tconst deviceId =\n\t\t\t\t\t\t\t\topts.deviceId ??\n\t\t\t\t\t\t\t\t(await resolveOutputDeviceId(\n\t\t\t\t\t\t\t\t\tclient,\n\t\t\t\t\t\t\t\t\t// biome-ignore lint/style/noNonNullAssertion: guarded above — slot is set when deviceId is not\n\t\t\t\t\t\t\t\t\topts.slot!,\n\t\t\t\t\t\t\t\t));\n\t\t\t\t\t\t\tif (unsubscribed) return;\n\t\t\t\t\t\t\tconst s = await deviceStream(client).connect(deviceId, {\n\t\t\t\t\t\t\t\t...(opts.token !== undefined ? { token: opts.token } : {}),\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tif (unsubscribed) {\n\t\t\t\t\t\t\t\ts.close();\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tstream = s;\n\t\t\t\t\t\t\tfor await (const command of s) handler(command);\n\t\t\t\t\t\t};\n\t\t\t\t\t\tvoid run().catch((err: unknown) => {\n\t\t\t\t\t\t\tconst e = err instanceof Error ? err : new Error(String(err));\n\t\t\t\t\t\t\tif (opts.onError) opts.onError(e);\n\t\t\t\t\t\t\telse throw e;\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn () => {\n\t\t\t\t\t\t\tunsubscribed = true;\n\t\t\t\t\t\t\tstream?.close();\n\t\t\t\t\t\t};\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t};\n\t\t\treturn {\n\t\t\t\tget spec(): ExperienceSpec {\n\t\t\t\t\treturn spec;\n\t\t\t\t},\n\t\t\t\tget experienceId(): string | undefined {\n\t\t\t\t\treturn experienceId;\n\t\t\t\t},\n\t\t\t\tasync create(\n\t\t\t\t\ttarget: { resourceId: string },\n\t\t\t\t\topts?: RequestOptions,\n\t\t\t\t): Promise<Experience> {\n\t\t\t\t\tconst created = await exp.create(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tresourceId: target.resourceId,\n\t\t\t\t\t\t\t...compileToExperienceInput(spec),\n\t\t\t\t\t\t},\n\t\t\t\t\t\topts,\n\t\t\t\t\t);\n\t\t\t\t\texperienceId = created.id;\n\t\t\t\t\treturn created;\n\t\t\t\t},\n\t\t\t\tsimulate(event: TriggerEvent): SimulationResult {\n\t\t\t\t\treturn simulateSpec(spec, event);\n\t\t\t\t},\n\t\t\t\tports,\n\t\t\t};\n\t\t},\n\t};\n}\n","// keyStore module — durable two-slot device-key storage (ERA-6198).\n//\n// The client half of device-key rotation (ERA-6037). The server revokes a\n// predecessor key the moment its successor FIRST authenticates, and presenting\n// a rotated-out key trips reuse detection: the whole lineage is revoked and the\n// device is flagged `credential_compromised` — bricked until re-provisioned.\n// The only way rotation is restart-safe is write-ahead persistence: the\n// successor must be durably stored BEFORE it is ever used.\n//\n// Two-slot (A/B) protocol, same shape as firmware OTA slots:\n// 1. renew mints K2 → savePending(K2) (durable BEFORE first use)\n// 2. K2 first authenticates → server revokes K1 (inside its grace window)\n// 3. connection confirmed → promote() (pending → current)\n// Boot: try `current`; on auth-reject (1008) fall back to `pending` once.\n//\n// Every crash window is safe: before savePending → K1 still live (an unused\n// successor is replaced server-side on the next renew); after savePending but\n// before activation → K1 connects, K2 idle; after activation but before\n// promote → K1 is rejected and the boot fallback finds K2 in `pending`; after\n// promote → K2 is `current`. No ordering leaves the device holding ONLY a\n// rotated-out key.\n//\n// `deviceStream({ autoRenew: true, keyStore })` drives this protocol and will\n// not activate a successor until `savePending` has resolved AND a read-back\n// confirms the store returns it (catches no-op implementations).\n//\n// Tree-shakeable barrel import: `import { fileKeyStore } from '@era-laboratories/era-sdk'`.\n\n/**\n * Durable two-slot storage for a device-bound key.\n *\n * DURABILITY CONTRACT — read this before implementing for NVS/Keychain/TPM:\n * `savePending` MUST NOT resolve until the key would survive an immediate\n * power loss (fsync or the platform equivalent). If it resolves before the\n * bytes are durable, a crash after the successor's first use leaves the device\n * holding only the revoked predecessor — the next boot trips reuse detection\n * and the device is flagged `credential_compromised`.\n */\nexport interface DeviceKeyStore {\n\t/**\n\t * Read both slots. Either may be absent (a fresh device returns `{}`).\n\t * Called at connect time to pick the key to present, and again as the\n\t * read-back that gates successor activation.\n\t *\n\t * @returns The two slots: `current` (the active key) and `pending` (a\n\t * durably-stored successor awaiting its first confirmed connection). A key\n\t * that is absent from the store is omitted from the result.\n\t * @throws {Error} If the underlying store cannot be read or its contents are\n\t * corrupt — a corrupt store MUST throw rather than read as empty (an empty\n\t * read could route the caller onto a revoked key).\n\t */\n\tload(): Promise<{ current?: string; pending?: string }>;\n\t/**\n\t * Write-ahead the successor key into the `pending` slot. Must NOT resolve\n\t * until the bytes would survive an immediate power loss (fsync or the\n\t * platform equivalent) — this durability is the whole point of the store.\n\t *\n\t * @param key - The successor `era_sk_*` key to persist before it is used.\n\t * @throws {Error} If the key cannot be durably written.\n\t */\n\tsavePending(key: string): Promise<void>;\n\t/**\n\t * Promote `pending` to `current` and clear `pending`, called after the\n\t * successor's first confirmed connection. Idempotent: a no-op when there is\n\t * no pending key.\n\t *\n\t * @throws {Error} If the store cannot be updated durably.\n\t */\n\tpromote(): Promise<void>;\n}\n\n/**\n * On-disk shape for {@link fileKeyStore}.\n *\n * @internal Implementation detail of the file-backed store — not exported.\n */\ninterface FileKeyStoreState {\n\tcurrent?: string;\n\tpending?: string;\n}\n\nfunction parseState(raw: string, path: string): FileKeyStoreState {\n\tlet data: unknown;\n\ttry {\n\t\tdata = JSON.parse(raw);\n\t} catch (err) {\n\t\t// A corrupt store is a loud failure, never a silent empty store — an\n\t\t// \"empty\" read here could route the caller onto a revoked key.\n\t\tthrow new Error(\n\t\t\t`fileKeyStore: ${path} is corrupt (invalid JSON): ${err instanceof Error ? err.message : String(err)}`,\n\t\t);\n\t}\n\tif (typeof data !== 'object' || data === null) {\n\t\tthrow new Error(`fileKeyStore: ${path} is corrupt (expected an object)`);\n\t}\n\tconst obj = data as Record<string, unknown>;\n\tconst out: FileKeyStoreState = {};\n\tif (typeof obj.current === 'string') out.current = obj.current;\n\tif (typeof obj.pending === 'string') out.pending = obj.pending;\n\treturn out;\n}\n\n/**\n * File-backed {@link DeviceKeyStore} — the reference implementation for\n * Node-capable devices (Pi, kiosk, desktop). Two-slot JSON file with atomic\n * replace (write temp + fsync + rename) and a directory fsync so the rename\n * itself survives power loss. Node-only: uses `node:fs/promises` via dynamic\n * import so browser bundles that tree-shake the barrel stay clean.\n *\n * @param path - Absolute path to the JSON file that holds the two slots. The\n * parent directory must already exist. Must be non-empty.\n * @returns A {@link DeviceKeyStore} backed by that file — hand it to\n * `deviceStream(...).connect(deviceId, { autoRenew: true, keyStore })`.\n * @throws {Error} Synchronously if `path` is empty. Its methods reject if the\n * file is unreadable/unwritable, or (on `load`) if the file exists but is\n * corrupt (invalid JSON or wrong shape); a missing file reads as `{}`.\n *\n * @example\n * ```ts\n * const store = fileKeyStore('/var/lib/mydevice/era-key.json');\n * const stream = await deviceStream(era).connect(deviceId, {\n * autoRenew: true,\n * keyStore: store,\n * });\n * ```\n *\n * @remarks\n * `savePending` writes to a `.tmp` sibling, fsyncs it, atomically renames it\n * over `path`, then fsyncs the directory so the rename itself survives power\n * loss (directory fsync is best-effort — silently skipped on platforms that\n * disallow it). `promote` is idempotent (a no-op when nothing is pending).\n * Node-only: `node:fs/promises` is loaded via dynamic import so browser bundles\n * that tree-shake the barrel never pull in `node:` specifiers.\n */\nexport function fileKeyStore(path: string): DeviceKeyStore {\n\tif (!path) throw new Error('fileKeyStore: `path` is required');\n\n\t// Loaded once, lazily — keeps `node:` specifiers out of browser bundles.\n\tconst fsp = () => import('node:fs/promises');\n\tconst dirOf = (p: string): string => {\n\t\tconst i = Math.max(p.lastIndexOf('/'), p.lastIndexOf('\\\\'));\n\t\treturn i > 0 ? p.slice(0, i) : '.';\n\t};\n\n\tasync function read(): Promise<FileKeyStoreState> {\n\t\tconst fs = await fsp();\n\t\tlet raw: string;\n\t\ttry {\n\t\t\traw = await fs.readFile(path, 'utf8');\n\t\t} catch (err) {\n\t\t\tif ((err as NodeJS.ErrnoException).code === 'ENOENT') return {};\n\t\t\tthrow err;\n\t\t}\n\t\treturn parseState(raw, path);\n\t}\n\n\tasync function write(state: FileKeyStoreState): Promise<void> {\n\t\tconst fs = await fsp();\n\t\tconst tmp = `${path}.tmp`;\n\t\tconst handle = await fs.open(tmp, 'w', 0o600);\n\t\ttry {\n\t\t\tawait handle.writeFile(JSON.stringify(state), 'utf8');\n\t\t\t// Durable BEFORE the rename makes it visible.\n\t\t\tawait handle.sync();\n\t\t} finally {\n\t\t\tawait handle.close();\n\t\t}\n\t\tawait fs.rename(tmp, path);\n\t\t// fsync the directory so the rename itself survives power loss. Some\n\t\t// platforms disallow directory fsync — degrade silently (rename is\n\t\t// already atomic; this only narrows the power-loss window further).\n\t\ttry {\n\t\t\tconst dir = await fs.open(dirOf(path), 'r');\n\t\t\ttry {\n\t\t\t\tawait dir.sync();\n\t\t\t} finally {\n\t\t\t\tawait dir.close();\n\t\t\t}\n\t\t} catch {\n\t\t\t/* best-effort */\n\t\t}\n\t}\n\n\treturn {\n\t\tload: read,\n\t\tasync savePending(key: string): Promise<void> {\n\t\t\tif (!key) throw new Error('fileKeyStore.savePending: empty key');\n\t\t\tconst state = await read();\n\t\t\tawait write({ ...state, pending: key });\n\t\t},\n\t\tasync promote(): Promise<void> {\n\t\t\tconst state = await read();\n\t\t\tif (state.pending === undefined) return; // idempotent\n\t\t\tawait write({ current: state.pending });\n\t\t},\n\t};\n}\n","// triggers module — the modality-agnostic Trigger contract (PRD #1 §5.2, ERA-4396).\n// FROZEN for PRD #2–#4 adapters. Two layers:\n// - Runtime contract: the `TriggerEvent` envelope + `Utterance` + per-kind\n// payloads — what the input port publishes. The agent never cares which\n// modality fired it.\n// - Author-time builders: `triggers.*` → a `TriggerDef` bound into a chain\n// via `chain(...).trigger(...)` / `defineChain({ trigger })`.\n//\n// The envelope enumerates all kinds now (frozen). Only `button` + `voice` have\n// author-time builders + typed payloads for the beta; the ambient watchers\n// (schedule / calendar / weather / webhook / geofence) are PRD #2.\n//\n// Tree-shakeable subpath import: `@era-laboratories/era-sdk/modules/triggers`.\n\nimport * as z from 'zod';\nimport type { ContextSnapshot } from './context.js';\nimport { contextSnapshotSchema } from './context.js';\n\n// ─── Runtime contract ───────────────────────────────────────────────────────────\n\n/**\n * The kind of thing that fired a trigger — the discriminant of both the runtime\n * {@link TriggerEvent} envelope and the author-time {@link TriggerDef}.\n *\n * @remarks\n * All kinds are enumerated and frozen, but only `button` and `voice` are wired\n * end-to-end today: they have author-time builders ({@link triggers}) and typed\n * payloads ({@link ButtonPayload} / {@link VoicePayload}). The ambient kinds are\n * reserved — their events carry an opaque JSON payload until their adapters ship.\n *\n * - `button` — a hardware or UI button press.\n * - `voice` — a spoken utterance.\n * - `schedule` — a time/cron trigger (reserved).\n * - `calendar` — a calendar-event trigger (reserved).\n * - `weather` — a weather-condition trigger (reserved).\n * - `webhook` — an inbound HTTP webhook (reserved).\n * - `geofence` — a region enter/exit trigger (reserved).\n */\nexport type TriggerKind =\n\t| 'button'\n\t| 'voice'\n\t| 'schedule'\n\t| 'calendar'\n\t| 'weather'\n\t| 'webhook'\n\t| 'geofence';\n\n/**\n * Which surface published a trigger event.\n *\n * @remarks\n * - `maker` — the maker/authoring surface (console, simulator, tooling).\n * - `ios` — the Era iOS app.\n * - `server` — a server-side or headless caller.\n */\nexport type TriggerSurface = 'maker' | 'ios' | 'server';\n\n/**\n * Which version of an Experience a trigger targets.\n *\n * @remarks\n * - `published` — the live, published version end users see.\n * - `preview` — the in-progress draft version, for testing before publish.\n *\n * Optional on {@link TriggerEvent}.\n */\nexport type VersionChannel = 'published' | 'preview';\n\n/**\n * Identifies the surface that fired a {@link TriggerEvent}: which kind of\n * surface, the device (if any), and the live session it occupies.\n */\nexport interface TriggerSource {\n\t/** Which kind of surface published the event. */\n\tsurface: TriggerSurface;\n\t/** The physical device that fired the trigger, when one is involved. */\n\tdeviceId?: string;\n\t/**\n\t * The live session/room the firing surface occupies. The trigger port routes\n\t * the event into this session, so an in-session trigger supplies it in\n\t * practice.\n\t */\n\tsessionId?: string;\n}\n\n/**\n * Speech payload. `text` is ALWAYS present to the agent — if an adapter\n * supplies only `audioRef`, ingress fills `text` via the existing one-shot STT\n * (no new STT is built).\n */\nexport interface Utterance {\n\t/**\n\t * Transcribed speech, always present to the agent. If an adapter supplies\n\t * only `audioRef`, ingress fills this via one-shot STT before the agent runs.\n\t */\n\ttext: string;\n\t/** Reference to the captured audio clip, when an adapter supplies one. */\n\taudioRef?: string;\n\t/** STT confidence for the transcription, in the range 0–1. */\n\tconfidence?: number;\n\t/** BCP-47 language tag of the utterance (e.g. `'en-US'`). */\n\tlang?: string;\n}\n\n/**\n * The button gesture that fired a trigger.\n *\n * @remarks\n * - `press` — a single short press.\n * - `press-and-hold` — a sustained press (e.g. hold-to-talk).\n * - `double` — two quick presses.\n *\n * The gesture is part of chain resolution: under one Experience, `press` and\n * `double` can route to two different chains, and a chain with no gesture\n * catches whatever no gesture-specific chain claims.\n */\nexport type ButtonGesture = 'press' | 'press-and-hold' | 'double';\n\n/** Payload of a `button` {@link TriggerEvent}: the gesture, plus any speech captured alongside the press. */\nexport interface ButtonPayload {\n\t/** The gesture that fired the trigger. */\n\tgesture: ButtonGesture;\n\t/** Speech captured with the press (e.g. press-and-hold to talk), when the surface provides it. */\n\tspeech?: Utterance;\n}\n\n/** Payload of a `voice` {@link TriggerEvent}: the spoken utterance that fired the trigger. */\nexport interface VoicePayload {\n\t/** The spoken utterance, transcribed to text for the agent. */\n\tutterance: Utterance;\n}\n\n/**\n * The modality-agnostic trigger envelope a firing surface publishes. One shape\n * for every kind, with a discriminated, kind-specific `payload` — so the agent\n * that receives it never has to care which modality fired it.\n *\n * @remarks\n * Validate an untrusted value into this shape with {@link parseTriggerEvent},\n * which checks `payload` against the schema for its `kind`. `button` / `voice`\n * payloads are strictly validated ({@link ButtonPayload} / {@link VoicePayload});\n * the ambient kinds carry an opaque JSON payload for now.\n */\nexport interface TriggerEvent {\n\t/** The Experience this trigger fires; the firing surface knows its own id. */\n\texperienceId: string;\n\t/** Which version channel of the Experience the event targets — `'published'` or `'preview'`. */\n\tversionChannel?: VersionChannel;\n\t/** Identifier of the trigger definition that produced this event. */\n\ttriggerId: string;\n\t/** Discriminant selecting the `payload` shape. */\n\tkind: TriggerKind;\n\t/** When the trigger fired, as an ISO-8601 timestamp with offset. */\n\toccurredAt: string;\n\t/** Stable key used to dedupe under at-least-once delivery — retries reuse the same key. */\n\tidempotencyKey: string;\n\t/** Which surface, device, and session fired the event. */\n\tsource: TriggerSource;\n\t/** Context captured by the surface at fire time; passed through to the run as its trigger context. */\n\tcontext?: ContextSnapshot;\n\t/** Kind-specific data — {@link ButtonPayload} for `button`, {@link VoicePayload} for `voice`, opaque JSON otherwise. */\n\tpayload: unknown;\n}\n\n// ─── Author-time trigger definitions (the `trigger:` field) ───────────────────────\n\n/**\n * A button {@link TriggerDef} — produced by {@link triggers.button} and bound\n * into a chain via `chain(...).trigger(...)`.\n */\nexport interface ButtonTrigger {\n\t/** Discriminant identifying this as a button trigger. */\n\tkind: 'button';\n\t/**\n\t * Named input slot — a declarative name for the input this chain expects.\n\t * It documents intent and is not matched by routing.\n\t *\n\t * @remarks\n\t * To route a specific device input to a chain explicitly, bind it with\n\t * `devices(era).bindInput(deviceId, { inputSlot, gesture })`; the binding is\n\t * keyed on the id the device reports for the input, not on this `slot`.\n\t * Reusing that id here is a helpful naming convention, nothing more. When a\n\t * device input has no explicit binding, the platform falls back to implicit\n\t * resolution by `kind` + the device's selected Experience + `gesture`.\n\t */\n\tslot: string;\n\t/** Which press fires the trigger; omit to catch any gesture no gesture-specific chain claims. */\n\tgesture?: ButtonGesture;\n}\n\n/**\n * A voice {@link TriggerDef} — produced by {@link triggers.voice} and bound into\n * a chain via `chain(...).trigger(...)`.\n */\nexport interface VoiceTrigger {\n\t/** Discriminant identifying this as a voice trigger. */\n\tkind: 'voice';\n\t/**\n\t * Optional named input slot — declarative; it documents the input this chain\n\t * expects and routing never reads it.\n\t *\n\t * @remarks\n\t * Explicit routing uses `devices(era).bindInput`, which is keyed on the\n\t * physical `(device, input, gesture)` and is kind-agnostic — so a voice chain\n\t * bound to a button input runs when that input fires (e.g. press-to-record).\n\t * A spoken utterance that arrives without a bound input is matched against\n\t * the Experience's configured voice commands (spoken phrase → chain) first;\n\t * when no command matches, the platform falls back to implicit resolution by\n\t * `kind` + the device's selected Experience.\n\t */\n\tslot?: string;\n}\n/**\n * The union both {@link triggers} builders produce: what a chain's\n * `.trigger(...)` accepts (assignable to the chain-side `ChainTrigger`).\n *\n * @remarks\n * `button` and `voice` are the buildable kinds. The ambient kinds\n * (schedule / calendar / weather / webhook / geofence) exist in the runtime\n * {@link TriggerEvent} envelope but have no author-time builder yet.\n */\nexport type TriggerDef = ButtonTrigger | VoiceTrigger;\n\n/**\n * Author-time trigger builders. Call one and pass the result to a chain's\n * `.trigger(...)` (or `defineChain({ trigger })`) to declare what fires it.\n *\n * @remarks\n * Pure — these construct a plain {@link TriggerDef} object and make no network\n * call. Reached as the top-level `triggers` export of the SDK.\n *\n * @example\n * import { chain, triggers } from '@era-laboratories/era-sdk';\n *\n * const doorbell = chain('doorbell-greeter')\n * .trigger(triggers.button({ slot: 'front-door', gesture: 'press' }))\n * .agentTurn('greet', { prompt: 'Greet the visitor and take a message.' })\n * .build();\n */\nexport const triggers = {\n\t/**\n\t * Build a button {@link TriggerDef}.\n\t *\n\t * @param opts.slot - Declarative name for the input this chain expects; documentation of intent, not matched by routing (see {@link ButtonTrigger.slot}).\n\t * @param opts.gesture - Which press fires it; omit to catch any gesture no gesture-specific chain claims.\n\t * @returns A {@link ButtonTrigger}.\n\t *\n\t * @example\n\t * triggers.button({ slot: 'front-door', gesture: 'press' });\n\t * // { kind: 'button', slot: 'front-door', gesture: 'press' }\n\t */\n\tbutton: (opts: { slot: string; gesture?: ButtonGesture }): ButtonTrigger => ({\n\t\tkind: 'button',\n\t\tslot: opts.slot,\n\t\t...(opts.gesture ? { gesture: opts.gesture } : {}),\n\t}),\n\t/**\n\t * Build a voice {@link TriggerDef}.\n\t *\n\t * @param opts.slot - Optional declarative input-slot name; routing never reads it (see {@link VoiceTrigger.slot}).\n\t * @returns A {@link VoiceTrigger}.\n\t *\n\t * @example\n\t * triggers.voice(); // { kind: 'voice' }\n\t * triggers.voice({ slot: 'kitchen-puck' }); // { kind: 'voice', slot: 'kitchen-puck' }\n\t */\n\tvoice: (opts?: { slot?: string }): VoiceTrigger => ({\n\t\tkind: 'voice',\n\t\t...(opts?.slot ? { slot: opts.slot } : {}),\n\t}),\n};\n\n// ─── Validation (zod) ─────────────────────────────────────────────────────────────\n\n/**\n * Zod schema for {@link Utterance} — validate a speech payload at your own\n * boundary. Exported so callers can reuse the same contract the runtime uses.\n */\nexport const utteranceSchema = z\n\t.object({\n\t\ttext: z.string().meta({\n\t\t\tdescription:\n\t\t\t\t'Transcribed speech text — always present to the agent (one-shot STT fills it from audioRef).',\n\t\t}),\n\t\taudioRef: z.string().optional().meta({\n\t\t\tdescription:\n\t\t\t\t'Reference to the captured audio clip, when an adapter supplies one.',\n\t\t}),\n\t\tconfidence: z.number().optional().meta({\n\t\t\tdescription: 'STT confidence score for the transcription (0–1).',\n\t\t}),\n\t\tlang: z\n\t\t\t.string()\n\t\t\t.optional()\n\t\t\t.meta({ description: 'BCP-47 language tag of the utterance.' }),\n\t})\n\t.meta({\n\t\tid: 'Utterance',\n\t\tdescription:\n\t\t\t'Speech payload; `text` is always present to the agent (one-shot STT fills it from `audioRef`).',\n\t});\n\nconst buttonPayloadSchema = z.object({\n\tgesture: z.enum(['press', 'press-and-hold', 'double']),\n\tspeech: utteranceSchema.optional(),\n});\nconst voicePayloadSchema = z.object({ utterance: utteranceSchema });\n\nconst triggerEventBaseSchema = z.object({\n\texperienceId: z.string().min(1),\n\tversionChannel: z.enum(['published', 'preview']).optional(),\n\ttriggerId: z.string().min(1),\n\toccurredAt: z.iso.datetime({ offset: true }),\n\tidempotencyKey: z.string().min(1),\n\tsource: z.object({\n\t\tsurface: z.enum(['maker', 'ios', 'server']),\n\t\tdeviceId: z.string().optional(),\n\t\tsessionId: z.string().optional(),\n\t}),\n\tcontext: contextSnapshotSchema.optional(),\n});\n\n/**\n * Zod schema for the runtime {@link TriggerEvent} envelope. Validates the\n * envelope and discriminates `payload` by `kind`: `button` / `voice` payloads\n * are strictly typed, the ambient kinds accept any JSON `payload`. Prefer\n * {@link parseTriggerEvent} for the typed result; use this schema directly when\n * you need to compose or extend it.\n */\nexport const triggerEventSchema = z\n\t.discriminatedUnion('kind', [\n\t\tz.object({\n\t\t\t...triggerEventBaseSchema.shape,\n\t\t\tkind: z.literal('button').meta({\n\t\t\t\tdescription: 'Trigger kind discriminant — a hardware/UI button press.',\n\t\t\t}),\n\t\t\tpayload: buttonPayloadSchema,\n\t\t}),\n\t\tz.object({\n\t\t\t...triggerEventBaseSchema.shape,\n\t\t\tkind: z.literal('voice').meta({\n\t\t\t\tdescription: 'Trigger kind discriminant — a voice utterance.',\n\t\t\t}),\n\t\t\tpayload: voicePayloadSchema,\n\t\t}),\n\t\tz.object({\n\t\t\t...triggerEventBaseSchema.shape,\n\t\t\tkind: z.literal('schedule').meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Trigger kind discriminant — a scheduled (time/cron) trigger.',\n\t\t\t}),\n\t\t\tpayload: z.json().meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Ambient schedule payload — opaque JSON until its adapter lands (PRD #2).',\n\t\t\t}),\n\t\t}),\n\t\tz.object({\n\t\t\t...triggerEventBaseSchema.shape,\n\t\t\tkind: z.literal('calendar').meta({\n\t\t\t\tdescription: 'Trigger kind discriminant — a calendar-event trigger.',\n\t\t\t}),\n\t\t\tpayload: z.json().meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Ambient calendar payload — opaque JSON until its adapter lands (PRD #2).',\n\t\t\t}),\n\t\t}),\n\t\tz.object({\n\t\t\t...triggerEventBaseSchema.shape,\n\t\t\tkind: z.literal('weather').meta({\n\t\t\t\tdescription: 'Trigger kind discriminant — a weather-condition trigger.',\n\t\t\t}),\n\t\t\tpayload: z.json().meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Ambient weather payload — opaque JSON until its adapter lands (PRD #2).',\n\t\t\t}),\n\t\t}),\n\t\tz.object({\n\t\t\t...triggerEventBaseSchema.shape,\n\t\t\tkind: z.literal('webhook').meta({\n\t\t\t\tdescription: 'Trigger kind discriminant — an inbound webhook trigger.',\n\t\t\t}),\n\t\t\tpayload: z.json().meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Ambient webhook payload — opaque JSON until its adapter lands (PRD #2).',\n\t\t\t}),\n\t\t}),\n\t\tz.object({\n\t\t\t...triggerEventBaseSchema.shape,\n\t\t\tkind: z.literal('geofence').meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Trigger kind discriminant — a geofence enter/exit trigger.',\n\t\t\t}),\n\t\t\tpayload: z.json().meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Ambient geofence payload — opaque JSON until its adapter lands (PRD #2).',\n\t\t\t}),\n\t\t}),\n\t])\n\t.meta({\n\t\tid: 'TriggerEvent',\n\t\ttitle: 'Trigger event',\n\t\tdescription:\n\t\t\t'Modality-agnostic trigger envelope (PRD §5.2), frozen for PRD #2–#4 adapters.',\n\t});\n\n/**\n * Parse and validate an unknown value into a runtime {@link TriggerEvent},\n * checking `payload` against the schema for its `kind`. Use it at any boundary\n * where a trigger arrives as untrusted JSON (e.g. an inbound webhook body).\n *\n * @param input - The value to validate — typically parsed JSON.\n * @returns The validated {@link TriggerEvent}. `payload` has been validated for\n * its `kind`, but statically it stays `unknown` — narrow it as in the example.\n * @throws A `ZodError` if `input` does not match the envelope for its `kind`.\n *\n * @example\n * import { parseTriggerEvent } from '@era-laboratories/era-sdk';\n *\n * const event = parseTriggerEvent(await request.json());\n * if (event.kind === 'voice') {\n * const { utterance } = event.payload as VoicePayload;\n * console.log(utterance.text);\n * }\n */\nexport function parseTriggerEvent(input: unknown): TriggerEvent {\n\treturn triggerEventSchema.parse(input) as TriggerEvent;\n}\n\n/**\n * Zod schema for the author-time {@link TriggerDef} (button + voice), discriminated\n * by `kind`. Exported for callers that validate trigger definitions at their own\n * boundary; the ambient kinds have no author-time schema yet.\n */\nexport const triggerDefSchema = z\n\t.discriminatedUnion('kind', [\n\t\tz.object({\n\t\t\tkind: z.literal('button').meta({\n\t\t\t\tdescription: 'Author-time trigger kind — button.',\n\t\t\t}),\n\t\t\tslot: z.string().min(1).meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Named input slot — the declared name of the input this chain expects; documentation of intent, never matched by routing. Explicit routing uses devices.bindInput (ERA-6476), whose inputSlot must equal the id the device reports in data.input — the platform never compares it to this slot. Unbound devices fall back to implicit resolution by kind + selected experience + gesture.',\n\t\t\t}),\n\t\t\tgesture: z.enum(['press', 'press-and-hold', 'double']).optional().meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Button gesture that fires the trigger; defaults to any press.',\n\t\t\t}),\n\t\t}),\n\t\tz.object({\n\t\t\tkind: z.literal('voice').meta({\n\t\t\t\tdescription: 'Author-time trigger kind — voice.',\n\t\t\t}),\n\t\t\tslot: z.string().optional().meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'Optional named input slot for the voice surface — declarative; routing never reads it. Explicit routing uses devices.bindInput, keyed on the physical (device, inputSlot, gesture) and kind-agnostic: a device voice clip that names its input (ERA-6511) consults the binding table first. On a binding miss, voice routing matches the utterance against the selected experience voice commands (spoken phrase → chain), then falls back to implicit resolution by kind + selected experience.',\n\t\t\t}),\n\t\t}),\n\t])\n\t.meta({\n\t\tid: 'TriggerDef',\n\t\tdescription:\n\t\t\t'Author-time trigger binding for chain(...).trigger(...). Button + voice for beta; ambient kinds are PRD #2.',\n\t});\n"],"mappings":"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"}