@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
@@ -0,0 +1,177 @@
1
+ /**
2
+ * TRMNL BYOS settings — reads/writes the `trmnl` block in
3
+ * ~/.agentdeck/settings.json, preserving all other keys (same pattern as
4
+ * pixoo-settings.ts).
5
+ *
6
+ * Shape:
7
+ * "trmnl": {
8
+ * "enabled": false, // force the module on even with no device yet
9
+ * "refreshRate": 180, // seconds between device polls
10
+ * "autoRegister": true, // auto-enroll a device that hits /api/setup
11
+ * "devices": [ { mac, apiKey, friendlyId, name? } ]
12
+ * }
13
+ */
14
+ import { readFileSync, writeFileSync, mkdirSync } from 'fs';
15
+ import { join } from 'path';
16
+ import { homedir } from 'os';
17
+ import { randomBytes } from 'crypto';
18
+ // Resolve lazily so tests (and explicit pinning) can redirect via
19
+ // AGENTDECK_DATA_DIR, matching apme/settings.ts and esp32-serial.ts.
20
+ function settingsDir() {
21
+ return process.env.AGENTDECK_DATA_DIR || join(homedir(), '.agentdeck');
22
+ }
23
+ function settingsPath() {
24
+ return join(settingsDir(), 'settings.json');
25
+ }
26
+ export const TRMNL_DEFAULT_REFRESH = 180;
27
+ /**
28
+ * Cadence used only while an agent is AWAITING the user. NOT used for "working":
29
+ * a battery e-ink panel deep-sleeps between polls and full-flashes the screen on
30
+ * every refresh, so we only tighten the loop when the user actually needs to act.
31
+ */
32
+ export const TRMNL_DEFAULT_REFRESH_ACTIVE = 60;
33
+ /** Floor on any cadence — too-frequent polls drain the panel battery + flash it. */
34
+ export const TRMNL_MIN_REFRESH = 30;
35
+ /**
36
+ * Seconds the firmware waits for the image download before giving up (its
37
+ * `image_url_timeout`). Generous so a slow/flaky WiFi link doesn't trip the
38
+ * device's "not responding" (WIFI_FAILED) screen. Firmware caps at ~65s, so we
39
+ * keep a healthy default (most "not responding" cycles are a lossy image GET
40
+ * timing out, not a server error — see docs/devices.md TRMNL section).
41
+ */
42
+ export const TRMNL_DEFAULT_IMAGE_TIMEOUT = 50;
43
+ /** Hard cap the firmware honors (it clamps `image_url_timeout` ~65s internally). */
44
+ export const TRMNL_MAX_IMAGE_TIMEOUT = 65;
45
+ /**
46
+ * RSSI (dBm) at or below which the panel's WiFi link is treated as weak/lossy.
47
+ * A weak link is the dominant real-world cause of "not responding" (WIFI_FAILED):
48
+ * the image download drops packets and times out. RSSI is sent by the firmware on
49
+ * every poll, so we can react per-poll. (-78 ≈ marginal; below it, retransmits
50
+ * stack up and the default window can be too tight.)
51
+ */
52
+ export const TRMNL_WEAK_RSSI_DBM = -78;
53
+ /** Image-download window served on a weak link — near the firmware cap. */
54
+ export const TRMNL_WEAK_LINK_IMAGE_TIMEOUT = 60;
55
+ /**
56
+ * Cadence for a poll. We only speed up for AWAITING (the user needs to act);
57
+ * "working" stays on the slow cadence because a deep-sleep e-ink panel can't be
58
+ * pushed and each wake full-flashes the screen + costs battery.
59
+ */
60
+ export function effectiveRefreshRate(cfg, activity) {
61
+ return Math.max(TRMNL_MIN_REFRESH, activity.awaiting > 0 ? cfg.refreshActive : cfg.refreshRate);
62
+ }
63
+ /**
64
+ * Image-download timeout (seconds) for a single poll. Widens toward the firmware
65
+ * cap when the panel reports a weak WiFi signal, so a lossy image GET still has
66
+ * the maximum window to finish before the firmware shows "not responding"
67
+ * (WIFI_FAILED) — the #1 source of TRMNL flicker on a marginal link. A strong link
68
+ * keeps the lower default so a genuinely dead link doesn't hold the radio on
69
+ * (battery). RSSI absent (older firmware) ⇒ default; the default itself is already
70
+ * generous. Clamped to [5, firmware cap].
71
+ */
72
+ export function effectiveImageTimeout(cfg, opts = {}) {
73
+ let t = cfg.imageUrlTimeout;
74
+ if (opts.rssi != null && Number.isFinite(opts.rssi) && opts.rssi <= TRMNL_WEAK_RSSI_DBM) {
75
+ t = Math.max(t, TRMNL_WEAK_LINK_IMAGE_TIMEOUT);
76
+ }
77
+ return Math.min(TRMNL_MAX_IMAGE_TIMEOUT, Math.max(5, Math.floor(t)));
78
+ }
79
+ function readSettings() {
80
+ try {
81
+ return JSON.parse(readFileSync(settingsPath(), 'utf-8'));
82
+ }
83
+ catch {
84
+ return {};
85
+ }
86
+ }
87
+ function writeSettings(settings) {
88
+ mkdirSync(settingsDir(), { recursive: true });
89
+ writeFileSync(settingsPath(), JSON.stringify(settings, null, 2) + '\n');
90
+ }
91
+ /** Load the trmnl config with defaults filled in. */
92
+ export function loadTrmnlConfig() {
93
+ const raw = (readSettings().trmnl ?? {});
94
+ const refreshRate = typeof raw.refreshRate === 'number' && raw.refreshRate >= 5 ? Math.floor(raw.refreshRate) : TRMNL_DEFAULT_REFRESH;
95
+ const refreshActive = typeof raw.refreshActive === 'number' && raw.refreshActive >= 5
96
+ ? Math.floor(raw.refreshActive)
97
+ : TRMNL_DEFAULT_REFRESH_ACTIVE;
98
+ const imageUrlTimeout = typeof raw.imageUrlTimeout === 'number' && raw.imageUrlTimeout > 0
99
+ ? Math.min(TRMNL_MAX_IMAGE_TIMEOUT, Math.floor(raw.imageUrlTimeout))
100
+ : TRMNL_DEFAULT_IMAGE_TIMEOUT;
101
+ return {
102
+ enabled: raw.enabled === true,
103
+ refreshRate,
104
+ refreshActive,
105
+ imageUrlTimeout,
106
+ autoRegister: raw.autoRegister !== false, // default on
107
+ devices: Array.isArray(raw.devices) ? raw.devices.filter((d) => d && typeof d.mac === 'string') : [],
108
+ };
109
+ }
110
+ function saveTrmnlConfig(cfg) {
111
+ const settings = readSettings();
112
+ settings.trmnl = cfg;
113
+ writeSettings(settings);
114
+ }
115
+ /**
116
+ * Normalize a MAC to a stable identity key. A canonical 12-hex address becomes
117
+ * uppercase colon-separated pairs; any other id (some firmware/cloud setups
118
+ * report non-standard or punctuated identifiers) collapses to its bare uppercase
119
+ * hex digits. Returning the raw, untrimmed string for the odd case would orphan
120
+ * or duplicate a device whose punctuation varies between polls — so we always
121
+ * derive a deterministic key from the hex content.
122
+ */
123
+ export function normalizeMac(mac) {
124
+ const hex = (mac || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase();
125
+ if (hex.length === 12)
126
+ return hex.match(/.{2}/g).join(':');
127
+ return hex || (mac || '').trim().toUpperCase();
128
+ }
129
+ /** True when two MAC spellings resolve to the same canonical identity. */
130
+ export function sameMac(a, b) {
131
+ return normalizeMac(a) === normalizeMac(b);
132
+ }
133
+ export function findDeviceByMac(mac) {
134
+ return loadTrmnlConfig().devices.find((d) => sameMac(d.mac, mac));
135
+ }
136
+ function genFriendlyId() {
137
+ // 6 chars from an unambiguous base32 alphabet (no 0/1/O/I).
138
+ const alphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
139
+ const bytes = randomBytes(6);
140
+ let out = '';
141
+ for (let i = 0; i < 6; i++)
142
+ out += alphabet[bytes[i] % alphabet.length];
143
+ return out;
144
+ }
145
+ /**
146
+ * Find or create a device record for the given MAC. Returns the device and
147
+ * whether it was newly created. Respects `autoRegister`: when off, a new MAC
148
+ * is NOT persisted and `created` is false with `device` undefined.
149
+ */
150
+ export function ensureDevice(mac, name) {
151
+ const cfg = loadTrmnlConfig();
152
+ const norm = normalizeMac(mac);
153
+ const existing = cfg.devices.find((d) => sameMac(d.mac, mac));
154
+ if (existing)
155
+ return { device: existing, created: false };
156
+ if (!cfg.autoRegister)
157
+ return { created: false };
158
+ const device = {
159
+ mac: norm,
160
+ apiKey: randomBytes(16).toString('hex'),
161
+ friendlyId: genFriendlyId(),
162
+ name,
163
+ };
164
+ cfg.devices.push(device);
165
+ saveTrmnlConfig(cfg);
166
+ return { device, created: true };
167
+ }
168
+ export function removeDevice(mac) {
169
+ const cfg = loadTrmnlConfig();
170
+ const filtered = cfg.devices.filter((d) => !sameMac(d.mac, mac));
171
+ if (filtered.length === cfg.devices.length)
172
+ return false;
173
+ cfg.devices = filtered;
174
+ saveTrmnlConfig(cfg);
175
+ return true;
176
+ }
177
+ //# sourceMappingURL=trmnl-settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trmnl-settings.js","sourceRoot":"","sources":["../../src/trmnl/trmnl-settings.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ export interface DeviceTelemetry {
2
+ /** Normalized MAC identity (matches TrmnlDevice.mac). */
3
+ mac: string;
4
+ fwVersion: string;
5
+ batteryVoltage: number | null;
6
+ rssi: number | null;
7
+ width: number | null;
8
+ height: number | null;
9
+ /** Cadence the device says it's polling at (we still dictate it on response). */
10
+ refreshRate: number | null;
11
+ userAgent: string;
12
+ /** Epoch ms of the most recent poll. */
13
+ lastSeen: number;
14
+ }
15
+ export interface TelemetryInput {
16
+ fwVersion?: string;
17
+ batteryVoltage?: number | null;
18
+ rssi?: number | null;
19
+ width?: number | null;
20
+ height?: number | null;
21
+ refreshRate?: number | null;
22
+ userAgent?: string;
23
+ }
24
+ /**
25
+ * Upsert the last-seen telemetry for a device. `now` is injected at the daemon
26
+ * boundary (defaults to wall clock) so layout/render code stays clock-free.
27
+ * Returns the previous `lastSeen` (epoch ms) so callers can detect poll gaps.
28
+ */
29
+ export declare function recordTelemetry(mac: string, t: TelemetryInput, now?: number): number | null;
30
+ /** Snapshot of all known device telemetry, most-recently-seen first. */
31
+ export declare function getTelemetry(): DeviceTelemetry[];
32
+ /** Telemetry plus derived freshness — `secondsSinceSeen` and a `stale` flag. */
33
+ export interface DeviceHealth extends DeviceTelemetry {
34
+ secondsSinceSeen: number;
35
+ /** True when the panel hasn't polled in over 2× its expected cadence. */
36
+ stale: boolean;
37
+ }
38
+ /**
39
+ * Telemetry annotated with health. A panel that hasn't polled within 2× its
40
+ * cadence is flagged `stale` so the dashboard can distinguish "panel stuck /
41
+ * offline" from "nothing changed". `now`/`refreshRate` are injected at the daemon
42
+ * boundary so this stays clock- and config-free.
43
+ */
44
+ export declare function getTelemetryHealth(refreshRate: number, now?: number): DeviceHealth[];
45
+ /** Test helper — clear the runtime map. */
46
+ export declare function _resetTelemetry(): void;
47
+ //# sourceMappingURL=trmnl-telemetry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trmnl-telemetry.d.ts","sourceRoot":"","sources":["../../src/trmnl/trmnl-telemetry.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,eAAe;IAC9B,yDAAyD;IACzD,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,iFAAiF;IACjF,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,wCAAwC;IACxC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAID;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,cAAc,EAAE,GAAG,GAAE,MAAmB,GAAG,MAAM,GAAG,IAAI,CAgBvG;AAED,wEAAwE;AACxE,wBAAgB,YAAY,IAAI,eAAe,EAAE,CAEhD;AAED,gFAAgF;AAChF,MAAM,WAAW,YAAa,SAAQ,eAAe;IACnD,gBAAgB,EAAE,MAAM,CAAC;IACzB,yEAAyE;IACzE,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,GAAE,MAAmB,GAAG,YAAY,EAAE,CAMhG;AAED,2CAA2C;AAC3C,wBAAgB,eAAe,IAAI,IAAI,CAEtC"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * TRMNL device telemetry — a runtime-only, per-MAC snapshot of the headers a
3
+ * BYOS panel reports on each /api/setup or /api/display poll (firmware version,
4
+ * battery voltage, WiFi signal, panel resolution). This is NOT persisted to
5
+ * settings.json: polls arrive every ~180s and writing the settings file on each
6
+ * one would thrash it and race the user's manual edits. The daemon status
7
+ * snapshot reads this map for diagnostics; it's lost on daemon restart, which is
8
+ * fine — the next poll repopulates it.
9
+ */
10
+ import { normalizeMac } from './trmnl-settings.js';
11
+ const telemetry = new Map();
12
+ /**
13
+ * Upsert the last-seen telemetry for a device. `now` is injected at the daemon
14
+ * boundary (defaults to wall clock) so layout/render code stays clock-free.
15
+ * Returns the previous `lastSeen` (epoch ms) so callers can detect poll gaps.
16
+ */
17
+ export function recordTelemetry(mac, t, now = Date.now()) {
18
+ const key = normalizeMac(mac);
19
+ if (!key)
20
+ return null;
21
+ const prevLastSeen = telemetry.get(key)?.lastSeen ?? null;
22
+ telemetry.set(key, {
23
+ mac: key,
24
+ fwVersion: t.fwVersion ?? '',
25
+ batteryVoltage: t.batteryVoltage ?? null,
26
+ rssi: t.rssi ?? null,
27
+ width: t.width ?? null,
28
+ height: t.height ?? null,
29
+ refreshRate: t.refreshRate ?? null,
30
+ userAgent: t.userAgent ?? '',
31
+ lastSeen: now,
32
+ });
33
+ return prevLastSeen;
34
+ }
35
+ /** Snapshot of all known device telemetry, most-recently-seen first. */
36
+ export function getTelemetry() {
37
+ return [...telemetry.values()].sort((a, b) => b.lastSeen - a.lastSeen);
38
+ }
39
+ /**
40
+ * Telemetry annotated with health. A panel that hasn't polled within 2× its
41
+ * cadence is flagged `stale` so the dashboard can distinguish "panel stuck /
42
+ * offline" from "nothing changed". `now`/`refreshRate` are injected at the daemon
43
+ * boundary so this stays clock- and config-free.
44
+ */
45
+ export function getTelemetryHealth(refreshRate, now = Date.now()) {
46
+ const staleAfterMs = Math.max(30, refreshRate) * 2 * 1000;
47
+ return getTelemetry().map((t) => {
48
+ const ageMs = now - t.lastSeen;
49
+ return { ...t, secondsSinceSeen: Math.round(ageMs / 1000), stale: ageMs > staleAfterMs };
50
+ });
51
+ }
52
+ /** Test helper — clear the runtime map. */
53
+ export function _resetTelemetry() {
54
+ telemetry.clear();
55
+ }
56
+ //# sourceMappingURL=trmnl-telemetry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trmnl-telemetry.js","sourceRoot":"","sources":["../../src/trmnl/trmnl-telemetry.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AA2BnD,MAAM,SAAS,GAAG,IAAI,GAAG,EAA2B,CAAC;AAErD;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW,EAAE,CAAiB,EAAE,MAAc,IAAI,CAAC,GAAG,EAAE;IACtF,MAAM,GAAG,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,MAAM,YAAY,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,QAAQ,IAAI,IAAI,CAAC;IAC1D,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE;QACjB,GAAG,EAAE,GAAG;QACR,SAAS,EAAE,CAAC,CAAC,SAAS,IAAI,EAAE;QAC5B,cAAc,EAAE,CAAC,CAAC,cAAc,IAAI,IAAI;QACxC,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,IAAI;QACpB,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,IAAI;QACtB,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,IAAI;QACxB,WAAW,EAAE,CAAC,CAAC,WAAW,IAAI,IAAI;QAClC,SAAS,EAAE,CAAC,CAAC,SAAS,IAAI,EAAE;QAC5B,QAAQ,EAAE,GAAG;KACd,CAAC,CAAC;IACH,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,YAAY;IAC1B,OAAO,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC;AACzE,CAAC;AASD;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,WAAmB,EAAE,MAAc,IAAI,CAAC,GAAG,EAAE;IAC9E,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,WAAW,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;IAC1D,OAAO,YAAY,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAC9B,MAAM,KAAK,GAAG,GAAG,GAAG,CAAC,CAAC,QAAQ,CAAC;QAC/B,OAAO,EAAE,GAAG,CAAC,EAAE,gBAAgB,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,GAAG,YAAY,EAAE,CAAC;IAC3F,CAAC,CAAC,CAAC;AACL,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,eAAe;IAC7B,SAAS,CAAC,KAAK,EAAE,CAAC;AACpB,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Whisper-server singleton manager.
3
+ *
4
+ * Fixed port 9100 — one server shared across all agentdeck sessions.
5
+ * Info file at ~/.agentdeck/whisper-server.json for discovery.
6
+ * Last session to exit kills the server.
7
+ */
8
+ export declare const WHISPER_SERVER_PORT = 9100;
9
+ /**
10
+ * Ensure a whisper-server is running on the singleton port.
11
+ * Returns port on success, null if unavailable (binary missing, model missing, etc).
12
+ */
13
+ export declare function ensureWhisperServer(serverBin: string, modelPath: string): Promise<number | null>;
14
+ /**
15
+ * Release the whisper-server if no other sessions remain.
16
+ * Call AFTER deregisterSession() so the count is already decremented.
17
+ */
18
+ export declare function releaseWhisperServer(): void;
19
+ //# sourceMappingURL=whisper-server-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whisper-server-manager.d.ts","sourceRoot":"","sources":["../src/whisper-server-manager.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAaH,eAAO,MAAM,mBAAmB,OAAO,CAAC;AA2DxC;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA6ExB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAiB3C"}
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Whisper-server singleton manager.
3
+ *
4
+ * Fixed port 9100 — one server shared across all agentdeck sessions.
5
+ * Info file at ~/.agentdeck/whisper-server.json for discovery.
6
+ * Last session to exit kills the server.
7
+ */
8
+ import { spawn } from 'child_process';
9
+ import { readFileSync, writeFileSync, renameSync, unlinkSync, mkdirSync, existsSync, } from 'fs';
10
+ import { join } from 'path';
11
+ import { homedir } from 'os';
12
+ import { randomUUID } from 'crypto';
13
+ import { listActive } from './session-registry.js';
14
+ import { debug } from './logger.js';
15
+ export const WHISPER_SERVER_PORT = 9100;
16
+ const INFO_DIR = join(homedir(), '.agentdeck');
17
+ const INFO_FILE = join(INFO_DIR, 'whisper-server.json');
18
+ function ensureDir() {
19
+ if (!existsSync(INFO_DIR)) {
20
+ mkdirSync(INFO_DIR, { recursive: true });
21
+ }
22
+ }
23
+ function readInfo() {
24
+ try {
25
+ return JSON.parse(readFileSync(INFO_FILE, 'utf-8'));
26
+ }
27
+ catch {
28
+ return null;
29
+ }
30
+ }
31
+ function writeInfo(info) {
32
+ ensureDir();
33
+ const tmp = join(INFO_DIR, `.whisper-server.${randomUUID()}.tmp`);
34
+ writeFileSync(tmp, JSON.stringify(info, null, 2), 'utf-8');
35
+ renameSync(tmp, INFO_FILE);
36
+ }
37
+ function removeInfo() {
38
+ try {
39
+ unlinkSync(INFO_FILE);
40
+ }
41
+ catch { /* already gone */ }
42
+ }
43
+ function isProcessAlive(pid) {
44
+ try {
45
+ process.kill(pid, 0);
46
+ return true;
47
+ }
48
+ catch {
49
+ return false;
50
+ }
51
+ }
52
+ async function isServerResponding(port) {
53
+ try {
54
+ await fetch(`http://127.0.0.1:${port}`, { signal: AbortSignal.timeout(1000) });
55
+ return true;
56
+ }
57
+ catch {
58
+ return false;
59
+ }
60
+ }
61
+ async function pollUntilReady(port, maxMs) {
62
+ const start = Date.now();
63
+ while (Date.now() - start < maxMs) {
64
+ if (await isServerResponding(port))
65
+ return true;
66
+ await new Promise((r) => setTimeout(r, 500));
67
+ }
68
+ return false;
69
+ }
70
+ /**
71
+ * Ensure a whisper-server is running on the singleton port.
72
+ * Returns port on success, null if unavailable (binary missing, model missing, etc).
73
+ */
74
+ export async function ensureWhisperServer(serverBin, modelPath) {
75
+ if (!existsSync(serverBin)) {
76
+ debug('WhisperMgr', `whisper-server not found at ${serverBin}, skipping`);
77
+ return null;
78
+ }
79
+ // 1. Check existing info file
80
+ const info = readInfo();
81
+ if (info) {
82
+ if (isProcessAlive(info.pid)) {
83
+ // PID alive — check if server is responding
84
+ if (await isServerResponding(info.port)) {
85
+ debug('WhisperMgr', `Reusing existing server (pid=${info.pid}, port=${info.port})`);
86
+ return info.port;
87
+ }
88
+ // PID alive but not responding — model may still be loading, poll 30s
89
+ debug('WhisperMgr', `Server pid=${info.pid} alive but not responding, waiting for model load...`);
90
+ if (await pollUntilReady(info.port, 30_000)) {
91
+ debug('WhisperMgr', `Server became ready (pid=${info.pid}, port=${info.port})`);
92
+ return info.port;
93
+ }
94
+ // Still not responding after 30s — stale
95
+ debug('WhisperMgr', `Server pid=${info.pid} never responded, treating as stale`);
96
+ }
97
+ // PID dead or stale — clean up
98
+ removeInfo();
99
+ }
100
+ // 2. Spawn new server (detached so it survives parent exit)
101
+ debug('WhisperMgr', `Starting whisper-server on port ${WHISPER_SERVER_PORT}...`);
102
+ const args = [
103
+ '--model', modelPath,
104
+ '--port', String(WHISPER_SERVER_PORT),
105
+ '--host', '127.0.0.1',
106
+ '-l', 'auto',
107
+ '--no-timestamps',
108
+ '--convert',
109
+ '--prompt', 'coding, programming, Claude, terminal, git, function, component, API',
110
+ ];
111
+ const proc = spawn(serverBin, args, {
112
+ stdio: 'ignore',
113
+ detached: true,
114
+ });
115
+ proc.unref();
116
+ const pid = proc.pid;
117
+ if (!pid) {
118
+ debug('WhisperMgr', 'Failed to spawn whisper-server (no pid)');
119
+ return null;
120
+ }
121
+ // 3. Brief wait to detect immediate crash (e.g. port conflict)
122
+ await new Promise((r) => setTimeout(r, 200));
123
+ if (!isProcessAlive(pid)) {
124
+ debug('WhisperMgr', `whisper-server died immediately (pid=${pid}), checking for existing server...`);
125
+ // Another process may have won the port race — re-check info file
126
+ const otherInfo = readInfo();
127
+ if (otherInfo && isProcessAlive(otherInfo.pid) && await isServerResponding(otherInfo.port)) {
128
+ debug('WhisperMgr', `Found winner server (pid=${otherInfo.pid}, port=${otherInfo.port})`);
129
+ return otherInfo.port;
130
+ }
131
+ removeInfo();
132
+ return null;
133
+ }
134
+ // 4. Write info file
135
+ writeInfo({ pid, port: WHISPER_SERVER_PORT, modelPath, startedAt: new Date().toISOString() });
136
+ // 5. Poll until ready (up to 30s for model loading)
137
+ if (await pollUntilReady(WHISPER_SERVER_PORT, 30_000)) {
138
+ debug('WhisperMgr', `whisper-server ready on port ${WHISPER_SERVER_PORT} (pid=${pid})`);
139
+ return WHISPER_SERVER_PORT;
140
+ }
141
+ debug('WhisperMgr', 'whisper-server failed to become ready within 30s');
142
+ return null;
143
+ }
144
+ /**
145
+ * Release the whisper-server if no other sessions remain.
146
+ * Call AFTER deregisterSession() so the count is already decremented.
147
+ */
148
+ export function releaseWhisperServer() {
149
+ const activeSessions = listActive();
150
+ if (activeSessions.length > 0) {
151
+ debug('WhisperMgr', `Keeping whisper-server alive (${activeSessions.length} session(s) remain)`);
152
+ return;
153
+ }
154
+ const info = readInfo();
155
+ if (!info)
156
+ return;
157
+ debug('WhisperMgr', `Killing whisper-server (pid=${info.pid})`);
158
+ try {
159
+ process.kill(info.pid, 'SIGTERM');
160
+ }
161
+ catch { /* already dead */ }
162
+ // Force kill after 3s
163
+ setTimeout(() => {
164
+ try {
165
+ process.kill(info.pid, 'SIGKILL');
166
+ }
167
+ catch { /* already dead */ }
168
+ }, 3000);
169
+ removeInfo();
170
+ }
171
+ //# sourceMappingURL=whisper-server-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whisper-server-manager.js","sourceRoot":"","sources":["../src/whisper-server-manager.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.6",
3
+ "version": "1.0.7",
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/hooks": "1.0.7",
37
+ "@agentdeck/shared": "1.0.7"
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
+ }
@@ -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()