@wrongstack/core 0.265.1 → 0.268.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 (75) hide show
  1. package/dist/{agent-bridge-DrkBxszZ.d.ts → agent-bridge-UhojbpWx.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-DM2pP-B6.d.ts → agent-subagent-runner-Bvtf1o9K.d.ts} +25 -7
  3. package/dist/{brain-BXd_61kQ.d.ts → brain-69wzMKp1.d.ts} +73 -1
  4. package/dist/{compactor-B8pOf45Y.d.ts → compactor-CBQAJoDc.d.ts} +19 -1
  5. package/dist/{config-BMCj_XDs.d.ts → config-VKfOZ-6X.d.ts} +122 -3
  6. package/dist/{context-MRk5PhNv.d.ts → context-C0U8B9NF.d.ts} +88 -1
  7. package/dist/coordination/index.d.ts +57 -161
  8. package/dist/coordination/index.js +471 -177
  9. package/dist/coordination/index.js.map +1 -1
  10. package/dist/defaults/index.d.ts +26 -25
  11. package/dist/defaults/index.js +1818 -844
  12. package/dist/defaults/index.js.map +1 -1
  13. package/dist/execution/index.d.ts +72 -16
  14. package/dist/execution/index.js +1270 -265
  15. package/dist/execution/index.js.map +1 -1
  16. package/dist/execution/prompt-enhancer.d.ts +1 -1
  17. package/dist/extension/index.d.ts +7 -6
  18. package/dist/global-mailbox-KByEFFBa.d.ts +663 -0
  19. package/dist/{goal-preamble-DvHDSKSe.d.ts → goal-preamble-CrYjmdw4.d.ts} +28 -11
  20. package/dist/{goal-store-DtLMySNb.d.ts → goal-store-Y_zdLZ3q.d.ts} +1 -1
  21. package/dist/hq/index.d.ts +195 -0
  22. package/dist/hq/index.js +1884 -0
  23. package/dist/hq/index.js.map +1 -0
  24. package/dist/index-BfaS-f_m.d.ts +82 -0
  25. package/dist/{index-B-ch8K9C.d.ts → index-CtQnmkaS.d.ts} +8 -8
  26. package/dist/{index-CEDeNodM.d.ts → index-gCv830d7.d.ts} +5 -5
  27. package/dist/index.d.ts +124 -47
  28. package/dist/index.js +5600 -2662
  29. package/dist/index.js.map +1 -1
  30. package/dist/infrastructure/index.d.ts +6 -6
  31. package/dist/infrastructure/index.js +117 -19
  32. package/dist/infrastructure/index.js.map +1 -1
  33. package/dist/kernel/index.d.ts +10 -9
  34. package/dist/kernel/index.js.map +1 -1
  35. package/dist/{pipeline-DPDxH_7m.d.ts → mailbox-types-Ct2hJq0P.d.ts} +1 -244
  36. package/dist/{mcp-servers-2x4w6Jn9.d.ts → mcp-servers-HT3Fi7Bl.d.ts} +10 -4
  37. package/dist/models/index.d.ts +5 -5
  38. package/dist/models/index.js +33 -3
  39. package/dist/models/index.js.map +1 -1
  40. package/dist/{models-registry-DmJlKuNp.d.ts → models-registry-Bvcl3Vaa.d.ts} +1 -1
  41. package/dist/{multi-agent-coordinator-DyCkCZnU.d.ts → multi-agent-coordinator-BACjsmkC.d.ts} +1 -1
  42. package/dist/{null-fleet-bus-CG9QY2aP.d.ts → null-fleet-bus-DA7fvhUg.d.ts} +14 -9
  43. package/dist/observability/index.d.ts +2 -2
  44. package/dist/{parallel-eternal-engine-Jw9uhEoT.d.ts → parallel-eternal-engine-Ci71gYu_.d.ts} +11 -15
  45. package/dist/{path-resolver-Dy2ej-gE.d.ts → path-resolver-O1IJnmKE.d.ts} +4 -3
  46. package/dist/{permission-B9SB45lp.d.ts → permission-Bd-57Lbl.d.ts} +1 -1
  47. package/dist/{permission-policy-CkjSXabK.d.ts → permission-policy-uNXC6Kge.d.ts} +2 -3
  48. package/dist/pipeline-BDNvENyV.d.ts +245 -0
  49. package/dist/{plan-templates-CzD9GnAU.d.ts → plan-templates-EMsalEtN.d.ts} +5 -5
  50. package/dist/{llm-selector-C0tfTCUe.d.ts → provider-model-resolve-CEb9x886.d.ts} +40 -3
  51. package/dist/{provider-runner-DMa70ODu.d.ts → provider-runner-DWJbpo70.d.ts} +3 -3
  52. package/dist/{retry-policy-CN0khdlj.d.ts → retry-policy-C3s_lvdK.d.ts} +1 -1
  53. package/dist/sdd/index.d.ts +9 -8
  54. package/dist/sdd/index.js +44 -14
  55. package/dist/sdd/index.js.map +1 -1
  56. package/dist/{secret-vault-B2yw84VT.d.ts → secret-vault-Cgduf5xL.d.ts} +2 -2
  57. package/dist/security/index.d.ts +5 -67
  58. package/dist/security/index.js +129 -99
  59. package/dist/security/index.js.map +1 -1
  60. package/dist/{selector-CzHh_igB.d.ts → selector-47LBnBVk.d.ts} +1 -1
  61. package/dist/{session-event-bridge-BUI6Jf-4.d.ts → session-event-bridge-Cw7oqmW2.d.ts} +1 -1
  62. package/dist/{session-reader-CMgdMSRP.d.ts → session-reader-DD4v2Obw.d.ts} +1 -1
  63. package/dist/storage/index.d.ts +14 -12
  64. package/dist/storage/index.js +144 -120
  65. package/dist/storage/index.js.map +1 -1
  66. package/dist/tools/index.d.ts +4 -2
  67. package/dist/tools/index.js +166 -31
  68. package/dist/tools/index.js.map +1 -1
  69. package/dist/types/index.d.ts +20 -19
  70. package/dist/types/index.js +1358 -476
  71. package/dist/types/index.js.map +1 -1
  72. package/dist/utils/index.d.ts +472 -405
  73. package/dist/utils/index.js +2321 -1193
  74. package/dist/utils/index.js.map +1 -1
  75. package/package.json +5 -1
@@ -1,8 +1,8 @@
1
1
  import { randomBytes, createCipheriv, createDecipheriv, randomUUID } from 'crypto';
2
2
  import * as fs from 'fs/promises';
3
- import * as path4 from 'path';
4
- import * as fs2 from 'fs';
3
+ import * as path6 from 'path';
5
4
  import * as os from 'os';
5
+ import * as fs2 from 'fs';
6
6
 
7
7
  // src/types/blocks.ts
