@wrongstack/core 0.256.1 → 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.
package/dist/index.d.ts CHANGED
@@ -16,8 +16,8 @@ export { C as CheckpointInfo, R as RewindResult, a as RewindResultExtended, S as
16
16
  import { A as AttachmentStore, b as AttachmentRef, a as AddAttachmentInput } from './session-reader-CqRvaL5v.js';
17
17
  export { c as Attachment, d as AttachmentKind, e as AttachmentMeta, D as DefaultSessionReader, f as DefaultSessionReaderOptions, S as SessionReader } from './session-reader-CqRvaL5v.js';
18
18
  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';
19
- import { a as DefaultSecretVault } from './secret-vault-BkYkJWQs.js';
20
- export { D as DefaultSecretScrubber, S as SecretVaultOptions, d as decryptConfigSecrets, e as encryptConfigSecrets, i as isSecretField, m as migratePlaintextSecrets, r as rewriteConfigEncrypted } from './secret-vault-BkYkJWQs.js';
19
+ import { a as DefaultSecretVault } from './secret-vault-gxtFZYBt.js';
20
+ export { D as DefaultSecretScrubber, S as SecretVaultOptions, d as decryptConfigSecrets, e as encryptConfigSecrets, i as isSecretField, m as migratePlaintextSecrets, r as rewriteConfigEncrypted } from './secret-vault-gxtFZYBt.js';
21
21
  import { D as DefaultLogger } from './logger-DmmQhf4P.js';
22
22
  export { a as DefaultLoggerOptions, L as LogFormat, n as noOpLogger } from './logger-DmmQhf4P.js';
23
23
  import { D as DefaultPathResolver } from './path-resolver-CbkT-RMU.js';
@@ -504,6 +504,10 @@ declare function hasTextContent(m: Message): boolean;
504
504
  * user/assistant messages until `preserveK` are covered, then walks forward to
505
505
  * keep any tool_use/tool_result protocol pair intact — so a tool_result whose
506
506
  * tool_use is preserved is never elided.
507
+ *
508
+ * Instrumentation: emits `compaction.find_preserve_start.ended` with the
509
+ * forward-walk inner-loop count so we can track whether the `.some()` calls
510
+ * over content blocks are causing measurable O(n·m) overhead.
507
511
  */
508
512
  declare function findPreserveStart(messages: readonly Message[], preserveK: number): number;
509
513
  interface EliseResult {
package/dist/index.js CHANGED
@@ -16,8 +16,13 @@ import { pipeline } from 'stream/promises';
16
16
 
17
17
  var __defProp = Object.defineProperty;
18
18
  var __getOwnPropNames = Object.getOwnPropertyNames;
19
- var __esm = (fn, res) => function __init() {
20
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
19
+ var __esm = (fn, res, err) => function __init() {
20
+ if (err) throw err[0];
21
+ try {
22
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
23
+ } catch (e) {
24
+ throw err = [e], e;
25
+ }
21
26
  };
22
27
  var __export = (target, all) => {
23
28
  for (var name in all)
@@ -2169,16 +2174,6 @@ var MEMORY_TYPE_LABELS = {
2169
2174
  anti_pattern: "Anti-pattern"
2170
2175
  };
2171
2176
 
2172
- // src/utils/expect-defined.ts
2173
- function expectDefined(value, label) {
2174
- if (value === null || value === void 0) {
2175
- const err = new Error(label ? `Expected ${label} to be defined` : "Expected value to be defined");
2176
- err.name = "ExpectDefinedError";
2177
- throw err;
2178
- }
2179
- return value;
2180
- }
2181
-
2182
2177
  // src/utils/token-estimate.ts
2183
2178
  var RoughTokenEstimate = (text, charsPerToken = 3.5) => Math.max(1, Math.ceil(text.length / charsPerToken));
2184
2179
  var CAL_ALPHA = 0.3;
@@ -2199,12 +2194,15 @@ function getCachedEstimate(key, compute) {
2199
2194
  const existing = ESTIMATE_CACHE.get(key);
2200
2195
  if (existing !== void 0) return existing;
2201
2196
  if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
2202
- const keys = [...ESTIMATE_CACHE.keys()];
2203
- for (let i = 0; i < Math.floor(ESTIMATE_CACHE_MAX_SIZE / 4); i++) {
2204
- ESTIMATE_CACHE.delete(expectDefined(keys[i]));
2197
+ let evicted = 0;
2198
+ const maxEvict = Math.floor(ESTIMATE_CACHE_MAX_SIZE / 4);
2199
+ for (const k of ESTIMATE_CACHE.keys()) {
2200
+ if (evicted >= maxEvict) break;
2201
+ ESTIMATE_CACHE.delete(k);
2202
+ evicted++;
2205
2203
  }
2206
2204
  }
2207
- const estimate = compute();
2205
+ const estimate = compute(key);
2208
2206
  ESTIMATE_CACHE.set(key, estimate);
2209
2207
  return estimate;
2210
2208
  }
@@ -2213,13 +2211,11 @@ function estimateToolInputTokens(input) {
2213
2211
  if (input === null || typeof input !== "object") {
2214
2212
  return RoughTokenEstimate(String(input));
2215
2213
  }
2216
- const key = JSON.stringify(input);
2217
- return getCachedEstimate(key, () => RoughTokenEstimate(key));
2214
+ return getCachedEstimate(JSON.stringify(input), (key) => RoughTokenEstimate(key));
2218
2215
  }
2219
2216
  function estimateToolResultTokens(content) {
2220
2217
  if (typeof content === "string") return RoughTokenEstimate(content);
2221
- const key = JSON.stringify(content);
2222
- return getCachedEstimate(key, () => RoughTokenEstimate(key));
2218
+ return getCachedEstimate(JSON.stringify(content), (key) => RoughTokenEstimate(key));
2223
2219
  }
2224
2220
  function estimateTextTokens(text) {
2225
2221
  return RoughTokenEstimate(text);
@@ -2347,6 +2343,16 @@ function resetCalibration(calibrationKey) {
2347
2343
  _cals.delete(calibrationKey);
2348
2344
  }
2349
2345
 
2346
+ // src/utils/expect-defined.ts
2347
+ function expectDefined(value, label) {
2348
+ if (value === null || value === void 0) {
2349
+ const err = new Error(label ? `Expected ${label} to be defined` : "Expected value to be defined");
2350
+ err.name = "ExpectDefinedError";
2351
+ throw err;
2352
+ }
2353
+ return value;
2354
+ }
2355
+
2350
2356
  // src/utils/message-invariants.ts
2351
2357
  function repairToolUseAdjacency(messages) {
2352
2358
  const removedToolUses = [];
@@ -2440,6 +2446,25 @@ function isEmptyMessage(msg) {
2440
2446
  }
2441
2447
 
2442
2448
  // src/execution/compaction-core.ts
2449
+ function emitCompactionMetrics(event, metrics) {
2450
+ console.log(
2451
+ JSON.stringify({
2452
+ level: "debug",
2453
+ event,
2454
+ messageCount: metrics.messageCount,
2455
+ preserveStart: metrics.preserveStart,
2456
+ fastPathIterations: metrics.fastPathIterations,
2457
+ fastPathInnerIterations: metrics.fastPathInnerIterations,
2458
+ // Ratios — anything > 2.0 indicates the inner loop is running more than expected
2459
+ fastPathInnerPerOuter: metrics.fastPathIterations > 0 ? metrics.fastPathInnerIterations / metrics.fastPathIterations : 0,
2460
+ fullPassIterations: metrics.fullPassIterations,
2461
+ fullPassInnerIterations: metrics.fullPassInnerIterations,
2462
+ fullPassInnerPerOuter: metrics.fullPassIterations > 0 ? metrics.fullPassInnerIterations / metrics.fullPassIterations : 0,
2463
+ tokensSaved: metrics.tokensSaved,
2464
+ changed: metrics.changed
2465
+ })
2466
+ );
2467
+ }
2443
2468
  var estimateMessages = estimateMessageTokens;
2444
2469
  function hasTextContent(m) {
2445
2470
  if (typeof m.content === "string") return m.content.trim().length > 0;
@@ -2456,37 +2481,78 @@ function findPreserveStart(messages, preserveK) {
2456
2481
  preserveStart = i;
2457
2482
  }
2458
2483
  }
2484
+ let forwardWalkIterations = 0;
2485
+ let forwardWalkInnerIterations = 0;
2459
2486
  for (let i = preserveStart; i < messages.length; i++) {
2487
+ forwardWalkIterations++;
2460
2488
  const m = messages[i];
2461
2489
  if (!m || typeof m.content === "string" || !Array.isArray(m.content)) continue;
2462
- const hasToolUse3 = m.content.some((b) => b.type === "tool_use");
2490
+ const hasToolUse3 = m.content.some((b) => {
2491
+ forwardWalkInnerIterations++;
2492
+ return b.type === "tool_use";
2493
+ });
2463
2494
  if (hasToolUse3 && i + 1 < messages.length) {
2464
2495
  const next = messages[i + 1];
2465
- if (next && next.role === "user" && typeof next.content !== "string" && Array.isArray(next.content) && next.content.some((b) => b.type === "tool_result")) {
2496
+ if (next && next.role === "user" && typeof next.content !== "string" && Array.isArray(next.content) && next.content.some((b) => {
2497
+ forwardWalkInnerIterations++;
2498
+ return b.type === "tool_result";
2499
+ })) {
2466
2500
  preserveStart = i + 1;
2467
2501
  }
2468
2502
  }
2469
2503
  }
2504
+ console.log(
2505
+ JSON.stringify({
2506
+ level: "debug",
2507
+ event: "compaction.find_preserve_start.ended",
2508
+ messageCount: messages.length,
2509
+ preserveK,
2510
+ preserveStart,
2511
+ forwardWalkIterations,
2512
+ forwardWalkInnerIterations,
2513
+ forwardWalkInnerPerOuter: forwardWalkIterations > 0 ? forwardWalkInnerIterations / forwardWalkIterations : 0
2514
+ })
2515
+ );
2470
2516
  return preserveStart;
2471
2517
  }
2472
2518
  function eliseOldToolResults(messages, opts) {
2473
2519
  const preserveStart = findPreserveStart(messages, opts.preserveK);
2474
2520
  let hasOversized = false;
2521
+ let fastPathIterations = 0;
2522
+ let fastPathInnerIterations = 0;
2475
2523
  for (let i = 0; i < preserveStart && !hasOversized; i++) {
2524
+ fastPathIterations++;
2476
2525
  const msg = messages[i];
2477
2526
  if (!msg || !Array.isArray(msg.content)) continue;
2478
2527
  for (const b of msg.content) {
2528
+ fastPathInnerIterations++;
2479
2529
  if (b.type === "tool_result" && estimateToolResultTokens(b.content) >= opts.eliseThreshold) {
2480
2530
  hasOversized = true;
2481
2531
  break;
2482
2532
  }
2483
2533
  }
2484
2534
  }
2535
+ emitCompactionMetrics(
2536
+ hasOversized ? "compaction.elision.fast_path.oversized_found" : "compaction.elision.fast_path.no_oversized",
2537
+ {
2538
+ messageCount: messages.length,
2539
+ preserveStart,
2540
+ fastPathIterations,
2541
+ fastPathInnerIterations,
2542
+ fullPassIterations: 0,
2543
+ fullPassInnerIterations: 0,
2544
+ tokensSaved: 0,
2545
+ changed: false
2546
+ }
2547
+ );
2485
2548
  if (!hasOversized) return { messages, saved: 0, changed: false };
2486
2549
  let saved = 0;
2487
2550
  let changed = false;
2551
+ let fullPassIterations = 0;
2552
+ let fullPassInnerIterations = 0;
2488
2553
  const next = new Array(messages.length);
2489
2554
  for (let i = 0; i < messages.length; i++) {
2555
+ fullPassIterations++;
2490
2556
  const msg = messages[i];
2491
2557
  if (i >= preserveStart || !msg || !Array.isArray(msg.content)) {
2492
2558
  next[i] = msg;
@@ -2512,7 +2578,33 @@ function eliseOldToolResults(messages, opts) {
2512
2578
  next[i] = { ...msg, content: newContent };
2513
2579
  changed = true;
2514
2580
  }
2581
+ fullPassInnerIterations += original.length;
2582
+ if (process.env["NODE_ENV"] === "development" || process.env["WRONGSTACK_DEBUG"] === "1") {
2583
+ const ratio = fullPassInnerIterations / fullPassIterations;
2584
+ if (ratio > 10) {
2585
+ console.error(
2586
+ JSON.stringify({
2587
+ level: "error",
2588
+ event: "compaction.elision.regression",
2589
+ message: `fullPassInnerPerOuter=${ratio.toFixed(2)} exceeds threshold 10 \u2014 possible O(n\xB7m) regression`,
2590
+ messageCount: messages.length,
2591
+ fullPassIterations,
2592
+ fullPassInnerIterations
2593
+ })
2594
+ );
2595
+ }
2596
+ }
2515
2597
  }
2598
+ emitCompactionMetrics("compaction.elision.full_pass.ended", {
2599
+ messageCount: messages.length,
2600
+ preserveStart,
2601
+ fastPathIterations,
2602
+ fastPathInnerIterations,
2603
+ fullPassIterations,
2604
+ fullPassInnerIterations,
2605
+ tokensSaved: saved,
2606
+ changed
2607
+ });
2516
2608
  return { messages: changed ? next : messages, saved, changed };
2517
2609
  }
2518
2610
  function buildLosslessDigest(messages) {
@@ -3060,6 +3152,10 @@ var PATTERNS = [
3060
3152
  regex: /(?:^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?:\s|$)/g
3061
3153
  }
3062
3154
  ];
3155
+ var SIMPLE_PATTERNS = PATTERNS.filter((p) => p.type !== "high_entropy_env");
3156
+ var COMBINED_REGEX = new RegExp(SIMPLE_PATTERNS.map((p) => `(${p.regex.source})`).join("|"), "g");
3157
+ var HIGH_ENTROPY_REGEX = PATTERNS.find((p) => p.type === "high_entropy_env").regex;
3158
+ var COMBINED_REPLACEMENTS = SIMPLE_PATTERNS.map((p) => `[REDACTED:${p.type}]`);
3063
3159
  var SCRUB_CHUNK_BYTES = 64 * 1024;
3064
3160
  function hasCredentialAnchors(text) {
3065
3161
  return text.includes("-----BEGIN") || // Private keys (most unique → cheap reject)
@@ -3101,17 +3197,27 @@ var DefaultSecretScrubber = class {
3101
3197
  }
3102
3198
  scrubOne(text) {
3103
3199
  if (!hasCredentialAnchors(text)) return text;
3104
- let out = text;
3105
- for (const p of PATTERNS) {
3106
- out = out.replace(p.regex, (_match, group1, group2) => {
3107
- if (p.type === "high_entropy_env" && group1 && group2) {
3108
- return `${group1}=[REDACTED:${p.type}]`;
3109
- }
3110
- return `[REDACTED:${p.type}]`;
3111
- });
3112
- }
3200
+ let out = text.replace(
3201
+ COMBINED_REGEX,
3202
+ (match, ...groups) => {
3203
+ const idx = groups.findIndex((g) => g !== void 0);
3204
+ if (idx < 0) return match;
3205
+ const replacement = COMBINED_REPLACEMENTS[idx];
3206
+ return replacement !== void 0 ? replacement : match;
3207
+ }
3208
+ );
3209
+ out = out.replace(HIGH_ENTROPY_REGEX, (_match, group1, _group2) => {
3210
+ return `${group1}=[REDACTED:high_entropy_env]`;
3211
+ });
3113
3212
  return out;
3114
3213
  }
3214
+ /**
3215
+ * Recursively scrub every string value in an object/array graph. Secrets can
3216
+ * appear under any key — a URL query param, an `authorization` header, an
3217
+ * arbitrarily-named nested field — so we don't gate recursion on key names.
3218
+ * The per-string `scrub()` fast-path (anchor pre-scan) keeps this cheap: any
3219
+ * value without a credential anchor returns immediately without regex work.
3220
+ */
3115
3221
  scrubObject(obj) {
3116
3222
  const seen = /* @__PURE__ */ new WeakSet();
3117
3223
  const visit = (v) => {
@@ -4857,12 +4963,12 @@ var ConversationState = class {
4857
4963
  for (const m of messages) {
4858
4964
  if (m._estTokens === void 0) {
4859
4965
  m._estTokens = computeMessageTokens(m);
4860
- }
4861
- if (!hasToolBlock && Array.isArray(m.content)) {
4862
- for (const b of m.content) {
4863
- if (b.type === "tool_use" || b.type === "tool_result") {
4864
- hasToolBlock = true;
4865
- break;
4966
+ if (Array.isArray(m.content)) {
4967
+ for (const b of m.content) {
4968
+ if (b.type === "tool_use" || b.type === "tool_result") {
4969
+ hasToolBlock = true;
4970
+ break;
4971
+ }
4866
4972
  }
4867
4973
  }
4868
4974
  }
@@ -5121,7 +5227,7 @@ var DefaultSessionReader = class {
5121
5227
  this.store = opts.store;
5122
5228
  }
5123
5229
  async query(q = {}) {
5124
- const raw = await this.store.list(q.limit ? Math.max(q.limit * 4, 100) : 1e3);
5230
+ const raw = await this.store.list(q.limit ? Math.max(q.limit, 100) : 1e3);
5125
5231
  const titleNeedle = q.titleContains?.toLowerCase();
5126
5232
  const filtered = raw.filter((s) => {
5127
5233
  if (q.since && s.startedAt < q.since) return false;