@llui/agent 0.0.29

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 (151) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +210 -0
  3. package/dist/client/agentConfirm.d.ts +60 -0
  4. package/dist/client/agentConfirm.d.ts.map +1 -0
  5. package/dist/client/agentConfirm.js +66 -0
  6. package/dist/client/agentConfirm.js.map +1 -0
  7. package/dist/client/agentConnect.d.ts +125 -0
  8. package/dist/client/agentConnect.d.ts.map +1 -0
  9. package/dist/client/agentConnect.js +114 -0
  10. package/dist/client/agentConnect.js.map +1 -0
  11. package/dist/client/agentLog.d.ts +51 -0
  12. package/dist/client/agentLog.d.ts.map +1 -0
  13. package/dist/client/agentLog.js +53 -0
  14. package/dist/client/agentLog.js.map +1 -0
  15. package/dist/client/effect-handler.d.ts +15 -0
  16. package/dist/client/effect-handler.d.ts.map +1 -0
  17. package/dist/client/effect-handler.js +146 -0
  18. package/dist/client/effect-handler.js.map +1 -0
  19. package/dist/client/effects.d.ts +27 -0
  20. package/dist/client/effects.d.ts.map +1 -0
  21. package/dist/client/effects.js +2 -0
  22. package/dist/client/effects.js.map +1 -0
  23. package/dist/client/factory.d.ts +47 -0
  24. package/dist/client/factory.d.ts.map +1 -0
  25. package/dist/client/factory.js +105 -0
  26. package/dist/client/factory.js.map +1 -0
  27. package/dist/client/index.d.ts +7 -0
  28. package/dist/client/index.d.ts.map +1 -0
  29. package/dist/client/index.js +5 -0
  30. package/dist/client/index.js.map +1 -0
  31. package/dist/client/rpc/describe-context.d.ts +10 -0
  32. package/dist/client/rpc/describe-context.d.ts.map +1 -0
  33. package/dist/client/rpc/describe-context.js +8 -0
  34. package/dist/client/rpc/describe-context.js.map +1 -0
  35. package/dist/client/rpc/describe-visible-content.d.ts +22 -0
  36. package/dist/client/rpc/describe-visible-content.d.ts.map +1 -0
  37. package/dist/client/rpc/describe-visible-content.js +66 -0
  38. package/dist/client/rpc/describe-visible-content.js.map +1 -0
  39. package/dist/client/rpc/get-state.d.ts +15 -0
  40. package/dist/client/rpc/get-state.d.ts.map +1 -0
  41. package/dist/client/rpc/get-state.js +37 -0
  42. package/dist/client/rpc/get-state.js.map +1 -0
  43. package/dist/client/rpc/list-actions.d.ts +27 -0
  44. package/dist/client/rpc/list-actions.d.ts.map +1 -0
  45. package/dist/client/rpc/list-actions.js +38 -0
  46. package/dist/client/rpc/list-actions.js.map +1 -0
  47. package/dist/client/rpc/query-dom.d.ts +20 -0
  48. package/dist/client/rpc/query-dom.d.ts.map +1 -0
  49. package/dist/client/rpc/query-dom.js +37 -0
  50. package/dist/client/rpc/query-dom.js.map +1 -0
  51. package/dist/client/rpc/send-message.d.ts +28 -0
  52. package/dist/client/rpc/send-message.d.ts.map +1 -0
  53. package/dist/client/rpc/send-message.js +40 -0
  54. package/dist/client/rpc/send-message.js.map +1 -0
  55. package/dist/client/uuid.d.ts +2 -0
  56. package/dist/client/uuid.d.ts.map +1 -0
  57. package/dist/client/uuid.js +24 -0
  58. package/dist/client/uuid.js.map +1 -0
  59. package/dist/client/ws-client.d.ts +44 -0
  60. package/dist/client/ws-client.d.ts.map +1 -0
  61. package/dist/client/ws-client.js +176 -0
  62. package/dist/client/ws-client.js.map +1 -0
  63. package/dist/protocol.d.ts +319 -0
  64. package/dist/protocol.d.ts.map +1 -0
  65. package/dist/protocol.js +6 -0
  66. package/dist/protocol.js.map +1 -0
  67. package/dist/server/audit.d.ts +6 -0
  68. package/dist/server/audit.d.ts.map +1 -0
  69. package/dist/server/audit.js +6 -0
  70. package/dist/server/audit.js.map +1 -0
  71. package/dist/server/factory.d.ts +10 -0
  72. package/dist/server/factory.d.ts.map +1 -0
  73. package/dist/server/factory.js +69 -0
  74. package/dist/server/factory.js.map +1 -0
  75. package/dist/server/http/mint.d.ts +23 -0
  76. package/dist/server/http/mint.d.ts.map +1 -0
  77. package/dist/server/http/mint.js +63 -0
  78. package/dist/server/http/mint.js.map +1 -0
  79. package/dist/server/http/resume.d.ts +14 -0
  80. package/dist/server/http/resume.d.ts.map +1 -0
  81. package/dist/server/http/resume.js +89 -0
  82. package/dist/server/http/resume.js.map +1 -0
  83. package/dist/server/http/revoke.d.ts +11 -0
  84. package/dist/server/http/revoke.d.ts.map +1 -0
  85. package/dist/server/http/revoke.js +24 -0
  86. package/dist/server/http/revoke.js.map +1 -0
  87. package/dist/server/http/router.d.ts +13 -0
  88. package/dist/server/http/router.d.ts.map +1 -0
  89. package/dist/server/http/router.js +28 -0
  90. package/dist/server/http/router.js.map +1 -0
  91. package/dist/server/http/sessions.d.ts +8 -0
  92. package/dist/server/http/sessions.d.ts.map +1 -0
  93. package/dist/server/http/sessions.js +27 -0
  94. package/dist/server/http/sessions.js.map +1 -0
  95. package/dist/server/identity.d.ts +8 -0
  96. package/dist/server/identity.d.ts.map +1 -0
  97. package/dist/server/identity.js +41 -0
  98. package/dist/server/identity.js.map +1 -0
  99. package/dist/server/index.d.ts +11 -0
  100. package/dist/server/index.d.ts.map +1 -0
  101. package/dist/server/index.js +6 -0
  102. package/dist/server/index.js.map +1 -0
  103. package/dist/server/lap/confirm-result.d.ts +14 -0
  104. package/dist/server/lap/confirm-result.d.ts.map +1 -0
  105. package/dist/server/lap/confirm-result.js +60 -0
  106. package/dist/server/lap/confirm-result.js.map +1 -0
  107. package/dist/server/lap/describe.d.ts +22 -0
  108. package/dist/server/lap/describe.d.ts.map +1 -0
  109. package/dist/server/lap/describe.js +67 -0
  110. package/dist/server/lap/describe.js.map +1 -0
  111. package/dist/server/lap/forward.d.ts +24 -0
  112. package/dist/server/lap/forward.d.ts.map +1 -0
  113. package/dist/server/lap/forward.js +68 -0
  114. package/dist/server/lap/forward.js.map +1 -0
  115. package/dist/server/lap/message.d.ts +14 -0
  116. package/dist/server/lap/message.d.ts.map +1 -0
  117. package/dist/server/lap/message.js +97 -0
  118. package/dist/server/lap/message.js.map +1 -0
  119. package/dist/server/lap/router.d.ts +4 -0
  120. package/dist/server/lap/router.d.ts.map +1 -0
  121. package/dist/server/lap/router.js +37 -0
  122. package/dist/server/lap/router.js.map +1 -0
  123. package/dist/server/lap/wait.d.ts +14 -0
  124. package/dist/server/lap/wait.d.ts.map +1 -0
  125. package/dist/server/lap/wait.js +35 -0
  126. package/dist/server/lap/wait.js.map +1 -0
  127. package/dist/server/options.d.ts +41 -0
  128. package/dist/server/options.d.ts.map +1 -0
  129. package/dist/server/options.js +2 -0
  130. package/dist/server/options.js.map +1 -0
  131. package/dist/server/rate-limit.d.ts +14 -0
  132. package/dist/server/rate-limit.d.ts.map +1 -0
  133. package/dist/server/rate-limit.js +43 -0
  134. package/dist/server/rate-limit.js.map +1 -0
  135. package/dist/server/token-store.d.ts +27 -0
  136. package/dist/server/token-store.d.ts.map +1 -0
  137. package/dist/server/token-store.js +55 -0
  138. package/dist/server/token-store.js.map +1 -0
  139. package/dist/server/token.d.ts +24 -0
  140. package/dist/server/token.d.ts.map +1 -0
  141. package/dist/server/token.js +77 -0
  142. package/dist/server/token.js.map +1 -0
  143. package/dist/server/ws/pairing-registry.d.ts +53 -0
  144. package/dist/server/ws/pairing-registry.d.ts.map +1 -0
  145. package/dist/server/ws/pairing-registry.js +205 -0
  146. package/dist/server/ws/pairing-registry.js.map +1 -0
  147. package/dist/server/ws/upgrade.d.ts +23 -0
  148. package/dist/server/ws/upgrade.d.ts.map +1 -0
  149. package/dist/server/ws/upgrade.js +81 -0
  150. package/dist/server/ws/upgrade.js.map +1 -0
  151. package/package.json +57 -0
