@llui/agent 0.0.46 → 0.0.48

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.
@@ -1 +1 @@
1
- {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../src/server/core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAClD,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAWlF;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;IACnC,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,eAAe,CAAA;IAC1B;;;;;;;;;;;;;;;;;OAiBG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAA;CAC9B,CAAA;AAED,MAAM,MAAM,YAAY,GACpB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACzB;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,aAAa,GAAG,SAAS,CAAA;CAAE,CAAA;AAElE;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAA;IAClD,QAAQ,EAAE,eAAe,CAAA;IACzB,UAAU,EAAE,UAAU,CAAA;IACtB,SAAS,EAAE,SAAS,CAAA;IACpB;;;;;;;;;;OAUG;IACH,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,KAAK,OAAO,CAAC,YAAY,CAAC,CAAA;CACpF,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,GAAE,WAAgB,GAAG,eAAe,CA2G3E"}
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../src/server/core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAClD,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAWlF;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;IACnC,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,eAAe,CAAA;IAC1B;;;;;;;;;;;;;;;;;OAiBG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAA;CAC9B,CAAA;AAED,MAAM,MAAM,YAAY,GACpB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACzB;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,aAAa,GAAG,SAAS,CAAA;CAAE,CAAA;AAElE;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAA;IAClD,QAAQ,EAAE,eAAe,CAAA;IACzB,UAAU,EAAE,UAAU,CAAA;IACtB,SAAS,EAAE,SAAS,CAAA;IACpB;;;;;;;;;;OAUG;IACH,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,KAAK,OAAO,CAAC,YAAY,CAAC,CAAA;CACpF,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,GAAE,WAAgB,GAAG,eAAe,CAkH3E"}
@@ -88,6 +88,13 @@ export function createLluiAgentCore(opts = {}) {
88
88
  // pairing again. Restore the original label so audit context
89
89
  // doesn't show a "reconnected" placeholder bouncing in and out.
90
90
  await tokenStore.markActive(tid, rec.label ?? '(reconnected)', nowMs);
91
+ // Tell the browser the pairing is live again so its connect-
92
+ // panel flips from `pending-claude` (or `reconnecting`) to
93
+ // `active`. Without this, the page would stay on
94
+ // "Waiting for AI to claim" indefinitely after a refresh —
95
+ // ensureActive on the next LAP call wouldn't fire either,
96
+ // since the record is already `active`.
97
+ registry.send(tid, { t: 'active' });
91
98
  }
92
99
  else {
93
100
  await tokenStore.markAwaitingClaude(tid, nowMs);
@@ -1 +1 @@
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 } 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 } 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 } 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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llui/agent",
3
- "version": "0.0.46",
3
+ "version": "0.0.48",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "exports": {
@@ -40,12 +40,12 @@
40
40
  "ws": "^8.18.0"
41
41
  },
42
42
  "peerDependencies": {
43
- "@llui/dom": "^0.0.34"
43
+ "@llui/dom": "^0.0.35"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/node": "^22.0.0",
47
47
  "@types/ws": "^8.5.13",
48
- "@llui/dom": "0.0.34"
48
+ "@llui/dom": "0.0.35"
49
49
  },
50
50
  "description": "LLui Agent — LAP server + browser client runtime for driving LLui apps from LLM clients",
51
51
  "keywords": [