@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.
- package/LICENSE +21 -0
- package/README.md +210 -0
- package/dist/client/agentConfirm.d.ts +60 -0
- package/dist/client/agentConfirm.d.ts.map +1 -0
- package/dist/client/agentConfirm.js +66 -0
- package/dist/client/agentConfirm.js.map +1 -0
- package/dist/client/agentConnect.d.ts +125 -0
- package/dist/client/agentConnect.d.ts.map +1 -0
- package/dist/client/agentConnect.js +114 -0
- package/dist/client/agentConnect.js.map +1 -0
- package/dist/client/agentLog.d.ts +51 -0
- package/dist/client/agentLog.d.ts.map +1 -0
- package/dist/client/agentLog.js +53 -0
- package/dist/client/agentLog.js.map +1 -0
- package/dist/client/effect-handler.d.ts +15 -0
- package/dist/client/effect-handler.d.ts.map +1 -0
- package/dist/client/effect-handler.js +146 -0
- package/dist/client/effect-handler.js.map +1 -0
- package/dist/client/effects.d.ts +27 -0
- package/dist/client/effects.d.ts.map +1 -0
- package/dist/client/effects.js +2 -0
- package/dist/client/effects.js.map +1 -0
- package/dist/client/factory.d.ts +47 -0
- package/dist/client/factory.d.ts.map +1 -0
- package/dist/client/factory.js +105 -0
- package/dist/client/factory.js.map +1 -0
- package/dist/client/index.d.ts +7 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +5 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/rpc/describe-context.d.ts +10 -0
- package/dist/client/rpc/describe-context.d.ts.map +1 -0
- package/dist/client/rpc/describe-context.js +8 -0
- package/dist/client/rpc/describe-context.js.map +1 -0
- package/dist/client/rpc/describe-visible-content.d.ts +22 -0
- package/dist/client/rpc/describe-visible-content.d.ts.map +1 -0
- package/dist/client/rpc/describe-visible-content.js +66 -0
- package/dist/client/rpc/describe-visible-content.js.map +1 -0
- package/dist/client/rpc/get-state.d.ts +15 -0
- package/dist/client/rpc/get-state.d.ts.map +1 -0
- package/dist/client/rpc/get-state.js +37 -0
- package/dist/client/rpc/get-state.js.map +1 -0
- package/dist/client/rpc/list-actions.d.ts +27 -0
- package/dist/client/rpc/list-actions.d.ts.map +1 -0
- package/dist/client/rpc/list-actions.js +38 -0
- package/dist/client/rpc/list-actions.js.map +1 -0
- package/dist/client/rpc/query-dom.d.ts +20 -0
- package/dist/client/rpc/query-dom.d.ts.map +1 -0
- package/dist/client/rpc/query-dom.js +37 -0
- package/dist/client/rpc/query-dom.js.map +1 -0
- package/dist/client/rpc/send-message.d.ts +28 -0
- package/dist/client/rpc/send-message.d.ts.map +1 -0
- package/dist/client/rpc/send-message.js +40 -0
- package/dist/client/rpc/send-message.js.map +1 -0
- package/dist/client/uuid.d.ts +2 -0
- package/dist/client/uuid.d.ts.map +1 -0
- package/dist/client/uuid.js +24 -0
- package/dist/client/uuid.js.map +1 -0
- package/dist/client/ws-client.d.ts +44 -0
- package/dist/client/ws-client.d.ts.map +1 -0
- package/dist/client/ws-client.js +176 -0
- package/dist/client/ws-client.js.map +1 -0
- package/dist/protocol.d.ts +319 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +6 -0
- package/dist/protocol.js.map +1 -0
- package/dist/server/audit.d.ts +6 -0
- package/dist/server/audit.d.ts.map +1 -0
- package/dist/server/audit.js +6 -0
- package/dist/server/audit.js.map +1 -0
- package/dist/server/factory.d.ts +10 -0
- package/dist/server/factory.d.ts.map +1 -0
- package/dist/server/factory.js +69 -0
- package/dist/server/factory.js.map +1 -0
- package/dist/server/http/mint.d.ts +23 -0
- package/dist/server/http/mint.d.ts.map +1 -0
- package/dist/server/http/mint.js +63 -0
- package/dist/server/http/mint.js.map +1 -0
- package/dist/server/http/resume.d.ts +14 -0
- package/dist/server/http/resume.d.ts.map +1 -0
- package/dist/server/http/resume.js +89 -0
- package/dist/server/http/resume.js.map +1 -0
- package/dist/server/http/revoke.d.ts +11 -0
- package/dist/server/http/revoke.d.ts.map +1 -0
- package/dist/server/http/revoke.js +24 -0
- package/dist/server/http/revoke.js.map +1 -0
- package/dist/server/http/router.d.ts +13 -0
- package/dist/server/http/router.d.ts.map +1 -0
- package/dist/server/http/router.js +28 -0
- package/dist/server/http/router.js.map +1 -0
- package/dist/server/http/sessions.d.ts +8 -0
- package/dist/server/http/sessions.d.ts.map +1 -0
- package/dist/server/http/sessions.js +27 -0
- package/dist/server/http/sessions.js.map +1 -0
- package/dist/server/identity.d.ts +8 -0
- package/dist/server/identity.d.ts.map +1 -0
- package/dist/server/identity.js +41 -0
- package/dist/server/identity.js.map +1 -0
- package/dist/server/index.d.ts +11 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +6 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/lap/confirm-result.d.ts +14 -0
- package/dist/server/lap/confirm-result.d.ts.map +1 -0
- package/dist/server/lap/confirm-result.js +60 -0
- package/dist/server/lap/confirm-result.js.map +1 -0
- package/dist/server/lap/describe.d.ts +22 -0
- package/dist/server/lap/describe.d.ts.map +1 -0
- package/dist/server/lap/describe.js +67 -0
- package/dist/server/lap/describe.js.map +1 -0
- package/dist/server/lap/forward.d.ts +24 -0
- package/dist/server/lap/forward.d.ts.map +1 -0
- package/dist/server/lap/forward.js +68 -0
- package/dist/server/lap/forward.js.map +1 -0
- package/dist/server/lap/message.d.ts +14 -0
- package/dist/server/lap/message.d.ts.map +1 -0
- package/dist/server/lap/message.js +97 -0
- package/dist/server/lap/message.js.map +1 -0
- package/dist/server/lap/router.d.ts +4 -0
- package/dist/server/lap/router.d.ts.map +1 -0
- package/dist/server/lap/router.js +37 -0
- package/dist/server/lap/router.js.map +1 -0
- package/dist/server/lap/wait.d.ts +14 -0
- package/dist/server/lap/wait.d.ts.map +1 -0
- package/dist/server/lap/wait.js +35 -0
- package/dist/server/lap/wait.js.map +1 -0
- package/dist/server/options.d.ts +41 -0
- package/dist/server/options.d.ts.map +1 -0
- package/dist/server/options.js +2 -0
- package/dist/server/options.js.map +1 -0
- package/dist/server/rate-limit.d.ts +14 -0
- package/dist/server/rate-limit.d.ts.map +1 -0
- package/dist/server/rate-limit.js +43 -0
- package/dist/server/rate-limit.js.map +1 -0
- package/dist/server/token-store.d.ts +27 -0
- package/dist/server/token-store.d.ts.map +1 -0
- package/dist/server/token-store.js +55 -0
- package/dist/server/token-store.js.map +1 -0
- package/dist/server/token.d.ts +24 -0
- package/dist/server/token.d.ts.map +1 -0
- package/dist/server/token.js +77 -0
- package/dist/server/token.js.map +1 -0
- package/dist/server/ws/pairing-registry.d.ts +53 -0
- package/dist/server/ws/pairing-registry.d.ts.map +1 -0
- package/dist/server/ws/pairing-registry.js +205 -0
- package/dist/server/ws/pairing-registry.js.map +1 -0
- package/dist/server/ws/upgrade.d.ts +23 -0
- package/dist/server/ws/upgrade.d.ts.map +1 -0
- package/dist/server/ws/upgrade.js +81 -0
- package/dist/server/ws/upgrade.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { signToken } from '../token.js';
|
|
2
|
+
export async function handleResumeList(req, deps) {
|
|
3
|
+
if (req.method !== 'POST')
|
|
4
|
+
return methodNotAllowed();
|
|
5
|
+
const body = (await req.json().catch(() => null));
|
|
6
|
+
if (!body || !Array.isArray(body.tids))
|
|
7
|
+
return badRequest();
|
|
8
|
+
const uid = await deps.identityResolver(req);
|
|
9
|
+
const nowMs = (deps.now ?? (() => Date.now()))();
|
|
10
|
+
const out = [];
|
|
11
|
+
for (const tid of body.tids) {
|
|
12
|
+
const rec = await deps.tokenStore.findByTid(tid);
|
|
13
|
+
if (!rec)
|
|
14
|
+
continue;
|
|
15
|
+
if (rec.uid !== uid)
|
|
16
|
+
continue;
|
|
17
|
+
if (rec.status !== 'pending-resume')
|
|
18
|
+
continue;
|
|
19
|
+
if (rec.pendingResumeUntil === null || rec.pendingResumeUntil < nowMs)
|
|
20
|
+
continue;
|
|
21
|
+
out.push({
|
|
22
|
+
tid: rec.tid,
|
|
23
|
+
label: rec.label ?? '(unknown)',
|
|
24
|
+
status: 'pending-resume',
|
|
25
|
+
createdAt: rec.createdAt,
|
|
26
|
+
lastSeenAt: rec.lastSeenAt,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
const payload = { sessions: out };
|
|
30
|
+
return jsonResponse(payload, 200);
|
|
31
|
+
}
|
|
32
|
+
export async function handleResumeClaim(req, deps) {
|
|
33
|
+
if (req.method !== 'POST')
|
|
34
|
+
return methodNotAllowed();
|
|
35
|
+
if (!deps.signingKey)
|
|
36
|
+
return new Response(null, { status: 500 });
|
|
37
|
+
const body = (await req.json().catch(() => null));
|
|
38
|
+
if (!body || typeof body.tid !== 'string')
|
|
39
|
+
return badRequest();
|
|
40
|
+
const uid = await deps.identityResolver(req);
|
|
41
|
+
const rec = await deps.tokenStore.findByTid(body.tid);
|
|
42
|
+
if (!rec)
|
|
43
|
+
return forbidden();
|
|
44
|
+
if (rec.uid !== uid)
|
|
45
|
+
return forbidden();
|
|
46
|
+
if (rec.status !== 'pending-resume')
|
|
47
|
+
return forbidden();
|
|
48
|
+
const origin = new URL(req.url).origin;
|
|
49
|
+
if (rec.origin !== origin)
|
|
50
|
+
return forbidden();
|
|
51
|
+
const nowMs = (deps.now ?? (() => Date.now()))();
|
|
52
|
+
const hardExpiryMs = deps.hardExpiryMs ?? 24 * 60 * 60 * 1000;
|
|
53
|
+
const iat = Math.floor(nowMs / 1000);
|
|
54
|
+
const exp = Math.floor((nowMs + hardExpiryMs) / 1000);
|
|
55
|
+
const payload = { tid: rec.tid, iat, exp, scope: 'agent' };
|
|
56
|
+
const token = signToken(payload, deps.signingKey);
|
|
57
|
+
await deps.tokenStore.markActive(rec.tid, rec.label ?? '(resumed)', nowMs);
|
|
58
|
+
await deps.auditSink.write({
|
|
59
|
+
at: nowMs,
|
|
60
|
+
tid: rec.tid,
|
|
61
|
+
uid: rec.uid,
|
|
62
|
+
event: 'claim',
|
|
63
|
+
detail: { origin },
|
|
64
|
+
});
|
|
65
|
+
const wsUrl = toWsUrl(origin) + '/agent/ws';
|
|
66
|
+
const out = { token, wsUrl };
|
|
67
|
+
return jsonResponse(out, 200);
|
|
68
|
+
}
|
|
69
|
+
function jsonResponse(body, status) {
|
|
70
|
+
return new Response(JSON.stringify(body), {
|
|
71
|
+
status,
|
|
72
|
+
headers: { 'content-type': 'application/json' },
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function methodNotAllowed() {
|
|
76
|
+
return jsonResponse({ error: { code: 'method-not-allowed' } }, 405);
|
|
77
|
+
}
|
|
78
|
+
function badRequest() {
|
|
79
|
+
return jsonResponse({ error: { code: 'invalid' } }, 400);
|
|
80
|
+
}
|
|
81
|
+
function forbidden() {
|
|
82
|
+
return jsonResponse({ error: { code: 'revoked' } }, 403);
|
|
83
|
+
}
|
|
84
|
+
function toWsUrl(httpOrigin) {
|
|
85
|
+
return httpOrigin.startsWith('https://')
|
|
86
|
+
? 'wss://' + httpOrigin.slice('https://'.length)
|
|
87
|
+
: 'ws://' + httpOrigin.slice('http://'.length);
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=resume.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resume.js","sourceRoot":"","sources":["../../../src/server/http/resume.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TokenStore } from '../token-store.js'\nimport type { IdentityResolver } from '../identity.js'\nimport type { AuditSink } from '../audit.js'\nimport { signToken } from '../token.js'\nimport type {\n ResumeListRequest,\n ResumeListResponse,\n ResumeClaimRequest,\n ResumeClaimResponse,\n TokenPayload,\n AgentSession,\n} from '../../protocol.js'\n\nexport type ResumeDeps = {\n tokenStore: TokenStore\n identityResolver: IdentityResolver\n auditSink: AuditSink\n signingKey?: string | Uint8Array\n now?: () => number\n hardExpiryMs?: number\n}\n\nexport async function handleResumeList(req: Request, deps: ResumeDeps): Promise<Response> {\n if (req.method !== 'POST') return methodNotAllowed()\n const body = (await req.json().catch(() => null)) as ResumeListRequest | null\n if (!body || !Array.isArray(body.tids)) return badRequest()\n\n const uid = await deps.identityResolver(req)\n const nowMs = (deps.now ?? (() => Date.now()))()\n const out: AgentSession[] = []\n for (const tid of body.tids) {\n const rec = await deps.tokenStore.findByTid(tid)\n if (!rec) continue\n if (rec.uid !== uid) continue\n if (rec.status !== 'pending-resume') continue\n if (rec.pendingResumeUntil === null || rec.pendingResumeUntil < nowMs) continue\n out.push({\n tid: rec.tid,\n label: rec.label ?? '(unknown)',\n status: 'pending-resume',\n createdAt: rec.createdAt,\n lastSeenAt: rec.lastSeenAt,\n })\n }\n\n const payload: ResumeListResponse = { sessions: out }\n return jsonResponse(payload, 200)\n}\n\nexport async function handleResumeClaim(req: Request, deps: ResumeDeps): Promise<Response> {\n if (req.method !== 'POST') return methodNotAllowed()\n if (!deps.signingKey) return new Response(null, { status: 500 })\n\n const body = (await req.json().catch(() => null)) as ResumeClaimRequest | null\n if (!body || typeof body.tid !== 'string') return badRequest()\n\n const uid = await deps.identityResolver(req)\n const rec = await deps.tokenStore.findByTid(body.tid)\n if (!rec) return forbidden()\n if (rec.uid !== uid) return forbidden()\n if (rec.status !== 'pending-resume') return forbidden()\n\n const origin = new URL(req.url).origin\n if (rec.origin !== origin) return forbidden()\n\n const nowMs = (deps.now ?? (() => Date.now()))()\n const hardExpiryMs = deps.hardExpiryMs ?? 24 * 60 * 60 * 1000\n const iat = Math.floor(nowMs / 1000)\n const exp = Math.floor((nowMs + hardExpiryMs) / 1000)\n const payload: TokenPayload = { tid: rec.tid, iat, exp, scope: 'agent' }\n const token = signToken(payload, deps.signingKey)\n\n await deps.tokenStore.markActive(rec.tid, rec.label ?? '(resumed)', nowMs)\n\n await deps.auditSink.write({\n at: nowMs,\n tid: rec.tid,\n uid: rec.uid,\n event: 'claim',\n detail: { origin },\n })\n\n const wsUrl = toWsUrl(origin) + '/agent/ws'\n const out: ResumeClaimResponse = { token, wsUrl }\n return jsonResponse(out, 200)\n}\n\nfunction jsonResponse(body: unknown, status: number): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { 'content-type': 'application/json' },\n })\n}\n\nfunction methodNotAllowed(): Response {\n return jsonResponse({ error: { code: 'method-not-allowed' } }, 405)\n}\n\nfunction badRequest(): Response {\n return jsonResponse({ error: { code: 'invalid' } }, 400)\n}\n\nfunction forbidden(): Response {\n return jsonResponse({ error: { code: 'revoked' } }, 403)\n}\n\nfunction toWsUrl(httpOrigin: string): string {\n return httpOrigin.startsWith('https://')\n ? 'wss://' + httpOrigin.slice('https://'.length)\n : 'ws://' + httpOrigin.slice('http://'.length)\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { TokenStore } from '../token-store.js';
|
|
2
|
+
import type { IdentityResolver } from '../identity.js';
|
|
3
|
+
import type { AuditSink } from '../audit.js';
|
|
4
|
+
export type RevokeDeps = {
|
|
5
|
+
tokenStore: TokenStore;
|
|
6
|
+
identityResolver: IdentityResolver;
|
|
7
|
+
auditSink: AuditSink;
|
|
8
|
+
now?: () => number;
|
|
9
|
+
};
|
|
10
|
+
export declare function handleRevoke(req: Request, deps: RevokeDeps): Promise<Response>;
|
|
11
|
+
//# sourceMappingURL=revoke.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"revoke.d.ts","sourceRoot":"","sources":["../../../src/server/http/revoke.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAG5C,MAAM,MAAM,UAAU,GAAG;IACvB,UAAU,EAAE,UAAU,CAAA;IACtB,gBAAgB,EAAE,gBAAgB,CAAA;IAClC,SAAS,EAAE,SAAS,CAAA;IACpB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB,CAAA;AAED,wBAAsB,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC,CAiBpF"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export async function handleRevoke(req, deps) {
|
|
2
|
+
if (req.method !== 'POST') {
|
|
3
|
+
return json({ error: { code: 'method-not-allowed' } }, 405);
|
|
4
|
+
}
|
|
5
|
+
const body = (await req.json().catch(() => null));
|
|
6
|
+
if (!body || typeof body.tid !== 'string')
|
|
7
|
+
return json({ error: { code: 'invalid' } }, 400);
|
|
8
|
+
const uid = await deps.identityResolver(req);
|
|
9
|
+
const rec = await deps.tokenStore.findByTid(body.tid);
|
|
10
|
+
if (!rec || rec.uid !== uid)
|
|
11
|
+
return json({ error: { code: 'revoked' } }, 403);
|
|
12
|
+
const nowMs = (deps.now ?? (() => Date.now()))();
|
|
13
|
+
await deps.tokenStore.revoke(body.tid);
|
|
14
|
+
await deps.auditSink.write({ at: nowMs, tid: body.tid, uid, event: 'revoke', detail: {} });
|
|
15
|
+
const out = { status: 'revoked' };
|
|
16
|
+
return json(out, 200);
|
|
17
|
+
}
|
|
18
|
+
function json(b, s) {
|
|
19
|
+
return new Response(JSON.stringify(b), {
|
|
20
|
+
status: s,
|
|
21
|
+
headers: { 'content-type': 'application/json' },
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=revoke.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"revoke.js","sourceRoot":"","sources":["../../../src/server/http/revoke.ts"],"names":[],"mappings":"AAYA,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAAY,EAAE,IAAgB;IAC/D,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,oBAAoB,EAAE,EAAE,EAAE,GAAG,CAAC,CAAA;IAC7D,CAAC;IACD,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAyB,CAAA;IACzE,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,CAAC,GAAG,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,GAAG,CAAC,CAAA;IAE3F,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAA;IAC5C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACrD,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,GAAG,CAAC,CAAA;IAE7E,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAA;IAChD,MAAM,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACtC,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAA;IAE1F,MAAM,GAAG,GAAmB,EAAE,MAAM,EAAE,SAAS,EAAE,CAAA;IACjD,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 { IdentityResolver } from '../identity.js'\nimport type { AuditSink } from '../audit.js'\nimport type { RevokeRequest, RevokeResponse } from '../../protocol.js'\n\nexport type RevokeDeps = {\n tokenStore: TokenStore\n identityResolver: IdentityResolver\n auditSink: AuditSink\n now?: () => number\n}\n\nexport async function handleRevoke(req: Request, deps: RevokeDeps): Promise<Response> {\n if (req.method !== 'POST') {\n return json({ error: { code: 'method-not-allowed' } }, 405)\n }\n const body = (await req.json().catch(() => null)) as RevokeRequest | null\n if (!body || typeof body.tid !== 'string') return json({ error: { code: 'invalid' } }, 400)\n\n const uid = await deps.identityResolver(req)\n const rec = await deps.tokenStore.findByTid(body.tid)\n if (!rec || rec.uid !== uid) return json({ error: { code: 'revoked' } }, 403)\n\n const nowMs = (deps.now ?? (() => Date.now()))()\n await deps.tokenStore.revoke(body.tid)\n await deps.auditSink.write({ at: nowMs, tid: body.tid, uid, event: 'revoke', detail: {} })\n\n const out: RevokeResponse = { status: 'revoked' }\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,13 @@
|
|
|
1
|
+
import { type MintDeps } from './mint.js';
|
|
2
|
+
import { type ResumeDeps } from './resume.js';
|
|
3
|
+
import { type RevokeDeps } from './revoke.js';
|
|
4
|
+
import { type SessionsDeps } from './sessions.js';
|
|
5
|
+
export type RouterDeps = MintDeps & ResumeDeps & RevokeDeps & SessionsDeps;
|
|
6
|
+
/**
|
|
7
|
+
* Matches any /agent/* request and returns the appropriate Response.
|
|
8
|
+
* Returns `null` when the request doesn't match any known path — caller
|
|
9
|
+
* can fall through to their framework's 404 handling. LAP and WS paths
|
|
10
|
+
* are NOT handled here (they land in Plan 5 + factory composition).
|
|
11
|
+
*/
|
|
12
|
+
export declare function createHttpRouter(deps: RouterDeps): (req: Request) => Promise<Response | null>;
|
|
13
|
+
//# sourceMappingURL=router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../../src/server/http/router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAA;AACrD,OAAO,EAAuC,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAClF,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAC3D,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,eAAe,CAAA;AAEjE,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,GAAG,YAAY,CAAA;AAE1E;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,UAAU,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAa7F"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { handleMint } from './mint.js';
|
|
2
|
+
import { handleResumeList, handleResumeClaim } from './resume.js';
|
|
3
|
+
import { handleRevoke } from './revoke.js';
|
|
4
|
+
import { handleSessions } from './sessions.js';
|
|
5
|
+
/**
|
|
6
|
+
* Matches any /agent/* request and returns the appropriate Response.
|
|
7
|
+
* Returns `null` when the request doesn't match any known path — caller
|
|
8
|
+
* can fall through to their framework's 404 handling. LAP and WS paths
|
|
9
|
+
* are NOT handled here (they land in Plan 5 + factory composition).
|
|
10
|
+
*/
|
|
11
|
+
export function createHttpRouter(deps) {
|
|
12
|
+
return async (req) => {
|
|
13
|
+
const url = new URL(req.url);
|
|
14
|
+
const path = url.pathname;
|
|
15
|
+
if (path === '/agent/mint')
|
|
16
|
+
return handleMint(req, deps);
|
|
17
|
+
if (path === '/agent/resume/list')
|
|
18
|
+
return handleResumeList(req, deps);
|
|
19
|
+
if (path === '/agent/resume/claim')
|
|
20
|
+
return handleResumeClaim(req, deps);
|
|
21
|
+
if (path === '/agent/revoke')
|
|
22
|
+
return handleRevoke(req, deps);
|
|
23
|
+
if (path === '/agent/sessions')
|
|
24
|
+
return handleSessions(req, deps);
|
|
25
|
+
return null;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.js","sourceRoot":"","sources":["../../../src/server/http/router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAiB,MAAM,WAAW,CAAA;AACrD,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAmB,MAAM,aAAa,CAAA;AAClF,OAAO,EAAE,YAAY,EAAmB,MAAM,aAAa,CAAA;AAC3D,OAAO,EAAE,cAAc,EAAqB,MAAM,eAAe,CAAA;AAIjE;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAgB;IAC/C,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;QAEzB,IAAI,IAAI,KAAK,aAAa;YAAE,OAAO,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;QACxD,IAAI,IAAI,KAAK,oBAAoB;YAAE,OAAO,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;QACrE,IAAI,IAAI,KAAK,qBAAqB;YAAE,OAAO,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;QACvE,IAAI,IAAI,KAAK,eAAe;YAAE,OAAO,YAAY,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;QAC5D,IAAI,IAAI,KAAK,iBAAiB;YAAE,OAAO,cAAc,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;QAEhE,OAAO,IAAI,CAAA;IACb,CAAC,CAAA;AACH,CAAC","sourcesContent":["import { handleMint, type MintDeps } from './mint.js'\nimport { handleResumeList, handleResumeClaim, type ResumeDeps } from './resume.js'\nimport { handleRevoke, type RevokeDeps } from './revoke.js'\nimport { handleSessions, type SessionsDeps } from './sessions.js'\n\nexport type RouterDeps = MintDeps & ResumeDeps & RevokeDeps & SessionsDeps\n\n/**\n * Matches any /agent/* request and returns the appropriate Response.\n * Returns `null` when the request doesn't match any known path — caller\n * can fall through to their framework's 404 handling. LAP and WS paths\n * are NOT handled here (they land in Plan 5 + factory composition).\n */\nexport function createHttpRouter(deps: RouterDeps): (req: Request) => Promise<Response | null> {\n return async (req) => {\n const url = new URL(req.url)\n const path = url.pathname\n\n if (path === '/agent/mint') return handleMint(req, deps)\n if (path === '/agent/resume/list') return handleResumeList(req, deps)\n if (path === '/agent/resume/claim') return handleResumeClaim(req, deps)\n if (path === '/agent/revoke') return handleRevoke(req, deps)\n if (path === '/agent/sessions') return handleSessions(req, deps)\n\n return null\n }\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { TokenStore } from '../token-store.js';
|
|
2
|
+
import type { IdentityResolver } from '../identity.js';
|
|
3
|
+
export type SessionsDeps = {
|
|
4
|
+
tokenStore: TokenStore;
|
|
5
|
+
identityResolver: IdentityResolver;
|
|
6
|
+
};
|
|
7
|
+
export declare function handleSessions(req: Request, deps: SessionsDeps): Promise<Response>;
|
|
8
|
+
//# sourceMappingURL=sessions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../../src/server/http/sessions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAGtD,MAAM,MAAM,YAAY,GAAG;IACzB,UAAU,EAAE,UAAU,CAAA;IACtB,gBAAgB,EAAE,gBAAgB,CAAA;CACnC,CAAA;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,CAmBxF"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export async function handleSessions(req, deps) {
|
|
2
|
+
if (req.method !== 'GET') {
|
|
3
|
+
return json({ error: { code: 'method-not-allowed' } }, 405);
|
|
4
|
+
}
|
|
5
|
+
const uid = await deps.identityResolver(req);
|
|
6
|
+
if (uid === null) {
|
|
7
|
+
return json({ sessions: [] }, 200);
|
|
8
|
+
}
|
|
9
|
+
const records = await deps.tokenStore.listByIdentity(uid);
|
|
10
|
+
const sessions = records
|
|
11
|
+
.filter((r) => r.status === 'active' || r.status === 'pending-resume')
|
|
12
|
+
.map((r) => ({
|
|
13
|
+
tid: r.tid,
|
|
14
|
+
label: r.label ?? '(unknown)',
|
|
15
|
+
status: r.status,
|
|
16
|
+
createdAt: r.createdAt,
|
|
17
|
+
lastSeenAt: r.lastSeenAt,
|
|
18
|
+
}));
|
|
19
|
+
return json({ sessions }, 200);
|
|
20
|
+
}
|
|
21
|
+
function json(b, s) {
|
|
22
|
+
return new Response(JSON.stringify(b), {
|
|
23
|
+
status: s,
|
|
24
|
+
headers: { 'content-type': 'application/json' },
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=sessions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../../src/server/http/sessions.ts"],"names":[],"mappings":"AASA,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,GAAY,EAAE,IAAkB;IACnE,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;QACzB,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,oBAAoB,EAAE,EAAE,EAAE,GAAG,CAAC,CAAA;IAC7D,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAA;IAC5C,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QACjB,OAAO,IAAI,CAAC,EAAE,QAAQ,EAAE,EAAE,EAA6B,EAAE,GAAG,CAAC,CAAA;IAC/D,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,GAAG,CAAC,CAAA;IACzD,MAAM,QAAQ,GAAmB,OAAO;SACrC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,KAAK,gBAAgB,CAAC;SACrE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACX,GAAG,EAAE,CAAC,CAAC,GAAG;QACV,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,WAAW;QAC7B,MAAM,EAAE,CAAC,CAAC,MAAqC;QAC/C,SAAS,EAAE,CAAC,CAAC,SAAS;QACtB,UAAU,EAAE,CAAC,CAAC,UAAU;KACzB,CAAC,CAAC,CAAA;IACL,OAAO,IAAI,CAAC,EAAE,QAAQ,EAA6B,EAAE,GAAG,CAAC,CAAA;AAC3D,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 { IdentityResolver } from '../identity.js'\nimport type { AgentSession, SessionsResponse } from '../../protocol.js'\n\nexport type SessionsDeps = {\n tokenStore: TokenStore\n identityResolver: IdentityResolver\n}\n\nexport async function handleSessions(req: Request, deps: SessionsDeps): Promise<Response> {\n if (req.method !== 'GET') {\n return json({ error: { code: 'method-not-allowed' } }, 405)\n }\n const uid = await deps.identityResolver(req)\n if (uid === null) {\n return json({ sessions: [] } satisfies SessionsResponse, 200)\n }\n const records = await deps.tokenStore.listByIdentity(uid)\n const sessions: AgentSession[] = records\n .filter((r) => r.status === 'active' || r.status === 'pending-resume')\n .map((r) => ({\n tid: r.tid,\n label: r.label ?? '(unknown)',\n status: r.status as 'active' | 'pending-resume',\n createdAt: r.createdAt,\n lastSeenAt: r.lastSeenAt,\n }))\n return json({ sessions } satisfies SessionsResponse, 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,8 @@
|
|
|
1
|
+
export type IdentityResolver = (req: Request) => Promise<string | null>;
|
|
2
|
+
export type IdentityCookieConfig = {
|
|
3
|
+
name: string;
|
|
4
|
+
signingKey: string | Uint8Array;
|
|
5
|
+
};
|
|
6
|
+
export declare function defaultIdentityResolver(cfg: IdentityCookieConfig): IdentityResolver;
|
|
7
|
+
export declare function signCookieValue(value: string, signingKey: string | Uint8Array): string;
|
|
8
|
+
//# sourceMappingURL=identity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/server/identity.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,gBAAgB,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;AAEvE,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,GAAG,UAAU,CAAA;CAChC,CAAA;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,oBAAoB,GAAG,gBAAgB,CA8BnF;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAKtF"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
export function defaultIdentityResolver(cfg) {
|
|
3
|
+
if (!cfg.signingKey || (typeof cfg.signingKey === 'string' && cfg.signingKey.length < 32)) {
|
|
4
|
+
throw new Error('IdentityCookie signingKey must be at least 32 bytes');
|
|
5
|
+
}
|
|
6
|
+
const keyBuf = typeof cfg.signingKey === 'string'
|
|
7
|
+
? Buffer.from(cfg.signingKey, 'utf8')
|
|
8
|
+
: Buffer.from(cfg.signingKey);
|
|
9
|
+
return async (req) => {
|
|
10
|
+
const cookie = req.headers.get('cookie');
|
|
11
|
+
if (!cookie)
|
|
12
|
+
return null;
|
|
13
|
+
const pairs = cookie.split(';').map((s) => s.trim());
|
|
14
|
+
for (const pair of pairs) {
|
|
15
|
+
const eq = pair.indexOf('=');
|
|
16
|
+
if (eq < 0)
|
|
17
|
+
continue;
|
|
18
|
+
const name = pair.slice(0, eq);
|
|
19
|
+
const value = pair.slice(eq + 1);
|
|
20
|
+
if (name !== cfg.name)
|
|
21
|
+
continue;
|
|
22
|
+
const dot = value.indexOf('.');
|
|
23
|
+
if (dot < 0)
|
|
24
|
+
return null;
|
|
25
|
+
const rawValue = value.slice(0, dot);
|
|
26
|
+
const sigPart = value.slice(dot + 1);
|
|
27
|
+
const sigBuf = Buffer.from(sigPart, 'base64url');
|
|
28
|
+
const expected = createHmac('sha256', keyBuf).update(rawValue).digest();
|
|
29
|
+
if (expected.length !== sigBuf.length || !timingSafeEqual(expected, sigBuf))
|
|
30
|
+
return null;
|
|
31
|
+
return rawValue;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export function signCookieValue(value, signingKey) {
|
|
37
|
+
const keyBuf = typeof signingKey === 'string' ? Buffer.from(signingKey, 'utf8') : Buffer.from(signingKey);
|
|
38
|
+
const mac = createHmac('sha256', keyBuf).update(value).digest('base64url');
|
|
39
|
+
return `${value}.${mac}`;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/server/identity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AASzD,MAAM,UAAU,uBAAuB,CAAC,GAAyB;IAC/D,IAAI,CAAC,GAAG,CAAC,UAAU,IAAI,CAAC,OAAO,GAAG,CAAC,UAAU,KAAK,QAAQ,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC;QAC1F,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAA;IACxE,CAAC;IACD,MAAM,MAAM,GACV,OAAO,GAAG,CAAC,UAAU,KAAK,QAAQ;QAChC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC;QACrC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;IAEjC,OAAO,KAAK,EAAE,GAAG,EAAE,EAAE;QACnB,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;QACxC,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAA;QACxB,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;QACpD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;YAC5B,IAAI,EAAE,GAAG,CAAC;gBAAE,SAAQ;YACpB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;YAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA;YAChC,IAAI,IAAI,KAAK,GAAG,CAAC,IAAI;gBAAE,SAAQ;YAC/B,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;YAC9B,IAAI,GAAG,GAAG,CAAC;gBAAE,OAAO,IAAI,CAAA;YACxB,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;YACpC,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAA;YACpC,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAA;YAChD,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,CAAA;YACvE,IAAI,QAAQ,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,IAAI,CAAC,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC;gBAAE,OAAO,IAAI,CAAA;YACxF,OAAO,QAAQ,CAAA;QACjB,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC,CAAA;AACH,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAa,EAAE,UAA+B;IAC5E,MAAM,MAAM,GACV,OAAO,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IAC5F,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;IAC1E,OAAO,GAAG,KAAK,IAAI,GAAG,EAAE,CAAA;AAC1B,CAAC","sourcesContent":["import { createHmac, timingSafeEqual } from 'node:crypto'\n\nexport type IdentityResolver = (req: Request) => Promise<string | null>\n\nexport type IdentityCookieConfig = {\n name: string\n signingKey: string | Uint8Array\n}\n\nexport function defaultIdentityResolver(cfg: IdentityCookieConfig): IdentityResolver {\n if (!cfg.signingKey || (typeof cfg.signingKey === 'string' && cfg.signingKey.length < 32)) {\n throw new Error('IdentityCookie signingKey must be at least 32 bytes')\n }\n const keyBuf =\n typeof cfg.signingKey === 'string'\n ? Buffer.from(cfg.signingKey, 'utf8')\n : Buffer.from(cfg.signingKey)\n\n return async (req) => {\n const cookie = req.headers.get('cookie')\n if (!cookie) return null\n const pairs = cookie.split(';').map((s) => s.trim())\n for (const pair of pairs) {\n const eq = pair.indexOf('=')\n if (eq < 0) continue\n const name = pair.slice(0, eq)\n const value = pair.slice(eq + 1)\n if (name !== cfg.name) continue\n const dot = value.indexOf('.')\n if (dot < 0) return null\n const rawValue = value.slice(0, dot)\n const sigPart = value.slice(dot + 1)\n const sigBuf = Buffer.from(sigPart, 'base64url')\n const expected = createHmac('sha256', keyBuf).update(rawValue).digest()\n if (expected.length !== sigBuf.length || !timingSafeEqual(expected, sigBuf)) return null\n return rawValue\n }\n return null\n }\n}\n\nexport function signCookieValue(value: string, signingKey: string | Uint8Array): string {\n const keyBuf =\n typeof signingKey === 'string' ? Buffer.from(signingKey, 'utf8') : Buffer.from(signingKey)\n const mac = createHmac('sha256', keyBuf).update(value).digest('base64url')\n return `${value}.${mac}`\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { createLluiAgentServer } from './factory.js';
|
|
2
|
+
export type { ServerOptions, AgentServerHandle } from './options.js';
|
|
3
|
+
export { InMemoryTokenStore } from './token-store.js';
|
|
4
|
+
export type { TokenStore } from './token-store.js';
|
|
5
|
+
export { defaultIdentityResolver } from './identity.js';
|
|
6
|
+
export type { IdentityResolver } from './identity.js';
|
|
7
|
+
export { consoleAuditSink } from './audit.js';
|
|
8
|
+
export type { AuditSink } from './audit.js';
|
|
9
|
+
export { defaultRateLimiter } from './rate-limit.js';
|
|
10
|
+
export type { RateLimiter } from './rate-limit.js';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AACpD,YAAY,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAA;AACvD,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAC7C,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACpD,YAAY,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { createLluiAgentServer } from './factory.js';
|
|
2
|
+
export { InMemoryTokenStore } from './token-store.js';
|
|
3
|
+
export { defaultIdentityResolver } from './identity.js';
|
|
4
|
+
export { consoleAuditSink } from './audit.js';
|
|
5
|
+
export { defaultRateLimiter } from './rate-limit.js';
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AAEpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAErD,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAA;AAEvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAE7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA","sourcesContent":["export { createLluiAgentServer } from './factory.js'\nexport type { ServerOptions, AgentServerHandle } from './options.js'\nexport { InMemoryTokenStore } from './token-store.js'\nexport type { TokenStore } from './token-store.js'\nexport { defaultIdentityResolver } from './identity.js'\nexport type { IdentityResolver } from './identity.js'\nexport { consoleAuditSink } from './audit.js'\nexport type { AuditSink } from './audit.js'\nexport { defaultRateLimiter } from './rate-limit.js'\nexport type { RateLimiter } from './rate-limit.js'\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 LapConfirmResultDeps = {
|
|
6
|
+
signingKey: string | Uint8Array;
|
|
7
|
+
tokenStore: TokenStore;
|
|
8
|
+
registry: WsPairingRegistry;
|
|
9
|
+
auditSink: AuditSink;
|
|
10
|
+
rateLimiter: RateLimiter;
|
|
11
|
+
now?: () => number;
|
|
12
|
+
};
|
|
13
|
+
export declare function handleLapConfirmResult(req: Request, deps: LapConfirmResultDeps): Promise<Response>;
|
|
14
|
+
//# sourceMappingURL=confirm-result.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"confirm-result.d.ts","sourceRoot":"","sources":["../../../src/server/lap/confirm-result.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,KAAK,EAAE,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,oBAAoB,GAAG;IACjC,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,sBAAsB,CAC1C,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,QAAQ,CAAC,CA0DnB"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { verifyAndReadTid } from './describe.js';
|
|
2
|
+
export async function handleLapConfirmResult(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 || typeof body.confirmId !== 'string')
|
|
17
|
+
return json({ error: { code: 'invalid' } }, 400);
|
|
18
|
+
const timeoutMs = body.timeoutMs ?? 5_000;
|
|
19
|
+
// Spec: if the confirm was already resolved during the earlier long-poll on
|
|
20
|
+
// /message, there's no second resolution to wait for. In the current design
|
|
21
|
+
// /confirm-result is ONLY used when /message bailed out early with
|
|
22
|
+
// pending-confirmation. So we call waitForConfirm with the given timeoutMs.
|
|
23
|
+
// If no resolution arrives in time, we surface 'still-pending'.
|
|
24
|
+
const result = await deps.registry.waitForConfirm(auth.tid, body.confirmId, timeoutMs);
|
|
25
|
+
const nowMs = (deps.now ?? (() => Date.now()))();
|
|
26
|
+
if (result.outcome === 'confirmed') {
|
|
27
|
+
await deps.auditSink.write({
|
|
28
|
+
at: nowMs,
|
|
29
|
+
tid: auth.tid,
|
|
30
|
+
uid: rec.uid,
|
|
31
|
+
event: 'confirm-approved',
|
|
32
|
+
detail: { confirmId: body.confirmId },
|
|
33
|
+
});
|
|
34
|
+
return json({ status: 'confirmed', stateAfter: result.stateAfter }, 200);
|
|
35
|
+
}
|
|
36
|
+
// user-cancelled OR timeout. WsPairingRegistry returns user-cancelled on timeout too;
|
|
37
|
+
// we distinguish by checking whether the confirm is still in registry.pendingConfirm —
|
|
38
|
+
// but pendingConfirm cleanup happens inside waitForConfirm's timer, so we can't peek.
|
|
39
|
+
// For v1: treat user-cancelled as user-cancelled; treat explicit timeout as timeout by
|
|
40
|
+
// comparing elapsed vs. timeoutMs. Simpler: just return 'still-pending' on the timeout
|
|
41
|
+
// branch to let Claude poll again. Registry returns {outcome: 'user-cancelled'} on
|
|
42
|
+
// both timer and actual cancel — so we can't distinguish. Punt: return 'user-cancelled'
|
|
43
|
+
// (matches registry semantics). Spec §8.2 get_confirm_result allows 'user-cancelled' |
|
|
44
|
+
// 'timeout' | 'still-pending' — a refinement to distinguish is follow-up work.
|
|
45
|
+
await deps.auditSink.write({
|
|
46
|
+
at: nowMs,
|
|
47
|
+
tid: auth.tid,
|
|
48
|
+
uid: rec.uid,
|
|
49
|
+
event: 'confirm-rejected',
|
|
50
|
+
detail: { confirmId: body.confirmId },
|
|
51
|
+
});
|
|
52
|
+
return json({ status: 'rejected', reason: 'user-cancelled' }, 200);
|
|
53
|
+
}
|
|
54
|
+
function json(b, s) {
|
|
55
|
+
return new Response(JSON.stringify(b), {
|
|
56
|
+
status: s,
|
|
57
|
+
headers: { 'content-type': 'application/json' },
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=confirm-result.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"confirm-result.js","sourceRoot":"","sources":["../../../src/server/lap/confirm-result.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TokenStore } from '../token-store.js'\nimport type { 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 { LapConfirmResultRequest, LapConfirmResultResponse } from '../../protocol.js'\n\nexport type LapConfirmResultDeps = {\n signingKey: string | Uint8Array\n tokenStore: TokenStore\n registry: WsPairingRegistry\n auditSink: AuditSink\n rateLimiter: RateLimiter\n now?: () => number\n}\n\nexport async function handleLapConfirmResult(\n req: Request,\n deps: LapConfirmResultDeps,\n): Promise<Response> {\n const auth = verifyAndReadTid(req, deps.signingKey)\n if (!auth.ok) return json({ error: { code: auth.code } }, auth.status)\n\n const rec = await deps.tokenStore.findByTid(auth.tid)\n if (!rec || rec.status === 'revoked') return json({ error: { code: 'revoked' } }, 403)\n if (!deps.registry.isPaired(auth.tid)) return json({ error: { code: 'paused' } }, 503)\n\n const rlCheck = await deps.rateLimiter.check(auth.tid, 'token')\n if (!rlCheck.allowed) {\n return json({ error: { code: 'rate-limited', retryAfterMs: rlCheck.retryAfterMs } }, 429)\n }\n\n const body = (await req.json().catch(() => null)) as LapConfirmResultRequest | null\n if (!body || typeof body.confirmId !== 'string') return json({ error: { code: 'invalid' } }, 400)\n const timeoutMs = body.timeoutMs ?? 5_000\n\n // Spec: if the confirm was already resolved during the earlier long-poll on\n // /message, there's no second resolution to wait for. In the current design\n // /confirm-result is ONLY used when /message bailed out early with\n // pending-confirmation. So we call waitForConfirm with the given timeoutMs.\n // If no resolution arrives in time, we surface 'still-pending'.\n const result = await deps.registry.waitForConfirm(auth.tid, body.confirmId, timeoutMs)\n\n const nowMs = (deps.now ?? (() => Date.now()))()\n if (result.outcome === 'confirmed') {\n await deps.auditSink.write({\n at: nowMs,\n tid: auth.tid,\n uid: rec.uid,\n event: 'confirm-approved',\n detail: { confirmId: body.confirmId },\n })\n return json(\n { status: 'confirmed', stateAfter: result.stateAfter } satisfies LapConfirmResultResponse,\n 200,\n )\n }\n // user-cancelled OR timeout. WsPairingRegistry returns user-cancelled on timeout too;\n // we distinguish by checking whether the confirm is still in registry.pendingConfirm —\n // but pendingConfirm cleanup happens inside waitForConfirm's timer, so we can't peek.\n // For v1: treat user-cancelled as user-cancelled; treat explicit timeout as timeout by\n // comparing elapsed vs. timeoutMs. Simpler: just return 'still-pending' on the timeout\n // branch to let Claude poll again. Registry returns {outcome: 'user-cancelled'} on\n // both timer and actual cancel — so we can't distinguish. Punt: return 'user-cancelled'\n // (matches registry semantics). Spec §8.2 get_confirm_result allows 'user-cancelled' |\n // 'timeout' | 'still-pending' — a refinement to distinguish is follow-up work.\n await deps.auditSink.write({\n at: nowMs,\n tid: auth.tid,\n uid: rec.uid,\n event: 'confirm-rejected',\n detail: { confirmId: body.confirmId },\n })\n return json(\n { status: 'rejected', reason: 'user-cancelled' } satisfies LapConfirmResultResponse,\n 200,\n )\n}\n\nfunction json(b: unknown, s: number): Response {\n return new Response(JSON.stringify(b), {\n status: s,\n headers: { 'content-type': 'application/json' },\n })\n}\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
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 LapDescribeDeps = {
|
|
6
|
+
signingKey: string | Uint8Array;
|
|
7
|
+
tokenStore: TokenStore;
|
|
8
|
+
registry: WsPairingRegistry;
|
|
9
|
+
auditSink: AuditSink;
|
|
10
|
+
rateLimiter: RateLimiter;
|
|
11
|
+
now?: () => number;
|
|
12
|
+
};
|
|
13
|
+
export declare function handleLapDescribe(req: Request, deps: LapDescribeDeps): Promise<Response>;
|
|
14
|
+
export declare function verifyAndReadTid(req: Request, key: string | Uint8Array): {
|
|
15
|
+
ok: true;
|
|
16
|
+
tid: string;
|
|
17
|
+
} | {
|
|
18
|
+
ok: false;
|
|
19
|
+
status: number;
|
|
20
|
+
code: string;
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=describe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"describe.d.ts","sourceRoot":"","sources":["../../../src/server/lap/describe.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAClE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAGnD,MAAM,MAAM,eAAe,GAAG;IAC5B,UAAU,EAAE,MAAM,GAAG,UAAU,CAAA;IAC/B,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,EAAE,iBAAiB,CAAA;IAC3B,SAAS,EAAE,SAAS,CAAA;IACpB,WAAW,EAAE,WAAW,CAAA;IACxB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB,CAAA;AAED,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC,CAoD9F;AAED,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,MAAM,GAAG,UAAU,GACvB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAOzE"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { verifyToken } from '../token.js';
|
|
2
|
+
export async function handleLapDescribe(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 hello = deps.registry.getHello(auth.tid);
|
|
16
|
+
if (!hello)
|
|
17
|
+
return json({ error: { code: 'paused' } }, 503);
|
|
18
|
+
const messages = hello.msgSchema;
|
|
19
|
+
const out = {
|
|
20
|
+
name: hello.appName,
|
|
21
|
+
version: hello.appVersion,
|
|
22
|
+
stateSchema: hello.stateSchema,
|
|
23
|
+
messages,
|
|
24
|
+
docs: hello.docs,
|
|
25
|
+
conventions: {
|
|
26
|
+
dispatchModel: 'TEA',
|
|
27
|
+
confirmationModel: 'runtime-mediated',
|
|
28
|
+
readSurfaces: ['state', 'query_dom', 'describe_visible_content', 'describe_context'],
|
|
29
|
+
},
|
|
30
|
+
schemaHash: hello.schemaHash,
|
|
31
|
+
};
|
|
32
|
+
const nowMs = (deps.now ?? (() => Date.now()))();
|
|
33
|
+
// Transition to active: Claude has made its first LAP call to /describe,
|
|
34
|
+
// confirming both the browser WS and Claude are live.
|
|
35
|
+
const wasAwaitingClaude = rec.status === 'awaiting-claude';
|
|
36
|
+
const label = rec.uid ?? 'Claude';
|
|
37
|
+
await deps.tokenStore.markActive(auth.tid, label, nowMs);
|
|
38
|
+
// Fire the active signal to the browser only on the first transition.
|
|
39
|
+
if (wasAwaitingClaude) {
|
|
40
|
+
deps.registry.notify(auth.tid, { t: 'active' });
|
|
41
|
+
}
|
|
42
|
+
await deps.auditSink.write({
|
|
43
|
+
at: nowMs,
|
|
44
|
+
tid: auth.tid,
|
|
45
|
+
uid: rec.uid,
|
|
46
|
+
event: 'lap-call',
|
|
47
|
+
detail: { path: '/lap/v1/describe' },
|
|
48
|
+
});
|
|
49
|
+
return json(out, 200);
|
|
50
|
+
}
|
|
51
|
+
export function verifyAndReadTid(req, key) {
|
|
52
|
+
const auth = req.headers.get('authorization');
|
|
53
|
+
if (!auth || !auth.startsWith('Bearer '))
|
|
54
|
+
return { ok: false, status: 401, code: 'auth-failed' };
|
|
55
|
+
const token = auth.slice('Bearer '.length);
|
|
56
|
+
const v = verifyToken(token, key);
|
|
57
|
+
if (v.kind !== 'ok')
|
|
58
|
+
return { ok: false, status: 401, code: 'auth-failed' };
|
|
59
|
+
return { ok: true, tid: v.payload.tid };
|
|
60
|
+
}
|
|
61
|
+
function json(b, s) {
|
|
62
|
+
return new Response(JSON.stringify(b), {
|
|
63
|
+
status: s,
|
|
64
|
+
headers: { 'content-type': 'application/json' },
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=describe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"describe.js","sourceRoot":"","sources":["../../../src/server/lap/describe.ts"],"names":[],"mappings":"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","sourcesContent":["import { verifyToken } from '../token.js'\nimport type { TokenStore } from '../token-store.js'\nimport type { WsPairingRegistry } from '../ws/pairing-registry.js'\nimport type { AuditSink } from '../audit.js'\nimport type { RateLimiter } from '../rate-limit.js'\nimport type { LapDescribeResponse, MessageSchemaEntry } from '../../protocol.js'\n\nexport type LapDescribeDeps = {\n signingKey: string | Uint8Array\n tokenStore: TokenStore\n registry: WsPairingRegistry\n auditSink: AuditSink\n rateLimiter: RateLimiter\n now?: () => number\n}\n\nexport async function handleLapDescribe(req: Request, deps: LapDescribeDeps): 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 hello = deps.registry.getHello(auth.tid)\n if (!hello) return json({ error: { code: 'paused' } }, 503)\n\n const messages: Record<string, MessageSchemaEntry> = hello.msgSchema as Record<\n string,\n MessageSchemaEntry\n >\n const out: LapDescribeResponse = {\n name: hello.appName,\n version: hello.appVersion,\n stateSchema: hello.stateSchema,\n messages,\n docs: hello.docs,\n conventions: {\n dispatchModel: 'TEA',\n confirmationModel: 'runtime-mediated',\n readSurfaces: ['state', 'query_dom', 'describe_visible_content', 'describe_context'],\n },\n schemaHash: hello.schemaHash,\n }\n\n const nowMs = (deps.now ?? (() => Date.now()))()\n // Transition to active: Claude has made its first LAP call to /describe,\n // confirming both the browser WS and Claude are live.\n const wasAwaitingClaude = rec.status === 'awaiting-claude'\n const label = rec.uid ?? 'Claude'\n await deps.tokenStore.markActive(auth.tid, label, nowMs)\n // Fire the active signal to the browser only on the first transition.\n if (wasAwaitingClaude) {\n deps.registry.notify(auth.tid, { t: 'active' })\n }\n await deps.auditSink.write({\n at: nowMs,\n tid: auth.tid,\n uid: rec.uid,\n event: 'lap-call',\n detail: { path: '/lap/v1/describe' },\n })\n return json(out, 200)\n}\n\nexport function verifyAndReadTid(\n req: Request,\n key: string | Uint8Array,\n): { ok: true; tid: string } | { ok: false; status: number; code: string } {\n const auth = req.headers.get('authorization')\n if (!auth || !auth.startsWith('Bearer ')) return { ok: false, status: 401, code: 'auth-failed' }\n const token = auth.slice('Bearer '.length)\n const v = verifyToken(token, key)\n if (v.kind !== 'ok') return { ok: false, status: 401, code: 'auth-failed' }\n return { ok: true, tid: v.payload.tid }\n}\n\nfunction json(b: unknown, s: number): Response {\n return new Response(JSON.stringify(b), {\n status: s,\n headers: { 'content-type': 'application/json' },\n })\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
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 ForwardDeps = {
|
|
6
|
+
signingKey: string | Uint8Array;
|
|
7
|
+
tokenStore: TokenStore;
|
|
8
|
+
registry: WsPairingRegistry;
|
|
9
|
+
auditSink: AuditSink;
|
|
10
|
+
rateLimiter: RateLimiter;
|
|
11
|
+
now?: () => number;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Generic LAP handler. `parseArgs` is called with the parsed body (may be
|
|
15
|
+
* null for empty bodies); it returns the args object to forward or null
|
|
16
|
+
* to reject as invalid. `tool` is the browser-side tool name.
|
|
17
|
+
*/
|
|
18
|
+
export declare function makeForwardHandler(tool: string, parseArgs: (body: unknown) => object | null, auditDetail?: (tid: string, args: object) => Record<string, unknown>): (req: Request, deps: ForwardDeps) => Promise<Response>;
|
|
19
|
+
export declare const handleLapState: (req: Request, deps: ForwardDeps) => Promise<Response>;
|
|
20
|
+
export declare const handleLapActions: (req: Request, deps: ForwardDeps) => Promise<Response>;
|
|
21
|
+
export declare const handleLapQueryDom: (req: Request, deps: ForwardDeps) => Promise<Response>;
|
|
22
|
+
export declare const handleLapDescribeVisible: (req: Request, deps: ForwardDeps) => Promise<Response>;
|
|
23
|
+
export declare const handleLapContext: (req: Request, deps: ForwardDeps) => Promise<Response>;
|
|
24
|
+
//# sourceMappingURL=forward.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forward.d.ts","sourceRoot":"","sources":["../../../src/server/lap/forward.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAClE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAGnD,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,EAAE,MAAM,GAAG,UAAU,CAAA;IAC/B,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,EAAE,iBAAiB,CAAA;IAC3B,SAAS,EAAE,SAAS,CAAA;IACpB,WAAW,EAAE,WAAW,CAAA;IACxB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB,CAAA;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,GAAG,IAAI,EAC3C,WAAW,GAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAc,IAElE,KAAK,OAAO,EAAE,MAAM,WAAW,KAAG,OAAO,CAAC,QAAQ,CAAC,CAoClE;AAUD,eAAO,MAAM,cAAc,QA9CN,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CAkDhE,CAAA;AAEF,eAAO,MAAM,gBAAgB,QApDR,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CAoDY,CAAA;AAE9E,eAAO,MAAM,iBAAiB,QAtDT,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CA0DhE,CAAA;AAEF,eAAO,MAAM,wBAAwB,QA5DhB,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CA4DgC,CAAA;AAElG,eAAO,MAAM,gBAAgB,QA9DR,OAAO,QAAQ,WAAW,KAAG,OAAO,CAAC,QAAQ,CA8DgB,CAAA"}
|