fix(telemetry): store the prompt cache only for CLIs whose statusline reports it

The status-telemetry route gated the model report on
capabilities.statusLineTelemetry but stored the prompt-cache report for
any session mode, even though both ride the same statusline payload the
exporter POSTs. Gate the prompt-cache store on the same capability so a
CLI with no statusline exporter no longer carries prompt-cache state it
never actually reported.
This commit is contained in:
JD committed 2026-10-13 20:45:13 -04:00
1 parent a8a8bb71a3
commit 4091378e15
2 files changed
+24 -5

No files matched your search

+10 -5
View File
@@ -51,12 +51,17 @@ export function registerStatusTelemetryRoutes(app: FastifyInstance, ctx: Session
const payload = data as RawStatuslinePayload | undefined;
// Only a CLI that has a statusline exporter at all speaks for its own model and
// prompt-cache state: both ride the SAME statusline payload the exporter POSTs, so
// both are gated on capabilities.statusLineTelemetry (the gate that also decides
// whether the exporter is injected at all, in tmux-manager._resolveStatusLine).
const session = ctx.sessions.get(sessionId);
const reportsStatusLine = !!session && !!getCli(session.mode)?.capabilities.statusLineTelemetry;
// The model the CLI says it is running, for the session headers (displayModel). It
// rides every render, so it follows an in-session `/model`; the session dedupes and
// broadcasts only a change. Only a CLI that has a statusline exporter at all may
// report one here.
const session = ctx.sessions.get(sessionId);
if (session && getCli(session.mode)?.capabilities.statusLineTelemetry) {
// broadcasts only a change.
if (session && reportsStatusLine) {
session.noteReportedModel('statusline', payload?.model?.display_name);
}
@@ -68,7 +73,7 @@ export function registerStatusTelemetryRoutes(app: FastifyInstance, ctx: Session
// it backgrounds this POST (hooks-config.ts), so two sub-second loopback reports can
// arrive out of order; a stale one then briefly overwrites newer state until the next
// render corrects it. Accepted for a display-only readout rather than ordered here.
const promptCache = parsePromptCache(payload);
const promptCache = reportsStatusLine ? parsePromptCache(payload) : null;
if (promptCache && session?.setPromptCache(promptCache)) {
ctx.broadcast(SessionPromptCache, { sessionId, ...session.promptCache });
// Persisted on change, like a model change, so a crash restores the latest report.
@@ -212,6 +212,20 @@ describe('POST /api/status-telemetry: the session model (displayModel)', () => {
expect(session.toState().displayModel).toBeUndefined();
});
it('stores no prompt-cache from a CLI with no statusline exporter, but still broadcasts plan usage', async () => {
const session = realSession('codex', 'codex-cache');
const res = await post({
sessionId: 'codex-cache',
data: { ...REAL, prompt_cache: { warm: true, ttl: '1h', expires_at: Math.floor(Date.now() / 1000) + 3600 } },
});
expect(res.statusCode).toBe(200);
// Prompt cache is gated like the model report; the rest of the post still lands.
expect(session.toState().promptCache).toBeUndefined();
const events = h.ctx.broadcast.mock.calls.map(([name]) => name);
expect(events).not.toContain(SessionPromptCache);
expect(events).toContain(SessionStatusTelemetry);
});
it('broadcasts the STORED prompt-cache object, so a compaction stamp reaches the live readout', async () => {
const session = realSession('claude', 'claude-compact');
const expiresAtS = Math.floor(Date.now() / 1000) + 3600;