@wrongstack/core 0.292.1 → 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 (51) hide show
  1. package/dist/coordination/index.js.map +2 -2
  2. package/dist/core/fallback-profile-manager.d.ts +0 -2
  3. package/dist/core/fallback-profile-manager.d.ts.map +1 -1
  4. package/dist/core/system-prompt-builder.d.ts.map +1 -1
  5. package/dist/defaults/index.js +346 -317
  6. package/dist/defaults/index.js.map +4 -4
  7. package/dist/execution/compaction-core.d.ts +3 -0
  8. package/dist/execution/compaction-core.d.ts.map +1 -1
  9. package/dist/execution/compactor.d.ts +4 -0
  10. package/dist/execution/compactor.d.ts.map +1 -1
  11. package/dist/execution/index.js +133 -49
  12. package/dist/execution/index.js.map +4 -4
  13. package/dist/execution/intelligent-compactor.d.ts +5 -1
  14. package/dist/execution/intelligent-compactor.d.ts.map +1 -1
  15. package/dist/execution/selective-compactor.d.ts +4 -0
  16. package/dist/execution/selective-compactor.d.ts.map +1 -1
  17. package/dist/goal/phase-orchestrator.d.ts +4 -0
  18. package/dist/goal/phase-orchestrator.d.ts.map +1 -1
  19. package/dist/hooks/runner.d.ts +0 -2
  20. package/dist/hooks/runner.d.ts.map +1 -1
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +116 -109
  24. package/dist/index.js.map +2 -2
  25. package/dist/models/alibaba-token-plan-catalog.d.ts +85 -0
  26. package/dist/models/alibaba-token-plan-catalog.d.ts.map +1 -0
  27. package/dist/models/index.d.ts +1 -0
  28. package/dist/models/index.d.ts.map +1 -1
  29. package/dist/models/index.js +200 -15
  30. package/dist/models/index.js.map +4 -4
  31. package/dist/models/llm-selector.d.ts +4 -0
  32. package/dist/models/llm-selector.d.ts.map +1 -1
  33. package/dist/models/models-registry.d.ts +8 -0
  34. package/dist/models/models-registry.d.ts.map +1 -1
  35. package/dist/security/index.js +0 -23
  36. package/dist/security/index.js.map +2 -2
  37. package/dist/security/permission-policy.d.ts +0 -23
  38. package/dist/security/permission-policy.d.ts.map +1 -1
  39. package/dist/storage/config-loader.d.ts.map +1 -1
  40. package/dist/storage/index.js +6 -5
  41. package/dist/storage/index.js.map +2 -2
  42. package/dist/tools/index.js.map +2 -2
  43. package/dist/types/config.d.ts +9 -16
  44. package/dist/types/config.d.ts.map +1 -1
  45. package/dist/types/index.js +2 -3
  46. package/dist/types/index.js.map +2 -2
  47. package/dist/utils/index.js +15 -0
  48. package/dist/utils/index.js.map +2 -2
  49. package/dist/utils/merge-models-payload.d.ts +9 -0
  50. package/dist/utils/merge-models-payload.d.ts.map +1 -1
  51. 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) {
@@ -15077,25 +15092,30 @@ var WHITESPACE_COLLAPSE_PATTERN = /\s+/g;
15077
15092
  function compactionDebugEnabled() {
15078
15093
  return process.env["NODE_ENV"] === "development" || process.env["WRONGSTACK_DEBUG"] === "1";
15079
15094
  }
15095
+ var _debugLogger;
15096
+ function setCompactionDebugLogger(logger) {
15097
+ _debugLogger = logger;
15098
+ }
15080
15099
  function emitCompactionMetrics(event, metrics) {
15081
15100
  if (!compactionDebugEnabled()) return;
15082
- console.log(
15083
- JSON.stringify({
15084
- level: "debug",
15085
- event,
15086
- messageCount: metrics.messageCount,
15087
- preserveStart: metrics.preserveStart,
15088
- fastPathIterations: metrics.fastPathIterations,
15089
- fastPathInnerIterations: metrics.fastPathInnerIterations,
15090
- // Ratios — anything > 2.0 indicates the inner loop is running more than expected
15091
- fastPathInnerPerOuter: metrics.fastPathIterations > 0 ? metrics.fastPathInnerIterations / metrics.fastPathIterations : 0,
15092
- fullPassIterations: metrics.fullPassIterations,
15093
- fullPassInnerIterations: metrics.fullPassInnerIterations,
15094
- fullPassInnerPerOuter: metrics.fullPassIterations > 0 ? metrics.fullPassInnerIterations / metrics.fullPassIterations : 0,
15095
- tokensSaved: metrics.tokensSaved,
15096
- changed: metrics.changed
15097
- })
15098
- );
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
+ }
15099
15119
  }
15100
15120
  var estimateMessages = estimateMessageTokens;
15101
15121
  function hasTextContent(m) {
@@ -15127,18 +15147,20 @@ function findPreserveStart(messages, preserveK) {
15127
15147
  preserveStart--;
15128
15148
  }
15129
15149
  if (compactionDebugEnabled()) {
15130
- console.log(
15131
- JSON.stringify({
15132
- level: "debug",
15133
- event: "compaction.find_preserve_start.ended",
15134
- messageCount: messages.length,
15135
- preserveK,
15136
- preserveStart,
15137
- pairRepairIterations,
15138
- pairRepairInnerIterations,
15139
- pairRepairInnerPerOuter: pairRepairIterations > 0 ? pairRepairInnerIterations / pairRepairIterations : 0
15140
- })
15141
- );
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
+ }
15142
15164
  }
15143
15165
  return preserveStart;
15144
15166
  }
