@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.
- 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 +79 -0
- package/dist/daemon-server.js.map +1 -1
- package/dist/daemon.js +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/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/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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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TRMNL BYOS (Bring Your Own Server) HTTP handlers, mounted on the daemon hub.
|
|
3
|
+
*
|
|
4
|
+
* Implements the minimal TRMNL device contract so a stock-firmware panel pointed
|
|
5
|
+
* at `http://<daemon-host>:9120` renders the AgentDeck dashboard:
|
|
6
|
+
*
|
|
7
|
+
* GET /api/setup ID:<mac> → { api_key, friendly_id, image_url }
|
|
8
|
+
* GET /api/display ID:<mac> Access-Token:<key> → { image_url, filename, refresh_rate, ... }
|
|
9
|
+
* GET /trmnl/image/<W>x<H>-<hash>.png → 1-bit PNG frame (per resolution)
|
|
10
|
+
* POST /api/log ID:<mac> → 204 (device logs, debug only)
|
|
11
|
+
*
|
|
12
|
+
* The device authenticates only by MAC (ID header); the api_key issued at setup
|
|
13
|
+
* is a soft gate on /api/display. This is LAN-local hardware, so no token is
|
|
14
|
+
* required to fetch the image itself. Any panel auto-enrolls regardless of MAC,
|
|
15
|
+
* and the dashboard is rendered at the device-reported Width/Height so different
|
|
16
|
+
* BYOS panels render correctly. The device's FW-Version/RSSI/Battery/etc. headers
|
|
17
|
+
* are captured as runtime telemetry. See docs: https://docs.trmnl.com/go/diy/byos
|
|
18
|
+
*/
|
|
19
|
+
import type { IncomingMessage, ServerResponse } from 'http';
|
|
20
|
+
/** Test helper — clear weak-RSSI transition tracking. */
|
|
21
|
+
export declare function _resetWeakRssiTracking(): void;
|
|
22
|
+
/** GET /api/setup — enroll the device (by MAC) and hand back an api_key. */
|
|
23
|
+
export declare function handleTrmnlSetup(req: IncomingMessage, res: ServerResponse): void;
|
|
24
|
+
/**
|
|
25
|
+
* GET /api/display — return the next image + polling cadence.
|
|
26
|
+
*
|
|
27
|
+
* NOTE: we always reply with HTTP 200; the BYOS `status` field carries the real
|
|
28
|
+
* signal (0 = show image, 202 = call /api/setup). Stock firmware treats a non-200
|
|
29
|
+
* HTTP status as an error and goes back to sleep without rendering, so HTTP 200 is
|
|
30
|
+
* required even in the "needs setup" case.
|
|
31
|
+
*/
|
|
32
|
+
export declare function handleTrmnlDisplay(req: IncomingMessage, res: ServerResponse): void;
|
|
33
|
+
/** GET /trmnl/image/<W>x<H>-<hash>.png — serve the 1-bit PNG for that resolution. */
|
|
34
|
+
export declare function handleTrmnlImage(req: IncomingMessage, res: ServerResponse): void;
|
|
35
|
+
/** POST /api/log — the panel's own error report; always logged (rate-limited). */
|
|
36
|
+
export declare function handleTrmnlLog(req: IncomingMessage, res: ServerResponse): void;
|
|
37
|
+
/** True for any path this module owns, so the daemon router can delegate. */
|
|
38
|
+
export declare function isTrmnlImagePath(pathname: string): boolean;
|
|
39
|
+
//# sourceMappingURL=byos-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"byos-server.d.ts","sourceRoot":"","sources":["../../src/trmnl/byos-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AA+C5D,yDAAyD;AACzD,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C;AAkGD,4EAA4E;AAC5E,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,GAAG,IAAI,CA8BhF;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,GAAG,IAAI,CA4ElF;AAED,qFAAqF;AACrF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,GAAG,IAAI,CAoBhF;AA0BD,kFAAkF;AAClF,wBAAgB,cAAc,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,GAAG,IAAI,CA0B9E;AAED,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE1D"}
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { TRMNL_WIDTH, TRMNL_HEIGHT } from '@agentdeck/shared';
|
|
2
|
+
import { ensureDevice, findDeviceByMac, loadTrmnlConfig, normalizeMac, effectiveRefreshRate, effectiveImageTimeout, TRMNL_WEAK_RSSI_DBM, } from './trmnl-settings.js';
|
|
3
|
+
import { getTrmnlFrame, getTrmnlFrameByKey, forceRenderTrmnlFrame, getTrmnlActivity } from './frame-cache.js';
|
|
4
|
+
import { recordTelemetry } from './trmnl-telemetry.js';
|
|
5
|
+
import { debug, logTagged } from '../logger.js';
|
|
6
|
+
const TAG = 'trmnl-byos';
|
|
7
|
+
/** MACs currently flagged weak-signal — tracked so we log a transition, not every poll. */
|
|
8
|
+
const weakRssiMacs = new Set();
|
|
9
|
+
/**
|
|
10
|
+
* Log once when a panel's RSSI crosses into/out of the weak-link threshold. A
|
|
11
|
+
* weak/lossy WiFi link is the dominant real-world cause of the firmware's
|
|
12
|
+
* "WiFi connected / not responding" (WIFI_FAILED) screen, so this gives an
|
|
13
|
+
* operator a correlatable record — always visible (not gated behind --debug),
|
|
14
|
+
* but rate-limited to state transitions instead of spamming every poll.
|
|
15
|
+
*/
|
|
16
|
+
function noteRssiHealth(mac, rssi) {
|
|
17
|
+
const key = normalizeMac(mac);
|
|
18
|
+
if (!key)
|
|
19
|
+
return;
|
|
20
|
+
const isWeak = rssi != null && Number.isFinite(rssi) && rssi <= TRMNL_WEAK_RSSI_DBM;
|
|
21
|
+
const wasWeak = weakRssiMacs.has(key);
|
|
22
|
+
if (isWeak && !wasWeak) {
|
|
23
|
+
weakRssiMacs.add(key);
|
|
24
|
+
logTagged(TAG, `weak WiFi signal on ${key}: rssi=${rssi}dBm (<= ${TRMNL_WEAK_RSSI_DBM}dBm threshold) — ` +
|
|
25
|
+
`image_url_timeout widened; panel may show "not responding" if this persists`);
|
|
26
|
+
}
|
|
27
|
+
else if (!isWeak && wasWeak) {
|
|
28
|
+
weakRssiMacs.delete(key);
|
|
29
|
+
logTagged(TAG, `WiFi signal recovered on ${key}: rssi=${rssi ?? 'n/a'}dBm`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** Test helper — clear weak-RSSI transition tracking. */
|
|
33
|
+
export function _resetWeakRssiTracking() {
|
|
34
|
+
weakRssiMacs.clear();
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Record a poll and log when the panel comes back after missing its window.
|
|
38
|
+
* A battery e-ink panel polls on the cadence we hand it; a gap of more than
|
|
39
|
+
* 2× the slow cadence (+ grace) means at least one poll round-trip failed at
|
|
40
|
+
* the network layer — exactly the moment the firmware flashes its "TRMNL not
|
|
41
|
+
* responding" screen. Logging the gap on recovery (always visible, one line
|
|
42
|
+
* per incident) gives the operator a correlatable record of every dead window.
|
|
43
|
+
*/
|
|
44
|
+
function notePollGap(mac, h, cfg) {
|
|
45
|
+
const now = Date.now();
|
|
46
|
+
const prevLastSeen = recordTelemetry(mac, h, now);
|
|
47
|
+
if (prevLastSeen == null)
|
|
48
|
+
return;
|
|
49
|
+
const gapSec = Math.round((now - prevLastSeen) / 1000);
|
|
50
|
+
const expected = Math.max(cfg.refreshRate, cfg.refreshActive);
|
|
51
|
+
if (gapSec > expected * 2 + 30) {
|
|
52
|
+
logTagged(TAG, `panel ${normalizeMac(mac)} back after ${gapSec}s silence (cadence ${expected}s) — ` +
|
|
53
|
+
`it missed ≥1 poll window; likely showed "not responding" in between ` +
|
|
54
|
+
`(rssi=${h.rssi ?? 'n/a'}dBm battery=${h.batteryVoltage ?? 'n/a'}V)`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Sane device-reported resolution bounds; anything outside falls back to OG size.
|
|
58
|
+
const MIN_DIM = 120;
|
|
59
|
+
const MAX_DIM = 4000;
|
|
60
|
+
function header(req, name) {
|
|
61
|
+
const v = req.headers[name.toLowerCase()];
|
|
62
|
+
return Array.isArray(v) ? (v[0] ?? '') : (v ?? '');
|
|
63
|
+
}
|
|
64
|
+
function toNum(v) {
|
|
65
|
+
if (!v)
|
|
66
|
+
return null;
|
|
67
|
+
const n = Number(v.trim());
|
|
68
|
+
return Number.isFinite(n) ? n : null;
|
|
69
|
+
}
|
|
70
|
+
/** Parse the BYOS telemetry headers a panel sends on /api/setup + /api/display. */
|
|
71
|
+
function parseDeviceHeaders(req) {
|
|
72
|
+
const sane = (n) => (n != null && n >= MIN_DIM && n <= MAX_DIM ? Math.round(n) : null);
|
|
73
|
+
return {
|
|
74
|
+
mac: header(req, 'ID'),
|
|
75
|
+
accessToken: header(req, 'Access-Token'),
|
|
76
|
+
fwVersion: header(req, 'FW-Version'),
|
|
77
|
+
batteryVoltage: toNum(header(req, 'Battery-Voltage')),
|
|
78
|
+
rssi: toNum(header(req, 'RSSI')),
|
|
79
|
+
refreshRate: toNum(header(req, 'Refresh-Rate')),
|
|
80
|
+
width: sane(toNum(header(req, 'Width'))),
|
|
81
|
+
height: sane(toNum(header(req, 'Height'))),
|
|
82
|
+
userAgent: header(req, 'User-Agent'),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/** Render size for a device: its reported resolution, or the OG default. */
|
|
86
|
+
function renderSize(h) {
|
|
87
|
+
return { width: h.width ?? TRMNL_WIDTH, height: h.height ?? TRMNL_HEIGHT };
|
|
88
|
+
}
|
|
89
|
+
/** Build the image URL, qualified by resolution so the image route serves the
|
|
90
|
+
* right-sized frame. The hash is the cache-buster the firmware re-downloads on. */
|
|
91
|
+
function imageUrl(req, size, hash) {
|
|
92
|
+
return `${imageBase(req)}/trmnl/image/${size.width}x${size.height}-${hash}.png`;
|
|
93
|
+
}
|
|
94
|
+
function imageBase(req) {
|
|
95
|
+
// The device reached us at exactly this host:port — reuse it so the image_url
|
|
96
|
+
// is always resolvable from the device's perspective (no IP guessing).
|
|
97
|
+
return `http://${req.headers.host ?? 'localhost'}`;
|
|
98
|
+
}
|
|
99
|
+
function sendJson(res, status, body) {
|
|
100
|
+
const payload = JSON.stringify(body);
|
|
101
|
+
res.writeHead(status, {
|
|
102
|
+
'Content-Type': 'application/json',
|
|
103
|
+
'Cache-Control': 'no-store',
|
|
104
|
+
'Access-Control-Allow-Origin': '*',
|
|
105
|
+
});
|
|
106
|
+
res.end(payload);
|
|
107
|
+
}
|
|
108
|
+
/** GET /api/setup — enroll the device (by MAC) and hand back an api_key. */
|
|
109
|
+
export function handleTrmnlSetup(req, res) {
|
|
110
|
+
const h = parseDeviceHeaders(req);
|
|
111
|
+
const mac = h.mac;
|
|
112
|
+
if (!mac) {
|
|
113
|
+
sendJson(res, 400, { status: 400, message: 'Missing ID (MAC) header' });
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const { device, created } = ensureDevice(mac);
|
|
117
|
+
if (!device) {
|
|
118
|
+
// autoRegister is off and this MAC isn't enrolled.
|
|
119
|
+
debug(TAG, `setup rejected for ${normalizeMac(mac)} (autoRegister off, not enrolled)`);
|
|
120
|
+
sendJson(res, 404, { status: 404, message: 'Device not enrolled. Add it to settings.trmnl.devices.' });
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
notePollGap(mac, h, loadTrmnlConfig());
|
|
124
|
+
noteRssiHealth(mac, h.rssi);
|
|
125
|
+
const size = renderSize(h);
|
|
126
|
+
if (created) {
|
|
127
|
+
debug(TAG, `enrolled TRMNL ${device.mac} as ${device.friendlyId} (${size.width}x${size.height})`);
|
|
128
|
+
forceRenderTrmnlFrame(size.width, size.height); // first served image reflects live state
|
|
129
|
+
}
|
|
130
|
+
const frame = getTrmnlFrame(size.width, size.height);
|
|
131
|
+
sendJson(res, 200, {
|
|
132
|
+
status: 200,
|
|
133
|
+
api_key: device.apiKey,
|
|
134
|
+
friendly_id: device.friendlyId,
|
|
135
|
+
image_url: imageUrl(req, size, frame.contentHash),
|
|
136
|
+
filename: frame.contentHash,
|
|
137
|
+
message: 'Welcome to AgentDeck',
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* GET /api/display — return the next image + polling cadence.
|
|
142
|
+
*
|
|
143
|
+
* NOTE: we always reply with HTTP 200; the BYOS `status` field carries the real
|
|
144
|
+
* signal (0 = show image, 202 = call /api/setup). Stock firmware treats a non-200
|
|
145
|
+
* HTTP status as an error and goes back to sleep without rendering, so HTTP 200 is
|
|
146
|
+
* required even in the "needs setup" case.
|
|
147
|
+
*/
|
|
148
|
+
export function handleTrmnlDisplay(req, res) {
|
|
149
|
+
const h = parseDeviceHeaders(req);
|
|
150
|
+
const mac = h.mac;
|
|
151
|
+
const cfg = loadTrmnlConfig();
|
|
152
|
+
const size = renderSize(h);
|
|
153
|
+
if (mac) {
|
|
154
|
+
notePollGap(mac, h, cfg);
|
|
155
|
+
noteRssiHealth(mac, h.rssi);
|
|
156
|
+
}
|
|
157
|
+
let device = mac ? findDeviceByMac(mac) : undefined;
|
|
158
|
+
if (!device) {
|
|
159
|
+
// Real devices often carry an api_key from a prior (cloud) setup and never
|
|
160
|
+
// call /api/setup again — they poll /api/display straight away. If we answer
|
|
161
|
+
// "not registered" (status 202) they just sleep/retry forever and never show
|
|
162
|
+
// a screen. So auto-enroll on first poll (when autoRegister is on) and serve
|
|
163
|
+
// a real dashboard (status 0). Only fall back to 202 when autoRegister is off.
|
|
164
|
+
if (mac && cfg.autoRegister) {
|
|
165
|
+
const r = ensureDevice(mac);
|
|
166
|
+
device = r.device;
|
|
167
|
+
if (r.created) {
|
|
168
|
+
debug(TAG, `auto-enrolled ${normalizeMac(mac)} on display poll (${size.width}x${size.height})`);
|
|
169
|
+
forceRenderTrmnlFrame(size.width, size.height);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (!device) {
|
|
173
|
+
// autoRegister off and unenrolled: serve a real, correctly-sized frame (the
|
|
174
|
+
// idle hero / dashboard) rather than a bogus `setup.png` the image route
|
|
175
|
+
// can't match. status 202 still tells the firmware "not fully set up".
|
|
176
|
+
const setupFrame = getTrmnlFrame(size.width, size.height);
|
|
177
|
+
debug(TAG, `display from unenrolled ${normalizeMac(mac)} (autoRegister off) — requesting setup`);
|
|
178
|
+
sendJson(res, 200, {
|
|
179
|
+
status: 202,
|
|
180
|
+
image_url: imageUrl(req, size, setupFrame.contentHash),
|
|
181
|
+
filename: setupFrame.contentHash,
|
|
182
|
+
refresh_rate: cfg.refreshRate,
|
|
183
|
+
image_url_timeout: effectiveImageTimeout(cfg, { rssi: h.rssi }),
|
|
184
|
+
special_function: 'sleep',
|
|
185
|
+
reset_firmware: false,
|
|
186
|
+
update_firmware: false,
|
|
187
|
+
firmware_url: null,
|
|
188
|
+
});
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Soft auth only: the MAC (ID header) identifies the device. The api_key it
|
|
193
|
+
// presents may have been issued by a previous/cloud server, so we don't
|
|
194
|
+
// hard-reject on mismatch — this is same-LAN hardware.
|
|
195
|
+
const frame = getTrmnlFrame(size.width, size.height);
|
|
196
|
+
// Adaptive cadence: poll fast while an agent is AWAITING/WORKING, slow when idle.
|
|
197
|
+
const activity = getTrmnlActivity();
|
|
198
|
+
const refreshRate = effectiveRefreshRate(cfg, activity);
|
|
199
|
+
// Widen the image-download window on a weak link so a lossy GET still finishes
|
|
200
|
+
// before the firmware shows "not responding" (WIFI_FAILED).
|
|
201
|
+
const imageTimeout = effectiveImageTimeout(cfg, { rssi: h.rssi });
|
|
202
|
+
debug(TAG, `display ${normalizeMac(mac)} ${size.width}x${size.height} hash=${frame.contentHash} ` +
|
|
203
|
+
`refresh=${refreshRate}s imageTimeout=${imageTimeout}s rssi=${h.rssi ?? 'n/a'} ` +
|
|
204
|
+
`awaiting=${activity.awaiting} working=${activity.working}`);
|
|
205
|
+
sendJson(res, 200, {
|
|
206
|
+
status: 0,
|
|
207
|
+
image_url: imageUrl(req, size, frame.contentHash),
|
|
208
|
+
filename: frame.contentHash,
|
|
209
|
+
// Number (matches the reference BYOS + firmware's uint parse), not a string.
|
|
210
|
+
refresh_rate: refreshRate,
|
|
211
|
+
// Generous, link-aware image-download window so a flaky WiFi link doesn't trip
|
|
212
|
+
// the firmware's "not responding" screen.
|
|
213
|
+
image_url_timeout: imageTimeout,
|
|
214
|
+
special_function: 'sleep',
|
|
215
|
+
reset_firmware: false,
|
|
216
|
+
update_firmware: false,
|
|
217
|
+
firmware_url: null,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
/** GET /trmnl/image/<W>x<H>-<hash>.png — serve the 1-bit PNG for that resolution. */
|
|
221
|
+
export function handleTrmnlImage(req, res) {
|
|
222
|
+
// The URL carries the resolution (and a cache-busting hash) so we serve the
|
|
223
|
+
// right-sized frame. We ignore the hash itself — the device always wants the
|
|
224
|
+
// freshest screen for its resolution. Unknown/legacy URLs fall back to the
|
|
225
|
+
// default-size frame.
|
|
226
|
+
const pathname = (req.url || '').split('?')[0];
|
|
227
|
+
const m = /\/trmnl\/image\/(\d+)x(\d+)-[0-9a-f]+\.png$/.exec(pathname);
|
|
228
|
+
let frame;
|
|
229
|
+
if (m) {
|
|
230
|
+
const key = `${m[1]}x${m[2]}`;
|
|
231
|
+
frame = getTrmnlFrameByKey(key) ?? getTrmnlFrame(Number(m[1]), Number(m[2]));
|
|
232
|
+
}
|
|
233
|
+
if (!frame)
|
|
234
|
+
frame = getTrmnlFrame();
|
|
235
|
+
res.writeHead(200, {
|
|
236
|
+
'Content-Type': frame.contentType,
|
|
237
|
+
'Content-Length': frame.buffer.length,
|
|
238
|
+
'Cache-Control': 'no-store',
|
|
239
|
+
'Access-Control-Allow-Origin': '*',
|
|
240
|
+
});
|
|
241
|
+
res.end(frame.buffer);
|
|
242
|
+
}
|
|
243
|
+
// Device-log throttle: the firmware only POSTs /api/log when something went
|
|
244
|
+
// wrong on its side (failed poll, image timeout, low battery), so these lines
|
|
245
|
+
// ARE the root-cause record for "not responding" incidents. Always log them,
|
|
246
|
+
// but cap the rate per panel so a wedged device can't flood stderr.
|
|
247
|
+
const DEVICE_LOG_INTERVAL_MS = 10_000;
|
|
248
|
+
const lastDeviceLogAt = new Map();
|
|
249
|
+
/** Compress the firmware's log JSON into one greppable line (best effort). */
|
|
250
|
+
function summarizeDeviceLog(body) {
|
|
251
|
+
try {
|
|
252
|
+
const parsed = JSON.parse(body);
|
|
253
|
+
const arr = parsed?.log?.logs_array ?? parsed?.logs?.logs_array ?? parsed?.logs_array;
|
|
254
|
+
if (Array.isArray(arr) && arr.length > 0) {
|
|
255
|
+
return arr
|
|
256
|
+
.map((e) => `[${e?.log_codeline ?? e?.log_sourcefile ?? '?'}] ${e?.log_message ?? JSON.stringify(e)}`)
|
|
257
|
+
.join(' | ')
|
|
258
|
+
.slice(0, 600);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
/* not JSON — fall through to raw */
|
|
263
|
+
}
|
|
264
|
+
return body.replace(/\s+/g, ' ').slice(0, 600);
|
|
265
|
+
}
|
|
266
|
+
/** POST /api/log — the panel's own error report; always logged (rate-limited). */
|
|
267
|
+
export function handleTrmnlLog(req, res) {
|
|
268
|
+
let body = '';
|
|
269
|
+
req.on('data', (c) => {
|
|
270
|
+
body += c;
|
|
271
|
+
if (body.length > 64_000)
|
|
272
|
+
req.destroy();
|
|
273
|
+
});
|
|
274
|
+
req.on('end', () => {
|
|
275
|
+
const mac = normalizeMac(header(req, 'ID'));
|
|
276
|
+
const now = Date.now();
|
|
277
|
+
if (now - (lastDeviceLogAt.get(mac) ?? 0) >= DEVICE_LOG_INTERVAL_MS) {
|
|
278
|
+
lastDeviceLogAt.set(mac, now);
|
|
279
|
+
logTagged(TAG, `device log from ${mac}: ${summarizeDeviceLog(body)}`);
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
debug(TAG, `device log from ${mac} (throttled): ${body.slice(0, 500)}`);
|
|
283
|
+
}
|
|
284
|
+
res.writeHead(204, { 'Access-Control-Allow-Origin': '*' });
|
|
285
|
+
res.end();
|
|
286
|
+
});
|
|
287
|
+
req.on('error', () => {
|
|
288
|
+
try {
|
|
289
|
+
res.writeHead(204);
|
|
290
|
+
res.end();
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
/* ignore */
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
/** True for any path this module owns, so the daemon router can delegate. */
|
|
298
|
+
export function isTrmnlImagePath(pathname) {
|
|
299
|
+
return pathname.startsWith('/trmnl/image/');
|
|
300
|
+
}
|
|
301
|
+
//# sourceMappingURL=byos-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"byos-server.js","sourceRoot":"","sources":["../../src/trmnl/byos-server.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EACL,YAAY,EACZ,eAAe,EACf,eAAe,EACf,YAAY,EACZ,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,GAEpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAE9G,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEhD,MAAM,GAAG,GAAG,YAAY,CAAC;AAEzB,2FAA2F;AAC3F,MAAM,YAAY,GAAG,IAAI,GAAG,EAAU,CAAC;AAEvC;;;;;;GAMG;AACH,SAAS,cAAc,CAAC,GAAW,EAAE,IAAmB;IACtD,MAAM,GAAG,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,CAAC,GAAG;QAAE,OAAO;IACjB,MAAM,MAAM,GAAG,IAAI,IAAI,IAAI,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,mBAAmB,CAAC;IACpF,MAAM,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACtC,IAAI,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACvB,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACtB,SAAS,CACP,GAAG,EACH,uBAAuB,GAAG,UAAU,IAAI,WAAW,mBAAmB,mBAAmB;YACvF,6EAA6E,CAChF,CAAC;IACJ,CAAC;SAAM,IAAI,CAAC,MAAM,IAAI,OAAO,EAAE,CAAC;QAC9B,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACzB,SAAS,CAAC,GAAG,EAAE,4BAA4B,GAAG,UAAU,IAAI,IAAI,KAAK,KAAK,CAAC,CAAC;IAC9E,CAAC;AACH,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,sBAAsB;IACpC,YAAY,CAAC,KAAK,EAAE,CAAC;AACvB,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,WAAW,CAAC,GAAW,EAAE,CAAgB,EAAE,GAAgB;IAClE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,MAAM,YAAY,GAAG,eAAe,CAAC,GAAG,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;IAClD,IAAI,YAAY,IAAI,IAAI;QAAE,OAAO;IACjC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,YAAY,CAAC,GAAG,IAAI,CAAC,CAAC;IACvD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,GAAG,CAAC,aAAa,CAAC,CAAC;IAC9D,IAAI,MAAM,GAAG,QAAQ,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC;QAC/B,SAAS,CACP,GAAG,EACH,SAAS,YAAY,CAAC,GAAG,CAAC,eAAe,MAAM,sBAAsB,QAAQ,OAAO;YAClF,sEAAsE;YACtE,SAAS,CAAC,CAAC,IAAI,IAAI,KAAK,eAAe,CAAC,CAAC,cAAc,IAAI,KAAK,IAAI,CACvE,CAAC;IACJ,CAAC;AACH,CAAC;AAED,kFAAkF;AAClF,MAAM,OAAO,GAAG,GAAG,CAAC;AACpB,MAAM,OAAO,GAAG,IAAI,CAAC;AAErB,SAAS,MAAM,CAAC,GAAoB,EAAE,IAAY;IAChD,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IAC1C,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;AACrD,CAAC;AAED,SAAS,KAAK,CAAC,CAAS;IACtB,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC3B,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACvC,CAAC;AAgBD,mFAAmF;AACnF,SAAS,kBAAkB,CAAC,GAAoB;IAC9C,MAAM,IAAI,GAAG,CAAC,CAAgB,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,OAAO,IAAI,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACtG,OAAO;QACL,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC,GAAG,EAAE,cAAc,CAAC;QACxC,SAAS,EAAE,MAAM,CAAC,GAAG,EAAE,YAAY,CAAC;QACpC,cAAc,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC;QACrD,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAChC,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;QAC/C,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC;QACxC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC;QAC1C,SAAS,EAAE,MAAM,CAAC,GAAG,EAAE,YAAY,CAAC;KACrC,CAAC;AACJ,CAAC;AAED,4EAA4E;AAC5E,SAAS,UAAU,CAAC,CAAgB;IAClC,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,YAAY,EAAE,CAAC;AAC7E,CAAC;AAED;mFACmF;AACnF,SAAS,QAAQ,CAAC,GAAoB,EAAE,IAAuC,EAAE,IAAY;IAC3F,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,gBAAgB,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,MAAM,CAAC;AAClF,CAAC;AAED,SAAS,SAAS,CAAC,GAAoB;IACrC,8EAA8E;IAC9E,uEAAuE;IACvE,OAAO,UAAU,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,WAAW,EAAE,CAAC;AACrD,CAAC;AAED,SAAS,QAAQ,CAAC,GAAmB,EAAE,MAAc,EAAE,IAAa;IAClE,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACrC,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE;QACpB,cAAc,EAAE,kBAAkB;QAClC,eAAe,EAAE,UAAU;QAC3B,6BAA6B,EAAE,GAAG;KACnC,CAAC,CAAC;IACH,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AACnB,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,gBAAgB,CAAC,GAAoB,EAAE,GAAmB;IACxE,MAAM,CAAC,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC;IAClB,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,yBAAyB,EAAE,CAAC,CAAC;QACxE,OAAO;IACT,CAAC;IACD,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IAC9C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,mDAAmD;QACnD,KAAK,CAAC,GAAG,EAAE,sBAAsB,YAAY,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;QACvF,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,wDAAwD,EAAE,CAAC,CAAC;QACvG,OAAO;IACT,CAAC;IACD,WAAW,CAAC,GAAG,EAAE,CAAC,EAAE,eAAe,EAAE,CAAC,CAAC;IACvC,cAAc,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IAC5B,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;IAC3B,IAAI,OAAO,EAAE,CAAC;QACZ,KAAK,CAAC,GAAG,EAAE,kBAAkB,MAAM,CAAC,GAAG,OAAO,MAAM,CAAC,UAAU,KAAK,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;QAClG,qBAAqB,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,yCAAyC;IAC3F,CAAC;IACD,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IACrD,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE;QACjB,MAAM,EAAE,GAAG;QACX,OAAO,EAAE,MAAM,CAAC,MAAM;QACtB,WAAW,EAAE,MAAM,CAAC,UAAU;QAC9B,SAAS,EAAE,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC,WAAW,CAAC;QACjD,QAAQ,EAAE,KAAK,CAAC,WAAW;QAC3B,OAAO,EAAE,sBAAsB;KAChC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAoB,EAAE,GAAmB;IAC1E,MAAM,CAAC,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC;IAClB,MAAM,GAAG,GAAG,eAAe,EAAE,CAAC;IAC9B,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;IAC3B,IAAI,GAAG,EAAE,CAAC;QACR,WAAW,CAAC,GAAG,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;QACzB,cAAc,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IACD,IAAI,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAEpD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,2EAA2E;QAC3E,6EAA6E;QAC7E,6EAA6E;QAC7E,6EAA6E;QAC7E,+EAA+E;QAC/E,IAAI,GAAG,IAAI,GAAG,CAAC,YAAY,EAAE,CAAC;YAC5B,MAAM,CAAC,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;YAC5B,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;YAClB,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;gBACd,KAAK,CAAC,GAAG,EAAE,iBAAiB,YAAY,CAAC,GAAG,CAAC,qBAAqB,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;gBAChG,qBAAqB,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;YACjD,CAAC;QACH,CAAC;QACD,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,4EAA4E;YAC5E,yEAAyE;YACzE,uEAAuE;YACvE,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;YAC1D,KAAK,CAAC,GAAG,EAAE,2BAA2B,YAAY,CAAC,GAAG,CAAC,wCAAwC,CAAC,CAAC;YACjG,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE;gBACjB,MAAM,EAAE,GAAG;gBACX,SAAS,EAAE,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC,WAAW,CAAC;gBACtD,QAAQ,EAAE,UAAU,CAAC,WAAW;gBAChC,YAAY,EAAE,GAAG,CAAC,WAAW;gBAC7B,iBAAiB,EAAE,qBAAqB,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;gBAC/D,gBAAgB,EAAE,OAAO;gBACzB,cAAc,EAAE,KAAK;gBACrB,eAAe,EAAE,KAAK;gBACtB,YAAY,EAAE,IAAI;aACnB,CAAC,CAAC;YACH,OAAO;QACT,CAAC;IACH,CAAC;IAED,4EAA4E;IAC5E,wEAAwE;IACxE,uDAAuD;IACvD,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IACrD,kFAAkF;IAClF,MAAM,QAAQ,GAAG,gBAAgB,EAAE,CAAC;IACpC,MAAM,WAAW,GAAG,oBAAoB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IACxD,+EAA+E;IAC/E,4DAA4D;IAC5D,MAAM,YAAY,GAAG,qBAAqB,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAClE,KAAK,CACH,GAAG,EACH,WAAW,YAAY,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,SAAS,KAAK,CAAC,WAAW,GAAG;QACpF,WAAW,WAAW,kBAAkB,YAAY,UAAU,CAAC,CAAC,IAAI,IAAI,KAAK,GAAG;QAChF,YAAY,QAAQ,CAAC,QAAQ,YAAY,QAAQ,CAAC,OAAO,EAAE,CAC9D,CAAC;IACF,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE;QACjB,MAAM,EAAE,CAAC;QACT,SAAS,EAAE,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC,WAAW,CAAC;QACjD,QAAQ,EAAE,KAAK,CAAC,WAAW;QAC3B,6EAA6E;QAC7E,YAAY,EAAE,WAAW;QACzB,+EAA+E;QAC/E,0CAA0C;QAC1C,iBAAiB,EAAE,YAAY;QAC/B,gBAAgB,EAAE,OAAO;QACzB,cAAc,EAAE,KAAK;QACrB,eAAe,EAAE,KAAK;QACtB,YAAY,EAAE,IAAI;KACnB,CAAC,CAAC;AACL,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,gBAAgB,CAAC,GAAoB,EAAE,GAAmB;IACxE,4EAA4E;IAC5E,6EAA6E;IAC7E,2EAA2E;IAC3E,sBAAsB;IACtB,MAAM,QAAQ,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/C,MAAM,CAAC,GAAG,6CAA6C,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACvE,IAAI,KAA6B,CAAC;IAClC,IAAI,CAAC,EAAE,CAAC;QACN,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9B,KAAK,GAAG,kBAAkB,CAAC,GAAG,CAAC,IAAI,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/E,CAAC;IACD,IAAI,CAAC,KAAK;QAAE,KAAK,GAAG,aAAa,EAAE,CAAC;IACpC,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;QACjB,cAAc,EAAE,KAAK,CAAC,WAAW;QACjC,gBAAgB,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM;QACrC,eAAe,EAAE,UAAU;QAC3B,6BAA6B,EAAE,GAAG;KACnC,CAAC,CAAC;IACH,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;AACxB,CAAC;AAED,4EAA4E;AAC5E,8EAA8E;AAC9E,6EAA6E;AAC7E,oEAAoE;AACpE,MAAM,sBAAsB,GAAG,MAAM,CAAC;AACtC,MAAM,eAAe,GAAG,IAAI,GAAG,EAAkB,CAAC;AAElD,8EAA8E;AAC9E,SAAS,kBAAkB,CAAC,IAAY;IACtC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAChC,MAAM,GAAG,GAAG,MAAM,EAAE,GAAG,EAAE,UAAU,IAAI,MAAM,EAAE,IAAI,EAAE,UAAU,IAAI,MAAM,EAAE,UAAU,CAAC;QACtF,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzC,OAAO,GAAG;iBACP,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,YAAY,IAAI,CAAC,EAAE,cAAc,IAAI,GAAG,KAAK,CAAC,EAAE,WAAW,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;iBAC1G,IAAI,CAAC,KAAK,CAAC;iBACX,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,oCAAoC;IACtC,CAAC;IACD,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AACjD,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,cAAc,CAAC,GAAoB,EAAE,GAAmB;IACtE,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAS,EAAE,EAAE;QAC3B,IAAI,IAAI,CAAC,CAAC;QACV,IAAI,IAAI,CAAC,MAAM,GAAG,MAAM;YAAE,GAAG,CAAC,OAAO,EAAE,CAAC;IAC1C,CAAC,CAAC,CAAC;IACH,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;QACjB,MAAM,GAAG,GAAG,YAAY,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;QAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,GAAG,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,sBAAsB,EAAE,CAAC;YACpE,eAAe,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YAC9B,SAAS,CAAC,GAAG,EAAE,mBAAmB,GAAG,KAAK,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACxE,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,GAAG,EAAE,mBAAmB,GAAG,iBAAiB,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;QAC1E,CAAC;QACD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,6BAA6B,EAAE,GAAG,EAAE,CAAC,CAAC;QAC3D,GAAG,CAAC,GAAG,EAAE,CAAC;IACZ,CAAC,CAAC,CAAC;IACH,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;QACnB,IAAI,CAAC;YACH,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YACnB,GAAG,CAAC,GAAG,EAAE,CAAC;QACZ,CAAC;QAAC,MAAM,CAAC;YACP,YAAY;QACd,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,OAAO,QAAQ,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;AAC9C,CAAC"}
|