@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
@@ -4,7 +4,7 @@
4
4
  import { randomBytes as randomBytes2, randomUUID as randomUUID2 } from "node:crypto";
5
5
  import * as fsp from "node:fs/promises";
6
6
  import * as net from "node:net";
7
- import * as path3 from "node:path";
7
+ import * as path4 from "node:path";
8
8
  import { bindProjectEndpoint } from "@wrongstack/persistence";
9
9
 
10
10
  // src/security/file-permissions.ts
@@ -179,9 +179,9 @@ function sessionCatalogProjectServerMetadataPath(projectDir) {
179
179
  }
180
180
 
181
181
  // src/session-catalog/store.ts
182
- import { createHash as createHash2, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
183
- import * as fs from "node:fs";
184
- import * as path2 from "node:path";
182
+ import { randomBytes, randomUUID } from "node:crypto";
183
+ import * as fs2 from "node:fs";
184
+ import * as path3 from "node:path";
185
185
 
186
186
  // src/coordination/sqlite-mailbox-schema.ts
187
187
  import { createRequire } from "node:module";
@@ -556,6 +556,10 @@ function isPidAlive(pid) {
556
556
  }
557
557
  }
558
558
 
559
+ // src/session-catalog/store-rebuild.ts
560
+ import * as fs from "node:fs";
561
+ import * as path2 from "node:path";
562
+
559
563
  // src/utils/session-scoped-path.ts
560
564
  var SESSION_SIDECAR_JSONL_SUFFIXES = [
561
565
  ".replay.jsonl",
@@ -572,7 +576,8 @@ function isSessionTranscriptFileName(name) {
572
576
  return !SESSION_SIDECAR_JSONL_SUFFIXES.some((suffix) => name.endsWith(suffix));
573
577
  }
574
578
 
575
- // src/session-catalog/store.ts
579
+ // src/session-catalog/store-schema.ts
580
+ import { createHash as createHash2, timingSafeEqual } from "node:crypto";
576
581
  var SCHEMA_VERSION = 1;
577
582
  var MAX_LEASE_MS = 12e4;
578
583
  var MAX_RESERVATION_MS = 6e4;
@@ -617,12 +622,192 @@ function boundPresenceValue(value, depth) {
617
622
  }
618
623
  return result;
619
624
  }
