@wrongstack/core 0.256.0 → 0.257.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.
@@ -6,7 +6,7 @@ export { a as PermissionDecision, P as PermissionPolicy, T as TrustPolicy } from
6
6
  export { C as CheckpointInfo, R as RewindResult, a as RewindResultExtended, S as SessionRewinder } from '../session-rewinder-C9HnMkhP.js';
7
7
  export { a as AddAttachmentInput, c as Attachment, d as AttachmentKind, e as AttachmentMeta, b as AttachmentRef, A as AttachmentStore, D as DefaultSessionReader } from '../session-reader-CqRvaL5v.js';
8
8
  export { D as DEFAULT_AUTONOMY_CONFIG, a as DEFAULT_CONTEXT_CONFIG, b as DEFAULT_SESSION_LOGGING_CONFIG, c as DEFAULT_SESSION_PRUNE_DAYS, d as DEFAULT_TOOLS_CONFIG } from '../default-config-CXsDvOmP.js';
9
- export { D as DefaultSecretScrubber, a as DefaultSecretVault, S as SecretVaultOptions, d as decryptConfigSecrets, e as encryptConfigSecrets, i as isSecretField, m as migratePlaintextSecrets, r as rewriteConfigEncrypted } from '../secret-vault-BkYkJWQs.js';
9
+ export { D as DefaultSecretScrubber, a as DefaultSecretVault, S as SecretVaultOptions, d as decryptConfigSecrets, e as encryptConfigSecrets, i as isSecretField, m as migratePlaintextSecrets, r as rewriteConfigEncrypted } from '../secret-vault-gxtFZYBt.js';
10
10
  export { D as DefaultLogger, a as DefaultLoggerOptions, L as LogFormat, n as noOpLogger } from '../logger-DmmQhf4P.js';
11
11
  export { D as DefaultPathResolver, a as DefaultTokenCounter } from '../path-resolver-CbkT-RMU.js';
12
12
  export { p as MEMORY_TYPE_LABELS, q as MemoryClearedPayload, r as MemoryConsolidatedPayload, k as MemoryEntry, s as MemoryForgottenPayload, t as MemoryPriority, m as MemoryRelevanceContext, u as MemoryRememberedPayload, M as MemoryScope, l as MemoryStore, v as MemoryType, S as ScoredEntry } from '../brain-TjEEwSpw.js';
@@ -1098,16 +1098,6 @@ var MEMORY_TYPE_LABELS = {
1098
1098
  anti_pattern: "Anti-pattern"
1099
1099
  };
1100
1100
 
1101
- // src/utils/expect-defined.ts
1102
- function expectDefined(value, label) {
1103
- if (value === null || value === void 0) {
1104
- const err = new Error("Expected value to be defined");
1105
- err.name = "ExpectDefinedError";
1106
- throw err;
1107
- }
1108
- return value;
1109
- }
1110
-
1111
1101
  // src/utils/token-estimate.ts
1112
1102
  var RoughTokenEstimate = (text, charsPerToken = 3.5) => Math.max(1, Math.ceil(text.length / charsPerToken));
1113
1103
  var CALIBRATION_GLOBAL_KEY = "__global__";
@@ -1126,12 +1116,15 @@ function getCachedEstimate(key, compute) {
1126
1116
  const existing = ESTIMATE_CACHE.get(key);
1127
1117
  if (existing !== void 0) return existing;
1128
1118
  if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
1129
- const keys = [...ESTIMATE_CACHE.keys()];
1130
- for (let i = 0; i < Math.floor(ESTIMATE_CACHE_MAX_SIZE / 4); i++) {
1131
- ESTIMATE_CACHE.delete(expectDefined(keys[i]));
1119
+ let evicted = 0;
1120
+ const maxEvict = Math.floor(ESTIMATE_CACHE_MAX_SIZE / 4);
1121
+ for (const k of ESTIMATE_CACHE.keys()) {
1122
+ if (evicted >= maxEvict) break;
1123
+ ESTIMATE_CACHE.delete(k);
1124
+ evicted++;
1132
1125
  }
1133
1126
  }
1134
- const estimate = compute();
1127
+ const estimate = compute(key);
1135
1128
  ESTIMATE_CACHE.set(key, estimate);
1136
1129
  return estimate;
1137
1130
  }
