@wrongstack/core 0.306.4 → 0.307.1

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.
Files changed (113) hide show
  1. package/dist/chronicle/index.js +957 -953
  2. package/dist/chronicle/metrics-ingest.d.ts +55 -0
  3. package/dist/chronicle/metrics-schema.d.ts +86 -0
  4. package/dist/chronicle/metrics-store.d.ts +5 -120
  5. package/dist/chronicle/project-server.js +1083 -1084
  6. package/dist/chronicle/sqlite-journal-quota.d.ts +39 -0
  7. package/dist/chronicle/sqlite-journal-schema.d.ts +25 -0
  8. package/dist/chronicle/sqlite-journal.d.ts +12 -177
  9. package/dist/coordination/agents/index.js +1803 -1701
  10. package/dist/coordination/agents/project-agent-capture.d.ts +48 -0
  11. package/dist/coordination/agents/project-agent-identity.d.ts +3 -129
  12. package/dist/coordination/agents/project-agent-roster.d.ts +17 -0
  13. package/dist/coordination/agents/types.d.ts +6 -9
  14. package/dist/coordination/index.js +6051 -5880
  15. package/dist/coordination/mail-tools.d.ts +1 -1
  16. package/dist/coordination/mailbox-http-actor-query.d.ts +16 -0
  17. package/dist/coordination/mailbox-http-router.d.ts +0 -48
  18. package/dist/coordination/mailbox-http-sse.d.ts +12 -0
  19. package/dist/coordination/mailbox-message-types.d.ts +71 -0
  20. package/dist/coordination/mailbox-predicates.d.ts +13 -0
  21. package/dist/coordination/mailbox-project-server.js +1 -1
  22. package/dist/coordination/mailbox-session-sync.d.ts +8 -0
  23. package/dist/coordination/mailbox-types.d.ts +6 -793
  24. package/dist/core/agent-loop-context.d.ts +28 -0
  25. package/dist/core/agent-loop-detector.d.ts +25 -0
  26. package/dist/core/fallback-doctor.d.ts +72 -0
  27. package/dist/core/index.d.ts +1 -0
  28. package/dist/core/index.js +1859 -1335
  29. package/dist/defaults/index.js +3647 -3559
  30. package/dist/execution/autonomy-brain-llm.d.ts +55 -0
  31. package/dist/execution/autonomy-brain.d.ts +3 -156
  32. package/dist/execution/compaction-budget.d.ts +38 -0
  33. package/dist/execution/compaction-core.d.ts +3 -170
  34. package/dist/execution/compaction-elision.d.ts +58 -0
  35. package/dist/execution/council-orchestrator-helpers.d.ts +39 -0
  36. package/dist/execution/council-orchestrator.d.ts +3 -42
  37. package/dist/execution/council-response-parser.d.ts +56 -0
  38. package/dist/execution/index.js +2475 -2449
  39. package/dist/execution/prompt-enhancer.js +11 -3
  40. package/dist/execution/tool-executor-guard.d.ts +18 -0
  41. package/dist/execution/tool-executor-runner.d.ts +12 -0
  42. package/dist/execution/tool-executor.d.ts +0 -75
  43. package/dist/goal/index.js +19 -6
  44. package/dist/hq/index.js +8 -2
  45. package/dist/index.d.ts +1 -0
  46. package/dist/index.js +8021 -7405
  47. package/dist/infrastructure/index.js +3 -1
  48. package/dist/kernel/events/agent-events.d.ts +2 -0
  49. package/dist/kernel/events/provider-events.d.ts +13 -0
  50. package/dist/plugin/index.js +2590 -2561
  51. package/dist/plugins/auto-review-config.d.ts +53 -0
  52. package/dist/plugins/auto-review-git.d.ts +19 -0
  53. package/dist/plugins/auto-review-plugin.d.ts +2 -140
  54. package/dist/plugins/review-finding-verification.d.ts +7 -0
  55. package/dist/prompts/index.d.ts +1 -0
  56. package/dist/prompts/index.js +601 -0
  57. package/dist/prompts/prompt-journal.d.ts +103 -0
  58. package/dist/security/index.js +627 -652
  59. package/dist/security/permission-explain.d.ts +19 -0
  60. package/dist/security/permission-policy.d.ts +0 -101
  61. package/dist/security/yolo-risk.d.ts +5 -4
  62. package/dist/session-catalog/index.js +220 -212
  63. package/dist/session-catalog/project-server.js +227 -219
  64. package/dist/session-catalog/store-rebuild.d.ts +9 -0
  65. package/dist/session-catalog/store-schema.d.ts +48 -0
  66. package/dist/session-catalog/store.d.ts +0 -17
  67. package/dist/storage/director-state.d.ts +6 -0
  68. package/dist/storage/file-session-writer.d.ts +2 -58
  69. package/dist/storage/index.d.ts +2 -1
  70. package/dist/storage/index.js +2057 -1925
  71. package/dist/storage/orphan-lock-cleaner.d.ts +15 -0
  72. package/dist/storage/queue-store.d.ts +6 -0
  73. package/dist/storage/session-recovery.d.ts +9 -0
  74. package/dist/storage/session-store/rename-session.d.ts +16 -0
  75. package/dist/storage/session-store/resume-session.d.ts +26 -0
  76. package/dist/storage/session-store/session-store-clear.d.ts +11 -0
  77. package/dist/storage/session-store/session-store-index.d.ts +13 -0
  78. package/dist/storage/session-store.d.ts +1 -129
  79. package/dist/storage/session-summary-tracker.d.ts +39 -0
  80. package/dist/storage/session-write-buffer.d.ts +43 -0
  81. package/dist/storage/session-writer/session-writer-flush.d.ts +4 -0
  82. package/dist/storage/session-writer/session-writer-summary-tracker.d.ts +28 -0
  83. package/dist/tools/fallback-agent-model-assign-tool.d.ts +18 -0
  84. package/dist/tools/fallback-leader-model-set-tool.d.ts +18 -0
  85. package/dist/tools/fallback-manage-tools.d.ts +8 -45
  86. package/dist/tools/fallback-profile-manage-tool.d.ts +16 -0
  87. package/dist/tools/fallback-provider-key-set-tool.d.ts +17 -0
  88. package/dist/tools/fallback-provider-manage-tool.d.ts +26 -0
  89. package/dist/tools/index.js +5124 -5071
  90. package/dist/types/config/root.d.ts +7 -2
  91. package/dist/types/default-config.d.ts +1 -1
  92. package/dist/types/index.d.ts +54 -55
  93. package/dist/types/index.js +953 -932
  94. package/dist/types/runtime-capability-manifest.d.ts +5 -5
  95. package/dist/types/session-markers.d.ts +12 -0
  96. package/dist/types/session-markers.js +19 -0
  97. package/dist/worktree/worktree-manager.d.ts +1 -1
  98. package/instructions/agents/architect.md +2 -0
  99. package/instructions/agents/code-reviewer.md +1 -0
  100. package/instructions/agents/debugger.md +2 -0
  101. package/instructions/agents/executor.md +2 -0
  102. package/instructions/agents/explore.md +3 -1
  103. package/instructions/agents/refactor.md +2 -0
  104. package/instructions/agents/reviewer.md +1 -0
  105. package/instructions/coordination/subagent-baseline.md +68 -7
  106. package/instructions/llm/prompt-enhancer.md +2 -1
  107. package/instructions/sections/tool/common-patterns.md +30 -0
  108. package/instructions/sections/tool/delegation-compact.md +3 -1
  109. package/instructions/sections/tool/delegation-full.md +5 -1
  110. package/instructions/system-lite.md +32 -4
  111. package/instructions/system-pro.md +63 -9
  112. package/instructions/system.md +56 -11
  113. package/package.json +9 -5
