@llui/agent 0.0.48 → 0.0.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.
Files changed (67) hide show
  1. package/dist/client/agentAttention.d.ts +129 -0
  2. package/dist/client/agentAttention.d.ts.map +1 -0
  3. package/dist/client/agentAttention.js +156 -0
  4. package/dist/client/agentAttention.js.map +1 -0
  5. package/dist/client/agentChat.d.ts +100 -0
  6. package/dist/client/agentChat.d.ts.map +1 -0
  7. package/dist/client/agentChat.js +84 -0
  8. package/dist/client/agentChat.js.map +1 -0
  9. package/dist/client/agentLog.d.ts +17 -0
  10. package/dist/client/agentLog.d.ts.map +1 -1
  11. package/dist/client/agentLog.js +18 -0
  12. package/dist/client/agentLog.js.map +1 -1
  13. package/dist/client/diff-render.d.ts +68 -0
  14. package/dist/client/diff-render.d.ts.map +1 -0
  15. package/dist/client/diff-render.js +141 -0
  16. package/dist/client/diff-render.js.map +1 -0
  17. package/dist/client/effect-handler.d.ts +29 -0
  18. package/dist/client/effect-handler.d.ts.map +1 -1
  19. package/dist/client/effect-handler.js +39 -0
  20. package/dist/client/effect-handler.js.map +1 -1
  21. package/dist/client/effects.d.ts +43 -0
  22. package/dist/client/effects.d.ts.map +1 -1
  23. package/dist/client/effects.js.map +1 -1
  24. package/dist/client/factory.d.ts +21 -0
  25. package/dist/client/factory.d.ts.map +1 -1
  26. package/dist/client/factory.js +15 -2
  27. package/dist/client/factory.js.map +1 -1
  28. package/dist/client/index.d.ts +4 -0
  29. package/dist/client/index.d.ts.map +1 -1
  30. package/dist/client/index.js +3 -0
  31. package/dist/client/index.js.map +1 -1
  32. package/dist/client/ws-client.d.ts +9 -0
  33. package/dist/client/ws-client.d.ts.map +1 -1
  34. package/dist/client/ws-client.js +120 -0
  35. package/dist/client/ws-client.js.map +1 -1
  36. package/dist/protocol.d.ts +103 -3
  37. package/dist/protocol.d.ts.map +1 -1
  38. package/dist/protocol.js.map +1 -1
  39. package/dist/server/cloudflare/durable-object.d.ts +41 -0
  40. package/dist/server/cloudflare/durable-object.d.ts.map +1 -1
  41. package/dist/server/cloudflare/durable-object.js +46 -0
  42. package/dist/server/cloudflare/durable-object.js.map +1 -1
  43. package/dist/server/cloudflare/index.d.ts +10 -3
  44. package/dist/server/cloudflare/index.d.ts.map +1 -1
  45. package/dist/server/cloudflare/index.js +10 -3
  46. package/dist/server/cloudflare/index.js.map +1 -1
  47. package/dist/server/core.d.ts +11 -1
  48. package/dist/server/core.d.ts.map +1 -1
  49. package/dist/server/core.js +1 -0
  50. package/dist/server/core.js.map +1 -1
  51. package/dist/server/lap/narrate.d.ts +31 -0
  52. package/dist/server/lap/narrate.d.ts.map +1 -0
  53. package/dist/server/lap/narrate.js +70 -0
  54. package/dist/server/lap/narrate.js.map +1 -0
  55. package/dist/server/lap/router.d.ts.map +1 -1
  56. package/dist/server/lap/router.js +6 -0
  57. package/dist/server/lap/router.js.map +1 -1
  58. package/dist/server/lap/wait-for-user-input.d.ts +13 -0
  59. package/dist/server/lap/wait-for-user-input.d.ts.map +1 -0
  60. package/dist/server/lap/wait-for-user-input.js +53 -0
  61. package/dist/server/lap/wait-for-user-input.js.map +1 -0
  62. package/dist/server/ws/pairing-registry.d.ts +101 -0
  63. package/dist/server/ws/pairing-registry.d.ts.map +1 -1
  64. package/dist/server/ws/pairing-registry.js +160 -0
  65. package/dist/server/ws/pairing-registry.js.map +1 -1
  66. package/package.json +7 -5
  67. package/styles/agent-panel.css +153 -0