@@ -1140,13 +1133,11 @@ function estimateToolInputTokens(input) {
1140
1133
  if (input === null || typeof input !== "object") {
1141
1134
  return RoughTokenEstimate(String(input));
1142
1135
  }
1143
- const key = JSON.stringify(input);
1144
- return getCachedEstimate(key, () => RoughTokenEstimate(key));
1136
+ return getCachedEstimate(JSON.stringify(input), (key) => RoughTokenEstimate(key));
1145
1137
  }
1146
1138
  function estimateToolResultTokens(content) {
1147
1139
  if (typeof content === "string") return RoughTokenEstimate(content);
1148
- const key = JSON.stringify(content);
1149
- return getCachedEstimate(key, () => RoughTokenEstimate(key));
1140
+ return getCachedEstimate(JSON.stringify(content), (key) => RoughTokenEstimate(key));
1150
1141
  }
1151
1142
  function estimateTextTokens(text) {
1152
1143
  return RoughTokenEstimate(text);
@@ -1231,6 +1222,16 @@ function estimateRequestTokens(messages, systemPrompt, tools, calibrationKey = C
1231
1222
  };
1232
1223
  }
1233
1224
 
1225
+ // src/utils/expect-defined.ts
1226
+ function expectDefined(value, label) {
1227
+ if (value === null || value === void 0) {
1228
+ const err = new Error("Expected value to be defined");
1229
+ err.name = "ExpectDefinedError";
1230
+ throw err;
1231
+ }
1232
+ return value;
1233
+ }
1234
+
1234
1235
  // src/utils/message-invariants.ts
