@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.
- package/README.md +9 -7
- package/dist/client/agentConnect.d.ts +1 -1
- package/dist/client/agentConnect.d.ts.map +1 -1
- package/dist/client/agentConnect.js +12 -8
- package/dist/client/agentConnect.js.map +1 -1
- package/dist/client/rpc/list-actions.d.ts +24 -7
- package/dist/client/rpc/list-actions.d.ts.map +1 -1
- package/dist/client/rpc/list-actions.js +48 -30
- package/dist/client/rpc/list-actions.js.map +1 -1
- package/dist/protocol.d.ts +17 -6
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js.map +1 -1
- package/dist/server/cloudflare/durable-object.d.ts +11 -4
- package/dist/server/cloudflare/durable-object.d.ts.map +1 -1
- package/dist/server/cloudflare/durable-object.js.map +1 -1
- package/dist/server/cloudflare/index.d.ts +8 -4
- package/dist/server/cloudflare/index.d.ts.map +1 -1
- package/dist/server/cloudflare/index.js +8 -4
- package/dist/server/cloudflare/index.js.map +1 -1
- package/dist/server/cloudflare/worker.d.ts +10 -2
- package/dist/server/cloudflare/worker.d.ts.map +1 -1
- package/dist/server/cloudflare/worker.js +13 -6
- package/dist/server/cloudflare/worker.js.map +1 -1
- package/dist/server/core-entry.d.ts +2 -2
- package/dist/server/core-entry.d.ts.map +1 -1
- package/dist/server/core-entry.js +1 -1
- package/dist/server/core-entry.js.map +1 -1
- package/dist/server/core.d.ts +1 -3
- package/dist/server/core.d.ts.map +1 -1
- package/dist/server/core.js +13 -12
- package/dist/server/core.js.map +1 -1
- package/dist/server/factory.d.ts +1 -1
- package/dist/server/factory.d.ts.map +1 -1
- package/dist/server/factory.js +1 -2
- package/dist/server/factory.js.map +1 -1
- package/dist/server/http/mint.d.ts +6 -1
- package/dist/server/http/mint.d.ts.map +1 -1
- package/dist/server/http/mint.js +14 -6
- package/dist/server/http/mint.js.map +1 -1
- package/dist/server/http/resume.d.ts +3 -1
- package/dist/server/http/resume.d.ts.map +1 -1
- package/dist/server/http/resume.js +9 -7
- package/dist/server/http/resume.js.map +1 -1
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/lap/confirm-result.d.ts +0 -1
- package/dist/server/lap/confirm-result.d.ts.map +1 -1
- package/dist/server/lap/confirm-result.js +1 -1
- package/dist/server/lap/confirm-result.js.map +1 -1
- package/dist/server/lap/describe.d.ts +13 -2
- package/dist/server/lap/describe.d.ts.map +1 -1
- package/dist/server/lap/describe.js +23 -6
- package/dist/server/lap/describe.js.map +1 -1
- package/dist/server/lap/forward.d.ts +0 -1
- package/dist/server/lap/forward.d.ts.map +1 -1
- package/dist/server/lap/forward.js +2 -2
- package/dist/server/lap/forward.js.map +1 -1
- package/dist/server/lap/message.d.ts +0 -1
- package/dist/server/lap/message.d.ts.map +1 -1
- package/dist/server/lap/message.js +1 -1
- package/dist/server/lap/message.js.map +1 -1
- package/dist/server/lap/observe.d.ts +0 -1
- package/dist/server/lap/observe.d.ts.map +1 -1
- package/dist/server/lap/observe.js +1 -1
- package/dist/server/lap/observe.js.map +1 -1
- package/dist/server/lap/wait.d.ts +0 -1
- package/dist/server/lap/wait.d.ts.map +1 -1
- package/dist/server/lap/wait.js +1 -1
- package/dist/server/lap/wait.js.map +1 -1
- package/dist/server/options.d.ts +7 -5
- package/dist/server/options.d.ts.map +1 -1
- package/dist/server/options.js.map +1 -1
- package/dist/server/token-store.d.ts +22 -0
- package/dist/server/token-store.d.ts.map +1 -1
- package/dist/server/token-store.js +24 -0
- package/dist/server/token-store.js.map +1 -1
- package/dist/server/token.d.ts +32 -17
- package/dist/server/token.d.ts.map +1 -1
- package/dist/server/token.js +40 -103
- package/dist/server/token.js.map +1 -1
- package/dist/server/web/upgrade.d.ts +1 -1
- package/dist/server/web/upgrade.js +1 -1
- package/dist/server/web/upgrade.js.map +1 -1
- package/dist/server/ws/upgrade.d.ts +0 -1
- package/dist/server/ws/upgrade.d.ts.map +1 -1
- package/dist/server/ws/upgrade.js +12 -4
- package/dist/server/ws/upgrade.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"observe.d.ts","sourceRoot":"","sources":["../../../src/server/lap/observe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAUnD,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,EAAE,
|
|
1
|
+
{"version":3,"file":"observe.d.ts","sourceRoot":"","sources":["../../../src/server/lap/observe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAUnD,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,EAAE,eAAe,CAAA;IACzB,SAAS,EAAE,SAAS,CAAA;IACpB,WAAW,EAAE,WAAW,CAAA;IACxB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,QAAQ,CAAC,CA6D5F"}
|
|
@@ -13,7 +13,7 @@ import { verifyAndReadTid } from './describe.js';
|
|
|
13
13
|
* is one call instead of three.
|
|
14
14
|
*/
|
|
15
15
|
export async function handleLapObserve(req, deps) {
|
|
16
|
-
const auth = await verifyAndReadTid(req, deps.
|
|
16
|
+
const auth = await verifyAndReadTid(req, deps.tokenStore);
|
|
17
17
|
if (!auth.ok)
|
|
18
18
|
return json({ error: { code: auth.code } }, auth.status);
|
|
19
19
|
const rec = await deps.tokenStore.findByTid(auth.tid);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"observe.js","sourceRoot":"","sources":["../../../src/server/lap/observe.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"observe.js","sourceRoot":"","sources":["../../../src/server/lap/observe.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TokenStore } from '../token-store.js'\nimport type { PairingRegistry } from '../ws/pairing-registry.js'\nimport type { AuditSink } from '../audit.js'\nimport type { RateLimiter } from '../rate-limit.js'\nimport { verifyAndReadTid } from './describe.js'\nimport type {\n AgentContext,\n LapActionsResponse,\n LapDescribeResponse,\n LapObserveResponse,\n MessageSchemaEntry,\n} from '../../protocol.js'\n\nexport type LapObserveDeps = {\n tokenStore: TokenStore\n registry: PairingRegistry\n auditSink: AuditSink\n rateLimiter: RateLimiter\n now?: () => number\n}\n\n/**\n * Unified bootstrap endpoint. One call returns everything the LLM\n * needs to start acting on the app:\n * - state (dynamic, from browser)\n * - actions (dynamic, from browser)\n * - description (static, from cached hello frame)\n * - context (dynamic, from browser — agentContext(state))\n *\n * Replaces the get_state + list_actions + describe_app trio at the\n * MCP layer. Those LAP endpoints remain available for specialized\n * callers, but the common \"what can I see, what can I do\" question\n * is one call instead of three.\n */\nexport async function handleLapObserve(req: Request, deps: LapObserveDeps): Promise<Response> {\n const auth = await verifyAndReadTid(req, deps.tokenStore)\n if (!auth.ok) return json({ error: { code: auth.code } }, auth.status)\n\n const rec = await deps.tokenStore.findByTid(auth.tid)\n if (!rec || rec.status === 'revoked') return json({ error: { code: 'revoked' } }, 403)\n if (!deps.registry.isPaired(auth.tid)) return 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 let dynamic: {\n state: unknown\n actions: LapActionsResponse['actions']\n context: AgentContext | null\n }\n try {\n dynamic = (await deps.registry.rpc(auth.tid, 'observe', {})) as typeof dynamic\n } catch (e: unknown) {\n const err = e as { code?: string; detail?: string }\n const code = err.code ?? 'internal'\n const status = code === 'paused' ? 503 : code === 'timeout' ? 504 : 500\n return json({ error: { code, detail: err.detail } }, status)\n }\n\n const description: LapDescribeResponse = {\n name: hello.appName,\n version: hello.appVersion,\n stateSchema: hello.stateSchema,\n messages: hello.msgSchema as Record<string, MessageSchemaEntry>,\n docs: hello.docs,\n conventions: {\n dispatchModel: 'TEA',\n confirmationModel: 'runtime-mediated',\n readSurfaces: ['state', 'query_dom', 'describe_visible_content', 'describe_context'],\n },\n schemaHash: hello.schemaHash,\n }\n\n const out: LapObserveResponse = {\n state: dynamic.state,\n actions: dynamic.actions,\n description,\n context: dynamic.context,\n }\n\n const nowMs = (deps.now ?? (() => Date.now()))()\n await deps.tokenStore.touch(auth.tid, nowMs)\n await deps.auditSink.write({\n at: nowMs,\n tid: auth.tid,\n uid: rec.uid,\n event: 'lap-call',\n detail: { tool: 'observe' },\n })\n return json(out, 200)\n}\n\nfunction json(b: unknown, s: number): Response {\n return new Response(JSON.stringify(b), {\n status: s,\n headers: { 'content-type': 'application/json' },\n })\n}\n"]}
|
|
@@ -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 LapWaitDeps = {
|
|
6
|
-
signingKey: string | Uint8Array;
|
|
7
6
|
tokenStore: TokenStore;
|
|
8
7
|
registry: PairingRegistry;
|
|
9
8
|
auditSink: AuditSink;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wait.d.ts","sourceRoot":"","sources":["../../../src/server/lap/wait.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAInD,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,EAAE,
|
|
1
|
+
{"version":3,"file":"wait.d.ts","sourceRoot":"","sources":["../../../src/server/lap/wait.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAInD,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,EAAE,eAAe,CAAA;IACzB,SAAS,EAAE,SAAS,CAAA;IACpB,WAAW,EAAE,WAAW,CAAA;IACxB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB,CAAA;AAED,wBAAsB,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CA2BtF"}
|
package/dist/server/lap/wait.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { verifyAndReadTid } from './describe.js';
|
|
2
2
|
export async function handleLapWait(req, deps) {
|
|
3
|
-
const auth = await verifyAndReadTid(req, deps.
|
|
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":"wait.js","sourceRoot":"","sources":["../../../src/server/lap/wait.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"wait.js","sourceRoot":"","sources":["../../../src/server/lap/wait.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAWhD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAY,EAAE,IAAiB;IACjE,MAAM,IAAI,GAAG,MAAM,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,CAAA;IACzD,IAAI,CAAC,IAAI,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;IAEtE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACrD,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,GAAG,CAAC,CAAA;IACtF,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,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 { 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 { LapWaitRequest, LapWaitResponse } from '../../protocol.js'\n\nexport type LapWaitDeps = {\n tokenStore: TokenStore\n registry: PairingRegistry\n auditSink: AuditSink\n rateLimiter: RateLimiter\n now?: () => number\n}\n\nexport async function handleLapWait(req: Request, deps: LapWaitDeps): Promise<Response> {\n const auth = await verifyAndReadTid(req, deps.tokenStore)\n if (!auth.ok) return json({ error: { code: auth.code } }, auth.status)\n\n const rec = await deps.tokenStore.findByTid(auth.tid)\n if (!rec || rec.status === 'revoked') return json({ error: { code: 'revoked' } }, 403)\n if (!deps.registry.isPaired(auth.tid)) return 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"]}
|
package/dist/server/options.d.ts
CHANGED
|
@@ -8,13 +8,15 @@ import type { PairingRegistry } from './ws/pairing-registry.js';
|
|
|
8
8
|
import type { AcceptResult } from './core.js';
|
|
9
9
|
import type { PairingConnection } from './ws/pairing-registry.js';
|
|
10
10
|
/**
|
|
11
|
-
* Options accepted by `createLluiAgentServer`. All values
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* Options accepted by `createLluiAgentServer`. All values are
|
|
12
|
+
* optional and fall back to in-memory defaults. See spec §10.1.
|
|
13
|
+
*
|
|
14
|
+
* Pre-0.0.35 this required a `signingKey` for HMAC-signed JWT tokens.
|
|
15
|
+
* The new opaque-token scheme (token.ts) doesn't sign anything — the
|
|
16
|
+
* server stores the SHA-256 hash and looks tokens up. The option is
|
|
17
|
+
* gone; existing config that passed `signingKey` should drop it.
|
|
14
18
|
*/
|
|
15
19
|
export type ServerOptions = {
|
|
16
|
-
/** HMAC key for signing tokens. ≥32 bytes; rotation invalidates all tokens. */
|
|
17
|
-
signingKey: string | Uint8Array;
|
|
18
20
|
/** Token store. Defaults to an `InMemoryTokenStore`. */
|
|
19
21
|
tokenStore?: TokenStore;
|
|
20
22
|
/** Identity resolver. Defaults to anonymous (always null). */
|
|
@@ -1 +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;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAC7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAEjE
|
|
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;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAC7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAEjE;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,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;;;;;;OAMG;IACH,SAAS,EAAE,CAAC,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAChF,oEAAoE;IACpE,QAAQ,EAAE,eAAe,CAAA;IACzB,8BAA8B;IAC9B,UAAU,EAAE,UAAU,CAAA;IACtB,6BAA6B;IAC7B,SAAS,EAAE,SAAS,CAAA;IACpB;;;;;;OAMG;IACH,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,KAAK,OAAO,CAAC,YAAY,CAAC,CAAA;CACpF,CAAA"}
|
|
@@ -1 +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'\nimport type { PairingRegistry } from './ws/pairing-registry.js'\nimport type { AcceptResult } from './core.js'\nimport type { PairingConnection } from './ws/pairing-registry.js'\n\n/**\n * Options accepted by `createLluiAgentServer`. All values
|
|
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'\nimport type { PairingRegistry } from './ws/pairing-registry.js'\nimport type { AcceptResult } from './core.js'\nimport type { PairingConnection } from './ws/pairing-registry.js'\n\n/**\n * Options accepted by `createLluiAgentServer`. All values are\n * optional and fall back to in-memory defaults. See spec §10.1.\n *\n * Pre-0.0.35 this required a `signingKey` for HMAC-signed JWT tokens.\n * The new opaque-token scheme (token.ts) doesn't sign anything — the\n * server stores the SHA-256 hash and looks tokens up. The option is\n * gone; existing config that passed `signingKey` should drop it.\n */\nexport type ServerOptions = {\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 /**\n * Handles Node HTTP upgrade events for `/agent/ws`. Returns a Promise\n * because token verification uses WebCrypto (async). Node's\n * `server.on('upgrade', handler)` fires the handler without awaiting,\n * which is fine — the handler writes errors directly to the socket\n * and never throws back to the caller.\n */\n wsUpgrade: (req: IncomingMessage, socket: Duplex, head: Buffer) => Promise<void>\n /** The pairing registry. Runtime-neutral adapters may access it. */\n registry: PairingRegistry\n /** The active token store. */\n tokenStore: TokenStore\n /** The active audit sink. */\n auditSink: AuditSink\n /**\n * Runtime-neutral WebSocket acceptance primitive. Validates a token\n * and registers a `PairingConnection` with the registry. The Node\n * `wsUpgrade` above calls this internally; web-runtime adapters\n * (`@llui/agent/server/web`) use it after accepting a WebSocket via\n * their native API.\n */\n acceptConnection: (token: string, conn: PairingConnection) => Promise<AcceptResult>\n}\n"]}
|
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
import type { TokenRecord } from '../protocol.js';
|
|
2
2
|
/**
|
|
3
3
|
* Append-only, read-friendly storage for token records. See spec §10.3.
|
|
4
|
+
*
|
|
5
|
+
* Tokens are looked up by `tokenHash` (SHA-256 of the presented bearer
|
|
6
|
+
* value) on every authenticated request. The `tid` index is kept for
|
|
7
|
+
* the resume / revoke / sessions surfaces — those operate on session
|
|
8
|
+
* IDs the user can see and copy.
|
|
4
9
|
*/
|
|
5
10
|
export interface TokenStore {
|
|
6
11
|
create(record: TokenRecord): Promise<void>;
|
|
7
12
|
findByTid(tid: string): Promise<TokenRecord | null>;
|
|
13
|
+
/**
|
|
14
|
+
* Look up a record by the SHA-256 hash of its bearer token. Returns
|
|
15
|
+
* `null` when the hash isn't in the store (the typical "this token
|
|
16
|
+
* isn't ours / has been revoked / never existed" case).
|
|
17
|
+
*/
|
|
18
|
+
findByTokenHash(tokenHash: string): Promise<TokenRecord | null>;
|
|
8
19
|
listByIdentity(uid: string): Promise<TokenRecord[]>;
|
|
9
20
|
touch(tid: string, now: number): Promise<void>;
|
|
10
21
|
markPendingResume(tid: string, until: number): Promise<void>;
|
|
@@ -12,16 +23,27 @@ export interface TokenStore {
|
|
|
12
23
|
markAwaitingClaude(tid: string, now: number): Promise<void>;
|
|
13
24
|
markActive(tid: string, label: string, now: number): Promise<void>;
|
|
14
25
|
revoke(tid: string): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Replace the bearer token's hash and bump expiry. Used by the
|
|
28
|
+
* resume-claim flow: the old token is invalidated (its hash is no
|
|
29
|
+
* longer indexed) and a freshly-minted opaque token takes its
|
|
30
|
+
* place. The `tid` stays stable so existing audit / pairing state
|
|
31
|
+
* carries over.
|
|
32
|
+
*/
|
|
33
|
+
rotateTokenHash(tid: string, newTokenHash: string, expiresAt: number): Promise<void>;
|
|
15
34
|
}
|
|
16
35
|
export declare class InMemoryTokenStore implements TokenStore {
|
|
17
36
|
private byTid;
|
|
37
|
+
private tidByTokenHash;
|
|
18
38
|
create(record: TokenRecord): Promise<void>;
|
|
19
39
|
findByTid(tid: string): Promise<TokenRecord | null>;
|
|
40
|
+
findByTokenHash(tokenHash: string): Promise<TokenRecord | null>;
|
|
20
41
|
listByIdentity(uid: string): Promise<TokenRecord[]>;
|
|
21
42
|
touch(tid: string, now: number): Promise<void>;
|
|
22
43
|
markPendingResume(tid: string, until: number): Promise<void>;
|
|
23
44
|
markAwaitingClaude(tid: string, now: number): Promise<void>;
|
|
24
45
|
markActive(tid: string, label: string, now: number): Promise<void>;
|
|
25
46
|
revoke(tid: string): Promise<void>;
|
|
47
|
+
rotateTokenHash(tid: string, newTokenHash: string, expiresAt: number): Promise<void>;
|
|
26
48
|
}
|
|
27
49
|
//# sourceMappingURL=token-store.d.ts.map
|
|
@@ -1 +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
|
|
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;;;;;;;GAOG;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;;;;OAIG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAA;IAC/D,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;IAClC;;;;;;OAMG;IACH,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACrF;AAED,qBAAa,kBAAmB,YAAW,UAAU;IACnD,OAAO,CAAC,KAAK,CAAiC;IAI9C,OAAO,CAAC,cAAc,CAA4B;IAE5C,MAAM,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAK1C,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAKnD,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAO/D,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;IAUlC,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAO3F"}
|
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
export class InMemoryTokenStore {
|
|
2
2
|
byTid = new Map();
|
|
3
|
+
// Secondary index for the auth hot path. Kept in sync with `byTid`
|
|
4
|
+
// on `create`. Persistent stores would index this column at schema
|
|
5
|
+
// time; the in-memory map is the same idea minus the DB.
|
|
6
|
+
tidByTokenHash = new Map();
|
|
3
7
|
async create(record) {
|
|
4
8
|
this.byTid.set(record.tid, { ...record });
|
|
9
|
+
this.tidByTokenHash.set(record.tokenHash, record.tid);
|
|
5
10
|
}
|
|
6
11
|
async findByTid(tid) {
|
|
7
12
|
const r = this.byTid.get(tid);
|
|
8
13
|
return r ? { ...r } : null;
|
|
9
14
|
}
|
|
15
|
+
async findByTokenHash(tokenHash) {
|
|
16
|
+
const tid = this.tidByTokenHash.get(tokenHash);
|
|
17
|
+
if (!tid)
|
|
18
|
+
return null;
|
|
19
|
+
const r = this.byTid.get(tid);
|
|
20
|
+
return r ? { ...r } : null;
|
|
21
|
+
}
|
|
10
22
|
async listByIdentity(uid) {
|
|
11
23
|
const out = [];
|
|
12
24
|
for (const r of this.byTid.values()) {
|
|
@@ -50,6 +62,18 @@ export class InMemoryTokenStore {
|
|
|
50
62
|
if (!r)
|
|
51
63
|
return;
|
|
52
64
|
this.byTid.set(tid, { ...r, status: 'revoked', pendingResumeUntil: null });
|
|
65
|
+
// Drop the hash index entry so revoked tokens fail at the auth
|
|
66
|
+
// boundary even if the bearer leaks. The byTid record stays for
|
|
67
|
+
// audit / replay purposes.
|
|
68
|
+
this.tidByTokenHash.delete(r.tokenHash);
|
|
69
|
+
}
|
|
70
|
+
async rotateTokenHash(tid, newTokenHash, expiresAt) {
|
|
71
|
+
const r = this.byTid.get(tid);
|
|
72
|
+
if (!r)
|
|
73
|
+
return;
|
|
74
|
+
this.tidByTokenHash.delete(r.tokenHash);
|
|
75
|
+
this.byTid.set(tid, { ...r, tokenHash: newTokenHash, expiresAt });
|
|
76
|
+
this.tidByTokenHash.set(newTokenHash, tid);
|
|
53
77
|
}
|
|
54
78
|
}
|
|
55
79
|
//# sourceMappingURL=token-store.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token-store.js","sourceRoot":"","sources":["../../src/server/token-store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"token-store.js","sourceRoot":"","sources":["../../src/server/token-store.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TokenRecord } from '../protocol.js'\n\n/**\n * Append-only, read-friendly storage for token records. See spec §10.3.\n *\n * Tokens are looked up by `tokenHash` (SHA-256 of the presented bearer\n * value) on every authenticated request. The `tid` index is kept for\n * the resume / revoke / sessions surfaces — those operate on session\n * IDs the user can see and copy.\n */\nexport interface TokenStore {\n create(record: TokenRecord): Promise<void>\n findByTid(tid: string): Promise<TokenRecord | null>\n /**\n * Look up a record by the SHA-256 hash of its bearer token. Returns\n * `null` when the hash isn't in the store (the typical \"this token\n * isn't ours / has been revoked / never existed\" case).\n */\n findByTokenHash(tokenHash: 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 * Replace the bearer token's hash and bump expiry. Used by the\n * resume-claim flow: the old token is invalidated (its hash is no\n * longer indexed) and a freshly-minted opaque token takes its\n * place. The `tid` stays stable so existing audit / pairing state\n * carries over.\n */\n rotateTokenHash(tid: string, newTokenHash: string, expiresAt: number): Promise<void>\n}\n\nexport class InMemoryTokenStore implements TokenStore {\n private byTid = new Map<string, TokenRecord>()\n // Secondary index for the auth hot path. Kept in sync with `byTid`\n // on `create`. Persistent stores would index this column at schema\n // time; the in-memory map is the same idea minus the DB.\n private tidByTokenHash = new Map<string, string>()\n\n async create(record: TokenRecord): Promise<void> {\n this.byTid.set(record.tid, { ...record })\n this.tidByTokenHash.set(record.tokenHash, record.tid)\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 findByTokenHash(tokenHash: string): Promise<TokenRecord | null> {\n const tid = this.tidByTokenHash.get(tokenHash)\n if (!tid) return 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 // Drop the hash index entry so revoked tokens fail at the auth\n // boundary even if the bearer leaks. The byTid record stays for\n // audit / replay purposes.\n this.tidByTokenHash.delete(r.tokenHash)\n }\n\n async rotateTokenHash(tid: string, newTokenHash: string, expiresAt: number): Promise<void> {\n const r = this.byTid.get(tid)\n if (!r) return\n this.tidByTokenHash.delete(r.tokenHash)\n this.byTid.set(tid, { ...r, tokenHash: newTokenHash, expiresAt })\n this.tidByTokenHash.set(newTokenHash, tid)\n }\n}\n"]}
|
package/dist/server/token.d.ts
CHANGED
|
@@ -1,28 +1,43 @@
|
|
|
1
1
|
import type { AgentToken } from '../protocol.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Result of looking up a presented token. The `expired` reason is
|
|
4
|
+
* returned by the verify path when the token's record exists but its
|
|
5
|
+
* hard-expiry has passed; `unknown` covers both "no record" and
|
|
6
|
+
* "wrong hash" so a probe-by-hash leak surface is uniform.
|
|
7
|
+
*/
|
|
8
8
|
export type VerifyResult = {
|
|
9
9
|
kind: 'ok';
|
|
10
|
-
|
|
10
|
+
tid: string;
|
|
11
11
|
} | {
|
|
12
12
|
kind: 'invalid';
|
|
13
|
-
reason: 'malformed' | '
|
|
13
|
+
reason: 'malformed' | 'unknown' | 'expired';
|
|
14
14
|
};
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
16
|
+
* Mint an opaque random bearer token + the SHA-256 hash the server
|
|
17
|
+
* stores as a lookup key. Tokens are 32 bytes of CSPRNG entropy (256
|
|
18
|
+
* bits) base64url-encoded with the `llui-agent_` prefix — total
|
|
19
|
+
* 54–55 chars, vs the previous JWT format's ~250.
|
|
20
|
+
*
|
|
21
|
+
* The token itself never persists; only the hash does. A leaked store
|
|
22
|
+
* therefore does not compromise live tokens, since the bearer secret
|
|
23
|
+
* isn't recoverable from the hash. This matches the standard "session
|
|
24
|
+
* cookie / API key" pattern.
|
|
25
|
+
*
|
|
26
|
+
* The opaque form is the only token format the server understands as
|
|
27
|
+
* of 0.0.35. The previous HMAC-signed JWT format is gone; clients
|
|
28
|
+
* carrying old tokens will fail with `unknown` on first call and need
|
|
29
|
+
* to remint. See CHANGELOG.
|
|
20
30
|
*/
|
|
21
|
-
export declare function
|
|
31
|
+
export declare function mintToken(): Promise<{
|
|
32
|
+
token: AgentToken;
|
|
33
|
+
tokenHash: string;
|
|
34
|
+
}>;
|
|
22
35
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
36
|
+
* Compute the SHA-256 hash of a presented bearer token. Returns `null`
|
|
37
|
+
* when the prefix is missing — the verify path uses that to fail-fast
|
|
38
|
+
* on garbage-shaped Authorization headers without a crypto round-trip.
|
|
39
|
+
* Hash is hex-encoded for portability across stores (Postgres `text`,
|
|
40
|
+
* KV string, etc.).
|
|
26
41
|
*/
|
|
27
|
-
export declare function
|
|
42
|
+
export declare function tokenHashOf(token: string): Promise<string | null>;
|
|
28
43
|
//# sourceMappingURL=token.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../src/server/token.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../src/server/token.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAKhD;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC3B;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,SAAS,CAAA;CAAE,CAAA;AAEpE;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,SAAS,IAAI,OAAO,CAAC;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAMnF;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGvE"}
|
package/dist/server/token.js
CHANGED
|
@@ -1,117 +1,54 @@
|
|
|
1
1
|
const PREFIX = 'llui-agent_';
|
|
2
|
+
const TOKEN_BYTES = 32;
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Mint an opaque random bearer token + the SHA-256 hash the server
|
|
5
|
+
* stores as a lookup key. Tokens are 32 bytes of CSPRNG entropy (256
|
|
6
|
+
* bits) base64url-encoded with the `llui-agent_` prefix — total
|
|
7
|
+
* 54–55 chars, vs the previous JWT format's ~250.
|
|
8
|
+
*
|
|
9
|
+
* The token itself never persists; only the hash does. A leaked store
|
|
10
|
+
* therefore does not compromise live tokens, since the bearer secret
|
|
11
|
+
* isn't recoverable from the hash. This matches the standard "session
|
|
12
|
+
* cookie / API key" pattern.
|
|
13
|
+
*
|
|
14
|
+
* The opaque form is the only token format the server understands as
|
|
15
|
+
* of 0.0.35. The previous HMAC-signed JWT format is gone; clients
|
|
16
|
+
* carrying old tokens will fail with `unknown` on first call and need
|
|
17
|
+
* to remint. See CHANGELOG.
|
|
9
18
|
*/
|
|
10
|
-
function
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
return
|
|
16
|
-
}
|
|
17
|
-
function toKeyBytes(key) {
|
|
18
|
-
if (typeof key === 'string') {
|
|
19
|
-
if (key.length < 32)
|
|
20
|
-
throw new Error('signingKey must be at least 32 bytes');
|
|
21
|
-
}
|
|
22
|
-
else if (key.byteLength < 32) {
|
|
23
|
-
throw new Error('signingKey must be at least 32 bytes');
|
|
24
|
-
}
|
|
25
|
-
return toBytes(key);
|
|
19
|
+
export async function mintToken() {
|
|
20
|
+
const bytes = new Uint8Array(TOKEN_BYTES);
|
|
21
|
+
crypto.getRandomValues(bytes);
|
|
22
|
+
const token = (PREFIX + toBase64Url(bytes));
|
|
23
|
+
const tokenHash = await sha256Hex(token);
|
|
24
|
+
return { token, tokenHash };
|
|
26
25
|
}
|
|
27
26
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
27
|
+
* Compute the SHA-256 hash of a presented bearer token. Returns `null`
|
|
28
|
+
* when the prefix is missing — the verify path uses that to fail-fast
|
|
29
|
+
* on garbage-shaped Authorization headers without a crypto round-trip.
|
|
30
|
+
* Hash is hex-encoded for portability across stores (Postgres `text`,
|
|
31
|
+
* KV string, etc.).
|
|
32
32
|
*/
|
|
33
|
-
async function
|
|
34
|
-
|
|
33
|
+
export async function tokenHashOf(token) {
|
|
34
|
+
if (!token.startsWith(PREFIX))
|
|
35
|
+
return null;
|
|
36
|
+
return sha256Hex(token);
|
|
37
|
+
}
|
|
38
|
+
async function sha256Hex(s) {
|
|
39
|
+
const bytes = new TextEncoder().encode(s);
|
|
40
|
+
const buf = await crypto.subtle.digest('SHA-256', bytes);
|
|
41
|
+
const arr = new Uint8Array(buf);
|
|
42
|
+
let out = '';
|
|
43
|
+
for (let i = 0; i < arr.length; i++) {
|
|
44
|
+
out += arr[i].toString(16).padStart(2, '0');
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
35
47
|
}
|
|
36
48
|
function toBase64Url(bytes) {
|
|
37
|
-
// btoa needs a binary string; build it manually to avoid ArrayBuffer/Uint8Array quirks.
|
|
38
49
|
let bin = '';
|
|
39
50
|
for (let i = 0; i < bytes.byteLength; i++)
|
|
40
51
|
bin += String.fromCharCode(bytes[i]);
|
|
41
52
|
return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
42
53
|
}
|
|
43
|
-
function fromBase64Url(s) {
|
|
44
|
-
try {
|
|
45
|
-
const b64 = s.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - (s.length % 4)) % 4);
|
|
46
|
-
const bin = atob(b64);
|
|
47
|
-
const buf = new ArrayBuffer(bin.length);
|
|
48
|
-
const bytes = new Uint8Array(buf);
|
|
49
|
-
for (let i = 0; i < bin.length; i++)
|
|
50
|
-
bytes[i] = bin.charCodeAt(i);
|
|
51
|
-
return bytes;
|
|
52
|
-
}
|
|
53
|
-
catch {
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Serialize a payload to `llui-agent_<base64url(json)>.<base64url(hmac)>`.
|
|
59
|
-
* See spec §6.1. Async because WebCrypto's HMAC sign/verify is the
|
|
60
|
-
* cross-runtime standard; Node, Cloudflare, Deno, and Bun all expose
|
|
61
|
-
* `crypto.subtle` identically.
|
|
62
|
-
*/
|
|
63
|
-
export async function signToken(payload, key) {
|
|
64
|
-
const cryptoKey = await importHmacKey(key, ['sign']);
|
|
65
|
-
const jsonBytes = toBytes(JSON.stringify(payload));
|
|
66
|
-
const payloadPart = toBase64Url(jsonBytes);
|
|
67
|
-
const macBuf = await crypto.subtle.sign('HMAC', cryptoKey, toBytes(payloadPart));
|
|
68
|
-
const sigPart = toBase64Url(new Uint8Array(macBuf));
|
|
69
|
-
return (PREFIX + payloadPart + '.' + sigPart);
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Verify the signature, parse the payload, and check expiry.
|
|
73
|
-
* `crypto.subtle.verify` does the constant-time compare internally,
|
|
74
|
-
* so we don't need a separate `timingSafeEqual`.
|
|
75
|
-
*/
|
|
76
|
-
export async function verifyToken(token, key, nowSec = Math.floor(Date.now() / 1000)) {
|
|
77
|
-
if (!token.startsWith(PREFIX))
|
|
78
|
-
return { kind: 'invalid', reason: 'malformed' };
|
|
79
|
-
const body = token.slice(PREFIX.length);
|
|
80
|
-
const dot = body.indexOf('.');
|
|
81
|
-
if (dot < 0)
|
|
82
|
-
return { kind: 'invalid', reason: 'malformed' };
|
|
83
|
-
const payloadPart = body.slice(0, dot);
|
|
84
|
-
const sigPart = body.slice(dot + 1);
|
|
85
|
-
const sigBytes = fromBase64Url(sigPart);
|
|
86
|
-
if (!sigBytes)
|
|
87
|
-
return { kind: 'invalid', reason: 'malformed' };
|
|
88
|
-
const cryptoKey = await importHmacKey(key, ['verify']);
|
|
89
|
-
const ok = await crypto.subtle.verify('HMAC', cryptoKey, sigBytes, toBytes(payloadPart));
|
|
90
|
-
if (!ok)
|
|
91
|
-
return { kind: 'invalid', reason: 'bad-signature' };
|
|
92
|
-
const jsonBytes = fromBase64Url(payloadPart);
|
|
93
|
-
if (!jsonBytes)
|
|
94
|
-
return { kind: 'invalid', reason: 'malformed' };
|
|
95
|
-
let parsed;
|
|
96
|
-
try {
|
|
97
|
-
parsed = JSON.parse(new TextDecoder().decode(jsonBytes));
|
|
98
|
-
}
|
|
99
|
-
catch {
|
|
100
|
-
return { kind: 'invalid', reason: 'malformed' };
|
|
101
|
-
}
|
|
102
|
-
if (!isTokenPayload(parsed))
|
|
103
|
-
return { kind: 'invalid', reason: 'malformed' };
|
|
104
|
-
if (parsed.exp <= nowSec)
|
|
105
|
-
return { kind: 'invalid', reason: 'expired' };
|
|
106
|
-
return { kind: 'ok', payload: parsed };
|
|
107
|
-
}
|
|
108
|
-
function isTokenPayload(x) {
|
|
109
|
-
if (!x || typeof x !== 'object')
|
|
110
|
-
return false;
|
|
111
|
-
const o = x;
|
|
112
|
-
return (typeof o.tid === 'string' &&
|
|
113
|
-
typeof o.iat === 'number' &&
|
|
114
|
-
typeof o.exp === 'number' &&
|
|
115
|
-
o.scope === 'agent');
|
|
116
|
-
}
|
|
117
54
|
//# sourceMappingURL=token.js.map
|
package/dist/server/token.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/server/token.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/server/token.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,GAAG,aAAa,CAAA;AAC5B,MAAM,WAAW,GAAG,EAAE,CAAA;AAYtB;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS;IAC7B,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,WAAW,CAAC,CAAA;IACzC,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,CAAA;IAC7B,MAAM,KAAK,GAAG,CAAC,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,CAAe,CAAA;IACzD,MAAM,SAAS,GAAG,MAAM,SAAS,CAAC,KAAK,CAAC,CAAA;IACxC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAA;AAC7B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,KAAa;IAC7C,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAA;IAC1C,OAAO,SAAS,CAAC,KAAK,CAAC,CAAA;AACzB,CAAC;AAED,KAAK,UAAU,SAAS,CAAC,CAAS;IAChC,MAAM,KAAK,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;IACzC,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;IACxD,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,CAAA;IAC/B,IAAI,GAAG,GAAG,EAAE,CAAA;IACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;IAC9C,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,SAAS,WAAW,CAAC,KAAiB;IACpC,IAAI,GAAG,GAAG,EAAE,CAAA;IACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,UAAU,EAAE,CAAC,EAAE;QAAE,GAAG,IAAI,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAA;IAChF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;AAC7E,CAAC","sourcesContent":["import type { AgentToken } from '../protocol.js'\n\nconst PREFIX = 'llui-agent_'\nconst TOKEN_BYTES = 32\n\n/**\n * Result of looking up a presented token. The `expired` reason is\n * returned by the verify path when the token's record exists but its\n * hard-expiry has passed; `unknown` covers both \"no record\" and\n * \"wrong hash\" so a probe-by-hash leak surface is uniform.\n */\nexport type VerifyResult =\n | { kind: 'ok'; tid: string }\n | { kind: 'invalid'; reason: 'malformed' | 'unknown' | 'expired' }\n\n/**\n * Mint an opaque random bearer token + the SHA-256 hash the server\n * stores as a lookup key. Tokens are 32 bytes of CSPRNG entropy (256\n * bits) base64url-encoded with the `llui-agent_` prefix — total\n * 54–55 chars, vs the previous JWT format's ~250.\n *\n * The token itself never persists; only the hash does. A leaked store\n * therefore does not compromise live tokens, since the bearer secret\n * isn't recoverable from the hash. This matches the standard \"session\n * cookie / API key\" pattern.\n *\n * The opaque form is the only token format the server understands as\n * of 0.0.35. The previous HMAC-signed JWT format is gone; clients\n * carrying old tokens will fail with `unknown` on first call and need\n * to remint. See CHANGELOG.\n */\nexport async function mintToken(): Promise<{ token: AgentToken; tokenHash: string }> {\n const bytes = new Uint8Array(TOKEN_BYTES)\n crypto.getRandomValues(bytes)\n const token = (PREFIX + toBase64Url(bytes)) as AgentToken\n const tokenHash = await sha256Hex(token)\n return { token, tokenHash }\n}\n\n/**\n * Compute the SHA-256 hash of a presented bearer token. Returns `null`\n * when the prefix is missing — the verify path uses that to fail-fast\n * on garbage-shaped Authorization headers without a crypto round-trip.\n * Hash is hex-encoded for portability across stores (Postgres `text`,\n * KV string, etc.).\n */\nexport async function tokenHashOf(token: string): Promise<string | null> {\n if (!token.startsWith(PREFIX)) return null\n return sha256Hex(token)\n}\n\nasync function sha256Hex(s: string): Promise<string> {\n const bytes = new TextEncoder().encode(s)\n const buf = await crypto.subtle.digest('SHA-256', bytes)\n const arr = new Uint8Array(buf)\n let out = ''\n for (let i = 0; i < arr.length; i++) {\n out += arr[i]!.toString(16).padStart(2, '0')\n }\n return out\n}\n\nfunction toBase64Url(bytes: Uint8Array): string {\n let bin = ''\n for (let i = 0; i < bytes.byteLength; i++) bin += String.fromCharCode(bytes[i]!)\n return btoa(bin).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n"]}
|
|
@@ -13,7 +13,7 @@ export declare function extractToken(req: Request): string | null;
|
|
|
13
13
|
*
|
|
14
14
|
* Usage:
|
|
15
15
|
* ```ts
|
|
16
|
-
* const agent = createLluiAgentCore(
|
|
16
|
+
* const agent = createLluiAgentCore()
|
|
17
17
|
* export default {
|
|
18
18
|
* async fetch(req, env) {
|
|
19
19
|
* const url = new URL(req.url)
|