@@ -15253,16 +15275,18 @@ function eliseOldToolResults(messages, opts) {
15253
15275
  if (compactionDebugEnabled()) {
15254
15276
  const ratio = fullPassInnerIterations / fullPassIterations;
15255
15277
  if (ratio > 10) {
15256
- console.error(
15257
- JSON.stringify({
15258
- level: "error",
15259
- event: "compaction.elision.regression",
15260
- message: `fullPassInnerPerOuter=${ratio.toFixed(2)} exceeds threshold 10 \u2014 possible O(n\xB7m) regression`,
15261
- messageCount: messages.length,
15262
- fullPassIterations,
15263
- fullPassInnerIterations
15264
- })
15265
- );
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
+ }
15266
15290
  }
15267
15291
  }
15268
15292
  }
@@ -16287,6 +16311,207 @@ function resolveContextWindowPolicy(config = {}, overrideMode) {
16287
16311
  };
16288
16312
  }
16289
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
+
16290
16515
  // src/types/default-config.ts
16291
16516
  var DEFAULT_TOOLS_CONFIG = Object.freeze({
16292
16517
  defaultExecutionStrategy: "smart",
@@ -16332,10 +16557,13 @@ var HybridCompactor = class {
16332
16557
  preserveK;
16333
16558
  eliseThreshold;
16334
16559
  smart;
16560
+ logger;
16335
16561
  constructor(opts = {}) {
16336
16562
  this.preserveK = opts.preserveK ?? 5;
16337
16563
  this.eliseThreshold = opts.eliseThreshold ?? 2e3;
16338
16564
  this.smart = opts.smart ?? false;
16565
+ this.logger = opts.logger ?? noOpLogger;
16566
+ setCompactionDebugLogger(this.logger);
16339
16567
  }
16340
16568
  async compact(ctx, opts = {}) {
16341
16569
  const beforeTokens = estimateMessages(ctx.messages);
@@ -16673,7 +16901,7 @@ var AutonomousRunner = class {
16673
16901
  };
16674
16902
 
16675
16903
  // src/storage/goal-store.ts
16676
- import * as fsp10 from "node:fs/promises";
16904
+ import * as fsp11 from "node:fs/promises";
16677
16905
  var MAX_JOURNAL_ENTRIES = 500;
16678
16906
  function goalFilePath(projectRoot) {
16679
16907
  return resolveWstackPaths({ projectRoot }).projectGoal;
@@ -16682,7 +16910,7 @@ async function loadGoal(filePath, events, warn) {
16682
16910
  const t0 = Date.now();
16683
16911
  let raw;
16684
16912
  try {
16685
- raw = await fsp10.readFile(filePath, "utf8");
16913
+ raw = await fsp11.readFile(filePath, "utf8");
16686
16914
  } catch (err) {
16687
16915
  const code = err.code;
16688
16916
  if (code === "ENOENT") {
@@ -16871,7 +17099,7 @@ function isDesignStack(v) {
16871
17099
  // src/execution/design-kit-loader.ts
16872
17100
  import { existsSync } from "node:fs";
16873
17101
  import * as fs4 from "node:fs/promises";
16874
- import * as path14 from "node:path";
17102
+ import * as path15 from "node:path";
16875
17103
  import { fileURLToPath as fileURLToPath3 } from "node:url";
16876
17104
  var KIT_FILE = "KIT.md";
16877
17105
  var TOKENS_FILE = "tokens.json";
@@ -16984,7 +17212,7 @@ var DefaultDesignKitLoader = class {
16984
17212
  }
16985
17213
  for (const e of entries) {
16986
17214
  if (!e.isDirectory()) continue;
16987
- const kitFile = path14.join(dir, e.name, KIT_FILE);
17215
+ const kitFile = path15.join(dir, e.name, KIT_FILE);
16988
17216
  try {
16989
17217
  const raw = await fs4.readFile(kitFile, "utf8");
16990
17218
  const fm = parseKitFrontmatter(raw);
@@ -17058,7 +17286,7 @@ var DefaultDesignKitLoader = class {
17058
17286
  const m = await this.find(id);
17059
17287
  let tokens;
17060
17288
  if (m) {
17061
- const tokensPath = path14.join(path14.dirname(m.path), TOKENS_FILE);
17289
+ const tokensPath = path15.join(path15.dirname(m.path), TOKENS_FILE);
17062
17290
  try {
17063
17291
  const raw = await fs4.readFile(tokensPath, "utf8");
17064
17292
  const parsed = JSON.parse(raw);
@@ -17085,12 +17313,12 @@ var DefaultDesignKitLoader = class {
17085
17313
  };
17086
17314
  function resolveBundledDesignKitsDir() {
17087
17315
  try {
17088
- const here = path14.dirname(fileURLToPath3(import.meta.url));
17316
+ const here = path15.dirname(fileURLToPath3(import.meta.url));
17089
17317
  const candidates = [
17090
- path14.join(here, "design-kits"),
17091
- path14.join(here, "..", "design-kits"),
17092
- path14.join(here, "..", "..", "design-kits"),
17093
- 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")
17094
17322
  ];
17095
17323
  for (const c of candidates) {
17096
17324
  if (existsSync(c)) return c;
@@ -17119,10 +17347,10 @@ function _resetDesignKitLoaderMemo() {
17119
17347
  // src/execution/design-project-store.ts
17120
17348
  import { existsSync as existsSync2 } from "node:fs";
17121
17349
  import * as fs5 from "node:fs/promises";
17122
- import * as path15 from "node:path";
17350
+ import * as path16 from "node:path";
17123
17351
  var DESIGN_DIR = ".design";
17124
17352
  function designProjectDir(projectRoot) {
17125
- return path15.join(projectRoot, DESIGN_DIR);
17353
+ return path16.join(projectRoot, DESIGN_DIR);
17126
17354
  }
17127
17355
  var RULE_FILES = ["rules.md", "RULES.md", "design.md"];
17128
17356
  var rulesCache = /* @__PURE__ */ new Map();
@@ -17131,7 +17359,7 @@ async function loadProjectDesignRules(projectRoot) {
17131
17359
  let rules;
17132
17360
  for (const name of RULE_FILES) {
17133
17361
  try {
17134
- const txt = await fs5.readFile(path15.join(designProjectDir(projectRoot), name), "utf8");
17362
+ const txt = await fs5.readFile(path16.join(designProjectDir(projectRoot), name), "utf8");
17135
17363
  if (txt.trim()) {
17136
17364
  rules = txt.trim();
17137
17365
  break;
@@ -17152,7 +17380,7 @@ function parseOverrides(value) {
17152
17380
  }
17153
17381
  async function loadActiveKit(projectRoot) {
17154
17382
  try {
17155
- 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");
17156
17384
  const parsed = JSON.parse(raw);
17157
17385
  if (parsed && typeof parsed.kit === "string") {
17158
17386
  return {
@@ -17186,7 +17414,7 @@ function applyTokenOverrides(tokens, overrides) {
17186
17414
  async function ensureDesignDir(projectRoot) {
17187
17415
  const dir = designProjectDir(projectRoot);
17188
17416
  await fs5.mkdir(dir, { recursive: true });
17189
- const gi = path15.join(dir, ".gitignore");
17417
+ const gi = path16.join(dir, ".gitignore");
17190
17418
  if (!existsSync2(gi)) {
17191
17419
  try {
17192
17420
  await fs5.writeFile(gi, "*\n");
@@ -17200,11 +17428,11 @@ async function recordKitChoice(projectRoot, kit, stack, source, isoTime, overrid
17200
17428
  const dir = await ensureDesignDir(projectRoot);
17201
17429
  const record = { kit, stack: stack ?? null };
17202
17430
  if (overrides && Object.keys(overrides).length > 0) record.overrides = overrides;
17203
- 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)}
17204
17432
  `);
17205
17433
  const line = `- ${isoTime} \xB7 kit=${kit}${stack ? ` stack=${stack}` : ""} \xB7 via=${source}
17206
17434
  `;
17207
- await fs5.appendFile(path15.join(dir, "decisions.md"), line);
17435
+ await fs5.appendFile(path16.join(dir, "decisions.md"), line);
17208
17436
  } catch {
17209
17437
  }
17210
17438
  }
@@ -17220,11 +17448,11 @@ async function recordOverrides(projectRoot, patch, isoTime) {
17220
17448
  const dir = await ensureDesignDir(projectRoot);
17221
17449
  const record = { kit: active.kit, stack: active.stack ?? null };
17222
17450
  if (Object.keys(merged).length > 0) record.overrides = merged;
17223
- 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)}
17224
17452
  `);
17225
17453
  const keys = Object.keys(patch).join(",");
17226
17454
  await fs5.appendFile(
17227
- path15.join(dir, "decisions.md"),
17455
+ path16.join(dir, "decisions.md"),
17228
17456
  `- ${isoTime} \xB7 kit=${active.kit} \xB7 override=${keys} \xB7 via=set
17229
17457
  `
17230
17458
  );
@@ -17234,7 +17462,7 @@ async function recordOverrides(projectRoot, patch, isoTime) {
17234
17462
  }
17235
17463
  async function clearPersistedActiveKit(projectRoot) {
17236
17464
  try {
17237
- await fs5.rm(path15.join(designProjectDir(projectRoot), "active.json"), { force: true });
17465
+ await fs5.rm(path16.join(designProjectDir(projectRoot), "active.json"), { force: true });
17238
17466
  } catch {
17239
17467
  }
17240
17468
  }
@@ -19018,6 +19246,7 @@ var IntelligentCompactor = class {
19018
19246
  summarizerPrompt;
19019
19247
  summarizerModel;
19020
19248
  oneShotOrchestrator;
19249
+ logger;
19021
19250
  constructor(opts) {
19022
19251
  this.provider = opts.provider;
19023
19252
  this.warnThreshold = opts.warnThreshold ?? 0.5;
@@ -19029,6 +19258,8 @@ var IntelligentCompactor = class {
19029
19258
  this.summarizerPrompt = opts.summarizerPrompt ?? readBundledInstructionText("llm/intelligent-compactor-summarizer.md");
19030
19259
  this.summarizerModel = opts.summarizerModel;
19031
19260
  this.oneShotOrchestrator = opts.oneShotOrchestrator;
19261
+ this.logger = opts.logger ?? noOpLogger;
19262
+ setCompactionDebugLogger(this.logger);
19032
19263
  }
19033
19264
  async compact(ctx, opts = {}) {
19034
19265
  const beforeTokens = estimateMessages(ctx.messages);
@@ -20422,11 +20653,13 @@ var LLMSelector = class {
20422
20653
  systemPrompt;
20423
20654
  maxOutputTokens;
20424
20655
  oneShotOrchestrator;
20656
+ logger;
20425
20657
  constructor(opts) {
20426
20658
  this.provider = opts.provider;
20427
20659
  this.model = opts.model ?? "unknown";
20660
+ this.logger = opts.logger ?? noOpLogger;
20428
20661
  if (this.model === "unknown" && (process.env["NODE_ENV"] === "development" || process.env["WRONGSTACK_DEBUG"] === "1")) {
20429
- console.warn(
20662
+ this.logger.warn(
20430
20663
  "[LLMSelector] model not set \u2014 selector will use the provider default. Set `model` explicitly in LLMSelectorOptions to silence this warning."
20431
20664
  );
20432
20665
  }
@@ -20476,14 +20709,9 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
20476
20709
  }
20477
20710
  } catch (err) {
20478
20711
  if (err instanceof Error) {
20479
- console.warn(
20480
- JSON.stringify({
20481
- level: "warn",
20482
- event: "llm_selector.call_failed",
20483
- message: `selector call failed, using recency fallback: ${err.message}`,
20484
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
20485
- })
20486
- );
20712
+ this.logger.warn(`selector call failed, using recency fallback: ${err.message}`, {
20713
+ event: "llm_selector.call_failed"
20714
+ });
20487
20715
  }
20488
20716
  return this.fallbackSelect(messages, effectiveBudget);
20489
20717
  } finally {
@@ -20590,6 +20818,7 @@ var SelectiveCompactor = class {
20590
20818
  eliseThreshold;
20591
20819
  summarizerModel;
20592
20820
  summarizerPrompt;
20821
+ logger;
20593
20822
  constructor(opts) {
20594
20823
  this.provider = opts.provider;
20595
20824
  this.selector = opts.selector ?? new LLMSelector({ provider: opts.provider, model: opts.selectorModel, maxOutputTokens: opts.selectorMaxOutputTokens });
@@ -20600,8 +20829,10 @@ var SelectiveCompactor = class {
20600
20829
  this.preserveK = opts.preserveK ?? 4;
20601
20830
  this.eliseThreshold = opts.eliseThreshold ?? 300;
20602
20831
  this.summarizerModel = opts.summarizerModel ?? opts.selectorModel;
20832
+ this.logger = opts.logger ?? noOpLogger;
20833
+ setCompactionDebugLogger(this.logger);
20603
20834
  if (this.summarizerModel === void 0 && (process.env["NODE_ENV"] === "development" || process.env["WRONGSTACK_DEBUG"] === "1")) {
20604
- console.warn(
20835
+ this.logger.warn(
20605
20836
  "[SelectiveCompactor] summarizerModel not set \u2014 will fall back to ctx.model at summarize time. Set `summarizerModel` explicitly to silence this warning."
20606
20837
  );
20607
20838
  }
@@ -20830,7 +21061,7 @@ Summarize the following message range:`;
20830
21061
 
20831
21062
  // src/execution/skill-loader.ts
20832
21063
  import * as fs6 from "node:fs/promises";
20833
- import * as path16 from "node:path";
21064
+ import * as path17 from "node:path";
20834
21065
 
20835
21066
  // src/skills/foreign-sources.ts
20836
21067
  var FOREIGN_SKILL_TOOLS = [
@@ -20980,7 +21211,7 @@ async function entryIsDirectory(dir, entry) {
20980
21211
  if (entry.isDirectory()) return true;
20981
21212
  if (entry.isSymbolicLink()) {
20982
21213
  try {
20983
- return (await fs6.stat(path16.join(dir, entry.name))).isDirectory();
21214
+ return (await fs6.stat(path17.join(dir, entry.name))).isDirectory();
20984
21215
  } catch {
20985
21216
  return false;
20986
21217
  }
@@ -21001,7 +21232,7 @@ var DefaultSkillLoader = class {
21001
21232
  for (const tool of FOREIGN_SKILL_TOOLS) {
21002
21233
  if (!foreignIds.includes(tool.id)) continue;
21003
21234
  dirs.push({
21004
- dir: path16.join(root, "." + tool.id, tool.subdir),
21235
+ dir: path17.join(root, "." + tool.id, tool.subdir),
21005
21236
  source: "foreign",
21006
21237
  originTool: tool.id
21007
21238
  });
@@ -21028,7 +21259,7 @@ var DefaultSkillLoader = class {
21028
21259
  );
21029
21260
  for (const e of entries) {
21030
21261
  if (!await entryIsDirectory(dir, e)) continue;
21031
- const skillFile = path16.join(dir, e.name, "SKILL.md");
21262
+ const skillFile = path17.join(dir, e.name, "SKILL.md");
21032
21263
  try {
21033
21264
  const raw = await fs6.readFile(skillFile, "utf8");
21034
21265
  const fm = parseSkillFrontmatter(raw);
@@ -21112,7 +21343,7 @@ var DefaultSkillLoader = class {
21112
21343
  if (cached !== void 0) return cached;
21113
21344
  const m = await this.find(name);
21114
21345
  if (!m) throw new Error(`Skill "${name}" not found`);
21115
- const savePath = path16.join(path16.dirname(m.path), "SKILL.save.md");
21346
+ const savePath = path17.join(path17.dirname(m.path), "SKILL.save.md");
21116
21347
  let result;
21117
21348
  try {
21118
21349
  result = await fs6.readFile(savePath, "utf8");
@@ -21146,11 +21377,11 @@ function parseDescriptionFromText(desc) {
21146
21377
 
21147
21378
  // src/execution/prompt-loader.ts
21148
21379
  import * as fs8 from "node:fs/promises";
21149
- import * as path18 from "node:path";
21380
+ import * as path19 from "node:path";
21150
21381
 
21151
21382
  // src/storage/prompt-store.ts
21152
21383
  import * as fs7 from "node:fs/promises";
21153
- import * as path17 from "node:path";
21384
+ import * as path18 from "node:path";
21154
21385
  var SCHEMA_VERSION = 2;
21155
21386
  function migratePromptEntry(raw) {
21156
21387
  if (!raw || typeof raw !== "object") return null;
@@ -21216,7 +21447,7 @@ var DefaultPromptStore = class {
21216
21447
  if (!file.endsWith(".json")) continue;
21217
21448
  try {
21218
21449
  const raw = JSON.parse(
21219
- await fs7.readFile(path17.join(this.dir, file), "utf8")
21450
+ await fs7.readFile(path18.join(this.dir, file), "utf8")
21220
21451
  );
21221
21452
  const migrated = migratePromptEntry(raw.entry);
21222
21453
  if (migrated) entries.push(migrated);
@@ -21230,7 +21461,7 @@ var DefaultPromptStore = class {
21230
21461
  );
21231
21462
  }
21232
21463
  async get(id) {
21233
- const file = path17.join(this.dir, `${id}.json`);
21464
+ const file = path18.join(this.dir, `${id}.json`);
21234
21465
  try {
21235
21466
  const raw = JSON.parse(await fs7.readFile(file, "utf8"));
21236
21467
  return migratePromptEntry(raw.entry);
@@ -21240,12 +21471,12 @@ var DefaultPromptStore = class {
21240
21471
  }
21241
21472
  async save(entry) {
21242
21473
  await ensureDir(this.dir);
21243
- const file = path17.join(this.dir, `${entry.id}.json`);
21474
+ const file = path18.join(this.dir, `${entry.id}.json`);
21244
21475
  const raw = { version: SCHEMA_VERSION, entry };
21245
21476
  await atomicWrite(file, JSON.stringify(raw, null, 2));
21246
21477
  }
21247
21478
  async delete(id) {
21248
- const file = path17.join(this.dir, `${id}.json`);
21479
+ const file = path18.join(this.dir, `${id}.json`);
21249
21480
  try {
21250
21481
  await fs7.unlink(file);
21251
21482
  return true;
@@ -21334,7 +21565,7 @@ var DefaultPromptLoader = class {
21334
21565
  constructor(opts) {
21335
21566
  this.projectStore = typeof opts.paths.inProjectPrompts === "string" ? new DefaultPromptStore(opts.paths.inProjectPrompts) : void 0;
21336
21567
  this.userStore = typeof opts.paths.globalPrompts === "string" ? new DefaultPromptStore(opts.paths.globalPrompts) : void 0;
21337
- this.builtinDir = opts.bundledDir ? path18.join(opts.bundledDir, "prompts") : void 0;
21568
+ this.builtinDir = opts.bundledDir ? path19.join(opts.bundledDir, "prompts") : void 0;
21338
21569
  }
21339
21570
  async list() {
21340
21571
  if (this.cache) return this.cache;
@@ -21466,7 +21697,7 @@ async function walkJson(dir) {
21466
21697
  return out;
21467
21698
  }
21468
21699
  for (const e of entries) {
21469
- const full = path18.join(dir, e.name);
21700
+ const full = path19.join(dir, e.name);
21470
21701
  if (e.isDirectory()) {
21471
21702
  out.push(...await walkJson(full));
21472
21703
  } else if (e.name.endsWith(".json") && e.name !== "index.json" && e.name !== "schema.json") {
@@ -21665,11 +21896,11 @@ function runWithProcessTelemetry(context, run) {
21665
21896
  // src/execution/tool-executor.ts
21666
21897
  import { isDeepStrictEqual } from "node:util";
21667
21898
  import * as fs9 from "node:fs/promises";
21668
- import * as path20 from "node:path";
21899
+ import * as path21 from "node:path";
21669
21900
 
21670
21901
  // src/security/kanban-boundary.ts
21671
21902
  import { realpath as realpath3 } from "node:fs/promises";
21672
- import * as path19 from "node:path";
21903
+ import * as path20 from "node:path";
21673
21904
  import {
21674
21905
  evaluateKanbanBoundaryOpaque,
21675
21906
  evaluateKanbanBoundaryPath,
@@ -21744,10 +21975,10 @@ function resolveKanbanIdentity(ctx) {
21744
21975
  async function extractCandidatePaths(toolName, input, ctx) {
21745
21976
  if (toolName === "patch" && typeof input["patch"] === "string") {
21746
21977
  const directoryInput = stringValue(input["directory"]) ?? ctx.workingDir;
21747
- const directory = path19.isAbsolute(directoryInput) ? directoryInput : path19.resolve(ctx.workingDir, directoryInput);
21978
+ const directory = path20.isAbsolute(directoryInput) ? directoryInput : path20.resolve(ctx.workingDir, directoryInput);
21748
21979
  const strip = Math.max(1, numericValue(input["strip"]) ?? 1);
21749
21980
  const targets = extractPatchTargets(input["patch"], strip).map(
21750
- (target) => relativeToProject(path19.resolve(directory, target), ctx.projectRoot)
21981
+ (target) => relativeToProject(path20.resolve(directory, target), ctx.projectRoot)
21751
21982
  );
21752
21983
  return Promise.all(targets.map((target) => canonicalizeCandidatePath(target, ctx)));
21753
21984
  }
@@ -21757,7 +21988,7 @@ async function extractCandidatePaths(toolName, input, ctx) {
21757
21988
  collectPathValues(input, values, pathKeys);
21758
21989
  if (toolName === "scaffold" && typeof input["name"] === "string") {
21759
21990
  const cwd = stringValue(input["cwd"]) ?? ctx.workingDir;
21760
- values.push(path19.join(cwd, input["name"]));
21991
+ values.push(path20.join(cwd, input["name"]));
21761
21992
  }
21762
21993
  const candidates = [
21763
21994
  ...new Set(values.flatMap(splitPathList).map((value) => resolveInputPath(value, ctx)))
@@ -21765,21 +21996,21 @@ async function extractCandidatePaths(toolName, input, ctx) {
21765
21996
  return Promise.all(candidates.map((candidate) => canonicalizeCandidatePath(candidate, ctx)));
21766
21997
  }
21767
21998
  async function canonicalizeCandidatePath(candidate, ctx) {
21768
- if (path19.isAbsolute(candidate)) return candidate;
21769
- const absolute = path19.resolve(ctx.projectRoot, candidate);
21999
+ if (path20.isAbsolute(candidate)) return candidate;
22000
+ const absolute = path20.resolve(ctx.projectRoot, candidate);
21770
22001
  const canonicalRoot = await realpath3(ctx.projectRoot).catch(() => ctx.projectRoot);
21771
22002
  let probe = absolute;
21772
22003
  const missingSegments = [];
21773
22004
  while (true) {
21774
22005
  try {
21775
- const canonical = path19.join(await realpath3(probe), ...missingSegments);
22006
+ const canonical = path20.join(await realpath3(probe), ...missingSegments);
21776
22007
  return relativeToProject(canonical, canonicalRoot);
21777
22008
  } catch (cause) {
21778
22009
  const code = cause.code;
21779
22010
  if (code !== "ENOENT" && code !== "ENOTDIR") return absolute;
21780
- const parent = path19.dirname(probe);
22011
+ const parent = path20.dirname(probe);
21781
22012
  if (parent === probe) return absolute;
21782
- missingSegments.unshift(path19.basename(probe));
22013
+ missingSegments.unshift(path20.basename(probe));
21783
22014
  probe = parent;
21784
22015
  }
21785
22016
  }
@@ -21802,12 +22033,12 @@ function splitPathList(value) {
21802
22033
  return value.split(",").map((item) => item.trim()).filter(Boolean);
21803
22034
  }
21804
22035
  function resolveInputPath(value, ctx) {
21805
- const absolute = path19.isAbsolute(value) ? value : path19.resolve(ctx.workingDir, value);
22036
+ const absolute = path20.isAbsolute(value) ? value : path20.resolve(ctx.workingDir, value);
21806
22037
  return relativeToProject(absolute, ctx.projectRoot);
21807
22038
  }
21808
22039
  function relativeToProject(absolute, projectRoot) {
21809
- const relative6 = path19.relative(projectRoot, absolute).replace(/\\/g, "/");
21810
- 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 || ".";
21811
22042
  }
21812
22043
  function extractPatchTargets(patchText, strip) {
21813
22044
  const targets = [];
@@ -22151,7 +22382,7 @@ ${errorDetails}`,
22151
22382
  const inputPath = use.input && typeof use.input === "object" ? use.input.path : void 0;
22152
22383
  const caps = tool.capabilities ?? [];
22153
22384
  const hasFileCapability = caps.includes("fs.read") || caps.includes("fs.write");
22154
- 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;
22155
22386
  let writeTargetExisted;
22156
22387
  if (tool.name === "write" && caps.includes("fs.write") && absPath) {
22157
22388
  writeTargetExisted = await fs9.stat(absPath).then(
@@ -22743,11 +22974,11 @@ async function maybePersistLargeToolOutput(toolName, content, budget) {
22743
22974
  return content;
22744
22975
  }
22745
22976
  try {
22746
- const dir = path20.join(wstackGlobalRoot(), "tool-output");
22977
+ const dir = path21.join(wstackGlobalRoot(), "tool-output");
22747
22978
  await fs9.mkdir(dir, { recursive: true });
22748
22979
  const safeTool = toolName.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 40) || "tool";
22749
22980
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
22750
- const filePath = path20.join(dir, `${stamp}-${safeTool}-${randomUUID11()}.log`);
22981
+ const filePath = path21.join(dir, `${stamp}-${safeTool}-${randomUUID11()}.log`);
22751
22982
  await fs9.writeFile(filePath, content, "utf8");
22752
22983
  const marker = `[full tool output: ${bytes} bytes at ${filePath}; read/grep that file selectively instead of re-running or requesting more output]`;
22753
22984
  const fixedBytes = Buffer.byteLength(marker + TOOL_OUTPUT_ARTIFACT_OMISSION, "utf8");
@@ -23066,191 +23297,6 @@ function createContextManagerTool(opts = {}) {
23066
23297
  }
23067
23298
  var contextManagerTool = createContextManagerTool();
23068
23299
 
23069
- // src/infrastructure/logger.ts
23070
- import * as fsp11 from "node:fs/promises";
23071
- import * as path21 from "node:path";
23072
- var LEVEL_RANK2 = {
23073
- error: 0,
23074
- warn: 1,
23075
- info: 2,
23076
- debug: 3,
23077
- trace: 4
23078
- };
23079
- var COLORS = {
23080
- error: color.red,
23081
- warn: color.yellow,
23082
- info: color.cyan,
23083
- debug: color.gray,
23084
- trace: color.dim
23085
- };
23086
- var LOG_LEVELS = /* @__PURE__ */ new Set(["error", "warn", "info", "debug", "trace"]);
23087
- var LOG_FORMATS = /* @__PURE__ */ new Set(["pretty", "json"]);
23088
- var DefaultLogger = class _DefaultLogger {
23089
- /** How many file writes between rotation size checks (statSync is not free). */
23090
- static ROTATE_CHECK_EVERY = 100;
23091
- level;
23092
- file;
23093
- bindings;
23094
- format;
23095
- stderr;
23096
- maxFileBytes;
23097
- writesSinceRotateCheck = 0;
23098
- /**
23099
- * Serialized async tail for file writes. Every appendFile (and any
23100
- * chained rotation) is awaited through this promise so file I/O
23101
- * never overlaps itself — preserving the per-line ordering the
23102
- * sync version had, but without blocking the caller thread. Any
23103
- * rejection is swallowed (`catch(() => {})`) because logging must
23104
- * never crash the host.
23105
- *
23106
- * Children share the parent's tail: `child.tail === parent.tail`
23107
- * for the lifetime of the chain. Read/write access goes through
23108
- * `_tail` so that, when a child has been wired to a parent, both
23109
- * `enqueueRotate` and `log` always observe the parent's current tail
23110
- * rather than a stale snapshot taken at `child()` time.
23111
- */
23112
- tail = Promise.resolve();
23113
- parent = null;
23114
- /**
23115
- * Resolve the current tail. For the root logger this is the field;
23116
- * for a child logger we always read through the parent so that a
23117
- * child's appends land on the parent's most recent tail, and a
23118
- * parent's `flush()` waits for everything the child chained.
23119
- */
23120
- get _tail() {
23121
- return this.parent ? this.parent._tail : this.tail;
23122
- }
23123
- set _tail(next) {
23124
- if (this.parent) this.parent.tail = next;
23125
- else this.tail = next;
23126
- }
23127
- constructor(opts = {}) {
23128
- this.level = opts.level ?? parseLogLevel(process.env.WRONGSTACK_LOG_LEVEL);
23129
- this.file = opts.file;
23130
- this.bindings = opts.bindings ?? {};
23131
- this.format = opts.format ?? parseLogFormat(process.env.WRONGSTACK_LOG_FORMAT);
23132
- this.stderr = opts.stderr !== false;
23133
- this.maxFileBytes = opts.maxFileBytes ?? 10 * 1024 * 1024;
23134
- if (this.file) {
23135
- const dir = path21.dirname(this.file);
23136
- this._tail = this._tail.then(async () => {
23137
- await fsp11.mkdir(dir, { recursive: true });
23138
- }).catch(() => void 0);
23139
- }
23140
- }
23141
- error(msg, ctx) {
23142
- this.log("error", msg, ctx);
23143
- }
23144
- warn(msg, ctx) {
23145
- this.log("warn", msg, ctx);
23146
- }
23147
- info(msg, ctx) {
23148
- this.log("info", msg, ctx);
23149
- }
23150
- debug(msg, ctx) {
23151
- this.log("debug", msg, ctx);
23152
- }
23153
- trace(msg, ctx) {
23154
- this.log("trace", msg, ctx);
23155
- }
23156
- child(bindings) {
23157
- const child = Object.create(_DefaultLogger.prototype);
23158
- child.level = this.level;
23159
- child.file = this.file;
23160
- child.bindings = { ...this.bindings, ...bindings };
23161
- child.format = this.format;
23162
- child.stderr = this.stderr;
23163
- child.maxFileBytes = this.maxFileBytes;
23164
- child.parent = this;
23165
- child.writesSinceRotateCheck = this.writesSinceRotateCheck;
23166
- return child;
23167
- }
23168
- /**
23169
- * Wait until all queued file writes (and any pending rotation) have
23170
- * completed. `log()` is fire-and-forget by design — the caller never
23171
- * blocks on disk — so tests, shutdown handlers, and processes that
23172
- * need a deterministic "everything is on disk now" guarantee should
23173
- * `await logger.flush()` before reading the file or exiting.
23174
- */
23175
- flush() {
23176
- return this._tail;
23177
- }
23178
- /**
23179
- * Size-based rotation: when the file outgrows `maxFileBytes`, rename it to
23180
- * `<file>.1` (dropping the previous `.1`) so the live file restarts empty.
23181
- * Checked on the first write and every ROTATE_CHECK_EVERY writes after.
23182
- * Best-effort: a rename can fail on Windows while another process holds
23183
- * the file — the next check retries. Multiple processes appending to the
23184
- * same log all run this check; whoever crosses the threshold first wins.
23185
- *
23186
- * Async: the rotation runs on the file-write tail (so its writes don't
23187
- * interleave with the next append), and the caller never blocks on a
23188
- * statSync / renameSync syscall on the hot log path.
23189
- */
23190
- enqueueRotate(file) {
23191
- if (this.writesSinceRotateCheck++ % _DefaultLogger.ROTATE_CHECK_EVERY !== 0) return;
23192
- this._tail = this._tail.then(async () => {
23193
- let st;
23194
- try {
23195
- st = await fsp11.stat(file);
23196
- } catch {
23197
- return;
23198
- }
23199
- if (st.size < this.maxFileBytes) return;
23200
- try {
23201
- await fsp11.rm(`${file}.1`, { force: true });
23202
- await fsp11.rename(file, `${file}.1`);
23203
- } catch {
23204
- }
23205
- }).catch(() => void 0);
23206
- }
23207
- log(level, msg, ctx) {
23208
- const r = LEVEL_RANK2[level];
23209
- const allowed = LEVEL_RANK2[this.level];
23210
- if (r > allowed) return;
23211
- const ts = (/* @__PURE__ */ new Date()).toISOString();
23212
- const entry = { ts, level, msg, ...this.bindings };
23213
- if (ctx !== void 0) {
23214
- entry.ctx = ctx instanceof Error ? { message: ctx.message, stack: ctx.stack } : ctx;
23215
- }
23216
- if (this.file) {
23217
- this.enqueueRotate(this.file);
23218
- const line = `${JSON.stringify(entry)}
23219
- `;
23220
- this._tail = this._tail.then(() => fsp11.appendFile(this.file, line)).catch(() => void 0);
23221
- }
23222
- if (!this.stderr) return;
23223
- if (this.format === "json") {
23224
- writeErr(`${JSON.stringify(entry)}
23225
- `);
23226
- } else {
23227
- const head = `${color.dim(ts)} ${COLORS[level](level.toUpperCase().padEnd(5))} ${msg}`;
23228
- if (ctx !== void 0) {
23229
- writeErr(`${head} ${formatCtx(ctx)}
23230
- `);
23231
- } else {
23232
- writeErr(`${head}
23233
- `);
23234
- }
23235
- }
23236
- }
23237
- };
23238
- function parseLogLevel(raw) {
23239
- return raw && LOG_LEVELS.has(raw) ? raw : "info";
23240
- }
23241
- function parseLogFormat(raw) {
23242
- return raw && LOG_FORMATS.has(raw) ? raw : "pretty";
23243
- }
23244
- function formatCtx(ctx) {
23245
- if (ctx instanceof Error) return color.dim(ctx.message);
23246
- if (typeof ctx === "string") return color.dim(ctx);
23247
- try {
23248
- return color.dim(JSON.stringify(ctx));
23249
- } catch {
23250
- return color.dim(String(ctx));
23251
- }
23252
- }
23253
-
23254
23300
  // src/infrastructure/mcp-servers.ts
23255
23301
  var filesystemServer = () => ({
23256
23302
  name: "filesystem",
@@ -23829,6 +23875,7 @@ var DefaultModelsRegistry = class {
23829
23875
  overlayUrl;
23830
23876
  overlayFile;
23831
23877
  overlayCacheFile;
23878
+ logger;
23832
23879
  constructor(opts) {
23833
23880
  this.cacheFile = opts.cacheFile;
23834
23881
  this.url = opts.url ?? process.env[ENV_URL_KEY] ?? DEFAULT_URL;
@@ -23842,6 +23889,7 @@ var DefaultModelsRegistry = class {
23842
23889
  this.overlayUrl = opts.overlayUrl;
23843
23890
  this.overlayFile = opts.overlayFile;
23844
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;
23845
23893
  }
23846
23894
  async load(opts = {}) {
23847
23895
  if (this.payload && !opts.force) return this.payload;
@@ -23890,16 +23938,18 @@ var DefaultModelsRegistry = class {
23890
23938
  if (cached && this.isWithinMaxStaleAge(cached.fetchedAt)) {
23891
23939
  this.fetchedAt = new Date(cached.fetchedAt);
23892
23940
  const ageSeconds = Math.floor((Date.now() - this.fetchedAt.getTime()) / 1e3);
23893
- console.warn(
23894
- `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" }
23895
23944
  );
23896
23945
  return cached.payload;
23897
23946
  }
23898
23947
  if (overlayAvailable) {
23899
- console.warn(
23948
+ this.logger.warn(
23900
23949
  `ModelsRegistry: models.dev unavailable (${toErrorMessage(
23901
23950
  err
23902
- )}); serving curated overlay only.`
23951
+ )}); serving curated overlay only.`,
23952
+ { event: "models_registry.overlay_only_fallback" }
23903
23953
  );
23904
23954
  return {};
23905
23955
  }
@@ -23995,8 +24045,9 @@ var DefaultModelsRegistry = class {
23995
24045
  const cached = await this.readCacheAt(this.overlayCacheFile);
23996
24046
  if (cached && this.isWithinMaxStaleAge(cached.fetchedAt)) {
23997
24047
  const ageSeconds = Math.floor((Date.now() - new Date(cached.fetchedAt).getTime()) / 1e3);
23998
- console.warn(
23999
- `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 }
24000
24051
  );
24001
24052
  return cached.payload;
24002
24053
  }
@@ -25149,9 +25200,6 @@ var DefaultPermissionPolicy = class {
25149
25200
  loaded = false;
25150
25201
  trustFile;
25151
25202
  yolo;
25152
- yoloDestructive;
25153
- /** Deprecated compatibility flag; no longer gates YOLO calls. */
25154
- confirmDestructive;
25155
25203
  /**
25156
25204
  * Session-scoped "soft deny" map. When the user presses 'n' (block once),
25157
25205
  * the tool+pattern is added here. If the LLM retries in the same session,
@@ -25204,8 +25252,6 @@ var DefaultPermissionPolicy = class {
25204
25252
  constructor(opts) {
25205
25253
  this.trustFile = opts.trustFile;
25206
25254
  this.yolo = opts.yolo ?? false;
25207
- this.yoloDestructive = opts.yoloDestructive ?? opts.forceAllYolo ?? false;
25208
- this.confirmDestructive = opts.confirmDestructive ?? false;
25209
25255
  this.promptDelegate = opts.promptDelegate;
25210
25256
  }
25211
25257
  /**
@@ -25226,24 +25272,6 @@ var DefaultPermissionPolicy = class {
25226
25272
  getYolo() {
25227
25273
  return this.yolo;
25228
25274
  }
25229
- /** Toggle the destructive YOLO override at runtime. */
25230
- setYoloDestructive(enabled) {
25231
- if (this.yoloDestructive !== enabled) this._evalCache.clear();
25232
- this.yoloDestructive = enabled;
25233
- }
25234
- /** Check whether the destructive YOLO override is active. */
25235
- getYoloDestructive() {
25236
- return this.yoloDestructive;
25237
- }
25238
- /** Toggle deprecated destructive confirmation compatibility flag. */
25239
- setConfirmDestructive(enabled) {
25240
- if (this.confirmDestructive !== enabled) this._evalCache.clear();
25241
- this.confirmDestructive = enabled;
25242
- }
25243
- /** Check deprecated destructive confirmation compatibility flag. */
25244
- getConfirmDestructive() {
25245
- return this.confirmDestructive;
25246
- }
25247
25275
  /** Read-only diagnostics for policy inspector/editor surfaces. */
25248
25276
  getPolicyDiagnostics() {
25249
25277
  return this.policyDiagnostics.map((diagnostic) => ({ ...diagnostic }));
@@ -26544,9 +26572,10 @@ var BEHAVIOR_DEFAULTS = {
26544
26572
  prompts: true,
26545
26573
  // 'auto' → resolveTokenSavingTier picks a concrete tier from the model's
26546
26574
  // context window ONCE per session (cache-safe): lean prompt on small
26547
- // windows (<32k medium, <96k light) where the fixed identity+tool prose is
26548
- // a big fraction; full prompt (off) on 96k+ so nothing changes for the
26549
- // 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.
26550
26579
  tokenSavingMode: "auto",
26551
26580
  allowOutsideProjectRoot: true
26552
26581
  },
@@ -26614,7 +26643,7 @@ var BEHAVIOR_DEFAULTS = {
26614
26643
  // Mirrored from the top-level yolo default so the autonomy subsystem
26615
26644
  // (which reads autonomy.yolo) stays consistent with config.yolo.
26616
26645
  yolo: false,
26617
- streamFleet: true,
26646
+ fleetChatVerbosity: "off",
26618
26647
  chime: false,
26619
26648
  confirmExit: true,
26620
26649
  mouseMode: false,
@@ -26632,7 +26661,7 @@ var BEHAVIOR_DEFAULTS = {
26632
26661
  // silently omitted, and surfaced as a per-request warning. Users who
26633
26662
  // want a specific effort can opt in via `/settings` or the WebUI panel.
26634
26663
  reasoning: { mode: "auto" },
26635
- cache: {}
26664
+ cache: { ttl: "1h" }
26636
26665
  }
26637
26666
  };
26638
26667
  function isPlainRecord(value) {