Fix new-window context leak showing previous session ctx%

Stop tmux status from falling back to global status files, seed new
tmux instances at 0%, rank live sessions by opened_at instead of
signals.json keepalive mtime, and bind Grok pids to tmux more reliably.
This commit is contained in:
2026-07-17 16:53:41 +08:00
parent 9225922a10
commit ed1a1ade45
10 changed files with 299 additions and 28 deletions
+89
View File
@@ -62,6 +62,95 @@ describe("dashboard", () => {
fs.rmSync(tmp, { recursive: true, force: true });
});
it("new window: old live session with fresher signals mtime does not steal primary", async () => {
// Repro: old tab still live and signals.json keeps updating; new tab has
// later opened_at but no/low context — must pick new (ctx ~0), not old 78%.
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "hud-newwin-"));
const cwd = "/Users/dex/AI FILM SPACE/0717";
const oldId = "old-busy-78";
const newId = "new-window-zero";
const enc = encodeURIComponent(cwd);
const oldDir = path.join(tmp, "sessions", enc, oldId);
const newDir = path.join(tmp, "sessions", enc, newId);
fs.mkdirSync(oldDir, { recursive: true });
fs.mkdirSync(newDir, { recursive: true });
const oldSignals = {
contextWindowUsage: 78,
contextTokensUsed: 392180,
contextWindowTokens: 500000,
turnCount: 18,
toolCallCount: 344,
primaryModelId: "grok-4.5",
};
fs.writeFileSync(path.join(oldDir, "signals.json"), JSON.stringify(oldSignals));
fs.writeFileSync(
path.join(oldDir, "summary.json"),
JSON.stringify({
info: { id: oldId, cwd },
last_active_at: "2026-07-17T06:00:00.000Z",
updated_at: "2026-07-17T06:00:00.000Z",
created_at: "2026-07-17T04:00:00.000Z",
current_model_id: "grok-4.5",
}),
);
// Touch signals again so mtime is "now" (old session keepalive)
const now = Date.now();
fs.utimesSync(path.join(oldDir, "signals.json"), new Date(now), new Date(now));
fs.writeFileSync(
path.join(newDir, "summary.json"),
JSON.stringify({
info: { id: newId, cwd },
last_active_at: "2026-07-17T08:00:00.000Z",
updated_at: "2026-07-17T08:00:00.000Z",
created_at: "2026-07-17T08:00:00.000Z",
current_model_id: "grok-4.5",
}),
);
// Explicit empty/low context for new window
fs.writeFileSync(
path.join(newDir, "signals.json"),
JSON.stringify({
contextWindowUsage: 0,
contextTokensUsed: 0,
contextWindowTokens: 500000,
turnCount: 0,
toolCallCount: 0,
primaryModelId: "grok-4.5",
}),
);
fs.writeFileSync(
path.join(tmp, "active_sessions.json"),
JSON.stringify([
{
session_id: oldId,
pid: process.pid,
cwd,
opened_at: "2026-07-17T04:00:00.000Z",
},
{
session_id: newId,
pid: process.pid,
cwd,
opened_at: "2026-07-17T08:00:00.000Z",
},
]),
);
const r = await refreshDashboard({ grokHome: tmp, noUsage: true });
assert.ok(r.session);
assert.equal(r.session!.sessionId, newId);
assert.ok(
Math.round(r.session!.contextPercent) < 5,
`new window must be ~0% ctx, got ${r.session!.contextPercent}`,
);
assert.doesNotMatch(r.title, /78%/);
fs.rmSync(tmp, { recursive: true, force: true });
});
it("after /new: prefers newest active session, not first-listed old 78%", async () => {
// Repro: active_sessions keeps old tab first; new tab second with low/no signals.
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "hud-new-"));
+22
View File
@@ -1,11 +1,14 @@
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import os from "node:os";
import path from "node:path";
import {
uniqueTmuxSessionName,
sanitizeId,
isInProcessTree,
parentPid,
} from "../src/multi-session.js";
import { tmuxStatusCommands } from "../src/tmux-hud.js";
describe("multi-session isolation", () => {
it("uniqueTmuxSessionName never returns fixed grok-hud", () => {
@@ -48,4 +51,23 @@ describe("multi-session isolation", () => {
assert.equal(isInProcessTree(process.pid, pp), true);
}
});
it("tmux status format never falls back to global status (new window isolation)", () => {
const home = path.join(os.tmpdir(), "hud-fake-home");
const cmds = tmuxStatusCommands(home);
const formats = cmds
.filter((a) => a[0] === "set" && String(a[2] ?? "").startsWith("status-format"))
.map((a) => a.slice(3).join(" "));
assert.ok(formats.length >= 1);
for (const f of formats) {
// Must only read per-session path; global fallback leaked old ctx%
assert.match(f, /hud\/tmux\/#\{session_name\}/);
assert.doesNotMatch(
f,
/hud\/tmux-lines\.txt|hud\/tmux-status\.txt(?!\/)/,
);
// No "|| cat …/hud/tmux-status" style global fallback
assert.doesNotMatch(f, /\|\|\s*cat\s+\S*\/hud\/tmux-/);
}
});
});