@@ -735,9 +735,9 @@ function hasProjectSessionRegistry(globalRoot) {
735
735
  }
736
736
 
737
737
  // src/session-catalog/store.ts
738
- import { createHash as createHash2, randomBytes, randomUUID as randomUUID2, timingSafeEqual } from "node:crypto";
739
- import * as fs4 from "node:fs";
740
- import * as path4 from "node:path";
738
+ import { randomBytes, randomUUID as randomUUID2 } from "node:crypto";
739
+ import * as fs5 from "node:fs";
740
+ import * as path5 from "node:path";
741
741
 
742
742
  // src/coordination/sqlite-mailbox-schema.ts
743
743
  import { createRequire } from "node:module";
@@ -1233,6 +1233,10 @@ function isPidAlive(pid) {
1233
1233
  }
1234
1234
  }
1235
1235
 
1236
+ // src/session-catalog/store-rebuild.ts
1237
+ import * as fs4 from "node:fs";
1238
+ import * as path4 from "node:path";
1239
+
1236
1240
  // src/utils/session-scoped-path.ts
1237
1241
  var SESSION_SIDECAR_JSONL_SUFFIXES = [
1238
1242
  ".replay.jsonl",
@@ -1249,7 +1253,8 @@ function isSessionTranscriptFileName(name) {
1249
1253
  return !SESSION_SIDECAR_JSONL_SUFFIXES.some((suffix) => name.endsWith(suffix));
1250
1254
  }
1251
1255
 
1252
- // src/session-catalog/store.ts
1256
+ // src/session-catalog/store-schema.ts
1257
+ import { createHash as createHash2, timingSafeEqual } from "node:crypto";
1253
1258
  var SCHEMA_VERSION = 1;
1254
1259
  var MAX_LEASE_MS = 12e4;
1255
1260
  var MAX_RESERVATION_MS = 6e4;
@@ -1294,12 +1299,192 @@ function boundPresenceValue(value, depth) {
1294
1299
  }
1295
1300
  return result;
1296
1301
  }
