pi-smart-compact 9.6.2 → 9.7.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.
package/dist/index.js CHANGED
@@ -1,13 +1,11 @@
1
1
  // @bun
2
- var __require = import.meta.require;
3
-
4
2
  // src/index.ts
5
3
  import {
6
4
  convertToLlm as convertToLlm5
7
5
  } from "@earendil-works/pi-coding-agent";
8
6
 
9
7
  // src/constants.ts
10
- var VERSION = "9.6.2";
8
+ var VERSION = "9.7.0";
11
9
  var CHARS_PER_TOKEN = 3.8;
12
10
  var MIN_COMPACTION_SAVING_RATIO = 0.1;
13
11
  var ESTIMATOR_ROUNDING_TOLERANCE_TOKENS = 1;
@@ -63,16 +61,17 @@ var CONFIG_NUMERIC_LIMITS = {
63
61
  maxLlmInputTokens: { min: 0, max: 1e6, integer: true },
64
62
  codexMaxCallMs: {
65
63
  min: 5000,
66
- max: 300000,
64
+ max: 3600000,
67
65
  integer: true,
68
66
  zeroOrRange: true
69
67
  },
70
68
  maxLatencyMs: {
71
69
  min: 5000,
72
- max: 600000,
70
+ max: 7200000,
73
71
  integer: true,
74
72
  zeroOrRange: true
75
- }
73
+ },
74
+ pendingTtlMs: { min: 1000, max: 3600000, integer: true }
76
75
  };
