@agentdeck/bridge 1.0.6 → 1.0.8
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 +10 -0
- package/dist/apme/collector.d.ts.map +1 -1
- package/dist/apme/collector.js +39 -10
- package/dist/apme/collector.js.map +1 -1
- package/dist/apme/dashboard-html.d.ts.map +1 -1
- package/dist/apme/dashboard-html.js +289 -0
- package/dist/apme/dashboard-html.js.map +1 -1
- package/dist/apme/graph.d.ts +48 -0
- package/dist/apme/graph.d.ts.map +1 -0
- package/dist/apme/graph.js +226 -0
- package/dist/apme/graph.js.map +1 -0
- package/dist/apme/http.d.ts.map +1 -1
- package/dist/apme/http.js +52 -0
- package/dist/apme/http.js.map +1 -1
- package/dist/apme/judge-detect.d.ts.map +1 -1
- package/dist/apme/judge-detect.js +26 -2
- package/dist/apme/judge-detect.js.map +1 -1
- package/dist/apme/store.d.ts +69 -1
- package/dist/apme/store.d.ts.map +1 -1
- package/dist/apme/store.js +238 -4
- package/dist/apme/store.js.map +1 -1
- package/dist/apme/types.d.ts +1 -1
- package/dist/apme/types.d.ts.map +1 -1
- package/dist/apme/types.js.map +1 -1
- package/dist/ble-sync-spawn.d.ts +25 -2
- package/dist/ble-sync-spawn.d.ts.map +1 -1
- package/dist/ble-sync-spawn.js +38 -2
- package/dist/ble-sync-spawn.js.map +1 -1
- package/dist/ble-sync-status.d.ts +77 -0
- package/dist/ble-sync-status.d.ts.map +1 -0
- package/dist/ble-sync-status.js +157 -0
- package/dist/ble-sync-status.js.map +1 -0
- package/dist/bridge-core.d.ts.map +1 -1
- package/dist/bridge-core.js +7 -1
- package/dist/bridge-core.js.map +1 -1
- package/dist/check-deps.d.ts.map +1 -1
- package/dist/check-deps.js +4 -12
- package/dist/check-deps.js.map +1 -1
- package/dist/cli.js +0 -0
- package/dist/codex-rate-limits-live.d.ts +60 -0
- package/dist/codex-rate-limits-live.d.ts.map +1 -0
- package/dist/codex-rate-limits-live.js +282 -0
- package/dist/codex-rate-limits-live.js.map +1 -0
- package/dist/daemon-server.d.ts.map +1 -1
- package/dist/daemon-server.js +81 -3
- package/dist/daemon-server.js.map +1 -1
- package/dist/daemon.js +1 -1
- package/dist/idotmatrix/idotmatrix-daemon-sync.d.ts +8 -0
- package/dist/idotmatrix/idotmatrix-daemon-sync.d.ts.map +1 -1
- package/dist/idotmatrix/idotmatrix-daemon-sync.js +19 -3
- package/dist/idotmatrix/idotmatrix-daemon-sync.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -4
- package/dist/index.js.map +1 -1
- package/dist/modules/idotmatrix-module.d.ts.map +1 -1
- package/dist/modules/idotmatrix-module.js +12 -1
- package/dist/modules/idotmatrix-module.js.map +1 -1
- package/dist/modules/timebox-module.d.ts.map +1 -1
- package/dist/modules/timebox-module.js +12 -1
- package/dist/modules/timebox-module.js.map +1 -1
- package/dist/timebox/timebox-daemon-sync.d.ts +8 -0
- package/dist/timebox/timebox-daemon-sync.d.ts.map +1 -1
- package/dist/timebox/timebox-daemon-sync.js +29 -1
- package/dist/timebox/timebox-daemon-sync.js.map +1 -1
- package/dist/timeline-summarizer.d.ts +12 -2
- package/dist/timeline-summarizer.d.ts.map +1 -1
- package/dist/timeline-summarizer.js +66 -4
- package/dist/timeline-summarizer.js.map +1 -1
- package/dist/voice-assistant.d.ts +2 -2
- package/dist/voice-assistant.d.ts.map +1 -1
- package/dist/voice-assistant.js +1 -1
- package/dist/voice.d.ts +5 -5
- package/dist/voice.d.ts.map +1 -1
- package/dist/voice.js +8 -10
- package/dist/voice.js.map +1 -1
- package/package.json +13 -14
- package/src/idotmatrix/sync.py +16 -1
- package/src/pysync/matrix_sync_common.py +83 -0
- package/src/timebox/sync_ble.py +16 -0
|
@@ -7,8 +7,11 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { deviceId, loadTimeboxDevices } from './timebox-settings.js';
|
|
9
9
|
import { createSyncCycleSquelch, spawnPythonSync, terminateSyncChild } from '../ble-sync-spawn.js';
|
|
10
|
+
import { createBleLinkTracker, mergeBleLinkSnapshots, } from '../ble-sync-status.js';
|
|
10
11
|
import { getBleRuntimeStatus } from '../python-ble-runtime.js';
|
|
11
12
|
const entries = new Map();
|
|
13
|
+
/** Set when no child could be spawned at all (missing BLE runtime). */
|
|
14
|
+
let unavailableReason = null;
|
|
12
15
|
const BASE_BACKOFF_MS = 5_000;
|
|
13
16
|
const MAX_BACKOFF_MS = 60_000;
|
|
14
17
|
const HEALTHY_UPTIME_MS = 30_000;
|
|
@@ -22,8 +25,12 @@ export function startTimeboxSync(httpPort) {
|
|
|
22
25
|
const runtime = getBleRuntimeStatus();
|
|
23
26
|
if (!runtime.ready || !runtime.python) {
|
|
24
27
|
log(`sync unavailable (${runtime.reason}); run \`agentdeck ble setup\``);
|
|
28
|
+
// Register the reason so /health can say *why* the panel is dark instead of
|
|
29
|
+
// reporting a bare `connected:false` with no explanation.
|
|
30
|
+
unavailableReason = `BLE sync unavailable: ${runtime.reason}`;
|
|
25
31
|
return;
|
|
26
32
|
}
|
|
33
|
+
unavailableReason = null;
|
|
27
34
|
for (const device of devices) {
|
|
28
35
|
const id = deviceId(device);
|
|
29
36
|
if (!id || entries.has(id))
|
|
@@ -36,6 +43,7 @@ export function startTimeboxSync(httpPort) {
|
|
|
36
43
|
consecutiveFailures: 0,
|
|
37
44
|
startedAt: 0,
|
|
38
45
|
squelch: createSyncCycleSquelch(log),
|
|
46
|
+
link: createBleLinkTracker(),
|
|
39
47
|
};
|
|
40
48
|
entries.set(id, entry);
|
|
41
49
|
spawnSync(entry, runtime.python, runtime.paths.scripts.timeboxSync, httpPort);
|
|
@@ -51,9 +59,12 @@ function spawnSync(entry, venvPython, syncScript, httpPort) {
|
|
|
51
59
|
const args = [syncScript, '--address', device.address, '--url', url, '--brightness', String(brightness)];
|
|
52
60
|
entry.squelch.logStart(`Starting BLE sync for ${device.name ?? 'Timebox Mini'} (${id}, bridge ${url}, brightness ${brightness}%)`);
|
|
53
61
|
entry.startedAt = Date.now();
|
|
62
|
+
entry.link.noteSpawn();
|
|
54
63
|
// stdout/stderr are captured into small rings so clean exits and crashes both
|
|
55
64
|
// leave enough context without flooding the daemon log while running.
|
|
56
|
-
const { proc, stderrTail, outputTail } = spawnPythonSync(venvPython, args
|
|
65
|
+
const { proc, stderrTail, outputTail } = spawnPythonSync(venvPython, args, {
|
|
66
|
+
onStatus: (payload) => entry.link.applyStatusLine(payload),
|
|
67
|
+
});
|
|
57
68
|
entry.child = proc;
|
|
58
69
|
proc.on('error', (err) => {
|
|
59
70
|
log(`sync failed to spawn for ${id}: ${err.message}`);
|
|
@@ -74,6 +85,7 @@ function spawnSync(entry, venvPython, syncScript, httpPort) {
|
|
|
74
85
|
const delay = Math.min(MAX_BACKOFF_MS, BASE_BACKOFF_MS * entry.consecutiveFailures);
|
|
75
86
|
const tail = stderrTail() || outputTail();
|
|
76
87
|
const why = tail ? `; output: ${tail}` : '';
|
|
88
|
+
entry.link.noteExit(tail || `sync exited (code=${code} signal=${signal})`, Date.now() + delay);
|
|
77
89
|
entry.squelch.logExit(code, signal, uptimeMs, tail, `sync for ${id} exited (code=${code} signal=${signal})${why}; respawning in ${Math.round(delay / 1000)}s`);
|
|
78
90
|
entry.respawnTimer = setTimeout(() => spawnSync(entry, venvPython, syncScript, httpPort), delay);
|
|
79
91
|
if (entry.respawnTimer.unref)
|
|
@@ -114,7 +126,23 @@ export async function stopTimeboxSync(awaitFarewell = false) {
|
|
|
114
126
|
}
|
|
115
127
|
}
|
|
116
128
|
entries.clear();
|
|
129
|
+
unavailableReason = null;
|
|
117
130
|
if (procs.length)
|
|
118
131
|
await Promise.all(procs.map((p) => terminateSyncChild(p)));
|
|
119
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Live link state for the configured Timebox panels, in the `BLEMatrixHealth`
|
|
135
|
+
* wire shape. Called by `TimeboxModule.statusSnapshot()` so `/health` reports
|
|
136
|
+
* whether the panel is actually being driven — the configured-device list alone
|
|
137
|
+
* left every dashboard drawing a streaming panel as disconnected.
|
|
138
|
+
*/
|
|
139
|
+
export function timeboxLinkSnapshot(configuredDeviceCount) {
|
|
140
|
+
const merged = mergeBleLinkSnapshots([...entries.values()].map((e) => e.link.snapshot(configuredDeviceCount)), configuredDeviceCount);
|
|
141
|
+
// A configured device with no entry at all means the supervisor never got to
|
|
142
|
+
// spawn anything — surface that instead of an unexplained `connected:false`.
|
|
143
|
+
if (unavailableReason && configuredDeviceCount > 0 && !merged.connected) {
|
|
144
|
+
return { ...merged, statusReason: unavailableReason };
|
|
145
|
+
}
|
|
146
|
+
return merged;
|
|
147
|
+
}
|
|
120
148
|
//# sourceMappingURL=timebox-daemon-sync.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timebox-daemon-sync.js","sourceRoot":"","sources":["../../src/timebox/timebox-daemon-sync.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,QAAQ,EAAE,kBAAkB,EAAsB,MAAM,uBAAuB,CAAC;AACzF,OAAO,EAAE,sBAAsB,EAAE,eAAe,EAAE,kBAAkB,EAAyB,MAAM,sBAAsB,CAAC;AAC1H,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"timebox-daemon-sync.js","sourceRoot":"","sources":["../../src/timebox/timebox-daemon-sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,10 +2,20 @@
|
|
|
2
2
|
* Timeline summarizer — uses local LLM to create concise 1-line summaries
|
|
3
3
|
* of OpenClaw chat responses for timeline display.
|
|
4
4
|
*
|
|
5
|
-
* Tries: local mlx-serve qwen (port 8800) →
|
|
6
|
-
* Non-blocking — caller should fire-and-forget,
|
|
5
|
+
* Tries: on-device Foundation Models → local mlx-serve qwen (port 8800) →
|
|
6
|
+
* Ollama → heuristic fallback. Non-blocking — caller should fire-and-forget,
|
|
7
|
+
* update entry when ready.
|
|
8
|
+
*
|
|
9
|
+
* The provider order is the Node mirror of Swift's `TimelineSummarizer`
|
|
10
|
+
* `.auto` chain (FoundationModels → MLX → Ollama → heuristic). It was
|
|
11
|
+
* MLX-first for a long time, which meant a CLI-only user on macOS 26 with
|
|
12
|
+
* Apple Intelligence available still got heuristic rows unless they also ran
|
|
13
|
+
* an MLX server — the same machine summarized differently depending on which
|
|
14
|
+
* daemon happened to be up. Keep the two chains in the same order.
|
|
7
15
|
*/
|
|
8
16
|
export { extractTopicHint } from '@agentdeck/shared';
|
|
17
|
+
/** Reset the cached provider availability (tests only). */
|
|
18
|
+
export declare function clearSummarizerProviderCacheForTests(): void;
|
|
9
19
|
/**
|
|
10
20
|
* Summarize a chat response into a concise 1-line Korean summary.
|
|
11
21
|
* Returns null if summarization fails (caller should use fallback).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timeline-summarizer.d.ts","sourceRoot":"","sources":["../src/timeline-summarizer.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"timeline-summarizer.d.ts","sourceRoot":"","sources":["../src/timeline-summarizer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAmCrD,2DAA2D;AAC3D,wBAAgB,oCAAoC,IAAI,IAAI,CAS3D;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAyF5E"}
|
|
@@ -2,12 +2,21 @@
|
|
|
2
2
|
* Timeline summarizer — uses local LLM to create concise 1-line summaries
|
|
3
3
|
* of OpenClaw chat responses for timeline display.
|
|
4
4
|
*
|
|
5
|
-
* Tries: local mlx-serve qwen (port 8800) →
|
|
6
|
-
* Non-blocking — caller should fire-and-forget,
|
|
5
|
+
* Tries: on-device Foundation Models → local mlx-serve qwen (port 8800) →
|
|
6
|
+
* Ollama → heuristic fallback. Non-blocking — caller should fire-and-forget,
|
|
7
|
+
* update entry when ready.
|
|
8
|
+
*
|
|
9
|
+
* The provider order is the Node mirror of Swift's `TimelineSummarizer`
|
|
10
|
+
* `.auto` chain (FoundationModels → MLX → Ollama → heuristic). It was
|
|
11
|
+
* MLX-first for a long time, which meant a CLI-only user on macOS 26 with
|
|
12
|
+
* Apple Intelligence available still got heuristic rows unless they also ran
|
|
13
|
+
* an MLX server — the same machine summarized differently depending on which
|
|
14
|
+
* daemon happened to be up. Keep the two chains in the same order.
|
|
7
15
|
*/
|
|
8
16
|
import { debug, log } from './logger.js';
|
|
9
17
|
import { SUMMARY_SYSTEM_PROMPT, cleanLLMOutput, mlxChatUrl, resolveMlxModel } from '@agentdeck/shared';
|
|
10
18
|
import { fetchMlxModels } from './mlx-probe.js';
|
|
19
|
+
import { callFoundationModelsHelper, probeFoundationModelsHelper } from './foundation-models-helper.js';
|
|
11
20
|
export { extractTopicHint } from '@agentdeck/shared';
|
|
12
21
|
const MLX_URL = mlxChatUrl();
|
|
13
22
|
// In-memory cache of the probe's first result, so summarizers don't hit
|
|
@@ -32,11 +41,24 @@ async function resolveModelForCall() {
|
|
|
32
41
|
const OLLAMA_URL = 'http://localhost:11434/api/chat';
|
|
33
42
|
const TIMEOUT_MS = 30_000; // 30s — first inference needs model load time
|
|
34
43
|
const MAX_INPUT_CHARS = 2000;
|
|
44
|
+
let fmAvailable = null;
|
|
35
45
|
let mlxAvailable = null;
|
|
36
46
|
let ollamaAvailable = null;
|
|
47
|
+
let fmFailedAt = 0;
|
|
37
48
|
let mlxFailedAt = 0;
|
|
38
49
|
let ollamaFailedAt = 0;
|
|
39
50
|
const RETRY_INTERVAL_MS = 60_000; // retry failed providers after 60s
|
|
51
|
+
/** Reset the cached provider availability (tests only). */
|
|
52
|
+
export function clearSummarizerProviderCacheForTests() {
|
|
53
|
+
fmAvailable = null;
|
|
54
|
+
mlxAvailable = null;
|
|
55
|
+
ollamaAvailable = null;
|
|
56
|
+
fmFailedAt = 0;
|
|
57
|
+
mlxFailedAt = 0;
|
|
58
|
+
ollamaFailedAt = 0;
|
|
59
|
+
probedFirstModel = null;
|
|
60
|
+
probedAt = 0;
|
|
61
|
+
}
|
|
40
62
|
/**
|
|
41
63
|
* Summarize a chat response into a concise 1-line Korean summary.
|
|
42
64
|
* Returns null if summarization fails (caller should use fallback).
|
|
@@ -49,7 +71,29 @@ export async function summarizeResponse(text) {
|
|
|
49
71
|
: text;
|
|
50
72
|
let mlxJustFailed = false;
|
|
51
73
|
let ollamaJustFailed = false;
|
|
52
|
-
// Try
|
|
74
|
+
// Try on-device Foundation Models first — no server for the user to run,
|
|
75
|
+
// and it is the same helper process the APME judge already keeps warm.
|
|
76
|
+
// A miss here is not an error state: off-macOS, on macOS < 26, or with
|
|
77
|
+
// Apple Intelligence disabled the probe simply reports unavailable and the
|
|
78
|
+
// chain moves on. Only MLX+Ollama failing together is worth telling the
|
|
79
|
+
// user about (that pair is what the user opts into by installing them).
|
|
80
|
+
if (fmAvailable !== false || (Date.now() - fmFailedAt > RETRY_INTERVAL_MS)) {
|
|
81
|
+
try {
|
|
82
|
+
const result = await callFoundationModels(input);
|
|
83
|
+
if (result) {
|
|
84
|
+
if (fmAvailable === false)
|
|
85
|
+
debug('summarizer', 'Foundation Models recovered');
|
|
86
|
+
fmAvailable = true;
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
fmAvailable = false;
|
|
92
|
+
fmFailedAt = Date.now();
|
|
93
|
+
debug('summarizer', `Foundation Models not available: ${String(err)}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Try MLX qwen next (retry after RETRY_INTERVAL_MS)
|
|
53
97
|
if (mlxAvailable !== false || (Date.now() - mlxFailedAt > RETRY_INTERVAL_MS)) {
|
|
54
98
|
try {
|
|
55
99
|
const result = await callMLX(input);
|
|
@@ -99,11 +143,29 @@ export async function summarizeResponse(text) {
|
|
|
99
143
|
if ((mlxJustFailed && ollamaJustFailed)
|
|
100
144
|
|| (mlxJustFailed && ollamaAvailable === false)
|
|
101
145
|
|| (ollamaJustFailed && mlxAvailable === false)) {
|
|
102
|
-
log(
|
|
146
|
+
log(`[timeline] LLM summary backend offline (on-device Foundation Models: ${fmAvailable === false ? 'unavailable' : 'not reached'}, MLX:8800, Ollama:11434).`, 'Timeline rows will use heuristic summaries.', 'Install MLX (`mlx_vlm.server`) or Ollama to get LLM-summarized chat_end rows.');
|
|
103
147
|
}
|
|
104
148
|
return null;
|
|
105
149
|
}
|
|
106
150
|
// extractTopicHint and cleanLLMOutput moved to @agentdeck/shared/timeline-summarizer
|
|
151
|
+
/** On-device summary via the bundled Swift helper (Apple Intelligence).
|
|
152
|
+
* Throws when the helper is unavailable so the caller falls to MLX; the
|
|
153
|
+
* helper owns its own request timeout, so there is no unbounded await here. */
|
|
154
|
+
async function callFoundationModels(input) {
|
|
155
|
+
// Probe only on the first attempt: the probe is a full health round-trip to
|
|
156
|
+
// the helper, and after that the generate call is its own liveness check.
|
|
157
|
+
// (`fmAvailable` is null exactly once — the caller flips it either way.)
|
|
158
|
+
if (fmAvailable === null) {
|
|
159
|
+
const status = await probeFoundationModelsHelper();
|
|
160
|
+
if (!status.available)
|
|
161
|
+
throw new Error(status.reason ?? 'helper unavailable');
|
|
162
|
+
}
|
|
163
|
+
const text = await callFoundationModelsHelper(input, SUMMARY_SYSTEM_PROMPT);
|
|
164
|
+
const result = cleanLLMOutput(text);
|
|
165
|
+
if (result)
|
|
166
|
+
debug('summarizer', `Foundation Models summary: ${result}`);
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
107
169
|
async function callMLX(input) {
|
|
108
170
|
const controller = new AbortController();
|
|
109
171
|
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timeline-summarizer.js","sourceRoot":"","sources":["../src/timeline-summarizer.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"timeline-summarizer.js","sourceRoot":"","sources":["../src/timeline-summarizer.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* 1. WakeWordListener detects "오픈클로"
|
|
6
6
|
* 2. Record user speech via sox/rec (reusing VoiceManager's approach)
|
|
7
7
|
* 3. VAD: stop recording on silence
|
|
8
|
-
* 4. Transcribe via
|
|
8
|
+
* 4. Transcribe via the bundled helper (Apple on-device Speech)
|
|
9
9
|
* 5. Route to OpenClaw Gateway (or Claude Code) via adapter
|
|
10
10
|
* 6. Receive response text
|
|
11
11
|
* 7. Synthesize + play via macOS say
|
|
@@ -17,7 +17,7 @@ import type { VoiceAssistantState } from '@agentdeck/shared';
|
|
|
17
17
|
export interface VoiceAssistantOptions {
|
|
18
18
|
/** Callback to send a prompt to the active agent */
|
|
19
19
|
sendPrompt: (text: string) => void;
|
|
20
|
-
/** Callback to transcribe a WAV file
|
|
20
|
+
/** Callback to transcribe a WAV file (Apple on-device Speech via the helper) */
|
|
21
21
|
transcribeFile: (filePath: string) => Promise<string>;
|
|
22
22
|
/** Function that returns true if push-to-talk is recording (mutex) */
|
|
23
23
|
isPttRecording?: () => boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"voice-assistant.d.ts","sourceRoot":"","sources":["../src/voice-assistant.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAStC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AA4D7D,MAAM,WAAW,qBAAqB;IACpC,oDAAoD;IACpD,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,
|
|
1
|
+
{"version":3,"file":"voice-assistant.d.ts","sourceRoot":"","sources":["../src/voice-assistant.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAStC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AA4D7D,MAAM,WAAW,qBAAqB;IACpC,oDAAoD;IACpD,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,gFAAgF;IAChF,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACtD,sEAAsE;IACtE,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC;CAChC;AAED,qBAAa,qBAAsB,SAAQ,YAAY;IACrD,OAAO,CAAC,KAAK,CAAmC;IAChD,OAAO,CAAC,QAAQ,CAAmB;IACnC,OAAO,CAAC,GAAG,CAAY;IACvB,OAAO,CAAC,IAAI,CAAwB;IACpC,OAAO,CAAC,MAAM,CAAS;IAGvB,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,SAAS,CAAM;IACvB,OAAO,CAAC,QAAQ,CAA+C;IAC/D,OAAO,CAAC,iBAAiB,CAA8C;IACvE,OAAO,CAAC,cAAc,CAAK;IAG3B,OAAO,CAAC,eAAe,CAA8C;IAGrE,OAAO,CAAC,eAAe,CAAM;gBAEjB,IAAI,EAAE,qBAAqB;IAcvC,6DAA6D;IACvD,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC;IA2B/B,0CAA0C;IAC1C,IAAI,IAAI,IAAI;IAQZ,QAAQ,IAAI,mBAAmB;IAI/B;;;;OAIG;IACH,oBAAoB,IAAI,IAAI;IAa5B,8CAA8C;IACxC,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqCjD,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,kBAAkB;IAyB1B,OAAO,CAAC,cAAc;IA+CtB,OAAO,CAAC,QAAQ;YAoBF,eAAe;IA+F7B,8EAA8E;YAChE,cAAc;IAkB5B,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,WAAW;IAWnB,OAAO,CAAC,YAAY;CAMrB"}
|
package/dist/voice-assistant.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* 1. WakeWordListener detects "오픈클로"
|
|
6
6
|
* 2. Record user speech via sox/rec (reusing VoiceManager's approach)
|
|
7
7
|
* 3. VAD: stop recording on silence
|
|
8
|
-
* 4. Transcribe via
|
|
8
|
+
* 4. Transcribe via the bundled helper (Apple on-device Speech)
|
|
9
9
|
* 5. Route to OpenClaw Gateway (or Claude Code) via adapter
|
|
10
10
|
* 6. Receive response text
|
|
11
11
|
* 7. Synthesize + play via macOS say
|
package/dist/voice.d.ts
CHANGED
|
@@ -6,11 +6,11 @@ export declare class VoiceManager extends EventEmitter {
|
|
|
6
6
|
private recBin;
|
|
7
7
|
private soxBin;
|
|
8
8
|
constructor();
|
|
9
|
-
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
/** Probe the bundled Swift helper so an unavailable recognizer shows up in
|
|
10
|
+
* the log at startup rather than on the user's first utterance. There is
|
|
11
|
+
* nothing to connect to or tear down — the helper is a long-lived process
|
|
12
|
+
* owned by foundation-models-helper.ts. */
|
|
13
|
+
probeSpeechHelper(): Promise<void>;
|
|
14
14
|
startRecording(): void;
|
|
15
15
|
stopRecording(): Promise<string>;
|
|
16
16
|
cancel(): void;
|
package/dist/voice.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"voice.d.ts","sourceRoot":"","sources":["../src/voice.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"voice.d.ts","sourceRoot":"","sources":["../src/voice.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AA8BtC,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;;;gDAG4C;IACtC,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAOxC,cAAc,IAAI,IAAI;IA2ChB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;IA0DtC,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
|
@@ -13,7 +13,6 @@ function findBinary(candidates, fallback) {
|
|
|
13
13
|
}
|
|
14
14
|
return fallback;
|
|
15
15
|
}
|
|
16
|
-
/** Check if a whisper-cli binary has Metal GPU support (native arm64 + libggml-metal). */
|
|
17
16
|
/** Compute RMS energy of a 16-bit PCM WAV file (skip 44-byte header). */
|
|
18
17
|
function computeRms(wavFile) {
|
|
19
18
|
const buf = readFileSync(wavFile);
|
|
@@ -40,18 +39,16 @@ export class VoiceManager extends EventEmitter {
|
|
|
40
39
|
this.soxBin = findBinary(SOX_CANDIDATES, 'sox');
|
|
41
40
|
debug('Voice', `Binaries: rec=${this.recBin}, sox=${this.soxBin} (transcription: Apple Speech via bundled helper)`);
|
|
42
41
|
}
|
|
43
|
-
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
|
|
42
|
+
/** Probe the bundled Swift helper so an unavailable recognizer shows up in
|
|
43
|
+
* the log at startup rather than on the user's first utterance. There is
|
|
44
|
+
* nothing to connect to or tear down — the helper is a long-lived process
|
|
45
|
+
* owned by foundation-models-helper.ts. */
|
|
46
|
+
async probeSpeechHelper() {
|
|
47
47
|
const status = await probeFoundationModelsHelper();
|
|
48
48
|
if (!status.available) {
|
|
49
49
|
debug('Voice', `Speech helper unavailable: ${status.reason ?? 'unknown'}`);
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
-
disconnectFromServer() {
|
|
53
|
-
// Helper lifetime is owned by foundation-models-helper.ts.
|
|
54
|
-
}
|
|
55
52
|
startRecording() {
|
|
56
53
|
if (this.recording)
|
|
57
54
|
return;
|
|
@@ -120,14 +117,15 @@ export class VoiceManager extends EventEmitter {
|
|
|
120
117
|
this.cleanup();
|
|
121
118
|
throw new Error('Recording too short or empty');
|
|
122
119
|
}
|
|
123
|
-
// Check audio RMS to detect silence
|
|
120
|
+
// Check audio RMS to detect silence — a recognizer handed silence returns
|
|
121
|
+
// invented text rather than an error, so catch it before transcribing.
|
|
124
122
|
const rms = computeRms(this.audioFile);
|
|
125
123
|
debug('Voice', `Audio RMS: ${rms.toFixed(4)}`);
|
|
126
124
|
if (rms < 0.001) {
|
|
127
125
|
this.cleanup();
|
|
128
126
|
throw new Error('No audio detected — check microphone permission');
|
|
129
127
|
}
|
|
130
|
-
// --- Transcription:
|
|
128
|
+
// --- Transcription: Apple on-device Speech via the bundled helper ---
|
|
131
129
|
try {
|
|
132
130
|
const text = await this.transcribeViaHelper(this.audioFile);
|
|
133
131
|
debug('Voice', `Transcription result: "${text.slice(0, 80)}"`);
|
package/dist/voice.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"voice.js","sourceRoot":"","sources":["../src/voice.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"voice.js","sourceRoot":"","sources":["../src/voice.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentdeck/bridge",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -24,18 +24,7 @@
|
|
|
24
24
|
"bin": {
|
|
25
25
|
"agentdeck": "./dist/cli.js"
|
|
26
26
|
},
|
|
27
|
-
"scripts": {
|
|
28
|
-
"build": "tsc",
|
|
29
|
-
"build:fm-helper": "node scripts/build-fm-helper.mjs",
|
|
30
|
-
"prepack": "node scripts/build-fm-helper.mjs",
|
|
31
|
-
"dev": "tsc --watch",
|
|
32
|
-
"clean": "rm -rf dist",
|
|
33
|
-
"typecheck": "tsc --noEmit",
|
|
34
|
-
"start": "node dist/cli.js claude"
|
|
35
|
-
},
|
|
36
27
|
"dependencies": {
|
|
37
|
-
"@agentdeck/hooks": "workspace:*",
|
|
38
|
-
"@agentdeck/shared": "workspace:*",
|
|
39
28
|
"@anthropic-ai/sdk": "^0.111.0",
|
|
40
29
|
"bonjour-service": "^1.3.0",
|
|
41
30
|
"commander": "^14.0.3",
|
|
@@ -43,7 +32,9 @@
|
|
|
43
32
|
"qrcode": "^1.5.4",
|
|
44
33
|
"sharp": "^0.34.5",
|
|
45
34
|
"strip-ansi": "^7.2.0",
|
|
46
|
-
"ws": "^8.20.0"
|
|
35
|
+
"ws": "^8.20.0",
|
|
36
|
+
"@agentdeck/shared": "1.0.8",
|
|
37
|
+
"@agentdeck/hooks": "1.0.8"
|
|
47
38
|
},
|
|
48
39
|
"optionalDependencies": {
|
|
49
40
|
"@picovoice/porcupine-node": "^4.0.2",
|
|
@@ -59,5 +50,13 @@
|
|
|
59
50
|
"@types/qrcode": "^1.5.6",
|
|
60
51
|
"@types/ws": "^8.18.1",
|
|
61
52
|
"typescript": "^6.0.3"
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "tsc",
|
|
56
|
+
"build:fm-helper": "node scripts/build-fm-helper.mjs",
|
|
57
|
+
"dev": "tsc --watch",
|
|
58
|
+
"clean": "rm -rf dist",
|
|
59
|
+
"typecheck": "tsc --noEmit",
|
|
60
|
+
"start": "node dist/cli.js claude"
|
|
62
61
|
}
|
|
63
|
-
}
|
|
62
|
+
}
|
package/src/idotmatrix/sync.py
CHANGED
|
@@ -21,6 +21,7 @@ from matrix_sync_common import ( # noqa: E402
|
|
|
21
21
|
DEFAULT_URL,
|
|
22
22
|
POLL_INTERVAL,
|
|
23
23
|
BRIDGE_GONE_EXIT_SEC,
|
|
24
|
+
StatusReporter,
|
|
24
25
|
fetch_display_state as _fetch_display_state_sync,
|
|
25
26
|
bridge_reachable as _bridge_reachable_sync,
|
|
26
27
|
resolve_display_brightness as _resolve_display_brightness_common,
|
|
@@ -124,7 +125,13 @@ async def run_sync(address: str, url: str, brightness: int = 100, boost: float =
|
|
|
124
125
|
print(f"AgentDeck Bridge API URL: {url}")
|
|
125
126
|
print(f"Initial Hardware Brightness: {brightness}%")
|
|
126
127
|
print(f"Software Brightness Boost: {boost}x")
|
|
127
|
-
|
|
128
|
+
|
|
129
|
+
# Machine-readable link state for the daemon supervisor — it spawns us but
|
|
130
|
+
# cannot see the BLE link, and we reconnect inside this loop rather than
|
|
131
|
+
# exiting, so process liveness tells it nothing about whether the panel is
|
|
132
|
+
# actually being driven.
|
|
133
|
+
status = StatusReporter()
|
|
134
|
+
|
|
128
135
|
manager = ConnectionManager()
|
|
129
136
|
|
|
130
137
|
idm_image = IdmImage()
|
|
@@ -187,8 +194,10 @@ async def run_sync(address: str, url: str, brightness: int = 100, boost: float =
|
|
|
187
194
|
# 1. Ensure bluetooth connection
|
|
188
195
|
if not connected:
|
|
189
196
|
print(f"Connecting to iDotMatrix ({address})...")
|
|
197
|
+
status.connecting()
|
|
190
198
|
await manager.connectByAddress(address)
|
|
191
199
|
print("Connected to Bluetooth device!")
|
|
200
|
+
status.connected()
|
|
192
201
|
|
|
193
202
|
# Settle: the panel drops commands sent too soon after the GATT
|
|
194
203
|
# link comes up. Without this the first setBrightness is lost on a
|
|
@@ -227,6 +236,7 @@ async def run_sync(address: str, url: str, brightness: int = 100, boost: float =
|
|
|
227
236
|
# bleak's is_connected reflects the real OS-level link state.
|
|
228
237
|
if manager.client is None or not manager.client.is_connected:
|
|
229
238
|
print("BLE link lost — reconnecting...")
|
|
239
|
+
status.disconnected("BLE link lost")
|
|
230
240
|
connected = False
|
|
231
241
|
last_hash = ""
|
|
232
242
|
await _interruptible_sleep(stop_event, 1.0)
|
|
@@ -274,6 +284,7 @@ async def run_sync(address: str, url: str, brightness: int = 100, boost: float =
|
|
|
274
284
|
last_brightness_assert = time.monotonic()
|
|
275
285
|
|
|
276
286
|
if display_dimmed:
|
|
287
|
+
status.streaming(dimmed=True)
|
|
277
288
|
await _interruptible_sleep(stop_event, POLL_INTERVAL)
|
|
278
289
|
continue
|
|
279
290
|
|
|
@@ -302,6 +313,7 @@ async def run_sync(address: str, url: str, brightness: int = 100, boost: float =
|
|
|
302
313
|
current_hash = hashlib.sha256(frame_data).hexdigest()
|
|
303
314
|
if current_hash == last_hash:
|
|
304
315
|
# Frame didn't change, skip BLE transmission to save battery and bandwidth
|
|
316
|
+
status.streaming()
|
|
305
317
|
await _interruptible_sleep(stop_event, POLL_INTERVAL)
|
|
306
318
|
continue
|
|
307
319
|
|
|
@@ -332,8 +344,10 @@ async def run_sync(address: str, url: str, brightness: int = 100, boost: float =
|
|
|
332
344
|
if res:
|
|
333
345
|
last_hash = current_hash
|
|
334
346
|
print("Frame uploaded successfully.")
|
|
347
|
+
status.frame_sent()
|
|
335
348
|
else:
|
|
336
349
|
print("Failed to upload frame (uploadUnprocessed returned False).")
|
|
350
|
+
status.disconnected("frame upload failed")
|
|
337
351
|
|
|
338
352
|
await _interruptible_sleep(stop_event, POLL_INTERVAL)
|
|
339
353
|
|
|
@@ -343,6 +357,7 @@ async def run_sync(address: str, url: str, brightness: int = 100, boost: float =
|
|
|
343
357
|
except Exception as e:
|
|
344
358
|
print(f"Error during loop: {e}")
|
|
345
359
|
print("Resetting bluetooth connection...")
|
|
360
|
+
status.failed(e)
|
|
346
361
|
connected = False
|
|
347
362
|
try:
|
|
348
363
|
await manager.disconnect()
|
|
@@ -10,6 +10,7 @@ so this module must stay dependency-free beyond the stdlib.
|
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
import json
|
|
13
|
+
import sys
|
|
13
14
|
import urllib.request
|
|
14
15
|
|
|
15
16
|
DEFAULT_URL = "http://127.0.0.1:9120"
|
|
@@ -87,3 +88,85 @@ def resolve_display_brightness(display_state, normal_brightness, *, off_floor, l
|
|
|
87
88
|
if display_on or not dim_enabled:
|
|
88
89
|
return normal_brightness, False, signature, dim_mode
|
|
89
90
|
return (dim_level if dim_mode == "min" else off_floor), True, signature, dim_mode
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# ---------------------------------------------------------------------------
|
|
94
|
+
# Link status reporting
|
|
95
|
+
# ---------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
# Prefix of the machine-readable status lines the daemon supervisor parses.
|
|
98
|
+
# Mirrored in bridge/src/ble-sync-spawn.ts (`BLE_STATUS_LINE_PREFIX`).
|
|
99
|
+
STATUS_LINE_PREFIX = "AGENTDECK_STATUS "
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class StatusReporter:
|
|
103
|
+
"""Report BLE link state to the daemon that spawned us, one line per change.
|
|
104
|
+
|
|
105
|
+
The Node daemon has no BLE of its own: it spawns this client and can only
|
|
106
|
+
watch the process. But both clients reconnect *inside* their own loop, so a
|
|
107
|
+
powered-off panel leaves the child running indefinitely and "process alive"
|
|
108
|
+
says nothing about whether the panel is being driven. Without this signal
|
|
109
|
+
the daemon's /health could only report the configured device list, and every
|
|
110
|
+
consumer that renders `connected` — macOS menubar, Dashboard rail, TUI —
|
|
111
|
+
drew both BLE panels as disconnected while they were streaming.
|
|
112
|
+
|
|
113
|
+
Emitting only on *change* keeps the frame loop from flooding the daemon log,
|
|
114
|
+
and the supervisor keeps these lines out of its diagnostic output ring so a
|
|
115
|
+
reconnect never looks like a new crash cycle.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
def __init__(self, emit=None):
|
|
119
|
+
self._emit = emit if emit is not None else _write_status_line
|
|
120
|
+
self._last = None
|
|
121
|
+
# Sticky: once we have painted the panel, our frame is on it even while
|
|
122
|
+
# the link is down. Matches `hasFrame` in the daemon's health shape.
|
|
123
|
+
self._has_frame = False
|
|
124
|
+
|
|
125
|
+
def _update(self, connected, phase, dimmed=False, error=None):
|
|
126
|
+
payload = {
|
|
127
|
+
"connected": bool(connected),
|
|
128
|
+
"phase": phase,
|
|
129
|
+
"hasFrame": self._has_frame,
|
|
130
|
+
"dimmed": bool(dimmed),
|
|
131
|
+
"error": error,
|
|
132
|
+
}
|
|
133
|
+
if payload == self._last:
|
|
134
|
+
return
|
|
135
|
+
self._last = payload
|
|
136
|
+
self._emit(payload)
|
|
137
|
+
|
|
138
|
+
def connecting(self):
|
|
139
|
+
self._update(False, "connecting")
|
|
140
|
+
|
|
141
|
+
def connected(self):
|
|
142
|
+
self._update(True, "connected")
|
|
143
|
+
|
|
144
|
+
def streaming(self, dimmed=False):
|
|
145
|
+
"""Link up and the frame loop is running — does not claim a painted frame.
|
|
146
|
+
|
|
147
|
+
Used on the dim/pause path, where a reconnect while the host display is
|
|
148
|
+
already asleep runs the loop without pushing anything.
|
|
149
|
+
"""
|
|
150
|
+
self._update(True, "streaming", dimmed=dimmed)
|
|
151
|
+
|
|
152
|
+
def frame_sent(self, dimmed=False):
|
|
153
|
+
self._has_frame = True
|
|
154
|
+
self.streaming(dimmed=dimmed)
|
|
155
|
+
|
|
156
|
+
def disconnected(self, error=None):
|
|
157
|
+
self._update(False, "disconnected", error=error)
|
|
158
|
+
|
|
159
|
+
def failed(self, error):
|
|
160
|
+
self._update(False, "error", error=str(error) if error is not None else None)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _write_status_line(payload):
|
|
164
|
+
"""Write one status line to stdout. Never raises — a closed pipe during
|
|
165
|
+
shutdown must not take the farewell frame down with it."""
|
|
166
|
+
try:
|
|
167
|
+
sys.stdout.write(
|
|
168
|
+
STATUS_LINE_PREFIX + json.dumps(payload, separators=(",", ":")) + "\n"
|
|
169
|
+
)
|
|
170
|
+
sys.stdout.flush()
|
|
171
|
+
except Exception:
|
|
172
|
+
pass
|