@agentdeck/bridge 1.0.21 → 1.0.22
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/dist/ble-sync-spawn.d.ts +27 -0
- package/dist/ble-sync-spawn.d.ts.map +1 -1
- package/dist/ble-sync-spawn.js +32 -0
- package/dist/ble-sync-spawn.js.map +1 -1
- package/dist/bridge-core.d.ts +21 -4
- package/dist/bridge-core.d.ts.map +1 -1
- package/dist/bridge-core.js +52 -23
- package/dist/bridge-core.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +13 -0
- package/dist/cli.js.map +1 -1
- package/dist/codex-rate-limits-live.d.ts +58 -9
- package/dist/codex-rate-limits-live.d.ts.map +1 -1
- package/dist/codex-rate-limits-live.js +64 -15
- package/dist/codex-rate-limits-live.js.map +1 -1
- package/dist/codex-rate-limits.d.ts +2 -2
- package/dist/codex-rate-limits.d.ts.map +1 -1
- package/dist/codex-rate-limits.js +34 -16
- package/dist/codex-rate-limits.js.map +1 -1
- package/dist/daemon-server.d.ts +46 -2
- package/dist/daemon-server.d.ts.map +1 -1
- package/dist/daemon-server.js +65 -40
- package/dist/daemon-server.js.map +1 -1
- package/dist/daemon.js +1 -1
- package/dist/hook-server.d.ts.map +1 -1
- package/dist/hook-server.js +17 -0
- package/dist/hook-server.js.map +1 -1
- package/dist/idotmatrix/idotmatrix-daemon-sync.d.ts.map +1 -1
- package/dist/idotmatrix/idotmatrix-daemon-sync.js +13 -1
- package/dist/idotmatrix/idotmatrix-daemon-sync.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/python-ble-runtime.d.ts +2 -0
- package/dist/python-ble-runtime.d.ts.map +1 -1
- package/dist/python-ble-runtime.js +101 -7
- package/dist/python-ble-runtime.js.map +1 -1
- package/dist/timebox/timebox-daemon-sync.d.ts.map +1 -1
- package/dist/timebox/timebox-daemon-sync.js +11 -1
- package/dist/timebox/timebox-daemon-sync.js.map +1 -1
- package/dist/usage-api.d.ts +28 -1
- package/dist/usage-api.d.ts.map +1 -1
- package/dist/usage-api.js +74 -33
- package/dist/usage-api.js.map +1 -1
- package/dist/usage-event.d.ts.map +1 -1
- package/dist/usage-event.js +2 -17
- package/dist/usage-event.js.map +1 -1
- package/package.json +3 -3
package/dist/daemon-server.js
CHANGED
|
@@ -76,7 +76,7 @@ import { listActive as listActiveSessions, findAvailablePort, findExistingDaemon
|
|
|
76
76
|
import { isForeignDaemon } from './daemon-takeover.js';
|
|
77
77
|
import { loadDaemonSettings } from './daemon-settings.js';
|
|
78
78
|
import { resolveDaemonPort, describeDaemonPortSource, PREFERRED_PORT_RECLAIM_MS, } from './daemon-port.js';
|
|
79
|
-
import { fetchUsageFromApi,
|
|
79
|
+
import { fetchUsageFromApi, resetConsecutiveFailures } from './usage-api.js';
|
|
80
80
|
import { getOrCreateToken, isLocalConnection, validateToken } from './auth.js';
|
|
81
81
|
import { buildPublicHealth, gateHttpRequest, isAuthorizedHttpRequest } from './http-auth-gate.js';
|
|
82
82
|
import { closePairingWindow, getPairingWindowStatus, openPairingWindow, pairingWindowOpen, redeemPairingCode, } from './pairing-window.js';
|
|
@@ -201,16 +201,50 @@ function saveStagedFw() {
|
|
|
201
201
|
log(`[agentdeck] staged-fw persist failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
202
202
|
}
|
|
203
203
|
}
|
|
204
|
+
/** Resolve a stage target (canonical board, registry key, or IP) to the board
|
|
205
|
+
* string the advert is keyed on.
|
|
206
|
+
*
|
|
207
|
+
* This map is read back by `stagedFwAdvert(device_info.board)`, so anything
|
|
208
|
+
* else stored as the key is an advert that can never fire. Staging with an IP
|
|
209
|
+
* is not a misuse to reject — it is the documented way to name one unit when
|
|
210
|
+
* two share a `board` string (see esp32-ota's ambiguity error) — so normalize
|
|
211
|
+
* it here instead. Consequence worth stating: the feed pull carries only a
|
|
212
|
+
* board, so a stage aimed at one unit adverts to every unit of that board.
|
|
213
|
+
* They run the same image, so that is correct rather than merely tolerable.
|
|
214
|
+
*
|
|
215
|
+
* Unlike `findWifiOtaTarget` this must NOT require an open socket: a
|
|
216
|
+
* wake-sync-sleep board holds none by design, which is the entire reason
|
|
217
|
+
* staging exists. An unrecognized target is kept verbatim — staging for a
|
|
218
|
+
* board this daemon has not met yet is legitimate. */
|
|
219
|
+
function resolveStagedFwBoard(target) {
|
|
220
|
+
for (const [key, device] of wifiEsp32Devices) {
|
|
221
|
+
if (key === target || device.board === target || device.ip === target)
|
|
222
|
+
return device.board;
|
|
223
|
+
}
|
|
224
|
+
// A pull-sync client ages out of the WS roster between wakes; the feed
|
|
225
|
+
// tracker keeps its IP→board memory precisely for that gap.
|
|
226
|
+
const puller = feedPulls.clients().find((c) => c.client === target || c.board === target);
|
|
227
|
+
return puller?.board ?? target;
|
|
228
|
+
}
|
|
204
229
|
/** Stage a build for a board. Reads the file once to fingerprint it; the file
|
|
205
230
|
* itself is re-read at device download time (a rebuild at the same path is
|
|
206
|
-
* re-fingerprinted by re-staging).
|
|
207
|
-
|
|
231
|
+
* re-fingerprinted by re-staging).
|
|
232
|
+
*
|
|
233
|
+
* `pullSeen` reports whether this daemon has actually observed a feed pull
|
|
234
|
+
* from the board, because "staged" proves neither transfer nor install and a
|
|
235
|
+
* bare success reads as if it did. Only pull-sync firmware fetches a staged
|
|
236
|
+
* image; a board that holds a live WS never asks, so its stage sits forever
|
|
237
|
+
* while the CLI says it is handled. It is daemon-lifetime state, so `false`
|
|
238
|
+
* means "not seen since this daemon started" — not "cannot pull". */
|
|
239
|
+
function stageEsp32Fw(target, firmwarePath) {
|
|
240
|
+
const board = resolveStagedFwBoard(target);
|
|
208
241
|
const firmware = readFileSync(firmwarePath);
|
|
209
242
|
const md5 = createHash('md5').update(firmware).digest('hex');
|
|
210
243
|
stagedFwByBoard.set(board, { firmwarePath, md5, size: firmware.length, stagedAt: Date.now() });
|
|
211
244
|
saveStagedFw();
|
|
212
|
-
|
|
213
|
-
|
|
245
|
+
const pullSeen = feedPulls.clients().some((c) => c.board === board);
|
|
246
|
+
log(`[agentdeck] staged firmware for ${board}: ${firmware.length} bytes md5=${md5} — ${pullSeen ? 'installs on its next feed pull' : 'NO feed pull seen from this board since daemon start'}`);
|
|
247
|
+
return { board, bytes: firmware.length, md5, pullSeen };
|
|
214
248
|
}
|
|
215
249
|
/** The staged advert for a feed response, re-validated against the file on
|
|
216
250
|
* disk so a rebuilt-in-place binary never adverts a stale md5. */
|
|
@@ -490,6 +524,11 @@ export const __wifiOtaTestApi = {
|
|
|
490
524
|
unregisterWifiEsp32Socket,
|
|
491
525
|
handleEsp32OtaReply,
|
|
492
526
|
performWifiEsp32Ota,
|
|
527
|
+
resolveStagedFwBoard,
|
|
528
|
+
stageEsp32Fw,
|
|
529
|
+
stagedFwAdvert,
|
|
530
|
+
feedPulls,
|
|
531
|
+
clearStagedFwForTest: () => { stagedFwByBoard.clear(); },
|
|
493
532
|
};
|
|
494
533
|
async function performWifiEsp32Ota(core, target, firmwarePath) {
|
|
495
534
|
const { key, device } = findWifiOtaTarget(target);
|
|
@@ -679,13 +718,15 @@ async function fetchUsageViaWs(siblings) {
|
|
|
679
718
|
export async function fetchUsageRelayed(selfPort) {
|
|
680
719
|
const sessions = listActiveSessions();
|
|
681
720
|
const siblings = sessions.filter(s => s.port !== selfPort && s.agentType !== 'daemon');
|
|
721
|
+
// A sibling's reading is a real successful one, not a failure fallback, so it
|
|
722
|
+
// is `fresh` — its age contract is the 5-minute gate inside fetchUsageViaHttp.
|
|
682
723
|
if (siblings.length > 0) {
|
|
683
724
|
const httpResult = await fetchUsageViaHttp(siblings);
|
|
684
725
|
if (httpResult)
|
|
685
|
-
return httpResult.usage;
|
|
726
|
+
return { data: httpResult.usage, fresh: true };
|
|
686
727
|
const wsResult = await fetchUsageViaWs(siblings);
|
|
687
728
|
if (wsResult)
|
|
688
|
-
return wsResult;
|
|
729
|
+
return { data: wsResult, fresh: true };
|
|
689
730
|
// Relay is only an optimization to avoid multiple API pollers. A PTY session
|
|
690
731
|
// bridge only refreshes usage when it has *direct* inbound WS clients, but
|
|
691
732
|
// dashboards/Stream Deck connect to the daemon (sole entry point) — so a
|
|
@@ -3074,15 +3115,7 @@ export async function startDaemon(opts) {
|
|
|
3074
3115
|
// Reset backoff from pre-sleep failures and fetch fresh usage after network stabilizes
|
|
3075
3116
|
resetConsecutiveFailures();
|
|
3076
3117
|
setTimeout(() => {
|
|
3077
|
-
fetchUsageRelayed(port).then((
|
|
3078
|
-
if (usage)
|
|
3079
|
-
core.updateApiUsage(usage);
|
|
3080
|
-
else {
|
|
3081
|
-
core.oauthConnected = hasOAuthToken();
|
|
3082
|
-
if (core.cachedApiUsage)
|
|
3083
|
-
core.apiUsageStale = true;
|
|
3084
|
-
}
|
|
3085
|
-
});
|
|
3118
|
+
fetchUsageRelayed(port).then((result) => core.applyUsageResult(result));
|
|
3086
3119
|
}, 4000);
|
|
3087
3120
|
});
|
|
3088
3121
|
// Subscribe to sibling session bridges' timelines + modelCatalog relay
|
|
@@ -3177,6 +3210,19 @@ export async function startDaemon(opts) {
|
|
|
3177
3210
|
// So split the event by half instead of by event: the ACCOUNT half (quota,
|
|
3178
3211
|
// scoped caps, extra usage, subscriptions, Codex/Ollama, usageStale) comes
|
|
3179
3212
|
// from the daemon's authoritative aggregate, the SESSION half from the relay.
|
|
3213
|
+
//
|
|
3214
|
+
// That split applies on the `else` branch ONLY. When the relay carries
|
|
3215
|
+
// Claude percentages its event goes out verbatim, so the account half —
|
|
3216
|
+
// `codexRateLimits` included — is the BRIDGE's. Usually identical: both
|
|
3217
|
+
// sides run the same plan-aware rollout read. They can differ only when no
|
|
3218
|
+
// plan-matching rollout exists at all, since the daemon additionally holds
|
|
3219
|
+
// a `codex app-server` reading that a session bridge structurally cannot
|
|
3220
|
+
// (no host processes there) — then successive events alternate between a
|
|
3221
|
+
// real Codex window and a voided one. Left as is deliberately: the only
|
|
3222
|
+
// targeted fix is to overwrite the block from `core.buildUsage()` here, and
|
|
3223
|
+
// that is the call which arms the throttled spawn, on the one path whose
|
|
3224
|
+
// whole history is about not clobbering the dashboard. Tracked as issue
|
|
3225
|
+
// #253, not just as this comment.
|
|
3180
3226
|
if (hasClaudeData) {
|
|
3181
3227
|
core.wsServer.broadcast(evt);
|
|
3182
3228
|
}
|
|
@@ -4458,12 +4504,7 @@ export async function startDaemon(opts) {
|
|
|
4458
4504
|
return;
|
|
4459
4505
|
}
|
|
4460
4506
|
if (cmd.type === 'query_usage') {
|
|
4461
|
-
fetchUsageRelayed(port).then((
|
|
4462
|
-
if (usage)
|
|
4463
|
-
core.updateApiUsage(usage);
|
|
4464
|
-
else if (core.cachedApiUsage)
|
|
4465
|
-
core.apiUsageStale = true;
|
|
4466
|
-
});
|
|
4507
|
+
fetchUsageRelayed(port).then((result) => core.applyUsageResult(result));
|
|
4467
4508
|
}
|
|
4468
4509
|
};
|
|
4469
4510
|
// ── Device-sourced voice ──────────────────────────────────────────────
|
|
@@ -5286,15 +5327,7 @@ export async function startDaemon(opts) {
|
|
|
5286
5327
|
// Fetch usage on connect if stale
|
|
5287
5328
|
const cacheAge = Date.now() - core.lastApiFetchTime;
|
|
5288
5329
|
if (!core.cachedApiUsage || (core.lastApiFetchTime > 0 && cacheAge > 5 * 60 * 1000)) {
|
|
5289
|
-
fetchUsageRelayed(port).then((
|
|
5290
|
-
if (usage)
|
|
5291
|
-
core.updateApiUsage(usage);
|
|
5292
|
-
else {
|
|
5293
|
-
core.oauthConnected = hasOAuthToken();
|
|
5294
|
-
if (core.cachedApiUsage)
|
|
5295
|
-
core.apiUsageStale = true;
|
|
5296
|
-
}
|
|
5297
|
-
});
|
|
5330
|
+
fetchUsageRelayed(port).then((result) => core.applyUsageResult(result));
|
|
5298
5331
|
}
|
|
5299
5332
|
});
|
|
5300
5333
|
core.wsServer.onClientDisconnect((ws) => {
|
|
@@ -5511,15 +5544,7 @@ export async function startDaemon(opts) {
|
|
|
5511
5544
|
}
|
|
5512
5545
|
// Initial usage fetch (delayed 10s)
|
|
5513
5546
|
core.addTimeout(setTimeout(() => {
|
|
5514
|
-
fetchUsageRelayed(port).then((
|
|
5515
|
-
if (usage)
|
|
5516
|
-
core.updateApiUsage(usage);
|
|
5517
|
-
else {
|
|
5518
|
-
core.oauthConnected = hasOAuthToken();
|
|
5519
|
-
if (core.cachedApiUsage)
|
|
5520
|
-
core.apiUsageStale = true;
|
|
5521
|
-
}
|
|
5522
|
-
});
|
|
5547
|
+
fetchUsageRelayed(port).then((result) => core.applyUsageResult(result));
|
|
5523
5548
|
}, 10_000));
|
|
5524
5549
|
// Backstop sweep for held PreToolUse approval responses whose per-entry timer
|
|
5525
5550
|
// somehow didn't fire — resolves anything older than 60s to "ask" so a held
|