@llui/agent 0.0.30 → 0.0.31
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/server/cloudflare/durable-object.d.ts +58 -0
- package/dist/server/cloudflare/durable-object.d.ts.map +1 -0
- package/dist/server/cloudflare/durable-object.js +33 -0
- package/dist/server/cloudflare/durable-object.js.map +1 -0
- package/dist/server/cloudflare/index.d.ts +49 -0
- package/dist/server/cloudflare/index.d.ts.map +1 -0
- package/dist/server/cloudflare/index.js +49 -0
- package/dist/server/cloudflare/index.js.map +1 -0
- package/dist/server/cloudflare/worker.d.ts +40 -0
- package/dist/server/cloudflare/worker.d.ts.map +1 -0
- package/dist/server/cloudflare/worker.js +55 -0
- package/dist/server/cloudflare/worker.js.map +1 -0
- package/dist/server/core-entry.d.ts +27 -0
- package/dist/server/core-entry.d.ts.map +1 -0
- package/dist/server/core-entry.js +19 -0
- package/dist/server/core-entry.js.map +1 -0
- package/dist/server/core.d.ts +78 -0
- package/dist/server/core.d.ts.map +1 -0
- package/dist/server/core.js +84 -0
- package/dist/server/core.js.map +1 -0
- package/dist/server/factory.d.ts +5 -3
- package/dist/server/factory.d.ts.map +1 -1
- package/dist/server/factory.js +18 -58
- package/dist/server/factory.js.map +1 -1
- package/dist/server/http/mint.d.ts.map +1 -1
- package/dist/server/http/mint.js +2 -3
- package/dist/server/http/mint.js.map +1 -1
- package/dist/server/http/resume.js +1 -1
- package/dist/server/http/resume.js.map +1 -1
- package/dist/server/identity.d.ts +5 -1
- package/dist/server/identity.d.ts.map +1 -1
- package/dist/server/identity.js +49 -11
- package/dist/server/identity.js.map +1 -1
- package/dist/server/index.d.ts +16 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +13 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/lap/confirm-result.d.ts +2 -2
- package/dist/server/lap/confirm-result.d.ts.map +1 -1
- package/dist/server/lap/confirm-result.js +1 -1
- package/dist/server/lap/confirm-result.js.map +1 -1
- package/dist/server/lap/describe.d.ts +4 -4
- package/dist/server/lap/describe.d.ts.map +1 -1
- package/dist/server/lap/describe.js +4 -4
- package/dist/server/lap/describe.js.map +1 -1
- package/dist/server/lap/forward.d.ts +2 -2
- package/dist/server/lap/forward.d.ts.map +1 -1
- package/dist/server/lap/forward.js +1 -1
- package/dist/server/lap/forward.js.map +1 -1
- package/dist/server/lap/message.d.ts +2 -2
- package/dist/server/lap/message.d.ts.map +1 -1
- package/dist/server/lap/message.js +1 -1
- package/dist/server/lap/message.js.map +1 -1
- package/dist/server/lap/observe.d.ts +2 -2
- package/dist/server/lap/observe.d.ts.map +1 -1
- package/dist/server/lap/observe.js +1 -1
- package/dist/server/lap/observe.js.map +1 -1
- package/dist/server/lap/wait.d.ts +2 -2
- package/dist/server/lap/wait.d.ts.map +1 -1
- package/dist/server/lap/wait.js +1 -1
- package/dist/server/lap/wait.js.map +1 -1
- package/dist/server/options.d.ts +25 -1
- package/dist/server/options.d.ts.map +1 -1
- package/dist/server/options.js.map +1 -1
- package/dist/server/token.d.ts +7 -3
- package/dist/server/token.d.ts.map +1 -1
- package/dist/server/token.js +66 -26
- package/dist/server/token.js.map +1 -1
- package/dist/server/web/adapter.d.ts +16 -0
- package/dist/server/web/adapter.d.ts.map +1 -0
- package/dist/server/web/adapter.js +45 -0
- package/dist/server/web/adapter.js.map +1 -0
- package/dist/server/web/index.d.ts +12 -0
- package/dist/server/web/index.d.ts.map +1 -0
- package/dist/server/web/index.js +12 -0
- package/dist/server/web/index.js.map +1 -0
- package/dist/server/web/upgrade.d.ts +41 -0
- package/dist/server/web/upgrade.d.ts.map +1 -0
- package/dist/server/web/upgrade.js +96 -0
- package/dist/server/web/upgrade.js.map +1 -0
- package/dist/server/ws/pairing-registry.d.ts +84 -21
- package/dist/server/ws/pairing-registry.d.ts.map +1 -1
- package/dist/server/ws/pairing-registry.js +89 -151
- package/dist/server/ws/pairing-registry.js.map +1 -1
- package/dist/server/ws/rpc.d.ts +39 -0
- package/dist/server/ws/rpc.d.ts.map +1 -0
- package/dist/server/ws/rpc.js +126 -0
- package/dist/server/ws/rpc.js.map +1 -0
- package/dist/server/ws/upgrade.d.ts +3 -3
- package/dist/server/ws/upgrade.d.ts.map +1 -1
- package/dist/server/ws/upgrade.js +2 -2
- package/dist/server/ws/upgrade.js.map +1 -1
- package/package.json +13 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pairing-registry.js","sourceRoot":"","sources":["../../../src/server/ws/pairing-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from 'node:crypto'\nimport type { ClientFrame, ServerFrame, HelloFrame, LogEntry } from '../../protocol.js'\n\n/**\n * Thin abstraction over a WebSocket so the registry is testable with\n * a fake EventEmitter-style mock.\n */\nexport interface PairingConnection {\n send(frame: ServerFrame): void\n onFrame(handler: (f: ClientFrame) => void): void\n onClose(handler: () => void): void\n close(): void\n}\n\ntype RpcEntry = {\n resolve: (result: unknown) => void\n reject: (err: RpcError) => void\n timer: ReturnType<typeof setTimeout> | null\n}\n\ntype ConfirmEntry = {\n resolve: (r: { outcome: 'confirmed' | 'user-cancelled'; stateAfter?: unknown }) => void\n timer: ReturnType<typeof setTimeout> | null\n}\n\ntype WaitEntry = {\n path: string | undefined\n resolve: (r: { status: 'changed' | 'timeout'; stateAfter: unknown }) => void\n timer: ReturnType<typeof setTimeout> | null\n}\n\ntype Pairing = {\n conn: PairingConnection\n hello: HelloFrame | null\n pendingRpc: Map<string, RpcEntry>\n pendingConfirm: Map<string, ConfirmEntry>\n pendingWait: WaitEntry[]\n closed: boolean\n}\n\nexport type RpcError = {\n code: 'paused' | 'invalid' | 'timeout' | 'schema-error' | 'internal' | string\n detail?: string\n}\n\nexport type RpcOptions = { timeoutMs?: number }\n\n/**\n * Tracks live browser pairings and correlates rpc requests with replies.\n * One instance per server; shared by all LAP handlers + the upgrade\n * handler. Spec §10.4–§10.5.\n */\nexport class WsPairingRegistry {\n private pairings = new Map<string, Pairing>()\n private now: () => number\n private onLogAppend: ((tid: string, entry: LogEntry) => void) | null\n\n constructor(\n opts: {\n now?: () => number\n onLogAppend?: (tid: string, entry: LogEntry) => void\n } = {},\n ) {\n this.now = opts.now ?? (() => Date.now())\n this.onLogAppend = opts.onLogAppend ?? null\n }\n\n register(tid: string, conn: PairingConnection): void {\n const p: Pairing = {\n conn,\n hello: null,\n pendingRpc: new Map(),\n pendingConfirm: new Map(),\n pendingWait: [],\n closed: false,\n }\n this.pairings.set(tid, p)\n conn.onFrame((frame) => this.handleClientFrame(tid, frame))\n conn.onClose(() => this.handleClose(tid))\n }\n\n unregister(tid: string): void {\n const p = this.pairings.get(tid)\n if (!p) return\n this.handleClose(tid)\n }\n\n isPaired(tid: string): boolean {\n const p = this.pairings.get(tid)\n return !!p && !p.closed\n }\n\n /**\n * Send a ServerFrame to the paired browser connection, if one is live.\n * No-op when unpaired or closed.\n */\n notify(tid: string, frame: ServerFrame): void {\n const p = this.pairings.get(tid)\n if (!p || p.closed) return\n try {\n p.conn.send(frame)\n } catch {\n // connection may have dropped between isPaired and notify; ignore\n }\n }\n\n getHello(tid: string): HelloFrame | null {\n return this.pairings.get(tid)?.hello ?? null\n }\n\n async rpc(tid: string, tool: string, args: unknown, opts: RpcOptions = {}): Promise<unknown> {\n const p = this.pairings.get(tid)\n if (!p || p.closed) {\n const err: RpcError = { code: 'paused' }\n throw err\n }\n const id = randomUUID()\n const timeoutMs = opts.timeoutMs ?? 15_000\n return new Promise((resolve, reject) => {\n const entry: RpcEntry = {\n resolve,\n reject,\n timer: setTimeout(() => {\n p.pendingRpc.delete(id)\n reject({ code: 'timeout' } as RpcError)\n }, timeoutMs),\n }\n p.pendingRpc.set(id, entry)\n const frame: ServerFrame = { t: 'rpc', id, tool, args }\n try {\n p.conn.send(frame)\n } catch (e) {\n p.pendingRpc.delete(id)\n if (entry.timer) clearTimeout(entry.timer)\n reject({ code: 'internal', detail: String(e) } as RpcError)\n }\n })\n }\n\n async waitForConfirm(\n tid: string,\n confirmId: string,\n timeoutMs: number,\n ): Promise<{ outcome: 'confirmed' | 'user-cancelled'; stateAfter?: unknown }> {\n const p = this.pairings.get(tid)\n if (!p || p.closed) {\n return { outcome: 'user-cancelled' }\n }\n return new Promise((resolve) => {\n const entry: ConfirmEntry = {\n resolve,\n timer: setTimeout(() => {\n p.pendingConfirm.delete(confirmId)\n resolve({ outcome: 'user-cancelled' })\n }, timeoutMs),\n }\n p.pendingConfirm.set(confirmId, entry)\n })\n }\n\n async waitForChange(\n tid: string,\n path: string | undefined,\n timeoutMs: number,\n ): Promise<{ status: 'changed' | 'timeout'; stateAfter: unknown }> {\n const p = this.pairings.get(tid)\n if (!p || p.closed) return { status: 'timeout', stateAfter: null }\n return new Promise((resolve) => {\n const entry: WaitEntry = {\n path,\n resolve,\n timer: setTimeout(() => {\n const idx = p.pendingWait.indexOf(entry)\n if (idx >= 0) p.pendingWait.splice(idx, 1)\n resolve({ status: 'timeout', stateAfter: null })\n }, timeoutMs),\n }\n p.pendingWait.push(entry)\n })\n }\n\n private handleClientFrame(tid: string, frame: ClientFrame): void {\n const p = this.pairings.get(tid)\n if (!p || p.closed) return\n switch (frame.t) {\n case 'hello': {\n p.hello = frame\n break\n }\n case 'rpc-reply': {\n const e = p.pendingRpc.get(frame.id)\n if (!e) break\n p.pendingRpc.delete(frame.id)\n if (e.timer) clearTimeout(e.timer)\n e.resolve(frame.result)\n break\n }\n case 'rpc-error': {\n const e = p.pendingRpc.get(frame.id)\n if (!e) break\n p.pendingRpc.delete(frame.id)\n if (e.timer) clearTimeout(e.timer)\n e.reject({ code: frame.code, detail: frame.detail } as RpcError)\n break\n }\n case 'confirm-resolved': {\n const e = p.pendingConfirm.get(frame.confirmId)\n if (!e) break\n p.pendingConfirm.delete(frame.confirmId)\n if (e.timer) clearTimeout(e.timer)\n e.resolve({ outcome: frame.outcome, stateAfter: frame.stateAfter })\n break\n }\n case 'state-update': {\n for (let i = p.pendingWait.length - 1; i >= 0; i--) {\n const w = p.pendingWait[i]\n if (w === undefined) continue\n if (w.path === undefined || w.path === frame.path || frame.path.startsWith(w.path)) {\n p.pendingWait.splice(i, 1)\n if (w.timer) clearTimeout(w.timer)\n w.resolve({ status: 'changed', stateAfter: frame.stateAfter })\n }\n }\n break\n }\n case 'log-append': {\n this.onLogAppend?.(tid, frame.entry)\n break\n }\n }\n }\n\n private handleClose(tid: string): void {\n const p = this.pairings.get(tid)\n if (!p) return\n p.closed = true\n for (const [, e] of p.pendingRpc) {\n if (e.timer) clearTimeout(e.timer)\n e.reject({ code: 'paused' } as RpcError)\n }\n p.pendingRpc.clear()\n for (const [, e] of p.pendingConfirm) {\n if (e.timer) clearTimeout(e.timer)\n e.resolve({ outcome: 'user-cancelled' })\n }\n p.pendingConfirm.clear()\n for (const w of p.pendingWait) {\n if (w.timer) clearTimeout(w.timer)\n w.resolve({ status: 'timeout', stateAfter: null })\n }\n p.pendingWait.length = 0\n this.pairings.delete(tid)\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"pairing-registry.js","sourceRoot":"","sources":["../../../src/server/ws/pairing-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ClientFrame, ServerFrame, HelloFrame, LogEntry } from '../../protocol.js'\nimport {\n rpc as rpcHelper,\n waitForConfirm as waitForConfirmHelper,\n waitForChange as waitForChangeHelper,\n type RpcOptions,\n type RpcError,\n} from './rpc.js'\n\nexport type { RpcOptions, RpcError }\n\n/**\n * Thin abstraction over a single paired WebSocket. Consumed by the\n * registry implementations; runtime-specific adapters (`ws`-lib,\n * `WebSocketPair`, `Deno.upgradeWebSocket`, `Bun.serve` upgrade) build\n * one of these and pass it to `registry.register()`.\n */\nexport interface PairingConnection {\n send(frame: ServerFrame): void\n onFrame(handler: (f: ClientFrame) => void): void\n onClose(handler: () => void): void\n close(): void\n}\n\n/**\n * A per-call frame subscriber. Return `true` to remove this\n * subscriber (one-shot), or `false` to keep receiving. The registry\n * dispatches every inbound `ClientFrame` to every active subscriber\n * for the given `tid`; subscribers filter by `frame.t` + identifiers\n * (correlation id, confirm id, state path) to find the one that\n * belongs to their request.\n */\nexport type FrameSubscriber = (frame: ClientFrame) => boolean\n\n/**\n * Registry of live browser pairings. Pure routing + hello cache —\n * request-lifecycle state (in-flight RPC promises, confirm waits,\n * long-polls) lives in the LAP handlers that need it, not here.\n *\n * Two implementations ship today:\n * - `InMemoryPairingRegistry` for long-lived server processes\n * (Node, Bun, Deno, Deno Deploy).\n * - A Cloudflare Durable Object implementation (see\n * `server/cloudflare`) for stateless Worker runtimes.\n *\n * Other runtimes can implement this interface the same way; the\n * contract is intentionally small.\n */\nexport interface PairingRegistry {\n // ── Routing primitives ─────────────────────────────────────────\n register(tid: string, conn: PairingConnection): void\n unregister(tid: string): void\n isPaired(tid: string): boolean\n getHello(tid: string): HelloFrame | null\n /** Send a frame. No-op when the pairing is absent or closed. */\n send(tid: string, frame: ServerFrame): void\n /**\n * Subscribe to frames from the paired browser. Returns an\n * unsubscribe function. A subscriber can remove itself mid-dispatch\n * by returning `true` from its callback — useful for one-shot\n * request/response correlation.\n */\n subscribe(tid: string, handler: FrameSubscriber): () => void\n /**\n * Observe the pairing closing (WebSocket drop, `unregister`, etc.).\n * Handlers registered before close fire; handlers registered after\n * close fire synchronously. Returns an unsubscribe function.\n */\n onClose(tid: string, handler: () => void): () => void\n\n // ── Request/response helpers ───────────────────────────────────\n // These are part of the contract (LAP handlers call them directly)\n // but implementations almost always delegate to the free helpers in\n // `./rpc.ts`, which are built on the routing primitives above. The\n // Cloudflare Durable Object registry uses the same helpers; the\n // split exists so the routing surface is small enough to implement\n // across stateful boundaries (DO storage, WebSocket hibernation),\n // while the correlation logic lives once in a runtime-neutral file.\n\n /**\n * Send a typed rpc frame and await its matching reply. See\n * `./rpc.ts::rpc` for the full contract.\n */\n rpc(tid: string, tool: string, args: unknown, opts?: RpcOptions): Promise<unknown>\n /** See `./rpc.ts::waitForConfirm`. */\n waitForConfirm(\n tid: string,\n confirmId: string,\n timeoutMs: number,\n ): Promise<{ outcome: 'confirmed' | 'user-cancelled'; stateAfter?: unknown }>\n /** See `./rpc.ts::waitForChange`. */\n waitForChange(\n tid: string,\n path: string | undefined,\n timeoutMs: number,\n ): Promise<{ status: 'changed' | 'timeout'; stateAfter: unknown }>\n}\n\ntype Pairing = {\n conn: PairingConnection\n hello: HelloFrame | null\n subscribers: Set<FrameSubscriber>\n closeHandlers: Set<() => void>\n closed: boolean\n}\n\n/**\n * Single-process in-memory registry. Correct for Node/Bun/Deno/Deno\n * Deploy — anywhere the server process can hold a long-lived\n * WebSocket. Not suitable for stateless Worker isolates; use the\n * Durable Object registry for Cloudflare.\n */\nexport class InMemoryPairingRegistry implements PairingRegistry {\n private pairings = new Map<string, Pairing>()\n private onLogAppend: ((tid: string, entry: LogEntry) => void) | null\n\n constructor(\n opts: {\n onLogAppend?: (tid: string, entry: LogEntry) => void\n } = {},\n ) {\n this.onLogAppend = opts.onLogAppend ?? null\n }\n\n register(tid: string, conn: PairingConnection): void {\n const p: Pairing = {\n conn,\n hello: null,\n subscribers: new Set(),\n closeHandlers: new Set(),\n closed: false,\n }\n this.pairings.set(tid, p)\n conn.onFrame((frame) => this.dispatch(tid, frame))\n conn.onClose(() => this.handleClose(tid))\n }\n\n unregister(tid: string): void {\n this.handleClose(tid)\n }\n\n isPaired(tid: string): boolean {\n const p = this.pairings.get(tid)\n return !!p && !p.closed\n }\n\n getHello(tid: string): HelloFrame | null {\n return this.pairings.get(tid)?.hello ?? null\n }\n\n send(tid: string, frame: ServerFrame): void {\n const p = this.pairings.get(tid)\n if (!p || p.closed) return\n try {\n p.conn.send(frame)\n } catch {\n // Connection may have dropped between isPaired() and send(); no-op.\n }\n }\n\n subscribe(tid: string, handler: FrameSubscriber): () => void {\n const p = this.pairings.get(tid)\n if (!p || p.closed) return () => {}\n p.subscribers.add(handler)\n return () => {\n p.subscribers.delete(handler)\n }\n }\n\n onClose(tid: string, handler: () => void): () => void {\n const p = this.pairings.get(tid)\n if (!p || p.closed) {\n // Already closed — fire synchronously so callers don't hang\n // waiting for a close that already happened.\n queueMicrotask(handler)\n return () => {}\n }\n p.closeHandlers.add(handler)\n return () => {\n p.closeHandlers.delete(handler)\n }\n }\n\n private dispatch(tid: string, frame: ClientFrame): void {\n const p = this.pairings.get(tid)\n if (!p || p.closed) return\n // hello and log-append are registry-owned side effects — handled\n // here so no per-call subscriber has to pick them up.\n if (frame.t === 'hello') {\n p.hello = frame\n return\n }\n if (frame.t === 'log-append') {\n this.onLogAppend?.(tid, frame.entry)\n return\n }\n // Iterate over a snapshot because subscribers may self-remove\n // mid-iteration by returning true.\n const snapshot = Array.from(p.subscribers)\n for (const sub of snapshot) {\n try {\n if (sub(frame)) p.subscribers.delete(sub)\n } catch {\n // One bad subscriber shouldn't break the others.\n p.subscribers.delete(sub)\n }\n }\n }\n\n // ── Convenience wrappers ───────────────────────────────────────\n // The following methods delegate to the free-function helpers in\n // `./rpc.ts`. They're here so the in-memory registry remains a\n // one-stop testing surface (spy on `registry.rpc`, etc.) without\n // couping the `PairingRegistry` interface to request-lifecycle\n // details. External implementations (e.g. the Cloudflare Durable\n // Object registry) are NOT required to provide these; the LAP\n // handlers always go through the free helpers.\n\n rpc(tid: string, tool: string, args: unknown, opts: RpcOptions = {}): Promise<unknown> {\n return rpcHelper(this, tid, tool, args, opts)\n }\n\n waitForConfirm(\n tid: string,\n confirmId: string,\n timeoutMs: number,\n ): Promise<{ outcome: 'confirmed' | 'user-cancelled'; stateAfter?: unknown }> {\n return waitForConfirmHelper(this, tid, confirmId, timeoutMs)\n }\n\n waitForChange(\n tid: string,\n path: string | undefined,\n timeoutMs: number,\n ): Promise<{ status: 'changed' | 'timeout'; stateAfter: unknown }> {\n return waitForChangeHelper(this, tid, path, timeoutMs)\n }\n\n /** @deprecated Use `send(tid, frame)` directly; semantics are identical. */\n notify(tid: string, frame: ServerFrame): void {\n this.send(tid, frame)\n }\n\n private handleClose(tid: string): void {\n const p = this.pairings.get(tid)\n if (!p || p.closed) return\n p.closed = true\n for (const h of Array.from(p.closeHandlers)) {\n try {\n h()\n } catch {\n // Swallow — handlers run best-effort.\n }\n }\n p.closeHandlers.clear()\n p.subscribers.clear()\n this.pairings.delete(tid)\n }\n}\n\n/**\n * Back-compat alias for the prior class name. New code should use\n * `InMemoryPairingRegistry`. Removed in a future major.\n *\n * @deprecated Use `InMemoryPairingRegistry` directly.\n */\nexport const WsPairingRegistry = InMemoryPairingRegistry\nexport type WsPairingRegistry = InMemoryPairingRegistry\n"]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { PairingRegistry } from './pairing-registry.js';
|
|
2
|
+
export type RpcError = {
|
|
3
|
+
code: 'paused' | 'invalid' | 'timeout' | 'schema-error' | 'internal' | string;
|
|
4
|
+
detail?: string;
|
|
5
|
+
};
|
|
6
|
+
export type RpcOptions = {
|
|
7
|
+
timeoutMs?: number;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Send an `rpc` frame to the paired browser and await its
|
|
11
|
+
* matching `rpc-reply` / `rpc-error`. Runs its own one-shot frame
|
|
12
|
+
* subscription against the registry — no state stored on the
|
|
13
|
+
* registry itself, which keeps the registry small enough to
|
|
14
|
+
* implement in a Durable Object or other stateful primitive.
|
|
15
|
+
*
|
|
16
|
+
* Rejects with `{code: 'paused'}` when the pairing is absent,
|
|
17
|
+
* `{code: 'timeout'}` when the browser doesn't reply in time,
|
|
18
|
+
* or whatever the browser sent in its `rpc-error` frame otherwise.
|
|
19
|
+
*/
|
|
20
|
+
export declare function rpc(registry: PairingRegistry, tid: string, tool: string, args: unknown, opts?: RpcOptions): Promise<unknown>;
|
|
21
|
+
/**
|
|
22
|
+
* Await a `confirm-resolved` frame for the given `confirmId`.
|
|
23
|
+
* Resolves with `{outcome: 'user-cancelled'}` on timeout or pairing
|
|
24
|
+
* drop (approvals lapse when the user isn't present to act on them).
|
|
25
|
+
*/
|
|
26
|
+
export declare function waitForConfirm(registry: PairingRegistry, tid: string, confirmId: string, timeoutMs: number): Promise<{
|
|
27
|
+
outcome: 'confirmed' | 'user-cancelled';
|
|
28
|
+
stateAfter?: unknown;
|
|
29
|
+
}>;
|
|
30
|
+
/**
|
|
31
|
+
* Await a `state-update` frame whose path matches (exact or prefix).
|
|
32
|
+
* Used by the long-poll `/lap/v1/wait` endpoint for external state
|
|
33
|
+
* pushes (WebSocket messages, timers) arriving while the LLM is idle.
|
|
34
|
+
*/
|
|
35
|
+
export declare function waitForChange(registry: PairingRegistry, tid: string, path: string | undefined, timeoutMs: number): Promise<{
|
|
36
|
+
status: 'changed' | 'timeout';
|
|
37
|
+
stateAfter: unknown;
|
|
38
|
+
}>;
|
|
39
|
+
//# sourceMappingURL=rpc.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rpc.d.ts","sourceRoot":"","sources":["../../../src/server/ws/rpc.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAG5D,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,cAAc,GAAG,UAAU,GAAG,MAAM,CAAA;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAE/C;;;;;;;;;;GAUG;AACH,wBAAsB,GAAG,CACvB,QAAQ,EAAE,eAAe,EACzB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,EACb,IAAI,GAAE,UAAe,GACpB,OAAO,CAAC,OAAO,CAAC,CA8ClB;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,eAAe,EACzB,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,OAAO,EAAE,WAAW,GAAG,gBAAgB,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CA8B5E;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,eAAe,EACzB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,MAAM,EAAE,SAAS,GAAG,SAAS,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,CAAC,CA+BjE"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Send an `rpc` frame to the paired browser and await its
|
|
3
|
+
* matching `rpc-reply` / `rpc-error`. Runs its own one-shot frame
|
|
4
|
+
* subscription against the registry — no state stored on the
|
|
5
|
+
* registry itself, which keeps the registry small enough to
|
|
6
|
+
* implement in a Durable Object or other stateful primitive.
|
|
7
|
+
*
|
|
8
|
+
* Rejects with `{code: 'paused'}` when the pairing is absent,
|
|
9
|
+
* `{code: 'timeout'}` when the browser doesn't reply in time,
|
|
10
|
+
* or whatever the browser sent in its `rpc-error` frame otherwise.
|
|
11
|
+
*/
|
|
12
|
+
export async function rpc(registry, tid, tool, args, opts = {}) {
|
|
13
|
+
if (!registry.isPaired(tid)) {
|
|
14
|
+
const err = { code: 'paused' };
|
|
15
|
+
throw err;
|
|
16
|
+
}
|
|
17
|
+
const id = crypto.randomUUID();
|
|
18
|
+
const timeoutMs = opts.timeoutMs ?? 15_000;
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
let settled = false;
|
|
21
|
+
const done = (fn) => {
|
|
22
|
+
if (settled)
|
|
23
|
+
return;
|
|
24
|
+
settled = true;
|
|
25
|
+
clearTimeout(timer);
|
|
26
|
+
unsubFrame();
|
|
27
|
+
unsubClose();
|
|
28
|
+
fn();
|
|
29
|
+
};
|
|
30
|
+
const unsubFrame = registry.subscribe(tid, (frame) => {
|
|
31
|
+
if (frame.t === 'rpc-reply' && frame.id === id) {
|
|
32
|
+
done(() => resolve(frame.result));
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
if (frame.t === 'rpc-error' && frame.id === id) {
|
|
36
|
+
done(() => reject({ code: frame.code, detail: frame.detail }));
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
});
|
|
41
|
+
const unsubClose = registry.onClose(tid, () => {
|
|
42
|
+
done(() => reject({ code: 'paused' }));
|
|
43
|
+
});
|
|
44
|
+
const timer = setTimeout(() => {
|
|
45
|
+
done(() => reject({ code: 'timeout' }));
|
|
46
|
+
}, timeoutMs);
|
|
47
|
+
try {
|
|
48
|
+
const frame = { t: 'rpc', id, tool, args };
|
|
49
|
+
registry.send(tid, frame);
|
|
50
|
+
}
|
|
51
|
+
catch (e) {
|
|
52
|
+
done(() => reject({ code: 'internal', detail: String(e) }));
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Await a `confirm-resolved` frame for the given `confirmId`.
|
|
58
|
+
* Resolves with `{outcome: 'user-cancelled'}` on timeout or pairing
|
|
59
|
+
* drop (approvals lapse when the user isn't present to act on them).
|
|
60
|
+
*/
|
|
61
|
+
export async function waitForConfirm(registry, tid, confirmId, timeoutMs) {
|
|
62
|
+
if (!registry.isPaired(tid))
|
|
63
|
+
return { outcome: 'user-cancelled' };
|
|
64
|
+
return new Promise((resolve) => {
|
|
65
|
+
let settled = false;
|
|
66
|
+
const done = (result) => {
|
|
67
|
+
if (settled)
|
|
68
|
+
return;
|
|
69
|
+
settled = true;
|
|
70
|
+
clearTimeout(timer);
|
|
71
|
+
unsubFrame();
|
|
72
|
+
unsubClose();
|
|
73
|
+
resolve(result);
|
|
74
|
+
};
|
|
75
|
+
const unsubFrame = registry.subscribe(tid, (frame) => {
|
|
76
|
+
if (frame.t === 'confirm-resolved' && frame.confirmId === confirmId) {
|
|
77
|
+
done({ outcome: frame.outcome, stateAfter: frame.stateAfter });
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
});
|
|
82
|
+
const unsubClose = registry.onClose(tid, () => {
|
|
83
|
+
done({ outcome: 'user-cancelled' });
|
|
84
|
+
});
|
|
85
|
+
const timer = setTimeout(() => {
|
|
86
|
+
done({ outcome: 'user-cancelled' });
|
|
87
|
+
}, timeoutMs);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Await a `state-update` frame whose path matches (exact or prefix).
|
|
92
|
+
* Used by the long-poll `/lap/v1/wait` endpoint for external state
|
|
93
|
+
* pushes (WebSocket messages, timers) arriving while the LLM is idle.
|
|
94
|
+
*/
|
|
95
|
+
export async function waitForChange(registry, tid, path, timeoutMs) {
|
|
96
|
+
if (!registry.isPaired(tid))
|
|
97
|
+
return { status: 'timeout', stateAfter: null };
|
|
98
|
+
return new Promise((resolve) => {
|
|
99
|
+
let settled = false;
|
|
100
|
+
const done = (result) => {
|
|
101
|
+
if (settled)
|
|
102
|
+
return;
|
|
103
|
+
settled = true;
|
|
104
|
+
clearTimeout(timer);
|
|
105
|
+
unsubFrame();
|
|
106
|
+
unsubClose();
|
|
107
|
+
resolve(result);
|
|
108
|
+
};
|
|
109
|
+
const unsubFrame = registry.subscribe(tid, (frame) => {
|
|
110
|
+
if (frame.t !== 'state-update')
|
|
111
|
+
return false;
|
|
112
|
+
if (path === undefined || frame.path === path || frame.path.startsWith(path)) {
|
|
113
|
+
done({ status: 'changed', stateAfter: frame.stateAfter });
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
});
|
|
118
|
+
const unsubClose = registry.onClose(tid, () => {
|
|
119
|
+
done({ status: 'timeout', stateAfter: null });
|
|
120
|
+
});
|
|
121
|
+
const timer = setTimeout(() => {
|
|
122
|
+
done({ status: 'timeout', stateAfter: null });
|
|
123
|
+
}, timeoutMs);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=rpc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rpc.js","sourceRoot":"","sources":["../../../src/server/ws/rpc.ts"],"names":[],"mappings":"AAUA;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,GAAG,CACvB,QAAyB,EACzB,GAAW,EACX,IAAY,EACZ,IAAa,EACb,OAAmB,EAAE;IAErB,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAa,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAA;QACxC,MAAM,GAAG,CAAA;IACX,CAAC;IACD,MAAM,EAAE,GAAG,MAAM,CAAC,UAAU,EAAE,CAAA;IAC9B,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,MAAM,CAAA;IAE1C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,IAAI,OAAO,GAAG,KAAK,CAAA;QACnB,MAAM,IAAI,GAAG,CAAC,EAAc,EAAE,EAAE;YAC9B,IAAI,OAAO;gBAAE,OAAM;YACnB,OAAO,GAAG,IAAI,CAAA;YACd,YAAY,CAAC,KAAK,CAAC,CAAA;YACnB,UAAU,EAAE,CAAA;YACZ,UAAU,EAAE,CAAA;YACZ,EAAE,EAAE,CAAA;QACN,CAAC,CAAA;QAED,MAAM,UAAU,GAAG,QAAQ,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,EAAE;YACnD,IAAI,KAAK,CAAC,CAAC,KAAK,WAAW,IAAI,KAAK,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC;gBAC/C,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAA;gBACjC,OAAO,IAAI,CAAA;YACb,CAAC;YACD,IAAI,KAAK,CAAC,CAAC,KAAK,WAAW,IAAI,KAAK,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC;gBAC/C,IAAI,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAqB,CAAC,CAAC,CAAA;gBACjF,OAAO,IAAI,CAAA;YACb,CAAC;YACD,OAAO,KAAK,CAAA;QACd,CAAC,CAAC,CAAA;QAEF,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE;YAC5C,IAAI,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAqB,CAAC,CAAC,CAAA;QAC3D,CAAC,CAAC,CAAA;QAEF,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,IAAI,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAqB,CAAC,CAAC,CAAA;QAC5D,CAAC,EAAE,SAAS,CAAC,CAAA;QAEb,IAAI,CAAC;YACH,MAAM,KAAK,GAAgB,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;YACvD,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;QAC3B,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,EAAqB,CAAC,CAAC,CAAA;QAChF,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,QAAyB,EACzB,GAAW,EACX,SAAiB,EACjB,SAAiB;IAEjB,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAA;IAEjE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,IAAI,OAAO,GAAG,KAAK,CAAA;QACnB,MAAM,IAAI,GAAG,CAAC,MAAyE,EAAE,EAAE;YACzF,IAAI,OAAO;gBAAE,OAAM;YACnB,OAAO,GAAG,IAAI,CAAA;YACd,YAAY,CAAC,KAAK,CAAC,CAAA;YACnB,UAAU,EAAE,CAAA;YACZ,UAAU,EAAE,CAAA;YACZ,OAAO,CAAC,MAAM,CAAC,CAAA;QACjB,CAAC,CAAA;QAED,MAAM,UAAU,GAAG,QAAQ,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,EAAE;YACnD,IAAI,KAAK,CAAC,CAAC,KAAK,kBAAkB,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;gBACpE,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC,CAAA;gBAC9D,OAAO,IAAI,CAAA;YACb,CAAC;YACD,OAAO,KAAK,CAAA;QACd,CAAC,CAAC,CAAA;QAEF,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE;YAC5C,IAAI,CAAC,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC,CAAA;QACrC,CAAC,CAAC,CAAA;QAEF,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,IAAI,CAAC,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC,CAAA;QACrC,CAAC,EAAE,SAAS,CAAC,CAAA;IACf,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,QAAyB,EACzB,GAAW,EACX,IAAwB,EACxB,SAAiB;IAEjB,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,CAAA;IAE3E,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,IAAI,OAAO,GAAG,KAAK,CAAA;QACnB,MAAM,IAAI,GAAG,CAAC,MAA8D,EAAE,EAAE;YAC9E,IAAI,OAAO;gBAAE,OAAM;YACnB,OAAO,GAAG,IAAI,CAAA;YACd,YAAY,CAAC,KAAK,CAAC,CAAA;YACnB,UAAU,EAAE,CAAA;YACZ,UAAU,EAAE,CAAA;YACZ,OAAO,CAAC,MAAM,CAAC,CAAA;QACjB,CAAC,CAAA;QAED,MAAM,UAAU,GAAG,QAAQ,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,EAAE;YACnD,IAAI,KAAK,CAAC,CAAC,KAAK,cAAc;gBAAE,OAAO,KAAK,CAAA;YAC5C,IAAI,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC7E,IAAI,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC,CAAA;gBACzD,OAAO,IAAI,CAAA;YACb,CAAC;YACD,OAAO,KAAK,CAAA;QACd,CAAC,CAAC,CAAA;QAEF,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE;YAC5C,IAAI,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAA;QAC/C,CAAC,CAAC,CAAA;QAEF,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,IAAI,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAA;QAC/C,CAAC,EAAE,SAAS,CAAC,CAAA;IACf,CAAC,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import type { PairingRegistry } from './pairing-registry.js'\nimport type { ServerFrame } from '../../protocol.js'\n\nexport type RpcError = {\n code: 'paused' | 'invalid' | 'timeout' | 'schema-error' | 'internal' | string\n detail?: string\n}\n\nexport type RpcOptions = { timeoutMs?: number }\n\n/**\n * Send an `rpc` frame to the paired browser and await its\n * matching `rpc-reply` / `rpc-error`. Runs its own one-shot frame\n * subscription against the registry — no state stored on the\n * registry itself, which keeps the registry small enough to\n * implement in a Durable Object or other stateful primitive.\n *\n * Rejects with `{code: 'paused'}` when the pairing is absent,\n * `{code: 'timeout'}` when the browser doesn't reply in time,\n * or whatever the browser sent in its `rpc-error` frame otherwise.\n */\nexport async function rpc(\n registry: PairingRegistry,\n tid: string,\n tool: string,\n args: unknown,\n opts: RpcOptions = {},\n): Promise<unknown> {\n if (!registry.isPaired(tid)) {\n const err: RpcError = { code: 'paused' }\n throw err\n }\n const id = crypto.randomUUID()\n const timeoutMs = opts.timeoutMs ?? 15_000\n\n return new Promise((resolve, reject) => {\n let settled = false\n const done = (fn: () => void) => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n unsubFrame()\n unsubClose()\n fn()\n }\n\n const unsubFrame = registry.subscribe(tid, (frame) => {\n if (frame.t === 'rpc-reply' && frame.id === id) {\n done(() => resolve(frame.result))\n return true\n }\n if (frame.t === 'rpc-error' && frame.id === id) {\n done(() => reject({ code: frame.code, detail: frame.detail } satisfies RpcError))\n return true\n }\n return false\n })\n\n const unsubClose = registry.onClose(tid, () => {\n done(() => reject({ code: 'paused' } satisfies RpcError))\n })\n\n const timer = setTimeout(() => {\n done(() => reject({ code: 'timeout' } satisfies RpcError))\n }, timeoutMs)\n\n try {\n const frame: ServerFrame = { t: 'rpc', id, tool, args }\n registry.send(tid, frame)\n } catch (e) {\n done(() => reject({ code: 'internal', detail: String(e) } satisfies RpcError))\n }\n })\n}\n\n/**\n * Await a `confirm-resolved` frame for the given `confirmId`.\n * Resolves with `{outcome: 'user-cancelled'}` on timeout or pairing\n * drop (approvals lapse when the user isn't present to act on them).\n */\nexport async function waitForConfirm(\n registry: PairingRegistry,\n tid: string,\n confirmId: string,\n timeoutMs: number,\n): Promise<{ outcome: 'confirmed' | 'user-cancelled'; stateAfter?: unknown }> {\n if (!registry.isPaired(tid)) return { outcome: 'user-cancelled' }\n\n return new Promise((resolve) => {\n let settled = false\n const done = (result: { outcome: 'confirmed' | 'user-cancelled'; stateAfter?: unknown }) => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n unsubFrame()\n unsubClose()\n resolve(result)\n }\n\n const unsubFrame = registry.subscribe(tid, (frame) => {\n if (frame.t === 'confirm-resolved' && frame.confirmId === confirmId) {\n done({ outcome: frame.outcome, stateAfter: frame.stateAfter })\n return true\n }\n return false\n })\n\n const unsubClose = registry.onClose(tid, () => {\n done({ outcome: 'user-cancelled' })\n })\n\n const timer = setTimeout(() => {\n done({ outcome: 'user-cancelled' })\n }, timeoutMs)\n })\n}\n\n/**\n * Await a `state-update` frame whose path matches (exact or prefix).\n * Used by the long-poll `/lap/v1/wait` endpoint for external state\n * pushes (WebSocket messages, timers) arriving while the LLM is idle.\n */\nexport async function waitForChange(\n registry: PairingRegistry,\n tid: string,\n path: string | undefined,\n timeoutMs: number,\n): Promise<{ status: 'changed' | 'timeout'; stateAfter: unknown }> {\n if (!registry.isPaired(tid)) return { status: 'timeout', stateAfter: null }\n\n return new Promise((resolve) => {\n let settled = false\n const done = (result: { status: 'changed' | 'timeout'; stateAfter: unknown }) => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n unsubFrame()\n unsubClose()\n resolve(result)\n }\n\n const unsubFrame = registry.subscribe(tid, (frame) => {\n if (frame.t !== 'state-update') return false\n if (path === undefined || frame.path === path || frame.path.startsWith(path)) {\n done({ status: 'changed', stateAfter: frame.stateAfter })\n return true\n }\n return false\n })\n\n const unsubClose = registry.onClose(tid, () => {\n done({ status: 'timeout', stateAfter: null })\n })\n\n const timer = setTimeout(() => {\n done({ status: 'timeout', stateAfter: null })\n }, timeoutMs)\n })\n}\n"]}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { IncomingMessage } from 'node:http';
|
|
2
2
|
import type { Duplex } from 'node:stream';
|
|
3
|
-
import type {
|
|
3
|
+
import type { PairingRegistry } from './pairing-registry.js';
|
|
4
4
|
import type { TokenStore } from '../token-store.js';
|
|
5
5
|
import type { AuditSink } from '../audit.js';
|
|
6
6
|
export type UpgradeDeps = {
|
|
7
7
|
signingKey: string | Uint8Array;
|
|
8
8
|
tokenStore: TokenStore;
|
|
9
|
-
registry:
|
|
9
|
+
registry: PairingRegistry;
|
|
10
10
|
auditSink: AuditSink;
|
|
11
11
|
now?: () => number;
|
|
12
12
|
};
|
|
@@ -19,5 +19,5 @@ export type UpgradeDeps = {
|
|
|
19
19
|
*
|
|
20
20
|
* Spec §10.2, §10.4.
|
|
21
21
|
*/
|
|
22
|
-
export declare function createWsUpgradeHandler(deps: UpgradeDeps): (req: IncomingMessage, socket: Duplex, head: Buffer) => void
|
|
22
|
+
export declare function createWsUpgradeHandler(deps: UpgradeDeps): (req: IncomingMessage, socket: Duplex, head: Buffer) => Promise<void>;
|
|
23
23
|
//# sourceMappingURL=upgrade.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upgrade.d.ts","sourceRoot":"","sources":["../../../src/server/ws/upgrade.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"upgrade.d.ts","sourceRoot":"","sources":["../../../src/server/ws/upgrade.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,eAAe,EAAqB,MAAM,uBAAuB,CAAA;AAC/E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAI5C,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,EAAE,MAAM,GAAG,UAAU,CAAA;IAC/B,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,EAAE,eAAe,CAAA;IACzB,SAAS,EAAE,SAAS,CAAA;IACpB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB,CAAA;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,WAAW,IAIxC,KAAK,eAAe,EAAE,QAAQ,MAAM,EAAE,MAAM,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC,CAoEjF"}
|
|
@@ -12,7 +12,7 @@ import { verifyToken } from '../token.js';
|
|
|
12
12
|
export function createWsUpgradeHandler(deps) {
|
|
13
13
|
const wss = new WebSocketServer({ noServer: true });
|
|
14
14
|
const now = deps.now ?? (() => Date.now());
|
|
15
|
-
return (req, socket, head) => {
|
|
15
|
+
return async (req, socket, head) => {
|
|
16
16
|
// Path check
|
|
17
17
|
const url = new URL(req.url ?? '/', 'http://localhost');
|
|
18
18
|
if (url.pathname !== '/agent/ws') {
|
|
@@ -33,7 +33,7 @@ export function createWsUpgradeHandler(deps) {
|
|
|
33
33
|
socket.destroy();
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
|
-
const verified = verifyToken(token, deps.signingKey);
|
|
36
|
+
const verified = await verifyToken(token, deps.signingKey);
|
|
37
37
|
if (verified.kind !== 'ok') {
|
|
38
38
|
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
|
|
39
39
|
socket.destroy();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upgrade.js","sourceRoot":"","sources":["../../../src/server/ws/upgrade.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,IAAI,CAAA;AAOpC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAWzC;;;;;;;;GAQG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAAiB;IACtD,MAAM,GAAG,GAAG,IAAI,eAAe,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;IACnD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;IAE1C,OAAO,
|
|
1
|
+
{"version":3,"file":"upgrade.js","sourceRoot":"","sources":["../../../src/server/ws/upgrade.ts"],"names":[],"mappings":"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","sourcesContent":["import { WebSocketServer } from 'ws'\nimport type { WebSocket } from 'ws'\nimport type { IncomingMessage } from 'node:http'\nimport type { Duplex } from 'node:stream'\nimport type { PairingRegistry, PairingConnection } from './pairing-registry.js'\nimport type { TokenStore } from '../token-store.js'\nimport type { AuditSink } from '../audit.js'\nimport { verifyToken } from '../token.js'\nimport type { ClientFrame, ServerFrame } from '../../protocol.js'\n\nexport type UpgradeDeps = {\n signingKey: string | Uint8Array\n tokenStore: TokenStore\n registry: PairingRegistry\n auditSink: AuditSink\n now?: () => number\n}\n\n/**\n * Returns a handler for `server.on('upgrade', ...)`. Validates the token\n * from the query string, attaches to the registry, wires frame/close\n * routing. Unauthorized paths/tokens get a bare HTTP error response on\n * the raw socket (per RFC 6455 the response must be sent before the\n * socket is torn down).\n *\n * Spec §10.2, §10.4.\n */\nexport function createWsUpgradeHandler(deps: UpgradeDeps) {\n const wss = new WebSocketServer({ noServer: true })\n const now = deps.now ?? (() => Date.now())\n\n return async (req: IncomingMessage, socket: Duplex, head: Buffer): Promise<void> => {\n // Path check\n const url = new URL(req.url ?? '/', 'http://localhost')\n if (url.pathname !== '/agent/ws') {\n socket.write('HTTP/1.1 404 Not Found\\r\\n\\r\\n')\n socket.destroy()\n return\n }\n\n // Token — try query string first, then Authorization header\n let token = url.searchParams.get('token')\n if (!token) {\n const auth = req.headers['authorization']\n if (typeof auth === 'string' && auth.startsWith('Bearer ')) {\n token = auth.slice('Bearer '.length)\n }\n }\n if (!token) {\n socket.write('HTTP/1.1 401 Unauthorized\\r\\n\\r\\n')\n socket.destroy()\n return\n }\n\n const verified = await verifyToken(token, deps.signingKey)\n if (verified.kind !== 'ok') {\n socket.write('HTTP/1.1 401 Unauthorized\\r\\n\\r\\n')\n socket.destroy()\n return\n }\n const { tid } = verified.payload\n\n // Perform upgrade, wire to registry\n wss.handleUpgrade(req, socket, head, (ws: WebSocket) => {\n const conn: PairingConnection = {\n send(frame: ServerFrame) {\n ws.send(JSON.stringify(frame))\n },\n onFrame(handler) {\n ws.on('message', (data: Buffer | string) => {\n const raw = typeof data === 'string' ? data : data.toString('utf8')\n try {\n const parsed = JSON.parse(raw) as ClientFrame\n handler(parsed)\n } catch {\n // Ignore malformed frames.\n }\n })\n },\n onClose(handler) {\n ws.on('close', handler)\n },\n close() {\n ws.close()\n },\n }\n deps.registry.register(tid, conn)\n\n // Transition status: browser WS is now live, waiting for Claude's first call.\n void deps.tokenStore.markAwaitingClaude(tid, now())\n void deps.auditSink.write({\n at: now(),\n tid,\n uid: null,\n event: 'claim',\n detail: { transport: 'ws' },\n })\n })\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llui/agent",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.31",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"exports": {
|
|
@@ -8,6 +8,18 @@
|
|
|
8
8
|
"types": "./dist/server/index.d.ts",
|
|
9
9
|
"import": "./dist/server/index.js"
|
|
10
10
|
},
|
|
11
|
+
"./server/core": {
|
|
12
|
+
"types": "./dist/server/core-entry.d.ts",
|
|
13
|
+
"import": "./dist/server/core-entry.js"
|
|
14
|
+
},
|
|
15
|
+
"./server/web": {
|
|
16
|
+
"types": "./dist/server/web/index.d.ts",
|
|
17
|
+
"import": "./dist/server/web/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./server/cloudflare": {
|
|
20
|
+
"types": "./dist/server/cloudflare/index.d.ts",
|
|
21
|
+
"import": "./dist/server/cloudflare/index.js"
|
|
22
|
+
},
|
|
11
23
|
"./client": {
|
|
12
24
|
"types": "./dist/client/index.d.ts",
|
|
13
25
|
"import": "./dist/client/index.js"
|