@wrongstack/core 0.309.1 → 0.310.0

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 (116) hide show
  1. package/dist/chronicle/index.js +113 -41
  2. package/dist/chronicle/metrics-ingest.d.ts +7 -0
  3. package/dist/chronicle/metrics-schema.d.ts +4 -1
  4. package/dist/chronicle/project-server.js +94 -38
  5. package/dist/chronicle/query.d.ts +1 -0
  6. package/dist/chronicle/sqlite-journal-schema.d.ts +2 -1
  7. package/dist/chronicle/types.d.ts +2 -0
  8. package/dist/{agent-status-helpers.d.ts → coordination/agent-status-helpers.d.ts} +1 -1
  9. package/dist/{agent-status-tracker.d.ts → coordination/agent-status-tracker.d.ts} +2 -2
  10. package/dist/{middleware → coordination}/collab-pause.d.ts +1 -1
  11. package/dist/coordination/director-kanban-queue-helpers.d.ts +1 -1
  12. package/dist/coordination/fleet-status-tool.d.ts +1 -1
  13. package/dist/coordination/index.d.ts +5 -1
  14. package/dist/coordination/index.js +2061 -346
  15. package/dist/coordination/kanban-dispatch-port.d.ts +30 -0
  16. package/dist/coordination/kanban-ops-port.d.ts +21 -0
  17. package/dist/coordination/mailbox-hooks.d.ts +1 -1
  18. package/dist/coordination/mailbox-project-server.js +14 -9
  19. package/dist/core/context.d.ts +35 -44
  20. package/dist/core/conversation-state.d.ts +16 -52
  21. package/dist/core/index.js +53 -16
  22. package/dist/core/provider-runner.d.ts +2 -2
  23. package/dist/core/run-env.d.ts +7 -28
  24. package/dist/core/streaming-response-builder.d.ts +2 -2
  25. package/dist/execution/index.js +111 -153
  26. package/dist/goal/index.js +100 -22
  27. package/dist/hq/auth-store.d.ts +9 -0
  28. package/dist/hq/cost-bridge.d.ts +1 -1
  29. package/dist/hq/index.js +24 -2
  30. package/dist/index.d.ts +12 -5
  31. package/dist/index.js +27806 -33937
  32. package/dist/infrastructure/index.js +210 -132
  33. package/dist/infrastructure/provider-cache-ledger.d.ts +1 -1
  34. package/dist/infrastructure/token-counter.d.ts +5 -1
  35. package/dist/kernel/events/file-events.d.ts +6 -0
  36. package/dist/kernel/events/provider-events.d.ts +10 -7
  37. package/dist/kernel/events/session-events.d.ts +4 -4
  38. package/dist/kernel/events/tool-events.d.ts +12 -2
  39. package/dist/plugin/index.js +105 -35
  40. package/dist/security/index.d.ts +1 -0
  41. package/dist/security/index.js +47 -18
  42. package/dist/security/kanban-boundary.d.ts +1 -1
  43. package/dist/security/kanban-governance-port.d.ts +28 -0
  44. package/dist/session-catalog/index.js +2 -3
  45. package/dist/session-catalog/project-server.js +2 -3
  46. package/dist/session-catalog/protocol.d.ts +1 -1
  47. package/dist/session-catalog/registry.d.ts +1 -1
  48. package/dist/session-catalog/store-schema.d.ts +1 -1
  49. package/dist/session-catalog/store.d.ts +1 -1
  50. package/dist/storage/annotations-store.d.ts +1 -1
  51. package/dist/storage/board-store-port.d.ts +45 -0
  52. package/dist/storage/completed-work-checkpoint.d.ts +1 -1
  53. package/dist/storage/config-loader/types.d.ts +1 -1
  54. package/dist/storage/event-bus-port.d.ts +27 -0
  55. package/dist/storage/file-session-writer.d.ts +47 -5
  56. package/dist/storage/goal-coordination.d.ts +1 -1
  57. package/dist/storage/goal-store.d.ts +1 -1
  58. package/dist/storage/index.d.ts +3 -4
  59. package/dist/storage/index.js +1063 -1459
  60. package/dist/storage/plan-store.d.ts +1 -1
  61. package/dist/storage/queue-store.d.ts +1 -1
  62. package/dist/storage/replay-log-store.d.ts +1 -1
  63. package/dist/storage/session-recovery.d.ts +10 -0
  64. package/dist/storage/session-resume-validation.d.ts +13 -1
  65. package/dist/storage/session-store/events.d.ts +1 -1
  66. package/dist/storage/session-store/load-session-data.d.ts +1 -1
  67. package/dist/storage/session-store/rename-session.d.ts +1 -1
  68. package/dist/storage/session-store/resume-session.d.ts +3 -1
  69. package/dist/storage/session-store/session-store-index.d.ts +2 -1
  70. package/dist/storage/session-store/types.d.ts +1 -1
  71. package/dist/storage/session-store.d.ts +70 -0
  72. package/dist/storage/session-summary-tracker.d.ts +8 -0
  73. package/dist/storage/session-write-buffer.d.ts +31 -2
  74. package/dist/storage/task-store.d.ts +1 -1
  75. package/dist/storage/todos-checkpoint.d.ts +1 -1
  76. package/dist/storage/tool-audit-log.d.ts +1 -1
  77. package/dist/tasking/index.js +100 -22
  78. package/dist/tasking/task-tracker.d.ts +24 -1
  79. package/dist/types/compactor.d.ts +2 -2
  80. package/dist/types/context.d.ts +212 -0
  81. package/dist/types/conversation-state.d.ts +109 -0
  82. package/dist/types/error-handler.d.ts +2 -2
  83. package/dist/types/file-event-record.d.ts +6 -0
  84. package/dist/types/index.d.ts +3 -3
  85. package/dist/types/index.js +17 -1
  86. package/dist/types/permission.d.ts +3 -3
  87. package/dist/types/plugin.d.ts +3 -3
  88. package/dist/types/provider-runner.d.ts +2 -2
  89. package/dist/types/provider.d.ts +10 -0
  90. package/dist/types/run-env.d.ts +32 -0
  91. package/dist/types/session.d.ts +3 -0
  92. package/dist/types/slash-command.d.ts +2 -2
  93. package/dist/types/token-counter.d.ts +30 -1
  94. package/dist/types/tool-executor.d.ts +2 -2
  95. package/dist/types/tool.d.ts +5 -5
  96. package/dist/utils/context-breakdown.d.ts +2 -2
  97. package/dist/utils/context-evidence.d.ts +12 -12
  98. package/dist/utils/crash-shield.d.ts +9 -0
  99. package/dist/utils/heap-watchdog.js +11 -3
  100. package/dist/utils/index.d.ts +1 -1
  101. package/dist/utils/index.js +72 -156
  102. package/dist/utils/regex-guard.d.ts +7 -30
  103. package/dist/utils/todos-format.d.ts +1 -1
  104. package/dist/utils/tree-kill.d.ts +2 -0
  105. package/dist/utils/tree-kill.js +1 -0
  106. package/instructions/coordination/subagent-baseline.md +10 -0
  107. package/instructions/system-lite.md +2 -0
  108. package/instructions/system-pro.md +40 -0
  109. package/instructions/system.md +15 -0
  110. package/package.json +6 -9
  111. package/dist/defaults/index.d.ts +0 -69
  112. package/dist/defaults/index.js +0 -38133
  113. /package/dist/{fleet-notifier.d.ts → coordination/fleet-notifier.d.ts} +0 -0
  114. /package/dist/{session-registry-atomic-file.d.ts → session-catalog/session-registry-atomic-file.d.ts} +0 -0
  115. /package/dist/{session-registry-types.d.ts → session-catalog/session-registry-types.d.ts} +0 -0
  116. /package/dist/{session-registry.d.ts → session-catalog/session-registry.d.ts} +0 -0
