@wrongstack/core 0.269.0 → 0.272.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/{agent-bridge-PcHQl_UQ.d.ts → agent-bridge-jVSZiygR.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-SHJW7t8q.d.ts → agent-subagent-runner-DOLIwBRo.d.ts} +7 -7
  3. package/dist/{brain-BYcK__Ym.d.ts → brain-CdbbJWi3.d.ts} +71 -1
  4. package/dist/{compactor-C2RKEBtC.d.ts → compactor-72ug-ZRB.d.ts} +1 -1
  5. package/dist/{config-C_ae2k86.d.ts → config-D2DGoGSQ.d.ts} +29 -2
  6. package/dist/{context-Dp87Bcaq.d.ts → context-Dw55zZ_Q.d.ts} +110 -1
  7. package/dist/coordination/index.d.ts +121 -17
  8. package/dist/coordination/index.js +738 -74
  9. package/dist/coordination/index.js.map +1 -1
  10. package/dist/defaults/index.d.ts +25 -25
  11. package/dist/defaults/index.js +599 -86
  12. package/dist/defaults/index.js.map +1 -1
  13. package/dist/execution/index.d.ts +23 -18
  14. package/dist/execution/index.js +136 -41
  15. package/dist/execution/index.js.map +1 -1
  16. package/dist/execution/prompt-enhancer.d.ts +36 -6
  17. package/dist/execution/prompt-enhancer.js +35 -9
  18. package/dist/execution/prompt-enhancer.js.map +1 -1
  19. package/dist/extension/index.d.ts +6 -6
  20. package/dist/{global-mailbox-Bvrz1P3f.d.ts → global-mailbox-CQj_C9Dp.d.ts} +139 -3
  21. package/dist/{goal-preamble-CA_4yiGQ.d.ts → goal-preamble-ZXDjjR1y.d.ts} +9 -9
  22. package/dist/{goal-store-DhuJoUNG.d.ts → goal-store-CcJBd-g1.d.ts} +1 -1
  23. package/dist/hq/index.d.ts +93 -6
  24. package/dist/hq/index.js +619 -49
  25. package/dist/hq/index.js.map +1 -1
  26. package/dist/{index-whDfTANu.d.ts → index-2Lhk5v0o.d.ts} +2 -2
  27. package/dist/{index-CZQ6Pwbs.d.ts → index-BL7BAx0p.d.ts} +8 -8
  28. package/dist/{index-W4VJCzHa.d.ts → index-Qo4kTzgw.d.ts} +5 -5
  29. package/dist/index.d.ts +96 -56
  30. package/dist/index.js +1941 -352
  31. package/dist/index.js.map +1 -1
  32. package/dist/infrastructure/index.d.ts +6 -6
  33. package/dist/infrastructure/index.js +5 -3
  34. package/dist/infrastructure/index.js.map +1 -1
  35. package/dist/kernel/index.d.ts +9 -9
  36. package/dist/kernel/index.js.map +1 -1
  37. package/dist/{mcp-servers-DJdZiRcv.d.ts → mcp-servers-DS-YUXvF.d.ts} +3 -3
  38. package/dist/models/index.d.ts +5 -5
  39. package/dist/models/index.js +28 -5
  40. package/dist/models/index.js.map +1 -1
  41. package/dist/{models-registry-C3a-2-Yd.d.ts → models-registry-DP6pGHet.d.ts} +1 -1
  42. package/dist/{multi-agent-coordinator-CJSpTe5O.d.ts → multi-agent-coordinator-BvbdNQ14.d.ts} +1 -1
  43. package/dist/{null-fleet-bus-QVshIsDx.d.ts → null-fleet-bus-BxTfXBKo.d.ts} +6 -6
  44. package/dist/observability/index.d.ts +2 -2
  45. package/dist/{parallel-eternal-engine-D9y5Pkcc.d.ts → parallel-eternal-engine-Cf-GTegR.d.ts} +9 -9
  46. package/dist/{path-resolver-CnQ8SIfh.d.ts → path-resolver-DztfnFcv.d.ts} +3 -3
  47. package/dist/{permission-CvYQNUqZ.d.ts → permission-CC7XFYWG.d.ts} +1 -1
  48. package/dist/{permission-policy-D5Ss8j4B.d.ts → permission-policy-cYR4RJmw.d.ts} +2 -2
  49. package/dist/{pipeline-l_zzFRh3.d.ts → pipeline-sNIkhXeB.d.ts} +2 -2
  50. package/dist/{plan-templates-NtPgyeJA.d.ts → plan-templates-DYiKFmEb.d.ts} +11 -5
  51. package/dist/{provider-model-resolve-d5poT5y0.d.ts → provider-model-resolve-dYAbTs_i.d.ts} +3 -3
  52. package/dist/{provider-runner-gkctlQV_.d.ts → provider-runner-Dw8x0F7u.d.ts} +3 -3
  53. package/dist/{retry-policy-CtFhfwa8.d.ts → retry-policy-BV7nzeAd.d.ts} +1 -1
  54. package/dist/sdd/index.d.ts +8 -8
  55. package/dist/sdd/index.js +2 -0
  56. package/dist/sdd/index.js.map +1 -1
  57. package/dist/{secret-vault-BLsVmTIK.d.ts → secret-vault-eMBKfheR.d.ts} +9 -1
  58. package/dist/security/index.d.ts +5 -5
  59. package/dist/security/index.js +137 -10
  60. package/dist/security/index.js.map +1 -1
  61. package/dist/{selector-CXl2_y9W.d.ts → selector-C4ORTOid.d.ts} +1 -1
  62. package/dist/{session-event-bridge-Ccud20CC.d.ts → session-event-bridge-CeNpUL9w.d.ts} +1 -1
  63. package/dist/{session-reader-ZeXQmsmE.d.ts → session-reader-BepLSnGL.d.ts} +1 -1
  64. package/dist/storage/index.d.ts +45 -13
  65. package/dist/storage/index.js +374 -113
  66. package/dist/storage/index.js.map +1 -1
  67. package/dist/tools/index.d.ts +2 -2
  68. package/dist/tools/index.js +9 -2
  69. package/dist/tools/index.js.map +1 -1
  70. package/dist/types/index.d.ts +19 -19
  71. package/dist/types/index.js +202 -41
  72. package/dist/types/index.js.map +1 -1
  73. package/dist/utils/index.d.ts +17 -4
  74. package/dist/utils/index.js +48 -9
  75. package/dist/utils/index.js.map +1 -1
  76. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { randomBytes, createCipheriv, createDecipheriv, randomUUID } from 'crypto';
