ocuclaw 1.3.3 → 1.3.4

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 (83) hide show
  1. package/README.md +29 -1
  2. package/dist/config/runtime-config-session-title-model.test.js +0 -3
  3. package/dist/config/runtime-config.js +22 -33
  4. package/dist/domain/activity-status-adapter.js +0 -7
  5. package/dist/domain/activity-status-arbiter.js +3 -27
  6. package/dist/domain/activity-status-labels.js +8 -38
  7. package/dist/domain/code-span-regions.js +4 -24
  8. package/dist/domain/constant-time-equal.js +9 -0
  9. package/dist/domain/constant-time-equal.test.js +28 -0
  10. package/dist/domain/conversation-state.js +27 -138
  11. package/dist/domain/debug-bundle-cache.js +52 -0
  12. package/dist/domain/debug-bundle-format.js +60 -0
  13. package/dist/domain/debug-bundle-preview.js +123 -0
  14. package/dist/domain/debug-bundle-redaction.js +182 -0
  15. package/dist/domain/debug-bundle-save.js +11 -0
  16. package/dist/domain/debug-bundle-zip.js +15 -0
  17. package/dist/domain/debug-bundle.js +97 -0
  18. package/dist/domain/debug-store.js +6 -17
  19. package/dist/domain/debug-upload-preset.js +27 -0
  20. package/dist/domain/glasses-display-system-prompt.js +0 -5
  21. package/dist/domain/glasses-display-system-prompt.test.js +1 -1
  22. package/dist/domain/glasses-ui-content-summary.js +0 -6
  23. package/dist/domain/glasses-ui-system-prompt.test.js +1 -2
  24. package/dist/domain/message-emoji-allowlist.js +0 -7
  25. package/dist/domain/message-emoji-filter.js +3 -9
  26. package/dist/domain/neural-emoji-reactor-tag-config.js +3 -3
  27. package/dist/domain/prompt-channel-fragments.js +1 -10
  28. package/dist/domain/tagged-span-parser.js +3 -26
  29. package/dist/domain/tagged-span-strip.js +0 -7
  30. package/dist/even-ai/even-ai-endpoint.js +77 -24
  31. package/dist/even-ai/even-ai-run-waiter.js +0 -1
  32. package/dist/even-ai/even-ai-settings-store.js +11 -0
  33. package/dist/gateway/gateway-bridge.js +8 -9
  34. package/dist/gateway/gateway-timing-ledger.js +8 -6
  35. package/dist/gateway/openclaw-client.js +97 -297
  36. package/dist/gateway/sanitize-connect-reason.js +10 -0
  37. package/dist/gateway/sanitize-connect-reason.test.js +34 -0
  38. package/dist/index.js +3 -3
  39. package/dist/runtime/channel-two-hook.js +1 -6
  40. package/dist/runtime/container-env.js +1 -5
  41. package/dist/runtime/debug-bundle-handler.js +159 -0
  42. package/dist/runtime/display-toggle-states.js +6 -17
  43. package/dist/runtime/downstream-handler.js +682 -508
  44. package/dist/runtime/glasses-backpressure-latch.js +2 -24
  45. package/dist/runtime/ocuclaw-settings-store.js +10 -1
  46. package/dist/runtime/openclaw-host-version.js +5 -0
  47. package/dist/runtime/plugin-version-service.js +13 -6
  48. package/dist/runtime/provider-usage-select.js +0 -6
  49. package/dist/runtime/register-session-title-distiller.js +14 -16
  50. package/dist/runtime/relay-core.js +601 -290
  51. package/dist/runtime/relay-service.js +19 -47
  52. package/dist/runtime/relay-worker-approval-replay-cache.js +1 -1
  53. package/dist/runtime/relay-worker-entry.js +1 -2
  54. package/dist/runtime/relay-worker-health.js +2 -10
  55. package/dist/runtime/relay-worker-protocol.js +6 -1
  56. package/dist/runtime/relay-worker-supervisor.js +103 -41
  57. package/dist/runtime/relay-worker-transport.js +150 -17
  58. package/dist/runtime/session-context-service.js +5 -45
  59. package/dist/runtime/session-service.js +157 -175
  60. package/dist/runtime/session-title-distiller-budget.js +1 -5
  61. package/dist/runtime/session-title-distiller-helpers.js +14 -24
  62. package/dist/runtime/session-title-distiller.js +109 -122
  63. package/dist/runtime/session-title-record.js +0 -6
  64. package/dist/runtime/stable-prompt-snapshot.js +3 -14
  65. package/dist/runtime/upstream-runtime.js +600 -103
  66. package/dist/tools/device-info-tool.js +4 -21
  67. package/dist/tools/glasses-ui-cron.js +22 -77
  68. package/dist/tools/glasses-ui-descriptors.js +4 -33
  69. package/dist/tools/glasses-ui-limits.js +0 -13
  70. package/dist/tools/glasses-ui-paint-floor.js +5 -39
  71. package/dist/tools/glasses-ui-recipes.js +92 -101
  72. package/dist/tools/glasses-ui-surfaces.js +31 -163
  73. package/dist/tools/glasses-ui-template.js +7 -22
  74. package/dist/tools/glasses-ui-tool-description.test.js +2 -2
  75. package/dist/tools/glasses-ui-tool.js +87 -451
  76. package/dist/tools/glasses-ui-voicemail.js +6 -63
  77. package/dist/tools/glasses-ui-wake.js +9 -76
  78. package/dist/tools/session-title-tool.js +2 -7
  79. package/dist/tools/session-title-tool.test.js +1 -1
  80. package/dist/version.js +3 -2
  81. package/openclaw.plugin.json +60 -13
  82. package/package.json +3 -2
  83. package/dist/runtime/protocol-adapter.js +0 -387
