iobroker.assistant 1.4.0 → 1.4.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/README.md +10 -0
- package/admin/custom/.vite/manifest.json +1 -1
- package/admin/custom/assets/Components-CCl7INIF.js +2 -0
- package/admin/custom/customComponents.js +1 -1
- package/admin/custom/index.html +1 -1
- package/admin/custom/mf-manifest.json +1 -1
- package/admin/custom/mf-stats.json +1 -1
- package/admin/i18n/de.json +2 -2
- package/admin/i18n/en.json +2 -2
- package/admin/i18n/es.json +2 -2
- package/admin/i18n/fr.json +2 -2
- package/admin/i18n/it.json +2 -2
- package/admin/i18n/nl.json +2 -2
- package/admin/i18n/pl.json +2 -2
- package/admin/i18n/pt.json +2 -2
- package/admin/i18n/ru.json +2 -2
- package/admin/i18n/uk.json +2 -2
- package/admin/i18n/zh-cn.json +2 -2
- package/admin/jsonConfig.json +2 -2
- package/build/lib/journal.js.map +1 -1
- package/build/lib/voice/captures.js.map +1 -1
- package/build/lib/voice/esphome.js +36 -24
- package/build/lib/voice/esphome.js.map +1 -1
- package/build/lib/voice/esphomeEntities.js +19 -5
- package/build/lib/voice/esphomeEntities.js.map +1 -1
- package/build/lib/voice/fallback.js +18 -3
- package/build/lib/voice/fallback.js.map +1 -1
- package/build/lib/voice/speechAudio.js +97 -0
- package/build/lib/voice/speechAudio.js.map +1 -0
- package/build/lib/voice/vad.js +39 -4
- package/build/lib/voice/vad.js.map +1 -1
- package/build/main.js +26 -1
- package/build/main.js.map +1 -1
- package/io-package.json +27 -27
- package/package.json +1 -1
- package/admin/custom/assets/Components-D15eOzOp.js +0 -2
package/build/lib/journal.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"journal.js","sourceRoot":"","sources":["../../src/lib/journal.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Conversation journal: one entry per request the assistant answered — where it came from (chat, script,\n * Telegram, which satellite, which wake word), which tier answered it (routine, rule-based NLU, local\n * model, cloud model), which tools the model called and which state ids were read or written along the\n * way. The admin chat shows it live, so one can check what a satellite heard and what was done about it.\n *\n * Tools and state accesses happen deep inside the pipeline (NLU executor, LLM tool loop, routine\n * executor). Instead of threading an entry object through all of them, the entry rides along in an\n * {@link AsyncLocalStorage}: {@link Journal.run} opens it, and {@link Journal.note}/{@link Journal.tool}/\n * {@link Journal.stateAccess} write to whatever entry the current call chain belongs to — and do nothing\n * outside of one (a trigger writing a state is not a conversation).\n *\n * Besides the entry, every request keeps a **trace**: the steps of its processing with their timing and\n * whatever explains them — the NLU's parse, the full prompt and every round of the model, tool arguments\n * and results in full, speech recognition and synthesis. Traces are too big to ride along in `journal.last`\n * and in `journal.json`, so the chat fetches one when a request is opened ({@link Journal.trace}); each is\n * kept in a file of its own and only for the newest {@link MAX_TRACES} requests.\n */\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport { promises as fs } from 'node:fs';\nimport * as path from 'node:path';\n\n/** How a request reached us. */\nexport type JournalTransport = 'chat' | 'script' | 'state' | 'telegram' | 'esphome' | 'udp' | 'native' | 'wyoming';\n\n/** Which stage of the pipeline produced the answer. */\nexport type JournalTier =\n | 'stop' // stop word silenced a ringing timer/alarm\n | 'askReply' // the utterance answered an open `askUser` question\n | 'routine'\n | 'nlu'\n | 'localLlm'\n | 'cloud'\n | 'none'; // nothing local understood it and there is no cloud provider\n\n/** Tokens a model call used (summed over the rounds of a request in {@link JournalEntry.tokens}). */\nexport interface JournalTokens {\n input: number;\n output: number;\n /** Prompt tokens served from the provider's prompt cache. */\n cacheRead?: number;\n /** Prompt tokens written to the provider's prompt cache (Anthropic). */\n cacheWrite?: number;\n}\n\n/** One step of how a request was processed — what the debug view of the admin chat shows. */\nexport interface JournalStep {\n /** Start of the step in ms after the request started (negative for speech recognition, which comes first). */\n t: number;\n /** Duration, for steps that took time. */\n ms?: number;\n /** Pipeline stage: request, stt, context, stop, askReply, routine, youtube, nlu, localLlm, cloud, tool, state, answer, tts, error. */\n stage: string;\n title: string;\n level?: 'warn' | 'error';\n tokens?: JournalTokens;\n /** Whatever explains the step (prompts, model responses, parse results) — a JSON snapshot. */\n data?: unknown;\n}\n\n/** What a caller records; the journal adds the timing. */\nexport type JournalStepInput = Omit<JournalStep, 't'>;\n\n/** Hook for library code that should not know the journal (the LLM agent). */\nexport type TraceFn = (step: JournalStepInput) => void;\n\nexport interface JournalTool {\n name: string;\n args: Record<string, unknown>;\n ms: number;\n /** Result, JSON, shortened. */\n result?: string;\n error?: string;\n}\n\nexport interface JournalEntry {\n id: string;\n /** Start of the answer, ms. */\n ts: number;\n /** False while the answer is still being produced. */\n done: boolean;\n /** Time to produce the answer (without STT/TTS). */\n ms?: number;\n source: string;\n transport: JournalTransport;\n satellite?: string;\n room?: string;\n /** Wake word the satellite reported; '' = the mic was re-opened for a follow-up, absent = unknown. */\n wakeWord?: string;\n /** Speech recognition time, for voice requests. */\n sttMs?: number;\n /** Length of the recorded utterance. */\n audioMs?: number;\n /** Speech synthesis time of the reply (voice requests, filled in after the answer). */\n ttsMs?: number;\n /** Length of the spoken reply. */\n replyAudioMs?: number;\n question: string;\n answer?: string;\n error?: string;\n tier?: JournalTier;\n /** Model name, routine name or NLU actions — whatever explains the tier. */\n detail?: string;\n tools: JournalTool[];\n /** State ids read to produce the answer. */\n read: string[];\n /** States written to produce the answer. */\n written: { id: string; value: unknown }[];\n /** The reply asks back, so a satellite re-opened its mic. */\n followUp?: boolean;\n /** Tokens of all model calls, when a cloud model answered. */\n tokens?: JournalTokens;\n}\n\n/** What the caller knows about a request before the pipeline starts. */\nexport interface JournalOrigin {\n transport?: JournalTransport;\n satellite?: string;\n room?: string;\n wakeWord?: string;\n sttMs?: number;\n audioMs?: number;\n /** Voice activity detection of the utterance (ESPHome) — trace only. */\n vad?: { peak: number; noiseFloor: number; startLevel: number; endLevel: number };\n}\n\n/** What a transport reports after it synthesised the reply. */\nexport interface JournalSpeech {\n ms: number;\n audioMs: number;\n provider: string;\n fallback?: string;\n voice?: string;\n}\n\nexport interface JournalOptions {\n /** Retention in hours; 0 disables the journal entirely. */\n hours: number;\n /** File the journal is kept in across restarts ('' = memory only). */\n file: string;\n /** Called with every new or updated entry (drives the `journal.last` state the chat listens to). */\n onChange?: (entry: JournalEntry) => void;\n log: ioBroker.Logger;\n}\n\n/** Hard cap independent of the retention — a script asking every few seconds must not fill the disk. */\nconst MAX_ENTRIES = 3000;\nconst SAVE_DELAY_MS = 5000;\nconst RESULT_CHARS = 300;\n/** Requests whose trace is kept on disk. */\nexport const MAX_TRACES = 200;\n/** Traces held in memory (the newest ones — the ones somebody is about to open). */\nconst MEMORY_TRACES = 30;\n/** Steps per request; a tool loop gone wild must not grow one trace without end. */\nconst MAX_STEPS = 400;\n/** Longer strings are cut — enough for a system prompt with a few hundred devices. */\nconst STEP_STRING_CHARS = 100_000;\n/** A reply is matched to its synthesis only this long after the answer. */\nconst SPEECH_WINDOW_MS = 120_000;\n/** Entry ids as {@link Journal.run} makes them — checked before one becomes a file name. */\nconst ID_RE = /^[a-z0-9-]+$/;\n\n/** Directory the traces of a journal file are kept in. */\nexport function traceDirFor(file: string): string {\n return path.join(path.dirname(file), 'journal-trace');\n}\n\n/**\n * JSON snapshot of a step's data — taken when it is recorded, because the objects go on changing (the\n * model's message list grows, cache markers move). Long strings are cut and buffers summarised.\n */\nexport function snapshot(value: unknown): unknown {\n if (value === undefined) {\n return undefined;\n }\n try {\n const text = JSON.stringify(value, (_key, v: unknown) => {\n if (typeof v === 'string' && v.length > STEP_STRING_CHARS) {\n return `${v.slice(0, STEP_STRING_CHARS)}… [+${v.length - STEP_STRING_CHARS} chars]`;\n }\n if (typeof v === 'bigint') {\n return v.toString();\n }\n // Buffer.toJSON() has already run when the replacer sees it.\n const b = v as { type?: unknown; data?: unknown } | null;\n if (b && typeof b === 'object' && b.type === 'Buffer' && Array.isArray(b.data)) {\n return `<${b.data.length} bytes>`;\n }\n return v;\n });\n return text === undefined ? undefined : JSON.parse(text);\n } catch (e) {\n return `<not serializable: ${(e as Error).message}>`;\n }\n}\n\n/** Everything an error carries that helps — an API error's status and body, the stack. */\nexport function errorInfo(e: unknown): Record<string, unknown> {\n if (!(e instanceof Error)) {\n return { message: String(e) };\n }\n const info: Record<string, unknown> = { name: e.name, message: e.message };\n for (const key of ['status', 'code', 'type', 'param', 'error', 'request_id', 'requestID']) {\n const v = (e as unknown as Record<string, unknown>)[key];\n if (v !== undefined && v !== null) {\n info[key] = v;\n }\n }\n if (e.stack) {\n info.stack = e.stack;\n }\n return info;\n}\n\n/** Derive the transport from a request source when the caller did not say. */\nexport function transportForSource(source: string): JournalTransport {\n if (source === 'chat') {\n return 'chat';\n }\n if (source === '') {\n return 'state';\n }\n if (source === 'wyoming') {\n return 'wyoming';\n }\n if (source.startsWith('telegram:')) {\n return 'telegram';\n }\n return 'script';\n}\n\nexport class Journal {\n private entries: JournalEntry[] = [];\n private readonly store = new AsyncLocalStorage<JournalEntry>();\n private saveTimer: NodeJS.Timeout | null = null;\n private counter = 0;\n /** Steps of the newest requests; older ones are read back from their file. */\n private readonly traces = new Map<string, JournalStep[]>();\n /** Traces changed since they were last written. */\n private readonly dirty = new Set<string>();\n /** LLM tool calls in progress per request: a state access inside one belongs to that tool's step. */\n private readonly toolDepth = new WeakMap<JournalEntry, number>();\n private readonly traceDir: string;\n\n constructor(private readonly opts: JournalOptions) {\n this.traceDir = opts.file ? traceDirFor(opts.file) : '';\n }\n\n get enabled(): boolean {\n return this.opts.hours > 0;\n }\n\n /** Load the entries of the last run (silently starts empty when there are none). */\n async load(): Promise<void> {\n if (!this.enabled || !this.opts.file) {\n return;\n }\n try {\n const raw = JSON.parse(await fs.readFile(this.opts.file, 'utf8')) as unknown;\n if (Array.isArray(raw)) {\n // An entry still open at shutdown never got its answer.\n this.entries = (raw as JournalEntry[])\n .filter(e => e && typeof e.id === 'string' && typeof e.ts === 'number')\n .map(e => (e.done ? e : { ...e, done: true, error: e.error || 'interrupted' }));\n this.prune();\n }\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code !== 'ENOENT') {\n this.opts.log.warn(`Journal could not be read: ${(e as Error).message}`);\n }\n }\n }\n\n /** Entries newer than `since` (ms), oldest first. */\n list(since = 0): JournalEntry[] {\n this.prune();\n return this.entries.filter(e => e.ts > since);\n }\n\n clear(): void {\n this.entries = [];\n this.traces.clear();\n this.dirty.clear();\n this.scheduleSave();\n }\n\n /** The entry of a request (the chat asks for it together with its trace). */\n get(id: string): JournalEntry | undefined {\n return this.entries.find(e => e.id === id);\n }\n\n /** The processing steps of a request, or null when none were kept (too old, or from an older version). */\n async trace(id: string): Promise<JournalStep[] | null> {\n if (!ID_RE.test(id) || !this.get(id)) {\n return null;\n }\n const steps = this.traces.get(id);\n if (steps) {\n return steps;\n }\n if (!this.traceDir) {\n return null;\n }\n try {\n const raw = JSON.parse(await fs.readFile(path.join(this.traceDir, `${id}.json`), 'utf8')) as {\n steps?: unknown;\n };\n return Array.isArray(raw?.steps) ? (raw.steps as JournalStep[]) : null;\n } catch {\n return null;\n }\n }\n\n /**\n * Run `fn` as one journalled request. The entry is published once when the request starts (so the\n * chat shows the question while the answer is still being worked out) and once when it is finished.\n */\n async run(question: string, source: string, origin: JournalOrigin, fn: () => Promise<string>): Promise<string> {\n if (!this.enabled) {\n return fn();\n }\n const entry: JournalEntry = {\n id: `${Date.now().toString(36)}-${(this.counter++).toString(36)}`,\n ts: Date.now(),\n done: false,\n source,\n transport: origin.transport || transportForSource(source),\n question,\n tools: [],\n read: [],\n written: [],\n };\n for (const key of ['satellite', 'room', 'wakeWord', 'sttMs', 'audioMs'] as const) {\n if (origin[key] !== undefined && origin[key] !== '') {\n (entry as unknown as Record<string, unknown>)[key] = origin[key];\n }\n }\n // A satellite that re-opened its mic reports no wake word — say so instead of \"unknown\".\n if (origin.wakeWord === '') {\n entry.wakeWord = '';\n }\n this.entries.push(entry);\n this.traces.set(entry.id, []);\n this.addStep(entry, {\n stage: 'request',\n title: `Request from ${source || entry.transport} (${entry.transport})`,\n data: { question, source, origin },\n });\n this.publish(entry);\n try {\n const answer = await this.store.run(entry, fn);\n entry.answer = answer;\n return answer;\n } catch (e) {\n entry.error = (e as Error).message;\n this.addStep(entry, { stage: 'error', level: 'error', title: entry.error, data: errorInfo(e) });\n throw e;\n } finally {\n entry.done = true;\n entry.ms = Date.now() - entry.ts;\n this.dirty.add(entry.id);\n this.publish(entry);\n this.prune();\n this.scheduleSave();\n }\n }\n\n /** Add a step to the current request's trace (nothing outside of one). */\n step(input: JournalStepInput): void {\n const entry = this.store.getStore();\n if (entry) {\n this.addStep(entry, input);\n }\n }\n\n /**\n * Run an LLM tool as part of the current request: the call lands in the entry's tool list and, with the\n * full arguments and result, in the trace.\n */\n async toolCall<T>(name: string, args: Record<string, unknown>, fn: () => Promise<T>): Promise<T> {\n const entry = this.store.getStore();\n if (!entry) {\n return fn();\n }\n this.toolDepth.set(entry, (this.toolDepth.get(entry) || 0) + 1);\n const start = Date.now();\n try {\n const result = await fn();\n const ms = Date.now() - start;\n this.addStep(entry, { stage: 'tool', title: `Tool ${name}`, ms, data: { args, result } });\n this.tool(name, args, ms, result);\n return result;\n } catch (e) {\n const ms = Date.now() - start;\n const message = (e as Error).message;\n this.addStep(entry, {\n stage: 'tool',\n level: 'error',\n title: `Tool ${name} failed: ${message}`,\n ms,\n data: { args, error: errorInfo(e) },\n });\n this.tool(name, args, ms, undefined, message);\n throw e;\n } finally {\n this.toolDepth.set(entry, (this.toolDepth.get(entry) || 1) - 1);\n }\n }\n\n /**\n * Run a state call (MCP `get_states`/`set_state`/…) as part of the current request: the ids land in the\n * entry's read/written lists, and — unless an LLM tool made the call, whose step already shows it — the\n * call with its result in the trace.\n */\n async stateCall<T>(name: string, args: Record<string, unknown> | undefined, fn: () => Promise<T>): Promise<T> {\n const entry = this.store.getStore();\n if (!entry) {\n return fn();\n }\n this.stateAccess(name, args);\n if (this.toolDepth.get(entry)) {\n return fn();\n }\n const start = Date.now();\n try {\n const result = await fn();\n this.addStep(entry, { stage: 'state', title: name, ms: Date.now() - start, data: { args, result } });\n return result;\n } catch (e) {\n this.addStep(entry, {\n stage: 'state',\n level: 'error',\n title: `${name} failed: ${(e as Error).message}`,\n ms: Date.now() - start,\n data: { args, error: errorInfo(e) },\n });\n throw e;\n }\n }\n\n /**\n * A transport synthesised the reply to the latest request of `source`. That happens after the answer,\n * outside of the request, so the entry is found by its source.\n */\n spoken(source: string, speech: JournalSpeech): void {\n const now = Date.now();\n for (let i = this.entries.length - 1; i >= 0; i--) {\n const entry = this.entries[i];\n if (entry.source !== source) {\n continue;\n }\n if (!entry.done || entry.ttsMs !== undefined || now - entry.ts - (entry.ms || 0) > SPEECH_WINDOW_MS) {\n return;\n }\n entry.ttsMs = speech.ms;\n entry.replyAudioMs = Math.round(speech.audioMs);\n const voice = speech.voice ? ` (${speech.voice})` : '';\n this.addStep(entry, {\n stage: 'tts',\n title: `Speech synthesis: ${speech.provider}${voice}, ${(speech.audioMs / 1000).toFixed(1)} s of audio`,\n ms: speech.ms,\n data: speech,\n });\n this.dirty.add(entry.id);\n this.publish(entry);\n this.scheduleSave();\n return;\n }\n }\n\n private addStep(entry: JournalEntry, input: JournalStepInput): void {\n const steps = this.traces.get(entry.id);\n if (!steps || steps.length > MAX_STEPS) {\n return;\n }\n if (steps.length === MAX_STEPS) {\n steps.push({\n t: Date.now() - entry.ts,\n stage: 'error',\n level: 'warn',\n title: `More than ${MAX_STEPS} steps — the rest is not recorded`,\n });\n return;\n }\n const step: JournalStep = {\n t: Date.now() - entry.ts - (input.ms || 0),\n stage: input.stage,\n title: input.title,\n };\n if (input.ms !== undefined) {\n step.ms = input.ms;\n }\n if (input.level) {\n step.level = input.level;\n }\n if (input.tokens) {\n step.tokens = input.tokens;\n const sum = (entry.tokens ||= { input: 0, output: 0 });\n sum.input += input.tokens.input || 0;\n sum.output += input.tokens.output || 0;\n if (input.tokens.cacheRead) {\n sum.cacheRead = (sum.cacheRead || 0) + input.tokens.cacheRead;\n }\n if (input.tokens.cacheWrite) {\n sum.cacheWrite = (sum.cacheWrite || 0) + input.tokens.cacheWrite;\n }\n }\n if (input.data !== undefined) {\n step.data = snapshot(input.data);\n }\n steps.push(step);\n }\n\n /** Fill in fields of the current request's entry (tier, detail, followUp …). */\n note(fields: Partial<Pick<JournalEntry, 'tier' | 'detail' | 'followUp'>>): void {\n const entry = this.store.getStore();\n if (entry) {\n Object.assign(entry, fields);\n }\n }\n\n /** Record a tool call the model made for the current request. */\n tool(name: string, args: Record<string, unknown>, ms: number, result: unknown, error?: string): void {\n const entry = this.store.getStore();\n if (!entry) {\n return;\n }\n const item: JournalTool = { name, args: args || {}, ms };\n if (error) {\n item.error = error;\n } else {\n let text: string;\n try {\n text = typeof result === 'string' ? result : JSON.stringify(result);\n } catch {\n text = String(result);\n }\n item.result = text.length > RESULT_CHARS ? `${text.slice(0, RESULT_CHARS)}…` : text;\n }\n entry.tools.push(item);\n // Live view: show each tool call as it happens, a cloud round can take seconds.\n this.publish(entry);\n }\n\n /** Record which states a call reads or writes (MCP `get_states`/`set_state`/`set_states`, routines). */\n stateAccess(tool: string, args: Record<string, unknown> | undefined): void {\n const entry = this.store.getStore();\n if (!entry || !args) {\n return;\n }\n if (tool === 'get_states' && Array.isArray(args.ids)) {\n for (const id of args.ids) {\n if (typeof id === 'string' && !entry.read.includes(id)) {\n entry.read.push(id);\n }\n }\n } else if (tool === 'get_state' && typeof args.id === 'string') {\n if (!entry.read.includes(args.id)) {\n entry.read.push(args.id);\n }\n } else if (tool === 'set_state' && typeof args.id === 'string') {\n entry.written.push({ id: args.id, value: args.value });\n } else if (tool === 'set_states' && Array.isArray(args.states)) {\n for (const s of args.states as { id?: unknown; value?: unknown }[]) {\n if (typeof s?.id === 'string') {\n entry.written.push({ id: s.id, value: s.value });\n }\n }\n }\n }\n\n /** Write pending changes now (on unload). */\n async flush(): Promise<void> {\n if (this.saveTimer) {\n clearTimeout(this.saveTimer);\n this.saveTimer = null;\n }\n await this.save();\n }\n\n private publish(entry: JournalEntry): void {\n try {\n this.opts.onChange?.(entry);\n } catch {\n // a listener must never break the answer\n }\n }\n\n private prune(): void {\n const cutoff = Date.now() - this.opts.hours * 3600_000;\n let start = 0;\n while (start < this.entries.length && this.entries[start].ts < cutoff) {\n start++;\n }\n start = Math.max(start, this.entries.length - MAX_ENTRIES);\n if (start > 0) {\n for (const e of this.entries.slice(0, start)) {\n this.traces.delete(e.id);\n this.dirty.delete(e.id);\n }\n this.entries = this.entries.slice(start);\n }\n // Without a file the memory is all there is: keep as many as would be on disk.\n const keep = this.traceDir ? MEMORY_TRACES : MAX_TRACES;\n for (const id of this.traces.keys()) {\n if (this.traces.size <= keep) {\n break;\n }\n // Not written yet, or still being answered: needed until it is on disk.\n if ((!this.traceDir || !this.dirty.has(id)) && this.get(id)?.done !== false) {\n this.traces.delete(id);\n }\n }\n }\n\n private scheduleSave(): void {\n if (!this.opts.file || this.saveTimer) {\n return;\n }\n this.saveTimer = setTimeout(() => {\n this.saveTimer = null;\n void this.save();\n }, SAVE_DELAY_MS);\n this.saveTimer.unref?.();\n }\n\n private async save(): Promise<void> {\n if (!this.opts.file) {\n return;\n }\n try {\n await fs.mkdir(path.dirname(this.opts.file), { recursive: true });\n await fs.writeFile(this.opts.file, JSON.stringify(this.entries.filter(e => e.done)));\n } catch (e) {\n this.opts.log.warn(`Journal could not be saved: ${(e as Error).message}`);\n }\n await this.saveTraces();\n this.prune();\n }\n\n /** Write the traces that changed, and delete the files of requests that are no longer kept. */\n private async saveTraces(): Promise<void> {\n try {\n await fs.mkdir(this.traceDir, { recursive: true });\n for (const id of [...this.dirty]) {\n const steps = this.traces.get(id);\n this.dirty.delete(id);\n if (steps) {\n await fs.writeFile(path.join(this.traceDir, `${id}.json`), JSON.stringify({ id, steps }));\n }\n }\n const keep = new Set(this.entries.slice(-MAX_TRACES).map(e => e.id));\n for (const name of await fs.readdir(this.traceDir)) {\n if (name.endsWith('.json') && !keep.has(name.slice(0, -5))) {\n await fs.rm(path.join(this.traceDir, name), { force: true });\n }\n }\n } catch (e) {\n this.opts.log.warn(`Journal traces could not be saved: ${(e as Error).message}`);\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"journal.js","sourceRoot":"","sources":["../../src/lib/journal.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Conversation journal: one entry per request the assistant answered — where it came from (chat, script,\n * Telegram, which satellite, which wake word), which tier answered it (routine, rule-based NLU, local\n * model, cloud model), which tools the model called and which state ids were read or written along the\n * way. The admin chat shows it live, so one can check what a satellite heard and what was done about it.\n *\n * Tools and state accesses happen deep inside the pipeline (NLU executor, LLM tool loop, routine\n * executor). Instead of threading an entry object through all of them, the entry rides along in an\n * {@link AsyncLocalStorage}: {@link Journal.run} opens it, and {@link Journal.note}/{@link Journal.tool}/\n * {@link Journal.stateAccess} write to whatever entry the current call chain belongs to — and do nothing\n * outside of one (a trigger writing a state is not a conversation).\n *\n * Besides the entry, every request keeps a **trace**: the steps of its processing with their timing and\n * whatever explains them — the NLU's parse, the full prompt and every round of the model, tool arguments\n * and results in full, speech recognition and synthesis. Traces are too big to ride along in `journal.last`\n * and in `journal.json`, so the chat fetches one when a request is opened ({@link Journal.trace}); each is\n * kept in a file of its own and only for the newest {@link MAX_TRACES} requests.\n */\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport { promises as fs } from 'node:fs';\nimport * as path from 'node:path';\nimport type { VadReport } from './voice/vad';\n\n/** How a request reached us. */\nexport type JournalTransport = 'chat' | 'script' | 'state' | 'telegram' | 'esphome' | 'udp' | 'native' | 'wyoming';\n\n/** Which stage of the pipeline produced the answer. */\nexport type JournalTier =\n | 'stop' // stop word silenced a ringing timer/alarm\n | 'askReply' // the utterance answered an open `askUser` question\n | 'routine'\n | 'nlu'\n | 'localLlm'\n | 'cloud'\n | 'none'; // nothing local understood it and there is no cloud provider\n\n/** Tokens a model call used (summed over the rounds of a request in {@link JournalEntry.tokens}). */\nexport interface JournalTokens {\n input: number;\n output: number;\n /** Prompt tokens served from the provider's prompt cache. */\n cacheRead?: number;\n /** Prompt tokens written to the provider's prompt cache (Anthropic). */\n cacheWrite?: number;\n}\n\n/** One step of how a request was processed — what the debug view of the admin chat shows. */\nexport interface JournalStep {\n /** Start of the step in ms after the request started (negative for speech recognition, which comes first). */\n t: number;\n /** Duration, for steps that took time. */\n ms?: number;\n /** Pipeline stage: request, stt, context, stop, askReply, routine, youtube, nlu, localLlm, cloud, tool, state, answer, tts, error. */\n stage: string;\n title: string;\n level?: 'warn' | 'error';\n tokens?: JournalTokens;\n /** Whatever explains the step (prompts, model responses, parse results) — a JSON snapshot. */\n data?: unknown;\n}\n\n/** What a caller records; the journal adds the timing. */\nexport type JournalStepInput = Omit<JournalStep, 't'>;\n\n/** Hook for library code that should not know the journal (the LLM agent). */\nexport type TraceFn = (step: JournalStepInput) => void;\n\nexport interface JournalTool {\n name: string;\n args: Record<string, unknown>;\n ms: number;\n /** Result, JSON, shortened. */\n result?: string;\n error?: string;\n}\n\nexport interface JournalEntry {\n id: string;\n /** Start of the answer, ms. */\n ts: number;\n /** False while the answer is still being produced. */\n done: boolean;\n /** Time to produce the answer (without STT/TTS). */\n ms?: number;\n source: string;\n transport: JournalTransport;\n satellite?: string;\n room?: string;\n /** Wake word the satellite reported; '' = the mic was re-opened for a follow-up, absent = unknown. */\n wakeWord?: string;\n /** Speech recognition time, for voice requests. */\n sttMs?: number;\n /** Length of the recorded utterance. */\n audioMs?: number;\n /** Speech synthesis time of the reply (voice requests, filled in after the answer). */\n ttsMs?: number;\n /** Length of the spoken reply. */\n replyAudioMs?: number;\n question: string;\n answer?: string;\n error?: string;\n tier?: JournalTier;\n /** Model name, routine name or NLU actions — whatever explains the tier. */\n detail?: string;\n tools: JournalTool[];\n /** State ids read to produce the answer. */\n read: string[];\n /** States written to produce the answer. */\n written: { id: string; value: unknown }[];\n /** The reply asks back, so a satellite re-opened its mic. */\n followUp?: boolean;\n /** Tokens of all model calls, when a cloud model answered. */\n tokens?: JournalTokens;\n}\n\n/** What the caller knows about a request before the pipeline starts. */\nexport interface JournalOrigin {\n transport?: JournalTransport;\n satellite?: string;\n room?: string;\n wakeWord?: string;\n sttMs?: number;\n audioMs?: number;\n /** Voice activity detection of the utterance (ESPHome) — trace only. */\n vad?: VadReport;\n}\n\n/** What a transport reports after it synthesised the reply. */\nexport interface JournalSpeech {\n ms: number;\n audioMs: number;\n provider: string;\n fallback?: string;\n voice?: string;\n}\n\nexport interface JournalOptions {\n /** Retention in hours; 0 disables the journal entirely. */\n hours: number;\n /** File the journal is kept in across restarts ('' = memory only). */\n file: string;\n /** Called with every new or updated entry (drives the `journal.last` state the chat listens to). */\n onChange?: (entry: JournalEntry) => void;\n log: ioBroker.Logger;\n}\n\n/** Hard cap independent of the retention — a script asking every few seconds must not fill the disk. */\nconst MAX_ENTRIES = 3000;\nconst SAVE_DELAY_MS = 5000;\nconst RESULT_CHARS = 300;\n/** Requests whose trace is kept on disk. */\nexport const MAX_TRACES = 200;\n/** Traces held in memory (the newest ones — the ones somebody is about to open). */\nconst MEMORY_TRACES = 30;\n/** Steps per request; a tool loop gone wild must not grow one trace without end. */\nconst MAX_STEPS = 400;\n/** Longer strings are cut — enough for a system prompt with a few hundred devices. */\nconst STEP_STRING_CHARS = 100_000;\n/** A reply is matched to its synthesis only this long after the answer. */\nconst SPEECH_WINDOW_MS = 120_000;\n/** Entry ids as {@link Journal.run} makes them — checked before one becomes a file name. */\nconst ID_RE = /^[a-z0-9-]+$/;\n\n/** Directory the traces of a journal file are kept in. */\nexport function traceDirFor(file: string): string {\n return path.join(path.dirname(file), 'journal-trace');\n}\n\n/**\n * JSON snapshot of a step's data — taken when it is recorded, because the objects go on changing (the\n * model's message list grows, cache markers move). Long strings are cut and buffers summarised.\n */\nexport function snapshot(value: unknown): unknown {\n if (value === undefined) {\n return undefined;\n }\n try {\n const text = JSON.stringify(value, (_key, v: unknown) => {\n if (typeof v === 'string' && v.length > STEP_STRING_CHARS) {\n return `${v.slice(0, STEP_STRING_CHARS)}… [+${v.length - STEP_STRING_CHARS} chars]`;\n }\n if (typeof v === 'bigint') {\n return v.toString();\n }\n // Buffer.toJSON() has already run when the replacer sees it.\n const b = v as { type?: unknown; data?: unknown } | null;\n if (b && typeof b === 'object' && b.type === 'Buffer' && Array.isArray(b.data)) {\n return `<${b.data.length} bytes>`;\n }\n return v;\n });\n return text === undefined ? undefined : JSON.parse(text);\n } catch (e) {\n return `<not serializable: ${(e as Error).message}>`;\n }\n}\n\n/** Everything an error carries that helps — an API error's status and body, the stack. */\nexport function errorInfo(e: unknown): Record<string, unknown> {\n if (!(e instanceof Error)) {\n return { message: String(e) };\n }\n const info: Record<string, unknown> = { name: e.name, message: e.message };\n for (const key of ['status', 'code', 'type', 'param', 'error', 'request_id', 'requestID']) {\n const v = (e as unknown as Record<string, unknown>)[key];\n if (v !== undefined && v !== null) {\n info[key] = v;\n }\n }\n if (e.stack) {\n info.stack = e.stack;\n }\n return info;\n}\n\n/** Derive the transport from a request source when the caller did not say. */\nexport function transportForSource(source: string): JournalTransport {\n if (source === 'chat') {\n return 'chat';\n }\n if (source === '') {\n return 'state';\n }\n if (source === 'wyoming') {\n return 'wyoming';\n }\n if (source.startsWith('telegram:')) {\n return 'telegram';\n }\n return 'script';\n}\n\nexport class Journal {\n private entries: JournalEntry[] = [];\n private readonly store = new AsyncLocalStorage<JournalEntry>();\n private saveTimer: NodeJS.Timeout | null = null;\n private counter = 0;\n /** Steps of the newest requests; older ones are read back from their file. */\n private readonly traces = new Map<string, JournalStep[]>();\n /** Traces changed since they were last written. */\n private readonly dirty = new Set<string>();\n /** LLM tool calls in progress per request: a state access inside one belongs to that tool's step. */\n private readonly toolDepth = new WeakMap<JournalEntry, number>();\n private readonly traceDir: string;\n\n constructor(private readonly opts: JournalOptions) {\n this.traceDir = opts.file ? traceDirFor(opts.file) : '';\n }\n\n get enabled(): boolean {\n return this.opts.hours > 0;\n }\n\n /** Load the entries of the last run (silently starts empty when there are none). */\n async load(): Promise<void> {\n if (!this.enabled || !this.opts.file) {\n return;\n }\n try {\n const raw = JSON.parse(await fs.readFile(this.opts.file, 'utf8')) as unknown;\n if (Array.isArray(raw)) {\n // An entry still open at shutdown never got its answer.\n this.entries = (raw as JournalEntry[])\n .filter(e => e && typeof e.id === 'string' && typeof e.ts === 'number')\n .map(e => (e.done ? e : { ...e, done: true, error: e.error || 'interrupted' }));\n this.prune();\n }\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code !== 'ENOENT') {\n this.opts.log.warn(`Journal could not be read: ${(e as Error).message}`);\n }\n }\n }\n\n /** Entries newer than `since` (ms), oldest first. */\n list(since = 0): JournalEntry[] {\n this.prune();\n return this.entries.filter(e => e.ts > since);\n }\n\n clear(): void {\n this.entries = [];\n this.traces.clear();\n this.dirty.clear();\n this.scheduleSave();\n }\n\n /** The entry of a request (the chat asks for it together with its trace). */\n get(id: string): JournalEntry | undefined {\n return this.entries.find(e => e.id === id);\n }\n\n /** The processing steps of a request, or null when none were kept (too old, or from an older version). */\n async trace(id: string): Promise<JournalStep[] | null> {\n if (!ID_RE.test(id) || !this.get(id)) {\n return null;\n }\n const steps = this.traces.get(id);\n if (steps) {\n return steps;\n }\n if (!this.traceDir) {\n return null;\n }\n try {\n const raw = JSON.parse(await fs.readFile(path.join(this.traceDir, `${id}.json`), 'utf8')) as {\n steps?: unknown;\n };\n return Array.isArray(raw?.steps) ? (raw.steps as JournalStep[]) : null;\n } catch {\n return null;\n }\n }\n\n /**\n * Run `fn` as one journalled request. The entry is published once when the request starts (so the\n * chat shows the question while the answer is still being worked out) and once when it is finished.\n */\n async run(question: string, source: string, origin: JournalOrigin, fn: () => Promise<string>): Promise<string> {\n if (!this.enabled) {\n return fn();\n }\n const entry: JournalEntry = {\n id: `${Date.now().toString(36)}-${(this.counter++).toString(36)}`,\n ts: Date.now(),\n done: false,\n source,\n transport: origin.transport || transportForSource(source),\n question,\n tools: [],\n read: [],\n written: [],\n };\n for (const key of ['satellite', 'room', 'wakeWord', 'sttMs', 'audioMs'] as const) {\n if (origin[key] !== undefined && origin[key] !== '') {\n (entry as unknown as Record<string, unknown>)[key] = origin[key];\n }\n }\n // A satellite that re-opened its mic reports no wake word — say so instead of \"unknown\".\n if (origin.wakeWord === '') {\n entry.wakeWord = '';\n }\n this.entries.push(entry);\n this.traces.set(entry.id, []);\n this.addStep(entry, {\n stage: 'request',\n title: `Request from ${source || entry.transport} (${entry.transport})`,\n data: { question, source, origin },\n });\n this.publish(entry);\n try {\n const answer = await this.store.run(entry, fn);\n entry.answer = answer;\n return answer;\n } catch (e) {\n entry.error = (e as Error).message;\n this.addStep(entry, { stage: 'error', level: 'error', title: entry.error, data: errorInfo(e) });\n throw e;\n } finally {\n entry.done = true;\n entry.ms = Date.now() - entry.ts;\n this.dirty.add(entry.id);\n this.publish(entry);\n this.prune();\n this.scheduleSave();\n }\n }\n\n /** Add a step to the current request's trace (nothing outside of one). */\n step(input: JournalStepInput): void {\n const entry = this.store.getStore();\n if (entry) {\n this.addStep(entry, input);\n }\n }\n\n /**\n * Run an LLM tool as part of the current request: the call lands in the entry's tool list and, with the\n * full arguments and result, in the trace.\n */\n async toolCall<T>(name: string, args: Record<string, unknown>, fn: () => Promise<T>): Promise<T> {\n const entry = this.store.getStore();\n if (!entry) {\n return fn();\n }\n this.toolDepth.set(entry, (this.toolDepth.get(entry) || 0) + 1);\n const start = Date.now();\n try {\n const result = await fn();\n const ms = Date.now() - start;\n this.addStep(entry, { stage: 'tool', title: `Tool ${name}`, ms, data: { args, result } });\n this.tool(name, args, ms, result);\n return result;\n } catch (e) {\n const ms = Date.now() - start;\n const message = (e as Error).message;\n this.addStep(entry, {\n stage: 'tool',\n level: 'error',\n title: `Tool ${name} failed: ${message}`,\n ms,\n data: { args, error: errorInfo(e) },\n });\n this.tool(name, args, ms, undefined, message);\n throw e;\n } finally {\n this.toolDepth.set(entry, (this.toolDepth.get(entry) || 1) - 1);\n }\n }\n\n /**\n * Run a state call (MCP `get_states`/`set_state`/…) as part of the current request: the ids land in the\n * entry's read/written lists, and — unless an LLM tool made the call, whose step already shows it — the\n * call with its result in the trace.\n */\n async stateCall<T>(name: string, args: Record<string, unknown> | undefined, fn: () => Promise<T>): Promise<T> {\n const entry = this.store.getStore();\n if (!entry) {\n return fn();\n }\n this.stateAccess(name, args);\n if (this.toolDepth.get(entry)) {\n return fn();\n }\n const start = Date.now();\n try {\n const result = await fn();\n this.addStep(entry, { stage: 'state', title: name, ms: Date.now() - start, data: { args, result } });\n return result;\n } catch (e) {\n this.addStep(entry, {\n stage: 'state',\n level: 'error',\n title: `${name} failed: ${(e as Error).message}`,\n ms: Date.now() - start,\n data: { args, error: errorInfo(e) },\n });\n throw e;\n }\n }\n\n /**\n * A transport synthesised the reply to the latest request of `source`. That happens after the answer,\n * outside of the request, so the entry is found by its source.\n */\n spoken(source: string, speech: JournalSpeech): void {\n const now = Date.now();\n for (let i = this.entries.length - 1; i >= 0; i--) {\n const entry = this.entries[i];\n if (entry.source !== source) {\n continue;\n }\n if (!entry.done || entry.ttsMs !== undefined || now - entry.ts - (entry.ms || 0) > SPEECH_WINDOW_MS) {\n return;\n }\n entry.ttsMs = speech.ms;\n entry.replyAudioMs = Math.round(speech.audioMs);\n const voice = speech.voice ? ` (${speech.voice})` : '';\n this.addStep(entry, {\n stage: 'tts',\n title: `Speech synthesis: ${speech.provider}${voice}, ${(speech.audioMs / 1000).toFixed(1)} s of audio`,\n ms: speech.ms,\n data: speech,\n });\n this.dirty.add(entry.id);\n this.publish(entry);\n this.scheduleSave();\n return;\n }\n }\n\n private addStep(entry: JournalEntry, input: JournalStepInput): void {\n const steps = this.traces.get(entry.id);\n if (!steps || steps.length > MAX_STEPS) {\n return;\n }\n if (steps.length === MAX_STEPS) {\n steps.push({\n t: Date.now() - entry.ts,\n stage: 'error',\n level: 'warn',\n title: `More than ${MAX_STEPS} steps — the rest is not recorded`,\n });\n return;\n }\n const step: JournalStep = {\n t: Date.now() - entry.ts - (input.ms || 0),\n stage: input.stage,\n title: input.title,\n };\n if (input.ms !== undefined) {\n step.ms = input.ms;\n }\n if (input.level) {\n step.level = input.level;\n }\n if (input.tokens) {\n step.tokens = input.tokens;\n const sum = (entry.tokens ||= { input: 0, output: 0 });\n sum.input += input.tokens.input || 0;\n sum.output += input.tokens.output || 0;\n if (input.tokens.cacheRead) {\n sum.cacheRead = (sum.cacheRead || 0) + input.tokens.cacheRead;\n }\n if (input.tokens.cacheWrite) {\n sum.cacheWrite = (sum.cacheWrite || 0) + input.tokens.cacheWrite;\n }\n }\n if (input.data !== undefined) {\n step.data = snapshot(input.data);\n }\n steps.push(step);\n }\n\n /** Fill in fields of the current request's entry (tier, detail, followUp …). */\n note(fields: Partial<Pick<JournalEntry, 'tier' | 'detail' | 'followUp'>>): void {\n const entry = this.store.getStore();\n if (entry) {\n Object.assign(entry, fields);\n }\n }\n\n /** Record a tool call the model made for the current request. */\n tool(name: string, args: Record<string, unknown>, ms: number, result: unknown, error?: string): void {\n const entry = this.store.getStore();\n if (!entry) {\n return;\n }\n const item: JournalTool = { name, args: args || {}, ms };\n if (error) {\n item.error = error;\n } else {\n let text: string;\n try {\n text = typeof result === 'string' ? result : JSON.stringify(result);\n } catch {\n text = String(result);\n }\n item.result = text.length > RESULT_CHARS ? `${text.slice(0, RESULT_CHARS)}…` : text;\n }\n entry.tools.push(item);\n // Live view: show each tool call as it happens, a cloud round can take seconds.\n this.publish(entry);\n }\n\n /** Record which states a call reads or writes (MCP `get_states`/`set_state`/`set_states`, routines). */\n stateAccess(tool: string, args: Record<string, unknown> | undefined): void {\n const entry = this.store.getStore();\n if (!entry || !args) {\n return;\n }\n if (tool === 'get_states' && Array.isArray(args.ids)) {\n for (const id of args.ids) {\n if (typeof id === 'string' && !entry.read.includes(id)) {\n entry.read.push(id);\n }\n }\n } else if (tool === 'get_state' && typeof args.id === 'string') {\n if (!entry.read.includes(args.id)) {\n entry.read.push(args.id);\n }\n } else if (tool === 'set_state' && typeof args.id === 'string') {\n entry.written.push({ id: args.id, value: args.value });\n } else if (tool === 'set_states' && Array.isArray(args.states)) {\n for (const s of args.states as { id?: unknown; value?: unknown }[]) {\n if (typeof s?.id === 'string') {\n entry.written.push({ id: s.id, value: s.value });\n }\n }\n }\n }\n\n /** Write pending changes now (on unload). */\n async flush(): Promise<void> {\n if (this.saveTimer) {\n clearTimeout(this.saveTimer);\n this.saveTimer = null;\n }\n await this.save();\n }\n\n private publish(entry: JournalEntry): void {\n try {\n this.opts.onChange?.(entry);\n } catch {\n // a listener must never break the answer\n }\n }\n\n private prune(): void {\n const cutoff = Date.now() - this.opts.hours * 3600_000;\n let start = 0;\n while (start < this.entries.length && this.entries[start].ts < cutoff) {\n start++;\n }\n start = Math.max(start, this.entries.length - MAX_ENTRIES);\n if (start > 0) {\n for (const e of this.entries.slice(0, start)) {\n this.traces.delete(e.id);\n this.dirty.delete(e.id);\n }\n this.entries = this.entries.slice(start);\n }\n // Without a file the memory is all there is: keep as many as would be on disk.\n const keep = this.traceDir ? MEMORY_TRACES : MAX_TRACES;\n for (const id of this.traces.keys()) {\n if (this.traces.size <= keep) {\n break;\n }\n // Not written yet, or still being answered: needed until it is on disk.\n if ((!this.traceDir || !this.dirty.has(id)) && this.get(id)?.done !== false) {\n this.traces.delete(id);\n }\n }\n }\n\n private scheduleSave(): void {\n if (!this.opts.file || this.saveTimer) {\n return;\n }\n this.saveTimer = setTimeout(() => {\n this.saveTimer = null;\n void this.save();\n }, SAVE_DELAY_MS);\n this.saveTimer.unref?.();\n }\n\n private async save(): Promise<void> {\n if (!this.opts.file) {\n return;\n }\n try {\n await fs.mkdir(path.dirname(this.opts.file), { recursive: true });\n await fs.writeFile(this.opts.file, JSON.stringify(this.entries.filter(e => e.done)));\n } catch (e) {\n this.opts.log.warn(`Journal could not be saved: ${(e as Error).message}`);\n }\n await this.saveTraces();\n this.prune();\n }\n\n /** Write the traces that changed, and delete the files of requests that are no longer kept. */\n private async saveTraces(): Promise<void> {\n try {\n await fs.mkdir(this.traceDir, { recursive: true });\n for (const id of [...this.dirty]) {\n const steps = this.traces.get(id);\n this.dirty.delete(id);\n if (steps) {\n await fs.writeFile(path.join(this.traceDir, `${id}.json`), JSON.stringify({ id, steps }));\n }\n }\n const keep = new Set(this.entries.slice(-MAX_TRACES).map(e => e.id));\n for (const name of await fs.readdir(this.traceDir)) {\n if (name.endsWith('.json') && !keep.has(name.slice(0, -5))) {\n await fs.rm(path.join(this.traceDir, name), { force: true });\n }\n }\n } catch (e) {\n this.opts.log.warn(`Journal traces could not be saved: ${(e as Error).message}`);\n }\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"captures.js","sourceRoot":"","sources":["../../../src/lib/voice/captures.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAwCH,oCAOC;AAMD,0CAOC;AA1DD,+BAAiC;AACjC,+
|
|
1
|
+
{"version":3,"file":"captures.js","sourceRoot":"","sources":["../../../src/lib/voice/captures.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Diagnosis recordings: microphone audio that a satellite captured but that never became a request — dropped as\n * silence, or returned empty by the speech recognition. Without the audio there is no telling whether a false\n * wake heard the TV, a quiet microphone missed a voice, or the recognition failed on real speech.\n *\n * Each recording is a WAV plus a JSON with what the voice activity detection made of it and a level profile (RMS\n * per 100 ms), so the levels can be read without an audio editor. Only the newest {@link MAX_CAPTURES} are kept.\n * The store does not care where files go; the adapter hands it its file storage (Admin → Files).\n */\n\nimport { pcmToWav } from './stt';\nimport { rms, type Bytes, type VadReport } from './vad';\n\n/** Why a recording was dropped. */\nexport type DiscardReason =\n /** nobody started speaking before the start timeout */\n | 'no-speech'\n /** sound, but never loud enough for the speech threshold */\n | 'silence'\n /** speech by level, but the recognition returned no text */\n | 'empty-stt';\n\nexport interface DiscardedCapture {\n device: string;\n room: string;\n /** Wake word that opened the mic; '' = re-opened for a follow-up. */\n wakeWord: string;\n reason: DiscardReason;\n pcm: Bytes;\n sampleRate: number;\n vad: VadReport;\n}\n\n/** File operations, relative to the store's root (the adapter's file storage). */\nexport interface CaptureFiles {\n write(path: string, data: Buffer | string): Promise<void>;\n /** File names in `dir`; empty (not an error) when it does not exist yet. */\n list(dir: string): Promise<string[]>;\n remove(path: string): Promise<void>;\n}\n\nexport const CAPTURE_DIR = 'captures';\n/** ~400 KB per 12 s recording at 16 kHz, so at most ~20 MB. */\nexport const MAX_CAPTURES = 50;\n/** Resolution of the level profile in the JSON. */\nexport const LEVEL_STEP_MS = 100;\n\n/** RMS per `stepMs` of mono 16-bit PCM, rounded. */\nexport function levelProfile(pcm: Bytes, sampleRate: number, stepMs = LEVEL_STEP_MS): number[] {\n const step = Math.max(2, Math.round((sampleRate * stepMs) / 1000) * 2);\n const levels: number[] = [];\n for (let offset = 0; offset < pcm.length; offset += step) {\n levels.push(Math.round(rms(pcm.subarray(offset, Math.min(pcm.length, offset + step)))));\n }\n return levels;\n}\n\n/**\n * `2026-11-03_08-33-36-123_<device>_<reason>` in local time: sorts chronologically by name, which is what\n * pruning relies on, and reads well in the Admin file list.\n */\nexport function captureBaseName(date: Date, device: string, reason: DiscardReason): string {\n const pad = (n: number, len = 2): string => String(n).padStart(len, '0');\n const time =\n `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}_` +\n `${pad(date.getHours())}-${pad(date.getMinutes())}-${pad(date.getSeconds())}-${pad(date.getMilliseconds(), 3)}`;\n const name = (device || 'satellite').replace(/[^\\w.-]/g, '_').slice(0, 60);\n return `${time}_${name}_${reason}`;\n}\n\nexport class CaptureStore {\n /** Saves run one after another, so two at once cannot prune each other's files. */\n private queue: Promise<unknown> = Promise.resolve();\n\n constructor(\n private readonly files: CaptureFiles,\n private readonly max = MAX_CAPTURES,\n ) {}\n\n /** Store one recording and drop the oldest beyond the limit. Resolves with the path of the WAV. */\n save(capture: DiscardedCapture, now = new Date()): Promise<string> {\n const run = this.queue.then(() => this.write(capture, now));\n this.queue = run.catch(() => undefined);\n return run;\n }\n\n private async write(capture: DiscardedCapture, now: Date): Promise<string> {\n const base = `${CAPTURE_DIR}/${captureBaseName(now, capture.device, capture.reason)}`;\n const info = {\n time: now.toISOString(),\n device: capture.device,\n room: capture.room,\n wakeWord: capture.wakeWord || '(follow-up)',\n reason: capture.reason,\n seconds: Math.round((capture.pcm.length / 2 / capture.sampleRate) * 100) / 100,\n sampleRate: capture.sampleRate,\n vad: capture.vad,\n levelStepMs: LEVEL_STEP_MS,\n levels: levelProfile(capture.pcm, capture.sampleRate),\n };\n await this.files.write(`${base}.wav`, pcmToWav(Buffer.from(capture.pcm), capture.sampleRate));\n await this.files.write(`${base}.json`, `${JSON.stringify(info, null, 2)}\\n`);\n await this.prune();\n return `${base}.wav`;\n }\n\n private async prune(): Promise<void> {\n const names = (await this.files.list(CAPTURE_DIR)).filter(n => /\\.(wav|json)$/.test(n));\n const bases = [...new Set(names.map(n => n.replace(/\\.(wav|json)$/, '')))].sort();\n for (const base of bases.slice(0, Math.max(0, bases.length - this.max))) {\n for (const name of names.filter(n => n.replace(/\\.(wav|json)$/, '') === base)) {\n await this.files.remove(`${CAPTURE_DIR}/${name}`).catch(() => undefined);\n }\n }\n }\n}\n"]}
|
|
@@ -62,7 +62,9 @@ const net = __importStar(require("node:net"));
|
|
|
62
62
|
const esphomeProto_1 = require("./esphomeProto");
|
|
63
63
|
const esphomeEntities_1 = require("./esphomeEntities");
|
|
64
64
|
const vad_1 = require("./vad");
|
|
65
|
+
const speechAudio_1 = require("./speechAudio");
|
|
65
66
|
const stt_1 = require("./stt");
|
|
67
|
+
const captures_1 = require("./captures");
|
|
66
68
|
/** `VoiceAssistantEvent` (api.proto) — the subset we emit. */
|
|
67
69
|
const EVENT = {
|
|
68
70
|
ERROR: 0,
|
|
@@ -97,8 +99,9 @@ const MIC_SAMPLE_RATE = 16000;
|
|
|
97
99
|
*
|
|
98
100
|
* so the chirp is still above the gate at 500 ms and only clears it around 550–600. 600 ms is the
|
|
99
101
|
* smallest value that rejects a chirp-only wake while still detecting the speech in every one of the
|
|
100
|
-
* four captures, with no change to when the utterance closes.
|
|
101
|
-
*
|
|
102
|
+
* four captures, with no change to when the utterance closes. Only the analysis skips it: STT gets the audio
|
|
103
|
+
* from 400 ms before the detected speech (`speechAudio.ts`), so a speaker who starts inside this window loses
|
|
104
|
+
* nothing but a late gate open.
|
|
102
105
|
*/
|
|
103
106
|
const WAKE_CHIRP_MS = 600;
|
|
104
107
|
/** Default API port of an ESPHome device. */
|
|
@@ -124,6 +127,15 @@ exports.DEFAULT_SPEECH_LEVEL = 250;
|
|
|
124
127
|
const NO_SPEECH_MS = 5000;
|
|
125
128
|
/** The same after a follow-up question re-opened the mic: the user may need a moment to think. */
|
|
126
129
|
const NO_SPEECH_FOLLOW_UP_MS = 8000;
|
|
130
|
+
/** Silence that ends an utterance when the configuration leaves it empty. */
|
|
131
|
+
const DEFAULT_SILENCE_MS = 900;
|
|
132
|
+
/**
|
|
133
|
+
* After this much speech a pause may be {@link LONG_PAUSE_FACTOR} times as long before the utterance ends: whoever
|
|
134
|
+
* talks that long is dictating a request and pauses to think, while "Licht an" should still close quickly. Field
|
|
135
|
+
* report 2026-11-03: "Я хочу, чтоб ты создал задачу, чтобы утром говорить мне…" was cut off right there.
|
|
136
|
+
*/
|
|
137
|
+
const LONG_REQUEST_MS = 2500;
|
|
138
|
+
const LONG_PAUSE_FACTOR = 1.5;
|
|
127
139
|
/** Silence threshold as a share of the speech threshold — keeps the default pair 250/150. */
|
|
128
140
|
const SILENCE_SHARE = 0.6;
|
|
129
141
|
/**
|
|
@@ -590,11 +602,14 @@ class EsphomeConnection {
|
|
|
590
602
|
void this.finishCapture();
|
|
591
603
|
}, PIPELINE_TIMEOUT_MS);
|
|
592
604
|
timer.unref();
|
|
605
|
+
const silenceMs = this.opts.silenceMs || DEFAULT_SILENCE_MS;
|
|
593
606
|
this.capture = {
|
|
594
607
|
chunks: [],
|
|
595
608
|
detector: new vad_1.SpeechDetector({
|
|
596
609
|
sampleRate: MIC_SAMPLE_RATE,
|
|
597
|
-
silenceMs
|
|
610
|
+
silenceMs,
|
|
611
|
+
longSilenceMs: Math.round(silenceMs * LONG_PAUSE_FACTOR),
|
|
612
|
+
longAfterMs: LONG_REQUEST_MS,
|
|
598
613
|
// Microphone gain differs wildly between these devices, so fixed RMS levels do not
|
|
599
614
|
// travel: derive them from the room instead, once the wake chirp is out of the way.
|
|
600
615
|
skipMs: WAKE_CHIRP_MS,
|
|
@@ -634,13 +649,11 @@ class EsphomeConnection {
|
|
|
634
649
|
void this.finishCapture();
|
|
635
650
|
}
|
|
636
651
|
}
|
|
637
|
-
/** Stop the mic stream and run STT → answer → TTS for what was captured. */
|
|
638
652
|
/** Hand a recording that never became a request to {@link EsphomeSatellitesOptions.onDiscarded}. */
|
|
639
|
-
discard(capture, pcm, reason) {
|
|
653
|
+
discard(capture, pcm, reason, vad) {
|
|
640
654
|
if (!this.opts.onDiscarded || !pcm.length) {
|
|
641
655
|
return;
|
|
642
656
|
}
|
|
643
|
-
const d = capture.detector;
|
|
644
657
|
try {
|
|
645
658
|
this.opts.onDiscarded({
|
|
646
659
|
device: this.deviceName,
|
|
@@ -649,19 +662,15 @@ class EsphomeConnection {
|
|
|
649
662
|
reason,
|
|
650
663
|
pcm,
|
|
651
664
|
sampleRate: MIC_SAMPLE_RATE,
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
analysedPeak: Math.round(d.analysedPeak),
|
|
655
|
-
noiseFloor: Math.round(d.noiseFloor),
|
|
656
|
-
startLevel: Math.round(d.startLevel),
|
|
657
|
-
endLevel: Math.round(d.endLevel),
|
|
658
|
-
},
|
|
665
|
+
// The store writes its own level profile next to it.
|
|
666
|
+
vad: { ...vad, levels: undefined },
|
|
659
667
|
});
|
|
660
668
|
}
|
|
661
669
|
catch (e) {
|
|
662
670
|
this.opts.log.debug(`ESPHome ${this.deviceName}: keeping the recording failed — ${e.message}`);
|
|
663
671
|
}
|
|
664
672
|
}
|
|
673
|
+
/** Stop the mic stream and run STT → answer → TTS for what was captured. */
|
|
665
674
|
async finishCapture(end) {
|
|
666
675
|
const capture = this.capture;
|
|
667
676
|
if (!capture || capture.closed) {
|
|
@@ -674,9 +683,12 @@ class EsphomeConnection {
|
|
|
674
683
|
this.event(EVENT.STT_VAD_END);
|
|
675
684
|
const pcm = Buffer.concat(capture.chunks);
|
|
676
685
|
const seconds = pcm.length / 2 / MIC_SAMPLE_RATE;
|
|
686
|
+
const vad = { ...capture.detector.report(), end: end ?? 'stream' };
|
|
687
|
+
const startedAt = vad.speechStartMs !== undefined ? `, speech from ${(vad.speechStartMs / 1000).toFixed(2)} s` : '';
|
|
688
|
+
const endedBy = vad.end === 'speech-end' ? `ended after ${vad.silenceMs} ms of silence` : `ended by ${vad.end}`;
|
|
677
689
|
this.opts.log.debug(`ESPHome ${this.deviceName}: captured ${seconds.toFixed(2)} s ` +
|
|
678
|
-
`(peak RMS ${
|
|
679
|
-
|
|
690
|
+
`(peak RMS ${vad.peak}, noise floor ${vad.noiseFloor}, speech/silence gate ${vad.startLevel}/${vad.endLevel}` +
|
|
691
|
+
`${startedAt}, ${endedBy})`);
|
|
680
692
|
if (!pcm.length || !capture.detector.sawSpeech) {
|
|
681
693
|
const loudest = Math.round(capture.detector.analysedPeak);
|
|
682
694
|
const gate = Math.round(capture.detector.startLevel);
|
|
@@ -690,7 +702,7 @@ class EsphomeConnection {
|
|
|
690
702
|
else {
|
|
691
703
|
this.opts.log.debug(`ESPHome ${this.deviceName}: nothing but silence${end === 'no-speech' ? ' (nobody started speaking)' : ''} — dropping the utterance`);
|
|
692
704
|
}
|
|
693
|
-
this.discard(capture, pcm, end === 'no-speech' ? 'no-speech' : 'silence');
|
|
705
|
+
this.discard(capture, pcm, end === 'no-speech' ? 'no-speech' : 'silence', vad);
|
|
694
706
|
this.event(EVENT.RUN_END);
|
|
695
707
|
this.status('idle');
|
|
696
708
|
return;
|
|
@@ -698,15 +710,19 @@ class EsphomeConnection {
|
|
|
698
710
|
this.status('processing');
|
|
699
711
|
try {
|
|
700
712
|
const hints = this.opts.getHints ? await this.opts.getHints() : undefined;
|
|
713
|
+
const speech = (0, speechAudio_1.prepareForStt)(pcm, MIC_SAMPLE_RATE, { startMs: vad.speechStartMs, endLevel: vad.endLevel });
|
|
714
|
+
vad.stt = { fromMs: speech.fromMs, toMs: speech.toMs, gain: speech.gain };
|
|
715
|
+
this.opts.log.debug(`ESPHome ${this.deviceName}: speech recognition gets ${(speech.fromMs / 1000).toFixed(2)}–` +
|
|
716
|
+
`${(speech.toMs / 1000).toFixed(2)} s${speech.gain > 1 ? `, amplified ×${speech.gain}` : ''}`);
|
|
701
717
|
const sttStart = Date.now();
|
|
702
|
-
const text = (await this.opts.stt.transcribe(pcm, MIC_SAMPLE_RATE, this.opts.language, hints)).trim();
|
|
718
|
+
const text = (await this.opts.stt.transcribe(speech.pcm, MIC_SAMPLE_RATE, this.opts.language, hints)).trim();
|
|
703
719
|
const sttMs = Date.now() - sttStart;
|
|
704
720
|
this.event(EVENT.STT_END, { text });
|
|
705
721
|
if (!text) {
|
|
706
722
|
// Speech was heard, so the user waits for an answer: say why none comes. Typically a word in another
|
|
707
723
|
// language than the STT listens in (a German "Ja" to a Russian STT comes back empty).
|
|
708
724
|
this.opts.log.info(`Voice Q (${this.deviceName}): (empty) — speech recognition returned no text for ${seconds.toFixed(1)} s of audio`);
|
|
709
|
-
this.discard(capture, pcm, 'empty-stt');
|
|
725
|
+
this.discard(capture, pcm, 'empty-stt', vad);
|
|
710
726
|
this.event(EVENT.RUN_END);
|
|
711
727
|
this.status('idle');
|
|
712
728
|
return;
|
|
@@ -718,12 +734,8 @@ class EsphomeConnection {
|
|
|
718
734
|
wakeWord: capture.wakeWord,
|
|
719
735
|
sttMs,
|
|
720
736
|
audioMs: Math.round(seconds * 1000),
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
noiseFloor: Math.round(capture.detector.noiseFloor),
|
|
724
|
-
startLevel: Math.round(capture.detector.startLevel),
|
|
725
|
-
endLevel: Math.round(capture.detector.endLevel),
|
|
726
|
-
},
|
|
737
|
+
// Numbers only, no audio: shows where a request was cut off without keeping recordings.
|
|
738
|
+
vad: { ...vad, levels: (0, captures_1.levelProfile)(pcm, MIC_SAMPLE_RATE) },
|
|
727
739
|
})).trim();
|
|
728
740
|
const followUp = !!answer && this.opts.expectsFollowUp?.(answer, this.deviceName) === true;
|
|
729
741
|
this.event(EVENT.INTENT_END, followUp ? { continue_conversation: '1' } : undefined);
|