chapterhouse 0.3.3 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/server.js +2 -2
- package/dist/store/db.js +23 -1
- package/dist/store/db.test.js +43 -0
- package/package.json +1 -1
package/dist/api/server.js
CHANGED
|
@@ -19,7 +19,7 @@ import { withWikiWrite } from "../wiki/lock.js";
|
|
|
19
19
|
import { listSkills, removeSkill } from "../copilot/skills.js";
|
|
20
20
|
import { restartDaemon } from "../daemon.js";
|
|
21
21
|
import { API_TOKEN_PATH, resolveWikiRelativePath } from "../paths.js";
|
|
22
|
-
import { getDb, getSessionMessages, getTaskEvents } from "../store/db.js";
|
|
22
|
+
import { getDb, getSessionMessages, getTaskEvents, normalizeSqliteTsToIso } from "../store/db.js";
|
|
23
23
|
import { getStatus, onStatusChange } from "../status.js";
|
|
24
24
|
import { formatSseData, formatSseEvent } from "./sse.js";
|
|
25
25
|
import { syncDecisionsFileToWiki } from "../squad/mirror.js";
|
|
@@ -614,7 +614,7 @@ app.get("/api/projects", (_req, res) => {
|
|
|
614
614
|
squadDir: r.squad_dir,
|
|
615
615
|
// Count from live filesystem — authoritative per Squad SDK rule: repo files win over cache.
|
|
616
616
|
agentCount: countAgentsOnDisk(r.project_root),
|
|
617
|
-
loadedAt: r.loaded_at,
|
|
617
|
+
loadedAt: normalizeSqliteTsToIso(r.loaded_at),
|
|
618
618
|
lastUsedAt: r.last_used_at != null ? new Date(r.last_used_at).toISOString() : undefined,
|
|
619
619
|
})));
|
|
620
620
|
});
|
package/dist/store/db.js
CHANGED
|
@@ -307,6 +307,28 @@ export function getRecentConversation(limit, sessionKey) {
|
|
|
307
307
|
}
|
|
308
308
|
const MAX_SESSION_MESSAGES_LIMIT = 500;
|
|
309
309
|
const DEFAULT_SESSION_MESSAGES_LIMIT = 100;
|
|
310
|
+
/**
|
|
311
|
+
* Normalize a SQLite CURRENT_TIMESTAMP string to a proper ISO-8601 UTC string.
|
|
312
|
+
*
|
|
313
|
+
* SQLite stores CURRENT_TIMESTAMP as "YYYY-MM-DD HH:MM:SS" — no timezone
|
|
314
|
+
* marker, space separator. Browsers that receive this bare string may parse it
|
|
315
|
+
* as *local* time instead of UTC, shifting every displayed timestamp by the
|
|
316
|
+
* user's UTC offset. This helper appends the `Z` suffix (and replaces the space
|
|
317
|
+
* separator with `T`) so that `new Date(ts)` always parses as UTC.
|
|
318
|
+
*
|
|
319
|
+
* - Already-ISO strings (containing `T`) are returned unchanged (idempotent).
|
|
320
|
+
* - Strings with a `T` but no `Z` are left as-is; they are already ISO format
|
|
321
|
+
* and the caller is responsible for any timezone semantics (we do not blindly
|
|
322
|
+
* append `Z` and risk double-shifting a value that might already be local).
|
|
323
|
+
* - Falsy / empty input is returned as an empty string rather than throwing.
|
|
324
|
+
*/
|
|
325
|
+
export function normalizeSqliteTsToIso(ts) {
|
|
326
|
+
if (!ts)
|
|
327
|
+
return "";
|
|
328
|
+
if (ts.includes("T"))
|
|
329
|
+
return ts;
|
|
330
|
+
return ts.replace(" ", "T") + "Z";
|
|
331
|
+
}
|
|
310
332
|
/**
|
|
311
333
|
* Return conversation_log rows for a specific session as structured JSON,
|
|
312
334
|
* suitable for seeding the frontend Zustand store on mount.
|
|
@@ -328,7 +350,7 @@ export function getSessionMessages(sessionKey, limit) {
|
|
|
328
350
|
return rows.map((r) => ({
|
|
329
351
|
role: r.role,
|
|
330
352
|
content: r.content,
|
|
331
|
-
ts: r.ts,
|
|
353
|
+
ts: normalizeSqliteTsToIso(r.ts),
|
|
332
354
|
}));
|
|
333
355
|
}
|
|
334
356
|
/**
|
package/dist/store/db.test.js
CHANGED
|
@@ -280,4 +280,47 @@ test("#86: agent_task_events table exists in schema after getDb()", async () =>
|
|
|
280
280
|
dbModule.closeDb();
|
|
281
281
|
}
|
|
282
282
|
});
|
|
283
|
+
// ---------------------------------------------------------------------------
|
|
284
|
+
// normalizeSqliteTsToIso — unit tests
|
|
285
|
+
// ---------------------------------------------------------------------------
|
|
286
|
+
test("normalizeSqliteTsToIso converts SQLite space-separated UTC string to ISO-8601", async () => {
|
|
287
|
+
const dbModule = await loadDbModule();
|
|
288
|
+
assert.equal(dbModule.normalizeSqliteTsToIso("2026-05-09 00:54:31"), "2026-05-09T00:54:31Z", "space-separated string should become T-separated with Z suffix");
|
|
289
|
+
});
|
|
290
|
+
test("normalizeSqliteTsToIso is idempotent for already-ISO strings ending with Z", async () => {
|
|
291
|
+
const dbModule = await loadDbModule();
|
|
292
|
+
assert.equal(dbModule.normalizeSqliteTsToIso("2026-05-09T00:54:31Z"), "2026-05-09T00:54:31Z", "already-ISO string should be returned unchanged");
|
|
293
|
+
});
|
|
294
|
+
test("normalizeSqliteTsToIso leaves T-but-no-Z strings untouched", async () => {
|
|
295
|
+
// Strings that already contain T are considered already in ISO format.
|
|
296
|
+
// We do NOT append Z because the timezone semantics are unknown — the caller
|
|
297
|
+
// is responsible. Appending Z blindly could double-shift a local-time value.
|
|
298
|
+
const dbModule = await loadDbModule();
|
|
299
|
+
assert.equal(dbModule.normalizeSqliteTsToIso("2026-05-09T00:54:31"), "2026-05-09T00:54:31", "T-but-no-Z string should be left as-is");
|
|
300
|
+
});
|
|
301
|
+
test("normalizeSqliteTsToIso handles empty and nullish input gracefully", async () => {
|
|
302
|
+
const dbModule = await loadDbModule();
|
|
303
|
+
assert.equal(dbModule.normalizeSqliteTsToIso(""), "", "empty string → empty string");
|
|
304
|
+
assert.equal(dbModule.normalizeSqliteTsToIso(null), "", "null → empty string");
|
|
305
|
+
assert.equal(dbModule.normalizeSqliteTsToIso(undefined), "", "undefined → empty string");
|
|
306
|
+
});
|
|
307
|
+
// ---------------------------------------------------------------------------
|
|
308
|
+
// getSessionMessages — integration: ts field is normalized to ISO UTC
|
|
309
|
+
// ---------------------------------------------------------------------------
|
|
310
|
+
test("getSessionMessages returns ts values normalized to ISO-8601 UTC (ends with Z)", async () => {
|
|
311
|
+
const dbModule = await loadDbModule();
|
|
312
|
+
try {
|
|
313
|
+
const db = dbModule.getDb();
|
|
314
|
+
// Insert a row with a bare SQLite-format timestamp (no T, no Z)
|
|
315
|
+
db.prepare(`INSERT INTO conversation_log (role, content, source, session_key, ts)
|
|
316
|
+
VALUES ('user', 'timezone test', 'web', 'tz-session', '2026-05-09 01:23:45')`).run();
|
|
317
|
+
const messages = dbModule.getSessionMessages("tz-session");
|
|
318
|
+
assert.equal(messages.length, 1, "should return the inserted row");
|
|
319
|
+
assert.equal(messages[0].ts, "2026-05-09T01:23:45Z", "ts must be normalized to ISO-8601 UTC");
|
|
320
|
+
assert.ok(messages[0].ts.endsWith("Z"), "ts must end with Z");
|
|
321
|
+
}
|
|
322
|
+
finally {
|
|
323
|
+
dbModule.closeDb();
|
|
324
|
+
}
|
|
325
|
+
});
|
|
283
326
|
//# sourceMappingURL=db.test.js.map
|
package/package.json
CHANGED