@@ -16,11 +16,19 @@ import { createWorkerMessageSendQueue } from "./relay-worker-queue.js";
16
16
  import { createRelayWorkerHealthMonitor } from "./relay-worker-health.js";
17
17
  import { createApprovalReplayCache } from "./relay-worker-approval-replay-cache.js";
18
18
  import { createRelayClientNudgeController } from "./relay-client-nudge-controller.js";
19
+ import { constantTimeEqual } from "../domain/constant-time-equal.js";
19
20
 
20
21
  const WebSocket = WebSocketModule.default || WebSocketModule.WebSocket || WebSocketModule;
21
22
  const WebSocketServer = WebSocketModule.WebSocketServer || WebSocketModule.Server || WebSocket.Server;
22
23
  const SEND_BUFFER_HIGH_WATER_BYTES = 262_144;
23
24
 
25
+ const SEND_BUFFER_HIGH_WATER_SHED_MS = 30_000;
26
+
27
+ const LIVENESS_MAX_MISSED_PINGS = 2;
28
+
29
+ const CONTROL_QUEUE_MAX_DEFAULT = 1000;
30
+ const TRANSACTIONAL_QUEUE_MAX_DEFAULT = 1000;
31
+
24
32
  function normalizeLogger(logger) {
25
33
  if (!logger || typeof logger !== "object") return console;
26
34
  return {
@@ -52,12 +60,19 @@ export function createRelayWorkerTransport(options = {}) {
52
60
  const logger = normalizeLogger(options.logger);
53
61
  const postToMain = typeof options.postToMain === "function" ? options.postToMain : () => {};
54
62
  const now = typeof options.now === "function" ? options.now : () => Date.now();
63
+ const listenRetryDelayMs =
64
+ Number.isFinite(options.listenRetryDelayMs) && options.listenRetryDelayMs >= 0
65
+ ? Math.floor(options.listenRetryDelayMs)
66
+ : 200;
67
+ const listenRetryMaxAttempts =
68
+ Number.isFinite(options.listenRetryMaxAttempts) && options.listenRetryMaxAttempts >= 0
69
+ ? Math.floor(options.listenRetryMaxAttempts)
70
+ : 5;
55
71
  let manifest = null;
56
72
  let httpServer = null;
57
73
  let wss = null;
58
74
  let nextClientId = 1;
59
- // Invalid-token rejects are the one connection log an internet-facing
60
- // misconfiguration could flood, so they are rate-limited per remote address.
75
+
61
76
  const TOKEN_REJECT_LOG_WINDOW_MS = 60000;
62
77
  const TOKEN_REJECT_LOG_MAX_ADDRESSES = 100;
63
78
  const tokenRejectLogState = new Map();
@@ -88,10 +103,13 @@ export function createRelayWorkerTransport(options = {}) {
88
103
  }
89
104
  let expireTimer = null;
90
105
  let healthTimer = null;
106
+ let livenessTimer = null;
91
107
  let loopDelayMonitor = null;
92
108
  const clients = new Map();
93
109
  const protocolState = new Map();
94
110
  const outboundQueues = new Map();
111
+
112
+ const sendBufferOverWaterSince = new Map();
95
113
  const sockets = new Set();
96
114
  const pendingHttp = new Map();
97
115
  const cache = {
@@ -223,14 +241,21 @@ export function createRelayWorkerTransport(options = {}) {
223
241
  return !!normalizeRequestId(parsed && parsed.requestId);
224
242
  }
225
243
 
244
+ function controlQueueMax() {
245
+ const v = manifest && manifest.queue ? manifest.queue.controlQueueMax : undefined;
246
+ return Number.isFinite(v) && v > 0 ? Math.floor(v) : CONTROL_QUEUE_MAX_DEFAULT;
247
+ }
248
+
249
+ function transactionalQueueMax() {
250
+ const v = manifest && manifest.queue ? manifest.queue.transactionalQueueMax : undefined;
251
+ return Number.isFinite(v) && v > 0 ? Math.floor(v) : TRANSACTIONAL_QUEUE_MAX_DEFAULT;
252
+ }
253
+
226
254
  function enqueueFrame(clientId, frame, options = {}) {
227
255
  const ws = clients.get(clientId);
228
256
  if (!ws || ws.readyState !== WebSocket.OPEN) return;
229
257
  const parsedFrame = parseFrame(frame);
230
- // F11 fan-out: reuse the type the main thread already parsed (threaded via
231
- // options.knownType from the broadcast/unicast call site) instead of
232
- // re-deriving it per client. parsedFrame is still needed below for
233
- // isImportantTransactionalFrame's requestId read, so parseFrame stays.
258
+
234
259
  const type = options.knownType !== undefined ? options.knownType : parseMessageType(parsedFrame);
235
260
  const q = ensureOutboundQueue(clientId);
236
261
  if (
@@ -247,8 +272,26 @@ export function createRelayWorkerTransport(options = {}) {
247
272
  type === APP_PROTOCOL.operationReceived
248
273
  ) {
249
274
  q.control.push(frame);
275
+ while (q.control.length > controlQueueMax()) {
276
+
277
+ const dropped = q.control.shift();
278
+ emitDebug("worker_control_frame_dropped", "warn", {
279
+ clientId,
280
+ droppedType: parseMessageType(parseFrame(dropped)),
281
+ queueDepth: q.control.length,
282
+ });
283
+ }
250
284
  } else if (isImportantTransactionalFrame(type, parsedFrame)) {
251
285
  q.transactional.push(frame);
286
+ while (q.transactional.length > transactionalQueueMax()) {
287
+
288
+ const dropped = q.transactional.shift();
289
+ emitDebug("worker_transactional_frame_dropped", "warn", {
290
+ clientId,
291
+ droppedType: parseMessageType(parseFrame(dropped)),
292
+ queueDepth: q.transactional.length,
293
+ });
294
+ }
252
295
  } else if (
253
296
  type === APP_PROTOCOL.pages ||
254
297
  type === APP_PROTOCOL.status ||
@@ -258,7 +301,7 @@ export function createRelayWorkerTransport(options = {}) {
258
301
  } else {
259
302
  q.bestEffort.push(frame);
260
303
  while (q.bestEffort.length > 100) {
261
- // Newest-wins eviction: drop the oldest queued best-effort frame.
304
+
262
305
  const dropped = q.bestEffort.shift();
263
306
  emitDebug("worker_best_effort_frame_dropped", "warn", {
264
307
  clientId,
@@ -600,7 +643,7 @@ export function createRelayWorkerTransport(options = {}) {
600
643
  ? parsed.hash
601
644
  : null;
602
645
  if (!requestedAgentName || !requestedHash) {
603
- // Malformed request — silently drop; mirrors the non-worker handler.
646
+
604
647
  return;
605
648
  }
606
649
  const dataUri =
@@ -918,14 +961,15 @@ export function createRelayWorkerTransport(options = {}) {
918
961
  sockets.delete(socket);
919
962
  });
920
963
  });
921
- wss = new WebSocketServer({ noServer: true });
964
+
965
+ wss = new WebSocketServer({ noServer: true, maxPayload: manifest.rpc.wsMaxMessageBytes });
922
966
  httpServer.on("upgrade", (req, socket, head) => {
923
967
  wss.handleUpgrade(req, socket, head, (ws) => wss.emit("connection", ws, req));
924
968
  });
925
969
  wss.on("connection", (ws, req) => {
926
970
  const requestUrl = new URL(req.url || "/", `http://${req.headers.host || "127.0.0.1"}`);
927
971
  const remoteAddress = (req.socket && req.socket.remoteAddress) || "unknown";
928
- if (requestUrl.searchParams.get("token") !== manifest.relayToken) {
972
+ if (!constantTimeEqual(requestUrl.searchParams.get("token"), manifest.relayToken)) {
929
973
  logTokenReject(remoteAddress);
930
974
  ws.close(4001, "invalid_token");
931
975
  return;
@@ -936,6 +980,9 @@ export function createRelayWorkerTransport(options = {}) {
936
980
  `[ocuclaw] relay client connected clientId=${clientId} remote=${remoteAddress}`,
937
981
  );
938
982
  clients.set(clientId, ws);
983
+
984
+ ws.__ocuMissedPings = 0;
985
+ ws.on("pong", () => { ws.__ocuMissedPings = 0; });
939
986
  protocolState.set(clientId, {
940
987
  protocolVersion: null,
941
988
  clientKind: "unknown",
@@ -954,6 +1001,7 @@ export function createRelayWorkerTransport(options = {}) {
954
1001
  clients.delete(clientId);
955
1002
  protocolState.delete(clientId);
956
1003
  outboundQueues.delete(clientId);
1004
+ sendBufferOverWaterSince.delete(clientId);
957
1005
  if (nudgeController) nudgeController.deleteClient(clientId);
958
1006
  const closeReasonStr =
959
1007
  reason == null
@@ -978,11 +1026,43 @@ export function createRelayWorkerTransport(options = {}) {
978
1026
  });
979
1027
 
980
1028
  await new Promise((resolve, reject) => {
981
- httpServer.once("error", reject);
982
- httpServer.listen(manifest.port, manifest.host, () => {
983
- httpServer.off("error", reject);
1029
+ let attempt = 0;
1030
+ let settled = false;
1031
+ const onError = (err) => {
1032
+ if (settled) return;
1033
+
1034
+ if (err && err.code === "EADDRINUSE" && attempt < listenRetryMaxAttempts) {
1035
+ attempt += 1;
1036
+ emitDebug("worker_listen_retry", "warn", {
1037
+ attempt,
1038
+ code: err.code,
1039
+ port: manifest.port,
1040
+ });
1041
+ const retryTimer = setTimeout(tryListen, listenRetryDelayMs);
1042
+ if (typeof retryTimer.unref === "function") retryTimer.unref();
1043
+ return;
1044
+ }
1045
+ settled = true;
1046
+ reject(err);
1047
+ };
1048
+ const onListening = () => {
1049
+ if (settled) return;
1050
+ settled = true;
1051
+ httpServer.off("error", onError);
984
1052
  resolve();
985
- });
1053
+ };
1054
+ function tryListen() {
1055
+ if (settled) return;
1056
+ if (!httpServer) {
1057
+
1058
+ settled = true;
1059
+ reject(new Error("relay worker transport closed during listen retry"));
1060
+ return;
1061
+ }
1062
+ httpServer.once("error", onError);
1063
+ httpServer.listen(manifest.port, manifest.host, onListening);
1064
+ }
1065
+ tryListen();
986
1066
  });
987
1067
  loopDelayMonitor = monitorEventLoopDelay({ resolution: 50 });
988
1068
  loopDelayMonitor.enable();
@@ -1000,15 +1080,36 @@ export function createRelayWorkerTransport(options = {}) {
1000
1080
  const sendBufferHighWaterClients = countSendBufferHighWaterClients();
1001
1081
  health.updateSendBufferHighWaterClients(sendBufferHighWaterClients);
1002
1082
  health.sample();
1003
- // Roadmap 4a: main holds the glasses paint-floor shed latch; the ws
1004
- // sockets (and their bufferedAmount) live here in the worker, so the
1005
- // count crosses on every heartbeat (the latch fail-opens on staleness).
1083
+
1084
+ sweepStuckSlowClients();
1085
+
1006
1086
  postToMain({
1007
1087
  kind: "worker.backpressure",
1008
1088
  workerEpoch: manifest.workerEpoch,
1009
1089
  sendBufferHighWaterClients,
1010
1090
  });
1011
1091
  }, manifest.health.heartbeatIntervalMs);
1092
+
1093
+ const livenessIntervalMs =
1094
+ Number.isFinite(manifest.health.livenessIntervalMs) && manifest.health.livenessIntervalMs > 0
1095
+ ? Math.floor(manifest.health.livenessIntervalMs)
1096
+ : Math.max(15000, manifest.health.heartbeatIntervalMs * 3);
1097
+ livenessTimer = setInterval(() => {
1098
+ for (const [, ws] of clients) {
1099
+ if (ws.readyState !== WebSocket.OPEN) continue;
1100
+ if ((ws.__ocuMissedPings || 0) >= LIVENESS_MAX_MISSED_PINGS) {
1101
+ ws.terminate();
1102
+ continue;
1103
+ }
1104
+ ws.__ocuMissedPings = (ws.__ocuMissedPings || 0) + 1;
1105
+ try {
1106
+ ws.ping();
1107
+ } catch {
1108
+
1109
+ }
1110
+ }
1111
+ }, livenessIntervalMs);
1112
+ if (typeof livenessTimer.unref === "function") livenessTimer.unref();
1012
1113
  postToMain({ kind: "worker.ready", workerEpoch: manifest.workerEpoch, address: address() });
1013
1114
  }
1014
1115
 
@@ -1034,6 +1135,35 @@ export function createRelayWorkerTransport(options = {}) {
1034
1135
  return count;
1035
1136
  }
1036
1137
 
1138
+ function sweepStuckSlowClients() {
1139
+ const at = now();
1140
+ for (const [clientId, ws] of clients) {
1141
+ if ((protocolState.get(clientId) || {}).clientKind !== "app") continue;
1142
+ const over =
1143
+ ws.readyState === WebSocket.OPEN &&
1144
+ Number.isFinite(ws.bufferedAmount) &&
1145
+ ws.bufferedAmount > SEND_BUFFER_HIGH_WATER_BYTES;
1146
+ if (!over) {
1147
+ sendBufferOverWaterSince.delete(clientId);
1148
+ continue;
1149
+ }
1150
+ const since = sendBufferOverWaterSince.get(clientId);
1151
+ if (since === undefined) {
1152
+ sendBufferOverWaterSince.set(clientId, at);
1153
+ continue;
1154
+ }
1155
+ if (at - since >= SEND_BUFFER_HIGH_WATER_SHED_MS) {
1156
+ emitDebug("worker_client_send_buffer_shed", "warn", {
1157
+ clientId,
1158
+ bufferedAmount: ws.bufferedAmount,
1159
+ overHighWaterMs: at - since,
1160
+ });
1161
+ sendBufferOverWaterSince.delete(clientId);
1162
+ ws.terminate();
1163
+ }
1164
+ }
1165
+ }
1166
+
1037
1167
  function address() {
1038
1168
  return httpServer && typeof httpServer.address === "function" ? httpServer.address() : null;
1039
1169
  }
@@ -1041,8 +1171,10 @@ export function createRelayWorkerTransport(options = {}) {
1041
1171
  function close() {
1042
1172
  if (expireTimer) clearInterval(expireTimer);
1043
1173
  if (healthTimer) clearInterval(healthTimer);
1174
+ if (livenessTimer) clearInterval(livenessTimer);
1044
1175
  expireTimer = null;
1045
1176
  healthTimer = null;
1177
+ livenessTimer = null;
1046
1178
  if (loopDelayMonitor) {
1047
1179
  loopDelayMonitor.disable();
1048
1180
  loopDelayMonitor = null;
@@ -1058,6 +1190,7 @@ export function createRelayWorkerTransport(options = {}) {
1058
1190
  clients.clear();
1059
1191
  protocolState.clear();
1060
1192
  outboundQueues.clear();
1193
+ sendBufferOverWaterSince.clear();
1061
1194
  if (nudgeController) nudgeController.clear();
1062
1195
  nudgeController = null;
1063
1196
  if (approvalReplay) approvalReplay.clear();
@@ -1,19 +1,3 @@
1
- // session-context-service.ts
2
- //
3
- // Owns the active-session "context tokens vs context window" snapshot
4
- // lifecycle. Reads via gateway sessions.describe; reads compaction
5
- // checkpoint count via sessions.compaction.list; broadcasts on changes.
6
- //
7
- // Field mapping (gateway → snapshot):
8
- // session.totalTokens → contextTokens (current cumulative usage)
9
- // session.contextTokens → contextWindow (model's configured window)
10
- // openclaw uses `contextTokens` on the row for window size; `totalTokens`
11
- // is the running usage. NOTE: the window is NOT resolved on a fresh session —
12
- // the gateway warms a model's window only after the first turn (sessions.describe
13
- // returns no window pre-turn). So we cache the observed window per model and
14
- // reuse it for subsequent cold/new-session reads. The cache is persisted under
15
- // `stateDir` (keyed by provider/model) so it survives relay restarts.
16
-
17
1
  import * as fs from "node:fs";
18
2
  import * as path from "node:path";
19
3
 
@@ -42,7 +26,7 @@ function loadModelContextWindowCache(cachePath) {
42
26
  }
43
27
  }
44
28
  } catch {
45
- // Missing/corrupt cache file — relearn from the next turn.
29
+
46
30
  }
47
31
  return cache;
48
32
  }
@@ -55,29 +39,10 @@ function persistModelContextWindowCache(cachePath, cache) {
55
39
  for (const [key, value] of cache.entries()) obj[key] = value;
56
40
  fs.writeFileSync(cachePath, JSON.stringify(obj), "utf8");
57
41
  } catch {
58
- // Best-effort persistence; the in-memory cache still works this process.
42
+
59
43
  }
60
44
  }
61
45
 
62
- /**
63
- * @typedef SessionContextSnapshot
64
- * @property {string} type
65
- * @property {string} sessionKey
66
- * @property {number} contextTokens
67
- * @property {number} contextWindow
68
- * @property {number} compactionCount
69
- * @property {boolean} runActive
70
- * @property {number} snapshotAtMs
71
- */
72
-
73
- /**
74
- * @param {object} opts
75
- * @param {{request: (method: string, params: object) => Promise<any>}} opts.gatewayBridge
76
- * @param {() => string|null} opts.getActiveSessionKey
77
- * @param {() => boolean} opts.getRunActive
78
- * @param {() => number} opts.nowMs
79
- * @param {(frame: SessionContextSnapshot) => void} opts.broadcast
80
- */
81
46
  export function createSessionContextService(opts) {
82
47
  const gatewayBridge = opts.gatewayBridge;
83
48
  const getActiveSessionKey = opts.getActiveSessionKey;
@@ -87,15 +52,10 @@ export function createSessionContextService(opts) {
87
52
  const getActiveModelKey =
88
53
  typeof opts.getActiveModelKey === "function" ? opts.getActiveModelKey : () => null;
89
54
 
90
- /** @type {SessionContextSnapshot|null} */
91
55
  let lastSnapshot = null;
92
56
 
93
- // Per-model context-window cache: the gateway only resolves a model's window
94
- // after the first turn warms its discovery, so once we observe a real window
95
- // we remember it per model and reuse it for later cold/new-session reads.
96
- // Persisted under stateDir so it survives relay restarts.
97
57
  const modelContextWindowCachePath = resolveModelContextWindowCachePath(opts.stateDir);
98
- /** @type {Map<string, number>} */
58
+
99
59
  const modelContextWindowCache = loadModelContextWindowCache(modelContextWindowCachePath);
100
60
 
101
61
  async function refreshActiveSessionContext() {
@@ -128,13 +88,13 @@ export function createSessionContextService(opts) {
128
88
  const modelKey = getActiveModelKey();
129
89
  let contextWindow = describeWindow;
130
90
  if (describeWindow > 0) {
131
- // Learned naturally (post-turn): remember (and persist) for future cold reads.
91
+
132
92
  if (modelKey && modelContextWindowCache.get(modelKey) !== describeWindow) {
133
93
  modelContextWindowCache.set(modelKey, describeWindow);
134
94
  persistModelContextWindowCache(modelContextWindowCachePath, modelContextWindowCache);
135
95
  }
136
96
  } else if (modelKey && modelContextWindowCache.has(modelKey)) {
137
- // Cold on a new session: reuse the window learned earlier for this model.
97
+
138
98
  contextWindow = modelContextWindowCache.get(modelKey);
139
99
  }
140
100
  const checkpoints =