@remnic/capture-audio 9.63.0 → 9.63.2
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/dist/{chunk-MFWH245M.js → chunk-4Z3DZYEB.js} +229 -62
- package/dist/chunk-4Z3DZYEB.js.map +1 -0
- package/dist/cli-bin.js +1 -1
- package/dist/index.d.ts +177 -144
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/dist/chunk-MFWH245M.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/constants.ts","../src/errors.ts","../src/config.ts","../src/util.ts","../src/coerce.ts","../src/control.ts","../src/token.ts","../src/validate.ts","../src/daemon.ts","../src/paths.ts","../src/replay.ts","../src/model.ts","../src/janitor.ts","../src/buffer-policy.ts","../src/spool.ts","../src/assembly.ts","../src/diarization.ts","../src/native.ts","../src/dedup.ts","../src/processor.ts","../src/orphan-scan.ts","../src/stt.ts","../src/capture.ts","../src/enroll.ts","../src/service.ts","../src/cli.ts"],"sourcesContent":["/** Package-wide constants for @remnic/capture-audio. */\n\n/**\n * Reported by GET /v1/health. Kept in sync with package.json by the\n * release tooling; the health endpoint tolerates drift because the\n * connector never gates on an exact match (it reads `ok`).\n */\nexport const CAPTURE_AUDIO_VERSION = \"9.14.0\";\n\n/** Loopback default; capture is local-first (charter). */\nexport const DEFAULT_HOST = \"127.0.0.1\";\nexport const DEFAULT_PORT = 4340;\n\n/** Spool schema version, persisted in the `meta` table. */\nexport const SPOOL_SCHEMA_VERSION = 3;\n\n/** Upper bound for the conversations `limit` query parameter. */\nexport const MAX_CONVERSATIONS_LIMIT = 500;\n/** Default page size when `limit` is omitted. */\nexport const DEFAULT_CONVERSATIONS_LIMIT = 50;\n","/**\n * Error taxonomy for @remnic/capture-audio.\n *\n * Two authored-message classes, mirroring the wearables split\n * (packages/remnic-core/src/wearables/errors.ts): configuration problems\n * and caller-correctable input. Both carry operator-safe messages (never\n * foreign error text, never credentials). The HTTP layer maps\n * CaptureInputError to 400; anything else is a backend fault (500).\n */\n\n/** Config load/validation failure — surfaced loudly, never silently defaulted (rule 39). */\nexport class CaptureConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"CaptureConfigError\";\n }\n}\n\n/** Caller-correctable request/CLI input — maps to HTTP 400. */\nexport class CaptureInputError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"CaptureInputError\";\n }\n}\n","/**\n * Daemon config (`~/.remnic/capture/audio.json`), created by\n * `remnic-capture-audio init`. Strict and loud: an absent field takes the\n * documented default, but a present-but-invalid value throws\n * CaptureConfigError (rule 39 — no silent defaulting). Ports are integers\n * in [1, 65535] (rule 17); booleans coerce boolean-like strings (rule 24).\n *\n * Only `whisper-cpp` is currently accepted for STT. VAD configuration maps\n * directly to the optional Sherpa Silero runtime adapter.\n */\n\nimport { readFileSync } from \"node:fs\";\n\nimport { coerceNumber } from \"./coerce.js\";\nimport { DEFAULT_HOST, DEFAULT_PORT } from \"./constants.js\";\nimport { CaptureConfigError } from \"./errors.js\";\nimport { describeValue } from \"./util.js\";\n\nexport interface SttConfig {\n engine: \"whisper-cpp\";\n modelPath: string | null;\n threads: number | null;\n}\nexport interface VadConfig {\n modelPath: string | null;\n minSpeechMs: number;\n minSilenceMs: number;\n maxSpeechMs: number;\n threshold: number;\n threads: number;\n}\nexport interface DiarizationConfig {\n similarityThreshold: number;\n}\nexport interface DeviceConfig {\n mic: string | null;\n system: string | null;\n}\nexport interface DaemonConfig {\n host: string;\n port: number;\n chunkSeconds: number;\n captureChannel: \"mic\" | \"system\" | \"both\";\n conversationGapMinutes: number;\n /**\n * Bounded reorder window, in seconds, for cross-channel arrival skew\n * (issue #2145). Chunks are held until the newest observed chunk end is\n * this far past their own end, then released oldest-first, so a delayed\n * system chunk is grouped with the conversation it belongs to instead of\n * a later mic chunk's. 0 disables buffering: every chunk is released on\n * arrival, which is the pre-#2145 behavior.\n */\n reorderWindowSeconds: number;\n rawRetentionHours: number;\n spoolRetentionDays: number;\n vad: VadConfig;\n diarization: DiarizationConfig;\n stt: SttConfig;\n denyApps: string[];\n devices: DeviceConfig;\n}\n\nexport function defaultDaemonConfig(): DaemonConfig {\n return {\n host: DEFAULT_HOST,\n port: DEFAULT_PORT,\n chunkSeconds: 30,\n captureChannel: \"both\",\n conversationGapMinutes: 10,\n rawRetentionHours: 0,\n spoolRetentionDays: 30,\n vad: {\n modelPath: null,\n minSpeechMs: 500,\n minSilenceMs: 500,\n maxSpeechMs: 30_000,\n threshold: 0.5,\n threads: 1,\n },\n reorderWindowSeconds: 60,\n diarization: { similarityThreshold: 0.4 },\n stt: { engine: \"whisper-cpp\", modelPath: null, threads: null },\n denyApps: [],\n devices: { mic: null, system: null },\n };\n}\n\nconst KNOWN_TOP_KEYS: Record<string, true> = {\n host: true,\n port: true,\n chunkSeconds: true,\n captureChannel: true,\n conversationGapMinutes: true,\n reorderWindowSeconds: true,\n rawRetentionHours: true,\n spoolRetentionDays: true,\n vad: true,\n diarization: true,\n stt: true,\n denyApps: true,\n devices: true,\n};\n\nfunction asObject(value: unknown, label: string): Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new CaptureConfigError(`${label}: expected an object, got ${describeValue(value)}`);\n }\n return value as Record<string, unknown>;\n}\n\nfunction warnUnknownKeys(obj: Record<string, unknown>, known: Record<string, true>, label: string): void {\n for (const key of Object.keys(obj)) {\n if (!Object.hasOwn(known, key)) {\n console.warn(`remnic-capture-audio: ${label}: ignoring unknown key '${key}'`);\n }\n }\n}\n\nfunction requireString(value: unknown, label: string): string {\n if (typeof value !== \"string\" || value.trim() === \"\") {\n throw new CaptureConfigError(`${label}: expected a non-empty string, got ${describeValue(value)}`);\n }\n return value.trim();\n}\n\nexport function parseDaemonConfig(raw: unknown): DaemonConfig {\n const cfg = defaultDaemonConfig();\n const obj = asObject(raw, \"config\");\n warnUnknownKeys(obj, KNOWN_TOP_KEYS, \"config\");\n\n if (obj.host !== undefined) cfg.host = requireString(obj.host, \"host\");\n if (obj.port !== undefined) {\n cfg.port = coerceNumber(obj.port, \"port\", { integer: true, min: 1, max: 65535 });\n }\n if (obj.chunkSeconds !== undefined) {\n cfg.chunkSeconds = coerceNumber(obj.chunkSeconds, \"chunkSeconds\", { integer: true, min: 1, max: 3600 });\n }\n if (obj.captureChannel !== undefined) {\n if (obj.captureChannel !== \"mic\" && obj.captureChannel !== \"system\" && obj.captureChannel !== \"both\") {\n throw new CaptureConfigError(\n `captureChannel: expected 'mic' | 'system' | 'both', got ${describeValue(obj.captureChannel)}`,\n );\n }\n cfg.captureChannel = obj.captureChannel;\n }\n if (obj.conversationGapMinutes !== undefined) {\n cfg.conversationGapMinutes = coerceNumber(obj.conversationGapMinutes, \"conversationGapMinutes\", { min: 0 });\n }\n if (obj.rawRetentionHours !== undefined) {\n cfg.rawRetentionHours = coerceNumber(obj.rawRetentionHours, \"rawRetentionHours\", { min: 0 });\n }\n if (obj.spoolRetentionDays !== undefined) {\n cfg.spoolRetentionDays = coerceNumber(obj.spoolRetentionDays, \"spoolRetentionDays\", { integer: true, min: 1 });\n }\n if (obj.reorderWindowSeconds !== undefined) {\n cfg.reorderWindowSeconds = coerceNumber(obj.reorderWindowSeconds, \"reorderWindowSeconds\", {\n min: 0,\n max: 3600,\n });\n }\n\n if (obj.vad !== undefined) {\n const vad = asObject(obj.vad, \"vad\");\n warnUnknownKeys(\n vad,\n {\n modelPath: true,\n minSpeechMs: true,\n minSilenceMs: true,\n maxSpeechMs: true,\n threshold: true,\n threads: true,\n },\n \"vad\",\n );\n if (vad.modelPath !== undefined) {\n cfg.vad.modelPath = vad.modelPath === null ? null : requireString(vad.modelPath, \"vad.modelPath\");\n }\n if (vad.minSpeechMs !== undefined) {\n cfg.vad.minSpeechMs = coerceNumber(vad.minSpeechMs, \"vad.minSpeechMs\", { integer: true, min: 1 });\n }\n if (vad.minSilenceMs !== undefined) {\n cfg.vad.minSilenceMs = coerceNumber(vad.minSilenceMs, \"vad.minSilenceMs\", { integer: true, min: 0 });\n }\n if (vad.maxSpeechMs !== undefined) {\n cfg.vad.maxSpeechMs = coerceNumber(vad.maxSpeechMs, \"vad.maxSpeechMs\", { integer: true, min: 1 });\n }\n if (vad.threshold !== undefined) {\n const threshold = coerceNumber(vad.threshold, \"vad.threshold\", { max: 1 });\n if (threshold <= 0 || threshold >= 1) {\n throw new CaptureConfigError(\"vad.threshold must be between 0 and 1\");\n }\n cfg.vad.threshold = threshold;\n }\n if (vad.threads !== undefined) {\n cfg.vad.threads = coerceNumber(vad.threads, \"vad.threads\", { integer: true, min: 1, max: 256 });\n }\n if (cfg.vad.minSpeechMs > cfg.vad.maxSpeechMs) {\n throw new CaptureConfigError(\"vad.maxSpeechMs must be greater than or equal to vad.minSpeechMs\");\n }\n }\n\n if (obj.diarization !== undefined) {\n const dia = asObject(obj.diarization, \"diarization\");\n warnUnknownKeys(dia, { similarityThreshold: true }, \"diarization\");\n if (dia.similarityThreshold !== undefined) {\n const threshold = coerceNumber(dia.similarityThreshold, \"diarization.similarityThreshold\", { max: 1 });\n if (threshold <= 0 || threshold >= 1) {\n throw new CaptureConfigError(\"diarization.similarityThreshold must be between 0 and 1\");\n }\n cfg.diarization.similarityThreshold = threshold;\n }\n }\n\n if (obj.stt !== undefined) {\n const stt = asObject(obj.stt, \"stt\");\n warnUnknownKeys(stt, { engine: true, modelPath: true, threads: true }, \"stt\");\n if (stt.engine !== undefined && stt.engine !== \"whisper-cpp\") {\n throw new CaptureConfigError(`stt.engine: only 'whisper-cpp' is supported, got ${describeValue(stt.engine)}`);\n }\n if (stt.modelPath !== undefined && stt.modelPath !== null) {\n if (typeof stt.modelPath !== \"string\") {\n throw new CaptureConfigError(`stt.modelPath: expected a string, got ${describeValue(stt.modelPath)}`);\n }\n cfg.stt.modelPath = stt.modelPath.trim() || null;\n }\n if (stt.threads !== undefined && stt.threads !== null) {\n cfg.stt.threads = coerceNumber(stt.threads, \"stt.threads\", { integer: true, min: 1, max: 256 });\n }\n }\n\n if (obj.denyApps !== undefined) {\n if (!Array.isArray(obj.denyApps) || !obj.denyApps.every((app) => typeof app === \"string\")) {\n throw new CaptureConfigError(`denyApps: expected an array of strings, got ${describeValue(obj.denyApps)}`);\n }\n cfg.denyApps = [...(obj.denyApps as string[])];\n }\n\n if (obj.devices !== undefined) {\n const dev = asObject(obj.devices, \"devices\");\n warnUnknownKeys(dev, { mic: true, system: true }, \"devices\");\n if (dev.mic !== undefined && dev.mic !== null) {\n if (typeof dev.mic !== \"string\") {\n throw new CaptureConfigError(`devices.mic: expected a string, got ${describeValue(dev.mic)}`);\n }\n cfg.devices.mic = dev.mic;\n }\n if (dev.system !== undefined && dev.system !== null) {\n if (typeof dev.system !== \"string\") {\n throw new CaptureConfigError(`devices.system: expected a string, got ${describeValue(dev.system)}`);\n }\n cfg.devices.system = dev.system;\n }\n }\n\n return cfg;\n}\n\nexport function loadDaemonConfig(configPath: string): DaemonConfig {\n let text: string;\n try {\n text = readFileSync(configPath, \"utf8\");\n } catch {\n throw new CaptureConfigError(\n `config not found at ${configPath} — run \\`remnic-capture-audio init\\` first`,\n );\n }\n let raw: unknown;\n try {\n raw = JSON.parse(text);\n } catch (err) {\n throw new CaptureConfigError(`config at ${configPath} is not valid JSON: ${(err as Error).message}`);\n }\n return parseDaemonConfig(raw);\n}\n\nexport function serializeDaemonConfig(cfg: DaemonConfig): string {\n return `${JSON.stringify(cfg, null, 2)}\\n`;\n}\n","/** Small dependency-free helpers shared across the package. */\n\nimport { randomFillSync } from \"node:crypto\";\n\nconst CROCKFORD = \"0123456789ABCDEFGHJKMNPQRSTVWXYZ\";\n\n/**\n * Monotonic-enough ULID. 48-bit millisecond time in the leading 10\n * characters, 80 bits of randomness in the trailing 16. Uniqueness comes\n * from the random tail; the time prefix keeps ids lexically sortable so\n * `conv_<ulid>` keyset pagination orders by creation time for free.\n */\nexport function ulid(time: number = Date.now()): string {\n return encodeTime(time) + encodeRandom();\n}\n\nfunction encodeTime(time: number): string {\n if (!Number.isFinite(time) || time < 0) {\n throw new Error(\"ulid: time must be a non-negative finite number\");\n }\n let out = \"\";\n let t = Math.floor(time);\n for (let i = 0; i < 10; i++) {\n out = CROCKFORD[t % 32] + out;\n t = Math.floor(t / 32);\n }\n return out;\n}\n\nfunction encodeRandom(): string {\n const bytes = new Uint8Array(16);\n randomFillSync(bytes);\n let out = \"\";\n for (let i = 0; i < 16; i++) out += CROCKFORD[bytes[i] % 32];\n return out;\n}\n\n/**\n * Format a Date as YYYY-MM-DD in the given IANA timezone. Local copy of\n * the wearables-pipeline helper — capture-audio is à-la-carte and does\n * not depend on @remnic/core (that dependency arrives with the connector\n * in a later checklist item).\n */\nexport function dateInTimezone(date: Date, timezone: string): string {\n const parts = new Intl.DateTimeFormat(\"en-CA\", {\n timeZone: timezone,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n }).formatToParts(date);\n const get = (type: string) => parts.find((part) => part.type === type)?.value ?? \"\";\n return `${get(\"year\")}-${get(\"month\")}-${get(\"day\")}`;\n}\n\nconst LOOPBACK_HOSTS: Record<string, true> = {\n \"127.0.0.1\": true,\n \"::1\": true,\n localhost: true,\n \"::ffff:127.0.0.1\": true,\n};\n\n/**\n * A host is loopback when it can only be reached from this machine.\n * Binding anything else (a LAN address, 0.0.0.0, ::) exposes the daemon\n * to the network and REQUIRES bearer-token auth on every request.\n */\n/** Strip a single pair of surrounding brackets from a URL-authority IPv6 host\n * (`[::1]` -> `::1`); non-bracketed hosts pass through unchanged. */\nexport function stripIpv6Brackets(host: string): string {\n const h = host.trim();\n return h.startsWith(\"[\") && h.endsWith(\"]\") ? h.slice(1, -1) : h;\n}\n\nexport function isLoopbackHost(host: string): boolean {\n return Object.hasOwn(LOOPBACK_HOSTS, stripIpv6Brackets(host).toLowerCase());\n}\n\n/** Compact, credential-free description of an unexpected value for messages. */\nexport function describeValue(value: unknown): string {\n if (value === null) return \"null\";\n if (Array.isArray(value)) return \"an array\";\n const t = typeof value;\n if (t === \"string\") return `a string`;\n if (t === \"object\") return \"an object\";\n return `${t} (${String(value)})`;\n}\n\n/** Wrap an IPv6 host in brackets for use in a URL authority; IPv4/hostnames pass through. */\nexport function formatHostForUrl(host: string): string {\n return host.includes(\":\") ? `[${host}]` : host;\n}\n","/**\n * Config-layer coercion. Every helper THROWS on an unrecognized value\n * (never silently defaults — rule 39); callers apply defaults only when a\n * field is absent. Boolean-ish strings coerce per the shared connector\n * convention (rule 24/36): true/1/yes/on and false/0/no/off.\n */\n\nimport { CaptureConfigError } from \"./errors.js\";\nimport { describeValue } from \"./util.js\";\n\nconst BOOL_TOKENS: Record<string, boolean> = {\n true: true,\n \"1\": true,\n yes: true,\n on: true,\n false: false,\n \"0\": false,\n no: false,\n off: false,\n};\n\nexport function coerceBool(value: unknown, label: string): boolean {\n if (typeof value === \"boolean\") return value;\n if (typeof value === \"number\" && (value === 0 || value === 1)) return value === 1;\n if (typeof value === \"string\") {\n const token = value.trim().toLowerCase();\n if (Object.hasOwn(BOOL_TOKENS, token)) return BOOL_TOKENS[token];\n }\n throw new CaptureConfigError(\n `${label}: expected a boolean (true/false/1/0/yes/no/on/off), got ${describeValue(value)}`,\n );\n}\n\nexport interface NumberBounds {\n min?: number;\n max?: number;\n integer?: boolean;\n}\n\nexport function coerceNumber(value: unknown, label: string, bounds: NumberBounds = {}): number {\n let n: number;\n if (typeof value === \"number\") {\n n = value;\n } else if (typeof value === \"string\" && value.trim() !== \"\") {\n n = Number(value);\n } else {\n throw new CaptureConfigError(`${label}: expected a number, got ${describeValue(value)}`);\n }\n if (!Number.isFinite(n)) {\n throw new CaptureConfigError(`${label}: '${String(value)}' is not a finite number`);\n }\n if (bounds.integer && !Number.isInteger(n)) {\n throw new CaptureConfigError(`${label}: expected an integer, got ${n}`);\n }\n if (bounds.min !== undefined && n < bounds.min) {\n throw new CaptureConfigError(`${label}: must be >= ${bounds.min}, got ${n}`);\n }\n if (bounds.max !== undefined && n > bounds.max) {\n throw new CaptureConfigError(`${label}: must be <= ${bounds.max}, got ${n}`);\n }\n return n;\n}\n","/**\n * Daemon process control: an atomic, identity-bearing pid file plus\n * liveness probing.\n *\n * The pid file is JSON `{ pid, instanceId, startedAtIso }` written via a\n * temp-file + rename so a reader never sees a partial write, and reads are\n * tolerant of a concurrent delete. `instanceId` (the spool instance id)\n * lets `stop`/`status` confirm — over the authenticated health endpoint —\n * that the recorded pid really is our daemon before signalling it, which\n * guards against PID reuse. Removal is owner-checked so a late shutdown\n * can't delete a newer daemon's control file.\n */\n\nimport { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from \"node:fs\";\nimport { randomBytes } from \"node:crypto\";\nimport path from \"node:path\";\n\nexport interface PidRecord {\n pid: number;\n /** Daemon instance id (spool instance_id) for cross-process identity; null when unknown. */\n instanceId: string | null;\n /** ISO timestamp the record was written. */\n startedAtIso: string;\n /** Effective bound host, when known (so status/stop reach the daemon the CLI actually started). */\n host: string | null;\n /** Effective bound port, when known. */\n port: number | null;\n}\n\nexport interface PidWriteOptions {\n instanceId?: string | null;\n startedAtIso?: string;\n host?: string | null;\n port?: number | null;\n}\n\n/** Atomically write the pid record (temp file + rename) — no partial reads. */\nexport function writePidFile(pidPath: string, pid: number, options: PidWriteOptions = {}): void {\n mkdirSync(path.dirname(pidPath), { recursive: true });\n const record: PidRecord = {\n pid,\n instanceId: options.instanceId ?? null,\n startedAtIso: options.startedAtIso ?? new Date().toISOString(),\n host: options.host ?? null,\n port: options.port ?? null,\n };\n const tmp = `${pidPath}.${process.pid}.${randomBytes(4).toString(\"hex\")}.tmp`;\n writeFileSync(tmp, `${JSON.stringify(record)}\\n`, \"utf8\");\n renameSync(tmp, pidPath);\n}\n\n/** Read the pid record; a missing file or a partial/concurrent write returns null. */\nexport function readPidRecord(pidPath: string): PidRecord | null {\n let text: string;\n try {\n text = readFileSync(pidPath, \"utf8\");\n } catch {\n return null;\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return null;\n }\n if (typeof parsed !== \"object\" || parsed === null) return null;\n const record = parsed as Record<string, unknown>;\n const pid = typeof record.pid === \"number\" ? record.pid : Number.NaN;\n if (!Number.isInteger(pid) || pid <= 0) return null;\n const port =\n typeof record.port === \"number\" && Number.isInteger(record.port) && record.port > 0 ? record.port : null;\n return {\n pid,\n instanceId: typeof record.instanceId === \"string\" ? record.instanceId : null,\n startedAtIso: typeof record.startedAtIso === \"string\" ? record.startedAtIso : \"\",\n host: typeof record.host === \"string\" && record.host !== \"\" ? record.host : null,\n port,\n };\n}\n\n/** Convenience accessor: the recorded pid, or null. */\nexport function readPidFile(pidPath: string): number | null {\n return readPidRecord(pidPath)?.pid ?? null;\n}\n\n/** Liveness via signal 0. ESRCH → gone; EPERM → alive but owned by another user. */\nexport function isProcessAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n return (err as NodeJS.ErrnoException).code === \"EPERM\";\n }\n}\n\n/** Remove the pid file unconditionally (stale reclaim). */\nexport function removePidFile(pidPath: string): void {\n rmSync(pidPath, { force: true });\n}\n\n/**\n * Remove the pid file only when it still records `pid`. Prevents a late\n * shutdown or `stop` from deleting a NEWER daemon's control file after a\n * restart or PID reuse.\n */\nexport function removePidFileIfOwner(pidPath: string, pid: number): void {\n const record = readPidRecord(pidPath);\n if (record && record.pid === pid) rmSync(pidPath, { force: true });\n}\n","/**\n * Bearer-token lifecycle. The daemon auto-generates a 256-bit token on\n * first use and stores it 0600; a pre-existing file is re-chmod'd 0600\n * defensively because a world-readable token is a credential leak. The\n * token is REQUIRED on every request when the daemon binds a non-loopback\n * host (see daemon.ts); on loopback it exists but localhost is trusted.\n */\n\nimport { Buffer } from \"node:buffer\";\nimport { randomBytes, timingSafeEqual } from \"node:crypto\";\nimport { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport path from \"node:path\";\n\nexport function generateToken(): string {\n return randomBytes(32).toString(\"base64url\");\n}\n\nexport function loadOrCreateToken(tokenPath: string): string {\n mkdirSync(path.dirname(tokenPath), { recursive: true });\n if (existsSync(tokenPath)) {\n chmodSync(tokenPath, 0o600);\n const existing = readFileSync(tokenPath, \"utf8\").trim();\n if (existing) return existing;\n }\n const token = generateToken();\n writeFileSync(tokenPath, `${token}\\n`, { mode: 0o600 });\n chmodSync(tokenPath, 0o600);\n return token;\n}\n\n/** Constant-time compare; unequal lengths short-circuit to false. */\nexport function tokensMatch(expected: string, presented: string): boolean {\n const a = Buffer.from(expected, \"utf8\");\n const b = Buffer.from(presented, \"utf8\");\n if (a.length !== b.length) return false;\n return timingSafeEqual(a, b);\n}\n\n/** Parse `Authorization: Bearer <token>`; returns null when absent/malformed. */\nexport function bearerFromHeader(header: string | string[] | undefined): string | null {\n const value = Array.isArray(header) ? header[0] : header;\n if (!value) return null;\n const trimmed = value.trim();\n if (trimmed.slice(0, 6).toLowerCase() !== \"bearer\") return null;\n const separator = trimmed.charCodeAt(6);\n if (separator !== 32 && separator !== 9) return null;\n const token = trimmed.slice(6).trim();\n return token || null;\n}\n","/**\n * Request-input validation for the HTTP surface. Every failure raises\n * CaptureInputError, which the daemon maps to HTTP 400 — invalid date,\n * timezone, limit, or cursor is rejected loudly, never silently defaulted\n * (rule 39). The keyset cursor is an opaque base64url token over the\n * (started_at_utc, id) tuple the conversations query orders by.\n */\n\nimport { Buffer } from \"node:buffer\";\n\nimport { DEFAULT_CONVERSATIONS_LIMIT, MAX_CONVERSATIONS_LIMIT } from \"./constants.js\";\nimport { CaptureInputError } from \"./errors.js\";\n\nconst DATE_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/** Validate a YYYY-MM-DD calendar date (rejects e.g. 2026-02-30). */\nexport function parseTranscriptDate(value: string | null | undefined): string {\n if (typeof value !== \"string\" || !DATE_RE.test(value)) {\n throw new CaptureInputError(`invalid date '${value ?? \"\"}' — expected YYYY-MM-DD`);\n }\n const [year, month, day] = value.split(\"-\").map(Number);\n const dt = new Date(Date.UTC(year, month - 1, day));\n // Date.UTC maps a 2-digit year (0-99) into 1900-1999; force the real year.\n dt.setUTCFullYear(year);\n if (dt.getUTCFullYear() !== year || dt.getUTCMonth() !== month - 1 || dt.getUTCDate() !== day) {\n throw new CaptureInputError(`invalid date '${value}' — not a real calendar date`);\n }\n return value;\n}\n\n/** Validate an IANA timezone by attempting to build a formatter for it. */\nexport function assertValidTimezone(value: string | null | undefined): string {\n if (typeof value !== \"string\" || value.trim() === \"\") {\n throw new CaptureInputError(\"invalid timezone '' — expected an IANA timezone\");\n }\n try {\n new Intl.DateTimeFormat(\"en-CA\", { timeZone: value });\n } catch {\n throw new CaptureInputError(`invalid timezone '${value}' — not a known IANA timezone`);\n }\n return value;\n}\n\n/** Absent limit → default; present-but-invalid → 400. */\nexport function parseLimit(value: string | null | undefined): number {\n if (value === null || value === undefined) return DEFAULT_CONVERSATIONS_LIMIT;\n const n = Number(value);\n if (value === \"\" || !Number.isInteger(n) || n < 1 || n > MAX_CONVERSATIONS_LIMIT) {\n throw new CaptureInputError(\n `invalid limit '${value}' — expected an integer between 1 and ${MAX_CONVERSATIONS_LIMIT}`,\n );\n }\n return n;\n}\n\nexport interface Cursor {\n startedAtUtc: string;\n id: string;\n}\n\nexport function encodeCursor(startedAtUtc: string, id: string): string {\n return Buffer.from(JSON.stringify([startedAtUtc, id]), \"utf8\").toString(\"base64url\");\n}\n\n/** Absent cursor → null (first page); malformed cursor → 400. */\nexport function decodeCursor(value: string | null | undefined): Cursor | null {\n if (value === null || value === undefined || value === \"\") return null;\n let parsed: unknown;\n try {\n parsed = JSON.parse(Buffer.from(value, \"base64url\").toString(\"utf8\"));\n } catch {\n throw new CaptureInputError(\"invalid cursor — not a recognized pagination token\");\n }\n if (\n Array.isArray(parsed) &&\n parsed.length === 2 &&\n typeof parsed[0] === \"string\" &&\n typeof parsed[1] === \"string\" &&\n parsed[1] !== \"\" &&\n /^\\d{4}-\\d{2}-\\d{2}T/.test(parsed[0]) &&\n Number.isFinite(Date.parse(parsed[0]))\n ) {\n return { startedAtUtc: parsed[0], id: parsed[1] };\n }\n throw new CaptureInputError(\"invalid cursor — not a recognized pagination token\");\n}\n","/**\n * Loopback-only HTTP daemon. Serves the spool over three read-only routes:\n *\n * GET /v1/health → liveness + capture status + instanceId\n * GET /v1/conversations → final conversations for a local day (keyset paged)\n * GET /v1/speakers → speaker clusters (curation aid)\n *\n * Security: capture-audio serves PLAIN HTTP and has no TLS contract, so it\n * refuses to bind a non-loopback host — transcript data must never cross\n * the network in cleartext (a remote reader must front it with their own\n * TLS/tunnel, out of scope here). Every request MUST carry\n * `Authorization: Bearer <token>` matching the daemon token, even on\n * loopback, so another local user cannot read transcripts off 127.0.0.1.\n * Input errors are 400; anything unexpected is 500 with no foreign text.\n */\n\nimport http from \"node:http\";\nimport { Buffer } from \"node:buffer\";\n\nimport { CAPTURE_AUDIO_VERSION } from \"./constants.js\";\nimport { CaptureConfigError, CaptureInputError } from \"./errors.js\";\nimport { bearerFromHeader, tokensMatch } from \"./token.js\";\nimport { formatHostForUrl, isLoopbackHost } from \"./util.js\";\nimport { assertValidTimezone, parseLimit, parseTranscriptDate } from \"./validate.js\";\nimport type { DaemonConfig } from \"./config.js\";\nimport type { Spool } from \"./spool.js\";\n\nexport interface DaemonDeps {\n spool: Spool;\n config: DaemonConfig;\n token: string;\n /** Live capture status for /v1/health; a getter is re-read per request so it tracks the live runner. */\n capturing?: boolean | (() => boolean);\n}\n\nexport interface DaemonHandle {\n server: http.Server;\n host: string;\n port: number;\n url: string;\n close(): Promise<void>;\n}\n\nfunction sendJson(res: http.ServerResponse, status: number, body: unknown): void {\n const payload = JSON.stringify(body);\n res.writeHead(status, {\n \"content-type\": \"application/json; charset=utf-8\",\n \"content-length\": Buffer.byteLength(payload),\n });\n res.end(payload);\n}\n\nfunction handleHealth(deps: DaemonDeps, res: http.ServerResponse): void {\n sendJson(res, 200, {\n ok: true,\n version: CAPTURE_AUDIO_VERSION,\n platform: process.platform,\n capturing: typeof deps.capturing === \"function\" ? deps.capturing() : (deps.capturing ?? false),\n sttModel: deps.config.stt.modelPath,\n pendingChunks: deps.spool.pendingChunkCount(),\n instanceId: deps.spool.meta(\"instance_id\"),\n replayStatus: deps.spool.meta(\"replay_status\"),\n pid: process.pid,\n });\n}\n\nfunction handleConversations(deps: DaemonDeps, url: URL, res: http.ServerResponse): void {\n const date = parseTranscriptDate(url.searchParams.get(\"date\"));\n const timezone = assertValidTimezone(url.searchParams.get(\"timezone\"));\n const limit = parseLimit(url.searchParams.get(\"limit\"));\n const cursor = url.searchParams.get(\"cursor\");\n const page = deps.spool.queryFinalConversations({ date, timezone, cursor, limit });\n sendJson(res, 200, page);\n}\n\nfunction handleSpeakers(deps: DaemonDeps, res: http.ServerResponse): void {\n const speakers = deps.spool.listSpeakers().map((s) => ({ id: s.id, label: s.label, isSelf: s.isSelf }));\n sendJson(res, 200, { speakers });\n}\n\nexport function createRequestHandler(deps: DaemonDeps): http.RequestListener {\n if (!isLoopbackHost(deps.config.host)) {\n throw new CaptureConfigError(\n `refusing to bind non-loopback host '${deps.config.host}': capture-audio serves plain HTTP with no TLS contract; ` +\n \"bind a loopback address (127.0.0.1 or ::1) only\",\n );\n }\n if (!deps.token) {\n throw new CaptureConfigError(\"daemon requires a bearer token\");\n }\n return (req, res) => {\n try {\n const presented = bearerFromHeader(req.headers[\"authorization\"]);\n if (!presented || !tokensMatch(deps.token, presented)) {\n sendJson(res, 401, { error: \"unauthorized\" });\n return;\n }\n if (req.method !== \"GET\") {\n sendJson(res, 405, { error: \"method not allowed\" });\n return;\n }\n const url = new URL(req.url ?? \"/\", \"http://localhost\");\n switch (url.pathname) {\n case \"/v1/health\":\n handleHealth(deps, res);\n return;\n case \"/v1/conversations\":\n handleConversations(deps, url, res);\n return;\n case \"/v1/speakers\":\n handleSpeakers(deps, res);\n return;\n default:\n sendJson(res, 404, { error: \"not found\" });\n }\n } catch (err) {\n if (err instanceof CaptureInputError) {\n sendJson(res, 400, { error: err.message });\n return;\n }\n sendJson(res, 500, { error: \"internal error\" });\n }\n };\n}\n\nexport function startDaemon(deps: DaemonDeps): Promise<DaemonHandle> {\n return new Promise((resolve, reject) => {\n let handler: http.RequestListener;\n try {\n handler = createRequestHandler(deps);\n } catch (err) {\n reject(err as Error);\n return;\n }\n const server = http.createServer(handler);\n const onError = (err: Error) => reject(err);\n server.once(\"error\", onError);\n server.listen(deps.config.port, deps.config.host, () => {\n server.removeListener(\"error\", onError);\n // Keep a persistent handler so a post-startup socket error cannot crash\n // the daemon as an unhandled 'error' event (sanitized; no foreign text).\n server.on(\"error\", (err: NodeJS.ErrnoException) => {\n process.stderr.write(`capture-audio daemon server error: ${err.code ?? err.name}\\n`);\n });\n const address = server.address();\n const port = typeof address === \"object\" && address ? address.port : deps.config.port;\n const host = deps.config.host;\n resolve({\n server,\n host,\n port,\n url: `http://${formatHostForUrl(host)}:${port}`,\n close: () =>\n new Promise<void>((res2, rej2) => {\n server.close((closeErr) => (closeErr ? rej2(closeErr) : res2()));\n }),\n });\n });\n });\n}\n","/** Filesystem layout for the capture working directory. */\n\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nexport interface CapturePaths {\n baseDir: string;\n configPath: string;\n spoolPath: string;\n tokenPath: string;\n pidPath: string;\n logPath: string;\n}\n\nexport function expandTilde(value: string): string {\n if (value === \"~\") return os.homedir();\n if (value.startsWith(\"~/\")) return path.join(os.homedir(), value.slice(2));\n return value;\n}\n\n/**\n * Root of the capture working directory. `REMNIC_CAPTURE_DIR` overrides\n * the default `~/.remnic/capture` (tests and multi-instance setups point\n * it at a scratch dir). A leading `~` expands to the home directory.\n */\n\nexport function captureBaseDir(env: NodeJS.ProcessEnv = process.env): string {\n const override = env.REMNIC_CAPTURE_DIR?.trim();\n if (override) return expandTilde(override);\n return path.join(os.homedir(), \".remnic\", \"capture\");\n}\n\nexport function capturePaths(baseDir: string = captureBaseDir()): CapturePaths {\n return {\n baseDir,\n configPath: path.join(baseDir, \"audio.json\"),\n spoolPath: path.join(baseDir, \"audio.sqlite\"),\n tokenPath: path.join(baseDir, \"token\"),\n pidPath: path.join(baseDir, \"daemon.pid\"),\n logPath: path.join(baseDir, \"daemon.log\"),\n };\n}\n","/**\n * `--replay <dir>` ingestion. Feeds synthetic fixture conversations into\n * the spool so the entire read path (spool + HTTP API) is testable in CI\n * without capture hardware or STT. Fixtures are synthetic by policy — no\n * real audio or conversation data lives in the repo, and none is required\n * for tests.\n *\n * Each `*.json` fixture is either a single conversation object or an array\n * of them. Every field is validated loudly: an absent optional field takes\n * its default, but a present-but-wrong-typed/invalid field throws\n * CaptureConfigError naming the file and path (no silent coercion). A\n * conversation is fully parsed BEFORE its speakers are upserted, so a\n * malformed conversation never persists speaker rows.\n *\n * {\n * \"id\": \"conv_demo1\", // optional; generated if absent\n * \"startedAtUtc\": \"2026-07-20T15:00:00.000Z\",\n * \"endedAtUtc\": \"2026-07-20T15:05:00.000Z\", // optional\n * \"state\": \"final\", // optional; \"final\" | \"capturing\"\n * \"device\": \"MacBook mic\", // optional\n * \"speakers\": [ { \"id\": \"spk_1\", \"label\": \"Alice\", \"isSelf\": false } ],\n * \"segments\": [\n * { \"speakerCluster\": \"spk_1\", \"isWearer\": false, \"channel\": \"mic\",\n * \"text\": \"hello there\", \"startUtc\": \"...\", \"endUtc\": \"...\" }\n * ]\n * }\n *\n * Ingestion is idempotent by conversation id (see Spool.insertConversation),\n * so re-running a replay is a content no-op.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { lstatSync, readdirSync, readFileSync } from \"node:fs\";\nimport path from \"node:path\";\n\nimport { CaptureConfigError } from \"./errors.js\";\nimport type { ConversationInput, SegmentInput, SpeakerInput, Spool } from \"./spool.js\";\n\nexport interface ReplayResult {\n files: number;\n conversationsIngested: number;\n segmentsIngested: number;\n ids: string[];\n /** True when a cooperative cancel (AbortSignal) stopped ingestion early. */\n aborted: boolean;\n}\n\nfunction asObject(value: unknown, where: string): Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new CaptureConfigError(`${where}: expected a conversation object`);\n }\n return value as Record<string, unknown>;\n}\n\n/** A full ISO instant: date + time + Z or numeric offset. Offsetless local\n * timestamps are rejected so a non-UTC host cannot shift a `*Utc` fixture to\n * the wrong UTC day during canonicalization below. */\nconst REPLAY_INSTANT = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(:\\d{2}(\\.\\d{1,9})?)?(Z|[+-]\\d{2}:\\d{2})$/;\n\nfunction parseTimestamp(value: unknown, where: string): string {\n if (typeof value !== \"string\" || !REPLAY_INSTANT.test(value)) {\n throw new CaptureConfigError(`${where}: expected an ISO instant with a Z or numeric offset`);\n }\n const ms = Date.parse(value);\n if (!Number.isFinite(ms)) {\n throw new CaptureConfigError(`${where}: expected a valid ISO timestamp`);\n }\n // Reject non-round-trippable calendar dates (e.g. 2026-02-30) that Date.parse\n // silently rolls forward. Validate the written date part directly.\n const [cy, cm, cd] = value.slice(0, 10).split(\"-\").map(Number);\n const probe = new Date(Date.UTC(cy, cm - 1, cd));\n probe.setUTCFullYear(cy);\n if (probe.getUTCFullYear() !== cy || probe.getUTCMonth() !== cm - 1 || probe.getUTCDate() !== cd) {\n throw new CaptureConfigError(`${where}: '${value}' is not a real calendar date`);\n }\n // Canonicalize to UTC (Z) so an offset timestamp sorts correctly under the\n // keyset that orders by the stored *_utc value.\n return new Date(ms).toISOString();\n}\n\n/** Optional string: absent → fallback; present-non-string → throw. */\nfunction optionalString(value: unknown, where: string, fallback: string | null): string | null {\n if (value === undefined) return fallback;\n if (typeof value !== \"string\") throw new CaptureConfigError(`${where}: expected a string`);\n return value;\n}\n\nfunction parseSegment(raw: unknown, where: string): SegmentInput {\n const obj = asObject(raw, where);\n if (typeof obj.text !== \"string\" || obj.text === \"\") {\n throw new CaptureConfigError(`${where}.text: expected a non-empty string`);\n }\n const startUtc = parseTimestamp(obj.startUtc, `${where}.startUtc`);\n const endUtc = parseTimestamp(obj.endUtc, `${where}.endUtc`);\n if (Date.parse(endUtc) < Date.parse(startUtc)) {\n throw new CaptureConfigError(`${where}: endUtc must not precede startUtc`);\n }\n if (obj.isWearer !== undefined && typeof obj.isWearer !== \"boolean\") {\n throw new CaptureConfigError(`${where}.isWearer: expected a boolean`);\n }\n const channel = obj.channel === undefined ? \"mic\" : obj.channel;\n if (typeof channel !== \"string\" || channel === \"\") {\n throw new CaptureConfigError(`${where}.channel: expected a non-empty string`);\n }\n return {\n speakerCluster: optionalString(obj.speakerCluster, `${where}.speakerCluster`, null),\n isWearer: obj.isWearer === true,\n channel,\n text: obj.text,\n startUtc,\n endUtc,\n };\n}\n\nfunction parseConversation(raw: unknown, where: string): ConversationInput {\n const obj = asObject(raw, where);\n const startedAtUtc = parseTimestamp(obj.startedAtUtc, `${where}.startedAtUtc`);\n const endedAtUtc = obj.endedAtUtc === undefined ? null : parseTimestamp(obj.endedAtUtc, `${where}.endedAtUtc`);\n if (endedAtUtc !== null && Date.parse(endedAtUtc) < Date.parse(startedAtUtc)) {\n throw new CaptureConfigError(`${where}: endedAtUtc must not precede startedAtUtc`);\n }\n if (obj.state !== undefined && obj.state !== \"capturing\" && obj.state !== \"final\") {\n throw new CaptureConfigError(`${where}.state: expected \"capturing\" or \"final\"`);\n }\n if (obj.id !== undefined && (typeof obj.id !== \"string\" || obj.id === \"\")) {\n throw new CaptureConfigError(`${where}.id: expected a non-empty string`);\n }\n if (!Array.isArray(obj.segments)) {\n throw new CaptureConfigError(`${where}.segments: expected an array`);\n }\n return {\n id: obj.id === undefined ? undefined : (obj.id as string),\n startedAtUtc,\n endedAtUtc,\n state: obj.state ?? \"final\",\n device: optionalString(obj.device, `${where}.device`, null),\n segments: obj.segments.map((seg, i) => parseSegment(seg, `${where}.segments[${i}]`)),\n };\n}\n\n/** Parse + validate a fixture's speakers WITHOUT touching the spool. */\nfunction parseSpeakers(raw: unknown, where: string): SpeakerInput[] {\n if (raw === undefined) return [];\n if (!Array.isArray(raw)) {\n throw new CaptureConfigError(`${where}.speakers: expected an array`);\n }\n return raw.map((entry, i) => {\n const obj = asObject(entry, `${where}.speakers[${i}]`);\n if (typeof obj.id !== \"string\" || obj.id === \"\") {\n throw new CaptureConfigError(`${where}.speakers[${i}].id: expected a non-empty string`);\n }\n if (obj.isSelf !== undefined && typeof obj.isSelf !== \"boolean\") {\n throw new CaptureConfigError(`${where}.speakers[${i}].isSelf: expected a boolean`);\n }\n // Only carry fields the fixture actually provided so upsertSpeaker\n // preserves omitted label/isSelf on an existing cluster (issue: a later\n // { id } reference must not wipe an established speaker).\n const speaker: SpeakerInput = { id: obj.id };\n if (obj.label !== undefined) {\n speaker.label = optionalString(obj.label, `${where}.speakers[${i}].label`, null);\n }\n if (obj.isSelf !== undefined) {\n speaker.isSelf = obj.isSelf === true;\n }\n return speaker;\n });\n}\n\ninterface ParsedFixture {\n speakers: SpeakerInput[];\n conv: ConversationInput;\n}\n\n/**\n * Ingest a directory of synthetic replay fixtures atomically: EVERY record\n * (conversation + speakers) is parsed and validated in a first pass, and\n * nothing is written to the spool until the whole set is known valid. A\n * later invalid record therefore leaves no earlier mutation (no stray\n * speaker rows, no half-committed batch).\n */\ninterface ParsedReplay {\n fixtures: ParsedFixture[];\n files: number;\n}\n\n/**\n * List the sorted `*.json` fixture files in a replay directory, rejecting a\n * symlinked directory. Throws when the directory is missing/unreadable or empty.\n */\nfunction listReplayFixtureFiles(dir: string): string[] {\n let entries: string[];\n try {\n if (lstatSync(dir).isSymbolicLink()) {\n throw new CaptureConfigError(`replay dir ${dir} is a symlink; refusing to follow it`);\n }\n entries = readdirSync(dir).filter((name) => name.endsWith(\".json\")).sort();\n } catch (err) {\n if (err instanceof CaptureConfigError) throw err;\n throw new CaptureConfigError(`replay dir not found or unreadable: ${dir}`);\n }\n if (entries.length === 0) {\n throw new CaptureConfigError(`replay dir ${dir} contains no *.json fixtures`);\n }\n return entries;\n}\n\n/**\n * Parse + validate ONE fixture file into `fixtures`. Rejects symlinked files\n * and invalid JSON, derives content-hash ids, and guards intra-batch id\n * collisions via the shared `seenIds` set. Pure w.r.t. the spool (no writes).\n */\nfunction parseReplayFile(\n dir: string,\n name: string,\n seenIds: Set<string>,\n fixtures: ParsedFixture[],\n): void {\n const filePath = path.join(dir, name);\n if (lstatSync(filePath).isSymbolicLink()) {\n throw new CaptureConfigError(`replay fixture ${name} is a symlink; refusing to follow it`);\n }\n let raw: unknown;\n try {\n raw = JSON.parse(readFileSync(filePath, \"utf8\"));\n } catch (err) {\n throw new CaptureConfigError(`replay fixture ${name} is not valid JSON: ${(err as Error).message}`);\n }\n const docs = Array.isArray(raw) ? raw : [raw];\n docs.forEach((doc, i) => {\n const where = `${name}[${i}]`;\n const conv = parseConversation(doc, where);\n if (conv.id === undefined) {\n // Derive the id from conversation CONTENT so identical fixtures stay\n // idempotent across replays while distinct conversations that happen to\n // share a filename/index/start time cannot collide.\n const material = JSON.stringify({\n startedAtUtc: conv.startedAtUtc,\n endedAtUtc: conv.endedAtUtc,\n state: conv.state,\n segments: conv.segments,\n });\n conv.id = `conv_${createHash(\"sha1\").update(material).digest(\"hex\").slice(0, 24)}`;\n }\n // Distinct conversations must not share an id within one batch — a silent\n // delete-then-insert overwrite would drop data. (Cross-call re-ingest of\n // the same fixture stays idempotent: each call is its own batch.)\n if (seenIds.has(conv.id as string)) {\n throw new CaptureConfigError(`${where}: duplicate conversation id '${conv.id}' in this replay batch`);\n }\n seenIds.add(conv.id as string);\n const speakers = parseSpeakers(asObject(doc, where).speakers, where);\n fixtures.push({ speakers, conv });\n });\n}\n\n/**\n * Parse + validate an entire replay directory WITHOUT writing to the spool.\n * Throws on the first invalid record so nothing is ever partially committed\n * (atomic failure semantics live here, before any commit runs).\n */\nfunction parseReplayDir(dir: string): ParsedReplay {\n const entries = listReplayFixtureFiles(dir);\n const fixtures: ParsedFixture[] = [];\n const seenIds = new Set<string>();\n for (const name of entries) parseReplayFile(dir, name, seenIds, fixtures);\n return { fixtures, files: entries.length };\n}\n\nfunction commitFixture(spool: Spool, fixture: ParsedFixture, result: ReplayResult): void {\n for (const speaker of fixture.speakers) spool.upsertSpeaker(speaker);\n const id = spool.insertConversation(fixture.conv);\n result.ids.push(id);\n result.conversationsIngested += 1;\n result.segmentsIngested += fixture.conv.segments.length;\n}\n\n/** Commit size between event-loop yields in the responsive ingester. */\nexport const REPLAY_COMMIT_BATCH = 25;\n\n/** Synchronous ingest: validate the whole directory, then commit it all. */\nexport function ingestReplayDir(spool: Spool, dir: string): ReplayResult {\n const { fixtures, files } = parseReplayDir(dir);\n const result: ReplayResult = { files, conversationsIngested: 0, segmentsIngested: 0, ids: [], aborted: false };\n for (const fixture of fixtures) commitFixture(spool, fixture, result);\n return result;\n}\n\n/**\n * Responsive ingest: everything is validated up front (atomic — a later\n * invalid record commits nothing), then committed in bounded batches with an\n * event-loop yield between them so a co-hosted HTTP server stays responsive\n * during a large replay.\n */\nexport async function ingestReplayDirResponsive(\n spool: Spool,\n dir: string,\n options: { signal?: AbortSignal } = {},\n): Promise<ReplayResult> {\n const entries = listReplayFixtureFiles(dir);\n const fixtures: ParsedFixture[] = [];\n const seenIds = new Set<string>();\n // Phase 1 — parse + validate with an event-loop yield between files so a\n // co-hosted HTTP server stays responsive while a large multi-file replay is\n // read/parsed (not just while it commits). A single very large JSON file\n // still blocks on its one synchronous JSON.parse — inherent to sync JSON.\n for (const name of entries) {\n if (options.signal?.aborted) {\n return { files: entries.length, conversationsIngested: 0, segmentsIngested: 0, ids: [], aborted: true };\n }\n parseReplayFile(dir, name, seenIds, fixtures);\n await new Promise<void>((resolve) => setImmediate(resolve));\n }\n // Phase 2 — commit the fully-validated set in bounded batches (still atomic:\n // a parse failure above committed nothing).\n const files = entries.length;\n const result: ReplayResult = { files, conversationsIngested: 0, segmentsIngested: 0, ids: [], aborted: false };\n for (let i = 0; i < fixtures.length; i += REPLAY_COMMIT_BATCH) {\n // Cooperative cancel: check between batches so no commit runs after a\n // shutdown has been requested (and thus none after the spool is closed).\n if (options.signal?.aborted) {\n result.aborted = true;\n break;\n }\n for (const fixture of fixtures.slice(i, i + REPLAY_COMMIT_BATCH)) {\n commitFixture(spool, fixture, result);\n }\n await new Promise<void>((resolve) => setImmediate(resolve));\n }\n return result;\n}","import { createWriteStream, lstatSync, statSync } from \"node:fs\";\nimport { mkdir, rename, rm } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { Readable } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\";\n\nimport { CaptureConfigError } from \"./errors.js\";\n\nconst MODEL_FILES: Record<string, string> = {\n base: \"ggml-base.bin\",\n small: \"ggml-small.bin\",\n \"large-v3-turbo-q5_0\": \"ggml-large-v3-turbo-q5_0.bin\",\n};\n\nconst MODEL_REPOSITORY = \"https://huggingface.co/ggerganov/whisper.cpp/resolve/main\";\n\ntype ModelFetch = (url: string) => Promise<Response>;\n\nexport interface ModelDownloadInput {\n model: string;\n directory: string;\n fetch?: ModelFetch;\n}\n\nexport interface ModelDownloadResult {\n path: string;\n downloaded: boolean;\n}\n\nfunction responseBodyToReadable(body: ReadableStream<Uint8Array>): Readable {\n const reader = body.getReader();\n return new Readable({\n read() {\n void reader\n .read()\n .then(({ done, value }) => this.push(done ? null : Buffer.from(value)))\n .catch((error: unknown) => this.destroy(error as Error));\n },\n });\n}\n\nfunction existingFile(destination: string): boolean {\n let entry;\n try {\n entry = statSync(destination);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n // statSync follows symlinks, so ENOENT is either no directory entry at\n // all or a dangling symlink. lstatSync tells them apart: if it succeeds,\n // a broken symlink occupies the path and must be cleared, not silently\n // treated as absent (a later rename/link would fail on it).\n try {\n lstatSync(destination);\n } catch (linkError) {\n if ((linkError as NodeJS.ErrnoException).code === \"ENOENT\") return false;\n throw linkError;\n }\n throw new CaptureConfigError(`Whisper model path is a broken symlink: ${destination}; remove it and retry`);\n }\n if (!entry.isFile()) {\n throw new CaptureConfigError(`Whisper model path exists but is not a regular file: ${destination}`);\n }\n return true;\n}\n\nexport function whisperModelUrl(model: string): string {\n if (!Object.hasOwn(MODEL_FILES, model)) {\n throw new CaptureConfigError(`unknown Whisper model '${model}'; expected one of ${Object.keys(MODEL_FILES).join(\", \")}`);\n }\n const file = MODEL_FILES[model];\n return `${MODEL_REPOSITORY}/${file}`;\n}\n\nexport async function downloadWhisperModel(input: ModelDownloadInput): Promise<ModelDownloadResult> {\n const url = whisperModelUrl(input.model);\n const filename = new URL(url).pathname.split(\"/\").at(-1);\n if (!filename) throw new CaptureConfigError(\"Whisper model URL has no filename\");\n\n await mkdir(input.directory, { recursive: true });\n const destination = path.join(input.directory, filename);\n if (existingFile(destination)) return { path: destination, downloaded: false };\n\n let response;\n try {\n response = await (input.fetch ?? ((value) => fetch(value)))(url);\n } catch {\n throw new CaptureConfigError(\n `failed to download ${input.model}: the network request to Hugging Face failed (check connectivity/proxy/DNS)`,\n );\n }\n if (!response.ok || !response.body) {\n throw new CaptureConfigError(`failed to download ${input.model}: HTTP ${response.status}`);\n }\n\n const temporary = path.join(input.directory, `.${filename}.${process.pid}.${crypto.randomUUID()}.tmp`);\n try {\n await pipeline(responseBodyToReadable(response.body), createWriteStream(temporary, { flags: \"wx\", mode: 0o600 }));\n // Atomic same-directory rename: works on filesystems without hard-link\n // support (FAT32/exFAT, some network mounts), unlike link()+rm().\n await rename(temporary, destination);\n return { path: destination, downloaded: true };\n } catch (error) {\n await rm(temporary, { force: true });\n throw error;\n }\n}\n","import { lstat, readdir, rm } from \"node:fs/promises\";\nimport type { Dirent } from \"node:fs\";\nimport path from \"node:path\";\n\nimport { CaptureConfigError } from \"./errors.js\";\n\nexport async function pruneExpiredRawAudio(rawDirectory: string, retentionMs: number, nowMs: number = Date.now()): Promise<string[]> {\n if (!Number.isFinite(retentionMs) || retentionMs < 0) {\n throw new CaptureConfigError(\"raw audio retention must be a non-negative duration\");\n }\n const cutoffMs = nowMs - retentionMs;\n let root;\n try {\n root = await lstat(rawDirectory);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return [];\n throw error;\n }\n if (root.isSymbolicLink() || !root.isDirectory()) {\n throw new CaptureConfigError(\"raw audio directory must be a non-symlink directory\");\n }\n\n const entries = await readdir(rawDirectory, { withFileTypes: true });\n const removed: string[] = [];\n for (const entry of entries) {\n if (!entry.isFile()) continue;\n const location = path.join(rawDirectory, entry.name);\n try {\n const stat = await lstat(location);\n if (!stat.isFile()) continue;\n if (stat.mtimeMs <= cutoffMs) {\n await rm(location);\n removed.push(location);\n }\n } catch (error) {\n // A concurrent capture/cleanup can delete a chunk between readdir and\n // lstat/rm; a vanished file is already pruned, so skip it and continue\n // instead of aborting the whole janitor run with a generic FS error.\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") continue;\n throw error;\n }\n }\n return removed.sort();\n}\n","import type { ChunkEvent } from \"./native.js\";\n\n/** Ceiling on chunks held in the reorder buffer. */\nexport const MAX_BUFFERED_CHUNKS = 512;\n\n/** Consecutive apply failures before a chunk is parked. */\nexport const QUARANTINE_AFTER_FAILURES = 3;\n\n/** Oldest quarantined rows are dropped once this many are stored. */\nexport const MAX_QUARANTINED_CHUNKS = 64;\n\nexport type PendingChunkReason = \"evicted\" | \"quarantined\";\n\nexport interface PendingChunkInput {\n id: string;\n wavPath: string;\n startedAtUtc: string;\n endedAtUtc: string;\n channel: ChunkEvent[\"channel\"];\n device: string | null;\n reason: PendingChunkReason;\n}\n\nexport interface PendingChunkRecord extends PendingChunkInput {\n createdAtUtc: string;\n}\n\nexport class ChunkApplyError extends Error {\n readonly chunkId: string;\n\n constructor(chunkId: string, cause: unknown) {\n super(cause instanceof Error ? cause.message : String(cause));\n this.name = \"ChunkApplyError\";\n this.chunkId = chunkId;\n if (cause instanceof Error) this.cause = cause;\n }\n}\n\nexport function spansOverlap(\n left: { startMs: number; endMs: number },\n right: { startMs: number; endMs: number },\n): boolean {\n return left.startMs < right.endMs && right.startMs < left.endMs;\n}\n\n/**\n * A chunk may leave the buffer only when the watermark has passed its end\n * and no still-held chunk overlaps it (issue #2379).\n */\nexport function isReleaseEligible(\n candidate: { startMs: number; endMs: number },\n threshold: number,\n held: readonly { startMs: number; endMs: number }[],\n): boolean {\n if (candidate.endMs > threshold) return false;\n for (const other of held) {\n if (spansOverlap(candidate, other)) return false;\n }\n return true;\n}\n","/**\n * SQLite spool — the daemon's local buffer of captured conversations.\n *\n * Uses the built-in `node:sqlite` driver (no native dependency), keeping\n * @remnic/capture-audio à-la-carte: installing it pulls zero extra runtime\n * packages. WAL mode + foreign keys are enabled per connection.\n *\n * Schema (names/semantics fixed by issue #1897):\n * chunks(id, channel, device, started_at_utc, ended_at_utc, status, wav_path)\n * segments(id, chunk_id FK, conversation_id FK, speaker_cluster, is_wearer,\n * channel, text, start_utc, end_utc, ordinal)\n * conversations(id, started_at_utc, ended_at_utc, state, segment_count)\n * speaker_clusters(id, label, centroid, example_embeddings, embedding_count, is_self)\n * meta(key, value)\n *\n * The public read API (`queryFinalConversations`) serves ONLY `final`\n * conversations, ordered by a stable keyset (started_at_utc, id) so the\n * connector never ingests half a meeting and pagination is deterministic\n * even when two conversations share a start timestamp.\n */\n\nimport { chmodSync } from \"node:fs\";\nimport { DatabaseSync } from \"node:sqlite\";\n\nimport { SPOOL_SCHEMA_VERSION } from \"./constants.js\";\nimport {\n MAX_QUARANTINED_CHUNKS,\n type PendingChunkInput,\n type PendingChunkReason,\n type PendingChunkRecord,\n} from \"./buffer-policy.js\";\nimport { CaptureConfigError } from \"./errors.js\";\nimport { dateInTimezone, ulid } from \"./util.js\";\nimport { decodeCursor, encodeCursor } from \"./validate.js\";\n\nexport type ConversationState = \"capturing\" | \"final\";\nexport type ChunkStatus = \"pending\" | \"transcribed\" | \"failed\" | \"deleted\";\n\nexport interface SegmentInput {\n speakerCluster?: string | null;\n isWearer?: boolean;\n /**\n * Speaker embedding for this segment, persisted as a JSON BLOB (issue\n * #2145). Clustering runs at finalize over the segments that SURVIVE\n * cross-channel dedup, so a pruned loopback duplicate never inflates a\n * cluster's centroid or count.\n */\n embedding?: readonly number[] | null;\n channel: string;\n text: string;\n startUtc: string;\n endUtc: string;\n}\nexport interface ConversationInput {\n id?: string;\n startedAtUtc: string;\n endedAtUtc?: string | null;\n state?: ConversationState;\n device?: string | null;\n chunkStatus?: ChunkStatus;\n wavPath?: string | null;\n segments: SegmentInput[];\n}\nexport interface SpeakerInput {\n id: string;\n label?: string | null;\n isSelf?: boolean;\n embeddingCount?: number;\n /** Speaker embedding centroid; persisted as a JSON BLOB for restart-stable ids. */\n centroid?: readonly number[] | null;\n /** Bounded diverse example embeddings; persisted as a JSON BLOB. */\n examples?: readonly (readonly number[])[] | null;\n}\nexport interface SpeakerClusterRow {\n id: string;\n label: string | null;\n isSelf: boolean;\n embeddingCount: number;\n centroid: number[];\n examples: number[][];\n}\nexport interface DaemonSegment {\n textRaw: string;\n speakerKey: string | null;\n isWearer: boolean;\n channel: string;\n startUtc: string;\n endUtc: string;\n}\nexport interface DaemonConversation {\n id: string;\n startedAtUtc: string;\n endedAtUtc: string | null;\n state: ConversationState;\n segmentCount: number;\n segments: DaemonSegment[];\n}\nexport interface ConversationPage {\n conversations: DaemonConversation[];\n nextCursor: string | null;\n}\nexport interface SpeakerRow {\n id: string;\n label: string | null;\n isSelf: boolean;\n embeddingCount: number;\n}\nexport interface QueryFinalOptions {\n date: string;\n timezone: string;\n cursor?: string | null;\n limit: number;\n}\nexport interface AssemblyAppendInput {\n /** Durable dedup marker for one application (e.g. a transcribed chunk id). */\n idempotencyKey: string;\n /** Stable `conv_<ulid>` id from the assembler. */\n conversationId: string;\n /** Conversation start; used only when the conversation is first created. */\n startedAtUtc: string;\n /** Defaults to `capturing`; the conversation is finalized by a later call. */\n state?: ConversationState;\n device?: string | null;\n /** Backing chunk row id; defaults to `idempotencyKey`. */\n chunkId?: string;\n /** Backing WAV path recorded on the chunk row; retained for the janitor/audit. */\n wavPath?: string | null;\n segments: SegmentInput[];\n}\nexport interface AssemblyAppendResult {\n /** False when `idempotencyKey` was already applied (a replay no-op). */\n applied: boolean;\n conversationId: string;\n /** Total segments in the conversation after this call. */\n segmentCount: number;\n}\n\ninterface ConversationRow {\n id: string;\n startedAtUtc: string;\n endedAtUtc: string | null;\n state: ConversationState;\n segmentCount: number;\n}\n\nconst SCHEMA_SQL = `\nCREATE TABLE IF NOT EXISTS meta (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS conversations (\n id TEXT PRIMARY KEY,\n started_at_utc TEXT NOT NULL,\n ended_at_utc TEXT,\n state TEXT NOT NULL,\n segment_count INTEGER NOT NULL DEFAULT 0\n);\nCREATE TABLE IF NOT EXISTS chunks (\n id TEXT PRIMARY KEY,\n channel TEXT NOT NULL,\n device TEXT,\n started_at_utc TEXT NOT NULL,\n ended_at_utc TEXT NOT NULL,\n status TEXT NOT NULL,\n wav_path TEXT\n);\nCREATE TABLE IF NOT EXISTS segments (\n id TEXT PRIMARY KEY,\n chunk_id TEXT REFERENCES chunks(id) ON DELETE CASCADE,\n conversation_id TEXT REFERENCES conversations(id) ON DELETE CASCADE,\n speaker_cluster TEXT,\n is_wearer INTEGER NOT NULL DEFAULT 0,\n channel TEXT NOT NULL,\n text TEXT NOT NULL,\n start_utc TEXT NOT NULL,\n end_utc TEXT NOT NULL,\n ordinal INTEGER NOT NULL DEFAULT 0,\n embedding BLOB\n);\nCREATE TABLE IF NOT EXISTS speaker_clusters (\n id TEXT PRIMARY KEY,\n label TEXT,\n centroid BLOB,\n example_embeddings BLOB,\n embedding_count INTEGER NOT NULL DEFAULT 0,\n is_self INTEGER NOT NULL DEFAULT 0\n);\nCREATE TABLE IF NOT EXISTS applied_chunks (\n idempotency_key TEXT PRIMARY KEY,\n conversation_id TEXT NOT NULL,\n applied_at_utc TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS pending_chunks (\n id TEXT PRIMARY KEY,\n wav_path TEXT NOT NULL,\n started_at_utc TEXT NOT NULL,\n ended_at_utc TEXT NOT NULL,\n channel TEXT NOT NULL,\n device TEXT,\n reason TEXT NOT NULL,\n created_at_utc TEXT NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_conv_keyset ON conversations(started_at_utc, id);\nCREATE INDEX IF NOT EXISTS idx_seg_conv ON segments(conversation_id, ordinal);\n`;\n\n/**\n * Encode a speaker embedding for the `segments.embedding` BLOB. `null` and an\n * empty vector both persist as NULL: an absent embedding must not look like a\n * zero-length one at finalize.\n */\nfunction encodeEmbedding(embedding: readonly number[] | null | undefined): Buffer | null {\n if (!embedding || embedding.length === 0) return null;\n return Buffer.from(JSON.stringify(embedding));\n}\n\n/** Decode a JSON-encoded numeric vector BLOB; corrupt rows decode to null. */\nfunction decodeEmbedding(blob: Buffer | Uint8Array | null): number[] | null {\n if (!blob || blob.byteLength === 0) return null;\n try {\n const parsed: unknown = JSON.parse(Buffer.from(blob).toString(\"utf8\"));\n if (!Array.isArray(parsed)) return null;\n return parsed.every((n) => typeof n === \"number\" && Number.isFinite(n)) ? (parsed as number[]) : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Canonical instant required at the Spool boundary: a full date + time with a\n * Z or numeric offset (e.g. `2026-07-20T15:04:05.000Z`). Date-only strings\n * (`2026-07-20`) and offsetless local timestamps are REJECTED so every value\n * persisted in a `*_utc` column - and every keyset cursor derived from one -\n * is an unambiguous, order-stable instant. Replay/connector inputs are already\n * canonicalized to Z upstream; this guards direct `insertConversation` callers.\n */\nconst ISO_INSTANT = /^(\\d{4})-(\\d{2})-(\\d{2})T\\d{2}:\\d{2}(:\\d{2}(\\.\\d{1,9})?)?(Z|[+-]\\d{2}:\\d{2})$/;\n\nfunction assertIsoInstant(value: string, label: string): void {\n const match = typeof value === \"string\" ? ISO_INSTANT.exec(value) : null;\n if (!match || !Number.isFinite(Date.parse(value))) {\n throw new CaptureConfigError(\n `${label}: '${value}' is not a canonical ISO instant (need date, time, and Z or offset)`,\n );\n }\n const year = Number(match[1]);\n const month = Number(match[2]);\n const day = Number(match[3]);\n const probe = new Date(Date.UTC(year, month - 1, day));\n probe.setUTCFullYear(year);\n if (probe.getUTCFullYear() !== year || probe.getUTCMonth() !== month - 1 || probe.getUTCDate() !== day) {\n throw new CaptureConfigError(`${label}: '${value}' is not a real calendar date`);\n }\n}\n\n/**\n * Validate + canonicalize an instant to UTC `Z`. Accepted offset instants are\n * normalized (not stored verbatim) so every persisted `*_utc` value and the\n * keyset cursor sort by true UTC under SQLite's lexical TEXT ordering.\n */\nfunction canonicalInstant(value: string, label: string): string {\n assertIsoInstant(value, label);\n return new Date(value).toISOString();\n}\n\n/** Runtime-checkable enum tables (Spool is exported; JS callers are unchecked by TS). */\nconst CONVERSATION_STATES: Record<ConversationState, true> = { capturing: true, final: true };\nconst CHUNK_STATUSES: Record<ChunkStatus, true> = {\n pending: true,\n transcribed: true,\n failed: true,\n deleted: true,\n};\n\nexport class Spool {\n #db: DatabaseSync;\n #closed = false;\n\n constructor(location: string) {\n this.#db = new DatabaseSync(location);\n this.#db.exec(\"PRAGMA journal_mode = WAL;\");\n this.#db.exec(\"PRAGMA foreign_keys = ON;\");\n this.#db.exec(\"PRAGMA busy_timeout = 5000;\");\n this.#db.exec(SCHEMA_SQL);\n if (location !== \":memory:\") {\n // Transcript spool holds conversation text; keep it owner-only on\n // multi-user desktops (best-effort; ignored where chmod is a no-op).\n try {\n chmodSync(location, 0o600);\n } catch {\n // filesystem without POSIX perms (e.g. some Windows mounts)\n }\n }\n // Refuse a spool written by a NEWER binary: migrating it here, and then\n // stamping our lower version over its higher one, would leave that binary\n // reading a schema it no longer recognizes (issue #2145).\n const storedVersion = this.#db\n .prepare(\"SELECT value FROM meta WHERE key = 'schema_version'\")\n .get() as { value?: string } | undefined;\n if (storedVersion?.value !== undefined) {\n const parsed = Number(storedVersion.value);\n if (!Number.isInteger(parsed) || parsed < 1) {\n throw new CaptureConfigError(\n `spool schema_version is malformed: ${JSON.stringify(storedVersion.value)}`,\n );\n }\n if (parsed > SPOOL_SCHEMA_VERSION) {\n throw new CaptureConfigError(\n `spool schema_version ${parsed} is newer than this build supports (${SPOOL_SCHEMA_VERSION})`,\n );\n }\n }\n // Additive migration for a spool created before segment embeddings existed\n // (issue #2145). CREATE TABLE IF NOT EXISTS leaves an existing table alone,\n // so the column is added explicitly; ALTER is skipped when it is present.\n const segmentColumns = this.#db.prepare(\"PRAGMA table_info(segments)\").all() as Array<{ name: string }>;\n if (!segmentColumns.some((column) => column.name === \"embedding\")) {\n this.#db.exec(\"ALTER TABLE segments ADD COLUMN embedding BLOB\");\n }\n this.#db\n .prepare(\"INSERT INTO meta(key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value\")\n .run(\"schema_version\", String(SPOOL_SCHEMA_VERSION));\n this.#db.prepare(\"INSERT OR IGNORE INTO meta(key, value) VALUES (?, ?)\").run(\"instance_id\", ulid());\n }\n\n close(): void {\n if (this.#closed) return;\n this.#closed = true;\n this.#db.close();\n }\n\n meta(key: string): string | null {\n const row = this.#db.prepare(\"SELECT value FROM meta WHERE key = ?\").get(key) as\n | { value: string }\n | undefined;\n return row?.value ?? null;\n }\n\n setMeta(key: string, value: string): void {\n this.#db\n .prepare(\"INSERT INTO meta(key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value\")\n .run(key, value);\n }\n\n /**\n * Insert (or replace) a whole conversation with its segments and a\n * backing chunk row, atomically. Idempotent by conversation id:\n * re-ingesting the same id deletes the prior rows first, so a repeated\n * replay is a content no-op (kill-9 restart safety, acceptance criteria).\n */\n insertConversation(input: ConversationInput): string {\n if (typeof input.startedAtUtc !== \"string\" || input.startedAtUtc.trim() === \"\") {\n throw new CaptureConfigError(\"conversation.startedAtUtc: expected a non-empty ISO timestamp\");\n }\n if (!Array.isArray(input.segments)) {\n throw new CaptureConfigError(\"conversation.segments: expected an array\");\n }\n // Validate every timestamp before persisting so a direct Spool caller (not\n // just replay) cannot store a value that becomes an Invalid Date in\n // queryFinalConversations' day bucketing.\n const startedAtUtc = canonicalInstant(input.startedAtUtc, \"conversation.startedAtUtc\");\n const endedAtUtcInput =\n input.endedAtUtc !== undefined && input.endedAtUtc !== null\n ? canonicalInstant(input.endedAtUtc, \"conversation.endedAtUtc\")\n : null;\n if (endedAtUtcInput !== null && Date.parse(endedAtUtcInput) < Date.parse(startedAtUtc)) {\n throw new CaptureConfigError(\"conversation.endedAtUtc: must not precede startedAtUtc\");\n }\n // Spool is exported and callable from JS, so reject an unknown state/chunkStatus\n // at the persistence boundary instead of storing a row the query/finalize/count\n // paths don't recognize (which would silently hide or miscount it).\n if (input.state !== undefined && !Object.hasOwn(CONVERSATION_STATES, input.state)) {\n throw new CaptureConfigError(`conversation.state: unknown value '${input.state}'`);\n }\n if (input.chunkStatus !== undefined && !Object.hasOwn(CHUNK_STATUSES, input.chunkStatus)) {\n throw new CaptureConfigError(`conversation.chunkStatus: unknown value '${input.chunkStatus}'`);\n }\n const segments = input.segments.map((seg, i) => {\n const startUtc = canonicalInstant(seg.startUtc, `conversation.segments[${i}].startUtc`);\n const endUtc = canonicalInstant(seg.endUtc, `conversation.segments[${i}].endUtc`);\n if (Date.parse(endUtc) < Date.parse(startUtc)) {\n throw new CaptureConfigError(`conversation.segments[${i}]: endUtc must not precede startUtc`);\n }\n if (typeof seg.text !== \"string\" || seg.text === \"\") {\n throw new CaptureConfigError(`conversation.segments[${i}].text: expected a non-empty string`);\n }\n return { ...seg, startUtc, endUtc };\n });\n const convId = input.id ?? `conv_${ulid()}`;\n const chunkId = `chk_${convId}`;\n const state: ConversationState = input.state ?? \"final\";\n const chunkStatus: ChunkStatus = input.chunkStatus ?? \"transcribed\";\n const wavPath = input.wavPath ?? null;\n const endedAtUtc = endedAtUtcInput ?? segments[segments.length - 1]?.endUtc ?? startedAtUtc;\n const chunkChannel = segments[0]?.channel ?? \"mic\";\n\n const db = this.#db;\n db.exec(\"BEGIN\");\n try {\n db.prepare(\"DELETE FROM conversations WHERE id = ?\").run(convId);\n db.prepare(\"DELETE FROM chunks WHERE id = ?\").run(chunkId);\n db.prepare(\n \"INSERT INTO chunks(id, channel, device, started_at_utc, ended_at_utc, status, wav_path) VALUES (?,?,?,?,?,?,?)\",\n ).run(chunkId, chunkChannel, input.device ?? null, startedAtUtc, endedAtUtc, chunkStatus, wavPath);\n db.prepare(\n \"INSERT INTO conversations(id, started_at_utc, ended_at_utc, state, segment_count) VALUES (?,?,?,?,?)\",\n ).run(convId, startedAtUtc, endedAtUtc, state, segments.length);\n const segStmt = db.prepare(\n \"INSERT INTO segments(id, chunk_id, conversation_id, speaker_cluster, is_wearer, channel, text, start_utc, end_utc, ordinal, embedding) VALUES (?,?,?,?,?,?,?,?,?,?,?)\",\n );\n for (let i = 0; i < segments.length; i++) {\n const seg = segments[i];\n segStmt.run(\n `seg_${ulid()}`,\n chunkId,\n convId,\n seg.speakerCluster ?? null,\n seg.isWearer ? 1 : 0,\n seg.channel,\n seg.text,\n seg.startUtc,\n seg.endUtc,\n i,\n encodeEmbedding(seg.embedding),\n );\n }\n db.exec(\"COMMIT\");\n } catch (err) {\n db.exec(\"ROLLBACK\");\n throw err;\n }\n return convId;\n }\n\n /** Flip every still-open conversation to `final` (daemon stop / gap timeout). */\n finalizeOpenConversations(): number {\n const result = this.#db.prepare(\"UPDATE conversations SET state = 'final' WHERE state = 'capturing'\").run();\n return Number(result.changes);\n }\n\n /**\n * Durably append one transcribed chunk's segments to a conversation,\n * idempotent on `idempotencyKey` (a replay/restart of the same chunk is a\n * no-op). Creates the conversation as `capturing` on first append; a later\n * `finalizeConversation`/`finalizeOpenConversations` flips it to `final`.\n */\n appendAssembledSegments(input: AssemblyAppendInput): AssemblyAppendResult {\n if (typeof input.idempotencyKey !== \"string\" || input.idempotencyKey.trim() === \"\") {\n throw new CaptureConfigError(\"appendAssembledSegments.idempotencyKey: expected a non-empty string\");\n }\n if (typeof input.conversationId !== \"string\" || input.conversationId.trim() === \"\") {\n throw new CaptureConfigError(\"appendAssembledSegments.conversationId: expected a non-empty string\");\n }\n if (typeof input.startedAtUtc !== \"string\" || input.startedAtUtc.trim() === \"\") {\n throw new CaptureConfigError(\"appendAssembledSegments.startedAtUtc: expected a non-empty ISO timestamp\");\n }\n if (!Array.isArray(input.segments) || input.segments.length === 0) {\n throw new CaptureConfigError(\"appendAssembledSegments.segments: expected a non-empty array\");\n }\n if (input.state !== undefined && !Object.hasOwn(CONVERSATION_STATES, input.state)) {\n throw new CaptureConfigError(`appendAssembledSegments.state: unknown value '${input.state}'`);\n }\n const startedAtUtc = canonicalInstant(input.startedAtUtc, \"appendAssembledSegments.startedAtUtc\");\n const segments = input.segments.map((seg, i) => {\n const startUtc = canonicalInstant(seg.startUtc, `appendAssembledSegments.segments[${i}].startUtc`);\n const endUtc = canonicalInstant(seg.endUtc, `appendAssembledSegments.segments[${i}].endUtc`);\n if (Date.parse(endUtc) < Date.parse(startUtc)) {\n throw new CaptureConfigError(`appendAssembledSegments.segments[${i}]: endUtc must not precede startUtc`);\n }\n if (typeof seg.text !== \"string\" || seg.text === \"\") {\n throw new CaptureConfigError(`appendAssembledSegments.segments[${i}].text: expected a non-empty string`);\n }\n return { ...seg, startUtc, endUtc };\n });\n const convId = input.conversationId;\n const chunkId = input.chunkId ?? input.idempotencyKey;\n const state: ConversationState = input.state ?? \"capturing\";\n const chunkChannel = segments[0]?.channel ?? \"mic\";\n const lastEnd = segments[segments.length - 1].endUtc;\n const chunkStart = segments[0].startUtc;\n\n const db = this.#db;\n db.exec(\"BEGIN\");\n try {\n const seen = db\n .prepare(\"SELECT conversation_id AS conversationId FROM applied_chunks WHERE idempotency_key = ?\")\n .get(input.idempotencyKey) as { conversationId: string } | undefined;\n if (seen) {\n db.exec(\"COMMIT\");\n return { applied: false, conversationId: seen.conversationId, segmentCount: this.#segmentCount(seen.conversationId) };\n }\n db.prepare(\n \"INSERT OR IGNORE INTO chunks(id, channel, device, started_at_utc, ended_at_utc, status, wav_path) VALUES (?,?,?,?,?,?,?)\",\n ).run(chunkId, chunkChannel, input.device ?? null, chunkStart, lastEnd, \"transcribed\", input.wavPath ?? null);\n const existing = db.prepare(\"SELECT id FROM conversations WHERE id = ?\").get(convId) as { id: string } | undefined;\n if (!existing) {\n db.prepare(\n \"INSERT INTO conversations(id, started_at_utc, ended_at_utc, state, segment_count) VALUES (?,?,?,?,0)\",\n ).run(convId, startedAtUtc, lastEnd, state);\n }\n const ordinalRow = db\n .prepare(\"SELECT COALESCE(MAX(ordinal), -1) + 1 AS n FROM segments WHERE conversation_id = ?\")\n .get(convId) as { n: number };\n const nextOrdinal = Number(ordinalRow.n);\n const segStmt = db.prepare(\n \"INSERT INTO segments(id, chunk_id, conversation_id, speaker_cluster, is_wearer, channel, text, start_utc, end_utc, ordinal, embedding) VALUES (?,?,?,?,?,?,?,?,?,?,?)\",\n );\n for (let i = 0; i < segments.length; i++) {\n const seg = segments[i];\n segStmt.run(\n `seg_${ulid()}`,\n chunkId,\n convId,\n seg.speakerCluster ?? null,\n seg.isWearer ? 1 : 0,\n seg.channel,\n seg.text,\n seg.startUtc,\n seg.endUtc,\n nextOrdinal + i,\n encodeEmbedding(seg.embedding),\n );\n }\n db.prepare(\n \"UPDATE conversations SET segment_count = segment_count + ?, \" +\n \"ended_at_utc = CASE WHEN ended_at_utc IS NULL OR ? > ended_at_utc THEN ? ELSE ended_at_utc END WHERE id = ?\",\n ).run(segments.length, lastEnd, lastEnd, convId);\n db.prepare(\"INSERT INTO applied_chunks(idempotency_key, conversation_id, applied_at_utc) VALUES (?,?,?)\").run(\n input.idempotencyKey,\n convId,\n new Date().toISOString(),\n );\n db.exec(\"COMMIT\");\n } catch (err) {\n db.exec(\"ROLLBACK\");\n throw err;\n }\n return { applied: true, conversationId: convId, segmentCount: this.#segmentCount(convId) };\n }\n\n /** Flip one conversation to `final`; returns true when it was capturing. */\n finalizeConversation(id: string): boolean {\n const result = this.#db\n .prepare(\"UPDATE conversations SET state = 'final' WHERE id = ? AND state = 'capturing'\")\n .run(id);\n return Number(result.changes) > 0;\n }\n\n /**\n * A conversation's segments in the shape cross-channel dedup needs (segment\n * id + DedupSegment fields), chronological. Used to prune loopback duplicates\n * at finalization, which is order-independent (all segments are present).\n */\n conversationSegmentsForDedup(\n conversationId: string,\n ): Array<{ id: string; channel: string; text: string; startUtc: string; endUtc: string }> {\n return this.#db\n .prepare(\n \"SELECT id, channel, text, start_utc AS startUtc, end_utc AS endUtc FROM segments \" +\n \"WHERE conversation_id = ? ORDER BY start_utc ASC, ordinal ASC, id ASC\",\n )\n .all(conversationId) as Array<{ id: string; channel: string; text: string; startUtc: string; endUtc: string }>;\n }\n\n /**\n * Delete specific segments (dedup prune), keeping each owning conversation's\n * segment_count in sync. Returns the number actually removed.\n */\n deleteSegments(ids: readonly string[]): number {\n if (ids.length === 0) return 0;\n const db = this.#db;\n let removed = 0;\n db.exec(\"BEGIN\");\n try {\n const findConv = db.prepare(\"SELECT conversation_id AS conversationId FROM segments WHERE id = ?\");\n const del = db.prepare(\"DELETE FROM segments WHERE id = ?\");\n const dec = db.prepare(\"UPDATE conversations SET segment_count = MAX(segment_count - 1, 0) WHERE id = ?\");\n const affected = new Set<string>();\n for (const id of ids) {\n const row = findConv.get(id) as { conversationId: string } | undefined;\n if (!row) continue;\n if (Number(del.run(id).changes) > 0) {\n dec.run(row.conversationId);\n affected.add(row.conversationId);\n removed++;\n }\n }\n // Recompute time bounds from the surviving segments: pruning the earliest\n // or latest segment must not leave the conversation under a deleted row's\n // start/end (which would mis-bucket/mis-order it in queryFinalConversations).\n const bounds = db.prepare(\n \"SELECT MIN(start_utc) AS minStart, MAX(end_utc) AS maxEnd FROM segments WHERE conversation_id = ?\",\n );\n const setBounds = db.prepare(\"UPDATE conversations SET started_at_utc = ?, ended_at_utc = ? WHERE id = ?\");\n for (const convId of affected) {\n const b = bounds.get(convId) as { minStart: string | null; maxEnd: string | null };\n if (b.minStart !== null && b.maxEnd !== null) setBounds.run(b.minStart, b.maxEnd, convId);\n }\n db.exec(\"COMMIT\");\n } catch (err) {\n db.exec(\"ROLLBACK\");\n throw err;\n }\n return removed;\n }\n\n /**\n * Segments of one conversation that still need a speaker, chronological.\n *\n * Only rows with a stored embedding and no cluster yet: clustering runs at\n * finalize over the segments that SURVIVED dedup (issue #2145), and skipping\n * already-assigned rows keeps a repeated finalize from double-counting a\n * centroid.\n */\n conversationSegmentsForDiarization(\n conversationId: string,\n ): Array<{ id: string; channel: string; embedding: number[] }> {\n const rows = this.#db\n .prepare(\n \"SELECT id, channel, embedding FROM segments \" +\n \"WHERE conversation_id = ? AND embedding IS NOT NULL AND speaker_cluster IS NULL \" +\n \"ORDER BY start_utc ASC, ordinal ASC, id ASC\",\n )\n .all(conversationId) as Array<{ id: string; channel: string; embedding: Buffer | null }>;\n const out: Array<{ id: string; channel: string; embedding: number[] }> = [];\n for (const row of rows) {\n const embedding = decodeEmbedding(row.embedding);\n if (embedding !== null) out.push({ id: row.id, channel: row.channel, embedding });\n }\n return out;\n }\n\n /**\n * Commit one conversation's diarization: cluster snapshots and the segment\n * assignments that produced them, in ONE transaction.\n *\n * Splitting the two lets a crash persist an updated `embedding_count` while\n * its segments stay unassigned; the next finalize would select the same rows\n * and count the same embeddings again (issue #2145). Atomicity is what makes\n * the repeated-finalize idempotency claim true.\n */\n commitDiarization(input: {\n clusters: readonly SpeakerInput[];\n assignments: ReadonlyArray<{ id: string; speakerCluster: string; isWearer: boolean }>;\n }): number {\n if (input.assignments.length === 0 && input.clusters.length === 0) return 0;\n const stmt = this.#db.prepare(\"UPDATE segments SET speaker_cluster = ?, is_wearer = ? WHERE id = ?\");\n let updated = 0;\n this.#db.exec(\"BEGIN\");\n try {\n // Clusters first inside the transaction, so the segments' foreign\n // reference is already present when they are written.\n for (const cluster of input.clusters) this.#upsertSpeakerUnlocked(cluster);\n for (const assignment of input.assignments) {\n updated += Number(stmt.run(assignment.speakerCluster, assignment.isWearer ? 1 : 0, assignment.id).changes);\n }\n this.#db.exec(\"COMMIT\");\n } catch (err) {\n this.#db.exec(\"ROLLBACK\");\n throw err;\n }\n return updated;\n }\n\n /** Ids of every still-`capturing` conversation (dedup-before-finalize sweep). */\n capturingConversationIds(): string[] {\n const rows = this.#db\n .prepare(\"SELECT id FROM conversations WHERE state = 'capturing' ORDER BY id ASC\")\n .all() as Array<{ id: string }>;\n return rows.map((r) => r.id);\n }\n\n /**\n * Record a bare idempotency marker (no segments).\n *\n * Used to persist facts a later replay cannot re-derive — such as how many\n * segments a chunk's transcript produced, which is the only way to tell a\n * legitimately shorter retranscription from a missing tail (issue #2145).\n */\n markApplied(idempotencyKey: string, conversationId: string): void {\n this.#db\n .prepare(\"INSERT OR IGNORE INTO applied_chunks(idempotency_key, conversation_id, applied_at_utc) VALUES (?,?,?)\")\n .run(idempotencyKey, conversationId, new Date().toISOString());\n }\n\n /**\n * Whether ANY idempotency key for this chunk was applied.\n *\n * Only a SILENT replay needs this — a chunk partially applied by a binary\n * predating the transcript manifest has no manifest to compare, and a\n * zero-segment replay has no per-segment key to look up exactly. Speech\n * chunks use the indexed manifest lookup below, so continuous capture never\n * pays for this scan (issue #2145).\n */\n hasAppliedChunkPrefix(chunkIdPrefix: string): boolean {\n const escaped = chunkIdPrefix.replace(/[\\\\%_]/g, \"\\\\$&\");\n const row = this.#db\n .prepare(\"SELECT 1 AS present FROM applied_chunks WHERE idempotency_key LIKE ? ESCAPE '\\\\' LIMIT 1\")\n .get(`${escaped}%`) as { present?: number } | undefined;\n return row?.present === 1;\n }\n\n /**\n * Conversations a chunk actually contributed stored segments to.\n *\n * Used to scope a rebuilt replay hold to the prefix that chunk belongs to,\n * rather than to every conversation that happens to be capturing (#2145).\n * Matches the bare chunk id and every per-segment or per-group derivative\n * (`<chunkId>:h<hash>`, and the pre-manifest `<chunkId>:<n>`).\n */\n conversationIdsForChunk(chunkId: string): string[] {\n const escaped = chunkId.replace(/[\\\\%_]/g, \"\\\\$&\");\n const rows = this.#db\n .prepare(\n `SELECT DISTINCT conversation_id AS conversationId\n FROM segments\n WHERE conversation_id IS NOT NULL\n AND (chunk_id = ? OR chunk_id LIKE ? ESCAPE '\\\\')\n ORDER BY conversation_id`,\n )\n .all(chunkId, `${escaped}:%`) as { conversationId: string }[];\n return rows.map((row) => row.conversationId);\n }\n\n /**\n * Chunks whose transcript manifest is recorded but which never completed.\n *\n * A restart loses the in-memory record of which chunks are still awaiting a\n * replay, so it is re-derived from these two durable markers: the manifest is\n * written before any append, `:done` only after every segment is stored\n * (issue #2145).\n */\n incompleteChunkIds(): string[] {\n const rows = this.#db\n .prepare(\n `SELECT substr(idempotency_key, 1, length(idempotency_key) - 9) AS chunkId\n FROM applied_chunks\n WHERE idempotency_key LIKE '%:manifest'\n AND substr(idempotency_key, 1, length(idempotency_key) - 9) || ':done' NOT IN (\n SELECT idempotency_key FROM applied_chunks\n )`,\n )\n .all() as { chunkId: string }[];\n return rows.map((row) => row.chunkId);\n }\n\n /**\n * The value stored alongside an idempotency marker, or `undefined`.\n *\n * `markApplied` uses this column to carry a fact a replay cannot re-derive —\n * the chunk's transcript manifest hash — and this is the exact, primary-key\n * lookup that reads it back (issue #2145).\n */\n appliedChunkValue(idempotencyKey: string): string | undefined {\n const row = this.#db\n .prepare(\"SELECT conversation_id AS conversationId FROM applied_chunks WHERE idempotency_key = ?\")\n .get(idempotencyKey) as { conversationId?: string } | undefined;\n return row?.conversationId;\n }\n\n /** Whether a chunk with this idempotency key was already durably applied. */\n isChunkApplied(idempotencyKey: string): boolean {\n return (\n this.#db.prepare(\"SELECT 1 FROM applied_chunks WHERE idempotency_key = ? LIMIT 1\").get(idempotencyKey) !==\n undefined\n );\n }\n\n /**\n * Record that a whole chunk finished (every group appended) via a `<id>:done`\n * marker, so a later full replay can skip transcription + diarization. A crash\n * before this leaves no marker, so the missing groups re-append on replay.\n */\n markChunkComplete(chunkId: string, conversationId: string): void {\n this.#db\n .prepare(\"INSERT OR IGNORE INTO applied_chunks(idempotency_key, conversation_id, applied_at_utc) VALUES (?,?,?)\")\n .run(`${chunkId}:done`, conversationId, new Date().toISOString());\n }\n\n /**\n * The newest still-`capturing` conversation, so a chunk arriving after a\n * process restart continues it (subject to the assembler's gap rule) instead\n * of splitting off a new one. Null when none is open.\n */\n latestCapturingConversation(): { id: string; startedAtUtc: string; endedAtUtc: string } | null {\n const row = this.#db\n .prepare(\n \"SELECT id, started_at_utc AS startedAtUtc, ended_at_utc AS endedAtUtc FROM conversations \" +\n \"WHERE state = 'capturing' ORDER BY ended_at_utc DESC, id DESC LIMIT 1\",\n )\n .get() as { id: string; startedAtUtc: string; endedAtUtc: string | null } | undefined;\n if (!row) return null;\n return { id: row.id, startedAtUtc: row.startedAtUtc, endedAtUtc: row.endedAtUtc ?? row.startedAtUtc };\n }\n\n /** One capturing conversation by id, for resuming a specific prefix. */\n capturingConversationById(\n id: string,\n ): { id: string; startedAtUtc: string; endedAtUtc: string } | null {\n const row = this.#db\n .prepare(\n \"SELECT id, started_at_utc AS startedAtUtc, ended_at_utc AS endedAtUtc FROM conversations \" +\n \"WHERE id = ? AND state = 'capturing'\",\n )\n .get(id) as { id: string; startedAtUtc: string; endedAtUtc: string | null } | undefined;\n if (!row) return null;\n return { id: row.id, startedAtUtc: row.startedAtUtc, endedAtUtc: row.endedAtUtc ?? row.startedAtUtc };\n }\n\n #segmentCount(conversationId: string): number {\n const row = this.#db\n .prepare(\"SELECT segment_count AS n FROM conversations WHERE id = ?\")\n .get(conversationId) as { n: number } | undefined;\n return row ? Number(row.n) : 0;\n }\n\n upsertSpeaker(input: SpeakerInput): void {\n this.#upsertSpeakerUnlocked(input);\n }\n\n /** `upsertSpeaker` without its own transaction, for use inside one. */\n #upsertSpeakerUnlocked(input: SpeakerInput): void {\n const current = this.#db\n .prepare(\n \"SELECT label, embedding_count AS embeddingCount, is_self AS isSelf, centroid, example_embeddings AS examples FROM speaker_clusters WHERE id = ?\",\n )\n .get(input.id) as\n | { label: string | null; embeddingCount: number; isSelf: number; centroid: Buffer | null; examples: Buffer | null }\n | undefined;\n const label = Object.hasOwn(input, \"label\") ? (input.label ?? null) : (current?.label ?? null);\n const embeddingCount = Object.hasOwn(input, \"embeddingCount\")\n ? (input.embeddingCount ?? 0)\n : (current?.embeddingCount ?? 0);\n const isSelf = Object.hasOwn(input, \"isSelf\") ? (input.isSelf ? 1 : 0) : (current?.isSelf ?? 0);\n const centroid = Object.hasOwn(input, \"centroid\")\n ? input.centroid\n ? Buffer.from(JSON.stringify(input.centroid))\n : null\n : (current?.centroid ?? null);\n const examples = Object.hasOwn(input, \"examples\")\n ? input.examples\n ? Buffer.from(JSON.stringify(input.examples))\n : null\n : (current?.examples ?? null);\n this.#db\n .prepare(\n \"INSERT INTO speaker_clusters(id, label, embedding_count, is_self, centroid, example_embeddings) VALUES (?,?,?,?,?,?) \" +\n \"ON CONFLICT(id) DO UPDATE SET label = excluded.label, is_self = excluded.is_self, embedding_count = excluded.embedding_count, \" +\n \"centroid = excluded.centroid, example_embeddings = excluded.example_embeddings\",\n )\n .run(input.id, label, embeddingCount, isSelf, centroid, examples);\n }\n\n /** Read every speaker cluster with decoded centroid + examples (diarization restart seed). */\n readSpeakerClusters(): SpeakerClusterRow[] {\n const rows = this.#db\n .prepare(\n \"SELECT id, label, is_self AS isSelf, embedding_count AS embeddingCount, centroid, example_embeddings AS examples FROM speaker_clusters ORDER BY id ASC\",\n )\n .all() as Array<{\n id: string;\n label: string | null;\n isSelf: number;\n embeddingCount: number;\n centroid: Buffer | null;\n examples: Buffer | null;\n }>;\n const decode = (blob: Buffer | Uint8Array | null): unknown => {\n if (!blob || blob.byteLength === 0) return null;\n try {\n return JSON.parse(Buffer.from(blob).toString(\"utf8\"));\n } catch {\n return null;\n }\n };\n return rows.map((r) => {\n const centroid = decode(r.centroid);\n const examples = decode(r.examples);\n return {\n id: r.id,\n label: r.label,\n isSelf: r.isSelf === 1,\n embeddingCount: r.embeddingCount,\n centroid: Array.isArray(centroid) ? (centroid as number[]) : [],\n examples: Array.isArray(examples) ? (examples as number[][]) : [],\n };\n });\n }\n\n listSpeakers(): SpeakerRow[] {\n const rows = this.#db\n .prepare(\"SELECT id, label, is_self AS isSelf, embedding_count AS embeddingCount FROM speaker_clusters ORDER BY id ASC\")\n .all() as Array<{ id: string; label: string | null; isSelf: number; embeddingCount: number }>;\n return rows.map((r) => ({ id: r.id, label: r.label, isSelf: r.isSelf === 1, embeddingCount: r.embeddingCount }));\n }\n\n pendingChunkCount(): number {\n const row = this.#db.prepare(\"SELECT COUNT(*) AS n FROM chunks WHERE status = 'pending'\").get() as { n: number };\n return row.n;\n }\n\n recordPendingChunk(input: PendingChunkInput): void {\n this.#db\n .prepare(\n \"INSERT INTO pending_chunks(id, wav_path, started_at_utc, ended_at_utc, channel, device, reason, created_at_utc) \" +\n \"VALUES (?,?,?,?,?,?,?,?) \" +\n \"ON CONFLICT(id) DO UPDATE SET wav_path = excluded.wav_path, started_at_utc = excluded.started_at_utc, \" +\n \"ended_at_utc = excluded.ended_at_utc, channel = excluded.channel, device = excluded.device, \" +\n \"reason = excluded.reason, created_at_utc = excluded.created_at_utc\",\n )\n .run(\n input.id,\n input.wavPath,\n canonicalInstant(input.startedAtUtc, \"pendingChunk.startedAtUtc\"),\n canonicalInstant(input.endedAtUtc, \"pendingChunk.endedAtUtc\"),\n input.channel,\n input.device,\n input.reason,\n new Date().toISOString(),\n );\n if (input.reason !== \"quarantined\") return;\n const extra = this.#db\n .prepare(\n \"SELECT id FROM pending_chunks WHERE reason = 'quarantined' ORDER BY created_at_utc ASC, id ASC\",\n )\n .all() as Array<{ id: string }>;\n const overflow = extra.length - MAX_QUARANTINED_CHUNKS;\n if (overflow <= 0) return;\n const drop = this.#db.prepare(\"DELETE FROM pending_chunks WHERE id = ?\");\n for (let i = 0; i < overflow; i++) {\n const id = extra[i]?.id;\n if (id !== undefined) drop.run(id);\n }\n }\n\n listPendingChunks(reason?: PendingChunkReason): PendingChunkRecord[] {\n const rows = (\n reason === undefined\n ? this.#db.prepare(\n \"SELECT id, wav_path AS wavPath, started_at_utc AS startedAtUtc, ended_at_utc AS endedAtUtc, \" +\n \"channel, device, reason, created_at_utc AS createdAtUtc FROM pending_chunks \" +\n \"ORDER BY created_at_utc ASC, id ASC\",\n ).all()\n : this.#db\n .prepare(\n \"SELECT id, wav_path AS wavPath, started_at_utc AS startedAtUtc, ended_at_utc AS endedAtUtc, \" +\n \"channel, device, reason, created_at_utc AS createdAtUtc FROM pending_chunks \" +\n \"WHERE reason = ? ORDER BY created_at_utc ASC, id ASC\",\n )\n .all(reason)\n ) as Array<{\n id: string;\n wavPath: string;\n startedAtUtc: string;\n endedAtUtc: string;\n channel: \"mic\" | \"system\";\n device: string | null;\n reason: PendingChunkReason;\n createdAtUtc: string;\n }>;\n return rows;\n }\n\n deletePendingChunk(id: string): void {\n this.#db.prepare(\"DELETE FROM pending_chunks WHERE id = ?\").run(id);\n }\n\n\n stats(): { conversations: number; segments: number; chunks: number } {\n const count = (table: string): number =>\n (this.#db.prepare(`SELECT COUNT(*) AS n FROM ${table}`).get() as { n: number }).n;\n return { conversations: count(\"conversations\"), segments: count(\"segments\"), chunks: count(\"chunks\") };\n }\n\n getConversation(id: string): DaemonConversation | null {\n const row = this.#db\n .prepare(\n \"SELECT id, started_at_utc AS startedAtUtc, ended_at_utc AS endedAtUtc, state, segment_count AS segmentCount FROM conversations WHERE id = ?\",\n )\n .get(id) as unknown as ConversationRow | undefined;\n return row ? this.#hydrate(row) : null;\n }\n\n /**\n * Final conversations whose local day (per `timezone`) equals `date`,\n * paged by the stable (started_at_utc, id) keyset. Fetches all final\n * rows after the cursor (the spool is a bounded buffer, not an archive),\n * filters to the requested local day, then pages — so the id tiebreak\n * keeps pagination correct across duplicate start timestamps.\n */\n queryFinalConversations(opts: QueryFinalOptions): ConversationPage {\n const cursor = decodeCursor(opts.cursor ?? null);\n const afterStarted = cursor ? cursor.startedAtUtc : \"\";\n const afterId = cursor ? cursor.id : \"\";\n const rows = this.#db\n .prepare(\n \"SELECT id, started_at_utc AS startedAtUtc, ended_at_utc AS endedAtUtc, state, segment_count AS segmentCount \" +\n \"FROM conversations WHERE state = 'final' AND (started_at_utc > ? OR (started_at_utc = ? AND id > ?)) \" +\n \"ORDER BY started_at_utc ASC, id ASC\",\n )\n .all(afterStarted, afterStarted, afterId) as unknown as ConversationRow[];\n\n const matches: ConversationRow[] = [];\n for (const row of rows) {\n if (dateInTimezone(new Date(row.startedAtUtc), opts.timezone) === opts.date) {\n matches.push(row);\n if (matches.length > opts.limit) break;\n }\n }\n const hasMore = matches.length > opts.limit;\n const page = hasMore ? matches.slice(0, opts.limit) : matches;\n const last = page[page.length - 1];\n return {\n conversations: page.map((row) => this.#hydrate(row)),\n nextCursor: hasMore && last ? encodeCursor(last.startedAtUtc, last.id) : null,\n };\n }\n\n #hydrate(row: ConversationRow): DaemonConversation {\n const segs = this.#db\n .prepare(\n \"SELECT text, speaker_cluster AS speakerKey, is_wearer AS isWearer, channel, start_utc AS startUtc, end_utc AS endUtc \" +\n // Order by timestamp first: with channel \"both\", mic and system chunks\n // arrive independently, so ordinal (arrival order) isn't chronological.\n \"FROM segments WHERE conversation_id = ? ORDER BY start_utc ASC, ordinal ASC, id ASC\",\n )\n .all(row.id) as Array<{\n text: string;\n speakerKey: string | null;\n isWearer: number;\n channel: string;\n startUtc: string;\n endUtc: string;\n }>;\n return {\n id: row.id,\n startedAtUtc: row.startedAtUtc,\n endedAtUtc: row.endedAtUtc,\n state: row.state,\n segmentCount: row.segmentCount,\n segments: segs.map((s) => ({\n textRaw: s.text,\n speakerKey: s.speakerKey,\n isWearer: s.isWearer === 1,\n channel: s.channel,\n startUtc: s.startUtc,\n endUtc: s.endUtc,\n })),\n };\n }\n}\n","/**\n * Conversation assembly (issue #1897, component 2.5).\n *\n * A conversation is a maximal run of consecutive speech segments whose\n * inter-segment gap stays below `conversationGapMinutes`. A gap greater\n * than OR EQUAL to the threshold starts a new conversation (the join rule\n * is strictly `gap < threshold`, per the issue). Pure over ordered\n * segments so the daemon pipeline and unit tests share one implementation;\n * output rows map 1:1 to Spool.insertConversation input.\n */\n\nimport type { ConversationInput, ConversationState, SegmentInput } from \"./spool.js\";\nimport { CaptureConfigError, CaptureInputError } from \"./errors.js\";\nimport { ulid } from \"./util.js\";\n\n/** A segment as it enters assembly (post dedup + diarization). */\nexport type AssemblySegment = SegmentInput;\n\n/**\n * Group ordered segments into conversations. Segments MUST already be in\n * chronological order (the pipeline emits them that way). Each returned\n * row omits `id` so Spool.insertConversation mints a `conv_<ulid>`.\n *\n * `gapMinutes` is the max silence that keeps two segments in the same\n * conversation; `state` is applied to every produced conversation\n * (default \"final\" — the API only serves final; callers pass \"capturing\"\n * for the still-open tail).\n */\nexport function assembleConversations(\n segments: readonly AssemblySegment[],\n gapMinutes: number,\n state: ConversationState = \"final\",\n): ConversationInput[] {\n if (!Number.isFinite(gapMinutes) || gapMinutes < 0) {\n throw new Error(\"assembleConversations: gapMinutes must be a non-negative number\");\n }\n const gapMs = gapMinutes * 60_000;\n const conversations: ConversationInput[] = [];\n let current: AssemblySegment[] = [];\n let prevEndMs = Number.NaN;\n\n const flush = (): void => {\n if (current.length === 0) return;\n const startedAtUtc = current[0].startUtc;\n const endedAtUtc = current[current.length - 1].endUtc;\n conversations.push({ startedAtUtc, endedAtUtc, state, segments: current });\n current = [];\n };\n\n for (const seg of segments) {\n const startMs = Date.parse(seg.startUtc);\n if (current.length > 0 && Number.isFinite(prevEndMs) && Number.isFinite(startMs) && startMs - prevEndMs >= gapMs) {\n flush();\n }\n current.push(seg);\n const endMs = Date.parse(seg.endUtc);\n prevEndMs = Number.isFinite(endMs) ? endMs : startMs;\n }\n flush();\n return conversations;\n}\n\n/** Default per issue #1897 config surface. */\nexport const DEFAULT_CONVERSATION_GAP_MINUTES = 10;\n\n/** A conversation the stateful assembler is building incrementally. */\nexport interface AssembledConversation {\n id: string;\n startedAtUtc: string;\n endedAtUtc: string;\n state: ConversationState;\n segments: AssemblySegment[];\n}\n\nexport interface AssemblerOptions {\n gapMinutes?: number;\n /** Injectable for deterministic ids in tests; defaults to `conv_<ulid>`. */\n makeId?: () => string;\n}\n\n/**\n * Copy a segment for a rollback snapshot. `slice()` alone would keep the same\n * mutable segment objects — and the same `embedding` array — so a later\n * mutation would rewrite the snapshot it is supposed to restore from.\n */\nfunction cloneSegment(segment: AssemblySegment): AssemblySegment {\n return {\n ...segment,\n ...(segment.embedding ? { embedding: segment.embedding.slice() } : {}),\n };\n}\n\n/** Parse an ISO-8601 timestamp to epoch ms, rejecting garbage loudly. */\nfunction epochMs(value: string, field: string): number {\n const ms = Date.parse(value);\n if (!Number.isFinite(ms)) {\n throw new CaptureInputError(`segment.${field}: expected an ISO-8601 timestamp`);\n }\n return ms;\n}\n\n/**\n * Incremental sibling of `assembleConversations` for the live daemon: feed\n * segments one chunk at a time and it groups them into conversations under the\n * same `gap < threshold` rule, tracking a single open (`capturing`)\n * conversation. The batch function stays the source of truth for replay; this\n * class owns the streaming case. Pure in-memory — the processor decides when to\n * persist and provides restart continuity via `resume`.\n */\nexport class ConversationAssembler {\n readonly #gapMs: number;\n readonly #makeId: () => string;\n readonly #conversations: AssembledConversation[] = [];\n\n constructor(options: AssemblerOptions = {}) {\n const gapMinutes = options.gapMinutes ?? DEFAULT_CONVERSATION_GAP_MINUTES;\n if (!Number.isFinite(gapMinutes) || gapMinutes < 0) {\n throw new CaptureConfigError(\"conversationGapMinutes must be a non-negative number\");\n }\n this.#gapMs = gapMinutes * 60_000;\n this.#makeId = options.makeId ?? (() => `conv_${ulid()}`);\n }\n\n /**\n * Append one segment, returning the conversation it landed in. Segments\n * arrive in non-decreasing start order; a gap of at least the threshold\n * closes the open conversation and starts a new one.\n */\n add(segment: AssemblySegment): AssembledConversation {\n const startMs = epochMs(segment.startUtc, \"startUtc\");\n epochMs(segment.endUtc, \"endUtc\");\n const open = this.#open();\n if (open) {\n const lastEnd = epochMs(open.endedAtUtc, \"endedAtUtc\");\n if (startMs - lastEnd >= this.#gapMs) {\n open.state = \"final\";\n } else {\n open.segments.push(segment);\n if (segment.endUtc > open.endedAtUtc) open.endedAtUtc = segment.endUtc;\n return open;\n }\n }\n return this.#start(segment);\n }\n\n /** Flip every open (`capturing`) conversation to `final`; returns the count changed. */\n finalize(): number {\n let changed = 0;\n for (const conv of this.#conversations) {\n if (conv.state === \"capturing\") {\n conv.state = \"final\";\n changed++;\n }\n }\n return changed;\n }\n\n /**\n * Re-open a conversation recovered from durable storage so a chunk arriving\n * after a process restart continues it (subject to the same gap rule via\n * `add`) instead of splitting off a new one. No-op when a conversation is\n * already open in this run.\n */\n resume(conversation: { id: string; startedAtUtc: string; endedAtUtc: string }): void {\n if (this.#open()) return;\n epochMs(conversation.endedAtUtc, \"endedAtUtc\");\n this.#conversations.push({\n id: conversation.id,\n startedAtUtc: conversation.startedAtUtc,\n endedAtUtc: conversation.endedAtUtc,\n state: \"capturing\",\n segments: [],\n });\n }\n\n /**\n * Drop finalized conversations the caller no longer needs.\n *\n * A long-running daemon would otherwise retain every conversation and every\n * segment forever, which makes the rollback snapshot below O(capture\n * history) and the daemon's per-chunk work quadratic (issue #2145). Only the\n * open conversation can still be mutated, so nothing else needs keeping.\n */\n pruneFinalized(): number {\n const open = this.#open();\n const removed = this.#conversations.length - (open ? 1 : 0);\n this.#conversations.length = 0;\n if (open) this.#conversations.push(open);\n return removed;\n }\n\n /**\n * Deep snapshot for rollback (issue #2145).\n *\n * `add` mutates the open conversation in place. A caller that fails BEFORE\n * anything was persisted must be able to rewind, or the retry feeds earlier\n * timestamps into an advanced assembler and collapses conversations the\n * first attempt had split. A caller that already persisted something must\n * NOT rewind: the durable ids would then diverge from the in-memory ones.\n */\n checkpoint(): AssembledConversation[] {\n return this.#conversations.map((conv) => ({ ...conv, segments: conv.segments.map(cloneSegment) }));\n }\n\n /** Rewind to a {@link checkpoint}. */\n rewind(snapshot: readonly AssembledConversation[]): void {\n this.#conversations.length = 0;\n for (const conv of snapshot) {\n this.#conversations.push({ ...conv, segments: conv.segments.map(cloneSegment) });\n }\n }\n\n /** Ordered snapshot; segments are cloned so callers cannot mutate internal state. */\n conversations(): AssembledConversation[] {\n return this.#conversations.map((conv) => ({ ...conv, segments: conv.segments.slice() }));\n }\n\n /**\n * Finalize the open conversation when `nowUtc` is at least the gap past its\n * last segment, so a run of silent chunks (which carry no segments to `add`)\n * still closes a conversation instead of leaving it `capturing` until stop.\n * Returns the closed conversation's id, or null when nothing closed.\n */\n closeIfIdle(nowUtc: string): string | null {\n const open = this.#open();\n if (!open) return null;\n if (epochMs(nowUtc, \"nowUtc\") - epochMs(open.endedAtUtc, \"endedAtUtc\") < this.#gapMs) return null;\n open.state = \"final\";\n return open.id;\n }\n\n #open(): AssembledConversation | undefined {\n const last = this.#conversations[this.#conversations.length - 1];\n return last && last.state === \"capturing\" ? last : undefined;\n }\n\n #start(segment: AssemblySegment): AssembledConversation {\n const conv: AssembledConversation = {\n id: this.#makeId(),\n startedAtUtc: segment.startUtc,\n endedAtUtc: segment.endUtc,\n state: \"capturing\",\n segments: [segment],\n };\n this.#conversations.push(conv);\n return conv;\n }\n}\n","/**\n * Speaker diarization clustering (issue #1897, component 2.3).\n *\n * The daemon computes one speaker embedding per VAD speech segment (via\n * the optional sherpa-onnx speaker-id model, wired with the native\n * capture layer). This module owns the CPU-cheap, hardware-free half:\n * matching an embedding to a stable speaker cluster and maintaining the\n * cluster's running centroid + a bounded diverse example set. It is pure\n * over embedding vectors so the fragmentation regression (one synthetic\n * voice across many segments -> one cluster) runs in CI without models.\n *\n * Match score = best cosine similarity against BOTH the cluster centroid\n * and up to `maxExamples` stored examples (issue: \"take the best score\").\n */\n\nimport { CaptureConfigError } from \"./errors.js\";\n\nexport type Embedding = readonly number[];\n\nexport interface SpeakerCluster {\n id: string;\n centroid: number[];\n examples: number[][];\n embeddingCount: number;\n isSelf: boolean;\n label: string | null;\n}\n\nconst MAX_EXAMPLES = 10;\nconst SELF_ID = \"self\";\n\nexport function cosineSimilarity(a: Embedding, b: Embedding): number {\n if (a.length === 0 || a.length !== b.length) return 0;\n let dot = 0;\n let normA = 0;\n let normB = 0;\n for (let i = 0; i < a.length; i++) {\n dot += a[i] * b[i];\n normA += a[i] * a[i];\n normB += b[i] * b[i];\n }\n if (normA === 0 || normB === 0) return 0;\n return dot / (Math.sqrt(normA) * Math.sqrt(normB));\n}\n\n/**\n * Assigns embeddings to stable speaker clusters. Ids are `spk_<n>` (or\n * `self` for the enrolled wearer). Seed with persisted clusters so ids\n * survive daemon restarts.\n */\nexport class SpeakerClusterer {\n #clusters: SpeakerCluster[] = [];\n #threshold: number;\n #next = 1;\n\n constructor(threshold: number, seed: readonly SpeakerCluster[] = []) {\n if (!Number.isFinite(threshold) || threshold <= 0 || threshold >= 1) {\n throw new CaptureConfigError(\"diarization.similarityThreshold must be between 0 and 1\");\n }\n this.#threshold = threshold;\n for (const c of seed) {\n this.#clusters.push({\n id: c.id,\n centroid: [...c.centroid],\n examples: c.examples.map((e) => [...e]),\n embeddingCount: c.embeddingCount,\n isSelf: c.isSelf,\n label: c.label,\n });\n const n = /^spk_(\\d+)$/.exec(c.id);\n if (n) this.#next = Math.max(this.#next, Number(n[1]) + 1);\n }\n }\n\n /** Register an enrolled self profile (its embedding seeds the `self` cluster). */\n enrollSelf(embedding: Embedding): void {\n const existing = this.#clusters.find((c) => c.id === SELF_ID);\n if (existing) {\n this.#update(existing, embedding);\n existing.isSelf = true;\n return;\n }\n this.#clusters.unshift({\n id: SELF_ID,\n centroid: [...embedding],\n examples: [[...embedding]],\n embeddingCount: 1,\n isSelf: true,\n label: null,\n });\n }\n\n /** Best cosine over a cluster's centroid + examples. */\n #score(cluster: SpeakerCluster, embedding: Embedding): number {\n let best = cosineSimilarity(cluster.centroid, embedding);\n for (const ex of cluster.examples) {\n const s = cosineSimilarity(ex, embedding);\n if (s > best) best = s;\n }\n return best;\n }\n\n #update(cluster: SpeakerCluster, embedding: Embedding): void {\n // Running mean centroid.\n const n = cluster.embeddingCount;\n for (let i = 0; i < cluster.centroid.length && i < embedding.length; i++) {\n cluster.centroid[i] = (cluster.centroid[i] * n + embedding[i]) / (n + 1);\n }\n cluster.embeddingCount = n + 1;\n if (cluster.examples.length < MAX_EXAMPLES) {\n cluster.examples.push([...embedding]);\n } else {\n // Keep the set diverse: replace the example most similar to the\n // incoming one, so the cluster spans more of the speaker's range.\n let mostSimilar = 0;\n let mostSimilarScore = -Infinity;\n for (let i = 0; i < cluster.examples.length; i++) {\n const s = cosineSimilarity(cluster.examples[i], embedding);\n if (s > mostSimilarScore) {\n mostSimilarScore = s;\n mostSimilar = i;\n }\n }\n cluster.examples[mostSimilar] = [...embedding];\n }\n }\n\n /** Match `embedding` to an existing cluster or create a new `spk_<n>`. */\n assign(embedding: Embedding): string {\n let best: SpeakerCluster | null = null;\n let bestScore = -Infinity;\n for (const cluster of this.#clusters) {\n const s = this.#score(cluster, embedding);\n if (s > bestScore) {\n bestScore = s;\n best = cluster;\n }\n }\n if (best && bestScore >= this.#threshold) {\n this.#update(best, embedding);\n return best.id;\n }\n const cluster: SpeakerCluster = {\n id: `spk_${this.#next++}`,\n centroid: [...embedding],\n examples: [[...embedding]],\n embeddingCount: 1,\n isSelf: false,\n label: null,\n };\n this.#clusters.push(cluster);\n return cluster.id;\n }\n\n /**\n * Replace every cluster with `snapshot` (issue #2145).\n *\n * `assign` mutates centroids and counts in place, so a diarization commit\n * that rolls back in SQLite must roll back here too — otherwise the retry\n * counts the same embeddings twice. Deep-copied, so the caller's snapshot\n * cannot alias internal state.\n */\n restore(snapshot: readonly SpeakerCluster[]): void {\n this.#clusters = snapshot.map((cluster) => ({\n ...cluster,\n centroid: cluster.centroid.slice(),\n examples: cluster.examples.map((example) => example.slice()),\n }));\n let highest = 0;\n for (const cluster of this.#clusters) {\n const parsed = /^spk_(\\d+)$/.exec(cluster.id);\n if (parsed) highest = Math.max(highest, Number(parsed[1]));\n }\n this.#next = highest + 1;\n }\n\n /** Snapshot for persistence. */\n clusters(): SpeakerCluster[] {\n return this.#clusters.map((c) => ({\n id: c.id,\n centroid: [...c.centroid],\n examples: c.examples.map((e) => [...e]),\n embeddingCount: c.embeddingCount,\n isSelf: c.isSelf,\n label: c.label,\n }));\n }\n}\n","/**\n * Native capture helper resolver + supervised process runner (issue #1897,\n * \"audio native macOS helper\" slice — Node side only).\n *\n * The native recorder is the ONE shared macOS helper shipped by #2138\n * (`remnic-capture-helper`), driven here through its `audio-capture`\n * subcommand. It emits one JSONL `ChunkEvent` per recorded WAV chunk on\n * stdout. This module is deliberately à-la-carte, mirroring the VAD/STT\n * adapters and the screen daemon's helper seam:\n *\n * - The helper ships as an OPTIONAL, per-platform package\n * (`@remnic/capture-native-darwin-arm64` / `-x64`) that exports a\n * `helperBinaryPath` and declares the same binary under `bin`. It is a\n * peer dependency, never a runtime dependency, so `@remnic/capture-audio`\n * installs and works on any platform without it.\n * - The package specifier is COMPUTED from `process.platform`/`arch` so a\n * static importer never bundles a foreign-arch binary, and resolution uses\n * Node module resolution (`require.resolve`).\n * - `REMNIC_CAPTURE_HELPER_BIN` overrides resolution with an explicit binary\n * path (manual installs and the hardware-free test seam, which points it at\n * a fake script emitting canned JSON).\n * - A missing optional package reports the EXACT install command instead of a\n * raw resolver error.\n *\n * The runner is the sole owner of the child process: it spawns the helper,\n * parses stdout strictly line-by-line, reports validated events to a callback,\n * reports stderr/errors separately, and restarts only UNEXPECTED exits with\n * bounded exponential backoff. It never writes the Spool and never invents a\n * conversation — the processing/assembly layer owns eventual Spool writes\n * downstream of the validated events this runner surfaces.\n */\n\nimport { spawn as nodeSpawn } from \"node:child_process\";\nimport { readFileSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport path from \"node:path\";\n\nimport { CaptureConfigError, CaptureInputError } from \"./errors.js\";\nimport { expandTilde } from \"./paths.js\";\n\n/** One recorded audio chunk, as emitted by the native helper on stdout (JSONL). */\nexport interface ChunkEvent {\n path: string;\n channel: \"mic\" | \"system\";\n startedAtUtc: string;\n endedAtUtc: string;\n device: string | null;\n}\n\n/** Which channels the `audio-capture` subcommand records. */\nexport type ChannelSelection = \"mic\" | \"system\" | \"both\";\n\n/** A resolved native helper: its source specifier and the on-disk binary path. */\nexport interface HelperResolution {\n specifier: string;\n binaryPath: string;\n}\n\n/** The narrow child-process surface the runner depends on (injectable for tests). */\nexport interface HelperChild {\n stdout: { on(event: \"data\", listener: (chunk: Buffer | string) => void): unknown };\n stderr: { on(event: \"data\", listener: (chunk: Buffer | string) => void): unknown };\n once(event: \"error\", listener: (err: Error) => void): unknown;\n // `close` (not `exit`) fires after the stdio pipes are fully drained, so the\n // helper's final buffered chunk is always read before the runner settles.\n once(event: \"close\", listener: (code: number | null, signal: NodeJS.Signals | null) => void): unknown;\n kill(signal?: NodeJS.Signals): boolean;\n readonly killed?: boolean;\n readonly pid?: number;\n}\n\n/** Spawns the helper binary. Defaults to a `node:child_process` adapter. */\nexport type HelperSpawn = (binaryPath: string, args: string[]) => HelperChild;\n\n/** An opaque restart-timer token returned by `scheduleRestart`. */\nexport type RestartTimer = unknown;\n\nexport interface ResolveHelperDeps {\n platform?: NodeJS.Platform;\n arch?: string;\n /** `require.resolve`-style resolver; defaults to this module's require. */\n resolve?: (specifier: string) => string;\n readFile?: (file: string) => string;\n /** Environment source for the `REMNIC_CAPTURE_HELPER_BIN` override. */\n env?: NodeJS.ProcessEnv;\n}\n\nexport interface NativeRunnerOptions {\n /** Directory the helper writes WAV chunks into (`audio-capture --out`). */\n outDir: string;\n chunkSeconds: number;\n /** Channels to record; defaults to \"both\". */\n channel?: ChannelSelection;\n /** Optional CoreAudio microphone device UID (`--device`). */\n device?: string | null;\n /** Called once per validated ChunkEvent. */\n onChunk: (event: ChunkEvent) => void;\n /** Called for a rejected stdout line or a spawn/child error. */\n onError?: (error: Error) => void;\n /** Called once per complete stderr line. */\n onStderr?: (line: string) => void;\n /** Pre-resolved helper; when absent the runner resolves it lazily on `start()`. */\n resolution?: HelperResolution;\n resolveBinary?: (deps: ResolveHelperDeps) => HelperResolution;\n spawn?: HelperSpawn;\n /** Max consecutive unexpected restarts before giving up (default 5). */\n maxRestarts?: number;\n /** First backoff delay in ms (default 500). */\n baseBackoffMs?: number;\n /** Backoff ceiling in ms (default 30000). */\n maxBackoffMs?: number;\n scheduleRestart?: (fn: () => void, delayMs: number) => RestartTimer;\n cancelRestart?: (timer: RestartTimer) => void;\n}\n\n/** A running native-capture supervisor. */\nexport interface NativeCaptureRunner {\n start(): void;\n /** Stop the helper (SIGTERM) and resolve once it exits and its final chunk is read. */\n stop(): Promise<void>;\n /** True between a `start()` and its matching `stop()`. */\n readonly running: boolean;\n}\n\n/** The env var that overrides package resolution with an explicit binary path. */\nexport const HELPER_BIN_ENV = \"REMNIC_CAPTURE_HELPER_BIN\";\n/** How long stop() waits for the helper to flush its final chunk and exit. */\nconst STOP_DRAIN_TIMEOUT_MS = 5000;\n/** The `bin` key the #2138 platform package declares (same file as helperBinaryPath). */\nconst HELPER_BIN_NAME = \"remnic-capture-helper\";\n\n/**\n * Compute the optional native-helper package specifier for a platform/arch.\n * The helper is macOS-only and hardware-gated; every other platform (and any\n * unsupported macOS architecture) throws loudly rather than resolving to a\n * package that cannot exist.\n */\nexport function helperPackageSpecifier(platform: NodeJS.Platform | string, arch: string): string {\n if (platform !== \"darwin\") {\n throw new CaptureConfigError(\n `Desktop audio capture native helper is only available on macOS; platform \"${platform}\" is unsupported`,\n );\n }\n if (arch === \"arm64\") return \"@remnic/capture-native-darwin-arm64\";\n if (arch === \"x64\") return \"@remnic/capture-native-darwin-x64\";\n throw new CaptureConfigError(\n `Desktop audio capture native helper is unavailable for macOS architecture \"${arch}\"`,\n );\n}\n\n/**\n * Import-aware resolution: the platform packages expose `.` only under the\n * `import` condition, so a CJS `require.resolve` cannot see them.\n */\nfunction defaultResolve(specifier: string): string {\n return fileURLToPath(import.meta.resolve(specifier));\n}\n\n/**\n * Resolve the native helper binary. Order: explicit `REMNIC_CAPTURE_HELPER_BIN`\n * override, then the computed platform package's declared executable (resolved\n * via Node module resolution, identical to its `helperBinaryPath` export).\n * Throws a CaptureConfigError naming the exact install command when the\n * optional package is not installed.\n */\nexport function resolveHelperBinary(deps: ResolveHelperDeps = {}): HelperResolution {\n const env = deps.env ?? process.env;\n const override = env[HELPER_BIN_ENV]?.trim();\n if (override) return { specifier: `(${HELPER_BIN_ENV})`, binaryPath: expandTilde(override) };\n\n const platform = deps.platform ?? process.platform;\n const arch = deps.arch ?? process.arch;\n const resolve = deps.resolve ?? defaultResolve;\n const readFile = deps.readFile ?? ((file: string) => readFileSync(file, \"utf8\"));\n\n const specifier = helperPackageSpecifier(platform, arch);\n\n let entry: string;\n try {\n // Resolve the package's main entry. Its `exports` map need not expose\n // `package.json`, so never resolve that subpath directly. The helper binary\n // lives at the same path the package's `helperBinaryPath` export resolves to.\n entry = resolve(specifier);\n } catch {\n throw new CaptureConfigError(\n `Desktop audio capture requires the optional native helper ${specifier}, which is not available. ` +\n `Build the Swift helper from source (packages/capture-native-darwin-helper) and set ` +\n `${HELPER_BIN_ENV} to the built remnic-capture-helper binary.`,\n );\n }\n const pkgJsonPath = path.join(path.dirname(entry), \"package.json\");\n return { specifier, binaryPath: helperBinaryFromPackage(pkgJsonPath, readFile, specifier) };\n}\n\nfunction helperBinaryFromPackage(pkgJsonPath: string, readFile: (file: string) => string, specifier: string): string {\n let pkg: unknown;\n try {\n pkg = JSON.parse(readFile(pkgJsonPath));\n } catch {\n throw new CaptureConfigError(`native helper ${specifier} has an unreadable package.json at ${pkgJsonPath}`);\n }\n\n let bin: unknown;\n if (pkg !== null && typeof pkg === \"object\" && \"bin\" in pkg) bin = pkg.bin;\n\n let rel: string | undefined;\n if (typeof bin === \"string\") {\n rel = bin;\n } else if (bin !== null && typeof bin === \"object\") {\n const map: Record<string, unknown> = bin as Record<string, unknown>;\n const named = map[HELPER_BIN_NAME];\n const first = Object.values(map).find((v) => typeof v === \"string\");\n if (typeof named === \"string\") rel = named;\n else if (typeof first === \"string\") rel = first;\n }\n if (rel === undefined || rel === \"\") {\n throw new CaptureConfigError(`native helper ${specifier} does not declare an executable in its package.json \"bin\"`);\n }\n return path.resolve(path.dirname(pkgJsonPath), rel);\n}\n\n/** Build the `audio-capture` argv from runner options (#2138 helper contract). */\nexport function buildHelperArgs(\n opts: Pick<NativeRunnerOptions, \"outDir\" | \"chunkSeconds\" | \"channel\" | \"device\">,\n): string[] {\n const channel = opts.channel ?? \"both\";\n const args = [\n \"audio-capture\",\n \"--channel\",\n channel,\n \"--chunk-seconds\",\n String(opts.chunkSeconds),\n \"--out\",\n opts.outDir,\n ];\n const device = opts.device;\n if (typeof device === \"string\" && device !== \"\") args.push(\"--device\", device);\n return args;\n}\n\nconst ISO_PREFIX_RE = /^\\d{4}-\\d{2}-\\d{2}T/;\n\nfunction parseTimestamp(value: unknown, where: string): string {\n if (typeof value !== \"string\" || !ISO_PREFIX_RE.test(value)) {\n throw new CaptureInputError(`${where}: expected an ISO timestamp`);\n }\n if (!Number.isFinite(Date.parse(value))) {\n throw new CaptureInputError(`${where}: expected a valid ISO timestamp`);\n }\n return value;\n}\n\n/** Parse and validate one JSONL line into a ChunkEvent; throws on anything malformed. */\nexport function parseChunkEvent(line: string): ChunkEvent {\n let raw: unknown;\n try {\n raw = JSON.parse(line);\n } catch {\n const trimmed = line.trim();\n const preview = trimmed.length > 80 ? `${trimmed.slice(0, 77)}...` : trimmed;\n throw new CaptureInputError(`native helper emitted a non-JSON line: ${preview}`);\n }\n if (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) {\n throw new CaptureInputError(\"native helper chunk: expected a JSON object\");\n }\n const obj: Record<string, unknown> = raw as Record<string, unknown>;\n\n if (typeof obj.path !== \"string\" || obj.path.trim() === \"\") {\n throw new CaptureInputError(\"native helper chunk.path: expected a non-empty string\");\n }\n if (obj.channel !== \"mic\" && obj.channel !== \"system\") {\n throw new CaptureInputError('native helper chunk.channel: expected \"mic\" or \"system\"');\n }\n const startedAtUtc = parseTimestamp(obj.startedAtUtc, \"native helper chunk.startedAtUtc\");\n const endedAtUtc = parseTimestamp(obj.endedAtUtc, \"native helper chunk.endedAtUtc\");\n if (Date.parse(endedAtUtc) < Date.parse(startedAtUtc)) {\n throw new CaptureInputError(\"native helper chunk.endedAtUtc: must not precede startedAtUtc\");\n }\n let device: string | null = null;\n if (obj.device !== undefined && obj.device !== null) {\n if (typeof obj.device !== \"string\") {\n throw new CaptureInputError(\"native helper chunk.device: expected a string, null, or absent\");\n }\n device = obj.device;\n }\n return { path: obj.path, channel: obj.channel, startedAtUtc, endedAtUtc, device };\n}\n\ninterface LineReader {\n push(chunk: Buffer | string): void;\n flush(): void;\n}\n\n/** Incrementally split a byte/string stream into complete newline-terminated lines. */\nfunction makeLineReader(onLine: (line: string) => void): LineReader {\n let buffer = \"\";\n return {\n push(chunk) {\n buffer += typeof chunk === \"string\" ? chunk : chunk.toString(\"utf8\");\n let idx = buffer.indexOf(\"\\n\");\n while (idx >= 0) {\n onLine(buffer.slice(0, idx).replace(/\\r$/, \"\"));\n buffer = buffer.slice(idx + 1);\n idx = buffer.indexOf(\"\\n\");\n }\n },\n flush() {\n if (buffer.length > 0) {\n const line = buffer.replace(/\\r$/, \"\");\n buffer = \"\";\n onLine(line);\n }\n },\n };\n}\n\nconst defaultSpawn: HelperSpawn = (binaryPath, args) =>\n // node's typings make stdout/stderr nullable; our piped stdio guarantees them.\n nodeSpawn(binaryPath, args, { shell: false, stdio: [\"ignore\", \"pipe\", \"pipe\"] }) as unknown as HelperChild;\n\n/** Max device-enumerate stdout we will buffer (guards a runaway child). */\nconst MAX_ENUMERATE_BYTES = 1024 * 1024;\n/** Bounded wait for the one-shot device-enumerate helper. */\nconst ENUMERATE_TIMEOUT_MS = 15_000;\n\n/**\n * Run the helper's one-shot `device-enumerate` subcommand and return the parsed\n * device list. Bounded, argv-only, and injectable for tests. Throws a\n * CaptureInputError on a nonzero exit, empty output, or invalid JSON.\n */\nexport function enumerateDevices(\n binaryPath: string,\n spawn: HelperSpawn = defaultSpawn,\n timeoutMs: number = ENUMERATE_TIMEOUT_MS,\n): Promise<unknown[]> {\n // `new Promise` (not Promise.withResolvers) matches the sibling capture\n // packages; this package's tsconfig lib predates withResolvers.\n return new Promise<unknown[]>((resolve, reject) => {\n const child = spawn(binaryPath, [\"device-enumerate\"]);\n let out = \"\";\n let size = 0;\n let settled = false;\n const timer = setTimeout(() => {\n child.kill(\"SIGKILL\");\n fail(new CaptureInputError(\"native helper device-enumerate timed out\"));\n }, timeoutMs);\n timer.unref();\n const fail = (err: Error): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n reject(err);\n };\n child.stdout.on(\"data\", (chunk) => {\n const text = typeof chunk === \"string\" ? chunk : chunk.toString(\"utf8\");\n size += Buffer.byteLength(text);\n if (size > MAX_ENUMERATE_BYTES) {\n child.kill(\"SIGKILL\");\n fail(new CaptureInputError(\"native helper device-enumerate produced too much output\"));\n return;\n }\n out += text;\n });\n child.stderr.on(\"data\", () => undefined);\n child.once(\"error\", (err) => fail(err instanceof Error ? err : new Error(String(err))));\n child.once(\"close\", (code) => {\n if (settled) return;\n if (code !== 0) {\n fail(new CaptureInputError(`native helper device-enumerate exited with status ${code ?? \"unknown\"}`));\n return;\n }\n if (out.trim() === \"\") {\n fail(new CaptureInputError(\"native helper device-enumerate produced no output\"));\n return;\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(out);\n } catch {\n fail(new CaptureInputError(\"native helper device-enumerate produced invalid JSON\"));\n return;\n }\n let list: unknown[] | null = null;\n if (Array.isArray(parsed)) {\n list = parsed;\n } else if (parsed !== null && typeof parsed === \"object\" && \"devices\" in parsed) {\n const devices = parsed.devices;\n if (Array.isArray(devices)) list = devices;\n }\n if (list === null) {\n fail(new CaptureInputError(\"native helper device-enumerate did not return a device array\"));\n return;\n }\n settled = true;\n clearTimeout(timer);\n resolve(list);\n });\n });\n}\n\n/**\n * Create a supervised native-capture runner. Dependency-injectable: pass\n * `spawn`, `resolution`/`resolveBinary`, and `scheduleRestart`/`cancelRestart`\n * to drive it deterministically in tests.\n */\nexport function createNativeCaptureRunner(options: NativeRunnerOptions): NativeCaptureRunner {\n if (typeof options.outDir !== \"string\" || options.outDir.trim() === \"\") {\n throw new CaptureConfigError(\"native runner outDir must be a non-empty string\");\n }\n if (!Number.isFinite(options.chunkSeconds) || options.chunkSeconds <= 0) {\n throw new CaptureConfigError(\"native runner chunkSeconds must be a positive number\");\n }\n\n const spawn: HelperSpawn = options.spawn ?? defaultSpawn;\n const scheduleRestart = options.scheduleRestart ?? ((fn, delayMs) => setTimeout(fn, delayMs).unref());\n const cancelRestart = options.cancelRestart ?? ((timer) => clearTimeout(timer as NodeJS.Timeout));\n const resolveBinary = options.resolveBinary ?? resolveHelperBinary;\n const maxRestarts = options.maxRestarts ?? 5;\n const baseBackoffMs = options.baseBackoffMs ?? 500;\n const maxBackoffMs = options.maxBackoffMs ?? 30_000;\n const args = buildHelperArgs(options);\n\n const onError = (error: Error): void => {\n options.onError?.(error);\n };\n\n let resolution: HelperResolution | undefined = options.resolution;\n let stopped = true;\n let child: HelperChild | undefined;\n let restartTimer: RestartTimer | undefined;\n let restarts = 0;\n let stopResolve: (() => void) | null = null;\n\n function scheduleUnexpectedRestart(): void {\n if (stopped) return;\n if (restarts >= maxRestarts) {\n stopped = true;\n onError(new Error(`native capture helper failed ${restarts} times; giving up`));\n return;\n }\n const delayMs = Math.min(maxBackoffMs, baseBackoffMs * 2 ** restarts);\n restarts += 1;\n restartTimer = scheduleRestart(() => {\n restartTimer = undefined;\n if (!stopped) spawnChild();\n }, delayMs);\n }\n\n function spawnChild(): void {\n if (resolution === undefined) {\n resolution = resolveBinary({});\n }\n const current = spawn(resolution.binaryPath, args);\n child = current;\n let settled = false;\n\n const stdoutReader = makeLineReader((line) => {\n if (line.trim() === \"\") return;\n try {\n const event = parseChunkEvent(line);\n restarts = 0; // a valid chunk proves the helper is healthy — reset backoff\n options.onChunk(event);\n } catch (err) {\n onError(err instanceof Error ? err : new Error(String(err)));\n }\n });\n const stderrReader = makeLineReader((line) => {\n if (line.trim() !== \"\") options.onStderr?.(line);\n });\n\n current.stdout.on(\"data\", (chunk) => stdoutReader.push(chunk));\n current.stderr.on(\"data\", (chunk) => stderrReader.push(chunk));\n\n const settle = (): void => {\n settled = true;\n stdoutReader.flush();\n stderrReader.flush();\n if (child === current) child = undefined;\n if (stopResolve) {\n const done = stopResolve;\n stopResolve = null;\n done();\n }\n };\n\n current.once(\"error\", (err) => {\n if (settled) return;\n settle();\n onError(err instanceof Error ? err : new Error(String(err)));\n scheduleUnexpectedRestart();\n });\n current.once(\"close\", (code, signal) => {\n if (settled) return;\n settle();\n if (stopped) return; // explicit stop — never restart\n onError(\n new Error(`native capture helper exited unexpectedly (code=${code ?? \"null\"}, signal=${signal ?? \"null\"})`),\n );\n scheduleUnexpectedRestart();\n });\n }\n\n return {\n get running(): boolean {\n return !stopped;\n },\n start(): void {\n if (!stopped) return;\n stopped = false;\n restarts = 0;\n try {\n spawnChild();\n } catch (err) {\n // Resolution/spawn failed synchronously — the runner is not running.\n stopped = true;\n throw err;\n }\n },\n stop(): Promise<void> {\n if (stopped) return Promise.resolve();\n stopped = true;\n if (restartTimer !== undefined) {\n cancelRestart(restartTimer);\n restartTimer = undefined;\n }\n const current = child;\n if (!current || current.killed === true) {\n child = undefined;\n return Promise.resolve();\n }\n // Resolve once the child exits (settle() flushes its final chunk first),\n // with a bounded fallback so a wedged helper never hangs shutdown.\n return new Promise<void>((resolve) => {\n let done = false;\n const finish = (): void => {\n if (done) return;\n done = true;\n clearTimeout(killTimer);\n clearTimeout(hardTimer);\n resolve();\n };\n // Normal path: 'close' fires after stdout is flushed -> settle() ->\n // stopResolve(finish), so the helper's final chunk is read first.\n stopResolve = finish;\n // If SIGTERM hasn't drained + closed the child in time, escalate to\n // SIGKILL (whose 'close' still flushes buffered stdout). A hard cap\n // beyond that guarantees shutdown never hangs on a truly wedged helper.\n const killTimer = setTimeout(() => {\n if (!done && current.killed !== true) current.kill(\"SIGKILL\");\n }, STOP_DRAIN_TIMEOUT_MS);\n killTimer.unref();\n const hardTimer = setTimeout(finish, STOP_DRAIN_TIMEOUT_MS + 2000);\n hardTimer.unref();\n current.kill(\"SIGTERM\");\n });\n },\n };\n}\n","/**\n * Cross-channel dedup (issue #1897, component 2.4).\n *\n * A speakerphone is heard twice: once on the mic and once on the system\n * (loopback) channel. When the mic and system channels transcribe\n * near-identical text in overlapping time, keep the SYSTEM copy (the\n * cleaner far-end signal) and drop the mic copy. Match rule: word-level\n * Jaccard >= 0.8 within +-5 s. Pure over segment arrays so the pipeline\n * and the unit tests share one implementation.\n */\n\n/** Minimum shape needed to dedup; the pipeline's richer segments satisfy it. */\nexport interface DedupSegment {\n channel: string;\n text: string;\n startUtc: string;\n endUtc: string;\n}\n\n/** Default temporal tolerance for \"overlapping time\" (ms). */\nconst OVERLAP_TOLERANCE_MS = 5_000;\n/** Default word-level Jaccard threshold for \"near-identical text\". */\nconst JACCARD_THRESHOLD = 0.8;\n\nfunction wordSet(text: string): Set<string> {\n const words = text\n .toLowerCase()\n .replace(/[^\\p{L}\\p{N}\\s]/gu, \" \")\n .split(/\\s+/)\n .filter((w) => w.length > 0);\n return new Set(words);\n}\n\nexport function wordJaccard(a: string, b: string): number {\n const setA = wordSet(a);\n const setB = wordSet(b);\n if (setA.size === 0 && setB.size === 0) return 1;\n if (setA.size === 0 || setB.size === 0) return 0;\n let intersection = 0;\n for (const w of setA) {\n if (setB.has(w)) intersection++;\n }\n const union = setA.size + setB.size - intersection;\n return union === 0 ? 0 : intersection / union;\n}\n\n/** True when two segments overlap in time, expanded by `toleranceMs` on each side. */\nfunction overlapsWithin(a: DedupSegment, b: DedupSegment, toleranceMs: number): boolean {\n const aStart = Date.parse(a.startUtc);\n const aEnd = Date.parse(a.endUtc);\n const bStart = Date.parse(b.startUtc);\n const bEnd = Date.parse(b.endUtc);\n if (!Number.isFinite(aStart) || !Number.isFinite(aEnd) || !Number.isFinite(bStart) || !Number.isFinite(bEnd)) {\n return false;\n }\n return aStart <= bEnd + toleranceMs && bStart <= aEnd + toleranceMs;\n}\n\n/**\n * Drop mic segments that duplicate a system segment (overlapping time +\n * Jaccard >= threshold). System segments and non-duplicate mic segments\n * are preserved in input order. Generic so callers keep their richer type.\n */\nexport function dedupeCrossChannel<T extends DedupSegment>(\n segments: readonly T[],\n options: { toleranceMs?: number; jaccardThreshold?: number } = {},\n): T[] {\n const toleranceMs = options.toleranceMs ?? OVERLAP_TOLERANCE_MS;\n const threshold = options.jaccardThreshold ?? JACCARD_THRESHOLD;\n const systemSegments = segments.filter((s) => s.channel === \"system\");\n return segments.filter((seg) => {\n if (seg.channel !== \"mic\") return true;\n const duplicated = systemSegments.some(\n (sys) => overlapsWithin(seg, sys, toleranceMs) && wordJaccard(seg.text, sys.text) >= threshold,\n );\n return !duplicated;\n });\n}\n","/**\n * Chunk processor (issue #1897) — turns completed native WAV chunk events\n * into durable, replay-safe conversations in the spool.\n *\n * The native helper runner owns process lifecycle and emits one validated\n * `ChunkEvent` per recorded WAV. This module consumes those events through a\n * single serialized promise chain: resolve model -> transcribe -> normalize\n * nonempty segments -> assemble -> persist via durable chunk idempotency ->\n * delete the raw WAV. A rejected chunk is reported and the chain recovers so\n * the daemon stays alive; the durable `applied_chunks` guard keeps a\n * restart/replay of the same chunk from duplicating segments.\n *\n * Every collaborator (STT, model resolution, raw-audio cleanup) is injected,\n * so no optional VAD/native runtime is imported here and the package stays\n * à-la-carte.\n */\n\nimport { createHash } from \"node:crypto\";\n\nimport { log } from \"@remnic/core/logger\";\n\nimport {\n ChunkApplyError,\n isReleaseEligible,\n MAX_BUFFERED_CHUNKS,\n QUARANTINE_AFTER_FAILURES,\n} from \"./buffer-policy.js\";\nimport type { AssemblySegment, ConversationAssembler } from \"./assembly.js\";\nimport { dedupeCrossChannel } from \"./dedup.js\";\nimport { CaptureInputError } from \"./errors.js\";\nimport type { Embedding, SpeakerClusterer } from \"./diarization.js\";\nimport type { ChunkEvent } from \"./native.js\";\nimport type { Spool } from \"./spool.js\";\nimport type { TranscribedSegment } from \"./stt.js\";\n\nexport interface ChunkTranscribeInput {\n wavPath: string;\n modelPath: string;\n chunkStartedAtUtc: string;\n}\n\nexport interface ChunkProcessorDeps {\n spool: Spool;\n /** Stateful assembler that groups consecutive segments into conversations. */\n assembler: ConversationAssembler;\n /** Resolve the STT model path; called per speech chunk and may throw when absent. */\n resolveModel: () => string;\n /** Transcribe one WAV chunk into raw segments. */\n transcribe: (input: ChunkTranscribeInput) => Promise<TranscribedSegment[]>;\n /** Delete the raw WAV under retention once the chunk is durably persisted. */\n cleanupRawAudio: (event: ChunkEvent) => Promise<void>;\n /**\n * VAD speech gate. When provided and it resolves false, the chunk is treated\n * as non-speech: STT is skipped (the CPU-budget guard) and no segments\n * persist. Absent -> every chunk is transcribed.\n */\n detectSpeech?: (event: ChunkEvent) => boolean | Promise<boolean>;\n /**\n * Speaker-embedding extractor for diarization. With `diarizer`, each segment\n * is embedded and assigned to a speaker cluster; absent -> the interim\n * mic=wearer heuristic and no speaker cluster.\n */\n embed?: (event: ChunkEvent, segment: TranscribedSegment) => Embedding | Promise<Embedding>;\n /** Speaker clusterer (seeded from the spool); its clusters are persisted on finalize. */\n diarizer?: SpeakerClusterer;\n /** Cross-channel dedup window in ms; defaults to the dedup module's tolerance. */\n dedupWindowMs?: number;\n /**\n * Bounded reorder window in ms for cross-channel arrival skew (issue\n * #2145). A transcribed chunk is HELD until the newest observed chunk end\n * is this far past its own end, then released oldest-first, so a delayed\n * system chunk is assembled into the conversation it temporally belongs to\n * rather than joined to a later mic chunk's. 0 (the default here, so\n * existing callers are unchanged) releases every chunk on arrival.\n */\n reorderWindowMs?: number;\n /** Reports a per-chunk failure; the chain keeps running afterwards. */\n onError?: (error: Error, event: ChunkEvent) => void;\n}\n\nexport interface ChunkProcessor {\n /** onChunk seam for `NativeRunnerOptions`. Never throws; failures route to `onError`. */\n enqueue(event: ChunkEvent): void;\n /** Resolve once the serialized chain has settled all enqueued chunks. */\n drain(): Promise<void>;\n /** Drain, then flip open conversations to `final`; returns the count closed. */\n finalize(): Promise<number>;\n}\n\nexport { MAX_BUFFERED_CHUNKS, QUARANTINE_AFTER_FAILURES } from \"./buffer-policy.js\";\n\n/**\n * Stable chunk identity derived purely from the WAV path. Because it never\n * depends on a freshly-generated conversation id, the same chunk yields the\n * same idempotency key across process restarts.\n */\n/**\n * Stable per-segment idempotency key.\n *\n * Derived from the segment's CONTENT — its bounds and text — not its position.\n * An index is not an identity: a retranscription that changes segment\n * boundaries, or inserts a segment before a partially committed prefix, would\n * bind an existing key to different audio (issue #2145). Content survives\n * both, so a key always means the same bytes.\n */\nexport function segmentStableKey(chunkId: string, segment: { startUtc: string; endUtc: string; text: string }): string {\n const digest = createHash(\"sha1\")\n .update(`${segment.startUtc}\\u0000${segment.endUtc}\\u0000${segment.text}`)\n .digest(\"hex\")\n .slice(0, 16);\n return `${chunkId}:h${digest}`;\n}\n\n/**\n * Marker key describing a chunk's WHOLE transcript.\n *\n * A segment count is too weak a manifest: a replay that returns the same\n * number of DIFFERENT segments would match it and let a partially applied\n * chunk be marked complete with mixed content. Hashing every segment key\n * catches both a shortened and a changed transcript (issue #2145).\n */\nexport function transcriptManifestHash(\n chunkId: string,\n segments: readonly { startUtc: string; endUtc: string; text: string }[],\n): string {\n return createHash(\"sha1\")\n .update(segments.map((segment) => segmentStableKey(chunkId, segment)).join(\"\\n\"))\n .digest(\"hex\")\n .slice(0, 16);\n}\n\n/** Fixed, indexed key holding a chunk's transcript manifest hash. */\nexport function transcriptManifestKey(chunkId: string): string {\n return `${chunkId}:manifest`;\n}\n\nexport function chunkStableId(event: ChunkEvent): string {\n return `chk_${createHash(\"sha1\").update(event.path).digest(\"hex\")}`;\n}\n\n\n/** One transcribed chunk waiting in the reorder buffer (issue #2145). */\ninterface BufferedChunk {\n event: ChunkEvent;\n chunkId: string;\n built: Array<{ seg: AssemblySegment; raw: TranscribedSegment }>;\n /** Earliest segment start, or the chunk's own start when it is silent. */\n startMs: number;\n /** Latest segment end, never earlier than the chunk's own end. */\n endMs: number;\n /**\n * Whether this chunk's transcript manifest is durably recorded. A chunk is\n * never released without it: the manifest is what stops a later, changed\n * retranscription from completing a partially applied chunk (issue #2145).\n */\n manifestRecorded: boolean;\n}\n\n/**\n * Epoch ms for an instant the native layer already validated. A value that\n * cannot be parsed would silently sort first and defeat the reorder buffer,\n * so it is rejected instead.\n */\nfunction instantMs(value: string, field: string): number {\n const ms = Date.parse(value);\n if (Number.isNaN(ms)) {\n throw new CaptureInputError(`${field}: expected an ISO-8601 instant, got ${JSON.stringify(value)}`);\n }\n return ms;\n}\n\nfunction firstStartMs(event: ChunkEvent, raw: readonly TranscribedSegment[]): number {\n let earliest = instantMs(event.startedAtUtc, \"chunk.startedAtUtc\");\n for (const segment of raw) {\n earliest = Math.min(earliest, instantMs(segment.startUtc, \"segment.startUtc\"));\n }\n return earliest;\n}\n\nfunction lastEndMs(event: ChunkEvent, raw: readonly TranscribedSegment[]): number {\n let latest = instantMs(event.endedAtUtc, \"chunk.endedAtUtc\");\n for (const segment of raw) {\n latest = Math.max(latest, instantMs(segment.endUtc, \"segment.endUtc\"));\n }\n return latest;\n}\n\nexport function createChunkProcessor(deps: ChunkProcessorDeps): ChunkProcessor {\n let tail: Promise<void> = Promise.resolve();\n const failCounts = new Map<string, number>();\n let openConversationId: string | null = null;\n // Set when a chunk is deliberately left unapplied for a later replay: its\n // conversation must stay `capturing` so that replay can resume it.\n /**\n * Conversations each retained chunk may still need to `resume`, keyed by\n * chunk id.\n *\n * Retention is per CHUNK and per CONVERSATION, never a run-wide latch: a\n * sticky flag would stop the daemon from finalizing any conversation again\n * after one retention event, and clearing it per batch would release the hold\n * as soon as the NEXT batch had nothing retained. An entry lives exactly as\n * long as its chunk is incomplete (issue #2145).\n */\n const retainedChunks = new Map<string, Set<string>>();\n\n /** Whether a conversation is a prefix some retained chunk could resume. */\n function isHeldForReplay(conversationId: string): boolean {\n for (const held of retainedChunks.values()) {\n if (held.has(conversationId)) return true;\n }\n return false;\n }\n\n /**\n * Record, or clear, the conversations a chunk's replay could still need.\n *\n * `resumable` is snapshotted BEFORE the batch appends anything: a healthy\n * conversation opened by a gap split in the same batch is not a prefix any\n * replay can resume, so holding it would keep unrelated audio out of the\n * final-only read path until the retained chunk completes.\n */\n function trackRetention(chunkId: string, retained: boolean, resumable: ReadonlySet<string>): void {\n if (!retained) {\n retainedChunks.delete(chunkId);\n return;\n }\n retainedChunks.set(chunkId, heldFor(chunkId, resumable));\n }\n\n /**\n * The conversations one chunk's replay could still need.\n *\n * Scoped to the conversations the chunk actually contributed segments to, so\n * a hold never keeps unrelated audio off the final-only read path. A chunk\n * with a manifest but no stored segments — a crash between the two — has no\n * mapping yet, so it conservatively holds everything currently resumable.\n */\n function heldFor(chunkId: string, resumable: ReadonlySet<string>): Set<string> {\n const own = deps.spool.conversationIdsForChunk(chunkId).filter((id) => resumable.has(id));\n return own.length > 0 ? new Set(own) : new Set(resumable);\n }\n\n /** Conversations a replay could resume right now. */\n function resumableConversations(): Set<string> {\n const ids = new Set(deps.spool.capturingConversationIds());\n if (openConversationId !== null) ids.add(openConversationId);\n return ids;\n }\n const processedThisRun = new Set<string>();\n // Bounded reorder buffer (issue #2145). With `captureChannel: \"both\"` the\n // native helper emits one chunk stream per channel, and a system chunk for\n // an earlier window can arrive AFTER a later mic chunk. Feeding the\n // assembler in arrival order then joins the delayed (earlier) segment to a\n // conversation that started after it. Chunks are held here until the newest\n // observed chunk end is `reorderWindowMs` past their own end, then released\n // oldest-first, so the assembler always sees a chronological stream.\n //\n // A HELD chunk is not marked complete and keeps its WAV, so a crash before\n // release replays it from the raw audio — the same durability contract as\n // an untranscribed chunk.\n const reorderWindowMs = Math.max(0, deps.reorderWindowMs ?? 0);\n const buffer: BufferedChunk[] = [];\n const bufferedIds = new Set<string>();\n let watermarkSourceMs = Number.NEGATIVE_INFINITY;\n // Order-independent cross-channel dedup, applied at finalization: a mic\n // segment duplicating a system (loopback) segment in the same conversation is\n // pruned, keeping the cleaner system copy. Running at finalize — when every\n // segment is present — means arrival order (mic-before-system or the reverse)\n // never matters, so the native helper's shutdown ordering can't leak a dup.\n const dedupeConversation = (id: string): void => {\n const segs = deps.spool.conversationSegmentsForDedup(id);\n if (segs.length === 0) return;\n const options = deps.dedupWindowMs !== undefined ? { toleranceMs: deps.dedupWindowMs } : {};\n const keep = new Set(dedupeCrossChannel(segs, options).map((s) => s.id));\n const drop = segs.filter((s) => !keep.has(s.id)).map((s) => s.id);\n if (drop.length > 0) deps.spool.deleteSegments(drop);\n };\n /**\n * Cluster one conversation's surviving segments (issue #2145).\n *\n * Runs AFTER dedup, so a pruned mic loopback contributes nothing: no\n * inflated `embeddingCount`, no phantom cluster for a speaker that already\n * exists. Only rows that still have no cluster are assigned, so a repeated\n * finalize is idempotent. Diarization is therefore a pure function of the\n * deduped segment set, independent of cross-channel arrival order.\n */\n const diarizeConversation = (id: string): void => {\n const diarizer = deps.diarizer;\n if (!diarizer) return;\n const pending = deps.spool.conversationSegmentsForDiarization(id);\n if (pending.length === 0) return;\n // `assign` mutates the in-memory centroids and counts. If the commit rolls\n // back, those mutations must roll back too — otherwise the retry counts\n // the same embeddings against an already-advanced clusterer and persists\n // skewed snapshots. The snapshot below is the undo log.\n const before = diarizer.clusters();\n // A label-only enrolled self can never match live audio, so the mic\n // heuristic stays the wearer signal until voice enrollment lands.\n const selfVoiceEnrolled = diarizer.clusters().some((c) => c.isSelf && c.embeddingCount > 0);\n const assignments: Array<{ id: string; speakerCluster: string; isWearer: boolean }> = [];\n const touched = new Set<string>();\n for (const segment of pending) {\n const clusterId = diarizer.assign(segment.embedding);\n const assigned = diarizer.clusters().find((c) => c.id === clusterId);\n const isWearer =\n (assigned?.isSelf ?? false) || (segment.channel === \"mic\" && !selfVoiceEnrolled);\n assignments.push({ id: segment.id, speakerCluster: clusterId, isWearer });\n touched.add(clusterId);\n }\n // ONE transaction: a crash between persisting the cluster counts and\n // persisting the assignments would leave the counts advanced while the\n // segments still look pending, and the next finalize would count the same\n // embeddings again (issue #2145).\n const byId = new Map(diarizer.clusters().map((c) => [c.id, c]));\n const clusters = [...touched]\n .map((clusterId) => byId.get(clusterId))\n .filter((cluster): cluster is NonNullable<typeof cluster> => cluster !== undefined)\n .map((cluster) => ({\n id: cluster.id,\n label: cluster.label,\n isSelf: cluster.isSelf,\n embeddingCount: cluster.embeddingCount,\n centroid: cluster.centroid,\n examples: cluster.examples,\n }));\n try {\n deps.spool.commitDiarization({ clusters, assignments });\n } catch (err) {\n // Restore the clusterer to its pre-assign state so the next finalize\n // starts from the same place SQLite did.\n diarizer.restore(before);\n throw err;\n }\n };\n const finalizeConv = (id: string): boolean => {\n // A held prefix must stay `capturing` so a later replay can resume it. This\n // is the single durable choke point: the assembler closes conversations\n // from several paths (idle close, gap split inside `add`, shutdown), and\n // guarding each of them would leave the next one to be found.\n if (isHeldForReplay(id)) return false;\n dedupeConversation(id);\n diarizeConversation(id);\n deps.spool.finalizeConversation(id);\n return true;\n };\n\n\n const report = (error: unknown, event: ChunkEvent): void => {\n // A throwing operator callback must not become a pipeline failure: it would\n // propagate into the serialized chain and stall every later chunk.\n try {\n deps.onError?.(error instanceof Error ? error : new Error(String(error)), event);\n } catch {\n // nothing left to report it to\n }\n };\n\n function persistPending(entry: BufferedChunk, reason: \"evicted\" | \"quarantined\"): void {\n deps.spool.recordPendingChunk({\n id: entry.chunkId,\n wavPath: entry.event.path,\n startedAtUtc: entry.event.startedAtUtc,\n endedAtUtc: entry.event.endedAtUtc,\n channel: entry.event.channel,\n device: entry.event.device,\n reason,\n });\n }\n\n async function process(event: ChunkEvent): Promise<void> {\n const chunkId = chunkStableId(event);\n // In-run replay: already applied, or already transcribed and waiting in\n // the reorder buffer. Either way this event carries nothing new.\n if (processedThisRun.has(chunkId) || bufferedIds.has(chunkId)) return;\n // Durable (cross-restart) FULL replay: a completed chunk wrote a per-chunk\n // ':done' marker, so its segments AND cluster updates are persisted. Skip\n // transcription + diarization entirely. A PARTIAL replay (crash between\n // group appends) has no marker, so it falls through and per-group\n // idempotency re-appends only the missing groups below.\n if (deps.spool.isChunkApplied(`${chunkId}:done`)) {\n processedThisRun.add(chunkId);\n // Retry raw-WAV reclaim: the marker is written before cleanup, so if the\n // first run's cleanup failed (or it died between marking and deleting), a\n // replay is our chance to remove the file instead of waiting for the janitor.\n try {\n await deps.cleanupRawAudio(event);\n } catch (err) {\n report(err, event);\n }\n return;\n }\n\n // Pre-#2145 runs keyed appends per GROUP (`chunkId` or `chunkId:<n>`).\n // Those keys cannot be mapped onto the new per-segment keys, so a chunk\n // carrying one is treated as already applied: re-appending under new keys\n // would duplicate its segments, which is worse than leaving the tail of\n // one in-flight chunk unsent across the upgrade.\n if (deps.spool.isChunkApplied(chunkId) || deps.spool.isChunkApplied(`${chunkId}:0`)) {\n // Legacy group keys prove only that SOME group persisted, so this chunk\n // is left strictly alone: not re-appended (which would duplicate the\n // stored groups), and not marked done or reclaimed (which would discard\n // an unpersisted tail). The WAV stays for a rebuild or manual replay,\n // and the operator is told once.\n processedThisRun.add(chunkId);\n log.warn(\n `[capture-audio] chunk ${chunkId} was partially applied under the pre-#2145 key scheme; leaving it and its raw audio in place for replay`,\n );\n return;\n }\n // VAD gate: a non-speech chunk skips STT (and model resolution) entirely,\n // flowing through as a zero-segment chunk so idle-close + WAV reclaim still\n // run. Absent detectSpeech -> transcribe every chunk.\n const isSpeech = deps.detectSpeech ? await deps.detectSpeech(event) : true;\n const raw = isSpeech\n ? await deps.transcribe({\n wavPath: event.path,\n modelPath: deps.resolveModel(),\n chunkStartedAtUtc: event.startedAtUtc,\n })\n : [];\n const built = buildSegments(event, raw);\n buffer.push({\n event,\n chunkId,\n built,\n startMs: firstStartMs(event, raw),\n endMs: lastEndMs(event, raw),\n manifestRecorded: false,\n });\n bufferedIds.add(chunkId);\n watermarkSourceMs = Math.max(watermarkSourceMs, lastEndMs(event, raw));\n if (buffer.length > MAX_BUFFERED_CHUNKS) {\n // A sustained persistence outage would otherwise grow this buffer — and\n // the work of every release scan — without bound. The OLDEST chunk is\n // dropped from memory, not from disk: its WAV stays for replay.\n const evicted = buffer.shift();\n if (evicted !== undefined) {\n bufferedIds.delete(evicted.chunkId);\n persistPending(evicted, \"evicted\");\n report(\n new Error(\n `reorder buffer is full (${MAX_BUFFERED_CHUNKS}); chunk ${evicted.chunkId} was dropped with its raw audio retained`,\n ),\n evicted.event,\n );\n }\n }\n await releaseReady(false);\n }\n\n /** Build the base (undiarized) segments for one chunk's raw transcript. */\n function buildSegments(\n event: ChunkEvent,\n raw: readonly TranscribedSegment[],\n ): Array<{ seg: AssemblySegment; raw: TranscribedSegment }> {\n const built: Array<{ seg: AssemblySegment; raw: TranscribedSegment }> = [];\n for (const s of raw) {\n const text = s.text.trim();\n if (text === \"\") continue;\n built.push({\n seg: {\n channel: event.channel,\n text,\n startUtc: s.startUtc,\n endUtc: s.endUtc,\n isWearer: event.channel === \"mic\",\n },\n raw: s,\n });\n }\n return built;\n }\n\n /**\n * Apply one batch of released chunks.\n *\n * Whole-chunk release alone is not enough: two chunks can COVER THE SAME\n * WINDOW on different channels, so a mic segment at 00:00 and 00:20 must not\n * be applied ahead of a system segment at 00:10 (issue #2145). The batch's\n * segments are therefore interleaved into one chronological stream, fed to\n * the assembler in that order, and appended as runs that each belong to one\n * chunk — so the per-chunk idempotency keys (`chunkId`, `chunkId:g`,\n * `chunkId:done`) keep their existing shape and a lone chunk still uses the\n * bare `chunkId` exactly as before.\n *\n * Everything here runs on the RELEASE timeline, not the arrival timeline.\n */\n async function applyBatch(\n batch: readonly BufferedChunk[],\n progress: { persisted: boolean },\n ): Promise<void> {\n // Reconstruct assembler position from durable rows on every apply\n // (issue #2379). A partial persist then retry must resume the capturing\n // conversation and its segment bounds, not keep an advanced assembler.\n deps.assembler.rewind([]);\n openConversationId = null;\n const ownPrefix = batch\n .flatMap((entry) => deps.spool.conversationIdsForChunk(entry.chunkId))\n .map((id) => deps.spool.capturingConversationById(id))\n .find((conversation) => conversation !== null);\n const prior = ownPrefix ?? deps.spool.latestCapturingConversation();\n if (prior) {\n deps.assembler.resume(prior);\n openConversationId = prior.id;\n }\n const capturingNow = new Set(deps.spool.capturingConversationIds());\n for (const chunkId of deps.spool.incompleteChunkIds()) {\n const held = heldFor(chunkId, capturingNow);\n if (held.size > 0) retainedChunks.set(chunkId, held);\n }\n // Decide retention BEFORE anything can close a conversation: a chunk kept\n // for a later replay must keep its durable prefix resumable, and both the\n // idle-close below and the gap split inside `assembler.add` would otherwise\n // flip it to final while this batch is still being applied (issue #2145).\n const resumable = resumableConversations();\n for (const entry of batch) trackRetention(entry.chunkId, !isFullyProcessed(entry), resumable);\n const earliestStart = batch.reduce(\n (earliest, entry) => (entry.startMs < earliest.startMs ? entry : earliest),\n batch[0],\n );\n const closed = deps.assembler.closeIfIdle(earliestStart.event.startedAtUtc);\n if (closed !== null && closed === openConversationId) {\n // Dedupes, diarizes and flips a conversation in the spool — durable, so\n // the caller must not rewind past it. Flagged AFTER the call: a throw\n // inside it leaves nothing durable, and the batch should still rewind.\n if (finalizeConv(closed)) {\n progress.persisted = true;\n openConversationId = null;\n }\n }\n\n // One chronological stream across the batch. The comparator is total —\n // start, then end, then chunk id, then position — so the same batch always\n // interleaves the same way (rule 12).\n const stream = batch\n .flatMap((entry) =>\n entry.built.map((item, index) => ({ entry, item, index })),\n )\n .sort((left, right) => {\n if (left.item.seg.startUtc !== right.item.seg.startUtc) {\n return left.item.seg.startUtc < right.item.seg.startUtc ? -1 : 1;\n }\n if (left.item.seg.endUtc !== right.item.seg.endUtc) {\n return left.item.seg.endUtc < right.item.seg.endUtc ? -1 : 1;\n }\n if (left.entry.chunkId !== right.entry.chunkId) {\n return left.entry.chunkId < right.entry.chunkId ? -1 : 1;\n }\n return left.index - right.index;\n });\n\n // Durable segments leave the stream before anything else touches them: a\n // replay must not pay for their embedding again, and an input-specific\n // embedding failure on an already-persisted segment would requeue the\n // batch forever and block the segments that ARE missing (issue #2145).\n const fresh = stream.filter(\n ({ entry, item }) => !deps.spool.isChunkApplied(segmentStableKey(entry.chunkId, item.seg)),\n );\n\n\n // Embed before a single `assembler.add`. Embedding is the only await that\n // can throw before persistence, and the assembler has no undo: a throw\n // after it had consumed segments would leave it advanced.\n if (deps.embed) {\n for (const { entry, item } of fresh) {\n try {\n item.seg.embedding = await deps.embed(entry.event, item.raw);\n } catch (error) {\n throw new ChunkApplyError(entry.chunkId, error);\n }\n }\n }\n\n // Assign conversations over the interleaved stream, then cut it into runs\n // that each belong to ONE chunk and ONE conversation. The runs are already\n // in chronological order, so a conversation is only finalized once the\n // stream has truly moved past it.\n //\n // Already-applied segments are dropped BEFORE the assembler sees them.\n // They live in a durable conversation already, so re-adding them would\n // either duplicate in-memory state or — if the assembler were rewound to\n // undo them — mint a second id for a conversation that is already on disk\n // and split it. Skipping is the only option that keeps the in-memory ids\n // and the persisted ids identical (issue #2145).\n const runs: Array<{\n entry: BufferedChunk;\n id: string;\n startedAtUtc: string;\n items: Array<{ seg: AssemblySegment; raw: TranscribedSegment; index: number }>;\n }> = [];\n for (const { entry, item, index } of fresh) {\n const conv = deps.assembler.add(item.seg);\n const carried = { ...item, index };\n const last = runs[runs.length - 1];\n if (last && last.id === conv.id && last.entry === entry) last.items.push(carried);\n else runs.push({ entry, id: conv.id, startedAtUtc: conv.startedAtUtc, items: [carried] });\n }\n\n // Idempotency keys are derived from each segment's position in ITS OWN\n // chunk, never from the batch: `chunkId:i<index>`. A key must mean the\n // same bytes on every replay, and a replay rarely reproduces the same\n // batch — so a positional `chunkId:g` could skip a group that now holds\n // different segments, or miss an applied one and append twice. One append\n // per segment costs one extra row per segment and makes the guard exact.\n for (const run of runs) {\n const { chunkId, event } = run.entry;\n for (const item of run.items) {\n const key = segmentStableKey(chunkId, item.seg);\n if (openConversationId !== null && openConversationId !== run.id) {\n // A held conversation is left `capturing`, so nothing durable changed\n // and the batch must still be able to rewind.\n if (finalizeConv(openConversationId)) progress.persisted = true;\n }\n try {\n deps.spool.appendAssembledSegments({\n idempotencyKey: key,\n chunkId: key,\n conversationId: run.id,\n startedAtUtc: run.startedAtUtc,\n state: \"capturing\",\n device: event.device,\n wavPath: event.path,\n segments: [item.seg],\n });\n } catch (error) {\n throw new ChunkApplyError(chunkId, error);\n }\n progress.persisted = true;\n openConversationId = run.id;\n }\n }\n\n for (const entry of batch) {\n // Mark the whole chunk complete only when it is safe: either we processed\n // real segments this run (so every run of this chunk is now applied), or\n // it is a genuinely fresh silent chunk with no prior partial application.\n // A zero-segment run over a chunk whose earlier groups were already\n // applied (a partial crash) must NOT be marked done, or the missing tail\n // groups would be stranded forever.\n // Completeness needs one fact a replay cannot re-derive: how many\n // segments the transcript produced the FIRST time. Without it, a shorter\n // retranscription is indistinguishable from a missing tail — mark done\n // and a tail can be lost, refuse and a fully-applied chunk\n // re-transcribes forever. So the count is persisted once and compared.\n // The manifest marker was written at transcribe time, so a mismatch here\n // means an EARLIER run produced a DIFFERENT transcript — shorter, or the\n // same length with different content. Either way a segment no run has\n // accounted for may be missing, so the chunk stays open and keeps its\n // audio.\n const fullyProcessed = isFullyProcessed(entry);\n trackRetention(entry.chunkId, !fullyProcessed, resumable);\n if (entry.built.length > 0 && !fullyProcessed) {\n log.warn(\n `[capture-audio] chunk ${entry.chunkId} retranscribed to a different transcript than an earlier run; keeping its raw audio for replay`,\n );\n }\n processedThisRun.add(entry.chunkId);\n if (!fullyProcessed) continue;\n // The chunk is fully durably transcribed: record completion and reclaim\n // the raw WAV. A partial chunk (earlier groups applied, this run added\n // nothing) must RETAIN its WAV so a later full replay can still\n // transcribe the missing tail. Cleanup is best-effort; the janitor is\n // the backstop.\n deps.spool.markChunkComplete(entry.chunkId, openConversationId ?? \"-\");\n deps.spool.deletePendingChunk(entry.chunkId);\n failCounts.delete(entry.chunkId);\n try {\n await deps.cleanupRawAudio(entry.event);\n } catch (err) {\n report(err, entry.event);\n }\n }\n }\n\n /**\n * Whether every segment this chunk ever produced is now durably stored.\n *\n * Completeness needs one fact a replay cannot re-derive: the transcript the\n * chunk produced the FIRST time. The manifest marker carries it, written\n * before any append, so a mismatch here means an earlier run produced a\n * DIFFERENT transcript — shorter, or the same length with different content.\n * Either way a segment no run has accounted for may be missing, so the chunk\n * stays open and keeps its audio. A chunk partially applied by a binary\n * predating the manifest has none to match, so a silent replay must not be\n * the first to record one (issue #2145).\n */\n function isFullyProcessed(entry: BufferedChunk): boolean {\n if (entry.built.length === 0) return !deps.spool.hasAppliedChunkPrefix(`${entry.chunkId}:`);\n return (\n deps.spool.appliedChunkValue(transcriptManifestKey(entry.chunkId)) ===\n transcriptManifestHash(entry.chunkId, entry.built.map((item) => item.seg))\n );\n }\n\n /**\n * Persist a buffered chunk's transcript manifest, once. Returns whether the\n * chunk may now be released.\n */\n function recordTranscriptManifest(entry: BufferedChunk): boolean | \"conflict\" {\n if (entry.built.length === 0) {\n // A silent transcript has nothing to append, so it needs no manifest —\n // and writing one would let an empty replay of a partially applied chunk\n // become the authoritative manifest, stranding the real tail.\n entry.manifestRecorded = true;\n return true;\n }\n try {\n const key = transcriptManifestKey(entry.chunkId);\n const ours = transcriptManifestHash(entry.chunkId, entry.built.map((item) => item.seg));\n const recorded = deps.spool.appliedChunkValue(key);\n if (recorded === undefined) {\n deps.spool.markApplied(key, ours);\n } else if (recorded !== ours) {\n // A divergent retranscription of a partially applied chunk. Appending\n // it would interleave content the first transcript never had, so this\n // chunk contributes NOTHING: it is dropped from the run and its raw\n // audio is retained for a replay that reproduces the manifest.\n return \"conflict\";\n }\n entry.manifestRecorded = true;\n return true;\n } catch (err) {\n report(err, entry.event);\n return false;\n }\n }\n\n /**\n * A chunk becomes releasable when the newest observed chunk end is\n * `reorderWindowMs` past its own end AND no still-held chunk overlaps it\n * (issue #2379). Releasing the whole releasable set together is what lets\n * `applyBatch` interleave overlapping cross-channel windows.\n *\n * `flushAll` ignores the watermark, for `finalize()`.\n */\n async function releaseReady(flushAll: boolean): Promise<void> {\n // A failed FINAL flush must reach the caller: `stop()` discards the buffer\n // afterwards, and reporting success would silently drop the retained\n // chunks (issue #2145). A mid-run failure stays reported-and-retried.\n let finalFailure: unknown;\n for (;;) {\n const threshold = flushAll ? Number.POSITIVE_INFINITY : watermarkSourceMs - reorderWindowMs;\n const held = buffer.filter((entry) => entry.endMs > threshold);\n const batch: BufferedChunk[] = [];\n let manifestBlocked = false;\n for (let i = buffer.length - 1; i >= 0; i--) {\n const candidate = buffer[i];\n if (!isReleaseEligible(candidate, threshold, held)) continue;\n // Record the transcript manifest BEFORE the chunk can be appended, and\n // only once. A chunk whose manifest cannot be written stays buffered\n // with its raw audio: releasing it would let a later, changed\n // retranscription record the FIRST manifest and complete a partially\n // applied chunk (issue #2145). The event is not lost — the next pass\n // retries the write.\n const manifest = candidate.manifestRecorded ? true : recordTranscriptManifest(candidate);\n if (manifest === \"conflict\") {\n buffer.splice(i, 1);\n bufferedIds.delete(candidate.chunkId);\n // The chunk keeps its audio for a matching replay, so its durable\n // prefix must stay resumable — do not let the sweep close it.\n trackRetention(candidate.chunkId, true, resumableConversations());\n report(\n new Error(\n `transcript for chunk ${candidate.chunkId} does not match the recorded manifest; raw audio retained`,\n ),\n candidate.event,\n );\n continue;\n }\n if (!manifest) {\n // Hold the WHOLE window, not just this entry: applyBatch interleaves\n // the ready set together, and releasing peers without it would apply\n // the held chunk's segments out of order later (issue #2145).\n manifestBlocked = true;\n break;\n }\n buffer.splice(i, 1);\n bufferedIds.delete(candidate.chunkId);\n batch.push(candidate);\n }\n if (manifestBlocked) {\n // Put back anything already taken so the window releases as one set.\n for (const entry of batch) {\n buffer.push(entry);\n bufferedIds.add(entry.chunkId);\n }\n if (flushAll) {\n throw new Error(\"flush-plan manifest could not be persisted; chunks are retained for replay\");\n }\n return;\n }\n if (batch.length === 0) {\n if (flushAll && buffer.length > 0) {\n for (const entry of buffer) persistPending(entry, \"evicted\");\n throw new Error(\"buffered chunks could not be released; they are retained for replay\");\n }\n return;\n }\n // Total order so the same batch always applies the same way (rule 12).\n batch.sort(\n (left, right) =>\n left.startMs - right.startMs ||\n left.endMs - right.endMs ||\n (left.chunkId < right.chunkId ? -1 : left.chunkId > right.chunkId ? 1 : 0),\n );\n const progress = { persisted: false };\n try {\n await applyBatch(batch, progress);\n } catch (error) {\n // Next apply reseeds from the spool. Drop in-memory assembly here so a\n // pre-commit failure leaves the assembler empty and a partial persist\n // does not keep an advanced position (issue #2379).\n deps.assembler.rewind([]);\n openConversationId = null;\n let quarantinedId: string | undefined;\n if (error instanceof ChunkApplyError) {\n const failures = (failCounts.get(error.chunkId) ?? 0) + 1;\n failCounts.set(error.chunkId, failures);\n if (failures >= QUARANTINE_AFTER_FAILURES) {\n const poisoned = batch.find((entry) => entry.chunkId === error.chunkId);\n if (poisoned !== undefined) {\n persistPending(poisoned, \"quarantined\");\n retainedChunks.delete(poisoned.chunkId);\n quarantinedId = poisoned.chunkId;\n }\n }\n }\n for (const entry of batch) {\n if (processedThisRun.has(entry.chunkId) || bufferedIds.has(entry.chunkId)) continue;\n if (entry.chunkId === quarantinedId) continue;\n buffer.push(entry);\n bufferedIds.add(entry.chunkId);\n }\n if (flushAll) {\n for (const entry of buffer) persistPending(entry, \"evicted\");\n finalFailure ??= error;\n }\n report(error, batch[0].event);\n if (finalFailure !== undefined) throw finalFailure;\n if (quarantinedId !== undefined) continue;\n return;\n }\n // The batch is durable, so no rollback can need the conversations it\n // closed. Drop them: retaining every conversation for the daemon's\n // lifetime would make each checkpoint above O(capture history), and the\n // per-chunk work quadratic (issue #2145).\n deps.assembler.pruneFinalized();\n }\n }\n\n function enqueue(event: ChunkEvent): void {\n tail = tail.then(() => process(event)).catch((error) => report(error, event));\n }\n\n function drain(): Promise<void> {\n return tail.then(() => undefined);\n }\n\n async function finalize(): Promise<number> {\n // Flush the reorder buffer ON the serialized chain, so a chunk still\n // arriving cannot interleave with the flush.\n // `report` runs an operator-supplied callback that may itself throw, so the\n // chain is recovered explicitly: a rejected `tail` would make every later\n // drain/finalize reject with the same stale error (AGENTS.md #28).\n const flush = tail.then(() => releaseReady(true));\n tail = flush.catch(() => undefined);\n // A failed flush must NOT skip the sweep: shutdown flips whatever is left\n // to `final`, and skipping would serve conversations that were never\n // deduped or diarized. Run the sweep, then report the flush failure.\n let flushFailure: unknown;\n try {\n await flush;\n } catch (error) {\n flushFailure = error;\n }\n // A failed flush left chunks in the buffer that may belong to the open\n // conversation. Closing it now would strand them: a retry or a raw-WAV\n // replay would open a NEW conversation instead of joining the original.\n // The sweep below still dedupes and diarizes, so anything a later\n // shutdown flips is clean.\n const holdOpen = flushFailure !== undefined;\n if (!holdOpen) deps.assembler.finalize();\n // Dedup then cluster EVERY still-capturing conversation before the bulk\n // flip to final — including one left by a crashed prior run that this\n // process never touched (so it has no in-memory openConversationId) — so\n // the served (final-only) output never contains a loopback duplicate and\n // every surviving segment carries its speaker.\n let sweepFailure: unknown;\n let closed = 0;\n for (const id of deps.spool.capturingConversationIds()) {\n try {\n dedupeConversation(id);\n // Diarization is deferred whenever segments are still outstanding — a\n // failed flush OR a chunk retained for replay: clustering only ever\n // ADDS, so embedding a mic copy whose system duplicate has not arrived\n // yet would leave that copy's contribution in the centroid after dedup\n // deletes the segment.\n if (!holdOpen && !isHeldForReplay(id)) diarizeConversation(id);\n // Flipped per conversation, not in bulk: a conversation whose\n // diarization failed must STAY capturing so a later finalize retries\n // it, while the ones that succeeded still reach the final-only read\n // path instead of being stranded behind it.\n if (!holdOpen && !isHeldForReplay(id) && deps.spool.finalizeConversation(id)) closed++;\n } catch (error) {\n sweepFailure ??= error;\n }\n }\n // The bulk flip only catches conversations created after the id snapshot\n // above. Skipping it when the sweep failed is what keeps the FAILED\n // conversation `capturing` for the next finalize to retry.\n // The bulk flip is unconditional in the spool, so it must not run while a\n // conversation is held for replay — it would flip exactly the prefix the\n // sweep above deliberately skipped.\n const total =\n sweepFailure === undefined && !holdOpen && retainedChunks.size === 0\n ? closed + deps.spool.finalizeOpenConversations()\n : closed;\n if (flushFailure !== undefined) throw flushFailure;\n if (sweepFailure !== undefined) throw sweepFailure;\n return total;\n }\n\n return { enqueue, drain, finalize };\n}\n","import { existsSync, lstatSync, readdirSync } from \"node:fs\";\nimport path from \"node:path\";\n\nimport { chunkStableId } from \"./processor.js\";\nimport type { ChunkEvent } from \"./native.js\";\nimport type { Spool } from \"./spool.js\";\n\nconst DEFAULT_CHUNK_MS = 30_000;\n\nexport interface OrphanScanInput {\n rawDirectory: string;\n spool: Spool;\n}\n\n/**\n * Rebuild chunk events from durable pending rows and leftover WAVs so a\n * restart can feed them through the live processor (issue #2379).\n */\nexport function scanOrphanedChunks(input: OrphanScanInput): ChunkEvent[] {\n const recovered = new Map<string, ChunkEvent>();\n const quarantined = new Set(input.spool.listPendingChunks(\"quarantined\").map((row) => row.id));\n\n for (const row of input.spool.listPendingChunks(\"evicted\")) {\n if (quarantined.has(row.id)) continue;\n if (input.spool.isChunkApplied(`${row.id}:done`)) continue;\n if (!existsSync(row.wavPath)) continue;\n recovered.set(row.id, {\n path: row.wavPath,\n channel: row.channel,\n startedAtUtc: row.startedAtUtc,\n endedAtUtc: row.endedAtUtc,\n device: row.device,\n });\n }\n\n let root;\n try {\n root = lstatSync(input.rawDirectory);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return [...recovered.values()].sort(byStart);\n }\n throw error;\n }\n if (root.isSymbolicLink() || !root.isDirectory()) return [...recovered.values()].sort(byStart);\n\n for (const name of readdirSync(input.rawDirectory)) {\n if (!name.endsWith(\".wav\")) continue;\n const location = path.join(input.rawDirectory, name);\n let stat;\n try {\n stat = lstatSync(location);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") continue;\n throw error;\n }\n if (!stat.isFile()) continue;\n const event: ChunkEvent = {\n path: location,\n channel: \"mic\",\n startedAtUtc: new Date(stat.mtimeMs - DEFAULT_CHUNK_MS).toISOString(),\n endedAtUtc: new Date(stat.mtimeMs).toISOString(),\n device: null,\n };\n const id = chunkStableId(event);\n if (quarantined.has(id) || recovered.has(id)) continue;\n if (input.spool.isChunkApplied(`${id}:done`)) continue;\n recovered.set(id, event);\n }\n\n return [...recovered.values()].sort(byStart);\n}\n\nfunction byStart(left: ChunkEvent, right: ChunkEvent): number {\n if (left.startedAtUtc !== right.startedAtUtc) return left.startedAtUtc < right.startedAtUtc ? -1 : 1;\n return left.path < right.path ? -1 : left.path > right.path ? 1 : 0;\n}\n","import { statSync } from \"node:fs\";\nimport { spawn } from \"node:child_process\";\n\nimport { expandTilde } from \"./paths.js\";\nimport { CaptureConfigError } from \"./errors.js\";\n\nexport interface TranscribedSegment {\n text: string;\n startUtc: string;\n endUtc: string;\n}\n\nexport interface WhisperRunResult {\n code: number;\n stdout: string;\n stderr: string;\n}\n\nexport interface WhisperTranscriptionInput {\n wavPath: string;\n modelPath: string;\n chunkStartedAtUtc: string;\n threads?: number | null;\n run: (command: string, args: string[]) => Promise<WhisperRunResult>;\n}\n\ninterface WhisperSegment {\n text?: unknown;\n offsets?: { from?: unknown; to?: unknown };\n}\n\nfunction isRegularFile(filePath: string): boolean {\n try {\n return statSync(filePath).isFile();\n } catch {\n return false;\n }\n}\n\nfunction timestampAt(chunkStartedAtUtc: string, offsetMs: number): string {\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{1,2}):(\\d{2}):(\\d{2})(?:\\.(\\d{1,3}))?Z$/.exec(chunkStartedAtUtc);\n if (!match) {\n throw new CaptureConfigError(\"chunk start timestamp is invalid\");\n }\n const [, year, month, day, hour, minute, second, millisecond = \"0\"] = match;\n const startMs = Date.UTC(\n Number(year),\n Number(month) - 1,\n Number(day),\n Number(hour),\n Number(minute),\n Number(second),\n Number(millisecond.padEnd(3, \"0\")),\n );\n const start = new Date(startMs);\n if (\n start.getUTCFullYear() !== Number(year) ||\n start.getUTCMonth() !== Number(month) - 1 ||\n start.getUTCDate() !== Number(day) ||\n start.getUTCHours() !== Number(hour) ||\n start.getUTCMinutes() !== Number(minute) ||\n start.getUTCSeconds() !== Number(second) ||\n start.getUTCMilliseconds() !== Number(millisecond.padEnd(3, \"0\"))\n ) {\n throw new CaptureConfigError(\"chunk start timestamp is invalid\");\n }\n const timestampMs = startMs + offsetMs;\n if (!Number.isFinite(timestampMs) || Math.abs(timestampMs) > 8.64e15) {\n throw new CaptureConfigError(\"whisper-cli segment offset produces an invalid timestamp\");\n }\n return new Date(timestampMs).toISOString();\n}\n\nexport function parseWhisperJson(output: string, chunkStartedAtUtc: string): TranscribedSegment[] {\n let parsed: unknown;\n try {\n parsed = JSON.parse(output);\n } catch {\n throw new CaptureConfigError(\"whisper-cli returned malformed JSON\");\n }\n if (!parsed || typeof parsed !== \"object\" || !Array.isArray((parsed as { transcription?: unknown }).transcription)) {\n throw new CaptureConfigError(\"whisper-cli JSON must contain a transcription array\");\n }\n\n return (parsed as { transcription: unknown[] }).transcription.map((value, index) => {\n if (!value || typeof value !== \"object\") {\n throw new CaptureConfigError(`whisper-cli transcription[${index}] is invalid`);\n }\n const segment = value as WhisperSegment;\n if (\n typeof segment.text !== \"string\" ||\n segment.text.trim() === \"\" ||\n !segment.offsets ||\n typeof segment.offsets.from !== \"number\" ||\n typeof segment.offsets.to !== \"number\" ||\n !Number.isFinite(segment.offsets.from) ||\n !Number.isFinite(segment.offsets.to) ||\n segment.offsets.from < 0 ||\n segment.offsets.to < segment.offsets.from\n ) {\n throw new CaptureConfigError(`whisper-cli transcription[${index}] is invalid`);\n }\n return {\n text: segment.text.trim(),\n startUtc: timestampAt(chunkStartedAtUtc, segment.offsets.from),\n endUtc: timestampAt(chunkStartedAtUtc, segment.offsets.to),\n };\n });\n}\n\nexport function resolveModelPath(\n configuredPath: string | undefined,\n defaultPath: string,\n exists: (path: string) => boolean = isRegularFile,\n): string {\n const modelPath = expandTilde(configuredPath?.trim() || defaultPath);\n if (!exists(modelPath)) {\n throw new CaptureConfigError(\n `whisper model not found at ${modelPath}; run 'remnic-capture-audio download-model --model base' or set stt.modelPath`,\n );\n }\n return modelPath;\n}\n\nexport function buildWhisperArgs(wavPath: string, modelPath: string, threads?: number | null): string[] {\n const args = [\"-m\", modelPath, \"-f\", wavPath, \"--no-prints\", \"--output-json\", \"--output-file\", \"-\"];\n if (typeof threads === \"number\" && Number.isInteger(threads) && threads > 0) {\n args.push(\"-t\", String(threads));\n }\n return args;\n}\n\nexport async function transcribeWithWhisper(input: WhisperTranscriptionInput): Promise<TranscribedSegment[]> {\n const result = await input.run(\"whisper-cli\", buildWhisperArgs(input.wavPath, input.modelPath, input.threads));\n if (result.code !== 0) {\n throw new CaptureConfigError(`whisper-cli failed with exit code ${result.code}`);\n }\n return parseWhisperJson(result.stdout, input.chunkStartedAtUtc);\n}\n\nexport function runWhisperCli(command: string, args: string[]): Promise<WhisperRunResult> {\n return new Promise((resolve, reject) => {\n const child = spawn(command, args, { shell: false, stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n const stdout: Buffer[] = [];\n const stderr: Buffer[] = [];\n child.stdout.on(\"data\", (chunk: Buffer) => stdout.push(chunk));\n child.stderr.on(\"data\", (chunk: Buffer) => stderr.push(chunk));\n child.once(\"error\", (error) => {\n const code = (error as NodeJS.ErrnoException).code;\n if (code === \"ENOENT\") {\n reject(\n new CaptureConfigError(\n `whisper-cli executable '${command}' not found on PATH; install whisper.cpp or configure its path before transcribing`,\n ),\n );\n return;\n }\n reject(new CaptureConfigError(`failed to launch whisper-cli '${command}'${code ? ` (${code})` : \"\"}`));\n });\n child.once(\"close\", (code) => {\n resolve({\n code: code ?? 1,\n stdout: Buffer.concat(stdout).toString(\"utf8\"),\n stderr: Buffer.concat(stderr).toString(\"utf8\"),\n });\n });\n });\n}\n","/**\n * Live capture wiring (issue #1897) — assembles the native helper runner and\n * the chunk processor into one start/stop unit the daemon drives.\n *\n * The native runner owns the helper process and surfaces validated\n * `ChunkEvent`s; the processor turns each recorded WAV into durable, replay-safe\n * conversations in the spool. This module wires them with production defaults\n * (whisper STT, model resolution, raw-audio cleanup) while keeping every\n * collaborator injectable, so tests drive the whole pipeline against a fake\n * helper binary and a fake transcriber without any macOS runtime.\n */\n\nimport { realpathSync } from \"node:fs\";\nimport { rm } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { ConversationAssembler } from \"./assembly.js\";\nimport { SpeakerClusterer, type Embedding } from \"./diarization.js\";\nimport type { DaemonConfig } from \"./config.js\";\nimport { CaptureInputError } from \"./errors.js\";\nimport {\n createNativeCaptureRunner,\n type ChunkEvent,\n type HelperResolution,\n type HelperSpawn,\n type NativeCaptureRunner,\n type ResolveHelperDeps,\n type RestartTimer,\n} from \"./native.js\";\nimport { scanOrphanedChunks } from \"./orphan-scan.js\";\nimport { createChunkProcessor, type ChunkProcessor, type ChunkTranscribeInput } from \"./processor.js\";\nimport type { Spool } from \"./spool.js\";\nimport { resolveModelPath, runWhisperCli, transcribeWithWhisper, type TranscribedSegment } from \"./stt.js\";\n\nexport interface LiveCaptureOptions {\n spool: Spool;\n config: DaemonConfig;\n /** Directory the helper writes WAV chunks into (`audio-capture --out`). */\n outDir: string;\n /** Default whisper model path when `config.stt.modelPath` is unset. */\n defaultModelPath: string;\n onError?: (error: Error) => void;\n onStderr?: (line: string) => void;\n\n // Injectable seams (tests) — real defaults are built from `config` below.\n spawn?: HelperSpawn;\n resolveBinary?: (deps: ResolveHelperDeps) => HelperResolution;\n resolution?: HelperResolution;\n transcribe?: (input: ChunkTranscribeInput) => Promise<TranscribedSegment[]>;\n resolveModel?: () => string;\n cleanupRawAudio?: (event: ChunkEvent) => Promise<void>;\n scheduleRestart?: (fn: () => void, delayMs: number) => RestartTimer;\n cancelRestart?: (timer: RestartTimer) => void;\n makeConversationId?: () => string;\n /** VAD speech gate seam; production supplies a sherpa-onnx detector. */\n detectSpeech?: (event: ChunkEvent) => boolean | Promise<boolean>;\n /** Speaker-embedding seam for diarization; production supplies sherpa speaker-id. */\n embed?: (event: ChunkEvent, segment: TranscribedSegment) => Embedding | Promise<Embedding>;\n}\n\nexport interface LiveCapture {\n start(): void;\n /** Stop the helper, then drain + finalize the processor. */\n stop(): Promise<number>;\n readonly running: boolean;\n /** Test/observability seam. */\n readonly processor: ChunkProcessor;\n}\n\n/** Wire the native runner + chunk processor into one live-capture unit. */\nexport function createLiveCapture(options: LiveCaptureOptions): LiveCapture {\n const { spool, config, outDir, defaultModelPath } = options;\n\n const resolveModel =\n options.resolveModel ?? (() => resolveModelPath(config.stt.modelPath ?? undefined, defaultModelPath));\n\n const transcribe =\n options.transcribe ??\n ((input: ChunkTranscribeInput) =>\n transcribeWithWhisper({\n wavPath: input.wavPath,\n modelPath: input.modelPath,\n chunkStartedAtUtc: input.chunkStartedAtUtc,\n threads: config.stt.threads,\n run: runWhisperCli,\n }));\n\n const rawBase = path.resolve(outDir);\n // Resolve symlinks so a symlinked chunk path can't escape the raw dir; fall\n // back to the lexical path when the target doesn't exist yet (tests, or a\n // chunk whose WAV was already removed).\n const realOrResolved = (p: string): string => {\n try {\n return realpathSync(path.resolve(p));\n } catch {\n return path.resolve(p);\n }\n };\n const rawBaseReal = realOrResolved(rawBase);\n const withinRawDir = (p: string): boolean => {\n const real = realOrResolved(p);\n return real === rawBaseReal || real.startsWith(rawBaseReal + path.sep);\n };\n const cleanupRawAudio =\n options.cleanupRawAudio ??\n (async (event: ChunkEvent): Promise<void> => {\n // Retention 0 = keep no raw audio: delete the WAV once its chunk is\n // durably persisted. A positive retention leaves it for the janitor.\n if (config.rawRetentionHours > 0) return;\n // event.path is helper-supplied; never delete anything outside the raw dir.\n if (!withinRawDir(event.path)) return;\n await rm(path.resolve(event.path), { force: true });\n });\n\n const assembler = new ConversationAssembler({\n gapMinutes: config.conversationGapMinutes,\n ...(options.makeConversationId ? { makeId: options.makeConversationId } : {}),\n });\n\n // Diarization is active only when an embedder is wired (production: the\n // sherpa speaker-id model from the native layer; tests inject a fake). Seed\n // the clusterer from persisted clusters so speaker ids survive restarts.\n const diarizer = options.embed\n ? new SpeakerClusterer(config.diarization.similarityThreshold, spool.readSpeakerClusters())\n : undefined;\n\n const processor = createChunkProcessor({\n spool,\n assembler,\n resolveModel,\n transcribe,\n cleanupRawAudio,\n // Cross-channel arrival skew: hold each transcribed chunk until the\n // watermark passes it, so a delayed system chunk still assembles into the\n // conversation it belongs to (issue #2145).\n // Only \"both\" has two independent chunk streams to interleave. A single\n // channel is already ordered, so buffering would add latency and widen the\n // crash window for nothing (issue #2145).\n reorderWindowMs: config.captureChannel === \"both\" ? config.reorderWindowSeconds * 1000 : 0,\n ...(options.detectSpeech ? { detectSpeech: options.detectSpeech } : {}),\n ...(options.embed ? { embed: options.embed } : {}),\n ...(diarizer ? { diarizer } : {}),\n ...(options.onError ? { onError: (error: Error) => options.onError?.(error) } : {}),\n });\n\n const runner: NativeCaptureRunner = createNativeCaptureRunner({\n outDir,\n chunkSeconds: config.chunkSeconds,\n // Channels to record (config.captureChannel, default \"both\"). With \"both\",\n // the processor's finalize-time cross-channel dedup drops mic segments that\n // duplicate system (loopback) speech, so it is stored once (the cleaner\n // system copy). Operators without system-audio permission can set \"mic\" so\n // microphone capture never depends on the system-audio path being available.\n channel: config.captureChannel,\n device: config.devices.mic,\n onChunk: (event) => {\n // Reject a helper-supplied path that escapes the raw capture dir BEFORE it\n // is read for transcription — resolving symlinks, so a symlink under the\n // dir can't point the transcriber at an arbitrary file.\n if (!withinRawDir(event.path)) {\n options.onError?.(new CaptureInputError(`native helper chunk path escapes the capture directory: ${event.path}`));\n return;\n }\n processor.enqueue({ ...event, path: path.resolve(event.path) });\n },\n ...(options.onError ? { onError: options.onError } : {}),\n ...(options.onStderr ? { onStderr: options.onStderr } : {}),\n ...(options.resolution ? { resolution: options.resolution } : {}),\n ...(options.resolveBinary ? { resolveBinary: options.resolveBinary } : {}),\n ...(options.spawn ? { spawn: options.spawn } : {}),\n ...(options.scheduleRestart ? { scheduleRestart: options.scheduleRestart } : {}),\n ...(options.cancelRestart ? { cancelRestart: options.cancelRestart } : {}),\n });\n\n return {\n get running(): boolean {\n return runner.running;\n },\n processor,\n start(): void {\n // Pre-flight the STT model once so a missing/unreadable model fails fast\n // (actionable) here instead of throwing on every captured chunk.\n resolveModel();\n for (const event of scanOrphanedChunks({ rawDirectory: outDir, spool })) {\n processor.enqueue(event);\n }\n runner.start();\n },\n async stop(): Promise<number> {\n // Await the helper's exit so its final flushed chunk is enqueued before\n // we drain and finalize.\n await runner.stop();\n return processor.finalize();\n },\n };\n}\n","/**\n * `enroll-self` (issue #1897) — register the wearer as the `self` speaker so\n * the diarizer and downstream attribution can distinguish the wearer's own\n * voice from everyone else's.\n *\n * Enrollment stores a durable `self` speaker row. When a voice embedding is\n * supplied (extracted from a recorded sample by a speaker-embedding model —\n * à-la-carte, like whisper STT and the Silero VAD), it is stored as the self\n * centroid so live diarization can match against it. Without an embedder the\n * wearer's identity is still registered (embedding refinement lands with the\n * diarization slice), so the pipeline can already tag `desktop:self`.\n *\n * Pure over its injected `spool`: the caller owns recording the sample and\n * extracting the embedding, so no optional native/model runtime is imported.\n */\n\nimport { SpeakerClusterer, type Embedding } from \"./diarization.js\";\nimport { CaptureConfigError } from \"./errors.js\";\nimport type { Spool } from \"./spool.js\";\n\n/** The stable speaker id for the enrolled wearer. */\nexport const SELF_SPEAKER_ID = \"self\";\n\nexport interface EnrollSelfInput {\n spool: Spool;\n /** Human label for the wearer; defaults to \"You\". */\n label?: string | null;\n /** Optional wearer voice embedding; when present it is stored as the self centroid. */\n embedding?: Embedding;\n}\n\nexport interface EnrollSelfResult {\n speakerId: string;\n label: string | null;\n hasEmbedding: boolean;\n dimensions: number;\n}\n\n/**\n * Register (or refresh) the `self` speaker. With an embedding, the canonical\n * self cluster (centroid + example) is persisted; without one, only the\n * identity is upserted, preserving any embedding a prior enroll stored.\n */\nexport function enrollSelf(input: EnrollSelfInput): EnrollSelfResult {\n const label = input.label ?? \"You\";\n\n if (input.embedding !== undefined) {\n if (!Array.isArray(input.embedding) || input.embedding.length === 0) {\n throw new CaptureConfigError(\"enroll-self embedding must be a non-empty number array\");\n }\n for (const value of input.embedding) {\n if (typeof value !== \"number\" || !Number.isFinite(value)) {\n throw new CaptureConfigError(\"enroll-self embedding must contain only finite numbers\");\n }\n }\n // Build the canonical self cluster through the diarizer so the stored shape\n // (id/centroid/examples/count) matches what live diarization seeds from.\n const clusterer = new SpeakerClusterer(0.5);\n clusterer.enrollSelf(input.embedding);\n const self = clusterer.clusters().find((c) => c.isSelf);\n if (!self) {\n throw new CaptureConfigError(\"enroll-self failed to build the self speaker cluster\");\n }\n input.spool.upsertSpeaker({\n id: self.id,\n isSelf: true,\n label,\n embeddingCount: self.embeddingCount,\n centroid: self.centroid,\n examples: self.examples,\n });\n return { speakerId: self.id, label, hasEmbedding: true, dimensions: input.embedding.length };\n }\n\n input.spool.upsertSpeaker({ id: SELF_SPEAKER_ID, isSelf: true, label });\n // upsertSpeaker preserves any prior centroid; report what is actually stored\n // so a relabel doesn't falsely claim no embedding exists.\n const stored = input.spool.readSpeakerClusters().find((c) => c.id === SELF_SPEAKER_ID);\n const dimensions = stored?.centroid.length ?? 0;\n return { speakerId: SELF_SPEAKER_ID, label, hasEmbedding: dimensions > 0, dimensions };\n}\n","/**\n * `install-service` support (issue #1897) — render and install a per-user\n * background service that runs the capture-audio daemon in live-capture mode.\n *\n * macOS uses a launchd LaunchAgent (`~/Library/LaunchAgents/<label>.plist`);\n * Linux uses a systemd user unit (`~/.config/systemd/user/<name>.service`).\n * The renderers are pure (deterministic strings) and `installService` /\n * `uninstallService` take injected filesystem + environment seams so the whole\n * surface is testable without touching the real user launch directories.\n */\n\nimport path from \"node:path\";\n\nimport { CaptureConfigError } from \"./errors.js\";\n\nexport const DEFAULT_SERVICE_LABEL = \"com.remnic.capture-audio\";\nconst SYSTEMD_UNIT_NAME = \"remnic-capture-audio.service\";\n\nexport interface ServiceSpec {\n /** argv that launches the daemon, e.g. [node, cliEntry, \"start\", \"--foreground\", \"--capture\"]. */\n programArguments: string[];\n logPath: string;\n label?: string;\n /** Env vars the launched daemon needs (e.g. PATH, REMNIC_CAPTURE_HELPER_BIN). */\n environment?: Record<string, string>;\n}\n\nexport interface ServicePlan {\n platform: NodeJS.Platform;\n /** Absolute path the unit file is written to. */\n path: string;\n contents: string;\n /** One-line operator instruction to load/enable the service. */\n loadHint: string;\n}\n\nfunction xmlEscape(value: string): string {\n return value\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n\nconst SAFE_LABEL = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;\n\n/** Reject a label that could escape the unit filename or inject unit content. */\nfunction validateLabel(label: string): string {\n if (!SAFE_LABEL.test(label)) {\n throw new CaptureConfigError(\n `install-service label must match ${SAFE_LABEL.source} (got: ${JSON.stringify(label)})`,\n );\n }\n return label;\n}\n\n/** Render a launchd LaunchAgent plist that keeps the daemon alive at login. */\nexport function renderLaunchAgent(spec: ServiceSpec): string {\n const label = spec.label ?? DEFAULT_SERVICE_LABEL;\n const args = spec.programArguments.map((a) => ` <string>${xmlEscape(a)}</string>`).join(\"\\n\");\n const envEntries = Object.entries(spec.environment ?? {})\n .map(([k, v]) => ` <key>${xmlEscape(k)}</key>\\n <string>${xmlEscape(v)}</string>`)\n .join(\"\\n\");\n const envBlock = envEntries === \"\" ? \"\" : ` <key>EnvironmentVariables</key>\\n <dict>\\n${envEntries}\\n </dict>\\n`;\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>${xmlEscape(label)}</string>\n <key>ProgramArguments</key>\n <array>\n${args}\n </array>\n${envBlock} <key>RunAtLoad</key>\n <true/>\n <key>KeepAlive</key>\n <true/>\n <key>ProcessType</key>\n <string>Background</string>\n <key>StandardOutPath</key>\n <string>${xmlEscape(spec.logPath)}</string>\n <key>StandardErrorPath</key>\n <string>${xmlEscape(spec.logPath)}</string>\n</dict>\n</plist>\n`;\n}\n\n/** systemd quotes args that contain whitespace or its quote/backslash chars. */\nfunction systemdArg(value: string): string {\n // systemd treats % as a specifier introducer; escape it regardless of quoting.\n const escaped = value.replace(/%/g, \"%%\");\n if (escaped === \"\" || /[\\s\"'\\\\]/.test(escaped)) {\n return `\"${escaped.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"')}\"`;\n }\n return escaped;\n}\n\n/** Render a systemd user unit that restarts the daemon on failure. */\nexport function renderSystemdUnit(spec: ServiceSpec): string {\n const execStart = spec.programArguments.map(systemdArg).join(\" \");\n const logPath = spec.logPath.replace(/%/g, \"%%\");\n const envLines = Object.entries(spec.environment ?? {})\n .map(([k, v]) => `Environment=${systemdArg(`${k}=${v}`)}`)\n .join(\"\\n\");\n const envBlock = envLines === \"\" ? \"\" : `${envLines}\\n`;\n return `[Unit]\nDescription=Remnic desktop audio capture daemon\nAfter=default.target\n\n[Service]\nType=simple\nExecStart=${execStart}\nStandardOutput=append:${logPath}\nStandardError=append:${logPath}\n${envBlock}Restart=on-failure\nRestartSec=5\n\n[Install]\nWantedBy=default.target\n`;\n}\n\nexport interface PlanServiceDeps {\n platform: NodeJS.Platform;\n home: string;\n spec: ServiceSpec;\n}\n\n/** Decide the unit file path + contents for the current platform. */\nexport function planService(deps: PlanServiceDeps): ServicePlan {\n const label = validateLabel(deps.spec.label ?? DEFAULT_SERVICE_LABEL);\n if (deps.platform === \"darwin\") {\n const target = path.join(deps.home, \"Library\", \"LaunchAgents\", `${label}.plist`);\n return {\n platform: deps.platform,\n path: target,\n contents: renderLaunchAgent(deps.spec),\n loadHint: `launchctl load ${target}`,\n };\n }\n if (deps.platform === \"linux\") {\n // Honor a custom --label so distinct installs get distinct units; the\n // default keeps the clean canonical unit name.\n const unitName = deps.spec.label ? `${label}.service` : SYSTEMD_UNIT_NAME;\n const target = path.join(deps.home, \".config\", \"systemd\", \"user\", unitName);\n return {\n platform: deps.platform,\n path: target,\n contents: renderSystemdUnit(deps.spec),\n loadHint: `systemctl --user enable --now ${unitName}`,\n };\n }\n throw new CaptureConfigError(`install-service is unsupported on platform \"${deps.platform}\"`);\n}\n\nexport interface InstallServiceDeps extends PlanServiceDeps {\n mkdir: (dir: string) => void;\n writeFile: (file: string, contents: string) => void;\n /** True to overwrite an already-installed unit. */\n force?: boolean;\n exists?: (file: string) => boolean;\n}\n\n/** Write the planned unit file, creating its directory. Returns the plan. */\nexport function installService(deps: InstallServiceDeps): ServicePlan {\n const plan = planService(deps);\n if (!deps.force && deps.exists?.(plan.path)) {\n throw new CaptureConfigError(`a capture-audio service is already installed at ${plan.path} (use --force to replace)`);\n }\n deps.mkdir(path.dirname(plan.path));\n deps.writeFile(plan.path, plan.contents);\n return plan;\n}\n\nexport interface UninstallServiceDeps extends PlanServiceDeps {\n remove: (file: string) => void;\n exists: (file: string) => boolean;\n}\n\n/** Remove the installed unit file. Returns the plan + whether a file was removed. */\nexport function uninstallService(deps: UninstallServiceDeps): { plan: ServicePlan; removed: boolean } {\n const plan = planService(deps);\n if (!deps.exists(plan.path)) return { plan, removed: false };\n deps.remove(plan.path);\n return { plan, removed: true };\n}\n","/**\n * `remnic-capture-audio` CLI. Subcommands: init, start, stop, status,\n * devices, logs. `start --replay <dir>` feeds synthetic fixtures through\n * the spool + HTTP API (the CI-friendly, hardware-free path). Native\n * device enumeration and real capture arrive in later checklist items;\n * `devices` reports that honestly rather than faking a device list.\n */\n\nimport { spawn } from \"node:child_process\";\nimport { chmodSync, existsSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { setTimeout as delay } from \"node:timers/promises\";\n\nimport { CAPTURE_AUDIO_VERSION } from \"./constants.js\";\nimport { coerceNumber } from \"./coerce.js\";\nimport {\n defaultDaemonConfig,\n loadDaemonConfig,\n serializeDaemonConfig,\n type DaemonConfig,\n} from \"./config.js\";\nimport {\n isProcessAlive,\n readPidRecord,\n removePidFile,\n removePidFileIfOwner,\n writePidFile,\n type PidRecord,\n} from \"./control.js\";\nimport { startDaemon, type DaemonHandle } from \"./daemon.js\";\nimport { CaptureConfigError, CaptureInputError } from \"./errors.js\";\nimport { capturePaths, captureBaseDir, expandTilde, type CapturePaths } from \"./paths.js\";\nimport { ingestReplayDirResponsive } from \"./replay.js\";\nimport { downloadWhisperModel, type ModelDownloadInput, type ModelDownloadResult } from \"./model.js\";\nimport { pruneExpiredRawAudio } from \"./janitor.js\";\nimport { Spool } from \"./spool.js\";\nimport { loadOrCreateToken } from \"./token.js\";\nimport { formatHostForUrl, isLoopbackHost, stripIpv6Brackets } from \"./util.js\";\nimport { homedir } from \"node:os\";\n\nimport { createLiveCapture, type LiveCapture } from \"./capture.js\";\nimport { enrollSelf } from \"./enroll.js\";\nimport { enumerateDevices, resolveHelperBinary } from \"./native.js\";\nimport { installService, uninstallService } from \"./service.js\";\n\nexport interface CliIo {\n argv: string[];\n env?: NodeJS.ProcessEnv;\n stdout?: (line: string) => void;\n downloadModel?: (input: ModelDownloadInput) => Promise<ModelDownloadResult>;\n stderr?: (line: string) => void;\n /**\n * argv tokens (after the node executable) that re-launch THIS CLI, used\n * when the daemon backgrounds itself into `--foreground`. Defaults to\n * [process.argv[1]] (direct `remnic-capture-audio` invocation). The\n * `remnic capture audio` passthrough supplies [remnicBin, \"capture\",\n * \"audio\"] so the detached child is `remnic capture audio start\n * --foreground`, not `remnic start --foreground`.\n */\n spawnArgvPrefix?: string[];\n}\n\ninterface ParsedArgs {\n command: string;\n positionals: string[];\n flags: Record<string, string | boolean>;\n}\n\n/** Flags that consume the next argv token as their value. */\nconst VALUE_FLAGS: Record<string, true> = {\n replay: true,\n host: true,\n port: true,\n listen: true,\n \"base-dir\": true,\n model: true,\n lines: true,\n label: true,\n};\n\n/** Standalone boolean flags. Any other `--flag` is rejected loudly. */\nconst BOOLEAN_FLAGS: Record<string, true> = {\n foreground: true,\n force: true,\n help: true,\n capture: true,\n uninstall: true,\n};\n\n/** Non-global flags each subcommand accepts; anything else is rejected. */\nconst COMMAND_FLAGS: Record<string, Record<string, true>> = {\n init: { force: true },\n start: { foreground: true, replay: true, host: true, port: true, listen: true, capture: true },\n stop: { force: true },\n status: {},\n devices: {},\n logs: { lines: true },\n \"download-model\": { model: true },\n janitor: {},\n \"install-service\": { force: true, uninstall: true, label: true },\n \"enroll-self\": { label: true },\n help: {},\n};\n\n/** Flags accepted regardless of subcommand. */\nconst GLOBAL_FLAGS: Record<string, true> = { \"base-dir\": true, help: true };\n\n/** How long background start waits for the child daemon to bind before failing. */\nconst READINESS_TIMEOUT_MS = 10_000;\n\n/** How long `stop` waits for the daemon to exit + remove its pid file before returning. */\nconst STOP_TIMEOUT_MS = 10_000;\n\nfunction parseArgs(argv: string[]): ParsedArgs {\n const tokens: string[] = [];\n const flags: Record<string, string | boolean> = {};\n // Collect flags anywhere (so global flags may precede the subcommand); the\n // first non-flag token is the command, the rest are positionals.\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n if (arg.startsWith(\"--\")) {\n const key = arg.slice(2);\n if (Object.hasOwn(VALUE_FLAGS, key)) {\n const next = argv[i + 1];\n if (next === undefined || next.startsWith(\"--\")) {\n throw new CaptureInputError(`flag --${key} requires a value`);\n }\n flags[key] = next;\n i += 1;\n } else if (Object.hasOwn(BOOLEAN_FLAGS, key)) {\n flags[key] = true;\n } else {\n throw new CaptureInputError(`unknown flag --${key}`);\n }\n } else {\n tokens.push(arg);\n }\n }\n const command = tokens.length > 0 ? tokens[0] : \"help\";\n return { command, positionals: tokens.slice(1), flags };\n}\n\nfunction resolvePaths(flags: Record<string, string | boolean>, env: NodeJS.ProcessEnv): CapturePaths {\n const baseDir =\n typeof flags[\"base-dir\"] === \"string\"\n ? captureBaseDir({ ...env, REMNIC_CAPTURE_DIR: flags[\"base-dir\"] })\n : captureBaseDir(env);\n return capturePaths(baseDir);\n}\n\nfunction loadConfigOrDefault(paths: CapturePaths, stderr: (line: string) => void): DaemonConfig {\n if (existsSync(paths.configPath)) return loadDaemonConfig(paths.configPath);\n stderr(`no config at ${paths.configPath}; using defaults (run \\`init\\` to customize)`);\n return defaultDaemonConfig();\n}\n\nfunction applyBindingOverrides(\n config: DaemonConfig,\n flags: Record<string, string | boolean>,\n): DaemonConfig {\n const next = { ...config };\n if (typeof flags.listen === \"string\") {\n const idx = flags.listen.lastIndexOf(\":\");\n if (idx <= 0) throw new CaptureInputError(`--listen expects host:port, got '${flags.listen}'`);\n next.host = flags.listen.slice(0, idx);\n next.port = coerceNumber(flags.listen.slice(idx + 1), \"--listen port\", { integer: true, min: 1, max: 65535 });\n }\n if (typeof flags.host === \"string\") next.host = flags.host;\n if (typeof flags.port === \"string\") {\n next.port = coerceNumber(flags.port, \"--port\", { integer: true, min: 1, max: 65535 });\n }\n // Normalize a bracketed IPv6 authority host ([::1]) to its bare form (::1) so\n // the loopback check and the actual listen() bind both see a valid address.\n next.host = stripIpv6Brackets(next.host);\n return next;\n}\n\nfunction healthUrlFor(host: string, port: number): string {\n return `http://${formatHostForUrl(host)}:${port}/v1/health`;\n}\n\n/**\n * Health URL for the running daemon: prefer the effective binding persisted\n * in the pid record, falling back to on-disk config. This lets status/stop\n * reach a daemon started with --host/--port/--listen even when the config\n * file on disk says something else.\n */\nfunction recordHealthUrl(record: PidRecord, paths: CapturePaths, stderr: (l: string) => void): string {\n if (record.host !== null && record.port !== null) return healthUrlFor(record.host, record.port);\n const config = loadConfigOrDefault(paths, stderr);\n return healthUrlFor(config.host, config.port);\n}\n\nfunction tokenHeader(paths: CapturePaths): Record<string, string> {\n if (!existsSync(paths.tokenPath)) return {};\n return { authorization: `Bearer ${readFileSync(paths.tokenPath, \"utf8\").trim()}` };\n}\n\n/** Create the capture dir owner-only (transcript spool + token live here). */\nfunction ensurePrivateDir(dir: string): void {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n try {\n chmodSync(dir, 0o700);\n } catch {\n // filesystem without POSIX perms\n }\n}\n\n/** Operator-safe error description — never echoes foreign message text/paths. */\nfunction describeError(err: unknown): string {\n const code = (err as NodeJS.ErrnoException).code;\n if (typeof code === \"string\" && code) return code;\n return err instanceof Error ? err.name : \"unknown error\";\n}\n\ninterface DaemonIdentity {\n instanceId: string;\n pid: number;\n}\n\n/** Authenticated health probe; returns the serving daemon's identity or null. */\nasync function probeIdentity(paths: CapturePaths, url: string): Promise<DaemonIdentity | null> {\n try {\n const res = await fetch(url, { headers: tokenHeader(paths), signal: AbortSignal.timeout(2000) });\n if (!res.ok) return null;\n const body = (await res.json()) as { instanceId?: unknown; pid?: unknown };\n if (typeof body.instanceId === \"string\" && typeof body.pid === \"number\") {\n return { instanceId: body.instanceId, pid: body.pid };\n }\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Persist the background child's pid record; if that write fails, terminate the\n * child so a spawned-but-unrecorded daemon is never orphaned. Returns false\n * (child killed) on failure.\n */\nexport function recordChildPidOrTerminate(\n pid: number,\n paths: CapturePaths,\n binding: { host: string; port: number },\n stderr: (l: string) => void,\n): boolean {\n const existing = readPidRecord(paths.pidPath);\n // If the detached child was scheduled first and already published its OWN\n // full ready record (its pid + a non-null instanceId), don't clobber it with\n // this provisional write. A record for a DIFFERENT pid (or our pid without an\n // instanceId yet) is stale by this point — cmdStart's start guard already\n // refused a real prior daemon — so we overwrite it.\n if (existing !== null && existing.pid === pid && existing.instanceId !== null) {\n return true;\n }\n try {\n writePidFile(paths.pidPath, pid, binding);\n return true;\n } catch (err) {\n try {\n process.kill(pid, \"SIGTERM\");\n } catch {\n // already gone\n }\n stderr(`failed to record daemon pid: ${describeError(err)}; terminated child pid ${pid}`);\n return false;\n }\n}\n\n/**\n * True when the recorded pid is (as far as we can tell) our daemon.\n * Without a recorded instanceId we cannot verify, so we conservatively\n * assume yes and refuse to double-start. With an instanceId we treat the\n * pid as a reused stranger only on a CONFIRMED mismatch from its health\n * endpoint; an unreachable probe stays conservative so we never race a new\n * daemon onto a port a live one may still hold.\n */\nasync function isOwnRunningDaemon(\n record: PidRecord,\n paths: CapturePaths,\n stderr: (l: string) => void,\n): Promise<boolean> {\n if (record.instanceId === null) return true;\n const live = await probeIdentity(paths, recordHealthUrl(record, paths, stderr));\n if (live === null) return true;\n return live.instanceId === record.instanceId && live.pid === record.pid;\n}\n\n/**\n * True when the pid record denotes a DIFFERENT, currently-running daemon that a\n * new start must not run over. A record whose pid is our OWN process (the\n * background parent's prewritten record for this child) is never treated as a\n * running prior daemon, so the child can proceed to bind and overwrite it.\n */\nexport async function recordedDaemonIsRunning(\n record: PidRecord,\n paths: CapturePaths,\n stderr: (l: string) => void,\n): Promise<boolean> {\n if (record.pid === process.pid) return false;\n if (!isProcessAlive(record.pid)) return false;\n return isOwnRunningDaemon(record, paths, stderr);\n}\n\n/**\n * Run replay ingestion as a supervised task AFTER the daemon is ready. Never\n * throws: success/failure is surfaced via the spool's `replay_status` meta\n * (also exposed on /v1/health) and the daemon log, so a failed or slow replay\n * never kills the daemon or retracts its readiness.\n */\nexport async function superviseReplay(\n spool: Spool,\n replayDir: string,\n io: { stdout: (l: string) => void; stderr: (l: string) => void },\n signal?: AbortSignal,\n): Promise<void> {\n // Yield first so the caller returns to serving before the (synchronous)\n // ingest runs — readiness is already established.\n await Promise.resolve();\n spool.setMeta(\"replay_status\", \"running\");\n try {\n const summary = await ingestReplayDirResponsive(spool, replayDir, { signal });\n if (summary.aborted) {\n spool.setMeta(\"replay_status\", \"cancelled\");\n io.stdout(`replay: cancelled after ${summary.conversationsIngested} conversation(s)`);\n } else {\n spool.setMeta(\"replay_status\", \"ok\");\n io.stdout(\n `replay: ingested ${summary.conversationsIngested} conversation(s), ` +\n `${summary.segmentsIngested} segment(s) from ${summary.files} fixture file(s)`,\n );\n }\n } catch (err) {\n const message =\n err instanceof CaptureConfigError || err instanceof CaptureInputError ? err.message : describeError(err);\n // Sanitize before it reaches the spool meta / /v1/health: strip any\n // filesystem paths from the surfaced status; the full detail goes to the log.\n const sanitized = message.replace(/\\/\\S+/g, \"<path>\");\n spool.setMeta(\"replay_status\", `failed: ${sanitized}`);\n io.stderr(`replay ingestion failed: ${message}`);\n }\n}\n\nfunction cmdInit(paths: CapturePaths, flags: Record<string, string | boolean>, stdout: (l: string) => void): number {\n ensurePrivateDir(paths.baseDir);\n if (existsSync(paths.configPath) && flags.force !== true) {\n stdout(`config already exists at ${paths.configPath} (use --force to overwrite)`);\n } else {\n writeFileSync(paths.configPath, serializeDaemonConfig(defaultDaemonConfig()), \"utf8\");\n stdout(`wrote default config to ${paths.configPath}`);\n }\n const token = loadOrCreateToken(paths.tokenPath);\n stdout(`token ready at ${paths.tokenPath} (${token.length} chars, mode 0600)`);\n stdout(`spool will be created at ${paths.spoolPath} on first start`);\n return 0;\n}\n\nasync function cmdDownloadModel(\n paths: CapturePaths,\n flags: Record<string, string | boolean>,\n stdout: (line: string) => void,\n downloadModel: (input: ModelDownloadInput) => Promise<ModelDownloadResult>,\n): Promise<number> {\n if (typeof flags.model !== \"string\") throw new CaptureInputError(\"flag --model requires a value\");\n const result = await downloadModel({ model: flags.model, directory: path.join(paths.baseDir, \"models\") });\n stdout(`${result.downloaded ? \"downloaded\" : \"model already present\"} ${flags.model} to ${result.path}`);\n return 0;\n}\n\nasync function cmdJanitor(\n paths: CapturePaths,\n stdout: (line: string) => void,\n stderr: (line: string) => void,\n): Promise<number> {\n const config = loadConfigOrDefault(paths, stderr);\n const removed = await pruneExpiredRawAudio(path.join(paths.baseDir, \"raw\"), config.rawRetentionHours * 60 * 60 * 1000);\n stdout(`janitor: removed ${removed.length} expired raw audio file(s)`);\n return 0;\n}\n\nasync function cmdStart(\n paths: CapturePaths,\n flags: Record<string, string | boolean>,\n env: NodeJS.ProcessEnv,\n stdout: (l: string) => void,\n stderr: (l: string) => void,\n spawnArgvPrefix: readonly string[],\n): Promise<number> {\n const config = applyBindingOverrides(loadConfigOrDefault(paths, stderr), flags);\n if (!isLoopbackHost(config.host)) {\n stderr(\n `refusing to bind non-loopback host '${config.host}': capture-audio serves plain HTTP with no TLS contract; ` +\n \"use a loopback address (127.0.0.1 or ::1)\",\n );\n return 1;\n }\n const replayDir = typeof flags.replay === \"string\" ? expandTilde(flags.replay) : null;\n const previousRecord = readPidRecord(paths.pidPath);\n if (previousRecord !== null) {\n if (await recordedDaemonIsRunning(previousRecord, paths, stderr)) {\n stdout(`daemon already running (pid ${previousRecord.pid})`);\n return 0;\n }\n // Not a running prior daemon. Reclaim a stale/foreign record, but never our\n // OWN prewritten record — the background parent wrote it for this child, and\n // the child overwrites it with the full record after it binds.\n if (previousRecord.pid !== process.pid) {\n removePidFile(paths.pidPath);\n }\n }\n\n if (flags.foreground !== true) {\n const relaunch = spawnArgvPrefix.length > 0 ? [...spawnArgvPrefix] : [process.argv[1]];\n const forwarded = [\"start\", \"--foreground\"];\n if (replayDir) forwarded.push(\"--replay\", replayDir);\n if (typeof flags[\"base-dir\"] === \"string\") forwarded.push(\"--base-dir\", flags[\"base-dir\"]);\n if (typeof flags.host === \"string\") forwarded.push(\"--host\", flags.host);\n if (typeof flags.port === \"string\") forwarded.push(\"--port\", flags.port);\n if (typeof flags.listen === \"string\") forwarded.push(\"--listen\", flags.listen);\n if (flags.capture === true) forwarded.push(\"--capture\");\n ensurePrivateDir(paths.baseDir);\n const logFd = openSync(paths.logPath, \"a\");\n const child = spawn(process.execPath, [...relaunch, ...forwarded], {\n detached: true,\n stdio: [\"ignore\", logFd, logFd],\n env: { ...process.env, ...env },\n });\n child.on(\"error\", (err) => stderr(`daemon failed to launch: ${describeError(err)}`));\n child.unref();\n if (typeof child.pid !== \"number\") {\n stderr(\"failed to spawn daemon process\");\n return 1;\n }\n // Record pid + effective binding now; the child adds instanceId once bound.\n // If persistence fails, kill the child so it isn't orphaned unrecorded.\n if (!recordChildPidOrTerminate(child.pid, paths, { host: config.host, port: config.port }, stderr)) {\n return 1;\n }\n // Do not report success until the child actually binds: it writes an\n // instanceId into the pid record only after the HTTP server is listening.\n const deadline = Date.now() + READINESS_TIMEOUT_MS;\n while (Date.now() < deadline) {\n if (!isProcessAlive(child.pid)) {\n removePidFileIfOwner(paths.pidPath, child.pid);\n stderr(`daemon exited during startup; see ${paths.logPath}`);\n return 1;\n }\n if (readPidRecord(paths.pidPath)?.instanceId) {\n stdout(`started daemon (pid ${child.pid}); listening; logs at ${paths.logPath}`);\n return 0;\n }\n await delay(100);\n }\n // Readiness timed out: the child never bound. Terminate it and fail loudly\n // so automation never observes a false success.\n try {\n process.kill(child.pid, \"SIGTERM\");\n } catch {\n // already gone\n }\n removePidFileIfOwner(paths.pidPath, child.pid);\n stderr(\n `daemon did not become ready within ${READINESS_TIMEOUT_MS / 1000}s; terminated pid ${child.pid}. See ${paths.logPath}.`,\n );\n return 1;\n }\n\n ensurePrivateDir(paths.baseDir);\n const token = loadOrCreateToken(paths.tokenPath);\n const spool = new Spool(paths.spoolPath);\n\n // Start live native capture (opt-in) BEFORE binding so the daemon's reported\n // `capturing` reflects whether the helper actually started. A missing or\n // unavailable helper degrades honestly — the daemon still serves the spool.\n let live: LiveCapture | null = null;\n if (flags.capture === true) {\n try {\n const rawDir = path.join(paths.baseDir, \"raw\");\n mkdirSync(rawDir, { recursive: true });\n live = createLiveCapture({\n spool,\n config,\n outDir: rawDir,\n defaultModelPath: path.join(paths.baseDir, \"models\", \"ggml-base.bin\"),\n onError: (e) => stderr(`capture: ${describeError(e)}`),\n onStderr: (l) => stderr(`helper: ${l}`),\n });\n live.start();\n } catch (err) {\n const detail = err instanceof CaptureConfigError || err instanceof CaptureInputError ? err.message : describeError(err);\n stderr(`live capture unavailable: ${detail}; serving without capture`);\n live = null;\n }\n }\n\n let handle: DaemonHandle;\n try {\n // Bind and report the HTTP service; `capturing` mirrors the live runner.\n handle = await startDaemon({ spool, config, token, capturing: () => live !== null && live.running });\n } catch (err) {\n // Don't leak the live runner or spool handle if the bind fails.\n if (live) await live.stop().catch(() => undefined);\n spool.close();\n throw err;\n }\n try {\n writePidFile(paths.pidPath, process.pid, {\n instanceId: spool.meta(\"instance_id\"),\n host: handle.host,\n port: handle.port,\n });\n } catch (err) {\n // Bound but couldn't persist the record: release everything half-started.\n if (live) await live.stop().catch(() => undefined);\n await handle.close();\n spool.close();\n throw err;\n }\n stdout(`listening on ${handle.url}`);\n if (live) stdout(\"live capture started\");\n // Supervised AFTER readiness: replay volume/failure never delays or fails\n // readiness, and never kills the daemon. The task is tracked so shutdown can\n // cancel and drain it before the spool closes.\n const replayAbort = new AbortController();\n const replayTask: Promise<void> = replayDir\n ? superviseReplay(spool, replayDir, { stdout, stderr }, replayAbort.signal)\n : Promise.resolve();\n\n return await new Promise<number>((resolve) => {\n let closing = false;\n const shutdown = () => {\n if (closing) return;\n closing = true;\n // Cancel any in-flight replay and drain it (bounded: ingestion stops at\n // the next commit-batch boundary) BEFORE closing the spool, so no\n // ingestion write can ever hit a closed database.\n replayAbort.abort();\n void replayTask\n .catch(() => undefined)\n .then(() => (live ? live.stop().then(() => undefined) : undefined))\n .catch((error: unknown) => {\n // A failed shutdown flush means chunks are still held in memory and\n // their raw audio is still on disk. Swallowing that silently made a\n // recoverable state look like a clean exit (issue #2145).\n process.stderr.write(\n `[capture-audio] shutdown flush failed; retained raw audio was NOT ingested: ${describeError(error)}\\n`,\n );\n })\n .then(() => {\n spool.finalizeOpenConversations();\n return handle.close().catch(() => undefined);\n })\n .finally(() => {\n spool.close();\n removePidFileIfOwner(paths.pidPath, process.pid);\n resolve(0);\n });\n };\n process.once(\"SIGINT\", shutdown);\n process.once(\"SIGTERM\", shutdown);\n });\n}\n\nasync function cmdStop(\n paths: CapturePaths,\n flags: Record<string, string | boolean>,\n stdout: (l: string) => void,\n stderr: (l: string) => void,\n): Promise<number> {\n const record = readPidRecord(paths.pidPath);\n if (record === null || !isProcessAlive(record.pid)) {\n removePidFile(paths.pidPath);\n stdout(\"daemon not running\");\n return 0;\n }\n // Identity guard: when we know which instance owns the pid, confirm the\n // live process really is that daemon before signalling.\n if (record.instanceId !== null) {\n const live = await probeIdentity(paths, recordHealthUrl(record, paths, stderr));\n if (live !== null && (live.instanceId !== record.instanceId || live.pid !== record.pid)) {\n // Verified NOT the recorded process: the daemon answering this endpoint\n // reports a different instance id or pid than the record. Never signal an\n // unverified pid, and never reclaim before a safe stop — either could kill\n // an unrelated process or orphan the serving daemon. --force does not\n // override a verified mismatch.\n stderr(\n `recorded pid ${record.pid} does not match the daemon serving this endpoint (identity/pid mismatch); ` +\n `not signalling and preserving ${paths.pidPath}. Stop the serving daemon via its own controls, ` +\n `or remove the pid file after verifying it is stale.`,\n );\n return 1;\n }\n if (live === null && flags.force !== true) {\n // Cannot confirm identity (health unreachable): refuse to signal a pid we\n // can't prove is ours, unless the operator forces it.\n stderr(\n `cannot confirm daemon identity for pid ${record.pid} (health unreachable); not signalling. ` +\n `Re-run \\`stop --force\\` to stop it anyway, or remove ${paths.pidPath}.`,\n );\n return 1;\n }\n } else if (flags.force !== true) {\n // No recorded instance id -> identity is unverifiable; refuse to signal a\n // pid we can't prove is ours (guards against PID reuse) unless forced.\n stderr(\n `cannot verify daemon identity for pid ${record.pid} (no recorded instance id); not signalling. ` +\n `Re-run \\`stop --force\\` to stop it anyway, or remove ${paths.pidPath}.`,\n );\n return 1;\n }\n try {\n process.kill(record.pid, \"SIGTERM\");\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ESRCH\") {\n removePidFile(paths.pidPath);\n stdout(\"daemon not running\");\n return 0;\n }\n if (code === \"EPERM\") {\n stderr(`daemon (pid ${record.pid}) is running but not controllable from this user`);\n return 1;\n }\n throw err;\n }\n // The daemon's own shutdown handler removes the pid file once it has\n // finalized conversations and released the spool/socket — don't delete it\n // here (valid state must survive until replacement is confirmed). Wait\n // boundedly for that so automation (a following `start`/`status`) doesn't\n // see the old pid as still running, and callers don't observe `stop` as\n // done before final conversations are persisted.\n const deadline = Date.now() + STOP_TIMEOUT_MS;\n while (Date.now() < deadline) {\n if (!isProcessAlive(record.pid) || readPidRecord(paths.pidPath) === null) {\n stdout(`daemon (pid ${record.pid}) stopped`);\n return 0;\n }\n await delay(100);\n }\n stdout(\n `sent SIGTERM to daemon (pid ${record.pid}); still shutting down after ${STOP_TIMEOUT_MS / 1000}s`,\n );\n return 0;\n}\n\nasync function cmdStatus(\n paths: CapturePaths,\n stdout: (l: string) => void,\n stderr: (l: string) => void,\n): Promise<number> {\n const record = readPidRecord(paths.pidPath);\n if (record === null || !isProcessAlive(record.pid)) {\n stdout(\"status: not running\");\n return 0;\n }\n try {\n const res = await fetch(recordHealthUrl(record, paths, stderr), {\n headers: tokenHeader(paths),\n signal: AbortSignal.timeout(2000),\n });\n const body = await res.text();\n stdout(`status: running (pid ${record.pid}) — HTTP ${res.status} ${body}`);\n } catch (err) {\n stdout(`status: process alive (pid ${record.pid}) but health check failed (${describeError(err)})`);\n }\n return 0;\n}\n\nasync function cmdDevices(env: NodeJS.ProcessEnv, stdout: (l: string) => void): Promise<number> {\n // A missing/unsupported helper throws a CaptureConfigError with an actionable\n // message; the runCapture handler surfaces it verbatim and exits nonzero.\n const { binaryPath } = resolveHelperBinary({ env });\n const devices = await enumerateDevices(binaryPath);\n stdout(JSON.stringify({ devices }, null, 2));\n return 0;\n}\n\nfunction cmdInstallService(\n paths: CapturePaths,\n flags: Record<string, string | boolean>,\n env: NodeJS.ProcessEnv,\n stdout: (l: string) => void,\n spawnArgvPrefix: readonly string[],\n): number {\n const platform = process.platform;\n const home = homedir();\n const label = typeof flags.label === \"string\" ? flags.label : undefined;\n // Persist the env the daemon needs under launchd/systemd: PATH (to find\n // whisper-cli) and any REMNIC_CAPTURE_HELPER_BIN override the operator set.\n const environment: Record<string, string> = {};\n if (typeof env.PATH === \"string\" && env.PATH !== \"\") environment.PATH = env.PATH;\n const helperBin = env.REMNIC_CAPTURE_HELPER_BIN;\n if (typeof helperBin === \"string\" && helperBin !== \"\") environment.REMNIC_CAPTURE_HELPER_BIN = helperBin;\n const spec = {\n programArguments: [\n process.execPath,\n ...(spawnArgvPrefix.length > 0 ? [...spawnArgvPrefix] : [process.argv[1]]),\n \"start\",\n \"--foreground\",\n \"--capture\",\n \"--base-dir\",\n paths.baseDir,\n ],\n logPath: paths.logPath,\n ...(label ? { label } : {}),\n ...(Object.keys(environment).length > 0 ? { environment } : {}),\n };\n if (flags.uninstall === true) {\n const { plan, removed } = uninstallService({\n platform,\n home,\n spec,\n exists: existsSync,\n remove: (f) => rmSync(f, { force: true }),\n });\n stdout(removed ? `removed ${plan.path}` : `no capture-audio service installed at ${plan.path}`);\n return 0;\n }\n // The unit's StandardOut/Err path is under baseDir; create it now so launchd/\n // systemd can write logs even when install-service is the first command run.\n ensurePrivateDir(paths.baseDir);\n const plan = installService({\n platform,\n home,\n spec,\n force: flags.force === true,\n exists: existsSync,\n mkdir: (dir) => mkdirSync(dir, { recursive: true }),\n writeFile: (file, contents) => writeFileSync(file, contents, { mode: 0o644 }),\n });\n stdout(`installed ${plan.platform} service at ${plan.path}`);\n stdout(`enable it with: ${plan.loadHint}`);\n return 0;\n}\n\nfunction cmdEnrollSelf(\n paths: CapturePaths,\n flags: Record<string, string | boolean>,\n stdout: (l: string) => void,\n): number {\n ensurePrivateDir(paths.baseDir);\n const spool = new Spool(paths.spoolPath);\n try {\n const label = typeof flags.label === \"string\" ? flags.label : undefined;\n const result = enrollSelf({ spool, label });\n stdout(`enrolled self speaker '${result.speakerId}' (${result.label})`);\n if (!result.hasEmbedding) {\n stdout(\"no voice embedding stored yet; voice-based diarization refinement lands with the diarization slice\");\n }\n return 0;\n } finally {\n spool.close();\n }\n}\n\nfunction cmdLogs(paths: CapturePaths, flags: Record<string, string | boolean>, stdout: (l: string) => void): number {\n if (!existsSync(paths.logPath)) {\n stdout(`no log file at ${paths.logPath}`);\n return 0;\n }\n const lines = typeof flags.lines === \"string\" ? coerceNumber(flags.lines, \"--lines\", { integer: true, min: 1 }) : 200;\n const all = readFileSync(paths.logPath, \"utf8\").split(\"\\n\");\n stdout(all.slice(Math.max(0, all.length - lines)).join(\"\\n\"));\n return 0;\n}\n\nfunction usage(stdout: (l: string) => void): number {\n stdout(\n [\n `remnic-capture-audio v${CAPTURE_AUDIO_VERSION}`,\n \"usage: remnic-capture-audio <command> [flags]\",\n \"commands: init | start | stop | status | devices | logs | download-model | janitor | install-service | enroll-self\",\n \"start flags: --foreground --capture --replay <dir> --host <h> --port <n> --listen <host:port> --base-dir <dir>\",\n \"download-model flags: --model <base|small|large-v3-turbo-q5_0> --base-dir <dir>\",\n \"install-service flags: --force --uninstall --label <id>; enroll-self flags: --label <name>\",\n \"janitor uses rawRetentionHours from audio.json to remove expired files under raw/\",\n ].join(\"\\n\"),\n );\n return 0;\n}\n\nexport async function runCapture(io: CliIo): Promise<number> {\n const env = io.env ?? process.env;\n const stdout = io.stdout ?? ((line: string) => console.log(line));\n const stderr = io.stderr ?? ((line: string) => console.error(line));\n try {\n const parsed = parseArgs(io.argv);\n const paths = resolvePaths(parsed.flags, env);\n if (parsed.flags.help === true || parsed.positionals.includes(\"-h\") || parsed.positionals.includes(\"--help\")) {\n return usage(stdout);\n }\n if (parsed.positionals.length > 0) {\n stderr(`unexpected argument(s): ${parsed.positionals.join(\" \")}`);\n usage(stderr);\n return 2;\n }\n const allowedFlags = COMMAND_FLAGS[parsed.command];\n if (allowedFlags !== undefined) {\n for (const key of Object.keys(parsed.flags)) {\n if (!Object.hasOwn(GLOBAL_FLAGS, key) && !Object.hasOwn(allowedFlags, key)) {\n stderr(`flag --${key} is not valid for command '${parsed.command}'`);\n usage(stderr);\n return 2;\n }\n }\n }\n switch (parsed.command) {\n case \"init\":\n return cmdInit(paths, parsed.flags, stdout);\n case \"start\":\n return await cmdStart(paths, parsed.flags, env, stdout, stderr, io.spawnArgvPrefix ?? [process.argv[1]]);\n case \"stop\":\n return await cmdStop(paths, parsed.flags, stdout, stderr);\n case \"status\":\n return await cmdStatus(paths, stdout, stderr);\n case \"devices\":\n return await cmdDevices(env, stdout);\n case \"logs\":\n return cmdLogs(paths, parsed.flags, stdout);\n case \"download-model\":\n return await cmdDownloadModel(paths, parsed.flags, stdout, io.downloadModel ?? downloadWhisperModel);\n case \"janitor\":\n return await cmdJanitor(paths, stdout, stderr);\n case \"install-service\":\n return cmdInstallService(paths, parsed.flags, env, stdout, io.spawnArgvPrefix ?? [process.argv[1]]);\n case \"enroll-self\":\n return cmdEnrollSelf(paths, parsed.flags, stdout);\n case \"help\":\n case \"--help\":\n case \"-h\":\n return usage(stdout);\n default:\n stderr(`unknown command '${parsed.command}'`);\n usage(stderr);\n return 2;\n }\n } catch (err) {\n if (err instanceof CaptureConfigError || err instanceof CaptureInputError) {\n stderr(`error: ${err.message}`);\n return err instanceof CaptureInputError ? 2 : 1;\n }\n stderr(`error: ${describeError(err)}`);\n return 1;\n }\n}\n"],"mappings":"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","names":["readFileSync","Buffer","randomBytes","mkdirSync","readFileSync","writeFileSync","path","Buffer","Buffer","Buffer","Buffer","path","readFileSync","path","asObject","path","readFileSync","lstatSync","path","lstatSync","path","rm","path","path","rm","chmodSync","chmodSync","cluster","readFileSync","path","readFileSync","path","parseTimestamp","spawn","createHash","createHash","process","existsSync","lstatSync","readdirSync","path","existsSync","lstatSync","readdirSync","path","statSync","statSync","rm","path","path","rm","path","path","spawn","chmodSync","existsSync","mkdirSync","readFileSync","rmSync","writeFileSync","path","existsSync","readFileSync","mkdirSync","chmodSync","writeFileSync","path","spawn","plan","rmSync"]}
|