@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.
- package/dist/client/agentAttention.d.ts +129 -0
- package/dist/client/agentAttention.d.ts.map +1 -0
- package/dist/client/agentAttention.js +156 -0
- package/dist/client/agentAttention.js.map +1 -0
- package/dist/client/agentChat.d.ts +100 -0
- package/dist/client/agentChat.d.ts.map +1 -0
- package/dist/client/agentChat.js +84 -0
- package/dist/client/agentChat.js.map +1 -0
- package/dist/client/agentLog.d.ts +17 -0
- package/dist/client/agentLog.d.ts.map +1 -1
- package/dist/client/agentLog.js +18 -0
- package/dist/client/agentLog.js.map +1 -1
- package/dist/client/diff-render.d.ts +68 -0
- package/dist/client/diff-render.d.ts.map +1 -0
- package/dist/client/diff-render.js +141 -0
- package/dist/client/diff-render.js.map +1 -0
- package/dist/client/effect-handler.d.ts +29 -0
- package/dist/client/effect-handler.d.ts.map +1 -1
- package/dist/client/effect-handler.js +39 -0
- package/dist/client/effect-handler.js.map +1 -1
- package/dist/client/effects.d.ts +43 -0
- package/dist/client/effects.d.ts.map +1 -1
- package/dist/client/effects.js.map +1 -1
- package/dist/client/factory.d.ts +21 -0
- package/dist/client/factory.d.ts.map +1 -1
- package/dist/client/factory.js +15 -2
- package/dist/client/factory.js.map +1 -1
- package/dist/client/index.d.ts +4 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +3 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/ws-client.d.ts +9 -0
- package/dist/client/ws-client.d.ts.map +1 -1
- package/dist/client/ws-client.js +120 -0
- package/dist/client/ws-client.js.map +1 -1
- package/dist/protocol.d.ts +103 -3
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js.map +1 -1
- package/dist/server/cloudflare/durable-object.d.ts +41 -0
- package/dist/server/cloudflare/durable-object.d.ts.map +1 -1
- package/dist/server/cloudflare/durable-object.js +46 -0
- package/dist/server/cloudflare/durable-object.js.map +1 -1
- package/dist/server/cloudflare/index.d.ts +10 -3
- package/dist/server/cloudflare/index.d.ts.map +1 -1
- package/dist/server/cloudflare/index.js +10 -3
- package/dist/server/cloudflare/index.js.map +1 -1
- package/dist/server/core.d.ts +11 -1
- package/dist/server/core.d.ts.map +1 -1
- package/dist/server/core.js +1 -0
- package/dist/server/core.js.map +1 -1
- package/dist/server/lap/narrate.d.ts +31 -0
- package/dist/server/lap/narrate.d.ts.map +1 -0
- package/dist/server/lap/narrate.js +70 -0
- package/dist/server/lap/narrate.js.map +1 -0
- package/dist/server/lap/router.d.ts.map +1 -1
- package/dist/server/lap/router.js +6 -0
- package/dist/server/lap/router.js.map +1 -1
- package/dist/server/lap/wait-for-user-input.d.ts +13 -0
- package/dist/server/lap/wait-for-user-input.d.ts.map +1 -0
- package/dist/server/lap/wait-for-user-input.js +53 -0
- package/dist/server/lap/wait-for-user-input.js.map +1 -0
- package/dist/server/ws/pairing-registry.d.ts +101 -0
- package/dist/server/ws/pairing-registry.d.ts.map +1 -1
- package/dist/server/ws/pairing-registry.js +160 -0
- package/dist/server/ws/pairing-registry.js.map +1 -1
- package/package.json +7 -5
- package/styles/agent-panel.css +153 -0
package/dist/server/core.js.map
CHANGED
|
@@ -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 // 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;
|
|
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":"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;;;;;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;
|
|
1
|
+
{"version":3,"file":"pairing-registry.d.ts","sourceRoot":"","sources":["../../../src/server/ws/pairing-registry.ts"],"names":[],"mappings":"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"}
|