@@ -2047,7 +2047,11 @@ function relationKeys(event) {
2047
2047
  add("attempt", event.correlation.attemptId, "explicit");
2048
2048
  add("decision", event.attributes?.decisionId, "explicit");
2049
2049
  add("network_request", event.attributes?.requestId, "explicit");
2050
- add("prompt_manifest", event.attributes?.promptManifest?.manifestId, "explicit");
2050
+ add(
2051
+ "prompt_manifest",
2052
+ event.correlation.promptManifestId ?? event.attributes?.promptManifest?.manifestId,
2053
+ "explicit"
2054
+ );
2051
2055
  add("resource_lineage", event.resource?.id, "inferred");
2052
2056
  if (event.eventType === "metrics.rollup" && event.attributes?.signal === "tool.resource.observed") {
2053
2057
  const resources = event.attributes.resources;
@@ -2069,6 +2073,7 @@ function matches(event, query) {
2069
2073
  if (!equal(query.agentId, event.scope.agentId) || !equal(query.taskId, event.scope.taskId)) return false;
2070
2074
  if (!equal(query.providerId, event.runtime?.providerId) || !equal(query.modelId, event.runtime?.modelId)) return false;
2071
2075
  if (!equal(query.traceId, event.correlation.traceId) || !equal(query.logicalRequestId, event.correlation.logicalRequestId)) return false;
2076
+ if (!equal(query.promptManifestId, event.correlation.promptManifestId)) return false;
2072
2077
  if (!equal(query.attemptId, event.correlation.attemptId) || !equal(query.toolCallId, event.correlation.toolCallId)) return false;
2073
2078
  if (!equal(query.resourceKind, event.resource?.kind) || !equal(query.resourceId, event.resource?.id)) return false;
2074
2079
  if (query.path && normalize2(event.resource?.path) !== normalize2(query.path)) return false;
@@ -2209,6 +2214,7 @@ function isChronicleEvent(value) {
2209
2214
  ]) && typeof value.correlation.traceId === "string" && typeof value.correlation.spanId === "string" && optionalStrings(value.correlation, [
2210
2215
  "parentSpanId",
2211
2216
  "logicalRequestId",
2217
+ "promptManifestId",
2212
2218
  "attemptId",
2213
2219
  "toolCallId"
2214
2220
  ]) && isRuntime(value.runtime) && isResource(value.resource) && (value.attributes === void 0 || isRecord(value.attributes)) && (value.tags === void 0 || isStringRecord(value.tags));
@@ -2398,7 +2404,7 @@ var ChronicleQuotaManager = class {
2398
2404
  };
2399
2405
 
2400
2406
  // src/chronicle/sqlite-journal-schema.ts
2401
- import { createRequire } from "node:module";
2407
+ import { loadRuntimeDatabaseSync } from "@wrongstack/persistence";
2402
2408
 
2403
2409
  // src/utils/pid.ts
2404
2410
  function isPidAlive(pid) {
@@ -2442,20 +2448,18 @@ var CHRONICLE_SQLITE_FILE = "chronicle.sqlite";
2442
2448
  var LEGACY_JSONL_MIGRATION_KEY = "legacy-jsonl-v1";
2443
2449
  var LEGACY_JSONL_QUARANTINE_KEY = "legacy-jsonl-v1:quarantine";
2444
2450
  var LEGACY_JSONL_BOUNDARY_KEY = "legacy-jsonl-v1:boundary";
2445
- var SCHEMA_VERSION = 2;
2451
+ var SCHEMA_VERSION = 3;
2446
2452
  var Ctor;
2447
2453
  function loadDatabaseSync() {
2448
2454
  if (Ctor) return Ctor;
2449
- if (Ctor === null) throw new Error("node:sqlite is unavailable in this runtime");
2455
+ if (Ctor === null) throw new Error("SQLite is unavailable in this runtime");
2450
2456
  try {
2451
- Ctor = withSqliteExperimentalWarningSuppressed(
2452
- () => createRequire(import.meta.url)("node:sqlite").DatabaseSync
2453
- );
2457
+ Ctor = withSqliteExperimentalWarningSuppressed(loadRuntimeDatabaseSync);
2454
2458
  return Ctor;
2455
2459
  } catch (error) {
2456
2460
  Ctor = null;
2457
2461
  throw new Error(
2458
- "The Chronicle journal needs Node's built-in SQLite (node:sqlite, Node >= 22.5): " + (error instanceof Error ? error.message : String(error))
2462
+ "The Chronicle journal needs node:sqlite (Node >= 22.5) or bun:sqlite: " + (error instanceof Error ? error.message : String(error))
2459
2463
  );
2460
2464
  }
2461
2465
  }
@@ -2470,6 +2474,7 @@ function projectEvent(event) {
2470
2474
  taskId: event.scope.taskId ?? null,
2471
2475
  traceId: event.correlation?.traceId ?? null,
2472
2476
  logicalRequestId: event.correlation?.logicalRequestId ?? null,
2477
+ promptManifestId: event.correlation?.promptManifestId ?? null,
2473
2478
  resourceKind: event.resource?.kind ?? null,
2474
2479
  resourceId: event.resource?.id ?? null,
2475
2480
  resourcePath: event.resource?.path ?? null,
@@ -2494,6 +2499,7 @@ function ensureChronicleSchema(db) {
2494
2499
  task_id TEXT,
2495
2500
  trace_id TEXT,
2496
2501
  logical_request_id TEXT,
2502
+ prompt_manifest_id TEXT,
2497
2503
  resource_kind TEXT,
2498
2504
  resource_id TEXT,
2499
2505
  resource_path TEXT,
@@ -2521,6 +2527,10 @@ function ensureChronicleSchema(db) {
2521
2527
  if (version < 2) {
2522
2528
  db.exec("DROP INDEX IF EXISTS events_resource_path");
2523
2529
  }
2530
+ if (version > 0 && version < 3) {
2531
+ db.exec("ALTER TABLE events ADD COLUMN prompt_manifest_id TEXT");
2532
+ }
2533
+ db.exec("CREATE INDEX IF NOT EXISTS events_prompt_manifest ON events(prompt_manifest_id)");
2524
2534
  if (version !== SCHEMA_VERSION) {
2525
2535
  db.exec(`PRAGMA user_version = ${SCHEMA_VERSION}`);
2526
2536
  }
@@ -2557,6 +2567,7 @@ function pushDown(query) {
2557
2567
  eq("task_id", query.taskId);
2558
2568
  eq("trace_id", query.traceId);
2559
2569
  eq("logical_request_id", query.logicalRequestId);
2570
+ eq("prompt_manifest_id", query.promptManifestId);
2560
2571
  eq("resource_kind", query.resourceKind);
2561
2572
  eq("resource_id", query.resourceId);
2562
2573
  if (query.eventTypes?.length) {
@@ -2913,6 +2924,7 @@ var ChronicleSqliteJournal = class {
2913
2924
  row.taskId,
2914
2925
  row.traceId,
2915
2926
  row.logicalRequestId,
2927
+ row.promptManifestId,
2916
2928
  row.resourceKind,
2917
2929
  row.resourceId,
2918
2930
  row.resourcePath,
@@ -3072,9 +3084,9 @@ var ChronicleSqliteJournal = class {
3072
3084
  const insert = this.db.prepare(
3073
3085
  `INSERT INTO events (
3074
3086
  day, sequence, event_id, hash, previous_hash, occurred_at, event_type, outcome,
3075
- project_id, session_id, agent_id, task_id, trace_id, logical_request_id,
3087
+ project_id, session_id, agent_id, task_id, trace_id, logical_request_id, prompt_manifest_id,
3076
3088
  resource_kind, resource_id, resource_path, duration_ns, payload
3077
- ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`
3089
+ ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`
3078
3090
  );
3079
3091
  const checkpoint = this.db.prepare(
3080
3092
  `INSERT INTO chain_checkpoint (day, sequence, hash) VALUES (?, ?, ?)
@@ -3103,6 +3115,7 @@ var ChronicleSqliteJournal = class {
3103
3115
  row.taskId,
3104
3116
  row.traceId,
3105
3117
  row.logicalRequestId,
3118
+ row.promptManifestId,
3106
3119
  row.resourceKind,
3107
3120
  row.resourceId,
3108
3121
  row.resourcePath,
@@ -3189,9 +3202,9 @@ var ChronicleSqliteJournal = class {
3189
3202
  insert: this.db.prepare(
3190
3203
  `INSERT INTO events (
3191
3204
  day, sequence, event_id, hash, previous_hash, occurred_at, event_type, outcome,
3192
- project_id, session_id, agent_id, task_id, trace_id, logical_request_id,
3205
+ project_id, session_id, agent_id, task_id, trace_id, logical_request_id, prompt_manifest_id,
3193
3206
  resource_kind, resource_id, resource_path, duration_ns, payload
3194
- ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`
3207
+ ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`
3195
3208
  ),
3196
3209
  trimBoundary: this.db.prepare(
3197
3210
  "SELECT day, sequence, hash FROM events ORDER BY day, sequence LIMIT 1 OFFSET ?"
@@ -3232,8 +3245,8 @@ var ChronicleSqliteJournal = class {
3232
3245
  };
3233
3246
 
3234
3247
  // src/chronicle/metrics-schema.ts
3235
- import { createRequire as createRequire2 } from "node:module";
3236
- var SCHEMA_VERSION2 = 3;
3248
+ import { loadRuntimeDatabaseSync as loadRuntimeDatabaseSync2 } from "@wrongstack/persistence";
3249
+ var SCHEMA_VERSION2 = 4;
3237
3250
  var READ_CHUNK_BYTES = 1024 * 1024;
3238
3251
  var SQLITE_SOURCE_PREFIX = "sqlite:";
3239
3252
  var SQLITE_INGEST_BATCH = 2e3;
@@ -3251,16 +3264,14 @@ var EMPTY_FAMILIES = {
3251
3264
  var Ctor2;
3252
3265
  function loadDatabaseSync2() {
3253
3266
  if (Ctor2) return Ctor2;
3254
- if (Ctor2 === null) throw new Error("node:sqlite is unavailable in this runtime");
3267
+ if (Ctor2 === null) throw new Error("SQLite is unavailable in this runtime");
3255
3268
  try {
3256
- Ctor2 = withSqliteExperimentalWarningSuppressed(
3257
- () => createRequire2(import.meta.url)("node:sqlite").DatabaseSync
3258
- );
3269
+ Ctor2 = withSqliteExperimentalWarningSuppressed(loadRuntimeDatabaseSync2);
3259
3270
  return Ctor2;
3260
3271
  } catch (error) {
3261
3272
  Ctor2 = null;
3262
3273
  throw new Error(
3263
- "Chronicle metrics need Node's built-in SQLite (node:sqlite, Node >= 22.5): " + (error instanceof Error ? error.message : String(error))
3274
+ "Chronicle metrics need node:sqlite (Node >= 22.5) or bun:sqlite: " + (error instanceof Error ? error.message : String(error))
3264
3275
  );
3265
3276
  }
3266
3277
  }
@@ -3329,6 +3340,9 @@ function ensureMetricsSchema(db) {
3329
3340
  tool_name TEXT NOT NULL DEFAULT '',
3330
3341
  provider_id TEXT NOT NULL DEFAULT '',
3331
3342
  model_id TEXT NOT NULL DEFAULT '',
3343
+ logical_request_id TEXT NOT NULL DEFAULT '',
3344
+ prompt_manifest_id TEXT NOT NULL DEFAULT '',
3345
+ provenance_confidence TEXT NOT NULL DEFAULT 'unknown',
3332
3346
  source TEXT NOT NULL DEFAULT ''
3333
3347
  );
3334
3348
  CREATE INDEX IF NOT EXISTS idx_file_lineage_path ON file_lineage(path_key, occurred_at);
@@ -3421,6 +3435,20 @@ var ChronicleMetricsIngester = class {
3421
3435
  db;
3422
3436
  directory;
3423
3437
  dbPath;
3438
+ /**
3439
+ * Prepared-statement cache for the per-event ingest hot paths. Preparing
3440
+ * the same SQL on every event was the dominant ingest cost; node:sqlite
3441
+ * statements are reusable, so each distinct SQL string is prepared once.
3442
+ */
3443
+ stmtCache = /* @__PURE__ */ new Map();
3444
+ stmt(sql) {
3445
+ let s = this.stmtCache.get(sql);
3446
+ if (!s) {
3447
+ s = this.db.prepare(sql);
3448
+ this.stmtCache.set(sql, s);
3449
+ }
3450
+ return s;
3451
+ }
3424
3452
  async refresh() {
3425
3453
  const result = {
3426
3454
  ingestedEvents: 0,
@@ -3450,7 +3478,7 @@ var ChronicleMetricsIngester = class {
3450
3478
  let base;
3451
3479
  if (!migrated) {
3452
3480
  base = offsets.get(key) ?? 0;
3453
- } else if (boundary !== null && boundary.has(key)) {
3481
+ } else if (boundary?.has(key)) {
3454
3482
  base = Math.max(offsets.get(key) ?? 0, boundary.get(key));
3455
3483
  } else {
3456
3484
  base = rebuilt ? 0 : offsets.get(key) ?? 0;
@@ -3673,11 +3701,11 @@ var ChronicleMetricsIngester = class {
3673
3701
  }
3674
3702
  ingestDailyCounters(event) {
3675
3703
  const day = eventDay(event);
3676
- this.db.prepare("INSERT OR IGNORE INTO daily_counters (day) VALUES (?)").run(day);
3677
- const bump = (sql, ...params) => this.db.prepare(`UPDATE daily_counters SET ${sql} WHERE day = ?`).run(...params, day);
3704
+ this.stmt("INSERT OR IGNORE INTO daily_counters (day) VALUES (?)").run(day);
3705
+ const bump = (sql, ...params) => this.stmt(`UPDATE daily_counters SET ${sql} WHERE day = ?`).run(...params, day);
3678
3706
  const family = signalFamily(event);
3679
3707
  const failed = isTerminalFailure(event) ? 1 : 0;
3680
- this.db.prepare(
3708
+ this.stmt(
3681
3709
  `INSERT INTO family_daily (day, family, count, failure_count) VALUES (?, ?, 1, ?)
3682
3710
  ON CONFLICT(day, family) DO UPDATE SET count = count + 1, failure_count = failure_count + excluded.failure_count`
3683
3711
  ).run(day, family, failed);
@@ -3686,12 +3714,15 @@ var ChronicleMetricsIngester = class {
3686
3714
  bump("cancellations = cancellations + 1");
3687
3715
  if (family === "agent") bump("agent_events = agent_events + 1");
3688
3716
  if (event.correlation.logicalRequestId) {
3689
- this.db.prepare(
3717
+ this.stmt(
3690
3718
  "INSERT OR IGNORE INTO logical_request_daily (day, logical_request_id) VALUES (?, ?)"
3691
3719
  ).run(day, event.correlation.logicalRequestId);
3692
3720
  }
3693
3721
  if (event.scope.agentId) {
3694
- this.db.prepare("INSERT OR IGNORE INTO agent_daily (day, agent_id) VALUES (?, ?)").run(day, event.scope.agentId);
3722
+ this.stmt("INSERT OR IGNORE INTO agent_daily (day, agent_id) VALUES (?, ?)").run(
3723
+ day,
3724
+ event.scope.agentId
3725
+ );
3695
3726
  }
3696
3727
  const type = event.eventType;
3697
3728
  if (type === "decision.requested") bump("decisions = decisions + 1");
@@ -3713,7 +3744,10 @@ var ChronicleMetricsIngester = class {
3713
3744
  if (event.resource?.kind === "file" || type.startsWith("file.")) {
3714
3745
  bump("file_events_all = file_events_all + 1");
3715
3746
  if (event.resource?.path) {
3716
- this.db.prepare("INSERT OR IGNORE INTO file_seen_daily (day, path_key) VALUES (?, ?)").run(day, normalizePathKey(event.resource.path));
3747
+ this.stmt("INSERT OR IGNORE INTO file_seen_daily (day, path_key) VALUES (?, ?)").run(
3748
+ day,
3749
+ normalizePathKey(event.resource.path)
3750
+ );
3717
3751
  }
3718
3752
  }
3719
3753
  }
@@ -3722,8 +3756,10 @@ var ChronicleMetricsIngester = class {
3722
3756
  const providerId = event.runtime?.providerId ?? asString(readPath2(event.attributes ?? {}, "from.providerId")) ?? "";
3723
3757
  const modelId = event.runtime?.modelId ?? asString(readPath2(event.attributes ?? {}, "from.model")) ?? "";
3724
3758
  if (!providerId && !modelId) return;
3725
- this.db.prepare("INSERT OR IGNORE INTO provider_daily (day, provider_id, model_id) VALUES (?, ?, ?)").run(day, providerId, modelId);
3726
- const update = (sql, ...params) => this.db.prepare(
3759
+ this.stmt(
3760
+ "INSERT OR IGNORE INTO provider_daily (day, provider_id, model_id) VALUES (?, ?, ?)"
3761
+ ).run(day, providerId, modelId);
3762
+ const update = (sql, ...params) => this.stmt(
3727
3763
  `UPDATE provider_daily SET ${sql} WHERE day = ? AND provider_id = ? AND model_id = ?`
3728
3764
  ).run(...params, day, providerId, modelId);
3729
3765
  const duration = durationMs2(event);
@@ -3764,7 +3800,7 @@ var ChronicleMetricsIngester = class {
3764
3800
  if (typeof cost !== "number" || !Number.isFinite(cost)) return;
3765
3801
  const scopeKey2 = `${event.scope.projectId ?? ""}\0${event.scope.sessionId ?? ""}\0${event.scope.agentId ?? ""}`;
3766
3802
  const occurredAt = event.occurredAt ?? event.observedAt;
3767
- this.db.prepare(
3803
+ this.stmt(
3768
3804
  `INSERT INTO token_cost (scope_key, day, occurred_at, sequence, cost) VALUES (?, ?, ?, ?, ?)
3769
3805
  ON CONFLICT(scope_key) DO UPDATE SET
3770
3806
  day = excluded.day, occurred_at = excluded.occurred_at,
@@ -3778,8 +3814,8 @@ var ChronicleMetricsIngester = class {
3778
3814
  const taskId = event.scope.taskId ?? stringAt(attributes, "taskId");
3779
3815
  if (!taskId) return;
3780
3816
  const occurredAt = event.occurredAt ?? event.observedAt;
3781
- this.db.prepare("INSERT OR IGNORE INTO task_outcomes (task_id) VALUES (?)").run(taskId);
3782
- const set = (sql, ...params) => this.db.prepare(`UPDATE task_outcomes SET ${sql} WHERE task_id = ?`).run(...params, taskId);
3817
+ this.stmt("INSERT OR IGNORE INTO task_outcomes (task_id) VALUES (?)").run(taskId);
3818
+ const set = (sql, ...params) => this.stmt(`UPDATE task_outcomes SET ${sql} WHERE task_id = ?`).run(...params, taskId);
3783
3819
  const lineage = [
3784
3820
  ["run_id", stringAt(attributes, "runId")],
3785
3821
  ["board_id", event.scope.kanbanBoardId ?? stringAt(attributes, "boardId")],
@@ -3826,11 +3862,12 @@ var ChronicleMetricsIngester = class {
3826
3862
  if (!operation || operation === "read") return;
3827
3863
  const filePath = event.resource?.path ?? stringAt(attributes, "filePath");
3828
3864
  if (!filePath) return;
3829
- this.db.prepare(
3865
+ this.stmt(
3830
3866
  `INSERT OR IGNORE INTO file_lineage
3831
3867
  (event_id, path, path_key, operation, occurred_at, session_id, agent_id, task_id, board_id, run_id,
3832
- tool_name, provider_id, model_id, source)
3833
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
3868
+ tool_name, provider_id, model_id, logical_request_id, prompt_manifest_id,
3869
+ provenance_confidence, source)
3870
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
3834
3871
  ).run(
3835
3872
  event.eventId,
3836
3873
  normalizeKey(filePath),
@@ -3845,10 +3882,17 @@ var ChronicleMetricsIngester = class {
3845
3882
  stringAt(attributes, "toolName") ?? "",
3846
3883
  event.runtime?.providerId ?? stringAt(attributes, "provider") ?? "",
3847
3884
  event.runtime?.modelId ?? stringAt(attributes, "model") ?? "",
3885
+ event.correlation.logicalRequestId ?? stringAt(attributes, "logicalRequestId") ?? "",
3886
+ event.correlation.promptManifestId ?? stringAt(attributes, "promptManifestId") ?? "",
3887
+ provenanceConfidence(attributes),
3848
3888
  stringAt(attributes, "source") ?? (event.eventType === "file.event" ? "tool" : "external")
3849
3889
  );
3850
3890
  }
3851
3891
  };
3892
+ function provenanceConfidence(attributes) {
3893
+ const value = attributes["provenanceConfidence"];
3894
+ return value === "explicit" || value === "correlated" || value === "inferred" ? value : "unknown";
3895
+ }
3852
3896
 
3853
3897
  // src/chronicle/metrics-store.ts
3854
3898
  var ChronicleMetricsStore = class _ChronicleMetricsStore {
@@ -3980,7 +4024,8 @@ var ChronicleMetricsStore = class _ChronicleMetricsStore {
3980
4024
  const where = clauses.length > 0 ? ` WHERE ${clauses.join(" AND ")}` : "";
3981
4025
  params.push(clampLimit(options.limit, 200));
3982
4026
  const projection = `path, operation, occurred_at, session_id, agent_id, task_id, board_id, run_id,
3983
- tool_name, provider_id, model_id, source`;
4027
+ tool_name, provider_id, model_id, logical_request_id, prompt_manifest_id,
4028
+ provenance_confidence, source`;
3984
4029
  const sql = options.latestPerPath ? `SELECT ${projection} FROM (
3985
4030
  SELECT ${projection}, ROW_NUMBER() OVER (
3986
4031
  PARTITION BY path_key ORDER BY occurred_at DESC, event_id DESC
@@ -4001,6 +4046,9 @@ var ChronicleMetricsStore = class _ChronicleMetricsStore {
4001
4046
  toolName: row.tool_name,
4002
4047
  providerId: row.provider_id,
4003
4048
  modelId: row.model_id,
4049
+ logicalRequestId: row.logical_request_id,
4050
+ promptManifestId: row.prompt_manifest_id,
4051
+ provenanceConfidence: row.provenance_confidence,
4004
4052
  source: row.source
4005
4053
  }));
4006
4054
  }
@@ -4845,6 +4893,7 @@ var EMPTY_JOURNAL_STATS = {
4845
4893
  largestBatch: 0,
4846
4894
  partitionRolls: 0
4847
4895
  };
4896
+ var MAX_OPEN_JOURNAL_DAYS = 2;
4848
4897
  var InlineChronicleProjectAccess = class {
4849
4898
  constructor(options) {
4850
4899
  this.options = options;
@@ -4953,6 +5002,26 @@ var InlineChronicleProjectAccess = class {
4953
5002
  get chronicleDirectory() {
4954
5003
  return path15.join(this.options.projectDir, "chronicle");
4955
5004
  }
5005
+ /**
5006
+ * Drop journals for days we will not write to again.
5007
+ *
5008
+ * Mirrors the daemon's `pruneJournals` in `project-server.ts`: without a cap
5009
+ * an inline access object that lives across midnight kept one open
5010
+ * `ChronicleJournal` (with its write buffer and file handle) per day, forever.
5011
+ */
5012
+ pruneJournals(currentDay) {
5013
+ if (this.journals.size <= MAX_OPEN_JOURNAL_DAYS) return;
5014
+ const keep = new Set(
5015
+ [...this.journals.keys()].sort().reverse().slice(0, MAX_OPEN_JOURNAL_DAYS)
5016
+ );
5017
+ keep.add(currentDay);
5018
+ for (const [day, journal] of this.journals) {
5019
+ if (keep.has(day)) continue;
5020
+ this.journals.delete(day);
5021
+ void journal.flush().catch(() => {
5022
+ });
5023
+ }
5024
+ }
4956
5025
  journalForToday() {
4957
5026
  const now = /* @__PURE__ */ new Date();
4958
5027
  const day = now.toISOString().slice(0, 10);
@@ -4969,6 +5038,7 @@ var InlineChronicleProjectAccess = class {
4969
5038
  retentionDays: this.options.retentionDays
4970
5039
  });
4971
5040
  this.journals.set(day, journal);
5041
+ this.pruneJournals(day);
4972
5042
  }
4973
5043
  return journal;
4974
5044
  }
@@ -5016,8 +5086,7 @@ var InlineChronicleProjectAccess = class {
5016
5086
 
5017
5087
  // src/chronicle/sqlite-compaction.ts
5018
5088
  import { existsSync as existsSync2, readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
5019
- import { createRequire as createRequire3 } from "node:module";
5020
- var require2 = createRequire3(import.meta.url);
5089
+ import { loadRuntimeDatabaseSync as loadRuntimeDatabaseSync3 } from "@wrongstack/persistence";
5021
5090
  var ChronicleCompactionBusyError = class extends Error {
5022
5091
  constructor(pid) {
5023
5092
  super(`Chronicle project server is still running (pid ${pid}); stop it before compacting`);
@@ -5040,9 +5109,7 @@ function compactChronicleSqlite(options) {
5040
5109
  if (!existsSync2(options.dbPath)) {
5041
5110
  return { beforeBytes, afterBytes: beforeBytes, reclaimedBytes: 0 };
5042
5111
  }
5043
- const Database = withSqliteExperimentalWarningSuppressed(
5044
- () => require2("node:sqlite").DatabaseSync
5045
- );
5112
+ const Database = withSqliteExperimentalWarningSuppressed(loadRuntimeDatabaseSync3);
5046
5113
  const db = new Database(options.dbPath);
5047
5114
  try {
5048
5115
  db.exec("PRAGMA busy_timeout = 1");
@@ -5112,6 +5179,7 @@ function persist(options, event, base, seenToolManifests) {
5112
5179
  ...context.correlation,
5113
5180
  ...event.traceId ? { traceId: event.traceId } : {},
5114
5181
  logicalRequestId: event.logicalRequestId,
5182
+ ...event.promptManifestId ? { promptManifestId: event.promptManifestId } : {},
5115
5183
  attemptId: event.attemptId
5116
5184
  },
5117
5185
  runtime: { providerId: event.providerId, modelId: event.model },
@@ -5127,6 +5195,7 @@ function providerAttributes(event, seenToolManifests) {
5127
5195
  providerId: _providerId,
5128
5196
  model: _model,
5129
5197
  logicalRequestId: _logicalRequestId,
5198
+ promptManifestId: _promptManifestId,
5130
5199
  attemptId: _attemptId,
5131
5200
  ...rest
5132
5201
  } = event;
@@ -5269,6 +5338,8 @@ function persist2(options, event, fields) {
5269
5338
  correlation: {
5270
5339
  ...context.correlation,
5271
5340
  ...event.traceId ? { traceId: event.traceId } : {},
5341
+ ...event.logicalRequestId ? { logicalRequestId: event.logicalRequestId } : {},
5342
+ ...event.promptManifestId ? { promptManifestId: event.promptManifestId } : {},
5272
5343
  ...event.id ? { toolCallId: event.id } : {}
5273
5344
  },
5274
5345
  ...event.provider || event.model ? {
@@ -5528,7 +5599,8 @@ function wireDomainEventsToChronicle(options) {
5528
5599
  ...stringField(record, "traceId") ? { traceId: stringField(record, "traceId") } : {},
5529
5600
  ...stringField(record, "toolCallId") ? { toolCallId: stringField(record, "toolCallId") } : {},
5530
5601
  ...stringField(record, "attemptId") ? { attemptId: stringField(record, "attemptId") } : {},
5531
- ...stringField(record, "logicalRequestId") ? { logicalRequestId: stringField(record, "logicalRequestId") } : {}
5602
+ ...stringField(record, "logicalRequestId") ? { logicalRequestId: stringField(record, "logicalRequestId") } : {},
5603
+ ...stringField(record, "promptManifestId") ? { promptManifestId: stringField(record, "promptManifestId") } : {}
5532
5604
  },
5533
5605
  outcome: inferOutcome(eventName, record),
5534
5606
  runtime: {
@@ -4,7 +4,14 @@ export declare class ChronicleMetricsIngester {
4
4
  private readonly db;
5
5
  private readonly directory;
6
6
  private readonly dbPath;
7
+ /**
8
+ * Prepared-statement cache for the per-event ingest hot paths. Preparing
9
+ * the same SQL on every event was the dominant ingest cost; node:sqlite
10
+ * statements are reusable, so each distinct SQL string is prepared once.
11
+ */
12
+ private stmtCache;
7
13
  constructor(db: DatabaseSync, directory: string, dbPath: string);
14
+ private stmt;
8
15
  refresh(): Promise<ChronicleMetricsRefreshResult>;
9
16
  private loadOffsets;
10
17
  /** True once the legacy JSONL journal was imported into the SQLite journal
@@ -1,7 +1,7 @@
1
1
  import type { DatabaseSync } from 'node:sqlite';
2
2
  import type { ChronicleSignalFamily } from './query.js';
3
3
  import type { ChronicleEvent } from './types.js';
4
- export declare const SCHEMA_VERSION = 3;
4
+ export declare const SCHEMA_VERSION = 4;
5
5
  export declare const READ_CHUNK_BYTES: number;
6
6
  export declare const SQLITE_SOURCE_PREFIX = "sqlite:";
7
7
  export declare const SQLITE_INGEST_BATCH = 2000;
@@ -54,6 +54,9 @@ export interface ChronicleFileLineageRow {
54
54
  toolName: string;
55
55
  providerId: string;
56
56
  modelId: string;
57
+ logicalRequestId: string;
58
+ promptManifestId: string;
59
+ provenanceConfidence: 'explicit' | 'correlated' | 'inferred' | 'unknown';
57
60
  source: string;
58
61
  }
59
62
  export interface ChronicleMetricsSummary {