@threahq/bot-runtime-client 0.1.0
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 +99 -0
- package/archive-grace.d.ts +112 -0
- package/attachment-files.d.ts +15 -0
- package/crypto.d.ts +206 -0
- package/index.d.ts +14 -0
- package/index.js +2344 -0
- package/index.js.map +19 -0
- package/invocation-control.d.ts +128 -0
- package/keyring.d.ts +242 -0
- package/package.json +49 -0
- package/sealed-stream-client.d.ts +145 -0
- package/sealed.d.ts +305 -0
- package/transport-test-helpers.d.ts +53 -0
- package/transport.d.ts +132 -0
- package/types.d.ts +243 -0
- package/user-key.d.ts +53 -0
- package/ws-hint.d.ts +17 -0
package/index.js.map
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/transport.ts", "../src/sealed.ts", "../src/crypto.ts", "../src/ws-hint.ts", "../src/invocation-control.ts", "../src/archive-grace.ts", "../src/attachment-files.ts", "../src/sealed-stream-client.ts", "../src/keyring.ts", "../src/user-key.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"// Namespace import so tests can spyOn the `io` factory (INV-48) — the transport\n// is otherwise untestable without dialing a real Socket.IO server.\nimport * as socketIoClient from \"socket.io-client\"\nimport type { Socket } from \"socket.io-client\"\nimport { THREA_CALLBACK_TOKEN_HEADER, type SealedStepFrame } from \"./sealed\"\nimport {\n InvocationControlManager,\n parseCancellationReason,\n parseRevision,\n type ControlSyncResult,\n type InvocationControlState,\n type InvocationControlSyncRequest,\n type ObserveClaimParams,\n type ObservedClaimHandle,\n} from \"./invocation-control\"\nimport { buildBotSocketUrl, isObject, parseWsHint, type WsHint } from \"./ws-hint\"\nimport type {\n BotDecisionPayload,\n BotE2eGrantPayload,\n BotE2eRevokePayload,\n BotHelloBootstrap,\n BotRuntimeTransportCallbacks,\n DelegationAvailableNudge,\n BotRuntimeTransportOptions,\n BotWriteAck,\n StepFrame,\n} from \"./types\"\n\nconst DEFAULT_WS_ACK_TIMEOUT_MS = 5_000\nconst DEFAULT_RECONNECTION_DELAY_MAX_MS = 30_000\nconst DEFAULT_FETCH_TIMEOUT_MS = 30_000\nconst DEFAULT_STALE_SOCKET_REDIAL_MS = 3 * 60 * 1000\n\n/**\n * Owns the `/bot` WebSocket and the routing for a runtime's background writes.\n *\n * Why it exists: every `presence` / `renew` / `steps` POST goes through the edge\n * Worker (`app.threa.io/api/*`) and is billed as a request; the same frame over\n * the already-open socket (a direct CNAME to the regional backend) is free. A\n * single agentic turn can fire 150+ step writes, so moving them off HTTP is the\n * difference between an idle daemon costing nothing and one steadily burning the\n * Cloudflare quota.\n *\n * Routing rule for the three write methods: prefer the socket; if the server\n * acks (ok or a definitive failure) trust it; only a missing ack or a dead\n * socket triggers the HTTP fallback. Steps are best-effort; presence is\n * low-stakes.\n *\n * The transport owns ONLY these three ops + the socket. Durable, low-frequency\n * writes (claim/complete/fail/session) stay on each extension's own HTTP client.\n */\nexport class BotRuntimeTransport {\n private readonly base: string\n private readonly workspaceId: string\n private readonly apiKey: string\n private readonly hello: BotRuntimeTransportOptions[\"hello\"]\n private readonly beforeHello: BotRuntimeTransportOptions[\"beforeHello\"]\n private readonly callbacks: BotRuntimeTransportCallbacks\n private readonly wsAckTimeoutMs: number\n private readonly reconnectionDelayMaxMs: number\n private readonly fetchTimeoutMs: number\n private readonly staleSocketRedialMs: number\n private readonly logFn: (message: string) => void\n\n private socket: Socket | undefined\n private connected = false\n private helloReady = false\n private helloInFlight = false\n private connecting = false\n private stopped = false\n private redialTimer: ReturnType<typeof setTimeout> | undefined\n /** When the current outage started: set at attach and on disconnect, cleared on connect. */\n private disconnectedAt: number | undefined\n /** The cursor echoed by the last hello ack; re-sent on the next hello so the bootstrap only replays unseen events. */\n private cursor: string | undefined\n private readonly controls: InvocationControlManager\n\n constructor(opts: BotRuntimeTransportOptions) {\n this.base = opts.baseUrl.replace(/\\/$/, \"\")\n this.workspaceId = opts.workspaceId\n this.apiKey = opts.apiKey\n this.hello = opts.hello\n this.beforeHello = opts.beforeHello\n this.callbacks = opts.callbacks ?? {}\n this.wsAckTimeoutMs = opts.wsAckTimeoutMs ?? DEFAULT_WS_ACK_TIMEOUT_MS\n this.reconnectionDelayMaxMs = opts.reconnectionDelayMaxMs ?? DEFAULT_RECONNECTION_DELAY_MAX_MS\n this.fetchTimeoutMs = opts.fetchTimeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS\n this.staleSocketRedialMs = opts.staleSocketRedialMs ?? DEFAULT_STALE_SOCKET_REDIAL_MS\n this.logFn = opts.log ?? (() => {})\n this.controls = new InvocationControlManager(\n {\n sync: (request) => this.syncObservedClaim(request),\n socketReady: () => this.socketConnected,\n log: this.logFn,\n },\n {\n retryDelayMs: opts.controlRetryDelayMs,\n minRenewDelayMs: opts.controlMinRenewDelayMs,\n scheduler: opts.controlScheduler,\n }\n )\n }\n\n /** Whether the `/bot` socket is currently connected. */\n get socketConnected(): boolean {\n return this.connected && this.helloReady\n }\n\n // --- Socket lifecycle -----------------------------------------------------\n\n /**\n * Resolve the WS hint and open the socket (idempotent — a guard keeps the boot\n * call and the first poll tick from opening two). The hint resolve is the only\n * HTTP the transport does on the hot path; a failure leaves the socket closed\n * and the caller keeps polling/HTTP-writing until the next `connect()`.\n *\n * An existing-but-disconnected socket is normally left to Socket.IO's own\n * retry loop, EXCEPT when the outage has outlived `staleSocketRedialMs`: then\n * the socket is wedged in a state retries can't fix (a stale ws hint after the\n * backend moved, a dead retry loop) and the only cure is a teardown + fresh\n * dial. Without this, one wedged socket leaves the runtime on the fast HTTP\n * poll forever — the exact Cloudflare-quota burn the transport exists to avoid.\n */\n async connect(): Promise<void> {\n if (this.connecting || this.stopped) return\n if (this.socket) {\n if (this.connected) {\n if (!this.helloReady) this.sendHello()\n return\n }\n const outageMs = Date.now() - (this.disconnectedAt ?? Date.now())\n if (outageMs < this.staleSocketRedialMs) return\n this.logFn(`socket disconnected for ${Math.round(outageMs / 1000)}s; redialing from a fresh hint`)\n this.teardownSocket()\n }\n this.connecting = true\n try {\n let hint: WsHint | undefined\n try {\n hint = await this.resolveWsHint()\n } catch (error) {\n this.logFn(`ws hint resolve failed (staying on HTTP): ${summarize(error)}`)\n }\n if (hint) this.attachSocket(hint)\n } finally {\n this.connecting = false\n }\n }\n\n private attachSocket(hint: WsHint): void {\n if (this.socket || this.stopped) return\n let socket: Socket\n try {\n socket = socketIoClient.io(buildBotSocketUrl(hint), {\n path: hint.path,\n auth: { token: this.apiKey },\n transports: [\"websocket\"],\n reconnection: true,\n reconnectionDelayMax: this.reconnectionDelayMaxMs,\n })\n } catch (error) {\n this.logFn(`socket attach failed (HTTP only): ${summarize(error)}`)\n return\n }\n this.socket = socket\n this.disconnectedAt = Date.now()\n socket.on(\"connect\", () => {\n this.connected = true\n this.helloReady = false\n this.disconnectedAt = undefined\n this.sendHello()\n })\n socket.on(\"disconnect\", (reason: string) => {\n const wasReady = this.socketConnected\n this.connected = false\n this.helloReady = false\n this.helloInFlight = false\n this.disconnectedAt ??= Date.now()\n if (wasReady) this.callbacks.onDisconnected?.()\n this.controls.wake()\n // Socket.IO's auto-reconnect covers every disconnect reason EXCEPT a\n // server-initiated one (deploy drain, kick) — there the client stays down\n // until someone calls connect(). Redial immediately; a flap lands on the\n // normal reconnection backoff.\n if (reason === \"io server disconnect\") socket.connect()\n })\n socket.on(\"connect_error\", (error: unknown) => {\n const wasReady = this.socketConnected\n this.connected = false\n this.helloReady = false\n this.helloInFlight = false\n this.disconnectedAt ??= Date.now()\n if (wasReady) this.callbacks.onDisconnected?.()\n this.controls.wake()\n this.logFn(`socket connect_error: ${summarize(error)}`)\n })\n socket.on(\"bot_invocation:available\", () =>\n this.controls.enqueueAdapter(() => this.callbacks.onInvocationAvailable?.())\n )\n socket.on(\"bot_invocation:input_updated\", (payload: unknown) => this.controls.hint(payload, false))\n socket.on(\"bot_invocation:cancelled\", (payload: unknown) => this.controls.hint(payload, true))\n socket.on(\"delegation:available\", (payload: unknown) =>\n this.callbacks.onDelegationAvailable?.(payload as DelegationAvailableNudge)\n )\n socket.on(\"bot_invocation:claimed\", (payload: unknown) => this.callbacks.onInvocationClaimed?.(payload))\n socket.on(\"bot:active_actor_changed\", (payload: unknown) => this.callbacks.onActiveActorChanged?.(payload))\n socket.on(\"bot:session_archived\", (payload: unknown) => this.callbacks.onSessionArchived?.(payload))\n socket.on(\"bot:session_restored\", (payload: unknown) => this.callbacks.onSessionRestored?.(payload))\n socket.on(\"decision:resolved\", (payload: unknown) =>\n this.callbacks.onDecisionResolved?.(payload as BotDecisionPayload)\n )\n socket.on(\"decision:cancelled\", (payload: unknown) =>\n this.callbacks.onDecisionCancelled?.(payload as BotDecisionPayload)\n )\n socket.on(\"bot:e2e_grant\", (payload: unknown) => this.callbacks.onE2eGrant?.(payload as BotE2eGrantPayload))\n socket.on(\"bot:e2e_revoke\", (payload: unknown) => this.callbacks.onE2eRevoke?.(payload as BotE2eRevokePayload))\n socket.on(\"bot:resync\", () => {\n this.callbacks.onResync?.()\n this.teardownSocket()\n void this.connect()\n })\n }\n\n /** (Re)announce this instance + capabilities and pull the bootstrap snapshot. */\n sendHello(): void {\n const socket = this.socket\n if (!socket || this.helloInFlight) return\n this.helloInFlight = true\n this.beforeHello?.(this.hello)\n socket\n .timeout(this.wsAckTimeoutMs)\n .emit(\n \"bot:hello\",\n { ...this.hello, ...(this.cursor ? { sinceCursor: this.cursor } : {}) },\n (error: unknown, ack: unknown) => {\n if (socket !== this.socket) return\n this.helloInFlight = false\n if (error || !isObject(ack) || ack.ok !== true) {\n this.logFn(`bot:hello rejected: ${error ? summarize(error) : isObject(ack) ? String(ack.error) : \"no ack\"}`)\n this.teardownSocket()\n this.scheduleRedial()\n return\n }\n this.helloReady = true\n if (typeof ack.serverGeneratedAt === \"string\") this.cursor = ack.serverGeneratedAt\n const bootstrap: BotHelloBootstrap = {\n serverGeneratedAt: typeof ack.serverGeneratedAt === \"string\" ? ack.serverGeneratedAt : undefined,\n ...(typeof ack.botId === \"string\" ? { botId: ack.botId } : {}),\n availableInvocations: Array.isArray(ack.availableInvocations) ? ack.availableInvocations : [],\n ownedClaims: Array.isArray(ack.ownedClaims) ? ack.ownedClaims : [],\n e2eGrantedStreamIds: Array.isArray(ack.e2eGrantedStreamIds)\n ? ack.e2eGrantedStreamIds.filter((id): id is string => typeof id === \"string\")\n : [],\n }\n void this.controls.bootstrap(Array.isArray(ack.recentCancellations) ? ack.recentCancellations : [], () =>\n this.callbacks.onBootstrap?.(bootstrap)\n )\n }\n )\n }\n\n /** Tear the socket down (idempotent). After this the transport is HTTP-only and won't reconnect. */\n disconnect(): void {\n this.stopped = true\n if (this.redialTimer) clearTimeout(this.redialTimer)\n this.redialTimer = undefined\n this.controls.stop()\n this.teardownSocket()\n }\n\n private scheduleRedial(): void {\n if (this.stopped || this.redialTimer) return\n this.redialTimer = setTimeout(() => {\n this.redialTimer = undefined\n void this.connect()\n }, this.reconnectionDelayMaxMs)\n }\n\n /** Drop the current socket without stopping the transport — the next `connect()` dials fresh. */\n private teardownSocket(): void {\n const wasReady = this.socketConnected\n this.connected = false\n this.helloReady = false\n this.helloInFlight = false\n this.disconnectedAt = undefined\n if (wasReady) this.callbacks.onDisconnected?.()\n const socket = this.socket\n this.socket = undefined\n if (socket) {\n try {\n socket.removeAllListeners()\n socket.disconnect()\n } catch {\n // already closed\n }\n }\n }\n\n /** Own renewal and control synchronization for a claimed invocation. */\n observeClaim(params: ObserveClaimParams): ObservedClaimHandle {\n return this.controls.observe(params)\n }\n\n // --- Routed background writes (WS-first, HTTP fallback) --------------------\n\n /**\n * Record one or more trace steps. Best-effort: a turn's narration is nice to\n * have, not load-bearing, so a failure is logged and dropped rather than\n * surfaced. This is the high-volume op the whole exercise targets.\n */\n async recordSteps(\n invocationId: string,\n claimToken: string,\n steps: StepFrame[],\n statusText?: string,\n // The instance that holds the claim. Defaults to the hello instance; the\n // override exists for runtimes (pi-remote) whose claim instance can differ\n // from the session instance the transport registered with.\n instanceId: string = this.hello.instanceId\n ): Promise<void> {\n if (steps.length === 0) return\n // Stamp each frame with an idempotency key (shared across the WS frame and the\n // HTTP fallback) so the server can never persist the same step twice.\n const keyed = steps.map((step) => ({ ...step, clientStepId: step.clientStepId ?? crypto.randomUUID() }))\n const { sent, ack } = await this.emitWrite(\"bot:invocation:steps\", {\n invocationId,\n instanceId,\n claimToken,\n steps: keyed,\n ...(statusText ? { statusText } : {}),\n })\n if (ack) {\n if (!ack.ok) this.logFn(`steps rejected (${ack.code ?? \"?\"}): ${ack.message ?? \"\"}`)\n return\n }\n if (sent) {\n // The frame is in flight; the ack just didn't arrive in time. Steps are\n // best-effort, so rather than re-POST (an edge request we're avoiding,\n // dedup'd server-side or not) we drop and trust the in-flight frame.\n this.logFn(\"steps ack timed out; relying on the in-flight frame (no HTTP retry)\")\n return\n }\n // Socket was down — the frame never left, so HTTP is the only path. The\n // idempotency key still guards against a late WS delivery racing this POST.\n await this.httpRecordStepsFallback(invocationId, claimToken, keyed, statusText, instanceId)\n }\n\n /**\n * Record one or more SEALED trace steps for an E2E turn. Same routing and\n * best-effort semantics as {@link recordSteps} — WS frame first, per-step HTTP\n * fallback — but the frames carry ciphertext + envelope instead of plaintext\n * content, and auth is the per-claim callback token (model A), not\n * `instanceId`/`claimToken`. `stepId` is the idempotency key: the server\n * finalizes/upserts by it, so a duplicate delivery can't double-persist.\n */\n async recordSealedSteps(invocationId: string, callbackToken: string, steps: SealedStepFrame[]): Promise<void> {\n if (steps.length === 0) return\n const { sent, ack } = await this.emitWrite(\"bot:invocation:sealed-steps\", {\n invocationId,\n callbackToken,\n steps,\n })\n if (ack) {\n if (!ack.ok) this.logFn(`sealed steps rejected (${ack.code ?? \"?\"}): ${ack.message ?? \"\"}`)\n return\n }\n if (sent) {\n this.logFn(\"sealed steps ack timed out; relying on the in-flight frame (no HTTP retry)\")\n return\n }\n await this.httpRecordSealedStepsFallback(invocationId, callbackToken, steps)\n }\n\n /**\n * Renew a claim's lease. Correctness-critical, so the HTTP fallback is\n * mandatory: a missing ack, a dead socket, or any non-`NOT_FOUND` server error\n * all retry over HTTP. Returns `{ notFound: true }` when the claim is gone\n * (the caller should drop it); the caller never lets it silently lapse.\n * `renewed` is true only when the server confirmed the extension — a caller\n * with side effects can stop work once a lease has gone unconfirmed for a\n * full TTL rather than run on after another runtime may have claimed it.\n */\n async renewClaim(\n invocationId: string,\n claimToken: string,\n claimTtlSeconds: number,\n instanceId: string = this.hello.instanceId\n ): Promise<{ notFound: boolean; renewed: boolean }> {\n const { ack } = await this.emitWrite(\"bot:invocation:renew\", {\n invocationId,\n instanceId,\n claimToken,\n claimTtlSeconds,\n })\n if (ack) {\n if (ack.ok) return { notFound: false, renewed: true }\n if (ack.code === \"NOT_FOUND\") return { notFound: true, renewed: false }\n this.logFn(`renew rejected (${ack.code ?? \"?\"}); retrying over HTTP`)\n }\n // Renew is an idempotent CAS (re-setting claim_expires_at is harmless), so —\n // unlike steps — we retry over HTTP on ANY missing ack (not sent OR timed\n // out). The lease must not lapse because the socket flapped; a redundant\n // renew when the WS frame also lands just re-sets the same expiry.\n return this.httpRenewFallback(invocationId, claimToken, claimTtlSeconds, instanceId)\n }\n\n /**\n * Push a presence update. Low-stakes (the socket connection itself is the\n * primary liveness signal); if the socket can't ack it, fall back to HTTP so\n * the row still lands. `body` is the full presence body, identical to the HTTP\n * `/bot-runtime/presence` payload.\n */\n async updatePresence(body: Record<string, unknown>): Promise<void> {\n const { ack } = await this.emitWrite(\"bot:presence:update\", body)\n if (ack) {\n if (!ack.ok) this.logFn(`presence rejected (${ack.code ?? \"?\"}): ${ack.message ?? \"\"}`)\n return\n }\n // Idempotent upsert on (workspace, bot, instance) — safe to retry over HTTP on\n // any missing ack; a redundant upsert when the WS frame lands is last-writer-wins.\n await this.httpPresenceFallback(body)\n }\n\n // --- Socket write primitive -----------------------------------------------\n\n /**\n * Emit a write event and await its ack.\n *\n * `sent` distinguishes the two failure modes that look identical at the ack\n * layer but must NOT be handled the same way: `sent: false` means the frame\n * never left (no live socket / `emit` threw), so an HTTP retry is the only way\n * the write lands and is safe; `sent: true, ack: null` means the frame IS in\n * flight but the server didn't ack within the timeout. Steps carry a\n * `client_step_id` so a re-POST would dedup rather than duplicate, but it would\n * still bill an edge request the WS path exists to avoid — so a best-effort\n * caller drops on `sent` instead of retrying. Idempotent writes (renew CAS,\n * presence upsert) ignore the distinction and retry on either.\n */\n private emitWrite(\n event: string,\n payload: unknown,\n signal?: AbortSignal,\n timeoutMs = this.wsAckTimeoutMs\n ): Promise<{ sent: boolean; ack: BotWriteAck | null; aborted?: boolean }> {\n const socket = this.socket\n if (signal?.aborted) return Promise.resolve({ sent: false, ack: null, aborted: true })\n if (!socket || !this.connected || !this.helloReady) return Promise.resolve({ sent: false, ack: null })\n return new Promise((resolve) => {\n let settled = false\n const onAbort = () => done({ sent: true, ack: null, aborted: true })\n const done = (result: { sent: boolean; ack: BotWriteAck | null; aborted?: boolean }) => {\n if (settled) return\n settled = true\n signal?.removeEventListener(\"abort\", onAbort)\n resolve(result)\n }\n signal?.addEventListener(\"abort\", onAbort, { once: true })\n try {\n socket.timeout(timeoutMs).emit(event, payload, (err: unknown, ack: unknown) => {\n // Either way the frame was sent; only the ack is in question.\n done({ sent: true, ack: err ? null : normalizeAck(ack) })\n })\n } catch (error) {\n this.logFn(`socket emit ${event} threw: ${summarize(error)}`)\n done({ sent: false, ack: null })\n }\n })\n }\n\n // --- HTTP fallback --------------------------------------------------------\n\n /** The wsUrl hint is served by the edge workspace-router at `/api/workspaces/:id/config` (NOT /api/v1). */\n async resolveWsHint(): Promise<WsHint | undefined> {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), this.fetchTimeoutMs)\n try {\n // Headers AND body under one abort window. `httpRequest` clears its timer\n // once headers arrive, so a response whose body then stalls left\n // `res.json()` awaiting forever — with `connecting` latched true, no later\n // call could ever redial, and the runtime sat \"linked\" but unreachable.\n const res = await fetch(`${this.base}/api/workspaces/${this.workspaceId}/config`, {\n method: \"GET\",\n signal: controller.signal,\n headers: { Authorization: `Bearer ${this.apiKey}`, \"Content-Type\": \"application/json\" },\n })\n if (!res.ok) return undefined\n const body = (await res.json()) as { wsUrl?: string }\n return parseWsHint({ url: body.wsUrl })\n } finally {\n clearTimeout(timeout)\n }\n }\n\n private async httpRecordStepsFallback(\n invocationId: string,\n claimToken: string,\n steps: StepFrame[],\n statusText: string | undefined,\n instanceId: string\n ): Promise<void> {\n // The HTTP /steps endpoint takes one step per request, so a batched WS frame\n // unrolls into N posts here. Best-effort: swallow per-step failures.\n for (const step of steps) {\n try {\n await this.httpRequest(this.v1Path(`/bot-invocations/${invocationId}/steps`), {\n method: \"POST\",\n body: JSON.stringify({\n instanceId,\n claimToken,\n stepType: step.stepType,\n content: step.content,\n ...(step.clientStepId ? { clientStepId: step.clientStepId } : {}),\n ...(step.phase ? { phase: step.phase } : {}),\n ...(step.durationMs !== undefined ? { durationMs: step.durationMs } : {}),\n ...(statusText ? { statusText } : {}),\n }),\n })\n } catch (error) {\n this.logFn(`step HTTP fallback failed: ${summarize(error)}`)\n }\n }\n }\n\n private async httpRecordSealedStepsFallback(\n invocationId: string,\n callbackToken: string,\n steps: SealedStepFrame[]\n ): Promise<void> {\n // The HTTP /sealed-steps endpoint takes one step per request, so a batched\n // WS frame unrolls into N posts here. Best-effort: swallow per-step failures.\n for (const step of steps) {\n try {\n await this.httpRequest(this.v1Path(`/bot-invocations/${invocationId}/sealed-steps`), {\n method: \"POST\",\n headers: { [THREA_CALLBACK_TOKEN_HEADER]: callbackToken },\n body: JSON.stringify(step),\n })\n } catch (error) {\n this.logFn(`sealed step HTTP fallback failed: ${summarize(error)}`)\n }\n }\n }\n\n private async httpRenewFallback(\n invocationId: string,\n claimToken: string,\n claimTtlSeconds: number,\n instanceId: string\n ): Promise<{ notFound: boolean; renewed: boolean }> {\n try {\n const res = await this.httpRequest(this.v1Path(`/bot-invocations/${invocationId}/renew`), {\n method: \"POST\",\n body: JSON.stringify({ instanceId, claimToken, claimTtlSeconds }),\n })\n if (res.status === 404) return { notFound: true, renewed: false }\n if (!res.ok) this.logFn(`renew HTTP fallback ${res.status}`)\n return { notFound: false, renewed: res.ok }\n } catch (error) {\n this.logFn(`renew HTTP fallback failed: ${summarize(error)}`)\n return { notFound: false, renewed: false }\n }\n }\n\n private async syncObservedClaim(request: InvocationControlSyncRequest): Promise<ControlSyncResult> {\n const payload = {\n invocationId: request.invocationId,\n instanceId: request.instanceId ?? this.hello.instanceId,\n claimToken: request.claimToken,\n claimTtlSeconds: request.claimTtlSeconds,\n knownSourceRevision: request.knownSourceRevision,\n ...(request.restartRequiredRevision === undefined\n ? {}\n : { restartRequiredRevision: request.restartRequiredRevision }),\n }\n const ws = await this.emitWrite(\n \"bot:invocation:renew\",\n payload,\n request.signal,\n Math.min(this.wsAckTimeoutMs, request.ackTimeoutMs)\n )\n if (request.signal.aborted || ws.aborted) return { kind: \"aborted\" }\n if (ws.ack?.ok) {\n const parsed = parseControlState(ws.ack.data, request.invocationId, request.minimumSourceRevision)\n if (parsed) return { kind: \"control\", state: parsed }\n this.logFn(`control state rejected over WS (${request.invocationId}); retrying over HTTP`)\n } else if (ws.ack?.code === \"NOT_FOUND\") {\n return { kind: \"not_found\" }\n } else if (ws.ack) {\n this.logFn(`control renew rejected over WS (${ws.ack.code ?? \"?\"}); retrying over HTTP`)\n }\n if (request.signal.aborted) return { kind: \"aborted\" }\n try {\n const res = await this.httpRequest(\n this.v1Path(`/bot-invocations/${request.invocationId}/renew`),\n {\n method: \"POST\",\n body: JSON.stringify({\n instanceId: payload.instanceId,\n claimToken: payload.claimToken,\n claimTtlSeconds: payload.claimTtlSeconds,\n knownSourceRevision: request.knownSourceRevision,\n ...(request.restartRequiredRevision === undefined\n ? {}\n : { restartRequiredRevision: request.restartRequiredRevision }),\n }),\n },\n request.signal\n )\n if (request.signal.aborted) return { kind: \"aborted\" }\n if (res.status === 404) return { kind: \"not_found\" }\n if (!res.ok) return { kind: \"retry\" }\n const body = (await res.json()) as unknown\n const parsed = isObject(body)\n ? parseControlState(body.data, request.invocationId, request.minimumSourceRevision)\n : undefined\n return parsed ? { kind: \"control\", state: parsed } : { kind: \"retry\" }\n } catch {\n return request.signal.aborted ? { kind: \"aborted\" } : { kind: \"retry\" }\n }\n }\n\n private async httpPresenceFallback(body: Record<string, unknown>): Promise<void> {\n try {\n await this.httpRequest(this.v1Path(\"/bot-runtime/presence\"), { method: \"POST\", body: JSON.stringify(body) })\n } catch (error) {\n this.logFn(`presence HTTP fallback failed: ${summarize(error)}`)\n }\n }\n\n private v1Path(suffix: string): string {\n return `/api/v1/workspaces/${this.workspaceId}${suffix}`\n }\n\n private async httpRequest(path: string, init: RequestInit, callerSignal?: AbortSignal): Promise<Response> {\n const controller = new AbortController()\n const onCallerAbort = () => controller.abort()\n callerSignal?.addEventListener(\"abort\", onCallerAbort, { once: true })\n if (callerSignal?.aborted) controller.abort()\n const timeout = setTimeout(() => controller.abort(), this.fetchTimeoutMs)\n try {\n return await fetch(`${this.base}${path}`, {\n ...init,\n signal: controller.signal,\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n ...init.headers,\n },\n })\n } finally {\n clearTimeout(timeout)\n callerSignal?.removeEventListener(\"abort\", onCallerAbort)\n }\n }\n}\n\nfunction parseControlState(\n value: unknown,\n expectedInvocationId: string,\n minimumSourceRevision: number\n): InvocationControlState | undefined {\n if (!isObject(value) || value.invocationId !== expectedInvocationId) return\n const sourceRevision = parseRevision(value.sourceRevision)\n if (sourceRevision === undefined) return\n if (value.status === \"active\" && typeof value.claimExpiresAt === \"string\") {\n if (!Number.isFinite(Date.parse(value.claimExpiresAt))) return\n if (value.update !== undefined) {\n if (!isObject(value.update) || parseRevision(value.update.sourceRevision) !== sourceRevision) return\n }\n return {\n invocationId: expectedInvocationId,\n status: \"active\",\n claimExpiresAt: value.claimExpiresAt,\n sourceRevision,\n ...(value.update === undefined ? {} : { update: value.update }),\n }\n }\n const reason = parseCancellationReason(value.reason)\n if (\n value.status === \"cancelled\" &&\n value.claimExpiresAt === null &&\n sourceRevision >= minimumSourceRevision &&\n reason\n ) {\n return { invocationId: expectedInvocationId, status: \"cancelled\", claimExpiresAt: null, sourceRevision, reason }\n }\n return\n}\n\nfunction normalizeAck(ack: unknown): BotWriteAck | null {\n if (!isObject(ack) || typeof ack.ok !== \"boolean\") return null\n return {\n ok: ack.ok,\n data: isObject(ack.data) ? ack.data : undefined,\n code: typeof ack.code === \"string\" ? ack.code : undefined,\n message: typeof ack.message === \"string\" ? ack.message : undefined,\n }\n}\n\nfunction summarize(error: unknown): string {\n return (error instanceof Error ? error.message : String(error)).slice(0, 200)\n}\n",
|
|
6
|
+
"/**\n * Sealed (E2EE) turn support for bot-runtime harnesses.\n *\n * A harness that serves an end-to-end-encrypted scratchpad holds a keyring of\n * BIKs (Bot Identity Keys): X25519 keypairs the owner wraps the stream's\n * symmetric key (SSK) to. One key per host is the default, so every runtime on\n * a box shares it; a key can also be pinned to a single stream. On a winning\n * claim the backend hands the harness a\n * `sealedContext` — SSK wraps addressed to one of its keys plus the sealed\n * trigger and history ciphertext — and the harness seals every reply and trace step back\n * under the same SSK. The server never sees plaintext (INV-E7); the owner's\n * client opens the harness's output exactly as it opens the enclave's.\n *\n * This module is pure crypto + a small keystore; it does no HTTP. Transport\n * routing lives in `BotRuntimeTransport` (sealed steps) and each harness's own\n * HTTP client (sealed complete / interim messages, low-frequency writes).\n */\n\nimport { ulid } from \"ulid\"\nimport type { E2eKeyRecord, E2eKeyring } from \"./keyring\"\nimport {\n base64ToBytes,\n buildDecisionAad,\n buildDecisionNoteAad,\n buildMessageAad,\n buildWrapAad,\n bytesToBase64,\n exportPrivateKey,\n exportPublicKey,\n generateKeyPair,\n generateStreamKey,\n importRecipientPrivateKey,\n importRecipientPublicKey,\n openMessageAsString,\n parseSealedPayload,\n sealMessage,\n serializeSealedPayload,\n unwrapStreamKey,\n wrapStreamKey,\n type WebCryptoKey,\n type AttachmentRef,\n type SealedPayloadExtras,\n type StreamEnvelope,\n} from \"./crypto\"\n\n/**\n * Per-claim secret (model A) the backend hands a sealed turn in `sealedContext`;\n * echoed on every sealed callback so the backend can bind it to that session.\n */\nexport const THREA_CALLBACK_TOKEN_HEADER = \"X-Threa-Callback-Token\"\n\n/** This install's registered Bot Identity Key — held in memory; the private key never re-exports once loaded. */\nexport interface BotIdentityKey {\n publicKeyId: string\n publicKeyBase64: string\n privateKey: WebCryptoKey\n}\n\n/** One SSK wrap addressed to this bot's BIK (wire shape from the claim's `sealedContext`). */\nexport interface SealedSskWrap {\n keyGeneration: number\n wrapEnc: string\n wrapCt: string\n}\n\n/** One SSK-sealed message: base64 ciphertext + its envelope (wire shape). */\nexport interface SealedMessageWire {\n ciphertext: string\n envelope: StreamEnvelope\n}\n\n/**\n * The sealed work handed to an owner-granted external bot on a winning claim\n * when the delivery verdict is `sealed`. Mirrors `@threahq/types`' `SealedTurnContext`\n * (which standalone extensions can't import). The backend never decrypts: it\n * ships ciphertext + SSK wraps addressed to this bot's BIK; the bot unwraps\n * with its identity private key, opens history/prompt, runs its turn, and seals\n * each reply/step back under the same SSK.\n */\nexport interface SealedTurnContext {\n callbackToken: string\n wraps: SealedSskWrap[]\n history: (SealedMessageWire & { role: \"user\" | \"assistant\"; sequence: string })[]\n prompt: SealedMessageWire\n reply: { keyGeneration: number; senderId: string }\n trigger?: { messageId: string; authorName: string; authorType: string; createdAt: string }\n}\n\n/** Everything a sealed turn needs to seal its replies/steps back under the stream key. */\nexport interface SealingState {\n /** E2E root stream id — bound into every wrap/message/step AAD. */\n streamId: string\n replyKeyGeneration: number\n replySenderId: string\n /** The recovered SSK for `replyKeyGeneration`; replies and steps seal under it. */\n replySsk: Uint8Array\n callbackToken: string\n}\n\n/** One decrypted prior message, oldest→newest. Formatting into a prompt is the harness's job. */\nexport interface DecryptedHistoryItem {\n role: \"user\" | \"assistant\"\n sequence: string\n contentMarkdown: string\n /** Per-file keys for the message's E2E attachments — download + decrypt is the harness's job. */\n attachmentRefs: AttachmentRef[]\n}\n\nexport interface OpenedSealedTurn {\n promptMarkdown: string\n /** Refs sealed into the trigger message's payload (the files attached to the request itself). */\n promptAttachmentRefs: AttachmentRef[]\n history: DecryptedHistoryItem[]\n sealing: SealingState\n}\n\n/** The body of a sealed reply or interim message: `msg_…` id in clear, content sealed. */\nexport interface SealedReplyBody {\n messageId: string\n ciphertext: string\n envelope: StreamEnvelope\n}\n\n/** One sealed trace step (the `/sealed-steps` wire shape; `stepId` keys the row, content is ciphertext). */\nexport interface SealedStepFrame {\n stepId: string\n stepType: string\n messageId?: string\n ciphertext: string\n envelope: StreamEnvelope\n durationMs?: number\n}\n\n// ── keyring ───────────────────────────────────────────────────────────────────\n\n/** Mint a fresh identity key record: a `bik_…` id and an X25519 keypair, base64. */\nexport async function mintE2eKeyRecord(): Promise<E2eKeyRecord> {\n const keyPair = await generateKeyPair()\n return {\n keyId: `bik_${ulid()}`,\n publicKey: bytesToBase64(await exportPublicKey(keyPair.publicKey)),\n privateKey: bytesToBase64(await exportPrivateKey(keyPair.privateKey)),\n }\n}\n\n/**\n * This install's identity keys, ready to open sealed turns. The records live in\n * an {@link E2eKeyring} (keychain or file); this adds the WebCrypto import and\n * caches the result for the process.\n *\n * The public halves must ride EVERY `bot:hello` and presence update: the\n * server reads an advertised keyring as the instance's complete set, so a\n * heartbeat that omits it unregisters every key and breaks sealed-claim wrap\n * coverage.\n */\nexport class BotKeyring {\n private readonly buildRecords: () => E2eKeyring\n private readonly log: (message: string) => void\n private records: E2eKeyring | undefined\n private cached: BotIdentityKey[] = []\n private queue: Promise<BotIdentityKey[]> = Promise.resolve([])\n private loaded = false\n\n constructor(opts: { keyring: () => E2eKeyring; log?: (message: string) => void }) {\n this.buildRecords = opts.keyring\n this.log = opts.log ?? ((message) => console.error(message))\n }\n\n /** The loaded keys, if `ensure()` has resolved. */\n get identities(): BotIdentityKey[] {\n return this.cached\n }\n\n /**\n * Load or create this install's keys, caching them for the process. Returns\n * an empty keyring when the store or WebCrypto fails, logged loudly: the\n * harness then serves plaintext streams only, rather than sealed turns\n * becoming unservable with no clue why. Nothing is downgraded by that — a\n * runtime with no registered key cannot claim a sealed stream at all.\n */\n async ensure(): Promise<BotIdentityKey[]> {\n if (this.loaded) return this.cached\n return this.enqueue((keyring) => keyring.ensure())\n }\n\n /**\n * The key this install reads `streamId` with, imported alongside the rest.\n * Under the default policy one key already covers every stream and this is\n * the plain `ensure()`; under the per-stream policy it mints this stream's\n * key, so presence can advertise it before the owner re-wraps.\n */\n async ensureForStream(streamId: string): Promise<BotIdentityKey[]> {\n return this.enqueue((keyring) => keyring.ensureForStream(streamId))\n }\n\n /**\n * The identity a wrap for `streamId` must be addressed to, minting it first\n * if this install does not hold it yet. Undefined when no key could be\n * created at all, which is the one case a caller must treat as \"sealed is\n * unavailable here\" rather than falling back to another key.\n */\n async identityForStream(streamId: string): Promise<BotIdentityKey | undefined> {\n const identities = await this.ensureForStream(streamId)\n const record = this.records?.forStream(streamId)\n return record ? identities.find((identity) => identity.publicKeyId === record.keyId) : undefined\n }\n\n /**\n * Forget the key held for a stream this bot was just revoked from, so the\n * next presence write stops advertising it. A no-op under every policy but\n * per-stream, where the key exists for that one scratchpad and nothing else.\n */\n async dropStream(streamId: string): Promise<BotIdentityKey[]> {\n return this.enqueue(async (keyring) => keyring.dropStream(streamId))\n }\n\n /** The fields to spread into every `bot:hello` and presence body. Empty until `ensure()` resolves. */\n presenceFields(): ReturnType<E2eKeyring[\"presenceFields\"]> {\n return this.records?.presenceFields() ?? {}\n }\n\n /**\n * One key operation at a time. Boot presence, `bot:hello` and a grant can all\n * land together, and each rebuilds the imported set from the keyring's whole\n * record list — interleaved, the slower one would publish a set missing the\n * key the other had just added.\n */\n private enqueue(work: (keyring: E2eKeyring) => Promise<E2eKeyRecord[]>): Promise<BotIdentityKey[]> {\n const next = this.queue.then(async () => {\n try {\n this.records ??= this.buildRecords()\n this.cached = await importAll(await work(this.records))\n this.loaded = this.cached.length > 0\n } catch (error) {\n this.log(`Threa sealed: key load/create failed; sealed scratchpads are unavailable: ${String(error)}`)\n }\n return this.cached\n })\n this.queue = next\n return next\n }\n}\n\nasync function importAll(records: E2eKeyRecord[]): Promise<BotIdentityKey[]> {\n const identities: BotIdentityKey[] = []\n for (const record of records) {\n identities.push({\n publicKeyId: record.keyId,\n publicKeyBase64: record.publicKey,\n privateKey: await importRecipientPrivateKey(base64ToBytes(record.privateKey)),\n })\n }\n return identities\n}\n\n// ── sealed claim wire validation ─────────────────────────────────────────────\n\nfunction isEnvelope(value: unknown): value is StreamEnvelope {\n if (typeof value !== \"object\" || value === null) return false\n const v = value as Record<string, unknown>\n return (\n typeof v.v === \"number\" &&\n typeof v.keyGeneration === \"number\" &&\n typeof v.iv === \"string\" &&\n typeof v.aad === \"string\"\n )\n}\n\nfunction isSealedMessage(value: unknown): value is SealedMessageWire {\n if (typeof value !== \"object\" || value === null) return false\n const m = value as Record<string, unknown>\n return typeof m.ciphertext === \"string\" && isEnvelope(m.envelope)\n}\n\n/**\n * Validate a claim response's `sealedContext` field. The claim body is untyped\n * JSON at the harness boundary; a malformed context returns `undefined` so the\n * caller can fail the invocation loudly instead of crashing mid-hydration.\n */\nexport function parseSealedTurnContext(raw: unknown): SealedTurnContext | undefined {\n if (typeof raw !== \"object\" || raw === null) return undefined\n const c = raw as Record<string, unknown>\n if (typeof c.callbackToken !== \"string\" || c.callbackToken.length === 0) return undefined\n if (!Array.isArray(c.wraps)) return undefined\n const wraps: SealedSskWrap[] = []\n for (const wrap of c.wraps) {\n if (typeof wrap !== \"object\" || wrap === null) return undefined\n const w = wrap as Record<string, unknown>\n if (typeof w.keyGeneration !== \"number\" || typeof w.wrapEnc !== \"string\" || typeof w.wrapCt !== \"string\") {\n return undefined\n }\n wraps.push({ keyGeneration: w.keyGeneration, wrapEnc: w.wrapEnc, wrapCt: w.wrapCt })\n }\n if (!isSealedMessage(c.prompt)) return undefined\n const reply = c.reply as Record<string, unknown> | undefined\n if (!reply || typeof reply.keyGeneration !== \"number\" || typeof reply.senderId !== \"string\") return undefined\n const history: SealedTurnContext[\"history\"] = []\n if (c.history !== undefined) {\n if (!Array.isArray(c.history)) return undefined\n for (const item of c.history) {\n if (!isSealedMessage(item)) return undefined\n const h = item as unknown as Record<string, unknown>\n const role = h.role === \"assistant\" ? \"assistant\" : \"user\"\n history.push({\n ciphertext: (item as SealedMessageWire).ciphertext,\n envelope: (item as SealedMessageWire).envelope,\n role,\n sequence: typeof h.sequence === \"string\" ? h.sequence : \"0\",\n })\n }\n }\n const trigger = c.trigger as Record<string, unknown> | undefined\n return {\n callbackToken: c.callbackToken,\n wraps,\n history,\n prompt: c.prompt,\n reply: { keyGeneration: reply.keyGeneration, senderId: reply.senderId },\n ...(trigger &&\n typeof trigger.messageId === \"string\" &&\n typeof trigger.authorName === \"string\" &&\n typeof trigger.authorType === \"string\" &&\n typeof trigger.createdAt === \"string\"\n ? {\n trigger: {\n messageId: trigger.messageId,\n authorName: trigger.authorName,\n authorType: trigger.authorType,\n createdAt: trigger.createdAt,\n },\n }\n : {}),\n }\n}\n\n// ── sealed turn crypto (pure; no module state or I/O) ─────────────────────────\n\n/**\n * Recover one wrap's stream key with whichever of this runtime's keys it was\n * addressed to. The wire wraps carry no recipient id, so the holder of a\n * keyring has to try: the wrap AAD binds the key id, so every key but the right\n * one fails to authenticate. A wrap nothing opens is a generation this runtime\n * was not invited to, which is why the miss is `undefined` and not a throw.\n */\nasync function unwrapWithAny(params: {\n wrap: SealedSskWrap\n identities: BotIdentityKey[]\n streamId: string\n}): Promise<Uint8Array | undefined> {\n const { wrap, identities, streamId } = params\n for (const identity of identities) {\n try {\n return await unwrapStreamKey({\n enc: base64ToBytes(wrap.wrapEnc),\n ct: base64ToBytes(wrap.wrapCt),\n recipientPrivateKey: identity.privateKey,\n aad: buildWrapAad({ streamId, keyGeneration: wrap.keyGeneration, recipientKeyId: identity.publicKeyId }),\n })\n } catch {\n continue\n }\n }\n return undefined\n}\n\n/**\n * Open a sealed claim with this bot's keyring: recover the SSK for every generation\n * the backend wrapped to us (AAD-bound to our key id), open the trigger + prior\n * history, and return the decrypted prompt plus the {@link SealingState} the turn\n * seals replies/steps with. `streamId` is the E2E root stream — wraps and the\n * owner's message AAD both bind to it. A wrap or history row we can't open is\n * skipped (a generation predating our invite), never fatal; a missing reply or\n * prompt key is fatal (the turn can't be served).\n */\nexport async function openSealedTurnContext(params: {\n sealed: SealedTurnContext\n identities: BotIdentityKey[]\n streamId: string\n}): Promise<OpenedSealedTurn> {\n const { sealed, identities, streamId } = params\n const sskByGeneration = new Map<number, Uint8Array>()\n for (const wrap of sealed.wraps) {\n const ssk = await unwrapWithAny({ wrap, identities, streamId })\n if (ssk) sskByGeneration.set(wrap.keyGeneration, ssk)\n }\n\n const promptSsk = sskByGeneration.get(sealed.prompt.envelope.keyGeneration)\n if (!promptSsk) throw new Error(\"Sealed claim: no SSK wrap for the prompt's key generation\")\n const promptRaw = await openMessageAsString({\n key: promptSsk,\n envelope: sealed.prompt.envelope,\n ciphertext: base64ToBytes(sealed.prompt.ciphertext),\n })\n const promptPayload = parseSealedPayload(promptRaw)\n\n const replySsk = sskByGeneration.get(sealed.reply.keyGeneration)\n if (!replySsk) throw new Error(\"Sealed claim: no SSK wrap for the reply's key generation\")\n\n const history: DecryptedHistoryItem[] = []\n for (const item of sealed.history) {\n const ssk = sskByGeneration.get(item.envelope.keyGeneration)\n if (!ssk) continue\n try {\n const raw = await openMessageAsString({\n key: ssk,\n envelope: item.envelope,\n ciphertext: base64ToBytes(item.ciphertext),\n })\n const payload = parseSealedPayload(raw)\n history.push({\n role: item.role,\n sequence: item.sequence,\n contentMarkdown: payload.contentMarkdown,\n attachmentRefs: payload.attachmentRefs,\n })\n } catch {\n continue\n }\n }\n\n return {\n promptMarkdown: promptPayload.contentMarkdown,\n promptAttachmentRefs: promptPayload.attachmentRefs,\n history,\n sealing: {\n streamId,\n replyKeyGeneration: sealed.reply.keyGeneration,\n replySenderId: sealed.reply.senderId,\n replySsk,\n callbackToken: sealed.callbackToken,\n },\n }\n}\n\n/**\n * Seal a reply (or interim message) under the stream key, bound to a fresh\n * `msg_…` id — the body of the sealed `/complete` reply and of a sealed\n * interim `/sealed-messages` post.\n */\nexport async function sealReply(\n sealing: SealingState,\n markdown: string,\n extras?: SealedPayloadExtras\n): Promise<SealedReplyBody> {\n const messageId = `msg_${ulid()}`\n const sealed = await sealMessage({\n key: sealing.replySsk,\n keyGeneration: sealing.replyKeyGeneration,\n payload: serializeSealedPayload(markdown, extras),\n aad: buildMessageAad({ streamId: sealing.streamId, messageId, senderId: sealing.replySenderId }),\n })\n return { messageId, ciphertext: bytesToBase64(sealed.ciphertext), envelope: sealed.envelope }\n}\n\n/**\n * Seal one trace step under the stream key, bound to a fresh `step_…` id — the\n * sealed `/steps` wire shape. The `step_…` id rides the `messageId` slot of the\n * message AAD, exactly as the enclave's trace-observer binds its steps.\n * Content is sealed as-given: clamping oversized tool output is the caller's\n * policy, not hidden truncation here.\n */\nexport async function sealStep(\n sealing: SealingState,\n stepType: string,\n content: string,\n opts?: { durationMs?: number }\n): Promise<SealedStepFrame> {\n const stepId = `step_${ulid()}`\n const sealed = await sealMessage({\n key: sealing.replySsk,\n keyGeneration: sealing.replyKeyGeneration,\n payload: serializeSealedPayload(content),\n aad: buildMessageAad({ streamId: sealing.streamId, messageId: stepId, senderId: sealing.replySenderId }),\n })\n return {\n stepId,\n stepType,\n ciphertext: bytesToBase64(sealed.ciphertext),\n envelope: sealed.envelope,\n ...(opts?.durationMs !== undefined ? { durationMs: opts.durationMs } : {}),\n }\n}\n\n/** The words a sealed decision card carries; the wire row holds placeholders in their place. */\nexport interface SealedDecisionContent {\n title: string\n bodyMarkdown?: string\n /** Option id → the label to show on its button. Ids and tones stay in the clear. */\n optionLabels: Record<string, string>\n}\n\n/** The sealed half of a decision card, with the id its AAD is bound to. */\nexport interface SealedDecisionCard {\n decisionId: string\n ciphertext: string\n envelope: StreamEnvelope\n}\n\n/**\n * Seal a decision card's question under the stream key, bound to a fresh\n * `dreq_…` id — the `decisionId` + `sealed` half of a sealed create.\n *\n * The AAD names the stream the CARD lives on, which on a thread is not\n * `sealing.streamId` (that is the root the key hangs off, and what every wrap\n * and message AAD binds to). Pass the stream being posted to; the server checks\n * the same string and refuses a card sealed to another slot.\n */\nexport async function sealDecision(\n sealing: SealingState,\n card: { streamId: string; requesterBotId: string },\n content: SealedDecisionContent\n): Promise<SealedDecisionCard> {\n const decisionId = `dreq_${ulid()}`\n const sealed = await sealMessage({\n key: sealing.replySsk,\n keyGeneration: sealing.replyKeyGeneration,\n payload: JSON.stringify(content),\n aad: buildDecisionAad({ streamId: card.streamId, decisionId, requesterBotId: card.requesterBotId }),\n })\n return { decisionId, ciphertext: bytesToBase64(sealed.ciphertext), envelope: sealed.envelope }\n}\n\n/**\n * Open the sealed note a member attached to their answer. Returns null when the\n * envelope names another slot, or a key generation this turn does not hold — a\n * rotation between opening the card and answering it leaves the answer readable\n * and its note not, and losing the note beats losing the answer.\n */\nexport async function openSealedDecisionNote(\n sealing: SealingState,\n note: { streamId: string; decisionId: string; decidedBy: string; ciphertext: string; envelope: StreamEnvelope }\n): Promise<string | null> {\n const expected = bytesToBase64(\n buildDecisionNoteAad({\n streamId: note.streamId,\n decisionId: note.decisionId,\n decidedBy: note.decidedBy,\n })\n )\n if (note.envelope.aad !== expected) return null\n if (note.envelope.keyGeneration !== sealing.replyKeyGeneration) return null\n try {\n return await openMessageAsString({\n key: sealing.replySsk,\n envelope: note.envelope,\n ciphertext: base64ToBytes(note.ciphertext),\n })\n } catch {\n return null\n }\n}\n\n/** Error text for a sealed `/fail`: class name only, never the message — it could echo decrypted content. */\nexport function scrubSealedError(error: unknown): string {\n return error instanceof Error ? error.name || \"Error\" : \"Error\"\n}\n\n// ── session-control sealed ack ────────────────────────────────────────────────\n\n/**\n * The minimal sealed material a claim carries for a session-control command\n * (e.g. `/model`) on an E2E scratchpad: the current-generation SSK wraps\n * addressed to this bot's BIK plus the reply binding. No trigger/history — the\n * command name is cleartext dispatch metadata, so only the ack needs sealing.\n * Absent when the bot can't seal (no key / wrap race); the harness then closes\n * the command silently.\n */\nexport interface SealedAckContext {\n wraps: SealedSskWrap[]\n reply: { keyGeneration: number; senderId: string }\n}\n\n/** Validate a claim's `sealedAck` field (untyped JSON at the harness boundary). */\nexport function parseSealedAckContext(raw: unknown): SealedAckContext | undefined {\n if (typeof raw !== \"object\" || raw === null) return undefined\n const c = raw as Record<string, unknown>\n if (!Array.isArray(c.wraps)) return undefined\n const wraps: SealedSskWrap[] = []\n for (const wrap of c.wraps) {\n if (typeof wrap !== \"object\" || wrap === null) return undefined\n const w = wrap as Record<string, unknown>\n if (typeof w.keyGeneration !== \"number\" || typeof w.wrapEnc !== \"string\" || typeof w.wrapCt !== \"string\") {\n return undefined\n }\n wraps.push({ keyGeneration: w.keyGeneration, wrapEnc: w.wrapEnc, wrapCt: w.wrapCt })\n }\n const reply = c.reply as Record<string, unknown> | undefined\n if (!reply || typeof reply.keyGeneration !== \"number\" || typeof reply.senderId !== \"string\") return undefined\n return { wraps, reply: { keyGeneration: reply.keyGeneration, senderId: reply.senderId } }\n}\n\n/**\n * Open a session-control sealed ack: unwrap the SSK for the reply generation\n * with this bot's BIK and return the {@link SealingState} `sealReply` seals the\n * ack with. `callbackToken` is empty — a session-control ack authorizes with the\n * claim token on `/complete`, not a per-turn callback token. Throws when no wrap\n * covers the reply generation (a key race); the caller falls back to a silent close.\n */\nexport async function openSealedAck(params: {\n ack: SealedAckContext\n identities: BotIdentityKey[]\n streamId: string\n}): Promise<SealingState> {\n const { ack, identities, streamId } = params\n let replySsk: Uint8Array | undefined\n for (const wrap of ack.wraps) {\n if (wrap.keyGeneration !== ack.reply.keyGeneration) continue\n replySsk = await unwrapWithAny({ wrap, identities, streamId })\n if (replySsk) break\n }\n if (!replySsk) throw new Error(\"Sealed ack: no SSK wrap for the reply's key generation\")\n return {\n streamId,\n replyKeyGeneration: ack.reply.keyGeneration,\n replySenderId: ack.reply.senderId,\n replySsk,\n callbackToken: \"\",\n }\n}\n\n// ── stream-key provisioning (harness-created E2E scratchpads) ─────────────────\n\n/** One recipient a freshly-minted stream key is wrapped to. */\nexport interface ProvisionRecipient {\n recipientKind: \"user\" | \"bot\"\n /** UIK/BIK key id — the AAD binds the wrap to this slot. */\n recipientKeyId: string\n /** Base64 raw X25519 public key. */\n publicKeyBase64: string\n}\n\nexport interface ProvisionedWrap {\n recipientKind: \"user\" | \"bot\"\n recipientKeyId: string\n wrapEnc: string\n wrapCt: string\n}\n\n/**\n * Mint a fresh generation-0 stream key for a harness-created E2E scratchpad and\n * wrap it to each recipient (the owner's UIK + this install's BIK) — the wire\n * body of the phase-two provisioning POST. The SSK itself is returned only so\n * the caller can drop it deliberately: future turns recover it from the claim's\n * wraps, so nothing needs (or should) persist it locally.\n */\nexport async function mintStreamKeyWraps(params: {\n streamId: string\n keyGeneration: number\n recipients: ProvisionRecipient[]\n}): Promise<{ wraps: ProvisionedWrap[] }> {\n const ssk = generateStreamKey()\n const wraps: ProvisionedWrap[] = []\n for (const recipient of params.recipients) {\n const publicKey = await importRecipientPublicKey(base64ToBytes(recipient.publicKeyBase64))\n const wrapped = await wrapStreamKey({\n key: ssk,\n recipientPublicKey: publicKey,\n aad: buildWrapAad({\n streamId: params.streamId,\n keyGeneration: params.keyGeneration,\n recipientKeyId: recipient.recipientKeyId,\n }),\n })\n wraps.push({\n recipientKind: recipient.recipientKind,\n recipientKeyId: recipient.recipientKeyId,\n wrapEnc: bytesToBase64(wrapped.enc),\n wrapCt: bytesToBase64(wrapped.ct),\n })\n }\n return { wraps }\n}\n",
|
|
7
|
+
"/**\n * Vendored subset of `@threahq/crypto` (the repo's `packages/crypto`).\n *\n * The bot-runtime extensions ship standalone — they are copied to the user's\n * machine (e.g. `~/.pi/agent/extensions/`) and installed there, where the\n * private, unpublished `@threahq/crypto` workspace package cannot resolve. So the\n * slice the sealed (E2EE) bot path needs is copied here verbatim and depends\n * only on the published `@hpke/*` packages plus WebCrypto. Both harnesses\n * (pi-remote, claude-code-remote via remote-session) consume this one copy.\n *\n * Source of truth: `packages/crypto/src/{encoding,hpke,stream-key,envelope,sealed-payload,attachment}.ts`.\n * `crypto.parity.test.ts` imports BOTH this module and the canonical package and\n * asserts byte-for-byte agreement on the AAD builders, envelope/payload versions,\n * and cross seal/open + wrap/unwrap round-trips — so a drift here fails loudly.\n * Keep the two in sync; if the canonical AAD layout or envelope version changes,\n * the owner's client can no longer open this harness's sealed replies.\n *\n * Only the recipient/seal half lives here: the harness unwraps the stream key\n * with its identity private key, opens history/prompt, and seals replies/steps\n * under the stream key. It never wraps a key to another recipient, so the HPKE\n * `seal`/`wrapStreamKey` direction is deliberately omitted.\n */\n\nimport { Aes256Gcm, CipherSuite, HkdfSha256 } from \"@hpke/core\"\n// Pure-JS (noble) X25519 KEM, NOT `@hpke/core`'s native one. The harnesses run\n// in whatever runtime hosts them; Bun's WebCrypto lacks X25519 `deriveBits`, so\n// the native KEM throws `EncapError/DecapError: The algorithm is not supported`\n// there. The noble KEM works in any JS runtime and is the identical RFC 9180\n// DHKEM(X25519, HKDF-SHA256) — wire-compatible with the canonical native KEM the\n// owner's browser/enclave wraps with (`@hpke` supports mixing the two; keys\n// serialize to the same 32 raw bytes).\nimport { DhkemX25519HkdfSha256 } from \"@hpke/dhkem-x25519\"\n\n// WebCrypto key types derived from the `crypto` global in scope rather than\n// named: the bare `CryptoKey` type exists under DOM and Bun typings but not\n// under @types/node, so a Node consumer of the published declarations would\n// not resolve it. This alias is emitted verbatim and resolves in every setup.\ntype CryptoKey = Awaited<ReturnType<typeof crypto.subtle.importKey>>\ntype CryptoKeyPair = { publicKey: CryptoKey; privateKey: CryptoKey }\nexport type { CryptoKey as WebCryptoKey }\n\n// ── encoding ────────────────────────────────────────────────────────────────\n\nexport function bytesToBase64(bytes: Uint8Array | ArrayBuffer): string {\n const view = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes)\n let binary = \"\"\n for (let i = 0; i < view.length; i++) binary += String.fromCharCode(view[i]!)\n return btoa(binary)\n}\n\nexport function base64ToBytes(b64: string): Uint8Array<ArrayBuffer> {\n const binary = atob(b64)\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i)\n return bytes\n}\n\nexport function utf8Encode(text: string): Uint8Array<ArrayBuffer> {\n return new TextEncoder().encode(text) as Uint8Array<ArrayBuffer>\n}\n\nexport function utf8Decode(bytes: Uint8Array): string {\n return new TextDecoder().decode(bytes)\n}\n\nexport function concatBytes(...parts: Uint8Array[]): Uint8Array<ArrayBuffer> {\n let total = 0\n for (const p of parts) total += p.length\n const out = new Uint8Array(total)\n let offset = 0\n for (const p of parts) {\n out.set(p, offset)\n offset += p.length\n }\n return out\n}\n\n// ── HPKE (RFC 9180: DHKEM(X25519, HKDF-SHA256) + HKDF-SHA256 + AES-256-GCM) ────\n\nlet suite: CipherSuite | null = null\n\nfunction getSuite(): CipherSuite {\n if (!suite) {\n suite = new CipherSuite({\n kem: new DhkemX25519HkdfSha256(),\n kdf: new HkdfSha256(),\n aead: new Aes256Gcm(),\n })\n }\n return suite\n}\n\nexport async function generateKeyPair(): Promise<CryptoKeyPair> {\n return getSuite().kem.generateKeyPair()\n}\n\nexport async function importRecipientPrivateKey(raw: Uint8Array | ArrayBuffer): Promise<CryptoKey> {\n const buf = raw instanceof Uint8Array ? raw.buffer.slice(raw.byteOffset, raw.byteOffset + raw.byteLength) : raw\n return getSuite().kem.deserializePrivateKey(buf)\n}\n\nexport async function exportPublicKey(key: CryptoKey): Promise<Uint8Array<ArrayBuffer>> {\n return new Uint8Array(await getSuite().kem.serializePublicKey(key))\n}\n\nexport async function exportPrivateKey(key: CryptoKey): Promise<Uint8Array<ArrayBuffer>> {\n return new Uint8Array(await getSuite().kem.serializePrivateKey(key))\n}\n\nexport async function importRecipientPublicKey(raw: Uint8Array | ArrayBuffer): Promise<CryptoKey> {\n const buf = raw instanceof Uint8Array ? raw.buffer.slice(raw.byteOffset, raw.byteOffset + raw.byteLength) : raw\n return getSuite().kem.deserializePublicKey(buf)\n}\n\n/**\n * Decrypt an HPKE-sealed payload. Throws if `aad` does not match what the\n * sender used (the GCM tag fails verification).\n */\nasync function hpkeOpen(params: {\n recipientPrivateKey: CryptoKey\n enc: Uint8Array\n ct: Uint8Array\n aad?: Uint8Array\n}): Promise<Uint8Array<ArrayBuffer>> {\n const buf = await getSuite().open(\n { recipientKey: params.recipientPrivateKey, enc: params.enc },\n params.ct,\n params.aad\n )\n return new Uint8Array(buf)\n}\n\n/** HPKE-seal a payload to a recipient public key (the wrap direction of `hpkeOpen`). */\nasync function hpkeSeal(params: {\n recipientPublicKey: CryptoKey\n payload: Uint8Array\n aad?: Uint8Array\n}): Promise<{ enc: Uint8Array<ArrayBuffer>; ct: Uint8Array<ArrayBuffer> }> {\n const sealed = await getSuite().seal({ recipientPublicKey: params.recipientPublicKey }, params.payload, params.aad)\n return { enc: new Uint8Array(sealed.enc), ct: new Uint8Array(sealed.ct) }\n}\n\n// ── per-stream symmetric key (SSK) ───────────────────────────────────────────\n\n/** Stream-envelope version; a reader switches on `envelope.v`. */\nexport const STREAM_ENVELOPE_VERSION = 2\nconst SSK_LENGTH = 32 // AES-256\nconst IV_LENGTH = 12\n\n/**\n * Reject empty AAD at the boundary. Slot-binding is a security invariant of the\n * SSK design, so an empty `aad` is always a caller bug rather than a valid\n * \"unbound\" mode — fail loud instead of producing ciphertext that binds to nothing.\n */\nfunction assertBoundAad(fn: string, aad: Uint8Array): void {\n if (aad.length === 0) {\n throw new Error(`${fn}: aad must be non-empty (see buildMessageAad/buildWrapAad)`)\n }\n}\n\nexport interface StreamEnvelope {\n /** Always `STREAM_ENVELOPE_VERSION`; old clients reject an unknown version loudly. */\n v: number\n /** Which generation of the stream's SSK sealed this message. */\n keyGeneration: number\n /** Base64-encoded AES-GCM IV. */\n iv: string\n /** Base64-encoded AAD (caller-supplied binding bytes — see `buildMessageAad`). */\n aad: string\n}\n\nexport interface SealMessageInput {\n /** 32-byte SSK for `keyGeneration`. */\n key: Uint8Array\n keyGeneration: number\n payload: Uint8Array | string\n /** Bytes bound into AEAD as additional-authenticated-data — use `buildMessageAad`. Required. */\n aad: Uint8Array\n}\n\nexport interface SealMessageResult {\n envelope: StreamEnvelope\n /** AES-256-GCM ciphertext (tag included). */\n ciphertext: Uint8Array<ArrayBuffer>\n}\n\n/** AEAD-seal a message payload under the stream's SSK for `keyGeneration`. */\nexport async function sealMessage(input: SealMessageInput): Promise<SealMessageResult> {\n if (input.key.length !== SSK_LENGTH) {\n throw new Error(`sealMessage: SSK must be ${SSK_LENGTH} bytes, got ${input.key.length}`)\n }\n assertBoundAad(\"sealMessage\", input.aad)\n\n const iv = new Uint8Array(IV_LENGTH)\n crypto.getRandomValues(iv)\n\n const plaintext: Uint8Array<ArrayBuffer> =\n typeof input.payload === \"string\" ? utf8Encode(input.payload) : new Uint8Array(input.payload)\n const aad: Uint8Array<ArrayBuffer> = new Uint8Array(input.aad)\n\n const sskKey = await crypto.subtle.importKey(\"raw\", new Uint8Array(input.key), { name: \"AES-GCM\" }, false, [\n \"encrypt\",\n ])\n const ciphertext = new Uint8Array(\n await crypto.subtle.encrypt({ name: \"AES-GCM\", iv, additionalData: aad }, sskKey, plaintext)\n )\n\n return {\n envelope: {\n v: STREAM_ENVELOPE_VERSION,\n keyGeneration: input.keyGeneration,\n iv: bytesToBase64(iv),\n aad: bytesToBase64(aad),\n },\n ciphertext,\n }\n}\n\nexport interface OpenMessageInput {\n /** 32-byte SSK for `envelope.keyGeneration`. */\n key: Uint8Array\n envelope: StreamEnvelope\n /** AES-256-GCM ciphertext (tag included). */\n ciphertext: Uint8Array\n}\n\n/** Open an SSK-sealed message. Throws on version mismatch, wrong key, or forged AAD. */\nexport async function openMessage(input: OpenMessageInput): Promise<Uint8Array<ArrayBuffer>> {\n if (input.envelope.v !== STREAM_ENVELOPE_VERSION) {\n throw new Error(`Unsupported stream envelope version: ${input.envelope.v}`)\n }\n if (input.key.length !== SSK_LENGTH) {\n throw new Error(`openMessage: SSK must be ${SSK_LENGTH} bytes, got ${input.key.length}`)\n }\n\n const aad = base64ToBytes(input.envelope.aad)\n const sskKey = await crypto.subtle.importKey(\"raw\", new Uint8Array(input.key), { name: \"AES-GCM\" }, false, [\n \"decrypt\",\n ])\n const plaintext = new Uint8Array(\n await crypto.subtle.decrypt(\n { name: \"AES-GCM\", iv: base64ToBytes(input.envelope.iv), additionalData: aad },\n sskKey,\n new Uint8Array(input.ciphertext)\n )\n )\n return plaintext\n}\n\nexport async function openMessageAsString(input: OpenMessageInput): Promise<string> {\n return utf8Decode(await openMessage(input))\n}\n\nexport interface UnwrapStreamKeyInput {\n enc: Uint8Array\n ct: Uint8Array\n /** The recipient's HPKE private key (the harness's BIK private key). */\n recipientPrivateKey: CryptoKey\n /** Must match the `aad` used at wrap time (see `buildWrapAad`). */\n aad: Uint8Array\n}\n\nexport interface WrapStreamKeyInput {\n /** The 32-byte SSK to wrap. */\n key: Uint8Array\n /** The recipient's HPKE public key (imported via `importRecipientPublicKey`). */\n recipientPublicKey: CryptoKey\n /** Slot binding — use `buildWrapAad`. Required. */\n aad: Uint8Array\n}\n\n/**\n * HPKE-wrap an SSK to a recipient — used when a harness PROVISIONS a fresh\n * stream key for its own E2E scratchpad (wrapping to the owner's UIK and its\n * own BIK). Wire-identical to `@threahq/crypto`'s `wrapStreamKey`; the parity\n * test asserts a vendored wrap opens with the vendored unwrap under the same\n * AAD binding.\n */\nexport async function wrapStreamKey(input: WrapStreamKeyInput): Promise<{ enc: Uint8Array; ct: Uint8Array }> {\n assertBoundAad(\"wrapStreamKey\", input.aad)\n if (input.key.length !== SSK_LENGTH) {\n throw new Error(`wrapStreamKey: SSK must be ${SSK_LENGTH} bytes, got ${input.key.length}`)\n }\n return hpkeSeal({ recipientPublicKey: input.recipientPublicKey, payload: new Uint8Array(input.key), aad: input.aad })\n}\n\n/** A fresh random 32-byte SSK (AES-256). */\nexport function generateStreamKey(): Uint8Array<ArrayBuffer> {\n const key = new Uint8Array(SSK_LENGTH)\n crypto.getRandomValues(key)\n return key\n}\n\n/** Recover the SSK from a wrap. Throws if the key doesn't match or AAD is forged. */\nexport async function unwrapStreamKey(input: UnwrapStreamKeyInput): Promise<Uint8Array<ArrayBuffer>> {\n assertBoundAad(\"unwrapStreamKey\", input.aad)\n const key = await hpkeOpen({\n recipientPrivateKey: input.recipientPrivateKey,\n enc: input.enc,\n ct: input.ct,\n aad: input.aad,\n })\n if (key.length !== SSK_LENGTH) {\n throw new Error(`unwrapStreamKey: recovered key is ${key.length} bytes, expected ${SSK_LENGTH}`)\n }\n return key\n}\n\n/**\n * Canonical AAD for an SSK wrap. Binds a wrap to its `(streamId, keyGeneration,\n * recipientKeyId)` slot so a malicious server can't relocate a wrap row. Keep\n * stable — changing the layout breaks unwrapping of every existing wrap.\n */\nexport function buildWrapAad(parts: {\n streamId: string\n keyGeneration: number\n recipientKeyId: string\n}): Uint8Array<ArrayBuffer> {\n if (parts.streamId.length === 0 || parts.recipientKeyId.length === 0) {\n throw new Error(\"buildWrapAad: streamId and recipientKeyId must be non-empty\")\n }\n if (parts.streamId.includes(\"|\") || parts.recipientKeyId.includes(\"|\")) {\n throw new Error(\"buildWrapAad: streamId and recipientKeyId must not contain '|'\")\n }\n if (!Number.isInteger(parts.keyGeneration) || parts.keyGeneration < 0) {\n throw new Error(\"buildWrapAad: keyGeneration must be a non-negative integer\")\n }\n return concatBytes(\n utf8Encode(parts.streamId),\n utf8Encode(\"|\"),\n utf8Encode(String(parts.keyGeneration)),\n utf8Encode(\"|\"),\n utf8Encode(parts.recipientKeyId)\n )\n}\n\n/**\n * Canonical AAD for an SSK-sealed message (and trace step — the `step_…` id\n * rides the `messageId` slot). Binds the ciphertext to `streamId|messageId|senderId`\n * so the server can't shuffle it onto another row. Keep stable.\n */\nexport function buildMessageAad(parts: {\n streamId: string\n messageId: string\n senderId: string\n}): Uint8Array<ArrayBuffer> {\n return concatBytes(\n utf8Encode(parts.streamId),\n utf8Encode(\"|\"),\n utf8Encode(parts.messageId),\n utf8Encode(\"|\"),\n utf8Encode(parts.senderId)\n )\n}\n\n/**\n * Canonical AAD for a sealed decision card and for the note a member attaches\n * to their answer. Mirrors `buildDecisionAad` / `buildDecisionNoteAad` in\n * `@threahq/crypto`; the label keeps the two apart, and both apart from a\n * sealed message body. Keep stable.\n */\nexport function buildDecisionAad(parts: {\n streamId: string\n decisionId: string\n requesterBotId: string\n}): Uint8Array<ArrayBuffer> {\n return decisionAad(\"decision\", parts.streamId, parts.decisionId, parts.requesterBotId)\n}\n\nexport function buildDecisionNoteAad(parts: {\n streamId: string\n decisionId: string\n decidedBy: string\n}): Uint8Array<ArrayBuffer> {\n return decisionAad(\"decision-note\", parts.streamId, parts.decisionId, parts.decidedBy)\n}\n\nfunction decisionAad(label: string, streamId: string, decisionId: string, actorId: string): Uint8Array<ArrayBuffer> {\n return concatBytes(\n utf8Encode(streamId),\n utf8Encode(\"|\"),\n utf8Encode(label),\n utf8Encode(\"|\"),\n utf8Encode(decisionId),\n utf8Encode(\"|\"),\n utf8Encode(actorId)\n )\n}\n\n// ── E2E attachment bytes (per-file single-use key) ────────────────────────────\n\n// Domain-separation label bound as GCM AAD. The per-attachment key is random\n// and used exactly once, so relocation/confusion attacks gain nothing and the\n// AAD's only job is to satisfy the AEAD interface and pin the ciphertext to\n// this scheme. It carries no secret and is reconstructed verbatim on decrypt.\nexport const ATTACHMENT_AAD = utf8Encode(\"threa-attachment-v1\")\n/** Single-key scheme: attachment keys are per-file, never rotated. */\nexport const ATTACHMENT_KEY_GENERATION = 0\n\nexport interface EncryptedAttachment {\n /** Ciphertext bytes to upload as the opaque file body (a valid `BlobPart`). */\n ciphertext: Uint8Array<ArrayBuffer>\n /** Base64 key + iv to stash in the message's `attachmentRefs`. */\n key: string\n iv: string\n}\n\n/**\n * Encrypt a file's bytes under a fresh single-use key for upload to an E2E\n * stream. Returns the ciphertext plus the key/iv the message payload must carry\n * so a recipient can decrypt it later. Reuses the message seal primitive\n * (AES-256-GCM) rather than a parallel raw-bytes path (INV-35).\n */\nexport async function encryptAttachmentBytes(plaintext: Uint8Array): Promise<EncryptedAttachment> {\n const key = generateStreamKey()\n const { envelope, ciphertext } = await sealMessage({\n key,\n keyGeneration: ATTACHMENT_KEY_GENERATION,\n payload: plaintext,\n aad: ATTACHMENT_AAD,\n })\n return { ciphertext, key: bytesToBase64(key), iv: envelope.iv }\n}\n\n/**\n * Decrypt the opaque S3 ciphertext of an E2E attachment back to its bytes, using\n * the `key`/`iv` carried in the message's `attachmentRef`. Reconstructs the\n * single-key envelope (gen 0, the domain-separation AAD) and opens it. Throws if\n * the key/iv don't match or the bytes were tampered (AES-GCM tag check).\n */\nexport async function decryptAttachmentBytes(input: {\n ciphertext: Uint8Array\n key: string\n iv: string\n}): Promise<Uint8Array<ArrayBuffer>> {\n return openMessage({\n key: base64ToBytes(input.key),\n envelope: {\n v: STREAM_ENVELOPE_VERSION,\n keyGeneration: ATTACHMENT_KEY_GENERATION,\n iv: input.iv,\n aad: bytesToBase64(ATTACHMENT_AAD),\n },\n ciphertext: input.ciphertext,\n })\n}\n\n// ── sealed payload wrapper ────────────────────────────────────────────────────\n\nexport const E2E_PAYLOAD_VERSION = 1\n\n/** One citation source sealed into a payload (structural twin of `@threahq/types`' `SourceItem`). */\nexport interface SealedSourceItem {\n type?: string\n title: string\n url: string\n snippet?: string\n}\n\n/** A per-file attachment key sealed into a payload (structural twin of `@threahq/crypto`'s `AttachmentRef`). */\nexport interface AttachmentRef {\n attachmentId: string\n key: string\n iv: string\n filename: string\n mimeType: string\n sizeBytes: number\n}\n\ninterface E2eSealedPayload {\n __e2ePayload: typeof E2E_PAYLOAD_VERSION\n contentMarkdown: string\n attachmentRefs: AttachmentRef[]\n sources?: SealedSourceItem[]\n draftContentJson?: unknown\n}\n\nexport interface SealedPayloadExtras {\n attachmentRefs?: AttachmentRef[]\n sources?: SealedSourceItem[]\n draftContentJson?: unknown\n}\n\n/** Build the bytes to seal: bare markdown, or the versioned wrapper when an adjunct rides along. */\nexport function serializeSealedPayload(contentMarkdown: string, extras?: SealedPayloadExtras): string {\n const attachmentRefs = extras?.attachmentRefs\n const sources = extras?.sources\n const draftContentJson = extras?.draftContentJson\n const hasRefs = attachmentRefs !== undefined && attachmentRefs.length > 0\n const hasSources = sources !== undefined && sources.length > 0\n const hasDraftBody = draftContentJson !== undefined && draftContentJson !== null\n if (!hasRefs && !hasSources && !hasDraftBody) return contentMarkdown\n return JSON.stringify({\n __e2ePayload: E2E_PAYLOAD_VERSION,\n contentMarkdown,\n attachmentRefs: attachmentRefs ?? [],\n ...(hasSources ? { sources } : {}),\n ...(hasDraftBody ? { draftContentJson } : {}),\n } satisfies E2eSealedPayload)\n}\n\nexport interface ParsedSealedPayload {\n contentMarkdown: string\n attachmentRefs: AttachmentRef[]\n sources: SealedSourceItem[]\n draftContentJson: unknown | null\n}\n\nfunction isAttachmentRef(value: unknown): value is AttachmentRef {\n if (typeof value !== \"object\" || value === null) return false\n const r = value as Record<string, unknown>\n return (\n typeof r.attachmentId === \"string\" &&\n typeof r.key === \"string\" &&\n typeof r.iv === \"string\" &&\n typeof r.filename === \"string\" &&\n typeof r.mimeType === \"string\" &&\n typeof r.sizeBytes === \"number\"\n )\n}\n\nfunction isSealedSourceItem(value: unknown): value is SealedSourceItem {\n if (typeof value !== \"object\" || value === null) return false\n const s = value as Record<string, unknown>\n return (\n typeof s.title === \"string\" &&\n typeof s.url === \"string\" &&\n (s.type === undefined || typeof s.type === \"string\") &&\n (s.snippet === undefined || typeof s.snippet === \"string\")\n )\n}\n\nfunction isDocLike(value: unknown): boolean {\n if (typeof value !== \"object\" || value === null) return false\n const v = value as Record<string, unknown>\n return v.type === \"doc\" && Array.isArray(v.content)\n}\n\n/**\n * Inverse of `serializeSealedPayload`. A decrypted string is either the bare\n * markdown body or the versioned wrapper; anything that doesn't parse as our\n * wrapper is treated as raw markdown so older messages keep opening unchanged.\n */\nexport function parseSealedPayload(raw: string): ParsedSealedPayload {\n if (raw.startsWith(\"{\")) {\n try {\n const parsed = JSON.parse(raw) as Partial<E2eSealedPayload>\n if (parsed.__e2ePayload === E2E_PAYLOAD_VERSION && typeof parsed.contentMarkdown === \"string\") {\n const attachmentRefs = Array.isArray(parsed.attachmentRefs) ? parsed.attachmentRefs.filter(isAttachmentRef) : []\n const sources = Array.isArray(parsed.sources) ? parsed.sources.filter(isSealedSourceItem) : []\n const draftContentJson = isDocLike(parsed.draftContentJson) ? parsed.draftContentJson : null\n return { contentMarkdown: parsed.contentMarkdown, attachmentRefs, sources, draftContentJson }\n }\n } catch {\n // Not our wrapper — fall through and treat the whole string as markdown.\n }\n }\n return { contentMarkdown: raw, attachmentRefs: [], sources: [], draftContentJson: null }\n}\n",
|
|
8
|
+
"export interface WsHint {\n url: string\n path: string\n namespace: string\n}\n\nexport function isObject(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === \"object\" && !Array.isArray(value)\n}\n\n/**\n * Normalize the `{ wsUrl }` the edge workspace-router returns from\n * `GET /api/workspaces/:id/config` into a connectable hint. Defaults match the\n * server: the default Socket.IO path and the `/bot` namespace.\n */\nexport function parseWsHint(value: unknown): WsHint | undefined {\n if (!isObject(value)) return undefined\n const url = typeof value.url === \"string\" ? value.url.trim() : \"\"\n if (!url) return undefined\n // Use the trimmed value, not the raw one — `buildBotSocketUrl` concatenates\n // `namespace` onto the pathname verbatim, so a stray-whitespace hint would\n // otherwise produce a malformed connect URL.\n const path = typeof value.path === \"string\" && value.path.trim() ? value.path.trim() : \"/socket.io/\"\n const namespace = typeof value.namespace === \"string\" && value.namespace.trim() ? value.namespace.trim() : \"/bot\"\n return { url, path, namespace }\n}\n\n/**\n * Append the `/bot` namespace to the pathname while preserving any query string.\n * A naive `${url}${namespace}` concat breaks staging URLs that carry `?region=…`.\n */\nexport function buildBotSocketUrl(hint: WsHint): string {\n const parsed = new URL(hint.url)\n const trimmedPath = parsed.pathname.replace(/\\/$/, \"\")\n parsed.pathname = `${trimmedPath}${hint.namespace}`\n return parsed.toString()\n}\n",
|
|
9
|
+
"import type { AttachmentRef } from \"./crypto\"\nimport {\n openSealedTurnContext,\n parseSealedTurnContext,\n scrubSealedError,\n type BotIdentityKey,\n type SealingState,\n} from \"./sealed\"\nimport { isObject } from \"./ws-hint\"\n\nexport const BOT_INVOCATION_CANCELLATION_REASONS = [\n \"source_deleted\",\n \"routing_changed\",\n \"input_restart\",\n \"input_stale\",\n \"key_grant_lost\",\n] as const\n\nexport type InputUpdateDisposition = \"applied\" | \"restart-required\"\nexport type BotInvocationCancellationReason = (typeof BOT_INVOCATION_CANCELLATION_REASONS)[number]\n\nexport interface InvocationInputUpdate {\n sourceRevision: number\n delivery: \"plaintext\" | \"sealed\"\n promptMarkdown: string\n attachmentRefs: AttachmentRef[]\n sealing?: SealingState\n}\n\nexport interface InvocationCancellation {\n invocationId: string\n sourceRevision: number\n reason: BotInvocationCancellationReason\n}\n\nexport interface InvocationControlCallbacks {\n onInputUpdated(\n update: InvocationInputUpdate,\n signal: AbortSignal\n ): Promise<InputUpdateDisposition> | InputUpdateDisposition\n onCancelled(): Promise<void> | void\n /** The authoritative claim no longer exists, without a typed backend cancellation. */\n onClaimLost?(): Promise<void> | void\n}\n\nexport interface ObserveClaimParams {\n invocationId: string\n claimToken: string\n sourceRevision: number\n claimTtlSeconds: number\n instanceId?: string\n callbacks: InvocationControlCallbacks\n sealed?: {\n identities: BotIdentityKey[]\n streamId: string\n callbackToken: string\n }\n}\n\nexport interface ObservedClaimHandle {\n sync(): Promise<void>\n unregister(): void\n dispose(): void\n}\n\nexport type InvocationControlState =\n | { invocationId: string; status: \"active\"; claimExpiresAt: string; sourceRevision: number; update?: unknown }\n | {\n invocationId: string\n status: \"cancelled\"\n claimExpiresAt: null\n sourceRevision: number\n reason: BotInvocationCancellationReason\n }\n\nexport type ControlSyncResult =\n | { kind: \"control\"; state: InvocationControlState }\n | { kind: \"not_found\" }\n | { kind: \"retry\" }\n | { kind: \"aborted\" }\n\nexport interface InvocationControlSyncRequest {\n invocationId: string\n instanceId?: string\n claimToken: string\n claimTtlSeconds: number\n knownSourceRevision: number\n minimumSourceRevision: number\n restartRequiredRevision?: number\n ackTimeoutMs: number\n signal: AbortSignal\n}\n\ninterface SealedBinding {\n identities: BotIdentityKey[]\n streamId: string\n callbackToken: string\n}\n\nexport interface InvocationControlScheduler {\n setTimeout(callback: () => void, delayMs: number): unknown\n clearTimeout(handle: unknown): void\n}\n\ninterface Observation {\n readonly generation: number\n readonly invocationId: string\n readonly claimTtlSeconds: number\n readonly instanceId?: string\n claimToken: string\n callbacks?: InvocationControlCallbacks\n sealedBinding?: SealedBinding\n appliedRevision: number\n highWaterRevision: number\n claimExpiresAtMs?: number\n timer?: unknown\n terminal: boolean\n dirty: boolean\n drain?: Promise<void>\n abortController: AbortController\n enqueuedRevisions: Set<number>\n restartPendingRevision?: number\n immediateFollowupRevision?: number\n}\n\ninterface InvocationControlManagerOptions {\n retryDelayMs?: number\n minRenewDelayMs?: number\n now?: () => number\n scheduler?: InvocationControlScheduler\n}\n\nexport class InvocationControlManager {\n private readonly observations = new Map<string, Observation>()\n private readonly pendingTerminalGenerations = new Map<string, number>()\n private queue: Promise<void> = Promise.resolve()\n private stopped = false\n private generation = 0\n private readonly retryDelayMs: number\n private readonly minRenewDelayMs: number\n private readonly now: () => number\n private readonly scheduler: InvocationControlScheduler\n\n constructor(\n private readonly hooks: {\n sync(request: InvocationControlSyncRequest): Promise<ControlSyncResult>\n socketReady(): boolean\n log(message: string): void\n },\n options: InvocationControlManagerOptions = {}\n ) {\n this.retryDelayMs = options.retryDelayMs ?? 5_000\n this.minRenewDelayMs = options.minRenewDelayMs ?? 1_000\n this.now = options.now ?? Date.now\n this.scheduler = options.scheduler ?? {\n setTimeout: (callback, delayMs) => setTimeout(callback, delayMs),\n clearTimeout: (handle) => clearTimeout(handle as ReturnType<typeof setTimeout>),\n }\n }\n\n observe(params: ObserveClaimParams): ObservedClaimHandle {\n this.pendingTerminalGenerations.delete(params.invocationId)\n const prior = this.observations.get(params.invocationId)\n if (prior) this.unregister(prior)\n const observation: Observation = {\n generation: ++this.generation,\n invocationId: params.invocationId,\n claimTtlSeconds: params.claimTtlSeconds,\n instanceId: params.instanceId,\n claimToken: params.claimToken,\n callbacks: params.callbacks,\n sealedBinding: params.sealed\n ? {\n identities: params.sealed.identities,\n streamId: params.sealed.streamId,\n callbackToken: params.sealed.callbackToken,\n }\n : undefined,\n appliedRevision: params.sourceRevision,\n highWaterRevision: params.sourceRevision,\n terminal: false,\n dirty: false,\n abortController: new AbortController(),\n enqueuedRevisions: new Set(),\n }\n this.observations.set(params.invocationId, observation)\n void this.requestSync(observation)\n return {\n sync: () => this.syncAndDrain(observation),\n unregister: () => this.unregister(observation),\n dispose: () => this.unregister(observation),\n }\n }\n\n hint(payload: unknown, cancelled: boolean): void {\n const hint = cancelled ? parseInvocationCancellation(payload) : parseUpdateHint(payload)\n if (!hint) return\n const observation = this.observations.get(hint.invocationId)\n if (!observation) return\n if (cancelled) {\n this.terminalizeCancellation(observation, hint as InvocationCancellation)\n return\n }\n if (hint.sourceRevision <= observation.highWaterRevision) return\n observation.highWaterRevision = hint.sourceRevision\n void this.requestSync(observation)\n }\n\n async bootstrap(recentCancellations: unknown[], callback: () => void): Promise<void> {\n for (const value of recentCancellations) {\n const cancellation = parseInvocationCancellation(value)\n if (!cancellation) continue\n const observation = this.observations.get(cancellation.invocationId)\n if (observation) this.terminalizeCancellation(observation, cancellation)\n }\n for (const observation of this.observations.values()) void this.requestSync(observation)\n await this.enqueueAdapter(callback)\n }\n\n wake(): void {\n for (const observation of this.observations.values()) void this.requestSync(observation)\n }\n\n async enqueueAdapter(callback: () => void): Promise<void> {\n await this.awaitStableControls()\n if (!this.stopped) {\n await this.enqueue(async () => {\n if (this.stopped) return\n try {\n callback()\n } catch {\n this.hooks.log(\"invocation availability callback failed\")\n }\n })\n }\n }\n\n stop(): void {\n this.stopped = true\n this.pendingTerminalGenerations.clear()\n for (const observation of [...this.observations.values()]) this.unregister(observation)\n }\n\n private async syncAndDrain(observation: Observation): Promise<void> {\n if (this.isCurrent(observation)) void this.requestSync(observation)\n await this.awaitStableControls()\n }\n\n private requestSync(observation: Observation): Promise<void> {\n if (!this.isCurrent(observation)) return Promise.resolve()\n observation.dirty = true\n if (observation.drain) return observation.drain\n const drain = Promise.resolve().then(() => this.runDrain(observation))\n observation.drain = drain\n return drain\n }\n\n private async runDrain(observation: Observation): Promise<void> {\n while (this.isCurrent(observation) && observation.dirty) {\n observation.dirty = false\n const restartRevision = observation.restartPendingRevision\n const knownSourceRevision = restartRevision ?? observation.appliedRevision\n const request: InvocationControlSyncRequest = {\n invocationId: observation.invocationId,\n instanceId: observation.instanceId,\n claimToken: observation.claimToken,\n claimTtlSeconds: observation.claimTtlSeconds,\n knownSourceRevision,\n minimumSourceRevision: observation.appliedRevision,\n ...(restartRevision === undefined ? {} : { restartRequiredRevision: restartRevision }),\n ackTimeoutMs: Math.min(5_000, (observation.claimTtlSeconds * 1_000) / 6),\n signal: observation.abortController.signal,\n }\n const authorityBehind = await this.runSync(observation, request)\n if (!this.isCurrent(observation)) break\n if (authorityBehind) {\n // Equal high-water marks mean authority repeated the same behind revision; defer to the scheduled retry.\n if (observation.immediateFollowupRevision !== observation.highWaterRevision) {\n observation.immediateFollowupRevision = observation.highWaterRevision\n observation.dirty = true\n } else {\n this.schedule(observation, this.retryDelayMs)\n }\n } else {\n observation.immediateFollowupRevision = undefined\n }\n }\n observation.drain = undefined\n }\n\n private async runSync(observation: Observation, request: InvocationControlSyncRequest): Promise<boolean> {\n let result: ControlSyncResult\n try {\n result = await this.hooks.sync(request)\n } catch {\n result = { kind: \"retry\" }\n this.hooks.log(`invocation control sync failed (${observation.invocationId})`)\n }\n if (!this.isCurrent(observation) || result.kind === \"aborted\") return false\n if (result.kind === \"not_found\") {\n if (request.restartRequiredRevision !== undefined) {\n observation.restartPendingRevision = undefined\n observation.dirty = true\n } else {\n const callback = observation.callbacks?.onClaimLost\n this.makeTerminal(observation, callback)\n }\n return false\n }\n if (result.kind === \"retry\") {\n this.schedule(observation, this.retryDelayMs)\n return false\n }\n\n const state = result.state\n if (state.status === \"cancelled\") {\n const cancellation = {\n invocationId: state.invocationId,\n sourceRevision: state.sourceRevision,\n reason: state.reason,\n }\n if (!this.terminalizeCancellation(observation, cancellation)) this.schedule(observation, this.retryDelayMs)\n return false\n }\n\n const expiresAt = Date.parse(state.claimExpiresAt)\n if (!Number.isFinite(expiresAt)) {\n this.schedule(observation, this.retryDelayMs)\n return false\n }\n observation.claimExpiresAtMs = expiresAt\n observation.highWaterRevision = Math.max(observation.highWaterRevision, state.sourceRevision)\n this.schedule(observation)\n\n if (request.restartRequiredRevision !== undefined) {\n if (observation.restartPendingRevision === request.restartRequiredRevision) {\n this.schedule(observation, this.retryDelayMs)\n }\n return state.sourceRevision < observation.highWaterRevision\n }\n\n if (state.sourceRevision > observation.appliedRevision) {\n if (state.update) await this.prepareUpdate(observation, state.update)\n else this.schedule(observation, this.retryDelayMs)\n }\n return state.sourceRevision < observation.highWaterRevision\n }\n\n private async prepareUpdate(observation: Observation, raw: unknown): Promise<void> {\n const revision = parseRevision(isObject(raw) ? raw.sourceRevision : undefined)\n if (revision === undefined || revision <= observation.appliedRevision) return\n const highestEnqueued = Math.max(observation.appliedRevision, ...observation.enqueuedRevisions)\n if (revision <= highestEnqueued || observation.restartPendingRevision !== undefined) return\n\n let update: InvocationInputUpdate\n try {\n update = await openUpdate(observation, raw)\n } catch (error) {\n this.hooks.log(`sealed invocation update failed (${observation.invocationId}): ${scrubSealedError(error)}`)\n this.markRestartPending(observation, revision)\n return\n }\n if (!this.isCurrent(observation)) return\n\n observation.enqueuedRevisions.add(revision)\n const invocationId = observation.invocationId\n const generation = observation.generation\n void this.enqueue(async () => {\n const current = this.observations.get(invocationId)\n if (!current || current.generation !== generation || current.terminal || this.stopped) return\n if (revision <= current.appliedRevision || current.restartPendingRevision !== undefined) {\n current.enqueuedRevisions.delete(revision)\n return\n }\n let disposition: InputUpdateDisposition = \"restart-required\"\n try {\n disposition =\n (await current.callbacks?.onInputUpdated(update, current.abortController.signal)) ?? \"restart-required\"\n } catch {\n disposition = \"restart-required\"\n }\n const stillCurrent = this.observations.get(invocationId)\n if (!stillCurrent || stillCurrent.generation !== generation || stillCurrent.terminal) return\n stillCurrent.enqueuedRevisions.delete(revision)\n if (disposition === \"applied\") {\n stillCurrent.appliedRevision = revision\n if (stillCurrent.highWaterRevision > revision) void this.requestSync(stillCurrent)\n } else {\n this.markRestartPending(stillCurrent, revision)\n }\n })\n }\n\n private markRestartPending(observation: Observation, revision: number): void {\n if (!this.isCurrent(observation)) return\n if (observation.restartPendingRevision !== undefined && observation.restartPendingRevision >= revision) return\n observation.restartPendingRevision = revision\n observation.enqueuedRevisions.clear()\n void this.requestSync(observation)\n }\n\n private highestLocallyKnownRevision(observation: Observation): number {\n return Math.max(\n observation.appliedRevision,\n observation.highWaterRevision,\n ...observation.enqueuedRevisions,\n observation.restartPendingRevision ?? -1\n )\n }\n\n private terminalizeCancellation(observation: Observation, cancellation: InvocationCancellation): boolean {\n if (!this.isCurrent(observation) || cancellation.sourceRevision < this.highestLocallyKnownRevision(observation)) {\n return false\n }\n this.makeTerminal(observation, observation.callbacks?.onCancelled)\n return true\n }\n\n private makeTerminal(observation: Observation, callback?: () => void | Promise<void>): void {\n if (!this.isCurrent(observation)) return\n this.teardown(observation)\n if (!callback) return\n const { generation, invocationId } = observation\n this.pendingTerminalGenerations.set(invocationId, generation)\n void this.enqueue(async () => {\n if (this.stopped || this.pendingTerminalGenerations.get(invocationId) !== generation) return\n try {\n await callback()\n } catch {\n this.hooks.log(`invocation cancellation callback failed (${invocationId})`)\n } finally {\n if (this.pendingTerminalGenerations.get(invocationId) === generation) {\n this.pendingTerminalGenerations.delete(invocationId)\n }\n }\n })\n }\n\n private unregister(observation: Observation): void {\n if (this.pendingTerminalGenerations.get(observation.invocationId) === observation.generation) {\n this.pendingTerminalGenerations.delete(observation.invocationId)\n }\n if (observation.terminal) return\n this.teardown(observation)\n }\n\n private teardown(observation: Observation): void {\n observation.terminal = true\n observation.abortController.abort()\n if (observation.timer !== undefined) this.scheduler.clearTimeout(observation.timer)\n observation.timer = undefined\n if (this.observations.get(observation.invocationId) === observation) {\n this.observations.delete(observation.invocationId)\n }\n this.scrub(observation)\n }\n\n private scrub(observation: Observation): void {\n observation.claimToken = \"\"\n observation.callbacks = undefined\n observation.sealedBinding = undefined\n observation.enqueuedRevisions.clear()\n observation.restartPendingRevision = undefined\n observation.immediateFollowupRevision = undefined\n }\n\n private schedule(observation: Observation, requestedDelay?: number): void {\n if (!this.isCurrent(observation)) return\n if (observation.timer !== undefined) this.scheduler.clearTimeout(observation.timer)\n const nominalLeaseMs = observation.claimTtlSeconds * 1_000\n const safetyMs = Math.min(30_000, Math.max(this.minRenewDelayMs, nominalLeaseMs / 3))\n const authoritativeDelay =\n observation.claimExpiresAtMs === undefined\n ? this.retryDelayMs\n : Math.max(this.minRenewDelayMs, observation.claimExpiresAtMs - this.now() - safetyMs)\n let delay = requestedDelay === undefined ? authoritativeDelay : Math.min(authoritativeDelay, requestedDelay)\n if (!this.hooks.socketReady()) delay = Math.min(delay, this.retryDelayMs)\n observation.timer = this.scheduler.setTimeout(\n () => {\n observation.timer = undefined\n void this.requestSync(observation)\n },\n Math.max(this.minRenewDelayMs, delay)\n )\n }\n\n private isCurrent(observation: Observation): boolean {\n return !this.stopped && !observation.terminal && this.observations.get(observation.invocationId) === observation\n }\n\n private enqueue(work: () => Promise<void>): Promise<void> {\n const result = this.queue.then(work, work)\n this.queue = result.catch(() => {})\n return result\n }\n\n private async awaitStableControls(): Promise<void> {\n while (true) {\n const drains = [...this.observations.values()].flatMap((observation) =>\n observation.drain ? [observation.drain] : []\n )\n await Promise.allSettled(drains)\n const adapterQueue = this.queue\n await adapterQueue\n if (\n this.queue === adapterQueue &&\n [...this.observations.values()].every((observation) => observation.drain === undefined)\n ) {\n return\n }\n }\n }\n}\n\nasync function openUpdate(observation: Observation, raw: unknown): Promise<InvocationInputUpdate> {\n if (!isObject(raw)) throw new Error(\"Invalid update\")\n const sourceRevision = parseRevision(raw.sourceRevision)\n if (sourceRevision === undefined) throw new Error(\"Invalid update\")\n const expectsSealed = observation.sealedBinding !== undefined\n if (!expectsSealed && raw.delivery === \"plaintext\" && typeof raw.promptMarkdown === \"string\") {\n return {\n sourceRevision,\n delivery: \"plaintext\",\n promptMarkdown: raw.promptMarkdown,\n attachmentRefs: [],\n }\n }\n if (!expectsSealed || raw.delivery !== \"sealed\" || !observation.sealedBinding) {\n throw new Error(\"Invalid update delivery\")\n }\n const sealed = parseSealedTurnContext({\n callbackToken: observation.sealedBinding.callbackToken,\n wraps: raw.wraps,\n history: [],\n prompt: raw.prompt,\n reply: raw.reply,\n })\n if (!sealed) throw new Error(\"Invalid sealed update\")\n const opened = await openSealedTurnContext({\n sealed,\n identities: observation.sealedBinding.identities,\n streamId: observation.sealedBinding.streamId,\n })\n return {\n sourceRevision,\n delivery: \"sealed\",\n promptMarkdown: opened.promptMarkdown,\n attachmentRefs: opened.promptAttachmentRefs,\n sealing: opened.sealing,\n }\n}\n\nconst cancellationReasonSet = new Set<string>(BOT_INVOCATION_CANCELLATION_REASONS)\n\nexport function parseCancellationReason(value: unknown): BotInvocationCancellationReason | undefined {\n if (value === \"adapter_restart_required\") return \"input_restart\"\n return typeof value === \"string\" && cancellationReasonSet.has(value)\n ? (value as BotInvocationCancellationReason)\n : undefined\n}\n\nexport function parseInvocationCancellation(value: unknown): InvocationCancellation | undefined {\n if (!isObject(value) || typeof value.invocationId !== \"string\") return\n const sourceRevision = parseRevision(value.sourceRevision)\n const reason = parseCancellationReason(value.reason)\n if (sourceRevision === undefined || !reason) return\n return { invocationId: value.invocationId, sourceRevision, reason }\n}\n\nfunction parseUpdateHint(value: unknown): { invocationId: string; sourceRevision: number } | undefined {\n if (!isObject(value) || typeof value.invocationId !== \"string\") return\n const sourceRevision = parseRevision(value.sourceRevision)\n return sourceRevision === undefined ? undefined : { invocationId: value.invocationId, sourceRevision }\n}\n\nexport function parseRevision(value: unknown): number | undefined {\n return Number.isInteger(value) && (value as number) >= 0 ? (value as number) : undefined\n}\n",
|
|
10
|
+
"/**\n * How long a runtime survives its scratchpad being archived before winding\n * down. An unarchive inside this window reattaches the live agent in place.\n * Shared: Claude (`@threahq/remote-session`) and Pi run separate session\n * implementations, and a grace tuned on one must not diverge from the other.\n */\nexport const ARCHIVE_RESTORE_GRACE_MS = 5 * 60 * 1000\n/** Reattach-probe cadence while detached. Bounded by the grace window, so it cannot become a quota burn. */\nexport const ARCHIVE_RESTORE_PROBE_MS = 45_000\n/**\n * Poll cadence while the `/bot` socket is up: pushes deliver work within a\n * frame, so the poll is only a backstop for a dropped one. Shared because it\n * is also the worst case for a runtime to notice an archive it was not pushed,\n * which is what any external reaper has to wait out.\n */\nexport const WS_BACKSTOP_POLL_MS = 15 * 60 * 1000\n\n/**\n * The archive → grace → wind-down state machine, shared by every harness\n * runtime.\n *\n * Archiving a scratchpad ends its session server-side, so the worktree behind\n * it is finished — but archiving is also how a mis-click gets undone, so the\n * wind-down (hand the worktree to harnessd, kill the tmux window) waits out a\n * grace window that an unarchive can cancel.\n *\n * This is deliberately one implementation rather than one per runtime. Every\n * bug this machine has produced came from the same shape: state read before an\n * `await` and acted on after it, once the deadline had already fired or a\n * second caller had won. The `pending` object is the identity token — every\n * resumption re-checks `this.pending !== pending` and bails, which is what\n * makes the wind-down terminal.\n */\n\nexport interface ArchiveGraceHooks {\n /**\n * Server truth for the attached direction. `undefined` means \"could not\n * tell\" (transient failure, missing scope, outage) and never detaches — a\n * diagnostic that did not run is not evidence the scratchpad is archived.\n */\n isArchived(rootStreamId: string): Promise<boolean | undefined>\n /**\n * Server truth for the detached direction: try to revive this runtime's link.\n * `true` once reattached, `false` while the scratchpad is still archived.\n * Throwing is treated as `false` — the probe cadence retries.\n */\n reattach(rootStreamId: string): Promise<boolean>\n /** Detach effects: go offline, suspend claiming, pull the poll onto {@link probeDelayMs}. */\n onDetached(rootStreamId: string, graceMs: number): Promise<void> | void\n /** Reattach effects: back to available, resume claiming. */\n onReattached(rootStreamId: string): Promise<void> | void\n /**\n * Terminal. Hand the worktree to harnessd ({@link markHarnessLinkWoundDown})\n * and take the window down; the runtime usually dies here. Preserving the\n * branch and removing the worktree is harnessd's job, never a runtime's —\n * only harnessd holds the lock a concurrent revive also takes.\n */\n onWindDown(rootStreamId: string): Promise<void> | void\n log(message: string): void\n}\n\nexport interface ArchiveGraceOptions {\n /** Override the grace window. Tests use a few hundred ms; production takes the shared default. */\n graceMs?: number\n}\n\ninterface Pending {\n rootStreamId: string\n deadline: ReturnType<typeof setTimeout>\n}\n\nexport class ArchiveGraceController {\n private pending: Pending | undefined\n private probing = false\n private stopped = false\n private transitions = 0\n private readonly graceMs: number\n\n constructor(\n private readonly hooks: ArchiveGraceHooks,\n options: ArchiveGraceOptions = {}\n ) {\n this.graceMs = options.graceMs ?? ARCHIVE_RESTORE_GRACE_MS\n }\n\n /** Detached and waiting out the grace: claims must stay suspended while this is true. */\n get detached(): boolean {\n return this.pending !== undefined\n }\n\n get pendingRootStreamId(): string | undefined {\n return this.pending?.rootStreamId\n }\n\n /**\n * Bumped on every attach/detach transition. A runtime with a request already\n * in flight snapshots this before awaiting and drops its result if the value\n * moved — otherwise a link created before the archive lands is committed\n * after it, resurrecting a link to a scratchpad that is archived\n * server-side.\n */\n get generation(): number {\n return this.transitions\n }\n\n /**\n * Poll cadence while detached, scaled so several probes always fit inside the\n * grace even when it is shortened for a test. Bounded by the window, so it\n * cannot become a quota burn.\n */\n get probeDelayMs(): number {\n return Math.min(ARCHIVE_RESTORE_PROBE_MS, Math.max(Math.floor(this.graceMs / 4), 10))\n }\n\n /**\n * This root is archived (a `bot:session_archived` push, or a probe that\n * found `archivedAt`). Callers scope the event to their own runtime session\n * and current root before calling — a stale event for a retired root must\n * never wind down the scratchpad now linked.\n */\n async archived(rootStreamId: string): Promise<void> {\n if (this.stopped || this.pending) return\n const pending: Pending = {\n rootStreamId,\n deadline: setTimeout(() => void this.windDown(pending), this.graceMs),\n }\n this.pending = pending\n this.transitions += 1\n this.hooks.log(\n `scratchpad ${rootStreamId} archived — detaching (reattaches if unarchived within ${Math.round(this.graceMs / 1000)}s)`\n )\n await this.hooks.onDetached(rootStreamId, this.graceMs)\n }\n\n /**\n * The scratchpad came back (a `bot:session_restored` push). Revives the link\n * and cancels the wind-down; a transient failure keeps the detached state so\n * the probe cadence retries inside the remaining window.\n */\n async restored(): Promise<void> {\n const pending = this.pending\n if (this.stopped || !pending) return\n // The server says the scratchpad is back, so restart the clock even if the\n // relink below fails transiently: winding down 1s after an unarchive push\n // because the grace happened to be nearly spent is the exact mis-click\n // recovery this window exists for. Same `pending` object, so every\n // post-await identity check still holds.\n clearTimeout(pending.deadline)\n pending.deadline = setTimeout(() => void this.windDown(pending), this.graceMs)\n await this.attemptReattach(pending)\n }\n\n /**\n * The poll-tick backstop. `bot:session_archived` is a one-shot push with no\n * replay, so a runtime whose socket was down when the archive landed would\n * otherwise hold its worktree forever. Re-derives from the server and drives\n * whichever direction applies.\n */\n async probe(currentRootStreamId: string | undefined): Promise<void> {\n if (this.stopped || this.probing) return\n this.probing = true\n try {\n const pending = this.pending\n if (pending) {\n await this.attemptReattach(pending)\n return\n }\n if (!currentRootStreamId) return\n const archived = await this.hooks.isArchived(currentRootStreamId)\n if (archived !== true) return\n // The awaits above can race a real push or a relink onto another root.\n if (this.stopped || this.pending) return\n this.hooks.log(`archive backstop: ${currentRootStreamId} is archived with no push received`)\n await this.archived(currentRootStreamId)\n } catch (error) {\n this.hooks.log(`archive probe failed: ${describe(error)}`)\n } finally {\n this.probing = false\n }\n }\n\n /** Teardown. Disarms the deadline so a shutting-down runtime cannot wind down behind itself. */\n stop(): void {\n this.stopped = true\n if (this.pending) clearTimeout(this.pending.deadline)\n this.pending = undefined\n }\n\n private async attemptReattach(pending: Pending): Promise<void> {\n let reattached = false\n try {\n reattached = await this.hooks.reattach(pending.rootStreamId)\n } catch (error) {\n this.hooks.log(`reattach failed, staying detached: ${describe(error)}`)\n return\n }\n // The deadline can fire, or a second caller can win, while the request\n // above is in flight. Reattaching against a session that already wound\n // down would resurrect a dead worktree.\n if (!reattached || this.stopped || this.pending !== pending) return\n clearTimeout(pending.deadline)\n this.pending = undefined\n this.transitions += 1\n this.hooks.log(`scratchpad ${pending.rootStreamId} restored — reattached`)\n await this.hooks.onReattached(pending.rootStreamId)\n }\n\n private async windDown(pending: Pending): Promise<void> {\n if (this.stopped || this.pending !== pending) return\n clearTimeout(pending.deadline)\n this.pending = undefined\n this.transitions += 1\n this.hooks.log(`scratchpad ${pending.rootStreamId} stayed archived — winding down`)\n try {\n await this.hooks.onWindDown(pending.rootStreamId)\n } catch (error) {\n this.hooks.log(`wind-down failed: ${describe(error)}`)\n }\n }\n}\n\nfunction describe(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n",
|
|
11
|
+
"import { join } from \"node:path\"\n\nconst UNSAFE_SEGMENT_CHARS = /[\\\\/:*?\"<>|]/g\n/** ext4/APFS cap a path component at 255 BYTES, not characters — a 180-char CJK name is 540. */\nconst MAX_SEGMENT_BYTES = 180\nconst MAX_EXTENSION_BYTES = 16\nconst encoder = new TextEncoder()\n\nfunction byteLength(value: string): number {\n return encoder.encode(value).length\n}\n\nfunction truncateToBytes(value: string, maxBytes: number): string {\n if (byteLength(value) <= maxBytes) return value\n let out = \"\"\n let bytes = 0\n for (const char of value) {\n const size = byteLength(char)\n if (bytes + size > maxBytes) break\n out += char\n bytes += size\n }\n return out\n}\n\nfunction clean(value: string): string {\n return value.replace(UNSAFE_SEGMENT_CHARS, \"_\").replace(/^\\.+$/, \"_\")\n}\n\nfunction safeSegment(value: string, fallback: string): string {\n return truncateToBytes(clean(value), MAX_SEGMENT_BYTES) || fallback\n}\n\n/**\n * One downloaded attachment's filename, stripped of path separators and\n * Windows-hostile characters. An over-long name loses stem, never extension:\n * the agent (and a `THREA_ATTACH:` re-upload) picks the mime type off the\n * suffix, so a truncated `.png` would land as `application/octet-stream`.\n */\nexport function safeAttachmentFilename(filename: string): string {\n const cleaned = clean(filename)\n const dot = cleaned.lastIndexOf(\".\")\n const extension = dot > 0 ? cleaned.slice(dot) : \"\"\n if (!extension || byteLength(extension) > MAX_EXTENSION_BYTES) {\n return truncateToBytes(cleaned, MAX_SEGMENT_BYTES) || \"attachment\"\n }\n const stem = truncateToBytes(cleaned.slice(0, dot), MAX_SEGMENT_BYTES - byteLength(extension))\n return `${stem}${extension}`\n}\n\n/**\n * A downloaded attachment lands in a per-attachment-id subdirectory: filenames\n * are not unique (the same `image.png` pasted into two messages, or one file\n * carried by both the source and a context message), so a flat directory\n * silently clobbers the earlier download. The leaf keeps the original filename\n * so a re-upload round-trips the name and extension unchanged.\n */\nexport function attachmentLocalPath(dir: string, attachmentId: string, filename: string): string {\n return join(dir, safeSegment(attachmentId, \"attachment\"), safeAttachmentFilename(filename))\n}\n",
|
|
12
|
+
"/**\n * Read and write an end-to-end-encrypted stream over Threa's public API.\n *\n * The sealed-turn path in `./sealed` covers a bot answering an invocation: the\n * backend hands it ciphertext and SSK wraps on the claim. Everything else — a\n * CLI reading its owner's scratchpad, a bot posting into a sealed stream it was\n * granted but was not invoked in — has to fetch the wraps itself, unwrap the\n * stream key, and open or seal each body. That is this client: the crypto is\n * the same module, but the keys come from the caller's own keyring and the\n * transport is the public HTTP API.\n *\n * Threads inherit their root scratchpad's key and carry no wraps of their own,\n * so every id is resolved to its root before any key work — pass a thread id\n * and it still reads and seals correctly.\n */\n\nimport { ulid } from \"ulid\"\nimport {\n base64ToBytes,\n buildMessageAad,\n buildWrapAad,\n bytesToBase64,\n importRecipientPrivateKey,\n openMessageAsString,\n parseSealedPayload,\n sealMessage,\n serializeSealedPayload,\n unwrapStreamKey,\n type AttachmentRef,\n type StreamEnvelope,\n type WebCryptoKey,\n} from \"./crypto\"\nimport type { E2eKeyring } from \"./keyring\"\n\n/** One key this client can open a wrap with. */\nexport interface SealedKeyIdentity {\n /** The id wraps are addressed to — a BIK's `publicKeyId`, or a user's UIK key id. */\n keyId: string\n privateKey: WebCryptoKey\n}\n\n/**\n * Where the client's private keys come from. A bot passes its runtime keyring;\n * an interactive client passes whatever it unlocked. Called per stream because\n * the per-stream key policy mints one key per sealed stream.\n */\nexport interface SealedKeySource {\n keysForStream(streamId: string): Promise<SealedKeyIdentity[]>\n}\n\n/** Bridge a runtime's {@link E2eKeyring} into a {@link SealedKeySource}. */\nexport function keyringKeySource(keyring: E2eKeyring): SealedKeySource {\n const imported = new Map<string, Promise<WebCryptoKey>>()\n return {\n async keysForStream(streamId: string): Promise<SealedKeyIdentity[]> {\n await keyring.ensureForStream(streamId)\n const held = keyring.forStream(streamId)\n if (!held) return []\n let privateKey = imported.get(held.keyId)\n if (!privateKey) {\n privateKey = importRecipientPrivateKey(base64ToBytes(held.privateKey))\n imported.set(held.keyId, privateKey)\n }\n return [{ keyId: held.keyId, privateKey: await privateKey }]\n },\n }\n}\n\n/**\n * One message from a sealed stream. `contentMarkdown` is the opened body;\n * it is `null` exactly when this client could not open the row, and\n * `unreadableReason` then says why — a row sealed under the pre-stream-key\n * scheme, or a generation no key here is wrapped to. Neither is fatal for the\n * rest of the page, so the row is reported rather than thrown.\n */\nexport interface SealedStreamMessage {\n id: string\n sequence: string\n authorId: string\n authorType: string\n authorDisplayName?: string\n createdAt: string\n contentMarkdown: string | null\n attachmentRefs: AttachmentRef[]\n unreadableReason?: string\n}\n\nexport interface SealedStreamPage {\n messages: SealedStreamMessage[]\n hasMore: boolean\n}\n\nexport interface SealedStreamClientOptions {\n /** Workspace API origin, e.g. `https://eu.threa.io`. */\n baseUrl: string\n apiKey: string\n workspaceId: string\n keys: SealedKeySource\n /**\n * The actor id bound into outgoing message AAD. Resolved from\n * `GET /me` when omitted, which is what a caller holding only a key knows.\n */\n senderId?: string\n fetch?: typeof globalThis.fetch\n}\n\n/** The `sealed` field a message row carries in place of readable content. */\nexport interface SealedMessageBody {\n ciphertext: string\n envelope: StreamEnvelope\n}\n\n/** What opening one {@link SealedMessageBody} produced. */\nexport interface OpenedSealedBody {\n contentMarkdown: string | null\n attachmentRefs: AttachmentRef[]\n unreadableReason?: string\n}\n\ninterface WireMessage {\n id: string\n sequence: string\n authorId: string\n authorType: string\n authorDisplayName?: string\n createdAt: string\n sealed?: SealedMessageBody\n}\n\ninterface WireWraps {\n currentKeyGeneration: number\n wraps: { keyGeneration: number; recipientKeyId: string; wrapEnc: string; wrapCt: string }[]\n}\n\n/** A non-2xx from the API, carrying the `code` the wire named. */\nexport class SealedStreamApiError extends Error {\n readonly status: number\n readonly code: string\n\n constructor(message: string, status: number, code: string) {\n super(message)\n this.name = \"SealedStreamApiError\"\n this.status = status\n this.code = code\n }\n}\n\nfunction reasonOf(error: unknown): string {\n return String(error instanceof Error ? error.message : error)\n}\n\nexport class SealedStreamClient {\n private readonly opts: SealedStreamClientOptions\n private readonly doFetch: typeof globalThis.fetch\n private readonly roots = new Map<string, Promise<string>>()\n private readonly ssks = new Map<string, Uint8Array>()\n private readonly generations = new Map<string, number>()\n private sender?: Promise<string>\n\n constructor(opts: SealedStreamClientOptions) {\n this.opts = opts\n this.doFetch = opts.fetch ?? globalThis.fetch\n }\n\n /**\n * One page of decrypted messages, newest-last. `before`/`after` take the\n * `sequence` of a message already held, exactly as the plaintext list does.\n */\n async readMessages(\n streamId: string,\n opts: { limit?: number; before?: string; after?: string } = {}\n ): Promise<SealedStreamPage> {\n const root = await this.resolveRoot(streamId)\n const query = new URLSearchParams()\n if (opts.limit !== undefined) query.set(\"limit\", String(opts.limit))\n if (opts.before) query.set(\"before\", opts.before)\n if (opts.after) query.set(\"after\", opts.after)\n const suffix = query.size > 0 ? `?${query.toString()}` : \"\"\n const page = await this.request<{ data: WireMessage[]; hasMore: boolean }>(\n \"GET\",\n `/streams/${streamId}/messages${suffix}`\n )\n const messages: SealedStreamMessage[] = []\n for (const wire of page.data) {\n messages.push(await this.openMessage(root, wire))\n }\n return { messages, hasMore: page.hasMore }\n }\n\n /**\n * Seal `contentMarkdown` under the stream key and post it. The returned\n * `messageId` is the server's row id; `clientMessageId` is the id this client\n * minted, bound into the body's AAD and reusable to retry the send.\n */\n async sendMessage(\n streamId: string,\n contentMarkdown: string,\n opts: { attachmentRefs?: AttachmentRef[]; clientMessageId?: string } = {}\n ): Promise<{ messageId: string; clientMessageId: string }> {\n const root = await this.resolveRoot(streamId)\n const keyGeneration = await this.currentGeneration(root)\n const key = await this.streamKey(root, keyGeneration)\n const senderId = await this.resolveSenderId()\n const clientMessageId = opts.clientMessageId ?? `msg_${ulid()}`\n const sealed = await sealMessage({\n key,\n keyGeneration,\n payload: serializeSealedPayload(contentMarkdown, { attachmentRefs: opts.attachmentRefs }),\n aad: buildMessageAad({ streamId: root, messageId: clientMessageId, senderId }),\n })\n const created = await this.request<{ data: { id: string } }>(\"POST\", `/streams/${streamId}/messages`, {\n sealed: { ciphertext: bytesToBase64(sealed.ciphertext), envelope: sealed.envelope },\n clientMessageId,\n })\n return { messageId: created.data.id, clientMessageId }\n }\n\n /**\n * Open one sealed body for a caller that fetched the row itself — a client\n * reading the plaintext list route, where sealed rows arrive beside an opaque\n * placeholder. `streamId` may be the thread the row lives in; its root's key\n * is what opens it.\n *\n * A body this client cannot open comes back with a null `contentMarkdown` and\n * a reason rather than throwing, so one unreadable generation never costs the\n * caller the rest of the page.\n */\n async openSealedBody(streamId: string, sealed: SealedMessageBody): Promise<OpenedSealedBody> {\n const root = await this.resolveRoot(streamId)\n return this.openBody(root, sealed)\n }\n\n private async openBody(root: string, sealed: SealedMessageBody): Promise<OpenedSealedBody> {\n let key: Uint8Array\n try {\n key = await this.streamKey(root, sealed.envelope.keyGeneration)\n } catch (error) {\n return { contentMarkdown: null, attachmentRefs: [], unreadableReason: reasonOf(error) }\n }\n try {\n const raw = await openMessageAsString({\n key,\n ciphertext: base64ToBytes(sealed.ciphertext),\n envelope: sealed.envelope,\n })\n const payload = parseSealedPayload(raw)\n return { contentMarkdown: payload.contentMarkdown, attachmentRefs: payload.attachmentRefs ?? [] }\n } catch (error) {\n return { contentMarkdown: null, attachmentRefs: [], unreadableReason: reasonOf(error) }\n }\n }\n\n private async openMessage(root: string, wire: WireMessage): Promise<SealedStreamMessage> {\n const base = {\n id: wire.id,\n sequence: wire.sequence,\n authorId: wire.authorId,\n authorType: wire.authorType,\n ...(wire.authorDisplayName ? { authorDisplayName: wire.authorDisplayName } : {}),\n createdAt: wire.createdAt,\n attachmentRefs: [] as AttachmentRef[],\n }\n if (!wire.sealed) {\n return { ...base, contentMarkdown: null, unreadableReason: \"Message carries no stream-key envelope\" }\n }\n return { ...base, ...(await this.openBody(root, wire.sealed)) }\n }\n\n /**\n * The stream key for one generation. Wraps are fetched once per stream and\n * every generation they cover is unwrapped in that pass, so a page spanning a\n * rotation costs one round trip.\n */\n private async streamKey(root: string, keyGeneration: number): Promise<Uint8Array> {\n const cached = this.ssks.get(`${root}:${keyGeneration}`)\n if (cached) return cached\n await this.loadWraps(root)\n const key = this.ssks.get(`${root}:${keyGeneration}`)\n if (!key) {\n throw new Error(`No key wrap for generation ${keyGeneration} of ${root} is addressed to a key this client holds`)\n }\n return key\n }\n\n /**\n * Never cached: sealing under a stale generation after the owner rolled the\n * key would hand the revoked generation's holders a readable message, and the\n * send path validates only the envelope's shape.\n */\n private async currentGeneration(root: string): Promise<number> {\n await this.loadWraps(root)\n return this.generations.get(root) ?? 0\n }\n\n private async loadWraps(root: string): Promise<void> {\n const identities = await this.opts.keys.keysForStream(root)\n if (identities.length === 0) {\n throw new Error(`No end-to-end key available for ${root}`)\n }\n const wraps = await this.request<{ data: WireWraps }>(\"GET\", `/streams/${root}/e2e/key-wraps`)\n this.generations.set(root, wraps.data.currentKeyGeneration)\n for (const wrap of wraps.data.wraps) {\n const identity = identities.find((candidate) => candidate.keyId === wrap.recipientKeyId)\n if (!identity) continue\n if (this.ssks.has(`${root}:${wrap.keyGeneration}`)) continue\n const key = await unwrapStreamKey({\n enc: base64ToBytes(wrap.wrapEnc),\n ct: base64ToBytes(wrap.wrapCt),\n recipientPrivateKey: identity.privateKey,\n aad: buildWrapAad({\n streamId: root,\n keyGeneration: wrap.keyGeneration,\n recipientKeyId: wrap.recipientKeyId,\n }),\n })\n this.ssks.set(`${root}:${wrap.keyGeneration}`, key)\n }\n }\n\n /** A thread's key lives on its root; a root resolves to itself. */\n private async resolveRoot(streamId: string): Promise<string> {\n let pending = this.roots.get(streamId)\n if (!pending) {\n pending = this.request<{ data: { id: string; rootStreamId?: string } }>(\"GET\", `/streams/${streamId}`)\n .then((stream) => stream.data.rootStreamId ?? stream.data.id)\n .catch((error: unknown) => {\n this.roots.delete(streamId)\n throw error\n })\n this.roots.set(streamId, pending)\n }\n return pending\n }\n\n private async resolveSenderId(): Promise<string> {\n if (this.opts.senderId) return this.opts.senderId\n if (!this.sender) {\n this.sender = this.request<{ data: { kind: string; userId?: string; botId?: string } }>(\"GET\", \"/me\")\n .then((me) => {\n const id = me.data.kind === \"bot\" ? me.data.botId : me.data.userId\n if (!id) throw new Error(\"GET /me named no principal id\")\n return id\n })\n .catch((error: unknown) => {\n this.sender = undefined\n throw error\n })\n }\n return this.sender\n }\n\n private async request<T>(method: string, path: string, body?: unknown): Promise<T> {\n const url = `${this.opts.baseUrl.replace(/\\/$/, \"\")}/api/v1/workspaces/${this.opts.workspaceId}${path}`\n const response = await this.doFetch(url, {\n method,\n headers: {\n Authorization: `Bearer ${this.opts.apiKey}`,\n ...(body === undefined ? {} : { \"Content-Type\": \"application/json\" }),\n },\n ...(body === undefined ? {} : { body: JSON.stringify(body) }),\n })\n const text = await response.text()\n if (!response.ok) {\n // A gateway or proxy answers in HTML; the status and the fallback code are\n // what the caller acts on, so they must survive an unparseable body.\n let parsed: Record<string, unknown> = {}\n try {\n if (text.length > 0) parsed = JSON.parse(text) as Record<string, unknown>\n } catch {\n parsed = {}\n }\n throw new SealedStreamApiError(\n typeof parsed.message === \"string\" ? parsed.message : `${method} ${path} failed`,\n response.status,\n typeof parsed.code === \"string\" ? parsed.code : \"UNKNOWN\"\n )\n }\n return (text.length > 0 ? (JSON.parse(text) as Record<string, unknown>) : {}) as T\n }\n}\n",
|
|
13
|
+
"/**\n * The E2E keyring a bot runtime holds: the X25519 identity keys an owner wraps\n * a sealed stream's key to, and where they live on the operator's machine.\n *\n * A runtime used to hold exactly one key per install. It now advertises a\n * keyring, so several runtimes on one box can share a single key (the default —\n * one key per host) and a key can be pinned to one stream. The server stores\n * the advertised set as the instance's complete keyring and wraps every sealed\n * stream key to each eligible member.\n *\n * Keys are secrets, so where they are kept is the operator's explicit choice:\n * the OS keychain (driven through its command-line tool, which survives a\n * runtime being rebuilt and reinstalled) or a `0600` file. There is no silent\n * fallback between the two — an unavailable keychain is an error naming both\n * options, not a quiet downgrade to disk.\n */\n\nimport { spawnSync } from \"node:child_process\"\nimport { createHash } from \"node:crypto\"\nimport { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from \"node:fs\"\nimport { dirname, join } from \"node:path\"\n\nexport const E2E_KEY_SCOPES = [\"host\", \"identity\", \"instance\", \"stream\"] as const\nexport type E2eKeyScope = (typeof E2E_KEY_SCOPES)[number]\n\nexport const E2E_KEY_STORE_KINDS = [\"keychain\", \"file\"] as const\nexport type E2eKeyStoreKind = (typeof E2E_KEY_STORE_KINDS)[number]\n\n/** A key as it is persisted and as it rides presence: public half plus the private key, base64. */\nexport interface E2eKeyRecord {\n keyId: string\n publicKey: string\n privateKey: string\n}\n\n/** A held key, the account it came from, and the stream it is pinned to, if any. */\nexport interface HeldE2eKey extends E2eKeyRecord {\n account: string\n streamId?: string\n}\n\n/**\n * A place a key record is kept. `createExclusive` never overwrites: when\n * another process wrote the account first its record is returned instead, so\n * two runtimes racing to mint a shared key converge on one.\n */\nexport interface E2eKeyStore {\n readonly kind: E2eKeyStoreKind\n /** Where the keys live, for the boot log — a path or the keychain service. */\n readonly describe: string\n read(account: string): E2eKeyRecord | undefined\n createExclusive(account: string, record: E2eKeyRecord): E2eKeyRecord\n /**\n * Replace whatever is filed under `account`. For a person acting on their own\n * key — unlocking it on this machine, or replacing it after a rotation. A\n * runtime converging with its peers on a shared key wants `createExclusive`,\n * which never clobbers the winner of that race.\n */\n write(account: string, record: E2eKeyRecord): void\n /** Forget the account. Silent when nothing is filed there. */\n remove(account: string): void\n}\n\nfunction hash16(value: string): string {\n return createHash(\"sha256\").update(value).digest(\"hex\").slice(0, 16)\n}\n\n/**\n * The account a scope's default key is filed under, or `null` under `stream`,\n * where there is no default: each key is minted for one sealed stream on its\n * grant and filed under {@link e2eStreamKeyAccount}.\n *\n * `host` hashes the hostname because `~/.threa` can be a home directory shared\n * across machines, and a key that followed the home directory would put every\n * box on one identity without the operator ever choosing that.\n */\nexport function e2eKeyAccount(params: {\n scope: E2eKeyScope\n hostname: string\n instanceId: string\n /** Secret that identifies the bot (its API key); hashed, never stored. */\n identitySeed: string\n}): string | null {\n switch (params.scope) {\n case \"identity\":\n return `identity-${hash16(params.identitySeed)}`\n case \"instance\":\n return `instance-${params.instanceId.replace(/[^A-Za-z0-9_-]+/g, \"-\")}`.slice(0, 96)\n case \"host\":\n return `host-${hash16(params.hostname)}`\n case \"stream\":\n return null\n }\n}\n\n/** The account one stream's key is filed under. */\nexport function e2eStreamKeyAccount(streamId: string): string {\n return `stream-${hash16(streamId)}`\n}\n\n/**\n * The account a person's own identity key is filed under, once they unlock it\n * on this machine. Separate from the bot scopes above: this is the key the web\n * app minted from their passphrase, and a CLI holding it reads their streams as\n * them, not as a runtime.\n */\nexport function e2eUserKeyAccount(workspaceId: string, userId: string): string {\n return `user-${hash16(`${workspaceId}:${userId}`)}`\n}\n\nfunction decodeRecord(raw: string): E2eKeyRecord | undefined {\n try {\n const parsed = JSON.parse(raw) as Partial<E2eKeyRecord>\n if (\n typeof parsed.keyId === \"string\" &&\n typeof parsed.publicKey === \"string\" &&\n typeof parsed.privateKey === \"string\"\n ) {\n return { keyId: parsed.keyId, publicKey: parsed.publicKey, privateKey: parsed.privateKey }\n }\n } catch {\n // Reported by the caller, which knows which account was unreadable.\n }\n return undefined\n}\n\nexport class FileKeyStore implements E2eKeyStore {\n readonly kind = \"file\" as const\n readonly describe: string\n private readonly dir: string\n\n constructor(opts: { dir: string }) {\n this.dir = opts.dir\n this.describe = opts.dir\n }\n\n private path(account: string): string {\n return join(this.dir, `${account}.json`)\n }\n\n read(account: string): E2eKeyRecord | undefined {\n const path = this.path(account)\n if (!existsSync(path)) return undefined\n return decodeRecord(readFileSync(path, \"utf8\"))\n }\n\n /**\n * Whether this directory already serves any key. The per-stream policy has no\n * single account to probe for — its keys are named after streams it has not\n * been granted yet — so store selection asks this instead.\n */\n hasAny(): boolean {\n if (!existsSync(this.dir)) return false\n return readdirSync(this.dir).some((entry) => entry.endsWith(\".json\"))\n }\n\n createExclusive(account: string, record: E2eKeyRecord): E2eKeyRecord {\n const path = this.path(account)\n mkdirSync(dirname(path), { recursive: true })\n try {\n writeFileSync(path, `${JSON.stringify(record, null, 2)}\\n`, { mode: 0o600, flag: \"wx\" })\n return record\n } catch (error) {\n if ((error as { code?: string }).code !== \"EEXIST\") throw error\n const winner = this.read(account)\n if (!winner) throw new Error(`${path} exists but could not be read`)\n return winner\n }\n }\n\n write(account: string, record: E2eKeyRecord): void {\n const path = this.path(account)\n mkdirSync(dirname(path), { recursive: true })\n writeFileSync(path, `${JSON.stringify(record, null, 2)}\\n`, { mode: 0o600 })\n }\n\n remove(account: string): void {\n rmSync(this.path(account), { force: true })\n }\n}\n\ninterface CommandResult {\n status: number\n stdout: string\n stderr: string\n /** The command could not be run at all (binary missing). */\n unavailable: boolean\n}\n\n// A locked keyring answers a lookup or store by raising a password prompt and\n// waiting on it. On a headless box nobody sees the prompt, and spawnSync holds\n// the event loop the whole time, so the host's MCP handshake starves with it.\nexport const KEYCHAIN_COMMAND_TIMEOUT_MS = 5_000\n\nexport function runKeychainCommand(\n command: string,\n args: string[],\n input?: string,\n timeoutMs: number = KEYCHAIN_COMMAND_TIMEOUT_MS\n): CommandResult {\n const result = spawnSync(command, args, { encoding: \"utf8\", input, timeout: timeoutMs })\n if ((result.error as { code?: string } | undefined)?.code === \"ETIMEDOUT\") {\n return {\n status: -1,\n stdout: \"\",\n stderr: `${command} gave no answer within ${timeoutMs}ms; the keyring is probably locked and waiting on a password prompt`,\n unavailable: true,\n }\n }\n if (result.error) {\n return { status: -1, stdout: \"\", stderr: String(result.error), unavailable: true }\n }\n return { status: result.status ?? -1, stdout: result.stdout ?? \"\", stderr: result.stderr ?? \"\", unavailable: false }\n}\n\nexport type CommandRunner = (command: string, args: string[], input?: string) => CommandResult\n\n/** Keychain access is base64 so the record survives tools that split on whitespace or quotes. */\nfunction encodeSecret(record: E2eKeyRecord): string {\n return Buffer.from(JSON.stringify(record), \"utf8\").toString(\"base64\")\n}\n\nfunction decodeSecret(raw: string): E2eKeyRecord | undefined {\n const trimmed = raw.trim()\n if (!trimmed) return undefined\n return decodeRecord(Buffer.from(trimmed, \"base64\").toString(\"utf8\"))\n}\n\nconst KEYCHAIN_SERVICE = \"threa-e2e\"\n\n/**\n * The macOS keychain, driven through `/usr/bin/security`. The command-line tool\n * rather than an in-process keychain API on purpose: a keychain item is bound\n * to the signature of the process that created it, so a runtime that is rebuilt\n * and reinstalled loses access to its own key — `security` is a stable system\n * binary and keeps it.\n */\nconst sameRecord = (a: E2eKeyRecord, b: E2eKeyRecord): boolean =>\n a.keyId === b.keyId && a.publicKey === b.publicKey && a.privateKey === b.privateKey\n\nexport class MacKeychainStore implements E2eKeyStore {\n readonly kind = \"keychain\" as const\n readonly describe = `macOS keychain (service ${KEYCHAIN_SERVICE})`\n private readonly exec: CommandRunner\n\n constructor(opts: { exec?: CommandRunner } = {}) {\n this.exec = opts.exec ?? runKeychainCommand\n }\n\n read(account: string): E2eKeyRecord | undefined {\n const result = this.exec(\"/usr/bin/security\", [\n \"find-generic-password\",\n \"-s\",\n KEYCHAIN_SERVICE,\n \"-a\",\n account,\n \"-w\",\n ])\n if (result.unavailable) throw new Error(`macOS keychain unavailable: ${result.stderr}`)\n if (result.status !== 0) return undefined\n return decodeSecret(result.stdout)\n }\n\n createExclusive(account: string, record: E2eKeyRecord): E2eKeyRecord {\n // The secret rides stdin (`security -i` reads commands there) so it never\n // appears in this process's argv, where any local `ps` would read it.\n const result = this.exec(\n \"/usr/bin/security\",\n [\"-i\"],\n `add-generic-password -s ${KEYCHAIN_SERVICE} -a ${account} -w ${encodeSecret(record)}\\n`\n )\n if (result.unavailable) throw new Error(`macOS keychain unavailable: ${result.stderr}`)\n const stored = this.read(account)\n if (!stored) throw new Error(`macOS keychain accepted no key for ${account}: ${result.stderr || result.stdout}`)\n return stored\n }\n\n write(account: string, record: E2eKeyRecord): void {\n // `-U` updates in place; without it `add-generic-password` fails on an\n // account that already exists. Secret on stdin, as above.\n const result = this.exec(\n \"/usr/bin/security\",\n [\"-i\"],\n `add-generic-password -U -s ${KEYCHAIN_SERVICE} -a ${account} -w ${encodeSecret(record)}\\n`\n )\n if (result.unavailable) throw new Error(`macOS keychain unavailable: ${result.stderr}`)\n if (result.status !== 0) {\n throw new Error(`macOS keychain rejected the key for ${account}: ${result.stderr || result.stdout}`)\n }\n const stored = this.read(account)\n if (!stored || !sameRecord(stored, record)) {\n throw new Error(`macOS keychain did not store the key for ${account}: ${result.stderr || result.stdout}`)\n }\n }\n\n remove(account: string): void {\n const result = this.exec(\"/usr/bin/security\", [\"delete-generic-password\", \"-s\", KEYCHAIN_SERVICE, \"-a\", account])\n if (result.unavailable) throw new Error(`macOS keychain unavailable: ${result.stderr}`)\n // A non-zero status here is \"no such item\", which is the state asked for.\n if (this.read(account)) throw new Error(`macOS keychain kept the key for ${account}: ${result.stderr}`)\n }\n}\n\n/**\n * The freedesktop Secret Service, driven through `secret-tool`. `store`\n * overwrites, so exclusivity is a read before the write and a read after it:\n * the value that comes back is the one every process on this box will use.\n */\nexport class SecretServiceStore implements E2eKeyStore {\n readonly kind = \"keychain\" as const\n readonly describe = `Secret Service keyring (service ${KEYCHAIN_SERVICE})`\n private readonly exec: CommandRunner\n\n constructor(opts: { exec?: CommandRunner } = {}) {\n this.exec = opts.exec ?? runKeychainCommand\n }\n\n read(account: string): E2eKeyRecord | undefined {\n const result = this.exec(\"secret-tool\", [\"lookup\", \"service\", KEYCHAIN_SERVICE, \"account\", account])\n if (result.unavailable) throw new Error(`Secret Service unavailable: ${result.stderr}`)\n if (result.status !== 0) return undefined\n return decodeSecret(result.stdout)\n }\n\n createExclusive(account: string, record: E2eKeyRecord): E2eKeyRecord {\n const existing = this.read(account)\n if (existing) return existing\n const result = this.exec(\n \"secret-tool\",\n [\"store\", \"--label\", `Threa E2E key ${account}`, \"service\", KEYCHAIN_SERVICE, \"account\", account],\n encodeSecret(record)\n )\n if (result.unavailable) throw new Error(`Secret Service unavailable: ${result.stderr}`)\n const stored = this.read(account)\n if (!stored) throw new Error(`Secret Service stored no key for ${account}: ${result.stderr || result.stdout}`)\n return stored\n }\n\n write(account: string, record: E2eKeyRecord): void {\n const result = this.exec(\n \"secret-tool\",\n [\"store\", \"--label\", `Threa E2E key ${account}`, \"service\", KEYCHAIN_SERVICE, \"account\", account],\n encodeSecret(record)\n )\n if (result.unavailable) throw new Error(`Secret Service unavailable: ${result.stderr}`)\n if (result.status !== 0) {\n throw new Error(`Secret Service rejected the key for ${account}: ${result.stderr || result.stdout}`)\n }\n const stored = this.read(account)\n if (!stored || !sameRecord(stored, record)) {\n throw new Error(`Secret Service did not store the key for ${account}: ${result.stderr || result.stdout}`)\n }\n }\n\n remove(account: string): void {\n const result = this.exec(\"secret-tool\", [\"clear\", \"service\", KEYCHAIN_SERVICE, \"account\", account])\n if (result.unavailable) throw new Error(`Secret Service unavailable: ${result.stderr}`)\n if (this.read(account)) throw new Error(`Secret Service kept the key for ${account}: ${result.stderr}`)\n }\n}\n\nexport interface ResolveKeyStoreInput {\n /** The operator's explicit choice. Unset lets an available keychain win. */\n requested?: E2eKeyStoreKind\n platform: NodeJS.Platform\n /** Where a file store keeps its keys. */\n dir: string\n /** Already-persisted key material, when found: an existing file store keeps serving it. */\n hasExistingFileKey: boolean\n exec?: CommandRunner\n}\n\n/**\n * Pick the store for this machine. An explicit `keychain` that cannot run is an\n * error rather than a quiet move to disk (INV-11), and with nothing explicit a\n * box without a working keychain is asked to choose instead of being given one.\n */\nexport function resolveKeyStore(input: ResolveKeyStoreInput): E2eKeyStore {\n const keychain = (): E2eKeyStore =>\n input.platform === \"darwin\"\n ? new MacKeychainStore({ exec: input.exec })\n : new SecretServiceStore({ exec: input.exec })\n\n if (input.requested === \"file\") return new FileKeyStore({ dir: input.dir })\n if (input.requested === \"keychain\") {\n const store = keychain()\n store.read(\"threa-probe\")\n return store\n }\n\n if (input.hasExistingFileKey) return new FileKeyStore({ dir: input.dir })\n const store = keychain()\n try {\n store.read(\"threa-probe\")\n return store\n } catch (error) {\n throw new Error(\n `No OS keychain available for Threa's end-to-end keys (${String(error)}). ` +\n `Set keyStore to \"keychain\" once one is installed and unlocked, or \"file\" to keep them in ${input.dir} at mode 0600.`\n )\n }\n}\n\nexport interface E2eKeyringOptions {\n store: E2eKeyStore\n /**\n * The account the unscoped default key is filed under; see\n * {@link e2eKeyAccount}. `null` selects the per-stream policy: no default\n * key, one minted per sealed stream the bot is granted.\n */\n account: string | null\n /** Mints a fresh record when the account is empty. */\n mint: () => Promise<E2eKeyRecord>\n /**\n * A single-key file from before the keyring. When the account holds nothing\n * and this does, the old key is adopted under the new account: its id is the\n * address of every wrap an owner already made, so minting a fresh one instead\n * would strand every sealed stream the runtime serves.\n */\n legacy?: () => E2eKeyRecord | undefined\n log?: (message: string) => void\n}\n\n/**\n * This runtime's keyring. `ensure()` loads or mints the default key once;\n * `ensureForStream()` adds a stream-pinned key under the per-stream policy. The\n * result is what rides `bot:hello` and every presence write, where the server\n * reads it as the instance's complete set.\n */\nexport class E2eKeyring {\n private readonly opts: E2eKeyringOptions\n private readonly log: (message: string) => void\n private held: HeldE2eKey[] = []\n private inFlight = new Map<string, Promise<HeldE2eKey[]>>()\n private loaded = false\n\n constructor(opts: E2eKeyringOptions) {\n this.opts = opts\n this.log = opts.log ?? ((message) => console.error(message))\n }\n\n /** The loaded keys; empty until `ensure()` resolves. */\n get current(): HeldE2eKey[] {\n return this.held\n }\n\n async ensure(): Promise<HeldE2eKey[]> {\n if (this.loaded) return this.held\n const account = this.opts.account\n if (account === null) {\n // Per-stream: nothing to hold until the first grant arrives.\n this.loaded = true\n return this.held\n }\n await this.loadAccount(account, undefined)\n this.loaded = this.held.length > 0\n return this.held\n }\n\n /**\n * The key this runtime reads `streamId` with. Under the default policy that\n * is the unscoped key, which already covers every stream, so this is a no-op.\n * Under the per-stream policy it mints one key for this stream — the owner's\n * next re-wrap addresses the stream key to it.\n */\n async ensureForStream(streamId: string): Promise<HeldE2eKey[]> {\n if (this.opts.account !== null) return this.ensure()\n return this.loadAccount(e2eStreamKeyAccount(streamId), streamId)\n }\n\n /**\n * Forget this stream's key: drop it from the held set and from the store.\n * Under the default policy the key covers every stream this runtime serves,\n * so a revoke on one of them must leave it alone — only a key minted FOR the\n * revoked stream is dead, and only once its wraps are gone server-side.\n */\n dropStream(streamId: string): E2eKeyRecord[] {\n const held = this.held.find((key) => key.streamId === streamId)\n if (!held) return this.held\n this.held = this.held.filter((key) => key !== held)\n this.opts.store.remove(held.account)\n this.log(`Threa sealed: dropped ${held.keyId}, the key for revoked stream ${streamId}`)\n return this.held\n }\n\n /**\n * The key a wrap for `streamId` must be addressed to, once `ensureForStream`\n * has resolved. Under the default policy that is the unscoped key whatever\n * the stream; under the per-stream policy picking the first held key would\n * address another stream's.\n */\n forStream(streamId: string): HeldE2eKey | undefined {\n if (this.opts.account !== null) return this.held.find((key) => !key.streamId)\n return this.held.find((key) => key.streamId === streamId)\n }\n\n /**\n * The keyring as it rides presence. `publicKey`/`publicKeyId` carry the\n * default key as well: a server from before the registry reads only those,\n * and both name the same key, so a mixed-version rollout addresses one key\n * either way. Under the per-stream policy there is no default key, so those\n * two are omitted rather than naming a stream key an old server would\n * register as covering everything.\n */\n presenceFields():\n | { e2eKeys: { keyId: string; publicKey: string; streamId?: string }[]; publicKey?: string; publicKeyId?: string }\n | Record<string, never> {\n if (this.held.length === 0) return {}\n const e2eKeys = this.held.map((key) => ({\n keyId: key.keyId,\n publicKey: key.publicKey,\n ...(key.streamId ? { streamId: key.streamId } : {}),\n }))\n const unscoped = this.held.find((key) => !key.streamId)\n return unscoped ? { e2eKeys, publicKey: unscoped.publicKey, publicKeyId: unscoped.keyId } : { e2eKeys }\n }\n\n /**\n * Load or mint one account's key and fold it into the held set. Concurrent\n * callers for the same account share one attempt: boot presence and\n * `bot:hello` both ensure, and a grant can arrive while either is in flight,\n * so without this they would each mint past the cache check and race the\n * store.\n */\n private async loadAccount(account: string, streamId: string | undefined): Promise<HeldE2eKey[]> {\n const existing = this.held.find((key) => key.account === account)\n if (existing) return this.held\n let attempt = this.inFlight.get(account)\n if (!attempt) {\n attempt = this.mintAccount(account, streamId).finally(() => this.inFlight.delete(account))\n this.inFlight.set(account, attempt)\n }\n return attempt\n }\n\n private async mintAccount(account: string, streamId: string | undefined): Promise<HeldE2eKey[]> {\n const record = this.opts.store.read(account) ?? (await this.createRecord(account))\n if (!this.held.some((key) => key.account === account)) {\n this.held = [...this.held, { ...record, account, ...(streamId ? { streamId } : {}) }]\n }\n return this.held\n }\n\n private async createRecord(account: string): Promise<E2eKeyRecord> {\n // Only the default key adopts the pre-keyring file. Filing that one record\n // under a second account too would advertise one key id twice, which the\n // server rejects as a duplicate keyring entry.\n const legacy = account === this.opts.account ? this.opts.legacy?.() : undefined\n if (legacy) {\n const adopted = this.opts.store.createExclusive(account, legacy)\n this.log(\n `Threa sealed: adopted this install's existing key ${adopted.keyId} into ${this.opts.store.describe} as ${account}`\n )\n return adopted\n }\n const minted = this.opts.store.createExclusive(account, await this.opts.mint())\n this.log(`Threa sealed: end-to-end key ${minted.keyId} is in ${this.opts.store.describe} as ${account}`)\n return minted\n }\n}\n\n/**\n * Read the single-key BIK file a runtime used before keyrings. Its `publicKeyId`\n * is the address of every wrap the owner already made for this install, so the\n * record is adopted under the configured scope rather than replaced.\n */\nexport function readLegacyBikFile(path: string): E2eKeyRecord | undefined {\n if (!existsSync(path)) return undefined\n try {\n const parsed = JSON.parse(readFileSync(path, \"utf8\")) as Record<string, unknown>\n if (\n typeof parsed.publicKeyId === \"string\" &&\n typeof parsed.publicKey === \"string\" &&\n typeof parsed.privateKey === \"string\"\n ) {\n return { keyId: parsed.publicKeyId, publicKey: parsed.publicKey, privateKey: parsed.privateKey }\n }\n } catch {\n // An unreadable legacy file is not fatal: a fresh key is minted instead.\n }\n return undefined\n}\n",
|
|
14
|
+
"import { argon2id } from \"hash-wasm\"\nimport { importRecipientPrivateKey, type WebCryptoKey } from \"./crypto\"\n\n/**\n * Recovering a user's identity key from a passphrase, outside the browser.\n *\n * The web app wraps the UIK's private half in AES-256-GCM under an Argon2id\n * KEK and hands the server nothing but the ciphertext. A CLI on a machine that\n * has never run the web app fetches that bundle from\n * `GET /api/v1/workspaces/{ws}/me/e2e-key` and repeats the derivation here.\n *\n * Every constant below is wire format shared with\n * `apps/frontend/src/lib/crypto/{passphrase,keys}.ts`. `user-key.parity.test.ts`\n * wraps with the browser code and unwraps with this one, so drift fails CI\n * rather than locking someone out of their own streams.\n */\n\nexport interface KdfParams {\n algorithm: \"argon2id\"\n /** Memory cost in kibibytes (Argon2 `m`). */\n m: number\n /** Iteration count (Argon2 `t`). */\n t: number\n /** Parallelism degree (Argon2 `p`). */\n p: number\n /** Argon2 algorithm version. 19 = `0x13`, current as of RFC 9106. */\n version: number\n}\n\nexport const DEFAULT_KDF_PARAMS: KdfParams = {\n algorithm: \"argon2id\",\n m: 64 * 1024,\n t: 3,\n p: 1,\n version: 19,\n}\n\nconst KEK_LENGTH_BYTES = 32\nconst PRIVATE_BUNDLE_VERSION = 1\nconst IV_LENGTH = 12\nconst ARGON2_VERSION = 19\n\n/**\n * Derive the 32-byte AES-GCM key-encryption key a wrapped bundle was sealed\n * under. Non-extractable: nothing downstream needs the raw bytes, and the\n * passphrase should not become recoverable material sitting in a variable.\n */\nexport async function deriveKEK(\n passphrase: string,\n salt: Uint8Array,\n params: KdfParams = DEFAULT_KDF_PARAMS\n): Promise<WebCryptoKey> {\n if (params.algorithm !== \"argon2id\") {\n throw new Error(`Unsupported KDF algorithm: ${params.algorithm}`)\n }\n // hash-wasm implements Argon2 v1.3 only. A bundle asking for anything else\n // would derive a silently wrong KEK and surface as \"wrong passphrase\".\n if (params.version !== ARGON2_VERSION) {\n throw new Error(`Unsupported Argon2 version: ${params.version}`)\n }\n\n const raw = (await argon2id({\n password: passphrase,\n salt,\n iterations: params.t,\n parallelism: params.p,\n memorySize: params.m,\n hashLength: KEK_LENGTH_BYTES,\n outputType: \"binary\",\n })) as Uint8Array\n\n return crypto.subtle.importKey(\"raw\", new Uint8Array(raw), { name: \"AES-GCM\" }, false, [\"decrypt\"])\n}\n\n/** The GCM tag rejected the derived KEK: a wrong passphrase, or a tampered bundle. */\nexport class WrongPassphraseError extends Error {\n constructor() {\n super(\"Wrapped private bundle did not open with this passphrase\")\n this.name = \"WrongPassphraseError\"\n }\n}\n\n/**\n * Open a `[version (1) | iv (12) | AES-GCM ciphertext]` bundle and re-import\n * the X25519 private key. A tag mismatch is a `WrongPassphraseError`; a\n * malformed or unsupported bundle throws its own error, so a caller can tell\n * \"you typed it wrong\" from \"this bundle is not what we can read\".\n */\nexport async function unwrapPrivate(bundle: Uint8Array, kek: WebCryptoKey): Promise<WebCryptoKey> {\n if (bundle.length < 1 + IV_LENGTH + 1) {\n throw new Error(\"Wrapped private bundle is too short\")\n }\n const version = bundle[0]\n if (version !== PRIVATE_BUNDLE_VERSION) {\n throw new Error(`Unsupported private bundle version: ${version}`)\n }\n const iv = bundle.slice(1, 1 + IV_LENGTH)\n const ciphertext = bundle.slice(1 + IV_LENGTH)\n const plaintext = await crypto.subtle.decrypt({ name: \"AES-GCM\", iv }, kek, ciphertext).catch(() => {\n throw new WrongPassphraseError()\n })\n const privBytes = new Uint8Array(plaintext)\n return importRecipientPrivateKey(privBytes)\n}\n\nexport interface UnlockUserKeyInput {\n passphrase: string\n /** `encryptedPrivateBundle` exactly as the API returns it. */\n encryptedPrivateBundle: Uint8Array\n /** `kdfSalt` exactly as the API returns it. */\n kdfSalt: Uint8Array\n kdfParams: KdfParams\n}\n\n/** The whole passphrase → private key path in one call. */\nexport async function unlockUserKey(input: UnlockUserKeyInput): Promise<WebCryptoKey> {\n const kek = await deriveKEK(input.passphrase, input.kdfSalt, input.kdfParams)\n return unwrapPrivate(input.encryptedPrivateBundle, kek)\n}\n"
|
|
15
|
+
],
|
|
16
|
+
"mappings": "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",
|
|
17
|
+
"debugId": "A1F550552BD6E62464756E2164756E21",
|
|
18
|
+
"names": []
|
|
19
|
+
}
|