@agentdeck/bridge 1.0.2 → 1.0.4

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 (175) hide show
  1. package/assets/fm-helper/agentdeck-fm-helper +0 -0
  2. package/dist/apme/collector.d.ts.map +1 -1
  3. package/dist/apme/collector.js +5 -2
  4. package/dist/apme/collector.js.map +1 -1
  5. package/dist/apme/runner.d.ts +11 -0
  6. package/dist/apme/runner.d.ts.map +1 -1
  7. package/dist/apme/runner.js +60 -4
  8. package/dist/apme/runner.js.map +1 -1
  9. package/dist/apme/settings.d.ts +10 -0
  10. package/dist/apme/settings.d.ts.map +1 -1
  11. package/dist/apme/settings.js +14 -0
  12. package/dist/apme/settings.js.map +1 -1
  13. package/dist/awaiting-overlay.d.ts +44 -10
  14. package/dist/awaiting-overlay.d.ts.map +1 -1
  15. package/dist/awaiting-overlay.js +96 -28
  16. package/dist/awaiting-overlay.js.map +1 -1
  17. package/dist/bridge-core.d.ts +10 -0
  18. package/dist/bridge-core.d.ts.map +1 -1
  19. package/dist/bridge-core.js +47 -12
  20. package/dist/bridge-core.js.map +1 -1
  21. package/dist/card-feed.d.ts +107 -0
  22. package/dist/card-feed.d.ts.map +1 -0
  23. package/dist/card-feed.js +277 -0
  24. package/dist/card-feed.js.map +1 -0
  25. package/dist/card-modules.d.ts +87 -0
  26. package/dist/card-modules.d.ts.map +1 -0
  27. package/dist/card-modules.js +179 -0
  28. package/dist/card-modules.js.map +1 -0
  29. package/dist/cli.d.ts.map +1 -1
  30. package/dist/cli.js +232 -69
  31. package/dist/cli.js.map +1 -1
  32. package/dist/daemon-server.d.ts.map +1 -1
  33. package/dist/daemon-server.js +1008 -47
  34. package/dist/daemon-server.js.map +1 -1
  35. package/dist/daemon-ws-client.d.ts +2 -1
  36. package/dist/daemon-ws-client.d.ts.map +1 -1
  37. package/dist/daemon-ws-client.js +2 -1
  38. package/dist/daemon-ws-client.js.map +1 -1
  39. package/dist/daemon.js +1 -1
  40. package/dist/device-photo.d.ts +86 -0
  41. package/dist/device-photo.d.ts.map +1 -0
  42. package/dist/device-photo.js +200 -0
  43. package/dist/device-photo.js.map +1 -0
  44. package/dist/device-voice-reply.d.ts +124 -0
  45. package/dist/device-voice-reply.d.ts.map +1 -0
  46. package/dist/device-voice-reply.js +236 -0
  47. package/dist/device-voice-reply.js.map +1 -0
  48. package/dist/device-voice.d.ts +56 -0
  49. package/dist/device-voice.d.ts.map +1 -0
  50. package/dist/device-voice.js +134 -0
  51. package/dist/device-voice.js.map +1 -0
  52. package/dist/esp32-serial.d.ts +40 -0
  53. package/dist/esp32-serial.d.ts.map +1 -1
  54. package/dist/esp32-serial.js +109 -13
  55. package/dist/esp32-serial.js.map +1 -1
  56. package/dist/foundation-models-helper.d.ts +30 -0
  57. package/dist/foundation-models-helper.d.ts.map +1 -1
  58. package/dist/foundation-models-helper.js +45 -0
  59. package/dist/foundation-models-helper.js.map +1 -1
  60. package/dist/idotmatrix/idotmatrix-daemon-sync.d.ts.map +1 -1
  61. package/dist/idotmatrix/idotmatrix-daemon-sync.js +5 -18
  62. package/dist/idotmatrix/idotmatrix-daemon-sync.js.map +1 -1
  63. package/dist/idotmatrix/idotmatrix-discover.d.ts.map +1 -1
  64. package/dist/idotmatrix/idotmatrix-discover.js +6 -14
  65. package/dist/idotmatrix/idotmatrix-discover.js.map +1 -1
  66. package/dist/index.js +2 -2
  67. package/dist/index.js.map +1 -1
  68. package/dist/mdns.d.ts +11 -0
  69. package/dist/mdns.d.ts.map +1 -1
  70. package/dist/mdns.js +13 -0
  71. package/dist/mdns.js.map +1 -1
  72. package/dist/observed-inject.d.ts +119 -0
  73. package/dist/observed-inject.d.ts.map +1 -0
  74. package/dist/observed-inject.js +405 -0
  75. package/dist/observed-inject.js.map +1 -0
  76. package/dist/passive-observer.d.ts +14 -0
  77. package/dist/passive-observer.d.ts.map +1 -1
  78. package/dist/passive-observer.js +35 -5
  79. package/dist/passive-observer.js.map +1 -1
  80. package/dist/peripheral-mapping.d.ts +65 -0
  81. package/dist/peripheral-mapping.d.ts.map +1 -0
  82. package/dist/peripheral-mapping.js +115 -0
  83. package/dist/peripheral-mapping.js.map +1 -0
  84. package/dist/python-ble-runtime.d.ts +62 -0
  85. package/dist/python-ble-runtime.d.ts.map +1 -0
  86. package/dist/python-ble-runtime.js +215 -0
  87. package/dist/python-ble-runtime.js.map +1 -0
  88. package/dist/session-aggregator.d.ts +4 -1
  89. package/dist/session-aggregator.d.ts.map +1 -1
  90. package/dist/session-aggregator.js +5 -5
  91. package/dist/session-aggregator.js.map +1 -1
  92. package/dist/session-id-resolve.d.ts +17 -0
  93. package/dist/session-id-resolve.d.ts.map +1 -0
  94. package/dist/session-id-resolve.js +35 -0
  95. package/dist/session-id-resolve.js.map +1 -0
  96. package/dist/session-transcript-timeline.d.ts +7 -0
  97. package/dist/session-transcript-timeline.d.ts.map +1 -1
  98. package/dist/session-transcript-timeline.js +16 -6
  99. package/dist/session-transcript-timeline.js.map +1 -1
  100. package/dist/timebox/timebox-daemon-sync.d.ts.map +1 -1
  101. package/dist/timebox/timebox-daemon-sync.js +5 -20
  102. package/dist/timebox/timebox-daemon-sync.js.map +1 -1
  103. package/dist/timebox/timebox-discover.d.ts.map +1 -1
  104. package/dist/timebox/timebox-discover.js +6 -15
  105. package/dist/timebox/timebox-discover.js.map +1 -1
  106. package/dist/timeline-store.js +2 -2
  107. package/dist/timeline-store.js.map +1 -1
  108. package/dist/usage-event.d.ts.map +1 -1
  109. package/dist/usage-event.js +14 -7
  110. package/dist/usage-event.js.map +1 -1
  111. package/dist/voice.d.ts +13 -10
  112. package/dist/voice.d.ts.map +1 -1
  113. package/dist/voice.js +26 -272
  114. package/dist/voice.js.map +1 -1
  115. package/dist/ws-server.d.ts +14 -0
  116. package/dist/ws-server.d.ts.map +1 -1
  117. package/dist/ws-server.js +66 -1
  118. package/dist/ws-server.js.map +1 -1
  119. package/fm-helper/AgentDeckFMHelper.swift +303 -0
  120. package/package.json +8 -4
  121. package/python/requirements-ble.txt +5 -0
  122. package/src/idotmatrix/brightness.py +40 -0
  123. package/src/idotmatrix/scan.py +35 -0
  124. package/src/idotmatrix/sync.py +400 -0
  125. package/src/pysync/matrix_sync_common.py +89 -0
  126. package/src/timebox/scan_ble.py +52 -0
  127. package/src/timebox/sync_ble.py +394 -0
  128. package/dist/d200h/hid-protocol.d.ts +0 -62
  129. package/dist/d200h/hid-protocol.d.ts.map +0 -1
  130. package/dist/d200h/hid-protocol.js +0 -124
  131. package/dist/d200h/hid-protocol.js.map +0 -1
  132. package/dist/d200h/image-renderer.d.ts +0 -24
  133. package/dist/d200h/image-renderer.d.ts.map +0 -1
  134. package/dist/d200h/image-renderer.js +0 -321
  135. package/dist/d200h/image-renderer.js.map +0 -1
  136. package/dist/hook-migration.d.ts +0 -2
  137. package/dist/hook-migration.d.ts.map +0 -1
  138. package/dist/hook-migration.js +0 -104
  139. package/dist/hook-migration.js.map +0 -1
  140. package/dist/modules/d200h-module.d.ts +0 -65
  141. package/dist/modules/d200h-module.d.ts.map +0 -1
  142. package/dist/modules/d200h-module.js +0 -495
  143. package/dist/modules/d200h-module.js.map +0 -1
  144. package/dist/modules/d200h-renderer.d.ts +0 -7
  145. package/dist/modules/d200h-renderer.d.ts.map +0 -1
  146. package/dist/modules/d200h-renderer.js +0 -321
  147. package/dist/modules/d200h-renderer.js.map +0 -1
  148. package/dist/modules/trmnl-module.d.ts +0 -38
  149. package/dist/modules/trmnl-module.d.ts.map +0 -1
  150. package/dist/modules/trmnl-module.js +0 -111
  151. package/dist/modules/trmnl-module.js.map +0 -1
  152. package/dist/trmnl/byos-server.d.ts +0 -39
  153. package/dist/trmnl/byos-server.d.ts.map +0 -1
  154. package/dist/trmnl/byos-server.js +0 -301
  155. package/dist/trmnl/byos-server.js.map +0 -1
  156. package/dist/trmnl/frame-cache.d.ts +0 -37
  157. package/dist/trmnl/frame-cache.d.ts.map +0 -1
  158. package/dist/trmnl/frame-cache.js +0 -157
  159. package/dist/trmnl/frame-cache.js.map +0 -1
  160. package/dist/trmnl/image-renderer.d.ts +0 -27
  161. package/dist/trmnl/image-renderer.d.ts.map +0 -1
  162. package/dist/trmnl/image-renderer.js +0 -235
  163. package/dist/trmnl/image-renderer.js.map +0 -1
  164. package/dist/trmnl/trmnl-settings.d.ts +0 -96
  165. package/dist/trmnl/trmnl-settings.d.ts.map +0 -1
  166. package/dist/trmnl/trmnl-settings.js +0 -177
  167. package/dist/trmnl/trmnl-settings.js.map +0 -1
  168. package/dist/trmnl/trmnl-telemetry.d.ts +0 -47
  169. package/dist/trmnl/trmnl-telemetry.d.ts.map +0 -1
  170. package/dist/trmnl/trmnl-telemetry.js +0 -56
  171. package/dist/trmnl/trmnl-telemetry.js.map +0 -1
  172. package/dist/whisper-server-manager.d.ts +0 -19
  173. package/dist/whisper-server-manager.d.ts.map +0 -1
  174. package/dist/whisper-server-manager.js +0 -171
  175. package/dist/whisper-server-manager.js.map +0 -1
