@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.
- package/dist/{agent-bridge-BbskZ7HH.d.ts → agent-bridge-DrkBxszZ.d.ts} +1 -1
- package/dist/{agent-subagent-runner-BNIGZx18.d.ts → agent-subagent-runner-DM2pP-B6.d.ts} +116 -12
- package/dist/{brain-C2yDd7Lw.d.ts → brain-BXd_61kQ.d.ts} +32 -3
- package/dist/{compactor-t0R_AIt_.d.ts → compactor-B8pOf45Y.d.ts} +1 -1
- package/dist/{config-FG6As4H5.d.ts → config-BMCj_XDs.d.ts} +86 -12
- package/dist/{context-JFOVvu6z.d.ts → context-MRk5PhNv.d.ts} +26 -1
- package/dist/coordination/index.d.ts +1737 -15
- package/dist/coordination/index.js +3152 -494
- package/dist/coordination/index.js.map +1 -1
- package/dist/{default-config-CXsDvOmP.d.ts → default-config-B0cj-Hry.d.ts} +11 -1
- package/dist/defaults/index.d.ts +28 -28
- package/dist/defaults/index.js +1804 -1363
- package/dist/defaults/index.js.map +1 -1
- package/dist/dispatcher-types.d-BBeXBQgS.d.ts +66 -0
- package/dist/execution/index.d.ts +16 -16
- package/dist/execution/index.js +933 -672
- package/dist/execution/index.js.map +1 -1
- package/dist/execution/prompt-enhancer.d.ts +1 -1
- package/dist/execution/prompt-enhancer.js +7 -1
- package/dist/execution/prompt-enhancer.js.map +1 -1
- package/dist/extension/index.d.ts +6 -6
- package/dist/extension/index.js.map +1 -1
- package/dist/{goal-preamble-B1IXJtLX.d.ts → goal-preamble-DvHDSKSe.d.ts} +26 -10
- package/dist/{goal-store-CPXz6Mml.d.ts → goal-store-DtLMySNb.d.ts} +1 -1
- package/dist/{index-CebbJB94.d.ts → index-B-ch8K9C.d.ts} +8 -8
- package/dist/{index-BPcg4N3M.d.ts → index-CEDeNodM.d.ts} +5 -5
- package/dist/index.d.ts +189 -104
- package/dist/index.js +24693 -21162
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +6 -6
- package/dist/infrastructure/index.js +12 -8
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +9 -9
- package/dist/kernel/index.js +7 -2
- package/dist/kernel/index.js.map +1 -1
- package/dist/{llm-selector-DXxI2tlu.d.ts → llm-selector-C0tfTCUe.d.ts} +14 -2
- package/dist/{mcp-servers-OwNHo43-.d.ts → mcp-servers-2x4w6Jn9.d.ts} +3 -3
- package/dist/models/index.d.ts +5 -5
- package/dist/models/index.js +80 -31
- package/dist/models/index.js.map +1 -1
- package/dist/{models-registry-Djlmq4uB.d.ts → models-registry-DmJlKuNp.d.ts} +1 -1
- package/dist/{multi-agent-coordinator-CEmrSCMJ.d.ts → multi-agent-coordinator-DyCkCZnU.d.ts} +2 -2
- package/dist/{null-fleet-bus-DT92xqgJ.d.ts → null-fleet-bus-CG9QY2aP.d.ts} +6 -6
- package/dist/observability/index.d.ts +2 -2
- package/dist/observability/index.js +8 -3
- package/dist/observability/index.js.map +1 -1
- package/dist/{parallel-eternal-engine-0SItuq5r.d.ts → parallel-eternal-engine-Jw9uhEoT.d.ts} +9 -9
- package/dist/{path-resolver-DKBh6Jlo.d.ts → path-resolver-Dy2ej-gE.d.ts} +3 -3
- package/dist/{permission-BJ7eO9Vl.d.ts → permission-B9SB45lp.d.ts} +1 -1
- package/dist/{permission-policy-DEXOfnpm.d.ts → permission-policy-CkjSXabK.d.ts} +2 -2
- package/dist/{pipeline-zflkI2dp.d.ts → pipeline-DPDxH_7m.d.ts} +59 -4
- package/dist/{plan-templates-BFXyRkEK.d.ts → plan-templates-CzD9GnAU.d.ts} +32 -8
- package/dist/{provider-runner-BC-uywtT.d.ts → provider-runner-DMa70ODu.d.ts} +3 -3
- package/dist/{retry-policy-Cavrzmtk.d.ts → retry-policy-CN0khdlj.d.ts} +1 -1
- package/dist/sdd/index.d.ts +8 -8
- package/dist/sdd/index.js +313 -122
- package/dist/sdd/index.js.map +1 -1
- package/dist/{secret-vault-CDvDYXWX.d.ts → secret-vault-B2yw84VT.d.ts} +43 -4
- package/dist/secret-vault-BAKpgFw_.d.ts +57 -0
- package/dist/security/index.d.ts +5 -5
- package/dist/security/index.js +411 -225
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-B7AivHsu.d.ts → selector-CzHh_igB.d.ts} +1 -1
- package/dist/{session-event-bridge-BmIDxdJd.d.ts → session-event-bridge-BUI6Jf-4.d.ts} +8 -2
- package/dist/{session-reader-DtofsB-2.d.ts → session-reader-CMgdMSRP.d.ts} +1 -1
- package/dist/skills/index.js +67 -64
- package/dist/skills/index.js.map +1 -1
- package/dist/storage/index.d.ts +132 -16
- package/dist/storage/index.js +851 -432
- package/dist/storage/index.js.map +1 -1
- package/dist/tools/index.d.ts +57 -0
- package/dist/tools/index.js +411 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/types/index.d.ts +21 -21
- package/dist/types/index.js +928 -711
- package/dist/types/index.js.map +1 -1
- package/dist/utils/error.d.ts +7 -0
- package/dist/utils/error.js +8 -0
- package/dist/utils/error.js.map +1 -0
- package/dist/utils/index.d.ts +8 -68
- package/dist/utils/index.js +20 -10
- package/dist/utils/index.js.map +1 -1
- package/dist/{wstack-paths-CJjEwPXn.d.ts → wstack-paths-hOpNLmvf.d.ts} +2 -0
- package/package.json +5 -1
- package/skills/api-design/SKILL.md +1 -1
- package/skills/audit-log/SKILL.md +6 -6
- package/skills/bug-hunter/SKILL.md +5 -5
- package/skills/chimera/SKILL.md +4 -4
- package/skills/docker-deploy/SKILL.md +1 -1
- package/skills/git-flow/SKILL.md +3 -3
- package/skills/multi-agent/SKILL.md +3 -3
- package/skills/node-modern/SKILL.md +1 -0
- package/skills/observability/SKILL.md +2 -2
- package/skills/output-standards/SKILL.md +51 -28
- package/skills/refactor-planner/SKILL.md +3 -3
- package/skills/security-scanner/SKILL.md +4 -3
- package/skills/tech-stack/SKILL.md +1 -2
- package/dist/package-outdated-watcher-C70ag2G9.d.ts +0 -581
- package/dist/secret-vault-BJDY28ev.d.ts +0 -25
package/dist/types/index.js
CHANGED
|
@@ -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
|
|
29
|
-
|
|
30
|
-
|
|
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 =
|
|
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
|
|
391
|
-
|
|
392
|
-
|
|
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" &&
|
|
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
|
-
|
|
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
|
|
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
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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
|
-
|
|
598
|
-
|
|
599
|
-
|
|
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
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
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
|
-
|
|
618
|
-
|
|
619
|
-
|
|
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
|
|
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
|
|
1255
|
+
return walk2(cfg, vault, (v) => vault.encrypt(v));
|
|
640
1256
|
}
|
|
641
|
-
function
|
|
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) =>
|
|
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] =
|
|
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
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
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 (
|
|
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 (${
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
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
|