1
+ import { randomBytes, createCipheriv, createDecipheriv, randomUUID, scryptSync } from 'crypto';
2
2
  import * as fs from 'fs/promises';
3
3
  import * as path6 from 'path';
4
4
  import * as os from 'os';
@@ -703,18 +703,20 @@ function calState(key) {
703
703
  return state;
704
704
  }
705
705
  var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
706
+ var _estimateCacheOrder = [];
706
707
  var ESTIMATE_CACHE_MAX_SIZE = 5e4;
707
708
  function getCachedEstimate(key, compute) {
708
709
  const existing = ESTIMATE_CACHE.get(key);
709
710
  if (existing !== void 0) return existing;
710
711
  if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
711
- for (const k of ESTIMATE_CACHE.keys()) {
712
- if (ESTIMATE_CACHE.size <= Math.floor(ESTIMATE_CACHE_MAX_SIZE / 2)) break;
713
- ESTIMATE_CACHE.delete(k);
712
+ while (ESTIMATE_CACHE.size > Math.floor(ESTIMATE_CACHE_MAX_SIZE / 2)) {
713
+ const oldest = _estimateCacheOrder.shift();
714
+ if (oldest !== void 0) ESTIMATE_CACHE.delete(oldest);
714
715
  }
715
716
  }
716
717
  const estimate = compute(key);
717
718
  ESTIMATE_CACHE.set(key, estimate);
719
+ _estimateCacheOrder.push(key);
718
720
  return estimate;
719
721
  }
