@llui/agent 0.0.34 → 0.0.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +9 -7
  2. package/dist/client/agentConnect.d.ts +1 -1
  3. package/dist/client/agentConnect.d.ts.map +1 -1
  4. package/dist/client/agentConnect.js +12 -8
  5. package/dist/client/agentConnect.js.map +1 -1
  6. package/dist/client/rpc/list-actions.d.ts +24 -7
  7. package/dist/client/rpc/list-actions.d.ts.map +1 -1
  8. package/dist/client/rpc/list-actions.js +48 -30
  9. package/dist/client/rpc/list-actions.js.map +1 -1
  10. package/dist/protocol.d.ts +17 -6
  11. package/dist/protocol.d.ts.map +1 -1
  12. package/dist/protocol.js.map +1 -1
  13. package/dist/server/cloudflare/durable-object.d.ts +11 -4
  14. package/dist/server/cloudflare/durable-object.d.ts.map +1 -1
  15. package/dist/server/cloudflare/durable-object.js.map +1 -1
  16. package/dist/server/cloudflare/index.d.ts +8 -4
  17. package/dist/server/cloudflare/index.d.ts.map +1 -1
  18. package/dist/server/cloudflare/index.js +8 -4
  19. package/dist/server/cloudflare/index.js.map +1 -1
  20. package/dist/server/cloudflare/worker.d.ts +10 -2
  21. package/dist/server/cloudflare/worker.d.ts.map +1 -1
  22. package/dist/server/cloudflare/worker.js +13 -6
  23. package/dist/server/cloudflare/worker.js.map +1 -1
  24. package/dist/server/core-entry.d.ts +2 -2
  25. package/dist/server/core-entry.d.ts.map +1 -1
  26. package/dist/server/core-entry.js +1 -1
  27. package/dist/server/core-entry.js.map +1 -1
  28. package/dist/server/core.d.ts +1 -3
  29. package/dist/server/core.d.ts.map +1 -1
  30. package/dist/server/core.js +13 -12
  31. package/dist/server/core.js.map +1 -1
  32. package/dist/server/factory.d.ts +1 -1
  33. package/dist/server/factory.d.ts.map +1 -1
  34. package/dist/server/factory.js +1 -2
  35. package/dist/server/factory.js.map +1 -1
  36. package/dist/server/http/mint.d.ts +6 -1
  37. package/dist/server/http/mint.d.ts.map +1 -1
  38. package/dist/server/http/mint.js +14 -6
  39. package/dist/server/http/mint.js.map +1 -1
  40. package/dist/server/http/resume.d.ts +3 -1
  41. package/dist/server/http/resume.d.ts.map +1 -1
  42. package/dist/server/http/resume.js +9 -7
  43. package/dist/server/http/resume.js.map +1 -1
  44. package/dist/server/index.d.ts +2 -2
  45. package/dist/server/index.d.ts.map +1 -1
  46. package/dist/server/index.js +1 -1
  47. package/dist/server/index.js.map +1 -1
  48. package/dist/server/lap/confirm-result.d.ts +0 -1
  49. package/dist/server/lap/confirm-result.d.ts.map +1 -1
  50. package/dist/server/lap/confirm-result.js +1 -1
  51. package/dist/server/lap/confirm-result.js.map +1 -1
  52. package/dist/server/lap/describe.d.ts +13 -2
  53. package/dist/server/lap/describe.d.ts.map +1 -1
  54. package/dist/server/lap/describe.js +23 -6
  55. package/dist/server/lap/describe.js.map +1 -1
  56. package/dist/server/lap/forward.d.ts +0 -1
  57. package/dist/server/lap/forward.d.ts.map +1 -1
  58. package/dist/server/lap/forward.js +2 -2
  59. package/dist/server/lap/forward.js.map +1 -1
  60. package/dist/server/lap/message.d.ts +0 -1
  61. package/dist/server/lap/message.d.ts.map +1 -1
  62. package/dist/server/lap/message.js +1 -1
  63. package/dist/server/lap/message.js.map +1 -1
  64. package/dist/server/lap/observe.d.ts +0 -1
  65. package/dist/server/lap/observe.d.ts.map +1 -1
  66. package/dist/server/lap/observe.js +1 -1
  67. package/dist/server/lap/observe.js.map +1 -1
  68. package/dist/server/lap/wait.d.ts +0 -1
  69. package/dist/server/lap/wait.d.ts.map +1 -1
  70. package/dist/server/lap/wait.js +1 -1
  71. package/dist/server/lap/wait.js.map +1 -1
  72. package/dist/server/options.d.ts +7 -5
  73. package/dist/server/options.d.ts.map +1 -1
  74. package/dist/server/options.js.map +1 -1
  75. package/dist/server/token-store.d.ts +22 -0
  76. package/dist/server/token-store.d.ts.map +1 -1
  77. package/dist/server/token-store.js +24 -0
  78. package/dist/server/token-store.js.map +1 -1
  79. package/dist/server/token.d.ts +32 -17
  80. package/dist/server/token.d.ts.map +1 -1
  81. package/dist/server/token.js +40 -103
  82. package/dist/server/token.js.map +1 -1
  83. package/dist/server/web/upgrade.d.ts +1 -1
  84. package/dist/server/web/upgrade.js +1 -1
  85. package/dist/server/web/upgrade.js.map +1 -1
  86. package/dist/server/ws/upgrade.d.ts +0 -1
  87. package/dist/server/ws/upgrade.d.ts.map +1 -1
  88. package/dist/server/ws/upgrade.js +12 -4
  89. package/dist/server/ws/upgrade.js.map +1 -1
  90. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { signToken } from '../token.js';
