@agentdeck/bridge 1.0.1 → 1.0.3
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/assets/fm-helper/agentdeck-fm-helper +0 -0
- package/dist/apme/collector.d.ts.map +1 -1
- package/dist/apme/collector.js +5 -2
- package/dist/apme/collector.js.map +1 -1
- package/dist/apme/runner.d.ts +11 -0
- package/dist/apme/runner.d.ts.map +1 -1
- package/dist/apme/runner.js +60 -4
- package/dist/apme/runner.js.map +1 -1
- package/dist/apme/settings.d.ts +10 -0
- package/dist/apme/settings.d.ts.map +1 -1
- package/dist/apme/settings.js +14 -0
- package/dist/apme/settings.js.map +1 -1
- package/dist/awaiting-overlay.d.ts +44 -10
- package/dist/awaiting-overlay.d.ts.map +1 -1
- package/dist/awaiting-overlay.js +96 -28
- package/dist/awaiting-overlay.js.map +1 -1
- package/dist/bridge-core.d.ts +10 -0
- package/dist/bridge-core.d.ts.map +1 -1
- package/dist/bridge-core.js +47 -12
- package/dist/bridge-core.js.map +1 -1
- package/dist/card-feed.d.ts +107 -0
- package/dist/card-feed.d.ts.map +1 -0
- package/dist/card-feed.js +277 -0
- package/dist/card-feed.js.map +1 -0
- package/dist/card-modules.d.ts +87 -0
- package/dist/card-modules.d.ts.map +1 -0
- package/dist/card-modules.js +179 -0
- package/dist/card-modules.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +93 -0
- package/dist/cli.js.map +1 -1
- package/dist/daemon-server.d.ts.map +1 -1
- package/dist/daemon-server.js +1025 -50
- package/dist/daemon-server.js.map +1 -1
- package/dist/daemon-ws-client.d.ts +2 -1
- package/dist/daemon-ws-client.d.ts.map +1 -1
- package/dist/daemon-ws-client.js +2 -1
- package/dist/daemon-ws-client.js.map +1 -1
- package/dist/daemon.js +1 -1
- package/dist/device-photo.d.ts +86 -0
- package/dist/device-photo.d.ts.map +1 -0
- package/dist/device-photo.js +200 -0
- package/dist/device-photo.js.map +1 -0
- package/dist/device-voice-reply.d.ts +124 -0
- package/dist/device-voice-reply.d.ts.map +1 -0
- package/dist/device-voice-reply.js +236 -0
- package/dist/device-voice-reply.js.map +1 -0
- package/dist/device-voice.d.ts +56 -0
- package/dist/device-voice.d.ts.map +1 -0
- package/dist/device-voice.js +134 -0
- package/dist/device-voice.js.map +1 -0
- package/dist/esp32-serial.d.ts +40 -0
- package/dist/esp32-serial.d.ts.map +1 -1
- package/dist/esp32-serial.js +109 -13
- package/dist/esp32-serial.js.map +1 -1
- package/dist/foundation-models-helper.d.ts +30 -0
- package/dist/foundation-models-helper.d.ts.map +1 -1
- package/dist/foundation-models-helper.js +45 -0
- package/dist/foundation-models-helper.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/mdns.d.ts +11 -0
- package/dist/mdns.d.ts.map +1 -1
- package/dist/mdns.js +13 -0
- package/dist/mdns.js.map +1 -1
- package/dist/observed-inject.d.ts +119 -0
- package/dist/observed-inject.d.ts.map +1 -0
- package/dist/observed-inject.js +405 -0
- package/dist/observed-inject.js.map +1 -0
- package/dist/passive-observer.d.ts +14 -0
- package/dist/passive-observer.d.ts.map +1 -1
- package/dist/passive-observer.js +35 -5
- package/dist/passive-observer.js.map +1 -1
- package/dist/peripheral-mapping.d.ts +65 -0
- package/dist/peripheral-mapping.d.ts.map +1 -0
- package/dist/peripheral-mapping.js +115 -0
- package/dist/peripheral-mapping.js.map +1 -0
- package/dist/session-aggregator.d.ts +4 -1
- package/dist/session-aggregator.d.ts.map +1 -1
- package/dist/session-aggregator.js +5 -5
- package/dist/session-aggregator.js.map +1 -1
- package/dist/session-id-resolve.d.ts +17 -0
- package/dist/session-id-resolve.d.ts.map +1 -0
- package/dist/session-id-resolve.js +35 -0
- package/dist/session-id-resolve.js.map +1 -0
- package/dist/session-registry.d.ts +12 -0
- package/dist/session-registry.d.ts.map +1 -1
- package/dist/session-registry.js +31 -0
- package/dist/session-registry.js.map +1 -1
- package/dist/session-transcript-timeline.d.ts +7 -0
- package/dist/session-transcript-timeline.d.ts.map +1 -1
- package/dist/session-transcript-timeline.js +16 -6
- package/dist/session-transcript-timeline.js.map +1 -1
- package/dist/timeline-store.js +2 -2
- package/dist/timeline-store.js.map +1 -1
- package/dist/usage-event.d.ts.map +1 -1
- package/dist/usage-event.js +14 -7
- package/dist/usage-event.js.map +1 -1
- package/dist/voice.d.ts +13 -10
- package/dist/voice.d.ts.map +1 -1
- package/dist/voice.js +26 -272
- package/dist/voice.js.map +1 -1
- package/dist/ws-server.d.ts +14 -0
- package/dist/ws-server.d.ts.map +1 -1
- package/dist/ws-server.js +66 -1
- package/dist/ws-server.js.map +1 -1
- package/fm-helper/AgentDeckFMHelper.swift +303 -0
- package/package.json +3 -3
- package/dist/d200h/hid-protocol.d.ts +0 -62
- package/dist/d200h/hid-protocol.d.ts.map +0 -1
- package/dist/d200h/hid-protocol.js +0 -124
- package/dist/d200h/hid-protocol.js.map +0 -1
- package/dist/d200h/image-renderer.d.ts +0 -24
- package/dist/d200h/image-renderer.d.ts.map +0 -1
- package/dist/d200h/image-renderer.js +0 -321
- package/dist/d200h/image-renderer.js.map +0 -1
- package/dist/hook-migration.d.ts +0 -2
- package/dist/hook-migration.d.ts.map +0 -1
- package/dist/hook-migration.js +0 -104
- package/dist/hook-migration.js.map +0 -1
- package/dist/modules/d200h-module.d.ts +0 -65
- package/dist/modules/d200h-module.d.ts.map +0 -1
- package/dist/modules/d200h-module.js +0 -495
- package/dist/modules/d200h-module.js.map +0 -1
- package/dist/modules/d200h-renderer.d.ts +0 -7
- package/dist/modules/d200h-renderer.d.ts.map +0 -1
- package/dist/modules/d200h-renderer.js +0 -321
- package/dist/modules/d200h-renderer.js.map +0 -1
- package/dist/modules/trmnl-module.d.ts +0 -38
- package/dist/modules/trmnl-module.d.ts.map +0 -1
- package/dist/modules/trmnl-module.js +0 -111
- package/dist/modules/trmnl-module.js.map +0 -1
- package/dist/trmnl/byos-server.d.ts +0 -39
- package/dist/trmnl/byos-server.d.ts.map +0 -1
- package/dist/trmnl/byos-server.js +0 -301
- package/dist/trmnl/byos-server.js.map +0 -1
- package/dist/trmnl/frame-cache.d.ts +0 -37
- package/dist/trmnl/frame-cache.d.ts.map +0 -1
- package/dist/trmnl/frame-cache.js +0 -157
- package/dist/trmnl/frame-cache.js.map +0 -1
- package/dist/trmnl/image-renderer.d.ts +0 -27
- package/dist/trmnl/image-renderer.d.ts.map +0 -1
- package/dist/trmnl/image-renderer.js +0 -235
- package/dist/trmnl/image-renderer.js.map +0 -1
- package/dist/trmnl/trmnl-settings.d.ts +0 -96
- package/dist/trmnl/trmnl-settings.d.ts.map +0 -1
- package/dist/trmnl/trmnl-settings.js +0 -177
- package/dist/trmnl/trmnl-settings.js.map +0 -1
- package/dist/trmnl/trmnl-telemetry.d.ts +0 -47
- package/dist/trmnl/trmnl-telemetry.d.ts.map +0 -1
- package/dist/trmnl/trmnl-telemetry.js +0 -56
- package/dist/trmnl/trmnl-telemetry.js.map +0 -1
- package/dist/whisper-server-manager.d.ts +0 -19
- package/dist/whisper-server-manager.d.ts.map +0 -1
- package/dist/whisper-server-manager.js +0 -171
- package/dist/whisper-server-manager.js.map +0 -1
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import Foundation
|
|
2
|
+
import AVFoundation
|
|
3
|
+
import Speech
|
|
2
4
|
#if canImport(FoundationModels)
|
|
3
5
|
import FoundationModels
|
|
4
6
|
#endif
|
|
@@ -9,6 +11,14 @@ struct HelperRequest: Decodable {
|
|
|
9
11
|
let prompt: String?
|
|
10
12
|
let instructions: String?
|
|
11
13
|
let temperature: Double?
|
|
14
|
+
/// `transcribe`: absolute path to a WAV file the daemon already wrote.
|
|
15
|
+
let wav: String?
|
|
16
|
+
/// BCP-47 locale hint, e.g. "ko-KR". Falls back to the current locale then en-US.
|
|
17
|
+
let locale: String?
|
|
18
|
+
/// `speak`: text to synthesize, with optional voice/rate overrides.
|
|
19
|
+
let text: String?
|
|
20
|
+
let voice: String?
|
|
21
|
+
let rate: Double?
|
|
12
22
|
}
|
|
13
23
|
|
|
14
24
|
@main
|
|
@@ -35,6 +45,21 @@ struct AgentDeckFMHelper {
|
|
|
35
45
|
return
|
|
36
46
|
}
|
|
37
47
|
|
|
48
|
+
if request.type == "transcribe" {
|
|
49
|
+
await handleTranscribe(request)
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if request.type == "speak" {
|
|
54
|
+
await handleSpeak(request)
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if request.type == "synthesize" {
|
|
59
|
+
await handleSynthesize(request)
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
|
|
38
63
|
guard let prompt = request.prompt, !prompt.isEmpty else {
|
|
39
64
|
write(["id": request.id, "error": "bad_request", "reason": "missing prompt"])
|
|
40
65
|
return
|
|
@@ -69,6 +94,192 @@ struct AgentDeckFMHelper {
|
|
|
69
94
|
#endif
|
|
70
95
|
}
|
|
71
96
|
|
|
97
|
+
// MARK: - Speech to text
|
|
98
|
+
|
|
99
|
+
/// On-device transcription of a WAV the daemon already captured (from the
|
|
100
|
+
/// host mic or streamed off a board). `requiresOnDeviceRecognition` keeps
|
|
101
|
+
/// audio local — the captured speech routinely contains project and code
|
|
102
|
+
/// names that must not reach Apple's servers. Mirrors the Swift daemon's
|
|
103
|
+
/// `VoiceSpeechTranscriber` so both daemons transcribe identically.
|
|
104
|
+
private static func handleTranscribe(_ request: HelperRequest) async {
|
|
105
|
+
guard let path = request.wav, !path.isEmpty else {
|
|
106
|
+
write(["id": request.id, "error": "bad_request", "reason": "missing wav path"])
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
guard FileManager.default.fileExists(atPath: path) else {
|
|
110
|
+
write(["id": request.id, "error": "bad_request", "reason": "wav not found: \(path)"])
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let status = await ensureSpeechAuthorization()
|
|
115
|
+
guard status == .authorized else {
|
|
116
|
+
write([
|
|
117
|
+
"id": request.id,
|
|
118
|
+
"error": "unauthorized",
|
|
119
|
+
"reason": "speech recognition not authorized (\(status.rawValue)) — grant it in System Settings › Privacy & Security › Speech Recognition",
|
|
120
|
+
])
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
var locales: [Locale] = []
|
|
125
|
+
if let tag = request.locale, !tag.isEmpty { locales.append(Locale(identifier: tag)) }
|
|
126
|
+
locales.append(Locale.current)
|
|
127
|
+
locales.append(Locale(identifier: "en-US"))
|
|
128
|
+
|
|
129
|
+
guard let recognizer = locales.lazy.compactMap({ SFSpeechRecognizer(locale: $0) }).first(where: { $0.isAvailable }) else {
|
|
130
|
+
write([
|
|
131
|
+
"id": request.id,
|
|
132
|
+
"error": "unavailable",
|
|
133
|
+
"reason": "no available on-device recognizer — the dictation model may still be downloading",
|
|
134
|
+
])
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let speechRequest = SFSpeechURLRecognitionRequest(url: URL(fileURLWithPath: path))
|
|
139
|
+
speechRequest.shouldReportPartialResults = false
|
|
140
|
+
speechRequest.requiresOnDeviceRecognition = true
|
|
141
|
+
speechRequest.taskHint = .dictation
|
|
142
|
+
|
|
143
|
+
let result: [String: Any] = await withCheckedContinuation { continuation in
|
|
144
|
+
let box = ResumeOnce(continuation)
|
|
145
|
+
_ = recognizer.recognitionTask(with: speechRequest) { result, error in
|
|
146
|
+
if let error {
|
|
147
|
+
box.resume(["id": request.id, "error": "speech_error", "reason": String(describing: error)])
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
guard let result, result.isFinal else { return }
|
|
151
|
+
let text = result.bestTranscription.formattedString
|
|
152
|
+
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
153
|
+
box.resume(["id": request.id, "text": text])
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
write(result)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private static func ensureSpeechAuthorization() async -> SFSpeechRecognizerAuthorizationStatus {
|
|
160
|
+
let current = SFSpeechRecognizer.authorizationStatus()
|
|
161
|
+
guard current == .notDetermined else { return current }
|
|
162
|
+
return await withCheckedContinuation { continuation in
|
|
163
|
+
let box = ResumeOnce(continuation)
|
|
164
|
+
SFSpeechRecognizer.requestAuthorization { box.resume($0) }
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// MARK: - Text to speech
|
|
169
|
+
|
|
170
|
+
/// Speak a reply through the host's audio output. Kept in the same helper
|
|
171
|
+
/// so the voice round trip needs exactly one bundled binary.
|
|
172
|
+
private static func handleSpeak(_ request: HelperRequest) async {
|
|
173
|
+
guard let text = request.text, !text.isEmpty else {
|
|
174
|
+
write(["id": request.id, "error": "bad_request", "reason": "missing text"])
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
let utterance = AVSpeechUtterance(string: text)
|
|
178
|
+
if let voiceId = request.voice, let v = AVSpeechSynthesisVoice(identifier: voiceId) {
|
|
179
|
+
utterance.voice = v
|
|
180
|
+
} else if let tag = request.locale, let v = AVSpeechSynthesisVoice(language: tag) {
|
|
181
|
+
utterance.voice = v
|
|
182
|
+
}
|
|
183
|
+
if let rate = request.rate { utterance.rate = Float(rate) }
|
|
184
|
+
|
|
185
|
+
let synthesizer = AVSpeechSynthesizer()
|
|
186
|
+
let delegate = SpeakDelegate()
|
|
187
|
+
synthesizer.delegate = delegate
|
|
188
|
+
await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in
|
|
189
|
+
delegate.onFinish = ResumeOnce(continuation)
|
|
190
|
+
synthesizer.speak(utterance)
|
|
191
|
+
}
|
|
192
|
+
write(["id": request.id, "spoken": true])
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/// Synthesize to a WAV file instead of the host's speakers, so the daemon
|
|
196
|
+
/// can stream the audio somewhere else — a battery-powered board holding
|
|
197
|
+
/// the only speaker the user is near. Fixed at 16 kHz mono PCM16: that is
|
|
198
|
+
/// what the boards' I2S amplifiers are configured for and what keeps a
|
|
199
|
+
/// spoken reply inside a WiFi budget an ESP32 can actually drain.
|
|
200
|
+
private static func handleSynthesize(_ request: HelperRequest) async {
|
|
201
|
+
guard let text = request.text, !text.isEmpty else {
|
|
202
|
+
write(["id": request.id, "error": "bad_request", "reason": "missing text"])
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
guard let outPath = request.wav, !outPath.isEmpty else {
|
|
206
|
+
write(["id": request.id, "error": "bad_request", "reason": "missing wav output path"])
|
|
207
|
+
return
|
|
208
|
+
}
|
|
209
|
+
let sampleRate = 16000.0
|
|
210
|
+
guard let outFormat = AVAudioFormat(commonFormat: .pcmFormatInt16,
|
|
211
|
+
sampleRate: sampleRate,
|
|
212
|
+
channels: 1,
|
|
213
|
+
interleaved: true) else {
|
|
214
|
+
write(["id": request.id, "error": "synthesis_failed", "reason": "output format unavailable"])
|
|
215
|
+
return
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
let utterance = AVSpeechUtterance(string: text)
|
|
219
|
+
if let voiceId = request.voice, let v = AVSpeechSynthesisVoice(identifier: voiceId) {
|
|
220
|
+
utterance.voice = v
|
|
221
|
+
} else if let tag = request.locale, let v = AVSpeechSynthesisVoice(language: tag) {
|
|
222
|
+
utterance.voice = v
|
|
223
|
+
}
|
|
224
|
+
if let rate = request.rate { utterance.rate = Float(rate) }
|
|
225
|
+
|
|
226
|
+
// The write callback is not documented to run on any particular queue,
|
|
227
|
+
// and it appends to shared state, so the accumulator owns a lock.
|
|
228
|
+
let sink = PCMSink()
|
|
229
|
+
let synthesizer = AVSpeechSynthesizer()
|
|
230
|
+
await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in
|
|
231
|
+
let done = ResumeOnce(continuation)
|
|
232
|
+
synthesizer.write(utterance) { buffer in
|
|
233
|
+
guard let input = buffer as? AVAudioPCMBuffer else { return }
|
|
234
|
+
// A zero-length buffer is the documented end-of-synthesis
|
|
235
|
+
// signal; without it this call never completes.
|
|
236
|
+
if input.frameLength == 0 {
|
|
237
|
+
done.resume(())
|
|
238
|
+
return
|
|
239
|
+
}
|
|
240
|
+
sink.append(input, to: outFormat)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if let failure = sink.failure {
|
|
245
|
+
write(["id": request.id, "error": "synthesis_failed", "reason": failure])
|
|
246
|
+
return
|
|
247
|
+
}
|
|
248
|
+
let pcm = sink.pcm
|
|
249
|
+
guard !pcm.isEmpty else {
|
|
250
|
+
write(["id": request.id, "error": "synthesis_failed", "reason": "no audio produced"])
|
|
251
|
+
return
|
|
252
|
+
}
|
|
253
|
+
do {
|
|
254
|
+
try wavData(pcm: pcm, sampleRate: Int(sampleRate)).write(to: URL(fileURLWithPath: outPath))
|
|
255
|
+
} catch {
|
|
256
|
+
write(["id": request.id, "error": "synthesis_failed", "reason": String(describing: error)])
|
|
257
|
+
return
|
|
258
|
+
}
|
|
259
|
+
write([
|
|
260
|
+
"id": request.id,
|
|
261
|
+
"wav": outPath,
|
|
262
|
+
"sampleRate": Int(sampleRate),
|
|
263
|
+
"samples": pcm.count / 2,
|
|
264
|
+
"durationMs": Int(Double(pcm.count / 2) / sampleRate * 1000.0),
|
|
265
|
+
])
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/// Canonical 44-byte RIFF header + samples. Kept here rather than shelling
|
|
269
|
+
/// out so the helper stays the single bundled binary.
|
|
270
|
+
private static func wavData(pcm: Data, sampleRate: Int) -> Data {
|
|
271
|
+
var out = Data()
|
|
272
|
+
func u32(_ v: Int) { withUnsafeBytes(of: UInt32(v).littleEndian) { out.append(contentsOf: $0) } }
|
|
273
|
+
func u16(_ v: Int) { withUnsafeBytes(of: UInt16(v).littleEndian) { out.append(contentsOf: $0) } }
|
|
274
|
+
out.append(contentsOf: Array("RIFF".utf8)); u32(36 + pcm.count)
|
|
275
|
+
out.append(contentsOf: Array("WAVE".utf8))
|
|
276
|
+
out.append(contentsOf: Array("fmt ".utf8)); u32(16)
|
|
277
|
+
u16(1); u16(1); u32(sampleRate); u32(sampleRate * 2); u16(2); u16(16)
|
|
278
|
+
out.append(contentsOf: Array("data".utf8)); u32(pcm.count)
|
|
279
|
+
out.append(pcm)
|
|
280
|
+
return out
|
|
281
|
+
}
|
|
282
|
+
|
|
72
283
|
private static func healthResponse(id: Int) -> [String: Any] {
|
|
73
284
|
#if canImport(FoundationModels)
|
|
74
285
|
if #available(macOS 26.0, *) {
|
|
@@ -110,3 +321,95 @@ struct AgentDeckFMHelper {
|
|
|
110
321
|
FileHandle.standardOutput.write(Data((text + "\n").utf8))
|
|
111
322
|
}
|
|
112
323
|
}
|
|
324
|
+
|
|
325
|
+
/// One-shot continuation guard: speech and synthesis callbacks can fire more
|
|
326
|
+
/// than once, and resuming a continuation twice traps.
|
|
327
|
+
private final class ResumeOnce<T>: @unchecked Sendable {
|
|
328
|
+
private var continuation: CheckedContinuation<T, Never>?
|
|
329
|
+
private let lock = NSLock()
|
|
330
|
+
|
|
331
|
+
init(_ continuation: CheckedContinuation<T, Never>) {
|
|
332
|
+
self.continuation = continuation
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
func resume(_ value: T) {
|
|
336
|
+
lock.lock()
|
|
337
|
+
let c = continuation
|
|
338
|
+
continuation = nil
|
|
339
|
+
lock.unlock()
|
|
340
|
+
c?.resume(returning: value)
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/// Accumulates synthesized audio, resampling each delivered buffer to the
|
|
345
|
+
/// target format. The converter is built from the first buffer because the
|
|
346
|
+
/// synthesizer picks its own rate and sample type per voice.
|
|
347
|
+
private final class PCMSink: @unchecked Sendable {
|
|
348
|
+
private let lock = NSLock()
|
|
349
|
+
private var buffer = Data()
|
|
350
|
+
private var converter: AVAudioConverter?
|
|
351
|
+
private var error: String?
|
|
352
|
+
|
|
353
|
+
var pcm: Data {
|
|
354
|
+
lock.lock(); defer { lock.unlock() }
|
|
355
|
+
return buffer
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
var failure: String? {
|
|
359
|
+
lock.lock(); defer { lock.unlock() }
|
|
360
|
+
return error
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
func append(_ input: AVAudioPCMBuffer, to outFormat: AVAudioFormat) {
|
|
364
|
+
lock.lock(); defer { lock.unlock() }
|
|
365
|
+
if error != nil { return }
|
|
366
|
+
if converter == nil {
|
|
367
|
+
converter = AVAudioConverter(from: input.format, to: outFormat)
|
|
368
|
+
if converter == nil {
|
|
369
|
+
error = "no converter from \(input.format) to \(outFormat)"
|
|
370
|
+
return
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
guard let converter else { return }
|
|
374
|
+
// Round up, and leave slack: the converter may emit slightly more than
|
|
375
|
+
// the naive ratio when it flushes filter state.
|
|
376
|
+
let ratio = outFormat.sampleRate / input.format.sampleRate
|
|
377
|
+
let capacity = AVAudioFrameCount((Double(input.frameLength) * ratio).rounded(.up)) + 1024
|
|
378
|
+
guard let output = AVAudioPCMBuffer(pcmFormat: outFormat, frameCapacity: capacity) else {
|
|
379
|
+
error = "output buffer allocation failed"
|
|
380
|
+
return
|
|
381
|
+
}
|
|
382
|
+
var supplied = false
|
|
383
|
+
var convertError: NSError?
|
|
384
|
+
let status = converter.convert(to: output, error: &convertError) { _, outStatus in
|
|
385
|
+
if supplied {
|
|
386
|
+
outStatus.pointee = .noDataNow
|
|
387
|
+
return nil
|
|
388
|
+
}
|
|
389
|
+
supplied = true
|
|
390
|
+
outStatus.pointee = .haveData
|
|
391
|
+
return input
|
|
392
|
+
}
|
|
393
|
+
if status == .error {
|
|
394
|
+
error = convertError?.localizedDescription ?? "conversion failed"
|
|
395
|
+
return
|
|
396
|
+
}
|
|
397
|
+
guard let channel = output.int16ChannelData, output.frameLength > 0 else { return }
|
|
398
|
+
let count = Int(output.frameLength)
|
|
399
|
+
channel[0].withMemoryRebound(to: UInt8.self, capacity: count * 2) { raw in
|
|
400
|
+
buffer.append(raw, count: count * 2)
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
private final class SpeakDelegate: NSObject, AVSpeechSynthesizerDelegate {
|
|
406
|
+
var onFinish: ResumeOnce<Void>?
|
|
407
|
+
|
|
408
|
+
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
|
|
409
|
+
onFinish?.resume(())
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didCancel utterance: AVSpeechUtterance) {
|
|
413
|
+
onFinish?.resume(())
|
|
414
|
+
}
|
|
415
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentdeck/bridge",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"qrcode": "^1.5.4",
|
|
29
29
|
"strip-ansi": "^7.2.0",
|
|
30
30
|
"ws": "^8.20.0",
|
|
31
|
-
"@agentdeck/shared": "1.0.
|
|
32
|
-
"@agentdeck/hooks": "1.0.
|
|
31
|
+
"@agentdeck/shared": "1.0.3",
|
|
32
|
+
"@agentdeck/hooks": "1.0.3"
|
|
33
33
|
},
|
|
34
34
|
"optionalDependencies": {
|
|
35
35
|
"@picovoice/porcupine-node": "^4.0.2",
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* D200H HID Protocol — D200-compatible 1024-byte packet protocol
|
|
3
|
-
*
|
|
4
|
-
* Packet structure:
|
|
5
|
-
* [0-1] Header: 0x7C 0x7C
|
|
6
|
-
* [2-3] Command: big-endian uint16
|
|
7
|
-
* [4-7] Length: little-endian uint32
|
|
8
|
-
* [8-1023] Payload (zero-padded)
|
|
9
|
-
*
|
|
10
|
-
* Image delivery: ZIP(manifest.json + icons/*.png) sent in chunked 1024-byte packets.
|
|
11
|
-
* ZIP quirk: bytes at offset 1016, 2040, 3064... must NOT be 0x00 or 0x7C.
|
|
12
|
-
*/
|
|
13
|
-
export declare const CMD: {
|
|
14
|
-
readonly SET_BUTTONS: 1;
|
|
15
|
-
readonly PARTIAL_UPDATE: 13;
|
|
16
|
-
readonly SET_SMALL_WINDOW: 6;
|
|
17
|
-
readonly SET_BRIGHTNESS: 10;
|
|
18
|
-
readonly SET_LABEL_STYLE: 11;
|
|
19
|
-
readonly IN_BUTTON: 257;
|
|
20
|
-
readonly IN_DEVICE_INFO: 771;
|
|
21
|
-
};
|
|
22
|
-
export declare function buildPacket(command: number, payload: Buffer, totalLength?: number): Buffer;
|
|
23
|
-
/**
|
|
24
|
-
* Build all packets for a ZIP file transfer (SET_BUTTONS or PARTIAL_UPDATE).
|
|
25
|
-
* First packet has the header; subsequent packets are raw 1024-byte chunks.
|
|
26
|
-
*/
|
|
27
|
-
export declare function buildZipPackets(zipData: Buffer, command?: number): Buffer[];
|
|
28
|
-
export declare function buildBrightnessPacket(brightness: number): Buffer;
|
|
29
|
-
export declare function buildSmallWindowPacket(mode: number, cpu: number, mem: number, time: string, gpu: number): Buffer;
|
|
30
|
-
export interface ButtonEvent {
|
|
31
|
-
index: number;
|
|
32
|
-
pressed: boolean;
|
|
33
|
-
state: number;
|
|
34
|
-
}
|
|
35
|
-
export interface DeviceInfo {
|
|
36
|
-
serialNumber: string;
|
|
37
|
-
firmwareVersion: string;
|
|
38
|
-
deviceType: string;
|
|
39
|
-
hardwareVersion: string;
|
|
40
|
-
}
|
|
41
|
-
export type IncomingEvent = {
|
|
42
|
-
type: 'button';
|
|
43
|
-
data: ButtonEvent;
|
|
44
|
-
} | {
|
|
45
|
-
type: 'device_info';
|
|
46
|
-
data: DeviceInfo;
|
|
47
|
-
} | {
|
|
48
|
-
type: 'unknown';
|
|
49
|
-
command: number;
|
|
50
|
-
raw: Buffer;
|
|
51
|
-
};
|
|
52
|
-
export declare function parseIncoming(data: Buffer): IncomingEvent | null;
|
|
53
|
-
/**
|
|
54
|
-
* Check if a ZIP has invalid bytes at 1024-byte chunk boundaries.
|
|
55
|
-
* At offset 1016, 2040, 3064... the byte must not be 0x00 or 0x7C.
|
|
56
|
-
*/
|
|
57
|
-
export declare function validateZipBoundaries(zipData: Buffer): boolean;
|
|
58
|
-
/**
|
|
59
|
-
* Generate a random dummy string to pad the ZIP until boundaries are valid.
|
|
60
|
-
*/
|
|
61
|
-
export declare function generateDummyPadding(attempt: number): string;
|
|
62
|
-
//# sourceMappingURL=hid-protocol.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hid-protocol.d.ts","sourceRoot":"","sources":["../../src/d200h/hid-protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAaH,eAAO,MAAM,GAAG;;;;;;;;CAQN,CAAC;AAIX,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAY1F;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,MAAwB,GAAG,MAAM,EAAE,CAiB5F;AAED,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAGhE;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAGhH;AAID,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAoChE;AAID;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAO9D;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE5D"}
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* D200H HID Protocol — D200-compatible 1024-byte packet protocol
|
|
3
|
-
*
|
|
4
|
-
* Packet structure:
|
|
5
|
-
* [0-1] Header: 0x7C 0x7C
|
|
6
|
-
* [2-3] Command: big-endian uint16
|
|
7
|
-
* [4-7] Length: little-endian uint32
|
|
8
|
-
* [8-1023] Payload (zero-padded)
|
|
9
|
-
*
|
|
10
|
-
* Image delivery: ZIP(manifest.json + icons/*.png) sent in chunked 1024-byte packets.
|
|
11
|
-
* ZIP quirk: bytes at offset 1016, 2040, 3064... must NOT be 0x00 or 0x7C.
|
|
12
|
-
*/
|
|
13
|
-
import { randomBytes } from 'crypto';
|
|
14
|
-
const TAG = 'd200h-hid';
|
|
15
|
-
const PACKET_SIZE = 1024;
|
|
16
|
-
const HEADER = Buffer.from([0x7c, 0x7c]);
|
|
17
|
-
const INVALID_BOUNDARY_BYTES = [0x00, 0x7c];
|
|
18
|
-
// --- Commands ---
|
|
19
|
-
export const CMD = {
|
|
20
|
-
SET_BUTTONS: 0x0001,
|
|
21
|
-
PARTIAL_UPDATE: 0x000d,
|
|
22
|
-
SET_SMALL_WINDOW: 0x0006,
|
|
23
|
-
SET_BRIGHTNESS: 0x000a,
|
|
24
|
-
SET_LABEL_STYLE: 0x000b,
|
|
25
|
-
IN_BUTTON: 0x0101,
|
|
26
|
-
IN_DEVICE_INFO: 0x0303,
|
|
27
|
-
};
|
|
28
|
-
// --- Packet building ---
|
|
29
|
-
export function buildPacket(command, payload, totalLength) {
|
|
30
|
-
const pkt = Buffer.alloc(PACKET_SIZE, 0);
|
|
31
|
-
// Header
|
|
32
|
-
pkt[0] = 0x7c;
|
|
33
|
-
pkt[1] = 0x7c;
|
|
34
|
-
// Command (big-endian uint16)
|
|
35
|
-
pkt.writeUInt16BE(command, 2);
|
|
36
|
-
// Length (little-endian uint32) — total file size for multi-packet, else payload length
|
|
37
|
-
pkt.writeUInt32LE(totalLength ?? payload.length, 4);
|
|
38
|
-
// Payload
|
|
39
|
-
payload.copy(pkt, 8, 0, Math.min(payload.length, PACKET_SIZE - 8));
|
|
40
|
-
return pkt;
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Build all packets for a ZIP file transfer (SET_BUTTONS or PARTIAL_UPDATE).
|
|
44
|
-
* First packet has the header; subsequent packets are raw 1024-byte chunks.
|
|
45
|
-
*/
|
|
46
|
-
export function buildZipPackets(zipData, command = CMD.SET_BUTTONS) {
|
|
47
|
-
const packets = [];
|
|
48
|
-
const fileSize = zipData.length;
|
|
49
|
-
// First packet: header(8) + first chunk
|
|
50
|
-
const firstChunkSize = PACKET_SIZE - 8;
|
|
51
|
-
const firstChunk = zipData.subarray(0, firstChunkSize);
|
|
52
|
-
packets.push(buildPacket(command, firstChunk, fileSize));
|
|
53
|
-
// Remaining chunks (raw, no header)
|
|
54
|
-
for (let offset = firstChunkSize; offset < fileSize; offset += PACKET_SIZE) {
|
|
55
|
-
const chunk = Buffer.alloc(PACKET_SIZE, 0);
|
|
56
|
-
zipData.copy(chunk, 0, offset, Math.min(offset + PACKET_SIZE, fileSize));
|
|
57
|
-
packets.push(chunk);
|
|
58
|
-
}
|
|
59
|
-
return packets;
|
|
60
|
-
}
|
|
61
|
-
export function buildBrightnessPacket(brightness) {
|
|
62
|
-
const val = Math.max(0, Math.min(100, Math.round(brightness)));
|
|
63
|
-
return buildPacket(CMD.SET_BRIGHTNESS, Buffer.from(String(val), 'utf-8'));
|
|
64
|
-
}
|
|
65
|
-
export function buildSmallWindowPacket(mode, cpu, mem, time, gpu) {
|
|
66
|
-
const data = `${mode}|${cpu}|${mem}|${time}|${gpu}`;
|
|
67
|
-
return buildPacket(CMD.SET_SMALL_WINDOW, Buffer.from(data, 'utf-8'));
|
|
68
|
-
}
|
|
69
|
-
export function parseIncoming(data) {
|
|
70
|
-
if (data.length < 8)
|
|
71
|
-
return null;
|
|
72
|
-
if (data[0] !== 0x7c || data[1] !== 0x7c)
|
|
73
|
-
return null;
|
|
74
|
-
const command = data.readUInt16BE(2);
|
|
75
|
-
if (command === CMD.IN_BUTTON && data.length >= 12) {
|
|
76
|
-
return {
|
|
77
|
-
type: 'button',
|
|
78
|
-
data: {
|
|
79
|
-
state: data[8],
|
|
80
|
-
index: data[9],
|
|
81
|
-
pressed: data[11] === 0x01,
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
if (command === CMD.IN_DEVICE_INFO) {
|
|
86
|
-
try {
|
|
87
|
-
const jsonStr = data.subarray(8).toString('ascii').split('\0')[0];
|
|
88
|
-
const info = JSON.parse(jsonStr);
|
|
89
|
-
return {
|
|
90
|
-
type: 'device_info',
|
|
91
|
-
data: {
|
|
92
|
-
serialNumber: info.SerialNumber ?? '',
|
|
93
|
-
firmwareVersion: info.Dversion ?? '',
|
|
94
|
-
deviceType: info.DeviceType ?? '',
|
|
95
|
-
hardwareVersion: info.HardwareVersion ?? '',
|
|
96
|
-
},
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
catch {
|
|
100
|
-
return { type: 'unknown', command, raw: data };
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
return { type: 'unknown', command, raw: data };
|
|
104
|
-
}
|
|
105
|
-
// --- ZIP validation (boundary byte workaround) ---
|
|
106
|
-
/**
|
|
107
|
-
* Check if a ZIP has invalid bytes at 1024-byte chunk boundaries.
|
|
108
|
-
* At offset 1016, 2040, 3064... the byte must not be 0x00 or 0x7C.
|
|
109
|
-
*/
|
|
110
|
-
export function validateZipBoundaries(zipData) {
|
|
111
|
-
for (let i = 1016; i < zipData.length; i += PACKET_SIZE) {
|
|
112
|
-
if (INVALID_BOUNDARY_BYTES.includes(zipData[i])) {
|
|
113
|
-
return false;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
return true;
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Generate a random dummy string to pad the ZIP until boundaries are valid.
|
|
120
|
-
*/
|
|
121
|
-
export function generateDummyPadding(attempt) {
|
|
122
|
-
return 'AgentDeck ' + randomBytes(8 * (attempt + 1)).toString('hex');
|
|
123
|
-
}
|
|
124
|
-
//# sourceMappingURL=hid-protocol.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hid-protocol.js","sourceRoot":"","sources":["../../src/d200h/hid-protocol.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* D200H Image Renderer — Renders AgentDeck state as 196×196 PNG key icons
|
|
3
|
-
* using the shared SVG renderers (same visual output as Stream Deck plugin).
|
|
4
|
-
*
|
|
5
|
-
* Pipeline: state → shared SVG generators (144×144) → resvg rasterize (196×196 PNG) → ZIP
|
|
6
|
-
*
|
|
7
|
-
* Falls back to solid-color PNGs if resvg-js is not available.
|
|
8
|
-
*/
|
|
9
|
-
import { parseState, buildButtonCommandMap } from '@agentdeck/shared';
|
|
10
|
-
export { parseState, buildButtonCommandMap };
|
|
11
|
-
export type { DashState, ButtonCommand, KeySlot } from '@agentdeck/shared';
|
|
12
|
-
/** Initialize the renderer (call once at module start). */
|
|
13
|
-
export declare function initRenderer(): Promise<void>;
|
|
14
|
-
export declare function isResvgLoaded(): boolean;
|
|
15
|
-
/**
|
|
16
|
-
* Render the full AgentDeck dashboard as a ZIP ready for SET_BUTTONS.
|
|
17
|
-
* Uses shared SVG renderers → resvg rasterization for SD+-quality output.
|
|
18
|
-
*/
|
|
19
|
-
export declare function renderDashboardZip(stateEvt: any): Buffer;
|
|
20
|
-
/**
|
|
21
|
-
* Create a simple hash of the visual state for change detection.
|
|
22
|
-
*/
|
|
23
|
-
export declare function stateHash(stateEvt: any): string;
|
|
24
|
-
//# sourceMappingURL=image-renderer.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"image-renderer.d.ts","sourceRoot":"","sources":["../../src/d200h/image-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAQH,OAAO,EAEL,UAAU,EACV,qBAAqB,EAEtB,MAAM,mBAAmB,CAAC;AAI3B,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,CAAC;AAC7C,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AA2D3E,2DAA2D;AAC3D,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAElD;AAED,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AA+LD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,GAAG,GAAG,MAAM,CAmExD;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,GAAG,GAAG,MAAM,CAI/C"}
|