@wrongstack/core 0.292.0 → 0.293.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 (65) hide show
  1. package/dist/coordination/index.d.ts +1 -1
  2. package/dist/coordination/index.d.ts.map +1 -1
  3. package/dist/coordination/index.js +163 -39
  4. package/dist/coordination/index.js.map +3 -3
  5. package/dist/coordination/mailbox-http-router.d.ts +38 -0
  6. package/dist/coordination/mailbox-http-router.d.ts.map +1 -1
  7. package/dist/coordination/provider-status-tracker.d.ts.map +1 -1
  8. package/dist/core/fallback-model.d.ts.map +1 -1
  9. package/dist/core/fallback-profile-manager.d.ts +0 -2
  10. package/dist/core/fallback-profile-manager.d.ts.map +1 -1
  11. package/dist/core/system-prompt-builder.d.ts.map +1 -1
  12. package/dist/defaults/index.js +351 -318
  13. package/dist/defaults/index.js.map +4 -4
  14. package/dist/execution/compaction-core.d.ts +3 -0
  15. package/dist/execution/compaction-core.d.ts.map +1 -1
  16. package/dist/execution/compactor.d.ts +4 -0
  17. package/dist/execution/compactor.d.ts.map +1 -1
  18. package/dist/execution/index.js +141 -50
  19. package/dist/execution/index.js.map +4 -4
  20. package/dist/execution/intelligent-compactor.d.ts +5 -1
  21. package/dist/execution/intelligent-compactor.d.ts.map +1 -1
  22. package/dist/execution/one-shot-llm.d.ts.map +1 -1
  23. package/dist/execution/selective-compactor.d.ts +4 -0
  24. package/dist/execution/selective-compactor.d.ts.map +1 -1
  25. package/dist/goal/phase-orchestrator.d.ts +4 -0
  26. package/dist/goal/phase-orchestrator.d.ts.map +1 -1
  27. package/dist/hooks/runner.d.ts +0 -2
  28. package/dist/hooks/runner.d.ts.map +1 -1
  29. package/dist/index.d.ts +1 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +421 -248
  32. package/dist/index.js.map +4 -4
  33. package/dist/models/alibaba-token-plan-catalog.d.ts +85 -0
  34. package/dist/models/alibaba-token-plan-catalog.d.ts.map +1 -0
  35. package/dist/models/index.d.ts +1 -0
  36. package/dist/models/index.d.ts.map +1 -1
  37. package/dist/models/index.js +200 -15
  38. package/dist/models/index.js.map +4 -4
  39. package/dist/models/llm-selector.d.ts +4 -0
  40. package/dist/models/llm-selector.d.ts.map +1 -1
  41. package/dist/models/models-registry.d.ts +8 -0
  42. package/dist/models/models-registry.d.ts.map +1 -1
  43. package/dist/security/index.js +0 -23
  44. package/dist/security/index.js.map +2 -2
  45. package/dist/security/permission-policy.d.ts +0 -23
  46. package/dist/security/permission-policy.d.ts.map +1 -1
  47. package/dist/storage/config-loader.d.ts.map +1 -1
  48. package/dist/storage/index.js +6 -5
  49. package/dist/storage/index.js.map +2 -2
  50. package/dist/tools/index.js +8 -1
  51. package/dist/tools/index.js.map +2 -2
  52. package/dist/types/config.d.ts +9 -16
  53. package/dist/types/config.d.ts.map +1 -1
  54. package/dist/types/index.js +7 -4
  55. package/dist/types/index.js.map +2 -2
  56. package/dist/types/provider.d.ts.map +1 -1
  57. package/dist/utils/connectivity.d.ts +36 -0
  58. package/dist/utils/connectivity.d.ts.map +1 -0
  59. package/dist/utils/index.d.ts +1 -0
  60. package/dist/utils/index.d.ts.map +1 -1
  61. package/dist/utils/index.js +59 -11
  62. package/dist/utils/index.js.map +4 -4
  63. package/dist/utils/merge-models-payload.d.ts +9 -0
  64. package/dist/utils/merge-models-payload.d.ts.map +1 -1
  65. package/package.json +2 -2
@@ -1625,15 +1625,30 @@ function deepEqual(a, b) {
1625
1625
  }
1626
1626
 
1627
1627
  // src/utils/merge-models-payload.ts
1628
+ var REMOVE_PROVIDERS_KEY = "_removeProviders";
1629
+ var REMOVE_MODELS_KEY = "_removeModels";
1628
1630
  function mergeModelsPayload(base, overlay) {
1631
+ const removeProviders = Array.isArray(overlay[REMOVE_PROVIDERS_KEY]) ? overlay[REMOVE_PROVIDERS_KEY] : [];
1632
+ const removeModels = overlay[REMOVE_MODELS_KEY] && typeof overlay[REMOVE_MODELS_KEY] === "object" ? overlay[REMOVE_MODELS_KEY] : {};
1629
1633
  const out = {};
1630
1634
  for (const [id, provider] of Object.entries(base)) {
1631
1635
  out[id] = cloneProvider(provider);
1632
1636
  }
1633
1637
  for (const [id, ovProvider] of Object.entries(overlay)) {
1638
+ if (id === REMOVE_PROVIDERS_KEY || id === REMOVE_MODELS_KEY) continue;
1634
1639
  const existing = out[id];
1635
1640
  out[id] = existing ? mergeProvider(existing, ovProvider) : cloneProvider(ovProvider);
1636
1641
  }
1642
+ for (const providerId of removeProviders) {
1643
+ delete out[providerId];
1644
+ }
1645
+ for (const [providerId, modelIds] of Object.entries(removeModels)) {
1646
+ const provider = out[providerId];
1647
+ if (!provider || !provider.models) continue;
1648
+ for (const modelId of modelIds) {
1649
+ delete provider.models[modelId];
1650
+ }
1651
+ }
1637
1652
  return out;
1638
1653
  }
