aiden-runtime 4.0.1 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/README.md +11 -7
  2. package/config/hardware.json +2 -2
  3. package/dist/api/server.js +50 -52
  4. package/dist/cli/v4/aidenCLI.js +513 -14
  5. package/dist/cli/v4/aidenPrompt.js +317 -0
  6. package/dist/cli/v4/box.js +105 -39
  7. package/dist/cli/v4/callbacks.js +39 -6
  8. package/dist/cli/v4/chatSession.js +269 -52
  9. package/dist/cli/v4/citationFooter.js +97 -0
  10. package/dist/cli/v4/commands/channel.js +656 -0
  11. package/dist/cli/v4/commands/clear.js +1 -1
  12. package/dist/cli/v4/commands/compress.js +1 -1
  13. package/dist/cli/v4/commands/cron.js +44 -16
  14. package/dist/cli/v4/commands/fanout.js +236 -0
  15. package/dist/cli/v4/commands/help.js +15 -4
  16. package/dist/cli/v4/commands/history.js +84 -0
  17. package/dist/cli/v4/commands/index.js +19 -1
  18. package/dist/cli/v4/commands/mcp.js +358 -0
  19. package/dist/cli/v4/commands/setup.js +34 -0
  20. package/dist/cli/v4/commands/show.js +43 -0
  21. package/dist/cli/v4/commands/skills.js +169 -4
  22. package/dist/cli/v4/commands/status.js +84 -0
  23. package/dist/cli/v4/commands/subagent.js +78 -0
  24. package/dist/cli/v4/commands/verbose.js +1 -1
  25. package/dist/cli/v4/commands/voice.js +218 -0
  26. package/dist/cli/v4/cronCli.js +103 -0
  27. package/dist/cli/v4/display.js +300 -14
  28. package/dist/cli/v4/doctor.js +41 -0
  29. package/dist/cli/v4/envSources.js +105 -0
  30. package/dist/cli/v4/ghostMatch.js +74 -0
  31. package/dist/cli/v4/historyStore.js +163 -0
  32. package/dist/cli/v4/pasteCompression.js +124 -0
  33. package/dist/cli/v4/pasteIntercept.js +203 -0
  34. package/dist/cli/v4/replyRenderer.js +209 -0
  35. package/dist/cli/v4/resizeGuard.js +92 -0
  36. package/dist/cli/v4/setupWizard.js +466 -232
  37. package/dist/cli/v4/shellInterpolation.js +139 -0
  38. package/dist/cli/v4/skinEngine.js +21 -1
  39. package/dist/cli/v4/streamingPrefix.js +121 -0
  40. package/dist/cli/v4/syntaxHighlight.js +345 -0
  41. package/dist/cli/v4/table.js +216 -0
  42. package/dist/cli/v4/themeDetect.js +81 -0
  43. package/dist/cli/v4/uiBuild.js +74 -0
  44. package/dist/cli/v4/voiceCli.js +113 -0
  45. package/dist/cli/v4/voicePromptApi.js +196 -0
  46. package/dist/core/channels/discord.js +16 -10
  47. package/dist/core/channels/email.js +13 -9
  48. package/dist/core/channels/imessage.js +13 -9
  49. package/dist/core/channels/manager.js +25 -7
  50. package/dist/core/channels/pdf-extract.js +180 -0
  51. package/dist/core/channels/photo-vision.js +157 -0
  52. package/dist/core/channels/signal.js +11 -7
  53. package/dist/core/channels/slack.js +13 -10
  54. package/dist/core/channels/telegram-commands.js +154 -0
  55. package/dist/core/channels/telegram-groups.js +198 -0
  56. package/dist/core/channels/telegram-rate-limit.js +124 -0
  57. package/dist/core/channels/telegram.js +1980 -0
  58. package/dist/core/channels/twilio.js +11 -7
  59. package/dist/core/channels/webhook.js +9 -5
  60. package/dist/core/channels/whatsapp.js +15 -11
  61. package/dist/core/channels/whisper-transcribe.js +163 -0
  62. package/dist/core/cronManager.js +33 -294
  63. package/dist/core/gateway.js +29 -8
  64. package/dist/core/playwrightBridge.js +90 -0
  65. package/dist/core/v4/aidenAgent.js +35 -0
  66. package/dist/core/v4/auxiliaryClient.js +2 -2
  67. package/dist/core/v4/cron/atomicWrite.js +18 -4
  68. package/dist/core/v4/cron/cronExecute.js +300 -0
  69. package/dist/core/v4/cron/cronManager.js +502 -0
  70. package/dist/core/v4/cron/cronState.js +314 -0
  71. package/dist/core/v4/cron/cronTick.js +90 -0
  72. package/dist/core/v4/cron/diagnostics.js +104 -0
  73. package/dist/core/v4/cron/graceWindow.js +79 -0
  74. package/dist/core/v4/firstRun/providerDetection.js +287 -0
  75. package/dist/core/v4/logger/factory.js +110 -0
  76. package/dist/core/v4/logger/index.js +22 -0
  77. package/dist/core/v4/logger/logger.js +101 -0
  78. package/dist/core/v4/logger/sinks/fileSink.js +110 -0
  79. package/dist/core/v4/logger/sinks/multiSink.js +43 -0
  80. package/dist/core/v4/logger/sinks/nullSink.js +53 -0
  81. package/dist/core/v4/logger/sinks/stdSink.js +81 -0
  82. package/dist/core/v4/mcp/server/diagnostics.js +40 -0
  83. package/dist/core/v4/mcp/server/skillBridge.js +94 -0
  84. package/dist/core/v4/mcp/server/stdioServer.js +119 -0
  85. package/dist/core/v4/mcp/server/toolBridge.js +168 -0
  86. package/dist/core/v4/platformPaths.js +105 -0
  87. package/dist/core/v4/providerFallback.js +25 -0
  88. package/dist/core/v4/skillLoader.js +21 -5
  89. package/dist/core/v4/skillMining/candidateStore.js +164 -0
  90. package/dist/core/v4/skillMining/extractorPrompt.js +111 -0
  91. package/dist/core/v4/skillMining/proposalBuilder.js +139 -0
  92. package/dist/core/v4/skillMining/skillMiner.js +191 -0
  93. package/dist/core/v4/skillMining/traceFingerprint.js +51 -0
  94. package/dist/core/v4/subagent/budget.js +76 -0
  95. package/dist/core/v4/subagent/diagnostics.js +22 -0
  96. package/dist/core/v4/subagent/fanout.js +216 -0
  97. package/dist/core/v4/subagent/merger.js +148 -0
  98. package/dist/core/v4/subagent/providerRotation.js +54 -0
  99. package/dist/core/v4/voice/audioStream.js +373 -0
  100. package/dist/core/v4/voice/cliVoice.js +393 -0
  101. package/dist/core/v4/voice/diagnostics.js +66 -0
  102. package/dist/core/v4/voice/ttsStream.js +193 -0
  103. package/dist/core/version.js +1 -1
  104. package/dist/core/visionAnalyze.js +291 -90
  105. package/dist/core/voice/audio.js +61 -5
  106. package/dist/core/voice/audioBackend.js +134 -0
  107. package/dist/core/voice/stt.js +61 -6
  108. package/dist/core/voice/tts.js +19 -3
  109. package/dist/providers/v4/nullAdapter.js +58 -0
  110. package/dist/tools/v4/index.js +32 -1
  111. package/dist/tools/v4/subagent/subagentFanout.js +166 -0
  112. 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
+ }