1
+ import { mintToken } from '../token.js';
2
2
  /**
3
3
  * POST /agent/mint — creates a pairing record and returns the mint
4
4
  * response. See spec §6.2. The caller is responsible for routing
@@ -17,16 +17,21 @@ export async function handleMint(req, deps) {
17
17
  const uid = await deps.identityResolver(req);
18
18
  const tid = uuid();
19
19
  const nowMs = now();
20
- const iat = Math.floor(nowMs / 1000);
21
- const exp = Math.floor((nowMs + hardExpiryMs) / 1000);
20
+ const expiresAt = nowMs + hardExpiryMs;
22
21
  const origin = new URL(req.url).origin;
23
- const payload = { tid, iat, exp, scope: 'agent' };
24
- const token = await signToken(payload, deps.signingKey);
22
+ // Mint an opaque random token + the SHA-256 hash we'll persist. The
23
+ // plaintext token is returned to the caller in this single HTTP
24
+ // response; from this point on, the server only knows the hash. See
25
+ // token.ts for the security rationale.
26
+ const mint = deps.mint ?? mintToken;
27
+ const { token, tokenHash } = await mint();
25
28
  const record = {
26
29
  tid,
30
+ tokenHash,
27
31
  uid,
28
32
  status: 'awaiting-ws',
29
33
  createdAt: nowMs,
34
+ expiresAt,
30
35
  lastSeenAt: nowMs,
31
36
  pendingResumeUntil: null,
32
37
  origin,
@@ -47,7 +52,10 @@ export async function handleMint(req, deps) {
47
52
  tid,
48
53
  wsUrl,
49
54
  lapUrl,
50
- expiresAt: exp,
55
+ // Wire format keeps the seconds-since-epoch convention from the
56
+ // pre-0.0.35 JWT-payload `exp` so existing clients reading
57
+ // `expiresAt` see the same units.
58
+ expiresAt: Math.floor(expiresAt / 1000),
51
59
  };
52
60
  return new Response(JSON.stringify(body), {
53
61
  status: 200,
@@ -1 +1 @@
1
- {"version":3,"file":"mint.js","sourceRoot":"","sources":["../../../src/server/http/mint.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TokenStore } from '../token-store.js'\nimport type { IdentityResolver } from '../identity.js'\nimport type { AuditSink } from '../audit.js'\nimport { signToken } from '../token.js'\nimport type { MintResponse, TokenPayload, TokenRecord } from '../../protocol.js'\n\nexport type MintDeps = {\n signingKey: string | Uint8Array\n tokenStore: TokenStore\n identityResolver: IdentityResolver\n auditSink: AuditSink\n lapBasePath: string\n /** Wall-clock in milliseconds; injectable for tests. */\n now?: () => number\n /** UUID generator; injectable for tests. */\n uuid?: () => string\n /** Hard-expiry window, default 24 h. */\n hardExpiryMs?: number\n}\n\n/**\n * POST /agent/mint — creates a pairing record and returns the mint\n * response. See spec §6.2. The caller is responsible for routing\n * `/agent/mint` requests to this handler; `router.ts` composes that.\n */\nexport async function handleMint(req: Request, deps: MintDeps): Promise<Response> {\n if (req.method !== 'POST') {\n return new Response(JSON.stringify({ error: { code: 'method-not-allowed' } }), {\n status: 405,\n headers: { 'content-type': 'application/json' },\n })\n }\n\n const now = deps.now ?? (() => Date.now())\n const uuid = deps.uuid ?? (() => crypto.randomUUID())\n const hardExpiryMs = deps.hardExpiryMs ?? 24 * 60 * 60 * 1000\n\n const uid = await deps.identityResolver(req)\n const tid = uuid()\n const nowMs = now()\n const iat = Math.floor(nowMs / 1000)\n const exp = Math.floor((nowMs + hardExpiryMs) / 1000)\n const origin = new URL(req.url).origin\n\n const payload: TokenPayload = { tid, iat, exp, scope: 'agent' }\n const token = await signToken(payload, deps.signingKey)\n\n const record: TokenRecord = {\n tid,\n uid,\n status: 'awaiting-ws',\n createdAt: nowMs,\n lastSeenAt: nowMs,\n pendingResumeUntil: null,\n origin,\n label: null,\n }\n await deps.tokenStore.create(record)\n\n await deps.auditSink.write({\n at: nowMs,\n tid,\n uid,\n event: 'mint',\n detail: { origin },\n })\n\n const wsUrl = toWsUrl(new URL(req.url).origin) + '/agent/ws'\n const lapUrl = new URL(deps.lapBasePath, origin).toString()\n\n const body: MintResponse = {\n token,\n tid,\n wsUrl,\n lapUrl,\n expiresAt: exp,\n }\n return new Response(JSON.stringify(body), {\n status: 200,\n headers: { 'content-type': 'application/json' },\n })\n}\n\nfunction toWsUrl(httpOrigin: string): string {\n return httpOrigin.startsWith('https://')\n ? 'wss://' + httpOrigin.slice('https://'.length)\n : 'ws://' + httpOrigin.slice('http://'.length)\n}\n"]}
1
+ {"version":3,"file":"mint.js","sourceRoot":"","sources":["../../../src/server/http/mint.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TokenStore } from '../token-store.js'\nimport type { IdentityResolver } from '../identity.js'\nimport type { AuditSink } from '../audit.js'\nimport { mintToken } from '../token.js'\nimport type { MintResponse, TokenRecord } from '../../protocol.js'\n\nexport type MintDeps = {\n tokenStore: TokenStore\n identityResolver: IdentityResolver\n auditSink: AuditSink\n lapBasePath: string\n /** Wall-clock in milliseconds; injectable for tests. */\n now?: () => number\n /** UUID generator; injectable for tests. */\n uuid?: () => string\n /** Hard-expiry window, default 24 h. */\n hardExpiryMs?: number\n /**\n * Override the token mint primitive (for tests that need a known\n * token value). Production uses the default opaque mint.\n */\n mint?: typeof mintToken\n}\n\n/**\n * POST /agent/mint — creates a pairing record and returns the mint\n * response. See spec §6.2. The caller is responsible for routing\n * `/agent/mint` requests to this handler; `router.ts` composes that.\n */\nexport async function handleMint(req: Request, deps: MintDeps): Promise<Response> {\n if (req.method !== 'POST') {\n return new Response(JSON.stringify({ error: { code: 'method-not-allowed' } }), {\n status: 405,\n headers: { 'content-type': 'application/json' },\n })\n }\n\n const now = deps.now ?? (() => Date.now())\n const uuid = deps.uuid ?? (() => crypto.randomUUID())\n const hardExpiryMs = deps.hardExpiryMs ?? 24 * 60 * 60 * 1000\n\n const uid = await deps.identityResolver(req)\n const tid = uuid()\n const nowMs = now()\n const expiresAt = nowMs + hardExpiryMs\n const origin = new URL(req.url).origin\n\n // Mint an opaque random token + the SHA-256 hash we'll persist. The\n // plaintext token is returned to the caller in this single HTTP\n // response; from this point on, the server only knows the hash. See\n // token.ts for the security rationale.\n const mint = deps.mint ?? mintToken\n const { token, tokenHash } = await mint()\n\n const record: TokenRecord = {\n tid,\n tokenHash,\n uid,\n status: 'awaiting-ws',\n createdAt: nowMs,\n expiresAt,\n lastSeenAt: nowMs,\n pendingResumeUntil: null,\n origin,\n label: null,\n }\n await deps.tokenStore.create(record)\n\n await deps.auditSink.write({\n at: nowMs,\n tid,\n uid,\n event: 'mint',\n detail: { origin },\n })\n\n const wsUrl = toWsUrl(new URL(req.url).origin) + '/agent/ws'\n const lapUrl = new URL(deps.lapBasePath, origin).toString()\n\n const body: MintResponse = {\n token,\n tid,\n wsUrl,\n lapUrl,\n // Wire format keeps the seconds-since-epoch convention from the\n // pre-0.0.35 JWT-payload `exp` so existing clients reading\n // `expiresAt` see the same units.\n expiresAt: Math.floor(expiresAt / 1000),\n }\n return new Response(JSON.stringify(body), {\n status: 200,\n headers: { 'content-type': 'application/json' },\n })\n}\n\nfunction toWsUrl(httpOrigin: string): string {\n return httpOrigin.startsWith('https://')\n ? 'wss://' + httpOrigin.slice('https://'.length)\n : 'ws://' + httpOrigin.slice('http://'.length)\n}\n"]}
@@ -1,13 +1,15 @@
1
1
  import type { TokenStore } from '../token-store.js';
