@agentdeck/bridge 1.0.5 → 1.0.7
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/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/d200h/hid-protocol.d.ts +62 -0
- package/dist/d200h/hid-protocol.d.ts.map +1 -0
- package/dist/d200h/hid-protocol.js +124 -0
- package/dist/d200h/hid-protocol.js.map +1 -0
- package/dist/d200h/image-renderer.d.ts +24 -0
- package/dist/d200h/image-renderer.d.ts.map +1 -0
- package/dist/d200h/image-renderer.js +321 -0
- package/dist/d200h/image-renderer.js.map +1 -0
- package/dist/daemon-server.d.ts.map +1 -1
- package/dist/daemon-server.js +163 -10
- package/dist/daemon-server.js.map +1 -1
- package/dist/daemon.js +1 -1
- package/dist/device-voice-reply.d.ts +1 -10
- package/dist/device-voice-reply.d.ts.map +1 -1
- package/dist/device-voice-reply.js +4 -33
- package/dist/device-voice-reply.js.map +1 -1
- package/dist/foundation-models-helper.d.ts.map +1 -1
- package/dist/foundation-models-helper.js +59 -22
- package/dist/foundation-models-helper.js.map +1 -1
- package/dist/hook-migration.d.ts +2 -0
- package/dist/hook-migration.d.ts.map +1 -0
- package/dist/hook-migration.js +116 -0
- package/dist/hook-migration.js.map +1 -0
- 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/modules/d200h-module.d.ts +65 -0
- package/dist/modules/d200h-module.d.ts.map +1 -0
- package/dist/modules/d200h-module.js +495 -0
- package/dist/modules/d200h-module.js.map +1 -0
- package/dist/modules/d200h-renderer.d.ts +7 -0
- package/dist/modules/d200h-renderer.d.ts.map +1 -0
- package/dist/modules/d200h-renderer.js +321 -0
- package/dist/modules/d200h-renderer.js.map +1 -0
- 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/modules/trmnl-module.d.ts +38 -0
- package/dist/modules/trmnl-module.d.ts.map +1 -0
- package/dist/modules/trmnl-module.js +111 -0
- package/dist/modules/trmnl-module.js.map +1 -0
- package/dist/openclaw-session.d.ts +6 -0
- package/dist/openclaw-session.d.ts.map +1 -1
- package/dist/openclaw-session.js +7 -1
- package/dist/openclaw-session.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/trmnl/byos-server.d.ts +39 -0
- package/dist/trmnl/byos-server.d.ts.map +1 -0
- package/dist/trmnl/byos-server.js +301 -0
- package/dist/trmnl/byos-server.js.map +1 -0
- package/dist/trmnl/frame-cache.d.ts +37 -0
- package/dist/trmnl/frame-cache.d.ts.map +1 -0
- package/dist/trmnl/frame-cache.js +157 -0
- package/dist/trmnl/frame-cache.js.map +1 -0
- package/dist/trmnl/image-renderer.d.ts +27 -0
- package/dist/trmnl/image-renderer.d.ts.map +1 -0
- package/dist/trmnl/image-renderer.js +235 -0
- package/dist/trmnl/image-renderer.js.map +1 -0
- package/dist/trmnl/trmnl-settings.d.ts +96 -0
- package/dist/trmnl/trmnl-settings.d.ts.map +1 -0
- package/dist/trmnl/trmnl-settings.js +177 -0
- package/dist/trmnl/trmnl-settings.js.map +1 -0
- package/dist/trmnl/trmnl-telemetry.d.ts +47 -0
- package/dist/trmnl/trmnl-telemetry.d.ts.map +1 -0
- package/dist/trmnl/trmnl-telemetry.js +56 -0
- package/dist/trmnl/trmnl-telemetry.js.map +1 -0
- package/dist/whisper-server-manager.d.ts +19 -0
- package/dist/whisper-server-manager.d.ts.map +1 -0
- package/dist/whisper-server-manager.js +171 -0
- package/dist/whisper-server-manager.js.map +1 -0
- package/fm-helper/Info.plist +27 -0
- package/package.json +13 -14
- package/scripts/build-fm-helper.mjs +35 -3
- package/src/idotmatrix/sync.py +16 -1
- package/src/pysync/matrix_sync_common.py +83 -0
- package/src/timebox/sync_ble.py +16 -0
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
|
package/src/timebox/sync_ble.py
CHANGED
|
@@ -31,6 +31,7 @@ from matrix_sync_common import ( # noqa: E402
|
|
|
31
31
|
DEFAULT_URL,
|
|
32
32
|
POLL_INTERVAL,
|
|
33
33
|
BRIDGE_GONE_EXIT_SEC,
|
|
34
|
+
StatusReporter,
|
|
34
35
|
fetch_display_state,
|
|
35
36
|
bridge_reachable,
|
|
36
37
|
resolve_display_brightness as _resolve_display_brightness_common,
|
|
@@ -182,6 +183,11 @@ async def push_micro_frame(client, url, brightness, gamma, sat, contrast, last_k
|
|
|
182
183
|
|
|
183
184
|
async def run(address: str, url: str, brightness: int, gamma: float, sat: float, contrast: float, once: bool = False) -> None:
|
|
184
185
|
print(f"Starting Timebox Mini BLE sync: {address} <- {url} brightness={brightness}% gamma={gamma}")
|
|
186
|
+
# Machine-readable link state for the daemon supervisor — it spawns us but
|
|
187
|
+
# cannot see the BLE link, and we reconnect inside this loop rather than
|
|
188
|
+
# exiting, so process liveness tells it nothing about whether the panel is
|
|
189
|
+
# actually being driven.
|
|
190
|
+
status = StatusReporter()
|
|
185
191
|
stop = asyncio.Event()
|
|
186
192
|
# Why we're stopping — decides the farewell. 'signal' = clean daemon shutdown
|
|
187
193
|
# (no successor → blank the panel). 'orphan' = parent died (a successor daemon
|
|
@@ -238,12 +244,15 @@ async def run(address: str, url: str, brightness: int, gamma: float, sat: float,
|
|
|
238
244
|
# the inner loop would spin forever on a dead link and the outer reconnect
|
|
239
245
|
# path (below) would never run.
|
|
240
246
|
print("BLE peripheral disconnected — will reconnect.", file=sys.stderr)
|
|
247
|
+
status.disconnected("BLE peripheral disconnected")
|
|
241
248
|
loop.call_soon_threadsafe(link_lost.set)
|
|
242
249
|
|
|
243
250
|
try:
|
|
244
251
|
print(f"Connecting BLE {address}...")
|
|
252
|
+
status.connecting()
|
|
245
253
|
async with BleakClient(address, timeout=15.0, disconnected_callback=on_disconnect) as client:
|
|
246
254
|
print(f"BLE connected (MTU={client.mtu_size})")
|
|
255
|
+
status.connected()
|
|
247
256
|
|
|
248
257
|
last_key = ""
|
|
249
258
|
last_sent_at = time.monotonic()
|
|
@@ -270,6 +279,7 @@ async def run(address: str, url: str, brightness: int, gamma: float, sat: float,
|
|
|
270
279
|
# on a dead link — only this flag / is_connected reveals it.
|
|
271
280
|
if link_lost.is_set() or not client.is_connected:
|
|
272
281
|
print("BLE link lost — reconnecting.", file=sys.stderr)
|
|
282
|
+
status.disconnected("BLE link lost")
|
|
273
283
|
break
|
|
274
284
|
# 1. Apply host display sleep/wake. The daemon exposes the same
|
|
275
285
|
# display_state Pixoo/iDotMatrix/ESP32 receive; older session-
|
|
@@ -289,6 +299,7 @@ async def run(address: str, url: str, brightness: int, gamma: float, sat: float,
|
|
|
289
299
|
# brightness on the transition (0 => blank sleep frame), then
|
|
290
300
|
# pause polling to save BLE bandwidth (mirrors iDotMatrix).
|
|
291
301
|
if display_dimmed:
|
|
302
|
+
status.streaming(dimmed=True)
|
|
292
303
|
if transitioned or once:
|
|
293
304
|
try:
|
|
294
305
|
last_key, sent = await push_micro_frame(
|
|
@@ -297,9 +308,11 @@ async def run(address: str, url: str, brightness: int, gamma: float, sat: float,
|
|
|
297
308
|
if sent:
|
|
298
309
|
last_sent_at = time.monotonic()
|
|
299
310
|
last_bridge_ok = time.monotonic()
|
|
311
|
+
status.frame_sent(dimmed=True)
|
|
300
312
|
except Exception as e:
|
|
301
313
|
print(f"Dim-frame send error: {e}", file=sys.stderr)
|
|
302
314
|
if link_lost.is_set() or not client.is_connected:
|
|
315
|
+
status.disconnected(f"dim-frame send error: {e}")
|
|
303
316
|
break
|
|
304
317
|
if once:
|
|
305
318
|
return
|
|
@@ -321,6 +334,7 @@ async def run(address: str, url: str, brightness: int, gamma: float, sat: float,
|
|
|
321
334
|
if sent:
|
|
322
335
|
last_sent_at = time.monotonic()
|
|
323
336
|
last_bridge_ok = time.monotonic()
|
|
337
|
+
status.frame_sent()
|
|
324
338
|
except Exception as e:
|
|
325
339
|
print(f"Frame fetch/send error: {e}", file=sys.stderr)
|
|
326
340
|
# A write error after the link dropped must escalate to a
|
|
@@ -328,6 +342,7 @@ async def run(address: str, url: str, brightness: int, gamma: float, sat: float,
|
|
|
328
342
|
# not — keep streaming and let should_exit() handle a truly
|
|
329
343
|
# gone bridge.
|
|
330
344
|
if link_lost.is_set() or not client.is_connected:
|
|
345
|
+
status.disconnected(f"frame send error: {e}")
|
|
331
346
|
break
|
|
332
347
|
if once:
|
|
333
348
|
return
|
|
@@ -364,6 +379,7 @@ async def run(address: str, url: str, brightness: int, gamma: float, sat: float,
|
|
|
364
379
|
print(f"Farewell blank failed: {e}", file=sys.stderr)
|
|
365
380
|
except Exception as e:
|
|
366
381
|
print(f"BLE connection error: {e}", file=sys.stderr)
|
|
382
|
+
status.failed(f"BLE connection error: {e}")
|
|
367
383
|
if once:
|
|
368
384
|
raise
|
|
369
385
|
try:
|