package/dist/voice.d.ts CHANGED
@@ -3,15 +3,12 @@ export declare class VoiceManager extends EventEmitter {
3
3
  private recording;
4
4
  private audioProcess;
5
5
  private audioFile;
6
- private whisperBin;
7
- private whisperModel;
8
6
  private recBin;
9
7
  private soxBin;
10
- private hasMetal;
11
- private whisperServerBin;
12
- private serverPort;
13
- private useServer;
14
8
  constructor();
9
+ /** No-op kept so callers written for the whisper-server era keep compiling.
10
+ * Transcription now goes through the bundled Swift helper, which the
11
+ * Foundation Models client already manages as a long-lived process. */
15
12
  connectToServer(): Promise<void>;
16
13
  disconnectFromServer(): void;
17
14
  startRecording(): void;
@@ -20,10 +17,16 @@ export declare class VoiceManager extends EventEmitter {
20
17
  isRecording(): boolean;
21
18
  /** Transcribe a WAV file (used by /voice/transcribe endpoint) */
22
19
  transcribeFile(filePath: string): Promise<string>;
23
- private transcribeViaServer;
24
- private transcribeWithCli;
25
- private resample;
26
- private transcribe;
20
+ /**
21
+ * Transcribe with Apple's on-device recognizer via the bundled Swift helper
22
+ * (the same binary the APME judge uses for Foundation Models). whisper.cpp
23
+ * was retired here: it required arm64 Homebrew, `brew install whisper-cpp`,
24
+ * a ~1.5 GB model download and a second long-lived server process, which is
25
+ * exactly the install surface docs/voice-setup.md removed from the Apple
26
+ * side. Both daemons now transcribe with the same engine and nothing to
27
+ * install.
28
+ */
29
+ private transcribeViaHelper;
27
30
  private cleanup;
28
31
  }
29
32
  //# sourceMappingURL=voice.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"voice.d.ts","sourceRoot":"","sources":["../src/voice.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAuFtC,qBAAa,YAAa,SAAQ,YAAY;IAC5C,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,SAAS,CAAM;IACvB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAU;IAC1B,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,SAAS,CAAS;;IAiBpB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAQtC,oBAAoB,IAAI,IAAI;IAM5B,cAAc,IAAI,IAAI;IA0ChB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;IAwEtC,MAAM,IAAI,IAAI;IAUd,WAAW,IAAI,OAAO;IAItB,iEAAiE;IAC3D,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;YAoBzC,mBAAmB;YAkCnB,iBAAiB;IAyC/B,OAAO,CAAC,QAAQ;IA6BhB,OAAO,CAAC,UAAU;IA+ElB,OAAO,CAAC,OAAO;CAMhB"}
1
+ {"version":3,"file":"voice.d.ts","sourceRoot":"","sources":["../src/voice.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AA+BtC,qBAAa,YAAa,SAAQ,YAAY;IAC5C,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,SAAS,CAAM;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAS;;IASvB;;4EAEwE;IAClE,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAOtC,oBAAoB,IAAI,IAAI;IAI5B,cAAc,IAAI,IAAI;IA0ChB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;IAyDtC,MAAM,IAAI,IAAI;IAUd,WAAW,IAAI,OAAO;IAItB,iEAAiE;IAC3D,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAWvD;;;;;;;;OAQG;YACW,mBAAmB;IAOjC,OAAO,CAAC,OAAO;CAMhB"}
package/dist/voice.js CHANGED
@@ -1,16 +1,11 @@
1
1
  import { EventEmitter } from 'events';
2
- import { spawn, execSync } from 'child_process';
2
+ import { spawn } from 'child_process';
3
3
  import { tmpdir } from 'os';
4
4
  import { join } from 'path';
5
5
  import { unlinkSync, existsSync, statSync, readFileSync } from 'fs';
6
6
  import { debug } from './logger.js';
7
- import { ensureWhisperServer, releaseWhisperServer } from './whisper-server-manager.js';
8
- import { MODEL_SEARCH_DIRS, MODELS_WITH_METAL, MODELS_WITHOUT_METAL, WHISPER_CANDIDATES, REC_CANDIDATES, SOX_CANDIDATES, WHISPER_SERVER_CANDIDATES, } from '@agentdeck/shared';
9
- // Dynamic timeout: base + multiplier × audio duration
10
- // Metal GPU: fast inference; Rosetta/CPU: ~3-4x slower
11
- const TIMEOUT_BASE_MS = 15_000;
12
- const TIMEOUT_MULTIPLIER_METAL = 1; // 15s + 1× audio duration
13
- const TIMEOUT_MULTIPLIER_ROSETTA = 4; // 15s + 4× audio duration
7
+ import { transcribeWithHelper, probeFoundationModelsHelper } from './foundation-models-helper.js';
8
+ import { REC_CANDIDATES, SOX_CANDIDATES } from '@agentdeck/shared';
14
9
  function findBinary(candidates, fallback) {
15
10
  for (const path of candidates) {
16
11
  if (existsSync(path))
@@ -19,52 +14,6 @@ function findBinary(candidates, fallback) {
19
14
  return fallback;
20
15
  }
21
16
  /** Check if a whisper-cli binary has Metal GPU support (native arm64 + libggml-metal). */
22
- function detectMetal(whisperPath) {
23
- // Skip if whisper-cli doesn't exist (just a fallback name, not installed)
24
- if (!existsSync(whisperPath)) {
25
- debug('Voice', `whisper-cli not found, skipping Metal detection`);
26
- return false;
27
- }
28
- try {
29
- const otoolOut = execSync(`otool -L "${whisperPath}"`, { encoding: 'utf8' });
30
- const hasMetal = otoolOut.includes('libggml-metal');
31
- const fileOut = execSync(`file "${whisperPath}"`, { encoding: 'utf8' });
32
- const isArm64 = fileOut.includes('arm64');
33
- debug('Voice', `whisper-cli: arm64=${isArm64}, metal=${hasMetal} (${whisperPath})`);
34
- return hasMetal && isArm64;
35
- }
36
- catch {
37
- debug('Voice', `Could not detect capabilities: ${whisperPath}`);
38
- return false;
39
- }
40
- }
41
- function findWhisperModel(preference) {
42
- for (const model of preference) {
43
- for (const dir of MODEL_SEARCH_DIRS) {
44
- const path = join(dir, model);
45
- if (existsSync(path)) {
46
- debug('Voice', `Selected whisper model: ${path}`);
47
- return path;
48
- }
49
- }
50
- }
51
- debug('Voice', `No whisper model found in: ${MODEL_SEARCH_DIRS.join(', ')}`);
52
- return join(MODEL_SEARCH_DIRS[0], preference[0]);
53
- }
54
- function findFallbackModel(currentModel) {
55
- const all = [...MODELS_WITH_METAL];
56
- const currentName = currentModel.split('/').pop() ?? '';
57
- const idx = all.indexOf(currentName);
58
- // Find a smaller model than current
59
- for (let i = idx + 1; i < all.length; i++) {
60
- for (const dir of MODEL_SEARCH_DIRS) {
61
- const path = join(dir, all[i]);
62
- if (existsSync(path))
63
- return path;
64
- }
65
- }
66
- return null;
67
- }
68
17
  /** Compute RMS energy of a 16-bit PCM WAV file (skip 44-byte header). */
69
18
  function computeRms(wavFile) {
70
19
  const buf = readFileSync(wavFile);
@@ -83,39 +32,25 @@ export class VoiceManager extends EventEmitter {
83
32
  recording = false;
84
33
  audioProcess = null;
85
34
  audioFile = '';
86
- whisperBin;
87
- whisperModel;
88
35
  recBin;
89
36
  soxBin;
90
- hasMetal;
91
- whisperServerBin;
92
- serverPort = null;
93
- useServer = false;
94
37
  constructor() {
95
38
  super();
96
- this.whisperBin = findBinary(WHISPER_CANDIDATES, 'whisper-cli');
97
39
  this.recBin = findBinary(REC_CANDIDATES, 'rec');
98
40
  this.soxBin = findBinary(SOX_CANDIDATES, 'sox');
99
- this.whisperServerBin = findBinary(WHISPER_SERVER_CANDIDATES, 'whisper-server');
100
- this.hasMetal = detectMetal(this.whisperBin);
101
- const preference = this.hasMetal ? MODELS_WITH_METAL : MODELS_WITHOUT_METAL;
102
- this.whisperModel = findWhisperModel(preference);
103
- if (!this.hasMetal) {
104
- debug('Voice', 'WARNING: whisper-cli has no Metal GPU support (x86/Rosetta). Using smaller model for speed. Install arm64 Homebrew for best quality.');
105
- }
106
- debug('Voice', `Binaries: whisper=${this.whisperBin}, rec=${this.recBin}, sox=${this.soxBin}, server=${this.whisperServerBin}`);
41
+ debug('Voice', `Binaries: rec=${this.recBin}, sox=${this.soxBin} (transcription: Apple Speech via bundled helper)`);
107
42
  }
43
+ /** No-op kept so callers written for the whisper-server era keep compiling.
44
+ * Transcription now goes through the bundled Swift helper, which the
45
+ * Foundation Models client already manages as a long-lived process. */
108
46
  async connectToServer() {
109
- const port = await ensureWhisperServer(this.whisperServerBin, this.whisperModel);
110
- if (port) {
111
- this.serverPort = port;
112
- this.useServer = true;
47
+ const status = await probeFoundationModelsHelper();
48
+ if (!status.available) {
49
+ debug('Voice', `Speech helper unavailable: ${status.reason ?? 'unknown'}`);
113
50
  }
114
51
  }
115
52
  disconnectFromServer() {
116
- this.useServer = false;
117
- this.serverPort = null;
118
- releaseWhisperServer();
53
+ // Helper lifetime is owned by foundation-models-helper.ts.
119
54
  }
120
55
  startRecording() {
121
56
  if (this.recording)
@@ -193,22 +128,7 @@ export class VoiceManager extends EventEmitter {
193
128
  }
194
129
  // --- Transcription: prefer whisper-server, fallback to whisper-cli ---
195
130
  try {
196
- let text;
197
- if (this.useServer && this.serverPort) {
198
- // Server mode: skip resample, whisper-server handles format conversion (--convert)
199
- debug('Voice', 'Server mode: skipping sox resample');
200
- try {
201
- text = await this.transcribeViaServer(this.audioFile);
202
- }
203
- catch (serverErr) {
204
- debug('Voice', `Server transcription failed, falling back to whisper-cli: ${serverErr}`);
205
- this.useServer = false;
206
- text = await this.transcribeWithCli(this.audioFile);
207
- }
208
- }
209
- else {
210
- text = await this.transcribeWithCli(this.audioFile);
211
- }
131
+ const text = await this.transcribeViaHelper(this.audioFile);
212
132
  debug('Voice', `Transcription result: "${text.slice(0, 80)}"`);
213
133
  this.emit('transcription', text);
214
134
  this.cleanup();
@@ -242,188 +162,22 @@ export class VoiceManager extends EventEmitter {
242
162
  if (rms < 0.001) {
243
163
  throw new Error('No audio detected — silent recording');
244
164
  }
245
- if (this.useServer && this.serverPort) {
246
- try {
247
- return await this.transcribeViaServer(filePath);
248
- }
249
- catch (serverErr) {
250
- debug('Voice', `Server transcription failed, falling back to whisper-cli: ${serverErr}`);
251
- this.useServer = false;
252
- return await this.transcribeWithCli(filePath);
253
- }
254
- }
255
- return await this.transcribeWithCli(filePath);
256
- }
257
- async transcribeViaServer(audioFile) {
258
- const fileData = readFileSync(audioFile);
259
- const boundary = `----whisper${Date.now()}`;
260
- const filename = audioFile.split('/').pop() || 'audio.wav';
261
- // Build multipart form-data manually (no external deps)
262
- const header = Buffer.from(`--${boundary}\r\n` +
263
- `Content-Disposition: form-data; name="file"; filename="${filename}"\r\n` +
264
- `Content-Type: audio/wav\r\n\r\n`);
265
- const footer = Buffer.from(`\r\n--${boundary}--\r\n`);
266
- const body = Buffer.concat([header, fileData, footer]);
267
- const url = `http://127.0.0.1:${this.serverPort}/inference`;
268
- debug('Voice', `POST ${url} (${fileData.length} bytes audio)`);
269
- const res = await fetch(url, {
270
- method: 'POST',
271
- headers: { 'Content-Type': `multipart/form-data; boundary=${boundary}` },
272
- body,
273
- signal: AbortSignal.timeout(15_000),
274
- });
275
- if (!res.ok) {
276
- throw new Error(`whisper-server returned ${res.status}: ${await res.text()}`);
277
- }
278
- const json = await res.json();
279
- const text = (json.text ?? '').trim();
280
- debug('Voice', `whisper-server response: "${text.slice(0, 80)}"`);
165
+ return await this.transcribeViaHelper(filePath);
166
+ }
167
+ /**
168
+ * Transcribe with Apple's on-device recognizer via the bundled Swift helper
169
+ * (the same binary the APME judge uses for Foundation Models). whisper.cpp
170
+ * was retired here: it required arm64 Homebrew, `brew install whisper-cpp`,
171
+ * a ~1.5 GB model download and a second long-lived server process, which is
172
+ * exactly the install surface docs/voice-setup.md removed from the Apple
173
+ * side. Both daemons now transcribe with the same engine and nothing to
174
+ * install.
175
+ */
176
+ async transcribeViaHelper(audioFile) {
177
+ const text = await transcribeWithHelper(audioFile);
178
+ debug('Voice', `Helper transcription: "${text.slice(0, 80)}"`);
281
179
  return text;
282
180
  }
283
- async transcribeWithCli(audioFile) {
284
- // Resample to 16kHz/16-bit mono WAV (macOS coreaudio may record at 48kHz/32-bit)
285
- const resampledFile = audioFile.replace('.wav', '_16k.wav');
286
- debug('Voice', `Resampling ${audioFile} → ${resampledFile}`);
287
- try {
288
- await this.resample(audioFile, resampledFile);
289
- }
290
- catch (err) {
291
- debug('Voice', `Resample failed, using original: ${err}`);
292
- }
293
- const whisperInput = existsSync(resampledFile) ? resampledFile : audioFile;
294
- debug('Voice', `Whisper input: ${whisperInput}`);
295
- // Calculate dynamic timeout based on audio duration and Metal support
296
- const audioDurationSec = (statSync(whisperInput).size - 44) / 32000; // 16kHz × 16-bit
297
- const multiplier = this.hasMetal ? TIMEOUT_MULTIPLIER_METAL : TIMEOUT_MULTIPLIER_ROSETTA;
298
- const timeoutMs = TIMEOUT_BASE_MS + Math.ceil(audioDurationSec * 1000 * multiplier);
299
- debug('Voice', `Starting whisper-cli transcription (${audioDurationSec.toFixed(1)}s audio, timeout ${(timeoutMs / 1000).toFixed(0)}s, metal=${this.hasMetal})...`);
300
- try {
301
- let text;
302
- try {
303
- text = await this.transcribe(whisperInput, this.whisperModel, timeoutMs);
304
- }
305
- catch (err) {
306
- const isTimeout = err instanceof Error && err.message.includes('timed out');
307
- const fallback = isTimeout ? findFallbackModel(this.whisperModel) : null;
308
- if (fallback) {
309
- debug('Voice', `Timeout with current model, retrying with fallback: ${fallback}`);
310
- this.whisperModel = fallback; // persist for future calls
311
- text = await this.transcribe(whisperInput, fallback, timeoutMs);
312
- }
313
- else {
314
- throw err;
315
- }
316
- }
317
- if (existsSync(resampledFile))
318
- try {
319
- unlinkSync(resampledFile);
320
- }
321
- catch { /* */ }
322
- return text;
323
- }
324
- catch (err) {
325
- if (existsSync(resampledFile))
326
- try {
327
- unlinkSync(resampledFile);
328
- }
329
- catch { /* */ }
330
- throw err;
331
- }
332
- }
333
- resample(inputFile, outputFile) {
334
- return new Promise((resolve, reject) => {
335
- // Resample to 16kHz/16-bit mono + gentle highpass (80Hz removes rumble, preserves voice)
336
- // + normalize volume for consistent whisper input
337
- const sox = spawn(this.soxBin, [
338
- inputFile,
339
- '-r', '16000',
340
- '-c', '1',
341
- '-b', '16',
342
- outputFile,
343
- 'highpass', '80',
344
- 'norm',
345
- ]);
346
- let stderr = '';
347
- sox.stderr?.on('data', (data) => { stderr += data.toString(); });
348
- sox.on('error', (err) => reject(new Error(`sox spawn error: ${err.message}`)));
349
- sox.on('close', (code) => {
350
- if (code !== 0) {
351
- reject(new Error(`sox exited with code ${code}: ${stderr.slice(-200)}`));
352
- }
353
- else {
354
- debug('Voice', `Resampled OK (${statSync(outputFile).size} bytes)`);
355
- resolve();
356
- }
357
- });
358
- });
359
- }
360
- transcribe(audioFile, modelPath, timeoutMs) {
361
- return new Promise((resolve, reject) => {
362
- const args = [
363
- '-m', modelPath,
364
- '-l', 'auto', // auto-detect language (supports Korean, English, etc.)
365
- '-f', audioFile,
366
- '--no-timestamps',
367
- '-np', // suppress whisper's own progress output
368
- '--prompt', 'coding, programming, Claude, terminal, git, function, component, API',
369
- ];
370
- debug('Voice', `${this.whisperBin} ${args.join(' ')}`);
371
- const whisper = spawn(this.whisperBin, args);
372
- let stdout = '';
373
- let stderr = '';
374
- let settled = false;
375
- // Timeout guard (dynamic based on audio length + Metal support)
376
- const timer = setTimeout(() => {
377
- if (!settled) {
378
- settled = true;
379
- debug('Voice', `whisper-cli timed out after ${(timeoutMs / 1000).toFixed(0)}s, killing`);
380
- whisper.kill('SIGKILL');
381
- reject(new Error(`whisper-cli timed out after ${(timeoutMs / 1000).toFixed(0)}s`));
382
- }
383
- }, timeoutMs);
384
- whisper.stdout.on('data', (data) => {
385
- stdout += data.toString();
386
- });
387
- whisper.stderr.on('data', (data) => {
388
- stderr += data.toString();
389
- });
390
- whisper.on('error', (err) => {
391
- if (!settled) {
392
- settled = true;
393
- clearTimeout(timer);
394
- debug('Voice', `whisper-cli spawn error: ${err.message}`);
395
- reject(new Error(`Failed to run whisper-cli: ${err.message}`));
396
- }
397
- });
398
- whisper.on('close', (code) => {
399
- if (settled)
400
- return;
401
- settled = true;
402
- clearTimeout(timer);
403
- debug('Voice', `whisper-cli exited with code ${code}`);
404
- if (stderr.length > 0) {
405
- debug('Voice', `whisper stderr (last 200): ${stderr.slice(-200)}`);
406
- }
407
- if (code !== 0) {
408
- reject(new Error(`whisper-cli exited with code ${code}: ${stderr.slice(-300)}`));
409
- return;
410
- }
411
- const text = stdout
412
- .split('\n')
413
- .map((line) => line.trim())
414
- .filter((line) => line.length > 0 &&
415
- !line.startsWith('[') && // skip timestamp lines
416
- line !== '(blank audio)' && // skip blank audio markers
417
- !line.startsWith('(') && // skip other whisper annotations
418
- !/^\[BLANK_AUDIO\]$/i.test(line) // skip blank audio tags
419
- )
420
- .join(' ')
421
- .trim();
422
- debug('Voice', `whisper stdout raw (${stdout.length} chars): "${stdout.slice(0, 100)}"`);
423
- resolve(text);
424
- });
425
- });
426
- }
427
181
  cleanup() {
428
182
  if (this.audioFile && existsSync(this.audioFile)) {
429
183
  try {
package/dist/voice.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"voice.js","sourceRoot":"","sources":["../src/voice.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"voice.js","sourceRoot":"","sources":["../src/voice.ts"],"names":[],"mappings":"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"}
@@ -5,10 +5,16 @@ export declare class WsServer {
5
5
  private wss;
6
6
  private commandCallback;
7
7
  private rawMessageCallback;
8
+ private binaryCallback;
9
+ private onPongCallback;
8
10
  private onConnectCallback;
9
11
  private onDisconnectCallback;
10
12
  private clientAlive;
11
13
  private esp32Clients;
14
+ private recentConnectsByIp;
15
+ private flappingClients;
16
+ private knownBoardIps;
17
+ private socketRemoteIp;
12
18
  private eventTransformer;
13
19
  private ulanziClients;
14
20
  private tuiClients;
@@ -19,10 +25,18 @@ export declare class WsServer {
19
25
  onBroadcast(hook: (event: BridgeEvent) => void): void;
20
26
  setEventTransformer(transformer: ((event: BridgeEvent, client: WebSocket) => BridgeEvent | null) | null): void;
21
27
  isEsp32Client(ws: WebSocket): boolean;
28
+ /** True when this socket's remote IP has been reconnecting fast enough to
29
+ * count as flapping (see the connection handler). Callers use it to skip
30
+ * the heavy parts of the initial burst so a marginal client can't resonate
31
+ * the daemon into saturation. */
32
+ isFlappingClient(ws: WebSocket): boolean;
22
33
  markEsp32Client(ws: WebSocket): void;
23
34
  private payloadFor;
24
35
  broadcast(event: BridgeEvent): void;
25
36
  onCommand(callback: (cmd: PluginCommand) => void): void;
37
+ /** Register a callback for binary frames (device audio). */
38
+ onPong(callback: (ws: WebSocket) => void): void;
39
+ onBinary(callback: (data: Buffer, sender: WebSocket) => void): void;
26
40
  /** Register a callback for raw messages before PluginCommand dispatch. Return true to consume. */
27
41
  onRawMessage(callback: (msg: Record<string, unknown>, sender: WebSocket) => boolean): void;
28
42
  /** Broadcast to all clients except the sender */
@@ -1 +1 @@
1
- {"version":3,"file":"ws-server.d.ts","sourceRoot":"","sources":["../src/ws-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,SAAS,EAAE,MAAM,IAAI,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,EAAmB,MAAM,MAAM,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAK7D,qBAAa,QAAQ;IACnB,OAAO,CAAC,GAAG,CAAkB;IAC7B,OAAO,CAAC,eAAe,CAA+C;IACtE,OAAO,CAAC,kBAAkB,CAA+E;IACzG,OAAO,CAAC,iBAAiB,CAA0C;IACnE,OAAO,CAAC,oBAAoB,CAA0C;IACtE,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,YAAY,CAAwB;IAC5C,OAAO,CAAC,gBAAgB,CAAgF;IAGxG,OAAO,CAAC,aAAa,CAAwB;IAK7C,OAAO,CAAC,UAAU,CAAsD;IACxE,OAAO,CAAC,SAAS,CAAiC;gBAEtC,MAAM,EAAE,MAAM;IAiH1B,OAAO,CAAC,cAAc,CAA2C;IAEjE,sFAAsF;IACtF,WAAW,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,GAAG,IAAI;IAIrD,mBAAmB,CAAC,WAAW,EAAE,CAAC,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,KAAK,WAAW,GAAG,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI;IAI9G,aAAa,CAAC,EAAE,EAAE,SAAS,GAAG,OAAO;IAIrC,eAAe,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI;IAIpC,OAAO,CAAC,UAAU;IAOlB,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;IAiBnC,SAAS,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,IAAI,GAAG,IAAI;IAIvD,kGAAkG;IAClG,YAAY,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,SAAS,KAAK,OAAO,GAAG,IAAI;IAI1F,iDAAiD;IACjD,eAAe,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,GAAG,IAAI;IAW5D,eAAe,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,GAAG,IAAI;IAIxD,kBAAkB,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,GAAG,IAAI;IAI3D,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI;IAQ/C,cAAc,IAAI,MAAM;IAIxB,oBAAoB,IAAI,MAAM;IAI9B;;oDAEgD;IAChD,aAAa,IAAI,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAMpD,KAAK,IAAI,IAAI;CASd"}
1
+ {"version":3,"file":"ws-server.d.ts","sourceRoot":"","sources":["../src/ws-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,SAAS,EAAE,MAAM,IAAI,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,EAAmB,MAAM,MAAM,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAK7D,qBAAa,QAAQ;IACnB,OAAO,CAAC,GAAG,CAAkB;IAC7B,OAAO,CAAC,eAAe,CAA+C;IACtE,OAAO,CAAC,kBAAkB,CAA+E;IACzG,OAAO,CAAC,cAAc,CAA4D;IAClF,OAAO,CAAC,cAAc,CAA0C;IAChE,OAAO,CAAC,iBAAiB,CAA0C;IACnE,OAAO,CAAC,oBAAoB,CAA0C;IACtE,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,YAAY,CAAwB;IAE5C,OAAO,CAAC,kBAAkB,CAA+B;IACzD,OAAO,CAAC,eAAe,CAAwB;IAO/C,OAAO,CAAC,aAAa,CAAqB;IAC1C,OAAO,CAAC,cAAc,CAAgC;IACtD,OAAO,CAAC,gBAAgB,CAAgF;IAGxG,OAAO,CAAC,aAAa,CAAwB;IAK7C,OAAO,CAAC,UAAU,CAAsD;IACxE,OAAO,CAAC,SAAS,CAAiC;gBAEtC,MAAM,EAAE,MAAM;IAoJ1B,OAAO,CAAC,cAAc,CAA2C;IAEjE,sFAAsF;IACtF,WAAW,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,GAAG,IAAI;IAIrD,mBAAmB,CAAC,WAAW,EAAE,CAAC,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,KAAK,WAAW,GAAG,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI;IAI9G,aAAa,CAAC,EAAE,EAAE,SAAS,GAAG,OAAO;IAIrC;;;sCAGkC;IAClC,gBAAgB,CAAC,EAAE,EAAE,SAAS,GAAG,OAAO;IAIxC,eAAe,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI;IAMpC,OAAO,CAAC,UAAU;IAOlB,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;IAiBnC,SAAS,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,IAAI,GAAG,IAAI;IAIvD,4DAA4D;IAC5D,MAAM,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,GAAG,IAAI;IAI/C,QAAQ,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,KAAK,IAAI,GAAG,IAAI;IAInE,kGAAkG;IAClG,YAAY,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,SAAS,KAAK,OAAO,GAAG,IAAI;IAI1F,iDAAiD;IACjD,eAAe,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,GAAG,IAAI;IAW5D,eAAe,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,GAAG,IAAI;IAIxD,kBAAkB,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,GAAG,IAAI;IAI3D,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI;IAQ/C,cAAc,IAAI,MAAM;IAIxB,oBAAoB,IAAI,MAAM;IAI9B;;oDAEgD;IAChD,aAAa,IAAI,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAMpD,KAAK,IAAI,IAAI;CASd"}
package/dist/ws-server.js CHANGED
@@ -6,10 +6,23 @@ export class WsServer {
6
6
  wss;
7
7
  commandCallback = null;
8
8
  rawMessageCallback = null;
9
+ binaryCallback = null;
10
+ onPongCallback = null;
9
11
  onConnectCallback = null;
10
12
  onDisconnectCallback = null;
11
13
  clientAlive = new Map();
12
14
  esp32Clients = new Set();
15
+ // Per-IP connect timestamps for the flap guard (window: 30s, threshold: >6).
16
+ recentConnectsByIp = new Map();
17
+ flappingClients = new Set();
18
+ // IPs that have ever sent device_info this daemon lifetime. A board that
19
+ // predates the `?clientType=esp32` tag (XTeink 6ccbe140) is only recognized
20
+ // AFTER its first device_info — one connect too late, because the initial
21
+ // burst already went out untagged. Remembering the IP makes every subsequent
22
+ // connect board-class from byte one, so the ≤4096B invariant holds and a
23
+ // heap-tight board can't be killed by its own welcome payload.
24
+ knownBoardIps = new Set();
25
+ socketRemoteIp = new Map();
13
26
  eventTransformer = null;
14
27
  // Clients that registered as the Ulanzi Studio plugin. Their WebSocket
15
28
  // presence is the daemon's D200H connectivity signal.
@@ -60,10 +73,31 @@ export class WsServer {
60
73
  debug('WS', `Remote client authenticated from ${remoteIp}`);
61
74
  }
62
75
  const url = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
76
+ this.socketRemoteIp.set(ws, remoteIp);
63
77
  if (url.searchParams.get('clientType') === 'esp32' || url.searchParams.get('esp32') === '1') {
64
78
  this.esp32Clients.add(ws);
65
79
  debug('WS', 'ESP32 WiFi client tagged from query');
66
80
  }
81
+ else if (this.knownBoardIps.has(remoteIp)) {
82
+ this.esp32Clients.add(ws);
83
+ debug('WS', `ESP32 WiFi client tagged from known board IP ${remoteIp}`);
84
+ }
85
+ // Flap guard: a client whose IP reconnected >6 times in the last 30s is
86
+ // marked flapping. It is still served (rejecting outright would blind a
87
+ // recovering board forever), but sendInitialState skips the expensive
88
+ // burst for it — a marginal-RF board must not be able to resonate the
89
+ // daemon into event-loop saturation (connect → heavy serialize → client
90
+ // dies on the burst → immediate reconnect → repeat).
91
+ {
92
+ const now = Date.now();
93
+ const recent = (this.recentConnectsByIp.get(remoteIp) ?? []).filter((t) => now - t < 30_000);
94
+ recent.push(now);
95
+ this.recentConnectsByIp.set(remoteIp, recent);
96
+ if (recent.length > 6) {
97
+ this.flappingClients.add(ws);
98
+ debug('WS', `Flapping client ${remoteIp}: ${recent.length} connects/30s — initial burst reduced`);
99
+ }
100
+ }
67
101
  debug('WS', 'Plugin connected');
68
102
  this.clientAlive.set(ws, true);
69
103
  // Send current state to newly connected client
@@ -72,8 +106,20 @@ export class WsServer {
72
106
  }
73
107
  ws.on('pong', () => {
74
108
  this.clientAlive.set(ws, true);
109
+ // A display-only board sends no application messages while idle, so a
110
+ // message-based lastSeen brands every healthy quiet board "stale". Its
111
+ // pong every WS_PING_INTERVAL_MS is real liveness — surface it.
112
+ this.onPongCallback?.(ws);
75
113
  });
76
- ws.on('message', (data) => {
114
+ ws.on('message', (data, isBinary) => {
115
+ // Binary frames are device audio (voice capture), not JSON — route
116
+ // them out before the parser, which would otherwise log a parse error
117
+ // per 1 KB PCM frame.
118
+ if (isBinary) {
119
+ const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
120
+ this.binaryCallback?.(buf, ws);
121
+ return;
122
+ }
77
123
  try {
78
124
  const msg = JSON.parse(data.toString());
79
125
  debug('WS', `recv cmd: ${msg.type}`);
@@ -109,6 +155,8 @@ export class WsServer {
109
155
  debug('WS', 'Plugin disconnected');
110
156
  this.clientAlive.delete(ws);
111
157
  this.esp32Clients.delete(ws);
158
+ this.flappingClients.delete(ws);
159
+ this.socketRemoteIp.delete(ws);
112
160
  this.tuiClients.delete(ws);
113
161
  if (this.ulanziClients.delete(ws) && this.ulanziClients.size === 0) {
114
162
  debug('WS', 'Ulanzi plugin gone — D200H disconnected');
@@ -133,8 +181,18 @@ export class WsServer {
133
181
  isEsp32Client(ws) {
134
182
  return this.esp32Clients.has(ws);
135
183
  }
184
+ /** True when this socket's remote IP has been reconnecting fast enough to
185
+ * count as flapping (see the connection handler). Callers use it to skip
186
+ * the heavy parts of the initial burst so a marginal client can't resonate
187
+ * the daemon into saturation. */
188
+ isFlappingClient(ws) {
189
+ return this.flappingClients.has(ws);
190
+ }
136
191
  markEsp32Client(ws) {
137
192
  this.esp32Clients.add(ws);
193
+ const ip = this.socketRemoteIp.get(ws);
194
+ if (ip)
195
+ this.knownBoardIps.add(ip);
138
196
  }
139
197
  payloadFor(event, client, cachedPayload) {
140
198
  if (!this.eventTransformer)
@@ -170,6 +228,13 @@ export class WsServer {
170
228
  onCommand(callback) {
171
229
  this.commandCallback = callback;
172
230
  }
231
+ /** Register a callback for binary frames (device audio). */
232
+ onPong(callback) {
233
+ this.onPongCallback = callback;
234
+ }
235
+ onBinary(callback) {
236
+ this.binaryCallback = callback;
237
+ }
173
238
  /** Register a callback for raw messages before PluginCommand dispatch. Return true to consume. */
174
239
  onRawMessage(callback) {
175
240
  this.rawMessageCallback = callback;