2
2
  import type { IdentityResolver } from '../identity.js';
3
3
  import type { AuditSink } from '../audit.js';
4
+ import { mintToken } from '../token.js';
4
5
  export type ResumeDeps = {
5
6
  tokenStore: TokenStore;
6
7
  identityResolver: IdentityResolver;
7
8
  auditSink: AuditSink;
8
- signingKey?: string | Uint8Array;
9
9
  now?: () => number;
10
10
  hardExpiryMs?: number;
11
+ /** Override mint primitive for tests. */
12
+ mint?: typeof mintToken;
11
13
  };
12
14
  export declare function handleResumeList(req: Request, deps: ResumeDeps): Promise<Response>;
13
15
  export declare function handleResumeClaim(req: Request, deps: ResumeDeps): Promise<Response>;
@@ -1 +1 @@
1
- {"version":3,"file":"resume.d.ts","sourceRoot":"","sources":["../../../src/server/http/resume.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAW5C,MAAM,MAAM,UAAU,GAAG;IACvB,UAAU,EAAE,UAAU,CAAA;IACtB,gBAAgB,EAAE,gBAAgB,CAAA;IAClC,SAAS,EAAE,SAAS,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,GAAG,UAAU,CAAA;IAChC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AAED,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC,CAyBxF;AAED,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC,CAoCzF"}
1
+ {"version":3,"file":"resume.d.ts","sourceRoot":"","sources":["../../../src/server/http/resume.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AASvC,MAAM,MAAM,UAAU,GAAG;IACvB,UAAU,EAAE,UAAU,CAAA;IACtB,gBAAgB,EAAE,gBAAgB,CAAA;IAClC,SAAS,EAAE,SAAS,CAAA;IACpB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,yCAAyC;IACzC,IAAI,CAAC,EAAE,OAAO,SAAS,CAAA;CACxB,CAAA;AAED,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC,CAyBxF;AAED,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC,CAuCzF"}
@@ -1,4 +1,4 @@
1
- import { signToken } from '../token.js';
1
+ import { mintToken } from '../token.js';
2
2
  export async function handleResumeList(req, deps) {
3
3
  if (req.method !== 'POST')
4
4
  return methodNotAllowed();
@@ -32,8 +32,6 @@ export async function handleResumeList(req, deps) {
32
32
  export async function handleResumeClaim(req, deps) {
33
33
  if (req.method !== 'POST')
34
34
  return methodNotAllowed();
35
- if (!deps.signingKey)
36
- return new Response(null, { status: 500 });
37
35
  const body = (await req.json().catch(() => null));
38
36
  if (!body || typeof body.tid !== 'string')
39
37
  return badRequest();
@@ -50,10 +48,14 @@ export async function handleResumeClaim(req, deps) {
50
48
  return forbidden();
51
49
  const nowMs = (deps.now ?? (() => Date.now()))();
52
50
  const hardExpiryMs = deps.hardExpiryMs ?? 24 * 60 * 60 * 1000;
53
- const iat = Math.floor(nowMs / 1000);
54
- const exp = Math.floor((nowMs + hardExpiryMs) / 1000);
55
- const payload = { tid: rec.tid, iat, exp, scope: 'agent' };
56
- const token = await signToken(payload, deps.signingKey);
51
+ const expiresAt = nowMs + hardExpiryMs;
52
+ // Resume mints a fresh opaque token bound to the existing `tid` and
53
+ // rotates the stored hash. The previous bearer is dropped from the
54
+ // hash index immediately, so a leaked old token can't continue
55
+ // using the resumed session. Same record, new bearer.
56
+ const mint = deps.mint ?? mintToken;
57
+ const { token, tokenHash } = await mint();
58
+ await deps.tokenStore.rotateTokenHash(rec.tid, tokenHash, expiresAt);
57
59
  await deps.tokenStore.markActive(rec.tid, rec.label ?? '(resumed)', nowMs);
58
60
  await deps.auditSink.write({
59
61
  at: nowMs,
@@ -1 +1 @@
1
- {"version":3,"file":"resume.js","sourceRoot":"","sources":["../../../src/server/http/resume.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TokenStore } from '../token-store.js'\nimport type { IdentityResolver } from '../identity.js'\nimport type { AuditSink } from '../audit.js'\nimport { signToken } from '../token.js'\nimport type {\n ResumeListRequest,\n ResumeListResponse,\n ResumeClaimRequest,\n ResumeClaimResponse,\n TokenPayload,\n AgentSession,\n} from '../../protocol.js'\n\nexport type ResumeDeps = {\n tokenStore: TokenStore\n identityResolver: IdentityResolver\n auditSink: AuditSink\n signingKey?: string | Uint8Array\n now?: () => number\n hardExpiryMs?: number\n}\n\nexport async function handleResumeList(req: Request, deps: ResumeDeps): Promise<Response> {\n if (req.method !== 'POST') return methodNotAllowed()\n const body = (await req.json().catch(() => null)) as ResumeListRequest | null\n if (!body || !Array.isArray(body.tids)) return badRequest()\n\n const uid = await deps.identityResolver(req)\n const nowMs = (deps.now ?? (() => Date.now()))()\n const out: AgentSession[] = []\n for (const tid of body.tids) {\n const rec = await deps.tokenStore.findByTid(tid)\n if (!rec) continue\n if (rec.uid !== uid) continue\n if (rec.status !== 'pending-resume') continue\n if (rec.pendingResumeUntil === null || rec.pendingResumeUntil < nowMs) continue\n out.push({\n tid: rec.tid,\n label: rec.label ?? '(unknown)',\n status: 'pending-resume',\n createdAt: rec.createdAt,\n lastSeenAt: rec.lastSeenAt,\n })\n }\n\n const payload: ResumeListResponse = { sessions: out }\n return jsonResponse(payload, 200)\n}\n\nexport async function handleResumeClaim(req: Request, deps: ResumeDeps): Promise<Response> {\n if (req.method !== 'POST') return methodNotAllowed()\n if (!deps.signingKey) return new Response(null, { status: 500 })\n\n const body = (await req.json().catch(() => null)) as ResumeClaimRequest | null\n if (!body || typeof body.tid !== 'string') return badRequest()\n\n const uid = await deps.identityResolver(req)\n const rec = await deps.tokenStore.findByTid(body.tid)\n if (!rec) return forbidden()\n if (rec.uid !== uid) return forbidden()\n if (rec.status !== 'pending-resume') return forbidden()\n\n const origin = new URL(req.url).origin\n if (rec.origin !== origin) return forbidden()\n\n const nowMs = (deps.now ?? (() => Date.now()))()\n const hardExpiryMs = deps.hardExpiryMs ?? 24 * 60 * 60 * 1000\n const iat = Math.floor(nowMs / 1000)\n const exp = Math.floor((nowMs + hardExpiryMs) / 1000)\n const payload: TokenPayload = { tid: rec.tid, iat, exp, scope: 'agent' }\n const token = await signToken(payload, deps.signingKey)\n\n await deps.tokenStore.markActive(rec.tid, rec.label ?? '(resumed)', nowMs)\n\n await deps.auditSink.write({\n at: nowMs,\n tid: rec.tid,\n uid: rec.uid,\n event: 'claim',\n detail: { origin },\n })\n\n const wsUrl = toWsUrl(origin) + '/agent/ws'\n const out: ResumeClaimResponse = { token, wsUrl }\n return jsonResponse(out, 200)\n}\n\nfunction jsonResponse(body: unknown, status: number): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { 'content-type': 'application/json' },\n })\n}\n\nfunction methodNotAllowed(): Response {\n return jsonResponse({ error: { code: 'method-not-allowed' } }, 405)\n}\n\nfunction badRequest(): Response {\n return jsonResponse({ error: { code: 'invalid' } }, 400)\n}\n\nfunction forbidden(): Response {\n return jsonResponse({ error: { code: 'revoked' } }, 403)\n}\n\nfunction toWsUrl(httpOrigin: string): string {\n return httpOrigin.startsWith('https://')\n ? 'wss://' + httpOrigin.slice('https://'.length)\n : 'ws://' + httpOrigin.slice('http://'.length)\n}\n"]}
1
+ {"version":3,"file":"resume.js","sourceRoot":"","sources":["../../../src/server/http/resume.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TokenStore } from '../token-store.js'\nimport type { IdentityResolver } from '../identity.js'\nimport type { AuditSink } from '../audit.js'\nimport { mintToken } from '../token.js'\nimport type {\n ResumeListRequest,\n ResumeListResponse,\n ResumeClaimRequest,\n ResumeClaimResponse,\n AgentSession,\n} from '../../protocol.js'\n\nexport type ResumeDeps = {\n tokenStore: TokenStore\n identityResolver: IdentityResolver\n auditSink: AuditSink\n now?: () => number\n hardExpiryMs?: number\n /** Override mint primitive for tests. */\n mint?: typeof mintToken\n}\n\nexport async function handleResumeList(req: Request, deps: ResumeDeps): Promise<Response> {\n if (req.method !== 'POST') return methodNotAllowed()\n const body = (await req.json().catch(() => null)) as ResumeListRequest | null\n if (!body || !Array.isArray(body.tids)) return badRequest()\n\n const uid = await deps.identityResolver(req)\n const nowMs = (deps.now ?? (() => Date.now()))()\n const out: AgentSession[] = []\n for (const tid of body.tids) {\n const rec = await deps.tokenStore.findByTid(tid)\n if (!rec) continue\n if (rec.uid !== uid) continue\n if (rec.status !== 'pending-resume') continue\n if (rec.pendingResumeUntil === null || rec.pendingResumeUntil < nowMs) continue\n out.push({\n tid: rec.tid,\n label: rec.label ?? '(unknown)',\n status: 'pending-resume',\n createdAt: rec.createdAt,\n lastSeenAt: rec.lastSeenAt,\n })\n }\n\n const payload: ResumeListResponse = { sessions: out }\n return jsonResponse(payload, 200)\n}\n\nexport async function handleResumeClaim(req: Request, deps: ResumeDeps): Promise<Response> {\n if (req.method !== 'POST') return methodNotAllowed()\n\n const body = (await req.json().catch(() => null)) as ResumeClaimRequest | null\n if (!body || typeof body.tid !== 'string') return badRequest()\n\n const uid = await deps.identityResolver(req)\n const rec = await deps.tokenStore.findByTid(body.tid)\n if (!rec) return forbidden()\n if (rec.uid !== uid) return forbidden()\n if (rec.status !== 'pending-resume') return forbidden()\n\n const origin = new URL(req.url).origin\n if (rec.origin !== origin) return forbidden()\n\n const nowMs = (deps.now ?? (() => Date.now()))()\n const hardExpiryMs = deps.hardExpiryMs ?? 24 * 60 * 60 * 1000\n const expiresAt = nowMs + hardExpiryMs\n\n // Resume mints a fresh opaque token bound to the existing `tid` and\n // rotates the stored hash. The previous bearer is dropped from the\n // hash index immediately, so a leaked old token can't continue\n // using the resumed session. Same record, new bearer.\n const mint = deps.mint ?? mintToken\n const { token, tokenHash } = await mint()\n await deps.tokenStore.rotateTokenHash(rec.tid, tokenHash, expiresAt)\n await deps.tokenStore.markActive(rec.tid, rec.label ?? '(resumed)', nowMs)\n\n await deps.auditSink.write({\n at: nowMs,\n tid: rec.tid,\n uid: rec.uid,\n event: 'claim',\n detail: { origin },\n })\n\n const wsUrl = toWsUrl(origin) + '/agent/ws'\n const out: ResumeClaimResponse = { token, wsUrl }\n return jsonResponse(out, 200)\n}\n\nfunction jsonResponse(body: unknown, status: number): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { 'content-type': 'application/json' },\n })\n}\n\nfunction methodNotAllowed(): Response {\n return jsonResponse({ error: { code: 'method-not-allowed' } }, 405)\n}\n\nfunction badRequest(): Response {\n return jsonResponse({ error: { code: 'invalid' } }, 400)\n}\n\nfunction forbidden(): Response {\n return jsonResponse({ error: { code: 'revoked' } }, 403)\n}\n\nfunction toWsUrl(httpOrigin: string): string {\n return httpOrigin.startsWith('https://')\n ? 'wss://' + httpOrigin.slice('https://'.length)\n : 'ws://' + httpOrigin.slice('http://'.length)\n}\n"]}
@@ -21,6 +21,6 @@ export { consoleAuditSink } from './audit.js';
21
21
  export type { AuditSink } from './audit.js';
22
22
  export { defaultRateLimiter } from './rate-limit.js';
23
23
  export type { RateLimiter } from './rate-limit.js';
24
- export { signToken, verifyToken } from './token.js';
25
- export type { TokenPayload, VerifyResult } from './token.js';
24
+ export { mintToken, tokenHashOf } from './token.js';
25
+ export type { VerifyResult } from './token.js';
26
26
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AACpD,YAAY,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAEpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAA;AAC/C,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAC3E,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAClE,YAAY,EAAE,iBAAiB,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AACnG,OAAO,EAAE,GAAG,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAChE,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AACxE,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAC7C,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACpD,YAAY,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAClD,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACnD,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AACpD,YAAY,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAEpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAA;AAC/C,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAC3E,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAClE,YAAY,EAAE,iBAAiB,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AACnG,OAAO,EAAE,GAAG,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAChE,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AACxE,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAC7C,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACpD,YAAY,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAClD,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACnD,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA"}
@@ -14,5 +14,5 @@ export { InMemoryTokenStore } from './token-store.js';
14
14
  export { defaultIdentityResolver, signCookieValue } from './identity.js';
15
15
  export { consoleAuditSink } from './audit.js';
16
16
  export { defaultRateLimiter } from './rate-limit.js';
17
- export { signToken, verifyToken } from './token.js';
17
+ export { mintToken, tokenHashOf } from './token.js';
18
18
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AAEpD,+EAA+E;AAC/E,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAA;AAE/C,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAElE,OAAO,EAAE,GAAG,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAEhE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAErD,OAAO,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAExE,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAE7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAEpD,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA","sourcesContent":["/**\n * Default entry for Node server processes. Bundles the runtime-neutral\n * core with a `ws`-library-based WebSocket upgrade handler. For web\n * runtimes (Cloudflare Workers, Deno, Bun) where `ws` isn't\n * available, use `@llui/agent/server/core` + `@llui/agent/server/web`\n * instead.\n */\nexport { createLluiAgentServer } from './factory.js'\nexport type { ServerOptions, AgentServerHandle } from './options.js'\n// Runtime-neutral core — re-exported so Node users don't need a second import.\nexport { createLluiAgentCore } from './core.js'\nexport type { CoreOptions, AgentCoreHandle, AcceptResult } from './core.js'\nexport { InMemoryPairingRegistry } from './ws/pairing-registry.js'\nexport type { PairingConnection, PairingRegistry, FrameSubscriber } from './ws/pairing-registry.js'\nexport { rpc, waitForConfirm, waitForChange } from './ws/rpc.js'\nexport type { RpcOptions, RpcError } from './ws/rpc.js'\nexport { InMemoryTokenStore } from './token-store.js'\nexport type { TokenStore } from './token-store.js'\nexport { defaultIdentityResolver, signCookieValue } from './identity.js'\nexport type { IdentityResolver } from './identity.js'\nexport { consoleAuditSink } from './audit.js'\nexport type { AuditSink } from './audit.js'\nexport { defaultRateLimiter } from './rate-limit.js'\nexport type { RateLimiter } from './rate-limit.js'\nexport { signToken, verifyToken } from './token.js'\nexport type { TokenPayload, VerifyResult } from './token.js'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AAEpD,+EAA+E;AAC/E,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAA;AAE/C,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAElE,OAAO,EAAE,GAAG,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAEhE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAErD,OAAO,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAExE,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAE7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAEpD,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA","sourcesContent":["/**\n * Default entry for Node server processes. Bundles the runtime-neutral\n * core with a `ws`-library-based WebSocket upgrade handler. For web\n * runtimes (Cloudflare Workers, Deno, Bun) where `ws` isn't\n * available, use `@llui/agent/server/core` + `@llui/agent/server/web`\n * instead.\n */\nexport { createLluiAgentServer } from './factory.js'\nexport type { ServerOptions, AgentServerHandle } from './options.js'\n// Runtime-neutral core — re-exported so Node users don't need a second import.\nexport { createLluiAgentCore } from './core.js'\nexport type { CoreOptions, AgentCoreHandle, AcceptResult } from './core.js'\nexport { InMemoryPairingRegistry } from './ws/pairing-registry.js'\nexport type { PairingConnection, PairingRegistry, FrameSubscriber } from './ws/pairing-registry.js'\nexport { rpc, waitForConfirm, waitForChange } from './ws/rpc.js'\nexport type { RpcOptions, RpcError } from './ws/rpc.js'\nexport { InMemoryTokenStore } from './token-store.js'\nexport type { TokenStore } from './token-store.js'\nexport { defaultIdentityResolver, signCookieValue } from './identity.js'\nexport type { IdentityResolver } from './identity.js'\nexport { consoleAuditSink } from './audit.js'\nexport type { AuditSink } from './audit.js'\nexport { defaultRateLimiter } from './rate-limit.js'\nexport type { RateLimiter } from './rate-limit.js'\nexport { mintToken, tokenHashOf } from './token.js'\nexport type { VerifyResult } from './token.js'\n"]}
@@ -3,7 +3,6 @@ import type { PairingRegistry } from '../ws/pairing-registry.js';
3
3
  import type { AuditSink } from '../audit.js';
4
4
  import type { RateLimiter } from '../rate-limit.js';
5
5
  export type LapConfirmResultDeps = {
6
- signingKey: string | Uint8Array;
7
6
  tokenStore: TokenStore;
8
7
  registry: PairingRegistry;
9
8
  auditSink: AuditSink;
@@ -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;AAInD,MAAM,MAAM,oBAAoB,GAAG;IACjC,UAAU,EAAE,MAAM,GAAG,UAAU,CAAA;IAC/B,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;AAInD,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,6 +1,6 @@
1
1
  import { verifyAndReadTid } from './describe.js';
2
2
  export async function handleLapConfirmResult(req, deps) {
3
- const auth = await verifyAndReadTid(req, deps.signingKey);
3
+ const auth = await verifyAndReadTid(req, deps.tokenStore);
4
4
  if (!auth.ok)
5
5
  return json({ error: { code: auth.code } }, auth.status);
6
6
  const rec = await deps.tokenStore.findByTid(auth.tid);
@@ -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 type { LapConfirmResultRequest, LapConfirmResultResponse } from '../../protocol.js'\n\nexport type LapConfirmResultDeps = {\n signingKey: string | Uint8Array\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.signingKey)\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 json({ error: { code: 'paused' } }, 503)\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 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 json({ error: { code: 'paused' } }, 503)\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"]}
@@ -3,7 +3,6 @@ import type { PairingRegistry } from '../ws/pairing-registry.js';
3
3
  import type { AuditSink } from '../audit.js';
4
4
  import type { RateLimiter } from '../rate-limit.js';
5
5
  export type LapDescribeDeps = {
6
- signingKey: string | Uint8Array;
7
6
  tokenStore: TokenStore;
8
7
  registry: PairingRegistry;
9
8
  auditSink: AuditSink;
@@ -11,7 +10,19 @@ export type LapDescribeDeps = {
11
10
  now?: () => number;
12
11
  };
13
12
  export declare function handleLapDescribe(req: Request, deps: LapDescribeDeps): Promise<Response>;
14
- export declare function verifyAndReadTid(req: Request, key: string | Uint8Array): Promise<{
13
+ /**
14
+ * Resolve the bearer token on a request to a `tid`. The opaque-token
15
+ * scheme means "verify" is "look up the SHA-256 hash in the store and
16
+ * check expiry." A missing prefix, an unknown hash, or an expired
17
+ * record all collapse to the same `auth-failed` so a probe-by-hash
18
+ * leak surface is uniform.
19
+ *
20
+ * Status check (revoked / paused / etc.) is the caller's job — every
21
+ * LAP handler does its own follow-up `findByTid` to read the current
22
+ * status. This function only cares whether the bearer is one of ours
23
+ * and unexpired.
24
+ */
25
+ export declare function verifyAndReadTid(req: Request, tokenStore: TokenStore, nowMs?: number): Promise<{
15
26
  ok: true;
16
27
  tid: string;
17
28
  } | {
@@ -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;AAGnD,MAAM,MAAM,eAAe,GAAG;IAC5B,UAAU,EAAE,MAAM,GAAG,UAAU,CAAA;IAC/B,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,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,MAAM,GAAG,UAAU,GACvB,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,CAOlF"}
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;AAGnD,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,6 +1,6 @@
1
- import { verifyToken } from '../token.js';
1
+ import { tokenHashOf } from '../token.js';
2
2
  export async function handleLapDescribe(req, deps) {
3
- const auth = await verifyAndReadTid(req, deps.signingKey);
3
+ const auth = await verifyAndReadTid(req, deps.tokenStore);
4
4
  if (!auth.ok)
5
5
  return json({ error: { code: auth.code } }, auth.status);
6
6
  const rec = await deps.tokenStore.findByTid(auth.tid);
@@ -48,15 +48,32 @@ export async function handleLapDescribe(req, deps) {
48
48
  });
49
49
  return json(out, 200);
50
50
  }
51
- export async function verifyAndReadTid(req, key) {
51
+ /**
52
+ * Resolve the bearer token on a request to a `tid`. The opaque-token
53
+ * scheme means "verify" is "look up the SHA-256 hash in the store and
54
+ * check expiry." A missing prefix, an unknown hash, or an expired
55
+ * record all collapse to the same `auth-failed` so a probe-by-hash
56
+ * leak surface is uniform.
57
+ *
58
+ * Status check (revoked / paused / etc.) is the caller's job — every
59
+ * LAP handler does its own follow-up `findByTid` to read the current
60
+ * status. This function only cares whether the bearer is one of ours
61
+ * and unexpired.
62
+ */
63
+ export async function verifyAndReadTid(req, tokenStore, nowMs = Date.now()) {
52
64
  const auth = req.headers.get('authorization');
53
65
  if (!auth || !auth.startsWith('Bearer '))
54
66
  return { ok: false, status: 401, code: 'auth-failed' };
55
67
  const token = auth.slice('Bearer '.length);
56
- const v = await verifyToken(token, key);
57
- if (v.kind !== 'ok')
68
+ const hash = await tokenHashOf(token);
69
+ if (!hash)
58
70
  return { ok: false, status: 401, code: 'auth-failed' };
59
- return { ok: true, tid: v.payload.tid };
71
+ const rec = await tokenStore.findByTokenHash(hash);
72
+ if (!rec)
73
+ return { ok: false, status: 401, code: 'auth-failed' };
74
+ if (rec.expiresAt <= nowMs)
75
+ return { ok: false, status: 401, code: 'auth-failed' };
76
+ return { ok: true, tid: rec.tid };
60
77
  }
61
78
  function json(b, s) {
62
79
  return new Response(JSON.stringify(b), {
@@ -1 +1 @@
1
- {"version":3,"file":"describe.js","sourceRoot":"","sources":["../../../src/server/lap/describe.ts"],"names":[],"mappings":"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","sourcesContent":["import { verifyToken } 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 type { LapDescribeResponse, MessageSchemaEntry } from '../../protocol.js'\n\nexport type LapDescribeDeps = {\n signingKey: string | Uint8Array\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.signingKey)\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 json({ error: { code: 'paused' } }, 503)\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 json({ error: { code: 'paused' } }, 503)\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\nexport async function verifyAndReadTid(\n req: Request,\n key: string | Uint8Array,\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 v = await verifyToken(token, key)\n if (v.kind !== 'ok') return { ok: false, status: 401, code: 'auth-failed' }\n return { ok: true, tid: v.payload.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 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 json({ error: { code: 'paused' } }, 503)\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 json({ error: { code: 'paused' } }, 503)\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"]}
@@ -3,7 +3,6 @@ import type { PairingRegistry } from '../ws/pairing-registry.js';
3
3
  import type { AuditSink } from '../audit.js';
4
4
  import type { RateLimiter } from '../rate-limit.js';
5
5
  export type ForwardDeps = {
6
- signingKey: string | Uint8Array;
7
6
  tokenStore: TokenStore;
8
7
  registry: PairingRegistry;
9
8
  auditSink: AuditSink;
@@ -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;AAGnD,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,EAAE,MAAM,GAAG,UAAU,CAAA;IAC/B,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,EAAE,eAAe,CAAA;IACzB,SAAS,EAAE,SAAS,CAAA;IACpB,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,CAoClE;AAUD,eAAO,MAAM,cAAc,QA9CN,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CAkDhE,CAAA;AAEF,eAAO,MAAM,mBAAmB,QApDX,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CAwDhE,CAAA;AAEF,eAAO,MAAM,gBAAgB,QA1DR,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CA0DY,CAAA;AAE9E,eAAO,MAAM,iBAAiB,QA5DT,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CAgEhE,CAAA;AAEF,eAAO,MAAM,wBAAwB,QAlEhB,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CAkEgC,CAAA;AAElG,eAAO,MAAM,gBAAgB,QApER,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CAoEgB,CAAA;AAElF,eAAO,MAAM,sBAAsB,QAtEd,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CAiFhE,CAAA;AAEF;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAwD/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;AAGnD,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,CAoClE;AAUD,eAAO,MAAM,cAAc,QA9CN,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CAkDhE,CAAA;AAEF,eAAO,MAAM,mBAAmB,QApDX,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CAwDhE,CAAA;AAEF,eAAO,MAAM,gBAAgB,QA1DR,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CA0DY,CAAA;AAE9E,eAAO,MAAM,iBAAiB,QA5DT,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CAgEhE,CAAA;AAEF,eAAO,MAAM,wBAAwB,QAlEhB,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CAkEgC,CAAA;AAElG,eAAO,MAAM,gBAAgB,QApER,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CAoEgB,CAAA;AAElF,eAAO,MAAM,sBAAsB,QAtEd,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CAiFhE,CAAA;AAEF;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAwD/F"}
@@ -6,7 +6,7 @@ import { verifyAndReadTid } from './describe.js';
6
6
  */
7
7
  export function makeForwardHandler(tool, parseArgs, auditDetail = () => ({})) {
8
8
  return async (req, deps) => {
9
- const auth = await verifyAndReadTid(req, deps.signingKey);
9
+ const auth = await verifyAndReadTid(req, deps.tokenStore);
10
10
  if (!auth.ok)
11
11
  return json({ error: { code: auth.code } }, auth.status);
12
12
  const rec = await deps.tokenStore.findByTid(auth.tid);
@@ -92,7 +92,7 @@ export const handleLapWouldDispatch = makeForwardHandler('would_dispatch', (body
92
92
  * rate-limit gates run identically.
93
93
  */
94
94
  export async function handleLapRecentActions(req, deps) {
95
- const auth = await verifyAndReadTid(req, deps.signingKey);
95
+ const auth = await verifyAndReadTid(req, deps.tokenStore);
96
96
  if (!auth.ok)
97
97
  return new Response(JSON.stringify({ error: { code: auth.code } }), {
98
98
  status: auth.status,
@@ -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'\n\nexport type ForwardDeps = {\n signingKey: string | Uint8Array\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.signingKey)\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 json({ error: { code: 'paused' } }, 503)\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 const status = code === 'paused' ? 503 : 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.signingKey)\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 new Response(JSON.stringify({ error: { code: 'paused' } }), {\n status: 503,\n headers: { 'content-type': 'application/json' },\n })\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'\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 json({ error: { code: 'paused' } }, 503)\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 const status = code === 'paused' ? 503 : 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 new Response(JSON.stringify({ error: { code: 'paused' } }), {\n status: 503,\n headers: { 'content-type': 'application/json' },\n })\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"]}
@@ -3,7 +3,6 @@ import type { PairingRegistry } from '../ws/pairing-registry.js';
3
3
  import type { AuditSink } from '../audit.js';
4
4
  import type { RateLimiter } from '../rate-limit.js';
5
5
  export type LapMessageDeps = {
6
- signingKey: string | Uint8Array;
7
6
  tokenStore: TokenStore;
8
7
  registry: PairingRegistry;
9
8
  auditSink: AuditSink;
@@ -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;AAInD,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,EAAE,MAAM,GAAG,UAAU,CAAA;IAC/B,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;AAInD,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,6 +1,6 @@
1
1
  import { verifyAndReadTid } from './describe.js';
2
2
  export async function handleLapMessage(req, deps) {
3
- const auth = await verifyAndReadTid(req, deps.signingKey);
3
+ const auth = await verifyAndReadTid(req, deps.tokenStore);
4
4
  if (!auth.ok)
5
5
  return json({ error: { code: auth.code } }, auth.status);
6
6
  const rec = await deps.tokenStore.findByTid(auth.tid);
@@ -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 type { LapMessageRequest, LapMessageResponse } from '../../protocol.js'\n\nexport type LapMessageDeps = {\n signingKey: string | Uint8Array\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.signingKey)\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 json({ error: { code: 'paused' } }, 503)\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 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 json({ error: { code: 'paused' } }, 503)\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"]}
@@ -3,7 +3,6 @@ import type { PairingRegistry } from '../ws/pairing-registry.js';
3
3
  import type { AuditSink } from '../audit.js';
4
4
  import type { RateLimiter } from '../rate-limit.js';
5
5
  export type LapObserveDeps = {
6
- signingKey: string | Uint8Array;
7
6
  tokenStore: TokenStore;
8
7
  registry: PairingRegistry;
9
8
  auditSink: AuditSink;