mirror of
https://github.com/mksglu/context-mode.git
synced 2026-10-02 04:14:38 +08:00
fix(stats): fold tool_calls.bytes_returned into 'With context-mode' bar
ctx_stats Section 1 rendered 'With context-mode: 1 B / 100% kept out' on tool-heavy sessions. Root cause: getRealBytesStats summed only session_events.bytes_returned (snapshot-replay only, ~0), while real MCP returns (ctx_execute/ctx_search stdout, redirect stubs) land in tool_calls.bytes_returned via incrementToolCall. Now folds tool_calls.bytes_returned across all three tiers (session/project/lifetime), so the bar shows the real bytes that entered context. Verified empirically: a live session read 0 from session_events but 423 KB from tool_calls. Conversation tier has zero overlap; lifetime overlap is ~0.4% (legacy sandbox-execute events). TDD: new 8.1b test seeds tool_calls and asserts the fold; existing tiers unchanged.
This commit is contained in:
@@ -1274,6 +1274,17 @@ export function getRealBytesStats(opts: {
|
||||
).get(opts.sessionId) as { bytes: number } | undefined;
|
||||
if (snap?.bytes) snapshotBytes += Number(snap.bytes);
|
||||
} catch { /* old schema */ }
|
||||
try {
|
||||
// MCP tool returns (ctx_execute/ctx_search stdout, redirect stubs)
|
||||
// land in tool_calls.bytes_returned via incrementToolCall, NOT in
|
||||
// session_events.bytes_returned (which carries snapshot-replay only).
|
||||
// Folding them here is what the "With context-mode" bar measures;
|
||||
// without it a tool-heavy session rendered a false "1 B / 100%".
|
||||
const tc = sdb.prepare(
|
||||
"SELECT COALESCE(SUM(bytes_returned), 0) AS bytes FROM tool_calls WHERE session_id = ?",
|
||||
).get(opts.sessionId) as { bytes: number } | undefined;
|
||||
if (tc?.bytes) bytesReturned += Number(tc.bytes);
|
||||
} catch { /* old schema: no tool_calls table */ }
|
||||
} else if (opts.projectDir) {
|
||||
// Bug E+F: META-scoped aggregation. Take every session_id whose
|
||||
// session_meta.project_dir matches, then sum ALL of those
|
||||
@@ -1307,6 +1318,16 @@ export function getRealBytesStats(opts: {
|
||||
).get(opts.projectDir) as { bytes: number } | undefined;
|
||||
if (snap?.bytes) snapshotBytes += Number(snap.bytes);
|
||||
} catch { /* old schema */ }
|
||||
try {
|
||||
const tc = sdb.prepare(
|
||||
`SELECT COALESCE(SUM(bytes_returned), 0) AS bytes
|
||||
FROM tool_calls
|
||||
WHERE session_id IN (
|
||||
SELECT session_id FROM session_meta WHERE project_dir = ?
|
||||
)`,
|
||||
).get(opts.projectDir) as { bytes: number } | undefined;
|
||||
if (tc?.bytes) bytesReturned += Number(tc.bytes);
|
||||
} catch { /* old schema: no tool_calls table */ }
|
||||
} else {
|
||||
const row = sdb.prepare(
|
||||
`SELECT
|
||||
@@ -1328,6 +1349,12 @@ export function getRealBytesStats(opts: {
|
||||
).get() as { bytes: number } | undefined;
|
||||
if (snap?.bytes) snapshotBytes += Number(snap.bytes);
|
||||
} catch { /* old schema */ }
|
||||
try {
|
||||
const tc = sdb.prepare(
|
||||
"SELECT COALESCE(SUM(bytes_returned), 0) AS bytes FROM tool_calls",
|
||||
).get() as { bytes: number } | undefined;
|
||||
if (tc?.bytes) bytesReturned += Number(tc.bytes);
|
||||
} catch { /* old schema: no tool_calls table */ }
|
||||
}
|
||||
} finally {
|
||||
sdb.close();
|
||||
|
||||
@@ -62,6 +62,7 @@ function seed(
|
||||
sessionId: string,
|
||||
events: Array<{ type: string; category: string; data: string; bytesAvoided?: number; bytesReturned?: number }>,
|
||||
snapshots?: Array<{ snapshot: string }>,
|
||||
toolCalls?: Array<{ tool: string; bytesReturned: number }>,
|
||||
): void {
|
||||
const sdb = new SessionDB({ dbPath });
|
||||
try {
|
||||
@@ -90,6 +91,11 @@ function seed(
|
||||
sdb.upsertResume(sessionId, s.snapshot, events.length);
|
||||
}
|
||||
}
|
||||
if (toolCalls) {
|
||||
for (const tc of toolCalls) {
|
||||
sdb.incrementToolCall(sessionId, tc.tool, tc.bytesReturned);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
sdb.close();
|
||||
}
|
||||
@@ -124,6 +130,30 @@ describe("getRealBytesStats (Phase 8 renderer source-of-truth)", () => {
|
||||
expect(r.totalSavedTokens).toBeGreaterThan(9_000); // ≈ 9_500
|
||||
});
|
||||
|
||||
test("8.1b conversation tier: folds tool_calls.bytes_returned (MCP returns) into bytesReturned", () => {
|
||||
// Production reality: ctx_execute / ctx_search return bytes to context via
|
||||
// the tool_calls counter (incrementToolCall / persistToolCallCounter), NOT
|
||||
// session_events.bytes_returned. A session with real MCP returns but no
|
||||
// snapshot replay reported bytesReturned=0 before this fix, rendering the
|
||||
// Section 1 bar as "With context-mode: 1 B / 100% kept out" — false.
|
||||
const dir = mkSessionsDir();
|
||||
const sid = `sess-${randomUUID()}`;
|
||||
const dbPath = dbPathFor(dir, "cafebabecafebabe");
|
||||
seed(
|
||||
dbPath,
|
||||
sid,
|
||||
[{ type: "tool_use", category: "file", data: "src/app.ts", bytesAvoided: 90_000, bytesReturned: 0 }],
|
||||
undefined,
|
||||
[{ tool: "ctx_execute", bytesReturned: 8_000 }, { tool: "ctx_search", bytesReturned: 2_000 }],
|
||||
);
|
||||
|
||||
const r = getRealBytesStats({ sessionId: sid, sessionsDir: dir });
|
||||
|
||||
// 8_000 + 2_000 from tool_calls — the bytes that actually entered context.
|
||||
expect(r.bytesReturned).toBe(10_000);
|
||||
expect(r.bytesAvoided).toBe(90_000);
|
||||
});
|
||||
|
||||
test("8.5 lifetime tier: omitting sessionId aggregates every session in sessionsDir", () => {
|
||||
const dir = mkSessionsDir();
|
||||
const sidA = `lifeA-${randomUUID()}`;
|
||||
|
||||
Reference in New Issue
Block a user