8
8
  function isTextBlock(b) {
@@ -30,9 +30,9 @@ function asText(content) {
30
30
  return content.filter((b) => b.type === "text").map((b) => b.text).join("");
31
31
  }
32
32
  async function atomicWrite(targetPath, content, opts = {}) {
33
- const dir = path4.dirname(targetPath);
33
+ const dir = path6.dirname(targetPath);
34
34
  await fs.mkdir(dir, { recursive: true });
35
- const tmp = path4.join(dir, `.${path4.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
35
+ const tmp = path6.join(dir, `.${path6.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
36
36
  try {
37
37
  if (typeof content === "string") {
38
38
  await fs.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
@@ -85,17 +85,12 @@ async function renameWithRetry(from, to) {
85
85
  if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
86
86
  throw err;
87
87
  }
88
- await new Promise((resolve4) => setTimeout(resolve4, delays[i]));
88
+ await new Promise((resolve6) => setTimeout(resolve6, delays[i]));
89
89
  }
90
90
  }
91
91
  throw lastErr;
92
92
  }
93
93
 
94
- // src/utils/error.ts
95
- function toErrorMessage(err) {
96
- return err instanceof Error ? err.message : String(err);
97
- }
98
-
99
94
  // src/utils/term.ts
100
95
  var hasStdout = () => typeof process !== "undefined" && !!process.stdout;
101
96
  function isStdoutTTY() {
@@ -143,20 +138,75 @@ var color = {
143
138
  bgRed: wrap("41", "49"),
144
139
  bgGreen: wrap("42", "49")
145
140
  };
146
-
147
- // src/utils/string.ts
148
- function truncate(s, max) {
149
- return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
141
+ var MAX_DIGEST_CHARS = 4e3;
142
+ function createContextEvidenceState() {
143
+ return {
144
+ sessionGoals: [],
145
+ implicitFacts: [],
146
+ activeErrors: [],
147
+ toolCalls: [],
148
+ fileGraph: {},
149
+ repeatedReads: [],
150
+ updatedAt: Date.now()
151
+ };
150
152
  }
151
-
152
- // src/utils/expect-defined.ts
153
- function expectDefined(value, label) {
154
- if (value === null || value === void 0) {
155
- const err = new Error("Expected value to be defined");
156
- err.name = "ExpectDefinedError";
157
- throw err;
153
+ function buildContextEvidenceDigest(ctx) {
154
+ const state = ensureEvidence(ctx);
155
+ const lines = [];
156
+ if (state.currentIntent?.text) {
157
+ lines.push(`intent: ${state.currentIntent.text}`);
158
+ }
159
+ const goals = state.sessionGoals.slice(-3);
160
+ if (goals.length > 0) {
161
+ lines.push("session_goals:");
162
+ for (const goal of goals) lines.push(`- ${goal}`);
163
+ }
164
+ const activeErrors = state.activeErrors.slice(-5);
165
+ if (activeErrors.length > 0) {
166
+ lines.push("active_errors:");
167
+ for (const err of activeErrors) lines.push(`- ${err}`);
168
+ }
169
+ const files = Object.values(state.fileGraph).sort((a, b) => b.writes - a.writes || b.reads - a.reads || a.path.localeCompare(b.path)).slice(0, 12);
170
+ if (files.length > 0) {
171
+ lines.push("dependency_graph:");
172
+ for (const file of files) {
173
+ const actions = [
174
+ file.reads > 0 ? `read ${file.reads}x` : "",
175
+ file.writes > 0 ? `write ${file.writes}x` : ""
176
+ ].filter(Boolean).join(", ");
177
+ const refs = file.referenced ? "; referenced by assistant" : "";
178
+ const via = file.lastToolUseId ? `; last via ${file.lastToolUseId}` : "";
179
+ lines.push(`- ${file.path} (${actions || "seen"}${refs}${via})`);
180
+ }
158
181
  }
159
- return value;
182
+ const referenced = state.toolCalls.filter((tool) => tool.status === "referenced").slice(-10);
183
+ const recentSeen = state.toolCalls.filter((tool) => tool.status === "seen").slice(-5);
184
+ const trail = [...referenced, ...recentSeen];
185
+ if (trail.length > 0) {
186
+ lines.push("tool_trail:");
187
+ for (const tool of trail) {
188
+ const size = tool.outputTokens ? `; ~${tool.outputTokens} tokens` : "";
189
+ const filesText = tool.files.length > 0 ? `; files=${tool.files.slice(0, 4).join(", ")}` : "";
190
+ const symbolsText = tool.symbols.length > 0 ? `; symbols=${tool.symbols.slice(0, 4).join(", ")}` : "";
191
+ lines.push(
192
+ `- ${tool.toolUseId} ${tool.toolName} ${tool.status}: ${tool.summary}${filesText}${symbolsText}${size}`
193
+ );
194
+ }
195
+ }
196
+ const facts = state.implicitFacts.slice(-8);
197
+ if (facts.length > 0) {
198
+ lines.push("implicit_facts:");
199
+ for (const fact of facts) lines.push(`- ${fact}`);
200
+ }
201
+ const digest = lines.join("\n");
202
+ if (digest.length <= MAX_DIGEST_CHARS) return digest;
203
+ return `${digest.slice(0, MAX_DIGEST_CHARS)}... [+${digest.length - MAX_DIGEST_CHARS} chars]`;
204
+ }
205
+ function ensureEvidence(ctx) {
206
+ if (!ctx.contextEvidence) {
207
+ ctx.contextEvidence = createContextEvidenceState();
208
+ }
209
+ return ctx.contextEvidence;
160
210
  }
161
211
 
162
212
  // src/utils/deep-merge.ts
@@ -218,170 +268,179 @@ function deepMerge(base, patch, options = {}) {
218
268
  return out;
219
269
  }
220
270
 
221
- // src/utils/tool-output-serializer.ts
222
- function createToolOutputSerializer(opts = {}) {
223
- const capBytes = opts.perIterationOutputCapBytes ?? 1e5;
224
- function serialize(value) {
225
- if (typeof value === "string") return value;
226
- if (value === null || value === void 0) return "";
227
- if (typeof value === "object") {
228
- if (Array.isArray(value)) return value.map(serialize).join("\n");
229
- if ("text" in value) {
230
- const t = value.text;
231
- return typeof t === "string" ? t : JSON.stringify(value, null, 2);
232
- }
233
- try {
234
- return JSON.stringify(value, null, 2);
235
- } catch {
236
- return String(value);
237
- }
271
+ // src/utils/error.ts
272
+ function toErrorMessage(err) {
273
+ return err instanceof Error ? err.message : String(err);
274
+ }
275
+
276
+ // src/utils/expect-defined.ts
277
+ function expectDefined(value, label) {
278
+ if (value === null || value === void 0) {
279
+ const err = new Error("Expected value to be defined");
280
+ err.name = "ExpectDefinedError";
281
+ throw err;
282
+ }
283
+ return value;
284
+ }
285
+
286
+ // src/utils/json-schema-validate.ts
287
+ function validateAgainstSchema(value, schema) {
288
+ const errors = [];
289
+ walk(value, schema, "", errors);
290
+ return { ok: errors.length === 0, errors };
291
+ }
292
+ function walk(value, schema, path10, errors) {
293
+ if (schema.enum !== void 0) {
294
+ if (!schema.enum.some((e) => deepEqual(e, value))) {
295
+ errors.push({
296
+ path: path10 || "<root>",
297
+ message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
298
+ });
299
+ return;
238
300
  }
239
- return String(value);
240
301
  }
241
- function enforceCap(text, remainingBudget) {
242
- if (remainingBudget <= 0) {
243
- return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
302
+ if (typeof schema.type === "string") {
303
+ if (!checkType(value, schema.type)) {
304
+ errors.push({
305
+ path: path10 || "<root>",
306
+ message: `expected ${schema.type}, got ${describeType(value)}`
307
+ });
308
+ return;
244
309
  }
245
- const textBytes = Buffer.byteLength(text, "utf8");
246
- if (textBytes <= remainingBudget) {
247
- return { text, newBudget: remainingBudget - textBytes };
310
+ }
311
+ if (schema.type === "object" && isPlainObject(value)) {
312
+ const obj = value;
313
+ for (const req of schema.required ?? []) {
314
+ if (!(req in obj)) {
315
+ errors.push({ path: joinPath(path10, req), message: "required property missing" });
316
+ }
248
317
  }
249
- const marker = `
250
- \u2026[truncated ${textBytes - remainingBudget} bytes]\u2026
251
- `;
252
- const markerBytes = Buffer.byteLength(marker, "utf8");
253
- const available = remainingBudget - markerBytes;
254
- if (available <= 0) {
255
- return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
318
+ if (schema.properties) {
319
+ for (const [key, subSchema] of Object.entries(schema.properties)) {
320
+ if (key in obj) {
321
+ walk(obj[key], subSchema, joinPath(path10, key), errors);
322
+ }
323
+ }
256
324
  }
257
- const half = Math.floor(available / 2);
258
- const first = text.slice(0, half);
259
- const second = text.slice(text.length - half);
260
- return { text: `${first}${marker}${second}`, newBudget: 0 };
261
- }
262
- return { serialize, enforceCap, capBytes };
263
- }
264
-
265
- // src/utils/token-estimate.ts
266
- var RoughTokenEstimate = (text, charsPerToken = 3.5) => Math.max(1, Math.ceil(text.length / charsPerToken));
267
- var CALIBRATION_GLOBAL_KEY = "__global__";
268
- var _cals = /* @__PURE__ */ new Map();
269
- function calState(key) {
270
- let state = _cals.get(key);
271
- if (!state) {
272
- state = { ratio: 1, count: 0, prevEst: 0 };
273
- _cals.set(key, state);
274
325
  }
275
- return state;
276
- }
277
- var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
278
- var ESTIMATE_CACHE_MAX_SIZE = 1e4;
279
- function getCachedEstimate(key, compute) {
280
- const existing = ESTIMATE_CACHE.get(key);
281
- if (existing !== void 0) return existing;
282
- if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
283
- for (const k of ESTIMATE_CACHE.keys()) {
284
- if (ESTIMATE_CACHE.size <= Math.floor(ESTIMATE_CACHE_MAX_SIZE / 2)) break;
285
- ESTIMATE_CACHE.delete(k);
326
+ if (schema.type === "array" && Array.isArray(value) && schema.items) {
327
+ for (let i = 0; i < value.length; i++) {
328
+ walk(value[i], schema.items, `${path10}[${i}]`, errors);
286
329
  }
287
330
  }
288
- const estimate = compute(key);
289
- ESTIMATE_CACHE.set(key, estimate);
290
- return estimate;
291
331
  }
292
- function estimateToolInputTokens(input) {
293
- if (typeof input === "string") return RoughTokenEstimate(input);
294
- if (input === null || typeof input !== "object") {
295
- return RoughTokenEstimate(String(input));
332
+ function checkType(value, type) {
333
+ switch (type) {
334
+ case "string":
335
+ return typeof value === "string";
336
+ case "number":
337
+ return typeof value === "number" && !Number.isNaN(value);
338
+ case "integer":
339
+ return typeof value === "number" && Number.isInteger(value);
340
+ case "boolean":
341
+ return typeof value === "boolean";
342
+ case "null":
343
+ return value === null;
344
+ case "array":
345
+ return Array.isArray(value);
346
+ case "object":
347
+ return isPlainObject(value);
348
+ default:
349
+ return true;
296
350
  }
297
- return getCachedEstimate(JSON.stringify(input), (key) => RoughTokenEstimate(key));
298
351
  }
299
- function estimateToolResultTokens(content) {
300
- if (typeof content === "string") return RoughTokenEstimate(content);
301
- return getCachedEstimate(JSON.stringify(content), (key) => RoughTokenEstimate(key));
352
+ function isPlainObject(v) {
353
+ return typeof v === "object" && v !== null && !Array.isArray(v);
302
354
  }
303
- function estimateTextTokens(text) {
304
- return RoughTokenEstimate(text);
355
+ function describeType(v) {
356
+ if (v === null) return "null";
357
+ if (Array.isArray(v)) return "array";
358
+ return typeof v;
305
359
  }
306
- function computeMessageTokens(msg) {
307
- if (typeof msg.content === "string") return estimateTextTokens(msg.content);
308
- let total = 0;
309
- for (const b of msg.content) {
310
- if (b.type === "text") total += estimateTextTokens(b.text);
311
- else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
312
- else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
313
- else total += RoughTokenEstimate(JSON.stringify(b));
314
- }
315
- return total;
360
+ function joinPath(parent, key) {
361
+ if (!parent) return key;
362
+ return `${parent}.${key}`;
316
363
  }
317
- function estimateMessageTokens(messages) {
318
- let total = 0;
319
- for (const m of messages) {
320
- if (typeof m._estTokens === "number" && m._estTokens > 0) {
321
- total += m._estTokens;
322
- continue;
323
- }
324
- total += computeMessageTokens(m);
364
+ function deepEqual(a, b) {
365
+ if (a === b) return true;
366
+ if (typeof a !== typeof b) return false;
367
+ if (a === null || b === null) return a === b;
368
+ if (Array.isArray(a) && Array.isArray(b)) {
369
+ return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
325
370
  }
326
- return total;
327
- }
328
- function estimateToolDefTokens(tool) {
329
- const cached = tool._estDefTokens;
330
- if (typeof cached === "number" && cached > 0) return cached;
331
- return RoughTokenEstimate(tool.name) + RoughTokenEstimate(tool.description ?? "") + RoughTokenEstimate(JSON.stringify(tool.inputSchema));
371
+ if (typeof a === "object" && typeof b === "object") {
372
+ const ak = Object.keys(a);
373
+ const bk = Object.keys(b);
374
+ if (ak.length !== bk.length) return false;
375
+ return ak.every(
376
+ (k) => deepEqual(a[k], b[k])
377
+ );
378
+ }
379
+ return false;
332
380
  }
333
- function estimateRequestTokens(messages, systemPrompt, tools, calibrationKey = CALIBRATION_GLOBAL_KEY) {
334
- let messagesTokens = 0;
335
- if (typeof messages === "string") {
336
- messagesTokens = RoughTokenEstimate(messages);
337
- } else if (Array.isArray(messages)) {
338
- for (const m of messages) {
339
- if (typeof m === "object" && m !== null && "content" in m) {
340
- const cached = m._estTokens;
341
- if (typeof cached === "number" && cached > 0) {
342
- messagesTokens += cached;
343
- continue;
344
- }
345
- const content = m.content;
346
- if (typeof content === "string") {
347
- messagesTokens += RoughTokenEstimate(content);
348
- } else if (Array.isArray(content)) {
349
- for (const b of content) {
350
- if (typeof b === "object" && b !== null) {
351
- if (b.type === "text") {
352
- messagesTokens += RoughTokenEstimate(b.text);
353
- } else {
354
- messagesTokens += RoughTokenEstimate(JSON.stringify(b));
355
- }
356
- }
357
- }
358
- }
359
- }
360
- }
381
+
382
+ // src/utils/merge-models-payload.ts
383
+ function mergeModelsPayload(base, overlay) {
384
+ const out = {};
385
+ for (const [id, provider] of Object.entries(base)) {
386
+ out[id] = cloneProvider(provider);
361
387
  }
362
- let systemTokens = 0;
363
- if (typeof systemPrompt === "string") {
364
- systemTokens = RoughTokenEstimate(systemPrompt);
365
- } else if (Array.isArray(systemPrompt)) {
366
- for (const b of systemPrompt) {
367
- if (typeof b === "object" && b !== null && b.type === "text") {
368
- systemTokens += RoughTokenEstimate(b.text);
369
- }
370
- }
388
+ for (const [id, ovProvider] of Object.entries(overlay)) {
389
+ const existing = out[id];
390
+ out[id] = existing ? mergeProvider(existing, ovProvider) : cloneProvider(ovProvider);
371
391
  }
372
- let toolsTokens = 0;
373
- for (const t of tools) {
374
- toolsTokens += estimateToolDefTokens(t);
392
+ return out;
393
+ }
394
+ function mergeProvider(base, overlay) {
395
+ const models = {};
396
+ for (const [mid, m] of Object.entries(base.models ?? {})) {
397
+ models[mid] = { ...m };
398
+ }
399
+ for (const [mid, ovModel] of Object.entries(overlay.models ?? {})) {
400
+ const existing = models[mid];
401
+ models[mid] = existing ? mergeModel(existing, ovModel) : { ...ovModel };
375
402
  }
376
- const total = messagesTokens + systemTokens + toolsTokens;
377
- calState(calibrationKey).prevEst = total;
378
403
  return {
379
- messages: messagesTokens,
380
- systemPrompt: systemTokens,
381
- tools: toolsTokens,
382
- total
404
+ ...base,
405
+ // Overlay scalar fields win when explicitly provided; otherwise keep base.
406
+ ...stripUndefined({
407
+ id: overlay.id,
408
+ name: overlay.name,
409
+ npm: overlay.npm,
410
+ api: overlay.api,
411
+ env: overlay.env,
412
+ doc: overlay.doc
413
+ }),
414
+ models
383
415
  };
384
416
  }
417
+ function mergeModel(base, overlay) {
418
+ const merged = { ...base, ...overlay };
419
+ if (base.limit || overlay.limit) {
420
+ merged.limit = { ...base.limit, ...overlay.limit };
421
+ }
422
+ if (base.cost || overlay.cost) {
423
+ merged.cost = { ...base.cost, ...overlay.cost };
424
+ }
425
+ if (base.modalities || overlay.modalities) {
426
+ merged.modalities = { ...base.modalities, ...overlay.modalities };
427
+ }
428
+ return merged;
429
+ }
430
+ function cloneProvider(p) {
431
+ const models = {};
432
+ for (const [mid, m] of Object.entries(p.models ?? {})) {
433
+ models[mid] = { ...m };
434
+ }
435
+ return { ...p, models };
436
+ }
437
+ function stripUndefined(obj) {
438
+ const out = {};
439
+ for (const [k, v] of Object.entries(obj)) {
440
+ if (v !== void 0) out[k] = v;
441
+ }
442
+ return out;
443
+ }
385
444
 
386
445
  // src/utils/message-invariants.ts
387
446
  function repairToolUseAdjacency(messages) {
@@ -475,199 +534,918 @@ function isEmptyMessage(msg) {
475
534
  return msg.content.length === 0;
476
535
  }
477
536
 
478
- // src/utils/json-schema-validate.ts
479
- function validateAgainstSchema(value, schema) {
480
- const errors = [];
481
- walk(value, schema, "", errors);
482
- return { ok: errors.length === 0, errors };
537
+ // src/utils/regex-guard.ts
538
+ var MAX_PATTERN_LEN = 512;
539
+ var DANGEROUS_PATTERNS = [
540
+ /(\([^)]*[+*][^)]*\))[+*]/,
541
+ // (a+)+, (.*)+, etc
542
+ /(\(\?:[^)]*[+*][^)]*\))[+*]/
543
+ // same, with non-capturing group
544
+ ];
545
+ function compileUserRegex(pattern, flags) {
546
+ if (typeof pattern !== "string") {
547
+ return { ok: false, reason: "pattern must be a string" };
548
+ }
549
+ if (pattern.length === 0) {
550
+ return { ok: false, reason: "pattern is empty" };
551
+ }
552
+ if (pattern.length > MAX_PATTERN_LEN) {
553
+ return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
554
+ }
555
+ for (const rx of DANGEROUS_PATTERNS) {
556
+ if (rx.test(pattern)) {
557
+ return {
558
+ ok: false,
559
+ reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
560
+ };
561
+ }
562
+ }
563
+ try {
564
+ return { ok: true, regex: new RegExp(pattern, flags) };
565
+ } catch (err) {
566
+ return {
567
+ ok: false,
568
+ reason: err instanceof Error ? err.message : "invalid regex"
569
+ };
570
+ }
571
+ }
572
+
573
+ // src/utils/string.ts
574
+ function truncate(s, max) {
575
+ return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
576
+ }
577
+
578
+ // src/utils/tool-subject.ts
579
+ var GLOB_METACHARACTERS = /[*?[\]]/g;
580
+ function escapeGlobSubject(value) {
581
+ return value.replace(GLOB_METACHARACTERS, (char) => `\\${char}`);
582
+ }
583
+ function normalizePathSubject(value) {
584
+ return escapeGlobSubject(value.replace(/\\/g, "/"));
585
+ }
586
+ function isPathSubjectKey(subjectKey) {
587
+ return subjectKey === "path" || subjectKey === "file" || subjectKey === "files";
588
+ }
589
+ function subjectForToolInput(toolName, input, subjectKey) {
590
+ if (!input || typeof input !== "object") return void 0;
591
+ const obj = input;
592
+ if (subjectKey) {
593
+ const value = obj[subjectKey];
594
+ if (typeof value === "string") {
595
+ return isPathSubjectKey(subjectKey) ? normalizePathSubject(value) : escapeGlobSubject(value);
596
+ }
597
+ }
598
+ if (toolName === "bash" && typeof obj.command === "string") {
599
+ return escapeGlobSubject(obj.command);
600
+ }
601
+ if (typeof obj.path === "string") {
602
+ return normalizePathSubject(obj.path);
603
+ }
604
+ if (typeof obj.url === "string") {
605
+ return escapeGlobSubject(obj.url);
606
+ }
607
+ if (typeof obj.name === "string") {
608
+ return escapeGlobSubject(obj.name);
609
+ }
610
+ return void 0;
611
+ }
612
+
613
+ // src/utils/tool-wire-compact.ts
614
+ var TOOL_DESCRIPTION_MAX_CHARS = 640;
615
+ var SCHEMA_DESCRIPTION_MAX_CHARS = 180;
616
+ var compactCache = /* @__PURE__ */ new WeakMap();
617
+ function compactToolDefinitionForWire(tool, opts = {}) {
618
+ const useDefaultOptions = opts.descriptionMaxChars === void 0 && opts.schemaDescriptionMaxChars === void 0;
619
+ if (useDefaultOptions && typeof tool === "object" && tool !== null) {
620
+ const cached = compactCache.get(tool);
621
+ if (cached) return cached;
622
+ }
623
+ const compact = {
624
+ name: tool.name,
625
+ description: compactDescription(
626
+ tool.description ?? "",
627
+ opts.descriptionMaxChars ?? TOOL_DESCRIPTION_MAX_CHARS
628
+ ),
629
+ inputSchema: compactSchemaDescriptions(
630
+ tool.inputSchema,
631
+ opts.schemaDescriptionMaxChars ?? SCHEMA_DESCRIPTION_MAX_CHARS
632
+ )
633
+ };
634
+ if (useDefaultOptions && typeof tool === "object" && tool !== null) {
635
+ compactCache.set(tool, compact);
636
+ }
637
+ return compact;
638
+ }
639
+ function compactSchemaDescriptions(schema, maxDescriptionChars = SCHEMA_DESCRIPTION_MAX_CHARS) {
640
+ const compact = compactSchemaNode(schema, maxDescriptionChars);
641
+ return isRecord(compact) ? compact : { type: "object", properties: {} };
642
+ }
643
+ function compactSchemaNode(node, maxDescriptionChars) {
644
+ if (Array.isArray(node)) {
645
+ return node.map((item) => compactSchemaNode(item, maxDescriptionChars));
646
+ }
647
+ if (!isRecord(node)) return node;
648
+ const out = {};
649
+ for (const [key, value] of Object.entries(node)) {
650
+ if (key === "description" && typeof value === "string") {
651
+ out[key] = compactDescription(value, maxDescriptionChars);
652
+ } else {
653
+ out[key] = compactSchemaNode(value, maxDescriptionChars);
654
+ }
655
+ }
656
+ return out;
657
+ }
658
+ function compactDescription(text, maxChars) {
659
+ const normalized = text.replace(/\s+/g, " ").trim();
660
+ if (normalized.length <= maxChars) return normalized;
661
+ if (maxChars <= 20) return normalized.slice(0, maxChars);
662
+ const hardLimit = maxChars - 12;
663
+ const boundary = findSemanticBoundary(normalized, hardLimit);
664
+ const head = normalized.slice(0, boundary > 0 ? boundary : hardLimit).trimEnd();
665
+ return `${head} ...`;
666
+ }
667
+ function findSemanticBoundary(text, limit) {
668
+ const punctuation = Math.max(
669
+ text.lastIndexOf(". ", limit),
670
+ text.lastIndexOf("; ", limit),
671
+ text.lastIndexOf(": ", limit)
672
+ );
673
+ if (punctuation >= Math.floor(limit * 0.45)) return punctuation + 1;
674
+ const comma = text.lastIndexOf(", ", limit);
675
+ if (comma >= Math.floor(limit * 0.6)) return comma + 1;
676
+ const space = text.lastIndexOf(" ", limit);
677
+ return space >= Math.floor(limit * 0.6) ? space : limit;
678
+ }
679
+ function isRecord(value) {
680
+ return !!value && typeof value === "object" && !Array.isArray(value);
681
+ }
682
+
683
+ // src/utils/token-estimate.ts
684
+ var RoughTokenEstimate = (text, charsPerToken = 3.5) => Math.max(1, Math.ceil(text.length / charsPerToken));
685
+ var CALIBRATION_GLOBAL_KEY = "__global__";
686
+ var _cals = /* @__PURE__ */ new Map();
687
+ function calState(key) {
688
+ let state = _cals.get(key);
689
+ if (!state) {
690
+ state = { ratio: 1, count: 0, prevEst: 0 };
691
+ _cals.set(key, state);
692
+ }
693
+ return state;
694
+ }
695
+ var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
696
+ var ESTIMATE_CACHE_MAX_SIZE = 1e4;
697
+ function getCachedEstimate(key, compute) {
698
+ const existing = ESTIMATE_CACHE.get(key);
699
+ if (existing !== void 0) return existing;
700
+ if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
701
+ for (const k of ESTIMATE_CACHE.keys()) {
702
+ if (ESTIMATE_CACHE.size <= Math.floor(ESTIMATE_CACHE_MAX_SIZE / 2)) break;
703
+ ESTIMATE_CACHE.delete(k);
704
+ }
705
+ }
706
+ const estimate = compute(key);
707
+ ESTIMATE_CACHE.set(key, estimate);
708
+ return estimate;
709
+ }
710
+ function estimateToolInputTokens(input) {
711
+ if (typeof input === "string") return RoughTokenEstimate(input);
712
+ if (input === null || typeof input !== "object") {
713
+ return RoughTokenEstimate(String(input));
714
+ }
715
+ return getCachedEstimate(JSON.stringify(input), (key) => RoughTokenEstimate(key));
716
+ }
717
+ function estimateToolResultTokens(content) {
718
+ if (typeof content === "string") return RoughTokenEstimate(content);
719
+ return getCachedEstimate(JSON.stringify(content), (key) => RoughTokenEstimate(key));
720
+ }
721
+ function estimateTextTokens(text) {
722
+ return RoughTokenEstimate(text);
723
+ }
724
+ function computeMessageTokens(msg) {
725
+ if (typeof msg.content === "string") return estimateTextTokens(msg.content);
726
+ let total = 0;
727
+ for (const b of msg.content) {
728
+ if (b.type === "text") total += estimateTextTokens(b.text);
729
+ else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
730
+ else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
731
+ else total += RoughTokenEstimate(JSON.stringify(b));
732
+ }
733
+ return total;
734
+ }
735
+ function estimateMessageTokens(messages) {
736
+ let total = 0;
737
+ for (const m of messages) {
738
+ if (typeof m._estTokens === "number" && m._estTokens > 0) {
739
+ total += m._estTokens;
740
+ continue;
741
+ }
742
+ total += computeMessageTokens(m);
743
+ }
744
+ return total;
745
+ }
746
+ function estimateToolDefTokens(tool) {
747
+ const cached = tool._estDefTokens;
748
+ if (typeof cached === "number" && cached > 0) return cached;
749
+ const compact = compactToolDefinitionForWire(tool);
750
+ return RoughTokenEstimate(tool.name) + RoughTokenEstimate(compact.description) + RoughTokenEstimate(JSON.stringify(compact.inputSchema));
751
+ }
752
+ function estimateRequestTokens(messages, systemPrompt, tools, calibrationKey = CALIBRATION_GLOBAL_KEY) {
753
+ let messagesTokens = 0;
754
+ if (typeof messages === "string") {
755
+ messagesTokens = RoughTokenEstimate(messages);
756
+ } else if (Array.isArray(messages)) {
757
+ for (const m of messages) {
758
+ if (typeof m === "object" && m !== null && "content" in m) {
759
+ const cached = m._estTokens;
760
+ if (typeof cached === "number" && cached > 0) {
761
+ messagesTokens += cached;
762
+ continue;
763
+ }
764
+ const content = m.content;
765
+ if (typeof content === "string") {
766
+ messagesTokens += RoughTokenEstimate(content);
767
+ } else if (Array.isArray(content)) {
768
+ for (const b of content) {
769
+ if (typeof b === "object" && b !== null) {
770
+ if (b.type === "text") {
771
+ messagesTokens += RoughTokenEstimate(b.text);
772
+ } else {
773
+ messagesTokens += RoughTokenEstimate(JSON.stringify(b));
774
+ }
775
+ }
776
+ }
777
+ }
778
+ }
779
+ }
780
+ }
781
+ let systemTokens = 0;
782
+ if (typeof systemPrompt === "string") {
783
+ systemTokens = RoughTokenEstimate(systemPrompt);
784
+ } else if (Array.isArray(systemPrompt)) {
785
+ for (const b of systemPrompt) {
786
+ if (typeof b === "object" && b !== null && b.type === "text") {
787
+ systemTokens += RoughTokenEstimate(b.text);
788
+ }
789
+ }
790
+ }
791
+ let toolsTokens = 0;
792
+ for (const t of tools) {
793
+ toolsTokens += estimateToolDefTokens(t);
794
+ }
795
+ const total = messagesTokens + systemTokens + toolsTokens;
796
+ calState(calibrationKey).prevEst = total;
797
+ return {
798
+ messages: messagesTokens,
799
+ systemPrompt: systemTokens,
800
+ tools: toolsTokens,
801
+ total
802
+ };
803
+ }
804
+
805
+ // src/utils/tool-output-serializer.ts
806
+ var DEFAULT_LIST_LIMIT = 500;
807
+ var LOG_ENTRY_LIMIT = 200;
808
+ var INLINE_LIMIT = 240;
809
+ var GREP_FILE_LIMIT = 80;
810
+ var GREP_MATCHES_PER_FILE = 3;
811
+ var DIFF_INLINE_LINE_LIMIT = 260;
812
+ var DIFF_HUNK_LIMIT = 8;
813
+ var DIFF_HUNK_CONTEXT = 14;
814
+ function createToolOutputSerializer(opts = {}) {
815
+ const capBytes = opts.perIterationOutputCapBytes ?? 1e5;
816
+ function serialize(value, context = {}) {
817
+ if (typeof value === "string") return value;
818
+ if (value === null || value === void 0) return "";
819
+ if (typeof value === "object") {
820
+ if (Array.isArray(value)) return value.map((item) => serialize(item)).join("\n");
821
+ if (context.toolName) {
822
+ const compact = renderToolObject(context.toolName, value, context.input);
823
+ if (compact !== void 0) return compact;
824
+ return renderGenericToolObject(context.toolName, value);
825
+ }
826
+ if ("text" in value) {
827
+ const t = value.text;
828
+ return typeof t === "string" ? t : JSON.stringify(value, null, 2);
829
+ }
830
+ try {
831
+ return JSON.stringify(value, null, 2);
832
+ } catch {
833
+ return String(value);
834
+ }
835
+ }
836
+ return String(value);
837
+ }
838
+ function enforceCap(text, remainingBudget) {
839
+ if (remainingBudget <= 0) {
840
+ return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
841
+ }
842
+ const textBytes = Buffer.byteLength(text, "utf8");
843
+ if (textBytes <= remainingBudget) {
844
+ return { text, newBudget: remainingBudget - textBytes };
845
+ }
846
+ const marker = `
847
+ \u2026[truncated ${textBytes - remainingBudget} bytes]\u2026
848
+ `;
849
+ const markerBytes = Buffer.byteLength(marker, "utf8");
850
+ const available = remainingBudget - markerBytes;
851
+ if (available <= 0) {
852
+ return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
853
+ }
854
+ const half = Math.floor(available / 2);
855
+ const first = text.slice(0, half);
856
+ const second = text.slice(text.length - half);
857
+ return { text: `${first}${marker}${second}`, newBudget: 0 };
858
+ }
859
+ return { serialize, enforceCap, capBytes };
860
+ }
861
+ function renderToolObject(toolName, obj, input) {
862
+ if (toolName === "read" && typeof obj["text"] === "string") {
863
+ return joinSections([
864
+ renderHeader(
865
+ `read: ${stringFromInput(input, "path") ?? stringField(obj, "path") ?? "<unknown>"}`,
866
+ {
867
+ offset: numberFromInput(input, "offset"),
868
+ limit: numberFromInput(input, "limit"),
869
+ total_lines: obj["total_lines"],
870
+ encoding: obj["encoding"],
871
+ truncated: obj["truncated"],
872
+ cached: obj["cached"],
873
+ note: obj["note"]
874
+ }
875
+ ),
876
+ obj["text"]
877
+ ]);
878
+ }
879
+ if (toolName === "grep" && Array.isArray(obj["matches"])) {
880
+ const matches = stringArrayField(obj, "matches");
881
+ return joinSections([
882
+ renderHeader(`grep: ${stringFromInput(input, "pattern") ?? "<pattern>"}`, {
883
+ path: stringFromInput(input, "path"),
884
+ glob: stringFromInput(input, "glob"),
885
+ mode: stringFromInput(input, "output_mode"),
886
+ count: obj["count"],
887
+ shown: matches.length,
888
+ truncated: obj["truncated"],
889
+ used: obj["used"]
890
+ }),
891
+ renderGrepMatches(matches, stringFromInput(input, "output_mode"))
892
+ ]);
893
+ }
894
+ if (toolName === "patch" && Array.isArray(obj["files"])) {
895
+ const files = stringArrayField(obj, "files");
896
+ return joinSections([
897
+ renderHeader("patch", {
898
+ applied: obj["applied"],
899
+ rejected: obj["rejected"],
900
+ files: files.length,
901
+ dry_run: obj["dry_run"]
902
+ }),
903
+ typeof obj["message"] === "string" ? `message:
904
+ ${obj["message"]}` : void 0,
905
+ files.length > 0 ? `files:
906
+ ${renderStringList(files)}` : void 0
907
+ ]);
908
+ }
909
+ if (toolName === "glob" && Array.isArray(obj["files"])) {
910
+ const files = stringArrayField(obj, "files");
911
+ return joinSections([
912
+ renderHeader(
913
+ `${toolName}: ${stringFromInput(input, "pattern") ?? stringFromInput(input, "files") ?? stringFromInput(input, "path") ?? ""}`.trim(),
914
+ {
915
+ path: stringFromInput(input, "path"),
916
+ files: files.length,
917
+ truncated: obj["truncated"]
918
+ }
919
+ ),
920
+ renderStringList(files, "(no files)")
921
+ ]);
922
+ }
923
+ if (toolName === "tree" && typeof obj["tree"] === "string") {
924
+ return joinSections([
925
+ renderHeader(
926
+ `tree: ${stringField(obj, "path") ?? stringFromInput(input, "path") ?? "<cwd>"}`,
927
+ {
928
+ total_files: obj["total_files"],
929
+ total_dirs: obj["total_dirs"],
930
+ truncated: obj["truncated"]
931
+ }
932
+ ),
933
+ obj["tree"]
934
+ ]);
935
+ }
936
+ if (toolName === "fetch" && typeof obj["content"] === "string") {
937
+ return joinSections([
938
+ renderHeader(
939
+ `fetch: ${stringField(obj, "url") ?? stringFromInput(input, "url") ?? "<url>"}`,
940
+ {
941
+ status: obj["status"],
942
+ content_type: obj["content_type"]
943
+ }
944
+ ),
945
+ obj["content"]
946
+ ]);
947
+ }
948
+ if (toolName === "replace" && Array.isArray(obj["results"])) {
949
+ const results = obj["results"].filter(isRecord2);
950
+ const sections = [
951
+ renderHeader("replace", {
952
+ files_modified: obj["files_modified"],
953
+ total_replacements: obj["total_replacements"],
954
+ dry_run: obj["dry_run"]
955
+ })
956
+ ];
957
+ for (const r of results.slice(0, DEFAULT_LIST_LIMIT)) {
958
+ sections.push(
959
+ joinSections([
960
+ renderHeader(`file: ${stringField(r, "path") ?? "<unknown>"}`, {
961
+ replacements: r["replacements"]
962
+ }),
963
+ typeof r["diff"] === "string" ? r["diff"] : void 0
964
+ ])
965
+ );
966
+ }
967
+ if (results.length > DEFAULT_LIST_LIMIT) {
968
+ sections.push(`[serializer omitted ${results.length - DEFAULT_LIST_LIMIT} result item(s)]`);
969
+ }
970
+ return joinSections(sections);
971
+ }
972
+ if (typeof obj["diff"] === "string") {
973
+ const diff = obj["diff"];
974
+ return joinSections([
975
+ renderHeader(toolName, {
976
+ path: obj["path"],
977
+ replacements: obj["replacements"],
978
+ bytes_written: obj["bytes_written"],
979
+ created: obj["created"],
980
+ note: obj["note"],
981
+ files: Array.isArray(obj["files"]) ? obj["files"].length : void 0,
982
+ truncated: obj["truncated"],
983
+ mode: obj["mode"]
984
+ }),
985
+ compactDiff(diff)
986
+ ]);
987
+ }
988
+ if (toolName === "test" && typeof obj["output"] === "string") {
989
+ return renderTestOutput(obj, input);
990
+ }
991
+ if ((toolName === "typecheck" || toolName === "lint" || toolName === "format") && typeof obj["output"] === "string") {
992
+ return renderVerifierOutput(toolName, obj, input);
993
+ }
994
+ if (hasCommandOutputShape(obj)) {
995
+ return renderCommandOutput(toolName, obj, input);
996
+ }
997
+ if (toolName === "json" && typeof obj["formatted"] === "string") {
998
+ return joinSections([
999
+ renderHeader("json", {
1000
+ type: obj["type"],
1001
+ keys: Array.isArray(obj["keys"]) ? obj["keys"].length : void 0,
1002
+ query: stringFromInput(input, "query"),
1003
+ error: obj["error"]
1004
+ }),
1005
+ obj["formatted"]
1006
+ ]);
1007
+ }
1008
+ if (toolName === "logs" && Array.isArray(obj["entries"])) {
1009
+ const entries = obj["entries"].filter(isRecord2);
1010
+ const lines = entries.slice(0, LOG_ENTRY_LIMIT).map((entry) => {
1011
+ const ts = stringField(entry, "timestamp") ?? "";
1012
+ const level = stringField(entry, "level") ?? "info";
1013
+ const message = stringField(entry, "message") ?? "";
1014
+ const source = stringField(entry, "source");
1015
+ return [ts, level, source, message].filter(Boolean).join(" ");
1016
+ });
1017
+ if (entries.length > LOG_ENTRY_LIMIT) {
1018
+ lines.push(`[serializer omitted ${entries.length - LOG_ENTRY_LIMIT} log entry item(s)]`);
1019
+ }
1020
+ return joinSections([
1021
+ renderHeader(`logs: ${stringField(obj, "source") ?? "<source>"}`, {
1022
+ total: obj["total"],
1023
+ shown: Math.min(entries.length, LOG_ENTRY_LIMIT),
1024
+ truncated: obj["truncated"],
1025
+ stream_mode: obj["stream_mode"]
1026
+ }),
1027
+ lines.length > 0 ? lines.join("\n") : "(no log entries)"
1028
+ ]);
1029
+ }
1030
+ if (toolName === "audit" && Array.isArray(obj["vulnerabilities"])) {
1031
+ const vulns = obj["vulnerabilities"].filter(isRecord2);
1032
+ const lines = vulns.slice(0, DEFAULT_LIST_LIMIT).map((v) => {
1033
+ const severity = stringField(v, "severity") ?? "unknown";
1034
+ const pkg = stringField(v, "package") ?? "<package>";
1035
+ const title = stringField(v, "title") ?? "";
1036
+ const url = stringField(v, "url");
1037
+ return [severity, pkg, title, url].filter(Boolean).join(" | ");
1038
+ });
1039
+ if (vulns.length > DEFAULT_LIST_LIMIT) {
1040
+ lines.push(`[serializer omitted ${vulns.length - DEFAULT_LIST_LIMIT} vulnerability item(s)]`);
1041
+ }
1042
+ return joinSections([
1043
+ renderHeader("audit", {
1044
+ exit_code: obj["exit_code"],
1045
+ total: obj["total"],
1046
+ summary: obj["summary"],
1047
+ truncated: obj["truncated"]
1048
+ }),
1049
+ lines.length > 0 ? lines.join("\n") : stringField(obj, "output")
1050
+ ]);
1051
+ }
1052
+ if (toolName === "outdated" && Array.isArray(obj["packages"])) {
1053
+ const packages = obj["packages"].filter(isRecord2);
1054
+ const lines = packages.slice(0, DEFAULT_LIST_LIMIT).map(
1055
+ (p) => [
1056
+ stringField(p, "name") ?? "<package>",
1057
+ `current=${stringField(p, "current") ?? "unknown"}`,
1058
+ `wanted=${stringField(p, "wanted") ?? "unknown"}`,
1059
+ `latest=${stringField(p, "latest") ?? "unknown"}`,
1060
+ stringField(p, "type")
1061
+ ].filter(Boolean).join(" | ")
1062
+ );
1063
+ if (packages.length > DEFAULT_LIST_LIMIT) {
1064
+ lines.push(`[serializer omitted ${packages.length - DEFAULT_LIST_LIMIT} package item(s)]`);
1065
+ }
1066
+ return joinSections([
1067
+ renderHeader("outdated", {
1068
+ exit_code: obj["exit_code"],
1069
+ total: obj["total"],
1070
+ truncated: obj["truncated"]
1071
+ }),
1072
+ lines.length > 0 ? lines.join("\n") : stringField(obj, "output")
1073
+ ]);
1074
+ }
1075
+ return void 0;
1076
+ }
1077
+ function renderTestOutput(obj, input) {
1078
+ const exitCode = numberField(obj, "exit_code") ?? 0;
1079
+ const failed = numberField(obj, "failed") ?? 0;
1080
+ const output = stringField(obj, "output") ?? "";
1081
+ const header = renderHeader(`test: ${stringField(obj, "runner") ?? "runner"}`, {
1082
+ exit_code: obj["exit_code"],
1083
+ tests_run: obj["tests_run"],
1084
+ passed: obj["passed"],
1085
+ failed: obj["failed"],
1086
+ duration_ms: obj["duration_ms"],
1087
+ truncated: obj["truncated"],
1088
+ files: inputListSummary(input, "files"),
1089
+ grep: stringFromInput(input, "grep")
1090
+ });
1091
+ if (exitCode === 0 && failed === 0) {
1092
+ return joinSections([
1093
+ header,
1094
+ joinSections([
1095
+ "report:",
1096
+ `status=passed`,
1097
+ `tests_run=${obj["tests_run"] ?? 0}`,
1098
+ `passed=${obj["passed"] ?? 0}`,
1099
+ `failed=${obj["failed"] ?? 0}`,
1100
+ `duration_ms=${obj["duration_ms"] ?? 0}`,
1101
+ extractSpoolNote(output)
1102
+ ])
1103
+ ]);
1104
+ }
1105
+ return joinSections([
1106
+ header,
1107
+ `error_context:
1108
+ ${compactFailureOutput(output || "(no runner output)")}`
1109
+ ]);
1110
+ }
1111
+ function renderVerifierOutput(toolName, obj, input) {
1112
+ const exitCode = numberField(obj, "exit_code") ?? 0;
1113
+ const errors = numberField(obj, "errors") ?? 0;
1114
+ const warnings = numberField(obj, "warnings") ?? 0;
1115
+ const output = stringField(obj, "output") ?? "";
1116
+ const changed = numberField(obj, "files_changed") ?? 0;
1117
+ const header = renderHeader(toolName, {
1118
+ exit_code: obj["exit_code"],
1119
+ errors: obj["errors"],
1120
+ warnings: obj["warnings"],
1121
+ files_checked: obj["files_checked"],
1122
+ files_changed: obj["files_changed"],
1123
+ fix_applied: obj["fix_applied"],
1124
+ fixer: obj["fixer"],
1125
+ linter: obj["linter"],
1126
+ project: obj["project"],
1127
+ truncated: obj["truncated"],
1128
+ files: inputListSummary(input, "files"),
1129
+ cwd: stringFromInput(input, "cwd")
1130
+ });
1131
+ if (exitCode === 0 && errors === 0 && (toolName !== "format" || changed === 0)) {
1132
+ return joinSections([
1133
+ header,
1134
+ joinSections([
1135
+ "report:",
1136
+ "status=passed",
1137
+ `errors=${errors}`,
1138
+ `warnings=${warnings}`,
1139
+ toolName === "format" ? `files_changed=${changed}` : void 0,
1140
+ extractSpoolNote(output)
1141
+ ])
1142
+ ]);
1143
+ }
1144
+ if (exitCode === 0 && toolName === "format") {
1145
+ return joinSections([
1146
+ header,
1147
+ joinSections([
1148
+ "report:",
1149
+ "status=changed",
1150
+ `files_changed=${changed}`,
1151
+ extractSpoolNote(output)
1152
+ ])
1153
+ ]);
1154
+ }
1155
+ return joinSections([
1156
+ header,
1157
+ `error_context:
1158
+ ${compactFailureOutput(output || "(no verifier output)")}`
1159
+ ]);
1160
+ }
1161
+ function renderGrepMatches(matches, mode) {
1162
+ if (matches.length === 0) return "(no matches)";
1163
+ if (mode === "files_with_matches") return renderStringList(matches, "(no files)");
1164
+ if (mode === "count") return renderStringList(matches, "(no counts)");
1165
+ const groups = /* @__PURE__ */ new Map();
1166
+ const passthrough = [];
1167
+ for (const match of matches) {
1168
+ const parsed = parseGrepContentLine(match);
1169
+ if (!parsed) {
1170
+ passthrough.push(match);
1171
+ continue;
1172
+ }
1173
+ const list = groups.get(parsed.file) ?? [];
1174
+ list.push(`${parsed.line}:${parsed.text}`);
1175
+ groups.set(parsed.file, list);
1176
+ }
1177
+ if (groups.size === 0) return renderStringList(matches, "(no matches)");
1178
+ const sections = [];
1179
+ let fileIndex = 0;
1180
+ for (const [file, lines] of groups) {
1181
+ fileIndex++;
1182
+ if (fileIndex > GREP_FILE_LIMIT) break;
1183
+ const shown = lines.slice(0, GREP_MATCHES_PER_FILE);
1184
+ sections.push(
1185
+ `${file} (${lines.length} match(es), showing ${shown.length})
1186
+ ${shown.join("\n")}`
1187
+ );
1188
+ }
1189
+ if (groups.size > GREP_FILE_LIMIT) {
1190
+ sections.push(`[serializer omitted ${groups.size - GREP_FILE_LIMIT} file group(s)]`);
1191
+ }
1192
+ if (passthrough.length > 0) {
1193
+ sections.push(`ungrouped:
1194
+ ${renderStringList(passthrough, "", 50)}`);
1195
+ }
1196
+ return sections.join("\n");
1197
+ }
1198
+ function parseGrepContentLine(line) {
1199
+ const match = /^(.+?):(\d+):(.*)$/.exec(line);
1200
+ if (!match?.[1] || !match[2]) return void 0;
1201
+ return { file: match[1], line: match[2], text: match[3] ?? "" };
1202
+ }
1203
+ function compactDiff(diff) {
1204
+ const lines = diff.split(/\r?\n/);
1205
+ if (lines.length <= DIFF_INLINE_LINE_LIMIT) return diff;
1206
+ const fileCount = Math.max(
1207
+ new Set(
1208
+ lines.map(
1209
+ (line) => /^diff --git\s+a\/(.+?)\s+b\//.exec(line)?.[1] ?? /^---\s+(.+)/.exec(line)?.[1]
1210
+ ).filter(Boolean)
1211
+ ).size,
1212
+ 0
1213
+ );
1214
+ const hunks = lines.filter((line) => line.startsWith("@@")).length;
1215
+ const added = lines.filter((line) => line.startsWith("+") && !line.startsWith("+++")).length;
1216
+ const removed = lines.filter((line) => line.startsWith("-") && !line.startsWith("---")).length;
1217
+ const selected = /* @__PURE__ */ new Set();
1218
+ let hunkCount = 0;
1219
+ for (let i = 0; i < lines.length; i++) {
1220
+ const line = lines[i] ?? "";
1221
+ if (line.startsWith("diff --git") || line.startsWith("--- ") || line.startsWith("+++ ")) {
1222
+ selected.add(i);
1223
+ continue;
1224
+ }
1225
+ if (!line.startsWith("@@")) continue;
1226
+ if (hunkCount >= DIFF_HUNK_LIMIT) continue;
1227
+ hunkCount++;
1228
+ for (let j = i; j <= Math.min(lines.length - 1, i + DIFF_HUNK_CONTEXT); j++) {
1229
+ selected.add(j);
1230
+ }
1231
+ }
1232
+ if (selected.size === 0) {
1233
+ return joinSections([
1234
+ renderHeader("diff_summary", {
1235
+ files: fileCount,
1236
+ hunks,
1237
+ added,
1238
+ removed,
1239
+ lines: lines.length
1240
+ }),
1241
+ lines.slice(0, DIFF_INLINE_LINE_LIMIT).join("\n"),
1242
+ `[serializer omitted ${Math.max(0, lines.length - DIFF_INLINE_LINE_LIMIT)} diff line(s)]`
1243
+ ]);
1244
+ }
1245
+ const excerpt = [];
1246
+ let previous = -1;
1247
+ for (const index of [...selected].sort((a, b) => a - b)) {
1248
+ if (index > previous + 1) {
1249
+ const omitted = previous === -1 ? index : index - previous - 1;
1250
+ excerpt.push(`[serializer omitted ${omitted} diff line(s)]`);
1251
+ }
1252
+ excerpt.push(lines[index] ?? "");
1253
+ previous = index;
1254
+ }
1255
+ const trailing = lines.length - previous - 1;
1256
+ if (trailing > 0) excerpt.push(`[serializer omitted ${trailing} trailing diff line(s)]`);
1257
+ return joinSections([
1258
+ renderHeader("diff_summary", {
1259
+ files: fileCount,
1260
+ hunks,
1261
+ shown_hunks: Math.min(hunks, DIFF_HUNK_LIMIT),
1262
+ added,
1263
+ removed,
1264
+ lines: lines.length
1265
+ }),
1266
+ excerpt.join("\n")
1267
+ ]);
483
1268
  }
484
- function walk(value, schema, path7, errors) {
485
- if (schema.enum !== void 0) {
486
- if (!schema.enum.some((e) => deepEqual(e, value))) {
487
- errors.push({
488
- path: path7 || "<root>",
489
- message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
490
- });
491
- return;
1269
+ function compactFailureOutput(output) {
1270
+ const lines = output.split(/\r?\n/);
1271
+ if (lines.length <= 260) return output.trimEnd();
1272
+ const selected = /* @__PURE__ */ new Set();
1273
+ const marker = /\b(fail|failed|failure|error|exception|assertionerror|expected|received|actual|timeout|stack)\b/i;
1274
+ let markerHits = 0;
1275
+ for (let i = 0; i < lines.length; i++) {
1276
+ if (!marker.test(lines[i] ?? "")) continue;
1277
+ markerHits++;
1278
+ for (let j = Math.max(0, i - 4); j <= Math.min(lines.length - 1, i + 10); j++) {
1279
+ selected.add(j);
492
1280
  }
493
1281
  }
494
- if (typeof schema.type === "string") {
495
- if (!checkType(value, schema.type)) {
496
- errors.push({
497
- path: path7 || "<root>",
498
- message: `expected ${schema.type}, got ${describeType(value)}`
499
- });
500
- return;
1282
+ if (markerHits === 0) {
1283
+ return lines.slice(-220).join("\n").trimEnd();
1284
+ }
1285
+ const ordered = [...selected].sort((a, b) => a - b);
1286
+ const out = [];
1287
+ let previous = -1;
1288
+ for (const index of ordered) {
1289
+ if (index > previous + 1) {
1290
+ const omitted = previous === -1 ? index : index - previous - 1;
1291
+ out.push(`[serializer omitted ${omitted} line(s)]`);
501
1292
  }
1293
+ out.push(lines[index] ?? "");
1294
+ previous = index;
502
1295
  }
503
- if (schema.type === "object" && isPlainObject(value)) {
504
- const obj = value;
505
- for (const req of schema.required ?? []) {
506
- if (!(req in obj)) {
507
- errors.push({ path: joinPath(path7, req), message: "required property missing" });
1296
+ return out.join("\n").trimEnd();
1297
+ }
1298
+ function extractSpoolNote(output) {
1299
+ return output.split(/\r?\n/).find((line) => line.startsWith("[output truncated") && line.includes("full"));
1300
+ }
1301
+ function hasCommandOutputShape(obj) {
1302
+ return typeof obj["stdout"] === "string" || typeof obj["stderr"] === "string" || typeof obj["output"] === "string" || typeof obj["exitCode"] === "number" || typeof obj["exit_code"] === "number";
1303
+ }
1304
+ function renderCommandOutput(toolName, obj, input) {
1305
+ const command = stringField(obj, "command") ?? stringFromInput(input, "command");
1306
+ const args = stringArrayField(obj, "args");
1307
+ const commandLine = command ? [command, ...args].join(" ") : void 0;
1308
+ const output = stringField(obj, "output");
1309
+ const stdout = stringField(obj, "stdout");
1310
+ const stderr = stringField(obj, "stderr");
1311
+ return joinSections([
1312
+ renderHeader(commandLine ? `${toolName}: ${commandLine}` : toolName, {
1313
+ exit_code: obj["exit_code"] ?? obj["exitCode"],
1314
+ timed_out: obj["timed_out"],
1315
+ pid: obj["pid"],
1316
+ allowed: obj["allowed"],
1317
+ truncated: obj["truncated"],
1318
+ runner: obj["runner"],
1319
+ linter: obj["linter"],
1320
+ fixer: obj["fixer"],
1321
+ project: obj["project"],
1322
+ tests_run: obj["tests_run"],
1323
+ passed: obj["passed"],
1324
+ failed: obj["failed"],
1325
+ duration_ms: obj["duration_ms"],
1326
+ errors: obj["errors"],
1327
+ warnings: obj["warnings"],
1328
+ files_checked: obj["files_checked"],
1329
+ files_changed: obj["files_changed"],
1330
+ fix_applied: obj["fix_applied"]
1331
+ }),
1332
+ stringField(obj, "error") ? `error:
1333
+ ${stringField(obj, "error")}` : void 0,
1334
+ output ? `output:
1335
+ ${output}` : void 0,
1336
+ stdout ? `stdout:
1337
+ ${stdout}` : void 0,
1338
+ stderr ? `stderr:
1339
+ ${stderr}` : void 0
1340
+ ]);
1341
+ }
1342
+ function renderGenericToolObject(toolName, obj) {
1343
+ const scalars = {};
1344
+ const blocks = [];
1345
+ for (const [key, value] of Object.entries(obj)) {
1346
+ if (value === void 0) continue;
1347
+ if (isScalar(value)) {
1348
+ const inline = String(value);
1349
+ if (inline.length <= INLINE_LIMIT && !inline.includes("\n")) {
1350
+ scalars[key] = value;
1351
+ } else {
1352
+ blocks.push(`${key}:
1353
+ ${inline}`);
508
1354
  }
1355
+ continue;
509
1356
  }
510
- if (schema.properties) {
511
- for (const [key, subSchema] of Object.entries(schema.properties)) {
512
- if (key in obj) {
513
- walk(obj[key], subSchema, joinPath(path7, key), errors);
514
- }
1357
+ if (Array.isArray(value)) {
1358
+ if (value.every((item) => typeof item === "string")) {
1359
+ blocks.push(`${key}:
1360
+ ${renderStringList(value)}`);
1361
+ } else {
1362
+ blocks.push(`${key}:
1363
+ ${renderUnknownList(value)}`);
515
1364
  }
1365
+ continue;
516
1366
  }
1367
+ blocks.push(`${key}: ${clipInline(oneLineJson(value))}`);
517
1368
  }
518
- if (schema.type === "array" && Array.isArray(value) && schema.items) {
519
- for (let i = 0; i < value.length; i++) {
520
- walk(value[i], schema.items, `${path7}[${i}]`, errors);
521
- }
522
- }
1369
+ return joinSections([renderHeader(toolName, scalars), ...blocks]);
523
1370
  }
524
- function checkType(value, type) {
525
- switch (type) {
526
- case "string":
527
- return typeof value === "string";
528
- case "number":
529
- return typeof value === "number" && !Number.isNaN(value);
530
- case "integer":
531
- return typeof value === "number" && Number.isInteger(value);
532
- case "boolean":
533
- return typeof value === "boolean";
534
- case "null":
535
- return value === null;
536
- case "array":
537
- return Array.isArray(value);
538
- case "object":
539
- return isPlainObject(value);
540
- default:
541
- return true;
542
- }
1371
+ function renderHeader(label, fields) {
1372
+ const parts = Object.entries(fields).filter(([, value]) => value !== void 0 && value !== null && value !== "").map(([key, value]) => `${key}=${clipInline(formatInlineValue(value))}`);
1373
+ return parts.length > 0 ? `${label} (${parts.join(" ")})` : label;
543
1374
  }
544
- function isPlainObject(v) {
545
- return typeof v === "object" && v !== null && !Array.isArray(v);
1375
+ function renderStringList(items, empty = "", limit = DEFAULT_LIST_LIMIT) {
1376
+ if (items.length === 0) return empty;
1377
+ const shown = items.slice(0, limit);
1378
+ const omitted = items.length - shown.length;
1379
+ return [
1380
+ ...shown,
1381
+ ...omitted > 0 ? [`[serializer omitted ${omitted} item(s); narrow the request for more]`] : []
1382
+ ].join("\n");
546
1383
  }
547
- function describeType(v) {
548
- if (v === null) return "null";
549
- if (Array.isArray(v)) return "array";
550
- return typeof v;
1384
+ function renderUnknownList(items, limit = DEFAULT_LIST_LIMIT) {
1385
+ const shown = items.slice(0, limit).map((item) => clipInline(oneLineJson(item), 1e3));
1386
+ const omitted = items.length - shown.length;
1387
+ if (omitted > 0)
1388
+ shown.push(`[serializer omitted ${omitted} item(s); narrow the request for more]`);
1389
+ return shown.join("\n");
551
1390
  }
552
- function joinPath(parent, key) {
553
- if (!parent) return key;
554
- return `${parent}.${key}`;
1391
+ function joinSections(sections) {
1392
+ return sections.map((section) => typeof section === "string" ? section.trimEnd() : void 0).filter((section) => !!section).join("\n");
555
1393
  }
556
- function deepEqual(a, b) {
557
- if (a === b) return true;
558
- if (typeof a !== typeof b) return false;
559
- if (a === null || b === null) return a === b;
560
- if (Array.isArray(a) && Array.isArray(b)) {
561
- return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
562
- }
563
- if (typeof a === "object" && typeof b === "object") {
564
- const ak = Object.keys(a);
565
- const bk = Object.keys(b);
566
- if (ak.length !== bk.length) return false;
567
- return ak.every(
568
- (k) => deepEqual(a[k], b[k])
569
- );
570
- }
571
- return false;
1394
+ function formatInlineValue(value) {
1395
+ if (Array.isArray(value)) return `[${value.map(formatInlineValue).join(",")}]`;
1396
+ if (isScalar(value)) return String(value);
1397
+ return oneLineJson(value);
572
1398
  }
573
-
574
- // src/utils/regex-guard.ts
575
- var MAX_PATTERN_LEN = 512;
576
- var DANGEROUS_PATTERNS = [
577
- /(\([^)]*[+*][^)]*\))[+*]/,
578
- // (a+)+, (.*)+, etc
579
- /(\(\?:[^)]*[+*][^)]*\))[+*]/
580
- // same, with non-capturing group
581
- ];
582
- function compileUserRegex(pattern, flags) {
583
- if (typeof pattern !== "string") {
584
- return { ok: false, reason: "pattern must be a string" };
585
- }
586
- if (pattern.length === 0) {
587
- return { ok: false, reason: "pattern is empty" };
588
- }
589
- if (pattern.length > MAX_PATTERN_LEN) {
590
- return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
591
- }
592
- for (const rx of DANGEROUS_PATTERNS) {
593
- if (rx.test(pattern)) {
594
- return {
595
- ok: false,
596
- reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
597
- };
598
- }
599
- }
1399
+ function clipInline(value, max = INLINE_LIMIT) {
1400
+ const compact = value.replace(/\s+/g, " ").trim();
1401
+ return compact.length <= max ? compact : `${compact.slice(0, max - 15)}...(${compact.length} chars)`;
1402
+ }
1403
+ function oneLineJson(value) {
600
1404
  try {
601
- return { ok: true, regex: new RegExp(pattern, flags) };
602
- } catch (err) {
603
- return {
604
- ok: false,
605
- reason: err instanceof Error ? err.message : "invalid regex"
606
- };
1405
+ return JSON.stringify(value);
1406
+ } catch {
1407
+ return String(value);
607
1408
  }
608
1409
  }
609
-
610
- // src/utils/merge-models-payload.ts
611
- function mergeModelsPayload(base, overlay) {
612
- const out = {};
613
- for (const [id, provider] of Object.entries(base)) {
614
- out[id] = cloneProvider(provider);
615
- }
616
- for (const [id, ovProvider] of Object.entries(overlay)) {
617
- const existing = out[id];
618
- out[id] = existing ? mergeProvider(existing, ovProvider) : cloneProvider(ovProvider);
619
- }
620
- return out;
1410
+ function stringField(obj, key) {
1411
+ const value = obj[key];
1412
+ return typeof value === "string" ? value : void 0;
621
1413
  }
622
- function mergeProvider(base, overlay) {
623
- const models = {};
624
- for (const [mid, m] of Object.entries(base.models ?? {})) {
625
- models[mid] = { ...m };
626
- }
627
- for (const [mid, ovModel] of Object.entries(overlay.models ?? {})) {
628
- const existing = models[mid];
629
- models[mid] = existing ? mergeModel(existing, ovModel) : { ...ovModel };
630
- }
631
- return {
632
- ...base,
633
- // Overlay scalar fields win when explicitly provided; otherwise keep base.
634
- ...stripUndefined({
635
- id: overlay.id,
636
- name: overlay.name,
637
- npm: overlay.npm,
638
- api: overlay.api,
639
- env: overlay.env,
640
- doc: overlay.doc
641
- }),
642
- models
643
- };
1414
+ function numberField(obj, key) {
1415
+ const value = obj[key];
1416
+ return typeof value === "number" ? value : void 0;
644
1417
  }
645
- function mergeModel(base, overlay) {
646
- const merged = { ...base, ...overlay };
647
- if (base.limit || overlay.limit) {
648
- merged.limit = { ...base.limit, ...overlay.limit };
649
- }
650
- if (base.cost || overlay.cost) {
651
- merged.cost = { ...base.cost, ...overlay.cost };
652
- }
653
- if (base.modalities || overlay.modalities) {
654
- merged.modalities = { ...base.modalities, ...overlay.modalities };
655
- }
656
- return merged;
1418
+ function stringArrayField(obj, key) {
1419
+ const value = obj[key];
1420
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
657
1421
  }
658
- function cloneProvider(p) {
659
- const models = {};
660
- for (const [mid, m] of Object.entries(p.models ?? {})) {
661
- models[mid] = { ...m };
662
- }
663
- return { ...p, models };
1422
+ function stringFromInput(input, key) {
1423
+ if (!isRecord2(input)) return void 0;
1424
+ const value = input[key];
1425
+ return typeof value === "string" ? value : void 0;
664
1426
  }
665
- function stripUndefined(obj) {
666
- const out = {};
667
- for (const [k, v] of Object.entries(obj)) {
668
- if (v !== void 0) out[k] = v;
669
- }
670
- return out;
1427
+ function numberFromInput(input, key) {
1428
+ if (!isRecord2(input)) return void 0;
1429
+ const value = input[key];
1430
+ return typeof value === "number" ? value : void 0;
1431
+ }
1432
+ function inputListSummary(input, key) {
1433
+ if (!isRecord2(input)) return void 0;
1434
+ const value = input[key];
1435
+ if (typeof value === "string") return value;
1436
+ if (Array.isArray(value)) return value.filter((item) => typeof item === "string").join(",");
1437
+ return void 0;
1438
+ }
1439
+ function isRecord2(value) {
1440
+ return !!value && typeof value === "object" && !Array.isArray(value);
1441
+ }
1442
+ function isScalar(value) {
1443
+ return value === null || ["string", "number", "boolean"].includes(typeof value);
1444
+ }
1445
+ function wstackGlobalRoot() {
1446
+ const fromEnv = process.env["WRONGSTACK_HOME"];
1447
+ if (fromEnv && fromEnv.trim().length > 0) return path6.resolve(fromEnv);
1448
+ return path6.join(os.homedir(), ".wrongstack");
671
1449
  }
672
1450
 
673
1451
  // src/types/errors.ts
@@ -1150,7 +1928,7 @@ var DefaultSecretVault = class {
1150
1928
  KEY_FILE_MAGIC.copy(keyFileBuf, 0);
1151
1929
  keyFileBuf[KEY_FILE_MAGIC.length] = newVersion;
1152
1930
  newKey.copy(keyFileBuf, KEY_FILE_MAGIC.length + 1);
1153
- fs2.mkdirSync(path4.dirname(this.keyFile), { recursive: true });
1931
+ fs2.mkdirSync(path6.dirname(this.keyFile), { recursive: true });
1154
1932
  fs2.writeFileSync(this.keyFile, keyFileBuf, { mode: 384 });
1155
1933
  checkKeyFilePermissions(this.keyFile);
1156
1934
  this.key = newKey;
@@ -1198,7 +1976,7 @@ var DefaultSecretVault = class {
1198
1976
  } catch (err) {
1199
1977
  if (err.code !== "ENOENT") throw err;
1200
1978
  }
1201
- fs2.mkdirSync(path4.dirname(this.keyFile), { recursive: true });
1979
+ fs2.mkdirSync(path6.dirname(this.keyFile), { recursive: true });
1202
1980
  const key = randomBytes(KEY_BYTES);
1203
1981
  try {
1204
1982
  fs2.writeFileSync(this.keyFile, key, { mode: 384, flag: "wx" });
@@ -1288,7 +2066,7 @@ async function rewriteConfigEncrypted(configPath, vault, patch) {
1288
2066
  }
1289
2067
  const merged = deepMerge(current, patch ?? {});
1290
2068
  const encrypted = encryptConfigSecrets(merged, vault);
1291
- await fs.mkdir(path4.dirname(configPath), { recursive: true });
2069
+ await fs.mkdir(path6.dirname(configPath), { recursive: true });
1292
2070
  await atomicWrite(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
1293
2071
  await restrictFilePermissions(configPath);
1294
2072
  }
@@ -1334,8 +2112,13 @@ async function rotateConfigKeys(configPath, vault, logger) {
1334
2112
  warn(`[secret-vault] Config file ${configPath} is not valid JSON \u2014 skipping rotation`);
1335
2113
  return { rotated: 0, oldVersion: vault.keyVersion, newVersion: vault.keyVersion, file: configPath };
1336
2114
  }
1337
- const counter = { n: 0 };
2115
+ const counter = { n: 0, failed: [] };
1338
2116
  const decrypted = walkDecryptCount(parsed, vault, counter);
2117
+ if (counter.failed.length > 0) {
2118
+ throw new Error(
2119
+ `[secret-vault] Aborting key rotation: ${counter.failed.length} field(s) could not be decrypted with the current key and would be permanently lost on rotation: ${counter.failed.join(", ")}. Restore or remove these fields before rotating.`
2120
+ );
2121
+ }
1339
2122
  if (counter.n === 0) {
1340
2123
  const { oldVersion: oldVersion2, newVersion: newVersion2 } = vault.rotateKey();
1341
2124
  log(`[secret-vault] Key rotated (v${oldVersion2} \u2192 v${newVersion2}) \u2014 no encrypted fields to re-encrypt`);
@@ -1348,23 +2131,27 @@ async function rotateConfigKeys(configPath, vault, logger) {
1348
2131
  log(`[secret-vault] Key rotated (v${oldVersion} \u2192 v${newVersion}) \u2014 re-encrypted ${counter.n} field(s)`);
1349
2132
  return { rotated: counter.n, oldVersion, newVersion, file: configPath };
1350
2133
  }
1351
- function walkDecryptCount(node, vault, counter) {
2134
+ function walkDecryptCount(node, vault, counter, pathPrefix = "") {
1352
2135
  if (node === null || node === void 0) return node;
1353
2136
  if (typeof node !== "object") return node;
1354
2137
  if (Array.isArray(node)) {
1355
- return node.map((item) => walkDecryptCount(item, vault, counter));
2138
+ return node.map(
2139
+ (item, i) => walkDecryptCount(item, vault, counter, `${pathPrefix}[${i}]`)
2140
+ );
1356
2141
  }
1357
2142
  const out = /* @__PURE__ */ Object.create(null);
1358
2143
  for (const [k, v] of Object.entries(node)) {
2144
+ const keyPath = pathPrefix ? `${pathPrefix}.${k}` : k;
1359
2145
  if (typeof v === "string" && vault.isEncrypted(v)) {
1360
2146
  try {
1361
2147
  out[k] = vault.decrypt(v);
1362
2148
  counter.n++;
1363
2149
  } catch {
2150
+ counter.failed.push(keyPath);
1364
2151
  out[k] = v;
1365
2152
  }
1366
2153
  } else if (typeof v === "object" && v !== null) {
1367
- out[k] = walkDecryptCount(v, vault, counter);
2154
+ out[k] = walkDecryptCount(v, vault, counter, keyPath);
1368
2155
  } else {
1369
2156
  out[k] = v;
1370
2157
  }
@@ -1477,7 +2264,7 @@ var DefaultLogger = class _DefaultLogger {
1477
2264
  this.maxFileBytes = opts.maxFileBytes ?? 10 * 1024 * 1024;
1478
2265
  if (this.file) {
1479
2266
  try {
1480
- fs2.mkdirSync(path4.dirname(this.file), { recursive: true });
2267
+ fs2.mkdirSync(path6.dirname(this.file), { recursive: true });
1481
2268
  } catch {
1482
2269
  }
1483
2270
  }
@@ -1621,11 +2408,10 @@ var DefaultTokenCounter = class {
1621
2408
  const price = model ? this.priceCache.get(model) : void 0;
1622
2409
  if (price) {
1623
2410
  this.applyPrice(usage, price);
1624
- this.events?.emit("token.accounted", {
1625
- usage: this.total(),
1626
- cost: { input: this.costInput, output: this.costOutput, total: this.costInput + this.costOutput }
1627
- });
1628
- } else if (this.registry && this.providerId && model) {
2411
+ this.emitAccounted();
2412
+ return;
2413
+ }
2414
+ if (this.registry && this.providerId && model) {
1629
2415
  if (this.priceCache.size >= PRICE_CACHE_MAX_SIZE) {
1630
2416
  const keys = [...this.priceCache.keys()];
1631
2417
  this.priceCache.delete(keys[0] ?? "");
@@ -1635,16 +2421,16 @@ var DefaultTokenCounter = class {
1635
2421
  const p = priceFromModel(m);
1636
2422
  this.priceCache.set(model, p);
1637
2423
  this.applyPrice(usage, p);
1638
- this.events?.emit("token.accounted", {
1639
- usage: this.total(),
1640
- cost: { input: this.costInput, output: this.costOutput, total: this.costInput + this.costOutput }
1641
- });
1642
2424
  }
2425
+ this.emitAccounted();
1643
2426
  }).catch(() => {
1644
2427
  this.events?.emit("token.cost_estimate_unavailable", { model: model ?? "<unknown>" });
2428
+ this.emitAccounted();
1645
2429
  return void 0;
1646
2430
  });
2431
+ return;
1647
2432
  }
2433
+ this.emitAccounted();
1648
2434
  }
1649
2435
  /** Synchronous variant for code paths that have already resolved the model. */
1650
2436
  accountWithModel(usage, resolved) {
@@ -1661,10 +2447,7 @@ var DefaultTokenCounter = class {
1661
2447
  }
1662
2448
  this.priceCache.set(resolved.modelId, price);
1663
2449
  this.applyPrice(usage, price);
1664
- this.events?.emit("token.accounted", {
1665
- usage: this.total(),
1666
- cost: { input: this.costInput, output: this.costOutput, total: this.costInput + this.costOutput }
1667
- });
2450
+ this.emitAccounted();
1668
2451
  }
1669
2452
  total() {
1670
2453
  return {
@@ -1697,6 +2480,12 @@ var DefaultTokenCounter = class {
1697
2480
  invalidateCache() {
1698
2481
  this.priceCache.clear();
1699
2482
  }
2483
+ emitAccounted() {
2484
+ this.events?.emit("token.accounted", {
2485
+ usage: this.total(),
2486
+ cost: { input: this.costInput, output: this.costOutput, total: this.costInput + this.costOutput }
2487
+ });
2488
+ }
1700
2489
  reset() {
1701
2490
  this.input = 0;
1702
2491
  this.output = 0;
@@ -1704,6 +2493,9 @@ var DefaultTokenCounter = class {
1704
2493
  this.cacheWrite = 0;
1705
2494
  this.costInput = 0;
1706
2495
  this.costOutput = 0;
2496
+ this.lastInput = 0;
2497
+ this.lastCacheRead = 0;
2498
+ this.emitAccounted();
1707
2499
  }
1708
2500
  applyPrice(usage, price) {
1709
2501
  if (price.input) this.costInput += usage.input / 1e6 * price.input;
@@ -1711,8 +2503,14 @@ var DefaultTokenCounter = class {
1711
2503
  if (usage.cacheRead && price.cacheRead) {
1712
2504
  this.costInput += usage.cacheRead / 1e6 * price.cacheRead;
1713
2505
  }
1714
- if (usage.cacheWrite && price.cacheWrite) {
1715
- this.costInput += usage.cacheWrite / 1e6 * price.cacheWrite;
2506
+ const hasCacheWriteSplit = usage.cacheWrite5m !== void 0 || usage.cacheWrite1h !== void 0;
2507
+ const cacheWrite5m = usage.cacheWrite5m ?? (hasCacheWriteSplit ? 0 : usage.cacheWrite);
2508
+ const cacheWrite1h = usage.cacheWrite1h ?? 0;
2509
+ if (cacheWrite5m && (price.cacheWrite5m ?? price.cacheWrite)) {
2510
+ this.costInput += cacheWrite5m / 1e6 * (price.cacheWrite5m ?? price.cacheWrite ?? 0);
2511
+ }
2512
+ if (cacheWrite1h && (price.cacheWrite1h ?? price.cacheWrite)) {
2513
+ this.costInput += cacheWrite1h / 1e6 * (price.cacheWrite1h ?? price.cacheWrite ?? 0);
1716
2514
  }
1717
2515
  }
1718
2516
  };
@@ -1721,7 +2519,9 @@ function priceFromModel(m) {
1721
2519
  input: m.cost?.input,
1722
2520
  output: m.cost?.output,
1723
2521
  cacheRead: m.cost?.cache_read,
1724
- cacheWrite: m.cost?.cache_write
2522
+ cacheWrite: m.cost?.cache_write,
2523
+ cacheWrite5m: m.cost?.cache_write_5m ?? m.cost?.cache_write,
2524
+ cacheWrite1h: m.cost?.cache_write_1h ?? (m.cost?.input !== void 0 ? m.cost.input * 2 : void 0)
1725
2525
  };
1726
2526
  }
1727
2527
  function round4(n) {
@@ -1778,25 +2578,26 @@ function findPreserveStart(messages, preserveK) {
1778
2578
  preserveStart = i;
1779
2579
  }
1780
2580
  }
1781
- let forwardWalkIterations = 0;
1782
- let forwardWalkInnerIterations = 0;
1783
- for (let i = preserveStart; i < messages.length; i++) {
1784
- forwardWalkIterations++;
1785
- const m = messages[i];
1786
- if (!m || typeof m.content === "string" || !Array.isArray(m.content)) continue;
1787
- const hasToolUse3 = m.content.some((b) => {
1788
- forwardWalkInnerIterations++;
1789
- return b.type === "tool_use";
1790
- });
1791
- if (hasToolUse3 && i + 1 < messages.length) {
1792
- const next = messages[i + 1];
1793
- if (next && next.role === "user" && typeof next.content !== "string" && Array.isArray(next.content) && next.content.some((b) => {
1794
- forwardWalkInnerIterations++;
1795
- return b.type === "tool_result";
1796
- })) {
1797
- preserveStart = i + 1;
1798
- }
2581
+ let pairRepairIterations = 0;
2582
+ let pairRepairInnerIterations = 0;
2583
+ while (preserveStart > 0) {
2584
+ pairRepairIterations++;
2585
+ const first = messages[preserveStart];
2586
+ const prev = messages[preserveStart - 1];
2587
+ if (!first || !prev || first.role !== "user" || prev.role !== "assistant") break;
2588
+ if (typeof first.content === "string" || typeof prev.content === "string") break;
2589
+ const resultIds = /* @__PURE__ */ new Set();
2590
+ for (const block of first.content) {
2591
+ pairRepairInnerIterations++;
2592
+ if (block.type === "tool_result") resultIds.add(block.tool_use_id);
1799
2593
  }
2594
+ if (resultIds.size === 0) break;
2595
+ const hasMatchingUse = prev.content.some((block) => {
2596
+ pairRepairInnerIterations++;
2597
+ return block.type === "tool_use" && resultIds.has(block.id);
2598
+ });
2599
+ if (!hasMatchingUse) break;
2600
+ preserveStart--;
1800
2601
  }
1801
2602
  if (compactionDebugEnabled()) {
1802
2603
  console.log(
@@ -1806,9 +2607,9 @@ function findPreserveStart(messages, preserveK) {
1806
2607
  messageCount: messages.length,
1807
2608
  preserveK,
1808
2609
  preserveStart,
1809
- forwardWalkIterations,
1810
- forwardWalkInnerIterations,
1811
- forwardWalkInnerPerOuter: forwardWalkIterations > 0 ? forwardWalkInnerIterations / forwardWalkIterations : 0
2610
+ pairRepairIterations,
2611
+ pairRepairInnerIterations,
2612
+ pairRepairInnerPerOuter: pairRepairIterations > 0 ? pairRepairInnerIterations / pairRepairIterations : 0
1812
2613
  })
1813
2614
  );
1814
2615
  }
@@ -1825,7 +2626,8 @@ function eliseOldToolResults(messages, opts) {
1825
2626
  if (!msg || !Array.isArray(msg.content)) continue;
1826
2627
  for (const b of msg.content) {
1827
2628
  fastPathInnerIterations++;
1828
- if (b.type === "tool_result" && estimateToolResultTokens(b.content) >= opts.eliseThreshold) {
2629
+ const oversized = b.type === "tool_result" && estimateToolResultTokens(b.content) >= opts.eliseThreshold || b.type === "tool_use" && estimateToolInputTokens(b.input) >= opts.eliseThreshold;
2630
+ if (oversized) {
1829
2631
  hasOversized = true;
1830
2632
  break;
1831
2633
  }
@@ -1859,6 +2661,13 @@ function eliseOldToolResults(messages, opts) {
1859
2661
  }
1860
2662
  const original = msg.content;
1861
2663
  const newContent = original.map((b) => {
2664
+ if (b.type === "tool_use") {
2665
+ const tokens2 = estimateToolInputTokens(b.input);
2666
+ if (tokens2 < opts.eliseThreshold) return b;
2667
+ const elidedInput = summarizeToolUseInputElision(b, tokens2);
2668
+ saved += Math.max(0, tokens2 - estimateToolInputTokens(elidedInput));
2669
+ return { ...b, input: elidedInput };
2670
+ }
1862
2671
  if (b.type !== "tool_result") return b;
1863
2672
  const tokens = estimateToolResultTokens(b.content);
1864
2673
  if (tokens < opts.eliseThreshold) return b;
@@ -1866,7 +2675,7 @@ function eliseOldToolResults(messages, opts) {
1866
2675
  const elided = {
1867
2676
  type: "tool_result",
1868
2677
  tool_use_id: b.tool_use_id,
1869
- content: `[elided: ~${tokens} tokens]`,
2678
+ content: summarizeToolResultElision(b, tokens),
1870
2679
  is_error: b.is_error
1871
2680
  };
1872
2681
  return elided;
@@ -1906,6 +2715,65 @@ function eliseOldToolResults(messages, opts) {
1906
2715
  });
1907
2716
  return { messages: changed ? next : messages, saved, changed };
1908
2717
  }
2718
+ function summarizeToolUseInputElision(block, tokens) {
2719
+ const fields = {};
2720
+ for (const [key, value] of Object.entries(block.input ?? {})) {
2721
+ fields[key] = summarizeToolUseInputValue(value);
2722
+ }
2723
+ return {
2724
+ __elided_tool_input: `~${tokens} tokens; original arguments are in the session log`,
2725
+ tool: block.name,
2726
+ fields
2727
+ };
2728
+ }
2729
+ function summarizeToolUseInputValue(value) {
2730
+ if (value === null || value === void 0) return value;
2731
+ if (typeof value === "number" || typeof value === "boolean") return value;
2732
+ if (typeof value === "string") {
2733
+ const oneLine = value.replace(/\s+/g, " ").trim();
2734
+ return oneLine.length <= 160 ? oneLine : `${oneLine.slice(0, 120)}...(${oneLine.length} chars)`;
2735
+ }
2736
+ if (Array.isArray(value)) {
2737
+ return `[array:${value.length}]`;
2738
+ }
2739
+ if (typeof value === "object") {
2740
+ const keys = Object.keys(value);
2741
+ return `[object:${keys.slice(0, 8).join(",")}${keys.length > 8 ? ",..." : ""}]`;
2742
+ }
2743
+ return String(value);
2744
+ }
2745
+ function summarizeToolResultElision(block, tokens) {
2746
+ const parts = [`elided: ~${tokens} tokens`];
2747
+ if (block.name) parts.push(`tool=${block.name}`);
2748
+ const files = extractPathHints(block.content).slice(0, 5);
2749
+ if (files.length > 0) parts.push(`files=${files.join(", ")}`);
2750
+ const error = firstErrorLine(block.content);
2751
+ if (error) parts.push(`error=${error}`);
2752
+ return `[${parts.join("; ")}]`;
2753
+ }
2754
+ function extractPathHints(content) {
2755
+ const text = typeof content === "string" ? content : JSON.stringify(content);
2756
+ const out = /* @__PURE__ */ new Set();
2757
+ const re = /(?:(?:[A-Za-z]:)?[./\\]?[\w@.-]+(?:[\\/][\w@(). -]+)+\.[A-Za-z0-9]{1,12})/g;
2758
+ for (const match of text.matchAll(re)) {
2759
+ const clean = match[0]?.replace(/\\/g, "/").replace(/^["'`]+|["'`),;:]+$/g, "");
2760
+ if (clean && clean.length <= 220) out.add(clean);
2761
+ if (out.size >= 5) break;
2762
+ }
2763
+ return [...out];
2764
+ }
2765
+ function firstErrorLine(content) {
2766
+ const text = typeof content === "string" ? content : JSON.stringify(content);
2767
+ for (const line of text.split(/\r?\n/)) {
2768
+ if (!/\b(error|exception|failed|failure|fatal|panic|timeout|denied|enoent|eacces|eperm)\b/i.test(
2769
+ line
2770
+ ))
2771
+ continue;
2772
+ const trimmed = line.replace(/\s+/g, " ").trim();
2773
+ if (trimmed) return trimmed.slice(0, 180);
2774
+ }
2775
+ return void 0;
2776
+ }
1909
2777
  function buildLosslessDigest(messages) {
1910
2778
  const lines = [];
1911
2779
  for (const m of messages) {
@@ -2016,15 +2884,15 @@ function buildSmartDigest(messages) {
2016
2884
  lines.push(`[${m.role}]: ${display}${marker}`);
2017
2885
  }
2018
2886
  if (noiseCount > 0) {
2019
- lines.push(`[system]: ${noiseCount} low-importance turn(s) collapsed (repeated failures / pure tool I/O)`);
2887
+ lines.push(
2888
+ `[system]: ${noiseCount} low-importance turn(s) collapsed (repeated failures / pure tool I/O)`
2889
+ );
2020
2890
  }
2021
2891
  return lines.join("\n");
2022
2892
  }
2023
2893
  function countToolBlocks(m) {
2024
2894
  if (typeof m.content === "string") return 0;
2025
- return m.content.filter(
2026
- (b) => b.type === "tool_use" || b.type === "tool_result"
2027
- ).length;
2895
+ return m.content.filter((b) => b.type === "tool_use" || b.type === "tool_result").length;
2028
2896
  }
2029
2897
  function firstSentence(text) {
2030
2898
  const trimmed = text.trim();
@@ -2070,10 +2938,12 @@ var HybridCompactor = class {
2070
2938
  if (elide.changed) ctx.state.replaceMessages(elide.messages);
2071
2939
  if (elide.saved > 0) reductions.push({ phase: "elision", saved: elide.saved });
2072
2940
  let collapsedDigest;
2941
+ let evidenceDigest;
2073
2942
  if (opts.aggressive) {
2074
2943
  const phase2 = this.collapseAncientTurns(ctx, preserveK);
2075
2944
  if (phase2.saved > 0) reductions.push({ phase: "summary", saved: phase2.saved });
2076
2945
  collapsedDigest = phase2.digest;
2946
+ evidenceDigest = phase2.evidenceDigest;
2077
2947
  }
2078
2948
  const repaired = repairToolUseAdjacency(ctx.messages);
2079
2949
  if (repaired.report.changed) {
@@ -2081,6 +2951,11 @@ var HybridCompactor = class {
2081
2951
  }
2082
2952
  const afterTokens = estimateMessages(ctx.messages);
2083
2953
  const afterFull = this.estimateFullRequest(ctx);
2954
+ const quality = checkCompactionQuality(ctx, {
2955
+ collapsedDigest,
2956
+ evidenceDigest,
2957
+ reduced: beforeTokens > afterTokens || beforeFull > afterFull
2958
+ });
2084
2959
  return {
2085
2960
  before: beforeTokens,
2086
2961
  after: afterTokens,
@@ -2088,6 +2963,8 @@ var HybridCompactor = class {
2088
2963
  fullRequestTokensAfter: afterFull,
2089
2964
  reductions,
2090
2965
  collapsedDigest,
2966
+ evidenceDigest,
2967
+ quality,
2091
2968
  repaired: repaired.report.changed ? {
2092
2969
  removedToolUses: repaired.report.removedToolUses,
2093
2970
  removedToolResults: repaired.report.removedToolResults,
@@ -2129,7 +3006,13 @@ var HybridCompactor = class {
2129
3006
  if (boundary <= 0) return { saved: 0 };
2130
3007
  const removed = messages.slice(0, boundary);
2131
3008
  const removedTokens = estimateMessages(removed);
2132
- const digest = this.smart ? buildSmartDigest(removed) || `${removed.length} earlier turns (no textual content; tool I/O omitted \u2014 see session log)` : buildLosslessDigest(removed) || `${removed.length} earlier turns (no textual content; tool I/O omitted \u2014 see session log)`;
3009
+ const historyDigest = this.smart ? buildSmartDigest(removed) || `${removed.length} earlier turns (no textual content; tool I/O omitted; see session log)` : buildLosslessDigest(removed) || `${removed.length} earlier turns (no textual content; tool I/O omitted; see session log)`;
3010
+ const evidenceDigest = buildContextEvidenceDigest(ctx);
3011
+ const digest = evidenceDigest ? `[context_state]
3012
+ ${evidenceDigest}
3013
+
3014
+ [prior_history]
3015
+ ${historyDigest}` : historyDigest;
2133
3016
  const summaryMsg = {
2134
3017
  role: "system",
2135
3018
  content: `[prior_turns_digest: ${digest}]`
@@ -2138,10 +3021,29 @@ var HybridCompactor = class {
2138
3021
  ctx.state.replaceMessages([summaryMsg, ...tail]);
2139
3022
  return {
2140
3023
  saved: Math.max(0, removedTokens - estimateMessages([summaryMsg])),
2141
- digest
3024
+ digest,
3025
+ evidenceDigest: evidenceDigest || void 0
2142
3026
  };
2143
3027
  }
2144
3028
  };
3029
+ function checkCompactionQuality(ctx, opts) {
3030
+ const evidence = ctx.contextEvidence;
3031
+ const digest = `${opts.collapsedDigest ?? ""}
3032
+ ${opts.evidenceDigest ?? ""}`;
3033
+ const hasIntent = Boolean(evidence?.currentIntent?.text || /\b(intent|goal|session_goals)\b/i.test(digest));
3034
+ const hasPathTrail = Boolean(
3035
+ Object.keys(evidence?.fileGraph ?? {}).length > 0 || (evidence?.toolCalls.length ?? 0) > 0 || /\b(dependency_graph|tool_trail|files=)\b/i.test(digest)
3036
+ );
3037
+ const issues = [];
3038
+ if (opts.reduced && !hasIntent) issues.push("missing intent anchor");
3039
+ if (opts.reduced && !hasPathTrail) issues.push("missing tool/path trail");
3040
+ return {
3041
+ ok: issues.length === 0,
3042
+ hasIntent,
3043
+ hasPathTrail,
3044
+ issues
3045
+ };
3046
+ }
2145
3047
  function readContextWindowPolicy(ctx) {
2146
3048
  const policy = ctx.meta?.["contextWindowPolicy"];
2147
3049
  if (!policy || typeof policy !== "object") return null;
@@ -2168,52 +3070,52 @@ var DefaultPathResolver = class {
2168
3070
  projectRoot;
2169
3071
  cwd;
2170
3072
  constructor(cwd = process.cwd()) {
2171
- this.cwd = path4.resolve(cwd);
3073
+ this.cwd = path6.resolve(cwd);
2172
3074
  this.projectRoot = this.detectProjectRoot(this.cwd);
2173
3075
  }
2174
3076
  detectProjectRoot(start) {
2175
- let dir = path4.resolve(start);
2176
- const root = path4.parse(dir).root;
2177
- const home = path4.resolve(os.homedir());
2178
- const startPath = path4.resolve(start);
3077
+ let dir = path6.resolve(start);
3078
+ const root = path6.parse(dir).root;
3079
+ const home = path6.resolve(os.homedir());
3080
+ const startPath = path6.resolve(start);
2179
3081
  while (dir !== root) {
2180
3082
  if (dir === home && dir !== startPath) {
2181
3083
  break;
2182
3084
  }
2183
3085
  for (const marker of PROJECT_MARKERS) {
2184
3086
  try {
2185
- fs2.accessSync(path4.join(dir, marker));
3087
+ fs2.accessSync(path6.join(dir, marker));
2186
3088
  return dir;
2187
3089
  } catch {
2188
3090
  }
2189
3091
  }
2190
- const parent = path4.dirname(dir);
3092
+ const parent = path6.dirname(dir);
2191
3093
  if (parent === dir) break;
2192
3094
  dir = parent;
2193
3095
  }
2194
3096
  return startPath;
2195
3097
  }
2196
3098
  resolve(input) {
2197
- const abs = path4.isAbsolute(input) ? input : path4.resolve(this.cwd, input);
3099
+ const abs = path6.isAbsolute(input) ? input : path6.resolve(this.cwd, input);
2198
3100
  let real;
2199
3101
  try {
2200
3102
  real = fs2.realpathSync(abs);
2201
3103
  } catch {
2202
- real = path4.normalize(abs);
3104
+ real = path6.normalize(abs);
2203
3105
  }
2204
3106
  return real;
2205
3107
  }
2206
3108
  isInsideRoot(absPath) {
2207
- const normalized = path4.normalize(absPath);
2208
- const root = path4.normalize(this.projectRoot);
3109
+ const normalized = path6.normalize(absPath);
3110
+ const root = path6.normalize(this.projectRoot);
2209
3111
  if (normalized === root) return true;
2210
- const rel = path4.relative(root, normalized);
2211
- return !rel.startsWith("..") && !path4.isAbsolute(rel);
3112
+ const rel = path6.relative(root, normalized);
3113
+ return !rel.startsWith("..") && !path6.isAbsolute(rel);
2212
3114
  }
2213
3115
  ensureInsideRoot(absPath) {
2214
3116
  const resolved = this.resolve(absPath);
2215
3117
  if (!this.isInsideRoot(resolved)) {
2216
- const display = path4.isAbsolute(absPath) ? path4.basename(absPath) : absPath;
3118
+ const display = path6.isAbsolute(absPath) ? path6.basename(absPath) : absPath;
2217
3119
  const err = new Error(`Path "${display}" resolves outside the project root`);
2218
3120
  err.fullPath = absPath;
2219
3121
  err.projectRoot = this.projectRoot;
@@ -2592,7 +3494,7 @@ var DefaultModelsRegistry = class {
2592
3494
  this.overlay = opts.overlay;
2593
3495
  this.overlayUrl = opts.overlayUrl;
2594
3496
  this.overlayFile = opts.overlayFile;
2595
- this.overlayCacheFile = opts.overlayCacheFile ?? (opts.overlayUrl ? path4.join(path4.dirname(opts.cacheFile), "models-overlay-cache.json") : void 0);
3497
+ this.overlayCacheFile = opts.overlayCacheFile ?? (opts.overlayUrl ? path6.join(path6.dirname(opts.cacheFile), "models-overlay-cache.json") : void 0);
2596
3498
  }
2597
3499
  async load(opts = {}) {
2598
3500
  if (this.payload && !opts.force) return this.payload;
@@ -2751,10 +3653,11 @@ var DefaultModelsRegistry = class {
2751
3653
  capabilities: {
2752
3654
  tools: model.tool_call ?? false,
2753
3655
  vision: Boolean(model.modalities?.input?.includes("image")),
2754
- reasoning: model.reasoning ?? false,
3656
+ reasoning: model.reasoning ?? model.reasoningConfig !== void 0,
2755
3657
  maxContext: model.limit?.context ?? 0,
2756
3658
  maxOutput: model.limit?.output,
2757
- knowledge: model.knowledge
3659
+ knowledge: model.knowledge,
3660
+ reasoningConfig: model.reasoningConfig
2758
3661
  },
2759
3662
  cost: model.cost
2760
3663
  };
@@ -2805,7 +3708,7 @@ var DefaultModelsRegistry = class {
2805
3708
  }
2806
3709
  /** Used by `wstack models refresh` to expose where the cache lives. */
2807
3710
  cacheLocation() {
2808
- return path4.resolve(this.cacheFile);
3711
+ return path6.resolve(this.cacheFile);
2809
3712
  }
2810
3713
  };
2811
3714
  function hasEntries(payload) {
@@ -3303,7 +4206,7 @@ var InMemoryAgentBridge = class {
3303
4206
  });
3304
4207
  }
3305
4208
  this.inflightGuards.add(correlationId);
3306
- return new Promise((resolve4, reject) => {
4209
+ return new Promise((resolve6, reject) => {
3307
4210
  const timer = setTimeout(() => {
3308
4211
  this.inflightGuards.delete(correlationId);
3309
4212
  this.pendingRequests.delete(correlationId);
@@ -3322,7 +4225,7 @@ var InMemoryAgentBridge = class {
3322
4225
  return;
3323
4226
  }
3324
4227
  this.pendingRequests.set(correlationId, {
3325
- resolve: resolve4,
4228
+ resolve: resolve6,
3326
4229
  reject,
3327
4230
  timer
3328
4231
  });
@@ -3612,7 +4515,8 @@ ${errorDetails}`,
3612
4515
  let effectivePermission = decision.permission;
3613
4516
  const policy = this.opts.permissionPolicy;
3614
4517
  const yolo = policy.getYolo?.() === true || policy.getYoloDestructive?.() === true;
3615
- if (toolDangerousCaps.length > 0 && effectivePermission === "auto" && !yolo) {
4518
+ const authoritativeAuto = decision.source === "yolo";
4519
+ if (toolDangerousCaps.length > 0 && effectivePermission === "auto" && !yolo && !authoritativeAuto) {
3616
4520
  effectivePermission = "confirm";
3617
4521
  }
3618
4522
  if (effectivePermission === "deny") {
@@ -3634,7 +4538,7 @@ ${errorDetails}`,
3634
4538
  return { result, tool, durationMs: Date.now() - start };
3635
4539
  }
3636
4540
  } else {
3637
- const suggestedPattern = this.subjectFor(tool.name, use.input, tool.subjectKey) ?? tool.name;
4541
+ const suggestedPattern = subjectForToolInput(tool.name, use.input, tool.subjectKey) ?? tool.name;
3638
4542
  const pending = {
3639
4543
  type: "tool_confirm_pending",
3640
4544
  toolUseId: use.id,
@@ -3754,9 +4658,10 @@ ${post.additionalContext}`;
3754
4658
  });
3755
4659
  this.opts.renderer?.writeToolCall(tool.name, use.input);
3756
4660
  const output = await this.runWithTimeout(tool, use.input, ctx.signal, ctx, use.id);
3757
- const text = this.serializer.serialize(output);
4661
+ const text = this.serializer.serialize(output, { toolName: tool.name, input: use.input });
3758
4662
  const scrubbed = this.opts.secretScrubber.scrub(text);
3759
- const { text: capped, newBudget } = this.serializer.enforceCap(scrubbed, budget);
4663
+ const withArtifact = await maybePersistLargeToolOutput(tool.name, scrubbed, budget);
4664
+ const { text: capped, newBudget } = this.serializer.enforceCap(withArtifact, budget);
3760
4665
  this.opts.renderer?.writeToolResult(tool.name, capped, false);
3761
4666
  return {
3762
4667
  block: {
@@ -3781,38 +4686,27 @@ ${post.additionalContext}`;
3781
4686
  tool.timeoutMs ?? this.iterationTimeoutMs,
3782
4687
  this.maxToolTimeoutMs
3783
4688
  );
3784
- const ctrl = new AbortController();
3785
- const timer = setTimeout(() => ctrl.abort(new Error("tool timeout")), timeoutMs);
3786
- const combined = AbortSignal.any([parentSignal, ctrl.signal]);
3787
- let cleanupCalled = false;
3788
- let caught = false;
4689
+ const timeoutSignal = AbortSignal.timeout(timeoutMs);
4690
+ const combined = AbortSignal.any([parentSignal, timeoutSignal]);
4691
+ let output;
3789
4692
  try {
3790
- if (typeof tool.executeStream === "function") {
3791
- return await this.runStreamedTool(tool, input, ctx, combined, toolUseId);
3792
- }
3793
- return await tool.execute(input, ctx, { signal: combined });
4693
+ output = typeof tool.executeStream === "function" ? await this.runStreamedTool(tool, input, ctx, combined, toolUseId) : await tool.execute(input, ctx, { signal: combined });
3794
4694
  } catch (err) {
3795
- caught = true;
3796
- if (combined.aborted && typeof tool.cleanup === "function") {
3797
- cleanupCalled = true;
3798
- try {
3799
- await tool.cleanup(input, ctx);
3800
- } catch {
3801
- }
3802
- }
4695
+ if (combined.aborted) await this.runToolCleanup(tool, input, ctx);
3803
4696
  throw err;
3804
- } finally {
3805
- clearTimeout(timer);
3806
- if (combined.aborted && !caught) {
3807
- if (!cleanupCalled && typeof tool.cleanup === "function") {
3808
- try {
3809
- await tool.cleanup(input, ctx);
3810
- } catch {
3811
- }
3812
- }
3813
- const reason = combined.reason instanceof Error ? combined.reason : new Error(typeof combined.reason === "string" ? combined.reason : "aborted");
3814
- throw reason;
3815
- }
4697
+ }
4698
+ if (combined.aborted) {
4699
+ await this.runToolCleanup(tool, input, ctx);
4700
+ throw combined.reason instanceof Error ? combined.reason : new Error(typeof combined.reason === "string" ? combined.reason : "tool timeout");
4701
+ }
4702
+ return output;
4703
+ }
4704
+ /** Best-effort tool cleanup; never let it mask the original error. */
4705
+ async runToolCleanup(tool, input, ctx) {
4706
+ if (typeof tool.cleanup !== "function") return;
4707
+ try {
4708
+ await tool.cleanup(input, ctx);
4709
+ } catch {
3816
4710
  }
3817
4711
  }
3818
4712
  async runStreamedTool(tool, input, ctx, signal, toolUseId) {
@@ -3923,38 +4817,6 @@ ${excerpt}`;
3923
4817
  budgetForString(content, budget) {
3924
4818
  return Math.max(0, budget - Buffer.byteLength(content, "utf8"));
3925
4819
  }
3926
- /**
3927
- * Compute the suggestedPattern string for a tool+input pair.
3928
- * Matches the logic in DefaultPermissionPolicy so the TUI shows the
3929
- * same subject that the trust file would use.
3930
- */
3931
- subjectFor(toolName, input, subjectKey) {
3932
- if (!input || typeof input !== "object") return void 0;
3933
- const obj = input;
3934
- const globChars = /[*?[\]]/g;
3935
- const escapeGlob = (s) => s.replace(globChars, (c) => `\\${c}`);
3936
- const normalizePath = (s) => escapeGlob(s.replace(/\\/g, "/"));
3937
- if (subjectKey) {
3938
- const v = obj[subjectKey];
3939
- if (typeof v === "string") {
3940
- const isPathKey = subjectKey === "path" || subjectKey === "file" || subjectKey === "files";
3941
- return isPathKey ? normalizePath(v) : escapeGlob(v);
3942
- }
3943
- }
3944
- if (toolName === "bash" && typeof obj.command === "string") {
3945
- return escapeGlob(obj.command);
3946
- }
3947
- if (typeof obj.path === "string") {
3948
- return normalizePath(obj.path);
3949
- }
3950
- if (typeof obj.url === "string") {
3951
- return escapeGlob(obj.url);
3952
- }
3953
- if (typeof obj.name === "string") {
3954
- return escapeGlob(obj.name);
3955
- }
3956
- return void 0;
3957
- }
3958
4820
  };
3959
4821
  function clampTimeoutMs(timeoutMs, maxTimeoutMs) {
3960
4822
  const fallback = 3e5;
@@ -3981,6 +4843,25 @@ function extractMalformedRaw(input) {
3981
4843
  return String(value);
3982
4844
  }
3983
4845
  }
4846
+ var TOOL_OUTPUT_ARTIFACT_THRESHOLD_BYTES = 64 * 1024;
4847
+ async function maybePersistLargeToolOutput(toolName, content, budget) {
4848
+ const bytes = Buffer.byteLength(content, "utf8");
4849
+ if (bytes <= Math.min(TOOL_OUTPUT_ARTIFACT_THRESHOLD_BYTES, Math.max(0, budget))) {
4850
+ return content;
4851
+ }
4852
+ try {
4853
+ const dir = path6.join(wstackGlobalRoot(), "tool-output");
4854
+ await fs.mkdir(dir, { recursive: true });
4855
+ const safeTool = toolName.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 40) || "tool";
4856
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4857
+ const filePath = path6.join(dir, `${stamp}-${safeTool}-${randomUUID()}.log`);
4858
+ await fs.writeFile(filePath, content, "utf8");
4859
+ return content + `
4860
+ [full tool output: ${bytes} bytes at ${filePath}; read/grep that file selectively instead of re-running or requesting more output]`;
4861
+ } catch {
4862
+ return content;
4863
+ }
4864
+ }
3984
4865
 
3985
4866
  // src/core/conversation-state.ts
3986
4867
  var ConversationState = class {
@@ -4099,6 +4980,7 @@ var Context = class {
4099
4980
  todos = [];
4100
4981
  readFiles = /* @__PURE__ */ new Set();
4101
4982
  fileMtimes = /* @__PURE__ */ new Map();
4983
+ contextEvidence = createContextEvidenceState();
4102
4984
  systemPrompt;
4103
4985
  provider;
4104
4986
  session;
@@ -4230,11 +5112,11 @@ var Context = class {
4230
5112
  * Returns the resolved absolute path.
4231
5113
  */
4232
5114
  setWorkingDir(dir) {
4233
- const resolved = path4.isAbsolute(dir) ? path4.resolve(dir) : path4.resolve(this.projectRoot, dir);
5115
+ const resolved = path6.isAbsolute(dir) ? path6.resolve(dir) : path6.resolve(this.projectRoot, dir);
4234
5116
  if (!this.allowOutsideProjectRoot) {
4235
- const root = path4.resolve(this.projectRoot);
4236
- const rel = path4.relative(root, resolved);
4237
- if (rel.startsWith("..") || path4.isAbsolute(rel)) {
5117
+ const root = path6.resolve(this.projectRoot);
5118
+ const rel = path6.relative(root, resolved);
5119
+ if (rel.startsWith("..") || path6.isAbsolute(rel)) {
4238
5120
  throw new Error(
4239
5121
  `Working directory "${resolved}" is outside project root "${root}"`
4240
5122
  );