@@ -1 +1 @@
1
- {"version":3,"file":"core.js","sourceRoot":"","sources":["../../src/server/core.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAClE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAExC,MAAM,kBAAkB,GAAqB,KAAK,IAAI,EAAE,CAAC,IAAI,CAAA;AAsE7D;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAoB,EAAE;IACxD,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,IAAI,kBAAkB,EAAE,CAAA;IAC9D,MAAM,gBAAgB,GAAG,IAAI,CAAC,gBAAgB,IAAI,kBAAkB,CAAA;IACpE,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,gBAAgB,CAAA;IACpD,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,kBAAkB,CAAC,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,CAAA;IACtF,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,eAAe,CAAA;IACvD,MAAM,oBAAoB,GAAG,IAAI,CAAC,oBAAoB,IAAI,MAAM,CAAA;IAEhE,MAAM,QAAQ,GACZ,IAAI,CAAC,QAAQ;QACb,IAAI,uBAAuB,CAAC;YAC1B,WAAW,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE;gBAC1B,KAAK,SAAS,CAAC,KAAK,CAAC;oBACnB,EAAE,EAAE,KAAK,CAAC,EAAE;oBACZ,GAAG;oBACH,GAAG,EAAE,IAAI;oBACT,KAAK,EAAE,UAAU;oBACjB,MAAM,EAAE;wBACN,MAAM,EAAE,YAAY;wBACpB,IAAI,EAAE,KAAK,CAAC,IAAI;wBAChB,OAAO,EAAE,KAAK,CAAC,OAAO;wBACtB,MAAM,EAAE,KAAK,CAAC,MAAM;qBACrB;iBACF,CAAC,CAAA;YACJ,CAAC;SACF,CAAC,CAAA;IAEJ,MAAM,UAAU,GAAG,gBAAgB,CAAC;QAClC,UAAU;QACV,gBAAgB;QAChB,SAAS;QACT,WAAW;KACZ,CAAC,CAAA;IAEF,MAAM,SAAS,GAAG,eAAe,CAC/B;QACE,UAAU;QACV,QAAQ;QACR,SAAS;QACT,WAAW;KACZ,EACD,WAAW,CACZ,CAAA;IAED,MAAM,MAAM,GAA8B,KAAK,EAAE,GAAG,EAAE,EAAE;QACtD,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,CAAA;QACnC,IAAI,MAAM;YAAE,OAAO,MAAM,CAAA;QACzB,OAAO,UAAU,CAAC,GAAG,CAAC,CAAA;IACxB,CAAC,CAAA;IAED,MAAM,gBAAgB,GAAwC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QAClF,gEAAgE;QAChE,6CAA6C;QAC7C,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,KAAK,CAAC,CAAA;QACrC,IAAI,CAAC,IAAI;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,aAAa,EAAE,CAAA;QACjE,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,eAAe,CAAC,IAAI,CAAC,CAAA;QAClD,IAAI,CAAC,GAAG;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,aAAa,EAAE,CAAA;QAChE,IAAI,GAAG,CAAC,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,aAAa,EAAE,CAAA;QACvF,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,CAAA;QAChF,gEAAgE;QAChE,6DAA6D;QAC7D,8DAA8D;QAC9D,iCAAiC;QACjC,IACE,GAAG,CAAC,MAAM,KAAK,gBAAgB;YAC/B,GAAG,CAAC,kBAAkB,KAAK,IAAI;YAC/B,GAAG,CAAC,kBAAkB,IAAI,IAAI,CAAC,GAAG,EAAE,EACpC,CAAC;YACD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,aAAa,EAAE,CAAA;QACxD,CAAC;QACD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAA;QACnB,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,KAAK,gBAAgB,CAAA;QAChD,QAAQ,CAAC,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;QAC5B,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACxB,IAAI,QAAQ,EAAE,CAAC;YACb,2DAA2D;YAC3D,0DAA0D;YAC1D,+DAA+D;YAC/D,6DAA6D;YAC7D,gEAAgE;YAChE,MAAM,UAAU,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,KAAK,IAAI,eAAe,EAAE,KAAK,CAAC,CAAA;YACrE,6DAA6D;YAC7D,2DAA2D;YAC3D,iDAAiD;YACjD,2DAA2D;YAC3D,0DAA0D;YAC1D,wCAAwC;YACxC,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAA;QACrC,CAAC;aAAM,CAAC;YACN,MAAM,UAAU,CAAC,kBAAkB,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;QACjD,CAAC;QACD,8DAA8D;QAC9D,+DAA+D;QAC/D,6DAA6D;QAC7D,8DAA8D;QAC9D,6DAA6D;QAC7D,8DAA8D;QAC9D,mCAAmC;QACnC,IAAI,oBAAoB,GAAG,CAAC,EAAE,CAAC;YAC7B,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE;gBACzB,KAAK,UAAU,CAAC,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,oBAAoB,CAAC,CAAA;YAC3E,CAAC,CAAC,CAAA;QACJ,CAAC;QACD,MAAM,SAAS,CAAC,KAAK,CAAC;YACpB,EAAE,EAAE,KAAK;YACT,GAAG;YACH,GAAG,EAAE,IAAI;YACT,KAAK,EAAE,OAAO;YACd,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE;SAC9C,CAAC,CAAA;QACF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAA;IAC1B,CAAC,CAAA;IAED,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,gBAAgB,EAAE,CAAA;AACtE,CAAC","sourcesContent":["/**\n * Runtime-neutral core of the LLui agent server. Exports everything\n * that works on any runtime with `crypto.subtle` + `Request`/`Response`\n * + long-lived connection primitives — in practice: Node, Bun, Deno,\n * Deno Deploy, Cloudflare Workers + Durable Objects.\n *\n * Intentionally does NOT import the `ws` library or any `node:*`\n * modules. Node-specific wiring lives in `./factory.ts`\n * (`createLluiAgentServer`); web runtimes use `./web/` adapters on\n * top of this core.\n */\nimport type { TokenStore } from './token-store.js'\nimport type { IdentityResolver } from './identity.js'\nimport type { AuditSink } from './audit.js'\nimport type { RateLimiter } from './rate-limit.js'\nimport type { PairingConnection, PairingRegistry } from './ws/pairing-registry.js'\nimport { InMemoryTokenStore } from './token-store.js'\nimport { consoleAuditSink } from './audit.js'\nimport { defaultRateLimiter } from './rate-limit.js'\nimport { createHttpRouter } from './http/router.js'\nimport { createLapRouter } from './lap/router.js'\nimport { InMemoryPairingRegistry } from './ws/pairing-registry.js'\nimport { tokenHashOf } from './token.js'\n\nconst ANONYMOUS_RESOLVER: IdentityResolver = async () => null\n\n/**\n * Options accepted by `createLluiAgentCore`. Strict subset of\n * `ServerOptions` — everything needed to build the router, registry,\n * and accept-connection primitive. The Node factory adds WebSocket\n * upgrade wiring on top.\n */\nexport type CoreOptions = {\n tokenStore?: TokenStore\n identityResolver?: IdentityResolver\n auditSink?: AuditSink\n rateLimiter?: RateLimiter\n lapBasePath?: string\n /**\n * Override the default `InMemoryPairingRegistry`. Web runtimes that\n * need a different pairing implementation (e.g. a Cloudflare\n * Durable Object that persists across isolates) pass it here.\n */\n registry?: PairingRegistry\n /**\n * How long, in milliseconds, a token's record stays in\n * `pending-resume` after the WS pairing closes. During this window\n * the same browser can reconnect with the same bearer token and\n * the WS re-pairs without going through the rotate-on-resume path\n * (`/resume/claim`). The agent's existing token stays valid the\n * whole time, so brief network drops, page reloads, and quick\n * server restarts don't invalidate the agent's session.\n *\n * After the window, LAP calls report `X-LLui-Reconnect: expired`\n * and the record becomes resume-claimable (rotation required).\n * Set to `0` to opt out — the WS close immediately drops the\n * record and any reconnect must go through `/resume/claim`.\n *\n * Default: 60 seconds — long enough for laptop sleep, brief Wi-Fi\n * flicker, and a server restart; short enough that a deliberately-\n * closed tab doesn't keep the record alive forever.\n */\n pendingResumeGraceMs?: number\n}\n\nexport type AcceptResult =\n | { ok: true; tid: string }\n | { ok: false; status: number; code: 'auth-failed' | 'revoked' }\n\n/**\n * Handle returned by `createLluiAgentCore`. Purely runtime-neutral —\n * `router` is a Fetch-style handler, `acceptConnection` is the\n * primitive that runtime-specific WebSocket adapters call after\n * accepting a socket in their native way.\n */\nexport type AgentCoreHandle = {\n router: (req: Request) => Promise<Response | null>\n registry: PairingRegistry\n tokenStore: TokenStore\n auditSink: AuditSink\n /**\n * Validate an agent token and register a `PairingConnection` with\n * the registry. Use this after accepting a WebSocket upgrade via\n * your runtime's native API (e.g. `WebSocketPair` on Cloudflare,\n * `Deno.upgradeWebSocket` on Deno, `server.upgrade` on Bun).\n *\n * On success: marks the token `awaiting-claude`, writes an audit\n * entry, and returns `{ok: true, tid}`. On failure: returns an\n * appropriate HTTP status for the caller to encode into the\n * upgrade response (401 for auth failure, 403 for revoked).\n */\n acceptConnection: (token: string, conn: PairingConnection) => Promise<AcceptResult>\n}\n\n/**\n * Compose the runtime-neutral agent server. The returned handle has\n * everything the LAP HTTP routes and the WebSocket acceptance\n * plumbing need; runtime adapters wire the native upgrade API on\n * top (see `@llui/agent/server` for Node, `@llui/agent/server/web`\n * for WHATWG runtimes).\n */\nexport function createLluiAgentCore(opts: CoreOptions = {}): AgentCoreHandle {\n const tokenStore = opts.tokenStore ?? new InMemoryTokenStore()\n const identityResolver = opts.identityResolver ?? ANONYMOUS_RESOLVER\n const auditSink = opts.auditSink ?? consoleAuditSink\n const rateLimiter = opts.rateLimiter ?? defaultRateLimiter({ perBucket: '30/minute' })\n const lapBasePath = opts.lapBasePath ?? '/agent/lap/v1'\n const pendingResumeGraceMs = opts.pendingResumeGraceMs ?? 60_000\n\n const registry: PairingRegistry =\n opts.registry ??\n new InMemoryPairingRegistry({\n onLogAppend: (tid, entry) => {\n void auditSink.write({\n at: entry.at,\n tid,\n uid: null,\n event: 'lap-call',\n detail: {\n source: 'client-log',\n kind: entry.kind,\n variant: entry.variant,\n intent: entry.intent,\n },\n })\n },\n })\n\n const httpRouter = createHttpRouter({\n tokenStore,\n identityResolver,\n auditSink,\n lapBasePath,\n })\n\n const lapRouter = createLapRouter(\n {\n tokenStore,\n registry,\n auditSink,\n rateLimiter,\n },\n lapBasePath,\n )\n\n const router: AgentCoreHandle['router'] = async (req) => {\n const lapRes = await lapRouter(req)\n if (lapRes) return lapRes\n return httpRouter(req)\n }\n\n const acceptConnection: AgentCoreHandle['acceptConnection'] = async (token, conn) => {\n // Same hash-lookup path as the LAP HTTP routes — keeps the auth\n // story uniform across HTTP and WS surfaces.\n const hash = await tokenHashOf(token)\n if (!hash) return { ok: false, status: 401, code: 'auth-failed' }\n const rec = await tokenStore.findByTokenHash(hash)\n if (!rec) return { ok: false, status: 401, code: 'auth-failed' }\n if (rec.expiresAt <= Date.now()) return { ok: false, status: 401, code: 'auth-failed' }\n if (rec.status === 'revoked') return { ok: false, status: 403, code: 'revoked' }\n // Reject `pending-resume` records past their grace window — the\n // agent has to go through `/resume/claim` (which rotates the\n // bearer) for those, since the long-gap path can't assume the\n // previous bearer wasn't leaked.\n if (\n rec.status === 'pending-resume' &&\n rec.pendingResumeUntil !== null &&\n rec.pendingResumeUntil <= Date.now()\n ) {\n return { ok: false, status: 401, code: 'auth-failed' }\n }\n const tid = rec.tid\n const isRepair = rec.status === 'pending-resume'\n registry.register(tid, conn)\n const nowMs = Date.now()\n if (isRepair) {\n // Same browser came back within the grace window — re-pair\n // without a token rotation. Claude was already bound; its\n // existing token stays valid and the next LAP call sees a live\n // pairing again. Restore the original label so audit context\n // doesn't show a \"reconnected\" placeholder bouncing in and out.\n await tokenStore.markActive(tid, rec.label ?? '(reconnected)', nowMs)\n // Tell the browser the pairing is live again so its connect-\n // panel flips from `pending-claude` (or `reconnecting`) to\n // `active`. Without this, the page would stay on\n // \"Waiting for AI to claim\" indefinitely after a refresh —\n // ensureActive on the next LAP call wouldn't fire either,\n // since the record is already `active`.\n registry.send(tid, { t: 'active' })\n } else {\n await tokenStore.markAwaitingClaude(tid, nowMs)\n }\n // Hook the close: when the WS drops, transition the record to\n // `pending-resume` with a TTL so the next reconnect within the\n // grace window can re-pair without rotating the token. After\n // grace, LAP calls return `X-LLui-Reconnect: expired` and the\n // agent must call `/resume/claim` to start fresh. The token-\n // store guards the transition so `revoke`/`expired` don't get\n // lifted back into a grace window.\n if (pendingResumeGraceMs > 0) {\n registry.onClose(tid, () => {\n void tokenStore.markPendingResume(tid, Date.now() + pendingResumeGraceMs)\n })\n }\n await auditSink.write({\n at: nowMs,\n tid,\n uid: null,\n event: 'claim',\n detail: { transport: 'ws', repair: isRepair },\n })\n return { ok: true, tid }\n }\n\n return { router, registry, tokenStore, auditSink, acceptConnection }\n}\n"]}
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../../src/server/core.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Runtime-neutral core of the LLui agent server. Exports everything\n * that works on any runtime with `crypto.subtle` + `Request`/`Response`\n * + long-lived connection primitives — in practice: Node, Bun, Deno,\n * Deno Deploy, Cloudflare Workers + Durable Objects.\n *\n * Intentionally does NOT import the `ws` library or any `node:*`\n * modules. Node-specific wiring lives in `./factory.ts`\n * (`createLluiAgentServer`); web runtimes use `./web/` adapters on\n * top of this core.\n */\nimport type { TokenStore } from './token-store.js'\nimport type { IdentityResolver } from './identity.js'\nimport type { AuditSink } from './audit.js'\nimport type { RateLimiter } from './rate-limit.js'\nimport type { PairingConnection, PairingRegistry, UserInputStorage } from './ws/pairing-registry.js'\nimport { InMemoryTokenStore } from './token-store.js'\nimport { consoleAuditSink } from './audit.js'\nimport { defaultRateLimiter } from './rate-limit.js'\nimport { createHttpRouter } from './http/router.js'\nimport { createLapRouter } from './lap/router.js'\nimport { InMemoryPairingRegistry } from './ws/pairing-registry.js'\nimport { tokenHashOf } from './token.js'\n\nconst ANONYMOUS_RESOLVER: IdentityResolver = async () => null\n\n/**\n * Options accepted by `createLluiAgentCore`. Strict subset of\n * `ServerOptions` — everything needed to build the router, registry,\n * and accept-connection primitive. The Node factory adds WebSocket\n * upgrade wiring on top.\n */\nexport type CoreOptions = {\n tokenStore?: TokenStore\n identityResolver?: IdentityResolver\n auditSink?: AuditSink\n rateLimiter?: RateLimiter\n lapBasePath?: string\n /**\n * Override the default `InMemoryPairingRegistry`. Web runtimes that\n * need a different pairing implementation (e.g. a Cloudflare\n * Durable Object that persists across isolates) pass it here.\n */\n registry?: PairingRegistry\n /**\n * Optional persistence adapter for the chat-composer's\n * `wait_for_user_input` buffer. Wired automatically into the\n * default `InMemoryPairingRegistry`; ignored when `opts.registry`\n * is overridden (custom registries supply their own persistence).\n * See `UserInputStorage` for the contract. Cloudflare DO hosts get\n * a ready-made adapter from\n * `@llui/agent/server/cloudflare`'s `makeDurableObjectUserInputStorage`.\n */\n userInputStorage?: UserInputStorage\n /**\n * How long, in milliseconds, a token's record stays in\n * `pending-resume` after the WS pairing closes. During this window\n * the same browser can reconnect with the same bearer token and\n * the WS re-pairs without going through the rotate-on-resume path\n * (`/resume/claim`). The agent's existing token stays valid the\n * whole time, so brief network drops, page reloads, and quick\n * server restarts don't invalidate the agent's session.\n *\n * After the window, LAP calls report `X-LLui-Reconnect: expired`\n * and the record becomes resume-claimable (rotation required).\n * Set to `0` to opt out — the WS close immediately drops the\n * record and any reconnect must go through `/resume/claim`.\n *\n * Default: 60 seconds — long enough for laptop sleep, brief Wi-Fi\n * flicker, and a server restart; short enough that a deliberately-\n * closed tab doesn't keep the record alive forever.\n */\n pendingResumeGraceMs?: number\n}\n\nexport type AcceptResult =\n | { ok: true; tid: string }\n | { ok: false; status: number; code: 'auth-failed' | 'revoked' }\n\n/**\n * Handle returned by `createLluiAgentCore`. Purely runtime-neutral —\n * `router` is a Fetch-style handler, `acceptConnection` is the\n * primitive that runtime-specific WebSocket adapters call after\n * accepting a socket in their native way.\n */\nexport type AgentCoreHandle = {\n router: (req: Request) => Promise<Response | null>\n registry: PairingRegistry\n tokenStore: TokenStore\n auditSink: AuditSink\n /**\n * Validate an agent token and register a `PairingConnection` with\n * the registry. Use this after accepting a WebSocket upgrade via\n * your runtime's native API (e.g. `WebSocketPair` on Cloudflare,\n * `Deno.upgradeWebSocket` on Deno, `server.upgrade` on Bun).\n *\n * On success: marks the token `awaiting-claude`, writes an audit\n * entry, and returns `{ok: true, tid}`. On failure: returns an\n * appropriate HTTP status for the caller to encode into the\n * upgrade response (401 for auth failure, 403 for revoked).\n */\n acceptConnection: (token: string, conn: PairingConnection) => Promise<AcceptResult>\n}\n\n/**\n * Compose the runtime-neutral agent server. The returned handle has\n * everything the LAP HTTP routes and the WebSocket acceptance\n * plumbing need; runtime adapters wire the native upgrade API on\n * top (see `@llui/agent/server` for Node, `@llui/agent/server/web`\n * for WHATWG runtimes).\n */\nexport function createLluiAgentCore(opts: CoreOptions = {}): AgentCoreHandle {\n const tokenStore = opts.tokenStore ?? new InMemoryTokenStore()\n const identityResolver = opts.identityResolver ?? ANONYMOUS_RESOLVER\n const auditSink = opts.auditSink ?? consoleAuditSink\n const rateLimiter = opts.rateLimiter ?? defaultRateLimiter({ perBucket: '30/minute' })\n const lapBasePath = opts.lapBasePath ?? '/agent/lap/v1'\n const pendingResumeGraceMs = opts.pendingResumeGraceMs ?? 60_000\n\n const registry: PairingRegistry =\n opts.registry ??\n new InMemoryPairingRegistry({\n onLogAppend: (tid, entry) => {\n void auditSink.write({\n at: entry.at,\n tid,\n uid: null,\n event: 'lap-call',\n detail: {\n source: 'client-log',\n kind: entry.kind,\n variant: entry.variant,\n intent: entry.intent,\n },\n })\n },\n userInputStorage: opts.userInputStorage,\n })\n\n const httpRouter = createHttpRouter({\n tokenStore,\n identityResolver,\n auditSink,\n lapBasePath,\n })\n\n const lapRouter = createLapRouter(\n {\n tokenStore,\n registry,\n auditSink,\n rateLimiter,\n },\n lapBasePath,\n )\n\n const router: AgentCoreHandle['router'] = async (req) => {\n const lapRes = await lapRouter(req)\n if (lapRes) return lapRes\n return httpRouter(req)\n }\n\n const acceptConnection: AgentCoreHandle['acceptConnection'] = async (token, conn) => {\n // Same hash-lookup path as the LAP HTTP routes — keeps the auth\n // story uniform across HTTP and WS surfaces.\n const hash = await tokenHashOf(token)\n if (!hash) return { ok: false, status: 401, code: 'auth-failed' }\n const rec = await tokenStore.findByTokenHash(hash)\n if (!rec) return { ok: false, status: 401, code: 'auth-failed' }\n if (rec.expiresAt <= Date.now()) return { ok: false, status: 401, code: 'auth-failed' }\n if (rec.status === 'revoked') return { ok: false, status: 403, code: 'revoked' }\n // Reject `pending-resume` records past their grace window — the\n // agent has to go through `/resume/claim` (which rotates the\n // bearer) for those, since the long-gap path can't assume the\n // previous bearer wasn't leaked.\n if (\n rec.status === 'pending-resume' &&\n rec.pendingResumeUntil !== null &&\n rec.pendingResumeUntil <= Date.now()\n ) {\n return { ok: false, status: 401, code: 'auth-failed' }\n }\n const tid = rec.tid\n const isRepair = rec.status === 'pending-resume'\n registry.register(tid, conn)\n const nowMs = Date.now()\n if (isRepair) {\n // Same browser came back within the grace window — re-pair\n // without a token rotation. Claude was already bound; its\n // existing token stays valid and the next LAP call sees a live\n // pairing again. Restore the original label so audit context\n // doesn't show a \"reconnected\" placeholder bouncing in and out.\n await tokenStore.markActive(tid, rec.label ?? '(reconnected)', nowMs)\n // Tell the browser the pairing is live again so its connect-\n // panel flips from `pending-claude` (or `reconnecting`) to\n // `active`. Without this, the page would stay on\n // \"Waiting for AI to claim\" indefinitely after a refresh —\n // ensureActive on the next LAP call wouldn't fire either,\n // since the record is already `active`.\n registry.send(tid, { t: 'active' })\n } else {\n await tokenStore.markAwaitingClaude(tid, nowMs)\n }\n // Hook the close: when the WS drops, transition the record to\n // `pending-resume` with a TTL so the next reconnect within the\n // grace window can re-pair without rotating the token. After\n // grace, LAP calls return `X-LLui-Reconnect: expired` and the\n // agent must call `/resume/claim` to start fresh. The token-\n // store guards the transition so `revoke`/`expired` don't get\n // lifted back into a grace window.\n if (pendingResumeGraceMs > 0) {\n registry.onClose(tid, () => {\n void tokenStore.markPendingResume(tid, Date.now() + pendingResumeGraceMs)\n })\n }\n await auditSink.write({\n at: nowMs,\n tid,\n uid: null,\n event: 'claim',\n detail: { transport: 'ws', repair: isRepair },\n })\n return { ok: true, tid }\n }\n\n return { router, registry, tokenStore, auditSink, acceptConnection }\n}\n"]}
@@ -0,0 +1,31 @@
1
+ import type { TokenStore } from '../token-store.js';
2
+ import type { PairingRegistry } from '../ws/pairing-registry.js';
3
+ import type { AuditSink } from '../audit.js';
4
+ import type { RateLimiter } from '../rate-limit.js';
5
+ export type LapNarrateDeps = {
6
+ tokenStore: TokenStore;
7
+ registry: PairingRegistry;
8
+ auditSink: AuditSink;
9
+ rateLimiter: RateLimiter;
10
+ now?: () => number;
11
+ };
12
+ /**
13
+ * `narrate` LAP handler. Synthesizes a `LogEntry { kind: 'narrate' }`
14
+ * and:
15
+ *
16
+ * 1. pushes a `log-push` server frame to the paired runtime so the
17
+ * in-app activity feed renders the narration in real time;
18
+ * 2. (the runtime echoes a `log-append` of the same id back to the
19
+ * server through the existing browser → server channel — that
20
+ * drives audit + recent-log persistence). The handler does NOT
21
+ * record into recent-log directly here, to keep ONE writer per
22
+ * buffer — server-side push, client-side echo, single audit
23
+ * pathway.
24
+ *
25
+ * The agent receives `{ ok: true }` once the server has accepted the
26
+ * narration. If the pairing is paused, the call returns 503 paused
27
+ * (same as every other LAP write) — the agent can retry once the
28
+ * runtime is back.
29
+ */
30
+ export declare function handleLapNarrate(req: Request, deps: LapNarrateDeps): Promise<Response>;
31
+ //# sourceMappingURL=narrate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"narrate.d.ts","sourceRoot":"","sources":["../../../src/server/lap/narrate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAOnD,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,EAAE,eAAe,CAAA;IACzB,SAAS,EAAE,SAAS,CAAA;IACpB,WAAW,EAAE,WAAW,CAAA;IACxB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB,CAAA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,QAAQ,CAAC,CA0C5F"}
@@ -0,0 +1,70 @@
1
+ import { verifyAndReadTid } from './describe.js';
2
+ import { buildPausedResponse } from './paused.js';
3
+ import { ensureActive } from './active.js';
4
+ import { randomUUID } from 'crypto';
5
+ /**
6
+ * `narrate` LAP handler. Synthesizes a `LogEntry { kind: 'narrate' }`
7
+ * and:
8
+ *
9
+ * 1. pushes a `log-push` server frame to the paired runtime so the
10
+ * in-app activity feed renders the narration in real time;
11
+ * 2. (the runtime echoes a `log-append` of the same id back to the
12
+ * server through the existing browser → server channel — that
13
+ * drives audit + recent-log persistence). The handler does NOT
14
+ * record into recent-log directly here, to keep ONE writer per
15
+ * buffer — server-side push, client-side echo, single audit
16
+ * pathway.
17
+ *
18
+ * The agent receives `{ ok: true }` once the server has accepted the
19
+ * narration. If the pairing is paused, the call returns 503 paused
20
+ * (same as every other LAP write) — the agent can retry once the
21
+ * runtime is back.
22
+ */
23
+ export async function handleLapNarrate(req, deps) {
24
+ const auth = await verifyAndReadTid(req, deps.tokenStore);
25
+ if (!auth.ok)
26
+ return json({ error: { code: auth.code } }, auth.status);
27
+ const rec = await deps.tokenStore.findByTid(auth.tid);
28
+ if (!rec || rec.status === 'revoked')
29
+ return json({ error: { code: 'revoked' } }, 403);
30
+ if (!deps.registry.isPaired(auth.tid))
31
+ return buildPausedResponse(deps.tokenStore, auth.tid);
32
+ const rlCheck = await deps.rateLimiter.check(auth.tid, 'token');
33
+ if (!rlCheck.allowed) {
34
+ return json({ error: { code: 'rate-limited', retryAfterMs: rlCheck.retryAfterMs } }, 429);
35
+ }
36
+ const body = ((await req.json().catch(() => null)) ?? {});
37
+ if (typeof body.text !== 'string' || body.text.length === 0) {
38
+ return json({ error: { code: 'invalid', detail: 'text required' } }, 400);
39
+ }
40
+ const nowMs = (deps.now ?? (() => Date.now()))();
41
+ const entry = {
42
+ id: `narrate-${nowMs}-${randomUUID().slice(0, 8)}`,
43
+ at: nowMs,
44
+ kind: 'narrate',
45
+ intent: body.intent ?? 'Agent narrated',
46
+ detail: body.text,
47
+ };
48
+ // Push to the paired runtime. The runtime's ws-client mirrors it via
49
+ // onLogEntry into local slices AND echoes a log-append frame back
50
+ // here, which the registry routes through its existing recent-log +
51
+ // audit-sink path — so we don't need to double-record server-side.
52
+ deps.registry.send(auth.tid, { t: 'log-push', entry });
53
+ await ensureActive(deps.tokenStore, deps.registry, auth.tid, rec, nowMs);
54
+ await deps.auditSink.write({
55
+ at: nowMs,
56
+ tid: auth.tid,
57
+ uid: rec.uid,
58
+ event: 'lap-call',
59
+ detail: { path: '/lap/v1/narrate', outcome: 'ok' },
60
+ });
61
+ const out = { ok: true };
62
+ return json(out, 200);
63
+ }
64
+ function json(b, s) {
65
+ return new Response(JSON.stringify(b), {
66
+ status: s,
67
+ headers: { 'content-type': 'application/json' },
68
+ });
69
+ }
70
+ //# sourceMappingURL=narrate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"narrate.js","sourceRoot":"","sources":["../../../src/server/lap/narrate.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TokenStore } from '../token-store.js'\nimport type { PairingRegistry } from '../ws/pairing-registry.js'\nimport type { AuditSink } from '../audit.js'\nimport type { RateLimiter } from '../rate-limit.js'\nimport { verifyAndReadTid } from './describe.js'\nimport { buildPausedResponse } from './paused.js'\nimport { ensureActive } from './active.js'\nimport type { LapNarrateRequest, LapNarrateResponse, LogEntry } from '../../protocol.js'\nimport { randomUUID } from 'crypto'\n\nexport type LapNarrateDeps = {\n tokenStore: TokenStore\n registry: PairingRegistry\n auditSink: AuditSink\n rateLimiter: RateLimiter\n now?: () => number\n}\n\n/**\n * `narrate` LAP handler. Synthesizes a `LogEntry { kind: 'narrate' }`\n * and:\n *\n * 1. pushes a `log-push` server frame to the paired runtime so the\n * in-app activity feed renders the narration in real time;\n * 2. (the runtime echoes a `log-append` of the same id back to the\n * server through the existing browser → server channel — that\n * drives audit + recent-log persistence). The handler does NOT\n * record into recent-log directly here, to keep ONE writer per\n * buffer — server-side push, client-side echo, single audit\n * pathway.\n *\n * The agent receives `{ ok: true }` once the server has accepted the\n * narration. If the pairing is paused, the call returns 503 paused\n * (same as every other LAP write) — the agent can retry once the\n * runtime is back.\n */\nexport async function handleLapNarrate(req: Request, deps: LapNarrateDeps): Promise<Response> {\n const auth = await verifyAndReadTid(req, deps.tokenStore)\n if (!auth.ok) return json({ error: { code: auth.code } }, auth.status)\n\n const rec = await deps.tokenStore.findByTid(auth.tid)\n if (!rec || rec.status === 'revoked') return json({ error: { code: 'revoked' } }, 403)\n if (!deps.registry.isPaired(auth.tid)) return buildPausedResponse(deps.tokenStore, auth.tid)\n\n const rlCheck = await deps.rateLimiter.check(auth.tid, 'token')\n if (!rlCheck.allowed) {\n return json({ error: { code: 'rate-limited', retryAfterMs: rlCheck.retryAfterMs } }, 429)\n }\n\n const body = ((await req.json().catch(() => null)) ?? {}) as LapNarrateRequest\n if (typeof body.text !== 'string' || body.text.length === 0) {\n return json({ error: { code: 'invalid', detail: 'text required' } }, 400)\n }\n\n const nowMs = (deps.now ?? (() => Date.now()))()\n const entry: LogEntry = {\n id: `narrate-${nowMs}-${randomUUID().slice(0, 8)}`,\n at: nowMs,\n kind: 'narrate',\n intent: body.intent ?? 'Agent narrated',\n detail: body.text,\n }\n // Push to the paired runtime. The runtime's ws-client mirrors it via\n // onLogEntry into local slices AND echoes a log-append frame back\n // here, which the registry routes through its existing recent-log +\n // audit-sink path — so we don't need to double-record server-side.\n deps.registry.send(auth.tid, { t: 'log-push', entry })\n\n await ensureActive(deps.tokenStore, deps.registry, auth.tid, rec, nowMs)\n await deps.auditSink.write({\n at: nowMs,\n tid: auth.tid,\n uid: rec.uid,\n event: 'lap-call',\n detail: { path: '/lap/v1/narrate', outcome: 'ok' },\n })\n const out: LapNarrateResponse = { ok: true }\n return json(out, 200)\n}\n\nfunction json(b: unknown, s: number): Response {\n return new Response(JSON.stringify(b), {\n status: s,\n headers: { 'content-type': 'application/json' },\n })\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../../src/server/lap/router.ts"],"names":[],"mappings":"AACA,OAAO,EASL,KAAK,WAAW,EACjB,MAAM,cAAc,CAAA;AAMrB,MAAM,MAAM,aAAa,GAAG,WAAW,CAAA;AAEvC,wBAAgB,eAAe,CAC7B,IAAI,EAAE,aAAa,EACnB,QAAQ,EAAE,MAAM,GACf,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAqC5C"}
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../../src/server/lap/router.ts"],"names":[],"mappings":"AACA,OAAO,EASL,KAAK,WAAW,EACjB,MAAM,cAAc,CAAA;AAQrB,MAAM,MAAM,aAAa,GAAG,WAAW,CAAA;AAEvC,wBAAgB,eAAe,CAC7B,IAAI,EAAE,aAAa,EACnB,QAAQ,EAAE,MAAM,GACf,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAyC5C"}
@@ -2,6 +2,8 @@ import { handleLapDescribe } from './describe.js';
2
2
  import { handleLapState, handleLapQueryState, handleLapActions, handleLapQueryDom, handleLapDescribeVisible, handleLapContext, handleLapRecentActions, handleLapWouldDispatch, } from './forward.js';
3
3
  import { handleLapMessage } from './message.js';
4
4
  import { handleLapWait } from './wait.js';
5
+ import { handleLapWaitForUserInput } from './wait-for-user-input.js';
6
+ import { handleLapNarrate } from './narrate.js';
5
7
  import { handleLapConfirmResult } from './confirm-result.js';
6
8
  import { handleLapObserve } from './observe.js';
7
9
  export function createLapRouter(deps, basePath) {
@@ -26,6 +28,10 @@ export function createLapRouter(deps, basePath) {
26
28
  return handleLapConfirmResult(req, deps);
27
29
  case '/wait':
28
30
  return handleLapWait(req, deps);
31
+ case '/wait-for-user-input':
32
+ return handleLapWaitForUserInput(req, deps);
33
+ case '/narrate':
34
+ return handleLapNarrate(req, deps);
29
35
  case '/query-dom':
30
36
  return handleLapQueryDom(req, deps);
31
37
  case '/describe-visible':
@@ -1 +1 @@
1
- {"version":3,"file":"router.js","sourceRoot":"","sources":["../../../src/server/lap/router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,wBAAwB,EACxB,gBAAgB,EAChB,sBAAsB,EACtB,sBAAsB,GAEvB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AACzC,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAI/C,MAAM,UAAU,eAAe,CAC7B,IAAmB,EACnB,QAAgB;IAEhB,OAAO,KAAK,EAAE,GAAG,EAAE,EAAE;QACnB,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC5B,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAA;QACzB,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,GAAG,GAAG,CAAC;YAAE,OAAO,IAAI,CAAA;QACjD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;QACxC,QAAQ,IAAI,EAAE,CAAC;YACb,KAAK,WAAW;gBACd,OAAO,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YACrC,KAAK,QAAQ;gBACX,OAAO,cAAc,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YAClC,KAAK,cAAc;gBACjB,OAAO,mBAAmB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YACvC,KAAK,UAAU;gBACb,OAAO,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YACpC,KAAK,UAAU;gBACb,OAAO,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YACpC,KAAK,iBAAiB;gBACpB,OAAO,sBAAsB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YAC1C,KAAK,OAAO;gBACV,OAAO,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YACjC,KAAK,YAAY;gBACf,OAAO,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YACrC,KAAK,mBAAmB;gBACtB,OAAO,wBAAwB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YAC5C,KAAK,UAAU;gBACb,OAAO,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YACpC,KAAK,UAAU;gBACb,OAAO,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YACpC,KAAK,iBAAiB;gBACpB,OAAO,sBAAsB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YAC1C,KAAK,iBAAiB;gBACpB,OAAO,sBAAsB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YAC1C;gBACE,OAAO,IAAI,CAAA;QACf,CAAC;IACH,CAAC,CAAA;AACH,CAAC","sourcesContent":["import { handleLapDescribe } from './describe.js'\nimport {\n handleLapState,\n handleLapQueryState,\n handleLapActions,\n handleLapQueryDom,\n handleLapDescribeVisible,\n handleLapContext,\n handleLapRecentActions,\n handleLapWouldDispatch,\n type ForwardDeps,\n} from './forward.js'\nimport { handleLapMessage } from './message.js'\nimport { handleLapWait } from './wait.js'\nimport { handleLapConfirmResult } from './confirm-result.js'\nimport { handleLapObserve } from './observe.js'\n\nexport type LapRouterDeps = ForwardDeps\n\nexport function createLapRouter(\n deps: LapRouterDeps,\n basePath: string,\n): (req: Request) => Promise<Response | null> {\n return async (req) => {\n const url = new URL(req.url)\n const path = url.pathname\n if (!path.startsWith(basePath + '/')) return null\n const tail = path.slice(basePath.length)\n switch (tail) {\n case '/describe':\n return handleLapDescribe(req, deps)\n case '/state':\n return handleLapState(req, deps)\n case '/query-state':\n return handleLapQueryState(req, deps)\n case '/actions':\n return handleLapActions(req, deps)\n case '/message':\n return handleLapMessage(req, deps)\n case '/confirm-result':\n return handleLapConfirmResult(req, deps)\n case '/wait':\n return handleLapWait(req, deps)\n case '/query-dom':\n return handleLapQueryDom(req, deps)\n case '/describe-visible':\n return handleLapDescribeVisible(req, deps)\n case '/context':\n return handleLapContext(req, deps)\n case '/observe':\n return handleLapObserve(req, deps)\n case '/recent-actions':\n return handleLapRecentActions(req, deps)\n case '/would-dispatch':\n return handleLapWouldDispatch(req, deps)\n default:\n return null\n }\n }\n}\n"]}
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../../../src/server/lap/router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,wBAAwB,EACxB,gBAAgB,EAChB,sBAAsB,EACtB,sBAAsB,GAEvB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AACzC,OAAO,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAA;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAI/C,MAAM,UAAU,eAAe,CAC7B,IAAmB,EACnB,QAAgB;IAEhB,OAAO,KAAK,EAAE,GAAG,EAAE,EAAE;QACnB,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC5B,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAA;QACzB,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,GAAG,GAAG,CAAC;YAAE,OAAO,IAAI,CAAA;QACjD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;QACxC,QAAQ,IAAI,EAAE,CAAC;YACb,KAAK,WAAW;gBACd,OAAO,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YACrC,KAAK,QAAQ;gBACX,OAAO,cAAc,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YAClC,KAAK,cAAc;gBACjB,OAAO,mBAAmB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YACvC,KAAK,UAAU;gBACb,OAAO,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YACpC,KAAK,UAAU;gBACb,OAAO,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YACpC,KAAK,iBAAiB;gBACpB,OAAO,sBAAsB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YAC1C,KAAK,OAAO;gBACV,OAAO,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YACjC,KAAK,sBAAsB;gBACzB,OAAO,yBAAyB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YAC7C,KAAK,UAAU;gBACb,OAAO,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YACpC,KAAK,YAAY;gBACf,OAAO,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YACrC,KAAK,mBAAmB;gBACtB,OAAO,wBAAwB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YAC5C,KAAK,UAAU;gBACb,OAAO,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YACpC,KAAK,UAAU;gBACb,OAAO,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YACpC,KAAK,iBAAiB;gBACpB,OAAO,sBAAsB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YAC1C,KAAK,iBAAiB;gBACpB,OAAO,sBAAsB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YAC1C;gBACE,OAAO,IAAI,CAAA;QACf,CAAC;IACH,CAAC,CAAA;AACH,CAAC","sourcesContent":["import { handleLapDescribe } from './describe.js'\nimport {\n handleLapState,\n handleLapQueryState,\n handleLapActions,\n handleLapQueryDom,\n handleLapDescribeVisible,\n handleLapContext,\n handleLapRecentActions,\n handleLapWouldDispatch,\n type ForwardDeps,\n} from './forward.js'\nimport { handleLapMessage } from './message.js'\nimport { handleLapWait } from './wait.js'\nimport { handleLapWaitForUserInput } from './wait-for-user-input.js'\nimport { handleLapNarrate } from './narrate.js'\nimport { handleLapConfirmResult } from './confirm-result.js'\nimport { handleLapObserve } from './observe.js'\n\nexport type LapRouterDeps = ForwardDeps\n\nexport function createLapRouter(\n deps: LapRouterDeps,\n basePath: string,\n): (req: Request) => Promise<Response | null> {\n return async (req) => {\n const url = new URL(req.url)\n const path = url.pathname\n if (!path.startsWith(basePath + '/')) return null\n const tail = path.slice(basePath.length)\n switch (tail) {\n case '/describe':\n return handleLapDescribe(req, deps)\n case '/state':\n return handleLapState(req, deps)\n case '/query-state':\n return handleLapQueryState(req, deps)\n case '/actions':\n return handleLapActions(req, deps)\n case '/message':\n return handleLapMessage(req, deps)\n case '/confirm-result':\n return handleLapConfirmResult(req, deps)\n case '/wait':\n return handleLapWait(req, deps)\n case '/wait-for-user-input':\n return handleLapWaitForUserInput(req, deps)\n case '/narrate':\n return handleLapNarrate(req, deps)\n case '/query-dom':\n return handleLapQueryDom(req, deps)\n case '/describe-visible':\n return handleLapDescribeVisible(req, deps)\n case '/context':\n return handleLapContext(req, deps)\n case '/observe':\n return handleLapObserve(req, deps)\n case '/recent-actions':\n return handleLapRecentActions(req, deps)\n case '/would-dispatch':\n return handleLapWouldDispatch(req, deps)\n default:\n return null\n }\n }\n}\n"]}
@@ -0,0 +1,13 @@
1
+ import type { TokenStore } from '../token-store.js';
2
+ import type { PairingRegistry } from '../ws/pairing-registry.js';
3
+ import type { AuditSink } from '../audit.js';
4
+ import type { RateLimiter } from '../rate-limit.js';
5
+ export type LapWaitForUserInputDeps = {
6
+ tokenStore: TokenStore;
7
+ registry: PairingRegistry;
8
+ auditSink: AuditSink;
9
+ rateLimiter: RateLimiter;
10
+ now?: () => number;
11
+ };
12
+ export declare function handleLapWaitForUserInput(req: Request, deps: LapWaitForUserInputDeps): Promise<Response>;
13
+ //# sourceMappingURL=wait-for-user-input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wait-for-user-input.d.ts","sourceRoot":"","sources":["../../../src/server/lap/wait-for-user-input.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAMnD,MAAM,MAAM,uBAAuB,GAAG;IACpC,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,EAAE,eAAe,CAAA;IACzB,SAAS,EAAE,SAAS,CAAA;IACpB,WAAW,EAAE,WAAW,CAAA;IACxB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB,CAAA;AAkBD,wBAAsB,yBAAyB,CAC7C,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,uBAAuB,GAC5B,OAAO,CAAC,QAAQ,CAAC,CA4BnB"}
@@ -0,0 +1,53 @@
1
+ import { verifyAndReadTid } from './describe.js';
2
+ import { buildPausedResponse } from './paused.js';
3
+ import { ensureActive } from './active.js';
4
+ /**
5
+ * Long-poll for the user's next chat-composer submission. Mirrors the
6
+ * shape of `/lap/v1/wait` (the state-change long-poll) — same auth,
7
+ * same paused/revoked gating, same rate-limit class — but waits on a
8
+ * different in-registry channel. The two endpoints are functionally
9
+ * independent: an agent can have both parked simultaneously (one for
10
+ * "watch state change", one for "wait for user to chime in"), and
11
+ * each resolves on its own signal.
12
+ *
13
+ * Default timeout 30s — chat replies don't always arrive quickly,
14
+ * and the agent's calling pattern is "park → react → park again",
15
+ * so keeping the default high reduces churn. The agent can shorten
16
+ * it per call when impatient.
17
+ */
18
+ const DEFAULT_TIMEOUT_MS = 30_000;
19
+ export async function handleLapWaitForUserInput(req, deps) {
20
+ const auth = await verifyAndReadTid(req, deps.tokenStore);
21
+ if (!auth.ok)
22
+ return json({ error: { code: auth.code } }, auth.status);
23
+ const rec = await deps.tokenStore.findByTid(auth.tid);
24
+ if (!rec || rec.status === 'revoked')
25
+ return json({ error: { code: 'revoked' } }, 403);
26
+ if (!deps.registry.isPaired(auth.tid))
27
+ return buildPausedResponse(deps.tokenStore, auth.tid);
28
+ const rlCheck = await deps.rateLimiter.check(auth.tid, 'token');
29
+ if (!rlCheck.allowed) {
30
+ return json({ error: { code: 'rate-limited', retryAfterMs: rlCheck.retryAfterMs } }, 429);
31
+ }
32
+ const body = ((await req.json().catch(() => null)) ?? {});
33
+ const timeoutMs = body.timeoutMs ?? DEFAULT_TIMEOUT_MS;
34
+ const result = await deps.registry.waitForUserInput(auth.tid, timeoutMs);
35
+ const out = result;
36
+ const nowMs = (deps.now ?? (() => Date.now()))();
37
+ await ensureActive(deps.tokenStore, deps.registry, auth.tid, rec, nowMs);
38
+ await deps.auditSink.write({
39
+ at: nowMs,
40
+ tid: auth.tid,
41
+ uid: rec.uid,
42
+ event: 'lap-call',
43
+ detail: { path: '/lap/v1/wait-for-user-input', outcome: result.status },
44
+ });
45
+ return json(out, 200);
46
+ }
47
+ function json(b, s) {
48
+ return new Response(JSON.stringify(b), {
49
+ status: s,
50
+ headers: { 'content-type': 'application/json' },
51
+ });
52
+ }
53
+ //# sourceMappingURL=wait-for-user-input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wait-for-user-input.js","sourceRoot":"","sources":["../../../src/server/lap/wait-for-user-input.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TokenStore } from '../token-store.js'\nimport type { PairingRegistry } from '../ws/pairing-registry.js'\nimport type { AuditSink } from '../audit.js'\nimport type { RateLimiter } from '../rate-limit.js'\nimport { verifyAndReadTid } from './describe.js'\nimport { buildPausedResponse } from './paused.js'\nimport { ensureActive } from './active.js'\nimport type { LapWaitForUserInputRequest, LapWaitForUserInputResponse } from '../../protocol.js'\n\nexport type LapWaitForUserInputDeps = {\n tokenStore: TokenStore\n registry: PairingRegistry\n auditSink: AuditSink\n rateLimiter: RateLimiter\n now?: () => number\n}\n\n/**\n * Long-poll for the user's next chat-composer submission. Mirrors the\n * shape of `/lap/v1/wait` (the state-change long-poll) — same auth,\n * same paused/revoked gating, same rate-limit class — but waits on a\n * different in-registry channel. The two endpoints are functionally\n * independent: an agent can have both parked simultaneously (one for\n * \"watch state change\", one for \"wait for user to chime in\"), and\n * each resolves on its own signal.\n *\n * Default timeout 30s — chat replies don't always arrive quickly,\n * and the agent's calling pattern is \"park → react → park again\",\n * so keeping the default high reduces churn. The agent can shorten\n * it per call when impatient.\n */\nconst DEFAULT_TIMEOUT_MS = 30_000\n\nexport async function handleLapWaitForUserInput(\n req: Request,\n deps: LapWaitForUserInputDeps,\n): Promise<Response> {\n const auth = await verifyAndReadTid(req, deps.tokenStore)\n if (!auth.ok) return json({ error: { code: auth.code } }, auth.status)\n\n const rec = await deps.tokenStore.findByTid(auth.tid)\n if (!rec || rec.status === 'revoked') return json({ error: { code: 'revoked' } }, 403)\n if (!deps.registry.isPaired(auth.tid)) return buildPausedResponse(deps.tokenStore, auth.tid)\n\n const rlCheck = await deps.rateLimiter.check(auth.tid, 'token')\n if (!rlCheck.allowed) {\n return json({ error: { code: 'rate-limited', retryAfterMs: rlCheck.retryAfterMs } }, 429)\n }\n\n const body = ((await req.json().catch(() => null)) ?? {}) as LapWaitForUserInputRequest\n const timeoutMs = body.timeoutMs ?? DEFAULT_TIMEOUT_MS\n const result = await deps.registry.waitForUserInput(auth.tid, timeoutMs)\n const out: LapWaitForUserInputResponse = result\n\n const nowMs = (deps.now ?? (() => Date.now()))()\n await ensureActive(deps.tokenStore, deps.registry, auth.tid, rec, nowMs)\n await deps.auditSink.write({\n at: nowMs,\n tid: auth.tid,\n uid: rec.uid,\n event: 'lap-call',\n detail: { path: '/lap/v1/wait-for-user-input', outcome: result.status },\n })\n return json(out, 200)\n}\n\nfunction json(b: unknown, s: number): Response {\n return new Response(JSON.stringify(b), {\n status: s,\n headers: { 'content-type': 'application/json' },\n })\n}\n"]}
@@ -1,6 +1,75 @@
1
1
  import type { ClientFrame, ServerFrame, HelloFrame, LogEntry } from '../../protocol.js';
2
2
  import { type RpcOptions, type RpcError } from './rpc.js';
3
3
  export type { RpcOptions, RpcError };
4
+ /**
5
+ * Resolution shape for `waitForUserInput`. Mirrors `LapWaitForUserInputResponse`
6
+ * one-for-one — declared here so the interface stays expressible without
7
+ * importing the LAP layer's protocol types upward.
8
+ */
9
+ export type UserInputResolution = {
10
+ status: 'submitted';
11
+ text: string;
12
+ at: number;
13
+ } | {
14
+ status: 'timeout';
15
+ };
16
+ /**
17
+ * Optional persistence adapter for the per-tid user-input buffer.
18
+ *
19
+ * Most runtimes (Node, Bun, Deno, Deno Deploy) don't need this — the
20
+ * in-memory registry survives for the lifetime of the process, which
21
+ * is also the lifetime of the WS pairing. Buffered submissions either
22
+ * get drained by an agent's `wait_for_user_input` call or are
23
+ * irrelevant when the pairing eventually closes.
24
+ *
25
+ * Cloudflare Durable Objects are the motivating case. A DO process
26
+ * can be evicted (deploys, idle eviction, runtime restarts) while a
27
+ * WS pairing is paused mid-conversation; the next request rebuilds
28
+ * a fresh DO with a fresh `InMemoryPairingRegistry`. Wiring this
29
+ * adapter to the DO's `state.storage` makes buffered submissions
30
+ * survive eviction.
31
+ *
32
+ * Parked waiters (Promise resolvers from `waitForUserInput`) CAN'T be
33
+ * persisted — they live in JS memory only. After eviction + wake the
34
+ * agent's LAP client times out on its parked HTTP request and retries
35
+ * via the same long-poll loop; the retry sees the restored buffer.
36
+ *
37
+ * Calls are best-effort: the registry doesn't await them on the hot
38
+ * path. A storage outage causes lost messages on eviction but never
39
+ * wedges a live conversation.
40
+ */
41
+ export interface UserInputStorage {
42
+ /**
43
+ * Read any persisted buffer for this tid. Called from `register()`
44
+ * when the registry sees a fresh pairing — the returned entries
45
+ * seed the in-memory buffer so subsequent `waitForUserInput` calls
46
+ * find them.
47
+ *
48
+ * Returning an empty array (or rejecting) for an unknown tid is
49
+ * normal — a fresh DO has nothing to restore.
50
+ */
51
+ read(tid: string): Promise<Array<{
52
+ text: string;
53
+ at: number;
54
+ }>>;
55
+ /**
56
+ * Persist the current buffer for this tid. Called whenever the
57
+ * buffer mutates (push on `user-input-submitted`, shift on
58
+ * `waitForUserInput` drain). Receives the FULL buffer, not just
59
+ * the delta — simpler contract, idempotent writes, and the buffer
60
+ * is small (capped at USER_INPUT_BUFFER_CAP).
61
+ */
62
+ write(tid: string, buffer: Array<{
63
+ text: string;
64
+ at: number;
65
+ }>): Promise<void>;
66
+ /**
67
+ * Drop persisted buffer for this tid. Called from `handleClose()`
68
+ * so an evicted-then-restarted DO doesn't see stale messages from
69
+ * a session that ended.
70
+ */
71
+ clear(tid: string): Promise<void>;
72
+ }
4
73
  /**
5
74
  * Thin abstraction over a single paired WebSocket. Consumed by the
6
75
  * registry implementations; runtime-specific adapters (`ws`-lib,
@@ -64,6 +133,21 @@ export interface PairingRegistry {
64
133
  * for unknown tids.
65
134
  */
66
135
  getRecentLog(tid: string, n: number): LogEntry[];
136
+ /**
137
+ * Long-poll for the next user-input submission from the paired
138
+ * runtime. The registry buffers a small number of submissions
139
+ * received with no waiter parked (so a user typing before Claude
140
+ * reaches the tool call doesn't lose the message); when a waiter
141
+ * parks with a non-empty buffer it resolves immediately with the
142
+ * oldest buffered submission. When the buffer is empty, the waiter
143
+ * sleeps until a `user-input-submitted` frame arrives, the WS
144
+ * pairing closes, or `timeoutMs` elapses.
145
+ *
146
+ * FIFO delivery: each submission is consumed by exactly one waiter.
147
+ * Multiple parked waiters form a queue; submissions are dispatched
148
+ * in arrival order to the head of the waiter queue.
149
+ */
150
+ waitForUserInput(tid: string, timeoutMs: number): Promise<UserInputResolution>;
67
151
  /**
68
152
  * Send a typed rpc frame and await its matching reply. See
69
153
  * `./rpc.ts::rpc` for the full contract.
@@ -83,6 +167,7 @@ export interface PairingRegistry {
83
167
  export declare class InMemoryPairingRegistry implements PairingRegistry {
84
168
  private pairings;
85
169
  private onLogAppend;
170
+ private userInputStorage;
86
171
  /**
87
172
  * Per-tid ring buffer of recent log entries. Populated as the
88
173
  * registry sees `log-append` frames; trimmed to RECENT_LOG_CAP.
@@ -92,6 +177,13 @@ export declare class InMemoryPairingRegistry implements PairingRegistry {
92
177
  private recentLog;
93
178
  constructor(opts?: {
94
179
  onLogAppend?: (tid: string, entry: LogEntry) => void;
180
+ /**
181
+ * Optional adapter for persisting the user-input buffer across
182
+ * runtime restarts (Cloudflare DO eviction, mainly). See
183
+ * `UserInputStorage` for the contract. Omit on Node/Bun/Deno —
184
+ * those runtimes don't need it.
185
+ */
186
+ userInputStorage?: UserInputStorage;
95
187
  });
96
188
  /**
97
189
  * Read the most recent `n` log entries for a tid, newest-first. Returns
@@ -117,8 +209,17 @@ export declare class InMemoryPairingRegistry implements PairingRegistry {
117
209
  status: 'changed' | 'timeout';
118
210
  stateAfter: unknown;
119
211
  }>;
212
+ waitForUserInput(tid: string, timeoutMs: number): Promise<UserInputResolution>;
120
213
  /** @deprecated Use `send(tid, frame)` directly; semantics are identical. */
121
214
  notify(tid: string, frame: ServerFrame): void;
215
+ /**
216
+ * Fire-and-forget write-through to the optional storage adapter.
217
+ * Snapshots the buffer (defensive copy — the in-memory array can
218
+ * mutate again before the adapter's write resolves) and ignores
219
+ * rejections. Called on every buffer mutation; cheap when the
220
+ * adapter is unset (no-ops) and best-effort when wired.
221
+ */
222
+ private persistUserInputBuffer;
122
223
  private handleClose;
123
224
  }
124
225
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"pairing-registry.d.ts","sourceRoot":"","sources":["../../../src/server/ws/pairing-registry.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pairing-registry.d.ts","sourceRoot":"","sources":["../../../src/server/ws/pairing-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACvF,OAAO,EAIL,KAAK,UAAU,EACf,KAAK,QAAQ,EACd,MAAM,UAAU,CAAA;AAEjB,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAA;AAEpC;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAC3B;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GACjD;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,CAAA;AAEzB;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;;OAQG;IACH,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAA;IAC/D;;;;;;OAMG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9E;;;;OAIG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAClC;AAED;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAAA;IAC9B,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,WAAW,KAAK,IAAI,GAAG,IAAI,CAAA;IAChD,OAAO,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI,CAAA;IAClC,KAAK,IAAI,IAAI,CAAA;CACd;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,WAAW,KAAK,OAAO,CAAA;AAE7D;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,eAAe;IAE9B,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,IAAI,CAAA;IACpD,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAA;IACxC,gEAAgE;IAChE,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,CAAA;IAC3C;;;;;OAKG;IACH,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,MAAM,IAAI,CAAA;IAC5D;;;;OAIG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAA;IAErD;;;;;;OAMG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,QAAQ,EAAE,CAAA;IAEhD;;;;;;;;;;;;;OAaG;IACH,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAA;IAW9E;;;OAGG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAClF,sCAAsC;IACtC,cAAc,CACZ,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;QAAE,OAAO,EAAE,WAAW,GAAG,gBAAgB,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;IAC7E,qCAAqC;IACrC,aAAa,CACX,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;QAAE,MAAM,EAAE,SAAS,GAAG,SAAS,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;CACnE;AAsDD,qBAAa,uBAAwB,YAAW,eAAe;IAC7D,OAAO,CAAC,QAAQ,CAA6B;IAC7C,OAAO,CAAC,WAAW,CAAiD;IACpE,OAAO,CAAC,gBAAgB,CAAyB;IACjD;;;;;OAKG;IACH,OAAO,CAAC,SAAS,CAAgC;gBAG/C,IAAI,GAAE;QACJ,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAA;QACpD;;;;;WAKG;QACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;KAC/B;IAMR;;;;;OAKG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,QAAQ,EAAE;IAShD,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,IAAI;IA0CpD,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAI7B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAK9B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;IAIxC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI;IAU3C,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,MAAM,IAAI;IAS5D,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAcrD,OAAO,CAAC,QAAQ;IAmEhB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,GAAE,UAAe,GAAG,OAAO,CAAC,OAAO,CAAC;IAItF,cAAc,CACZ,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;QAAE,OAAO,EAAE,WAAW,GAAG,gBAAgB,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAI7E,aAAa,CACX,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;QAAE,MAAM,EAAE,SAAS,GAAG,SAAS,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,CAAC;IAIlE,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IA8D9E,4EAA4E;IAC5E,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI;IAI7C;;;;;;OAMG;IACH,OAAO,CAAC,sBAAsB;IAW9B,OAAO,CAAC,WAAW;CA6CpB;AAED;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,gCAA0B,CAAA;AACxD,MAAM,MAAM,iBAAiB,GAAG,uBAAuB,CAAA"}