625
+ function configureCatalogDatabase(db) {
626
+ db.exec(
627
+ "PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON; PRAGMA busy_timeout=5000; PRAGMA synchronous=NORMAL;"
628
+ );
629
+ }
630
+ function initializeCatalogSchema(db) {
631
+ db.exec(`
632
+ CREATE TABLE IF NOT EXISTS catalog_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
633
+ CREATE TABLE IF NOT EXISTS sessions (
634
+ session_id TEXT PRIMARY KEY,
635
+ transcript_relative_path TEXT NOT NULL,
636
+ summary_relative_path TEXT NOT NULL,
637
+ summary_json TEXT NOT NULL,
638
+ transcript_size INTEGER NOT NULL DEFAULT 0,
639
+ transcript_mtime_ms REAL NOT NULL DEFAULT 0,
640
+ summary_revision INTEGER NOT NULL DEFAULT 1,
641
+ indexed_at TEXT NOT NULL,
642
+ damaged INTEGER NOT NULL DEFAULT 0
643
+ );
644
+ CREATE TABLE IF NOT EXISTS session_leases (
645
+ session_id TEXT PRIMARY KEY,
646
+ lease_id TEXT NOT NULL UNIQUE,
647
+ lease_secret_hash TEXT NOT NULL,
648
+ owner_instance_id TEXT NOT NULL,
649
+ owner_pid INTEGER NOT NULL,
650
+ owner_started_at TEXT NOT NULL,
651
+ entry_json TEXT NOT NULL,
652
+ agent_revision INTEGER NOT NULL DEFAULT 0,
653
+ status TEXT NOT NULL,
654
+ last_heartbeat_at INTEGER NOT NULL,
655
+ lease_expires_at INTEGER NOT NULL
656
+ );
657
+ CREATE TABLE IF NOT EXISTS resume_reservations (
658
+ reservation_id TEXT PRIMARY KEY,
659
+ target_session_id TEXT NOT NULL UNIQUE,
660
+ requester_instance_id TEXT NOT NULL,
661
+ current_session_id TEXT,
662
+ created_at INTEGER NOT NULL,
663
+ expires_at INTEGER NOT NULL
664
+ );
665
+ CREATE TABLE IF NOT EXISTS maintenance_leases (
666
+ session_id TEXT PRIMARY KEY,
667
+ operation TEXT NOT NULL,
668
+ holder_id TEXT NOT NULL,
669
+ lease_id TEXT NOT NULL UNIQUE,
670
+ acquired_at INTEGER NOT NULL,
671
+ expires_at INTEGER NOT NULL
672
+ );
673
+ CREATE INDEX IF NOT EXISTS idx_sessions_activity ON sessions(indexed_at DESC);
674
+ CREATE INDEX IF NOT EXISTS idx_leases_expiry ON session_leases(lease_expires_at);
675
+ CREATE INDEX IF NOT EXISTS idx_reservations_expiry ON resume_reservations(expires_at);
676
+ CREATE INDEX IF NOT EXISTS idx_maintenance_expiry ON maintenance_leases(expires_at);
677
+ `);
678
+ db.prepare("INSERT INTO catalog_meta(key,value) VALUES (?,?) ON CONFLICT(key) DO NOTHING").run("schema_version", String(SCHEMA_VERSION));
679
+ db.prepare("INSERT INTO catalog_meta(key,value) VALUES (?,?) ON CONFLICT(key) DO NOTHING").run("generation", "0");
680
+ const row = db.prepare("SELECT value FROM catalog_meta WHERE key=?").get("schema_version");
681
+ if (Number(row.value) !== SCHEMA_VERSION)
682
+ throw new Error(`Unsupported session catalog schema ${row.value}`);
683
+ }
684
+
685
+ // src/session-catalog/store-rebuild.ts
686
+ function walkSessionFiles(root, suffix) {
687
+ const result = [];
688
+ const visit = (dir) => {
689
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
690
+ if (entry.isDirectory()) {
691
+ if (entry.name !== "_cas" && entry.name !== "_trash") visit(path2.join(dir, entry.name));
692
+ } else if (entry.isFile() && entry.name.endsWith(suffix))
693
+ result.push(path2.join(dir, entry.name));
694
+ }
695
+ };
696
+ visit(root);
697
+ return result;
698
+ }
699
+ function summarizeTranscriptFile(transcriptFilePath, id) {
700
+ const lines = fs.readFileSync(transcriptFilePath, "utf8").split(/\r?\n/);
701
+ let start;
702
+ let endedAt;
703
+ let lastActivityAt;
704
+ let messageCount = 0;
705
+ let iterationCount = 0;
706
+ let toolCallCount = 0;
707
+ let compactionCount = 0;
708
+ let tokenTotal = 0;
709
+ for (const line of lines) {
710
+ if (!line) continue;
711
+ let event;
712
+ try {
713
+ event = parseJson(line);
714
+ } catch {
715
+ continue;
716
+ }
717
+ lastActivityAt = event.ts;
718
+ if (event.type === "session_start") start = event;
719
+ if (event.type === "session_end") endedAt = event.ts;
720
+ if (event.type === "message_appended" && (event.message.role === "user" || event.message.role === "assistant"))
721
+ messageCount++;
722
+ if (event.type === "llm_response") {
723
+ iterationCount++;
724
+ tokenTotal += event.usage.input + event.usage.output + (event.usage.cacheRead ?? 0) + (event.usage.cacheWrite ?? 0);
725
+ }
726
+ if (event.type === "tool_call_end") toolCallCount++;
727
+ if (event.type === "compaction") compactionCount++;
728
+ }
729
+ if (!start) throw new Error("missing session_start");
730
+ return {
731
+ id,
732
+ title: id,
733
+ startedAt: start.ts,
734
+ ...endedAt ? { endedAt } : {},
735
+ model: start.model,
736
+ provider: start.provider,
737
+ tokenTotal,
738
+ ...lastActivityAt ? { lastActivityAt } : {},
739
+ messageCount,
740
+ iterationCount,
741
+ toolCallCount,
742
+ compactionCount
743
+ };
744
+ }
745
+ function rebuildCatalogIndex(db, sessionsDir, containedPathFn, transactionFn, bumpGenerationFn) {
746
+ const summaries = walkSessionFiles(sessionsDir, ".summary.json");
747
+ const transcripts = walkSessionFiles(sessionsDir, ".jsonl").filter(
748
+ (file) => isSessionTranscriptFileName(path2.basename(file))
749
+ );
750
+ const ids = /* @__PURE__ */ new Set();
751
+ for (const file of [...summaries, ...transcripts]) {
752
+ const relative3 = path2.relative(sessionsDir, file).replaceAll("\\", "/");
753
+ ids.add(relative3.replace(/\.summary\.json$|\.jsonl$/, ""));
754
+ }
755
+ let indexed = 0;
756
+ let damaged = 0;
757
+ transactionFn(() => {
758
+ db.prepare("DELETE FROM sessions").run();
759
+ for (const id of ids) {
760
+ try {
761
+ const summaryFile = containedPathFn(`${id}.summary.json`);
762
+ const summary = fs.existsSync(summaryFile) ? parseJson(fs.readFileSync(summaryFile, "utf8")) : summarizeTranscriptFile(containedPathFn(`${id}.jsonl`), id);
763
+ if (!summary || summary.id !== id) throw new Error("summary identity mismatch");
764
+ const transcript = containedPathFn(`${id}.jsonl`);
765
+ const stat = fs.existsSync(transcript) ? fs.statSync(transcript) : void 0;
766
+ const now = (/* @__PURE__ */ new Date()).toISOString();
767
+ db.prepare(
768
+ "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)"
769
+ ).run(
770
+ id,
771
+ `${id}.jsonl`,
772
+ `${id}.summary.json`,
773
+ JSON.stringify(summary),
774
+ stat?.size ?? 0,
775
+ stat?.mtimeMs ?? 0,
776
+ 1,
777
+ now
778
+ );
779
+ indexed++;
780
+ } catch {
781
+ const now = (/* @__PURE__ */ new Date()).toISOString();
782
+ const fallback = {
783
+ id,
784
+ title: id,
785
+ startedAt: now,
786
+ model: "",
787
+ provider: "",
788
+ tokenTotal: 0
789
+ };
790
+ db.prepare(
791
+ "INSERT INTO sessions(session_id,transcript_relative_path,summary_relative_path,summary_json,summary_revision,indexed_at,damaged) VALUES (?,?,?,?,1,?,1)"
792
+ ).run(id, `${id}.jsonl`, `${id}.summary.json`, JSON.stringify(fallback), now);
793
+ damaged++;
794
+ }
795
+ }
796
+ db.prepare(
797
+ "INSERT INTO catalog_meta(key,value) VALUES ('last_reconciliation',?) ON CONFLICT(key) DO UPDATE SET value=excluded.value"
798
+ ).run((/* @__PURE__ */ new Date()).toISOString());
799
+ bumpGenerationFn();
800
+ });
801
+ return { indexed, damaged };
802
+ }
803
+
804
+ // src/session-catalog/store.ts
620
805
  var SessionCatalogStore = class {
621
806
  constructor(projectDir) {
622
807
  this.projectDir = projectDir;
623
- this.sessionsDir = path2.join(projectDir, "sessions");
624
- fs.mkdirSync(this.sessionsDir, { recursive: true, mode: 448 });
625
- this.databasePath = path2.join(this.sessionsDir, "catalog.sqlite");
808
+ this.sessionsDir = path3.join(projectDir, "sessions");
809
+ fs2.mkdirSync(this.sessionsDir, { recursive: true, mode: 448 });
810
+ this.databasePath = path3.join(this.sessionsDir, "catalog.sqlite");
626
811
  const Database = loadDatabaseSync();
627
812
  this.db = new Database(this.databasePath);
628
813
  try {
@@ -639,12 +824,12 @@ var SessionCatalogStore = class {
639
824
  this.db.close();
640
825
  const quarantine = `${this.databasePath}.corrupt-${Date.now()}`;
641
826
  try {
642
- fs.renameSync(this.databasePath, quarantine);
827
+ fs2.renameSync(this.databasePath, quarantine);
643
828
  } catch {
644
829
  }
645
830
  for (const suffix of ["-wal", "-shm"]) {
646
831
  try {
647
- fs.renameSync(`${this.databasePath}${suffix}`, `${quarantine}${suffix}`);
832
+ fs2.renameSync(`${this.databasePath}${suffix}`, `${quarantine}${suffix}`);
648
833
  } catch {
649
834
  }
650
835
  }
@@ -656,7 +841,7 @@ var SessionCatalogStore = class {
656
841
  const rowCount = Number(
657
842
  this.db.prepare("SELECT COUNT(*) AS count FROM sessions").get().count
658
843
  );
659
- if (rowCount === 0 && this.walkFiles(this.sessionsDir, ".jsonl").some((file) => !file.endsWith("_index.jsonl"))) {
844
+ if (rowCount === 0 && walkSessionFiles(this.sessionsDir, ".jsonl").some((file) => !file.endsWith("_index.jsonl"))) {
660
845
  this.rebuildCatalog();
661
846
  }
662
847
  }
@@ -666,66 +851,13 @@ var SessionCatalogStore = class {
666
851
  db;
667
852
  scrubber = new DefaultSecretScrubber();
668
853
  configureDatabase() {
669
- this.db.exec(
670
- "PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON; PRAGMA busy_timeout=5000; PRAGMA synchronous=NORMAL;"
671
- );
854
+ configureCatalogDatabase(this.db);
672
855
  }
673
856
  close() {
674
857
  this.db.close();
675
858
  }
676
859
  initialize() {
677
- this.db.exec(`
678
- CREATE TABLE IF NOT EXISTS catalog_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
679
- CREATE TABLE IF NOT EXISTS sessions (
680
- session_id TEXT PRIMARY KEY,
681
- transcript_relative_path TEXT NOT NULL,
682
- summary_relative_path TEXT NOT NULL,
683
- summary_json TEXT NOT NULL,
684
- transcript_size INTEGER NOT NULL DEFAULT 0,
685
- transcript_mtime_ms REAL NOT NULL DEFAULT 0,
686
- summary_revision INTEGER NOT NULL DEFAULT 1,
687
- indexed_at TEXT NOT NULL,
688
- damaged INTEGER NOT NULL DEFAULT 0
689
- );
690
- CREATE TABLE IF NOT EXISTS session_leases (
691
- session_id TEXT PRIMARY KEY,
692
- lease_id TEXT NOT NULL UNIQUE,
693
- lease_secret_hash TEXT NOT NULL,
694
- owner_instance_id TEXT NOT NULL,
695
- owner_pid INTEGER NOT NULL,
696
- owner_started_at TEXT NOT NULL,
697
- entry_json TEXT NOT NULL,
698
- agent_revision INTEGER NOT NULL DEFAULT 0,
699
- status TEXT NOT NULL,
700
- last_heartbeat_at INTEGER NOT NULL,
701
- lease_expires_at INTEGER NOT NULL
702
- );
703
- CREATE TABLE IF NOT EXISTS resume_reservations (
704
- reservation_id TEXT PRIMARY KEY,
705
- target_session_id TEXT NOT NULL UNIQUE,
706
- requester_instance_id TEXT NOT NULL,
707
- current_session_id TEXT,
708
- created_at INTEGER NOT NULL,
709
- expires_at INTEGER NOT NULL
710
- );
711
- CREATE TABLE IF NOT EXISTS maintenance_leases (
712
- session_id TEXT PRIMARY KEY,
713
- operation TEXT NOT NULL,
714
- holder_id TEXT NOT NULL,
715
- lease_id TEXT NOT NULL UNIQUE,
716
- acquired_at INTEGER NOT NULL,
717
- expires_at INTEGER NOT NULL
718
- );
719
- CREATE INDEX IF NOT EXISTS idx_sessions_activity ON sessions(indexed_at DESC);
720
- CREATE INDEX IF NOT EXISTS idx_leases_expiry ON session_leases(lease_expires_at);
721
- CREATE INDEX IF NOT EXISTS idx_reservations_expiry ON resume_reservations(expires_at);
722
- CREATE INDEX IF NOT EXISTS idx_maintenance_expiry ON maintenance_leases(expires_at);
723
- `);
724
- this.db.prepare("INSERT INTO catalog_meta(key,value) VALUES (?,?) ON CONFLICT(key) DO NOTHING").run("schema_version", String(SCHEMA_VERSION));
725
- this.db.prepare("INSERT INTO catalog_meta(key,value) VALUES (?,?) ON CONFLICT(key) DO NOTHING").run("generation", "0");
726
- const row = this.db.prepare("SELECT value FROM catalog_meta WHERE key=?").get("schema_version");
727
- if (Number(row.value) !== SCHEMA_VERSION)
728
- throw new Error(`Unsupported session catalog schema ${row.value}`);
860
+ initializeCatalogSchema(this.db);
729
861
  }
730
862
  transaction(run) {
731
863
  this.db.exec("BEGIN IMMEDIATE");
@@ -766,21 +898,6 @@ var SessionCatalogStore = class {
766
898
  leaseRow(sessionId) {
767
899
  return this.db.prepare("SELECT * FROM session_leases WHERE session_id=?").get(sessionId);
768
900
  }
769
- /**
770
- * True when `sessionId` has a live `session_leases` row owned by a
771
- * different OS process than the *caller*. Used by
772
- * `acquireMaintenance` to distinguish "another running wstack" (which
773
- * must be fenced off) from "this very TUI owning its own session"
774
- * (which is allowed to claim a non-destructive maintenance lease on
775
- * its own transcript).
776
- *
777
- * `callerPid` must be the pid of the process that asked for the lease,
778
- * NOT `process.pid`. In the built runtime this store lives inside the
779
- * detached project-catalog daemon, so `process.pid` is the daemon's and
780
- * would mark every lease — including the caller's own session — foreign,
781
- * making `/clear` and `/rewind` fail with "Session … is live". Callers
782
- * that run the store in-process may omit it.
783
- */
784
901
  foreignLiveLease(sessionId, callerPid = process.pid) {
785
902
  const live = this.leaseRow(sessionId);
786
903
  return Boolean(live) && live.owner_pid !== callerPid;
@@ -870,7 +987,7 @@ var SessionCatalogStore = class {
870
987
  if (this.maintenanceExists(targetSessionId))
871
988
  throw conflict(`Session ${targetSessionId} is under maintenance`);
872
989
  const catalog = this.db.prepare("SELECT 1 AS yes FROM sessions WHERE session_id=?").get(targetSessionId);
873
- if (!catalog && !fs.existsSync(this.containedPath(`${targetSessionId}.jsonl`)))
990
+ if (!catalog && !fs2.existsSync(this.containedPath(`${targetSessionId}.jsonl`)))
874
991
  throw new Error(`Session not found: ${targetSessionId}`);
875
992
  const reservationId = randomUUID();
876
993
  const now = Date.now();
@@ -998,9 +1115,9 @@ var SessionCatalogStore = class {
998
1115
  }
999
1116
  containedPath(relative3) {
1000
1117
  assertId(relative3.replace(/\.(jsonl|summary\.json)$/, ""), "session path");
1001
- const root = path2.resolve(this.sessionsDir);
1002
- const candidate = path2.resolve(root, relative3);
1003
- const prefix = `${root}${path2.sep}`;
1118
+ const root = path3.resolve(this.sessionsDir);
1119
+ const candidate = path3.resolve(root, relative3);
1120
+ const prefix = `${root}${path3.sep}`;
1004
1121
  if (candidate !== root && !(process.platform === "win32" ? candidate.toLowerCase().startsWith(prefix.toLowerCase()) : candidate.startsWith(prefix)))
1005
1122
  throw new TypeError("Session path escapes sessions directory");
1006
1123
  return candidate;
@@ -1016,7 +1133,7 @@ var SessionCatalogStore = class {
1016
1133
  transcriptRelativePath = normalizedTranscript;
1017
1134
  summaryRelativePath = normalizedSummary;
1018
1135
  const transcript = this.containedPath(transcriptRelativePath);
1019
- const stat = fs.existsSync(transcript) ? fs.statSync(transcript) : void 0;
1136
+ const stat = fs2.existsSync(transcript) ? fs2.statSync(transcript) : void 0;
1020
1137
  const now = (/* @__PURE__ */ new Date()).toISOString();
1021
1138
  return this.transaction(() => {
1022
1139
  const prior = this.db.prepare("SELECT summary_revision FROM sessions WHERE session_id=?").get(summary.id);
@@ -1139,7 +1256,7 @@ var SessionCatalogStore = class {
1139
1256
  if (trimmed) summary.name = this.scrubber.scrub(trimmed).slice(0, 500);
1140
1257
  else delete summary.name;
1141
1258
  const summaryPath = this.containedPath(current.summaryRelativePath);
1142
- fs.mkdirSync(path2.dirname(summaryPath), { recursive: true, mode: 448 });
1259
+ fs2.mkdirSync(path3.dirname(summaryPath), { recursive: true, mode: 448 });
1143
1260
  await atomicWrite(summaryPath, `${JSON.stringify(summary)}
1144
1261
  `, { mode: 384 });
1145
1262
  try {
@@ -1199,16 +1316,16 @@ var SessionCatalogStore = class {
1199
1316
  this.containedPath(`${sessionId}.plan.json`),
1200
1317
  this.containedPath(`${sessionId}.tasks.json`),
1201
1318
  this.containedPath(`${sessionId}.todos.json`),
1202
- path2.join(path2.dirname(transcript), path2.basename(sessionId))
1319
+ path3.join(path3.dirname(transcript), path3.basename(sessionId))
1203
1320
  ];
1204
- const trashRoot = path2.join(this.sessionsDir, "_trash", lease.leaseId);
1205
- fs.mkdirSync(trashRoot, { recursive: true, mode: 448 });
1321
+ const trashRoot = path3.join(this.sessionsDir, "_trash", lease.leaseId);
1322
+ fs2.mkdirSync(trashRoot, { recursive: true, mode: 448 });
1206
1323
  const moved = [];
1207
1324
  try {
1208
1325
  artifacts.forEach((artifact, index) => {
1209
- if (!fs.existsSync(artifact)) return;
1210
- const target = path2.join(trashRoot, `${index}-${path2.basename(artifact)}`);
1211
- fs.renameSync(artifact, target);
1326
+ if (!fs2.existsSync(artifact)) return;
1327
+ const target = path3.join(trashRoot, `${index}-${path3.basename(artifact)}`);
1328
+ fs2.renameSync(artifact, target);
1212
1329
  moved.push({ from: artifact, to: target });
1213
1330
  });
1214
1331
  this.transaction(() => {
@@ -1223,17 +1340,17 @@ var SessionCatalogStore = class {
1223
1340
  } catch (error) {
1224
1341
  for (const item of moved.reverse()) {
1225
1342
  try {
1226
- fs.mkdirSync(path2.dirname(item.from), { recursive: true, mode: 448 });
1227
- fs.renameSync(item.to, item.from);
1343
+ fs2.mkdirSync(path3.dirname(item.from), { recursive: true, mode: 448 });
1344
+ fs2.renameSync(item.to, item.from);
1228
1345
  } catch {
1229
1346
  }
1230
1347
  }
1231
1348
  throw error;
1232
1349
  }
1233
1350
  try {
1234
- fs.rmSync(trashRoot, { recursive: true, force: true });
1235
- const trashParent = path2.dirname(trashRoot);
1236
- if (fs.readdirSync(trashParent).length === 0) fs.rmdirSync(trashParent);
1351
+ fs2.rmSync(trashRoot, { recursive: true, force: true });
1352
+ const trashParent = path3.dirname(trashRoot);
1353
+ if (fs2.readdirSync(trashParent).length === 0) fs2.rmdirSync(trashParent);
1237
1354
  } catch {
1238
1355
  }
1239
1356
  }
@@ -1254,122 +1371,13 @@ var SessionCatalogStore = class {
1254
1371
  return deleted;
1255
1372
  }
1256
1373
  rebuildCatalog() {
1257
- const summaries = this.walkFiles(this.sessionsDir, ".summary.json");
1258
- const transcripts = this.walkFiles(this.sessionsDir, ".jsonl").filter(
1259
- (file) => isSessionTranscriptFileName(path2.basename(file))
1374
+ return rebuildCatalogIndex(
1375
+ this.db,
1376
+ this.sessionsDir,
1377
+ (rel) => this.containedPath(rel),
1378
+ (run) => this.transaction(run),
1379
+ () => this.bumpGeneration()
1260
1380
  );
1261
- const ids = /* @__PURE__ */ new Set();
1262
- for (const file of [...summaries, ...transcripts]) {
1263
- const relative3 = path2.relative(this.sessionsDir, file).replaceAll("\\", "/");
1264
- ids.add(relative3.replace(/\.summary\.json$|\.jsonl$/, ""));
1265
- }
1266
- let indexed = 0;
1267
- let damaged = 0;
1268
- this.transaction(() => {
1269
- this.db.prepare("DELETE FROM sessions").run();
1270
- for (const id of ids) {
1271
- try {
1272
- const summaryFile = this.containedPath(`${id}.summary.json`);
1273
- const summary = fs.existsSync(summaryFile) ? parseJson(fs.readFileSync(summaryFile, "utf8")) : this.summarizeTranscript(id);
1274
- if (!summary || summary.id !== id) throw new Error("summary identity mismatch");
1275
- const transcript = this.containedPath(`${id}.jsonl`);
1276
- const stat = fs.existsSync(transcript) ? fs.statSync(transcript) : void 0;
1277
- const now = (/* @__PURE__ */ new Date()).toISOString();
1278
- this.db.prepare(
1279
- "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)"
1280
- ).run(
1281
- id,
1282
- `${id}.jsonl`,
1283
- `${id}.summary.json`,
1284
- JSON.stringify(summary),
1285
- stat?.size ?? 0,
1286
- stat?.mtimeMs ?? 0,
1287
- 1,
1288
- now
1289
- );
1290
- indexed++;
1291
- } catch {
1292
- const now = (/* @__PURE__ */ new Date()).toISOString();
1293
- const fallback = {
1294
- id,
1295
- title: id,
1296
- startedAt: now,
1297
- model: "",
1298
- provider: "",
1299
- tokenTotal: 0
1300
- };
1301
- this.db.prepare(
1302
- "INSERT INTO sessions(session_id,transcript_relative_path,summary_relative_path,summary_json,summary_revision,indexed_at,damaged) VALUES (?,?,?,?,1,?,1)"
1303
- ).run(id, `${id}.jsonl`, `${id}.summary.json`, JSON.stringify(fallback), now);
1304
- damaged++;
1305
- }
1306
- }
1307
- this.db.prepare(
1308
- "INSERT INTO catalog_meta(key,value) VALUES ('last_reconciliation',?) ON CONFLICT(key) DO UPDATE SET value=excluded.value"
1309
- ).run((/* @__PURE__ */ new Date()).toISOString());
1310
- this.bumpGeneration();
1311
- });
1312
- return { indexed, damaged };
1313
- }
1314
- walkFiles(root, suffix) {
1315
- const result = [];
1316
- const visit = (dir) => {
1317
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
1318
- if (entry.isDirectory()) {
1319
- if (entry.name !== "_cas" && entry.name !== "_trash") visit(path2.join(dir, entry.name));
1320
- } else if (entry.isFile() && entry.name.endsWith(suffix))
1321
- result.push(path2.join(dir, entry.name));
1322
- }
1323
- };
1324
- visit(root);
1325
- return result;
1326
- }
1327
- summarizeTranscript(id) {
1328
- const file = this.containedPath(`${id}.jsonl`);
1329
- const lines = fs.readFileSync(file, "utf8").split(/\r?\n/);
1330
- let start;
1331
- let endedAt;
1332
- let lastActivityAt;
1333
- let messageCount = 0;
1334
- let iterationCount = 0;
1335
- let toolCallCount = 0;
1336
- let compactionCount = 0;
1337
- let tokenTotal = 0;
1338
- for (const line of lines) {
1339
- if (!line) continue;
1340
- let event;
1341
- try {
1342
- event = parseJson(line);
1343
- } catch {
1344
- continue;
1345
- }
1346
- lastActivityAt = event.ts;
1347
- if (event.type === "session_start") start = event;
1348
- if (event.type === "session_end") endedAt = event.ts;
1349
- if (event.type === "message_appended" && (event.message.role === "user" || event.message.role === "assistant"))
1350
- messageCount++;
1351
- if (event.type === "llm_response") {
1352
- iterationCount++;
1353
- tokenTotal += event.usage.input + event.usage.output + (event.usage.cacheRead ?? 0) + (event.usage.cacheWrite ?? 0);
1354
- }
1355
- if (event.type === "tool_call_end") toolCallCount++;
1356
- if (event.type === "compaction") compactionCount++;
1357
- }
1358
- if (!start) throw new Error("missing session_start");
1359
- return {
1360
- id,
1361
- title: id,
1362
- startedAt: start.ts,
1363
- ...endedAt ? { endedAt } : {},
1364
- model: start.model,
1365
- provider: start.provider,
1366
- tokenTotal,
1367
- ...lastActivityAt ? { lastActivityAt } : {},
1368
- messageCount,
1369
- iterationCount,
1370
- toolCallCount,
1371
- compactionCount
1372
- };
1373
1381
  }
1374
1382
  health(base) {
1375
1383
  this.reapExpired();
@@ -1401,13 +1409,13 @@ function parseArgs(argv) {
1401
1409
  const projectRoot = values.get("--project-root");
1402
1410
  if (!projectDir) throw new Error("Session Catalog project server requires --project-dir");
1403
1411
  if (!projectRoot) throw new Error("Session Catalog project server requires --project-root");
1404
- return { projectDir: path3.resolve(projectDir), projectRoot: path3.resolve(projectRoot) };
1412
+ return { projectDir: path4.resolve(projectDir), projectRoot: path4.resolve(projectRoot) };
1405
1413
  }
1406
1414
  useDaemonPerfDefaults();
1407
1415
  var parsed = parseArgs(process.argv.slice(2));
1408
1416
  var endpoint = sessionCatalogProjectServerEndpoint(parsed.projectDir);
1409
1417
  var metadataPath = sessionCatalogProjectServerMetadataPath(parsed.projectDir);
1410
- var databasePath = path3.join(parsed.projectDir, "sessions", "catalog.sqlite");
1418
+ var databasePath = path4.join(parsed.projectDir, "sessions", "catalog.sqlite");
1411
1419
  var startedAt = (/* @__PURE__ */ new Date()).toISOString();
1412
1420
  var instanceId = randomUUID2();
1413
1421
  var authToken = randomBytes2(32).toString("hex");
@@ -1424,7 +1432,7 @@ var serverInfo = {
1424
1432
  instanceId,
1425
1433
  startedAt
1426
1434
  };
1427
- process.title = `wrongstack-session-catalog:${path3.basename(parsed.projectRoot)}`;
1435
+ process.title = `wrongstack-session-catalog:${path4.basename(parsed.projectRoot)}`;
1428
1436
  var store;
1429
1437
  var activeRequests = 0;
1430
1438
  var stopping = false;
@@ -1528,13 +1536,13 @@ function validateOperationArgs(op, args) {
1528
1536
  if (record["entry"] !== void 0) exact(record["entry"], `${op}.entry`, entryKeys);
1529
1537
  if (record["entry"] && typeof record["entry"] === "object") {
1530
1538
  const entry = record["entry"];
1531
- if (typeof entry["projectRoot"] !== "string" || (process.platform === "win32" ? path3.resolve(entry["projectRoot"]).toLowerCase() !== parsed.projectRoot.toLowerCase() : path3.resolve(entry["projectRoot"]) !== parsed.projectRoot)) {
1539
+ if (typeof entry["projectRoot"] !== "string" || (process.platform === "win32" ? path4.resolve(entry["projectRoot"]).toLowerCase() !== parsed.projectRoot.toLowerCase() : path4.resolve(entry["projectRoot"]) !== parsed.projectRoot)) {
1532
1540
  throw new TypeError(`${op}.entry project identity does not match this daemon`);
1533
1541
  }
1534
1542
  if (typeof entry["workingDir"] !== "string")
1535
1543
  throw new TypeError(`${op}.entry workingDir is required`);
1536
- const relative3 = path3.relative(parsed.projectRoot, path3.resolve(entry["workingDir"]));
1537
- if (relative3 === ".." || relative3.startsWith(`..${path3.sep}`) || path3.isAbsolute(relative3)) {
1544
+ const relative3 = path4.relative(parsed.projectRoot, path4.resolve(entry["workingDir"]));
1545
+ if (relative3 === ".." || relative3.startsWith(`..${path4.sep}`) || path4.isAbsolute(relative3)) {
1538
1546
  throw new TypeError(`${op}.entry workingDir is outside the project root`);
1539
1547
  }
1540
1548
  }
@@ -0,0 +1,9 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ import type { SessionSummary } from '../types/session.js';
3
+ export declare function walkSessionFiles(root: string, suffix: string): string[];
4
+ export declare function summarizeTranscriptFile(transcriptFilePath: string, id: string): SessionSummary;
5
+ export declare function rebuildCatalogIndex(db: DatabaseSync, sessionsDir: string, containedPathFn: (relative: string) => string, transactionFn: <T>(run: () => T) => T, bumpGenerationFn: () => number): {
6
+ indexed: number;
7
+ damaged: number;
8
+ };
9
+ //# sourceMappingURL=store-rebuild.d.ts.map
@@ -0,0 +1,48 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ import type { SessionRegistryEntry } from '../session-registry-types.js';
3
+ export declare const SCHEMA_VERSION = 1;
4
+ export declare const MAX_LEASE_MS = 120000;
5
+ export declare const MAX_RESERVATION_MS = 60000;
6
+ export declare const MAX_MAINTENANCE_MS: number;
7
+ export declare const MAX_PAGE = 1000;
8
+ export interface LeaseRow {
9
+ session_id: string;
10
+ lease_id: string;
11
+ lease_secret_hash: string;
12
+ owner_instance_id: string;
13
+ owner_pid: number;
14
+ owner_started_at: string;
15
+ entry_json: string;
16
+ agent_revision: number;
17
+ status: SessionRegistryEntry['status'];
18
+ last_heartbeat_at: number;
19
+ lease_expires_at: number;
20
+ }
21
+ export interface ReservationRow {
22
+ reservation_id: string;
23
+ target_session_id: string;
24
+ requester_instance_id: string;
25
+ current_session_id: string | null;
26
+ expires_at: number;
27
+ }
28
+ export interface CatalogRow {
29
+ session_id: string;
30
+ transcript_relative_path: string;
31
+ summary_relative_path: string;
32
+ summary_json: string;
33
+ transcript_size: number;
34
+ transcript_mtime_ms: number;
35
+ summary_revision: number;
36
+ indexed_at: string;
37
+ damaged: number;
38
+ }
39
+ export declare function hashSecret(secret: string): string;
40
+ export declare function secretMatches(secret: string, expectedHex: string): boolean;
41
+ export declare function boundedMs(value: number | undefined, fallback: number, max: number): number;
42
+ export declare function parseJson<T>(value: string): T;
43
+ export declare function conflict(message: string): Error;
44
+ export declare function assertId(value: string, label?: string): void;
45
+ export declare function boundPresenceValue(value: unknown, depth: number): unknown;
46
+ export declare function configureCatalogDatabase(db: DatabaseSync): void;
47
+ export declare function initializeCatalogSchema(db: DatabaseSync): void;
48
+ //# sourceMappingURL=store-schema.d.ts.map