@tangle-network/agent-app 0.44.48 → 0.44.50
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/assistant/index.js +2 -2
- package/dist/chat-routes/index.d.ts +55 -2
- package/dist/chat-routes/index.js +79 -9
- package/dist/chat-routes/index.js.map +1 -1
- package/dist/chat-store/index.d.ts +12 -0
- package/dist/chat-store/index.js +12 -0
- package/dist/chat-store/index.js.map +1 -1
- package/dist/{chunk-2EJFIQUV.js → chunk-6VWA26BV.js} +57 -79
- package/dist/chunk-6VWA26BV.js.map +1 -0
- package/dist/{chunk-T3TYG3VW.js → chunk-D3GK2IPA.js} +2 -2
- package/dist/chunk-JEZJ6HTF.js +77 -0
- package/dist/chunk-JEZJ6HTF.js.map +1 -0
- package/dist/{chunk-3E4MW5LR.js → chunk-YOSSGDIG.js} +25 -16
- package/dist/chunk-YOSSGDIG.js.map +1 -0
- package/dist/session-shell/index.d.ts +11 -2
- package/dist/session-shell/index.js +1 -1
- package/dist/stream/index.d.ts +1 -1
- package/dist/stream/index.js +11 -5
- package/dist/{turn-buffer-BcPfhr1_.d.ts → turn-buffer-CFKQlnxf.d.ts} +24 -6
- package/dist/turn-stream/index.d.ts +3 -1
- package/dist/turn-stream/index.js +8 -1
- package/dist/turn-stream/index.js.map +1 -1
- package/dist/web-react/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-2EJFIQUV.js.map +0 -1
- package/dist/chunk-3E4MW5LR.js.map +0 -1
- /package/dist/{chunk-T3TYG3VW.js.map → chunk-D3GK2IPA.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/turn-stream/core.ts","../../src/turn-stream/do.ts","../../src/turn-stream/adapters.ts","../../src/turn-stream/memory.ts"],"sourcesContent":["/**\n * `/turn-stream` core — the pure, substrate-free half of the shared durable\n * turn replay/broadcast/lock channel (issue #221).\n *\n * Extracted from the reference consumer's hand-rolled Durable Object\n * (gtm-agent `SessionStreamDO` + `session-broadcast.ts`): the per-turn\n * segment store that backs reconnect replay over a live socket, the\n * single-flight chat-turn lock record and its release fences, and the wire\n * contract (channel keys, endpoint paths, request/response bodies) shared by\n * the DO transport shell (`./do`) and the worker-side adapters\n * (`./adapters`). Everything here is plain data + functions — no\n * `cloudflare:workers`, no storage, no sockets — so the semantics are\n * unit-testable in Node and the DO stays a thin shell.\n *\n * ── The two-lane rule (measured, not assumed) ────────────────────────────\n *\n * A 4-arm A/B on production (sandbox.tangle.tools, SDK 0.12.0, one box, one\n * gateway client per arm) established which sandbox lane a browser can see:\n *\n * | turn driver | raw turn events | seen at gateway |\n * | ------------------------------------ | --------------- | --------------- |\n * | `box.streamPrompt()` (run/stream) | 71 / 527 / 408 | 0 / 0 / 0 |\n * | `box.session(id).sendMessage()` | 297 | 297 |\n *\n * `POST /agents/run/stream` publishes nothing to the sidecar session event\n * bus, so a `SessionGatewayClient` attached to that session receives zero\n * turn events — three different session-id strategies all got 0, the id was\n * not the variable. `POST /agents/sessions/{id}/messages` publishes to the\n * bus and the gateway delivered every frame, byte-matching the sidecar tail.\n *\n * Consequences for this module, and they cut both ways:\n *\n * 1. INTERACTIVE sandbox turns should be driven on the message lane and\n * tailed by the browser through `box.mintScopedToken({ scope: 'session' })`\n * + `SessionGatewayClient`. Re-broadcasting those same events through the\n * per-turn SEGMENT buffer below duplicates the SDK and adds a worker hop.\n * That half is `@deprecated` (see the tags on {@link createSegmentStore},\n * {@link appendSegmentEvent}, {@link replayActiveSegment} and\n * `broadcastTurnStreamEvent` in `./adapters`).\n * 2. DETACHED/autonomous turns are INVISIBLE to the gateway:\n * `dispatchPrompt({ detach: true })` and `driveTurn` both go through\n * `streamPrompt` internally, i.e. the run/stream lane, which does not fan\n * out. A browser that must tail an unattended run still needs a buffer —\n * `runDetachedTurn` (`/chat-routes`) over the durable turn-event rows\n * below. That half is NOT deprecated and has no SDK replacement today.\n * 3. The LOCK and the per-workspace SIGNALS have no gateway equivalent at\n * all (the gateway is per-session and read-only). They stay canonical.\n *\n * Server-side resume of a run/stream turn is also already solved by the SDK\n * and needs nothing here: `box.streamPrompt('', { executionId, lastEventId })`\n * replays strictly after the cursor without re-dispatching — measured across\n * a SIGKILL mid-run and a fresh process resuming from the cursor alone:\n * 0 lost, 0 duplicated, 0 out-of-order, ids 1..517 contiguous.\n */\n\n// ── events ──────────────────────────────────────────────────────────────────\n\n/** One event on a turn-stream channel. `seq` is monotonic within a turn\n * segment and assigned by {@link appendSegmentEvent} on arrival at the DO. */\nexport interface TurnStreamEvent {\n type: string\n data?: unknown\n timestamp: number\n seq?: number\n}\n\n/** Terminal run markers: they close a turn segment and auto-release the\n * channel's chat-turn lock for the segment's execution.\n *\n * KEPT (not deprecated with the segment buffer): the lock auto-release\n * reads it. A product that stops broadcasting turn events to the thread\n * channel loses only that auto-release — the cooperative release on settle\n * (`createDurableTurnLock().release`) and `reconcileStaleDurableTurnLock`\n * both still fire, which is what actually frees a wedged lane. */\nexport function isTerminalRunEvent(type: string): boolean {\n return type === 'session.run.completed' || type === 'session.run.failed'\n}\n\n// ── channel keys ────────────────────────────────────────────────────────────\n//\n// One DO instance per channel key. Three families:\n// thread `${workspaceId}:${threadId}` — live turn fanout + segments +\n// the thread-scope lock.\n// workspace `${workspaceId}` — sidebar activity + thread.created\n// + the workspace-scope lock.\n// turn `turn:${turnId}` — durable turn-event rows + status\n// (the TurnEventStore contract; replay survives DO eviction).\n// scope `scope:${scopeId}` — running-turn index for a thread,\n// backing `TurnEventStore.listRunning`.\n\n/** Define the scope level for acquiring a turn lock within thread or workspace contexts */\nexport type TurnLockScope = 'thread' | 'workspace'\n\n/** Generate a unique string key combining workspace and thread identifiers.\n *\n * KEPT: thread-scope LOCKS are keyed on it (see {@link turnLockChannelKey}).\n * Only its second use — addressing a live-viewer socket for interactive\n * sandbox-turn rebroadcast — is superseded by the session gateway. */\nexport function threadChannelKey(workspaceId: string, threadId: string): string {\n return `${workspaceId}:${threadId}`\n}\n\n/** Generate a unique channel key based on the given workspace identifier.\n *\n * KEPT and canonical: the per-workspace signal channel (`thread.created`,\n * `thread.activity`) plus workspace-scope locks. The session gateway is\n * per-SESSION and read-only, so it cannot carry either. */\nexport function workspaceChannelKey(workspaceId: string): string {\n return workspaceId\n}\n\n/** The channel a lock lives on: workspace-scope locks serialize every thread\n * in the workspace (one shared sandbox), thread-scope locks serialize one\n * thread (router lane). Same keying as the reference consumer, so a product\n * swapping its fork for this package contends on identical instances. */\nexport function turnLockChannelKey(workspaceId: string, threadId: string, scope: TurnLockScope): string {\n return scope === 'workspace' ? workspaceChannelKey(workspaceId) : threadChannelKey(workspaceId, threadId)\n}\n\n/** Generate a storage channel key string for a given turn identifier.\n *\n * KEPT and canonical: the DETACHED lane's durable turn-event rows live on\n * this instance. A detached run never reaches the session gateway, so this\n * is the only way a browser tails one. */\nexport function turnStorageChannelKey(turnId: string): string {\n return `turn:${turnId}`\n}\n\n/** Generate a unique channel key string based on the provided scope identifier.\n *\n * KEPT and canonical: backs `TurnEventStore.listRunning`, which is how a\n * reloaded client rediscovers an in-flight DETACHED turn. */\nexport function scopeIndexChannelKey(scopeId: string): string {\n return `scope:${scopeId}`\n}\n\n// ── segment store (reconnect replay over a live socket) ─────────────────────\n\n/** DEPRECATED (interactive turn-rebroadcast buffer) — represent a segment of a turn containing events, sequence limit, and terminal status.\n *\n * @deprecated Part of the interactive turn-rebroadcast buffer — see the\n * two-lane rule in this file's header. Removal is a major-version change. */\nexport interface TurnSegment {\n events: TurnStreamEvent[]\n maxSeq: number\n terminal: boolean\n}\n\n/** DEPRECATED (interactive turn-rebroadcast buffer) — define a store managing segments and tracking the active execution identifier.\n *\n * @deprecated Part of the interactive turn-rebroadcast buffer — see the\n * two-lane rule in this file's header. Removal is a major-version change. */\nexport interface SegmentStore {\n segments: Map<string, TurnSegment>\n activeExecutionId: string | null\n}\n\n/** DEPRECATED (interactive turn-rebroadcast buffer) — per-turn replay window. Generous enough for normal turns; a turn that\n * exceeds it loses its earliest deltas from replay (a late resumer\n * self-heals via the final `result` event + loader revalidation).\n *\n * @deprecated Sizes the interactive turn-rebroadcast buffer only. The\n * DETACHED lane's durable rows (`turnEvent:` storage) are uncapped and are\n * not affected. */\nexport const MAX_SEGMENT_EVENTS = 2000\n\n/** Recent `thread.created` markers kept for late-connecting sidebars.\n *\n * KEPT: a workspace-level signal, not a turn rebroadcast. */\nexport const MAX_RECENT_CREATED = 50\n\n/** A responding marker older than this is treated as stale, so a dropped\n * `end` broadcast can't leave a permanently-stuck \"responding\" dot. */\nexport const ACTIVITY_TTL_MS = 15 * 60 * 1000\n\n/** DEPRECATED (interactive sandbox-turn rebroadcast; the SDK's session gateway replaces it) — create a SegmentStore with initialized segments and no active execution ID.\n *\n * @deprecated Backs the interactive sandbox-turn rebroadcast, which the\n * sandbox SDK already does better (measured: run/stream → 0 frames at the\n * gateway, message lane → 297/297; see the two-lane rule in this file's\n * header). Sandbox turns: drive on `box.session(id).sendMessage()` and let\n * the browser attach with `box.mintScopedToken({ scope: 'session' })` +\n * `SessionGatewayClient`. Sandbox-FREE turns: `/stream`'s\n * `replayTurnEvents` (`GET /chat/stream/:turnId`) already follows a running\n * turn from a cursor. Detached turns keep the durable turn-event rows —\n * they are a different, non-deprecated lane. Removal is a major-version\n * change; nothing is deleted here. */\nexport function createSegmentStore(): SegmentStore {\n return { segments: new Map(), activeExecutionId: null }\n}\n\n/**\n * DEPRECATED (interactive turn-rebroadcast buffer) — append a per-turn event to\n * its execution's segment, assigning a monotonic\n * `seq`. A `session.run.started` (or the first-seen event for an execution)\n * opens a fresh segment, makes it active, and drops prior turns' buffers so a\n * resumer only ever replays the current turn. A terminal run event marks the\n * segment terminal. Returns the seq-stamped event to broadcast.\n *\n * @deprecated The interactive rebroadcast half — {@link createSegmentStore}\n * names the replacement per lane; this file's header holds the measurement.\n */\nexport function appendSegmentEvent(\n store: SegmentStore,\n executionId: string,\n incoming: TurnStreamEvent,\n maxEvents = MAX_SEGMENT_EVENTS,\n): TurnStreamEvent {\n let segment = store.segments.get(executionId)\n if (incoming.type === 'session.run.started' || !segment) {\n if (!segment) {\n segment = { events: [], maxSeq: 0, terminal: false }\n store.segments.set(executionId, segment)\n }\n store.activeExecutionId = executionId\n for (const id of store.segments.keys()) {\n if (id !== executionId) store.segments.delete(id)\n }\n }\n const seq = ++segment.maxSeq\n const stamped: TurnStreamEvent = { ...incoming, seq }\n segment.events.push(stamped)\n if (segment.events.length > maxEvents) {\n segment.events = segment.events.slice(-maxEvents)\n }\n if (isTerminalRunEvent(incoming.type)) {\n segment.terminal = true\n }\n return stamped\n}\n\n/**\n * DEPRECATED (interactive turn-rebroadcast buffer; the SDK replays losslessly on\n * both lanes) — events of the active, non-terminal turn with `seq > afterSeq`,\n * i.e. what a\n * (re)connecting client replays before going live. A terminal (finished) turn\n * replays nothing: the client falls back to the loader's persisted row.\n *\n * @deprecated The interactive rebroadcast half — {@link createSegmentStore}\n * names the replacement per lane. The SDK's own reconnect replay is\n * `SessionGatewayClient` + `lastEventId` (browser) or\n * `box.streamPrompt('', { executionId, lastEventId })` (worker); both were\n * measured lossless.\n */\nexport function replayActiveSegment(store: SegmentStore, afterSeq: number): TurnStreamEvent[] {\n const segment = store.activeExecutionId ? store.segments.get(store.activeExecutionId) : undefined\n if (!segment || segment.terminal) return []\n return segment.events.filter((event) => (event.seq ?? 0) > afterSeq)\n}\n\n/**\n * Remove responding entries (threadId → startedAt) older than `ttlMs`, so a\n * dropped `end` broadcast can't leave a permanently-stuck dot. Mutates\n * `active` and returns the removed thread ids.\n */\nexport function pruneStaleThreads(active: Map<string, number>, now: number, ttlMs: number): string[] {\n const removed: string[] = []\n for (const [threadId, startedAt] of active) {\n if (now - startedAt > ttlMs) {\n active.delete(threadId)\n removed.push(threadId)\n }\n }\n return removed\n}\n\n// ── chat-turn lock record + fences ──────────────────────────────────────────\n\n/** Default lifetime of an unreleased lock. Long enough that a legitimately\n * slow sandbox turn never loses its guard mid-run; the way OUT of a wedge is\n * never the TTL but `reconcileStaleTurnLock` (in `/chat-routes`), which\n * probes the execution's actual state.\n *\n * Everything from here down is the LOCK, and it is fully KEPT. The sandbox\n * SDK ships no single-flight primitive — the session gateway is a read-only\n * fanout — so moving a product to the message lane changes nothing about\n * who is allowed to start a turn. */\nexport const TURN_LOCK_TTL_MS = 30 * 60 * 1000\n\n/** The stored single-flight lock. Field-compatible with the reference\n * consumer's `ChatTurnLock` so adoption is a swap, not a migration. */\nexport interface DurableTurnLock {\n workspaceId: string\n threadId: string\n scope: TurnLockScope\n executionId: string\n lockId: string\n startedAt: number\n expiresAt: number\n turnId?: string\n /** The turn released this lock, but a product-owned post-turn task (e.g.\n * file persistence reading the box) is still running, so the release is\n * parked on the lock until the task settles. Written only through the DO's\n * defer seam — the base package never sets it on its own. */\n releasePending?: boolean\n}\n\n/** Define input parameters required to acquire a turn-based lock in a workspace thread */\nexport interface TurnLockAcquireInput {\n workspaceId: string\n threadId: string\n scope: TurnLockScope\n executionId: string\n lockId: string\n turnId?: string\n}\n\n/** Resolve the result of attempting to acquire a turn lock indicating success or active lock status */\nexport type TurnLockAcquireResult =\n | { acquired: true; lock: DurableTurnLock }\n | { acquired: false; active: DurableTurnLock }\n\n/** Define input parameters required to release a turn lock in a specific workspace thread */\nexport interface TurnLockReleaseInput {\n workspaceId: string\n threadId: string\n scope: TurnLockScope\n executionId: string\n lockId: string\n}\n\n/** Fenced out-of-band release (stop button, stale-lock reconciliation). The\n * fences make it refuse a SUCCESSOR lock: `interruptedAt` must not precede\n * the lock's own start, and when either side names a turn, both must name\n * the same one. */\nexport interface TurnLockInterruptedReleaseInput {\n workspaceId: string\n threadId: string\n /** Try only this scope; omit to try workspace then thread. */\n scope?: TurnLockScope\n interruptedAt: number\n turnId?: string\n}\n\n/** `stored` is what the DO read from storage; expired locks are dead. */\nexport function activeTurnLock(stored: DurableTurnLock | undefined, now: number): DurableTurnLock | null {\n if (!stored) return null\n // Locks written before the scope field existed default to thread scope.\n const lock = stored.scope ? stored : { ...stored, scope: 'thread' as const }\n return lock.expiresAt > now ? lock : null\n}\n\n/** Create a durable turn lock object with timing and scope based on input parameters */\nexport function createTurnLock(input: TurnLockAcquireInput, now: number, ttlMs = TURN_LOCK_TTL_MS): DurableTurnLock {\n return {\n workspaceId: input.workspaceId,\n threadId: input.threadId,\n scope: input.scope,\n executionId: input.executionId,\n lockId: input.lockId,\n startedAt: now,\n expiresAt: now + ttlMs,\n ...(input.turnId ? { turnId: input.turnId } : {}),\n }\n}\n\n/** A cooperative release must present the lock's own identity — both the\n * execution and the lockId minted at acquire — so a retry of a PREVIOUS turn\n * can never release the current one. `lockId` is optional only for the DO's\n * internal terminal-event auto-release, which knows the execution but not\n * the caller-held lockId. */\nexport function turnLockMatchesRelease(\n active: DurableTurnLock,\n input: { executionId: string; lockId?: string },\n): boolean {\n if (active.executionId !== input.executionId) return false\n if (input.lockId && active.lockId !== input.lockId) return false\n return true\n}\n\n/**\n * The interrupted/stale release fence. `interruptedAt` is the instant the\n * releasing evidence was observed (the stop click, the stale-lock probe) —\n * a lock STARTED after that instant is a successor the evidence says nothing\n * about, so it survives. When the lock recorded a client turnId, the release\n * must name the same turn; a lock without one refuses a turn-specific\n * release (it cannot prove it is that turn).\n */\nexport function interruptedReleaseApplies(\n active: DurableTurnLock,\n input: { threadId: string; interruptedAt: number; turnId?: string },\n): boolean {\n if (active.threadId !== input.threadId) return false\n if (active.startedAt > input.interruptedAt) return false\n if (active.turnId) {\n if (active.turnId !== input.turnId) return false\n } else if (input.turnId) {\n return false\n }\n return true\n}\n\n// ── wire contract (adapter ↔ DO endpoint paths) ─────────────────────────────\n//\n// Kept byte-identical to the reference consumer's DO where an endpoint\n// existed there, so a product deletes its fork by re-pointing a binding, not\n// by re-speaking a protocol.\n\n/** Provide constant paths for managing chat turn streams and locks */\nexport const TURN_STREAM_PATHS = {\n broadcast: '/broadcast',\n lockAcquire: '/chat-turn-lock/acquire',\n lockRelease: '/chat-turn-lock/release',\n lockReleaseInterrupted: '/chat-turn-lock/release-interrupted',\n turnEventsAppend: '/turn-events/append',\n turnEventsRead: '/turn-events/read',\n turnStatusSet: '/turn-status/set',\n turnStatusGet: '/turn-status/get',\n scopeStatusSet: '/turn-scope/set',\n scopeRunningList: '/turn-scope/running',\n} as const\n\n/** Storage keys inside a DO instance. Exported for subclass coexistence —\n * a product extending the DO must not collide with these. */\nexport const TURN_STREAM_STORAGE_KEYS = {\n lock: 'chatTurnLock',\n activeThreads: 'activeThreads',\n turnStatus: 'turnStatus',\n turnScope: 'turnScopeIndex',\n turnEventPrefix: 'turnEvent:',\n} as const\n\n/** Zero-padded seq so DO storage `list({ prefix })` returns rows in replay\n * order without a sort. 10 digits holds any realistic turn. */\nexport function turnEventStorageKey(seq: number): string {\n return `${TURN_STREAM_STORAGE_KEYS.turnEventPrefix}${String(seq).padStart(10, '0')}`\n}\n","/**\n * `TurnStreamDO` — the shared Durable Object transport shell over the pure\n * core (`./core`). One class serves every channel family; the instance NAME\n * decides which endpoints a given instance ever sees:\n *\n * - **thread channel** (`${workspaceId}:${threadId}`) — the thread-scope lock\n * (KEPT), plus the live turn rebroadcast: WebSocket fanout and per-turn\n * segments with `sync`/`afterSeq` reconnect replay. That rebroadcast is\n * `@deprecated` for sandbox-backed interactive turns — the sandbox session\n * gateway already does it, browser-direct, when the turn is driven on the\n * message lane (`./core`'s header has the production measurement).\n * - **workspace channel** (`${workspaceId}`) — coarse sidebar signals\n * (`thread.activity` responding set, durable across eviction;\n * `thread.created` recent list) and the workspace-scope lock. KEPT: the\n * gateway is per-session and read-only, so it carries neither.\n * - **turn storage** (`turn:${turnId}`) — the durable `TurnEventStore` rows +\n * status for one buffered turn (replay survives DO eviction — this is what\n * graduates the vertical's `turnStore` from no-op). KEPT and load-bearing:\n * a DETACHED run never reaches the gateway, so this is the only way a\n * browser tails autonomous work.\n * - **scope index** (`scope:${scopeId}`) — the running-turn index backing\n * `TurnEventStore.listRunning` reconnect discovery. KEPT.\n *\n * The class is a PLAIN class over a structural {@link TurnStreamDOState} —\n * no `cloudflare:workers` import, so this package stays substrate-free and\n * the DO is unit-testable in Node. Cloudflare's `DurableObjectState`\n * satisfies the interface; a product binds it in wrangler by re-exporting:\n *\n * // worker entry\n * export { TurnStreamDO } from '@tangle-network/agent-app/turn-stream'\n *\n * Fan-out enumerates `state.getWebSockets()` (never an in-memory socket map)\n * and reads per-socket metadata from the serialized attachment, so it is\n * correct across WebSocket hibernation.\n *\n * Product extension (how the reference consumer keeps its Vault machinery\n * while deleting its fork): subclass and override\n * {@link TurnStreamDO.handleProductRequest} (extra POST endpoints),\n * {@link TurnStreamDO.shouldDeferLockRelease} /\n * {@link TurnStreamDO.completeDeferredLockRelease} (park a lock release\n * behind a product-owned post-turn task), and\n * {@link TurnStreamDO.productSyncEvents} (extra state replayed to a\n * late-connecting socket). Product storage keys must avoid\n * {@link TURN_STREAM_STORAGE_KEYS}.\n */\n\nimport {\n ACTIVITY_TTL_MS,\n MAX_RECENT_CREATED,\n MAX_SEGMENT_EVENTS,\n TURN_LOCK_TTL_MS,\n TURN_STREAM_PATHS,\n TURN_STREAM_STORAGE_KEYS,\n activeTurnLock,\n appendSegmentEvent,\n createSegmentStore,\n createTurnLock,\n interruptedReleaseApplies,\n isTerminalRunEvent,\n pruneStaleThreads,\n replayActiveSegment,\n turnEventStorageKey,\n turnLockMatchesRelease,\n type DurableTurnLock,\n type TurnLockAcquireResult,\n type TurnLockScope,\n type TurnStreamEvent,\n} from './core'\n\n// ── structural Cloudflare surface ───────────────────────────────────────────\n\n/** The socket surface the DO touches. Cloudflare's hibernatable `WebSocket`\n * satisfies it. */\nexport interface TurnStreamSocket {\n send(data: string): void\n close(code?: number, reason?: string): void\n serializeAttachment(value: unknown): void\n deserializeAttachment(): unknown\n}\n\n/** The storage surface the DO touches (Cloudflare `DurableObjectStorage`\n * satisfies it structurally). `list` must return keys in ascending order —\n * the turn-event rows rely on it for replay order. */\nexport interface TurnStreamStorage {\n get<T = unknown>(key: string): Promise<T | undefined>\n put<T = unknown>(key: string, value: T): Promise<void>\n delete(key: string): Promise<boolean | void>\n list<T = unknown>(options: { prefix: string; start?: string }): Promise<Map<string, T>>\n}\n\n/** The `DurableObjectState` surface the DO uses. */\nexport interface TurnStreamDOState {\n storage: TurnStreamStorage\n acceptWebSocket(ws: TurnStreamSocket): void\n getWebSockets(): TurnStreamSocket[]\n}\n\ninterface SocketMeta {\n sessionId: string\n scope: 'thread' | 'workspace'\n /** Live fan-out is withheld until the client has run its initial `sync`\n * (see {@link TurnStreamDO.webSocketMessage}). This makes replay and live\n * delivery mutually exclusive per socket, so frames can neither interleave\n * out of order nor drop in the connect race. */\n synced: boolean\n}\n\n// ── request/response plumbing ───────────────────────────────────────────────\n\nfunction jsonResponse(body: unknown, status = 200): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { 'Content-Type': 'application/json' },\n })\n}\n\nfunction stringValue(value: unknown): string | null {\n return typeof value === 'string' && value.trim() ? value.trim() : null\n}\n\nasync function jsonBody(request: Request): Promise<Record<string, unknown> | null> {\n const body = await request.json().catch(() => null)\n return body && typeof body === 'object' && !Array.isArray(body) ? (body as Record<string, unknown>) : null\n}\n\n/** Per-scope running-turn index entry (one record per buffered turn). */\ninterface ScopeTurnEntry {\n status: 'running' | 'complete' | 'error'\n updatedAt: number\n}\n\n/** Cap on remembered turns per scope; oldest terminal entries are pruned\n * first, so a busy thread's index stays bounded without ever dropping a\n * still-running turn. */\nconst MAX_SCOPE_TURNS = 100\n\n// ── the DO ──────────────────────────────────────────────────────────────────\n\n/** Define options to override default TTL and event limits for TURN stream DO operations */\nexport interface TurnStreamDOOptions {\n /** Override {@link TURN_LOCK_TTL_MS}. */\n lockTtlMs?: number\n /** Override {@link MAX_SEGMENT_EVENTS}. */\n maxSegmentEvents?: number\n /** Override {@link ACTIVITY_TTL_MS}. */\n activityTtlMs?: number\n}\n\n/** Manage per-turn segments and track active threads with durable event storage */\nexport class TurnStreamDO {\n protected readonly state: TurnStreamDOState\n protected readonly env: unknown\n protected readonly options: TurnStreamDOOptions\n\n // Thread channel: per-turn segments; only the active one is replayed.\n private segments = createSegmentStore()\n // Workspace channel: recent thread.created markers (in-memory, best-effort)\n // + durable responding set (threadId → startedAt) that survives eviction.\n private recentCreated: TurnStreamEvent[] = []\n private activeThreads: Map<string, number> | null = null\n\n constructor(state: TurnStreamDOState, env?: unknown, options: TurnStreamDOOptions = {}) {\n this.state = state\n this.env = env\n this.options = options\n }\n\n async fetch(request: Request): Promise<Response> {\n const url = new URL(request.url)\n\n if (request.headers.get('Upgrade') === 'websocket') {\n return this.handleWebSocketUpgrade(request, url)\n }\n\n if (request.method === 'POST') {\n switch (url.pathname) {\n case TURN_STREAM_PATHS.broadcast:\n return this.handleBroadcast(request)\n case TURN_STREAM_PATHS.lockAcquire:\n return this.handleLockAcquire(request)\n case TURN_STREAM_PATHS.lockRelease:\n return this.handleLockRelease(request)\n case TURN_STREAM_PATHS.lockReleaseInterrupted:\n return this.handleLockReleaseInterrupted(request)\n case TURN_STREAM_PATHS.turnEventsAppend:\n return this.handleTurnEventsAppend(request)\n case TURN_STREAM_PATHS.turnEventsRead:\n return this.handleTurnEventsRead(request)\n case TURN_STREAM_PATHS.turnStatusSet:\n return this.handleTurnStatusSet(request)\n case TURN_STREAM_PATHS.turnStatusGet:\n return this.handleTurnStatusGet()\n case TURN_STREAM_PATHS.scopeStatusSet:\n return this.handleScopeStatusSet(request)\n case TURN_STREAM_PATHS.scopeRunningList:\n return this.handleScopeRunningList()\n }\n }\n\n const product = await this.handleProductRequest(request, url)\n if (product) return product\n return request.method === 'POST'\n ? jsonResponse({ error: `Unknown turn-stream endpoint ${url.pathname}` }, 404)\n : new Response('Method not allowed', { status: 405 })\n }\n\n // ── product extension seam ────────────────────────────────────────────────\n\n /** Called for any request no base endpoint claimed (before the 404), so a\n * subclass adds product endpoints without touching base routing. Return\n * `null` to decline. */\n protected async handleProductRequest(_request: Request, _url: URL): Promise<Response | null> {\n return null\n }\n\n /** Consulted before any lock release (cooperative, interrupted, or the\n * terminal-event auto-release). Return `true` while a product-owned\n * post-turn task for `executionId` must keep the scope serialized (e.g.\n * file persistence still reading the box) — the release is then parked as\n * `releasePending` on the lock and completed via\n * {@link completeDeferredLockRelease}. Base: never defer. */\n protected async shouldDeferLockRelease(_executionId: string): Promise<boolean> {\n return false\n }\n\n /** Complete a release parked by {@link shouldDeferLockRelease}. A subclass\n * calls this when its deferred condition settles. */\n protected async completeDeferredLockRelease(executionId: string): Promise<boolean> {\n const active = await this.loadActiveLock()\n if (!active?.releasePending || active.executionId !== executionId) return false\n await this.state.storage.delete(TURN_STREAM_STORAGE_KEYS.lock)\n return true\n }\n\n /** Extra product state replayed to a socket during its `sync`, after the\n * base replay for its scope (e.g. an in-flight persistence status card).\n * Base: none. */\n protected async productSyncEvents(_scope: 'thread' | 'workspace', _meta: { sessionId: string }): Promise<TurnStreamEvent[]> {\n return []\n }\n\n // ── WebSocket channel ─────────────────────────────────────────────────────\n\n private handleWebSocketUpgrade(_request: Request, url: URL): Response {\n const sessionId = url.searchParams.get('sessionId')\n if (!sessionId) return new Response('Missing sessionId', { status: 400 })\n const scope = url.searchParams.get('scope') === 'workspace' ? 'workspace' : 'thread'\n\n const PairCtor = (globalThis as { WebSocketPair?: new () => Record<number, unknown> }).WebSocketPair\n if (!PairCtor) {\n return new Response('WebSocket upgrades require the Cloudflare runtime', { status: 501 })\n }\n const pair = new PairCtor()\n const client = pair[0]\n const server = pair[1] as TurnStreamSocket\n\n // Accept for hibernation and record routing metadata. No replay is pushed\n // here: sending before the client finishes the 101 handshake is racy\n // (frames can drop). The client issues a `sync` once open; replay happens\n // there.\n this.state.acceptWebSocket(server)\n server.serializeAttachment({ sessionId, scope, synced: false } satisfies SocketMeta)\n\n return new Response(null, { status: 101, webSocket: client } as ResponseInit)\n }\n\n /**\n * First (and only) client message after open: `{ type: 'sync', afterSeq }`.\n * Replays the current state for the socket's scope, then marks it `synced`\n * so live broadcasts start flowing. Because the DO is single-threaded, the\n * replay snapshot and the synced flip are atomic w.r.t. broadcasts — every\n * event reaches the socket exactly once, in order, via replay XOR live\n * fan-out.\n */\n async webSocketMessage(ws: TurnStreamSocket, message: string | ArrayBuffer): Promise<void> {\n const meta = ws.deserializeAttachment() as SocketMeta | null\n if (!meta || meta.synced) return\n let afterSeq = 0\n try {\n const parsed = JSON.parse(typeof message === 'string' ? message : '') as { type?: string; afterSeq?: number }\n if (parsed.type !== 'sync') return\n afterSeq = typeof parsed.afterSeq === 'number' ? parsed.afterSeq : 0\n } catch {\n return\n }\n\n if (meta.scope === 'workspace') {\n // Current responding state as synthetic `start` markers.\n const active = await this.loadActiveThreads()\n const removed = pruneStaleThreads(active, Date.now(), this.options.activityTtlMs ?? ACTIVITY_TTL_MS)\n if (removed.length > 0) await this.persistActiveThreads(active)\n for (const [threadId, startedAt] of active) {\n this.trySend(ws, {\n type: 'thread.activity',\n data: { threadId, phase: 'start', sessionId: meta.sessionId },\n timestamp: startedAt,\n })\n }\n // Recently-created threads for late joiners.\n for (const event of this.recentCreated) this.trySend(ws, event)\n } else {\n // The active, non-terminal turn segment from the cursor.\n for (const event of replayActiveSegment(this.segments, afterSeq)) {\n this.trySend(ws, event)\n }\n }\n for (const event of await this.productSyncEvents(meta.scope, { sessionId: meta.sessionId })) {\n this.trySend(ws, event)\n }\n\n ws.serializeAttachment({ ...meta, synced: true } satisfies SocketMeta)\n }\n\n webSocketClose(ws: TurnStreamSocket, code: number, reason: string): void {\n try {\n ws.close(code, reason)\n } catch {\n // Already closed.\n }\n }\n\n webSocketError(): void {\n // Socket dropped; getWebSockets() will no longer include it.\n }\n\n private trySend(ws: TurnStreamSocket, event: TurnStreamEvent): void {\n try {\n ws.send(JSON.stringify(event))\n } catch {\n // Client may not be ready yet, or is closing.\n }\n }\n\n // ── broadcast (live fanout + segments + activity) ─────────────────────────\n\n private async handleBroadcast(request: Request): Promise<Response> {\n const incoming = (await request.json()) as TurnStreamEvent\n const data = (incoming.data ?? {}) as Record<string, unknown>\n const sessionId = typeof data.sessionId === 'string' ? data.sessionId : undefined\n let outgoing: TurnStreamEvent = incoming\n\n if (incoming.type === 'thread.activity') {\n const threadId = typeof data.threadId === 'string' ? data.threadId : undefined\n if (threadId) {\n const active = await this.loadActiveThreads()\n if (data.phase === 'end') active.delete(threadId)\n else active.set(threadId, Date.now())\n await this.persistActiveThreads(active)\n }\n } else if (incoming.type === 'thread.created') {\n this.recentCreated.push(incoming)\n if (this.recentCreated.length > MAX_RECENT_CREATED) {\n this.recentCreated = this.recentCreated.slice(-MAX_RECENT_CREATED)\n }\n } else if (typeof data.executionId === 'string') {\n // Deprecated lane: per-turn rebroadcast. A product on the sandbox\n // message lane stops sending these and the tab reads the gateway\n // instead; the lock's cooperative release and the stale-lock\n // reconciler then own release (this terminal auto-release is a\n // convenience, not the only path out of a wedge).\n outgoing = appendSegmentEvent(\n this.segments,\n data.executionId,\n incoming,\n this.options.maxSegmentEvents ?? MAX_SEGMENT_EVENTS,\n )\n // A finished turn frees the channel's single-flight lock without waiting\n // for the worker's cooperative release — unless a product task defers it.\n if (isTerminalRunEvent(incoming.type)) {\n if (await this.shouldDeferLockRelease(data.executionId)) {\n await this.deferLockRelease({ executionId: data.executionId })\n } else {\n await this.releaseActiveLock({ executionId: data.executionId })\n }\n }\n }\n\n // Fan out to live sockets for this session. Enumerate accepted sockets so\n // fan-out is correct after WebSocket hibernation (no in-memory socket map).\n // Only `synced` sockets receive live frames; an un-synced socket is still\n // mid-handshake and will pick this event up in its `sync` replay snapshot.\n const message = JSON.stringify(outgoing)\n for (const ws of this.state.getWebSockets()) {\n const meta = ws.deserializeAttachment() as SocketMeta | null\n if (!meta?.synced) continue\n if (!sessionId || meta.sessionId === sessionId) {\n try {\n ws.send(message)\n } catch {\n // Dead/closing socket — getWebSockets() will stop returning it.\n }\n }\n }\n\n return new Response('OK', { status: 200 })\n }\n\n private async loadActiveThreads(): Promise<Map<string, number>> {\n if (this.activeThreads === null) {\n const stored = await this.state.storage.get<Record<string, number>>(TURN_STREAM_STORAGE_KEYS.activeThreads)\n this.activeThreads = new Map(Object.entries(stored ?? {}))\n }\n return this.activeThreads\n }\n\n private async persistActiveThreads(map: Map<string, number>): Promise<void> {\n await this.state.storage.put(TURN_STREAM_STORAGE_KEYS.activeThreads, Object.fromEntries(map))\n }\n\n // ── chat-turn lock ────────────────────────────────────────────────────────\n\n protected async loadActiveLock(now = Date.now()): Promise<DurableTurnLock | null> {\n const stored = await this.state.storage.get<DurableTurnLock>(TURN_STREAM_STORAGE_KEYS.lock)\n const lock = activeTurnLock(stored, now)\n if (stored && !lock) await this.state.storage.delete(TURN_STREAM_STORAGE_KEYS.lock)\n return lock\n }\n\n private async releaseActiveLock(input: { executionId: string; lockId?: string }): Promise<boolean> {\n const active = await this.loadActiveLock()\n if (!active || !turnLockMatchesRelease(active, input)) return false\n await this.state.storage.delete(TURN_STREAM_STORAGE_KEYS.lock)\n return true\n }\n\n /** Park a release on the lock itself; {@link completeDeferredLockRelease}\n * finishes it once the product's deferred condition settles. */\n private async deferLockRelease(input: { executionId: string; lockId?: string }): Promise<boolean> {\n const active = await this.loadActiveLock()\n if (!active || !turnLockMatchesRelease(active, input)) return false\n if (active.releasePending) return true\n await this.state.storage.put(TURN_STREAM_STORAGE_KEYS.lock, { ...active, releasePending: true })\n return true\n }\n\n private async handleLockAcquire(request: Request): Promise<Response> {\n const body = await jsonBody(request)\n const workspaceId = stringValue(body?.workspaceId)\n const threadId = stringValue(body?.threadId)\n const executionId = stringValue(body?.executionId)\n const lockId = stringValue(body?.lockId)\n const scope: TurnLockScope | null =\n body?.scope === 'workspace' ? 'workspace' : body?.scope === 'thread' ? 'thread' : null\n const turnId = stringValue(body?.turnId) ?? undefined\n if (!workspaceId || !threadId || !executionId || !lockId || !scope) {\n return jsonResponse({ error: 'Missing workspaceId, threadId, executionId, lockId, or scope' }, 400)\n }\n\n const active = await this.loadActiveLock()\n if (active) {\n return jsonResponse({ acquired: false, active } satisfies TurnLockAcquireResult, 409)\n }\n\n const lock = createTurnLock(\n { workspaceId, threadId, scope, executionId, lockId, ...(turnId ? { turnId } : {}) },\n Date.now(),\n this.options.lockTtlMs ?? TURN_LOCK_TTL_MS,\n )\n await this.state.storage.put(TURN_STREAM_STORAGE_KEYS.lock, lock)\n return jsonResponse({ acquired: true, lock } satisfies TurnLockAcquireResult)\n }\n\n private async handleLockRelease(request: Request): Promise<Response> {\n const body = await jsonBody(request)\n const executionId = stringValue(body?.executionId)\n const lockId = stringValue(body?.lockId)\n if (!executionId || !lockId) {\n return jsonResponse({ error: 'Missing executionId or lockId' }, 400)\n }\n\n if (await this.shouldDeferLockRelease(executionId)) {\n const deferred = await this.deferLockRelease({ executionId, lockId })\n return jsonResponse({ released: false, deferred })\n }\n const released = await this.releaseActiveLock({ executionId, lockId })\n return jsonResponse({ released })\n }\n\n private async handleLockReleaseInterrupted(request: Request): Promise<Response> {\n const body = await jsonBody(request)\n const threadId = stringValue(body?.threadId)\n const interruptedAt =\n typeof body?.interruptedAt === 'number' && Number.isFinite(body.interruptedAt) ? body.interruptedAt : undefined\n if (!threadId || interruptedAt === undefined) {\n return jsonResponse({ error: 'Missing threadId or interruptedAt' }, 400)\n }\n const turnId = stringValue(body?.turnId) ?? undefined\n\n const active = await this.loadActiveLock()\n if (!active) return jsonResponse({ released: false })\n // A deferred product task holds the scope even against an out-of-band\n // release — the box is still being read.\n if (await this.shouldDeferLockRelease(active.executionId)) {\n return jsonResponse({ released: false })\n }\n if (!interruptedReleaseApplies(active, { threadId, interruptedAt, ...(turnId ? { turnId } : {}) })) {\n return jsonResponse({ released: false })\n }\n await this.state.storage.delete(TURN_STREAM_STORAGE_KEYS.lock)\n return jsonResponse({ released: true })\n }\n\n // ── durable turn-event storage (TurnEventStore backing) ───────────────────\n\n private async handleTurnEventsAppend(request: Request): Promise<Response> {\n const body = await jsonBody(request)\n const events = Array.isArray(body?.events) ? body.events : null\n if (!events) return jsonResponse({ error: 'Missing events' }, 400)\n for (const row of events) {\n const seq = (row as { seq?: unknown }).seq\n const event = (row as { event?: unknown }).event\n if (typeof seq !== 'number' || !Number.isInteger(seq) || seq < 1 || typeof event !== 'string') {\n return jsonResponse({ error: 'Invalid turn-event row' }, 400)\n }\n }\n for (const row of events as Array<{ seq: number; event: string }>) {\n await this.state.storage.put(turnEventStorageKey(row.seq), row.event)\n }\n return jsonResponse({ appended: events.length })\n }\n\n private async handleTurnEventsRead(request: Request): Promise<Response> {\n const body = await jsonBody(request)\n const fromSeq = typeof body?.fromSeq === 'number' && Number.isFinite(body.fromSeq) ? Math.trunc(body.fromSeq) : 0\n const rows = await this.state.storage.list<string>({\n prefix: TURN_STREAM_STORAGE_KEYS.turnEventPrefix,\n start: turnEventStorageKey(fromSeq + 1),\n })\n const events: Array<{ seq: number; event: string }> = []\n for (const [key, event] of rows) {\n const seq = Number(key.slice(TURN_STREAM_STORAGE_KEYS.turnEventPrefix.length))\n if (Number.isFinite(seq) && seq > fromSeq) events.push({ seq, event })\n }\n return jsonResponse({ events })\n }\n\n private async handleTurnStatusSet(request: Request): Promise<Response> {\n const body = await jsonBody(request)\n const status = body?.status\n if (status !== 'running' && status !== 'complete' && status !== 'error') {\n return jsonResponse({ error: 'Invalid status' }, 400)\n }\n await this.state.storage.put(TURN_STREAM_STORAGE_KEYS.turnStatus, status)\n return jsonResponse({ ok: true })\n }\n\n private async handleTurnStatusGet(): Promise<Response> {\n const status = await this.state.storage.get<string>(TURN_STREAM_STORAGE_KEYS.turnStatus)\n return jsonResponse({ status: status ?? null })\n }\n\n // ── scope index (listRunning reconnect discovery) ─────────────────────────\n\n private async handleScopeStatusSet(request: Request): Promise<Response> {\n const body = await jsonBody(request)\n const turnId = stringValue(body?.turnId)\n const status = body?.status\n if (!turnId || (status !== 'running' && status !== 'complete' && status !== 'error')) {\n return jsonResponse({ error: 'Invalid scope-status request' }, 400)\n }\n const index =\n (await this.state.storage.get<Record<string, ScopeTurnEntry>>(TURN_STREAM_STORAGE_KEYS.turnScope)) ?? {}\n index[turnId] = { status, updatedAt: Date.now() }\n const entries = Object.entries(index)\n if (entries.length > MAX_SCOPE_TURNS) {\n // Prune oldest terminal entries first; a running turn is never dropped.\n const removable = entries\n .filter(([, entry]) => entry.status !== 'running')\n .sort((a, b) => a[1].updatedAt - b[1].updatedAt)\n for (const [id] of removable.slice(0, entries.length - MAX_SCOPE_TURNS)) {\n delete index[id]\n }\n }\n await this.state.storage.put(TURN_STREAM_STORAGE_KEYS.turnScope, index)\n return jsonResponse({ ok: true })\n }\n\n private async handleScopeRunningList(): Promise<Response> {\n const index =\n (await this.state.storage.get<Record<string, ScopeTurnEntry>>(TURN_STREAM_STORAGE_KEYS.turnScope)) ?? {}\n const running = Object.entries(index)\n .filter(([, entry]) => entry.status === 'running')\n .sort((a, b) => b[1].updatedAt - a[1].updatedAt)\n .map(([turnId]) => turnId)\n return jsonResponse({ running })\n }\n}\n","/**\n * Worker-side adapters over {@link TurnStreamDO}: the concrete implementations\n * of the chat vertical's `turnStore` and `turnLock` seams, the WebSocket\n * upgrade forwarder, the best-effort broadcast helpers, and an in-process\n * memory harness for tests and keyless local dev.\n *\n * Everything takes the namespace STRUCTURALLY ({@link TurnStreamNamespaceLike}\n * — Cloudflare's `DurableObjectNamespace` satisfies it), so nothing here\n * imports Cloudflare types and the same adapters run against the memory\n * harness in vitest.\n *\n * Live fanout is deliberately NOT a side effect of the turn-event store: the\n * store is keyed by turnId/scopeId while viewer sockets live on the\n * `${workspaceId}:${threadId}` channel, and only the product's per-turn\n * context knows both. Products wire the workspace signal helpers into\n * `createChatTurnRoutes`' `onEvent`.\n *\n * Which adapters are still the right answer (see `./core`'s header for the\n * production measurement behind this split):\n *\n * | adapter | status |\n * | ------------------------------------ | --------------------------------- |\n * | {@link createDurableTurnLock} | KEPT — no SDK equivalent |\n * | {@link reconcileStaleDurableTurnLock}| KEPT — no SDK equivalent |\n * | {@link createDurableObjectTurnEventStore} | KEPT — the DETACHED lane |\n * | {@link broadcastWorkspaceActivity} | KEPT — workspace signal |\n * | {@link broadcastThreadCreated} | KEPT — workspace signal |\n * | {@link createTurnStreamUpgradeHandler} | KEPT for the workspace channel |\n * | {@link broadcastTurnStreamEvent} | `@deprecated` for sandbox turns |\n */\n\nimport { reconcileStaleTurnLock, type ReconcileStaleTurnLockOptions } from '../chat-routes/stale-turn-lock'\nimport type { TurnEventStore, TurnStatus } from '../stream/turn-buffer'\nimport {\n TURN_STREAM_PATHS,\n scopeIndexChannelKey,\n threadChannelKey,\n turnLockChannelKey,\n turnStorageChannelKey,\n workspaceChannelKey,\n type DurableTurnLock,\n type TurnLockAcquireResult,\n type TurnLockReleaseInput,\n type TurnLockScope,\n type TurnStreamEvent,\n} from './core'\n\n// ── structural namespace ────────────────────────────────────────────────────\n\n/** Resolve a stub interface for handling fetch requests with optional initialization parameters */\nexport interface TurnStreamStubLike {\n fetch(input: Request | string, init?: RequestInit): Promise<Response>\n}\n\n/** The surface of `DurableObjectNamespace` the adapters use. */\nexport interface TurnStreamNamespaceLike {\n idFromName(name: string): unknown\n get(id: unknown): TurnStreamStubLike\n}\n\nconst INTERNAL_ORIGIN = 'https://turn-stream.internal'\n\nasync function postJson<T>(\n namespace: TurnStreamNamespaceLike,\n channelKey: string,\n path: string,\n body: unknown,\n): Promise<{ status: number; body: T }> {\n const stub = namespace.get(namespace.idFromName(channelKey))\n const response = await stub.fetch(`${INTERNAL_ORIGIN}${path}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n })\n return { status: response.status, body: (await response.json()) as T }\n}\n\nasync function postJsonOk<T>(\n namespace: TurnStreamNamespaceLike,\n channelKey: string,\n path: string,\n body: unknown,\n): Promise<T> {\n const result = await postJson<T>(namespace, channelKey, path, body)\n if (result.status !== 200) {\n throw new Error(`turn-stream ${path} failed with status ${result.status}`)\n }\n return result.body\n}\n\n// ── TurnEventStore adapter (the real turnStore) ─────────────────────────────\n\n/**\n * A {@link TurnEventStore} backed by {@link TurnStreamDO} storage — the\n * production implementation of `createChatTurnRoutes`' `turnStore` seam for\n * apps that don't run D1 for turn events (or want replay co-located with the\n * live channel). Each buffered turn lives on its own `turn:<turnId>` DO\n * instance; `listRunning` reconnect discovery rides a per-scope index\n * instance. Drops in wherever `createD1TurnEventStore(env.DB)` would.\n *\n * KEPT and load-bearing for AUTONOMOUS work. A detached run\n * (`dispatchPrompt({ detach: true })`, `driveTurn`) executes on the sandbox\n * run/stream lane, which publishes nothing to the session event bus — a\n * `SessionGatewayClient` attached to that session sees zero turn events\n * (measured: 0 of 71 / 0 of 527 / 0 of 408 across three session-id\n * strategies). So a browser that must tail a mission step, a queue job, or\n * an inbound-email review has no SDK path; it needs these durable rows plus\n * `runDetachedTurn` (`/chat-routes`). Nothing here is deprecated.\n */\nexport function createDurableObjectTurnEventStore(namespace: TurnStreamNamespaceLike): TurnEventStore {\n return {\n async append(turnId, events) {\n if (!events.length) return\n await postJsonOk(namespace, turnStorageChannelKey(turnId), TURN_STREAM_PATHS.turnEventsAppend, { events })\n },\n async read(turnId, fromSeq) {\n const body = await postJsonOk<{ events: Array<{ seq: number; event: string }> }>(\n namespace,\n turnStorageChannelKey(turnId),\n TURN_STREAM_PATHS.turnEventsRead,\n { fromSeq },\n )\n return body.events\n },\n async setStatus(turnId, status, scopeId) {\n await postJsonOk(namespace, turnStorageChannelKey(turnId), TURN_STREAM_PATHS.turnStatusSet, { status })\n if (scopeId) {\n await postJsonOk(namespace, scopeIndexChannelKey(scopeId), TURN_STREAM_PATHS.scopeStatusSet, {\n turnId,\n status,\n })\n }\n },\n async getStatus(turnId) {\n const body = await postJsonOk<{ status: TurnStatus | null }>(\n namespace,\n turnStorageChannelKey(turnId),\n TURN_STREAM_PATHS.turnStatusGet,\n {},\n )\n return body.status\n },\n async listRunning(scopeId) {\n const body = await postJsonOk<{ running: string[] }>(\n namespace,\n scopeIndexChannelKey(scopeId),\n TURN_STREAM_PATHS.scopeRunningList,\n {},\n )\n return body.running\n },\n }\n}\n\n// ── lock primitives (usable outside the route seam too) ─────────────────────\n\n/** Define input parameters required to acquire a durable turn lock in a workspace thread context */\nexport interface AcquireDurableTurnLockInput {\n workspaceId: string\n threadId: string\n scope: TurnLockScope\n executionId: string\n turnId?: string\n /** Supply to reclaim/retry with a stable id; default mints a UUID. */\n lockId?: string\n}\n\n/** Acquire a durable turn lock in the specified namespace with given input parameters */\nexport async function acquireDurableTurnLock(\n namespace: TurnStreamNamespaceLike,\n input: AcquireDurableTurnLockInput,\n): Promise<TurnLockAcquireResult> {\n const lockId = input.lockId ?? crypto.randomUUID()\n const key = turnLockChannelKey(input.workspaceId, input.threadId, input.scope)\n const result = await postJson<TurnLockAcquireResult>(namespace, key, TURN_STREAM_PATHS.lockAcquire, {\n ...input,\n lockId,\n })\n if (result.status !== 200 && result.status !== 409) {\n throw new Error(`turn-stream lock acquire failed with status ${result.status}`)\n }\n return result.body\n}\n\n/** Release a durable turn lock and indicate if the release was successful or deferred */\nexport async function releaseDurableTurnLock(\n namespace: TurnStreamNamespaceLike,\n input: TurnLockReleaseInput,\n): Promise<{ released: boolean; deferred?: boolean }> {\n const key = turnLockChannelKey(input.workspaceId, input.threadId, input.scope)\n return postJsonOk(namespace, key, TURN_STREAM_PATHS.lockRelease, input)\n}\n\n/** Define input parameters to release an interrupted durable turn lock in a workspace or thread */\nexport interface ReleaseInterruptedDurableTurnLockInput {\n workspaceId: string\n threadId: string\n /** Try only this scope; omit to try workspace then thread (a stop button\n * doesn't know which lane the wedged turn ran on). */\n scope?: TurnLockScope\n interruptedAt: number\n turnId?: string\n}\n\n/** Fenced out-of-band release — the DO refuses a successor lock (started\n * after `interruptedAt`) and a turnId mismatch. Returns whether any scope\n * released. */\nexport async function releaseInterruptedDurableTurnLock(\n namespace: TurnStreamNamespaceLike,\n input: ReleaseInterruptedDurableTurnLockInput,\n): Promise<boolean> {\n const scopes: readonly TurnLockScope[] = input.scope ? [input.scope] : ['workspace', 'thread']\n for (const scope of scopes) {\n const key = turnLockChannelKey(input.workspaceId, input.threadId, scope)\n const result = await postJsonOk<{ released: boolean }>(namespace, key, TURN_STREAM_PATHS.lockReleaseInterrupted, {\n threadId: input.threadId,\n interruptedAt: input.interruptedAt,\n ...(input.turnId ? { turnId: input.turnId } : {}),\n })\n if (result.released) return true\n }\n return false\n}\n\n// ── stale-lock reconciliation against the DO ────────────────────────────────\n\n/** Define options to reconcile stale durable turn locks with context, namespace, workspace, and active lock details */\nexport interface ReconcileStaleDurableTurnLockOptions\n extends Pick<\n ReconcileStaleTurnLockOptions,\n 'probeSandbox' | 'probeSession' | 'graceMs' | 'terminalGraceMs' | 'context' | 'log' | 'now'\n > {\n namespace: TurnStreamNamespaceLike\n workspaceId: string\n threadId: string\n /** The lock the acquire attempt was refused on. */\n active: DurableTurnLock\n}\n\n/**\n * `/chat-routes`' `reconcileStaleTurnLock` policy wired to the DO: the\n * product supplies the probes (which box, what its session says), the policy\n * decides, and a release lands as a FENCED interrupted release —\n * `interruptedAt` is the policy's `fence.observedAt`, so a successor lock\n * acquired while the probes were in flight survives by construction.\n */\nexport async function reconcileStaleDurableTurnLock(\n options: ReconcileStaleDurableTurnLockOptions,\n): Promise<{ released: boolean; diagnostics: Record<string, unknown> }> {\n const { namespace, workspaceId, threadId, active, ...policy } = options\n return reconcileStaleTurnLock({\n ...policy,\n lockStartedAt: active.startedAt,\n release: (fence) =>\n releaseInterruptedDurableTurnLock(namespace, {\n workspaceId,\n threadId: active.threadId,\n scope: active.scope,\n interruptedAt: fence.observedAt,\n ...(active.turnId ? { turnId: active.turnId } : {}),\n }),\n })\n}\n\n// ── ChatTurnLock adapter (the turnLock seam) ────────────────────────────────\n\n/** The subset of `ChatTurnProduceArgs` the lock adapter reads — structural,\n * so this module needs no import from `/chat-routes/turn-routes` (which\n * would drag the `agent-runtime` peer into every `/turn-stream` consumer). */\nexport interface TurnLockSeamArgs<TContext> {\n identity: { tenantId: string; sessionId: string }\n executionId: string\n context: TContext\n body: { turnId?: string }\n}\n\n/** Verdict shape of the vertical's `turnLock.acquire`. */\nexport type TurnLockSeamResult =\n | { acquired: true; handle?: unknown }\n | { acquired: false; response: Response }\n\n/** Define options for creating a durable turn lock with customizable scope and identification methods */\nexport interface CreateDurableTurnLockOptions<TContext> {\n namespace: TurnStreamNamespaceLike\n /** Which lane serializes this turn: `'workspace'` (shared sandbox — one\n * turn per workspace) or `'thread'` (router lane — one turn per thread). */\n scopeOf(args: TurnLockSeamArgs<TContext>): TurnLockScope\n /** Override the execution id the lock records (e.g. a follow-up reclaiming\n * the execution it already dispatched). Default: the turn's own. */\n lockExecutionIdOf?(args: TurnLockSeamArgs<TContext>): string | undefined\n /** Client turn id recorded on the lock (fences interrupted releases to the\n * right turn). Default: the request body's `turnId`. */\n clientTurnIdOf?(args: TurnLockSeamArgs<TContext>): string | undefined\n /**\n * Attempt stale-lock recovery after a refused acquire. Return whether the\n * held lock was released (the adapter then retries the acquire once).\n * Products wire {@link reconcileStaleDurableTurnLock} with their sandbox +\n * session probes here. Omit → a refused acquire is final.\n */\n reconcile?(\n args: TurnLockSeamArgs<TContext>,\n active: DurableTurnLock,\n ): Promise<{ released: boolean; diagnostics?: Record<string, unknown> }>\n /** Build the refusal `Response`. Default: a 409 with the shared body shape\n * (`code`, `message`, lock identity + age, reconcile diagnostics). */\n onRefused?(active: DurableTurnLock, diagnostics: Record<string, unknown> | undefined): Response\n}\n\nfunction defaultRefusalResponse(\n active: DurableTurnLock,\n diagnostics: Record<string, unknown> | undefined,\n): Response {\n const workspaceConflict = active.scope === 'workspace'\n return Response.json(\n {\n error: {\n code: workspaceConflict ? 'workspace_turn_in_flight' : 'chat_turn_in_flight',\n message: workspaceConflict\n ? 'A chat turn is already running for this workspace. Wait for it to finish before starting another.'\n : 'A chat turn is already running for this thread. Reconnect to the active response or wait for it to finish.',\n threadId: active.threadId,\n executionId: active.executionId,\n ...(active.turnId ? { turnId: active.turnId } : {}),\n lockStartedAt: active.startedAt,\n lockAgeMs: Date.now() - active.startedAt,\n ...(diagnostics ? { diagnostics } : {}),\n },\n },\n { status: 409 },\n )\n}\n\n/**\n * The vertical's `turnLock` seam on the shared DO: dual-scope single-flight\n * acquire (with one reconcile-then-retry pass when the product supplies a\n * stale-lock reconciler) and cooperative release on settle. The returned\n * object satisfies `createChatTurnRoutes`' `ChatTurnLock<TContext>`\n * structurally.\n */\nexport function createDurableTurnLock<TContext>(options: CreateDurableTurnLockOptions<TContext>): {\n acquire(args: TurnLockSeamArgs<TContext>): Promise<TurnLockSeamResult>\n release(handle: unknown): Promise<void>\n} {\n return {\n async acquire(args) {\n const workspaceId = args.identity.tenantId\n const threadId = args.identity.sessionId\n const scope = options.scopeOf(args)\n const executionId = options.lockExecutionIdOf?.(args) ?? args.executionId\n const turnId = options.clientTurnIdOf ? options.clientTurnIdOf(args) : args.body.turnId\n const input: AcquireDurableTurnLockInput = {\n workspaceId,\n threadId,\n scope,\n executionId,\n ...(turnId ? { turnId } : {}),\n }\n\n let acquired = await acquireDurableTurnLock(options.namespace, input)\n let diagnostics: Record<string, unknown> | undefined\n if (!acquired.acquired && options.reconcile) {\n const reconciled = await options.reconcile(args, acquired.active)\n diagnostics = reconciled.diagnostics\n if (reconciled.released) {\n acquired = await acquireDurableTurnLock(options.namespace, input)\n }\n }\n if (!acquired.acquired) {\n return {\n acquired: false,\n response: (options.onRefused ?? defaultRefusalResponse)(acquired.active, diagnostics),\n }\n }\n const handle: TurnLockReleaseInput = {\n workspaceId,\n threadId,\n scope,\n executionId,\n lockId: acquired.lock.lockId,\n }\n return { acquired: true, handle }\n },\n async release(handle) {\n if (!handle) return\n await releaseDurableTurnLock(options.namespace, handle as TurnLockReleaseInput)\n },\n }\n}\n\n// ── broadcast helpers (products wire these into onEvent) ────────────────────\n\n/**\n * DEPRECATED for sandbox-backed interactive turns (drive on the session-message\n * lane + `SessionGatewayClient` instead) — fan a turn event out to the\n * per-thread channel. `executionId` groups events\n * into a per-turn segment with a monotonic seq, so a reconnecting client\n * replays only the active turn and resumes from a cursor. Callers MUST await\n * (the DO assigns seq on arrival — emission order matters); failures are\n * swallowed (fanout is best-effort and never breaks chat delivery).\n *\n * @deprecated For a SANDBOX-backed interactive turn this re-broadcasts events\n * the sandbox platform already fans out, at the cost of a worker hop. Drive\n * the turn on the session-MESSAGE lane instead —\n * `box.createSession({ sessionId, backend })` then\n * `box.session(id).sendMessage({ parts: [{ type: 'text', text }] })` — and let\n * the tab attach with `box.mintScopedToken({ scope: 'session', sessionId,\n * runtimeSessionId })` + `SessionGatewayClient`\n * (`@tangle-network/sandbox/session-gateway`). Measured on production\n * (4 arms, SDK 0.12.0): turns driven with `box.streamPrompt()` delivered\n * 0 of 71 / 0 of 527 / 0 of 408 turn events to a gateway client, because\n * `POST /agents/run/stream` publishes nothing to the session event bus;\n * the message lane delivered 297 of 297.\n *\n * Two things this deprecation does NOT cover, both still supported:\n * DETACHED runs (no gateway fanout at all — keep `runDetachedTurn` over the\n * durable turn-event rows) and the per-workspace signals\n * ({@link broadcastWorkspaceActivity} / {@link broadcastThreadCreated}).\n * A SANDBOX-FREE copilot that wants a second viewer should use `/stream`'s\n * `replayTurnEvents` (`GET /chat/stream/:turnId`), which follows a running\n * turn from a cursor without a second broadcast fabric.\n *\n * Kept for back-compat; removal is a major-version change.\n */\nexport async function broadcastTurnStreamEvent(\n namespace: TurnStreamNamespaceLike,\n input: {\n workspaceId: string\n threadId: string\n executionId: string\n event: { type: string; data?: Record<string, unknown> }\n },\n): Promise<void> {\n try {\n await postJson(namespace, threadChannelKey(input.workspaceId, input.threadId), TURN_STREAM_PATHS.broadcast, {\n type: input.event.type,\n timestamp: Date.now(),\n data: {\n ...(input.event.data ?? {}),\n workspaceId: input.workspaceId,\n threadId: input.threadId,\n sessionId: input.threadId,\n executionId: input.executionId,\n },\n } satisfies TurnStreamEvent)\n } catch {\n // Best-effort.\n }\n}\n\n/** Coarse per-workspace marker that a thread's turn started / ended — drives\n * a sidebar \"agent responding\" indicator subscribed once per workspace. */\nexport async function broadcastWorkspaceActivity(\n namespace: TurnStreamNamespaceLike,\n workspaceId: string,\n threadId: string,\n phase: 'start' | 'end',\n): Promise<void> {\n try {\n await postJson(namespace, workspaceChannelKey(workspaceId), TURN_STREAM_PATHS.broadcast, {\n type: 'thread.activity',\n timestamp: Date.now(),\n data: { threadId, phase, workspaceId, sessionId: workspaceId },\n } satisfies TurnStreamEvent)\n } catch {\n // Best-effort: the indicator is non-critical UI.\n }\n}\n\n/** Per-workspace marker that a new thread was created, so an already-open\n * history list prepends it without a reload. */\nexport async function broadcastThreadCreated(\n namespace: TurnStreamNamespaceLike,\n workspaceId: string,\n thread: { threadId: string; title: string },\n): Promise<void> {\n try {\n await postJson(namespace, workspaceChannelKey(workspaceId), TURN_STREAM_PATHS.broadcast, {\n type: 'thread.created',\n timestamp: Date.now(),\n data: { threadId: thread.threadId, title: thread.title, workspaceId, sessionId: workspaceId },\n } satisfies TurnStreamEvent)\n } catch {\n // Best-effort: the loader reconciles on the next revalidation.\n }\n}\n\n// ── WebSocket upgrade forwarder (worker entry) ──────────────────────────────\n\n/** Represent success or failure of a TURN stream upgrade authorization with optional response data */\nexport type TurnStreamUpgradeAuthorization = { ok: true } | { ok: false; response: Response }\n\n/** Define options for creating a TURN stream upgrade handler including namespace, path, and authorization logic */\nexport interface CreateTurnStreamUpgradeHandlerOptions {\n namespace: TurnStreamNamespaceLike\n /** The worker route serving the stream. Default `/api/session-stream`. */\n path?: string\n /** Viewer access check (session cookie → workspace membership). */\n authorize(\n request: Request,\n target: { workspaceId: string; threadId: string | null },\n ): Promise<TurnStreamUpgradeAuthorization>\n}\n\n/**\n * The worker-entry WebSocket forwarder. Call BEFORE the app router (a router\n * loader cannot return a 101); returns `null` for requests that are not a\n * WebSocket upgrade on the configured path.\n *\n * GET {path}?workspaceId=... → workspace channel (sidebar)\n * GET {path}?workspaceId=...&threadId=… → thread channel (turn resume)\n *\n * After the 101, the client sends `{type:'sync', afterSeq}` and receives the\n * replay-then-live stream (see {@link TurnStreamDO.webSocketMessage}).\n *\n * NOT deprecated — the workspace variant (no `threadId`) is the canonical\n * transport for the per-workspace signals, which the session gateway cannot\n * carry (it is per-session and read-only). The THREAD variant is the\n * deprecated half: for a sandbox-backed interactive turn the tab should\n * attach to the session gateway directly instead of to this socket. See\n * {@link broadcastTurnStreamEvent} for the measurement and the replacement\n * wiring.\n */\nexport function createTurnStreamUpgradeHandler(\n options: CreateTurnStreamUpgradeHandlerOptions,\n): (request: Request) => Promise<Response | null> {\n const path = options.path ?? '/api/session-stream'\n return async (request) => {\n const url = new URL(request.url)\n if (url.pathname !== path || request.headers.get('Upgrade') !== 'websocket') return null\n\n const workspaceId = url.searchParams.get('workspaceId')\n const threadId = url.searchParams.get('threadId')\n if (!workspaceId) return new Response('Missing workspaceId', { status: 400 })\n\n const auth = await options.authorize(request, { workspaceId, threadId })\n if (!auth.ok) return auth.response\n\n // threadId present → per-thread turn channel; absent → workspace channel.\n const key = threadId ? threadChannelKey(workspaceId, threadId) : workspaceChannelKey(workspaceId)\n const sessionId = threadId ?? workspaceId\n const stub = options.namespace.get(options.namespace.idFromName(key))\n\n const forwardUrl = new URL(request.url)\n forwardUrl.searchParams.set('sessionId', sessionId)\n forwardUrl.searchParams.set('scope', threadId ? 'thread' : 'workspace')\n return stub.fetch(new Request(forwardUrl, request))\n }\n}\n","/**\n * In-process turn-stream harness: a fake namespace that routes `idFromName`\n * to REAL {@link TurnStreamDO} instances over an in-memory storage/socket\n * state. The adapters and the DO run their production code paths — only the\n * Cloudflare runtime (isolation, hibernation, real sockets) is simulated.\n *\n * For vitest composition tests and keyless local dev (the same role\n * `createMemoryTurnEventStore` plays for the D1 store). Not for production:\n * state is per-process and evaporates on restart.\n */\n\nimport { TurnStreamDO, type TurnStreamDOOptions, type TurnStreamDOState, type TurnStreamSocket, type TurnStreamStorage } from './do'\nimport type { TurnStreamNamespaceLike, TurnStreamStubLike } from './adapters'\n\nfunction createMemoryStorage(): TurnStreamStorage {\n const map = new Map<string, unknown>()\n return {\n async get<T>(key: string) {\n return map.get(key) as T | undefined\n },\n async put(key, value) {\n map.set(key, value)\n },\n async delete(key) {\n return map.delete(key)\n },\n async list<T>({ prefix, start }: { prefix: string; start?: string }) {\n const keys = [...map.keys()]\n .filter((key) => key.startsWith(prefix) && (start === undefined || key >= start))\n .sort()\n return new Map(keys.map((key) => [key, map.get(key) as T]))\n },\n }\n}\n\n/** A test-side viewer socket: records frames sent by the DO and lets the\n * test drive the `sync` handshake. */\nexport interface MemoryTurnStreamSocket extends TurnStreamSocket {\n readonly frames: string[]\n readonly closed: boolean\n}\n\n/** Define an in-memory channel for streaming turn-based data with viewer socket connection support */\nexport interface MemoryTurnStreamChannel {\n /** Attach a viewer socket to this channel (bypasses the HTTP 101 — the\n * upgrade handshake is Cloudflare-runtime-only) and run its `sync`. */\n connect(input: { sessionId: string; scope: 'thread' | 'workspace'; afterSeq?: number }): Promise<MemoryTurnStreamSocket>\n readonly instance: TurnStreamDO\n}\n\n/** Provide an interface to manage channels and namespaces for memory-based turn stream testing */\nexport interface MemoryTurnStreamHarness {\n namespace: TurnStreamNamespaceLike\n /** The channel (creating its DO instance if needed) for a channel key —\n * e.g. `threadChannelKey(ws, thread)` — to connect test viewers. */\n channel(name: string): MemoryTurnStreamChannel\n}\n\ninterface MemoryInstance {\n instance: TurnStreamDO\n sockets: MemoryTurnStreamSocket[]\n}\n\n/**\n * Build the harness. `createInstance` lets a product test run its own\n * `TurnStreamDO` subclass through the same wiring.\n */\nexport function createMemoryTurnStreamHarness(\n createInstance: (state: TurnStreamDOState) => TurnStreamDO = (state) => new TurnStreamDO(state),\n _options: TurnStreamDOOptions = {},\n): MemoryTurnStreamHarness {\n const instances = new Map<string, MemoryInstance>()\n\n function ensure(name: string): MemoryInstance {\n let entry = instances.get(name)\n if (!entry) {\n const sockets: MemoryTurnStreamSocket[] = []\n const state: TurnStreamDOState = {\n storage: createMemoryStorage(),\n acceptWebSocket: (ws) => sockets.push(ws as MemoryTurnStreamSocket),\n getWebSockets: () => sockets.filter((ws) => !ws.closed),\n }\n entry = { instance: createInstance(state), sockets }\n instances.set(name, entry)\n }\n return entry\n }\n\n const namespace: TurnStreamNamespaceLike = {\n idFromName: (name) => name,\n get(id): TurnStreamStubLike {\n const entry = ensure(String(id))\n return {\n fetch: (input, init) =>\n entry.instance.fetch(typeof input === 'string' ? new Request(input, init) : input),\n }\n },\n }\n\n return {\n namespace,\n channel(name) {\n const entry = ensure(name)\n return {\n instance: entry.instance,\n async connect({ sessionId, scope, afterSeq = 0 }) {\n let attachment: unknown = null\n const frames: string[] = []\n let closed = false\n const socket: MemoryTurnStreamSocket = {\n get frames() {\n return frames\n },\n get closed() {\n return closed\n },\n send(data) {\n if (closed) throw new Error('socket closed')\n frames.push(data)\n },\n close() {\n closed = true\n },\n serializeAttachment(value) {\n attachment = value\n },\n deserializeAttachment() {\n return attachment\n },\n }\n socket.serializeAttachment({ sessionId, scope, synced: false })\n entry.sockets.push(socket)\n await entry.instance.webSocketMessage(socket, JSON.stringify({ type: 'sync', afterSeq }))\n return socket\n },\n }\n },\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/turn-stream/core.ts","../../src/turn-stream/do.ts","../../src/turn-stream/adapters.ts","../../src/turn-stream/memory.ts"],"sourcesContent":["/**\n * `/turn-stream` core — the pure, substrate-free half of the shared durable\n * turn replay/broadcast/lock channel (issue #221).\n *\n * Extracted from the reference consumer's hand-rolled Durable Object\n * (gtm-agent `SessionStreamDO` + `session-broadcast.ts`): the per-turn\n * segment store that backs reconnect replay over a live socket, the\n * single-flight chat-turn lock record and its release fences, and the wire\n * contract (channel keys, endpoint paths, request/response bodies) shared by\n * the DO transport shell (`./do`) and the worker-side adapters\n * (`./adapters`). Everything here is plain data + functions — no\n * `cloudflare:workers`, no storage, no sockets — so the semantics are\n * unit-testable in Node and the DO stays a thin shell.\n *\n * ── The two-lane rule (measured, not assumed) ────────────────────────────\n *\n * A 4-arm A/B on production (sandbox.tangle.tools, SDK 0.12.0, one box, one\n * gateway client per arm) established which sandbox lane a browser can see:\n *\n * | turn driver | raw turn events | seen at gateway |\n * | ------------------------------------ | --------------- | --------------- |\n * | `box.streamPrompt()` (run/stream) | 71 / 527 / 408 | 0 / 0 / 0 |\n * | `box.session(id).sendMessage()` | 297 | 297 |\n *\n * `POST /agents/run/stream` publishes nothing to the sidecar session event\n * bus, so a `SessionGatewayClient` attached to that session receives zero\n * turn events — three different session-id strategies all got 0, the id was\n * not the variable. `POST /agents/sessions/{id}/messages` publishes to the\n * bus and the gateway delivered every frame, byte-matching the sidecar tail.\n *\n * Consequences for this module, and they cut both ways:\n *\n * 1. INTERACTIVE sandbox turns should be driven on the message lane and\n * tailed by the browser through `box.mintScopedToken({ scope: 'session' })`\n * + `SessionGatewayClient`. Re-broadcasting those same events through the\n * per-turn SEGMENT buffer below duplicates the SDK and adds a worker hop.\n * That half is `@deprecated` (see the tags on {@link createSegmentStore},\n * {@link appendSegmentEvent}, {@link replayActiveSegment} and\n * `broadcastTurnStreamEvent` in `./adapters`).\n * 2. DETACHED/autonomous turns are INVISIBLE to the gateway:\n * `dispatchPrompt({ detach: true })` and `driveTurn` both go through\n * `streamPrompt` internally, i.e. the run/stream lane, which does not fan\n * out. A browser that must tail an unattended run still needs a buffer —\n * `runDetachedTurn` (`/chat-routes`) over the durable turn-event rows\n * below. That half is NOT deprecated and has no SDK replacement today.\n * 3. The LOCK and the per-workspace SIGNALS have no gateway equivalent at\n * all (the gateway is per-session and read-only). They stay canonical.\n *\n * Server-side resume of a run/stream turn is also already solved by the SDK\n * and needs nothing here: `box.streamPrompt('', { executionId, lastEventId })`\n * replays strictly after the cursor without re-dispatching — measured across\n * a SIGKILL mid-run and a fresh process resuming from the cursor alone:\n * 0 lost, 0 duplicated, 0 out-of-order, ids 1..517 contiguous.\n */\n\n// ── events ──────────────────────────────────────────────────────────────────\n\n/** One event on a turn-stream channel. `seq` is monotonic within a turn\n * segment and assigned by {@link appendSegmentEvent} on arrival at the DO. */\nexport interface TurnStreamEvent {\n type: string\n data?: unknown\n timestamp: number\n seq?: number\n}\n\n/** Terminal run markers: they close a turn segment and auto-release the\n * channel's chat-turn lock for the segment's execution.\n *\n * KEPT (not deprecated with the segment buffer): the lock auto-release\n * reads it. A product that stops broadcasting turn events to the thread\n * channel loses only that auto-release — the cooperative release on settle\n * (`createDurableTurnLock().release`) and `reconcileStaleDurableTurnLock`\n * both still fire, which is what actually frees a wedged lane. */\nexport function isTerminalRunEvent(type: string): boolean {\n return type === 'session.run.completed' || type === 'session.run.failed'\n}\n\n// ── channel keys ────────────────────────────────────────────────────────────\n//\n// One DO instance per channel key. Three families:\n// thread `${workspaceId}:${threadId}` — live turn fanout + segments +\n// the thread-scope lock.\n// workspace `${workspaceId}` — sidebar activity + thread.created\n// + the workspace-scope lock.\n// turn `turn:${turnId}` — durable turn-event rows + status\n// (the TurnEventStore contract; replay survives DO eviction).\n// scope `scope:${scopeId}` — running-turn index for a thread,\n// backing `TurnEventStore.listRunning`.\n\n/** Define the scope level for acquiring a turn lock within thread or workspace contexts */\nexport type TurnLockScope = 'thread' | 'workspace'\n\n/** Generate a unique string key combining workspace and thread identifiers.\n *\n * KEPT: thread-scope LOCKS are keyed on it (see {@link turnLockChannelKey}).\n * Only its second use — addressing a live-viewer socket for interactive\n * sandbox-turn rebroadcast — is superseded by the session gateway. */\nexport function threadChannelKey(workspaceId: string, threadId: string): string {\n return `${workspaceId}:${threadId}`\n}\n\n/** Generate a unique channel key based on the given workspace identifier.\n *\n * KEPT and canonical: the per-workspace signal channel (`thread.created`,\n * `thread.activity`) plus workspace-scope locks. The session gateway is\n * per-SESSION and read-only, so it cannot carry either. */\nexport function workspaceChannelKey(workspaceId: string): string {\n return workspaceId\n}\n\n/** The channel a lock lives on: workspace-scope locks serialize every thread\n * in the workspace (one shared sandbox), thread-scope locks serialize one\n * thread (router lane). Same keying as the reference consumer, so a product\n * swapping its fork for this package contends on identical instances. */\nexport function turnLockChannelKey(workspaceId: string, threadId: string, scope: TurnLockScope): string {\n return scope === 'workspace' ? workspaceChannelKey(workspaceId) : threadChannelKey(workspaceId, threadId)\n}\n\n/** Generate a storage channel key string for a given turn identifier.\n *\n * KEPT and canonical: the DETACHED lane's durable turn-event rows live on\n * this instance. A detached run never reaches the session gateway, so this\n * is the only way a browser tails one. */\nexport function turnStorageChannelKey(turnId: string): string {\n return `turn:${turnId}`\n}\n\n/** Generate a unique channel key string based on the provided scope identifier.\n *\n * KEPT and canonical: backs `TurnEventStore.listRunning`, which is how a\n * reloaded client rediscovers an in-flight DETACHED turn. */\nexport function scopeIndexChannelKey(scopeId: string): string {\n return `scope:${scopeId}`\n}\n\n// ── segment store (reconnect replay over a live socket) ─────────────────────\n\n/** DEPRECATED (interactive turn-rebroadcast buffer) — represent a segment of a turn containing events, sequence limit, and terminal status.\n *\n * @deprecated Part of the interactive turn-rebroadcast buffer — see the\n * two-lane rule in this file's header. Removal is a major-version change. */\nexport interface TurnSegment {\n events: TurnStreamEvent[]\n maxSeq: number\n terminal: boolean\n}\n\n/** DEPRECATED (interactive turn-rebroadcast buffer) — define a store managing segments and tracking the active execution identifier.\n *\n * @deprecated Part of the interactive turn-rebroadcast buffer — see the\n * two-lane rule in this file's header. Removal is a major-version change. */\nexport interface SegmentStore {\n segments: Map<string, TurnSegment>\n activeExecutionId: string | null\n}\n\n/** DEPRECATED (interactive turn-rebroadcast buffer) — per-turn replay window. Generous enough for normal turns; a turn that\n * exceeds it loses its earliest deltas from replay (a late resumer\n * self-heals via the final `result` event + loader revalidation).\n *\n * @deprecated Sizes the interactive turn-rebroadcast buffer only. The\n * DETACHED lane's durable rows (`turnEvent:` storage) are uncapped and are\n * not affected. */\nexport const MAX_SEGMENT_EVENTS = 2000\n\n/** Recent `thread.created` markers kept for late-connecting sidebars.\n *\n * KEPT: a workspace-level signal, not a turn rebroadcast. */\nexport const MAX_RECENT_CREATED = 50\n\n/** A responding marker older than this is treated as stale, so a dropped\n * `end` broadcast can't leave a permanently-stuck \"responding\" dot. */\nexport const ACTIVITY_TTL_MS = 15 * 60 * 1000\n\n/** DEPRECATED (interactive sandbox-turn rebroadcast; the SDK's session gateway replaces it) — create a SegmentStore with initialized segments and no active execution ID.\n *\n * @deprecated Backs the interactive sandbox-turn rebroadcast, which the\n * sandbox SDK already does better (measured: run/stream → 0 frames at the\n * gateway, message lane → 297/297; see the two-lane rule in this file's\n * header). Sandbox turns: drive on `box.session(id).sendMessage()` and let\n * the browser attach with `box.mintScopedToken({ scope: 'session' })` +\n * `SessionGatewayClient`. Sandbox-FREE turns: `/stream`'s\n * `replayTurnEvents` (`GET /chat/stream/:turnId`) already follows a running\n * turn from a cursor. Detached turns keep the durable turn-event rows —\n * they are a different, non-deprecated lane. Removal is a major-version\n * change; nothing is deleted here. */\nexport function createSegmentStore(): SegmentStore {\n return { segments: new Map(), activeExecutionId: null }\n}\n\n/**\n * DEPRECATED (interactive turn-rebroadcast buffer) — append a per-turn event to\n * its execution's segment, assigning a monotonic\n * `seq`. A `session.run.started` (or the first-seen event for an execution)\n * opens a fresh segment, makes it active, and drops prior turns' buffers so a\n * resumer only ever replays the current turn. A terminal run event marks the\n * segment terminal. Returns the seq-stamped event to broadcast.\n *\n * @deprecated The interactive rebroadcast half — {@link createSegmentStore}\n * names the replacement per lane; this file's header holds the measurement.\n */\nexport function appendSegmentEvent(\n store: SegmentStore,\n executionId: string,\n incoming: TurnStreamEvent,\n maxEvents = MAX_SEGMENT_EVENTS,\n): TurnStreamEvent {\n let segment = store.segments.get(executionId)\n if (incoming.type === 'session.run.started' || !segment) {\n if (!segment) {\n segment = { events: [], maxSeq: 0, terminal: false }\n store.segments.set(executionId, segment)\n }\n store.activeExecutionId = executionId\n for (const id of store.segments.keys()) {\n if (id !== executionId) store.segments.delete(id)\n }\n }\n const seq = ++segment.maxSeq\n const stamped: TurnStreamEvent = { ...incoming, seq }\n segment.events.push(stamped)\n if (segment.events.length > maxEvents) {\n segment.events = segment.events.slice(-maxEvents)\n }\n if (isTerminalRunEvent(incoming.type)) {\n segment.terminal = true\n }\n return stamped\n}\n\n/**\n * DEPRECATED (interactive turn-rebroadcast buffer; the SDK replays losslessly on\n * both lanes) — events of the active, non-terminal turn with `seq > afterSeq`,\n * i.e. what a\n * (re)connecting client replays before going live. A terminal (finished) turn\n * replays nothing: the client falls back to the loader's persisted row.\n *\n * @deprecated The interactive rebroadcast half — {@link createSegmentStore}\n * names the replacement per lane. The SDK's own reconnect replay is\n * `SessionGatewayClient` + `lastEventId` (browser) or\n * `box.streamPrompt('', { executionId, lastEventId })` (worker); both were\n * measured lossless.\n */\nexport function replayActiveSegment(store: SegmentStore, afterSeq: number): TurnStreamEvent[] {\n const segment = store.activeExecutionId ? store.segments.get(store.activeExecutionId) : undefined\n if (!segment || segment.terminal) return []\n return segment.events.filter((event) => (event.seq ?? 0) > afterSeq)\n}\n\n/**\n * Remove responding entries (threadId → startedAt) older than `ttlMs`, so a\n * dropped `end` broadcast can't leave a permanently-stuck dot. Mutates\n * `active` and returns the removed thread ids.\n */\nexport function pruneStaleThreads(active: Map<string, number>, now: number, ttlMs: number): string[] {\n const removed: string[] = []\n for (const [threadId, startedAt] of active) {\n if (now - startedAt > ttlMs) {\n active.delete(threadId)\n removed.push(threadId)\n }\n }\n return removed\n}\n\n// ── chat-turn lock record + fences ──────────────────────────────────────────\n\n/** Default lifetime of an unreleased lock. Long enough that a legitimately\n * slow sandbox turn never loses its guard mid-run; the way OUT of a wedge is\n * never the TTL but `reconcileStaleTurnLock` (in `/chat-routes`), which\n * probes the execution's actual state.\n *\n * Everything from here down is the LOCK, and it is fully KEPT. The sandbox\n * SDK ships no single-flight primitive — the session gateway is a read-only\n * fanout — so moving a product to the message lane changes nothing about\n * who is allowed to start a turn. */\nexport const TURN_LOCK_TTL_MS = 30 * 60 * 1000\n\n/** The stored single-flight lock. Field-compatible with the reference\n * consumer's `ChatTurnLock` so adoption is a swap, not a migration. */\nexport interface DurableTurnLock {\n workspaceId: string\n threadId: string\n scope: TurnLockScope\n executionId: string\n lockId: string\n startedAt: number\n expiresAt: number\n turnId?: string\n /** The turn released this lock, but a product-owned post-turn task (e.g.\n * file persistence reading the box) is still running, so the release is\n * parked on the lock until the task settles. Written only through the DO's\n * defer seam — the base package never sets it on its own. */\n releasePending?: boolean\n}\n\n/** Define input parameters required to acquire a turn-based lock in a workspace thread */\nexport interface TurnLockAcquireInput {\n workspaceId: string\n threadId: string\n scope: TurnLockScope\n executionId: string\n lockId: string\n turnId?: string\n}\n\n/** Resolve the result of attempting to acquire a turn lock indicating success or active lock status */\nexport type TurnLockAcquireResult =\n | { acquired: true; lock: DurableTurnLock }\n | { acquired: false; active: DurableTurnLock }\n\n/** Define input parameters required to release a turn lock in a specific workspace thread */\nexport interface TurnLockReleaseInput {\n workspaceId: string\n threadId: string\n scope: TurnLockScope\n executionId: string\n lockId: string\n}\n\n/** Fenced out-of-band release (stop button, stale-lock reconciliation). The\n * fences make it refuse a SUCCESSOR lock: `interruptedAt` must not precede\n * the lock's own start, and when either side names a turn, both must name\n * the same one. */\nexport interface TurnLockInterruptedReleaseInput {\n workspaceId: string\n threadId: string\n /** Try only this scope; omit to try workspace then thread. */\n scope?: TurnLockScope\n interruptedAt: number\n turnId?: string\n}\n\n/** `stored` is what the DO read from storage; expired locks are dead. */\nexport function activeTurnLock(stored: DurableTurnLock | undefined, now: number): DurableTurnLock | null {\n if (!stored) return null\n // Locks written before the scope field existed default to thread scope.\n const lock = stored.scope ? stored : { ...stored, scope: 'thread' as const }\n return lock.expiresAt > now ? lock : null\n}\n\n/** Create a durable turn lock object with timing and scope based on input parameters */\nexport function createTurnLock(input: TurnLockAcquireInput, now: number, ttlMs = TURN_LOCK_TTL_MS): DurableTurnLock {\n return {\n workspaceId: input.workspaceId,\n threadId: input.threadId,\n scope: input.scope,\n executionId: input.executionId,\n lockId: input.lockId,\n startedAt: now,\n expiresAt: now + ttlMs,\n ...(input.turnId ? { turnId: input.turnId } : {}),\n }\n}\n\n/** A cooperative release must present the lock's own identity — both the\n * execution and the lockId minted at acquire — so a retry of a PREVIOUS turn\n * can never release the current one. `lockId` is optional only for the DO's\n * internal terminal-event auto-release, which knows the execution but not\n * the caller-held lockId. */\nexport function turnLockMatchesRelease(\n active: DurableTurnLock,\n input: { executionId: string; lockId?: string },\n): boolean {\n if (active.executionId !== input.executionId) return false\n if (input.lockId && active.lockId !== input.lockId) return false\n return true\n}\n\n/**\n * The interrupted/stale release fence. `interruptedAt` is the instant the\n * releasing evidence was observed (the stop click, the stale-lock probe) —\n * a lock STARTED after that instant is a successor the evidence says nothing\n * about, so it survives. When the lock recorded a client turnId, the release\n * must name the same turn; a lock without one refuses a turn-specific\n * release (it cannot prove it is that turn).\n */\nexport function interruptedReleaseApplies(\n active: DurableTurnLock,\n input: { threadId: string; interruptedAt: number; turnId?: string },\n): boolean {\n if (active.threadId !== input.threadId) return false\n if (active.startedAt > input.interruptedAt) return false\n if (active.turnId) {\n if (active.turnId !== input.turnId) return false\n } else if (input.turnId) {\n return false\n }\n return true\n}\n\n// ── wire contract (adapter ↔ DO endpoint paths) ─────────────────────────────\n//\n// Kept byte-identical to the reference consumer's DO where an endpoint\n// existed there, so a product deletes its fork by re-pointing a binding, not\n// by re-speaking a protocol.\n\n/** Provide constant paths for managing chat turn streams and locks */\nexport const TURN_STREAM_PATHS = {\n broadcast: '/broadcast',\n lockAcquire: '/chat-turn-lock/acquire',\n lockRelease: '/chat-turn-lock/release',\n lockReleaseInterrupted: '/chat-turn-lock/release-interrupted',\n turnEventsAppend: '/turn-events/append',\n turnEventsRead: '/turn-events/read',\n turnStatusSet: '/turn-status/set',\n turnStatusGet: '/turn-status/get',\n scopeStatusSet: '/turn-scope/set',\n scopeRunningList: '/turn-scope/running',\n} as const\n\n/** Storage keys inside a DO instance. Exported for subclass coexistence —\n * a product extending the DO must not collide with these. */\nexport const TURN_STREAM_STORAGE_KEYS = {\n lock: 'chatTurnLock',\n activeThreads: 'activeThreads',\n turnStatus: 'turnStatus',\n turnScope: 'turnScopeIndex',\n turnEventPrefix: 'turnEvent:',\n} as const\n\n/** Zero-padded seq so DO storage `list({ prefix })` returns rows in replay\n * order without a sort. 10 digits holds any realistic turn. */\nexport function turnEventStorageKey(seq: number): string {\n return `${TURN_STREAM_STORAGE_KEYS.turnEventPrefix}${String(seq).padStart(10, '0')}`\n}\n","/**\n * `TurnStreamDO` — the shared Durable Object transport shell over the pure\n * core (`./core`). One class serves every channel family; the instance NAME\n * decides which endpoints a given instance ever sees:\n *\n * - **thread channel** (`${workspaceId}:${threadId}`) — the thread-scope lock\n * (KEPT), plus the live turn rebroadcast: WebSocket fanout and per-turn\n * segments with `sync`/`afterSeq` reconnect replay. That rebroadcast is\n * `@deprecated` for sandbox-backed interactive turns — the sandbox session\n * gateway already does it, browser-direct, when the turn is driven on the\n * message lane (`./core`'s header has the production measurement).\n * - **workspace channel** (`${workspaceId}`) — coarse sidebar signals\n * (`thread.activity` responding set, durable across eviction;\n * `thread.created` recent list) and the workspace-scope lock. KEPT: the\n * gateway is per-session and read-only, so it carries neither.\n * - **turn storage** (`turn:${turnId}`) — the durable `TurnEventStore` rows +\n * status for one buffered turn (replay survives DO eviction — this is what\n * graduates the vertical's `turnStore` from no-op). KEPT and load-bearing:\n * a DETACHED run never reaches the gateway, so this is the only way a\n * browser tails autonomous work.\n * - **scope index** (`scope:${scopeId}`) — the running-turn index backing\n * `TurnEventStore.listRunning` reconnect discovery. KEPT.\n *\n * The class is a PLAIN class over a structural {@link TurnStreamDOState} —\n * no `cloudflare:workers` import, so this package stays substrate-free and\n * the DO is unit-testable in Node. Cloudflare's `DurableObjectState`\n * satisfies the interface; a product binds it in wrangler by re-exporting:\n *\n * // worker entry\n * export { TurnStreamDO } from '@tangle-network/agent-app/turn-stream'\n *\n * Fan-out enumerates `state.getWebSockets()` (never an in-memory socket map)\n * and reads per-socket metadata from the serialized attachment, so it is\n * correct across WebSocket hibernation.\n *\n * Product extension (how the reference consumer keeps its Vault machinery\n * while deleting its fork): subclass and override\n * {@link TurnStreamDO.handleProductRequest} (extra POST endpoints),\n * {@link TurnStreamDO.shouldDeferLockRelease} /\n * {@link TurnStreamDO.completeDeferredLockRelease} (park a lock release\n * behind a product-owned post-turn task), and\n * {@link TurnStreamDO.productSyncEvents} (extra state replayed to a\n * late-connecting socket). Product storage keys must avoid\n * {@link TURN_STREAM_STORAGE_KEYS}.\n */\n\nimport { DEFAULT_RUNNING_TURN_LEASE_MS } from '../stream/turn-buffer'\n\nimport {\n ACTIVITY_TTL_MS,\n MAX_RECENT_CREATED,\n MAX_SEGMENT_EVENTS,\n TURN_LOCK_TTL_MS,\n TURN_STREAM_PATHS,\n TURN_STREAM_STORAGE_KEYS,\n activeTurnLock,\n appendSegmentEvent,\n createSegmentStore,\n createTurnLock,\n interruptedReleaseApplies,\n isTerminalRunEvent,\n pruneStaleThreads,\n replayActiveSegment,\n turnEventStorageKey,\n turnLockMatchesRelease,\n type DurableTurnLock,\n type TurnLockAcquireResult,\n type TurnLockScope,\n type TurnStreamEvent,\n} from './core'\n\n// ── structural Cloudflare surface ───────────────────────────────────────────\n\n/** The socket surface the DO touches. Cloudflare's hibernatable `WebSocket`\n * satisfies it. */\nexport interface TurnStreamSocket {\n send(data: string): void\n close(code?: number, reason?: string): void\n serializeAttachment(value: unknown): void\n deserializeAttachment(): unknown\n}\n\n/** The storage surface the DO touches (Cloudflare `DurableObjectStorage`\n * satisfies it structurally). `list` must return keys in ascending order —\n * the turn-event rows rely on it for replay order. */\nexport interface TurnStreamStorage {\n get<T = unknown>(key: string): Promise<T | undefined>\n put<T = unknown>(key: string, value: T): Promise<void>\n delete(key: string): Promise<boolean | void>\n list<T = unknown>(options: { prefix: string; start?: string }): Promise<Map<string, T>>\n}\n\n/** The `DurableObjectState` surface the DO uses. */\nexport interface TurnStreamDOState {\n storage: TurnStreamStorage\n acceptWebSocket(ws: TurnStreamSocket): void\n getWebSockets(): TurnStreamSocket[]\n}\n\ninterface SocketMeta {\n sessionId: string\n scope: 'thread' | 'workspace'\n /** Live fan-out is withheld until the client has run its initial `sync`\n * (see {@link TurnStreamDO.webSocketMessage}). This makes replay and live\n * delivery mutually exclusive per socket, so frames can neither interleave\n * out of order nor drop in the connect race. */\n synced: boolean\n}\n\n// ── request/response plumbing ───────────────────────────────────────────────\n\nfunction jsonResponse(body: unknown, status = 200): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { 'Content-Type': 'application/json' },\n })\n}\n\nfunction stringValue(value: unknown): string | null {\n return typeof value === 'string' && value.trim() ? value.trim() : null\n}\n\nasync function jsonBody(request: Request): Promise<Record<string, unknown> | null> {\n const body = await request.json().catch(() => null)\n return body && typeof body === 'object' && !Array.isArray(body) ? (body as Record<string, unknown>) : null\n}\n\n/** Per-scope running-turn index entry (one record per buffered turn). */\ninterface ScopeTurnEntry {\n status: 'running' | 'complete' | 'error'\n updatedAt: number\n}\n\n/** Cap on remembered turns per scope; oldest terminal entries are pruned\n * first, so a busy thread's index stays bounded without ever dropping a\n * still-running turn. */\nconst MAX_SCOPE_TURNS = 100\n\n// ── the DO ──────────────────────────────────────────────────────────────────\n\n/** Define options to override default TTL and event limits for TURN stream DO operations */\nexport interface TurnStreamDOOptions {\n /** Override {@link TURN_LOCK_TTL_MS}. */\n lockTtlMs?: number\n /** Override {@link MAX_SEGMENT_EVENTS}. */\n maxSegmentEvents?: number\n /** Override {@link ACTIVITY_TTL_MS}. */\n activityTtlMs?: number\n /** How long an unrenewed running turn remains discoverable. */\n runningTurnLeaseMs?: number\n}\n\n/** Manage per-turn segments and track active threads with durable event storage */\nexport class TurnStreamDO {\n protected readonly state: TurnStreamDOState\n protected readonly env: unknown\n protected readonly options: TurnStreamDOOptions\n\n // Thread channel: per-turn segments; only the active one is replayed.\n private segments = createSegmentStore()\n // Workspace channel: recent thread.created markers (in-memory, best-effort)\n // + durable responding set (threadId → startedAt) that survives eviction.\n private recentCreated: TurnStreamEvent[] = []\n private activeThreads: Map<string, number> | null = null\n\n constructor(state: TurnStreamDOState, env?: unknown, options: TurnStreamDOOptions = {}) {\n this.state = state\n this.env = env\n this.options = options\n }\n\n async fetch(request: Request): Promise<Response> {\n const url = new URL(request.url)\n\n if (request.headers.get('Upgrade') === 'websocket') {\n return this.handleWebSocketUpgrade(request, url)\n }\n\n if (request.method === 'POST') {\n switch (url.pathname) {\n case TURN_STREAM_PATHS.broadcast:\n return this.handleBroadcast(request)\n case TURN_STREAM_PATHS.lockAcquire:\n return this.handleLockAcquire(request)\n case TURN_STREAM_PATHS.lockRelease:\n return this.handleLockRelease(request)\n case TURN_STREAM_PATHS.lockReleaseInterrupted:\n return this.handleLockReleaseInterrupted(request)\n case TURN_STREAM_PATHS.turnEventsAppend:\n return this.handleTurnEventsAppend(request)\n case TURN_STREAM_PATHS.turnEventsRead:\n return this.handleTurnEventsRead(request)\n case TURN_STREAM_PATHS.turnStatusSet:\n return this.handleTurnStatusSet(request)\n case TURN_STREAM_PATHS.turnStatusGet:\n return this.handleTurnStatusGet()\n case TURN_STREAM_PATHS.scopeStatusSet:\n return this.handleScopeStatusSet(request)\n case TURN_STREAM_PATHS.scopeRunningList:\n return this.handleScopeRunningList()\n }\n }\n\n const product = await this.handleProductRequest(request, url)\n if (product) return product\n return request.method === 'POST'\n ? jsonResponse({ error: `Unknown turn-stream endpoint ${url.pathname}` }, 404)\n : new Response('Method not allowed', { status: 405 })\n }\n\n // ── product extension seam ────────────────────────────────────────────────\n\n /** Called for any request no base endpoint claimed (before the 404), so a\n * subclass adds product endpoints without touching base routing. Return\n * `null` to decline. */\n protected async handleProductRequest(_request: Request, _url: URL): Promise<Response | null> {\n return null\n }\n\n /** Consulted before any lock release (cooperative, interrupted, or the\n * terminal-event auto-release). Return `true` while a product-owned\n * post-turn task for `executionId` must keep the scope serialized (e.g.\n * file persistence still reading the box) — the release is then parked as\n * `releasePending` on the lock and completed via\n * {@link completeDeferredLockRelease}. Base: never defer. */\n protected async shouldDeferLockRelease(_executionId: string): Promise<boolean> {\n return false\n }\n\n /** Complete a release parked by {@link shouldDeferLockRelease}. A subclass\n * calls this when its deferred condition settles. */\n protected async completeDeferredLockRelease(executionId: string): Promise<boolean> {\n const active = await this.loadActiveLock()\n if (!active?.releasePending || active.executionId !== executionId) return false\n await this.state.storage.delete(TURN_STREAM_STORAGE_KEYS.lock)\n return true\n }\n\n /** Extra product state replayed to a socket during its `sync`, after the\n * base replay for its scope (e.g. an in-flight persistence status card).\n * Base: none. */\n protected async productSyncEvents(_scope: 'thread' | 'workspace', _meta: { sessionId: string }): Promise<TurnStreamEvent[]> {\n return []\n }\n\n // ── WebSocket channel ─────────────────────────────────────────────────────\n\n private handleWebSocketUpgrade(_request: Request, url: URL): Response {\n const sessionId = url.searchParams.get('sessionId')\n if (!sessionId) return new Response('Missing sessionId', { status: 400 })\n const scope = url.searchParams.get('scope') === 'workspace' ? 'workspace' : 'thread'\n\n const PairCtor = (globalThis as { WebSocketPair?: new () => Record<number, unknown> }).WebSocketPair\n if (!PairCtor) {\n return new Response('WebSocket upgrades require the Cloudflare runtime', { status: 501 })\n }\n const pair = new PairCtor()\n const client = pair[0]\n const server = pair[1] as TurnStreamSocket\n\n // Accept for hibernation and record routing metadata. No replay is pushed\n // here: sending before the client finishes the 101 handshake is racy\n // (frames can drop). The client issues a `sync` once open; replay happens\n // there.\n this.state.acceptWebSocket(server)\n server.serializeAttachment({ sessionId, scope, synced: false } satisfies SocketMeta)\n\n return new Response(null, { status: 101, webSocket: client } as ResponseInit)\n }\n\n /**\n * First (and only) client message after open: `{ type: 'sync', afterSeq }`.\n * Replays the current state for the socket's scope, then marks it `synced`\n * so live broadcasts start flowing. Because the DO is single-threaded, the\n * replay snapshot and the synced flip are atomic w.r.t. broadcasts — every\n * event reaches the socket exactly once, in order, via replay XOR live\n * fan-out.\n */\n async webSocketMessage(ws: TurnStreamSocket, message: string | ArrayBuffer): Promise<void> {\n const meta = ws.deserializeAttachment() as SocketMeta | null\n if (!meta || meta.synced) return\n let afterSeq = 0\n try {\n const parsed = JSON.parse(typeof message === 'string' ? message : '') as { type?: string; afterSeq?: number }\n if (parsed.type !== 'sync') return\n afterSeq = typeof parsed.afterSeq === 'number' ? parsed.afterSeq : 0\n } catch {\n return\n }\n\n if (meta.scope === 'workspace') {\n // Current responding state as synthetic `start` markers.\n const active = await this.loadActiveThreads()\n const removed = pruneStaleThreads(active, Date.now(), this.options.activityTtlMs ?? ACTIVITY_TTL_MS)\n if (removed.length > 0) await this.persistActiveThreads(active)\n for (const [threadId, startedAt] of active) {\n this.trySend(ws, {\n type: 'thread.activity',\n data: { threadId, phase: 'start', sessionId: meta.sessionId },\n timestamp: startedAt,\n })\n }\n // Recently-created threads for late joiners.\n for (const event of this.recentCreated) this.trySend(ws, event)\n } else {\n // The active, non-terminal turn segment from the cursor.\n for (const event of replayActiveSegment(this.segments, afterSeq)) {\n this.trySend(ws, event)\n }\n }\n for (const event of await this.productSyncEvents(meta.scope, { sessionId: meta.sessionId })) {\n this.trySend(ws, event)\n }\n\n ws.serializeAttachment({ ...meta, synced: true } satisfies SocketMeta)\n }\n\n webSocketClose(ws: TurnStreamSocket, code: number, reason: string): void {\n try {\n ws.close(code, reason)\n } catch {\n // Already closed.\n }\n }\n\n webSocketError(): void {\n // Socket dropped; getWebSockets() will no longer include it.\n }\n\n private trySend(ws: TurnStreamSocket, event: TurnStreamEvent): void {\n try {\n ws.send(JSON.stringify(event))\n } catch {\n // Client may not be ready yet, or is closing.\n }\n }\n\n // ── broadcast (live fanout + segments + activity) ─────────────────────────\n\n private async handleBroadcast(request: Request): Promise<Response> {\n const incoming = (await request.json()) as TurnStreamEvent\n const data = (incoming.data ?? {}) as Record<string, unknown>\n const sessionId = typeof data.sessionId === 'string' ? data.sessionId : undefined\n let outgoing: TurnStreamEvent = incoming\n\n if (incoming.type === 'thread.activity') {\n const threadId = typeof data.threadId === 'string' ? data.threadId : undefined\n if (threadId) {\n const active = await this.loadActiveThreads()\n if (data.phase === 'end') active.delete(threadId)\n else active.set(threadId, Date.now())\n await this.persistActiveThreads(active)\n }\n } else if (incoming.type === 'thread.created') {\n this.recentCreated.push(incoming)\n if (this.recentCreated.length > MAX_RECENT_CREATED) {\n this.recentCreated = this.recentCreated.slice(-MAX_RECENT_CREATED)\n }\n } else if (typeof data.executionId === 'string') {\n // Deprecated lane: per-turn rebroadcast. A product on the sandbox\n // message lane stops sending these and the tab reads the gateway\n // instead; the lock's cooperative release and the stale-lock\n // reconciler then own release (this terminal auto-release is a\n // convenience, not the only path out of a wedge).\n outgoing = appendSegmentEvent(\n this.segments,\n data.executionId,\n incoming,\n this.options.maxSegmentEvents ?? MAX_SEGMENT_EVENTS,\n )\n // A finished turn frees the channel's single-flight lock without waiting\n // for the worker's cooperative release — unless a product task defers it.\n if (isTerminalRunEvent(incoming.type)) {\n if (await this.shouldDeferLockRelease(data.executionId)) {\n await this.deferLockRelease({ executionId: data.executionId })\n } else {\n await this.releaseActiveLock({ executionId: data.executionId })\n }\n }\n }\n\n // Fan out to live sockets for this session. Enumerate accepted sockets so\n // fan-out is correct after WebSocket hibernation (no in-memory socket map).\n // Only `synced` sockets receive live frames; an un-synced socket is still\n // mid-handshake and will pick this event up in its `sync` replay snapshot.\n const message = JSON.stringify(outgoing)\n for (const ws of this.state.getWebSockets()) {\n const meta = ws.deserializeAttachment() as SocketMeta | null\n if (!meta?.synced) continue\n if (!sessionId || meta.sessionId === sessionId) {\n try {\n ws.send(message)\n } catch {\n // Dead/closing socket — getWebSockets() will stop returning it.\n }\n }\n }\n\n return new Response('OK', { status: 200 })\n }\n\n private async loadActiveThreads(): Promise<Map<string, number>> {\n if (this.activeThreads === null) {\n const stored = await this.state.storage.get<Record<string, number>>(TURN_STREAM_STORAGE_KEYS.activeThreads)\n this.activeThreads = new Map(Object.entries(stored ?? {}))\n }\n return this.activeThreads\n }\n\n private async persistActiveThreads(map: Map<string, number>): Promise<void> {\n await this.state.storage.put(TURN_STREAM_STORAGE_KEYS.activeThreads, Object.fromEntries(map))\n }\n\n // ── chat-turn lock ────────────────────────────────────────────────────────\n\n protected async loadActiveLock(now = Date.now()): Promise<DurableTurnLock | null> {\n const stored = await this.state.storage.get<DurableTurnLock>(TURN_STREAM_STORAGE_KEYS.lock)\n const lock = activeTurnLock(stored, now)\n if (stored && !lock) await this.state.storage.delete(TURN_STREAM_STORAGE_KEYS.lock)\n return lock\n }\n\n private async releaseActiveLock(input: { executionId: string; lockId?: string }): Promise<boolean> {\n const active = await this.loadActiveLock()\n if (!active || !turnLockMatchesRelease(active, input)) return false\n await this.state.storage.delete(TURN_STREAM_STORAGE_KEYS.lock)\n return true\n }\n\n /** Park a release on the lock itself; {@link completeDeferredLockRelease}\n * finishes it once the product's deferred condition settles. */\n private async deferLockRelease(input: { executionId: string; lockId?: string }): Promise<boolean> {\n const active = await this.loadActiveLock()\n if (!active || !turnLockMatchesRelease(active, input)) return false\n if (active.releasePending) return true\n await this.state.storage.put(TURN_STREAM_STORAGE_KEYS.lock, { ...active, releasePending: true })\n return true\n }\n\n private async handleLockAcquire(request: Request): Promise<Response> {\n const body = await jsonBody(request)\n const workspaceId = stringValue(body?.workspaceId)\n const threadId = stringValue(body?.threadId)\n const executionId = stringValue(body?.executionId)\n const lockId = stringValue(body?.lockId)\n const scope: TurnLockScope | null =\n body?.scope === 'workspace' ? 'workspace' : body?.scope === 'thread' ? 'thread' : null\n const turnId = stringValue(body?.turnId) ?? undefined\n if (!workspaceId || !threadId || !executionId || !lockId || !scope) {\n return jsonResponse({ error: 'Missing workspaceId, threadId, executionId, lockId, or scope' }, 400)\n }\n\n const active = await this.loadActiveLock()\n if (active) {\n return jsonResponse({ acquired: false, active } satisfies TurnLockAcquireResult, 409)\n }\n\n const lock = createTurnLock(\n { workspaceId, threadId, scope, executionId, lockId, ...(turnId ? { turnId } : {}) },\n Date.now(),\n this.options.lockTtlMs ?? TURN_LOCK_TTL_MS,\n )\n await this.state.storage.put(TURN_STREAM_STORAGE_KEYS.lock, lock)\n return jsonResponse({ acquired: true, lock } satisfies TurnLockAcquireResult)\n }\n\n private async handleLockRelease(request: Request): Promise<Response> {\n const body = await jsonBody(request)\n const executionId = stringValue(body?.executionId)\n const lockId = stringValue(body?.lockId)\n if (!executionId || !lockId) {\n return jsonResponse({ error: 'Missing executionId or lockId' }, 400)\n }\n\n if (await this.shouldDeferLockRelease(executionId)) {\n const deferred = await this.deferLockRelease({ executionId, lockId })\n return jsonResponse({ released: false, deferred })\n }\n const released = await this.releaseActiveLock({ executionId, lockId })\n return jsonResponse({ released })\n }\n\n private async handleLockReleaseInterrupted(request: Request): Promise<Response> {\n const body = await jsonBody(request)\n const threadId = stringValue(body?.threadId)\n const interruptedAt =\n typeof body?.interruptedAt === 'number' && Number.isFinite(body.interruptedAt) ? body.interruptedAt : undefined\n if (!threadId || interruptedAt === undefined) {\n return jsonResponse({ error: 'Missing threadId or interruptedAt' }, 400)\n }\n const turnId = stringValue(body?.turnId) ?? undefined\n\n const active = await this.loadActiveLock()\n if (!active) return jsonResponse({ released: false })\n // A deferred product task holds the scope even against an out-of-band\n // release — the box is still being read.\n if (await this.shouldDeferLockRelease(active.executionId)) {\n return jsonResponse({ released: false })\n }\n if (!interruptedReleaseApplies(active, { threadId, interruptedAt, ...(turnId ? { turnId } : {}) })) {\n return jsonResponse({ released: false })\n }\n await this.state.storage.delete(TURN_STREAM_STORAGE_KEYS.lock)\n return jsonResponse({ released: true })\n }\n\n // ── durable turn-event storage (TurnEventStore backing) ───────────────────\n\n private async handleTurnEventsAppend(request: Request): Promise<Response> {\n const body = await jsonBody(request)\n const events = Array.isArray(body?.events) ? body.events : null\n if (!events) return jsonResponse({ error: 'Missing events' }, 400)\n for (const row of events) {\n const seq = (row as { seq?: unknown }).seq\n const event = (row as { event?: unknown }).event\n if (typeof seq !== 'number' || !Number.isInteger(seq) || seq < 1 || typeof event !== 'string') {\n return jsonResponse({ error: 'Invalid turn-event row' }, 400)\n }\n }\n for (const row of events as Array<{ seq: number; event: string }>) {\n await this.state.storage.put(turnEventStorageKey(row.seq), row.event)\n }\n return jsonResponse({ appended: events.length })\n }\n\n private async handleTurnEventsRead(request: Request): Promise<Response> {\n const body = await jsonBody(request)\n const fromSeq = typeof body?.fromSeq === 'number' && Number.isFinite(body.fromSeq) ? Math.trunc(body.fromSeq) : 0\n const rows = await this.state.storage.list<string>({\n prefix: TURN_STREAM_STORAGE_KEYS.turnEventPrefix,\n start: turnEventStorageKey(fromSeq + 1),\n })\n const events: Array<{ seq: number; event: string }> = []\n for (const [key, event] of rows) {\n const seq = Number(key.slice(TURN_STREAM_STORAGE_KEYS.turnEventPrefix.length))\n if (Number.isFinite(seq) && seq > fromSeq) events.push({ seq, event })\n }\n return jsonResponse({ events })\n }\n\n private async handleTurnStatusSet(request: Request): Promise<Response> {\n const body = await jsonBody(request)\n const status = body?.status\n if (status !== 'running' && status !== 'complete' && status !== 'error') {\n return jsonResponse({ error: 'Invalid status' }, 400)\n }\n await this.state.storage.put(TURN_STREAM_STORAGE_KEYS.turnStatus, status)\n return jsonResponse({ ok: true })\n }\n\n private async handleTurnStatusGet(): Promise<Response> {\n const status = await this.state.storage.get<string>(TURN_STREAM_STORAGE_KEYS.turnStatus)\n return jsonResponse({ status: status ?? null })\n }\n\n // ── scope index (listRunning reconnect discovery) ─────────────────────────\n\n private async handleScopeStatusSet(request: Request): Promise<Response> {\n const body = await jsonBody(request)\n const turnId = stringValue(body?.turnId)\n const status = body?.status\n if (!turnId || (status !== 'running' && status !== 'complete' && status !== 'error')) {\n return jsonResponse({ error: 'Invalid scope-status request' }, 400)\n }\n const index =\n (await this.state.storage.get<Record<string, ScopeTurnEntry>>(TURN_STREAM_STORAGE_KEYS.turnScope)) ?? {}\n index[turnId] = { status, updatedAt: Date.now() }\n const entries = Object.entries(index)\n if (entries.length > MAX_SCOPE_TURNS) {\n // Prune oldest terminal entries first; a running turn is never dropped.\n const removable = entries\n .filter(([, entry]) => entry.status !== 'running')\n .sort((a, b) => a[1].updatedAt - b[1].updatedAt)\n for (const [id] of removable.slice(0, entries.length - MAX_SCOPE_TURNS)) {\n delete index[id]\n }\n }\n await this.state.storage.put(TURN_STREAM_STORAGE_KEYS.turnScope, index)\n return jsonResponse({ ok: true })\n }\n\n private async handleScopeRunningList(): Promise<Response> {\n const index =\n (await this.state.storage.get<Record<string, ScopeTurnEntry>>(TURN_STREAM_STORAGE_KEYS.turnScope)) ?? {}\n const cutoff = Date.now() - Math.max(\n 1,\n this.options.runningTurnLeaseMs ?? DEFAULT_RUNNING_TURN_LEASE_MS,\n )\n const running = Object.entries(index)\n .filter(([, entry]) => entry.status === 'running' && entry.updatedAt >= cutoff)\n .sort((a, b) => b[1].updatedAt - a[1].updatedAt)\n .map(([turnId]) => turnId)\n return jsonResponse({ running })\n }\n}\n","/**\n * Worker-side adapters over {@link TurnStreamDO}: the concrete implementations\n * of the chat vertical's `turnStore` and `turnLock` seams, the WebSocket\n * upgrade forwarder, the best-effort broadcast helpers, and an in-process\n * memory harness for tests and keyless local dev.\n *\n * Everything takes the namespace STRUCTURALLY ({@link TurnStreamNamespaceLike}\n * — Cloudflare's `DurableObjectNamespace` satisfies it), so nothing here\n * imports Cloudflare types and the same adapters run against the memory\n * harness in vitest.\n *\n * Live fanout is deliberately NOT a side effect of the turn-event store: the\n * store is keyed by turnId/scopeId while viewer sockets live on the\n * `${workspaceId}:${threadId}` channel, and only the product's per-turn\n * context knows both. Products wire the workspace signal helpers into\n * `createChatTurnRoutes`' `onEvent`.\n *\n * Which adapters are still the right answer (see `./core`'s header for the\n * production measurement behind this split):\n *\n * | adapter | status |\n * | ------------------------------------ | --------------------------------- |\n * | {@link createDurableTurnLock} | KEPT — no SDK equivalent |\n * | {@link reconcileStaleDurableTurnLock}| KEPT — no SDK equivalent |\n * | {@link createDurableObjectTurnEventStore} | KEPT — the DETACHED lane |\n * | {@link broadcastWorkspaceActivity} | KEPT — workspace signal |\n * | {@link broadcastThreadCreated} | KEPT — workspace signal |\n * | {@link createTurnStreamUpgradeHandler} | KEPT for the workspace channel |\n * | {@link broadcastTurnStreamEvent} | `@deprecated` for sandbox turns |\n */\n\nimport { reconcileStaleTurnLock, type ReconcileStaleTurnLockOptions } from '../chat-routes/stale-turn-lock'\nimport type { TurnEventStore, TurnStatus } from '../stream/turn-buffer'\nimport {\n TURN_STREAM_PATHS,\n scopeIndexChannelKey,\n threadChannelKey,\n turnLockChannelKey,\n turnStorageChannelKey,\n workspaceChannelKey,\n type DurableTurnLock,\n type TurnLockAcquireResult,\n type TurnLockReleaseInput,\n type TurnLockScope,\n type TurnStreamEvent,\n} from './core'\n\n// ── structural namespace ────────────────────────────────────────────────────\n\n/** Resolve a stub interface for handling fetch requests with optional initialization parameters */\nexport interface TurnStreamStubLike {\n fetch(input: Request | string, init?: RequestInit): Promise<Response>\n}\n\n/** The surface of `DurableObjectNamespace` the adapters use. */\nexport interface TurnStreamNamespaceLike {\n idFromName(name: string): unknown\n get(id: unknown): TurnStreamStubLike\n}\n\nconst INTERNAL_ORIGIN = 'https://turn-stream.internal'\n\nasync function postJson<T>(\n namespace: TurnStreamNamespaceLike,\n channelKey: string,\n path: string,\n body: unknown,\n): Promise<{ status: number; body: T }> {\n const stub = namespace.get(namespace.idFromName(channelKey))\n const response = await stub.fetch(`${INTERNAL_ORIGIN}${path}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n })\n return { status: response.status, body: (await response.json()) as T }\n}\n\nasync function postJsonOk<T>(\n namespace: TurnStreamNamespaceLike,\n channelKey: string,\n path: string,\n body: unknown,\n): Promise<T> {\n const result = await postJson<T>(namespace, channelKey, path, body)\n if (result.status !== 200) {\n throw new Error(`turn-stream ${path} failed with status ${result.status}`)\n }\n return result.body\n}\n\n// ── TurnEventStore adapter (the real turnStore) ─────────────────────────────\n\n/**\n * A {@link TurnEventStore} backed by {@link TurnStreamDO} storage — the\n * production implementation of `createChatTurnRoutes`' `turnStore` seam for\n * apps that don't run D1 for turn events (or want replay co-located with the\n * live channel). Each buffered turn lives on its own `turn:<turnId>` DO\n * instance; `listRunning` reconnect discovery rides a per-scope index\n * instance. Drops in wherever `createD1TurnEventStore(env.DB)` would.\n *\n * KEPT and load-bearing for AUTONOMOUS work. A detached run\n * (`dispatchPrompt({ detach: true })`, `driveTurn`) executes on the sandbox\n * run/stream lane, which publishes nothing to the session event bus — a\n * `SessionGatewayClient` attached to that session sees zero turn events\n * (measured: 0 of 71 / 0 of 527 / 0 of 408 across three session-id\n * strategies). So a browser that must tail a mission step, a queue job, or\n * an inbound-email review has no SDK path; it needs these durable rows plus\n * `runDetachedTurn` (`/chat-routes`). Nothing here is deprecated.\n */\nexport function createDurableObjectTurnEventStore(namespace: TurnStreamNamespaceLike): TurnEventStore {\n return {\n async append(turnId, events) {\n if (!events.length) return\n await postJsonOk(namespace, turnStorageChannelKey(turnId), TURN_STREAM_PATHS.turnEventsAppend, { events })\n },\n async read(turnId, fromSeq) {\n const body = await postJsonOk<{ events: Array<{ seq: number; event: string }> }>(\n namespace,\n turnStorageChannelKey(turnId),\n TURN_STREAM_PATHS.turnEventsRead,\n { fromSeq },\n )\n return body.events\n },\n async setStatus(turnId, status, scopeId) {\n await postJsonOk(namespace, turnStorageChannelKey(turnId), TURN_STREAM_PATHS.turnStatusSet, { status })\n if (scopeId) {\n await postJsonOk(namespace, scopeIndexChannelKey(scopeId), TURN_STREAM_PATHS.scopeStatusSet, {\n turnId,\n status,\n })\n }\n },\n async getStatus(turnId) {\n const body = await postJsonOk<{ status: TurnStatus | null }>(\n namespace,\n turnStorageChannelKey(turnId),\n TURN_STREAM_PATHS.turnStatusGet,\n {},\n )\n return body.status\n },\n async listRunning(scopeId) {\n const body = await postJsonOk<{ running: string[] }>(\n namespace,\n scopeIndexChannelKey(scopeId),\n TURN_STREAM_PATHS.scopeRunningList,\n {},\n )\n return body.running\n },\n }\n}\n\n// ── lock primitives (usable outside the route seam too) ─────────────────────\n\n/** Define input parameters required to acquire a durable turn lock in a workspace thread context */\nexport interface AcquireDurableTurnLockInput {\n workspaceId: string\n threadId: string\n scope: TurnLockScope\n executionId: string\n turnId?: string\n /** Supply to reclaim/retry with a stable id; default mints a UUID. */\n lockId?: string\n}\n\n/** Acquire a durable turn lock in the specified namespace with given input parameters */\nexport async function acquireDurableTurnLock(\n namespace: TurnStreamNamespaceLike,\n input: AcquireDurableTurnLockInput,\n): Promise<TurnLockAcquireResult> {\n const lockId = input.lockId ?? crypto.randomUUID()\n const key = turnLockChannelKey(input.workspaceId, input.threadId, input.scope)\n const result = await postJson<TurnLockAcquireResult>(namespace, key, TURN_STREAM_PATHS.lockAcquire, {\n ...input,\n lockId,\n })\n if (result.status !== 200 && result.status !== 409) {\n throw new Error(`turn-stream lock acquire failed with status ${result.status}`)\n }\n return result.body\n}\n\n/** Release a durable turn lock and indicate if the release was successful or deferred */\nexport async function releaseDurableTurnLock(\n namespace: TurnStreamNamespaceLike,\n input: TurnLockReleaseInput,\n): Promise<{ released: boolean; deferred?: boolean }> {\n const key = turnLockChannelKey(input.workspaceId, input.threadId, input.scope)\n return postJsonOk(namespace, key, TURN_STREAM_PATHS.lockRelease, input)\n}\n\n/** Define input parameters to release an interrupted durable turn lock in a workspace or thread */\nexport interface ReleaseInterruptedDurableTurnLockInput {\n workspaceId: string\n threadId: string\n /** Try only this scope; omit to try workspace then thread (a stop button\n * doesn't know which lane the wedged turn ran on). */\n scope?: TurnLockScope\n interruptedAt: number\n turnId?: string\n}\n\n/** Fenced out-of-band release — the DO refuses a successor lock (started\n * after `interruptedAt`) and a turnId mismatch. Returns whether any scope\n * released. */\nexport async function releaseInterruptedDurableTurnLock(\n namespace: TurnStreamNamespaceLike,\n input: ReleaseInterruptedDurableTurnLockInput,\n): Promise<boolean> {\n const scopes: readonly TurnLockScope[] = input.scope ? [input.scope] : ['workspace', 'thread']\n for (const scope of scopes) {\n const key = turnLockChannelKey(input.workspaceId, input.threadId, scope)\n const result = await postJsonOk<{ released: boolean }>(namespace, key, TURN_STREAM_PATHS.lockReleaseInterrupted, {\n threadId: input.threadId,\n interruptedAt: input.interruptedAt,\n ...(input.turnId ? { turnId: input.turnId } : {}),\n })\n if (result.released) return true\n }\n return false\n}\n\n// ── stale-lock reconciliation against the DO ────────────────────────────────\n\n/** Define options to reconcile stale durable turn locks with context, namespace, workspace, and active lock details */\nexport interface ReconcileStaleDurableTurnLockOptions\n extends Pick<\n ReconcileStaleTurnLockOptions,\n 'probeSandbox' | 'probeSession' | 'graceMs' | 'terminalGraceMs' | 'context' | 'log' | 'now'\n > {\n namespace: TurnStreamNamespaceLike\n workspaceId: string\n threadId: string\n /** The lock the acquire attempt was refused on. */\n active: DurableTurnLock\n}\n\n/**\n * `/chat-routes`' `reconcileStaleTurnLock` policy wired to the DO: the\n * product supplies the probes (which box, what its session says), the policy\n * decides, and a release lands as a FENCED interrupted release —\n * `interruptedAt` is the policy's `fence.observedAt`, so a successor lock\n * acquired while the probes were in flight survives by construction.\n */\nexport async function reconcileStaleDurableTurnLock(\n options: ReconcileStaleDurableTurnLockOptions,\n): Promise<{ released: boolean; diagnostics: Record<string, unknown> }> {\n const { namespace, workspaceId, threadId, active, ...policy } = options\n return reconcileStaleTurnLock({\n ...policy,\n lockStartedAt: active.startedAt,\n release: (fence) =>\n releaseInterruptedDurableTurnLock(namespace, {\n workspaceId,\n threadId: active.threadId,\n scope: active.scope,\n interruptedAt: fence.observedAt,\n ...(active.turnId ? { turnId: active.turnId } : {}),\n }),\n })\n}\n\n// ── ChatTurnLock adapter (the turnLock seam) ────────────────────────────────\n\n/** The subset of `ChatTurnProduceArgs` the lock adapter reads — structural,\n * so this module needs no import from `/chat-routes/turn-routes` (which\n * would drag the `agent-runtime` peer into every `/turn-stream` consumer). */\nexport interface TurnLockSeamArgs<TContext> {\n identity: { tenantId: string; sessionId: string }\n executionId: string\n context: TContext\n body: { turnId?: string }\n}\n\n/** Verdict shape of the vertical's `turnLock.acquire`. */\nexport type TurnLockSeamResult =\n | { acquired: true; handle?: unknown }\n | { acquired: false; response: Response }\n\n/** Define options for creating a durable turn lock with customizable scope and identification methods */\nexport interface CreateDurableTurnLockOptions<TContext> {\n namespace: TurnStreamNamespaceLike\n /** Which lane serializes this turn: `'workspace'` (shared sandbox — one\n * turn per workspace) or `'thread'` (router lane — one turn per thread). */\n scopeOf(args: TurnLockSeamArgs<TContext>): TurnLockScope\n /** Override the execution id the lock records (e.g. a follow-up reclaiming\n * the execution it already dispatched). Default: the turn's own. */\n lockExecutionIdOf?(args: TurnLockSeamArgs<TContext>): string | undefined\n /** Client turn id recorded on the lock (fences interrupted releases to the\n * right turn). Default: the request body's `turnId`. */\n clientTurnIdOf?(args: TurnLockSeamArgs<TContext>): string | undefined\n /**\n * Attempt stale-lock recovery after a refused acquire. Return whether the\n * held lock was released (the adapter then retries the acquire once).\n * Products wire {@link reconcileStaleDurableTurnLock} with their sandbox +\n * session probes here. Omit → a refused acquire is final.\n */\n reconcile?(\n args: TurnLockSeamArgs<TContext>,\n active: DurableTurnLock,\n ): Promise<{ released: boolean; diagnostics?: Record<string, unknown> }>\n /** Build the refusal `Response`. Default: a 409 with the shared body shape\n * (`code`, `message`, lock identity + age, reconcile diagnostics). */\n onRefused?(active: DurableTurnLock, diagnostics: Record<string, unknown> | undefined): Response\n}\n\nfunction defaultRefusalResponse(\n active: DurableTurnLock,\n diagnostics: Record<string, unknown> | undefined,\n): Response {\n const workspaceConflict = active.scope === 'workspace'\n return Response.json(\n {\n error: {\n code: workspaceConflict ? 'workspace_turn_in_flight' : 'chat_turn_in_flight',\n message: workspaceConflict\n ? 'A chat turn is already running for this workspace. Wait for it to finish before starting another.'\n : 'A chat turn is already running for this thread. Reconnect to the active response or wait for it to finish.',\n threadId: active.threadId,\n executionId: active.executionId,\n ...(active.turnId ? { turnId: active.turnId } : {}),\n lockStartedAt: active.startedAt,\n lockAgeMs: Date.now() - active.startedAt,\n ...(diagnostics ? { diagnostics } : {}),\n },\n },\n { status: 409 },\n )\n}\n\n/**\n * The vertical's `turnLock` seam on the shared DO: dual-scope single-flight\n * acquire (with one reconcile-then-retry pass when the product supplies a\n * stale-lock reconciler) and cooperative release on settle. The returned\n * object satisfies `createChatTurnRoutes`' `ChatTurnLock<TContext>`\n * structurally.\n */\nexport function createDurableTurnLock<TContext>(options: CreateDurableTurnLockOptions<TContext>): {\n acquire(args: TurnLockSeamArgs<TContext>): Promise<TurnLockSeamResult>\n release(handle: unknown): Promise<void>\n} {\n return {\n async acquire(args) {\n const workspaceId = args.identity.tenantId\n const threadId = args.identity.sessionId\n const scope = options.scopeOf(args)\n const executionId = options.lockExecutionIdOf?.(args) ?? args.executionId\n const turnId = options.clientTurnIdOf ? options.clientTurnIdOf(args) : args.body.turnId\n const input: AcquireDurableTurnLockInput = {\n workspaceId,\n threadId,\n scope,\n executionId,\n ...(turnId ? { turnId } : {}),\n }\n\n let acquired = await acquireDurableTurnLock(options.namespace, input)\n let diagnostics: Record<string, unknown> | undefined\n if (!acquired.acquired && options.reconcile) {\n const reconciled = await options.reconcile(args, acquired.active)\n diagnostics = reconciled.diagnostics\n if (reconciled.released) {\n acquired = await acquireDurableTurnLock(options.namespace, input)\n }\n }\n if (!acquired.acquired) {\n return {\n acquired: false,\n response: (options.onRefused ?? defaultRefusalResponse)(acquired.active, diagnostics),\n }\n }\n const handle: TurnLockReleaseInput = {\n workspaceId,\n threadId,\n scope,\n executionId,\n lockId: acquired.lock.lockId,\n }\n return { acquired: true, handle }\n },\n async release(handle) {\n if (!handle) return\n await releaseDurableTurnLock(options.namespace, handle as TurnLockReleaseInput)\n },\n }\n}\n\n// ── broadcast helpers (products wire these into onEvent) ────────────────────\n\n/**\n * DEPRECATED for sandbox-backed interactive turns (drive on the session-message\n * lane + `SessionGatewayClient` instead) — fan a turn event out to the\n * per-thread channel. `executionId` groups events\n * into a per-turn segment with a monotonic seq, so a reconnecting client\n * replays only the active turn and resumes from a cursor. Callers MUST await\n * (the DO assigns seq on arrival — emission order matters); failures are\n * swallowed (fanout is best-effort and never breaks chat delivery).\n *\n * @deprecated For a SANDBOX-backed interactive turn this re-broadcasts events\n * the sandbox platform already fans out, at the cost of a worker hop. Drive\n * the turn on the session-MESSAGE lane instead —\n * `box.createSession({ sessionId, backend })` then\n * `box.session(id).sendMessage({ parts: [{ type: 'text', text }] })` — and let\n * the tab attach with `box.mintScopedToken({ scope: 'session', sessionId,\n * runtimeSessionId })` + `SessionGatewayClient`\n * (`@tangle-network/sandbox/session-gateway`). Measured on production\n * (4 arms, SDK 0.12.0): turns driven with `box.streamPrompt()` delivered\n * 0 of 71 / 0 of 527 / 0 of 408 turn events to a gateway client, because\n * `POST /agents/run/stream` publishes nothing to the session event bus;\n * the message lane delivered 297 of 297.\n *\n * Two things this deprecation does NOT cover, both still supported:\n * DETACHED runs (no gateway fanout at all — keep `runDetachedTurn` over the\n * durable turn-event rows) and the per-workspace signals\n * ({@link broadcastWorkspaceActivity} / {@link broadcastThreadCreated}).\n * A SANDBOX-FREE copilot that wants a second viewer should use `/stream`'s\n * `replayTurnEvents` (`GET /chat/stream/:turnId`), which follows a running\n * turn from a cursor without a second broadcast fabric.\n *\n * Kept for back-compat; removal is a major-version change.\n */\nexport async function broadcastTurnStreamEvent(\n namespace: TurnStreamNamespaceLike,\n input: {\n workspaceId: string\n threadId: string\n executionId: string\n event: { type: string; data?: Record<string, unknown> }\n },\n): Promise<void> {\n try {\n await postJson(namespace, threadChannelKey(input.workspaceId, input.threadId), TURN_STREAM_PATHS.broadcast, {\n type: input.event.type,\n timestamp: Date.now(),\n data: {\n ...(input.event.data ?? {}),\n workspaceId: input.workspaceId,\n threadId: input.threadId,\n sessionId: input.threadId,\n executionId: input.executionId,\n },\n } satisfies TurnStreamEvent)\n } catch {\n // Best-effort.\n }\n}\n\n/** Coarse per-workspace marker that a thread's turn started / ended — drives\n * a sidebar \"agent responding\" indicator subscribed once per workspace. */\nexport async function broadcastWorkspaceActivity(\n namespace: TurnStreamNamespaceLike,\n workspaceId: string,\n threadId: string,\n phase: 'start' | 'end',\n): Promise<void> {\n try {\n await postJson(namespace, workspaceChannelKey(workspaceId), TURN_STREAM_PATHS.broadcast, {\n type: 'thread.activity',\n timestamp: Date.now(),\n data: { threadId, phase, workspaceId, sessionId: workspaceId },\n } satisfies TurnStreamEvent)\n } catch {\n // Best-effort: the indicator is non-critical UI.\n }\n}\n\n/** Per-workspace marker that a new thread was created, so an already-open\n * history list prepends it without a reload. */\nexport async function broadcastThreadCreated(\n namespace: TurnStreamNamespaceLike,\n workspaceId: string,\n thread: { threadId: string; title: string },\n): Promise<void> {\n try {\n await postJson(namespace, workspaceChannelKey(workspaceId), TURN_STREAM_PATHS.broadcast, {\n type: 'thread.created',\n timestamp: Date.now(),\n data: { threadId: thread.threadId, title: thread.title, workspaceId, sessionId: workspaceId },\n } satisfies TurnStreamEvent)\n } catch {\n // Best-effort: the loader reconciles on the next revalidation.\n }\n}\n\n// ── WebSocket upgrade forwarder (worker entry) ──────────────────────────────\n\n/** Represent success or failure of a TURN stream upgrade authorization with optional response data */\nexport type TurnStreamUpgradeAuthorization = { ok: true } | { ok: false; response: Response }\n\n/** Define options for creating a TURN stream upgrade handler including namespace, path, and authorization logic */\nexport interface CreateTurnStreamUpgradeHandlerOptions {\n namespace: TurnStreamNamespaceLike\n /** The worker route serving the stream. Default `/api/session-stream`. */\n path?: string\n /** Viewer access check (session cookie → workspace membership). */\n authorize(\n request: Request,\n target: { workspaceId: string; threadId: string | null },\n ): Promise<TurnStreamUpgradeAuthorization>\n}\n\n/**\n * The worker-entry WebSocket forwarder. Call BEFORE the app router (a router\n * loader cannot return a 101); returns `null` for requests that are not a\n * WebSocket upgrade on the configured path.\n *\n * GET {path}?workspaceId=... → workspace channel (sidebar)\n * GET {path}?workspaceId=...&threadId=… → thread channel (turn resume)\n *\n * After the 101, the client sends `{type:'sync', afterSeq}` and receives the\n * replay-then-live stream (see {@link TurnStreamDO.webSocketMessage}).\n *\n * NOT deprecated — the workspace variant (no `threadId`) is the canonical\n * transport for the per-workspace signals, which the session gateway cannot\n * carry (it is per-session and read-only). The THREAD variant is the\n * deprecated half: for a sandbox-backed interactive turn the tab should\n * attach to the session gateway directly instead of to this socket. See\n * {@link broadcastTurnStreamEvent} for the measurement and the replacement\n * wiring.\n */\nexport function createTurnStreamUpgradeHandler(\n options: CreateTurnStreamUpgradeHandlerOptions,\n): (request: Request) => Promise<Response | null> {\n const path = options.path ?? '/api/session-stream'\n return async (request) => {\n const url = new URL(request.url)\n if (url.pathname !== path || request.headers.get('Upgrade') !== 'websocket') return null\n\n const workspaceId = url.searchParams.get('workspaceId')\n const threadId = url.searchParams.get('threadId')\n if (!workspaceId) return new Response('Missing workspaceId', { status: 400 })\n\n const auth = await options.authorize(request, { workspaceId, threadId })\n if (!auth.ok) return auth.response\n\n // threadId present → per-thread turn channel; absent → workspace channel.\n const key = threadId ? threadChannelKey(workspaceId, threadId) : workspaceChannelKey(workspaceId)\n const sessionId = threadId ?? workspaceId\n const stub = options.namespace.get(options.namespace.idFromName(key))\n\n const forwardUrl = new URL(request.url)\n forwardUrl.searchParams.set('sessionId', sessionId)\n forwardUrl.searchParams.set('scope', threadId ? 'thread' : 'workspace')\n return stub.fetch(new Request(forwardUrl, request))\n }\n}\n","/**\n * In-process turn-stream harness: a fake namespace that routes `idFromName`\n * to REAL {@link TurnStreamDO} instances over an in-memory storage/socket\n * state. The adapters and the DO run their production code paths — only the\n * Cloudflare runtime (isolation, hibernation, real sockets) is simulated.\n *\n * For vitest composition tests and keyless local dev (the same role\n * `createMemoryTurnEventStore` plays for the D1 store). Not for production:\n * state is per-process and evaporates on restart.\n */\n\nimport { TurnStreamDO, type TurnStreamDOOptions, type TurnStreamDOState, type TurnStreamSocket, type TurnStreamStorage } from './do'\nimport type { TurnStreamNamespaceLike, TurnStreamStubLike } from './adapters'\n\nfunction createMemoryStorage(): TurnStreamStorage {\n const map = new Map<string, unknown>()\n return {\n async get<T>(key: string) {\n return map.get(key) as T | undefined\n },\n async put(key, value) {\n map.set(key, value)\n },\n async delete(key) {\n return map.delete(key)\n },\n async list<T>({ prefix, start }: { prefix: string; start?: string }) {\n const keys = [...map.keys()]\n .filter((key) => key.startsWith(prefix) && (start === undefined || key >= start))\n .sort()\n return new Map(keys.map((key) => [key, map.get(key) as T]))\n },\n }\n}\n\n/** A test-side viewer socket: records frames sent by the DO and lets the\n * test drive the `sync` handshake. */\nexport interface MemoryTurnStreamSocket extends TurnStreamSocket {\n readonly frames: string[]\n readonly closed: boolean\n}\n\n/** Define an in-memory channel for streaming turn-based data with viewer socket connection support */\nexport interface MemoryTurnStreamChannel {\n /** Attach a viewer socket to this channel (bypasses the HTTP 101 — the\n * upgrade handshake is Cloudflare-runtime-only) and run its `sync`. */\n connect(input: { sessionId: string; scope: 'thread' | 'workspace'; afterSeq?: number }): Promise<MemoryTurnStreamSocket>\n readonly instance: TurnStreamDO\n}\n\n/** Provide an interface to manage channels and namespaces for memory-based turn stream testing */\nexport interface MemoryTurnStreamHarness {\n namespace: TurnStreamNamespaceLike\n /** The channel (creating its DO instance if needed) for a channel key —\n * e.g. `threadChannelKey(ws, thread)` — to connect test viewers. */\n channel(name: string): MemoryTurnStreamChannel\n}\n\ninterface MemoryInstance {\n instance: TurnStreamDO\n sockets: MemoryTurnStreamSocket[]\n}\n\n/**\n * Build the harness. `createInstance` lets a product test run its own\n * `TurnStreamDO` subclass through the same wiring.\n */\nexport function createMemoryTurnStreamHarness(\n createInstance: (state: TurnStreamDOState) => TurnStreamDO = (state) => new TurnStreamDO(state),\n _options: TurnStreamDOOptions = {},\n): MemoryTurnStreamHarness {\n const instances = new Map<string, MemoryInstance>()\n\n function ensure(name: string): MemoryInstance {\n let entry = instances.get(name)\n if (!entry) {\n const sockets: MemoryTurnStreamSocket[] = []\n const state: TurnStreamDOState = {\n storage: createMemoryStorage(),\n acceptWebSocket: (ws) => sockets.push(ws as MemoryTurnStreamSocket),\n getWebSockets: () => sockets.filter((ws) => !ws.closed),\n }\n entry = { instance: createInstance(state), sockets }\n instances.set(name, entry)\n }\n return entry\n }\n\n const namespace: TurnStreamNamespaceLike = {\n idFromName: (name) => name,\n get(id): TurnStreamStubLike {\n const entry = ensure(String(id))\n return {\n fetch: (input, init) =>\n entry.instance.fetch(typeof input === 'string' ? new Request(input, init) : input),\n }\n },\n }\n\n return {\n namespace,\n channel(name) {\n const entry = ensure(name)\n return {\n instance: entry.instance,\n async connect({ sessionId, scope, afterSeq = 0 }) {\n let attachment: unknown = null\n const frames: string[] = []\n let closed = false\n const socket: MemoryTurnStreamSocket = {\n get frames() {\n return frames\n },\n get closed() {\n return closed\n },\n send(data) {\n if (closed) throw new Error('socket closed')\n frames.push(data)\n },\n close() {\n closed = true\n },\n serializeAttachment(value) {\n attachment = value\n },\n deserializeAttachment() {\n return attachment\n },\n }\n socket.serializeAttachment({ sessionId, scope, synced: false })\n entry.sockets.push(socket)\n await entry.instance.webSocketMessage(socket, JSON.stringify({ type: 'sync', afterSeq }))\n return socket\n },\n }\n },\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/web-react/index.js
CHANGED
|
@@ -78,9 +78,9 @@ import {
|
|
|
78
78
|
useSmoothText,
|
|
79
79
|
useThinkingSeconds,
|
|
80
80
|
waterfallLayout
|
|
81
|
-
} from "../chunk-
|
|
81
|
+
} from "../chunk-D3GK2IPA.js";
|
|
82
82
|
import "../chunk-FBVLEGEG.js";
|
|
83
|
-
import "../chunk-
|
|
83
|
+
import "../chunk-YOSSGDIG.js";
|
|
84
84
|
import {
|
|
85
85
|
EvidenceLineageTable,
|
|
86
86
|
ExceptionList,
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/stream/turn-identity.ts","../src/stream/turn-buffer.ts"],"sourcesContent":["import type { JsonRecord } from './stream-normalizer'\n\n/** Define the structure of a chat message stored for a specific conversation turn */\nexport interface PersistedChatMessageForTurn {\n id: string\n role: 'user' | 'assistant' | 'system' | 'tool'\n content: string\n parts: Array<Record<string, unknown>> | null\n}\n\n/** Represent a chat turn with resolved user message insertion and prior message context */\nexport interface ResolvedChatTurn {\n turnIndex: number\n shouldInsertUserMessage: boolean\n priorMessages: PersistedChatMessageForTurn[]\n userParts: JsonRecord[]\n /** The id of the user row this turn REUSES (retry dedup), when one was\n * found. Absent on the insert path, where no row exists yet.\n *\n * The reused row is deliberately EXCLUDED from `priorMessages` (it is this\n * turn's own user message, not prior context), so this field is the only\n * way a caller can name it. */\n reusedUserMessageId?: string\n}\n\n/** Normalize and validate a client turn ID string ensuring it meets format and length requirements */\nexport function normalizeClientTurnId(value: unknown): string | undefined {\n if (value === undefined || value === null) return undefined\n if (typeof value !== 'string') throw new Error('turnId must be a string')\n const trimmed = value.trim()\n if (!trimmed) throw new Error('turnId must not be blank')\n if (trimmed.length > 160) throw new Error('turnId is too long')\n if (!/^[A-Za-z0-9:_-]+$/.test(trimmed)) {\n throw new Error('turnId contains unsupported characters')\n }\n return trimmed\n}\n\n/** Build an array of text parts with optional turn ID for user input */\nexport function buildUserTextParts(text: string, turnId: string | undefined): JsonRecord[] {\n const part: JsonRecord = { type: 'text', text }\n if (turnId) part.turnId = turnId\n return [part]\n}\n\n/** Resolve whether a message contains any part with the specified turn ID */\nexport function messageHasTurnId(message: PersistedChatMessageForTurn, turnId: string): boolean {\n for (const part of message.parts ?? []) {\n if (part && typeof part === 'object' && String(part.turnId ?? '') === turnId) {\n return true\n }\n }\n return false\n}\n\n/** Resolve a chat turn by determining message reuse and constructing user message parts */\nexport function resolveChatTurn(input: {\n existingMessages: PersistedChatMessageForTurn[]\n userContent: string\n turnId?: string\n /** True when the thread has a turn still RUNNING in the turn-event buffer\n * (`turnStore.listRunning(threadId)`).\n *\n * Without incremental persistence the trailing row of a thread mid-turn is\n * always the user row, so the content fallback below could assume it. With\n * incremental persistence the assistant row lands seconds into the turn, so\n * a retry of that same turn finds an ASSISTANT row trailing and would\n * insert a duplicate user row.\n *\n * This flag is the discriminator that keeps both cases right, and it needs\n * no new state: an assistant row trailing a turn that is still running is\n * that turn's in-flight draft (walk past it — this is a retry), whereas an\n * assistant row trailing a SETTLED turn is a completed answer (stop — the\n * user genuinely repeated a message and deserves a new turn). */\n hasRunningTurn?: boolean\n}): ResolvedChatTurn {\n const { existingMessages, userContent, turnId } = input\n const reusableIndex = findReusableUserMessageIndex(\n existingMessages,\n userContent,\n turnId,\n input.hasRunningTurn === true,\n )\n if (reusableIndex >= 0) {\n // Guarded read: `id` is typed `string`, but these rows come from a product\n // store the shell does not validate, so an adapter that omits it must\n // surface as \"no id\" rather than as the string \"undefined\".\n const reusedId = existingMessages[reusableIndex]?.id\n return {\n turnIndex: countUserMessages(existingMessages.slice(0, reusableIndex)),\n shouldInsertUserMessage: false,\n priorMessages: existingMessages.slice(0, reusableIndex),\n userParts: buildUserTextParts(userContent, turnId),\n ...(typeof reusedId === 'string' && reusedId ? { reusedUserMessageId: reusedId } : {}),\n }\n }\n\n return {\n turnIndex: countUserMessages(existingMessages),\n shouldInsertUserMessage: true,\n priorMessages: existingMessages,\n userParts: buildUserTextParts(userContent, turnId),\n }\n}\n\nfunction findReusableUserMessageIndex(\n messages: PersistedChatMessageForTurn[],\n userContent: string,\n turnId: string | undefined,\n hasRunningTurn: boolean,\n): number {\n if (turnId) {\n for (let index = messages.length - 1; index >= 0; index -= 1) {\n const message = messages[index]\n if (message?.role === 'user' && messageHasTurnId(message, turnId)) return index\n }\n }\n\n // Content fallback for a client that sends no turnId. Only the trailing rows\n // of a turn still RUNNING are walked past (they are that turn's incrementally\n // persisted assistant draft); a settled assistant row still ends the scan, so\n // a user who genuinely repeats a message gets a new turn exactly as before.\n let index = messages.length - 1\n if (hasRunningTurn) {\n while (index >= 0 && messages[index]?.role === 'assistant') index -= 1\n }\n const latest = index >= 0 ? messages[index] : undefined\n if (latest?.role === 'user' && latest.content === userContent) return index\n\n return -1\n}\n\nfunction countUserMessages(messages: PersistedChatMessageForTurn[]): number {\n return messages.filter((message) => message.role === 'user').length\n}\n","/**\n * Resumable chat turns — the router-path answer to \"streams resume on\n * disconnect\" (issue #27). A turn's loop events are teed into a store as they\n * stream; the turn keeps running under `ctx.waitUntil` when the client drops;\n * a reconnecting client replays the buffered tail by sequence number and\n * keeps following until the turn completes.\n *\n * POST /chat/stream → pumpBufferedTurn(...) + live NDJSON\n * GET /chat/stream/:turnId → replayTurnEvents({ fromSeq }) → NDJSON\n *\n * Storage is a structural seam ({@link TurnEventStore}); a D1 implementation\n * ships here because that's what Cloudflare products have (KV is unsuitable:\n * eventually consistent cross-isolate). Per-token deltas would mean hundreds\n * of rows per turn, so consecutive text/reasoning deltas are coalesced within\n * a flush window before they are persisted — replay yields slightly chunkier\n * deltas with identical concatenation.\n */\n\nexport type TurnStatus = 'running' | 'complete' | 'error'\n\n/** Represent a buffered turn event with a sequence number and serialized event data */\nexport interface BufferedTurnEvent {\n seq: number\n /** The serialized event line (JSON string, no trailing newline). */\n event: string\n}\n\n/** Manage and query turn events and their lifecycle statuses within a scoped event store */\nexport interface TurnEventStore {\n append(turnId: string, events: BufferedTurnEvent[]): Promise<void>\n read(turnId: string, fromSeq: number): Promise<BufferedTurnEvent[]>\n /** Record turn lifecycle. `scopeId` (a thread/session id) is optional and lets\n * {@link TurnEventStore.listRunning} rediscover this turn after a client reload\n * loses the turnId; stores that don't track scope ignore it. */\n setStatus(turnId: string, status: TurnStatus, scopeId?: string): Promise<void>\n getStatus(turnId: string): Promise<TurnStatus | null>\n /** Running turnIds for a scope, newest first — so a reloaded client (clientRunId\n * lost) can find and resume the in-flight turn. Optional: a store records it\n * only if `setStatus` was given a `scopeId`. */\n listRunning?(scopeId: string): Promise<string[]>\n}\n\n// ── coalescing ────────────────────────────────────────────────────────────\n\ntype AnyRecord = Record<string, unknown>\n\nfunction deltaTypeOf(ev: unknown): 'text' | 'reasoning' | null {\n const e = ev as AnyRecord | null\n if (!e || typeof e !== 'object') return null\n const inner = (e.kind === 'event' ? (e.event as AnyRecord | undefined) : e) as AnyRecord | undefined\n if (!inner || typeof inner !== 'object') return null\n if ((inner.type === 'text' || inner.type === 'reasoning') && typeof inner.text === 'string') {\n return inner.type\n }\n return null\n}\n\n/** Merge consecutive text/reasoning deltas of the same type into one event.\n * Concatenation-preserving: replaying the coalesced stream produces the same\n * accumulated text as the original. */\nexport function coalesceDeltas(events: unknown[]): unknown[] {\n const out: unknown[] = []\n for (const ev of events) {\n const type = deltaTypeOf(ev)\n const prev = out[out.length - 1]\n if (type && prev && deltaTypeOf(prev) === type) {\n const read = (x: unknown): AnyRecord =>\n ((x as AnyRecord).kind === 'event' ? (x as AnyRecord).event : x) as AnyRecord\n const merged = JSON.parse(JSON.stringify(prev)) as AnyRecord\n read(merged).text = String(read(prev).text) + String(read(ev).text)\n out[out.length - 1] = merged\n continue\n }\n out.push(ev)\n }\n return out\n}\n\nfunction asPartUpdate(ev: unknown): { partId: unknown; delta: unknown } | null {\n const e = ev as AnyRecord | null\n if (!e || typeof e !== 'object' || e.type !== 'message.part.updated') return null\n const data = e.data as AnyRecord | undefined\n if (!data || typeof data !== 'object') return null\n const part = data.part as AnyRecord | undefined\n const partId = part?.id ?? data.partId ?? part?.partId ?? null\n return { partId, delta: data.delta }\n}\n\n/**\n * Coalesce consecutive `message.part.updated` deltas for the SAME part into one\n * event. agent-runtime products stream `ChatStreamEvent` NDJSON\n * (`{type:'message.part.updated', data:{part, delta}}`); pumped through the\n * buffer with the default tool-loop coalescer, every per-token delta persists as\n * its own row because that coalescer never recognizes the shape. Pass this as\n * {@link PumpBufferedTurnOptions.coalesce} instead.\n *\n * Concatenation-preserving for BOTH consumer styles: the merged event keeps the\n * LATEST event's `data.part` (already the cumulative accumulation) and sets\n * `data.delta` to the concatenation of the merged deltas, so a client that\n * appends `delta` and one that reads the cumulative `part` both reconstruct the\n * identical final text.\n */\nexport function coalesceChatStreamEvents(events: unknown[]): unknown[] {\n const out: unknown[] = []\n for (const ev of events) {\n const cur = asPartUpdate(ev)\n const prevEv = out[out.length - 1]\n const prev = prevEv ? asPartUpdate(prevEv) : null\n if (cur && prev && cur.partId != null && cur.partId === prev.partId) {\n // Base the merged row on the latest event (its `part` is the most complete\n // accumulation); carry forward the summed delta.\n const merged = JSON.parse(JSON.stringify(ev)) as AnyRecord\n ;(merged.data as AnyRecord).delta = String(prev.delta ?? '') + String(cur.delta ?? '')\n out[out.length - 1] = merged\n continue\n }\n out.push(ev)\n }\n return out\n}\n\n// ── buffering core (the tap) ────────────────────────────────────────────────\n\n/** Define options for buffering and flushing turn events with optional live client delivery and event coalescing */\nexport interface BufferedTurnOptions {\n store: TurnEventStore\n turnId: string\n /** Deliver one serialized line to the live client. Throwing here (client\n * disconnected) does NOT stop buffering — events keep persisting. */\n write?: (line: string) => Promise<void> | void\n /** Flush buffered events to the store at most this often. Default 400ms. */\n flushIntervalMs?: number\n /** Per-flush coalescer. Default {@link coalesceDeltas} (tool-loop text/reasoning\n * deltas). agent-runtime products streaming `ChatStreamEvent` pass\n * {@link coalesceChatStreamEvents} so per-token deltas don't each persist as a\n * row. Must be concatenation-preserving. */\n coalesce?: (events: unknown[]) => unknown[]\n /** Optional scope (thread/session id) recorded with the turn status, so\n * {@link TurnEventStore.listRunning} can find this turn after a reload. */\n scopeId?: string\n}\n\n/** A push-driven buffer for a turn whose producer the caller does NOT own. */\nexport interface BufferedTurnTap {\n /** Buffer one event: persist (coalesced, on the flush window) + best-effort\n * live-deliver. Wire to a push source's per-event hook (e.g. agent-runtime\n * `handleChatTurn`'s `hooks.onEvent`). Marks the turn 'running' on first call. */\n onEvent(raw: unknown): Promise<void>\n /** Settle the turn: final flush + set status. Call after the producer resolves\n * ('complete') or rejects ('error'). 'error' flushes what was produced first. */\n done(status?: Extract<TurnStatus, 'complete' | 'error'>): Promise<void>\n}\n\n/**\n * The buffering core. Sequence-numbers every event, delivers it to `write`\n * (best-effort — a disconnected client never stops buffering), and flushes to\n * the store in coalesced batches. Drives both transports:\n *\n * • {@link pumpBufferedTurn} — when you OWN an `AsyncIterable` producer.\n * • this tap (`onEvent`/`done`) — when the producer owns iteration and only\n * hands you a push callback (agent-runtime `handleChatTurn`'s `hooks.onEvent`\n * + the finished body). Durability stays here in the shell; the engine needs\n * no `TurnEventStore` seam.\n */\nexport function createBufferedTurnTap(opts: BufferedTurnOptions): BufferedTurnTap {\n const flushIntervalMs = opts.flushIntervalMs ?? 400\n const coalesce = opts.coalesce ?? coalesceDeltas\n const startedAt = Date.now()\n let seq = 0\n let clientGone = false\n let pending: unknown[] = []\n let lastFlush = Date.now()\n let started = false\n\n async function flush(): Promise<void> {\n if (pending.length === 0) return\n const batch = coalesce(pending)\n pending = []\n const rows = batch.map((ev) => ({ seq: ++seq, event: JSON.stringify(ev) }))\n await opts.store.append(opts.turnId, rows)\n lastFlush = Date.now()\n }\n\n async function ensureStarted(): Promise<void> {\n if (started) return\n started = true\n await opts.store.setStatus(opts.turnId, 'running', opts.scopeId)\n }\n\n return {\n async onEvent(raw) {\n await ensureStarted()\n // Stamp ms-since-turn-start so any stored turn is replayable AND traceable\n // (see ../trace) from the same buffered rows.\n const ev = raw && typeof raw === 'object' ? { ...(raw as Record<string, unknown>), _t: Date.now() - startedAt } : raw\n pending.push(ev)\n if (!clientGone && opts.write) {\n try {\n // Live delivery carries a provisional ordering hint, not the persisted\n // seq (coalescing changes seq assignment); clients resume with the\n // seqs from replay, or 0 for \"everything\".\n await opts.write(JSON.stringify(ev))\n } catch {\n clientGone = true\n }\n }\n if (Date.now() - lastFlush >= flushIntervalMs) await flush()\n },\n async done(status = 'complete') {\n await ensureStarted()\n if (status === 'error') {\n await flush().catch(() => {})\n await opts.store.setStatus(opts.turnId, 'error', opts.scopeId).catch(() => {})\n return\n }\n await flush()\n await opts.store.setStatus(opts.turnId, 'complete', opts.scopeId)\n },\n }\n}\n\n// ── pump (producer side) ──────────────────────────────────────────────────\n\n/** Define options to pump data from an asynchronous iterable source with buffered turn control */\nexport interface PumpBufferedTurnOptions extends BufferedTurnOptions {\n source: AsyncIterable<unknown>\n}\n\n/**\n * Drive a turn to completion regardless of the live client, when you OWN the\n * producer as an `AsyncIterable`. A thin driver over {@link createBufferedTurnTap}.\n * Returns a promise that resolves when the turn finishes — hand it to\n * `ctx.waitUntil` so a disconnect can't kill the turn. Never rejects on\n * client-write failure; a source error marks the turn 'error' (after flushing\n * what was produced) and rethrows.\n */\nexport async function pumpBufferedTurn(opts: PumpBufferedTurnOptions): Promise<void> {\n const tap = createBufferedTurnTap(opts)\n try {\n for await (const raw of opts.source) await tap.onEvent(raw)\n await tap.done('complete')\n } catch (err) {\n await tap.done('error')\n throw err\n }\n}\n\n// ── replay (consumer side) ────────────────────────────────────────────────\n\n/** Define options for replaying turn events with control over sequence, polling, and timeout */\nexport interface ReplayTurnEventsOptions {\n store: TurnEventStore\n turnId: string\n /** Replay strictly after this sequence number (0 = from the beginning). */\n fromSeq?: number\n /** Poll cadence while the turn is still running. Default 500ms. */\n pollMs?: number\n /** Give up following a 'running' turn after this long. Default 120s. */\n timeoutMs?: number\n}\n\n/**\n * Yield buffered events after `fromSeq`, then keep polling while the turn is\n * still 'running' until it completes, errors, or times out. Terminates with a\n * final `{seq: -1, event: '{\"type\":\"turn_status\",...}'}` marker so clients\n * know why the replay ended.\n */\nexport async function* replayTurnEvents(opts: ReplayTurnEventsOptions): AsyncGenerator<BufferedTurnEvent> {\n const pollMs = opts.pollMs ?? 500\n const timeoutMs = opts.timeoutMs ?? 120_000\n let cursor = opts.fromSeq ?? 0\n const deadline = Date.now() + timeoutMs\n\n for (;;) {\n const batch = await opts.store.read(opts.turnId, cursor)\n for (const row of batch) {\n cursor = Math.max(cursor, row.seq)\n yield row\n }\n const status = await opts.store.getStatus(opts.turnId)\n if (status !== 'running') {\n yield { seq: -1, event: JSON.stringify({ type: 'turn_status', status: status ?? 'unknown' }) }\n return\n }\n if (Date.now() >= deadline) {\n yield { seq: -1, event: JSON.stringify({ type: 'turn_status', status: 'timeout' }) }\n return\n }\n await new Promise((r) => setTimeout(r, pollMs))\n }\n}\n\n/**\n * Serialize a replayed row for the wire, stamping the buffer ordinal ONTO the\n * line so a reconnecting client can continue from `?fromSeq=<lastSeq>`.\n *\n * The seq lives on the {@link BufferedTurnEvent} row wrapper, not inside the\n * serialized event — `flush()` builds `{seq: ++seq, event: JSON.stringify(ev)}`.\n * A route that enqueues `row.event` alone therefore emits lines with no seq at\n * all, and every client cursor silently pins to 0: each reconnect refetches the\n * whole turn and re-applies every delta onto already-rendered state. This\n * restores the contract `web-react/chat-stream` already documents (\"replayed\n * lines carry an extra `seq` — transparently ignored\").\n *\n * The `{seq: -1}` `turn_status` sentinel is passed through unstamped: it is a\n * terminator, not a cursor position, and stamping it would move a client's\n * cursor to -1.\n *\n * Fail-soft by construction — a line that is not a JSON object passes through\n * verbatim. A stamping bug must degrade to today's behaviour, never break a\n * replay.\n */\nexport function stampReplaySeq(row: BufferedTurnEvent): string {\n if (row.seq <= 0) return row.event\n const line = row.event\n // Cheap splice instead of parse+stringify: these rows are already canonical\n // JSON objects from `JSON.stringify`, and replay is a hot per-event path.\n if (line.charCodeAt(0) !== 0x7b /* { */) return line\n const rest = line.slice(1)\n return rest.trimStart().startsWith('}')\n ? `{\"seq\":${row.seq}${rest}`\n : `{\"seq\":${row.seq},${rest}`\n}\n\n// ── D1 store ──────────────────────────────────────────────────────────────\n\n/** Minimal structural D1 contract (Cloudflare `D1Database` satisfies it). */\nexport interface D1LikeForTurns {\n prepare(sql: string): {\n bind(...values: unknown[]): {\n run(): Promise<unknown>\n all<T = Record<string, unknown>>(): Promise<{ results: T[] }>\n first<T = Record<string, unknown>>(): Promise<T | null>\n }\n }\n}\n\n/** Schema for the D1 store — append to the product's migrations. */\nexport const TURN_EVENTS_MIGRATION_SQL = `\nCREATE TABLE IF NOT EXISTS turn_events (\n turnId TEXT NOT NULL,\n seq INTEGER NOT NULL,\n event TEXT NOT NULL,\n PRIMARY KEY (turnId, seq)\n);\nCREATE TABLE IF NOT EXISTS turn_status (\n turnId TEXT PRIMARY KEY,\n status TEXT NOT NULL,\n scopeId TEXT,\n updatedAt TEXT NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_turn_status_scope ON turn_status (scopeId, status);\n`\n\n/** For deployments whose `turn_status` table predates `scopeId`/`listRunning` —\n * run once to add the column (the CREATE above already includes it for new\n * deployments). SQLite ignores a duplicate-add error if already applied. */\nexport const TURN_STATUS_SCOPE_MIGRATION_SQL = `ALTER TABLE turn_status ADD COLUMN scopeId TEXT;`\n\n/** Resolve a TurnEventStore that appends and reads turn events using a D1-like database interface */\nexport function createD1TurnEventStore(db: D1LikeForTurns): TurnEventStore {\n return {\n async append(turnId, events) {\n if (!events.length) return\n // One multi-row insert per flush window keeps write volume bounded.\n const placeholders = events.map(() => '(?, ?, ?)').join(', ')\n const values = events.flatMap((e) => [turnId, e.seq, e.event])\n await db.prepare(`INSERT OR IGNORE INTO turn_events (turnId, seq, event) VALUES ${placeholders}`).bind(...values).run()\n },\n async read(turnId, fromSeq) {\n const { results } = await db\n .prepare('SELECT seq, event FROM turn_events WHERE turnId = ? AND seq > ? ORDER BY seq ASC')\n .bind(turnId, fromSeq)\n .all<{ seq: number; event: string }>()\n return results\n },\n async setStatus(turnId, status, scopeId) {\n // COALESCE preserves a scopeId set on the initial 'running' write when a\n // later 'complete'/'error' write passes none.\n await db\n .prepare(\n 'INSERT INTO turn_status (turnId, status, scopeId, updatedAt) VALUES (?, ?, ?, ?) ON CONFLICT(turnId) DO UPDATE SET status = excluded.status, scopeId = COALESCE(excluded.scopeId, turn_status.scopeId), updatedAt = excluded.updatedAt',\n )\n .bind(turnId, status, scopeId ?? null, new Date().toISOString())\n .run()\n },\n async getStatus(turnId) {\n const row = await db.prepare('SELECT status FROM turn_status WHERE turnId = ?').bind(turnId).first<{ status: TurnStatus }>()\n return row?.status ?? null\n },\n async listRunning(scopeId) {\n const { results } = await db\n .prepare(\"SELECT turnId FROM turn_status WHERE scopeId = ? AND status = 'running' ORDER BY updatedAt DESC\")\n .bind(scopeId)\n .all<{ turnId: string }>()\n return results.map((r) => r.turnId)\n },\n }\n}\n\n/** In-memory store for tests and keyless local dev. */\nexport function createMemoryTurnEventStore(): TurnEventStore {\n const events = new Map<string, BufferedTurnEvent[]>()\n const status = new Map<string, TurnStatus>()\n const scopes = new Map<string, string>()\n const order: string[] = []\n return {\n async append(turnId, rows) {\n const list = events.get(turnId) ?? []\n list.push(...rows)\n events.set(turnId, list)\n },\n async read(turnId, fromSeq) {\n return (events.get(turnId) ?? []).filter((e) => e.seq > fromSeq)\n },\n async setStatus(turnId, s, scopeId) {\n status.set(turnId, s)\n if (scopeId) scopes.set(turnId, scopeId)\n if (!order.includes(turnId)) order.push(turnId)\n },\n async getStatus(turnId) {\n return status.get(turnId) ?? null\n },\n async listRunning(scopeId) {\n // Newest first, mirroring the D1 store's `ORDER BY updatedAt DESC`.\n return [...order].reverse().filter((t) => status.get(t) === 'running' && scopes.get(t) === scopeId)\n },\n }\n}\n"],"mappings":";AA0BO,SAAS,sBAAsB,OAAoC;AACxE,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,MAAI,OAAO,UAAU,SAAU,OAAM,IAAI,MAAM,yBAAyB;AACxE,QAAM,UAAU,MAAM,KAAK;AAC3B,MAAI,CAAC,QAAS,OAAM,IAAI,MAAM,0BAA0B;AACxD,MAAI,QAAQ,SAAS,IAAK,OAAM,IAAI,MAAM,oBAAoB;AAC9D,MAAI,CAAC,oBAAoB,KAAK,OAAO,GAAG;AACtC,UAAM,IAAI,MAAM,wCAAwC;AAAA,EAC1D;AACA,SAAO;AACT;AAGO,SAAS,mBAAmB,MAAc,QAA0C;AACzF,QAAM,OAAmB,EAAE,MAAM,QAAQ,KAAK;AAC9C,MAAI,OAAQ,MAAK,SAAS;AAC1B,SAAO,CAAC,IAAI;AACd;AAGO,SAAS,iBAAiB,SAAsC,QAAyB;AAC9F,aAAW,QAAQ,QAAQ,SAAS,CAAC,GAAG;AACtC,QAAI,QAAQ,OAAO,SAAS,YAAY,OAAO,KAAK,UAAU,EAAE,MAAM,QAAQ;AAC5E,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAGO,SAAS,gBAAgB,OAmBX;AACnB,QAAM,EAAE,kBAAkB,aAAa,OAAO,IAAI;AAClD,QAAM,gBAAgB;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,IACA,MAAM,mBAAmB;AAAA,EAC3B;AACA,MAAI,iBAAiB,GAAG;AAItB,UAAM,WAAW,iBAAiB,aAAa,GAAG;AAClD,WAAO;AAAA,MACL,WAAW,kBAAkB,iBAAiB,MAAM,GAAG,aAAa,CAAC;AAAA,MACrE,yBAAyB;AAAA,MACzB,eAAe,iBAAiB,MAAM,GAAG,aAAa;AAAA,MACtD,WAAW,mBAAmB,aAAa,MAAM;AAAA,MACjD,GAAI,OAAO,aAAa,YAAY,WAAW,EAAE,qBAAqB,SAAS,IAAI,CAAC;AAAA,IACtF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,WAAW,kBAAkB,gBAAgB;AAAA,IAC7C,yBAAyB;AAAA,IACzB,eAAe;AAAA,IACf,WAAW,mBAAmB,aAAa,MAAM;AAAA,EACnD;AACF;AAEA,SAAS,6BACP,UACA,aACA,QACA,gBACQ;AACR,MAAI,QAAQ;AACV,aAASA,SAAQ,SAAS,SAAS,GAAGA,UAAS,GAAGA,UAAS,GAAG;AAC5D,YAAM,UAAU,SAASA,MAAK;AAC9B,UAAI,SAAS,SAAS,UAAU,iBAAiB,SAAS,MAAM,EAAG,QAAOA;AAAA,IAC5E;AAAA,EACF;AAMA,MAAI,QAAQ,SAAS,SAAS;AAC9B,MAAI,gBAAgB;AAClB,WAAO,SAAS,KAAK,SAAS,KAAK,GAAG,SAAS,YAAa,UAAS;AAAA,EACvE;AACA,QAAM,SAAS,SAAS,IAAI,SAAS,KAAK,IAAI;AAC9C,MAAI,QAAQ,SAAS,UAAU,OAAO,YAAY,YAAa,QAAO;AAEtE,SAAO;AACT;AAEA,SAAS,kBAAkB,UAAiD;AAC1E,SAAO,SAAS,OAAO,CAAC,YAAY,QAAQ,SAAS,MAAM,EAAE;AAC/D;;;ACxFA,SAAS,YAAY,IAA0C;AAC7D,QAAM,IAAI;AACV,MAAI,CAAC,KAAK,OAAO,MAAM,SAAU,QAAO;AACxC,QAAM,QAAS,EAAE,SAAS,UAAW,EAAE,QAAkC;AACzE,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,OAAK,MAAM,SAAS,UAAU,MAAM,SAAS,gBAAgB,OAAO,MAAM,SAAS,UAAU;AAC3F,WAAO,MAAM;AAAA,EACf;AACA,SAAO;AACT;AAKO,SAAS,eAAe,QAA8B;AAC3D,QAAM,MAAiB,CAAC;AACxB,aAAW,MAAM,QAAQ;AACvB,UAAM,OAAO,YAAY,EAAE;AAC3B,UAAM,OAAO,IAAI,IAAI,SAAS,CAAC;AAC/B,QAAI,QAAQ,QAAQ,YAAY,IAAI,MAAM,MAAM;AAC9C,YAAM,OAAO,CAAC,MACV,EAAgB,SAAS,UAAW,EAAgB,QAAQ;AAChE,YAAM,SAAS,KAAK,MAAM,KAAK,UAAU,IAAI,CAAC;AAC9C,WAAK,MAAM,EAAE,OAAO,OAAO,KAAK,IAAI,EAAE,IAAI,IAAI,OAAO,KAAK,EAAE,EAAE,IAAI;AAClE,UAAI,IAAI,SAAS,CAAC,IAAI;AACtB;AAAA,IACF;AACA,QAAI,KAAK,EAAE;AAAA,EACb;AACA,SAAO;AACT;AAEA,SAAS,aAAa,IAAyD;AAC7E,QAAM,IAAI;AACV,MAAI,CAAC,KAAK,OAAO,MAAM,YAAY,EAAE,SAAS,uBAAwB,QAAO;AAC7E,QAAM,OAAO,EAAE;AACf,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC9C,QAAM,OAAO,KAAK;AAClB,QAAM,SAAS,MAAM,MAAM,KAAK,UAAU,MAAM,UAAU;AAC1D,SAAO,EAAE,QAAQ,OAAO,KAAK,MAAM;AACrC;AAgBO,SAAS,yBAAyB,QAA8B;AACrE,QAAM,MAAiB,CAAC;AACxB,aAAW,MAAM,QAAQ;AACvB,UAAM,MAAM,aAAa,EAAE;AAC3B,UAAM,SAAS,IAAI,IAAI,SAAS,CAAC;AACjC,UAAM,OAAO,SAAS,aAAa,MAAM,IAAI;AAC7C,QAAI,OAAO,QAAQ,IAAI,UAAU,QAAQ,IAAI,WAAW,KAAK,QAAQ;AAGnE,YAAM,SAAS,KAAK,MAAM,KAAK,UAAU,EAAE,CAAC;AAC3C,MAAC,OAAO,KAAmB,QAAQ,OAAO,KAAK,SAAS,EAAE,IAAI,OAAO,IAAI,SAAS,EAAE;AACrF,UAAI,IAAI,SAAS,CAAC,IAAI;AACtB;AAAA,IACF;AACA,QAAI,KAAK,EAAE;AAAA,EACb;AACA,SAAO;AACT;AA6CO,SAAS,sBAAsB,MAA4C;AAChF,QAAM,kBAAkB,KAAK,mBAAmB;AAChD,QAAM,WAAW,KAAK,YAAY;AAClC,QAAM,YAAY,KAAK,IAAI;AAC3B,MAAI,MAAM;AACV,MAAI,aAAa;AACjB,MAAI,UAAqB,CAAC;AAC1B,MAAI,YAAY,KAAK,IAAI;AACzB,MAAI,UAAU;AAEd,iBAAe,QAAuB;AACpC,QAAI,QAAQ,WAAW,EAAG;AAC1B,UAAM,QAAQ,SAAS,OAAO;AAC9B,cAAU,CAAC;AACX,UAAM,OAAO,MAAM,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,OAAO,KAAK,UAAU,EAAE,EAAE,EAAE;AAC1E,UAAM,KAAK,MAAM,OAAO,KAAK,QAAQ,IAAI;AACzC,gBAAY,KAAK,IAAI;AAAA,EACvB;AAEA,iBAAe,gBAA+B;AAC5C,QAAI,QAAS;AACb,cAAU;AACV,UAAM,KAAK,MAAM,UAAU,KAAK,QAAQ,WAAW,KAAK,OAAO;AAAA,EACjE;AAEA,SAAO;AAAA,IACL,MAAM,QAAQ,KAAK;AACjB,YAAM,cAAc;AAGpB,YAAM,KAAK,OAAO,OAAO,QAAQ,WAAW,EAAE,GAAI,KAAiC,IAAI,KAAK,IAAI,IAAI,UAAU,IAAI;AAClH,cAAQ,KAAK,EAAE;AACf,UAAI,CAAC,cAAc,KAAK,OAAO;AAC7B,YAAI;AAIF,gBAAM,KAAK,MAAM,KAAK,UAAU,EAAE,CAAC;AAAA,QACrC,QAAQ;AACN,uBAAa;AAAA,QACf;AAAA,MACF;AACA,UAAI,KAAK,IAAI,IAAI,aAAa,gBAAiB,OAAM,MAAM;AAAA,IAC7D;AAAA,IACA,MAAM,KAAK,SAAS,YAAY;AAC9B,YAAM,cAAc;AACpB,UAAI,WAAW,SAAS;AACtB,cAAM,MAAM,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAC5B,cAAM,KAAK,MAAM,UAAU,KAAK,QAAQ,SAAS,KAAK,OAAO,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAC7E;AAAA,MACF;AACA,YAAM,MAAM;AACZ,YAAM,KAAK,MAAM,UAAU,KAAK,QAAQ,YAAY,KAAK,OAAO;AAAA,IAClE;AAAA,EACF;AACF;AAiBA,eAAsB,iBAAiB,MAA8C;AACnF,QAAM,MAAM,sBAAsB,IAAI;AACtC,MAAI;AACF,qBAAiB,OAAO,KAAK,OAAQ,OAAM,IAAI,QAAQ,GAAG;AAC1D,UAAM,IAAI,KAAK,UAAU;AAAA,EAC3B,SAAS,KAAK;AACZ,UAAM,IAAI,KAAK,OAAO;AACtB,UAAM;AAAA,EACR;AACF;AAsBA,gBAAuB,iBAAiB,MAAkE;AACxG,QAAM,SAAS,KAAK,UAAU;AAC9B,QAAM,YAAY,KAAK,aAAa;AACpC,MAAI,SAAS,KAAK,WAAW;AAC7B,QAAM,WAAW,KAAK,IAAI,IAAI;AAE9B,aAAS;AACP,UAAM,QAAQ,MAAM,KAAK,MAAM,KAAK,KAAK,QAAQ,MAAM;AACvD,eAAW,OAAO,OAAO;AACvB,eAAS,KAAK,IAAI,QAAQ,IAAI,GAAG;AACjC,YAAM;AAAA,IACR;AACA,UAAM,SAAS,MAAM,KAAK,MAAM,UAAU,KAAK,MAAM;AACrD,QAAI,WAAW,WAAW;AACxB,YAAM,EAAE,KAAK,IAAI,OAAO,KAAK,UAAU,EAAE,MAAM,eAAe,QAAQ,UAAU,UAAU,CAAC,EAAE;AAC7F;AAAA,IACF;AACA,QAAI,KAAK,IAAI,KAAK,UAAU;AAC1B,YAAM,EAAE,KAAK,IAAI,OAAO,KAAK,UAAU,EAAE,MAAM,eAAe,QAAQ,UAAU,CAAC,EAAE;AACnF;AAAA,IACF;AACA,UAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC;AAAA,EAChD;AACF;AAsBO,SAAS,eAAe,KAAgC;AAC7D,MAAI,IAAI,OAAO,EAAG,QAAO,IAAI;AAC7B,QAAM,OAAO,IAAI;AAGjB,MAAI,KAAK,WAAW,CAAC,MAAM,IAAc,QAAO;AAChD,QAAM,OAAO,KAAK,MAAM,CAAC;AACzB,SAAO,KAAK,UAAU,EAAE,WAAW,GAAG,IAClC,UAAU,IAAI,GAAG,GAAG,IAAI,KACxB,UAAU,IAAI,GAAG,IAAI,IAAI;AAC/B;AAgBO,IAAM,4BAA4B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmBlC,IAAM,kCAAkC;AAGxC,SAAS,uBAAuB,IAAoC;AACzE,SAAO;AAAA,IACL,MAAM,OAAO,QAAQ,QAAQ;AAC3B,UAAI,CAAC,OAAO,OAAQ;AAEpB,YAAM,eAAe,OAAO,IAAI,MAAM,WAAW,EAAE,KAAK,IAAI;AAC5D,YAAM,SAAS,OAAO,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC;AAC7D,YAAM,GAAG,QAAQ,iEAAiE,YAAY,EAAE,EAAE,KAAK,GAAG,MAAM,EAAE,IAAI;AAAA,IACxH;AAAA,IACA,MAAM,KAAK,QAAQ,SAAS;AAC1B,YAAM,EAAE,QAAQ,IAAI,MAAM,GACvB,QAAQ,kFAAkF,EAC1F,KAAK,QAAQ,OAAO,EACpB,IAAoC;AACvC,aAAO;AAAA,IACT;AAAA,IACA,MAAM,UAAU,QAAQ,QAAQ,SAAS;AAGvC,YAAM,GACH;AAAA,QACC;AAAA,MACF,EACC,KAAK,QAAQ,QAAQ,WAAW,OAAM,oBAAI,KAAK,GAAE,YAAY,CAAC,EAC9D,IAAI;AAAA,IACT;AAAA,IACA,MAAM,UAAU,QAAQ;AACtB,YAAM,MAAM,MAAM,GAAG,QAAQ,iDAAiD,EAAE,KAAK,MAAM,EAAE,MAA8B;AAC3H,aAAO,KAAK,UAAU;AAAA,IACxB;AAAA,IACA,MAAM,YAAY,SAAS;AACzB,YAAM,EAAE,QAAQ,IAAI,MAAM,GACvB,QAAQ,iGAAiG,EACzG,KAAK,OAAO,EACZ,IAAwB;AAC3B,aAAO,QAAQ,IAAI,CAAC,MAAM,EAAE,MAAM;AAAA,IACpC;AAAA,EACF;AACF;AAGO,SAAS,6BAA6C;AAC3D,QAAM,SAAS,oBAAI,IAAiC;AACpD,QAAM,SAAS,oBAAI,IAAwB;AAC3C,QAAM,SAAS,oBAAI,IAAoB;AACvC,QAAM,QAAkB,CAAC;AACzB,SAAO;AAAA,IACL,MAAM,OAAO,QAAQ,MAAM;AACzB,YAAM,OAAO,OAAO,IAAI,MAAM,KAAK,CAAC;AACpC,WAAK,KAAK,GAAG,IAAI;AACjB,aAAO,IAAI,QAAQ,IAAI;AAAA,IACzB;AAAA,IACA,MAAM,KAAK,QAAQ,SAAS;AAC1B,cAAQ,OAAO,IAAI,MAAM,KAAK,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,MAAM,OAAO;AAAA,IACjE;AAAA,IACA,MAAM,UAAU,QAAQ,GAAG,SAAS;AAClC,aAAO,IAAI,QAAQ,CAAC;AACpB,UAAI,QAAS,QAAO,IAAI,QAAQ,OAAO;AACvC,UAAI,CAAC,MAAM,SAAS,MAAM,EAAG,OAAM,KAAK,MAAM;AAAA,IAChD;AAAA,IACA,MAAM,UAAU,QAAQ;AACtB,aAAO,OAAO,IAAI,MAAM,KAAK;AAAA,IAC/B;AAAA,IACA,MAAM,YAAY,SAAS;AAEzB,aAAO,CAAC,GAAG,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,MAAM,OAAO,IAAI,CAAC,MAAM,aAAa,OAAO,IAAI,CAAC,MAAM,OAAO;AAAA,IACpG;AAAA,EACF;AACF;","names":["index"]}
|