@@ -0,0 +1,68 @@
1
+ import { verifyAndReadTid } from './describe.js';
2
+ /**
3
+ * Generic LAP handler. `parseArgs` is called with the parsed body (may be
4
+ * null for empty bodies); it returns the args object to forward or null
5
+ * to reject as invalid. `tool` is the browser-side tool name.
6
+ */
7
+ export function makeForwardHandler(tool, parseArgs, auditDetail = () => ({})) {
8
+ return async (req, deps) => {
9
+ const auth = verifyAndReadTid(req, deps.signingKey);
10
+ if (!auth.ok)
11
+ return json({ error: { code: auth.code } }, auth.status);
12
+ const rec = await deps.tokenStore.findByTid(auth.tid);
13
+ if (!rec || rec.status === 'revoked')
14
+ return json({ error: { code: 'revoked' } }, 403);
15
+ if (!deps.registry.isPaired(auth.tid))
16
+ return json({ error: { code: 'paused' } }, 503);
17
+ const rlCheck = await deps.rateLimiter.check(auth.tid, 'token');
18
+ if (!rlCheck.allowed) {
19
+ return json({ error: { code: 'rate-limited', retryAfterMs: rlCheck.retryAfterMs } }, 429);
20
+ }
21
+ const rawBody = req.method === 'POST' ? await req.json().catch(() => null) : null;
22
+ const args = parseArgs(rawBody);
23
+ if (args === null)
24
+ return json({ error: { code: 'invalid' } }, 400);
25
+ try {
26
+ const result = await deps.registry.rpc(auth.tid, tool, args);
27
+ const nowMs = (deps.now ?? (() => Date.now()))();
28
+ await deps.tokenStore.touch(auth.tid, nowMs);
29
+ await deps.auditSink.write({
30
+ at: nowMs,
31
+ tid: auth.tid,
32
+ uid: rec.uid,
33
+ event: 'lap-call',
34
+ detail: { tool, ...auditDetail(auth.tid, args) },
35
+ });
36
+ return json(result, 200);
37
+ }
38
+ catch (e) {
39
+ const err = e;
40
+ const code = err.code ?? 'internal';
41
+ const status = code === 'paused' ? 503 : code === 'timeout' ? 504 : 500;
42
+ return json({ error: { code, detail: err.detail } }, status);
43
+ }
44
+ };
45
+ }
46
+ function json(b, s) {
47
+ return new Response(JSON.stringify(b), {
48
+ status: s,
49
+ headers: { 'content-type': 'application/json' },
50
+ });
51
+ }
52
+ // Concrete handlers:
53
+ export const handleLapState = makeForwardHandler('get_state', (body) => {
54
+ const b = (body ?? {});
55
+ if (b.path !== undefined && typeof b.path !== 'string')
56
+ return null;
57
+ return { path: b.path };
58
+ });
59
+ export const handleLapActions = makeForwardHandler('list_actions', () => ({}));
60
+ export const handleLapQueryDom = makeForwardHandler('query_dom', (body) => {
61
+ const b = (body ?? {});
62
+ if (typeof b.name !== 'string')
63
+ return null;
64
+ return { name: b.name, multiple: !!b.multiple };
65
+ });
66
+ export const handleLapDescribeVisible = makeForwardHandler('describe_visible_content', () => ({}));
67
+ export const handleLapContext = makeForwardHandler('describe_context', () => ({}));
68
+ //# sourceMappingURL=forward.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forward.js","sourceRoot":"","sources":["../../../src/server/lap/forward.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAWhD;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAChC,IAAY,EACZ,SAA2C,EAC3C,cAAsE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC;IAEhF,OAAO,KAAK,EAAE,GAAY,EAAE,IAAiB,EAAqB,EAAE;QAClE,MAAM,IAAI,GAAG,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,CAAA;QACnD,IAAI,CAAC,IAAI,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;QAEtE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QACrD,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,GAAG,CAAC,CAAA;QACtF,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,GAAG,CAAC,CAAA;QAEtF,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;QAC/D,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YACrB,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE,EAAE,EAAE,GAAG,CAAC,CAAA;QAC3F,CAAC;QAED,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;QACjF,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,CAAA;QAC/B,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,GAAG,CAAC,CAAA;QAEnE,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;YAC5D,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAA;YAChD,MAAM,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;YAC5C,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;gBACzB,EAAE,EAAE,KAAK;gBACT,GAAG,EAAE,IAAI,CAAC,GAAG;gBACb,GAAG,EAAE,GAAG,CAAC,GAAG;gBACZ,KAAK,EAAE,UAAU;gBACjB,MAAM,EAAE,EAAE,IAAI,EAAE,GAAG,WAAW,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE;aACjD,CAAC,CAAA;YACF,OAAO,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;QAC1B,CAAC;QAAC,OAAO,CAAU,EAAE,CAAC;YACpB,MAAM,GAAG,GAAG,CAAuC,CAAA;YACnD,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,IAAI,UAAU,CAAA;YACnC,MAAM,MAAM,GAAG,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAA;YACvE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,CAAA;QAC9D,CAAC;IACH,CAAC,CAAA;AACH,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;AAED,qBAAqB;AACrB,MAAM,CAAC,MAAM,cAAc,GAAG,kBAAkB,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE;IACrE,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAuB,CAAA;IAC5C,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IACnE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;AACzB,CAAC,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG,kBAAkB,CAAC,cAAc,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;AAE9E,MAAM,CAAC,MAAM,iBAAiB,GAAG,kBAAkB,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE;IACxE,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAA2C,CAAA;IAChE,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IAC3C,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;AACjD,CAAC,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,wBAAwB,GAAG,kBAAkB,CAAC,0BAA0B,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;AAElG,MAAM,CAAC,MAAM,gBAAgB,GAAG,kBAAkB,CAAC,kBAAkB,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA","sourcesContent":["import type { TokenStore } from '../token-store.js'\nimport type { WsPairingRegistry } 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: WsPairingRegistry\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 = 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 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"]}
@@ -0,0 +1,14 @@
1
+ import type { TokenStore } from '../token-store.js';
2
+ import type { WsPairingRegistry } from '../ws/pairing-registry.js';
3
+ import type { AuditSink } from '../audit.js';
4
+ import type { RateLimiter } from '../rate-limit.js';
5
+ export type LapMessageDeps = {
6
+ signingKey: string | Uint8Array;
7
+ tokenStore: TokenStore;
8
+ registry: WsPairingRegistry;
9
+ auditSink: AuditSink;
10
+ rateLimiter: RateLimiter;
11
+ now?: () => number;
12
+ };
13
+ export declare function handleLapMessage(req: Request, deps: LapMessageDeps): Promise<Response>;
14
+ //# sourceMappingURL=message.d.ts.map
@@ -0,0 +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,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAClE,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,iBAAiB,CAAA;IAC3B,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,CAwG5F"}
@@ -0,0 +1,97 @@
1
+ import { verifyAndReadTid } from './describe.js';
2
+ export async function handleLapMessage(req, deps) {
3
+ const auth = verifyAndReadTid(req, deps.signingKey);
4
+ if (!auth.ok)
5
+ return json({ error: { code: auth.code } }, auth.status);
6
+ const rec = await deps.tokenStore.findByTid(auth.tid);
7
+ if (!rec || rec.status === 'revoked')
8
+ return json({ error: { code: 'revoked' } }, 403);
9
+ if (!deps.registry.isPaired(auth.tid))
10
+ return json({ error: { code: 'paused' } }, 503);
11
+ const rlCheck = await deps.rateLimiter.check(auth.tid, 'token');
12
+ if (!rlCheck.allowed) {
13
+ return json({ error: { code: 'rate-limited', retryAfterMs: rlCheck.retryAfterMs } }, 429);
14
+ }
15
+ const body = (await req.json().catch(() => null));
16
+ if (!body || !body.msg || typeof body.msg.type !== 'string') {
17
+ return json({ error: { code: 'invalid' } }, 400);
18
+ }
19
+ const timeoutMs = body.timeoutMs ?? 15_000;
20
+ let initial;
21
+ try {
22
+ initial = (await deps.registry.rpc(auth.tid, 'send_message', body, {
23
+ timeoutMs,
24
+ }));
25
+ }
26
+ catch (e) {
27
+ const err = e;
28
+ const status = err.code === 'paused' ? 503 : err.code === 'timeout' ? 504 : 500;
29
+ // Build a detail string that surfaces whatever info we have — the rpc-error
30
+ // frame from the browser sometimes lacks `detail` (e.g., when a JS TypeError
31
+ // bubbles out of the handler). Falling back to the code + any Error-like
32
+ // fields gives Claude something actionable instead of an opaque 500.
33
+ const detail = err.detail ??
34
+ (e instanceof Error ? `${e.name}: ${e.message}` : undefined) ??
35
+ (err.code ? `rpc rejected with code '${err.code}'` : 'rpc rejected without a code');
36
+ // Mirror to the server console so operators see the real cause even when
37
+ // the client just shows "internal".
38
+ console.error(`[llui-agent] /lap/v1/message 500 — code=${err.code ?? 'internal'}, detail=${detail}`);
39
+ return json({ error: { code: err.code ?? 'internal', detail } }, status);
40
+ }
41
+ const nowMs = (deps.now ?? (() => Date.now()))();
42
+ await deps.tokenStore.touch(auth.tid, nowMs);
43
+ if (initial.status === 'dispatched' ||
44
+ initial.status === 'confirmed' ||
45
+ initial.status === 'rejected') {
46
+ await deps.auditSink.write({
47
+ at: nowMs,
48
+ tid: auth.tid,
49
+ uid: rec.uid,
50
+ event: initial.status === 'rejected' ? 'msg-blocked' : 'msg-dispatched',
51
+ detail: { variant: body.msg.type, status: initial.status },
52
+ });
53
+ return json(initial, 200);
54
+ }
55
+ if (initial.status === 'pending-confirmation') {
56
+ await deps.auditSink.write({
57
+ at: nowMs,
58
+ tid: auth.tid,
59
+ uid: rec.uid,
60
+ event: 'confirm-proposed',
61
+ detail: { variant: body.msg.type, confirmId: initial.confirmId },
62
+ });
63
+ const resolved = await deps.registry.waitForConfirm(auth.tid, initial.confirmId, timeoutMs);
64
+ const nowMs2 = (deps.now ?? (() => Date.now()))();
65
+ if (resolved.outcome === 'confirmed') {
66
+ await deps.auditSink.write({
67
+ at: nowMs2,
68
+ tid: auth.tid,
69
+ uid: rec.uid,
70
+ event: 'confirm-approved',
71
+ detail: { variant: body.msg.type, confirmId: initial.confirmId },
72
+ });
73
+ return json({ status: 'confirmed', stateAfter: resolved.stateAfter }, 200);
74
+ }
75
+ await deps.auditSink.write({
76
+ at: nowMs2,
77
+ tid: auth.tid,
78
+ uid: rec.uid,
79
+ event: 'confirm-rejected',
80
+ detail: { variant: body.msg.type, confirmId: initial.confirmId },
81
+ });
82
+ return json({ status: 'rejected', reason: 'user-cancelled' }, 200);
83
+ }
84
+ return json({
85
+ error: {
86
+ code: 'internal',
87
+ detail: `unexpected browser status: ${String(initial.status ?? 'undefined')}`,
88
+ },
89
+ }, 500);
90
+ }
91
+ function json(b, s) {
92
+ return new Response(JSON.stringify(b), {
93
+ status: s,
94
+ headers: { 'content-type': 'application/json' },
95
+ });
96
+ }
97
+ //# sourceMappingURL=message.js.map
@@ -0,0 +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 { WsPairingRegistry } 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: WsPairingRegistry\n auditSink: AuditSink\n rateLimiter: RateLimiter\n now?: () => number\n}\n\nexport async function handleLapMessage(req: Request, deps: LapMessageDeps): Promise<Response> {\n const auth = 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 ?? 15_000\n\n let initial: LapMessageResponse\n try {\n initial = (await deps.registry.rpc(auth.tid, 'send_message', body, {\n timeoutMs,\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"]}
@@ -0,0 +1,4 @@
1
+ import { type ForwardDeps } from './forward.js';
2
+ export type LapRouterDeps = ForwardDeps;
3
+ export declare function createLapRouter(deps: LapRouterDeps, basePath: string): (req: Request) => Promise<Response | null>;
4
+ //# sourceMappingURL=router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../../src/server/lap/router.ts"],"names":[],"mappings":"AACA,OAAO,EAML,KAAK,WAAW,EACjB,MAAM,cAAc,CAAA;AAKrB,MAAM,MAAM,aAAa,GAAG,WAAW,CAAA;AAEvC,wBAAgB,eAAe,CAC7B,IAAI,EAAE,aAAa,EACnB,QAAQ,EAAE,MAAM,GACf,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CA6B5C"}
@@ -0,0 +1,37 @@
1
+ import { handleLapDescribe } from './describe.js';
2
+ import { handleLapState, handleLapActions, handleLapQueryDom, handleLapDescribeVisible, handleLapContext, } from './forward.js';
3
+ import { handleLapMessage } from './message.js';
4
+ import { handleLapWait } from './wait.js';
5
+ import { handleLapConfirmResult } from './confirm-result.js';
6
+ export function createLapRouter(deps, basePath) {
7
+ return async (req) => {
8
+ const url = new URL(req.url);
9
+ const path = url.pathname;
10
+ if (!path.startsWith(basePath + '/'))
11
+ return null;
12
+ const tail = path.slice(basePath.length);
13
+ switch (tail) {
14
+ case '/describe':
15
+ return handleLapDescribe(req, deps);
16
+ case '/state':
17
+ return handleLapState(req, deps);
18
+ case '/actions':
19
+ return handleLapActions(req, deps);
20
+ case '/message':
21
+ return handleLapMessage(req, deps);
22
+ case '/confirm-result':
23
+ return handleLapConfirmResult(req, deps);
24
+ case '/wait':
25
+ return handleLapWait(req, deps);
26
+ case '/query-dom':
27
+ return handleLapQueryDom(req, deps);
28
+ case '/describe-visible':
29
+ return handleLapDescribeVisible(req, deps);
30
+ case '/context':
31
+ return handleLapContext(req, deps);
32
+ default:
33
+ return null;
34
+ }
35
+ };
36
+ }
37
+ //# sourceMappingURL=router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../../../src/server/lap/router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,wBAAwB,EACxB,gBAAgB,GAEjB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AACzC,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AAI5D,MAAM,UAAU,eAAe,CAC7B,IAAmB,EACnB,QAAgB;IAEhB,OAAO,KAAK,EAAE,GAAG,EAAE,EAAE;QACnB,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC5B,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAA;QACzB,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,GAAG,GAAG,CAAC;YAAE,OAAO,IAAI,CAAA;QACjD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;QACxC,QAAQ,IAAI,EAAE,CAAC;YACb,KAAK,WAAW;gBACd,OAAO,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YACrC,KAAK,QAAQ;gBACX,OAAO,cAAc,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YAClC,KAAK,UAAU;gBACb,OAAO,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YACpC,KAAK,UAAU;gBACb,OAAO,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YACpC,KAAK,iBAAiB;gBACpB,OAAO,sBAAsB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YAC1C,KAAK,OAAO;gBACV,OAAO,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YACjC,KAAK,YAAY;gBACf,OAAO,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YACrC,KAAK,mBAAmB;gBACtB,OAAO,wBAAwB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YAC5C,KAAK,UAAU;gBACb,OAAO,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YACpC;gBACE,OAAO,IAAI,CAAA;QACf,CAAC;IACH,CAAC,CAAA;AACH,CAAC","sourcesContent":["import { handleLapDescribe } from './describe.js'\nimport {\n handleLapState,\n handleLapActions,\n handleLapQueryDom,\n handleLapDescribeVisible,\n handleLapContext,\n type ForwardDeps,\n} from './forward.js'\nimport { handleLapMessage } from './message.js'\nimport { handleLapWait } from './wait.js'\nimport { handleLapConfirmResult } from './confirm-result.js'\n\nexport type LapRouterDeps = ForwardDeps\n\nexport function createLapRouter(\n deps: LapRouterDeps,\n basePath: string,\n): (req: Request) => Promise<Response | null> {\n return async (req) => {\n const url = new URL(req.url)\n const path = url.pathname\n if (!path.startsWith(basePath + '/')) return null\n const tail = path.slice(basePath.length)\n switch (tail) {\n case '/describe':\n return handleLapDescribe(req, deps)\n case '/state':\n return handleLapState(req, deps)\n case '/actions':\n return handleLapActions(req, deps)\n case '/message':\n return handleLapMessage(req, deps)\n case '/confirm-result':\n return handleLapConfirmResult(req, deps)\n case '/wait':\n return handleLapWait(req, deps)\n case '/query-dom':\n return handleLapQueryDom(req, deps)\n case '/describe-visible':\n return handleLapDescribeVisible(req, deps)\n case '/context':\n return handleLapContext(req, deps)\n default:\n return null\n }\n }\n}\n"]}
@@ -0,0 +1,14 @@
1
+ import type { TokenStore } from '../token-store.js';
2
+ import type { WsPairingRegistry } from '../ws/pairing-registry.js';
3
+ import type { AuditSink } from '../audit.js';
4
+ import type { RateLimiter } from '../rate-limit.js';
5
+ export type LapWaitDeps = {
6
+ signingKey: string | Uint8Array;
7
+ tokenStore: TokenStore;
8
+ registry: WsPairingRegistry;
9
+ auditSink: AuditSink;
10
+ rateLimiter: RateLimiter;
11
+ now?: () => number;
12
+ };
13
+ export declare function handleLapWait(req: Request, deps: LapWaitDeps): Promise<Response>;
14
+ //# sourceMappingURL=wait.d.ts.map
@@ -0,0 +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,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAClE,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,MAAM,GAAG,UAAU,CAAA;IAC/B,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,EAAE,iBAAiB,CAAA;IAC3B,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"}
@@ -0,0 +1,35 @@
1
+ import { verifyAndReadTid } from './describe.js';
2
+ export async function handleLapWait(req, deps) {
3
+ const auth = verifyAndReadTid(req, deps.signingKey);
4
+ if (!auth.ok)
5
+ return json({ error: { code: auth.code } }, auth.status);
6
+ const rec = await deps.tokenStore.findByTid(auth.tid);
7
+ if (!rec || rec.status === 'revoked')
8
+ return json({ error: { code: 'revoked' } }, 403);
9
+ if (!deps.registry.isPaired(auth.tid))
10
+ return json({ error: { code: 'paused' } }, 503);
11
+ const rlCheck = await deps.rateLimiter.check(auth.tid, 'token');
12
+ if (!rlCheck.allowed) {
13
+ return json({ error: { code: 'rate-limited', retryAfterMs: rlCheck.retryAfterMs } }, 429);
14
+ }
15
+ const body = ((await req.json().catch(() => null)) ?? {});
16
+ const timeoutMs = body.timeoutMs ?? 10_000;
17
+ const result = await deps.registry.waitForChange(auth.tid, body.path, timeoutMs);
18
+ const out = result;
19
+ const nowMs = (deps.now ?? (() => Date.now()))();
20
+ await deps.auditSink.write({
21
+ at: nowMs,
22
+ tid: auth.tid,
23
+ uid: rec.uid,
24
+ event: 'lap-call',
25
+ detail: { path: '/lap/v1/wait', outcome: result.status },
26
+ });
27
+ return json(out, 200);
28
+ }
29
+ function json(b, s) {
30
+ return new Response(JSON.stringify(b), {
31
+ status: s,
32
+ headers: { 'content-type': 'application/json' },
33
+ });
34
+ }
35
+ //# sourceMappingURL=wait.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wait.js","sourceRoot":"","sources":["../../../src/server/lap/wait.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAYhD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAY,EAAE,IAAiB;IACjE,MAAM,IAAI,GAAG,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,CAAA;IACnD,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,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,GAAG,CAAC,CAAA;IAEtF,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 { WsPairingRegistry } 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 { LapWaitRequest, LapWaitResponse } from '../../protocol.js'\n\nexport type LapWaitDeps = {\n signingKey: string | Uint8Array\n tokenStore: TokenStore\n registry: WsPairingRegistry\n auditSink: AuditSink\n rateLimiter: RateLimiter\n now?: () => number\n}\n\nexport async function handleLapWait(req: Request, deps: LapWaitDeps): Promise<Response> {\n const auth = 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 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"]}
@@ -0,0 +1,41 @@
1
+ import type { IncomingMessage } from 'node:http';
2
+ import type { Duplex } from 'node:stream';
3
+ import type { TokenStore } from './token-store.js';
4
+ import type { IdentityResolver } from './identity.js';
5
+ import type { AuditSink } from './audit.js';
6
+ import type { RateLimiter } from './rate-limit.js';
7
+ /**
8
+ * Options accepted by `createLluiAgentServer`. All values except
9
+ * `signingKey` are optional and fall back to in-memory defaults.
10
+ * See spec §10.1.
11
+ */
12
+ export type ServerOptions = {
13
+ /** HMAC key for signing tokens. ≥32 bytes; rotation invalidates all tokens. */
14
+ signingKey: string | Uint8Array;
15
+ /** Token store. Defaults to an `InMemoryTokenStore`. */
16
+ tokenStore?: TokenStore;
17
+ /** Identity resolver. Defaults to anonymous (always null). */
18
+ identityResolver?: IdentityResolver;
19
+ /** Audit sink. Defaults to `consoleAuditSink`. */
20
+ auditSink?: AuditSink;
21
+ /** Rate limiter. Defaults to `defaultRateLimiter` with 30/minute. */
22
+ rateLimiter?: RateLimiter;
23
+ /** Base path prefix for LAP endpoints. Defaults to `/agent/lap/v1`. */
24
+ lapBasePath?: string;
25
+ /** Pairing grace window after a tab closes, in ms. Default 15 min. */
26
+ pairingGraceMs?: number;
27
+ /** Sliding TTL for active tokens, in ms. Default 1 h. */
28
+ slidingTtlMs?: number;
29
+ /** Allowed origins for the HTTP surface (CORS). Empty = any. */
30
+ corsOrigins?: readonly string[];
31
+ };
32
+ /**
33
+ * Value returned by `createLluiAgentServer`. `router` matches any
34
+ * `/agent/*` request and returns a Response (or null to fall through).
35
+ * `wsUpgrade` handles Node HTTP upgrade events for `/agent/ws`.
36
+ */
37
+ export type AgentServerHandle = {
38
+ router: (req: Request) => Promise<Response | null>;
39
+ wsUpgrade: (req: IncomingMessage, socket: Duplex, head: Buffer) => void;
40
+ };
41
+ //# sourceMappingURL=options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../src/server/options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAElD;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,+EAA+E;IAC/E,UAAU,EAAE,MAAM,GAAG,UAAU,CAAA;IAE/B,wDAAwD;IACxD,UAAU,CAAC,EAAE,UAAU,CAAA;IAEvB,8DAA8D;IAC9D,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;IAEnC,kDAAkD;IAClD,SAAS,CAAC,EAAE,SAAS,CAAA;IAErB,qEAAqE;IACrE,WAAW,CAAC,EAAE,WAAW,CAAA;IAEzB,uEAAuE;IACvE,WAAW,CAAC,EAAE,MAAM,CAAA;IAEpB,sEAAsE;IACtE,cAAc,CAAC,EAAE,MAAM,CAAA;IAEvB,yDAAyD;IACzD,YAAY,CAAC,EAAE,MAAM,CAAA;IAErB,gEAAgE;IAChE,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAChC,CAAA;AAED;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAA;IAClD,SAAS,EAAE,CAAC,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CACxE,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.js","sourceRoot":"","sources":["../../src/server/options.ts"],"names":[],"mappings":"","sourcesContent":["import type { IncomingMessage } from 'node:http'\nimport type { Duplex } from 'node:stream'\nimport type { TokenStore } from './token-store.js'\nimport type { IdentityResolver } from './identity.js'\nimport type { AuditSink } from './audit.js'\nimport type { RateLimiter } from './rate-limit.js'\n\n/**\n * Options accepted by `createLluiAgentServer`. All values except\n * `signingKey` are optional and fall back to in-memory defaults.\n * See spec §10.1.\n */\nexport type ServerOptions = {\n /** HMAC key for signing tokens. ≥32 bytes; rotation invalidates all tokens. */\n signingKey: string | Uint8Array\n\n /** Token store. Defaults to an `InMemoryTokenStore`. */\n tokenStore?: TokenStore\n\n /** Identity resolver. Defaults to anonymous (always null). */\n identityResolver?: IdentityResolver\n\n /** Audit sink. Defaults to `consoleAuditSink`. */\n auditSink?: AuditSink\n\n /** Rate limiter. Defaults to `defaultRateLimiter` with 30/minute. */\n rateLimiter?: RateLimiter\n\n /** Base path prefix for LAP endpoints. Defaults to `/agent/lap/v1`. */\n lapBasePath?: string\n\n /** Pairing grace window after a tab closes, in ms. Default 15 min. */\n pairingGraceMs?: number\n\n /** Sliding TTL for active tokens, in ms. Default 1 h. */\n slidingTtlMs?: number\n\n /** Allowed origins for the HTTP surface (CORS). Empty = any. */\n corsOrigins?: readonly string[]\n}\n\n/**\n * Value returned by `createLluiAgentServer`. `router` matches any\n * `/agent/*` request and returns a Response (or null to fall through).\n * `wsUpgrade` handles Node HTTP upgrade events for `/agent/ws`.\n */\nexport type AgentServerHandle = {\n router: (req: Request) => Promise<Response | null>\n wsUpgrade: (req: IncomingMessage, socket: Duplex, head: Buffer) => void\n}\n"]}
@@ -0,0 +1,14 @@
1
+ export type RateLimitResult = {
2
+ allowed: true;
3
+ } | {
4
+ allowed: false;
5
+ retryAfterMs: number;
6
+ };
7
+ export interface RateLimiter {
8
+ check(key: string, bucket: 'token' | 'identity'): Promise<RateLimitResult>;
9
+ }
10
+ export type RateLimitConfig = {
11
+ perBucket: string;
12
+ };
13
+ export declare function defaultRateLimiter(cfg: RateLimitConfig, now?: () => number): RateLimiter;
14
+ //# sourceMappingURL=rate-limit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/server/rate-limit.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAA;AAE1F,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,UAAU,GAAG,OAAO,CAAC,eAAe,CAAC,CAAA;CAC3E;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAiBD,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,eAAe,EACpB,GAAG,GAAE,MAAM,MAAyB,GACnC,WAAW,CA4Bb"}
@@ -0,0 +1,43 @@
1
+ const UNIT_MS = {
2
+ second: 1000,
3
+ minute: 60_000,
4
+ hour: 3_600_000,
5
+ };
6
+ function parseRate(spec) {
7
+ const m = spec.match(/^(\d+)\/(second|minute|hour)$/);
8
+ if (!m)
9
+ throw new Error(`invalid rate spec: ${spec}`);
10
+ const count = Number(m[1]);
11
+ const windowMs = UNIT_MS[m[2]];
12
+ if (!windowMs)
13
+ throw new Error(`invalid rate spec: ${spec}`);
14
+ return { count, windowMs };
15
+ }
16
+ export function defaultRateLimiter(cfg, now = () => Date.now()) {
17
+ const { count, windowMs } = parseRate(cfg.perBucket);
18
+ const refillPerMs = count / windowMs;
19
+ const state = new Map();
20
+ return {
21
+ async check(key, bucket) {
22
+ const k = `${bucket}:${key}`;
23
+ const nowMs = now();
24
+ let b = state.get(k);
25
+ if (!b) {
26
+ b = { tokens: count, lastCheck: nowMs };
27
+ state.set(k, b);
28
+ }
29
+ else {
30
+ const delta = nowMs - b.lastCheck;
31
+ b.tokens = Math.min(count, b.tokens + delta * refillPerMs);
32
+ b.lastCheck = nowMs;
33
+ }
34
+ if (b.tokens >= 1) {
35
+ b.tokens -= 1;
36
+ return { allowed: true };
37
+ }
38
+ const retryAfterMs = Math.ceil((1 - b.tokens) / refillPerMs);
39
+ return { allowed: false, retryAfterMs };
40
+ },
41
+ };
42
+ }
43
+ //# sourceMappingURL=rate-limit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../src/server/rate-limit.ts"],"names":[],"mappings":"AAUA,MAAM,OAAO,GAA2B;IACtC,MAAM,EAAE,IAAI;IACZ,MAAM,EAAE,MAAM;IACd,IAAI,EAAE,SAAS;CAChB,CAAA;AAED,SAAS,SAAS,CAAC,IAAY;IAC7B,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAA;IACrD,IAAI,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,IAAI,EAAE,CAAC,CAAA;IACrD,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAC1B,MAAM,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAyB,CAAC,CAAA;IACtD,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,IAAI,EAAE,CAAC,CAAA;IAC5D,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAA;AAC5B,CAAC;AAED,MAAM,UAAU,kBAAkB,CAChC,GAAoB,EACpB,MAAoB,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE;IAEpC,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;IACpD,MAAM,WAAW,GAAG,KAAK,GAAG,QAAQ,CAAA;IAGpC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAuB,CAAA;IAE5C,OAAO;QACL,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM;YACrB,MAAM,CAAC,GAAG,GAAG,MAAM,IAAI,GAAG,EAAE,CAAA;YAC5B,MAAM,KAAK,GAAG,GAAG,EAAE,CAAA;YACnB,IAAI,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;YACpB,IAAI,CAAC,CAAC,EAAE,CAAC;gBACP,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,CAAA;gBACvC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;YACjB,CAAC;iBAAM,CAAC;gBACN,MAAM,KAAK,GAAG,KAAK,GAAG,CAAC,CAAC,SAAS,CAAA;gBACjC,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,MAAM,GAAG,KAAK,GAAG,WAAW,CAAC,CAAA;gBAC1D,CAAC,CAAC,SAAS,GAAG,KAAK,CAAA;YACrB,CAAC;YACD,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;gBAClB,CAAC,CAAC,MAAM,IAAI,CAAC,CAAA;gBACb,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;YAC1B,CAAC;YACD,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,GAAG,WAAW,CAAC,CAAA;YAC5D,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,CAAA;QACzC,CAAC;KACF,CAAA;AACH,CAAC","sourcesContent":["export type RateLimitResult = { allowed: true } | { allowed: false; retryAfterMs: number }\n\nexport interface RateLimiter {\n check(key: string, bucket: 'token' | 'identity'): Promise<RateLimitResult>\n}\n\nexport type RateLimitConfig = {\n perBucket: string\n}\n\nconst UNIT_MS: Record<string, number> = {\n second: 1000,\n minute: 60_000,\n hour: 3_600_000,\n}\n\nfunction parseRate(spec: string): { count: number; windowMs: number } {\n const m = spec.match(/^(\\d+)\\/(second|minute|hour)$/)\n if (!m) throw new Error(`invalid rate spec: ${spec}`)\n const count = Number(m[1])\n const windowMs = UNIT_MS[m[2] as keyof typeof UNIT_MS]\n if (!windowMs) throw new Error(`invalid rate spec: ${spec}`)\n return { count, windowMs }\n}\n\nexport function defaultRateLimiter(\n cfg: RateLimitConfig,\n now: () => number = () => Date.now(),\n): RateLimiter {\n const { count, windowMs } = parseRate(cfg.perBucket)\n const refillPerMs = count / windowMs\n\n type BucketState = { tokens: number; lastCheck: number }\n const state = new Map<string, BucketState>()\n\n return {\n async check(key, bucket) {\n const k = `${bucket}:${key}`\n const nowMs = now()\n let b = state.get(k)\n if (!b) {\n b = { tokens: count, lastCheck: nowMs }\n state.set(k, b)\n } else {\n const delta = nowMs - b.lastCheck\n b.tokens = Math.min(count, b.tokens + delta * refillPerMs)\n b.lastCheck = nowMs\n }\n if (b.tokens >= 1) {\n b.tokens -= 1\n return { allowed: true }\n }\n const retryAfterMs = Math.ceil((1 - b.tokens) / refillPerMs)\n return { allowed: false, retryAfterMs }\n },\n }\n}\n"]}
@@ -0,0 +1,27 @@
1
+ import type { TokenRecord } from '../protocol.js';
2
+ /**
3
+ * Append-only, read-friendly storage for token records. See spec §10.3.
4
+ */
5
+ export interface TokenStore {
6
+ create(record: TokenRecord): Promise<void>;
7
+ findByTid(tid: string): Promise<TokenRecord | null>;
8
+ listByIdentity(uid: string): Promise<TokenRecord[]>;
9
+ touch(tid: string, now: number): Promise<void>;
10
+ markPendingResume(tid: string, until: number): Promise<void>;
11
+ /** Transition to awaiting-claude: browser WS is connected, waiting for Claude's first call. */
12
+ markAwaitingClaude(tid: string, now: number): Promise<void>;
13
+ markActive(tid: string, label: string, now: number): Promise<void>;
14
+ revoke(tid: string): Promise<void>;
15
+ }
16
+ export declare class InMemoryTokenStore implements TokenStore {
17
+ private byTid;
18
+ create(record: TokenRecord): Promise<void>;
19
+ findByTid(tid: string): Promise<TokenRecord | null>;
20
+ listByIdentity(uid: string): Promise<TokenRecord[]>;
21
+ touch(tid: string, now: number): Promise<void>;
22
+ markPendingResume(tid: string, until: number): Promise<void>;
23
+ markAwaitingClaude(tid: string, now: number): Promise<void>;
24
+ markActive(tid: string, label: string, now: number): Promise<void>;
25
+ revoke(tid: string): Promise<void>;
26
+ }
27
+ //# sourceMappingURL=token-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-store.d.ts","sourceRoot":"","sources":["../../src/server/token-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAEjD;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1C,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAA;IACnD,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAAA;IACnD,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9C,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5D,+FAA+F;IAC/F,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3D,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAClE,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACnC;AAED,qBAAa,kBAAmB,YAAW,UAAU;IACnD,OAAO,CAAC,KAAK,CAAiC;IAExC,MAAM,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1C,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAKnD,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAQnD,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAM9C,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAM5D,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAM3D,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYlE,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAKzC"}
@@ -0,0 +1,55 @@
1
+ export class InMemoryTokenStore {
2
+ byTid = new Map();
3
+ async create(record) {
4
+ this.byTid.set(record.tid, { ...record });
5
+ }
6
+ async findByTid(tid) {
7
+ const r = this.byTid.get(tid);
8
+ return r ? { ...r } : null;
9
+ }
10
+ async listByIdentity(uid) {
11
+ const out = [];
12
+ for (const r of this.byTid.values()) {
13
+ if (r.uid === uid)
14
+ out.push({ ...r });
15
+ }
16
+ return out;
17
+ }
18
+ async touch(tid, now) {
19
+ const r = this.byTid.get(tid);
20
+ if (!r)
21
+ return;
22
+ this.byTid.set(tid, { ...r, lastSeenAt: now });
23
+ }
24
+ async markPendingResume(tid, until) {
25
+ const r = this.byTid.get(tid);
26
+ if (!r)
27
+ return;
28
+ this.byTid.set(tid, { ...r, status: 'pending-resume', pendingResumeUntil: until });
29
+ }
30
+ async markAwaitingClaude(tid, now) {
31
+ const r = this.byTid.get(tid);
32
+ if (!r)
33
+ return;
34
+ this.byTid.set(tid, { ...r, status: 'awaiting-claude', lastSeenAt: now });
35
+ }
36
+ async markActive(tid, label, now) {
37
+ const r = this.byTid.get(tid);
38
+ if (!r)
39
+ return;
40
+ this.byTid.set(tid, {
41
+ ...r,
42
+ status: 'active',
43
+ label,
44
+ lastSeenAt: now,
45
+ pendingResumeUntil: null,
46
+ });
47
+ }
48
+ async revoke(tid) {
49
+ const r = this.byTid.get(tid);
50
+ if (!r)
51
+ return;
52
+ this.byTid.set(tid, { ...r, status: 'revoked', pendingResumeUntil: null });
53
+ }
54
+ }
55
+ //# sourceMappingURL=token-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-store.js","sourceRoot":"","sources":["../../src/server/token-store.ts"],"names":[],"mappings":"AAiBA,MAAM,OAAO,kBAAkB;IACrB,KAAK,GAAG,IAAI,GAAG,EAAuB,CAAA;IAE9C,KAAK,CAAC,MAAM,CAAC,MAAmB;QAC9B,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,EAAE,GAAG,MAAM,EAAE,CAAC,CAAA;IAC3C,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,GAAW;QACzB,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC7B,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;IAC5B,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,GAAW;QAC9B,MAAM,GAAG,GAAkB,EAAE,CAAA;QAC7B,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;YACpC,IAAI,CAAC,CAAC,GAAG,KAAK,GAAG;gBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAA;QACvC,CAAC;QACD,OAAO,GAAG,CAAA;IACZ,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,GAAW,EAAE,GAAW;QAClC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC7B,IAAI,CAAC,CAAC;YAAE,OAAM;QACd,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,GAAG,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAA;IAChD,CAAC;IAED,KAAK,CAAC,iBAAiB,CAAC,GAAW,EAAE,KAAa;QAChD,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC7B,IAAI,CAAC,CAAC;YAAE,OAAM;QACd,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,KAAK,EAAE,CAAC,CAAA;IACpF,CAAC;IAED,KAAK,CAAC,kBAAkB,CAAC,GAAW,EAAE,GAAW;QAC/C,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC7B,IAAI,CAAC,CAAC;YAAE,OAAM;QACd,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,iBAAiB,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAA;IAC3E,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,GAAW,EAAE,KAAa,EAAE,GAAW;QACtD,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC7B,IAAI,CAAC,CAAC;YAAE,OAAM;QACd,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE;YAClB,GAAG,CAAC;YACJ,MAAM,EAAE,QAAQ;YAChB,KAAK;YACL,UAAU,EAAE,GAAG;YACf,kBAAkB,EAAE,IAAI;SACzB,CAAC,CAAA;IACJ,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,GAAW;QACtB,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC7B,IAAI,CAAC,CAAC;YAAE,OAAM;QACd,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC,CAAA;IAC5E,CAAC;CACF","sourcesContent":["import type { TokenRecord } from '../protocol.js'\n\n/**\n * Append-only, read-friendly storage for token records. See spec §10.3.\n */\nexport interface TokenStore {\n create(record: TokenRecord): Promise<void>\n findByTid(tid: string): Promise<TokenRecord | null>\n listByIdentity(uid: string): Promise<TokenRecord[]>\n touch(tid: string, now: number): Promise<void>\n markPendingResume(tid: string, until: number): Promise<void>\n /** Transition to awaiting-claude: browser WS is connected, waiting for Claude's first call. */\n markAwaitingClaude(tid: string, now: number): Promise<void>\n markActive(tid: string, label: string, now: number): Promise<void>\n revoke(tid: string): Promise<void>\n}\n\nexport class InMemoryTokenStore implements TokenStore {\n private byTid = new Map<string, TokenRecord>()\n\n async create(record: TokenRecord): Promise<void> {\n this.byTid.set(record.tid, { ...record })\n }\n\n async findByTid(tid: string): Promise<TokenRecord | null> {\n const r = this.byTid.get(tid)\n return r ? { ...r } : null\n }\n\n async listByIdentity(uid: string): Promise<TokenRecord[]> {\n const out: TokenRecord[] = []\n for (const r of this.byTid.values()) {\n if (r.uid === uid) out.push({ ...r })\n }\n return out\n }\n\n async touch(tid: string, now: number): Promise<void> {\n const r = this.byTid.get(tid)\n if (!r) return\n this.byTid.set(tid, { ...r, lastSeenAt: now })\n }\n\n async markPendingResume(tid: string, until: number): Promise<void> {\n const r = this.byTid.get(tid)\n if (!r) return\n this.byTid.set(tid, { ...r, status: 'pending-resume', pendingResumeUntil: until })\n }\n\n async markAwaitingClaude(tid: string, now: number): Promise<void> {\n const r = this.byTid.get(tid)\n if (!r) return\n this.byTid.set(tid, { ...r, status: 'awaiting-claude', lastSeenAt: now })\n }\n\n async markActive(tid: string, label: string, now: number): Promise<void> {\n const r = this.byTid.get(tid)\n if (!r) return\n this.byTid.set(tid, {\n ...r,\n status: 'active',\n label,\n lastSeenAt: now,\n pendingResumeUntil: null,\n })\n }\n\n async revoke(tid: string): Promise<void> {\n const r = this.byTid.get(tid)\n if (!r) return\n this.byTid.set(tid, { ...r, status: 'revoked', pendingResumeUntil: null })\n }\n}\n"]}
@@ -0,0 +1,24 @@
1
+ import type { AgentToken } from '../protocol.js';
2
+ export type TokenPayload = {
3
+ tid: string;
4
+ iat: number;
5
+ exp: number;
6
+ scope: 'agent';
7
+ };
8
+ export type VerifyResult = {
9
+ kind: 'ok';
10
+ payload: TokenPayload;
11
+ } | {
12
+ kind: 'invalid';
13
+ reason: 'malformed' | 'bad-signature' | 'expired';
14
+ };
15
+ /**
16
+ * Serialize a payload to `llui-agent_<base64url(json)>.<base64url(hmac)>`.
17
+ * See spec §6.1.
18
+ */
19
+ export declare function signToken(payload: TokenPayload, key: string | Uint8Array): AgentToken;
20
+ /**
21
+ * Verify the signature, parse the payload, and check expiry.
22
+ */
23
+ export declare function verifyToken(token: string, key: string | Uint8Array, nowSec?: number): VerifyResult;
24
+ //# sourceMappingURL=token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../src/server/token.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAEhD,MAAM,MAAM,YAAY,GAAG;IACzB,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAED,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,WAAW,GAAG,eAAe,GAAG,SAAS,CAAA;CAAE,CAAA;AAsB1E;;;GAGG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,UAAU,CAOrF;AAED;;GAEG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,GAAG,UAAU,EACxB,MAAM,GAAE,MAAsC,GAC7C,YAAY,CA6Bd"}