1302
+ function configureCatalogDatabase(db) {
1303
+ db.exec(
1304
+ "PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON; PRAGMA busy_timeout=5000; PRAGMA synchronous=NORMAL;"
1305
+ );
1306
+ }
1307
+ function initializeCatalogSchema(db) {
1308
+ db.exec(`
1309
+ CREATE TABLE IF NOT EXISTS catalog_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
1310
+ CREATE TABLE IF NOT EXISTS sessions (
1311
+ session_id TEXT PRIMARY KEY,
1312
+ transcript_relative_path TEXT NOT NULL,
1313
+ summary_relative_path TEXT NOT NULL,
1314
+ summary_json TEXT NOT NULL,
1315
+ transcript_size INTEGER NOT NULL DEFAULT 0,
1316
+ transcript_mtime_ms REAL NOT NULL DEFAULT 0,
1317
+ summary_revision INTEGER NOT NULL DEFAULT 1,
1318
+ indexed_at TEXT NOT NULL,
1319
+ damaged INTEGER NOT NULL DEFAULT 0
1320
+ );
1321
+ CREATE TABLE IF NOT EXISTS session_leases (
1322
+ session_id TEXT PRIMARY KEY,
1323
+ lease_id TEXT NOT NULL UNIQUE,
1324
+ lease_secret_hash TEXT NOT NULL,
1325
+ owner_instance_id TEXT NOT NULL,
1326
+ owner_pid INTEGER NOT NULL,
1327
+ owner_started_at TEXT NOT NULL,
1328
+ entry_json TEXT NOT NULL,
1329
+ agent_revision INTEGER NOT NULL DEFAULT 0,
1330
+ status TEXT NOT NULL,
1331
+ last_heartbeat_at INTEGER NOT NULL,
1332
+ lease_expires_at INTEGER NOT NULL
1333
+ );
1334
+ CREATE TABLE IF NOT EXISTS resume_reservations (
1335
+ reservation_id TEXT PRIMARY KEY,
1336
+ target_session_id TEXT NOT NULL UNIQUE,
1337
+ requester_instance_id TEXT NOT NULL,
1338
+ current_session_id TEXT,
1339
+ created_at INTEGER NOT NULL,
1340
+ expires_at INTEGER NOT NULL
1341
+ );
1342
+ CREATE TABLE IF NOT EXISTS maintenance_leases (
1343
+ session_id TEXT PRIMARY KEY,
1344
+ operation TEXT NOT NULL,
1345
+ holder_id TEXT NOT NULL,
1346
+ lease_id TEXT NOT NULL UNIQUE,
1347
+ acquired_at INTEGER NOT NULL,
1348
+ expires_at INTEGER NOT NULL
1349
+ );
1350
+ CREATE INDEX IF NOT EXISTS idx_sessions_activity ON sessions(indexed_at DESC);
1351
+ CREATE INDEX IF NOT EXISTS idx_leases_expiry ON session_leases(lease_expires_at);
1352
+ CREATE INDEX IF NOT EXISTS idx_reservations_expiry ON resume_reservations(expires_at);
1353
+ CREATE INDEX IF NOT EXISTS idx_maintenance_expiry ON maintenance_leases(expires_at);
1354
+ `);
1355
+ db.prepare("INSERT INTO catalog_meta(key,value) VALUES (?,?) ON CONFLICT(key) DO NOTHING").run("schema_version", String(SCHEMA_VERSION));
1356
+ db.prepare("INSERT INTO catalog_meta(key,value) VALUES (?,?) ON CONFLICT(key) DO NOTHING").run("generation", "0");
1357
+ const row = db.prepare("SELECT value FROM catalog_meta WHERE key=?").get("schema_version");
1358
+ if (Number(row.value) !== SCHEMA_VERSION)
1359
+ throw new Error(`Unsupported session catalog schema ${row.value}`);
1360
+ }
1361
+
1362
+ // src/session-catalog/store-rebuild.ts
1363
+ function walkSessionFiles(root, suffix) {
1364
+ const result = [];
1365
+ const visit = (dir) => {
1366
+ for (const entry of fs4.readdirSync(dir, { withFileTypes: true })) {
1367
+ if (entry.isDirectory()) {
1368
+ if (entry.name !== "_cas" && entry.name !== "_trash") visit(path4.join(dir, entry.name));
1369
+ } else if (entry.isFile() && entry.name.endsWith(suffix))
1370
+ result.push(path4.join(dir, entry.name));
1371
+ }
1372
+ };
1373
+ visit(root);
1374
+ return result;
1375
+ }
1376
+ function summarizeTranscriptFile(transcriptFilePath, id) {
1377
+ const lines = fs4.readFileSync(transcriptFilePath, "utf8").split(/\r?\n/);
1378
+ let start;
1379
+ let endedAt;
1380
+ let lastActivityAt;
1381
+ let messageCount = 0;
1382
+ let iterationCount = 0;
1383
+ let toolCallCount = 0;
1384
+ let compactionCount = 0;
1385
+ let tokenTotal = 0;
1386
+ for (const line of lines) {
1387
+ if (!line) continue;
1388
+ let event;
1389
+ try {
1390
+ event = parseJson(line);
1391
+ } catch {
1392
+ continue;
1393
+ }
1394
+ lastActivityAt = event.ts;
1395
+ if (event.type === "session_start") start = event;
1396
+ if (event.type === "session_end") endedAt = event.ts;
1397
+ if (event.type === "message_appended" && (event.message.role === "user" || event.message.role === "assistant"))
1398
+ messageCount++;
1399
+ if (event.type === "llm_response") {
1400
+ iterationCount++;
1401
+ tokenTotal += event.usage.input + event.usage.output + (event.usage.cacheRead ?? 0) + (event.usage.cacheWrite ?? 0);
1402
+ }
1403
+ if (event.type === "tool_call_end") toolCallCount++;
1404
+ if (event.type === "compaction") compactionCount++;
1405
+ }
1406
+ if (!start) throw new Error("missing session_start");
1407
+ return {
1408
+ id,
1409
+ title: id,
1410
+ startedAt: start.ts,
1411
+ ...endedAt ? { endedAt } : {},
1412
+ model: start.model,
1413
+ provider: start.provider,
1414
+ tokenTotal,
1415
+ ...lastActivityAt ? { lastActivityAt } : {},
1416
+ messageCount,
1417
+ iterationCount,
1418
+ toolCallCount,
1419
+ compactionCount
1420
+ };
1421
+ }
1422
+ function rebuildCatalogIndex(db, sessionsDir, containedPathFn, transactionFn, bumpGenerationFn) {
1423
+ const summaries = walkSessionFiles(sessionsDir, ".summary.json");
1424
+ const transcripts = walkSessionFiles(sessionsDir, ".jsonl").filter(
1425
+ (file) => isSessionTranscriptFileName(path4.basename(file))
1426
+ );
1427
+ const ids = /* @__PURE__ */ new Set();
1428
+ for (const file of [...summaries, ...transcripts]) {
1429
+ const relative2 = path4.relative(sessionsDir, file).replaceAll("\\", "/");
1430
+ ids.add(relative2.replace(/\.summary\.json$|\.jsonl$/, ""));
1431
+ }
1432
+ let indexed = 0;
1433
+ let damaged = 0;
1434
+ transactionFn(() => {
1435
+ db.prepare("DELETE FROM sessions").run();
1436
+ for (const id of ids) {
1437
+ try {
1438
+ const summaryFile = containedPathFn(`${id}.summary.json`);
1439
+ const summary = fs4.existsSync(summaryFile) ? parseJson(fs4.readFileSync(summaryFile, "utf8")) : summarizeTranscriptFile(containedPathFn(`${id}.jsonl`), id);
1440
+ if (!summary || summary.id !== id) throw new Error("summary identity mismatch");
1441
+ const transcript = containedPathFn(`${id}.jsonl`);
1442
+ const stat = fs4.existsSync(transcript) ? fs4.statSync(transcript) : void 0;
1443
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1444
+ db.prepare(
1445
+ "INSERT INTO sessions(session_id,transcript_relative_path,summary_relative_path,summary_json,transcript_size,transcript_mtime_ms,summary_revision,indexed_at,damaged) VALUES (?,?,?,?,?,?,?,?,0)"
1446
+ ).run(
1447
+ id,
1448
+ `${id}.jsonl`,
1449
+ `${id}.summary.json`,
1450
+ JSON.stringify(summary),
1451
+ stat?.size ?? 0,
1452
+ stat?.mtimeMs ?? 0,
1453
+ 1,
1454
+ now
1455
+ );
1456
+ indexed++;
1457
+ } catch {
1458
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1459
+ const fallback = {
1460
+ id,
1461
+ title: id,
1462
+ startedAt: now,
1463
+ model: "",
1464
+ provider: "",
1465
+ tokenTotal: 0
1466
+ };
1467
+ db.prepare(
1468
+ "INSERT INTO sessions(session_id,transcript_relative_path,summary_relative_path,summary_json,summary_revision,indexed_at,damaged) VALUES (?,?,?,?,1,?,1)"
1469
+ ).run(id, `${id}.jsonl`, `${id}.summary.json`, JSON.stringify(fallback), now);
1470
+ damaged++;
1471
+ }
1472
+ }
1473
+ db.prepare(
1474
+ "INSERT INTO catalog_meta(key,value) VALUES ('last_reconciliation',?) ON CONFLICT(key) DO UPDATE SET value=excluded.value"
1475
+ ).run((/* @__PURE__ */ new Date()).toISOString());
1476
+ bumpGenerationFn();
1477
+ });
1478
+ return { indexed, damaged };
1479
+ }
1480
+
1481
+ // src/session-catalog/store.ts
1297
1482
  var SessionCatalogStore = class {
1298
1483
  constructor(projectDir) {
1299
1484
  this.projectDir = projectDir;
1300
- this.sessionsDir = path4.join(projectDir, "sessions");
1301
- fs4.mkdirSync(this.sessionsDir, { recursive: true, mode: 448 });
1302
- this.databasePath = path4.join(this.sessionsDir, "catalog.sqlite");
1485
+ this.sessionsDir = path5.join(projectDir, "sessions");
1486
+ fs5.mkdirSync(this.sessionsDir, { recursive: true, mode: 448 });
1487
+ this.databasePath = path5.join(this.sessionsDir, "catalog.sqlite");
1303
1488
  const Database = loadDatabaseSync();
1304
1489
  this.db = new Database(this.databasePath);
1305
1490
  try {
@@ -1316,12 +1501,12 @@ var SessionCatalogStore = class {
1316
1501
  this.db.close();
1317
1502
  const quarantine = `${this.databasePath}.corrupt-${Date.now()}`;
1318
1503
  try {
1319
- fs4.renameSync(this.databasePath, quarantine);
1504
+ fs5.renameSync(this.databasePath, quarantine);
1320
1505
  } catch {
1321
1506
  }
1322
1507
  for (const suffix of ["-wal", "-shm"]) {
1323
1508
  try {
1324
- fs4.renameSync(`${this.databasePath}${suffix}`, `${quarantine}${suffix}`);
1509
+ fs5.renameSync(`${this.databasePath}${suffix}`, `${quarantine}${suffix}`);
1325
1510
  } catch {
1326
1511
  }
1327
1512
  }
@@ -1333,7 +1518,7 @@ var SessionCatalogStore = class {
1333
1518
  const rowCount = Number(
1334
1519
  this.db.prepare("SELECT COUNT(*) AS count FROM sessions").get().count
1335
1520
  );
1336
- if (rowCount === 0 && this.walkFiles(this.sessionsDir, ".jsonl").some((file) => !file.endsWith("_index.jsonl"))) {
1521
+ if (rowCount === 0 && walkSessionFiles(this.sessionsDir, ".jsonl").some((file) => !file.endsWith("_index.jsonl"))) {
1337
1522
  this.rebuildCatalog();
1338
1523
  }
1339
1524
  }
@@ -1343,66 +1528,13 @@ var SessionCatalogStore = class {
1343
1528
  db;
1344
1529
  scrubber = new DefaultSecretScrubber();
1345
1530
  configureDatabase() {
1346
- this.db.exec(
1347
- "PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON; PRAGMA busy_timeout=5000; PRAGMA synchronous=NORMAL;"
1348
- );
1531
+ configureCatalogDatabase(this.db);
1349
1532
  }
1350
1533
  close() {
1351
1534
  this.db.close();
1352
1535
  }
1353
1536
  initialize() {
1354
- this.db.exec(`
1355
- CREATE TABLE IF NOT EXISTS catalog_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
1356
- CREATE TABLE IF NOT EXISTS sessions (
1357
- session_id TEXT PRIMARY KEY,
1358
- transcript_relative_path TEXT NOT NULL,
1359
- summary_relative_path TEXT NOT NULL,
1360
- summary_json TEXT NOT NULL,
1361
- transcript_size INTEGER NOT NULL DEFAULT 0,
1362
- transcript_mtime_ms REAL NOT NULL DEFAULT 0,
1363
- summary_revision INTEGER NOT NULL DEFAULT 1,
1364
- indexed_at TEXT NOT NULL,
1365
- damaged INTEGER NOT NULL DEFAULT 0
1366
- );
1367
- CREATE TABLE IF NOT EXISTS session_leases (
1368
- session_id TEXT PRIMARY KEY,
1369
- lease_id TEXT NOT NULL UNIQUE,
1370
- lease_secret_hash TEXT NOT NULL,
1371
- owner_instance_id TEXT NOT NULL,
1372
- owner_pid INTEGER NOT NULL,
1373
- owner_started_at TEXT NOT NULL,
1374
- entry_json TEXT NOT NULL,
1375
- agent_revision INTEGER NOT NULL DEFAULT 0,
1376
- status TEXT NOT NULL,
1377
- last_heartbeat_at INTEGER NOT NULL,
1378
- lease_expires_at INTEGER NOT NULL
1379
- );
1380
- CREATE TABLE IF NOT EXISTS resume_reservations (
1381
- reservation_id TEXT PRIMARY KEY,
1382
- target_session_id TEXT NOT NULL UNIQUE,
1383
- requester_instance_id TEXT NOT NULL,
1384
- current_session_id TEXT,
1385
- created_at INTEGER NOT NULL,
1386
- expires_at INTEGER NOT NULL
1387
- );
1388
- CREATE TABLE IF NOT EXISTS maintenance_leases (
1389
- session_id TEXT PRIMARY KEY,
1390
- operation TEXT NOT NULL,
1391
- holder_id TEXT NOT NULL,
1392
- lease_id TEXT NOT NULL UNIQUE,
1393
- acquired_at INTEGER NOT NULL,
1394
- expires_at INTEGER NOT NULL
1395
- );
1396
- CREATE INDEX IF NOT EXISTS idx_sessions_activity ON sessions(indexed_at DESC);
1397
- CREATE INDEX IF NOT EXISTS idx_leases_expiry ON session_leases(lease_expires_at);
1398
- CREATE INDEX IF NOT EXISTS idx_reservations_expiry ON resume_reservations(expires_at);
1399
- CREATE INDEX IF NOT EXISTS idx_maintenance_expiry ON maintenance_leases(expires_at);
1400
- `);
1401
- this.db.prepare("INSERT INTO catalog_meta(key,value) VALUES (?,?) ON CONFLICT(key) DO NOTHING").run("schema_version", String(SCHEMA_VERSION));
1402
- this.db.prepare("INSERT INTO catalog_meta(key,value) VALUES (?,?) ON CONFLICT(key) DO NOTHING").run("generation", "0");
1403
- const row = this.db.prepare("SELECT value FROM catalog_meta WHERE key=?").get("schema_version");
1404
- if (Number(row.value) !== SCHEMA_VERSION)
1405
- throw new Error(`Unsupported session catalog schema ${row.value}`);
1537
+ initializeCatalogSchema(this.db);
1406
1538
  }
1407
1539
  transaction(run) {
1408
1540
  this.db.exec("BEGIN IMMEDIATE");
@@ -1443,21 +1575,6 @@ var SessionCatalogStore = class {
1443
1575
  leaseRow(sessionId) {
1444
1576
  return this.db.prepare("SELECT * FROM session_leases WHERE session_id=?").get(sessionId);
1445
1577
  }
1446
- /**
1447
- * True when `sessionId` has a live `session_leases` row owned by a
1448
- * different OS process than the *caller*. Used by
1449
- * `acquireMaintenance` to distinguish "another running wstack" (which
1450
- * must be fenced off) from "this very TUI owning its own session"
1451
- * (which is allowed to claim a non-destructive maintenance lease on
1452
- * its own transcript).
1453
- *
1454
- * `callerPid` must be the pid of the process that asked for the lease,
1455
- * NOT `process.pid`. In the built runtime this store lives inside the
1456
- * detached project-catalog daemon, so `process.pid` is the daemon's and
1457
- * would mark every lease — including the caller's own session — foreign,
1458
- * making `/clear` and `/rewind` fail with "Session … is live". Callers
1459
- * that run the store in-process may omit it.
1460
- */
1461
1578
  foreignLiveLease(sessionId, callerPid = process.pid) {
1462
1579
  const live = this.leaseRow(sessionId);
1463
1580
  return Boolean(live) && live.owner_pid !== callerPid;
@@ -1547,7 +1664,7 @@ var SessionCatalogStore = class {
1547
1664
  if (this.maintenanceExists(targetSessionId))
1548
1665
  throw conflict(`Session ${targetSessionId} is under maintenance`);
1549
1666
  const catalog = this.db.prepare("SELECT 1 AS yes FROM sessions WHERE session_id=?").get(targetSessionId);
1550
- if (!catalog && !fs4.existsSync(this.containedPath(`${targetSessionId}.jsonl`)))
1667
+ if (!catalog && !fs5.existsSync(this.containedPath(`${targetSessionId}.jsonl`)))
1551
1668
  throw new Error(`Session not found: ${targetSessionId}`);
1552
1669
  const reservationId = randomUUID2();
1553
1670
  const now = Date.now();
@@ -1675,9 +1792,9 @@ var SessionCatalogStore = class {
1675
1792
  }
1676
1793
  containedPath(relative2) {
1677
1794
  assertId(relative2.replace(/\.(jsonl|summary\.json)$/, ""), "session path");
1678
- const root = path4.resolve(this.sessionsDir);
1679
- const candidate = path4.resolve(root, relative2);
1680
- const prefix = `${root}${path4.sep}`;
1795
+ const root = path5.resolve(this.sessionsDir);
1796
+ const candidate = path5.resolve(root, relative2);
1797
+ const prefix = `${root}${path5.sep}`;
1681
1798
  if (candidate !== root && !(process.platform === "win32" ? candidate.toLowerCase().startsWith(prefix.toLowerCase()) : candidate.startsWith(prefix)))
1682
1799
  throw new TypeError("Session path escapes sessions directory");
1683
1800
  return candidate;
@@ -1693,7 +1810,7 @@ var SessionCatalogStore = class {
1693
1810
  transcriptRelativePath = normalizedTranscript;
1694
1811
  summaryRelativePath = normalizedSummary;
1695
1812
  const transcript = this.containedPath(transcriptRelativePath);
1696
- const stat = fs4.existsSync(transcript) ? fs4.statSync(transcript) : void 0;
1813
+ const stat = fs5.existsSync(transcript) ? fs5.statSync(transcript) : void 0;
1697
1814
  const now = (/* @__PURE__ */ new Date()).toISOString();
1698
1815
  return this.transaction(() => {
1699
1816
  const prior = this.db.prepare("SELECT summary_revision FROM sessions WHERE session_id=?").get(summary.id);
@@ -1816,7 +1933,7 @@ var SessionCatalogStore = class {
1816
1933
  if (trimmed) summary.name = this.scrubber.scrub(trimmed).slice(0, 500);
1817
1934
  else delete summary.name;
1818
1935
  const summaryPath = this.containedPath(current.summaryRelativePath);
1819
- fs4.mkdirSync(path4.dirname(summaryPath), { recursive: true, mode: 448 });
1936
+ fs5.mkdirSync(path5.dirname(summaryPath), { recursive: true, mode: 448 });
1820
1937
  await atomicWrite(summaryPath, `${JSON.stringify(summary)}
1821
1938
  `, { mode: 384 });
1822
1939
  try {
@@ -1876,16 +1993,16 @@ var SessionCatalogStore = class {
1876
1993
  this.containedPath(`${sessionId}.plan.json`),
1877
1994
  this.containedPath(`${sessionId}.tasks.json`),
1878
1995
  this.containedPath(`${sessionId}.todos.json`),
1879
- path4.join(path4.dirname(transcript), path4.basename(sessionId))
1996
+ path5.join(path5.dirname(transcript), path5.basename(sessionId))
1880
1997
  ];
1881
- const trashRoot = path4.join(this.sessionsDir, "_trash", lease.leaseId);
1882
- fs4.mkdirSync(trashRoot, { recursive: true, mode: 448 });
1998
+ const trashRoot = path5.join(this.sessionsDir, "_trash", lease.leaseId);
1999
+ fs5.mkdirSync(trashRoot, { recursive: true, mode: 448 });
1883
2000
  const moved = [];
1884
2001
  try {
1885
2002
  artifacts.forEach((artifact, index) => {
1886
- if (!fs4.existsSync(artifact)) return;
1887
- const target = path4.join(trashRoot, `${index}-${path4.basename(artifact)}`);
1888
- fs4.renameSync(artifact, target);
2003
+ if (!fs5.existsSync(artifact)) return;
2004
+ const target = path5.join(trashRoot, `${index}-${path5.basename(artifact)}`);
2005
+ fs5.renameSync(artifact, target);
1889
2006
  moved.push({ from: artifact, to: target });
1890
2007
  });
1891
2008
  this.transaction(() => {
@@ -1900,17 +2017,17 @@ var SessionCatalogStore = class {
1900
2017
  } catch (error) {
1901
2018
  for (const item of moved.reverse()) {
1902
2019
  try {
1903
- fs4.mkdirSync(path4.dirname(item.from), { recursive: true, mode: 448 });
1904
- fs4.renameSync(item.to, item.from);
2020
+ fs5.mkdirSync(path5.dirname(item.from), { recursive: true, mode: 448 });
2021
+ fs5.renameSync(item.to, item.from);
1905
2022
  } catch {
1906
2023
  }
1907
2024
  }
1908
2025
  throw error;
1909
2026
  }
1910
2027
  try {
1911
- fs4.rmSync(trashRoot, { recursive: true, force: true });
1912
- const trashParent = path4.dirname(trashRoot);
1913
- if (fs4.readdirSync(trashParent).length === 0) fs4.rmdirSync(trashParent);
2028
+ fs5.rmSync(trashRoot, { recursive: true, force: true });
2029
+ const trashParent = path5.dirname(trashRoot);
2030
+ if (fs5.readdirSync(trashParent).length === 0) fs5.rmdirSync(trashParent);
1914
2031
  } catch {
1915
2032
  }
1916
2033
  }
@@ -1931,122 +2048,13 @@ var SessionCatalogStore = class {
1931
2048
  return deleted;
1932
2049
  }
1933
2050
  rebuildCatalog() {
1934
- const summaries = this.walkFiles(this.sessionsDir, ".summary.json");
1935
- const transcripts = this.walkFiles(this.sessionsDir, ".jsonl").filter(
1936
- (file) => isSessionTranscriptFileName(path4.basename(file))
2051
+ return rebuildCatalogIndex(
2052
+ this.db,
2053
+ this.sessionsDir,
2054
+ (rel) => this.containedPath(rel),
2055
+ (run) => this.transaction(run),
2056
+ () => this.bumpGeneration()
1937
2057
  );
1938
- const ids = /* @__PURE__ */ new Set();
1939
- for (const file of [...summaries, ...transcripts]) {
1940
- const relative2 = path4.relative(this.sessionsDir, file).replaceAll("\\", "/");
1941
- ids.add(relative2.replace(/\.summary\.json$|\.jsonl$/, ""));
1942
- }
1943
- let indexed = 0;
1944
- let damaged = 0;
1945
- this.transaction(() => {
1946
- this.db.prepare("DELETE FROM sessions").run();
1947
- for (const id of ids) {
1948
- try {
1949
- const summaryFile = this.containedPath(`${id}.summary.json`);
1950
- const summary = fs4.existsSync(summaryFile) ? parseJson(fs4.readFileSync(summaryFile, "utf8")) : this.summarizeTranscript(id);
1951
- if (!summary || summary.id !== id) throw new Error("summary identity mismatch");
1952
- const transcript = this.containedPath(`${id}.jsonl`);
1953
- const stat = fs4.existsSync(transcript) ? fs4.statSync(transcript) : void 0;
1954
- const now = (/* @__PURE__ */ new Date()).toISOString();
1955
- this.db.prepare(
1956
- "INSERT INTO sessions(session_id,transcript_relative_path,summary_relative_path,summary_json,transcript_size,transcript_mtime_ms,summary_revision,indexed_at,damaged) VALUES (?,?,?,?,?,?,?,?,0)"
1957
- ).run(
1958
- id,
1959
- `${id}.jsonl`,
1960
- `${id}.summary.json`,
1961
- JSON.stringify(summary),
1962
- stat?.size ?? 0,
1963
- stat?.mtimeMs ?? 0,
1964
- 1,
1965
- now
1966
- );
1967
- indexed++;
1968
- } catch {
1969
- const now = (/* @__PURE__ */ new Date()).toISOString();
1970
- const fallback = {
1971
- id,
1972
- title: id,
1973
- startedAt: now,
1974
- model: "",
1975
- provider: "",
1976
- tokenTotal: 0
1977
- };
1978
- this.db.prepare(
1979
- "INSERT INTO sessions(session_id,transcript_relative_path,summary_relative_path,summary_json,summary_revision,indexed_at,damaged) VALUES (?,?,?,?,1,?,1)"
1980
- ).run(id, `${id}.jsonl`, `${id}.summary.json`, JSON.stringify(fallback), now);
1981
- damaged++;
1982
- }
1983
- }
1984
- this.db.prepare(
1985
- "INSERT INTO catalog_meta(key,value) VALUES ('last_reconciliation',?) ON CONFLICT(key) DO UPDATE SET value=excluded.value"
1986
- ).run((/* @__PURE__ */ new Date()).toISOString());
1987
- this.bumpGeneration();
1988
- });
1989
- return { indexed, damaged };
1990
- }
1991
- walkFiles(root, suffix) {
1992
- const result = [];
1993
- const visit = (dir) => {
1994
- for (const entry of fs4.readdirSync(dir, { withFileTypes: true })) {
1995
- if (entry.isDirectory()) {
1996
- if (entry.name !== "_cas" && entry.name !== "_trash") visit(path4.join(dir, entry.name));
1997
- } else if (entry.isFile() && entry.name.endsWith(suffix))
1998
- result.push(path4.join(dir, entry.name));
1999
- }
2000
- };
2001
- visit(root);
2002
- return result;
2003
- }
2004
- summarizeTranscript(id) {
2005
- const file = this.containedPath(`${id}.jsonl`);
2006
- const lines = fs4.readFileSync(file, "utf8").split(/\r?\n/);
2007
- let start;
2008
- let endedAt;
2009
- let lastActivityAt;
2010
- let messageCount = 0;
2011
- let iterationCount = 0;
2012
- let toolCallCount = 0;
2013
- let compactionCount = 0;
2014
- let tokenTotal = 0;
2015
- for (const line of lines) {
2016
- if (!line) continue;
2017
- let event;
2018
- try {
2019
- event = parseJson(line);
2020
- } catch {
2021
- continue;
2022
- }
2023
- lastActivityAt = event.ts;
2024
- if (event.type === "session_start") start = event;
2025
- if (event.type === "session_end") endedAt = event.ts;
2026
- if (event.type === "message_appended" && (event.message.role === "user" || event.message.role === "assistant"))
2027
- messageCount++;
2028
- if (event.type === "llm_response") {
2029
- iterationCount++;
2030
- tokenTotal += event.usage.input + event.usage.output + (event.usage.cacheRead ?? 0) + (event.usage.cacheWrite ?? 0);
2031
- }
2032
- if (event.type === "tool_call_end") toolCallCount++;
2033
- if (event.type === "compaction") compactionCount++;
2034
- }
2035
- if (!start) throw new Error("missing session_start");
2036
- return {
2037
- id,
2038
- title: id,
2039
- startedAt: start.ts,
2040
- ...endedAt ? { endedAt } : {},
2041
- model: start.model,
2042
- provider: start.provider,
2043
- tokenTotal,
2044
- ...lastActivityAt ? { lastActivityAt } : {},
2045
- messageCount,
2046
- iterationCount,
2047
- toolCallCount,
2048
- compactionCount
2049
- };
2050
2058
  }
2051
2059
  health(base) {
2052
2060
  this.reapExpired();