Spaces:
Running
Running
Bring Overview digest latency down to ~1s for Claude/Codex
Browse filesClaude/Codex updates took up to ~9s (4s poll + 5s server memo); OpenClaw
up to ~39s. New knobs: Overview polls at 1s, server memo TTL 400ms,
opencode/Hermes hot-DB guard 15s -> 8s, OpenClaw quiet window 30s ->
10s. The OpenClaw fence guard predates moving its state to local disk
(entrypoint.sh), where reads no longer disturb its metadata fingerprint
— keep a small margin since its session fence is unusually touchy.
Cost stays negligible thanks to per-file mtime caching: an unchanged
poll serves in ~1-5ms, re-parsing an actively-written 7MB rollout takes
~15ms, and a new prompt line is visible server-side ~70ms after the CLI
writes it (measured).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- server/src/traces.js +7 -4
- web/src/components/Overview.tsx +1 -1
server/src/traces.js
CHANGED
|
@@ -15,7 +15,7 @@ import { WORKSPACES_DIR } from './config.js';
|
|
| 15 |
|
| 16 |
const fileCache = new Map(); // path -> { key, parsed: { stats, digest } }
|
| 17 |
let resultMemo = { ts: 0, val: null };
|
| 18 |
-
const TTL =
|
| 19 |
|
| 20 |
function emptyStats() {
|
| 21 |
return { turns: 0, prompts: 0, toolCalls: 0, tools: {}, web: 0, tokensIn: 0, tokensOut: 0, cacheRead: 0, firstTs: 0, lastTs: 0, files: 0 };
|
|
@@ -293,7 +293,7 @@ function readOpencode() {
|
|
| 293 |
if (!ck) return [];
|
| 294 |
if (ocMemo.key === ck.key) return ocMemo.rows;
|
| 295 |
// actively written db on the FUSE mount: serve the previous read (first read allowed)
|
| 296 |
-
if (ocMemo.key && Date.now() - ck.hotMs <
|
| 297 |
const key = ck.key;
|
| 298 |
let db;
|
| 299 |
try { db = new DatabaseSync(p, { readOnly: true }); } catch { return ocMemo.rows; }
|
|
@@ -367,7 +367,7 @@ function readHermes() {
|
|
| 367 |
const ck = dbChangeKey(p);
|
| 368 |
if (!ck) return [];
|
| 369 |
if (hermesMemo.key === ck.key) return hermesMemo.rows;
|
| 370 |
-
if (hermesMemo.key && Date.now() - ck.hotMs <
|
| 371 |
let db;
|
| 372 |
try { db = new DatabaseSync(p, { readOnly: true }); } catch { return hermesMemo.rows; }
|
| 373 |
const rows = [];
|
|
@@ -560,7 +560,10 @@ async function build() {
|
|
| 560 |
const ocOwner = ocSessions.length === 1 ? ocSessions[0] : null;
|
| 561 |
for (const p of await openclawFiles()) {
|
| 562 |
seenFiles.add(p);
|
| 563 |
-
|
|
|
|
|
|
|
|
|
|
| 564 |
}
|
| 565 |
// Evict cache entries for files that no longer exist (rotated Codex rollouts,
|
| 566 |
// deleted transcripts) so fileCache doesn't grow unbounded on a long-lived Space.
|
|
|
|
| 15 |
|
| 16 |
const fileCache = new Map(); // path -> { key, parsed: { stats, digest } }
|
| 17 |
let resultMemo = { ts: 0, val: null };
|
| 18 |
+
const TTL = 400; // Overview polls at ~1Hz; per-file mtime caching keeps re-scans cheap
|
| 19 |
|
| 20 |
function emptyStats() {
|
| 21 |
return { turns: 0, prompts: 0, toolCalls: 0, tools: {}, web: 0, tokensIn: 0, tokensOut: 0, cacheRead: 0, firstTs: 0, lastTs: 0, files: 0 };
|
|
|
|
| 293 |
if (!ck) return [];
|
| 294 |
if (ocMemo.key === ck.key) return ocMemo.rows;
|
| 295 |
// actively written db on the FUSE mount: serve the previous read (first read allowed)
|
| 296 |
+
if (ocMemo.key && Date.now() - ck.hotMs < 8_000) return ocMemo.rows;
|
| 297 |
const key = ck.key;
|
| 298 |
let db;
|
| 299 |
try { db = new DatabaseSync(p, { readOnly: true }); } catch { return ocMemo.rows; }
|
|
|
|
| 367 |
const ck = dbChangeKey(p);
|
| 368 |
if (!ck) return [];
|
| 369 |
if (hermesMemo.key === ck.key) return hermesMemo.rows;
|
| 370 |
+
if (hermesMemo.key && Date.now() - ck.hotMs < 8_000) return hermesMemo.rows;
|
| 371 |
let db;
|
| 372 |
try { db = new DatabaseSync(p, { readOnly: true }); } catch { return hermesMemo.rows; }
|
| 373 |
const rows = [];
|
|
|
|
| 560 |
const ocOwner = ocSessions.length === 1 ? ocSessions[0] : null;
|
| 561 |
for (const p of await openclawFiles()) {
|
| 562 |
seenFiles.add(p);
|
| 563 |
+
// OpenClaw state lives on LOCAL disk now (see entrypoint.sh), so reads no
|
| 564 |
+
// longer risk tripping its metadata fence like on FUSE — keep a small
|
| 565 |
+
// quiet margin anyway since its session fence is unusually touchy.
|
| 566 |
+
attribute(ocOwner, await statsFor(p, parseOpenClaw, 10_000));
|
| 567 |
}
|
| 568 |
// Evict cache entries for files that no longer exist (rotated Codex rollouts,
|
| 569 |
// deleted transcripts) so fileCache doesn't grow unbounded on a long-lived Space.
|
web/src/components/Overview.tsx
CHANGED
|
@@ -154,7 +154,7 @@ export default function Overview({ clis, tree, filter, onOpen }: {
|
|
| 154 |
.then((r) => { if (alive) setMeta(Object.fromEntries(r.sessions.map((s) => [s.id, s]))); })
|
| 155 |
.catch(() => {});
|
| 156 |
load();
|
| 157 |
-
const t = setInterval(() => { if (!document.hidden) load(); },
|
| 158 |
return () => { alive = false; clearInterval(t); };
|
| 159 |
}, []);
|
| 160 |
|
|
|
|
| 154 |
.then((r) => { if (alive) setMeta(Object.fromEntries(r.sessions.map((s) => [s.id, s]))); })
|
| 155 |
.catch(() => {});
|
| 156 |
load();
|
| 157 |
+
const t = setInterval(() => { if (!document.hidden) load(); }, 1000);
|
| 158 |
return () => { alive = false; clearInterval(t); };
|
| 159 |
}, []);
|
| 160 |
|