@wrongstack/core 0.260.0 → 0.265.1

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 (99) hide show
  1. package/dist/{agent-bridge-BbskZ7HH.d.ts → agent-bridge-DrkBxszZ.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-BNIGZx18.d.ts → agent-subagent-runner-DM2pP-B6.d.ts} +116 -12
  3. package/dist/{brain-C2yDd7Lw.d.ts → brain-BXd_61kQ.d.ts} +32 -3
  4. package/dist/{compactor-t0R_AIt_.d.ts → compactor-B8pOf45Y.d.ts} +1 -1
  5. package/dist/{config-FG6As4H5.d.ts → config-BMCj_XDs.d.ts} +86 -12
  6. package/dist/{context-JFOVvu6z.d.ts → context-MRk5PhNv.d.ts} +26 -1
  7. package/dist/coordination/index.d.ts +1737 -15
  8. package/dist/coordination/index.js +3152 -494
  9. package/dist/coordination/index.js.map +1 -1
  10. package/dist/{default-config-CXsDvOmP.d.ts → default-config-B0cj-Hry.d.ts} +11 -1
  11. package/dist/defaults/index.d.ts +28 -28
  12. package/dist/defaults/index.js +1804 -1363
  13. package/dist/defaults/index.js.map +1 -1
  14. package/dist/dispatcher-types.d-BBeXBQgS.d.ts +66 -0
  15. package/dist/execution/index.d.ts +16 -16
  16. package/dist/execution/index.js +933 -672
  17. package/dist/execution/index.js.map +1 -1
  18. package/dist/execution/prompt-enhancer.d.ts +1 -1
  19. package/dist/execution/prompt-enhancer.js +7 -1
  20. package/dist/execution/prompt-enhancer.js.map +1 -1
  21. package/dist/extension/index.d.ts +6 -6
  22. package/dist/extension/index.js.map +1 -1
  23. package/dist/{goal-preamble-B1IXJtLX.d.ts → goal-preamble-DvHDSKSe.d.ts} +26 -10
  24. package/dist/{goal-store-CPXz6Mml.d.ts → goal-store-DtLMySNb.d.ts} +1 -1
  25. package/dist/{index-CebbJB94.d.ts → index-B-ch8K9C.d.ts} +8 -8
  26. package/dist/{index-BPcg4N3M.d.ts → index-CEDeNodM.d.ts} +5 -5
  27. package/dist/index.d.ts +189 -104
  28. package/dist/index.js +24693 -21162
  29. package/dist/index.js.map +1 -1
  30. package/dist/infrastructure/index.d.ts +6 -6
  31. package/dist/infrastructure/index.js +12 -8
  32. package/dist/infrastructure/index.js.map +1 -1
  33. package/dist/kernel/index.d.ts +9 -9
  34. package/dist/kernel/index.js +7 -2
  35. package/dist/kernel/index.js.map +1 -1
  36. package/dist/{llm-selector-DXxI2tlu.d.ts → llm-selector-C0tfTCUe.d.ts} +14 -2
  37. package/dist/{mcp-servers-OwNHo43-.d.ts → mcp-servers-2x4w6Jn9.d.ts} +3 -3
  38. package/dist/models/index.d.ts +5 -5
  39. package/dist/models/index.js +80 -31
  40. package/dist/models/index.js.map +1 -1
  41. package/dist/{models-registry-Djlmq4uB.d.ts → models-registry-DmJlKuNp.d.ts} +1 -1
  42. package/dist/{multi-agent-coordinator-CEmrSCMJ.d.ts → multi-agent-coordinator-DyCkCZnU.d.ts} +2 -2
  43. package/dist/{null-fleet-bus-DT92xqgJ.d.ts → null-fleet-bus-CG9QY2aP.d.ts} +6 -6
  44. package/dist/observability/index.d.ts +2 -2
  45. package/dist/observability/index.js +8 -3
  46. package/dist/observability/index.js.map +1 -1
  47. package/dist/{parallel-eternal-engine-0SItuq5r.d.ts → parallel-eternal-engine-Jw9uhEoT.d.ts} +9 -9
  48. package/dist/{path-resolver-DKBh6Jlo.d.ts → path-resolver-Dy2ej-gE.d.ts} +3 -3
  49. package/dist/{permission-BJ7eO9Vl.d.ts → permission-B9SB45lp.d.ts} +1 -1
  50. package/dist/{permission-policy-DEXOfnpm.d.ts → permission-policy-CkjSXabK.d.ts} +2 -2
  51. package/dist/{pipeline-zflkI2dp.d.ts → pipeline-DPDxH_7m.d.ts} +59 -4
  52. package/dist/{plan-templates-BFXyRkEK.d.ts → plan-templates-CzD9GnAU.d.ts} +32 -8
  53. package/dist/{provider-runner-BC-uywtT.d.ts → provider-runner-DMa70ODu.d.ts} +3 -3
  54. package/dist/{retry-policy-Cavrzmtk.d.ts → retry-policy-CN0khdlj.d.ts} +1 -1
  55. package/dist/sdd/index.d.ts +8 -8
  56. package/dist/sdd/index.js +313 -122
  57. package/dist/sdd/index.js.map +1 -1
  58. package/dist/{secret-vault-CDvDYXWX.d.ts → secret-vault-B2yw84VT.d.ts} +43 -4
  59. package/dist/secret-vault-BAKpgFw_.d.ts +57 -0
  60. package/dist/security/index.d.ts +5 -5
  61. package/dist/security/index.js +411 -225
  62. package/dist/security/index.js.map +1 -1
  63. package/dist/{selector-B7AivHsu.d.ts → selector-CzHh_igB.d.ts} +1 -1
  64. package/dist/{session-event-bridge-BmIDxdJd.d.ts → session-event-bridge-BUI6Jf-4.d.ts} +8 -2
  65. package/dist/{session-reader-DtofsB-2.d.ts → session-reader-CMgdMSRP.d.ts} +1 -1
  66. package/dist/skills/index.js +67 -64
  67. package/dist/skills/index.js.map +1 -1
  68. package/dist/storage/index.d.ts +132 -16
  69. package/dist/storage/index.js +851 -432
  70. package/dist/storage/index.js.map +1 -1
  71. package/dist/tools/index.d.ts +57 -0
  72. package/dist/tools/index.js +411 -0
  73. package/dist/tools/index.js.map +1 -0
  74. package/dist/types/index.d.ts +21 -21
  75. package/dist/types/index.js +928 -711
  76. package/dist/types/index.js.map +1 -1
  77. package/dist/utils/error.d.ts +7 -0
  78. package/dist/utils/error.js +8 -0
  79. package/dist/utils/error.js.map +1 -0
  80. package/dist/utils/index.d.ts +8 -68
  81. package/dist/utils/index.js +20 -10
  82. package/dist/utils/index.js.map +1 -1
  83. package/dist/{wstack-paths-CJjEwPXn.d.ts → wstack-paths-hOpNLmvf.d.ts} +2 -0
  84. package/package.json +5 -1
  85. package/skills/api-design/SKILL.md +1 -1
  86. package/skills/audit-log/SKILL.md +6 -6
  87. package/skills/bug-hunter/SKILL.md +5 -5
  88. package/skills/chimera/SKILL.md +4 -4
  89. package/skills/docker-deploy/SKILL.md +1 -1
  90. package/skills/git-flow/SKILL.md +3 -3
  91. package/skills/multi-agent/SKILL.md +3 -3
  92. package/skills/node-modern/SKILL.md +1 -0
  93. package/skills/observability/SKILL.md +2 -2
  94. package/skills/output-standards/SKILL.md +51 -28
  95. package/skills/refactor-planner/SKILL.md +3 -3
  96. package/skills/security-scanner/SKILL.md +4 -3
  97. package/skills/tech-stack/SKILL.md +1 -2
  98. package/dist/package-outdated-watcher-C70ag2G9.d.ts +0 -581
  99. package/dist/secret-vault-BJDY28ev.d.ts +0 -25
@@ -1,7 +1,7 @@
1
1
  import { randomBytes, createCipheriv, createDecipheriv, randomUUID } from 'crypto';
2
- import * as fs2 from 'fs';
3
2
  import * as fs from 'fs/promises';
4
3
  import * as path4 from 'path';
4
+ import * as fs2 from 'fs';
5
5
  import * as os from 'os';
6
6
 
7
7
  // src/types/blocks.ts