1639
1654
  function mergeProvider(base, overlay) {
@@ -9317,7 +9332,8 @@ function hashStr(s) {
9317
9332
  // src/types/provider.ts
9318
9333
  var CONTEXT_OVERFLOW_RE = /context.length|context.window|maximum context|max.*tokens?.*exceeded|prompt is too long|too long|exceeds the context|\btokens\b.*exceed|too many tokens|reduce the length|resulted in \d+ tokens|input.{0,12}too (?:large|long)|context_length_exceeded/i;
9319
9334
  var CONTENT_FILTER_RE = /content.(filter|policy|moderation)|safety (system|filter)/i;
9320
- var QUOTA_EXHAUSTED_RE = /(?:insufficient|exhausted|depleted|exceeded|no|not enough)[-_\s]*(?:quota|credit|balance)|(?:quota|credit|balance)[-_\s]*(?:exhausted|depleted|exceeded|insufficient)|billing[_\s-]*(?:hard[_\s-]*)?limit|payment required|spending limit|plan limit/i;
9335
+ var QUOTA_EXHAUSTED_RE = /(?:insufficient|exhausted|depleted|exceeded|no|not enough)[-_\s]*(?:quota|credit|balance)|(?:quota|credit|balance)[-_\s]*(?:exhausted|depleted|exceeded|insufficient)|billing[_\s-]*(?:hard[_\s-]*)?limit|payment required|spending limit|plan limit|usage[-_\s]*limit[-_\s]*(?:reached|exceeded)/i;
9336
+ var RATE_LIMIT_EXCEEDED_RE = /rate[-_\s]*limit[-_\s]*exceeded/i;
9321
9337
  function classifyProviderError(status, body, message) {
9322
9338
  const type = body?.type;
9323
9339
  const text = [message, body?.message, type, body?.raw].filter(Boolean).join("\n");
@@ -9325,6 +9341,9 @@ function classifyProviderError(status, body, message) {
9325
9341
  if (status === 408) return "timeout";
9326
9342
  if (status === 599) return "stream_hang";
9327
9343
  if (status === 402 || QUOTA_EXHAUSTED_RE.test(text)) return "quota_exhausted";
9344
+ if (status === 429 && body?.message && RATE_LIMIT_EXCEEDED_RE.test(body.message)) {
9345
+ return "quota_exhausted";
9346
+ }
9328
9347
  if (type === "rate_limit_error" || status === 429) return "rate_limit";
9329
9348
  if (type === "overloaded_error" || status === 529) return "overloaded";
9330
9349
  if (status >= 500) return "server";
@@ -15073,25 +15092,30 @@ var WHITESPACE_COLLAPSE_PATTERN = /\s+/g;
15073
15092
  function compactionDebugEnabled() {
15074
15093
  return process.env["NODE_ENV"] === "development" || process.env["WRONGSTACK_DEBUG"] === "1";
15075
15094
  }
15095
+ var _debugLogger;
15096
+ function setCompactionDebugLogger(logger) {
15097
+ _debugLogger = logger;
15098
+ }
15076
15099
  function emitCompactionMetrics(event, metrics) {
15077
15100
  if (!compactionDebugEnabled()) return;
15078
- console.log(
15079
- JSON.stringify({
15080
- level: "debug",
15081
- event,
15082
- messageCount: metrics.messageCount,
15083
- preserveStart: metrics.preserveStart,
15084
- fastPathIterations: metrics.fastPathIterations,
15085
- fastPathInnerIterations: metrics.fastPathInnerIterations,
15086
- // Ratios — anything > 2.0 indicates the inner loop is running more than expected
15087
- fastPathInnerPerOuter: metrics.fastPathIterations > 0 ? metrics.fastPathInnerIterations / metrics.fastPathIterations : 0,
15088
- fullPassIterations: metrics.fullPassIterations,
15089
- fullPassInnerIterations: metrics.fullPassInnerIterations,
15090
- fullPassInnerPerOuter: metrics.fullPassIterations > 0 ? metrics.fullPassInnerIterations / metrics.fullPassIterations : 0,
15091
- tokensSaved: metrics.tokensSaved,
15092
- changed: metrics.changed
15093
- })
15094
- );
15101
+ const ctx = {
15102
+ event,
15103
+ messageCount: metrics.messageCount,
15104
+ preserveStart: metrics.preserveStart,
15105
+ fastPathIterations: metrics.fastPathIterations,
15106
+ fastPathInnerIterations: metrics.fastPathInnerIterations,
15107
+ fastPathInnerPerOuter: metrics.fastPathIterations > 0 ? metrics.fastPathInnerIterations / metrics.fastPathIterations : 0,
15108
+ fullPassIterations: metrics.fullPassIterations,
15109
+ fullPassInnerIterations: metrics.fullPassInnerIterations,
15110
+ fullPassInnerPerOuter: metrics.fullPassIterations > 0 ? metrics.fullPassInnerIterations / metrics.fullPassIterations : 0,
15111
+ tokensSaved: metrics.tokensSaved,
15112
+ changed: metrics.changed
15113
+ };
15114
+ if (_debugLogger) {
15115
+ _debugLogger.debug(`compaction: ${event}`, ctx);
15116
+ } else {
15117
+ console.log(JSON.stringify({ level: "debug", ...ctx }));
15118
+ }
15095
15119
  }
15096
15120
  var estimateMessages = estimateMessageTokens;
15097
15121
  function hasTextContent(m) {
@@ -15123,18 +15147,20 @@ function findPreserveStart(messages, preserveK) {
15123
15147
  preserveStart--;
15124
15148
  }
15125
15149
  if (compactionDebugEnabled()) {
15126
- console.log(
15127
- JSON.stringify({
15128
- level: "debug",
15129
- event: "compaction.find_preserve_start.ended",
15130
- messageCount: messages.length,
15131
- preserveK,
15132
- preserveStart,
15133
- pairRepairIterations,
15134
- pairRepairInnerIterations,
15135
- pairRepairInnerPerOuter: pairRepairIterations > 0 ? pairRepairInnerIterations / pairRepairIterations : 0
15136
- })
15137
- );
15150
+ const ctx = {
15151
+ event: "compaction.find_preserve_start.ended",
15152
+ messageCount: messages.length,
15153
+ preserveK,
15154
+ preserveStart,
15155
+ pairRepairIterations,
15156
+ pairRepairInnerIterations,
15157
+ pairRepairInnerPerOuter: pairRepairIterations > 0 ? pairRepairInnerIterations / pairRepairIterations : 0
15158
+ };
15159
+ if (_debugLogger) {
15160
+ _debugLogger.debug("compaction: find_preserve_start.ended", ctx);
15161
+ } else {
15162
+ console.log(JSON.stringify({ level: "debug", ...ctx }));
15163
+ }
15138
15164
  }
15139
15165
  return preserveStart;
15140
15166
  }
@@ -15249,16 +15275,18 @@ function eliseOldToolResults(messages, opts) {
15249
15275
  if (compactionDebugEnabled()) {
15250
15276
  const ratio = fullPassInnerIterations / fullPassIterations;
15251
15277
  if (ratio > 10) {
15252
- console.error(
15253
- JSON.stringify({
15254
- level: "error",
15255
- event: "compaction.elision.regression",
15256
- message: `fullPassInnerPerOuter=${ratio.toFixed(2)} exceeds threshold 10 \u2014 possible O(n\xB7m) regression`,
15257
- messageCount: messages.length,
15258
- fullPassIterations,
15259
- fullPassInnerIterations
15260
- })
15261
- );
15278
+ const ctx = {
15279
+ event: "compaction.elision.regression",
15280
+ message: `fullPassInnerPerOuter=${ratio.toFixed(2)} exceeds threshold 10 \u2014 possible O(n\xB7m) regression`,
15281
+ messageCount: messages.length,
15282
+ fullPassIterations,
15283
+ fullPassInnerIterations
15284
+ };
15285
+ if (_debugLogger) {
15286
+ _debugLogger.error(`compaction: elision.regression \u2014 ratio ${ratio.toFixed(2)}`, ctx);
15287
+ } else {
15288
+ console.error(JSON.stringify({ level: "error", ...ctx }));
15289
+ }
15262
15290
  }
15263
15291
  }
15264
15292
  }
@@ -16283,6 +16311,207 @@ function resolveContextWindowPolicy(config = {}, overrideMode) {
16283
16311
  };
16284
16312
  }
16285
16313
 
16314
+ // src/infrastructure/logger.ts
16315
+ import * as fsp10 from "node:fs/promises";
16316
+ import * as path14 from "node:path";
16317
+ var LEVEL_RANK2 = {
16318
+ error: 0,
16319
+ warn: 1,
16320
+ info: 2,
16321
+ debug: 3,
16322
+ trace: 4
16323
+ };
16324
+ var COLORS = {
16325
+ error: color.red,
16326
+ warn: color.yellow,
16327
+ info: color.cyan,
16328
+ debug: color.gray,
16329
+ trace: color.dim
16330
+ };
16331
+ var LOG_LEVELS = /* @__PURE__ */ new Set(["error", "warn", "info", "debug", "trace"]);
16332
+ var LOG_FORMATS = /* @__PURE__ */ new Set(["pretty", "json"]);
16333
+ var DefaultLogger = class _DefaultLogger {
16334
+ /** How many file writes between rotation size checks (statSync is not free). */
16335
+ static ROTATE_CHECK_EVERY = 100;
16336
+ level;
16337
+ file;
16338
+ bindings;
16339
+ format;
16340
+ stderr;
16341
+ maxFileBytes;
16342
+ writesSinceRotateCheck = 0;
16343
+ /**
16344
+ * Serialized async tail for file writes. Every appendFile (and any
16345
+ * chained rotation) is awaited through this promise so file I/O
16346
+ * never overlaps itself — preserving the per-line ordering the
16347
+ * sync version had, but without blocking the caller thread. Any
16348
+ * rejection is swallowed (`catch(() => {})`) because logging must
16349
+ * never crash the host.
16350
+ *
16351
+ * Children share the parent's tail: `child.tail === parent.tail`
16352
+ * for the lifetime of the chain. Read/write access goes through
16353
+ * `_tail` so that, when a child has been wired to a parent, both
16354
+ * `enqueueRotate` and `log` always observe the parent's current tail
16355
+ * rather than a stale snapshot taken at `child()` time.
16356
+ */
16357
+ tail = Promise.resolve();
16358
+ parent = null;
16359
+ /**
16360
+ * Resolve the current tail. For the root logger this is the field;
16361
+ * for a child logger we always read through the parent so that a
16362
+ * child's appends land on the parent's most recent tail, and a
16363
+ * parent's `flush()` waits for everything the child chained.
16364
+ */
16365
+ get _tail() {
16366
+ return this.parent ? this.parent._tail : this.tail;
16367
+ }
16368
+ set _tail(next) {
16369
+ if (this.parent) this.parent.tail = next;
16370
+ else this.tail = next;
16371
+ }
16372
+ constructor(opts = {}) {
16373
+ this.level = opts.level ?? parseLogLevel(process.env.WRONGSTACK_LOG_LEVEL);
16374
+ this.file = opts.file;
16375
+ this.bindings = opts.bindings ?? {};
16376
+ this.format = opts.format ?? parseLogFormat(process.env.WRONGSTACK_LOG_FORMAT);
16377
+ this.stderr = opts.stderr !== false;
16378
+ this.maxFileBytes = opts.maxFileBytes ?? 10 * 1024 * 1024;
16379
+ if (this.file) {
16380
+ const dir = path14.dirname(this.file);
16381
+ this._tail = this._tail.then(async () => {
16382
+ await fsp10.mkdir(dir, { recursive: true });
16383
+ }).catch(() => void 0);
16384
+ }
16385
+ }
16386
+ error(msg, ctx) {
16387
+ this.log("error", msg, ctx);
16388
+ }
16389
+ warn(msg, ctx) {
16390
+ this.log("warn", msg, ctx);
16391
+ }
16392
+ info(msg, ctx) {
16393
+ this.log("info", msg, ctx);
16394
+ }
16395
+ debug(msg, ctx) {
16396
+ this.log("debug", msg, ctx);
16397
+ }
16398
+ trace(msg, ctx) {
16399
+ this.log("trace", msg, ctx);
16400
+ }
16401
+ child(bindings) {
16402
+ const child = Object.create(_DefaultLogger.prototype);
16403
+ child.level = this.level;
16404
+ child.file = this.file;
16405
+ child.bindings = { ...this.bindings, ...bindings };
16406
+ child.format = this.format;
16407
+ child.stderr = this.stderr;
16408
+ child.maxFileBytes = this.maxFileBytes;
16409
+ child.parent = this;
16410
+ child.writesSinceRotateCheck = this.writesSinceRotateCheck;
16411
+ return child;
16412
+ }
16413
+ /**
16414
+ * Wait until all queued file writes (and any pending rotation) have
16415
+ * completed. `log()` is fire-and-forget by design — the caller never
16416
+ * blocks on disk — so tests, shutdown handlers, and processes that
16417
+ * need a deterministic "everything is on disk now" guarantee should
16418
+ * `await logger.flush()` before reading the file or exiting.
16419
+ */
16420
+ flush() {
16421
+ return this._tail;
16422
+ }
16423
+ /**
16424
+ * Size-based rotation: when the file outgrows `maxFileBytes`, rename it to
16425
+ * `<file>.1` (dropping the previous `.1`) so the live file restarts empty.
16426
+ * Checked on the first write and every ROTATE_CHECK_EVERY writes after.
16427
+ * Best-effort: a rename can fail on Windows while another process holds
16428
+ * the file — the next check retries. Multiple processes appending to the
16429
+ * same log all run this check; whoever crosses the threshold first wins.
16430
+ *
16431
+ * Async: the rotation runs on the file-write tail (so its writes don't
16432
+ * interleave with the next append), and the caller never blocks on a
16433
+ * statSync / renameSync syscall on the hot log path.
16434
+ */
16435
+ enqueueRotate(file) {
16436
+ if (this.writesSinceRotateCheck++ % _DefaultLogger.ROTATE_CHECK_EVERY !== 0) return;
16437
+ this._tail = this._tail.then(async () => {
16438
+ let st;
16439
+ try {
16440
+ st = await fsp10.stat(file);
16441
+ } catch {
16442
+ return;
16443
+ }
16444
+ if (st.size < this.maxFileBytes) return;
16445
+ try {
16446
+ await fsp10.rm(`${file}.1`, { force: true });
16447
+ await fsp10.rename(file, `${file}.1`);
16448
+ } catch {
16449
+ }
16450
+ }).catch(() => void 0);
16451
+ }
16452
+ log(level, msg, ctx) {
16453
+ const r = LEVEL_RANK2[level];
16454
+ const allowed = LEVEL_RANK2[this.level];
16455
+ if (r > allowed) return;
16456
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
16457
+ const entry = { ts, level, msg, ...this.bindings };
16458
+ if (ctx !== void 0) {
16459
+ entry.ctx = ctx instanceof Error ? { message: ctx.message, stack: ctx.stack } : ctx;
16460
+ }
16461
+ if (this.file) {
16462
+ this.enqueueRotate(this.file);
16463
+ const line = `${JSON.stringify(entry)}
16464
+ `;
16465
+ this._tail = this._tail.then(() => fsp10.appendFile(this.file, line)).catch(() => void 0);
16466
+ }
16467
+ if (!this.stderr) return;
16468
+ if (this.format === "json") {
16469
+ writeErr(`${JSON.stringify(entry)}
16470
+ `);
16471
+ } else {
16472
+ const head = `${color.dim(ts)} ${COLORS[level](level.toUpperCase().padEnd(5))} ${msg}`;
16473
+ if (ctx !== void 0) {
16474
+ writeErr(`${head} ${formatCtx(ctx)}
16475
+ `);
16476
+ } else {
16477
+ writeErr(`${head}
16478
+ `);
16479
+ }
16480
+ }
16481
+ }
16482
+ };
16483
+ function parseLogLevel(raw) {
16484
+ return raw && LOG_LEVELS.has(raw) ? raw : "info";
16485
+ }
16486
+ function parseLogFormat(raw) {
16487
+ return raw && LOG_FORMATS.has(raw) ? raw : "pretty";
16488
+ }
16489
+ function formatCtx(ctx) {
16490
+ if (ctx instanceof Error) return color.dim(ctx.message);
16491
+ if (typeof ctx === "string") return color.dim(ctx);
16492
+ try {
16493
+ return color.dim(JSON.stringify(ctx));
16494
+ } catch {
16495
+ return color.dim(String(ctx));
16496
+ }
16497
+ }
16498
+ var noOpLogger = {
16499
+ // 'error' is the quietest level the Logger contract offers; the methods
16500
+ // discard everything regardless, this only matters to level checks.
16501
+ level: "error",
16502
+ error: () => {
16503
+ },
16504
+ warn: () => {
16505
+ },
16506
+ info: () => {
16507
+ },
16508
+ debug: () => {
16509
+ },
16510
+ trace: () => {
16511
+ },
16512
+ child: () => noOpLogger
16513
+ };
16514
+
16286
16515
  // src/types/default-config.ts
16287
16516
  var DEFAULT_TOOLS_CONFIG = Object.freeze({
16288
16517
  defaultExecutionStrategy: "smart",
@@ -16328,10 +16557,13 @@ var HybridCompactor = class {
16328
16557
  preserveK;
16329
16558
  eliseThreshold;
16330
16559
  smart;
16560
+ logger;
16331
16561
  constructor(opts = {}) {
16332
16562
  this.preserveK = opts.preserveK ?? 5;
16333
16563
  this.eliseThreshold = opts.eliseThreshold ?? 2e3;
16334
16564
  this.smart = opts.smart ?? false;
16565
+ this.logger = opts.logger ?? noOpLogger;
16566
+ setCompactionDebugLogger(this.logger);
16335
16567
  }
16336
16568
  async compact(ctx, opts = {}) {
16337
16569
  const beforeTokens = estimateMessages(ctx.messages);
@@ -16669,7 +16901,7 @@ var AutonomousRunner = class {
16669
16901
  };
16670
16902
 
16671
16903
  // src/storage/goal-store.ts
16672
- import * as fsp10 from "node:fs/promises";
16904
+ import * as fsp11 from "node:fs/promises";
16673
16905
  var MAX_JOURNAL_ENTRIES = 500;
16674
16906
  function goalFilePath(projectRoot) {
16675
16907
  return resolveWstackPaths({ projectRoot }).projectGoal;
@@ -16678,7 +16910,7 @@ async function loadGoal(filePath, events, warn) {
16678
16910
  const t0 = Date.now();
16679
16911
  let raw;
16680
16912
  try {
16681
- raw = await fsp10.readFile(filePath, "utf8");
16913
+ raw = await fsp11.readFile(filePath, "utf8");
16682
16914
  } catch (err) {
16683
16915
  const code = err.code;
16684
16916
  if (code === "ENOENT") {
@@ -16867,7 +17099,7 @@ function isDesignStack(v) {
16867
17099
  // src/execution/design-kit-loader.ts
16868
17100
  import { existsSync } from "node:fs";
16869
17101
  import * as fs4 from "node:fs/promises";
16870
- import * as path14 from "node:path";
17102
+ import * as path15 from "node:path";
16871
17103
  import { fileURLToPath as fileURLToPath3 } from "node:url";
16872
17104
  var KIT_FILE = "KIT.md";
16873
17105
  var TOKENS_FILE = "tokens.json";
@@ -16980,7 +17212,7 @@ var DefaultDesignKitLoader = class {
16980
17212
  }
16981
17213
  for (const e of entries) {
16982
17214
  if (!e.isDirectory()) continue;
16983
- const kitFile = path14.join(dir, e.name, KIT_FILE);
17215
+ const kitFile = path15.join(dir, e.name, KIT_FILE);
16984
17216
  try {
16985
17217
  const raw = await fs4.readFile(kitFile, "utf8");
16986
17218
  const fm = parseKitFrontmatter(raw);
@@ -17054,7 +17286,7 @@ var DefaultDesignKitLoader = class {
17054
17286
  const m = await this.find(id);
17055
17287
  let tokens;
17056
17288
  if (m) {
17057
- const tokensPath = path14.join(path14.dirname(m.path), TOKENS_FILE);
17289
+ const tokensPath = path15.join(path15.dirname(m.path), TOKENS_FILE);
17058
17290
  try {
17059
17291
  const raw = await fs4.readFile(tokensPath, "utf8");
17060
17292
  const parsed = JSON.parse(raw);
@@ -17081,12 +17313,12 @@ var DefaultDesignKitLoader = class {
17081
17313
  };
17082
17314
  function resolveBundledDesignKitsDir() {
17083
17315
  try {
17084
- const here = path14.dirname(fileURLToPath3(import.meta.url));
17316
+ const here = path15.dirname(fileURLToPath3(import.meta.url));
17085
17317
  const candidates = [
17086
- path14.join(here, "design-kits"),
17087
- path14.join(here, "..", "design-kits"),
17088
- path14.join(here, "..", "..", "design-kits"),
17089
- path14.join(here, "..", "..", "..", "design-kits")
17318
+ path15.join(here, "design-kits"),
17319
+ path15.join(here, "..", "design-kits"),
17320
+ path15.join(here, "..", "..", "design-kits"),
17321
+ path15.join(here, "..", "..", "..", "design-kits")
17090
17322
  ];
17091
17323
  for (const c of candidates) {
17092
17324
  if (existsSync(c)) return c;
@@ -17115,10 +17347,10 @@ function _resetDesignKitLoaderMemo() {
17115
17347
  // src/execution/design-project-store.ts
17116
17348
  import { existsSync as existsSync2 } from "node:fs";
17117
17349
  import * as fs5 from "node:fs/promises";
17118
- import * as path15 from "node:path";
17350
+ import * as path16 from "node:path";
17119
17351
  var DESIGN_DIR = ".design";
17120
17352
  function designProjectDir(projectRoot) {
17121
- return path15.join(projectRoot, DESIGN_DIR);
17353
+ return path16.join(projectRoot, DESIGN_DIR);
17122
17354
  }
17123
17355
  var RULE_FILES = ["rules.md", "RULES.md", "design.md"];
17124
17356
  var rulesCache = /* @__PURE__ */ new Map();
@@ -17127,7 +17359,7 @@ async function loadProjectDesignRules(projectRoot) {
17127
17359
  let rules;
17128
17360
  for (const name of RULE_FILES) {
17129
17361
  try {
17130
- const txt = await fs5.readFile(path15.join(designProjectDir(projectRoot), name), "utf8");
17362
+ const txt = await fs5.readFile(path16.join(designProjectDir(projectRoot), name), "utf8");
17131
17363
  if (txt.trim()) {
17132
17364
  rules = txt.trim();
17133
17365
  break;
@@ -17148,7 +17380,7 @@ function parseOverrides(value) {
17148
17380
  }
17149
17381
  async function loadActiveKit(projectRoot) {
17150
17382
  try {
17151
- const raw = await fs5.readFile(path15.join(designProjectDir(projectRoot), "active.json"), "utf8");
17383
+ const raw = await fs5.readFile(path16.join(designProjectDir(projectRoot), "active.json"), "utf8");
17152
17384
  const parsed = JSON.parse(raw);
17153
17385
  if (parsed && typeof parsed.kit === "string") {
17154
17386
  return {
@@ -17182,7 +17414,7 @@ function applyTokenOverrides(tokens, overrides) {
17182
17414
  async function ensureDesignDir(projectRoot) {
17183
17415
  const dir = designProjectDir(projectRoot);
17184
17416
  await fs5.mkdir(dir, { recursive: true });
17185
- const gi = path15.join(dir, ".gitignore");
17417
+ const gi = path16.join(dir, ".gitignore");
17186
17418
  if (!existsSync2(gi)) {
17187
17419
  try {
17188
17420
  await fs5.writeFile(gi, "*\n");
@@ -17196,11 +17428,11 @@ async function recordKitChoice(projectRoot, kit, stack, source, isoTime, overrid
17196
17428
  const dir = await ensureDesignDir(projectRoot);
17197
17429
  const record = { kit, stack: stack ?? null };
17198
17430
  if (overrides && Object.keys(overrides).length > 0) record.overrides = overrides;
17199
- await fs5.writeFile(path15.join(dir, "active.json"), `${JSON.stringify(record, null, 2)}
17431
+ await fs5.writeFile(path16.join(dir, "active.json"), `${JSON.stringify(record, null, 2)}
17200
17432
  `);
17201
17433
  const line = `- ${isoTime} \xB7 kit=${kit}${stack ? ` stack=${stack}` : ""} \xB7 via=${source}
17202
17434
  `;
17203
- await fs5.appendFile(path15.join(dir, "decisions.md"), line);
17435
+ await fs5.appendFile(path16.join(dir, "decisions.md"), line);
17204
17436
  } catch {
17205
17437
  }
17206
17438
  }
@@ -17216,11 +17448,11 @@ async function recordOverrides(projectRoot, patch, isoTime) {
17216
17448
  const dir = await ensureDesignDir(projectRoot);
17217
17449
  const record = { kit: active.kit, stack: active.stack ?? null };
17218
17450
  if (Object.keys(merged).length > 0) record.overrides = merged;
17219
- await fs5.writeFile(path15.join(dir, "active.json"), `${JSON.stringify(record, null, 2)}
17451
+ await fs5.writeFile(path16.join(dir, "active.json"), `${JSON.stringify(record, null, 2)}
17220
17452
  `);
17221
17453
  const keys = Object.keys(patch).join(",");
17222
17454
  await fs5.appendFile(
17223
- path15.join(dir, "decisions.md"),
17455
+ path16.join(dir, "decisions.md"),
17224
17456
  `- ${isoTime} \xB7 kit=${active.kit} \xB7 override=${keys} \xB7 via=set
17225
17457
  `
17226
17458
  );
@@ -17230,7 +17462,7 @@ async function recordOverrides(projectRoot, patch, isoTime) {
17230
17462
  }
17231
17463
  async function clearPersistedActiveKit(projectRoot) {
17232
17464
  try {
17233
- await fs5.rm(path15.join(designProjectDir(projectRoot), "active.json"), { force: true });
17465
+ await fs5.rm(path16.join(designProjectDir(projectRoot), "active.json"), { force: true });
17234
17466
  } catch {
17235
17467
  }
17236
17468
  }
@@ -19014,6 +19246,7 @@ var IntelligentCompactor = class {
19014
19246
  summarizerPrompt;
19015
19247
  summarizerModel;
19016
19248
  oneShotOrchestrator;
19249
+ logger;
19017
19250
  constructor(opts) {
19018
19251
  this.provider = opts.provider;
19019
19252
  this.warnThreshold = opts.warnThreshold ?? 0.5;
@@ -19025,6 +19258,8 @@ var IntelligentCompactor = class {
19025
19258
  this.summarizerPrompt = opts.summarizerPrompt ?? readBundledInstructionText("llm/intelligent-compactor-summarizer.md");
19026
19259
  this.summarizerModel = opts.summarizerModel;
19027
19260
  this.oneShotOrchestrator = opts.oneShotOrchestrator;
19261
+ this.logger = opts.logger ?? noOpLogger;
19262
+ setCompactionDebugLogger(this.logger);
19028
19263
  }
19029
19264
  async compact(ctx, opts = {}) {
19030
19265
  const beforeTokens = estimateMessages(ctx.messages);
@@ -20418,11 +20653,13 @@ var LLMSelector = class {
20418
20653
  systemPrompt;
20419
20654
  maxOutputTokens;
20420
20655
  oneShotOrchestrator;
20656
+ logger;
20421
20657
  constructor(opts) {
20422
20658
  this.provider = opts.provider;
20423
20659
  this.model = opts.model ?? "unknown";
20660
+ this.logger = opts.logger ?? noOpLogger;
20424
20661
  if (this.model === "unknown" && (process.env["NODE_ENV"] === "development" || process.env["WRONGSTACK_DEBUG"] === "1")) {
20425
- console.warn(
20662
+ this.logger.warn(
20426
20663
  "[LLMSelector] model not set \u2014 selector will use the provider default. Set `model` explicitly in LLMSelectorOptions to silence this warning."
20427
20664
  );
20428
20665
  }
@@ -20472,14 +20709,9 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
20472
20709
  }
20473
20710
  } catch (err) {
20474
20711
  if (err instanceof Error) {
20475
- console.warn(
20476
- JSON.stringify({
20477
- level: "warn",
20478
- event: "llm_selector.call_failed",
20479
- message: `selector call failed, using recency fallback: ${err.message}`,
20480
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
20481
- })
20482
- );
20712
+ this.logger.warn(`selector call failed, using recency fallback: ${err.message}`, {
20713
+ event: "llm_selector.call_failed"
20714
+ });
20483
20715
  }
20484
20716
  return this.fallbackSelect(messages, effectiveBudget);
20485
20717
  } finally {
@@ -20586,6 +20818,7 @@ var SelectiveCompactor = class {
20586
20818
  eliseThreshold;
20587
20819
  summarizerModel;
20588
20820
  summarizerPrompt;
20821
+ logger;
20589
20822
  constructor(opts) {
20590
20823
  this.provider = opts.provider;
20591
20824
  this.selector = opts.selector ?? new LLMSelector({ provider: opts.provider, model: opts.selectorModel, maxOutputTokens: opts.selectorMaxOutputTokens });
@@ -20596,8 +20829,10 @@ var SelectiveCompactor = class {
20596
20829
  this.preserveK = opts.preserveK ?? 4;
20597
20830
  this.eliseThreshold = opts.eliseThreshold ?? 300;
20598
20831
  this.summarizerModel = opts.summarizerModel ?? opts.selectorModel;
20832
+ this.logger = opts.logger ?? noOpLogger;
20833
+ setCompactionDebugLogger(this.logger);
20599
20834
  if (this.summarizerModel === void 0 && (process.env["NODE_ENV"] === "development" || process.env["WRONGSTACK_DEBUG"] === "1")) {
20600
- console.warn(
20835
+ this.logger.warn(
20601
20836
  "[SelectiveCompactor] summarizerModel not set \u2014 will fall back to ctx.model at summarize time. Set `summarizerModel` explicitly to silence this warning."
20602
20837
  );
20603
20838
  }
@@ -20826,7 +21061,7 @@ Summarize the following message range:`;
20826
21061
 
20827
21062
  // src/execution/skill-loader.ts
20828
21063
  import * as fs6 from "node:fs/promises";
20829
- import * as path16 from "node:path";
21064
+ import * as path17 from "node:path";
20830
21065
 
20831
21066
  // src/skills/foreign-sources.ts
20832
21067
  var FOREIGN_SKILL_TOOLS = [
@@ -20976,7 +21211,7 @@ async function entryIsDirectory(dir, entry) {
20976
21211
  if (entry.isDirectory()) return true;
20977
21212
  if (entry.isSymbolicLink()) {
20978
21213
  try {
20979
- return (await fs6.stat(path16.join(dir, entry.name))).isDirectory();
21214
+ return (await fs6.stat(path17.join(dir, entry.name))).isDirectory();
20980
21215
  } catch {
20981
21216
  return false;
20982
21217
  }
@@ -20997,7 +21232,7 @@ var DefaultSkillLoader = class {
20997
21232
  for (const tool of FOREIGN_SKILL_TOOLS) {
20998
21233
  if (!foreignIds.includes(tool.id)) continue;
20999
21234
  dirs.push({
21000
- dir: path16.join(root, "." + tool.id, tool.subdir),
21235
+ dir: path17.join(root, "." + tool.id, tool.subdir),
21001
21236
  source: "foreign",
21002
21237
  originTool: tool.id
21003
21238
  });
@@ -21024,7 +21259,7 @@ var DefaultSkillLoader = class {
21024
21259
  );
21025
21260
  for (const e of entries) {
21026
21261
  if (!await entryIsDirectory(dir, e)) continue;
21027
- const skillFile = path16.join(dir, e.name, "SKILL.md");
21262
+ const skillFile = path17.join(dir, e.name, "SKILL.md");
21028
21263
  try {
21029
21264
  const raw = await fs6.readFile(skillFile, "utf8");
21030
21265
  const fm = parseSkillFrontmatter(raw);
@@ -21108,7 +21343,7 @@ var DefaultSkillLoader = class {
21108
21343
  if (cached !== void 0) return cached;
21109
21344
  const m = await this.find(name);
21110
21345
  if (!m) throw new Error(`Skill "${name}" not found`);
21111
- const savePath = path16.join(path16.dirname(m.path), "SKILL.save.md");
21346
+ const savePath = path17.join(path17.dirname(m.path), "SKILL.save.md");
21112
21347
  let result;
21113
21348
  try {
21114
21349
  result = await fs6.readFile(savePath, "utf8");
@@ -21142,11 +21377,11 @@ function parseDescriptionFromText(desc) {
21142
21377
 
21143
21378
  // src/execution/prompt-loader.ts
21144
21379
  import * as fs8 from "node:fs/promises";
21145
- import * as path18 from "node:path";
21380
+ import * as path19 from "node:path";
21146
21381
 
21147
21382
  // src/storage/prompt-store.ts
21148
21383
  import * as fs7 from "node:fs/promises";
21149
- import * as path17 from "node:path";
21384
+ import * as path18 from "node:path";
21150
21385
  var SCHEMA_VERSION = 2;
21151
21386
  function migratePromptEntry(raw) {
21152
21387
  if (!raw || typeof raw !== "object") return null;
@@ -21212,7 +21447,7 @@ var DefaultPromptStore = class {
21212
21447
  if (!file.endsWith(".json")) continue;
21213
21448
  try {
21214
21449
  const raw = JSON.parse(
21215
- await fs7.readFile(path17.join(this.dir, file), "utf8")
21450
+ await fs7.readFile(path18.join(this.dir, file), "utf8")
21216
21451
  );
21217
21452
  const migrated = migratePromptEntry(raw.entry);
21218
21453
  if (migrated) entries.push(migrated);
@@ -21226,7 +21461,7 @@ var DefaultPromptStore = class {
21226
21461
  );
21227
21462
  }
21228
21463
  async get(id) {
21229
- const file = path17.join(this.dir, `${id}.json`);
21464
+ const file = path18.join(this.dir, `${id}.json`);
21230
21465
  try {
21231
21466
  const raw = JSON.parse(await fs7.readFile(file, "utf8"));
21232
21467
  return migratePromptEntry(raw.entry);
@@ -21236,12 +21471,12 @@ var DefaultPromptStore = class {
21236
21471
  }
21237
21472
  async save(entry) {
21238
21473
  await ensureDir(this.dir);
21239
- const file = path17.join(this.dir, `${entry.id}.json`);
21474
+ const file = path18.join(this.dir, `${entry.id}.json`);
21240
21475
  const raw = { version: SCHEMA_VERSION, entry };
21241
21476
  await atomicWrite(file, JSON.stringify(raw, null, 2));
21242
21477
  }
21243
21478
  async delete(id) {
21244
- const file = path17.join(this.dir, `${id}.json`);
21479
+ const file = path18.join(this.dir, `${id}.json`);
21245
21480
  try {
21246
21481
  await fs7.unlink(file);
21247
21482
  return true;
@@ -21330,7 +21565,7 @@ var DefaultPromptLoader = class {
21330
21565
  constructor(opts) {
21331
21566
  this.projectStore = typeof opts.paths.inProjectPrompts === "string" ? new DefaultPromptStore(opts.paths.inProjectPrompts) : void 0;
21332
21567
  this.userStore = typeof opts.paths.globalPrompts === "string" ? new DefaultPromptStore(opts.paths.globalPrompts) : void 0;
21333
- this.builtinDir = opts.bundledDir ? path18.join(opts.bundledDir, "prompts") : void 0;
21568
+ this.builtinDir = opts.bundledDir ? path19.join(opts.bundledDir, "prompts") : void 0;
21334
21569
  }
21335
21570
  async list() {
21336
21571
  if (this.cache) return this.cache;
@@ -21462,7 +21697,7 @@ async function walkJson(dir) {
21462
21697
  return out;
21463
21698
  }
21464
21699
  for (const e of entries) {
21465
- const full = path18.join(dir, e.name);
21700
+ const full = path19.join(dir, e.name);
21466
21701
  if (e.isDirectory()) {
21467
21702
  out.push(...await walkJson(full));
21468
21703
  } else if (e.name.endsWith(".json") && e.name !== "index.json" && e.name !== "schema.json") {
@@ -21661,11 +21896,11 @@ function runWithProcessTelemetry(context, run) {
21661
21896
  // src/execution/tool-executor.ts
21662
21897
  import { isDeepStrictEqual } from "node:util";
21663
21898
  import * as fs9 from "node:fs/promises";
21664
- import * as path20 from "node:path";
21899
+ import * as path21 from "node:path";
21665
21900
 
21666
21901
  // src/security/kanban-boundary.ts
21667
21902
  import { realpath as realpath3 } from "node:fs/promises";
21668
- import * as path19 from "node:path";
21903
+ import * as path20 from "node:path";
21669
21904
  import {
21670
21905
  evaluateKanbanBoundaryOpaque,
21671
21906
  evaluateKanbanBoundaryPath,
@@ -21740,10 +21975,10 @@ function resolveKanbanIdentity(ctx) {
21740
21975
  async function extractCandidatePaths(toolName, input, ctx) {
21741
21976
  if (toolName === "patch" && typeof input["patch"] === "string") {
21742
21977
  const directoryInput = stringValue(input["directory"]) ?? ctx.workingDir;
21743
- const directory = path19.isAbsolute(directoryInput) ? directoryInput : path19.resolve(ctx.workingDir, directoryInput);
21978
+ const directory = path20.isAbsolute(directoryInput) ? directoryInput : path20.resolve(ctx.workingDir, directoryInput);
21744
21979
  const strip = Math.max(1, numericValue(input["strip"]) ?? 1);
21745
21980
  const targets = extractPatchTargets(input["patch"], strip).map(
21746
- (target) => relativeToProject(path19.resolve(directory, target), ctx.projectRoot)
21981
+ (target) => relativeToProject(path20.resolve(directory, target), ctx.projectRoot)
21747
21982
  );
21748
21983
  return Promise.all(targets.map((target) => canonicalizeCandidatePath(target, ctx)));
21749
21984
  }
@@ -21753,7 +21988,7 @@ async function extractCandidatePaths(toolName, input, ctx) {
21753
21988
  collectPathValues(input, values, pathKeys);
21754
21989
  if (toolName === "scaffold" && typeof input["name"] === "string") {
21755
21990
  const cwd = stringValue(input["cwd"]) ?? ctx.workingDir;
21756
- values.push(path19.join(cwd, input["name"]));
21991
+ values.push(path20.join(cwd, input["name"]));
21757
21992
  }
21758
21993
  const candidates = [
21759
21994
  ...new Set(values.flatMap(splitPathList).map((value) => resolveInputPath(value, ctx)))
@@ -21761,21 +21996,21 @@ async function extractCandidatePaths(toolName, input, ctx) {
21761
21996
  return Promise.all(candidates.map((candidate) => canonicalizeCandidatePath(candidate, ctx)));
21762
21997
  }
21763
21998
  async function canonicalizeCandidatePath(candidate, ctx) {
21764
- if (path19.isAbsolute(candidate)) return candidate;
21765
- const absolute = path19.resolve(ctx.projectRoot, candidate);
21999
+ if (path20.isAbsolute(candidate)) return candidate;
22000
+ const absolute = path20.resolve(ctx.projectRoot, candidate);
21766
22001
  const canonicalRoot = await realpath3(ctx.projectRoot).catch(() => ctx.projectRoot);
21767
22002
  let probe = absolute;
21768
22003
  const missingSegments = [];
21769
22004
  while (true) {
21770
22005
  try {
21771
- const canonical = path19.join(await realpath3(probe), ...missingSegments);
22006
+ const canonical = path20.join(await realpath3(probe), ...missingSegments);
21772
22007
  return relativeToProject(canonical, canonicalRoot);
21773
22008
  } catch (cause) {
21774
22009
  const code = cause.code;
21775
22010
  if (code !== "ENOENT" && code !== "ENOTDIR") return absolute;
21776
- const parent = path19.dirname(probe);
22011
+ const parent = path20.dirname(probe);
21777
22012
  if (parent === probe) return absolute;
21778
- missingSegments.unshift(path19.basename(probe));
22013
+ missingSegments.unshift(path20.basename(probe));
21779
22014
  probe = parent;
21780
22015
  }
21781
22016
  }
@@ -21798,12 +22033,12 @@ function splitPathList(value) {
21798
22033
  return value.split(",").map((item) => item.trim()).filter(Boolean);
21799
22034
  }
21800
22035
  function resolveInputPath(value, ctx) {
21801
- const absolute = path19.isAbsolute(value) ? value : path19.resolve(ctx.workingDir, value);
22036
+ const absolute = path20.isAbsolute(value) ? value : path20.resolve(ctx.workingDir, value);
21802
22037
  return relativeToProject(absolute, ctx.projectRoot);
21803
22038
  }
21804
22039
  function relativeToProject(absolute, projectRoot) {
21805
- const relative6 = path19.relative(projectRoot, absolute).replace(/\\/g, "/");
21806
- return relative6.startsWith("../") || path19.isAbsolute(relative6) ? absolute : relative6 || ".";
22040
+ const relative6 = path20.relative(projectRoot, absolute).replace(/\\/g, "/");
22041
+ return relative6.startsWith("../") || path20.isAbsolute(relative6) ? absolute : relative6 || ".";
21807
22042
  }
21808
22043
  function extractPatchTargets(patchText, strip) {
21809
22044
  const targets = [];
@@ -22147,7 +22382,7 @@ ${errorDetails}`,
22147
22382
  const inputPath = use.input && typeof use.input === "object" ? use.input.path : void 0;
22148
22383
  const caps = tool.capabilities ?? [];
22149
22384
  const hasFileCapability = caps.includes("fs.read") || caps.includes("fs.write");
22150
- const absPath = hasFileCapability && typeof inputPath === "string" ? path20.isAbsolute(inputPath) ? inputPath : path20.resolve(ctx.projectRoot, inputPath) : void 0;
22385
+ const absPath = hasFileCapability && typeof inputPath === "string" ? path21.isAbsolute(inputPath) ? inputPath : path21.resolve(ctx.projectRoot, inputPath) : void 0;
22151
22386
  let writeTargetExisted;
22152
22387
  if (tool.name === "write" && caps.includes("fs.write") && absPath) {
22153
22388
  writeTargetExisted = await fs9.stat(absPath).then(
@@ -22739,11 +22974,11 @@ async function maybePersistLargeToolOutput(toolName, content, budget) {
22739
22974
  return content;
22740
22975
  }
22741
22976
  try {
22742
- const dir = path20.join(wstackGlobalRoot(), "tool-output");
22977
+ const dir = path21.join(wstackGlobalRoot(), "tool-output");
22743
22978
  await fs9.mkdir(dir, { recursive: true });
22744
22979
  const safeTool = toolName.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 40) || "tool";
22745
22980
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
22746
- const filePath = path20.join(dir, `${stamp}-${safeTool}-${randomUUID11()}.log`);
22981
+ const filePath = path21.join(dir, `${stamp}-${safeTool}-${randomUUID11()}.log`);
22747
22982
  await fs9.writeFile(filePath, content, "utf8");
22748
22983
  const marker = `[full tool output: ${bytes} bytes at ${filePath}; read/grep that file selectively instead of re-running or requesting more output]`;
22749
22984
  const fixedBytes = Buffer.byteLength(marker + TOOL_OUTPUT_ARTIFACT_OMISSION, "utf8");
@@ -23062,191 +23297,6 @@ function createContextManagerTool(opts = {}) {
23062
23297
  }
23063
23298
  var contextManagerTool = createContextManagerTool();
23064
23299
 
23065
- // src/infrastructure/logger.ts
23066
- import * as fsp11 from "node:fs/promises";
23067
- import * as path21 from "node:path";
23068
- var LEVEL_RANK2 = {
23069
- error: 0,
23070
- warn: 1,
23071
- info: 2,
23072
- debug: 3,
23073
- trace: 4
23074
- };
23075
- var COLORS = {
23076
- error: color.red,
23077
- warn: color.yellow,
23078
- info: color.cyan,
23079
- debug: color.gray,
23080
- trace: color.dim
23081
- };
23082
- var LOG_LEVELS = /* @__PURE__ */ new Set(["error", "warn", "info", "debug", "trace"]);
23083
- var LOG_FORMATS = /* @__PURE__ */ new Set(["pretty", "json"]);
23084
- var DefaultLogger = class _DefaultLogger {
23085
- /** How many file writes between rotation size checks (statSync is not free). */
23086
- static ROTATE_CHECK_EVERY = 100;
23087
- level;
23088
- file;
23089
- bindings;
23090
- format;
23091
- stderr;
23092
- maxFileBytes;
23093
- writesSinceRotateCheck = 0;
23094
- /**
23095
- * Serialized async tail for file writes. Every appendFile (and any
23096
- * chained rotation) is awaited through this promise so file I/O
23097
- * never overlaps itself — preserving the per-line ordering the
23098
- * sync version had, but without blocking the caller thread. Any
23099
- * rejection is swallowed (`catch(() => {})`) because logging must
23100
- * never crash the host.
23101
- *
23102
- * Children share the parent's tail: `child.tail === parent.tail`
23103
- * for the lifetime of the chain. Read/write access goes through
23104
- * `_tail` so that, when a child has been wired to a parent, both
23105
- * `enqueueRotate` and `log` always observe the parent's current tail
23106
- * rather than a stale snapshot taken at `child()` time.
23107
- */
23108
- tail = Promise.resolve();
23109
- parent = null;
23110
- /**
23111
- * Resolve the current tail. For the root logger this is the field;
23112
- * for a child logger we always read through the parent so that a
23113
- * child's appends land on the parent's most recent tail, and a
23114
- * parent's `flush()` waits for everything the child chained.
23115
- */
23116
- get _tail() {
23117
- return this.parent ? this.parent._tail : this.tail;
23118
- }
23119
- set _tail(next) {
23120
- if (this.parent) this.parent.tail = next;
23121
- else this.tail = next;
23122
- }
23123
- constructor(opts = {}) {
23124
- this.level = opts.level ?? parseLogLevel(process.env.WRONGSTACK_LOG_LEVEL);
23125
- this.file = opts.file;
23126
- this.bindings = opts.bindings ?? {};
23127
- this.format = opts.format ?? parseLogFormat(process.env.WRONGSTACK_LOG_FORMAT);
23128
- this.stderr = opts.stderr !== false;
23129
- this.maxFileBytes = opts.maxFileBytes ?? 10 * 1024 * 1024;
23130
- if (this.file) {
23131
- const dir = path21.dirname(this.file);
23132
- this._tail = this._tail.then(async () => {
23133
- await fsp11.mkdir(dir, { recursive: true });
23134
- }).catch(() => void 0);
23135
- }
23136
- }
23137
- error(msg, ctx) {
23138
- this.log("error", msg, ctx);
23139
- }
23140
- warn(msg, ctx) {
23141
- this.log("warn", msg, ctx);
23142
- }
23143
- info(msg, ctx) {
23144
- this.log("info", msg, ctx);
23145
- }
23146
- debug(msg, ctx) {
23147
- this.log("debug", msg, ctx);
23148
- }
23149
- trace(msg, ctx) {
23150
- this.log("trace", msg, ctx);
23151
- }
23152
- child(bindings) {
23153
- const child = Object.create(_DefaultLogger.prototype);
23154
- child.level = this.level;
23155
- child.file = this.file;
23156
- child.bindings = { ...this.bindings, ...bindings };
23157
- child.format = this.format;
23158
- child.stderr = this.stderr;
23159
- child.maxFileBytes = this.maxFileBytes;
23160
- child.parent = this;
23161
- child.writesSinceRotateCheck = this.writesSinceRotateCheck;
23162
- return child;
23163
- }
23164
- /**
23165
- * Wait until all queued file writes (and any pending rotation) have
23166
- * completed. `log()` is fire-and-forget by design — the caller never
23167
- * blocks on disk — so tests, shutdown handlers, and processes that
23168
- * need a deterministic "everything is on disk now" guarantee should
23169
- * `await logger.flush()` before reading the file or exiting.
23170
- */
23171
- flush() {
23172
- return this._tail;
23173
- }
23174
- /**
23175
- * Size-based rotation: when the file outgrows `maxFileBytes`, rename it to
23176
- * `<file>.1` (dropping the previous `.1`) so the live file restarts empty.
23177
- * Checked on the first write and every ROTATE_CHECK_EVERY writes after.
23178
- * Best-effort: a rename can fail on Windows while another process holds
23179
- * the file — the next check retries. Multiple processes appending to the
23180
- * same log all run this check; whoever crosses the threshold first wins.
23181
- *
23182
- * Async: the rotation runs on the file-write tail (so its writes don't
23183
- * interleave with the next append), and the caller never blocks on a
23184
- * statSync / renameSync syscall on the hot log path.
23185
- */
23186
- enqueueRotate(file) {
23187
- if (this.writesSinceRotateCheck++ % _DefaultLogger.ROTATE_CHECK_EVERY !== 0) return;
23188
- this._tail = this._tail.then(async () => {
23189
- let st;
23190
- try {
23191
- st = await fsp11.stat(file);
23192
- } catch {
23193
- return;
23194
- }
23195
- if (st.size < this.maxFileBytes) return;
23196
- try {
23197
- await fsp11.rm(`${file}.1`, { force: true });
23198
- await fsp11.rename(file, `${file}.1`);
23199
- } catch {
23200
- }
23201
- }).catch(() => void 0);
23202
- }
23203
- log(level, msg, ctx) {
23204
- const r = LEVEL_RANK2[level];
23205
- const allowed = LEVEL_RANK2[this.level];
23206
- if (r > allowed) return;
23207
- const ts = (/* @__PURE__ */ new Date()).toISOString();
23208
- const entry = { ts, level, msg, ...this.bindings };
23209
- if (ctx !== void 0) {
23210
- entry.ctx = ctx instanceof Error ? { message: ctx.message, stack: ctx.stack } : ctx;
23211
- }
23212
- if (this.file) {
23213
- this.enqueueRotate(this.file);
23214
- const line = `${JSON.stringify(entry)}
23215
- `;
23216
- this._tail = this._tail.then(() => fsp11.appendFile(this.file, line)).catch(() => void 0);
23217
- }
23218
- if (!this.stderr) return;
23219
- if (this.format === "json") {
23220
- writeErr(`${JSON.stringify(entry)}
23221
- `);
23222
- } else {
23223
- const head = `${color.dim(ts)} ${COLORS[level](level.toUpperCase().padEnd(5))} ${msg}`;
23224
- if (ctx !== void 0) {
23225
- writeErr(`${head} ${formatCtx(ctx)}
23226
- `);
23227
- } else {
23228
- writeErr(`${head}
23229
- `);
23230
- }
23231
- }
23232
- }
23233
- };
23234
- function parseLogLevel(raw) {
23235
- return raw && LOG_LEVELS.has(raw) ? raw : "info";
23236
- }
23237
- function parseLogFormat(raw) {
23238
- return raw && LOG_FORMATS.has(raw) ? raw : "pretty";
23239
- }
23240
- function formatCtx(ctx) {
23241
- if (ctx instanceof Error) return color.dim(ctx.message);
23242
- if (typeof ctx === "string") return color.dim(ctx);
23243
- try {
23244
- return color.dim(JSON.stringify(ctx));
23245
- } catch {
23246
- return color.dim(String(ctx));
23247
- }
23248
- }
23249
-
23250
23300
  // src/infrastructure/mcp-servers.ts
23251
23301
  var filesystemServer = () => ({
23252
23302
  name: "filesystem",
@@ -23825,6 +23875,7 @@ var DefaultModelsRegistry = class {
23825
23875
  overlayUrl;
23826
23876
  overlayFile;
23827
23877
  overlayCacheFile;
23878
+ logger;
23828
23879
  constructor(opts) {
23829
23880
  this.cacheFile = opts.cacheFile;
23830
23881
  this.url = opts.url ?? process.env[ENV_URL_KEY] ?? DEFAULT_URL;
@@ -23838,6 +23889,7 @@ var DefaultModelsRegistry = class {
23838
23889
  this.overlayUrl = opts.overlayUrl;
23839
23890
  this.overlayFile = opts.overlayFile;
23840
23891
  this.overlayCacheFile = opts.overlayCacheFile ?? (opts.overlayUrl ? path24.join(path24.dirname(opts.cacheFile), "models-overlay-cache.json") : void 0);
23892
+ this.logger = opts.logger ?? noOpLogger;
23841
23893
  }
23842
23894
  async load(opts = {}) {
23843
23895
  if (this.payload && !opts.force) return this.payload;
@@ -23886,16 +23938,18 @@ var DefaultModelsRegistry = class {
23886
23938
  if (cached && this.isWithinMaxStaleAge(cached.fetchedAt)) {
23887
23939
  this.fetchedAt = new Date(cached.fetchedAt);
23888
23940
  const ageSeconds = Math.floor((Date.now() - this.fetchedAt.getTime()) / 1e3);
23889
- console.warn(
23890
- `ModelsRegistry: models.dev unavailable (${toErrorMessage(err)}); using stale cache from ${formatAge(ageSeconds)} ago. Run \`wstack models refresh\` to retry.`
23941
+ this.logger.warn(
23942
+ `ModelsRegistry: models.dev unavailable (${toErrorMessage(err)}); using stale cache from ${formatAge(ageSeconds)} ago. Run \`wstack models refresh\` to retry.`,
23943
+ { event: "models_registry.stale_cache_fallback" }
23891
23944
  );
23892
23945
  return cached.payload;
23893
23946
  }
23894
23947
  if (overlayAvailable) {
23895
- console.warn(
23948
+ this.logger.warn(
23896
23949
  `ModelsRegistry: models.dev unavailable (${toErrorMessage(
23897
23950
  err
23898
- )}); serving curated overlay only.`
23951
+ )}); serving curated overlay only.`,
23952
+ { event: "models_registry.overlay_only_fallback" }
23899
23953
  );
23900
23954
  return {};
23901
23955
  }
@@ -23991,8 +24045,9 @@ var DefaultModelsRegistry = class {
23991
24045
  const cached = await this.readCacheAt(this.overlayCacheFile);
23992
24046
  if (cached && this.isWithinMaxStaleAge(cached.fetchedAt)) {
23993
24047
  const ageSeconds = Math.floor((Date.now() - new Date(cached.fetchedAt).getTime()) / 1e3);
23994
- console.warn(
23995
- `ModelsRegistry: overlay unavailable; using stale overlay from ${formatAge(ageSeconds)} ago.`
24048
+ this.logger.warn(
24049
+ `ModelsRegistry: overlay unavailable; using stale overlay from ${formatAge(ageSeconds)} ago.`,
24050
+ { event: "models_registry.overlay_stale_fallback", ageSeconds }
23996
24051
  );
23997
24052
  return cached.payload;
23998
24053
  }
@@ -25145,9 +25200,6 @@ var DefaultPermissionPolicy = class {
25145
25200
  loaded = false;
25146
25201
  trustFile;
25147
25202
  yolo;
25148
- yoloDestructive;
25149
- /** Deprecated compatibility flag; no longer gates YOLO calls. */
25150
- confirmDestructive;
25151
25203
  /**
25152
25204
  * Session-scoped "soft deny" map. When the user presses 'n' (block once),
25153
25205
  * the tool+pattern is added here. If the LLM retries in the same session,
@@ -25200,8 +25252,6 @@ var DefaultPermissionPolicy = class {
25200
25252
  constructor(opts) {
25201
25253
  this.trustFile = opts.trustFile;
25202
25254
  this.yolo = opts.yolo ?? false;
25203
- this.yoloDestructive = opts.yoloDestructive ?? opts.forceAllYolo ?? false;
25204
- this.confirmDestructive = opts.confirmDestructive ?? false;
25205
25255
  this.promptDelegate = opts.promptDelegate;
25206
25256
  }
25207
25257
  /**
@@ -25222,24 +25272,6 @@ var DefaultPermissionPolicy = class {
25222
25272
  getYolo() {
25223
25273
  return this.yolo;
25224
25274
  }
25225
- /** Toggle the destructive YOLO override at runtime. */
25226
- setYoloDestructive(enabled) {
25227
- if (this.yoloDestructive !== enabled) this._evalCache.clear();
25228
- this.yoloDestructive = enabled;
25229
- }
25230
- /** Check whether the destructive YOLO override is active. */
25231
- getYoloDestructive() {
25232
- return this.yoloDestructive;
25233
- }
25234
- /** Toggle deprecated destructive confirmation compatibility flag. */
25235
- setConfirmDestructive(enabled) {
25236
- if (this.confirmDestructive !== enabled) this._evalCache.clear();
25237
- this.confirmDestructive = enabled;
25238
- }
25239
- /** Check deprecated destructive confirmation compatibility flag. */
25240
- getConfirmDestructive() {
25241
- return this.confirmDestructive;
25242
- }
25243
25275
  /** Read-only diagnostics for policy inspector/editor surfaces. */
25244
25276
  getPolicyDiagnostics() {
25245
25277
  return this.policyDiagnostics.map((diagnostic) => ({ ...diagnostic }));
@@ -26540,9 +26572,10 @@ var BEHAVIOR_DEFAULTS = {
26540
26572
  prompts: true,
26541
26573
  // 'auto' → resolveTokenSavingTier picks a concrete tier from the model's
26542
26574
  // context window ONCE per session (cache-safe): lean prompt on small
26543
- // windows (<32k medium, <96k light) where the fixed identity+tool prose is
26544
- // a big fraction; full prompt (off) on 96k+ so nothing changes for the
26545
- // common large-window case. Explicit tiers are respected verbatim.
26575
+ // windows (<32k medium, <128k light) where the fixed identity+tool prose
26576
+ // is a big fraction; minimal trimming on >=128k so modern large-window
26577
+ // models still get cost savings without capability loss. Explicit tiers
26578
+ // are respected verbatim.
26546
26579
  tokenSavingMode: "auto",
26547
26580
  allowOutsideProjectRoot: true
26548
26581
  },
@@ -26610,7 +26643,7 @@ var BEHAVIOR_DEFAULTS = {
26610
26643
  // Mirrored from the top-level yolo default so the autonomy subsystem
26611
26644
  // (which reads autonomy.yolo) stays consistent with config.yolo.
26612
26645
  yolo: false,
26613
- streamFleet: true,
26646
+ fleetChatVerbosity: "off",
26614
26647
  chime: false,
26615
26648
  confirmExit: true,
26616
26649
  mouseMode: false,
@@ -26628,7 +26661,7 @@ var BEHAVIOR_DEFAULTS = {
26628
26661
  // silently omitted, and surfaced as a per-request warning. Users who
26629
26662
  // want a specific effort can opt in via `/settings` or the WebUI panel.
26630
26663
  reasoning: { mode: "auto" },
26631
- cache: {}
26664
+ cache: { ttl: "1h" }
26632
26665
  }
26633
26666
  };
26634
26667
  function isPlainRecord(value) {