@agentdeck/bridge 1.0.6 → 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.
Files changed (115) hide show
  1. package/assets/fm-helper/agentdeck-fm-helper +0 -0
  2. package/dist/apme/collector.d.ts +10 -0
  3. package/dist/apme/collector.d.ts.map +1 -1
  4. package/dist/apme/collector.js +39 -10
  5. package/dist/apme/collector.js.map +1 -1
  6. package/dist/apme/dashboard-html.d.ts.map +1 -1
  7. package/dist/apme/dashboard-html.js +289 -0
  8. package/dist/apme/dashboard-html.js.map +1 -1
  9. package/dist/apme/graph.d.ts +48 -0
  10. package/dist/apme/graph.d.ts.map +1 -0
  11. package/dist/apme/graph.js +226 -0
  12. package/dist/apme/graph.js.map +1 -0
  13. package/dist/apme/http.d.ts.map +1 -1
  14. package/dist/apme/http.js +52 -0
  15. package/dist/apme/http.js.map +1 -1
  16. package/dist/apme/judge-detect.d.ts.map +1 -1
  17. package/dist/apme/judge-detect.js +26 -2
  18. package/dist/apme/judge-detect.js.map +1 -1
  19. package/dist/apme/store.d.ts +69 -1
  20. package/dist/apme/store.d.ts.map +1 -1
  21. package/dist/apme/store.js +238 -4
  22. package/dist/apme/store.js.map +1 -1
  23. package/dist/apme/types.d.ts +1 -1
  24. package/dist/apme/types.d.ts.map +1 -1
  25. package/dist/apme/types.js.map +1 -1
  26. package/dist/ble-sync-spawn.d.ts +25 -2
  27. package/dist/ble-sync-spawn.d.ts.map +1 -1
  28. package/dist/ble-sync-spawn.js +38 -2
  29. package/dist/ble-sync-spawn.js.map +1 -1
  30. package/dist/ble-sync-status.d.ts +77 -0
  31. package/dist/ble-sync-status.d.ts.map +1 -0
  32. package/dist/ble-sync-status.js +157 -0
  33. package/dist/ble-sync-status.js.map +1 -0
  34. package/dist/bridge-core.d.ts.map +1 -1
  35. package/dist/bridge-core.js +7 -1
  36. package/dist/bridge-core.js.map +1 -1
  37. package/dist/cli.js +0 -0
  38. package/dist/codex-rate-limits-live.d.ts +60 -0
  39. package/dist/codex-rate-limits-live.d.ts.map +1 -0
  40. package/dist/codex-rate-limits-live.js +282 -0
  41. package/dist/codex-rate-limits-live.js.map +1 -0
  42. package/dist/d200h/hid-protocol.d.ts +62 -0
  43. package/dist/d200h/hid-protocol.d.ts.map +1 -0
  44. package/dist/d200h/hid-protocol.js +124 -0
  45. package/dist/d200h/hid-protocol.js.map +1 -0
  46. package/dist/d200h/image-renderer.d.ts +24 -0
  47. package/dist/d200h/image-renderer.d.ts.map +1 -0
  48. package/dist/d200h/image-renderer.js +321 -0
  49. package/dist/d200h/image-renderer.js.map +1 -0
  50. package/dist/daemon-server.d.ts.map +1 -1
  51. package/dist/daemon-server.js +79 -0
  52. package/dist/daemon-server.js.map +1 -1
  53. package/dist/daemon.js +1 -1
  54. package/dist/hook-migration.d.ts +2 -0
  55. package/dist/hook-migration.d.ts.map +1 -0
  56. package/dist/hook-migration.js +116 -0
  57. package/dist/hook-migration.js.map +1 -0
  58. package/dist/idotmatrix/idotmatrix-daemon-sync.d.ts +8 -0
  59. package/dist/idotmatrix/idotmatrix-daemon-sync.d.ts.map +1 -1
  60. package/dist/idotmatrix/idotmatrix-daemon-sync.js +19 -3
  61. package/dist/idotmatrix/idotmatrix-daemon-sync.js.map +1 -1
  62. package/dist/modules/d200h-module.d.ts +65 -0
  63. package/dist/modules/d200h-module.d.ts.map +1 -0
  64. package/dist/modules/d200h-module.js +495 -0
  65. package/dist/modules/d200h-module.js.map +1 -0
  66. package/dist/modules/d200h-renderer.d.ts +7 -0
  67. package/dist/modules/d200h-renderer.d.ts.map +1 -0
  68. package/dist/modules/d200h-renderer.js +321 -0
  69. package/dist/modules/d200h-renderer.js.map +1 -0
  70. package/dist/modules/idotmatrix-module.d.ts.map +1 -1
  71. package/dist/modules/idotmatrix-module.js +12 -1
  72. package/dist/modules/idotmatrix-module.js.map +1 -1
  73. package/dist/modules/timebox-module.d.ts.map +1 -1
  74. package/dist/modules/timebox-module.js +12 -1
  75. package/dist/modules/timebox-module.js.map +1 -1
  76. package/dist/modules/trmnl-module.d.ts +38 -0
  77. package/dist/modules/trmnl-module.d.ts.map +1 -0
  78. package/dist/modules/trmnl-module.js +111 -0
  79. package/dist/modules/trmnl-module.js.map +1 -0
  80. package/dist/timebox/timebox-daemon-sync.d.ts +8 -0
  81. package/dist/timebox/timebox-daemon-sync.d.ts.map +1 -1
  82. package/dist/timebox/timebox-daemon-sync.js +29 -1
  83. package/dist/timebox/timebox-daemon-sync.js.map +1 -1
  84. package/dist/timeline-summarizer.d.ts +12 -2
  85. package/dist/timeline-summarizer.d.ts.map +1 -1
  86. package/dist/timeline-summarizer.js +66 -4
  87. package/dist/timeline-summarizer.js.map +1 -1
  88. package/dist/trmnl/byos-server.d.ts +39 -0
  89. package/dist/trmnl/byos-server.d.ts.map +1 -0
  90. package/dist/trmnl/byos-server.js +301 -0
  91. package/dist/trmnl/byos-server.js.map +1 -0
  92. package/dist/trmnl/frame-cache.d.ts +37 -0
  93. package/dist/trmnl/frame-cache.d.ts.map +1 -0
  94. package/dist/trmnl/frame-cache.js +157 -0
  95. package/dist/trmnl/frame-cache.js.map +1 -0
  96. package/dist/trmnl/image-renderer.d.ts +27 -0
  97. package/dist/trmnl/image-renderer.d.ts.map +1 -0
  98. package/dist/trmnl/image-renderer.js +235 -0
  99. package/dist/trmnl/image-renderer.js.map +1 -0
  100. package/dist/trmnl/trmnl-settings.d.ts +96 -0
  101. package/dist/trmnl/trmnl-settings.d.ts.map +1 -0
  102. package/dist/trmnl/trmnl-settings.js +177 -0
  103. package/dist/trmnl/trmnl-settings.js.map +1 -0
  104. package/dist/trmnl/trmnl-telemetry.d.ts +47 -0
  105. package/dist/trmnl/trmnl-telemetry.d.ts.map +1 -0
  106. package/dist/trmnl/trmnl-telemetry.js +56 -0
  107. package/dist/trmnl/trmnl-telemetry.js.map +1 -0
  108. package/dist/whisper-server-manager.d.ts +19 -0
  109. package/dist/whisper-server-manager.d.ts.map +1 -0
  110. package/dist/whisper-server-manager.js +171 -0
  111. package/dist/whisper-server-manager.js.map +1 -0
  112. package/package.json +13 -14
  113. package/src/idotmatrix/sync.py +16 -1
  114. package/src/pysync/matrix_sync_common.py +83 -0
  115. package/src/timebox/sync_ble.py +16 -0
@@ -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
@@ -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: