@agentdeck/bridge 1.0.21 → 1.0.23
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/apme/activity-history.d.ts +59 -0
- package/dist/apme/activity-history.d.ts.map +1 -0
- package/dist/apme/activity-history.js +265 -0
- package/dist/apme/activity-history.js.map +1 -0
- package/dist/apme/dashboard-html.d.ts.map +1 -1
- package/dist/apme/dashboard-html.js +40 -6
- package/dist/apme/dashboard-html.js.map +1 -1
- package/dist/apme/http.d.ts +1 -0
- package/dist/apme/http.d.ts.map +1 -1
- package/dist/apme/http.js +9 -0
- package/dist/apme/http.js.map +1 -1
- package/dist/apme/runner.d.ts +20 -0
- package/dist/apme/runner.d.ts.map +1 -1
- package/dist/apme/runner.js +57 -0
- package/dist/apme/runner.js.map +1 -1
- package/dist/apme/settings.d.ts +21 -4
- package/dist/apme/settings.d.ts.map +1 -1
- package/dist/apme/settings.js +29 -5
- package/dist/apme/settings.js.map +1 -1
- package/dist/apme/store.d.ts +7 -0
- package/dist/apme/store.d.ts.map +1 -1
- package/dist/apme/store.js +35 -0
- package/dist/apme/store.js.map +1 -1
- 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 +261 -13
- 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 +165 -44
- package/dist/daemon-server.js.map +1 -1
- package/dist/daemon-takeover.d.ts +3 -0
- package/dist/daemon-takeover.d.ts.map +1 -1
- package/dist/daemon-takeover.js +5 -0
- package/dist/daemon-takeover.js.map +1 -1
- package/dist/daemon.js +1 -1
- package/dist/esp32-flash-lease.d.ts +47 -0
- package/dist/esp32-flash-lease.d.ts.map +1 -0
- package/dist/esp32-flash-lease.js +84 -0
- package/dist/esp32-flash-lease.js.map +1 -0
- package/dist/esp32-flash-transport.d.ts +90 -0
- package/dist/esp32-flash-transport.d.ts.map +1 -0
- package/dist/esp32-flash-transport.js +157 -0
- package/dist/esp32-flash-transport.js.map +1 -0
- package/dist/esp32-flash.d.ts +171 -0
- package/dist/esp32-flash.d.ts.map +1 -0
- package/dist/esp32-flash.js +523 -0
- package/dist/esp32-flash.js.map +1 -0
- package/dist/esp32-serial.d.ts +9 -1
- package/dist/esp32-serial.d.ts.map +1 -1
- package/dist/esp32-serial.js +74 -1
- package/dist/esp32-serial.js.map +1 -1
- package/dist/esptool-js-node.d.ts +17 -0
- package/dist/esptool-js-node.d.ts.map +1 -0
- package/dist/esptool-js-node.js +17 -0
- package/dist/esptool-js-node.js.map +1 -0
- 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/review-runner.d.ts.map +1 -1
- package/dist/review-runner.js +27 -9
- package/dist/review-runner.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/timeline-summarizer.d.ts +7 -2
- package/dist/timeline-summarizer.d.ts.map +1 -1
- package/dist/timeline-summarizer.js +13 -69
- package/dist/timeline-summarizer.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 +6 -4
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';
|
|
@@ -102,7 +102,8 @@ import { codexTurnOutcomeFromRollout, lastAgentMessageFromCodexRollout } from '.
|
|
|
102
102
|
import { callFoundationModelsHelper } from './foundation-models-helper.js';
|
|
103
103
|
import { initModules, stopModules, createDefaultModules, } from './modules/index.js';
|
|
104
104
|
import { bindHostFor, daemonModuleConfigs, describeDaemonPosture, resolveDaemonPosture, } from './network-posture.js';
|
|
105
|
-
import { esp32ConnectionCount, getESP32DeviceInfo, onESP32Message, sendAuthProvisionToAll, sendWifiProvision, sendWifiProvisionToAll, handleESP32Wake, getESP32Ports, getSerialConnectionStatus, getSerialLastError, getSerialReachableBoards } from './esp32-serial.js';
|
|
105
|
+
import { esp32ConnectionCount, getESP32DeviceInfo, onESP32Message, sendAuthProvisionToAll, sendWifiProvision, sendWifiProvisionToAll, handleESP32Wake, getESP32Ports, getSerialConnectionStatus, getSerialLastError, getSerialReachableBoards, releaseESP32SerialPorts } from './esp32-serial.js';
|
|
106
|
+
import { clampLeaseSeconds, clearLease, readLease, writeLease } from './esp32-flash-lease.js';
|
|
106
107
|
import { loadWifiConfig } from './wifi-config.js';
|
|
107
108
|
import { getConnectedAdbDevices, hasAdb, getAdbDeviceCount } from './adb-reverse.js';
|
|
108
109
|
import { getPixooDeviceDetails, pixooDeviceCount } from './pixoo/pixoo-bridge.js';
|
|
@@ -201,16 +202,50 @@ function saveStagedFw() {
|
|
|
201
202
|
log(`[agentdeck] staged-fw persist failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
202
203
|
}
|
|
203
204
|
}
|
|
205
|
+
/** Resolve a stage target (canonical board, registry key, or IP) to the board
|
|
206
|
+
* string the advert is keyed on.
|
|
207
|
+
*
|
|
208
|
+
* This map is read back by `stagedFwAdvert(device_info.board)`, so anything
|
|
209
|
+
* else stored as the key is an advert that can never fire. Staging with an IP
|
|
210
|
+
* is not a misuse to reject — it is the documented way to name one unit when
|
|
211
|
+
* two share a `board` string (see esp32-ota's ambiguity error) — so normalize
|
|
212
|
+
* it here instead. Consequence worth stating: the feed pull carries only a
|
|
213
|
+
* board, so a stage aimed at one unit adverts to every unit of that board.
|
|
214
|
+
* They run the same image, so that is correct rather than merely tolerable.
|
|
215
|
+
*
|
|
216
|
+
* Unlike `findWifiOtaTarget` this must NOT require an open socket: a
|
|
217
|
+
* wake-sync-sleep board holds none by design, which is the entire reason
|
|
218
|
+
* staging exists. An unrecognized target is kept verbatim — staging for a
|
|
219
|
+
* board this daemon has not met yet is legitimate. */
|
|
220
|
+
function resolveStagedFwBoard(target) {
|
|
221
|
+
for (const [key, device] of wifiEsp32Devices) {
|
|
222
|
+
if (key === target || device.board === target || device.ip === target)
|
|
223
|
+
return device.board;
|
|
224
|
+
}
|
|
225
|
+
// A pull-sync client ages out of the WS roster between wakes; the feed
|
|
226
|
+
// tracker keeps its IP→board memory precisely for that gap.
|
|
227
|
+
const puller = feedPulls.clients().find((c) => c.client === target || c.board === target);
|
|
228
|
+
return puller?.board ?? target;
|
|
229
|
+
}
|
|
204
230
|
/** Stage a build for a board. Reads the file once to fingerprint it; the file
|
|
205
231
|
* itself is re-read at device download time (a rebuild at the same path is
|
|
206
|
-
* re-fingerprinted by re-staging).
|
|
207
|
-
|
|
232
|
+
* re-fingerprinted by re-staging).
|
|
233
|
+
*
|
|
234
|
+
* `pullSeen` reports whether this daemon has actually observed a feed pull
|
|
235
|
+
* from the board, because "staged" proves neither transfer nor install and a
|
|
236
|
+
* bare success reads as if it did. Only pull-sync firmware fetches a staged
|
|
237
|
+
* image; a board that holds a live WS never asks, so its stage sits forever
|
|
238
|
+
* while the CLI says it is handled. It is daemon-lifetime state, so `false`
|
|
239
|
+
* means "not seen since this daemon started" — not "cannot pull". */
|
|
240
|
+
function stageEsp32Fw(target, firmwarePath) {
|
|
241
|
+
const board = resolveStagedFwBoard(target);
|
|
208
242
|
const firmware = readFileSync(firmwarePath);
|
|
209
243
|
const md5 = createHash('md5').update(firmware).digest('hex');
|
|
210
244
|
stagedFwByBoard.set(board, { firmwarePath, md5, size: firmware.length, stagedAt: Date.now() });
|
|
211
245
|
saveStagedFw();
|
|
212
|
-
|
|
213
|
-
|
|
246
|
+
const pullSeen = feedPulls.clients().some((c) => c.board === board);
|
|
247
|
+
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'}`);
|
|
248
|
+
return { board, bytes: firmware.length, md5, pullSeen };
|
|
214
249
|
}
|
|
215
250
|
/** The staged advert for a feed response, re-validated against the file on
|
|
216
251
|
* disk so a rebuilt-in-place binary never adverts a stale md5. */
|
|
@@ -490,6 +525,11 @@ export const __wifiOtaTestApi = {
|
|
|
490
525
|
unregisterWifiEsp32Socket,
|
|
491
526
|
handleEsp32OtaReply,
|
|
492
527
|
performWifiEsp32Ota,
|
|
528
|
+
resolveStagedFwBoard,
|
|
529
|
+
stageEsp32Fw,
|
|
530
|
+
stagedFwAdvert,
|
|
531
|
+
feedPulls,
|
|
532
|
+
clearStagedFwForTest: () => { stagedFwByBoard.clear(); },
|
|
493
533
|
};
|
|
494
534
|
async function performWifiEsp32Ota(core, target, firmwarePath) {
|
|
495
535
|
const { key, device } = findWifiOtaTarget(target);
|
|
@@ -679,13 +719,15 @@ async function fetchUsageViaWs(siblings) {
|
|
|
679
719
|
export async function fetchUsageRelayed(selfPort) {
|
|
680
720
|
const sessions = listActiveSessions();
|
|
681
721
|
const siblings = sessions.filter(s => s.port !== selfPort && s.agentType !== 'daemon');
|
|
722
|
+
// A sibling's reading is a real successful one, not a failure fallback, so it
|
|
723
|
+
// is `fresh` — its age contract is the 5-minute gate inside fetchUsageViaHttp.
|
|
682
724
|
if (siblings.length > 0) {
|
|
683
725
|
const httpResult = await fetchUsageViaHttp(siblings);
|
|
684
726
|
if (httpResult)
|
|
685
|
-
return httpResult.usage;
|
|
727
|
+
return { data: httpResult.usage, fresh: true };
|
|
686
728
|
const wsResult = await fetchUsageViaWs(siblings);
|
|
687
729
|
if (wsResult)
|
|
688
|
-
return wsResult;
|
|
730
|
+
return { data: wsResult, fresh: true };
|
|
689
731
|
// Relay is only an optimization to avoid multiple API pollers. A PTY session
|
|
690
732
|
// bridge only refreshes usage when it has *direct* inbound WS clients, but
|
|
691
733
|
// dashboards/Stream Deck connect to the daemon (sole entry point) — so a
|
|
@@ -1093,6 +1135,10 @@ export async function startDaemon(opts) {
|
|
|
1093
1135
|
/** Produces live timeline rows for observed Kiro sessions — see the class
|
|
1094
1136
|
* doc for why Kiro needs a producer when hook agents do not. */
|
|
1095
1137
|
const kiroTimelineFeed = new KiroTimelineFeed();
|
|
1138
|
+
/** Kiro has no CLI hooks, so transcript rows also feed APME. These maps are
|
|
1139
|
+
* bounded by the observer's current live set and discarded on disappearance. */
|
|
1140
|
+
const kiroApmeSessions = new Set();
|
|
1141
|
+
const kiroApmeResponseBySession = new Map();
|
|
1096
1142
|
/** Observed Claude sessions whose AskUserQuestion PreToolUse is held open for
|
|
1097
1143
|
* a device answer, keyed by the bare Claude session UUID. Its presence is
|
|
1098
1144
|
* what makes the session's options pressable (`liveAnswerable`) and what
|
|
@@ -1606,6 +1652,62 @@ export async function startDaemon(opts) {
|
|
|
1606
1652
|
}
|
|
1607
1653
|
return;
|
|
1608
1654
|
}
|
|
1655
|
+
// ===== Serial suspend / resume (USB flashing) =====
|
|
1656
|
+
// Both sit BEHIND the normal auth gate — `isAuthorizedHttpRequest()` treats
|
|
1657
|
+
// same-machine as authenticated, which is exactly the audience here, so
|
|
1658
|
+
// this adds no standing unauthenticated route. The one always-on pre-auth
|
|
1659
|
+
// route is still `GET /health`.
|
|
1660
|
+
//
|
|
1661
|
+
// `agentdeck esp32 flash` calls suspend, flashes, and calls resume in a
|
|
1662
|
+
// `finally`. Neither call is load-bearing on its own: expiry is enforced
|
|
1663
|
+
// when the lease is READ, so a CLI that is killed mid-flash recovers with
|
|
1664
|
+
// nothing running.
|
|
1665
|
+
if (req.method === 'POST' && pathname === '/esp32/serial/suspend') {
|
|
1666
|
+
(async () => {
|
|
1667
|
+
const body = await readJsonBody(req);
|
|
1668
|
+
const seconds = clampLeaseSeconds(body.seconds ?? 120);
|
|
1669
|
+
const lease = writeLease({
|
|
1670
|
+
until: Date.now() + seconds * 1000,
|
|
1671
|
+
reason: typeof body.reason === 'string' ? body.reason : 'usb flash',
|
|
1672
|
+
pid: typeof body.pid === 'number' ? body.pid : undefined,
|
|
1673
|
+
board: typeof body.board === 'string' ? body.board : undefined,
|
|
1674
|
+
});
|
|
1675
|
+
// Refusing to OPEN is only half of it — a port this daemon already
|
|
1676
|
+
// holds still blocks the flasher, and two readers on one TTY steal
|
|
1677
|
+
// each other's bytes instead of failing cleanly.
|
|
1678
|
+
const released = releaseESP32SerialPorts(`flash lease (${lease.board ?? 'usb flash'})`);
|
|
1679
|
+
return { ok: true, until: lease.until, seconds, released };
|
|
1680
|
+
})().then((result) => {
|
|
1681
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1682
|
+
res.end(JSON.stringify(result));
|
|
1683
|
+
}).catch((err) => {
|
|
1684
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
1685
|
+
res.end(JSON.stringify({ ok: false, error: err instanceof Error ? err.message : String(err) }));
|
|
1686
|
+
});
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1689
|
+
if (req.method === 'POST' && pathname === '/esp32/serial/resume') {
|
|
1690
|
+
// Idempotent by contract: resuming when nothing is suspended is a
|
|
1691
|
+
// success. The CLI calls this from a `finally`, where it may well run
|
|
1692
|
+
// after the lease has already expired on its own.
|
|
1693
|
+
const was = readLease();
|
|
1694
|
+
clearLease();
|
|
1695
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1696
|
+
res.end(JSON.stringify({ ok: true, wasSuspended: was !== null }));
|
|
1697
|
+
return;
|
|
1698
|
+
}
|
|
1699
|
+
if (req.method === 'GET' && pathname === '/esp32/serial/status') {
|
|
1700
|
+
const lease = readLease();
|
|
1701
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1702
|
+
res.end(JSON.stringify({
|
|
1703
|
+
ok: true,
|
|
1704
|
+
suspended: lease !== null,
|
|
1705
|
+
until: lease?.until,
|
|
1706
|
+
board: lease?.board,
|
|
1707
|
+
ports: getESP32Ports(),
|
|
1708
|
+
}));
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1609
1711
|
if (req.method === 'POST' && pathname === '/esp32/ota') {
|
|
1610
1712
|
(async () => {
|
|
1611
1713
|
const body = await readJsonBody(req);
|
|
@@ -3074,15 +3176,7 @@ export async function startDaemon(opts) {
|
|
|
3074
3176
|
// Reset backoff from pre-sleep failures and fetch fresh usage after network stabilizes
|
|
3075
3177
|
resetConsecutiveFailures();
|
|
3076
3178
|
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
|
-
});
|
|
3179
|
+
fetchUsageRelayed(port).then((result) => core.applyUsageResult(result));
|
|
3086
3180
|
}, 4000);
|
|
3087
3181
|
});
|
|
3088
3182
|
// Subscribe to sibling session bridges' timelines + modelCatalog relay
|
|
@@ -3177,6 +3271,19 @@ export async function startDaemon(opts) {
|
|
|
3177
3271
|
// So split the event by half instead of by event: the ACCOUNT half (quota,
|
|
3178
3272
|
// scoped caps, extra usage, subscriptions, Codex/Ollama, usageStale) comes
|
|
3179
3273
|
// from the daemon's authoritative aggregate, the SESSION half from the relay.
|
|
3274
|
+
//
|
|
3275
|
+
// That split applies on the `else` branch ONLY. When the relay carries
|
|
3276
|
+
// Claude percentages its event goes out verbatim, so the account half —
|
|
3277
|
+
// `codexRateLimits` included — is the BRIDGE's. Usually identical: both
|
|
3278
|
+
// sides run the same plan-aware rollout read. They can differ only when no
|
|
3279
|
+
// plan-matching rollout exists at all, since the daemon additionally holds
|
|
3280
|
+
// a `codex app-server` reading that a session bridge structurally cannot
|
|
3281
|
+
// (no host processes there) — then successive events alternate between a
|
|
3282
|
+
// real Codex window and a voided one. Left as is deliberately: the only
|
|
3283
|
+
// targeted fix is to overwrite the block from `core.buildUsage()` here, and
|
|
3284
|
+
// that is the call which arms the throttled spawn, on the one path whose
|
|
3285
|
+
// whole history is about not clobbering the dashboard. Tracked as issue
|
|
3286
|
+
// #253, not just as this comment.
|
|
3180
3287
|
if (hasClaudeData) {
|
|
3181
3288
|
core.wsServer.broadcast(evt);
|
|
3182
3289
|
}
|
|
@@ -3466,11 +3573,46 @@ export async function startDaemon(opts) {
|
|
|
3466
3573
|
// beside it stays empty. #218 taught the per-session QUERY to read Kiro's
|
|
3467
3574
|
// own transcript; the main timeline is a STREAM, and this is what feeds it.
|
|
3468
3575
|
try {
|
|
3469
|
-
const
|
|
3470
|
-
.filter((s) => typeof s.agentType === 'string' && s.agentType.startsWith('kiro'))
|
|
3471
|
-
|
|
3576
|
+
const observedKiro = passiveSessionObserver.collect([])
|
|
3577
|
+
.filter((s) => typeof s.agentType === 'string' && s.agentType.startsWith('kiro'));
|
|
3578
|
+
const kiroIds = observedKiro.map((s) => s.id);
|
|
3579
|
+
const projectBySession = new Map(observedKiro.map((s) => [rawSessionId(s.id), s.projectName]));
|
|
3580
|
+
const currentSessions = new Set(kiroIds.map(rawSessionId));
|
|
3581
|
+
for (const sessionId of kiroApmeSessions) {
|
|
3582
|
+
if (currentSessions.has(sessionId))
|
|
3583
|
+
continue;
|
|
3584
|
+
apme?.collector.closeRun(sessionId);
|
|
3585
|
+
kiroApmeSessions.delete(sessionId);
|
|
3586
|
+
kiroApmeResponseBySession.delete(sessionId);
|
|
3587
|
+
}
|
|
3472
3588
|
for (const entry of kiroTimelineFeed.pump(kiroIds)) {
|
|
3473
3589
|
core.bridgeTimeline.addEntry(entry);
|
|
3590
|
+
if (!apme || !entry.sessionId)
|
|
3591
|
+
continue;
|
|
3592
|
+
const sessionId = rawSessionId(entry.sessionId);
|
|
3593
|
+
if (entry.type === 'chat_start') {
|
|
3594
|
+
if (!apme.collector.getRunId(sessionId)) {
|
|
3595
|
+
apme.collector.openRun({
|
|
3596
|
+
sessionId,
|
|
3597
|
+
agentType: 'kiro-cli',
|
|
3598
|
+
projectName: projectBySession.get(sessionId) ?? undefined,
|
|
3599
|
+
});
|
|
3600
|
+
}
|
|
3601
|
+
kiroApmeSessions.add(sessionId);
|
|
3602
|
+
kiroApmeResponseBySession.delete(sessionId);
|
|
3603
|
+
apme.collector.ingestHook(sessionId, 'user_prompt_submit', {
|
|
3604
|
+
prompt: entry.detail || entry.raw,
|
|
3605
|
+
agent_type: 'kiro-cli',
|
|
3606
|
+
project_name: projectBySession.get(sessionId),
|
|
3607
|
+
});
|
|
3608
|
+
}
|
|
3609
|
+
else if (entry.type === 'chat_response' && apme.collector.getRunId(sessionId)) {
|
|
3610
|
+
const piece = entry.detail || entry.raw;
|
|
3611
|
+
const response = [kiroApmeResponseBySession.get(sessionId), piece].filter(Boolean).join('\n');
|
|
3612
|
+
kiroApmeResponseBySession.set(sessionId, response);
|
|
3613
|
+
apme.collector.setTurnResponse(sessionId, response);
|
|
3614
|
+
apme.collector.noteTurnStop(sessionId);
|
|
3615
|
+
}
|
|
3474
3616
|
}
|
|
3475
3617
|
}
|
|
3476
3618
|
catch (err) {
|
|
@@ -4458,12 +4600,7 @@ export async function startDaemon(opts) {
|
|
|
4458
4600
|
return;
|
|
4459
4601
|
}
|
|
4460
4602
|
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
|
-
});
|
|
4603
|
+
fetchUsageRelayed(port).then((result) => core.applyUsageResult(result));
|
|
4467
4604
|
}
|
|
4468
4605
|
};
|
|
4469
4606
|
// ── Device-sourced voice ──────────────────────────────────────────────
|
|
@@ -5286,15 +5423,7 @@ export async function startDaemon(opts) {
|
|
|
5286
5423
|
// Fetch usage on connect if stale
|
|
5287
5424
|
const cacheAge = Date.now() - core.lastApiFetchTime;
|
|
5288
5425
|
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
|
-
});
|
|
5426
|
+
fetchUsageRelayed(port).then((result) => core.applyUsageResult(result));
|
|
5298
5427
|
}
|
|
5299
5428
|
});
|
|
5300
5429
|
core.wsServer.onClientDisconnect((ws) => {
|
|
@@ -5511,15 +5640,7 @@ export async function startDaemon(opts) {
|
|
|
5511
5640
|
}
|
|
5512
5641
|
// Initial usage fetch (delayed 10s)
|
|
5513
5642
|
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
|
-
});
|
|
5643
|
+
fetchUsageRelayed(port).then((result) => core.applyUsageResult(result));
|
|
5523
5644
|
}, 10_000));
|
|
5524
5645
|
// Backstop sweep for held PreToolUse approval responses whose per-entry timer
|
|
5525
5646
|
// somehow didn't fire — resolves anything older than 60s to "ask" so a held
|