@llui/agent 0.0.45 → 0.0.46

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.
@@ -0,0 +1,19 @@
1
+ import type { TokenStore } from '../token-store.js';
2
+ import type { PairingRegistry } from '../ws/pairing-registry.js';
3
+ import type { TokenRecord } from '../../protocol.js';
4
+ /**
5
+ * Transition a tid to `active` and notify the browser when an LLM
6
+ * makes its first LAP call. Run from every LAP handler that auth's
7
+ * the token, so activation isn't gated by which endpoint the bridge
8
+ * happens to hit first (`describe` was the only one wiring this up
9
+ * historically; the bridge connects via `/observe` so the browser
10
+ * stayed at `awaiting-claude` indefinitely).
11
+ *
12
+ * No-op when the record is already `active` or in any other state —
13
+ * the `awaiting-claude` → `active` transition is the only one we
14
+ * care about here. `pending-resume` reattaches happen in
15
+ * `acceptConnection` (re-pair path); we don't second-guess them
16
+ * from the LAP layer.
17
+ */
18
+ export declare function ensureActive(tokenStore: TokenStore, registry: PairingRegistry, tid: string, rec: TokenRecord, now: number): Promise<void>;
19
+ //# sourceMappingURL=active.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"active.d.ts","sourceRoot":"","sources":["../../../src/server/lap/active.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,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAEpD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,YAAY,CAChC,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,eAAe,EACzB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,WAAW,EAChB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC,CAQf"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Transition a tid to `active` and notify the browser when an LLM
3
+ * makes its first LAP call. Run from every LAP handler that auth's
4
+ * the token, so activation isn't gated by which endpoint the bridge
5
+ * happens to hit first (`describe` was the only one wiring this up
6
+ * historically; the bridge connects via `/observe` so the browser
7
+ * stayed at `awaiting-claude` indefinitely).
8
+ *
9
+ * No-op when the record is already `active` or in any other state —
10
+ * the `awaiting-claude` → `active` transition is the only one we
11
+ * care about here. `pending-resume` reattaches happen in
12
+ * `acceptConnection` (re-pair path); we don't second-guess them
13
+ * from the LAP layer.
14
+ */
15
+ export async function ensureActive(tokenStore, registry, tid, rec, now) {
16
+ if (rec.status !== 'awaiting-claude')
17
+ return;
18
+ const label = rec.uid ?? rec.label ?? 'Claude';
19
+ await tokenStore.markActive(tid, label, now);
20
+ // Best-effort — the browser may have closed the WS in the gap;
21
+ // the registry's send is a no-op in that case and the close
22
+ // handler will mark the record `pending-resume` anyway.
23
+ registry.send(tid, { t: 'active' });
24
+ }
25
+ //# sourceMappingURL=active.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"active.js","sourceRoot":"","sources":["../../../src/server/lap/active.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,UAAsB,EACtB,QAAyB,EACzB,GAAW,EACX,GAAgB,EAChB,GAAW;IAEX,IAAI,GAAG,CAAC,MAAM,KAAK,iBAAiB;QAAE,OAAM;IAC5C,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,IAAI,QAAQ,CAAA;IAC9C,MAAM,UAAU,CAAC,UAAU,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,CAAA;IAC5C,+DAA+D;IAC/D,4DAA4D;IAC5D,wDAAwD;IACxD,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAA;AACrC,CAAC","sourcesContent":["import type { TokenStore } from '../token-store.js'\nimport type { PairingRegistry } from '../ws/pairing-registry.js'\nimport type { TokenRecord } from '../../protocol.js'\n\n/**\n * Transition a tid to `active` and notify the browser when an LLM\n * makes its first LAP call. Run from every LAP handler that auth's\n * the token, so activation isn't gated by which endpoint the bridge\n * happens to hit first (`describe` was the only one wiring this up\n * historically; the bridge connects via `/observe` so the browser\n * stayed at `awaiting-claude` indefinitely).\n *\n * No-op when the record is already `active` or in any other state —\n * the `awaiting-claude` → `active` transition is the only one we\n * care about here. `pending-resume` reattaches happen in\n * `acceptConnection` (re-pair path); we don't second-guess them\n * from the LAP layer.\n */\nexport async function ensureActive(\n tokenStore: TokenStore,\n registry: PairingRegistry,\n tid: string,\n rec: TokenRecord,\n now: number,\n): Promise<void> {\n if (rec.status !== 'awaiting-claude') return\n const label = rec.uid ?? rec.label ?? 'Claude'\n await tokenStore.markActive(tid, label, now)\n // Best-effort — the browser may have closed the WS in the gap;\n // the registry's send is a no-op in that case and the close\n // handler will mark the record `pending-resume` anyway.\n registry.send(tid, { t: 'active' })\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"confirm-result.d.ts","sourceRoot":"","sources":["../../../src/server/lap/confirm-result.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;AAKnD,MAAM,MAAM,oBAAoB,GAAG;IACjC,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,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,QAAQ,CAAC,CA0DnB"}
1
+ {"version":3,"file":"confirm-result.d.ts","sourceRoot":"","sources":["../../../src/server/lap/confirm-result.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,oBAAoB,GAAG;IACjC,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,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,QAAQ,CAAC,CA2DnB"}
@@ -1,5 +1,6 @@
1
1
  import { verifyAndReadTid } from './describe.js';
2
2
  import { buildPausedResponse } from './paused.js';
3
+ import { ensureActive } from './active.js';
3
4
  export async function handleLapConfirmResult(req, deps) {
4
5
  const auth = await verifyAndReadTid(req, deps.tokenStore);
5
6
  if (!auth.ok)
@@ -24,6 +25,7 @@ export async function handleLapConfirmResult(req, deps) {
24
25
  // If no resolution arrives in time, we surface 'still-pending'.
25
26
  const result = await deps.registry.waitForConfirm(auth.tid, body.confirmId, timeoutMs);
26
27
  const nowMs = (deps.now ?? (() => Date.now()))();
28
+ await ensureActive(deps.tokenStore, deps.registry, auth.tid, rec, nowMs);
27
29
  if (result.outcome === 'confirmed') {
28
30
  await deps.auditSink.write({
29
31
  at: nowMs,
@@ -1 +1 @@
1
- {"version":3,"file":"confirm-result.js","sourceRoot":"","sources":["../../../src/server/lap/confirm-result.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 type { LapConfirmResultRequest, LapConfirmResultResponse } from '../../protocol.js'\n\nexport type LapConfirmResultDeps = {\n tokenStore: TokenStore\n registry: PairingRegistry\n auditSink: AuditSink\n rateLimiter: RateLimiter\n now?: () => number\n}\n\nexport async function handleLapConfirmResult(\n req: Request,\n deps: LapConfirmResultDeps,\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 LapConfirmResultRequest | null\n if (!body || typeof body.confirmId !== 'string') return json({ error: { code: 'invalid' } }, 400)\n const timeoutMs = body.timeoutMs ?? 5_000\n\n // Spec: if the confirm was already resolved during the earlier long-poll on\n // /message, there's no second resolution to wait for. In the current design\n // /confirm-result is ONLY used when /message bailed out early with\n // pending-confirmation. So we call waitForConfirm with the given timeoutMs.\n // If no resolution arrives in time, we surface 'still-pending'.\n const result = await deps.registry.waitForConfirm(auth.tid, body.confirmId, timeoutMs)\n\n const nowMs = (deps.now ?? (() => Date.now()))()\n if (result.outcome === 'confirmed') {\n await deps.auditSink.write({\n at: nowMs,\n tid: auth.tid,\n uid: rec.uid,\n event: 'confirm-approved',\n detail: { confirmId: body.confirmId },\n })\n return json(\n { status: 'confirmed', stateAfter: result.stateAfter } satisfies LapConfirmResultResponse,\n 200,\n )\n }\n // user-cancelled OR timeout. WsPairingRegistry returns user-cancelled on timeout too;\n // we distinguish by checking whether the confirm is still in registry.pendingConfirm —\n // but pendingConfirm cleanup happens inside waitForConfirm's timer, so we can't peek.\n // For v1: treat user-cancelled as user-cancelled; treat explicit timeout as timeout by\n // comparing elapsed vs. timeoutMs. Simpler: just return 'still-pending' on the timeout\n // branch to let Claude poll again. Registry returns {outcome: 'user-cancelled'} on\n // both timer and actual cancel — so we can't distinguish. Punt: return 'user-cancelled'\n // (matches registry semantics). Spec §8.2 get_confirm_result allows 'user-cancelled' |\n // 'timeout' | 'still-pending' — a refinement to distinguish is follow-up work.\n await deps.auditSink.write({\n at: nowMs,\n tid: auth.tid,\n uid: rec.uid,\n event: 'confirm-rejected',\n detail: { confirmId: body.confirmId },\n })\n return json(\n { status: 'rejected', reason: 'user-cancelled' } satisfies LapConfirmResultResponse,\n 200,\n )\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
+ {"version":3,"file":"confirm-result.js","sourceRoot":"","sources":["../../../src/server/lap/confirm-result.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 { LapConfirmResultRequest, LapConfirmResultResponse } from '../../protocol.js'\n\nexport type LapConfirmResultDeps = {\n tokenStore: TokenStore\n registry: PairingRegistry\n auditSink: AuditSink\n rateLimiter: RateLimiter\n now?: () => number\n}\n\nexport async function handleLapConfirmResult(\n req: Request,\n deps: LapConfirmResultDeps,\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 LapConfirmResultRequest | null\n if (!body || typeof body.confirmId !== 'string') return json({ error: { code: 'invalid' } }, 400)\n const timeoutMs = body.timeoutMs ?? 5_000\n\n // Spec: if the confirm was already resolved during the earlier long-poll on\n // /message, there's no second resolution to wait for. In the current design\n // /confirm-result is ONLY used when /message bailed out early with\n // pending-confirmation. So we call waitForConfirm with the given timeoutMs.\n // If no resolution arrives in time, we surface 'still-pending'.\n const result = await deps.registry.waitForConfirm(auth.tid, body.confirmId, timeoutMs)\n\n const nowMs = (deps.now ?? (() => Date.now()))()\n await ensureActive(deps.tokenStore, deps.registry, auth.tid, rec, nowMs)\n if (result.outcome === 'confirmed') {\n await deps.auditSink.write({\n at: nowMs,\n tid: auth.tid,\n uid: rec.uid,\n event: 'confirm-approved',\n detail: { confirmId: body.confirmId },\n })\n return json(\n { status: 'confirmed', stateAfter: result.stateAfter } satisfies LapConfirmResultResponse,\n 200,\n )\n }\n // user-cancelled OR timeout. WsPairingRegistry returns user-cancelled on timeout too;\n // we distinguish by checking whether the confirm is still in registry.pendingConfirm —\n // but pendingConfirm cleanup happens inside waitForConfirm's timer, so we can't peek.\n // For v1: treat user-cancelled as user-cancelled; treat explicit timeout as timeout by\n // comparing elapsed vs. timeoutMs. Simpler: just return 'still-pending' on the timeout\n // branch to let Claude poll again. Registry returns {outcome: 'user-cancelled'} on\n // both timer and actual cancel — so we can't distinguish. Punt: return 'user-cancelled'\n // (matches registry semantics). Spec §8.2 get_confirm_result allows 'user-cancelled' |\n // 'timeout' | 'still-pending' — a refinement to distinguish is follow-up work.\n await deps.auditSink.write({\n at: nowMs,\n tid: auth.tid,\n uid: rec.uid,\n event: 'confirm-rejected',\n detail: { confirmId: body.confirmId },\n })\n return json(\n { status: 'rejected', reason: 'user-cancelled' } satisfies LapConfirmResultResponse,\n 200,\n )\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":"describe.d.ts","sourceRoot":"","sources":["../../../src/server/lap/describe.ts"],"names":[],"mappings":"AACA,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;AAInD,MAAM,MAAM,eAAe,GAAG;IAC5B,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,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC,CAoD9F;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,OAAO,EACZ,UAAU,EAAE,UAAU,EACtB,KAAK,GAAE,MAAmB,GACzB,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAUlF"}
1
+ {"version":3,"file":"describe.d.ts","sourceRoot":"","sources":["../../../src/server/lap/describe.ts"],"names":[],"mappings":"AACA,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;AAKnD,MAAM,MAAM,eAAe,GAAG;IAC5B,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,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC,CAiD9F;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,OAAO,EACZ,UAAU,EAAE,UAAU,EACtB,KAAK,GAAE,MAAmB,GACzB,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAUlF"}
@@ -1,5 +1,6 @@
1
1
  import { tokenHashOf } from '../token.js';
2
2
  import { buildPausedResponse } from './paused.js';
3
+ import { ensureActive } from './active.js';
3
4
  export async function handleLapDescribe(req, deps) {
4
5
  const auth = await verifyAndReadTid(req, deps.tokenStore);
5
6
  if (!auth.ok)
@@ -31,15 +32,12 @@ export async function handleLapDescribe(req, deps) {
31
32
  schemaHash: hello.schemaHash,
32
33
  };
33
34
  const nowMs = (deps.now ?? (() => Date.now()))();
34
- // Transition to active: Claude has made its first LAP call to /describe,
35
- // confirming both the browser WS and Claude are live.
36
- const wasAwaitingClaude = rec.status === 'awaiting-claude';
37
- const label = rec.uid ?? 'Claude';
38
- await deps.tokenStore.markActive(auth.tid, label, nowMs);
39
- // Fire the active signal to the browser only on the first transition.
40
- if (wasAwaitingClaude) {
41
- deps.registry.send(auth.tid, { t: 'active' });
42
- }
35
+ // First-LAP-call activation. Centralised in `ensureActive` so the
36
+ // same transition fires from every LAP endpoint, not just
37
+ // `/describe` the bridge typically connects via `/observe` and
38
+ // the old describe-only path left the browser stuck on
39
+ // `awaiting-claude` indefinitely.
40
+ await ensureActive(deps.tokenStore, deps.registry, auth.tid, rec, nowMs);
43
41
  await deps.auditSink.write({
44
42
  at: nowMs,
45
43
  tid: auth.tid,
@@ -1 +1 @@
1
- {"version":3,"file":"describe.js","sourceRoot":"","sources":["../../../src/server/lap/describe.ts"],"names":[],"mappings":"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","sourcesContent":["import { tokenHashOf } from '../token.js'\nimport 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 { buildPausedResponse } from './paused.js'\nimport type { LapDescribeResponse, MessageSchemaEntry } from '../../protocol.js'\n\nexport type LapDescribeDeps = {\n tokenStore: TokenStore\n registry: PairingRegistry\n auditSink: AuditSink\n rateLimiter: RateLimiter\n now?: () => number\n}\n\nexport async function handleLapDescribe(req: Request, deps: LapDescribeDeps): 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 hello = deps.registry.getHello(auth.tid)\n if (!hello) return buildPausedResponse(deps.tokenStore, auth.tid)\n\n const messages: Record<string, MessageSchemaEntry> = hello.msgSchema as Record<\n string,\n MessageSchemaEntry\n >\n const out: LapDescribeResponse = {\n name: hello.appName,\n version: hello.appVersion,\n stateSchema: hello.stateSchema,\n messages,\n docs: hello.docs,\n conventions: {\n dispatchModel: 'TEA',\n confirmationModel: 'runtime-mediated',\n readSurfaces: ['state', 'query_dom', 'describe_visible_content', 'describe_context'],\n },\n schemaHash: hello.schemaHash,\n }\n\n const nowMs = (deps.now ?? (() => Date.now()))()\n // Transition to active: Claude has made its first LAP call to /describe,\n // confirming both the browser WS and Claude are live.\n const wasAwaitingClaude = rec.status === 'awaiting-claude'\n const label = rec.uid ?? 'Claude'\n await deps.tokenStore.markActive(auth.tid, label, nowMs)\n // Fire the active signal to the browser only on the first transition.\n if (wasAwaitingClaude) {\n deps.registry.send(auth.tid, { t: 'active' })\n }\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/describe' },\n })\n return json(out, 200)\n}\n\n/**\n * Resolve the bearer token on a request to a `tid`. The opaque-token\n * scheme means \"verify\" is \"look up the SHA-256 hash in the store and\n * check expiry.\" A missing prefix, an unknown hash, or an expired\n * record all collapse to the same `auth-failed` so a probe-by-hash\n * leak surface is uniform.\n *\n * Status check (revoked / paused / etc.) is the caller's job — every\n * LAP handler does its own follow-up `findByTid` to read the current\n * status. This function only cares whether the bearer is one of ours\n * and unexpired.\n */\nexport async function verifyAndReadTid(\n req: Request,\n tokenStore: TokenStore,\n nowMs: number = Date.now(),\n): Promise<{ ok: true; tid: string } | { ok: false; status: number; code: string }> {\n const auth = req.headers.get('authorization')\n if (!auth || !auth.startsWith('Bearer ')) return { ok: false, status: 401, code: 'auth-failed' }\n const token = auth.slice('Bearer '.length)\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 <= nowMs) return { ok: false, status: 401, code: 'auth-failed' }\n return { ok: true, tid: rec.tid }\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
+ {"version":3,"file":"describe.js","sourceRoot":"","sources":["../../../src/server/lap/describe.ts"],"names":[],"mappings":"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","sourcesContent":["import { tokenHashOf } from '../token.js'\nimport 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 { buildPausedResponse } from './paused.js'\nimport { ensureActive } from './active.js'\nimport type { LapDescribeResponse, MessageSchemaEntry } from '../../protocol.js'\n\nexport type LapDescribeDeps = {\n tokenStore: TokenStore\n registry: PairingRegistry\n auditSink: AuditSink\n rateLimiter: RateLimiter\n now?: () => number\n}\n\nexport async function handleLapDescribe(req: Request, deps: LapDescribeDeps): 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 hello = deps.registry.getHello(auth.tid)\n if (!hello) return buildPausedResponse(deps.tokenStore, auth.tid)\n\n const messages: Record<string, MessageSchemaEntry> = hello.msgSchema as Record<\n string,\n MessageSchemaEntry\n >\n const out: LapDescribeResponse = {\n name: hello.appName,\n version: hello.appVersion,\n stateSchema: hello.stateSchema,\n messages,\n docs: hello.docs,\n conventions: {\n dispatchModel: 'TEA',\n confirmationModel: 'runtime-mediated',\n readSurfaces: ['state', 'query_dom', 'describe_visible_content', 'describe_context'],\n },\n schemaHash: hello.schemaHash,\n }\n\n const nowMs = (deps.now ?? (() => Date.now()))()\n // First-LAP-call activation. Centralised in `ensureActive` so the\n // same transition fires from every LAP endpoint, not just\n // `/describe` the bridge typically connects via `/observe` and\n // the old describe-only path left the browser stuck on\n // `awaiting-claude` indefinitely.\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/describe' },\n })\n return json(out, 200)\n}\n\n/**\n * Resolve the bearer token on a request to a `tid`. The opaque-token\n * scheme means \"verify\" is \"look up the SHA-256 hash in the store and\n * check expiry.\" A missing prefix, an unknown hash, or an expired\n * record all collapse to the same `auth-failed` so a probe-by-hash\n * leak surface is uniform.\n *\n * Status check (revoked / paused / etc.) is the caller's job — every\n * LAP handler does its own follow-up `findByTid` to read the current\n * status. This function only cares whether the bearer is one of ours\n * and unexpired.\n */\nexport async function verifyAndReadTid(\n req: Request,\n tokenStore: TokenStore,\n nowMs: number = Date.now(),\n): Promise<{ ok: true; tid: string } | { ok: false; status: number; code: string }> {\n const auth = req.headers.get('authorization')\n if (!auth || !auth.startsWith('Bearer ')) return { ok: false, status: 401, code: 'auth-failed' }\n const token = auth.slice('Bearer '.length)\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 <= nowMs) return { ok: false, status: 401, code: 'auth-failed' }\n return { ok: true, tid: rec.tid }\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":"forward.d.ts","sourceRoot":"","sources":["../../../src/server/lap/forward.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;AAInD,MAAM,MAAM,WAAW,GAAG;IACxB,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;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,GAAG,IAAI,EAC3C,WAAW,GAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAc,IAElE,KAAK,OAAO,EAAE,MAAM,WAAW,KAAG,OAAO,CAAC,QAAQ,CAAC,CAwClE;AAUD,eAAO,MAAM,cAAc,QAlDN,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CAsDhE,CAAA;AAEF,eAAO,MAAM,mBAAmB,QAxDX,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CA4DhE,CAAA;AAEF,eAAO,MAAM,gBAAgB,QA9DR,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CA8DY,CAAA;AAE9E,eAAO,MAAM,iBAAiB,QAhET,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CAoEhE,CAAA;AAEF,eAAO,MAAM,wBAAwB,QAtEhB,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CAsEgC,CAAA;AAElG,eAAO,MAAM,gBAAgB,QAxER,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CAwEgB,CAAA;AAElF,eAAO,MAAM,sBAAsB,QA1Ed,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CAqFhE,CAAA;AAEF;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAqD/F"}
1
+ {"version":3,"file":"forward.d.ts","sourceRoot":"","sources":["../../../src/server/lap/forward.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;AAKnD,MAAM,MAAM,WAAW,GAAG;IACxB,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;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,GAAG,IAAI,EAC3C,WAAW,GAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAc,IAElE,KAAK,OAAO,EAAE,MAAM,WAAW,KAAG,OAAO,CAAC,QAAQ,CAAC,CAyClE;AAUD,eAAO,MAAM,cAAc,QAnDN,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CAuDhE,CAAA;AAEF,eAAO,MAAM,mBAAmB,QAzDX,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CA6DhE,CAAA;AAEF,eAAO,MAAM,gBAAgB,QA/DR,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CA+DY,CAAA;AAE9E,eAAO,MAAM,iBAAiB,QAjET,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CAqEhE,CAAA;AAEF,eAAO,MAAM,wBAAwB,QAvEhB,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CAuEgC,CAAA;AAElG,eAAO,MAAM,gBAAgB,QAzER,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CAyEgB,CAAA;AAElF,eAAO,MAAM,sBAAsB,QA3Ed,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CAsFhE,CAAA;AAEF;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAsD/F"}
@@ -1,5 +1,6 @@
1
1
  import { verifyAndReadTid } from './describe.js';
2
2
  import { buildPausedResponse } from './paused.js';
3
+ import { ensureActive } from './active.js';
3
4
  /**
4
5
  * Generic LAP handler. `parseArgs` is called with the parsed body (may be
5
6
  * null for empty bodies); it returns the args object to forward or null
@@ -26,6 +27,7 @@ export function makeForwardHandler(tool, parseArgs, auditDetail = () => ({})) {
26
27
  try {
27
28
  const result = await deps.registry.rpc(auth.tid, tool, args);
28
29
  const nowMs = (deps.now ?? (() => Date.now()))();
30
+ await ensureActive(deps.tokenStore, deps.registry, auth.tid, rec, nowMs);
29
31
  await deps.tokenStore.touch(auth.tid, nowMs);
30
32
  await deps.auditSink.write({
31
33
  at: nowMs,
@@ -129,6 +131,7 @@ export async function handleLapRecentActions(req, deps) {
129
131
  entries = entries.filter((e) => e.kind === kindFilter).slice(0, n);
130
132
  }
131
133
  const nowMs = (deps.now ?? (() => Date.now()))();
134
+ await ensureActive(deps.tokenStore, deps.registry, auth.tid, rec, nowMs);
132
135
  await deps.tokenStore.touch(auth.tid, nowMs);
133
136
  await deps.auditSink.write({
134
137
  at: nowMs,
@@ -1 +1 @@
1
- {"version":3,"file":"forward.js","sourceRoot":"","sources":["../../../src/server/lap/forward.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'\n\nexport type ForwardDeps = {\n tokenStore: TokenStore\n registry: PairingRegistry\n auditSink: AuditSink\n rateLimiter: RateLimiter\n now?: () => number\n}\n\n/**\n * Generic LAP handler. `parseArgs` is called with the parsed body (may be\n * null for empty bodies); it returns the args object to forward or null\n * to reject as invalid. `tool` is the browser-side tool name.\n */\nexport function makeForwardHandler(\n tool: string,\n parseArgs: (body: unknown) => object | null,\n auditDetail: (tid: string, args: object) => Record<string, unknown> = () => ({}),\n) {\n return async (req: Request, deps: ForwardDeps): 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 rawBody = req.method === 'POST' ? await req.json().catch(() => null) : null\n const args = parseArgs(rawBody)\n if (args === null) return json({ error: { code: 'invalid' } }, 400)\n\n try {\n const result = await deps.registry.rpc(auth.tid, tool, args)\n const nowMs = (deps.now ?? (() => Date.now()))()\n await deps.tokenStore.touch(auth.tid, nowMs)\n await deps.auditSink.write({\n at: nowMs,\n tid: auth.tid,\n uid: rec.uid,\n event: 'lap-call',\n detail: { tool, ...auditDetail(auth.tid, args) },\n })\n return json(result, 200)\n } catch (e: unknown) {\n const err = e as { code?: string; detail?: string }\n const code = err.code ?? 'internal'\n // Paused mid-RPC means the WS dropped between the isPaired\n // check and the response — same advisory headers help the\n // agent decide whether to retry.\n if (code === 'paused') return buildPausedResponse(deps.tokenStore, auth.tid)\n const status = code === 'timeout' ? 504 : 500\n return json({ error: { code, detail: err.detail } }, status)\n }\n }\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\n// Concrete handlers:\nexport const handleLapState = makeForwardHandler('get_state', (body) => {\n const b = (body ?? {}) as { path?: unknown }\n if (b.path !== undefined && typeof b.path !== 'string') return null\n return { path: b.path }\n})\n\nexport const handleLapQueryState = makeForwardHandler('query_state', (body) => {\n const b = (body ?? {}) as { path?: unknown }\n if (typeof b.path !== 'string') return null\n return { path: b.path }\n})\n\nexport const handleLapActions = makeForwardHandler('list_actions', () => ({}))\n\nexport const handleLapQueryDom = makeForwardHandler('query_dom', (body) => {\n const b = (body ?? {}) as { name?: unknown; multiple?: unknown }\n if (typeof b.name !== 'string') return null\n return { name: b.name, multiple: !!b.multiple }\n})\n\nexport const handleLapDescribeVisible = makeForwardHandler('describe_visible_content', () => ({}))\n\nexport const handleLapContext = makeForwardHandler('describe_context', () => ({}))\n\nexport const handleLapWouldDispatch = makeForwardHandler('would_dispatch', (body) => {\n const b = (body ?? {}) as { msg?: unknown }\n if (\n b.msg === null ||\n b.msg === undefined ||\n typeof b.msg !== 'object' ||\n typeof (b.msg as { type?: unknown }).type !== 'string'\n ) {\n return null\n }\n return { msg: b.msg }\n})\n\n/**\n * Read recent log entries from the pairing registry's ring buffer.\n * Server-side only — no round-trip to the browser. Used by the\n * agent's `describe_recent_actions` tool to introspect its own\n * activity history without re-fetching state.\n *\n * Diverges from `makeForwardHandler` because the data lives on the\n * server (registry-owned), not the browser. The auth + paused +\n * rate-limit gates run identically.\n */\nexport async function handleLapRecentActions(req: Request, deps: ForwardDeps): Promise<Response> {\n const auth = await verifyAndReadTid(req, deps.tokenStore)\n if (!auth.ok)\n return new Response(JSON.stringify({ error: { code: auth.code } }), {\n status: auth.status,\n headers: { 'content-type': 'application/json' },\n })\n\n const rec = await deps.tokenStore.findByTid(auth.tid)\n if (!rec || rec.status === 'revoked') {\n return new Response(JSON.stringify({ error: { code: 'revoked' } }), {\n status: 403,\n headers: { 'content-type': 'application/json' },\n })\n }\n if (!deps.registry.isPaired(auth.tid)) {\n return buildPausedResponse(deps.tokenStore, auth.tid)\n }\n\n const rlCheck = await deps.rateLimiter.check(auth.tid, 'token')\n if (!rlCheck.allowed) {\n return new Response(\n JSON.stringify({ error: { code: 'rate-limited', retryAfterMs: rlCheck.retryAfterMs } }),\n { status: 429, headers: { 'content-type': 'application/json' } },\n )\n }\n\n const body = req.method === 'POST' ? await req.json().catch(() => null) : null\n const b = (body ?? {}) as { n?: unknown; kind?: unknown }\n const n = typeof b.n === 'number' && b.n > 0 ? Math.floor(b.n) : 10\n // Allow filtering by kind so the agent can ask for \"just dispatches\"\n // without sifting through reads. Default `null` returns all kinds.\n const kindFilter = typeof b.kind === 'string' ? b.kind : null\n\n let entries = deps.registry.getRecentLog(auth.tid, kindFilter !== null ? 100 : n)\n if (kindFilter !== null) {\n entries = entries.filter((e) => e.kind === kindFilter).slice(0, n)\n }\n\n const nowMs = (deps.now ?? (() => Date.now()))()\n await deps.tokenStore.touch(auth.tid, nowMs)\n await deps.auditSink.write({\n at: nowMs,\n tid: auth.tid,\n uid: rec.uid,\n event: 'lap-call',\n detail: { tool: 'describe_recent_actions', count: entries.length, kindFilter },\n })\n\n return new Response(JSON.stringify({ entries }), {\n status: 200,\n headers: { 'content-type': 'application/json' },\n })\n}\n"]}
1
+ {"version":3,"file":"forward.js","sourceRoot":"","sources":["../../../src/server/lap/forward.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'\n\nexport type ForwardDeps = {\n tokenStore: TokenStore\n registry: PairingRegistry\n auditSink: AuditSink\n rateLimiter: RateLimiter\n now?: () => number\n}\n\n/**\n * Generic LAP handler. `parseArgs` is called with the parsed body (may be\n * null for empty bodies); it returns the args object to forward or null\n * to reject as invalid. `tool` is the browser-side tool name.\n */\nexport function makeForwardHandler(\n tool: string,\n parseArgs: (body: unknown) => object | null,\n auditDetail: (tid: string, args: object) => Record<string, unknown> = () => ({}),\n) {\n return async (req: Request, deps: ForwardDeps): 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 rawBody = req.method === 'POST' ? await req.json().catch(() => null) : null\n const args = parseArgs(rawBody)\n if (args === null) return json({ error: { code: 'invalid' } }, 400)\n\n try {\n const result = await deps.registry.rpc(auth.tid, tool, args)\n const nowMs = (deps.now ?? (() => Date.now()))()\n await ensureActive(deps.tokenStore, deps.registry, auth.tid, rec, nowMs)\n await deps.tokenStore.touch(auth.tid, nowMs)\n await deps.auditSink.write({\n at: nowMs,\n tid: auth.tid,\n uid: rec.uid,\n event: 'lap-call',\n detail: { tool, ...auditDetail(auth.tid, args) },\n })\n return json(result, 200)\n } catch (e: unknown) {\n const err = e as { code?: string; detail?: string }\n const code = err.code ?? 'internal'\n // Paused mid-RPC means the WS dropped between the isPaired\n // check and the response — same advisory headers help the\n // agent decide whether to retry.\n if (code === 'paused') return buildPausedResponse(deps.tokenStore, auth.tid)\n const status = code === 'timeout' ? 504 : 500\n return json({ error: { code, detail: err.detail } }, status)\n }\n }\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\n// Concrete handlers:\nexport const handleLapState = makeForwardHandler('get_state', (body) => {\n const b = (body ?? {}) as { path?: unknown }\n if (b.path !== undefined && typeof b.path !== 'string') return null\n return { path: b.path }\n})\n\nexport const handleLapQueryState = makeForwardHandler('query_state', (body) => {\n const b = (body ?? {}) as { path?: unknown }\n if (typeof b.path !== 'string') return null\n return { path: b.path }\n})\n\nexport const handleLapActions = makeForwardHandler('list_actions', () => ({}))\n\nexport const handleLapQueryDom = makeForwardHandler('query_dom', (body) => {\n const b = (body ?? {}) as { name?: unknown; multiple?: unknown }\n if (typeof b.name !== 'string') return null\n return { name: b.name, multiple: !!b.multiple }\n})\n\nexport const handleLapDescribeVisible = makeForwardHandler('describe_visible_content', () => ({}))\n\nexport const handleLapContext = makeForwardHandler('describe_context', () => ({}))\n\nexport const handleLapWouldDispatch = makeForwardHandler('would_dispatch', (body) => {\n const b = (body ?? {}) as { msg?: unknown }\n if (\n b.msg === null ||\n b.msg === undefined ||\n typeof b.msg !== 'object' ||\n typeof (b.msg as { type?: unknown }).type !== 'string'\n ) {\n return null\n }\n return { msg: b.msg }\n})\n\n/**\n * Read recent log entries from the pairing registry's ring buffer.\n * Server-side only — no round-trip to the browser. Used by the\n * agent's `describe_recent_actions` tool to introspect its own\n * activity history without re-fetching state.\n *\n * Diverges from `makeForwardHandler` because the data lives on the\n * server (registry-owned), not the browser. The auth + paused +\n * rate-limit gates run identically.\n */\nexport async function handleLapRecentActions(req: Request, deps: ForwardDeps): Promise<Response> {\n const auth = await verifyAndReadTid(req, deps.tokenStore)\n if (!auth.ok)\n return new Response(JSON.stringify({ error: { code: auth.code } }), {\n status: auth.status,\n headers: { 'content-type': 'application/json' },\n })\n\n const rec = await deps.tokenStore.findByTid(auth.tid)\n if (!rec || rec.status === 'revoked') {\n return new Response(JSON.stringify({ error: { code: 'revoked' } }), {\n status: 403,\n headers: { 'content-type': 'application/json' },\n })\n }\n if (!deps.registry.isPaired(auth.tid)) {\n return buildPausedResponse(deps.tokenStore, auth.tid)\n }\n\n const rlCheck = await deps.rateLimiter.check(auth.tid, 'token')\n if (!rlCheck.allowed) {\n return new Response(\n JSON.stringify({ error: { code: 'rate-limited', retryAfterMs: rlCheck.retryAfterMs } }),\n { status: 429, headers: { 'content-type': 'application/json' } },\n )\n }\n\n const body = req.method === 'POST' ? await req.json().catch(() => null) : null\n const b = (body ?? {}) as { n?: unknown; kind?: unknown }\n const n = typeof b.n === 'number' && b.n > 0 ? Math.floor(b.n) : 10\n // Allow filtering by kind so the agent can ask for \"just dispatches\"\n // without sifting through reads. Default `null` returns all kinds.\n const kindFilter = typeof b.kind === 'string' ? b.kind : null\n\n let entries = deps.registry.getRecentLog(auth.tid, kindFilter !== null ? 100 : n)\n if (kindFilter !== null) {\n entries = entries.filter((e) => e.kind === kindFilter).slice(0, n)\n }\n\n const nowMs = (deps.now ?? (() => Date.now()))()\n await ensureActive(deps.tokenStore, deps.registry, auth.tid, rec, nowMs)\n await deps.tokenStore.touch(auth.tid, nowMs)\n await deps.auditSink.write({\n at: nowMs,\n tid: auth.tid,\n uid: rec.uid,\n event: 'lap-call',\n detail: { tool: 'describe_recent_actions', count: entries.length, kindFilter },\n })\n\n return new Response(JSON.stringify({ entries }), {\n status: 200,\n headers: { 'content-type': 'application/json' },\n })\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../../../src/server/lap/message.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;AAKnD,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,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,QAAQ,CAAC,CA6G5F"}
1
+ {"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../../../src/server/lap/message.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,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,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,QAAQ,CAAC,CA8G5F"}
@@ -1,5 +1,6 @@
1
1
  import { verifyAndReadTid } from './describe.js';
2
2
  import { buildPausedResponse } from './paused.js';
3
+ import { ensureActive } from './active.js';
3
4
  export async function handleLapMessage(req, deps) {
4
5
  const auth = await verifyAndReadTid(req, deps.tokenStore);
5
6
  if (!auth.ok)
@@ -44,6 +45,7 @@ export async function handleLapMessage(req, deps) {
44
45
  return json({ error: { code: err.code ?? 'internal', detail } }, status);
45
46
  }
46
47
  const nowMs = (deps.now ?? (() => Date.now()))();
48
+ await ensureActive(deps.tokenStore, deps.registry, auth.tid, rec, nowMs);
47
49
  await deps.tokenStore.touch(auth.tid, nowMs);
48
50
  if (initial.status === 'dispatched' ||
49
51
  initial.status === 'confirmed' ||
@@ -1 +1 @@
1
- {"version":3,"file":"message.js","sourceRoot":"","sources":["../../../src/server/lap/message.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 type { LapMessageRequest, LapMessageResponse } from '../../protocol.js'\n\nexport type LapMessageDeps = {\n tokenStore: TokenStore\n registry: PairingRegistry\n auditSink: AuditSink\n rateLimiter: RateLimiter\n now?: () => number\n}\n\nexport async function handleLapMessage(req: Request, deps: LapMessageDeps): 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 LapMessageRequest | null\n if (!body || !body.msg || typeof body.msg.type !== 'string') {\n return json({ error: { code: 'invalid' } }, 400)\n }\n\n const timeoutMs = body.timeoutMs ?? 5_000\n\n // The browser-side drain loop caps at `timeoutMs`; give the outer\n // RPC a small buffer so a near-edge drain doesn't race the transport\n // timeout and come back as a false 504.\n const rpcTimeoutMs = timeoutMs + 1_000\n\n let initial: LapMessageResponse\n try {\n initial = (await deps.registry.rpc(auth.tid, 'send_message', body, {\n timeoutMs: rpcTimeoutMs,\n })) as LapMessageResponse\n } catch (e: unknown) {\n const err = e as { code?: string; detail?: string }\n const status = err.code === 'paused' ? 503 : err.code === 'timeout' ? 504 : 500\n // Build a detail string that surfaces whatever info we have — the rpc-error\n // frame from the browser sometimes lacks `detail` (e.g., when a JS TypeError\n // bubbles out of the handler). Falling back to the code + any Error-like\n // fields gives Claude something actionable instead of an opaque 500.\n const detail =\n err.detail ??\n (e instanceof Error ? `${e.name}: ${e.message}` : undefined) ??\n (err.code ? `rpc rejected with code '${err.code}'` : 'rpc rejected without a code')\n // Mirror to the server console so operators see the real cause even when\n // the client just shows \"internal\".\n console.error(\n `[llui-agent] /lap/v1/message 500 — code=${err.code ?? 'internal'}, detail=${detail}`,\n )\n return json({ error: { code: err.code ?? 'internal', detail } }, status)\n }\n\n const nowMs = (deps.now ?? (() => Date.now()))()\n await deps.tokenStore.touch(auth.tid, nowMs)\n\n if (\n initial.status === 'dispatched' ||\n initial.status === 'confirmed' ||\n initial.status === 'rejected'\n ) {\n await deps.auditSink.write({\n at: nowMs,\n tid: auth.tid,\n uid: rec.uid,\n event: initial.status === 'rejected' ? 'msg-blocked' : 'msg-dispatched',\n detail: { variant: body.msg.type, status: initial.status },\n })\n return json(initial, 200)\n }\n\n if (initial.status === 'pending-confirmation') {\n await deps.auditSink.write({\n at: nowMs,\n tid: auth.tid,\n uid: rec.uid,\n event: 'confirm-proposed',\n detail: { variant: body.msg.type, confirmId: initial.confirmId },\n })\n const resolved = await deps.registry.waitForConfirm(auth.tid, initial.confirmId, timeoutMs)\n const nowMs2 = (deps.now ?? (() => Date.now()))()\n if (resolved.outcome === 'confirmed') {\n await deps.auditSink.write({\n at: nowMs2,\n tid: auth.tid,\n uid: rec.uid,\n event: 'confirm-approved',\n detail: { variant: body.msg.type, confirmId: initial.confirmId },\n })\n return json(\n { status: 'confirmed', stateAfter: resolved.stateAfter } satisfies LapMessageResponse,\n 200,\n )\n }\n await deps.auditSink.write({\n at: nowMs2,\n tid: auth.tid,\n uid: rec.uid,\n event: 'confirm-rejected',\n detail: { variant: body.msg.type, confirmId: initial.confirmId },\n })\n return json({ status: 'rejected', reason: 'user-cancelled' } satisfies LapMessageResponse, 200)\n }\n\n return json(\n {\n error: {\n code: 'internal',\n detail: `unexpected browser status: ${String((initial as { status?: unknown }).status ?? 'undefined')}`,\n },\n },\n 500,\n )\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
+ {"version":3,"file":"message.js","sourceRoot":"","sources":["../../../src/server/lap/message.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 { LapMessageRequest, LapMessageResponse } from '../../protocol.js'\n\nexport type LapMessageDeps = {\n tokenStore: TokenStore\n registry: PairingRegistry\n auditSink: AuditSink\n rateLimiter: RateLimiter\n now?: () => number\n}\n\nexport async function handleLapMessage(req: Request, deps: LapMessageDeps): 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 LapMessageRequest | null\n if (!body || !body.msg || typeof body.msg.type !== 'string') {\n return json({ error: { code: 'invalid' } }, 400)\n }\n\n const timeoutMs = body.timeoutMs ?? 5_000\n\n // The browser-side drain loop caps at `timeoutMs`; give the outer\n // RPC a small buffer so a near-edge drain doesn't race the transport\n // timeout and come back as a false 504.\n const rpcTimeoutMs = timeoutMs + 1_000\n\n let initial: LapMessageResponse\n try {\n initial = (await deps.registry.rpc(auth.tid, 'send_message', body, {\n timeoutMs: rpcTimeoutMs,\n })) as LapMessageResponse\n } catch (e: unknown) {\n const err = e as { code?: string; detail?: string }\n const status = err.code === 'paused' ? 503 : err.code === 'timeout' ? 504 : 500\n // Build a detail string that surfaces whatever info we have — the rpc-error\n // frame from the browser sometimes lacks `detail` (e.g., when a JS TypeError\n // bubbles out of the handler). Falling back to the code + any Error-like\n // fields gives Claude something actionable instead of an opaque 500.\n const detail =\n err.detail ??\n (e instanceof Error ? `${e.name}: ${e.message}` : undefined) ??\n (err.code ? `rpc rejected with code '${err.code}'` : 'rpc rejected without a code')\n // Mirror to the server console so operators see the real cause even when\n // the client just shows \"internal\".\n console.error(\n `[llui-agent] /lap/v1/message 500 — code=${err.code ?? 'internal'}, detail=${detail}`,\n )\n return json({ error: { code: err.code ?? 'internal', detail } }, status)\n }\n\n const nowMs = (deps.now ?? (() => Date.now()))()\n await ensureActive(deps.tokenStore, deps.registry, auth.tid, rec, nowMs)\n await deps.tokenStore.touch(auth.tid, nowMs)\n\n if (\n initial.status === 'dispatched' ||\n initial.status === 'confirmed' ||\n initial.status === 'rejected'\n ) {\n await deps.auditSink.write({\n at: nowMs,\n tid: auth.tid,\n uid: rec.uid,\n event: initial.status === 'rejected' ? 'msg-blocked' : 'msg-dispatched',\n detail: { variant: body.msg.type, status: initial.status },\n })\n return json(initial, 200)\n }\n\n if (initial.status === 'pending-confirmation') {\n await deps.auditSink.write({\n at: nowMs,\n tid: auth.tid,\n uid: rec.uid,\n event: 'confirm-proposed',\n detail: { variant: body.msg.type, confirmId: initial.confirmId },\n })\n const resolved = await deps.registry.waitForConfirm(auth.tid, initial.confirmId, timeoutMs)\n const nowMs2 = (deps.now ?? (() => Date.now()))()\n if (resolved.outcome === 'confirmed') {\n await deps.auditSink.write({\n at: nowMs2,\n tid: auth.tid,\n uid: rec.uid,\n event: 'confirm-approved',\n detail: { variant: body.msg.type, confirmId: initial.confirmId },\n })\n return json(\n { status: 'confirmed', stateAfter: resolved.stateAfter } satisfies LapMessageResponse,\n 200,\n )\n }\n await deps.auditSink.write({\n at: nowMs2,\n tid: auth.tid,\n uid: rec.uid,\n event: 'confirm-rejected',\n detail: { variant: body.msg.type, confirmId: initial.confirmId },\n })\n return json({ status: 'rejected', reason: 'user-cancelled' } satisfies LapMessageResponse, 200)\n }\n\n return json(\n {\n error: {\n code: 'internal',\n detail: `unexpected browser status: ${String((initial as { status?: unknown }).status ?? 'undefined')}`,\n },\n },\n 500,\n )\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":"observe.d.ts","sourceRoot":"","sources":["../../../src/server/lap/observe.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;AAWnD,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;;;;;;;;;;;;GAYG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,QAAQ,CAAC,CA8D5F"}
1
+ {"version":3,"file":"observe.d.ts","sourceRoot":"","sources":["../../../src/server/lap/observe.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;AAYnD,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;;;;;;;;;;;;GAYG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,QAAQ,CAAC,CA+D5F"}
@@ -1,5 +1,6 @@
1
1
  import { verifyAndReadTid } from './describe.js';
2
2
  import { buildPausedResponse } from './paused.js';
3
+ import { ensureActive } from './active.js';
3
4
  /**
4
5
  * Unified bootstrap endpoint. One call returns everything the LLM
5
6
  * needs to start acting on the app:
@@ -61,6 +62,7 @@ export async function handleLapObserve(req, deps) {
61
62
  context: dynamic.context,
62
63
  };
63
64
  const nowMs = (deps.now ?? (() => Date.now()))();
65
+ await ensureActive(deps.tokenStore, deps.registry, auth.tid, rec, nowMs);
64
66
  await deps.tokenStore.touch(auth.tid, nowMs);
65
67
  await deps.auditSink.write({
66
68
  at: nowMs,
@@ -1 +1 @@
1
- {"version":3,"file":"observe.js","sourceRoot":"","sources":["../../../src/server/lap/observe.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 type {\n AgentContext,\n LapActionsResponse,\n LapDescribeResponse,\n LapObserveResponse,\n MessageSchemaEntry,\n} from '../../protocol.js'\n\nexport type LapObserveDeps = {\n tokenStore: TokenStore\n registry: PairingRegistry\n auditSink: AuditSink\n rateLimiter: RateLimiter\n now?: () => number\n}\n\n/**\n * Unified bootstrap endpoint. One call returns everything the LLM\n * needs to start acting on the app:\n * - state (dynamic, from browser)\n * - actions (dynamic, from browser)\n * - description (static, from cached hello frame)\n * - context (dynamic, from browser — agentContext(state))\n *\n * Replaces the get_state + list_actions + describe_app trio at the\n * MCP layer. Those LAP endpoints remain available for specialized\n * callers, but the common \"what can I see, what can I do\" question\n * is one call instead of three.\n */\nexport async function handleLapObserve(req: Request, deps: LapObserveDeps): 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 hello = deps.registry.getHello(auth.tid)\n if (!hello) return buildPausedResponse(deps.tokenStore, auth.tid)\n\n let dynamic: {\n state: unknown\n actions: LapActionsResponse['actions']\n context: AgentContext | null\n }\n try {\n dynamic = (await deps.registry.rpc(auth.tid, 'observe', {})) as typeof dynamic\n } catch (e: unknown) {\n const err = e as { code?: string; detail?: string }\n const code = err.code ?? 'internal'\n if (code === 'paused') return buildPausedResponse(deps.tokenStore, auth.tid)\n const status = code === 'timeout' ? 504 : 500\n return json({ error: { code, detail: err.detail } }, status)\n }\n\n const description: LapDescribeResponse = {\n name: hello.appName,\n version: hello.appVersion,\n stateSchema: hello.stateSchema,\n messages: hello.msgSchema as Record<string, MessageSchemaEntry>,\n docs: hello.docs,\n conventions: {\n dispatchModel: 'TEA',\n confirmationModel: 'runtime-mediated',\n readSurfaces: ['state', 'query_dom', 'describe_visible_content', 'describe_context'],\n },\n schemaHash: hello.schemaHash,\n }\n\n const out: LapObserveResponse = {\n state: dynamic.state,\n actions: dynamic.actions,\n description,\n context: dynamic.context,\n }\n\n const nowMs = (deps.now ?? (() => Date.now()))()\n await deps.tokenStore.touch(auth.tid, nowMs)\n await deps.auditSink.write({\n at: nowMs,\n tid: auth.tid,\n uid: rec.uid,\n event: 'lap-call',\n detail: { tool: 'observe' },\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
+ {"version":3,"file":"observe.js","sourceRoot":"","sources":["../../../src/server/lap/observe.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 {\n AgentContext,\n LapActionsResponse,\n LapDescribeResponse,\n LapObserveResponse,\n MessageSchemaEntry,\n} from '../../protocol.js'\n\nexport type LapObserveDeps = {\n tokenStore: TokenStore\n registry: PairingRegistry\n auditSink: AuditSink\n rateLimiter: RateLimiter\n now?: () => number\n}\n\n/**\n * Unified bootstrap endpoint. One call returns everything the LLM\n * needs to start acting on the app:\n * - state (dynamic, from browser)\n * - actions (dynamic, from browser)\n * - description (static, from cached hello frame)\n * - context (dynamic, from browser — agentContext(state))\n *\n * Replaces the get_state + list_actions + describe_app trio at the\n * MCP layer. Those LAP endpoints remain available for specialized\n * callers, but the common \"what can I see, what can I do\" question\n * is one call instead of three.\n */\nexport async function handleLapObserve(req: Request, deps: LapObserveDeps): 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 hello = deps.registry.getHello(auth.tid)\n if (!hello) return buildPausedResponse(deps.tokenStore, auth.tid)\n\n let dynamic: {\n state: unknown\n actions: LapActionsResponse['actions']\n context: AgentContext | null\n }\n try {\n dynamic = (await deps.registry.rpc(auth.tid, 'observe', {})) as typeof dynamic\n } catch (e: unknown) {\n const err = e as { code?: string; detail?: string }\n const code = err.code ?? 'internal'\n if (code === 'paused') return buildPausedResponse(deps.tokenStore, auth.tid)\n const status = code === 'timeout' ? 504 : 500\n return json({ error: { code, detail: err.detail } }, status)\n }\n\n const description: LapDescribeResponse = {\n name: hello.appName,\n version: hello.appVersion,\n stateSchema: hello.stateSchema,\n messages: hello.msgSchema as Record<string, MessageSchemaEntry>,\n docs: hello.docs,\n conventions: {\n dispatchModel: 'TEA',\n confirmationModel: 'runtime-mediated',\n readSurfaces: ['state', 'query_dom', 'describe_visible_content', 'describe_context'],\n },\n schemaHash: hello.schemaHash,\n }\n\n const out: LapObserveResponse = {\n state: dynamic.state,\n actions: dynamic.actions,\n description,\n context: dynamic.context,\n }\n\n const nowMs = (deps.now ?? (() => Date.now()))()\n await ensureActive(deps.tokenStore, deps.registry, auth.tid, rec, nowMs)\n await deps.tokenStore.touch(auth.tid, nowMs)\n await deps.auditSink.write({\n at: nowMs,\n tid: auth.tid,\n uid: rec.uid,\n event: 'lap-call',\n detail: { tool: 'observe' },\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 +1 @@
1
- {"version":3,"file":"wait.d.ts","sourceRoot":"","sources":["../../../src/server/lap/wait.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;AAKnD,MAAM,MAAM,WAAW,GAAG;IACxB,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,wBAAsB,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CA2BtF"}
1
+ {"version":3,"file":"wait.d.ts","sourceRoot":"","sources":["../../../src/server/lap/wait.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,WAAW,GAAG;IACxB,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,wBAAsB,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CA4BtF"}
@@ -1,5 +1,6 @@
1
1
  import { verifyAndReadTid } from './describe.js';
2
2
  import { buildPausedResponse } from './paused.js';
3
+ import { ensureActive } from './active.js';
3
4
  export async function handleLapWait(req, deps) {
4
5
  const auth = await verifyAndReadTid(req, deps.tokenStore);
5
6
  if (!auth.ok)
@@ -18,6 +19,7 @@ export async function handleLapWait(req, deps) {
18
19
  const result = await deps.registry.waitForChange(auth.tid, body.path, timeoutMs);
19
20
  const out = result;
20
21
  const nowMs = (deps.now ?? (() => Date.now()))();
22
+ await ensureActive(deps.tokenStore, deps.registry, auth.tid, rec, nowMs);
21
23
  await deps.auditSink.write({
22
24
  at: nowMs,
23
25
  tid: auth.tid,
@@ -1 +1 @@
1
- {"version":3,"file":"wait.js","sourceRoot":"","sources":["../../../src/server/lap/wait.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAWjD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAY,EAAE,IAAiB;IACjE,MAAM,IAAI,GAAG,MAAM,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,CAAA;IACzD,IAAI,CAAC,IAAI,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;IAEtE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACrD,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,GAAG,CAAC,CAAA;IACtF,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,mBAAmB,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,CAAA;IAE5F,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IAC/D,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QACrB,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE,EAAE,EAAE,GAAG,CAAC,CAAA;IAC3F,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAmB,CAAA;IAC3E,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,MAAM,CAAA;IAC1C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;IAChF,MAAM,GAAG,GAAoB,MAAM,CAAA;IAEnC,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAA;IAChD,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;QACzB,EAAE,EAAE,KAAK;QACT,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,KAAK,EAAE,UAAU;QACjB,MAAM,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;KACzD,CAAC,CAAA;IACF,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;AACvB,CAAC;AAED,SAAS,IAAI,CAAC,CAAU,EAAE,CAAS;IACjC,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE;QACrC,MAAM,EAAE,CAAC;QACT,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;KAChD,CAAC,CAAA;AACJ,CAAC","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 type { LapWaitRequest, LapWaitResponse } from '../../protocol.js'\n\nexport type LapWaitDeps = {\n tokenStore: TokenStore\n registry: PairingRegistry\n auditSink: AuditSink\n rateLimiter: RateLimiter\n now?: () => number\n}\n\nexport async function handleLapWait(req: Request, deps: LapWaitDeps): 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 LapWaitRequest\n const timeoutMs = body.timeoutMs ?? 10_000\n const result = await deps.registry.waitForChange(auth.tid, body.path, timeoutMs)\n const out: LapWaitResponse = result\n\n const nowMs = (deps.now ?? (() => Date.now()))()\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', 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
+ {"version":3,"file":"wait.js","sourceRoot":"","sources":["../../../src/server/lap/wait.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAW1C,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAY,EAAE,IAAiB;IACjE,MAAM,IAAI,GAAG,MAAM,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,CAAA;IACzD,IAAI,CAAC,IAAI,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;IAEtE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACrD,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,GAAG,CAAC,CAAA;IACtF,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,mBAAmB,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,CAAA;IAE5F,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IAC/D,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QACrB,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE,EAAE,EAAE,GAAG,CAAC,CAAA;IAC3F,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAmB,CAAA;IAC3E,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,MAAM,CAAA;IAC1C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;IAChF,MAAM,GAAG,GAAoB,MAAM,CAAA;IAEnC,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAA;IAChD,MAAM,YAAY,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC,CAAA;IACxE,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;QACzB,EAAE,EAAE,KAAK;QACT,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,KAAK,EAAE,UAAU;QACjB,MAAM,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;KACzD,CAAC,CAAA;IACF,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;AACvB,CAAC;AAED,SAAS,IAAI,CAAC,CAAU,EAAE,CAAS;IACjC,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE;QACrC,MAAM,EAAE,CAAC;QACT,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;KAChD,CAAC,CAAA;AACJ,CAAC","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 { LapWaitRequest, LapWaitResponse } from '../../protocol.js'\n\nexport type LapWaitDeps = {\n tokenStore: TokenStore\n registry: PairingRegistry\n auditSink: AuditSink\n rateLimiter: RateLimiter\n now?: () => number\n}\n\nexport async function handleLapWait(req: Request, deps: LapWaitDeps): 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 LapWaitRequest\n const timeoutMs = body.timeoutMs ?? 10_000\n const result = await deps.registry.waitForChange(auth.tid, body.path, timeoutMs)\n const out: LapWaitResponse = 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', 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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llui/agent",
3
- "version": "0.0.45",
3
+ "version": "0.0.46",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "exports": {