mirror of
https://github.com/mksglu/context-mode.git
synced 2026-10-02 04:14:38 +08:00
fix(pi): parse session age timestamps as UTC (#488)
Co-authored-by: s010mn <s010mn@users.noreply.github.com>
This commit is contained in:
@@ -151,6 +151,22 @@ function deriveSessionId(ctx: Record<string, unknown>): string {
|
||||
return `pi-${Date.now()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse SessionDB timestamps as UTC. SQLite datetime('now') returns
|
||||
* "YYYY-MM-DD HH:MM:SS" in UTC without a timezone suffix; JavaScript parses
|
||||
* that shape as local time, which skews ages by the local UTC offset.
|
||||
*/
|
||||
function parseSessionTimestampMs(value: string): number {
|
||||
const trimmed = value.trim();
|
||||
const sqliteUtc = trimmed.match(
|
||||
/^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2})(\.\d+)?$/,
|
||||
);
|
||||
const normalized = sqliteUtc
|
||||
? `${sqliteUtc[1]}T${sqliteUtc[2]}${sqliteUtc[3] ?? ""}Z`
|
||||
: trimmed;
|
||||
return Date.parse(normalized);
|
||||
}
|
||||
|
||||
/** Build stats text for the /ctx-stats command. */
|
||||
function buildStatsText(db: SessionDB, sessionId: string): string {
|
||||
try {
|
||||
@@ -179,9 +195,11 @@ function buildStatsText(db: SessionDB, sessionId: string): string {
|
||||
|
||||
// Session age
|
||||
if (stats?.started_at) {
|
||||
const startedMs = new Date(stats.started_at).getTime();
|
||||
const ageMinutes = Math.round((Date.now() - startedMs) / 60_000);
|
||||
lines.push(`- Session age: ${ageMinutes}m`);
|
||||
const startedMs = parseSessionTimestampMs(stats.started_at);
|
||||
if (Number.isFinite(startedMs)) {
|
||||
const ageMinutes = Math.round((Date.now() - startedMs) / 60_000);
|
||||
lines.push(`- Session age: ${ageMinutes}m`);
|
||||
}
|
||||
}
|
||||
|
||||
return lines.join("\n");
|
||||
|
||||
@@ -21,6 +21,7 @@ import { createHash } from "node:crypto";
|
||||
import { mkdtempSync, rmSync, mkdirSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import { SessionDB } from "../src/session/db.js";
|
||||
|
||||
// ── Mock Pi API ──────────────────────────────────────────────
|
||||
|
||||
@@ -534,6 +535,63 @@ describe("Pi Extension", () => {
|
||||
const result = await cmd!.handler!({});
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
it("/ctx-stats treats SQLite started_at as UTC", async () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date("2026-05-09T12:30:00Z"));
|
||||
|
||||
const originalTZ = process.env.TZ;
|
||||
process.env.TZ = "America/Los_Angeles";
|
||||
|
||||
const sessionFile = join(tempDir, "stats-utc-session.jsonl");
|
||||
const sessionId = createHash("sha256")
|
||||
.update(sessionFile)
|
||||
.digest("hex")
|
||||
.slice(0, 16);
|
||||
|
||||
try {
|
||||
await registerPiExtension(api);
|
||||
await api._trigger(
|
||||
"session_start",
|
||||
{ type: "session_start", reason: "startup" },
|
||||
{ sessionManager: { getSessionFile: () => sessionFile } },
|
||||
);
|
||||
|
||||
const dbPath = join(
|
||||
process.env.HOME!,
|
||||
".pi",
|
||||
"context-mode",
|
||||
"sessions",
|
||||
"context-mode.db",
|
||||
);
|
||||
const db = new SessionDB({ dbPath });
|
||||
try {
|
||||
// SQLite datetime('now') stores UTC as "YYYY-MM-DD HH:MM:SS".
|
||||
// If parsed as local time in America/Los_Angeles, this would be
|
||||
// 2026-05-09T19:00:00Z and the age would not be 30 minutes.
|
||||
db.db
|
||||
.prepare(
|
||||
"UPDATE session_meta SET started_at = ? WHERE session_id = ?",
|
||||
)
|
||||
.run("2026-05-09 12:00:00", sessionId);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
|
||||
const result = await api._getCommand("ctx-stats")!.handler!({});
|
||||
|
||||
expect((result as { text: string }).text).toContain(
|
||||
"- Session age: 30m",
|
||||
);
|
||||
} finally {
|
||||
if (originalTZ === undefined) {
|
||||
delete process.env.TZ;
|
||||
} else {
|
||||
process.env.TZ = originalTZ;
|
||||
}
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
Reference in New Issue
Block a user