720
722
  function estimateToolInputTokens(input) {
@@ -1864,6 +1866,83 @@ var ALGO = "aes-256-gcm";
1864
1866
  var KEY_FILE_MODE = 384;
1865
1867
  var KEY_FILE_MAGIC = Buffer.from("WSKV", "ascii");
1866
1868
  var VERSIONED_KEY_FILE_SIZE = KEY_FILE_MAGIC.length + 1 + KEY_BYTES;
1869
+ var KEK_MAGIC = Buffer.from("WSKW", "ascii");
1870
+ var KEK_SALT_BYTES = 16;
1871
+ var WRAPPED_KEY_FILE_SIZE = KEK_MAGIC.length + 1 + KEK_SALT_BYTES + IV_BYTES + TAG_BYTES + KEY_BYTES;
1872
+ var SCRYPT_N = 1 << 15;
1873
+ var SCRYPT_R = 8;
1874
+ var SCRYPT_P = 1;
1875
+ var SCRYPT_MAXMEM = 64 * 1024 * 1024;
1876
+ function getVaultPassphrase() {
1877
+ const v = process.env["WRONGSTACK_VAULT_PASSPHRASE"];
1878
+ return v && v.length > 0 ? v : void 0;
1879
+ }
1880
+ function isWrappedKeyFile(buf) {
1881
+ return buf.length === WRAPPED_KEY_FILE_SIZE && buf.subarray(0, KEK_MAGIC.length).equals(KEK_MAGIC);
1882
+ }
1883
+ function deriveKEK(passphrase, salt) {
1884
+ return scryptSync(passphrase, salt, KEY_BYTES, {
1885
+ N: SCRYPT_N,
1886
+ r: SCRYPT_R,
1887
+ p: SCRYPT_P,
1888
+ maxmem: SCRYPT_MAXMEM
1889
+ });
1890
+ }
1891
+ function wrapDataKey(dataKey, keyVersion, passphrase) {
1892
+ const salt = randomBytes(KEK_SALT_BYTES);
1893
+ const iv = randomBytes(IV_BYTES);
1894
+ const kek = deriveKEK(passphrase, salt);
1895
+ const cipher = createCipheriv(ALGO, kek, iv);
1896
+ const ct = Buffer.concat([cipher.update(dataKey), cipher.final()]);
1897
+ const tag = cipher.getAuthTag();
1898
+ const out = Buffer.alloc(WRAPPED_KEY_FILE_SIZE);
1899
+ let off = 0;
1900
+ KEK_MAGIC.copy(out, off);
1901
+ off += KEK_MAGIC.length;
1902
+ out[off] = keyVersion & 255;
1903
+ off += 1;
1904
+ salt.copy(out, off);
1905
+ off += KEK_SALT_BYTES;
1906
+ iv.copy(out, off);
1907
+ off += IV_BYTES;
1908
+ tag.copy(out, off);
1909
+ off += TAG_BYTES;
1910
+ ct.copy(out, off);
1911
+ return out;
1912
+ }
1913
+ function unwrapDataKey(buf, keyFile) {
1914
+ const passphrase = getVaultPassphrase();
1915
+ if (!passphrase) {
1916
+ throw new ConfigError({
1917
+ message: `SecretVault: key file ${keyFile} is passphrase-protected \u2014 set the WRONGSTACK_VAULT_PASSPHRASE environment variable to unlock it.`,
1918
+ code: ERROR_CODES.CONFIG_INVALID,
1919
+ context: { keyFile }
1920
+ });
1921
+ }
1922
+ let off = KEK_MAGIC.length;
1923
+ const version = buf[off];
1924
+ off += 1;
1925
+ const salt = buf.subarray(off, off + KEK_SALT_BYTES);
1926
+ off += KEK_SALT_BYTES;
1927
+ const iv = buf.subarray(off, off + IV_BYTES);
1928
+ off += IV_BYTES;
1929
+ const tag = buf.subarray(off, off + TAG_BYTES);
1930
+ off += TAG_BYTES;
1931
+ const ct = buf.subarray(off, off + KEY_BYTES);
1932
+ const kek = deriveKEK(passphrase, salt);
1933
+ const decipher = createDecipheriv(ALGO, kek, iv);
1934
+ decipher.setAuthTag(tag);
1935
+ try {
1936
+ const key = Buffer.concat([decipher.update(ct), decipher.final()]);
1937
+ return { key: Buffer.from(key), version };
1938
+ } catch {
1939
+ throw new ConfigError({
1940
+ message: `SecretVault: failed to unlock key file ${keyFile} \u2014 wrong WRONGSTACK_VAULT_PASSPHRASE (key unwrap authentication failed).`,
1941
+ code: ERROR_CODES.CONFIG_INVALID,
1942
+ context: { keyFile }
1943
+ });
1944
+ }
1945
+ }
1867
1946
  function checkKeyFilePermissions(keyFile) {
1868
1947
  if (process.platform === "win32") return;
1869
1948
  try {
@@ -1956,25 +2035,56 @@ var DefaultSecretVault = class {
1956
2035
  const oldVersion = this._keyVersion;
1957
2036
  const newKey = randomBytes(KEY_BYTES);
1958
2037
  const newVersion = oldVersion + 1;
1959
- const keyFileBuf = Buffer.alloc(VERSIONED_KEY_FILE_SIZE);
1960
- KEY_FILE_MAGIC.copy(keyFileBuf, 0);
1961
- keyFileBuf[KEY_FILE_MAGIC.length] = newVersion;
1962
- newKey.copy(keyFileBuf, KEY_FILE_MAGIC.length + 1);
1963
2038
  fs2.mkdirSync(path6.dirname(this.keyFile), { recursive: true });
1964
- fs2.writeFileSync(this.keyFile, keyFileBuf, { mode: 384 });
2039
+ const passphrase = getVaultPassphrase();
2040
+ if (passphrase) {
2041
+ fs2.writeFileSync(this.keyFile, wrapDataKey(newKey, newVersion, passphrase), { mode: 384 });
2042
+ } else {
2043
+ const keyFileBuf = Buffer.alloc(VERSIONED_KEY_FILE_SIZE);
2044
+ KEY_FILE_MAGIC.copy(keyFileBuf, 0);
2045
+ keyFileBuf[KEY_FILE_MAGIC.length] = newVersion;
2046
+ newKey.copy(keyFileBuf, KEY_FILE_MAGIC.length + 1);
2047
+ fs2.writeFileSync(this.keyFile, keyFileBuf, { mode: 384 });
2048
+ }
1965
2049
  checkKeyFilePermissions(this.keyFile);
1966
2050
  this.key = newKey;
1967
2051
  this._keyVersion = newVersion;
1968
2052
  return { oldVersion, newVersion };
1969
2053
  }
2054
+ /**
2055
+ * If WRONGSTACK_VAULT_PASSPHRASE is set but the key on disk is still stored
2056
+ * unwrapped (legacy v1 / versioned v2), re-write it in passphrase-wrapped (v3)
2057
+ * form. The data key is preserved, so all existing ciphertext keeps
2058
+ * decrypting. Best-effort: a write failure leaves the working unwrapped file
2059
+ * in place and is not fatal to load.
2060
+ */
2061
+ migrateToWrappedIfPassphrase() {
2062
+ const passphrase = getVaultPassphrase();
2063
+ if (!passphrase || !this.key) return;
2064
+ try {
2065
+ fs2.writeFileSync(this.keyFile, wrapDataKey(this.key, this._keyVersion, passphrase), {
2066
+ mode: 384
2067
+ });
2068
+ checkKeyFilePermissions(this.keyFile);
2069
+ } catch {
2070
+ }
2071
+ }
1970
2072
  loadOrCreateKey() {
1971
2073
  if (this.key) return this.key;
1972
2074
  try {
1973
2075
  const buf = fs2.readFileSync(this.keyFile);
2076
+ if (isWrappedKeyFile(buf)) {
2077
+ const { key: key2, version } = unwrapDataKey(buf, this.keyFile);
2078
+ this.key = key2;
2079
+ this._keyVersion = version;
2080
+ checkKeyFilePermissions(this.keyFile);
2081
+ return this.key;
2082
+ }
1974
2083
  if (buf.length === KEY_BYTES) {
1975
2084
  this.key = buf;
1976
2085
  this._keyVersion = 1;
1977
2086
  checkKeyFilePermissions(this.keyFile);
2087
+ this.migrateToWrappedIfPassphrase();
1978
2088
  return this.key;
1979
2089
  }
1980
2090
  if (buf.length === VERSIONED_KEY_FILE_SIZE) {
@@ -1998,6 +2108,7 @@ var DefaultSecretVault = class {
1998
2108
  this.key = Buffer.from(key2);
1999
2109
  this._keyVersion = version;
2000
2110
  checkKeyFilePermissions(this.keyFile);
2111
+ this.migrateToWrappedIfPassphrase();
2001
2112
  return this.key;
2002
2113
  }
2003
2114
  throw new ConfigError({
@@ -2010,11 +2121,20 @@ var DefaultSecretVault = class {
2010
2121
  }
2011
2122
  fs2.mkdirSync(path6.dirname(this.keyFile), { recursive: true });
2012
2123
  const key = randomBytes(KEY_BYTES);
2124
+ const passphrase = getVaultPassphrase();
2125
+ const initialBytes = passphrase ? wrapDataKey(key, 1, passphrase) : key;
2013
2126
  try {
2014
- fs2.writeFileSync(this.keyFile, key, { mode: 384, flag: "wx" });
2127
+ fs2.writeFileSync(this.keyFile, initialBytes, { mode: 384, flag: "wx" });
2015
2128
  } catch (err) {
2016
2129
  if (err.code !== "EEXIST") throw err;
2017
2130
  const buf = fs2.readFileSync(this.keyFile);
2131
+ if (isWrappedKeyFile(buf)) {
2132
+ const { key: winnerKey, version } = unwrapDataKey(buf, this.keyFile);
2133
+ this.key = winnerKey;
2134
+ this._keyVersion = version;
2135
+ checkKeyFilePermissions(this.keyFile);
2136
+ return this.key;
2137
+ }
2018
2138
  if (buf.length === KEY_BYTES) {
2019
2139
  this.key = buf;
2020
2140
  this._keyVersion = 1;
@@ -2618,17 +2738,9 @@ function findPreserveStart(messages, preserveK) {
2618
2738
  const prev = messages[preserveStart - 1];
2619
2739
  if (!first || !prev || first.role !== "user" || prev.role !== "assistant") break;
2620
2740
  if (typeof first.content === "string" || typeof prev.content === "string") break;
2621
- const resultIds = /* @__PURE__ */ new Set();
2622
- for (const block of first.content) {
2623
- pairRepairInnerIterations++;
2624
- if (block.type === "tool_result") resultIds.add(block.tool_use_id);
2625
- }
2626
- if (resultIds.size === 0) break;
2627
- const hasMatchingUse = prev.content.some((block) => {
2628
- pairRepairInnerIterations++;
2629
- return block.type === "tool_use" && resultIds.has(block.id);
2630
- });
2631
- if (!hasMatchingUse) break;
2741
+ const pairCheck = hasMatchingToolPair(first.content, prev.content);
2742
+ pairRepairInnerIterations += pairCheck.iterations;
2743
+ if (!pairCheck.matched) break;
2632
2744
  preserveStart--;
2633
2745
  }
2634
2746
  if (compactionDebugEnabled()) {
@@ -2647,9 +2759,34 @@ function findPreserveStart(messages, preserveK) {
2647
2759
  }
2648
2760
  return preserveStart;
2649
2761
  }
2762
+ function hasMatchingToolPair(resultContent, useContent) {
2763
+ let iterations = 0;
2764
+ let firstResultId;
2765
+ let resultIds;
2766
+ for (const block of resultContent) {
2767
+ iterations++;
2768
+ if (block.type !== "tool_result") continue;
2769
+ if (firstResultId === void 0) {
2770
+ firstResultId = block.tool_use_id;
2771
+ } else {
2772
+ resultIds ??= /* @__PURE__ */ new Set([firstResultId]);
2773
+ resultIds.add(block.tool_use_id);
2774
+ }
2775
+ }
2776
+ if (firstResultId === void 0) return { matched: false, iterations };
2777
+ for (const block of useContent) {
2778
+ iterations++;
2779
+ if (block.type !== "tool_use") continue;
2780
+ if (resultIds ? resultIds.has(block.id) : block.id === firstResultId) {
2781
+ return { matched: true, iterations };
2782
+ }
2783
+ }
2784
+ return { matched: false, iterations };
2785
+ }
2650
2786
  function eliseOldToolResults(messages, opts) {
2651
2787
  const preserveStart = findPreserveStart(messages, opts.preserveK);
2652
2788
  let hasOversized = false;
2789
+ let firstOversizedIndex = -1;
2653
2790
  let fastPathIterations = 0;
2654
2791
  let fastPathInnerIterations = 0;
2655
2792
  for (let i = 0; i < preserveStart && !hasOversized; i++) {
@@ -2661,6 +2798,7 @@ function eliseOldToolResults(messages, opts) {
2661
2798
  const oversized = b.type === "tool_result" && estimateToolResultTokens(b.content) >= opts.eliseThreshold || b.type === "tool_use" && estimateToolInputTokens(b.input) >= opts.eliseThreshold;
2662
2799
  if (oversized) {
2663
2800
  hasOversized = true;
2801
+ firstOversizedIndex = i;
2664
2802
  break;
2665
2803
  }
2666
2804
  }
@@ -2683,26 +2821,29 @@ function eliseOldToolResults(messages, opts) {
2683
2821
  let changed = false;
2684
2822
  let fullPassIterations = 0;
2685
2823
  let fullPassInnerIterations = 0;
2686
- const next = new Array(messages.length);
2687
- for (let i = 0; i < messages.length; i++) {
2824
+ let next;
2825
+ for (let i = firstOversizedIndex; i < preserveStart; i++) {
2688
2826
  fullPassIterations++;
2689
2827
  const msg = messages[i];
2690
- if (i >= preserveStart || !msg || !Array.isArray(msg.content)) {
2691
- next[i] = msg;
2692
- continue;
2693
- }
2828
+ if (!msg || !Array.isArray(msg.content)) continue;
2694
2829
  const original = msg.content;
2695
- const newContent = original.map((b) => {
2830
+ let newContent;
2831
+ for (let idx = 0; idx < original.length; idx++) {
2832
+ fullPassInnerIterations++;
2833
+ const b = original[idx];
2834
+ if (!b) continue;
2696
2835
  if (b.type === "tool_use") {
2697
2836
  const tokens2 = estimateToolInputTokens(b.input);
2698
- if (tokens2 < opts.eliseThreshold) return b;
2837
+ if (tokens2 < opts.eliseThreshold) continue;
2699
2838
  const elidedInput = summarizeToolUseInputElision(b, tokens2);
2700
2839
  saved += Math.max(0, tokens2 - estimateToolInputTokens(elidedInput));
2701
- return { ...b, input: elidedInput };
2840
+ newContent ??= original.slice();
2841
+ newContent[idx] = { ...b, input: elidedInput };
2842
+ continue;
2702
2843
  }
2703
- if (b.type !== "tool_result") return b;
2844
+ if (b.type !== "tool_result") continue;
2704
2845
  const tokens = estimateToolResultTokens(b.content);
2705
- if (tokens < opts.eliseThreshold) return b;
2846
+ if (tokens < opts.eliseThreshold) continue;
2706
2847
  saved += tokens;
2707
2848
  const elided = {
2708
2849
  type: "tool_result",
@@ -2710,15 +2851,14 @@ function eliseOldToolResults(messages, opts) {
2710
2851
  content: summarizeToolResultElision(b, tokens),
2711
2852
  is_error: b.is_error
2712
2853
  };
2713
- return elided;
2714
- });
2715
- if (newContent.every((b, idx) => b === original[idx])) {
2716
- next[i] = msg;
2717
- } else {
2854
+ newContent ??= original.slice();
2855
+ newContent[idx] = elided;
2856
+ }
2857
+ if (newContent) {
2858
+ next ??= messages.slice();
2718
2859
  next[i] = { ...msg, content: newContent };
2719
2860
  changed = true;
2720
2861
  }
2721
- fullPassInnerIterations += original.length;
2722
2862
  if (compactionDebugEnabled()) {
2723
2863
  const ratio = fullPassInnerIterations / fullPassIterations;
2724
2864
  if (ratio > 10) {
@@ -2745,7 +2885,7 @@ function eliseOldToolResults(messages, opts) {
2745
2885
  tokensSaved: saved,
2746
2886
  changed
2747
2887
  });
2748
- return { messages: changed ? next : messages, saved, changed };
2888
+ return { messages: changed && next ? next : messages, saved, changed };
2749
2889
  }
2750
2890
  function summarizeToolUseInputElision(block, tokens) {
2751
2891
  const fields = {};
@@ -3471,6 +3611,7 @@ var DefaultSecretScrubber = class {
3471
3611
  }
3472
3612
  };
3473
3613
  var DEFAULT_URL = "https://models.dev/api.json";
3614
+ var ENV_URL_KEY = "WRONGSTACK_MODELS_DEV_URL";
3474
3615
  var DEFAULT_TTL_SECONDS = 24 * 3600;
3475
3616
  var DEFAULT_REFRESH_TIMEOUT_MS = 15e3;
3476
3617
  var FAMILY_BY_NPM = {
@@ -3516,7 +3657,7 @@ var DefaultModelsRegistry = class {
3516
3657
  overlayCacheFile;
3517
3658
  constructor(opts) {
3518
3659
  this.cacheFile = opts.cacheFile;
3519
- this.url = opts.url ?? DEFAULT_URL;
3660
+ this.url = opts.url ?? process.env[ENV_URL_KEY] ?? DEFAULT_URL;
3520
3661
  this.ttlMs = (opts.ttlSeconds ?? DEFAULT_TTL_SECONDS) * 1e3;
3521
3662
  this.fetchImpl = opts.fetchImpl ?? fetch;
3522
3663
  this.seed = opts.seed;
@@ -3561,6 +3702,10 @@ var DefaultModelsRegistry = class {
3561
3702
  const cached = await this.readCacheAt(this.cacheFile);
3562
3703
  if (cached && this.isWithinMaxStaleAge(cached.fetchedAt)) {
3563
3704
  this.fetchedAt = new Date(cached.fetchedAt);
3705
+ const ageSeconds = Math.floor((Date.now() - this.fetchedAt.getTime()) / 1e3);
3706
+ console.warn(
3707
+ `ModelsRegistry: models.dev unavailable (${toErrorMessage(err)}); using stale cache from ${formatAge(ageSeconds)} ago. Run \`wstack models refresh\` to retry.`
3708
+ );
3564
3709
  return cached.payload;
3565
3710
  }
3566
3711
  if (overlayAvailable) {
@@ -3646,7 +3791,13 @@ var DefaultModelsRegistry = class {
3646
3791
  return json;
3647
3792
  } catch {
3648
3793
  const cached = await this.readCacheAt(this.overlayCacheFile);
3649
- if (cached && this.isWithinMaxStaleAge(cached.fetchedAt)) return cached.payload;
3794
+ if (cached && this.isWithinMaxStaleAge(cached.fetchedAt)) {
3795
+ const ageSeconds = Math.floor((Date.now() - new Date(cached.fetchedAt).getTime()) / 1e3);
3796
+ console.warn(
3797
+ `ModelsRegistry: overlay unavailable; using stale overlay from ${formatAge(ageSeconds)} ago.`
3798
+ );
3799
+ return cached.payload;
3800
+ }
3650
3801
  return void 0;
3651
3802
  }
3652
3803
  }
@@ -3743,6 +3894,16 @@ var DefaultModelsRegistry = class {
3743
3894
  return path6.resolve(this.cacheFile);
3744
3895
  }
3745
3896
  };
3897
+ function formatAge(seconds) {
3898
+ if (seconds < 60) return "<1m";
3899
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
3900
+ if (seconds < 86400) {
3901
+ const h = Math.floor(seconds / 3600);
3902
+ const m = Math.floor(seconds % 3600 / 60);
3903
+ return m > 0 ? `${h}h ${m}m` : `${h}h`;
3904
+ }
3905
+ return `${Math.floor(seconds / 86400)}d`;
3906
+ }
3746
3907
  function hasEntries(payload) {
3747
3908
  return payload !== void 0 && Object.keys(payload).length > 0;
3748
3909
  }