1235
1236
  function repairToolUseAdjacency(messages) {
1236
1237
  const removedToolUses = [];
@@ -1324,6 +1325,25 @@ function isEmptyMessage(msg) {
1324
1325
  }
1325
1326
 
1326
1327
  // src/execution/compaction-core.ts
1328
+ function emitCompactionMetrics(event, metrics) {
1329
+ console.log(
1330
+ JSON.stringify({
1331
+ level: "debug",
1332
+ event,
1333
+ messageCount: metrics.messageCount,
1334
+ preserveStart: metrics.preserveStart,
1335
+ fastPathIterations: metrics.fastPathIterations,
1336
+ fastPathInnerIterations: metrics.fastPathInnerIterations,
1337
+ // Ratios — anything > 2.0 indicates the inner loop is running more than expected
1338
+ fastPathInnerPerOuter: metrics.fastPathIterations > 0 ? metrics.fastPathInnerIterations / metrics.fastPathIterations : 0,
1339
+ fullPassIterations: metrics.fullPassIterations,
1340
+ fullPassInnerIterations: metrics.fullPassInnerIterations,
1341
+ fullPassInnerPerOuter: metrics.fullPassIterations > 0 ? metrics.fullPassInnerIterations / metrics.fullPassIterations : 0,
1342
+ tokensSaved: metrics.tokensSaved,
1343
+ changed: metrics.changed
1344
+ })
1345
+ );
1346
+ }
1327
1347
  var estimateMessages = estimateMessageTokens;
1328
1348
  function hasTextContent(m) {
1329
1349
  if (typeof m.content === "string") return m.content.trim().length > 0;
@@ -1340,37 +1360,78 @@ function findPreserveStart(messages, preserveK) {
1340
1360
  preserveStart = i;
1341
1361
  }
1342
1362
  }
1363
+ let forwardWalkIterations = 0;
1364
+ let forwardWalkInnerIterations = 0;
1343
1365
  for (let i = preserveStart; i < messages.length; i++) {
1366
+ forwardWalkIterations++;
1344
1367
  const m = messages[i];
1345
1368
  if (!m || typeof m.content === "string" || !Array.isArray(m.content)) continue;
1346
- const hasToolUse3 = m.content.some((b) => b.type === "tool_use");
1369
+ const hasToolUse3 = m.content.some((b) => {
1370
+ forwardWalkInnerIterations++;
1371
+ return b.type === "tool_use";
1372
+ });
1347
1373
  if (hasToolUse3 && i + 1 < messages.length) {
1348
1374
  const next = messages[i + 1];
1349
- if (next && next.role === "user" && typeof next.content !== "string" && Array.isArray(next.content) && next.content.some((b) => b.type === "tool_result")) {
1375
+ if (next && next.role === "user" && typeof next.content !== "string" && Array.isArray(next.content) && next.content.some((b) => {
1376
+ forwardWalkInnerIterations++;
1377
+ return b.type === "tool_result";
1378
+ })) {
1350
1379
  preserveStart = i + 1;
1351
1380
  }
1352
1381
  }
1353
1382
  }
1383
+ console.log(
1384
+ JSON.stringify({
1385
+ level: "debug",
1386
+ event: "compaction.find_preserve_start.ended",
1387
+ messageCount: messages.length,
1388
+ preserveK,
1389
+ preserveStart,
1390
+ forwardWalkIterations,
1391
+ forwardWalkInnerIterations,
1392
+ forwardWalkInnerPerOuter: forwardWalkIterations > 0 ? forwardWalkInnerIterations / forwardWalkIterations : 0
1393
+ })
1394
+ );
1354
1395
  return preserveStart;
1355
1396
  }
1356
1397
  function eliseOldToolResults(messages, opts) {
1357
1398
  const preserveStart = findPreserveStart(messages, opts.preserveK);
1358
1399
  let hasOversized = false;
1400
+ let fastPathIterations = 0;
1401
+ let fastPathInnerIterations = 0;
1359
1402
  for (let i = 0; i < preserveStart && !hasOversized; i++) {
1403
+ fastPathIterations++;
1360
1404
  const msg = messages[i];
1361
1405
  if (!msg || !Array.isArray(msg.content)) continue;
1362
1406
  for (const b of msg.content) {
1407
+ fastPathInnerIterations++;
1363
1408
  if (b.type === "tool_result" && estimateToolResultTokens(b.content) >= opts.eliseThreshold) {
1364
1409
  hasOversized = true;
1365
1410
  break;
1366
1411
  }
1367
1412
  }
1368
1413
  }
1414
+ emitCompactionMetrics(
1415
+ hasOversized ? "compaction.elision.fast_path.oversized_found" : "compaction.elision.fast_path.no_oversized",
1416
+ {
1417
+ messageCount: messages.length,
1418
+ preserveStart,
1419
+ fastPathIterations,
1420
+ fastPathInnerIterations,
1421
+ fullPassIterations: 0,
1422
+ fullPassInnerIterations: 0,
1423
+ tokensSaved: 0,
1424
+ changed: false
1425
+ }
1426
+ );
1369
1427
  if (!hasOversized) return { messages, saved: 0, changed: false };
1370
1428
  let saved = 0;
1371
1429
  let changed = false;
1430
+ let fullPassIterations = 0;
1431
+ let fullPassInnerIterations = 0;
1372
1432
  const next = new Array(messages.length);
1373
1433
  for (let i = 0; i < messages.length; i++) {
1434
+ fullPassIterations++;
1374
1435
  const msg = messages[i];
1375
1436
  if (i >= preserveStart || !msg || !Array.isArray(msg.content)) {
1376
1437
  next[i] = msg;
@@ -1396,7 +1457,33 @@ function eliseOldToolResults(messages, opts) {
1396
1457
  next[i] = { ...msg, content: newContent };
1397
1458
  changed = true;
1398
1459
  }
1460
+ fullPassInnerIterations += original.length;
1461
+ if (process.env["NODE_ENV"] === "development" || process.env["WRONGSTACK_DEBUG"] === "1") {
1462
+ const ratio = fullPassInnerIterations / fullPassIterations;
1463
+ if (ratio > 10) {
1464
+ console.error(
1465
+ JSON.stringify({
1466
+ level: "error",
1467
+ event: "compaction.elision.regression",
1468
+ message: `fullPassInnerPerOuter=${ratio.toFixed(2)} exceeds threshold 10 \u2014 possible O(n\xB7m) regression`,
1469
+ messageCount: messages.length,
1470
+ fullPassIterations,
1471
+ fullPassInnerIterations
1472
+ })
1473
+ );
1474
+ }
1475
+ }
1399
1476
  }
1477
+ emitCompactionMetrics("compaction.elision.full_pass.ended", {
1478
+ messageCount: messages.length,
1479
+ preserveStart,
1480
+ fastPathIterations,
1481
+ fastPathInnerIterations,
1482
+ fullPassIterations,
1483
+ fullPassInnerIterations,
1484
+ tokensSaved: saved,
1485
+ changed
1486
+ });
1400
1487
  return { messages: changed ? next : messages, saved, changed };
1401
1488
  }
1402
1489
  function buildLosslessDigest(messages) {
@@ -1921,6 +2008,10 @@ var PATTERNS = [
1921
2008
  regex: /(?:^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?:\s|$)/g
1922
2009
  }
1923
2010
  ];
2011
+ var SIMPLE_PATTERNS = PATTERNS.filter((p) => p.type !== "high_entropy_env");
2012
+ var COMBINED_REGEX = new RegExp(SIMPLE_PATTERNS.map((p) => `(${p.regex.source})`).join("|"), "g");
2013
+ var HIGH_ENTROPY_REGEX = PATTERNS.find((p) => p.type === "high_entropy_env").regex;
2014
+ var COMBINED_REPLACEMENTS = SIMPLE_PATTERNS.map((p) => `[REDACTED:${p.type}]`);
1924
2015
  var SCRUB_CHUNK_BYTES = 64 * 1024;
1925
2016
  function hasCredentialAnchors(text) {
1926
2017
  return text.includes("-----BEGIN") || // Private keys (most unique → cheap reject)
@@ -1962,17 +2053,27 @@ var DefaultSecretScrubber = class {
1962
2053
  }
1963
2054
  scrubOne(text) {
1964
2055
  if (!hasCredentialAnchors(text)) return text;
1965
- let out = text;
1966
- for (const p of PATTERNS) {
1967
- out = out.replace(p.regex, (_match, group1, group2) => {
1968
- if (p.type === "high_entropy_env" && group1 && group2) {
1969
- return `${group1}=[REDACTED:${p.type}]`;
1970
- }
1971
- return `[REDACTED:${p.type}]`;
1972
- });
1973
- }
2056
+ let out = text.replace(
2057
+ COMBINED_REGEX,
2058
+ (match, ...groups) => {
2059
+ const idx = groups.findIndex((g) => g !== void 0);
2060
+ if (idx < 0) return match;
2061
+ const replacement = COMBINED_REPLACEMENTS[idx];
2062
+ return replacement !== void 0 ? replacement : match;
2063
+ }
2064
+ );
2065
+ out = out.replace(HIGH_ENTROPY_REGEX, (_match, group1, _group2) => {
2066
+ return `${group1}=[REDACTED:high_entropy_env]`;
2067
+ });
1974
2068
  return out;
1975
2069
  }
2070
+ /**
2071
+ * Recursively scrub every string value in an object/array graph. Secrets can
2072
+ * appear under any key — a URL query param, an `authorization` header, an
2073
+ * arbitrarily-named nested field — so we don't gate recursion on key names.
2074
+ * The per-string `scrub()` fast-path (anchor pre-scan) keeps this cheap: any
2075
+ * value without a credential anchor returns immediately without regex work.
2076
+ */
1976
2077
  scrubObject(obj) {
1977
2078
  const seen = /* @__PURE__ */ new WeakSet();
1978
2079
  const visit = (v) => {
@@ -3688,12 +3789,12 @@ var ConversationState = class {
3688
3789
  for (const m of messages) {
3689
3790
  if (m._estTokens === void 0) {
3690
3791
  m._estTokens = computeMessageTokens(m);
3691
- }
3692
- if (!hasToolBlock && Array.isArray(m.content)) {
3693
- for (const b of m.content) {
3694
- if (b.type === "tool_use" || b.type === "tool_result") {
3695
- hasToolBlock = true;
3696
- break;
3792
+ if (Array.isArray(m.content)) {
3793
+ for (const b of m.content) {
3794
+ if (b.type === "tool_use" || b.type === "tool_result") {
3795
+ hasToolBlock = true;
3796
+ break;
3797
+ }
3697
3798
  }
3698
3799
  }
3699
3800
  }
@@ -3949,7 +4050,7 @@ var DefaultSessionReader = class {
3949
4050
  this.store = opts.store;
3950
4051
  }
3951
4052
  async query(q = {}) {
3952
- const raw = await this.store.list(q.limit ? Math.max(q.limit * 4, 100) : 1e3);
4053
+ const raw = await this.store.list(q.limit ? Math.max(q.limit, 100) : 1e3);
3953
4054
  const titleNeedle = q.titleContains?.toLowerCase();
3954
4055
  const filtered = raw.filter((s) => {
3955
4056
  if (q.since && s.startedAt < q.since) return false;