77
76
  var DEFAULT_CONFIG = {
78
77
  mode: "auto",
@@ -98,6 +97,7 @@ var DEFAULT_CONFIG = {
98
97
  maxLlmInputTokens: 0,
99
98
  codexMaxCallMs: 0,
100
99
  maxLatencyMs: 0,
100
+ pendingTtlMs: 300000,
101
101
  focusWeighting: true,
102
102
  zeroCallEnabled: true,
103
103
  contextGraphEnabled: true,
@@ -351,21 +351,24 @@ import crypto from "crypto";
351
351
 
352
352
  // src/utils/logger.ts
353
353
  var DEBUG = process.env.DEBUG?.includes("smart-compact") ?? false;
354
+ function ts() {
355
+ return new Date().toISOString();
356
+ }
354
357
  function warn(msg, err) {
355
358
  const detail = err instanceof Error ? err.message : err ?? "";
356
- console.error(LOG_PREFIX + " " + msg + (detail ? ": " + detail : ""));
359
+ console.error(ts() + " " + LOG_PREFIX + " " + msg + (detail ? ": " + detail : ""));
357
360
  }
358
361
  function error(msg, err) {
359
362
  const detail = err instanceof Error ? err.message + `
360
363
  ` + err.stack : err ?? "";
361
- console.error(LOG_PREFIX + " " + msg + (detail ? ": " + detail : ""));
364
+ console.error(ts() + " " + LOG_PREFIX + " " + msg + (detail ? ": " + detail : ""));
362
365
  }
363
366
  function info(msg, ...args) {
364
- console.error(LOG_PREFIX + " [info] " + msg, ...args);
367
+ console.error(ts() + " " + LOG_PREFIX + " [info] " + msg, ...args);
365
368
  }
366
369
  function debug(msg, ...args) {
367
370
  if (DEBUG)
368
- console.error(LOG_PREFIX + " [debug] " + msg, ...args);
371
+ console.error(ts() + " " + LOG_PREFIX + " [debug] " + msg, ...args);
369
372
  }
370
373
  function debugError(msg, err) {
371
374
  if (DEBUG)
@@ -420,18 +423,18 @@ function tryAcquireLock(target) {
420
423
  const token = process.pid + ":" + crypto.randomBytes(8).toString("hex");
421
424
  try {
422
425
  fs.mkdirSync(lockDir, { mode: 448 });
423
- } catch (error2) {
424
- if (error2?.code === "EEXIST")
426
+ } catch (error) {
427
+ if (error?.code === "EEXIST")
425
428
  return null;
426
- throw new Error("Failed to acquire lock for " + target, { cause: error2 });
429
+ throw new Error("Failed to acquire lock for " + target, { cause: error });
427
430
  }
428
431
  try {
429
432
  fs.writeFileSync(ownerFile, token, { mode: 384, flag: "wx" });
430
- } catch (error2) {
433
+ } catch (error) {
431
434
  try {
432
435
  fs.rmSync(lockDir, { recursive: true, force: true });
433
436
  } catch {}
434
- throw new Error("Failed to acquire lock for " + target, { cause: error2 });
437
+ throw new Error("Failed to acquire lock for " + target, { cause: error });
435
438
  }
436
439
  return () => {
437
440
  try {
@@ -474,9 +477,9 @@ async function appendLineLockedAsync(target, line, maxBytes) {
474
477
  let stat = null;
475
478
  try {
476
479
  stat = await fsp.stat(target);
477
- } catch (error2) {
478
- if (!error2 || typeof error2 !== "object" || !("code" in error2) || error2.code !== "ENOENT")
479
- throw error2;
480
+ } catch (error) {
481
+ if (!error || typeof error !== "object" || !("code" in error) || error.code !== "ENOENT")
482
+ throw error;
480
483
  }
481
484
  if (maxBytes !== undefined && stat && stat.size + payload.length > maxBytes) {
482
485
  const retainedLength = Math.min(stat.size, Math.max(0, maxBytes - payload.length));
@@ -879,12 +882,17 @@ var NUMERIC_RULES = [
879
882
  {
880
883
  key: "codexMaxCallMs",
881
884
  valid: (value) => validNumericLimit("codexMaxCallMs", value),
882
- message: () => "smart-compact config: codexMaxCallMs must be 0 or 5000\u2013300000; 0 derives a cap from maxTokens."
885
+ message: () => "smart-compact config: codexMaxCallMs must be 0 or 5000\u20133600000; 0 derives a cap from maxTokens."
883
886
  },
884
887
  {
885
888
  key: "maxLatencyMs",
886
889
  valid: (value) => validNumericLimit("maxLatencyMs", value),
887
- message: () => "smart-compact config: maxLatencyMs must be 0 or 5000\u2013600000; 0 means unlimited."
890
+ message: () => "smart-compact config: maxLatencyMs must be 0 or 5000\u20137200000; 0 means unlimited."
891
+ },
892
+ {
893
+ key: "pendingTtlMs",
894
+ valid: (value) => validNumericLimit("pendingTtlMs", value),
895
+ message: () => "smart-compact config: pendingTtlMs must be 1000\u20133600000 (staged summary TTL)."
888
896
  },
889
897
  {
890
898
  key: "minContextPercent",
@@ -980,8 +988,8 @@ function loadConfig() {
980
988
  cachedMtime = stat.mtimeMs;
981
989
  cachedPath = file;
982
990
  return cloneConfig(cachedConfig);
983
- } catch (error2) {
984
- debug("loadConfig: settings.json not found or unreadable, using defaults", error2);
991
+ } catch (error) {
992
+ debug("loadConfig: settings.json not found or unreadable, using defaults", error);
985
993
  cachedConfig = defaultConfig();
986
994
  cachedPath = null;
987
995
  return cloneConfig(cachedConfig);
@@ -1497,8 +1505,8 @@ function buildKnownPathReferenceIndex(knownPaths) {
1497
1505
  const boundarySuffixes = new Set;
1498
1506
  const normalizedPaths = [];
1499
1507
  let hasUnindexedSuffixes = false;
1500
- for (const path3 of knownPaths) {
1501
- const normalizedPath = normalizePath(path3).replace(/^\/+/, "");
1508
+ for (const path of knownPaths) {
1509
+ const normalizedPath = normalizePath(path).replace(/^\/+/, "");
1502
1510
  if (!normalizedPath)
1503
1511
  continue;
1504
1512
  normalizedPaths.push(normalizedPath);
@@ -2077,7 +2085,7 @@ function buildExtractionContext(extraction, forRange) {
2077
2085
  const files = forRange ? extraction.modifiedFiles.filter((f) => inRange(f.lastModifiedIndex)) : extraction.modifiedFiles;
2078
2086
  const readFiles = forRange ? [] : extraction.readFiles;
2079
2087
  const deletedFiles = forRange ? [] : extraction.deletedFiles;
2080
- const errors = extraction.errors.filter((error2) => inRange(error2.index));
2088
+ const errors = extraction.errors.filter((error) => inRange(error.index));
2081
2089
  const decisions = extraction.decisions.filter((decision) => inRange(decision.index));
2082
2090
  const constraints = extraction.constraints.filter((constraint) => inRange(constraint.index));
2083
2091
  const media = (extraction.mediaAttachments ?? []).filter((attachment) => inRange(attachment.index));
@@ -2314,7 +2322,7 @@ function buildSummaryPathEvidence(paths, budgetTokens = PROFILES.balanced.summar
2314
2322
  const unique = Array.from(new Set(paths.filter(Boolean)));
2315
2323
  if (!unique.length)
2316
2324
  return new Map;
2317
- const full = unique.map((path3) => [path3, summaryPathLine(path3)]);
2325
+ const full = unique.map((path) => [path, summaryPathLine(path)]);
2318
2326
  const minimumPerLine = JSON.stringify("#" + "x".repeat(12)).length + 3;
2319
2327
  const budgetChars = Math.max(unique.length * minimumPerLine, Math.min(20000, Math.max(4000, Math.floor(budgetTokens * 2))));
2320
2328
  if (full.reduce((total, [, line]) => total + line.length + 3, 0) <= budgetChars) {
@@ -2322,21 +2330,21 @@ function buildSummaryPathEvidence(paths, budgetTokens = PROFILES.balanced.summar
2322
2330
  }
2323
2331
  const digests = new Map;
2324
2332
  const owners = new Map;
2325
- for (const path3 of unique) {
2326
- const fullDigest = createHash("sha256").update(path3).digest("base64url");
2333
+ for (const path of unique) {
2334
+ const fullDigest = createHash("sha256").update(path).digest("base64url");
2327
2335
  let digest = fullDigest.slice(0, 12);
2328
2336
  const owner = owners.get(digest);
2329
- if (owner && owner !== path3) {
2337
+ if (owner && owner !== path) {
2330
2338
  digest = fullDigest;
2331
2339
  digests.set(owner, createHash("sha256").update(owner).digest("base64url"));
2332
2340
  }
2333
- owners.set(digest, path3);
2334
- digests.set(path3, digest);
2341
+ owners.set(digest, path);
2342
+ digests.set(path, digest);
2335
2343
  }
2336
2344
  const perPath = Math.max(JSON.stringify("#" + "x".repeat(12)).length, Math.floor((budgetChars - unique.length * 3) / unique.length));
2337
- return new Map(unique.map((path3) => [
2338
- path3,
2339
- compactPathLine(path3, perPath, digests.get(path3) ?? "")
2345
+ return new Map(unique.map((path) => [
2346
+ path,
2347
+ compactPathLine(path, perPath, digests.get(path) ?? "")
2340
2348
  ]));
2341
2349
  }
2342
2350
  function mergeBodies(first, second) {
@@ -3008,7 +3016,7 @@ function extractOpenLoops(msgs, extraction) {
3008
3016
  }));
3009
3017
  for (const err of extraction.errors.filter((e) => !e.resolved)) {
3010
3018
  const errLower = err.message.toLowerCase();
3011
- const errFiles = fileNeedles.filter(({ needles }) => needles.some((n) => errLower.includes(n))).map(({ path: path4 }) => path4);
3019
+ const errFiles = fileNeedles.filter(({ needles }) => needles.some((n) => errLower.includes(n))).map(({ path }) => path);
3012
3020
  loops.push({
3013
3021
  id: ID_PREFIX.OPEN_LOOP + ++loopId,
3014
3022
  type: "bugfix",
@@ -3536,27 +3544,27 @@ class SecretScrubber {
3536
3544
  findings.set("credential", (findings.get("credential") ?? 0) + 1);
3537
3545
  this.total++;
3538
3546
  };
3539
- const visit = (value2) => {
3540
- if (typeof value2 === "string") {
3541
- const result = this.scrubText(value2);
3547
+ const visit = (value) => {
3548
+ if (typeof value === "string") {
3549
+ const result = this.scrubText(value);
3542
3550
  mergeFindings(findings, result.findings);
3543
3551
  return result.value;
3544
3552
  }
3545
- if (value2 == null || typeof value2 !== "object")
3546
- return value2;
3547
- const cached = seen.get(value2);
3553
+ if (value == null || typeof value !== "object")
3554
+ return value;
3555
+ const cached = seen.get(value);
3548
3556
  if (cached !== undefined)
3549
3557
  return cached;
3550
- if (Array.isArray(value2)) {
3551
- const output2 = [];
3552
- seen.set(value2, output2);
3553
- for (const item of value2)
3554
- output2.push(visit(item));
3555
- return output2;
3558
+ if (Array.isArray(value)) {
3559
+ const output = [];
3560
+ seen.set(value, output);
3561
+ for (const item of value)
3562
+ output.push(visit(item));
3563
+ return output;
3556
3564
  }
3557
3565
  const output = {};
3558
- seen.set(value2, output);
3559
- for (const [key, item] of Object.entries(value2)) {
3566
+ seen.set(value, output);
3567
+ for (const [key, item] of Object.entries(value)) {
3560
3568
  const carriesSecret = typeof item === "string" && item.length >= 8;
3561
3569
  if (this.secretsEnabled && isSecretBearingKey(key) && carriesSecret) {
3562
3570
  output[key] = "[REDACTED:credential]";
@@ -3809,14 +3817,14 @@ function getDefaultServices() {
3809
3817
  }
3810
3818
 
3811
3819
  // src/domain/telemetry.ts
3812
- function errorFields(error2, seen = new Set) {
3813
- if (!error2 || typeof error2 !== "object") {
3814
- return { name: "", message: String(error2 ?? ""), status: null, code: "" };
3820
+ function errorFields(error, seen = new Set) {
3821
+ if (!error || typeof error !== "object") {
3822
+ return { name: "", message: String(error ?? ""), status: null, code: "" };
3815
3823
  }
3816
- if (seen.has(error2) || seen.size >= 8)
3824
+ if (seen.has(error) || seen.size >= 8)
3817
3825
  return { name: "", message: "", status: null, code: "" };
3818
- seen.add(error2);
3819
- const value = error2;
3826
+ seen.add(error);
3827
+ const value = error;
3820
3828
  const cause = value.cause ? errorFields(value.cause, seen) : null;
3821
3829
  const numericStatus = Number(value.status ?? value.statusCode);
3822
3830
  return {
@@ -3826,12 +3834,12 @@ function errorFields(error2, seen = new Set) {
3826
3834
  code: typeof value.code === "string" ? value.code : cause?.code ?? ""
3827
3835
  };
3828
3836
  }
3829
- function classifyTelemetryFailure(error2, timedOut = false) {
3830
- const fields = errorFields(error2);
3837
+ function classifyTelemetryFailure(error, timedOut = false) {
3838
+ const fields = errorFields(error);
3831
3839
  const text = (fields.name + " " + fields.code + " " + fields.message).toLowerCase();
3832
3840
  if (timedOut)
3833
3841
  return "timeout";
3834
- if (/max(?:imum)? output|output.?limit|visible[ -]output|length limit/.test(text))
3842
+ if (/max(?:imum)? output|output.?limit|visible[ -]output|length limit|stop reason length/.test(text))
3835
3843
  return "output-limit";
3836
3844
  if (/timeout|timed out|watchdog|deadline/.test(text))
3837
3845
  return "timeout";
@@ -4187,21 +4195,21 @@ function scheduleExtractionCacheCleanup() {
4187
4195
  });
4188
4196
  }
4189
4197
  function reconcileCachedErrors(errors, deltaMessages, deltaToolCalls, baseMsgCount) {
4190
- return errors.map((error2) => {
4191
- if (error2.resolved)
4192
- return { ...error2 };
4193
- if (!error2.operationSignature)
4194
- return { ...error2 };
4195
- let retryAttempted = error2.retryAttempted;
4198
+ return errors.map((error) => {
4199
+ if (error.resolved)
4200
+ return { ...error };
4201
+ if (!error.operationSignature)
4202
+ return { ...error };
4203
+ let retryAttempted = error.retryAttempted;
4196
4204
  let resolved = false;
4197
4205
  for (let j = 0;j < deltaMessages.length; j++) {
4198
4206
  const globalIndex = baseMsgCount + j;
4199
- if (globalIndex <= error2.index || globalIndex > error2.index + ERROR_RETRY_WINDOW)
4207
+ if (globalIndex <= error.index || globalIndex > error.index + ERROR_RETRY_WINDOW)
4200
4208
  continue;
4201
4209
  const message = deltaMessages[j];
4202
4210
  if (message.role !== "assistant" || !Array.isArray(message.content))
4203
4211
  continue;
4204
- const retry = message.content.flatMap(flattenToolCallBlock).find((call) => toolOperationSignature(call.name, call.arguments) === error2.operationSignature);
4212
+ const retry = message.content.flatMap(flattenToolCallBlock).find((call) => toolOperationSignature(call.name, call.arguments) === error.operationSignature);
4205
4213
  if (!retry)
4206
4214
  continue;
4207
4215
  retryAttempted = true;
@@ -4210,7 +4218,7 @@ function reconcileCachedErrors(errors, deltaMessages, deltaToolCalls, baseMsgCou
4210
4218
  if (result.role !== "toolResult" || result.isError)
4211
4219
  continue;
4212
4220
  const resultCall = deltaToolCalls.get(result.toolCallId ?? "");
4213
- const matches = retry.id == null ? Boolean(resultCall && toolOperationSignature(resultCall.name, resultCall.arguments) === error2.operationSignature) : result.toolCallId === retry.id;
4221
+ const matches = retry.id == null ? Boolean(resultCall && toolOperationSignature(resultCall.name, resultCall.arguments) === error.operationSignature) : result.toolCallId === retry.id;
4214
4222
  if (matches) {
4215
4223
  resolved = true;
4216
4224
  break;
@@ -4218,7 +4226,7 @@ function reconcileCachedErrors(errors, deltaMessages, deltaToolCalls, baseMsgCou
4218
4226
  }
4219
4227
  break;
4220
4228
  }
4221
- return { ...error2, retryAttempted, resolved };
4229
+ return { ...error, retryAttempted, resolved };
4222
4230
  });
4223
4231
  }
4224
4232
  function boundedTail(items, limit) {
@@ -4242,9 +4250,9 @@ function recentUnique(items, limit) {
4242
4250
  };
4243
4251
  }
4244
4252
  function mergeExtractions(base, delta, baseMsgCount, deltaMessages = [], deltaToolCalls = new Map) {
4245
- const offsetErrors = delta.errors.map((error2) => ({
4246
- ...error2,
4247
- index: error2.index + baseMsgCount
4253
+ const offsetErrors = delta.errors.map((error) => ({
4254
+ ...error,
4255
+ index: error.index + baseMsgCount
4248
4256
  }));
4249
4257
  const offsetDecisions = delta.decisions.map((decision) => ({
4250
4258
  ...decision,
@@ -4293,7 +4301,7 @@ function mergeExtractions(base, delta, baseMsgCount, deltaMessages = [], deltaTo
4293
4301
  const referencedFiles = recentUnique([...base.referencedFiles ?? [], ...delta.referencedFiles ?? []], EXTRACTION_LIMITS.REFERENCED_FILES);
4294
4302
  const mediaAttachments = boundedTail([...base.mediaAttachments ?? [], ...offsetMedia], EXTRACTION_LIMITS.MEDIA_ATTACHMENTS);
4295
4303
  const reconciledBaseErrors = reconcileCachedErrors(base.errors, deltaMessages, deltaToolCalls, baseMsgCount);
4296
- const errors = boundedTail([...reconciledBaseErrors, ...offsetErrors].filter((error2) => !isTransientToolDiagnostic(error2.message)), EXTRACTION_LIMITS.ERRORS);
4304
+ const errors = boundedTail([...reconciledBaseErrors, ...offsetErrors].filter((error) => !isTransientToolDiagnostic(error.message)), EXTRACTION_LIMITS.ERRORS);
4297
4305
  const decisions = boundedTail([...base.decisions, ...offsetDecisions], EXTRACTION_LIMITS.DECISIONS);
4298
4306
  const constraints = boundedTail([...base.constraints, ...offsetConstraints], EXTRACTION_LIMITS.CONSTRAINTS);
4299
4307
  const topics = boundedTail([...base.topics, ...offsetTopics], EXTRACTION_LIMITS.TOPICS);
@@ -4332,7 +4340,7 @@ function mergeExtractions(base, delta, baseMsgCount, deltaMessages = [], deltaTo
4332
4340
  ...base.lastUserMessages,
4333
4341
  ...delta.lastUserMessages
4334
4342
  ].slice(-5),
4335
- lastErrors: errors.values.filter((error2) => !error2.resolved).map((error2) => error2.message).slice(-3),
4343
+ lastErrors: errors.values.filter((error) => !error.resolved).map((error) => error.message).slice(-3),
4336
4344
  messageCount: baseMsgCount + delta.messageCount,
4337
4345
  ...Object.keys(evidenceOverflow).length ? { evidenceOverflow } : {}
4338
4346
  };
@@ -4349,8 +4357,8 @@ async function appendMetricsSnapshot(sessionId, snapshot) {
4349
4357
  ...snapshot
4350
4358
  });
4351
4359
  return true;
4352
- } catch (error2) {
4353
- warn("appendMetricsSnapshot failed", error2);
4360
+ } catch (error) {
4361
+ warn("appendMetricsSnapshot failed", error);
4354
4362
  return false;
4355
4363
  }
4356
4364
  }
@@ -4363,8 +4371,8 @@ async function appendMetricsLog(sessionId, extra, services) {
4363
4371
  ...extra
4364
4372
  });
4365
4373
  return true;
4366
- } catch (error2) {
4367
- warn("appendMetricsLog failed", error2);
4374
+ } catch (error) {
4375
+ warn("appendMetricsLog failed", error);
4368
4376
  return false;
4369
4377
  }
4370
4378
  }
@@ -4421,8 +4429,8 @@ function processAlive(pid) {
4421
4429
  try {
4422
4430
  process.kill(pid, 0);
4423
4431
  return true;
4424
- } catch (error2) {
4425
- return error2.code === "EPERM";
4432
+ } catch (error) {
4433
+ return error.code === "EPERM";
4426
4434
  }
4427
4435
  }
4428
4436
  function readLease(file) {
@@ -4441,11 +4449,11 @@ function acquireFileLease(file, staleMs) {
4441
4449
  try {
4442
4450
  fs4.writeFileSync(fd, JSON.stringify({ pid: process.pid, createdAt: Date.now(), token }) + `
4443
4451
  `);
4444
- } catch (error2) {
4452
+ } catch (error) {
4445
4453
  try {
4446
4454
  fs4.unlinkSync(file);
4447
4455
  } catch {}
4448
- throw error2;
4456
+ throw error;
4449
4457
  } finally {
4450
4458
  fs4.closeSync(fd);
4451
4459
  }
@@ -4463,9 +4471,9 @@ function acquireFileLease(file, staleMs) {
4463
4471
  }, Math.max(1e4, Math.floor(staleMs / 3)));
4464
4472
  lease.heartbeat.unref();
4465
4473
  return lease;
4466
- } catch (error2) {
4467
- if (error2.code !== "EEXIST")
4468
- throw error2;
4474
+ } catch (error) {
4475
+ if (error.code !== "EEXIST")
4476
+ throw error;
4469
4477
  return null;
4470
4478
  }
4471
4479
  };
@@ -4671,7 +4679,7 @@ function resolveMode(requested, contextPercent, extraction, additionalRisk = 0)
4671
4679
  return "fast";
4672
4680
  if (!extraction)
4673
4681
  return contextPercent < 70 ? "fast" : "balanced";
4674
- const unresolved = extraction.errors.filter((error2) => !error2.resolved).length;
4682
+ const unresolved = extraction.errors.filter((error) => !error.resolved).length;
4675
4683
  const risk = unresolved * 2 + extraction.decisions.length + extraction.constraints.length + Math.ceil(extraction.modifiedFiles.length / 5) + Math.ceil(extraction.topics.length / 4) + additionalRisk;
4676
4684
  if (risk >= 12)
4677
4685
  return "thorough";
@@ -4699,7 +4707,7 @@ function deterministicExtractionConfidence(extraction, context = {}) {
4699
4707
  score -= 0.25;
4700
4708
  if (extraction.topics.length > 6)
4701
4709
  score -= 0.15;
4702
- if (extraction.errors.filter((error2) => !error2.resolved).length > 2)
4710
+ if (extraction.errors.filter((error) => !error.resolved).length > 2)
4703
4711
  score -= 0.2;
4704
4712
  if ((extraction.mediaAttachments?.length ?? 0) > 0)
4705
4713
  score -= 0.15;
@@ -5002,8 +5010,8 @@ async function saveProjectFingerprint(projectId, sessionId, extraction) {
5002
5010
  release();
5003
5011
  }
5004
5012
  return true;
5005
- } catch (error2) {
5006
- warn("saveProjectFingerprint failed", error2);
5013
+ } catch (error) {
5014
+ warn("saveProjectFingerprint failed", error);
5007
5015
  return false;
5008
5016
  }
5009
5017
  }
@@ -5811,8 +5819,8 @@ function freshState(fp, data) {
5811
5819
  if (Date.now() - updatedAt > SEVEN_DAYS_MS) {
5812
5820
  try {
5813
5821
  fs5.unlinkSync(fp);
5814
- } catch (error2) {
5815
- debug("stale state cleanup failed", error2);
5822
+ } catch (error) {
5823
+ debug("stale state cleanup failed", error);
5816
5824
  }
5817
5825
  return null;
5818
5826
  }
@@ -5828,12 +5836,12 @@ function pruneScopedStateSnapshots(target) {
5828
5836
  for (const snapshot of snapshots.slice(Math.max(0, STATE_SNAPSHOT_MAX_FILES - 1))) {
5829
5837
  try {
5830
5838
  fs5.unlinkSync(snapshot.file);
5831
- } catch (error2) {
5832
- debug("state snapshot cleanup failed", error2);
5839
+ } catch (error) {
5840
+ debug("state snapshot cleanup failed", error);
5833
5841
  }
5834
5842
  }
5835
- } catch (error2) {
5836
- debug("state snapshot retention failed", error2);
5843
+ } catch (error) {
5844
+ debug("state snapshot retention failed", error);
5837
5845
  }
5838
5846
  }
5839
5847
  function saveCompactionState(projectId, state) {
@@ -5843,19 +5851,19 @@ function saveCompactionState(projectId, state) {
5843
5851
  if (state.scope)
5844
5852
  pruneScopedStateSnapshots(target);
5845
5853
  return true;
5846
- } catch (error2) {
5847
- warn("saveCompactionState failed", error2);
5854
+ } catch (error) {
5855
+ warn("saveCompactionState failed", error);
5848
5856
  return false;
5849
5857
  }
5850
5858
  }
5851
- function loadScopedCompactionState(scope, branchEntryIds2 = []) {
5859
+ function loadScopedCompactionState(scope, branchEntryIds = []) {
5852
5860
  const snapshotProbe = scopedCompactionStateFile(scope.projectId, scope.sessionId, "__snapshot__");
5853
5861
  let availableSnapshots = new Set;
5854
5862
  try {
5855
5863
  availableSnapshots = new Set(fs5.readdirSync(path8.dirname(snapshotProbe), { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name));
5856
5864
  } catch {}
5857
5865
  const ancestry = Array.from(new Set([
5858
- ...branchEntryIds2,
5866
+ ...branchEntryIds,
5859
5867
  ...scope.branchHeadId ? [scope.branchHeadId] : []
5860
5868
  ])).reverse();
5861
5869
  const valid = (state, branchHeadId) => Boolean(state?.scope?.schemaVersion === 2 && state.scope.projectId === scope.projectId && state.scope.sessionId === scope.sessionId && typeof state.scope.branchHeadId === "string" && (!branchHeadId || state.scope.branchHeadId === branchHeadId));
@@ -5874,8 +5882,8 @@ function loadScopedCompactionState(scope, branchEntryIds2 = []) {
5874
5882
  try {
5875
5883
  fs5.unlinkSync(legacyPath);
5876
5884
  writeJsonSync(getStatePath(scope.projectId, legacy), legacy, true);
5877
- } catch (error2) {
5878
- warn("branch state migration failed", error2);
5885
+ } catch (error) {
5886
+ warn("branch state migration failed", error);
5879
5887
  }
5880
5888
  return legacy;
5881
5889
  }
@@ -6010,7 +6018,7 @@ function mergeCompactionStates(previous, current) {
6010
6018
  const activePrevious = applyContinuityOverrides(previous, factOverrides);
6011
6019
  const currentPresent = new Set([...activeCurrent.modifiedFiles, ...activeCurrent.readFiles].map(normalizeFactKey));
6012
6020
  const currentDeleted = new Set(activeCurrent.deletedFiles.map(normalizeFactKey));
6013
- const resolvedKeys = new Set(activeCurrent.resolvedErrors.map((error2) => normalizeFactKey(error2.message)));
6021
+ const resolvedKeys = new Set(activeCurrent.resolvedErrors.map((error) => normalizeFactKey(error.message)));
6014
6022
  const isResolvedLoop = (loop) => {
6015
6023
  if (loop.type !== "bugfix" || loop.status !== "open")
6016
6024
  return false;
@@ -6025,7 +6033,7 @@ function mergeCompactionStates(previous, current) {
6025
6033
  };
6026
6034
  const decisions = mergeBy(activeCurrent.decisions, activePrevious.decisions, (item) => normalizeFactKey(item.summary), 30).map((item, index) => ({ ...item, id: ID_PREFIX.DECISION + (index + 1) }));
6027
6035
  const constraints = mergeBy(activeCurrent.constraints, activePrevious.constraints, (item) => normalizeFactKey(item.text), 30).map((item, index) => ({ ...item, id: "constraint-" + (index + 1) }));
6028
- const unresolvedErrors = mergeBy(activeCurrent.unresolvedErrors, activePrevious.unresolvedErrors.filter((error2) => !resolvedKeys.has(normalizeFactKey(error2.message))), (item) => normalizeFactKey(item.message), 15).map((item, index) => ({ ...item, id: ID_PREFIX.ERROR + (index + 1) }));
6036
+ const unresolvedErrors = mergeBy(activeCurrent.unresolvedErrors, activePrevious.unresolvedErrors.filter((error) => !resolvedKeys.has(normalizeFactKey(error.message))), (item) => normalizeFactKey(item.message), 15).map((item, index) => ({ ...item, id: ID_PREFIX.ERROR + (index + 1) }));
6029
6037
  const openLoops = mergeOpenLoops(activeCurrent.openLoops, activePrevious.openLoops.map((loop) => isResolvedLoop(loop) ? { ...loop, status: "resolved" } : loop));
6030
6038
  const currentGoalKey = activeCurrent.goalKey ?? (activeCurrent.goal ? normalizeFactKey(activeCurrent.goal) : "");
6031
6039
  const previousGoalKey = activePrevious.goalKey ?? (activePrevious.goal ? normalizeFactKey(activePrevious.goal) : "");
@@ -6122,7 +6130,7 @@ function computeDelta(prev, current) {
6122
6130
  const newModifiedFiles = current.modifiedFiles.filter((f) => !prevFiles.has(f));
6123
6131
  const prevErrorMsgs = new Set(prev.unresolvedErrors.map((e) => normalizeFactKey(e.message)));
6124
6132
  const resolvedErrorKeys = new Set([
6125
- ...current.resolvedErrors.map((error2) => normalizeFactKey(error2.message)),
6133
+ ...current.resolvedErrors.map((error) => normalizeFactKey(error.message)),
6126
6134
  ...retired("error")
6127
6135
  ]);
6128
6136
  const resolvedErrors = prev.unresolvedErrors.filter((e) => resolvedErrorKeys.has(normalizeFactKey(e.message))).map((e) => e.message);
@@ -6196,7 +6204,7 @@ function ensurePinnedPaths(summary, pinned) {
6196
6204
  if (!pinned.length)
6197
6205
  return summary;
6198
6206
  const lower = summary.toLowerCase();
6199
- const missing = pinned.map((path9) => summaryEvidenceLine(path9, TRUNC.MESSAGE)).filter((path9) => path9 && !lower.includes(path9.toLowerCase()));
6207
+ const missing = pinned.map((path) => summaryEvidenceLine(path, TRUNC.MESSAGE)).filter((path) => path && !lower.includes(path.toLowerCase()));
6200
6208
  if (!missing.length)
6201
6209
  return summary;
6202
6210
  const parsed = parseSummary(summary);
@@ -6225,7 +6233,7 @@ async function showOpenLoopsUI(ctx, sourceLoops, initialOverrides = []) {
6225
6233
  let changed = false;
6226
6234
  while (true) {
6227
6235
  const loops = applyLoopOverrides(sourceLoops, overrides);
6228
- const labels = loops.map((loop2, index) => index + 1 + ". [" + loop2.status + "/" + loop2.priority + "] " + loop2.summary.slice(0, TRUNC.TOPIC_LABEL));
6236
+ const labels = loops.map((loop, index) => index + 1 + ". [" + loop.status + "/" + loop.priority + "] " + loop.summary.slice(0, TRUNC.TOPIC_LABEL));
6229
6237
  const choice = await ctx.ui.select("Open loops", [...labels, "Done"]);
6230
6238
  if (!choice || choice === "Done")
6231
6239
  return changed ? overrides : null;
@@ -6418,13 +6426,13 @@ var SOFT_BOUNDARY_COPY = {
6418
6426
  function formatPreflightSummary(preflight, modelLabel, details = false) {
6419
6427
  const plan = preflight.plan;
6420
6428
  if (!plan) {
6421
- const lines2 = [
6429
+ const lines = [
6422
6430
  "Plan unavailable \xB7 " + explainPreflightReason(preflight.reason),
6423
6431
  "\u2713 Complete tool pairs \xB7 \u2713 zero-gap verification before apply"
6424
6432
  ];
6425
6433
  if (details)
6426
- lines2.push("Estimator messages ~" + tokenCount(preflight.rawEstimatedMessageTokens) + " \xB7 normalization unavailable", "Route " + modelLabel + " \xB7 viability " + preflight.reason);
6427
- return lines2;
6434
+ lines.push("Estimator messages ~" + tokenCount(preflight.rawEstimatedMessageTokens) + " \xB7 normalization unavailable", "Route " + modelLabel + " \xB7 viability " + preflight.reason);
6435
+ return lines;
6428
6436
  }
6429
6437
  const stateReserve = Math.max(0, (plan.finalSummaryAllowanceTokens ?? plan.summaryBudgetTokens + Math.ceil(plan.summaryBudgetTokens * POST_SUMMARY_RESERVE_RATIO)) - plan.summaryBudgetTokens);
6430
6438
  const lines = [
@@ -6716,8 +6724,8 @@ async function showCompactUI(ctx, opts) {
6716
6724
  const marker = index === selected ? "\u203A " : " ";
6717
6725
  const recommendedMark = mode === recommended.mode ? " recommended" : " ";
6718
6726
  const trait = mode === "fast" ? "quickest" : mode === "balanced" ? "default" : "deepest";
6719
- const stats2 = (viable && plan ? "~" + compactTokenCount(plan.projectedAfterTokens) + " after \xB7 " + percent(plan.projectedYield * 100) + " saved" : "unavailable \xB7 " + explainPreflightReason(preview.reason)) + " \xB7 " + trait;
6720
- const line = " " + marker + MODE_LABELS[mode].padEnd(9) + recommendedMark + " " + stats2;
6727
+ const stats = (viable && plan ? "~" + compactTokenCount(plan.projectedAfterTokens) + " after \xB7 " + percent(plan.projectedYield * 100) + " saved" : "unavailable \xB7 " + explainPreflightReason(preview.reason)) + " \xB7 " + trait;
6728
+ const line = " " + marker + MODE_LABELS[mode].padEnd(9) + recommendedMark + " " + stats;
6721
6729
  lines.push(cell(index === selected ? theme.fg("accent", theme.bold(line)) : theme.fg(viable ? mode === recommended.mode ? "success" : "text" : "muted", line)));
6722
6730
  }
6723
6731
  lines.push(divider);
@@ -6864,7 +6872,7 @@ async function* streamJsonlLines(file, chunkSize = 64 * 1024) {
6864
6872
  if (leftover.length > 0)
6865
6873
  yield leftover;
6866
6874
  } finally {
6867
- await handle.close().catch((error2) => debug("streamJsonlLines close failed", error2));
6875
+ await handle.close().catch((error) => debug("streamJsonlLines close failed", error));
6868
6876
  }
6869
6877
  }
6870
6878
  var LOG_PATH_CACHE_TTL_MS = 30000;
@@ -6906,26 +6914,26 @@ function findSessionLogFile(sessionId, cwd) {
6906
6914
  const cached = lruGet(logPathCache, cacheKey);
6907
6915
  if (cached && cached.home === home2 && cached.expiresAt > now)
6908
6916
  return cached.path;
6909
- const sessionsDir2 = getSessionsDir();
6910
- if (!fs6.existsSync(sessionsDir2))
6917
+ const sessionsDir = getSessionsDir();
6918
+ if (!fs6.existsSync(sessionsDir))
6911
6919
  return remember(null);
6912
6920
  if (directDirectory) {
6913
6921
  const direct = findLogInDirectory(directDirectory, sessionId);
6914
6922
  if (direct)
6915
6923
  return remember(direct);
6916
6924
  }
6917
- for (const subdir of fs6.readdirSync(sessionsDir2, { withFileTypes: true })) {
6925
+ for (const subdir of fs6.readdirSync(sessionsDir, { withFileTypes: true })) {
6918
6926
  if (!subdir.isDirectory())
6919
6927
  continue;
6920
- const subdirPath = path10.join(sessionsDir2, subdir.name);
6928
+ const subdirPath = path10.join(sessionsDir, subdir.name);
6921
6929
  if (subdirPath === directDirectory)
6922
6930
  continue;
6923
6931
  const found = findLogInDirectory(subdirPath, sessionId);
6924
6932
  if (found)
6925
6933
  return remember(found);
6926
6934
  }
6927
- } catch (error2) {
6928
- debug("findSessionLogFile failed", error2);
6935
+ } catch (error) {
6936
+ debug("findSessionLogFile failed", error);
6929
6937
  }
6930
6938
  return remember(null);
6931
6939
  }
@@ -6990,8 +6998,8 @@ async function readOriginalMessageMap(sessionId, wantedIds, cwd) {
6990
6998
  map
6991
6999
  }, getMaxEntries());
6992
7000
  return map;
6993
- } catch (error2) {
6994
- debug("readOriginalMessageMap failed", error2);
7001
+ } catch (error) {
7002
+ debug("readOriginalMessageMap failed", error);
6995
7003
  return null;
6996
7004
  }
6997
7005
  }
@@ -7284,12 +7292,12 @@ function prunePass(dir) {
7284
7292
  try {
7285
7293
  if (isOwnedBackupFile(full))
7286
7294
  fs7.unlinkSync(full);
7287
- } catch (error2) {
7288
- debug("prunePass unlink failed", error2);
7295
+ } catch (error) {
7296
+ debug("prunePass unlink failed", error);
7289
7297
  }
7290
7298
  }
7291
- } catch (error2) {
7292
- debug("prunePass scan failed", error2);
7299
+ } catch (error) {
7300
+ debug("prunePass scan failed", error);
7293
7301
  }
7294
7302
  }
7295
7303
  function schedulePruneBackups(dir) {
@@ -7323,8 +7331,8 @@ function prepareConversationBackup(source, sessionId, metadata = {}) {
7323
7331
  branchLeafId: metadata.branchLeafId,
7324
7332
  contextTokens: metadata.contextTokens
7325
7333
  };
7326
- } catch (error2) {
7327
- warn("prepareConversationBackup failed", error2);
7334
+ } catch (error) {
7335
+ warn("prepareConversationBackup failed", error);
7328
7336
  return null;
7329
7337
  }
7330
7338
  }
@@ -7342,8 +7350,8 @@ async function commitPreparedConversationBackup(prepared) {
7342
7350
  await atomicWriteFile(prepared.path, metadata + body);
7343
7351
  schedulePruneBackups(path11.dirname(prepared.path));
7344
7352
  return prepared.path;
7345
- } catch (error2) {
7346
- warn("commitPreparedConversationBackup failed", error2);
7353
+ } catch (error) {
7354
+ warn("commitPreparedConversationBackup failed", error);
7347
7355
  return null;
7348
7356
  }
7349
7357
  }
@@ -7376,8 +7384,8 @@ function listBackups(limit = 20) {
7376
7384
  }
7377
7385
  out.sort((left, right) => left.date < right.date ? 1 : left.date > right.date ? -1 : 0);
7378
7386
  return out.slice(0, limit);
7379
- } catch (error2) {
7380
- warn("listBackups failed", error2);
7387
+ } catch (error) {
7388
+ warn("listBackups failed", error);
7381
7389
  return [];
7382
7390
  }
7383
7391
  }
@@ -7405,8 +7413,8 @@ function readConversationBackup(file) {
7405
7413
  ...branchLeafId ? { branchLeafId } : {},
7406
7414
  ...contextTokens !== undefined && Number.isSafeInteger(contextTokens) ? { contextTokens } : {}
7407
7415
  };
7408
- } catch (error2) {
7409
- warn("readConversationBackup failed", error2);
7416
+ } catch (error) {
7417
+ warn("readConversationBackup failed", error);
7410
7418
  return null;
7411
7419
  }
7412
7420
  }
@@ -7554,10 +7562,10 @@ function extractWithCache(rc) {
7554
7562
 
7555
7563
  // src/phases/explore.ts
7556
7564
  import { Type } from "typebox";
7557
- function explicitlyRejectsTools(error2) {
7558
- if (!error2 || typeof error2 !== "object")
7565
+ function explicitlyRejectsTools(error) {
7566
+ if (!error || typeof error !== "object")
7559
7567
  return false;
7560
- const record = error2;
7568
+ const record = error;
7561
7569
  const status = Number(record.status ?? record.statusCode ?? record.response?.status);
7562
7570
  const message = String(record.message ?? "");
7563
7571
  return (status === 400 || status === 404 || status === 422) && /(?:(?:tools?|function(?:[ -]calling)?).{0,60}(?:unsupported|not supported|unknown|unavailable|invalid)|(?:unsupported|does not support|doesn't support).{0,60}(?:tools?|function))/i.test(message);
@@ -7925,13 +7933,13 @@ async function exploreConversation(llmMessages, extraction, model, auth, prevSum
7925
7933
  if (cachedSupport === false) {
7926
7934
  if (notify)
7927
7935
  notify("Tool support cached: unsupported (" + cacheLabel + ")", "info");
7928
- const report2 = await directExploration(llmMessages, extraction, model, auth, prevSummary, userNote, signal, svc);
7929
- if (!report2.boundaries.length) {
7936
+ const report = await directExploration(llmMessages, extraction, model, auth, prevSummary, userNote, signal, svc);
7937
+ if (!report.boundaries.length) {
7930
7938
  const retried = await explorationRetry(model, auth, llmMessages, extraction, userNote, signal, svc);
7931
7939
  if (retried.boundaries.length)
7932
7940
  return { report: retried, rounds: 1, toolSupported: false };
7933
7941
  }
7934
- return { report: report2, rounds: 1, toolSupported: false };
7942
+ return { report, rounds: 1, toolSupported: false };
7935
7943
  }
7936
7944
  const probeResp = await trackedComplete("explore", model, {
7937
7945
  systemPrompt: COMPACT_SYSTEM_PREFIX + `
@@ -7999,13 +8007,13 @@ async function exploreConversation(llmMessages, extraction, model, auth, prevSum
7999
8007
  if (nextToolCalls.length === 0) {
8000
8008
  const text = response.content.filter((c) => c.type === "text").map((c) => c.text).join(`
8001
8009
  `).trim();
8002
- let report2 = parseExplorationReport(text, llmMessages);
8003
- if (!report2.boundaries.length) {
8004
- report2 = await directExploration(llmMessages, extraction, model, auth, prevSummary, userNote, signal, svc);
8005
- if (report2.boundaries.length)
8010
+ let report = parseExplorationReport(text, llmMessages);
8011
+ if (!report.boundaries.length) {
8012
+ report = await directExploration(llmMessages, extraction, model, auth, prevSummary, userNote, signal, svc);
8013
+ if (report.boundaries.length)
8006
8014
  rounds++;
8007
8015
  }
8008
- return { report: report2, rounds, toolSupported: true };
8016
+ return { report, rounds, toolSupported: true };
8009
8017
  }
8010
8018
  messages.push(response);
8011
8019
  for (const tc of nextToolCalls) {
@@ -8024,21 +8032,21 @@ async function exploreConversation(llmMessages, extraction, model, auth, prevSum
8024
8032
  if (lastAssistant?.content) {
8025
8033
  const text = lastAssistant.content.filter((c) => c.type === "text").map((c) => c.text).join(`
8026
8034
  `).trim();
8027
- const report2 = parseExplorationReport(text, llmMessages);
8028
- if (report2.boundaries.length)
8029
- return { report: report2, rounds, toolSupported: true };
8035
+ const report = parseExplorationReport(text, llmMessages);
8036
+ if (report.boundaries.length)
8037
+ return { report, rounds, toolSupported: true };
8030
8038
  }
8031
8039
  } else {
8032
8040
  const text = probeResp.content.filter((c) => c.type === "text").map((c) => c.text).join(`
8033
8041
  `).trim();
8034
- let report2 = parseExplorationReport(text, llmMessages);
8035
- const parsedOk = report2.boundaries.length > 0;
8042
+ let report = parseExplorationReport(text, llmMessages);
8043
+ const parsedOk = report.boundaries.length > 0;
8036
8044
  if (!parsedOk) {
8037
- report2 = await directExploration(llmMessages, extraction, model, auth, prevSummary, userNote, signal, svc);
8045
+ report = await directExploration(llmMessages, extraction, model, auth, prevSummary, userNote, signal, svc);
8038
8046
  }
8039
8047
  if (parsedOk)
8040
8048
  toolSupport.set(cacheKey, true, svc.clock.now());
8041
- return { report: report2, rounds: 1, toolSupported: parsedOk };
8049
+ return { report, rounds: 1, toolSupported: parsedOk };
8042
8050
  }
8043
8051
  } catch (e) {
8044
8052
  const rejected = explicitlyRejectsTools(e);
@@ -8639,42 +8647,58 @@ async function summarizeBatch(batch, extraction, model, auth, signal, services,
8639
8647
  const cached = cacheKey ? getCachedBatch(cacheKey) : null;
8640
8648
  if (cached)
8641
8649
  return cached;
8642
- const resp = await trackedComplete("batch", model, {
8643
- systemPrompt: COMPACT_SYSTEM_PREFIX,
8644
- messages: [
8645
- {
8646
- role: "user",
8647
- content: [{ type: "text", text: promptPrefix }],
8648
- timestamp: Date.now()
8649
- },
8650
- {
8651
- role: "user",
8652
- content: [{ type: "text", text: dynamicSuffix }],
8653
- timestamp: Date.now()
8654
- }
8655
- ]
8656
- }, {
8650
+ const messages = [
8651
+ {
8652
+ role: "user",
8653
+ content: [{ type: "text", text: promptPrefix }],
8654
+ timestamp: Date.now()
8655
+ },
8656
+ {
8657
+ role: "user",
8658
+ content: [{ type: "text", text: dynamicSuffix }],
8659
+ timestamp: Date.now()
8660
+ }
8661
+ ];
8662
+ const baseOpts = {
8657
8663
  apiKey: auth.apiKey,
8658
8664
  headers: auth.headers,
8659
8665
  maxTokens: maxOutputTokens ?? Math.min(Math.max(1000, batch.length * 250), 4096, getProviderCaps(model.provider).maxOutputTokens),
8660
8666
  signal
8661
- }, services);
8662
- const output = resp.content.filter((c) => c.type === "text").map((c) => c.text).join(`
8667
+ };
8668
+ const attempt = async (reasoning) => {
8669
+ const resp = await trackedComplete("batch", model, {
8670
+ systemPrompt: COMPACT_SYSTEM_PREFIX,
8671
+ messages
8672
+ }, reasoning === undefined ? baseOpts : { ...baseOpts, reasoning }, services);
8673
+ const output = resp.content.filter((c) => c.type === "text").map((c) => c.text).join(`
8663
8674
  `);
8664
- const sectionMap = new Map;
8665
- const duplicateIds = new Set;
8666
- const sections = output.split(/^### /m).filter((s) => s.trim());
8667
- for (const sec of sections) {
8668
- const m = sec.match(/^CHUNK\s+(\d+):\s*(.*?)\n/i);
8669
- if (m) {
8670
- const id = parseInt(m[1], 10);
8671
- if (sectionMap.has(id))
8672
- duplicateIds.add(id);
8673
- else
8674
- sectionMap.set(id, sec);
8675
+ const sectionMap = new Map;
8676
+ const duplicateIds = new Set;
8677
+ const sections = output.split(/^### /m).filter((s) => s.trim());
8678
+ for (const sec of sections) {
8679
+ const m = sec.match(/^CHUNK\s+(\d+):\s*(.*?)\n/i);
8680
+ if (m) {
8681
+ const id = parseInt(m[1], 10);
8682
+ if (sectionMap.has(id))
8683
+ duplicateIds.add(id);
8684
+ else
8685
+ sectionMap.set(id, sec);
8686
+ }
8687
+ }
8688
+ assertCompleteBatchResponse(resp.stopReason, sectionMap, duplicateIds, batch.length);
8689
+ return sectionMap;
8690
+ };
8691
+ let sectionMap;
8692
+ try {
8693
+ sectionMap = await attempt();
8694
+ } catch (err) {
8695
+ if (err instanceof BatchSummaryFormatError && /non-terminal stop reason length/.test(err.message) && services?.thinkingLevels.summaryThinkingLevel !== "minimal") {
8696
+ info("Batch synthesis exhausted the output budget (reasoning shared it); retrying once with minimal reasoning");
8697
+ sectionMap = await attempt("minimal");
8698
+ } else {
8699
+ throw err;
8675
8700
  }
8676
8701
  }
8677
- assertCompleteBatchResponse(resp.stopReason, sectionMap, duplicateIds, batch.length);
8678
8702
  const result = batch.map((ch, i) => {
8679
8703
  const id = i + 1;
8680
8704
  const sec = sectionMap.get(id) ?? "";
@@ -8714,9 +8738,9 @@ function deterministicFileEvidence(extraction, budgetTokens, continuity = null)
8714
8738
  const deletedPaths = Array.from(new Set([...extraction.deletedFiles, ...continuity?.deletedFiles ?? []]));
8715
8739
  const evidence = buildSummaryPathEvidence([...modifiedPaths, ...readPaths, ...deletedPaths], budgetTokens);
8716
8740
  return {
8717
- modified: modifiedPaths.map((path12) => evidence.get(path12)).filter((path12) => Boolean(path12)),
8718
- read: readPaths.map((path12) => evidence.get(path12)).filter((path12) => Boolean(path12)),
8719
- deleted: deletedPaths.map((path12) => evidence.get(path12)).filter((path12) => Boolean(path12))
8741
+ modified: modifiedPaths.map((path) => evidence.get(path)).filter((path) => Boolean(path)),
8742
+ read: readPaths.map((path) => evidence.get(path)).filter((path) => Boolean(path)),
8743
+ deleted: deletedPaths.map((path) => evidence.get(path)).filter((path) => Boolean(path))
8720
8744
  };
8721
8745
  }
8722
8746
  async function assembleLLM(summaries, extraction, report, model, auth, budget, prevContext, signal, services, focus, continuity = null) {
@@ -8756,8 +8780,8 @@ function assembleFallback(summaries, extraction, steering = {}, budgetTokens = 6
8756
8780
  const detModified = files.modified;
8757
8781
  const detRead = files.read;
8758
8782
  const detDeleted = files.deleted;
8759
- const unresolved = extraction.errors.filter((error2) => !error2.resolved).map((error2) => safe(error2.message, TRUNC.PREVIEW)).filter(Boolean);
8760
- const resolved = extraction.errors.filter((error2) => error2.resolved).slice(-5).map((error2) => safe(error2.message, TRUNC.PREVIEW)).filter(Boolean);
8783
+ const unresolved = extraction.errors.filter((error) => !error.resolved).map((error) => safe(error.message, TRUNC.PREVIEW)).filter(Boolean);
8784
+ const resolved = extraction.errors.filter((error) => error.resolved).slice(-5).map((error) => safe(error.message, TRUNC.PREVIEW)).filter(Boolean);
8761
8785
  const constraints = extraction.constraints.map((item) => "- [" + item.category + "] " + safe(item.text)).filter((line) => !line.endsWith("] "));
8762
8786
  if (steering.focus?.trim())
8763
8787
  constraints.push("- [focus] Preserve detail about: " + safe(steering.focus, TRUNC.CONSTRAINT_TEXT));
@@ -8777,8 +8801,8 @@ function assembleFallback(summaries, extraction, steering = {}, budgetTokens = 6
8777
8801
  const goal = safe(extraction.mainGoal ?? "", TRUNC.DETAIL) || "Continue the current task.";
8778
8802
  const overflow = Object.entries(extraction.evidenceOverflow ?? {}).filter(([, count]) => typeof count === "number" && count > 0).map(([kind, count]) => "- Safety bound omitted " + count + " older " + kind + " item(s) from the human summary.");
8779
8803
  const critical = [
8780
- ...unresolved.map((error2) => "- Unresolved error: " + safe(error2, TRUNC.TOPIC_LABEL)),
8781
- ...resolved.map((error2) => "- Resolved error: " + safe(error2, TRUNC.TOPIC_LABEL)),
8804
+ ...unresolved.map((error) => "- Unresolved error: " + safe(error, TRUNC.TOPIC_LABEL)),
8805
+ ...resolved.map((error) => "- Resolved error: " + safe(error, TRUNC.TOPIC_LABEL)),
8782
8806
  ...overflow
8783
8807
  ];
8784
8808
  return [
@@ -8794,7 +8818,7 @@ function assembleFallback(summaries, extraction, steering = {}, budgetTokens = 6
8794
8818
  "### In Progress",
8795
8819
  ...inProgress.length ? inProgress : ["- Continue current work."],
8796
8820
  "### Blocked",
8797
- ...unresolved.length ? unresolved.map((error2) => "- " + error2) : ["- None recorded."],
8821
+ ...unresolved.length ? unresolved.map((error) => "- " + error) : ["- None recorded."],
8798
8822
  "",
8799
8823
  "## Key Decisions",
8800
8824
  ...decisions.length ? decisions : ["- None recorded."],
@@ -8946,7 +8970,7 @@ function hasListedPath(listed, file, display, normalizedOwners) {
8946
8970
  return false;
8947
8971
  }
8948
8972
  function outcomeClaims(summary, pathEvidence) {
8949
- const pathLines = new Set(Array.from(pathEvidence, ([path12, display]) => [path12, display, "`" + path12 + "`"]).flat());
8973
+ const pathLines = new Set(Array.from(pathEvidence, ([path, display]) => [path, display, "`" + path + "`"]).flat());
8950
8974
  return Array.from(new Set(summary.split(/\r?\n/).map((line) => line.replace(/^\s*(?:[-*+]|\d+[.)])\s+/, "").replace(/^\[[ x]\]\s+/i, "").trim()).filter((line) => line.length > 0 && !line.startsWith("#") && !pathLines.has(line)).filter((line) => HIGH_RISK_OUTCOME_RE.test(line)).filter((line) => /\bno\s+(?:errors?|failures?)\b/i.test(line) || !NEGATED_OUTCOME_RE.test(line)))).slice(0, 12);
8951
8975
  }
8952
8976
  function classifyOutcomeClaim(claim) {
@@ -9016,7 +9040,7 @@ function successfulToolEvidence(messages) {
9016
9040
  function successfulToolSupportsClaim(claim, tools, extraction) {
9017
9041
  const shape = semanticShape(claim);
9018
9042
  const category = classifyOutcomeClaim(claim);
9019
- if (category === "error" && extraction.errors.some((error2) => error2.resolved && hasSemanticEvidence(claim, error2.message)))
9043
+ if (category === "error" && extraction.errors.some((error) => error.resolved && hasSemanticEvidence(claim, error.message)))
9020
9044
  return true;
9021
9045
  if (category === "file" && extraction.modifiedFiles.some((file) => claim.toLowerCase().includes(file.path.toLowerCase())))
9022
9046
  return true;
@@ -9381,15 +9405,15 @@ function uniqueByText(items, text) {
9381
9405
  }
9382
9406
  function collectVerificationEvidence(extraction, continuity, evidence) {
9383
9407
  const unresolved = uniqueByText([
9384
- ...extraction.errors.flatMap((error2) => !error2.resolved ? [{ message: error2.message }] : []),
9385
- ...(continuity?.unresolvedErrors ?? []).map((error2) => ({
9386
- message: error2.message
9408
+ ...extraction.errors.flatMap((error) => !error.resolved ? [{ message: error.message }] : []),
9409
+ ...(continuity?.unresolvedErrors ?? []).map((error) => ({
9410
+ message: error.message
9387
9411
  }))
9388
9412
  ], (item) => item.message);
9389
9413
  const resolved = uniqueByText([
9390
- ...extraction.errors.flatMap((error2) => error2.resolved ? [{ message: error2.message }] : []),
9391
- ...(continuity?.resolvedErrors ?? []).map((error2) => ({
9392
- message: error2.message
9414
+ ...extraction.errors.flatMap((error) => error.resolved ? [{ message: error.message }] : []),
9415
+ ...(continuity?.resolvedErrors ?? []).map((error) => ({
9416
+ message: error.message
9393
9417
  }))
9394
9418
  ], (item) => item.message).slice(-5);
9395
9419
  const steeringConstraints = [];
@@ -9476,16 +9500,16 @@ function verifyPathCoverage(parsed, extraction, continuity, evidence, accumulato
9476
9500
  return { modified, read, deleted, rendered };
9477
9501
  }
9478
9502
  function verifyErrorEvidence(normalizedSummary, collected, accumulator) {
9479
- for (const error2 of collected.unresolved) {
9480
- const snippet = summaryEvidenceLine(error2.message, TRUNC.ERROR_SNIPPET).toLowerCase().replace(/\\/g, "/");
9503
+ for (const error of collected.unresolved) {
9504
+ const snippet = summaryEvidenceLine(error.message, TRUNC.ERROR_SNIPPET).toLowerCase().replace(/\\/g, "/");
9481
9505
  if (snippet.length > 5 && !normalizedSummary.includes(snippet)) {
9482
- addGap(accumulator, { kind: "missing-error", message: error2.message }, 5);
9506
+ addGap(accumulator, { kind: "missing-error", message: error.message }, 5);
9483
9507
  }
9484
9508
  }
9485
- for (const error2 of collected.resolved) {
9486
- const snippet = summaryEvidenceLine(error2.message, TRUNC.ERROR_SNIPPET).toLowerCase().replace(/\\/g, "/");
9509
+ for (const error of collected.resolved) {
9510
+ const snippet = summaryEvidenceLine(error.message, TRUNC.ERROR_SNIPPET).toLowerCase().replace(/\\/g, "/");
9487
9511
  if (snippet.length > 5 && !normalizedSummary.includes(snippet)) {
9488
- addGap(accumulator, { kind: "missing-error", message: error2.message, resolved: true }, 2);
9512
+ addGap(accumulator, { kind: "missing-error", message: error.message, resolved: true }, 2);
9489
9513
  }
9490
9514
  }
9491
9515
  }
@@ -9565,7 +9589,7 @@ function verifyFileReferences(summary, extraction, continuity, evidence, collect
9565
9589
  ...renderedPaths.flatMap(extractFileRefs),
9566
9590
  ...continuity?.modifiedFiles ?? [],
9567
9591
  ...continuity?.readFiles ?? [],
9568
- ...(continuity?.unresolvedErrors ?? []).flatMap((error2) => error2.files),
9592
+ ...(continuity?.unresolvedErrors ?? []).flatMap((error) => error.files),
9569
9593
  ...(continuity?.openLoops ?? []).flatMap((loop) => loop.files)
9570
9594
  ]));
9571
9595
  const knownFileIndex = buildKnownPathReferenceIndex(knownFiles);
@@ -9593,8 +9617,8 @@ function verifyProgressConsistency(parsed, extraction, collected, paths, accumul
9593
9617
  const needles = buildUniquePathNeedlesFromIndex(file.path, modifiedPathOwners);
9594
9618
  if (!needles.some((needle) => doneRefs.has(needle)))
9595
9619
  continue;
9596
- const unresolved = collected.unresolved.find((error2) => {
9597
- const firstLine = error2.message.split(/\r?\n/, 1)[0] ?? "";
9620
+ const unresolved = collected.unresolved.find((error) => {
9621
+ const firstLine = error.message.split(/\r?\n/, 1)[0] ?? "";
9598
9622
  const refs = extractFileRefs(firstLine).map(normalizePath);
9599
9623
  return needles.some((needle) => refs.includes(normalizePath(needle)));
9600
9624
  });
@@ -9645,19 +9669,19 @@ function patchDeterministic(summary, gaps, extraction, continuity = null, eviden
9645
9669
  const deletedPaths = Array.from(new Set([...extraction.deletedFiles, ...continuity?.deletedFiles ?? []]));
9646
9670
  const pathEvidence = buildSummaryPathEvidence([...modifiedPaths, ...readPaths, ...deletedPaths], evidence.summaryBudgetTokens);
9647
9671
  const replaceFileSection = (kind, paths) => {
9648
- const body = paths.map((path12) => "- " + (pathEvidence.get(path12) ?? JSON.stringify(path12))).join(`
9672
+ const body = paths.map((path) => "- " + (pathEvidence.get(path) ?? JSON.stringify(path))).join(`
9649
9673
  `);
9650
9674
  canonical = upsertSection(canonical, kind, body || "- None recorded.");
9651
9675
  };
9652
9676
  const safe = (value, max = TRUNC.MESSAGE) => summaryEvidenceLine(value, max);
9653
9677
  const unresolvedMessages = Array.from(new Set([
9654
- ...extraction.errors.filter((error2) => !error2.resolved).map((error2) => error2.message),
9655
- ...(continuity?.unresolvedErrors ?? []).map((error2) => error2.message)
9678
+ ...extraction.errors.filter((error) => !error.resolved).map((error) => error.message),
9679
+ ...(continuity?.unresolvedErrors ?? []).map((error) => error.message)
9656
9680
  ]));
9657
9681
  const unresolvedLoops = (continuity?.openLoops ?? []).filter((loop) => loop.status !== "resolved");
9658
9682
  const blockedItems = [
9659
9683
  ...unresolvedMessages.map((message) => safe(message)).filter(Boolean).map((message) => "- " + message),
9660
- ...unresolvedLoops.map((loop) => safe(loop.summary)).filter(Boolean).map((summary2) => "- " + summary2)
9684
+ ...unresolvedLoops.map((loop) => safe(loop.summary)).filter(Boolean).map((summary) => "- " + summary)
9661
9685
  ];
9662
9686
  const patchBlockedNone = () => {
9663
9687
  const progress = findSection(canonical, "progress");
@@ -9732,8 +9756,8 @@ function patchDeterministic(summary, gaps, extraction, continuity = null, eviden
9732
9756
  canonical = upsertSection(canonical, "goal", safe(gap.goal, TRUNC.DETAIL) || "Continue the current task.");
9733
9757
  break;
9734
9758
  case "missing-open-loops": {
9735
- const current = extraction.errors.filter((error2) => !error2.resolved).map((error2) => safe(error2.message, TRUNC.SNIPPET)).filter(Boolean).map((message) => "- [high] Resolve " + message);
9736
- const carriedErrors = (continuity?.unresolvedErrors ?? []).map((error2) => safe(error2.message, TRUNC.SNIPPET)).filter(Boolean).map((message) => "- [high] Resolve " + message);
9759
+ const current = extraction.errors.filter((error) => !error.resolved).map((error) => safe(error.message, TRUNC.SNIPPET)).filter(Boolean).map((message) => "- [high] Resolve " + message);
9760
+ const carriedErrors = (continuity?.unresolvedErrors ?? []).map((error) => safe(error.message, TRUNC.SNIPPET)).filter(Boolean).map((message) => "- [high] Resolve " + message);
9737
9761
  const carriedLoops = (continuity?.openLoops ?? []).filter((loop) => loop.status !== "resolved").map((loop) => ({
9738
9762
  priority: loop.priority,
9739
9763
  summary: safe(loop.summary, TRUNC.SNIPPET)
@@ -9828,8 +9852,8 @@ Return the COMPLETE corrected summary in the same format.`;
9828
9852
  ]));
9829
9853
  const preserved = originalSections.every((section) => !section.body.trim() || Boolean(patchedBodies.get(sectionIdentity(section))));
9830
9854
  return preserved ? patched : summary;
9831
- } catch (error2) {
9832
- debug("patchSummary LLM failed", error2);
9855
+ } catch (error) {
9856
+ debug("patchSummary LLM failed", error);
9833
9857
  return summary;
9834
9858
  }
9835
9859
  }
@@ -9905,20 +9929,20 @@ function failureAction(kind) {
9905
9929
  return "For local stack diagnostics, restart Pi with DEBUG=smart-compact and reproduce.";
9906
9930
  }
9907
9931
  }
9908
- function formatGenerationFailureForUi(error2) {
9909
- const kind = classifyTelemetryFailure(error2);
9932
+ function formatGenerationFailureForUi(error) {
9933
+ const kind = classifyTelemetryFailure(error);
9910
9934
  return kind + ". " + failureAction(kind);
9911
9935
  }
9912
- function formatCompactErrorForUi(error2) {
9913
- if (error2 instanceof VerificationGateError) {
9914
- const kinds = error2.gapKinds.slice(0, 4).join(", ") || "unknown";
9915
- return "Verification stopped apply at the " + error2.stage + " gate: " + error2.score + "/100, " + error2.gapCount + (error2.gapCount === 1 ? " unresolved gap [" : " unresolved gaps [") + kinds + "]. Conversation unchanged. " + "Review /smart-compact metrics; do not bypass verification. For local evidence, restart Pi with DEBUG=smart-compact.";
9936
+ function formatCompactErrorForUi(error) {
9937
+ if (error instanceof VerificationGateError) {
9938
+ const kinds = error.gapKinds.slice(0, 4).join(", ") || "unknown";
9939
+ return "Verification stopped apply at the " + error.stage + " gate: " + error.score + "/100, " + error.gapCount + (error.gapCount === 1 ? " unresolved gap [" : " unresolved gaps [") + kinds + "]. Conversation unchanged. " + "Review /smart-compact metrics; do not bypass verification. For local evidence, restart Pi with DEBUG=smart-compact.";
9916
9940
  }
9917
- if (error2 instanceof YieldGateError) {
9918
- const reason = error2.reason === "target-miss" ? "target missed" : "saving below 10%";
9919
- return "Yield check stopped apply: estimated " + error2.estimatedAfterTokens.toLocaleString() + "t after vs " + error2.targetAfterTokens.toLocaleString() + "t target (" + reason + "). Conversation unchanged. Try /smart-compact balanced for a larger target; safety checks still apply.";
9941
+ if (error instanceof YieldGateError) {
9942
+ const reason = error.reason === "target-miss" ? "target missed" : "saving below 10%";
9943
+ return "Yield check stopped apply: estimated " + error.estimatedAfterTokens.toLocaleString() + "t after vs " + error.targetAfterTokens.toLocaleString() + "t target (" + reason + "). Conversation unchanged. Try /smart-compact balanced for a larger target; safety checks still apply.";
9920
9944
  }
9921
- return "Smart compact failed [" + classifyTelemetryFailure(error2) + "]. Conversation unchanged. " + failureAction(classifyTelemetryFailure(error2));
9945
+ return "Smart compact failed [" + classifyTelemetryFailure(error) + "]. Conversation unchanged. " + failureAction(classifyTelemetryFailure(error));
9922
9946
  }
9923
9947
 
9924
9948
  // src/app/steps/synthesize.ts
@@ -9931,8 +9955,8 @@ async function summarizeConversation(rc) {
9931
9955
  const refined = resolveMode("auto", rc.contextPercent, extraction, continuityRisk(rc.previousState) + (rc.adapted ? 12 : 0));
9932
9956
  if (refined !== rc.mode) {
9933
9957
  rc.mode = refined;
9934
- const policy2 = MODE_POLICIES[refined];
9935
- rc.services.budget.setLimits(resolveCallBudget(rc.config.maxLlmCalls, refined, rc.maxLlmCalls, rc.flags.autoTriggered && !rc.flags.skipCompact), effectiveBudget(rc.config.maxLlmInputTokens, policy2.maxInputTokens, rc.maxLlmInputTokens), policy2.maxOutputTokens);
9958
+ const policy = MODE_POLICIES[refined];
9959
+ rc.services.budget.setLimits(resolveCallBudget(rc.config.maxLlmCalls, refined, rc.maxLlmCalls, rc.flags.autoTriggered && !rc.flags.skipCompact), effectiveBudget(rc.config.maxLlmInputTokens, policy.maxInputTokens, rc.maxLlmInputTokens), policy.maxOutputTokens);
9936
9960
  rc.notify("Auto strategy refined to " + refined + " within the planned " + rc.profile + " window", "info");
9937
9961
  }
9938
9962
  }
@@ -9972,9 +9996,9 @@ async function summarizeConversation(rc) {
9972
9996
  phaseName: "Synthesize",
9973
9997
  detail: "Building a deterministic continuation summary \xB7 no LLM call"
9974
9998
  });
9975
- const finalSummary2 = assembleFallback([], extraction, { focus: rc.focus, note: rc.userNote }, pc.summaryBudgetTokens, rc.previousState);
9999
+ const finalSummary = assembleFallback([], extraction, { focus: rc.focus, note: rc.userNote }, pc.summaryBudgetTokens, rc.previousState);
9976
10000
  setCachedSynthesis(cacheKey, {
9977
- finalSummary: finalSummary2,
10001
+ finalSummary,
9978
10002
  method: "heuristic",
9979
10003
  summaries: [],
9980
10004
  explorationReport: null,
@@ -9983,7 +10007,7 @@ async function summarizeConversation(rc) {
9983
10007
  });
9984
10008
  rc.notify("Zero-call deterministic compaction (high-confidence extraction)", "info");
9985
10009
  Object.assign(rc, {
9986
- finalSummary: finalSummary2,
10010
+ finalSummary,
9987
10011
  method: "heuristic",
9988
10012
  methodForMetrics: "zero-call",
9989
10013
  generationFallbacks: [],
@@ -10012,11 +10036,11 @@ async function summarizeConversation(rc) {
10012
10036
  let summaryAuth;
10013
10037
  try {
10014
10038
  summaryAuth = await resolveStageAuth(rc, "summary");
10015
- } catch (error2) {
10039
+ } catch (error) {
10016
10040
  cacheable = false;
10017
10041
  generationFallbacks.push("summary route unavailable");
10018
- debugError("Summary route unavailable", error2);
10019
- rc.notify("Summary route unavailable \xB7 using deterministic fallback [" + formatGenerationFailureForUi(error2) + "]", "warning");
10042
+ debugError("Summary route unavailable", error);
10043
+ rc.notify("Summary route unavailable \xB7 using deterministic fallback [" + formatGenerationFailureForUi(error) + "]", "warning");
10020
10044
  }
10021
10045
  if (!summaryAuth) {
10022
10046
  showProgressOverlay(rc.ctx, {
@@ -10128,6 +10152,9 @@ async function summarizeConversation(rc) {
10128
10152
  totalBatches
10129
10153
  });
10130
10154
  const concurrency = rc.providerCaps.concurrencyLimit;
10155
+ if (rc.services.thinkingLevels.summaryThinkingLevel == null) {
10156
+ rc.notify("Summary thinking level unset \u2014 provider-default reasoning may share the batch output budget (length-truncated batches retry once at minimal reasoning)", "info");
10157
+ }
10131
10158
  if (totalBatches <= 1) {
10132
10159
  const single = batches[0];
10133
10160
  if (single) {
@@ -10213,8 +10240,8 @@ async function summarizeConversation(rc) {
10213
10240
  if (r)
10214
10241
  summaries.push(...r);
10215
10242
  const failedBatches = errors.filter(Boolean);
10216
- for (const error2 of failedBatches)
10217
- debugError("Synthesis batch used deterministic fallback", error2);
10243
+ for (const error of failedBatches)
10244
+ debugError("Synthesis batch used deterministic fallback", error);
10218
10245
  if (failedBatches.length) {
10219
10246
  cacheable = false;
10220
10247
  generationFallbacks.push(failedBatches.length + " synthesis batch fallback(s)");
@@ -10331,8 +10358,8 @@ async function verifyAndPatch(rc) {
10331
10358
  try {
10332
10359
  const verifyAuth = await resolveStageAuth(rc, "verify");
10333
10360
  summary = await patchSummary(summary, verification.gaps, rc.verifyModel ?? rc.summaryModel, verifyAuth, rc.cancellation.signal, rc.services);
10334
- } catch (error2) {
10335
- debugError("LLM verification patch used deterministic fallback", error2);
10361
+ } catch (error) {
10362
+ debugError("LLM verification patch used deterministic fallback", error);
10336
10363
  }
10337
10364
  if (summary !== beforePatch) {
10338
10365
  llmPatched = true;
@@ -10543,11 +10570,11 @@ function bunSqliteAdapter(db) {
10543
10570
  const result = fn(...args);
10544
10571
  db.exec("COMMIT");
10545
10572
  return result;
10546
- } catch (error2) {
10573
+ } catch (error) {
10547
10574
  try {
10548
10575
  db.exec("ROLLBACK");
10549
10576
  } catch {}
10550
- throw error2;
10577
+ throw error;
10551
10578
  }
10552
10579
  },
10553
10580
  close: () => db.close()
@@ -10563,11 +10590,11 @@ function nodeSqliteAdapter(db) {
10563
10590
  const result = fn(...args);
10564
10591
  db.exec("COMMIT");
10565
10592
  return result;
10566
- } catch (error2) {
10593
+ } catch (error) {
10567
10594
  try {
10568
10595
  db.exec("ROLLBACK");
10569
10596
  } catch {}
10570
- throw error2;
10597
+ throw error;
10571
10598
  }
10572
10599
  },
10573
10600
  close: () => db.close()
@@ -10810,7 +10837,10 @@ function addFact(db, scope, sessionNodeId, kind, title, content, relatedPaths =
10810
10837
  });
10811
10838
  node.factKey = factKey(keyText);
10812
10839
  node.id = stableId(scope.projectId, scope.sessionId, kind, node.factKey, scope.branchHeadId ?? "");
10813
- if (sameActiveFact(latestLineageFact(db, scope, kind, node.factKey), node))
10840
+ const latest = latestLineageFact(db, scope, kind, node.factKey);
10841
+ if (sameActiveFact(latest, node))
10842
+ return;
10843
+ if (latest && latest.status !== "active" && node.status === "active")
10814
10844
  return;
10815
10845
  upsertNode(db, node);
10816
10846
  linkNodes(db, scope.projectId, sessionNodeId, node.id, "contains", 1, node.updatedAt);
@@ -10895,7 +10925,10 @@ function indexCompactionState(projectId, state) {
10895
10925
  relatedPaths: item.files,
10896
10926
  confidence: item.priority === "critical" || item.priority === "high" ? 0.98 : 0.88
10897
10927
  });
10898
- if (sameActiveFact(latestLineageFact(db, scope, "loop", node.factKey), node))
10928
+ const latestLoop = latestLineageFact(db, scope, "loop", node.factKey);
10929
+ if (sameActiveFact(latestLoop, node))
10930
+ continue;
10931
+ if (latestLoop && latestLoop.status !== "active" && node.status === "active")
10899
10932
  continue;
10900
10933
  upsertNode(db, node);
10901
10934
  linkNodes(db, projectId, sessionNode.id, node.id, "contains", 1, now);
@@ -10935,8 +10968,8 @@ function indexCompactionState(projectId, state) {
10935
10968
  });
10936
10969
  transaction();
10937
10970
  return true;
10938
- } catch (error2) {
10939
- warn("indexCompactionState failed", error2);
10971
+ } catch (error) {
10972
+ warn("indexCompactionState failed", error);
10940
10973
  return false;
10941
10974
  }
10942
10975
  }
@@ -10944,8 +10977,8 @@ var pendingCompactionIndexes = new Map;
10944
10977
  var compactionIndexDrainScheduled = false;
10945
10978
  var MAX_PENDING_COMPACTION_INDEXES = 64;
10946
10979
  function settleIndexJob(job, indexed) {
10947
- for (const resolve2 of job.resolve)
10948
- resolve2(indexed);
10980
+ for (const resolve of job.resolve)
10981
+ resolve(indexed);
10949
10982
  }
10950
10983
  function drainCompactionIndexes() {
10951
10984
  compactionIndexDrainScheduled = false;
@@ -10959,8 +10992,8 @@ function drainCompactionIndexes() {
10959
10992
  for (const job of jobs) {
10960
10993
  settleIndexJob(job, indexCompactionState(job.projectId, job.state));
10961
10994
  }
10962
- } catch (error2) {
10963
- warn("context graph index drain failed", error2);
10995
+ } catch (error) {
10996
+ warn("context graph index drain failed", error);
10964
10997
  for (const job of jobs)
10965
10998
  settleIndexJob(job, false);
10966
10999
  } finally {
@@ -10981,22 +11014,25 @@ function scheduleCompactionStateIndex(projectId, state) {
10981
11014
  if (!sessionId || !branchHeadId || state.scope?.projectId !== projectId)
10982
11015
  return Promise.resolve(false);
10983
11016
  const key = projectId + "\x00" + sessionId + "\x00" + branchHeadId;
10984
- return new Promise((resolve2) => {
11017
+ return new Promise((resolve) => {
10985
11018
  const existing = pendingCompactionIndexes.get(key);
10986
11019
  if (existing) {
10987
11020
  existing.state = state;
10988
- existing.resolve.push(resolve2);
11021
+ existing.resolve.push(resolve);
10989
11022
  return;
10990
11023
  }
10991
11024
  if (pendingCompactionIndexes.size >= MAX_PENDING_COMPACTION_INDEXES) {
10992
11025
  warn("context graph index queue full; new derived update was rejected");
10993
- resolve2(false);
11026
+ resolve(false);
10994
11027
  return;
10995
11028
  }
10996
- pendingCompactionIndexes.set(key, { projectId, state, resolve: [resolve2] });
11029
+ pendingCompactionIndexes.set(key, { projectId, state, resolve: [resolve] });
10997
11030
  armCompactionIndexDrain();
10998
11031
  });
10999
11032
  }
11033
+ function flushCompactionStateIndexes() {
11034
+ drainCompactionIndexes();
11035
+ }
11000
11036
  function closeContextMemory(projectId, kind, content, status) {
11001
11037
  const db = openDatabase();
11002
11038
  const rows = db.query(`
@@ -11225,8 +11261,8 @@ function recallContext(scope, query, options = {}) {
11225
11261
  break;
11226
11262
  }
11227
11263
  return [...deduped.values()];
11228
- } catch (error2) {
11229
- warn("recallContext failed", error2);
11264
+ } catch (error) {
11265
+ warn("recallContext failed", error);
11230
11266
  return [];
11231
11267
  }
11232
11268
  }
@@ -11260,6 +11296,42 @@ function formatRecallResults(results, maxChars = 6000) {
11260
11296
 
11261
11297
  `);
11262
11298
  }
11299
+ function getContextGraphStats(projectId) {
11300
+ try {
11301
+ const db = openDatabase();
11302
+ const row = db.query(`
11303
+ SELECT count(*) AS totalNodes,
11304
+ sum(CASE WHEN status = 'active' THEN 1 ELSE 0 END) AS activeNodes,
11305
+ count(DISTINCT CASE WHEN kind = 'session' THEN session_id END) AS sessions,
11306
+ max(updated_at) AS lastUpdatedAt
11307
+ FROM context_nodes WHERE project_id = ? AND kind NOT IN ('project')
11308
+ `).get(projectId);
11309
+ return {
11310
+ totalNodes: Number(row?.totalNodes ?? 0),
11311
+ activeNodes: Number(row?.activeNodes ?? 0),
11312
+ sessions: Number(row?.sessions ?? 0),
11313
+ lastUpdatedAt: row?.lastUpdatedAt == null ? null : Number(row.lastUpdatedAt)
11314
+ };
11315
+ } catch {
11316
+ return { totalNodes: 0, activeNodes: 0, sessions: 0, lastUpdatedAt: null };
11317
+ }
11318
+ }
11319
+ function forgetProjectGraph(projectId) {
11320
+ try {
11321
+ flushCompactionStateIndexes();
11322
+ const db = openDatabase();
11323
+ const transaction = db.transaction(() => {
11324
+ db.query("DELETE FROM context_nodes_fts WHERE node_id IN (SELECT id FROM context_nodes WHERE project_id = ?)").run(projectId);
11325
+ db.query("DELETE FROM context_edges WHERE project_id = ?").run(projectId);
11326
+ db.query("DELETE FROM context_nodes WHERE project_id = ?").run(projectId);
11327
+ });
11328
+ transaction();
11329
+ return true;
11330
+ } catch (error) {
11331
+ warn("forgetProjectGraph failed", error);
11332
+ return false;
11333
+ }
11334
+ }
11263
11335
 
11264
11336
  // src/domain/provider-evaluation.ts
11265
11337
  function providerStage(phase) {
@@ -11804,10 +11876,10 @@ var POLICY_PATHS = new Set([
11804
11876
  "autoTrigger",
11805
11877
  "showStatus"
11806
11878
  ]);
11807
- function applyGlobalSettingRuntime(path14, ctx, policy, contextTools) {
11808
- if (POLICY_PATHS.has(path14))
11879
+ function applyGlobalSettingRuntime(path, ctx, policy, contextTools) {
11880
+ if (POLICY_PATHS.has(path))
11809
11881
  policy.restore(ctx);
11810
- if (path14 === "contextGraphEnabled")
11882
+ if (path === "contextGraphEnabled")
11811
11883
  contextTools.apply();
11812
11884
  }
11813
11885
 
@@ -12073,8 +12145,8 @@ function createNativeContinuityBridge(opts = {}) {
12073
12145
  fs10.chmodSync(target, 384);
12074
12146
  } catch {}
12075
12147
  });
12076
- } catch (error2) {
12077
- debug("native continuity stage failed", error2);
12148
+ } catch (error) {
12149
+ debug("native continuity stage failed", error);
12078
12150
  }
12079
12151
  },
12080
12152
  take(scope) {
@@ -12094,8 +12166,8 @@ function createNativeContinuityBridge(opts = {}) {
12094
12166
  }
12095
12167
  return sameScope(entry.scope, scope) && now() - entry.createdAt <= ttlMs ? entry.text : null;
12096
12168
  });
12097
- } catch (error2) {
12098
- debug("native continuity take failed", error2);
12169
+ } catch (error) {
12170
+ debug("native continuity take failed", error);
12099
12171
  return null;
12100
12172
  }
12101
12173
  },
@@ -12114,15 +12186,15 @@ function createNativeContinuityBridge(opts = {}) {
12114
12186
  } catch {}
12115
12187
  }
12116
12188
  });
12117
- } catch (error2) {
12118
- debug("native continuity clear failed", error2);
12189
+ } catch (error) {
12190
+ debug("native continuity clear failed", error);
12119
12191
  }
12120
12192
  },
12121
12193
  size() {
12122
12194
  try {
12123
12195
  return locked(() => prune(0).length);
12124
- } catch (error2) {
12125
- debug("native continuity size failed", error2);
12196
+ } catch (error) {
12197
+ debug("native continuity size failed", error);
12126
12198
  return 0;
12127
12199
  }
12128
12200
  }
@@ -12163,7 +12235,7 @@ function createSettledAutoTrigger(options = {}) {
12163
12235
  return;
12164
12236
  const requestToken = Symbol(sessionId);
12165
12237
  active.set(sessionId, requestToken);
12166
- await new Promise((resolve2) => {
12238
+ await new Promise((resolve) => {
12167
12239
  let finished = false;
12168
12240
  const finish = () => {
12169
12241
  if (finished)
@@ -12171,7 +12243,7 @@ function createSettledAutoTrigger(options = {}) {
12171
12243
  finished = true;
12172
12244
  if (active.get(sessionId) === requestToken)
12173
12245
  active.delete(sessionId);
12174
- resolve2();
12246
+ resolve();
12175
12247
  };
12176
12248
  try {
12177
12249
  ctx.compact({
@@ -12180,13 +12252,13 @@ function createSettledAutoTrigger(options = {}) {
12180
12252
  noteCompaction(sessionId);
12181
12253
  finish();
12182
12254
  },
12183
- onError: (error2) => {
12184
- debugError("Settled smart compact request failed", error2);
12255
+ onError: (error) => {
12256
+ debugError("Settled smart compact request failed", error);
12185
12257
  finish();
12186
12258
  }
12187
12259
  });
12188
- } catch (error2) {
12189
- debugError("Settled smart compact request failed", error2);
12260
+ } catch (error) {
12261
+ debugError("Settled smart compact request failed", error);
12190
12262
  finish();
12191
12263
  }
12192
12264
  });
@@ -12430,14 +12502,13 @@ Paths: ` + relatedPaths.join(", ") : ""));
12430
12502
  ],
12431
12503
  details: { memory, redactions: scrubber.count() }
12432
12504
  };
12433
- } catch (error2) {
12434
- debugError("Project memory persistence failed", error2);
12435
- const message = scrubber.scrubText(error2 instanceof Error ? error2.message : String(error2)).value;
12505
+ } catch (error) {
12506
+ debugError("Project memory persistence failed", error);
12507
+ const message = scrubber.scrubText(error instanceof Error ? error.message : String(error)).value;
12436
12508
  throw new Error("Project memory could not be saved: " + message);
12437
12509
  }
12438
12510
  }
12439
12511
  });
12440
- availability.apply();
12441
12512
  pi.on("session_start", availability.apply);
12442
12513
  return availability;
12443
12514
  }
@@ -12468,8 +12539,8 @@ function createContextToolAvailability(pi) {
12468
12539
  hiddenByConfig.clear();
12469
12540
  disabledByConfig = false;
12470
12541
  }
12471
- } catch (error2) {
12472
- debugError("Context tool availability update failed", error2);
12542
+ } catch (error) {
12543
+ debugError("Context tool availability update failed", error);
12473
12544
  }
12474
12545
  }
12475
12546
  };
@@ -12856,14 +12927,14 @@ function summarizeDashboard(entries) {
12856
12927
  const durations = entries.map(metricDuration).filter(Boolean);
12857
12928
  const success = entries.filter((e) => statusLabel(e.status) === "success").length;
12858
12929
  const timeout = entries.filter((e) => e.status === "timeout").length;
12859
- const error2 = entries.filter((e) => e.status === "error").length;
12930
+ const error = entries.filter((e) => e.status === "error").length;
12860
12931
  const dryRun = entries.filter((e) => e.status === "dry-run").length;
12861
12932
  const scored = entries.map((e) => e.verificationScore).filter((v) => typeof v === "number");
12862
12933
  return {
12863
12934
  runs: entries.length,
12864
12935
  success,
12865
12936
  timeout,
12866
- error: error2,
12937
+ error,
12867
12938
  dryRun,
12868
12939
  successRate: entries.length ? success / entries.length : 0,
12869
12940
  avgDuration: Math.round(average(durations)),
@@ -13202,7 +13273,8 @@ var ACTIONS = {
13202
13273
  dashboard: "dashboard",
13203
13274
  restore: "restore",
13204
13275
  loops: "loops",
13205
- settings: "settings"
13276
+ settings: "settings",
13277
+ forget: "forget"
13206
13278
  };
13207
13279
  var MODES = {
13208
13280
  auto: "auto",
@@ -13415,12 +13487,12 @@ Dashboard: ` + dashboard : ""));
13415
13487
  const usage = ctx.getContextUsage?.();
13416
13488
  const totalTokens = usage?.tokens ?? 0;
13417
13489
  const contextPercent = safeContextPercent(totalTokens, ctx.model?.contextWindow);
13418
- const percent2 = Math.round(contextPercent);
13490
+ const percent = Math.round(contextPercent);
13419
13491
  if (!totalTokens || totalTokens < MIN_TOKEN_THRESHOLD) {
13420
- return textResult("Context is not large enough for compaction (" + totalTokens.toLocaleString() + " tokens, " + percent2 + "%). No action needed.");
13492
+ return textResult("Context is not large enough for compaction (" + totalTokens.toLocaleString() + " tokens, " + percent + "%). No action needed.");
13421
13493
  }
13422
13494
  if (contextPercent < config.minContextPercent) {
13423
- return textResult("Compaction skipped: context " + percent2 + "% (" + totalTokens.toLocaleString() + " / " + (ctx.model?.contextWindow ?? 0).toLocaleString() + " tokens), below the " + config.minContextPercent + "% agent-tool threshold. tool=XX% measures tool-output ratio, not context usage. " + "For deliberate early compaction, the user can run /smart-compact; preview and safety checks still apply.");
13495
+ return textResult("Compaction skipped: context " + percent + "% (" + totalTokens.toLocaleString() + " / " + (ctx.model?.contextWindow ?? 0).toLocaleString() + " tokens), below the " + config.minContextPercent + "% agent-tool threshold. tool=XX% measures tool-output ratio, not context usage. " + "For deliberate early compaction, the user can run /smart-compact; preview and safety checks still apply.");
13424
13496
  }
13425
13497
  const current = ctx.model;
13426
13498
  const { segModel, sumModel, verifyModel } = resolveModels(ctx, current, config);
@@ -13475,9 +13547,9 @@ Dashboard: ` + dashboard : ""));
13475
13547
  return textResult("Smart compact cancelled by " + outcome.source + "; no summary was staged.");
13476
13548
  }
13477
13549
  return textResult("Smart compact skipped: " + outcome.reason.replace(/-/g, " ") + ". No summary was staged.");
13478
- } catch (error2) {
13479
- debugError("Smart compact tool failed", error2);
13480
- throw new Error(formatCompactErrorForUi(error2));
13550
+ } catch (error) {
13551
+ debugError("Smart compact tool failed", error);
13552
+ throw new Error(formatCompactErrorForUi(error));
13481
13553
  }
13482
13554
  }
13483
13555
  });
@@ -13751,7 +13823,7 @@ var LIMIT_SETTINGS = [
13751
13823
  id: "codexMaxCallMs",
13752
13824
  label: "Codex call watchdog",
13753
13825
  description: "Zero derives the per-call watchdog automatically.",
13754
- placeholder: "0 or 5000\u2013300000; blank uses default",
13826
+ placeholder: "0 or 5000\u20133600000; blank uses default",
13755
13827
  parse: numberParser(CONFIG_NUMERIC_LIMITS.codexMaxCallMs),
13756
13828
  format: scalarFormat
13757
13829
  },
@@ -13759,9 +13831,17 @@ var LIMIT_SETTINGS = [
13759
13831
  id: "maxLatencyMs",
13760
13832
  label: "Pipeline latency limit",
13761
13833
  description: "Zero disables the overall pipeline deadline.",
13762
- placeholder: "0 or 5000\u2013600000; blank uses default",
13834
+ placeholder: "0 or 5000\u20137200000; blank uses default",
13763
13835
  parse: numberParser(CONFIG_NUMERIC_LIMITS.maxLatencyMs),
13764
13836
  format: scalarFormat
13837
+ },
13838
+ {
13839
+ id: "pendingTtlMs",
13840
+ label: "Staged summary TTL",
13841
+ description: "How long a staged summary waits for commit before expiry.",
13842
+ placeholder: "1000\u20133600000; blank uses default",
13843
+ parse: numberParser(CONFIG_NUMERIC_LIMITS.pendingTtlMs),
13844
+ format: scalarFormat
13765
13845
  }
13766
13846
  ];
13767
13847
  var PATH_SETTINGS = [
@@ -13865,17 +13945,17 @@ class InputSettingEditor extends Container3 {
13865
13945
  let parsed;
13866
13946
  try {
13867
13947
  parsed = setting.parse(value);
13868
- } catch (error2) {
13869
- this.status.setText(error2 instanceof Error ? error2.message : String(error2));
13948
+ } catch (error) {
13949
+ this.status.setText(error instanceof Error ? error.message : String(error));
13870
13950
  this.requestRender();
13871
13951
  return;
13872
13952
  }
13873
13953
  this.saving = true;
13874
13954
  this.status.setText("Saving\u2026");
13875
13955
  this.requestRender();
13876
- this.pending = this.save(parsed).then(() => this.done()).catch((error2) => {
13956
+ this.pending = this.save(parsed).then(() => this.done()).catch((error) => {
13877
13957
  this.saving = false;
13878
- this.status.setText(error2 instanceof Error ? error2.message : String(error2));
13958
+ this.status.setText(error instanceof Error ? error.message : String(error));
13879
13959
  this.requestRender();
13880
13960
  });
13881
13961
  };
@@ -13934,9 +14014,9 @@ class ModelSettingEditor extends Container3 {
13934
14014
  const selected = models.find((candidate) => candidate.value === item.value);
13935
14015
  if (!selected)
13936
14016
  return;
13937
- this.pending = save(selected.settingValue).then(done).catch((error2) => {
14017
+ this.pending = save(selected.settingValue).then(done).catch((error) => {
13938
14018
  this.saving = false;
13939
- this.status.setText(error2 instanceof Error ? error2.message : String(error2));
14019
+ this.status.setText(error instanceof Error ? error.message : String(error));
13940
14020
  this.requestRender();
13941
14021
  });
13942
14022
  };
@@ -14024,9 +14104,9 @@ function modelSettingsItems(ctx, requestRender, writeConfig = writeGlobalConfigV
14024
14104
  });
14025
14105
  }
14026
14106
  return new ModelSettingEditor(choices, selectedValue, requestRender, async (selected) => {
14027
- const effective2 = await writeConfig(setting.id, selected === "default" ? undefined : selected);
14107
+ const effective = await writeConfig(setting.id, selected === "default" ? undefined : selected);
14028
14108
  item.currentValue = selected;
14029
- item.description = `${setting.description} Effective global value: ${String(effective2[setting.id])}.`;
14109
+ item.description = `${setting.description} Effective global value: ${String(effective[setting.id])}.`;
14030
14110
  }, close);
14031
14111
  };
14032
14112
  return item;
@@ -14128,8 +14208,8 @@ class GlobalSettingsCoordinator {
14128
14208
  this.pending.delete(id);
14129
14209
  this.emit(id, { display, config });
14130
14210
  }
14131
- } catch (error2) {
14132
- onError(error2 instanceof Error ? error2.message : String(error2));
14211
+ } catch (error) {
14212
+ onError(error instanceof Error ? error.message : String(error));
14133
14213
  if (this.pending.get(id)?.revision === revision) {
14134
14214
  this.pending.delete(id);
14135
14215
  this.emit(id, {
@@ -14413,8 +14493,8 @@ function sessionSettingsList(policy, ctx, done) {
14413
14493
  ctx.ui.notify(result.error, "error");
14414
14494
  list.updateValue(id, displayValue(field, policy));
14415
14495
  }
14416
- } catch (error2) {
14417
- ctx.ui.notify(error2 instanceof Error ? error2.message : String(error2), "error");
14496
+ } catch (error) {
14497
+ ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
14418
14498
  list.updateValue(id, displayValue(policyField(id), policy));
14419
14499
  }
14420
14500
  }, done);
@@ -14511,9 +14591,9 @@ function createSettingsController(root, display, requestRender) {
14511
14591
  };
14512
14592
  }
14513
14593
  async function showSmartCompactSettings(ctx, policy, coordinator = new GlobalSettingsCoordinator, onApplied = () => {}) {
14514
- const writeConfig = async (path16, value) => {
14515
- const config = await writeGlobalConfigValue(path16, value);
14516
- await onApplied(path16, config);
14594
+ const writeConfig = async (path, value) => {
14595
+ const config = await writeGlobalConfigValue(path, value);
14596
+ await onApplied(path, config);
14517
14597
  return config;
14518
14598
  };
14519
14599
  await ctx.ui.custom((tui, theme, _keybindings, done) => {
@@ -14592,10 +14672,10 @@ async function restoreBackup(ctx) {
14592
14672
  if (result.cancelled)
14593
14673
  ctx.ui.notify("Restore cancelled", "info");
14594
14674
  return;
14595
- } catch (error2) {
14596
- debugError("Exact backup restore fork unavailable", error2);
14597
- if (!/Invalid entry ID for forking/i.test(error2 instanceof Error ? error2.message : String(error2))) {
14598
- ctx.ui.notify("Exact restore failed: " + (error2 instanceof Error ? error2.message : String(error2)), "error");
14675
+ } catch (error) {
14676
+ debugError("Exact backup restore fork unavailable", error);
14677
+ if (!/Invalid entry ID for forking/i.test(error instanceof Error ? error.message : String(error))) {
14678
+ ctx.ui.notify("Exact restore failed: " + (error instanceof Error ? error.message : String(error)), "error");
14599
14679
  return;
14600
14680
  }
14601
14681
  }
@@ -14611,10 +14691,36 @@ async function restoreBackup(ctx) {
14611
14691
  });
14612
14692
  if (result.cancelled)
14613
14693
  ctx.ui.notify("Restore cancelled", "info");
14614
- } catch (error2) {
14615
- debugError("Backup restore into new session failed", error2);
14616
- ctx.ui.notify("Restore failed: " + (error2 instanceof Error ? error2.message : String(error2)), "error");
14694
+ } catch (error) {
14695
+ debugError("Backup restore into new session failed", error);
14696
+ ctx.ui.notify("Restore failed: " + (error instanceof Error ? error.message : String(error)), "error");
14697
+ }
14698
+ }
14699
+ async function forgetProjectMemory(ctx) {
14700
+ if (ctx.mode !== "tui") {
14701
+ ctx.ui.notify("Forgetting project memory requires TUI mode for its confirmation dialog", "warning");
14702
+ return;
14703
+ }
14704
+ const projectId = deriveProjectIdFromCwd(ctx.cwd);
14705
+ if (!projectId) {
14706
+ ctx.ui.notify("Project memory must be forgotten from a project directory", "warning");
14707
+ return;
14708
+ }
14709
+ const stats = getContextGraphStats(projectId);
14710
+ if (!stats.totalNodes) {
14711
+ ctx.ui.notify("No persisted memory for this project", "info");
14712
+ return;
14713
+ }
14714
+ const confirmed = await ctx.ui.confirm("Forget project memory?", "Permanently delete " + stats.totalNodes + " memory nodes (" + stats.sessions + " session" + (stats.sessions === 1 ? "" : "s") + ") for this project, including FTS copies and edges. This cannot be undone. Compaction state (restore data) and backups are kept.");
14715
+ if (!confirmed) {
14716
+ ctx.ui.notify("Forget cancelled \u2014 project memory untouched", "info");
14717
+ return;
14617
14718
  }
14719
+ if (!forgetProjectGraph(projectId)) {
14720
+ ctx.ui.notify("Forget failed \u2014 the graph database reported an error (see DEBUG=smart-compact log)", "error");
14721
+ return;
14722
+ }
14723
+ ctx.ui.notify("Forgotten: deleted " + stats.totalNodes + " memory nodes for this project", "info");
14618
14724
  }
14619
14725
  async function manageOpenLoops(ctx) {
14620
14726
  const projectId = deriveProjectIdFromCwd(ctx.cwd);
@@ -14712,6 +14818,7 @@ function registerSmartCompactCommand(pi, dependencies) {
14712
14818
  "restore",
14713
14819
  "loops",
14714
14820
  "settings",
14821
+ "forget",
14715
14822
  "fast",
14716
14823
  "balanced",
14717
14824
  "thorough",
@@ -14747,12 +14854,16 @@ function registerSmartCompactCommand(pi, dependencies) {
14747
14854
  await manageOpenLoops(ctx);
14748
14855
  return;
14749
14856
  }
14857
+ if (input.action === "forget") {
14858
+ await forgetProjectMemory(ctx);
14859
+ return;
14860
+ }
14750
14861
  if (input.action === "settings") {
14751
14862
  if (ctx.mode !== "tui") {
14752
14863
  ctx.ui.notify("Smart Compact settings require TUI mode. Use settings.json for permanent defaults.", "warning");
14753
14864
  return;
14754
14865
  }
14755
- await showSmartCompactSettings(ctx, dependencies.policy, settingsCoordinator, (path16) => dependencies.onGlobalSettingApplied?.(path16, ctx));
14866
+ await showSmartCompactSettings(ctx, dependencies.policy, settingsCoordinator, (path) => dependencies.onGlobalSettingApplied?.(path, ctx));
14756
14867
  return;
14757
14868
  }
14758
14869
  const config = loadConfig();
@@ -14788,9 +14899,9 @@ function registerSmartCompactCommand(pi, dependencies) {
14788
14899
  timeoutMs: input.timeoutMs,
14789
14900
  force: true
14790
14901
  });
14791
- } catch (error2) {
14792
- debugError("Manual smart compact failed", error2);
14793
- ctx.ui.notify(formatCompactErrorForUi(error2), "error");
14902
+ } catch (error) {
14903
+ debugError("Manual smart compact failed", error);
14904
+ ctx.ui.notify(formatCompactErrorForUi(error), "error");
14794
14905
  }
14795
14906
  }
14796
14907
  });
@@ -14888,12 +14999,12 @@ function createSmartCompactPolicy(pi) {
14888
14999
  ctx.ui.setStatus(STATUS_KEY, current.showStatus ? statusText(effective) : undefined);
14889
15000
  return effective;
14890
15001
  };
14891
- const restoreToolMembership = (enabled2) => {
15002
+ const restoreToolMembership = (enabled) => {
14892
15003
  const active = pi.getActiveTools();
14893
15004
  const hasTool = active.includes(SMART_COMPACT_TOOL_NAME);
14894
- if (enabled2 && !hasTool) {
15005
+ if (enabled && !hasTool) {
14895
15006
  pi.setActiveTools([...new Set([...active, SMART_COMPACT_TOOL_NAME])]);
14896
- } else if (!enabled2 && hasTool) {
15007
+ } else if (!enabled && hasTool) {
14897
15008
  pi.setActiveTools(active.filter((name) => name !== SMART_COMPACT_TOOL_NAME));
14898
15009
  }
14899
15010
  };
@@ -14905,8 +15016,8 @@ function createSmartCompactPolicy(pi) {
14905
15016
  const effective = apply(ctx);
14906
15017
  pi.appendEntry(SMART_COMPACT_POLICY_ENTRY, { version: POLICY_VERSION, overrides: { ...overrides } });
14907
15018
  return { ok: true, policy: effective };
14908
- } catch (error2) {
14909
- debugError("Smart Compact policy update failed", error2);
15019
+ } catch (error) {
15020
+ debugError("Smart Compact policy update failed", error);
14910
15021
  overrides = previous;
14911
15022
  try {
14912
15023
  restoreToolMembership(previousToolEnabled);
@@ -15001,6 +15112,7 @@ function smartCompactExtension(pi) {
15001
15112
  return write;
15002
15113
  };
15003
15114
  const commitCandidates = createCompactionCommitStore({
15115
+ ttlMs: loadConfig().pendingTtlMs,
15004
15116
  onDiscard: (pending, reason) => {
15005
15117
  recordApplyFailure(pending, reason);
15006
15118
  }
@@ -15022,8 +15134,8 @@ function smartCompactExtension(pi) {
15022
15134
  else
15023
15135
  signal.addEventListener("abort", discardOnAbort, { once: true });
15024
15136
  return !signal.aborted;
15025
- } catch (error2) {
15026
- warn("Failed to stage smart compaction commit candidate", error2);
15137
+ } catch (error) {
15138
+ warn("Failed to stage smart compaction commit candidate", error);
15027
15139
  recordApplyFailure(pending, "apply-error");
15028
15140
  return false;
15029
15141
  }
@@ -15034,11 +15146,11 @@ function smartCompactExtension(pi) {
15034
15146
  runLock: isRunning,
15035
15147
  onNativeApplyError,
15036
15148
  policy,
15037
- onGlobalSettingApplied(path16, ctx) {
15149
+ onGlobalSettingApplied(path, ctx) {
15038
15150
  try {
15039
- applyGlobalSettingRuntime(path16, ctx, policy, contextToolAvailability);
15040
- } catch (error2) {
15041
- debugError("Smart Compact runtime settings refresh failed", error2);
15151
+ applyGlobalSettingRuntime(path, ctx, policy, contextToolAvailability);
15152
+ } catch (error) {
15153
+ debugError("Smart Compact runtime settings refresh failed", error);
15042
15154
  }
15043
15155
  }
15044
15156
  });
@@ -15056,8 +15168,8 @@ function smartCompactExtension(pi) {
15056
15168
  ...loadConfig(),
15057
15169
  autoTrigger: true
15058
15170
  });
15059
- } catch (error2) {
15060
- debugError("Settled smart compact trigger stopped", error2);
15171
+ } catch (error) {
15172
+ debugError("Settled smart compact trigger stopped", error);
15061
15173
  }
15062
15174
  });
15063
15175
  pi.on("session_before_compact", async (event, ctx) => {
@@ -15158,9 +15270,9 @@ function smartCompactExtension(pi) {
15158
15270
  ctx.ui.notify("Compaction applied, but durable persistence was incomplete: " + persistenceFailures.join(", "), "warning");
15159
15271
  }
15160
15272
  activateOnlineDamage(candidate);
15161
- } catch (error2) {
15273
+ } catch (error) {
15162
15274
  clearCompactProgress(ctx);
15163
- warn("Failed to commit applied smart compaction", error2);
15275
+ warn("Failed to commit applied smart compaction", error);
15164
15276
  }
15165
15277
  return;
15166
15278
  }
@@ -15236,8 +15348,8 @@ function smartCompactExtension(pi) {
15236
15348
  if (observation.report.damageScore > 0) {
15237
15349
  ctx.ui.notify("Post-compaction damage detected: " + observation.report.summary, "warning");
15238
15350
  }
15239
- } catch (error2) {
15240
- warn("online damage monitor message_end failed", error2);
15351
+ } catch (error) {
15352
+ warn("online damage monitor message_end failed", error);
15241
15353
  }
15242
15354
  });
15243
15355
  pi.on("session_shutdown", async (_event, ctx) => {
@@ -15255,7 +15367,7 @@ function smartCompactExtension(pi) {
15255
15367
  });
15256
15368
  }
15257
15369
  export {
15258
- resolveModels,
15370
+ smartCompactExtension as default,
15259
15371
  findModelById,
15260
- smartCompactExtension as default
15372
+ resolveModels
15261
15373
  };