@@ -20,14 +20,654 @@ function isImageBlock(b) {
20
20
  function isThinkingBlock(b) {
21
21
  return b.type === "thinking";
22
22
  }
23
-
24
- // src/types/messages.ts
25
- function asBlocks(content) {
26
- return typeof content === "string" ? [{ type: "text", text: content }] : content;
23
+
24
+ // src/types/messages.ts
25
+ function asBlocks(content) {
26
+ return typeof content === "string" ? [{ type: "text", text: content }] : content;
27
+ }
28
+ function asText(content) {
29
+ if (typeof content === "string") return content;
30
+ return content.filter((b) => b.type === "text").map((b) => b.text).join("");
31
+ }
32
+ async function atomicWrite(targetPath, content, opts = {}) {
33
+ const dir = path4.dirname(targetPath);
34
+ await fs.mkdir(dir, { recursive: true });
35
+ const tmp = path4.join(dir, `.${path4.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
36
+ try {
37
+ if (typeof content === "string") {
38
+ await fs.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
39
+ } else {
40
+ await fs.writeFile(tmp, content, { flag: "wx" });
41
+ }
42
+ try {
43
+ const fh = await fs.open(tmp, "r+");
44
+ try {
45
+ await fh.sync();
46
+ } finally {
47
+ await fh.close();
48
+ }
49
+ } catch {
50
+ }
51
+ let mode;
52
+ try {
53
+ const stat2 = await fs.stat(targetPath);
54
+ mode = stat2.mode & 511;
55
+ } catch {
56
+ mode = opts.mode;
57
+ }
58
+ if (mode !== void 0) {
59
+ await fs.chmod(tmp, mode);
60
+ }
61
+ await renameWithRetry(tmp, targetPath);
62
+ } catch (err) {
63
+ try {
64
+ await fs.unlink(tmp);
65
+ } catch {
66
+ }
67
+ throw err;
68
+ }
69
+ }
70
+ var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
71
+ async function renameWithRetry(from, to) {
72
+ if (process.platform !== "win32") {
73
+ await fs.rename(from, to);
74
+ return;
75
+ }
76
+ const delays = [10, 25, 60, 120, 250];
77
+ let lastErr;
78
+ for (let i = 0; i <= delays.length; i++) {
79
+ try {
80
+ await fs.rename(from, to);
81
+ return;
82
+ } catch (err) {
83
+ lastErr = err;
84
+ const code = err?.code;
85
+ if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
86
+ throw err;
87
+ }
88
+ await new Promise((resolve4) => setTimeout(resolve4, delays[i]));
89
+ }
90
+ }
91
+ throw lastErr;
92
+ }
93
+
94
+ // src/utils/error.ts
95
+ function toErrorMessage(err) {
96
+ return err instanceof Error ? err.message : String(err);
97
+ }
98
+
99
+ // src/utils/term.ts
100
+ var hasStdout = () => typeof process !== "undefined" && !!process.stdout;
101
+ function isStdoutTTY() {
102
+ return hasStdout() && Boolean(process.stdout.isTTY);
103
+ }
104
+ function writeTo(s, stream) {
105
+ if (!stream || typeof stream.write !== "function") return false;
106
+ {
107
+ stream.write(s);
108
+ return true;
109
+ }
110
+ }
111
+ function writeErr(s, stream = process.stderr) {
112
+ return writeTo(s, stream);
113
+ }
114
+
115
+ // src/utils/color.ts
116
+ var isColorTty = () => {
117
+ if (envFlag(process.env.NO_COLOR)) return false;
118
+ if (envFlag(process.env.FORCE_COLOR)) return true;
119
+ return isStdoutTTY();
120
+ };
121
+ function envFlag(value) {
122
+ if (value === void 0) return false;
123
+ if (value.trim() === "") return false;
124
+ return !/^(0|false|no|off)$/i.test(value.trim());
125
+ }
126
+ var COLOR = isColorTty();
127
+ var wrap = (open2, close) => (s) => COLOR ? `\x1B[${open2}m${s}\x1B[${close}m` : s;
128
+ var color = {
129
+ reset: wrap("0", "0"),
130
+ bold: wrap("1", "22"),
131
+ dim: wrap("2", "22"),
132
+ italic: wrap("3", "23"),
133
+ underline: wrap("4", "24"),
134
+ red: wrap("31", "39"),
135
+ green: wrap("32", "39"),
136
+ yellow: wrap("33", "39"),
137
+ blue: wrap("34", "39"),
138
+ magenta: wrap("35", "39"),
139
+ cyan: wrap("36", "39"),
140
+ gray: wrap("90", "39"),
141
+ amber: wrap("38;5;214", "39"),
142
+ pink: wrap("38;5;205", "39"),
143
+ bgRed: wrap("41", "49"),
144
+ bgGreen: wrap("42", "49")
145
+ };
146
+
147
+ // src/utils/string.ts
148
+ function truncate(s, max) {
149
+ return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
150
+ }
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;
158
+ }
159
+ return value;
160
+ }
161
+
162
+ // src/utils/deep-merge.ts
163
+ var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
164
+ "__proto__",
165
+ "constructor",
166
+ "prototype",
167
+ "__defineGetter__",
168
+ "__defineSetter__",
169
+ "__lookupGetter__",
170
+ "__lookupSetter__"
171
+ ]);
172
+ function isPrimitiveArray(a) {
173
+ return a.every((v) => v === null || typeof v !== "object" && typeof v !== "function");
174
+ }
175
+ function deepMerge(base, patch, options = {}) {
176
+ const {
177
+ conflictResolution = "prefer-patch",
178
+ arrayMode = "replace",
179
+ protectProto = true,
180
+ onNonPrimitiveArrayReplace
181
+ } = options;
182
+ if (typeof base !== "object" || base === null) {
183
+ return conflictResolution === "prefer-patch" ? patch : base;
184
+ }
185
+ if (typeof patch !== "object" || patch === null) {
186
+ return conflictResolution === "prefer-patch" ? patch : base;
187
+ }
188
+ if (Array.isArray(base) && Array.isArray(patch)) {
189
+ if (arrayMode === "concat-primitives" && isPrimitiveArray(base) && isPrimitiveArray(patch)) {
190
+ return [.../* @__PURE__ */ new Set([...base, ...patch])];
191
+ }
192
+ return conflictResolution === "prefer-patch" ? patch : base;
193
+ }
194
+ if (Array.isArray(base) || Array.isArray(patch)) {
195
+ return conflictResolution === "prefer-patch" ? patch : base;
196
+ }
197
+ const baseObj = base;
198
+ const patchObj = patch;
199
+ const out = { ...baseObj };
200
+ for (const [k, v] of Object.entries(patchObj)) {
201
+ if (protectProto && FORBIDDEN_PROTO_KEYS.has(k)) continue;
202
+ const existing = out[k];
203
+ if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
204
+ out[k] = deepMerge(existing, v, options);
205
+ } else if (Array.isArray(v) && Array.isArray(existing)) {
206
+ if (onNonPrimitiveArrayReplace && !isPrimitiveArray(v)) {
207
+ onNonPrimitiveArrayReplace(k, existing.length, v.length);
208
+ }
209
+ out[k] = deepMerge(existing, v, options);
210
+ } else if (v !== void 0) {
211
+ if (onNonPrimitiveArrayReplace && Array.isArray(v) && !isPrimitiveArray(v)) {
212
+ const existingLen = Array.isArray(existing) ? existing.length : 0;
213
+ onNonPrimitiveArrayReplace(k, existingLen, v.length);
214
+ }
215
+ out[k] = v;
216
+ }
217
+ }
218
+ return out;
219
+ }
220
+
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
+ }
238
+ }
239
+ return String(value);
240
+ }
241
+ function enforceCap(text, remainingBudget) {
242
+ if (remainingBudget <= 0) {
243
+ return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
244
+ }
245
+ const textBytes = Buffer.byteLength(text, "utf8");
246
+ if (textBytes <= remainingBudget) {
247
+ return { text, newBudget: remainingBudget - textBytes };
248
+ }
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 };
256
+ }
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
+ }
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);
286
+ }
287
+ }
288
+ const estimate = compute(key);
289
+ ESTIMATE_CACHE.set(key, estimate);
290
+ return estimate;
291
+ }
292
+ function estimateToolInputTokens(input) {
293
+ if (typeof input === "string") return RoughTokenEstimate(input);
294
+ if (input === null || typeof input !== "object") {
295
+ return RoughTokenEstimate(String(input));
296
+ }
297
+ return getCachedEstimate(JSON.stringify(input), (key) => RoughTokenEstimate(key));
298
+ }
299
+ function estimateToolResultTokens(content) {
300
+ if (typeof content === "string") return RoughTokenEstimate(content);
301
+ return getCachedEstimate(JSON.stringify(content), (key) => RoughTokenEstimate(key));
302
+ }
303
+ function estimateTextTokens(text) {
304
+ return RoughTokenEstimate(text);
305
+ }
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;
316
+ }
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);
325
+ }
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));
332
+ }
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
+ }
361
+ }
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
+ }
371
+ }
372
+ let toolsTokens = 0;
373
+ for (const t of tools) {
374
+ toolsTokens += estimateToolDefTokens(t);
375
+ }
376
+ const total = messagesTokens + systemTokens + toolsTokens;
377
+ calState(calibrationKey).prevEst = total;
378
+ return {
379
+ messages: messagesTokens,
380
+ systemPrompt: systemTokens,
381
+ tools: toolsTokens,
382
+ total
383
+ };
384
+ }
385
+
386
+ // src/utils/message-invariants.ts
387
+ function repairToolUseAdjacency(messages) {
388
+ const removedToolUses = [];
389
+ const removedToolResults = [];
390
+ let removedMessages = 0;
391
+ let changed = false;
392
+ const out = [];
393
+ for (let i = 0; i < messages.length; i++) {
394
+ const original = expectDefined(messages[i]);
395
+ let msg = original;
396
+ if (hasToolUse(msg)) {
397
+ const nextIds = toolResultIds(messages[i + 1]);
398
+ const filtered = mapContent(msg, (blocks) => {
399
+ const next = [];
400
+ for (const block of blocks) {
401
+ if (block.type === "tool_use" && !nextIds.has(block.id)) {
402
+ removedToolUses.push(block.id);
403
+ changed = true;
404
+ continue;
405
+ }
406
+ next.push(block);
407
+ }
408
+ return next;
409
+ });
410
+ msg = filtered ?? msg;
411
+ }
412
+ if (hasToolResult(msg)) {
413
+ const allowed = toolUseIds(out[out.length - 1]);
414
+ const filtered = mapContent(msg, (blocks) => {
415
+ const next = [];
416
+ for (const block of blocks) {
417
+ if (block.type === "tool_result" && !allowed.has(block.tool_use_id)) {
418
+ removedToolResults.push(block.tool_use_id);
419
+ changed = true;
420
+ continue;
421
+ }
422
+ next.push(block);
423
+ }
424
+ return next;
425
+ });
426
+ msg = filtered ?? msg;
427
+ }
428
+ if (isEmptyMessage(msg)) {
429
+ removedMessages++;
430
+ changed = true;
431
+ continue;
432
+ }
433
+ out.push(msg);
434
+ }
435
+ return {
436
+ messages: changed ? out : messages,
437
+ report: { changed, removedToolUses, removedToolResults, removedMessages }
438
+ };
439
+ }
440
+ function hasToolUse(msg) {
441
+ return contentBlocks(msg).some((b) => b.type === "tool_use");
442
+ }
443
+ function hasToolResult(msg) {
444
+ return contentBlocks(msg).some((b) => b.type === "tool_result");
445
+ }
446
+ function toolUseIds(msg) {
447
+ const ids = /* @__PURE__ */ new Set();
448
+ if (!msg || msg.role !== "assistant") return ids;
449
+ for (const block of contentBlocks(msg)) {
450
+ if (block.type === "tool_use") ids.add(block.id);
451
+ }
452
+ return ids;
453
+ }
454
+ function toolResultIds(msg) {
455
+ const ids = /* @__PURE__ */ new Set();
456
+ if (!msg || msg.role !== "user") return ids;
457
+ for (const block of contentBlocks(msg)) {
458
+ if (block.type === "tool_result") ids.add(block.tool_use_id);
459
+ }
460
+ return ids;
461
+ }
462
+ function contentBlocks(msg) {
463
+ return msg && Array.isArray(msg.content) ? msg.content : [];
464
+ }
465
+ function mapContent(msg, fn) {
466
+ if (!Array.isArray(msg.content)) return msg;
467
+ const next = fn(msg.content);
468
+ if (next.length === msg.content.length && next.every((b, idx) => b === msg.content[idx])) {
469
+ return msg;
470
+ }
471
+ return { ...msg, content: next };
472
+ }
473
+ function isEmptyMessage(msg) {
474
+ if (typeof msg.content === "string") return msg.content.trim().length === 0;
475
+ return msg.content.length === 0;
476
+ }
477
+
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 };
483
+ }
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;
492
+ }
493
+ }
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;
501
+ }
502
+ }
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" });
508
+ }
509
+ }
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
+ }
515
+ }
516
+ }
517
+ }
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
+ }
523
+ }
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
+ }
543
+ }
544
+ function isPlainObject(v) {
545
+ return typeof v === "object" && v !== null && !Array.isArray(v);
546
+ }
547
+ function describeType(v) {
548
+ if (v === null) return "null";
549
+ if (Array.isArray(v)) return "array";
550
+ return typeof v;
551
+ }
552
+ function joinPath(parent, key) {
553
+ if (!parent) return key;
554
+ return `${parent}.${key}`;
555
+ }
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;
572
+ }
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
+ }
600
+ 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
+ };
607
+ }
608
+ }
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;
621
+ }
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
+ };
644
+ }
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;
657
+ }
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 };
27
664
  }
28
- function asText(content) {
29
- if (typeof content === "string") return content;
30
- return content.filter((b) => b.type === "text").map((b) => b.text).join("");
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;
31
671
  }
32
672
 
33
673
  // src/types/errors.ts
@@ -175,7 +815,7 @@ var AgentError = class extends WrongStackError {
175
815
  };
176
816
  function toWrongStackError(err, code = ERROR_CODES.AGENT_RUN_FAILED) {
177
817
  if (err instanceof WrongStackError) return err;
178
- const message = err instanceof Error ? err.message : String(err);
818
+ const message = toErrorMessage(err);
179
819
  return new AgentError({
180
820
  message,
181
821
  code: code === "UNKNOWN" ? ERROR_CODES.AGENT_RUN_FAILED : code,
@@ -253,11 +893,6 @@ function isSddError(err) {
253
893
  return err instanceof SddError;
254
894
  }
255
895
 
256
- // src/utils/string.ts
257
- function truncate(s, max) {
258
- return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
259
- }
260
-
261
896
  // src/types/provider.ts
262
897
  var ProviderError = class extends WrongStackError {
263
898
  status;
@@ -360,6 +995,20 @@ function providerStatusToCode(status, type) {
360
995
  return ERROR_CODES.PROVIDER_INVALID_REQUEST;
361
996
  }
362
997
 
998
+ // src/types/config.ts
999
+ function normalizeTokenSavingTier(val) {
1000
+ if (val === void 0) return "off";
1001
+ if (typeof val === "boolean") return val ? "medium" : "off";
1002
+ const validTiers = /* @__PURE__ */ new Set([
1003
+ "off",
1004
+ "minimal",
1005
+ "light",
1006
+ "medium",
1007
+ "aggressive"
1008
+ ]);
1009
+ return validTiers.has(val) ? val : "off";
1010
+ }
1011
+
363
1012
  // src/types/default-config.ts
364
1013
  var DEFAULT_TOOLS_CONFIG = Object.freeze({
365
1014
  defaultExecutionStrategy: "smart",
@@ -367,7 +1016,8 @@ var DEFAULT_TOOLS_CONFIG = Object.freeze({
367
1016
  iterationTimeoutMs: 3e5,
368
1017
  sessionTimeoutMs: 18e5,
369
1018
  perIterationOutputCapBytes: 1e5,
370
- autoExtendLimit: true
1019
+ autoExtendLimit: true,
1020
+ restrictToProjectRoot: false
371
1021
  });
372
1022
  var DEFAULT_CONTEXT_CONFIG = Object.freeze({
373
1023
  preserveK: 10,
@@ -376,6 +1026,10 @@ var DEFAULT_CONTEXT_CONFIG = Object.freeze({
376
1026
  var DEFAULT_AUTONOMY_CONFIG = Object.freeze({
377
1027
  autoProceedDelayMs: 45e3
378
1028
  });
1029
+ var DEFAULT_CIRCUIT_BREAKER_CONFIG = Object.freeze({
1030
+ enabled: false,
1031
+ autoKillResetMs: 6e4
1032
+ });
379
1033
  var DEFAULT_SESSION_LOGGING_CONFIG = Object.freeze({
380
1034
  auditLevel: "standard",
381
1035
  sampling: {
@@ -387,126 +1041,9 @@ var DEFAULT_SESSION_LOGGING_CONFIG = Object.freeze({
387
1041
  var DEFAULT_SESSION_PRUNE_DAYS = 30;
388
1042
 
389
1043
  // src/types/secret-vault.ts
390
- var ENCRYPTED_PREFIX = "enc:v1:";
391
- async function atomicWrite(targetPath, content, opts = {}) {
392
- const dir = path4.dirname(targetPath);
393
- await fs.mkdir(dir, { recursive: true });
394
- const tmp = path4.join(dir, `.${path4.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
395
- try {
396
- if (typeof content === "string") {
397
- await fs.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
398
- } else {
399
- await fs.writeFile(tmp, content, { flag: "wx" });
400
- }
401
- try {
402
- const fh = await fs.open(tmp, "r+");
403
- try {
404
- await fh.sync();
405
- } finally {
406
- await fh.close();
407
- }
408
- } catch {
409
- }
410
- let mode;
411
- try {
412
- const stat2 = await fs.stat(targetPath);
413
- mode = stat2.mode & 511;
414
- } catch {
415
- mode = opts.mode;
416
- }
417
- if (mode !== void 0) {
418
- await fs.chmod(tmp, mode);
419
- }
420
- await renameWithRetry(tmp, targetPath);
421
- } catch (err) {
422
- try {
423
- await fs.unlink(tmp);
424
- } catch {
425
- }
426
- throw err;
427
- }
428
- }
429
- var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
430
- async function renameWithRetry(from, to) {
431
- if (process.platform !== "win32") {
432
- await fs.rename(from, to);
433
- return;
434
- }
435
- const delays = [10, 25, 60, 120, 250];
436
- let lastErr;
437
- for (let i = 0; i <= delays.length; i++) {
438
- try {
439
- await fs.rename(from, to);
440
- return;
441
- } catch (err) {
442
- lastErr = err;
443
- const code = err?.code;
444
- if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
445
- throw err;
446
- }
447
- await new Promise((resolve4) => setTimeout(resolve4, delays[i]));
448
- }
449
- }
450
- throw lastErr;
451
- }
452
-
453
- // src/utils/deep-merge.ts
454
- var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
455
- "__proto__",
456
- "constructor",
457
- "prototype",
458
- "__defineGetter__",
459
- "__defineSetter__",
460
- "__lookupGetter__",
461
- "__lookupSetter__"
462
- ]);
463
- function isPrimitiveArray(a) {
464
- return a.every((v) => v === null || typeof v !== "object" && typeof v !== "function");
465
- }
466
- function deepMerge(base, patch, options = {}) {
467
- const {
468
- conflictResolution = "prefer-patch",
469
- arrayMode = "replace",
470
- protectProto = true,
471
- onNonPrimitiveArrayReplace
472
- } = options;
473
- if (typeof base !== "object" || base === null) {
474
- return conflictResolution === "prefer-patch" ? patch : base;
475
- }
476
- if (typeof patch !== "object" || patch === null) {
477
- return conflictResolution === "prefer-patch" ? patch : base;
478
- }
479
- if (Array.isArray(base) && Array.isArray(patch)) {
480
- if (arrayMode === "concat-primitives" && isPrimitiveArray(base) && isPrimitiveArray(patch)) {
481
- return [.../* @__PURE__ */ new Set([...base, ...patch])];
482
- }
483
- return conflictResolution === "prefer-patch" ? patch : base;
484
- }
485
- if (Array.isArray(base) || Array.isArray(patch)) {
486
- return conflictResolution === "prefer-patch" ? patch : base;
487
- }
488
- const baseObj = base;
489
- const patchObj = patch;
490
- const out = { ...baseObj };
491
- for (const [k, v] of Object.entries(patchObj)) {
492
- if (protectProto && FORBIDDEN_PROTO_KEYS.has(k)) continue;
493
- const existing = out[k];
494
- if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
495
- out[k] = deepMerge(existing, v, options);
496
- } else if (Array.isArray(v) && Array.isArray(existing)) {
497
- if (onNonPrimitiveArrayReplace && !isPrimitiveArray(v)) {
498
- onNonPrimitiveArrayReplace(k, existing.length, v.length);
499
- }
500
- out[k] = deepMerge(existing, v, options);
501
- } else if (v !== void 0) {
502
- if (onNonPrimitiveArrayReplace && Array.isArray(v) && !isPrimitiveArray(v)) {
503
- const existingLen = Array.isArray(existing) ? existing.length : 0;
504
- onNonPrimitiveArrayReplace(k, existingLen, v.length);
505
- }
506
- out[k] = v;
507
- }
508
- }
509
- return out;
1044
+ var ENCRYPTED_PREFIX_PATTERN = /^enc:v(\d+):/;
1045
+ function encryptedPrefixForVersion(version) {
1046
+ return `enc:v${version}:`;
510
1047
  }
511
1048
 
512
1049
  // src/security/secret-vault.ts
@@ -515,6 +1052,8 @@ var IV_BYTES = 12;
515
1052
  var TAG_BYTES = 16;
516
1053
  var ALGO = "aes-256-gcm";
517
1054
  var KEY_FILE_MODE = 384;
1055
+ var KEY_FILE_MAGIC = Buffer.from("WSKV", "ascii");
1056
+ var VERSIONED_KEY_FILE_SIZE = KEY_FILE_MAGIC.length + 1 + KEY_BYTES;
518
1057
  function checkKeyFilePermissions(keyFile) {
519
1058
  if (process.platform === "win32") return;
520
1059
  try {
@@ -537,11 +1076,17 @@ function checkKeyFilePermissions(keyFile) {
537
1076
  var DefaultSecretVault = class {
538
1077
  keyFile;
539
1078
  key;
1079
+ _keyVersion = 1;
540
1080
  constructor(opts) {
541
1081
  this.keyFile = opts.keyFile;
542
1082
  }
1083
+ /** Current key version. Starts at 1; incremented by rotateKey(). */
1084
+ get keyVersion() {
1085
+ if (!this.key) this.loadOrCreateKey();
1086
+ return this._keyVersion;
1087
+ }
543
1088
  isEncrypted(value) {
544
- return typeof value === "string" && value.startsWith(ENCRYPTED_PREFIX);
1089
+ return typeof value === "string" && ENCRYPTED_PREFIX_PATTERN.test(value);
545
1090
  }
546
1091
  encrypt(plaintext) {
547
1092
  if (this.isEncrypted(plaintext)) return plaintext;
@@ -550,11 +1095,20 @@ var DefaultSecretVault = class {
550
1095
  const cipher = createCipheriv(ALGO, key, iv);
551
1096
  const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
552
1097
  const tag = cipher.getAuthTag();
553
- return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${tag.toString("base64")}:${ct.toString("base64")}`;
1098
+ const prefix = encryptedPrefixForVersion(this._keyVersion);
1099
+ return `${prefix}${iv.toString("base64")}:${tag.toString("base64")}:${ct.toString("base64")}`;
554
1100
  }
555
1101
  decrypt(value) {
556
1102
  if (!this.isEncrypted(value)) return value;
557
- const rest = value.slice(ENCRYPTED_PREFIX.length);
1103
+ const prefixMatch = value.match(ENCRYPTED_PREFIX_PATTERN);
1104
+ if (!prefixMatch) {
1105
+ throw new ConfigError({
1106
+ message: "SecretVault: malformed encrypted value",
1107
+ code: ERROR_CODES.CONFIG_PARSE_FAILED,
1108
+ context: { field: "encrypted_value" }
1109
+ });
1110
+ }
1111
+ const rest = value.slice(prefixMatch[0].length);
558
1112
  const parts = rest.split(":");
559
1113
  if (parts.length !== 3) {
560
1114
  throw new ConfigError({
@@ -583,20 +1137,64 @@ var DefaultSecretVault = class {
583
1137
  const pt = Buffer.concat([decipher.update(ct), decipher.final()]);
584
1138
  return pt.toString("utf8");
585
1139
  }
1140
+ /**
1141
+ * Generate a new encryption key, write it to disk, and increment the key version.
1142
+ * After rotation, encrypt() emits the new version prefix (e.g. enc:v2:).
1143
+ * The caller must re-encrypt existing config values (see rotateConfigKeys()).
1144
+ */
1145
+ rotateKey() {
1146
+ const oldVersion = this._keyVersion;
1147
+ const newKey = randomBytes(KEY_BYTES);
1148
+ const newVersion = oldVersion + 1;
1149
+ const keyFileBuf = Buffer.alloc(VERSIONED_KEY_FILE_SIZE);
1150
+ KEY_FILE_MAGIC.copy(keyFileBuf, 0);
1151
+ keyFileBuf[KEY_FILE_MAGIC.length] = newVersion;
1152
+ newKey.copy(keyFileBuf, KEY_FILE_MAGIC.length + 1);
1153
+ fs2.mkdirSync(path4.dirname(this.keyFile), { recursive: true });
1154
+ fs2.writeFileSync(this.keyFile, keyFileBuf, { mode: 384 });
1155
+ checkKeyFilePermissions(this.keyFile);
1156
+ this.key = newKey;
1157
+ this._keyVersion = newVersion;
1158
+ return { oldVersion, newVersion };
1159
+ }
586
1160
  loadOrCreateKey() {
587
1161
  if (this.key) return this.key;
588
1162
  try {
589
1163
  const buf = fs2.readFileSync(this.keyFile);
590
- if (buf.length !== KEY_BYTES) {
591
- throw new ConfigError({
592
- message: `SecretVault: key file ${this.keyFile} is ${buf.length} bytes (expected ${KEY_BYTES}). Remove it manually to generate a new key.`,
593
- code: ERROR_CODES.CONFIG_INVALID,
594
- context: { keyFile: this.keyFile, expectedBytes: KEY_BYTES, actualBytes: buf.length }
595
- });
1164
+ if (buf.length === KEY_BYTES) {
1165
+ this.key = buf;
1166
+ this._keyVersion = 1;
1167
+ checkKeyFilePermissions(this.keyFile);
1168
+ return this.key;
596
1169
  }
597
- this.key = buf;
598
- checkKeyFilePermissions(this.keyFile);
599
- return this.key;
1170
+ if (buf.length === VERSIONED_KEY_FILE_SIZE) {
1171
+ const magic = buf.subarray(0, KEY_FILE_MAGIC.length);
1172
+ if (!magic.equals(KEY_FILE_MAGIC)) {
1173
+ throw new ConfigError({
1174
+ message: `SecretVault: key file ${this.keyFile} has invalid magic header`,
1175
+ code: ERROR_CODES.CONFIG_INVALID,
1176
+ context: { keyFile: this.keyFile }
1177
+ });
1178
+ }
1179
+ const version = buf[KEY_FILE_MAGIC.length];
1180
+ const key2 = buf.subarray(KEY_FILE_MAGIC.length + 1);
1181
+ if (key2.length !== KEY_BYTES) {
1182
+ throw new ConfigError({
1183
+ message: `SecretVault: key file ${this.keyFile} has wrong key size (${key2.length} bytes, expected ${KEY_BYTES})`,
1184
+ code: ERROR_CODES.CONFIG_INVALID,
1185
+ context: { keyFile: this.keyFile, expectedBytes: KEY_BYTES, actualBytes: key2.length }
1186
+ });
1187
+ }
1188
+ this.key = Buffer.from(key2);
1189
+ this._keyVersion = version;
1190
+ checkKeyFilePermissions(this.keyFile);
1191
+ return this.key;
1192
+ }
1193
+ throw new ConfigError({
1194
+ message: `SecretVault: key file ${this.keyFile} is ${buf.length} bytes (expected ${KEY_BYTES} for v1 or ${VERSIONED_KEY_FILE_SIZE} for v2+). Remove it manually to generate a new key.`,
1195
+ code: ERROR_CODES.CONFIG_INVALID,
1196
+ context: { keyFile: this.keyFile, expectedBytes: KEY_BYTES, actualBytes: buf.length }
1197
+ });
600
1198
  } catch (err) {
601
1199
  if (err.code !== "ENOENT") throw err;
602
1200
  }
@@ -607,24 +1205,42 @@ var DefaultSecretVault = class {
607
1205
  } catch (err) {
608
1206
  if (err.code !== "EEXIST") throw err;
609
1207
  const buf = fs2.readFileSync(this.keyFile);
610
- if (buf.length !== KEY_BYTES) {
611
- throw new ConfigError({
612
- message: `SecretVault: key file ${this.keyFile} is ${buf.length} bytes (expected ${KEY_BYTES}). Remove it manually to generate a new key.`,
613
- code: ERROR_CODES.CONFIG_INVALID,
614
- context: { keyFile: this.keyFile, expectedBytes: KEY_BYTES, actualBytes: buf.length }
615
- });
1208
+ if (buf.length === KEY_BYTES) {
1209
+ this.key = buf;
1210
+ this._keyVersion = 1;
1211
+ checkKeyFilePermissions(this.keyFile);
1212
+ return this.key;
616
1213
  }
617
- this.key = buf;
618
- checkKeyFilePermissions(this.keyFile);
619
- return this.key;
1214
+ if (buf.length === VERSIONED_KEY_FILE_SIZE) {
1215
+ const magic = buf.subarray(0, KEY_FILE_MAGIC.length);
1216
+ if (!magic.equals(KEY_FILE_MAGIC)) {
1217
+ throw new ConfigError({
1218
+ message: `SecretVault: key file ${this.keyFile} has invalid magic header`,
1219
+ code: ERROR_CODES.CONFIG_INVALID,
1220
+ context: { keyFile: this.keyFile }
1221
+ });
1222
+ }
1223
+ const version = buf[KEY_FILE_MAGIC.length];
1224
+ const winnerKey = buf.subarray(KEY_FILE_MAGIC.length + 1);
1225
+ this.key = Buffer.from(winnerKey);
1226
+ this._keyVersion = version;
1227
+ checkKeyFilePermissions(this.keyFile);
1228
+ return this.key;
1229
+ }
1230
+ throw new ConfigError({
1231
+ message: `SecretVault: key file ${this.keyFile} is ${buf.length} bytes (expected ${KEY_BYTES} for v1 or ${VERSIONED_KEY_FILE_SIZE} for v2+). Remove it manually to generate a new key.`,
1232
+ code: ERROR_CODES.CONFIG_INVALID,
1233
+ context: { keyFile: this.keyFile, expectedBytes: KEY_BYTES, actualBytes: buf.length }
1234
+ });
620
1235
  }
621
1236
  this.key = key;
1237
+ this._keyVersion = 1;
622
1238
  return key;
623
1239
  }
624
1240
  };
625
1241
  function decryptConfigSecrets(cfg, vault, opts) {
626
1242
  const warn = opts?.warn ?? ((msg) => console.warn(msg));
627
- return walk(cfg, vault, (v, key) => {
1243
+ return walk2(cfg, vault, (v, key) => {
628
1244
  try {
629
1245
  return vault.decrypt(v);
630
1246
  } catch (err) {
@@ -636,20 +1252,20 @@ function decryptConfigSecrets(cfg, vault, opts) {
636
1252
  });
637
1253
  }
638
1254
  function encryptConfigSecrets(cfg, vault, _opts) {
639
- return walk(cfg, vault, (v) => vault.encrypt(v));
1255
+ return walk2(cfg, vault, (v) => vault.encrypt(v));
640
1256
  }
641
- function walk(node, vault, transform) {
1257
+ function walk2(node, vault, transform) {
642
1258
  if (node === null || node === void 0) return node;
643
1259
  if (typeof node !== "object") return node;
644
1260
  if (Array.isArray(node)) {
645
- return node.map((item) => walk(item, vault, transform));
1261
+ return node.map((item) => walk2(item, vault, transform));
646
1262
  }
647
1263
  const out = /* @__PURE__ */ Object.create(null);
648
1264
  for (const [k, v] of Object.entries(node)) {
649
1265
  if (typeof v === "string" && isSecretField(k)) {
650
1266
  out[k] = transform(v, k);
651
1267
  } else if (typeof v === "object" && v !== null) {
652
- out[k] = walk(v, vault, transform);
1268
+ out[k] = walk2(v, vault, transform);
653
1269
  } else {
654
1270
  out[k] = v;
655
1271
  }
@@ -699,6 +1315,80 @@ async function migratePlaintextSecrets(configPath, vault, logger) {
699
1315
  );
700
1316
  return { migrated: counter.n, file: configPath };
701
1317
  }
1318
+ async function rotateConfigKeys(configPath, vault, logger) {
1319
+ const log = logger?.info ?? (() => {
1320
+ });
1321
+ const warn = logger?.warn ?? ((msg) => console.warn(msg));
1322
+ let raw;
1323
+ try {
1324
+ raw = await fs.readFile(configPath, "utf8");
1325
+ } catch {
1326
+ const { oldVersion: oldVersion2, newVersion: newVersion2 } = vault.rotateKey();
1327
+ log(`[secret-vault] Key rotated (v${oldVersion2} \u2192 v${newVersion2}) \u2014 no config file to re-encrypt`);
1328
+ return { rotated: 0, oldVersion: oldVersion2, newVersion: newVersion2, file: configPath };
1329
+ }
1330
+ let parsed;
1331
+ try {
1332
+ parsed = JSON.parse(raw);
1333
+ } catch {
1334
+ warn(`[secret-vault] Config file ${configPath} is not valid JSON \u2014 skipping rotation`);
1335
+ return { rotated: 0, oldVersion: vault.keyVersion, newVersion: vault.keyVersion, file: configPath };
1336
+ }
1337
+ const counter = { n: 0 };
1338
+ const decrypted = walkDecryptCount(parsed, vault, counter);
1339
+ if (counter.n === 0) {
1340
+ const { oldVersion: oldVersion2, newVersion: newVersion2 } = vault.rotateKey();
1341
+ log(`[secret-vault] Key rotated (v${oldVersion2} \u2192 v${newVersion2}) \u2014 no encrypted fields to re-encrypt`);
1342
+ return { rotated: 0, oldVersion: oldVersion2, newVersion: newVersion2, file: configPath };
1343
+ }
1344
+ const { oldVersion, newVersion } = vault.rotateKey();
1345
+ const reencrypted = walkReencrypt(decrypted, vault);
1346
+ await atomicWrite(configPath, JSON.stringify(reencrypted, null, 2), { mode: 384 });
1347
+ await restrictFilePermissions(configPath, { warn });
1348
+ log(`[secret-vault] Key rotated (v${oldVersion} \u2192 v${newVersion}) \u2014 re-encrypted ${counter.n} field(s)`);
1349
+ return { rotated: counter.n, oldVersion, newVersion, file: configPath };
1350
+ }
1351
+ function walkDecryptCount(node, vault, counter) {
1352
+ if (node === null || node === void 0) return node;
1353
+ if (typeof node !== "object") return node;
1354
+ if (Array.isArray(node)) {
1355
+ return node.map((item) => walkDecryptCount(item, vault, counter));
1356
+ }
1357
+ const out = /* @__PURE__ */ Object.create(null);
1358
+ for (const [k, v] of Object.entries(node)) {
1359
+ if (typeof v === "string" && vault.isEncrypted(v)) {
1360
+ try {
1361
+ out[k] = vault.decrypt(v);
1362
+ counter.n++;
1363
+ } catch {
1364
+ out[k] = v;
1365
+ }
1366
+ } else if (typeof v === "object" && v !== null) {
1367
+ out[k] = walkDecryptCount(v, vault, counter);
1368
+ } else {
1369
+ out[k] = v;
1370
+ }
1371
+ }
1372
+ return out;
1373
+ }
1374
+ function walkReencrypt(node, vault) {
1375
+ if (node === null || node === void 0) return node;
1376
+ if (typeof node !== "object") return node;
1377
+ if (Array.isArray(node)) {
1378
+ return node.map((item) => walkReencrypt(item, vault));
1379
+ }
1380
+ const out = /* @__PURE__ */ Object.create(null);
1381
+ for (const [k, v] of Object.entries(node)) {
1382
+ if (typeof v === "string" && isSecretField(k) && v.length > 0 && !vault.isEncrypted(v)) {
1383
+ out[k] = vault.encrypt(v);
1384
+ } else if (typeof v === "object" && v !== null) {
1385
+ out[k] = walkReencrypt(v, vault);
1386
+ } else {
1387
+ out[k] = v;
1388
+ }
1389
+ }
1390
+ return out;
1391
+ }
702
1392
  async function restrictFilePermissions(filePath, opts) {
703
1393
  const warn = opts?.warn ?? ((msg) => console.warn(msg));
704
1394
  if (process.platform === "win32") {
@@ -739,69 +1429,19 @@ function walkCount(node, vault, counter) {
739
1429
  if (Array.isArray(node)) {
740
1430
  return node.map((item) => walkCount(item, vault, counter));
741
1431
  }
742
- const out = /* @__PURE__ */ Object.create(null);
743
- for (const [k, v] of Object.entries(node)) {
744
- if (typeof v === "string" && isSecretField(k) && !vault.isEncrypted(v) && v.length > 0) {
745
- out[k] = vault.encrypt(v);
746
- counter.n++;
747
- } else if (typeof v === "object" && v !== null) {
748
- out[k] = walkCount(v, vault, counter);
749
- } else {
750
- out[k] = v;
751
- }
752
- }
753
- return out;
754
- }
755
-
756
- // src/utils/term.ts
757
- var hasStdout = () => typeof process !== "undefined" && !!process.stdout;
758
- function isStdoutTTY() {
759
- return hasStdout() && Boolean(process.stdout.isTTY);
760
- }
761
- function writeTo(s, stream) {
762
- if (!stream || typeof stream.write !== "function") return false;
763
- {
764
- stream.write(s);
765
- return true;
766
- }
767
- }
768
- function writeErr(s, stream = process.stderr) {
769
- return writeTo(s, stream);
770
- }
771
-
772
- // src/utils/color.ts
773
- var isColorTty = () => {
774
- if (envFlag(process.env.NO_COLOR)) return false;
775
- if (envFlag(process.env.FORCE_COLOR)) return true;
776
- return isStdoutTTY();
777
- };
778
- function envFlag(value) {
779
- if (value === void 0) return false;
780
- if (value.trim() === "") return false;
781
- return !/^(0|false|no|off)$/i.test(value.trim());
1432
+ const out = /* @__PURE__ */ Object.create(null);
1433
+ for (const [k, v] of Object.entries(node)) {
1434
+ if (typeof v === "string" && isSecretField(k) && !vault.isEncrypted(v) && v.length > 0) {
1435
+ out[k] = vault.encrypt(v);
1436
+ counter.n++;
1437
+ } else if (typeof v === "object" && v !== null) {
1438
+ out[k] = walkCount(v, vault, counter);
1439
+ } else {
1440
+ out[k] = v;
1441
+ }
1442
+ }
1443
+ return out;
782
1444
  }
783
- var COLOR = isColorTty();
784
- var wrap = (open2, close) => (s) => COLOR ? `\x1B[${open2}m${s}\x1B[${close}m` : s;
785
- var color = {
786
- reset: wrap("0", "0"),
787
- bold: wrap("1", "22"),
788
- dim: wrap("2", "22"),
789
- italic: wrap("3", "23"),
790
- underline: wrap("4", "24"),
791
- red: wrap("31", "39"),
792
- green: wrap("32", "39"),
793
- yellow: wrap("33", "39"),
794
- blue: wrap("34", "39"),
795
- magenta: wrap("35", "39"),
796
- cyan: wrap("36", "39"),
797
- gray: wrap("90", "39"),
798
- amber: wrap("38;5;214", "39"),
799
- pink: wrap("38;5;205", "39"),
800
- bgRed: wrap("41", "49"),
801
- bgGreen: wrap("42", "49")
802
- };
803
-
804
- // src/infrastructure/logger.ts
805
1445
  var LEVEL_RANK = {
806
1446
  error: 0,
807
1447
  warn: 1,
@@ -1098,234 +1738,12 @@ var MEMORY_TYPE_LABELS = {
1098
1738
  anti_pattern: "Anti-pattern"
1099
1739
  };
1100
1740
 
1101
- // src/utils/token-estimate.ts
1102
- var RoughTokenEstimate = (text, charsPerToken = 3.5) => Math.max(1, Math.ceil(text.length / charsPerToken));
1103
- var CALIBRATION_GLOBAL_KEY = "__global__";
1104
- var _cals = /* @__PURE__ */ new Map();
1105
- function calState(key) {
1106
- let state = _cals.get(key);
1107
- if (!state) {
1108
- state = { ratio: 1, count: 0, prevEst: 0 };
1109
- _cals.set(key, state);
1110
- }
1111
- return state;
1112
- }
1113
- var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
1114
- var ESTIMATE_CACHE_MAX_SIZE = 1e4;
1115
- function getCachedEstimate(key, compute) {
1116
- const existing = ESTIMATE_CACHE.get(key);
1117
- if (existing !== void 0) return existing;
1118
- if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
1119
- let evicted = 0;
1120
- const maxEvict = Math.floor(ESTIMATE_CACHE_MAX_SIZE / 4);
1121
- for (const k of ESTIMATE_CACHE.keys()) {
1122
- if (evicted >= maxEvict) break;
1123
- ESTIMATE_CACHE.delete(k);
1124
- evicted++;
1125
- }
1126
- }
1127
- const estimate = compute(key);
1128
- ESTIMATE_CACHE.set(key, estimate);
1129
- return estimate;
1130
- }
1131
- function estimateToolInputTokens(input) {
1132
- if (typeof input === "string") return RoughTokenEstimate(input);
1133
- if (input === null || typeof input !== "object") {
1134
- return RoughTokenEstimate(String(input));
1135
- }
1136
- return getCachedEstimate(JSON.stringify(input), (key) => RoughTokenEstimate(key));
1137
- }
1138
- function estimateToolResultTokens(content) {
1139
- if (typeof content === "string") return RoughTokenEstimate(content);
1140
- return getCachedEstimate(JSON.stringify(content), (key) => RoughTokenEstimate(key));
1141
- }
1142
- function estimateTextTokens(text) {
1143
- return RoughTokenEstimate(text);
1144
- }
1145
- function computeMessageTokens(msg) {
1146
- if (typeof msg.content === "string") return estimateTextTokens(msg.content);
1147
- let total = 0;
1148
- for (const b of msg.content) {
1149
- if (b.type === "text") total += estimateTextTokens(b.text);
1150
- else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
1151
- else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
1152
- else total += RoughTokenEstimate(JSON.stringify(b));
1153
- }
1154
- return total;
1155
- }
1156
- function estimateMessageTokens(messages) {
1157
- let total = 0;
1158
- for (const m of messages) {
1159
- if (typeof m._estTokens === "number" && m._estTokens > 0) {
1160
- total += m._estTokens;
1161
- continue;
1162
- }
1163
- total += computeMessageTokens(m);
1164
- }
1165
- return total;
1166
- }
1167
- function estimateToolDefTokens(tool) {
1168
- const cached = tool._estDefTokens;
1169
- if (typeof cached === "number" && cached > 0) return cached;
1170
- return RoughTokenEstimate(tool.name) + RoughTokenEstimate(tool.description ?? "") + RoughTokenEstimate(JSON.stringify(tool.inputSchema));
1171
- }
1172
- function estimateRequestTokens(messages, systemPrompt, tools, calibrationKey = CALIBRATION_GLOBAL_KEY) {
1173
- let messagesTokens = 0;
1174
- if (typeof messages === "string") {
1175
- messagesTokens = RoughTokenEstimate(messages);
1176
- } else if (Array.isArray(messages)) {
1177
- for (const m of messages) {
1178
- if (typeof m === "object" && m !== null && "content" in m) {
1179
- const cached = m._estTokens;
1180
- if (typeof cached === "number" && cached > 0) {
1181
- messagesTokens += cached;
1182
- continue;
1183
- }
1184
- const content = m.content;
1185
- if (typeof content === "string") {
1186
- messagesTokens += RoughTokenEstimate(content);
1187
- } else if (Array.isArray(content)) {
1188
- for (const b of content) {
1189
- if (typeof b === "object" && b !== null) {
1190
- if (b.type === "text") {
1191
- messagesTokens += RoughTokenEstimate(b.text);
1192
- } else {
1193
- messagesTokens += RoughTokenEstimate(JSON.stringify(b));
1194
- }
1195
- }
1196
- }
1197
- }
1198
- }
1199
- }
1200
- }
1201
- let systemTokens = 0;
1202
- if (typeof systemPrompt === "string") {
1203
- systemTokens = RoughTokenEstimate(systemPrompt);
1204
- } else if (Array.isArray(systemPrompt)) {
1205
- for (const b of systemPrompt) {
1206
- if (typeof b === "object" && b !== null && b.type === "text") {
1207
- systemTokens += RoughTokenEstimate(b.text);
1208
- }
1209
- }
1210
- }
1211
- let toolsTokens = 0;
1212
- for (const t of tools) {
1213
- toolsTokens += estimateToolDefTokens(t);
1214
- }
1215
- const total = messagesTokens + systemTokens + toolsTokens;
1216
- calState(calibrationKey).prevEst = total;
1217
- return {
1218
- messages: messagesTokens,
1219
- systemPrompt: systemTokens,
1220
- tools: toolsTokens,
1221
- total
1222
- };
1223
- }
1224
-
1225
- // src/utils/expect-defined.ts
1226
- function expectDefined(value, label) {
1227
- if (value === null || value === void 0) {
1228
- const err = new Error("Expected value to be defined");
1229
- err.name = "ExpectDefinedError";
1230
- throw err;
1231
- }
1232
- return value;
1233
- }
1234
-
1235
- // src/utils/message-invariants.ts
1236
- function repairToolUseAdjacency(messages) {
1237
- const removedToolUses = [];
1238
- const removedToolResults = [];
1239
- let removedMessages = 0;
1240
- let changed = false;
1241
- const out = [];
1242
- for (let i = 0; i < messages.length; i++) {
1243
- const original = expectDefined(messages[i]);
1244
- let msg = original;
1245
- if (hasToolUse(msg)) {
1246
- const nextIds = toolResultIds(messages[i + 1]);
1247
- const filtered = mapContent(msg, (blocks) => {
1248
- const next = [];
1249
- for (const block of blocks) {
1250
- if (block.type === "tool_use" && !nextIds.has(block.id)) {
1251
- removedToolUses.push(block.id);
1252
- changed = true;
1253
- continue;
1254
- }
1255
- next.push(block);
1256
- }
1257
- return next;
1258
- });
1259
- msg = filtered ?? msg;
1260
- }
1261
- if (hasToolResult(msg)) {
1262
- const allowed = toolUseIds(out[out.length - 1]);
1263
- const filtered = mapContent(msg, (blocks) => {
1264
- const next = [];
1265
- for (const block of blocks) {
1266
- if (block.type === "tool_result" && !allowed.has(block.tool_use_id)) {
1267
- removedToolResults.push(block.tool_use_id);
1268
- changed = true;
1269
- continue;
1270
- }
1271
- next.push(block);
1272
- }
1273
- return next;
1274
- });
1275
- msg = filtered ?? msg;
1276
- }
1277
- if (isEmptyMessage(msg)) {
1278
- removedMessages++;
1279
- changed = true;
1280
- continue;
1281
- }
1282
- out.push(msg);
1283
- }
1284
- return {
1285
- messages: changed ? out : messages,
1286
- report: { changed, removedToolUses, removedToolResults, removedMessages }
1287
- };
1288
- }
1289
- function hasToolUse(msg) {
1290
- return contentBlocks(msg).some((b) => b.type === "tool_use");
1291
- }
1292
- function hasToolResult(msg) {
1293
- return contentBlocks(msg).some((b) => b.type === "tool_result");
1294
- }
1295
- function toolUseIds(msg) {
1296
- const ids = /* @__PURE__ */ new Set();
1297
- if (!msg || msg.role !== "assistant") return ids;
1298
- for (const block of contentBlocks(msg)) {
1299
- if (block.type === "tool_use") ids.add(block.id);
1300
- }
1301
- return ids;
1302
- }
1303
- function toolResultIds(msg) {
1304
- const ids = /* @__PURE__ */ new Set();
1305
- if (!msg || msg.role !== "user") return ids;
1306
- for (const block of contentBlocks(msg)) {
1307
- if (block.type === "tool_result") ids.add(block.tool_use_id);
1308
- }
1309
- return ids;
1310
- }
1311
- function contentBlocks(msg) {
1312
- return msg && Array.isArray(msg.content) ? msg.content : [];
1313
- }
1314
- function mapContent(msg, fn) {
1315
- if (!Array.isArray(msg.content)) return msg;
1316
- const next = fn(msg.content);
1317
- if (next.length === msg.content.length && next.every((b, idx) => b === msg.content[idx])) {
1318
- return msg;
1319
- }
1320
- return { ...msg, content: next };
1321
- }
1322
- function isEmptyMessage(msg) {
1323
- if (typeof msg.content === "string") return msg.content.trim().length === 0;
1324
- return msg.content.length === 0;
1325
- }
1326
-
1327
1741
  // src/execution/compaction-core.ts
1742
+ function compactionDebugEnabled() {
1743
+ return process.env["NODE_ENV"] === "development" || process.env["WRONGSTACK_DEBUG"] === "1";
1744
+ }
1328
1745
  function emitCompactionMetrics(event, metrics) {
1746
+ if (!compactionDebugEnabled()) return;
1329
1747
  console.log(
1330
1748
  JSON.stringify({
1331
1749
  level: "debug",
@@ -1380,18 +1798,20 @@ function findPreserveStart(messages, preserveK) {
1380
1798
  }
1381
1799
  }
1382
1800
  }
1383
- console.log(
1384
- JSON.stringify({
1385
- level: "debug",
1386
- event: "compaction.find_preserve_start.ended",
1387
- messageCount: messages.length,
1388
- preserveK,
1389
- preserveStart,
1390
- forwardWalkIterations,
1391
- forwardWalkInnerIterations,
1392
- forwardWalkInnerPerOuter: forwardWalkIterations > 0 ? forwardWalkInnerIterations / forwardWalkIterations : 0
1393
- })
1394
- );
1801
+ if (compactionDebugEnabled()) {
1802
+ console.log(
1803
+ JSON.stringify({
1804
+ level: "debug",
1805
+ event: "compaction.find_preserve_start.ended",
1806
+ messageCount: messages.length,
1807
+ preserveK,
1808
+ preserveStart,
1809
+ forwardWalkIterations,
1810
+ forwardWalkInnerIterations,
1811
+ forwardWalkInnerPerOuter: forwardWalkIterations > 0 ? forwardWalkInnerIterations / forwardWalkIterations : 0
1812
+ })
1813
+ );
1814
+ }
1395
1815
  return preserveStart;
1396
1816
  }
1397
1817
  function eliseOldToolResults(messages, opts) {
@@ -1458,7 +1878,7 @@ function eliseOldToolResults(messages, opts) {
1458
1878
  changed = true;
1459
1879
  }
1460
1880
  fullPassInnerIterations += original.length;
1461
- if (process.env["NODE_ENV"] === "development" || process.env["WRONGSTACK_DEBUG"] === "1") {
1881
+ if (compactionDebugEnabled()) {
1462
1882
  const ratio = fullPassInnerIterations / fullPassIterations;
1463
1883
  if (ratio > 10) {
1464
1884
  console.error(
@@ -1992,6 +2412,27 @@ var PATTERNS = [
1992
2412
  { type: "postgres_uri", regex: /postgres(?:ql)?:\/\/[^\s"'`]+/g },
1993
2413
  { type: "mysql_uri", regex: /mysql:\/\/[^\s"'`]+/g },
1994
2414
  { type: "redis_uri", regex: /redis:\/\/[^\s"'`]+/g },
2415
+ // AI/ML provider keys — modern LLM services with well-known prefixes
2416
+ {
2417
+ type: "huggingface_token",
2418
+ // HuggingFace tokens: hf_ followed by 34 alphanumeric chars
2419
+ regex: /(?<![A-Za-z0-9])hf_[A-Za-z0-9]{34}(?![A-Za-z0-9])/g
2420
+ },
2421
+ {
2422
+ type: "replicate_token",
2423
+ // Replicate tokens: r8_ followed by 40+ alphanumeric chars
2424
+ regex: /(?<![A-Za-z0-9])r8_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g
2425
+ },
2426
+ {
2427
+ type: "perplexity_key",
2428
+ // Perplexity API keys: pplx- followed by 40+ alphanumeric chars
2429
+ regex: /(?<![A-Za-z0-9])pplx-[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g
2430
+ },
2431
+ {
2432
+ type: "groq_key",
2433
+ // Groq API keys: gsk_ followed by 40+ alphanumeric chars
2434
+ regex: /(?<![A-Za-z0-9])gsk_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g
2435
+ },
1995
2436
  {
1996
2437
  type: "bearer_token",
1997
2438
  // Anchored with alternation instead of negative lookahead — avoids V8
@@ -2025,6 +2466,10 @@ function hasCredentialAnchors(text) {
2025
2466
  text.includes("xox") || // Slack token (xoxa/xoxb/xoxp/xoxo/xoxs)
2026
2467
  text.includes("Bearer ") || // Bearer token (space suffix reduces false positives)
2027
2468
  text.includes("/bot") || // Telegram bot token (URL path pattern)
2469
+ text.includes("hf_") || // HuggingFace token
2470
+ text.includes("r8_") || // Replicate token
2471
+ text.includes("pplx-") || // Perplexity API key
2472
+ text.includes("gsk_") || // Groq API key
2028
2473
  text.includes("_KEY=") || // High-entropy env vars: API_KEY=, SECRET_KEY=, ...
2029
2474
  text.includes("_TOKEN=") || // ACCESS_TOKEN=, AUTH_TOKEN=, ...
2030
2475
  text.includes("_SECRET=") || // API_SECRET=, CLIENT_SECRET=, ...
@@ -2091,71 +2536,6 @@ var DefaultSecretScrubber = class {
2091
2536
  return visit(obj);
2092
2537
  }
2093
2538
  };
2094
-
2095
- // src/utils/merge-models-payload.ts
2096
- function mergeModelsPayload(base, overlay) {
2097
- const out = {};
2098
- for (const [id, provider] of Object.entries(base)) {
2099
- out[id] = cloneProvider(provider);
2100
- }
2101
- for (const [id, ovProvider] of Object.entries(overlay)) {
2102
- const existing = out[id];
2103
- out[id] = existing ? mergeProvider(existing, ovProvider) : cloneProvider(ovProvider);
2104
- }
2105
- return out;
2106
- }
2107
- function mergeProvider(base, overlay) {
2108
- const models = {};
2109
- for (const [mid, m] of Object.entries(base.models ?? {})) {
2110
- models[mid] = { ...m };
2111
- }
2112
- for (const [mid, ovModel] of Object.entries(overlay.models ?? {})) {
2113
- const existing = models[mid];
2114
- models[mid] = existing ? mergeModel(existing, ovModel) : { ...ovModel };
2115
- }
2116
- return {
2117
- ...base,
2118
- // Overlay scalar fields win when explicitly provided; otherwise keep base.
2119
- ...stripUndefined({
2120
- id: overlay.id,
2121
- name: overlay.name,
2122
- npm: overlay.npm,
2123
- api: overlay.api,
2124
- env: overlay.env,
2125
- doc: overlay.doc
2126
- }),
2127
- models
2128
- };
2129
- }
2130
- function mergeModel(base, overlay) {
2131
- const merged = { ...base, ...overlay };
2132
- if (base.limit || overlay.limit) {
2133
- merged.limit = { ...base.limit, ...overlay.limit };
2134
- }
2135
- if (base.cost || overlay.cost) {
2136
- merged.cost = { ...base.cost, ...overlay.cost };
2137
- }
2138
- if (base.modalities || overlay.modalities) {
2139
- merged.modalities = { ...base.modalities, ...overlay.modalities };
2140
- }
2141
- return merged;
2142
- }
2143
- function cloneProvider(p) {
2144
- const models = {};
2145
- for (const [mid, m] of Object.entries(p.models ?? {})) {
2146
- models[mid] = { ...m };
2147
- }
2148
- return { ...p, models };
2149
- }
2150
- function stripUndefined(obj) {
2151
- const out = {};
2152
- for (const [k, v] of Object.entries(obj)) {
2153
- if (v !== void 0) out[k] = v;
2154
- }
2155
- return out;
2156
- }
2157
-
2158
- // src/models/models-registry.ts
2159
2539
  var DEFAULT_URL = "https://models.dev/api.json";
2160
2540
  var DEFAULT_TTL_SECONDS = 24 * 3600;
2161
2541
  var DEFAULT_REFRESH_TIMEOUT_MS = 15e3;
@@ -2251,7 +2631,7 @@ var DefaultModelsRegistry = class {
2251
2631
  }
2252
2632
  if (overlayAvailable) {
2253
2633
  console.warn(
2254
- `ModelsRegistry: models.dev unavailable (${err instanceof Error ? err.message : String(err)}); serving curated overlay only.`
2634
+ `ModelsRegistry: models.dev unavailable (${toErrorMessage(err)}); serving curated overlay only.`
2255
2635
  );
2256
2636
  return {};
2257
2637
  }
@@ -3135,144 +3515,6 @@ function getDangerousCapabilities(toolOrCaps) {
3135
3515
  );
3136
3516
  }
3137
3517
 
3138
- // src/utils/json-schema-validate.ts
3139
- function validateAgainstSchema(value, schema) {
3140
- const errors = [];
3141
- walk2(value, schema, "", errors);
3142
- return { ok: errors.length === 0, errors };
3143
- }
3144
- function walk2(value, schema, path7, errors) {
3145
- if (schema.enum !== void 0) {
3146
- if (!schema.enum.some((e) => deepEqual(e, value))) {
3147
- errors.push({
3148
- path: path7 || "<root>",
3149
- message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
3150
- });
3151
- return;
3152
- }
3153
- }
3154
- if (typeof schema.type === "string") {
3155
- if (!checkType(value, schema.type)) {
3156
- errors.push({
3157
- path: path7 || "<root>",
3158
- message: `expected ${schema.type}, got ${describeType(value)}`
3159
- });
3160
- return;
3161
- }
3162
- }
3163
- if (schema.type === "object" && isPlainObject(value)) {
3164
- const obj = value;
3165
- for (const req of schema.required ?? []) {
3166
- if (!(req in obj)) {
3167
- errors.push({ path: joinPath(path7, req), message: "required property missing" });
3168
- }
3169
- }
3170
- if (schema.properties) {
3171
- for (const [key, subSchema] of Object.entries(schema.properties)) {
3172
- if (key in obj) {
3173
- walk2(obj[key], subSchema, joinPath(path7, key), errors);
3174
- }
3175
- }
3176
- }
3177
- }
3178
- if (schema.type === "array" && Array.isArray(value) && schema.items) {
3179
- value.forEach((item, i) => walk2(item, schema.items, `${path7}[${i}]`, errors));
3180
- }
3181
- }
3182
- function checkType(value, type) {
3183
- switch (type) {
3184
- case "string":
3185
- return typeof value === "string";
3186
- case "number":
3187
- return typeof value === "number" && !Number.isNaN(value);
3188
- case "integer":
3189
- return typeof value === "number" && Number.isInteger(value);
3190
- case "boolean":
3191
- return typeof value === "boolean";
3192
- case "null":
3193
- return value === null;
3194
- case "array":
3195
- return Array.isArray(value);
3196
- case "object":
3197
- return isPlainObject(value);
3198
- default:
3199
- return true;
3200
- }
3201
- }
3202
- function isPlainObject(v) {
3203
- return typeof v === "object" && v !== null && !Array.isArray(v);
3204
- }
3205
- function describeType(v) {
3206
- if (v === null) return "null";
3207
- if (Array.isArray(v)) return "array";
3208
- return typeof v;
3209
- }
3210
- function joinPath(parent, key) {
3211
- if (!parent) return key;
3212
- return `${parent}.${key}`;
3213
- }
3214
- function deepEqual(a, b) {
3215
- if (a === b) return true;
3216
- if (typeof a !== typeof b) return false;
3217
- if (a === null || b === null) return a === b;
3218
- if (Array.isArray(a) && Array.isArray(b)) {
3219
- return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
3220
- }
3221
- if (typeof a === "object" && typeof b === "object") {
3222
- const ak = Object.keys(a);
3223
- const bk = Object.keys(b);
3224
- if (ak.length !== bk.length) return false;
3225
- return ak.every(
3226
- (k) => deepEqual(a[k], b[k])
3227
- );
3228
- }
3229
- return false;
3230
- }
3231
-
3232
- // src/utils/tool-output-serializer.ts
3233
- function createToolOutputSerializer(opts = {}) {
3234
- const capBytes = opts.perIterationOutputCapBytes ?? 1e5;
3235
- function serialize(value) {
3236
- if (typeof value === "string") return value;
3237
- if (value === null || value === void 0) return "";
3238
- if (typeof value === "object") {
3239
- if (Array.isArray(value)) return value.map(serialize).join("\n");
3240
- if ("text" in value) {
3241
- const t = value.text;
3242
- return typeof t === "string" ? t : JSON.stringify(value, null, 2);
3243
- }
3244
- try {
3245
- return JSON.stringify(value, null, 2);
3246
- } catch {
3247
- return String(value);
3248
- }
3249
- }
3250
- return String(value);
3251
- }
3252
- function enforceCap(text, remainingBudget) {
3253
- if (remainingBudget <= 0) {
3254
- return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
3255
- }
3256
- const textBytes = Buffer.byteLength(text, "utf8");
3257
- if (textBytes <= remainingBudget) {
3258
- return { text, newBudget: remainingBudget - textBytes };
3259
- }
3260
- const marker = `
3261
- \u2026[truncated ${textBytes - remainingBudget} bytes]\u2026
3262
- `;
3263
- const markerBytes = Buffer.byteLength(marker, "utf8");
3264
- const available = remainingBudget - markerBytes;
3265
- if (available <= 0) {
3266
- return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
3267
- }
3268
- const half = Math.floor(available / 2);
3269
- const first = text.slice(0, half);
3270
- const second = text.slice(text.length - half);
3271
- return { text: `${first}${marker}${second}`, newBudget: 0 };
3272
- }
3273
- return { serialize, enforceCap, capBytes };
3274
- }
3275
-
3276
3518
  // src/execution/tool-executor.ts
3277
3519
  var ToolExecutor = class _ToolExecutor {
3278
3520
  constructor(registry, opts) {
@@ -3436,7 +3678,7 @@ ${post.additionalContext}`;
3436
3678
  );
3437
3679
  return { result, tool, durationMs: Date.now() - start };
3438
3680
  } catch (err) {
3439
- const msg = err instanceof Error ? err.message : String(err);
3681
+ const msg = toErrorMessage(err);
3440
3682
  const scrubbed = this.opts.secretScrubber.scrub(msg);
3441
3683
  this.opts.renderer?.writeToolResult(tool.name, scrubbed, true);
3442
3684
  const result = {
@@ -3457,7 +3699,7 @@ ${post.additionalContext}`;
3457
3699
  try {
3458
3700
  return await runOne(use);
3459
3701
  } catch (err) {
3460
- const msg = err instanceof Error ? err.message : String(err);
3702
+ const msg = toErrorMessage(err);
3461
3703
  const scrubbed = this.opts.secretScrubber.scrub(msg);
3462
3704
  const result = {
3463
3705
  type: "tool_result",
@@ -3866,6 +4108,13 @@ var Context = class {
3866
4108
  projectRoot;
3867
4109
  /** Mutable working directory — starts as `cwd`. Change via `setWorkingDir()`. */
3868
4110
  workingDir;
4111
+ /**
4112
+ * When true, file tools (via `_util.ts`) and `setWorkingDir()` reject paths
4113
+ * outside `projectRoot`. When false, those boundary checks are bypassed so
4114
+ * tools may reach paths outside the project (still gated by permission
4115
+ * tiers). Mutable so `/settings` can toggle it live on the running session.
4116
+ */
4117
+ allowOutsideProjectRoot;
3869
4118
  model;
3870
4119
  tools = [];
3871
4120
  meta = {};
@@ -3910,11 +4159,13 @@ var Context = class {
3910
4159
  this.cwd = init.cwd;
3911
4160
  this.projectRoot = init.projectRoot;
3912
4161
  this.workingDir = init.workingDir ?? init.cwd;
4162
+ this.allowOutsideProjectRoot = init.allowOutsideProjectRoot ?? false;
3913
4163
  this.model = init.model;
3914
4164
  this.tools = init.tools ?? [];
3915
4165
  this.agentId = init.agentId ?? "unknown";
3916
4166
  this.agentName = init.agentName ?? "Unknown Agent";
3917
4167
  this.traceId = init.traceId;
4168
+ this.allowOutsideProjectRoot = init.allowOutsideProjectRoot ?? false;
3918
4169
  this.session.traceId = init.traceId;
3919
4170
  }
3920
4171
  /**
@@ -3980,12 +4231,14 @@ var Context = class {
3980
4231
  */
3981
4232
  setWorkingDir(dir) {
3982
4233
  const resolved = path4.isAbsolute(dir) ? path4.resolve(dir) : path4.resolve(this.projectRoot, dir);
3983
- const root = path4.resolve(this.projectRoot);
3984
- const rel = path4.relative(root, resolved);
3985
- if (rel.startsWith("..") || path4.isAbsolute(rel)) {
3986
- throw new Error(
3987
- `Working directory "${resolved}" is outside project root "${root}"`
3988
- );
4234
+ if (!this.allowOutsideProjectRoot) {
4235
+ const root = path4.resolve(this.projectRoot);
4236
+ const rel = path4.relative(root, resolved);
4237
+ if (rel.startsWith("..") || path4.isAbsolute(rel)) {
4238
+ throw new Error(
4239
+ `Working directory "${resolved}" is outside project root "${root}"`
4240
+ );
4241
+ }
3989
4242
  }
3990
4243
  const old = this.workingDir;
3991
4244
  this.workingDir = resolved;
@@ -4015,42 +4268,6 @@ var Context = class {
4015
4268
  }
4016
4269
  };
4017
4270
 
4018
- // src/utils/regex-guard.ts
4019
- var MAX_PATTERN_LEN = 512;
4020
- var DANGEROUS_PATTERNS = [
4021
- /(\([^)]*[+*][^)]*\))[+*]/,
4022
- // (a+)+, (.*)+, etc
4023
- /(\(\?:[^)]*[+*][^)]*\))[+*]/
4024
- // same, with non-capturing group
4025
- ];
4026
- function compileUserRegex(pattern, flags) {
4027
- if (typeof pattern !== "string") {
4028
- return { ok: false, reason: "pattern must be a string" };
4029
- }
4030
- if (pattern.length === 0) {
4031
- return { ok: false, reason: "pattern is empty" };
4032
- }
4033
- if (pattern.length > MAX_PATTERN_LEN) {
4034
- return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
4035
- }
4036
- for (const rx of DANGEROUS_PATTERNS) {
4037
- if (rx.test(pattern)) {
4038
- return {
4039
- ok: false,
4040
- reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
4041
- };
4042
- }
4043
- }
4044
- try {
4045
- return { ok: true, regex: new RegExp(pattern, flags) };
4046
- } catch (err) {
4047
- return {
4048
- ok: false,
4049
- reason: err instanceof Error ? err.message : "invalid regex"
4050
- };
4051
- }
4052
- }
4053
-
4054
4271
  // src/storage/session-reader.ts
4055
4272
  var DefaultSessionReader = class {
4056
4273
  store;
@@ -4327,6 +4544,6 @@ function renderPlainText(meta, events) {
4327
4544
  return lines.join("\n");
4328
4545
  }
4329
4546
 
4330
- export { AgentError, CONTEXT_WINDOW_MODES, ConfigError, Context, DEFAULT_AUTONOMY_CONFIG, DEFAULT_CONTEXT_CONFIG, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEFAULT_MODES, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SESSION_LOGGING_CONFIG, DEFAULT_SESSION_PRUNE_DAYS, DEFAULT_SPEC_TEMPLATE, DEFAULT_TOOLS_CONFIG, DefaultErrorHandler, DefaultLogger, DefaultModelsRegistry, DefaultPathResolver, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultTokenCounter, ERROR_CODES, FsError, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, MEMORY_TYPE_LABELS, PluginError, ProviderError, SddError, SessionError, StreamHangError, ToolError, ToolExecutor, WrongStackError, asBlocks, asText, buildRecoveryStrategies, classifyFamily, computeTaskProgress, createMessage, decryptConfigSecrets, encryptConfigSecrets, findCriticalPath, formatContextWindowModeList, getContextWindowMode, isAgentError, isConfigError, isContextWindowModeId, isFsError, isImageBlock, isPluginError, isSddError, isSecretField, isSessionError, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isWrongStackError, listContextWindowModes, migratePlaintextSecrets, noOpLogger, resolveContextWindowPolicy, rewriteConfigEncrypted, toWrongStackError, topologicalSort };
4547
+ export { AgentError, CONTEXT_WINDOW_MODES, ConfigError, Context, DEFAULT_AUTONOMY_CONFIG, DEFAULT_CIRCUIT_BREAKER_CONFIG, DEFAULT_CONTEXT_CONFIG, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEFAULT_MODES, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SESSION_LOGGING_CONFIG, DEFAULT_SESSION_PRUNE_DAYS, DEFAULT_SPEC_TEMPLATE, DEFAULT_TOOLS_CONFIG, DefaultErrorHandler, DefaultLogger, DefaultModelsRegistry, DefaultPathResolver, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultTokenCounter, ERROR_CODES, FsError, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, MEMORY_TYPE_LABELS, PluginError, ProviderError, SddError, SessionError, StreamHangError, ToolError, ToolExecutor, WrongStackError, asBlocks, asText, buildRecoveryStrategies, classifyFamily, computeTaskProgress, createMessage, decryptConfigSecrets, encryptConfigSecrets, findCriticalPath, formatContextWindowModeList, getContextWindowMode, isAgentError, isConfigError, isContextWindowModeId, isFsError, isImageBlock, isPluginError, isSddError, isSecretField, isSessionError, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isWrongStackError, listContextWindowModes, migratePlaintextSecrets, noOpLogger, normalizeTokenSavingTier, resolveContextWindowPolicy, rewriteConfigEncrypted, rotateConfigKeys, toWrongStackError, topologicalSort };
4331
4548
  //# sourceMappingURL=index.js.map
4332
4549
  //# sourceMappingURL=index.js.map