aiden-runtime 4.0.2 → 4.1.1
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 +19 -11
- package/config/hardware.json +2 -2
- package/dist/api/server.js +50 -52
- package/dist/cli/v4/aidenCLI.js +424 -7
- package/dist/cli/v4/aidenPrompt.js +317 -0
- package/dist/cli/v4/box.js +105 -39
- package/dist/cli/v4/callbacks.js +39 -6
- package/dist/cli/v4/chatSession.js +256 -55
- package/dist/cli/v4/citationFooter.js +97 -0
- package/dist/cli/v4/commands/channel.js +656 -0
- package/dist/cli/v4/commands/clear.js +1 -1
- package/dist/cli/v4/commands/compress.js +1 -1
- package/dist/cli/v4/commands/cron.js +44 -16
- package/dist/cli/v4/commands/fanout.js +236 -0
- package/dist/cli/v4/commands/help.js +15 -4
- package/dist/cli/v4/commands/history.js +84 -0
- package/dist/cli/v4/commands/index.js +16 -1
- package/dist/cli/v4/commands/mcp.js +358 -0
- package/dist/cli/v4/commands/show.js +43 -0
- package/dist/cli/v4/commands/skills.js +169 -4
- package/dist/cli/v4/commands/status.js +84 -0
- package/dist/cli/v4/commands/subagent.js +78 -0
- package/dist/cli/v4/commands/verbose.js +1 -1
- package/dist/cli/v4/commands/voice.js +218 -0
- package/dist/cli/v4/cronCli.js +103 -0
- package/dist/cli/v4/display.js +297 -13
- package/dist/cli/v4/doctor.js +102 -1
- package/dist/cli/v4/doctorLiveness.js +329 -0
- package/dist/cli/v4/envSources.js +105 -0
- package/dist/cli/v4/ghostMatch.js +74 -0
- package/dist/cli/v4/historyStore.js +163 -0
- package/dist/cli/v4/pasteCompression.js +124 -0
- package/dist/cli/v4/pasteIntercept.js +203 -0
- package/dist/cli/v4/replyRenderer.js +209 -0
- package/dist/cli/v4/resizeGuard.js +92 -0
- package/dist/cli/v4/shellInterpolation.js +139 -0
- package/dist/cli/v4/skinEngine.js +21 -1
- package/dist/cli/v4/streamingPrefix.js +121 -0
- package/dist/cli/v4/syntaxHighlight.js +345 -0
- package/dist/cli/v4/table.js +216 -0
- package/dist/cli/v4/themeDetect.js +81 -0
- package/dist/cli/v4/uiBuild.js +74 -0
- package/dist/cli/v4/voiceCli.js +113 -0
- package/dist/cli/v4/voicePromptApi.js +196 -0
- package/dist/core/channels/discord.js +16 -10
- package/dist/core/channels/email.js +13 -9
- package/dist/core/channels/imessage.js +13 -9
- package/dist/core/channels/manager.js +25 -7
- package/dist/core/channels/pdf-extract.js +180 -0
- package/dist/core/channels/photo-vision.js +157 -0
- package/dist/core/channels/signal.js +11 -7
- package/dist/core/channels/slack.js +13 -10
- package/dist/core/channels/telegram-commands.js +154 -0
- package/dist/core/channels/telegram-groups.js +198 -0
- package/dist/core/channels/telegram-rate-limit.js +124 -0
- package/dist/core/channels/telegram.js +1980 -0
- package/dist/core/channels/twilio.js +11 -7
- package/dist/core/channels/webhook.js +9 -5
- package/dist/core/channels/whatsapp.js +15 -11
- package/dist/core/channels/whisper-transcribe.js +163 -0
- package/dist/core/cronManager.js +33 -294
- package/dist/core/gateway.js +29 -8
- package/dist/core/playwrightBridge.js +90 -0
- package/dist/core/v4/aidenAgent.js +35 -0
- package/dist/core/v4/auxiliaryClient.js +2 -2
- package/dist/core/v4/cron/atomicWrite.js +18 -4
- package/dist/core/v4/cron/cronExecute.js +300 -0
- package/dist/core/v4/cron/cronManager.js +502 -0
- package/dist/core/v4/cron/cronState.js +314 -0
- package/dist/core/v4/cron/cronTick.js +90 -0
- package/dist/core/v4/cron/diagnostics.js +104 -0
- package/dist/core/v4/cron/graceWindow.js +79 -0
- package/dist/core/v4/logger/factory.js +110 -0
- package/dist/core/v4/logger/index.js +22 -0
- package/dist/core/v4/logger/logger.js +101 -0
- package/dist/core/v4/logger/sinks/fileSink.js +110 -0
- package/dist/core/v4/logger/sinks/multiSink.js +43 -0
- package/dist/core/v4/logger/sinks/nullSink.js +53 -0
- package/dist/core/v4/logger/sinks/stdSink.js +81 -0
- package/dist/core/v4/mcp/server/diagnostics.js +40 -0
- package/dist/core/v4/mcp/server/skillBridge.js +94 -0
- package/dist/core/v4/mcp/server/stdioServer.js +119 -0
- package/dist/core/v4/mcp/server/toolBridge.js +168 -0
- package/dist/core/v4/platformPaths.js +105 -0
- package/dist/core/v4/providerFallback.js +25 -0
- package/dist/core/v4/skillLoader.js +21 -5
- package/dist/core/v4/skillMining/candidateStore.js +164 -0
- package/dist/core/v4/skillMining/extractorPrompt.js +118 -0
- package/dist/core/v4/skillMining/proposalBuilder.js +140 -0
- package/dist/core/v4/skillMining/skillMiner.js +191 -0
- package/dist/core/v4/skillMining/traceFingerprint.js +51 -0
- package/dist/core/v4/subagent/budget.js +76 -0
- package/dist/core/v4/subagent/diagnostics.js +22 -0
- package/dist/core/v4/subagent/fanout.js +216 -0
- package/dist/core/v4/subagent/merger.js +148 -0
- package/dist/core/v4/subagent/providerRotation.js +54 -0
- package/dist/core/v4/voice/audioStream.js +373 -0
- package/dist/core/v4/voice/cliVoice.js +393 -0
- package/dist/core/v4/voice/diagnostics.js +66 -0
- package/dist/core/v4/voice/ttsStream.js +193 -0
- package/dist/core/version.js +1 -1
- package/dist/core/visionAnalyze.js +291 -90
- package/dist/core/voice/audio.js +61 -5
- package/dist/core/voice/audioBackend.js +134 -0
- package/dist/core/voice/stt.js +61 -6
- package/dist/core/voice/tts.js +19 -3
- package/dist/moat/dangerousPatterns.js +1 -1
- package/dist/providers/v4/codexResponsesAdapter.js +7 -2
- package/dist/providers/v4/errors.js +51 -1
- package/dist/providers/v4/ollamaPromptToolsAdapter.js +9 -2
- package/dist/tools/v4/index.js +32 -1
- package/dist/tools/v4/subagent/subagentFanout.js +190 -0
- package/package.json +11 -2
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2026 Shiva Deore (Taracod).
|
|
4
|
+
* Licensed under AGPL-3.0. See LICENSE for details.
|
|
5
|
+
*
|
|
6
|
+
* Aiden — local-first agent.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* core/v4/voice/audioStream.ts — Phase v4.1-voice-cli
|
|
10
|
+
*
|
|
11
|
+
* Streaming microphone capture with lazy-loaded backend.
|
|
12
|
+
* Two-tier fallback:
|
|
13
|
+
*
|
|
14
|
+
* 1. PRIMARY: `decibri` — Rust/cpal via napi-rs, prebuilt binaries
|
|
15
|
+
* for Win/mac/Linux. Zero compile, zero system bin, true
|
|
16
|
+
* streaming Readable, 16k PCM native.
|
|
17
|
+
* 2. FALLBACK: `node-record-lpcm16` — shells out to `sox`/`rec`.
|
|
18
|
+
* Used when decibri's prebuilt binary is unavailable for the
|
|
19
|
+
* target arch (rare).
|
|
20
|
+
* 3. UNAVAILABLE: neither installs — `startStream()` returns null
|
|
21
|
+
* and the caller surfaces a clear "install sox or check mic
|
|
22
|
+
* drivers" hint via `aiden voice doctor`.
|
|
23
|
+
*
|
|
24
|
+
* Lazy import is mandatory — eager loading the audio library breaks
|
|
25
|
+
* SSH-only / Docker / WSL boots where no audio device exists. We
|
|
26
|
+
* import at first use, cache the resolved backend, and never
|
|
27
|
+
* re-probe.
|
|
28
|
+
*
|
|
29
|
+
* Idle-timeout 5min auto-close mirrors `core/playwrightBridge.ts`
|
|
30
|
+
* — no use of mic for 5 minutes → release the device handle. Voice
|
|
31
|
+
* mode re-acquires on next `startStream()`.
|
|
32
|
+
*
|
|
33
|
+
* The stream emits Int16 PCM frames at 16 kHz / mono. Each frame is
|
|
34
|
+
* a Buffer the consumer can compute RMS over (cliVoice does this
|
|
35
|
+
* for VAD).
|
|
36
|
+
*/
|
|
37
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
38
|
+
if (k2 === undefined) k2 = k;
|
|
39
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
40
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
41
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
42
|
+
}
|
|
43
|
+
Object.defineProperty(o, k2, desc);
|
|
44
|
+
}) : (function(o, m, k, k2) {
|
|
45
|
+
if (k2 === undefined) k2 = k;
|
|
46
|
+
o[k2] = m[k];
|
|
47
|
+
}));
|
|
48
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
49
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
50
|
+
}) : function(o, v) {
|
|
51
|
+
o["default"] = v;
|
|
52
|
+
});
|
|
53
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
54
|
+
var ownKeys = function(o) {
|
|
55
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
56
|
+
var ar = [];
|
|
57
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
58
|
+
return ar;
|
|
59
|
+
};
|
|
60
|
+
return ownKeys(o);
|
|
61
|
+
};
|
|
62
|
+
return function (mod) {
|
|
63
|
+
if (mod && mod.__esModule) return mod;
|
|
64
|
+
var result = {};
|
|
65
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
66
|
+
__setModuleDefault(result, mod);
|
|
67
|
+
return result;
|
|
68
|
+
};
|
|
69
|
+
})();
|
|
70
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
71
|
+
exports.resolveAudioBackend = resolveAudioBackend;
|
|
72
|
+
exports.__resetAudioBackend = __resetAudioBackend;
|
|
73
|
+
exports.computeRms = computeRms;
|
|
74
|
+
exports.computePeakRms = computePeakRms;
|
|
75
|
+
exports.startAudioStream = startAudioStream;
|
|
76
|
+
exports.getAudioDiagnostics = getAudioDiagnostics;
|
|
77
|
+
const node_events_1 = require("node:events");
|
|
78
|
+
const factory_1 = require("../logger/factory");
|
|
79
|
+
// ── Idle-timeout state ────────────────────────────────────────────────────
|
|
80
|
+
const IDLE_MS = 5 * 60 * 1000;
|
|
81
|
+
let _activeBackend = null;
|
|
82
|
+
let _idleTimer = null;
|
|
83
|
+
let _activeHandle = null;
|
|
84
|
+
function resetIdleTimer(logger) {
|
|
85
|
+
if (_idleTimer)
|
|
86
|
+
clearTimeout(_idleTimer);
|
|
87
|
+
_idleTimer = setTimeout(() => {
|
|
88
|
+
logger.info('audio stream: idle 5min — releasing backend');
|
|
89
|
+
_activeBackend = null;
|
|
90
|
+
_idleTimer = null;
|
|
91
|
+
}, IDLE_MS);
|
|
92
|
+
}
|
|
93
|
+
// ── Backend probing ───────────────────────────────────────────────────────
|
|
94
|
+
let _resolvedBackend = null;
|
|
95
|
+
/**
|
|
96
|
+
* Probe which mic backend is usable on this system. Cached after
|
|
97
|
+
* first call. Pass `force` to re-probe (rare; only useful after the
|
|
98
|
+
* user installs a missing dep mid-session).
|
|
99
|
+
*/
|
|
100
|
+
async function resolveAudioBackend(logger = (0, factory_1.noopLogger)(), force = false) {
|
|
101
|
+
if (_resolvedBackend && !force)
|
|
102
|
+
return _resolvedBackend;
|
|
103
|
+
// Tier 1: decibri prebuilt
|
|
104
|
+
try {
|
|
105
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
106
|
+
require.resolve('decibri');
|
|
107
|
+
_resolvedBackend = 'decibri';
|
|
108
|
+
logger.info('audio stream: backend = decibri (prebuilt)');
|
|
109
|
+
return _resolvedBackend;
|
|
110
|
+
}
|
|
111
|
+
catch { /* not installed */ }
|
|
112
|
+
// Tier 2: node-record-lpcm16 (requires sox on PATH)
|
|
113
|
+
try {
|
|
114
|
+
require.resolve('node-record-lpcm16');
|
|
115
|
+
_resolvedBackend = 'node-record-lpcm16';
|
|
116
|
+
logger.info('audio stream: backend = node-record-lpcm16 (requires sox)');
|
|
117
|
+
return _resolvedBackend;
|
|
118
|
+
}
|
|
119
|
+
catch { /* not installed */ }
|
|
120
|
+
_resolvedBackend = 'unavailable';
|
|
121
|
+
logger.warn('audio stream: no backend available — install `decibri` (npm) or `sox` + `node-record-lpcm16`');
|
|
122
|
+
return _resolvedBackend;
|
|
123
|
+
}
|
|
124
|
+
/** Test-only: clear the cached backend so the next call re-probes. */
|
|
125
|
+
function __resetAudioBackend() {
|
|
126
|
+
_resolvedBackend = null;
|
|
127
|
+
}
|
|
128
|
+
// ── RMS helper ────────────────────────────────────────────────────────────
|
|
129
|
+
/** Root-mean-square of a 16-bit signed PCM buffer. Returns 0 for
|
|
130
|
+
* empty buffers. Used by the VAD in `cliVoice.ts`. */
|
|
131
|
+
function computeRms(pcm) {
|
|
132
|
+
if (pcm.length < 2)
|
|
133
|
+
return 0;
|
|
134
|
+
let sum = 0;
|
|
135
|
+
let count = 0;
|
|
136
|
+
for (let i = 0; i + 1 < pcm.length; i += 2) {
|
|
137
|
+
const sample = pcm.readInt16LE(i);
|
|
138
|
+
sum += sample * sample;
|
|
139
|
+
count += 1;
|
|
140
|
+
}
|
|
141
|
+
if (count === 0)
|
|
142
|
+
return 0;
|
|
143
|
+
return Math.round(Math.sqrt(sum / count));
|
|
144
|
+
}
|
|
145
|
+
/** Peak amplitude (absolute value) over a 16-bit signed PCM buffer.
|
|
146
|
+
* Peak RMS check on stop — rejects "no speech ever" recordings
|
|
147
|
+
* whose mean RMS is dragged down by trailing silence. */
|
|
148
|
+
function computePeakRms(pcm) {
|
|
149
|
+
if (pcm.length < 2)
|
|
150
|
+
return 0;
|
|
151
|
+
let peak = 0;
|
|
152
|
+
for (let i = 0; i + 1 < pcm.length; i += 2) {
|
|
153
|
+
const sample = Math.abs(pcm.readInt16LE(i));
|
|
154
|
+
if (sample > peak)
|
|
155
|
+
peak = sample;
|
|
156
|
+
}
|
|
157
|
+
return peak;
|
|
158
|
+
}
|
|
159
|
+
// ── Stream factory ────────────────────────────────────────────────────────
|
|
160
|
+
/**
|
|
161
|
+
* Start streaming microphone PCM. Returns null when no backend is
|
|
162
|
+
* available — caller surfaces a friendly error.
|
|
163
|
+
*/
|
|
164
|
+
async function startAudioStream(opts = {}) {
|
|
165
|
+
const logger = (opts.logger ?? (0, factory_1.noopLogger)()).child('audio-stream');
|
|
166
|
+
const sampleRate = opts.sampleRate ?? 16000;
|
|
167
|
+
const channels = opts.channels ?? 1;
|
|
168
|
+
const backend = await resolveAudioBackend(logger);
|
|
169
|
+
if (backend === 'unavailable')
|
|
170
|
+
return null;
|
|
171
|
+
// Refuse a second concurrent stream — the audio device handle is
|
|
172
|
+
// singleton (mirrors `playwrightBridge` invariant).
|
|
173
|
+
if (_activeHandle && !_activeHandle.closed) {
|
|
174
|
+
logger.warn('audio stream: already active — refusing concurrent claim');
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
_activeBackend = backend;
|
|
178
|
+
resetIdleTimer(logger);
|
|
179
|
+
const handle = backend === 'decibri'
|
|
180
|
+
? await startDecibri({ sampleRate, channels, logger })
|
|
181
|
+
: await startNodeRecord({ sampleRate, channels, logger });
|
|
182
|
+
if (!handle)
|
|
183
|
+
return null;
|
|
184
|
+
_activeHandle = handle;
|
|
185
|
+
return handle;
|
|
186
|
+
}
|
|
187
|
+
// ── decibri backend ───────────────────────────────────────────────────────
|
|
188
|
+
async function startDecibri(args) {
|
|
189
|
+
const { logger, sampleRate, channels } = args;
|
|
190
|
+
let mod;
|
|
191
|
+
try {
|
|
192
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
193
|
+
mod = require('decibri');
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
logger.warn('audio stream: decibri load failed', {
|
|
197
|
+
error: err instanceof Error ? err.message : String(err),
|
|
198
|
+
});
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
// The decibri public surface is `Recorder` or `Input`; the exact
|
|
202
|
+
// shape varies by minor version. We accept either via a tiny
|
|
203
|
+
// adapter so this module survives a v0.x → v1.x bump.
|
|
204
|
+
const factory = mod.Recorder
|
|
205
|
+
?? mod.Input;
|
|
206
|
+
if (typeof factory !== 'function') {
|
|
207
|
+
logger.warn('audio stream: decibri exports unrecognised shape — falling back');
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
let buffers = [];
|
|
211
|
+
const events = new node_events_1.EventEmitter();
|
|
212
|
+
let closed = false;
|
|
213
|
+
let recorder = null;
|
|
214
|
+
try {
|
|
215
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
216
|
+
recorder = new factory({ sampleRate, channels });
|
|
217
|
+
// Decibri exposes a Readable on `.stream` or directly is one.
|
|
218
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
219
|
+
const stream = recorder.stream ?? recorder;
|
|
220
|
+
if (stream && typeof stream.on === 'function') {
|
|
221
|
+
stream.on('data', (pcm) => {
|
|
222
|
+
if (closed)
|
|
223
|
+
return;
|
|
224
|
+
buffers.push(pcm);
|
|
225
|
+
const rms = computeRms(pcm);
|
|
226
|
+
events.emit('frame', { pcm, rms });
|
|
227
|
+
});
|
|
228
|
+
stream.on('error', (err) => {
|
|
229
|
+
logger.warn('audio stream: decibri stream error', { error: err.message });
|
|
230
|
+
events.emit('error', err);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
catch (err) {
|
|
235
|
+
logger.warn('audio stream: decibri init failed', {
|
|
236
|
+
error: err instanceof Error ? err.message : String(err),
|
|
237
|
+
});
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
events,
|
|
242
|
+
get closed() { return closed; },
|
|
243
|
+
async stop() {
|
|
244
|
+
if (closed)
|
|
245
|
+
return Buffer.concat(buffers);
|
|
246
|
+
closed = true;
|
|
247
|
+
try {
|
|
248
|
+
recorder?.stop?.();
|
|
249
|
+
}
|
|
250
|
+
catch { /* ignore */ }
|
|
251
|
+
try {
|
|
252
|
+
recorder?.close?.();
|
|
253
|
+
}
|
|
254
|
+
catch { /* ignore */ }
|
|
255
|
+
_activeHandle = null;
|
|
256
|
+
const out = Buffer.concat(buffers);
|
|
257
|
+
buffers = [];
|
|
258
|
+
return out;
|
|
259
|
+
},
|
|
260
|
+
cancel() {
|
|
261
|
+
if (closed)
|
|
262
|
+
return;
|
|
263
|
+
closed = true;
|
|
264
|
+
try {
|
|
265
|
+
recorder?.stop?.();
|
|
266
|
+
}
|
|
267
|
+
catch { /* ignore */ }
|
|
268
|
+
try {
|
|
269
|
+
recorder?.close?.();
|
|
270
|
+
}
|
|
271
|
+
catch { /* ignore */ }
|
|
272
|
+
_activeHandle = null;
|
|
273
|
+
buffers = [];
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
// ── node-record-lpcm16 backend ────────────────────────────────────────────
|
|
278
|
+
async function startNodeRecord(args) {
|
|
279
|
+
const { logger, sampleRate, channels } = args;
|
|
280
|
+
let mod;
|
|
281
|
+
try {
|
|
282
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
283
|
+
mod = require('node-record-lpcm16');
|
|
284
|
+
}
|
|
285
|
+
catch (err) {
|
|
286
|
+
logger.warn('audio stream: node-record-lpcm16 load failed', {
|
|
287
|
+
error: err instanceof Error ? err.message : String(err),
|
|
288
|
+
});
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
292
|
+
const recordFn = mod.record;
|
|
293
|
+
if (typeof recordFn !== 'function') {
|
|
294
|
+
logger.warn('audio stream: node-record-lpcm16 exports unrecognised shape');
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
let buffers = [];
|
|
298
|
+
const events = new node_events_1.EventEmitter();
|
|
299
|
+
let closed = false;
|
|
300
|
+
let recording = null;
|
|
301
|
+
try {
|
|
302
|
+
recording = recordFn({
|
|
303
|
+
sampleRate,
|
|
304
|
+
channels,
|
|
305
|
+
audioType: 'wav',
|
|
306
|
+
threshold: 0,
|
|
307
|
+
});
|
|
308
|
+
const stream = recording.stream?.();
|
|
309
|
+
if (stream) {
|
|
310
|
+
stream.on('data', (pcm) => {
|
|
311
|
+
if (closed)
|
|
312
|
+
return;
|
|
313
|
+
buffers.push(pcm);
|
|
314
|
+
const rms = computeRms(pcm);
|
|
315
|
+
events.emit('frame', { pcm, rms });
|
|
316
|
+
});
|
|
317
|
+
stream.on('error', (err) => {
|
|
318
|
+
logger.warn('audio stream: node-record-lpcm16 stream error', { error: err.message });
|
|
319
|
+
events.emit('error', err);
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
catch (err) {
|
|
324
|
+
logger.warn('audio stream: node-record-lpcm16 init failed', {
|
|
325
|
+
error: err instanceof Error ? err.message : String(err),
|
|
326
|
+
});
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
return {
|
|
330
|
+
events,
|
|
331
|
+
get closed() { return closed; },
|
|
332
|
+
async stop() {
|
|
333
|
+
if (closed)
|
|
334
|
+
return Buffer.concat(buffers);
|
|
335
|
+
closed = true;
|
|
336
|
+
try {
|
|
337
|
+
recording?.stop?.();
|
|
338
|
+
}
|
|
339
|
+
catch { /* ignore */ }
|
|
340
|
+
_activeHandle = null;
|
|
341
|
+
const out = Buffer.concat(buffers);
|
|
342
|
+
buffers = [];
|
|
343
|
+
return out;
|
|
344
|
+
},
|
|
345
|
+
cancel() {
|
|
346
|
+
if (closed)
|
|
347
|
+
return;
|
|
348
|
+
closed = true;
|
|
349
|
+
try {
|
|
350
|
+
recording?.stop?.();
|
|
351
|
+
}
|
|
352
|
+
catch { /* ignore */ }
|
|
353
|
+
_activeHandle = null;
|
|
354
|
+
buffers = [];
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
async function getAudioDiagnostics(logger = (0, factory_1.noopLogger)()) {
|
|
359
|
+
const resolved = await resolveAudioBackend(logger);
|
|
360
|
+
let soxOnPath = false;
|
|
361
|
+
try {
|
|
362
|
+
const { execSync } = await Promise.resolve().then(() => __importStar(require('node:child_process')));
|
|
363
|
+
const probe = process.platform === 'win32' ? 'where sox' : 'which sox';
|
|
364
|
+
execSync(probe, { stdio: 'ignore', timeout: 2000 });
|
|
365
|
+
soxOnPath = true;
|
|
366
|
+
}
|
|
367
|
+
catch { /* sox not on PATH */ }
|
|
368
|
+
return {
|
|
369
|
+
resolved,
|
|
370
|
+
active: !!_activeHandle && !_activeHandle.closed,
|
|
371
|
+
soxOnPath,
|
|
372
|
+
};
|
|
373
|
+
}
|