@byok-sdk/client 0.8.1 → 0.9.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/daemon/control-protocol.ts","../../src/bin/control-client.ts","../../src/bin/agent-message-mcp-server.ts","../../src/bin/byok-agent-message-mcp.ts"],"names":["fs","fsConstants"],"mappings":"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","file":"byok-agent-message-mcp.js","sourcesContent":["import { createHash, createHmac, randomBytes, timingSafeEqual } from 'node:crypto';\nimport path from 'node:path';\nimport type { TaskState } from '@byok-sdk/protocol';\nimport type { ApprovalDecision, PendingApproval } from './approvals';\nimport type { StorageCategory } from './journal/journal';\nimport type { StoragePressureState } from './journal/storage-policy';\nimport type { OperationalHealthSnapshot } from './operational-health';\nimport type { LocalAgentReleaseIdentity } from '../release-identity';\nimport type { McpToolsetConfig, McpToolsetRegistryStatus } from '../types';\n\n/**\n * M4 Phase 2: shared local-IPC contract between the daemon's control server\n * (`control-server.ts`) and the CLI's control client (`bin/control-client.ts`)\n * — frame shapes, endpoint path/pipe-name derivation, and the HMAC handshake\n * math. Both sides import from here so the two can never independently drift\n * (e.g. a mismatched HMAC label string, or a socket path computed two\n * slightly different ways).\n *\n * Transport: NDJSON (one JSON object per line) over a Unix domain socket\n * (darwin/linux) or a Windows named pipe — both addressed by the same\n * path-like string via Node's `net` module, so neither `control-server.ts`\n * nor `bin/control-client.ts` needs to special-case the transport itself,\n * only the path/pipe-name derivation below.\n */\n\nexport const CONTROL_PROTOCOL_VERSION = 1;\n\n/** Handshake must complete within this long, on both sides — see each side's own timer. */\nexport const HANDSHAKE_TIMEOUT_MS = 3000;\n\n// ---------------------------------------------------------------------------\n// Endpoint path / pipe name derivation\n// ---------------------------------------------------------------------------\n\n/**\n * Conservative soft limit for a Unix domain socket path, in UTF-8 bytes.\n * macOS's `sockaddr_un.sun_path` is 104 bytes total (including the NUL\n * terminator and any prefix the kernel reserves), so anything comfortably\n * under 104 avoids `ENAMETOOLONG` at `bind()`/`connect()` time on the\n * tightest common platform.\n */\nconst UNIX_SOCKET_PATH_SOFT_LIMIT = 100;\n\n/**\n * The one fixed root the long-path fallback below binds under. Deliberately\n * NOT `os.tmpdir()`, which reads `TMPDIR`/`TMP`/`TEMP` — an endpoint address\n * that varies with the environment is not an endpoint both sides can find,\n * and a caller may have pointed `TMPDIR` INSIDE the very tree that made the\n * natural path too long, where `os.tmpdir()` yields an address LONGER than\n * the one being escaped (`bind()` then fails `EINVAL` and the daemon degrades\n * to no control socket at all). A literal `/tmp` is POSIX-guaranteed,\n * environment-independent, and short enough that this candidate always fits\n * the budget above. Same fix, same reasons as `daemon-owner.ts`'s\n * `STORE_MUTEX_FALLBACK_ROOT`; kept as its own constant because the two\n * derivations differ in name and ownership contract and must stay\n * independently readable.\n */\nconst CONTROL_SOCKET_FALLBACK_ROOT = '/tmp';\n\nfunction shortHash(input: string): string {\n return createHash('sha256').update(input, 'utf8').digest('hex').slice(0, 16);\n}\n\n/**\n * The Unix domain socket path for a daemon rooted at `storeDir`. Prefers\n * `<storeDir>/control.sock` (keeps every one of this daemon's local state\n * files under one directory, which is already created+chmod'd 0700 by the\n * time this matters — see `control-server.ts`'s `startControlServer`);\n * falls back, whenever the natural path would risk exceeding {@link\n * UNIX_SOCKET_PATH_SOFT_LIMIT}, to a short, deterministic path nested under\n * a PER-DAEMON PRIVATE subdirectory of {@link CONTROL_SOCKET_FALLBACK_ROOT}\n * — derived from a hash of `storeDir` alone, so both the daemon and any CLI\n * invocation pointed at the same `storeDir` independently compute the\n * identical fallback path.\n *\n * That root was `os.tmpdir()` until it was proven to break both halves of\n * that sentence: it reads `TMPDIR`, so the daemon (under a service manager)\n * and the CLI (in an operator shell) derived DIFFERENT addresses for one\n * store, and under a `TMPDIR` nested in the same long tree the fallback came\n * out LONGER than the path it escaped — `bind()` `EINVAL`, and the daemon\n * ran on with no control socket at all.\n *\n * Nested one level deep (rather than a bare `<hash>.sock` file directly in\n * that shared, world-traversable root) specifically so\n * `control-server.ts`'s `bindControlEndpoint` can create+chmod that\n * subdirectory 0700 BEFORE ever binding inside it — the directory's own\n * mode gates traversal into it regardless of the socket file's own\n * (briefly default-permissioned, until the post-bind `chmod`) mode, closing\n * what would otherwise be a real window for another user on the same\n * machine to reach a socket living directly in a shared tmpdir.\n */\nexport function controlSocketPath(storeDir: string): string {\n const candidate = path.join(storeDir, 'control.sock');\n if (Buffer.byteLength(candidate, 'utf8') <= UNIX_SOCKET_PATH_SOFT_LIMIT) return candidate;\n return path.join(CONTROL_SOCKET_FALLBACK_ROOT, `byok-${shortHash(storeDir)}`, 'sock');\n}\n\n/**\n * The Windows named pipe name for a daemon identified by `productId` +\n * (`path.resolve`-normalized) `storeDir`. Named pipes have no filesystem\n * path (no stale-file cleanup concern the way Unix sockets have — see\n * `control-server.ts`), but DO share one flat namespace across the whole\n * machine, so the name must be scoped to this exact daemon instance: two\n * different products, or two different store directories (e.g. two agents\n * of the same product — see `templates/service/README.md`'s \"running\n * multiple agents\" section), must never collide. `storeDir` is resolved\n * before hashing so a trivial path-form difference (trailing slash, etc.)\n * between the two sides can't split the name.\n *\n * NOT keyed by the OS user: a WinSW-installed service runs the daemon under\n * the Windows service account (e.g. `SYSTEM`) while the operator CLI runs\n * as the interactive user, so both sides must derive the identical name\n * from the same `storeDir` alone. Impostor servers are defeated by the\n * mutual HMAC handshake below, not by pipe-name secrecy — keying by user\n * was security theater that broke the service-account topology.\n */\nexport function controlPipeName(productId: string, storeDir: string): string {\n const id = shortHash(`${productId}|${path.resolve(storeDir)}`);\n return `\\\\\\\\.\\\\pipe\\\\byok-${id}`;\n}\n\n/**\n * Dispatches to {@link controlPipeName} on `win32`, {@link controlSocketPath}\n * everywhere else. `platform` defaults to `process.platform`; overridable\n * for tests exercising a specific platform's branch on any host (mirrors\n * `lifecycle/create-service-lifecycle.ts`'s identical `platform` override —\n * the REAL win32 named-pipe semantics can only be proven on actual Windows,\n * which CI's `ipc-smoke` job does; this override just makes the PATH-CHOICE\n * logic itself testable everywhere).\n */\nexport function controlEndpointPath(productId: string, storeDir: string, platform: NodeJS.Platform = process.platform): string {\n return platform === 'win32' ? controlPipeName(productId, storeDir) : controlSocketPath(storeDir);\n}\n\n/** Where the daemon writes its per-session control-auth token (see the handshake section below). Always a real file, even on Windows (pipes have no path of their own to piggyback secrets on). */\nexport function controlTokenPath(storeDir: string): string {\n return path.join(storeDir, 'control.token');\n}\n\n// ---------------------------------------------------------------------------\n// Handshake: mutual HMAC proof, token never sent over the wire\n// ---------------------------------------------------------------------------\n\nconst SERVER_PROOF_LABEL = 'byok-control-server|';\nconst CLIENT_AUTH_LABEL = 'byok-control-client|';\n\nexport function randomNonceHex(): string {\n return randomBytes(32).toString('hex');\n}\n\nfunction hmacHex(token: string, message: string): string {\n return createHmac('sha256', token).update(message, 'utf8').digest('hex');\n}\n\n/** What the server proves to the client: it holds `token`, bound to the client's own nonce so a captured proof can't be replayed against a different handshake. */\nexport function computeServerProof(token: string, clientNonce: string): string {\n return hmacHex(token, SERVER_PROOF_LABEL + clientNonce);\n}\n\n/** What the client proves to the server, symmetrically, bound to the server's nonce. */\nexport function computeClientAuth(token: string, serverNonce: string): string {\n return hmacHex(token, CLIENT_AUTH_LABEL + serverNonce);\n}\n\n/** Constant-time hex-string comparison (`crypto.timingSafeEqual` requires equal-length buffers; a length mismatch is itself a safe, immediate \"not equal\" — no early-exit on content). */\nexport function timingSafeEqualHex(a: string, b: string): boolean {\n const bufA = Buffer.from(a, 'hex');\n const bufB = Buffer.from(b, 'hex');\n if (bufA.length !== bufB.length) return false;\n return timingSafeEqual(bufA, bufB);\n}\n\n// ---------------------------------------------------------------------------\n// Handshake frame shapes + parsers\n// ---------------------------------------------------------------------------\n\nexport interface ClientHello {\n v: 1;\n hello: 'client';\n nonce: string;\n}\nexport interface ServerHello {\n v: 1;\n hello: 'server';\n proof: string;\n nonce: string;\n}\nexport interface ClientAuth {\n v: 1;\n auth: string;\n}\nexport interface ServerReady {\n v: 1;\n ready: true;\n}\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nexport function parseClientHello(value: unknown): ClientHello | undefined {\n if (!isRecord(value)) return undefined;\n if (value.v !== CONTROL_PROTOCOL_VERSION || value.hello !== 'client' || typeof value.nonce !== 'string') return undefined;\n return { v: CONTROL_PROTOCOL_VERSION, hello: 'client', nonce: value.nonce };\n}\n\nexport function parseServerHello(value: unknown): ServerHello | undefined {\n if (!isRecord(value)) return undefined;\n if (\n value.v !== CONTROL_PROTOCOL_VERSION ||\n value.hello !== 'server' ||\n typeof value.proof !== 'string' ||\n typeof value.nonce !== 'string'\n ) {\n return undefined;\n }\n return { v: CONTROL_PROTOCOL_VERSION, hello: 'server', proof: value.proof, nonce: value.nonce };\n}\n\nexport function parseClientAuth(value: unknown): ClientAuth | undefined {\n if (!isRecord(value)) return undefined;\n if (value.v !== CONTROL_PROTOCOL_VERSION || typeof value.auth !== 'string') return undefined;\n return { v: CONTROL_PROTOCOL_VERSION, auth: value.auth };\n}\n\nexport function parseServerReady(value: unknown): ServerReady | undefined {\n if (!isRecord(value)) return undefined;\n if (value.v !== CONTROL_PROTOCOL_VERSION || value.ready !== true) return undefined;\n return { v: CONTROL_PROTOCOL_VERSION, ready: true };\n}\n\n// ---------------------------------------------------------------------------\n// RPC frame shapes (post-handshake)\n// ---------------------------------------------------------------------------\n\nexport interface RawControlRequest {\n /** Not narrowed to `1` here on purpose — an unexpected value is a `bad_version` RESPONSE, not a parse failure; see `control-server.ts`. */\n v: unknown;\n id: string;\n method: string;\n params?: unknown;\n}\n\n/** Loose shape check for an incoming request line: only `id`/`method` need to be well-formed for the server to be able to respond at all (including a `bad_version`/`unknown_method` response) — `v` is deliberately passed through unvalidated. */\nexport function parseRawControlRequest(value: unknown): RawControlRequest | undefined {\n if (!isRecord(value)) return undefined;\n if (typeof value.id !== 'string' || typeof value.method !== 'string') return undefined;\n return { v: value.v, id: value.id, method: value.method, params: value.params };\n}\n\nexport interface ControlErrorShape {\n code: string;\n message: string;\n}\n\nexport interface ControlResponseOk {\n v: 1;\n id: string;\n ok: true;\n result?: unknown;\n /** Present (and `true`) only on the final frame of a streaming method — see `control-server.ts`'s dispatch. */\n done?: true;\n}\n\nexport interface ControlResponseErr {\n v: 1;\n id: string;\n ok: false;\n error: ControlErrorShape;\n}\n\nexport type ControlResponse = ControlResponseOk | ControlResponseErr;\n\nexport interface ControlEventFrame {\n v: 1;\n id: string;\n event: unknown;\n}\n\nexport function encodeFrame(frame: unknown): string {\n return `${JSON.stringify(frame)}\\n`;\n}\n\n/** Thrown by a method handler to control the wire error `{code, message}` a caller sees — anything else thrown surfaces as a generic `internal_error`. See `control-server.ts`'s dispatch and `bin/control-client.ts`'s `request()` (which re-throws this same class on the client side). */\nexport class ControlError extends Error {\n constructor(\n public readonly code: string,\n message: string,\n ) {\n super(message);\n this.name = 'ControlError';\n }\n}\n\n// ---------------------------------------------------------------------------\n// NDJSON line framing\n// ---------------------------------------------------------------------------\n\n/**\n * Bound on a single NDJSON line's byte length. Every real frame this\n * protocol ever sends (handshake frames, requests/responses/events) is well\n * under this — it exists purely as a defensive cap against a misbehaving or\n * hostile peer streaming an unterminated line forever to grow `pending`\n * without bound. Exceeding it is a fail-closed condition: {@link\n * NdjsonLineReader.push} throws, and every caller (`control-server.ts`,\n * `bin/control-client.ts`) destroys the connection on that throw.\n */\nexport const MAX_LINE_BYTES = 64 * 1024;\n\n/**\n * Buffers raw socket bytes and yields complete lines. Splits on the raw byte\n * `0x0a` BEFORE any UTF-8 decoding (mirrors `bin/audit-log.ts`'s\n * `followAuditLog`) — `0x0A` can only ever appear as an actual newline in\n * valid UTF-8, so this never risks decoding a multi-byte character that\n * happened to straddle a chunk boundary.\n */\nexport class NdjsonLineReader {\n private pending: Buffer = Buffer.alloc(0);\n\n /** @throws if the still-unterminated remainder exceeds {@link MAX_LINE_BYTES} — see that constant's own doc comment. */\n push(chunk: Buffer): string[] {\n this.pending = this.pending.length > 0 ? Buffer.concat([this.pending, chunk]) : chunk;\n const lines: string[] = [];\n let newlineIndex: number;\n // eslint-disable-next-line no-cond-assign\n while ((newlineIndex = this.pending.indexOf(0x0a)) !== -1) {\n const line = this.pending.subarray(0, newlineIndex).toString('utf8');\n this.pending = this.pending.subarray(newlineIndex + 1);\n if (line.length > 0) lines.push(line);\n }\n if (this.pending.length > MAX_LINE_BYTES) {\n throw new Error(`NDJSON line exceeded ${MAX_LINE_BYTES} bytes without a terminating newline`);\n }\n return lines;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Method contracts (Phase 2 surface) — shared by create-daemon.ts's control\n// method registry and the CLI commands that call them.\n// ---------------------------------------------------------------------------\n\nexport interface ControlActiveTask {\n taskId: string;\n state: TaskState;\n}\n\n/**\n * M4 Phase 4 (part B.3, observability): a cheap per-active-task queue-depth\n * watermark for the `status` result. The IDEAL metric here would be each\n * runtime adapter's own event-queue depth (`util/async-queue.ts`'s\n * `AsyncQueue`) — but that queue lives inside each adapter's concrete\n * `Session` implementation, and `Session.events` (`types.ts`) is typed only\n * as a plain `AsyncIterable<AgentEvent>`, which has no queryable backlog\n * size; reaching it would mean adding a new method to the `Session`\n * interface AND implementing it in all three bundled adapters\n * (pi/claude/codex), which is out of scope for this pass. This instead\n * reflects two things `TaskRunner` already cheaply knows about the SAME\n * task without any new plumbing: how much progress is buffered locally\n * (not yet flushed as a `task.progress` batch), and how many out-of-band\n * approval requests are currently in flight for it. See\n * `task-runner.ts`'s `getQueueWatermarks` for how each field is computed.\n */\nexport interface TaskQueueWatermark {\n taskId: string;\n /** Events buffered in this task's `ProgressBatcher`, not yet flushed as a `task.progress` batch. */\n progressBatcherPending: number;\n /** Approval requests currently in flight for this task: 1 if one is actively dispatched (registered + `task.await_approval` sent) plus however many more are queued behind it (M4 Phase 4 fold-in — see `TaskRunner.requestApproval`). */\n pendingApprovals: number;\n}\n\n/**\n * S3b (L-003): local storage usage and pressure, as the `status` method\n * reports them (architecture §12.7.2.1).\n *\n * Named `storage*` throughout, NOT `watermark*`: {@link TaskQueueWatermark}\n * above is a per-task progress-buffer depth and has nothing to do with disk.\n * Two unrelated concepts sharing a name on one status result is how an\n * operator reads the wrong number during an incident.\n *\n * Present only when a daemon actually runs a storage policy\n * (`DaemonConfig.hostedJournal.storagePolicy`). Absent means \"not measured\",\n * which is a different statement from \"measured, and fine\" — so it is an\n * absent field rather than a zeroed one.\n */\nexport interface ControlStorageStatus {\n /** §12.7.2.1's four states. `hard-pressure` declines new offers; `emergency` refuses to ack at all. */\n pressureState: StoragePressureState;\n /** `maxStoreBytes` — the budget `usedBytes` is measured against. */\n budgetBytes: number;\n /** Total across every category below, as of `measuredAt`. */\n usedBytes: number;\n /** Bytes available to this daemon on the store's filesystem — the free-space axis of the watermark, independent of the budget. */\n freeBytes: number;\n measuredAt: string;\n /** The five §12.7.2.1 categories, always reported separately — a single total cannot drive a category-scoped cleanup order or a category-scoped never-delete list. */\n categories: ControlStorageCategoryUsage[];\n /** The most recent bounded WAL checkpoint + incremental vacuum, if one has run in this daemon's lifetime. */\n lastCompaction?: ControlStorageCompaction;\n}\n\nexport interface ControlStorageCategoryUsage {\n category: StorageCategory;\n bytes: number;\n /** `true` when this is a host-reported or sampled figure rather than one measured off the filesystem. */\n approximate: boolean;\n}\n\nexport interface ControlStorageCompaction {\n checkpointed: boolean;\n walFramesRemaining: number;\n pagesVacuumed: number;\n durationMs: number;\n at: string;\n}\n\n/** Result shape for the `status` method — see `create-daemon.ts`'s control-method wiring for how each field is sourced, and `bin/format.ts`'s `formatLiveStatusLines` for how the CLI renders it. */\nexport interface ControlStatusResult {\n /** Process-immutable Local Agent application release; absent only for an older control peer. */\n localAgentRelease?: Readonly<LocalAgentReleaseIdentity>;\n pid: number;\n uptimeMs: number;\n paired: boolean;\n deviceId?: string;\n /** The connection state machine's own current value (`ws-transport.ts`'s `ConnectionState`) — e.g. `'open'`, `'degraded'` (long-poll fallback), `'revoked'`, `'closed'`, `'connecting'`. */\n transport: string;\n activeTasks: ControlActiveTask[];\n runtimeIds: string[];\n /** M4 Phase 4 (part B.3): per-active-task queue watermarks — see {@link TaskQueueWatermark}. */\n queueWatermarks: TaskQueueWatermark[];\n /**\n * Finding F4 (cross-model adversarial review): the actual pending\n * approvals currently dispatched — the SAME entries `approvals.list`\n * returns (`ApprovalRegistry.list()`), surfaced here too so a single\n * `status` call can show an operator every `approvalId` they'd need to\n * `approve`/`reject`, without a second control-socket round trip. This is\n * `approvalsPending`'s own source list (`approvalsPending ===\n * approvals.length`, always).\n */\n approvals: PendingApproval[];\n /** M4 Phase 4 (part B.3): total approvals currently DISPATCHED (registered) across the whole daemon — the same count `approvals.list` returns, surfaced here too for a one-call status view. */\n approvalsPending: number;\n /** S3b (L-003): local storage usage + pressure — see {@link ControlStorageStatus}. Absent unless a storage policy is configured. */\n storage?: ControlStorageStatus;\n /** Local lifecycle/retry budget. This is not the transport state above. */\n operationalHealth: OperationalHealthSnapshot;\n /** Redacted content-addressed status from the daemon's single local registry. */\n toolsets: McpToolsetRegistryStatus;\n}\n\nexport interface ToolsetsReloadParams {\n expectedRevision: string;\n mcpToolsets: Record<string, McpToolsetConfig>;\n}\n\n/** Maximum opaque pairing-code payload accepted over local control IPC. */\nexport const ENROLLMENT_PAIRING_CODE_MAX_BYTES = 1024;\n\nexport interface EnrollmentPairParams {\n pairingCode: string;\n}\n\n/**\n * Exact shape gate for service-identity pairing. The code is opaque server\n * authority: this only bounds transport bytes and rejects unknown fields; it\n * never parses, normalizes or logs the code.\n */\nexport function parseEnrollmentPairParams(value: unknown): EnrollmentPairParams | undefined {\n if (!isRecord(value) || Object.keys(value).some((key) => key !== 'pairingCode')) return undefined;\n if (\n typeof value.pairingCode !== 'string' ||\n value.pairingCode.length === 0 ||\n Buffer.byteLength(value.pairingCode, 'utf8') > ENROLLMENT_PAIRING_CODE_MAX_BYTES\n ) {\n return undefined;\n }\n return { pairingCode: value.pairingCode };\n}\n\n/** Shape-only parser; executable definition validation remains registry-owned. */\nexport function parseToolsetsReloadParams(value: unknown): ToolsetsReloadParams | undefined {\n if (!isRecord(value) || Object.keys(value).some((key) => key !== 'expectedRevision' && key !== 'mcpToolsets')) {\n return undefined;\n }\n if (typeof value.expectedRevision !== 'string' || !isRecord(value.mcpToolsets) || Array.isArray(value.mcpToolsets)) {\n return undefined;\n }\n return {\n expectedRevision: value.expectedRevision,\n mcpToolsets: value.mcpToolsets as Record<string, McpToolsetConfig>,\n };\n}\n\nexport interface ApprovalsListResult {\n approvals: PendingApproval[];\n}\n\nexport type { ApprovalDecision, PendingApproval } from './approvals';\n\nexport interface ApprovalsResolveParams {\n approvalId: string;\n decision: ApprovalDecision;\n reason?: string;\n}\n\nexport function parseApprovalsResolveParams(value: unknown): ApprovalsResolveParams | undefined {\n if (!isRecord(value)) return undefined;\n if (typeof value.approvalId !== 'string') return undefined;\n if (value.decision !== 'approve' && value.decision !== 'reject') return undefined;\n if (value.reason !== undefined && typeof value.reason !== 'string') return undefined;\n return { approvalId: value.approvalId, decision: value.decision, reason: value.reason };\n}\n\n/**\n * M4 Phase 3: the control method `byok-approval-mcp` (`bin/byok-approval-mcp.ts`)\n * calls FROM a claude-spawned MCP-server child process — a genuinely\n * different OS process from the daemon, reachable only over this same\n * control socket (see `../types.ts`'s `ApprovalChannel` doc comment for the\n * full why). `taskId` correlates the request to an active task;\n * `summary` is a short, human-readable description of the gated action\n * (carried verbatim into the wire `task.await_approval.summary`).\n */\nexport interface ApprovalsRequestParams {\n taskId: string;\n summary: string;\n}\n\nexport function parseApprovalsRequestParams(value: unknown): ApprovalsRequestParams | undefined {\n if (!isRecord(value)) return undefined;\n if (typeof value.taskId !== 'string' || value.taskId.length === 0) return undefined;\n if (typeof value.summary !== 'string') return undefined;\n return { taskId: value.taskId, summary: value.summary };\n}\n\n/** Result of `approvals.request` — the outcome `byok-approval-mcp` translates into its own MCP `allow`/`deny` answer. */\nexport interface ApprovalsRequestResult {\n approved: boolean;\n reason?: string;\n}\n\nexport interface AgentMessagePublishParams {\n contextToken: string;\n contentType: 'text/plain' | 'text/markdown';\n body: string;\n}\n\nexport function parseAgentMessagePublishParams(value: unknown): AgentMessagePublishParams | undefined {\n if (!isRecord(value) || Object.keys(value).some((key) => key !== 'contextToken' && key !== 'contentType' && key !== 'body')) return undefined;\n if (typeof value.contextToken !== 'string' || value.contextToken.length < 32 || value.contextToken.length > 160) return undefined;\n if (value.contentType !== 'text/plain' && value.contentType !== 'text/markdown') return undefined;\n if (typeof value.body !== 'string' || value.body.length === 0) return undefined;\n return { contextToken: value.contextToken, contentType: value.contentType, body: value.body };\n}\n\nexport interface AgentMessagePublishResult {\n messageId: string;\n state: 'staged' | 'pending';\n}\n\nexport interface AgentMemoryRecallParams {\n contextToken: string;\n path: string;\n ifRevision?: string;\n}\n\nexport interface AgentMemorySaveParams {\n contextToken: string;\n op: 'replace' | 'delete';\n path: string;\n expectedRevision: string;\n content?: string;\n}\n\nfunction validAgentMemoryContextToken(value: unknown): value is string {\n return typeof value === 'string' && value.length >= 32 && value.length <= 160 && !/[\\u0000\\r\\n]/u.test(value);\n}\n\n/** Parser only validates the local IPC shape. Agent identity and memory root stay daemon-owned. */\nexport function parseAgentMemoryRecallParams(value: unknown): AgentMemoryRecallParams | undefined {\n if (!isRecord(value) || Object.keys(value).some((key) => key !== 'contextToken' && key !== 'path' && key !== 'ifRevision')) return undefined;\n if (!validAgentMemoryContextToken(value.contextToken) || typeof value.path !== 'string' || (value.ifRevision !== undefined && typeof value.ifRevision !== 'string')) return undefined;\n return { contextToken: value.contextToken, path: value.path, ...(value.ifRevision === undefined ? {} : { ifRevision: value.ifRevision }) };\n}\n\nexport function parseAgentMemorySaveParams(value: unknown): AgentMemorySaveParams | undefined {\n if (!isRecord(value) || Object.keys(value).some((key) => key !== 'contextToken' && key !== 'op' && key !== 'path' && key !== 'expectedRevision' && key !== 'content')) return undefined;\n if (!validAgentMemoryContextToken(value.contextToken) || (value.op !== 'replace' && value.op !== 'delete') || typeof value.path !== 'string' || typeof value.expectedRevision !== 'string' || (value.op === 'replace' && typeof value.content !== 'string') || (value.op === 'delete' && value.content !== undefined)) return undefined;\n const content = value.content;\n return { contextToken: value.contextToken, op: value.op, path: value.path, expectedRevision: value.expectedRevision, ...(typeof content === 'string' ? { content } : {}) };\n}\n\n// ---------------------------------------------------------------------------\n// `assertion.issue` (plan device-assertion-broker)\n// ---------------------------------------------------------------------------\n\n/**\n * Params for `assertion.issue`: a sibling local process (the host's own CLI,\n * installed alongside this daemon) asking the daemon to mint one short-lived,\n * audience-scoped device assertion with the paired device key. See\n * `@byok-sdk/core`'s `device-assertion.ts` for the envelope, and\n * `create-daemon.ts`'s handler for the six fail-closed gates every call passes\n * through in a fixed order.\n *\n * One field, and nothing else. In particular there is deliberately no caller\n * identity, no requested TTL, and no requested claim set: every process running\n * as this UID can reach the control socket, so anything a caller \"tells\" the\n * daemon about itself is decoration, and a caller-chosen lifetime is just the\n * TTL ceiling handed to whoever asks.\n */\nexport interface AssertionIssueParams {\n audience: string;\n}\n\n/**\n * Bound on the `audience` a caller may send, in UTF-8 bytes — mirrors\n * `@byok-sdk/core`'s `DEVICE_ASSERTION_AUDIENCE_MAX_BYTES`. Restated here rather\n * than imported so the WIRE bound is checked before anything reaches the claim\n * schema: this is the frame-level shape gate, and it must reject an oversized\n * value without that value ever reaching a signer or an audit line.\n */\nexport const ASSERTION_AUDIENCE_MAX_BYTES = 256;\n\n/**\n * Strict shape check. `undefined` means `bad_request` — a distinct gate from\n * `audience_denied` (see `create-daemon.ts`): \"you sent something that is not a\n * request\" and \"you asked for an audience you may not have\" are different\n * facts, and collapsing them would let a caller probe the allowlist by\n * malforming requests.\n *\n * Rejects an unknown key outright rather than ignoring it. A tolerated extra\n * field is how a future caller comes to believe it can influence the claim set.\n */\nexport function parseAssertionIssueParams(value: unknown): AssertionIssueParams | undefined {\n if (!isRecord(value)) return undefined;\n const keys = Object.keys(value);\n if (keys.length !== 1 || keys[0] !== 'audience') return undefined;\n const { audience } = value;\n if (typeof audience !== 'string' || audience.length === 0) return undefined;\n if (Buffer.byteLength(audience, 'utf8') > ASSERTION_AUDIENCE_MAX_BYTES) return undefined;\n return { audience };\n}\n\n/**\n * Result of `assertion.issue`. `assertion` is the full signing envelope\n * (`DeviceAssertionEnvelopeV1`), carried as an opaque JSON value on this wire —\n * the caller hands it to the host's cloud, which parses and verifies it with\n * core's own `verifyDeviceAssertion`. `expiresAt` is repeated outside the\n * envelope purely so a caller can schedule a refresh without parsing claims it\n * has no business interpreting.\n */\nexport interface AssertionIssueResult {\n assertion: unknown;\n expiresAt: string;\n}\n\n/**\n * The six `ControlError` codes `assertion.issue` can answer with, in the exact\n * order the handler checks them (`create-daemon.ts`). Each one is a distinct\n * refusal with a distinct cause; none of them ever signs anything.\n *\n * - `assertion_disabled` — this daemon has no `deviceAssertion` config, or an\n * empty audience allowlist. The feature is OFF by default.\n * - `bad_request` — params were not `{audience: string}` within the byte bound.\n * - `audience_denied` — the audience is not in the configured allowlist. The\n * message never echoes the allowlist: a refusal must not be an enumeration\n * oracle.\n * - `shutting_down` — a shutdown has been requested. Closes the window between\n * the shutdown RPC being acknowledged and the control socket actually\n * closing, during which a device that is being unpaired could otherwise still\n * mint credentials.\n * - `revoked` — the server has revoked this device.\n * - `not_paired` — there is no device record on disk (never paired, or already\n * cleared).\n */\nexport const ASSERTION_ISSUE_ERROR_CODES = [\n 'assertion_disabled',\n 'bad_request',\n 'audience_denied',\n 'shutting_down',\n 'revoked',\n 'not_paired',\n] as const;\n\nexport type AssertionIssueErrorCode = (typeof ASSERTION_ISSUE_ERROR_CODES)[number];\n\nexport type ShutdownReason = 'unpair' | 'operator';\n\nexport interface ShutdownParams {\n reason?: ShutdownReason;\n}\n\nexport function parseShutdownParams(value: unknown): ShutdownParams {\n if (!isRecord(value)) return {};\n return value.reason === 'unpair' || value.reason === 'operator' ? { reason: value.reason } : {};\n}\n","import { constants as fsConstants, promises as fs } from 'node:fs';\nimport net from 'node:net';\nimport {\n CONTROL_PROTOCOL_VERSION,\n ControlError,\n HANDSHAKE_TIMEOUT_MS,\n NdjsonLineReader,\n computeClientAuth,\n computeServerProof,\n controlEndpointPath,\n controlTokenPath,\n encodeFrame,\n isRecord,\n parseServerHello,\n parseServerReady,\n randomNonceHex,\n timingSafeEqualHex,\n} from '../daemon/control-protocol';\n\n/**\n * M4 Phase 2: the CLI-side half of the control socket — connects, performs\n * the mutual HMAC handshake (`../daemon/control-protocol.ts`), and exposes a\n * small `request()`/`subscribe()` surface every rewired command\n * (`status`/`tasks --follow`/`unpair`/`approve`/`reject`) builds on.\n *\n * `connectControlClient` never throws for the ordinary \"daemon isn't\n * running\" case — it returns a typed `{ok:false, reason}` result instead\n * (see {@link ConnectControlResult}), so a caller can render a clean\n * fallback message rather than catching an exception. Missing\n * `control.token` (the daemon was never started, or was stopped) is by far\n * the most common reason; any other connect/handshake failure collapses\n * into the same shape — a CLI user doesn't need to know WHY the control\n * socket isn't reachable, only that it isn't and a fallback is being used.\n */\n\nexport interface ControlClientOptions {\n storeDir: string;\n productId: string;\n /** Default: `HANDSHAKE_TIMEOUT_MS` (3000ms) — matches the server's own handshake timeout. */\n handshakeTimeoutMs?: number;\n /** Default 10000ms — applied per `request()` call; never applied to `subscribe()`, which is expected to stay open indefinitely. */\n requestTimeoutMs?: number;\n}\n\nexport interface ControlClient {\n /** Sends `{method, params}`, resolves with the server's `result`, or rejects with a {@link ControlError} (or a plain `Error` for a connection-level failure/timeout). */\n request<T = unknown>(method: string, params?: unknown): Promise<T>;\n /** Sends a streaming request; `onEvent` fires for each `event` frame. Returns a handle whose `close()` ends the WHOLE connection (per the protocol: \"client may just close the connection to unsubscribe\") — don't share a client between a `subscribe()` and other concurrent `request()` calls if you need them to outlive each other. */\n subscribe(method: string, params: unknown, onEvent: (event: unknown) => void): { close: () => void };\n /** Closes the underlying connection. Safe to call more than once. */\n close(): void;\n}\n\nexport type ConnectControlResult = { ok: true; client: ControlClient } | { ok: false; reason: string };\n\nconst MAX_CONTROL_TOKEN_BYTES = 256;\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\nfunction sameFileState(left: import('node:fs').BigIntStats, right: import('node:fs').BigIntStats): boolean {\n return (\n left.dev === right.dev &&\n left.ino === right.ino &&\n left.size === right.size &&\n left.mtimeNs === right.mtimeNs &&\n left.ctimeNs === right.ctimeNs\n );\n}\n\n/**\n * Read the authentication token through a bounded pathname-bound handle.\n * Diagnostics calls this client even when no daemon is expected, so a FIFO,\n * symlink swap, or oversized local file must become an ordinary offline result\n * rather than blocking the CLI or consuming unbounded memory.\n */\nasync function readControlToken(tokenPath: string): Promise<string | undefined> {\n let namedBefore: import('node:fs').BigIntStats;\n try {\n namedBefore = await fs.lstat(tokenPath, { bigint: true });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return undefined;\n throw err;\n }\n if (!namedBefore.isFile() || namedBefore.isSymbolicLink()) {\n throw new Error('control token is not a real regular file');\n }\n const handle = await fs.open(\n tokenPath,\n fsConstants.O_RDONLY | (fsConstants.O_NONBLOCK ?? 0) | (fsConstants.O_NOFOLLOW ?? 0),\n );\n try {\n const opened = await handle.stat({ bigint: true });\n const namedAfterOpen = await fs.lstat(tokenPath, { bigint: true });\n if (\n !opened.isFile() ||\n !namedAfterOpen.isFile() ||\n namedAfterOpen.isSymbolicLink() ||\n !sameFileState(namedBefore, opened) ||\n !sameFileState(opened, namedAfterOpen)\n ) {\n throw new Error('control token pathname changed before safe open');\n }\n if (opened.size < 0 || opened.size > BigInt(MAX_CONTROL_TOKEN_BYTES)) {\n throw new Error('control token exceeds the bounded read limit');\n }\n const size = Number(opened.size);\n const bytes = Buffer.alloc(size);\n const { bytesRead } = await handle.read(bytes, 0, size, 0);\n const afterRead = await handle.stat({ bigint: true });\n const namedAfterRead = await fs.lstat(tokenPath, { bigint: true });\n if (\n bytesRead !== size ||\n namedAfterRead.isSymbolicLink() ||\n !sameFileState(opened, afterRead) ||\n !sameFileState(afterRead, namedAfterRead)\n ) {\n throw new Error('control token changed during bounded read');\n }\n return bytes.toString('utf8').trim();\n } finally {\n await handle.close();\n }\n}\n\n/** Reads the control token, connects, and performs the handshake — see the module doc comment for why failures here collapse into `{ok:false, reason}` rather than throwing. */\nexport async function connectControlClient(opts: ControlClientOptions): Promise<ConnectControlResult> {\n const tokenPath = controlTokenPath(opts.storeDir);\n let token: string;\n try {\n const read = await readControlToken(tokenPath);\n if (read === undefined) {\n return { ok: false, reason: 'daemon is not running (no control.token found)' };\n }\n token = read;\n } catch (err) {\n return { ok: false, reason: `could not read the control token: ${errorMessage(err)}` };\n }\n if (!token) {\n return { ok: false, reason: 'control token file is empty' };\n }\n\n const endpoint = controlEndpointPath(opts.productId, opts.storeDir);\n try {\n const client = await connectAndHandshake(endpoint, token, opts);\n return { ok: true, client };\n } catch (err) {\n return { ok: false, reason: `daemon control socket not reachable: ${errorMessage(err)}` };\n }\n}\n\nfunction connectAndHandshake(endpoint: string, token: string, opts: ControlClientOptions): Promise<ControlClient> {\n return new Promise((resolve, reject) => {\n const socket = net.createConnection(endpoint);\n const reader = new NdjsonLineReader();\n let phase: 'server-hello' | 'ready' = 'server-hello';\n let settled = false;\n const clientNonce = randomNonceHex();\n\n const timer = setTimeout(() => {\n fail(new Error('handshake timed out'));\n }, opts.handshakeTimeoutMs ?? HANDSHAKE_TIMEOUT_MS);\n timer.unref?.();\n\n function fail(err: unknown): void {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n socket.removeAllListeners();\n socket.destroy();\n reject(err instanceof Error ? err : new Error(String(err)));\n }\n\n function succeed(): void {\n settled = true;\n clearTimeout(timer);\n socket.removeListener('error', onError);\n socket.removeListener('data', onData);\n resolve(createControlClient(socket, reader, opts));\n }\n\n function onData(chunk: Buffer): void {\n // Hardening finding (P2 re-gate): `reader.push` throws once the\n // still-unterminated remainder exceeds MAX_LINE_BYTES\n // (control-protocol.ts) — a hostile/broken peer sending a >64KiB line\n // with no newline. Uncaught, this would propagate out of the 'data'\n // listener and crash the whole CLI process (Node has no default\n // recovery for an exception thrown inside an EventEmitter callback).\n // Mirrors control-server.ts's own identical guard around its matching\n // `reader.push` call — route into this same handshake's `fail()` path\n // instead, exactly like every other handshake failure here.\n let lines: string[];\n try {\n lines = reader.push(chunk);\n } catch (err) {\n fail(err);\n return;\n }\n for (const line of lines) {\n let parsed: unknown;\n try {\n parsed = JSON.parse(line);\n } catch {\n fail(new Error('malformed handshake frame'));\n return;\n }\n if (phase === 'server-hello') {\n const hello = parseServerHello(parsed);\n if (!hello) {\n fail(new Error('malformed or unexpected server hello'));\n return;\n }\n if (!timingSafeEqualHex(hello.proof, computeServerProof(token, clientNonce))) {\n fail(new Error('server failed to prove it holds the control token'));\n return;\n }\n socket.write(encodeFrame({ v: CONTROL_PROTOCOL_VERSION, auth: computeClientAuth(token, hello.nonce) }));\n phase = 'ready';\n continue;\n }\n if (!parseServerReady(parsed)) {\n fail(new Error('server did not confirm readiness'));\n return;\n }\n succeed();\n return;\n }\n }\n\n function onError(err: unknown): void {\n fail(err);\n }\n\n socket.once('error', onError);\n socket.once('connect', () => {\n socket.write(encodeFrame({ v: CONTROL_PROTOCOL_VERSION, hello: 'client', nonce: clientNonce }));\n socket.on('data', onData);\n });\n });\n}\n\ninterface PendingCall {\n resolve: (value: unknown) => void;\n reject: (err: unknown) => void;\n onEvent?: (event: unknown) => void;\n}\n\nfunction withTimeout<T>(promise: Promise<T>, ms: number, message: string): Promise<T> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => reject(new Error(message)), ms);\n timer.unref?.();\n promise.then(\n (value) => {\n clearTimeout(timer);\n resolve(value);\n },\n (err: unknown) => {\n clearTimeout(timer);\n reject(err);\n },\n );\n });\n}\n\nfunction createControlClient(socket: net.Socket, reader: NdjsonLineReader, opts: ControlClientOptions): ControlClient {\n const pending = new Map<string, PendingCall>();\n let idSeq = 0;\n let closed = false;\n\n function handleFrame(parsed: unknown): void {\n if (!isRecord(parsed) || typeof parsed.id !== 'string') return;\n const entry = pending.get(parsed.id);\n if (!entry) return;\n if ('event' in parsed) {\n entry.onEvent?.(parsed.event);\n return;\n }\n if (parsed.ok === true) {\n pending.delete(parsed.id);\n entry.resolve(parsed.done === true ? undefined : (parsed as { result?: unknown }).result);\n return;\n }\n pending.delete(parsed.id);\n const shape = (parsed as { error?: { code?: unknown; message?: unknown } }).error;\n entry.reject(\n new ControlError(\n typeof shape?.code === 'string' ? shape.code : 'internal_error',\n typeof shape?.message === 'string' ? shape.message : 'unknown control error',\n ),\n );\n }\n\n socket.on('data', (chunk: Buffer) => {\n // Same hardening as connectAndHandshake's own onData above: `reader.push`\n // throws on a >64KiB unterminated line (MAX_LINE_BYTES,\n // control-protocol.ts) — uncaught, that would crash this CLI process\n // from inside an EventEmitter 'data' callback. This connection is\n // already past the handshake (there is no `fail()` closure here), so\n // fail closed the same way a malformed/unexpected frame elsewhere in\n // this function already does: destroy the connection (which rejects\n // every pending call via the 'close' handler below) rather than risk\n // continuing to read from a peer that just proved it doesn't speak this\n // protocol.\n let lines: string[];\n try {\n lines = reader.push(chunk);\n } catch {\n socket.destroy();\n return;\n }\n for (const line of lines) {\n let parsed: unknown;\n try {\n parsed = JSON.parse(line);\n } catch {\n continue; // ignore a malformed line rather than tearing down an otherwise-healthy connection\n }\n handleFrame(parsed);\n }\n });\n socket.on('close', () => {\n closed = true;\n for (const entry of pending.values()) entry.reject(new Error('control connection closed'));\n pending.clear();\n });\n socket.on('error', () => {\n // 'close' always follows and rejects every pending call above — swallow\n // here so a peer reset never becomes an unhandled 'error' crash.\n });\n\n function send(method: string, params: unknown, onEvent?: (event: unknown) => void): { id: string; promise: Promise<unknown> } {\n const id = `c${++idSeq}`;\n const promise = new Promise<unknown>((resolve, reject) => {\n pending.set(id, { resolve, reject, onEvent });\n });\n socket.write(encodeFrame({ v: CONTROL_PROTOCOL_VERSION, id, method, params }));\n return { id, promise };\n }\n\n return {\n async request<T>(method: string, params?: unknown): Promise<T> {\n if (closed) throw new Error('control connection is closed');\n const { promise } = send(method, params);\n const result = await withTimeout(promise, opts.requestTimeoutMs ?? 10_000, `control request \"${method}\" timed out`);\n return result as T;\n },\n subscribe(method, params, onEvent) {\n const { id, promise } = send(method, params, onEvent);\n promise.catch(() => {\n // subscribe()'s lifetime is managed via close()/onEvent, not this\n // promise — swallow so destroying the connection later (which\n // rejects every pending call, this one included) never surfaces as\n // an unhandled rejection.\n });\n return {\n close: (): void => {\n pending.delete(id);\n socket.destroy();\n },\n };\n },\n close(): void {\n socket.destroy();\n },\n };\n}\n\n/**\n * Unpair's own poll for \"has the daemon actually exited yet\" (see\n * `bin/commands/unpair.ts`): both the control token file being gone AND a\n * fresh connect attempt being refused, checked directly rather than through\n * a full `connectControlClient` handshake — cheaper per poll, and matches\n * the exact two-condition check the M4 design calls for.\n */\nexport async function isControlDaemonGone(storeDir: string, productId: string): Promise<boolean> {\n const tokenGone = await fs.stat(controlTokenPath(storeDir)).then(\n () => false,\n (err) => (err as NodeJS.ErrnoException).code === 'ENOENT',\n );\n if (!tokenGone) return false;\n\n const endpoint = controlEndpointPath(productId, storeDir);\n return new Promise<boolean>((resolve) => {\n const socket = net.createConnection(endpoint);\n const finish = (gone: boolean): void => {\n socket.removeAllListeners();\n socket.destroy();\n resolve(gone);\n };\n socket.once('connect', () => finish(false));\n socket.once('error', (err: NodeJS.ErrnoException) => finish(err.code === 'ECONNREFUSED' || err.code === 'ENOENT'));\n });\n}\n","import { createInterface } from 'node:readline';\nimport type { AgentMessageContentType } from '@byok-sdk/protocol';\n\nexport const AGENT_MESSAGE_TOOL_NAME = 'send_agent_message';\n\nexport interface AgentMessageMcpDeps {\n publish(input: { contentType: AgentMessageContentType; body: string }): Promise<{ messageId: string; state: string }>;\n}\n\ninterface RequestLike { jsonrpc?: unknown; id?: unknown; method?: unknown; params?: unknown }\n\nexport async function handleAgentMessageMcpRequest(\n request: RequestLike,\n deps: AgentMessageMcpDeps,\n): Promise<Record<string, unknown> | undefined> {\n const id = request.id;\n if (request.method === 'initialize') {\n const params = (request.params ?? {}) as { protocolVersion?: unknown };\n return {\n jsonrpc: '2.0',\n id,\n result: {\n protocolVersion: typeof params.protocolVersion === 'string' ? params.protocolVersion : '2024-11-05',\n capabilities: { tools: {} },\n serverInfo: { name: 'byok-agent-message-mcp', version: '0.0.1' },\n },\n };\n }\n if (request.method === 'notifications/initialized') return undefined;\n if (request.method === 'tools/list') {\n return {\n jsonrpc: '2.0', id, result: { tools: [{\n name: AGENT_MESSAGE_TOOL_NAME,\n description: 'Send the single user-visible Agent reply for this exact task. Routing identity is supplied by the authenticated task context.',\n inputSchema: {\n type: 'object', additionalProperties: false, required: ['body'],\n properties: {\n body: { type: 'string', minLength: 1 },\n contentType: { type: 'string', enum: ['text/plain', 'text/markdown'] },\n },\n },\n }] },\n };\n }\n if (request.method === 'tools/call') {\n const params = (request.params ?? {}) as { name?: unknown; arguments?: unknown };\n if (params.name !== AGENT_MESSAGE_TOOL_NAME) return { jsonrpc: '2.0', id, error: { code: -32602, message: 'unknown Agent message tool' } };\n const args = params.arguments;\n if (args === null || typeof args !== 'object' || Array.isArray(args)) return { jsonrpc: '2.0', id, error: { code: -32602, message: 'message input must be an object' } };\n const record = args as Record<string, unknown>;\n if (Object.keys(record).some((key) => key !== 'body' && key !== 'contentType') || typeof record.body !== 'string' || record.body.length === 0) {\n return { jsonrpc: '2.0', id, error: { code: -32602, message: 'message input accepts only non-empty body and optional contentType' } };\n }\n const contentType = record.contentType ?? 'text/markdown';\n if (contentType !== 'text/plain' && contentType !== 'text/markdown') return { jsonrpc: '2.0', id, error: { code: -32602, message: 'unsupported contentType' } };\n try {\n const receipt = await deps.publish({ contentType, body: record.body });\n return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: JSON.stringify(receipt) }] } };\n } catch (error) {\n return { jsonrpc: '2.0', id, error: { code: -32000, message: error instanceof Error ? error.message : String(error) } };\n }\n }\n return id === undefined ? undefined : { jsonrpc: '2.0', id, error: { code: -32601, message: `unknown method: ${String(request.method)}` } };\n}\n\nexport function serveAgentMessageMcpOverStdio(input: { deps: AgentMessageMcpDeps; stdin?: NodeJS.ReadableStream; stdout?: NodeJS.WritableStream }): void {\n const reader = createInterface({ input: input.stdin ?? process.stdin, terminal: false });\n const output = input.stdout ?? process.stdout;\n reader.on('line', (line) => {\n const trimmed = line.trim();\n if (trimmed.length === 0) return;\n void (async () => {\n let request: RequestLike;\n try { request = JSON.parse(trimmed) as RequestLike; } catch { return; }\n const response = await handleAgentMessageMcpRequest(request, input.deps);\n if (response !== undefined) output.write(`${JSON.stringify(response)}\\n`);\n })();\n });\n}\n","#!/usr/bin/env node\nimport { connectControlClient, type ControlClient } from './control-client';\nimport { serveAgentMessageMcpOverStdio, type AgentMessageMcpDeps } from './agent-message-mcp-server';\n\nfunction required(name: string): string {\n const value = process.env[name];\n if (!value) throw new Error(`missing required environment variable ${name}`);\n return value;\n}\n\nasync function main(): Promise<void> {\n const storeDir = required('BYOK_STORE_DIR');\n const productId = required('BYOK_PRODUCT_ID');\n const contextToken = required('BYOK_AGENT_MESSAGE_CONTEXT');\n let clientPromise: Promise<ControlClient> | undefined;\n const client = async (): Promise<ControlClient> => {\n if (!clientPromise) clientPromise = connectControlClient({ storeDir, productId }).then((result) => {\n if (!result.ok) throw new Error(result.reason);\n return result.client;\n });\n return clientPromise;\n };\n const deps: AgentMessageMcpDeps = {\n publish: async (input) => (await client()).request('agent_messages.publish', { contextToken, ...input }),\n };\n serveAgentMessageMcpOverStdio({ deps });\n}\n\nmain().catch((error: unknown) => {\n process.stderr.write(`byok-agent-message-mcp: ${error instanceof Error ? error.message : String(error)}\\n`);\n process.exit(1);\n});\n"]}